archy/.claude/plans/silly-wondering-flamingo.md
Dorian 4326f019c1 feat: replace Debian Live with custom debootstrap ISO base + branding
Major ISO build overhaul on dev-iso branch:

- Replace ~800MB Debian Live download with debootstrap --variant=minbase
  (~150MB installer squashfs built from scratch)
- Custom initramfs with archipelago-mount hook for boot media detection
- Systemd service auto-starts installer (replaces profile.d hack)
- GRUB + ISOLINUX configs written from scratch (no Debian Live dependency)
- EFI boot image built with grub-mkimage (no more MBR extraction)
- Archipelago GRUB theme: dark background, Bitcoin orange accents
- Theme installed on both installer ISO and target system
- Rootfs optimizations: --no-install-recommends, strip docs/man/locales,
  remove firmware-misc-nonfree/wget/htop, add explicit font deps
- Separate CI workflow (build-iso-dev.yml) for dev-iso branch
- Includes pre-existing fixes from main (build-iso.yml, middleware, Login)

Target: sub-2GB unbundled ISO (down from 3.9GB)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 11:34:29 +00:00

9.4 KiB

ISO Overhaul: Custom Minimal Base + Branding + Size Optimization

Context

The Archipelago ISO is ~3.9GB — too large. The root cause is a ~800MB Debian Live ISO used as the boot base, plus a ~2.1GB rootfs with no --no-install-recommends. We're replacing the Debian Live dependency entirely with a custom debootstrap-built installer, adding full Archipelago branding to the boot chain, and stripping the rootfs. Target: sub-2GB ISO.

All work on dev-iso branch with its own CI workflow. Main branch stays untouched.


Phase 0: Branch + CI Setup

Create dev-iso branch and separate CI workflow.

  1. Branch from current main
  2. Create .gitea/workflows/build-iso-dev.yml:
    • Trigger: push: branches: [dev-iso] + workflow_dispatch
    • Same structure as build-iso.yml (131 lines) but:
      • Remove "Cache Debian Live ISO" step (no longer needed)
      • Add debootstrap, squashfs-tools, isolinux, syslinux-common, mtools, grub-efi-amd64-bin, grub-pc-bin to tool dependencies
      • Output naming: archipelago-dev-unbundled-{date}.iso
    • Keep: backend build, frontend build, type check, tests, build report
  3. Push and verify CI triggers on .228 runner

Files:

  • New: .gitea/workflows/build-iso-dev.yml

Phase 1: Rootfs Size Optimizations

Shrink rootfs.tar from ~2.1GB to ~1.5GB. Only touches the Dockerfile heredoc in Step 1 (lines 210-335).

1.1 Add --no-install-recommends

  • Line 229: apt-get install -yapt-get install -y --no-install-recommends
  • Line 269: Same for Tailscale install
  • Explicitly add packages that may be needed as recommends: fonts-liberation, xfonts-base (for Chromium kiosk)
  • Saves: ~150-300MB

1.2 Remove firmware-misc-nonfree

  • Line 257: Remove firmware-misc-nonfree from package list
  • Keep: firmware-realtek, firmware-iwlwifi, intel-microcode, amd64-microcode
  • Saves: ~50-80MB

1.3 Strip docs/man/locales

  • Add after line 264 (after apt-get clean):
    RUN find /usr/share/doc -depth -type f ! -name copyright -delete 2>/dev/null; \
        find /usr/share/doc -empty -delete 2>/dev/null; \
        rm -rf /usr/share/man /usr/share/info /usr/share/lintian /usr/share/linda; \
        find /usr/share/locale -maxdepth 1 -mindepth 1 ! -name 'en_US' ! -name 'locale.alias' -exec rm -rf {} +
    
  • Saves: ~50-80MB

1.4 Remove wget and htop

  • Lines 244, 246: Remove wget (curl covers it) and htop (luxury tool)
  • Keep git (used by self-update system)
  • Saves: ~5MB (minor but removes unnecessary surface)

Verification

  • Build ISO, compare rootfs.tar size
  • Boot in QEMU, verify: kiosk renders, SSH works, nginx serves UI, podman runs

Files modified:

  • image-recipe/build-auto-installer-iso.sh (Step 1 Dockerfile heredoc, lines 210-335)

Phase 2: Replace Debian Live with Custom Debootstrap Base

The big one. Replaces Steps 2, 5, and parts of 4 and 6.

2.1 New Step 2: Build Minimal Installer Environment

Replace lines 420-502 entirely. Run debootstrap inside a container to produce:

  • vmlinuz — kernel (reused from linux-image-amd64)
  • initrd.img — custom initramfs with ISO-mount hook
  • filesystem.squashfs — minimal Debian root (~120-180MB)

The installer squashfs contains only what's needed to run the auto-install script:

  • debootstrap --variant=minbase --include=systemd,systemd-sysv,udev,bash,coreutils,mount,util-linux,cryptsetup,parted,dosfstools,e2fsprogs,kmod,procps,iproute2,ca-certificates,gdisk
  • Auto-login on tty1 via getty override
  • systemd service that auto-starts the installer (replaces profile.d hack)

Key: Custom initramfs hook (local-bottom/archipelago-mount) that:

  1. Scans /dev/sr0, /dev/sd* for a partition containing archipelago/auto-install.sh
  2. Mounts it read-only at /run/archiso
  3. This replaces Debian Live's boot=live components mechanism

2.2 New Step 5: Assemble ISO Directory

Replace lines 2236-2448 entirely. Much simpler — no squashfs overlay mechanism, no tools extraction (tools are in the squashfs), no profile.d manipulation.

