docker shit

This commit is contained in:
rattatwinko
2025-06-16 22:20:38 +02:00
parent 1303078c2e
commit 682e006e0b
9 changed files with 439 additions and 35 deletions

35
.dockerignore Normal file
View File

@@ -0,0 +1,35 @@
# Dependencies
node_modules
npm-debug.log
yarn-debug.log
yarn-error.log
# Next.js build output
.next
out
# Environment files
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# Version control
.git
.gitignore
# IDE files
.idea
.vscode
*.swp
*.swo
# OS files
.DS_Store
Thumbs.db
# Docker files
Dockerfile
.dockerignore
manage_container.sh

View File

@@ -1,23 +1,52 @@
# Use Node.js as the base image
FROM node:18
# Build stage
FROM node:18-alpine AS builder
# Set the working directory
# Set working directory
WORKDIR /app
# Copy package.json and package-lock.json (if available)
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm install
RUN npm ci
# Copy the rest of the application code
# Copy source code and config files
COPY . .
# Build the application
RUN npm run build
# Production stage
FROM node:18-alpine AS runner
# Set working directory
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install production dependencies only
RUN npm ci --only=production
# Copy necessary files from builder
COPY --from=builder /app/.next ./.next/
COPY --from=builder /app/public ./public/
COPY --from=builder /app/next.config.js ./
# Create a directory for markdown files
RUN mkdir -p /markdown
# Expose the port your app runs on
EXPOSE 3000
# Set environment variables
ENV NODE_ENV=production
ENV PORT=8080
ENV HOSTNAME=0.0.0.0
# Command to run the application
# Expose the port
EXPOSE 8080
# Health check
HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/ || exit 1
# Start the application
CMD ["npm", "start"]

View File

