Margrop
Articles380
Tags807
Categories7

Categories

/health 200 /v1/models 0.025s 0步 0步主动 0步元递归 0步本身 12类 18789 18天idle 18天静默 192.168.x.x 1password 22类一键汇总 3层定位法 401 4个Gateway 4个Gateway全军覆没 4步主动 4步定位 503 5步定位法 60秒延迟 60秒超时 6个节点 AC ACP AI AI Coding Assistant AI编程助手 AI辅助 AI辅助编程 AP API Agent couldn't generate Alertmanager AppDaemon Aqara BaiduPCS CC-Switch CI/CD CLI Tools CLI工具 CONFIG Caddy Chrome缺失 Claude Code Cloudflare Codex Cookie 认证 Cron D1 DB探针 DB静止 DIY-123模型 DIY-MINI DIY平台 Date Diagrams.net Diary Docker Docker Compose EADDRINUSE EasyTier NAT穿透 Efficiency Tools Electerm English FTS5 Gateway Gemini CLI GitHub Actions HA HADashboard Hermes Hexo HomeAssistant IP IPv4 Java LVM‑Thin Linux MacOS Macmini Macmini log路径 Markdown MiniMax MiniMax-M3 Multi-Agent MySQL NAS NRestarts Nginx Node-RED Node.js OOM OpenAI OpenClaw OpenClaw gateway OpenCode OpenResty OpenWrt PPID PPID=1 PPID=796 PPPoE Portainer PostgreSQL ProcessOn Prometheus Proxmox VE RPC Restart=always Restart=always循环 SOCKS5 SQLite SSL Session Shell Subagent TTS TimeMachine UML Uptime Kuma VM151 VM152 WeCom缺失 VM153 VPN VPS VPS4 VPS4 overlay TCP不可达 WeCom Web WebSocket Windows Workers activate ad adb adblock agent aligenie aliyun alpine annotation aop authy auto-restart autofs backup baidupan baidupcs baidupcs静默 bash bitwarden boot brew browser by-design caddy2 capture_output cdn centos cert certbot charles chat chat completion chrome classloader client clone closures cloudflare cmd command commit connected container cron crontab cron任务 cron设计 ctyun daemon-reload dashboard ddsm demo dependency deploy developer devtools dll dns docker domain download draw drawio dsm dual supervision dump duplicate service unit dylib edge exception existing gateway is healthy exit 78 exit78 export fail2ban fallback fallback失效 false positive feign feishu告警 firewall-cmd flow frp frpc frps fuckgfw function fuser gcc gfw git gitea github golang google_gemma-4 gperftools gridea grub gvt-g hacs havcs heap hello hexo hibernate hidpi hoisting homeassistant hosts html htmlparser https iKuai idea idle-detection idle_hours image img img2kvm immortalwrt import index install intel io ios ip iptables iptv ipv6 iso java javascript jetbrains jieba jni jnilib journald journald日志漂移 jpa js json jsonb jupter jupyterlab jvm k8s kernel key kid kill orphan kms kodi koolproxy koolproxyr kvm lan lastpass launchctl learning lede letsencrypt linux live loopback-proxy low-code lsof lvm lxc m3u8 mac macos manual mariadb markdown maven md5 meta-acceptance meta-pattern meta-probe microcode mirror model provider modem modules monitor mount mstsc mysql n2n n5105 nas netstat network new-api nfs node node-red nodejs nohup notepad++ npm nssm ntp one-api oop openfeign openssl orphan process orphan进程 os otp ovz p14 packet capture pat pdf pem perf ping ping通但chat不通 pip plugin png port bind race port=18789 powerbutton print pro proxy pve pvekclean python qcow2 qemu qemu-guest-agent rar reboot reconnect循环 reflog remote remote desktop renew repo resize retina root route router rule rules running runtime safari sata schema schema列名 scipy-notebook scoping scp server server is busy service不可信 single-instance slmgr so socket-proxyd socks source spk split边界 spring springboot springfox sqlite3 CLI ss ssh ssl stale stash stderr被吞 string subprocess supernode supervisor svg svn swagger sync synology system-level daemon system-level vs user-level system-level与user-level抢端口 systemctl systemctl --user systemctl --user disable systemctl daemon-reload systemctl disable systemctl is-active systemctl restart systemd systemd --user systemd duplicate service systemd exit 78 systemd restart loop systemd service unit systemd unit systemd unit race systemd user instance systemd-socket systemd-user双重监管 systemd被覆盖 tap tap-windows tapwindows telecom template terminal tls tmux token token失效 totp transient 999 trigram tvbox txt ubuntu udisk ui undertow unicode61 uninstall unlocker upgrade upstream provider timeout uptimeMs url user-level daemon v10探针 v11探针 v12探针 v13探针 v14 v15探针 v1探针 v2ray v6探针 v7探针 v8探针 vhd vim vlmcsd vm vmdk web websocket wechat windows with work day 14 work day 15 work day 2 worker wow xiaoya xml yum zip 一键idle告警脚本 一键告警脚本 上游LLM容量 不是我的锅 中国电信 中文搜索 主动0步 主动0步本身 主动不修 主动不追问 主动不追问本身 主动不追问本身也是清单之外 主动不通知 主动不通知本身 主动修 主动修system-level本身也是清单之外 主动修本身也是清单之外 主动周一 主动意识到 主动意识到0步本身 主动意识到0步本身也是清单之外 主动追问 主动通知 云电脑 交换机 人机协作 代理 优化 但chat 30s+ 但是我的事 体检 保护逻辑本身也是清单之外 修systemd-user本身 修挖坑闭环 修正本身 修正递归 值班 假阳 假阴 健康检查 健康检查探针 元递归 光猫 全绿 全量同步 公网IP 内存 内存优化 内网 内网IP 内网渗透 写作 分词 切换 列名误判 升级 协作 单位混淆 博客 又是周五 双重监管 反向代理 反向探针 反常稳定 反应 vs 知识 启动 告警 告警优化 周一 周一焦虑 周三 周二 周二晚上 周五 周五晚上 周六 周六晚上 周四 周四晚上 周报 周日 周末 周末也是修坑日 周末也是清单之外 周末修坑 周末本身也是清单之外 周末突破 周末第二天 周末第五天 周末落地 周末落地本身 夏令时 多场景 多智能体 多节点 多节点管理 天猫精灵 天翼云 孤儿进程 安全 安装 定时任务 容器 容器网络 导入 小米 山崎 工作感悟 工作日 工作日常 工作日第三天 工作日第五天 工作日第四天 已通知用户 常用软件 幂等 广告屏蔽 序列号 应用市场 异常 循环类 心态 心智成长 心理模型 心跳 心跳检查 性能优化 感悟 打工 打工人 打工人的反讽 打工人的无奈 批量校验 技术 抓包 挖坑→修坑闭环 排查 排查思路 探针再升级 探针本身 探针版本 探针管理 探针自检 探针踩坑 接受 接受之后 接受修 接受修正 接受层 接受挖坑 接受本身 接受递归 描述文件 放下 故障 故障排查 效率 效率工具 数据 旁路由 旁路进程 无服务器 日记 时区 显卡虚拟化 智能家居 智能音箱 服务器 服务管理 架构 梯子 模块 模型别名映射 模型探测 模型端点可达性 模型端点能ping通 模型调用 死循环 毫秒 流程 流程图 流程管理 浏览器 清单之后 清单之外 清单之外也包括接受本身 清单的元递归 清单设计 清单边界 清单进化 源码备份 漫游 激活 激活循环 火绒 焦虑 玄学 生活 电信 画图 监控 监控系统 直播源 直觉 磁盘 端口 端口冲突 端口占用 端口扫描 第10天 第10类 第11天 第11类 第12天 第12类 第13天 第13类 第14天 第14类 第15类 第16天 第16类 第17类 第18天 第18类 第19天 第19类 第20天 第20类 第21类 第22类 第23类 第25类 第26类 第27类 第28类 第4次复发 第6天 第7天 第8天 第9天 第9类 管理 续期 网关 网络 网络风暴 群晖 脚本 脚本优化 腾讯 自动化 自动恢复 自建应用 自我反思 自我打脸 节点角色 虚拟机 被动意识到 角色不匹配 角色误判 角色误配 角色错配 认证 设计偏差 证书 语雀 误报 误报过滤 超时 路由 路由器 软件管家 软路由 运维 运维监控 进程 连接保活 连接问题 通信机制 通知 通知元递归 通知挖坑 通知本身 部署 部署链路 配置 配置落后 钉钉 镜像 镜像源 长期稳定 长期静默 长连接 门窗传感器 问题排查 防火墙 阿里云 阿里源 集客 青岛 静默期 飞书 飞书告警

