- backend/testutil/setup.go: SQLite shops 表补充 business_hours 列 - stock_in_screen_test: 入库管理双 Tab 重构后,draft/pending 测试改为先切换到「入库审核」Tab - stock_out_insufficient_stock_flow_test: Inventory 构造参数 productUnit→unit,补充必填 id - inventory_repository_test: mock 数据改为平铺字段格式,补充必填 id - docs: 新增 NAS + Gitea 部署文档(Runner 设置、Shadowrocket 中继说明) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
8.0 KiB
NAS + Gitea 部署文档
概述
岩美酒库管理系统使用 群晖 NAS 上自托管的 Gitea(Forgejo) 作为代码仓库和 CI/CD 平台。代码推送到 main 分支后,NAS 上的 Gitea Runner 自动构建并部署到 EC2 服务器。
服务地址
| 服务 | 地址 |
|---|---|
| Gitea Web UI | http://192.168.3.200:3000 |
| Gitea SSH | ssh://192.168.3.200:2222 |
| Actions 面板 | http://192.168.3.200:3000/wangjia/jiu/actions |
| 线上应用 | https://jiu.51yanmei.com |
| 管理端 Web | https://jiu.51yanmei.com/app/ |
Gitea Runner 说明
Runner 实际运行在本机 Mac(不是 NAS),通过 LaunchAgent 开机自启,连接 NAS 上的 Forgejo。
本机文件布局
| 文件 | 说明 |
|---|---|
/Users/wangjia/bin/act_runner |
act_runner 二进制(v0.2.11) |
/Users/wangjia/bin/act_runner_start.sh |
启动脚本(含 TCP 中继逻辑) |
/Users/wangjia/bin/tcp_relay.py |
Python3 TCP 中继:127.0.0.1:13000 → 192.168.3.200:3000 |
/Users/wangjia/.act_runner |
Runner 注册信息(地址指向 127.0.0.1:13000) |
/Users/wangjia/.act_runner_config.yaml |
Runner 配置(label: mac:host,capacity: 1) |
~/Library/LaunchAgents/com.jiu.act-runner.plist |
LaunchAgent plist,开机自启 |
/tmp/act_runner.log |
运行日志 |
Workflow 标签
.gitea/workflows/deploy.yml 和 ci.yml 中使用 runs-on: mac,对应 runner label mac:host。
为什么需要 TCP 中继(重要)
Shadowrocket(代理工具)的 Network Extension 会拦截 LaunchAgent 启动的 Go 二进制发出的 TCP 连接,即使 Shadowrocket 内已配置直连规则。现象:Go gRPC 报 no route to host,而同进程里的 Python3 socket 可以正常连接。
解决方案: 用 Python3 做透明 TCP 中继。tcp_relay.py 监听 127.0.0.1:13000,将连接转发到 192.168.3.200:3000。act_runner 的注册地址改为 http://127.0.0.1:13000,完全绕开 Shadowrocket 拦截。
检查 Runner 状态
# 查看日志
tail -f /tmp/act_runner.log
# 确认进程在跑
ps aux | grep -E "act_runner|tcp_relay" | grep -v grep
# 手动重启(LaunchAgent 会自动重启,一般不需要)
kill $(pgrep -f act_runner_start.sh)
Runner 不工作时的排查步骤
- 确认 NAS 可达:
curl -sf http://192.168.3.200:3000/api/v1/version - 确认 relay 在跑:
ps aux | grep tcp_relay - 确认 relay 端口监听:
nc -zv 127.0.0.1 13000 - 查看最近日志:
tail -30 /tmp/act_runner.log - 手动测试:
/Users/wangjia/bin/act_runner daemon --config ~/.act_runner_config.yaml(从终端运行应直接成功) - 强制重启:
kill $(pgrep -f act_runner_start.sh)— LaunchAgent 会在几秒内自动重拉
重新注册 Runner(token 失效时)
# 1. 停止当前 runner
kill $(pgrep -f act_runner_start.sh)
# 2. 删除旧注册文件
rm ~/.act_runner
# 3. 在 Forgejo 获取新 token:
# Settings → Actions → Runners → Create Runner Token
# 4. 注册(从终端运行,不要从 LaunchAgent)
/Users/wangjia/bin/act_runner register \
--no-interactive \
--instance http://192.168.3.200:3000 \
--token <新token> \
--name mac-runner \
--labels mac:host
# 5. 把注册文件里的地址改回 127.0.0.1:13000
sed -i '' 's|http://192.168.3.200:3000|http://127.0.0.1:13000|' ~/.act_runner
# 6. 重启(LaunchAgent 自动接管)
kill $(pgrep -f act_runner_start.sh)
Workflows
仓库根目录 .gitea/workflows/ 下有三个 workflow 文件:
deploy.yml — 主部署流程
触发条件: push 到 main 分支
步骤:
go test ./...— 跑后端测试flutter test— 跑前端测试- 编译后端:
GOOS=linux GOARCH=amd64 go build生成jiu-server - 编译 Flutter Web:
flutter build web --release --base-href=/app/ - 创建 Forgejo Release(版本号格式
vYYYYMMDD.HHMM),上传jiu-server和web.tar.gz - SSH 到 EC2 部署:
- 停止 jiu 服务 → 替换二进制 → 启动 → 等待健康检查通过
- 替换 Flutter Web 静态文件到
/opt/jiu/web/ - 同步营销站点到
/opt/jiu/marketing/ nginx -s reload
ci.yml — PR 测试流程
触发条件: 非 main 分支的 push 或 PR
步骤: 只跑测试(go test + flutter test),不构建不部署。
backup.yml — 每日备份
触发条件: 每天凌晨 2:00(cron)
步骤: SSH 到 EC2,导出 MySQL 数据库备份,上传到指定目录。
Secrets 配置
在 Gitea 仓库的 Settings → Secrets 中配置以下密钥:
| Secret 名称 | 用途 |
|---|---|
EC2_SSH_KEY |
EC2 服务器的 SSH 私钥(PEM 格式,完整内容) |
EC2_HOST |
EC2 服务器 IP 或域名 |
EC2_USER |
EC2 SSH 用户名(如 ec2-user、ubuntu) |
DB_PASSWORD |
MySQL root 密码(备份 workflow 使用) |
FORGEJO_TOKEN |
Gitea 个人访问 Token(用于创建 Release) |
FORGEJO_URL |
Gitea 内网地址,如 http://192.168.3.200:3000 |
触发部署方式
日常开发流程:
# 开发 → 本地测试
go test ./...
flutter test
# 用户确认后推送到 NAS Gitea(触发自动部署)
git push nas main
推送到 nas remote(指向 http://192.168.3.200:3000/wangjia/jiu.git)后,Gitea Actions 自动运行 deploy.yml,全流程约 5~8 分钟。
EC2 服务器目录结构
/opt/jiu/
├── backend/
│ └── jiu-server # Go 可执行文件
├── web/ # Flutter Web 构建产物(/app/ 路径)
├── marketing/ # 营销站点静态 HTML
│ ├── assets/ # CSS / SVG
│ ├── index.html
│ ├── docs.html
│ ├── download.html
│ ├── scan.html
│ └── features/
│ ├── inventory.html
│ └── approval.html
├── images/ # 商品图片上传目录
└── web-old/ # 上一版本 Flutter Web(回滚备用)
Systemd 服务名:jiu
日志:journalctl -u jiu -f
Nginx 路由说明
配置文件:deploy/nginx-jiu.conf(部署时手动 scp 至服务器)
| 路径 | 服务 | 目录 |
|---|---|---|
/api/*, /health, /version |
Go 后端 API | proxy → localhost:8080 |
/images/* |
商品图片 | /opt/jiu/images/ |
/app/ |
Flutter 管理端 SPA | /opt/jiu/web/ |
/ |
营销首页 | /opt/jiu/marketing/index.html |
/docs |
文档页 | /opt/jiu/marketing/docs.html |
/download |
下载页 | /opt/jiu/marketing/download.html |
/scan/<id> |
商品防伪扫码页 | /opt/jiu/marketing/scan.html |
/features/* |
功能介绍页 | /opt/jiu/marketing/features/ |
/assets/* |
营销站点静态资源 | /opt/jiu/marketing/assets/ |
注意:
nginx-jiu.conf只包含jiu.51yanmei.com的 server block。服务器上其他服务的 nginx 配置不受影响。更新 nginx 配置时只需替换此文件,然后nginx -t && nginx -s reload。
回滚方式
后端回滚
# SSH 到 EC2
sudo systemctl stop jiu
# 从上一个 Forgejo Release 下载 jiu-server
cp /path/to/old-jiu-server /opt/jiu/backend/jiu-server
chmod +x /opt/jiu/backend/jiu-server
sudo systemctl start jiu
Flutter Web 回滚
# 恢复上一版本
mv /opt/jiu/web /opt/jiu/web-broken
mv /opt/jiu/web-old /opt/jiu/web
sudo nginx -s reload
环境变量(jiu 服务)
Systemd service 文件(/etc/systemd/system/jiu.service)中配置:
[Service]
Environment=DB_HOST=127.0.0.1
Environment=DB_PORT=3306
Environment=DB_NAME=jiu
Environment=DB_USER=jiu
Environment=DB_PASSWORD=<从 secrets 获取>
Environment=JWT_SECRET=<随机字符串>
Environment=APP_VERSION=1.0.0
Environment=BUILD_DATE=2025-05-23
Environment=DOWNLOAD_URL_MACOS=
Environment=DOWNLOAD_URL_WINDOWS=
APP_VERSION 和 BUILD_DATE 在每次部署时通过 deploy.yml 更新(预留,目前读取 Forgejo Release tag)。