- Updated BUILD-GUIDE.md to clarify instructions for building the Archipelago Auto-Installer ISO, emphasizing the recommended method of building directly on the target server. - Added auto-installation of missing dependencies (xorriso, podman) when running the build script with sudo. - Enhanced the build-auto-installer-iso.sh script to capture container images from the live server, ensuring the ISO includes the same set of applications as the dev server. - Revised deployment documentation to stress the importance of building the Rust backend on the Linux dev server and included new instructions for capturing system-level changes for ISO builds. - Improved UI components and added new bundled applications (BTCPay Server, Mempool Explorer, Nostr Relay, Strfry Relay, Tailscale) to enhance user experience.
1260 lines
49 KiB
Bash
Executable File
1260 lines
49 KiB
Bash
Executable File
#!/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.
|
|
#
|
|
# 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
|
|
#
|
|
# 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
|
|
#
|
|
|
|
set -e
|
|
|
|
# Configuration
|
|
DEV_SERVER="${DEV_SERVER:-archipelago@192.168.1.228}"
|
|
BUILD_FROM_SOURCE="${BUILD_FROM_SOURCE:-0}"
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
WORK_DIR="$SCRIPT_DIR/build/auto-installer"
|
|
OUTPUT_DIR="$SCRIPT_DIR/results"
|
|
ROOTFS_DIR="$WORK_DIR/rootfs"
|
|
INSTALLER_DIR="$WORK_DIR/installer"
|
|
|
|
echo "╔════════════════════════════════════════════════════════════════╗"
|
|
echo "║ Building Archipelago Auto-Installer ISO (StartOS-like) ║"
|
|
echo "╚════════════════════════════════════════════════════════════════╝"
|
|
echo ""
|
|
if [ "$BUILD_FROM_SOURCE" = "1" ]; then
|
|
echo "📦 Mode: Building from SOURCE CODE"
|
|
else
|
|
echo "📦 Mode: Capturing LIVE SERVER state"
|
|
echo " Server: $DEV_SERVER"
|
|
fi
|
|
echo ""
|
|
|
|
# Check for required tools
|
|
check_tools() {
|
|
local missing=""
|
|
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
|
|
|
|
# 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
|
|
|
|
if [ -n "$missing" ]; then
|
|
echo "❌ Missing required tools:$missing"
|
|
|
|
if [ "$can_install" = true ]; then
|
|
echo " 📦 Auto-installing missing dependencies..."
|
|
apt-get update -qq
|
|
|
|
if [[ "$missing" == *"xorriso"* ]]; then
|
|
apt-get install -y xorriso
|
|
fi
|
|
|
|
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"
|
|
exit 1
|
|
fi
|
|
|
|
echo "Using container runtime: $CONTAINER_CMD"
|
|
}
|
|
|
|
check_tools
|
|
|
|
mkdir -p "$WORK_DIR"
|
|
mkdir -p "$OUTPUT_DIR"
|
|
|
|
# =============================================================================
|
|
# STEP 1: Build complete root filesystem using Docker
|
|
# =============================================================================
|
|
echo "📦 Step 1: Building root filesystem..."
|
|
|
|
ROOTFS_TAR="$WORK_DIR/archipelago-rootfs.tar"
|
|
|
|
if [ ! -f "$ROOTFS_TAR" ] || [ "$1" == "--rebuild" ]; then
|
|
echo " Using Docker to create Debian root filesystem..."
|
|
|
|
# Create a Dockerfile for building the rootfs
|
|
cat > "$WORK_DIR/Dockerfile.rootfs" <<'DOCKERFILE'
|
|
FROM debian:bookworm
|
|
|
|
ENV DEBIAN_FRONTEND=noninteractive
|
|
|
|
# Install all packages we need including nginx and podman
|
|
RUN apt-get update && apt-get install -y \
|
|
linux-image-amd64 \
|
|
grub-efi-amd64 \
|
|
grub-efi-amd64-signed \
|
|
shim-signed \
|
|
systemd \
|
|
systemd-sysv \
|
|
dbus \
|
|
sudo \
|
|
network-manager \
|
|
openssh-server \
|
|
nginx \
|
|
podman \
|
|
curl \
|
|
wget \
|
|
htop \
|
|
vim-tiny \
|
|
ca-certificates \
|
|
locales \
|
|
console-setup \
|
|
keyboard-configuration \
|
|
&& apt-get clean \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
# Configure locale
|
|
RUN echo "en_US.UTF-8 UTF-8" > /etc/locale.gen && locale-gen
|
|
|
|
# Create archipelago user
|
|
RUN useradd -m -s /bin/bash -G sudo archipelago && \
|
|
echo "archipelago:archipelago" | chpasswd && \
|
|
echo "archipelago ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/archipelago
|
|
|
|
# Set hostname
|
|
RUN echo "archipelago" > /etc/hostname
|
|
|
|
# Configure SSH
|
|
RUN mkdir -p /etc/ssh && \
|
|
sed -i 's/#PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config || true && \
|
|
sed -i 's/#PasswordAuthentication.*/PasswordAuthentication yes/' /etc/ssh/sshd_config || true
|
|
|
|
# Configure nginx for Archipelago
|
|
RUN rm -f /etc/nginx/sites-enabled/default
|
|
COPY nginx-archipelago.conf /etc/nginx/sites-available/archipelago
|
|
RUN ln -sf /etc/nginx/sites-available/archipelago /etc/nginx/sites-enabled/archipelago
|
|
|
|
# Create archipelago systemd service
|
|
COPY archipelago.service /etc/systemd/system/archipelago.service
|
|
|
|
# Enable services
|
|
RUN systemctl enable NetworkManager || true && \
|
|
systemctl enable ssh || true && \
|
|
systemctl enable nginx || true && \
|
|
systemctl enable archipelago || true
|
|
|
|
# Create directories
|
|
RUN mkdir -p /var/lib/archipelago/{data,config,containers} && \
|
|
mkdir -p /etc/archipelago && \
|
|
mkdir -p /opt/archipelago/{bin,scripts,web-ui} && \
|
|
chown -R archipelago:archipelago /var/lib/archipelago /opt/archipelago
|
|
|
|
# Clean up
|
|
RUN apt-get clean && \
|
|
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
|
DOCKERFILE
|
|
|
|
# Create nginx config for the Docker build
|
|
cat > "$WORK_DIR/nginx-archipelago.conf" <<'NGINXCONF'
|
|
server {
|
|
listen 80;
|
|
server_name _;
|
|
|
|
root /opt/archipelago/web-ui;
|
|
index index.html;
|
|
|
|
# Serve static files (Vue.js SPA)
|
|
location / {
|
|
try_files $uri $uri/ /index.html;
|
|
}
|
|
|
|
# Proxy API requests to backend
|
|
location /rpc/ {
|
|
proxy_pass http://127.0.0.1:5678;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
}
|
|
|
|
# Proxy WebSocket
|
|
location /ws {
|
|
proxy_pass http://127.0.0.1:5678;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Upgrade $http_upgrade;
|
|
proxy_set_header Connection "upgrade";
|
|
proxy_set_header Host $host;
|
|
}
|
|
}
|
|
NGINXCONF
|
|
|
|
# Create systemd service for the Docker build
|
|
cat > "$WORK_DIR/archipelago.service" <<'SYSTEMDSERVICE'
|
|
[Unit]
|
|
Description=Archipelago Backend
|
|
After=network-online.target
|
|
Wants=network-online.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
User=archipelago
|
|
Environment="ARCHIPELAGO_BIND=0.0.0.0:5678"
|
|
Environment="ARCHIPELAGO_DEV_MODE=true"
|
|
ExecStart=/usr/local/bin/archipelago
|
|
Restart=on-failure
|
|
RestartSec=5
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
SYSTEMDSERVICE
|
|
|
|
echo " Building $CONTAINER_CMD image (this may take a few minutes)..."
|
|
$CONTAINER_CMD build --platform linux/amd64 -t archipelago-rootfs -f "$WORK_DIR/Dockerfile.rootfs" "$WORK_DIR"
|
|
|
|
echo " Exporting filesystem..."
|
|
$CONTAINER_CMD create --platform linux/amd64 --name archipelago-rootfs-tmp archipelago-rootfs
|
|
$CONTAINER_CMD export archipelago-rootfs-tmp > "$ROOTFS_TAR"
|
|
$CONTAINER_CMD rm archipelago-rootfs-tmp
|
|
|
|
echo "✅ Root filesystem created: $(du -h "$ROOTFS_TAR" | cut -f1)"
|
|
else
|
|
echo "✅ Using cached root filesystem: $(du -h "$ROOTFS_TAR" | cut -f1)"
|
|
fi
|
|
|
|
# =============================================================================
|
|
# STEP 2: Create installer environment
|
|
# =============================================================================
|
|
echo ""
|
|
echo "📦 Step 2: Creating installer environment..."
|
|
|
|
# Download Debian Live as our installer base
|
|
BASE_ISO="$WORK_DIR/debian-live-installer.iso"
|
|
EXPECTED_SIZE=369000000 # ~352MB
|
|
|
|
# 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
|
|
ISO_URL="https://sourceforge.net/projects/debian-live-respin-iso/files/standard/live-image-debian12.11-standard-20250522-amd64.hybrid.iso/download"
|
|
|
|
if command -v wget >/dev/null 2>&1; then
|
|
cd "$WORK_DIR"
|
|
wget --tries=10 --read-timeout=120 --continue --progress=bar:force \
|
|
--no-check-certificate \
|
|
"$ISO_URL" || {
|
|
echo " ❌ Download failed or incomplete"
|
|
echo " Partial file kept - run script again to continue"
|
|
exit 1
|
|
}
|
|
|
|
# Find the downloaded file (wget creates it with a name like "download" or the actual filename)
|
|
if [ -f "download" ]; then
|
|
mv "download" "$BASE_ISO"
|
|
elif [ -f "live-image-debian12.11-standard-20250522-amd64.hybrid.iso" ]; then
|
|
mv "live-image-debian12.11-standard-20250522-amd64.hybrid.iso" "$BASE_ISO"
|
|
else
|
|
echo " ❌ Downloaded file not found"
|
|
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)"
|
|
fi
|
|
|
|
echo " Extracting installer base..."
|
|
INSTALLER_ISO="$WORK_DIR/installer-iso"
|
|
rm -rf "$INSTALLER_ISO"
|
|
mkdir -p "$INSTALLER_ISO"
|
|
cd "$INSTALLER_ISO"
|
|
7z x -y "$BASE_ISO" >/dev/null 2>&1 || 7z x -y "$BASE_ISO"
|
|
|
|
# =============================================================================
|
|
# STEP 3: Add Archipelago components
|
|
# =============================================================================
|
|
echo ""
|
|
echo "📦 Step 3: Adding Archipelago components..."
|
|
|
|
ARCH_DIR="$INSTALLER_ISO/archipelago"
|
|
mkdir -p "$ARCH_DIR"
|
|
mkdir -p "$ARCH_DIR/bin"
|
|
mkdir -p "$ARCH_DIR/scripts"
|
|
|
|
# Copy the pre-built rootfs
|
|
echo " Including root filesystem..."
|
|
cp "$ROOTFS_TAR" "$ARCH_DIR/rootfs.tar"
|
|
|
|
# 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
|
|
# Check if we're running on the server itself (localhost or same machine)
|
|
if [ "$DEV_SERVER" = "localhost" ] || [ "$DEV_SERVER" = "127.0.0.1" ]; then
|
|
# Direct copy from local filesystem
|
|
if [ -f "/usr/local/bin/archipelago" ]; then
|
|
cp "/usr/local/bin/archipelago" "$ARCH_DIR/bin/archipelago"
|
|
chmod +x "$ARCH_DIR/bin/archipelago"
|
|
echo " ✅ Backend captured from local system ($(du -h "$ARCH_DIR/bin/archipelago" | cut -f1))"
|
|
BACKEND_CAPTURED=1
|
|
fi
|
|
else
|
|
# Remote copy via SCP
|
|
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'
|
|
FROM rust:1.93-bookworm as builder
|
|
WORKDIR /build
|
|
COPY core ./core
|
|
RUN cd core && cargo build --release --bin archipelago
|
|
BACKENDFILE
|
|
|
|
if $CONTAINER_CMD build --platform linux/amd64 -t archipelago-backend -f "$BACKEND_DOCKERFILE" "$SCRIPT_DIR/.." 2>&1 | tail -20; then
|
|
echo " Extracting backend binary..."
|
|
BACKEND_CONTAINER=$($CONTAINER_CMD create --platform linux/amd64 archipelago-backend)
|
|
$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..."
|
|
else
|
|
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
|
|
# Check if we're running on the server itself
|
|
if [ "$DEV_SERVER" = "localhost" ] || [ "$DEV_SERVER" = "127.0.0.1" ]; then
|
|
# Direct copy from local filesystem
|
|
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
|
|
else
|
|
# Remote copy via rsync
|
|
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
|
|
fi
|
|
fi
|
|
|
|
if [ "$WEBUI_CAPTURED" = "0" ]; then
|
|
if [ "$BUILD_FROM_SOURCE" != "1" ]; then
|
|
echo " ⚠️ Could not capture from live server, building from source..."
|
|
fi
|
|
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
|
|
else
|
|
echo " ⚠️ Web UI build failed"
|
|
# Try to use existing build
|
|
if [ -d "$SCRIPT_DIR/../web/dist/neode-ui" ]; then
|
|
echo " Using existing web UI build..."
|
|
cp -r "$SCRIPT_DIR/../web/dist/neode-ui/"* "$ARCH_DIR/web-ui/"
|
|
elif [ -d "$SCRIPT_DIR/../neode-ui/dist" ]; then
|
|
echo " Using neode-ui/dist..."
|
|
cp -r "$SCRIPT_DIR/../neode-ui/dist/"* "$ARCH_DIR/web-ui/"
|
|
else
|
|
echo " ❌ No web UI available"
|
|
exit 1
|
|
fi
|
|
fi
|
|
cd "$SCRIPT_DIR"
|
|
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 ""
|
|
echo "📦 Step 3b: Bundling container images for offline use..."
|
|
|
|
IMAGES_DIR="$ARCH_DIR/container-images"
|
|
mkdir -p "$IMAGES_DIR"
|
|
|
|
# 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)..."
|
|
CAPTURE_PATTERNS="bitcoin-ui bitcoin-knots lnd lnd-ui filebrowser mempool tailscale homeassistant btcpayserver nostr-rs-relay strfry"
|
|
REMOTE_TMP="/tmp/archipelago-image-capture-$$"
|
|
SAVED_LIST=$(ssh "$DEV_SERVER" "mkdir -p $REMOTE_TMP && for p in $CAPTURE_PATTERNS; do img=\$(sudo podman images --format '{{.Repository}}:{{.Tag}}' 2>/dev/null | grep -i \"\$p\" | head -1); [ -n \"\$img\" ] && sudo podman save -o \"$REMOTE_TMP/\$p.tar\" \"\$img\" 2>/dev/null && echo \"\$p\"; done" 2>/dev/null) || true
|
|
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.
|
|
CONTAINER_IMAGES="
|
|
bitcoinknots/bitcoin:29 bitcoin-knots.tar
|
|
lightninglabs/lnd:v0.18.4-beta lnd.tar
|
|
ghcr.io/home-assistant/home-assistant:stable homeassistant.tar
|
|
btcpayserver/btcpayserver:latest btcpayserver.tar
|
|
mempool/frontend:latest mempool.tar
|
|
docker.io/filebrowser/filebrowser:latest filebrowser.tar
|
|
scsibug/nostr-rs-relay:latest nostr-rs-relay.tar
|
|
hoytech/strfry:latest strfry.tar
|
|
tailscale/tailscale:latest tailscale.tar
|
|
"
|
|
|
|
# Pull and save each image (force AMD64 for x86_64 target) only if not already present
|
|
echo "$CONTAINER_IMAGES" | while read -r image filename; do
|
|
[ -z "$image" ] && continue
|
|
tarpath="$IMAGES_DIR/$filename"
|
|
|
|
if [ -f "$tarpath" ]; then
|
|
echo " ✅ Using cached: $filename"
|
|
else
|
|
echo " Pulling $image (linux/amd64)..."
|
|
if $CONTAINER_CMD pull --platform linux/amd64 "$image"; then
|
|
echo " Saving $filename..."
|
|
$CONTAINER_CMD save "$image" -o "$tarpath"
|
|
echo " ✅ Saved: $(du -h "$tarpath" | cut -f1)"
|
|
else
|
|
echo " ⚠️ Failed to pull $image - skipping"
|
|
fi
|
|
fi
|
|
done
|
|
|
|
# Create first-boot service to load images into Podman
|
|
echo " Creating first-boot image loader service..."
|
|
cat > "$WORK_DIR/archipelago-load-images.service" <<'LOADSERVICE'
|
|
[Unit]
|
|
Description=Load Archipelago Container Images
|
|
After=network.target podman.service
|
|
ConditionPathExists=/opt/archipelago/container-images
|
|
ConditionPathExists=!/var/lib/archipelago/.images-loaded
|
|
|
|
[Service]
|
|
Type=oneshot
|
|
ExecStart=/opt/archipelago/scripts/load-container-images.sh
|
|
ExecStartPost=/usr/bin/touch /var/lib/archipelago/.images-loaded
|
|
RemainAfterExit=yes
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
LOADSERVICE
|
|
|
|
cat > "$WORK_DIR/load-container-images.sh" <<'LOADSCRIPT'
|
|
#!/bin/bash
|
|
# Load pre-bundled container images into Podman
|
|
|
|
IMAGES_DIR="/opt/archipelago/container-images"
|
|
LOG_FILE="/var/log/archipelago-images.log"
|
|
|
|
echo "$(date): Starting container image load" >> "$LOG_FILE"
|
|
|
|
if [ ! -d "$IMAGES_DIR" ]; then
|
|
echo "$(date): No images directory found" >> "$LOG_FILE"
|
|
exit 0
|
|
fi
|
|
|
|
for tarfile in "$IMAGES_DIR"/*.tar; do
|
|
if [ -f "$tarfile" ]; then
|
|
echo "$(date): Loading $(basename "$tarfile")..." >> "$LOG_FILE"
|
|
podman load -i "$tarfile" >> "$LOG_FILE" 2>&1 && \
|
|
echo "$(date): Successfully loaded $(basename "$tarfile")" >> "$LOG_FILE" || \
|
|
echo "$(date): Failed to load $(basename "$tarfile")" >> "$LOG_FILE"
|
|
fi
|
|
done
|
|
|
|
echo "$(date): Container image load complete" >> "$LOG_FILE"
|
|
echo "$(date): Available images:" >> "$LOG_FILE"
|
|
podman images >> "$LOG_FILE" 2>&1
|
|
LOADSCRIPT
|
|
|
|
chmod +x "$WORK_DIR/load-container-images.sh"
|
|
|
|
# Copy scripts to ISO
|
|
mkdir -p "$ARCH_DIR/scripts"
|
|
cp "$WORK_DIR/load-container-images.sh" "$ARCH_DIR/scripts/"
|
|
cp "$WORK_DIR/archipelago-load-images.service" "$ARCH_DIR/scripts/"
|
|
|
|
echo " ✅ Container images bundled"
|
|
|
|
# =============================================================================
|
|
# STEP 4: Create auto-installer script
|
|
# =============================================================================
|
|
echo ""
|
|
echo "📦 Step 4: Creating auto-installer..."
|
|
|
|
cat > "$ARCH_DIR/auto-install.sh" <<'INSTALLER_SCRIPT'
|
|
#!/bin/bash
|
|
#
|
|
# Archipelago Auto-Installer
|
|
# Automatically installs to internal disk (StartOS-like experience)
|
|
#
|
|
|
|
set -e
|
|
|
|
# Colors
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m'
|
|
|
|
clear
|
|
echo ""
|
|
echo -e "${BLUE}╔═══════════════════════════════════════════════════════════════════╗${NC}"
|
|
echo -e "${BLUE}║ ║${NC}"
|
|
echo -e "${BLUE}║ ${GREEN}🏝️ ARCHIPELAGO BITCOIN NODE OS${BLUE} ║${NC}"
|
|
echo -e "${BLUE}║ ║${NC}"
|
|
echo -e "${BLUE}║ ${NC}Automatic Installation${BLUE} ║${NC}"
|
|
echo -e "${BLUE}║ ║${NC}"
|
|
echo -e "${BLUE}╚═══════════════════════════════════════════════════════════════════╝${NC}"
|
|
echo ""
|
|
|
|
# Check required tools are present (should be bundled in ISO)
|
|
echo -e "${YELLOW}🔧 Checking installer tools...${NC}"
|
|
MISSING=""
|
|
command -v parted >/dev/null 2>&1 || MISSING="parted $MISSING"
|
|
command -v mkfs.vfat >/dev/null 2>&1 || MISSING="mkfs.vfat $MISSING"
|
|
command -v mkfs.ext4 >/dev/null 2>&1 || MISSING="mkfs.ext4 $MISSING"
|
|
|
|
if [ -n "$MISSING" ]; then
|
|
echo " Missing tools: $MISSING"
|
|
echo " Attempting to install (requires network)..."
|
|
if apt-get update -qq >/dev/null 2>&1; then
|
|
apt-get install -y -qq parted dosfstools e2fsprogs >/dev/null 2>&1 && echo " ✅ Tools installed" || {
|
|
echo -e "${RED}❌ Failed to install required tools. No network?${NC}"
|
|
echo " Required: parted, mkfs.vfat, mkfs.ext4"
|
|
exit 1
|
|
}
|
|
else
|
|
echo -e "${RED}❌ No network available and tools not bundled.${NC}"
|
|
exit 1
|
|
fi
|
|
else
|
|
echo " ✅ All tools present"
|
|
fi
|
|
echo ""
|
|
|
|
# Find boot media
|
|
BOOT_MEDIA=""
|
|
for dev in /run/live/medium /lib/live/mount/medium /cdrom; do
|
|
if [ -d "$dev/archipelago" ]; then
|
|
BOOT_MEDIA="$dev"
|
|
break
|
|
fi
|
|
done
|
|
|
|
if [ -z "$BOOT_MEDIA" ]; then
|
|
echo -e "${RED}❌ Boot media not found${NC}"
|
|
exit 1
|
|
fi
|
|
|
|
ROOTFS_TAR="$BOOT_MEDIA/archipelago/rootfs.tar"
|
|
if [ ! -f "$ROOTFS_TAR" ]; then
|
|
echo -e "${RED}❌ Root filesystem not found: $ROOTFS_TAR${NC}"
|
|
exit 1
|
|
fi
|
|
|
|
# Find the boot USB device to exclude it
|
|
BOOT_DEV=$(findmnt -n -o SOURCE "$BOOT_MEDIA" 2>/dev/null | sed 's/[0-9]*$//' | sed 's/p[0-9]*$//')
|
|
BOOT_DEV_NAME=$(basename "$BOOT_DEV" 2>/dev/null || echo "")
|
|
|
|
echo -e "${YELLOW}📋 Detecting disks...${NC}"
|
|
echo ""
|
|
|
|
# Find internal disk (prefer NVMe, then SATA, skip USB)
|
|
TARGET_DISK=""
|
|
TARGET_SIZE=""
|
|
|
|
# Check NVMe drives first
|
|
for disk in /dev/nvme*n1; do
|
|
if [ -b "$disk" ]; then
|
|
disk_name=$(basename "$disk")
|
|
if [ "$disk_name" != "$BOOT_DEV_NAME" ]; then
|
|
size=$(lsblk -dn -o SIZE "$disk" 2>/dev/null)
|
|
model=$(cat /sys/block/$disk_name/device/model 2>/dev/null || echo "NVMe SSD")
|
|
echo " Found: $disk ($size) - $model"
|
|
TARGET_DISK="$disk"
|
|
TARGET_SIZE="$size"
|
|
break
|
|
fi
|
|
fi
|
|
done
|
|
|
|
# If no NVMe, check SATA drives
|
|
if [ -z "$TARGET_DISK" ]; then
|
|
for disk in /dev/sd[a-z]; do
|
|
if [ -b "$disk" ]; then
|
|
disk_name=$(basename "$disk")
|
|
if [ "$disk_name" != "$BOOT_DEV_NAME" ]; then
|
|
# Skip USB drives (check removable flag)
|
|
removable=$(cat /sys/block/$disk_name/removable 2>/dev/null || echo "0")
|
|
if [ "$removable" = "0" ]; then
|
|
size=$(lsblk -dn -o SIZE "$disk" 2>/dev/null)
|
|
model=$(cat /sys/block/$disk_name/device/model 2>/dev/null || echo "SATA Drive")
|
|
echo " Found: $disk ($size) - $model"
|
|
TARGET_DISK="$disk"
|
|
TARGET_SIZE="$size"
|
|
break
|
|
fi
|
|
fi
|
|
fi
|
|
done
|
|
fi
|
|
|
|
if [ -z "$TARGET_DISK" ]; then
|
|
echo ""
|
|
echo -e "${RED}❌ No suitable internal disk found${NC}"
|
|
echo " Please ensure an internal drive is connected."
|
|
exit 1
|
|
fi
|
|
|
|
echo ""
|
|
echo -e "${GREEN}✅ Target disk: $TARGET_DISK ($TARGET_SIZE)${NC}"
|
|
echo ""
|
|
echo -e "${RED}⚠️ WARNING: ALL DATA ON $TARGET_DISK WILL BE ERASED${NC}"
|
|
echo ""
|
|
echo "Press Enter to install Archipelago, or Ctrl+C to cancel..."
|
|
read
|
|
|
|
echo ""
|
|
echo -e "${YELLOW}🔧 Installing Archipelago...${NC}"
|
|
echo ""
|
|
|
|
# Unmount any existing partitions
|
|
umount ${TARGET_DISK}* 2>/dev/null || true
|
|
umount ${TARGET_DISK}p* 2>/dev/null || true
|
|
|
|
# Create partition table
|
|
echo " [1/6] Creating partitions..."
|
|
parted -s "$TARGET_DISK" mklabel gpt
|
|
parted -s "$TARGET_DISK" mkpart primary fat32 1MiB 513MiB
|
|
parted -s "$TARGET_DISK" set 1 esp on
|
|
parted -s "$TARGET_DISK" mkpart primary ext4 513MiB 100%
|
|
|
|
sleep 2
|
|
|
|
# Determine partition names
|
|
if [[ "$TARGET_DISK" == *nvme* ]]; then
|
|
EFI_PART="${TARGET_DISK}p1"
|
|
ROOT_PART="${TARGET_DISK}p2"
|
|
else
|
|
EFI_PART="${TARGET_DISK}1"
|
|
ROOT_PART="${TARGET_DISK}2"
|
|
fi
|
|
|
|
# Format partitions
|
|
echo " [2/6] Formatting partitions..."
|
|
mkfs.vfat -F32 -n EFI "$EFI_PART"
|
|
mkfs.ext4 -F -L archipelago "$ROOT_PART"
|
|
|
|
# Mount
|
|
echo " [3/6] Mounting filesystems..."
|
|
mkdir -p /mnt/target
|
|
mount "$ROOT_PART" /mnt/target
|
|
mkdir -p /mnt/target/boot/efi
|
|
mount "$EFI_PART" /mnt/target/boot/efi
|
|
|
|
# Extract rootfs
|
|
echo " [4/6] Installing system (this may take a few minutes)..."
|
|
tar -xf "$ROOTFS_TAR" -C /mnt/target
|
|
|
|
# Create fstab
|
|
echo " [5/6] Configuring system..."
|
|
cat > /mnt/target/etc/fstab <<EOF
|
|
# Archipelago Bitcoin Node OS
|
|
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
|
|
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
|
|
|
|
# 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
|
|
|
|
echo " ✅ Container images staged for first-boot loading"
|
|
fi
|
|
|
|
# Ensure correct ownership (use numeric UID:GID 1000:1000 since we're outside chroot)
|
|
chown -R 1000:1000 /mnt/target/opt/archipelago 2>/dev/null || true
|
|
chown -R 1000:1000 /mnt/target/var/lib/archipelago 2>/dev/null || true
|
|
|
|
# Create welcome profile (nginx serves on port 80)
|
|
cat > /mnt/target/etc/profile.d/archipelago.sh <<'PROFILE'
|
|
#!/bin/bash
|
|
if [ -t 0 ] && [ -z "$ARCHIPELAGO_WELCOMED" ]; then
|
|
export ARCHIPELAGO_WELCOMED=1
|
|
IP=$(hostname -I 2>/dev/null | awk '{print $1}')
|
|
echo ""
|
|
echo " _"
|
|
echo " ,--.\\\`-. __"
|
|
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 🏝️"
|
|
echo ""
|
|
if [ -n "$IP" ]; then
|
|
echo " 🌐 Web UI: http://$IP"
|
|
echo " 📡 SSH: ssh archipelago@$IP"
|
|
echo " 🔑 Password: archipelago (SSH) / password123 (Web UI)"
|
|
echo ""
|
|
fi
|
|
fi
|
|
PROFILE
|
|
chmod +x /mnt/target/etc/profile.d/archipelago.sh
|
|
|
|
# Systemd service is already in rootfs, but ensure it has correct config
|
|
cat > /mnt/target/etc/systemd/system/archipelago.service <<'SERVICE'
|
|
[Unit]
|
|
Description=Archipelago Backend
|
|
After=network-online.target
|
|
Wants=network-online.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
User=archipelago
|
|
Environment="ARCHIPELAGO_BIND=0.0.0.0:5678"
|
|
Environment="ARCHIPELAGO_DEV_MODE=true"
|
|
ExecStart=/usr/local/bin/archipelago
|
|
Restart=on-failure
|
|
RestartSec=5
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
SERVICE
|
|
|
|
# Install GRUB
|
|
echo " [6/6] Installing bootloader..."
|
|
mount --bind /dev /mnt/target/dev
|
|
mount --bind /dev/pts /mnt/target/dev/pts
|
|
mount --bind /proc /mnt/target/proc
|
|
mount --bind /sys /mnt/target/sys
|
|
mount --bind /run /mnt/target/run
|
|
|
|
chroot /mnt/target grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=archipelago --removable 2>/dev/null || \
|
|
chroot /mnt/target grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=archipelago 2>/dev/null || \
|
|
echo " Warning: GRUB install had issues, trying alternative..."
|
|
|
|
chroot /mnt/target update-grub
|
|
|
|
# Enable services
|
|
chroot /mnt/target systemctl enable archipelago.service 2>/dev/null || true
|
|
chroot /mnt/target systemctl enable nginx.service 2>/dev/null || true
|
|
chroot /mnt/target systemctl enable archipelago-load-images.service 2>/dev/null || true
|
|
|
|
# Cleanup
|
|
sync
|
|
umount /mnt/target/run 2>/dev/null || true
|
|
umount /mnt/target/sys 2>/dev/null || true
|
|
umount /mnt/target/proc 2>/dev/null || true
|
|
umount /mnt/target/dev/pts 2>/dev/null || true
|
|
umount /mnt/target/dev 2>/dev/null || true
|
|
umount /mnt/target/boot/efi 2>/dev/null || true
|
|
umount /mnt/target 2>/dev/null || true
|
|
|
|
echo ""
|
|
echo -e "${GREEN} _${NC}"
|
|
echo -e "${GREEN} ,--.\\\`-. __${NC}"
|
|
echo -e "${GREEN} _,.\\\`. \\:/,\" \\\`-._${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}"
|
|
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}"
|
|
echo -e "${GREEN}║ • BTCPay Server • Mempool • Nostr Relays ║${NC}"
|
|
echo -e "${GREEN}║ ║${NC}"
|
|
echo -e "${GREEN}╚═══════════════════════════════════════════════════════════════════╝${NC}"
|
|
echo ""
|
|
read -p "Press Enter to reboot..."
|
|
reboot
|
|
INSTALLER_SCRIPT
|
|
|
|
chmod +x "$ARCH_DIR/auto-install.sh"
|
|
|
|
# =============================================================================
|
|
# STEP 5: Configure auto-start installer on boot
|
|
# =============================================================================
|
|
echo ""
|
|
echo "📦 Step 5: Configuring auto-start..."
|
|
|
|
# Create a squashfs overlay module that Debian Live will automatically load
|
|
# This is the proper way to add files to the live filesystem
|
|
OVERLAY_DIR="$WORK_DIR/overlay-root"
|
|
rm -rf "$OVERLAY_DIR"
|
|
mkdir -p "$OVERLAY_DIR/etc/profile.d"
|
|
mkdir -p "$OVERLAY_DIR/etc/skel"
|
|
mkdir -p "$OVERLAY_DIR/usr/local/bin"
|
|
mkdir -p "$OVERLAY_DIR/usr/sbin"
|
|
mkdir -p "$OVERLAY_DIR/sbin"
|
|
|
|
# Download and extract required tools AND their libraries for offline use
|
|
echo " Downloading partitioning tools for offline use..."
|
|
TOOLS_DIR="$WORK_DIR/tools-extract"
|
|
rm -rf "$TOOLS_DIR"
|
|
mkdir -p "$TOOLS_DIR"
|
|
|
|
$CONTAINER_CMD run --rm --platform linux/amd64 \
|
|
-v "$TOOLS_DIR:/output" \
|
|
debian:bookworm \
|
|
bash -c '
|
|
apt-get update -qq
|
|
apt-get install -y -qq parted dosfstools e2fsprogs
|
|
|
|
# Copy binaries
|
|
cp /usr/sbin/parted /output/
|
|
cp /usr/sbin/mkfs.vfat /output/ 2>/dev/null || cp /sbin/mkfs.vfat /output/ 2>/dev/null || true
|
|
cp /usr/sbin/mkfs.ext4 /output/ 2>/dev/null || cp /sbin/mkfs.ext4 /output/ 2>/dev/null || true
|
|
cp /usr/sbin/mke2fs /output/ 2>/dev/null || cp /sbin/mke2fs /output/ 2>/dev/null || true
|
|
cp /sbin/mkfs.fat /output/ 2>/dev/null || true
|
|
|
|
# Copy required shared libraries for parted
|
|
mkdir -p /output/lib
|
|
cp /lib/x86_64-linux-gnu/libparted.so* /output/lib/ 2>/dev/null || true
|
|
cp /usr/lib/x86_64-linux-gnu/libparted.so* /output/lib/ 2>/dev/null || true
|
|
cp /lib/x86_64-linux-gnu/libreadline.so* /output/lib/ 2>/dev/null || true
|
|
cp /usr/lib/x86_64-linux-gnu/libreadline.so* /output/lib/ 2>/dev/null || true
|
|
cp /lib/x86_64-linux-gnu/libdevmapper.so* /output/lib/ 2>/dev/null || true
|
|
cp /usr/lib/x86_64-linux-gnu/libdevmapper.so* /output/lib/ 2>/dev/null || true
|
|
|
|
# List what parted actually needs
|
|
ldd /usr/sbin/parted 2>/dev/null | grep "=>" | awk "{print \$3}" | while read lib; do
|
|
[ -f "$lib" ] && cp "$lib" /output/lib/ 2>/dev/null || true
|
|
done
|
|
|
|
echo "Libraries bundled:"
|
|
ls -la /output/lib/
|
|
'
|
|
|
|
# Copy tools to overlay
|
|
cp "$TOOLS_DIR/parted" "$OVERLAY_DIR/usr/sbin/" 2>/dev/null || true
|
|
cp "$TOOLS_DIR/mkfs.vfat" "$OVERLAY_DIR/usr/sbin/" 2>/dev/null || true
|
|
cp "$TOOLS_DIR/mkfs.fat" "$OVERLAY_DIR/sbin/" 2>/dev/null || true
|
|
cp "$TOOLS_DIR/mkfs.ext4" "$OVERLAY_DIR/usr/sbin/" 2>/dev/null || true
|
|
cp "$TOOLS_DIR/mke2fs" "$OVERLAY_DIR/usr/sbin/" 2>/dev/null || true
|
|
|
|
# Copy shared libraries
|
|
mkdir -p "$OVERLAY_DIR/usr/lib/x86_64-linux-gnu"
|
|
cp "$TOOLS_DIR/lib/"*.so* "$OVERLAY_DIR/usr/lib/x86_64-linux-gnu/" 2>/dev/null || true
|
|
|
|
chmod +x "$OVERLAY_DIR/usr/sbin/"* 2>/dev/null || true
|
|
chmod +x "$OVERLAY_DIR/sbin/"* 2>/dev/null || true
|
|
echo " ✅ Partitioning tools and libraries bundled"
|
|
|
|
# Create the auto-start profile script
|
|
cat > "$OVERLAY_DIR/etc/profile.d/z99-archipelago-installer.sh" <<'AUTOSTART'
|
|
#!/bin/bash
|
|
# Auto-start Archipelago installer on login
|
|
|
|
# Only run once
|
|
if [ -n "$INSTALLER_STARTED" ]; then
|
|
return 0 2>/dev/null || exit 0
|
|
fi
|
|
export INSTALLER_STARTED=1
|
|
|
|
# Give system a moment to settle
|
|
sleep 1
|
|
|
|
clear
|
|
echo ""
|
|
echo " ╔═══════════════════════════════════════════════════════════════════╗"
|
|
echo " ║ ║"
|
|
echo " ║ 🏝️ ARCHIPELAGO BITCOIN NODE OS - INSTALLER ║"
|
|
echo " ║ ║"
|
|
echo " ╚═══════════════════════════════════════════════════════════════════╝"
|
|
echo ""
|
|
|
|
# Find boot media
|
|
BOOT_MEDIA=""
|
|
for dev in /run/live/medium /lib/live/mount/medium /cdrom /media/cdrom; do
|
|
if [ -f "$dev/archipelago/auto-install.sh" ]; then
|
|
BOOT_MEDIA="$dev"
|
|
break
|
|
fi
|
|
done
|
|
|
|
if [ -n "$BOOT_MEDIA" ]; then
|
|
echo " Found installer at: $BOOT_MEDIA"
|
|
echo ""
|
|
echo " Press Enter to start installation, or Ctrl+C for shell..."
|
|
read
|
|
sudo bash "$BOOT_MEDIA/archipelago/auto-install.sh"
|
|
else
|
|
echo " ⚠️ Installer not found on boot media."
|
|
echo ""
|
|
echo " Checked: /run/live/medium, /lib/live/mount/medium, /cdrom"
|
|
echo ""
|
|
echo " You can try manually:"
|
|
echo " sudo bash /path/to/archipelago/auto-install.sh"
|
|
echo ""
|
|
fi
|
|
AUTOSTART
|
|
chmod +x "$OVERLAY_DIR/etc/profile.d/z99-archipelago-installer.sh"
|
|
|
|
# Also create .bashrc that sources the profile script (belt and suspenders)
|
|
cat > "$OVERLAY_DIR/etc/skel/.bashrc" <<'BASHRC'
|
|
# ~/.bashrc: executed by bash(1) for non-login shells.
|
|
|
|
# If not running interactively, don't do anything
|
|
case $- in
|
|
*i*) ;;
|
|
*) return;;
|
|
esac
|
|
|
|
# Source profile.d scripts
|
|
if [ -d /etc/profile.d ]; then
|
|
for i in /etc/profile.d/*.sh; do
|
|
if [ -r "$i" ]; then
|
|
. "$i"
|
|
fi
|
|
done
|
|
fi
|
|
BASHRC
|
|
|
|
# Create a wrapper script that can be called directly
|
|
cat > "$OVERLAY_DIR/usr/local/bin/archipelago-install" <<'WRAPPER'
|
|
#!/bin/bash
|
|
# Archipelago installer wrapper
|
|
|
|
BOOT_MEDIA=""
|
|
for dev in /run/live/medium /lib/live/mount/medium /cdrom /media/cdrom; do
|
|
if [ -f "$dev/archipelago/auto-install.sh" ]; then
|
|
BOOT_MEDIA="$dev"
|
|
break
|
|
fi
|
|
done
|
|
|
|
if [ -n "$BOOT_MEDIA" ]; then
|
|
exec sudo bash "$BOOT_MEDIA/archipelago/auto-install.sh"
|
|
else
|
|
echo "Installer not found. Searched:"
|
|
echo " /run/live/medium, /lib/live/mount/medium, /cdrom, /media/cdrom"
|
|
exit 1
|
|
fi
|
|
WRAPPER
|
|
chmod +x "$OVERLAY_DIR/usr/local/bin/archipelago-install"
|
|
|
|
# Create the squashfs module
|
|
# Debian Live automatically loads all .squashfs files from live/ directory
|
|
echo " Creating overlay squashfs module..."
|
|
LIVE_DIR="$INSTALLER_ISO/live"
|
|
mkdir -p "$LIVE_DIR"
|
|
|
|
# Check if mksquashfs is available (may need to use Docker on macOS)
|
|
if command -v mksquashfs >/dev/null 2>&1; then
|
|
mksquashfs "$OVERLAY_DIR" "$LIVE_DIR/99-archipelago.squashfs" -comp xz -noappend
|
|
else
|
|
# Use $CONTAINER_CMD to create squashfs on macOS
|
|
echo " Using $CONTAINER_CMD to create squashfs..."
|
|
$CONTAINER_CMD run --rm --platform linux/amd64 \
|
|
-v "$OVERLAY_DIR:/overlay:ro" \
|
|
-v "$LIVE_DIR:/output" \
|
|
debian:bookworm \
|
|
bash -c "apt-get update && apt-get install -y squashfs-tools && mksquashfs /overlay /output/99-archipelago.squashfs -comp xz -noappend"
|
|
fi
|
|
|
|
echo " ✅ Created overlay module: 99-archipelago.squashfs"
|
|
|
|
# Modify GRUB config - update branding and ensure components are loaded
|
|
if [ -f "$INSTALLER_ISO/boot/grub/grub.cfg" ]; then
|
|
echo " Configuring GRUB..."
|
|
sed -i.bak \
|
|
-e 's/Debian GNU\/Linux/Archipelago Installer/g' \
|
|
-e 's/Live system/Install Archipelago/g' \
|
|
"$INSTALLER_ISO/boot/grub/grub.cfg"
|
|
|
|
# Ensure 'components' parameter is present (loads additional squashfs modules)
|
|
# Also add 'username=user' to ensure consistent username
|
|
if ! grep -q "components" "$INSTALLER_ISO/boot/grub/grub.cfg"; then
|
|
sed -i 's/boot=live/boot=live components/' "$INSTALLER_ISO/boot/grub/grub.cfg"
|
|
fi
|
|
fi
|
|
|
|
if [ -f "$INSTALLER_ISO/isolinux/live.cfg" ]; then
|
|
echo " Configuring ISOLINUX..."
|
|
sed -i.bak \
|
|
-e 's/Debian GNU\/Linux/Archipelago Installer/g' \
|
|
-e 's/Live system/Install Archipelago/g' \
|
|
"$INSTALLER_ISO/isolinux/live.cfg"
|
|
|
|
# Add components parameter
|
|
if ! grep -q "components" "$INSTALLER_ISO/isolinux/live.cfg"; then
|
|
sed -i 's/boot=live/boot=live components/' "$INSTALLER_ISO/isolinux/live.cfg"
|
|
fi
|
|
fi
|
|
|
|
if [ -f "$INSTALLER_ISO/isolinux/menu.cfg" ]; then
|
|
sed -i.bak \
|
|
-e 's/Debian GNU\/Linux/Archipelago Installer/g' \
|
|
"$INSTALLER_ISO/isolinux/menu.cfg"
|
|
fi
|
|
|
|
# =============================================================================
|
|
# STEP 6: Create final ISO
|
|
# =============================================================================
|
|
echo ""
|
|
echo "📦 Step 6: Creating bootable ISO..."
|
|
|
|
OUTPUT_ISO="$OUTPUT_DIR/archipelago-installer-x86_64.iso"
|
|
|
|
# Get MBR for hybrid boot
|
|
ISOHDPFX=""
|
|
for path in \
|
|
"/usr/local/share/syslinux/isohdpfx.bin" \
|
|
"/usr/share/syslinux/isohdpfx.bin" \
|
|
"/opt/homebrew/share/syslinux/isohdpfx.bin" \
|
|
"$INSTALLER_ISO/isolinux/isohdpfx.bin"; do
|
|
if [ -f "$path" ]; then
|
|
ISOHDPFX="$path"
|
|
break
|
|
fi
|
|
done
|
|
|
|
if [ -z "$ISOHDPFX" ]; then
|
|
echo " Extracting MBR from isolinux.bin..."
|
|
dd if="$INSTALLER_ISO/isolinux/isolinux.bin" of="$WORK_DIR/isohdpfx.bin" bs=432 count=1 2>/dev/null
|
|
ISOHDPFX="$WORK_DIR/isohdpfx.bin"
|
|
fi
|
|
|
|
xorriso -as mkisofs -o "$OUTPUT_ISO" \
|
|
-volid "ARCHIPELAGO" \
|
|
-J -R \
|
|
-isohybrid-mbr "$ISOHDPFX" \
|
|
-c isolinux/boot.cat \
|
|
-b isolinux/isolinux.bin \
|
|
-no-emul-boot -boot-load-size 4 -boot-info-table \
|
|
-eltorito-alt-boot \
|
|
-e boot/grub/efi.img \
|
|
-no-emul-boot \
|
|
-isohybrid-gpt-basdat \
|
|
"$INSTALLER_ISO"
|
|
|
|
echo ""
|
|
echo "╔════════════════════════════════════════════════════════════════╗"
|
|
echo "║ ✅ AUTO-INSTALLER ISO CREATED! ║"
|
|
echo "╚════════════════════════════════════════════════════════════════╝"
|
|
echo ""
|
|
echo "📀 Output: $OUTPUT_ISO"
|
|
echo " Size: $(du -h "$OUTPUT_ISO" | cut -f1)"
|
|
echo ""
|
|
echo "🔥 This is a StartOS-like automatic installer!"
|
|
echo ""
|
|
echo "Features:"
|
|
echo " • Pre-built system (no internet needed during install)"
|
|
echo " • Auto-detects internal disk"
|
|
echo " • One-button installation"
|
|
echo " • Boots directly to Archipelago after install"
|
|
echo " • Pre-bundled container apps:"
|
|
echo " - Bitcoin Knots v29"
|
|
echo " - LND v0.18.4"
|
|
echo " - Home Assistant"
|
|
echo ""
|
|
echo "To create USB:"
|
|
echo " 1. Flash with: sudo dd if=$OUTPUT_ISO of=/dev/rdiskX bs=4m"
|
|
echo " Or use Balena Etcher"
|
|
echo " 2. Boot from USB"
|
|
echo " 3. Press Enter to install"
|
|
echo " 4. Remove USB and reboot"
|
|
echo ""
|