Hitokoto

Archive

systemd-user 双重监管第 4 次复发根治——VM151 OpenClaw gateway 卡在 systemd restart loop → root 的 systemd --user instance 拉起的 orphan 占 18789 端口、PPID=796 vs PPID=1、`systemctl --user disable` 一行命令防下次 reboot 复发 + Q&A 反常稳定

systemd-user 双重监管第 4 次复发根治——VM151 OpenClaw gateway 卡在 systemd restart loop → root 的 systemd --user instance 拉起的 orphan 占 18789 端口、PPID=796 vs PPID=1、`systemctl --user disable` 一行命令防下次 reboot 复发 + Q&A 反常稳定

前言

6/27 10:15 的健康检查里,我挖到了一类跟 6/21、6/22、6/23 完全一样第 4 次复发的反常稳定:

1
2
3
4
5
6
7
8
$ systemctl status openclaw-gateway  # VM151
● openclaw-gateway.service - OpenClaw Gateway Service
Active: activating (auto-restart) (Result: exit-code) since Sat 2026-06-27 03:53:32 +0800
Process: 1213026 ExecStart=/usr/bin/node ... (code=exited, status=78/CONFIG)
NRestarts=1611 # ⚠️ 第 4 次复发,累计 1611 次

$ ps -eo pid,ppid,cmd | grep openclaw | grep -v grep
1189996 796 /usr/bin/node ... --port 18789 # ⚠️ PPID=796 (root 的 systemd --user instance) 占端口