New Step 5 just assembles the directory structure:

$INSTALLER_ISO/
  live/
    vmlinuz
    initrd.img
    filesystem.squashfs
  boot/grub/
    grub.cfg
    themes/archipelago/  (Phase 3)
    efi.img              (built with grub-mkimage)
  isolinux/
    isolinux.bin
    ldlinux.c32
    isolinux.cfg
  EFI/BOOT/
    BOOTX64.EFI          (built with grub-mkimage)
  archipelago/
    auto-install.sh
    rootfs.tar
    bin/archipelago
    web-ui/
    scripts/
    container-images/    (if bundled)

Generate EFI boot image with grub-mkimage and ISOLINUX files from the isolinux package. No more extracting MBR from Debian Live.

2.3 Updated Step 6: ISO Creation

Replace lines 2461-2511 (MBR extraction + EFI image search). Use:

  • MBR: /usr/lib/ISOLINUX/isohdpfx.bin (from isolinux package)
  • EFI: boot/grub/efi.img (built in Step 5)
  • xorriso command stays the same structure

2.4 Update Boot Media Paths in Step 4 (auto-install.sh)

Lines 1154-1155: Add /run/archiso as first search path:

for dev in /run/archiso /cdrom /media/cdrom /run/live/medium /lib/live/mount/medium; do

Also update lines 2326, 2377 (no longer needed — replaced by systemd service in installer squashfs).

2.5 Remove Debian Live cleanup from auto-install.sh

The installed system's auto-install script currently removes live-boot, live-boot-initramfs-tools, live-config (around line 1872). With the custom base, these packages won't exist in the rootfs, so this cleanup becomes a harmless no-op — but should be cleaned up for clarity.

Verification

  • Build ISO, verify size < 2GB
  • Boot in QEMU (UEFI mode): verify GRUB menu → installer → full install → reboot
  • Boot in QEMU (BIOS mode): verify ISOLINUX → installer → full install → reboot
  • After install: SSH, web UI, kiosk, container loading all work
  • Test test-iso-qemu.sh (may need minor path updates)

Files modified:

  • image-recipe/build-auto-installer-iso.sh (Steps 2, 4, 5, 6 — major rewrite)

Phase 3: Archipelago Boot Branding

Custom GRUB theme, installer banner, installed system GRUB.

3.1 Create GRUB Theme

New directory: image-recipe/branding/grub-theme/

  • theme.txt — dark background (#0a0a0a), white text, Bitcoin orange (#f7931a) highlight
  • background.png — 1920x1080 dark with subtle Archipelago logo watermark
  • Font files (.pf2) — generated with grub-mkfont from DejaVu Sans during build

GRUB menu entries:

  • "Install Archipelago" (default, quiet boot)
  • "Install Archipelago (verbose)" (no quiet, for debugging)
  • "Boot from local disk" (chainloader)

3.2 Create ISOLINUX Theme

New file: image-recipe/branding/isolinux.cfg

  • Matching dark theme for legacy BIOS boot
  • Same menu entries as GRUB

3.3 Branded Installer Banner

The systemd service's start script displays:

  ARCHIPELAGO BITCOIN NODE OS
  Automatic Installer v0.1.0

  Press Enter to start installation...

3.4 Install GRUB Theme to Target System

In Step 4 (auto-install.sh), before update-grub (around line 1888):

  • Copy GRUB theme from ISO to /mnt/target/boot/grub/themes/archipelago/
  • Add GRUB_THEME="/boot/grub/themes/archipelago/theme.txt" to /mnt/target/etc/default/grub
  • The installed system boots with Archipelago branding, not Debian default

3.5 Create Background Image

Render from existing SVG favicon (neode-ui/public/assets/icon/favico-black-v2.svg) to PNG at appropriate sizes. Dark background with subtle centered logo.

Verification

  • Boot ISO: GRUB shows Archipelago theme (dark + orange)
  • No Debian branding visible anywhere
  • After install: target system GRUB also shows Archipelago theme

Files:

  • New: image-recipe/branding/grub-theme/theme.txt
  • New: image-recipe/branding/grub-theme/background.png
  • New: image-recipe/branding/isolinux.cfg
  • Modified: image-recipe/build-auto-installer-iso.sh (Steps 5, 4)

Risk Areas

Risk Severity Mitigation
Custom initramfs fails to find USB media High Test multiple USB controller types in QEMU; add verbose fallback boot option
Missing packages in minbase break install Medium Trace auto-install.sh dependencies; test full install flow
GRUB EFI image missing modules High Include all common modules in grub-mkimage; test UEFI + BIOS
Kiosk breaks without recommends Medium Explicitly add Chromium/X11 font deps; test kiosk before merge
initramfs overlayfs mount fails High Follow well-established patterns from Arch/Ubuntu live ISOs

Implementation Order

  1. Phase 0 — branch + CI (~1 hour)
  2. Phase 1 — rootfs size opts (~2 hours, push + verify)
  3. Phase 2 — custom base (~8-10 hours, iterative QEMU testing)
  4. Phase 3 — branding (~3 hours)

Phases are sequential — each builds on the previous. Push after each phase, verify CI passes.


Key Files

File Role
image-recipe/build-auto-installer-iso.sh Main build script — most changes here
.gitea/workflows/build-iso-dev.yml New CI workflow for dev-iso branch
image-recipe/branding/grub-theme/* New GRUB theme assets
image-recipe/branding/isolinux.cfg New ISOLINUX config
image-recipe/test-iso-qemu.sh QEMU test script (minor updates)
.gitea/workflows/build-iso.yml Reference for new CI workflow
scripts/image-versions.sh Unchanged — container image versions