三库存储迁移 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。