Punch list from review of dev (20 commits ahead of prod). Ordered by impact.
Problem: defer os.RemoveAll(scratchDir) in main.go:493 never fires when
fatal() is reached, because os.Exit(1) skips deferred functions. On every
fatal error after prepareScratchDir, the OCI cache (~image size) is left on
the target disk.
Fix:
- Have
fatal()run a final-cleanup step aftercleanup.Run()that removes any registered "post-cleanup paths". ReintroduceCleanup.AddPostRemoval(path)whose paths are removed after all unmounts and LUKS close, not interleaved with them (which was the bug the original commit fixed). - Register
scratchDirviaAddPostRemovalinstead of relying ondefer. - Add a regression test asserting that registered post-removal paths are deleted only after all unmounts.
Problem: bootc.go hardcodes /var/lib/superiso-store detection in two
places. fisherman should not know about a downstream ISO product.
Fix:
- Add
AdditionalImageStores []stringtoRecipe(JSONadditionalImageStores). Each entry is a host path bind-mounted read-only into the bootc container and listed understorage.options.additionalimagestoresin the generatedstorage.conf. - Remove all
superiso-storestrings andos.Statprobes frombootc.go. - The existing caller-supplied
CONTAINERS_STORAGE_CONFenv override is retained as the pre-existing escape hatch. writeLiveStorageConfbecomeswriteAdditionalStoresConf(scratchDir, paths).- Update
bootcViaContainerandbootcToDiskViaContainerto call a single shared helperappendImageStoreArgs(podmanArgs, scratch, stores) ([]string, cleanupFn). - Downstream (SuperISO / tacklebox) sets
additionalImageStores: ["/var/lib/superiso-store"]in the recipe — no special-casing in fisherman.
4.2 MB ELF in the repo since #23. Not in .gitignore (only fisherman/fisherman is).
git rm fisherman/fisherman-overlay- Update
.gitignoretofisherman/fisherman*so future siblings don't sneak back in.
bootc.go:627 hard-codes TMPDIR=/var/fisherman-tmp. On a live ISO that path
lives on the constrained overlay/tmpfs — the exact case the surrounding
scratch-redirect plumbing is supposed to fix.
- Change
SkopeoExportOCIFnsignature tofunc(image, destDir, tmpdir string) error. - Callers pass
opts.scratchDir().
Currently the generated fisherman-storage-*.conf lands in scratchDir,
which is also the parent of oci-cache/. Put it under
scratchDir/fisherman-conf/ so ls of the OCI cache stays clean and a
future RemoveAll(ociDir) doesn't surprise anyone.
The deleted TestCleanup_RemovesRegisteredPathsAfterUnmount covered the old
contract. Replace with TestCleanup_PostRemovalsHappenAfterAllUnmounts
covering the new AddPostRemoval semantics from §1.
BOOTCREW_JUST_REFACTOR.md, BRANCH_PROTECTION.md → docs/. CHANGELOG.md
stays in the root (conventional).
Small table-driven test in internal/install/bootc_test.go so future bootc /
ostree / podman wording changes break a unit test instead of CI substep
output silently going blank.
targetMount and luksMapper are package-level constants. Add optional
recipe fields targetMount (default /mnt/fisherman-target) and
luksMapperName (default fisherman-root) so CI can run two installs on the
same host without colliding.
Reviewed the existing matrix (tests/bootcrew-matrix.yaml,
scripts/boot-verify.sh, bootcrew-vm.yml, bootcrew-nightly.yml).
Improvements:
- AdditionalImageStores coverage: add a CI job that materializes a fake
offline image store (an
oci:directory under/tmp/fake-store), writes a recipe withadditionalImageStores: ["/tmp/fake-store"], and asserts fisherman invokespodman runwith-v /tmp/fake-store:.../ :roand aCONTAINERS_STORAGE_CONFwhose generated file lists it underadditionalimagestores. This replaces the implicit (and untested) superiso-store probe. - Scratch-leak regression test: unit test asserting fatal-path cleanup
removes the scratch dir, AND an integration test under
tests/e2e/that runs the binary against a deliberately-failing recipe and asserts no.fisherman-scratchremains on the target loop image. - Substep classification: the new ClassifyLine table-driven test (§8)
also gets fed real captured
bootc installlog excerpts fromtests/fixtures/bootc-logs/*.txtso we catch wording drift in upstream bootc. - boot-verify.sh hardening: currently relies on a single SSH probe with
a fixed timeout. Improve by:
- tee'ing the QEMU serial console to
${ARTIFACTS}/serial.logalways (today only on failure), - dumping
journalctl -b --no-pagerandbootctl statuson failure into the artifacts dir for post-mortem, - adding a
--keep-vm-on-failureflag so a maintainer can attach a debugger to a still-running guest.
- tee'ing the QEMU serial console to
- Recipe-overridable mount paths (§9) is also a testing win: lets bootcrew-vm.yml run two recipes in parallel on the same runner.
- Deterministic temp paths: today scratch lives at
/var/fisherman-tmpor<target>/.fisherman-scratch. Add a--scratch-dirCLI flag (already plumbed throughOptions.ScratchDir) so tests can point scratch at a tmpfs they own and assert post-run cleanliness without racing other runs. go test -race ./...in CI: progress-pipe goroutines and the pull-output scanner all touch shared state; flagging this catches regressions before they hit the install path.
The dakota-iso repo (.github/workflows/test-luks-install.yml + the
luks-test-qemu justfile recipes) already runs the highest-fidelity E2E
test that exercises fisherman end-to-end: builds the live ISO, boots it in
QEMU, SCPs a recipe in, runs fisherman over SSH, reboots into the
installed disk, injects the LUKS passphrase at the Plymouth prompt via the
QEMU monitor sendkey channel, and verifies the system reaches login.
What we should pull into fisherman directly (in order of value/effort):
screendumpvia QEMU monitor at three checkpoints — live-ready, plymouth-prompt, final-boot. PPM files are tiny and convert to PNG withconvert(already in standard CI images). Already partially adopted in §10; extend to all three checkpoints.continue-on-error: trueon the heavy E2E step + always-run artifact upload step. We added the always-on upload in §10; the continue-on-error semantics also matter so a flaky boot still leaves screenshots + journal in the PR.- Two-stage boot test (live ISO → installed disk) rather than only verifying the installed disk. Catches regressions where the install succeeds but the resulting disk doesn't boot — the case the recent scratch-leak commit (§1) was chasing. Requires building a fisherman ISO in CI, which is a bigger lift; track separately.
- SCP-the-recipe-and-SSH-fisherman instead of running fisherman
directly against a loop device. This is the real code path the
user-facing installer goes through and would have caught the
superiso-storeandstorage.confregressions before they shipped. - PR comments with pass/fail + screenshot links to an orphan
ci-screenshotsbranch. Visual review beats log diving for boot regressions. Cheap to adopt: the dakota-iso workflow'scommit_pnghelper is reusable as-is. free-disk-spaceaction. GitHub-hosted runners have ~14 GiB free by default; a real ISO + qcow2 disk + OCI cache trips that quickly. Dakota frees ~30 GiB withjlumbroso/free-disk-space@….
Nothing in the above requires changes to fisherman code; the integration
points are all in .github/workflows/ and scripts/. Open as a follow-up
PR titled "adopt dakota-iso E2E patterns" once these cleanup items land.
Build on §2's unit tests (appendImageStoreArgs) with an integration
test that runs fisherman against a real fake-store layout:
testdata/fake-store/
storage.conf
overlay-images/
<a real OCI dir produced by `skopeo copy docker://hello-world …`>
The CI job:
- Creates the layout (skopeo copy of a tiny public image).
- Writes a recipe with
image: containers-storage:hello-worldandadditionalImageStores: ["/tmp/fake-store"]. - Stubs
bootcwith a script that echoes its argv and exits 0. - Runs
fisherman recipe.jsonand asserts the echoed argv contains-v /tmp/fake-store:/tmp/fake-store:roandCONTAINERS_STORAGE_CONF=/var/tmp/fisherman-conf/.... - Reads the generated storage.conf out of the scratch dir and asserts
it lists
/tmp/fake-storeunderadditionalimagestores.
This replaces the implicit untested behavior the old superiso probe relied on, and gives downstream (SuperISO / tacklebox) a concrete contract.
Deferred (not in this round): elapsed-time logging wrapper around
runner.HostArgs — useful but mechanical, separate PR.