quyu666 commited on
Commit
f451afc
·
1 Parent(s): 330cf83

sync processor from GitLab 6d86455: 版本 1.8.6 —— systemd 单元入库(僵尸挂载自愈 + 看门狗)

Browse files
processor/app/version.py CHANGED
@@ -33,4 +33,9 @@
33
  # 1.8.5 顺带删掉 renderGroupedWorkspaceLegacy(187 行死代码,全仓库零调用,
34
  # 里面还留着一份同样的 × 按钮)。它不渲染,删它纯粹是防止将来有人
35
  # 复活它把 × 带回来 —— 用户可见行为与 1.8.4 完全相同
36
- __version__ = "1.8.5"
 
 
 
 
 
 
33
  # 1.8.5 顺带删掉 renderGroupedWorkspaceLegacy(187 行死代码,全仓库零调用,
34
  # 里面还留着一份同样的 × 按钮)。它不渲染,删它纯粹是防止将来有人
35
  # 复活它把 × 带回来 —— 用户可见行为与 1.8.4 完全相同
36
+ # 1.8.6 部署加固:systemd 单元入库(scripts/systemd/host-units/,含安装脚本与
37
+ # 说明)。修「换网络/休眠后整套服务静默挂掉」:挂载加僵尸态自愈
38
+ # (启动前 fusermount3 -u -z)、API/worker 改 Requires+After 挂载、
39
+ # Restart 一律 always;另加每分钟看门狗自动重挂/重启。纯运维配置,
40
+ # 无代码行为与用户可见变化
41
+ __version__ = "1.8.6"
processor/scripts/systemd/host-units/README.md ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 生产机 systemd 用户单元(实际运行配置)
2
+
3
+ 本目录是生产机 `192.168.110.58` 上**实际在跑**的那套 systemd 用户单元,入库是为了
4
+ 换机/重装时可直接复用。与上级目录的 `*.service.in`(deploy.py 的模板,另一套命名
5
+ `egodata-backend` / `egodata-workers`)并存,互不影响 —— 当前运行的是本目录这套。
6
+
7
+ ## 包含什么
8
+
9
+ | 文件 | 作用 |
10
+ |---|---|
11
+ | `egodata-storage.service` | sshfs 挂载 NAS 数据(`~/.cache/egodata-data`)。含**僵尸挂载自愈**:启动前先 `fusermount3 -u -z` 清理残留,再重挂 |
12
+ | `egodata-api.service` | uvicorn 后端 + 前端网页(同一进程,0.0.0.0:8000)。`Requires`+`After` 挂载,挂载没就绪不会启动 |
13
+ | `egodata-worker.service` | 异步处理 worker(领工作流任务) |
14
+ | `egodata-watchdog.sh` + `.service` + `.timer` | 每分钟巡检:挂载掉了重挂、API 不健康重启。启动中(activating)跳过、连续 2 次不健康才动手,避开 API 冷启约 20s 的误判 |
15
+ | `install.sh` | 一键安装到 `~/.config/systemd/user/` + `~/.local/bin/` |
16
+
17
+ ## 为什么这样配(踩过的坑)
18
+
19
+ 1. **换网络/休眠后服务全挂**:sshfs 断线 → 挂载单元失败 → API 启动时扫描数据目录撞上
20
+ 死挂载崩溃 → systemd 因"正常退出码"不重启。已用 `Restart=always` + `Requires=` 修正。
21
+ 2. **僵尸挂载**(`Transport endpoint is not connected`):断线后挂载点残留,`mkdir`/`sshfs`
22
+ 全失败,重启重试 73 次也挂不上。已用 `ExecStartPre=fusermount3 -u -z` 自愈。
23
+ 3. **API 冷启约 20s**:看门狗若在这期间判为宕机就会反复重启,故加 `activating` 跳过 +
24
+ 连续 2 次阈值。
25
+
26
+ ## 安装
27
+
28
+ ```bash
29
+ bash install.sh
30
+ ```
31
+
32
+ 依赖:
33
+ - `sshfs`(`processor/.tools/sshfs/usr/bin/sshfs`,单元里写死此路径)
34
+ - NAS 的 SSH 私钥 `~/.ssh/id_rsa`
35
+ - `~/.config/egodata-worker-env`(含 `EGODATA_WORKER_API_KEY`,**不入库**,install.sh 会
36
+ 生成占位,取值见 `processor/.env`)
37
+
38
+ ## 常用操作
39
+
40
+ ```bash
41
+ systemctl --user status egodata-api # 状态
42
+ journalctl --user -u egodata-api -f # 实时日志
43
+ systemctl --user restart egodata-api # 重启后端(前端同一个进程)
44
+ systemctl --user list-timers egodata-watchdog.timer # 看门狗巡检计划
45
+ ```
46
+
47
+ > 注意:前端与后端是**同一个服务**(uvicorn 同时提供 API 和网页模板),没有独立的前端进程。
processor/scripts/systemd/host-units/egodata-api.service ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [Unit]
2
+ Description=EgoData API (uvicorn, port 8000)
3
+ After=network-online.target egodata-storage.service
4
+ # 数据在 NAS 上:挂载没就绪就别启动(否则启动扫描会撞上死挂载直接崩)
5
+ Requires=egodata-storage.service
6
+ Wants=network-online.target
7
+ StartLimitIntervalSec=0
8
+
9
+ [Service]
10
+ Type=simple
11
+ WorkingDirectory=/home/stouching/Desktop/Date-V/processor
12
+ ExecStart=/home/stouching/Desktop/Date-V/processor/.venv-linux/bin/python -m uvicorn app.main:app --host 0.0.0.0 --port 8000
13
+ Restart=always
14
+ RestartSec=5
15
+ Environment=PYTHONUNBUFFERED=1
16
+
17
+ [Install]
18
+ WantedBy=default.target
processor/scripts/systemd/host-units/egodata-storage.service ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [Unit]
2
+ Description=EgoData remote data storage mount
3
+ After=network-online.target
4
+ Wants=network-online.target
5
+ Before=egodata-backend.service egodata-api.service egodata-worker.service
6
+ # 挂载是整条链路的地基:网络/换网/休眠导致断线时不要放弃重试,一直试到挂上为止
7
+ StartLimitIntervalSec=0
8
+
9
+ [Service]
10
+ Type=forking
11
+ # 断线后挂载点可能残留成"僵尸挂载"(Transport endpoint is not connected):
12
+ # 此时 mkdir/stat 都会失败,重试再多次也挂不上。先强制(惰性)卸载清掉残留,
13
+ # 失败用 "-" 前缀忽略(本来就没挂载时 fusermount 会返回非零)。
14
+ ExecStartPre=-/usr/bin/fusermount3 -u -z /home/stouching/.cache/egodata-data
15
+ ExecStartPre=/usr/bin/mkdir -p /home/stouching/.cache/egodata-data
16
+ ExecStart="/home/stouching/Desktop/Date-V/processor/.tools/sshfs/usr/bin/sshfs" "Stouch@192.168.110.41:/vol1/docker/odoo-dev/Data Acquisition/data" /home/stouching/.cache/egodata-data -o IdentityFile=/home/stouching/.ssh/id_rsa -o StrictHostKeyChecking=no -o reconnect -o ServerAliveInterval=15 -o ServerAliveCountMax=3 -o idmap=user -o cache=yes -o cache_timeout=10
17
+ ExecStop=/usr/bin/fusermount3 -u -z /home/stouching/.cache/egodata-data
18
+ Restart=always
19
+ RestartSec=10
20
+
21
+ [Install]
22
+ WantedBy=default.target
processor/scripts/systemd/host-units/egodata-watchdog.service ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ [Unit]
2
+ Description=EgoData watchdog (storage mount + API health)
3
+
4
+ [Service]
5
+ Type=oneshot
6
+ ExecStart=/home/stouching/.local/bin/egodata-watchdog.sh
7
+ Environment=XDG_RUNTIME_DIR=/run/user/1000
processor/scripts/systemd/host-units/egodata-watchdog.sh ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ # EgoData 看门狗:挂载掉了重挂,API 掉了重启。
3
+ # 由 egodata-watchdog.timer 每分钟调用一次;只做本地检查(不读 NAS 数据),
4
+ # 开销可忽略。systemctl --user 在用户单元里可用,输出进 journal。
5
+ set -u
6
+
7
+ MOUNT=/home/stouching/.cache/egodata-data
8
+ API_URL=http://127.0.0.1:8000/health
9
+ STATE_FILE=/run/user/1000/egodata-watchdog.fails
10
+ FAIL_THRESHOLD=2 # 连续 2 次不健康才动手,避开"启动中"的误判
11
+
12
+ # 1) 挂载在不在(mountpoint 查内核挂载表,零网络开销)。
13
+ # 断线可能留下"僵尸挂载"(Transport endpoint is not connected):
14
+ # mountpoint 判为未挂载,但残留条目会让 mkdir/sshfs 全部失败 ——
15
+ # 先强制卸载清掉,再重启挂载单元(单元里也有同样的 ExecStartPre 兜底)。
16
+ if ! mountpoint -q "$MOUNT"; then
17
+ echo "storage 未挂载,清理残留并重启 egodata-storage"
18
+ fusermount3 -u -z "$MOUNT" 2>/dev/null
19
+ systemctl --user reset-failed egodata-storage.service 2>/dev/null
20
+ systemctl --user restart egodata-storage.service
21
+ sleep 5
22
+ fi
23
+
24
+ # 2) API 健不健康。启动中(activating)直接放行 —— API 冷启要约 20s,
25
+ # 正好落在两次巡检之间会被误判成宕机并反复重启。
26
+ state=$(systemctl --user is-active egodata-api.service 2>/dev/null)
27
+ if [ "$state" = "activating" ] || [ "$state" = "deactivating" ]; then
28
+ echo "API 正在启动/停止中($state),跳过本次巡检"
29
+ exit 0
30
+ fi
31
+
32
+ code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 10 "$API_URL" 2>/dev/null)
33
+ if [ "$code" = "200" ]; then
34
+ rm -f "$STATE_FILE"
35
+ exit 0
36
+ fi
37
+
38
+ fails=$(( $(cat "$STATE_FILE" 2>/dev/null || echo 0) + 1 ))
39
+ echo "$fails" > "$STATE_FILE"
40
+ if [ "$fails" -lt "$FAIL_THRESHOLD" ]; then
41
+ echo "API health=$code(连续第 $fails 次),再观察一轮"
42
+ exit 0
43
+ fi
44
+
45
+ echo "API health=$code(连续 $fails 次),重启 egodata-api"
46
+ rm -f "$STATE_FILE"
47
+ systemctl --user restart egodata-api.service
processor/scripts/systemd/host-units/egodata-watchdog.timer ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ [Unit]
2
+ Description=Run EgoData watchdog every minute
3
+
4
+ [Timer]
5
+ OnBootSec=3min
6
+ OnUnitActiveSec=1min
7
+ AccuracySec=10s
8
+
9
+ [Install]
10
+ WantedBy=timers.target
processor/scripts/systemd/host-units/egodata-worker.service ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [Unit]
2
+ Description=EgoData processing worker
3
+ After=network-online.target egodata-api.service egodata-storage.service
4
+ Requires=egodata-storage.service
5
+ StartLimitIntervalSec=0
6
+
7
+ [Service]
8
+ Type=simple
9
+ WorkingDirectory=/home/stouching/Desktop/Date-V/processor
10
+ EnvironmentFile=%h/.config/egodata-worker-env
11
+ ExecStart=/home/stouching/Desktop/Date-V/processor/.venv-linux/bin/python -m worker
12
+ Restart=always
13
+ RestartSec=5
14
+ Environment=PYTHONUNBUFFERED=1
15
+
16
+ [Install]
17
+ WantedBy=default.target
processor/scripts/systemd/host-units/install.sh ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ # 安装本目录下的 systemd 用户单元(生产机实际配置)。
3
+ #
4
+ # 覆盖内容:NAS 挂载(egodata-storage)、API(egodata-api)、worker(egodata-worker)、
5
+ # 看门狗(每分钟巡检挂载+API 并自动恢复)。
6
+ #
7
+ # 用法:bash install.sh
8
+ # 卸载:systemctl --user disable --now egodata-storage egodata-api egodata-worker egodata-watchdog.timer
9
+ set -euo pipefail
10
+
11
+ HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
12
+ UNIT_DIR="${HOME}/.config/systemd/user"
13
+ BIN_DIR="${HOME}/.local/bin"
14
+
15
+ mkdir -p "${UNIT_DIR}" "${BIN_DIR}"
16
+
17
+ install -m 644 "${HERE}"/egodata-storage.service \
18
+ "${HERE}"/egodata-api.service \
19
+ "${HERE}"/egodata-worker.service \
20
+ "${HERE}"/egodata-watchdog.service \
21
+ "${HERE}"/egodata-watchdog.timer "${UNIT_DIR}/"
22
+ install -m 755 "${HERE}/egodata-watchdog.sh" "${BIN_DIR}/egodata-watchdog.sh"
23
+
24
+ # worker 的 API key 文件(含密钥,故意不入库):不存在时建占位,需人工填。
25
+ ENV_FILE="${HOME}/.config/egodata-worker-env"
26
+ if [ ! -f "${ENV_FILE}" ]; then
27
+ umask 077
28
+ echo "EGODATA_WORKER_API_KEY=change-me" > "${ENV_FILE}"
29
+ chmod 600 "${ENV_FILE}"
30
+ echo "⚠️ 已生成 ${ENV_FILE} 占位,请填入真实 WORKER_API_KEY(取值见 processor/.env)"
31
+ fi
32
+
33
+ systemctl --user daemon-reload
34
+ systemctl --user enable --now egodata-storage.service \
35
+ egodata-api.service \
36
+ egodata-worker.service \
37
+ egodata-watchdog.timer
38
+
39
+ echo "✅ 安装完成。状态:"
40
+ systemctl --user is-active egodata-storage egodata-api egodata-worker egodata-watchdog.timer