危险操作只有一套语法:PIG 运维 CLI 安全契约
决策日期: 2026-07-02
状态:pg、pb、pt、pitr已于 v1.5.0 交付;2026-08-29 的do与build proxy修订已随 v1.8.1 发布。下述验收修订已实现(Implemented),面向 v1.10.0;源码证据见验证章节,尚未发布。
当前参考:pig pg、pig pb、pig pitr、pig do与pig build
范围: PIG 实现的运维命令;透明上游命令继续采用上游的确认与退出行为。
决策
操作便利性不能模糊操作含义。PIG 明确区分底层原语与多阶段编排器,显式表达破坏性意图, 谨慎分配别名,并要求计划与结构化结果描述的动作必须和文本模式真正执行的动作一致。
最重要的例子是恢复:pig pb restore 是 pgBackRest 原语,pig pitr 则协调 Patroni、
PostgreSQL 停止、恢复、重新启动与恢复后指引。任何别名都不能让这两条路径看起来可以互换。
背景
第一代便利别名积累了不一致的位置参数、确认参数、输出处理与服务语义。
restart、restore、promote、failover 等相似词在不同层次代表完全不同的操作。
跨越层次的短别名可能把看似无害的调用变成缺少编排保护的破坏性原语。
自动化还暴露出假成功问题:包装器输出、子进程输出和结果渲染可能使用不同的成功定义。
考虑过的方案
- 尽可能增加短别名。 冲突和跨层同义词带来的风险高于节省几个字符的价值。
- 给所有看起来危险的单词都加确认。 对透传命令不成立,因为提示与语义必须由上游工具拥有。
- 让编排器成为原语的便利别名。 恢复编排拥有额外的停止、验证与重启不变量,不能合并。
- 启动内层命令后立即返回成功。 结果必须反映 PIG 拥有的完整工作流。
- 将维护命令的库名限制为 ASCII 标识符。 这会拒绝原本支持的空格和非 ASCII 名称。 只拒绝 libpq 连接串语法,并在生成命令时保留位置参数边界。
契约
- 同级命令名称与别名唯一;
- 子命令别名不能遮蔽另一个顶层命令;
- PIG 实现的破坏性操作要求显式确认,并在有意义时提供无副作用计划;
- 命令层在产生副作用前拒绝错误或多余的位置参数;
- 命令专用名称遵循下游 Pigsty 契约;下游 Playbook 没有显式语法时,只采用有文档说明的最窄安全边界;
- 结构化输出与文本模式共享同一个结果和成功定义;
- 含凭据的值不进入诊断输出;就绪或连通性失败必须保持命令失败,不能只记警告后返回成功;
- 底层 restore 不声称管理 Patroni 或 HA 路由;
- PITR 编排器按需停止管理器,检查 PostgreSQL 已停止,执行恢复,可选启动 PostgreSQL, 并有意保持 Patroni 停止,等待运维人员验证;
- 额外原生参数只通过明确记录的边界传递。
2026-10-16 至 2026-10-17 的修订将相同边界用于 v1.10.0 候选版本:
- 跨用户 DBSU 写入以排他方式创建私有临时文件,使用
0600和执行 DBSU 的属主,拒绝符号链接与 非普通文件目标,精确替换目标路径,失败时清理暂存。Linux 使用精确目标重命名;macOS 使用系统 Perl 的rename系统调用,避免 BSDmv将源文件移入目录的语义。 repo rm在变更前校验完整参数列表。只有零参数代表全量备份;all仍只表示node、pgsql、infra。requested_modules保留原始请求,removed_repos描述展开后的执行动作。repo add、repo set、repo info同样整次拒绝显式空名称和逗号空项;只有省略参数 才保留 add/set 默认选择all的行为。 add/set 还会在初始化仓库管理器前校验模块名格式。不安全名称使整次请求失败;格式正确但 不存在的名称仍保留添加模式的部分成功行为,替换模式则在变更前整次拒绝。- 显式指定
pg -D时,SQL 操作和数据库枚举绑定到该目录记录的端口与 socket,元数据不明确时先失败。 省略-D时保留原生客户端的默认连接方式;实例操作的默认目录仍为/pg/data。 显式空字符串或纯空白的-D在配置初始化和操作前失败。 计划、确认重试和验证命令保留相同的目标与操作选项,包括显式的-D /pg/data。VACUUM FULL计划与执行一样要求确认。 2026-10-17 的维护命令修订要求在计划、确认和执行前拒绝连接串形式的数据库参数,且不回显原值。 否则 libpq 连接串仍能覆盖所选主机和端口,并将凭据带入输出。vacuum、analyze、freeze在维护任何数据库前,先校验枚举出的完整库名列表。 生成的vacuum和repack命令将全部选项放在--之前、库名放在其后, 保证--all这样的库名仍表示单个数据库。 - 覆盖非空非托管 fork 目标必须有明确记录目标的匹配元数据,普通非 PGDATA 目录也不例外。
空目标仍可使用,托管 fork 必须按名称替换。私有路径以 DBSU 解析;无法解析的别名仍直接失败。
2026-10-17 的复核撤销额外的
/pg/data禁令,按目标归属、源目标关系和运行状态决定是否允许操作。 约定路径既不能识别所有主库,也可能是合法的 fork 目标。 保留受管孤儿目录的强制清理契约。 非托管 fork 删除的本地路径检查遇到权限不足时,以所选 DBSU 重试;确认目标为目录且不是 符号链接后,才允许继续删除。 - 不能仅凭
EPERM放行pg init --force或 PITR 恢复。独立的可执行程序身份可证明 PID 已被复用; 身份无法确认时仍为未知。向存活 PID 发信号或使用其连接元数据执行 SQL 前,必须确认它属于 PostgreSQL。 未知状态不提供可用于强制停止升级的 PID,fork start 也不能写入成功状态;列表显示unknown并在结构化结果中设置runtime_unknown: true。 角色检测在运行状态不明时同样失败;文件中的角色证据不能推翻已确认的停止状态。 强制初始化分别报告探测失败与正在运行的冲突。 计划前置检查将无法确认的运行状态报告为unknown,不能以 PID 零标记为检查通过。 状态查询区分权限失败(130201,退出码3)与其他探测失败(130808,退出码1); 两类失败中的部分数据均不能证明实例已停止。 - 时间线查询将 SQL 绑定到所选目录有效 PID 元数据中的端口与 socket。无法解析时,使用该目录的
pg_controldata或报告未知时间线;promote 执行过程及必须确认主库角色的成功判据保持不变。 - PITR 取消确认或遇到 EOF 时,在停服务、恢复与启动之前返回确认缺失(
161001,退出码7)。 验收必须执行真实确认路径,并检查生命周期操作的副作用为零。 PIG_NON_INTERACTIVE和结构化执行关闭提示 stdin,并加上 sudo-n,旧式结构化包装也遵循 此规则。原生透传与显式数据 stdin 保留各自契约。- 敏感捕获输出和错误详情保持私有,签发密钥加载失败不回显输入。文档规定的原始导出通道仍然可用。
影响
部分历史缩写被移除,一些脚本需要采用 cluster-first 或显式目标语法。 作为回报,命令名称保留了层次边界,计划对应真实动作,恢复自动化也不会悄悄用原语替换编排器。
计划列出受影响的资源与动作,供用户在执行恢复前审阅。
验证与演进
v1.10.0 已提交修订的固定源码与回归测试证据包括:
运行身份与 SQL 目标、
fork 归属与私有路径、
PITR 取消与未知状态、
计划目标保留、
状态错误分类。这些提交不构成版本发布证据。
后续的维护数据库名与 -- 操作数边界修订
包含 Cobra 参数重解析测试与真实 libpq 目标边界回归测试。
仓库参数预检 在准备密钥或写入仓库前拒绝
不安全模块名,并保留格式正确但模块不存在时的添加部分成功行为。
规范命令契约在 c62c0f5 中进入仓库。
后续提交继续统一别名、早期校验、恢复目标、服务语义与角色检测。
Guard 测试遍历 Cobra 树,拒绝同级与跨层别名冲突;恢复测试覆盖计划、确认、停止升级、旁路恢复、
重启行为与结构化失败结果。
维护命令的回归检查应使用 Cobra 重新解析生成的命令,并让真实 libpq 客户端访问受控本地端点;
仅检查参数列表中包含 -h 和 -p 不足以证明目标绑定正确。
Patroni 后来改成透明透传,这仍遵循同一原则:PIG 只为自己拥有的工作流提供安全保证。
同一契约在 3e1603b
中扩展到 pig do 的名称与集群校验,并在
a880485
中封闭 Ansible 内置目标。软件包驱动、凭据安全且如实报告失败的 build proxy 设置进入
220ef9c,
随后由 74cb128
补齐结构化参数遮盖与机器注解,并在
de7ffd0
中把可选参数同步到机器语法。这些修改均在 Ubuntu 24.04 与 Rocky Linux 9 ARM64 Farrow
客体中完成实机测试,并在 v1.8.1 CI
通过后,从源码提交 1c6f524
正式发布。
当前状态
明确需要 pgBackRest 原语时使用 pig pb restore;需要受控恢复流程时使用 pig pitr。
当前语法与平台要求见对应命令参考页。