fit部署流程

MTL_Sakura 发布于 2 小时前 1 次阅读


部署小白笔记

这份笔记记录 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

部署脚本做了什么:

  1. 检查 gitpython3npmsystemctlcurl 是否存在。
  2. 检查 shared/.env 是否存在。
  3. 从 GitHub 拉取代码到新的 releases/<时间戳>
  4. 创建后端虚拟环境 .venv
  5. 安装 Python 依赖。
  6. 构建前端 frontend/dist
  7. 执行数据库迁移。
  8. current 指向新 release。
  9. 重启后端 systemd 服务。
  10. 调用健康检查确认服务正常。

为什么先构建前端,再迁移数据库:

  • 前端构建最容易因为内存不足失败。
  • 如果前端失败,就不切换版本,也不动数据库。
  • 这样不会出现半成品版本。

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。

解决:

  1. 宝塔中给 fit.snowmoon1824.top 单独申请 SSL。
  2. 确认 HTTPS server block 也使用同样的 rootlocation /location /api/
  3. 保存后:
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

宝塔面板打不开

可能是前端构建占满内存。

处理:

  1. 等一会儿。
  2. 尝试 Ctrl + C 停掉当前部署。
  3. 从云服务器控制台重启。
  4. 加 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 部署就完成了。

此作者没有提供个人介绍。
最后更新于 2026-06-02