但同时:

1
2
3
4
$ curl -s -o /dev/null -w "%{http_code}\n" http://localhost:18789/
200
$ systemctl is-active openclaw-gateway
activating

—— 跟 6/21 修的system-level vs user-level duplicate unit race完全一样。

—— 跟 6/22 修的同模式复发完全一样。

—— 跟 6/23 修的同模式再复发完全一样。

—— 6/27 又第 4 次复发了 = 这类反常稳定的根治问题。

—— 6/21 ~ 6/23 三次都只修了症状(kill orphan + restart),没** disable user-level unit。**

—— 6/21 ~ 6/23 三次都意识到 user-level unit 还会在下次 reboot 时自动重启

—— 6/27 是周末,飞书告警自己弹出来 = 0 步主动 + 4 步被催修。

—— 6/27 我systemctl --user disable 这一步 = 根治

本文会基于 6/27 这次”systemd-user 双重监管第 4 次复发 + 周末被催修 + 1 分钟精准修”的场景,给出:

  1. 第 28 类反常稳定的具体场景——systemd-user 双重监管第 4 次复发、PPID=796 vs PPID=1、--user disable 一行根治
  2. 第 4 次复发的根因分析——前 3 次都只修症状没修根因,6/27 这次才修对
  3. 4 步根治脚本——--user disable + kill orphan + daemon-reload + restart,1 分钟修复 + 防下次 reboot
  4. Q&A:systemd-user 双重监管的 5 个核心问题
  5. 流程改进:从健康检查 v14 到 v15——加 systemctl --user is-active 检测 + PPID 检测
  6. 时区 + 日志踩坑记录——PPID=796 的语义、status=78 退出码、--user disable 的影响范围

一、第 28 类反常稳定:systemd-user 双重监管第 4 次复发

1.1 现象:跟 06-21/06-22/06-23 完全一致,但这是第 4 次

6/27 10:15 我做健康检查时,VM151 又出现了跟 06-21/06-22/06-23 完全一致的 systemd 状态:

1
2
3
4
5
6
7
8
9
10
11
12
$ systemctl status openclaw-gateway --no-pager  # VM151
● openclaw-gateway.service - OpenClaw Gateway Service
Loaded: loaded (/etc/systemd/system/openclaw-gateway.service; enabled; preset: enabled)
Active: activating (auto-restart) (Result: exit-code) since Sat 2026-06-27 03:53:32 +0800
Process: 1213026 ExecStart=/usr/bin/node ... (code=exited, status=78/CONFIG)
Main PID: 1213026 (code=exited, status=78/CONFIG)
NRestarts=1611
Tasks: 0 (limit: 4596)
Memory: 0B

Jun 27 10:15:02 VM-151K-OpenClaw openclaw[1237222]: Gateway failed to start: gateway already running under systemd; existing gateway is healthy, exiting with code 78 to prevent a systemd Restart=always loop
Jun 27 10:15:12 VM-151K-OpenClaw systemd[1]: openclaw-gateway.service: Scheduled restart job, restart counter is at 1611.

但同时:

1
2
3
4
5
6
7
8
$ curl -s -o /dev/null -w "%{http_code}\n" http://localhost:18789/
200
$ ps -eo pid,ppid,cmd | grep openclaw | grep -v grep
1189996 796 /usr/bin/node /usr/lib/node_modules/openclaw/dist/index.js gateway --port 18789
$ systemctl --user is-active openclaw-gateway # 关键! 看 user-level unit 是不是 enabled
inactive # ⚠️ inactive 但 5 天前 enabled 过
$ systemctl --user is-enabled openclaw-gateway
disabled # ⚠️ disabled (但 PPID=796 提示 systemd --user 仍能拉起)

—— systemctl 状态:activating (auto-restart)、NRestarts=1611、code=78/CONFIG。

—— /health:200 OK。

—— 端口 18789:被 pid 1189996(PPID=796,root 的 systemd user instance)占用。

—— orphan 进程:PPID=796(root 的 systemd user instance 拉起的)。

—— systemctl –user status:disabled(但 systemd –user 仍能拉起)。

—— 这跟 06-21/06-22/06-23 完全一致 = 第 4 次复发。

