部署小白笔记
这份笔记记录 AI Fitness Planner 从 GitHub 私有仓库部署到宝塔服务器的完整流程。
它不是只给命令,而是解释每一步为什么要这样做。以后换服务器、重装系统、给朋友讲部署,照这份走就行。
0. 最终部署长什么样
最终访问路径:
https://fit.snowmoon1824.top
整体结构:
flowchart TD
Browser["浏览器"] --> Nginx["宝塔 Nginx"]
Nginx --> Frontend["前端静态文件 frontend/dist"]
Nginx --> API["FastAPI 127.0.0.1:8010"]
API --> MySQL["MySQL 5.7"]
API --> Env["shared/.env"]
为什么这样设计:
- 浏览器只访问 Nginx,不直接访问 FastAPI。
- 前端是静态文件,交给 Nginx 最省资源。
- FastAPI 只监听
127.0.0.1,外网不能直接访问后端端口,安全一些。 - MySQL 只给后端访问,不暴露到公网。
.env放服务器,不提交 GitHub,避免泄露数据库密码和 OpenAI Key。
1. 服务器需要准备什么
宝塔里需要安装:
- Nginx
- MySQL 5.7
- Python 3.10+,推荐 3.11 或 3.12
- Node.js + npm
- Git
为什么需要这些:
- Nginx:负责网站入口、HTTPS、反向代理。
- MySQL 5.7:保存用户、训练计划、训练记录。
- Python:运行 FastAPI 后端。
- Node.js/npm:构建 Vue 前端。构建完成后,生产环境不需要长期运行 Node。
- Git:从 GitHub 私有仓库拉代码。
检查命令:
nginx -v
mysql --version
python3 --version
node -v
npm -v
git --version
如果 python3 -m venv 报错,需要安装 venv 组件:
apt update
apt install -y python3-venv python3-pip
如果提示具体版本,比如 Python 3.10:
apt install -y python3.10-venv python3-pip
如果没有 npm,可以安装 Node.js:
apt update
apt install -y curl ca-certificates gnupg
curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
apt install -y nodejs
node -v
npm -v
2. 域名怎么填
本项目建议使用明确的子域名:
fit.snowmoon1824.top
不需要填泛域名:
*.snowmoon1824.top
为什么:
- 这个应用只需要一个固定入口。
- 泛域名适合很多子站点共用一套配置,本项目不需要。
- 单独子域名申请 SSL 和排查问题更简单。
DNS 解析:
类型:A
主机记录:fit
记录值:服务器公网 IP
一级域名是否已经有网页不重要。fit.snowmoon1824.top 可以单独指向服务器。
3. 为什么要用 Deploy Key
仓库是私有的,服务器直接 git clone 会没有权限。
Deploy Key 是专门给服务器拉取某个仓库用的 SSH key。它比把你的 GitHub 账号密码放到服务器安全。
服务器生成 key:
ssh-keygen -t ed25519 -f ~/.ssh/ai_fitness_planner_deploy -C "ai-fitness-planner-deploy"
cat ~/.ssh/ai_fitness_planner_deploy.pub
把输出的公钥添加到 GitHub 仓库:
Settings -> Deploy keys -> Add deploy key
设置:
Title: ai-fitness-planner-deploy
Key: 粘贴 ssh-ed25519 开头的整行公钥
Allow write access: 不勾选
为什么不勾选写权限:
- 服务器只需要拉代码。
- 不需要从服务器往 GitHub 推代码。
- 少给权限,风险更低。
配置 SSH 别名:
cat >> ~/.ssh/config <<'EOF'
Host github.com-ai-fitness-planner
HostName github.com
User git
IdentityFile ~/.ssh/ai_fitness_planner_deploy
IdentitiesOnly yes
EOF
chmod 600 ~/.ssh/config
ssh -T git@github.com-ai-fitness-planner
成功时会看到类似:
Hi MTL-Sakura/ai-fitness-planner! You've successfully authenticated, but GitHub does not provide shell access.
4. 服务器目录为什么这样设计
项目放在:
/www/wwwroot/ai-fitness-planner/
里面分三类:
/www/wwwroot/ai-fitness-planner/
current/ 当前正在运行的版本,软链接
releases/ 每次部署生成一个版本目录
shared/ 跨版本共享的数据
releases 示例:
/www/wwwroot/ai-fitness-planner/releases/20260602081958
为什么要有 release:
- 每次部署都是一个独立目录。
- 新版本构建失败,不影响旧版本。
- 出问题可以把
current指回旧版本。
为什么 shared 不放进 release:
.env是生产配置,不能跟着代码覆盖。- 上传文件、日志、备份要跨版本保留。
创建目录:
mkdir -p /www/wwwroot/ai-fitness-planner/shared/{uploads,logs,backups}
mkdir -p /www/wwwroot/ai-fitness-planner/releases
5. .env 是什么,为什么不能提交 GitHub
.env 是生产环境配置。
创建文件:
nano /www/wwwroot/ai-fitness-planner/shared/.env
示例:
APP_ENV=production
APP_SECRET_KEY=replace-with-random-secret
DATABASE_URL=mysql+pymysql://ai_fitness_user:replace-password@127.0.0.1:3306/ai_fitness_planner?charset=utf8mb4
OPENAI_API_KEY=
OPENAI_MODEL=
JWT_ACCESS_EXPIRE_MINUTES=30
JWT_REFRESH_EXPIRE_DAYS=30
CORS_ORIGINS=https://fit.snowmoon1824.top
UPLOAD_DIR=/www/wwwroot/ai-fitness-planner/shared/uploads
LOG_DIR=/www/wwwroot/ai-fitness-planner/shared/logs
生成 APP_SECRET_KEY:
openssl rand -hex 32
为什么要放 .env:
- 数据库密码不能写进代码。
- JWT 签名密钥不能写进代码。
- OpenAI API Key 以后也不能写进前端或 GitHub。
6. MySQL 要怎么建
宝塔数据库里创建:
数据库名:ai_fitness_planner
用户名:ai_fitness_user
字符集:utf8mb4
为什么用 utf8mb4:
- 支持中文。
- 支持 emoji 和更多 Unicode 字符。
- 比普通
utf8更完整。
数据库连接写进 .env:
DATABASE_URL=mysql+pymysql://ai_fitness_user:数据库密码@127.0.0.1:3306/ai_fitness_planner?charset=utf8mb4
为什么是 127.0.0.1:
- 数据库和后端在同一台服务器。
- 不需要暴露数据库公网端口。
7. systemd 是什么,为什么要用它
FastAPI 后端需要一直运行。
如果你手动执行:
uvicorn app.main:app
关闭终端后服务就停了。
systemd 的作用:
- 开机自动启动后端。
- 后端崩了自动重启。
- 用
systemctl status看状态。 - 用
journalctl看日志。
服务文件位置:
/etc/systemd/system/ai-fitness-planner.service
内容:
[Unit]
Description=AI Fitness Planner API
After=network.target
[Service]
WorkingDirectory=/www/wwwroot/ai-fitness-planner/current/backend
EnvironmentFile=/www/wwwroot/ai-fitness-planner/shared/.env
ExecStart=/www/wwwroot/ai-fitness-planner/current/backend/.venv/bin/python -m uvicorn app.main:app --host 127.0.0.1 --port 8010
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
为什么用 python -m uvicorn:
- 比直接执行
.venv/bin/uvicorn更稳。 - 能避免某些服务器上 wrapper 脚本执行失败导致
status=203/EXEC。
启用服务:
systemctl daemon-reload
systemctl enable ai-fitness-planner
8. 首次部署怎么跑
先把仓库克隆到临时目录:
git clone git@github.com-ai-fitness-planner:MTL-Sakura/ai-fitness-planner.git /tmp/ai-fitness-planner
安装 systemd 服务:
cp /tmp/ai-fitness-planner/scripts/ai-fitness-planner.service /etc/systemd/system/ai-fitness-planner.service
systemctl daemon-reload
systemctl enable ai-fitness-planner
执行部署:
APP_ROOT=/www/wwwroot/ai-fitness-planner \
REPO_URL=git@github.com-ai-fitness-planner:MTL-Sakura/ai-fitness-planner.git \
BRANCH=main \
SERVICE_NAME=ai-fitness-planner \
bash /tmp/ai-fitness-planner/scripts/deploy.sh
部署脚本做了什么:
- 检查
git、python3、npm、systemctl、curl是否存在。 - 检查
shared/.env是否存在。 - 从 GitHub 拉取代码到新的
releases/<时间戳>。 - 创建后端虚拟环境
.venv。 - 安装 Python 依赖。
- 构建前端
frontend/dist。 - 执行数据库迁移。
- 把
current指向新 release。 - 重启后端 systemd 服务。
- 调用健康检查确认服务正常。
为什么先构建前端,再迁移数据库:
- 前端构建最容易因为内存不足失败。
- 如果前端失败,就不切换版本,也不动数据库。
- 这样不会出现半成品版本。
9. 前端构建卡死怎么办
小服务器在执行:
npm run build
时可能内存不够,导致宝塔面板也打不开。
先检查内存:
free -h
swapon --show
如果没有 swap,加 2GB:
fallocate -l 2G /swapfile || dd if=/dev/zero of=/swapfile bs=1M count=2048
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
grep -q '/swapfile' /etc/fstab || echo '/swapfile none swap sw 0 0' >> /etc/fstab
free -h
为什么要加 swap:
- 物理内存不够时,Linux 可以临时用磁盘当内存缓冲。
- 速度比内存慢,但能避免构建过程直接卡死。
如果构建中断,检查最新 release:
APP_ROOT=/www/wwwroot/ai-fitness-planner
LATEST=$(ls -1dt $APP_ROOT/releases/* | head -1)
echo $LATEST
test -x "$LATEST/backend/.venv/bin/python" && echo "backend venv OK" || echo "backend venv missing"
test -f "$LATEST/frontend/dist/index.html" && echo "frontend build OK" || echo "frontend build missing"
如果只有前端缺失,可以只补前端构建:
cd "$LATEST/frontend"
npm install
npm run build
然后切换:
ln -sfn "$LATEST" "$APP_ROOT/current.next"
mv -Tf "$APP_ROOT/current.next" "$APP_ROOT/current"
systemctl restart ai-fitness-planner
10. 健康检查怎么看
后端本机检查:
curl http://127.0.0.1:8010/api/v1/health
成功:
{"data":{"status":"ok","database":"ok"},"message":"ok"}
公网检查:
curl https://fit.snowmoon1824.top/api/v1/health
为什么有两个检查:
127.0.0.1检查 FastAPI 自己是否正常。- 域名检查 Nginx 反向代理是否正常。
11. Nginx 为什么要这样配
宝塔网站根目录:
/www/wwwroot/ai-fitness-planner/current/frontend/dist
核心配置:
root /www/wwwroot/ai-fitness-planner/current/frontend/dist;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://127.0.0.1:8010/api/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /uploads/ {
alias /www/wwwroot/ai-fitness-planner/shared/uploads/;
}
为什么要 try_files ... /index.html:
- Vue 是单页应用。
/admin/users这种路径不是服务器真实文件。- 需要 Nginx 把前端路由都交回
index.html。
为什么 /api/ 要反向代理:
- 前端请求
/api/v1/...。 - Nginx 把它转发到本机 FastAPI。
- 浏览器看起来还是同一个域名,没有跨域麻烦。
配置改完后:
nginx -t
systemctl reload nginx
12. HTTPS 和博客串站问题
如果访问:
http://fit.snowmoon1824.top
自动跳到:
https://fit.snowmoon1824.top
但显示的是博客页面,通常说明:
80配了 fit 站点。443 HTTPS没有给 fit 单独配置。- Nginx 用默认 HTTPS 站点响应了请求,默认站点可能是 blog。
解决:
- 宝塔中给
fit.snowmoon1824.top单独申请 SSL。 - 确认 HTTPS server block 也使用同样的
root、location /、location /api/。 - 保存后:
nginx -t
systemctl reload nginx
13. 创建管理员账号
部署成功后创建管理员:
cd /www/wwwroot/ai-fitness-planner/current/backend
source .venv/bin/activate
python -m app.cli create-admin --username admin --password "换成强密码"
为什么不用公开注册:
- 这个应用只给自己和少数朋友用。
- 管理员手动创建账号更安全。
如果忘记密码,当前阶段可以通过服务器脚本重置,后续会做管理后台重置功能。
14. 以后更新版本怎么做
以后代码更新后,服务器执行:
cd /tmp/ai-fitness-planner
git pull
APP_ROOT=/www/wwwroot/ai-fitness-planner \
REPO_URL=git@github.com-ai-fitness-planner:MTL-Sakura/ai-fitness-planner.git \
BRANCH=main \
SERVICE_NAME=ai-fitness-planner \
bash /tmp/ai-fitness-planner/scripts/deploy.sh
为什么要先 git pull:
/tmp/ai-fitness-planner/scripts/deploy.sh是临时克隆出来的脚本。- 如果脚本本身更新了,不
git pull就还在用旧脚本。
15. 常见问题
npm: command not found
原因:服务器没有安装 npm。
解决:
curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
apt install -y nodejs
npm -v
python3 -m venv 失败
原因:缺少 python3-venv 或对应版本的 venv 包。
解决:
apt install -y python3-venv python3-pip
status=203/EXEC
原因:systemd 执行命令失败,常见于 uvicorn 路径不存在或 wrapper 脚本执行失败。
解决:服务文件使用:
ExecStart=/www/wwwroot/ai-fitness-planner/current/backend/.venv/bin/python -m uvicorn app.main:app --host 127.0.0.1 --port 8010
current/backend/.venv 不存在
原因:current 不是 release 软链接,或者部署没完成。
检查:
readlink -f /www/wwwroot/ai-fitness-planner/current
ls -lt /www/wwwroot/ai-fitness-planner/releases
修复:选择一个完整 release,重新指向:
APP_ROOT=/www/wwwroot/ai-fitness-planner
LATEST=$(ls -1dt $APP_ROOT/releases/* | head -1)
ln -sfn "$LATEST" "$APP_ROOT/current.next"
mv -Tf "$APP_ROOT/current.next" "$APP_ROOT/current"
systemctl restart ai-fitness-planner
宝塔面板打不开
可能是前端构建占满内存。
处理:
- 等一会儿。
- 尝试
Ctrl + C停掉当前部署。 - 从云服务器控制台重启。
- 加 swap 后再构建。
访问 fit 域名显示博客
原因:HTTPS 站点匹配到了博客的默认 server。
解决:给 fit.snowmoon1824.top 单独申请 SSL,并确认 443 配置也指向本项目。
16. 最小验收清单
部署完成后,逐项检查:
systemctl status ai-fitness-planner --no-pager
curl http://127.0.0.1:8010/api/v1/health
curl https://fit.snowmoon1824.top/api/v1/health
test -f /www/wwwroot/ai-fitness-planner/current/frontend/dist/index.html && echo "frontend OK"
浏览器检查:
- 打开
https://fit.snowmoon1824.top - 能看到登录页
- 能用管理员登录
- 能看到训练控制台
- 用户管理页面能打开
这些都通过,阶段 1 部署就完成了。

Comments NOTHING