diff --git a/Dockerfile b/Dockerfile index 57037791a5..80e2f88547 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,7 +18,7 @@ COPY ./ /root/containerbuild/ RUN ./build.sh write_archive_info RUN ./build.sh make_and_makeinstall RUN ./build.sh configure_user -RUN ./build.sh patch_osbuild +# RUN ./build.sh patch_osbuild # clean up scripts (it will get cached in layers, but oh well) WORKDIR /srv/ diff --git a/build.sh b/build.sh index ad91881e8e..82092e7333 100755 --- a/build.sh +++ b/build.sh @@ -173,9 +173,8 @@ patch_osbuild() { mv /usr/bin/osbuild-mpp /usr/lib/osbuild/tools/ # Now all the software is under the /usr/lib/osbuild dir and we can patch - cat /usr/lib/coreos-assembler/0001-osbuild-remoteloop-add-more-loop-device-options.patch \ - /usr/lib/coreos-assembler/0001-util-osrelease.py-Replaced-string-stripping-with-shl.patch \ - /usr/lib/coreos-assembler/0005-stages-add-coreos.live-artifacts.mono-stage.patch \ + cat patch1.patch \ + patch2.patch \ | patch -d /usr/lib/osbuild -p1 # And then move the files back; supermin appliance creation will need it back @@ -199,5 +198,5 @@ else install_ocp_tools trust_redhat_gpg_keys configure_user - patch_osbuild + #patch_osbuild fi diff --git a/src/0001-osbuild-remoteloop-add-more-loop-device-options.patch b/src/0001-osbuild-remoteloop-add-more-loop-device-options.patch deleted file mode 100644 index b063fbf6c7..0000000000 --- a/src/0001-osbuild-remoteloop-add-more-loop-device-options.patch +++ /dev/null @@ -1,92 +0,0 @@ -From f4b899873b1f108edbf12d81ee5dbed037238a7e Mon Sep 17 00:00:00 2001 -From: Dusty Mabe -Date: Fri, 22 Nov 2024 19:02:57 -0500 -Subject: [PATCH] osbuild/remoteloop: add more loop device options - -This adds lock, partscan, read_only, sector_size to _create_device() -similar to make_loop() from devices/org.osbuild.loopback. ---- - osbuild/remoteloop.py | 42 +++++++++++++++++++++++++++++++++++++----- - 1 file changed, 37 insertions(+), 5 deletions(-) - -diff --git a/osbuild/remoteloop.py b/osbuild/remoteloop.py -index 0544e0be..0fd2cfc0 100644 ---- a/osbuild/remoteloop.py -+++ b/osbuild/remoteloop.py -@@ -41,8 +41,23 @@ class LoopServer(api.BaseAPI): - self.devs = [] - self.ctl = loop.LoopControl() - -- def _create_device(self, fd, dir_fd, offset=None, sizelimit=None): -- lo = self.ctl.loop_for_fd(fd, offset=offset, sizelimit=sizelimit, autoclear=True) -+ def _create_device( -+ self, -+ fd, -+ dir_fd, -+ offset=None, -+ sizelimit=None, -+ lock=False, -+ partscan=False, -+ read_only=False, -+ sector_size=512): -+ lo = self.ctl.loop_for_fd(fd, lock=lock, -+ offset=offset, -+ sizelimit=sizelimit, -+ blocksize=sector_size, -+ partscan=partscan, -+ read_only=read_only, -+ autoclear=True) - lo.mknod(dir_fd) - # Pin the Loop objects so they are only released when the LoopServer - # is destroyed. -@@ -54,8 +69,12 @@ class LoopServer(api.BaseAPI): - dir_fd = fds[msg["dir_fd"]] - offset = msg.get("offset") - sizelimit = msg.get("sizelimit") -+ lock = msg.get("lock", False) -+ partscan = msg.get("partscan", False) -+ read_only = msg.get("read_only", False) -+ sector_size = msg.get("sector_size", 512) - -- devname = self._create_device(fd, dir_fd, offset, sizelimit) -+ devname = self._create_device(fd, dir_fd, offset, sizelimit, lock, partscan, read_only, sector_size) - sock.send({"devname": devname}) - - def _cleanup(self): -@@ -75,11 +94,20 @@ class LoopClient: - self.client.close() - - @contextlib.contextmanager -- def device(self, filename, offset=None, sizelimit=None): -+ def device( -+ self, -+ filename, -+ offset=None, -+ sizelimit=None, -+ lock=False, -+ partscan=False, -+ read_only=False, -+ sector_size=512): - req = {} - fds = [] - -- fd = os.open(filename, os.O_RDWR) -+ flags = os.O_RDONLY if read_only else os.O_RDWR -+ fd = os.open(filename, flags) - dir_fd = os.open("/dev", os.O_DIRECTORY) - - fds.append(fd) -@@ -91,6 +119,10 @@ class LoopClient: - req["offset"] = offset - if sizelimit: - req["sizelimit"] = sizelimit -+ req["lock"] = lock -+ req["partscan"] = partscan -+ req["read_only"] = read_only -+ req["sector_size"] = sector_size - - self.client.send(req, fds=fds) - os.close(dir_fd) --- -2.47.0 - diff --git a/src/0001-util-osrelease.py-Replaced-string-stripping-with-shl.patch b/src/0001-util-osrelease.py-Replaced-string-stripping-with-shl.patch deleted file mode 100644 index c1068855b0..0000000000 --- a/src/0001-util-osrelease.py-Replaced-string-stripping-with-shl.patch +++ /dev/null @@ -1,44 +0,0 @@ -From 07d4f6955dd5dd5535b3e2f8e4722eb20f95e613 Mon Sep 17 00:00:00 2001 -From: Renata Ravanelli -Date: Tue, 12 Nov 2024 15:12:52 -0300 -Subject: [PATCH] util/osrelease.py: Replaced string stripping with - `shlex.split()` - -- Replaced string stripping with `shlex.split()` to properly -handle values in the os-release file; -- This ensures cleaner and more accurate key-value assignments, -follwing a broader set of shell-like parsing rules; -- Add os-release file for Fedora CoreOS 40 for testing. - -Signed-off-by: Renata Ravanelli ---- - osbuild/util/osrelease.py | 6 +++++- - 1 file changed, 5 insertions(+), 1 deletion(-) - -diff --git a/osbuild/util/osrelease.py b/osbuild/util/osrelease.py -index b8d56e73..cc97faf5 100644 ---- a/osbuild/util/osrelease.py -+++ b/osbuild/util/osrelease.py -@@ -5,6 +5,7 @@ related documentation can be found in `os-release(5)`. - """ - - import os -+import shlex - - # The default paths where os-release is located, as per os-release(5) - DEFAULT_PATHS = [ -@@ -33,7 +34,10 @@ def parse_files(*paths): - if line[0] == "#": - continue - key, value = line.split("=", 1) -- osrelease[key] = value.strip('"') -+ split_value = shlex.split(value) -+ if not split_value or len(split_value) > 1: -+ raise ValueError(f"Key '{key}' has an empty value or more than one token: {value}") -+ osrelease[key] = split_value[0] - - return osrelease - --- -2.47.0 - diff --git a/src/0005-stages-add-coreos.live-artifacts.mono-stage.patch b/src/0005-stages-add-coreos.live-artifacts.mono-stage.patch deleted file mode 100644 index 082aab77e8..0000000000 --- a/src/0005-stages-add-coreos.live-artifacts.mono-stage.patch +++ /dev/null @@ -1,966 +0,0 @@ -From 70ff8d75b81f0ee87792a6f109ac006f5f351bc5 Mon Sep 17 00:00:00 2001 -From: Jonathan Lebon -Date: Tue, 13 Aug 2024 12:13:53 -0400 -Subject: [PATCH 5/5] stages: add `coreos.live-artifacts.mono` stage - -This adds a new `org.osbuild.coreos.live-artifacts.mono` stage to build -CoreOS Live ISO/PXE artifacts. The code is heavily based on the -`cmd-buildextend-live` script from coreos-assembler [1], but a lot of -things had to be adapted: -- the stage is provided the deployed oscontainer tree, metal, and - metal4k images as inputs -- we use chroot instead of supermin to execute some commands in the - context of the target oscontainer -- a bunch of calls that were wrapped by libguestfs for us (e.g. - mkfs.vfat, mksquashfs), we now have to call ourselves; to retain - maximum compatibility, we ensured that we still effectively use the - same args that libguestfs passed - -And various other minor adjustments. - -Of course, this is not really in line with the OSBuild philosophy -of having smaller-scoped stages. We have labeled this with a .mono -suffix to denote it is monolithic, similar to the existing -`org.osbuild.bootiso.mono` stage today. - -Eventually we may be able to break this stage down if we find it worth -the effort. Alternatively the need for it may go away as we align more -with Image Mode. - -Co-authored-by: Dusty Mabe -Co-authored-by: Renata Ravanelli ---- - stages/org.osbuild.coreos.live-artifacts.mono | 845 ++++++++++++++++++ - ...build.coreos.live-artifacts.mono.meta.json | 68 ++ - 2 files changed, 913 insertions(+) - create mode 100755 stages/org.osbuild.coreos.live-artifacts.mono - create mode 100644 stages/org.osbuild.coreos.live-artifacts.mono.meta.json - -diff --git a/stages/org.osbuild.coreos.live-artifacts.mono b/stages/org.osbuild.coreos.live-artifacts.mono -new file mode 100755 -index 00000000..44dbde5a ---- /dev/null -+++ b/stages/org.osbuild.coreos.live-artifacts.mono -@@ -0,0 +1,845 @@ -+#!/usr/bin/python3 -+ -+# This stage is based on coreos-assembler's `cmd-buildextend-live`. It -+# builds the CoreOS Live ISO and PXE (kernel, initramfs, and rootfs) -+# based on the provided metal and metal4k disk images that are provided -+# as inputs. -+# -+# For historical context and to see git history, refer to the original source: -+# https://github.com/coreos/coreos-assembler/blob/43a9c80e1f548269d71d6d586f0d5754c60f6144/src/cmd-buildextend-live -+ -+import glob -+import hashlib -+import json -+import os -+import re -+import shutil -+import struct -+import subprocess -+import sys -+import tarfile -+import tempfile -+ -+import yaml -+ -+import osbuild.api -+import osbuild.remoteloop as remoteloop -+from osbuild.util import checksum, osrelease -+from osbuild.util.chroot import Chroot -+ -+# Size of file used to embed an Ignition config within a CPIO. -+IGNITION_IMG_SIZE = 256 * 1024 -+ -+# Size of the file used to embed miniso data. -+MINISO_DATA_FILE_SIZE = 16 * 1024 -+ -+LIVE_EXCLUDE_KARGS = set([ -+ '$ignition_firstboot', # unsubstituted variable in grub config -+ 'console', # no serial console by default on ISO -+ 'ignition.platform.id', # we hardcode "metal" -+ 'ostree', # dracut finds the tree automatically -+]) -+ -+ -+# The kernel requires that uncompressed cpio archives appended to an initrd -+# start on a 4-byte boundary. If there's misalignment, it stops unpacking -+# and says: -+# -+# Initramfs unpacking failed: invalid magic at start of compressed archive -+# -+# Append NUL bytes to destf until its size is a multiple of 4 bytes. -+# -+# https://www.kernel.org/doc/Documentation/early-userspace/buffer-format.txt -+# https://github.com/torvalds/linux/blob/47ec5303/init/initramfs.c#L463 -+def align_initrd_for_uncompressed_append(destf): -+ offset = destf.tell() -+ if offset % 4: -+ destf.write(b'\0' * (4 - offset % 4)) -+ -+ -+# Return OS features table for features.json, which is read by -+# coreos-installer {iso|pxe} customize -+def get_os_features(tree): -+ features = { -+ # coreos-installer >= 0.12.0 -+ 'installer-config': True, -+ # coreos/fedora-coreos-config@3edd2f28 -+ 'live-initrd-network': True, -+ } -+ file = os.path.join(tree, 'usr/share/coreos-installer/example-config.yaml') -+ with open(file, encoding='utf8') as f: -+ example_config_yaml = yaml.safe_load(f) -+ features['installer-config-directives'] = { -+ k: True for k in example_config_yaml -+ } -+ return features -+ -+ -+# https://www.kernel.org/doc/html/latest/admin-guide/initrd.html#compressed-cpio-images -+def mkinitrd_pipe(tmproot, destf, compress=True): -+ if not compress: -+ align_initrd_for_uncompressed_append(destf) -+ files = subprocess.check_output(['find', '.', '-mindepth', '1', '-print0'], -+ cwd=tmproot) -+ file_list = files.split(b'\0') -+ # If there's a root.squashfs, it _must_ be the first file in the cpio -+ # archive, since the dracut 20live module assumes its contents are at -+ # a fixed offset in the archive. -+ squashfs = b'./root.squashfs' -+ if squashfs in file_list: -+ file_list.remove(squashfs) -+ file_list.insert(0, squashfs) -+ cpioproc = subprocess.Popen(['cpio', '-o', '-H', 'newc', '-R', 'root:root', -+ '--quiet', '--reproducible', '--force-local', '--null', -+ '-D', tmproot], stdin=subprocess.PIPE, stdout=subprocess.PIPE) -+ if compress: -+ gzipargs = ['gzip', '-9'] -+ else: -+ gzipargs = ['cat'] -+ gzipproc = subprocess.Popen(gzipargs, stdin=cpioproc.stdout, stdout=destf) -+ cpioproc.stdin.write(b'\0'.join(file_list)) -+ cpioproc.stdin.close() -+ assert cpioproc.wait() == 0, f"cpio exited with {cpioproc.returncode}" -+ assert gzipproc.wait() == 0, f"gzip exited with {gzipproc.returncode}" -+ # Fix up padding so the user can append the rootfs afterward -+ align_initrd_for_uncompressed_append(destf) -+ -+ -+def extend_initramfs(initramfs, tree, compress=True): -+ with open(initramfs, 'ab') as fdst: -+ mkinitrd_pipe(tree, fdst, compress=compress) -+ -+ -+def cp_reflink(src, dest): -+ subprocess.check_call(['cp', '--reflink=auto', src, dest]) -+ -+ -+# Make stream hash for `rdcore stream-hash` -+# https://github.com/coreos/coreos-installer/blob/a8d6f50dea6e/src/bin/rdcore/stream_hash.rs#L26-L41 -+def make_stream_hash(src, dest): -+ bufsize = 2 * 1024 * 1024 -+ with open(src, 'rb') as inf: -+ with open(dest, 'w', encoding='utf8') as outf: -+ outf.write(f'stream-hash sha256 {bufsize}\n') -+ while True: -+ buf = inf.read(bufsize) -+ if not buf: -+ break -+ outf.write(hashlib.sha256(buf).hexdigest() + '\n') -+ -+ -+def get_os_name(tree): -+ file = os.path.join(tree, 'usr/share/rpm-ostree/treefile.json') -+ with open(file, encoding='utf8') as f: -+ treefile = json.load(f) -+ return treefile['metadata']['name'] -+ -+ -+def ensure_glob(pathname, **kwargs): -+ '''Call glob.glob(), and fail if there are no results.''' -+ ret = glob.glob(pathname, **kwargs) -+ if not ret: -+ raise ValueError(f'No matches for {pathname}') -+ return ret -+ -+ -+# This creates efiboot.img, which is a FAT filesystem. -+def make_efi_bootfile(loop_client, input_tarball, output_efiboot_img): -+ # Create the efiboot image file. Determine the size we should make -+ # it by taking the tarball size and adding 2MiB for fs overhead. -+ size = os.path.getsize(input_tarball) + 2 * 1024 * 1024 -+ with open(output_efiboot_img, "wb") as out: -+ out.truncate(size) -+ # Make loopback device; mkfs; populate with files -+ with loop_client.device(output_efiboot_img) as loopdev: -+ # On RHEL 8, when booting from a disk device (rather than a CD), -+ # https://github.com/systemd/systemd/issues/14408 causes the -+ # hybrid ESP to race with the ISO9660 filesystem for the -+ # /dev/disk/by-label symlink unless the ESP has its own label, -+ # so set EFI-SYSTEM for consistency with the metal image. -+ # This should not be needed on Fedora or RHEL 9, but seems like -+ # a good thing to do anyway. -+ label = 'EFI-SYSTEM' -+ # NOTE: the arguments to mkfs here match how virt-make-fs calls mkfs -+ subprocess.check_call(['mkfs', '-t', 'vfat', '-I', '--mbr=n', '-n', label, loopdev]) -+ with tempfile.TemporaryDirectory() as d: -+ try: -+ subprocess.check_call(['mount', '-o', 'utf8', loopdev, d]) -+ subprocess.check_call(['tar', '-C', d, '-xf', input_tarball]) -+ finally: -+ subprocess.check_call(['umount', d]) -+ -+ -+def parse_metal_inputs(inputs): -+ def get_filepath_from_input(name): -+ files = inputs[name]["data"]["files"] -+ assert len(files) == 1 -+ filename, _ = files.popitem() -+ filepath = os.path.join(inputs[name]["path"], filename) -+ return filepath -+ metal_file = get_filepath_from_input('metal') -+ metal4k_file = get_filepath_from_input('metal4k') -+ return metal_file, metal4k_file -+ -+ -+# Here we will generate the 4 Live/PXE CoreOS artifacts. -+# There are four files created and exported from this stage. -+# -+# 1. live-iso -+# 2. live-kernel (i.e. images/pxeboot/vmlinuz) -+# 3. live-initramfs (i.e. images/pxeboot/initrd.img) -+# 4. live-rootfs (i.e. images/pxeboot/rootfs.img) -+# -+# The live-iso itself contains the other 3 artifacts inside of -+# it. A rough approximation of the structure looks like: -+# -+# live-iso.iso -+# -> images/pxeboot/vmlinuz -+# -> images/pxeboot/initrd.img -+# -> images/pxeboot/rootfs.img -+# -> metal.osmet -+# -> metal4k.osmet -+# -> root.squashfs -+# -> full filesystem of metal image -+# -+# Where the ISO contains the kernel and initrd and the rootfs. The -+# rootfs contains the osmet file and the root.squashfs, which itself -+# contains all the files from a full CoreOS system. -+# pylint: disable=too-many-statements,too-many-branches -+def main(workdir, tree, inputs, options, loop_client): -+ squashfs_compression = 'zstd' -+ basearch = os.uname().machine -+ filenames = options['filenames'] -+ output_iso = os.path.join(tree, filenames['live-iso']) -+ output_kernel = os.path.join(tree, filenames['live-kernel']) -+ output_rootfs = os.path.join(tree, filenames['live-rootfs']) -+ output_initramfs = os.path.join(tree, filenames['live-initramfs']) -+ img_metal, img_metal4k = parse_metal_inputs(inputs) -+ # The deployed tree input is a deployment tree just as it would -+ # appear on a booted OSTree system. We copy some files out of this -+ # input tree since it is easier and they match what is in the metal -+ # images anyway. We also use it as a chroot when we run `coreos-installer`. -+ deployed_tree = inputs['deployed-tree']['path'] -+ -+ # For dev/test purposes it is useful to create test fixture ISO -+ # images for the coreos-installer CI framework to use [1]. This test -+ # fixture is much smaller than an actual ISO and can be stored in git. -+ # Determine if the user wants a test fixture ISO created by checking -+ # for the coreos-installer-test-fixture file baked in the tree. -+ # [1] https://github.com/coreos/coreos-installer/tree/main/fixtures/iso -+ test_fixture = os.path.exists( -+ os.path.join( -+ deployed_tree, -+ 'usr/share/coreos-assembler/coreos-installer-test-fixture')) -+ -+ # Determine some basic information about the CoreOS we are operating on. -+ base_name = get_os_name(tree=deployed_tree) -+ os_release = osrelease.parse_files(os.path.join(deployed_tree, 'etc', 'os-release')) -+ version = os_release['OSTREE_VERSION'] -+ name_version = f'{base_name}-{version}' -+ # The short volume ID can only be 32 characters (bytes probably). We may in -+ # the future want to shorten this more intelligently, otherwise we truncate the -+ # version which may impede uniqueness. -+ volid = name_version[0:32] -+ -+ tmpisofile = os.path.join(workdir, 'live.iso') -+ tmpisoroot = os.path.join(workdir, 'iso') -+ tmpisocoreos = os.path.join(tmpisoroot, 'coreos') -+ tmpisoimages = os.path.join(tmpisoroot, 'images') -+ tmpisoimagespxe = os.path.join(tmpisoimages, 'pxeboot') -+ tmpisoisolinux = os.path.join(tmpisoroot, 'isolinux') -+ # contents of initramfs on both PXE and ISO -+ tmpinitrd_base = os.path.join(workdir, 'initrd') -+ # contents of rootfs image -+ tmpinitrd_rootfs = os.path.join(workdir, 'initrd-rootfs') -+ -+ for d in (tmpisoroot, tmpisocoreos, tmpisoimages, tmpisoimagespxe, -+ tmpisoisolinux, tmpinitrd_base, tmpinitrd_rootfs): -+ os.mkdir(d) -+ -+ # convention for kernel and initramfs names and a few others -+ kernel_img = 'vmlinuz' -+ initrd_img = 'initrd.img' -+ rootfs_img = 'rootfs.img' -+ kargs_file = 'kargs.json' -+ igninfo_file = 'igninfo.json' -+ -+ # Find the directory under `/usr/lib/modules/` where the -+ # kernel/initrd live. It will be the only entity in there. -+ modules_dir = os.path.join(deployed_tree, 'usr/lib/modules') -+ modules_dirents = os.listdir(modules_dir) -+ if len(modules_dirents) != 1: -+ raise ValueError(f"expected unique entry in modules dir, found: {modules_dirents}") -+ moduledir = modules_dirents[0] -+ -+ # copy those files out of the ostree into the iso root dir -+ initramfs_img = 'initramfs.img' -+ for file in [kernel_img, initramfs_img]: -+ src = os.path.join(modules_dir, moduledir, file) -+ dst = os.path.join(tmpisoimagespxe, file) -+ if file == initramfs_img: -+ dst = os.path.join(tmpisoimagespxe, initrd_img) -+ shutil.copyfile(src, dst) -+ # initramfs isn't world readable by default so let's open up perms -+ os.chmod(dst, 0o644) -+ -+ # Generate initramfs stamp file indicating that this is a live -+ # initramfs. Store the build ID in it. -+ stamppath = os.path.join(tmpinitrd_base, 'etc/coreos-live-initramfs') -+ os.makedirs(os.path.dirname(stamppath), exist_ok=True) -+ with open(stamppath, 'w', encoding='utf8') as fh: -+ fh.write(version + '\n') -+ -+ # Generate rootfs stamp file with the build ID, indicating that the -+ # rootfs has been appended and confirming that initramfs and rootfs are -+ # from the same build. -+ stamppath = os.path.join(tmpinitrd_rootfs, 'etc/coreos-live-rootfs') -+ os.makedirs(os.path.dirname(stamppath), exist_ok=True) -+ with open(stamppath, 'w', encoding='utf8') as fh: -+ fh.write(version + '\n') -+ -+ # Add placeholder for Ignition CPIO file. This allows an external tool, -+ # `coreos-installer iso ignition embed`, to modify an existing ISO image -+ # to embed a user's custom Ignition config. The tool wraps the Ignition -+ # config in a cpio.xz and write it directly into this file in the ISO -+ # image. The cpio.xz will be read into the initramfs filesystem at -+ # runtime and the Ignition Dracut module will ensure that the config is -+ # moved where Ignition will see it. We only handle !s390x here since that's -+ # the simple case (where layered initrds are supported). The s390x case is -+ # handled lower down -+ if basearch != 's390x': -+ with open(os.path.join(tmpisoimages, 'ignition.img'), 'wb') as fdst: -+ fdst.write(bytes(IGNITION_IMG_SIZE)) -+ igninfo_json = {'file': 'images/ignition.img'} -+ -+ # Generate JSON file that lists OS features available to -+ # coreos-installer {iso|pxe} customize. Put it in the initramfs for -+ # pxe customize and the ISO for iso customize. -+ features = json.dumps(get_os_features(tree=deployed_tree), indent=2, sort_keys=True) + '\n' -+ featurespath = os.path.join(tmpinitrd_base, 'etc/coreos/features.json') -+ os.makedirs(os.path.dirname(featurespath), exist_ok=True) -+ with open(featurespath, 'w', encoding='utf8') as fh: -+ fh.write(features) -+ with open(os.path.join(tmpisocoreos, 'features.json'), 'w', encoding='utf8') as fh: -+ fh.write(features) -+ -+ # Add osmet files. Use a chroot here to use the same coreos-installer as is -+ # in the artifacts we are building. Use the deployed_tree as the chroot since -+ # that's the easiest thing to do. -+ for img, sector_size in [(img_metal, 512), (img_metal4k, 4096)]: -+ with loop_client.device(img, partscan=True, read_only=True, sector_size=sector_size) as loopdev: -+ img_name = os.path.basename(img) -+ print(f'Generating osmet file for {img_name} image') -+ img_checksum = checksum.hexdigest_file(loopdev, "sha256") -+ img_osmet_file = os.path.join(tmpinitrd_rootfs, f'{img_name}.osmet') -+ with Chroot(deployed_tree, bind_mounts=["/run", "/tmp"]) as chroot: -+ cmd = ['coreos-installer', 'pack', 'osmet', loopdev, -+ '--description', os_release['PRETTY_NAME'], -+ '--checksum', img_checksum, '--output', img_osmet_file] -+ chroot.run(cmd, check=True) -+ -+ tmp_squashfs_dir = os.path.join(workdir, 'tmp-squashfs-dir') -+ os.mkdir(tmp_squashfs_dir) -+ -+ # Since inputs are read-only and we want to modify we'll make a -+ # copy of the metal.raw image and then mount that. -+ tmp_img_metal = os.path.join(workdir, os.path.basename(img_metal)) -+ cp_reflink(img_metal, tmp_img_metal) -+ with loop_client.device(tmp_img_metal, partscan=True) as loopdev: -+ # So we can temporarily see the loopXpX devices -+ subprocess.check_call(['mount', '-t', 'devtmpfs', 'devtmpfs', '/dev/']) -+ # Mount manually to avoid conflicts with osmet. -+ # If mounted via the manifest, the stage begins with mounts already in place, -+ # but osmet also performs a mount operation, leading to conflicts due to duplicate -+ # filesystem UUIDs. Perform the manual mount only after the osmet stage -+ subprocess.check_call(['mount', '-o', 'rw', loopdev + 'p4', tmp_squashfs_dir]) -+ subprocess.check_call(['mount', '-o', 'rw', loopdev + 'p3', -+ os.path.join(tmp_squashfs_dir, 'boot')]) -+ if basearch in ['x86_64', 'aarch64']: -+ subprocess.check_call(['mount', '-o', 'rw', loopdev + 'p2', -+ os.path.join(tmp_squashfs_dir, 'boot/efi')]) -+ squashfs_dir_umount_needed = True -+ -+ # Immplements necessary CoreOS adjustments -+ # including creating hardlinks in the /boot/ filesystem -+ # and modifying the read-only flag in OSTree configurations -+ # Where the contents of rootfs image are stored -+ # Make sure to create it, if it is not created yet. -+ tmpinitrd_rootfs = os.path.join(workdir, 'initrd-rootfs') -+ os.makedirs(tmpinitrd_rootfs, exist_ok=True) -+ -+ # Remove the sysroot=readonly flag, see https://github.com/coreos/fedora-coreos-tracker/issues/589 -+ subprocess.check_call(['sed', '-i', '/readonly=true/d', f'{tmp_squashfs_dir}/ostree/repo/config']) -+ -+ # And ensure that the kernel binary and hmac file is in the place that dracut -+ # expects it to be; xref https://issues.redhat.com/browse/OCPBUGS-15843 -+ -+ kernel_binary = glob.glob(f"{tmp_squashfs_dir}/boot/ostree/*/vmlinuz*")[0] -+ kernel_hmac = glob.glob(f"{tmp_squashfs_dir}/boot/ostree/*/.*.hmac")[0] -+ kernel_binary_basename = os.path.basename(kernel_binary) -+ kernel_hmac_basename = os.path.basename(kernel_hmac) -+ -+ # Create hard links in the /boot directory -+ os.link(kernel_hmac, f"{tmp_squashfs_dir}/boot/{kernel_hmac_basename}") -+ os.link(kernel_binary, f"{tmp_squashfs_dir}/boot/{kernel_binary_basename}") -+ -+ print(f"Kernel binary linked: {tmp_squashfs_dir}/boot/{kernel_binary_basename}") -+ print(f"Kernel HMAC linked: {tmp_squashfs_dir}/boot/{kernel_hmac_basename}") -+ # Generate root squashfs -+ print(f'Compressing squashfs with {squashfs_compression}') -+ -+ try: -+ # Name must be exactly "root.squashfs" because the 20live dracut module -+ # makes assumptions about the length of the name in sysroot.mount -+ tmp_squashfs = os.path.join(tmpinitrd_rootfs, 'root.squashfs') -+ # this matches the set of flags we implicitly passed when doing this -+ # through libguestfs' mksquashfs command -+ subprocess.check_call(['mksquashfs', tmp_squashfs_dir, tmp_squashfs, -+ '-root-becomes', tmp_squashfs_dir, '-wildcards', '-no-recovery', -+ '-comp', squashfs_compression]) -+ -+ # while it's mounted here, also get the kargs -+ blsentry = ensure_glob(os.path.join(tmp_squashfs_dir, 'boot/loader/entries/*.conf')) -+ if len(blsentry) != 1: -+ raise ValueError(f'Found != 1 BLS entries: {blsentry}') -+ blsentry = blsentry[0] -+ blsentry_kargs = [] -+ with open(blsentry, encoding='utf8') as f: -+ for line in f: -+ if line.startswith('options '): -+ blsentry_kargs = line.split(' ', 1)[1].strip().split(' ') -+ break -+ if len(blsentry_kargs) == 0: -+ raise ValueError("found no kargs in metal image") -+ finally: -+ if squashfs_dir_umount_needed: -+ subprocess.check_call(['umount', '-R', tmp_squashfs_dir]) -+ subprocess.check_call(['umount', '/dev/']) -+ -+ # Generate rootfs image -+ iso_rootfs = os.path.join(tmpisoimagespxe, rootfs_img) -+ # The rootfs must be uncompressed because the ISO mounts root.squashfs -+ # directly from the middle of the file -+ extend_initramfs(initramfs=iso_rootfs, tree=tmpinitrd_rootfs, compress=False) -+ # Check that the root.squashfs magic number is in the offset hardcoded -+ # in sysroot.mount in 20live/live-generator -+ with open(iso_rootfs, 'rb') as fh: -+ fh.seek(124) -+ if fh.read(4) != b'hsqs': -+ raise ValueError("root.squashfs not at expected offset in rootfs image") -+ # Save stream hash of rootfs for verifying out-of-band fetches -+ os.makedirs(os.path.join(tmpinitrd_base, 'etc'), exist_ok=True) -+ make_stream_hash(iso_rootfs, os.path.join(tmpinitrd_base, 'etc/coreos-live-want-rootfs')) -+ # Add common content -+ iso_initramfs = os.path.join(tmpisoimagespxe, initrd_img) -+ extend_initramfs(initramfs=iso_initramfs, tree=tmpinitrd_base) -+ -+ # Filter kernel arguments for substituting into ISO bootloader -+ kargs_array = [karg for karg in blsentry_kargs -+ if karg.split('=')[0] not in LIVE_EXCLUDE_KARGS] -+ kargs_array.append(f"coreos.liveiso={volid}") -+ kargs = ' '.join(kargs_array) -+ print(f'Substituting ISO kernel arguments: {kargs}') -+ -+ kargs_json = {'files': []} -+ cmdline = '' -+ karg_embed_area_length = 0 -+ srcdir_prefix = os.path.join(deployed_tree, 'usr/share/coreos-assembler/live/') -+ # Grab all the contents from the live dir from the configs -+ for srcdir, _, filenames in os.walk(srcdir_prefix): -+ dir_suffix = srcdir.replace(srcdir_prefix, '', 1) -+ dstdir = os.path.join(tmpisoroot, dir_suffix) -+ if not os.path.exists(dstdir): -+ os.mkdir(dstdir) -+ for filename in filenames: -+ # Skip development readmes to avoid confusing users -+ if filename == 'README-devel.md': -+ continue -+ srcfile = os.path.join(srcdir, filename) -+ dstfile = os.path.join(dstdir, filename) -+ # Assumes all files are text -+ with open(srcfile, encoding='utf8') as fh: -+ buf = fh.read() -+ newbuf = buf.replace('@@KERNEL-ARGS@@', kargs) -+ # if we injected kargs, also check for an embed area -+ if buf != newbuf: -+ karg_area_start = re.search(r'@@KERNEL-ARGS@@', buf) -+ buf = newbuf -+ karg_area_end = re.search(r'(#+)# COREOS_KARG_EMBED_AREA\n', buf) -+ if karg_area_end is not None: -+ file_kargs = buf[karg_area_start.start():karg_area_end.start()] -+ if len(cmdline) == 0: -+ cmdline = file_kargs -+ elif cmdline != file_kargs: -+ raise ValueError(f'Default cmdline is different: "{cmdline}" != "{file_kargs}"') -+ -+ length = karg_area_end.start() + len(karg_area_end[1]) - karg_area_start.start() -+ kargs_json['files'].append({ -+ 'path': os.path.join(dir_suffix, filename), -+ 'offset': karg_area_start.start(), -+ 'pad': '#', -+ 'end': '\n', -+ }) -+ if karg_embed_area_length == 0: -+ karg_embed_area_length = length -+ elif length != karg_embed_area_length: -+ raise ValueError(f"Karg embed areas of varying length {kargs_json['files']}") -+ with open(dstfile, 'w', encoding='utf8') as fh: -+ fh.write(buf) -+ shutil.copystat(srcfile, dstfile) -+ print(f'{srcfile} -> {dstfile}') -+ -+ if karg_embed_area_length > 0: -+ assert (karg_embed_area_length > len(cmdline)) -+ kargs_json.update( -+ size=karg_embed_area_length, -+ default=cmdline.strip(), -+ ) -+ -+ # These sections are based on lorax templates -+ # see https://github.com/weldr/lorax/tree/master/share/templates.d/99-generic -+ -+ # Generate the ISO image. Lots of good info here: -+ # https://fedoraproject.org/wiki/User:Pjones/BootableCDsForBIOSAndUEFI -+ genisoargs = ['/usr/bin/genisoimage', '-verbose', -+ '-V', volid, -+ '-volset', f"{name_version}", -+ # For greater portability, consider using both -+ # Joliet and Rock Ridge extensions. Umm, OK :) -+ '-rational-rock', '-J', '-joliet-long'] -+ -+ # For x86_64 legacy boot (BIOS) booting -+ if basearch == "x86_64": -+ # Install binaries from syslinux package -+ isolinuxfiles = [('/usr/share/syslinux/isolinux.bin', 0o755), -+ ('/usr/share/syslinux/ldlinux.c32', 0o755), -+ ('/usr/share/syslinux/libcom32.c32', 0o755), -+ ('/usr/share/syslinux/libutil.c32', 0o755), -+ ('/usr/share/syslinux/vesamenu.c32', 0o755)] -+ for src, mode in isolinuxfiles: -+ dst = os.path.join(tmpisoisolinux, os.path.basename(src)) -+ shutil.copyfile(src, dst) -+ os.chmod(dst, mode) -+ -+ # for legacy bios boot AKA eltorito boot -+ genisoargs += ['-eltorito-boot', 'isolinux/isolinux.bin', -+ '-eltorito-catalog', 'isolinux/boot.cat', -+ '-no-emul-boot', -+ '-boot-load-size', '4', -+ '-boot-info-table'] -+ -+ elif basearch == "ppc64le": -+ os.makedirs(os.path.join(tmpisoroot, 'boot/grub')) -+ # can be EFI/fedora or EFI/redhat -+ grubpath = ensure_glob(os.path.join(tmpisoroot, 'EFI/*/grub.cfg')) -+ if len(grubpath) != 1: -+ raise ValueError(f'Found != 1 grub.cfg files: {grubpath}') -+ shutil.move(grubpath[0], os.path.join(tmpisoroot, 'boot/grub/grub.cfg')) -+ for f in kargs_json['files']: -+ if re.match('^EFI/.*/grub.cfg$', f['path']): -+ f['path'] = 'boot/grub/grub.cfg' -+ -+ # safely remove things we don't need in the final ISO tree -+ for d in ['EFI', 'isolinux']: -+ shutil.rmtree(os.path.join(tmpisoroot, d)) -+ -+ # grub2-mkrescue is a wrapper around xorriso -+ genisoargs = ['grub2-mkrescue', '-volid', volid] -+ elif basearch == "s390x": -+ # Reserve 32MB for the kernel, starting memory address of the initramfs -+ # See https://github.com/weldr/lorax/blob/master/share/templates.d/99-generic/s390.tmpl -+ INITRD_ADDRESS = '0x02000000' -+ lorax_templates = '/usr/share/lorax/templates.d/99-generic/config_files/s390' -+ shutil.copy(os.path.join(lorax_templates, 'redhat.exec'), tmpisoimages) -+ with open(os.path.join(lorax_templates, 'generic.ins'), 'r', encoding='utf8') as fp1: -+ with open(os.path.join(tmpisoroot, 'generic.ins'), 'w', encoding='utf8') as fp2: -+ _ = [fp2.write(line.replace('@INITRD_LOAD_ADDRESS@', INITRD_ADDRESS)) for line in fp1] -+ for prmfile in ['cdboot.prm', 'genericdvd.prm', 'generic.prm']: -+ with open(os.path.join(tmpisoimages, prmfile), 'w', encoding='utf8') as fp1: -+ with open(os.path.join(tmpisoroot, 'zipl.prm'), 'r', encoding='utf8') as fp2: -+ fp1.write(fp2.read().strip()) -+ -+ # s390x's z/VM CMS files are limited to 8 char for filenames and extensions -+ # Also it is nice to keep naming convetion with Fedora/RHEL for existing users and code -+ kernel_dest = os.path.join(tmpisoimagespxe, 'kernel.img') -+ shutil.move(os.path.join(tmpisoimagespxe, kernel_img), kernel_dest) -+ kernel_img = 'kernel.img' -+ -+ if test_fixture: -+ # truncate it to 128k so it includes the offsets to the initrd and kargs -+ # https://github.com/ibm-s390-linux/s390-tools/blob/032304d5034e/netboot/mk-s390image#L21-L24 -+ with open(kernel_dest, 'rb+') as f: -+ f.truncate(128 * 1024) -+ with open(iso_initramfs, 'rb+') as f: -+ f.truncate(1024) -+ -+ # On s390x, we reserve space for the Ignition config in the initrd -+ # image directly since the bootloader doesn't support multiple initrds. -+ # We do this by inflating the initramfs just for the duration of the -+ # `mk-s390image` call. -+ initramfs_size = os.stat(iso_initramfs).st_size -+ # sanity-check it's 4-byte aligned (see align_initrd_for_uncompressed_append) -+ assert initramfs_size % 4 == 0 -+ -+ # combine kernel, initramfs and cmdline using the mk-s390image tool -+ os.truncate(iso_initramfs, initramfs_size + IGNITION_IMG_SIZE) -+ subprocess.check_call(['/usr/bin/mk-s390image', -+ kernel_dest, -+ os.path.join(tmpisoimages, 'cdboot.img'), -+ '-r', iso_initramfs, -+ '-p', os.path.join(tmpisoimages, 'cdboot.prm')]) -+ os.truncate(iso_initramfs, initramfs_size) -+ -+ # Get the kargs and initramfs offsets in the cdboot.img. For more info, see: -+ # https://github.com/ibm-s390-linux/s390-tools/blob/032304d5034e/netboot/mk-s390image#L21-L23 -+ CDBOOT_IMG_OFFS_INITRD_START_BYTES = 66568 -+ CDBOOT_IMG_OFFS_KARGS_START_BYTES = 66688 -+ CDBOOT_IMG_OFFS_KARGS_MAX_SIZE = 896 -+ with open(os.path.join(tmpisoimages, 'cdboot.img'), 'rb') as f: -+ f.seek(CDBOOT_IMG_OFFS_INITRD_START_BYTES) -+ offset = struct.unpack(">Q", f.read(8))[0] -+ -+ # sanity-check we're at the right spot by comparing a few bytes -+ f.seek(offset) -+ with open(iso_initramfs, 'rb') as canonical: -+ if f.read(1024) != canonical.read(1024): -+ raise ValueError(f"expected initrd at offset {offset}") -+ -+ igninfo_json = { -+ 'file': 'images/cdboot.img', -+ 'offset': offset + initramfs_size, -+ 'length': IGNITION_IMG_SIZE, -+ } -+ -+ # kargs are part of 'images/cdboot.img' blob -+ kargs_json['files'].append({ -+ 'path': 'images/cdboot.img', -+ 'offset': CDBOOT_IMG_OFFS_KARGS_START_BYTES, -+ 'pad': '\0', -+ 'end': '\0', -+ }) -+ kargs_json.update( -+ size=CDBOOT_IMG_OFFS_KARGS_MAX_SIZE, -+ ) -+ # generate .addrsize file for LPAR -+ with open(os.path.join(tmpisoimages, 'initrd.addrsize'), 'wb') as addrsize: -+ addrsize_data = struct.pack(">iiii", 0, int(INITRD_ADDRESS, 16), 0, -+ os.stat(iso_initramfs).st_size) -+ addrsize.write(addrsize_data) -+ -+ # safely remove things we don't need in the final ISO tree -+ for d in ['EFI', 'isolinux']: -+ shutil.rmtree(os.path.join(tmpisoroot, d)) -+ -+ genisoargs = ['/usr/bin/xorrisofs', '-verbose', -+ '-volid', volid, -+ '-volset', f"{name_version}", -+ '-rational-rock', '-J', '-joliet-long', -+ '-no-emul-boot', '-eltorito-boot', -+ os.path.join(os.path.relpath(tmpisoimages, tmpisoroot), 'cdboot.img')] -+ -+ # Drop zipl.prm as it was either unused (!s390x) or is now no longer -+ # needed (s390x) -+ os.unlink(os.path.join(tmpisoroot, 'zipl.prm')) -+ -+ # For x86_64 and aarch64 UEFI booting -+ if basearch in ("x86_64", "aarch64"): -+ # Create the efiboot.img file. This is a fat32 formatted -+ # filesystem that contains all the files needed for EFI boot -+ # from an ISO. -+ with tempfile.TemporaryDirectory(): -+ -+ # In restrictive environments, setgid, setuid and ownership changes -+ # may be restricted. This sets the file ownership to root and -+ # removes the setgid and setuid bits in the tarball. -+ def strip(tarinfo): -+ tarinfo.uid = 0 -+ tarinfo.gid = 0 -+ if tarinfo.isdir(): -+ tarinfo.mode = 0o755 -+ elif tarinfo.isfile(): -+ tarinfo.mode = 0o0644 -+ return tarinfo -+ -+ tmpimageefidir = os.path.join(workdir, "efi") -+ shutil.copytree(os.path.join(deployed_tree, 'usr/lib/bootupd/updates/EFI'), tmpimageefidir) -+ -+ # Find name of vendor directory -+ vendor_ids = [n for n in os.listdir(tmpimageefidir) if n != "BOOT"] -+ if len(vendor_ids) != 1: -+ raise ValueError(f"did not find exactly one EFI vendor ID: {vendor_ids}") -+ vendor_id = vendor_ids[0] -+ -+ # Always replace live/EFI/{vendor} to actual live/EFI/{vendor_id} -+ # https://github.com/openshift/os/issues/954 -+ dfd = os.open(tmpisoroot, os.O_RDONLY) -+ grubfilepath = ensure_glob('EFI/*/grub.cfg', dir_fd=dfd) -+ if len(grubfilepath) != 1: -+ raise ValueError(f'Found != 1 grub.cfg files: {grubfilepath}') -+ srcpath = os.path.dirname(grubfilepath[0]) -+ if srcpath != f'EFI/{vendor_id}': -+ print(f"Renaming '{srcpath}' to 'EFI/{vendor_id}'") -+ os.rename(srcpath, f"EFI/{vendor_id}", src_dir_fd=dfd, dst_dir_fd=dfd) -+ # And update kargs.json -+ for file in kargs_json['files']: -+ if file['path'] == grubfilepath[0]: -+ file['path'] = f'EFI/{vendor_id}/grub.cfg' -+ os.close(dfd) -+ -+ # Delete fallback and its CSV file. Its purpose is to create -+ # EFI boot variables, which we don't want when booting from -+ # removable media. -+ # -+ # A future shim release will merge fallback.efi into the main -+ # shim binary and enable the fallback behavior when the CSV -+ # exists. But for now, fail if fallback.efi is missing. -+ for path in ensure_glob(os.path.join(tmpimageefidir, "BOOT", "fb*.efi")): -+ os.unlink(path) -+ for path in ensure_glob(os.path.join(tmpimageefidir, vendor_id, "BOOT*.CSV")): -+ os.unlink(path) -+ -+ # Drop vendor copies of shim; we already have it in BOOT*.EFI in -+ # BOOT -+ for path in ensure_glob(os.path.join(tmpimageefidir, vendor_id, "shim*.efi")): -+ os.unlink(path) -+ -+ # Consolidate remaining files into BOOT. shim needs GRUB to be -+ # there, and the rest doesn't hurt. -+ for path in ensure_glob(os.path.join(tmpimageefidir, vendor_id, "*")): -+ shutil.move(path, os.path.join(tmpimageefidir, "BOOT")) -+ os.rmdir(os.path.join(tmpimageefidir, vendor_id)) -+ -+ # Inject a stub grub.cfg pointing to the one in the main ISO image. -+ # -+ # When booting via El Torito, this stub is not used; GRUB reads -+ # the ISO image directly using its own ISO support. This -+ # happens when booting from a CD device, or when the ISO is -+ # copied to a USB stick and booted on EFI firmware which prefers -+ # to boot a hard disk from an El Torito image if it has one. -+ # EDK II in QEMU behaves this way. -+ # -+ # This stub is used with EFI firmware which prefers to boot a -+ # hard disk from an ESP, or which cannot boot a hard disk via El -+ # Torito at all. In that case, GRUB thinks it booted from a -+ # partition of the disk (a fake ESP created by isohybrid, -+ # pointing to efiboot.img) and needs a grub.cfg there. -+ with open(os.path.join(tmpimageefidir, "BOOT", "grub.cfg"), "w", encoding='utf8') as fh: -+ fh.write(f'''search --label "{volid}" --set root --no-floppy -+set prefix=($root)/EFI/{vendor_id} -+echo "Booting via ESP..." -+configfile $prefix/grub.cfg -+boot -+''') -+ -+ # Install binaries from boot partition -+ # Manually construct the tarball to ensure proper permissions and ownership -+ efitarfile = tempfile.NamedTemporaryFile(suffix=".tar") -+ with tarfile.open(efitarfile.name, "w:", dereference=True) as tar: -+ tar.add(tmpimageefidir, arcname="/EFI", filter=strip) -+ -+ # Create the efiboot.img file in the images/ dir -+ efibootfile = os.path.join(tmpisoimages, 'efiboot.img') -+ make_efi_bootfile(loop_client, input_tarball=efitarfile.name, output_efiboot_img=efibootfile) -+ -+ genisoargs += ['-eltorito-alt-boot', -+ '-efi-boot', 'images/efiboot.img', -+ '-no-emul-boot'] -+ -+ # We've done everything that might affect kargs, so filter out any files -+ # that no longer exist and write out the kargs JSON if it lists any files -+ kargs_json['files'] = [f for f in kargs_json['files'] -+ if os.path.exists(os.path.join(tmpisoroot, f['path']))] -+ kargs_json['files'].sort(key=lambda f: f['path']) -+ if kargs_json['files']: -+ # Store the location of "karg embed areas" for use by -+ # `coreos-installer iso kargs modify` -+ with open(os.path.join(tmpisocoreos, kargs_file), 'w', encoding='utf8') as fh: -+ json.dump(kargs_json, fh, indent=2, sort_keys=True) -+ fh.write('\n') -+ -+ # Write out the igninfo.json file. This is used by coreos-installer to know -+ # how to embed the Ignition config. -+ with open(os.path.join(tmpisocoreos, igninfo_file), 'w', encoding='utf8') as fh: -+ json.dump(igninfo_json, fh, indent=2, sort_keys=True) # pylint: disable=E0601 -+ fh.write('\n') -+ -+ # Define inputs and outputs -+ genisoargs_final = genisoargs + ['-o', tmpisofile, tmpisoroot] -+ -+ miniso_data = os.path.join(tmpisocoreos, "miniso.dat") -+ with open(miniso_data, 'wb') as f: -+ f.truncate(MINISO_DATA_FILE_SIZE) -+ -+ if test_fixture: -+ # Replace or delete anything irrelevant to coreos-installer -+ with open(os.path.join(tmpisoimages, 'efiboot.img'), 'w', encoding='utf8') as fh: -+ fh.write('efiboot.img\n') -+ with open(os.path.join(tmpisoimagespxe, 'rootfs.img'), 'w', encoding='utf8') as fh: -+ fh.write('rootfs data\n') -+ with open(os.path.join(tmpisoimagespxe, 'initrd.img'), 'w', encoding='utf8') as fh: -+ fh.write('initrd data\n') -+ with open(os.path.join(tmpisoimagespxe, 'vmlinuz'), 'w', encoding='utf8') as fh: -+ fh.write('the kernel\n') -+ # this directory doesn't exist on s390x -+ if os.path.isdir(tmpisoisolinux): -+ with open(os.path.join(tmpisoisolinux, 'isolinux.bin'), 'rb+') as fh: -+ flen = fh.seek(0, 2) -+ fh.truncate(0) -+ fh.truncate(flen) -+ fh.seek(64) -+ # isohybrid checks for this magic -+ fh.write(b'\xfb\xc0\x78\x70') -+ for f in ensure_glob(os.path.join(tmpisoisolinux, '*.c32')): -+ os.unlink(f) -+ for f in ensure_glob(os.path.join(tmpisoisolinux, '*.msg')): -+ os.unlink(f) -+ -+ subprocess.check_call(genisoargs_final) -+ -+ # Add MBR, and GPT with ESP, for x86_64 BIOS/UEFI boot when ISO is -+ # copied to a USB stick -+ if basearch == "x86_64": -+ subprocess.check_call(['/usr/bin/isohybrid', '--uefi', tmpisofile]) -+ -+ # Copy to final output locations the things that won't change from -+ # this point forward. We do it here because we unlink the the rootfs below. -+ cp_reflink(os.path.join(tmpisoimagespxe, kernel_img), output_kernel) -+ cp_reflink(os.path.join(tmpisoimagespxe, initrd_img), output_initramfs) -+ cp_reflink(iso_rootfs, output_rootfs) -+ -+ # Here we generate a minimal ISO purely for the purpose of -+ # calculating some data such that, given the full live ISO in -+ # future, coreos-installer can regenerate a minimal ISO. -+ # -+ # We first generate the minimal ISO and then call coreos-installer -+ # pack with both the minimal and full ISO as arguments. This will -+ # delete the minimal ISO (i.e. --consume) and update in place the -+ # full Live ISO. -+ # -+ # The only difference in the minimal ISO is that we drop two files. -+ # We keep everything else the same to maximize file matching between -+ # the two versions so we can get the smallest delta. E.g. we keep the -+ # `coreos.liveiso` karg, even though the miniso doesn't need it. -+ # coreos-installer takes care of removing it. -+ os.unlink(iso_rootfs) -+ os.unlink(miniso_data) -+ subprocess.check_call(genisoargs + ['-o', f'{tmpisofile}.minimal', tmpisoroot]) -+ if basearch == "x86_64": -+ subprocess.check_call(['/usr/bin/isohybrid', '--uefi', f'{tmpisofile}.minimal']) -+ with Chroot(deployed_tree, bind_mounts=["/run"]) as chroot: -+ chroot.run(['coreos-installer', 'pack', 'minimal-iso', tmpisofile, -+ f'{tmpisofile}.minimal', '--consume'], check=True) -+ -+ # Copy the ISO to the final output location -+ shutil.move(tmpisofile, output_iso) -+ -+ -+if __name__ == "__main__": -+ args = osbuild.api.arguments() -+ with tempfile.TemporaryDirectory(dir=args["tree"]) as tmdir: -+ r = main(workdir=tmdir, -+ tree=args["tree"], -+ inputs=args["inputs"], -+ options=args["options"], -+ loop_client=remoteloop.LoopClient("/run/osbuild/api/remoteloop")) -+ sys.exit(r) -diff --git a/stages/org.osbuild.coreos.live-artifacts.mono.meta.json b/stages/org.osbuild.coreos.live-artifacts.mono.meta.json -new file mode 100644 -index 00000000..9b41e5b7 ---- /dev/null -+++ b/stages/org.osbuild.coreos.live-artifacts.mono.meta.json -@@ -0,0 +1,68 @@ -+{ -+ "summary": "Build CoreOS Live ISO and PXE kernel,initramfs,rootfs", -+ "description": [ -+ "This stage builds the CoreOS Live ISO and PXE kernel,initramfs,rootfs", -+ "artifacts. Input to this stage are metal and metal4k raw disk images", -+ "that are then used to generate the Live artifacts." -+ ], -+ "capabilities": [ -+ "CAP_MAC_ADMIN" -+ ], -+ "schema_2": { -+ "inputs": { -+ "type": "object", -+ "additionalProperties": false, -+ "required": [ -+ "deployed-tree", -+ "metal", -+ "metal4k" -+ ], -+ "properties": { -+ "deployed-tree": { -+ "type": "object", -+ "additionalProperties": true -+ }, -+ "metal": { -+ "type": "object", -+ "additionalProperties": true -+ }, -+ "metal4k": { -+ "type": "object", -+ "additionalProperties": true -+ } -+ } -+ }, -+ "options": { -+ "additionalProperties": false, -+ "required": [ -+ "filenames" -+ ], -+ "properties": { -+ "filenames": { -+ "type": "object", -+ "additionalProperties": false, -+ "required": [ -+ "live-iso", -+ "live-kernel", -+ "live-initramfs", -+ "live-rootfs" -+ ], -+ "properties": { -+ "live-iso": { -+ "type": "string" -+ }, -+ "live-kernel": { -+ "type": "string" -+ }, -+ "live-initramfs": { -+ "type": "string" -+ }, -+ "live-rootfs": { -+ "type": "string" -+ } -+ } -+ } -+ } -+ } -+ } -+} --- -2.47.0 -