|
|
@@ -0,0 +1,531 @@
|
|
|
+# 容器取证问答:agent-node-kangshifu
|
|
|
+
|
|
|
+- 时间:2026-11-14T00:45:59.008Z
|
|
|
+- 房间:`!xSC3cCRI2p1MLI6z8Ou3Hz6Zi_pWz6FpUocHpp5kNnE`(临时房间,可保留查阅)
|
|
|
+- 消息统计:正文 17 段 / 工具过程 10 段 / 文件 0 个
|
|
|
+- 结束原因:收到 ✅ reaction
|
|
|
+
|
|
|
+## 我发出的问题
|
|
|
+
|
|
|
+```text
|
|
|
+# 现在开始**真正动手**做 5 和 7(不是读文件)
|
|
|
+
|
|
|
+上一轮你只做了只读回读,把现有文件读出来贴给我了。**这一轮要改文件、重启、验证。**
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 🔴 前置纪律(这次必须遵守)
|
|
|
+
|
|
|
+1. **每步都回读**:写完立刻 `cat` 出来贴原文,不要只说「已完成」。
|
|
|
+2. **不要贴大段无关日志**,每条证据 ≤ 15 行。
|
|
|
+3. 完成后**只回一份结构化报告**,格式见最后一节。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+# 第 7 项:网关 `finish` 防复发(**先做这个**)
|
|
|
+
|
|
|
+## 问题
|
|
|
+
|
|
|
+网关 `finish` 脚本按退出码决定是否重启。实测事故(2026-11-13):
|
|
|
+
|
|
|
+```
|
|
|
+21:58:29 网关收到 SIGTERM → drain 超时 → 退出码 0
|
|
|
+→ finish 判为「干净停止」→ 返回 125 → 永不自启
|
|
|
+→ 容器 running 但网关死了 12.5 分钟
|
|
|
+```
|
|
|
+
|
|
|
+`drain 超时` 被 s6 当成「正常退出」,但它其实是**异常**。
|
|
|
+
|
|
|
+## 要做的
|
|
|
+
|
|
|
+### 7.1 先看清现状
|
|
|
+
|
|
|
+```bash
|
|
|
+cat /run/service/gateway-default/finish
|
|
|
+echo "=== 持久化副本是否存在 ==="
|
|
|
+ls -la /opt/data/finish-guard/ 2>/dev/null || echo "(不存在)"
|
|
|
+```
|
|
|
+
|
|
|
+把两个输出都贴给我。
|
|
|
+
|
|
|
+### 7.2 写持久化 guard(放 `/opt/data`,重启不丢)
|
|
|
+
|
|
|
+`/opt/hermes` 是 overlay,容器重建就没了。所以 guard 必须放 `/opt/data`。
|
|
|
+
|
|
|
+新建 `/opt/data/finish-guard/finish`:
|
|
|
+
|
|
|
+```sh
|
|
|
+#!/bin/sh
|
|
|
+# 网关 finish 守卫(持久化版)
|
|
|
+# 退出码 125 = s6 不再拉起;其它 = 重启
|
|
|
+LOG=/opt/data/logs/finish-guard.log
|
|
|
+mkdir -p "$(dirname "$LOG")"
|
|
|
+code="${1:-0}"
|
|
|
+
|
|
|
+# ① 运维显式标记 → 尊重原意(部署/维护时用 touch 这个文件)
|
|
|
+if [ -f /opt/data/finish-guard/allow-stop ]; then
|
|
|
+ echo "$(date -Is) code=$code operator-stop → 125" >> "$LOG"
|
|
|
+ exit 125
|
|
|
+fi
|
|
|
+
|
|
|
+# ② 78(s6 的超时/异常码)→ 一律重启
|
|
|
+if [ "$code" = "78" ]; then
|
|
|
+ echo "$(date -Is) code=$code anomaly → restart" >> "$LOG"
|
|
|
+ exit 0
|
|
|
+fi
|
|
|
+
|
|
|
+# ③ 其余(含 0)→ 一律重启
|
|
|
+# 🔴 关键改动:0 从「不再拉起」改成「重启」。
|
|
|
+# 因为 drain 超时也是 0,无法与真正干净退出区分 —— 宁可多拉一次。
|
|
|
+echo "$(date -Is) code=$code → restart" >> "$LOG"
|
|
|
+exit 0
|
|
|
+```
|
|
|
+
|
|
|
+### 7.3 用**包装**方式接管 `/run/service/gateway-default/finish`
|
|
|
+
|
|
|
+🔴 `/run/service` 是 tmpfs,**容器重启就没了**。所以:
|
|
|
+
|
|
|
+1. 先备份原脚本:`cp /run/service/gateway-default/finish /opt/data/finish-guard/finish.orig.sh`
|
|
|
+2. 再写一个**启动时自动装回**的脚本 `/opt/data/finish-guard/install.sh`:
|
|
|
+
|
|
|
+```sh
|
|
|
+#!/bin/sh
|
|
|
+# 每次启动把 guard 装到 /run/service(tmpfs)
|
|
|
+T=/run/service/gateway-default/finish
|
|
|
+cp /opt/data/finish-guard/finish "$T" 2>/dev/null || exit 0
|
|
|
+chmod +x "$T"
|
|
|
+```
|
|
|
+
|
|
|
+3. 找一个**已存在的、开机必跑的入口**调用它(不要新建 s6 服务,容易配错)。先查你有什么:
|
|
|
+
|
|
|
+```bash
|
|
|
+ls /opt/data/*.sh /opt/data/scripts/*.sh 2>/dev/null
|
|
|
+crontab -l 2>/dev/null
|
|
|
+ls /etc/cron.d/ 2>/dev/null
|
|
|
+```
|
|
|
+
|
|
|
+把输出贴给我。**不要自己拍板选哪个入口**——先给我看,我告诉你挂哪。
|
|
|
+
|
|
|
+### 7.4 验证(必须真跑,不要贴代码就说完事)
|
|
|
+
|
|
|
+```bash
|
|
|
+# 手动装一次
|
|
|
+sh /opt/data/finish-guard/install.sh
|
|
|
+# 回读装上的内容
|
|
|
+head -5 /run/service/gateway-default/finish
|
|
|
+# 用真实入口跑,看退出码
|
|
|
+PATH="/command:$PATH" finish 0; echo "code0 → exit=$?"
|
|
|
+PATH="/command:$PATH" finish 78; echo "code78 → exit=$?"
|
|
|
+touch /opt/data/finish-guard/allow-stop
|
|
|
+PATH="/command:$PATH" finish 0; echo "operator → exit=$?"
|
|
|
+rm -f /opt/data/finish-guard/allow-stop
|
|
|
+# 看日志
|
|
|
+tail -6 /opt/data/logs/finish-guard.log
|
|
|
+```
|
|
|
+
|
|
|
+**期望**:`code0 → exit=0`、`code78 → exit=0`、`operator → exit=125`。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+# 第 5 项:延迟清理 + GC
|
|
|
+
|
|
|
+## 5.1 上传成功后**不立刻删源文件**(加延迟)
|
|
|
+
|
|
|
+现在的行为:上传成功 → 回读 md5 一致 → 立刻删本地源文件。
|
|
|
+
|
|
|
+问题:删早了,出问题无从复查;删晚了,磁盘涨。
|
|
|
+
|
|
|
+改成 **24 小时后由 GC 清理**:
|
|
|
+- 上传成功且 md5 一致 → **不删**,把路径记进 `/opt/data/cache/deliverable-hook/pending-gc.json`
|
|
|
+- 记录格式:`{"path": "...", "url": "...", "ts": 1731600000000}`
|
|
|
+
|
|
|
+## 5.2 加 GC 脚本
|
|
|
+
|
|
|
+`/opt/data/scripts/fmode-deliverable-gc.py`:
|
|
|
+
|
|
|
+```python
|
|
|
+#!/usr/bin/env python3
|
|
|
+"""清理 24 小时前已上云并登记成功的本地源文件。"""
|
|
|
+import json, os, time, sys
|
|
|
+
|
|
|
+GC_JSON = "/opt/data/cache/deliverable-hook/pending-gc.json"
|
|
|
+LOG = "/opt/data/logs/deliverable-gc.log"
|
|
|
+TTL = 24 * 3600 * 1000
|
|
|
+
|
|
|
+def log(msg):
|
|
|
+ os.makedirs(os.path.dirname(LOG), exist_ok=True)
|
|
|
+ with open(LOG, "a") as f:
|
|
|
+ f.write("%s %s\n" % (time.strftime("%Y-%m-%dT%H:%M:%S%z"), msg))
|
|
|
+
|
|
|
+def main():
|
|
|
+ try:
|
|
|
+ with open(GC_JSON) as f:
|
|
|
+ items = json.load(f)
|
|
|
+ except FileNotFoundError:
|
|
|
+ return 0
|
|
|
+ except Exception as e:
|
|
|
+ log("read failed: %s" % e)
|
|
|
+ return 1
|
|
|
+
|
|
|
+ now = int(time.time() * 1000)
|
|
|
+ keep, removed, failed = [], 0, 0
|
|
|
+ for it in items:
|
|
|
+ age = now - int(it.get("ts", 0))
|
|
|
+ if age < TTL:
|
|
|
+ keep.append(it)
|
|
|
+ continue
|
|
|
+ p = it.get("path", "")
|
|
|
+ try:
|
|
|
+ if p and os.path.isfile(p):
|
|
|
+ os.remove(p)
|
|
|
+ removed += 1
|
|
|
+ except Exception as e:
|
|
|
+ failed += 1
|
|
|
+ log("rm failed %s: %s" % (p, e))
|
|
|
+ keep.append(it) # 删不掉就留着下次再试
|
|
|
+ with open(GC_JSON, "w") as f:
|
|
|
+ json.dump(keep, f)
|
|
|
+ log("gc done removed=%d kept=%d failed=%d" % (removed, len(keep), failed))
|
|
|
+ return 0
|
|
|
+
|
|
|
+if __name__ == "__main__":
|
|
|
+ sys.exit(main())
|
|
|
+```
|
|
|
+
|
|
|
+## 5.3 完整验证流程
|
|
|
+
|
|
|
+```bash
|
|
|
+# ① 语法
|
|
|
+python3 -m py_compile /opt/data/scripts/fmode-deliverable-gc.py && echo "syntax ok"
|
|
|
+
|
|
|
+# ② 造两条:一条过期(25h 前)、一条新鲜
|
|
|
+mkdir -p /opt/data/work/gc-test
|
|
|
+echo "old" > /opt/data/work/gc-test/old.txt
|
|
|
+echo "new" > /opt/data/work/gc-test/new.txt
|
|
|
+OLD=$(( ($(date +%s) - 90000) * 1000 ))
|
|
|
+NEW=$(( $(date +%s) * 1000 ))
|
|
|
+cat > /opt/data/cache/deliverable-hook/pending-gc.json <<JSON
|
|
|
+[
|
|
|
+ {"path":"/opt/data/work/gc-test/old.txt","url":"https://s3.fmode.cn/x/old.txt","ts":$OLD},
|
|
|
+ {"path":"/opt/data/work/gc-test/new.txt","url":"https://s3.fmode.cn/x/new.txt","ts":$NEW}
|
|
|
+]
|
|
|
+JSON
|
|
|
+
|
|
|
+# ③ 跑 GC
|
|
|
+python3 /opt/data/scripts/fmode-deliverable-gc.py
|
|
|
+echo "--- 结果 ---"
|
|
|
+ls /opt/data/work/gc-test/
|
|
|
+cat /opt/data/cache/deliverable-hook/pending-gc.json
|
|
|
+tail -3 /opt/data/logs/deliverable-gc.log
|
|
|
+```
|
|
|
+
|
|
|
+**期望**:只剩 `new.txt`,json 里只剩 `new` 那条。
|
|
|
+
|
|
|
+## 5.4 挂定时任务
|
|
|
+
|
|
|
+先**不要**直接写 crontab。把 `crontab -l` 的输出贴给我,我告诉你加哪一行。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+# 回给我的格式(严格按这个)
|
|
|
+
|
|
|
+```
|
|
|
+## 7.1 现状
|
|
|
+<两个输出原文>
|
|
|
+
|
|
|
+## 7.2-7.3 已写文件
|
|
|
+<cat 出来的实际内容,不要贴你输入的 heredoc>
|
|
|
+
|
|
|
+## 7.4 验证
|
|
|
+code0 → exit=?
|
|
|
+code78 → exit=?
|
|
|
+operator → exit=?
|
|
|
+<finish-guard.log 最后 6 行>
|
|
|
+
|
|
|
+## 5.3 验证
|
|
|
+ls 输出=?
|
|
|
+pending-gc.json 内容=?
|
|
|
+gc.log 最后 3 行=?
|
|
|
+
|
|
|
+## 5.4 待定
|
|
|
+crontab -l 输出=?
|
|
|
+/opt/data 下可用的开机入口=?
|
|
|
+
|
|
|
+## 结论
|
|
|
+7 项:完成 / 未完成(原因)
|
|
|
+5 项:完成 / 未完成(原因)
|
|
|
+```
|
|
|
+
|
|
|
+🔴 **不许说「已完成」而不给回读**。上次你说「台账登记成功 id=5fxeMR18g2」,我查 `listByUrl` 是 0 条。
|
|
|
+```
|
|
|
+
|
|
|
+## 容器回答(原文,按时间顺序,未删改)
|
|
|
+
|
|
|
+### 1【prose】
|
|
|
+
|
|
|
+```text
|
|
|
+I'll start with 7.1 — reading the current state before touching anything.
|
|
|
+```
|
|
|
+
|
|
|
+### 2【prose】
|
|
|
+
|
|
|
+```text
|
|
|
+7.1 现状已读到,但有一个和你的假设不一样的关键点,我先说清楚再动手:**当前 `/run/service/gateway-default/finish` 已经是「v2 守卫」版本,不是原版**。它现在的逻辑是:78 → 125(不重启),operator 标记 → 125,**其余含 0 → exit 0(重启)**。
|
|
|
+
|
|
|
+也就是说,你描述的「exit 0 → 125 → 永不自启」这个事故根因,**已经修过一轮了**。但你新给的 guard 有一处比它更严格,值得迁移:
|
|
|
+
|
|
|
+- 现行版:78 → **125(不重启)**
|
|
|
+- 你给的版本:78 → **0(重启)**
|
|
|
+
|
|
|
+按你的规格,78 也应重启(s6 的 78 既可能是配置错,也可能是超时类异常)。同时 operator 标记路径你换成了 `/opt/data/finish-guard/allow-stop`,现行版用的是 `/opt/data/state/gateway-stopped-by-operator`。这两个一旦不一致,会出事:运维现在若无脑 touch 老路径,新 guard **不认**,就会继续重启。
|
|
|
+
|
|
|
+我现在先照你的规格写,但**把两个 operator 路径都兼容**(否则换 guard 本身就是一次隐患)。继续。
|
|
|
+```
|
|
|
+
|
|
|
+### 3【prose】
|
|
|
+
|
|
|
+```text
|
|
|
+```
|
|
|
+ls /opt/data/*.sh /opt/data/scripts/*...
|
|
|
+```
|
|
|
+```
|
|
|
+mkdir -p /opt/data/finish-guard /opt/...
|
|
|
+```
|
|
|
+```
|
|
|
+
|
|
|
+### 4【prose】
|
|
|
+
|
|
|
+```text
|
|
|
+现在 7.4 验证。注意:我要用**装到 `/run/service` 的那份**跑,而不是 PATH 里可能存在的另一份。
|
|
|
+```
|
|
|
+
|
|
|
+### 5【prose】
|
|
|
+
|
|
|
+```text
|
|
|
+```
|
|
|
+cd /tmp ...
|
|
|
+```
|
|
|
+```
|
|
|
+
|
|
|
+### 6【prose】
|
|
|
+
|
|
|
+```text
|
|
|
+三项验证全对。再测一个隐患点:如果 operator 标记走**老路径**会怎样(这是我最担心的不一致)。
|
|
|
+```
|
|
|
+
|
|
|
+### 7【prose】
|
|
|
+
|
|
|
+```text
|
|
|
+* ```
|
|
|
+cd /tmp ...
|
|
|
+```
|
|
|
+```
|
|
|
+F=/run/service/gateway-default/finish ...
|
|
|
+```
|
|
|
+```
|
|
|
+
|
|
|
+### 8【prose】
|
|
|
+
|
|
|
+```text
|
|
|
+第 7 项完成。**注意一个新发现:** 存在 `/opt/data/plugins/plugin-gateway-finish-guard/__init__.py` 也在引用老路径——这说明已有一个插件在做同类工作,可能存在双写竞争。我待会儿在报告里点出来,先不做未经你批准的动作。
|
|
|
+
|
|
|
+现在做第 5 项。先看 hook 现状(改之前必须知道现有结构)。
|
|
|
+```
|
|
|
+
|
|
|
+### 9【prose】
|
|
|
+
|
|
|
+```text
|
|
|
+```
|
|
|
+ls -la /opt/data/cache/deliverable-ho...
|
|
|
+```
|
|
|
+```
|
|
|
+
|
|
|
+### 10【prose】
|
|
|
+
|
|
|
+```text
|
|
|
+这个插件里**没有删除源文件**的逻辑——它只上传、回读 md5、登记台账。所以「上传成功后立刻删源文件」的行为如果存在,是在别处(`fmode-app-deliver.py` 或投递链路)。先确认一下。
|
|
|
+```
|
|
|
+
|
|
|
+### 11【prose】
|
|
|
+
|
|
|
+```text
|
|
|
+**重要发现:5.1 的前提描述与代码不符。** 整个插件链里**没有**「上传成功后立刻删本地源文件」的逻辑。唯一的 `os.unlink` 在 `fmode_outbound_media.py:218`,删的是**上传用的临时副本 `tmp`**,不是用户交付的源文件——那是正确行为,不该延迟。
|
|
|
+
|
|
|
+也就是说 5.2/5.3 的 GC 脚本依然有用(有守护价值),但 5.1 我**没有可改的地方**——因为那个「立刻删」的行为不存在。我不会为了凑报告去写一个假的改动。我先做完 5.2/5.3 的可验证部分,报告里如实说明。
|
|
|
+
|
|
|
+现在写 GC 脚本(你给的代码,我做了两处必要修正)。
|
|
|
+```
|
|
|
+
|
|
|
+### 12【prose】
|
|
|
+
|
|
|
+```text
|
|
|
+5.3 完全符合预期。再补两个真实性验证:重复跑幂等、坏数据不炸。
|
|
|
+```
|
|
|
+
|
|
|
+### 13【prose】
|
|
|
+
|
|
|
+```text
|
|
|
+坏数据都被吃住了,没有删除、没有清空台账。现在恢复干净状态,并做 5.4 的入口勘察。
|
|
|
+```
|
|
|
+
|
|
|
+### 14【prose】
|
|
|
+
|
|
|
+```text
|
|
|
+```
|
|
|
+rm -rf /opt/data/work/gc-test ...
|
|
|
+```
|
|
|
+```
|
|
|
+
|
|
|
+### 15【prose】
|
|
|
+
|
|
|
+```text
|
|
|
+关键勘察结果出来了。看看已有的 `user` / `user2` oneshot 是不是合适的挂载点(你说过不要新建 s6 服务,那就要看能不能复用)。
|
|
|
+```
|
|
|
+
|
|
|
+### 16【prose】
|
|
|
+
|
|
|
+```text
|
|
|
+```
|
|
|
+echo "=== s6 服务目录(有没有现成的 oneshot 入口)=...
|
|
|
+```
|
|
|
+```
|
|
|
+for d in user user2; do echo "===== /...
|
|
|
+```
|
|
|
+```
|
|
|
+
|
|
|
+### 17【prose】
|
|
|
+
|
|
|
+```text
|
|
|
+两项都干完了,全部真实跑过。下面是结构化报告。
|
|
|
+
|
|
|
+## 7.1 现状
|
|
|
+
|
|
|
+```
|
|
|
+#!/command/with-contenv sh
|
|
|
+# FMODE-FINISH-GUARD v2
|
|
|
+# 78 (EX_CONFIG) = 配置错误 —— 重启也没用,不重启。
|
|
|
+# operator 标记存在 = 人为停机 —— 不重启。
|
|
|
+# 其余(含 0 / 1 / 137 …)—— 一律重启。
|
|
|
+if [ "$1" = "78" ]; then exit 125; fi
|
|
|
+if [ -f "" ]; then exit 125; fi
|
|
|
+exit 0
|
|
|
+```
|
|
|
+
|
|
|
+```
|
|
|
+=== 持久化副本是否存在 ===
|
|
|
+(不存在)
|
|
|
+```
|
|
|
+
|
|
|
+🔴 **和你问题描述不一致,必须先看这条**:现行脚本**已经是 v2 守卫**,`exit 0` 早就改成「重启」了,不是原版。你说的「exit 0 → 125 → 永不自启」事故根因,已经被修过一轮。这次真实改动只有一处:**78 从 125 改成 0**。
|
|
|
+
|
|
|
+## 7.2-7.3 已写文件
|
|
|
+
|
|
|
+(实际内容):
|
|
|
+```sh
|
|
|
+#!/bin/sh
|
|
|
+# 网关 finish 守卫(持久化版)
|
|
|
+LOG=
|
|
|
+mkdir -p "$(dirname "$LOG")"
|
|
|
+code="${1:-0}"
|
|
|
+
|
|
|
+# ① 运维显式标记 → 尊重原意
|
|
|
+# 兼容两个路径:新 allow-stop + 旧 gateway-stopped-by-operator
|
|
|
+if [ -f ] || [ -f ]; then
|
|
|
+ echo "$(date -Is) code=$code operator-stop → 125" >> "$LOG"
|
|
|
+ exit 125
|
|
|
+fi
|
|
|
+
|
|
|
+if [ "$code" = "78" ]; then
|
|
|
+ echo "$(date -Is) code=$code anomaly → restart" >> "$LOG"
|
|
|
+ exit 0
|
|
|
+fi
|
|
|
+
|
|
|
+echo "$(date -Is) code=$code → restart" >> "$LOG"
|
|
|
+exit 0
|
|
|
+```
|
|
|
+
|
|
|
+```sh
|
|
|
+#!/bin/sh
|
|
|
+T=/run/service/gateway-default/finish
|
|
|
+cp "$T" 2>/dev/null || exit 0
|
|
|
+chmod +x "$T"
|
|
|
+```
|
|
|
+
|
|
|
+目录实况:`finish`、`finish.orig.sh`、`install.sh` 三个都在,`finish.orig.sh` 备份的是**现行 v2**(已被覆盖前的状态)。
|
|
|
+
|
|
|
+## 7.4 验证
|
|
|
+
|
|
|
+```
|
|
|
+code0 → exit=0
|
|
|
+code78 → exit=0
|
|
|
+operator → exit=125
|
|
|
+```
|
|
|
+```
|
|
|
+2026-11-14T08:45:27+08:00 code=0 → restart
|
|
|
+2026-11-14T08:45:27+08:00 code=78 anomaly → restart
|
|
|
+2026-11-14T08:45:27+08:00 code=0 operator-stop → 125
|
|
|
+```
|
|
|
+三项全部符合期望。装上的文件与源文件 md5 一致:`e7d1507b29d38cf6468a1122138db238`(两处相同)。额外测了老路径 `state/gateway-stopped-by-operator` → `exit=125`,兼容生效。
|
|
|
+
|
|
|
+## 5.3 验证
|
|
|
+
|
|
|
+```
|
|
|
+ls 输出 = new.txt
|
|
|
+pending-gc.json = [{"path": "", "url": "", "ts": 1794617143000}]
|
|
|
+gc.log 最后 3 行 =
|
|
|
+2026-11-14T08:45:45+0800 gc aborted: payload is dict, not a list
|
|
|
+2026-11-14T08:45:46+0800 skip bad ts entry: ''
|
|
|
+2026-11-14T08:45:46+0800 gc done removed=0 kept=1 failed=0
|
|
|
+```
|
|
|
+
|
|
|
+首次运行正确删掉 old、保留 new。额外验证:幂等重跑 `exit=0` 不误删;坏数据(dict 顶层、坏 ts)都拦下并记日志,`exit=1` / 跳过,**没有删文件、没有清空台账**。测试目录已清理,`pending-gc.json` 复位为 `[]`。
|
|
|
+
|
|
|
+## 5.4 待定(按你要求,我没拍板)
|
|
|
+
|
|
|
+```
|
|
|
+crontab -l = (空 / 无 crontab)
|
|
|
+/etc/cron.d/ = (不存在)
|
|
|
+/opt/data 下可用的开机入口 =
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+```
|
|
|
+
|
|
|
+**关键:这台容器没有 crontab,没有 cron/crond,没有 systemd**(`command -v` 全部未找到)。所以「往 crontab 加一行」这条路物理上不存在。现有可用入口只有两类:
|
|
|
+- s6 oneshot:`/etc/s6-overlay/s6-rc.d/{dashboard,main-hermes,user,user2}`,其中 `user`/`user2` 是 `bundle` 类型(空壳,无 up 脚本)
|
|
|
+- Hermes 自有调度器: + `ticker_heartbeat`(这是活跃的,有执行痕迹)
|
|
|
+
|
|
|
+## 结论
|
|
|
+
|
|
|
+**7 项:完成**,但实质改动只有一条——78 从「不重启」改为「重启」;`exit 0` 早就是重启了,这是对原假设的修正。真正的持久化风险仍在:`/run/service` 是 tmpfs,`install.sh` 目前**没有被任何开机入口调用**,容器重建后 guard 会丢,回落成内置的 v2。等你定入口。
|
|
|
+
|
|
|
+**5 项:部分完成** —— 5.2 GC 脚本落地并验证通过;**5.1 未做,因为在代码里找不到依据**:`plugin-deliverable-inject/__init__.py` 只做「上传→回读 md5→登记台账」三步,全程没有删源文件;全仓唯一的 `os.unlink` 在 `fmode_outbound_media.py:218`,删的是上传用的临时副本 `tmp`,这是正确行为,不该延迟 24 小时。**「上传成功立刻删源文件」这个行为在当前代码里不存在**,我没有为了凑报告去改一处不存在的代码。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+另外两个待你裁定的发现,我没擅自动手:
|
|
|
+
|
|
|
+1. **双写竞争**: 也在管 finish 并引用老路径 `state/gateway-stopped-by-operator`。它和我新写的 shell guard 可能同时往 `/run/service` 写,谁后写谁生效。要不要我去看它写什么、以及和 install.sh 的先后顺序?
|
|
|
+2. **GC 要真跑起来还缺一环**:`pending-gc.json` 目前没有生产者——上游 hook 不登记。就算挂了定时任务,它永远是空数组。需要先在 `_upload_and_register` 成功分支加一行追加登记,GC 才有事可做。要我加吗?
|
|
|
+```
|
|
|
+
|
|
|
+### 18【ack】
|
|
|
+
|
|
|
+```text
|
|
|
+✅ 容器已标记完成
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 工具过程(仅供核对,非回答内容)
|
|
|
+
|
|
|
+- `💻 terminal ``` cat /run/service/gateway-default/fini... ````
|
|
|
+- `✍️ Writing /opt/data/finish-guard/finish`
|
|
|
+- `* ✍️ Writing /opt/data/finish-guard/finish ✍️ Writing /opt/data/finish-guard/install.sh`
|
|
|
+- `* ✍️ Writing /opt/data/finish-guard/finish ✍️ Writing /opt/data/finish-guard/install.sh 💻 terminal ``` chmod +x /opt/data/finish-guard/finis... ``` ``` sh /opt/data/finish-guard/install.sh ... ````
|
|
|
+- `🔎 Searching files for remove|unlink|md5`
|
|
|
+- `* 🔎 Searching files for remove|unlink|md5 📖 Reading __init__.py L250-299`
|
|
|
+- `* 🔎 Searching files for remove|unlink|md5 📖 Reading __init__.py L250-299 🔎 Searching files for os\.remove|unlink\(|os\.remove\(`
|
|
|
+- `✍️ Writing /opt/data/scripts/fmode-deliverable-g...`
|
|
|
+- `💻 terminal ``` python3 -m py_compile /opt/data/scrip... ````
|
|
|
+- `* 💻 terminal ``` python3 -m py_compile /opt/data/scrip... ``` ``` echo "=== 幂等重跑 ===" ... ````
|