Spaces:
Running
Running
sync processor from GitLab 6d86455: 版本 1.8.6 —— systemd 单元入库(僵尸挂载自愈 + 看门狗)
Browse files- processor/app/version.py +6 -1
- processor/scripts/systemd/host-units/README.md +47 -0
- processor/scripts/systemd/host-units/egodata-api.service +18 -0
- processor/scripts/systemd/host-units/egodata-storage.service +22 -0
- processor/scripts/systemd/host-units/egodata-watchdog.service +7 -0
- processor/scripts/systemd/host-units/egodata-watchdog.sh +47 -0
- processor/scripts/systemd/host-units/egodata-watchdog.timer +10 -0
- processor/scripts/systemd/host-units/egodata-worker.service +17 -0
- processor/scripts/systemd/host-units/install.sh +40 -0
processor/app/version.py
CHANGED
|
@@ -33,4 +33,9 @@
|
|
| 33 |
# 1.8.5 顺带删掉 renderGroupedWorkspaceLegacy(187 行死代码,全仓库零调用,
|
| 34 |
# 里面还留着一份同样的 × 按钮)。它不渲染,删它纯粹是防止将来有人
|
| 35 |
# 复活它把 × 带回来 —— 用户可见行为与 1.8.4 完全相同
|
| 36 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|