162 lines
5.2 KiB
Bash
162 lines
5.2 KiB
Bash
|
|
#!/bin/bash
|
||
|
|
#
|
||
|
|
# App surface smoke test.
|
||
|
|
#
|
||
|
|
# Verifies that installed containers have their published host ports listening
|
||
|
|
# and that known nginx app proxy paths return a non-5xx response. This catches
|
||
|
|
# the common "container is running but UI disappeared" failure mode.
|
||
|
|
#
|
||
|
|
# Usage:
|
||
|
|
# scripts/app-surface-smoke-test.sh --target archipelago@192.168.1.228 --ssh-key /path/key
|
||
|
|
|
||
|
|
set -euo pipefail
|
||
|
|
|
||
|
|
TARGET=""
|
||
|
|
SSH_KEY="${ARCHIPELAGO_SSH_KEY:-}"
|
||
|
|
SSH_EXTRA=()
|
||
|
|
|
||
|
|
while [ "$#" -gt 0 ]; do
|
||
|
|
case "$1" in
|
||
|
|
--target) TARGET="${2:-}"; shift 2 ;;
|
||
|
|
--ssh-key) SSH_KEY="${2:-}"; shift 2 ;;
|
||
|
|
--ssh-option) SSH_EXTRA+=("-o" "${2:-}"); shift 2 ;;
|
||
|
|
-h|--help) sed -n '1,12p' "$0"; exit 0 ;;
|
||
|
|
*) echo "unknown argument: $1" >&2; exit 2 ;;
|
||
|
|
esac
|
||
|
|
done
|
||
|
|
|
||
|
|
[ -n "$TARGET" ] || { echo "--target is required" >&2; exit 2; }
|
||
|
|
|
||
|
|
SSH_OPTS=(-F /dev/null -o BatchMode=yes -o PreferredAuthentications=publickey -o PasswordAuthentication=no)
|
||
|
|
[ -n "$SSH_KEY" ] && SSH_OPTS+=(-i "$SSH_KEY")
|
||
|
|
SSH_OPTS+=("${SSH_EXTRA[@]}")
|
||
|
|
|
||
|
|
ssh_run() {
|
||
|
|
ssh "${SSH_OPTS[@]}" "$TARGET" "$@"
|
||
|
|
}
|
||
|
|
|
||
|
|
ssh_run 'bash -s' <<'REMOTE'
|
||
|
|
set -u
|
||
|
|
|
||
|
|
pass=0
|
||
|
|
fail=0
|
||
|
|
|
||
|
|
ok() { echo " PASS $*"; pass=$((pass + 1)); }
|
||
|
|
bad() { echo " FAIL $*"; fail=$((fail + 1)); }
|
||
|
|
|
||
|
|
container_exists() {
|
||
|
|
podman ps -a --format '{{.Names}}' 2>/dev/null | grep -qx "$1"
|
||
|
|
}
|
||
|
|
|
||
|
|
port_listening() {
|
||
|
|
ss -ltn 2>/dev/null | awk '{print $4}' | grep -Eq "(^|:)$1$"
|
||
|
|
}
|
||
|
|
|
||
|
|
http_code() {
|
||
|
|
local url="$1" code
|
||
|
|
for _ in 1 2 3; do
|
||
|
|
code=$(curl -ksS -o /dev/null -w '%{http_code}' --max-time 12 "$url" 2>/dev/null || true)
|
||
|
|
[ -n "$code" ] || code=000
|
||
|
|
[ "$code" != "000" ] && { echo "$code"; return; }
|
||
|
|
sleep 2
|
||
|
|
done
|
||
|
|
echo "$code"
|
||
|
|
}
|
||
|
|
|
||
|
|
http_post_code() {
|
||
|
|
local url="$1" code
|
||
|
|
for _ in 1 2 3; do
|
||
|
|
code=$(curl -ksS -o /dev/null -w '%{http_code}' --max-time 25 \
|
||
|
|
-H 'Content-Type: application/json' \
|
||
|
|
-d '{"jsonrpc":"2.0","id":1,"method":"getblockchaininfo","params":[]}' \
|
||
|
|
"$url" 2>/dev/null || true)
|
||
|
|
[ -n "$code" ] || code=000
|
||
|
|
[ "$code" != "000" ] && { echo "$code"; return; }
|
||
|
|
sleep 2
|
||
|
|
done
|
||
|
|
echo "$code"
|
||
|
|
}
|
||
|
|
|
||
|
|
assert_http() {
|
||
|
|
local label="$1" url="$2" code
|
||
|
|
code=$(http_code "$url")
|
||
|
|
case "$code" in
|
||
|
|
200|204|301|302|307|308|401|403) ok "$label HTTP $code" ;;
|
||
|
|
*) bad "$label HTTP $code ($url)" ;;
|
||
|
|
esac
|
||
|
|
}
|
||
|
|
|
||
|
|
assert_http_post() {
|
||
|
|
local label="$1" url="$2" code
|
||
|
|
code=$(http_post_code "$url")
|
||
|
|
case "$code" in
|
||
|
|
200|204|401|403) ok "$label HTTP POST $code" ;;
|
||
|
|
*) bad "$label HTTP POST $code ($url)" ;;
|
||
|
|
esac
|
||
|
|
}
|
||
|
|
|
||
|
|
assert_container_ports() {
|
||
|
|
local name="$1" ports port missing=0
|
||
|
|
container_exists "$name" || return 0
|
||
|
|
ports=$(podman inspect "$name" --format '{{range $p,$bindings := .NetworkSettings.Ports}}{{if $bindings}}{{range $bindings}}{{.HostPort}}{{"\n"}}{{end}}{{end}}{{end}}' 2>/dev/null | sort -u)
|
||
|
|
[ -n "$ports" ] || return 0
|
||
|
|
while IFS= read -r port; do
|
||
|
|
[ -n "$port" ] || continue
|
||
|
|
if port_listening "$port"; then
|
||
|
|
ok "$name port $port listening"
|
||
|
|
else
|
||
|
|
bad "$name port $port missing listener"
|
||
|
|
missing=1
|
||
|
|
fi
|
||
|
|
done <<< "$ports"
|
||
|
|
return "$missing"
|
||
|
|
}
|
||
|
|
|
||
|
|
assert_env_contains() {
|
||
|
|
local name="$1" key="$2" needle="$3" val
|
||
|
|
container_exists "$name" || return 0
|
||
|
|
val=$(podman inspect "$name" --format '{{range .Config.Env}}{{println .}}{{end}}' 2>/dev/null | sed -n "s/^${key}=//p" | head -n 1)
|
||
|
|
if [ -n "$val" ] && printf '%s' "$val" | grep -qF "$needle"; then
|
||
|
|
ok "$name env $key"
|
||
|
|
else
|
||
|
|
bad "$name env $key missing $needle"
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
echo "[surface] host=$(hostname) ip=$(hostname -I 2>/dev/null | awk '{print $1}')"
|
||
|
|
|
||
|
|
for c in $(podman ps -a --format '{{.Names}}' 2>/dev/null | sort); do
|
||
|
|
assert_container_ports "$c" || true
|
||
|
|
done
|
||
|
|
|
||
|
|
container_exists archy-bitcoin-ui && {
|
||
|
|
assert_http "bitcoin-ui" "http://127.0.0.1/app/bitcoin-ui/"
|
||
|
|
assert_http "bitcoin status" "http://127.0.0.1/app/bitcoin-ui/bitcoin-status"
|
||
|
|
assert_http_post "bitcoin rpc proxy" "http://127.0.0.1/app/bitcoin-ui/bitcoin-rpc/"
|
||
|
|
}
|
||
|
|
|
||
|
|
container_exists archy-electrs-ui && {
|
||
|
|
assert_http "electrumx ui" "http://127.0.0.1/app/electrumx/"
|
||
|
|
assert_http "electrumx status" "http://127.0.0.1/app/electrumx/electrs-status"
|
||
|
|
assert_http "electrs legacy status" "http://127.0.0.1/app/electrs/electrs-status"
|
||
|
|
}
|
||
|
|
|
||
|
|
container_exists mempool && assert_http "mempool ui" "http://127.0.0.1/app/mempool/"
|
||
|
|
container_exists indeedhub && assert_http "indeedhub ui" "http://127.0.0.1:7778/"
|
||
|
|
container_exists uptime-kuma && assert_http "uptime-kuma" "http://127.0.0.1/app/uptime-kuma/"
|
||
|
|
container_exists filebrowser && assert_http "filebrowser" "http://127.0.0.1/app/filebrowser/"
|
||
|
|
container_exists searxng && assert_http "searxng" "http://127.0.0.1/app/searxng/"
|
||
|
|
container_exists grafana && assert_http "grafana" "http://127.0.0.1/app/grafana/"
|
||
|
|
container_exists portainer && assert_http "portainer" "http://127.0.0.1/app/portainer/"
|
||
|
|
container_exists vaultwarden && assert_http "vaultwarden" "http://127.0.0.1/app/vaultwarden/"
|
||
|
|
container_exists nextcloud && assert_http "nextcloud" "http://127.0.0.1/app/nextcloud/"
|
||
|
|
container_exists archy-nbxplorer && assert_env_contains "archy-nbxplorer" "NBXPLORER_POSTGRES" "Database=nbxplorer"
|
||
|
|
container_exists btcpay-server && {
|
||
|
|
assert_env_contains "btcpay-server" "BTCPAY_POSTGRES" "Database=btcpay"
|
||
|
|
assert_http "btcpay" "http://127.0.0.1/app/btcpay/"
|
||
|
|
}
|
||
|
|
|
||
|
|
echo "[surface] summary: pass=$pass fail=$fail"
|
||
|
|
[ "$fail" -eq 0 ]
|
||
|
|
REMOTE
|