#!/bin/bash # # Build Archipelago Auto-Installer ISO (StartOS-like) # # This creates an ISO that automatically installs to the internal disk # with minimal user interaction - similar to StartOS experience. # # Features: # - Pre-built root filesystem (no network needed during install) # - Auto-detects internal disk (skips USB boot drive) # - Automatic installation with progress display # - Boots directly to web UI after install # # Usage: ./build-auto-installer-iso.sh # set -e SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" WORK_DIR="$SCRIPT_DIR/build/auto-installer" OUTPUT_DIR="$SCRIPT_DIR/results" ROOTFS_DIR="$WORK_DIR/rootfs" INSTALLER_DIR="$WORK_DIR/installer" echo "╔════════════════════════════════════════════════════════════════╗" echo "║ Building Archipelago Auto-Installer ISO (StartOS-like) ║" echo "╚════════════════════════════════════════════════════════════════╝" echo "" # Check for required tools check_tools() { local missing="" for tool in docker xorriso; do if ! command -v $tool >/dev/null 2>&1; then missing="$missing $tool" fi done if [ -n "$missing" ]; then echo "❌ Missing required tools:$missing" echo " Install with: brew install$missing" exit 1 fi } check_tools mkdir -p "$WORK_DIR" mkdir -p "$OUTPUT_DIR" # ============================================================================= # STEP 1: Build complete root filesystem using Docker # ============================================================================= echo "📦 Step 1: Building root filesystem..." ROOTFS_TAR="$WORK_DIR/archipelago-rootfs.tar" if [ ! -f "$ROOTFS_TAR" ] || [ "$1" == "--rebuild" ]; then echo " Using Docker to create Debian root filesystem..." # Create a Dockerfile for building the rootfs cat > "$WORK_DIR/Dockerfile.rootfs" <<'DOCKERFILE' FROM debian:bookworm ENV DEBIAN_FRONTEND=noninteractive # Install all packages we need including nginx and podman RUN apt-get update && apt-get install -y \ linux-image-amd64 \ grub-efi-amd64 \ grub-efi-amd64-signed \ shim-signed \ systemd \ systemd-sysv \ dbus \ sudo \ network-manager \ openssh-server \ nginx \ podman \ curl \ wget \ htop \ vim-tiny \ ca-certificates \ locales \ console-setup \ keyboard-configuration \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* # Configure locale RUN echo "en_US.UTF-8 UTF-8" > /etc/locale.gen && locale-gen # Create archipelago user RUN useradd -m -s /bin/bash -G sudo archipelago && \ echo "archipelago:archipelago" | chpasswd && \ echo "archipelago ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/archipelago # Set hostname RUN echo "archipelago" > /etc/hostname # Configure SSH RUN mkdir -p /etc/ssh && \ sed -i 's/#PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config || true && \ sed -i 's/#PasswordAuthentication.*/PasswordAuthentication yes/' /etc/ssh/sshd_config || true # Configure nginx for Archipelago RUN rm -f /etc/nginx/sites-enabled/default COPY nginx-archipelago.conf /etc/nginx/sites-available/archipelago RUN ln -sf /etc/nginx/sites-available/archipelago /etc/nginx/sites-enabled/archipelago # Create archipelago systemd service COPY archipelago.service /etc/systemd/system/archipelago.service # Enable services RUN systemctl enable NetworkManager || true && \ systemctl enable ssh || true && \ systemctl enable nginx || true && \ systemctl enable archipelago || true # Create directories RUN mkdir -p /var/lib/archipelago/{data,config,containers} && \ mkdir -p /etc/archipelago && \ mkdir -p /opt/archipelago/{bin,scripts,web-ui} && \ chown -R archipelago:archipelago /var/lib/archipelago /opt/archipelago # Clean up RUN apt-get clean && \ rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* DOCKERFILE # Create nginx config for the Docker build cat > "$WORK_DIR/nginx-archipelago.conf" <<'NGINXCONF' server { listen 80; server_name _; root /opt/archipelago/web-ui; index index.html; # Serve static files (Vue.js SPA) location / { try_files $uri $uri/ /index.html; } # Proxy API requests to backend location /rpc/ { proxy_pass http://127.0.0.1:5678; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } # Proxy WebSocket location /ws { proxy_pass http://127.0.0.1:5678; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; } } NGINXCONF # Create systemd service for the Docker build cat > "$WORK_DIR/archipelago.service" <<'SYSTEMDSERVICE' [Unit] Description=Archipelago Backend After=network-online.target Wants=network-online.target [Service] Type=simple User=archipelago Environment="ARCHIPELAGO_BIND=0.0.0.0:5678" Environment="ARCHIPELAGO_DEV_MODE=true" ExecStart=/usr/local/bin/archipelago Restart=on-failure RestartSec=5 [Install] WantedBy=multi-user.target SYSTEMDSERVICE echo " Building Docker image (this may take a few minutes)..." docker build --platform linux/amd64 -t archipelago-rootfs -f "$WORK_DIR/Dockerfile.rootfs" "$WORK_DIR" echo " Exporting filesystem..." docker create --platform linux/amd64 --name archipelago-rootfs-tmp archipelago-rootfs docker export archipelago-rootfs-tmp > "$ROOTFS_TAR" docker rm archipelago-rootfs-tmp echo "✅ Root filesystem created: $(du -h "$ROOTFS_TAR" | cut -f1)" else echo "✅ Using cached root filesystem: $(du -h "$ROOTFS_TAR" | cut -f1)" fi # ============================================================================= # STEP 2: Create installer environment # ============================================================================= echo "" echo "📦 Step 2: Creating installer environment..." # Download Debian Live as our installer base BASE_ISO="$WORK_DIR/debian-live-installer.iso" if [ ! -f "$BASE_ISO" ] || [ $(stat -f%z "$BASE_ISO" 2>/dev/null || echo 0) -lt 100000000 ]; then echo " Downloading Debian Live base..." rm -f "$BASE_ISO" curl -L -o "$BASE_ISO" \ "https://sourceforge.net/projects/debian-live-respin-iso/files/standard/live-image-debian12.11-standard-20250522-amd64.hybrid.iso/download" fi echo " Extracting installer base..." INSTALLER_ISO="$WORK_DIR/installer-iso" rm -rf "$INSTALLER_ISO" mkdir -p "$INSTALLER_ISO" cd "$INSTALLER_ISO" 7z x -y "$BASE_ISO" >/dev/null 2>&1 || 7z x -y "$BASE_ISO" # ============================================================================= # STEP 3: Add Archipelago components # ============================================================================= echo "" echo "📦 Step 3: Adding Archipelago components..." ARCH_DIR="$INSTALLER_ISO/archipelago" mkdir -p "$ARCH_DIR" mkdir -p "$ARCH_DIR/bin" mkdir -p "$ARCH_DIR/scripts" # Copy the pre-built rootfs echo " Including root filesystem..." cp "$ROOTFS_TAR" "$ARCH_DIR/rootfs.tar" # Build and copy backend binary echo " Building backend binary for Linux x86_64..." BACKEND_DOCKERFILE="$WORK_DIR/Dockerfile.backend" cat > "$BACKEND_DOCKERFILE" <<'BACKENDFILE' FROM rust:1.93-bookworm as builder WORKDIR /build COPY core ./core RUN cd core && cargo build --release --bin archipelago BACKENDFILE if docker build --platform linux/amd64 -t archipelago-backend -f "$BACKEND_DOCKERFILE" "$SCRIPT_DIR/.." 2>&1 | tail -20; then echo " Extracting backend binary..." BACKEND_CONTAINER=$(docker create --platform linux/amd64 archipelago-backend) docker cp "$BACKEND_CONTAINER:/build/core/target/release/archipelago" "$ARCH_DIR/bin/" && \ echo " ✅ Backend binary included ($(du -h "$ARCH_DIR/bin/archipelago" | cut -f1))" docker rm "$BACKEND_CONTAINER" else echo " ⚠️ Backend build failed - using existing binary if available" if [ -f "$SCRIPT_DIR/../core/target/release/archipelago" ]; then cp "$SCRIPT_DIR/../core/target/release/archipelago" "$ARCH_DIR/bin/" echo " Using local backend binary (may not be compatible)" fi fi # Build and copy web UI echo " Building web UI..." cd "$SCRIPT_DIR/../neode-ui" if npm run build 2>&1 | tail -5; then if [ -d "$SCRIPT_DIR/../web/dist/neode-ui" ]; then echo " Including web UI from web/dist/neode-ui..." cp -r "$SCRIPT_DIR/../web/dist/neode-ui" "$ARCH_DIR/web-ui" echo " ✅ Web UI included ($(du -sh "$ARCH_DIR/web-ui" | cut -f1))" fi else echo " ⚠️ Web UI build failed" # Try to use existing build if [ -d "$SCRIPT_DIR/../web/dist/neode-ui" ]; then echo " Using existing web UI build..." cp -r "$SCRIPT_DIR/../web/dist/neode-ui" "$ARCH_DIR/web-ui" elif [ -d "$SCRIPT_DIR/../neode-ui/dist" ]; then echo " Using neode-ui/dist..." cp -r "$SCRIPT_DIR/../neode-ui/dist" "$ARCH_DIR/web-ui" else echo " ❌ No web UI available" fi fi cd "$SCRIPT_DIR" # Copy app manifests if [ -d "$SCRIPT_DIR/../apps" ]; then echo " Including app manifests..." cp -r "$SCRIPT_DIR/../apps" "$ARCH_DIR/" fi # ============================================================================= # STEP 3b: Bundle container images for offline installation # ============================================================================= echo "" echo "📦 Step 3b: Bundling container images for offline use..." IMAGES_DIR="$ARCH_DIR/container-images" mkdir -p "$IMAGES_DIR" # Define images to bundle (space-separated: image filename) CONTAINER_IMAGES=" bitcoinknots/bitcoin:29 bitcoin-knots.tar lightninglabs/lnd:v0.18.4-beta lnd.tar ghcr.io/home-assistant/home-assistant:stable homeassistant.tar " # Pull and save each image (force AMD64 for x86_64 target) echo "$CONTAINER_IMAGES" | while read -r image filename; do [ -z "$image" ] && continue tarpath="$IMAGES_DIR/$filename" if [ -f "$tarpath" ]; then echo " ✅ Using cached: $filename" else echo " Pulling $image (linux/amd64)..." if docker pull --platform linux/amd64 "$image"; then echo " Saving $filename..." docker save "$image" -o "$tarpath" echo " ✅ Saved: $(du -h "$tarpath" | cut -f1)" else echo " ⚠️ Failed to pull $image - skipping" fi fi done # Create first-boot service to load images into Podman echo " Creating first-boot image loader service..." cat > "$WORK_DIR/archipelago-load-images.service" <<'LOADSERVICE' [Unit] Description=Load Archipelago Container Images After=network.target podman.service ConditionPathExists=/opt/archipelago/container-images ConditionPathExists=!/var/lib/archipelago/.images-loaded [Service] Type=oneshot ExecStart=/opt/archipelago/scripts/load-container-images.sh ExecStartPost=/usr/bin/touch /var/lib/archipelago/.images-loaded RemainAfterExit=yes [Install] WantedBy=multi-user.target LOADSERVICE cat > "$WORK_DIR/load-container-images.sh" <<'LOADSCRIPT' #!/bin/bash # Load pre-bundled container images into Podman IMAGES_DIR="/opt/archipelago/container-images" LOG_FILE="/var/log/archipelago-images.log" echo "$(date): Starting container image load" >> "$LOG_FILE" if [ ! -d "$IMAGES_DIR" ]; then echo "$(date): No images directory found" >> "$LOG_FILE" exit 0 fi for tarfile in "$IMAGES_DIR"/*.tar; do if [ -f "$tarfile" ]; then echo "$(date): Loading $(basename "$tarfile")..." >> "$LOG_FILE" podman load -i "$tarfile" >> "$LOG_FILE" 2>&1 && \ echo "$(date): Successfully loaded $(basename "$tarfile")" >> "$LOG_FILE" || \ echo "$(date): Failed to load $(basename "$tarfile")" >> "$LOG_FILE" fi done echo "$(date): Container image load complete" >> "$LOG_FILE" echo "$(date): Available images:" >> "$LOG_FILE" podman images >> "$LOG_FILE" 2>&1 LOADSCRIPT chmod +x "$WORK_DIR/load-container-images.sh" # Copy scripts to ISO mkdir -p "$ARCH_DIR/scripts" cp "$WORK_DIR/load-container-images.sh" "$ARCH_DIR/scripts/" cp "$WORK_DIR/archipelago-load-images.service" "$ARCH_DIR/scripts/" echo " ✅ Container images bundled" # ============================================================================= # STEP 4: Create auto-installer script # ============================================================================= echo "" echo "📦 Step 4: Creating auto-installer..." cat > "$ARCH_DIR/auto-install.sh" <<'INSTALLER_SCRIPT' #!/bin/bash # # Archipelago Auto-Installer # Automatically installs to internal disk (StartOS-like experience) # set -e # Colors RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' clear echo "" echo -e "${BLUE}╔═══════════════════════════════════════════════════════════════════╗${NC}" echo -e "${BLUE}║ ║${NC}" echo -e "${BLUE}║ ${GREEN}🏝️ ARCHIPELAGO BITCOIN NODE OS${BLUE} ║${NC}" echo -e "${BLUE}║ ║${NC}" echo -e "${BLUE}║ ${NC}Automatic Installation${BLUE} ║${NC}" echo -e "${BLUE}║ ║${NC}" echo -e "${BLUE}╚═══════════════════════════════════════════════════════════════════╝${NC}" echo "" # Check required tools are present (should be bundled in ISO) echo -e "${YELLOW}🔧 Checking installer tools...${NC}" MISSING="" command -v parted >/dev/null 2>&1 || MISSING="parted $MISSING" command -v mkfs.vfat >/dev/null 2>&1 || MISSING="mkfs.vfat $MISSING" command -v mkfs.ext4 >/dev/null 2>&1 || MISSING="mkfs.ext4 $MISSING" if [ -n "$MISSING" ]; then echo " Missing tools: $MISSING" echo " Attempting to install (requires network)..." if apt-get update -qq >/dev/null 2>&1; then apt-get install -y -qq parted dosfstools e2fsprogs >/dev/null 2>&1 && echo " ✅ Tools installed" || { echo -e "${RED}❌ Failed to install required tools. No network?${NC}" echo " Required: parted, mkfs.vfat, mkfs.ext4" exit 1 } else echo -e "${RED}❌ No network available and tools not bundled.${NC}" exit 1 fi else echo " ✅ All tools present" fi echo "" # Find boot media BOOT_MEDIA="" for dev in /run/live/medium /lib/live/mount/medium /cdrom; do if [ -d "$dev/archipelago" ]; then BOOT_MEDIA="$dev" break fi done if [ -z "$BOOT_MEDIA" ]; then echo -e "${RED}❌ Boot media not found${NC}" exit 1 fi ROOTFS_TAR="$BOOT_MEDIA/archipelago/rootfs.tar" if [ ! -f "$ROOTFS_TAR" ]; then echo -e "${RED}❌ Root filesystem not found: $ROOTFS_TAR${NC}" exit 1 fi # Find the boot USB device to exclude it BOOT_DEV=$(findmnt -n -o SOURCE "$BOOT_MEDIA" 2>/dev/null | sed 's/[0-9]*$//' | sed 's/p[0-9]*$//') BOOT_DEV_NAME=$(basename "$BOOT_DEV" 2>/dev/null || echo "") echo -e "${YELLOW}📋 Detecting disks...${NC}" echo "" # Find internal disk (prefer NVMe, then SATA, skip USB) TARGET_DISK="" TARGET_SIZE="" # Check NVMe drives first for disk in /dev/nvme*n1; do if [ -b "$disk" ]; then disk_name=$(basename "$disk") if [ "$disk_name" != "$BOOT_DEV_NAME" ]; then size=$(lsblk -dn -o SIZE "$disk" 2>/dev/null) model=$(cat /sys/block/$disk_name/device/model 2>/dev/null || echo "NVMe SSD") echo " Found: $disk ($size) - $model" TARGET_DISK="$disk" TARGET_SIZE="$size" break fi fi done # If no NVMe, check SATA drives if [ -z "$TARGET_DISK" ]; then for disk in /dev/sd[a-z]; do if [ -b "$disk" ]; then disk_name=$(basename "$disk") if [ "$disk_name" != "$BOOT_DEV_NAME" ]; then # Skip USB drives (check removable flag) removable=$(cat /sys/block/$disk_name/removable 2>/dev/null || echo "0") if [ "$removable" = "0" ]; then size=$(lsblk -dn -o SIZE "$disk" 2>/dev/null) model=$(cat /sys/block/$disk_name/device/model 2>/dev/null || echo "SATA Drive") echo " Found: $disk ($size) - $model" TARGET_DISK="$disk" TARGET_SIZE="$size" break fi fi fi done fi if [ -z "$TARGET_DISK" ]; then echo "" echo -e "${RED}❌ No suitable internal disk found${NC}" echo " Please ensure an internal drive is connected." exit 1 fi echo "" echo -e "${GREEN}✅ Target disk: $TARGET_DISK ($TARGET_SIZE)${NC}" echo "" echo -e "${RED}⚠️ WARNING: ALL DATA ON $TARGET_DISK WILL BE ERASED${NC}" echo "" echo "Press Enter to install Archipelago, or Ctrl+C to cancel..." read echo "" echo -e "${YELLOW}🔧 Installing Archipelago...${NC}" echo "" # Unmount any existing partitions umount ${TARGET_DISK}* 2>/dev/null || true umount ${TARGET_DISK}p* 2>/dev/null || true # Create partition table echo " [1/6] Creating partitions..." parted -s "$TARGET_DISK" mklabel gpt parted -s "$TARGET_DISK" mkpart primary fat32 1MiB 513MiB parted -s "$TARGET_DISK" set 1 esp on parted -s "$TARGET_DISK" mkpart primary ext4 513MiB 100% sleep 2 # Determine partition names if [[ "$TARGET_DISK" == *nvme* ]]; then EFI_PART="${TARGET_DISK}p1" ROOT_PART="${TARGET_DISK}p2" else EFI_PART="${TARGET_DISK}1" ROOT_PART="${TARGET_DISK}2" fi # Format partitions echo " [2/6] Formatting partitions..." mkfs.vfat -F32 -n EFI "$EFI_PART" mkfs.ext4 -F -L archipelago "$ROOT_PART" # Mount echo " [3/6] Mounting filesystems..." mkdir -p /mnt/target mount "$ROOT_PART" /mnt/target mkdir -p /mnt/target/boot/efi mount "$EFI_PART" /mnt/target/boot/efi # Extract rootfs echo " [4/6] Installing system (this may take a few minutes)..." tar -xf "$ROOTFS_TAR" -C /mnt/target # Create fstab echo " [5/6] Configuring system..." cat > /mnt/target/etc/fstab < /mnt/target/etc/hostname cat > /mnt/target/etc/hosts </dev/null || true chmod +x /mnt/target/usr/local/bin/* 2>/dev/null || true fi if [ -d "$BOOT_MEDIA/archipelago/web-ui" ]; then cp -r "$BOOT_MEDIA/archipelago/web-ui" /mnt/target/opt/archipelago/ fi if [ -d "$BOOT_MEDIA/archipelago/apps" ]; then cp -r "$BOOT_MEDIA/archipelago/apps" /mnt/target/etc/archipelago/ fi # Copy pre-bundled container images if [ -d "$BOOT_MEDIA/archipelago/container-images" ]; then echo " Copying container images (this may take a moment)..." mkdir -p /mnt/target/opt/archipelago/container-images cp -r "$BOOT_MEDIA/archipelago/container-images/"*.tar /mnt/target/opt/archipelago/container-images/ 2>/dev/null || true # Copy first-boot loader script and service mkdir -p /mnt/target/opt/archipelago/scripts if [ -f "$BOOT_MEDIA/archipelago/scripts/load-container-images.sh" ]; then cp "$BOOT_MEDIA/archipelago/scripts/load-container-images.sh" /mnt/target/opt/archipelago/scripts/ chmod +x /mnt/target/opt/archipelago/scripts/load-container-images.sh fi if [ -f "$BOOT_MEDIA/archipelago/scripts/archipelago-load-images.service" ]; then cp "$BOOT_MEDIA/archipelago/scripts/archipelago-load-images.service" /mnt/target/etc/systemd/system/ fi echo " ✅ Container images staged for first-boot loading" fi # Ensure correct ownership (use numeric UID:GID 1000:1000 since we're outside chroot) chown -R 1000:1000 /mnt/target/opt/archipelago 2>/dev/null || true chown -R 1000:1000 /mnt/target/var/lib/archipelago 2>/dev/null || true # Create welcome profile (nginx serves on port 80) cat > /mnt/target/etc/profile.d/archipelago.sh <<'PROFILE' #!/bin/bash if [ -t 0 ] && [ -z "$ARCHIPELAGO_WELCOMED" ]; then export ARCHIPELAGO_WELCOMED=1 IP=$(hostname -I 2>/dev/null | awk '{print $1}') echo "" echo " ╔═══════════════════════════════════════════════════════════╗" echo " ║ 🏝️ ARCHIPELAGO BITCOIN NODE OS ║" echo " ╚═══════════════════════════════════════════════════════════╝" echo "" if [ -n "$IP" ]; then echo " 🌐 Web UI: http://$IP" echo " 📡 SSH: ssh archipelago@$IP" echo " 🔑 Password: archipelago (SSH) / password123 (Web UI)" echo "" fi fi PROFILE chmod +x /mnt/target/etc/profile.d/archipelago.sh # Systemd service is already in rootfs, but ensure it has correct config cat > /mnt/target/etc/systemd/system/archipelago.service <<'SERVICE' [Unit] Description=Archipelago Backend After=network-online.target Wants=network-online.target [Service] Type=simple User=archipelago Environment="ARCHIPELAGO_BIND=0.0.0.0:5678" Environment="ARCHIPELAGO_DEV_MODE=true" ExecStart=/usr/local/bin/archipelago Restart=on-failure RestartSec=5 [Install] WantedBy=multi-user.target SERVICE # Install GRUB echo " [6/6] Installing bootloader..." mount --bind /dev /mnt/target/dev mount --bind /dev/pts /mnt/target/dev/pts mount --bind /proc /mnt/target/proc mount --bind /sys /mnt/target/sys mount --bind /run /mnt/target/run chroot /mnt/target grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=archipelago --removable 2>/dev/null || \ chroot /mnt/target grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=archipelago 2>/dev/null || \ echo " Warning: GRUB install had issues, trying alternative..." chroot /mnt/target update-grub # Enable services chroot /mnt/target systemctl enable archipelago.service 2>/dev/null || true chroot /mnt/target systemctl enable nginx.service 2>/dev/null || true chroot /mnt/target systemctl enable archipelago-load-images.service 2>/dev/null || true # Cleanup sync umount /mnt/target/run 2>/dev/null || true umount /mnt/target/sys 2>/dev/null || true umount /mnt/target/proc 2>/dev/null || true umount /mnt/target/dev/pts 2>/dev/null || true umount /mnt/target/dev 2>/dev/null || true umount /mnt/target/boot/efi 2>/dev/null || true umount /mnt/target 2>/dev/null || true echo "" echo -e "${GREEN}╔═══════════════════════════════════════════════════════════════════╗${NC}" echo -e "${GREEN}║ ║${NC}" echo -e "${GREEN}║ ✅ INSTALLATION COMPLETE! ║${NC}" echo -e "${GREEN}║ ║${NC}" echo -e "${GREEN}║ Remove the USB drive and press Enter to reboot. ║${NC}" echo -e "${GREEN}║ ║${NC}" echo -e "${GREEN}║ After reboot: ║${NC}" echo -e "${GREEN}║ • Web UI: http:// ║${NC}" echo -e "${GREEN}║ • SSH: ssh archipelago@ ║${NC}" echo -e "${GREEN}║ • SSH Password: archipelago ║${NC}" echo -e "${GREEN}║ • Web Password: password123 ║${NC}" echo -e "${GREEN}║ ║${NC}" echo -e "${GREEN}║ Pre-loaded apps (ready to start via Web UI): ║${NC}" echo -e "${GREEN}║ • Bitcoin Knots • LND • Home Assistant ║${NC}" echo -e "${GREEN}║ ║${NC}" echo -e "${GREEN}╚═══════════════════════════════════════════════════════════════════╝${NC}" echo "" read -p "Press Enter to reboot..." reboot INSTALLER_SCRIPT chmod +x "$ARCH_DIR/auto-install.sh" # ============================================================================= # STEP 5: Configure auto-start installer on boot # ============================================================================= echo "" echo "📦 Step 5: Configuring auto-start..." # 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" # 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" docker run --rm --platform linux/amd64 \ -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/x86_64-linux-gnu/libparted.so* /output/lib/ 2>/dev/null || true cp /usr/lib/x86_64-linux-gnu/libparted.so* /output/lib/ 2>/dev/null || true cp /lib/x86_64-linux-gnu/libreadline.so* /output/lib/ 2>/dev/null || true cp /usr/lib/x86_64-linux-gnu/libreadline.so* /output/lib/ 2>/dev/null || true cp /lib/x86_64-linux-gnu/libdevmapper.so* /output/lib/ 2>/dev/null || true cp /usr/lib/x86_64-linux-gnu/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/x86_64-linux-gnu" cp "$TOOLS_DIR/lib/"*.so* "$OVERLAY_DIR/usr/lib/x86_64-linux-gnu/" 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, only in interactive terminal, only for regular users if [ -n "$INSTALLER_STARTED" ] || [ ! -t 0 ]; then return 0 2>/dev/null || exit 0 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" 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 Docker to create squashfs on macOS echo " Using Docker to create squashfs..." docker run --rm --platform linux/amd64 \ -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" fi echo " ✅ Created overlay module: 99-archipelago.squashfs" # 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 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 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 # ============================================================================= # STEP 6: Create final ISO # ============================================================================= echo "" echo "📦 Step 6: Creating bootable ISO..." OUTPUT_ISO="$OUTPUT_DIR/archipelago-installer-x86_64.iso" # Get MBR for hybrid boot ISOHDPFX="" for path in \ "/usr/local/share/syslinux/isohdpfx.bin" \ "/usr/share/syslinux/isohdpfx.bin" \ "/opt/homebrew/share/syslinux/isohdpfx.bin" \ "$INSTALLER_ISO/isolinux/isohdpfx.bin"; do if [ -f "$path" ]; then ISOHDPFX="$path" break fi done if [ -z "$ISOHDPFX" ]; then echo " Extracting MBR from isolinux.bin..." dd if="$INSTALLER_ISO/isolinux/isolinux.bin" of="$WORK_DIR/isohdpfx.bin" bs=432 count=1 2>/dev/null ISOHDPFX="$WORK_DIR/isohdpfx.bin" fi xorriso -as mkisofs -o "$OUTPUT_ISO" \ -volid "ARCHIPELAGO" \ -J -R \ -isohybrid-mbr "$ISOHDPFX" \ -c isolinux/boot.cat \ -b isolinux/isolinux.bin \ -no-emul-boot -boot-load-size 4 -boot-info-table \ -eltorito-alt-boot \ -e boot/grub/efi.img \ -no-emul-boot \ -isohybrid-gpt-basdat \ "$INSTALLER_ISO" echo "" echo "╔════════════════════════════════════════════════════════════════╗" echo "║ ✅ AUTO-INSTALLER ISO CREATED! ║" echo "╚════════════════════════════════════════════════════════════════╝" 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 "" echo "To create USB:" echo " 1. Flash with: sudo dd if=$OUTPUT_ISO of=/dev/rdiskX bs=4m" echo " Or use Balena Etcher" echo " 2. Boot from USB" echo " 3. Press Enter to install" echo " 4. Remove USB and reboot" echo ""