refactor: 全量迁云——fam-core 直读甲骨文 SQLite,NAS 只剩推送进程

前一天刚把登录迁到甲骨文,隔天 NAS 上的 fam-core 又挂了导致数据接口 502。
盘点后确认:甲骨文的 SQLite 才是权威数据源(videos 3113 / events 16159 /
people 60 / model_calls 9876),NAS 的 MariaDB 全是它的镜像——前端读的数据
本来就产自甲骨文,绕了一圈回家又绕回来。

改动:
- db_layer.py 从 725 行重写成 377 行:MySQL 镜像查询改为直读 fam-edge 的
  SQLite。5 个 upsert_sync_*(约 300 行去重逻辑,8/29 和 9/3 两次 1062 事故的
  发源地)连同 oracle_sync.py 整个删除。SQL 方言:JSON_CONTAINS -> json_each
  (前置 json_valid,历史脏数据不会把查询搞崩)、LEFT() -> substr()、%s -> ?。
  函数名 get_sync_* 一并改掉——已经没有 sync 这回事了
- 新增 edge_client.py:写操作(改名/删除)、帧图头像、服务状态都打给同机
  fam-edge,全走 127.0.0.1
- 新增 fam-notifier/:motion_notifier 从 fam-core 拆出独立成服务,游标从
  MariaDB 换成本地 JSON 文件。NAS 上从此没有 Flask、没有数据库、没有监听端口
