From 02ab39872600e4f87c527a2ebb5acd22ce3ddf7a Mon Sep 17 00:00:00 2001 From: Dorian Date: Fri, 10 Apr 2026 04:01:35 -0400 Subject: [PATCH] fix: unbundled first-boot, fast VPN status, kiosk relay dedup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Unbundled ISO: first-boot only creates FileBrowser (marker file .unbundled) Users install apps from Marketplace — no more bitcoin/mempool on clean install - VPN status: read tunnel IP from config file (instant) instead of nvpn status (22s) - Kiosk: App.vue skips remote relay on /kiosk path (prevents duplicate input) Co-Authored-By: Claude Opus 4.6 (1M context) --- core/archipelago/src/vpn.rs | 56 ++++++++--------------- image-recipe/build-auto-installer-iso.sh | 8 ++++ scripts/first-boot-containers.sh | 57 ++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 38 deletions(-) diff --git a/core/archipelago/src/vpn.rs b/core/archipelago/src/vpn.rs index b6b6a6df..f38a3aa0 100644 --- a/core/archipelago/src/vpn.rs +++ b/core/archipelago/src/vpn.rs @@ -322,46 +322,26 @@ async fn get_nostr_vpn_status() -> Result { anyhow::bail!("nostr-vpn service not running"); } - // Get tunnel IP: try nvpn status first, fall back to config, then interface + // Get tunnel IP: config file first (instant), fall back to interface + // NOTE: Do NOT call `nvpn status` — it blocks for 20+ seconds connecting to relays let ip = { - // Method 1: nvpn status (most accurate) - let status_ip = tokio::process::Command::new("nvpn") - .args(["status"]) - .env("HOME", "/var/lib/archipelago/nostr-vpn") - .env("XDG_CONFIG_HOME", "/var/lib/archipelago/nostr-vpn/.config") - .output() - .await - .ok() - .and_then(|o| { - let out = String::from_utf8_lossy(&o.stdout).to_string(); - out.lines() - .find(|l| l.starts_with("tunnel_ip:")) - .map(|l| l.split(':').nth(1).unwrap_or("").trim().to_string()) - }) - .filter(|s| !s.is_empty()); - - if status_ip.is_some() { - status_ip + let cfg_ip = read_nvpn_config_value("node", "tunnel_ip").await; + if cfg_ip.is_some() { + cfg_ip } else { - // Method 2: config file - let cfg_ip = read_nvpn_config_value("node", "tunnel_ip").await; - if cfg_ip.is_some() { - cfg_ip - } else { - // Method 3: interface IP - tokio::process::Command::new("ip") - .args(["-4", "addr", "show", "nvpn0"]) - .output() - .await - .ok() - .and_then(|o| { - let out = String::from_utf8_lossy(&o.stdout).to_string(); - out.lines() - .find(|l| l.contains("inet ")) - .and_then(|l| l.split_whitespace().nth(1)) - .map(|s| s.to_string()) - }) - } + // Fallback: read from network interface + tokio::process::Command::new("ip") + .args(["-4", "addr", "show", "nvpn0"]) + .output() + .await + .ok() + .and_then(|o| { + let out = String::from_utf8_lossy(&o.stdout).to_string(); + out.lines() + .find(|l| l.contains("inet ")) + .and_then(|l| l.split_whitespace().nth(1)) + .map(|s| s.to_string()) + }) } }; diff --git a/image-recipe/build-auto-installer-iso.sh b/image-recipe/build-auto-installer-iso.sh index 007ffc65..b667c6c1 100755 --- a/image-recipe/build-auto-installer-iso.sh +++ b/image-recipe/build-auto-installer-iso.sh @@ -1150,6 +1150,8 @@ echo "" if [ "$UNBUNDLED" = "1" ]; then echo "📦 Step 3b: Bundling core containers only (UNBUNDLED mode)" echo " Optional apps will be downloaded on-demand from the Marketplace after install." + # Marker file: first-boot-containers.sh checks this to skip app creation + touch "$ARCH_DIR/.unbundled" IMAGES_DIR="$ARCH_DIR/container-images" mkdir -p "$IMAGES_DIR" # FileBrowser is a core dependency (powers the Cloud file manager) — always bundle it @@ -2154,6 +2156,12 @@ if [ -d "$BOOT_MEDIA/archipelago/web-ui" ]; then cp -r "$BOOT_MEDIA/archipelago/web-ui" /mnt/target/opt/archipelago/ fi +# Mark unbundled mode so first-boot only creates FileBrowser (user installs apps from Marketplace) +if [ -f "$BOOT_MEDIA/archipelago/.unbundled" ]; then + touch /mnt/target/opt/archipelago/.unbundled + echo " Unbundled mode: apps install on-demand from Marketplace" +fi + if [ -d "$BOOT_MEDIA/archipelago/apps" ]; then cp -r "$BOOT_MEDIA/archipelago/apps" /mnt/target/etc/archipelago/ fi diff --git a/scripts/first-boot-containers.sh b/scripts/first-boot-containers.sh index 88e31bdd..81ce4913 100644 --- a/scripts/first-boot-containers.sh +++ b/scripts/first-boot-containers.sh @@ -48,6 +48,63 @@ SCRIPT_DIR_FBC="$(cd "$(dirname "$0")" && pwd)" # as root (rootful podman), the backend can't see them at all. DOCKER="runuser -u archipelago -- env XDG_RUNTIME_DIR=/run/user/$(id -u archipelago) podman" +# UNBUNDLED mode: only create FileBrowser, skip all other containers. +# Users install apps on-demand from the Marketplace. +UNBUNDLED_MARKER="/opt/archipelago/.unbundled" +if [ -f "$UNBUNDLED_MARKER" ]; then + log "UNBUNDLED mode detected — creating FileBrowser only (apps install from Marketplace)" + + # Core setup: secrets, podman prerequisites, FileBrowser + SECRETS_DIR="/var/lib/archipelago/secrets" + mkdir -p "$SECRETS_DIR" && chmod 700 "$SECRETS_DIR" + if [ ! -f "$SECRETS_DIR/bitcoin-rpc-password" ]; then + openssl rand -hex 16 > "$SECRETS_DIR/bitcoin-rpc-password" + chmod 600 "$SECRETS_DIR/bitcoin-rpc-password" + fi + # Generate all DB passwords upfront so they're stable + for svc in mempool btcpay mysql-root; do + if [ ! -f "$SECRETS_DIR/${svc}-db-password" ]; then + openssl rand -hex 16 > "$SECRETS_DIR/${svc}-db-password" + chmod 600 "$SECRETS_DIR/${svc}-db-password" + fi + done + chown -R 1000:1000 "$SECRETS_DIR" + + # Podman prerequisites + loginctl enable-linger archipelago 2>/dev/null || true + DOCKER="runuser -u archipelago -- env XDG_RUNTIME_DIR=/run/user/$(id -u archipelago) podman" + $DOCKER system migrate 2>/dev/null || true + + # Ensure archy-net exists + $DOCKER network create archy-net 2>/dev/null || true + + # Create FileBrowser only + if ! $DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -q filebrowser; then + log "Creating FileBrowser..." + mkdir -p /var/lib/archipelago/filebrowser /var/lib/archipelago/filebrowser-data + mkdir -p /var/lib/archipelago/data/cloud/{Documents,Photos,Music,Videos,Downloads} + sudo chown -R 100000:100000 /var/lib/archipelago/filebrowser + sudo chown -R 100000:100000 /var/lib/archipelago/filebrowser-data + sudo chown -R 100000:100000 /var/lib/archipelago/data + $DOCKER run -d --name filebrowser --restart unless-stopped \ + --cap-drop=ALL --cap-add=DAC_OVERRIDE --cap-add=NET_BIND_SERVICE \ + --security-opt=no-new-privileges:true \ + --health-cmd='curl -sf http://localhost:80/ || exit 1' \ + --health-interval=30s --health-timeout=5s --health-retries=3 \ + --memory=256m \ + -p 8083:80 \ + -v /var/lib/archipelago/filebrowser:/srv \ + -v /var/lib/archipelago/filebrowser-data:/data \ + -v /var/lib/archipelago/data/cloud:/srv/cloud \ + ${FILEBROWSER_IMAGE} \ + --database=/data/database.db --root=/srv --address=0.0.0.0 --port=80 2>>"$LOG" && \ + log " FileBrowser created" || log " WARNING: FileBrowser creation failed" + fi + + log "Unbundled first-boot complete" + exit 0 +fi + TARGET_IP=$(hostname -I 2>/dev/null | awk '{print $1}') [ -z "$TARGET_IP" ] && TARGET_IP="127.0.0.1"