# Minimal-installer ISO spike (Option B)

**Status:** P1 **PASS** (2026-09-10). Strategy locked in
[`docs/iso-strategy.md`](iso-strategy.md). Consume-gate on VM 141 is
**PASS** — this spike starts the user-facing install path without blocking
on a polished offline ISO (Option A).

Public source of truth remains `oci.thepeoples.io/desktop/quarry:stable`
(cosign-signed).

## Goal of this spike

Prove a **thin install path** that:

1. Runs interactively by default (guided prompts).
2. Verifies cosign against the product pubkey.
3. Supports known-catalog **variant picker** vs custom/local **full ref**.
4. Supports optional **local registry mirror** remap (fail closed).
5. Can install via `bootc install to-disk` (from a live/helper env) or
   document `bootc switch` on an existing bootc throwaway.
6. Treats cloud-init as optional (not required for success).

A full custom live ISO built on `quarry-builder-01` is **phase 2**. Phase 1
ships scripts + catalog + installer Containerfile stub + docs so we can
drive installs from Fedora bootc live / existing QA VMs today.

## Phases

| Phase | Deliverable | Done when |
| --- | --- | --- |
| **P0** (this commit) | Spike plan, `scripts/iso/*`, `Containerfile.installer` stub, Just + CI validate | Scripts validate; docs linked from consume/design/README |
| **P1** | Run `install-quarry.sh` on a throwaway (VM 141-class or fresh disk) → cosign-verified Quarry boots | **PASS** — see results below |
| **P2** | Build minimal installer image (`Containerfile.installer`) on builder (scripts + catalog + cosign) | Image builds; `install-quarry` dry-run inside container |
| **P2.5** | Burnable ISO media via BIB / `bootc-generic-iso` on builder storage (not new PVE disks) | **PASS** (media); boot-smoke was PARTIAL until P2.6 |
| **P2.6** | Unattended kickstart embedded in BIB anaconda-iso; one-shot smoke → helper → `install-quarry` | **PASS** — see P2.6 results |
| **P3** | Mirror + custom-ref QA matrix; optional NoCloud seed check | Exit criteria 3–6 in iso-strategy.md |
| **Later** | Option A full offline ISO for named `git-<sha>` releases | Separate; not required for product default |

## Layout

```
docs/iso-strategy.md          # product decisions (mirror, variants, UX)
docs/iso-spike.md             # this plan + status
scripts/iso/catalog.json      # curated known-catalog variants
scripts/iso/mirror-registries.conf.tmpl
scripts/iso/install-quarry.sh # interactive / flags installer helper
scripts/iso/validate-spike.sh # non-root CI/local sanity
scripts/iso/quarry-installer.ks # unattended Anaconda kickstart (P2.6)
scripts/iso/embed-kickstart.py  # embed KS into BIB config.toml
scripts/iso/bib-config.toml     # ISO metadata; KS injected at build
Containerfile.installer       # helper image
.gitea/workflows/iso-spike.yml
```

## How to use (P0/P1 — no custom ISO yet)

On a **throwaway** host only (never builders / steamvm / daily drivers).
See forbidden list in [`docs/consume-gate.md`](consume-gate.md).

### A. Existing fedora-bootc throwaway → switch

```bash
# from this repo on dig-agent, copy scripts to the QA VM, then:
sudo ./scripts/iso/install-quarry.sh --mode switch
# or noninteractive:
sudo ./scripts/iso/install-quarry.sh --mode switch --yes \
  --variant desktop/quarry:stable
```

### B. Live environment → install to a blank disk

```bash
sudo ./scripts/iso/install-quarry.sh --mode to-disk --disk /dev/sdX --yes \
  --variant desktop/quarry:stable
```

Under the hood this prefers `podman run --privileged … <image> bootc install
to-disk` when installing a remote OCI ref from a non-bootc live host, and
native `bootc switch` / `bootc install` when already on bootc.

### Mirror example

```bash
sudo ./scripts/iso/install-quarry.sh --mode switch --yes \
  --variant desktop/quarry:stable \
  --mirror oci.thepeoples.dev
```

Cosign still verifies the **canonical** `oci.thepeoples.io/…` name.

### Custom / local registry (no variant picker)

```bash
sudo ./scripts/iso/install-quarry.sh --mode switch --yes \
  --image my.registry.lan/lab/quarry:exp \
  --skip-cosign   # lab only; product default is fail-closed verify
```



## P1 results (2026-09-10 CT / UTC)

