diff --git a/.github/workflows/container.yml b/.github/workflows/container.yml index e28c613..fb85567 100644 --- a/.github/workflows/container.yml +++ b/.github/workflows/container.yml @@ -103,3 +103,36 @@ jobs: tags: ghcr.io/${{ github.repository_owner }}/composefs-os:ubuntu-26.04-uki-sb cache-from: type=gha,scope=ubuntu cache-to: type=gha,mode=max,scope=ubuntu + + - name: Build and push composefs-os:arch-latest (GRUB) + uses: docker/build-push-action@v6 + with: + context: . + file: Containerfile.arch + target: grub + push: true + tags: ghcr.io/${{ github.repository_owner }}/composefs-os:arch-latest + cache-from: type=gha,scope=arch + cache-to: type=gha,mode=max,scope=arch + + - name: Build and push composefs-os:arch-latest-uki (systemd-boot + UKI) + uses: docker/build-push-action@v6 + with: + context: . + file: Containerfile.arch + target: uki + push: true + tags: ghcr.io/${{ github.repository_owner }}/composefs-os:arch-latest-uki + cache-from: type=gha,scope=arch + cache-to: type=gha,mode=max,scope=arch + + - name: Build and push composefs-os:arch-latest-uki-sb (systemd-boot + UKI + Secure Boot) + uses: docker/build-push-action@v6 + with: + context: . + file: Containerfile.arch + target: uki-secureboot + push: true + tags: ghcr.io/${{ github.repository_owner }}/composefs-os:arch-latest-uki-sb + cache-from: type=gha,scope=arch + cache-to: type=gha,mode=max,scope=arch diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 5082fea..c0e881e 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -159,6 +159,143 @@ jobs: disk-uki-sb.raw retention-days: 1 + e2e-arch: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Enable KVM + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' \ + | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + + - name: Install dependencies + run: | + # noble's buildah 1.33 does not support COPY < /rootfs/usr/lib/dracut/modules.d/37composefs/module-setup.sh <<'EOF' +#!/usr/bin/bash +check() { return 0; } +depends() { return 0; } +install() { + inst "${moddir}/composefs-setup-root" /bin/composefs-setup-root + inst "${moddir}/composefs-setup-root.service" \ + "${systemdsystemunitdir}/composefs-setup-root.service" + $SYSTEMCTL -q --root "${initdir}" add-wants \ + initrd-root-fs.target composefs-setup-root.service +} +EOF +RUN chmod +x /rootfs/usr/lib/dracut/modules.d/37composefs/module-setup.sh +RUN cat > /rootfs/usr/lib/dracut/modules.d/37composefs/composefs-setup-root.service <<'EOF' +[Unit] +DefaultDependencies=no +ConditionKernelCommandLine=composefs +ConditionPathExists=/etc/initrd-release +After=sysroot.mount +Requires=sysroot.mount +Before=initrd-root-fs.target +Before=initrd-switch-root.target +OnFailure=emergency.target +OnFailureJobMode=isolate + +[Service] +Type=oneshot +ExecStart=/bin/composefs-setup-root +StandardInput=null +StandardOutput=journal +StandardError=journal+console +RemainAfterExit=yes +EOF + +# Build initramfs using dracut --sysroot (no chroot or bind-mounts needed). +# Arch installs the kernel to /boot/vmlinuz-linux; copy it to the path +# expected by the cbootc image contract (/usr/lib/modules//vmlinuz). +RUN KVER=$(ls /rootfs/usr/lib/modules/ | head -1) && \ + [ -n "$KVER" ] || { echo "ERROR: no kernel found in /rootfs/usr/lib/modules" >&2; exit 1; } && \ + dracut --sysroot /rootfs \ + --force --no-hostonly --kver "$KVER" \ + --tmpdir /tmp \ + --add "systemd systemd-initrd dbus base bash composefs" \ + --add-drivers "overlay erofs loop virtio_blk virtio_pci vfat" \ + --reproducible \ + "/rootfs/usr/lib/modules/$KVER/initramfs.img" && \ + cp /rootfs/boot/vmlinuz-linux /rootfs/usr/lib/modules/$KVER/vmlinuz + +# --------------------------------------------------------------------------- +# Install cfsctl, cbootc, and update units. +# Enable services — symlinks land in /rootfs/etc/systemd (preserved in image). +# --------------------------------------------------------------------------- +COPY --from=composefs-builder /build/target/release/cfsctl /rootfs/usr/bin/cfsctl +COPY --from=cbootc-builder /build/target/release/cbootc /rootfs/usr/bin/cbootc +COPY units/cbootc-update.service units/cbootc-update.timer \ + /rootfs/usr/lib/systemd/system/ + +RUN systemctl --root /rootfs enable \ + dbus-broker.service \ + NetworkManager.service systemd-resolved.service cbootc-update.timer && \ + systemctl --root /rootfs set-default multi-user.target && \ + systemctl --root /rootfs mask systemd-firstboot.service + +# --------------------------------------------------------------------------- +# composefs-required filesystem structure +# /sysroot — mount point for the underlying ext4 after switch_root +# /home /opt /srv — conventional symlinks into /var +# --------------------------------------------------------------------------- +RUN mkdir -p /rootfs/sysroot && \ + mkdir -p /rootfs/var/home /rootfs/var/opt /rootfs/var/srv && \ + rm -rf /rootfs/home /rootfs/opt /rootfs/srv && \ + ln -sf var/home /rootfs/home && \ + ln -sf var/opt /rootfs/opt && \ + ln -sf var/srv /rootfs/srv + +# --------------------------------------------------------------------------- +# Stage 2a: GRUB — install grub and efibootmgr, clear /boot. +# At install time cbootc uses grub-mkstandalone to build the EFI binary from +# grub's module files (same code path as Ubuntu; Arch has 'grub', not 'grub2'). +# --------------------------------------------------------------------------- +FROM rootfs-common AS rootfs-grub +RUN pacman -r /rootfs --noconfirm -Syu grub efibootmgr +RUN rm -rf /rootfs/boot/* + +# --------------------------------------------------------------------------- +# Stage 2b: systemd-boot — add systemd-ukify, clear /boot. +# systemd-boot (bootctl) is included in the systemd package already installed +# in rootfs-common. +# --------------------------------------------------------------------------- +FROM rootfs-common AS rootfs-uki +RUN pacman -r /rootfs --noconfirm -Syu systemd-ukify +RUN rm -rf /rootfs/boot/* + +# --------------------------------------------------------------------------- +# Stage 2c: systemd-boot + Secure Boot — adds sbsigntools and openssl so +# cbootc can sign systemd-boot and UKIs with a self-generated key at install +# time. No shim needed: firmware verifies against the UEFI Signature Database. +# --------------------------------------------------------------------------- +FROM rootfs-common AS rootfs-uki-sb +RUN pacman -r /rootfs --noconfirm -Syu systemd-ukify sbsigntools openssl +RUN rm -rf /rootfs/boot/* + +# --------------------------------------------------------------------------- +# Final stage: GRUB (default target, backward-compat) +# --------------------------------------------------------------------------- +FROM scratch AS grub +COPY --from=rootfs-grub /rootfs / + +LABEL containers.bootc=1 +LABEL composefs.backend=cfs-rs +LABEL org.opencontainers.image.title="Arch Linux CFS Base" +LABEL org.opencontainers.image.description="Base image for composefs-rs bootable Arch Linux systems (GRUB)" + +CMD ["/sbin/init"] + +# --------------------------------------------------------------------------- +# Final stage: UKI (--target uki) +# --------------------------------------------------------------------------- +FROM scratch AS uki +COPY --from=rootfs-uki /rootfs / + +LABEL containers.bootc=1 +LABEL composefs.backend=cfs-rs +LABEL cbootc.boot-style=uki +LABEL org.opencontainers.image.title="Arch Linux CFS Base (UKI)" +LABEL org.opencontainers.image.description="Base image for composefs-rs bootable Arch Linux systems (systemd-boot + UKI)" + +CMD ["/sbin/init"] + +# --------------------------------------------------------------------------- +# Final stage: UKI + Secure Boot (--target uki-secureboot) +# --------------------------------------------------------------------------- +FROM scratch AS uki-secureboot +COPY --from=rootfs-uki-sb /rootfs / + +LABEL containers.bootc=1 +LABEL composefs.backend=cfs-rs +LABEL cbootc.boot-style=uki-secureboot +LABEL org.opencontainers.image.title="Arch Linux CFS Base (UKI + Secure Boot)" +LABEL org.opencontainers.image.description="Base image for composefs-rs bootable Arch Linux systems (systemd-boot + UKI + Secure Boot)" + +CMD ["/sbin/init"] diff --git a/examples/arch/Containerfile b/examples/arch/Containerfile index e5f11ef..d89e915 100644 --- a/examples/arch/Containerfile +++ b/examples/arch/Containerfile @@ -1,65 +1,46 @@ -# Minimal bootable Arch image for composefs-rs deployment. STUB. +# Template for a custom Arch Linux composefs-os image. # -# Not yet validated. Same contract as the Fedora image. Arch normally uses -# mkinitcpio, but for cbootc we standardize on dracut everywhere. +# Pattern: +# 1. FROM composefs-os:arch-latest (or the -uki / -uki-sb variant) +# 2. Install packages, write config — /etc and /var are normal here +# 3. Keep the LABEL block at the bottom # -# Build: podman build -t arch-cfs-minimal:latest -f examples/arch/Containerfile . +# Build locally: +# podman build -t my-arch-cfs:latest \ +# --build-arg BASE_IMAGE=composefs-os:arch-latest \ +# -f examples/arch/Containerfile . +# +# Deploy to a disk image file: +# sudo podman run --rm --privileged \ +# -v $(pwd):/output -v /var/lib/containers:/var/lib/containers \ +# -v /var/tmp:/var/tmp \ +# my-arch-cfs:latest cbootc install to-disk /output/disk.raw --size 10G -# --------------------------------------------------------------------------- -# Stage 0: Build cbootc from source -# --------------------------------------------------------------------------- -FROM rust:1-slim AS builder -WORKDIR /build -COPY Cargo.toml Cargo.lock ./ -COPY src/ src/ -RUN cargo build --release +ARG BASE_IMAGE=composefs-os:arch-latest +FROM ${BASE_IMAGE} -# --------------------------------------------------------------------------- -# Stage 1: OS image -# --------------------------------------------------------------------------- -FROM archlinux:latest +# --- Customise below this line ------------------------------------------- -RUN pacman -Syu --noconfirm \ - linux linux-firmware \ - systemd udev dbus-broker \ - util-linux coreutils bash \ - networkmanager iproute2 iputils \ - e2fsprogs dosfstools btrfs-progs \ - grub efibootmgr \ - dracut \ - vim less \ - && pacman -Rns --noconfirm mkinitcpio || true \ - && pacman -Scc --noconfirm +# Use COPY (not RUN echo) for /etc/hostname: buildah bind-mounts a synthetic +# /etc/hostname into every RUN container, so writes via RUN are silently lost. +# COPY writes directly into the image layer without launching a container. +COPY < /usr/lib/tmpfiles.d/00-var-factory.conf +# Serial console autologin — convenient for local QEMU testing. +# Remove or replace with SSH keys for any internet-facing deployment. +RUN mkdir -p /usr/lib/systemd/system/serial-getty@ttyS0.service.d && \ + printf '[Service]\nExecStart=\nExecStart=-/sbin/agetty --autologin root --noclear %%I 115200 vt220\n' \ + > /usr/lib/systemd/system/serial-getty@ttyS0.service.d/autologin.conf -# TODO: composefs dracut module + dracut invocation +# gawk is already provided by the base package; add your own packages here, e.g.: +RUN pacman -S --noconfirm --needed htop && pacman -Scc --noconfirm -# --------------------------------------------------------------------------- -# Install cbootc -# --------------------------------------------------------------------------- -COPY --from=builder /build/target/release/cbootc /usr/bin/cbootc -COPY units/cbootc-update.service units/cbootc-update.timer /usr/lib/systemd/system/ -RUN systemctl enable cbootc-update.timer +# --- Do not edit below this line ------------------------------------------ LABEL containers.bootc=1 LABEL composefs.backend=cfs-rs -LABEL org.opencontainers.image.title="Arch Minimal (composefs-rs)" +LABEL org.opencontainers.image.title="My Arch Linux composefs-os image" CMD ["/sbin/init"] diff --git a/justfile b/justfile index 3837414..ab7692e 100644 --- a/justfile +++ b/justfile @@ -16,6 +16,14 @@ example_image_ubuntu := "composefs-os-ubuntu-test:latest" example_image_ubuntu_uki := "composefs-os-ubuntu-uki-test:latest" example_image_ubuntu_uki_sb := "composefs-os-ubuntu-uki-sb-test:latest" +# Arch image tags (rolling release — no version suffix) +base_image_arch := "composefs-os:arch-latest" +base_image_arch_uki := "composefs-os:arch-latest-uki" +base_image_arch_uki_sb := "composefs-os:arch-latest-uki-sb" +example_image_arch := "composefs-os-arch-test:latest" +example_image_arch_uki := "composefs-os-arch-uki-test:latest" +example_image_arch_uki_sb := "composefs-os-arch-uki-sb-test:latest" + # List available recipes default: @just --list @@ -107,6 +115,39 @@ build-example-ubuntu-uki-secureboot base=base_image_ubuntu_uki_sb: --build-arg BASE_IMAGE={{base}} \ -f examples/ubuntu/Containerfile . +# Build the Arch base GRUB image +build-base-arch: + podman build --network=host -t {{base_image_arch}} --target grub -f Containerfile.arch . + +# Build the Arch base UKI/systemd-boot image +build-base-arch-uki: + podman build --network=host -t {{base_image_arch_uki}} --target uki -f Containerfile.arch . + +# Build the Arch base UKI + Secure Boot image +build-base-arch-uki-secureboot: + podman build --network=host -t {{base_image_arch_uki_sb}} --target uki-secureboot -f Containerfile.arch . + +# Build the Arch example GRUB image on top of the base +build-example-arch base=base_image_arch: + podman build -t {{example_image_arch}} \ + --network=host \ + --build-arg BASE_IMAGE={{base}} \ + -f examples/arch/Containerfile . + +# Build the Arch example UKI image +build-example-arch-uki base=base_image_arch_uki: + podman build -t {{example_image_arch_uki}} \ + --network=host \ + --build-arg BASE_IMAGE={{base}} \ + -f examples/arch/Containerfile . + +# Build the Arch example UKI + Secure Boot image +build-example-arch-uki-secureboot base=base_image_arch_uki_sb: + podman build -t {{example_image_arch_uki_sb}} \ + --network=host \ + --build-arg BASE_IMAGE={{base}} \ + -f examples/arch/Containerfile . + # ── Disk install ────────────────────────────────────────────────────────────── # Create a bootable GRUB raw disk image (requires sudo) @@ -201,6 +242,23 @@ e2e-uki-upgrade-secureboot image=example_image_uki_sb disk="disk-uki-sb.raw" ovm fi sudo python3 tests/e2e.py --upgrade --uki-secureboot --source-image {{image}} --ovmf-vars "$vars" {{disk}} +# Arch upgrade variants +e2e-upgrade-arch image=example_image_arch disk="disk-arch.raw": + sudo python3 tests/e2e.py --upgrade --source-image {{image}} {{disk}} + +e2e-uki-upgrade-arch image=example_image_arch_uki disk="disk-arch-uki.raw" ovmf_vars="": + v="{{ovmf_vars}}"; sudo python3 tests/e2e.py --upgrade --uki --source-image {{image}} ${v:+--ovmf-vars "$v"} {{disk}} + +e2e-uki-upgrade-secureboot-arch image=example_image_arch_uki_sb disk="disk-arch-uki-sb.raw" ovmf_vars="": + #!/usr/bin/env bash + set -euo pipefail + vars="{{ovmf_vars}}" + if [ -z "$vars" ]; then + just prep-sb-vars {{disk}} ovmf-vars-arch-uki-sb.fd + vars=ovmf-vars-arch-uki-sb.fd + fi + sudo python3 tests/e2e.py --upgrade --uki-secureboot --source-image {{image}} --ovmf-vars "$vars" {{disk}} + # Ubuntu upgrade variants e2e-upgrade-ubuntu image=example_image_ubuntu disk="disk-ubuntu.raw": sudo python3 tests/e2e.py --upgrade --source-image {{image}} {{disk}} @@ -246,6 +304,24 @@ ci-uki-secureboot: build-base-uki-secureboot (build-example-uki-secureboot base_ just install-disk-uki-secureboot just e2e-uki-secureboot +# Full Arch GRUB workflow (no Secure Boot — shim-signed not in official repos) +ci-arch-grub: build-base-arch (build-example-arch base_image_arch) + just install-disk {{example_image_arch}} disk-arch.raw 5G + just e2e disk-arch.raw + +# Full Arch UKI workflow +ci-arch-uki: build-base-arch-uki (build-example-arch-uki base_image_arch_uki) + #!/usr/bin/env bash + set -euo pipefail + just install-disk-uki {{example_image_arch_uki}} disk-arch-uki.raw 5G + ovmf_vars=$(find /usr/share -name 'OVMF_VARS.fd' ! -name '*.secboot*' ! -name '*.ms.*' 2>/dev/null | head -1) + just e2e-uki disk-arch-uki.raw "$ovmf_vars" + +# Full Arch UKI + Secure Boot workflow +ci-arch-uki-secureboot: build-base-arch-uki-secureboot (build-example-arch-uki-secureboot base_image_arch_uki_sb) + just install-disk-uki-secureboot {{example_image_arch_uki_sb}} disk-arch-uki-sb.raw 5G + just e2e-uki-secureboot disk-arch-uki-sb.raw + # Full Ubuntu GRUB workflow ci-ubuntu-grub: build-base-ubuntu (build-example-ubuntu base_image_ubuntu) just install-disk {{example_image_ubuntu}} disk-ubuntu.raw 5G