From d38999d5ad678a1dc1e13ec83d2ca051688d42ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hern=C3=A2ni=20Marques?= Date: Fri, 17 Jul 2026 09:30:04 +0200 Subject: [PATCH 1/8] monitoring: domain profiles + coloured tags --- scripts/taler-monitoring/domains.conf | 32 +++ scripts/taler-monitoring/lib.sh | 244 ++++++++++++------- scripts/taler-monitoring/taler-monitoring.sh | 29 ++- 3 files changed, 210 insertions(+), 95 deletions(-) create mode 100644 scripts/taler-monitoring/domains.conf diff --git a/scripts/taler-monitoring/domains.conf b/scripts/taler-monitoring/domains.conf new file mode 100644 index 0000000..fccc411 --- /dev/null +++ b/scripts/taler-monitoring/domains.conf @@ -0,0 +1,32 @@ +# taler-monitoring domain profiles +# +# When you add a stack, declare the three public endpoints here: +# name bank exchange merchant-backend currency local landing [canonical] +# +# - name: what -d / TALER_DOMAIN matches (aliases allowed as extra lines) +# - bank / exchange / merchant: hostname or full https:// URL +# - currency: GOA | KUDOS | CHF | … (empty = report only) +# - local: 1 = koopa SSH/inside/e2e stack; 0 = public-only +# - landing: 1 = check /intro landings + assets; 0 = skip (no GOA-style landings) +# - canonical: optional TALER_DOMAIN label after alias match +# +# CLI overrides after load: --bank --exchange --merchant --currency +# Alternate file: TALER_DOMAINS_CONF=/path/to/domains.conf + +# Local GOA (koopa) — landings matter +koopa bank.hacktivism.ch exchange.hacktivism.ch taler.hacktivism.ch GOA 1 1 hacktivism.ch +hacktivism bank.hacktivism.ch exchange.hacktivism.ch taler.hacktivism.ch GOA 1 1 hacktivism.ch +hacktivism.ch bank.hacktivism.ch exchange.hacktivism.ch taler.hacktivism.ch GOA 1 1 + +# Public demo (KUDOS) — has intro-style pages; keep soft landing checks +taler.net bank.demo.taler.net exchange.demo.taler.net backend.demo.taler.net KUDOS 0 1 demo.taler.net +demo.taler.net bank.demo.taler.net exchange.demo.taler.net backend.demo.taler.net KUDOS 0 1 + +# Public test stack (TESTKUDOS) — no GOA-style landings +test.taler.net bank.test.taler.net exchange.test.taler.net backend.test.taler.net TESTKUDOS 0 0 + +# TOPS CHF — multi-tenant merchant my.taler-ops.ch; no public GOA landings +taler-ops.ch bank.taler-ops.ch exchange.taler-ops.ch my.taler-ops.ch CHF 0 0 +my.taler-ops.ch bank.taler-ops.ch exchange.taler-ops.ch my.taler-ops.ch CHF 0 0 taler-ops.ch +stage.taler-ops.ch bank.stage.taler-ops.ch exchange.stage.taler-ops.ch my.stage.taler-ops.ch CHF 0 0 +my.stage.taler-ops.ch bank.stage.taler-ops.ch exchange.stage.taler-ops.ch my.stage.taler-ops.ch CHF 0 0 stage.taler-ops.ch diff --git a/scripts/taler-monitoring/lib.sh b/scripts/taler-monitoring/lib.sh index d405a2d..451d56d 100755 --- a/scripts/taler-monitoring/lib.sh +++ b/scripts/taler-monitoring/lib.sh @@ -14,10 +14,13 @@ : "${MERCHANT_INSTANCE:=goa-demo-cp4zqk}" : "${WITHDRAW_AMT:=GOA:20}" # single-shot fallback; e2e ladder uses ATM notes : "${PAY_AMT:=GOA:0.01}" -: "${CREDIT_AMT:=GOA:400}" # covers ATM ladder 20+50+100+200 +: "${CREDIT_AMT:=GOA:4700}" # covers ATM ladder 20+50+100+200+4200 (paivana) : "${TIMEOUT:=12}" -: "${E2E_TIMEOUT:=55}" # whole e2e budget; skip rest when exceeded +: "${E2E_TIMEOUT:=55}" # whole e2e budget; skip rest when exceeded (e2e raises as needed) : "${E2E_PAY_SECS:=22}" # dedicated seconds for pay handle-uri (avoid Alarm clock) +# Local GOA: public paivana paywall (https://paivana.hacktivism.ch · template GOA:4200) +: "${PAIVANA_PUBLIC:=https://paivana.hacktivism.ch}" +: "${E2E_PAIVANA:=1}" # 0 = skip paivana section in e2e # Devtest: inject reserve credit via wire-gateway admin/add-incoming (optional). # Default off once wirewatch DNS works; set E2E_FAKE_INCOMING=1 to force. : "${E2E_FAKE_INCOMING:=0}" @@ -37,17 +40,103 @@ EXCHANGE_PUBLIC=${EXCHANGE_PUBLIC%/} MERCHANT_PUBLIC=${MERCHANT_PUBLIC%/} # --------------------------------------------------------------------------- -# Domain presets → public bank / exchange / merchant base URLs +# Domain profiles → bank / exchange / merchant base URLs # -# TALER_DOMAIN=hacktivism.ch (default, GOA, local stack) -# TALER_DOMAIN=taler.net → demo.taler.net (KUDOS) -# TALER_DOMAIN=demo.taler.net -# TALER_DOMAIN=taler-ops.ch → exchange.taler-ops.ch (CHF; bank/merchant if up) -# TALER_DOMAIN=example.org → bank/exchange/backend|taler|merchant.example.org +# Single place to declare a stack: scripts/taler-monitoring/domains.conf +# (or TALER_DOMAINS_CONF). Each profile names the three public endpoints. # -# Explicit BANK_PUBLIC / EXCHANGE_PUBLIC / MERCHANT_PUBLIC still win if set -# *after* apply_taler_domain, or pass full URLs via --bank/--exchange/--merchant. +# CLI still wins after profile load: +# --bank URL --exchange URL --merchant URL --currency CODE +# Env: BANK_PUBLIC EXCHANGE_PUBLIC MERCHANT_PUBLIC EXPECT_CURRENCY +# +# Unknown domains fall back to heuristics (see apply_taler_domain). # --------------------------------------------------------------------------- + +_MONITOR_LIB_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +: "${TALER_DOMAINS_CONF:=${_MONITOR_LIB_DIR}/domains.conf}" + +# host_or_url → https://… (no trailing slash) +_to_https_base() { + local x="${1%/}" + case "$x" in + https://*|http://*) printf '%s' "$x" ;; + "") printf '' ;; + *) printf 'https://%s' "$x" ;; + esac +} + +# 1 = check GOA-style /intro landing pages (assets, link crawl, demo-withdraw). +# 0 = skip (e.g. taler-ops.ch / mytops — no public landings). +: "${CHECK_LANDING:=1}" + +# Declare bank + exchange + merchant (+ currency, local stack, landing, domain id). +# Usage: +# set_taler_stack BANK EXCHANGE MERCHANT [CURRENCY] [LOCAL 0|1] [LANDING 0|1] [TALER_DOMAIN] +# BANK/EXCHANGE/MERCHANT: hostname or full URL +set_taler_stack() { + local bank="${1:-}" exchange="${2:-}" merchant="${3:-}" + local currency="${4:-}" local_stack="${5:-0}" landing="${6:-}" domain_id="${7:-}" + + BANK_PUBLIC=$(_to_https_base "$bank") + EXCHANGE_PUBLIC=$(_to_https_base "$exchange") + MERCHANT_PUBLIC=$(_to_https_base "$merchant") + [ -n "$currency" ] && EXPECT_CURRENCY="$currency" + LOCAL_STACK="$local_stack" + if [ "$LOCAL_STACK" = "1" ]; then + SKIP_SSH=0 + else + SKIP_SSH=1 + fi + # default: landings only matter on local GOA stack + if [ -n "$landing" ]; then + CHECK_LANDING="$landing" + elif [ "$LOCAL_STACK" = "1" ]; then + CHECK_LANDING=1 + else + CHECK_LANDING=0 + fi + [ -n "$domain_id" ] && TALER_DOMAIN="$domain_id" + + BANK_PUBLIC=${BANK_PUBLIC%/} + EXCHANGE_PUBLIC=${EXCHANGE_PUBLIC%/} + MERCHANT_PUBLIC=${MERCHANT_PUBLIC%/} +} + +# Load first matching profile from domains.conf. +# Fields (whitespace-separated; # comments; blank lines ignored): +# name bank exchange merchant currency local[0|1] landing[0|1] [canonical_domain] +# Returns 0 if found, 1 if not. +load_domain_profile() { + local want="$1" conf="${TALER_DOMAINS_CONF:-}" + local line name bank exchange merchant currency local_stack landing canon + [ -n "$want" ] || return 1 + [ -n "$conf" ] && [ -f "$conf" ] || return 1 + + while IFS= read -r line || [ -n "$line" ]; do + line="${line%%#*}" + # shellcheck disable=SC2086 + set -- $line + [ $# -ge 5 ] || continue + name="$1" + [ "$name" = "$want" ] || continue + bank="$2" + exchange="$3" + merchant="$4" + currency="$5" + local_stack="${6:-0}" + landing="${7:-}" + canon="${8:-$name}" + # Backward compat: old 7th field was canonical domain (contains a dot) + if [ -n "$landing" ] && [[ "$landing" == *.* && "$landing" != "0" && "$landing" != "1" ]]; then + canon="$landing" + landing="" + fi + set_taler_stack "$bank" "$exchange" "$merchant" "$currency" "$local_stack" "$landing" "$canon" + return 0 + done <"$conf" + return 1 +} + _normalize_domain() { local d="$1" d="${d#https://}" @@ -58,7 +147,7 @@ _normalize_domain() { # e.g. bank.demo.taler.net → demo.taler.net, taler.hacktivism.ch → hacktivism.ch # but NOT taler.net → net case "$d" in - bank.*|exchange.*|taler.*|backend.*|merchant.*|shop.*|libeufin.*) + bank.*|exchange.*|taler.*|backend.*|merchant.*|shop.*|libeufin.*|my.*|map.*) rest="${d#*.}" if [[ "$rest" == *.* ]]; then d="$rest" @@ -82,75 +171,50 @@ apply_taler_domain() { d=$(_normalize_domain "$raw") TALER_DOMAIN="$d" - case "$d" in - # Local koopa stack only — SSH / inside / e2e allowed - koopa|hacktivism.ch|hacktivism) - BANK_PUBLIC="https://bank.hacktivism.ch" - EXCHANGE_PUBLIC="https://exchange.hacktivism.ch" - MERCHANT_PUBLIC="https://taler.hacktivism.ch" - EXPECT_CURRENCY="GOA" - LOCAL_STACK=1 - SKIP_SSH=0 - TALER_DOMAIN="hacktivism.ch" - : "${WITHDRAW_AMT:=GOA:20}" - : "${PAY_AMT:=GOA:0.01}" - : "${CREDIT_AMT:=GOA:400}" - ;; - taler.net|demo.taler.net) - # Official public demo (KUDOS) — public only, never SSH; tiny e2e amounts - BANK_PUBLIC="https://bank.demo.taler.net" - EXCHANGE_PUBLIC="https://exchange.demo.taler.net" - MERCHANT_PUBLIC="https://backend.demo.taler.net" - EXPECT_CURRENCY="KUDOS" - LOCAL_STACK=0 - SKIP_SSH=1 - MERCHANT_INSTANCE="${MERCHANT_INSTANCE:-sandbox}" - WITHDRAW_AMT="${WITHDRAW_AMT:-KUDOS:20}" - PAY_AMT="${PAY_AMT:-KUDOS:0.01}" - CREDIT_AMT="${CREDIT_AMT:-KUDOS:100}" - TALER_DOMAIN="demo.taler.net" - ;; - taler-ops.ch) - # Public CHF exchange; bank/merchant hosts vary — probe common names - EXCHANGE_PUBLIC="https://exchange.taler-ops.ch" - BANK_PUBLIC="https://bank.taler-ops.ch" - MERCHANT_PUBLIC="https://backend.taler-ops.ch" - EXPECT_CURRENCY="CHF" - LOCAL_STACK=0 - SKIP_SSH=1 - WITHDRAW_AMT="${WITHDRAW_AMT:-CHF:20}" - PAY_AMT="${PAY_AMT:-CHF:0.01}" - CREDIT_AMT="${CREDIT_AMT:-CHF:100}" - if [ "${TALER_DOMAIN_PROBE}" = "1" ]; then - local h - for h in bank.taler-ops.ch bank.demo.taler-ops.ch; do - _probe_https_config "$h" && { BANK_PUBLIC="https://$h"; break; } - done - for h in backend.taler-ops.ch merchant.taler-ops.ch taler.taler-ops.ch shop.taler-ops.ch; do - _probe_https_config "$h" && { MERCHANT_PUBLIC="https://$h"; break; } - done - fi - ;; - *) - # Any other domain — public HTTPS only, never SSH to koopa - BANK_PUBLIC="https://bank.${d}" - EXCHANGE_PUBLIC="https://exchange.${d}" - MERCHANT_PUBLIC="https://backend.${d}" - EXPECT_CURRENCY="${EXPECT_CURRENCY:-}" # unknown — don't hard-fail currency - LOCAL_STACK=0 - SKIP_SSH=1 - if [ "${TALER_DOMAIN_PROBE}" = "1" ]; then - local h - for h in "backend.${d}" "taler.${d}" "merchant.${d}" "shop.${d}"; do - _probe_https_config "$h" && { MERCHANT_PUBLIC="https://$h"; break; } - done - for h in "bank.${d}" "libeufin.${d}"; do - _probe_https_config "$h" && { BANK_PUBLIC="https://$h"; break; } - done - _probe_https_config "exchange.${d}" || true - fi - ;; - esac + # 1) Explicit profile (domains.conf) — preferred way to add stacks + if load_domain_profile "$d" || load_domain_profile "$raw"; then + # Optional tiny defaults for e2e ladders by currency + case "${EXPECT_CURRENCY:-}" in + GOA) + : "${WITHDRAW_AMT:=GOA:20}" + : "${PAY_AMT:=GOA:0.01}" + : "${CREDIT_AMT:=GOA:400}" + ;; + KUDOS|TESTKUDOS) + MERCHANT_INSTANCE="${MERCHANT_INSTANCE:-sandbox}" + WITHDRAW_AMT="${WITHDRAW_AMT:-${EXPECT_CURRENCY}:20}" + PAY_AMT="${PAY_AMT:-${EXPECT_CURRENCY}:0.01}" + CREDIT_AMT="${CREDIT_AMT:-${EXPECT_CURRENCY}:100}" + ;; + CHF) + WITHDRAW_AMT="${WITHDRAW_AMT:-CHF:20}" + PAY_AMT="${PAY_AMT:-CHF:0.01}" + CREDIT_AMT="${CREDIT_AMT:-CHF:100}" + ;; + esac + else + # 2) Unknown domain — heuristics only (prefer profile in domains.conf) + # Override: --bank / --exchange / --merchant / --currency + # Do not keep default GOA: empty currency = report only, no hard fail. + BANK_PUBLIC="https://bank.${d}" + EXCHANGE_PUBLIC="https://exchange.${d}" + # TOPS-style multi-tenant merchant first, then legacy names + MERCHANT_PUBLIC="https://my.${d}" + EXPECT_CURRENCY="" + LOCAL_STACK=0 + SKIP_SSH=1 + CHECK_LANDING=0 + if [ "${TALER_DOMAIN_PROBE}" = "1" ]; then + local h + for h in "backend.${d}" "my.${d}" "taler.${d}" "merchant.${d}" "shop.${d}"; do + _probe_https_config "$h" && { MERCHANT_PUBLIC="https://$h"; break; } + done + for h in "bank.${d}" "libeufin.${d}"; do + _probe_https_config "$h" && { BANK_PUBLIC="https://$h"; break; } + done + _probe_https_config "exchange.${d}" || true + fi + fi BANK_PUBLIC=${BANK_PUBLIC%/} EXCHANGE_PUBLIC=${EXCHANGE_PUBLIC%/} @@ -241,10 +305,18 @@ koopa_ssh_bash() { with_timeout "$t" ssh "${SSH_BASE_OPTS[@]}" "${KOOPA_SSH}" 'bash -s' } -if [ "${NO_COLOR:-0}" = "1" ] || [ ! -t 1 ]; then +# ANSI colours for tags (green OK / yellow WARN / red ERROR·BLOCKER / cyan INFO). +# Default: always on so pasted ```bash``` logs keep visible tags when the terminal +# supports colour. Disable: NO_COLOR=1. Force off for dumb pipes: CLICOLOR=0. +if [ "${NO_COLOR:-0}" = "1" ] || [ "${CLICOLOR:-1}" = "0" ]; then G= R= Y= C= N= B= else - G=$'\e[32m'; R=$'\e[31m'; Y=$'\e[33m'; C=$'\e[36m'; B=$'\e[1m'; N=$'\e[0m' + G=$'\e[1;32m' # bold green [OK] + R=$'\e[1;31m' # bold red [ERROR] [BLOCKER] + Y=$'\e[1;33m' # bold yellow [WARN] + C=$'\e[1;36m' # bold cyan [INFO] + B=$'\e[1m' + N=$'\e[0m' fi PASS_N=0 @@ -282,9 +354,13 @@ _fmt_tid() { } ok() { - local label="$1" + local label="$1" detail="${2:-}" _take_tid - printf '%s[OK]%s %s%s\n' "$G" "$N" "$(_fmt_tid)" "$label" + if [ -n "$detail" ]; then + printf '%s[OK]%s %s%s — %s\n' "$G" "$N" "$(_fmt_tid)" "$label" "$detail" + else + printf '%s[OK]%s %s%s\n' "$G" "$N" "$(_fmt_tid)" "$label" + fi PASS_N=$((PASS_N + 1)) } # component-scoped error: err bank "libeufin down" "detail" diff --git a/scripts/taler-monitoring/taler-monitoring.sh b/scripts/taler-monitoring/taler-monitoring.sh index 321525b..6ee72b4 100755 --- a/scripts/taler-monitoring/taler-monitoring.sh +++ b/scripts/taler-monitoring/taler-monitoring.sh @@ -29,27 +29,33 @@ Phases: all urls + inside + versions + sanity + e2e (SSH phases only on koopa) Options: - -d, --domain DOMAIN target domain - koopa / hacktivism.ch → local stack, SSH ok - everything else → no SSH; e2e optional (tiny amounts) + -d, --domain DOMAIN load profile from domains.conf (bank/exchange/merchant) presets: koopa | hacktivism.ch | taler.net | taler-ops.ch - generic: bank/exchange/backend. - --bank URL override bank base (https://…) - --exchange URL override exchange base - --merchant URL override merchant base + | my.taler-ops.ch | stage.taler-ops.ch + unknown domain → heuristic hosts (see README) + local (koopa) allows SSH; others public-only + --bank URL bank base (hostname or https://…) — overrides profile + --exchange URL exchange base — overrides profile + --merchant URL merchant-backend base — overrides profile --currency CODE expected currency (GOA, KUDOS, CHF, …); empty = report only - --no-probe do not probe alternate merchant/bank hostnames + --no-probe do not probe alternate hosts (unknown domains only) -h, --help +Add a stack: edit domains.conf (name + bank + exchange + merchant + currency). + Examples: ./taler-monitoring.sh -d taler.net ./taler-monitoring.sh -d taler-ops.ch urls + ./taler-monitoring.sh -d my.taler-ops.ch urls + ./taler-monitoring.sh -d taler-ops.ch --merchant https://my.taler-ops.ch urls ./taler-monitoring.sh -d demo.taler.net --currency KUDOS - ./taler-monitoring.sh --exchange https://exchange.taler-ops.ch urls + ./taler-monitoring.sh --exchange https://exchange.taler-ops.ch \ + --merchant https://my.taler-ops.ch --bank https://bank.taler-ops.ch urls Env (same meaning): TALER_DOMAIN BANK_PUBLIC EXCHANGE_PUBLIC MERCHANT_PUBLIC EXPECT_CURRENCY - SKIP_SSH=1 NO_COLOR=1 + TALER_DOMAINS_CONF SKIP_SSH=1 + NO_COLOR=1 / CLICOLOR=0 disable green/yellow/red tags (default: coloured) EOF } @@ -129,10 +135,11 @@ fi # Export so check_*.sh (re-source lib) see the same targets via env export TALER_DOMAIN BANK_PUBLIC EXCHANGE_PUBLIC MERCHANT_PUBLIC -export EXPECT_CURRENCY SKIP_SSH LOCAL_STACK TALER_DOMAIN_PROBE +export EXPECT_CURRENCY SKIP_SSH LOCAL_STACK TALER_DOMAIN_PROBE CHECK_LANDING 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 +export PAIVANA_PUBLIC E2E_PAIVANA E2E_PAIVANA_TEMPLATE E2E_PAIVANA_AMOUNT E2E_PAIVANA_INSTANCE export TALER_DOMAIN_APPLIED=1 # Default phases From 9c2d3077a565490470ab50a0c096770ac3e08683 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hern=C3=A2ni=20Marques?= Date: Fri, 17 Jul 2026 10:18:23 +0200 Subject: [PATCH 2/8] monitoring: compact landings + stats load --- scripts/taler-monitoring/check_urls.sh | 452 ++++++++++++++++++------- 1 file changed, 338 insertions(+), 114 deletions(-) diff --git a/scripts/taler-monitoring/check_urls.sh b/scripts/taler-monitoring/check_urls.sh index 2f7d1de..2f2f4a4 100755 --- a/scripts/taler-monitoring/check_urls.sh +++ b/scripts/taler-monitoring/check_urls.sh @@ -136,9 +136,13 @@ PY fi fi fi -check_url_soft "exchange /intro/" 200 "$EXCHANGE_PUBLIC/intro/" -# Root should land on intro (302/301 then 200 on follow is checked separately) -check_url_soft "exchange /" 302,301,200 "$EXCHANGE_PUBLIC/" +if [ "${CHECK_LANDING:-1}" = "1" ]; then + check_url_soft "exchange /intro/" 200 "$EXCHANGE_PUBLIC/intro/" + # Root should land on intro (302/301 then 200 on follow is checked separately) + check_url_soft "exchange /" 302,301,200 "$EXCHANGE_PUBLIC/" +else + info "exchange /intro" "skipped (CHECK_LANDING=0 · no public landings for this stack)" +fi # --------------------------------------------------------------------------- # Performance — outside-in public HTTPS latency (this runner, not loopback) @@ -188,21 +192,137 @@ check_perf() { # Bank first (wallet-critical paths before UI chrome) check_perf "perf bank /taler-integration/config" "$BANK_PUBLIC/taler-integration/config" check_perf "perf bank /config" "$BANK_PUBLIC/config" -check_perf "perf bank /intro/" "$BANK_PUBLIC/intro/" -check_perf "perf bank /intro/stats.json" "$BANK_PUBLIC/intro/stats.json" 200 +if [ "${CHECK_LANDING:-1}" = "1" ]; then + check_perf "perf bank /intro/" "$BANK_PUBLIC/intro/" + check_perf "perf bank /intro/stats.json" "$BANK_PUBLIC/intro/stats.json" 200 +fi check_perf "perf bank /webui/" "$BANK_PUBLIC/webui/" 200,301,302 # Exchange check_perf "perf exchange /config" "$EXCHANGE_PUBLIC/config" check_perf "perf exchange /keys" "$EXCHANGE_PUBLIC/keys" -check_perf "perf exchange /intro/" "$EXCHANGE_PUBLIC/intro/" +if [ "${CHECK_LANDING:-1}" = "1" ]; then + check_perf "perf exchange /intro/" "$EXCHANGE_PUBLIC/intro/" +fi # Merchant check_perf "perf merchant /config" "$MERCHANT_PUBLIC/config" check_perf "perf merchant /webui/" "$MERCHANT_PUBLIC/webui/" 200,301,302 -check_perf "perf merchant /intro/" "$MERCHANT_PUBLIC/intro/" +if [ "${CHECK_LANDING:-1}" = "1" ]; then + check_perf "perf merchant /intro/" "$MERCHANT_PUBLIC/intro/" +fi -info "perf note" "measured from this host (outside-in); not container loopback" +info "perf note" "RTT measured from this host (outside-in)" + +# --------------------------------------------------------------------------- +# Landing load stats (stats.json written *inside* containers, usually public). +# Prefer HTTPS /intro/stats.json (no SSH). If missing and SSH works, cat from +# the container. Report loadavg + RSS + in-container probe ms — one line each. +# --------------------------------------------------------------------------- +: "${STATS_STALE_SECS:=900}" # warn if generated_at older than 15m + +_fetch_landing_stats_json() { + # $1=name bank|exchange|merchant $2=base URL → writes $tmp/stats-$1.json, exit 0 if ok + local name="$1" base="$2" + local f="$tmp/stats-${name}.json" + local code ctr path + rm -f "$f" + code=$(http_body "${base}/intro/stats.json" "$f" 2>/dev/null || echo 000) + if [ "$code" = "200" ] && [ -s "$f" ]; then + return 0 + fi + # Optional inside-container fallback (no public path or empty) + if [ "${LOCAL_STACK:-0}" = "1" ] && [ "${SKIP_SSH:-0}" != "1" ] && koopa_ssh_ok 2>/dev/null; then + case "$name" in + bank) ctr=taler-hacktivism-bank; path=/var/www/bank-landing/stats.json ;; + exchange) ctr=taler-hacktivism-exchange-ansible; path=/var/www/exchange-landing/stats.json ;; + merchant) ctr=taler-hacktivism; path=/var/www/merchant-landing/stats.json ;; + *) return 1 ;; + esac + if koopa_ssh_run 12 "podman exec ${ctr} cat ${path} 2>/dev/null" >"$f" 2>/dev/null \ + && [ -s "$f" ]; then + return 0 + fi + fi + return 1 +} + +report_landing_load_stats() { + local name="$1" base="$2" + local f="$tmp/stats-${name}.json" + local line age_s + + if ! _fetch_landing_stats_json "$name" "$base"; then + if [ "${CHECK_LANDING:-1}" = "1" ] || [ "${LOCAL_STACK:-0}" = "1" ]; then + warn "perf ${name} landing-stats" "stats.json not available (public or SSH)" + fi + return + fi + + line=$(python3 - "$f" "${STATS_STALE_SECS}" <<'PY' 2>/dev/null || true +import json, sys, time +path, stale = sys.argv[1], int(sys.argv[2]) +try: + d = json.load(open(path)) +except Exception as e: + print("ERR parse:" + str(e)[:80]) + sys.exit(0) +if not d.get("ok", True): + print("ERR ok=false " + str(d.get("error") or "")[:80]) + sys.exit(0) +p = d.get("performance") or {} +if not isinstance(p, dict) or not p: + print("ERR no performance block") + sys.exit(0) +mem = p.get("memory") if isinstance(p.get("memory"), dict) else {} +rss = mem.get("container_rss_human") or mem.get("proc_sum_rss_human") or "?" +load = p.get("loadavg") or "?" +# latency fields differ by site +bits = [] +for k in ("config_ms", "integration_ms", "webui_ms", "keys_ms", "terms_ms"): + if k in p and p[k] is not None: + bits.append("%s=%sms" % (k.replace("_ms", ""), p[k])) +lat = " ".join(bits) if bits else "latency=?" +gen = d.get("generated_at_human") or d.get("generated_at") or "?" +# staleness +age = "" +try: + gu = d.get("generated_at_unix") + if gu is not None: + age_s = int(time.time()) - int(gu) + age = " age=%ss" % age_s + if age_s > stale: + print("STALE loadavg=%s RSS=%s %s gen=%s%s" % (load, rss, lat, gen, age)) + sys.exit(0) +except Exception: + pass +print("OK loadavg=%s RSS=%s %s gen=%s%s" % (load, rss, lat, gen, age)) +PY +) + + case "$line" in + OK\ *) + ok "perf ${name} landing-stats" "${line#OK }" + ;; + STALE\ *) + warn "perf ${name} landing-stats" "stale · ${line#STALE }" + ;; + ERR\ *) + warn "perf ${name} landing-stats" "${line#ERR }" + ;; + *) + warn "perf ${name} landing-stats" "unreadable stats.json" + ;; + esac +} + +if [ "${CHECK_LANDING:-1}" = "1" ] || [ "${LOCAL_STACK:-0}" = "1" ]; then + section "www · performance · landing load stats (stats.json · in-container probes)" + report_landing_load_stats "bank" "$BANK_PUBLIC" + report_landing_load_stats "exchange" "$EXCHANGE_PUBLIC" + report_landing_load_stats "merchant" "$MERCHANT_PUBLIC" + info "perf landing-stats note" "from /intro/stats.json (public); SSH container fallback if needed" +fi # Terms + privacy (legal docs) @@ -241,13 +361,14 @@ if [ "$code" = "200" ]; then check_url_soft "bank /taler-integration/config" 200 "$BANK_PUBLIC/taler-integration/config" fi check_url_soft "bank /webui/" 200 "$BANK_PUBLIC/webui/" - check_url_soft "bank /intro/" 200 "$BANK_PUBLIC/intro/" - check_url_soft "bank /" 302,301,200 "$BANK_PUBLIC/" - # Auto-account: credentials + shared-pool taler://withdraw (like step 2) - aa_code=$(http_body "$BANK_PUBLIC/intro/auto-account.json" "$tmp/aa.json") - case "$aa_code" in - 200) - if python3 - "$tmp/aa.json" <<'PY' + if [ "${CHECK_LANDING:-1}" = "1" ]; then + check_url_soft "bank /intro/" 200 "$BANK_PUBLIC/intro/" + check_url_soft "bank /" 302,301,200 "$BANK_PUBLIC/" + # Auto-account: credentials + shared-pool taler://withdraw (like step 2) + aa_code=$(http_body "$BANK_PUBLIC/intro/auto-account.json" "$tmp/aa.json") + case "$aa_code" in + 200) + if python3 - "$tmp/aa.json" <<'PY' import json, re, sys from urllib.parse import urlparse d = json.load(open(sys.argv[1])) @@ -268,19 +389,20 @@ if u.scheme not in ("http", "https") or "webui" not in (u.path or ""): print("user=%s withdraw=%s login=%s" % (d.get("username"), wm.group(1), webui)) sys.exit(0) PY - then - ok "bank /intro/auto-account.json" "$(python3 -c 'import json;d=json.load(open("'"$tmp/aa.json"'"));print(d.get("username",""),"·",(d.get("taler_withdraw_uri") or "")[:72])' 2>/dev/null || true)" - else - fail "bank /intro/auto-account.json" "invalid withdraw/login (HTTP body bad)" - fi - ;; - 405|501|404|502|503|000) - fail "bank /intro/auto-account.json" "HTTP $aa_code (want 200; 405/501 = broken)" - ;; - *) - fail "bank /intro/auto-account.json" "HTTP $aa_code want 200" - ;; - esac + then + ok "bank /intro/auto-account.json" "$(python3 -c 'import json;d=json.load(open("'"$tmp/aa.json"'"));print(d.get("username",""),"·",(d.get("taler_withdraw_uri") or "")[:72])' 2>/dev/null || true)" + else + fail "bank /intro/auto-account.json" "invalid withdraw/login (HTTP body bad)" + fi + ;; + 405|501|404|502|503|000) + fail "bank /intro/auto-account.json" "HTTP $aa_code (want 200; 405/501 = broken)" + ;; + *) + fail "bank /intro/auto-account.json" "HTTP $aa_code want 200" + ;; + esac + fi fi # Bank legal docs (landing nginx via Caddy /terms* /privacy* or /intro/*) @@ -346,7 +468,9 @@ PY rm -f /tmp/alt-mer.$$ # Follow each exchange listed in merchant /config and require its /config alt_unit_names check_merchant_listed_exchanges_alt_units "$tmp/mc.json" - check_url_soft "merchant /intro/" 200 "$MERCHANT_PUBLIC/intro/" + if [ "${CHECK_LANDING:-1}" = "1" ]; then + check_url_soft "merchant /intro/" 200 "$MERCHANT_PUBLIC/intro/" + fi check_url_soft "merchant /webui/" 200 "$MERCHANT_PUBLIC/webui/" check_url_soft "merchant /" 302,301,200 "$MERCHANT_PUBLIC/" fi @@ -360,49 +484,92 @@ case "$code" in *) warn "merchant /terms/" "HTTP $code (expect 302 → /terms)" ;; esac +# --------------------------------------------------------------------------- +# Paivana paywall (local GOA stack) — public front only; pay path is e2e +# --------------------------------------------------------------------------- +if [ "${LOCAL_STACK:-1}" = "1" ] && [ "${E2E_PAIVANA:-1}" != "0" ]; then + section "www · paivana paywall" + : "${PAIVANA_PUBLIC:=https://paivana.hacktivism.ch}" + PAIVANA_PUBLIC="${PAIVANA_PUBLIC%/}" + hdr=$(curl -skS -m "${TIMEOUT}" -D - -o /dev/null "${PAIVANA_PUBLIC}/" 2>/dev/null || true) + pcode=$(printf '%s' "$hdr" | awk 'BEGIN{c="000"} /^HTTP/{c=$2} END{print c}') + loc=$(printf '%s' "$hdr" | awk 'BEGIN{IGNORECASE=1} /^location:/{sub(/\r$/,""); sub(/^location:[[:space:]]*/,""); print; exit}') + case "$pcode" in + 301|302|303|307|308) + if printf '%s' "$loc" | grep -qiE 'paivana|templates|well-known'; then + ok "paivana /" "HTTP $pcode → template flow" + else + ok "paivana /" "HTTP $pcode redirect" + fi + info "paivana Location" "${loc:0:140}" + ;; + 200) + warn "paivana /" "HTTP 200 (expected paywall redirect to template)" + ;; + *) + warn "paivana /" "HTTP ${pcode:-000} — ${PAIVANA_PUBLIC}/ (e2e pay may still work via template)" + ;; + esac +fi + # --------------------------------------------------------------------------- # Landing pages: every HTTPS link exposed on bank / merchant / exchange intros # + required static assets + bank withdraw mint (taler://withdraw only) +# Skipped when CHECK_LANDING=0 (e.g. taler-ops.ch — no GOA-style landings). # --------------------------------------------------------------------------- +if [ "${CHECK_LANDING:-1}" != "1" ]; then + section "www · landing pages" + info "landing checks" "skipped (CHECK_LANDING=0 · stack has no public /intro landings)" + summary + exit 0 +fi + section "www · landing exposed links · bank / merchant / exchange" -# Known-good landing static paths (relative to each host base) -# Checked even if HTML parse misses them. -check_landing_asset() { - local label="$1" url="$2" soft="${3:-0}" +# Probe one URL: print code to stdout (200 after following redirects counts as 200). +# Sets _landing_code. Exit 0 if OK (200 or redirect→200), 1 otherwise. +_landing_probe() { + local url="$1" local code code=$(http_code "$url") case "$code" in - 200) ok "$label" "HTTP 200 · $url" ;; + 200) _landing_code=200; return 0 ;; 301|302|303|307|308) - # follow once for assets that redirect - code=$(curl -skS --max-redirs 3 -L -m "${TIMEOUT}" -o /dev/null -w '%{http_code}' "$url" 2>/dev/null || echo 000) - if [ "$code" = "200" ]; then - ok "$label" "HTTP redirect→200 · $url" - elif [ "$soft" = "1" ]; then - warn "$label" "HTTP $code — $url" - else - fail "$label" "HTTP $code after redirect — $url" - fi + code=$(curl -skS --max-redirs 5 -L -m "${TIMEOUT}" -o /dev/null -w '%{http_code}' "$url" 2>/dev/null || echo 000) + _landing_code="$code" + [ "$code" = "200" ] && return 0 + return 1 ;; *) - if [ "$soft" = "1" ]; then - warn "$label" "HTTP $code — $url" - else - fail "$label" "HTTP $code — $url" - fi + _landing_code="$code" + return 1 ;; esac } -# Soft external (app stores / upstream docs): WARN if down, never ERROR +# Known-good landing static paths — one report line via caller aggregate, or +# soft=1 single warn. Returns 0 if ok. +check_landing_asset() { + local label="$1" url="$2" soft="${3:-0}" + if _landing_probe "$url"; then + return 0 + fi + if [ "$soft" = "1" ]; then + warn "$label" "HTTP ${_landing_code:-?} — $url" + else + fail "$label" "HTTP ${_landing_code:-?} — $url" + fi + return 1 +} + +# Soft external: never ERROR; used only for failures in aggregated external probe. check_external_soft() { local label="$1" url="$2" local code code=$(curl -skS --max-redirs 5 -L -m "${TIMEOUT}" -o /dev/null -w '%{http_code}' "$url" 2>/dev/null || echo 000) case "$code" in - 200|204|301|302|303|307|308) ok "$label" "HTTP $code · $url" ;; - *) warn "$label" "HTTP $code (external soft) · $url" ;; + 200|204|301|302|303|307|308) return 0 ;; + *) warn "$label" "HTTP $code · $url"; return 1 ;; esac } @@ -499,95 +666,139 @@ check_one_landing() { local html="$tmp/landing-${name}.html" local pref="$tmp/urls-${name}" local code n own_n ext_n + local a_ok=0 a_fail=0 a_soft=0 + local own_ok=0 own_fail=0 + local ext_ok=0 ext_fail=0 + local fail_sample="" soft_sample="" + code=$(http_body "${base}/intro/" "$html") if [ "$code" != "200" ]; then if [ "${LOCAL_STACK:-1}" = "1" ]; then - fail "landing ${name} /intro/" "HTTP $code" + fail "landing ${name}" "/intro/ HTTP $code — skip assets/links" else - warn "landing ${name} /intro/" "HTTP $code" + warn "landing ${name}" "/intro/ HTTP $code — skip assets/links" fi return fi - ok "landing ${name} /intro/" "HTTP 200 · $(wc -c <"$html" | tr -d ' ') bytes" - # Required static assets (hard on local) - check_landing_asset "landing ${name} qrcode.min.js" "${base}/intro/qrcode.min.js" - check_landing_asset "landing ${name} og-goa-shop.png" "${base}/intro/og-goa-shop.png" - check_landing_asset "landing ${name} qr-logo.png" "${base}/intro/qr-logo.png" 1 + # Required static assets (hard: qrcode + og; soft: qr-logo) + for url in \ + "${base}/intro/qrcode.min.js" \ + "${base}/intro/og-goa-shop.png" + do + if _landing_probe "$url"; then + a_ok=$((a_ok + 1)) + else + a_fail=$((a_fail + 1)) + fail_sample="${fail_sample}${fail_sample:+; }HTTP ${_landing_code} $url" + fi + done + if _landing_probe "${base}/intro/qr-logo.png"; then + a_ok=$((a_ok + 1)) + else + a_soft=$((a_soft + 1)) + soft_sample="${soft_sample}${soft_sample:+; }HTTP ${_landing_code} qr-logo.png" + fi n=$(extract_landing_urls "$base" "$html" "$pref" 2>/dev/null || echo "own=0 ext=0") - info "landing ${name} link extract" "$n" own_n=0 ext_n=0 [ -f "${pref}.own" ] && own_n=$(grep -c . "${pref}.own" 2>/dev/null || echo 0) [ -f "${pref}.ext" ] && ext_n=$(grep -c . "${pref}.ext" 2>/dev/null || echo 0) - if [ "${own_n:-0}" -lt 1 ]; then - fail "landing ${name} own-stack links" "none extracted from HTML" - else - ok "landing ${name} own-stack links" "${own_n} URLs to probe" - fi + # strip newlines from grep -c edge cases + own_n=${own_n//[^0-9]/} + ext_n=${ext_n//[^0-9]/} + own_n=${own_n:-0} + ext_n=${ext_n:-0} - # Probe every own-stack URL from the page + # Probe own-stack URLs — count only; list failures if [ -f "${pref}.own" ]; then while IFS= read -r u; do [ -n "$u" ] || continue - # skip mint endpoints that create resources on GET if any (auto-account creates accounts) - case "$u" in - */intro/auto-account.json) - # shape checked separately; still require 200 GET - ;; - esac - code=$(http_code "$u") - case "$code" in - 200) ok "landing ${name} link" "HTTP 200 · $u" ;; - 301|302|303|307|308) - code=$(curl -skS --max-redirs 5 -L -m "${TIMEOUT}" -o /dev/null -w '%{http_code}' "$u" 2>/dev/null || echo 000) - if [ "$code" = "200" ]; then - ok "landing ${name} link" "redirect→200 · $u" - else - fail "landing ${name} link" "HTTP $code after redirect · $u" - fi - ;; - 405|501) - # some APIs reject wrong method — try GET already failed; soft note - fail "landing ${name} link" "HTTP $code · $u" - ;; - *) - if [ "${LOCAL_STACK:-1}" = "1" ]; then - fail "landing ${name} link" "HTTP $code · $u" - else - warn "landing ${name} link" "HTTP $code · $u" - fi - ;; - esac + if _landing_probe "$u"; then + own_ok=$((own_ok + 1)) + else + own_fail=$((own_fail + 1)) + # keep a few samples (max ~3) + if [ "$own_fail" -le 3 ]; then + fail_sample="${fail_sample}${fail_sample:+; }own HTTP ${_landing_code} $u" + fi + fi done < "${pref}.own" fi - # External store / docs: soft + # External: soft counts if [ -f "${pref}.ext" ]; then while IFS= read -r u; do [ -n "$u" ] || continue - check_external_soft "landing ${name} external" "$u" + code=$(curl -skS --max-redirs 5 -L -m "${TIMEOUT}" -o /dev/null -w '%{http_code}' "$u" 2>/dev/null || echo 000) + case "$code" in + 200|204|301|302|303|307|308) ext_ok=$((ext_ok + 1)) ;; + *) + ext_fail=$((ext_fail + 1)) + if [ "$ext_fail" -le 3 ]; then + soft_sample="${soft_sample}${soft_sample:+; }ext HTTP $code $u" + fi + ;; + esac done < "${pref}.ext" fi + + # One primary line per landing + local detail + detail="/intro $(wc -c <"$html" | tr -d ' ')B · assets ${a_ok}/$((a_ok + a_fail + a_soft)) · own-links ${own_ok}/${own_n} · external ${ext_ok}/${ext_n}" + if [ "$a_fail" -gt 0 ] || [ "$own_fail" -gt 0 ] || [ "${own_n:-0}" -lt 1 ]; then + if [ "${LOCAL_STACK:-1}" = "1" ]; then + fail "landing ${name}" "$detail${fail_sample:+ · $fail_sample}" + else + warn "landing ${name}" "$detail${fail_sample:+ · $fail_sample}" + fi + else + ok "landing ${name}" "$detail" + fi + if [ "$a_soft" -gt 0 ] || [ "$ext_fail" -gt 0 ]; then + warn "landing ${name} soft" "${soft_sample:-soft issues}" + fi } check_one_landing "bank" "$BANK_PUBLIC" check_one_landing "merchant" "$MERCHANT_PUBLIC" check_one_landing "exchange" "$EXCHANGE_PUBLIC" -# Cross-links between the three landings (always on local stack) +# Cross-links: one line if [ "${LOCAL_STACK:-1}" = "1" ]; then - check_landing_asset "cross bank→merchant intro" "$MERCHANT_PUBLIC/intro/" - check_landing_asset "cross bank→exchange intro" "$EXCHANGE_PUBLIC/intro/" - check_landing_asset "cross merchant→bank intro" "$BANK_PUBLIC/intro/" - check_landing_asset "cross exchange→bank intro" "$BANK_PUBLIC/intro/" + _cx_ok=0 + _cx_fail=0 + _cx_detail="" + for pair in \ + "bank→merchant|$MERCHANT_PUBLIC/intro/" \ + "bank→exchange|$EXCHANGE_PUBLIC/intro/" \ + "merchant→bank|$BANK_PUBLIC/intro/" \ + "exchange→bank|$BANK_PUBLIC/intro/" + do + _cx_name="${pair%%|*}" + _cx_url="${pair#*|}" + if _landing_probe "$_cx_url"; then + _cx_ok=$((_cx_ok + 1)) + else + _cx_fail=$((_cx_fail + 1)) + _cx_detail="${_cx_detail}${_cx_detail:+; }${_cx_name} HTTP ${_landing_code}" + fi + done + if [ "$_cx_fail" -eq 0 ]; then + ok "landing cross-links" "${_cx_ok}/4 intros reachable" + else + fail "landing cross-links" "${_cx_ok}/4 ok · ${_cx_detail}" + fi fi -# Bank-only: shared-pool withdraw mint + static withdraw files + shop assets +# Bank withdraw mint + shop assets — compact if [ "${LOCAL_STACK:-1}" = "1" ] || [ -n "${BANK_PUBLIC:-}" ]; then - check_landing_asset "bank shop-pay.js" "$BANK_PUBLIC/intro/shop-pay.js" 1 - check_landing_asset "bank shop-pay.css" "$BANK_PUBLIC/intro/shop-pay.css" 1 + _ba_ok=0 + _ba_soft=0 + _ba_msg="" + if _landing_probe "$BANK_PUBLIC/intro/shop-pay.js"; then _ba_ok=$((_ba_ok + 1)); else _ba_soft=$((_ba_soft + 1)); _ba_msg="${_ba_msg}shop-pay.js; "; fi + if _landing_probe "$BANK_PUBLIC/intro/shop-pay.css"; then _ba_ok=$((_ba_ok + 1)); else _ba_soft=$((_ba_soft + 1)); _ba_msg="${_ba_msg}shop-pay.css; "; fi dw_code=$(http_body "$BANK_PUBLIC/intro/demo-withdraw.json" "$tmp/dw.json") case "$dw_code" in 200) @@ -606,31 +817,44 @@ print(u[:88]) sys.exit(0) PY then - ok "bank /intro/demo-withdraw.json" "$(python3 -c 'import json;print(json.load(open("'"$tmp/dw.json"'")).get("taler_withdraw_uri","")[:80])' 2>/dev/null || true)" + _ba_ok=$((_ba_ok + 1)) wid=$(python3 -c 'import json;print(json.load(open("'"$tmp/dw.json"'")).get("withdrawal_id",""))' 2>/dev/null || true) if [ -n "$wid" ]; then - check_landing_asset "bank taler-integration withdraw op" \ - "$BANK_PUBLIC/taler-integration/withdrawal-operation/${wid}" + if _landing_probe "$BANK_PUBLIC/taler-integration/withdrawal-operation/${wid}"; then + _ba_ok=$((_ba_ok + 1)) + else + fail "landing bank withdraw-op" "HTTP ${_landing_code} · id=$wid" + fi fi + ok "landing bank withdraw/shop" "demo-withdraw + shop assets ok (${_ba_ok} checks)" else - fail "bank /intro/demo-withdraw.json" "invalid taler://withdraw shape" + fail "landing bank demo-withdraw" "invalid taler://withdraw shape" fi ;; 405|501|404|502|503|000) - fail "bank /intro/demo-withdraw.json" "HTTP $dw_code (want 200)" + fail "landing bank demo-withdraw" "HTTP $dw_code (want 200)" ;; *) if [ "${LOCAL_STACK:-1}" = "1" ]; then - fail "bank /intro/demo-withdraw.json" "HTTP $dw_code want 200" + fail "landing bank demo-withdraw" "HTTP $dw_code want 200" else - warn "bank /intro/demo-withdraw.json" "HTTP $dw_code" + warn "landing bank demo-withdraw" "HTTP $dw_code" fi ;; esac + if [ "$_ba_soft" -gt 0 ]; then + warn "landing bank shop assets" "${_ba_msg}soft-missing" + fi fi -# Merchant landing shop assets -check_landing_asset "merchant shop-pay.js" "$MERCHANT_PUBLIC/intro/shop-pay.js" 1 -check_landing_asset "merchant shop-pay.css" "$MERCHANT_PUBLIC/intro/shop-pay.css" 1 +# Merchant shop assets — one soft line +_ma=0 +_landing_probe "$MERCHANT_PUBLIC/intro/shop-pay.js" && _ma=$((_ma + 1)) +_landing_probe "$MERCHANT_PUBLIC/intro/shop-pay.css" && _ma=$((_ma + 1)) +if [ "$_ma" -eq 2 ]; then + ok "landing merchant shop assets" "shop-pay.js + .css" +else + warn "landing merchant shop assets" "${_ma}/2 present (soft)" +fi summary From 4c28af91e1a14faa1894ba69701492e3a5282df4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hern=C3=A2ni=20Marques?= Date: Fri, 17 Jul 2026 11:04:05 +0200 Subject: [PATCH 3/8] monitoring: ATM 4200 ladder + paivana e2e --- scripts/taler-monitoring/check_e2e.sh | 117 ++++++++++++++++++++++++-- 1 file changed, 111 insertions(+), 6 deletions(-) diff --git a/scripts/taler-monitoring/check_e2e.sh b/scripts/taler-monitoring/check_e2e.sh index 2cbf8c4..5566039 100755 --- a/scripts/taler-monitoring/check_e2e.sh +++ b/scripts/taler-monitoring/check_e2e.sh @@ -205,14 +205,14 @@ except Exception: print("")' 2>/dev/null || true) # E2E_ATM_MAX=N # try at most N ATM withdraws (budget) # --------------------------------------------------------------------------- : "${E2E_VARIABLE:=1}" -: "${E2E_ATM_MAX:=4}" +: "${E2E_ATM_MAX:=5}" if [ "$E2E_REMOTE" = "1" ]; then # remote: still ATM-shaped but smaller notes; keep cheap : "${E2E_WITHDRAW_VALUES:=10 20 50}" : "${E2E_PAY_VALUES:=0.01 0.05 0.1 1}" else - # local GOA: classic ATM denominations - : "${E2E_WITHDRAW_VALUES:=20 50 100 200}" + # local GOA: classic ATM notes + 4200 for paivana paywall template + : "${E2E_WITHDRAW_VALUES:=20 50 100 200 4200}" : "${E2E_PAY_VALUES:=0.01 0.05 0.1 0.5 1 2 5 10}" fi @@ -264,12 +264,24 @@ PAY_AMT=$(printf '%s' "$PAY_LIST" | awk '{print $1}') if [ "$E2E_VARIABLE" = "1" ]; then n_w=$(printf '%s' "$WITHDRAW_LIST" | wc -w | tr -d ' ') n_p=$(printf '%s' "$PAY_LIST" | wc -w | tr -d ' ') - need=$(( 40 + n_w * 35 + n_p * 20 )) + need=$(( 50 + n_w * 40 + n_p * 20 + 60 )) # + room for paivana pay if [ "${E2E_TIMEOUT}" -lt "$need" ]; then E2E_TIMEOUT=$need fi - # cap ATM attempts - WITHDRAW_LIST=$(printf '%s' "$WITHDRAW_LIST" | tr ' ' '\n' | head -n "$E2E_ATM_MAX" | tr '\n' ' ' | sed 's/ *$//') + # cap ATM attempts but always keep CUR:4200 if listed (paivana) + WITHDRAW_LIST=$( + printf '%s' "$WITHDRAW_LIST" | tr ' ' '\n' | awk -v max="$E2E_ATM_MAX" -v big="${CUR}:4200" ' + NF==0 { next } + $0==big { has=1; next } + { small[++n]=$0 } + END { + keep = max - (has ? 1 : 0) + if (keep < 0) keep = 0 + for (i = 1; i <= n && i <= keep; i++) printf "%s ", small[i] + if (has) printf "%s", big + print "" + }' | sed 's/ *$//' + ) fi BANK_HOST=$(python3 -c 'from urllib.parse import urlparse; print(urlparse("'"$BANK_PUBLIC"'").hostname or "bank")' 2>/dev/null || echo bank) @@ -1196,11 +1208,104 @@ EOF fi fi +# --------------------------------------------------------------------------- +section "e2e · paivana paywall (template GOA:4200)" +# --------------------------------------------------------------------------- +# Public paywall: https://paivana.hacktivism.ch → 302 to merchant template "paivana". +# Requires spendable ≥ 4200 GOA (ATM ladder includes GOA:4200 by default). +: "${PAIVANA_PUBLIC:=https://paivana.hacktivism.ch}" +: "${E2E_PAIVANA:=1}" +: "${E2E_PAIVANA_TEMPLATE:=paivana}" +: "${E2E_PAIVANA_AMOUNT:=GOA:4200}" +: "${E2E_PAIVANA_INSTANCE:=goa-shop}" +PAIVANA_OK=0 +PAIVANA_REPORT="" +if [ "$E2E_REMOTE" = "1" ] || [ "${CUR:-}" != "GOA" ] || [ "${E2E_PAIVANA}" = "0" ]; then + info "paivana" "SKIPPED (remote, non-GOA, or E2E_PAIVANA=0)" + PAIVANA_REPORT="SKIP" +else + PAIVANA_PUBLIC="${PAIVANA_PUBLIC%/}" + # HTTP: paywall front must redirect into template flow (or serve well-known) + hdr=$(curl -skS -m 12 -D - -o /dev/null "$PAIVANA_PUBLIC/" 2>/dev/null || true) + pcode=$(printf '%s' "$hdr" | awk 'BEGIN{c="000"} /^HTTP/{c=$2} END{print c}') + loc=$(printf '%s' "$hdr" | awk 'BEGIN{IGNORECASE=1} /^location:/{sub(/\r$/,""); sub(/^location:[[:space:]]*/,""); print; exit}') + case "$pcode" in + 301|302|303|307|308) + if printf '%s' "$loc" | grep -qiE 'paivana|templates|well-known'; then + ok "paivana HTTP" "HTTP $pcode → ${loc:0:120}" + else + ok "paivana HTTP" "HTTP $pcode redirect (Location: ${loc:0:80})" + fi + ;; + 200) + warn "paivana HTTP" "HTTP 200 without paywall redirect — unexpected for locked site" + ;; + *) + fail "paivana HTTP" "HTTP ${pcode:-000} on $PAIVANA_PUBLIC/ (want 302 to template)" + PAIVANA_REPORT="HTTP_FAIL" + ;; + esac + + # Ensure wallet can cover 4200 (extra ATM if ladder did not fund enough) + pay_need=$(python3 -c 'import sys; print(float(sys.argv[1].split(":",1)[-1]))' "$E2E_PAIVANA_AMOUNT" 2>/dev/null || echo 4200) + av_now=$(wallet_avail_num) + if ! python3 -c "import sys; sys.exit(0 if float(sys.argv[1]) + 1e-9 >= float(sys.argv[2]) else 1)" "$av_now" "$pay_need" 2>/dev/null; then + info "paivana" "avail ${CUR}:${av_now} < ${pay_need} — ATM withdraw ${E2E_PAIVANA_AMOUNT}" + e2e_over && warn "paivana" "budget low before 4200 withdraw" + set +e + e2e_one_withdraw "$E2E_PAIVANA_AMOUNT" + wc=$? + set -e + if [ "$wc" = "0" ]; then + WITHDRAW_OK=1 + WITHDRAW_OK_N=$((WITHDRAW_OK_N + 1)) + WITHDRAW_REPORT="${WITHDRAW_REPORT}${WITHDRAW_REPORT:+ }${E2E_PAIVANA_AMOUNT}=OK(paivana)" + elif [ "$wc" = "2" ]; then + WITHDRAW_LAG_N=$((WITHDRAW_LAG_N + 1)) + WITHDRAW_REPORT="${WITHDRAW_REPORT}${WITHDRAW_REPORT:+ }${E2E_PAIVANA_AMOUNT}=LAG(paivana)" + else + WITHDRAW_FAIL_N=$((WITHDRAW_FAIL_N + 1)) + WITHDRAW_REPORT="${WITHDRAW_REPORT}${WITHDRAW_REPORT:+ }${E2E_PAIVANA_AMOUNT}=FAIL(paivana)" + fi + wait_wallet_balance "$(python3 -c "print(max(0, $pay_need - 1))" 2>/dev/null || echo 4199)" 12 3 || true + av_now=$(wallet_avail_num) + fi + + if ! python3 -c "import sys; sys.exit(0 if float(sys.argv[1]) + 1e-9 >= float(sys.argv[2]) else 1)" "$av_now" "$pay_need" 2>/dev/null; then + warn "paivana" "skip pay — insufficient avail ${CUR}:${av_now} (need ${pay_need})" + PAIVANA_REPORT="${PAIVANA_REPORT:+$PAIVANA_REPORT+}SKIP(bal)" + else + _E2E_INST_SAVE="$INST" + INST="${E2E_PAIVANA_INSTANCE}" + set +e + e2e_one_pay_public_template \ + "${E2E_PAIVANA_TEMPLATE}" \ + "Paivana paywall" \ + "${E2E_PAIVANA_AMOUNT}" + pc=$? + set -e + INST="$_E2E_INST_SAVE" + unset _E2E_INST_SAVE + if [ "$pc" = "0" ]; then + PAIVANA_OK=1 + PAY_OK=1 + PAY_OK_N=$((PAY_OK_N + 1)) + PAIVANA_REPORT="${PAIVANA_REPORT:+$PAIVANA_REPORT+}PAY_OK" + ok "paivana" "template ${E2E_PAIVANA_TEMPLATE} paid ${E2E_PAIVANA_AMOUNT}" + else + PAIVANA_REPORT="${PAIVANA_REPORT:+$PAIVANA_REPORT+}PAY_FAIL" + warn "paivana" "template pay failed (${E2E_PAIVANA_TEMPLATE} · ${E2E_PAIVANA_AMOUNT})" + fi + fi + info "paivana summary" "${PAIVANA_REPORT:-?} · instance ${E2E_PAIVANA_INSTANCE}" +fi + section "e2e · report" info "user" "$USER" info "ATM withdraws" "$WITHDRAW_REPORT" info "payments" "$PAY_REPORT" info "goa-shop" "${SHOP_REPORT:-(n/a)}" +info "paivana" "${PAIVANA_REPORT:-(n/a)}" info "final balance" "$(fmt_bal "$SCRATCH/bal-live.out" 2>/dev/null || echo "(n/a)")" info "scratch" "$SCRATCH" # Success if we had coins and at least one pay, OR coins + only pay skips (nothing affordable) From cd3cb1c0544f638108c52aef35393f657de47b12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hern=C3=A2ni=20Marques?= Date: Fri, 17 Jul 2026 11:42:41 +0200 Subject: [PATCH 4/8] docs: GOA deb upgrades + monitoring notes --- 2026/2026-07-16--taler-package-upgrade-goa.md | 145 ++++++++++++++++++ scripts/README.md | 5 + scripts/taler-monitoring/README.md | 68 +++++++- scripts/taler-monitoring/TESTS.md | 11 +- scripts/taler-shared/upgrade-goa-debs.sh | 126 +++++++++++++++ 5 files changed, 345 insertions(+), 10 deletions(-) create mode 100644 2026/2026-07-16--taler-package-upgrade-goa.md create mode 100755 scripts/taler-shared/upgrade-goa-debs.sh diff --git a/2026/2026-07-16--taler-package-upgrade-goa.md b/2026/2026-07-16--taler-package-upgrade-goa.md new file mode 100644 index 0000000..91a412f --- /dev/null +++ b/2026/2026-07-16--taler-package-upgrade-goa.md @@ -0,0 +1,145 @@ +# 2026-07-16 — GOA container package upgrade (trixie debs) + +## Why + +`taler-monitoring.sh versions` reported packages **behind** `deb.taler.net` **trixie**: + +| Component | Package | Was | Target (trixie index) | +|-----------|---------|-----|------------------------| +| bank | libeufin-bank / common | 1.6.6 | **1.6.7** | +| exchange | taler-exchange* / libtalerexchange | 1.6.6 | **1.6.7~dev2** | +| merchant | taler-merchant-webui | 1.6.9 | **1.6.11** | +| merchant | taler-merchant / libtalermerchant | 1.6.9 | still 1.6.9 (no newer in suite for those) | + +Host is openSUSE Tumbleweed — **irrelevant** for these versions. Debs live **inside** Debian-based podman images. + +## Containers + +| Role | Name | +|------|------| +| bank | `taler-hacktivism-bank` | +| exchange | `taler-hacktivism-exchange-ansible` | +| merchant | `taler-hacktivism` | + +**No systemd as PID 1** in these containers. Do **not** rely on `systemctl restart` after apt (policy-rc.d / no bus). Restart with: + +```bash +# bank +podman exec -u root taler-hacktivism-bank \ + runuser -u libeufin-bank -- /usr/local/bin/start_bank.sh --restart + +# merchant +podman exec -u root taler-hacktivism \ + runuser -u taler-merchant-httpd -- /usr/local/bin/start_merchant.sh --restart + +# exchange: base (root) then start_exchange as httpd user +podman exec -u root taler-hacktivism-exchange-ansible \ + bash -c '/root/start_base_services_for_taler_exchange.sh --no-shell 2>/dev/null; \ + runuser -u taler-exchange-httpd -- /usr/local/bin/start_exchange.sh --restart' +``` + +## Procedure (manual) + +```bash +# as root inside each container (example bank) +export DEBIAN_FRONTEND=noninteractive +apt-get update -qq +apt-get install -y libeufin-bank libeufin-common # bank +# exchange: taler-exchange taler-exchange-database libtalerexchange … +# merchant: taler-merchant-webui (and stack as needed) +``` + +Then restart with `start_*.sh` as above. + +**Scripted (host):** `scripts/taler-shared/upgrade-goa-debs.sh` +Copy to koopa or run from a checkout: `./upgrade-goa-debs.sh` / `bank|exchange|merchant`. + +## Lessons from 2026-07-16 run + +1. **`apt-get install` as root works** (`podman exec -u root`); suite `trixie` (+ merchant may also have `trixie-testing`). +2. **`taler-exchange-dbinit` as root fails** (`role "root" does not exist`) — run as `taler-exchange-httpd` if needed. +3. **Bank after upgrade:** if postgres socket was down, bank dies with pool init error; `pg_ctlcluster 17 main start` then `start_bank.sh --restart`. +4. **Merchant `taler-merchant-dbinit` / merchant-0041.sql** can noise-fail (psql cluster path); after `start_merchant.sh --restart`, health check can still be green — verify `https://127.0.0.1:9010/config` and public `taler.hacktivism.ch`. +5. Exchange **postinst** may warn about missing SPA files under `/usr/share/taler-exchange/{aml,kyc}-spa/`; packages `taler-exchange-aml-webui` / `kyc-webui` pull in SPAs — re-check if AML UI is used. +6. **Images are live-writable** (not immutable rebuild): upgraded debs are in the running container layers until next image rebuild/snapshot. + +## Smoke after upgrade + +```bash +curl -sS -o /dev/null -w "%{http_code}\n" http://127.0.0.1:9012/config +curl -sS -o /dev/null -w "%{http_code}\n" http://127.0.0.1:9011/config +curl -skS -o /dev/null -w "%{http_code}\n" https://127.0.0.1:9010/config +# public +curl -skS -o /dev/null -w "%{http_code}\n" https://bank.hacktivism.ch/config +curl -skS -o /dev/null -w "%{http_code}\n" https://exchange.hacktivism.ch/config +curl -skS -o /dev/null -w "%{http_code}\n" https://taler.hacktivism.ch/config +``` + +Laptop: `./scripts/taler-monitoring/taler-monitoring.sh versions` (or `urls`). + +## Result (this day) + +- bank **1.6.7**, exchange **1.6.7~dev2**, merchant-webui **1.6.11** +- public `/config` **200** for bank / exchange / merchant after restarts +- `taler-monitoring.sh -d hacktivism.ch versions` → **behind=0** (all package rows OK) + +## Specific residual problems (re-checked after upgrade) + +Severity: **P1** = breaks settlement/ops soon · **P2** = noise / tooling · **P3** = hygiene. + +### P1 — none known after restarts + +Settlement path: bank/exchange/merchant `/config` 200; merchant health can pass including wirewatch (see below). + +### P2 — `taler-merchant-dbinit` fails when run by hand + +```text +WARNING Could not run PSQL on file …/global_procedures.sql: psql exit code was 1 +ERROR Failed to initialize tables +``` + +- **Schema is actually current:** `_v.patches` includes **`merchant-0041`** (applied 2026-07-16, by role `postgres` during upgrade noise). +- **Service works:** `check_merchant-health.sh` → ALL CRITICAL CHECKS PASSED when helpers are up. +- **Cause (likely):** service-user `psql` / cluster path (`Error: Invalid data directory for cluster 17 main` when run poorly), not missing migrations. +- **Action:** do not treat hand-run `dbinit` exit≠0 as deploy failure; verify `_v.patches` + `/config`. Optional follow-up: fix `pg_wrapper`/`.postgresqlrc` for `taler-merchant-httpd` so dbinit is clean. + +### P2 — merchant health vs wirewatch (race / detect) + +- `check_merchant-health.sh` uses `pgrep` for `taler-merchant-wirewatch`. +- Wirewatch is supervised by **`taler-merchant-wirewatch-supervise.sh`** (restarts on PG NOTIFY exit). +- During restart windows the helper can be absent for seconds → **false FAIL**. +- When wirewatch + supervise are both live, health reports **OK**. +- **Action:** re-run health after 5s; ensure supervise is started after package upgrades (start_merchant / ensure_helpers). + +### P2 — `check_exchange-health.sh` missing in live exchange container + +```text +exec: "/usr/local/bin/check_exchange-health.sh": no such file or directory +``` + +- Script exists in **admin-log** (`scripts/taler-exchange/check_exchange-health.sh`) but was **not installed** into `taler-hacktivism-exchange-ansible`. +- **Action:** copy into image/live `/usr/local/bin/` on next deploy (same as bank/merchant health scripts). + +### P3 — zombie processes inside bank + merchant containers + +- **bank:** defunct `java`, `python3`, occasional `postgres` / `dpkg-preconfigu`. +- **merchant:** many old `taler-merchant-*` defunct (pre-restart leftovers). +- **Cause:** no proper init/reaper (not systemd PID 1); supervise/start scripts leave zombies. +- **Impact:** mostly cosmetic / PID table clutter unless extreme. +- **Action:** periodic container restart or install a tiny reaper; not urgent. + +### P3 — package skew (informational) + +| Package | Installed | Note | +|---------|-----------|------| +| taler-merchant / libtalermerchant / typst | 1.6.9 | no newer in trixie index at check time | +| taler-merchant-webui | 1.6.11 | intentionally newer SPA | +| exchange aml/kyc webui | 1.6.8~dev3 | pulled with exchange upgrade | + +Monitoring does **not** ERROR on this skew when suite index matches installed. + +### Resolved during upgrade (do not re-open without evidence) + +- Version **behind** bank/exchange/webui — fixed. +- Exchange postinst “missing aml-spa/forms.json” — paths **`/usr/share/taler-exchange/{aml,kyc}-spa`** now present after webui packages. +- Bank down after apt — fixed with postgres socket + `start_bank.sh --restart`. diff --git a/scripts/README.md b/scripts/README.md index 34ef2bc..341260a 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -7,6 +7,7 @@ | `taler-hacktivism-bank/` | podman `taler-hacktivism-bank`: `/root`, `/usr/local/bin` | | `taler-sanity/` | host root checks (stack, settlement, helpers) | | `taler-monitoring/` | **outside-in** public URL walk (`/config` → keys/terms/integration/webui) | +| `taler-shared/` | shared helpers (`upgrade-goa-debs.sh` = apt upgrade **inside** bank/exchange/merchant containers) | | `monitoring/` | host `/home/hernani/scripts` (tor relay stats) | | `taler-wallet-cli/` | thin wrappers; **benchmarks live in** `../benchmarks/` | | `castopod/` | host `hernani` podman-compose `~/koopa-castopod` — see `castopod/README.md` | @@ -44,4 +45,8 @@ Exchange one-shots (root, offline / wire): `runuser -u USER -- bash` (never `-u` with `-s` on util-linux). +### Package upgrades (trixie debs inside containers) + +Containers are **not** systemd. After `apt-get install` of Taler/libeufin packages, restart with `start_*.sh` only — see **`taler-shared/upgrade-goa-debs.sh`** and day note `2026/2026-07-16--taler-package-upgrade-goa.md`. Host `zypper` does not update these. + SMS helper symlinks into `/var/taler-src/...` are not copied (merchant only). diff --git a/scripts/taler-monitoring/README.md b/scripts/taler-monitoring/README.md index 35edc67..170bf46 100644 --- a/scripts/taler-monitoring/README.md +++ b/scripts/taler-monitoring/README.md @@ -33,16 +33,67 @@ End of each phase: totals + list of **BLOCKERS** and **ERRORS**. # Other domains — public HTTPS only, never SSH ./taler-monitoring.sh -d taler.net ./taler-monitoring.sh --domain taler-ops.ch +./taler-monitoring.sh -d my.taler-ops.ch urls ./taler-monitoring.sh -d demo.taler.net urls ./taler-monitoring.sh taler.net # bare domain = same as -d + +# Explicit endpoints (any mix; override profile or skip -d) +./taler-monitoring.sh --bank https://bank.demo.taler.net \ + --exchange https://exchange.demo.taler.net \ + --merchant https://backend.demo.taler.net --currency KUDOS urls ``` -| Domain | Bank | Exchange | Merchant | Currency | -|--------|------|----------|----------|----------| -| `hacktivism.ch` (default) | bank.hacktivism.ch | exchange.hacktivism.ch | taler.hacktivism.ch | GOA | +## Domain profiles (`domains.conf`) + +Each monitored stack must say **what is bank, exchange, and merchant-backend**. + +Edit **[domains.conf](./domains.conf)** (or set `TALER_DOMAINS_CONF=`): + +```text +# name bank exchange merchant-backend currency local [canonical] +taler-ops.ch bank.taler-ops.ch exchange.taler-ops.ch my.taler-ops.ch CHF 0 +``` + +| Field | Meaning | +|-------|---------| +| **name** | `-d NAME` / `TALER_DOMAIN` match (add alias lines as needed) | +| **bank** | libeufin/bank public host or `https://…` | +| **exchange** | exchange public host or URL | +| **merchant** | merchant **backend** (SPA/API), e.g. `my.taler-ops.ch` | +| **currency** | `GOA` / `KUDOS` / `CHF` / … | +| **local** | `1` = koopa SSH stack; `0` = public only | +| **landing** | `1` = check `/intro` landings + assets; `0` = skip (TOPS / mytops) | +| **canonical** | optional label after alias (e.g. `my.taler-ops.ch` → `taler-ops.ch`) | + +Then: + +```bash +./taler-monitoring.sh -d taler-ops.ch urls +``` + +Overrides (always win after profile load): + +```bash +./taler-monitoring.sh -d taler-ops.ch \ + --merchant https://my.taler-ops.ch \ + --exchange https://exchange.taler-ops.ch \ + --bank https://bank.taler-ops.ch urls +``` + +Helpers in `lib.sh`: `set_taler_stack`, `load_domain_profile`, `apply_taler_domain`. + +### Built-in profiles (see `domains.conf`) + +| Domain / alias | Bank | Exchange | Merchant | Currency | +|----------------|------|----------|----------|----------| +| `hacktivism.ch` / `koopa` | bank.hacktivism.ch | exchange.hacktivism.ch | taler.hacktivism.ch | GOA | | `taler.net` / `demo.taler.net` | bank.demo.taler.net | exchange.demo.taler.net | backend.demo.taler.net | KUDOS | -| `taler-ops.ch` | bank.* (probe) | exchange.taler-ops.ch | backend.* (probe) | CHF | -| other | bank.DOMAIN | exchange.DOMAIN | backend/taler/merchant (probe) | any | +| `test.taler.net` | bank.test… | exchange.test… | backend.test… | **TESTKUDOS** · 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** | +| 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. **SSH only for koopa** (`hacktivism.ch` / `-d koopa`). @@ -52,8 +103,9 @@ Other domains: never SSH. Optional **e2e** aborts cleanly on login/KYC. | | Local (koopa) | Remote | |--|---------------|--------| -| **ATM withdraw** | 20 · 50 · 100 · 200 | 10 · 20 · 50 | +| **ATM withdraw** | 20 · 50 · 100 · 200 · **4200** (paivana) | 10 · 20 · 50 | | **Pay ladder** | 0.01 … 10 | 0.01 … 1 | +| **Paivana** | HTTP paywall + template pay **GOA:4200** (`goa-shop` / `paivana`) | skipped | ```bash ./taler-monitoring.sh e2e @@ -66,6 +118,10 @@ E2E_VARIABLE=0 WITHDRAW_AMT=GOA:50 PAY_AMT=GOA:1 ./taler-monitoring.sh e2e # s # E2E_SHOP_PRODUCTS lines: id|Product name|GOA:amount # E2E_SHOP_PICK_N=2 # (landing QR = taler://pay-template/…/{id}; popup = live taler://pay after POST templates/{id}) +# Paivana paywall (local GOA only): +# E2E_PAIVANA=1 (default) PAIVANA_PUBLIC=https://paivana.hacktivism.ch +# E2E_PAIVANA_TEMPLATE=paivana E2E_PAIVANA_AMOUNT=GOA:4200 E2E_PAIVANA_INSTANCE=goa-shop +# E2E_PAIVANA=0 # skip # remote secrets: # E2E_BANK_ADMIN_PASS=… E2E_MERCHANT_TOKEN=… ``` diff --git a/scripts/taler-monitoring/TESTS.md b/scripts/taler-monitoring/TESTS.md index 51686d4..2774dac 100644 --- a/scripts/taler-monitoring/TESTS.md +++ b/scripts/taler-monitoring/TESTS.md @@ -43,12 +43,13 @@ IDs are assigned **in run order** within the area (`set_area` resets the counter | www-… | **merchant `/terms`** body (dual-currency notice) | | www-… | **merchant `/privacy`** body (must not be `not configured`) | | www-… | merchant `/terms/` redirect | -| www-… | **landing exposed links** (bank / merchant / exchange): parse each `/intro/` HTML, probe every own-stack `https://` + root-relative `href`/`src`/`content`, soft-check external stores/docs | -| www-… | landing static: `qrcode.min.js`, `og-goa-shop.png`, `qr-logo.png`, shop-pay.js/css | -| www-… | cross-links between bank ↔ merchant ↔ exchange intros (local stack) | +| www-… | **landing (aggregated per site)**: one line for bank/merchant/exchange (`/intro` + assets + own/external link counts); failures listed as samples; soft external/shop assets compact | +| www-… | cross-links: one line (4 intros) on local stack | +| www-… | bank demo-withdraw + shop assets: one line (+ soft shop-pay if missing) | | www-… | **bank `/intro/demo-withdraw.json`** → `taler://withdraw/HOST:PORT/taler-integration/…` + integration op HTTP 200 | | www-… | bank `/intro/auto-account.json` (earlier) → same withdraw shape, **no payto_uri**, login at `/webui/` | -| www-… | **performance** (outside-in): public HTTPS RTT for bank `/config`, `/taler-integration/config`, `/webui/`, `/intro/`, `stats.json`; exchange `/config`, `/keys`, `/intro/`; merchant `/config`, `/webui/`, `/intro/` — report ms; WARN ≥ `PERF_WARN_MS` (default 8000); **ERROR ≥ `PERF_FAIL_MS` (default 20000)** | +| www-… | **performance** (outside-in RTT): bank/exchange/merchant `/config`, keys, webui, intro — ms; WARN ≥ `PERF_WARN_MS` (8000); ERROR ≥ `PERF_FAIL_MS` (20000) | +| www-… | **landing load stats**: `/intro/stats.json` → loadavg, container RSS, in-container probe ms (public; SSH container cat fallback); warn if stale/missing | **Legal docs rule:** HTTP 200, non-empty body, not plain `not configured`, not merchant API JSON `code:21`. On local stack, optional content needle (terms/privacy/FADP/GOA…). @@ -135,6 +136,8 @@ Without SSH (`SKIP_SSH=1` or remote domain): still runs outside-in repo checks; ## e2e — payment path (`./taler-monitoring.sh e2e`) +Local GOA also: **ATM includes GOA:4200**, then **paivana** (HTTP 302 on `PAIVANA_PUBLIC` + public template pay `paivana` / GOA:4200 on `goa-shop`). Disable with `E2E_PAIVANA=0`. + | ID | Step (approx.) | |----|----------------| | e2e-001 | budget info | diff --git a/scripts/taler-shared/upgrade-goa-debs.sh b/scripts/taler-shared/upgrade-goa-debs.sh new file mode 100755 index 0000000..0022510 --- /dev/null +++ b/scripts/taler-shared/upgrade-goa-debs.sh @@ -0,0 +1,126 @@ +#!/usr/bin/env bash +# Upgrade GNU Taler .deb packages *inside* the three GOA containers on koopa. +# Host openSUSE packages are unrelated (zypper) — do not confuse with this. +# +# Containers (no systemd as PID 1): after apt, restart with start_*.sh only. +# +# Run on koopa host as a user that can `podman exec` the containers: +# ./upgrade-goa-debs.sh +# ./upgrade-goa-debs.sh bank|exchange|merchant +# +# Mirror: scripts/taler-shared/ in koopa-admin-log. +# Live note: 2026/2026-07-16--taler-package-upgrade-goa.md +set -euo pipefail + +BANK_CTR="${BANK_CTR:-taler-hacktivism-bank}" +EXCHANGE_CTR="${EXCHANGE_CTR:-taler-hacktivism-exchange-ansible}" +MERCHANT_CTR="${MERCHANT_CTR:-taler-hacktivism}" + +only="${1:-all}" + +pod_root() { + local ctr="$1" + shift + podman exec -u root "$ctr" bash -c "$*" +} + +upgrade_bank() { + echo "======== bank ($BANK_CTR) ========" + pod_root "$BANK_CTR" ' + set -e + export DEBIAN_FRONTEND=noninteractive + apt-get update -qq + apt-get install -y libeufin-bank libeufin-common + dpkg -l libeufin-bank libeufin-common | awk "/^ii/{print \$2,\$3}" + ' + # restart app only (postgres via start_base if needed) + pod_root "$BANK_CTR" ' + set -e + if ! runuser -u postgres -- psql -c "SELECT 1" >/dev/null 2>&1; then + pg_ctlcluster 17 main start || true + sleep 2 + fi + mkdir -p /var/log/libeufin-bank + chown libeufin-bank:libeufin-bank /var/log/libeufin-bank 2>/dev/null || true + runuser -u libeufin-bank -- /usr/local/bin/start_bank.sh --restart + ' +} + +upgrade_exchange() { + echo "======== exchange ($EXCHANGE_CTR) ========" + pod_root "$EXCHANGE_CTR" ' + set -e + export DEBIAN_FRONTEND=noninteractive + apt-get update -qq + apt-get install -y \ + taler-exchange taler-exchange-database taler-exchange-offline \ + taler-exchange-typst libtalerexchange taler-terms-generator \ + taler-exchange-aml-webui taler-exchange-kyc-webui || true + dpkg -l "taler-exchange*" "libtalerexchange" "taler-terms-generator" 2>/dev/null \ + | awk "/^ii/{print \$2,\$3}" + ' + # do not run dbinit as root (role "root" does not exist) + pod_root "$EXCHANGE_CTR" ' + set -e + if id taler-exchange-httpd >/dev/null 2>&1; then + runuser -u taler-exchange-httpd -- taler-exchange-dbinit 2>&1 | tail -20 || true + fi + if [ -x /root/start_base_services_for_taler_exchange.sh ]; then + /root/start_base_services_for_taler_exchange.sh --no-shell 2>/dev/null || true + fi + if [ -x /usr/local/bin/start_exchange.sh ]; then + runuser -u taler-exchange-httpd -- /usr/local/bin/start_exchange.sh --restart 2>&1 | tail -30 || true + fi + pgrep -a taler-exchange-httpd | head -3 || true + ' +} + +upgrade_merchant() { + echo "======== merchant ($MERCHANT_CTR) ========" + pod_root "$MERCHANT_CTR" ' + set -e + export DEBIAN_FRONTEND=noninteractive + apt-get update -qq + apt-get install -y \ + taler-merchant taler-merchant-webui taler-merchant-typst \ + libtalermerchant taler-terms-generator + dpkg -l "taler-merchant*" "libtalermerchant" 2>/dev/null | awk "/^ii/{print \$2,\$3}" + ' + pod_root "$MERCHANT_CTR" ' + set -e + # schema: prefer tools as service user; failures may be noisy — check /config after + if id taler-merchant-httpd >/dev/null 2>&1; then + runuser -u taler-merchant-httpd -- taler-merchant-dbinit 2>&1 | tail -20 || true + fi + runuser -u taler-merchant-httpd -- /usr/local/bin/start_merchant.sh --restart + ' +} + +smoke() { + echo "======== smoke (host loopback) ========" + curl -sS -m 8 -o /dev/null -w "bank %{http_code} :9012/config\n" http://127.0.0.1:9012/config || true + curl -sS -m 8 -o /dev/null -w "exchange %{http_code} :9011/config\n" http://127.0.0.1:9011/config || true + curl -skS -m 8 -o /dev/null -w "merchant %{http_code} :9010/config\n" https://127.0.0.1:9010/config || true +} + +case "$only" in + all) + upgrade_bank + upgrade_exchange + upgrade_merchant + smoke + ;; + bank) upgrade_bank; smoke ;; + exchange) upgrade_exchange; smoke ;; + merchant) upgrade_merchant; smoke ;; + -h|--help|help) + sed -n '2,20p' "$0" + exit 0 + ;; + *) + echo "usage: $0 [all|bank|exchange|merchant]" >&2 + exit 2 + ;; +esac + +echo "DONE — re-run taler-monitoring versions/urls from a laptop if desired." From b9d37f6729f8f4de6c8a161cbcdd2204d9aaab34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hern=C3=A2ni=20Marques?= Date: Fri, 17 Jul 2026 12:32:07 +0200 Subject: [PATCH 5/8] monitoring: find user-local wallet-cli --- scripts/taler-monitoring/check_e2e.sh | 2 +- scripts/taler-monitoring/lib.sh | 23 ++++++++++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/scripts/taler-monitoring/check_e2e.sh b/scripts/taler-monitoring/check_e2e.sh index 5566039..44d1b41 100755 --- a/scripts/taler-monitoring/check_e2e.sh +++ b/scripts/taler-monitoring/check_e2e.sh @@ -288,7 +288,7 @@ BANK_HOST=$(python3 -c 'from urllib.parse import urlparse; print(urlparse("'"$BA SCRATCH=$(mktemp -d) WDB="$SCRATCH/wallet.sqlite3" -export PATH="/opt/homebrew/bin:/usr/local/bin:$PATH" +export PATH="${HOME}/.local/bin:${HOME}/taler/opt/taler-wallet-cli/usr/bin:/opt/homebrew/bin:/usr/local/bin:$PATH" # Always dump balances on any exit (timeout, blocker, signal, success) trap 'ec=$?; e2e_finish "$ec"; rm -rf "$SCRATCH"; exit "$ec"' EXIT diff --git a/scripts/taler-monitoring/lib.sh b/scripts/taler-monitoring/lib.sh index 451d56d..62f3311 100755 --- a/scripts/taler-monitoring/lib.sh +++ b/scripts/taler-monitoring/lib.sh @@ -599,14 +599,35 @@ read_secret() { } find_wallet_cli() { + # Must return a path suitable for: node "$WALLET_CLI" … + # Prefer *.mjs / bundled entry; plain /usr/bin/taler-wallet-cli is often a + # shell wrapper and cannot be passed to node. if [ -n "${WALLET_CLI:-}" ] && [ -f "$WALLET_CLI" ]; then echo "$WALLET_CLI"; return 0 fi + local c cand for c in \ + "${HOME}/.local/bin/taler-wallet-cli.mjs" \ + "${HOME}/taler/opt/taler-wallet-cli/usr/lib/taler-wallet-cli/node_modules/taler-wallet-cli/bin/taler-wallet-cli.mjs" \ + /home/hernani/taler/opt/taler-wallet-cli/usr/lib/taler-wallet-cli/node_modules/taler-wallet-cli/bin/taler-wallet-cli.mjs \ /Users/newkamek/src/taler/taler-typescript-core/packages/taler-wallet-cli/bin/taler-wallet-cli.mjs \ - "$(command -v taler-wallet-cli 2>/dev/null || true)" + /usr/lib/taler-wallet-cli/node_modules/taler-wallet-cli/bin/taler-wallet-cli.mjs \ + /usr/share/taler-wallet-cli/bin/taler-wallet-cli.mjs do [ -n "$c" ] && [ -f "$c" ] && { echo "$c"; return 0; } done + # Debian package wrapper → resolve real .mjs + cand=$(command -v taler-wallet-cli 2>/dev/null || true) + if [ -n "$cand" ] && [ -f "$cand" ]; then + if head -1 "$cand" 2>/dev/null | grep -q 'node\|mjs'; then + # shebang node script or .mjs + echo "$cand"; return 0 + fi + # follow symlink into package tree + if [ -L "$cand" ]; then + c=$(readlink -f "$cand" 2>/dev/null || true) + [ -n "$c" ] && [ -f "$c" ] && { echo "$c"; return 0; } + fi + fi return 1 } From acac6d0ed330eea7b60698b1c856a1c1df0994d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hern=C3=A2ni=20Marques?= Date: Fri, 17 Jul 2026 13:18:40 +0200 Subject: [PATCH 6/8] monitoring: wallet-cli version in e2e --- scripts/taler-monitoring/check_e2e.sh | 36 ++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/scripts/taler-monitoring/check_e2e.sh b/scripts/taler-monitoring/check_e2e.sh index 44d1b41..25cde8b 100755 --- a/scripts/taler-monitoring/check_e2e.sh +++ b/scripts/taler-monitoring/check_e2e.sh @@ -317,7 +317,41 @@ WALLET_CLI=$(find_wallet_cli) || { blocker "prereq" "taler-wallet-cli not found (set WALLET_CLI=)" exit 1 } -ok "wallet-cli ($WALLET_CLI)" +# Implementation version (--version) + wallet-core API ranges (version command) +WALLET_IMPL_VER=$( + node "$WALLET_CLI" --version 2>/dev/null | head -1 | tr -d '\r' || true +) +WALLET_CORE_VER=$( + _wver_db=$(mktemp "${TMPDIR:-/tmp}/wver.XXXXXX.sqlite3" 2>/dev/null || echo "${TMPDIR:-/tmp}/wver-$$.sqlite3") + node "$WALLET_CLI" --wallet-db="$_wver_db" --no-throttle --skip-defaults version 2>/dev/null \ + | python3 -c ' +import json,re,sys +raw=sys.stdin.read() +d=None +for m in re.finditer(r"\{", raw): + try: + d=json.loads(raw[m.start():]) + if isinstance(d, dict) and "version" in d: + break + except Exception: + d=None +if not isinstance(d, dict): + sys.exit(0) +parts=[] +# wallet-core libversion + protocol ranges used against exchange/merchant/bank +for k in ("version","implementationSemver","exchange","merchant","bank","bankIntegrationApiRange","corebankApiRange"): + if k in d and d[k] is not None: + parts.append("%s=%s" % (k, d[k])) +print(" ".join(parts)) +' 2>/dev/null || true + rm -f "$_wver_db" "$_wver_db"-* 2>/dev/null || true +) +ok "wallet-cli" "${WALLET_IMPL_VER:-?} · ${WALLET_CLI}" +if [ -n "${WALLET_CORE_VER:-}" ]; then + info "wallet-core" "$WALLET_CORE_VER" +else + info "wallet-core" "version details unavailable (impl ${WALLET_IMPL_VER:-?})" +fi info "e2e mode" "$([ "$E2E_REMOTE" = "1" ] && echo "remote/public domain (no SSH)" || echo "local koopa stack")" info "currency" "$CUR" info "ATM withdraw ladder" "$WITHDRAW_LIST (credit $CREDIT_AMT)" From b33b3265a5984cd3fdafcd469a40af262f053bb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hern=C3=A2ni=20Marques?= Date: Fri, 17 Jul 2026 13:59:47 +0200 Subject: [PATCH 7/8] monitoring: lefrancpaysan monnaie profile --- scripts/taler-monitoring/README.md | 1 + scripts/taler-monitoring/domains.conf | 7 +++++++ scripts/taler-monitoring/lib.sh | 11 ++++++++--- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/scripts/taler-monitoring/README.md b/scripts/taler-monitoring/README.md index 170bf46..75c21cf 100644 --- a/scripts/taler-monitoring/README.md +++ b/scripts/taler-monitoring/README.md @@ -89,6 +89,7 @@ Helpers in `lib.sh`: `set_taler_stack`, `load_domain_profile`, `apply_taler_doma | `hacktivism.ch` / `koopa` | bank.hacktivism.ch | exchange.hacktivism.ch | taler.hacktivism.ch | GOA | | `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 | | `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 | diff --git a/scripts/taler-monitoring/domains.conf b/scripts/taler-monitoring/domains.conf index fccc411..d198173 100644 --- a/scripts/taler-monitoring/domains.conf +++ b/scripts/taler-monitoring/domains.conf @@ -30,3 +30,10 @@ taler-ops.ch bank.taler-ops.ch exchange.taler-ops.ch my.taler-ops.ch my.taler-ops.ch bank.taler-ops.ch exchange.taler-ops.ch my.taler-ops.ch CHF 0 0 taler-ops.ch stage.taler-ops.ch bank.stage.taler-ops.ch exchange.stage.taler-ops.ch my.stage.taler-ops.ch CHF 0 0 my.stage.taler-ops.ch bank.stage.taler-ops.ch exchange.stage.taler-ops.ch my.stage.taler-ops.ch CHF 0 0 stage.taler-ops.ch + +# Franc Paysan (Infomaniak) — merchant host is monnaie.* (not taler./backend.) +# No GOA-style /intro landings; currency leave empty → report-only until confirmed +lefrancpaysan.ch bank.lefrancpaysan.ch exchange.lefrancpaysan.ch monnaie.lefrancpaysan.ch - 0 0 +monnaie.lefrancpaysan.ch bank.lefrancpaysan.ch exchange.lefrancpaysan.ch monnaie.lefrancpaysan.ch - 0 0 lefrancpaysan.ch +bank.lefrancpaysan.ch bank.lefrancpaysan.ch exchange.lefrancpaysan.ch monnaie.lefrancpaysan.ch - 0 0 lefrancpaysan.ch +exchange.lefrancpaysan.ch bank.lefrancpaysan.ch exchange.lefrancpaysan.ch monnaie.lefrancpaysan.ch - 0 0 lefrancpaysan.ch diff --git a/scripts/taler-monitoring/lib.sh b/scripts/taler-monitoring/lib.sh index 62f3311..267b00f 100755 --- a/scripts/taler-monitoring/lib.sh +++ b/scripts/taler-monitoring/lib.sh @@ -80,7 +80,12 @@ set_taler_stack() { BANK_PUBLIC=$(_to_https_base "$bank") EXCHANGE_PUBLIC=$(_to_https_base "$exchange") MERCHANT_PUBLIC=$(_to_https_base "$merchant") - [ -n "$currency" ] && EXPECT_CURRENCY="$currency" + # currency "-" or empty → report-only (do not force GOA) + if [ -n "$currency" ] && [ "$currency" != "-" ]; then + EXPECT_CURRENCY="$currency" + elif [ "$currency" = "-" ]; then + EXPECT_CURRENCY="" + fi LOCAL_STACK="$local_stack" if [ "$LOCAL_STACK" = "1" ]; then SKIP_SSH=0 @@ -147,7 +152,7 @@ _normalize_domain() { # e.g. bank.demo.taler.net → demo.taler.net, taler.hacktivism.ch → hacktivism.ch # but NOT taler.net → net case "$d" in - bank.*|exchange.*|taler.*|backend.*|merchant.*|shop.*|libeufin.*|my.*|map.*) + bank.*|exchange.*|taler.*|backend.*|merchant.*|shop.*|libeufin.*|my.*|map.*|monnaie.*) rest="${d#*.}" if [[ "$rest" == *.* ]]; then d="$rest" @@ -206,7 +211,7 @@ apply_taler_domain() { CHECK_LANDING=0 if [ "${TALER_DOMAIN_PROBE}" = "1" ]; then local h - for h in "backend.${d}" "my.${d}" "taler.${d}" "merchant.${d}" "shop.${d}"; do + for h in "monnaie.${d}" "backend.${d}" "my.${d}" "taler.${d}" "merchant.${d}" "shop.${d}"; do _probe_https_config "$h" && { MERCHANT_PUBLIC="https://$h"; break; } done for h in "bank.${d}" "libeufin.${d}"; do From 94c02058b31e6e2c6e57cf226698ce440b540a06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hern=C3=A2ni=20Marques?= Date: Fri, 17 Jul 2026 15:02:34 +0200 Subject: [PATCH 8/8] monitoring: stage TESTPAYSAN profile --- scripts/taler-monitoring/domains.conf | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/scripts/taler-monitoring/domains.conf b/scripts/taler-monitoring/domains.conf index d198173..1cc12af 100644 --- a/scripts/taler-monitoring/domains.conf +++ b/scripts/taler-monitoring/domains.conf @@ -32,8 +32,15 @@ stage.taler-ops.ch bank.stage.taler-ops.ch exchange.stage.taler-ops.ch my.stage. my.stage.taler-ops.ch bank.stage.taler-ops.ch exchange.stage.taler-ops.ch my.stage.taler-ops.ch CHF 0 0 stage.taler-ops.ch # Franc Paysan (Infomaniak) — merchant host is monnaie.* (not taler./backend.) -# No GOA-style /intro landings; currency leave empty → report-only until confirmed +# Prod: no monetary launch yet; stage uses TESTPAYSAN (public HTTPS landings + shops) lefrancpaysan.ch bank.lefrancpaysan.ch exchange.lefrancpaysan.ch monnaie.lefrancpaysan.ch - 0 0 monnaie.lefrancpaysan.ch bank.lefrancpaysan.ch exchange.lefrancpaysan.ch monnaie.lefrancpaysan.ch - 0 0 lefrancpaysan.ch bank.lefrancpaysan.ch bank.lefrancpaysan.ch exchange.lefrancpaysan.ch monnaie.lefrancpaysan.ch - 0 0 lefrancpaysan.ch exchange.lefrancpaysan.ch bank.lefrancpaysan.ch exchange.lefrancpaysan.ch monnaie.lefrancpaysan.ch - 0 0 lefrancpaysan.ch + +# Stage TESTPAYSAN — landings on / + farmer shops under stage.monnaie…/shops/ +# name bank exchange merchant currency local landing [canonical] +stage.lefrancpaysan.ch stage.bank.lefrancpaysan.ch stage.exchange.lefrancpaysan.ch stage.monnaie.lefrancpaysan.ch TESTPAYSAN 0 1 +stage.bank.lefrancpaysan.ch stage.bank.lefrancpaysan.ch stage.exchange.lefrancpaysan.ch stage.monnaie.lefrancpaysan.ch TESTPAYSAN 0 1 stage.lefrancpaysan.ch +stage.exchange.lefrancpaysan.ch stage.bank.lefrancpaysan.ch stage.exchange.lefrancpaysan.ch stage.monnaie.lefrancpaysan.ch TESTPAYSAN 0 1 stage.lefrancpaysan.ch +stage.monnaie.lefrancpaysan.ch stage.bank.lefrancpaysan.ch stage.exchange.lefrancpaysan.ch stage.monnaie.lefrancpaysan.ch TESTPAYSAN 0 1 stage.lefrancpaysan.ch