博客部署与运维指南

2026年6月26日 blogTech 17 分钟阅读 24 次阅读
📖 文章摘要

从零开始的部署教程:服务器环境准备、前后端推送、systemd 服务配置、Nginx 反代、SSL 证书、日常更新、常见问题排查。

博客部署指南

服务器信息

项目
SSH 端口
用户 root(已配密钥免密登录)
项目路径 /root/blog

一、快速部署(日常使用)

推荐使用部署脚本:

cd D:\Projects\blog
python deploy/deploy.py

菜单选项:

选项 功能 说明
1 完整部署 构建前端 → 打包前后端代码 → SCP 上传 → 服务器解压并重启服务
2 数据库同步 下载(服务器→本地)/ 上传(本地→服务器),包含数据库 + uploads 文件

部署前注意: 生产环境需设置环境变量 SITE_URL=https://blog.vue2.xyz(CORS 白名单依赖此变量)和 SECRET_KEY(强随机字符串,见下方 systemd 配置)

数据库同步注意事项

  • 下载前会暂停服务器 blog-api 服务,下载完成后自动重启
  • 同步包含 blog.dbdata/uploads/ 目录下的所有上传文件及子目录(bed / background / editor)
  • 不包含 data/pan/ 目录,需手动 scp 同步
  • Windows 下本地 dev server 运行时 SQLite 文件被占用,脚本采用 copy2 覆盖写入

二、首次部署(从头搭建)

1. 服务器环境准备

ssh root@43.108.33.153 -p 2894

apt update && apt install -y python3 python3-pip python3-venv nginx certbot python3-certbot-nginx

# Node.js 20+(Ubuntu 默认 v18 不够)
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
apt-get install -y nodejs

mkdir -p /root/blog/data/uploads

2. 推送后端代码

# 本地打包
cd /d/Projects/blog
tar czf backend.tar.gz backend/ --exclude=backend/venv --exclude=backend/__pycache__ --exclude='backend/**/__pycache__'

# 上传
scp -P 2894 backend.tar.gz root@43.108.33.153:/root/blog/

# 服务器解压安装
ssh root@43.108.33.153 -p 2894
cd /root/blog
tar xzf backend.tar.gz
cd backend
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt

3. 推送前端代码

# 本地构建
cd /d/Projects/blog/frontend
npm install
npm run build

# 打包上传
tar czf frontend.tar.gz .output/
scp -P 2894 frontend.tar.gz root@43.108.33.153:/root/blog/

# 服务器解压
ssh root@43.108.33.153 -p 2894
cd /root/blog
tar xzf frontend.tar.gz

4. 配置后端 systemd 服务

ssh root@43.108.33.153 -p 2894
cp /root/blog/deploy/blog-api.service /etc/systemd/system/

# 编辑服务文件,替换 SECRET_KEY
sed -i "s/SECRET_KEY=change-this-in-production/SECRET_KEY=$(openssl rand -hex 32)/" /etc/systemd/system/blog-api.service

systemctl daemon-reload
systemctl enable blog-api
systemctl start blog-api

服务文件参考:

[Unit]
Description=Blog API (FastAPI)
After=network.target

[Service]
User=root
WorkingDirectory=/root/blog/backend
ExecStart=/root/blog/backend/venv/bin/gunicorn -w 2 -k uvicorn.workers.UvicornWorker -b 127.0.0.1:8001 main:app
Restart=always
RestartSec=5
Environment=SECRET_KEY=<your-generated-key>      # 必填:openssl rand -hex 32 生成
Environment=SITE_URL=https://blog.vue2.xyz        # 必填:CORS 白名单来源

[Install]
WantedBy=multi-user.target

注意: SECRET_KEY 默认为 dev-secret-change-in-production,生产环境启动时若 SITE_URL 不是 localhost 会报错退出,强制要求设置环境变量。

5. 配置前端 systemd 服务

