diff --git a/.github/scripts/ci-package.sh b/.github/scripts/ci-package.sh index cb6b0bc..6b0ab62 100755 --- a/.github/scripts/ci-package.sh +++ b/.github/scripts/ci-package.sh @@ -11,7 +11,7 @@ set -euxo pipefail format="$1" -dnf -y install image-builder podman +dnf -y install image-builder podman jq osbuild if [ -n "${GHCR_TOKEN:-}" ]; then podman login ghcr.io -u "${GHCR_USER}" -p "${GHCR_TOKEN}" @@ -30,19 +30,34 @@ cache_dir="${PWD}/.osbuild-cache" case "${format}" in iso) podman pull "${WORKSTATION_ISO_IMAGE}" + manifest_json="${OUTPUT_NAME%.iso}.osbuild-manifest.json" + patched_manifest="${OUTPUT_NAME%.iso}.osbuild-manifest.oci.json" image-builder build \ --cache "${cache_dir}" \ - --bootc-default-fs btrfs \ --output-dir . \ --output-name "${OUTPUT_NAME}" \ + --with-manifest \ --bootc-ref "${WORKSTATION_ISO_IMAGE}" \ --bootc-installer-payload-ref "${WORKSTATION_IMAGE}" \ bootc-generic-iso + test -f "${manifest_json}" + # Embed the payload as an OCI layout (ready-made layer blobs) instead of + # a containers-storage blob, so bootc install streams it straight to + # disk instead of re-tarring each layer into RAM. Same manifest patch + # the local Justfile flow applies. + bash tools/patch-iso-payload-to-oci.sh "${manifest_json}" "${patched_manifest}" + rm -rf bootiso + osbuild \ + --store "${cache_dir}" \ + --output-directory . \ + --export bootiso \ + "${patched_manifest}" + mv -f bootiso/install.iso "${OUTPUT_NAME}" + rm -rf bootiso ;; qcow2) image-builder build \ --cache "${cache_dir}" \ - --bootc-default-fs btrfs \ --output-dir . \ --output-name "${OUTPUT_NAME}" \ --bootc-ref "${WORKSTATION_IMAGE}" \ diff --git a/AGENTS.md b/AGENTS.md index e0736e4..2f5d82a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -36,7 +36,7 @@ Use `QEMU_RESET_DISK=1 just qemu iso` when the installer test needs a fresh disk The workstation ISO uses Sirius (the LuminusOS installer, consumed as a prebuilt RPM from its GitHub release) and installs the embedded workstation image from container storage. ISO packaging uses `luminusos-workstation:-iso` as the live root and `luminusos-workstation:` as the installed payload. The live ISO itself is not booted as a bootc deployment. -Installed Linux filesystems should be Btrfs in the Sirius ISO install path. Keep `--bootc-default-fs btrfs` and the Sirius repart templates (`files/usr/share/sirius/repart.d/`) aligned when changing that storage layout. The direct qcow2 `image-builder` path uses `/usr/lib/image-builder/bootc/disk.yaml`; keep root/home/var Btrfs there, but `/boot` must remain ext4 because image-builder qcow2 generation does not support Btrfs for `/boot`. The ISO boot menu is configured through `/usr/lib/image-builder/bootc/iso.yaml`. +Installed Linux filesystems should be Btrfs in the Sirius ISO install path. Keep `/usr/lib/image-builder/bootc/disk.yaml` (which image-builder uses for the filesystem layout, overriding `--bootc-default-fs`) and the Sirius repart templates (`files/usr/share/sirius/repart.d/`) aligned when changing that storage layout: root/home/var stay Btrfs, but `/boot` must remain ext4 because image-builder qcow2 generation does not support Btrfs for `/boot`. The ISO boot menu is configured through `/usr/lib/image-builder/bootc/iso.yaml`. Sirius is configured through `/etc/sirius/distro.toml` and `/etc/sirius/sirius.toml`, installed only into the ISO live root by `editions/workstation/Containerfile.installer`. The installed workstation payload should not ship Sirius. diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 92d3ee1..5d6fb79 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -311,7 +311,6 @@ The live ISO is not itself a bootc deployment. It is generated from the workstat | `/usr/lib/systemd/user/luminusos-sirius.service` | Starts Sirius in the live user session. | | `/etc/dconf/db/local.d/00-iso-live-mode` | Live ISO GNOME Shell and Aurora Shell module defaults. | | `/usr/share/polkit-1/rules.d/50-sirius-live.rules` | Lets `liveuser` run the privileged Sirius install action without authentication. | -| `/usr/lib/systemd/system/var-tmp.mount` | Live-only tmpfs on `/var/tmp` (75% of RAM) so `bootc install` can stage the compressed payload. | ```mermaid flowchart TD @@ -371,7 +370,7 @@ args = ["--skip-fetch-check"] repart_dir = "/usr/share/sirius/repart.d" ``` -`image` points at the payload embedded in the live ISO; `target_imgref` is the reference the installed system uses for future `bootc upgrade`. `/etc/sirius/sirius.toml` gates the wizard: keyboard/timezone/user pages are disabled (GNOME Initial Setup owns user creation) and diagnostics require UEFI, ≥5 GiB usable RAM, and enough disk space — the RAM gate exists because `bootc install` stages the ~2.5 GiB compressed payload in the live tmpfs `/var/tmp` (see `var-tmp.mount`). +`image` points at the payload embedded in the live ISO as an OCI layout (`oci:/usr/lib/luminusos/payload.oci:latest`, see [Install Memory Staging](#install-memory-staging)); `target_imgref` is the reference the installed system uses for future `bootc upgrade`. `/etc/sirius/sirius.toml` gates the wizard: keyboard/timezone/user pages are disabled (GNOME Initial Setup owns user creation) and diagnostics require UEFI, ≥2 GiB usable RAM, and enough disk space. ## Installation Flow @@ -398,7 +397,7 @@ flowchart TD The Sirius ISO install storage model must remain aligned across: -- `--bootc-default-fs btrfs` in the `Justfile`. +- `/usr/lib/image-builder/bootc/disk.yaml` in the workstation image (btrfs root), which image-builder uses for both the ISO install path and direct qcow2 artifacts — it overrides `--bootc-default-fs`. - Sirius repart templates under `/usr/share/sirius/repart.d/`. Expected layout: @@ -443,7 +442,7 @@ flowchart TD ### Live-Only Isolation -The workstation payload never contains live-only installer artifacts: Sirius, its configs (`/etc/sirius/`), the live polkit rule, the `live-installer` session files, `var-tmp.mount`, and the `liveuser` autologin setup are added only in `Containerfile.installer`, on top of the payload image. The bootc install path deploys the clean payload image, so no exclusion list is needed — live files simply never existed in the payload. +The workstation payload never contains live-only installer artifacts: Sirius, its configs (`/etc/sirius/`), the live polkit rule, the `live-installer` session files, and the `liveuser` autologin setup are added only in `Containerfile.installer`, on top of the payload image. The bootc install path deploys the clean payload image, so no exclusion list is needed — live files simply never existed in the payload. Sirius itself belongs to the installer image, not to the installed workstation payload. @@ -472,12 +471,13 @@ flowchart TD ## Install Memory Staging -`bootc install` stages the compressed payload copy (~2.5 GiB) under `/var/tmp` while the live session itself runs from RAM. Two live-only pieces make that fit: +`bootc-generic-iso` normally embeds the installer payload as a `containers-storage` blob. `bootc install` can't stream that directly: containers/storage keeps layers already unpacked on disk, so install has to re-diff and re-tar each layer into a large `/var/tmp` staging area (~2.5 GiB compressed) before it can deploy them. On a live ISO, `/var/tmp` has nowhere to go but RAM, which is why installs used to need a dedicated tmpfs and a ~5 GiB RAM gate. -- `/usr/lib/systemd/system/var-tmp.mount` mounts a dedicated tmpfs at `/var/tmp` sized at 75% of RAM, only when booted with `rd.live.image`. -- `/etc/containers/containers.conf.d/99-luminusos-bootc-install-tmp.conf` points podman/bootc staging (`image_copy_tmp_dir`) at that tmpfs. +Instead, `just package iso` generates the `bootc-generic-iso` osbuild manifest, patches the one `org.osbuild.skopeo` stage in the `os-tree` pipeline to embed the payload as an **OCI layout** (`destination: {type: oci, path: /usr/lib/luminusos/payload.oci:latest}`) instead of `containers-storage`, and runs `osbuild` directly against the patched manifest (see `tools/patch-iso-payload-to-oci.sh`). The `:latest` suffix in the destination path is load-bearing: `skopeo copy oci:...` strips it from the on-disk directory name and records it as the `org.opencontainers.image.ref.name` annotation in `index.json`, which is what makes the `oci:/usr/lib/luminusos/payload.oci:latest` reference resolvable. OCI layout blobs are already ready-made layer tarballs, so `bootc install --source-imgref oci:/usr/lib/luminusos/payload.oci:latest` (set in `distro.toml`) streams them straight to the target disk — no re-tar, no large staging area. This mirrors how Anaconda embeds ostree-native container payloads. -The Sirius diagnostics gate (`min_ram_gib = 5`) blocks machines that cannot hold the staging. +`image-builder-cli` has no flag for the OCI destination, so the manifest patch is a bridge until `osbuild/images` grows one upstream; the jq patch fails loudly if the manifest shape it depends on changes. + +The Sirius diagnostics gate (`min_ram_gib = 2`) only needs to cover the live GNOME session now. ## Workstation Artifact Packaging @@ -489,7 +489,6 @@ Conceptual command: ```bash sudo image-builder build \ - --bootc-default-fs btrfs \ --output-dir . \ --output-name luminusos-workstation-.iso \ --bootc-ref \ @@ -505,7 +504,6 @@ Conceptual command: ```bash sudo image-builder build \ - --bootc-default-fs btrfs \ --output-dir . \ --output-name luminusos-workstation-.qcow2 \ --bootc-ref \ @@ -630,8 +628,7 @@ flowchart TD When changing this repository, keep these points aligned: - `Justfile`, `README.md`, `ARCHITECTURE.md`, and `AGENTS.md` must agree on active commands and supported editions. -- `--bootc-default-fs btrfs` and the Sirius repart templates must describe the same ISO install storage layout. -- `disk.yaml` must keep root/home/var Btrfs for direct qcow2 artifacts, with `/boot` ext4 for image-builder compatibility. +- `disk.yaml` must keep root/home/var Btrfs with `/boot` ext4 (image-builder compatibility) and describe the same storage layout as the Sirius repart templates; image-builder takes the filesystem from `disk.yaml`, not from `--bootc-default-fs`. - Any live-only file added under `editions/workstation/files/` must be installed only by `Containerfile.installer` so it stays out of the workstation payload. - Changes to `live-installer` must consider the Wayland session file, GNOME session file, GNOME Shell mode JSON, systemd user drop-in, and Aurora Shell metadata. - If the install flow changes, update `/etc/sirius/distro.toml` / `sirius.toml` and this document. diff --git a/Justfile b/Justfile index 417d162..b6e76aa 100644 --- a/Justfile +++ b/Justfile @@ -246,7 +246,6 @@ package edition="workstation" format="all": shift 3 sudo image-builder build \ - --bootc-default-fs btrfs \ --output-dir . \ --output-name "$output" \ "$@" \ @@ -261,17 +260,66 @@ package edition="workstation" format="all": echo "Wrote artifact pointer: $pointer -> $path" } + # bootc-generic-iso embeds the installer payload as a containers-storage + # blob, which forces `bootc install` to re-diff/tar each layer into + # /var/tmp at install time (~2.5 GiB, hence the tmpfs var-tmp.mount and + # the 5 GiB RAM gate). osbuild's skopeo stage also supports an "oci" + # destination, which stores ready-made layer blobs — no re-tar, no + # large staging area — matching how Anaconda embeds ostree-native + # container payloads. image-builder-cli has no flag for this, so we + # generate the manifest, patch that one stage, and run osbuild directly. + patch_iso_payload_to_oci() { + ./tools/patch-iso-payload-to-oci.sh "$1" "$2" + } + package_iso() { build_iso_image - if [[ "$iso_image_ref" == localhost/* ]]; then - echo "No post-build squash needed for the workstation ISO image: Containerfile.installer squashes itself into a single layer" - fi - echo "Building workstation ISO from $iso_image_ref with payload $image_ref" - run_image_builder bootc-generic-iso \ - "luminusos-workstation-${package_tag}.iso" .test/last-iso \ + local out_name="luminusos-workstation-${package_tag}.iso" + local ib_cache="$(pwd)/.test/image-builder-cache" + local manifest_json=".test/${package_tag}.osbuild-manifest.json" + local patched_manifest=".test/${package_tag}.osbuild-manifest.oci.json" + mkdir -p "$ib_cache" + + echo "Generating osbuild manifest for bootc-generic-iso (payload: $image_ref)" + sudo image-builder build \ + --output-dir . \ + --output-name "$out_name" \ + --cache "$ib_cache" \ + --with-manifest \ --bootc-ref "$iso_image_ref" \ - --bootc-installer-payload-ref "$image_ref" + --bootc-installer-payload-ref "$image_ref" \ + bootc-generic-iso + + local generated_manifest="${out_name%.iso}.osbuild-manifest.json" + if [ ! -f "$generated_manifest" ]; then + echo "Expected osbuild manifest was not generated: $generated_manifest" + exit 1 + fi + mv -f "$generated_manifest" "$manifest_json" + + echo "Patching installer payload embed: containers-storage -> oci" + patch_iso_payload_to_oci "$manifest_json" "$patched_manifest" + + echo "Rebuilding ISO from the patched manifest (payload embedded as OCI layout)" + sudo rm -rf bootiso + sudo osbuild \ + --store "$ib_cache" \ + --output-directory . \ + --export bootiso \ + "$patched_manifest" + + if [ ! -f bootiso/install.iso ]; then + echo "Expected osbuild export not found: bootiso/install.iso" + exit 1 + fi + sudo mv -f bootiso/install.iso "$out_name" + sudo rm -rf bootiso + sudo chown "$(id -u):$(id -g)" "$out_name" + + local iso_path="$(pwd)/$out_name" + printf '%s\n' "$iso_path" > .test/last-iso + echo "Wrote artifact pointer: .test/last-iso -> $iso_path" } package_qcow2() { diff --git a/editions/workstation/Containerfile.installer b/editions/workstation/Containerfile.installer index d24ae45..68d8aa8 100644 --- a/editions/workstation/Containerfile.installer +++ b/editions/workstation/Containerfile.installer @@ -47,9 +47,6 @@ RUN --mount=type=cache,target=/var/cache/dnf \ RUN --mount=type=bind,from=ctx-files,source=/workstation/files,target=/wfiles \ install -Dm0644 /wfiles/etc/dconf/db/local.d/00-iso-live-mode /etc/dconf/db/local.d/00-iso-live-mode && \ install -Dm0644 /wfiles/etc/dconf/profile/user /etc/dconf/profile/user && \ - install -Dm0644 /wfiles/etc/containers/containers.conf.d/99-luminusos-bootc-install-tmp.conf /etc/containers/containers.conf.d/99-luminusos-bootc-install-tmp.conf && \ - install -Dm0644 /wfiles/usr/lib/systemd/system/var-tmp.mount /usr/lib/systemd/system/var-tmp.mount && \ - systemctl enable var-tmp.mount && \ install -Dm0644 /wfiles/usr/lib/image-builder/bootc/iso.yaml /usr/lib/image-builder/bootc/iso.yaml && \ install -Dm0644 /wfiles/usr/share/wayland-sessions/live-installer.desktop /usr/share/wayland-sessions/live-installer.desktop && \ install -Dm0644 /wfiles/usr/share/gnome-session/sessions/live-installer.session /usr/share/gnome-session/sessions/live-installer.session && \ @@ -58,10 +55,9 @@ RUN --mount=type=bind,from=ctx-files,source=/workstation/files,target=/wfiles \ RUN --mount=type=bind,from=ctx-files,source=/workstation/files,target=/wfiles \ install -Dm0644 /wfiles/etc/sirius/distro.toml /etc/sirius/distro.toml && \ sed -i \ - -e "s|@WORKSTATION_IMAGE@|${workstation_image}|g" \ -e "s|@WORKSTATION_TARGET_IMAGE@|${workstation_target_image}|g" \ /etc/sirius/distro.toml && \ - grep -q 'image = "containers-storage:'"${workstation_image}"'"' /etc/sirius/distro.toml && \ + grep -q 'image = "oci:/usr/lib/luminusos/payload.oci:latest"' /etc/sirius/distro.toml && \ grep -q 'target_imgref = "'"${workstation_target_image}"'"' /etc/sirius/distro.toml && \ install -Dm0644 /wfiles/etc/sirius/sirius.toml /etc/sirius/sirius.toml && \ install -Dm0644 /wfiles/usr/share/polkit-1/rules.d/50-sirius-live.rules /usr/share/polkit-1/rules.d/50-sirius-live.rules && \ @@ -163,7 +159,6 @@ RUN command -v sirius && \ test -z "$(find / -xdev -name '.wh.*' -print -quit)" && \ test -f /etc/sirius/distro.toml && \ test -f /etc/sirius/sirius.toml && \ - test -f /etc/containers/containers.conf.d/99-luminusos-bootc-install-tmp.conf && \ # Exactly the three LuminusOS repart definitions: the RPM's generic # 10-esp/20-root pair must be gone or systemd-repart would see five. test ! -e /usr/share/sirius/repart.d/10-esp.conf && \ diff --git a/editions/workstation/files/etc/containers/containers.conf.d/99-luminusos-bootc-install-tmp.conf b/editions/workstation/files/etc/containers/containers.conf.d/99-luminusos-bootc-install-tmp.conf deleted file mode 100644 index e5c1236..0000000 --- a/editions/workstation/files/etc/containers/containers.conf.d/99-luminusos-bootc-install-tmp.conf +++ /dev/null @@ -1,2 +0,0 @@ -[engine] -image_copy_tmp_dir = "/var/tmp" diff --git a/editions/workstation/files/etc/sirius/distro.toml b/editions/workstation/files/etc/sirius/distro.toml index a89ec81..4bd4e75 100644 --- a/editions/workstation/files/etc/sirius/distro.toml +++ b/editions/workstation/files/etc/sirius/distro.toml @@ -1,5 +1,9 @@ [bootc] -image = "containers-storage:@WORKSTATION_IMAGE@" +# The ISO embeds the payload as an OCI layout at this fixed path (see +# `package_iso()` in the top-level Justfile) instead of containers-storage, +# so `bootc install` streams ready-made layer blobs straight to disk +# instead of re-tarring each layer into a large /var/tmp staging area. +image = "oci:/usr/lib/luminusos/payload.oci:latest" target_imgref = "@WORKSTATION_TARGET_IMAGE@" # TODO: sign the published images (cosign/sigstore) and flip this to true — # today the ghcr.io images are unsigned, so verification has nothing to check. diff --git a/editions/workstation/files/etc/sirius/sirius.toml b/editions/workstation/files/etc/sirius/sirius.toml index 852b157..db38951 100644 --- a/editions/workstation/files/etc/sirius/sirius.toml +++ b/editions/workstation/files/etc/sirius/sirius.toml @@ -4,10 +4,8 @@ disabled = ["keyboard", "timezone", "user"] [diagnostics] require = ["uefi", "ram", "disk_space"] warn = ["secure_boot", "network", "virt"] -# bootc stages the compressed image copy (~2.5 GiB) in /var/tmp, a dedicated -# tmpfs capped at 75% of RAM (see var-tmp.mount), while the live session -# itself also runs from RAM. Installs need roughly 6 GB of memory. The probe -# measures USABLE RAM (a "6 GB" machine reports ~5.7 GiB after -# kernel/firmware reservations), so the gate sits at 5 GiB: 6 GB machines -# pass, machines that cannot hold the staging are blocked. -min_ram_gib = 5 +# The installer payload is embedded as an OCI layout (see distro.toml), so +# `bootc install` streams layer blobs straight to disk instead of staging a +# re-tarred copy in RAM. The gate only needs to cover the live GNOME +# session itself. +min_ram_gib = 2 diff --git a/editions/workstation/files/usr/lib/systemd/system/var-tmp.mount b/editions/workstation/files/usr/lib/systemd/system/var-tmp.mount deleted file mode 100644 index a3e6dcf..0000000 --- a/editions/workstation/files/usr/lib/systemd/system/var-tmp.mount +++ /dev/null @@ -1,26 +0,0 @@ -# Large RAM-backed /var/tmp for the live installer session. -# -# The live root's writable overlay keeps its upperdir in /run, a tmpfs the -# kernel caps at 20% of RAM (1.6 GiB on an 8 GiB machine). `bootc install` -# stages the compressed container image (~2.5 GiB) in /var/tmp before -# deploying, which can never fit there. A dedicated tmpfs on /var/tmp lifts -# that ceiling to 75% of RAM without touching /run or the overlay. -# -# Modeled after systemd's stock tmp.mount. - -[Unit] -Description=Large temporary directory for bootc image staging (/var/tmp) -ConditionKernelCommandLine=rd.live.image -DefaultDependencies=no -Conflicts=umount.target -Before=local-fs.target umount.target -After=swap.target - -[Mount] -What=tmpfs -Where=/var/tmp -Type=tmpfs -Options=mode=1777,strictatime,nosuid,nodev,size=75% - -[Install] -WantedBy=local-fs.target diff --git a/tests/run.sh b/tests/run.sh index c602107..f041dfc 100755 --- a/tests/run.sh +++ b/tests/run.sh @@ -62,11 +62,11 @@ for toml in "${WORKSTATION_FILES}/etc/sirius/distro.toml" \ python3 -c 'import sys, tomllib; tomllib.load(open(sys.argv[1], "rb"))' "${toml}" done -# ── distro.toml still carries the build-time placeholders ───────────── -expect "distro.toml has @WORKSTATION_IMAGE@ placeholder" \ - grep -q '@WORKSTATION_IMAGE@' "${WORKSTATION_FILES}/etc/sirius/distro.toml" +# ── distro.toml still carries the build-time placeholder ────────────── expect "distro.toml has @WORKSTATION_TARGET_IMAGE@ placeholder" \ grep -q '@WORKSTATION_TARGET_IMAGE@' "${WORKSTATION_FILES}/etc/sirius/distro.toml" +expect "distro.toml points bootc install at the embedded OCI payload" \ + grep -q 'image = "oci:/usr/lib/luminusos/payload.oci:latest"' "${WORKSTATION_FILES}/etc/sirius/distro.toml" # ── JSON files are valid ────────────────────────────────────────────── while IFS= read -r json; do @@ -98,6 +98,21 @@ expect "repart.d ships exactly 3 definitions" \ expect "repart root is btrfs" grep -q 'Format=btrfs' "${repart_dir}/10-root.conf" expect "repart esp exists" test -f "${repart_dir}/30-esp.conf" +# ── patch-iso-payload-to-oci.sh ───────────────────────────────────────── +cat >"${tmp}/manifest.json" <<'EOF' +{"pipelines":[{"name":"os-tree","stages":[{"type":"org.osbuild.skopeo","options":{"destination":{"type":"containers-storage"}}}]}]} +EOF +bash "${ROOT}/tools/patch-iso-payload-to-oci.sh" "${tmp}/manifest.json" "${tmp}/patched.json" +expect "payload patch writes oci destination with :latest tag" \ + jq -e '.pipelines[0].stages[0].options.destination == {"type":"oci","path":"/usr/lib/luminusos/payload.oci:latest"}' \ + "${tmp}/patched.json" +echo '{"pipelines":[{"name":"os-tree","stages":[]}]}' >"${tmp}/bad-manifest.json" +if bash "${ROOT}/tools/patch-iso-payload-to-oci.sh" "${tmp}/bad-manifest.json" "${tmp}/x.json" >/dev/null 2>&1; then + fail "payload patch rejects manifest without the skopeo stage" +else + pass "payload patch rejects manifest without the skopeo stage" +fi + # ── Justfile parses and lists recipes ───────────────────────────────── if command -v just >/dev/null 2>&1; then expect "Justfile parses" just --justfile "${ROOT}/Justfile" --list diff --git a/tools/patch-iso-payload-to-oci.sh b/tools/patch-iso-payload-to-oci.sh new file mode 100755 index 0000000..bd267dc --- /dev/null +++ b/tools/patch-iso-payload-to-oci.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +# Patch the bootc-generic-iso osbuild manifest so the installer payload is +# embedded as an OCI layout instead of a containers-storage blob. +# +# bootc-generic-iso embeds the payload as a containers-storage blob, which +# forces `bootc install` to re-diff/re-tar each layer into /var/tmp at +# install time (~2.5 GiB). On a live ISO that has nowhere to go but RAM. +# osbuild's skopeo stage also supports an "oci" destination, which stores +# ready-made layer blobs — no re-tar, no large staging area — matching how +# Anaconda embeds ostree-native container payloads. +# +# The destination path carries the ":latest" tag on purpose: the skopeo +# stage passes it straight to `skopeo copy oci:...`, which strips the tag +# from the on-disk directory name and records it as the +# org.opencontainers.image.ref.name annotation in index.json. Without it +# the layout has no ref name and bootc fails with 'no descriptor found for +# reference "latest"' (distro.toml points at the :latest reference). +# +# Usage: patch-iso-payload-to-oci.sh +set -euo pipefail + +manifest_json="$1" +patched_manifest="$2" + +skopeo_count="$(jq '[.pipelines[] | select(.name == "os-tree") | .stages[] | select(.type == "org.osbuild.skopeo")] | length' "$manifest_json")" +if [ "$skopeo_count" != "1" ]; then + echo "Expected exactly 1 org.osbuild.skopeo stage in the 'os-tree' pipeline, found $skopeo_count" >&2 + echo "osbuild/images changed the bootc-generic-iso manifest shape; update tools/patch-iso-payload-to-oci.sh" >&2 + exit 1 +fi + +jq ' + (.pipelines[] | select(.name == "os-tree") | .stages[] | select(.type == "org.osbuild.skopeo") | .options.destination) + = {"type": "oci", "path": "/usr/lib/luminusos/payload.oci:latest"} +' "$manifest_json" >"$patched_manifest" diff --git a/tools/qemu.sh b/tools/qemu.sh index ba3778c..f32d8ff 100755 --- a/tools/qemu.sh +++ b/tools/qemu.sh @@ -17,11 +17,11 @@ if [ -f .env ]; then fi # default values for QEMU -# NOTE: the live session stages the bootc image copy in RAM (/var/tmp is a -# dedicated tmpfs capped at 75% of RAM, see var-tmp.mount in the installer -# image). The compressed payload is ~2.5 GiB, so the installer needs at -# least ~6 GiB of guest memory; 8 GiB is safe. -: ${QEMU_MEM:="8G"} +# NOTE: the installer payload is embedded as an OCI layout, so `bootc +# install` streams layer blobs straight to disk instead of staging a +# re-tarred copy in RAM (see distro.toml). 4 GiB comfortably covers the +# live GNOME session plus headroom. +: ${QEMU_MEM:="4G"} : ${QEMU_CPU:="4"} : ${QEMU_BOOT:="uefi"} : ${QEMU_DISPLAY:="auto"}