monitoring: stage.lefrancpaysan like hacktivism (inside+versions via stagepaysan)

Expand default phases for TESTPAYSAN to urls + inside + versions + e2e.
Inside/version/load use low-priv francpaysan-stage-user (stagepaysan)
and stage-lfp-* containers on 9030–9032; public stats.json stays outside-in.
This commit is contained in:
Hernâni Marques 2026-07-17 19:17:40 +02:00
parent 19bbcc09da
commit 08c55e71a7
No known key found for this signature in database
7 changed files with 398 additions and 48 deletions

View file

@ -81,12 +81,18 @@ Each check prints **global** and **grouped** ids:
./taler-monitoring.sh sanity ./taler-monitoring.sh sanity
./taler-monitoring.sh e2e ./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 ./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/) # (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 -d taler.net
./taler-monitoring.sh --domain taler-ops.ch ./taler-monitoring.sh --domain taler-ops.ch
./taler-monitoring.sh -d my.taler-ops.ch urls ./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 | | `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 | | `test.taler.net` | bank.test… | exchange.test… | backend.test… | **TESTKUDOS** · no landings |
| `lefrancpaysan.ch` | bank.… | exchange.… | **monnaie.…** (merchant) | report-only · 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** | | `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** | | `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 | | 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. Other domains: never SSH. Optional **e2e** aborts cleanly on login/KYC.

View file

@ -1,5 +1,9 @@
#!/usr/bin/env bash #!/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 90309032; stats remain outside-in (urls).
set -euo pipefail set -euo pipefail
ROOT=$(cd "$(dirname "$0")" && pwd) ROOT=$(cd "$(dirname "$0")" && pwd)
# shellcheck source=lib.sh # shellcheck source=lib.sh
@ -7,14 +11,250 @@ source "$ROOT/lib.sh"
# shellcheck source=metrics.sh # shellcheck source=metrics.sh
source "$ROOT/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_area inside
set_group ssh 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" section "inside · collect from koopa"
if [ "${SKIP_SSH}" = "1" ]; then if [ "${SKIP_SSH}" = "1" ] && [ "${LOCAL_STACK:-0}" != "1" ]; then
warn "ssh" "SKIP_SSH=1 — skipped" warn "ssh" "SKIP_SSH=1 and not local — skipped"
summary summary
exit 0 exit 0
fi fi

View file

@ -166,28 +166,56 @@ fi
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# 2) INSIDE — containers can reach deb.taler.net (pasta / install path) # 2) INSIDE — containers can reach deb.taler.net (pasta / install path)
# koopa: KOOPA_SSH · stage-lfp: INSIDE_SSH (stagepaysan, low-priv)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
set_group inside set_group inside
section "versions · inside · containers → deb.taler.net" section "versions · inside · containers → deb.taler.net"
if [ "${SKIP_SSH}" = "1" ]; then _VERS_SSH_HOST=""
warn "ssh" "SKIP_SSH=1 — skip container reachability + installed package compare" _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" info "outside-only" "deb.taler.net public checks completed above"
summary summary
exit 0 exit 0
fi fi
if ! koopa_ssh_ok; then
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" err "ssh" "cannot reach ${KOOPA_SSH} — set SKIP_SSH=1 to skip container checks"
info "outside-only" "deb.taler.net public checks completed above" info "outside-only" "deb.taler.net public checks completed above"
summary summary
exit 1 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 fi
ok "ssh ${KOOPA_SSH}"
INRELEASE_URL="${APT_BASE}/dists/${SUITE}/InRelease" INRELEASE_URL="${APT_BASE}/dists/${SUITE}/InRelease"
# Write remote script to a file (avoids bash 3.2 parse bugs with case/;; inside $(…)<<heredoc). # Write remote script to a file (avoids bash 3.2 parse bugs with case/;; inside $(…)<<heredoc).
{ {
printf 'INRELEASE_URL=%q\n' "$INRELEASE_URL" printf 'INRELEASE_URL=%q\n' "$INRELEASE_URL"
printf 'WANT_BANK=%q; WANT_EX=%q; WANT_MER=%q\n' "$_VERS_BANK" "$_VERS_EX" "$_VERS_MER"
cat <<'REMOTE' cat <<'REMOTE'
set +e set +e
resolve_ctr() { resolve_ctr() {
@ -196,15 +224,15 @@ resolve_ctr() {
if [ -n "$c" ]; then echo "$c"; return; fi if [ -n "$c" ]; then echo "$c"; return; fi
case "$want" in case "$want" in
*exchange*) podman ps --format '{{.Names}}' 2>/dev/null | grep -i exchange | head -1 ;; *exchange*) podman ps --format '{{.Names}}' 2>/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 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 podman ps --format '{{.Names}}' 2>/dev/null | grep -iE 'merchant|hacktivism' | grep -viE 'bank|exchange' | head -1
;; ;;
esac 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") c=$(resolve_ctr "$want")
[ -n "$c" ] || continue [ -n "$c" ] || continue
role=other role=other
@ -229,7 +257,11 @@ done
REMOTE REMOTE
} >"$tmp/remote-versions.sh" } >"$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 '^R\|' >"$tmp/reach.tsv" || true
printf '%s\n' "$REMOTE_OUT" | grep -E '^S\|' >"$tmp/sources.tsv" || true printf '%s\n' "$REMOTE_OUT" | grep -E '^S\|' >"$tmp/sources.tsv" || true

View file

@ -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_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_PAY_VALUES:=5 8.5 6}"
: "${E2E_WITHDRAW_VALUES:=20 50}" : "${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) CHF)
WITHDRAW_AMT="${WITHDRAW_AMT:-CHF:20}" WITHDRAW_AMT="${WITHDRAW_AMT:-CHF:20}"
@ -246,10 +259,14 @@ apply_taler_domain() {
EXCHANGE_PUBLIC=${EXCHANGE_PUBLIC%/} EXCHANGE_PUBLIC=${EXCHANGE_PUBLIC%/}
MERCHANT_PUBLIC=${MERCHANT_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 if [ "${LOCAL_STACK}" != "1" ]; then
SKIP_SSH=1 SKIP_SSH=1
fi 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). # Apply TALER_DOMAIN from env once (CLI exports TALER_DOMAIN_APPLIED=1 after overrides).
@ -310,6 +327,20 @@ with_timeout() {
' "$secs" "$@" ' "$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). # 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. # Sets KOOPA_SSH to the first host that answers. 0 = ok, 1 = none.
KOOPA_SSH_RESOLVED=0 KOOPA_SSH_RESOLVED=0

View file

@ -489,9 +489,9 @@ print(rep["summary"])
PY 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. # 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() { metrics_taler_load() {
local out="${1:-$METRICS_DIR/load.json}" local out="${1:-$METRICS_DIR/load.json}"
local label="${2:-snap}" local label="${2:-snap}"
@ -502,7 +502,20 @@ metrics_taler_load() {
fi fi
local raw="" local raw=""
local remote_py 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 <<PY
import base64, json, os, re, subprocess, time import base64, json, os, re, subprocess, time
from collections import defaultdict from collections import defaultdict
@ -557,8 +570,12 @@ def disk_io():
return {"sectors_read": r, "sectors_written": w, return {"sectors_read": r, "sectors_written": w,
"approx_write_bytes": w*512, "approx_read_bytes": r*512} "approx_write_bytes": w*512, "approx_read_bytes": r*512}
CTRS = [("bank","taler-hacktivism-bank"),("merchant","taler-hacktivism"), CTRS = [("bank","${_m_bank}"),("merchant","${_m_mer}"),
("exchange","taler-hacktivism-exchange-ansible")] ("exchange","${_m_ex}")]
PY
)
# Append the rest of the remote probe (quoted so shell does not expand)
remote_py+=$(cat <<'PY'
def running(name): def running(name):
return sh(f"podman inspect -f '{{{{.State.Running}}}}' {name}").strip() == "true" return sh(f"podman inspect -f '{{{{.State.Running}}}}' {name}").strip() == "true"
@ -715,19 +732,25 @@ print(json.dumps({
})) }))
PY PY
) )
# Prefer SSH to koopa (LAN or koopa-external). Local podman only if we are on the host. # Prefer stagepaysan (stage-lfp), else koopa SSH, else local podman only on LOCAL_STACK.
if [ "${SKIP_SSH:-0}" != "1" ] && type koopa_ssh_ok >/dev/null 2>&1 && koopa_ssh_ok; then 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 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) raw=$(printf '%s' "$remote_py" | koopa_ssh_python "${METRICS_LOAD_SSH_TIMEOUT:-90}" 2>/dev/null || true)
else else
raw=$(printf '%s' "$remote_py" | with_timeout "${METRICS_LOAD_SSH_TIMEOUT:-90}" \ raw=$(printf '%s' "$remote_py" | with_timeout "${METRICS_LOAD_SSH_TIMEOUT:-90}" \
ssh "${SSH_BASE_OPTS[@]}" "${KOOPA_SSH}" python3 - 2>/dev/null || true) ssh "${SSH_BASE_OPTS[@]}" "${KOOPA_SSH}" python3 - 2>/dev/null || true)
fi fi
elif command -v podman >/dev/null 2>&1; then elif [ "${LOCAL_STACK:-0}" = "1" ] && command -v podman >/dev/null 2>&1; then
raw=$(python3 -c "$remote_py" 2>/dev/null || true) 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 fi
if [ -z "$raw" ]; then 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 return 1
fi fi
printf '%s\n' "$raw" | python3 -c ' printf '%s\n' "$raw" | python3 -c '

View file

@ -27,7 +27,8 @@
# MERCHANT_INSTANCE_PASSWORD= # MERCHANT_INSTANCE_PASSWORD=
# --- FrancPaysan STAGE (TESTPAYSAN) e2e --- # --- 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): # Admin credit (required for ATM withdraw ladder on stage):
# E2E_BANK_ADMIN_PASS=… # or auto-load via SSH francpaysan-host # E2E_BANK_ADMIN_PASS=… # or auto-load via SSH francpaysan-host
# FRANCPAYSAN_SSH=francpaysan-host # FRANCPAYSAN_SSH=francpaysan-host

View file

@ -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_FAKE_INCOMING E2E_REMOTE E2E_VARIABLE E2E_ATM_MAX
export E2E_WITHDRAW_VALUES E2E_PAY_VALUES E2E_USE_TEMPLATES E2E_TEMPLATE_MAP 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 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). # 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. # Defaults so set -u export is safe when vars were never set by caller.
: "${LADDER_STEPS:=23}" : "${LADDER_STEPS:=23}"
@ -181,17 +185,17 @@ export LADDER_PAY LADDER_WITHDRAW_SCALE LADDER_PAY_SETTLE_ROUNDS
export TALER_DOMAIN_APPLIED=1 export TALER_DOMAIN_APPLIED=1
# Default phases # Default phases
# - local GOA (koopa): urls + SSH phases + e2e # - local GOA (koopa): urls + inside + versions + e2e
# - FrancPaysan STAGE (TESTPAYSAN / stage.*lefrancpaysan*): urls + e2e # - FrancPaysan STAGE: urls + inside (stagepaysan) + versions + e2e
# (public withdraw + template pay — no SSH) # - other remote: urls only (pass e2e/inside explicitly)
# - other remote: urls only (pass "e2e" explicitly)
if [ "${#PHASES[@]}" -eq 0 ]; then if [ "${#PHASES[@]}" -eq 0 ]; then
if [ "${LOCAL_STACK}" = "1" ]; then if [ "${LOCAL_STACK}" = "1" ]; then
PHASES=(urls inside versions e2e) PHASES=(urls inside versions e2e)
elif [ "${EXPECT_CURRENCY:-}" = "TESTPAYSAN" ] \ elif [ "${EXPECT_CURRENCY:-}" = "TESTPAYSAN" ] \
|| [[ "${TALER_DOMAIN:-}" == stage.*lefrancpaysan* ]] \ || [[ "${TALER_DOMAIN:-}" == stage.*lefrancpaysan* ]] \
|| [[ "${TALER_DOMAIN:-}" == *stage.lefrancpaysan* ]]; then || [[ "${TALER_DOMAIN:-}" == *stage.lefrancpaysan* ]]; then
PHASES=(urls e2e) # Like hacktivism breadth: public urls + inside (stagepaysan) + versions + e2e
PHASES=(urls inside versions e2e)
else else
PHASES=(urls) PHASES=(urls)
fi fi
@ -202,8 +206,10 @@ for p in "${PHASES[@]}"; do
if [ "$p" = "all" ]; then if [ "$p" = "all" ]; then
if [ "${LOCAL_STACK}" = "1" ]; then if [ "${LOCAL_STACK}" = "1" ]; then
OUT_PHASES+=(urls inside versions sanity e2e) OUT_PHASES+=(urls inside versions sanity e2e)
elif [ -n "${INSIDE_SSH:-}" ] || [ "${INSIDE_PROFILE:-}" = "stage-lfp" ]; then
OUT_PHASES+=(urls inside versions e2e)
else else
# remote: repo index check only (no installed pkgs without SSH) # remote without inside SSH: public + optional e2e
OUT_PHASES+=(urls versions e2e) OUT_PHASES+=(urls versions e2e)
fi fi
else else
@ -213,15 +219,21 @@ done
PHASES=() PHASES=()
seen=" " seen=" "
for p in "${OUT_PHASES[@]}"; do for p in "${OUT_PHASES[@]}"; do
# Drop SSH-only phases for non-koopa (e2e is allowed — public path only) # server = koopa-only. inside allowed on koopa OR stagepaysan (INSIDE_SSH).
# versions still runs: outside-in deb.taler.net checks; container parts soft-skip if no SSH case "$p" in
if [ "${LOCAL_STACK}" != "1" ] || [ "${SKIP_SSH}" = "1" ]; then server)
case "$p" in inside|server) if [ "${LOCAL_STACK}" != "1" ]; then
echo "[INFO] skip phase '$p' (only koopa uses SSH)" >&2 echo "[INFO] skip phase 'server' (koopa only)" >&2
continue continue
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 esac
fi
case "$seen" in *" $p "*) ;; *) PHASES+=("$p"); seen="$seen$p " ;; esac case "$seen" in *" $p "*) ;; *) PHASES+=("$p"); seen="$seen$p " ;; esac
done done