cp /root/blog/deploy/blog-web.service /etc/systemd/system/
systemctl daemon-reload
systemctl enable blog-web
systemctl start blog-web
[Unit]
Description=Blog Web (Nuxt SSR)
After=network.target

[Service]
User=root
WorkingDirectory=/root/blog/frontend
ExecStart=/usr/bin/node .output/server/index.mjs
Restart=always
RestartSec=5
Environment=NODE_ENV=production
Environment=NUXT_API_BASE=http://127.0.0.1:8001
Environment=HOST=127.0.0.1
Environment=PORT=3000

[Install]
WantedBy=multi-user.target

6. 配置 Nginx

cp /root/blog/deploy/nginx-blog.conf /etc/nginx/sites-available/blog.vue2.xyz
ln -s /etc/nginx/sites-available/blog.vue2.xyz /etc/nginx/sites-enabled/

nginx -t && nginx -s reload

Nginx 配置要点:

  • SSL 使用 vue2.xyz 的通配证书(多域名共用)
  • /api/* 反代到 :8001
  • /uploads/* Nginx 直出,缓存 3 天
  • 其他路径反代到 Nuxt SSR (:3000)
  • client_max_body_size 20m 支持大图片上传

7. 申请 SSL 证书

certbot --nginx -d blog.vue2.xyz

会自动在现有 vue2.xyz 证书上追加 blog 子域名。如首次申请:

certbot --nginx --expand -d vue2.xyz -d www.vue2.xyz -d blog.vue2.xyz

8. 首次启动初始化

后端首次启动时自动完成:

  • 创建 SQLite 数据库及所有表(含 card_styles 卡片样式表)
  • 写入 26 行卡片样式默认值
  • 卡片样式可在后台 /admin/settings →「🎴 卡片样式」面板可视化调节

9. 初始化管理员

curl -s -X POST http://127.0.0.1:8001/api/auth/init \
  -H 'Content-Type: application/json' \
  -d '{"username":"admin","password":"<your-password>"}'

10. 验证

# API 健康检查
curl http://127.0.0.1:8001/api/health
# 预期: {"status":"ok"}

# 前端 SSR
curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:3000/
# 预期: 200

# 管理员登录
curl -s -X POST http://127.0.0.1:8001/api/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"username":"admin","password":"<your-password>"}'
# 预期: {"access_token":"...","token_type":"bearer"}

# 也可不传 username,单用户自动匹配:
curl -s -X POST http://127.0.0.1:8001/api/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"password":"<your-password>"}'
# 预期: {"access_token":"...","token_type":"bearer"}

三、日常更新

后端更新(手动)

# 本地
cd /d/Projects/blog
git pull
tar czf backend.tar.gz backend/ --exclude=backend/venv --exclude=backend/__pycache__ --exclude='backend/**/__pycache__'
scp -P 2894 backend.tar.gz root@43.108.33.153:/root/blog/

# 服务器
ssh root@43.108.33.153 -p 2894
cd /root/blog
systemctl stop blog-api
tar xzf backend.tar.gz --overwrite
systemctl start blog-api

前端更新(手动)

# 本地构建打包
cd /d/Projects/blog/frontend
git pull
npm run build
tar czf frontend.tar.gz .output/
scp -P 2894 frontend.tar.gz root@43.108.33.153:/root/blog/

# 服务器
ssh root@43.108.33.153 -p 2894
cd /root/blog
systemctl stop blog-web
rm -rf frontend/.output
tar xzf frontend.tar.gz -C frontend/
systemctl start blog-web

数据库同步

cd D:\Projects\blog
python deploy/deploy.py
# 选择 2 → 1 下载 / 2 上传

下载流程:停止服务器 blog-api → ssh cat 传输 db 到 .tmp → copy2 覆盖本地 → scp 拉取 uploads(含子目录) → 重启 blog-api

上传流程:scp 上传 db → scp 上传 uploads(含子目录) → 重启 blog-api


