diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml
index 9d03667..27feff2 100644
--- a/.gitea/workflows/deploy.yml
+++ b/.gitea/workflows/deploy.yml
@@ -12,16 +12,13 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v3
- - name: Set up Node.js
- uses: actions/setup-node@v3
- with:
- node-version: '20'
+ - name: Install Docker
+ uses: docker/setup-buildx-action@v2
- - name: Install dependencies
- run: npm install
+ - name: Build Docker image
+ run: docker build -t markdownblog .
- - name: Build project
- run: npm run build
+ - name: Push Docker image
+ run: docker push registry.gitea.com/user/markdownblog:latest
- - name: Start application
- run: npm start -- --port 1337
\ No newline at end of file
+ - name: Deploy to Gitea
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
index bda7c05..895faeb 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -14,6 +14,10 @@ VOLUME ["/app/docker"]
EXPOSE 3000
+COPY entrypoint.sh /entrypoint.sh
+RUN chmod +x /entrypoint.sh
+ENTRYPOINT ["/entrypoint.sh"]
+
CMD ["npm", "start"]
# Building Instructions
diff --git a/config.ecosystem.js b/config.ecosystem.js
deleted file mode 100644
index 3afcd55..0000000
--- a/config.ecosystem.js
+++ /dev/null
@@ -1,21 +0,0 @@
-module.exports = {
- apps: [
- {
- name: "markdownblog",
- script: "node_modules/next/dist/bin/next",
- args: "start",
- instances: 1, // Use 1 unless you have a reverse proxy
- autorestart: true,
- watch: false,
- max_memory_restart: "1G",
- env: {
- NODE_ENV: "development"
- },
- env_production: {
- NODE_ENV: "production",
- PORT: 3000,
- HOST: "0.0.0.0"
- }
- }
- ]
-};
diff --git a/docker.sh b/docker.sh
new file mode 100755
index 0000000..4ab52b0
--- /dev/null
+++ b/docker.sh
@@ -0,0 +1,36 @@
+#!/usr/bin/env bash
+
+set -e
+
+IMAGE_NAME="markdownblog"
+CONTAINER_NAME="markdownblog"
+VOLUME_NAME="markdownblog-posts"
+PORT="8080"
+
+# Stop and remove any containers using the volume
+VOLUME_CONTAINERS=$(docker ps -a --filter volume=$VOLUME_NAME -q)
+if [ -n "$VOLUME_CONTAINERS" ]; then
+ echo "Stopping and removing containers using the volume..."
+ docker rm -f $VOLUME_CONTAINERS
+fi
+
+# Remove the volume for a clean start (optional, comment out if you want to keep posts)
+echo "Removing Docker volume (for a clean start)..."
+docker volume rm $VOLUME_NAME 2>/dev/null || true
+
+echo "Building Docker image..."
+docker build -t $IMAGE_NAME .
+
+echo "Running new container with persistent volume..."
+docker run -d \
+ --name $CONTAINER_NAME \
+ -p $PORT:3000 \
+ -v $VOLUME_NAME:/app/docker \
+ $IMAGE_NAME
+
+# Copy built-in posts to the volume if it's empty (one-time init)
+echo "Copying built-in posts to Docker volume if empty..."
+docker exec $CONTAINER_NAME sh -c 'if [ -d /app/posts ] && [ -d /app/docker ] && [ "$(ls -A /app/docker)" = "" ]; then cp -r /app/posts/* /app/docker/; fi'
+
+echo "Deployment complete!"
+echo "App should be available at http://localhost:$PORT"
diff --git a/entrypoint.sh b/entrypoint.sh
new file mode 100644
index 0000000..0c61c61
--- /dev/null
+++ b/entrypoint.sh
@@ -0,0 +1,6 @@
+#!/bin/sh
+# Copy built-in posts to docker volume if the volume is empty
+if [ -d /app/posts ] && [ -d /app/docker ] && [ "$(ls -A /app/docker)" = "" ]; then
+ cp -r /app/posts/* /app/docker/
+fi
+exec "$@"
diff --git a/package-lock.json b/package-lock.json
index f293768..0806d80 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -36,6 +36,7 @@
"@types/dompurify": "^3.0.5",
"@types/jsdom": "^21.1.6",
"@types/marked": "^5.0.2",
+ "@types/tar": "^6.1.13",
"concurrently": "^8.2.2",
"eslint": "^8.57.0",
"eslint-config-next": "14.1.0"
@@ -1245,6 +1246,27 @@
"@types/node": "*"
}
},
+ "node_modules/@types/tar": {
+ "version": "6.1.13",
+ "resolved": "https://registry.npmjs.org/@types/tar/-/tar-6.1.13.tgz",
+ "integrity": "sha512-IznnlmU5f4WcGTh2ltRu/Ijpmk8wiWXfF0VA4s+HPjHZgvFggk1YaIkbo5krX/zUCzWF8N/l4+W/LNxnvAJ8nw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "minipass": "^4.0.0"
+ }
+ },
+ "node_modules/@types/tar/node_modules/minipass": {
+ "version": "4.2.8",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz",
+ "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/@types/tough-cookie": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz",
diff --git a/package.json b/package.json
index 964cb7c..4d3d066 100644
--- a/package.json
+++ b/package.json
@@ -39,6 +39,7 @@
"@types/dompurify": "^3.0.5",
"@types/jsdom": "^21.1.6",
"@types/marked": "^5.0.2",
+ "@types/tar": "^6.1.13",
"concurrently": "^8.2.2",
"eslint": "^8.57.0",
"eslint-config-next": "14.1.0"
diff --git a/posts/mdtest.md b/posts/mdtest.md
index 294b23c..945a490 100644
--- a/posts/mdtest.md
+++ b/posts/mdtest.md
@@ -1,156 +1,319 @@
---
-title: "Markdown Testing"
-date: "2025-06-17"
-tags: ["testing"]
-summary: "Testing Markdown Features"
----
-# Markdown Feature Test
-
-## Table of Contents
-- [Headings](#headings)
-- [Text Formatting](#text-formatting)
-- [Lists](#lists)
-- [Links](#links)
-- [Images](#images)
-- [Code](#code)
-- [Blockquotes](#blockquotes)
-- [Horizontal Rule](#horizontal-rule)
-- [Tables](#tables)
-- [Task Lists](#task-lists)
-- [HTML in Markdown](#html-in-markdown)
-
+title: Markdown Demo!
+date: '2025-06-19'
+tags:
+ - demo
+summary: Demo of Markdown Parsing
+author: Rattatwinko's
---
-## Headings
+# Markdown: Syntax
-# H1
-## H2
-### H3
-#### H4
-##### H5
-###### H6
-
----
-
-## Text Formatting
-
-- **Bold**
-- *Italic*
-- ***Bold and Italic***
-- ~~Strikethrough~~
-- Underline (HTML)
-- ==Highlight (non-standard)==
-
----
-
-## Lists
-
-### Unordered
-- Item 1
- - Subitem 1.1
- - Subitem 1.1.1
-- Item 2
-
-### Ordered
-1. First item
-2. Second item
- 1. Subitem
- 2. Subitem
-3. Third item
-
----
-
-## Links
-
-- [Inline Link](https://www.example.com)
-- [Reference Link][example]
--
-
-[example]: https://www.example.com
-
----
-
-## Images
-
-
-
-GIF (Image works too):
-
-
----
-
-## Code
-
-### Inline Code
-
-Here is `inline code`.
-
-### Code Block (fenced)
-
-```Python
-
-def hello\_world():
-print("Hello, world!")
-
-```
-
-### Code Blog (test)
-
-```Python
-def hello():
- return true;
-```
+* [Overview](#overview)
+ * [Philosophy](#philosophy)
+ * [Inline HTML](#html)
+ * [Automatic Escaping for Special Characters](#autoescape)
+* [Block Elements](#block)
+ * [Paragraphs and Line Breaks](#p)
+ * [Headers](#header)
+ * [Blockquotes](#blockquote)
+ * [Lists](#list)
+ * [Code Blocks](#precode)
+ * [Horizontal Rules](#hr)
+* [Span Elements](#span)
+ * [Links](#link)
+ * [Emphasis](#em)
+ * [Code](#code)
+ * [Images](#img)
+* [Miscellaneous](#misc)
+ * [Backslash Escapes](#backslash)
+ * [Automatic Links](#autolink)
-### Code Block (indented)
-```Python
- def indented_example():
- return True
-```
----
+**Note:** This document is itself written using Markdown; you
+can [see the source for it by adding '.text' to the URL](/projects/markdown/syntax.text).
-## Blockquotes
+----
-> This is a blockquote.
+## Overview
+
+### Philosophy
+
+Markdown is intended to be as easy-to-read and easy-to-write as is feasible.
+
+Readability, however, is emphasized above all else. A Markdown-formatted
+document should be publishable as-is, as plain text, without looking
+like it's been marked up with tags or formatting instructions. While
+Markdown's syntax has been influenced by several existing text-to-HTML
+filters -- including [Setext](http://docutils.sourceforge.net/mirror/setext.html), [atx](http://www.aaronsw.com/2002/atx/), [Textile](http://textism.com/tools/textile/), [reStructuredText](http://docutils.sourceforge.net/rst.html),
+[Grutatext](http://www.triptico.com/software/grutatxt.html), and [EtText](http://ettext.taint.org/doc/) -- the single biggest source of
+inspiration for Markdown's syntax is the format of plain text email.
+
+## Block Elements
+
+### Paragraphs and Line Breaks
+
+A paragraph is simply one or more consecutive lines of text, separated
+by one or more blank lines. (A blank line is any line that looks like a
+blank line -- a line containing nothing but spaces or tabs is considered
+blank.) Normal paragraphs should not be indented with spaces or tabs.
+
+The implication of the "one or more consecutive lines of text" rule is
+that Markdown supports "hard-wrapped" text paragraphs. This differs
+significantly from most other text-to-HTML formatters (including Movable
+Type's "Convert Line Breaks" option) which translate every line break
+character in a paragraph into a ` ` tag.
+
+When you *do* want to insert a ` ` break tag using Markdown, you
+end a line with two or more spaces, then type return.
+
+### Headers
+
+Markdown supports two styles of headers, [Setext] [1] and [atx] [2].
+
+Optionally, you may "close" atx-style headers. This is purely
+cosmetic -- you can use this if you think it looks better. The
+closing hashes don't even need to match the number of hashes
+used to open the header. (The number of opening hashes
+determines the header level.)
+
+
+### Blockquotes
+
+Markdown uses email-style `>` characters for blockquoting. If you're
+familiar with quoting passages of text in an email message, then you
+know how to create a blockquote in Markdown. It looks best if you hard
+wrap the text and put a `>` before every line:
+
+> This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet,
+> consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus.
+> Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.
+>
+> Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse
+> id sem consectetuer libero luctus adipiscing.
+
+Markdown allows you to be lazy and only put the `>` before the first
+line of a hard-wrapped paragraph:
+
+> This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet,
+consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus.
+Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.
+
+> Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse
+id sem consectetuer libero luctus adipiscing.
+
+Blockquotes can be nested (i.e. a blockquote-in-a-blockquote) by
+adding additional levels of `>`:
+
+> This is the first level of quoting.
>
-> > Nested blockquote.
+> > This is nested blockquote.
+>
+> Back to the first level.
----
+Blockquotes can contain other Markdown elements, including headers, lists,
+and code blocks:
-## Horizontal Rule
+> ## This is a header.
+>
+> 1. This is the first list item.
+> 2. This is the second list item.
+>
+> Here's some example code:
+>
+> return shell_exec("echo $input | $markdown_script");
----
+Any decent text editor should make email-style quoting easy. For
+example, with BBEdit, you can make a selection and choose Increase
+Quote Level from the Text menu.
-___
-***
+### Lists
----
+Markdown supports ordered (numbered) and unordered (bulleted) lists.
-## Tables
+Unordered lists use asterisks, pluses, and hyphens -- interchangably
+-- as list markers:
-| Syntax | Description |
-|--------|-------------|
-| Header | Title |
-| Cell | Content |
+* Red
+* Green
+* Blue
----
+is equivalent to:
-## Task Lists
++ Red
++ Green
++ Blue
-- [x] Task 1
-- [ ] Task 2
- - [x] Subtask
- - [ ] Subtask
+and:
----
+- Red
+- Green
+- Blue
-## HTML in Markdown
+Ordered lists use numbers followed by periods:
-
-This is raw HTML in Markdown.
-
+1. Bird
+2. McHale
+3. Parish
----
+It's important to note that the actual numbers you use to mark the
+list have no effect on the HTML output Markdown produces. The HTML
+Markdown produces from the above list is:
-_End of Markdown Feature Test_
+If you instead wrote the list in Markdown like this:
+
+1. Bird
+1. McHale
+1. Parish
+
+or even:
+
+3. Bird
+1. McHale
+8. Parish
+
+you'd get the exact same HTML output. The point is, if you want to,
+you can use ordinal numbers in your ordered Markdown lists, so that
+the numbers in your source match the numbers in your published HTML.
+But if you want to be lazy, you don't have to.
+
+To make lists look nice, you can wrap items with hanging indents:
+
+* Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
+ Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi,
+ viverra nec, fringilla in, laoreet vitae, risus.
+* Donec sit amet nisl. Aliquam semper ipsum sit amet velit.
+ Suspendisse id sem consectetuer libero luctus adipiscing.
+
+But if you want to be lazy, you don't have to:
+
+* Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
+Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi,
+viverra nec, fringilla in, laoreet vitae, risus.
+* Donec sit amet nisl. Aliquam semper ipsum sit amet velit.
+Suspendisse id sem consectetuer libero luctus adipiscing.
+
+List items may consist of multiple paragraphs. Each subsequent
+paragraph in a list item must be indented by either 4 spaces
+or one tab:
+
+1. This is a list item with two paragraphs. Lorem ipsum dolor
+ sit amet, consectetuer adipiscing elit. Aliquam hendrerit
+ mi posuere lectus.
+
+ Vestibulum enim wisi, viverra nec, fringilla in, laoreet
+ vitae, risus. Donec sit amet nisl. Aliquam semper ipsum
+ sit amet velit.
+
+2. Suspendisse id sem consectetuer libero luctus adipiscing.
+
+It looks nice if you indent every line of the subsequent
+paragraphs, but here again, Markdown will allow you to be
+lazy:
+
+* This is a list item with two paragraphs.
+
+ This is the second paragraph in the list item. You're
+only required to indent the first line. Lorem ipsum dolor
+sit amet, consectetuer adipiscing elit.
+
+* Another item in the same list.
+
+To put a blockquote within a list item, the blockquote's `>`
+delimiters need to be indented:
+
+* A list item with a blockquote:
+
+ > This is a blockquote
+ > inside a list item.
+
+To put a code block within a list item, the code block needs
+to be indented *twice* -- 8 spaces or two tabs:
+
+* A list item with a code block:
+
+
+
+### Code Blocks
+
+Pre-formatted code blocks are used for writing about programming or
+markup source code. Rather than forming normal paragraphs, the lines
+of a code block are interpreted literally. Markdown wraps a code block
+in both `
` and `` tags.
+
+To produce a code block in Markdown, simply indent every line of the
+block by at least 4 spaces or 1 tab.
+
+This is a normal paragraph:
+
+ This is a code block.
+
+Here is an example of AppleScript:
+
+ tell application "Foo"
+ beep
+ end tell
+
+A code block continues until it reaches a line that is not indented
+(or the end of the article).
+
+Within a code block, ampersands (`&`) and angle brackets (`<` and `>`)
+are automatically converted into HTML entities. This makes it very
+easy to include example HTML source code using Markdown -- just paste
+it and indent it, and Markdown will handle the hassle of encoding the
+ampersands and angle brackets. For example, this:
+
+
+
+Regular Markdown syntax is not processed within code blocks. E.g.,
+asterisks are just literal asterisks within a code block. This means
+it's also easy to use Markdown to write about Markdown's own syntax.
+
+```
+tell application "Foo"
+ beep
+end tell
+```
+
+## Span Elements
+
+### Links
+
+Markdown supports two style of links: *inline* and *reference*.
+
+In both styles, the link text is delimited by [square brackets].
+
+To create an inline link, use a set of regular parentheses immediately
+after the link text's closing square bracket. Inside the parentheses,
+put the URL where you want the link to point, along with an *optional*
+title for the link, surrounded in quotes. For example:
+
+This is [an example](http://example.com/) inline link.
+
+[This link](http://example.net/) has no title attribute.
+
+### Emphasis
+
+Markdown treats asterisks (`*`) and underscores (`_`) as indicators of
+emphasis. Text wrapped with one `*` or `_` will be wrapped with an
+HTML `` tag; double `*`'s or `_`'s will be wrapped with an HTML
+`` tag. E.g., this input:
+
+*single asterisks*
+
+_single underscores_
+
+**double asterisks**
+
+__double underscores__
+
+### Code
+
+To indicate a span of code, wrap it with backtick quotes (`` ` ``).
+Unlike a pre-formatted code block, a code span indicates code within a
+normal paragraph. For example:
+
+Use the `printf()` function.
diff --git a/posts/welcome.md b/posts/welcome.md
index 62dda9e..6c0f677 100644
--- a/posts/welcome.md
+++ b/posts/welcome.md
@@ -100,7 +100,13 @@ You can pin a post both in the UI and in the backend of the server.
| Status | Task |
|:---------------------------------------------:|:-------------------------------------------------:|
-|DONE|Code Editor in Admin Panel with saving!
+|DONE|Code Editor in Admin Panel with saving!|
+| SEMI | Exporting Tar of 'Posts/' Folder|
+
+### Exporting of Folder:
+
+This for now atleast , only works with Next.JS Production Server `npm install && npm run build && npm start` for reference.
+Docker Support for now is limited. I've gotten Persistence working. ( On Branch PM2 )
---
diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx
index 37f59a0..527262e 100644
--- a/src/app/admin/page.tsx
+++ b/src/app/admin/page.tsx
@@ -78,6 +78,7 @@ export default function AdminPage() {
const [changePwFeedback, setChangePwFeedback] = useState(null);
const [previewHtml, setPreviewHtml] = useState('');
const [editingPost, setEditingPost] = useState<{ slug: string, path: string } | null>(null);
+ const [isDocker, setIsDocker] = useState(false);
const router = useRouter();
const usernameRef = useRef(null);
const passwordRef = useRef(null);
@@ -96,6 +97,7 @@ export default function AdminPage() {
useEffect(() => {
localStorage.setItem('pinnedPosts', JSON.stringify(pinned));
}, [pinned]);
+
useEffect(() => {
marked.setOptions({
gfm: true,
@@ -111,6 +113,14 @@ export default function AdminPage() {
setPreviewHtml(marked.parse(newPost.content || '') as string);
}, [newPost.content]);
+ useEffect(() => {
+ // Check if docker is used
+ fetch('/api/admin/docker')
+ .then(res => res.json())
+ .then(data => setIsDocker(!!data.docker))
+ .catch(() => setIsDocker(false));
+ }, []);
+
const loadContent = async () => {
try {
const response = await fetch('/api/posts');
@@ -480,6 +490,25 @@ export default function AdminPage() {
}
};
+ function handleExportTarball() {
+ fetch('/api/admin/export')
+ .then(async (res) => {
+ if (!res.ok) throw new Error('Export failed');
+ const blob = await res.blob();
+ const url = window.URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = 'markdownblog-export.tar.gz';
+ document.body.appendChild(a);
+ a.click();
+ a.remove();
+ window.URL.revokeObjectURL(url);
+ })
+ .catch((err) => {
+ alert('Export failed: ' + err.message);
+ });
+ }
+
return (