diff --git a/stages/org.osbuild.ostree.fillvar b/stages/org.osbuild.ostree.fillvar new file mode 100755 index 00000000..e4bef090 --- /dev/null +++ b/stages/org.osbuild.ostree.fillvar @@ -0,0 +1,124 @@ +#!/usr/bin/python3 +""" +Pre-populate /var directory for a given stateroot. +""" + + +import contextlib +import os +import sys +import subprocess + +import osbuild.api +from osbuild.util import ostree + + +SCHEMA = """ +"additionalProperties": false, +"required": ["deployment"], +"properties": { + "deployment": { + "additionalProperties": false, + "required": ["osname", "ref"], + "properties": { + "osname": { + "description": "Name of the stateroot to be used in the deployment", + "type": "string" + }, + "ref": { + "description": "OStree ref to create and use for deployment", + "type": "string" + }, + "serial": { + "description": "The deployment serial (usually '0')", + "type": "number", + "default": 0 + } + } + } +} +""" + + +class MountGuard(contextlib.AbstractContextManager): + def __init__(self): + self.mounts = [] + + def mount(self, source, target, bind=True, ro=False, mode="0755"): + options = [] + if bind: + options += ["bind"] + if ro: + options += ["ro"] + if mode: + options += [mode] + + args = ["--make-private"] + if options: + args += ["-o", ",".join(options)] + + subprocess.run(["mount"] + args + [source, target], check=True) + self.mounts += [{"source": source, "target": target}] + + subprocess.run(["mount"] + args + [source, target], check=True) + + def unmount(self): + + while self.mounts: + mount = self.mounts.pop() # FILO: get the last mount + target = mount["target"] + subprocess.run(["umount", "--lazy", target], + check=True) + + def __exit__(self, exc_type, exc_val, exc_tb): + self.unmount() + + +def populate_var(sysroot): + # Like anaconda[1] and Fedora CoreOS dracut[2] + # [1] pyanaconda/payload/rpmostreepayload.py + # [2] ignition-ostree-populate-var.sh + + for target in ('lib', 'log'): + os.makedirs(f"{sysroot}/var/{target}", exist_ok=True) + + for target in ('home', 'roothome', 'lib/rpm', 'opt', 'srv', + 'usrlocal', 'mnt', 'media', 'spool', 'spool/mail'): + + if os.path.exists(f"{sysroot}/var/{target}"): + continue + + res = subprocess.run(["systemd-tmpfiles", "--create", "--boot", + "--root=" + sysroot, + "--prefix=/var/" + target], + encoding="utf-8", + stdout=sys.stderr, + check=False) + + # According to systemd-tmpfiles(8), the return values are: + # 0 → success + # 65 → so some lines had to be ignored, but no other errors + # 73 → configuration ok, but could not be created + # 1 → other error + if res.returncode not in [0, 65]: + raise RuntimeError(f"Failed to provision /var/{target}") + + +def main(tree, options): + dep = options["deployment"] + osname = dep["osname"] + ref = dep["ref"] + serial = dep.get("serial", 0) + + deployment = ostree.deployment_path(tree, osname, ref, serial) + + with MountGuard() as mgr: + mgr.mount(f"{deployment}/var", f"{deployment}/var") + populate_var(deployment) + + +if __name__ == '__main__': + stage_args = osbuild.api.arguments() + r = main(stage_args["tree"], + stage_args["options"]) + sys.exit(r)