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:
165
docs/DEPLOY.md
165
docs/DEPLOY.md
@@ -1,54 +1,58 @@
|
||||
# 部署指南(v3 运动事件驱动架构,2026-08-22 同步)
|
||||
# 部署指南(2026-09-13 全量迁云后同步)
|
||||
|
||||
## 1. NAS 端部署 (FAM-Core + MariaDB;FAM-UI 见 §1.3,部署在云服务器)
|
||||
> **一句话架构**:除了录像本身,全部跑在甲骨文。NAS 只剩 Surveillance Station
|
||||
> 和一个往甲骨文推运动事件的进程(fam-notifier)——NAS 离线不影响网站访问。
|
||||
|
||||
### 1.1 MariaDB
|
||||
## 1. NAS 端部署(只有 fam-notifier)
|
||||
|
||||
2026-09-13 迁云后 NAS 上**不再有** MariaDB、FAM-Core、FAM-UI:镜像层整个删除
|
||||
(数据权威源本来就在甲骨文),接口和前端也都搬走了。旧部署的清理见 §1.2。
|
||||
|
||||
### 1.1 fam-notifier(轮询 Surveillance Station → 推甲骨文)
|
||||
```bash
|
||||
# 通过 Synology 套件中心安装 MariaDB(10.11),unix_socket=/run/mysqld/mysqld10.sock
|
||||
|
||||
# 执行 DDL
|
||||
python3 scripts/init_db.py --host 127.0.0.1 --port 3306 --user root --password <密码>
|
||||
```
|
||||
|
||||
### 1.2 FAM-Core
|
||||
```bash
|
||||
cd /volume1/web/sentinel-home-ai/fam-core
|
||||
cd /volume1/web/sentinel-home-ai/fam-notifier
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
pip install -r requirements.txt # 只有 requests + PyYAML
|
||||
|
||||
# 配置
|
||||
cp config/config.yaml.example config/config.yaml
|
||||
# 编辑 config.yaml:数据库密码、oracle_sync.base_url/token、motion_notifier(DSM 凭据走 .env)
|
||||
# 编辑 config.yaml:camera_ids、oracle_base_url;DSM 凭据与 token 走仓库根 .env
|
||||
# 仓库根 .env 需提供 DSM_ACCOUNT / DSM_PASSWORD / ORACLE_SYNC_TOKEN
|
||||
|
||||
# 仓库根 .env 提供 DSM_ACCOUNT/DSM_PASSWORD/ORACLE_SYNC_TOKEN/AUTH_HUB_*(start_core.sh 会 source)
|
||||
# 启动(DSM 没有 systemd,用 setsid 脱离 SSH 会话;挂了不会自启,需要人工重启)
|
||||
cd /volume1/web/sentinel-home-ai/fam-notifier
|
||||
setsid bash scripts/start_notifier.sh >> logs/start.log 2>&1 &
|
||||
|
||||
# 登录改接 auth-hub 统一登录(2026-08-31),先在 auth-hub 侧注册本站点 client:
|
||||
# ssh 到 auth-hub 所在 Oracle 主机,cd /opt/auth-hub
|
||||
# python -m auth_hub.manage_clients create "FAM-Core" "http://129.146.26.249/api/auth/callback"
|
||||
# 输出的 client_id/client_secret 明文只显示这一次,立即抄进 .env
|
||||
# 仓库根 .env 补齐四项(缺任一项 fam-core 直接拒绝所有登录,fail closed):
|
||||
# AUTH_HUB_ISSUER=http://129.146.26.249:5300
|
||||
# AUTH_HUB_CLIENT_ID=<上一步输出>
|
||||
# AUTH_HUB_CLIENT_SECRET=<上一步输出>
|
||||
# AUTH_HUB_REDIRECT_URI=http://129.146.26.249/api/auth/callback # 必须跟注册时的 redirect_uri 逐字符一致
|
||||
|
||||
# 启动(含 Oracle-Sync + MotionNotifier 轮询)
|
||||
cd /volume1/web/sentinel-home-ai && bash start_core.sh
|
||||
# 确认在跑(进程没有监听端口,只能看进程和日志)
|
||||
ps aux | grep "[f]am_notifier"
|
||||
tail -f logs/fam-notifier.log # 应每 60s 轮询、每 5min 心跳
|
||||
```
|
||||
|
||||
### 1.3 FAM-UI(Vue3 SPA,托管于云服务器 Caddy :80)
|
||||
游标存在 `fam-notifier/data/cursor.json`(迁云前存在 MariaDB)。删掉也不致命:
|
||||
窗口回看会把最近的事件补推一遍,甲骨文按 event_id 幂等落库。
|
||||
|
||||
### 1.2 清理旧部署(迁云一次性动作)
|
||||
```bash
|
||||
# 1) 停掉 NAS 上的 fam-core(它已经不该再跑了;跑着也没用,前端不再连它)
|
||||
ps aux | grep "[f]am-core/venv/bin/gunicorn" # 字符类写法:pkill 会杀掉 SSH 自己
|
||||
kill <上面的 pid>
|
||||
|
||||
# 2) frpc 里的 8000 映射可以删了(/etc/frp/frpc.toml 的 fam-core 段),
|
||||
# 甲骨文侧对应的 iptables DROP 规则也就没有存在意义了
|
||||
# 3) MariaDB 的 sentinel_home_ai 库确认 chat_history 已迁移(§2.4)后再考虑删
|
||||
```
|
||||
|
||||
### 1.3 FAM-UI(Vue3 SPA,构建后托管在甲骨文 Caddy)
|
||||
```bash
|
||||
cd /Users/ericwyuan/Desktop/Work/sentinel-home-ai/fam-ui # 本地开发机
|
||||
npm install
|
||||
npm run build # 产物 fam-ui/dist/
|
||||
# 部署 dist 到云服务器(Caddy 静态目录,/api 经 frp 隧道反代回 NAS):
|
||||
tar czf - fam-ui/dist | ssh -i ~/.ssh/oracle_new ubuntu@129.146.26.249 \
|
||||
'sudo tar xzf - --strip-components=2 -C /var/www/fam-ui && sudo chown -R ubuntu:ubuntu /var/www/fam-ui'
|
||||
# 浏览器访问 http://129.146.26.249/(Caddy 托管,/api/* 反代 NAS fam-core)
|
||||
tar czf - -C fam-ui dist | ssh -i ~/.ssh/oracle_new ubuntu@129.146.26.249 \
|
||||
'sudo tar xzf - --strip-components=1 -C /var/www/fam-ui && sudo chown -R ubuntu:ubuntu /var/www/fam-ui'
|
||||
# 浏览器访问 https://smart-camera.zichuan.xyz/
|
||||
```
|
||||
|
||||
## 2. Oracle 端部署 (FAM-Edge + Ollama + FFmpeg)
|
||||
## 2. Oracle 端部署 (FAM-Edge + FAM-Core + Ollama + FFmpeg)
|
||||
|
||||
### 2.1 系统依赖
|
||||
```bash
|
||||
@@ -74,6 +78,89 @@ sudo systemctl enable fam-edge
|
||||
sudo systemctl restart fam-edge # 部署代码后必须用 systemctl 重启,勿手动 setsid
|
||||
```
|
||||
|
||||
### 2.3 统一登录(2026-09-12 从 NAS 迁入,fam-edge + Caddy + 防火墙三件套)
|
||||
|
||||
登录入口不再依赖 NAS。三处缺一不可:
|
||||
|
||||
```bash
|
||||
# (1) /opt/fam-edge/.env 追加六项(缺任一项 fam-edge 拒绝所有登录,fail closed)
|
||||
# client_secret 用 rotate-secret 现拿,明文只显示一次:
|
||||
# cd /opt/auth-hub && venv/bin/python -m auth_hub.manage_clients rotate-secret <client_id>
|
||||
# 会话密钥自己生成:openssl rand -hex 32
|
||||
export AUTH_HUB_ISSUER=https://auth.zichuan.xyz # 公网:浏览器跳转 + id_token 的 iss 校验
|
||||
export AUTH_HUB_INTERNAL_BASE=http://127.0.0.1:5300 # 本机:换 token + 拉 JWKS,不走公网 TLS
|
||||
export AUTH_HUB_CLIENT_ID=<auth-hub 注册的 client_id>
|
||||
export AUTH_HUB_CLIENT_SECRET=<rotate-secret 输出>
|
||||
export AUTH_HUB_REDIRECT_URI=https://smart-camera.zichuan.xyz/api/auth/callback # 与 auth-hub 登记的逐字符一致
|
||||
export FAM_SESSION_SECRET=<openssl rand -hex 32> # 会话 cookie 签名密钥,换掉即全员下线
|
||||
|
||||
# (2) venv 补依赖(Python 3.8,pip 会自动选到兼容版本)后重启
|
||||
/opt/fam-edge/venv/bin/pip install "PyJWT>=2.8.0" "cryptography>=42.0.0"
|
||||
sudo systemctl restart fam-edge
|
||||
|
||||
# (3) Caddy:登录端点走本机 fam-edge,其余 /api/* 先 forward_auth 再回源 NAS
|
||||
# 改完先 caddy validate 再 reload,见下方 Caddyfile 片段
|
||||
sudo caddy validate --adapter caddyfile --config /etc/caddy/Caddyfile && sudo systemctl reload caddy
|
||||
```
|
||||
|
||||
```caddyfile
|
||||
smart-camera.zichuan.xyz {
|
||||
handle /login { reverse_proxy 127.0.0.1:5000 }
|
||||
handle /api/auth/* { reverse_proxy 127.0.0.1:5000 }
|
||||
handle /api/logout { reverse_proxy 127.0.0.1:5000 }
|
||||
handle /api/* {
|
||||
forward_auth 127.0.0.1:5000 { uri /api/auth/verify } # 删了等于数据接口全裸
|
||||
reverse_proxy 127.0.0.1:8000
|
||||
}
|
||||
handle { root * /var/www/fam-ui; encode gzip; try_files {path} /index.html; file_server }
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
# (4) 防火墙:frps 把 NAS fam-core 的 :8000 暴露在公网,而 fam-core 自身已无鉴权,
|
||||
# 必须只放行本机(Caddy)访问,否则绕过 Caddy 就能读到全部数据
|
||||
sudo iptables -I INPUT 1 -p tcp --dport 8000 ! -i lo -j DROP
|
||||
sudo netfilter-persistent save # 否则重启后规则丢失
|
||||
```
|
||||
|
||||
### 2.4 FAM-Core(UI/对话/成员接口,2026-09-13 从 NAS 迁入)
|
||||
|
||||
```bash
|
||||
# 代码(本地开发机 → 甲骨文)
|
||||
tar czf - --exclude=venv --exclude=__pycache__ --exclude=logs --exclude='config/config.yaml' fam-core | \
|
||||
ssh -i ~/.ssh/oracle_new ubuntu@129.146.26.249 'tar xzf - --strip-components=1 -C /opt/fam-core'
|
||||
|
||||
# 甲骨文上:venv + 配置
|
||||
python3 -m venv /opt/fam-core/venv
|
||||
/opt/fam-core/venv/bin/pip install -r /opt/fam-core/requirements.txt
|
||||
cp /opt/fam-core/config/config.yaml.example /opt/fam-core/config/config.yaml
|
||||
# .env 只需要 ORACLE_SYNC_TOKEN(与 fam-edge 的一致),config_loader 会自动加载
|
||||
grep "^export ORACLE_SYNC_TOKEN=" /opt/fam-edge/.env > /opt/fam-core/.env && chmod 600 /opt/fam-core/.env
|
||||
|
||||
sudo systemctl restart fam-core # 单元见 /etc/systemd/system/fam-core.service
|
||||
curl -s http://127.0.0.1:5401/api/status # {"db":{"ok":true},...}
|
||||
```
|
||||
|
||||
**端口 5401 不是笔误**:5400 被这台机器上的 chat-relay 占了。fam-core 只绑
|
||||
127.0.0.1,外部进不来,唯一客户端是同机 Caddy(先 forward_auth 再反代)。
|
||||
|
||||
**chat_history 迁移(一次性)**:这是 NAS MariaDB 里唯一不是镜像的表。
|
||||
在 NAS 上导出,再从本地导入甲骨文的 SQLite:
|
||||
```bash
|
||||
# NAS 上导出(用 fam-core 旧 venv 里的 PyMySQL)
|
||||
ssh -p 2222 ericwyuan@192.168.50.64 '/volume1/web/sentinel-home-ai/fam-core/venv/bin/python -c "
|
||||
import pymysql, json, yaml
|
||||
cfg = yaml.safe_load(open(\"/volume1/web/sentinel-home-ai/fam-core/config/config.yaml\"))[\"database\"]
|
||||
c = pymysql.connect(host=\"127.0.0.1\", user=cfg[\"user\"], password=cfg[\"password\"], database=cfg[\"database\"], cursorclass=pymysql.cursors.DictCursor)
|
||||
cur = c.cursor(); cur.execute(\"SELECT * FROM chat_history ORDER BY chat_id\")
|
||||
print(json.dumps(cur.fetchall(), ensure_ascii=False, default=str))
|
||||
"' > /tmp/chat_history.json
|
||||
|
||||
# 本地 → 甲骨文导入(幂等:按 chat_id 跳过已存在的)
|
||||
cat /tmp/chat_history.json | ssh -i ~/.ssh/oracle_new ubuntu@129.146.26.249 \
|
||||
'/opt/fam-core/venv/bin/python /opt/fam-core/scripts/import_chat_history.py'
|
||||
```
|
||||
|
||||
## 3. 代码同步(tar 管道,scp 在 NAS 被禁用)
|
||||
|
||||
```bash
|
||||
@@ -91,10 +178,12 @@ tar czf - --exclude=venv --exclude=__pycache__ --exclude=data --exclude=gdrive_v
|
||||
|
||||
| 项目 | 命令 | 预期 |
|
||||
|------|------|------|
|
||||
| NAS MariaDB | `mysql -u root -p -e "SHOW DATABASES"` | 包含 sentinel_home_ai |
|
||||
| NAS FAM-Core | `curl http://localhost:8000/health` | `{"status":"ok"}` |
|
||||
| NAS 运动监测 | `curl http://localhost:8000/api/ss/status` | `poll_enabled: true, running: true` |
|
||||
| 云服务器 FAM-UI | 浏览器访问 `http://129.146.26.249/`(Caddy 托管) | 未登录被 302 到 auth-hub 登录页;登录(审批过的 auth-hub 账号)后跳回展示 Vue3 SPA(时间轴/人物管理/统计) |
|
||||
| 云服务器 FAM-UI | 浏览器访问 `https://smart-camera.zichuan.xyz/` | 未登录时 `/api/*` 返回 401,前端自动跳 `/login` → auth-hub 登录页;登录后跳回展示 Vue3 SPA |
|
||||
| 登录不依赖 NAS | `curl -sI https://smart-camera.zichuan.xyz/login`(此时哪怕 fam-core 是停的) | 302 到 `auth.zichuan.xyz/authorize?...`,**不再是 502** |
|
||||
| 鉴权闸门 | `curl -s -o /dev/null -w '%{http_code}' https://smart-camera.zichuan.xyz/api/ui/videos` | 401(Caddy forward_auth 拦下,没到 NAS) |
|
||||
| NAS fam-notifier | NAS 上 `ps aux \| grep "[f]am_notifier"` | 有进程;`fam-notifier/logs/fam-notifier.log` 每 60s 一条轮询、每 5min 一条心跳 |
|
||||
| 甲骨文 FAM-Core | `curl -s http://127.0.0.1:5401/api/status` | `{"db":{"ok":true,...}}`;`/api/ui/stats` 返回真实计数 |
|
||||
| NAS 离线也能用 | 关掉 NAS 后访问 `https://smart-camera.zichuan.xyz/timeline` | 页面与数据照常(只是不再有新运动事件);**这是本次迁云的验收标准** |
|
||||
| Oracle FAM-Edge | `curl http://localhost:5000/health` | `{"status":"ok","queue_alive":true}` |
|
||||
| Oracle 运动事件 | `sqlite3 /opt/fam-edge/data/oracle.db "SELECT COUNT(*) FROM ss_motion_events"` | >0(NAS 推送) |
|
||||
| Oracle 运动片段 | `ls /opt/fam-edge/motion_clips/` | 存在 motion_*.mp4(素材分割产物) |
|
||||
|
||||
Reference in New Issue
Block a user