diff --git a/.cursor/rules/Development-Workflow.mdc b/.cursor/rules/Development-Workflow.mdc new file mode 100644 index 00000000..331caf4a --- /dev/null +++ b/.cursor/rules/Development-Workflow.mdc @@ -0,0 +1,136 @@ +# Archipelago Development Workflow + +## Overview + +Development happens on Mac (editing in Cursor), with the HP ProDesk running Archipelago as the live test target via SSH. + +## Architecture + +``` +┌─────────────────────┐ SSH/rsync ┌─────────────────────┐ +│ Mac (Dev Host) │ ──────────────────────────▶│ HP ProDesk (Target)│ +│ │ │ │ +│ • Cursor IDE │ │ • Archipelago OS │ +│ • Source code │ │ • Live testing │ +│ • ISO builds │ │ • Vue.js dev server│ +│ │ │ • Rust backend │ +└─────────────────────┘ └─────────────────────┘ +``` + +## Target Machine Setup + +**SSH Access:** +```bash +ssh archipelago@192.168.1.228 +# Password: archipelago +``` + +**Required packages on target (install once):** +```bash +sudo apt update && sudo apt install -y \ + nodejs npm \ + rustc cargo \ + git \ + build-essential +``` + +## Development Commands + +### Sync Code to Target +```bash +# From Mac - sync entire project +rsync -avz --exclude 'node_modules' --exclude 'target' --exclude 'dist' \ + /Users/dorian/Projects/archy/ \ + archipelago@192.168.1.228:/home/archipelago/archy/ + +# Or just the frontend +rsync -avz --exclude 'node_modules' \ + /Users/dorian/Projects/archy/neode-ui/ \ + archipelago@192.168.1.228:/home/archipelago/archy/neode-ui/ +``` + +### Frontend Development (Vue.js) +```bash +# On target via SSH +cd ~/archy/neode-ui +npm install +npm run dev -- --host 0.0.0.0 + +# Access from Mac browser: http://192.168.1.228:5173 +``` + +### Backend Development (Rust) +```bash +# On target via SSH +cd ~/archy/core +cargo build --release + +# Test run +./target/release/archipelago +``` + +### Quick Deploy Script +Create `~/deploy.sh` on Mac: +```bash +#!/bin/bash +TARGET="archipelago@192.168.1.228" +PROJECT="/Users/dorian/Projects/archy" + +# Sync code +rsync -avz --exclude 'node_modules' --exclude 'target' --exclude 'dist' \ + "$PROJECT/" "$TARGET:/home/archipelago/archy/" + +# Rebuild on target +ssh $TARGET "cd ~/archy/neode-ui && npm install && npm run build" +ssh $TARGET "cd ~/archy/core && cargo build --release" + +# Deploy to live system +ssh $TARGET "sudo cp ~/archy/core/target/release/archipelago /usr/local/bin/" +ssh $TARGET "sudo cp -r ~/archy/neode-ui/dist/* /opt/archipelago/web-ui/" +ssh $TARGET "sudo systemctl restart archipelago" + +echo "Deployed! Check http://192.168.1.228" +``` + +## ISO Builds + +ISO builds still happen on Mac (requires Docker Desktop for creating rootfs): + +```bash +cd /Users/dorian/Projects/archy/image-recipe +./build-auto-installer-iso.sh +``` + +**Docker Desktop is required for:** +- Building the Debian rootfs tarball +- Creating squashfs overlay modules +- Pulling/saving container images for bundling + +## File Locations + +| Component | Mac (Source) | Target (Dev) | Target (Live) | +|-----------|--------------|--------------|---------------| +| Frontend | `neode-ui/` | `~/archy/neode-ui/` | `/opt/archipelago/web-ui/` | +| Backend | `core/` | `~/archy/core/` | `/usr/local/bin/archipelago` | +| App manifests | `apps/` | `~/archy/apps/` | `/etc/archipelago/apps/` | + +## What You Can Remove from Mac + +**Keep:** +- Docker Desktop (needed for ISO builds) +- Node.js/npm (for local editing/linting) +- Cursor IDE + +**Can remove:** +- Any local test containers +- Podman (if installed) +- Local development servers (test on target instead) + +## Workflow Summary + +1. **Edit** code in Cursor on Mac +2. **Sync** to HP ProDesk with rsync +3. **Test** on target (run dev server or deploy to live) +4. **Iterate** until working +5. **Build ISO** on Mac when ready for distribution +6. **Flash & test** ISO on HP ProDesk diff --git a/core/Cargo.toml b/core/Cargo.toml index dc546e25..1bcd3e51 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -19,11 +19,5 @@ opt-level = 0 [profile.test] opt-level = 3 -[profile.dev.package.backtrace] -opt-level = 3 - -[profile.dev.package.sqlx-macros] -opt-level = 3 - # Archipelago workspace - no StartOS dependencies # All patches removed - we use standard crates.io dependencies diff --git a/core/archipelago/src/config.rs b/core/archipelago/src/config.rs index 2581d1e9..91cb2ffb 100644 --- a/core/archipelago/src/config.rs +++ b/core/archipelago/src/config.rs @@ -142,8 +142,8 @@ impl Default for Config { fn default() -> Self { Self { data_dir: PathBuf::from("/var/lib/archipelago"), - bind_host: "127.0.0.1".to_string(), - bind_port: 5959, + bind_host: "0.0.0.0".to_string(), + bind_port: 5678, log_level: "info".to_string(), dev_mode: false, container_runtime: ContainerRuntime::Auto, diff --git a/core/archipelago/src/server.rs b/core/archipelago/src/server.rs index afa534de..2690e8c1 100644 --- a/core/archipelago/src/server.rs +++ b/core/archipelago/src/server.rs @@ -14,7 +14,7 @@ use tracing::{error, info}; pub struct Server { _config: Config, api_handler: Arc, - state_manager: Arc, + _state_manager: Arc, } impl Server { @@ -48,7 +48,7 @@ impl Server { Ok(Self { _config: config, api_handler, - state_manager, + _state_manager: state_manager, }) } diff --git a/core/container/src/podman_client.rs b/core/container/src/podman_client.rs index a7ac5f99..f6f572d5 100644 --- a/core/container/src/podman_client.rs +++ b/core/container/src/podman_client.rs @@ -49,14 +49,14 @@ impl From<&str> for ContainerState { } pub struct PodmanClient { - user: String, + _user: String, rootless: bool, } impl PodmanClient { pub fn new(user: String) -> Self { Self { - user, + _user: user, rootless: true, } } diff --git a/core/container/src/runtime.rs b/core/container/src/runtime.rs index 9cf541be..16317eac 100644 --- a/core/container/src/runtime.rs +++ b/core/container/src/runtime.rs @@ -82,12 +82,12 @@ impl ContainerRuntime for PodmanRuntime { } pub struct DockerRuntime { - user: String, + _user: String, } impl DockerRuntime { pub fn new(user: String) -> Self { - Self { user } + Self { _user: user } } fn docker_async(&self) -> TokioCommand { diff --git a/image-recipe/archipelago-scripts/archipelago-menu.sh b/image-recipe/archipelago-scripts/archipelago-menu.sh index 9304f4e8..c16a985b 100755 --- a/image-recipe/archipelago-scripts/archipelago-menu.sh +++ b/image-recipe/archipelago-scripts/archipelago-menu.sh @@ -96,12 +96,24 @@ main_menu() { show_banner show_status - # Show Web UI URL + # Show Web UI URL prominently IP=$(hostname -I 2>/dev/null | awk '{print $1}') + [ -z "$IP" ] && IP=$(ip -4 addr show | grep -oP '(?<=inet\s)\d+(\.\d+){3}' | grep -v '127.0.0.1' | head -1) + + echo " ┌─────────────────────────────────────────────────────────────┐" if [ -n "$IP" ]; then - echo " 🌐 Web UI: http://$IP" - echo "" + # Check if backend is running + if pgrep -f "archipelago" >/dev/null 2>&1; then + echo " │ 🌐 Web UI: http://$IP:5678 (running) │" + else + echo " │ 🌐 Web UI: http://$IP:5678 (not started) │" + fi + echo " │ 📡 SSH: ssh user@$IP (password: archipelago) │" + else + echo " │ 🌐 Web UI: (no network) │" fi + echo " └─────────────────────────────────────────────────────────────┘" + echo "" echo " Main Menu:" echo " ─────────────────────────────────────────────────────────────" @@ -122,17 +134,34 @@ main_menu() { case $choice in w|W) - if [ -f "$SCRIPT_DIR/start-web-ui.sh" ]; then - sudo bash "$SCRIPT_DIR/start-web-ui.sh" 80 - else - # Try to find and start web UI - for path in /opt/archipelago/scripts /run/live/medium/archipelago/scripts; do - if [ -f "$path/start-web-ui.sh" ]; then - sudo bash "$path/start-web-ui.sh" 80 - break + echo "" + # Start the real backend on port 5678 + if command -v archipelago >/dev/null 2>&1; then + if pgrep -f "archipelago" >/dev/null 2>&1; then + echo " ✅ Archipelago backend already running on port 5678" + else + echo " 🚀 Starting Archipelago backend on port 5678..." + nohup archipelago >/tmp/archipelago.log 2>&1 & + sleep 2 + if pgrep -f "archipelago" >/dev/null 2>&1; then + echo " ✅ Backend started!" + else + echo " ⚠️ Failed to start backend. Check /tmp/archipelago.log" fi - done + fi + IP=$(hostname -I 2>/dev/null | awk '{print $1}') + echo "" + echo " ┌─────────────────────────────────────────────────────────────┐" + echo " │ 🌐 Open in browser: http://$IP:5678 │" + echo " └─────────────────────────────────────────────────────────────┘" + else + echo " ⚠️ Archipelago binary not found at /usr/local/bin/archipelago" + echo "" + echo " Try running:" + echo " sudo cp /run/live/medium/archipelago/bin/archipelago /usr/local/bin/" fi + echo "" + read -p " Press Enter to continue..." ;; 1) if [ -f "$SCRIPT_DIR/install-to-disk.sh" ]; then diff --git a/image-recipe/archipelago-scripts/install-to-disk.sh b/image-recipe/archipelago-scripts/install-to-disk.sh index fd424235..e3364b86 100755 --- a/image-recipe/archipelago-scripts/install-to-disk.sh +++ b/image-recipe/archipelago-scripts/install-to-disk.sh @@ -117,10 +117,13 @@ fi echo "⚙️ Configuring system..." -# Mount virtual filesystems +# Mount virtual filesystems for chroot mount --bind /dev /mnt/archipelago/dev +mount --bind /dev/pts /mnt/archipelago/dev/pts mount --bind /proc /mnt/archipelago/proc mount --bind /sys /mnt/archipelago/sys +mount --bind /sys/firmware/efi/efivars /mnt/archipelago/sys/firmware/efi/efivars 2>/dev/null || true +mount --bind /run /mnt/archipelago/run # Create fstab cat > /mnt/archipelago/etc/fstab < /mnt/archipelago/etc/hosts < /etc/apt/sources.list < /mnt/archipelago/etc/apt/sources.list <&1 || \ + grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=archipelago --removable 2>&1 || \ + echo '⚠️ GRUB install had issues, trying alternative...' +" +chroot /mnt/archipelago update-grub -# Enable services -systemctl enable NetworkManager -systemctl enable ssh -systemctl enable podman +echo "👤 Creating archipelago user..." +# Create user first, then add to groups that exist +chroot /mnt/archipelago useradd -m -s /bin/bash archipelago || echo "User may already exist" +chroot /mnt/archipelago usermod -aG sudo archipelago || true +chroot /mnt/archipelago usermod -aG podman archipelago 2>/dev/null || true -# Create Archipelago directories -mkdir -p /var/lib/archipelago/{data,config,containers} -mkdir -p /etc/archipelago -chown -R archipelago:archipelago /var/lib/archipelago +# Set password using chpasswd +echo "archipelago:archipelago" | chroot /mnt/archipelago chpasswd -echo "✅ Base system installed" -CHROOT_EOF +echo "⚙️ Enabling services..." +chroot /mnt/archipelago systemctl enable NetworkManager || true +chroot /mnt/archipelago systemctl enable ssh || chroot /mnt/archipelago systemctl enable sshd || true + +echo "📁 Creating Archipelago directories..." +chroot /mnt/archipelago mkdir -p /var/lib/archipelago/{data,config,containers} +chroot /mnt/archipelago mkdir -p /etc/archipelago +chroot /mnt/archipelago chown -R archipelago:archipelago /var/lib/archipelago + +echo "✅ Base system configured" # Copy Archipelago files echo "📋 Installing Archipelago components..." @@ -228,7 +239,7 @@ if [ -n "$BOOT_MEDIA" ]; then cp -r "$BOOT_MEDIA/archipelago/apps" /mnt/archipelago/etc/archipelago/ fi - # Create profile.d script for auto-menu on login + # Create profile.d script for welcome message on login mkdir -p /mnt/archipelago/etc/profile.d cat > /mnt/archipelago/etc/profile.d/archipelago.sh <<'PROFILE_EOF' #!/bin/bash @@ -237,26 +248,60 @@ if [ -t 0 ] && [ -z "$ARCHIPELAGO_WELCOMED" ]; then export ARCHIPELAGO_WELCOMED=1 IP=$(hostname -I 2>/dev/null | awk '{print $1}') echo "" - echo " 🏝️ Welcome to Archipelago Bitcoin Node OS" + echo " ╔═══════════════════════════════════════════════════════════╗" + echo " ║ 🏝️ ARCHIPELAGO BITCOIN NODE OS ║" + echo " ╚═══════════════════════════════════════════════════════════╝" echo "" if [ -n "$IP" ]; then - echo " Web UI: http://$IP" + echo " ┌─────────────────────────────────────────────────────────────┐" + echo " │ 🌐 Web UI: http://$IP:5678 │" + echo " │ 📡 SSH: ssh archipelago@$IP │" + echo " └─────────────────────────────────────────────────────────────┘" + echo "" fi - echo " Menu: Type 'archipelago' to open setup menu" + echo " Commands:" + echo " archipelago - Start backend server" + echo " archipelago-menu - Open setup menu" echo "" fi PROFILE_EOF chmod +x /mnt/archipelago/etc/profile.d/archipelago.sh + + # Create systemd service to auto-start archipelago backend + mkdir -p /mnt/archipelago/etc/systemd/system + cat > /mnt/archipelago/etc/systemd/system/archipelago.service <<'SERVICE_EOF' +[Unit] +Description=Archipelago Backend Server +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +User=archipelago +ExecStart=/usr/local/bin/archipelago +Restart=on-failure +RestartSec=5 + +[Install] +WantedBy=multi-user.target +SERVICE_EOF + + # Enable the service + chroot /mnt/archipelago systemctl enable archipelago.service 2>/dev/null || true fi echo "🧹 Cleaning up..." -# Unmount -umount /mnt/archipelago/dev -umount /mnt/archipelago/proc -umount /mnt/archipelago/sys -umount /mnt/archipelago/boot/efi -umount /mnt/archipelago +# Unmount in reverse order +sync +umount /mnt/archipelago/run 2>/dev/null || true +umount /mnt/archipelago/sys/firmware/efi/efivars 2>/dev/null || true +umount /mnt/archipelago/sys 2>/dev/null || true +umount /mnt/archipelago/proc 2>/dev/null || true +umount /mnt/archipelago/dev/pts 2>/dev/null || true +umount /mnt/archipelago/dev 2>/dev/null || true +umount /mnt/archipelago/boot/efi 2>/dev/null || true +umount /mnt/archipelago 2>/dev/null || true echo "" echo "╔═══════════════════════════════════════════════════════════╗" diff --git a/image-recipe/build-auto-installer-iso.sh b/image-recipe/build-auto-installer-iso.sh new file mode 100755 index 00000000..f71a926e --- /dev/null +++ b/image-recipe/build-auto-installer-iso.sh @@ -0,0 +1,962 @@ +#!/bin/bash +# +# Build Archipelago Auto-Installer ISO (StartOS-like) +# +# This creates an ISO that automatically installs to the internal disk +# with minimal user interaction - similar to StartOS experience. +# +# Features: +# - Pre-built root filesystem (no network needed during install) +# - Auto-detects internal disk (skips USB boot drive) +# - Automatic installation with progress display +# - Boots directly to web UI after install +# +# Usage: ./build-auto-installer-iso.sh +# + +set -e + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +WORK_DIR="$SCRIPT_DIR/build/auto-installer" +OUTPUT_DIR="$SCRIPT_DIR/results" +ROOTFS_DIR="$WORK_DIR/rootfs" +INSTALLER_DIR="$WORK_DIR/installer" + +echo "╔════════════════════════════════════════════════════════════════╗" +echo "║ Building Archipelago Auto-Installer ISO (StartOS-like) ║" +echo "╚════════════════════════════════════════════════════════════════╝" +echo "" + +# Check for required tools +check_tools() { + local missing="" + for tool in docker xorriso; do + if ! command -v $tool >/dev/null 2>&1; then + missing="$missing $tool" + fi + done + if [ -n "$missing" ]; then + echo "❌ Missing required tools:$missing" + echo " Install with: brew install$missing" + exit 1 + fi +} + +check_tools + +mkdir -p "$WORK_DIR" +mkdir -p "$OUTPUT_DIR" + +# ============================================================================= +# STEP 1: Build complete root filesystem using Docker +# ============================================================================= +echo "📦 Step 1: Building root filesystem..." + +ROOTFS_TAR="$WORK_DIR/archipelago-rootfs.tar" + +if [ ! -f "$ROOTFS_TAR" ] || [ "$1" == "--rebuild" ]; then + echo " Using Docker to create Debian root filesystem..." + + # Create a Dockerfile for building the rootfs + cat > "$WORK_DIR/Dockerfile.rootfs" <<'DOCKERFILE' +FROM debian:bookworm + +ENV DEBIAN_FRONTEND=noninteractive + +# Install all packages we need including nginx and podman +RUN apt-get update && apt-get install -y \ + linux-image-amd64 \ + grub-efi-amd64 \ + grub-efi-amd64-signed \ + shim-signed \ + systemd \ + systemd-sysv \ + dbus \ + sudo \ + network-manager \ + openssh-server \ + nginx \ + podman \ + curl \ + wget \ + htop \ + vim-tiny \ + ca-certificates \ + locales \ + console-setup \ + keyboard-configuration \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# Configure locale +RUN echo "en_US.UTF-8 UTF-8" > /etc/locale.gen && locale-gen + +# Create archipelago user +RUN useradd -m -s /bin/bash -G sudo archipelago && \ + echo "archipelago:archipelago" | chpasswd && \ + echo "archipelago ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/archipelago + +# Set hostname +RUN echo "archipelago" > /etc/hostname + +# Configure SSH +RUN mkdir -p /etc/ssh && \ + sed -i 's/#PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config || true && \ + sed -i 's/#PasswordAuthentication.*/PasswordAuthentication yes/' /etc/ssh/sshd_config || true + +# Configure nginx for Archipelago +RUN rm -f /etc/nginx/sites-enabled/default +COPY nginx-archipelago.conf /etc/nginx/sites-available/archipelago +RUN ln -sf /etc/nginx/sites-available/archipelago /etc/nginx/sites-enabled/archipelago + +# Create archipelago systemd service +COPY archipelago.service /etc/systemd/system/archipelago.service + +# Enable services +RUN systemctl enable NetworkManager || true && \ + systemctl enable ssh || true && \ + systemctl enable nginx || true && \ + systemctl enable archipelago || true + +# Create directories +RUN mkdir -p /var/lib/archipelago/{data,config,containers} && \ + mkdir -p /etc/archipelago && \ + mkdir -p /opt/archipelago/{bin,scripts,web-ui} && \ + chown -R archipelago:archipelago /var/lib/archipelago /opt/archipelago + +# Clean up +RUN apt-get clean && \ + rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* +DOCKERFILE + + # Create nginx config for the Docker build + cat > "$WORK_DIR/nginx-archipelago.conf" <<'NGINXCONF' +server { + listen 80; + server_name _; + + root /opt/archipelago/web-ui; + index index.html; + + # Serve static files (Vue.js SPA) + location / { + try_files $uri $uri/ /index.html; + } + + # Proxy API requests to backend + location /rpc/ { + proxy_pass http://127.0.0.1:5678; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + } + + # Proxy WebSocket + location /ws { + proxy_pass http://127.0.0.1:5678; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + } +} +NGINXCONF + + # Create systemd service for the Docker build + cat > "$WORK_DIR/archipelago.service" <<'SYSTEMDSERVICE' +[Unit] +Description=Archipelago Backend +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +User=archipelago +Environment="ARCHIPELAGO_BIND=0.0.0.0:5678" +Environment="ARCHIPELAGO_DEV_MODE=true" +ExecStart=/usr/local/bin/archipelago +Restart=on-failure +RestartSec=5 + +[Install] +WantedBy=multi-user.target +SYSTEMDSERVICE + + echo " Building Docker image (this may take a few minutes)..." + docker build --platform linux/amd64 -t archipelago-rootfs -f "$WORK_DIR/Dockerfile.rootfs" "$WORK_DIR" + + echo " Exporting filesystem..." + docker create --platform linux/amd64 --name archipelago-rootfs-tmp archipelago-rootfs + docker export archipelago-rootfs-tmp > "$ROOTFS_TAR" + docker rm archipelago-rootfs-tmp + + echo "✅ Root filesystem created: $(du -h "$ROOTFS_TAR" | cut -f1)" +else + echo "✅ Using cached root filesystem: $(du -h "$ROOTFS_TAR" | cut -f1)" +fi + +# ============================================================================= +# STEP 2: Create installer environment +# ============================================================================= +echo "" +echo "📦 Step 2: Creating installer environment..." + +# Download Debian Live as our installer base +BASE_ISO="$WORK_DIR/debian-live-installer.iso" +if [ ! -f "$BASE_ISO" ] || [ $(stat -f%z "$BASE_ISO" 2>/dev/null || echo 0) -lt 100000000 ]; then + echo " Downloading Debian Live base..." + rm -f "$BASE_ISO" + curl -L -o "$BASE_ISO" \ + "https://sourceforge.net/projects/debian-live-respin-iso/files/standard/live-image-debian12.11-standard-20250522-amd64.hybrid.iso/download" +fi + +echo " Extracting installer base..." +INSTALLER_ISO="$WORK_DIR/installer-iso" +rm -rf "$INSTALLER_ISO" +mkdir -p "$INSTALLER_ISO" +cd "$INSTALLER_ISO" +7z x -y "$BASE_ISO" >/dev/null 2>&1 || 7z x -y "$BASE_ISO" + +# ============================================================================= +# STEP 3: Add Archipelago components +# ============================================================================= +echo "" +echo "📦 Step 3: Adding Archipelago components..." + +ARCH_DIR="$INSTALLER_ISO/archipelago" +mkdir -p "$ARCH_DIR" +mkdir -p "$ARCH_DIR/bin" +mkdir -p "$ARCH_DIR/scripts" + +# Copy the pre-built rootfs +echo " Including root filesystem..." +cp "$ROOTFS_TAR" "$ARCH_DIR/rootfs.tar" + +# Copy backend binary +if [ -f "$SCRIPT_DIR/../core/target/release/archipelago" ]; then + echo " Including backend binary..." + cp "$SCRIPT_DIR/../core/target/release/archipelago" "$ARCH_DIR/bin/" +fi + +# Copy web UI (check both possible locations) +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" +elif [ -d "$SCRIPT_DIR/../neode-ui/dist" ]; then + echo " Including web UI from neode-ui/dist..." + cp -r "$SCRIPT_DIR/../neode-ui/dist" "$ARCH_DIR/web-ui" +else + echo " ⚠️ Web UI not found - build it first with: cd neode-ui && npm run build" +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" + +# Define images to bundle (space-separated: image filename) +CONTAINER_IMAGES=" +bitcoinknots/bitcoin:29 bitcoin-knots.tar +lightninglabs/lnd:v0.18.4-beta lnd.tar +ghcr.io/home-assistant/home-assistant:stable homeassistant.tar +" + +# Pull and save each image (force AMD64 for x86_64 target) +echo "$CONTAINER_IMAGES" | while read -r image filename; do + [ -z "$image" ] && continue + tarpath="$IMAGES_DIR/$filename" + + if [ -f "$tarpath" ]; then + echo " ✅ Using cached: $filename" + else + echo " Pulling $image (linux/amd64)..." + if docker pull --platform linux/amd64 "$image"; then + echo " Saving $filename..." + docker save "$image" -o "$tarpath" + echo " ✅ Saved: $(du -h "$tarpath" | cut -f1)" + else + echo " ⚠️ Failed to pull $image - skipping" + fi + fi +done + +# Create first-boot service to load images into Podman +echo " Creating first-boot image loader service..." +cat > "$WORK_DIR/archipelago-load-images.service" <<'LOADSERVICE' +[Unit] +Description=Load Archipelago Container Images +After=network.target podman.service +ConditionPathExists=/opt/archipelago/container-images +ConditionPathExists=!/var/lib/archipelago/.images-loaded + +[Service] +Type=oneshot +ExecStart=/opt/archipelago/scripts/load-container-images.sh +ExecStartPost=/usr/bin/touch /var/lib/archipelago/.images-loaded +RemainAfterExit=yes + +[Install] +WantedBy=multi-user.target +LOADSERVICE + +cat > "$WORK_DIR/load-container-images.sh" <<'LOADSCRIPT' +#!/bin/bash +# Load pre-bundled container images into Podman + +IMAGES_DIR="/opt/archipelago/container-images" +LOG_FILE="/var/log/archipelago-images.log" + +echo "$(date): Starting container image load" >> "$LOG_FILE" + +if [ ! -d "$IMAGES_DIR" ]; then + echo "$(date): No images directory found" >> "$LOG_FILE" + exit 0 +fi + +for tarfile in "$IMAGES_DIR"/*.tar; do + if [ -f "$tarfile" ]; then + echo "$(date): Loading $(basename "$tarfile")..." >> "$LOG_FILE" + podman load -i "$tarfile" >> "$LOG_FILE" 2>&1 && \ + echo "$(date): Successfully loaded $(basename "$tarfile")" >> "$LOG_FILE" || \ + echo "$(date): Failed to load $(basename "$tarfile")" >> "$LOG_FILE" + fi +done + +echo "$(date): Container image load complete" >> "$LOG_FILE" +echo "$(date): Available images:" >> "$LOG_FILE" +podman images >> "$LOG_FILE" 2>&1 +LOADSCRIPT + +chmod +x "$WORK_DIR/load-container-images.sh" + +# Copy scripts to ISO +mkdir -p "$ARCH_DIR/scripts" +cp "$WORK_DIR/load-container-images.sh" "$ARCH_DIR/scripts/" +cp "$WORK_DIR/archipelago-load-images.service" "$ARCH_DIR/scripts/" + +echo " ✅ Container images bundled" + +# ============================================================================= +# STEP 4: Create auto-installer script +# ============================================================================= +echo "" +echo "📦 Step 4: Creating auto-installer..." + +cat > "$ARCH_DIR/auto-install.sh" <<'INSTALLER_SCRIPT' +#!/bin/bash +# +# Archipelago Auto-Installer +# Automatically installs to internal disk (StartOS-like experience) +# + +set -e + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +clear +echo "" +echo -e "${BLUE}╔═══════════════════════════════════════════════════════════════════╗${NC}" +echo -e "${BLUE}║ ║${NC}" +echo -e "${BLUE}║ ${GREEN}🏝️ ARCHIPELAGO BITCOIN NODE OS${BLUE} ║${NC}" +echo -e "${BLUE}║ ║${NC}" +echo -e "${BLUE}║ ${NC}Automatic Installation${BLUE} ║${NC}" +echo -e "${BLUE}║ ║${NC}" +echo -e "${BLUE}╚═══════════════════════════════════════════════════════════════════╝${NC}" +echo "" + +# Check required tools are present (should be bundled in ISO) +echo -e "${YELLOW}🔧 Checking installer tools...${NC}" +MISSING="" +command -v parted >/dev/null 2>&1 || MISSING="parted $MISSING" +command -v mkfs.vfat >/dev/null 2>&1 || MISSING="mkfs.vfat $MISSING" +command -v mkfs.ext4 >/dev/null 2>&1 || MISSING="mkfs.ext4 $MISSING" + +if [ -n "$MISSING" ]; then + echo " Missing tools: $MISSING" + echo " Attempting to install (requires network)..." + if apt-get update -qq >/dev/null 2>&1; then + apt-get install -y -qq parted dosfstools e2fsprogs >/dev/null 2>&1 && echo " ✅ Tools installed" || { + echo -e "${RED}❌ Failed to install required tools. No network?${NC}" + echo " Required: parted, mkfs.vfat, mkfs.ext4" + exit 1 + } + else + echo -e "${RED}❌ No network available and tools not bundled.${NC}" + exit 1 + fi +else + echo " ✅ All tools present" +fi +echo "" + +# Find boot media +BOOT_MEDIA="" +for dev in /run/live/medium /lib/live/mount/medium /cdrom; do + if [ -d "$dev/archipelago" ]; then + BOOT_MEDIA="$dev" + break + fi +done + +if [ -z "$BOOT_MEDIA" ]; then + echo -e "${RED}❌ Boot media not found${NC}" + exit 1 +fi + +ROOTFS_TAR="$BOOT_MEDIA/archipelago/rootfs.tar" +if [ ! -f "$ROOTFS_TAR" ]; then + echo -e "${RED}❌ Root filesystem not found: $ROOTFS_TAR${NC}" + exit 1 +fi + +# Find the boot USB device to exclude it +BOOT_DEV=$(findmnt -n -o SOURCE "$BOOT_MEDIA" 2>/dev/null | sed 's/[0-9]*$//' | sed 's/p[0-9]*$//') +BOOT_DEV_NAME=$(basename "$BOOT_DEV" 2>/dev/null || echo "") + +echo -e "${YELLOW}📋 Detecting disks...${NC}" +echo "" + +# Find internal disk (prefer NVMe, then SATA, skip USB) +TARGET_DISK="" +TARGET_SIZE="" + +# Check NVMe drives first +for disk in /dev/nvme*n1; do + if [ -b "$disk" ]; then + disk_name=$(basename "$disk") + if [ "$disk_name" != "$BOOT_DEV_NAME" ]; then + size=$(lsblk -dn -o SIZE "$disk" 2>/dev/null) + model=$(cat /sys/block/$disk_name/device/model 2>/dev/null || echo "NVMe SSD") + echo " Found: $disk ($size) - $model" + TARGET_DISK="$disk" + TARGET_SIZE="$size" + break + fi + fi +done + +# If no NVMe, check SATA drives +if [ -z "$TARGET_DISK" ]; then + for disk in /dev/sd[a-z]; do + if [ -b "$disk" ]; then + disk_name=$(basename "$disk") + if [ "$disk_name" != "$BOOT_DEV_NAME" ]; then + # Skip USB drives (check removable flag) + removable=$(cat /sys/block/$disk_name/removable 2>/dev/null || echo "0") + if [ "$removable" = "0" ]; then + size=$(lsblk -dn -o SIZE "$disk" 2>/dev/null) + model=$(cat /sys/block/$disk_name/device/model 2>/dev/null || echo "SATA Drive") + echo " Found: $disk ($size) - $model" + TARGET_DISK="$disk" + TARGET_SIZE="$size" + break + fi + fi + fi + done +fi + +if [ -z "$TARGET_DISK" ]; then + echo "" + echo -e "${RED}❌ No suitable internal disk found${NC}" + echo " Please ensure an internal drive is connected." + exit 1 +fi + +echo "" +echo -e "${GREEN}✅ Target disk: $TARGET_DISK ($TARGET_SIZE)${NC}" +echo "" +echo -e "${RED}⚠️ WARNING: ALL DATA ON $TARGET_DISK WILL BE ERASED${NC}" +echo "" +echo "Press Enter to install Archipelago, or Ctrl+C to cancel..." +read + +echo "" +echo -e "${YELLOW}🔧 Installing Archipelago...${NC}" +echo "" + +# Unmount any existing partitions +umount ${TARGET_DISK}* 2>/dev/null || true +umount ${TARGET_DISK}p* 2>/dev/null || true + +# Create partition table +echo " [1/6] Creating partitions..." +parted -s "$TARGET_DISK" mklabel gpt +parted -s "$TARGET_DISK" mkpart primary fat32 1MiB 513MiB +parted -s "$TARGET_DISK" set 1 esp on +parted -s "$TARGET_DISK" mkpart primary ext4 513MiB 100% + +sleep 2 + +# Determine partition names +if [[ "$TARGET_DISK" == *nvme* ]]; then + EFI_PART="${TARGET_DISK}p1" + ROOT_PART="${TARGET_DISK}p2" +else + EFI_PART="${TARGET_DISK}1" + ROOT_PART="${TARGET_DISK}2" +fi + +# Format partitions +echo " [2/6] Formatting partitions..." +mkfs.vfat -F32 -n EFI "$EFI_PART" +mkfs.ext4 -F -L archipelago "$ROOT_PART" + +# Mount +echo " [3/6] Mounting filesystems..." +mkdir -p /mnt/target +mount "$ROOT_PART" /mnt/target +mkdir -p /mnt/target/boot/efi +mount "$EFI_PART" /mnt/target/boot/efi + +# Extract rootfs +echo " [4/6] Installing system (this may take a few minutes)..." +tar -xf "$ROOTFS_TAR" -C /mnt/target + +# Create fstab +echo " [5/6] Configuring system..." +cat > /mnt/target/etc/fstab < /mnt/target/etc/hostname +cat > /mnt/target/etc/hosts </dev/null || true + chmod +x /mnt/target/usr/local/bin/* 2>/dev/null || true +fi + +if [ -d "$BOOT_MEDIA/archipelago/web-ui" ]; then + cp -r "$BOOT_MEDIA/archipelago/web-ui" /mnt/target/opt/archipelago/ +fi + +if [ -d "$BOOT_MEDIA/archipelago/apps" ]; then + cp -r "$BOOT_MEDIA/archipelago/apps" /mnt/target/etc/archipelago/ +fi + +# Copy pre-bundled container images +if [ -d "$BOOT_MEDIA/archipelago/container-images" ]; then + echo " Copying container images (this may take a moment)..." + mkdir -p /mnt/target/opt/archipelago/container-images + cp -r "$BOOT_MEDIA/archipelago/container-images/"*.tar /mnt/target/opt/archipelago/container-images/ 2>/dev/null || true + + # Copy first-boot loader script and service + mkdir -p /mnt/target/opt/archipelago/scripts + if [ -f "$BOOT_MEDIA/archipelago/scripts/load-container-images.sh" ]; then + cp "$BOOT_MEDIA/archipelago/scripts/load-container-images.sh" /mnt/target/opt/archipelago/scripts/ + chmod +x /mnt/target/opt/archipelago/scripts/load-container-images.sh + fi + + if [ -f "$BOOT_MEDIA/archipelago/scripts/archipelago-load-images.service" ]; then + cp "$BOOT_MEDIA/archipelago/scripts/archipelago-load-images.service" /mnt/target/etc/systemd/system/ + fi + + echo " ✅ Container images staged for first-boot loading" +fi + +# Ensure correct ownership (use numeric UID:GID 1000:1000 since we're outside chroot) +chown -R 1000:1000 /mnt/target/opt/archipelago 2>/dev/null || true +chown -R 1000:1000 /mnt/target/var/lib/archipelago 2>/dev/null || true + +# Create welcome profile (nginx serves on port 80) +cat > /mnt/target/etc/profile.d/archipelago.sh <<'PROFILE' +#!/bin/bash +if [ -t 0 ] && [ -z "$ARCHIPELAGO_WELCOMED" ]; then + export ARCHIPELAGO_WELCOMED=1 + IP=$(hostname -I 2>/dev/null | awk '{print $1}') + echo "" + echo " ╔═══════════════════════════════════════════════════════════╗" + echo " ║ 🏝️ ARCHIPELAGO BITCOIN NODE OS ║" + echo " ╚═══════════════════════════════════════════════════════════╝" + echo "" + if [ -n "$IP" ]; then + echo " 🌐 Web UI: http://$IP" + echo " 📡 SSH: ssh archipelago@$IP" + echo " 🔑 Password: archipelago (SSH) / password123 (Web UI)" + echo "" + fi +fi +PROFILE +chmod +x /mnt/target/etc/profile.d/archipelago.sh + +# Systemd service is already in rootfs, but ensure it has correct config +cat > /mnt/target/etc/systemd/system/archipelago.service <<'SERVICE' +[Unit] +Description=Archipelago Backend +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +User=archipelago +Environment="ARCHIPELAGO_BIND=0.0.0.0:5678" +Environment="ARCHIPELAGO_DEV_MODE=true" +ExecStart=/usr/local/bin/archipelago +Restart=on-failure +RestartSec=5 + +[Install] +WantedBy=multi-user.target +SERVICE + +# Install GRUB +echo " [6/6] Installing bootloader..." +mount --bind /dev /mnt/target/dev +mount --bind /dev/pts /mnt/target/dev/pts +mount --bind /proc /mnt/target/proc +mount --bind /sys /mnt/target/sys +mount --bind /run /mnt/target/run + +chroot /mnt/target grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=archipelago --removable 2>/dev/null || \ +chroot /mnt/target grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=archipelago 2>/dev/null || \ +echo " Warning: GRUB install had issues, trying alternative..." + +chroot /mnt/target update-grub + +# Enable services +chroot /mnt/target systemctl enable archipelago.service 2>/dev/null || true +chroot /mnt/target systemctl enable nginx.service 2>/dev/null || true +chroot /mnt/target systemctl enable archipelago-load-images.service 2>/dev/null || true + +# Cleanup +sync +umount /mnt/target/run 2>/dev/null || true +umount /mnt/target/sys 2>/dev/null || true +umount /mnt/target/proc 2>/dev/null || true +umount /mnt/target/dev/pts 2>/dev/null || true +umount /mnt/target/dev 2>/dev/null || true +umount /mnt/target/boot/efi 2>/dev/null || true +umount /mnt/target 2>/dev/null || true + +echo "" +echo -e "${GREEN}╔═══════════════════════════════════════════════════════════════════╗${NC}" +echo -e "${GREEN}║ ║${NC}" +echo -e "${GREEN}║ ✅ INSTALLATION COMPLETE! ║${NC}" +echo -e "${GREEN}║ ║${NC}" +echo -e "${GREEN}║ Remove the USB drive and press Enter to reboot. ║${NC}" +echo -e "${GREEN}║ ║${NC}" +echo -e "${GREEN}║ After reboot: ║${NC}" +echo -e "${GREEN}║ • Web UI: http:// ║${NC}" +echo -e "${GREEN}║ • SSH: ssh archipelago@ ║${NC}" +echo -e "${GREEN}║ • SSH Password: archipelago ║${NC}" +echo -e "${GREEN}║ • Web Password: password123 ║${NC}" +echo -e "${GREEN}║ ║${NC}" +echo -e "${GREEN}║ Pre-loaded apps (ready to start via Web UI): ║${NC}" +echo -e "${GREEN}║ • Bitcoin Knots • LND • Home Assistant ║${NC}" +echo -e "${GREEN}║ ║${NC}" +echo -e "${GREEN}╚═══════════════════════════════════════════════════════════════════╝${NC}" +echo "" +read -p "Press Enter to reboot..." +reboot +INSTALLER_SCRIPT + +chmod +x "$ARCH_DIR/auto-install.sh" + +# ============================================================================= +# STEP 5: Configure auto-start installer on boot +# ============================================================================= +echo "" +echo "📦 Step 5: Configuring auto-start..." + +# Create a squashfs overlay module that Debian Live will automatically load +# This is the proper way to add files to the live filesystem +OVERLAY_DIR="$WORK_DIR/overlay-root" +rm -rf "$OVERLAY_DIR" +mkdir -p "$OVERLAY_DIR/etc/profile.d" +mkdir -p "$OVERLAY_DIR/etc/skel" +mkdir -p "$OVERLAY_DIR/usr/local/bin" +mkdir -p "$OVERLAY_DIR/usr/sbin" +mkdir -p "$OVERLAY_DIR/sbin" + +# Download and extract required tools AND their libraries for offline use +echo " Downloading partitioning tools for offline use..." +TOOLS_DIR="$WORK_DIR/tools-extract" +rm -rf "$TOOLS_DIR" +mkdir -p "$TOOLS_DIR" + +docker run --rm --platform linux/amd64 \ + -v "$TOOLS_DIR:/output" \ + debian:bookworm \ + bash -c ' + apt-get update -qq + apt-get install -y -qq parted dosfstools e2fsprogs + + # Copy binaries + cp /usr/sbin/parted /output/ + cp /usr/sbin/mkfs.vfat /output/ 2>/dev/null || cp /sbin/mkfs.vfat /output/ 2>/dev/null || true + cp /usr/sbin/mkfs.ext4 /output/ 2>/dev/null || cp /sbin/mkfs.ext4 /output/ 2>/dev/null || true + cp /usr/sbin/mke2fs /output/ 2>/dev/null || cp /sbin/mke2fs /output/ 2>/dev/null || true + cp /sbin/mkfs.fat /output/ 2>/dev/null || true + + # Copy required shared libraries for parted + mkdir -p /output/lib + cp /lib/x86_64-linux-gnu/libparted.so* /output/lib/ 2>/dev/null || true + cp /usr/lib/x86_64-linux-gnu/libparted.so* /output/lib/ 2>/dev/null || true + cp /lib/x86_64-linux-gnu/libreadline.so* /output/lib/ 2>/dev/null || true + cp /usr/lib/x86_64-linux-gnu/libreadline.so* /output/lib/ 2>/dev/null || true + cp /lib/x86_64-linux-gnu/libdevmapper.so* /output/lib/ 2>/dev/null || true + cp /usr/lib/x86_64-linux-gnu/libdevmapper.so* /output/lib/ 2>/dev/null || true + + # List what parted actually needs + ldd /usr/sbin/parted 2>/dev/null | grep "=>" | awk "{print \$3}" | while read lib; do + [ -f "$lib" ] && cp "$lib" /output/lib/ 2>/dev/null || true + done + + echo "Libraries bundled:" + ls -la /output/lib/ + ' + +# Copy tools to overlay +cp "$TOOLS_DIR/parted" "$OVERLAY_DIR/usr/sbin/" 2>/dev/null || true +cp "$TOOLS_DIR/mkfs.vfat" "$OVERLAY_DIR/usr/sbin/" 2>/dev/null || true +cp "$TOOLS_DIR/mkfs.fat" "$OVERLAY_DIR/sbin/" 2>/dev/null || true +cp "$TOOLS_DIR/mkfs.ext4" "$OVERLAY_DIR/usr/sbin/" 2>/dev/null || true +cp "$TOOLS_DIR/mke2fs" "$OVERLAY_DIR/usr/sbin/" 2>/dev/null || true + +# Copy shared libraries +mkdir -p "$OVERLAY_DIR/usr/lib/x86_64-linux-gnu" +cp "$TOOLS_DIR/lib/"*.so* "$OVERLAY_DIR/usr/lib/x86_64-linux-gnu/" 2>/dev/null || true + +chmod +x "$OVERLAY_DIR/usr/sbin/"* 2>/dev/null || true +chmod +x "$OVERLAY_DIR/sbin/"* 2>/dev/null || true +echo " ✅ Partitioning tools and libraries bundled" + +# Create the auto-start profile script +cat > "$OVERLAY_DIR/etc/profile.d/z99-archipelago-installer.sh" <<'AUTOSTART' +#!/bin/bash +# Auto-start Archipelago installer on login + +# Only run once, only in interactive terminal, only for regular users +if [ -n "$INSTALLER_STARTED" ] || [ ! -t 0 ]; then + return 0 2>/dev/null || exit 0 +fi +export INSTALLER_STARTED=1 + +# Give system a moment to settle +sleep 1 + +clear +echo "" +echo " ╔═══════════════════════════════════════════════════════════════════╗" +echo " ║ ║" +echo " ║ 🏝️ ARCHIPELAGO BITCOIN NODE OS - INSTALLER ║" +echo " ║ ║" +echo " ╚═══════════════════════════════════════════════════════════════════╝" +echo "" + +# Find boot media +BOOT_MEDIA="" +for dev in /run/live/medium /lib/live/mount/medium /cdrom /media/cdrom; do + if [ -f "$dev/archipelago/auto-install.sh" ]; then + BOOT_MEDIA="$dev" + break + fi +done + +if [ -n "$BOOT_MEDIA" ]; then + echo " Found installer at: $BOOT_MEDIA" + echo "" + echo " Press Enter to start installation, or Ctrl+C for shell..." + read + sudo bash "$BOOT_MEDIA/archipelago/auto-install.sh" +else + echo " ⚠️ Installer not found on boot media." + echo "" + echo " Checked: /run/live/medium, /lib/live/mount/medium, /cdrom" + echo "" + echo " You can try manually:" + echo " sudo bash /path/to/archipelago/auto-install.sh" + echo "" +fi +AUTOSTART +chmod +x "$OVERLAY_DIR/etc/profile.d/z99-archipelago-installer.sh" + +# Also create .bashrc that sources the profile script (belt and suspenders) +cat > "$OVERLAY_DIR/etc/skel/.bashrc" <<'BASHRC' +# ~/.bashrc: executed by bash(1) for non-login shells. + +# If not running interactively, don't do anything +case $- in + *i*) ;; + *) return;; +esac + +# Source profile.d scripts +if [ -d /etc/profile.d ]; then + for i in /etc/profile.d/*.sh; do + if [ -r "$i" ]; then + . "$i" + fi + done +fi +BASHRC + +# Create a wrapper script that can be called directly +cat > "$OVERLAY_DIR/usr/local/bin/archipelago-install" <<'WRAPPER' +#!/bin/bash +# Archipelago installer wrapper + +BOOT_MEDIA="" +for dev in /run/live/medium /lib/live/mount/medium /cdrom /media/cdrom; do + if [ -f "$dev/archipelago/auto-install.sh" ]; then + BOOT_MEDIA="$dev" + break + fi +done + +if [ -n "$BOOT_MEDIA" ]; then + exec sudo bash "$BOOT_MEDIA/archipelago/auto-install.sh" +else + echo "Installer not found. Searched:" + echo " /run/live/medium, /lib/live/mount/medium, /cdrom, /media/cdrom" + exit 1 +fi +WRAPPER +chmod +x "$OVERLAY_DIR/usr/local/bin/archipelago-install" + +# Create the squashfs module +# Debian Live automatically loads all .squashfs files from live/ directory +echo " Creating overlay squashfs module..." +LIVE_DIR="$INSTALLER_ISO/live" +mkdir -p "$LIVE_DIR" + +# Check if mksquashfs is available (may need to use Docker on macOS) +if command -v mksquashfs >/dev/null 2>&1; then + mksquashfs "$OVERLAY_DIR" "$LIVE_DIR/99-archipelago.squashfs" -comp xz -noappend +else + # Use Docker to create squashfs on macOS + echo " Using Docker to create squashfs..." + docker run --rm --platform linux/amd64 \ + -v "$OVERLAY_DIR:/overlay:ro" \ + -v "$LIVE_DIR:/output" \ + debian:bookworm \ + bash -c "apt-get update && apt-get install -y squashfs-tools && mksquashfs /overlay /output/99-archipelago.squashfs -comp xz -noappend" +fi + +echo " ✅ Created overlay module: 99-archipelago.squashfs" + +# Modify GRUB config - update branding and ensure components are loaded +if [ -f "$INSTALLER_ISO/boot/grub/grub.cfg" ]; then + echo " Configuring GRUB..." + sed -i.bak \ + -e 's/Debian GNU\/Linux/Archipelago Installer/g' \ + -e 's/Live system/Install Archipelago/g' \ + "$INSTALLER_ISO/boot/grub/grub.cfg" + + # Ensure 'components' parameter is present (loads additional squashfs modules) + # Also add 'username=user' to ensure consistent username + if ! grep -q "components" "$INSTALLER_ISO/boot/grub/grub.cfg"; then + sed -i 's/boot=live/boot=live components/' "$INSTALLER_ISO/boot/grub/grub.cfg" + fi +fi + +if [ -f "$INSTALLER_ISO/isolinux/live.cfg" ]; then + echo " Configuring ISOLINUX..." + sed -i.bak \ + -e 's/Debian GNU\/Linux/Archipelago Installer/g' \ + -e 's/Live system/Install Archipelago/g' \ + "$INSTALLER_ISO/isolinux/live.cfg" + + # Add components parameter + if ! grep -q "components" "$INSTALLER_ISO/isolinux/live.cfg"; then + sed -i 's/boot=live/boot=live components/' "$INSTALLER_ISO/isolinux/live.cfg" + fi +fi + +if [ -f "$INSTALLER_ISO/isolinux/menu.cfg" ]; then + sed -i.bak \ + -e 's/Debian GNU\/Linux/Archipelago Installer/g' \ + "$INSTALLER_ISO/isolinux/menu.cfg" +fi + +# ============================================================================= +# STEP 6: Create final ISO +# ============================================================================= +echo "" +echo "📦 Step 6: Creating bootable ISO..." + +OUTPUT_ISO="$OUTPUT_DIR/archipelago-installer-x86_64.iso" + +# Get MBR for hybrid boot +ISOHDPFX="" +for path in \ + "/usr/local/share/syslinux/isohdpfx.bin" \ + "/usr/share/syslinux/isohdpfx.bin" \ + "/opt/homebrew/share/syslinux/isohdpfx.bin" \ + "$INSTALLER_ISO/isolinux/isohdpfx.bin"; do + if [ -f "$path" ]; then + ISOHDPFX="$path" + break + fi +done + +if [ -z "$ISOHDPFX" ]; then + echo " Extracting MBR from isolinux.bin..." + dd if="$INSTALLER_ISO/isolinux/isolinux.bin" of="$WORK_DIR/isohdpfx.bin" bs=432 count=1 2>/dev/null + ISOHDPFX="$WORK_DIR/isohdpfx.bin" +fi + +xorriso -as mkisofs -o "$OUTPUT_ISO" \ + -volid "ARCHIPELAGO" \ + -J -R \ + -isohybrid-mbr "$ISOHDPFX" \ + -c isolinux/boot.cat \ + -b isolinux/isolinux.bin \ + -no-emul-boot -boot-load-size 4 -boot-info-table \ + -eltorito-alt-boot \ + -e boot/grub/efi.img \ + -no-emul-boot \ + -isohybrid-gpt-basdat \ + "$INSTALLER_ISO" + +echo "" +echo "╔════════════════════════════════════════════════════════════════╗" +echo "║ ✅ AUTO-INSTALLER ISO CREATED! ║" +echo "╚════════════════════════════════════════════════════════════════╝" +echo "" +echo "📀 Output: $OUTPUT_ISO" +echo " Size: $(du -h "$OUTPUT_ISO" | cut -f1)" +echo "" +echo "🔥 This is a StartOS-like automatic installer!" +echo "" +echo "Features:" +echo " • Pre-built system (no internet needed during install)" +echo " • Auto-detects internal disk" +echo " • One-button installation" +echo " • Boots directly to Archipelago after install" +echo " • Pre-bundled container apps:" +echo " - Bitcoin Knots v29" +echo " - LND v0.18.4" +echo " - Home Assistant" +echo "" +echo "To create USB:" +echo " 1. Flash with: sudo dd if=$OUTPUT_ISO of=/dev/rdiskX bs=4m" +echo " Or use Balena Etcher" +echo " 2. Boot from USB" +echo " 3. Press Enter to install" +echo " 4. Remove USB and reboot" +echo "" diff --git a/image-recipe/build-debian-iso.sh b/image-recipe/build-debian-iso.sh index e7d997a6..cb1c82b9 100755 --- a/image-recipe/build-debian-iso.sh +++ b/image-recipe/build-debian-iso.sh @@ -314,9 +314,9 @@ ln -sf ../archipelago-ssh.service "$ISO_CUSTOM/etc/systemd/system/multi-user.tar # Create system-wide profile script that runs on ANY login mkdir -p "$ISO_CUSTOM/etc/profile.d" -cat > "$ISO_CUSTOM/etc/profile.d/archipelago.sh" <<'PROFILE_EOF' +cat > "$ISO_CUSTOM/etc/profile.d/z99-archipelago.sh" <<'PROFILE_EOF' #!/bin/bash -# Archipelago auto-start - runs on login +# Archipelago auto-start - runs on login (z99 = runs last) # Only run once per session and only in interactive shells if [ -n "$ARCHIPELAGO_STARTED" ] || [ ! -t 0 ]; then @@ -333,23 +333,97 @@ for dev in /run/live/medium /lib/live/mount/medium /cdrom; do fi done +# Get IP address +IP=$(hostname -I 2>/dev/null | awk '{print $1}') +[ -z "$IP" ] && IP=$(ip -4 addr show | grep -oP '(?<=inet\s)\d+(\.\d+){3}' | grep -v '127.0.0.1' | head -1) + +# Show welcome banner ALWAYS +clear +echo "" +echo " ╔═══════════════════════════════════════════════════════════╗" +echo " ║ ║" +echo " ║ 🏝️ ARCHIPELAGO BITCOIN NODE OS ║" +echo " ║ ║" +echo " ║ Your sovereign Bitcoin infrastructure ║" +echo " ║ ║" +echo " ╚═══════════════════════════════════════════════════════════╝" +echo "" +if [ -n "$IP" ]; then + echo " ┌─────────────────────────────────────────────────────────────┐" + echo " │ 🌐 Web UI: http://$IP:5678 │" + echo " │ 📡 SSH: ssh user@$IP (password: archipelago) │" + echo " └─────────────────────────────────────────────────────────────┘" + echo "" +fi + if [ -z "$BOOT_MEDIA" ]; then + echo " ⚠️ Boot media not found at /run/live/medium" + echo "" + echo " Manual commands:" + echo " archipelago - Start backend server" + echo " archipelago-menu - Open setup menu" + echo "" return 0 2>/dev/null || exit 0 fi -# Install archipelago command if not present +echo " 📍 Boot media: $BOOT_MEDIA" +echo "" + +# Install archipelago commands if not present if [ ! -f /usr/local/bin/archipelago ] && [ -f "$BOOT_MEDIA/archipelago/bin/archipelago" ]; then sudo cp "$BOOT_MEDIA/archipelago/bin/archipelago" /usr/local/bin/ 2>/dev/null sudo chmod +x /usr/local/bin/archipelago 2>/dev/null fi -# Run auto-start -if [ -f "$BOOT_MEDIA/archipelago/auto-start.sh" ]; then - source "$BOOT_MEDIA/archipelago/auto-start.sh" +if [ ! -f /usr/local/bin/archipelago-menu ] && [ -f "$BOOT_MEDIA/archipelago/bin/archipelago-menu" ]; then + sudo cp "$BOOT_MEDIA/archipelago/bin/archipelago-menu" /usr/local/bin/ 2>/dev/null + sudo chmod +x /usr/local/bin/archipelago-menu 2>/dev/null +fi + +# Copy scripts to /opt +if [ ! -d /opt/archipelago/scripts ] && [ -d "$BOOT_MEDIA/archipelago/scripts" ]; then + sudo mkdir -p /opt/archipelago + sudo cp -r "$BOOT_MEDIA/archipelago/scripts" /opt/archipelago/ 2>/dev/null + sudo chmod +x /opt/archipelago/scripts/*.sh 2>/dev/null +fi + +# Start web backend in background if available +if command -v archipelago >/dev/null 2>&1; then + if ! pgrep -f "archipelago" >/dev/null 2>&1; then + echo " 🚀 Starting Archipelago backend on port 5678..." + nohup archipelago >/tmp/archipelago.log 2>&1 & + sleep 2 + else + echo " ✅ Archipelago backend already running" + fi +fi + +echo "" +echo " Commands:" +echo " archipelago-menu - Open interactive setup menu" +echo " archipelago - Start/restart backend server" +echo "" +read -p " Press Enter to open the setup menu (or Ctrl+C to skip)... " + +# Launch the menu +if [ -f /opt/archipelago/scripts/archipelago-menu.sh ]; then + exec bash /opt/archipelago/scripts/archipelago-menu.sh +elif [ -f "$BOOT_MEDIA/archipelago/scripts/archipelago-menu.sh" ]; then + exec bash "$BOOT_MEDIA/archipelago/scripts/archipelago-menu.sh" fi PROFILE_EOF -chmod +x "$ISO_CUSTOM/etc/profile.d/archipelago.sh" +chmod +x "$ISO_CUSTOM/etc/profile.d/z99-archipelago.sh" + +# Also add to /etc/skel/.bashrc as fallback for new user sessions +mkdir -p "$ISO_CUSTOM/etc/skel" +cat >> "$ISO_CUSTOM/etc/skel/.bashrc" <<'BASHRC_EOF' + +# Archipelago auto-start fallback +if [ -z "$ARCHIPELAGO_STARTED" ] && [ -t 0 ]; then + [ -f /etc/profile.d/z99-archipelago.sh ] && source /etc/profile.d/z99-archipelago.sh +fi +BASHRC_EOF # Modify GRUB config for Archipelago branding echo "" diff --git a/image-recipe/build-debian-live-iso.sh b/image-recipe/build-debian-live-iso.sh new file mode 100755 index 00000000..cb1c82b9 --- /dev/null +++ b/image-recipe/build-debian-live-iso.sh @@ -0,0 +1,511 @@ +#!/bin/bash +# +# Build Archipelago Bitcoin Node OS ISO - Debian Live Edition +# Based on Debian Live for reliable USB boot (like StartOS) +# +# Usage: ./build-debian-iso.sh +# + +set -e + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +WORK_DIR="$SCRIPT_DIR/build/debian-iso" +OUTPUT_DIR="$SCRIPT_DIR/results" +DEBIAN_VERSION="bookworm" +ARCH="amd64" + +echo "╔════════════════════════════════════════════════════════╗" +echo "║ Building Archipelago - Debian Live Edition ║" +echo "╚════════════════════════════════════════════════════════╝" +echo "" + +# Create directories +mkdir -p "$WORK_DIR" +mkdir -p "$OUTPUT_DIR" + +# Download Debian Live ISO if not exists +BASE_ISO="$WORK_DIR/debian-live-12-${ARCH}-standard.iso" +if [ ! -f "$BASE_ISO" ] || [ $(stat -f%z "$BASE_ISO" 2>/dev/null || echo 0) -lt 100000000 ]; then + echo "📥 Downloading Debian Live 12 (Bookworm) Standard ISO..." + rm -f "$BASE_ISO" + # Use SourceForge respin which has up-to-date Debian 12 Live + curl -L -o "$BASE_ISO" \ + "https://sourceforge.net/projects/debian-live-respin-iso/files/standard/live-image-debian12.11-standard-20250522-amd64.hybrid.iso/download" + echo "✅ Downloaded Debian Live ISO" +else + echo "✅ Using cached Debian Live ISO" +fi + +# Extract ISO +echo "" +echo "📦 Extracting Debian Live ISO..." +ISO_CUSTOM="$WORK_DIR/custom" +rm -rf "$ISO_CUSTOM" +mkdir -p "$ISO_CUSTOM" + +cd "$ISO_CUSTOM" +7z x -y "$BASE_ISO" >/dev/null 2>&1 || 7z x -y "$BASE_ISO" + +echo "✅ Extracted ISO" + +# Add Archipelago files +echo "" +echo "📋 Adding Archipelago files..." + +ARCHIPELAGO_DIR="$ISO_CUSTOM/archipelago" +mkdir -p "$ARCHIPELAGO_DIR" +mkdir -p "$ARCHIPELAGO_DIR/bin" +mkdir -p "$ARCHIPELAGO_DIR/scripts" + +# Copy the pre-built backend if it exists +if [ -d "$SCRIPT_DIR/../core/target/release" ]; then + echo "🦀 Including Archipelago backend..." + cp "$SCRIPT_DIR/../core/target/release/archipelago" "$ARCHIPELAGO_DIR/bin/" 2>/dev/null || true +fi + +# Copy the frontend build if it exists +if [ -d "$SCRIPT_DIR/../web/dist/neode-ui" ]; then + echo "🎨 Including Archipelago Web UI..." + cp -r "$SCRIPT_DIR/../web/dist/neode-ui" "$ARCHIPELAGO_DIR/web-ui" +elif [ -d "$SCRIPT_DIR/../neode-ui/dist" ]; then + echo "🎨 Including Archipelago frontend..." + cp -r "$SCRIPT_DIR/../neode-ui/dist" "$ARCHIPELAGO_DIR/web-ui" 2>/dev/null || true +fi + +# Copy app manifests +if [ -d "$SCRIPT_DIR/../apps" ]; then + echo "📦 Including app manifests..." + cp -r "$SCRIPT_DIR/../apps" "$ARCHIPELAGO_DIR/apps" 2>/dev/null || true +fi + +# Copy setup scripts +if [ -d "$SCRIPT_DIR/archipelago-scripts" ]; then + echo "📜 Including setup scripts..." + cp "$SCRIPT_DIR/archipelago-scripts/"*.sh "$ARCHIPELAGO_DIR/scripts/" 2>/dev/null || true + chmod +x "$ARCHIPELAGO_DIR/scripts/"*.sh +fi + +# Create main setup script for Archipelago +cat > "$ARCHIPELAGO_DIR/setup-archipelago.sh" <<'SETUP_EOF' +#!/bin/bash +# +# Archipelago Setup Script for Debian Live +# +echo "" +echo "╔═══════════════════════════════════════════════════════════╗" +echo "║ 🏝️ ARCHIPELAGO BITCOIN NODE OS - Debian Edition ║" +echo "╚═══════════════════════════════════════════════════════════╝" +echo "" + +# Find the boot media +BOOT_MEDIA="" +for dev in /run/live/medium /lib/live/mount/medium /cdrom /media/cdrom; do + if [ -d "$dev/archipelago" ]; then + BOOT_MEDIA="$dev" + break + fi +done + +if [ -z "$BOOT_MEDIA" ]; then + echo "❌ Could not find Archipelago files on boot media" + echo " Looking in: /run/live/medium, /lib/live/mount/medium, /cdrom" + exit 1 +fi + +echo "📍 Found Archipelago at: $BOOT_MEDIA/archipelago" +echo "" + +# Copy files to system +if [ -d "$BOOT_MEDIA/archipelago/bin" ]; then + echo "📋 Installing Archipelago binaries..." + sudo cp -r "$BOOT_MEDIA/archipelago/bin/"* /usr/local/bin/ 2>/dev/null || true +fi + +if [ -d "$BOOT_MEDIA/archipelago/apps" ]; then + echo "📋 Installing app manifests..." + sudo mkdir -p /etc/archipelago + sudo cp -r "$BOOT_MEDIA/archipelago/apps" /etc/archipelago/ 2>/dev/null || true +fi + +if [ -d "$BOOT_MEDIA/archipelago/scripts" ]; then + echo "📋 Installing setup scripts..." + sudo mkdir -p /opt/archipelago + sudo cp -r "$BOOT_MEDIA/archipelago/scripts" /opt/archipelago/ 2>/dev/null || true + sudo chmod +x /opt/archipelago/scripts/*.sh +fi + +echo "" +echo "✅ Archipelago installed!" +echo "" + +# Automatically launch the menu +sleep 1 +exec /opt/archipelago/scripts/archipelago-menu.sh +SETUP_EOF + +chmod +x "$ARCHIPELAGO_DIR/setup-archipelago.sh" + +# Create auto-start script that runs on login +cat > "$ARCHIPELAGO_DIR/auto-start.sh" <<'AUTOSTART_EOF' +#!/bin/bash +# +# Archipelago Auto-Start - Runs on first login +# + +# 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 + +# Install essential tools on first boot (required for disk installer) +if [ ! -f /tmp/.archipelago-tools-installed ]; then + clear + echo "" + echo " ╔═══════════════════════════════════════════════════════════╗" + echo " ║ 🏝️ ARCHIPELAGO - First Boot Setup ║" + echo " ╚═══════════════════════════════════════════════════════════╝" + echo "" + echo " 📦 Installing required tools..." + echo "" + + # Update and install essential tools + sudo apt-get update -qq 2>/dev/null + sudo apt-get install -y parted debootstrap dosfstools e2fsprogs 2>/dev/null + + echo " ✅ Tools installed" + touch /tmp/.archipelago-tools-installed + sleep 1 +fi + +# Get IP address +IP=$(hostname -I 2>/dev/null | awk '{print $1}') + +# Start web UI in background if not already running +if [ -n "$BOOT_MEDIA" ] && ! pgrep -f "http.server" >/dev/null; then + WEB_UI_DIR="$BOOT_MEDIA/archipelago/web-ui" + if [ -d "$WEB_UI_DIR" ]; then + cd "$WEB_UI_DIR" + nohup python3 -m http.server 80 --bind 0.0.0.0 >/dev/null 2>&1 & + fi +fi + +clear +echo "" +echo " ╔═══════════════════════════════════════════════════════════╗" +echo " ║ ║" +echo " ║ 🏝️ ARCHIPELAGO BITCOIN NODE OS ║" +echo " ║ ║" +echo " ║ Your sovereign Bitcoin infrastructure ║" +echo " ║ ║" +echo " ╚═══════════════════════════════════════════════════════════╝" +echo "" +if [ -n "$IP" ]; then +echo " ┌─────────────────────────────────────────────────────────────┐" +echo " │ 🌐 Web UI: http://$IP " +echo " └─────────────────────────────────────────────────────────────┘" +echo "" +fi +echo " Type 'archipelago' to open the setup menu" +echo "" + +# Check if already set up +if [ -f ~/.archipelago-setup-done ]; then + return 2>/dev/null || exit 0 +fi + +# First time - run setup automatically +if [ -n "$BOOT_MEDIA" ] && [ -f "$BOOT_MEDIA/archipelago/setup-archipelago.sh" ]; then + echo "" + read -p " Press Enter to continue to setup menu..." + bash "$BOOT_MEDIA/archipelago/setup-archipelago.sh" +fi +AUTOSTART_EOF + +chmod +x "$ARCHIPELAGO_DIR/auto-start.sh" + +# Create a simple 'archipelago-menu' command wrapper (don't overwrite the backend binary!) +cat > "$ARCHIPELAGO_DIR/bin/archipelago-menu" <<'CMD_EOF' +#!/bin/bash +# Archipelago menu command - launches the setup menu + +if [ -f /opt/archipelago/scripts/archipelago-menu.sh ]; then + exec /opt/archipelago/scripts/archipelago-menu.sh +else + # Find on boot media + for dev in /run/live/medium /lib/live/mount/medium /cdrom; do + if [ -f "$dev/archipelago/scripts/archipelago-menu.sh" ]; then + exec bash "$dev/archipelago/scripts/archipelago-menu.sh" + fi + done + echo "Archipelago menu not found. Run setup first:" + echo " sh /run/live/medium/archipelago/setup-archipelago.sh" +fi +CMD_EOF + +chmod +x "$ARCHIPELAGO_DIR/bin/archipelago-menu" + +# Verify the real backend binary is there +if [ -f "$ARCHIPELAGO_DIR/bin/archipelago" ]; then + echo " Backend binary: $(file "$ARCHIPELAGO_DIR/bin/archipelago" | grep -o 'ELF.*' || echo 'included')" +fi + +# Create SSH auto-start script for live environment +mkdir -p "$ISO_CUSTOM/etc/live/config.conf.d" +cat > "$ISO_CUSTOM/etc/live/config.conf.d/archipelago.conf" <<'LIVE_CONF_EOF' +# Archipelago live config +LIVE_HOSTNAME="archipelago" +LIVE_USER_DEFAULT_GROUPS="audio cdrom dip floppy video plugdev netdev powerdev scanner bluetooth sudo" +LIVE_CONF_EOF + +# Create rc.local to start SSH on boot (works in live environment) +mkdir -p "$ISO_CUSTOM/etc/rc.local.d" +cat > "$ISO_CUSTOM/etc/rc.local.d/archipelago-ssh.sh" <<'RCLOCAL_EOF' +#!/bin/bash +# Enable SSH in live environment + +# Install and start SSH if not running +if ! systemctl is-active --quiet ssh 2>/dev/null; then + # Try to start SSH (may already be installed) + systemctl start ssh 2>/dev/null || true + + # If SSH not installed, install it + if ! command -v sshd >/dev/null 2>&1; then + apt-get update -qq + apt-get install -y openssh-server + systemctl start ssh + fi +fi + +# Set password for user (live user is typically 'user' with empty password) +echo "user:archipelago" | chpasswd 2>/dev/null || true +echo "root:archipelago" | chpasswd 2>/dev/null || true + +# Allow password auth +sed -i 's/#PasswordAuthentication.*/PasswordAuthentication yes/' /etc/ssh/sshd_config 2>/dev/null +sed -i 's/PasswordAuthentication no/PasswordAuthentication yes/' /etc/ssh/sshd_config 2>/dev/null +systemctl restart ssh 2>/dev/null || true +RCLOCAL_EOF +chmod +x "$ISO_CUSTOM/etc/rc.local.d/archipelago-ssh.sh" + +# Also create a systemd service that runs early +mkdir -p "$ISO_CUSTOM/etc/systemd/system" +cat > "$ISO_CUSTOM/etc/systemd/system/archipelago-ssh.service" <<'SERVICE_EOF' +[Unit] +Description=Archipelago SSH Setup +After=network-online.target +Wants=network-online.target + +[Service] +Type=oneshot +ExecStart=/bin/bash -c 'apt-get update -qq && apt-get install -y openssh-server && echo "user:archipelago" | chpasswd && echo "root:archipelago" | chpasswd && sed -i "s/#PasswordAuthentication.*/PasswordAuthentication yes/" /etc/ssh/sshd_config && sed -i "s/PasswordAuthentication no/PasswordAuthentication yes/" /etc/ssh/sshd_config && systemctl restart ssh' +RemainAfterExit=yes + +[Install] +WantedBy=multi-user.target +SERVICE_EOF + +# Enable the service +mkdir -p "$ISO_CUSTOM/etc/systemd/system/multi-user.target.wants" +ln -sf ../archipelago-ssh.service "$ISO_CUSTOM/etc/systemd/system/multi-user.target.wants/archipelago-ssh.service" + +# Create system-wide profile script that runs on ANY login +mkdir -p "$ISO_CUSTOM/etc/profile.d" +cat > "$ISO_CUSTOM/etc/profile.d/z99-archipelago.sh" <<'PROFILE_EOF' +#!/bin/bash +# Archipelago auto-start - runs on login (z99 = runs last) + +# Only run once per session and only in interactive shells +if [ -n "$ARCHIPELAGO_STARTED" ] || [ ! -t 0 ]; then + return 0 2>/dev/null || exit 0 +fi +export ARCHIPELAGO_STARTED=1 + +# Find archipelago directory +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 + +# Get IP address +IP=$(hostname -I 2>/dev/null | awk '{print $1}') +[ -z "$IP" ] && IP=$(ip -4 addr show | grep -oP '(?<=inet\s)\d+(\.\d+){3}' | grep -v '127.0.0.1' | head -1) + +# Show welcome banner ALWAYS +clear +echo "" +echo " ╔═══════════════════════════════════════════════════════════╗" +echo " ║ ║" +echo " ║ 🏝️ ARCHIPELAGO BITCOIN NODE OS ║" +echo " ║ ║" +echo " ║ Your sovereign Bitcoin infrastructure ║" +echo " ║ ║" +echo " ╚═══════════════════════════════════════════════════════════╝" +echo "" +if [ -n "$IP" ]; then + echo " ┌─────────────────────────────────────────────────────────────┐" + echo " │ 🌐 Web UI: http://$IP:5678 │" + echo " │ 📡 SSH: ssh user@$IP (password: archipelago) │" + echo " └─────────────────────────────────────────────────────────────┘" + echo "" +fi + +if [ -z "$BOOT_MEDIA" ]; then + echo " ⚠️ Boot media not found at /run/live/medium" + echo "" + echo " Manual commands:" + echo " archipelago - Start backend server" + echo " archipelago-menu - Open setup menu" + echo "" + return 0 2>/dev/null || exit 0 +fi + +echo " 📍 Boot media: $BOOT_MEDIA" +echo "" + +# Install archipelago commands if not present +if [ ! -f /usr/local/bin/archipelago ] && [ -f "$BOOT_MEDIA/archipelago/bin/archipelago" ]; then + sudo cp "$BOOT_MEDIA/archipelago/bin/archipelago" /usr/local/bin/ 2>/dev/null + sudo chmod +x /usr/local/bin/archipelago 2>/dev/null +fi + +if [ ! -f /usr/local/bin/archipelago-menu ] && [ -f "$BOOT_MEDIA/archipelago/bin/archipelago-menu" ]; then + sudo cp "$BOOT_MEDIA/archipelago/bin/archipelago-menu" /usr/local/bin/ 2>/dev/null + sudo chmod +x /usr/local/bin/archipelago-menu 2>/dev/null +fi + +# Copy scripts to /opt +if [ ! -d /opt/archipelago/scripts ] && [ -d "$BOOT_MEDIA/archipelago/scripts" ]; then + sudo mkdir -p /opt/archipelago + sudo cp -r "$BOOT_MEDIA/archipelago/scripts" /opt/archipelago/ 2>/dev/null + sudo chmod +x /opt/archipelago/scripts/*.sh 2>/dev/null +fi + +# Start web backend in background if available +if command -v archipelago >/dev/null 2>&1; then + if ! pgrep -f "archipelago" >/dev/null 2>&1; then + echo " 🚀 Starting Archipelago backend on port 5678..." + nohup archipelago >/tmp/archipelago.log 2>&1 & + sleep 2 + else + echo " ✅ Archipelago backend already running" + fi +fi + +echo "" +echo " Commands:" +echo " archipelago-menu - Open interactive setup menu" +echo " archipelago - Start/restart backend server" +echo "" +read -p " Press Enter to open the setup menu (or Ctrl+C to skip)... " + +# Launch the menu +if [ -f /opt/archipelago/scripts/archipelago-menu.sh ]; then + exec bash /opt/archipelago/scripts/archipelago-menu.sh +elif [ -f "$BOOT_MEDIA/archipelago/scripts/archipelago-menu.sh" ]; then + exec bash "$BOOT_MEDIA/archipelago/scripts/archipelago-menu.sh" +fi +PROFILE_EOF + +chmod +x "$ISO_CUSTOM/etc/profile.d/z99-archipelago.sh" + +# Also add to /etc/skel/.bashrc as fallback for new user sessions +mkdir -p "$ISO_CUSTOM/etc/skel" +cat >> "$ISO_CUSTOM/etc/skel/.bashrc" <<'BASHRC_EOF' + +# Archipelago auto-start fallback +if [ -z "$ARCHIPELAGO_STARTED" ] && [ -t 0 ]; then + [ -f /etc/profile.d/z99-archipelago.sh ] && source /etc/profile.d/z99-archipelago.sh +fi +BASHRC_EOF + +# Modify GRUB config for Archipelago branding +echo "" +echo "⚙️ Configuring boot..." + +if [ -f "$ISO_CUSTOM/boot/grub/grub.cfg" ]; then + sed -i.bak \ + -e 's/Debian GNU\/Linux/Archipelago Bitcoin Node OS/g' \ + -e 's/Live system/Archipelago Live/g' \ + "$ISO_CUSTOM/boot/grub/grub.cfg" +fi + +if [ -f "$ISO_CUSTOM/isolinux/menu.cfg" ]; then + sed -i.bak \ + -e 's/Debian GNU\/Linux/Archipelago Bitcoin Node OS/g' \ + -e 's/Live system/Archipelago Live/g' \ + "$ISO_CUSTOM/isolinux/menu.cfg" +fi + +if [ -f "$ISO_CUSTOM/isolinux/live.cfg" ]; then + sed -i.bak \ + -e 's/Live system/Archipelago Live/g' \ + "$ISO_CUSTOM/isolinux/live.cfg" +fi + +# Create final ISO +OUTPUT_ISO="$OUTPUT_DIR/archipelago-debian-12-x86_64.iso" +echo "" +echo "🔥 Creating final bootable ISO..." + +if command -v xorriso >/dev/null 2>&1; then + # Create proper hybrid ISO with MBR for USB boot + # Need to extract MBR from isolinux or use system syslinux + + # Check for isohdpfx.bin in various locations + ISOHDPFX="" + for path in \ + "/usr/local/share/syslinux/isohdpfx.bin" \ + "/usr/share/syslinux/isohdpfx.bin" \ + "/opt/homebrew/share/syslinux/isohdpfx.bin" \ + "$ISO_CUSTOM/isolinux/isohdpfx.bin"; do + if [ -f "$path" ]; then + ISOHDPFX="$path" + break + fi + done + + if [ -z "$ISOHDPFX" ]; then + echo "⚠️ No isohdpfx.bin found, extracting from isolinux.bin..." + # Extract first 432 bytes from isolinux.bin as MBR + dd if="$ISO_CUSTOM/isolinux/isolinux.bin" of="$WORK_DIR/isohdpfx.bin" bs=432 count=1 2>/dev/null + ISOHDPFX="$WORK_DIR/isohdpfx.bin" + fi + + echo " Using MBR: $ISOHDPFX" + + 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 \ + "$ISO_CUSTOM" +else + echo "❌ xorriso not found. Please install it: brew install xorriso" + exit 1 +fi + +echo "" +echo "✅ ISO created successfully!" +echo "" +echo "📀 Output: $OUTPUT_ISO" +echo " Size: $(du -h "$OUTPUT_ISO" | cut -f1)" +echo "" +echo "🔥 This is Debian Live-based - reliable USB boot like StartOS!" +echo "" +echo "To create USB:" +echo " 1. Use Balena Etcher to flash the ISO" +echo " 2. Or: sudo dd if=$OUTPUT_ISO of=/dev/sdX bs=4M status=progress" +echo "" diff --git a/scripts/deploy-to-target.sh b/scripts/deploy-to-target.sh new file mode 100755 index 00000000..5973badb --- /dev/null +++ b/scripts/deploy-to-target.sh @@ -0,0 +1,99 @@ +#!/bin/bash +# +# Deploy Archipelago code to the HP ProDesk target +# +# Usage: +# ./scripts/deploy-to-target.sh # Sync and rebuild +# ./scripts/deploy-to-target.sh --quick # Sync only, no rebuild +# ./scripts/deploy-to-target.sh --live # Deploy to live system +# + +set -e + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PROJECT_DIR="$(dirname "$SCRIPT_DIR")" + +# Configuration +TARGET_HOST="${ARCHIPELAGO_TARGET:-archipelago@192.168.1.228}" +TARGET_DIR="/home/archipelago/archy" + +echo "╔════════════════════════════════════════════════════════════════╗" +echo "║ Deploying to Archipelago Target ║" +echo "╚════════════════════════════════════════════════════════════════╝" +echo "" +echo "Target: $TARGET_HOST" +echo "" + +# Parse arguments +QUICK=false +LIVE=false +for arg in "$@"; do + case $arg in + --quick) QUICK=true ;; + --live) LIVE=true ;; + esac +done + +# Sync code +echo "📦 Syncing code..." +rsync -avz --delete \ + --exclude 'node_modules' \ + --exclude 'target' \ + --exclude 'dist' \ + --exclude '.git' \ + --exclude 'image-recipe/build' \ + --exclude 'image-recipe/results' \ + "$PROJECT_DIR/" "$TARGET_HOST:$TARGET_DIR/" + +if [ "$QUICK" = true ]; then + echo "" + echo "✅ Quick sync complete!" + exit 0 +fi + +# Build on target +echo "" +echo "🔨 Building on target..." + +# Frontend +echo " Building frontend..." +ssh "$TARGET_HOST" "cd $TARGET_DIR/neode-ui && npm install --silent && npm run build" 2>&1 | sed 's/^/ /' + +# Backend (if Rust is installed) +if ssh "$TARGET_HOST" "command -v cargo" >/dev/null 2>&1; then + echo " Building backend..." + ssh "$TARGET_HOST" "cd $TARGET_DIR/core && cargo build --release 2>&1" | tail -5 | sed 's/^/ /' +else + echo " ⚠️ Rust not installed on target, skipping backend build" +fi + +if [ "$LIVE" = true ]; then + echo "" + echo "🚀 Deploying to live system..." + + # Deploy backend + ssh "$TARGET_HOST" "sudo cp $TARGET_DIR/core/target/release/archipelago /usr/local/bin/ 2>/dev/null || true" + + # Deploy frontend + ssh "$TARGET_HOST" "sudo rm -rf /opt/archipelago/web-ui/*" + ssh "$TARGET_HOST" "sudo cp -r $TARGET_DIR/neode-ui/dist/* /opt/archipelago/web-ui/" + ssh "$TARGET_HOST" "sudo chown -R 1000:1000 /opt/archipelago/web-ui" + + # Restart services + ssh "$TARGET_HOST" "sudo systemctl restart archipelago nginx" + + echo "" + echo "✅ Deployed to live system!" + echo " Web UI: http://$(echo $TARGET_HOST | cut -d@ -f2)" +else + echo "" + echo "✅ Build complete!" + echo "" + echo "To test frontend dev server:" + echo " ssh $TARGET_HOST" + echo " cd ~/archy/neode-ui && npm run dev -- --host 0.0.0.0" + echo " Then open: http://$(echo $TARGET_HOST | cut -d@ -f2):5173" + echo "" + echo "To deploy to live system:" + echo " ./scripts/deploy-to-target.sh --live" +fi diff --git a/scripts/setup-target-dev.sh b/scripts/setup-target-dev.sh new file mode 100755 index 00000000..251a53e1 --- /dev/null +++ b/scripts/setup-target-dev.sh @@ -0,0 +1,93 @@ +#!/bin/bash +# +# Setup development environment on Archipelago target machine +# +# Run this ON the HP ProDesk via SSH: +# curl -sSL https://raw.githubusercontent.com/.../setup-target-dev.sh | bash +# Or copy and run locally: +# scp scripts/setup-target-dev.sh archipelago@192.168.1.228:~/ +# ssh archipelago@192.168.1.228 'bash ~/setup-target-dev.sh' +# + +set -e + +echo "╔════════════════════════════════════════════════════════════════╗" +echo "║ Setting up Archipelago Development Environment ║" +echo "╚════════════════════════════════════════════════════════════════╝" +echo "" + +# Update packages +echo "📦 Updating packages..." +sudo apt update + +# Install Node.js (for Vue.js frontend) +echo "" +echo "📦 Installing Node.js..." +if ! command -v node &> /dev/null; then + curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - + sudo apt install -y nodejs +else + echo " Node.js already installed: $(node --version)" +fi + +# Install Rust (for backend) +echo "" +echo "📦 Installing Rust..." +if ! command -v cargo &> /dev/null; then + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + source ~/.cargo/env +else + echo " Rust already installed: $(rustc --version)" +fi + +# Install build tools +echo "" +echo "📦 Installing build tools..." +sudo apt install -y \ + build-essential \ + pkg-config \ + libssl-dev \ + git + +# Create development directory +echo "" +echo "📁 Creating development directory..." +mkdir -p ~/archy + +# Fix XDG_RUNTIME_DIR for rootless Podman (add to bashrc) +if ! grep -q "XDG_RUNTIME_DIR" ~/.bashrc; then + echo "" + echo "🔧 Fixing Podman rootless setup..." + cat >> ~/.bashrc << 'EOF' + +# Fix for rootless Podman +if [ -z "$XDG_RUNTIME_DIR" ]; then + export XDG_RUNTIME_DIR=/run/user/$(id -u) + if [ ! -d "$XDG_RUNTIME_DIR" ]; then + sudo mkdir -p "$XDG_RUNTIME_DIR" + sudo chown $(whoami):$(whoami) "$XDG_RUNTIME_DIR" + sudo chmod 700 "$XDG_RUNTIME_DIR" + fi +fi +EOF +fi + +# Enable user lingering for Podman +sudo loginctl enable-linger archipelago 2>/dev/null || true + +echo "" +echo "╔════════════════════════════════════════════════════════════════╗" +echo "║ ✅ Development environment ready! ║" +echo "╚════════════════════════════════════════════════════════════════╝" +echo "" +echo "Installed:" +echo " • Node.js: $(node --version 2>/dev/null || echo 'not found')" +echo " • npm: $(npm --version 2>/dev/null || echo 'not found')" +echo " • Rust: $(rustc --version 2>/dev/null || echo 'not found')" +echo " • Cargo: $(cargo --version 2>/dev/null || echo 'not found')" +echo "" +echo "Next steps:" +echo " 1. Log out and back in (or run: source ~/.bashrc)" +echo " 2. From your Mac, run: ./scripts/deploy-to-target.sh" +echo " 3. To start Vue.js dev server: cd ~/archy/neode-ui && npm run dev -- --host 0.0.0.0" +echo ""