Startup scripts
os.startup_scripts створює bounded systemd one-shot work для resulting image. Кожен entry оголошує shell command, required packages, execution user і ordering unit.
Startup scripts , root-capable code, unless run_as says otherwise. Вони мають отримати той самий review, що й any installation script.
Canonical shape
{
"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"
}
]
}
}Fields , description і command, not legacy script field. run_as і after also use snake_case. after , one systemd unit string, not an array.
Schema accepts at most 32 entries і bounded command size. Validation rejects empty commands і NUL bytes, but it does not make shell content safe or idempotent.
Design for retries and partial failure
Boot can be interrupted після деяких side effects. Write scripts so another invocation either completes safely або exits з clear, inspectable state.
Good patterns include:
- write to temporary file, verify it, then rename atomically;
- check whether users, directories, or configuration entries already exist;
- use
installfor explicit owner and mode; - apply
set -Eeuo pipefailand handle expected nonzero results deliberately; - use bounded network timeouts and a finite retry count; and
- write readiness marker only after all required steps succeed.
Do not rely on sleep as readiness check. Probe the actual dependency.
External downloads
Avoid curl ... | sh. If first boot must fetch an artifact:
- use HTTPS with certificate verification;
- pin the expected artifact or source version;
- verify a cryptographic digest or approved signature before execution;
- set connect and total timeouts;
- fail closed if verification fails; and
- avoid logging credentials or signed URLs.
For truly offline or reproducible behavior, include reviewed content in the image or an approved package repository instead of downloading on first boot.
Secrets
Never embed plaintext credentials in the recipe, command, URL, or generated marker. Recipe JSON and build logs are retained evidence and can be visible to operators. Use an approved deployment-time enrollment or secret-delivery mechanism and scope the resulting credential to the target.
Execution identity
Prefer an unprivileged service account. If root is required, reduce the command to the smallest privileged step and set explicit file ownership. Confirm that run_as names an account created before the unit starts.
Verification
Test outcomes rather than only the unit’s exit status:
{
"type": "file_contains",
"description": "The startup unit wrote its readiness marker.",
"params": {
"path": "/var/lib/example/build-ready",
"content": "ready"
}
}Also test a second boot, an unavailable dependency, and recovery from an interrupted first run. Inspect systemctl status and the unit journal on failure.