Files
jiu/docs/deployment-nas-gitea.md
T
wangjia 9f94cc97b7 fix(test): 修复测试数据库 schema 和 Flutter 测试与屏幕重构的兼容性
- 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>
2026-05-24 10:48:21 +08:00

252 lines
8.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# NAS + Gitea 部署文档
## 概述
岩美酒库管理系统使用 **群晖 NAS 上自托管的 GiteaForgejo** 作为代码仓库和 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 状态
```bash
# 查看日志
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 不工作时的排查步骤
1. **确认 NAS 可达**`curl -sf http://192.168.3.200:3000/api/v1/version`
2. **确认 relay 在跑**`ps aux | grep tcp_relay`
3. **确认 relay 端口监听**`nc -zv 127.0.0.1 13000`
4. **查看最近日志**`tail -30 /tmp/act_runner.log`
5. **手动测试**`/Users/wangjia/bin/act_runner daemon --config ~/.act_runner_config.yaml`(从终端运行应直接成功)
6. **强制重启**`kill $(pgrep -f act_runner_start.sh)` — LaunchAgent 会在几秒内自动重拉
### 重新注册 Runnertoken 失效时)
```bash
# 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` 分支
**步骤:**
1. `go test ./...` — 跑后端测试
2. `flutter test` — 跑前端测试
3. 编译后端:`GOOS=linux GOARCH=amd64 go build` 生成 `jiu-server`
4. 编译 Flutter Web`flutter build web --release --base-href=/app/`
5. 创建 Forgejo Release(版本号格式 `vYYYYMMDD.HHMM`),上传 `jiu-server``web.tar.gz`
6. 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:00cron
**步骤:** 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` |
---
## 触发部署方式
日常开发流程:
```bash
# 开发 → 本地测试
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`。
---
## 回滚方式
### 后端回滚
```bash
# 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 回滚
```bash
# 恢复上一版本
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`)中配置:
```ini
[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)。