#!/bin/bash set -euo pipefail # TEST-201: Automated install/uninstall test for marketplace apps. # Runs on the dev server via SSH, testing each app: # 1. Install via package.install RPC # 2. Wait for container to start # 3. Verify health check / port responds # 4. Uninstall via package.uninstall RPC # 5. Verify container is removed # # Usage: ./scripts/test-app-install.sh [app-id] # Without args: tests all apps # With arg: tests only that app SSH_KEY="${ARCHIPELAGO_SSH_KEY:-$HOME/.ssh/archipelago-deploy}" TARGET="archipelago@192.168.1.228" SSH_CMD="ssh -i $SSH_KEY -o StrictHostKeyChecking=no $TARGET" PASSWORD="password123" # All marketplace apps and their expected ports declare -A APP_PORTS=( [bitcoin-knots]="8332" [electrs]="50001" [btcpay-server]="23000" [lnd]="8080" [mempool]="18080" [homeassistant]="8123" [grafana]="3033" [searxng]="18888" [ollama]="11434" [onlyoffice]="8044" [penpot]="9001" [nextcloud]="8085" [vaultwarden]="8099" [jellyfin]="8096" [photoprism]="2342" [immich]="2283" [filebrowser]="18082" [nginx-proxy-manager]="8181" [portainer]="9443" [uptime-kuma]="3001" [tailscale]="0" [fedimint]="8174" [indeedhub]="18081" [dwn]="3000" [nostr-rs-relay]="18081" ) # Apps that take a long time or have heavy dependencies — skip in quick mode HEAVY_APPS="bitcoin-knots electrs btcpay-server immich nextcloud homeassistant" PASS=0 FAIL=0 SKIP=0 RESULTS=() log() { echo -e "\033[1;34m[TEST]\033[0m $*"; } pass() { echo -e "\033[1;32m[PASS]\033[0m $*"; PASS=$((PASS + 1)); RESULTS+=("PASS: $*"); } fail() { echo -e "\033[1;31m[FAIL]\033[0m $*"; FAIL=$((FAIL + 1)); RESULTS+=("FAIL: $*"); } skip() { echo -e "\033[1;33m[SKIP]\033[0m $*"; SKIP=$((SKIP + 1)); RESULTS+=("SKIP: $*"); } # Authenticate and get session cookie get_session() { local cookie cookie=$($SSH_CMD "curl -s -c - http://localhost:5678/rpc/v1 \ -X POST -H 'Content-Type: application/json' \ -d '{\"method\":\"auth.login\",\"params\":{\"password\":\"$PASSWORD\"}}' 2>/dev/null \ | grep session | awk '{print \$NF}'") echo "$cookie" } # Make an authenticated RPC call rpc_call() { local session="$1" local method="$2" local params="${3:-{}}" $SSH_CMD "curl -s http://localhost:5678/rpc/v1 \ -X POST -H 'Content-Type: application/json' \ -H 'Cookie: session=$session' \ -d '{\"method\":\"$method\",\"params\":$params}' 2>/dev/null" } # Check if a container exists container_exists() { local session="$1" local app_id="$2" local result result=$(rpc_call "$session" "container-list") echo "$result" | grep -q "\"$app_id\"" && return 0 || return 1 } # Wait for container to appear (up to 60s) wait_for_container() { local session="$1" local app_id="$2" local max_wait=60 local waited=0 while [ $waited -lt $max_wait ]; do if container_exists "$session" "$app_id"; then return 0 fi sleep 5 waited=$((waited + 5)) done return 1 } # Check if port responds check_port() { local port="$1" if [ "$port" = "0" ]; then return 0 # No port to check (e.g., tailscale) fi $SSH_CMD "curl -s -o /dev/null -w '%{http_code}' --connect-timeout 5 http://localhost:$port/ 2>/dev/null" | grep -qE '(200|301|302|401|403|404)' && return 0 || return 1 } test_app() { local app_id="$1" local session="$2" local port="${APP_PORTS[$app_id]:-0}" log "Testing $app_id (port: $port)" # Skip if container already exists (don't disturb running services) if container_exists "$session" "$app_id"; then skip "$app_id — already running, skipping to avoid disruption" return fi # 1. Install log " Installing $app_id..." local install_result install_result=$(rpc_call "$session" "package.install" "{\"id\":\"$app_id\"}") if echo "$install_result" | grep -q '"error"'; then local err_msg err_msg=$(echo "$install_result" | grep -o '"message":"[^"]*"' | head -1) # Dependency errors are expected for some apps if echo "$err_msg" | grep -qi "dependency\|requires\|must be"; then skip "$app_id — dependency not met: $err_msg" return fi fail "$app_id — install failed: $err_msg" return fi # 2. Wait for container log " Waiting for container..." if ! wait_for_container "$session" "$app_id"; then fail "$app_id — container did not appear within 60s" # Try to clean up rpc_call "$session" "package.uninstall" "{\"id\":\"$app_id\"}" > /dev/null 2>&1 return fi # 3. Check port (give it a moment to start) if [ "$port" != "0" ]; then sleep 3 log " Checking port $port..." if check_port "$port"; then log " Port $port responds" else log " Port $port not responding yet (may need more time)" fi fi # 4. Uninstall log " Uninstalling $app_id..." local uninstall_result uninstall_result=$(rpc_call "$session" "package.uninstall" "{\"id\":\"$app_id\"}") if echo "$uninstall_result" | grep -q '"error"'; then fail "$app_id — uninstall failed" return fi # 5. Verify container removed sleep 3 if container_exists "$session" "$app_id"; then fail "$app_id — container still exists after uninstall" return fi pass "$app_id — install/uninstall cycle complete" } main() { log "=== Archipelago App Install/Uninstall Test ===" log "Target: $TARGET" log "" # Get session log "Authenticating..." local session session=$(get_session) if [ -z "$session" ]; then echo "Failed to authenticate. Exiting." exit 1 fi log "Session: ${session:0:8}..." echo "" # Determine which apps to test local apps_to_test=() if [ $# -gt 0 ]; then apps_to_test=("$@") else for app in "${!APP_PORTS[@]}"; do apps_to_test+=("$app") done # Sort for consistent ordering IFS=$'\n' apps_to_test=($(sort <<<"${apps_to_test[*]}")); unset IFS fi log "Testing ${#apps_to_test[@]} apps" echo "" for app_id in "${apps_to_test[@]}"; do test_app "$app_id" "$session" echo "" done # Summary echo "" log "=== RESULTS ===" for r in "${RESULTS[@]}"; do echo " $r" done echo "" log "Pass: $PASS | Fail: $FAIL | Skip: $SKIP | Total: $((PASS + FAIL + SKIP))" if [ $FAIL -gt 0 ]; then exit 1 fi } main "$@"