diff --git a/.claude/plans/silly-wondering-flamingo.md b/.claude/plans/silly-wondering-flamingo.md new file mode 100644 index 00000000..c6e6903f --- /dev/null +++ b/.claude/plans/silly-wondering-flamingo.md @@ -0,0 +1,243 @@ +# 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 -y` → `apt-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): + ```dockerfile + 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: +```bash +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 | diff --git a/.gitea/workflows/build-iso-dev.yml b/.gitea/workflows/build-iso-dev.yml new file mode 100644 index 00000000..38153a1a --- /dev/null +++ b/.gitea/workflows/build-iso-dev.yml @@ -0,0 +1,128 @@ +name: Build Archipelago ISO (dev) + +on: + push: + branches: [dev-iso] + workflow_dispatch: + +jobs: + build-iso: + runs-on: ubuntu-latest + timeout-minutes: 60 + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 1 + clean: false + + - name: Install ISO build dependencies + run: | + sudo apt-get update -qq + sudo apt-get install -y -qq \ + debootstrap squashfs-tools xorriso \ + isolinux syslinux-common mtools \ + grub-efi-amd64-bin grub-pc-bin grub-common + + - name: Build backend + run: | + source $HOME/.cargo/env 2>/dev/null || true + cargo build --release --manifest-path core/Cargo.toml + + - name: Build frontend + run: cd neode-ui && npm ci && npm run build + + - name: Type check frontend + run: cd neode-ui && npx vue-tsc -b --noEmit + + - name: Run frontend tests + run: cd neode-ui && npx vitest run + + - name: Configure root podman for insecure registry + run: | + sudo mkdir -p /etc/containers/registries.conf.d + echo '[[registry]] + location = "80.71.235.15:3000" + insecure = true' | sudo tee /etc/containers/registries.conf.d/archipelago.conf + + - name: Build unbundled ISO + run: | + cd image-recipe + export ARCHIPELAGO_BIN="$(pwd)/../core/target/release/archipelago" + ls -la "$ARCHIPELAGO_BIN" || echo "WARNING: binary not found" + sudo -E UNBUNDLED=1 DEV_SERVER=localhost BUILD_FROM_SOURCE=0 \ + ARCHIPELAGO_BIN="$ARCHIPELAGO_BIN" \ + ./build-auto-installer-iso.sh + + - name: Copy to Builds + run: | + ISO=$(ls image-recipe/results/archipelago-installer-unbundled-*.iso 2>/dev/null | head -1) + if [ -n "$ISO" ]; then + DATE=$(date +%Y%m%d-%H%M) + DEST="/var/lib/archipelago/filebrowser/Builds/archipelago-dev-unbundled-${DATE}.iso" + sudo cp "$ISO" "$DEST" + sudo chown 1000:1000 "$DEST" + echo "ISO: archipelago-dev-unbundled-${DATE}.iso" + echo "Size: $(du -h "$DEST" | cut -f1)" + echo "SHA256: $(sha256sum "$DEST" | cut -d' ' -f1)" + fi + + - name: Build report + if: always() + continue-on-error: true + run: | + set +eo pipefail + echo "══════════════════════════════════════════" + echo "DEV ISO BUILD REPORT" + echo "══════════════════════════════════════════" + echo "Commit: $(git rev-parse --short HEAD) ($(git log -1 --format=%s))" + echo "Branch: ${GITHUB_REF_NAME:-dev-iso}" + echo "Date: $(date -u '+%Y-%m-%d %H:%M:%S UTC')" + echo "Runner: $(hostname)" + echo "" + echo "── Artifacts ──" + ls -lh image-recipe/results/*.iso 2>/dev/null || echo " No ISO produced" + ls -lh /var/lib/archipelago/filebrowser/Builds/archipelago-dev-*.iso 2>/dev/null | tail -3 + echo "" + echo "── Rootfs contents check ──" + ROOTFS=$(ls image-recipe/build/auto-installer/archipelago-rootfs.tar 2>/dev/null) || true + if [ -n "$ROOTFS" ]; then + echo " rootfs.tar: $(sudo du -h "$ROOTFS" 2>/dev/null | cut -f1 || echo 'unknown')" + echo " nginx config: $(sudo tar tf "$ROOTFS" ./etc/nginx/sites-available/archipelago 2>/dev/null && echo 'PRESENT' || echo 'MISSING')" + echo " SSL cert: $(sudo tar tf "$ROOTFS" ./etc/archipelago/ssl/archipelago.crt 2>/dev/null && echo 'PRESENT' || echo 'MISSING')" + echo " kiosk launcher: $(sudo tar tf "$ROOTFS" ./usr/local/bin/archipelago-kiosk-launcher 2>/dev/null && echo 'PRESENT' || echo 'MISSING')" + echo " backend binary: $(sudo tar tf "$ROOTFS" ./usr/local/bin/archipelago 2>/dev/null && echo 'PRESENT' || echo 'MISSING')" + echo " web-ui index: $(sudo tar tf "$ROOTFS" ./opt/archipelago/web-ui/index.html 2>/dev/null && echo 'PRESENT' || echo 'MISSING')" + else + echo " rootfs.tar not found in workspace" + fi + echo "" + echo "── ISO contents check ──" + ISO=$(ls image-recipe/results/archipelago-installer-unbundled-*.iso 2>/dev/null | head -1) || true + if [ -n "$ISO" ]; then + echo " ISO size: $(sudo du -h "$ISO" 2>/dev/null | cut -f1 || echo 'unknown')" + ISO_MOUNT=$(mktemp -d) + if sudo mount -o loop,ro "$ISO" "$ISO_MOUNT" 2>/dev/null; then + echo " auto-install.sh: $([ -f "$ISO_MOUNT/archipelago/auto-install.sh" ] && echo 'PRESENT' || echo 'MISSING')" + echo " rootfs.tar: $([ -f "$ISO_MOUNT/archipelago/rootfs.tar" ] && echo "PRESENT ($(sudo du -h "$ISO_MOUNT/archipelago/rootfs.tar" 2>/dev/null | cut -f1))" || echo 'MISSING')" + echo " backend bin: $([ -f "$ISO_MOUNT/archipelago/bin/archipelago" ] && echo "PRESENT ($(sudo du -h "$ISO_MOUNT/archipelago/bin/archipelago" 2>/dev/null | cut -f1))" || echo 'MISSING')" + echo " frontend: $([ -f "$ISO_MOUNT/archipelago/web-ui/index.html" ] && echo 'PRESENT' || echo 'MISSING')" + echo " vmlinuz: $([ -f "$ISO_MOUNT/live/vmlinuz" ] && echo 'PRESENT' || echo 'MISSING')" + echo " initrd: $([ -f "$ISO_MOUNT/live/initrd.img" ] && echo 'PRESENT' || echo 'MISSING')" + echo " squashfs: $([ -f "$ISO_MOUNT/live/filesystem.squashfs" ] && echo "PRESENT ($(sudo du -h "$ISO_MOUNT/live/filesystem.squashfs" 2>/dev/null | cut -f1))" || echo 'MISSING')" + echo " grub theme: $([ -d "$ISO_MOUNT/boot/grub/themes/archipelago" ] && echo 'PRESENT' || echo 'MISSING')" + sudo umount "$ISO_MOUNT" 2>/dev/null || true + else + echo " Could not mount ISO for inspection" + fi + rmdir "$ISO_MOUNT" 2>/dev/null || true + fi + echo "══════════════════════════════════════════" + + - name: Fix workspace permissions + if: always() + run: | + sudo chown -R $(id -u):$(id -g) . 2>/dev/null || true + sudo chmod -R u+rwX . 2>/dev/null || true + sudo chown -R $(id -u):$(id -g) "$HOME/.cache/act" 2>/dev/null || true + sudo chmod -R u+rwX "$HOME/.cache/act" 2>/dev/null || true diff --git a/image-recipe/branding/grub-theme/theme.txt b/image-recipe/branding/grub-theme/theme.txt new file mode 100644 index 00000000..da71bebb --- /dev/null +++ b/image-recipe/branding/grub-theme/theme.txt @@ -0,0 +1,52 @@ +# Archipelago GRUB Theme +# Dark background with Bitcoin orange accents + +title-text: "" +desktop-color: "#0a0a0a" +terminal-font: "DejaVu Sans Regular 16" + ++ boot_menu { + left = 25% + top = 40% + width = 50% + height = 30% + item_font = "DejaVu Sans Regular 16" + item_color = "#aaaaaa" + selected_item_font = "DejaVu Sans Regular 16" + selected_item_color = "#ffffff" + selected_item_pixmap_style = "select_*.png" + item_height = 36 + item_spacing = 8 + item_padding = 16 + scrollbar = false +} + ++ label { + left = 25% + top = 20% + width = 50% + text = "A R C H I P E L A G O" + font = "DejaVu Sans Bold 24" + color = "#f7931a" + align = "center" +} + ++ label { + left = 25% + top = 28% + width = 50% + text = "Bitcoin Node OS" + font = "DejaVu Sans Regular 14" + color = "#888888" + align = "center" +} + ++ label { + left = 25% + top = 90% + width = 50% + text = "Use arrow keys to select, Enter to boot" + font = "DejaVu Sans Regular 12" + color = "#555555" + align = "center" +} diff --git a/image-recipe/build-auto-installer-iso.sh b/image-recipe/build-auto-installer-iso.sh index eb8e4da0..0b186e42 100755 --- a/image-recipe/build-auto-installer-iso.sh +++ b/image-recipe/build-auto-installer-iso.sh @@ -31,7 +31,7 @@ # build-auto-installer-iso.sh — Main orchestrator (config, CLI args, step sequencing) # lib/ # rootfs.sh — Step 1: Build root filesystem via Docker (~185 lines) -# installer-env.sh — Step 2: Download/extract Debian Live base ISO (~80 lines) +# installer-env.sh — Step 2: Build minimal installer via debootstrap (~80 lines) # components.sh — Step 3: Add Archipelago components (binary, configs, web UI) (~120 lines) # container-images.sh — Step 3b: Bundle container images for offline install (~330 lines) # auto-install-script.sh — Step 4: Generate the embedded auto-install.sh (~615 lines) @@ -133,36 +133,42 @@ check_tools() { missing="$missing docker-or-podman" fi - if ! command -v xorriso >/dev/null 2>&1; then - missing="$missing xorriso" + for tool in xorriso mksquashfs; do + if ! command -v $tool >/dev/null 2>&1; then + missing="$missing $tool" + fi + done + # Check for isolinux MBR (needed for hybrid USB boot) + if [ ! -f /usr/lib/ISOLINUX/isohdpfx.bin ] && [ ! -f /usr/share/syslinux/isohdpfx.bin ]; then + missing="$missing isolinux" fi - if ! command -v 7z >/dev/null 2>&1 && ! command -v 7za >/dev/null 2>&1; then - missing="$missing p7zip-full" - fi - + if [ -n "$missing" ]; then - echo "❌ Missing required tools:$missing" - + echo "Missing required tools:$missing" + if [ "$can_install" = true ]; then - echo " 📦 Auto-installing missing dependencies..." + echo " Auto-installing missing dependencies..." apt-get update -qq - + if [[ "$missing" == *"xorriso"* ]]; then apt-get install -y xorriso fi - if [[ "$missing" == *"p7zip-full"* ]]; then - apt-get install -y p7zip-full + if [[ "$missing" == *"mksquashfs"* ]]; then + apt-get install -y squashfs-tools fi - + if [[ "$missing" == *"isolinux"* ]]; then + apt-get install -y isolinux syslinux-common + fi + if [[ "$missing" == *"docker-or-podman"* ]]; then echo " Installing podman..." apt-get install -y podman CONTAINER_CMD="podman" fi - - echo " ✅ Dependencies installed successfully!" + + echo " Dependencies installed successfully!" else - echo " Install with: sudo apt install xorriso podman" + echo " Install with: sudo apt install xorriso squashfs-tools isolinux podman" echo " Or run this script with sudo to auto-install" exit 1 fi @@ -226,7 +232,7 @@ RUN echo "deb http://deb.debian.org/debian bookworm main non-free-firmware" > /e rm -f /etc/apt/sources.list.d/debian.sources # Install all packages we need including nginx, podman, tor, and openssl (for self-signed certs) -RUN apt-get update && apt-get install -y \ +RUN apt-get update && apt-get install -y --no-install-recommends \ ${LINUX_IMAGE_PKG} \ ${GRUB_EFI_PKG} \ ${GRUB_EFI_SIGNED_PKG} \ @@ -241,9 +247,7 @@ RUN apt-get update && apt-get install -y \ podman \ tor \ curl \ - wget \ git \ - htop \ vim-tiny \ ca-certificates \ openssl \ @@ -254,19 +258,26 @@ RUN apt-get update && apt-get install -y \ cryptsetup \ firmware-realtek \ firmware-iwlwifi \ - firmware-misc-nonfree \ intel-microcode \ amd64-microcode \ xorg \ chromium \ unclutter \ + fonts-liberation \ + xfonts-base \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* +# Strip docs, man pages, and unused locales +RUN find /usr/share/doc -depth -type f ! -name copyright -delete 2>/dev/null || true && \ + find /usr/share/doc -empty -delete 2>/dev/null || true && \ + 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 {} + 2>/dev/null || true + # Install Tailscale from official repo RUN curl -fsSL https://pkgs.tailscale.com/stable/debian/bookworm.noarmor.gpg | tee /usr/share/keyrings/tailscale-archive-keyring.gpg >/dev/null && \ curl -fsSL https://pkgs.tailscale.com/stable/debian/bookworm.tailscale-keyring.list | tee /etc/apt/sources.list.d/tailscale.list && \ - apt-get update && apt-get install -y tailscale && \ + apt-get update && apt-get install -y --no-install-recommends tailscale && \ apt-get clean && rm -rf /var/lib/apt/lists/* # Configure locale @@ -418,88 +429,249 @@ else fi # ============================================================================= -# STEP 2: Create installer environment +# STEP 2: Build minimal installer environment (replaces Debian Live) # ============================================================================= echo "" -echo "📦 Step 2: Creating installer environment..." +echo "Step 2: Building minimal installer environment via debootstrap..." -# Download Debian Live as our installer base -BASE_ISO="$WORK_DIR/debian-live-installer.iso" -EXPECTED_SIZE=350000000 # ~350MB min (Debian 12 Live standard ~600MB) +INSTALLER_ISO="$WORK_DIR/installer-iso" +INSTALLER_SQUASHFS="$WORK_DIR/installer-squashfs" +rm -rf "$INSTALLER_ISO" "$INSTALLER_SQUASHFS" +mkdir -p "$INSTALLER_ISO/live" "$INSTALLER_ISO/archipelago" +mkdir -p "$INSTALLER_ISO/boot/grub" "$INSTALLER_ISO/isolinux" +mkdir -p "$INSTALLER_ISO/EFI/BOOT" -# Check if file exists and is complete -if [ -f "$BASE_ISO" ]; then - CURRENT_SIZE=$(stat -f%z "$BASE_ISO" 2>/dev/null || stat -c%s "$BASE_ISO" 2>/dev/null || echo 0) - if [ "$CURRENT_SIZE" -ge "$EXPECTED_SIZE" ]; then - echo " ✅ Debian Live base already downloaded" - else - echo " Found incomplete download ($(($CURRENT_SIZE / 1024 / 1024))MB), removing..." - rm -f "$BASE_ISO" +# Build the installer filesystem inside a container +# This creates: vmlinuz, initrd.img, filesystem.squashfs +echo " Building installer rootfs with debootstrap (this takes a few minutes)..." +$CONTAINER_CMD run --rm --privileged --platform $CONTAINER_PLATFORM \ + -v "$WORK_DIR:/output" \ + -e DEB_ARCH="$DEB_ARCH" \ + -e LIB_DIR="$LIB_DIR" \ + debian:bookworm bash -c ' +set -e + +apt-get update -qq +apt-get install -y -qq debootstrap squashfs-tools initramfs-tools dosfstools mtools \ + grub-efi-amd64-bin grub-pc-bin grub-common isolinux syslinux-common + +echo " [container] Running debootstrap --variant=minbase..." +debootstrap --variant=minbase --arch=${DEB_ARCH} \ + --include=systemd,systemd-sysv,udev,dbus,bash,coreutils,mount,util-linux,\ +kmod,procps,iproute2,ca-certificates,gdisk,\ +cryptsetup,cryptsetup-initramfs,parted,dosfstools,e2fsprogs,\ +linux-image-${DEB_ARCH},grub-efi-${DEB_ARCH},grub-pc-bin,\ +pciutils,usbutils,less,nano \ + bookworm /installer http://deb.debian.org/debian + +echo " [container] Configuring installer environment..." + +# Set hostname +echo "archipelago-installer" > /installer/etc/hostname + +# Set root password +echo "root:archipelago" | chroot /installer chpasswd + +# Auto-login on tty1 +mkdir -p /installer/etc/systemd/system/getty@tty1.service.d +cat > /installer/etc/systemd/system/getty@tty1.service.d/autologin.conf < /installer/etc/systemd/system/archipelago-installer.service < /installer/usr/local/bin/archipelago-start-installer </dev/null 2>&1; then - cd "$WORK_DIR" - wget --tries=10 --read-timeout=120 --continue --progress=bar:force \ - --no-check-certificate \ - "$ISO_URL" || { - echo " ❌ Download failed or incomplete" - echo " Partial file kept - run script again to continue" - exit 1 - } - - # Find the downloaded file — wget may use URL filename or follow redirects - FOUND_ISO=$(find "$WORK_DIR" -maxdepth 1 -name "debian-live-12*.iso" -o -name "download" 2>/dev/null | head -1) - if [ -n "$FOUND_ISO" ] && [ -f "$FOUND_ISO" ]; then - mv "$FOUND_ISO" "$BASE_ISO" - else - echo " ❌ Downloaded file not found in $WORK_DIR" - echo " Files present:" - ls -la "$WORK_DIR"/*.iso 2>/dev/null || echo " (no .iso files)" - exit 1 - fi - else - # Fallback to curl (no resume support) - curl -L --location-trusted --retry 10 --retry-delay 5 \ - --connect-timeout 60 --max-time 1800 \ - -o "$BASE_ISO" \ - "$ISO_URL" || { - echo " ❌ Download failed" - rm -f "$BASE_ISO" - exit 1 - } +# Custom initramfs hook: mount ISO boot media at /run/archiso +mkdir -p /installer/etc/initramfs-tools/hooks +cat > /installer/etc/initramfs-tools/hooks/archipelago </dev/null || true +copy_exec /sbin/blkid +manual_add_modules iso9660 vfat squashfs overlay +HOOK +chmod +x /installer/etc/initramfs-tools/hooks/archipelago + +mkdir -p /installer/etc/initramfs-tools/scripts/local-bottom +cat > /installer/etc/initramfs-tools/scripts/local-bottom/archipelago-mount </dev/null || continue + mount -o ro "\$dev" /run/archiso 2>/dev/null || continue + if [ -d /run/archiso/archipelago ]; then + log_end_msg 0 + echo "Found Archipelago media on \$dev" + exit 0 fi - - # Verify download size - FINAL_SIZE=$(stat -f%z "$BASE_ISO" 2>/dev/null || stat -c%s "$BASE_ISO" 2>/dev/null || echo 0) - if [ "$FINAL_SIZE" -lt "$EXPECTED_SIZE" ]; then - echo " ❌ Download incomplete: got $(($FINAL_SIZE / 1024 / 1024))MB, expected 352MB" + umount /run/archiso 2>/dev/null || true +done + +log_end_msg 1 +echo "Archipelago boot media not found (will retry from userspace)" +INITSCRIPT +chmod +x /installer/etc/initramfs-tools/scripts/local-bottom/archipelago-mount + +# Strip docs and man pages from installer +rm -rf /installer/usr/share/man/* /installer/usr/share/doc/* +rm -rf /installer/var/lib/apt/lists/* /installer/var/cache/apt/* + +# Extract kernel +KVER=$(ls /installer/lib/modules/ | sort -V | tail -1) +echo " [container] Kernel version: $KVER" +cp /installer/boot/vmlinuz-$KVER /output/vmlinuz + +# Build initramfs with our custom hooks +chroot /installer update-initramfs -c -k $KVER +cp /installer/boot/initrd.img-$KVER /output/initrd.img + +# Create squashfs +echo " [container] Creating installer squashfs..." +mksquashfs /installer /output/filesystem.squashfs -comp xz -Xbcj x86 -noappend -quiet + +# Build GRUB EFI image +echo " [container] Building GRUB EFI image..." +grub-mkimage -O x86_64-efi -o /output/BOOTX64.EFI -p /boot/grub \ + part_gpt part_msdos fat iso9660 udf normal boot linux search \ + search_fs_uuid search_fs_file search_label configfile echo cat \ + ls test true loopback gfxterm gfxmenu font png + +# Create EFI FAT image (4MB) +dd if=/dev/zero of=/output/efi.img bs=1M count=4 2>/dev/null +mkfs.vfat /output/efi.img >/dev/null +mmd -i /output/efi.img ::/EFI ::/EFI/BOOT +mcopy -i /output/efi.img /output/BOOTX64.EFI ::/EFI/BOOT/BOOTX64.EFI + +# Copy ISOLINUX files for legacy BIOS boot +cp /usr/lib/ISOLINUX/isolinux.bin /output/isolinux.bin +cp /usr/lib/syslinux/modules/bios/ldlinux.c32 /output/ldlinux.c32 +cp /usr/lib/syslinux/modules/bios/menu.c32 /output/menu.c32 2>/dev/null || true +cp /usr/lib/syslinux/modules/bios/libutil.c32 /output/libutil.c32 2>/dev/null || true +cp /usr/lib/ISOLINUX/isohdpfx.bin /output/isohdpfx.bin + +# Generate GRUB fonts for theme +echo " [container] Generating GRUB fonts..." +apt-get install -y -qq fonts-dejavu-core grub-common >/dev/null 2>&1 +mkdir -p /output/grub-fonts +grub-mkfont -s 12 -o /output/grub-fonts/dejavu_12.pf2 /usr/share/fonts/truetype/dejavu/DejaVuSans.ttf +grub-mkfont -s 14 -o /output/grub-fonts/dejavu_14.pf2 /usr/share/fonts/truetype/dejavu/DejaVuSans.ttf +grub-mkfont -s 16 -o /output/grub-fonts/dejavu_16.pf2 /usr/share/fonts/truetype/dejavu/DejaVuSans.ttf +grub-mkfont -s 24 -o /output/grub-fonts/dejavu_24.pf2 /usr/share/fonts/truetype/dejavu/DejaVuSansMono-Bold.ttf + +echo " [container] Done!" +' + +# Verify artifacts +for artifact in vmlinuz initrd.img filesystem.squashfs BOOTX64.EFI efi.img isolinux.bin isohdpfx.bin; do + if [ ! -f "$WORK_DIR/$artifact" ]; then + echo " FATAL: Missing build artifact: $artifact" exit 1 fi - - echo " ✅ Download complete ($(($FINAL_SIZE / 1024 / 1024))MB)" +done + +# Place artifacts into ISO directory structure +cp "$WORK_DIR/vmlinuz" "$INSTALLER_ISO/live/vmlinuz" +cp "$WORK_DIR/initrd.img" "$INSTALLER_ISO/live/initrd.img" +cp "$WORK_DIR/filesystem.squashfs" "$INSTALLER_ISO/live/filesystem.squashfs" +cp "$WORK_DIR/BOOTX64.EFI" "$INSTALLER_ISO/EFI/BOOT/BOOTX64.EFI" +cp "$WORK_DIR/efi.img" "$INSTALLER_ISO/boot/grub/efi.img" +cp "$WORK_DIR/isolinux.bin" "$INSTALLER_ISO/isolinux/isolinux.bin" +cp "$WORK_DIR/ldlinux.c32" "$INSTALLER_ISO/isolinux/ldlinux.c32" +cp "$WORK_DIR/menu.c32" "$INSTALLER_ISO/isolinux/menu.c32" 2>/dev/null || true +cp "$WORK_DIR/libutil.c32" "$INSTALLER_ISO/isolinux/libutil.c32" 2>/dev/null || true + +# Install GRUB theme +THEME_SRC="$SCRIPT_DIR/branding/grub-theme" +THEME_DST="$INSTALLER_ISO/boot/grub/themes/archipelago" +mkdir -p "$THEME_DST" +if [ -f "$THEME_SRC/theme.txt" ]; then + cp "$THEME_SRC/theme.txt" "$THEME_DST/" + echo " Installed GRUB theme from branding/grub-theme/" +fi +# Install generated fonts +if [ -d "$WORK_DIR/grub-fonts" ]; then + cp "$WORK_DIR/grub-fonts/"*.pf2 "$THEME_DST/" + # Also copy unicode font for GRUB to load + cp "$WORK_DIR/grub-fonts/dejavu_16.pf2" "$INSTALLER_ISO/boot/grub/font.pf2" fi -echo " Extracting installer base..." -INSTALLER_ISO="$WORK_DIR/installer-iso" -rm -rf "$INSTALLER_ISO" -mkdir -p "$INSTALLER_ISO" -cd "$INSTALLER_ISO" -# 7z returns exit code 2 for warnings (symlinks in ISO) — check for key files instead -7z x -y "$BASE_ISO" >/dev/null 2>&1 || 7za x -y "$BASE_ISO" >/dev/null 2>&1 || bsdtar -xf "$BASE_ISO" 2>/dev/null || true -if [ ! -d "$INSTALLER_ISO/live" ] || [ ! -f "$INSTALLER_ISO/live/vmlinuz" ]; then - echo " ❌ Failed to extract ISO. Install p7zip-full: sudo apt install p7zip-full" - exit 1 -fi +echo " Installer squashfs: $(du -h "$INSTALLER_ISO/live/filesystem.squashfs" | cut -f1)" +echo " Kernel: $(du -h "$INSTALLER_ISO/live/vmlinuz" | cut -f1)" +echo " Initrd: $(du -h "$INSTALLER_ISO/live/initrd.img" | cut -f1)" +echo " Step 2 complete (custom minimal base, no Debian Live)" # ============================================================================= # STEP 3: Add Archipelago components @@ -1152,7 +1324,7 @@ echo "" # Find boot media BOOT_MEDIA="" -for dev in /run/live/medium /lib/live/mount/medium /cdrom; do +for dev in /run/archiso /cdrom /media/cdrom /run/live/medium /lib/live/mount/medium /mnt/iso; do if [ -d "$dev/archipelago" ]; then BOOT_MEDIA="$dev" break @@ -1694,8 +1866,9 @@ while true; do --disable-save-password-bubble \ --disable-suggestions-service \ --password-store=basic \ - --disable-features=TranslateUI,PasswordManagerOnboarding \ + --disable-features=TranslateUI,PasswordManagerOnboarding,AutofillServerCommunication,PasswordManagerEnabled \ --disable-component-update \ + --credentials_enable_service=false \ --user-data-dir=/home/archipelago/.config/chromium-kiosk sleep 3 done @@ -1869,10 +2042,8 @@ elif [ -n "${GRUB_BIOS_TARGET}" ]; then echo " Skipping Legacy BIOS bootloader (machine supports UEFI)" fi -# Remove any live-boot artifacts that could cause squashfs errors on boot -# These can sneak in from Docker base image or package dependencies -echo " Cleaning live-boot artifacts..." -chroot /mnt/target apt-get remove -y --purge live-boot live-boot-initramfs-tools live-config 2>/dev/null || true +# Clean any stale live-boot artifacts (should not exist in the custom rootfs, +# but clean up defensively in case Docker base image pulled them in) rm -f /mnt/target/etc/initramfs-tools/conf.d/live-boot* 2>/dev/null || true rm -f /mnt/target/usr/share/initramfs-tools/scripts/live* 2>/dev/null || true rm -f /mnt/target/usr/share/initramfs-tools/hooks/live* 2>/dev/null || true @@ -1880,6 +2051,14 @@ rm -f /mnt/target/usr/share/initramfs-tools/hooks/live* 2>/dev/null || true # Suppress os-prober warning in GRUB echo "GRUB_DISABLE_OS_PROBER=true" >> /mnt/target/etc/default/grub +# Install Archipelago GRUB theme on target system +if [ -d "$BOOT_MEDIA/boot/grub/themes/archipelago" ]; then + mkdir -p /mnt/target/boot/grub/themes/archipelago + cp "$BOOT_MEDIA/boot/grub/themes/archipelago/"* /mnt/target/boot/grub/themes/archipelago/ + echo 'GRUB_THEME="/boot/grub/themes/archipelago/theme.txt"' >> /mnt/target/etc/default/grub + echo " Installed Archipelago GRUB theme on target" +fi + # Regenerate initramfs — the one from Docker export is corrupt/incomplete # (Docker builds have limited /proc, /sys, /dev so initramfs generation fails silently) echo " Regenerating initramfs..." @@ -1888,8 +2067,12 @@ chroot /mnt/target update-initramfs -u -k all chroot /mnt/target update-grub # Install udev rule for mesh radio stable naming (/dev/mesh-radio) -if [ -f /cdrom/99-mesh-radio.rules ]; then - cp /cdrom/99-mesh-radio.rules /mnt/target/etc/udev/rules.d/99-mesh-radio.rules +MESH_RULES="" +for p in "$BOOT_MEDIA/99-mesh-radio.rules" /cdrom/99-mesh-radio.rules "$BOOT_MEDIA/archipelago/configs/99-mesh-radio.rules"; do + [ -f "$p" ] && MESH_RULES="$p" && break +done +if [ -n "$MESH_RULES" ]; then + cp "$MESH_RULES" /mnt/target/etc/udev/rules.d/99-mesh-radio.rules echo " Installed mesh radio udev rule" fi @@ -2207,12 +2390,13 @@ read -p "Press Enter to reboot (make sure USB is removed)..." exec 2>/dev/null # Try to eject the USB boot media to prevent booting back into installer -BOOT_DEV=$(findmnt -n -o SOURCE /run/live/medium 2>/dev/null || findmnt -n -o SOURCE /lib/live/mount/medium 2>/dev/null || echo "") +BOOT_DEV=$(findmnt -n -o SOURCE /run/archiso 2>/dev/null || findmnt -n -o SOURCE /cdrom 2>/dev/null || findmnt -n -o SOURCE /run/live/medium 2>/dev/null || echo "") if [ -n "$BOOT_DEV" ]; then BOOT_DISK=$(lsblk -no PKNAME "$BOOT_DEV" 2>/dev/null | head -1) if [ -n "$BOOT_DISK" ]; then + umount -l /run/archiso 2>/dev/null || true + umount -l /cdrom 2>/dev/null || true umount -l /run/live/medium 2>/dev/null || true - umount -l /lib/live/mount/medium 2>/dev/null || true eject "/dev/$BOOT_DISK" 2>/dev/null || true fi fi @@ -2230,227 +2414,100 @@ fi chmod +x "$ARCH_DIR/auto-install.sh" # ============================================================================= -# STEP 5: Configure auto-start installer on boot +# STEP 5: Configure boot loader and ISO structure # ============================================================================= echo "" -echo "📦 Step 5: Configuring auto-start..." +echo "Step 5: Configuring boot loaders..." -# Create a squashfs overlay module that Debian Live will automatically load -# This is the proper way to add files to the live filesystem -OVERLAY_DIR="$WORK_DIR/overlay-root" -rm -rf "$OVERLAY_DIR" -mkdir -p "$OVERLAY_DIR/etc/profile.d" -mkdir -p "$OVERLAY_DIR/etc/skel" -mkdir -p "$OVERLAY_DIR/usr/local/bin" -mkdir -p "$OVERLAY_DIR/usr/sbin" -mkdir -p "$OVERLAY_DIR/sbin" +# The installer squashfs (from Step 2) already contains: +# - systemd service for auto-starting the installer +# - auto-login on tty1 +# - custom initramfs hook for mounting boot media at /run/archiso +# - all partitioning tools (parted, mkfs.*, cryptsetup) +# +# Step 5 just needs to create the GRUB and ISOLINUX boot configs. -# Download and extract required tools AND their libraries for offline use -echo " Downloading partitioning tools for offline use..." -TOOLS_DIR="$WORK_DIR/tools-extract" -rm -rf "$TOOLS_DIR" -mkdir -p "$TOOLS_DIR" +# Create GRUB configuration +echo " Writing GRUB config..." +cat > "$INSTALLER_ISO/boot/grub/grub.cfg" <<'GRUBCFG' +set timeout=5 +set default=0 -$CONTAINER_CMD run --rm --platform $CONTAINER_PLATFORM \ - -v "$TOOLS_DIR:/output" \ - debian:bookworm \ - bash -c ' - apt-get update -qq - apt-get install -y -qq parted dosfstools e2fsprogs - - # Copy binaries - cp /usr/sbin/parted /output/ - cp /usr/sbin/mkfs.vfat /output/ 2>/dev/null || cp /sbin/mkfs.vfat /output/ 2>/dev/null || true - cp /usr/sbin/mkfs.ext4 /output/ 2>/dev/null || cp /sbin/mkfs.ext4 /output/ 2>/dev/null || true - cp /usr/sbin/mke2fs /output/ 2>/dev/null || cp /sbin/mke2fs /output/ 2>/dev/null || true - cp /sbin/mkfs.fat /output/ 2>/dev/null || true - - # Copy required shared libraries for parted - mkdir -p /output/lib - cp /lib/${LIB_DIR}/libparted.so* /output/lib/ 2>/dev/null || true - cp /usr/lib/${LIB_DIR}/libparted.so* /output/lib/ 2>/dev/null || true - cp /lib/${LIB_DIR}/libreadline.so* /output/lib/ 2>/dev/null || true - cp /usr/lib/${LIB_DIR}/libreadline.so* /output/lib/ 2>/dev/null || true - cp /lib/${LIB_DIR}/libdevmapper.so* /output/lib/ 2>/dev/null || true - cp /usr/lib/${LIB_DIR}/libdevmapper.so* /output/lib/ 2>/dev/null || true - - # List what parted actually needs - ldd /usr/sbin/parted 2>/dev/null | grep "=>" | awk "{print \$3}" | while read lib; do - [ -f "$lib" ] && cp "$lib" /output/lib/ 2>/dev/null || true - done - - echo "Libraries bundled:" - ls -la /output/lib/ - ' - -# Copy tools to overlay -cp "$TOOLS_DIR/parted" "$OVERLAY_DIR/usr/sbin/" 2>/dev/null || true -cp "$TOOLS_DIR/mkfs.vfat" "$OVERLAY_DIR/usr/sbin/" 2>/dev/null || true -cp "$TOOLS_DIR/mkfs.fat" "$OVERLAY_DIR/sbin/" 2>/dev/null || true -cp "$TOOLS_DIR/mkfs.ext4" "$OVERLAY_DIR/usr/sbin/" 2>/dev/null || true -cp "$TOOLS_DIR/mke2fs" "$OVERLAY_DIR/usr/sbin/" 2>/dev/null || true - -# Copy shared libraries -mkdir -p "$OVERLAY_DIR/usr/lib/${LIB_DIR}" -cp "$TOOLS_DIR/lib/"*.so* "$OVERLAY_DIR/usr/lib/${LIB_DIR}/" 2>/dev/null || true - -chmod +x "$OVERLAY_DIR/usr/sbin/"* 2>/dev/null || true -chmod +x "$OVERLAY_DIR/sbin/"* 2>/dev/null || true -echo " ✅ Partitioning tools and libraries bundled" - -# Create the auto-start profile script -cat > "$OVERLAY_DIR/etc/profile.d/z99-archipelago-installer.sh" <<'AUTOSTART' -#!/bin/bash -# Auto-start Archipelago installer on login - -# Only run once -if [ -n "$INSTALLER_STARTED" ]; then - return 0 2>/dev/null || exit 0 +# Load font for graphical menu +if loadfont /boot/grub/font.pf2; then + set gfxmode=auto + insmod gfxterm + insmod png + terminal_output gfxterm fi -export INSTALLER_STARTED=1 -# Give system a moment to settle -sleep 1 - -clear -echo "" -echo " ╔═══════════════════════════════════════════════════════════════════╗" -echo " ║ ║" -echo " ║ 🏝️ ARCHIPELAGO BITCOIN NODE OS - INSTALLER ║" -echo " ║ ║" -echo " ╚═══════════════════════════════════════════════════════════════════╝" -echo "" - -# Find boot media -BOOT_MEDIA="" -for dev in /run/live/medium /lib/live/mount/medium /cdrom /media/cdrom; do - if [ -f "$dev/archipelago/auto-install.sh" ]; then - BOOT_MEDIA="$dev" - break - fi -done - -if [ -n "$BOOT_MEDIA" ]; then - echo " Found installer at: $BOOT_MEDIA" - echo "" - echo " Press Enter to start installation, or Ctrl+C for shell..." - read - sudo bash "$BOOT_MEDIA/archipelago/auto-install.sh" +# Archipelago GRUB theme +if [ -f /boot/grub/themes/archipelago/theme.txt ]; then + # Load theme fonts + loadfont /boot/grub/themes/archipelago/dejavu_12.pf2 + loadfont /boot/grub/themes/archipelago/dejavu_14.pf2 + loadfont /boot/grub/themes/archipelago/dejavu_16.pf2 + loadfont /boot/grub/themes/archipelago/dejavu_24.pf2 + set theme=/boot/grub/themes/archipelago/theme.txt else - echo " ⚠️ Installer not found on boot media." - echo "" - echo " Checked: /run/live/medium, /lib/live/mount/medium, /cdrom" - echo "" - echo " You can try manually:" - echo " sudo bash /path/to/archipelago/auto-install.sh" - echo "" -fi -AUTOSTART -chmod +x "$OVERLAY_DIR/etc/profile.d/z99-archipelago-installer.sh" - -# Also create .bashrc that sources the profile script (belt and suspenders) -cat > "$OVERLAY_DIR/etc/skel/.bashrc" <<'BASHRC' -# ~/.bashrc: executed by bash(1) for non-login shells. - -# If not running interactively, don't do anything -case $- in - *i*) ;; - *) return;; -esac - -# Source profile.d scripts -if [ -d /etc/profile.d ]; then - for i in /etc/profile.d/*.sh; do - if [ -r "$i" ]; then - . "$i" - fi - done -fi -BASHRC - -# Create a wrapper script that can be called directly -cat > "$OVERLAY_DIR/usr/local/bin/archipelago-install" <<'WRAPPER' -#!/bin/bash -# Archipelago installer wrapper - -BOOT_MEDIA="" -for dev in /run/live/medium /lib/live/mount/medium /cdrom /media/cdrom; do - if [ -f "$dev/archipelago/auto-install.sh" ]; then - BOOT_MEDIA="$dev" - break - fi -done - -if [ -n "$BOOT_MEDIA" ]; then - exec sudo bash "$BOOT_MEDIA/archipelago/auto-install.sh" -else - echo "Installer not found. Searched:" - echo " /run/live/medium, /lib/live/mount/medium, /cdrom, /media/cdrom" - exit 1 -fi -WRAPPER -chmod +x "$OVERLAY_DIR/usr/local/bin/archipelago-install" - -# Create the squashfs module -# Debian Live automatically loads all .squashfs files from live/ directory -echo " Creating overlay squashfs module..." -LIVE_DIR="$INSTALLER_ISO/live" -mkdir -p "$LIVE_DIR" - -# Check if mksquashfs is available (may need to use Docker on macOS) -if command -v mksquashfs >/dev/null 2>&1; then - mksquashfs "$OVERLAY_DIR" "$LIVE_DIR/99-archipelago.squashfs" -comp xz -noappend -else - # Use $CONTAINER_CMD to create squashfs on macOS - echo " Using $CONTAINER_CMD to create squashfs..." - $CONTAINER_CMD run --rm --platform $CONTAINER_PLATFORM \ - -v "$OVERLAY_DIR:/overlay:ro" \ - -v "$LIVE_DIR:/output" \ - debian:bookworm \ - bash -c "apt-get update && apt-get install -y squashfs-tools && mksquashfs /overlay /output/99-archipelago.squashfs -comp xz -noappend" + set menu_color_normal=light-gray/black + set menu_color_highlight=white/dark-gray fi -echo " ✅ Created overlay module: 99-archipelago.squashfs" +menuentry "Install Archipelago" --hotkey=i { + linux /live/vmlinuz quiet + initrd /live/initrd.img +} -# Modify GRUB config - update branding and ensure components are loaded -if [ -f "$INSTALLER_ISO/boot/grub/grub.cfg" ]; then - echo " Configuring GRUB..." - sed -i.bak \ - -e 's/Debian GNU\/Linux/Archipelago Installer/g' \ - -e 's/Live system/Install Archipelago/g' \ - "$INSTALLER_ISO/boot/grub/grub.cfg" - - # Ensure 'components' parameter is present (loads additional squashfs modules) - # Also add 'username=user' to ensure consistent username - if ! grep -q "components" "$INSTALLER_ISO/boot/grub/grub.cfg"; then - sed -i 's/boot=live/boot=live components/' "$INSTALLER_ISO/boot/grub/grub.cfg" - fi -fi +menuentry "Install Archipelago (verbose)" --hotkey=v { + linux /live/vmlinuz + initrd /live/initrd.img +} -if [ -f "$INSTALLER_ISO/isolinux/live.cfg" ]; then - echo " Configuring ISOLINUX..." - sed -i.bak \ - -e 's/Debian GNU\/Linux/Archipelago Installer/g' \ - -e 's/Live system/Install Archipelago/g' \ - "$INSTALLER_ISO/isolinux/live.cfg" - - # Add components parameter - if ! grep -q "components" "$INSTALLER_ISO/isolinux/live.cfg"; then - sed -i 's/boot=live/boot=live components/' "$INSTALLER_ISO/isolinux/live.cfg" - fi -fi +menuentry "Boot from local disk" --hotkey=b { + set root=(hd0) + chainloader +1 +} +GRUBCFG -if [ -f "$INSTALLER_ISO/isolinux/menu.cfg" ]; then - sed -i.bak \ - -e 's/Debian GNU\/Linux/Archipelago Installer/g' \ - "$INSTALLER_ISO/isolinux/menu.cfg" -fi +# Create ISOLINUX configuration (legacy BIOS boot) +echo " Writing ISOLINUX config..." +cat > "$INSTALLER_ISO/isolinux/isolinux.cfg" <<'ISOCFG' +UI menu.c32 +PROMPT 0 +TIMEOUT 50 + +MENU TITLE ARCHIPELAGO INSTALLER +MENU COLOR border 30;44 #40ffffff #00000000 std +MENU COLOR title 1;36;44 #ff00b7ff #00000000 std +MENU COLOR sel 7;37;40 #ffffffff #ff333333 std +MENU COLOR unsel 37;44 #ffaaaaaa #00000000 std + +DEFAULT install + +LABEL install + MENU LABEL Install Archipelago + KERNEL /live/vmlinuz + APPEND initrd=/live/initrd.img quiet + MENU DEFAULT + +LABEL install-verbose + MENU LABEL Install Archipelago (verbose) + KERNEL /live/vmlinuz + APPEND initrd=/live/initrd.img + +LABEL local + MENU LABEL Boot from local disk + LOCALBOOT 0x80 +ISOCFG + +echo " Step 5 complete (GRUB + ISOLINUX configured)" # ============================================================================= # STEP 6: Create final ISO # ============================================================================= echo "" -echo "📦 Step 6: Creating bootable ISO..." +echo "Step 6: Creating bootable ISO..." if [ "$UNBUNDLED" = "1" ]; then OUTPUT_ISO="$OUTPUT_DIR/archipelago-installer-unbundled-${ARCH}.iso" @@ -2458,60 +2515,27 @@ else OUTPUT_ISO="$OUTPUT_DIR/archipelago-installer-${ARCH}.iso" fi -# Extract MBR from original Debian Live ISO (most reliable for hybrid boot) -# This preserves the exact MBR that makes the ISO work as a USB drive in Balena Etcher -echo " Extracting hybrid MBR from original Debian Live ISO..." +# Use ISOLINUX isohdpfx.bin for hybrid USB boot (built in Step 2) ISOHDPFX="$WORK_DIR/isohdpfx.bin" -dd if="$BASE_ISO" bs=1 count=432 of="$ISOHDPFX" 2>/dev/null - -# Verify we got a valid MBR (should be 432 bytes) -ISOHDPFX_SIZE=$(stat -c%s "$ISOHDPFX" 2>/dev/null || stat -f%z "$ISOHDPFX" 2>/dev/null || echo 0) -if [ "$ISOHDPFX_SIZE" -ne 432 ]; then - echo " ⚠️ MBR extraction unexpected size ($ISOHDPFX_SIZE), trying syslinux paths..." +if [ ! -f "$ISOHDPFX" ]; then + # Fallback to system-installed copy for path in \ "/usr/lib/ISOLINUX/isohdpfx.bin" \ "/usr/share/syslinux/isohdpfx.bin" \ "/usr/local/share/syslinux/isohdpfx.bin"; do if [ -f "$path" ]; then ISOHDPFX="$path" - echo " Using $path" + echo " Using system isohdpfx.bin: $path" break fi done fi -# Find the EFI boot image — 7z may extract it to different locations -EFI_IMG="" -for efi_path in \ - "$INSTALLER_ISO/boot/grub/efi.img" \ - "$INSTALLER_ISO/EFI/boot/efi.img" \ - "$INSTALLER_ISO/efi.img"; do - if [ -f "$efi_path" ]; then - EFI_IMG="$efi_path" - break - fi -done +# EFI boot image was built in Step 2 +EFI_IMG="$INSTALLER_ISO/boot/grub/efi.img" -# If no standalone efi.img, check for [BOOT] directory from 7z extraction -if [ -z "$EFI_IMG" ] && [ -d "$INSTALLER_ISO/[BOOT]" ]; then - # 7z extracts El Torito boot images into [BOOT]/ — the EFI image is usually entry 2 - for entry in "$INSTALLER_ISO/[BOOT]/"*; do - # EFI images are typically > 1MB FAT filesystems - if [ -f "$entry" ]; then - entry_size=$(stat -c%s "$entry" 2>/dev/null || stat -f%z "$entry" 2>/dev/null || echo 0) - if [ "$entry_size" -gt 1048576 ]; then - mkdir -p "$INSTALLER_ISO/boot/grub" - cp "$entry" "$INSTALLER_ISO/boot/grub/efi.img" - EFI_IMG="$INSTALLER_ISO/boot/grub/efi.img" - echo " Recovered EFI image from [BOOT] directory" - break - fi - fi - done -fi - -if [ -z "$EFI_IMG" ]; then - echo " ⚠️ No EFI boot image found — ISO will only support Legacy BIOS boot" +if [ ! -f "$EFI_IMG" ]; then + echo " WARNING: No EFI boot image — ISO will only support Legacy BIOS boot" xorriso -as mkisofs -o "$OUTPUT_ISO" \ -volid "ARCHIPELAGO" \ -iso-level 3 \ @@ -2523,8 +2547,6 @@ if [ -z "$EFI_IMG" ]; then -partition_offset 16 \ "$INSTALLER_ISO" else - # Make EFI path relative to INSTALLER_ISO for xorriso - EFI_REL="${EFI_IMG#$INSTALLER_ISO/}" xorriso -as mkisofs -o "$OUTPUT_ISO" \ -volid "ARCHIPELAGO" \ -iso-level 3 \ @@ -2534,7 +2556,7 @@ else -b isolinux/isolinux.bin \ -no-emul-boot -boot-load-size 4 -boot-info-table \ -eltorito-alt-boot \ - -e "$EFI_REL" \ + -e boot/grub/efi.img \ -no-emul-boot \ -isohybrid-gpt-basdat \ -partition_offset 16 \ @@ -2543,43 +2565,19 @@ fi echo "" if [ "$UNBUNDLED" = "1" ]; then - echo "╔════════════════════════════════════════════════════════════════╗" - echo "║ ✅ UNBUNDLED AUTO-INSTALLER ISO CREATED! ║" - echo "╚════════════════════════════════════════════════════════════════╝" + echo "UNBUNDLED AUTO-INSTALLER ISO CREATED" echo "" - echo "📀 Output: $OUTPUT_ISO" - echo " Size: $(du -h "$OUTPUT_ISO" | cut -f1)" - echo "" - echo "🔥 Lightweight installer — apps downloaded on-demand!" - echo "" - echo "Features:" - echo " • Pre-built system (no internet needed during install)" - echo " • Auto-detects internal disk" - echo " • One-button installation" - echo " • Boots directly to Archipelago after install" - echo " • NO pre-bundled apps (smaller ISO)" - echo " • Install any app from the Marketplace (internet required)" + echo " Output: $OUTPUT_ISO" + echo " Size: $(du -h "$OUTPUT_ISO" | cut -f1)" echo "" + echo " Lightweight installer -- apps downloaded on-demand from Marketplace" else - echo "╔════════════════════════════════════════════════════════════════╗" - echo "║ ✅ AUTO-INSTALLER ISO CREATED! ║" - echo "╚════════════════════════════════════════════════════════════════╝" + echo "AUTO-INSTALLER ISO CREATED" echo "" - echo "📀 Output: $OUTPUT_ISO" - echo " Size: $(du -h "$OUTPUT_ISO" | cut -f1)" - echo "" - echo "🔥 This is a StartOS-like automatic installer!" - echo "" - echo "Features:" - echo " • Pre-built system (no internet needed during install)" - echo " • Auto-detects internal disk" - echo " • One-button installation" - echo " • Boots directly to Archipelago after install" - echo " • Pre-bundled container apps:" - echo " - Bitcoin Knots v29" - echo " - LND v0.18.4" - echo " - Home Assistant" + echo " Output: $OUTPUT_ISO" + echo " Size: $(du -h "$OUTPUT_ISO" | cut -f1)" echo "" + echo " Full installer with pre-bundled container apps" fi echo "To create USB:" echo " 1. Flash with: sudo dd if=$OUTPUT_ISO of=/dev/rdiskX bs=4m"