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:
69
README.md
69
README.md
@@ -63,9 +63,9 @@ Orchestrator 视觉阶段按 `fallback` 模式顺序降级:Gemini → NVIDIA N
|
||||
|
||||
| 节点 | 角色 | 硬件 | IP | 服务与端口 |
|
||||
|------|------|------|-----|-----------|
|
||||
| Synology NAS | FAM-Core + 数据库(采集 + 聚合 + 镜像层) | DS220+ (Geminilake), DSM 7 | 家庭局域网 192.168.50.64 | FAM-Core :8000(API 聚合 + 轮询 SS + OracleSync), MariaDB :3306, Surveillance Station :5000;FAM-UI 已迁至云服务器(见下) |
|
||||
| Oracle Cloud(云服务器) | FAM-Edge + AI-Gateway + Caddy(前端) | Ampere A1 4C23G ARM64(无 GPU), Ubuntu 20.04 | 公网 129.146.26.249 / Tailscale(已安装未启用,备用) | FAM-Edge :5000(systemd 守护,视频分析 + 转发问答), **AI-Gateway :5100(systemd 守护,对外监听,问答模型降级链)**, Caddy :80(托管 FAM-UI SPA + 反代 /api/* → NAS FAM-Core), Ollama :11434(仅本地,被 AI-Gateway 调用) |
|
||||
| 家庭网络 | 用户入口 | 普通终端 | 公网 / 家庭网络 | 浏览器访问 `http://129.146.26.249/`(Caddy 托管前端,/api 经 frp 隧道回源 NAS FAM-Core :8000) |
|
||||
| Synology NAS | 摄像头录像 + 运动事件推送(**仅此而已**,2026-09-13 起) | DS220+ (Geminilake), DSM 7 | 家庭局域网 192.168.50.64 | Surveillance Station :5000(录像机本体,搬不走), **fam-notifier**(无端口的推送进程,轮询 SS → 推甲骨文)。MariaDB 与 FAM-Core 已随镜像层一起下线 |
|
||||
| Oracle Cloud(云服务器) | **除录像外的全部**:视频分析 + 数据 + 接口 + 登录 + 前端 | Ampere A1 4C23G ARM64(无 GPU), Ubuntu 20.04 | 公网 129.146.26.249 / Tailscale(已安装未启用,备用) | FAM-Edge :5000(systemd,视频分析 + 统一登录 + SQLite 权威库), **FAM-Core :5401(systemd,UI/对话/成员接口,仅监听 127.0.0.1)**, AI-Gateway :5100(systemd,问答模型降级链), Caddy :80/:443(前端 + 反代 + forward_auth 鉴权), Ollama :11434(仅本地) |
|
||||
| 家庭网络 | 用户入口 | 普通终端 | 公网 / 家庭网络 | 浏览器访问 `https://smart-camera.zichuan.xyz/`(全部由甲骨文提供,**不依赖 NAS 在线**) |
|
||||
|
||||
**网络要点(推送模式)**:
|
||||
- 服务间通信只有两条出站:**NAS → Oracle 公网 IP:5000**(① `GET /api/oracle/sync` 拉增量 + `POST /api/oracle/people/correct` 命名回推 ② `POST /api/ss/motion` 推送运动侦测事件)。Edge 不需要反向访问 NAS
|
||||
@@ -98,19 +98,22 @@ Orchestrator 视觉阶段按 `fallback` 模式顺序降级:Gemini → NVIDIA N
|
||||
│ ├─ /v1/chat/completions:NVIDIA → Gemini(多 Key 轮换)→ 本地 Ollama 降级链 │
|
||||
│ ├─ Bearer token 鉴权(fail-closed),对外 0.0.0.0:5100,非本系统专属 │
|
||||
│ └─ 独立 .env(/opt/ai-gateway/.env,NVIDIA/Gemini key 与 FAM-Edge 各自一份) │
|
||||
│ Caddy :80:托管 FAM-UI SPA + 反代 /api/* → NAS FAM-Core(frp 隧道 :8000) │
|
||||
└───────────────────────────────┬───────────────────────────────────────────────┘
|
||||
│ HTTP GET /api/oracle/sync?since=&token= (每 30 分钟)
|
||||
▼
|
||||
│ FAM-Core (Flask :5401,仅 127.0.0.1) │
|
||||
│ ├─ UI-API: /api/ui/*(直读下面那个 SQLite,无镜像、无后台线程) │
|
||||
│ ├─ Chat-Handler: /api/chat/ask(查 events 拼上下文 → 转 fam-edge 编排) │
|
||||
│ ├─ Member-Manager: /api/member/name|merge(转 fam-edge,它负责合并人物) │
|
||||
│ └─ chat_history: 本服务唯一写的表,落在同一个 SQLite 里 │
|
||||
│ SQLite /opt/fam-edge/data/oracle.db(WAL):videos / events / people / │
|
||||
│ model_calls / person_identity_map / ss_motion_events / chat_history │
|
||||
│ Caddy :80/:443:前端静态 + /login 与 /api/auth/* → fam-edge │
|
||||
│ + /api/* 先 forward_auth 再反代 fam-core(唯一鉴权闸门) │
|
||||
└───────────────────────────────▲───────────────────────────────────────────────┘
|
||||
│ POST /api/ss/motion(单向推送 + 5 分钟心跳)
|
||||
│
|
||||
┌─────────────────────────────── NAS (192.168.50.64) ────────────────────────────┐
|
||||
│ FAM-Core (Flask :8000) │
|
||||
│ ├─ Oracle-Sync: 唯一后台线程,拉增量写 MariaDB 镜像 + 维护 sync_cursor │
|
||||
│ ├─ Chat-Handler: /api/chat/ask(查 sync_events 拼上下文 → 走 Oracle 编排) │
|
||||
│ ├─ Member-Manager: /api/member/name|merge(回推 Oracle + 即时拉回) │
|
||||
│ ▼ │
|
||||
│ MariaDB (sentinel_home_ai): sync_videos / sync_events / sync_people / │
|
||||
│ sync_cursor / chat_history │
|
||||
│ (FAM-UI 已迁至云服务器 Caddy :80,见上) │
|
||||
│ Surveillance Station :5000(录像机本体,摄像头插在这台上) │
|
||||
│ fam-notifier:轮询 SS EventCenter → 推运动事件给甲骨文。无端口、无数据库, │
|
||||
│ 游标存本地 JSON 文件。挂了重启即可,不影响网站。 │
|
||||
└─────────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
@@ -315,15 +318,23 @@ chat_history 独立表(问答上下文摘要留存)
|
||||
**GET /api/oracle/avatar** — 人物头像:`?label=&w=` → jpeg(从该人物候选事件 `person_appearances` bbox 裁剪)
|
||||
**GET /health** — 服务状态(含已处理视频数)
|
||||
|
||||
### 5.2 FAM-Core(NAS :8000)
|
||||
**登录相关端点**(2026-09-12 从 NAS FAM-Core 迁入,见 `fam-edge/src/fam_edge/auth.py`)
|
||||
|
||||
| 端点 | 方法 | 说明 |
|
||||
|------|------|------|
|
||||
| `/login` | GET | 登录入口:生成 PKCE 参数后 302 跳 auth-hub 公网 `/authorize` |
|
||||
| `/api/auth/callback` | GET | auth-hub 回跳:用 `AUTH_HUB_INTERNAL_BASE`(本机 :5300)换 token + 拉 JWKS 验签,成功后种 HttpOnly cookie `fam_session`(HS256 无状态签名,2 小时);**任何一步失败都渲染错误页,绝不 302 回 `/login`**——旧实现失败即跳 `/login`,而 auth-hub 有会话时会立刻再签发 code,两边对跳成死循环 |
|
||||
| `/api/logout` | POST | 退出登录(清 cookie,不影响 auth-hub 上的 SSO 会话) |
|
||||
| `/api/auth/check` | GET | 登录态检查:`{"authed": true\|false, "username": "..."}` |
|
||||
| `/api/auth/verify` | ANY | 给甲骨文 Caddy 的 `forward_auth` 用:已登录 204,未登录 401 JSON |
|
||||
|
||||
### 5.2 FAM-Core(甲骨文 127.0.0.1:5401)
|
||||
|
||||
> 2026-09-13 起 FAM-Core 与 FAM-Edge 同机,直读它的 SQLite(不再有 MariaDB 镜像层),且**只监听 127.0.0.1**——唯一客户端是同机 Caddy。登录端点在 FAM-Edge(见 §5.1),鉴权由 Caddy 的 `forward_auth` 前置完成,所以本服务自身不做任何校验。
|
||||
|
||||
| 端点 | 方法 | 说明 |
|
||||
|------|------|------|
|
||||
| `/health` | GET | 服务健康(**免登录**) |
|
||||
| `/login` | GET | 登录入口:302 跳转 auth-hub `/authorize`(Authorization Code + PKCE,**免登录**) |
|
||||
| `/api/auth/callback` | GET | auth-hub 登录回跳:拿 `code` 换 token、验 `id_token` 签名后种 HttpOnly cookie `fam_session`(2 小时,**免登录**) |
|
||||
| `/api/logout` | POST | 退出登录(清本地 cookie,不影响 auth-hub 上的登录态) |
|
||||
| `/api/auth/check` | GET | 登录态检查:`{"authed": true\|false}`(**免登录**) |
|
||||
| `/api/status` | GET | Oracle-Sync + MotionNotifier 状态(sync running/cursor;motion poll_enabled/pushed_total/heartbeat) |
|
||||
| `/api/chat/ask` | POST | 用户问答:`{"question","queried_person","queried_date"}` → `{"answer","context_summary","chat_id"}`(上下文来自 sync_events) |
|
||||
| `/api/chat/history` | GET | 对话历史(`?date=` 或 `?person=&limit=`) |
|
||||
@@ -540,17 +551,27 @@ task_id=289(30s 测试片段)全链路打通:推送 5.7MB → Edge 分析
|
||||
|
||||
| 组件 | 节点 | 路径 | 启动 |
|
||||
|------|------|------|------|
|
||||
| FAM-Core | NAS | `/volume1/web/sentinel-home-ai/fam-core/` | `bash start_core.sh`(gunicorn -w 1 :8000,source .env 注入 DSM_*/ORACLE_SYNC_TOKEN/AUTH_HUB_*) |
|
||||
| FAM-Core | Oracle | `/opt/fam-core/` | **systemd `fam-core.service`**(gunicorn -w 2 --threads 4,绑 127.0.0.1:5401);部署代码后 `sudo systemctl restart fam-core` |
|
||||
| fam-notifier | NAS | `/volume1/web/sentinel-home-ai/fam-notifier/` | `setsid bash scripts/start_notifier.sh &`(DSM 无 systemd,挂了不自启);NAS 上唯一运行的本项目进程 |
|
||||
| FAM-UI | 云服务器(甲骨文,129.146.26.249) | `/var/www/fam-ui/` | Vue3 构建产物,由 Caddy :80 静态托管(无需独立进程);本地改代码后 `npm run build`,`dist/` rsync/tar 到云服务器 |
|
||||
| FAM-Edge | Oracle | `/opt/fam-edge/` | **systemd `fam-edge.service` 守护**(Restart=always);部署代码后 `sudo systemctl restart fam-edge`(勿手动 setsid,会端口冲突) |
|
||||
| **AI-Gateway** | Oracle | `/opt/ai-gateway/` | **systemd `ai-gateway.service` 守护**(Restart=always),独立 venv(Python 3.8);gunicorn 绑定 `0.0.0.0:5100`(对外直接开放,非仅本机);部署代码后 `sudo systemctl restart ai-gateway` |
|
||||
| Ollama | Oracle | systemd 托管 | 环境变量 `OLLAMA_KEEP_ALIVE=-1`;**被 AI-Gateway 调用,不再被 FAM-Edge 调用** |
|
||||
|
||||
> **外网访问(frp 内网穿透)**:NAS 跑 `frpc`(`/etc/frp/frpc.toml`,S99frpc.sh 守护),映射到云服务器 `129.146.26.249`(frps :7000):
|
||||
> - `3000` → NAS Gitea、`8500` → NAS WordPress(8088)、**`8000` → NAS FAM-Core(本系统)**
|
||||
> - 前端入口 `http://129.146.26.249/`(Caddy 托管 FAM-UI,/api 经 frp 隧道反代回 NAS FAM-Core :8000),**需登录**(见 §5.2 `/login`)
|
||||
> - `3000` → NAS Gitea、`8500` → NAS WordPress(8088)
|
||||
> - **`8000` → NAS FAM-Core 这条已于 2026-09-13 迁云后废弃**(本系统不再有任何"回源 NAS"的流量,可从 frpc.toml 删除)
|
||||
> - 前端入口 `https://smart-camera.zichuan.xyz/`:静态页、接口、登录全部由甲骨文提供,**NAS 离线也能正常访问**(只是不再有新的运动事件推进来),**需登录**(见 §5.1 `/login`)
|
||||
>
|
||||
> **登录校验(2026-08-22 新增,2026-08-23 改为 fail-closed,2026-08-31 接入 auth-hub 统一登录)**:FAM-Core 不再自己保存/校验密码,全权委托给独立部署的 [auth-hub](http://129.146.26.249:5300)(OAuth2 Authorization Code + PKCE / OIDC,同一 Oracle 主机 :5300,与本系统完全独立部署)——`/login` 302 跳到 auth-hub `/authorize`,登录后带 `code` 跳回本服务 `/api/auth/callback`,服务端换 token、验完 `id_token` 签名(JWKS)后种本地会话 cookie。全站拦截仍在 `auth.py`:页面未登录 302 `/login`,`/api/*` 未登录 401;接入参数 `AUTH_HUB_ISSUER`/`AUTH_HUB_CLIENT_ID`/`AUTH_HUB_CLIENT_SECRET`/`AUTH_HUB_REDIRECT_URI`(NAS `.env` 配置,**无硬编码默认值**,任一没配置直接拒绝所有登录,fail closed);`AUTH_HUB_REDIRECT_URI` 必须与 auth-hub 用 `manage_clients create` 登记的 redirect_uri 逐字符一致(auth-hub 只做精确匹配,不做前缀/子串匹配)。白名单免登录:`/login` `/api/auth/callback` `/api/logout` `/api/auth/check` `/health` `/assets/*`(`/api/ss/webhook` 已随端点一起移除,2026-08-25)。本地会话仍是进程内 token + HttpOnly cookie(**2 小时**),过期或重启 fam-core 需重新走一遍 auth-hub 登录;auth-hub 侧注册是任何人都能自助注册(管理员审批后才能登录),**未对 fam-core 侧再加用户名白名单**——任何在 auth-hub 上审批通过的账号登录后都能访问本系统,这是有意识的取舍(自用场景,见项目记忆)。
|
||||
> **登录校验(2026-08-22 新增;2026-08-31 接入 auth-hub 统一登录;2026-09-12 整体迁到甲骨文 FAM-Edge)**:登录流程和会话校验现在都在甲骨文 FAM-Edge 的 `auth.py`(§5.1),NAS FAM-Core 自身不再做任何鉴权。迁移的直接原因:NAS 或 frp 隧道一挂,`smart-camera.zichuan.xyz/login` 直接 502,**连登录页都打不开**——登录是入口,不该依赖家里那台机器。
|
||||
>
|
||||
> - **链路**:`/login`(Caddy → 本机 fam-edge :5000)→ 302 到 auth-hub 公网 `/authorize`(Authorization Code + PKCE)→ 用户在 [auth-hub](https://auth.zichuan.xyz) 登录 → 回跳 `/api/auth/callback`(仍是 fam-edge)→ 走**本机** `http://127.0.0.1:5300` 换 token、拉 JWKS 验 `id_token` 签名 → 种 cookie `fam_session`
|
||||
> - **为什么服务端调用走本机**:旧实现是 NAS 跨公网访问 `https://auth.zichuan.xyz`,那条链路上 `jwt.PyJWKClient` 用 urllib + 系统 CA(不像 requests 自带 certifi),群晖上容易 `CERTIFICATE_VERIFY_FAILED`;两台机器的时钟偏差还会让 `iat` 看起来来自未来。同机直连把这两个坑一起消掉。但 `iss` 校验和浏览器跳转仍用公网 `AUTH_HUB_ISSUER`
|
||||
> - **拦截**:甲骨文 Caddy 对 `/api/*` 做 `forward_auth` → fam-edge `/api/auth/verify`,200/204 才反代回 NAS;`/login`、`/api/auth/*`、`/api/logout` 直接由 fam-edge 处理(Caddy 按路径具体程度排序,这几条稳定排在 `/api/*` 前面)
|
||||
> - **NAS :8000 必须靠防火墙兜底**:frps 把它暴露在公网(历史入口 `http://129.146.26.249:8000`),绕过 Caddy 直连就没有任何鉴权。甲骨文 iptables 只允许本机访问 :8000,见 `docs/DEPLOY.md` §2.3
|
||||
> - **参数**(`/opt/fam-edge/.env`,**无硬编码默认值**,缺任一项拒绝所有登录 fail closed):`AUTH_HUB_ISSUER`(公网,浏览器跳转 + `iss` 校验)/ `AUTH_HUB_INTERNAL_BASE`(本机,换 token + JWKS)/ `AUTH_HUB_CLIENT_ID` / `AUTH_HUB_CLIENT_SECRET` / `AUTH_HUB_REDIRECT_URI`(必须跟 auth-hub 登记的逐字符一致,只做精确匹配)/ `FAM_SESSION_SECRET`(会话签名密钥,≥32 字节)
|
||||
> - **会话是无状态签名 cookie**(HS256,2 小时),fam-edge 重启不掉线(旧实现是进程内 token 表,fam-core 一重启全员下线);要强制全员下线就换掉 `FAM_SESSION_SECRET` 再重启
|
||||
> - auth-hub 侧任何审批通过的账号登录后都能访问本系统,**未再加用户名白名单**——自用场景的有意取舍(见项目记忆)
|
||||
|
||||
> Oracle 部署方式:本地 git 提交 push Gitea → tar 管道到 `/opt/fam-edge`(`--strip-components=1` 解临时目录再 cp,避免动 data/venv/gdrive_videos)。**AI-Gateway 是独立 git 仓库**(http://192.168.50.64:3000/ericwyuan/ai-gateway,见 §10.4),同样 tar 管道部署到 `/opt/ai-gateway`,互不影响。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user