Throwaway: **VM 142** `quarry-iso-p1` at `172.25.0.21` (full clone of VM 141,
MAC `BC:24:11:AE:34:71`). VM 141 left intact.

| Step | Result |
| --- | --- |
| CI `iso-spike.yml` on `856f366` | **PASS** (Gitea Actions validate success) |
| Local `scripts/iso/validate-spike.sh` | **PASS** |
| Prep | `bootc rollback` → reboot onto `quay.io/fedora/fedora-bootc:42` |
| Direct path | `install-quarry.sh --mode switch --yes --variant desktop/quarry:stable` |
| Cosign verify | **PASS** — digest `sha256:3bb0562e0bc661ec074e6dcbbfd789590a4bfa968bda2dc88cf3abcdb5e6b3ac` |
| `bootc switch` | **PASS** — staged `oci.thepeoples.io/desktop/quarry:stable` / `44.20260908` |
| Post-reboot markers | **PASS** — `/usr/share/quarry/*`, `quarry.png`, `NAME=Bazzite` |
| Mirror path | **PARTIAL** — see below |

### Mirror notes

- `oci.thepeoples.dev` resolves on LAN (`172.25.2.64`) and answers `/v2/` (200).
- `--mirror oci.thepeoples.dev` **writes** `/etc/containers/registries.conf.d/50-quarry-mirror.conf`
  and still cosign-verifies the **canonical** `oci.thepeoples.io/...` identity (**PASS**).
- Actual pull via remap **FAILS closed**: registry returns `NAME_UNKNOWN` for
  `desktop/quarry` (repo not present on the mirror). Documented skip for byte
  fetch until the mirror is populated; direct path remains proven.
- Mirror fragment was removed before final reboot so the guest would not stick
  on a non-populated remap.

### KVM / SSH notes (P1)

- Desktop preset `81-desktop.preset` **disables** `sshd.service` / `sshd.socket`.
  After a fresh switch deploy, enable SSH explicitly if you need remote QA:
  `systemctl enable --now sshd` (or bake a wants symlink). Offline enable on the
  ostree deploy was required once when SSH never came up post-reboot.
- Keep `balloon: 0` and mask `bazzite-hardware-setup.service` (already on this
  lineage) per consume-gate.

### Cleanup

VM 142 is disposable; reclaim `local-lvm` after doc commit (pool was ~90% after
clone). Prefer keeping VM 141 as the long-lived QA reference.


## Builder notes

- Build installer image / ISO **only** on `quarry-builder-01` (label
  `desktop-quarry`). ~58 GiB free as of spike start — still avoid
  concurrent Bazzite + multi-flavor FROM pulls.
- `just build-iso` runs `scripts/iso/build-iso.sh` (BIB anaconda-iso of the
  installer helper) on builder storage under `/var/tmp`.
- Do not steal `desktop-flatpak` / Pyro runners; do not kill `act_runner`
  mid-job.

## CI

`.gitea/workflows/iso-spike.yml` runs `scripts/iso/validate-spike.sh` on
changes under `scripts/iso/`, `Containerfile.installer`, and this doc.
On push it also builds `Containerfile.installer` on `desktop-quarry` (helper image only). It does **not** build burnable ISO media in CI (P2.5; disk + time).

## P2 results (2026-09-10 CT / UTC)

Builder: **quarry-builder-01** (`172.25.0.140`, label `desktop-quarry`).
~45 GiB free on `/` after build; **no** new PVE `local-lvm` disks/VMs.
`/mnt/int01` not present. Keep VM 141. `act_runner` left running.

| Step | Result |
| --- | --- |
| `scripts/iso/validate-spike.sh` | **PASS** |
| `podman build -f Containerfile.installer` | **PASS** → `localhost/quarry-installer:spike` |
| Image ID (builder) | `48f34bcd0ee0` (`localhost/quarry-installer:spike`, also `:p2-20260910`) |
| Embed | `/usr/share/quarry/iso/*` + `/usr/local/bin/install-quarry` symlink |
| Cosign | F42 has **no** cosign RPM; P2 bakes static `cosign` (`COSIGN_VERSION`) into `/usr/local/bin/cosign` |
| In-image dry-run | **PASS** after symlink-safe `SCRIPT_DIR` (catalog beside real script) |
| `just build-installer` / `just save-installer` | Wired; OCI archive optional under `/var/tmp` on builder |
| OCI archive (builder) | `/var/tmp/quarry-installer-spike.tar` (~1.9 GiB) |
| BIB / burnable ISO | **Done in P2.5** — see results below |

