跳转至

三库存储迁移 YAML → SQLite(migrate-libs-to-sqlite.py)

仓库路径:deploy/nfos/migrate-libs-to-sqlite.py | 线上路径:/opt/Cy/services/system.d/nfos/migrate-libs-to-sqlite.py 语言:python3 | 定时器:无(一次性迁移/校验脚本)


用途

把「特征库 / 规则库 / 模板库 + 全局策略」的存储从 YAML 换到 SQLite:

  • SQLite(/opt/Cy/services/system.d/nfos/libs.db)成为唯一事实来源;
  • 三个 YAML 变成 lib-ops.sh 每次写入后自动导出的兼容视图,因此 security-guard.sh(在线防线,每 60s)、gen-waf-from-libs.py、log-audit.py、nfarmsd 的 /libs/* 读取路径零改动依旧可用。

它不是「读取方重写」,只做存储搬迁 + 校验。

为什么把 DDL / 字段映射放在 lib-ops.sh 里

本脚本从 lib-ops.sh 内部抽取 python 核心来跑(load_core():按 cat > "$TMP" <<'NFOS_LIB_OPS_PY' 起始标记与 NFOS_LIB_OPS_PY 结束标记切出核心,exec 到独立命名空间,__name__ 非 __main__ 因此不触发它的 main())。

理由:迁移时的「全量写入」与日常 lib-ops 写操作后的「增量同步」必须写出完全一样的行;在两处各写一份字段映射,早晚会漂移(例如将来特征库加一个字段,只改了一边),漂移的结果正是本平台最怕的「平台看到的 ≠ 检测器读到的」。所以字段映射与 DDL 只有一份实现(lib-ops.sh 的 sqlite_rebuild())。

独立性

本脚本不调用核心的校验函数,而是自己读 DB、自己读 YAML 做深比较——源码注释写明:「同一段代码自己检查自己不算证据」。

幂等

重复执行 = 用当前 YAML 重建整库(INSERT ... ON CONFLICT(id) DO UPDATE + 删除多余行 + 原子替换)。本脚本默认跑两遍并比较「内容摘要」(content_digest(),不含时间戳)来证明幂等。

回滚(源码头部逐字)

sudo rm -f /opt/Cy/services/system.d/nfos/libs.db
sudo cp -p /opt/Cy/services/system.d/nfos/libs.db.bak-<时间戳> .../libs.db   # 需要旧库时
# 三库 YAML 全程未被本脚本修改;彻底回退只需删掉 libs.db,nfarmsd 自动回退读 YAML

何时用

  • 首次落地 SQLite 存储时:跑一次完成迁移(sudo -n python3 migrate-libs-to-sqlite.py)。
  • 只想校验现有 libs.db、不做迁移:--check(只校验,不写)。
  • 沙箱演练:把 --libs-dir / --db / --lib-ops 全指向 /var/tmp/...,不碰生产。
  • 库内容演进后对基线:用 --expect 覆盖条目数;要「不一致即失败」加 --strict。
  • 想省时间:--once(只重建一次,跳过幂等证明)。

命令与参数

以下参数逐字取自 main() 的 argparse 定义。

参数 类型 默认值 说明
--libs-dir 路径 /opt/Cy/services/system.d/nfos 三库目录(默认 …)
--db 路径 ""(空) libs.db 路径(默认 <libs-dir>/libs.db)
--lib-ops 路径 ""(空) lib-ops.sh 路径(默认 <libs-dir>/lib-ops.sh)
--expect 字符串 27,27,10 迁移基线条目数 signatures,rules,templates(默认 …)
--strict 开关 False 条目数与 --expect 不符时按失败处理
--check 开关 False 只校验现有 libs.db,不做迁移
--once 开关 False 只重建一次(默认重建两次以证明幂等)

共 7 个参数。

处理顺序(源码):args.db / args.lib_ops 为空时按 <libs-dir> 推导;--libs-dir / --db / --lib-ops 都取 os.path.abspath。

不带参数时的默认行为

不 dry-run,直接迁移生产路径(/opt/Cy/services/system.d/nfos):打印 libs_dir / libs.db / lib-ops / 预期条目,做迁移前快照(三个 YAML 的 sha256 + libs.db 的 sha256)→ 读三个 YAML → 建表 + 全量写入(调 lib-ops.sh 核心的 sqlite_rebuild,默认两遍)→ 独立校验(verify_independent)→ 迁移后快照对比。

重要:不带参数就是真迁移。要点到为止请显式加 --check 或把三个路径指向沙箱。

示例

M=/opt/Cy/services/system.d/nfos/migrate-libs-to-sqlite.py

# 1) 只校验现有 libs.db,不写任何东西(最安全的第一步)
sudo -n python3 "$M" --check

# 2) 生产迁移(源码用法给出的形式)
sudo -n python3 "$M"

# 3) 沙箱演练:完全不碰生产路径
sudo install -d -m 755 /var/tmp/nfos-migrate
sudo cp -a /opt/Cy/services/system.d/nfos/{signature,rule,template}-library.yml \
           /opt/Cy/services/system.d/nfos/lib-ops.sh /var/tmp/nfos-migrate/
python3 "$M" --libs-dir /var/tmp/nfos-migrate --db /var/tmp/nfos-migrate/libs.db \
        --lib-ops /var/tmp/nfos-migrate/lib-ops.sh

# 4) 库内容已演进:覆盖基线条目数
sudo -n python3 "$M" --check --expect 30,29,11

# 5) 要求「条目数与基线不符即失败」(适合放在门禁/验收里)
sudo -n python3 "$M" --check --expect 30,29,11 --strict

# 6) 快速迁移一次(跳过幂等证明;**不推荐**用于首次迁移)
sudo -n python3 "$M" --once

依赖与权限

项 内容
解释器 python3
第三方模块 PyYAML(必需):read_yaml() 里 import yaml
外部命令 无(不调 sqlite3 CLI)
root 需要(生产路径)。三库与 libs.db 在 /opt/Cy 下;libs.db 是 root:nfarms 0640。源码用法写的是 sudo -n python3 migrate-libs-to-sqlite.py
sudo -n 用法示例里显式写 sudo -n,即要求免密(供非交互场景使用)
读取 <libs-dir>/signature-library.yml、rule-library.yml、template-library.yml、lib-ops.sh(抽取 python 核心)、现有 libs.db
写入 <libs-dir>/libs.db(经 lib-ops.sh 核心的 sqlite_rebuild() 原子替换,并产出 libs.db.bak-<时间戳> 形式的 db_backup);<libs-dir>/state/(核心用的 lib-backups / 基线路径)。三个 YAML 全程只读,本脚本不重写它们
网络 不联网
条目基线 DEFAULT_EXPECT = "27,27,10"(2026-09-23 现网实测值)
表映射 lib_signatures / lib_rules / lib_templates(TABLE_OF);schema 版本由核心给出(v1)

幂等与失败语义

  • 幂等(核心保证):重建 = INSERT ... ON CONFLICT(id) DO UPDATE + 删除多余行 + 原子替换。
  • 幂等(本脚本自证):默认跑两遍并比较 content_digest()(不含 last_sync_at / updated_at 等时间戳);一致 → ok("幂等:连续两次重建的内容摘要一致…"),不一致 → bad("幂等性不成立:两次重建内容摘要不同 …")。--once 会跳过这段证明。
  • YAML 完整性自证:迁移前后逐个比对三个 YAML 的 sha256;任一被改动 → bad("YAML 内容被改动(迁移不应触碰 YAML):…")。
  • 独立校验:verify_independent() 用自己的 sqlite3 连接逐条与 YAML 深比较(不调用核心的校验函数),并核对文件属主/权限(预期 root:nfarms 0640)。
  • 一半状态的风险:低——入库是原子替换,且先有 db_backup 再替换;最坏情况是得到一个「与 YAML 不一致的 libs.db」,此时 --check 会报出来,删掉 libs.db 即回退到 YAML。
  • 缺库文件即中止:任一 YAML 不存在 → 打印 !! 缺少库文件:<路径> 并 return 2(在动 DB 之前)。
  • lib-ops.sh 版本不支持时报错:load_core() 会在找不到起始/结束标记、或核心里没有 sqlite_rebuild() 时抛 RuntimeError(lib-ops.sh 的 python 核心没有 sqlite_rebuild():该版本不支持 SQLite 存储)。
  • 退出码:
退出码 含义
0 成功(打印 MIGRATE_OK);--check 且无 FAIL
1 有 FAIL(MIGRATE_HAS_FAILURES)——迁移/校验的 bad() 被调用过
2 缺少库文件(三个 YAML 之一不存在),在写 DB 之前就中止
非 0(未定义具体值) load_core() 抛 RuntimeError(lib-ops.sh 版本不支持 / 标记缺失),源码未捕获

WARN(例如 --expect 基线不符且没加 --strict)不影响退出码。

常见错误与排查

现象 代码里的对应分支 排查步骤
!! 缺少库文件:<路径>,退出码 2 for k, p in files.items(): if not os.path.exists(p) 检查 --libs-dir;生产为 /opt/Cy/services/system.d/nfos,三个文件名固定为 signature-library.yml / rule-library.yml / template-library.yml
RuntimeError: 在 … 里找不到 python 核心起始标记(cat > "$TMP" <<'NFOS_LIB_OPS_PY') CORE_BEGIN_RE.search(src) 为 None 说明 --lib-ops 指向的不是本仓库版本的 lib-ops.sh(或 heredoc 起始行被改过)。这是本脚本与 lib-ops.sh 的硬契约
RuntimeError: …找不到 python 核心结束标记 NFOS_LIB_OPS_PY src.find("\n" + CORE_END, start) < 0 同上;检查 heredoc 结束行是否被改动
RuntimeError: lib-ops.sh 的 python 核心没有 sqlite_rebuild():该版本不支持 SQLite 存储 if "def sqlite_rebuild(" not in core 节点上的 lib-ops.sh 是旧版;先同步 lib-ops.sh
bad("幂等性不成立:两次重建内容摘要不同 …"),退出码 1 两遍重建的 content_digest() 不一致 说明核心的写入不是幂等的(或 YAML 在两次之间被改)。这是一条真缺陷信号,不要忽略;先在沙箱用示例 3 复现
bad("YAML 内容被改动(迁移不应触碰 YAML):…") 迁移后 sha256 比对 本脚本只读 YAML;出现即说明有并发写入(例如值班同时用平台改库)或核心实现被改
条目数与 --expect 不符但退出码是 0 else: (bad if args.strict else warn)(…) 这是刻意的:--strict 才按失败处理。要门禁化就加 --strict
bad("权限不符合预期:owner=…:… mode=…(应为 root:nfarms 0640)") verify_independent() 的权限校验 ls -l <libs-dir>/libs.db;用 lib-ops.sh 的写路径重建会修正属主/权限
检测器读到的与平台看到的不一致 存储模型:SQLite 优先、漂移时回退 YAML 先 sudo bash <libs-dir>/lib-ops.sh --check-sqlite;不一致则 --sync-sqlite;见 lib-ops.md
missing PyYAML / ModuleNotFoundError: yaml read_yaml() 的 import yaml python3 -c "import yaml";装 python3-pyyaml

回滚方式

本脚本只写 libs.db(及其 db_backup),三个 YAML 全程不动。这是它最好回滚的原因:

改动面 内容 还原方式
libs.db 被重建为与当前 YAML 一致的整库 ① 彻底回退:sudo rm -f <libs-dir>/libs.db → nfarmsd 的 /libs/* 自动回退读 YAML;② 回到旧库:sudo cp -p <libs-dir>/libs.db.bak-<时间戳> <libs-dir>/libs.db
state/(核心用的 lib-backups / 基线) 核心在写入时留快照 按需用 lib-ops.sh 的 {"op":"rollback",…} 回滚单个库,见 lib-ops.md
三个 YAML 未被本脚本修改(脚本自己有 sha256 断言) 无需还原
脚本本体 migrate-libs-to-sqlite.py 用版本化备份覆盖回去

源码头部给出的回滚命令(逐字):

sudo rm -f /opt/Cy/services/system.d/nfos/libs.db
sudo cp -p /opt/Cy/services/system.d/nfos/libs.db.bak-<时间戳> .../libs.db   # 需要旧库时
D=/opt/Cy/services/system.d/nfos
BK=$D/state/unit-backups

# 1) 先备份当前 libs.db(改/删之前)
TS=$(date +%Y%m%d-%H%M%S)
sudo install -d -m 755 "$BK"
sudo cp -a "$D/libs.db" "$BK/libs.db.bak-$TS" 2>/dev/null

# 2) 彻底回退到 YAML 口径(nfarmsd 自动回退读 YAML)
sudo rm -f "$D/libs.db"

# 3) 或回到某个旧库
ls -lt "$D"/libs.db.bak-* | head
sudo cp -p "$D/libs.db.bak-<时间戳>" "$D/libs.db"
sudo chown root:nfarms "$D/libs.db" && sudo chmod 640 "$D/libs.db"

# 4) 回滚脚本本体
sudo cp -a "$D/migrate-libs-to-sqlite.py" "$BK/migrate-libs-to-sqlite.py.bak-$TS"
sudo cp -a "$BK/migrate-libs-to-sqlite.py.bak-<时间戳>" "$D/migrate-libs-to-sqlite.py"
sudo chown root:root "$D/migrate-libs-to-sqlite.py"
sudo chmod 644       "$D/migrate-libs-to-sqlite.py"

# 5) 验收:只校验,不写
sudo -n python3 "$D/migrate-libs-to-sqlite.py" --check
sudo bash "$D/lib-ops.sh" --check-sqlite
ls -l /etc/systemd/system/ | grep '\.bak'    # 必须为空(否则触发 PS-002)

备份落点铁律:脚本/单元/库的任何备份放 /opt/Cy/services/system.d/nfos/state/unit-backups/ (命名 <文件名>.bak-YYYYMMDD-HHMMSS)或仓库侧 /opt/Cy/nfarms/.backup/。 绝不能放在 /etc/systemd/system/ —— 会触发 PS-002 完整性告警。见 timers.md 第 11 节。

⚠️ 删掉 libs.db 是安全的回退(YAML 是完整的兼容视图,nfarmsd 会自动回退), 但不要在同一时刻手工编辑 YAML——本脚本的 sha256 断言假定 YAML 在迁移期间不被改动。

相关 CHANGELOG

CHANGELOG 章节标题
docs/CHANGELOG-2026-09-23.md §10. 三库存储迁移到 SQLite(YAML 降级为兼容视图)
docs/CHANGELOG-2026-09-23.md §E 进行中(本轮未验收)(迁移当时的验收口径)
docs/CHANGELOG-2026-09-23.md §12. 修两个真缺口:主节点邮件腿 + 备节点审计跑不了
docs/CHANGELOG-2026-09-18.md §4. lib-ops.sh 识别新的 detect 类型(迁移前后都必须认识 web_attack / log_anomaly)
docs/CHANGELOG-2026-09-26.md §5.1 事故:库哨兵误判导致安全巡检停摆约 45 秒

三库写入口与 --check-sqlite / --sync-sqlite 见 lib-ops.md; 消费三库的链路见 log-audit.md;定时器全景见 timers.md。