Demo images / Build & push demo images (push) Failing after 2m16s
Keeps the dev and test tooling an outside contributor would want, and takes our node addresses out of it. Scripts that silently defaulted to one of our nodes now require an explicit host and exit 2 without one: smoke-test.sh, trust-archipelago-cert.sh, dev-container-test.sh (which also derives its RPC and health URLs from the SSH target instead of a second hardcoded copy), and image-recipe/dev-branding.sh. A default that points at a machine the user does not own is worse than no default: it fails confusingly, or reaches a stranger's device. Usage examples, mock data and test fixtures move to the RFC 5737 documentation range (192.0.2.0/24). CGNAT test values stay inside 100.64.0.0/10 so the range-check semantics they exercise still hold, and 192.168.1.0/.1/.254 are left alone — those are gateway logic and UI placeholders, not our addresses. Playwright and the perf spec defaulted their baseURL to one of our nodes; they now default to localhost:8100, the local dev server. Removed neode-ui APP_URLS entirely. It is dead code — exported, never imported — and it pinned fedimint's *prod* launch URL to 192.168.1.228:8175. Had anything consumed it, every user's node would have tried to reach an address that on their LAN is either nothing or someone else's machine. Deleting beats sanitizing dead config. Verified: frontend 868/868 vitest across 108 files; archipelago-container 75/75; mesh tests 9/9; audit-secrets 5/5. Zero node addresses and zero node names remain in tracked files. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
410 lines
15 KiB
Bash
Executable File
410 lines
15 KiB
Bash
Executable File
#!/bin/bash
|
|
# Archipelago Development Server Starter
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
FRONTEND_DIR="$PROJECT_ROOT/neode-ui"
|
|
BACKEND_DIR="$PROJECT_ROOT/core"
|
|
|
|
# Quietly kill a port — avoids EAGAIN by not piping through xargs
|
|
kill_port() {
|
|
local pids
|
|
pids=$(lsof -ti:"$1" 2>/dev/null) || true
|
|
if [ -n "$pids" ]; then
|
|
echo "$pids" | while read -r pid; do
|
|
kill -9 "$pid" 2>/dev/null || true
|
|
done
|
|
sleep 1
|
|
fi
|
|
}
|
|
|
|
cleanup_ports() {
|
|
kill_port 5959
|
|
kill_port 8100
|
|
}
|
|
|
|
ensure_deps() {
|
|
cd "$FRONTEND_DIR"
|
|
if [ ! -d "node_modules" ]; then
|
|
echo " Installing dependencies..."
|
|
npm install
|
|
fi
|
|
}
|
|
|
|
if [ ! -d "$FRONTEND_DIR" ]; then
|
|
echo "Frontend directory not found: $FRONTEND_DIR"
|
|
exit 1
|
|
fi
|
|
|
|
echo ""
|
|
echo "Archipelago Dev Server"
|
|
echo ""
|
|
|
|
# Detect if running on a Linux dev machine (production-like mode available)
|
|
IS_LINUX=false
|
|
if [[ "$OSTYPE" == "linux"* ]]; then
|
|
IS_LINUX=true
|
|
fi
|
|
|
|
echo " 0) Boot branding dev (GRUB theme, Plymouth, installer — patch + QEMU)"
|
|
echo " 1) Mock backend (UI dev — fastest, no Docker/Podman needed)"
|
|
echo " 2) Full stack (Rust backend + frontend)"
|
|
echo " 3) Setup mode (first-time password setup — mock)"
|
|
echo " 4) Onboarding mode (onboarding flow — mock)"
|
|
echo " 5) Existing user (login screen — mock)"
|
|
echo " 6) Boot mode (simulated 25s startup — mock)"
|
|
echo " 7) Testnet stack (signet Bitcoin + LND + ThunderHub via Podman)"
|
|
echo " 8) Manual instructions"
|
|
echo " 9) Container orchestration dev (live testing on .228)"
|
|
if [ "$IS_LINUX" = true ]; then
|
|
echo " 10) Production build (Linux only — build, install, restart all services)"
|
|
echo " Mirrors ISO exactly: backend + frontend + Tor + WG + NostrVPN + nginx"
|
|
fi
|
|
echo ""
|
|
read -p "Enter choice [0-10]: " choice
|
|
|
|
case $choice in
|
|
0)
|
|
echo ""
|
|
echo "Boot Branding Dev"
|
|
echo ""
|
|
# Find an ISO to patch
|
|
ISO=$(ls -t ~/Desktop/archipelago-dev-*.iso 2>/dev/null | head -1)
|
|
if [ -z "$ISO" ]; then
|
|
ISO=$(ls -t "$PROJECT_ROOT/image-recipe/results/archipelago-"*.iso 2>/dev/null | head -1)
|
|
fi
|
|
|
|
DEV_BRANDING="$PROJECT_ROOT/image-recipe/dev-branding.sh"
|
|
|
|
if [ -z "$ISO" ] || [ ! -f "$ISO" ]; then
|
|
echo " No ISO found to patch. Options:"
|
|
echo ""
|
|
echo " a) Preview GRUB background only (instant):"
|
|
echo " python3 image-recipe/branding/generate-grub-background.py /tmp/grub-bg.png && open /tmp/grub-bg.png"
|
|
echo ""
|
|
echo " b) Download an ISO from FileBrowser (http://192.0.2.10:8083)"
|
|
echo " then drop it on your Desktop and re-run this option."
|
|
echo ""
|
|
echo " Files you can edit:"
|
|
echo " image-recipe/branding/grub-theme/background.png — GRUB boot background"
|
|
echo " image-recipe/branding/grub-theme/theme.txt — GRUB menu colors/layout"
|
|
echo " image-recipe/branding/plymouth-theme/logo.png — Plymouth boot logo"
|
|
echo " image-recipe/branding/plymouth-theme/*.script — Plymouth animation"
|
|
echo ""
|
|
exit 0
|
|
fi
|
|
|
|
echo " ISO: $ISO"
|
|
echo " Edit these files, then this script patches and boots in QEMU:"
|
|
echo " branding/grub-theme/background.png — GRUB background"
|
|
echo " branding/grub-theme/theme.txt — GRUB menu theme"
|
|
echo " branding/plymouth-theme/logo.png — Plymouth logo"
|
|
echo ""
|
|
|
|
if [ -f "$DEV_BRANDING" ]; then
|
|
exec bash "$DEV_BRANDING" "$ISO"
|
|
else
|
|
echo " dev-branding.sh not found at: $DEV_BRANDING"
|
|
exit 1
|
|
fi
|
|
;;
|
|
1)
|
|
echo ""
|
|
echo "Starting frontend with mock backend..."
|
|
cleanup_ports
|
|
ensure_deps
|
|
exec npm run dev:mock
|
|
;;
|
|
2)
|
|
echo ""
|
|
echo "Starting full stack (Rust backend + frontend)..."
|
|
cleanup_ports
|
|
|
|
if [ ! -d "$BACKEND_DIR" ]; then
|
|
echo "Backend directory not found: $BACKEND_DIR"
|
|
exit 1
|
|
fi
|
|
|
|
cd "$BACKEND_DIR"
|
|
if ! cargo check --bin archipelago > /tmp/archipelago-backend-check.log 2>&1; then
|
|
echo "Backend build check failed. See /tmp/archipelago-backend-check.log"
|
|
echo "Falling back to mock backend."
|
|
ensure_deps
|
|
exec npm run dev:mock
|
|
fi
|
|
|
|
echo " Starting Rust backend..."
|
|
export ARCHIPELAGO_DATA_DIR=/tmp/archipelago-dev
|
|
export ARCHIPELAGO_DEV_DATA_DIR=/tmp/archipelago-dev
|
|
export ARCHIPELAGO_DEV_MODE=true
|
|
export ARCHIPELAGO_BIND=127.0.0.1:5959
|
|
export ARCHIPELAGO_LOG_LEVEL=debug
|
|
export ARCHIPELAGO_BITCOIN_SIMULATION=mock
|
|
|
|
cargo run --bin archipelago > /tmp/archipelago-backend.log 2>&1 &
|
|
BACKEND_PID=$!
|
|
echo " Backend PID: $BACKEND_PID (logs: /tmp/archipelago-backend.log)"
|
|
|
|
echo " Waiting for backend on port 5959..."
|
|
for i in $(seq 1 60); do
|
|
if lsof -ti:5959 >/dev/null 2>&1; then break; fi
|
|
sleep 1
|
|
done
|
|
|
|
if ! lsof -ti:5959 >/dev/null 2>&1; then
|
|
echo "Backend did not start. Falling back to mock."
|
|
kill "$BACKEND_PID" 2>/dev/null || true
|
|
ensure_deps
|
|
exec npm run dev:mock
|
|
fi
|
|
|
|
echo " Backend ready."
|
|
trap "kill $BACKEND_PID 2>/dev/null" EXIT
|
|
|
|
ensure_deps
|
|
exec npm run dev
|
|
;;
|
|
3)
|
|
echo ""
|
|
echo "Starting setup mode..."
|
|
cleanup_ports
|
|
ensure_deps
|
|
VITE_DEV_MODE=setup exec npm run dev:mock
|
|
;;
|
|
4)
|
|
echo ""
|
|
echo "Starting onboarding mode..."
|
|
cleanup_ports
|
|
ensure_deps
|
|
VITE_DEV_MODE=onboarding exec npm run dev:mock
|
|
;;
|
|
5)
|
|
echo ""
|
|
echo "Starting existing user mode..."
|
|
cleanup_ports
|
|
ensure_deps
|
|
VITE_DEV_MODE=existing exec npm run dev:mock
|
|
;;
|
|
6)
|
|
echo ""
|
|
echo "Starting boot mode (25s simulated startup)..."
|
|
cleanup_ports
|
|
ensure_deps
|
|
VITE_DEV_MODE=boot exec npm run dev:mock
|
|
;;
|
|
7)
|
|
echo ""
|
|
echo "Starting testnet stack (signet) via Podman/Docker..."
|
|
|
|
# Check for a working container runtime (binary exists AND daemon responds)
|
|
RUNTIME=""
|
|
COMPOSE=""
|
|
if command -v docker &>/dev/null && docker ps &>/dev/null; then
|
|
RUNTIME="docker"
|
|
COMPOSE="docker compose"
|
|
elif command -v podman &>/dev/null && podman ps &>/dev/null; then
|
|
if command -v podman-compose &>/dev/null; then
|
|
RUNTIME="podman"
|
|
COMPOSE="podman-compose"
|
|
else
|
|
RUNTIME="podman"
|
|
COMPOSE="podman compose"
|
|
fi
|
|
fi
|
|
|
|
if [ -z "$RUNTIME" ]; then
|
|
if command -v podman &>/dev/null; then
|
|
echo " Podman machine not running — starting it..."
|
|
if ! podman machine ls --format '{{.Name}}' 2>/dev/null | grep -q .; then
|
|
echo " No Podman machine found — initializing..."
|
|
podman machine init
|
|
fi
|
|
podman machine start
|
|
if podman ps &>/dev/null; then
|
|
if command -v podman-compose &>/dev/null; then
|
|
RUNTIME="podman"
|
|
COMPOSE="podman-compose"
|
|
else
|
|
RUNTIME="podman"
|
|
COMPOSE="podman compose"
|
|
fi
|
|
else
|
|
echo " Failed to start Podman machine."
|
|
exit 1
|
|
fi
|
|
elif command -v docker &>/dev/null; then
|
|
echo ""
|
|
echo "Docker is installed but the daemon isn't running."
|
|
echo "Start Docker Desktop and try again."
|
|
exit 1
|
|
else
|
|
echo ""
|
|
echo "No container runtime found. Install one:"
|
|
echo " brew install podman podman-compose"
|
|
echo " # or"
|
|
echo " brew install --cask docker"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
echo " Using: $RUNTIME"
|
|
cd "$PROJECT_ROOT"
|
|
|
|
echo " Starting signet Bitcoin + LND + ThunderHub + Fedimint..."
|
|
$COMPOSE -f docker-compose.testnet.yml up -d
|
|
|
|
echo ""
|
|
echo " Testnet stack starting. Services:"
|
|
echo " ThunderHub: http://localhost:3010 (password: thunderhub)"
|
|
echo " Fedimint Guardian: http://localhost:18175"
|
|
echo " LND REST: http://localhost:8080"
|
|
echo " Bitcoin RPC: localhost:38332"
|
|
echo ""
|
|
echo " Get signet coins: https://signetfaucet.com"
|
|
echo ""
|
|
echo " Also starting mock frontend..."
|
|
cleanup_ports
|
|
ensure_deps
|
|
exec npm run dev:mock
|
|
;;
|
|
8)
|
|
echo ""
|
|
echo "Manual Instructions"
|
|
echo ""
|
|
echo "UI development (mock backend, no Docker):"
|
|
echo " cd $FRONTEND_DIR"
|
|
echo " npm install && npm run dev:mock"
|
|
echo ""
|
|
echo "Dev modes (prepend to command):"
|
|
echo " VITE_DEV_MODE=setup First-time setup flow"
|
|
echo " VITE_DEV_MODE=onboarding Onboarding flow"
|
|
echo " VITE_DEV_MODE=existing Login screen"
|
|
echo " VITE_DEV_MODE=boot Boot sequence"
|
|
echo ""
|
|
echo "Testnet stack (requires Podman or Docker):"
|
|
echo " podman compose -f docker-compose.testnet.yml up -d"
|
|
echo ""
|
|
echo "Full stack (requires Rust toolchain):"
|
|
echo " Terminal 1: cd $BACKEND_DIR && cargo run --bin archipelago"
|
|
echo " Terminal 2: cd $FRONTEND_DIR && npm run dev"
|
|
echo ""
|
|
echo "Access: http://localhost:8100 (password: password123)"
|
|
;;
|
|
9)
|
|
echo ""
|
|
echo "Container Orchestration Dev (live testing on .228)"
|
|
echo "Syncs code, builds on server, runs orchestration smoke tests."
|
|
echo ""
|
|
exec "$SCRIPT_DIR/dev-container-test.sh"
|
|
;;
|
|
10)
|
|
if [ "$IS_LINUX" != true ]; then
|
|
echo "Production build is only available on Linux dev machines."
|
|
exit 1
|
|
fi
|
|
|
|
echo ""
|
|
echo "Production Build — mirrors ISO install exactly"
|
|
echo ""
|
|
|
|
FAILED=0
|
|
|
|
# Step 1: Build backend
|
|
echo "[1/5] Building Rust backend (release)..."
|
|
cd "$BACKEND_DIR/archipelago"
|
|
if cargo build --release 2>&1 | tail -3; then
|
|
RELEASE_BIN="$BACKEND_DIR/target/release/archipelago"
|
|
sudo cp "$RELEASE_BIN" /usr/local/bin/archipelago
|
|
sudo chmod +x /usr/local/bin/archipelago
|
|
echo " Backend installed: $(ls -lh /usr/local/bin/archipelago | awk '{print $5}')"
|
|
else
|
|
echo " FAILED: cargo build --release"
|
|
FAILED=1
|
|
fi
|
|
|
|
# Step 2: Type-check + build frontend
|
|
echo "[2/5] Building frontend..."
|
|
cd "$FRONTEND_DIR"
|
|
if [ ! -d "node_modules" ]; then
|
|
npm install
|
|
fi
|
|
if npx vue-tsc -b --noEmit 2>&1 | tail -3; then
|
|
npm run build 2>&1 | tail -3
|
|
sudo cp -r "$PROJECT_ROOT/web/dist/neode-ui/"* /opt/archipelago/web-ui/
|
|
# Deploy AIUI (pre-built demo or source build)
|
|
if [ -d "$PROJECT_ROOT/../AIUI/packages/app/dist" ]; then
|
|
sudo cp -r "$PROJECT_ROOT/../AIUI/packages/app/dist/"* /opt/archipelago/web-ui/aiui/
|
|
echo " AIUI deployed from source build"
|
|
elif [ -d "$PROJECT_ROOT/demo/aiui" ]; then
|
|
sudo mkdir -p /opt/archipelago/web-ui/aiui/
|
|
sudo cp -r "$PROJECT_ROOT/demo/aiui/"* /opt/archipelago/web-ui/aiui/
|
|
echo " AIUI deployed from demo/"
|
|
fi
|
|
echo " Frontend deployed to /opt/archipelago/web-ui/"
|
|
else
|
|
echo " FAILED: vue-tsc type check"
|
|
FAILED=1
|
|
fi
|
|
|
|
# Step 3: Sync configs from repo
|
|
echo "[3/5] Syncing configs..."
|
|
sudo cp "$PROJECT_ROOT/image-recipe/configs/archipelago.service" /etc/systemd/system/archipelago.service
|
|
sudo cp "$PROJECT_ROOT/image-recipe/configs/nginx-archipelago.conf" /etc/nginx/sites-available/archipelago
|
|
sudo cp "$PROJECT_ROOT/image-recipe/configs/snippets/"*.conf /etc/nginx/snippets/ 2>/dev/null
|
|
for unit in archipelago-tor-helper.service archipelago-tor-helper.path archipelago-wg.service archipelago-wg-address.service nostr-relay.service nostr-vpn.service; do
|
|
sudo cp "$PROJECT_ROOT/image-recipe/configs/$unit" "/etc/systemd/system/$unit"
|
|
done
|
|
sudo cp "$PROJECT_ROOT/scripts/tor-helper.sh" /opt/archipelago/scripts/tor-helper.sh
|
|
sudo chmod +x /opt/archipelago/scripts/tor-helper.sh
|
|
sudo cp "$PROJECT_ROOT/scripts/archipelago-wg" /usr/local/bin/archipelago-wg
|
|
sudo chmod +x /usr/local/bin/archipelago-wg
|
|
echo " Configs synced"
|
|
|
|
# Step 4: Sync Tor hostnames
|
|
echo "[4/5] Syncing Tor hostnames..."
|
|
for svc in archipelago bitcoin electrumx lnd btcpay mempool fedimint; do
|
|
dir="/var/lib/archipelago/tor/hidden_service_$svc"
|
|
if [ -f "$dir/hostname" ]; then
|
|
sudo cp "$dir/hostname" "/var/lib/archipelago/tor-hostnames/$svc"
|
|
fi
|
|
done
|
|
sudo chown -R "$(whoami)":"$(whoami)" /var/lib/archipelago/tor-hostnames 2>/dev/null
|
|
|
|
# Step 5: Reload and restart all services
|
|
echo "[5/5] Restarting services..."
|
|
sudo systemctl daemon-reload
|
|
sudo nginx -t 2>&1 && sudo systemctl reload nginx
|
|
sudo systemctl restart archipelago
|
|
|
|
# Verify
|
|
echo ""
|
|
echo "Service Status:"
|
|
for svc in tor@default archipelago-wg archipelago-wg-address nostr-relay nostr-vpn archipelago-tor-helper.path archipelago nginx; do
|
|
STATUS=$(systemctl is-active "$svc" 2>/dev/null)
|
|
if [ "$STATUS" = "active" ]; then
|
|
printf " %-30s active\n" "$svc"
|
|
else
|
|
printf " %-30s FAILED\n" "$svc"
|
|
FAILED=1
|
|
fi
|
|
done
|
|
|
|
echo ""
|
|
if [ "$FAILED" -eq 0 ]; then
|
|
HOST_IP=$(hostname -I 2>/dev/null | awk '{print $1}')
|
|
ONION=$(cat /var/lib/archipelago/tor-hostnames/archipelago 2>/dev/null || echo "generating...")
|
|
echo "All services running. Access:"
|
|
echo " LAN: http://$HOST_IP"
|
|
echo " Tor: http://$ONION"
|
|
echo " WG: 10.44.0.1"
|
|
echo " RPC: http://127.0.0.1:5678/rpc/v1"
|
|
else
|
|
echo "Some services failed. Check: journalctl -u <service> --no-pager -n 20"
|
|
fi
|
|
;;
|
|
*)
|
|
echo "Invalid choice"
|
|
exit 1
|
|
;;
|
|
esac
|