#!/usr/bin/python3 """ Assemble a file system tree into a ostree commit Takes a file system tree that is already conforming to the ostree system layout[1] and commits it to an archive repository. The repository is located at the `/repo` directory and additional metadata is stored in `/compose.json` which contains the commit compose information. Alternatively, if the `tar` option is supplied, the repository and the `compose.json` will be archived in a file named via the `tar.filename` option. This supports auto-compression via the file extension (see the tar man page). Requires the `tar` command to be in the build root. [1] https://ostree.readthedocs.io/en/stable/manual/adapting-existing/ """ import json import os import subprocess import sys import tempfile from osbuild.util import ostree SCHEMA = """ "additionalProperties": false, "required": ["ref"], "properties": { "ref": { "description": "OStree ref to create for the commit", "type": "string", "default": "" }, "tmp-is-dir": { "description": "Create a regular directory for /tmp", "type": "boolean", "default": true }, "parent": { "description": "commit id of the parent commit", "type": "string" }, "tar": { "description": "Emit a tarball as the result", "type": "object", "additionalProperties": false, "required": ["filename"], "properties": { "filename": { "description": "File-name of the tarball to create. Compression is determined by the extension.", "type": "string" } } } } """ TOPLEVEL_DIRS = ["dev", "proc", "run", "sys", "sysroot", "var"] TOPLEVEL_LINKS = { "home": "var/home", "media": "run/media", "mnt": "var/mnt", "opt": "var/opt", "ostree": "sysroot/ostree", "root": "var/roothome", "srv": "var/srv", } def copy(name, source, dest): subprocess.run(["cp", "--reflink=auto", "-a", os.path.join(source, name), os.path.join(dest, name)], check=True) def init_rootfs(root, tmp_is_dir): """Initialize a pristine root file-system""" fd = os.open(root, os.O_DIRECTORY) os.fchmod(fd, 0o755) for d in TOPLEVEL_DIRS: os.mkdir(d, mode=0o755, dir_fd=fd) os.chmod(d, mode=0o755, dir_fd=fd) for l, t in TOPLEVEL_LINKS.items(): # /l -> t os.symlink(t, l, dir_fd=fd) if tmp_is_dir: os.mkdir("tmp", mode=0o1777, dir_fd=fd) os.chmod("tmp", mode=0o1777, dir_fd=fd) else: os.symlink("tmp", "sysroot/tmp", dir_fd=fd) def main(tree, output_dir, options, meta): ref = options["ref"] tmp_is_dir = options.get("tmp-is-dir", True) parent = options.get("parent", None) tar = options.get("tar", None) with tempfile.TemporaryDirectory(dir=output_dir) as root: print("Initializing root filesystem", file=sys.stderr) init_rootfs(root, tmp_is_dir) print("Copying data", file=sys.stderr) copy("usr", tree, root) copy("boot", tree, root) copy("var", tree, root) for name in ["bin", "lib", "lib32", "lib64", "sbin"]: if os.path.lexists(f"{tree}/{name}"): copy(name, tree, root) repo = os.path.join(output_dir, "repo") subprocess.run(["ostree", "init", "--mode=archive", f"--repo={repo}"], stdout=sys.stderr, check=True) treefile = ostree.Treefile() treefile["ref"] = ref argv = ["rpm-ostree", "compose", "commit"] argv += [f"--repo={repo}"] if parent: argv += [f"--parent={parent}"] argv += [ f"--add-metadata-string=rpmostree.inputhash={meta['id']}", f"--write-composejson-to={output_dir}/compose.json" ] with treefile.as_tmp_file() as path: argv += [path, root] subprocess.run(argv, stdout=sys.stderr, check=True) if tar: filename = tar["filename"] command = [ "tar", "--remove-files", "--auto-compress", "-cf", os.path.join(output_dir, filename), "-C", output_dir, "repo", "compose.json" ] subprocess.run(command, stdout=sys.stderr, check=True) if __name__ == '__main__': args = json.load(sys.stdin) r = main(args["tree"], args["output_dir"], args["options"], args["meta"]) sys.exit(r)