From e2fda5e3792e9dd66e34dda34013ff1e51fa7cb5 Mon Sep 17 00:00:00 2001 From: James Pace Date: Sun, 6 Nov 2022 15:27:07 -0500 Subject: [PATCH] Remove more features I won't be using. --- Makefile | 18 -- osbuildvm/osbuildvm | 394 ------------------------------- osbuildvm/osbuildvm.mpp.yml | 229 ------------------ runvm | 449 ------------------------------------ tools/runosbuild | 4 +- 5 files changed, 1 insertion(+), 1093 deletions(-) delete mode 100755 osbuildvm/osbuildvm delete mode 100644 osbuildvm/osbuildvm.mpp.yml delete mode 100755 runvm diff --git a/Makefile b/Makefile index 5ce3467..5bd05bc 100644 --- a/Makefile +++ b/Makefile @@ -84,7 +84,6 @@ help: @echo \ \ clean_caches: Removes intermediate image build artifacts \(that improve rebuild speed\) @echo \ \ clean_downloads: Removes files downloaded during image builds @echo \ \ clean: Run clean_caches and clean_downloads - @echo \ \ osbuildvm-images: Build a image that can be used to build images inside a VM @echo @echo There are also some common conversion rules: @echo \ \ foo.ext4.simg will build foo.ext4 and then convert it with img2simg @@ -95,9 +94,6 @@ help: @echo custom @suffix to change the name of the produced file. For example: @echo " make cs9-qemu-minimal-ostree@gdb.$(HOST_ARCH).qcow2 DEFINES='extra_rpms=[\"gdb\"]'" @echo - @echo If you pass VM=1, then the images used from \"make osbuildvm-images\" will be used to do the - @echo actual building. This means that you don\'t need sudo rights to run osbuild, and it means - @echo architectures other than the current ones can be built. @echo @echo Available image targets \(for $(HOST_ARCH)\) are: @echo @@ -181,22 +177,8 @@ clean_caches: .PHONY: clean clean: clean_downloads clean_caches -ifeq ($(VM), 1) -VM_SUDO= -VM_OSBUILD="osbuildvm/osbuildvm --arch=$(HOST_ARCH)" -else VM_SUDO=sudo VM_OSBUILD=sudo osbuild -endif - -.PHONY: osbuildvm-images -osbuildvm-images: $(BUILDDIR) - osbuild-mpp osbuildvm/osbuildvm.mpp.yml _build/osbuildvm-$(HOST_ARCH).json - $(VM_OSBUILD) --store $(STOREDIR) --output-directory $(OUTPUTDIR) --export osbuildvm _build/osbuildvm-$(HOST_ARCH).json - cp $(OUTPUTDIR)/osbuildvm/disk.qcow2 _build/osbuildvm-$(HOST_ARCH).img - cp $(OUTPUTDIR)/osbuildvm/initramfs _build/osbuildvm-$(HOST_ARCH).initramfs - cp $(OUTPUTDIR)/osbuildvm/vmlinuz _build/osbuildvm-$(HOST_ARCH).vmlinuz - $(VM_SUDO) rm -rf $(OUTPUTDIR)/osbuildvm %.ext4.simg : %.ext4 img2simg $< $@ diff --git a/osbuildvm/osbuildvm b/osbuildvm/osbuildvm deleted file mode 100755 index 1b41cf7..0000000 --- a/osbuildvm/osbuildvm +++ /dev/null @@ -1,394 +0,0 @@ -#!/usr/bin/python3 - -import argparse -import os -import platform -import select -import shlex -import socket -import subprocess -import sys -import tempfile -import threading -import time -import signal -import shutil -import json - -BLOCK_SIZE = 64*1024 - -def read_manifest(path): - if path == "-": - manifest = sys.stdin.read() - else: - with open(path) as f: - manifest = f.read() - - return manifest - -def parse_arguments(sys_argv): - parser = argparse.ArgumentParser(description="Build operating system images") - - parser.add_argument("manifest_path", metavar="MANIFEST", - help="json file containing the manifest that should be built, or a '-' to read from stdin") - parser.add_argument("--store", metavar="DIRECTORY", type=os.path.abspath, - default=".osbuild", - help="directory where intermediary os trees are stored") - parser.add_argument("--checkpoint", metavar="ID", action="append", type=str, default=None, - help="stage to commit to the object store during build (can be passed multiple times)") - parser.add_argument("--export", metavar="ID", action="append", type=str, default=None, - help="object to export, can be passed multiple times") - parser.add_argument("--output-directory", metavar="DIRECTORY", type=os.path.abspath, - help="directory where result objects are stored") - parser.add_argument("--arch", metavar="ARCH", type=str, default=platform.machine(), - help="Arch to build for") - - return parser.parse_args(sys_argv[1:]) - -def local_osbuild(manifest, opts): - cmd = ['osbuild'] + opts + ['-'] - try: - p = subprocess.run(cmd, check=True, input=manifest.encode("utf8"), capture_output=True ) - except subprocess.CalledProcessError as e: - print(e.output) - sys.exit(e.returncode) - lines = p.stdout.decode("utf8").splitlines() - checkpoints = {} - for l in lines: - p = l.split() - checkpoint = p[0][:-1] - checkpoints[checkpoint] = p[1] - return checkpoints - -def extract_dependencies(manifest): - j = json.loads(manifest) - version = j.get("version", None); - if version != '2': - print(f"Unsupported manifest version {version}, only version 2 supported") - sys.exit(1) - sources = j.get("sources", {}); - curl = sources.get("org.osbuild.curl", {}); - curl_items = curl.get("items", {}); - shas = curl_items.keys() - return list(shas) - -def find_images(arch): - base_image=f"osbuildvm-{arch}.img" - base_kernel=f"osbuildvm-{arch}.vmlinuz" - base_initrd=f"osbuildvm-{arch}.initramfs" - - image_dir=None - image_dirs = [os.getcwd(), os.path.join(os.getcwd(), "_build"), "/usr/share/osbuildvm"] - for i in image_dirs: - if os.path.exists(os.path.join(i, base_image)): - image_dir = i - break - if not image_dir: - print(f"Unable to find {base_image}, tried: {image_dirs}", file=sys.stderr) - sys.exit(1) - - image = os.path.join(image_dir, base_image) - kernel = os.path.join(image_dir, base_kernel) - initrd = os.path.join(image_dir, base_initrd) - return (image, kernel, initrd) - -def qemu_img(*args): - res = subprocess.run(["qemu-img"] + [*args], - stdout=subprocess.PIPE, - check=True) - -class QEmu(object): - def __init__(self, image1, image2, kernel, initrd, arch): - # This is where we store the sockets and the pidfile - self.tmpdir = tempfile.TemporaryDirectory(prefix="tmp-qemu-") - self.args = [] - self.pid = 0 - self.host_arch = platform.machine() - self.arch = arch - - debug_serial = False - - qemu_kvm_path = self.find_qemu() - - self.args.append(qemu_kvm_path) - - # Virtio serial ports - - self.args.extend(["-device", "virtio-serial"]) - out_socket_path = self.add_socket("output") - sync_socket_path = self.add_socket("sync") - stdout_socket_path = self.add_socket("stdout") - - debug_cmdline="quiet loglevel=1" - if debug_serial: - self.args.extend(["-serial", "file:/dev/stdout"]) - debug_cmdline="" - - # Machine details - self.args.extend(["-m", "size=2G", - "-nodefaults", - "-vga", "none", "-vnc", "none"]) - - if self.kvm_supported(): - self.args.extend(["-enable-kvm"]) - - if self.arch=="x86_64": - machine = "q35" - cpu = "qemu64" - if self.arch=="aarch64": - machine = "virt" - cpu = "cortex-a57" - - if self.arch == self.host_arch: - cpu = "host" - - self.args.extend(["-machine", machine, - "-cpu", cpu]) - - if self.arch=="aarch64": - self.args.extend(["-bios", "/usr/share/edk2/aarch64/QEMU_EFI.fd", - "-boot", "efi"]) - - pid_file = os.path.join(self.tmpdir.name, "qemu.pid") - self.args.extend(["-daemonize", "-pidfile" , pid_file, - "-kernel", kernel, - "-initrd", initrd, - "-append", f'root=/dev/vda console=ttyS0 init=/usr/bin/start.sh {debug_cmdline} ro', - "-drive", f"file={image1},index=0,media=disk,format=qcow2,snapshot=on,if=virtio", - "-drive", f"file={image2},index=1,media=disk,format=raw,if=virtio"]) - - p = subprocess.run(self.args, check=True) - - with open(pid_file, "r") as f: - self.pid = int(f.read()) - - self.sock_out = self.connect_socket(out_socket_path) - self.sock_sync = self.connect_socket(sync_socket_path) - self.sock_stdout = self.connect_socket(stdout_socket_path) - - def connect_socket(self, path): - sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - sock.connect(path) - return sock - - def add_socket(self, id): - socket_path = os.path.join(self.tmpdir.name, id + ".sock") - self.args.extend(["-chardev", f"socket,path={socket_path},server=on,wait=off,id={id}", - "-device", f"virtserialport,chardev={id},name={id}"]) - return socket_path - - def find_qemu(self): - if self.arch == self.host_arch: - binary_name = "qemu-kvm" - else: - binary_name = f"qemu-system-{self.arch}" - - - for d in ["/usr/bin", "/usr/libexec"]: - p = os.path.join(d, binary_name) - if os.path.isfile(p): - return p - - print(f"Can't find {binary_name}", file=sys.stderr) - sys.exit(1) - - def kvm_supported(self): - return self.arch == self.host_arch and os.path.exists("/dev/kvm") - - def copy_out(self, destination): - while True: - readable, writable, exceptional = select.select([self.sock_out, self.sock_sync, self.sock_stdout], [], []) - - read_something = False - - if self.sock_stdout in readable: - data = self.sock_stdout.recv(BLOCK_SIZE) - while len(data) > 0: - res = sys.stdout.buffer.write(data) - data = data[res:] - sys.stdout.flush() - read_something = True - - if self.sock_out in readable: - data = self.sock_out.recv(BLOCK_SIZE) - while len(data) > 0: - res = destination.write(data) - data = data[res:] - read_something = True - - if read_something: - continue # Don't exit until there is no more to read from sock or sock_stdout - - # If we had no buffered data in sock and sync_sock is readable that means we copied everything and can exit - if self.sock_sync in readable: - data = self.sock_sync.recv(BLOCK_SIZE) - break - - def kill(self): - if self.pid != 0: - os.kill(self.pid, signal.SIGTERM) - self.pid = 0 - - def __enter__(self): - return self - - def __exit__(self, type, value, traceback): - self.kill() - self.tmpdir.cleanup() - -def create_ext4_image(path, size, root_dir): - with open(path, "w") as f: - f.truncate(size) - - cmd = ["mkfs.ext4", "-d", root_dir, "-E", "no_copy_xattrs,root_owner=0:0", "-O", "^has_journal", path] - try: - p = subprocess.run(cmd, check=True,capture_output=True) - except subprocess.CalledProcessError as e: - print(f"Unable to create ext4 work fs: {e}", file=sys.stderr) - sys.exit(e.returncode) - -def link_or_copy_file(source_path, dest_path): - try: - os.link(source_path, dest_path) - except Exception as e: - shutil.copyfile(source_path, dest_path, follow_symlinks=False) - -def make_work_rootfs(args, tmpdirname, manifest, digests, main_sh, checkpoint_ids): - rootdir = os.path.join(tmpdirname, "root") - os.makedirs(rootdir, exist_ok=True) - - with open (os.open(os.path.join(rootdir, "main.sh"), os.O_CREAT | os.O_WRONLY, 0o775), "w") as mainsh: - mainsh.write(main_sh) - - with open (os.path.join(rootdir, "image.json"), "w") as image_json: - image_json.write(manifest) - - orig_sources_dir = os.path.join(args.store, "sources/org.osbuild.files") - root_sources_dir = os.path.join(rootdir, "osbuild_store/sources/org.osbuild.files") - os.makedirs(root_sources_dir, mode=0o777, exist_ok=True) - root_input_dir = os.path.join(rootdir, "input") - os.makedirs(root_input_dir, mode=0o777, exist_ok=True) - - for digest in digests: - link_or_copy_file(os.path.join(orig_sources_dir, digest), - os.path.join(root_sources_dir, digest)) - - for cp, cp_id in checkpoint_ids.items(): - source_path = os.path.join(args.store, "refs_tars/" + cp_id + ".tar.gz") - if os.path.isfile(source_path): - link_or_copy_file(source_path, - os.path.join(root_input_dir, cp_id + ".tar.gz")) - - return rootdir - -# Moves any files recursively in root_src_dir to root_dst_dir, replacing if needed -# Keeps old files and directories in root_dst_dir -def move_merged(root_src_dir, root_dst_dir): - root_src_dir = os.path.abspath(root_src_dir) - root_dst_dir = os.path.abspath(root_dst_dir) - for src_dir, dirs, files in os.walk(root_src_dir): - dst_dir = src_dir.replace(root_src_dir, root_dst_dir, 1) - if not os.path.exists(dst_dir): - os.makedirs(dst_dir) - for file_ in files: - src_file = os.path.join(src_dir, file_) - dst_file = os.path.join(dst_dir, file_) - if os.path.exists(dst_file): - os.remove(dst_file) - shutil.move(src_file, dst_dir) - -def run_in_vm(args, manifest, digests, tmpdirname, shell_to_run, checkpoint_ids): - image,kernel,initrd = find_images(args.arch) - - rootdir = make_work_rootfs(args, tmpdirname, manifest, digests, shell_to_run, checkpoint_ids) - work_image = os.path.join(tmpdirname, "work.img") - create_ext4_image(work_image, 100*1024*1024*1024, rootdir) - shutil.rmtree(rootdir) - - output_path = args.output_directory - os.makedirs(output_path, exist_ok=True) - - exit_status = 1 - - with tempfile.TemporaryDirectory(prefix="download-", dir=output_path) as output_tmpdir: - with QEmu(image, work_image, kernel, initrd, args.arch) as qemu: - with subprocess.Popen(["tar", "x", "-C", output_tmpdir], stdin=subprocess.PIPE) as proc: - qemu.copy_out(proc.stdin) - exit_status_path = os.path.join(output_tmpdir, "exit_status") - if os.path.isfile(exit_status_path): - with open(exit_status_path, "r") as f: - exit_status = int(f.read()) - - # Move osbuild outout to the real output dir - osbuild_output = os.path.join(output_tmpdir, "osbuild") - if os.path.isdir(osbuild_output): - move_merged(osbuild_output, output_path) - - checkpoints_output = os.path.join(output_tmpdir, "checkpoints") - checkpoints_dest = os.path.join(args.store, "refs_tars") - if os.path.isdir(checkpoints_output): - os.makedirs(checkpoints_dest, exist_ok=True) - for tar in os.listdir(checkpoints_output): - if not os.path.isfile(os.path.join(checkpoints_dest, tar)): - shutil.move(os.path.join(checkpoints_output, tar), - checkpoints_dest) - - sys.exit(exit_status) - -def main(): - args = parse_arguments(sys.argv) - manifest = read_manifest(args.manifest_path) - - print("Running osbuild on host to download files") - checkpoint_ids = local_osbuild(manifest, ['--store', args.store]) - - digests = extract_dependencies(manifest) - - mainsh_data = f'''\ -#!/bin/bash - -mkdir -p /work/osbuild_store -( - echo === Extracting checkpoints in vm === - for tar in $(find /work/input/ -mindepth 1 -print ); do - echo extracting $(basename $tar) - tar xf $tar --acls --selinux --xattrs -C /work/osbuild_store - done - echo === Running osbuild in vm === - osbuild --store /work/osbuild_store --output-directory /work/output/osbuild {' '.join(map(lambda e: "--export " + e, args.export))} {' '.join(map(lambda cp: "--checkpoint " + cp, args.checkpoint))} /work/image.json - RES=$? - echo $RES > /work/output/exit_status - - echo === Osbuild exit status $RES === - - echo === Saving checkpoints === - mkdir -p /work/output/checkpoints - for cp in $(find /work/osbuild_store/refs/ -mindepth 1 -printf "%f "); do - if test -f /work/input/$cp.tar.gz; then - continue - fi - obj=$(basename $(readlink /work/osbuild_store/refs/$cp)) - tar cSf /work/output/checkpoints/$cp.tar.gz --acls --selinux --xattrs -C /work/osbuild_store/ refs/$cp objects/$obj - echo Saved $cp - done -) > /dev/virtio-ports/stdout 2>&1 - -tar cSf /dev/virtio-ports/output -C /work/output ./ - -# Signal output ended -sleep 3 -echo DONE > /dev/virtio-ports/sync - -# Block for it to be fully read -cat /dev/virtio-ports/sync - -''' - - tmpdir = os.path.join(args.store, "tmp") - os.makedirs(tmpdir, exist_ok=True) - - with tempfile.TemporaryDirectory(prefix="osbuild-qemu-", dir=tmpdir) as tmpdirname: - run_in_vm(args, manifest, digests, tmpdirname, mainsh_data, checkpoint_ids) - -if __name__ == "__main__": - main() diff --git a/osbuildvm/osbuildvm.mpp.yml b/osbuildvm/osbuildvm.mpp.yml deleted file mode 100644 index 57bd219..0000000 --- a/osbuildvm/osbuildvm.mpp.yml +++ /dev/null @@ -1,229 +0,0 @@ -version: '2' -mpp-vars: - rootfs_uuid: 86a22bf4-f153-4541-b6c7-0332c0dfaead - rootfs_size: 2147483648 - cs9_baseurl: http://mirror.stream.centos.org/9-stream - cs9_repos: - - id: baseos - baseurl: $cs9_baseurl/BaseOS/$arch/os/ - - id: appstream - baseurl: $cs9_baseurl/AppStream/$arch/os/ - centos_gpg_key: | - -----BEGIN PGP PUBLIC KEY BLOCK----- - Version: GnuPG v2.0.22 (GNU/Linux) - - mQINBFzMWxkBEADHrskpBgN9OphmhRkc7P/YrsAGSvvl7kfu+e9KAaU6f5MeAVyn - rIoM43syyGkgFyWgjZM8/rur7EMPY2yt+2q/1ZfLVCRn9856JqTIq0XRpDUe4nKQ - 8BlA7wDVZoSDxUZkSuTIyExbDf0cpw89Tcf62Mxmi8jh74vRlPy1PgjWL5494b3X - 5fxDidH4bqPZyxTBqPrUFuo+EfUVEqiGF94Ppq6ZUvrBGOVo1V1+Ifm9CGEK597c - aevcGc1RFlgxIgN84UpuDjPR9/zSndwJ7XsXYvZ6HXcKGagRKsfYDWGPkA5cOL/e - f+yObOnC43yPUvpggQ4KaNJ6+SMTZOKikM8yciyBwLqwrjo8FlJgkv8Vfag/2UR7 - JINbyqHHoLUhQ2m6HXSwK4YjtwidF9EUkaBZWrrskYR3IRZLXlWqeOi/+ezYOW0m - vufrkcvsh+TKlVVnuwmEPjJ8mwUSpsLdfPJo1DHsd8FS03SCKPaXFdD7ePfEjiYk - nHpQaKE01aWVSLUiygn7F7rYemGqV9Vt7tBw5pz0vqSC72a5E3zFzIIuHx6aANry - Gat3aqU3qtBXOrA/dPkX9cWE+UR5wo/A2UdKJZLlGhM2WRJ3ltmGT48V9CeS6N9Y - m4CKdzvg7EWjlTlFrd/8WJ2KoqOE9leDPeXRPncubJfJ6LLIHyG09h9kKQARAQAB - tDpDZW50T1MgKENlbnRPUyBPZmZpY2lhbCBTaWduaW5nIEtleSkgPHNlY3VyaXR5 - QGNlbnRvcy5vcmc+iQI3BBMBAgAhBQJczFsZAhsDBgsJCAcDAgYVCAIJCgsDFgIB - Ah4BAheAAAoJEAW1VbOEg8ZdjOsP/2ygSxH9jqffOU9SKyJDlraL2gIutqZ3B8pl - Gy/Qnb9QD1EJVb4ZxOEhcY2W9VJfIpnf3yBuAto7zvKe/G1nxH4Bt6WTJQCkUjcs - N3qPWsx1VslsAEz7bXGiHym6Ay4xF28bQ9XYIokIQXd0T2rD3/lNGxNtORZ2bKjD - vOzYzvh2idUIY1DgGWJ11gtHFIA9CvHcW+SMPEhkcKZJAO51ayFBqTSSpiorVwTq - a0cB+cgmCQOI4/MY+kIvzoexfG7xhkUqe0wxmph9RQQxlTbNQDCdaxSgwbF2T+gw - byaDvkS4xtR6Soj7BKjKAmcnf5fn4C5Or0KLUqMzBtDMbfQQihn62iZJN6ZZ/4dg - q4HTqyVpyuzMXsFpJ9L/FqH2DJ4exGGpBv00ba/Zauy7GsqOc5PnNBsYaHCply0X - 407DRx51t9YwYI/ttValuehq9+gRJpOTTKp6AjZn/a5Yt3h6jDgpNfM/EyLFIY9z - V6CXqQQ/8JRvaik/JsGCf+eeLZOw4koIjZGEAg04iuyNTjhx0e/QHEVcYAqNLhXG - rCTTbCn3NSUO9qxEXC+K/1m1kaXoCGA0UWlVGZ1JSifbbMx0yxq/brpEZPUYm+32 - o8XfbocBWljFUJ+6aljTvZ3LQLKTSPW7TFO+GXycAOmCGhlXh2tlc6iTc41PACqy - yy+mHmSv - =kkH7 - -----END PGP PUBLIC KEY BLOCK----- -pipelines: -- runner: org.osbuild.centos9 - name: build - stages: - - type: org.osbuild.rpm - inputs: - packages: - type: org.osbuild.files - origin: org.osbuild.source - mpp-depsolve: - architecture: $arch - module-platform-id: platform:el9 - baseurl: $cs9_baseurl/BaseOS/$arch/os/ - repos: - mpp-eval: cs9_repos - packages: - - dnf - - e2fsprogs - - policycoreutils - - python3-iniparse - - python39 - - qemu-img - - selinux-policy-targeted - - tar - - xz - options: - gpgkeys: - - mpp-eval: centos_gpg_key - exclude: - docs: true - - type: org.osbuild.selinux - options: - file_contexts: etc/selinux/targeted/contexts/files/file_contexts - labels: - /usr/bin/cp: system_u:object_r:install_exec_t:s0 - /usr/bin/tar: system_u:object_r:install_exec_t:s0 -- name: rootfs - build: name:build - stages: - - type: org.osbuild.rpm - options: - gpgkeys: - - mpp-eval: centos_gpg_key - inputs: - packages: - type: org.osbuild.files - origin: org.osbuild.source - mpp-depsolve: - architecture: $arch - module-platform-id: platform:el9 - baseurl: $cs9_baseurl/BaseOS/$arch/os/ - repos: - mpp-join: - - mpp-eval: cs9_repos - - - id: osbuild - baseurl: https://download.copr.fedorainfracloud.org/results/@osbuild/osbuild/centos-stream-9-$arch - packages: - - bash - - dracut-config-generic - - kernel - - langpacks-en - - selinux-policy-targeted - - net-tools - - osbuild - - osbuild-tools - - osbuild-ostree - excludes: - - dracut-config-rescue - - type: org.osbuild.locale - options: - language: en_US.UTF-8 - - type: org.osbuild.copy - inputs: - inlinefile: - type: org.osbuild.files - origin: org.osbuild.source - mpp-embed: - id: osbuilder.sh - text: | - #!/usr/bin/bash - function clean_up { - systemctl poweroff -f -f - } - trap clean_up EXIT - if grep -q "osbuilder_bash=1" /proc/cmdline; then bash; exit; fi - mount /dev/vdb /work - /work/main.sh - options: - paths: - - from: - mpp-format-string: input://inlinefile/{embedded['osbuilder.sh']} - to: tree:///usr/bin/start.sh - - type: org.osbuild.chmod - options: - items: - /usr/bin/start.sh: - mode: a+x - - type: org.osbuild.mkdir - options: - paths: - - path: /work - - type: org.osbuild.selinux - options: - file_contexts: etc/selinux/targeted/contexts/files/file_contexts - - type: org.osbuild.selinux - options: - file_contexts: etc/selinux/targeted/contexts/files/file_contexts -- name: image - build: name:build - stages: - - type: org.osbuild.truncate - options: - filename: disk.img - size: - mpp-format-string: '{rootfs_size}' - - type: org.osbuild.mkfs.ext4 - devices: - device: - type: org.osbuild.loopback - options: - filename: disk.img - start: 0 - size: - mpp-eval: rootfs_size - options: - uuid: - mpp-eval: rootfs_uuid - label: root - - type: org.osbuild.copy - inputs: - tree: - type: org.osbuild.tree - origin: org.osbuild.pipeline - references: - - name:rootfs - options: - paths: - - from: input://tree/ - to: mount://root/ - devices: - root: - type: org.osbuild.loopback - options: - filename: disk.img - start: 0 - size: - mpp-eval: rootfs_size - mounts: - - name: root - type: org.osbuild.ext4 - source: root - target: / -- name: osbuildvm - build: name:build - stages: - - type: org.osbuild.qemu - inputs: - image: - type: org.osbuild.files - origin: org.osbuild.pipeline - references: - name:image: - file: disk.img - options: - filename: disk.qcow2 - format: - type: qcow2 - compat: '1.1' - - type: org.osbuild.copy - inputs: - rootfs: - type: org.osbuild.tree - origin: org.osbuild.pipeline - references: - - name:rootfs - options: - paths: - - from: - mpp-format-string: input://rootfs/usr/lib/modules/{rpms['rootfs']['kernel-core'].evra}/vmlinuz - to: tree:///vmlinuz - - from: - mpp-format-string: input://rootfs/boot/initramfs-{rpms['rootfs']['kernel-core'].evra}.img - to: tree:///initramfs - - type: org.osbuild.chmod - options: - items: - /initramfs: - mode: a+r diff --git a/runvm b/runvm deleted file mode 100755 index e027045..0000000 --- a/runvm +++ /dev/null @@ -1,449 +0,0 @@ -#!/usr/bin/env python3 - -import argparse -import atexit -import binascii -import http.server -import os -import platform -import select -import shutil -import signal -import socket -import socketserver -import subprocess -import sys -import tempfile -import time - -is_verbose = False -def print_verbose(s): - if is_verbose: - print(s) - -def print_error(s): - print(s, file=sys.stderr) - -def exit_error(s): - print_error(s) - sys.exit(1) - -def bool_arg(val): - return "on" if val else "off" - -def find_qemu(arch): - binary_names = [ f"qemu-system-{arch}" ] - if arch == platform.machine(): - binary_names.append("qemu-kvm") - - for binary_name in binary_names: - if "QEMU_BUILD_DIR" in os.environ: - p = os.path.join(os.environ["QEMU_BUILD_DIR"], binary_name) - if os.path.isfile(p): - return p - else: - exit_error(f"Can't find {binary_name}") - - qemu_bin_dirs = ["/usr/bin", "/usr/libexec"] - if "PATH" in os.environ: - qemu_bin_dirs += os.environ["PATH"].split(":") - - for d in qemu_bin_dirs: - p = os.path.join(d, binary_name) - if os.path.isfile(p): - return p - - exit_error(f"Can't find {binary_name}") - -def qemu_available_accels(qemu): - cmd = qemu + ' -accel help' - info = subprocess.check_output(cmd.split(" ")).decode('utf-8') - accel_list = [] - for accel in ('kvm', 'xen', 'hvf', 'hax', 'tcg'): - if info.find(accel) > 0: - accel_list.append(accel) - return accel_list - -def random_id(): - return binascii.b2a_hex(os.urandom(8)).decode('utf8') - -def machine_id(): - try: - with open("/etc/machine-id", "r") as f: - mid = f.read().strip() - except FileNotFoundError: - if sys.platform == "darwin": - # for macOS - import plistlib - cmd = "ioreg -rd1 -c IOPlatformExpertDevice -a" - plist_data = subprocess.check_output(cmd.split(" ")) - mid = plistlib.loads(plist_data)[0]["IOPlatformUUID"].replace("-","") - else: - # fallback for the other distros - hostname = socket.gethostname() - mid = ''.join(hex(ord(x))[2:] for x in (hostname*16)[:16]) - - return mid - -def generate_mac_address(): - # create a new mac address based on our machine id - data = machine_id() - - maclst = ["FE"] + [data[x:x+2] for x in range(-12, -2, 2)] - return ":".join(maclst) - -def run_http_server(path): - writer, reader = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM) - child_pid = os.fork() - if child_pid == 0: - reader.close() - - # Child - os.chdir(path) - - class HTTPHandler(http.server.SimpleHTTPRequestHandler): - def log_message(self, format, *args): - pass # Silence logs - - httpd = socketserver.TCPServer(("127.0.0.1", 0), HTTPHandler) - writer.send(str(httpd.server_address[1]).encode("utf8")) - writer.close() - - httpd.serve_forever() - sys.exit(0) - - # Parent - writer.close() - atexit.register(os.kill, child_pid,signal.SIGTERM) - - http_port = int(reader.recv(128).decode("utf8")) - reader.close() - - return http_port - -def find_ovmf(args): - dirs = [ - "~/.local/share/ovmf", - "/usr/share/OVMF", - "/usr/share/edk2/ovmf/" - ] - if args.ovmf_dir: - dirs.append(args.ovmf_dir) - - for d in dirs: - path = os.path.expanduser(d) - if os.path.exists(path): - return path - - raise RuntimeError("Could not find OMVF") - -# location can differ depending on how qemu is installed -def find_edk2(): - dirs = [ - "/usr/local/share/qemu", - "/opt/homebrew/share/qemu" - ] - - for d in dirs: - path = os.path.expanduser(d) - if os.path.exists(path): - return path - - raise RuntimeError("Could not find edk2 directory") - -def qemu_run_command(qmp_socket_path, command): - sock2 = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - sock2.connect(qmp_socket_path) - r = sock2.recv(1024) - sock2.send('{"execute":"qmp_capabilities"}\n'.encode("utf8")) - r = sock2.recv(1024) - sock2.send(f'{command}\n'.encode("utf8")) - r = sock2.recv(1024) - sock2.close() - -def virtio_serial_connect(virtio_socket_path): - sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - while True: - time.sleep(0.1) - try: - sock.connect(virtio_socket_path) - return sock - except FileNotFoundError: - pass - -def available_tcp_port(port_range_from = 1024): - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - port = port_range_from - port_range_to = port_range_from + 32 # limit for retry - while port < port_range_to: - try: - s.bind(('', port)) - except OSError: - port += 1 - continue - break - s.close() - return port - -class WatchdogCommand: - START = 1 - STOP = 2 - - def __init__(self, op, arg = None): - self.op = op - self.arg = arg - -def parse_watchdog_commands(sock): - commands = [] - data = sock.recv(16).decode("utf8") - for l in data.splitlines(): - if l.startswith("START"): - try: - arg = int(l[5:]) - except ValueError: - arg = 30 # Default if not specified - commands.append( WatchdogCommand(WatchdogCommand.START, arg) ) - elif l.startswith("STOP"): - commands.append( WatchdogCommand(WatchdogCommand.STOP) ) - else: - print_verbose(f"Unsupported watchdog command {l}") - return commands - -def run_watchdog(watch_socket_path, qmp_socket_path): - sock = virtio_serial_connect(watch_socket_path) - - p = select.poll() - p.register(sock, select.POLLIN) - - watchdog_timeout = None - watchdog_delay = 30 - - while True: - timeout = None - if watchdog_timeout != None: - timeout = max(watchdog_timeout - time.time(), 0) * 1000 - - poll_res = p.poll(timeout) - - if len(poll_res) > 0: - v = poll_res[0][1] - if v & select.POLLHUP: - sys.exit(0) - commands = parse_watchdog_commands(sock) - for cmd in commands: - if cmd.op == WatchdogCommand.START: - print_verbose(f"Starting watchdog for {cmd.arg} sec") - watchdog_timeout = time.time() + cmd.arg - if cmd.op == WatchdogCommand.STOP: - print_verbose(f"Stopped watchdog") - watchdog_timeout = None - - if watchdog_timeout != None and time.time() >= watchdog_timeout: - print_verbose(f"Triggering watchdog") - qemu_run_command(qmp_socket_path, '{"execute": "system_reset"}') - - # Queue a new timeout in case the next boot fails, until disabled - watchdog_timeout = time.time() + watchdog_delay - - -def main(): - parser = argparse.ArgumentParser(description="Boot virtual machine images") - parser.add_argument("--verbose", default=False, action="store_true") - parser.add_argument("--arch", default=platform.machine(), action="store", - help=f"Arch to run for (default {platform.machine()})") - parser.add_argument("--publish-dir", action="store", - help=f"Publish the specified directory over http in the vm") - parser.add_argument("--memory", default="2G", - help=f"Memory size (default 2G)") - parser.add_argument("--nographics", default=False, action="store_true", - help=f"Run without graphics") - parser.add_argument("--watchdog", default=False, action="store_true", - help=f"Enable watchdog") - parser.add_argument("--tpm2", default=False, action="store_true", - help=f"Enable TPM2") - parser.add_argument("--snapshot", default=False, action="store_true", - help=f"Work on a snapshot of the image") - parser.add_argument("--ovmf-dir", action="store", - help="Specify directory for OVMF files (Open Virtual Machine Firmware)") - parser.add_argument("--secureboot", dest="secureboot", action="store_true", default=False, - help="Enable SecureBoot") - parser.add_argument("--ssh-port", type=int, default=2222, - help="SSH port forwarding to SSH_PORT (default 2222)") - parser.add_argument("--cdrom", action="store", - help="Specify .iso to load") - parser.add_argument("image", type=str, help="The image to boot") - parser.add_argument('extra_args', nargs=argparse.REMAINDER, metavar="...", help="extra qemu arguments") - - args = parser.parse_args(sys.argv[1:]) - - global is_verbose - is_verbose = args.verbose - - # arm64 is an alias for aarch64 on macOS - if args.arch == "arm64": - args.arch = "aarch64" - - qemu = find_qemu(args.arch) - accel_list = qemu_available_accels(qemu) - qemu_args = [qemu] - - if args.arch == "x86_64": - machine = "q35" - default_cpu = "qemu64,+ssse3,+sse4.1,+sse4.2,+popcnt" - - ovmf = find_ovmf(args) - if args.secureboot: - qemu_args += [ - "-drive", f"file={ovmf}/OVMF_CODE.secboot.fd,if=pflash,format=raw,unit=0,readonly=on", - "-drive", f"file={ovmf}/OVMF_VARS.secboot.fd,if=pflash,format=raw,unit=1,snapshot=on,readonly=off", - ] - else: - qemu_args += [ - "-drive", f"file={ovmf}/OVMF_CODE.fd,if=pflash,format=raw,unit=0,readonly=on", - "-drive", f"file={ovmf}/OVMF_VARS.fd,if=pflash,format=raw,unit=1,snapshot=on,readonly=off", - ] - elif args.arch == "aarch64": - machine = "virt" - default_cpu = "cortex-a57" - if sys.platform == "darwin": - edk2 = find_edk2() - qemu_args += [ - "-device", "virtio-gpu-pci", # for display - "-display", "default,show-cursor=on", # for display - "-device", "qemu-xhci", # for keyboard - "-device", "usb-kbd", # for keyboard - "-device", "usb-tablet", # for mouse - "-smp", str(os.cpu_count()), # for max cores - "-drive", f"file={edk2}/edk2-aarch64-code.fd,if=pflash,format=raw,unit=0,readonly=on", - "-drive", f"file={edk2}/edk2-arm-vars.fd,if=pflash,format=raw,unit=1,snapshot=on,readonly=off" - ] - else: - qemu_args += [ - "-bios", "/usr/share/edk2/aarch64/QEMU_EFI.fd", - "-boot", "efi" - ] - else: - exit_error(f"unsupported architecture {args.arch}") - - accel_enabled = True - if 'kvm' in accel_list and os.path.exists("/dev/kvm"): - qemu_args += ['-enable-kvm'] - elif 'hvf' in accel_list: - qemu_args += ['-accel', 'hvf'] - else: - accel_enabled = False - print_verbose("Acceleration: off") - - qemu_args += [ - "-m", str(args.memory), - "-machine", machine, - "-cpu", "host" if accel_enabled else default_cpu - ] - - guestfwds="" - - if args.publish_dir: - if shutil.which("netcat") is None: - print("Command `netcat` not found in path, ignoring publish-dir") - else: - httpd_port = run_http_server(args.publish_dir) - guestfwds = f"guestfwd=tcp:10.0.2.100:80-cmd:netcat 127.0.0.1 {httpd_port}," - print_verbose(f"publishing {args.publish_dir} on http://10.0.2.100/") - - portfwd = { - available_tcp_port(args.ssh_port): 22 - } - for local, remote in portfwd.items(): - print_verbose(f"port: {local} → {remote}") - - fwds = [f"hostfwd=tcp::{h}-:{g}" for h, g in portfwd.items()] - - macstr = generate_mac_address() - print_verbose(f"MAC: {macstr}") - - qemu_args += [ - "-device", f"virtio-net-pci,netdev=n0,mac={macstr}", - "-netdev", "user,id=n0,net=10.0.2.0/24," + guestfwds + ",".join(fwds), - ] - - if args.nographics: - qemu_args += ["-nographic"] - - runvm_id = random_id() - - tmpdir = tempfile.TemporaryDirectory(prefix=f"runvm-{runvm_id}") - - watchdog_pid = 0 - if args.watchdog: - qmp_socket_path = os.path.join(tmpdir.name, "qmp-socket") - watch_socket_path = os.path.join(tmpdir.name, "watch-socket") - - qemu_args += [ - "-qmp", f"unix:{qmp_socket_path},server=on,wait=off", - "-device", "virtio-serial", "-chardev", f"socket,path={watch_socket_path},server=on,wait=off,id=watchdog", - "-device", "virtserialport,chardev=watchdog,name=watchdog.0" - ] - - watchdog_pid = os.fork() - if watchdog_pid == 0: - run_watchdog(watch_socket_path, qmp_socket_path) - sys.exit(0) - - if args.tpm2: - if shutil.which("swtpm") is None: - exit_error("Command `swtpm` not found in path, this is needed for tpm2 support") - - tpm2_socket = os.path.join(tmpdir.name, "tpm-socket") - - if args.snapshot: - tpm2_path = os.path.join(tmpdir.name, "tpm2_state") - else: - tpm2_path = ".tpm2_state" - os.makedirs(tpm2_path, exist_ok=True) - - swtpm_args = ["swtpm", "socket", "--tpm2", "--tpmstate", f"dir={tpm2_path}", "--ctrl", f"type=unixio,path={tpm2_socket}" ] - res = subprocess.Popen(swtpm_args) - - qemu_args += [ - "-chardev", f"socket,id=chrtpm,path={tpm2_socket}", - "-tpmdev", "emulator,id=tpm0,chardev=chrtpm", - "-device", "tpm-tis,tpmdev=tpm0" - ] - - print_verbose(f"Image: {args.image}") - - if args.image.endswith(".raw"): - qemu_args += [ - "-drive", f"file={args.image},index=0,media=disk,format=raw,if=virtio,snapshot={bool_arg(args.snapshot)}", - ] - else: # assume qcow2 - qemu_args += [ - "-drive", f"file={args.image},index=0,media=disk,format=qcow2,if=virtio,snapshot={bool_arg(args.snapshot)}", - ] - - if args.cdrom: - qemu_args += [ - "-cdrom", args.cdrom, - "-boot", "d" - ] - - qemu_args += args.extra_args - - print_verbose(f"Running: {' '.join(qemu_args)}") - - try: - res = subprocess.run(qemu_args, check=False) - except KeyboardInterrupt: - exit_error("Aborted") - - if watchdog_pid: - os.kill(watchdog_pid, signal.SIGTERM) - - tmpdir.cleanup() - - return res.returncode - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/tools/runosbuild b/tools/runosbuild index f626466..a054183 100755 --- a/tools/runosbuild +++ b/tools/runosbuild @@ -9,7 +9,6 @@ # # * Knows what target to export based on the extensions. # * Knows what the exported filename based on the extensions. -# * Supports building natively or in a VM via osbuildvm. # * Runs only the minimal required commands as root, and # chowns the resulting files to the user # * Supports exporting an extra ostree commit and pulling that @@ -54,8 +53,7 @@ if [ $ARCH == $HOST_ARCH -a $VM == 0 ]; then SUDO="sudo" OSBUILD="sudo osbuild" else - SUDO= - OSBUILD="osbuildvm/osbuildvm --arch=$ARCH" + echo "I don't support building for non-x86 nor in a VM." fi EXPORT_ARGS="--export $EXPORT"