2026-02-01 05:42:05 +00:00
|
|
|
#!/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.
|
|
|
|
|
#
|
2026-02-03 21:43:33 +00:00
|
|
|
# CRITICAL: This script CAPTURES the LIVE SERVER state by default.
|
|
|
|
|
# Set DEV_SERVER to point to your development server.
|
|
|
|
|
#
|
|
|
|
|
# Usage:
|
|
|
|
|
# DEV_SERVER=archipelago@192.168.1.228 ./build-auto-installer-iso.sh
|
|
|
|
|
# OR just: ./build-auto-installer-iso.sh (uses default server)
|
|
|
|
|
#
|
|
|
|
|
# To build from source instead:
|
|
|
|
|
# BUILD_FROM_SOURCE=1 ./build-auto-installer-iso.sh
|
|
|
|
|
#
|
2026-02-01 05:42:05 +00:00
|
|
|
# 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
|
|
|
|
|
#
|
2026-03-21 03:06:29 +00:00
|
|
|
# Image versions: sourced from scripts/image-versions.sh (single source of truth).
|
|
|
|
|
# All container image references MUST use the $*_IMAGE variables defined there.
|
|
|
|
|
#
|
|
|
|
|
# --- PLANNED REFACTOR (post-beta) ---
|
|
|
|
|
# This script is ~1870 lines and should be split into a modular library.
|
|
|
|
|
# Proposed structure:
|
|
|
|
|
# image-recipe/
|
|
|
|
|
# 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)
|
|
|
|
|
# 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)
|
|
|
|
|
# boot-config.sh — Step 5: Configure live boot auto-start + overlay squashfs (~215 lines)
|
|
|
|
|
# create-iso.sh — Step 6: Build final bootable ISO with xorriso/grub (~140 lines)
|
|
|
|
|
# Each lib/ script exports functions; main script sources them and calls in sequence.
|
|
|
|
|
# DO NOT split until tested on the build server — this is critical infrastructure.
|
|
|
|
|
# ---
|
|
|
|
|
#
|
2026-02-01 05:42:05 +00:00
|
|
|
|
|
|
|
|
set -e
|
|
|
|
|
|
2026-03-21 01:32:28 +00:00
|
|
|
# Source pinned image versions (single source of truth)
|
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
|
|
|
[ -f "$SCRIPT_DIR/../scripts/image-versions.sh" ] && . "$SCRIPT_DIR/../scripts/image-versions.sh"
|
|
|
|
|
|
2026-02-03 21:43:33 +00:00
|
|
|
# Configuration
|
|
|
|
|
DEV_SERVER="${DEV_SERVER:-archipelago@192.168.1.228}"
|
|
|
|
|
BUILD_FROM_SOURCE="${BUILD_FROM_SOURCE:-0}"
|
2026-03-10 23:29:05 +00:00
|
|
|
UNBUNDLED="${UNBUNDLED:-0}"
|
2026-03-12 00:19:30 +00:00
|
|
|
ARCH="${ARCH:-x86_64}"
|
|
|
|
|
|
|
|
|
|
# Architecture-dependent variables
|
|
|
|
|
case "$ARCH" in
|
|
|
|
|
x86_64|amd64)
|
|
|
|
|
ARCH="x86_64"
|
|
|
|
|
DEB_ARCH="amd64"
|
|
|
|
|
LINUX_IMAGE_PKG="linux-image-amd64"
|
|
|
|
|
GRUB_EFI_PKG="grub-efi-amd64"
|
|
|
|
|
GRUB_EFI_SIGNED_PKG="grub-efi-amd64-signed"
|
2026-03-21 01:11:05 +00:00
|
|
|
GRUB_PC_PKG="grub-pc-bin"
|
2026-03-12 00:19:30 +00:00
|
|
|
GRUB_TARGET="x86_64-efi"
|
2026-03-21 01:11:05 +00:00
|
|
|
GRUB_BIOS_TARGET="i386-pc"
|
2026-03-12 00:19:30 +00:00
|
|
|
CONTAINER_PLATFORM="linux/amd64"
|
|
|
|
|
LIB_DIR="${LIB_DIR}"
|
|
|
|
|
;;
|
|
|
|
|
arm64|aarch64)
|
|
|
|
|
ARCH="arm64"
|
|
|
|
|
DEB_ARCH="arm64"
|
|
|
|
|
LINUX_IMAGE_PKG="linux-image-arm64"
|
|
|
|
|
GRUB_EFI_PKG="grub-efi-arm64"
|
|
|
|
|
GRUB_EFI_SIGNED_PKG="grub-efi-arm64-signed"
|
2026-03-21 01:11:05 +00:00
|
|
|
GRUB_PC_PKG=""
|
2026-03-12 00:19:30 +00:00
|
|
|
GRUB_TARGET="arm64-efi"
|
2026-03-21 01:11:05 +00:00
|
|
|
GRUB_BIOS_TARGET=""
|
2026-03-12 00:19:30 +00:00
|
|
|
CONTAINER_PLATFORM="linux/arm64"
|
|
|
|
|
LIB_DIR="aarch64-linux-gnu"
|
|
|
|
|
;;
|
|
|
|
|
*)
|
|
|
|
|
echo "❌ Unsupported architecture: $ARCH (use x86_64 or arm64)"
|
|
|
|
|
exit 1
|
|
|
|
|
;;
|
|
|
|
|
esac
|
2026-02-03 21:43:33 +00:00
|
|
|
|
2026-02-01 05:42:05 +00:00
|
|
|
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"
|
|
|
|
|
|
2026-03-10 23:29:05 +00:00
|
|
|
if [ "$UNBUNDLED" = "1" ]; then
|
|
|
|
|
echo "╔════════════════════════════════════════════════════════════════╗"
|
|
|
|
|
echo "║ Building Archipelago UNBUNDLED ISO (no pre-loaded apps) ║"
|
|
|
|
|
echo "╚════════════════════════════════════════════════════════════════╝"
|
|
|
|
|
else
|
|
|
|
|
echo "╔════════════════════════════════════════════════════════════════╗"
|
|
|
|
|
echo "║ Building Archipelago Auto-Installer ISO (StartOS-like) ║"
|
|
|
|
|
echo "╚════════════════════════════════════════════════════════════════╝"
|
|
|
|
|
fi
|
2026-02-01 05:42:05 +00:00
|
|
|
echo ""
|
2026-02-03 21:43:33 +00:00
|
|
|
if [ "$BUILD_FROM_SOURCE" = "1" ]; then
|
|
|
|
|
echo "📦 Mode: Building from SOURCE CODE"
|
2026-03-10 23:29:05 +00:00
|
|
|
elif [ "$UNBUNDLED" = "1" ]; then
|
|
|
|
|
echo "📦 Mode: UNBUNDLED (apps downloaded on-demand from Marketplace)"
|
|
|
|
|
echo " Server: $DEV_SERVER (backend + web UI only)"
|
2026-02-03 21:43:33 +00:00
|
|
|
else
|
|
|
|
|
echo "📦 Mode: Capturing LIVE SERVER state"
|
|
|
|
|
echo " Server: $DEV_SERVER"
|
|
|
|
|
fi
|
2026-03-12 00:19:30 +00:00
|
|
|
echo "🏗️ Architecture: $ARCH ($DEB_ARCH)"
|
2026-02-03 21:43:33 +00:00
|
|
|
echo ""
|
2026-02-01 05:42:05 +00:00
|
|
|
|
|
|
|
|
# Check for required tools
|
|
|
|
|
check_tools() {
|
|
|
|
|
local missing=""
|
2026-02-14 16:44:20 +00:00
|
|
|
local can_install=false
|
|
|
|
|
|
|
|
|
|
# Check if we can auto-install (running as root on Debian/Ubuntu)
|
|
|
|
|
if [ "$EUID" -eq 0 ] && [ -f /etc/debian_version ]; then
|
|
|
|
|
can_install=true
|
|
|
|
|
fi
|
2026-02-01 18:46:35 +00:00
|
|
|
|
|
|
|
|
# Check for docker or podman
|
|
|
|
|
if command -v docker >/dev/null 2>&1; then
|
|
|
|
|
CONTAINER_CMD="docker"
|
|
|
|
|
elif command -v podman >/dev/null 2>&1; then
|
|
|
|
|
CONTAINER_CMD="podman"
|
|
|
|
|
else
|
|
|
|
|
missing="$missing docker-or-podman"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
if ! command -v xorriso >/dev/null 2>&1; then
|
|
|
|
|
missing="$missing xorriso"
|
|
|
|
|
fi
|
2026-02-17 15:03:34 +00:00
|
|
|
if ! command -v 7z >/dev/null 2>&1 && ! command -v 7za >/dev/null 2>&1; then
|
|
|
|
|
missing="$missing p7zip-full"
|
|
|
|
|
fi
|
2026-02-01 18:46:35 +00:00
|
|
|
|
2026-02-01 05:42:05 +00:00
|
|
|
if [ -n "$missing" ]; then
|
|
|
|
|
echo "❌ Missing required tools:$missing"
|
2026-02-14 16:44:20 +00:00
|
|
|
|
|
|
|
|
if [ "$can_install" = true ]; then
|
|
|
|
|
echo " 📦 Auto-installing missing dependencies..."
|
|
|
|
|
apt-get update -qq
|
|
|
|
|
|
|
|
|
|
if [[ "$missing" == *"xorriso"* ]]; then
|
|
|
|
|
apt-get install -y xorriso
|
|
|
|
|
fi
|
2026-02-17 15:03:34 +00:00
|
|
|
if [[ "$missing" == *"p7zip-full"* ]]; then
|
|
|
|
|
apt-get install -y p7zip-full
|
|
|
|
|
fi
|
2026-02-14 16:44:20 +00:00
|
|
|
|
|
|
|
|
if [[ "$missing" == *"docker-or-podman"* ]]; then
|
|
|
|
|
echo " Installing podman..."
|
|
|
|
|
apt-get install -y podman
|
|
|
|
|
CONTAINER_CMD="podman"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
echo " ✅ Dependencies installed successfully!"
|
|
|
|
|
else
|
|
|
|
|
echo " Install with: sudo apt install xorriso podman"
|
|
|
|
|
echo " Or run this script with sudo to auto-install"
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Re-check after potential installation
|
|
|
|
|
if command -v docker >/dev/null 2>&1; then
|
|
|
|
|
CONTAINER_CMD="docker"
|
|
|
|
|
elif command -v podman >/dev/null 2>&1; then
|
|
|
|
|
CONTAINER_CMD="podman"
|
|
|
|
|
else
|
|
|
|
|
echo "❌ Container runtime still not available after installation"
|
2026-02-01 05:42:05 +00:00
|
|
|
exit 1
|
|
|
|
|
fi
|
2026-02-01 18:46:35 +00:00
|
|
|
|
|
|
|
|
echo "Using container runtime: $CONTAINER_CMD"
|
2026-02-01 05:42:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
2026-03-12 00:19:30 +00:00
|
|
|
cat > "$WORK_DIR/Dockerfile.rootfs" <<DOCKERFILE
|
2026-02-01 05:42:05 +00:00
|
|
|
FROM debian:bookworm
|
|
|
|
|
|
|
|
|
|
ENV DEBIAN_FRONTEND=noninteractive
|
|
|
|
|
|
2026-03-26 19:50:59 +00:00
|
|
|
# Preseed keyboard/console config to prevent console-setup.service failure
|
|
|
|
|
RUN echo "keyboard-configuration keyboard-configuration/layoutcode string us" | debconf-set-selections && \
|
|
|
|
|
echo "keyboard-configuration keyboard-configuration/model select Generic 105-key PC" | debconf-set-selections && \
|
|
|
|
|
echo "console-setup console-setup/charmap47 select UTF-8" | debconf-set-selections && \
|
|
|
|
|
echo "console-setup console-setup/codeset47 select Guess optimal character set" | debconf-set-selections
|
|
|
|
|
|
2026-03-25 21:21:27 +00:00
|
|
|
# Enable non-free-firmware repo — replace DEB822 sources with traditional format
|
|
|
|
|
# (DEB822 sed was silently failing, so just overwrite with known-good sources.list)
|
|
|
|
|
RUN echo "deb http://deb.debian.org/debian bookworm main non-free-firmware" > /etc/apt/sources.list && \
|
|
|
|
|
echo "deb http://deb.debian.org/debian bookworm-updates main non-free-firmware" >> /etc/apt/sources.list && \
|
|
|
|
|
echo "deb http://deb.debian.org/debian-security bookworm-security main non-free-firmware" >> /etc/apt/sources.list && \
|
|
|
|
|
rm -f /etc/apt/sources.list.d/debian.sources
|
2026-03-25 16:56:02 +00:00
|
|
|
|
fix: add 9 missing apps to ISO build (ISO-01)
CAPTURE_PATTERNS: added photoprism, nextcloud, nginx-proxy-manager,
immich, onlyoffice, adguard, penpot patterns.
CONTAINER_IMAGES: added jellyfin, photoprism, nextcloud,
nginx-proxy-manager, immich-server, postgres-immich, redis-immich,
onlyoffice, adguardhome with pinned versions for fallback pull.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 03:17:12 +00:00
|
|
|
# Install all packages we need including nginx, podman, tor, and openssl (for self-signed certs)
|
2026-02-01 05:42:05 +00:00
|
|
|
RUN apt-get update && apt-get install -y \
|
2026-03-12 00:19:30 +00:00
|
|
|
${LINUX_IMAGE_PKG} \
|
|
|
|
|
${GRUB_EFI_PKG} \
|
|
|
|
|
${GRUB_EFI_SIGNED_PKG} \
|
2026-03-26 09:12:16 +00:00
|
|
|
${GRUB_PC_PKG} \
|
2026-02-01 05:42:05 +00:00
|
|
|
systemd \
|
|
|
|
|
systemd-sysv \
|
|
|
|
|
dbus \
|
|
|
|
|
sudo \
|
|
|
|
|
network-manager \
|
|
|
|
|
openssh-server \
|
|
|
|
|
nginx \
|
|
|
|
|
podman \
|
fix: add 9 missing apps to ISO build (ISO-01)
CAPTURE_PATTERNS: added photoprism, nextcloud, nginx-proxy-manager,
immich, onlyoffice, adguard, penpot patterns.
CONTAINER_IMAGES: added jellyfin, photoprism, nextcloud,
nginx-proxy-manager, immich-server, postgres-immich, redis-immich,
onlyoffice, adguardhome with pinned versions for fallback pull.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 03:17:12 +00:00
|
|
|
tor \
|
2026-02-01 05:42:05 +00:00
|
|
|
curl \
|
|
|
|
|
wget \
|
2026-03-25 15:52:26 +00:00
|
|
|
git \
|
2026-02-01 05:42:05 +00:00
|
|
|
htop \
|
|
|
|
|
vim-tiny \
|
|
|
|
|
ca-certificates \
|
2026-03-05 08:34:53 +00:00
|
|
|
openssl \
|
feat: rootless podman, session hardening, boot stability, sidebar fix
Rootless podman migration (TASK-11):
- Remove sudo from all podman calls in PodmanClient + 8 backend files
- Remove sudo from all podman/docker calls in deploy script
- Restore full systemd security hardening: NoNewPrivileges,
RestrictAddressFamilies, MemoryDenyWriteExecute, RestrictRealtime,
RestrictNamespaces, RestrictSUIDSGID, SystemCallFilter, ProtectSystem=strict
- Enable loginctl linger for rootless container persistence
- Remove Ollama from auto-deploy (marketplace-only)
Session & auth hardening:
- Increase MAX_CONCURRENT_SESSIONS 20→50 (prevents eviction storms)
- Debounced 401 redirect in rpc-client.ts (prevents redirect storms)
Boot stability:
- optimize-debian.sh: adds chrony, swap, removes policy-rc.d
- deploy script: pre-restart chrony + swap setup
- ISO build: chrony package, swap file creation
- BootScreen: no longer clears localStorage (prevents splash replay)
- RootRedirect: sole owner of localStorage clearing on server ready
UI fixes:
- Sidebar opacity default changed from 0→visible (fixes missing sidebar
after page-persistence login without entrance animation)
- Console.log/error wrapped in import.meta.env.DEV guards
- Remove unused route import from RootRedirect
Beta tracking:
- CLAUDE.md: beta freeze protocol added
- MASTER_PLAN.md: TASK-11, TASK-17, phase structure
- BETA-PROGRESS.md: initial tracking doc
- Tagged v1.2.0-alpha.1 as pre-rootless baseline
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 13:53:27 +00:00
|
|
|
chrony \
|
2026-02-01 05:42:05 +00:00
|
|
|
locales \
|
|
|
|
|
console-setup \
|
|
|
|
|
keyboard-configuration \
|
2026-03-26 09:12:16 +00:00
|
|
|
cryptsetup \
|
2026-03-25 16:56:02 +00:00
|
|
|
firmware-realtek \
|
|
|
|
|
firmware-iwlwifi \
|
|
|
|
|
firmware-misc-nonfree \
|
2026-03-25 18:25:01 +00:00
|
|
|
intel-microcode \
|
|
|
|
|
amd64-microcode \
|
2026-03-26 09:12:16 +00:00
|
|
|
xorg \
|
|
|
|
|
chromium \
|
|
|
|
|
unclutter \
|
2026-02-01 05:42:05 +00:00
|
|
|
&& apt-get clean \
|
|
|
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
|
|
fix: add 9 missing apps to ISO build (ISO-01)
CAPTURE_PATTERNS: added photoprism, nextcloud, nginx-proxy-manager,
immich, onlyoffice, adguard, penpot patterns.
CONTAINER_IMAGES: added jellyfin, photoprism, nextcloud,
nginx-proxy-manager, immich-server, postgres-immich, redis-immich,
onlyoffice, adguardhome with pinned versions for fallback pull.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 03:17:12 +00:00
|
|
|
# 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 clean && rm -rf /var/lib/apt/lists/*
|
|
|
|
|
|
2026-02-01 05:42:05 +00:00
|
|
|
# Configure locale
|
|
|
|
|
RUN echo "en_US.UTF-8 UTF-8" > /etc/locale.gen && locale-gen
|
|
|
|
|
|
2026-03-10 23:29:05 +00:00
|
|
|
# Create archipelago user with password "archipelago"
|
2026-02-01 05:42:05 +00:00
|
|
|
RUN useradd -m -s /bin/bash -G sudo archipelago && \
|
|
|
|
|
echo "archipelago:archipelago" | chpasswd && \
|
2026-03-10 23:29:05 +00:00
|
|
|
echo "root:archipelago" | chpasswd && \
|
2026-02-01 05:42:05 +00:00
|
|
|
echo "archipelago ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/archipelago
|
2026-03-10 23:29:05 +00:00
|
|
|
# Verify password hash was set (not locked)
|
|
|
|
|
RUN grep -q "^archipelago:\$" /etc/shadow && echo "Password set OK" || echo "WARNING: password may not be set"
|
2026-02-01 05:42:05 +00:00
|
|
|
|
|
|
|
|
# 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
|
|
|
|
|
|
2026-03-05 08:34:53 +00:00
|
|
|
# Install nginx snippets (PWA config, HTTPS app proxies)
|
|
|
|
|
COPY snippets/ /etc/nginx/snippets/
|
|
|
|
|
|
|
|
|
|
# Generate self-signed SSL certificate for HTTPS (PWA install requires secure context)
|
|
|
|
|
RUN mkdir -p /etc/archipelago/ssl && \
|
|
|
|
|
openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \
|
|
|
|
|
-keyout /etc/archipelago/ssl/archipelago.key \
|
|
|
|
|
-out /etc/archipelago/ssl/archipelago.crt \
|
|
|
|
|
-subj "/C=XX/ST=Bitcoin/L=Node/O=Archipelago/CN=archipelago" && \
|
|
|
|
|
chmod 600 /etc/archipelago/ssl/archipelago.key
|
|
|
|
|
|
2026-02-01 05:42:05 +00:00
|
|
|
# Create archipelago systemd service
|
|
|
|
|
COPY archipelago.service /etc/systemd/system/archipelago.service
|
2026-03-25 15:52:26 +00:00
|
|
|
COPY archipelago-update.service /etc/systemd/system/archipelago-update.service
|
|
|
|
|
COPY archipelago-update.timer /etc/systemd/system/archipelago-update.timer
|
2026-02-01 05:42:05 +00:00
|
|
|
|
|
|
|
|
# Enable services
|
|
|
|
|
RUN systemctl enable NetworkManager || true && \
|
|
|
|
|
systemctl enable ssh || true && \
|
|
|
|
|
systemctl enable nginx || true && \
|
fix: add 9 missing apps to ISO build (ISO-01)
CAPTURE_PATTERNS: added photoprism, nextcloud, nginx-proxy-manager,
immich, onlyoffice, adguard, penpot patterns.
CONTAINER_IMAGES: added jellyfin, photoprism, nextcloud,
nginx-proxy-manager, immich-server, postgres-immich, redis-immich,
onlyoffice, adguardhome with pinned versions for fallback pull.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 03:17:12 +00:00
|
|
|
systemctl enable archipelago || true && \
|
|
|
|
|
systemctl enable tor || true && \
|
feat: rootless podman, session hardening, boot stability, sidebar fix
Rootless podman migration (TASK-11):
- Remove sudo from all podman calls in PodmanClient + 8 backend files
- Remove sudo from all podman/docker calls in deploy script
- Restore full systemd security hardening: NoNewPrivileges,
RestrictAddressFamilies, MemoryDenyWriteExecute, RestrictRealtime,
RestrictNamespaces, RestrictSUIDSGID, SystemCallFilter, ProtectSystem=strict
- Enable loginctl linger for rootless container persistence
- Remove Ollama from auto-deploy (marketplace-only)
Session & auth hardening:
- Increase MAX_CONCURRENT_SESSIONS 20→50 (prevents eviction storms)
- Debounced 401 redirect in rpc-client.ts (prevents redirect storms)
Boot stability:
- optimize-debian.sh: adds chrony, swap, removes policy-rc.d
- deploy script: pre-restart chrony + swap setup
- ISO build: chrony package, swap file creation
- BootScreen: no longer clears localStorage (prevents splash replay)
- RootRedirect: sole owner of localStorage clearing on server ready
UI fixes:
- Sidebar opacity default changed from 0→visible (fixes missing sidebar
after page-persistence login without entrance animation)
- Console.log/error wrapped in import.meta.env.DEV guards
- Remove unused route import from RootRedirect
Beta tracking:
- CLAUDE.md: beta freeze protocol added
- MASTER_PLAN.md: TASK-11, TASK-17, phase structure
- BETA-PROGRESS.md: initial tracking doc
- Tagged v1.2.0-alpha.1 as pre-rootless baseline
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 13:53:27 +00:00
|
|
|
systemctl enable tailscaled || true && \
|
2026-03-25 15:52:26 +00:00
|
|
|
systemctl enable chrony || true && \
|
|
|
|
|
systemctl enable archipelago-update.timer || true
|
feat: rootless podman, session hardening, boot stability, sidebar fix
Rootless podman migration (TASK-11):
- Remove sudo from all podman calls in PodmanClient + 8 backend files
- Remove sudo from all podman/docker calls in deploy script
- Restore full systemd security hardening: NoNewPrivileges,
RestrictAddressFamilies, MemoryDenyWriteExecute, RestrictRealtime,
RestrictNamespaces, RestrictSUIDSGID, SystemCallFilter, ProtectSystem=strict
- Enable loginctl linger for rootless container persistence
- Remove Ollama from auto-deploy (marketplace-only)
Session & auth hardening:
- Increase MAX_CONCURRENT_SESSIONS 20→50 (prevents eviction storms)
- Debounced 401 redirect in rpc-client.ts (prevents redirect storms)
Boot stability:
- optimize-debian.sh: adds chrony, swap, removes policy-rc.d
- deploy script: pre-restart chrony + swap setup
- ISO build: chrony package, swap file creation
- BootScreen: no longer clears localStorage (prevents splash replay)
- RootRedirect: sole owner of localStorage clearing on server ready
UI fixes:
- Sidebar opacity default changed from 0→visible (fixes missing sidebar
after page-persistence login without entrance animation)
- Console.log/error wrapped in import.meta.env.DEV guards
- Remove unused route import from RootRedirect
Beta tracking:
- CLAUDE.md: beta freeze protocol added
- MASTER_PLAN.md: TASK-11, TASK-17, phase structure
- BETA-PROGRESS.md: initial tracking doc
- Tagged v1.2.0-alpha.1 as pre-rootless baseline
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 13:53:27 +00:00
|
|
|
|
|
|
|
|
# Remove policy-rc.d so services can start on first boot
|
|
|
|
|
RUN rm -f /usr/sbin/policy-rc.d
|
2026-02-01 05:42:05 +00:00
|
|
|
|
2026-03-05 08:34:53 +00:00
|
|
|
# Create directories (including Cloud storage for FileBrowser)
|
2026-02-01 05:42:05 +00:00
|
|
|
RUN mkdir -p /var/lib/archipelago/{data,config,containers} && \
|
|
|
|
|
mkdir -p /etc/archipelago && \
|
|
|
|
|
mkdir -p /opt/archipelago/{bin,scripts,web-ui} && \
|
2026-03-05 08:34:53 +00:00
|
|
|
mkdir -p /var/lib/archipelago/data/cloud/{Documents,Photos,Music,Videos,Downloads} && \
|
2026-02-01 05:42:05 +00:00
|
|
|
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
|
|
|
|
|
|
2026-03-05 08:34:53 +00:00
|
|
|
# Copy nginx snippets for HTTPS (PWA, app proxies)
|
|
|
|
|
if [ -d "$SCRIPT_DIR/configs/snippets" ]; then
|
|
|
|
|
mkdir -p "$WORK_DIR/snippets"
|
|
|
|
|
cp "$SCRIPT_DIR/configs/snippets/"*.conf "$WORK_DIR/snippets/" 2>/dev/null || true
|
|
|
|
|
echo " Using nginx snippets from configs/snippets/"
|
|
|
|
|
else
|
|
|
|
|
mkdir -p "$WORK_DIR/snippets"
|
|
|
|
|
echo " ⚠ No nginx snippets found, HTTPS features may not work"
|
|
|
|
|
fi
|
|
|
|
|
|
2026-03-22 03:30:21 +00:00
|
|
|
# Use nginx config from configs/ (includes app proxies for Nextcloud, Vaultwarden, etc.)
|
2026-02-25 18:20:50 +00:00
|
|
|
if [ -f "$SCRIPT_DIR/configs/nginx-archipelago.conf" ]; then
|
|
|
|
|
cp "$SCRIPT_DIR/configs/nginx-archipelago.conf" "$WORK_DIR/nginx-archipelago.conf"
|
|
|
|
|
echo " Using nginx config from configs/nginx-archipelago.conf"
|
|
|
|
|
else
|
|
|
|
|
echo " ⚠ configs/nginx-archipelago.conf not found, using minimal config"
|
|
|
|
|
cat > "$WORK_DIR/nginx-archipelago.conf" <<'NGINXCONF'
|
2026-02-01 05:42:05 +00:00
|
|
|
server {
|
|
|
|
|
listen 80;
|
|
|
|
|
server_name _;
|
|
|
|
|
root /opt/archipelago/web-ui;
|
|
|
|
|
index index.html;
|
2026-02-25 18:20:50 +00:00
|
|
|
location / { try_files $uri $uri/ /index.html; }
|
|
|
|
|
location /archipelago/ { 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; }
|
|
|
|
|
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_connect_timeout 300s; proxy_send_timeout 300s; proxy_read_timeout 300s; }
|
|
|
|
|
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; proxy_read_timeout 86400s; }
|
2026-02-01 05:42:05 +00:00
|
|
|
}
|
|
|
|
|
NGINXCONF
|
2026-02-25 18:20:50 +00:00
|
|
|
fi
|
2026-02-01 05:42:05 +00:00
|
|
|
|
2026-03-18 10:50:13 +00:00
|
|
|
# Copy udev rule for mesh radio stable naming
|
|
|
|
|
if [ -f "$SCRIPT_DIR/configs/99-mesh-radio.rules" ]; then
|
|
|
|
|
cp "$SCRIPT_DIR/configs/99-mesh-radio.rules" "$WORK_DIR/99-mesh-radio.rules"
|
|
|
|
|
echo " Using 99-mesh-radio.rules from configs/"
|
|
|
|
|
fi
|
|
|
|
|
|
2026-03-25 15:52:26 +00:00
|
|
|
# Copy update service and timer
|
|
|
|
|
if [ -f "$SCRIPT_DIR/configs/archipelago-update.service" ]; then
|
|
|
|
|
cp "$SCRIPT_DIR/configs/archipelago-update.service" "$WORK_DIR/archipelago-update.service"
|
|
|
|
|
cp "$SCRIPT_DIR/configs/archipelago-update.timer" "$WORK_DIR/archipelago-update.timer"
|
|
|
|
|
echo " Using archipelago-update.service + timer from configs/"
|
|
|
|
|
fi
|
|
|
|
|
|
2026-02-25 18:20:50 +00:00
|
|
|
# Use archipelago.service from configs/ (User=root for Podman container access)
|
|
|
|
|
if [ -f "$SCRIPT_DIR/configs/archipelago.service" ]; then
|
|
|
|
|
cp "$SCRIPT_DIR/configs/archipelago.service" "$WORK_DIR/archipelago.service"
|
|
|
|
|
echo " Using archipelago.service from configs/"
|
|
|
|
|
else
|
|
|
|
|
cat > "$WORK_DIR/archipelago.service" <<'SYSTEMDSERVICE'
|
2026-02-01 05:42:05 +00:00
|
|
|
[Unit]
|
|
|
|
|
Description=Archipelago Backend
|
|
|
|
|
After=network-online.target
|
|
|
|
|
Wants=network-online.target
|
|
|
|
|
|
|
|
|
|
[Service]
|
|
|
|
|
Type=simple
|
2026-02-25 18:20:50 +00:00
|
|
|
User=root
|
2026-03-19 12:55:31 +00:00
|
|
|
Environment="ARCHIPELAGO_BIND=127.0.0.1:5678"
|
2026-02-01 05:42:05 +00:00
|
|
|
Environment="ARCHIPELAGO_DEV_MODE=true"
|
|
|
|
|
ExecStart=/usr/local/bin/archipelago
|
|
|
|
|
Restart=on-failure
|
|
|
|
|
RestartSec=5
|
|
|
|
|
|
|
|
|
|
[Install]
|
|
|
|
|
WantedBy=multi-user.target
|
|
|
|
|
SYSTEMDSERVICE
|
2026-02-25 18:20:50 +00:00
|
|
|
fi
|
2026-02-01 05:42:05 +00:00
|
|
|
|
2026-02-01 18:46:35 +00:00
|
|
|
echo " Building $CONTAINER_CMD image (this may take a few minutes)..."
|
2026-03-25 21:31:51 +00:00
|
|
|
$CONTAINER_CMD build --no-cache --platform $CONTAINER_PLATFORM -t archipelago-rootfs -f "$WORK_DIR/Dockerfile.rootfs" "$WORK_DIR"
|
2026-02-01 05:42:05 +00:00
|
|
|
|
|
|
|
|
echo " Exporting filesystem..."
|
2026-03-12 00:19:30 +00:00
|
|
|
$CONTAINER_CMD create --platform $CONTAINER_PLATFORM --name archipelago-rootfs-tmp archipelago-rootfs
|
2026-02-01 18:46:35 +00:00
|
|
|
$CONTAINER_CMD export archipelago-rootfs-tmp > "$ROOTFS_TAR"
|
|
|
|
|
$CONTAINER_CMD rm archipelago-rootfs-tmp
|
2026-02-01 05:42:05 +00:00
|
|
|
|
|
|
|
|
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"
|
2026-03-25 16:51:14 +00:00
|
|
|
EXPECTED_SIZE=350000000 # ~350MB min (Debian 12 Live standard ~600MB)
|
2026-02-14 16:44:20 +00:00
|
|
|
|
|
|
|
|
# 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"
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
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
|
2026-03-25 16:51:14 +00:00
|
|
|
# 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"
|
2026-02-14 16:44:20 +00:00
|
|
|
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-25 17:03:21 +00:00
|
|
|
# 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"
|
2026-02-14 16:44:20 +00:00
|
|
|
else
|
2026-03-25 17:03:21 +00:00
|
|
|
echo " ❌ Downloaded file not found in $WORK_DIR"
|
|
|
|
|
echo " Files present:"
|
|
|
|
|
ls -la "$WORK_DIR"/*.iso 2>/dev/null || echo " (no .iso files)"
|
2026-02-14 16:44:20 +00:00
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
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"
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
echo " ✅ Download complete ($(($FINAL_SIZE / 1024 / 1024))MB)"
|
2026-02-01 05:42:05 +00:00
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
echo " Extracting installer base..."
|
|
|
|
|
INSTALLER_ISO="$WORK_DIR/installer-iso"
|
|
|
|
|
rm -rf "$INSTALLER_ISO"
|
|
|
|
|
mkdir -p "$INSTALLER_ISO"
|
|
|
|
|
cd "$INSTALLER_ISO"
|
2026-03-09 17:09:59 +00:00
|
|
|
# 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
|
2026-02-17 15:03:34 +00:00
|
|
|
echo " ❌ Failed to extract ISO. Install p7zip-full: sudo apt install p7zip-full"
|
|
|
|
|
exit 1
|
2026-03-09 17:09:59 +00:00
|
|
|
fi
|
2026-02-01 05:42:05 +00:00
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
|
|
# 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"
|
|
|
|
|
|
2026-02-03 21:43:33 +00:00
|
|
|
# Capture backend binary from live server
|
|
|
|
|
if [ "$BUILD_FROM_SOURCE" = "1" ]; then
|
|
|
|
|
echo " Building backend binary from source..."
|
|
|
|
|
else
|
|
|
|
|
echo " Capturing backend binary from live server..."
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Try to get from live server first (unless BUILD_FROM_SOURCE=1)
|
|
|
|
|
BACKEND_CAPTURED=0
|
|
|
|
|
if [ "$BUILD_FROM_SOURCE" != "1" ]; then
|
2026-03-26 15:28:43 +00:00
|
|
|
# Direct copy from ARCHIPELAGO_BIN env, local install, or remote
|
2026-03-26 16:23:19 +00:00
|
|
|
BIN="${ARCHIPELAGO_BIN:-/usr/local/bin/archipelago}"
|
2026-03-26 15:28:43 +00:00
|
|
|
if [ -f "$BIN" ]; then
|
|
|
|
|
cp "$BIN" "$ARCH_DIR/bin/archipelago"
|
2026-02-17 15:03:34 +00:00
|
|
|
chmod +x "$ARCH_DIR/bin/archipelago"
|
|
|
|
|
echo " ✅ Backend captured from local system ($(du -h "$ARCH_DIR/bin/archipelago" | cut -f1))"
|
|
|
|
|
BACKEND_CAPTURED=1
|
|
|
|
|
fi
|
|
|
|
|
# Remote copy via SCP if local failed
|
|
|
|
|
if [ "$BACKEND_CAPTURED" = "0" ] && [ "$DEV_SERVER" != "localhost" ] && [ "$DEV_SERVER" != "127.0.0.1" ]; then
|
2026-02-03 21:43:33 +00:00
|
|
|
if scp "$DEV_SERVER:/usr/local/bin/archipelago" "$ARCH_DIR/bin/archipelago" 2>/dev/null; then
|
|
|
|
|
chmod +x "$ARCH_DIR/bin/archipelago"
|
|
|
|
|
echo " ✅ Backend captured from remote server ($(du -h "$ARCH_DIR/bin/archipelago" | cut -f1))"
|
|
|
|
|
BACKEND_CAPTURED=1
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
if [ "$BACKEND_CAPTURED" = "0" ]; then
|
|
|
|
|
if [ "$BUILD_FROM_SOURCE" != "1" ]; then
|
|
|
|
|
echo " ⚠️ Could not capture from live server, building from source..."
|
|
|
|
|
fi
|
|
|
|
|
BACKEND_DOCKERFILE="$WORK_DIR/Dockerfile.backend"
|
|
|
|
|
cat > "$BACKEND_DOCKERFILE" <<'BACKENDFILE'
|
2026-02-01 13:24:03 +00:00
|
|
|
FROM rust:1.93-bookworm as builder
|
|
|
|
|
WORKDIR /build
|
|
|
|
|
COPY core ./core
|
|
|
|
|
RUN cd core && cargo build --release --bin archipelago
|
|
|
|
|
BACKENDFILE
|
|
|
|
|
|
2026-03-12 00:19:30 +00:00
|
|
|
if $CONTAINER_CMD build --platform $CONTAINER_PLATFORM -t archipelago-backend -f "$BACKEND_DOCKERFILE" "$SCRIPT_DIR/.." 2>&1 | tail -20; then
|
2026-02-03 21:43:33 +00:00
|
|
|
echo " Extracting backend binary..."
|
2026-03-12 00:19:30 +00:00
|
|
|
BACKEND_CONTAINER=$($CONTAINER_CMD create --platform $CONTAINER_PLATFORM archipelago-backend)
|
2026-02-03 21:43:33 +00:00
|
|
|
$CONTAINER_CMD cp "$BACKEND_CONTAINER:/build/core/target/release/archipelago" "$ARCH_DIR/bin/" && \
|
|
|
|
|
echo " ✅ Backend binary built ($(du -h "$ARCH_DIR/bin/archipelago" | cut -f1))"
|
|
|
|
|
$CONTAINER_CMD rm "$BACKEND_CONTAINER"
|
|
|
|
|
else
|
|
|
|
|
echo " ❌ Backend build failed and server capture failed"
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Capture web UI from live server
|
|
|
|
|
if [ "$BUILD_FROM_SOURCE" = "1" ]; then
|
|
|
|
|
echo " Building web UI from source..."
|
2026-02-01 13:24:03 +00:00
|
|
|
else
|
2026-02-03 21:43:33 +00:00
|
|
|
echo " Capturing web UI from live server..."
|
|
|
|
|
fi
|
|
|
|
|
mkdir -p "$ARCH_DIR/web-ui"
|
|
|
|
|
|
|
|
|
|
# Try to get from live server first (unless BUILD_FROM_SOURCE=1)
|
|
|
|
|
WEBUI_CAPTURED=0
|
|
|
|
|
if [ "$BUILD_FROM_SOURCE" != "1" ]; then
|
2026-02-17 15:03:34 +00:00
|
|
|
# Direct copy from local filesystem (when running on target with sudo)
|
|
|
|
|
if [ -d "/opt/archipelago/web-ui" ] && [ "$(ls -A /opt/archipelago/web-ui 2>/dev/null)" ]; then
|
|
|
|
|
cp -r /opt/archipelago/web-ui/* "$ARCH_DIR/web-ui/"
|
|
|
|
|
echo " ✅ Web UI captured from local system ($(du -sh "$ARCH_DIR/web-ui" | cut -f1))"
|
|
|
|
|
WEBUI_CAPTURED=1
|
|
|
|
|
fi
|
|
|
|
|
# Remote copy via rsync if local failed
|
|
|
|
|
if [ "$WEBUI_CAPTURED" = "0" ] && [ "$DEV_SERVER" != "localhost" ] && [ "$DEV_SERVER" != "127.0.0.1" ]; then
|
2026-02-03 21:43:33 +00:00
|
|
|
if rsync -az "$DEV_SERVER:/opt/archipelago/web-ui/" "$ARCH_DIR/web-ui/" 2>/dev/null && [ "$(ls -A "$ARCH_DIR/web-ui")" ]; then
|
|
|
|
|
echo " ✅ Web UI captured from remote server ($(du -sh "$ARCH_DIR/web-ui" | cut -f1))"
|
|
|
|
|
WEBUI_CAPTURED=1
|
|
|
|
|
fi
|
2026-02-01 13:24:03 +00:00
|
|
|
fi
|
2026-02-01 05:42:05 +00:00
|
|
|
fi
|
|
|
|
|
|
2026-02-03 21:43:33 +00:00
|
|
|
if [ "$WEBUI_CAPTURED" = "0" ]; then
|
|
|
|
|
if [ "$BUILD_FROM_SOURCE" != "1" ]; then
|
|
|
|
|
echo " ⚠️ Could not capture from live server, building from source..."
|
2026-02-01 13:24:03 +00:00
|
|
|
fi
|
2026-02-03 21:43:33 +00:00
|
|
|
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 built ($(du -sh "$ARCH_DIR/web-ui" | cut -f1))"
|
|
|
|
|
fi
|
2026-02-01 13:24:03 +00:00
|
|
|
else
|
2026-02-03 21:43:33 +00:00
|
|
|
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"
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
2026-02-01 13:24:03 +00:00
|
|
|
fi
|
2026-02-03 21:43:33 +00:00
|
|
|
cd "$SCRIPT_DIR"
|
2026-02-01 05:42:05 +00:00
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# 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 ""
|
2026-03-10 23:29:05 +00:00
|
|
|
|
|
|
|
|
if [ "$UNBUNDLED" = "1" ]; then
|
2026-03-26 09:12:16 +00:00
|
|
|
echo "📦 Step 3b: Bundling core containers only (UNBUNDLED mode)"
|
|
|
|
|
echo " Optional apps will be downloaded on-demand from the Marketplace after install."
|
2026-03-10 23:29:05 +00:00
|
|
|
IMAGES_DIR="$ARCH_DIR/container-images"
|
|
|
|
|
mkdir -p "$IMAGES_DIR"
|
2026-03-26 09:12:16 +00:00
|
|
|
# FileBrowser is a core dependency (powers the Cloud file manager) — always bundle it
|
2026-03-26 14:06:21 +00:00
|
|
|
CORE_IMAGE="${FILEBROWSER_IMAGE}"
|
2026-03-26 09:12:16 +00:00
|
|
|
CORE_FILE="filebrowser.tar"
|
|
|
|
|
if [ -f "$IMAGES_DIR/$CORE_FILE" ]; then
|
|
|
|
|
echo " ✅ Using cached: $CORE_FILE"
|
|
|
|
|
else
|
|
|
|
|
echo " Pulling $CORE_IMAGE ($CONTAINER_PLATFORM)..."
|
|
|
|
|
if $CONTAINER_CMD pull --platform $CONTAINER_PLATFORM "$CORE_IMAGE"; then
|
|
|
|
|
$CONTAINER_CMD save "$CORE_IMAGE" -o "$IMAGES_DIR/$CORE_FILE" 2>/dev/null && \
|
|
|
|
|
echo " ✅ Saved core: $CORE_FILE ($(du -h "$IMAGES_DIR/$CORE_FILE" | cut -f1))" || \
|
|
|
|
|
echo " ⚠️ Failed to save $CORE_IMAGE"
|
|
|
|
|
else
|
|
|
|
|
echo " ⚠️ Failed to pull $CORE_IMAGE — Cloud will not work until installed"
|
|
|
|
|
fi
|
|
|
|
|
fi
|
2026-03-10 23:29:05 +00:00
|
|
|
else
|
2026-02-01 05:42:05 +00:00
|
|
|
echo "📦 Step 3b: Bundling container images for offline use..."
|
|
|
|
|
|
|
|
|
|
IMAGES_DIR="$ARCH_DIR/container-images"
|
|
|
|
|
mkdir -p "$IMAGES_DIR"
|
|
|
|
|
|
2026-02-14 16:44:20 +00:00
|
|
|
# When DEV_SERVER is set (and not localhost), try to capture images from live server
|
|
|
|
|
# so the ISO includes the same set as the dev server (including custom UIs: bitcoin-ui, lnd-ui).
|
|
|
|
|
IMAGES_CAPTURED_FROM_SERVER=0
|
|
|
|
|
if [ -n "$DEV_SERVER" ] && [ "$DEV_SERVER" != "localhost" ] && [ "$DEV_SERVER" != "127.0.0.1" ]; then
|
|
|
|
|
echo " Capturing container images from live server ($DEV_SERVER)..."
|
2026-03-09 17:09:59 +00:00
|
|
|
# Patterns match against `podman images` repository names (not container names)
|
2026-03-22 03:30:21 +00:00
|
|
|
CAPTURE_PATTERNS="bitcoin-ui bitcoinknots lnd lnd-ui electrs-ui filebrowser mempool backend frontend electrs tailscale homeassistant home-assistant btcpayserver nbxplorer postgres alpine-tor nostr-rs-relay strfry fedimintd gatewayd dwn-server grafana uptime-kuma jellyfin vaultwarden searxng mariadb valkey nginx-alpine portainer photoprism nextcloud nginx-proxy-manager onlyoffice adguard"
|
2026-02-14 16:44:20 +00:00
|
|
|
REMOTE_TMP="/tmp/archipelago-image-capture-$$"
|
2026-03-21 01:11:05 +00:00
|
|
|
SAVED_LIST=$(ssh "$DEV_SERVER" "mkdir -p $REMOTE_TMP && for p in $CAPTURE_PATTERNS; do img=\$(podman images --format '{{.Repository}}:{{.Tag}}' 2>/dev/null | grep -i \"\$p\" | head -1); [ -n \"\$img\" ] && podman save -o \"$REMOTE_TMP/\$p.tar\" \"\$img\" 2>/dev/null && echo \"\$p\"; done" 2>/dev/null) || true
|
2026-02-14 16:44:20 +00:00
|
|
|
for p in $SAVED_LIST; do
|
|
|
|
|
if [ -n "$p" ] && scp "$DEV_SERVER:$REMOTE_TMP/$p.tar" "$IMAGES_DIR/$p.tar" 2>/dev/null; then
|
|
|
|
|
echo " ✅ Captured from server: $p.tar"
|
|
|
|
|
IMAGES_CAPTURED_FROM_SERVER=1
|
|
|
|
|
fi
|
|
|
|
|
done
|
|
|
|
|
ssh "$DEV_SERVER" "rm -rf $REMOTE_TMP" 2>/dev/null || true
|
|
|
|
|
if [ "$IMAGES_CAPTURED_FROM_SERVER" = "0" ]; then
|
|
|
|
|
echo " ⚠️ No images captured from server, will use registry pull fallback"
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Define images to bundle for fallback (when not from server or missing). Includes filebrowser.
|
|
|
|
|
# bitcoin-ui and lnd-ui are custom and normally captured from server or built separately.
|
2026-03-09 17:09:59 +00:00
|
|
|
# Alpha: core Bitcoin/Lightning stack + essential apps. Others pulled on-demand from Marketplace.
|
2026-02-01 05:42:05 +00:00
|
|
|
CONTAINER_IMAGES="
|
2026-03-26 14:06:21 +00:00
|
|
|
${BITCOIN_KNOTS_IMAGE} bitcoin-knots.tar
|
|
|
|
|
${LND_IMAGE} lnd.tar
|
|
|
|
|
${HOMEASSISTANT_IMAGE} homeassistant.tar
|
|
|
|
|
${BTCPAY_IMAGE} btcpayserver.tar
|
|
|
|
|
${NBXPLORER_IMAGE} nbxplorer.tar
|
|
|
|
|
${POSTGRES_IMAGE} postgres-btcpay.tar
|
|
|
|
|
${MEMPOOL_BACKEND_IMAGE} mempool-backend.tar
|
|
|
|
|
${MEMPOOL_WEB_IMAGE} mempool-frontend.tar
|
|
|
|
|
${ELECTRUMX_IMAGE} electrumx.tar
|
|
|
|
|
${MARIADB_IMAGE} mariadb-mempool.tar
|
|
|
|
|
${FEDIMINT_IMAGE} fedimint.tar
|
|
|
|
|
${FEDIMINT_GATEWAY_IMAGE} fedimint-gateway.tar
|
|
|
|
|
${FILEBROWSER_IMAGE} filebrowser.tar
|
|
|
|
|
${ALPINE_TOR_IMAGE} alpine-tor.tar
|
|
|
|
|
${NGINX_ALPINE_IMAGE} nginx-alpine.tar
|
|
|
|
|
${DWN_SERVER_IMAGE} dwn-server.tar
|
|
|
|
|
${GRAFANA_IMAGE} grafana.tar
|
|
|
|
|
${UPTIME_KUMA_IMAGE} uptime-kuma.tar
|
|
|
|
|
${VAULTWARDEN_IMAGE} vaultwarden.tar
|
|
|
|
|
${SEARXNG_IMAGE} searxng.tar
|
|
|
|
|
${PORTAINER_IMAGE} portainer.tar
|
|
|
|
|
${TAILSCALE_IMAGE} tailscale.tar
|
|
|
|
|
${JELLYFIN_IMAGE} jellyfin.tar
|
|
|
|
|
${PHOTOPRISM_IMAGE} photoprism.tar
|
|
|
|
|
${NEXTCLOUD_IMAGE} nextcloud.tar
|
|
|
|
|
${NPM_IMAGE} nginx-proxy-manager.tar
|
|
|
|
|
${ONLYOFFICE_IMAGE} onlyoffice.tar
|
|
|
|
|
${ADGUARDHOME_IMAGE} adguardhome.tar
|
2026-02-01 05:42:05 +00:00
|
|
|
"
|
|
|
|
|
|
2026-03-12 00:19:30 +00:00
|
|
|
# Pull and save each image (force target arch) only if not already present
|
2026-02-01 05:42:05 +00:00
|
|
|
echo "$CONTAINER_IMAGES" | while read -r image filename; do
|
|
|
|
|
[ -z "$image" ] && continue
|
|
|
|
|
tarpath="$IMAGES_DIR/$filename"
|
2026-03-10 23:29:05 +00:00
|
|
|
|
2026-02-01 05:42:05 +00:00
|
|
|
if [ -f "$tarpath" ]; then
|
|
|
|
|
echo " ✅ Using cached: $filename"
|
|
|
|
|
else
|
2026-03-12 00:19:30 +00:00
|
|
|
echo " Pulling $image ($CONTAINER_PLATFORM)..."
|
|
|
|
|
if $CONTAINER_CMD pull --platform $CONTAINER_PLATFORM "$image"; then
|
2026-02-01 05:42:05 +00:00
|
|
|
echo " Saving $filename..."
|
2026-03-06 13:00:28 +00:00
|
|
|
if $CONTAINER_CMD save "$image" -o "$tarpath" 2>/dev/null; then
|
|
|
|
|
echo " ✅ Saved: $(du -h "$tarpath" | cut -f1)"
|
|
|
|
|
else
|
|
|
|
|
echo " ⚠️ Failed to save $image (zstd/format issue) - skipping"
|
|
|
|
|
rm -f "$tarpath"
|
|
|
|
|
fi
|
2026-02-01 05:42:05 +00:00
|
|
|
else
|
|
|
|
|
echo " ⚠️ Failed to pull $image - skipping"
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
done
|
2026-03-10 23:29:05 +00:00
|
|
|
fi # end UNBUNDLED check
|
2026-02-01 05:42:05 +00:00
|
|
|
|
|
|
|
|
# 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
|
|
|
|
|
|
2026-02-17 15:03:34 +00:00
|
|
|
# Ensure archy-net exists for mempool stack (db, api, frontend)
|
|
|
|
|
podman network create archy-net 2>/dev/null || true
|
|
|
|
|
|
2026-02-01 05:42:05 +00:00
|
|
|
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/"
|
|
|
|
|
|
2026-02-17 15:03:34 +00:00
|
|
|
# Tor setup: copy torrc and create first-boot setup script
|
|
|
|
|
mkdir -p "$ARCH_DIR/scripts/tor"
|
|
|
|
|
if [ -f "$SCRIPT_DIR/../scripts/tor/torrc.template" ]; then
|
|
|
|
|
cp "$SCRIPT_DIR/../scripts/tor/torrc.template" "$ARCH_DIR/scripts/tor/torrc"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
echo " Creating first-boot Tor setup service..."
|
|
|
|
|
cat > "$WORK_DIR/archipelago-setup-tor.service" <<'TORSERVICE'
|
|
|
|
|
[Unit]
|
|
|
|
|
Description=Setup and start Archipelago Tor hidden services
|
|
|
|
|
After=archipelago-load-images.service network.target podman.service
|
|
|
|
|
ConditionPathExists=/opt/archipelago/scripts/setup-tor.sh
|
|
|
|
|
|
|
|
|
|
[Service]
|
|
|
|
|
Type=oneshot
|
|
|
|
|
ExecStart=/opt/archipelago/scripts/setup-tor.sh
|
|
|
|
|
RemainAfterExit=yes
|
|
|
|
|
|
|
|
|
|
[Install]
|
|
|
|
|
WantedBy=multi-user.target
|
|
|
|
|
TORSERVICE
|
|
|
|
|
|
|
|
|
|
cat > "$WORK_DIR/setup-tor.sh" <<'TORSCRIPT'
|
|
|
|
|
#!/bin/bash
|
fix: add 9 missing apps to ISO build (ISO-01)
CAPTURE_PATTERNS: added photoprism, nextcloud, nginx-proxy-manager,
immich, onlyoffice, adguard, penpot patterns.
CONTAINER_IMAGES: added jellyfin, photoprism, nextcloud,
nginx-proxy-manager, immich-server, postgres-immich, redis-immich,
onlyoffice, adguardhome with pinned versions for fallback pull.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 03:17:12 +00:00
|
|
|
# Setup and start Tor hidden services (autoinstaller first-boot)
|
|
|
|
|
# Prefers system Tor (apt package) over container
|
2026-02-17 15:03:34 +00:00
|
|
|
|
fix: add 9 missing apps to ISO build (ISO-01)
CAPTURE_PATTERNS: added photoprism, nextcloud, nginx-proxy-manager,
immich, onlyoffice, adguard, penpot patterns.
CONTAINER_IMAGES: added jellyfin, photoprism, nextcloud,
nginx-proxy-manager, immich-server, postgres-immich, redis-immich,
onlyoffice, adguardhome with pinned versions for fallback pull.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 03:17:12 +00:00
|
|
|
ARCHY_TOR_DIR="/var/lib/archipelago/tor"
|
|
|
|
|
TOR_DIR="/var/lib/tor"
|
2026-02-17 15:03:34 +00:00
|
|
|
LOG="/var/log/archipelago-tor.log"
|
|
|
|
|
|
fix: add 9 missing apps to ISO build (ISO-01)
CAPTURE_PATTERNS: added photoprism, nextcloud, nginx-proxy-manager,
immich, onlyoffice, adguard, penpot patterns.
CONTAINER_IMAGES: added jellyfin, photoprism, nextcloud,
nginx-proxy-manager, immich-server, postgres-immich, redis-immich,
onlyoffice, adguardhome with pinned versions for fallback pull.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 03:17:12 +00:00
|
|
|
mkdir -p "$ARCHY_TOR_DIR"
|
|
|
|
|
|
|
|
|
|
# Write services.json for the backend to read
|
|
|
|
|
python3 -c '
|
|
|
|
|
import json
|
|
|
|
|
services = [
|
|
|
|
|
{"name": "archipelago", "local_port": 80, "enabled": True},
|
|
|
|
|
{"name": "bitcoin", "local_port": 8333, "enabled": True},
|
2026-03-16 12:58:35 +00:00
|
|
|
{"name": "electrumx", "local_port": 50001, "enabled": True},
|
fix: add 9 missing apps to ISO build (ISO-01)
CAPTURE_PATTERNS: added photoprism, nextcloud, nginx-proxy-manager,
immich, onlyoffice, adguard, penpot patterns.
CONTAINER_IMAGES: added jellyfin, photoprism, nextcloud,
nginx-proxy-manager, immich-server, postgres-immich, redis-immich,
onlyoffice, adguardhome with pinned versions for fallback pull.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 03:17:12 +00:00
|
|
|
{"name": "lnd", "local_port": 9735, "enabled": True},
|
|
|
|
|
{"name": "btcpay", "local_port": 23000, "enabled": True},
|
|
|
|
|
{"name": "mempool", "local_port": 4080, "enabled": True},
|
|
|
|
|
{"name": "fedimint", "local_port": 8175, "enabled": True}
|
|
|
|
|
]
|
|
|
|
|
with open("'"$ARCHY_TOR_DIR"'/services.json", "w") as f:
|
|
|
|
|
json.dump({"services": services}, f, indent=2)
|
|
|
|
|
print("services.json created")
|
|
|
|
|
'
|
|
|
|
|
|
|
|
|
|
# Generate torrc — use /var/lib/tor/ for hidden services (AppArmor-safe)
|
|
|
|
|
cat > /etc/tor/torrc <<TORRC
|
|
|
|
|
SocksPort 9050
|
|
|
|
|
ControlPort 0
|
|
|
|
|
|
|
|
|
|
HiddenServiceDir $TOR_DIR/hidden_service_archipelago
|
|
|
|
|
HiddenServicePort 80 127.0.0.1:80
|
|
|
|
|
|
|
|
|
|
HiddenServiceDir $TOR_DIR/hidden_service_bitcoin
|
|
|
|
|
HiddenServicePort 8333 127.0.0.1:8333
|
|
|
|
|
|
2026-03-16 12:58:35 +00:00
|
|
|
HiddenServiceDir $TOR_DIR/hidden_service_electrumx
|
fix: add 9 missing apps to ISO build (ISO-01)
CAPTURE_PATTERNS: added photoprism, nextcloud, nginx-proxy-manager,
immich, onlyoffice, adguard, penpot patterns.
CONTAINER_IMAGES: added jellyfin, photoprism, nextcloud,
nginx-proxy-manager, immich-server, postgres-immich, redis-immich,
onlyoffice, adguardhome with pinned versions for fallback pull.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 03:17:12 +00:00
|
|
|
HiddenServicePort 50001 127.0.0.1:50001
|
|
|
|
|
|
|
|
|
|
HiddenServiceDir $TOR_DIR/hidden_service_lnd
|
|
|
|
|
HiddenServicePort 9735 127.0.0.1:9735
|
security+feat: v1.3.0 — pentest remediation, container reliability, UI overhaul
Security (33 pentest findings addressed):
- CRITICAL: backend binds 127.0.0.1, path traversal in tor.rs/dwn fixed
- HIGH: federation requires signatures, XSS login redirect, RBAC viewer restricted
- HIGH: tar slip prevention, S3 SSRF validation, backup ID validation
- MEDIUM: remember-me random secret, TOTP session rotation, password re-auth
- LOW: CSP unsafe-inline removed, CORS dev-only, onion/webhook validation
Container reliability:
- Memory limits on all 37 containers (OOM prevention)
- Exited vs stopped state distinction with health-aware status badges
- Crash recovery coordination (no more restart cascade)
- User-stopped tracking survives reboots
- Tiered boot recovery (databases → core → services → apps)
UI:
- Wallet TransactionsModal, health-aware app status badges
- Restart button on containers, exited/crashed red state
- Mesh view overhaul, glass button updates, BaseModal/ToggleSwitch
- Apps sticky header removed, dev faucet, mutable mock wallet
Infrastructure:
- LND REST port 8080 exposed over Tor (LND Connect fix)
- Nginx cookie_session fix, deploy script Tor config updated
- Dev environment: podman auto-start, boot mode simulation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 12:44:31 +00:00
|
|
|
HiddenServicePort 8080 127.0.0.1:8080
|
fix: add 9 missing apps to ISO build (ISO-01)
CAPTURE_PATTERNS: added photoprism, nextcloud, nginx-proxy-manager,
immich, onlyoffice, adguard, penpot patterns.
CONTAINER_IMAGES: added jellyfin, photoprism, nextcloud,
nginx-proxy-manager, immich-server, postgres-immich, redis-immich,
onlyoffice, adguardhome with pinned versions for fallback pull.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 03:17:12 +00:00
|
|
|
|
|
|
|
|
HiddenServiceDir $TOR_DIR/hidden_service_btcpay
|
|
|
|
|
HiddenServicePort 23000 127.0.0.1:23000
|
|
|
|
|
|
|
|
|
|
HiddenServiceDir $TOR_DIR/hidden_service_mempool
|
|
|
|
|
HiddenServicePort 4080 127.0.0.1:4080
|
|
|
|
|
|
|
|
|
|
HiddenServiceDir $TOR_DIR/hidden_service_fedimint
|
|
|
|
|
HiddenServicePort 8175 127.0.0.1:8175
|
|
|
|
|
TORRC
|
|
|
|
|
|
|
|
|
|
# Prefer system Tor (installed via apt)
|
|
|
|
|
if command -v tor >/dev/null 2>&1; then
|
|
|
|
|
echo "$(date): Using system Tor daemon" >> "$LOG"
|
|
|
|
|
systemctl enable tor 2>/dev/null
|
|
|
|
|
systemctl restart tor@default 2>/dev/null
|
|
|
|
|
else
|
|
|
|
|
# Fallback: use container
|
|
|
|
|
echo "$(date): System Tor not found, using container" >> "$LOG"
|
|
|
|
|
DOCKER=podman
|
|
|
|
|
command -v podman >/dev/null 2>&1 || DOCKER=docker
|
|
|
|
|
for c in $(sudo $DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -E 'archy-tor|^tor$'); do
|
|
|
|
|
[ -n "$c" ] && sudo $DOCKER stop "$c" 2>/dev/null; sudo $DOCKER rm -f "$c" 2>/dev/null
|
|
|
|
|
done
|
|
|
|
|
if ! sudo $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q archy-tor; then
|
|
|
|
|
sudo $DOCKER run -d --name archy-tor --restart unless-stopped --network host \
|
|
|
|
|
-v "$TOR_DIR:$TOR_DIR" \
|
|
|
|
|
--entrypoint tor \
|
2026-03-26 14:06:21 +00:00
|
|
|
${ALPINE_TOR_IMAGE} \
|
fix: add 9 missing apps to ISO build (ISO-01)
CAPTURE_PATTERNS: added photoprism, nextcloud, nginx-proxy-manager,
immich, onlyoffice, adguard, penpot patterns.
CONTAINER_IMAGES: added jellyfin, photoprism, nextcloud,
nginx-proxy-manager, immich-server, postgres-immich, redis-immich,
onlyoffice, adguardhome with pinned versions for fallback pull.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 03:17:12 +00:00
|
|
|
-f /etc/tor/torrc >> "$LOG" 2>&1
|
2026-02-17 15:03:34 +00:00
|
|
|
echo "$(date): Tor container started" >> "$LOG"
|
|
|
|
|
fi
|
|
|
|
|
fi
|
fix: add 9 missing apps to ISO build (ISO-01)
CAPTURE_PATTERNS: added photoprism, nextcloud, nginx-proxy-manager,
immich, onlyoffice, adguard, penpot patterns.
CONTAINER_IMAGES: added jellyfin, photoprism, nextcloud,
nginx-proxy-manager, immich-server, postgres-immich, redis-immich,
onlyoffice, adguardhome with pinned versions for fallback pull.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 03:17:12 +00:00
|
|
|
|
2026-02-17 15:03:34 +00:00
|
|
|
# Wait for Tor to create hostname files (~30-60s), then chmod so archipelago user can read
|
|
|
|
|
for i in 1 2 3 4 5 6 7 8 9 10; do
|
|
|
|
|
sleep 6
|
|
|
|
|
if [ -f "$TOR_DIR/hidden_service_archipelago/hostname" ]; then
|
fix: add 9 missing apps to ISO build (ISO-01)
CAPTURE_PATTERNS: added photoprism, nextcloud, nginx-proxy-manager,
immich, onlyoffice, adguard, penpot patterns.
CONTAINER_IMAGES: added jellyfin, photoprism, nextcloud,
nginx-proxy-manager, immich-server, postgres-immich, redis-immich,
onlyoffice, adguardhome with pinned versions for fallback pull.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 03:17:12 +00:00
|
|
|
chmod 750 "$TOR_DIR"/hidden_service_*/ 2>/dev/null || true
|
2026-02-17 15:03:34 +00:00
|
|
|
for f in "$TOR_DIR"/hidden_service_*/hostname; do
|
fix: add 9 missing apps to ISO build (ISO-01)
CAPTURE_PATTERNS: added photoprism, nextcloud, nginx-proxy-manager,
immich, onlyoffice, adguard, penpot patterns.
CONTAINER_IMAGES: added jellyfin, photoprism, nextcloud,
nginx-proxy-manager, immich-server, postgres-immich, redis-immich,
onlyoffice, adguardhome with pinned versions for fallback pull.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 03:17:12 +00:00
|
|
|
[ -f "$f" ] && chmod 640 "$f" && echo "$(date): chmod hostname $f" >> "$LOG"
|
2026-02-17 15:03:34 +00:00
|
|
|
done
|
|
|
|
|
echo "$(date): Tor hostname files readable by archipelago" >> "$LOG"
|
|
|
|
|
break
|
|
|
|
|
fi
|
|
|
|
|
done
|
|
|
|
|
TORSCRIPT
|
|
|
|
|
|
|
|
|
|
chmod +x "$WORK_DIR/setup-tor.sh"
|
|
|
|
|
cp "$WORK_DIR/setup-tor.sh" "$ARCH_DIR/scripts/"
|
|
|
|
|
cp "$WORK_DIR/archipelago-setup-tor.service" "$ARCH_DIR/scripts/"
|
|
|
|
|
|
2026-02-25 18:04:41 +00:00
|
|
|
# First-boot: create core containers (bitcoin, mempool, btcpay, lnd, fedimint, homeassistant)
|
2026-03-26 09:12:16 +00:00
|
|
|
# Unbundled builds only create FileBrowser (core dependency for Cloud)
|
2026-03-10 23:29:05 +00:00
|
|
|
if [ "$UNBUNDLED" = "1" ]; then
|
2026-03-26 09:12:16 +00:00
|
|
|
echo " Creating minimal first-boot service (UNBUNDLED: FileBrowser only)..."
|
|
|
|
|
# Create a minimal first-boot script that only starts FileBrowser
|
|
|
|
|
cat > "$WORK_DIR/first-boot-containers-unbundled.sh" <<'FBUNBUNDLED'
|
|
|
|
|
#!/bin/bash
|
|
|
|
|
# Minimal first-boot: create FileBrowser container only (unbundled ISO)
|
|
|
|
|
set -e
|
|
|
|
|
DOCKER="podman"
|
|
|
|
|
LOG="/var/log/archipelago-first-boot.log"
|
|
|
|
|
echo "[$(date)] Starting minimal first-boot (unbundled)..." >> "$LOG"
|
|
|
|
|
|
|
|
|
|
# Create Cloud storage directories
|
|
|
|
|
mkdir -p /var/lib/archipelago/filebrowser
|
|
|
|
|
mkdir -p /var/lib/archipelago/data/cloud/{Documents,Photos,Music,Videos,Downloads}
|
|
|
|
|
chown -R 1000:1000 /var/lib/archipelago/filebrowser
|
|
|
|
|
chown -R 1000:1000 /var/lib/archipelago/data
|
|
|
|
|
|
|
|
|
|
if ! $DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -q filebrowser; then
|
|
|
|
|
echo "[$(date)] Creating FileBrowser container..." >> "$LOG"
|
|
|
|
|
$DOCKER run -d --name filebrowser --restart unless-stopped \
|
|
|
|
|
--health-cmd="curl -sf http://localhost:80/ || exit 1" \
|
|
|
|
|
--health-interval=30s --health-timeout=5s --health-retries=3 \
|
|
|
|
|
--memory=256m \
|
|
|
|
|
-p 8083:80 \
|
|
|
|
|
-v /var/lib/archipelago/filebrowser:/srv \
|
2026-03-26 14:06:21 +00:00
|
|
|
"$FILEBROWSER_IMAGE" 2>>"$LOG" && \
|
2026-03-26 09:12:16 +00:00
|
|
|
echo "[$(date)] FileBrowser created successfully" >> "$LOG" || \
|
|
|
|
|
echo "[$(date)] WARNING: FileBrowser creation failed" >> "$LOG"
|
|
|
|
|
fi
|
|
|
|
|
echo "[$(date)] Minimal first-boot complete" >> "$LOG"
|
|
|
|
|
FBUNBUNDLED
|
|
|
|
|
chmod +x "$WORK_DIR/first-boot-containers-unbundled.sh"
|
|
|
|
|
cp "$WORK_DIR/first-boot-containers-unbundled.sh" "$ARCH_DIR/scripts/first-boot-containers.sh"
|
|
|
|
|
|
|
|
|
|
cat > "$WORK_DIR/archipelago-first-boot-containers.service" <<'FBCSERVICE'
|
|
|
|
|
[Unit]
|
|
|
|
|
Description=Create core Archipelago containers on first boot
|
|
|
|
|
After=archipelago-setup-tor.service network-online.target podman.service
|
|
|
|
|
ConditionPathExists=/opt/archipelago/scripts/first-boot-containers.sh
|
|
|
|
|
ConditionPathExists=!/var/lib/archipelago/.first-boot-containers-done
|
|
|
|
|
|
|
|
|
|
[Service]
|
|
|
|
|
Type=oneshot
|
|
|
|
|
ExecStart=/opt/archipelago/scripts/first-boot-containers.sh
|
|
|
|
|
ExecStartPost=/usr/bin/touch /var/lib/archipelago/.first-boot-containers-done
|
|
|
|
|
RemainAfterExit=yes
|
|
|
|
|
|
|
|
|
|
[Install]
|
|
|
|
|
WantedBy=multi-user.target
|
|
|
|
|
FBCSERVICE
|
|
|
|
|
cp "$WORK_DIR/archipelago-first-boot-containers.service" "$ARCH_DIR/scripts/"
|
2026-03-10 23:29:05 +00:00
|
|
|
else
|
|
|
|
|
echo " Creating first-boot container creation service..."
|
2026-03-21 03:06:29 +00:00
|
|
|
# Copy shared script library
|
|
|
|
|
if [ -d "$SCRIPT_DIR/../scripts/lib" ]; then
|
|
|
|
|
mkdir -p "$ARCH_DIR/scripts/lib"
|
|
|
|
|
cp "$SCRIPT_DIR/../scripts/lib/"*.sh "$ARCH_DIR/scripts/lib/" 2>/dev/null || true
|
|
|
|
|
fi
|
2026-03-10 23:29:05 +00:00
|
|
|
if [ -f "$SCRIPT_DIR/../scripts/first-boot-containers.sh" ]; then
|
|
|
|
|
cp "$SCRIPT_DIR/../scripts/first-boot-containers.sh" "$ARCH_DIR/scripts/"
|
|
|
|
|
chmod +x "$ARCH_DIR/scripts/first-boot-containers.sh"
|
|
|
|
|
cat > "$WORK_DIR/archipelago-first-boot-containers.service" <<'FBCSERVICE'
|
2026-02-25 18:04:41 +00:00
|
|
|
[Unit]
|
|
|
|
|
Description=Create core Archipelago containers on first boot
|
|
|
|
|
After=archipelago-setup-tor.service network-online.target podman.service
|
|
|
|
|
ConditionPathExists=/opt/archipelago/scripts/first-boot-containers.sh
|
|
|
|
|
ConditionPathExists=!/var/lib/archipelago/.first-boot-containers-done
|
|
|
|
|
|
|
|
|
|
[Service]
|
|
|
|
|
Type=oneshot
|
|
|
|
|
ExecStart=/opt/archipelago/scripts/first-boot-containers.sh
|
|
|
|
|
ExecStartPost=/usr/bin/touch /var/lib/archipelago/.first-boot-containers-done
|
|
|
|
|
RemainAfterExit=yes
|
|
|
|
|
|
|
|
|
|
[Install]
|
|
|
|
|
WantedBy=multi-user.target
|
|
|
|
|
FBCSERVICE
|
2026-03-10 23:29:05 +00:00
|
|
|
cp "$WORK_DIR/archipelago-first-boot-containers.service" "$ARCH_DIR/scripts/"
|
|
|
|
|
fi
|
2026-02-25 18:04:41 +00:00
|
|
|
fi
|
|
|
|
|
|
2026-03-09 17:09:59 +00:00
|
|
|
# Bundle E2E test script for post-install validation
|
|
|
|
|
if [ -f "$SCRIPT_DIR/../scripts/run-e2e-tests.sh" ]; then
|
|
|
|
|
cp "$SCRIPT_DIR/../scripts/run-e2e-tests.sh" "$ARCH_DIR/scripts/"
|
|
|
|
|
chmod +x "$ARCH_DIR/scripts/run-e2e-tests.sh"
|
|
|
|
|
echo " ✅ Bundled E2E test script for post-install validation"
|
|
|
|
|
fi
|
|
|
|
|
|
2026-03-25 15:52:26 +00:00
|
|
|
# Bundle self-update script and image-versions for update system
|
|
|
|
|
if [ -f "$SCRIPT_DIR/../scripts/self-update.sh" ]; then
|
|
|
|
|
cp "$SCRIPT_DIR/../scripts/self-update.sh" "$ARCH_DIR/scripts/"
|
|
|
|
|
chmod +x "$ARCH_DIR/scripts/self-update.sh"
|
|
|
|
|
echo " ✅ Bundled self-update script"
|
|
|
|
|
fi
|
|
|
|
|
if [ -f "$SCRIPT_DIR/../scripts/image-versions.sh" ]; then
|
|
|
|
|
cp "$SCRIPT_DIR/../scripts/image-versions.sh" "$ARCH_DIR/scripts/"
|
|
|
|
|
echo " ✅ Bundled image-versions.sh"
|
|
|
|
|
fi
|
|
|
|
|
|
2026-03-08 02:27:58 +00:00
|
|
|
# Bundle docker UI source files for building custom UIs on first boot (fallback if images not captured)
|
2026-03-10 23:29:05 +00:00
|
|
|
# Skip for unbundled builds
|
|
|
|
|
if [ "$UNBUNDLED" != "1" ]; then
|
|
|
|
|
DOCKER_UI_DIR="$SCRIPT_DIR/../docker"
|
|
|
|
|
if [ -d "$DOCKER_UI_DIR" ]; then
|
|
|
|
|
echo " Bundling docker UI source files..."
|
|
|
|
|
mkdir -p "$ARCH_DIR/docker"
|
|
|
|
|
for ui_dir in bitcoin-ui lnd-ui electrs-ui; do
|
|
|
|
|
if [ -d "$DOCKER_UI_DIR/$ui_dir" ]; then
|
|
|
|
|
cp -r "$DOCKER_UI_DIR/$ui_dir" "$ARCH_DIR/docker/"
|
|
|
|
|
echo " ✅ Bundled $ui_dir source"
|
|
|
|
|
fi
|
|
|
|
|
done
|
|
|
|
|
fi
|
2026-03-08 02:27:58 +00:00
|
|
|
fi
|
|
|
|
|
|
2026-03-10 23:29:05 +00:00
|
|
|
if [ "$UNBUNDLED" = "1" ]; then
|
|
|
|
|
echo " ✅ Unbundled build ready (Tor setup included, no container images)"
|
|
|
|
|
else
|
|
|
|
|
echo " ✅ Container images bundled (including Tor + first-boot)"
|
|
|
|
|
fi
|
2026-02-01 05:42:05 +00:00
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
|
|
# 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
|
|
|
|
|
|
feat: add build report and first-boot diagnostics
CI build report: checks rootfs contents (nginx, SSL, keyboard, kiosk,
lid config, backend, frontend) and ISO contents after build. Reports
in the Actions log so build issues are immediately visible.
First-boot diagnostics: one-shot systemd service runs 30s after first
boot, logs service status, nginx test, SSL certs, LUKS, podman,
kiosk, console-setup, disk, network, and journal errors to
/var/log/archipelago-first-boot-diag.log. Only runs once (ConditionPathExists).
SSH in and cat the log to debug any fresh install issues.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 20:54:32 +00:00
|
|
|
# Log everything to a file on the target disk (after mount) and to console
|
|
|
|
|
INSTALL_LOG="/tmp/archipelago-install.log"
|
|
|
|
|
exec > >(tee -a "$INSTALL_LOG") 2>&1
|
|
|
|
|
|
2026-03-21 01:11:05 +00:00
|
|
|
# Detect architecture at install time
|
|
|
|
|
case "$(uname -m)" in
|
|
|
|
|
x86_64|amd64)
|
|
|
|
|
ARCH="x86_64"
|
|
|
|
|
GRUB_TARGET="x86_64-efi"
|
|
|
|
|
GRUB_BIOS_TARGET="i386-pc"
|
|
|
|
|
;;
|
|
|
|
|
aarch64|arm64)
|
|
|
|
|
ARCH="arm64"
|
|
|
|
|
GRUB_TARGET="arm64-efi"
|
|
|
|
|
GRUB_BIOS_TARGET=""
|
|
|
|
|
;;
|
|
|
|
|
esac
|
|
|
|
|
|
2026-03-25 17:03:21 +00:00
|
|
|
# Colors (use $'...' syntax for reliable escape code interpretation)
|
|
|
|
|
RED=$'\033[0;31m'
|
|
|
|
|
GREEN=$'\033[0;32m'
|
|
|
|
|
YELLOW=$'\033[1;33m'
|
|
|
|
|
BLUE=$'\033[0;34m'
|
|
|
|
|
NC=$'\033[0m'
|
2026-02-01 05:42:05 +00:00
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
2026-03-26 09:12:16 +00:00
|
|
|
# Create partition table — dual BIOS+UEFI boot + LUKS2 encrypted data
|
|
|
|
|
echo " [1/7] Creating partitions..."
|
2026-02-01 05:42:05 +00:00
|
|
|
parted -s "$TARGET_DISK" mklabel gpt
|
2026-03-21 01:11:05 +00:00
|
|
|
# Partition 1: 1MB BIOS boot partition (for legacy BIOS GRUB on GPT disks)
|
|
|
|
|
parted -s "$TARGET_DISK" mkpart bios_boot 1MiB 2MiB
|
|
|
|
|
parted -s "$TARGET_DISK" set 1 bios_grub on
|
|
|
|
|
# Partition 2: 512MB EFI System Partition (for UEFI boot)
|
|
|
|
|
parted -s "$TARGET_DISK" mkpart efi fat32 2MiB 514MiB
|
|
|
|
|
parted -s "$TARGET_DISK" set 2 esp on
|
2026-03-26 09:12:16 +00:00
|
|
|
# Partition 3: Root filesystem (30GB — system, packages, container runtime)
|
|
|
|
|
parted -s "$TARGET_DISK" mkpart root ext4 514MiB 30GiB
|
|
|
|
|
# Partition 4: Encrypted data (LUKS2 — Bitcoin data, secrets, app volumes)
|
|
|
|
|
parted -s "$TARGET_DISK" mkpart data 30GiB 100%
|
2026-02-01 05:42:05 +00:00
|
|
|
|
|
|
|
|
sleep 2
|
|
|
|
|
|
|
|
|
|
# Determine partition names
|
|
|
|
|
if [[ "$TARGET_DISK" == *nvme* ]]; then
|
2026-03-21 01:11:05 +00:00
|
|
|
BIOS_PART="${TARGET_DISK}p1"
|
|
|
|
|
EFI_PART="${TARGET_DISK}p2"
|
|
|
|
|
ROOT_PART="${TARGET_DISK}p3"
|
2026-03-26 09:12:16 +00:00
|
|
|
DATA_PART="${TARGET_DISK}p4"
|
2026-02-01 05:42:05 +00:00
|
|
|
else
|
2026-03-21 01:11:05 +00:00
|
|
|
BIOS_PART="${TARGET_DISK}1"
|
|
|
|
|
EFI_PART="${TARGET_DISK}2"
|
|
|
|
|
ROOT_PART="${TARGET_DISK}3"
|
2026-03-26 09:12:16 +00:00
|
|
|
DATA_PART="${TARGET_DISK}4"
|
2026-02-01 05:42:05 +00:00
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Format partitions
|
2026-03-26 09:12:16 +00:00
|
|
|
echo " [2/7] Formatting partitions..."
|
2026-03-25 18:25:01 +00:00
|
|
|
# Zero out the BIOS boot partition to prevent FAT-fs read errors during boot
|
|
|
|
|
dd if=/dev/zero of="$BIOS_PART" bs=1M count=1 2>/dev/null || true
|
2026-02-01 05:42:05 +00:00
|
|
|
mkfs.vfat -F32 -n EFI "$EFI_PART"
|
|
|
|
|
mkfs.ext4 -F -L archipelago "$ROOT_PART"
|
|
|
|
|
|
2026-03-26 09:12:16 +00:00
|
|
|
# Mount root + extract rootfs (need cryptsetup from rootfs for LUKS)
|
|
|
|
|
echo " [3/7] Mounting filesystems..."
|
2026-02-01 05:42:05 +00:00
|
|
|
mkdir -p /mnt/target
|
|
|
|
|
mount "$ROOT_PART" /mnt/target
|
|
|
|
|
mkdir -p /mnt/target/boot/efi
|
|
|
|
|
mount "$EFI_PART" /mnt/target/boot/efi
|
|
|
|
|
|
2026-03-26 09:12:16 +00:00
|
|
|
echo " [4/7] Installing system (this may take a few minutes)..."
|
2026-02-01 05:42:05 +00:00
|
|
|
tar -xf "$ROOTFS_TAR" -C /mnt/target
|
|
|
|
|
|
2026-03-26 09:12:16 +00:00
|
|
|
# LUKS2 encryption for data partition
|
|
|
|
|
echo " [5/7] Encrypting data partition (LUKS2)..."
|
|
|
|
|
|
|
|
|
|
# Generate random 4KB key file
|
|
|
|
|
dd if=/dev/urandom of=/mnt/target/root/.luks-archipelago.key bs=4096 count=1 2>/dev/null
|
|
|
|
|
chmod 600 /mnt/target/root/.luks-archipelago.key
|
|
|
|
|
|
2026-03-26 17:28:08 +00:00
|
|
|
# Load dm_mod kernel module (required for device-mapper / LUKS)
|
|
|
|
|
modprobe dm_mod 2>/dev/null || true
|
|
|
|
|
modprobe dm_crypt 2>/dev/null || true
|
|
|
|
|
|
|
|
|
|
# Bind-mount /dev, /proc, /sys so cryptsetup works in chroot
|
2026-03-26 09:12:16 +00:00
|
|
|
mount --bind /dev /mnt/target/dev
|
2026-03-26 17:28:08 +00:00
|
|
|
mount --bind /proc /mnt/target/proc
|
|
|
|
|
mount --bind /sys /mnt/target/sys
|
2026-03-26 09:12:16 +00:00
|
|
|
|
|
|
|
|
# Detect AES-NI support for cipher selection
|
|
|
|
|
if grep -q aes /proc/cpuinfo 2>/dev/null; then
|
|
|
|
|
LUKS_CIPHER="aes-xts-plain64"
|
|
|
|
|
echo " AES-NI detected — using AES-256-XTS"
|
|
|
|
|
else
|
|
|
|
|
LUKS_CIPHER="xchacha20,aes-adiantum-plain64"
|
|
|
|
|
echo " No AES-NI — using ChaCha20-Adiantum"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Format LUKS2 partition with key file
|
|
|
|
|
chroot /mnt/target cryptsetup luksFormat --type luks2 \
|
|
|
|
|
--key-file /root/.luks-archipelago.key \
|
|
|
|
|
--cipher "$LUKS_CIPHER" --key-size 512 \
|
|
|
|
|
--pbkdf argon2id --batch-mode \
|
|
|
|
|
"$DATA_PART"
|
|
|
|
|
|
|
|
|
|
# Open the LUKS volume
|
|
|
|
|
chroot /mnt/target cryptsetup open --type luks2 \
|
|
|
|
|
--key-file /root/.luks-archipelago.key \
|
|
|
|
|
"$DATA_PART" archipelago-data
|
|
|
|
|
|
2026-03-26 17:28:08 +00:00
|
|
|
# Unmount chroot bind mounts (will be re-mounted later for grub-install)
|
|
|
|
|
umount /mnt/target/sys 2>/dev/null || true
|
|
|
|
|
umount /mnt/target/proc 2>/dev/null || true
|
2026-03-26 09:12:16 +00:00
|
|
|
umount /mnt/target/dev
|
|
|
|
|
|
|
|
|
|
# Format the inner filesystem
|
|
|
|
|
mkfs.ext4 -F -L archipelago-data /dev/mapper/archipelago-data
|
|
|
|
|
|
|
|
|
|
# Mount encrypted partition
|
|
|
|
|
mkdir -p /mnt/target/var/lib/archipelago
|
|
|
|
|
mount /dev/mapper/archipelago-data /mnt/target/var/lib/archipelago
|
|
|
|
|
|
|
|
|
|
# Recreate directory structure on encrypted partition
|
|
|
|
|
mkdir -p /mnt/target/var/lib/archipelago/{data,config,containers,secrets,tor,identities,lnd}
|
|
|
|
|
mkdir -p /mnt/target/var/lib/archipelago/data/cloud/{Documents,Photos,Music,Videos,Downloads}
|
|
|
|
|
chown -R 1000:1000 /mnt/target/var/lib/archipelago
|
|
|
|
|
|
|
|
|
|
echo " ✅ Data partition encrypted with LUKS2 ($LUKS_CIPHER)"
|
|
|
|
|
|
|
|
|
|
# Configure auto-unlock via crypttab (key file on root partition)
|
|
|
|
|
echo " [6/7] Configuring system..."
|
|
|
|
|
DATA_UUID=$(blkid -s UUID -o value "$DATA_PART")
|
|
|
|
|
echo "# LUKS2 encrypted data — auto-unlock with key file" > /mnt/target/etc/crypttab
|
|
|
|
|
echo "archipelago-data UUID=$DATA_UUID /root/.luks-archipelago.key luks,discard" >> /mnt/target/etc/crypttab
|
|
|
|
|
|
2026-02-01 05:42:05 +00:00
|
|
|
# Create fstab
|
|
|
|
|
cat > /mnt/target/etc/fstab <<EOF
|
|
|
|
|
# Archipelago Bitcoin Node OS
|
2026-03-26 09:12:16 +00:00
|
|
|
UUID=$(blkid -s UUID -o value "$ROOT_PART") / ext4 errors=remount-ro 0 1
|
|
|
|
|
UUID=$(blkid -s UUID -o value "$EFI_PART") /boot/efi vfat umask=0077 0 1
|
|
|
|
|
/dev/mapper/archipelago-data /var/lib/archipelago ext4 defaults 0 2
|
2026-02-01 05:42:05 +00:00
|
|
|
EOF
|
|
|
|
|
|
|
|
|
|
# Configure hostname
|
|
|
|
|
echo "archipelago" > /mnt/target/etc/hostname
|
|
|
|
|
cat > /mnt/target/etc/hosts <<EOF
|
|
|
|
|
127.0.0.1 localhost
|
|
|
|
|
127.0.1.1 archipelago
|
|
|
|
|
::1 localhost ip6-localhost ip6-loopback
|
|
|
|
|
EOF
|
|
|
|
|
|
2026-03-26 10:46:26 +00:00
|
|
|
# Configure Archipelago app registry (HTTP, insecure)
|
|
|
|
|
mkdir -p /mnt/target/home/archipelago/.config/containers
|
|
|
|
|
cat > /mnt/target/home/archipelago/.config/containers/registries.conf <<'REGCONF'
|
|
|
|
|
[[registry]]
|
|
|
|
|
location = "80.71.235.15:3000"
|
|
|
|
|
insecure = true
|
|
|
|
|
REGCONF
|
|
|
|
|
chown -R 1000:1000 /mnt/target/home/archipelago/.config
|
|
|
|
|
|
2026-03-26 15:58:16 +00:00
|
|
|
# Laptop support: ignore lid close so server keeps running
|
|
|
|
|
mkdir -p /mnt/target/etc/systemd/logind.conf.d
|
|
|
|
|
cat > /mnt/target/etc/systemd/logind.conf.d/lid-ignore.conf <<'LIDCONF'
|
|
|
|
|
[Login]
|
|
|
|
|
HandleLidSwitch=ignore
|
|
|
|
|
HandleLidSwitchExternalPower=ignore
|
|
|
|
|
HandleLidSwitchDocked=ignore
|
|
|
|
|
LIDCONF
|
|
|
|
|
|
2026-02-01 05:42:05 +00:00
|
|
|
# Copy Archipelago binaries and files
|
|
|
|
|
if [ -d "$BOOT_MEDIA/archipelago/bin" ]; then
|
|
|
|
|
cp -r "$BOOT_MEDIA/archipelago/bin/"* /mnt/target/usr/local/bin/ 2>/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
|
2026-02-17 15:03:34 +00:00
|
|
|
if [ -f "$BOOT_MEDIA/archipelago/scripts/setup-tor.sh" ]; then
|
|
|
|
|
cp "$BOOT_MEDIA/archipelago/scripts/setup-tor.sh" /mnt/target/opt/archipelago/scripts/
|
|
|
|
|
chmod +x /mnt/target/opt/archipelago/scripts/setup-tor.sh
|
|
|
|
|
fi
|
|
|
|
|
if [ -d "$BOOT_MEDIA/archipelago/scripts/tor" ]; then
|
|
|
|
|
mkdir -p /mnt/target/opt/archipelago/scripts/tor
|
|
|
|
|
cp -r "$BOOT_MEDIA/archipelago/scripts/tor/"* /mnt/target/opt/archipelago/scripts/tor/ 2>/dev/null || true
|
|
|
|
|
fi
|
|
|
|
|
if [ -f "$BOOT_MEDIA/archipelago/scripts/archipelago-setup-tor.service" ]; then
|
|
|
|
|
cp "$BOOT_MEDIA/archipelago/scripts/archipelago-setup-tor.service" /mnt/target/etc/systemd/system/
|
|
|
|
|
fi
|
2026-03-21 03:06:29 +00:00
|
|
|
# Copy shared script library
|
|
|
|
|
if [ -d "$BOOT_MEDIA/archipelago/scripts/lib" ]; then
|
|
|
|
|
mkdir -p /mnt/target/opt/archipelago/scripts/lib
|
|
|
|
|
cp -r "$BOOT_MEDIA/archipelago/scripts/lib/"* /mnt/target/opt/archipelago/scripts/lib/ 2>/dev/null || true
|
|
|
|
|
fi
|
2026-02-25 18:04:41 +00:00
|
|
|
if [ -f "$BOOT_MEDIA/archipelago/scripts/first-boot-containers.sh" ]; then
|
|
|
|
|
cp "$BOOT_MEDIA/archipelago/scripts/first-boot-containers.sh" /mnt/target/opt/archipelago/scripts/
|
|
|
|
|
chmod +x /mnt/target/opt/archipelago/scripts/first-boot-containers.sh
|
|
|
|
|
fi
|
|
|
|
|
if [ -f "$BOOT_MEDIA/archipelago/scripts/archipelago-first-boot-containers.service" ]; then
|
|
|
|
|
cp "$BOOT_MEDIA/archipelago/scripts/archipelago-first-boot-containers.service" /mnt/target/etc/systemd/system/
|
|
|
|
|
fi
|
2026-03-08 02:27:58 +00:00
|
|
|
# Copy docker UI source files for first-boot container builds
|
|
|
|
|
if [ -d "$BOOT_MEDIA/archipelago/docker" ]; then
|
|
|
|
|
mkdir -p /mnt/target/opt/archipelago/docker
|
|
|
|
|
cp -r "$BOOT_MEDIA/archipelago/docker/"* /mnt/target/opt/archipelago/docker/ 2>/dev/null || true
|
|
|
|
|
fi
|
|
|
|
|
|
2026-02-01 05:42:05 +00:00
|
|
|
echo " ✅ Container images staged for first-boot loading"
|
|
|
|
|
fi
|
|
|
|
|
|
2026-03-09 17:09:59 +00:00
|
|
|
# Initialize backend data directories for seamless first boot
|
|
|
|
|
mkdir -p /mnt/target/var/lib/archipelago/tor-config
|
|
|
|
|
mkdir -p /mnt/target/var/lib/archipelago/identities
|
|
|
|
|
mkdir -p /mnt/target/var/lib/archipelago/lnd
|
|
|
|
|
|
|
|
|
|
# Copy E2E test script for post-install validation
|
|
|
|
|
if [ -f "$BOOT_MEDIA/archipelago/scripts/run-e2e-tests.sh" ]; then
|
|
|
|
|
cp "$BOOT_MEDIA/archipelago/scripts/run-e2e-tests.sh" /mnt/target/opt/archipelago/scripts/
|
|
|
|
|
chmod +x /mnt/target/opt/archipelago/scripts/run-e2e-tests.sh
|
|
|
|
|
fi
|
|
|
|
|
|
2026-03-25 15:52:26 +00:00
|
|
|
# Copy self-update script
|
|
|
|
|
if [ -f "$BOOT_MEDIA/archipelago/scripts/self-update.sh" ]; then
|
|
|
|
|
cp "$BOOT_MEDIA/archipelago/scripts/self-update.sh" /mnt/target/opt/archipelago/scripts/
|
|
|
|
|
chmod +x /mnt/target/opt/archipelago/scripts/self-update.sh
|
|
|
|
|
# Also place in home for the update timer to find
|
|
|
|
|
mkdir -p /mnt/target/home/archipelago/archy/scripts
|
|
|
|
|
cp "$BOOT_MEDIA/archipelago/scripts/self-update.sh" /mnt/target/home/archipelago/archy/scripts/
|
|
|
|
|
chmod +x /mnt/target/home/archipelago/archy/scripts/self-update.sh
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Clone repo for git-based updates (first-boot will have network)
|
|
|
|
|
# Create a script that runs on first boot to clone the repo
|
|
|
|
|
cat > /mnt/target/opt/archipelago/scripts/setup-git-updates.sh <<'GITSETUP'
|
|
|
|
|
#!/bin/bash
|
|
|
|
|
# Clone the Archipelago repo for git-based self-updates
|
|
|
|
|
REPO_DIR="/home/archipelago/archy"
|
|
|
|
|
if [ -d "$REPO_DIR/.git" ]; then
|
|
|
|
|
exit 0 # Already cloned
|
|
|
|
|
fi
|
|
|
|
|
echo "[update] Cloning Archipelago repo for self-updates..."
|
|
|
|
|
su - archipelago -c "git clone https://git.tx1138.com/lfg2025/archy $REPO_DIR" 2>/dev/null || {
|
|
|
|
|
echo "[update] Git clone failed (network?). Updates will retry on next boot."
|
|
|
|
|
exit 0
|
|
|
|
|
}
|
|
|
|
|
chown -R 1000:1000 "$REPO_DIR"
|
|
|
|
|
echo "[update] Repo cloned. Self-updates enabled."
|
|
|
|
|
GITSETUP
|
|
|
|
|
chmod +x /mnt/target/opt/archipelago/scripts/setup-git-updates.sh
|
|
|
|
|
|
2026-02-01 05:42:05 +00:00
|
|
|
# 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
|
2026-03-21 01:11:05 +00:00
|
|
|
# Ensure /sbin and /usr/sbin are in PATH (needed for reboot, shutdown, etc.)
|
|
|
|
|
case ":$PATH:" in
|
|
|
|
|
*:/sbin:*) ;; *) export PATH="$PATH:/sbin:/usr/sbin" ;;
|
|
|
|
|
esac
|
2026-02-01 05:42:05 +00:00
|
|
|
if [ -t 0 ] && [ -z "$ARCHIPELAGO_WELCOMED" ]; then
|
|
|
|
|
export ARCHIPELAGO_WELCOMED=1
|
|
|
|
|
IP=$(hostname -I 2>/dev/null | awk '{print $1}')
|
|
|
|
|
echo ""
|
2026-02-14 16:44:20 +00:00
|
|
|
echo " _"
|
|
|
|
|
echo " ,--.\\\`-. __"
|
|
|
|
|
echo " _,.\\\`. \\:/,\" \\\`-._"
|
|
|
|
|
echo " ,-*\" _,.-;-*\\\`-.+\"*._ )"
|
|
|
|
|
echo " ( ,.\"* ,-\" / \\\`. \\\\. \\\`."
|
|
|
|
|
echo " ,\" ,;\" ,\"\\../\\ \\: \\"
|
|
|
|
|
echo " ( ,\"/ / \\\\.,' : )) /"
|
|
|
|
|
echo " \\ |/ / \\\\.,' / // ,'"
|
|
|
|
|
echo " \\_)\\ ,' \\\\.,' ( / )/"
|
|
|
|
|
echo " \\\` \\._,' \\\`\""
|
|
|
|
|
echo " \\..\/"
|
|
|
|
|
echo " \\..\/"
|
|
|
|
|
echo " ~ ~\\..\/ ~~ ~~"
|
|
|
|
|
echo " ~~ ~~ \\..\/ ~~ ~ ~~"
|
|
|
|
|
echo " ~~ ~ ~~ __...---\\../-...__ ~~~ ~~"
|
|
|
|
|
echo " ~~~~ ~_,--' \\..\/ \\\`--.__ ~~ ~~"
|
|
|
|
|
echo " ~~~ __,--' \\\`\" \\\`--.__ ~~~"
|
|
|
|
|
echo "~~ ,--' \\\`--."
|
|
|
|
|
echo " '------......______ ______......------\\\` ~~"
|
|
|
|
|
echo " ~~~ ~ ~~ ~ \\\`\\\`\\\`\\\`\\\`---\"\"\"\"\" ~~ ~ ~~"
|
|
|
|
|
echo " ~~~~ ~~ ~~~~ ~~~~~~ ~ ~~ ~~ ~~~ ~"
|
|
|
|
|
echo ""
|
|
|
|
|
echo " █████╗ ██████╗ ██████╗██╗ ██╗██╗██████╗ ███████╗██╗ █████╗ ██████╗ ██████╗ "
|
|
|
|
|
echo " ██╔══██╗██╔══██╗██╔════╝██║ ██║██║██╔══██╗██╔════╝██║ ██╔══██╗██╔════╝ ██╔═══██╗"
|
|
|
|
|
echo " ███████║██████╔╝██║ ███████║██║██████╔╝█████╗ ██║ ███████║██║ ███╗██║ ██║"
|
|
|
|
|
echo " ██╔══██║██╔══██╗██║ ██╔══██║██║██╔═══╝ ██╔══╝ ██║ ██╔══██║██║ ██║██║ ██║"
|
|
|
|
|
echo " ██║ ██║██║ ██║╚██████╗██║ ██║██║██║ ███████╗███████╗██║ ██║╚██████╔╝╚██████╔╝"
|
|
|
|
|
echo " ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝╚═╝╚═╝ ╚══════╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ "
|
|
|
|
|
echo ""
|
|
|
|
|
echo " 🏝️ BITCOIN NODE OS 🏝️"
|
2026-02-01 05:42:05 +00:00
|
|
|
echo ""
|
|
|
|
|
if [ -n "$IP" ]; then
|
|
|
|
|
echo " 🌐 Web UI: http://$IP"
|
|
|
|
|
echo " 📡 SSH: ssh archipelago@$IP"
|
|
|
|
|
echo " 🔑 Password: archipelago (SSH) / password123 (Web UI)"
|
|
|
|
|
echo ""
|
|
|
|
|
fi
|
2026-03-26 09:12:16 +00:00
|
|
|
if [ -b /dev/mapper/archipelago-data ]; then
|
|
|
|
|
echo " 🔒 Storage: LUKS2 encrypted"
|
|
|
|
|
fi
|
2026-03-26 18:30:13 +00:00
|
|
|
echo " 📺 Display:"
|
2026-03-26 09:12:16 +00:00
|
|
|
if systemctl is-active archipelago-kiosk.service >/dev/null 2>&1; then
|
2026-03-26 18:30:13 +00:00
|
|
|
echo " Kiosk ACTIVE — Ctrl+Alt+F1 for terminal"
|
2026-03-26 09:12:16 +00:00
|
|
|
else
|
2026-03-26 18:30:13 +00:00
|
|
|
echo " Console — Ctrl+Alt+F7 for kiosk"
|
2026-03-26 09:12:16 +00:00
|
|
|
fi
|
2026-03-26 18:30:13 +00:00
|
|
|
echo " Commands: sudo archipelago-kiosk enable|disable|toggle"
|
2026-03-26 09:12:16 +00:00
|
|
|
echo ""
|
2026-02-01 05:42:05 +00:00
|
|
|
fi
|
|
|
|
|
PROFILE
|
|
|
|
|
chmod +x /mnt/target/etc/profile.d/archipelago.sh
|
|
|
|
|
|
2026-02-25 18:20:50 +00:00
|
|
|
# Systemd service: User=root required for Podman container access
|
2026-02-01 05:42:05 +00:00
|
|
|
cat > /mnt/target/etc/systemd/system/archipelago.service <<'SERVICE'
|
|
|
|
|
[Unit]
|
|
|
|
|
Description=Archipelago Backend
|
2026-02-17 15:03:34 +00:00
|
|
|
After=network-online.target archipelago-setup-tor.service
|
2026-02-01 05:42:05 +00:00
|
|
|
Wants=network-online.target
|
|
|
|
|
|
|
|
|
|
[Service]
|
|
|
|
|
Type=simple
|
2026-02-25 18:20:50 +00:00
|
|
|
User=root
|
2026-03-19 12:55:31 +00:00
|
|
|
Environment="ARCHIPELAGO_BIND=127.0.0.1:5678"
|
2026-02-01 05:42:05 +00:00
|
|
|
Environment="ARCHIPELAGO_DEV_MODE=true"
|
2026-02-25 18:20:50 +00:00
|
|
|
ExecStartPre=/bin/bash -c 'mkdir -p /etc/archipelago && echo "ARCHIPELAGO_HOST_IP=$(hostname -I 2>/dev/null | awk \"{print \$1}\")" > /etc/archipelago/host-ip.env'
|
2026-02-01 05:42:05 +00:00
|
|
|
ExecStart=/usr/local/bin/archipelago
|
|
|
|
|
Restart=on-failure
|
|
|
|
|
RestartSec=5
|
|
|
|
|
|
|
|
|
|
[Install]
|
|
|
|
|
WantedBy=multi-user.target
|
|
|
|
|
SERVICE
|
|
|
|
|
|
2026-03-12 22:19:04 +00:00
|
|
|
# Claude API proxy — middleware that injects max_tokens, strips invalid fields
|
|
|
|
|
# API key must be set after install via setup-aiui-server.sh or manually
|
|
|
|
|
cat > /mnt/target/opt/archipelago/claude-api-proxy.py <<'CLAUDEPROXY'
|
|
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
import http.server, json, ssl, sys, os, urllib.request, urllib.error
|
|
|
|
|
API_KEY = os.environ.get("ANTHROPIC_API_KEY", "")
|
|
|
|
|
PORT = 3142
|
|
|
|
|
class Handler(http.server.BaseHTTPRequestHandler):
|
|
|
|
|
def do_POST(self):
|
|
|
|
|
if self.path == "/health":
|
|
|
|
|
self.send_response(200); self.send_header("Content-Type","application/json"); self.end_headers()
|
|
|
|
|
self.wfile.write(b'{"status":"ok"}'); return
|
|
|
|
|
cl = int(self.headers.get("Content-Length", 0))
|
|
|
|
|
body = self.rfile.read(cl)
|
|
|
|
|
try: data = json.loads(body)
|
|
|
|
|
except: data = {}
|
|
|
|
|
if "max_tokens" not in data: data["max_tokens"] = 8096
|
|
|
|
|
for f in ["webSearch","web_search"]: data.pop(f, None)
|
|
|
|
|
body = json.dumps(data).encode()
|
|
|
|
|
headers = {"Content-Type":"application/json","x-api-key":API_KEY,"anthropic-version":"2023-06-01","anthropic-dangerous-direct-browser-access":"true"}
|
|
|
|
|
for h in ["anthropic-version","anthropic-beta"]:
|
|
|
|
|
if self.headers.get(h): headers[h] = self.headers[h]
|
|
|
|
|
req = urllib.request.Request("https://api.anthropic.com"+self.path, data=body, headers=headers, method="POST")
|
|
|
|
|
try:
|
|
|
|
|
ctx = ssl.create_default_context()
|
|
|
|
|
resp = urllib.request.urlopen(req, context=ctx, timeout=300)
|
|
|
|
|
self.send_response(resp.status)
|
|
|
|
|
is_stream = "text/event-stream" in (resp.headers.get("Content-Type","") or "")
|
|
|
|
|
for k,v in resp.headers.items():
|
|
|
|
|
if k.lower() not in ("transfer-encoding","connection"): self.send_header(k,v)
|
|
|
|
|
if is_stream: self.send_header("Transfer-Encoding","chunked")
|
|
|
|
|
self.end_headers()
|
|
|
|
|
if is_stream:
|
|
|
|
|
while True:
|
|
|
|
|
chunk = resp.read(4096)
|
|
|
|
|
if not chunk: break
|
|
|
|
|
self.wfile.write(b"%x\r\n" % len(chunk)); self.wfile.write(chunk); self.wfile.write(b"\r\n"); self.wfile.flush()
|
|
|
|
|
self.wfile.write(b"0\r\n\r\n"); self.wfile.flush()
|
|
|
|
|
else: self.wfile.write(resp.read())
|
|
|
|
|
except urllib.error.HTTPError as e:
|
|
|
|
|
self.send_response(e.code); self.send_header("Content-Type","application/json"); self.end_headers(); self.wfile.write(e.read())
|
|
|
|
|
except Exception as e:
|
|
|
|
|
self.send_response(502); self.send_header("Content-Type","application/json"); self.end_headers(); self.wfile.write(json.dumps({"error":str(e)}).encode())
|
|
|
|
|
def do_GET(self):
|
|
|
|
|
if self.path == "/health":
|
|
|
|
|
self.send_response(200); self.send_header("Content-Type","application/json"); self.end_headers(); self.wfile.write(b'{"status":"ok"}')
|
|
|
|
|
else: self.send_response(404); self.end_headers()
|
|
|
|
|
def log_message(self, fmt, *args): pass
|
|
|
|
|
if not API_KEY: print("ERROR: ANTHROPIC_API_KEY not set — configure via setup-aiui-server.sh"); sys.exit(1)
|
|
|
|
|
server = http.server.HTTPServer(("127.0.0.1", PORT), Handler)
|
|
|
|
|
print(f"Claude API proxy on port {PORT}")
|
|
|
|
|
server.serve_forever()
|
|
|
|
|
CLAUDEPROXY
|
|
|
|
|
chmod +x /mnt/target/opt/archipelago/claude-api-proxy.py
|
|
|
|
|
|
|
|
|
|
# Claude API proxy systemd service (disabled by default — enabled after API key is configured)
|
|
|
|
|
cat > /mnt/target/etc/systemd/system/claude-api-proxy.service <<'CLAUDESVC'
|
|
|
|
|
[Unit]
|
|
|
|
|
Description=Claude API Proxy
|
|
|
|
|
After=network.target
|
|
|
|
|
|
|
|
|
|
[Service]
|
|
|
|
|
Type=simple
|
|
|
|
|
Environment=ANTHROPIC_API_KEY=
|
|
|
|
|
ExecStart=/usr/bin/python3 /opt/archipelago/claude-api-proxy.py
|
|
|
|
|
Restart=always
|
|
|
|
|
RestartSec=5
|
|
|
|
|
|
|
|
|
|
[Install]
|
|
|
|
|
WantedBy=multi-user.target
|
|
|
|
|
CLAUDESVC
|
|
|
|
|
|
2026-03-26 09:12:16 +00:00
|
|
|
# Kiosk mode — X11 + Chromium fullscreen on attached display
|
|
|
|
|
# Not enabled by default; toggle via: sudo archipelago-kiosk enable/disable
|
|
|
|
|
cat > /mnt/target/usr/local/bin/archipelago-kiosk-launcher <<'KIOSKLAUNCHER'
|
|
|
|
|
#!/bin/bash
|
|
|
|
|
# Start X server in the background
|
|
|
|
|
/usr/bin/Xorg :0 -nocursor vt1 -nolisten tcp -keeptty &
|
|
|
|
|
XPID=$!
|
|
|
|
|
sleep 2
|
|
|
|
|
|
|
|
|
|
if ! kill -0 $XPID 2>/dev/null; then
|
|
|
|
|
echo 'ERROR: Xorg failed to start'
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
export DISPLAY=:0
|
|
|
|
|
export HOME=/home/archipelago
|
|
|
|
|
|
|
|
|
|
xhost +SI:localuser:archipelago 2>/dev/null
|
|
|
|
|
xset s off 2>/dev/null
|
|
|
|
|
xset -dpms 2>/dev/null
|
|
|
|
|
xset s noblank 2>/dev/null
|
|
|
|
|
|
|
|
|
|
unclutter -idle 3 -root &
|
|
|
|
|
|
|
|
|
|
while true; do
|
2026-03-26 18:30:13 +00:00
|
|
|
# Get screen resolution for window sizing
|
|
|
|
|
SCREEN_RES=$(xdpyinfo 2>/dev/null | awk '/dimensions:/{print $2}')
|
|
|
|
|
SCREEN_RES=${SCREEN_RES:-1920x1080}
|
2026-03-26 09:12:16 +00:00
|
|
|
sudo -u archipelago env DISPLAY=:0 HOME=/home/archipelago chromium \
|
|
|
|
|
--kiosk \
|
2026-03-26 18:30:13 +00:00
|
|
|
--start-fullscreen \
|
|
|
|
|
--start-maximized \
|
|
|
|
|
--window-position=0,0 \
|
|
|
|
|
--window-size=${SCREEN_RES/x/,} \
|
2026-03-26 09:12:16 +00:00
|
|
|
--app=http://localhost/kiosk \
|
|
|
|
|
--noerrdialogs \
|
|
|
|
|
--disable-infobars \
|
|
|
|
|
--disable-translate \
|
|
|
|
|
--no-first-run \
|
|
|
|
|
--check-for-update-interval=31536000 \
|
|
|
|
|
--disable-features=TranslateUI \
|
|
|
|
|
--disable-session-crashed-bubble \
|
|
|
|
|
--disable-save-password-bubble \
|
|
|
|
|
--disable-suggestions-service \
|
|
|
|
|
--disable-component-update \
|
|
|
|
|
--user-data-dir=/home/archipelago/.config/chromium-kiosk
|
|
|
|
|
sleep 3
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
kill $XPID 2>/dev/null
|
|
|
|
|
KIOSKLAUNCHER
|
|
|
|
|
chmod +x /mnt/target/usr/local/bin/archipelago-kiosk-launcher
|
|
|
|
|
|
|
|
|
|
cat > /mnt/target/etc/systemd/system/archipelago-kiosk.service <<'KIOSKSVC'
|
|
|
|
|
[Unit]
|
|
|
|
|
Description=Archipelago Kiosk (X11 + Chromium)
|
|
|
|
|
After=archipelago.service
|
|
|
|
|
Wants=archipelago.service
|
|
|
|
|
ConditionPathExists=/usr/local/bin/archipelago-kiosk-launcher
|
|
|
|
|
Conflicts=getty@tty1.service
|
|
|
|
|
|
|
|
|
|
[Service]
|
|
|
|
|
Type=simple
|
|
|
|
|
ExecStartPre=/bin/bash -c 'for i in $(seq 1 15); do curl -sf http://localhost/health >/dev/null 2>&1 && exit 0; sleep 2; done; exit 0'
|
|
|
|
|
ExecStart=/usr/local/bin/archipelago-kiosk-launcher
|
|
|
|
|
TimeoutStartSec=60
|
|
|
|
|
Restart=always
|
|
|
|
|
RestartSec=5
|
|
|
|
|
|
|
|
|
|
[Install]
|
|
|
|
|
WantedBy=multi-user.target
|
|
|
|
|
KIOSKSVC
|
|
|
|
|
|
|
|
|
|
# Toggle script: sudo archipelago-kiosk enable|disable|status
|
|
|
|
|
cat > /mnt/target/usr/local/bin/archipelago-kiosk <<'KIOSKTOGGLE'
|
|
|
|
|
#!/bin/bash
|
|
|
|
|
set -e
|
|
|
|
|
|
|
|
|
|
case "${1:-status}" in
|
|
|
|
|
enable)
|
|
|
|
|
echo "Enabling kiosk mode (X11 + Chromium on display)..."
|
|
|
|
|
systemctl enable archipelago-kiosk.service
|
|
|
|
|
systemctl start archipelago-kiosk.service 2>/dev/null || true
|
|
|
|
|
echo "Kiosk mode ENABLED. Console login (tty1) is now disabled."
|
|
|
|
|
echo "To access the server, use SSH or the web UI."
|
|
|
|
|
;;
|
|
|
|
|
disable)
|
|
|
|
|
echo "Disabling kiosk mode (restoring console login)..."
|
|
|
|
|
systemctl stop archipelago-kiosk.service 2>/dev/null || true
|
|
|
|
|
systemctl disable archipelago-kiosk.service
|
|
|
|
|
systemctl restart getty@tty1.service 2>/dev/null || true
|
|
|
|
|
echo "Kiosk mode DISABLED. Console login restored on tty1."
|
|
|
|
|
;;
|
|
|
|
|
status)
|
|
|
|
|
if systemctl is-active archipelago-kiosk.service >/dev/null 2>&1; then
|
|
|
|
|
echo "Kiosk mode: ACTIVE (display showing web UI)"
|
|
|
|
|
elif systemctl is-enabled archipelago-kiosk.service >/dev/null 2>&1; then
|
|
|
|
|
echo "Kiosk mode: ENABLED (will start on next boot)"
|
|
|
|
|
else
|
|
|
|
|
echo "Kiosk mode: DISABLED (console login on tty1)"
|
|
|
|
|
fi
|
|
|
|
|
;;
|
2026-03-26 18:30:13 +00:00
|
|
|
toggle)
|
|
|
|
|
if systemctl is-active archipelago-kiosk.service >/dev/null 2>&1; then
|
|
|
|
|
systemctl stop archipelago-kiosk.service 2>/dev/null || true
|
|
|
|
|
systemctl restart getty@tty1.service 2>/dev/null || true
|
|
|
|
|
chvt 1 2>/dev/null || true
|
|
|
|
|
else
|
|
|
|
|
systemctl start archipelago-kiosk.service 2>/dev/null || true
|
|
|
|
|
fi
|
|
|
|
|
;;
|
2026-03-26 09:12:16 +00:00
|
|
|
*)
|
2026-03-26 18:30:13 +00:00
|
|
|
echo "Usage: archipelago-kiosk [enable|disable|status|toggle]"
|
2026-03-26 09:12:16 +00:00
|
|
|
echo " enable — Start kiosk (fullscreen web UI on display)"
|
|
|
|
|
echo " disable — Stop kiosk, restore console login"
|
2026-03-26 18:30:13 +00:00
|
|
|
echo " toggle — Switch between kiosk and terminal"
|
2026-03-26 09:12:16 +00:00
|
|
|
echo " status — Show current mode"
|
2026-03-26 18:30:13 +00:00
|
|
|
echo ""
|
|
|
|
|
echo "Keyboard shortcuts (from terminal):"
|
|
|
|
|
echo " Ctrl+Alt+F7 — Switch to kiosk display"
|
|
|
|
|
echo " Ctrl+Alt+F1 — Switch to terminal"
|
2026-03-26 09:12:16 +00:00
|
|
|
exit 1
|
|
|
|
|
;;
|
|
|
|
|
esac
|
|
|
|
|
KIOSKTOGGLE
|
|
|
|
|
chmod +x /mnt/target/usr/local/bin/archipelago-kiosk
|
|
|
|
|
|
2026-02-01 05:42:05 +00:00
|
|
|
# Install GRUB
|
2026-03-26 09:12:16 +00:00
|
|
|
echo " [7/7] Installing bootloader..."
|
2026-02-01 05:42:05 +00:00
|
|
|
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
|
|
|
|
|
|
2026-03-10 23:29:05 +00:00
|
|
|
# Set passwords reliably by directly editing /etc/shadow
|
|
|
|
|
# chpasswd fails silently in chroot due to missing PAM — use sed instead
|
|
|
|
|
echo " Setting user passwords..."
|
|
|
|
|
# Pre-computed SHA-512 hash of "archipelago"
|
|
|
|
|
ARCHY_HASH='$6$archipelago.salt1$QpB5VPzGHOKRVKQ5cTfd4R7PYqmMH5MUx6MxFN7MbZkxWKR3WxFp.RV4tBVbJiv.6iWXfHeq3vDph7G.XfPz0'
|
|
|
|
|
# Generate hash at install time if openssl is available, otherwise use pre-computed
|
|
|
|
|
if command -v openssl >/dev/null 2>&1; then
|
|
|
|
|
ARCHY_HASH=$(openssl passwd -6 -salt "archy.install" "archipelago")
|
|
|
|
|
fi
|
|
|
|
|
# Direct shadow file manipulation — works without PAM
|
|
|
|
|
sed -i "s|^archipelago:[^:]*:|archipelago:${ARCHY_HASH}:|" /mnt/target/etc/shadow
|
|
|
|
|
sed -i "s|^root:[^:]*:|root:${ARCHY_HASH}:|" /mnt/target/etc/shadow
|
|
|
|
|
# Verify the password was set (not locked/empty)
|
|
|
|
|
if grep -q "^archipelago:[!*]" /mnt/target/etc/shadow 2>/dev/null; then
|
|
|
|
|
echo " WARNING: Password still locked, trying chpasswd fallback..."
|
|
|
|
|
chroot /mnt/target bash -c 'echo "archipelago:archipelago" | chpasswd' 2>/dev/null || true
|
|
|
|
|
fi
|
|
|
|
|
echo " Passwords set for archipelago and root users"
|
|
|
|
|
|
2026-03-26 17:31:26 +00:00
|
|
|
# Remove shim-signed before grub-install to prevent hooks re-creating shim files
|
|
|
|
|
chroot /mnt/target dpkg --purge shim-signed shim-helpers-amd64-signed shim-helpers-arm64-signed 2>/dev/null || true
|
|
|
|
|
|
2026-03-21 01:11:05 +00:00
|
|
|
# UEFI boot: install to fallback path (/EFI/BOOT/BOOTX64.EFI) for maximum compatibility
|
|
|
|
|
echo " Installing UEFI bootloader..."
|
|
|
|
|
if chroot /mnt/target grub-install --target=${GRUB_TARGET} --efi-directory=/boot/efi --bootloader-id=archipelago --removable; then
|
|
|
|
|
echo " ✅ UEFI bootloader installed (removable/fallback path)"
|
|
|
|
|
else
|
|
|
|
|
echo " ⚠️ UEFI removable install failed, trying standard..."
|
|
|
|
|
if chroot /mnt/target grub-install --target=${GRUB_TARGET} --efi-directory=/boot/efi --bootloader-id=archipelago; then
|
|
|
|
|
echo " ✅ UEFI bootloader installed (standard)"
|
|
|
|
|
else
|
|
|
|
|
echo " ❌ UEFI bootloader installation failed"
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
|
2026-03-26 09:12:16 +00:00
|
|
|
# EFI boot: grub-install --removable places unsigned GRUB at /EFI/BOOT/BOOTX64.EFI
|
|
|
|
|
# No shim chain — Secure Boot must be disabled. shim-signed was removed from rootfs
|
|
|
|
|
# because it installs BOOTX64.CSV + shimx64.efi which cause "Failed to open \EFI\BOOT\"
|
|
|
|
|
# errors with garbled filenames on every boot.
|
2026-03-25 20:47:14 +00:00
|
|
|
echo " Verifying EFI boot files..."
|
|
|
|
|
EFI_BOOT_DIR="/mnt/target/boot/efi/EFI/BOOT"
|
2026-03-21 01:11:05 +00:00
|
|
|
if [ "$ARCH" = "x86_64" ]; then
|
|
|
|
|
EFI_BOOT_BINARY="BOOTX64.EFI"
|
|
|
|
|
else
|
|
|
|
|
EFI_BOOT_BINARY="BOOTAA64.EFI"
|
|
|
|
|
fi
|
2026-03-26 09:12:16 +00:00
|
|
|
# Remove any residual shim chain files (from grub-efi-*-signed package hooks)
|
|
|
|
|
# These cause firmware to try loading garbled vendor paths before falling back
|
|
|
|
|
for shim_file in shimx64.efi mmx64.efi fbx64.efi BOOTX64.CSV shimaa64.efi mmaa64.efi fbaa64.efi BOOTAA64.CSV; do
|
|
|
|
|
if [ -f "$EFI_BOOT_DIR/$shim_file" ] && [ "$shim_file" != "$EFI_BOOT_BINARY" ]; then
|
|
|
|
|
rm -f "$EFI_BOOT_DIR/$shim_file"
|
|
|
|
|
echo " Removed shim artifact: $shim_file"
|
|
|
|
|
fi
|
|
|
|
|
done
|
|
|
|
|
# Also remove vendor-specific EFI directory (shim creates /EFI/archipelago/)
|
|
|
|
|
rm -rf "/mnt/target/boot/efi/EFI/archipelago" 2>/dev/null || true
|
2026-03-26 17:31:26 +00:00
|
|
|
# Nuclear cleanup: remove everything except the GRUB binary from EFI/BOOT
|
|
|
|
|
if [ -d "$EFI_BOOT_DIR" ]; then
|
|
|
|
|
for f in "$EFI_BOOT_DIR"/*; do
|
|
|
|
|
[ "$(basename "$f")" = "$EFI_BOOT_BINARY" ] && continue
|
|
|
|
|
[ "$(basename "$f")" = "grub.cfg" ] && continue
|
|
|
|
|
rm -f "$f" 2>/dev/null && echo " Removed: $(basename "$f")"
|
|
|
|
|
done
|
|
|
|
|
fi
|
2026-03-25 20:47:14 +00:00
|
|
|
if [ -f "$EFI_BOOT_DIR/$EFI_BOOT_BINARY" ]; then
|
|
|
|
|
echo " ✅ UEFI boot binary present: $EFI_BOOT_DIR/$EFI_BOOT_BINARY"
|
2026-03-25 19:25:55 +00:00
|
|
|
ls -la "$EFI_BOOT_DIR/"
|
2026-03-21 01:11:05 +00:00
|
|
|
else
|
2026-03-25 20:47:14 +00:00
|
|
|
echo " ❌ Missing $EFI_BOOT_DIR/$EFI_BOOT_BINARY — boot will fail!"
|
2026-03-21 01:11:05 +00:00
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Legacy BIOS boot: only install if the installer booted in Legacy BIOS mode
|
|
|
|
|
# (if /sys/firmware/efi exists, the machine supports UEFI — no need for BIOS fallback)
|
|
|
|
|
if [ -n "${GRUB_BIOS_TARGET}" ] && [ ! -d /sys/firmware/efi ]; then
|
|
|
|
|
echo " Installing Legacy BIOS bootloader (machine booted in BIOS mode)..."
|
|
|
|
|
if chroot /mnt/target grub-install --target=${GRUB_BIOS_TARGET} "${TARGET_DISK}"; then
|
|
|
|
|
echo " ✅ Legacy BIOS bootloader installed"
|
|
|
|
|
else
|
|
|
|
|
echo " ⚠️ Legacy BIOS bootloader failed (UEFI-only boot)"
|
|
|
|
|
fi
|
|
|
|
|
elif [ -n "${GRUB_BIOS_TARGET}" ]; then
|
|
|
|
|
echo " Skipping Legacy BIOS bootloader (machine supports UEFI)"
|
|
|
|
|
fi
|
|
|
|
|
|
2026-03-25 16:51:14 +00:00
|
|
|
# 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
|
|
|
|
|
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
|
|
|
|
|
|
2026-03-25 16:56:02 +00:00
|
|
|
# Suppress os-prober warning in GRUB
|
|
|
|
|
echo "GRUB_DISABLE_OS_PROBER=true" >> /mnt/target/etc/default/grub
|
|
|
|
|
|
2026-03-21 01:11:05 +00:00
|
|
|
# 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..."
|
|
|
|
|
chroot /mnt/target update-initramfs -u -k all
|
2026-02-01 05:42:05 +00:00
|
|
|
|
|
|
|
|
chroot /mnt/target update-grub
|
|
|
|
|
|
2026-03-18 10:50:13 +00:00
|
|
|
# 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
|
|
|
|
|
echo " Installed mesh radio udev rule"
|
|
|
|
|
fi
|
|
|
|
|
|
2026-03-26 20:58:34 +00:00
|
|
|
# First-boot diagnostics — runs once, captures system state for debugging
|
|
|
|
|
cat > /mnt/target/usr/local/bin/archipelago-diagnostics <<'DIAG'
|
|
|
|
|
#!/bin/bash
|
|
|
|
|
LOG="/var/log/archipelago-first-boot-diagnostics.log"
|
|
|
|
|
echo "=== Archipelago First Boot Diagnostics ===" > "$LOG"
|
|
|
|
|
echo "Date: $(date -u)" >> "$LOG"
|
|
|
|
|
echo "Kernel: $(uname -r)" >> "$LOG"
|
|
|
|
|
echo "Hostname: $(hostname)" >> "$LOG"
|
|
|
|
|
echo "IP: $(hostname -I 2>/dev/null)" >> "$LOG"
|
|
|
|
|
echo "" >> "$LOG"
|
|
|
|
|
|
|
|
|
|
echo "=== Disk ===" >> "$LOG"
|
|
|
|
|
lsblk -o NAME,SIZE,TYPE,MOUNTPOINT,FSTYPE >> "$LOG" 2>&1
|
|
|
|
|
df -h >> "$LOG" 2>&1
|
|
|
|
|
echo "" >> "$LOG"
|
|
|
|
|
|
|
|
|
|
echo "=== LUKS ===" >> "$LOG"
|
|
|
|
|
ls -la /dev/mapper/archipelago-data 2>&1 >> "$LOG"
|
|
|
|
|
cryptsetup status archipelago-data >> "$LOG" 2>&1 || echo "No LUKS" >> "$LOG"
|
|
|
|
|
echo "" >> "$LOG"
|
|
|
|
|
|
|
|
|
|
echo "=== Services ===" >> "$LOG"
|
|
|
|
|
for svc in nginx archipelago archipelago-kiosk archipelago-load-images \
|
|
|
|
|
archipelago-first-boot-containers archipelago-setup-tor \
|
|
|
|
|
console-setup; do
|
|
|
|
|
STATUS=$(systemctl is-active "$svc" 2>/dev/null || echo "inactive")
|
|
|
|
|
ENABLED=$(systemctl is-enabled "$svc" 2>/dev/null || echo "disabled")
|
|
|
|
|
printf " %-40s %s / %s\n" "$svc" "$STATUS" "$ENABLED" >> "$LOG"
|
|
|
|
|
done
|
|
|
|
|
echo "" >> "$LOG"
|
|
|
|
|
|
|
|
|
|
echo "=== Failed Services ===" >> "$LOG"
|
|
|
|
|
systemctl --failed --no-pager >> "$LOG" 2>&1
|
|
|
|
|
echo "" >> "$LOG"
|
|
|
|
|
|
|
|
|
|
echo "=== Nginx ===" >> "$LOG"
|
|
|
|
|
nginx -t >> "$LOG" 2>&1
|
|
|
|
|
echo "" >> "$LOG"
|
|
|
|
|
|
|
|
|
|
echo "=== EFI Boot ===" >> "$LOG"
|
|
|
|
|
ls -laR /boot/efi/EFI/ >> "$LOG" 2>&1
|
|
|
|
|
echo "" >> "$LOG"
|
|
|
|
|
|
|
|
|
|
echo "=== SSL Cert ===" >> "$LOG"
|
|
|
|
|
ls -la /etc/archipelago/ssl/ >> "$LOG" 2>&1
|
|
|
|
|
echo "" >> "$LOG"
|
|
|
|
|
|
|
|
|
|
echo "=== Podman ===" >> "$LOG"
|
|
|
|
|
su - archipelago -c "podman ps -a --format '{{.Names}} {{.Status}}'" >> "$LOG" 2>&1
|
|
|
|
|
echo "" >> "$LOG"
|
|
|
|
|
|
|
|
|
|
echo "=== Memory ===" >> "$LOG"
|
|
|
|
|
free -h >> "$LOG" 2>&1
|
|
|
|
|
echo "" >> "$LOG"
|
|
|
|
|
|
|
|
|
|
echo "=== Journal Errors (last 50) ===" >> "$LOG"
|
|
|
|
|
journalctl -p err --no-pager -n 50 >> "$LOG" 2>&1
|
|
|
|
|
|
|
|
|
|
echo "Diagnostics saved to $LOG"
|
|
|
|
|
DIAG
|
|
|
|
|
chmod +x /mnt/target/usr/local/bin/archipelago-diagnostics
|
|
|
|
|
|
|
|
|
|
cat > /mnt/target/etc/systemd/system/archipelago-diagnostics.service <<'DIAGSVC'
|
|
|
|
|
[Unit]
|
|
|
|
|
Description=Archipelago First Boot Diagnostics
|
|
|
|
|
After=multi-user.target archipelago.service nginx.service
|
|
|
|
|
ConditionPathExists=!/var/log/archipelago-first-boot-diagnostics.log
|
|
|
|
|
|
|
|
|
|
[Service]
|
|
|
|
|
Type=oneshot
|
|
|
|
|
ExecStartPre=/bin/sleep 30
|
|
|
|
|
ExecStart=/usr/local/bin/archipelago-diagnostics
|
|
|
|
|
RemainAfterExit=yes
|
|
|
|
|
|
|
|
|
|
[Install]
|
|
|
|
|
WantedBy=multi-user.target
|
|
|
|
|
DIAGSVC
|
|
|
|
|
|
2026-03-26 18:30:13 +00:00
|
|
|
# Ensure SSL cert exists for nginx HTTPS (safety net if rootfs build missed it)
|
|
|
|
|
if [ ! -f /mnt/target/etc/archipelago/ssl/archipelago.crt ]; then
|
|
|
|
|
mkdir -p /mnt/target/etc/archipelago/ssl
|
|
|
|
|
chroot /mnt/target openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \
|
|
|
|
|
-keyout /etc/archipelago/ssl/archipelago.key \
|
|
|
|
|
-out /etc/archipelago/ssl/archipelago.crt \
|
|
|
|
|
-subj "/C=XX/ST=Bitcoin/L=Node/O=Archipelago/CN=archipelago" 2>/dev/null
|
|
|
|
|
chmod 600 /mnt/target/etc/archipelago/ssl/archipelago.key
|
|
|
|
|
echo " Generated self-signed SSL certificate"
|
|
|
|
|
fi
|
|
|
|
|
|
2026-02-01 05:42:05 +00:00
|
|
|
# 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
|
2026-02-17 15:03:34 +00:00
|
|
|
chroot /mnt/target systemctl enable archipelago-setup-tor.service 2>/dev/null || true
|
2026-02-25 18:04:41 +00:00
|
|
|
chroot /mnt/target systemctl enable archipelago-first-boot-containers.service 2>/dev/null || true
|
2026-03-26 19:50:59 +00:00
|
|
|
chroot /mnt/target systemctl enable archipelago-kiosk.service 2>/dev/null || true
|
2026-03-26 20:58:34 +00:00
|
|
|
chroot /mnt/target systemctl enable archipelago-diagnostics.service 2>/dev/null || true
|
2026-02-01 05:42:05 +00:00
|
|
|
|
feat: add build report and first-boot diagnostics
CI build report: checks rootfs contents (nginx, SSL, keyboard, kiosk,
lid config, backend, frontend) and ISO contents after build. Reports
in the Actions log so build issues are immediately visible.
First-boot diagnostics: one-shot systemd service runs 30s after first
boot, logs service status, nginx test, SSL certs, LUKS, podman,
kiosk, console-setup, disk, network, and journal errors to
/var/log/archipelago-first-boot-diag.log. Only runs once (ConditionPathExists).
SSH in and cat the log to debug any fresh install issues.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 20:54:32 +00:00
|
|
|
# Install first-boot diagnostic script — runs once after first boot and logs system state
|
|
|
|
|
cat > /mnt/target/opt/archipelago/scripts/first-boot-diag.sh <<'DIAGSCRIPT'
|
|
|
|
|
#!/bin/bash
|
|
|
|
|
LOG="/var/log/archipelago-first-boot-diag.log"
|
|
|
|
|
exec > "$LOG" 2>&1
|
|
|
|
|
echo "=== Archipelago First Boot Diagnostics ==="
|
|
|
|
|
echo "Date: $(date -u)"
|
|
|
|
|
echo "Hostname: $(hostname)"
|
|
|
|
|
echo "Kernel: $(uname -r)"
|
|
|
|
|
echo "IP: $(hostname -I 2>/dev/null | awk '{print $1}')"
|
|
|
|
|
echo ""
|
|
|
|
|
echo "=== Build Info ==="
|
|
|
|
|
cat /opt/archipelago/build-info.txt 2>/dev/null || echo "No build-info.txt"
|
|
|
|
|
echo ""
|
|
|
|
|
echo "=== Services ==="
|
|
|
|
|
for svc in nginx archipelago archipelago-kiosk archipelago-load-images archipelago-first-boot-containers; do
|
|
|
|
|
STATUS=$(systemctl is-active "$svc" 2>/dev/null || echo "missing")
|
|
|
|
|
ENABLED=$(systemctl is-enabled "$svc" 2>/dev/null || echo "missing")
|
|
|
|
|
printf " %-45s active=%-10s enabled=%s\n" "$svc" "$STATUS" "$ENABLED"
|
|
|
|
|
done
|
|
|
|
|
echo ""
|
|
|
|
|
echo "=== Nginx Test ==="
|
|
|
|
|
nginx -t 2>&1
|
|
|
|
|
echo ""
|
|
|
|
|
echo "=== SSL Cert ==="
|
|
|
|
|
ls -la /etc/archipelago/ssl/ 2>/dev/null || echo " No SSL directory"
|
|
|
|
|
echo ""
|
|
|
|
|
echo "=== EFI Boot ==="
|
|
|
|
|
ls -la /boot/efi/EFI/BOOT/ 2>/dev/null || echo " No EFI/BOOT directory"
|
|
|
|
|
echo ""
|
|
|
|
|
echo "=== LUKS ==="
|
|
|
|
|
ls -la /dev/mapper/archipelago-data 2>/dev/null && echo " LUKS volume open" || echo " No LUKS volume"
|
|
|
|
|
cat /etc/crypttab 2>/dev/null
|
|
|
|
|
echo ""
|
|
|
|
|
echo "=== Podman ==="
|
|
|
|
|
podman --version 2>/dev/null || echo " podman not found"
|
|
|
|
|
podman ps -a --format "{{.Names}} {{.Status}}" 2>/dev/null | head -20
|
|
|
|
|
echo ""
|
|
|
|
|
echo "=== Kiosk ==="
|
|
|
|
|
systemctl status archipelago-kiosk --no-pager 2>&1 | head -10
|
|
|
|
|
echo ""
|
|
|
|
|
echo "=== Console Setup ==="
|
|
|
|
|
systemctl status console-setup --no-pager 2>&1 | head -5
|
|
|
|
|
cat /etc/default/keyboard 2>/dev/null || echo " No keyboard config"
|
|
|
|
|
echo ""
|
|
|
|
|
echo "=== Logind (Lid) ==="
|
|
|
|
|
cat /etc/systemd/logind.conf.d/lid-ignore.conf 2>/dev/null || echo " No lid config"
|
|
|
|
|
echo ""
|
|
|
|
|
echo "=== Disk ==="
|
|
|
|
|
df -h / /boot/efi /var/lib/archipelago 2>/dev/null
|
|
|
|
|
echo ""
|
|
|
|
|
echo "=== Network ==="
|
|
|
|
|
ip addr show | grep -E "inet |link/" | head -10
|
|
|
|
|
echo ""
|
|
|
|
|
echo "=== Journal Errors (last boot) ==="
|
|
|
|
|
journalctl -b -p err --no-pager 2>/dev/null | tail -30
|
|
|
|
|
echo ""
|
|
|
|
|
echo "=== Done ==="
|
|
|
|
|
DIAGSCRIPT
|
|
|
|
|
chmod +x /mnt/target/opt/archipelago/scripts/first-boot-diag.sh
|
|
|
|
|
|
|
|
|
|
# Systemd oneshot service for first-boot diagnostics
|
|
|
|
|
cat > /mnt/target/etc/systemd/system/archipelago-diag.service <<'DIAGSVC'
|
|
|
|
|
[Unit]
|
|
|
|
|
Description=Archipelago First Boot Diagnostics
|
|
|
|
|
After=multi-user.target archipelago.service nginx.service
|
|
|
|
|
ConditionPathExists=!/var/log/archipelago-first-boot-diag.log
|
|
|
|
|
|
|
|
|
|
[Service]
|
|
|
|
|
Type=oneshot
|
|
|
|
|
ExecStartPre=/bin/sleep 30
|
|
|
|
|
ExecStart=/opt/archipelago/scripts/first-boot-diag.sh
|
|
|
|
|
RemainAfterExit=yes
|
|
|
|
|
|
|
|
|
|
[Install]
|
|
|
|
|
WantedBy=multi-user.target
|
|
|
|
|
DIAGSVC
|
|
|
|
|
chroot /mnt/target systemctl enable archipelago-diag.service 2>/dev/null || true
|
|
|
|
|
|
|
|
|
|
# Write build info into the installed system
|
|
|
|
|
cat > /mnt/target/opt/archipelago/build-info.txt <<BUILDINFO
|
|
|
|
|
commit=$(cd /tmp 2>/dev/null && git -C "$BOOT_MEDIA/../" rev-parse --short HEAD 2>/dev/null || echo "unknown")
|
|
|
|
|
date=$(date -u '+%Y-%m-%d %H:%M:%S UTC')
|
|
|
|
|
type=unbundled
|
|
|
|
|
BUILDINFO
|
|
|
|
|
|
2026-02-01 05:42:05 +00:00
|
|
|
# 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
|
2026-03-26 09:12:16 +00:00
|
|
|
umount /mnt/target/var/lib/archipelago 2>/dev/null || true
|
|
|
|
|
cryptsetup close archipelago-data 2>/dev/null || true
|
2026-02-01 05:42:05 +00:00
|
|
|
umount /mnt/target 2>/dev/null || true
|
|
|
|
|
|
2026-02-14 16:44:20 +00:00
|
|
|
echo ""
|
|
|
|
|
echo -e "${GREEN} _${NC}"
|
|
|
|
|
echo -e "${GREEN} ,--.\\\`-. __${NC}"
|
|
|
|
|
echo -e "${GREEN} _,.\\\`. \\:/,\" \\\`-._${NC}"
|
|
|
|
|
echo -e "${GREEN} ,-*\" _,.-;-*\\\`-.+\"*._ )${NC}"
|
|
|
|
|
echo -e "${GREEN} ( ,.\"* ,-\" / \\\`. \\\\. \\\`.${NC}"
|
|
|
|
|
echo -e "${GREEN} ,\" ,;\" ,\"\\../\\ \\: \\${NC}"
|
|
|
|
|
echo -e "${GREEN} ( ,\"/ / \\\\.,' : )) /${NC}"
|
|
|
|
|
echo -e "${GREEN} \\ |/ / \\\\.,' / // ,'${NC}"
|
|
|
|
|
echo -e "${GREEN} \\_)\\ ,' \\\\.,' ( / )/${NC}"
|
|
|
|
|
echo -e "${GREEN} \\\` \\._,' \\\`\"${NC}"
|
|
|
|
|
echo -e "${GREEN} \\../${NC}"
|
|
|
|
|
echo -e "${GREEN} \\../${NC}"
|
|
|
|
|
echo -e "${GREEN} ~ ~\\../ ~~ ~~${NC}"
|
|
|
|
|
echo -e "${GREEN} ~~ ~~ \\../ ~~ ~ ~~${NC}"
|
|
|
|
|
echo -e "${GREEN} ~~ ~ ~~ __...---\\../-...__ ~~~ ~~${NC}"
|
|
|
|
|
echo -e "${GREEN} ~~~~ ~_,--' \\../ \\\`--.__ ~~ ~~${NC}"
|
|
|
|
|
echo -e "${GREEN} ~~~ __,--' \\\`\" \\\`--.__ ~~~${NC}"
|
|
|
|
|
echo -e "${GREEN}~~ ,--' \\\`--.${NC}"
|
|
|
|
|
echo -e "${GREEN} '------......______ ______......------\\\` ~~${NC}"
|
|
|
|
|
echo -e "${GREEN} ~~~ ~ ~~ ~ \\\`\\\`\\\`\\\`\\\`---\"\"\"\"\" ~~ ~ ~~${NC}"
|
|
|
|
|
echo -e "${GREEN} ~~~~ ~~ ~~~~ ~~~~~~ ~ ~~ ~~ ~~~ ~${NC}"
|
|
|
|
|
echo ""
|
|
|
|
|
echo -e "${GREEN} █████╗ ██████╗ ██████╗██╗ ██╗██╗██████╗ ███████╗██╗ █████╗ ██████╗ ██████╗ ${NC}"
|
|
|
|
|
echo -e "${GREEN} ██╔══██╗██╔══██╗██╔════╝██║ ██║██║██╔══██╗██╔════╝██║ ██╔══██╗██╔════╝ ██╔═══██╗${NC}"
|
|
|
|
|
echo -e "${GREEN} ███████║██████╔╝██║ ███████║██║██████╔╝█████╗ ██║ ███████║██║ ███╗██║ ██║${NC}"
|
|
|
|
|
echo -e "${GREEN} ██╔══██║██╔══██╗██║ ██╔══██║██║██╔═══╝ ██╔══╝ ██║ ██╔══██║██║ ██║██║ ██║${NC}"
|
|
|
|
|
echo -e "${GREEN} ██║ ██║██║ ██║╚██████╗██║ ██║██║██║ ███████╗███████╗██║ ██║╚██████╔╝╚██████╔╝${NC}"
|
|
|
|
|
echo -e "${GREEN} ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝╚═╝╚═╝ ╚══════╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ${NC}"
|
|
|
|
|
echo ""
|
|
|
|
|
echo -e "${GREEN} 🏝️ BITCOIN NODE OS 🏝️${NC}"
|
2026-02-01 05:42:05 +00:00
|
|
|
echo ""
|
|
|
|
|
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://<IP> ║${NC}"
|
|
|
|
|
echo -e "${GREEN}║ • SSH: ssh archipelago@<IP> ║${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}"
|
2026-02-14 16:44:20 +00:00
|
|
|
echo -e "${GREEN}║ • BTCPay Server • Mempool • Nostr Relays ║${NC}"
|
2026-02-01 05:42:05 +00:00
|
|
|
echo -e "${GREEN}║ ║${NC}"
|
2026-03-09 17:09:59 +00:00
|
|
|
echo -e "${GREEN}║ Validate: bash /opt/archipelago/scripts/run-e2e-tests.sh ║${NC}"
|
|
|
|
|
echo -e "${GREEN}║ ║${NC}"
|
2026-02-01 05:42:05 +00:00
|
|
|
echo -e "${GREEN}╚═══════════════════════════════════════════════════════════════════╝${NC}"
|
|
|
|
|
echo ""
|
2026-03-25 16:56:02 +00:00
|
|
|
echo ""
|
|
|
|
|
echo -e "${YELLOW} >>> REMOVE THE USB DRIVE NOW <<<${NC}"
|
|
|
|
|
echo ""
|
feat: add build report and first-boot diagnostics
CI build report: checks rootfs contents (nginx, SSL, keyboard, kiosk,
lid config, backend, frontend) and ISO contents after build. Reports
in the Actions log so build issues are immediately visible.
First-boot diagnostics: one-shot systemd service runs 30s after first
boot, logs service status, nginx test, SSL certs, LUKS, podman,
kiosk, console-setup, disk, network, and journal errors to
/var/log/archipelago-first-boot-diag.log. Only runs once (ConditionPathExists).
SSH in and cat the log to debug any fresh install issues.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 20:54:32 +00:00
|
|
|
# Save install log to target disk for post-install debugging
|
|
|
|
|
cp "$INSTALL_LOG" /mnt/target/var/log/archipelago-install.log 2>/dev/null || true
|
|
|
|
|
|
2026-03-25 16:56:02 +00:00
|
|
|
read -p "Press Enter to reboot (make sure USB is removed)..."
|
|
|
|
|
|
2026-03-26 18:30:13 +00:00
|
|
|
# Suppress all error output during cleanup and reboot
|
|
|
|
|
exec 2>/dev/null
|
|
|
|
|
|
2026-03-25 16:56:02 +00:00
|
|
|
# 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 "")
|
|
|
|
|
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/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
|
|
|
|
|
|
2026-02-01 05:42:05 +00:00
|
|
|
reboot
|
|
|
|
|
INSTALLER_SCRIPT
|
|
|
|
|
|
2026-03-10 23:29:05 +00:00
|
|
|
# For unbundled builds, patch the completion message to reflect no pre-loaded apps
|
|
|
|
|
if [ "$UNBUNDLED" = "1" ]; then
|
|
|
|
|
sed -i 's/Pre-loaded apps (ready to start via Web UI):/Install apps from the Marketplace (internet required):/' "$ARCH_DIR/auto-install.sh"
|
|
|
|
|
sed -i 's/• Bitcoin Knots • LND • Home Assistant/ Open the Web UI → Marketplace → Install any app/' "$ARCH_DIR/auto-install.sh"
|
|
|
|
|
sed -i 's/• BTCPay Server • Mempool • Nostr Relays/ All apps download automatically via Podman /' "$ARCH_DIR/auto-install.sh"
|
|
|
|
|
fi
|
|
|
|
|
|
2026-02-01 05:42:05 +00:00
|
|
|
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"
|
|
|
|
|
|
2026-03-12 00:19:30 +00:00
|
|
|
$CONTAINER_CMD run --rm --platform $CONTAINER_PLATFORM \
|
2026-02-01 05:42:05 +00:00
|
|
|
-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
|
2026-03-12 00:19:30 +00:00
|
|
|
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
|
2026-02-01 05:42:05 +00:00
|
|
|
|
|
|
|
|
# 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
|
2026-03-12 00:19:30 +00:00
|
|
|
mkdir -p "$OVERLAY_DIR/usr/lib/${LIB_DIR}"
|
|
|
|
|
cp "$TOOLS_DIR/lib/"*.so* "$OVERLAY_DIR/usr/lib/${LIB_DIR}/" 2>/dev/null || true
|
2026-02-01 05:42:05 +00:00
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
2026-02-03 21:43:33 +00:00
|
|
|
# Only run once
|
|
|
|
|
if [ -n "$INSTALLER_STARTED" ]; then
|
2026-02-01 05:42:05 +00:00
|
|
|
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
|
2026-02-01 18:46:35 +00:00
|
|
|
# Use $CONTAINER_CMD to create squashfs on macOS
|
|
|
|
|
echo " Using $CONTAINER_CMD to create squashfs..."
|
2026-03-12 00:19:30 +00:00
|
|
|
$CONTAINER_CMD run --rm --platform $CONTAINER_PLATFORM \
|
2026-02-01 05:42:05 +00:00
|
|
|
-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..."
|
|
|
|
|
|
2026-03-10 23:29:05 +00:00
|
|
|
if [ "$UNBUNDLED" = "1" ]; then
|
2026-03-12 00:19:30 +00:00
|
|
|
OUTPUT_ISO="$OUTPUT_DIR/archipelago-installer-unbundled-${ARCH}.iso"
|
2026-03-10 23:29:05 +00:00
|
|
|
else
|
2026-03-12 00:19:30 +00:00
|
|
|
OUTPUT_ISO="$OUTPUT_DIR/archipelago-installer-${ARCH}.iso"
|
2026-03-10 23:29:05 +00:00
|
|
|
fi
|
2026-02-01 05:42:05 +00:00
|
|
|
|
2026-03-09 17:09:59 +00:00
|
|
|
# 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..."
|
|
|
|
|
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..."
|
|
|
|
|
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"
|
|
|
|
|
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"
|
2026-02-01 05:42:05 +00:00
|
|
|
break
|
|
|
|
|
fi
|
|
|
|
|
done
|
|
|
|
|
|
2026-03-09 17:09:59 +00:00
|
|
|
# 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
|
2026-02-01 05:42:05 +00:00
|
|
|
fi
|
|
|
|
|
|
2026-03-09 17:09:59 +00:00
|
|
|
if [ -z "$EFI_IMG" ]; then
|
|
|
|
|
echo " ⚠️ No EFI boot image found — ISO will only support Legacy BIOS boot"
|
|
|
|
|
xorriso -as mkisofs -o "$OUTPUT_ISO" \
|
|
|
|
|
-volid "ARCHIPELAGO" \
|
|
|
|
|
-iso-level 3 \
|
|
|
|
|
-J -joliet-long -R \
|
|
|
|
|
-isohybrid-mbr "$ISOHDPFX" \
|
|
|
|
|
-c isolinux/boot.cat \
|
|
|
|
|
-b isolinux/isolinux.bin \
|
|
|
|
|
-no-emul-boot -boot-load-size 4 -boot-info-table \
|
|
|
|
|
-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 \
|
|
|
|
|
-J -joliet-long -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 "$EFI_REL" \
|
|
|
|
|
-no-emul-boot \
|
|
|
|
|
-isohybrid-gpt-basdat \
|
|
|
|
|
-partition_offset 16 \
|
|
|
|
|
"$INSTALLER_ISO"
|
|
|
|
|
fi
|
2026-02-01 05:42:05 +00:00
|
|
|
|
|
|
|
|
echo ""
|
2026-03-10 23:29:05 +00:00
|
|
|
if [ "$UNBUNDLED" = "1" ]; then
|
|
|
|
|
echo "╔════════════════════════════════════════════════════════════════╗"
|
|
|
|
|
echo "║ ✅ UNBUNDLED AUTO-INSTALLER ISO CREATED! ║"
|
|
|
|
|
echo "╚════════════════════════════════════════════════════════════════╝"
|
|
|
|
|
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 ""
|
|
|
|
|
else
|
|
|
|
|
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 ""
|
|
|
|
|
fi
|
2026-02-01 05:42:05 +00:00
|
|
|
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 ""
|