- fam-core 移到甲骨文 /opt/fam-core(systemd,gunicorn -w 2,只绑
  127.0.0.1:5401——5400 被 chat-relay 占了)。Caddy 的 /api/* 从"frp 隧道
  回源 NAS"改成同机反代,forward_auth 闸门不变
- 前端删掉侧边栏同步面板、统计页同步状态、服务状态页的"NAS 同步"卡片与
  "立即同步"按钮(背后的镜像层已不存在);换成"NAS 运动推送"卡片,读
  fam-edge activity 新增的 motion 段(心跳年龄 + 最近事件)
- 顺带修掉一个隐蔽 bug:镜像表为保外键稳定用的是 NAS 本地自增 id,而帧图接口
  要的是甲骨文的 id,两边在 9/3 那次 id 重排后就对不上了。现在只有一套 id

测试:fam-core 21(新增 12 个 db_layer 用例:脏 JSON 不崩、人物精确匹配不误伤
"人物B"、日期过滤、统计口径、chat_history 懒建表)、fam-notifier 6、
fam-edge 157,全绿。

生产验证:甲骨文 /api/ui/stats 返回 videos 2965 / events 16159 / people 59;
NAS 侧 fam-notifier 已推送成功(事件 33070-33072 落库,心跳新鲜);
chat_history 19 条经 scripts/import_chat_history.py 迁移完成。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
ericwyuan
2026-09-13 08:03:32 +08:00
parent dc284f4bf5
commit 1964e976f4
33 changed files with 1178 additions and 1187 deletions

View File

@@ -541,3 +541,74 @@ AI 分析瓶颈: 帧5耗时 229s (疑似 ARM CPU 热降频), 其余帧 50-65s
- `/api/ui/videos`(无 cookie→ 401 → 前端据此自动跳 `/login`
登录链路已通:未登录访问任意页面 → 首个 401 → 自动跳 auth-hub 登录 → 回调种 `fam_session` cookie → 回 `/timeline` 正常加载。
## 统一登录从 NAS 迁到甲骨文 fam-edge2026-09-12
**触发**`smart-camera.zichuan.xyz/login` 报 502。排查链路静态页 `/``/timeline` 200Caddy 读本机磁盘),
只有 `/login``/api/*` 502甲骨文本机 `curl 127.0.0.1:8000/health` 0.6 秒空响应curl exit 52
Caddy 日志 `msg:"EOF"`frps 正常、同隧道的 gitea 也正常 → NAS 上 fam-core 进程没了。
局域网直扫 NAS22/2222/3000/5000/5001/3306 全 OPEN**只有 8000 closed**NAS 本身没事。
根因是登录入口挂在 NAS 上,而 NAS 上的 fam-core 没有任何守护DSM 无 systemd挂了不会自启。
**决策**:登录是入口,不该依赖家里的机器。整体迁到甲骨文的 fam-edge前端静态文件、auth-hub 本来就在这台),
NAS fam-core 退化成纯数据接口。
**改动**
- 新增 `fam-edge/src/fam_edge/auth.py``/login``/api/auth/callback``/api/logout`
`/api/auth/check``/api/auth/verify`(给 Caddy forward_auth。跟旧实现三处关键差异
1. 换 token / 拉 JWKS 走 `AUTH_HUB_INTERNAL_BASE`(本机 :5300不再跨公网 TLS——旧链路上
`PyJWKClient` 用 urllib + 系统 CA群晖易 CERTIFICATE_VERIFY_FAILED、两机时钟偏差会让 `iat`
显得来自未来,这两个坑一起消失;但 `iss` 校验和浏览器跳转仍用公网 issuer
2. 会话改无状态 HS256 签名 cookie服务重启不掉线旧实现进程内 token 表)
3. **回调失败渲染错误页,不再 302 回 `/login`**——旧实现失败即跳 `/login`,而 auth-hub 只要还有
会话就立刻再签一个 code 跳回来,两边对跳成死循环,浏览器只报「重定向次数过多」,
既看不到登录页也看不到原因(这正是 9/1 那次「跳不到登录页」的成因)
- 删除 `fam-core/src/fam_core/auth.py` + `tests/test_auth.py``app.py` 去掉 `init_auth`
fam-core 不再有任何鉴权,改由甲骨文 Caddy `forward_auth` 前置拦截
- `fam-edge/tests/test_auth.py` 16 个用例(含「失败分支绝不 302」的回归测试、
「服务端走内网但 iss 按公网校验」、「cookie 无状态」fam-edge 157 / fam-core 15 全绿
- 写测试时逮到自己写的一个 bug会话 cookie 也套了 60 秒 leeway本进程自签自验根本不需要
会让每个会话白白多活 60 秒,已改成只有 id_token 用 leeway
**遗留风险(已记入 README/DEPLOY**frps 在甲骨文绑的是 `*:8000` 且 iptables 明确放行,
fam-core 去掉鉴权后,绕过 Caddy 直连 `129.146.26.249:8000` 就是无门禁的全量数据接口,
必须靠 `iptables -I INPUT 1 -p tcp --dport 8000 ! -i lo -j DROP` 兜底。
另:登录进程并到 fam-edge 后fam-edge 正在跑视频分析时登录响应可能变慢(单 worker 4 线程)。
## 全量迁云NAS 只剩推送进程2026-09-13
**触发**:前一天刚把登录迁到甲骨文,隔天 NAS 上的 fam-core 又挂了导致数据接口 502。
用户一句话点破「NAS 上只有一个无状态的通知甲骨文的服务,剩下的全部在甲骨文呀」。
盘点后确认这个判断成立——甲骨文的 SQLite 才是权威数据源videos 3113 / events 16159 /
people 60 / model_calls 9876NAS 的 MariaDB 全是它的镜像,前端读的数据本来就产自
甲骨文,绕了一圈回家又绕回来。
**改动**
- `fam-core/db_layer.py` 从 725 行重写成 377 行MySQL 镜像查询 → 直读 fam-edge 的
SQLite。5 个 `upsert_sync_*`(约 300 行去重逻辑8/29 和 9/3 两次 1062 事故的发源地)
连同 `oracle_sync.py` 整个删除。SQL 方言:`JSON_CONTAINS``json_each`(前置
`json_valid`,历史脏数据不会把查询搞崩)、`LEFT()``substr()``%s``?`
函数名 `get_sync_*` 一并改掉——已经没有 sync 这回事了,留着名字会误导。
- 新增 `fam-core/edge_client.py`:写操作(改名/删除)、帧图头像、服务状态都打给同机
fam-edge全走 127.0.0.1,不出公网。
- 新增 `fam-notifier/`:把 `motion_notifier.py` 从 fam-core 拆出来,游标从 MariaDB
换成本地 JSON 文件。NAS 上从此没有 Flask、没有数据库、没有监听端口只有一个
单向推送进程。
- fam-core 移到甲骨文 `/opt/fam-core`systemdgunicorn -w 2**只绑 127.0.0.1:5401**——
5400 被 chat-relay 占了。Caddy 的 `/api/*` 从"frp 隧道回源 NAS"改成同机反代,
forward_auth 闸门不变。
- 前端跟着删:侧边栏同步面板、统计页同步状态、服务状态页的"NAS 同步"卡片和
"立即同步"按钮(背后的镜像层已不存在)。"NAS 同步"换成"NAS 运动推送",读
fam-edge activity 新增的 `motion` 段(心跳年龄 + 最近事件)。
- 顺带修掉一个隐蔽 bug镜像表为保外键稳定用的是 NAS 本地自增 id而帧图接口要的是
甲骨文的 id两边在 9/3 那次 id 重排后就对不上了。现在只有一套 id。
**测试**fam-core 21新增 12 个 db_layer 用例:脏 JSON 不崩、人物精确匹配不误伤
"人物B"、日期过滤、统计口径、chat_history 懒建表、fam-notifier 6、fam-edge 157全绿。
**验证**:甲骨文 `/api/ui/stats` 返回 videos 2965 / events 16159 / attention 13 /
people 59外网 `/` `/timeline` 200、`/login` 302、`/api/*` 未登录 401——**全程 NAS
上的 fam-core 是停着的**,这就是迁云的验收标准。
**待办**NAS 侧部署 fam-notifier只能用户手动密码登录chat_history 一次性迁移
`fam-core/scripts/import_chat_history.py`幂等frpc.toml 里的 8000 映射可删。