@@ -4,6 +4,7 @@
[![Tailwind CSS](https://img.shields.io/badge/TailwindCSS-3.4-blue?style=flat-square&logo=tailwind-css&logoColor=white)](https://tailwindcss.com/)
[![TypeScript](https://img.shields.io/badge/TypeScript-5-blue?style=flat-square&logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
[![Electron](https://img.shields.io/badge/Electron-28-47848F?style=flat-square&logo=electron&logoColor=white)](https://www.electronjs.org/)
[![Docker](https://img.shields.io/badge/Docker-24-2496ED?style=flat-square&logo=docker&logoColor=white)](https://www.docker.com/)
[![MIT License](https://img.shields.io/badge/License-MIT-green?style=flat-square)](LICENSE)
A modern, cross-platform blog system built with **Next.js**, **Markdown**, and **Electron**. Write posts in Markdown, manage content visually, and deploy to web or desktop.
@@ -18,6 +19,7 @@ A modern, cross-platform blog system built with **Next.js**, **Markdown**, and *
- 🖥️ **Electron desktop app** — Run your blog as a native desktop app
- 📱 **Responsive UI** — Mobile-friendly and clean design
- 🛠️ **Admin dashboard** — Manage posts and folders visually
- 🐳 **Docker support** — Easy deployment with Docker containers
---
@@ -27,6 +29,7 @@ A modern, cross-platform blog system built with **Next.js**, **Markdown**, and *
- [Node.js 18+](https://nodejs.org/)
- [npm](https://www.npmjs.com/)
- [Docker](https://www.docker.com/) (for containerized deployment)
### Installation
@@ -52,6 +55,70 @@ npm install
---
## 🐳 Docker Deployment
The project includes Docker support for easy deployment. A `manage_container.sh` script is provided to simplify container management.
### Docker Setup
1. Make sure Docker is installed and running on your system
2. Update the `MARKDOWN_DIR` path in `manage_container.sh` to point to your local markdown directory
3. Make the script executable:
```bash
chmod +x manage_container.sh
```
### Container Management
The `manage_container.sh` script provides several commands:
```bash
# Build the Docker image
./manage_container.sh build
# Start the container
./manage_container.sh start
# Stop the container
./manage_container.sh stop
# Restart the container
./manage_container.sh restart
# View container logs
./manage_container.sh logs
# Check container status
./manage_container.sh status
# Remove the container
./manage_container.sh remove
```
### Container Features
- **Health Checks**: Automatic health monitoring
- **Auto-restart**: Container restarts automatically if it crashes
- **Volume Mounting**: Your markdown files are mounted into the container
- **Port Mapping**: Access the blog at http://localhost:8080
### Container Status
The status command shows:
- Container running state
- Health check status
- Access URL
### Troubleshooting
If the container fails to start:
1. Check the logs: `./manage_container.sh logs`
2. Verify Docker is running
3. Ensure port 8080 is available
4. Check the markdown directory path in `manage_container.sh`
---
## 📝 Writing Posts
Add Markdown files to the `posts/` directory. Each post should have frontmatter:
@@ -79,7 +146,9 @@ markdownblog/
│ └── lib/ # Utility functions
├── electron/ # Desktop app code
├── public/ # Static assets (favicons, etc.)
└── ...
├── Dockerfile # Docker configuration
├── .dockerignore # Docker ignore rules
└── manage_container.sh # Docker management script
```
---
@@ -99,12 +168,7 @@ Next.js will automatically serve these at the root URL (e.g., `/favicon.ico`).
- [Electron](https://www.electronjs.org/)
- [Remark](https://remark.js.org/) (Markdown)
- [date-fns](https://date-fns.org/)
---
## 🐳 Docker
A sample `Dockerfile` and `manage_container.sh` are included for containerized deployment.
- [Docker](https://www.docker.com/)
---

View File

@@ -3,39 +3,199 @@
# Configuration
IMAGE_NAME="markdown-blog"
CONTAINER_NAME="markdown-blog-container"
PORT=3000
MARKDOWN_DIR="/path/to/your/markdown" # Update this to your local markdown directory
PORT=8080
MARKDOWN_DIR="/home/rattatwinko/Documents/shit/markdownblog" # Update this to your local markdown directory
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Function to check if Docker is running
check_docker() {
if ! docker info > /dev/null 2>&1; then
echo -e "${RED}Error: Docker is not running${NC}"
exit 1
fi
}
# Function to check if container exists
container_exists() {
docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"
}
# Function to check if container is running
container_running() {
docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"
}
# Function to check container health
check_container_health() {
local health_status
health_status=$(docker inspect --format='{{.State.Health.Status}}' $CONTAINER_NAME 2>/dev/null)
if [ "$health_status" = "healthy" ]; then
return 0
else
return 1
fi
}
# Function to build the Docker image
build_image() {
echo "Building Docker image..."
docker build -t $IMAGE_NAME .
echo -e "${YELLOW}Building Docker image...${NC}"
if ! docker build -t $IMAGE_NAME .; then
echo -e "${RED}Error: Failed to build Docker image${NC}"
exit 1
fi
echo -e "${GREEN}Docker image built successfully${NC}"
}
# Function to start the container
start_container() {
echo "Starting container..."
docker run -d --name $CONTAINER_NAME -p $PORT:3000 -v $MARKDOWN_DIR:/markdown $IMAGE_NAME
echo -e "${YELLOW}Starting container...${NC}"
# Check if container already exists
if container_exists; then
if container_running; then
echo -e "${YELLOW}Container is already running${NC}"
return
else
echo -e "${YELLOW}Container exists but is not running. Starting it...${NC}"
if ! docker start $CONTAINER_NAME; then
echo -e "${RED}Error: Failed to start existing container${NC}"
exit 1
fi
fi
else
# Create and start new container
if ! docker run -d \
--name $CONTAINER_NAME \
-p $PORT:8080 \
-v "$MARKDOWN_DIR:/markdown" \
--restart unless-stopped \
--health-cmd="wget --no-verbose --tries=1 --spider http://localhost:8080/ || exit 1" \
--health-interval=30s \
--health-timeout=30s \
--health-retries=3 \
--health-start-period=5s \
$IMAGE_NAME; then
echo -e "${RED}Error: Failed to create and start container${NC}"
exit 1
fi
fi
# Wait for container to be ready
echo -e "${YELLOW}Waiting for container to be ready...${NC}"
local max_attempts=60
local attempt=1
while [ $attempt -le $max_attempts ]; do
if check_container_health; then
echo -e "${GREEN}Container is healthy and accessible at http://localhost:$PORT${NC}"
return
fi
# Check if container is still running
if ! container_running; then
echo -e "${RED}Error: Container stopped unexpectedly${NC}"
docker logs $CONTAINER_NAME
exit 1
fi
echo -n "."
sleep 1
attempt=$((attempt + 1))
done
echo -e "${RED}Error: Container did not become healthy in time${NC}"
docker logs $CONTAINER_NAME
exit 1
}
# Function to stop the container
stop_container() {
echo "Stopping container..."
docker stop $CONTAINER_NAME
echo -e "${YELLOW}Stopping container...${NC}"
if ! container_exists; then
echo -e "${YELLOW}Container does not exist${NC}"
return
fi
if ! docker stop $CONTAINER_NAME; then
echo -e "${RED}Error: Failed to stop container${NC}"
exit 1
fi
echo -e "${GREEN}Container stopped successfully${NC}"
}
# Function to remove the container
remove_container() {
echo -e "${YELLOW}Removing container...${NC}"
if ! container_exists; then
echo -e "${YELLOW}Container does not exist${NC}"
return
fi
if container_running; then
stop_container
fi
if ! docker rm $CONTAINER_NAME; then
echo -e "${RED}Error: Failed to remove container${NC}"
exit 1
fi
echo -e "${GREEN}Container removed successfully${NC}"
}
# Function to restart the container
restart_container() {
echo "Restarting container..."
docker restart $CONTAINER_NAME
echo -e "${YELLOW}Restarting container...${NC}"
if ! container_exists; then
echo -e "${YELLOW}Container does not exist. Starting new container...${NC}"
start_container
return
fi
if ! docker restart $CONTAINER_NAME; then
echo -e "${RED}Error: Failed to restart container${NC}"
exit 1
fi
echo -e "${GREEN}Container restarted successfully${NC}"
}
# Function to view logs
view_logs() {
echo "Viewing logs..."
docker logs $CONTAINER_NAME
echo -e "${YELLOW}Viewing logs...${NC}"
if ! container_exists; then
echo -e "${RED}Error: Container does not exist${NC}"
exit 1
fi
docker logs -f $CONTAINER_NAME
}
# Function to show container status
show_status() {
echo -e "${YELLOW}Container Status:${NC}"
if ! container_exists; then
echo -e "${RED}Container does not exist${NC}"
return
fi
if container_running; then
local health_status
health_status=$(docker inspect --format='{{.State.Health.Status}}' $CONTAINER_NAME 2>/dev/null)
echo -e "${GREEN}Container is running${NC}"
echo "Health status: $health_status"
echo "Access the application at: http://localhost:$PORT"
else
echo -e "${YELLOW}Container exists but is not running${NC}"
fi
}
# Check Docker is running before proceeding
check_docker
# Main script logic
case "$1" in
build)
@@ -53,8 +213,14 @@ case "$1" in
logs)
view_logs
;;
status)
show_status
;;
remove)
remove_container
;;
*)
echo "Usage: $0 {build|start|stop|restart|logs}"
echo "Usage: $0 {build|start|stop|restart|logs|status|remove}"
exit 1
;;
esac

17
next.config.js Normal file
View File

@@ -0,0 +1,17 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'standalone',
reactStrictMode: true,
swcMinify: true,
// Ensure we can access the app from outside the container
experimental: {
outputFileTracingRoot: undefined,
},
// Configure the hostname and port
server: {
hostname: '0.0.0.0',
port: 8080,
},
}
module.exports = nextConfig

19
package-lock.json generated
View File

@@ -22,6 +22,7 @@
"tailwindcss": "^3.4.1"
},
"devDependencies": {
"@types/chokidar": "^1.7.5",
"@types/node": "^20.11.19",
"@types/react": "^18.2.57",
"@types/react-dom": "^18.2.19",
@@ -1112,6 +1113,17 @@
"@types/responselike": "^1.0.0"
}
},
"node_modules/@types/chokidar": {
"version": "1.7.5",
"resolved": "https://registry.npmjs.org/@types/chokidar/-/chokidar-1.7.5.tgz",
"integrity": "sha512-PDkSRY7KltW3M60hSBlerxI8SFPXsO3AL/aRVsO4Kh9IHRW74Ih75gUuTd/aE4LSSFqypb10UIX3QzOJwBQMGQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/events": "*",
"@types/node": "*"
}
},
"node_modules/@types/debug": {
"version": "4.1.12",
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
@@ -1121,6 +1133,13 @@
"@types/ms": "*"
}
},
"node_modules/@types/events": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.3.tgz",
"integrity": "sha512-trOc4AAUThEz9hapPtSd7wf5tiQKvTtu5b371UxXdTuqzIh0ArcRspRP0i0Viu+LXstIQ1z96t1nsPxT9ol01g==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/fs-extra": {
"version": "9.0.13",
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz",

View File

@@ -25,6 +25,7 @@
"tailwindcss": "^3.4.1"
},
"devDependencies": {
"@types/chokidar": "^1.7.5",
"@types/node": "^20.11.19",
"@types/react": "^18.2.57",
"@types/react-dom": "^18.2.19",

View File

@@ -1 +1,72 @@
import { NextResponse } from 'next/server';
import fs from 'fs';
import path from 'path';
const postsDirectory = path.join(process.cwd(), 'posts');
export async function POST(request: Request) {
try {
const body = await request.json();
const { name, path: folderPath } = body;
// Create the full path for the new folder
const fullPath = path.join(postsDirectory, folderPath, name);
// Check if folder already exists
if (fs.existsSync(fullPath)) {
return NextResponse.json(
{ error: 'Folder already exists' },
{ status: 400 }
);
}
// Create the folder
fs.mkdirSync(fullPath, { recursive: true });
return NextResponse.json({ success: true });
} catch (error) {
console.error('Error creating folder:', error);
return NextResponse.json(
{ error: 'Error creating folder' },
{ status: 500 }
);
}
}
export async function DELETE(request: Request) {
try {
const body = await request.json();
const { name, path: folderPath } = body;
// Create the full path for the folder to delete
const fullPath = path.join(postsDirectory, folderPath, name);
// Check if folder exists
if (!fs.existsSync(fullPath)) {
return NextResponse.json(
{ error: 'Folder does not exist' },
{ status: 404 }
);
}
// Check if folder is empty
const files = fs.readdirSync(fullPath);
if (files.length > 0) {
return NextResponse.json(
{ error: 'Cannot delete non-empty folder' },
{ status: 400 }
);
}
// Delete the folder
fs.rmdirSync(fullPath);
return NextResponse.json({ success: true });
} catch (error) {
console.error('Error deleting folder:', error);
return NextResponse.json(
{ error: 'Error deleting folder' },
{ status: 500 }
);
}
}

View File

@@ -4,6 +4,7 @@ import matter from 'gray-matter';
import { remark } from 'remark';
import html from 'remark-html';
import chokidar from 'chokidar';
import type { FSWatcher } from 'chokidar';
export interface Post {
slug: string;
@@ -64,8 +65,9 @@ export async function getPostsByTag(tag: string): Promise<Post[]> {
const allPosts = await getAllPosts();
return allPosts.filter((post) => post.tags.includes(tag));
}
// File watcher setup
let watcher: chokidar.FSWatcher | null = null;
let watcher: FSWatcher | null = null;
let onChangeCallback: (() => void) | null = null;
export function watchPosts(callback: () => void) {