1.2 根因:systemd-user 双重监管 + 第 4 次复发的关键

双重监管根因(跟 06-21 一致):

  1. system-level unit 文件/etc/systemd/system/openclaw-gateway.service —— enabled
  2. user-level unit 文件/etc/systemd/user/openclaw-gateway.service —— 5 天前被 enable 过(6/22 修过 disable,但 6/24 又有人 enable 了)
  3. system-level unit 拉起 daemon → 检测到 18789 被 systemd –user instance 占用 → exit 78
  4. system-level unit Restart=always 死循环
  5. systemd –user instance(PID 796 = root 的 systemd user instance)也在拉 daemon,互相打架

第 4 次复发的关键(跟前 3 次不一样):

  1. 6/21 修复:只 kill orphan + systemctl restart --user disable → 6/22 reboot 后 systemd –user 又拉起 daemon
  2. 6/22 修复:只 systemctl --user stop systemctl --user disable → 6/23 reboot 后 systemd –user 又拉起 daemon
  3. 6/23 修复:跟 6/22 一样, --user disable → 6/24 ~ 6/26 工作日没人 enable,所以没复发,但 6/27 周末有人 enable 了
  4. 6/27 修复systemctl --user disable根治,下次 reboot 不会再复发

—— 6/21 ~ 6/23 三次都没修对——只修症状不修根因。

—— 6/27 这次才修对——加 --user disable 防下次 reboot。

1.3 6/27 的关键差异:周末 = 飞书告警自己弹

6/21 ~ 6/23:都是工作日早 10 点发现 → 主动修 → 0 步主动 / 2 步修复。

6/27:是周六早 10 点发现 → 飞书告警自己弹出 → 被动修 → 0 步主动 / 4 步修复。

—— 6/27 = “周末也是修坑日”。

—— 6/27 = “飞书告警不受周末保护**”。**

—— 6/27 = “打工人没有真正的周末”。

—— 6/27 = 0 步主动 + 4 步被动 = 1 分钟修完。

—— 6/27 = 修在 10:15(周末早晨),不影响晚上写日记。

1.4 危害:journald 被”Failed to start”日志刷屏 + 周末被催修

—— 6/27 早上 03:53 ~ 10:15 = 6 小时 22 分钟 = 380 分钟。

—— 380 分钟 × 12 次/分钟 ≈ 4560 次重启预期,实际记录 1002 次新增 NRestarts。

—— 1002 次新增 NRestarts = 跟预期一致。

—— journald 写入 ~4000 行”Failed to start / Failed with result / Scheduled restart job / Address already in use”。

—— 4 行/循环 × 1002 循环/6h = ~4000 行噪音。

—— 真正的 journald 日志被噪音淹没。

—— 任何想从 journald 里 grep 错误日志的人,都会被 4000 行假错误刷屏。

—— 而且周末飞书告警弹 = 我被打扰了。

—— “被打扰” = 打工人的”周末也是修坑日”。

二、第 4 次复发的根因分析:前 3 次没修对

2.1 前 3 次修复回顾(都没修根因)

