浏览代码

feat: add comprehensive deployment configuration and scripts

theVakhovskeIsTaken 4 月之前
父节点
当前提交
10b4e88f60

+ 55 - 0
Caddyfile

@@ -0,0 +1,55 @@
+goonblog.thevakhovske.eu.org {
+    # Enable automatic HTTPS
+    # Caddy will automatically get and renew SSL certificates
+
+    # Serve the React frontend (static files)
+    root * dist
+    
+    # Try to serve static files first, then fallback to index.html for SPA routing
+    try_files {path} /index.html
+    
+    # API routes - proxy to backend
+    handle /api/* {
+        reverse_proxy localhost:3001
+    }
+    
+    # Health check endpoint
+    handle /health {
+        reverse_proxy localhost:3001
+    }
+    
+    # Serve static files with proper headers
+    header /assets/* {
+        Cache-Control "public, max-age=31536000, immutable"
+    }
+    
+    # Security headers
+    header {
+        # Enable HSTS
+        Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
+        
+        # Prevent clickjacking
+        X-Frame-Options "DENY"
+        
+        # Prevent MIME type sniffing
+        X-Content-Type-Options "nosniff"
+        
+        # XSS protection
+        X-XSS-Protection "1; mode=block"
+        
+        # Referrer policy
+        Referrer-Policy "strict-origin-when-cross-origin"
+        
+        # Content Security Policy (adjust as needed)
+        Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:;"
+    }
+    
+    # Gzip compression
+    encode gzip
+    
+    # Logging
+    log {
+        output file /var/log/caddy/gooneral-wheelchair.log
+        format json
+    }
+}

+ 259 - 0
DEPLOYMENT.md

@@ -0,0 +1,259 @@
+# Gooneral Wheelchair - Production Deployment Guide
+
+## Prerequisites
+
+1. **Server with Node.js** (v18+ recommended)
+2. **Caddy web server** installed
+3. **Domain name** pointing to your server
+4. **SSH access** to your server
+
+## Deployment Steps
+
+### 1. Prepare the Production Environment
+
+On your server, create a directory for your application:
+
+```bash
+# Create application directory
+sudo mkdir -p /opt/gooneral-wheelchair
+sudo chown $USER:$USER /opt/gooneral-wheelchair
+cd /opt/gooneral-wheelchair
+```
+
+### 2. Upload Files
+
+Upload these files to your server:
+
+```
+/opt/gooneral-wheelchair/
+├── dist/                    # Built frontend files
+├── backend/                 # Backend application
+│   ├── server.js
+│   ├── start-production.js
+│   ├── package.json
+│   ├── .env.production      # Configure this!
+│   ├── auth.js
+│   ├── themes.js
+│   └── node_modules/        # Install dependencies
+├── public/
+│   └── posts/               # Your blog posts
+├── Caddyfile               # Caddy configuration
+└── DEPLOYMENT.md           # This file
+```
+
+### 3. Configure Environment Variables
+
+Edit `/opt/gooneral-wheelchair/backend/.env.production`:
+
+```env
+# REQUIRED: Change these values!
+NODE_ENV=production
+PORT=3001
+SESSION_SECRET=your-super-secret-session-key-generate-a-strong-one
+FRONTEND_URL=https://yourdomain.com
+
+# Optional: Customize paths if needed
+POSTS_DIR=../public/posts
+THEMES_FILE=./themes.json
+SESSIONS_DIR=./sessions
+
+# Security settings
+COOKIE_SECURE=true
+COOKIE_SAME_SITE=strict
+```
+
+**⚠️ IMPORTANT**: Generate a strong session secret:
+```bash
+node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"
+```
+
+### 4. Install Backend Dependencies
+
+```bash
+cd /opt/gooneral-wheelchair/backend
+npm install --production
+```
+
+### 5. Create Required Directories
+
+```bash
+mkdir -p sessions
+mkdir -p ../public/posts
+```
+
+### 6. Set Up System User (Recommended)
+
+```bash
+# Create a system user for the application
+sudo useradd --system --shell /bin/false --home /opt/gooneral-wheelchair gooneral
+
+# Change ownership
+sudo chown -R gooneral:gooneral /opt/gooneral-wheelchair
+
+# Make start script executable
+sudo chmod +x /opt/gooneral-wheelchair/backend/start-production.js
+```
+
+### 7. Create Systemd Service
+
+Create `/etc/systemd/system/gooneral-wheelchair.service`:
+
+```ini
+[Unit]
+Description=Gooneral Wheelchair CMS Backend
+After=network.target
+
+[Service]
+Type=simple
+User=gooneral
+WorkingDirectory=/opt/gooneral-wheelchair/backend
+Environment=NODE_ENV=production
+ExecStart=/usr/bin/node start-production.js
+Restart=always
+RestartSec=10
+StandardOutput=syslog
+StandardError=syslog
+SyslogIdentifier=gooneral-wheelchair
+
+[Install]
+WantedBy=multi-user.target
+```
+
+Enable and start the service:
+
+```bash
+sudo systemctl daemon-reload
+sudo systemctl enable gooneral-wheelchair
+sudo systemctl start gooneral-wheelchair
+sudo systemctl status gooneral-wheelchair
+```
+
+### 8. Configure Caddy
+
+1. Update the Caddyfile with your domain:
+   ```
+   # Replace 'yourdomain.com' with your actual domain
+   yourdomain.com {
+       # ... rest of configuration
+   }
+   ```
+
+2. Copy Caddyfile to Caddy's configuration directory:
+   ```bash
+   sudo cp /opt/gooneral-wheelchair/Caddyfile /etc/caddy/Caddyfile
+   ```
+
+3. Test and reload Caddy:
+   ```bash
+   sudo caddy validate --config /etc/caddy/Caddyfile
+   sudo systemctl reload caddy
+   ```
+
+### 9. Create Initial Admin User
+
+SSH into your server and create an admin user:
+
+```bash
+cd /opt/gooneral-wheelchair/backend
+node -e "
+const bcrypt = require('bcryptjs');
+const fs = require('fs');
+const path = require('path');
+
+const username = 'admin';
+const password = 'your-secure-password'; // Change this!
+const hashedPassword = bcrypt.hashSync(password, 10);
+
+const usersFile = path.join(__dirname, 'users.json');
+const users = [
+  {
+    id: 1,
+    username: username,
+    password: hashedPassword,
+    role: 'admin'
+  }
+];
+
+fs.writeFileSync(usersFile, JSON.stringify(users, null, 2));
+console.log('Admin user created:', username);
+"
+```
+
+## Post-Deployment
+
+### 1. Test Your Deployment
+
+1. **Frontend**: Visit https://yourdomain.com
+2. **Backend API**: Visit https://yourdomain.com/api/health
+3. **Admin Login**: Visit https://yourdomain.com/login
+
+### 2. Monitor Logs
+
+```bash
+# Backend logs
+sudo journalctl -u gooneral-wheelchair -f
+
+# Caddy logs
+sudo journalctl -u caddy -f
+
+# Application logs (if configured)
+sudo tail -f /var/log/caddy/gooneral-wheelchair.log
+```
+
+### 3. Security Checklist
+
+- ✅ Strong session secret configured
+- ✅ HTTPS enabled (automatic with Caddy)
+- ✅ Secure cookies enabled in production
+- ✅ Security headers configured
+- ✅ Admin user created with strong password
+- ✅ Service running as non-root user
+- ✅ Firewall configured (only ports 80, 443, SSH open)
+
+## Updating Your Blog
+
+### 1. Add New Posts
+
+Upload markdown files to `/opt/gooneral-wheelchair/public/posts/`
+
+### 2. Update Application
+
+1. Build new version locally
+2. Upload new `dist/` directory
+3. Update backend files if needed
+4. Restart services:
+   ```bash
+   sudo systemctl restart gooneral-wheelchair
+   sudo systemctl reload caddy
+   ```
+
+## Troubleshooting
+
+### Backend Won't Start
+```bash
+sudo systemctl status gooneral-wheelchair
+sudo journalctl -u gooneral-wheelchair -n 50
+```
+
+### Frontend Not Loading
+- Check Caddy configuration
+- Verify `dist/` directory contains built files
+- Check Caddy logs
+
+### API Not Working
+- Verify backend is running on port 3001
+- Check CORS configuration matches your domain
+- Verify reverse proxy in Caddyfile
+
+### Session Issues
+- Check session secret is set
+- Verify cookie settings for HTTPS
+- Clear browser cookies and try again
+
+## Backup
+
+Regular backups should include:
+- `/opt/gooneral-wheelchair/public/posts/` (your blog posts)
+- `/opt/gooneral-wheelchair/backend/users.json` (user accounts)
+- `/opt/gooneral-wheelchair/backend/themes.json` (theme settings)
+- `/opt/gooneral-wheelchair/backend/sessions/` (active sessions - optional)

+ 112 - 0
QUICK-DEPLOY.md

@@ -0,0 +1,112 @@
+# 🚀 Gooneral Wheelchair - One-Click Deployment
+
+Ready to deploy your blog? Choose your method:
+
+## 🎯 Option 1: Super Simple (Batch File)
+
+Just double-click `deploy.bat` and enter your domain, or run from command line:
+
+```batch
+# Create deployment package
+deploy.bat yourdomain.com
+
+# Deploy directly to server (requires SSH keys set up)
+deploy.bat yourdomain.com your.server.ip username
+```
+
+## 🎯 Option 2: PowerShell Commands
+
+```powershell
+# Create deployment package only
+.\deploy-now.ps1 -Domain "yourdomain.com"
+
+# Deploy directly to server
+.\deploy-now.ps1 -Domain "yourdomain.com" -ServerHost "your.server.ip" -ServerUser "username"
+```
+
+## 🎯 Option 3: Advanced PowerShell
+
+```powershell
+# Full control over the deployment process
+.\prepare-deployment.ps1 -Domain "yourdomain.com" -ServerHost "192.168.1.100" -ServerUser "myuser" -UploadToServer
+```
+
+## ✨ What Happens Automatically
+
+The scripts will:
+
+### 🏗️ **Build Phase**
+- ✅ Build your React frontend for production
+- ✅ Generate secure session secrets
+- ✅ Create production configuration files
+- ✅ Package everything for deployment
+
+### 🚀 **Deploy Phase** (if server specified)
+- ✅ Upload files to your server
+- ✅ Install Node.js dependencies
+- ✅ Create system user for security
+- ✅ Set up systemd service
+- ✅ Configure Caddy with auto-SSL
+- ✅ Create admin user with random password
+- ✅ Start all services
+
+### 🎉 **Result**
+- 🌐 Your blog live at `https://yourdomain.com`
+- 🔧 Admin panel at `https://yourdomain.com/admin`
+- 🔐 Secure HTTPS with automatic certificates
+- 👤 Admin credentials displayed after deployment
+
+## 📋 Prerequisites
+
+### On Your Windows Machine:
+- ✅ Node.js installed
+- ✅ PowerShell execution policy allows scripts
+- ✅ SSH client (for direct deployment)
+
+### On Your Server:
+- ✅ Ubuntu/Debian/CentOS Linux
+- ✅ Node.js v18+ installed
+- ✅ Caddy web server installed
+- ✅ Domain pointing to server IP
+- ✅ SSH access configured
+
+## 🔥 Example Usage
+
+```batch
+# I want to deploy myblog.com to my server at 192.168.1.100
+deploy.bat myblog.com 192.168.1.100 myuser
+```
+
+That's it! The script will handle everything and show you the admin credentials at the end.
+
+## 🛠️ Manual Upload Method
+
+If direct deployment doesn't work:
+
+1. Run: `deploy.bat yourdomain.com` (creates package only)
+2. Upload the `deployment-ready` folder to your server
+3. SSH to your server: `ssh user@your.server.ip`
+4. Navigate to uploaded folder: `cd deployment-ready`
+5. Run: `chmod +x deploy.sh && ./deploy.sh`
+
+## 🔐 Security Notes
+
+- 🚨 **Keep deployment files secure** - they contain session secrets
+- 🚨 **Change admin password** after first login
+- 🚨 **Enable firewall** on your server (ports 80, 443, SSH only)
+- 🚨 **Regular backups** of `/opt/gooneral-wheelchair/`
+
+## 💡 Tips
+
+- **First time?** Use the package-only option to review files before deploying
+- **Multiple sites?** Run the script multiple times with different domains
+- **Updates?** Just run the script again - it will update your existing installation
+- **Troubleshooting?** Check the logs with `sudo journalctl -u gooneral-wheelchair -f`
+
+## 🆘 Need Help?
+
+If something goes wrong:
+1. Check `DEPLOYMENT.md` for detailed manual instructions
+2. Verify prerequisites are installed
+3. Check server logs for error messages
+4. Ensure domain DNS is pointing to your server

+ 20 - 0
backend/.env.production

@@ -0,0 +1,20 @@
+# Production Environment Variables
+
+# Server Configuration
+NODE_ENV=production
+PORT=3001
+
+# Session Configuration - CHANGE THIS SECRET IN PRODUCTION!
+SESSION_SECRET=your-super-secret-session-key-change-this-immediately
+
+# CORS Configuration - Update with your actual domain
+FRONTEND_URL=https://goonblog.thevakhovske.eu.org
+
+# Database/Storage paths (relative to backend directory)
+POSTS_DIR=../public/posts
+THEMES_FILE=./themes.json
+SESSIONS_DIR=./sessions
+
+# Security Settings
+COOKIE_SECURE=true
+COOKIE_SAME_SITE=strict

+ 13 - 0
backend/package-lock.json

@@ -11,6 +11,7 @@
         "bcryptjs": "^3.0.2",
         "connect-session-file": "^1.0.4",
         "cors": "^2.8.5",
+        "dotenv": "^17.2.3",
         "express": "^4.21.1",
         "express-session": "^1.18.2",
         "fs-extra": "^11.2.0",
@@ -421,6 +422,18 @@
         "npm": "1.2.8000 || >= 1.4.16"
       }
     },
+    "node_modules/dotenv": {
+      "version": "17.2.3",
+      "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz",
+      "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==",
+      "license": "BSD-2-Clause",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://dotenvx.com"
+      }
+    },
     "node_modules/dunder-proto": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",

+ 2 - 0
backend/package.json

@@ -6,12 +6,14 @@
   "main": "server.js",
   "scripts": {
     "start": "node server.js",
+    "start:production": "NODE_ENV=production node start-production.js",
     "dev": "nodemon server.js"
   },
   "dependencies": {
     "bcryptjs": "^3.0.2",
     "connect-session-file": "^1.0.4",
     "cors": "^2.8.5",
+    "dotenv": "^17.2.3",
     "express": "^4.21.1",
     "express-session": "^1.18.2",
     "fs-extra": "^11.2.0",

+ 9 - 5
backend/server.js

@@ -1,3 +1,4 @@
+import dotenv from 'dotenv';
 import express from 'express';
 import cors from 'cors';
 import session from 'express-session';
@@ -6,6 +7,9 @@ import fs from 'fs-extra';
 import path from 'path';
 import { fileURLToPath } from 'url';
 import { v4 as uuidv4 } from 'uuid';
+
+// Load environment variables
+dotenv.config();
 import { authenticateUser, getUserByUsername, changeUserPassword } from './auth.js';
 import { 
   getAllThemes, 
@@ -27,12 +31,12 @@ const app = express();
 const PORT = process.env.PORT || 3001;
 
 // Paths
-const POSTS_DIR = path.join(__dirname, '../public/posts');
+const POSTS_DIR = path.resolve(__dirname, process.env.POSTS_DIR || '../public/posts');
 const INDEX_FILE = path.join(POSTS_DIR, 'index.json');
 
 // Middleware
 app.use(cors({
-  origin: 'http://localhost:5173', // Frontend URL
+  origin: process.env.FRONTEND_URL || 'http://localhost:5173', // Frontend URL
   credentials: true // Enable cookies
 }));
 app.use(express.json());
@@ -41,7 +45,7 @@ app.use(express.urlencoded({ extended: true }));
 // Session configuration
 app.use(session({
   store: new FileStoreSession({
-    path: path.join(__dirname, 'sessions'),
+    path: path.join(__dirname, process.env.SESSIONS_DIR || 'sessions'),
     ttl: 86400, // 24 hours in seconds
     retries: 5,
     factor: 1,
@@ -53,10 +57,10 @@ app.use(session({
   saveUninitialized: false,
   name: 'gooneral-session',
   cookie: {
-    secure: false, // Set to true in production with HTTPS
+    secure: process.env.COOKIE_SECURE === 'true' || process.env.NODE_ENV === 'production',
     httpOnly: true,
     maxAge: 24 * 60 * 60 * 1000, // 24 hours
-    sameSite: 'lax' // Help with cross-origin cookies
+    sameSite: process.env.COOKIE_SAME_SITE || (process.env.NODE_ENV === 'production' ? 'strict' : 'lax')
   }
 }));
 

+ 49 - 0
backend/start-production.js

@@ -0,0 +1,49 @@
+#!/usr/bin/env node
+
+import { spawn } from 'child_process';
+import path from 'path';
+import { fileURLToPath } from 'url';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+
+// Set production environment
+process.env.NODE_ENV = 'production';
+
+// Load production environment file if it exists
+const envFile = path.join(__dirname, '.env.production');
+
+console.log('🚀 Starting Gooneral Wheelchair Backend in Production Mode');
+console.log('📁 Environment:', process.env.NODE_ENV);
+console.log('🌐 Port:', process.env.PORT || 3001);
+
+// Start the server
+const server = spawn('node', ['server.js'], {
+  cwd: __dirname,
+  stdio: 'inherit',
+  env: {
+    ...process.env,
+    NODE_ENV: 'production'
+  }
+});
+
+server.on('error', (err) => {
+  console.error('❌ Failed to start server:', err);
+  process.exit(1);
+});
+
+server.on('close', (code) => {
+  console.log(`🛑 Server process exited with code ${code}`);
+  process.exit(code);
+});
+
+// Graceful shutdown
+process.on('SIGINT', () => {
+  console.log('\n🛑 Received SIGINT, shutting down gracefully...');
+  server.kill('SIGINT');
+});
+
+process.on('SIGTERM', () => {
+  console.log('\n🛑 Received SIGTERM, shutting down gracefully...');
+  server.kill('SIGTERM');
+});

+ 193 - 0
create-deployment-package.js

@@ -0,0 +1,193 @@
+#!/usr/bin/env node
+
+import fs from 'fs-extra';
+import path from 'path';
+import { fileURLToPath } from 'url';
+import archiver from 'archiver';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+
+const deploymentDir = path.join(__dirname, 'deployment-package');
+const archivePath = path.join(__dirname, 'gooneral-wheelchair-deployment.zip');
+
+console.log('🚀 Creating deployment package...\n');
+
+async function createDeploymentPackage() {
+  try {
+    // Clean up previous package
+    await fs.remove(deploymentDir);
+    await fs.remove(archivePath);
+    
+    // Create deployment directory
+    await fs.ensureDir(deploymentDir);
+    
+    console.log('📁 Copying files...');
+    
+    // Copy built frontend
+    if (await fs.pathExists(path.join(__dirname, 'dist'))) {
+      await fs.copy(path.join(__dirname, 'dist'), path.join(deploymentDir, 'dist'));
+      console.log('✅ Frontend build copied');
+    } else {
+      console.log('⚠️  Frontend not built - run "npm run build" first');
+    }
+    
+    // Copy backend files
+    const backendFiles = [
+      'server.js',
+      'start-production.js',
+      'package.json',
+      'auth.js',
+      'themes.js',
+      '.env.production'
+    ];
+    
+    const backendDir = path.join(deploymentDir, 'backend');
+    await fs.ensureDir(backendDir);
+    
+    for (const file of backendFiles) {
+      const srcPath = path.join(__dirname, 'backend', file);
+      const destPath = path.join(backendDir, file);
+      
+      if (await fs.pathExists(srcPath)) {
+        await fs.copy(srcPath, destPath);
+        console.log(`✅ ${file} copied`);
+      } else {
+        console.log(`⚠️  ${file} not found`);
+      }
+    }
+    
+    // Copy public directory
+    if (await fs.pathExists(path.join(__dirname, 'public'))) {
+      await fs.copy(path.join(__dirname, 'public'), path.join(deploymentDir, 'public'));
+      console.log('✅ Public directory copied');
+    }
+    
+    // Copy configuration files
+    const configFiles = [
+      'Caddyfile',
+      'DEPLOYMENT.md'
+    ];
+    
+    for (const file of configFiles) {
+      const srcPath = path.join(__dirname, file);
+      const destPath = path.join(deploymentDir, file);
+      
+      if (await fs.pathExists(srcPath)) {
+        await fs.copy(srcPath, destPath);
+        console.log(`✅ ${file} copied`);
+      }
+    }
+    
+    // Create systemd service file
+    const systemdService = `[Unit]
+Description=Gooneral Wheelchair CMS Backend
+After=network.target
+
+[Service]
+Type=simple
+User=gooneral
+WorkingDirectory=/opt/gooneral-wheelchair/backend
+Environment=NODE_ENV=production
+ExecStart=/usr/bin/node start-production.js
+Restart=always
+RestartSec=10
+StandardOutput=syslog
+StandardError=syslog
+SyslogIdentifier=gooneral-wheelchair
+
+[Install]
+WantedBy=multi-user.target
+`;
+    
+    await fs.writeFile(path.join(deploymentDir, 'gooneral-wheelchair.service'), systemdService);
+    console.log('✅ Systemd service file created');
+    
+    // Create deployment script
+    const deployScript = `#!/bin/bash
+
+# Gooneral Wheelchair Deployment Script
+echo "🚀 Deploying Gooneral Wheelchair CMS..."
+
+# Check if running as root
+if [[ $EUID -eq 0 ]]; then
+   echo "❌ This script should not be run as root"
+   exit 1
+fi
+
+# Create application directory
+sudo mkdir -p /opt/gooneral-wheelchair
+sudo chown $USER:$USER /opt/gooneral-wheelchair
+
+# Copy files
+echo "📁 Copying application files..."
+cp -r dist /opt/gooneral-wheelchair/
+cp -r backend /opt/gooneral-wheelchair/
+cp -r public /opt/gooneral-wheelchair/
+cp Caddyfile /opt/gooneral-wheelchair/
+
+# Install backend dependencies
+echo "📦 Installing backend dependencies..."
+cd /opt/gooneral-wheelchair/backend
+npm install --production
+
+# Create directories
+mkdir -p sessions
+mkdir -p ../public/posts
+
+# Set up systemd service
+echo "⚙️ Setting up systemd service..."
+sudo cp ../gooneral-wheelchair.service /etc/systemd/system/
+sudo systemctl daemon-reload
+
+echo "✅ Deployment complete!"
+echo ""
+echo "Next steps:"
+echo "1. Edit /opt/gooneral-wheelchair/backend/.env.production"
+echo "2. Update Caddyfile with your domain"
+echo "3. Create admin user (see DEPLOYMENT.md)"
+echo "4. Start services:"
+echo "   sudo systemctl enable gooneral-wheelchair"
+echo "   sudo systemctl start gooneral-wheelchair"
+echo "   sudo systemctl reload caddy"
+`;
+    
+    await fs.writeFile(path.join(deploymentDir, 'deploy.sh'), deployScript);
+    await fs.chmod(path.join(deploymentDir, 'deploy.sh'), '755');
+    console.log('✅ Deployment script created');
+    
+    console.log('\n📦 Creating ZIP archive...');
+    
+    // Create ZIP archive
+    const output = fs.createWriteStream(archivePath);
+    const archive = archiver('zip', { zlib: { level: 9 } });
+    
+    archive.pipe(output);
+    archive.directory(deploymentDir, 'gooneral-wheelchair');
+    await archive.finalize();
+    
+    await new Promise((resolve, reject) => {
+      output.on('close', resolve);
+      archive.on('error', reject);
+    });
+    
+    console.log(`✅ Deployment package created: ${path.basename(archivePath)}`);
+    console.log(`📊 Archive size: ${(archive.pointer() / 1024 / 1024).toFixed(2)} MB`);
+    
+    // Clean up temporary directory
+    await fs.remove(deploymentDir);
+    
+    console.log('\n🎉 Ready for deployment!');
+    console.log('\nNext steps:');
+    console.log('1. Upload gooneral-wheelchair-deployment.zip to your server');
+    console.log('2. Extract: unzip gooneral-wheelchair-deployment.zip');
+    console.log('3. Run: cd gooneral-wheelchair && ./deploy.sh');
+    console.log('4. Follow the instructions in DEPLOYMENT.md');
+    
+  } catch (error) {
+    console.error('❌ Error creating deployment package:', error);
+    process.exit(1);
+  }
+}
+
+createDeploymentPackage();

+ 25 - 0
deploy-now.ps1

@@ -0,0 +1,25 @@
+# Gooneral Wheelchair - One-Line Deployment
+# Usage: .\deploy-now.ps1 -Domain "yourdomain.com" [-ServerHost "your.server.ip"] [-ServerUser "username"]
+
+param(
+    [Parameter(Mandatory=$true)]
+    [string]$Domain,
+    
+    [Parameter(Mandatory=$false)]
+    [string]$ServerHost,
+    
+    [Parameter(Mandatory=$false)]
+    [string]$ServerUser = "root"
+)
+
+if ($ServerHost) {
+    Write-Host "🚀 Running full deployment to server..." -ForegroundColor Green
+    & .\prepare-deployment.ps1 -Domain $Domain -ServerHost $ServerHost -ServerUser $ServerUser -UploadToServer
+} else {
+    Write-Host "📦 Preparing deployment package..." -ForegroundColor Green
+    & .\prepare-deployment.ps1 -Domain $Domain
+    
+    Write-Host ""
+    Write-Host "✨ Want to deploy directly to your server?" -ForegroundColor Yellow
+    Write-Host "Run: .\deploy-now.ps1 -Domain '$Domain' -ServerHost 'your.server.ip' -ServerUser 'username'" -ForegroundColor Gray
+}

+ 44 - 0
deploy-now.sh

@@ -0,0 +1,44 @@
+#!/bin/bash
+
+# Gooneral Wheelchair - One-Line Deployment for Linux/macOS
+# Usage: ./deploy-now.sh yourdomain.com [server-host] [server-user]
+
+# Colors for output
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+BLUE='\033[0;34m'
+CYAN='\033[0;36m'
+NC='\033[0m' # No Color
+
+DOMAIN="$1"
+SERVER_HOST="$2"
+SERVER_USER="${3:-root}"
+
+if [ -z "$DOMAIN" ]; then
+    echo -e "${RED}❌ Error: Domain name is required${NC}"
+    echo ""
+    echo "Usage Examples:"
+    echo "  $0 yourdomain.com                    # Create deployment package"
+    echo "  $0 yourdomain.com server-ip          # Deploy directly to server" 
+    echo "  $0 yourdomain.com server-ip username # Deploy with custom user"
+    exit 1
+fi
+
+echo -e "${GREEN}🚀 Gooneral Wheelchair - One-Line Deploy${NC}"
+echo -e "${CYAN}📁 Domain: $DOMAIN${NC}"
+if [ -n "$SERVER_HOST" ]; then
+    echo -e "${CYAN}🌐 Server: $SERVER_HOST${NC}"
+fi
+
+if [ -n "$SERVER_HOST" ]; then
+    echo -e "${GREEN}🚀 Running full deployment to server...${NC}"
+    ./prepare-deployment.sh "$DOMAIN" "$SERVER_HOST" "$SERVER_USER"
+else
+    echo -e "${GREEN}📦 Preparing deployment package...${NC}"
+    ./prepare-deployment.sh "$DOMAIN"
+    
+    echo ""
+    echo -e "${YELLOW}✨ Want to deploy directly to your server?${NC}"
+    echo -e "${BLUE}Run: $0 $DOMAIN your.server.ip username${NC}"
+fi

+ 40 - 0
deploy.bat

@@ -0,0 +1,40 @@
+@echo off
+echo 🚀 Gooneral Wheelchair - Quick Deploy Script
+echo.
+
+if "%1"=="" (
+    echo ❌ Error: Domain name is required
+    echo.
+    echo Usage Examples:
+    echo   deploy.bat yourdomain.com
+    echo   deploy.bat yourdomain.com server-ip username
+    echo.
+    pause
+    exit /b 1
+)
+
+set DOMAIN=%1
+set SERVER_HOST=%2
+set SERVER_USER=%3
+
+if "%SERVER_USER%"=="" set SERVER_USER=root
+
+echo 📁 Domain: %DOMAIN%
+if not "%SERVER_HOST%"=="" echo 🌐 Server: %SERVER_HOST%
+
+if "%SERVER_HOST%"=="" (
+    echo 📦 Creating deployment package...
+    powershell -ExecutionPolicy Bypass -File "deploy-now.ps1" -Domain "%DOMAIN%"
+) else (
+    echo 🚀 Deploying directly to server...
+    powershell -ExecutionPolicy Bypass -File "deploy-now.ps1" -Domain "%DOMAIN%" -ServerHost "%SERVER_HOST%" -ServerUser "%SERVER_USER%"
+)
+
+echo.
+if %ERRORLEVEL% EQU 0 (
+    echo ✅ Deployment script completed!
+) else (
+    echo ❌ Deployment script failed!
+)
+
+pause

二进制
gooneral-wheelchair-deployment.zip


文件差异内容过多而无法显示
+ 762 - 0
package-lock.json


+ 2 - 0
package.json

@@ -32,11 +32,13 @@
     "@types/react": "^19.1.10",
     "@types/react-dom": "^19.1.7",
     "@vitejs/plugin-react": "^5.0.0",
+    "archiver": "^7.0.1",
     "autoprefixer": "^10.4.21",
     "concurrently": "^9.2.1",
     "eslint": "^9.33.0",
     "eslint-plugin-react-hooks": "^5.2.0",
     "eslint-plugin-react-refresh": "^0.4.20",
+    "fs-extra": "^11.3.2",
     "globals": "^16.3.0",
     "postcss": "^8.5.6",
     "tailwindcss": "^4.1.13",

+ 425 - 0
prepare-deployment.ps1

@@ -0,0 +1,425 @@
+# Gooneral Wheelchair - Local Deployment Preparation Script
+# Run this on your Windows development machine
+
+param(
+    [Parameter(Mandatory=$true)]
+    [string]$Domain,
+    
+    [Parameter(Mandatory=$false)]
+    [string]$ServerUser = "root",
+    
+    [Parameter(Mandatory=$false)]
+    [string]$ServerHost,
+    
+    [Parameter(Mandatory=$false)]
+    [switch]$UploadToServer
+)
+
+Write-Host "🚀 Preparing Gooneral Wheelchair for deployment..." -ForegroundColor Green
+Write-Host "📁 Domain: $Domain" -ForegroundColor Cyan
+
+# Generate a secure session secret
+$SessionSecret = [System.Web.Security.Membership]::GeneratePassword(64, 10)
+
+# Build the frontend
+Write-Host "🏗️  Building frontend..." -ForegroundColor Yellow
+npm run build
+if ($LASTEXITCODE -ne 0) {
+    Write-Host "❌ Frontend build failed!" -ForegroundColor Red
+    exit 1
+}
+
+# Create deployment directory
+$DeployDir = ".\deployment-ready"
+if (Test-Path $DeployDir) {
+    Remove-Item $DeployDir -Recurse -Force
+}
+New-Item -ItemType Directory -Path $DeployDir -Force | Out-Null
+
+Write-Host "📦 Packaging files..." -ForegroundColor Yellow
+
+# Copy built frontend
+Copy-Item -Path ".\dist" -Destination "$DeployDir\dist" -Recurse
+
+# Copy backend files
+$BackendDir = "$DeployDir\backend"
+New-Item -ItemType Directory -Path $BackendDir -Force | Out-Null
+
+$BackendFiles = @(
+    "server.js",
+    "start-production.js", 
+    "package.json",
+    "auth.js",
+    "themes.js"
+)
+
+foreach ($file in $BackendFiles) {
+    if (Test-Path ".\backend\$file") {
+        Copy-Item -Path ".\backend\$file" -Destination "$BackendDir\$file"
+    }
+}
+
+# Copy public directory
+if (Test-Path ".\public") {
+    Copy-Item -Path ".\public" -Destination "$DeployDir\public" -Recurse
+}
+
+# Create production environment file with the generated secret
+$EnvContent = @"
+# Production Environment Variables - Generated $(Get-Date)
+
+# Server Configuration
+NODE_ENV=production
+PORT=3001
+
+# IMPORTANT: This session secret was auto-generated
+# Keep this secret and secure!
+SESSION_SECRET=$SessionSecret
+
+# CORS Configuration
+FRONTEND_URL=https://$Domain
+
+# Database/Storage paths (relative to backend directory)  
+POSTS_DIR=../public/posts
+THEMES_FILE=./themes.json
+SESSIONS_DIR=./sessions
+
+# Security Settings
+COOKIE_SECURE=true
+COOKIE_SAME_SITE=strict
+"@
+
+$EnvContent | Out-File -FilePath "$BackendDir\.env.production" -Encoding UTF8
+
+# Create Caddyfile with the actual domain
+$CaddyContent = @"
+# Caddy configuration for $Domain
+$Domain {
+    # Enable automatic HTTPS
+    # Caddy will automatically get and renew SSL certificates
+
+    # Serve the React frontend (static files)
+    root * dist
+    
+    # Try to serve static files first, then fallback to index.html for SPA routing
+    try_files {path} /index.html
+    
+    # API routes - proxy to backend
+    handle /api/* {
+        reverse_proxy localhost:3001
+    }
+    
+    # Health check endpoint  
+    handle /health {
+        reverse_proxy localhost:3001
+    }
+    
+    # Serve static files with proper headers
+    header /assets/* {
+        Cache-Control "public, max-age=31536000, immutable"
+    }
+    
+    # Security headers
+    header {
+        # Enable HSTS
+        Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
+        
+        # Prevent clickjacking
+        X-Frame-Options "DENY"
+        
+        # Prevent MIME type sniffing
+        X-Content-Type-Options "nosniff"
+        
+        # XSS protection
+        X-XSS-Protection "1; mode=block"
+        
+        # Referrer policy
+        Referrer-Policy "strict-origin-when-cross-origin"
+        
+        # Content Security Policy (adjust as needed)
+        Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:;"
+    }
+    
+    # Gzip compression
+    encode gzip
+    
+    # Logging
+    log {
+        output file /var/log/caddy/gooneral-wheelchair.log
+        format json
+    }
+}
+"@
+
+$CaddyContent | Out-File -FilePath "$DeployDir\Caddyfile" -Encoding UTF8
+
+# Create systemd service file
+$ServiceContent = @"
+[Unit]
+Description=Gooneral Wheelchair CMS Backend
+After=network.target
+
+[Service]
+Type=simple
+User=gooneral
+WorkingDirectory=/opt/gooneral-wheelchair/backend
+Environment=NODE_ENV=production
+ExecStart=/usr/bin/node start-production.js
+Restart=always
+RestartSec=10
+StandardOutput=syslog
+StandardError=syslog
+SyslogIdentifier=gooneral-wheelchair
+
+[Install]
+WantedBy=multi-user.target
+"@
+
+$ServiceContent | Out-File -FilePath "$DeployDir\gooneral-wheelchair.service" -Encoding UTF8
+
+# Create the server deployment script
+$ServerDeployScript = @"
+#!/bin/bash
+
+# Gooneral Wheelchair - Automated Server Deployment Script
+# This script will fully set up your CMS on the server
+
+set -e  # Exit on any error
+
+echo "🚀 Starting automated deployment of Gooneral Wheelchair CMS..."
+
+# Check if running as root
+if [[ `$EUID -eq 0 ]]; then
+   echo "❌ Please run this script as a regular user with sudo privileges, not as root"
+   exit 1
+fi
+
+# Check if required commands exist
+for cmd in node npm caddy systemctl; do
+    if ! command -v `$cmd &> /dev/null; then
+        echo "❌ Required command '`$cmd' not found. Please install it first."
+        exit 1
+    fi
+done
+
+echo "✅ Prerequisites check passed"
+
+# Generate random password for admin user
+ADMIN_PASSWORD=`$(openssl rand -base64 32 | tr -d "=+/" | cut -c1-16)
+
+# Create application directory
+echo "📁 Setting up application directory..."
+sudo mkdir -p /opt/gooneral-wheelchair
+sudo chown `$USER:`$USER /opt/gooneral-wheelchair
+
+# Copy files
+echo "📦 Installing application files..."
+cp -r dist /opt/gooneral-wheelchair/
+cp -r backend /opt/gooneral-wheelchair/
+cp -r public /opt/gooneral-wheelchair/ 2>/dev/null || mkdir -p /opt/gooneral-wheelchair/public/posts
+
+# Install backend dependencies
+echo "📥 Installing backend dependencies..."
+cd /opt/gooneral-wheelchair/backend
+npm install --production --silent
+
+# Create required directories
+mkdir -p sessions
+mkdir -p ../public/posts
+
+# Set up system user for security
+echo "👤 Setting up system user..."
+if ! id "gooneral" &>/dev/null; then
+    sudo useradd --system --shell /bin/false --home /opt/gooneral-wheelchair gooneral
+fi
+
+# Set proper ownership
+sudo chown -R gooneral:gooneral /opt/gooneral-wheelchair
+
+# Make start script executable  
+chmod +x start-production.js
+
+# Install and start systemd service
+echo "⚙️ Setting up systemd service..."
+sudo cp ../gooneral-wheelchair.service /etc/systemd/system/
+sudo systemctl daemon-reload
+sudo systemctl enable gooneral-wheelchair
+sudo systemctl start gooneral-wheelchair
+
+# Give the service a moment to start
+sleep 3
+
+if sudo systemctl is-active --quiet gooneral-wheelchair; then
+    echo "✅ Backend service started successfully"
+else
+    echo "❌ Backend service failed to start. Checking logs..."
+    sudo systemctl status gooneral-wheelchair
+    exit 1
+fi
+
+# Set up Caddy
+echo "🌐 Configuring Caddy..."
+sudo cp Caddyfile /etc/caddy/Caddyfile
+
+# Test Caddy configuration
+if sudo caddy validate --config /etc/caddy/Caddyfile; then
+    echo "✅ Caddy configuration is valid"
+    sudo systemctl reload caddy
+else
+    echo "❌ Caddy configuration is invalid"
+    exit 1
+fi
+
+# Create admin user
+echo "👨‍💼 Creating admin user..."
+cd /opt/gooneral-wheelchair/backend
+node -e "
+const bcrypt = require('bcryptjs');
+const fs = require('fs');
+const path = require('path');
+
+const username = 'admin';
+const password = '`$ADMIN_PASSWORD';
+const hashedPassword = bcrypt.hashSync(password, 10);
+
+const usersFile = path.join(__dirname, 'users.json');
+const users = [
+  {
+    id: 1,
+    username: username,
+    password: hashedPassword,
+    role: 'admin'
+  }
+];
+
+fs.writeFileSync(usersFile, JSON.stringify(users, null, 2));
+console.log('Admin user created successfully');
+"
+
+# Set proper ownership again after creating files
+sudo chown -R gooneral:gooneral /opt/gooneral-wheelchair
+
+# Final status check
+echo ""
+echo "🎉 Deployment completed successfully!"
+echo ""
+echo "==================== IMPORTANT ===================="
+echo "🔐 Your admin credentials:"
+echo "   Username: admin"
+echo "   Password: `$ADMIN_PASSWORD"
+echo ""
+echo "⚠️  SAVE THESE CREDENTIALS SECURELY!"
+echo "⚠️  Change the password after first login!"
+echo "==================== IMPORTANT ===================="
+echo ""
+echo "🌐 Your blog is now available at: https://$Domain"
+echo "🔧 Admin panel: https://$Domain/admin"
+echo ""
+echo "📊 Service status:"
+sudo systemctl status gooneral-wheelchair --no-pager
+echo ""
+sudo systemctl status caddy --no-pager
+echo ""
+echo "📝 To view logs:"
+echo "   Backend: sudo journalctl -u gooneral-wheelchair -f"
+echo "   Caddy: sudo journalctl -u caddy -f"
+echo ""
+echo "✅ Deployment complete!"
+"@
+
+$ServerDeployScript | Out-File -FilePath "$DeployDir\deploy.sh" -Encoding UTF8
+
+# Create a README for the deployment
+$ReadmeContent = @"
+# Gooneral Wheelchair - Deployment Package
+
+## Quick Deployment
+
+1. Upload this entire directory to your server
+2. Run the deployment script: `./deploy.sh`
+3. Access your blog at https://$Domain
+
+## What's Included
+
+- Built frontend in `dist/`
+- Backend application in `backend/`
+- Auto-configured Caddyfile for $Domain  
+- Systemd service configuration
+- Automated deployment script
+
+## Generated Configuration
+
+- **Domain**: $Domain
+- **Session Secret**: Auto-generated (64 characters)
+- **Admin User**: Will be created automatically
+- **SSL**: Automatic via Caddy + Let's Encrypt
+
+## After Deployment
+
+The script will output your admin credentials. Save them securely!
+
+Access your blog:
+- **Blog**: https://$Domain
+- **Admin**: https://$Domain/admin
+
+## Manual Steps (if needed)
+
+If the automated script doesn't work, see DEPLOYMENT.md for manual instructions.
+"@
+
+$ReadmeContent | Out-File -FilePath "$DeployDir\README.md" -Encoding UTF8
+
+# Copy the detailed deployment guide
+Copy-Item -Path ".\DEPLOYMENT.md" -Destination "$DeployDir\DEPLOYMENT.md"
+
+# Create the upload script if server details provided
+if ($ServerHost -and $UploadToServer) {
+    Write-Host "📤 Uploading to server..." -ForegroundColor Yellow
+    
+    # Create a tar.gz for easier upload (requires tar on Windows or WSL)
+    $TarFile = "gooneral-wheelchair-deployment.tar.gz"
+    
+    if (Get-Command tar -ErrorAction SilentlyContinue) {
+        tar -czf $TarFile -C $DeployDir .
+        
+        # Upload and deploy
+        scp $TarFile "${ServerUser}@${ServerHost}:~/"
+        ssh "${ServerUser}@${ServerHost}" "
+            mkdir -p gooneral-wheelchair-deploy
+            cd gooneral-wheelchair-deploy  
+            tar -xzf ../$TarFile
+            chmod +x deploy.sh
+            ./deploy.sh
+        "
+        
+        Remove-Item $TarFile
+        Write-Host "✅ Deployment completed remotely!" -ForegroundColor Green
+    } else {
+        Write-Host "⚠️  tar command not found. Please upload manually or install tar." -ForegroundColor Yellow
+    }
+}
+
+Write-Host ""
+Write-Host "🎉 Deployment package ready!" -ForegroundColor Green
+Write-Host "📁 Location: $DeployDir" -ForegroundColor Cyan
+Write-Host ""
+Write-Host "Next steps:" -ForegroundColor Yellow
+Write-Host "1. Upload the '$DeployDir' folder to your server" -ForegroundColor White
+Write-Host "2. SSH to your server and run: ./deploy.sh" -ForegroundColor White
+Write-Host "3. Access your blog at https://$Domain" -ForegroundColor White
+Write-Host ""
+Write-Host "💡 Or run this again with -UploadToServer and -ServerHost flags for automatic upload" -ForegroundColor Gray
+
+# Save deployment info
+$DeployInfo = @{
+    Domain = $Domain
+    SessionSecret = $SessionSecret
+    DeploymentTime = Get-Date
+    DeploymentPath = $DeployDir
+}
+
+$DeployInfo | ConvertTo-Json | Out-File -FilePath "$DeployDir\deployment-info.json" -Encoding UTF8
+
+Write-Host ""
+Write-Host "✅ Session secret generated and saved securely in deployment package" -ForegroundColor Green
+Write-Host "🔐 Keep your deployment files secure - they contain sensitive configuration!" -ForegroundColor Red

+ 440 - 0
prepare-deployment.sh

@@ -0,0 +1,440 @@
+#!/bin/bash
+
+# Gooneral Wheelchair - Linux/macOS Deployment Preparation Script
+# Usage: ./prepare-deployment.sh yourdomain.com [server-host] [server-user]
+
+set -e  # Exit on any error
+
+# Colors for output
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+BLUE='\033[0;34m'
+CYAN='\033[0;36m'
+NC='\033[0m' # No Color
+
+# Parse arguments
+DOMAIN="$1"
+SERVER_HOST="$2"
+SERVER_USER="${3:-root}"
+
+if [ -z "$DOMAIN" ]; then
+    echo -e "${RED}❌ Error: Domain name is required${NC}"
+    echo ""
+    echo "Usage:"
+    echo "  $0 yourdomain.com                    # Create deployment package"
+    echo "  $0 yourdomain.com server-ip          # Deploy directly to server"
+    echo "  $0 yourdomain.com server-ip username # Deploy with custom user"
+    exit 1
+fi
+
+echo -e "${GREEN}🚀 Preparing Gooneral Wheelchair for deployment...${NC}"
+echo -e "${CYAN}📁 Domain: $DOMAIN${NC}"
+
+# Generate secure session secret
+SESSION_SECRET=$(openssl rand -base64 64 | tr -d "=+/" | cut -c1-64)
+
+# Build the frontend
+echo -e "${YELLOW}🏗️  Building frontend...${NC}"
+npm run build
+if [ $? -ne 0 ]; then
+    echo -e "${RED}❌ Frontend build failed!${NC}"
+    exit 1
+fi
+
+# Create deployment directory
+DEPLOY_DIR="./deployment-ready"
+if [ -d "$DEPLOY_DIR" ]; then
+    rm -rf "$DEPLOY_DIR"
+fi
+mkdir -p "$DEPLOY_DIR"
+
+echo -e "${YELLOW}📦 Packaging files...${NC}"
+
+# Copy built frontend
+cp -r ./dist "$DEPLOY_DIR/"
+
+# Copy backend files
+BACKEND_DIR="$DEPLOY_DIR/backend"
+mkdir -p "$BACKEND_DIR"
+
+BACKEND_FILES=(
+    "server.js"
+    "start-production.js"
+    "package.json"
+    "auth.js"
+    "themes.js"
+)
+
+for file in "${BACKEND_FILES[@]}"; do
+    if [ -f "./backend/$file" ]; then
+        cp "./backend/$file" "$BACKEND_DIR/"
+    fi
+done
+
+# Copy public directory if it exists
+if [ -d "./public" ]; then
+    cp -r ./public "$DEPLOY_DIR/"
+fi
+
+# Create production environment file
+cat > "$BACKEND_DIR/.env.production" << EOF
+# Production Environment Variables - Generated $(date)
+
+# Server Configuration
+NODE_ENV=production
+PORT=3001
+
+# IMPORTANT: This session secret was auto-generated
+# Keep this secret and secure!
+SESSION_SECRET=$SESSION_SECRET
+
+# CORS Configuration
+FRONTEND_URL=https://$DOMAIN
+
+# Database/Storage paths (relative to backend directory)
+POSTS_DIR=../public/posts
+THEMES_FILE=./themes.json
+SESSIONS_DIR=./sessions
+
+# Security Settings
+COOKIE_SECURE=true
+COOKIE_SAME_SITE=strict
+EOF
+
+# Create Caddyfile with the actual domain
+cat > "$DEPLOY_DIR/Caddyfile" << EOF
+# Caddy configuration for $DOMAIN
+$DOMAIN {
+    # Enable automatic HTTPS
+    # Caddy will automatically get and renew SSL certificates
+
+    # Serve the React frontend (static files)
+    root * dist
+    
+    # Try to serve static files first, then fallback to index.html for SPA routing
+    try_files {path} /index.html
+    
+    # API routes - proxy to backend
+    handle /api/* {
+        reverse_proxy localhost:3001
+    }
+    
+    # Health check endpoint
+    handle /health {
+        reverse_proxy localhost:3001
+    }
+    
+    # Serve static files with proper headers
+    header /assets/* {
+        Cache-Control "public, max-age=31536000, immutable"
+    }
+    
+    # Security headers
+    header {
+        # Enable HSTS
+        Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
+        
+        # Prevent clickjacking
+        X-Frame-Options "DENY"
+        
+        # Prevent MIME type sniffing
+        X-Content-Type-Options "nosniff"
+        
+        # XSS protection
+        X-XSS-Protection "1; mode=block"
+        
+        # Referrer policy
+        Referrer-Policy "strict-origin-when-cross-origin"
+        
+        # Content Security Policy (adjust as needed)
+        Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:;"
+    }
+    
+    # Gzip compression
+    encode gzip
+    
+    # Logging
+    log {
+        output file /var/log/caddy/gooneral-wheelchair.log
+        format json
+    }
+}
+EOF
+
+# Create systemd service file
+cat > "$DEPLOY_DIR/gooneral-wheelchair.service" << EOF
+[Unit]
+Description=Gooneral Wheelchair CMS Backend
+After=network.target
+
+[Service]
+Type=simple
+User=gooneral
+WorkingDirectory=/opt/gooneral-wheelchair/backend
+Environment=NODE_ENV=production
+ExecStart=/usr/bin/node start-production.js
+Restart=always
+RestartSec=10
+StandardOutput=syslog
+StandardError=syslog
+SyslogIdentifier=gooneral-wheelchair
+
+[Install]
+WantedBy=multi-user.target
+EOF
+
+# Create the server deployment script
+cat > "$DEPLOY_DIR/deploy.sh" << 'EOF'
+#!/bin/bash
+
+# Gooneral Wheelchair - Automated Server Deployment Script
+# This script will fully set up your CMS on the server
+
+set -e  # Exit on any error
+
+# Colors for output
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+BLUE='\033[0;34m'
+NC='\033[0m'
+
+echo -e "${GREEN}🚀 Starting automated deployment of Gooneral Wheelchair CMS...${NC}"
+
+# Check if running as root
+if [[ $EUID -eq 0 ]]; then
+   echo -e "${RED}❌ Please run this script as a regular user with sudo privileges, not as root${NC}"
+   exit 1
+fi
+
+# Check if required commands exist
+for cmd in node npm caddy systemctl; do
+    if ! command -v $cmd &> /dev/null; then
+        echo -e "${RED}❌ Required command '$cmd' not found. Please install it first.${NC}"
+        exit 1
+    fi
+done
+
+echo -e "${GREEN}✅ Prerequisites check passed${NC}"
+
+# Generate random password for admin user
+ADMIN_PASSWORD=$(openssl rand -base64 32 | tr -d "=+/" | cut -c1-16)
+
+# Create application directory
+echo -e "${YELLOW}📁 Setting up application directory...${NC}"
+sudo mkdir -p /opt/gooneral-wheelchair
+sudo chown $USER:$USER /opt/gooneral-wheelchair
+
+# Copy files
+echo -e "${YELLOW}📦 Installing application files...${NC}"
+cp -r dist /opt/gooneral-wheelchair/
+cp -r backend /opt/gooneral-wheelchair/
+cp -r public /opt/gooneral-wheelchair/ 2>/dev/null || mkdir -p /opt/gooneral-wheelchair/public/posts
+
+# Install backend dependencies
+echo -e "${YELLOW}📥 Installing backend dependencies...${NC}"
+cd /opt/gooneral-wheelchair/backend
+npm install --production --silent
+
+# Create required directories
+mkdir -p sessions
+mkdir -p ../public/posts
+
+# Set up system user for security
+echo -e "${YELLOW}👤 Setting up system user...${NC}"
+if ! id "gooneral" &>/dev/null; then
+    sudo useradd --system --shell /bin/false --home /opt/gooneral-wheelchair gooneral
+fi
+
+# Set proper ownership
+sudo chown -R gooneral:gooneral /opt/gooneral-wheelchair
+
+# Make start script executable
+chmod +x start-production.js
+
+# Install and start systemd service
+echo -e "${YELLOW}⚙️ Setting up systemd service...${NC}"
+sudo cp ../gooneral-wheelchair.service /etc/systemd/system/
+sudo systemctl daemon-reload
+sudo systemctl enable gooneral-wheelchair
+sudo systemctl start gooneral-wheelchair
+
+# Give the service a moment to start
+sleep 3
+
+if sudo systemctl is-active --quiet gooneral-wheelchair; then
+    echo -e "${GREEN}✅ Backend service started successfully${NC}"
+else
+    echo -e "${RED}❌ Backend service failed to start. Checking logs...${NC}"
+    sudo systemctl status gooneral-wheelchair
+    exit 1
+fi
+
+# Set up Caddy
+echo -e "${YELLOW}🌐 Configuring Caddy...${NC}"
+sudo cp Caddyfile /etc/caddy/Caddyfile
+
+# Test Caddy configuration
+if sudo caddy validate --config /etc/caddy/Caddyfile; then
+    echo -e "${GREEN}✅ Caddy configuration is valid${NC}"
+    sudo systemctl reload caddy
+else
+    echo -e "${RED}❌ Caddy configuration is invalid${NC}"
+    exit 1
+fi
+
+# Create admin user
+echo -e "${YELLOW}👨‍💼 Creating admin user...${NC}"
+cd /opt/gooneral-wheelchair/backend
+node -e "
+const bcrypt = require('bcryptjs');
+const fs = require('fs');
+const path = require('path');
+
+const username = 'admin';
+const password = '$ADMIN_PASSWORD';
+const hashedPassword = bcrypt.hashSync(password, 10);
+
+const usersFile = path.join(__dirname, 'users.json');
+const users = [
+  {
+    id: 1,
+    username: username,
+    password: hashedPassword,
+    role: 'admin'
+  }
+];
+
+fs.writeFileSync(usersFile, JSON.stringify(users, null, 2));
+console.log('Admin user created successfully');
+"
+
+# Set proper ownership again after creating files
+sudo chown -R gooneral:gooneral /opt/gooneral-wheelchair
+
+# Extract domain from Caddyfile
+DOMAIN=$(grep -E '^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,} {' Caddyfile | head -1 | cut -d' ' -f1)
+
+# Final status check
+echo ""
+echo -e "${GREEN}🎉 Deployment completed successfully!${NC}"
+echo ""
+echo "==================== IMPORTANT ===================="
+echo -e "${BLUE}🔐 Your admin credentials:${NC}"
+echo "   Username: admin"
+echo "   Password: $ADMIN_PASSWORD"
+echo ""
+echo -e "${RED}⚠️  SAVE THESE CREDENTIALS SECURELY!${NC}"
+echo -e "${RED}⚠️  Change the password after first login!${NC}"
+echo "==================== IMPORTANT ===================="
+echo ""
+echo -e "${CYAN}🌐 Your blog is now available at: https://$DOMAIN${NC}"
+echo -e "${CYAN}🔧 Admin panel: https://$DOMAIN/admin${NC}"
+echo ""
+echo -e "${YELLOW}📊 Service status:${NC}"
+sudo systemctl status gooneral-wheelchair --no-pager
+echo ""
+sudo systemctl status caddy --no-pager
+echo ""
+echo -e "${YELLOW}📝 To view logs:${NC}"
+echo "   Backend: sudo journalctl -u gooneral-wheelchair -f"
+echo "   Caddy: sudo journalctl -u caddy -f"
+echo ""
+echo -e "${GREEN}✅ Deployment complete!${NC}"
+EOF
+
+chmod +x "$DEPLOY_DIR/deploy.sh"
+
+# Create README
+cat > "$DEPLOY_DIR/README.md" << EOF
+# Gooneral Wheelchair - Deployment Package
+
+## Quick Deployment
+
+1. Upload this entire directory to your server
+2. Run the deployment script: \`./deploy.sh\`
+3. Access your blog at https://$DOMAIN
+
+## What's Included
+
+- Built frontend in \`dist/\`
+- Backend application in \`backend/\`
+- Auto-configured Caddyfile for $DOMAIN
+- Systemd service configuration
+- Automated deployment script
+
+## Generated Configuration
+
+- **Domain**: $DOMAIN
+- **Session Secret**: Auto-generated (64 characters)
+- **Admin User**: Will be created automatically
+- **SSL**: Automatic via Caddy + Let's Encrypt
+
+## After Deployment
+
+The script will output your admin credentials. Save them securely!
+
+Access your blog:
+- **Blog**: https://$DOMAIN
+- **Admin**: https://$DOMAIN/admin
+
+## Manual Steps (if needed)
+
+If the automated script doesn't work, see DEPLOYMENT.md for manual instructions.
+EOF
+
+# Copy the detailed deployment guide
+cp ./DEPLOYMENT.md "$DEPLOY_DIR/" 2>/dev/null || echo "Note: DEPLOYMENT.md not found, skipping..."
+
+# Upload and deploy if server specified
+if [ -n "$SERVER_HOST" ]; then
+    echo -e "${YELLOW}📤 Uploading to server...${NC}"
+    
+    # Create tarball
+    TAR_FILE="gooneral-wheelchair-deployment.tar.gz"
+    tar -czf "$TAR_FILE" -C "$DEPLOY_DIR" .
+    
+    echo -e "${YELLOW}🚀 Deploying to $SERVER_HOST...${NC}"
+    
+    # Upload and execute deployment
+    scp "$TAR_FILE" "${SERVER_USER}@${SERVER_HOST}:~/"
+    ssh "${SERVER_USER}@${SERVER_HOST}" "
+        mkdir -p gooneral-wheelchair-deploy
+        cd gooneral-wheelchair-deploy
+        tar -xzf ../$TAR_FILE
+        chmod +x deploy.sh
+        ./deploy.sh
+    "
+    
+    # Clean up local tarball
+    rm "$TAR_FILE"
+    
+    echo -e "${GREEN}✅ Remote deployment completed!${NC}"
+else
+    echo ""
+    echo -e "${GREEN}🎉 Deployment package ready!${NC}"
+    echo -e "${CYAN}📁 Location: $DEPLOY_DIR${NC}"
+    echo ""
+    echo -e "${YELLOW}Next steps:${NC}"
+    echo "1. Upload the '$DEPLOY_DIR' folder to your server"
+    echo "2. SSH to your server and run: ./deploy.sh"
+    echo "3. Access your blog at https://$DOMAIN"
+    echo ""
+    echo -e "${BLUE}💡 Or run this again with server details for automatic deployment:${NC}"
+    echo "./prepare-deployment.sh $DOMAIN your.server.ip username"
+fi
+
+# Save deployment info
+cat > "$DEPLOY_DIR/deployment-info.json" << EOF
+{
+    "domain": "$DOMAIN",
+    "sessionSecret": "$SESSION_SECRET",
+    "deploymentTime": "$(date -Iseconds)",
+    "deploymentPath": "$DEPLOY_DIR"
+}
+EOF
+
+echo ""
+echo -e "${GREEN}✅ Session secret generated and saved securely in deployment package${NC}"
+echo -e "${RED}🔐 Keep your deployment files secure - they contain sensitive configuration!${NC}"

+ 1 - 1
src/App.jsx

@@ -41,7 +41,7 @@ const md = new MarkdownIt({
 .use(emoji)                   // GitHub-style emoji :emoji_name:
 .use(footnote);               // Standard footnotes [^1]
 
-const API_BASE = 'http://localhost:3001/api';
+const API_BASE = import.meta.env.PROD ? '/api' : 'http://localhost:3001/api';
 
 // Navigation Header Component
 function NavHeader() {

+ 1 - 1
src/contexts/AuthContext.jsx

@@ -1,6 +1,6 @@
 import React, { createContext, useContext, useState, useEffect } from 'react';
 
-const API_BASE = 'http://localhost:3001/api';
+const API_BASE = import.meta.env.PROD ? '/api' : 'http://localhost:3001/api';
 
 const AuthContext = createContext();
 

+ 1 - 1
src/contexts/ThemeContext.jsx

@@ -1,6 +1,6 @@
 import React, { createContext, useContext, useState, useEffect } from 'react';
 
-const API_BASE = 'http://localhost:3001/api';
+const API_BASE = import.meta.env.PROD ? '/api' : 'http://localhost:3001/api';
 
 const ThemeContext = createContext();
 

部分文件因为文件数量过多而无法显示