This commit is contained in:
Vaica committed 2026-06-22 14:04:35 +08:00
1 parent 4d98e5ca1e
commit 4b8018bcc3
3 files changed
+248 -158

No files matched your search

+220 -147
View File
@@ -1,202 +1,275 @@
# Write Server - Hugo Blog Admin for Linux
# 基于 Next.js 16 的 Hugo 博客管理后台 Linux 部署版本
# Write Server
这是一个为 Linux 服务器优化的 Hugo 博客管理后台,支持 Docker 部署和 systemd 服务管理。
Hugo 博客的本地管理后台,基于 Next.js 16 + Tailwind CSS 4,Docker 容器化部署。
## 特性
域名:`https://post.usj.cc`
- 完整的博客管理功能(文章、评论、友链、订阅源等)
- Docker 容器化部署
- systemd 服务支持
- Nginx 反向代理配置
- 自动重启和健康检查
- 日志管理
- 备份策略
## 功能模块
## 快速开始
| 模块 | 路径 | 说明 |
|------|------|------|
| 文章管理 | `/` | 列表、搜索、编辑、删除、预览 |
| 撰写文章 | `/edit` | Markdown 编辑器,图片上传 |
| 评论管理 | `/comments` | Artalk 评论回复、删除 |
| 订阅源 | `/feeds` | RSS 订阅源管理 |
| 友链管理 | `/links` | 友链增删改查 |
| 图片管理 | `/images` | 图片上传管理 |
| 公众号 | `/wechat` | 微信公众号发布(自包含) |
| 回收站 | `/recycle` | 已删文章恢复 |
| Bot 管理 | `/bot` | Telegram Bot / Hugo 状态 |
### 方式一:Docker 部署(推荐)
## 技术栈
```bash
# 1. 克隆项目
git clone <your-repo-url>
cd write-server
# 2. 配置环境变量
cp .env.example .env
nano .env # 编辑配置
# 3. 启动服务
docker-compose up -d
# 4. 查看日志
docker-compose logs -f
```
### 方式二:直接部署
```bash
# 1. 安装依赖
./scripts/install.sh
# 2. 配置环境变量
cp .env.example .env
nano .env
# 3. 启动服务
./scripts/start.sh
# 4. 设置开机自启
sudo ./scripts/setup-service.sh
```
- **框架**: Next.js 16 (Turbopack, standalone 输出)
- **样式**: Tailwind CSS 4, SEN 暖色系
- **数据**: 直接读写 Hugo Markdown 文件,无数据库
- **评论**: Artalk API v2
- **Bot**: Telegram Bot (长轮询)
- **公众号**: 微信 API 直连(不依赖外部服务)
- **反向代理**: Nginx + Basic Auth + Service Worker
- **Hugo 预览**: 独立端口 1313,HTTPS 访问
## 目录结构
```
write-server/
├── src/ # 源代码
├── public/ # 静态资源
├── scripts/ # 部署脚本
├── nginx/ # Nginx 配置
├── systemd/ # systemd 服务文件
├── backups/ # 备份目录
├── logs/ # 日志目录
├── Dockerfile # Docker 镜像配置
├── docker-compose.yml # Docker Compose 配置
├── .env.example # 环境变量模板
└── README.md # 本文档
├── src/ # 源代码
│ ├── app/ # Next.js 页面和 API
│ ├── components/ # React 组件
│ └── lib/ # 核心库(posts, recycle, bot, wechat, hugo)
├── nginx/ # Nginx 配置
│ ├── conf.d/ # 站点配置(主站 + Hugo 预览)
│ ├── login.html # 登录页面
│ └── sw.js # Service Worker(认证)
├── entrypoint.sh # Docker 入口脚本(权限修复)
├── Dockerfile # Docker 镜像
├── docker-compose.yml # Docker Compose
├── .env.example # 环境变量模板
└── .htpasswd # Basic Auth 密码文件
```
## 配置说明
## 端口说明
### 环境变量
| 端口 | 用途 | 协议 |
|------|------|------|
| 80 | HTTP → HTTPS 重定向 | HTTP |
| 443 | 写作后台(nginx 代理 → 8016) | HTTPS |
| 1313 | Hugo 预览(nginx 代理 → 容器内 1313) | HTTPS |
| 8016 | write-server(仅容器内部) | - |
复制 `.env.example` 为 `.env`,并填写以下配置:
## 环境变量 (.env)
```bash
# 博客根目录(Hugo 项目路径)
BLOG_ROOT=/path/to/your/hugo/blog
# 评论系统
ARTALK_SERVER=https://artalk.usj.cc
ARTALK_SITE=优世界
# 服务端口
PORT=8016
# Artalk 评论系统
ARTALK_SERVER=https://artalk.your-domain.com
ARTALK_SITE=Your Site Name
# Telegram Bot(可选)
# Telegram Bot
TG_BOT_TOKEN=your-bot-token
TG_ALLOWED_CHAT_IDS=your-chat-id
# 微信公众号(可选)
# 微信公众号(自包含,直连微信 API)
WECHAT_APP_ID=your-app-id
WECHAT_APP_SECRET=your-app-secret
# AI 配置(可选)
DEEPSEEK_API_KEY=your-api-key
# RSS/友链 API
RSS_API_BASE=https://api.usj.cc
RSS_API_TOKEN=your-token
# AI
DEEPSEEK_API_KEY=your-key
DEEPSEEK_BASE_URL=https://api.deepseek.com
DEEPSEEK_MODEL=deepseek-chat
# 其他
DEFAULT_AUTHOR=小赵
PREFER_IFACE=eth0
```
### Nginx 配置
如果需要域名访问,参考 `nginx/write-server.conf` 配置 Nginx 反向代理。
## 管理命令
## 常用命令
```bash
# Docker 方式
docker-compose up -d # 启动
docker-compose down # 停止
docker-compose restart # 重启
docker-compose logs -f # 查看日志
docker-compose ps # 查看状态
# 启动
docker compose up -d
# systemd 方式
sudo systemctl start write-server # 启动
sudo systemctl stop write-server # 停止
sudo systemctl restart write-server # 重启
sudo systemctl status write-server # 查看状态
sudo journalctl -u write-server -f # 查看日志
# 停止
docker compose down
# 重建(代码/Dockerfile 变更后)
docker compose down
docker compose build --no-cache
docker compose up -d
# 查看日志
docker logs write-server -f
docker logs write-nginx -f
# 重启单个服务
docker compose restart write-server
docker compose restart nginx
```
## 备份与恢复
---
## 迁移到新服务器
### 1. 新服务器准备
```bash
# 手动备份
./scripts/backup.sh
# 安装 Docker
curl -fsSL https://get.docker.com | sh
systemctl enable docker
systemctl start docker
# 恢复备份
./scripts/restore.sh backups/write-server-20260101.tar.gz
# 自动备份(已配置 cron)
crontab -l # 查看定时任务
# 安装 docker compose 插件
apt install -y docker-compose-plugin git
```
## 更新升级
### 2. 克隆项目
```bash
# 拉取最新代码
git pull
# Docker 方式
docker-compose build --no-cache
docker-compose up -d
# 直接部署方式
npm install
npm run build
sudo systemctl restart write-server
cd ~
mkdir -p hugo && cd hugo
git clone <your-repo-url> blog
cd blog/write-server
```
## 故障排查
### 查看日志
### 3. 配置环境变量
```bash
# 应用日志
tail -f logs/app.log
# PM2 日志(直接部署方式)
pm2 logs write-server
# Docker 日志
docker-compose logs -f
cp .env.example .env
nano .env
```
### 常见问题
填写所有必要的 Token 和 API Key。
1. **端口被占用**
```bash
lsof -i :8016
kill -9 <PID>
```
### 4. 生成 Basic Auth 密码
2. **权限问题**
```bash
sudo chown -R $USER:$USER /path/to/blog
```
```bash
docker run --rm httpd:alpine htpasswd -nbB admin "your-password" > .htpasswd
```
3. **Hugo 未安装**
```bash
# 参考 scripts/install.sh 安装 Hugo
```
### 5. 配置域名 DNS
## 安全建议
添加 A 记录指向新服务器 IP:
```
post.usj.cc → 新服务器IP
```
1. 使用 HTTPS(配置 SSL 证书)
2. 限制访问 IP(防火墙或 Nginx 配置)
3. 定期更新依赖
4. 启用访问日志
5. 配置 fail2ban 防暴力破解
### 6. 申请 SSL 证书
## 性能优化
```bash
apt install -y certbot
1. 启用 Gzip 压缩
2. 配置 CDN
3. 使用 PM2 集群模式
4. 配置 Redis 缓存(可选)
# 先启动 nginx(HTTP 模式)
docker compose up -d nginx
## 许可证
# 停 nginx 释放 80 端口
docker compose stop nginx
MIT License
# 申请证书
certbot certonly --standalone -d post.usj.cc --agree-tos --email your@email.com
# 复制证书
mkdir -p nginx/ssl
cp /etc/letsencrypt/live/post.usj.cc/fullchain.pem nginx/ssl/
cp /etc/letsencrypt/live/post.usj.cc/privkey.pem nginx/ssl/
# 启动所有服务
docker compose up -d
```
### 7. 配置防火墙
```bash
ufw allow 22/tcp
ufw allow 80/tcp
ufw allow 443/tcp
ufw allow 1313/tcp
ufw enable
```
### 8. 配置 Git
```bash
# 博客目录需要 git 可写
git config --global --add safe.directory /root/hugo/blog
```
### 9. 验证
```bash
# 检查容器状态
docker ps
# 检查 API
curl -s https://post.usj.cc/api/stats
# 检查 Hugo 预览
curl -s https://post.usj.cc:1313/ | head -5
```
### 10. 自动续期 SSL 证书
```bash
# 添加 crontab
crontab -e
# 添加:
0 3 1 * * certbot renew --quiet && docker compose restart nginx
```
---
## 迁移清单
换服务器时需要迁移的内容:
### 必须迁移
| 内容 | 位置 | 说明 |
|------|------|------|
| 博客源码 | `~/hugo/blog/` | git clone 即可 |
| write-server 代码 | `~/hugo/blog/write-server/` | git clone 即可 |
| .env 文件 | `write-server/.env` | 手动创建,填写 Token |
| .htpasswd 文件 | `write-server/.htpasswd` | 重新生成 |
| SSL 证书 | `nginx/ssl/` | 重新申请 |
### 可选迁移
| 内容 | 位置 | 说明 |
|------|------|------|
| 回收站数据 | `write-server/recycle/` | 30 天自动过期,可不迁移 |
| 日志 | `write-server/logs/` | 可不迁移 |
### 需要在新服务器操作
| 操作 | 说明 |
|------|------|
| DNS 解析 | 把 post.usj.cc 指向新 IP |
| SSL 证书 | 在新服务器重新申请 |
| 防火墙 | 放行 80, 443, 1313 端口 |
| git safe.directory | 新服务器执行 `git config --global --add safe.directory ...` |
| 微信 IP 白名单 | 如果用公众号功能,新服务器 IP 需加入微信白名单 |
### 不需要迁移
| 内容 | 原因 |
|------|------|
| Docker 镜像 | 新服务器重新构建 |
| node_modules | Docker 构建时自动安装 |
| .next 目录 | Docker 构建时自动生成 |
| Hugo 二进制 | Dockerfile 自动安装 |
---
## 已知限制
- Hugo 预览和写作后台共用域名,Hugo 导航链接只在 1313 端口有效
- Telegram Bot 通过 Docker 挂载的 SSH 密钥进行 git push
- 博客目录 owner 会被 entrypoint 改为 uid 1001(nextjs 用户)
- 3 小时登录过期(localStorage + Service Worker 双重检查)
## License
MIT
+26 -9
View File
@@ -1,7 +1,7 @@
"use client";
import { useState, useEffect } from "react";
import { Bot, RefreshCw, Server } from "lucide-react";
import { Bot, RefreshCw, Server, StopCircle } from "lucide-react";
interface BotStatus {
bot: { enabled: boolean; polling: boolean };
@@ -33,6 +33,13 @@ export default function BotPage() {
}, 3000);
}
async function stopHugo() {
try {
await fetch("/api/hugo", { method: "DELETE" });
await fetchStatus();
} catch { /* ignore */ }
}
useEffect(() => {
fetchStatus();
const timer = setInterval(fetchStatus, 10000);
@@ -87,14 +94,24 @@ export default function BotPage() {
: "已停止"}
</span>
</div>
<button
onClick={restartHugo}
disabled={restarting}
className="px-4 py-2 rounded-md bg-blue-600 text-white text-sm font-medium hover:bg-blue-700 disabled:opacity-50 flex items-center gap-2"
>
<RefreshCw size={14} className={restarting ? "animate-spin" : ""} />
{restarting ? "重启中..." : "重启 Hugo"}
</button>
<div className="flex items-center gap-2">
<button
onClick={restartHugo}
disabled={restarting}
className="px-4 py-2 rounded-md bg-blue-600 text-white text-sm font-medium hover:bg-blue-700 disabled:opacity-50 flex items-center gap-2"
>
<RefreshCw size={14} className={restarting ? "animate-spin" : ""} />
{restarting ? "重启中..." : "重启 Hugo"}
</button>
<button
onClick={stopHugo}
disabled={!status?.hugo.running}
className="px-4 py-2 rounded-md bg-red-600 text-white text-sm font-medium hover:bg-red-700 disabled:opacity-50 flex items-center gap-2"
>
<StopCircle size={14} />
停止 Hugo
</button>
</div>
</div>
</div>
);
+2 -2
View File
@@ -238,7 +238,7 @@ export default function WechatPage() {
我知道了
</button>
<a
href="https://mp.weixin.qq.com/"
href="https://developers.weixin.qq.com/platform"
target="_blank"
rel="noopener noreferrer"
className="rounded-md bg-accent px-4 py-2 text-[13px] font-medium text-white transition-colors hover:bg-accent-hover"
@@ -273,7 +273,7 @@ export default function WechatPage() {
</div>
<div className="mt-6 flex justify-end gap-3">
<a
href="https://mp.weixin.qq.com/"
href="https://developers.weixin.qq.com/platform"
target="_blank"
rel="noopener noreferrer"
className="rounded-md border border-line px-4 py-2 text-[13px] text-muted transition-colors hover:bg-surface hover:text-text"