### How to rebuild on builder

```bash
# on quarry-builder-01, from a checkout of this repo:
just iso-validate
just build-installer spike
just save-installer spike /var/tmp/quarry-installer-spike.tar
# later: podman load -i /var/tmp/quarry-installer-spike.tar
```

Burnable media is **P2.5** (below). Until attached, use the helper image on a
throwaway bootc VM (P1 path) or `podman run --rm -it localhost/quarry-installer:spike`.

### P2.5 wiring (burnable ISO media)

See **P2.5 results** below. Remaining QA: boot ISO on a throwaway VM and complete
`install-quarry` → `:stable`. Optional Shape A `installer-git-<sha7>` only — never `:stable`.


## P2.5 results (2026-09-10 CT)

Builder: **quarry-builder-01** (`172.25.0.140`). Builder `/` only — **no** new PVE
`local-lvm` disks/VMs. Keep VM 141. `act_runner` left running. `/mnt/int01` absent.

| Step | Result |
| --- | --- |
| BIB image | `quay.io/centos-bootc/bootc-image-builder:latest` (~964 MB) pulled on builder |
| `osbuild-selinux` | Installed on Alma 10 builder (SELinux Enforcing) |
| Input | `localhost/quarry-installer:spike` (Option B helper; not full desktop Quarry) |
| Config | `scripts/iso/bib-config.toml` |
| Recipe | `scripts/iso/build-iso.sh` + `just build-iso` → `--type anaconda-iso --rootfs ext4` |
| Output dir | `/var/tmp/quarry-iso-out` (builder disk) |
| ISO path / size | **PASS** — `/var/tmp/quarry-iso-out/bootiso/install.iso` (**2.0 GiB**); also `/var/tmp/quarry-installer-spike.iso` |
| Shape A publish | **Not done** — do not tag installer as `:stable`. Optional later: `installer-git-<sha7>` only |

### How to rebuild (builder)

```bash
cd /path/to/quarry   # synced checkout on quarry-builder-01
just build-installer spike   # if image missing
just build-iso spike /var/tmp/quarry-iso-out
# artifact: find /var/tmp/quarry-iso-out -name "*.iso"
```

### Attach / boot (QA)

1. Copy ISO off builder (scp) or attach from a share — still **not** a new local-lvm disk for the ISO file itself.
2. Proxmox: add CD/DVD to a **throwaway** VM (blank disk), boot from ISO.
3. Anaconda installs the **helper** (`quarry-installer`) to the first disk (default BIB kickstart + spike user `quarry` / password `quarry` — rotate on any shared host).
4. Reboot into helper → `sudo install-quarry --mode switch --yes --variant desktop/quarry:stable` (cosign-verified product path).
5. Do **not** use VM 141 as the install target; keep it as the long-lived consume-gate reference.

### Notes

- Full BIB of `desktop/quarry:stable` skipped for P2.5 size/risk on ~50 GiB free; Option B helper ISO matches iso-strategy.
- CI still does **not** build burnable ISO media (disk + time); validate + installer image only.


## P2.5 boot-smoke (2026-09-10 CT)

**Result: PARTIAL PASS** (media boots + Anaconda container deploy started; full helper→`:stable` not completed).

Throwaway: **VM 144** `quarry-iso-smoke` — **destroyed** after smoke (anti-sprawl). **VM 141** left intact as long-lived QA. CT105 was retired separately for `local-lvm` headroom; not used as install target.

| Step | Result |
| --- | --- |
| ISO on PVE `local` (dir) | **PASS** — `/var/lib/vz/template/iso/quarry-installer-spike.iso` (2.0 GiB) via `scp` builder→PVE (not `local-lvm`) |
| Firmware | SeaBIOS (ISO has DOS/MBR boot sector `QUARRY_INST`); GRUB 2.12 reached |
| VM shape | 4 GiB RAM, 2 vCPU, 15 G `local-lvm` disk (started 10 G; grew after partial stall), CDROM first |
| GRUB | **PASS** — `Install Fedora Linux 42` (auto-countdown) |
| Anaconda UI | **PASS** — Installation Summary (user `quarry` / root disabled / `ens18` up) → Installation Progress |
| Helper deploy | **PARTIAL** — stuck/slow on `Deployment starting: /run/install/repo/container` (~7% of 15 G written when stopped). First pass also reached this screen; premature disk-boot left incomplete ostree |
| Helper reboot + `install-quarry --mode switch --yes --variant desktop/quarry:stable` | **SKIP** — did not babysit GUI to completion; no cosign/bootc markers from this smoke |
| Cleanup | **PASS** — `qm destroy 144 --purge` (+ orphaned LV removed). ISO artifact **kept** on PVE `local` and builder `/var/tmp` |

