Todo
Tracked items not yet implemented. Roughly ordered by value / blocking-ness.
Featuresβ
tacklebox update <recipe.json>β
Re-pull the recipe's images and replay bootc install (or smarter
bootc switch) into existing per-env subtrees on a previously-built
media, without wiping the persist partition. Lets users refresh USB
media without re-formatting.
tacklebox add <env> / tacklebox remove <env>β
Mutate an existing media in place: add a fourth env to a built image, or drop one. Today the only way is to rebuild from scratch.
tacklebox verify <image-or-device>β
Sanity-check a built image. Asserts:
- All BLS entries resolve to existing kernel + initrd in the ESP.
- Each env's ostree commit hash is distinct from the others (catches today's bootc-install content-collision bug β see below).
- ESP usage is under its partition size.
- Per-env
tbox-install/<env>/ostree/repois consistent (ostree fsck). loader.confreferences an entry that exists.
Prerequisite for meaningful CI regression coverage β both the smoke
test and full E2E should call tacklebox verify after build.
Per-stateroot greenboot / rollbackβ
Each env boots independently with its own stateroot, so health-check
- auto-rollback should be wired per-env, not globally.
Persist-partition lifecycleβ
TBOX_PERSIST is formatted and /home is overlaid via the dracut
module, but there's no story for:
- Quota per env.
- Garbage collection when an env is removed.
- Migration if the recipe's env list changes between builds.
USB pre-flight: unmount busy partitionsβ
final-attempt.log shows mkfs.vfat failing because /dev/sdb1 was
auto-mounted by the desktop. internal/blockdev should sweep
/dev/<target>* mounts before format. Only matters for /dev/*
targets, not loop images.
Bugsβ
Serial bootc install shares ostree commits across envsβ
Reproduces with examples/all-test.json. Each bootc install to-filesystem runs from the correct container image, but both
tbox-install/aurora and tbox-install/bazzite end up with the
same ostree commit hash (e2c044ed7f9aβ¦ β bazzite content). The
aurora container itself contains genuine Aurora content when run
standalone (podman run --rm localhost/superiso-live-aurora cat /etc/os-release shows NAME="Aurora").
What was tried and didn't fix it (2026-05-11):
- Pass
--source-imgref containers-storage:<image>to bootc install to pin the source explicitly. Hash unchanged β verify still flags the same collision. - Remove the
--mount type=bind,src=/var/lib/containersso the install container can't see host's containers-storage at all. Hash still unchanged. So the bug is not (or not only) about which image bootc resolves; bootc seems to share state somewhere we haven't found.
Both fixes are kept in internal/install/bootc.go as defense in depth
since they're cheap and harmless, but the actual root cause needs
either:
- A deeper read of bootc's install-source resolution (RUST_LOG=debug on a single install would help isolate where the wrong commit gets computed), or
- Filing upstream β bootc 1.15.2 against ublue-os/aurora + ublue-os/ bazzite reproduces deterministically in our setup.
tacklebox verify (above) catches this in CI; nightly builds of
all-test will fail until the underlying bootc bug is fixed.
CI / automated testingβ
The goal is regression coverage for the dracut module, partitioning, bootloader wiring, and per-env install correctness, runnable on every PR.
Stage 1 β Lint + unit (every PR, ~2 min, ubuntu-latest)β
go test ./...go vet ./...shellcheck src/dracut/95tbox-root/*.shgo build ./...
Stage 2 β Recipe schema check (every PR, seconds)β
- Parse every
examples/*.jsonthrough tacklebox's loader; fail on any rejection.
Stage 3 β Two-env disk-build smoke (every PR, ~10 min, ubuntu-latest)β
- Two-env fixture recipe (
fixtures/smoke-2env.json) using minimal bootc images β e.g.quay.io/centos-bootc/centos-bootc:stream10twice with different stateroots, or two distinct minimal images. Two envs is the minimum useful E2E β it catches the kind of cross-env content-collision bug we hit on 2026-05-11. tacklebox build fixtures/smoke-2env.jsonto a ~30 GB loop image.tacklebox verifyβ must assert each env's ostree commit hash is distinct.
Disk budget on free ubuntu-latest (~14 GB workspace + ~75 GB on /mnt):
- Run
jlumbroso/free-disk-spacefirst to recover ~30 GB on/(Android SDK, .NET, Haskell, etc.). - Use
/mntas the build output base (tacklebox build -b /mnt/tb) so the 30 GB loop image lives on the NVMe. - Containers-storage on
/mnttoo (STORAGE_DRIVER=overlaywithgraphroot=/mnt/containers). - Total expected workspace: ~40β50 GB. Fits.
Stage 4 β QEMU boot smoke (every PR, ~5 min, ubuntu-latest with /dev/kvm)β
- Boot the Stage 3 image headless under KVM, capture serial log.
- Grep for
tbox-root.service: ... finished,ostree-prepare-root.service: ... finished, andWelcome to. - Boot both envs by editing
loader.confbetween runs (or by passingsystemd.unit=.../ boot-entry selection via QEMU OVMF) β confirms each env actually boots its own content, not just the alphabetically-first one. - On failure, upload the serial log + ESP contents as a CI artifact.
Stage 5 β Nightly full regression (optional, larger runner)β
- Real
examples/all-test.json(bazzite + aurora + dakota, 60 GB). Doesn't fit the two-env smoke budget β needs either aubuntu-latest-largepaid runner, a self-hosted runner, or an ephemeral cloud box spun up from the workflow. Mostly catches issues with the real upstream ublue images that the minimal fixtures don't exercise.
Workflow fileβ
.github/workflows/ci.yml (stages 1β4) + .github/workflows/nightly.yml
(stage 5, schedule: + workflow_dispatch:).
Test fixturesβ
fixtures/smoke-2env.jsonβ two-env recipe (minimum useful for catching cross-env regressions), ~30 GB shared store, 1 GB ESP.- Either pull fixture images from a public registry, or build them in-repo and push to GHCR on main.