跳转至

NFOS systemd 定时器全景(timers)

数据来源:主节点 systemctl show / systemctl list-timers 只读采集(快照见 .scratch/timers-data.json)。

文档版本:V1.0 文档性质:清单与排障类(只读采集,非规范) 采集范围:主节点 123.57.85.171(阿里云北京)单机;次节点 43.156.170.157 未采集


1. 总览

主节点上共有 17 个已安装并处于 active 的 NFOS 定时器(systemctl list-timers 'nfos-*' --all 报告 17 timers listed)。它们全部由 systemd 驱动(没有 crontab 参与),分布如下:

项目 值
主节点 123.57.85.171
已安装 timer 数 17(另有 1 个 nfos-tunnel.service 是常驻长进程,没有 timer)
调度引擎 systemd timer units(/etc/systemd/system/nfos-*.timer)
触发方式 13 个走 OnCalendar(日历/挂钟),3 个走单调计时器(OnBootSec+OnUnitActiveSec)+ nfos-admin-password 为「OnCalendar + OnBootSec 混合」
运行身份 除 nfos-admin-password、nfos-cert-renew 采集到 User= 为空(按系统默认/单元自身设定)外,其余 timer 对应服务均为 root
服务类型 对应 service 全部为 Type=oneshot:跑完即退出,相同周期内不会并发并存
日志落点 一半写 StandardOutput/StandardError=append: 到 /opt/Cy/services/logs/backend/nfos/*.log;nfos-admin-password、nfos-logrotate 未设置输出重定向 → 只进 journald

快照时间口径:timers-data.json 的 generated_utc 字段记录为 2026-09-28T00:0Z (probe realtime: 2026-09-28 07:5x CST, primary node 123.57.85.171); 同一批采集中 systemctl list-timers 原始输出里最近一次触发为 Mon 2026-09-28 07:51:04 CST,NextElapseUSecRealtime 最早为 08:00:00 CST。 因此本页所有「已安装 / 未安装 / 最近触发」结论的统一口径为 2026-09-28 07:5x CST(CST = UTC+8), 超出该时刻的线上状态需重新采集,不要沿用本页。

读法提醒:本页的 OnCalendar 与「运行的脚本与参数」两列是逐字从 timers-data.json 的 on_calendar / exec_start 抄录的,未做任何改写或省略; 「用途」列为便于阅读做了压缩,原文见快照 purpose 字段。 本页不记录任何口令、webhook URL、token 或私钥,涉及处一律写变量名或 <redacted>。


2. 一览表(17 个 timer)

列含义:幂等键 = 脚本用于「同一件事只做一次 / 不重复通知」的状态文件; 备份位置 = 该 timer 确实产生备份产物的位置,不产生备份的写 —。

Timer OnCalendar 运行的脚本与参数 用途 产物 / 日志路径 幂等键 备份位置
nfos-security.timer (单调:OnBootSec=120 / OnUnitActiveSec=60,Persistent=false) /bin/bash /opt/Cy/services/system.d/nfos/security-guard.sh 每 60 秒安全防线巡检(特征库/规则库/模板库驱动):反弹 Shell、异常外联、暴露监听、持久化、SSH 爆破、完整性漂移;命中先过自动处置,再上报 nfarmsd(source=security)并升级到工单/飞书 日志 /opt/Cy/services/logs/backend/nfos/security-guard.log;状态 /opt/Cy/services/system.d/nfos/state/security-guard.state;证据 state/snap-*.txt、/opt/Cy/services/logs/backend/nfos/auto-respond.log state/security-guard.state(记录当前正在跟踪的 dedup_key 集合) —
nfos-probe.timer (单调:OnBootSec=90 / OnUnitActiveSec=60,Persistent=false) /bin/bash /opt/Cy/services/system.d/nfos/probe-node.sh --quiet 每 60 秒互监测探针(主/次节点同款):两节点各服务端口 TCP 拨测 + 应用层检查 + SSH 隧道状态,落成 Prometheus textfile 日志 /opt/Cy/services/logs/backend/nfos/probe.log(当前 0 字节,输出全在 .prom 里);产物 /opt/Cy/services/system.d/nfos/monitoring/textfile/nfos_probe.prom (未见显式状态文件;每轮原子覆盖 textfile) —
nfos-ticket-notify.timer (单调:OnBootSec=120 / OnUnitActiveSec=300,Persistent=true) /usr/bin/python3 /opt/Cy/services/system.d/nfos/monitoring/ticket-notify.py 每 300 秒轮询本机 nfarmsd /tickets,与本地快照比对,新开单/状态转变 → 邮件 + 飞书(把无人消费的 ticket.created / ticket.updated 事件接上) 日志 /opt/Cy/services/logs/backend/nfos/ticket-notify.log;状态 /opt/Cy/services/system.d/nfos/state/ticket-notify-state.json state/ticket-notify-state.json (仅 unit 文件备份:state/unit-backups/nfos-ticket-notify.{service,timer}.bak-20260926-164409)
nfos-admin-password.timer *:0/10(另有 OnBootSec=3min,Persistent=true) /opt/Cy/services/system.d/nfos/admin-password-rotate.sh 每 10 分钟检查超管口令是否等于当日派生值,未轮换则经 nfarmsd reset-password 接口轮换(当天已轮换则幂等退出) 无输出重定向 → journald:journalctl -u nfos-admin-password.service;状态 /opt/Cy/services/system.d/nfos/state/admin-password-rotation.json state/admin-password-rotation.json(记录「今天已轮换」) (当日回滚记录:/opt/Cy/certs/backend/admin-password.txt,mode 600,内容 <redacted>)
nfos-digest-hourly.timer *-*-* *:00:30 /usr/bin/python3 /opt/Cy/services/system.d/nfos/monitoring/digest.py --window 1h 每小时汇总刚结束整点窗口的 critical 告警(攻击成功 / 抵御失败)→ 飞书安全群 + 安全邮箱 日志 /opt/Cy/services/logs/backend/nfos/digest.log;状态 /opt/Cy/services/system.d/nfos/state/digest.state.json state/digest.state.json(last_sent_bucket) —
nfos-alert-sweep.timer *-*-* *:02,17,32,47:10 /usr/bin/python3 /opt/Cy/services/system.d/nfos/alert-sweep.py --apply 每 15 分钟回收陈旧(孤儿)告警:只回收「recovered_at IS NULL + source=security + dedup_key LIKE 'nfos.sec:%' + 守卫已不再跟踪」的行,让活动告警数能降下来 日志 /opt/Cy/services/logs/backend/nfos/alert-sweep.log (无自有状态文件;判据 = state/security-guard.state + nfarmsd 库 recovered_at) —
nfos-log-audit.timer *-*-* *:07:00 /usr/bin/python3 /opt/Cy/services/system.d/nfos/log-audit.py 每小时系统日志异常审计(journalctl / messages / secure / kernel:OOM、I/O 与 FS 错误、磁盘写满、崩溃循环、core dump、认证失败激增、conntrack 满)→ 安全告警;告警看最近 15 分钟,报告按日归档 日志 /opt/Cy/services/logs/backend/nfos/log-audit.log;报告目录 /opt/Cy/services/logs/backend/nfos/log-audit/;状态 /opt/Cy/services/system.d/nfos/state/log-audit.state.json;产物 monitoring/textfile/nfos_log_audit.prom state/log-audit.state.json (仅 unit 文件备份:state/unit-backups/nfos-log-audit.timer.bak-20260926-170808)
nfos-maintenance-revert.timer *-*-* 09:00:00 /bin/bash /opt/Cy/services/system.d/nfos/state/maintenance-revert.sh 09:00 结束维护窗口,还原告警策略(恢复 observe_only,即把维护期置为「只记日志」的告警重新打开) 日志 /opt/Cy/services/logs/backend/nfos/maintenance.log;快照凭据 state/unit-backups/maintenance-snapshot-path-20260928-074114.txt (未见显式状态文件;脚本对只读账号不可读,无法确认) state/unit-backups/policy-before-maintenance-*.json、state/unit-backups/rule-library.yml.maintenance-*
nfos-report-attack.timer *-*-* 07,09,11,13,15,17,19,21:00:20 /usr/bin/python3 /opt/Cy/services/system.d/nfos/monitoring/digest.py --report attack 攻击成功通报:07:00–21:00 每 2 小时 → 飞书汇总群 + 安全邮箱 日志 /opt/Cy/services/logs/backend/nfos/report.log;状态 state/digest.state.json state/digest.state.json —
nfos-report-tickets.timer *-*-* 08:00:00 /usr/bin/python3 /opt/Cy/services/system.d/nfos/monitoring/digest.py --report tickets 每日 08:00 工单状态日报(已处理 / 未处理 / 未分配)→ 飞书汇总群 + 安全邮箱 日志 /opt/Cy/services/logs/backend/nfos/report.log;状态 state/digest.state.json state/digest.state.json(last_sent_bucket) —
nfos-report-daily-alerts.timer *-*-* 10:00:00 /usr/bin/python3 /opt/Cy/services/system.d/nfos/monitoring/digest.py --report daily-alerts 每日 10:00 前一日(00:00–23:59)告警汇总 → 飞书汇总群 + 安全邮箱 日志 /opt/Cy/services/logs/backend/nfos/report.log;状态 state/digest.state.json state/digest.state.json —
nfos-report-libs.timer *-*-* 12:00:00 /usr/bin/python3 /opt/Cy/services/system.d/nfos/monitoring/digest.py --report libs 每日 12:00 规则库 / 病毒特征库更新通报 → 飞书汇总群 + 安全邮箱 日志 /opt/Cy/services/logs/backend/nfos/report.log;状态 state/digest.state.json state/digest.state.json —
nfos-digest-12h.timer *-*-* 00,12:00:30 /usr/bin/python3 /opt/Cy/services/system.d/nfos/monitoring/digest.py --window 12h 每 12 小时汇总 high + warning 告警(攻击失败 / 抵御成功 / 结果未知)→ 飞书安全群 + 安全邮箱 日志 /opt/Cy/services/logs/backend/nfos/digest.log;状态 state/digest.state.json state/digest.state.json —
nfos-logrotate.timer *-*-* 00:20:00 /usr/sbin/logrotate /etc/logrotate.conf(另有 ExecStartPost=/bin/true) 每日 00:20 日志轮转,覆盖 /etc/logrotate.conf 与 /etc/logrotate.d/*,补齐主节点缺失的 /etc/cron.daily/logrotate(否则 logrotate.d/nfarms-nginx 永不执行) 无输出重定向 → journald:journalctl -u nfos-logrotate.service;条件 ConditionPathExists=/etc/logrotate.conf;配置目录 /etc/logrotate.d/ (未见显式状态文件) —
nfos-backup.timer *-*-* 02:00:00 /bin/bash /opt/Cy/services/system.d/nfos/backup-daily.sh 每日 02:00 备份日志 + 配置 + 数据库(nfarmsd SQLite 一致性快照、次节点 TimescaleDB pg_dump、NATS JetStream 归档),隧道/次节点不可达时降级为仅主节点并在 MANIFEST 注明 日志 /opt/Cy/services/logs/backend/nfos/backup.log;产物根 /home/ecs-user/temp/<YYYY-MM-DD>/;脚本自写日志 /home/ecs-user/temp/last-backup.log (无去重状态文件;产物按日期目录 /home/ecs-user/temp/<YYYY-MM-DD>/ 天然幂等) 备份产物根 /home/ecs-user/temp/<YYYY-MM-DD>/(logs/、config/、db/、MANIFEST.md、CHECKSUMS.sha256;保留最近 14 个日期目录)
nfos-cert-renew.timer *-*-* 03:20:00 /bin/bash /opt/Cy/services/system.d/nfos/cert-renew.sh renew 每日检查 9 域 NFarms SAN 站点证书是否需要续期(acme.sh + Let's Encrypt HTTP-01 webroot),剩余 30 天内自动续期 日志 /opt/Cy/services/logs/backend/nfos/cert-renew.log (未见显式状态文件;「未变更则不部署」由脚本自身判定) 脚本备份目录 /opt/Cy/certs/backup/nginx-<timestamp>/(保留最近 5 份)
nfos-cert-sync.timer *-*-* 04:20:00 /bin/bash /opt/Cy/services/system.d/nfos/cert-sync.sh --quiet 每日 04:20 证书审计:解析线上 nginx 配置、扫描 /opt/Cy/certs/nginx 下全部 PEM、登记 nfarmsd,并按规则产生/恢复安全告警(CertificateExpired / ExpiringCritical / ExpiringSoon / KeyPermissive / Missing) 日志 /opt/Cy/services/logs/backend/nfos/cert-sync.log;状态 /opt/Cy/services/system.d/nfos/state/cert-sync.state state/cert-sync.state(记录「已上报的 dedup_key → 状态/时间」) —

备份位置一列只有 5 处非空:nfos-ticket-notify、nfos-log-audit(仅 unit 文件备份)、 nfos-maintenance-revert(策略快照)、nfos-backup(备份产物根)、nfos-cert-renew(证书备份)。 其余 12 个 timer 不产生备份产物,写 —。


3. 安全防线

这几条是「秒/分钟级」的在线防线,也是唯一会在无人值守时自动处置的一层。 它们全部依赖 state/security-guard.state 这条链,所以该状态文件被重建/迁移会连带影响 nfos-alert-sweep。

nfos-security.timer

  • 触发节奏:单调计时器 —— 开机后 120 秒首跑,之后每 60 秒一轮(Persistent=false,不补跑)。
  • 跑什么:security-guard.sh 逐条执行特征库检测(反弹 Shell / 异常外联 / 暴露监听 / 持久化 / SSH 爆破 / 完整性漂移),命中先过自动处置引擎(monitoring/auto-respond.py),再按 rule-library.yml 的 dedup_key 上报 nfarmsd(source=security),critical 自动转工单并推送飞书/邮件。
  • 失败会怎样:单轮脚本非 0 退出 → 单元置 failed,security-guard.log 留痕;unit 未启用 Restart=,不会自动重试,靠 60 秒后的下一轮自愈。若脚本既未上报 nfarmsd 又未写日志,只能靠人工 systemctl status 发现——这正是第 8 节第 1 项缺口的风险点。
  • 日志去哪:/opt/Cy/services/logs/backend/nfos/security-guard.log(采集时 9.7 MB,最新写入 07:51)。
  • 手工跑一次:sudo systemctl start nfos-security.service

nfos-probe.timer

  • 触发节奏:单调计时器 —— 开机后 90 秒首跑,之后每 60 秒一轮(Persistent=false)。
  • 跑什么:probe-node.sh --quiet 对主/次节点每个服务端口做 TCP 拨测 + 应用层拨测 + SSH 隧道状态检查,原子写入 node_exporter textfile。
  • 失败会怎样:单元 failed;由于 probe.log 当前是 0 字节,失败信息主要落在单元状态里,journalctl -u nfos-probe.service 才是第一现场。textfile 若未被刷新,Prometheus 侧体现为指标停更。
  • 日志去哪:/opt/Cy/services/logs/backend/nfos/probe.log(0 字节);产物 /opt/Cy/services/system.d/nfos/monitoring/textfile/nfos_probe.prom。
  • 手工跑一次:sudo systemctl start nfos-probe.service

nfos-alert-sweep.timer

  • 触发节奏:每小时 :02、:17、:32、:47 各一次,另有 30 秒随机延迟。
  • 跑什么:alert-sweep.py --apply 只读打开 nfarmsd 库,回收「守卫已不再跟踪」的 nfos.sec:* 孤儿告警;prometheus/... 来源的告警绝不触碰。
  • 失败会怎样:单元 failed;后果不是误报而是「活动告警数降不下来」,Prometheus 的「活动告警数超阈值」元告警可能长期无法恢复。下一轮 15 分钟后重来。
  • 日志去哪:/opt/Cy/services/logs/backend/nfos/alert-sweep.log。
  • 手工跑一次:sudo systemctl start nfos-alert-sweep.service(先加 --dry-run 之外的参数前请读脚本用法)

nfos-admin-password.timer

  • 触发节奏:OnCalendar=*:0/10(每小时 :00、:10、…、:50)外加 OnBootSec=3min 开机补一次;Persistent=true。
  • 跑什么:admin-password-rotate.sh 校验超管口令是否等于当日派生值,不等则经 nfarmsd /users/:id/reset-password 接口轮换(走正规哈希与审计链,不直接改库)。
  • 失败会怎样:脚本头部明确「失败即上报统一告警(warning),在 online 平台可见,不会静默失败」;由于每 10 分钟一轮,当天某次失败(例如 nfarmsd 正在重启)可在 10 分钟内自愈。
  • 日志去哪:无输出重定向 → 只进 journald:journalctl -u nfos-admin-password.service;状态文件 /opt/Cy/services/system.d/nfos/state/admin-password-rotation.json。
  • 手工跑一次:sudo systemctl start nfos-admin-password.service;只读查看派生值与状态用 --status 子命令(派生规则与明文口令不在本页记录,见脚本头部)。
  • 脱敏提醒:/opt/Cy/certs/backend/admin-password.txt(mode 600)内容切勿粘贴到工单、文档或聊天。

4. 通知通报

这一层只「说话」不「动手」:7 个 timer 共用 2 个脚本(monitoring/digest.py、monitoring/ticket-notify.py), 幂等键集中在 state/digest.state.json 与 state/ticket-notify-state.json 两个文件上。

nfos-ticket-notify.timer

  • 触发节奏:单调计时器 —— 开机后 120 秒首跑,之后每 300 秒一轮;Persistent=true(当轮没开机,开机补跑一次)。
  • 跑什么:轮询 GET /tickets → 与本地快照比对 → 新开单/状态转变发邮件 + 飞书(首轮只登记不发信,避免历史工单邮件风暴;发送成功才落盘)。
  • 失败会怎样:发送失败保留旧状态、下一轮重试,不会静默丢通知;单元 failed + ticket-notify.log 留痕。
  • 日志去哪:/opt/Cy/services/logs/backend/nfos/ticket-notify.log(采集时 2.4 MB)。
  • 手工跑一次:sudo systemctl start nfos-ticket-notify.service

nfos-digest-hourly.timer

  • 触发节奏:每小时 :00:30;RandomizedDelaySec=0、AccuracySec=30s、Persistent=true。
  • 跑什么:digest.py --window 1h 汇总刚结束的整点窗口 critical 告警(攻击成功 / 抵御失败)→ 飞书安全群 + 安全邮箱。
  • 失败会怎样:窗口不会被重算(下轮只算新窗口),因此失败意味着该小时 critical 汇总缺失;Persistent=true 只能补跑一次错过的轮次。
  • 日志去哪:/opt/Cy/services/logs/backend/nfos/digest.log。
  • 手工跑一次:sudo systemctl start nfos-digest-hourly.service

nfos-digest-12h.timer

  • 触发节奏:每天 00:00:30 与 12:00:30;RandomizedDelaySec=60、Persistent=true。
  • 跑什么:digest.py --window 12h 汇总 high + warning(攻击失败 / 抵御成功 / 结果未知)→ 飞书安全群 + 安全邮箱。
  • 失败会怎样:同 1h 档,错过的 12h 窗口不会重算;digest.state.json 的 last_sent_bucket 决定哪些窗口算「已发」。
  • 日志去哪:/opt/Cy/services/logs/backend/nfos/digest.log。
  • 手工跑一次:sudo systemctl start nfos-digest-12h.service

nfos-report-attack.timer

  • 触发节奏:07,09,11,13,15,17,19,21 点的 :00:20(每 2 小时);RandomizedDelaySec=60、Persistent=true。
  • 跑什么:digest.py --report attack —— 攻击成功通报 → 飞书汇总群 + 安全邮箱。
  • 失败会怎样:单元 failed、report.log 留痕;报告类通报失败不阻断业务,但当天窗口的报告会缺口。
  • 日志去哪:/opt/Cy/services/logs/backend/nfos/report.log。
  • 手工跑一次:sudo systemctl start nfos-report-attack.service

nfos-report-tickets.timer

  • 触发节奏:每日 08:00:00(RandomizedDelaySec=120、Persistent=true)。采集时从未触发过(LastTriggerUSec 为空),下次 08:00:59。
  • 跑什么:digest.py --report tickets —— 工单状态日报(已处理 / 未处理 / 未分配)。
  • 失败会怎样:单元 failed、report.log 留痕;日报缺失不会自动补发(Persistent=true 只在开机补跑,不补历史日)。
  • 日志去哪:/opt/Cy/services/logs/backend/nfos/report.log。
  • 手工跑一次:sudo systemctl start nfos-report-tickets.service

nfos-report-daily-alerts.timer

  • 触发节奏:每日 10:00:00(RandomizedDelaySec=120、Persistent=true)。采集时同样从未触发。
  • 跑什么:digest.py --report daily-alerts —— 前一日 00:00–23:59 告警汇总。
  • 失败会怎样:同日报类,失败只在 report.log 与单元状态留痕。
  • 日志去哪:/opt/Cy/services/logs/backend/nfos/report.log。
  • 手工跑一次:sudo systemctl start nfos-report-daily-alerts.service

nfos-report-libs.timer

  • 触发节奏:每日 12:00:00(RandomizedDelaySec=120、Persistent=true)。采集时同样从未触发。
  • 跑什么:digest.py --report libs —— 规则库 / 病毒特征库更新通报。
  • 失败会怎样:同日报类;「库没更新」这类信息若漏报,等于失去一层对特征库停更的观测。
  • 日志去哪:/opt/Cy/services/logs/backend/nfos/report.log。
  • 手工跑一次:sudo systemctl start nfos-report-libs.service

5. 审计与轮转

nfos-log-audit.timer

  • 触发节奏:每小时 :07:00(AccuracySec=30s、RandomizedDelaySec=60、Persistent=true)。注意:仓库里的 OnCalendar 与线上不同(详见第 9 节备注),线上现行为小时级。
  • 跑什么:log-audit.py 读 journalctl / messages / secure / kernel,按特征库 detect: log_anomaly 条目计数并上报;告警只看最近 15 分钟,报告仍按整窗口统计并按日归档。
  • 失败会怎样:单元 failed、log-audit.log 留痕;设计上「每小时一轮 + 15 分钟近期窗口」意味着持续 ≥15 分钟的异常最迟 1 小时内告警,漏一轮就延长这段盲区。
  • 日志去哪:/opt/Cy/services/logs/backend/nfos/log-audit.log;报告 /opt/Cy/services/logs/backend/nfos/log-audit/;指标产物 monitoring/textfile/nfos_log_audit.prom。
  • 手工跑一次:sudo systemctl start nfos-log-audit.service;指定窗口可参考脚本用法(log-audit.py --since '3 days ago',直跑脚本前先确认参数)。

nfos-logrotate.timer

  • 触发节奏:每日 00:20:00(RandomizedDelaySec=300、Persistent=true)。
  • 跑什么:直接执行 /usr/sbin/logrotate /etc/logrotate.conf(另有 ExecStartPost=/bin/true)——这是唯一不跑 NFOS 脚本的 timer,用于补齐主节点缺失的 /etc/cron.daily/logrotate。
  • 失败会怎样:单元 failed;直接后果是 logrotate.d/nfarms-nginx 等规则不执行,日志不轮转、磁盘缓慢增长。无输出重定向,只能从 journald 看。
  • 日志去哪:journalctl -u nfos-logrotate.service(无 .log 文件)。
  • 手工跑一次:sudo systemctl start nfos-logrotate.service;等价手跑 sudo /usr/sbin/logrotate /etc/logrotate.conf。调试时务必先加 -d(dry-run),否则会真的切分日志。

6. 备份与证书

本组是「可恢复性」的全部来源。第 8 节第 3 项指出:维护窗口还原脚本在仓库里没有副本。

nfos-backup.timer

  • 触发节奏:每日 02:00:00(RandomizedDelaySec=300、Persistent=true;Persistent=true 意味着关机错过也会开机补跑)。
  • 跑什么:backup-daily.sh 打包日志(主节点 /opt/Cy/services/logs + 次节点经 SSH 尽力而为)、配置(敏感文件不复制,改为在 config/REDACTED.txt 登记跳过原因)、数据库(nfarmsd SQLite 一致性快照、次节点 TimescaleDB pg_dump -Fc、NATS JetStream 归档),生成 MANIFEST.md 与 CHECKSUMS.sha256。
  • 失败会怎样:任一产物 sha256sum -c 不通过 → 脚本显式报错并非 0 退出(不静默通过);隧道/次节点不可达时降级为仅主节点并在 MANIFEST 注明,不算失败但备份面收窄。
  • 日志去哪:/opt/Cy/services/logs/backend/nfos/backup.log;脚本自写 /home/ecs-user/temp/last-backup.log;产物 /home/ecs-user/temp/<YYYY-MM-DD>/。
  • 手工跑一次:sudo systemctl start nfos-backup.service;也可直接 sudo bash /opt/Cy/services/system.d/nfos/backup-daily.sh。
  • 保留策略提醒:只保留最近 14 个严格 YYYY-MM-DD 目录,且绝不删除今天及以后的目录;/home/ecs-user/temp 下其他文件从不触碰。

nfos-cert-renew.timer

  • 触发节奏:每日 03:20:00(RandomizedDelaySec=1800、Persistent=true)。线上该 timer 没有 Unit= 行也没有 [Install] WantedBy,但仍处于 enabled 并按名字解析到 nfos-cert-renew.service。
  • 跑什么:cert-renew.sh renew 走 acme.sh + Let's Encrypt HTTP-01 webroot 检查 9 域 SAN 证书剩余天数,剩余 30 天内自动续期;未变更则不部署。
  • 失败会怎样:单元 failed、cert-renew.log 留痕;后果是证书到期前未被发现,最终表现为站点证书过期。脚本自带「部署前整体备份 + 校验 + 回滚」路径。
  • 日志去哪:/opt/Cy/services/logs/backend/nfos/cert-renew.log。
  • 手工跑一次:sudo systemctl start nfos-cert-renew.service;只读体检用 bash /opt/Cy/services/system.d/nfos/cert-renew.sh check(零副作用)。
  • 回滚:bash /opt/Cy/services/system.d/nfos/cert-renew.sh rollback <备份目录>,备份目录在 /opt/Cy/certs/backup/nginx-<时间戳>/。

nfos-cert-sync.timer

  • 触发节奏:每日 04:20:00(RandomizedDelaySec=600、Persistent=true)。
  • 跑什么:cert-sync.sh --quiet 解析 nginx 配置 → 扫描 /opt/Cy/certs/nginx 全部 PEM → 指纹比对后登记 nfarmsd(已存在则跳过)→ 按 5 条规则产生/恢复安全告警。
  • 失败会怎样:单元 failed、cert-sync.log 留痕;由于它同时是证书告警的恢复方,漏跑会让「已修复的证书问题」继续挂着告警(与 nfos-alert-sweep 的困扰同类)。
  • 日志去哪:/opt/Cy/services/logs/backend/nfos/cert-sync.log;状态 /opt/Cy/services/system.d/nfos/state/cert-sync.state。
  • 手工跑一次:sudo systemctl start nfos-cert-sync.service;脚本支持 --dry-run(零副作用),排障优先用它。

7. 维护窗口

nfos-maintenance-revert.timer

  • 触发节奏:每日 09:00:00(AccuracySec=30s、Persistent=true)。这是每日固定开火的 timer,采集时 LastTriggerUSec 为空(当日尚未到点)。
  • 跑什么:执行 /opt/Cy/services/system.d/nfos/state/maintenance-revert.sh —— 结束维护窗口,把告警策略还原到 observe_only(即把关掉的告警重新打开)。它消费的是维护开始时留下的策略快照。
  • 失败会怎样:后果偏严重——若还原没跑成,系统会停留在「维护期策略」上(告警被压制),而运维以为窗口已结束。日志在 maintenance.log,单元 failed 可在 systemctl status 看到。
  • 日志去哪:/opt/Cy/services/logs/backend/nfos/maintenance.log;策略快照 state/unit-backups/policy-before-maintenance-*.json(例如 .../policy-before-maintenance-20260928-074114.json)、state/unit-backups/rule-library.yml.maintenance-20260928-074039;另有 state/unit-backups/maintenance-snapshot-path-20260928-074114.txt 指向快照路径。
  • 手工跑一次:sudo systemctl start nfos-maintenance-revert.service。注意:脚本内容对只读账号不可读(见第 8 节第 3 项),执行前请自行确认当前策略与快照内容,不要盲跑。
  • 配套事实:deploy/nfos/ 下没有 state/ 目录;.scratch/maintenance-window.sh、.scratch/maintenance-fix.sh 只是操作者的一次性脚本,不是 maintenance-revert.sh 的副本。

8. ⚠️ 与实际不符的三项(必须正视,不做淡化处理)

8.1 nfos-alert-selftest.{service,timer} —— 仓库里有,线上未安装

  • 事实:systemctl is-enabled nfos-alert-selftest.timer → not-found;systemctl is-active nfos-alert-selftest.service → inactive; 且 /opt/Cy/services/system.d/nfos/alert-selftest.sh 在节点上不存在。
  • 仓库 unit 描述的能力是:每日 08:00 做 告警链路端到端自检(校验 Prometheus / Alertmanager / 规则 / 桥接器 / nfarmsd 上报链路,注入合成告警,并做身份头伪造回归), 并原子刷新 node_exporter textfile 心跳 /opt/Cy/services/system.d/nfos/monitoring/textfile/nfos_alert_selftest.prom 作为死人开关 (手册约定:心跳超 36 小时不刷新,NfosAlertSelftestStale 规则应独立报出 critical)。
  • 结论:仓库 unit 描述的「每日告警链路自检 / 死人开关」当前线上并不存在。 即:主节点此刻没有任何机制能发现「告警链路整体哑掉」这种事——告警不响本身不会告警。
  • 处置方向(不在本页范围内):补部署 alert-selftest.sh + 两个 unit,或明确接受该缺口并记录到风险清单。

8.2 nfos-monitor-tunnel.service —— 仓库里有,线上未安装

  • 事实:systemctl is-active nfos-monitor-tunnel.service → inactive;is-enabled → not-found; 在 /etc/systemd/system、/lib/systemd/system、/usr/lib/systemd/system 下按 *monitor* 搜索, 只命中 cloudmonitor.service、lvm2-monitor.service(两份),搜不到任何 *monitor-tunnel* fragment。
  • 该 unit 的用途(仓库文本):次节点 → 主节点的监控反向隧道,在 10.89.0.1 上转发 nfarmsd api 9990 / metrics 9992 / node_exporter 9100, 供运行在 nfos.net 容器内的 Prometheus 采集(容器自身回环不可达宿主回环)。
  • 结论:这条监控反向隧道在采集时刻未安装。与 8.1 叠加的效果是:不仅告警链路没有自检,采集侧本身也缺一条已被设计好的通路。

8.3 state/maintenance-revert.sh —— 仓库里没有副本,且线上只读账号读不到

  • 事实:ExecStart 指向 /opt/Cy/services/system.d/nfos/state/maintenance-revert.sh; 该文件 mode 为 -rwxr-x--- root root,只读采集账号 head 返回 Permission denied(内容从未被读出)。
  • 仓库侧:deploy/nfos/ 下没有 state/ 目录(deploy/nfos/state 不存在),快照 script_repo_path 为 null。
  • 结论:这是本页最硬的一条 —— 该脚本没有版本化副本,节点一旦损毁即不可恢复; 而它偏偏是「维护窗口结束自动还原 policy.observe_only」的唯一执行者(见第 7 节:还原失败会让系统静默停留在告警压制状态)。
  • 处置方向(不在本页范围内):由有权账号把该脚本正文脱敏后纳入仓库版本管理,或至少做一次离线加密归档。

9. 仓库与线上差异对照

对照口径:仓库 deploy/nfos/systemd/(26 个文件,全平铺)↔ 主节点 /etc/systemd/system/(采集时刻)。

方向 数量 清单
线上有、仓库没有(units_live_only) 12 个 unit(含 6 个 timer) nfos-cert-renew.service、nfos-cert-renew.timer、nfos-cert-sync.service、nfos-cert-sync.timer、nfos-logrotate.service、nfos-logrotate.timer、nfos-maintenance-revert.service、nfos-maintenance-revert.timer、nfos-probe.service、nfos-probe.timer、nfos-security.service、nfos-security.timer
仓库有、线上没有(units_repo_only) 3 个 unit nfos-alert-selftest.service、nfos-alert-selftest.timer、nfos-monitor-tunnel.service

这意味着:6 条最关键的「分钟级防线 + 证书 + 轮转 + 维护窗口」timer(security / probe / cert-renew / cert-sync / logrotate / maintenance-revert) 在仓库里都没有 unit 副本——改坏了没有仓库侧参照,只能靠 state/unit-backups/ 的现场备份(见第 11 节)。

主节点 NRestarts 基线(采集时刻,用于判断「服务是否在被反复拉起」):

单元 NRestarts ActiveState / SubState ExecMainStartTimestamp
nfarmsd-api.service 0 active / running Mon 2026-09-28 00:20:00 CST
nfarmsd-gateway.service 0 active / running Mon 2026-09-28 00:20:02 CST
nfarmsd-worker.service 0 active / running Mon 2026-09-28 00:20:04 CST
nfarmsd-metrics.service 0 active / running Mon 2026-09-28 00:20:06 CST

四者同为 0 且启动时间同批(00:20:0x,与 nfos-logrotate.timer 当日 00:21 触发同窗口),说明采集时没有崩溃重启循环; 后续若该值增长,应优先查 nfos-logrotate 窗口与跨云隧道(nfos-tunnel.service)抖动。

两条易被忽略的差异备注:

  1. nfos-log-audit.timer 的 OnCalendar 线上为 *-*-* *:07:00(小时级),仓库注释里记录它曾从「每天 03:20」改到小时级,现场留有 unit 备份 state/unit-backups/nfos-log-audit.timer.bak-20260926-170808。
  2. nfos-ticket-notify.{service,timer} 于 2026-09-26 现场改过,备份为 state/unit-backups/nfos-ticket-notify.{service,timer}.bak-20260926-164409。
  3. nfos-tunnel.service 线上 enabled 且在跑,但它没有 timer(Type=simple、Restart=always、StartLimitIntervalSec=0 的常驻单元),所以不计入本页 17 个 timer。

10. 怎么手工触发 / 怎么排障

10.1 先看全局

# 17 个 timer 的下一跳、上一次触发、对应 service
systemctl list-timers 'nfos-*' --all --no-pager

# 单个 timer 的展开后调度(含单调计时器与日历两种形态)
systemctl show nfos-security.timer -p Id -p Unit -p TimersMonotonic -p TimersCalendar -p Persistent
systemctl show nfos-admin-password.timer -p Id -p TimersCalendar -p TimersMonotonic

10.2 手工触发一次

# 推荐:触发 service(oneshot 会同步跑完;timer 的 LastTrigger 不会因此更新)
sudo systemctl start nfos-security.service
sudo systemctl start nfos-log-audit.service
sudo systemctl start nfos-backup.service

# 也可触发 timer 本身(等价立刻到点)
sudo systemctl start nfos-cert-sync.timer

单次执行期间可用 systemctl status <unit> 观察 Active: activating (start),或直接看日志尾部。

10.3 看日志

# 只进 journald 的两个单元
journalctl -u nfos-admin-password.service -n 50 --no-pager
journalctl -u nfos-logrotate.service    -n 50 --no-pager

# 其余单元的 unit 级日志(无论有没有 .log 文件都可用)
journalctl -u nfos-security.service      -n 50 --no-pager
journalctl -u nfos-maintenance-revert.service -n 50 --no-pager

# 文件日志(真实路径,注意目录是 logs/backend/nfos/)
tail -n 50 /opt/Cy/services/logs/backend/nfos/security-guard.log
tail -n 50 /opt/Cy/services/logs/backend/nfos/probe.log        # 注意:当前 0 字节
tail -n 50 /opt/Cy/services/logs/backend/nfos/ticket-notify.log
tail -n 50 /opt/Cy/services/logs/backend/nfos/alert-sweep.log
tail -n 50 /opt/Cy/services/logs/backend/nfos/log-audit.log
tail -n 50 /opt/Cy/services/logs/backend/nfos/digest.log
tail -n 50 /opt/Cy/services/logs/backend/nfos/report.log
tail -n 50 /opt/Cy/services/logs/backend/nfos/backup.log
tail -n 50 /opt/Cy/services/logs/backend/nfos/cert-renew.log
tail -n 50 /opt/Cy/services/logs/backend/nfos/cert-sync.log
tail -n 50 /opt/Cy/services/logs/backend/nfos/maintenance.log

10.4 看状态文件与产物

ls -l /opt/Cy/services/system.d/nfos/state/
cat  /opt/Cy/services/system.d/nfos/state/digest.state.json
cat  /opt/Cy/services/system.d/nfos/state/ticket-notify-state.json
cat  /opt/Cy/services/system.d/nfos/state/cert-sync.state
cat  /opt/Cy/services/system.d/nfos/state/log-audit.state.json
# 口令轮换状态(只读 JSON,切勿打印口令文件内容)
cat  /opt/Cy/services/system.d/nfos/state/admin-password-rotation.json

# 指标产物
ls -l /opt/Cy/services/system.d/nfos/monitoring/textfile/
# 备份产物
ls -l /home/ecs-user/temp/ ; tail -n 30 /home/ecs-user/temp/last-backup.log
# 证书备份
ls -l /opt/Cy/certs/backup/
# unit 文件备份(回滚用)
ls -l /opt/Cy/services/system.d/nfos/state/unit-backups/

10.5 排障顺序(建议)

  1. systemctl list-timers 'nfos-*' --all —— 先确认 timer 本身还在跳(NEXT 有值、LAST 在推进);
  2. systemctl status <svc> —— 看是否 failed、退出码与 Restart 计数;
  3. journalctl -u <svc> -n 50 --no-pager —— 看单元级错误(含 systemd 自己的超时/权限报错);
  4. 对应 .log 文件 —— 看脚本自己的业务日志;
  5. 状态文件与产物 —— 判断「跑了但没做事」还是「根本没跑」(幂等键没推进 = 脚本提前幂等退出或未执行)。

10.6 改完 unit 一定要 reload

单位文件(.service / .timer)任何改动后必须让 systemd 重新读取,否则改动不生效:

sudo systemctl daemon-reload

# 改了 .timer:重新启用一次并核对下一跳
sudo systemctl restart nfos-<name>.timer
systemctl list-timers 'nfos-<name>*' --all --no-pager

# 改了 .service:不需要 restart timer,但如需立即验证就跑一次
sudo systemctl start nfos-<name>.service

注意:daemon-reload 不会自动重启已在运行的 timer 的调度;改 OnCalendar 后请用 restart(或 daemon-reload + try-restart)确认 NEXT 已按新表达式变化。


11. 回滚方式(unit 文件改动)

11.1 铁律:备份绝不放在 /etc/systemd/system/

  • 备份目录统一放 /opt/Cy/services/system.d/nfos/state/unit-backups/(现场既有实践,见该目录下 nfos-log-audit.timer.bak-20260926-170808、nfos-ticket-notify.*.bak-20260926-164409)。
  • 绝对不要把 .bak / ~ / .orig 之类的副本留在 /etc/systemd/system/ 里: 那个目录被完整性巡检盯着,会在 /etc/systemd/system/ 下出现多余 fragment 时触发 PS-002 完整性告警, 把「一次正常的备份动作」变成一次安全事件。
  • 命名约定(现场既有格式):<unit 文件名>.bak-YYYYMMDD-HHMMSS。

11.2 改动前先备份

TS=$(date +%Y%m%d-%H%M%S)
BK=/opt/Cy/services/system.d/nfos/state/unit-backups
sudo install -d -m 755 "$BK"
sudo cp -a /etc/systemd/system/nfos-<name>.service "$BK/nfos-<name>.service.bak-$TS"
sudo cp -a /etc/systemd/system/nfos-<name>.timer   "$BK/nfos-<name>.timer.bak-$TS"
ls -l "$BK" | grep 'nfos-<name>'

11.3 回滚命令模板

# 1) 选好要还原的备份(时间戳从 ls 里挑)
BK=/opt/Cy/services/system.d/nfos/state/unit-backups
ls -l "$BK" | grep 'nfos-<name>'

# 2) 还原(unit 文件属主/权限保持 root:root 0644)
sudo cp -a "$BK/nfos-<name>.service.bak-<YYYYMMDD-HHMMSS>" /etc/systemd/system/nfos-<name>.service
sudo cp -a "$BK/nfos-<name>.timer.bak-<YYYYMMDD-HHMMSS>"   /etc/systemd/system/nfos-<name>.timer
sudo chown root:root /etc/systemd/system/nfos-<name>.{service,timer}
sudo chmod 644       /etc/systemd/system/nfos-<name>.{service,timer}

# 3) 让 systemd 重新读取 + 重启 timer/service
sudo systemctl daemon-reload
sudo systemctl restart nfos-<name>.timer
sudo systemctl restart nfos-<name>.service   # 若是常驻单元或需立即验证

# 4) 核对:调度表达式与下一次触发是否回到预期
systemctl show nfos-<name>.timer -p Id -p TimersCalendar -p TimersMonotonic -p Persistent
systemctl list-timers 'nfos-<name>*' --all --no-pager
systemctl status nfos-<name>.service --no-pager

11.4 回滚后自检清单

  1. systemctl list-timers 'nfos-<name>*' --all —— NEXT 有值、LAST 会在一个周期内推进;
  2. journalctl -u nfos-<name>.service -n 50 --no-pager —— 无新的 Permission denied / 参数错误;
  3. 对应 state/ 状态文件与日志文件在下一轮被写入(幂等键没推进 = 没真正跑起来);
  4. ls /etc/systemd/system/ | grep '\.bak' 应为空——若不为空,说明有人把备份留在了原地,需立刻移走(PS-002 风险)。

12. 相关文档

  • 脚本库总览 —— 本页所在目录的入口,NFOS 运维脚本的分类总览与索引。
  • NFarms 备份机制规范(backup) —— 备份目标、阿里云盘上传、失败重试与恢复演练的规范口径; nfos-backup.timer(每日 02:00)与其中的调度设计对应,冲突时以规范文档为准。