Root cause of USB boot failure: our xorriso used -e boot/grub/efi.img to embed the EFI image inside the ISO. This works for CD-ROM and QEMU but NOT for USB on real UEFI hardware. Fix: use the Will Haley / Debian live-build approach: - -append_partition 2 (GPT type EFI) appends efi.img AFTER ISO data - -e --interval:appended_partition_2:all:: references the appended partition - --mbr-force-bootable forces MBR active flag - grub-mkstandalone with embedded bootstrap config (searches for grub.cfg) - grub.cfg placed in both /boot/grub/ AND /EFI/BOOT/ on ISO - grub.cfg uses search --label ARCHIPELAGO to find the ISO root This is the exact approach used by StartOS, TAILS, and every production custom Debian live ISO that boots from USB. Also: iso-debug, iso-branding skills + reference docs, dev-start.sh option 0 for branding dev, improved dev-branding.sh and test-iso-qemu.sh. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
4.8 KiB
name, description, allowed-tools
| name | description | allowed-tools |
|---|---|---|
| build-iso | Build Archipelago auto-installer ISOs. Custom debootstrap base (no Debian Live dependency), live-boot for squashfs root, hybrid BIOS+UEFI boot, Archipelago branding. Use when user says "build ISO", "build image", "create installer", or needs to work on the ISO build pipeline. | Bash, Read, Edit, Write, Grep, Glob, Agent |
Build Archipelago ISO
Architecture (dev-iso branch)
Custom debootstrap-based installer. NO Debian Live ISO download.
| Component | Source | Size |
|---|---|---|
| Installer squashfs | debootstrap --variant=minbase + live-boot | ~180MB |
| Target rootfs | Docker build (Debian bookworm, full stack) | ~1.5GB compressed |
| Kernel + initramfs | From debootstrap, with live-boot hooks | ~50MB |
| GRUB + ISOLINUX | Built from packages during Step 2 | ~1MB |
| Total ISO | Unbundled | ~2.2GB |
Build Pipeline (6 Steps)
Step 1 (lines ~200-430): Build target rootfs via Docker
- Debian bookworm + all runtime packages (podman, nginx, tor, chromium, etc.)
--no-install-recommendsfor size reduction- Strips docs/man/locales
- Output:
archipelago-rootfs.tar(~1.5GB)
Step 2 (lines ~430-710): Build installer environment via debootstrap
debootstrap --variant=minbaseinside a container- Installs live-boot via chroot (NOT --include — minbase can't resolve it)
- Custom initramfs with live-boot hooks
- Builds GRUB EFI image with grub-mkimage
- Creates ISOLINUX files, EFI boot image
- Installs GRUB theme + background
- Output: vmlinuz, initrd.img, filesystem.squashfs, BOOTX64.EFI, efi.img, isolinux.bin
Step 3 (lines ~710-850): Add Archipelago components
- Backend binary, web UI, rootfs.tar, scripts, Plymouth theme
Step 3b (lines ~850-1230): Bundle container images (skipped if UNBUNDLED=1)
Step 4 (lines ~1230-2380): Generate auto-install.sh
- Embedded installer script (~1100 lines)
- Disk detection, partitioning, LUKS encryption, GRUB install
- Installs GRUB + Plymouth theme on target
Step 5 (lines ~2380-2460): Configure boot loaders
- Write GRUB config (boot=live components)
- Write ISOLINUX config
- Both reference kernel at /live/vmlinuz
Step 6 (lines ~2460-2540): Create final ISO
- xorriso with hybrid BIOS+UEFI boot
- Uses proven MBR from
branding/isohdpfx.bin -partition_offset 16for UEFI compatibility
CI Workflow
Branch: dev-iso → .gitea/workflows/build-iso-dev.yml
Branch: main → .gitea/workflows/build-iso.yml
Dev CI includes a smoke test step that verifies:
- All critical files present in ISO
- Initrd contains live-boot scripts
- grub.cfg has boot=live
- Fails build before copying to Builds if any check fails
Critical Rules
-
MBR: Always use
branding/isohdpfx.bin(Debian Live MBR, starts with4552). The ISOLINUX generic MBR (33ed) doesn't boot on all hardware. -
live-boot: Must be installed via
chroot /installer apt-get installAFTER debootstrap completes. The--includeflag silently fails for live-boot. -
Initramfs:
update-initramfsneeds/proc,/sys,/devbind-mounted in the chroot. Without them, the initramfs is broken. -
scripts/live is a FILE: Verify with
[ -e ]not[ -d ]. -
Kernel params: Must include
boot=live components. Withoutboot=live, live-boot hooks never activate. -
partition_offset 16: Required in xorriso for UEFI firmware to recognize the USB.
-
Never push during a running CI build: The gitea-runner kills in-progress builds when a new commit arrives on the same branch.
Quick Commands
# Build locally (on .228):
ssh -i ~/.ssh/archipelago-deploy archipelago@192.168.1.228
cd ~/archy/image-recipe
sudo UNBUNDLED=1 DEV_SERVER=localhost BUILD_FROM_SOURCE=0 ./build-auto-installer-iso.sh
# Check build status:
ssh -i ~/.ssh/archipelago-deploy archipelago@192.168.1.228 \
"ps aux | grep build-auto | grep -v grep"
# Check latest ISO:
ssh -i ~/.ssh/archipelago-deploy archipelago@192.168.1.228 \
"ls -lt /var/lib/archipelago/filebrowser/Builds/archipelago-dev-*.iso | head -3"
# Verify ISO:
# See /iso-debug skill for the full verification checklist
# Iterate on branding without rebuilding:
./image-recipe/dev-branding.sh [path-to-iso]
# Or: ./scripts/dev-start.sh → option 0
Key Files
| File | Role |
|---|---|
image-recipe/build-auto-installer-iso.sh |
Main build script (~2600 lines) |
image-recipe/build-unbundled-iso.sh |
Wrapper: sets UNBUNDLED=1 |
image-recipe/branding/isohdpfx.bin |
Proven MBR (432 bytes) |
image-recipe/branding/grub-theme/ |
GRUB theme + background |
image-recipe/branding/plymouth-theme/ |
Plymouth boot splash |
scripts/image-versions.sh |
Pinned container image versions |
.gitea/workflows/build-iso-dev.yml |
CI for dev-iso branch |
image-recipe/test-iso-qemu.sh |
QEMU test script |
image-recipe/dev-branding.sh |
Quick branding iteration |