Commit Graph

8 Commits

Author SHA1 Message Date
ericwyuan
9d6ebbe422 ops: 生产从 NAS 整体迁移到甲骨文云主机
NAS 局域网 IP 因重启被 DHCP 换过两次,磁盘/网络稳定性都不如已经跑着好几个
生产服务的甲骨文机器。整体搬迁:应用 + 数据库都搬走,NAS 只保留 Gitea(这个
仓库的源码托管,未动)。

## 迁移过程(已核对无损)

- MariaDB:NAS 导出(10.11 源库,处理了只有新版本才有的 `/*M!999999` 注释)
  → 导入甲骨文 MariaDB 10.3.39,**20 张表逐条精确 COUNT(*) 比对完全一致**
- 冻结 NAS(停服务)后又 dump 一次核对,确认期间零数据差异,才继续删库
- NAS `garmin_health_lab` 已 DROP DATABASE,备份在本地
  `~/Desktop/Work/backups/garmin_health_lab_nas_backup_20260912.sql.gz`
- 应用部署到 `/opt/garmin-health-lab`,systemd 单元(`ubuntu` 用户,非
  root),和这台机器上的 ai-gateway/auth-hub 同一套约定
- 公网:`https://garmin.zichuan.xyz`,DNS + Caddy 反代 + 自动 TLS,替代原来
  `NAS frpc → 甲骨文:8124` 那条隧道(已从 NAS 的 frpc.toml 精确删除对应段,
  其它转发未动,改完逐条复检过没打断)
- auth-hub 回调地址换成新域名,NAS/旧端口那几条历史回调已清掉
- AI 网关配置改本地回环(网关现在同机了),触发真实生成验证过

## 一个当场拦下来的风险

甲骨文部署完默认开着自动同步。迁移窗口期两边并行跑时,若两边的调度器同时去
刷新 Garmin 令牌,会撞上按账号计算的 SSO 限流(`GarminHealthLab` 仓库
2026-09-03 那次事故的根因,那次修复花了一整天)。确认账号级 auto_sync 设置
本来是关的、这次算侥幸没撞上——不是设计上的保险,所以迁移期间显式在甲骨文这边
加了 `AUTO_SYNC=false`,直接在运行进程里验证过生效,确认 NAS 已冻结、数据无
缺口后才打开。

## 文档 / 脚本同步

