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>
This commit is contained in:
Dorian 2026-03-27 18:19:49 +00:00
parent 018f3c84d3
commit 4326f019c1
4 changed files with 802 additions and 381 deletions

View File

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

View File

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

View File

@ -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"
}

View File

@ -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 <<GETTY
[Service]
ExecStart=
ExecStart=-/sbin/agetty --autologin root --noclear %I \$TERM
GETTY
# Create the installer auto-start systemd service
cat > /installer/etc/systemd/system/archipelago-installer.service <<SVCFILE
[Unit]
Description=Archipelago Auto-Installer
After=multi-user.target systemd-logind.service
Wants=multi-user.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/archipelago-start-installer
StandardInput=tty
StandardOutput=tty
StandardError=tty
TTYPath=/dev/tty1
TTYReset=yes
TTYVHangup=yes
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
SVCFILE
chroot /installer systemctl enable archipelago-installer.service
# Create the installer start wrapper
cat > /installer/usr/local/bin/archipelago-start-installer <<WRAPPER
#!/bin/bash
sleep 2
clear
echo ""
echo " ARCHIPELAGO BITCOIN NODE OS"
echo " Automatic Installer"
echo ""
BOOT_MEDIA=""
for dev in /run/archiso /cdrom /media/cdrom /mnt/iso; 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
exec bash "\$BOOT_MEDIA/archipelago/auto-install.sh"
else
echo " Installer not found on boot media."
echo " Checked: /run/archiso, /cdrom, /media/cdrom, /mnt/iso"
echo ""
echo " Dropping to shell for manual recovery..."
exec /bin/bash
fi
WRAPPER
chmod +x /installer/usr/local/bin/archipelago-start-installer
if [ ! -f "$BASE_ISO" ]; then
echo " Downloading Debian Live base (352MB)..."
echo " (This may take 5-10 minutes depending on network speed)"
# Use wget without -O so --continue actually works
# Download with the ugly SourceForge filename, then rename
# Use Debian 12 (Bookworm) to match the rootfs — NOT Debian 13 (Trixie)
ISO_URL="https://cdimage.debian.org/cdimage/archive/12.10.0-live/${DEB_ARCH}/iso-hybrid/debian-live-12.10.0-${DEB_ARCH}-standard.iso"
if command -v wget >/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 <<HOOK
#!/bin/sh
set -e
PREREQ=""
prereqs() { echo "\$PREREQ"; }
case "\$1" in prereqs) prereqs; exit 0;; esac
. /usr/share/initramfs-tools/hook-functions
# Ensure mount helpers and filesystem tools are in initramfs
copy_exec /bin/mount
copy_exec /bin/umount
copy_exec /bin/findfs 2>/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 <<INITSCRIPT
#!/bin/sh
PREREQ=""
prereqs() { echo "\$PREREQ"; }
case "\$1" in prereqs) prereqs; exit 0;; esac
. /scripts/functions
# Try to find and mount the Archipelago boot media
mkdir -p /run/archiso
log_begin_msg "Searching for Archipelago boot media..."
# Try CD-ROM first, then USB partitions
for dev in /dev/sr0 /dev/sd??* /dev/nvme*p*; do
[ -b "\$dev" ] 2>/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"