Dorian 6cfb8082c5 fix: xorriso append_partition for real USB boot + grub-mkstandalone
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>
2026-03-28 11:34:29 +00:00

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-recommends for size reduction
  • Strips docs/man/locales
  • Output: archipelago-rootfs.tar (~1.5GB)

Step 2 (lines ~430-710): Build installer environment via debootstrap

  • debootstrap --variant=minbase inside 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 16 for 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

  1. MBR: Always use branding/isohdpfx.bin (Debian Live MBR, starts with 4552). The ISOLINUX generic MBR (33ed) doesn't boot on all hardware.

  2. live-boot: Must be installed via chroot /installer apt-get install AFTER debootstrap completes. The --include flag silently fails for live-boot.

  3. Initramfs: update-initramfs needs /proc, /sys, /dev bind-mounted in the chroot. Without them, the initramfs is broken.

  4. scripts/live is a FILE: Verify with [ -e ] not [ -d ].

  5. Kernel params: Must include boot=live components. Without boot=live, live-boot hooks never activate.

  6. partition_offset 16: Required in xorriso for UEFI firmware to recognize the USB.

  7. 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