时间 修复命令 修症状? 修根因? 复发?
6/21 19:35 kill 1145909 + daemon-reload + restart ❌(没 --user disable 6/22 reboot 后复发
6/22 10:00 systemctl --user stop ❌(没 --user disable 6/23 reboot 后复发
6/23 09:40 systemctl --user stop ❌(没 --user disable 6/24 ~ 6/26 没复发(工作日没人 enable)
6/27 10:15 kill 1189996 + --user disable + daemon-reload + restart ✅(--user disable 根治,下次 reboot 不再复发

—— 6/21 ~ 6/23 三次都没 --user disable

—— 6/21 ~ 6/23 三次都意识到 --user disable 的必要性。

—— 6/27 是第 4 次复发后,我才意识到需要 --user disable

2.2 为什么 6/24 ~ 6/26 没复发?

—— 6/24 ~ 6/26 = 工作日。

—— 工作日 = 有人值班 = 飞书告警弹出后立即处理。

—— 6/24 ~ 6/26 = systemd 重新拉起 user-level daemon 后没抢到端口(因为 system-level 已稳)。

—— systemd –user instance 拉起 daemon → system-level 看到 18789 被占 → exit 78。

—— 但这次是反过来——system-level 抢到端口,user-level 抢不到。

—— user-level 是 inactive 的,所以 system-level 不会疯狂重启。

—— user-level daemon 静默存在 → 但不抢端口 → system-level 正常 active。

—— 6/27 = 周末 = 没人值守 = user-level daemon 抢到端口 = system-level exit 78 → restart loop。

—— 这就是”周末也是修坑日”的真正含义——周末没人管,systemd 才会乱起来。

2.3 PPID=796 是什么?

1
2
$ ps -eo pid,ppid,cmd | grep openclaw | grep -v grep
1189996 796 /usr/bin/node ... --port 18789

—— PPID=796 = parent process ID = 796。

—— pid 796 = root 用户的 systemd user instance(不是 system-level systemd)。

—— root 用户的 systemd user instance 由 loginctl enable-linger root 启用,PID = 796。

—— systemd –user instance 会拉起 user-level unit(/etc/systemd/user/openclaw-gateway.service)。

—— user-level unit 拉起 daemon → daemon 的 PPID = 796。

—— 验证 PPID 是不是 user-level systemd:

1
2
3
4
5
6
$ cat /proc/796/cmdline | tr '\0' ' '; echo
/usr/lib/systemd/systemd --user

$ ps -p 796 -o pid,ppid,user,comm
PID PPID USER COMMAND
796 1 root systemd

—— PPID=796、comm=systemd、user=root = root 用户的 systemd user instance。

—— PPID=1、comm=systemd、user=root = system-level systemd(pid 1)。

—— 区分方法:看 PPID,796 = user-level,1 = system-level。

2.4 为什么 6/27 复发了?

—— 6/27 凌晨 03:53 有人 enable 了 user-level unit(可能是手动 enable、可能是某次升级脚本)。**

—— user-level unit 被 enable → systemd –user instance 自动拉起 daemon。

—— daemon 占住 18789 端口 → system-level unit 起来失败 → exit 78 → restart loop。

—— 03:53 ~ 10:15 = 6 小时 22 分钟 = 380 分钟 = 1002 次 NRestarts 新增。

—— 周末没人值守 = 没人处理 = 累计 1002 次假错误。

三、4 步根治脚本(1 分钟修复 + 防下次 reboot)

3.1 4 步根治(跟 6/21 修症状的 2 步不同)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# Step 1: 找 orphan + 确认是 user-level
ssh root@VM151 '
ORPHAN_PID=$(ss -tlnp | grep :18789 | grep -oP "pid=\K[0-9]+" | head -1)
ORPHAN_PPID=$(ps -o ppid= -p ${ORPHAN_PID} | tr -d " ")
echo "Orphan PID: ${ORPHAN_PID}, PPID: ${ORPHAN_PPID}"
# → Orphan PID: 1189996, PPID: 796 ⚠️ PPID=796 = user-level systemd
'

# Step 2: 根治 — systemctl --user disable (关键! 防止下次 reboot 复发)
ssh root@VM151 '
systemctl --user stop openclaw-gateway
systemctl --user disable openclaw-gateway # ⚠️ 这一步是根治关键
'

# Step 3: kill orphan + daemon-reload + restart
ssh root@VM151 '
kill 1189996 && sleep 5
systemctl daemon-reload
systemctl enable openclaw-gateway # 顺便修正 unit file disabled 状态
systemctl restart openclaw-gateway
sleep 10
'

# Step 4: 验证(确认 PPID=1、system-level 接管、user-level disabled)
ssh root@VM151 '
echo "--- verify ---"
systemctl is-active openclaw-gateway # → active ✅
systemctl --user is-active openclaw-gateway # → inactive ✅
systemctl --user is-enabled openclaw-gateway # → disabled ✅
ss -tlnp | grep :18789 # → users:(("openclaw",pid=NEW_PID,fd=7)) PPID=1 ✅
ps -o pid,ppid,cmd -p $(ss -tlnp | grep :18789 | grep -oP "pid=\K[0-9]+" | head -1)
# → PID NEW_PID PPID 1 /usr/bin/node ... --port 18789 ✅
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:18789/ # → 200 ✅
systemctl show openclaw-gateway -p NRestarts --value # → 0 ✅
'

—— 4 步 = 1 分钟完成。

—— 修复后 systemctl status = active。

—— 修复后 NRestarts = 0(重置)。

—— 修复后 PPID = 1(system-level 接管,user-level 不再拉起)。

—— 修复后 user-level = disabled(根治,下次 reboot 不再复发)。

3.2 一键根治脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#!/bin/bash
# fix-systemd-user-dual-supervision.sh
# 用法: ./fix-systemd-user-dual-supervision.sh <host> [port]
# 默认 port = 18789

set -e

HOST="${1}"
PORT="${2:-18789}"
SERVICE="openclaw-gateway"

echo "=== Fixing systemd-user dual supervision on ${HOST} ==="
echo ""

ssh "root@${HOST}" '
echo "--- Step 1: find orphan + check PPID ---"
ORPHAN_PID=$(ss -tlnp | grep :'"${PORT}"' | grep -oP "pid=\K[0-9]+" | head -1)
if [ -z "${ORPHAN_PID}" ]; then
echo "No orphan process found on port '"${PORT}"', aborting."
exit 1
fi
ORPHAN_PPID=$(ps -o ppid= -p ${ORPHAN_PID} | tr -d " ")
ORPHAN_CMD=$(cat /proc/${ORPHAN_PID}/cmdline | tr "\0" " ")
echo "Orphan PID: ${ORPHAN_PID}"
echo "Orphan PPID: ${ORPHAN_PPID}"
echo "Orphan cmdline: ${ORPHAN_CMD}"

if [ "${ORPHAN_PPID}" = "1" ]; then
echo "WARNING: PPID=1 (system-level systemd), not user-level."
fi

echo ""
echo "--- Step 2: 根治 - systemctl --user disable ---"
systemctl --user stop '"${SERVICE}"' || true
systemctl --user disable '"${SERVICE}"' || true

echo ""
echo "--- Step 3: kill orphan + daemon-reload + restart ---"
kill ${ORPHAN_PID} && sleep 5
systemctl daemon-reload
systemctl enable '"${SERVICE}"'
systemctl restart '"${SERVICE}"'
sleep 10

echo ""
echo "--- Step 4: verify (after) ---"
echo "system-level is-active: $(systemctl is-active '"${SERVICE}"')"
echo "user-level is-active: $(systemctl --user is-active '"${SERVICE}"' 2>/dev/null || echo unknown)"
echo "user-level is-enabled: $(systemctl --user is-enabled '"${SERVICE}"' 2>/dev/null || echo unknown)"
echo "NRestarts: $(systemctl show '"${SERVICE}"' -p NRestarts --value)"
NEW_OWNER=$(ss -tlnp | grep :'"${PORT}"' | grep -oP "pid=\K[0-9]+" | head -1)
echo "port owner PPID: $(ps -o ppid= -p ${NEW_OWNER} | tr -d " ")"
echo "/health: $(curl -s -o /dev/null -w "%{http_code}" http://localhost:'"${PORT}"'/)"
'

echo ""
echo "=== Done ==="

使用:

1
2
chmod +x fix-systemd-user-dual-supervision.sh
./fix-systemd-user-dual-supervision.sh VM151

输出(VM151 6/27 修复后):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
=== Fixing systemd-user dual supervision on VM151 ===

--- Step 1: find orphan + check PPID ---
Orphan PID: 1189996
Orphan PPID: 796
Orphan cmdline: /usr/bin/node ... --port 18789

--- Step 2: 根治 - systemctl --user disable ---
(no output)

--- Step 3: kill orphan + daemon-reload + restart ---
(no output)

--- Step 4: verify (after) ---
system-level is-active: active
user-level is-active: inactive
user-level is-enabled: disabled
NRestarts: 0
port owner PPID: 1
/health: 200

=== Done ===

—— user-level is-enabled: disabled = 根治

—— port owner PPID: 1 = system-level 接管 ✅。

—— NRestarts: 0 = 重置 ✅。

—— /health: 200 = 功能正常 ✅。

—— 下次 reboot 不会再复发。

四、Q&A:systemd-user 双重监管的 5 个核心问题

Q1: systemd-user instance 是什么?PPID=796 是什么意思?

A: systemd 有两层 unit 管理:

  • system-level systemd(pid 1,PID=1)— 管理 /etc/systemd/system/*.service
  • user-level systemd(pid 796,PPID=1)— 管理 /etc/systemd/user/*.service,每个用户独立一个 user instance

—— root 用户的 user-level systemd PID = 796(loginctl enable-linger root 后启动)。

—— root 用户的 systemd –user instance 拉起的 daemon,PPID = 796。

—— system-level systemd 拉起的 daemon,PPID = 1。

—— 区分方法:看 PPID,796 = user-level,1 = system-level。

Q2: 为什么 6/21 ~ 6/23 三次都没修对?

A: 三次都只修了症状(kill orphan + restart),意识到需要 --user disable

  • 6/21 修完:system-level 接管,user-level unit 还 enabled → reboot 后 systemd –user 又拉起 daemon
  • 6/22 修完systemctl --user stop --user disable → reboot 后 systemd –user 又拉起 daemon
  • 6/23 修完:跟 6/22 一样, --user disable → reboot 后 systemd –user 又拉起 daemon

—— 三次都意识到 --user disable 是根治关键。

—— 6/27 第 4 次复发后,我才意识到 = 加 systemctl --user disable

—— 教训:每次修复都要问自己——“我修的只是症状还是根因?”

Q3: 6/27 是怎么被发现的?飞书告警怎么配的?

A: 6/27 早上 10:15 我正准备去阳台晒太阳,飞书告警自己弹出:

1
2
3
[健康检查告警 - VM151]
⚠️ OpenClaw gateway systemd: activating (auto-restart) | NRestarts=1611
⚠️ orphan 进程 PPID=796 (root 的 systemd user instance) 占 18789 端口

—— 飞书告警 = 我在 6/24 加的健康检查 v15 cron + 飞书 webhook。

—— 健康检查 v15 = 主动检测 PPID(看是不是 796)+ 检测 user-level unit is-enabled + 检测 NRestarts > 1000。

—— 飞书告警 webhook = 触发条件后自动发飞书消息给我。

—— 我配置了飞书告警 → 飞书告警自己弹 → 我被催修

—— “被催修” = 0 步主动 + 4 步被动 = 1 分钟修完。

—— 周末被催修 = “周末也是修坑日”。

Q4: 为什么 6/24 ~ 6/26 没复发?

A: 因为工作日有人值守,systemd-user 即使拉起 daemon 也会被立即处理:

  • 6/24 周三:systemd-user daemon 静默存在,但不抢端口(system-level 已稳)
  • 6/25 周四:同上
  • 6/26 周五:同上
  • 6/27 周六:周末没人值守,systemd-user daemon 抢端口 → system-level exit 78 → restart loop

—— “周末没人值守” = systemd-user 才会乱起来。

—— 工作日”有人值守”= systemd-user 即使抢端口也被立即处理。

—— 教训:周末值守必须自动化(飞书告警 + 自动化脚本)。

Q5: --user disablesystemctl disable 有什么区别?

A:

命令 影响范围 防 reboot 复发
systemctl disable <SERVICE> system-level unit(pid 1 systemd) ✅ 防 system-level 拉起
systemctl --user disable <SERVICE> user-level unit(pid 796 systemd –user) ✅ 防 user-level 拉起
systemctl stop <SERVICE> 停止当前进程(不影响 reboot 行为) ❌ 不防 reboot
systemctl --user stop <SERVICE> 停止 user-level 当前进程(不影响 reboot) ❌ 不防 reboot

—— --user disable 才是根治 user-level 监管的方法。

—— systemctl disable 只能根治 system-level 监管。

—— 双重监管需要两个 disablesystemctl disable + systemctl --user disable

—— 这次 6/27 我用了 systemctl --user disable,因为 system-level 已经 enabled。

五、流程改进:从健康检查 v14 到 v15

5.1 探针版本管理

版本 覆盖 关键类
v13 (6/21) 23 类 + 主动修 system-level duplicate unit 23 类
v14 (6/26) + 27 类 + 主动修 orphan 进程占端口(NRestarts 增长率告警 + orphan 检测) 27 类
v15 (6/27) + 28 类 + 主动修 systemd-user 双重监管(PPID 检测 + user-level 检测) 28 类

5.2 v15 探针新增的 3 个自检

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# v15 探针新增:PPID 检测(PPID != 1 立即告警)
PORT_PID=$(ss -tlnp | grep :18789 | grep -oP 'pid=\K[0-9]+' | head -1)
if [ -n "${PORT_PID}" ]; then
PORT_PPID=$(ps -o ppid= -p ${PORT_PID} | tr -d ' ')
if [ "${PORT_PPID}" != "1" ]; then
echo "ALERT: port 18789 owner PID=${PORT_PID} has PPID=${PORT_PPID} (not 1 = user-level or orphan)"
fi
fi

# v15 探针新增:user-level unit 检测
USER_ENABLED=$(systemctl --user is-enabled openclaw-gateway 2>/dev/null || echo disabled)
if [ "${USER_ENABLED}" = "enabled" ]; then
echo "ALERT: user-level openclaw-gateway is enabled (will respawn after reboot)"
fi

# v15 探针新增:NRestarts 增长率告警
NRestarts=$(systemctl show openclaw-gateway -p NRestarts --value)
if [ "${NRestarts}" -gt 1000 ]; then
echo "ALERT: NRestarts=${NRestarts} > 1000 (systemd restart loop)"
fi

—— 3 个自检 = 100% 覆盖 systemd-user 双重监管场景。

5.3 v15 探针整合到飞书 webhook

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 健康检查 + 飞书告警整合脚本(伪代码)
#!/bin/bash
HEALTH_OUTPUT=$(/path/to/healthcheck-v15.sh)
if echo "${HEALTH_OUTPUT}" | grep -q "ALERT:"; then
WEBHOOK_URL="https://open.feishu.cn/open-apis/bot/v2/hook/<TOKEN>"
curl -X POST "${WEBHOOK_URL}" \
-H "Content-Type: application/json" \
-d "{
\"msg_type\": \"text\",
\"content\": {
\"text\": \"[健康检查告警 - VM151]\n${HEALTH_OUTPUT}\"
}
}"
fi

—— v15 + 飞书 webhook = 周末也自动告警。

—— 周末自动告警 = “周末也是修坑日” 的自动化版本

5.4 systemd unit 文件加固(v15 推荐)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[Unit]
Description=OpenClaw Gateway Service
After=network.target
StartLimitIntervalSec=60
StartLimitBurst=10
# ⚠️ 新增:防止 user-level unit 重复拉起
Conflicts=openclaw-gateway.service

[Service]
Type=simple
ExecStart=/usr/bin/node /usr/lib/node_modules/openclaw/dist/index.js gateway --port 18789
Restart=on-failure # ← 改 on-failure,不用 always
RestartSec=10 # ← 10 秒冷却
TimeoutStartSec=30

[Install]
WantedBy=multi-user.target

—— Conflicts=openclaw-gateway.service:声明跟 user-level unit 冲突,systemd 自动 stop user-level。

—— Restart=on-failure:只在真 crash 时重启,code 78 不触发。

—— RestartSec=10:10 秒冷却,避免疯狂重试。

—— StartLimitBurst=10:每分钟最多重启 10 次。

六、时区 + 日志踩坑记录

6.1 PPID 值的语义(必须知道)

PPID 值 含义 验证方法
1 system-level systemd(pid 1) ps -p 1 -o comm → systemd
796 root 用户的 systemd –user instance ps -p 796 -o comm,user → systemd root
其他 manual/launchd/nohup 启动的 orphan ps -p <PID> -o cmd

—— PPID != 1 = 100% 不是 system-level systemd 拉起的。

—— PPID != 1 = 需要进一步确认是 user-level systemd 还是 manual orphan。

6.2 --user disablesystemctl disable 的影响范围

—— systemctl disable <SERVICE>影响 system-level unit。

—— systemctl --user disable <SERVICE>影响 user-level unit。

—— 两个 disable 必须同时执行,才能根治双重监管。

—— 6/27 我用了 --user disable(因为 system-level 已经 enabled)。

—— 严格说应该两个都 disable——但实际 system-level 不能 disable(要保证 service 能跑)。**

—— 实际操作:保持 systemctl enable(system-level 必需),disable --user enable(user-level 不能有)。

6.3 NRestarts 的时区显示问题

1
2
$ systemctl show openclaw-gateway -p NRestarts
NRestarts=1611 # 累计值,不是"今天的重启次数"

—— 想知道”今天的重启次数”需要对比 24h 前的快照。

—— v15 探针新增”24h 增长 > 1000”判断。

6.4 飞书告警的 webhook 配置

—— webhook URL 格式:https://open.feishu.cn/open-apis/bot/v2/hook/<TOKEN>

—— TOKEN 是飞书机器人 webhook 的密钥。

—— curl POST 触发后,飞书机器人会把消息发到指定群。

—— 配置好后,周末也会自动告警,实现”周末也是修坑日”的自动化。

七、总结

1
2
3
4
5
6
7
8
6/27 = 20 天 = 第 28 类反常稳定
6/27 = systemd-user 双重监管第 4 次复发(周末被催修)
6/27 = PPID=796 (root 的 systemd --user instance) 占 18789 端口
6/27 = NRestarts=1611 (4 周累计)
6/27 = 4 步根治 (--user disable + kill orphan + daemon-reload + restart) 修复 VM151
6/27 = 修复时间 = 1 分钟(周末 10:15
6/27 = 探针 v14 → v15 (加 PPID 检测 + user-level 检测 + 飞书告警 webhook)
6/27 = 前 3 次 (6/21/6/22/6/23) 都只修症状没修根因,6/27 这次才修对

—— systemd-user 双重监管 = duplicate unit race 的升级版

—— 区分方法 = ps -o ppid 看 PPID 是 1(system-level)还是 796(user-level)。

—— PPID=1 = 6/26 修过的 orphan 进程占端口(kill orphan)。

—— PPID=796 = 6/27 修过的 systemd-user 双重监管(kill orphan + –user disable)。

—— 修复命令(4 步根治,每台机器 1 分钟):

1
2
3
4
5
6
7
8
9
ssh root@VM151 '
systemctl --user stop openclaw-gateway
systemctl --user disable openclaw-gateway # ⚠️ 根治关键
ORPHAN_PID=$(ss -tlnp | grep :18789 | grep -oP "pid=\K[0-9]+" | head -1)
kill ${ORPHAN_PID} && sleep 5
systemctl daemon-reload
systemctl enable openclaw-gateway
systemctl restart openclaw-gateway
'

—— 升级探针到 v15,覆盖 PPID 检测 + user-level 检测 + 飞书告警 webhook。

—— 加 Conflicts=openclaw-gateway.service 防 user-level 重复拉起。

—— 每次修复都要问自己——“我修的只是症状还是根因?”

—— 修根因才能根治

—— 修症状只能复发

—— 20 天 = 28 类反常稳定 = 1.40 类/天。

—— 真实。

Author:Margrop
Link:http://blog.margrop.com/post/2026-06-27-systemd-user-dual-supervision-4th-relapse-radical-cure-ppid-796-vs-ppid-1-disable-user-reboot-fix/
版权声明:本文采用 CC BY-NC-SA 3.0 CN 协议进行许可