四、服务器维护

查看服务状态

ssh root@43.108.33.153 -p 2894
systemctl status blog-api      # 后端
systemctl status blog-web       # 前端 SSR
systemctl status nginx          # Nginx

查看日志

ssh root@43.108.33.153 -p 2894
journalctl -u blog-api -n 50 --no-pager -f    # 后端日志(实时追踪加 -f)
journalctl -u blog-web -n 50 --no-pager        # 前端日志
journalctl -u nginx -n 50 --no-pager           # Nginx 日志
tail -f /var/log/nginx/access.log              # Nginx 访问日志
tail -f /var/log/nginx/error.log               # Nginx 错误日志

重启单个服务

ssh root@43.108.33.153 -p 2894
systemctl restart blog-api    # 重启后端
systemctl restart blog-web    # 重启前端
nginx -s reload               # 重载 Nginx

五、服务器目录结构

/root/blog/
├── backend/          # 后端代码 + venv
│   ├── main.py
│   ├── routers/
│   ├── models.py
│   └── ...
├── frontend/
│   └── .output/      # 前端构建产物(public + server)
├── data/
│   ├── blog.db       # SQLite 数据库
│   ├── uploads/
│   │   ├── bed/        # 图床——外链图片(不压缩,原文件)
│   │   ├── background/ # 背景图(自动压缩)
│   │   └── editor/     # 文章编辑器插图(自动压缩)
│   └── pan/          # 网盘文件(需密码验证)
├── deploy/
│   ├── blog-api.service
│   ├── blog-web.service
│   ├── nginx-blog.conf
│   ├── deploy.py      # 一键部署脚本
│   └── sync-db.py     # 旧版同步脚本

六、端口说明

服务 端口 绑定地址 说明
前端 SSR 3000 127.0.0.1 仅 Nginx 访问
后端 API 8001 127.0.0.1 仅 Nginx 访问
Nginx 443/80 0.0.0.0 对外公开
SSH 2894 0.0.0.0 非默认端口

七、Nginx 路由规则

路径 目标 说明
/api/* FastAPI :8001 所有 API 请求
/_nuxt/* Nuxt SSR :3000 前端构建资源
/uploads/* /root/blog/data/uploads/ 图片/文件直出,缓存 3 天(/uploads/bed/ 图床、/uploads/editor/ 文章插图、/uploads/background/ 背景图)
其他 Nuxt SSR :3000 页面路由(含 SSR)

八、常见问题

413 Request Entity Too Large

上传图片时出现此错误 → Nginx 默认 body 大小 1MB,配置中已设 client_max_body_size 20m。如仍有问题,检查配置是否生效。

500 Internal Server Error(文章详情页)

新建文章的 summarycover_image 等字段可能为 NULL,Pydantic v2 严格模式不接受 NULL。解决:更新数据库将 NULL 改为空字符串,schema 加 = "" 默认值。

ssh: connect to host port 22: Connection refused

SSH 端口不是默认 22,需加 -p 2894

数据库同步时报 Broken pipe

Windows 下本地 dev server 持有的 SQLite 文件句柄导致删除失败。修复:脚本已改为 shutil.copy2 覆盖写入。

登录不传 username 报错

登录接口支持不传 username 参数(单用户自动匹配第一个管理员)。如果请求仍然报错,检查后端是否为最新代码(schemas.pyLoginRequest.username 需有 = "" 默认值)。

部署后 SECRET_KEY 错误导致后端启动失败

config.pySITE_URL 不是 localhost 且 SECRET_KEY 为默认值时会抛出 RuntimeError。解决:在 blog-api.service 中正确设置 Environment=SECRET_KEY=<随机字符串>,用 openssl rand -hex 32 生成。

最后更新:2026年6月29日CC BY-NC-SA 4.0

评论

暂无评论,来写第一条吧

© 2026 My Blog. Built with Nuxt.js + FastAPI.