|
|
|
@@ -68,6 +68,9 @@ check_tools() {
|
|
|
|
|
if ! command -v xorriso >/dev/null 2>&1; then
|
|
|
|
|
missing="$missing xorriso"
|
|
|
|
|
fi
|
|
|
|
|
if ! command -v 7z >/dev/null 2>&1 && ! command -v 7za >/dev/null 2>&1; then
|
|
|
|
|
missing="$missing p7zip-full"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
if [ -n "$missing" ]; then
|
|
|
|
|
echo "❌ Missing required tools:$missing"
|
|
|
|
@@ -79,6 +82,9 @@ check_tools() {
|
|
|
|
|
if [[ "$missing" == *"xorriso"* ]]; then
|
|
|
|
|
apt-get install -y xorriso
|
|
|
|
|
fi
|
|
|
|
|
if [[ "$missing" == *"p7zip-full"* ]]; then
|
|
|
|
|
apt-get install -y p7zip-full
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
if [[ "$missing" == *"docker-or-podman"* ]]; then
|
|
|
|
|
echo " Installing podman..."
|
|
|
|
@@ -208,12 +214,24 @@ server {
|
|
|
|
|
try_files $uri $uri/ /index.html;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Peer-to-peer node messaging (receives from other nodes over Tor)
|
|
|
|
|
location /archipelago/ {
|
|
|
|
|
proxy_pass http://127.0.0.1:5678;
|
|
|
|
|
proxy_http_version 1.1;
|
|
|
|
|
proxy_set_header Host $host;
|
|
|
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# 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;
|
|
|
|
|
# Increase timeout for long-running operations (e.g., Docker image pulls)
|
|
|
|
|
proxy_connect_timeout 300s;
|
|
|
|
|
proxy_send_timeout 300s;
|
|
|
|
|
proxy_read_timeout 300s;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Proxy WebSocket
|
|
|
|
@@ -223,6 +241,7 @@ server {
|
|
|
|
|
proxy_set_header Upgrade $http_upgrade;
|
|
|
|
|
proxy_set_header Connection "upgrade";
|
|
|
|
|
proxy_set_header Host $host;
|
|
|
|
|
proxy_read_timeout 86400s;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
NGINXCONF
|
|
|
|
@@ -268,7 +287,7 @@ 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
|
|
|
|
|
EXPECTED_SIZE=1500000000 # ~1.5GB min (Debian 13 Live standard ~1.9GB)
|
|
|
|
|
|
|
|
|
|
# Check if file exists and is complete
|
|
|
|
|
if [ -f "$BASE_ISO" ]; then
|
|
|
|
@@ -287,7 +306,7 @@ if [ ! -f "$BASE_ISO" ]; then
|
|
|
|
|
|
|
|
|
|
# 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"
|
|
|
|
|
ISO_URL="https://cdimage.debian.org/debian-cd/current-live/amd64/iso-hybrid/debian-live-13.3.0-amd64-standard.iso"
|
|
|
|
|
|
|
|
|
|
if command -v wget >/dev/null 2>&1; then
|
|
|
|
|
cd "$WORK_DIR"
|
|
|
|
@@ -302,8 +321,8 @@ if [ ! -f "$BASE_ISO" ]; then
|
|
|
|
|
# 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"
|
|
|
|
|
elif [ -f "debian-live-13.3.0-amd64-standard.iso" ]; then
|
|
|
|
|
mv "debian-live-13.3.0-amd64-standard.iso" "$BASE_ISO"
|
|
|
|
|
else
|
|
|
|
|
echo " ❌ Downloaded file not found"
|
|
|
|
|
exit 1
|
|
|
|
@@ -335,7 +354,10 @@ 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"
|
|
|
|
|
(7z x -y "$BASE_ISO" 2>/dev/null || 7za x -y "$BASE_ISO" 2>/dev/null || bsdtar -xf "$BASE_ISO" 2>/dev/null) || {
|
|
|
|
|
echo " ❌ Failed to extract ISO. Install p7zip-full: sudo apt install p7zip-full"
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
|
|
# STEP 3: Add Archipelago components
|
|
|
|
@@ -362,17 +384,15 @@ 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
|
|
|
|
|
# Direct copy from local filesystem (when running on target with sudo)
|
|
|
|
|
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
|
|
|
|
|
# Remote copy via SCP if local failed
|
|
|
|
|
if [ "$BACKEND_CAPTURED" = "0" ] && [ "$DEV_SERVER" != "localhost" ] && [ "$DEV_SERVER" != "127.0.0.1" ]; then
|
|
|
|
|
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))"
|
|
|
|
@@ -416,16 +436,14 @@ 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
|
|
|
|
|
# Direct copy from local filesystem (when running on target with sudo)
|
|
|
|
|
if [ -d "/opt/archipelago/web-ui" ] && [ "$(ls -A /opt/archipelago/web-ui 2>/dev/null)" ]; then
|
|
|
|
|
cp -r /opt/archipelago/web-ui/* "$ARCH_DIR/web-ui/"
|
|
|
|
|
echo " ✅ Web UI captured from local system ($(du -sh "$ARCH_DIR/web-ui" | cut -f1))"
|
|
|
|
|
WEBUI_CAPTURED=1
|
|
|
|
|
fi
|
|
|
|
|
# Remote copy via rsync if local failed
|
|
|
|
|
if [ "$WEBUI_CAPTURED" = "0" ] && [ "$DEV_SERVER" != "localhost" ] && [ "$DEV_SERVER" != "127.0.0.1" ]; then
|
|
|
|
|
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
|
|
|
|
@@ -481,7 +499,7 @@ mkdir -p "$IMAGES_DIR"
|
|
|
|
|
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"
|
|
|
|
|
CAPTURE_PATTERNS="bitcoin-ui bitcoin-knots lnd lnd-ui filebrowser mempool mempool-electrs tailscale homeassistant btcpayserver nostr-rs-relay strfry alpine-tor"
|
|
|
|
|
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
|
|
|
|
@@ -503,11 +521,16 @@ 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
|
|
|
|
|
mempool/frontend:latest mempool-frontend.tar
|
|
|
|
|
mempool/backend:v2.5.0 mempool-backend.tar
|
|
|
|
|
mempool/electrs:latest mempool-electrs.tar
|
|
|
|
|
docker.io/mariadb:10.11 mariadb-mempool.tar
|
|
|
|
|
docker.io/fedimint/fedimintd:v0.10.0 fedimint.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
|
|
|
|
|
docker.io/andrius/alpine-tor:latest alpine-tor.tar
|
|
|
|
|
"
|
|
|
|
|
|
|
|
|
|
# Pull and save each image (force AMD64 for x86_64 target) only if not already present
|
|
|
|
@@ -571,6 +594,9 @@ for tarfile in "$IMAGES_DIR"/*.tar; do
|
|
|
|
|
fi
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
# Ensure archy-net exists for mempool stack (db, api, frontend)
|
|
|
|
|
podman network create archy-net 2>/dev/null || true
|
|
|
|
|
|
|
|
|
|
echo "$(date): Container image load complete" >> "$LOG_FILE"
|
|
|
|
|
echo "$(date): Available images:" >> "$LOG_FILE"
|
|
|
|
|
podman images >> "$LOG_FILE" 2>&1
|
|
|
|
@@ -583,7 +609,85 @@ 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"
|
|
|
|
|
# Tor setup: copy torrc and create first-boot setup script
|
|
|
|
|
mkdir -p "$ARCH_DIR/scripts/tor"
|
|
|
|
|
if [ -f "$SCRIPT_DIR/../scripts/tor/torrc.template" ]; then
|
|
|
|
|
cp "$SCRIPT_DIR/../scripts/tor/torrc.template" "$ARCH_DIR/scripts/tor/torrc"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
echo " Creating first-boot Tor setup service..."
|
|
|
|
|
cat > "$WORK_DIR/archipelago-setup-tor.service" <<'TORSERVICE'
|
|
|
|
|
[Unit]
|
|
|
|
|
Description=Setup and start Archipelago Tor hidden services
|
|
|
|
|
After=archipelago-load-images.service network.target podman.service
|
|
|
|
|
ConditionPathExists=/opt/archipelago/scripts/setup-tor.sh
|
|
|
|
|
|
|
|
|
|
[Service]
|
|
|
|
|
Type=oneshot
|
|
|
|
|
ExecStart=/opt/archipelago/scripts/setup-tor.sh
|
|
|
|
|
RemainAfterExit=yes
|
|
|
|
|
|
|
|
|
|
[Install]
|
|
|
|
|
WantedBy=multi-user.target
|
|
|
|
|
TORSERVICE
|
|
|
|
|
|
|
|
|
|
cat > "$WORK_DIR/setup-tor.sh" <<'TORSCRIPT'
|
|
|
|
|
#!/bin/bash
|
|
|
|
|
# Setup and start Tor container for unique .onion addresses (autoinstaller first-boot)
|
|
|
|
|
|
|
|
|
|
TOR_DIR="/var/lib/archipelago/tor"
|
|
|
|
|
TORRC_SRC="/opt/archipelago/scripts/tor/torrc"
|
|
|
|
|
LOG="/var/log/archipelago-tor.log"
|
|
|
|
|
|
|
|
|
|
mkdir -p "$TOR_DIR"
|
|
|
|
|
if [ -f "$TORRC_SRC" ]; then
|
|
|
|
|
cp "$TORRC_SRC" "$TOR_DIR/torrc"
|
|
|
|
|
fi
|
|
|
|
|
if [ ! -f "$TOR_DIR/torrc" ]; then
|
|
|
|
|
echo "SocksPort 9050" > "$TOR_DIR/torrc"
|
|
|
|
|
echo "ControlPort 0" >> "$TOR_DIR/torrc"
|
|
|
|
|
echo "DataDirectory $TOR_DIR" >> "$TOR_DIR/torrc"
|
|
|
|
|
echo "HiddenServiceDir $TOR_DIR/hidden_service_archipelago/" >> "$TOR_DIR/torrc"
|
|
|
|
|
echo "HiddenServicePort 80 127.0.0.1:80" >> "$TOR_DIR/torrc"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
DOCKER=podman
|
|
|
|
|
command -v podman >/dev/null 2>&1 || DOCKER=docker
|
|
|
|
|
|
|
|
|
|
for c in $(sudo $DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -E 'archy-tor|^tor$'); do
|
|
|
|
|
[ -n "$c" ] && sudo $DOCKER stop "$c" 2>/dev/null; sudo $DOCKER rm -f "$c" 2>/dev/null
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
if ! sudo $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q archy-tor; then
|
|
|
|
|
if sudo $DOCKER run -d --name archy-tor --restart unless-stopped --network host \
|
|
|
|
|
-v "$TOR_DIR:$TOR_DIR" \
|
|
|
|
|
--entrypoint tor \
|
|
|
|
|
docker.io/andrius/alpine-tor:latest \
|
|
|
|
|
-f "$TOR_DIR/torrc" >> "$LOG" 2>&1; then
|
|
|
|
|
echo "$(date): Tor container started" >> "$LOG"
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
# Wait for Tor to create hostname files (~30-60s), then chmod so archipelago user can read
|
|
|
|
|
# (Backend runs as archipelago and needs node_address for Nostr peer discovery)
|
|
|
|
|
# Must chmod parent dirs (711=traverse) and hostname files (644) - Tor creates 700 dirs
|
|
|
|
|
for i in 1 2 3 4 5 6 7 8 9 10; do
|
|
|
|
|
sleep 6
|
|
|
|
|
if [ -f "$TOR_DIR/hidden_service_archipelago/hostname" ]; then
|
|
|
|
|
chmod 711 "$TOR_DIR" "$TOR_DIR"/hidden_service_*/
|
|
|
|
|
for f in "$TOR_DIR"/hidden_service_*/hostname; do
|
|
|
|
|
[ -f "$f" ] && chmod 644 "$f" && echo "$(date): chmod hostname $f" >> "$LOG"
|
|
|
|
|
done
|
|
|
|
|
echo "$(date): Tor hostname files readable by archipelago" >> "$LOG"
|
|
|
|
|
break
|
|
|
|
|
fi
|
|
|
|
|
done
|
|
|
|
|
TORSCRIPT
|
|
|
|
|
|
|
|
|
|
chmod +x "$WORK_DIR/setup-tor.sh"
|
|
|
|
|
cp "$WORK_DIR/setup-tor.sh" "$ARCH_DIR/scripts/"
|
|
|
|
|
cp "$WORK_DIR/archipelago-setup-tor.service" "$ARCH_DIR/scripts/"
|
|
|
|
|
|
|
|
|
|
echo " ✅ Container images bundled (including Tor)"
|
|
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
|
|
# STEP 4: Create auto-installer script
|
|
|
|
@@ -813,6 +917,17 @@ if [ -d "$BOOT_MEDIA/archipelago/container-images" ]; then
|
|
|
|
|
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
|
|
|
|
|
if [ -f "$BOOT_MEDIA/archipelago/scripts/setup-tor.sh" ]; then
|
|
|
|
|
cp "$BOOT_MEDIA/archipelago/scripts/setup-tor.sh" /mnt/target/opt/archipelago/scripts/
|
|
|
|
|
chmod +x /mnt/target/opt/archipelago/scripts/setup-tor.sh
|
|
|
|
|
fi
|
|
|
|
|
if [ -d "$BOOT_MEDIA/archipelago/scripts/tor" ]; then
|
|
|
|
|
mkdir -p /mnt/target/opt/archipelago/scripts/tor
|
|
|
|
|
cp -r "$BOOT_MEDIA/archipelago/scripts/tor/"* /mnt/target/opt/archipelago/scripts/tor/ 2>/dev/null || true
|
|
|
|
|
fi
|
|
|
|
|
if [ -f "$BOOT_MEDIA/archipelago/scripts/archipelago-setup-tor.service" ]; then
|
|
|
|
|
cp "$BOOT_MEDIA/archipelago/scripts/archipelago-setup-tor.service" /mnt/target/etc/systemd/system/
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
echo " ✅ Container images staged for first-boot loading"
|
|
|
|
|
fi
|
|
|
|
@@ -873,7 +988,7 @@ chmod +x /mnt/target/etc/profile.d/archipelago.sh
|
|
|
|
|
cat > /mnt/target/etc/systemd/system/archipelago.service <<'SERVICE'
|
|
|
|
|
[Unit]
|
|
|
|
|
Description=Archipelago Backend
|
|
|
|
|
After=network-online.target
|
|
|
|
|
After=network-online.target archipelago-setup-tor.service
|
|
|
|
|
Wants=network-online.target
|
|
|
|
|
|
|
|
|
|
[Service]
|
|
|
|
@@ -907,6 +1022,7 @@ chroot /mnt/target update-grub
|
|
|
|
|
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
|
|
|
|
|
chroot /mnt/target systemctl enable archipelago-setup-tor.service 2>/dev/null || true
|
|
|
|
|
|
|
|
|
|
# Cleanup
|
|
|
|
|
sync
|
|
|
|
|