# bootc-image-builder spike plan — 1-week timebox **Agent 3 of 9-agent wave, 2026-05-05.** Schedule: v0.7. ## Containerfile draft ```dockerfile # veilor-os bootc image — Fedora 43 KDE base FROM quay.io/fedora/fedora-bootc:43 ARG VEILOR_VERSION=0.6.0 RUN dnf install -y --setopt=install_weak_deps=False \ @kde-desktop-environment @kde-apps @core @hardware-support @standard \ kernel-modules kernel-modules-extra glibc-all-langpacks \ grub2-efi-x64 grub2-efi-x64-modules grub2-pc grub2-pc-modules \ grub2-tools grub2-tools-extra shim-x64 efibootmgr \ newt parted cryptsetup lvm2 btrfs-progs \ fail2ban fail2ban-firewalld usbguard usbguard-tools audit \ policycoreutils-python-utils tuned chrony firewalld plymouth \ git vim-enhanced tmux htop podman skopeo \ NetworkManager NetworkManager-wifi \ fontconfig freetype fira-code-fonts \ zram-generator \ && dnf remove -y --noautoremove \ 'abrt*' snapd kde-connect open-vm-tools-desktop mlocate man-db man-pages \ && dnf clean all && rm -rf /var/cache/dnf ARG GUM_VERSION=0.17.0 ARG GUM_SHA256=69ee169bd6387331928864e94d47ed01ef649fbfe875baed1bbf27b5377a6fdb ADD https://github.com/charmbracelet/gum/releases/download/v${GUM_VERSION}/gum_${GUM_VERSION}_Linux_x86_64.tar.gz /tmp/gum.tgz RUN echo "${GUM_SHA256} /tmp/gum.tgz" | sha256sum -c - \ && tar -xzf /tmp/gum.tgz -C /tmp \ && install -m0755 /tmp/gum_${GUM_VERSION}_Linux_x86_64/gum /usr/bin/gum COPY overlay/ / COPY assets/ /usr/share/veilor-os/assets/ COPY scripts/ /usr/share/veilor-os/scripts/ RUN bash /usr/share/veilor-os/scripts/10-harden-base.sh \ && bash /usr/share/veilor-os/scripts/20-harden-kernel.sh \ && bash /usr/share/veilor-os/scripts/selinux/build-policy.sh \ && bash /usr/share/veilor-os/scripts/kde-theme-apply.sh \ && bash /usr/share/veilor-os/scripts/30-apply-v03-theme.sh RUN plymouth-set-default-theme details \ && sed -i \ -e 's|^GRUB_DISTRIBUTOR=.*|GRUB_DISTRIBUTOR="veilor-os"|' \ /etc/default/grub # bootc kargs go in /usr/lib/bootc/kargs.d/, not /etc/default/grub RUN mkdir -p /usr/lib/bootc/kargs.d && cat > /usr/lib/bootc/kargs.d/10-veilor-hardening.toml <<'EOF' kargs = [ "lockdown=integrity", "slab_nomerge", "init_on_alloc=1", "init_on_free=1", "randomize_kstack_offset=on", "vsyscall=none", "fbcon=nodefer", ] EOF RUN systemctl enable sshd fail2ban usbguard tuned auditd firewalld chronyd sddm \ veilor-firstboot.service veilor-modules-lock.service \ && passwd -l root \ && systemctl set-default graphical.target RUN bootc container lint LABEL org.veilor.version=${VEILOR_VERSION} ``` ## bootc-image-builder config (`build/disk-config.toml`) ```toml [customizations] hostname = "veilor-os" [[customizations.user]] name = "admin" password = "veilor" groups = ["wheel"] shell = "/bin/bash" [customizations.kernel] append = "lockdown=integrity slab_nomerge init_on_alloc=1 init_on_free=1 randomize_kstack_offset=on vsyscall=none fbcon=nodefer" [customizations.installer.kickstart] contents = """ zerombr clearpart --all --initlabel part /boot/efi --fstype=efi --size=600 part /boot --fstype=ext4 --size=1024 part btrfs.veilor --grow --encrypted --luks-version=luks2 --pbkdf=argon2id btrfs none --label=veilor btrfs.veilor btrfs / --subvol --name=root LABEL=veilor btrfs /home --subvol --name=home LABEL=veilor """ ``` ## GitHub Actions workflow `build-bootc-iso.yml`: - runs-on ubuntu-24.04, **timeout 30 min** (vs 90 for livecd-creator) - permissions: `contents: write`, `packages: write` - Build OCI image: `podman build` + `podman push ghcr.io/veilor/veilor-os:43` - Build ISO via `quay.io/centos-bootc/bootc-image-builder:latest` with `--type anaconda-iso --rootfs btrfs --config /build/disk-config.toml` - Reuse split + `softprops/action-gh-release@v2` from existing workflow ## Migration risks (10-row table) | # | Risk | Severity | Mitigation | |---|------|----------|------------| | 1 | %post --nochroot overlay-copy disappears | Low | `COPY overlay/ /` is simpler — win | | 2 | Update model: `bootc upgrade` (image swap) replaces `dnf upgrade` | High | `veilor-update` becomes thin `bootc upgrade --apply` wrapper | | 3 | /usr is read-only at runtime | Medium | etc-overlay handles /etc writes; relocate any /usr writers to /etc or build-time | | 4 | SELinux module compilation in container | Medium | Works in fedora-bootc:43 (verified per upstream pattern). Test spike day 2 | | 5 | `transaction_progress.py` patch unnecessary | Low | bootc-image-builder doesn't use dnf at install. Drop the patch. Win | | 6 | `rd.luks.uuid` is anaconda's job again | Low | Removes ~80 lines of fragile sed/grubby code. Win | | 7 | LUKS prompt UX: anaconda native, not gum | High | gum installer becomes `live·shell` only. v1.0 install = anaconda's native UI | | 8 | --privileged still required | None | Same as today | | 9 | OCI image size: ~3.5 GB compressed vs ~2.8 GB squashfs | Low | zstd:max recovers ~400 MB | | 10 | `kernel-install` BLS: `/etc/kernel/cmdline` not honored, `/usr/lib/bootc/kargs.d/*.toml` is | Medium | Already addressed in Containerfile draft | ## What we keep (zero churn) - `overlay/*` — copied verbatim by `COPY overlay/ /` - `scripts/*.sh` — invoked verbatim by Containerfile RUN - `assets/*` — copied verbatim - `test/*` — adapts: `podman run --rm -it ghcr.io/veilor/veilor-os:43 /bin/bash` smoke; QEMU ISO test unchanged - `kickstart/install.ks` — kept as fallback. Tag last anaconda build as `v0.5.99-anaconda` before flipping ## Spike success criteria (1 week) | Day | Milestone | |-----|-----------| | 1 | Containerfile builds clean (`podman build` exit 0, `bootc container lint` exit 0) | | 2 | `podman run` boots into image, KDE binaries present, SELinux + hardening sysctls applied | | 3 | bootc-image-builder produces installer ISO from OCI, ksvalidator clean | | 4 | ISO boots in QEMU to anaconda live menu | | 5 | Install completes, LUKS single-prompt, btrfs subvols present | | 6 | First boot reaches SDDM, admin login works, password-change-on-first-login enforced | | 7 | Buffer for fixes; doc `docs/BUILD-bootc.md`; tag `v0.5.99-anaconda` snapshot | ## Decision gate - **PASS** (all 7 criteria green): tag `v0.5.99-anaconda` as last-anaconda; merge `bootc-spike` → `main` as `v0.6.0-bootc`; deprecate `kickstart/veilor-os.ks` (keep `kickstart/install.ks` for one cycle). Update ROADMAP: v1.0 ships bootc-only. - **FAIL** (any of risks 3, 4, 7, 10 unfixable in week 1): keep anaconda path, defer migration to v1.1+; file each blocker as GH issue with reproducer. - **HYBRID FALLBACK**: ship anaconda ISO for v0.6/v0.7, ship bootc OCI alongside (matches existing `veilor-atomic` stretch goal).