diff --git a/scripts/taler-monitoring/README.md b/scripts/taler-monitoring/README.md index 4771c6a..e627a35 100644 --- a/scripts/taler-monitoring/README.md +++ b/scripts/taler-monitoring/README.md @@ -81,12 +81,18 @@ Each check prints **global** and **grouped** ids: ./taler-monitoring.sh sanity ./taler-monitoring.sh e2e -# FrancPaysan STAGE (TESTPAYSAN) — default: urls + e2e (withdraw + pay templates) +# FrancPaysan STAGE (TESTPAYSAN) — like hacktivism breadth: +# default phases: urls + inside + versions + e2e ./taler-monitoring.sh -d stage.lefrancpaysan.ch -# Admin credit: E2E_BANK_ADMIN_PASS=… or SSH francpaysan-host secrets +./taler-monitoring.sh -d stage.lefrancpaysan.ch inside # stagepaysan only +# inside: low-priv SSH francpaysan-stage-user (User stagepaysan) → podman +# containers stage-lfp-{bank,exchange-ansible,merchant} · ports 9032/9031/9030 +# stats: outside-in public …/intro/stats.json (age via generated_at_unix) +# Admin credit for e2e: E2E_BANK_ADMIN_PASS=… or francpaysan-host secrets # (bank-admin-password.txt under /mnt/data/stagepaysan/bank/secrets/) +# Override: INSIDE_SSH=… INSIDE_PROFILE=stage-lfp -# Other domains — public HTTPS only, never SSH (pass e2e explicitly) +# Other domains — public HTTPS only (pass e2e/inside explicitly) ./taler-monitoring.sh -d taler.net ./taler-monitoring.sh --domain taler-ops.ch ./taler-monitoring.sh -d my.taler-ops.ch urls @@ -165,13 +171,18 @@ Helpers in `lib.sh`: `set_taler_stack`, `load_domain_profile`, `apply_taler_doma | `taler.net` / `demo.taler.net` | bank.demo.taler.net | exchange.demo.taler.net | backend.demo.taler.net | KUDOS | | `test.taler.net` | bank.test… | exchange.test… | backend.test… | **TESTKUDOS** · no landings | | `lefrancpaysan.ch` | bank.… | exchange.… | **monnaie.…** (merchant) | report-only · no landings | +| **`stage.lefrancpaysan.ch`** | stage.bank.… | stage.exchange.… | **stage.monnaie.…** | **TESTPAYSAN** · landings · **inside via stagepaysan** | | `taler-ops.ch` / `my.taler-ops.ch` | bank.taler-ops.ch | exchange.taler-ops.ch | **my.taler-ops.ch** | CHF · **no landings** | | `stage.taler-ops.ch` | bank.stage… | exchange.stage… | **my.stage…** | CHF · **no landings** | | unknown | bank.DOMAIN | exchange.DOMAIN | my.DOMAIN then probe | any · no landings | -**Landings:** only GOA (and optionally demo) use public `/intro` pages. **taler-ops.ch does not** — `CHECK_LANDING=0` in the profile skips intro crawl, shop assets, and demo-withdraw checks. +**Landings:** GOA, stage LFP (`TESTPAYSAN`), and optionally demo use public `/intro` pages. **taler-ops.ch does not** — `CHECK_LANDING=0` skips intro crawl. -**SSH only for koopa** (`hacktivism.ch` / `-d koopa`). +**SSH:** +| Stack | Host alias | User | Scope | +|-------|------------|------|--------| +| koopa / hacktivism | `koopa` | hernani | full host + containers | +| stage LFP | `francpaysan-stage-user` | **stagepaysan** | own podman only (no sudo / no prod data) | Other domains: never SSH. Optional **e2e** aborts cleanly on login/KYC. diff --git a/scripts/taler-monitoring/check_inside.sh b/scripts/taler-monitoring/check_inside.sh index b5db3b1..0584c9b 100755 --- a/scripts/taler-monitoring/check_inside.sh +++ b/scripts/taler-monitoring/check_inside.sh @@ -1,5 +1,9 @@ #!/usr/bin/env bash -# Inside status for bank / exchange / merchant. Hard-capped SSH — never hang forever. +# Inside status for bank / exchange / merchant containers. +# Profiles: +# koopa (default LOCAL_STACK=1) — SSH KOOPA_SSH, containers taler-hacktivism* +# stage-lfp (TESTPAYSAN) — SSH INSIDE_SSH (francpaysan-stage-user / stagepaysan), +# low-priv podman only; ports 9030–9032; stats remain outside-in (urls). set -euo pipefail ROOT=$(cd "$(dirname "$0")" && pwd) # shellcheck source=lib.sh @@ -7,14 +11,250 @@ source "$ROOT/lib.sh" # shellcheck source=metrics.sh source "$ROOT/metrics.sh" -# Area inside.* — container / process state on koopa (SSH) -# Groups: inside.ssh / inside.bank / inside.exchange / inside.merchant / inside.caddy set_area inside set_group ssh + +PROFILE="${INSIDE_PROFILE:-}" +if [ -z "$PROFILE" ]; then + if [ "${LOCAL_STACK:-0}" = "1" ]; then + PROFILE=koopa + elif [ "${EXPECT_CURRENCY:-}" = "TESTPAYSAN" ] || [ -n "${INSIDE_SSH:-}" ]; then + PROFILE=stage-lfp + else + PROFILE=koopa + fi +fi + +section "inside · collect (${PROFILE})" + +# --------------------------------------------------------------------------- +# stage-lfp: low-priv stagepaysan on francpaysan-host +# --------------------------------------------------------------------------- +if [ "$PROFILE" = "stage-lfp" ]; then + SSH_HOST="${INSIDE_SSH:-francpaysan-stage-user}" + BANK_CTR="${INSIDE_BANK_CTR:-stage-lfp-bank}" + EX_CTR="${INSIDE_EXCHANGE_CTR:-stage-lfp-exchange-ansible}" + MER_CTR="${INSIDE_MERCHANT_CTR:-stage-lfp-merchant}" + BANK_PORT="${INSIDE_BANK_PORT:-9032}" + EX_PORT="${INSIDE_EXCHANGE_PORT:-9031}" + MER_PORT="${INSIDE_MERCHANT_PORT:-9030}" + DNS_BANK="${INSIDE_DNS_BANK:-stage.bank.lefrancpaysan.ch}" + DNS_EX="${INSIDE_DNS_EXCHANGE:-stage.exchange.lefrancpaysan.ch}" + DNS_MER="${INSIDE_DNS_MERCHANT:-stage.monnaie.lefrancpaysan.ch}" + # Stage remote has more podman execs than koopa; allow a bit more wall time + STAGE_SSH_T="${INSIDE_SSH_TIMEOUT:-${SSH_CMD_TIMEOUT:-24}}" + if [ "${STAGE_SSH_T}" -lt 24 ] 2>/dev/null; then STAGE_SSH_T=24; fi + + if ! mon_ssh_ok "$SSH_HOST"; then + err "ssh" "cannot reach ${SSH_HOST} (stagepaysan low-priv) — set INSIDE_SSH= or SKIP" + summary + exit 1 + fi + ok "ssh ${SSH_HOST}" "stagepaysan (podman, no sudo)" + + # Inject names/ports into remote (ssh bash -s does not inherit local env). + RAW=$( + { + printf 'BANK_CTR=%q; EX_CTR=%q; MER_CTR=%q\n' "$BANK_CTR" "$EX_CTR" "$MER_CTR" + printf 'BANK_PORT=%q; EX_PORT=%q; MER_PORT=%q\n' "$BANK_PORT" "$EX_PORT" "$MER_PORT" + printf 'DNS_BANK=%q; DNS_EX=%q; DNS_MER=%q\n' "$DNS_BANK" "$DNS_EX" "$DNS_MER" + cat <<'REMOTE' +set +e +emit() { printf 'E|%s|%s|%s|%s\n' "$1" "$2" "$3" "$(printf '%s' "${4:-}" | tr '\n\r' ' ' | head -c 200)"; } +hc() { curl -skS -m 3 -o /tmp/mb -w '%{http_code}' "$1" 2>/dev/null || echo 000; } +hasp() { podman exec "$1" pgrep -f "$2" >/dev/null 2>&1; } + +BANK="${BANK_CTR}" +EX="${EX_CTR}" +MER="${MER_CTR}" +podman ps --format '{{.Names}}' 2>/dev/null | grep -qx "$BANK" || BANK=$(podman ps --format '{{.Names}}' 2>/dev/null | grep -iE 'stage.*bank|lfp-bank' | head -1) +podman ps --format '{{.Names}}' 2>/dev/null | grep -qx "$EX" || EX=$(podman ps --format '{{.Names}}' 2>/dev/null | grep -iE 'stage.*exchange|lfp-exchange' | head -1) +podman ps --format '{{.Names}}' 2>/dev/null | grep -qx "$MER" || MER=$(podman ps --format '{{.Names}}' 2>/dev/null | grep -iE 'stage.*merchant|lfp-merchant' | head -1) + +check_dns() { + local comp="$1" ctr="$2" host="$3" + local line ip code + [ -n "$host" ] || { emit "$comp" WARN "dns" "empty host"; return 1; } + line=$(podman exec "$ctr" getent ahostsv4 "$host" 2>/dev/null | head -1) + [ -z "$line" ] && line=$(podman exec "$ctr" getent hosts "$host" 2>/dev/null | head -1) + ip=$(echo "$line" | awk '{print $1}') + if [ -z "$ip" ]; then + emit "$comp" WARN "dns $host" "no resolve inside container" + return 1 + fi + if [ "$ip" = "127.0.0.1" ] || [ "$ip" = "::1" ]; then + emit "$comp" WARN "dns $host" "$ip (loopback)" + return 1 + fi + # 169.254.x = pasta host-gateway pin is OK for stage + code=$(podman exec "$ctr" curl -skS -m 3 -o /dev/null -w '%{http_code}' "https://${host}/config" 2>/dev/null || echo 000) + if [ "$code" = "200" ]; then + emit "$comp" OK "dns $host" "→ $ip /config=$code" + else + emit "$comp" WARN "dns $host" "→ $ip /config=$code" + fi +} + +if [ -z "$BANK" ]; then emit bank ERROR container "not running" +else + emit bank INFO container "$(podman ps --filter name=^${BANK}$ --format '{{.Names}} {{.Status}}' | head -1)" + emit bank INFO ports "$(podman ps --filter name=^${BANK}$ --format '{{.Ports}}' | head -1)" + hasp "$BANK" 'MainKt serve|libeufin-bank serve' && emit bank OK libeufin "running" || emit bank ERROR libeufin "not running" + podman exec "$BANK" pg_isready -q 2>/dev/null && emit bank OK postgres "ready" || emit bank WARN postgres "pg_isready failed" + c=$(hc "http://127.0.0.1:${BANK_PORT}/config") + [ "$c" = "200" ] && emit bank OK "host :${BANK_PORT}/config" "HTTP $c" || emit bank ERROR "host :${BANK_PORT}/config" "HTTP $c" + c=$(hc "http://127.0.0.1:${BANK_PORT}/taler-integration/config") + [ "$c" = "200" ] && emit bank OK "host integration" "HTTP $c" || emit bank ERROR "host integration" "HTTP $c" + check_dns bank "$BANK" "$DNS_BANK" || true + check_dns bank "$BANK" "$DNS_EX" || true +fi + +if [ -z "$EX" ]; then emit exchange ERROR container "not running" +else + emit exchange INFO container "$(podman ps --filter name=^${EX}$ --format '{{.Names}} {{.Status}}' | head -1)" + c=$(hc "http://127.0.0.1:${EX_PORT}/config") + [ "$c" = "200" ] && emit exchange OK "host :${EX_PORT}/config" "HTTP $c" || emit exchange ERROR "host :${EX_PORT}/config" "HTTP $c" + c=$(curl -sS -m 5 -o /dev/null -w '%{http_code}' "http://127.0.0.1:${EX_PORT}/keys" 2>/dev/null || echo 000) + [ "$c" = "200" ] && emit exchange OK "host :${EX_PORT}/keys" "HTTP $c" || emit exchange ERROR "host :${EX_PORT}/keys" "HTTP $c" + hasp "$EX" 'taler-exchange-httpd' && emit exchange OK httpd "running" || emit exchange ERROR httpd "not running" + hasp "$EX" 'taler-exchange-wirewatch' && emit exchange OK wirewatch "running" || emit exchange ERROR wirewatch "not running" + hasp "$EX" 'taler-exchange-aggregator' && emit exchange OK aggregator "running" || emit exchange WARN aggregator "not running" + hasp "$EX" 'taler-exchange-transfer' && emit exchange OK transfer "running" || emit exchange WARN transfer "not running" + check_dns exchange "$EX" "$DNS_BANK" || true + check_dns exchange "$EX" "$DNS_EX" || true + check_dns exchange "$EX" "$DNS_MER" || true +fi + +if [ -z "$MER" ]; then emit merchant ERROR container "not running" +else + emit merchant INFO container "$(podman ps --filter name=^${MER}$ --format '{{.Names}} {{.Status}}' | head -1)" + c=$(hc "http://127.0.0.1:${MER_PORT}/config") + [ "$c" = "200" ] && emit merchant OK "host :${MER_PORT}/config" "HTTP $c" || emit merchant ERROR "host :${MER_PORT}/config" "HTTP $c" + hasp "$MER" 'taler-merchant-httpd' && emit merchant OK httpd "running" || emit merchant ERROR httpd "not running" + # Stage merchant often has no wirewatch unit — INFO not WARN + hasp "$MER" 'taler-merchant-wirewatch' && emit merchant OK wirewatch "running" || emit merchant INFO wirewatch "not running (optional on stage)" + hasp "$MER" 'taler-merchant-depositcheck' && emit merchant OK depositcheck "running" || emit merchant INFO depositcheck "not running (optional on stage)" + hasp "$MER" 'taler-merchant-exchangekeyupdate' && emit merchant OK exchangekeyupdate "running" || emit merchant INFO exchangekeyupdate "not running" + check_dns merchant "$MER" "$DNS_BANK" || true + check_dns merchant "$MER" "$DNS_EX" || true + check_dns merchant "$MER" "$DNS_MER" || true +fi + +# Caddy is host-wide (often root); stagepaysan can only see the process +if pgrep -x caddy >/dev/null 2>&1; then + emit caddy OK process "running (host)" +else + emit caddy WARN process "not seen as stagepaysan (may still run as root)" +fi + +echo DONE +REMOTE + } | mon_ssh_bash "$SSH_HOST" "${STAGE_SSH_T}" || true + ) + + if [ -z "$RAW" ] || ! echo "$RAW" | grep -q '^E|'; then + err "ssh" "stage remote timed out or empty (cap ${STAGE_SSH_T}s · host=${SSH_HOST})" + summary + exit 1 + fi + + _last_inside_grp="" + while IFS= read -r line; do + case "$line" in + E\|*) + IFS='|' read -r _ comp level key detail <<<"$line" + case "$comp" in + bank|exchange|merchant|caddy) _g="$comp" ;; + *) _g="ssh" ;; + esac + if [ "$_g" != "$_last_inside_grp" ]; then + set_group "$_g" + _last_inside_grp="$_g" + fi + case "$level" in + OK) ok "[$comp] $key${detail:+ ($detail)}" ;; + ERROR) err "$comp" "$key" "$detail" ;; + WARN) warn "[$comp] $key" "$detail" ;; + INFO) info "[$comp] $key" "$detail" ;; + esac + ;; + esac + done <<<"$RAW" + + # Stats from outside (laptop) — public HTTPS, no stagepaysan write needed + set_group stats + section "inside · public stats (outside-in)" + _probe_public_stats() { + local comp="$1" url="$2" + local body code age unix now + body=$(mktemp) + code=$(curl -skS -m 8 -o "$body" -w '%{http_code}' "$url" 2>/dev/null || echo 000) + if [ "$code" != "200" ]; then + warn "[$comp] public stats.json" "HTTP $code · $url" + rm -f "$body" + return + fi + unix=$(python3 -c 'import json,sys; d=json.load(open(sys.argv[1])); print(d.get("generated_at_unix") or 0)' "$body" 2>/dev/null || echo 0) + now=$(date +%s) + if [ "${unix:-0}" -gt 0 ] 2>/dev/null; then + age=$((now - unix)) + if [ "$age" -lt 0 ]; then age=0; fi + if [ "$age" -le "${STATS_STALE_SECS:-900}" ]; then + ok "[$comp] public stats.json" "HTTP 200 age=${age}s · $url" + elif [ "$age" -le "${STATS_FAIL_SECS:-3600}" ]; then + warn "[$comp] public stats.json" "stale age=${age}s · $url" + else + err "$comp" "public stats.json" "too old age=${age}s · $url" + fi + else + ok "[$comp] public stats.json" "HTTP 200 · $url" + fi + rm -f "$body" + } + _probe_public_stats bank "https://${DNS_BANK}/intro/stats.json" + _probe_public_stats exchange "https://${DNS_EX}/intro/stats.json" + _probe_public_stats merchant "https://${DNS_MER}/intro/stats.json" + + # Host load as stagepaysan (no container RSS from koopa metrics) + set_group load + section "inside · load (stagepaysan host)" + LOAD_LINE=$(mon_ssh_bash "$SSH_HOST" 8 <<'EOF' || true +python3 - <<'PY' +import os +la=os.getloadavg() +print("loadavg=%.2f,%.2f,%.2f" % la) +try: + with open("/proc/meminfo") as f: + d={} + for line in f: + k,v=line.split(":")[0], line.split(":")[1].strip().split()[0] + d[k]=int(v) + total=d.get("MemTotal",0)/1024/1024 + avail=d.get("MemAvailable",0)/1024/1024 + used=total-avail + print("mem_used=%.2fGiB avail=%.2fGiB total=%.2fGiB" % (used, avail, total)) +except Exception: + print("mem=?") +PY +EOF +) + if [ -n "$LOAD_LINE" ]; then + info "stage host" "$(echo "$LOAD_LINE" | tr '\n' ' ')" + else + info "stage host" "load probe empty" + fi + + summary + exit 0 +fi + +# --------------------------------------------------------------------------- +# koopa (default) — existing remote collect +# --------------------------------------------------------------------------- section "inside · collect from koopa" -if [ "${SKIP_SSH}" = "1" ]; then - warn "ssh" "SKIP_SSH=1 — skipped" +if [ "${SKIP_SSH}" = "1" ] && [ "${LOCAL_STACK:-0}" != "1" ]; then + warn "ssh" "SKIP_SSH=1 and not local — skipped" summary exit 0 fi diff --git a/scripts/taler-monitoring/check_versions.sh b/scripts/taler-monitoring/check_versions.sh index ea4df2f..3580732 100755 --- a/scripts/taler-monitoring/check_versions.sh +++ b/scripts/taler-monitoring/check_versions.sh @@ -166,28 +166,56 @@ fi # --------------------------------------------------------------------------- # 2) INSIDE — containers can reach deb.taler.net (pasta / install path) +# koopa: KOOPA_SSH · stage-lfp: INSIDE_SSH (stagepaysan, low-priv) # --------------------------------------------------------------------------- set_group inside section "versions · inside · containers → deb.taler.net" -if [ "${SKIP_SSH}" = "1" ]; then - warn "ssh" "SKIP_SSH=1 — skip container reachability + installed package compare" +_VERS_SSH_HOST="" +_VERS_BANK="taler-hacktivism-bank" +_VERS_EX="taler-hacktivism-exchange-ansible" +_VERS_MER="taler-hacktivism" +if [ "${INSIDE_PROFILE:-}" = "stage-lfp" ] \ + || [ "${EXPECT_CURRENCY:-}" = "TESTPAYSAN" ] \ + || [ -n "${INSIDE_SSH:-}" ]; then + _VERS_SSH_HOST="${INSIDE_SSH:-francpaysan-stage-user}" + _VERS_BANK="${INSIDE_BANK_CTR:-stage-lfp-bank}" + _VERS_EX="${INSIDE_EXCHANGE_CTR:-stage-lfp-exchange-ansible}" + _VERS_MER="${INSIDE_MERCHANT_CTR:-stage-lfp-merchant}" +elif [ "${LOCAL_STACK:-0}" = "1" ] && [ "${SKIP_SSH:-0}" != "1" ]; then + _VERS_SSH_HOST="${KOOPA_SSH}" +fi + +if [ -z "$_VERS_SSH_HOST" ]; then + info "ssh" "no container SSH for this domain — outside-only package index" info "outside-only" "deb.taler.net public checks completed above" summary exit 0 fi -if ! koopa_ssh_ok; then - err "ssh" "cannot reach ${KOOPA_SSH} — set SKIP_SSH=1 to skip container checks" - info "outside-only" "deb.taler.net public checks completed above" - summary - exit 1 + +if [ "$_VERS_SSH_HOST" = "${KOOPA_SSH:-}" ] || [ "${INSIDE_PROFILE:-}" = "koopa" ]; then + if ! koopa_ssh_ok; then + err "ssh" "cannot reach ${KOOPA_SSH} — set SKIP_SSH=1 to skip container checks" + info "outside-only" "deb.taler.net public checks completed above" + summary + exit 1 + fi + ok "ssh ${KOOPA_SSH}" +else + if ! mon_ssh_ok "$_VERS_SSH_HOST"; then + err "ssh" "cannot reach ${_VERS_SSH_HOST} (stagepaysan) — skip container package compare" + info "outside-only" "deb.taler.net public checks completed above" + summary + exit 1 + fi + ok "ssh ${_VERS_SSH_HOST}" "stagepaysan container package compare" fi -ok "ssh ${KOOPA_SSH}" INRELEASE_URL="${APT_BASE}/dists/${SUITE}/InRelease" # Write remote script to a file (avoids bash 3.2 parse bugs with case/;; inside $(…)</dev/null | grep -i exchange | head -1 ;; - *bank*) podman ps --format '{{.Names}}' 2>/dev/null | grep -iE 'hacktivism-bank|taler-bank' | head -1 ;; + *bank*) podman ps --format '{{.Names}}' 2>/dev/null | grep -iE 'stage.*bank|hacktivism-bank|taler-bank|lfp-bank' | head -1 ;; *) - c=$(podman ps --format '{{.Names}}' 2>/dev/null | grep -E '^taler-hacktivism$' | head -1) + c=$(podman ps --format '{{.Names}}' 2>/dev/null | grep -E '^taler-hacktivism$|^stage-lfp-merchant$' | head -1) if [ -n "$c" ]; then echo "$c"; return; fi podman ps --format '{{.Names}}' 2>/dev/null | grep -iE 'merchant|hacktivism' | grep -viE 'bank|exchange' | head -1 ;; esac } -for want in taler-hacktivism-exchange-ansible taler-hacktivism-bank taler-hacktivism; do +for want in "$WANT_EX" "$WANT_BANK" "$WANT_MER"; do c=$(resolve_ctr "$want") [ -n "$c" ] || continue role=other @@ -229,7 +257,11 @@ done REMOTE } >"$tmp/remote-versions.sh" -REMOTE_OUT=$(koopa_ssh_bash 60 <"$tmp/remote-versions.sh" || true) +if [ "$_VERS_SSH_HOST" = "${KOOPA_SSH:-}" ] || [ "${INSIDE_PROFILE:-}" = "koopa" ]; then + REMOTE_OUT=$(koopa_ssh_bash 60 <"$tmp/remote-versions.sh" || true) +else + REMOTE_OUT=$(mon_ssh_bash "$_VERS_SSH_HOST" 60 <"$tmp/remote-versions.sh" || true) +fi printf '%s\n' "$REMOTE_OUT" | grep -E '^R\|' >"$tmp/reach.tsv" || true printf '%s\n' "$REMOTE_OUT" | grep -E '^S\|' >"$tmp/sources.tsv" || true diff --git a/scripts/taler-monitoring/lib.sh b/scripts/taler-monitoring/lib.sh index 3f060a2..7bde111 100755 --- a/scripts/taler-monitoring/lib.sh +++ b/scripts/taler-monitoring/lib.sh @@ -211,6 +211,19 @@ apply_taler_domain() { : "${E2E_TEMPLATE_MAP:=5=oeufs-6 4.5=pain-seigle 6=jus-pomme 8.5=fromage-chevre 12=miel-printemps 25=panier-legumes}" : "${E2E_PAY_VALUES:=5 8.5 6}" : "${E2E_WITHDRAW_VALUES:=20 50}" + # Inside checks as low-priv stagepaysan (podman, no sudo) — not koopa + INSIDE_PROFILE="${INSIDE_PROFILE:-stage-lfp}" + INSIDE_SSH="${INSIDE_SSH:-francpaysan-stage-user}" + INSIDE_BANK_CTR="${INSIDE_BANK_CTR:-stage-lfp-bank}" + INSIDE_EXCHANGE_CTR="${INSIDE_EXCHANGE_CTR:-stage-lfp-exchange-ansible}" + INSIDE_MERCHANT_CTR="${INSIDE_MERCHANT_CTR:-stage-lfp-merchant}" + # Host pasta ports (127.0.0.1 on francpaysan-host) + INSIDE_BANK_PORT="${INSIDE_BANK_PORT:-9032}" + INSIDE_EXCHANGE_PORT="${INSIDE_EXCHANGE_PORT:-9031}" + INSIDE_MERCHANT_PORT="${INSIDE_MERCHANT_PORT:-9030}" + INSIDE_DNS_BANK="${INSIDE_DNS_BANK:-stage.bank.lefrancpaysan.ch}" + INSIDE_DNS_EXCHANGE="${INSIDE_DNS_EXCHANGE:-stage.exchange.lefrancpaysan.ch}" + INSIDE_DNS_MERCHANT="${INSIDE_DNS_MERCHANT:-stage.monnaie.lefrancpaysan.ch}" ;; CHF) WITHDRAW_AMT="${WITHDRAW_AMT:-CHF:20}" @@ -246,10 +259,14 @@ apply_taler_domain() { EXCHANGE_PUBLIC=${EXCHANGE_PUBLIC%/} MERCHANT_PUBLIC=${MERCHANT_PUBLIC%/} - # Hard rule: only the local koopa/hacktivism stack may use SSH + # Koopa SSH only for LOCAL_STACK=1. Stage LFP uses INSIDE_SSH (stagepaysan) separately. if [ "${LOCAL_STACK}" != "1" ]; then SKIP_SSH=1 fi + # Default inside profile for local GOA + if [ "${LOCAL_STACK}" = "1" ]; then + INSIDE_PROFILE="${INSIDE_PROFILE:-koopa}" + fi } # Apply TALER_DOMAIN from env once (CLI exports TALER_DOMAIN_APPLIED=1 after overrides). @@ -310,6 +327,20 @@ with_timeout() { ' "$secs" "$@" } +# Generic short SSH to a named host (low-priv stagepaysan or koopa). +# usage: mon_ssh_bash HOST [timeout] <<'EOF' ... EOF +mon_ssh_bash() { + local host="$1" t="${2:-$SSH_CMD_TIMEOUT}" + [ -n "$host" ] || return 1 + with_timeout "$t" ssh "${SSH_BASE_OPTS[@]}" "$host" bash -s +} +mon_ssh_ok() { + local host="$1" + [ -n "$host" ] || return 1 + with_timeout $((SSH_CONNECT_TIMEOUT + 5)) \ + ssh "${SSH_BASE_OPTS[@]}" "$host" true >/dev/null 2>&1 +} + # Pick a working SSH host: KOOPA_SSH first, then KOOPA_SSH_FALLBACKS (koopa-external). # Sets KOOPA_SSH to the first host that answers. 0 = ok, 1 = none. KOOPA_SSH_RESOLVED=0 diff --git a/scripts/taler-monitoring/metrics.sh b/scripts/taler-monitoring/metrics.sh index 5df8d76..4c440ea 100644 --- a/scripts/taler-monitoring/metrics.sh +++ b/scripts/taler-monitoring/metrics.sh @@ -489,9 +489,9 @@ print(rep["summary"]) PY } -# --- Taler stack load on koopa (host + bank/exchange/merchant) --- +# --- Taler stack load (host + bank/exchange/merchant) --- # Writes JSON to $1. RAM, process counts, DB sizes, disk I/O counters. -# Uses KOOPA_SSH with fallback to KOOPA_SSH_FALLBACKS (koopa-external). +# koopa: KOOPA_SSH (+ fallbacks). stage-lfp: INSIDE_SSH (stagepaysan) + stage-lfp-* names. metrics_taler_load() { local out="${1:-$METRICS_DIR/load.json}" local label="${2:-snap}" @@ -502,7 +502,20 @@ metrics_taler_load() { fi local raw="" local remote_py - remote_py=$(cat <<'PY' + local _m_bank _m_mer _m_ex _m_ssh + _m_bank="taler-hacktivism-bank" + _m_mer="taler-hacktivism" + _m_ex="taler-hacktivism-exchange-ansible" + _m_ssh="" + if [ "${INSIDE_PROFILE:-}" = "stage-lfp" ] \ + || [ "${EXPECT_CURRENCY:-}" = "TESTPAYSAN" ] \ + || { [ -n "${INSIDE_SSH:-}" ] && [ "${LOCAL_STACK:-0}" != "1" ]; }; then + _m_bank="${INSIDE_BANK_CTR:-stage-lfp-bank}" + _m_mer="${INSIDE_MERCHANT_CTR:-stage-lfp-merchant}" + _m_ex="${INSIDE_EXCHANGE_CTR:-stage-lfp-exchange-ansible}" + _m_ssh="${INSIDE_SSH:-francpaysan-stage-user}" + fi + remote_py=$(cat </dev/null 2>&1 && koopa_ssh_ok; then + # Prefer stagepaysan (stage-lfp), else koopa SSH, else local podman only on LOCAL_STACK. + if [ -n "${_m_ssh}" ] && type mon_ssh_ok >/dev/null 2>&1 && mon_ssh_ok "${_m_ssh}"; then + raw=$(printf '%s' "$remote_py" | with_timeout "${METRICS_LOAD_SSH_TIMEOUT:-90}" \ + ssh "${SSH_BASE_OPTS[@]}" "${_m_ssh}" python3 - 2>/dev/null || true) + elif [ "${SKIP_SSH:-0}" != "1" ] && type koopa_ssh_ok >/dev/null 2>&1 && koopa_ssh_ok; then if type koopa_ssh_python >/dev/null 2>&1; then raw=$(printf '%s' "$remote_py" | koopa_ssh_python "${METRICS_LOAD_SSH_TIMEOUT:-90}" 2>/dev/null || true) else raw=$(printf '%s' "$remote_py" | with_timeout "${METRICS_LOAD_SSH_TIMEOUT:-90}" \ ssh "${SSH_BASE_OPTS[@]}" "${KOOPA_SSH}" python3 - 2>/dev/null || true) fi - elif command -v podman >/dev/null 2>&1; then - raw=$(python3 -c "$remote_py" 2>/dev/null || true) + elif [ "${LOCAL_STACK:-0}" = "1" ] && command -v podman >/dev/null 2>&1; then + raw=$(printf '%s' "$remote_py" | python3 - 2>/dev/null || true) + else + printf '%s\n' "{\"ok\":false,\"reason\":\"no-ssh-for-load\",\"label\":\"$label\"}" >"$out" + return 0 fi if [ -z "$raw" ]; then - printf '%s\n' "{\"ok\":false,\"reason\":\"probe-failed\",\"label\":\"$label\",\"ssh\":\"${KOOPA_SSH:-?}\"}" >"$out" + printf '%s\n' "{\"ok\":false,\"reason\":\"probe-failed\",\"label\":\"$label\",\"ssh\":\"${_m_ssh:-${KOOPA_SSH:-?}}\"}" >"$out" return 1 fi printf '%s\n' "$raw" | python3 -c ' diff --git a/scripts/taler-monitoring/secrets.env.example b/scripts/taler-monitoring/secrets.env.example index f7e13de..064b14d 100644 --- a/scripts/taler-monitoring/secrets.env.example +++ b/scripts/taler-monitoring/secrets.env.example @@ -27,7 +27,8 @@ # MERCHANT_INSTANCE_PASSWORD= # --- FrancPaysan STAGE (TESTPAYSAN) e2e --- -# Default phases for -d stage.lefrancpaysan.ch: urls + e2e +# Default phases for -d stage.lefrancpaysan.ch: urls + inside + versions + e2e +# inside uses francpaysan-stage-user (stagepaysan); stats from public HTTPS. # Admin credit (required for ATM withdraw ladder on stage): # E2E_BANK_ADMIN_PASS=… # or auto-load via SSH francpaysan-host # FRANCPAYSAN_SSH=francpaysan-host diff --git a/scripts/taler-monitoring/taler-monitoring.sh b/scripts/taler-monitoring/taler-monitoring.sh index 05e6f5d..d8f6197 100755 --- a/scripts/taler-monitoring/taler-monitoring.sh +++ b/scripts/taler-monitoring/taler-monitoring.sh @@ -154,6 +154,10 @@ export WITHDRAW_AMT PAY_AMT CREDIT_AMT MERCHANT_INSTANCE export E2E_FAKE_INCOMING E2E_REMOTE E2E_VARIABLE E2E_ATM_MAX export E2E_WITHDRAW_VALUES E2E_PAY_VALUES E2E_USE_TEMPLATES E2E_TEMPLATE_MAP export PAIVANA_PUBLIC E2E_PAIVANA E2E_PAIVANA_TEMPLATE E2E_PAIVANA_AMOUNT E2E_PAIVANA_INSTANCE +export INSIDE_PROFILE INSIDE_SSH +export INSIDE_BANK_CTR INSIDE_EXCHANGE_CTR INSIDE_MERCHANT_CTR +export INSIDE_BANK_PORT INSIDE_EXCHANGE_PORT INSIDE_MERCHANT_PORT +export INSIDE_DNS_BANK INSIDE_DNS_EXCHANGE INSIDE_DNS_MERCHANT # Ladder: withdraw then pay — 0 + random mids + max-1 + max (see check_goa_ladder.sh). # Defaults so set -u export is safe when vars were never set by caller. : "${LADDER_STEPS:=23}" @@ -181,17 +185,17 @@ export LADDER_PAY LADDER_WITHDRAW_SCALE LADDER_PAY_SETTLE_ROUNDS export TALER_DOMAIN_APPLIED=1 # Default phases -# - local GOA (koopa): urls + SSH phases + e2e -# - FrancPaysan STAGE (TESTPAYSAN / stage.*lefrancpaysan*): urls + e2e -# (public withdraw + template pay — no SSH) -# - other remote: urls only (pass "e2e" explicitly) +# - local GOA (koopa): urls + inside + versions + e2e +# - FrancPaysan STAGE: urls + inside (stagepaysan) + versions + e2e +# - other remote: urls only (pass e2e/inside explicitly) if [ "${#PHASES[@]}" -eq 0 ]; then if [ "${LOCAL_STACK}" = "1" ]; then PHASES=(urls inside versions e2e) elif [ "${EXPECT_CURRENCY:-}" = "TESTPAYSAN" ] \ || [[ "${TALER_DOMAIN:-}" == stage.*lefrancpaysan* ]] \ || [[ "${TALER_DOMAIN:-}" == *stage.lefrancpaysan* ]]; then - PHASES=(urls e2e) + # Like hacktivism breadth: public urls + inside (stagepaysan) + versions + e2e + PHASES=(urls inside versions e2e) else PHASES=(urls) fi @@ -202,8 +206,10 @@ for p in "${PHASES[@]}"; do if [ "$p" = "all" ]; then if [ "${LOCAL_STACK}" = "1" ]; then OUT_PHASES+=(urls inside versions sanity e2e) + elif [ -n "${INSIDE_SSH:-}" ] || [ "${INSIDE_PROFILE:-}" = "stage-lfp" ]; then + OUT_PHASES+=(urls inside versions e2e) else - # remote: repo index check only (no installed pkgs without SSH) + # remote without inside SSH: public + optional e2e OUT_PHASES+=(urls versions e2e) fi else @@ -213,15 +219,21 @@ done PHASES=() seen=" " for p in "${OUT_PHASES[@]}"; do - # Drop SSH-only phases for non-koopa (e2e is allowed — public path only) - # versions still runs: outside-in deb.taler.net checks; container parts soft-skip if no SSH - if [ "${LOCAL_STACK}" != "1" ] || [ "${SKIP_SSH}" = "1" ]; then - case "$p" in inside|server) - echo "[INFO] skip phase '$p' (only koopa uses SSH)" >&2 - continue + # server = koopa-only. inside allowed on koopa OR stagepaysan (INSIDE_SSH). + case "$p" in + server) + if [ "${LOCAL_STACK}" != "1" ]; then + echo "[INFO] skip phase 'server' (koopa only)" >&2 + continue + fi ;; - esac - fi + inside) + if [ "${LOCAL_STACK}" != "1" ] && [ -z "${INSIDE_SSH:-}" ] && [ "${INSIDE_PROFILE:-}" != "stage-lfp" ]; then + echo "[INFO] skip phase 'inside' (no INSIDE_SSH / not local)" >&2 + continue + fi + ;; + esac case "$seen" in *" $p "*) ;; *) PHASES+=("$p"); seen="$seen$p " ;; esac done