fix: unbundled first-boot, fast VPN status, kiosk relay dedup

- 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) <noreply@anthropic.com>
This commit is contained in:
Dorian 2026-04-10 04:01:35 -04:00
parent 7393c5f158
commit 02ab398726
3 changed files with 83 additions and 38 deletions

View File

@ -322,46 +322,26 @@ async fn get_nostr_vpn_status() -> Result<VpnStatus> {
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())
})
}
};

View File

@ -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

View File

@ -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"