From 9741e73824b12d2d4a051cec9d7ce1bc2a8705c9 Mon Sep 17 00:00:00 2001 From: Dorian Date: Fri, 27 Mar 2026 17:17:18 +0000 Subject: [PATCH] fix: filebrowser port bind, CSRF in tests, console-setup, auto-test scope FileBrowser crash fix: - Add --cap-add=NET_BIND_SERVICE (port 80 needs it with --cap-drop=ALL) - Add --cap-add=DAC_OVERRIDE for rootless volume access - Both in first-boot script and backend config.rs Test script fixes: - Extract csrf_token cookie and send as X-CSRF-Token header on RPC calls - Add --phase1-only flag for safe install-only checks (no side effects) - Auto-test service uses --phase1-only so it doesn't steal onboarding Install fixes: - Pre-create ~/.local/share/containers (ReadWritePaths mount namespace error) - Fix console-setup.service: add After=tmp.mount + ExecStartPre mkdir /tmp Co-Authored-By: Claude Opus 4.6 (1M context) --- .../archipelago/src/api/rpc/package/config.rs | 3 +- image-recipe/build-auto-installer-iso.sh | 29 +++++++++---- scripts/run-post-install-tests.sh | 42 ++++++++++++++++--- 3 files changed, 61 insertions(+), 13 deletions(-) diff --git a/core/archipelago/src/api/rpc/package/config.rs b/core/archipelago/src/api/rpc/package/config.rs index f62a857f..7455c2a1 100644 --- a/core/archipelago/src/api/rpc/package/config.rs +++ b/core/archipelago/src/api/rpc/package/config.rs @@ -97,9 +97,10 @@ pub(super) fn get_app_capabilities(app_id: &str) -> Vec { "--cap-add=SETUID".to_string(), "--cap-add=SETGID".to_string(), ], - // FileBrowser needs DAC_OVERRIDE to read/write volume files under rootless podman + // FileBrowser needs DAC_OVERRIDE for volume access + NET_BIND_SERVICE to bind port 80 "filebrowser" => vec![ "--cap-add=DAC_OVERRIDE".to_string(), + "--cap-add=NET_BIND_SERVICE".to_string(), ], // Minimal apps (searxng, etc.) need no extra caps _ => vec![], diff --git a/image-recipe/build-auto-installer-iso.sh b/image-recipe/build-auto-installer-iso.sh index 250c2099..eb8e4da0 100755 --- a/image-recipe/build-auto-installer-iso.sh +++ b/image-recipe/build-auto-installer-iso.sh @@ -961,6 +961,7 @@ if ! runuser -u archipelago -- bash -c 'export XDG_RUNTIME_DIR=/run/user/1000 && runuser -u archipelago -- bash -c "export XDG_RUNTIME_DIR=/run/user/1000 && podman 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 \ --read-only \ --tmpfs=/tmp:rw,noexec,nosuid,size=64m \ @@ -1364,8 +1365,12 @@ cat > /mnt/target/etc/hosts < /mnt/target/home/archipelago/.config/containers/registries.conf <<'REGCONF' [[registry]] location = "80.71.235.15:3000" @@ -1998,6 +2003,17 @@ chroot /mnt/target systemctl enable archipelago-setup-tor.service 2>/dev/null || chroot /mnt/target systemctl enable archipelago-first-boot-containers.service 2>/dev/null || true chroot /mnt/target systemctl enable archipelago-kiosk.service 2>/dev/null || true +# Fix console-setup: setupcon needs /tmp writable, add ordering dependency +mkdir -p /mnt/target/etc/systemd/system/console-setup.service.d +cat > /mnt/target/etc/systemd/system/console-setup.service.d/fix-tmp.conf <<'CONSOLEFIX' +[Unit] +After=tmp.mount systemd-tmpfiles-setup.service +Wants=tmp.mount + +[Service] +ExecStartPre=/bin/mkdir -p /tmp +CONSOLEFIX + # Auto-login on tty1 — no password prompt on console mkdir -p /mnt/target/etc/systemd/system/getty@tty1.service.d cat > /mnt/target/etc/systemd/system/getty@tty1.service.d/autologin.conf <<'AUTOLOGIN' @@ -2007,24 +2023,23 @@ ExecStart=-/sbin/agetty --autologin archipelago --noclear %I $TERM AUTOLOGIN chroot /mnt/target systemctl enable archipelago-diagnostics.service 2>/dev/null || true -# Post-install test runner — runs once on first boot after all services are up +# Post-install smoke test — runs Phase 1 (install verification) only on first boot +# Does NOT run onboarding or create passwords — lets user do that via the UI cat > /mnt/target/etc/systemd/system/archipelago-post-install-tests.service <<'PITSERVICE' [Unit] -Description=Archipelago Post-Install Test Suite (first boot) +Description=Archipelago Install Verification (first boot) After=archipelago.service archipelago-first-boot-containers.service nginx.service Wants=archipelago.service nginx.service -ConditionPathExists=/opt/archipelago/scripts/run-post-install-tests.sh ConditionPathExists=!/var/lib/archipelago/.post-install-tests-done [Service] Type=oneshot -# Wait for backend to fully initialize ExecStartPre=/bin/bash -c 'for i in $(seq 1 30); do curl -sf http://127.0.0.1:5678/health >/dev/null 2>&1 && exit 0; sleep 2; done; exit 0' -ExecStart=/bin/bash -c '/opt/archipelago/scripts/run-post-install-tests.sh "password123" 2>&1 | tee /var/log/archipelago-post-install-tests.log; touch /var/lib/archipelago/.post-install-tests-done' +ExecStart=/bin/bash -c '/opt/archipelago/scripts/run-post-install-tests.sh --phase1-only 2>&1 | tee /var/log/archipelago-post-install-tests.log; touch /var/lib/archipelago/.post-install-tests-done' RemainAfterExit=yes StandardOutput=journal+console StandardError=journal+console -TimeoutStartSec=300 +TimeoutStartSec=120 [Install] WantedBy=multi-user.target diff --git a/scripts/run-post-install-tests.sh b/scripts/run-post-install-tests.sh index e0d0c79b..47ac80cd 100755 --- a/scripts/run-post-install-tests.sh +++ b/scripts/run-post-install-tests.sh @@ -3,15 +3,24 @@ # Run on an installed Archipelago node (SSH or local). # # Usage: bash run-post-install-tests.sh [password] -# password defaults to "testpass123!" for fresh installs +# bash run-post-install-tests.sh --phase1-only # Install checks only (no auth) # # Tests: -# Phase 1: Install verification (services, files, logs) -# Phase 2: Onboarding (password setup, auth flow) -# Phase 3: Container lifecycle (install 3 apps, start/stop/health) +# Phase 1: Install verification (services, files, logs) — safe, no side effects +# Phase 2: Onboarding (password setup, auth flow) — creates user account +# Phase 3: Container lifecycle (install 3 apps, start/stop/health) — needs auth set -u -PASSWORD="${1:-testpass123!}" +PHASE1_ONLY=false +PASSWORD="testpass123!" + +for arg in "$@"; do + case "$arg" in + --phase1-only) PHASE1_ONLY=true ;; + *) PASSWORD="$arg" ;; + esac +done + BASE="http://127.0.0.1:5678" JAR="/tmp/e2e-cookies.txt" rm -f "$JAR" @@ -22,11 +31,23 @@ fail() { FC=$((FC + 1)); printf "\033[31m ✗ %s — %s\033[0m\n" "$1" "${2:-}" skip() { SC=$((SC + 1)); printf "\033[33m ⊘ %s\033[0m\n" "$1"; } section() { printf "\n\033[1m━━━ %s ━━━\033[0m\n" "$1"; } +# Extract CSRF token from cookie jar +get_csrf() { + grep 'csrf_token' "$JAR" 2>/dev/null | awk '{print $NF}' +} + rpc() { local method="$1" local params="${2:-"{}"}" + local csrf + csrf=$(get_csrf) + local csrf_header="" + if [ -n "$csrf" ]; then + csrf_header="-H X-CSRF-Token:${csrf}" + fi curl -s -b "$JAR" -c "$JAR" \ -H "Content-Type: application/json" \ + $csrf_header \ -d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"$method\",\"params\":$params}" \ "${BASE}/rpc/v1" 2>/dev/null } @@ -153,6 +174,17 @@ else fail "Nginx config" "nginx -t failed" fi +# ── Phase 1 exit point ── +if [ "$PHASE1_ONLY" = "true" ]; then + section "Results (Phase 1 only)" + TOTAL=$((PC + FC + SC)) + printf "\n \033[32mPassed: %d\033[0m \033[31mFailed: %d\033[0m \033[33mSkipped: %d\033[0m Total: %d\n\n" "$PC" "$FC" "$SC" "$TOTAL" + [ "$FC" -gt 0 ] && echo " Phase 1: SOME CHECKS FAILED" && exit 1 + echo " Phase 1: ALL CHECKS PASSED" + echo " Run without --phase1-only to test onboarding + containers" + exit 0 +fi + # ═══════════════════════════════════════════ # PHASE 2: Onboarding & Auth # ═══════════════════════════════════════════