-
-[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/AboutButton.tsx b/src/app/AboutButton.tsx
index b4832ac..b313436 100644
--- a/src/app/AboutButton.tsx
+++ b/src/app/AboutButton.tsx
@@ -14,7 +14,11 @@ export default function AboutButton() {
label="ABOUT ME"
color="#2563eb"
icon={InfoIcon}
- onClick={() => window.open('http://' + window.location.hostname + ':80', '_blank')}
+ onClick={() => {
+ if (typeof window !== 'undefined') {
+ window.open('http://' + window.location.hostname + ':80', '_blank');
+ }
+ }}
/>
);
}
diff --git a/src/app/HeaderButtons.tsx b/src/app/HeaderButtons.tsx
index ff8a75e..fda0f03 100644
--- a/src/app/HeaderButtons.tsx
+++ b/src/app/HeaderButtons.tsx
@@ -27,7 +27,7 @@ export default function HeaderButtons() {
/>
{/* If your server for about me is running on a different port, change the port number here */}
-
+
(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 (
{pinFeedback && (
@@ -548,6 +577,19 @@ export default function AdminPage() {
>
Passwort ändern
+ {/* Docker warning above export button */}
+ {isDocker && (
+
+ Warning: Docker is in use. Exporting will export the entire /app root directory (including all files and folders in the container's root).
+
+ )}
+
+ Export Root as Tarball
+
diff --git a/src/app/api/admin/delete/route.ts b/src/app/api/admin/delete/route.ts
index 9411ca3..8cfe4df 100644
--- a/src/app/api/admin/delete/route.ts
+++ b/src/app/api/admin/delete/route.ts
@@ -1,6 +1,7 @@
import { NextRequest, NextResponse } from 'next/server';
import fs from 'fs/promises';
import path from 'path';
+import { getPostsDirectory } from '@/lib/postsDirectory';
export async function POST(request: NextRequest) {
try {
@@ -16,7 +17,7 @@ export async function POST(request: NextRequest) {
// Construct the full path to the item
const basePath = process.cwd();
- const postsDir = path.join(basePath, 'posts');
+ const postsDir = getPostsDirectory();
// Ensure the posts directory exists
try {
diff --git a/src/app/api/admin/docker/route.ts b/src/app/api/admin/docker/route.ts
new file mode 100644
index 0000000..26a0427
--- /dev/null
+++ b/src/app/api/admin/docker/route.ts
@@ -0,0 +1,10 @@
+import { existsSync } from 'fs';
+import path from 'path';
+import { NextResponse } from 'next/server';
+
+export async function GET() {
+ const rootDir = process.cwd();
+ const dockerDir = path.join(rootDir, 'docker');
+ const isDocker = existsSync(dockerDir);
+ return NextResponse.json({ docker: isDocker });
+}
\ No newline at end of file
diff --git a/src/app/api/admin/export/route.ts b/src/app/api/admin/export/route.ts
new file mode 100644
index 0000000..d0be048
--- /dev/null
+++ b/src/app/api/admin/export/route.ts
@@ -0,0 +1,69 @@
+import tar from 'tar';
+import { NextResponse } from 'next/server';
+import { statSync, createReadStream, existsSync } from 'fs';
+import path from 'path';
+
+export async function GET() {
+ try {
+ const rootDir = process.cwd();
+ const dockerDir = path.join(rootDir, 'docker');
+ const postsDir = path.join(rootDir, 'posts');
+ let tarballName: string;
+ let tarballPath: string;
+ let tarCwd: string;
+ let tarItems: string[];
+ let tarOptions: any = {
+ gzip: true,
+ portable: true,
+ noMtime: true,
+ };
+
+ if (existsSync(dockerDir)) {
+ // Docker is in use: export the entire root directory (excluding node_modules, .next, etc)
+ tarballName = 'root-export.tar.gz';
+ tarballPath = path.join('/tmp', tarballName);
+ tarCwd = rootDir;
+ tarItems = ['.'];
+ tarOptions.file = tarballPath;
+ tarOptions.cwd = tarCwd;
+ tarOptions.filter = (filepath: string) => {
+ // Exclude node_modules, .next, .git, /tmp, and tarball itself
+ const excludes = [
+ 'node_modules', '.next', '.git', 'tmp', 'docker.sock', tarballName
+ ];
+ // Only check top-level folders/files
+ const rel = filepath.split(path.sep)[0];
+ return !excludes.includes(rel);
+ };
+ } else {
+ // Not docker: export only the posts directory
+ tarballName = 'posts-export.tar.gz';
+ tarballPath = path.join('/tmp', tarballName);
+ tarCwd = rootDir;
+ tarItems = ['posts'];
+ tarOptions.file = tarballPath;
+ tarOptions.cwd = tarCwd;
+ }
+
+ // Create tarball
+ await tar.c(
+ tarOptions,
+ tarItems
+ );
+
+ // Stream the tarball as a response
+ const stat = statSync(tarballPath);
+ const stream = createReadStream(tarballPath);
+ return new Response(stream as any, {
+ status: 200,
+ headers: {
+ 'Content-Type': 'application/gzip',
+ 'Content-Disposition': `attachment; filename="${tarballName}"`,
+ 'Content-Length': stat.size.toString(),
+ },
+ });
+ } catch (error) {
+ console.error('Error exporting tarball:', error);
+ return NextResponse.json({ error: 'Error exporting tarball' }, { status: 500 });
+ }
+}
\ No newline at end of file
diff --git a/src/app/api/admin/folders/details/route.ts b/src/app/api/admin/folders/details/route.ts
index 59fca94..392fa91 100644
--- a/src/app/api/admin/folders/details/route.ts
+++ b/src/app/api/admin/folders/details/route.ts
@@ -1,8 +1,9 @@
import { NextResponse } from 'next/server';
import fs from 'fs';
import path from 'path';
+import { getPostsDirectory } from '@/lib/postsDirectory';
-const postsDirectory = path.join(process.cwd(), 'posts');
+const postsDirectory = getPostsDirectory();
function getFolderStats(folderPath: string) {
const fullPath = path.join(postsDirectory, folderPath);
diff --git a/src/app/api/admin/folders/route.ts b/src/app/api/admin/folders/route.ts
index 7609010..4231c2b 100644
--- a/src/app/api/admin/folders/route.ts
+++ b/src/app/api/admin/folders/route.ts
@@ -1,8 +1,9 @@
import { NextResponse } from 'next/server';
import fs from 'fs';
import path from 'path';
+import { getPostsDirectory } from '@/lib/postsDirectory';
-const postsDirectory = path.join(process.cwd(), 'posts');
+const postsDirectory = getPostsDirectory();
export async function POST(request: Request) {
try {
diff --git a/src/app/api/admin/posts/move/route.ts b/src/app/api/admin/posts/move/route.ts
index 4e5aed5..6f95ad1 100644
--- a/src/app/api/admin/posts/move/route.ts
+++ b/src/app/api/admin/posts/move/route.ts
@@ -1,8 +1,9 @@
import { NextResponse } from 'next/server';
import fs from 'fs';
import path from 'path';
+import { getPostsDirectory } from '@/lib/postsDirectory';
-const postsDirectory = path.join(process.cwd(), 'posts');
+const postsDirectory = getPostsDirectory();
export async function POST(request: Request) {
try {
diff --git a/src/app/api/admin/posts/raw/route.ts b/src/app/api/admin/posts/raw/route.ts
index 0b0ea93..82d3070 100644
--- a/src/app/api/admin/posts/raw/route.ts
+++ b/src/app/api/admin/posts/raw/route.ts
@@ -1,8 +1,9 @@
import { NextRequest, NextResponse } from 'next/server';
import fs from 'fs';
import path from 'path';
+import { getPostsDirectory } from '@/lib/postsDirectory';
-const postsDirectory = path.join(process.cwd(), 'posts');
+const postsDirectory = getPostsDirectory();
export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url);
diff --git a/src/app/api/admin/posts/route.ts b/src/app/api/admin/posts/route.ts
index 329920b..45be787 100644
--- a/src/app/api/admin/posts/route.ts
+++ b/src/app/api/admin/posts/route.ts
@@ -2,8 +2,9 @@ import { NextResponse } from 'next/server';
import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';
+import { getPostsDirectory } from '@/lib/postsDirectory';
-const postsDirectory = path.join(process.cwd(), 'posts');
+const postsDirectory = getPostsDirectory();
export async function POST(request: Request) {
try {
diff --git a/src/app/api/admin/posts/size/route.ts b/src/app/api/admin/posts/size/route.ts
index 5ba6734..7349fa5 100644
--- a/src/app/api/admin/posts/size/route.ts
+++ b/src/app/api/admin/posts/size/route.ts
@@ -1,8 +1,9 @@
import { NextResponse } from 'next/server';
import fs from 'fs';
import path from 'path';
+import { getPostsDirectory } from '@/lib/postsDirectory';
-const postsDirectory = path.join(process.cwd(), 'posts');
+const postsDirectory = getPostsDirectory();
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
diff --git a/src/app/api/admin/upload/route.ts b/src/app/api/admin/upload/route.ts
index 9b8e11a..983380b 100644
--- a/src/app/api/admin/upload/route.ts
+++ b/src/app/api/admin/upload/route.ts
@@ -2,8 +2,9 @@ import { NextResponse } from 'next/server';
import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';
+import { getPostsDirectory } from '@/lib/postsDirectory';
-const postsDirectory = path.join(process.cwd(), 'posts');
+const postsDirectory = getPostsDirectory();
export async function POST(request: Request) {
try {
diff --git a/src/app/api/posts/[slug]/route.ts b/src/app/api/posts/[slug]/route.ts
index cb3b5e1..67ebbbd 100644
--- a/src/app/api/posts/[slug]/route.ts
+++ b/src/app/api/posts/[slug]/route.ts
@@ -8,8 +8,9 @@ import { marked } from 'marked';
import DOMPurify from 'dompurify';
import { JSDOM } from 'jsdom';
import hljs from 'highlight.js';
+import { getPostsDirectory } from '@/lib/postsDirectory';
-const postsDirectory = path.join(process.cwd(), 'posts');
+const postsDirectory = getPostsDirectory();
const renderer = new marked.Renderer();
renderer.code = (code, infostring, escaped) => {
diff --git a/src/app/api/posts/route.ts b/src/app/api/posts/route.ts
index bf6b3c9..84dd410 100644
--- a/src/app/api/posts/route.ts
+++ b/src/app/api/posts/route.ts
@@ -8,8 +8,9 @@ import { marked } from 'marked';
import DOMPurify from 'dompurify';
import { JSDOM } from 'jsdom';
import hljs from 'highlight.js';
+import { getPostsDirectory } from '@/lib/postsDirectory';
-const postsDirectory = path.join(process.cwd(), 'posts');
+const postsDirectory = getPostsDirectory();
const pinnedPath = path.join(postsDirectory, 'pinned.json');
let pinnedSlugs: string[] = [];
diff --git a/src/lib/markdown.ts b/src/lib/markdown.ts
index ff051f7..1a8d169 100644
--- a/src/lib/markdown.ts
+++ b/src/lib/markdown.ts
@@ -7,6 +7,7 @@ import { JSDOM } from 'jsdom';
import chokidar from 'chokidar';
import type { FSWatcher } from 'chokidar';
import hljs from 'highlight.js';
+import { getPostsDirectory } from './postsDirectory';
export interface Post {
slug: string;
@@ -19,7 +20,7 @@ export interface Post {
author: string;
}
-const postsDirectory = path.join(process.cwd(), 'posts');
+const postsDirectory = getPostsDirectory();
// Function to get file creation date
function getFileCreationDate(filePath: string): Date {
diff --git a/src/lib/postsDirectory.ts b/src/lib/postsDirectory.ts
new file mode 100644
index 0000000..689d480
--- /dev/null
+++ b/src/lib/postsDirectory.ts
@@ -0,0 +1,9 @@
+import path from 'path';
+import { existsSync } from 'fs';
+
+export function getPostsDirectory() {
+ const rootDir = process.cwd();
+ const dockerDir = path.join(rootDir, 'docker');
+ const postsDir = path.join(rootDir, 'posts');
+ return existsSync(dockerDir) ? dockerDir : postsDir;
+}
\ No newline at end of file