#!/usr/bin/python3 """ Execute commands on first-boot 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. """ import os import sys import osbuild.api SCHEMA = """ "additionalProperties": false, "required": ["commands"], "properties": { "commands": { "type": "array", "description": "The command lines to execute", "items": { "type": "string" } }, "wait_for_network": { "type": "boolean", "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 = osbuild.api.arguments() r = main(args["tree"], args["options"]) sys.exit(r)