stage: add org.osbuild.first-boot
This stage runs a given command only on the first boot of the image, useful for doing instantiation tasks that can only be done in the target environment, or that should be done per-instance, rather than per image. Ideally we would use systemd's ConditionFirstBoot for this, but that requires images to ship without an /etc/machine-id, and currently we only support shipping images with an empty /etc/machine-id. Changing this would mean dropping /etc/fstab in favor of mounting the rootfs rw from the initrd. This is likely the right thing to do regardless, but we would have to audit what other first-boot services we would end up with pulling in in this case. Instead we introduce our own flag file /etc/osbuild-first-boot, and use ConditionPathExists. Signed-off-by: Tom Gundersen <teg@jklm.no>
This commit is contained in:
parent
551faf2d61
commit
c2243aee6a
4 changed files with 124 additions and 0 deletions
82
stages/org.osbuild.first-boot
Executable file
82
stages/org.osbuild.first-boot
Executable file
|
|
@ -0,0 +1,82 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
STAGE_DESC = "Execute commands on first-boot"
|
||||
STAGE_INFO = """
|
||||
Sequentially execute a list of commands on first-boot / instantiation.
|
||||
|
||||
This stage uses a logic similar to systemd's first-boot to execute a given
|
||||
script only the first time the image is booted.
|
||||
|
||||
An empty flag file /etc/osbuild-first-boot is written to /etc and a systemd
|
||||
service is enabled that is only run when the file exits, and will remove it
|
||||
before executing the given commands.
|
||||
|
||||
If the flag-file cannot be removed, the service fails without executing
|
||||
any further first-boot commands.
|
||||
"""
|
||||
STAGE_OPTS = """
|
||||
"required": ["commands"],
|
||||
"properties": {
|
||||
"commands": {
|
||||
"type": "array",
|
||||
"description": "The command lines to execute",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"wait_for_network": {
|
||||
"type": "bool",
|
||||
"description": "Wait for the network to be up before executing",
|
||||
"default": false
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
def add_first_boot(tree, commands, wait_for_network):
|
||||
if wait_for_network:
|
||||
network = """Wants=network-online.target
|
||||
After=network-online.target"""
|
||||
else:
|
||||
network = ""
|
||||
|
||||
execs = "\n"
|
||||
for command in commands:
|
||||
execs += f"ExecStart={command}\n"
|
||||
|
||||
service = f"""[Unit]
|
||||
Description=OSBuild First Boot Service
|
||||
ConditionPathExists=/etc/osbuild-first-boot
|
||||
{network}
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
{execs}"""
|
||||
|
||||
os.makedirs(f"{tree}/usr/lib/systemd/system/default.target.wants", exist_ok=True)
|
||||
with open(f"{tree}/usr/lib/systemd/system/osbuild-first-boot.service", "w") as f:
|
||||
f.write(service)
|
||||
os.symlink("../osbuild-first-boot.service",
|
||||
f"{tree}/usr/lib/systemd/system/default.target.wants/osbuild-first-boot.service")
|
||||
|
||||
os.makedirs(f"{tree}/etc", exist_ok=True)
|
||||
open(f"{tree}/etc/osbuild-first-boot", 'a').close()
|
||||
|
||||
def main(tree, options):
|
||||
commands = options["commands"]
|
||||
wait_for_network = options.get("wait_for_network", False)
|
||||
|
||||
commands = ["/usr/bin/rm /etc/osbuild-first-boot"] + commands
|
||||
|
||||
add_first_boot(tree, commands, wait_for_network)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
args = json.load(sys.stdin)
|
||||
r = main(args["tree"], args["options"])
|
||||
sys.exit(r)
|
||||
4
test/stages_tests/first-boot/a.json
Normal file
4
test/stages_tests/first-boot/a.json
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"sources": {},
|
||||
"pipeline": {}
|
||||
}
|
||||
16
test/stages_tests/first-boot/b.json
Normal file
16
test/stages_tests/first-boot/b.json
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"sources": {},
|
||||
"pipeline": {
|
||||
"stages": [
|
||||
{
|
||||
"name": "org.osbuild.first-boot",
|
||||
"options": {
|
||||
"commands": [
|
||||
"/usr/bin/true"
|
||||
],
|
||||
"wait_for_network": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
22
test/stages_tests/first-boot/diff.json
Normal file
22
test/stages_tests/first-boot/diff.json
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"added_files": [
|
||||
"/etc",
|
||||
"/etc/osbuild-first-boot",
|
||||
"/usr",
|
||||
"/usr/lib",
|
||||
"/usr/lib/systemd",
|
||||
"/usr/lib/systemd/system",
|
||||
"/usr/lib/systemd/system/default.target.wants",
|
||||
"/usr/lib/systemd/system/default.target.wants/osbuild-first-boot.service",
|
||||
"/usr/lib/systemd/system/osbuild-first-boot.service"
|
||||
],
|
||||
"deleted_files": [],
|
||||
"differences": {
|
||||
"/": {
|
||||
"mode": [
|
||||
16832,
|
||||
16877
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue