Tacklebox User Guide
Tacklebox turns bootable container images (bootc-style: a kernel
under /usr/lib/modules, systemd, an OS tree) into bootable media:
live ISOs, multi-boot USB sticks, and installed disk images β with
shared storage, unified systemd-boot management, and per-environment
add/remove/update after the fact.
It is one static Go binary. Building media needs root (loop devices,
mkfs, mounts) and podman (images are read from local container
storage or pulled). A pure-Go, root-free core is landing (#95) that
also powers the browser ISO builder.
tacklebox build Build media (disk image / USB / ISO) from a recipe
tacklebox update Re-install every env on existing media from a recipe
tacklebox update-all Update every bootable env on the running media
tacklebox add Add environment(s) to existing media
tacklebox remove Remove environment(s) from existing media
tacklebox status Show what's installed on a media
tacklebox verify Sanity-check a built image or ISO
tacklebox recipe-gen Generate a recipe from a simplified env list
1. Quick start: a live ISO from one imageβ
cat > my-iso.json <<'EOF'
{
"media_name": "MYOS",
"size": "20G",
"default_boot": "myos",
"bootable_environments": [
{
"id": "myos",
"image": "ghcr.io/you/your-os:stable",
"title": "My OS (live)",
"modes": ["live"]
}
]
}
EOF
sudo tacklebox build my-iso.json --iso ./myos.iso -b /var/tmp/tbx
tacklebox verify ./myos.iso
Boot it anywhere UEFI: qemu-system-x86_64 -m 4G -machine q35 \ -drive if=pflash,format=raw,readonly=on,file=/usr/share/OVMF/OVMF_CODE_4M.fd \ -cdrom myos.iso. The repo's scripts/test-live-boot.sh myos.iso does
this and asserts the live root actually assembles (same gate CI runs
daily).
What you get for free (the embedded live baseline, no
configuration needed): a passwordless liveuser, display-manager
autologin matched to the desktop the image ships (GDM, SDDM, lightdm,
greetd β with the session name the image actually has), NetworkManager
enabled if present, and suspend masked during the live session.
2. Recipesβ
A recipe is one JSON file describing the media:
{
"media_name": "TUNAOS", // volume label (CDLABEL / partition label)
"size": "40G", // media size (USB/disk targets)
"default_boot": "kde", // env id systemd-boot preselects
"kargs": ["mitigations=off"], // extra kernel args appended to every env
// Embed the images themselves so installs need no network (see Β§5)
"offline_payloads": ["ghcr.io/you/your-os:stable"],
"shared_store": {
"format": "ext4",
"dedup": true, // combined-squash layout (see Β§4)
"dedup_layout": "delta" // or delta layout: base + per-env diffs
},
"bootable_environments": [
{
"id": "kde", // unique, used in paths + BLS
"image": "ghcr.io/you/your-os:kde", // bootable container image
"title": "Your OS KDE (live)", // boot menu title
"modes": ["live"], // live | install
"live_customize": ["customize.sh"], // scripts run in a container
// of the image before squashing
"initrd": "path/or/url.img" // optional initramfs override
}
]
}
tacklebox recipe-gen produces one from a simple env list if you'd
rather not start from scratch.
live_customizeβ
Each listed script runs inside a container of the env's image
(root, CAP_SYS_ADMIN, network; the script's directory is mounted and
is the working directory). Whatever it changes lands in the live squash
only β installed systems never see it. Use it for branding, flatpak
preloading, kiosk setup. The distro-agnostic basics (live user,
autologin, networking) are already handled by the embedded baseline
that runs before your scripts; TBOX_LIVE_USER / TBOX_DESKTOP
override its defaults.
3. Media targetsβ
| Target | Flag | What it is |
|---|---|---|
| Disk image | (default) -b <dir> | GPT image with ESP + per-env root partitions + shared store |
| Block device / USB | device path in recipe/flags | same layout written to real hardware |
| Live ISO | --iso out.iso | ISO9660/El Torito UEFI media; per-env squashed roots under /LiveOS |
The ISO boot chain is fully owned by tacklebox: systemd-boot β
kernel β embedded dracut modules (tbox-live) mount the ISO by label,
loop-mount the env's root image (squashfs or EROFS β autodetected),
overlay a tmpfs, and hand systemd a ready /sysroot. No dmsquash-live,
no distro-specific live packages needed in your image.
4. Multi-env ISOs and dedupβ
Several bootable_environments on one ISO give a multi-boot menu.
Storage layouts:
- per-env (default): each env gets its own
<id>.rootfs.sfs. - dedup (
"dedup": true): onecombined.rootfs.sfsshared by all envs β identical files stored once. CI asserts this meaningfully shrinks the media. - delta (
"dedup_layout": "delta"): a base squash plus small per-env diff images stacked as extra overlay layers at boot.
5. Offline installs (offline_payloads)β
Listing images in offline_payloads embeds a container store into the
media. The live environment then installs without any network pull
β the installer (fisherman, or bootc install directly) reads the
image from the embedded store. This is the reliable path: guest
networking under virtualization is not dependable for multi-GB pulls.
6. Day-2: update / add / removeβ
Media built by tacklebox stays mutable:
sudo tacklebox update --yes my.json /dev/sdX -b /var/tmp/tbx # reinstall all envs
sudo tacklebox add extra.json /dev/sdX --yes # add env(s)
sudo tacklebox remove kde /dev/sdX --yes # drop an env
tacklebox status /dev/sdX # what's on it
update reuses the initramfs/squash caches keyed by image ID, so
unchanged envs are fast.
7. Verifying and boot-testingβ
tacklebox verify <media>β structural checks (partitions, ESP, BLS entries, squash presence).scripts/test-boot.sh <image>β boots a disk image in QEMU to a login prompt.scripts/test-live-boot.sh <iso>β boots a live ISO under OVMF and requires the tbox live root to assemble through tologin:.verifypasses on media that cannot boot; the boot gates are the real signal, and CI runs them on every PR and daily.
8. The pure-Go core and the browser builderβ
internal/oci + internal/purefs implement the full media pipeline
with no root, no shell-outs, and no filesystem dependency: registry
pull, overlay-semantics unpack, desktop introspection, live-user
baking, EROFS live root, FAT32 ESP, and ISO9660/Rock Ridge/El Torito β
all validated against the kernel and firmware. cmd/purebuild is the
native CLI over that core; cmd/tbwasm compiles the same code to
WebAssembly for the browser builder
(see the tunaOS repo's docs/iso-builder-guide.md for its user guide).
9. Troubleshootingβ
| Symptom | Likely cause |
|---|---|
Boot drops to emergency, /run/tacklebox-live-done missing | ISO label mismatch β check root=tbox:CDLABEL= vs actual label (spaces are escaped as \x20) |
| Emergency shell but live root prepared | sysroot.mount lost to fstab-generator β verify tbox-live-generator is executable in the initramfs (lsinitrd | grep generator) |
unknown filesystem type on the rootfs image | initramfs lacks erofs/squashfs modules β rebuild initramfs from the image (dracut --no-hostonly --add "tbox-live tbox-root") |
| Installer can't read embedded store | overlay-on-overlay needs fuse-overlayfs in the image + mount_program in storage.conf |
--keep-vm on the test scripts leaves QEMU running with a monitor
socket for interactive debugging; the serial log is always written next
to the output.