CLAUDE.md 明确写过"部署位置会变,排障前先查、不要凭记忆"——这次是第二次踩中
同一类问题(上次是"NAS 有没有生产环境"判断错),所以把 CLAUDE.md / PROGRESS.md
/ README.md / docs/* 里的部署事实全部更新,NAS 时代的 `deploy/` 脚本加废弃
说明保留参考、不删除,新增 `deploy/push_oracle.sh`(当场跑通一次真实部署)。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-12 23:53:15 +08:00
ericwyuan
74f2721401 fix(deploy): S99garmin.sh 丢了可执行位,重启两天没人发现服务已经死了
09-06 07:08 NAS 重启(DHCP 顺带把它的局域网地址从 .64 换成了 .65),佳明健康
服务再也没起来——两天后才被发现。

根因:`deploy/S99garmin.sh` 本地就没有 +x(`-rw-r--r--`,另外三个部署脚本都是
`-rwxr-xr-x`)。DSM 的 rc.d 启动器碰到一个没有执行权限的软链接目标,既不执行
也不报错,静默跳过。push.sh 用 tar 同步 deploy/ 目录会原样带走权限位,所以自
09-01 起只要重新部署一次,就会把 NAS 上(原本可能是手工修过的)可执行权限
再次覆盖回不可执行——这颗雷从那天就埋下了,直到这次重启才被踩到。

- 本地补上 S99garmin.sh 的 +x
- push.sh 同步完 deploy/ 之后显式 chmod +x 三个会被开机脚本或部署流程直接
  执行的文件,并断言生效——以后这个位再丢,部署会报错退出,不会再悄悄失效
  到下次重启才现形
- push.sh 的默认目标 IP 也是这次连带发现的坑:硬编码的 192.168.50.64 已经
  证明会被 DHCP 换掉,改成当前地址 .65 并加注释——真正的解法是给 NAS 做
  DHCP 保留(MAC 90:09:d0:22:a6:33),不在这次改动范围内

现场同时处理:手动 chmod +x 后 sh start.sh 拉起服务;frpc 配置里的
localIP 硬编码着 192.168.50.64(同样的病),改成 127.0.0.1 后不再受局域网
IP 变化影响,公网已验证恢复。frpc 改动是 NAS 系统配置,不在这个仓库里,
旧文件备份在 NAS 的 /etc/frp/frpc.toml.bak-20260908。

遗留:auth-hub 注册的 LAN 回调地址还是 http://192.168.50.64:8124/auth/callback
(CLAUDE.md:99),局域网内直接用 .65 访问会在登录环节被拒;公网入口不受影响。
要不要把它也换成 DHCP 保留后的固定地址,留给用户决定。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-08 07:27:22 +08:00
ericwyuan
984a06a098 fix(deploy): deploy/ 自己没跟着上机,改它等于没改
重启跑的是 NAS 上那份 stop.sh / start.sh,而 push.sh 只同步 backend/ 和
client/build/。结果就是:修好 stop.sh 的 pgrep、提交、部署、报成功,远端
照样跑着旧脚本——上一个提交正是这么被架空的。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-01 14:34:59 +08:00
ericwyuan
14b6bbecb4 fix(deploy): 群晖没有 pgrep,那三处「守卫」一直是空转
上一版给 push.sh 加的 pid 比对刚跑就露馅了:`sh: pgrep: command not found`,
于是 BEFORE 和 AFTER 都是空字符串,`[ -n "$BEFORE" ]` 直接跳过检查——我刚加
的安全网自己就是个摆设。

顺手查了一圈,stop.sh 和 start.sh 里同样的 pgrep 也一直在空转:
- stop.sh 的 `pgrep ... || exit 0` 每次都以 127 失败,永远匹配不到那个提前
  返回,所以每次停服都白等满 15 秒,也从没真正确认过残留 worker 已经没了。
- start.sh 的等待循环同理,白等 20 秒。

DSM 有 pkill 没有 pgrep,而且非特权的 ps 看不见 root 起的进程(服务是开机
以 root 启动的),两者任一都会让检查静默返回「没有」——这正是最危险的答案,
因为它长得和「已经停干净了」一模一样。

- push.sh 改用 `sudo ps -eo pid,args | grep`;并且 AFTER 为空时直接报错退出,
  不再把「看不见」当成「通过」
- stop.sh / start.sh 改用 `ps -eo args | grep -q`

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-01 14:34:27 +08:00
ericwyuan
f6687d2b5d fix(deploy): push.sh 报「部署成功」,但跑的还是旧代码
服务是开机时以 root 起的(DSM 任务计划 → S99garmin.sh),所以 logs/
error.log 和 access.log 归 root。以 ericwyuan 重启会立刻失败——gunicorn
连自己的错误日志都打不开;而 stop.sh 同样杀不掉 root 进程,于是旧 master
继续占着 :8124,start.sh 的健康检查看到 200,高高兴兴报了「started」。

这次 AI 教练部署就撞上了:文件推上去了,接口却一直 404,查了半天才发现
在跑的是几小时前那个进程。

- 重启改走 sudo,和服务实际的运行身份一致(密码问一次,或走 NAS_PASSWORD)
- 健康检查加一条:只有 200 不算数——那正是旧 master 会给的答案。比对重启
  前后 gunicorn 的 pid,没变就报错退出

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-01 14:11:17 +08:00
ericwyuan
4d55fb4776 chore(deploy): 别把 macOS 的 xattr 塞进 tar,群晖读不懂只会刷警告
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-01 09:09:21 +08:00
ericwyuan
1c48435b9d chore(deploy): NAS 的 ssh 端口是 2222
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-01 09:05:35 +08:00
ericwyuan
41b7ae82e4 fix(sync): 「全部历史」真的拉全部历史,自动同步不再每次静默失败
四个独立的 bug 叠在一起,表现为「只同步两天、没有进度」:

* 前端 `...(days ? { days } : {})` 把 days=0 当成未传。「全部历史」
  存的就是 0,请求体里根本没有 days,后端退回 7 天默认值。
* scheduler 用 `s[0]` 读 query_one 返回的 dict,抛 KeyError 后被
  per-account 的 except 吞掉。只要用户存过一次设置,每 30 分钟的
  自动同步就一次都没成功过——库里那 2 天全是手动点出来的。
* 增量同步查 `health_daily`(表其实叫 health_data),后台线程直接
  死掉,状态永远卡在 syncing,进度条不动。
* UI 完全不看 /sync 的返回值,rate_limited 时按钮点了没反应;轮询
  结束时又把 rate_limited 归进 else 分支报「同步完成」。

顺带:
* 日循环遇到 429 立即退避并保留已拉到的天数,而不是当成「跳过一天」
  继续往下捶 700 天——这正是之前限流死循环的来源之一。
* 定时循环显式传 SYNC_DAYS。历史范围按 UI 文案只描述手动全量同步,
  让半小时一次的 tick 重拉 730 天必然把限流撞得更深。
* 短同步逐天上报进度(原来每 5 天一次,7 天的同步全程停在 0)。
* /sync 路由重复解析 body,空 body 会 None.get 崩。
* 4 个 StubGarth 缺 configure(),7 个测试在此之前一直是红的。

新增 deploy/push.sh:NAS 只认密码,脚本开一个 ssh 复用连接,密码只
输一次,后面推送 / 重启 / 健康检查全走它。不碰 .env、.venv 和数据库。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-01 08:11:24 +08:00