启动脚本
os.startup_scripts 为生成的镜像创建有上限的 systemd 一次性任务。每条记录声明一条 shell 命令、所需软件包、执行用户和排序单元。
除非 run_as 另有说明,启动脚本都是具备 root 能力的代码。它们必须接受与任何安装脚本相同的审查。
规范形状
{
"os": {
"startup_scripts": [
{
"name": "write-build-marker",
"description": "Create a local readiness marker after networking is available.",
"command": "set -Eeuo pipefail\ninstall -d -m 0755 /var/lib/example\nprintf '%s\\n' ready > /var/lib/example/build-ready",
"packages": [],
"run_as": "root",
"after": "network-online.target"
}
]
}
}字段是 description 和 command,不是旧的 script 字段。run_as 和 after 也使用 snake_case。after 是一个 systemd 单元字符串,不是数组。
模式最多接受 32 条记录和有上限的命令大小。校验会拒绝空命令和 NUL 字节,但不会让 shell 内容变得安全或幂等。
为重试和部分失败做设计
引导可能在部分副作用已经发生后被中断。编写脚本,使再次调用要么安全完成,要么以可检查的明确状态退出。
良好模式包括:
- 写入临时文件,验证后再原子重命名;
- 检查用户、目录或配置项是否已经存在;
- 使用
install明确所有者和模式; - 应用
set -Eeuo pipefail,并有意处理预期的非零结果; - 使用有上限的网络超时和有限的重试次数; 以及
- 仅在所有必需步骤成功后写入就绪标记。
不要把 sleep 当作就绪检查。探测真正的依赖。
外部下载
避免 curl ... | sh。如果首次引导必须获取产物:
- 使用带证书验证的 HTTPS;
- 固定期望的产物或源版本;
- 在执行前验证加密摘要或已批准的签名;
- 设置连接超时和总超时;
- 验证失败时以关闭失败退出; 以及
- 避免记录凭据或带签名的 URL。
若要真正离线或可复现,把经过审查的内容放进镜像或已批准的软件包仓库,而不是在首次引导时下载。
机密
切勿把明文凭据嵌入配方、命令、URL 或生成的标记。配方 JSON 和构建日志会被保留为证据,并可能对操作员可见。使用已批准的部署时登记或机密投递机制,并把得到的凭据限定到目标。
执行身份
优先使用无特权服务账户。如果必须使用 root,把命令缩小到最小特权步骤,并设置明确的文件所有权。确认 run_as 指向在单元启动之前已创建的账户。
验证
测试结果,而不只是单元的退出状态:
{
"type": "file_contains",
"description": "The startup unit wrote its readiness marker.",
"params": {
"path": "/var/lib/example/build-ready",
"content": "ready"
}
}还要测试第二次引导、不可用的依赖,以及从中断的首次运行中恢复。失败时检查 systemctl status 和单元日志。