Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions .github/scripts/ci-package.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
Expand All @@ -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}" \
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:<tag>-iso` as the live root and `luminusos-workstation:<tag>` 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.

Expand Down
21 changes: 9 additions & 12 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand All @@ -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:
Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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

Expand All @@ -489,7 +489,6 @@ Conceptual command:

```bash
sudo image-builder build \
--bootc-default-fs btrfs \
--output-dir . \
--output-name luminusos-workstation-<tag>.iso \
--bootc-ref <workstation-iso-image> \
Expand All @@ -505,7 +504,6 @@ Conceptual command:

```bash
sudo image-builder build \
--bootc-default-fs btrfs \
--output-dir . \
--output-name luminusos-workstation-<tag>.qcow2 \
--bootc-ref <workstation-image> \
Expand Down Expand Up @@ -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.
Expand Down
64 changes: 56 additions & 8 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,6 @@ package edition="workstation" format="all":
shift 3

sudo image-builder build \
--bootc-default-fs btrfs \
--output-dir . \
--output-name "$output" \
"$@" \
Expand All @@ -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() {
Expand Down
7 changes: 1 addition & 6 deletions editions/workstation/Containerfile.installer
Original file line number Diff line number Diff line change
Expand Up @@ -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 && \
Expand All @@ -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 && \
Expand Down Expand Up @@ -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 && \
Expand Down

This file was deleted.

6 changes: 5 additions & 1 deletion editions/workstation/files/etc/sirius/distro.toml
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
12 changes: 5 additions & 7 deletions editions/workstation/files/etc/sirius/sirius.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
26 changes: 0 additions & 26 deletions editions/workstation/files/usr/lib/systemd/system/var-tmp.mount

This file was deleted.

Loading