From 6175dfb3ac3cdeb698cd18145b66fe513b60fe2c Mon Sep 17 00:00:00 2001 From: "michael.hudson@canonical.com" Date: Mon, 23 Feb 2026 13:56:37 +1300 Subject: [PATCH] build-livefs-lxd: helper to run build-livefs inside an LXD VM MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Creates (or reuses) a per-suite LXD VM, mounts the livecd-rootfs checkout into it, waits for the VM to be ready, installs dependencies, and runs build-livefs inside the VM — keeping the host clean. Co-Authored-By: Claude Sonnet 4.6 --- live-build/build-livefs-lxd | 126 ++++++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100755 live-build/build-livefs-lxd diff --git a/live-build/build-livefs-lxd b/live-build/build-livefs-lxd new file mode 100755 index 00000000..608e1f9b --- /dev/null +++ b/live-build/build-livefs-lxd @@ -0,0 +1,126 @@ +#!/usr/bin/env python3 + +import pathlib +import subprocess +import time + +import click + + +@click.command( + context_settings={"ignore_unknown_options": True, "allow_extra_args": True} +) +@click.option("--suite", required=True, help="Ubuntu suite/series (e.g. noble)") +@click.option( + "--vm-name", + default=None, + help="LXD VM name (default: livefs-builder-{suite})", +) +@click.argument("extra_args", nargs=-1, type=click.UNPROCESSED) +def main(suite, vm_name, extra_args): + livecd_rootfs_root = pathlib.Path(__file__).resolve().parent.parent + vm_name = vm_name or f"livefs-builder-{suite}" + host_conf = ( + pathlib.Path.home() / ".config" / "livecd-rootfs" / "build-livefs.conf" + ) + + result = subprocess.run(["lxc", "info", vm_name], capture_output=True) + if result.returncode != 0: + subprocess.run( + [ + "lxc", "launch", f"ubuntu-daily:{suite}", vm_name, "--vm", + "--config", "limits.cpu=4", + "--config", "limits.memory=8GiB", + "--device", "root,size=100GiB", + ], + check=True, + ) + + device_info = subprocess.run( + ["lxc", "config", "device", "show", vm_name], + capture_output=True, + text=True, + check=True, + ).stdout + if "livecd-rootfs" not in device_info: + subprocess.run( + [ + "lxc", + "config", + "device", + "add", + vm_name, + "livecd-rootfs", + "disk", + f"source={livecd_rootfs_root}", + "path=/srv/livecd-rootfs", + ], + check=True, + ) + + info = subprocess.run( + ["lxc", "info", vm_name], capture_output=True, text=True, check=True + ).stdout + if "Status: STOPPED" in info: + subprocess.run(["lxc", "start", vm_name], check=True) + + for _ in range(30): + result = subprocess.run( + ["lxc", "exec", vm_name, "--", "true"], capture_output=True + ) + if result.returncode == 0: + break + time.sleep(2) + else: + raise click.ClickException(f"VM {vm_name!r} did not become ready in time") + + subprocess.run( + ["lxc", "exec", vm_name, "--", "cloud-init", "status", "--wait"], check=True + ) + + subprocess.run( + ["lxc", "exec", vm_name, "--", "apt-get", "install", "-y", "livecd-rootfs"], + check=True, + ) + + if host_conf.exists(): + subprocess.run( + [ + "lxc", + "exec", + vm_name, + "--", + "mkdir", + "-p", + "/root/.config/livecd-rootfs", + ], + check=True, + ) + subprocess.run( + [ + "lxc", + "file", + "push", + str(host_conf), + f"{vm_name}/root/.config/livecd-rootfs/build-livefs.conf", + ], + check=True, + ) + + subprocess.run( + [ + "lxc", + "exec", + vm_name, + "--", + "/srv/livecd-rootfs/live-build/build-livefs", + "--suite", + suite, + *extra_args, + ], + check=True, + ) + + +if __name__ == "__main__": + main()