### Disk impact

- PVE `local` (dir): +2.0 GiB ISO; ~47% used / ~45 GiB free after.
- PVE `local-lvm`: briefly allocated 10–15 G thin for VM 144; fully released on destroy. Pool ~87.8% after cleanup (CT105 already gone).
- Builder `/`: unchanged (~49 GiB free); BIB caches **not** pruned.

### Next recommendation (superseded by P2.6)

P2.6 completed unattended KS + helper + `install-quarry` on one-shot VM 145 (destroyed). Keep using **VM 141** for day-2 image QA; do not leave extra VMs.


## P2.6 results (2026-09-10 CT) — unattended kickstart

**Result: PASS** (unattended Anaconda → helper login → cosign `install-quarry` → `:stable` markers).

| Step | Result |
| --- | --- |
| Kickstart | **PASS** — `scripts/iso/quarry-installer.ks` (text, autopart, DHCP, user `quarry`/`quarry`, serial bootloader append) |
| Embed | `embed-kickstart.py` + `build-iso.sh` → `[customizations.installer.kickstart]` (no `[[customizations.user]]`) |
| BIB rebuild | **PASS** — `/var/tmp/quarry-iso-out2/bootiso/install.iso` (2.0 GiB); PVE `local:iso/quarry-installer-spike.iso` |
| One-shot VM | **VM 145** `quarry-iso-ks-smoke` (16 G, SeaBIOS, serial0) — **destroyed** after |
| Unattended Anaconda | **PASS** — text mode; serial `QUARRY_KS_POST_DONE` |
| `%post` RO fix | First attempt wrote `/usr/share/quarry` → fatal RO. Fixed to `/etc/quarry` + `/var/lib/quarry` + MOTD/hint unit |
| Helper boot + SSH | **PASS** — login `quarry` @ `172.25.0.141` (DHCP); `install-quarry` present; `ks_post_ok=2026-09-10T15:34:34Z` |
| `install-quarry --mode switch --yes --variant desktop/quarry:stable` | **PASS** — cosign OK; staged digest `sha256:4e0ff90373f16b8f3eacf2ebc4dc1608e4f7a19f14aa7c5225194de19ca60d6c` (`44.20260908`) |
| Post-reboot markers (offline ostree) | **PASS** — `NAME=Bazzite`, `/usr/share/quarry/*`, `quarry.png`; origin `oci.thepeoples.io/desktop/quarry:stable` |
| Cleanup | **PASS** — `qm destroy 145 --purge`. ISO kept on PVE `local` + builder `/var/tmp`. VM 141 untouched |

### Kickstart notes

- BIB injects `ostreecontainer`; do **not** put it in `quarry-installer.ks`.
- `%post` must not write under `/usr` (ostree read-only). Use `/etc` / `/var`.
- Initrd may warn `No device with link found for --device=link`; Anaconda still activates DHCP.
- After install, set VM boot order to **disk first** (or rely on `reboot --eject`); CD-first reboots re-enter Anaconda.
- Desktop `:stable` disables `sshd` — enable explicitly for remote QA, or verify markers offline.

### Rebuild

```bash
# on quarry-builder-01
just build-installer spike   # if needed
just build-iso spike /var/tmp/quarry-iso-out
```


## Exit → hand back to iso-strategy

When P1 is green on a throwaway VM, update [`docs/consume-gate.md`](consume-gate.md)
and tick exit criteria in iso-strategy.md. Promote custom ISO artifacts
via Shape A / object patterns only after P2 produces something worth
publishing.


## VM layout (anti-sprawl)

| VMID | Name | Role |
| --- | --- | --- |
| 140 | `quarry-builder-01` | Long-lived **builder** (images, BIB, act_runner) |
| 141 | `quarry-qa-01` | Long-lived **test guest** for images built on 140 |

Do **not** leave extra Quarry QA VMs. Ephemeral ISO/Anaconda guests (e.g. former 144) are one-shot and must be destroyed when finished. Prefer reusing 141 for `install-quarry` / `bootc switch` QA.
