1002 lines
33 KiB
Bash
Executable file
1002 lines
33 KiB
Bash
Executable file
# shellcheck shell=bash
|
|
# Shared helpers for taler-monitoring (laptop or koopa).
|
|
|
|
# Default stack = GOA / hacktivism (overridden by TALER_DOMAIN / --domain)
|
|
: "${TALER_DOMAIN:=hacktivism.ch}"
|
|
: "${BANK_PUBLIC:=https://bank.hacktivism.ch}"
|
|
: "${EXCHANGE_PUBLIC:=https://exchange.hacktivism.ch}"
|
|
: "${MERCHANT_PUBLIC:=https://taler.hacktivism.ch}"
|
|
: "${BANK_LOCAL:=http://127.0.0.1:9012}"
|
|
: "${EXCHANGE_LOCAL:=http://127.0.0.1:9011}"
|
|
: "${MERCHANT_LOCAL:=https://127.0.0.1:9010}"
|
|
: "${LANDING_LOCAL:=http://127.0.0.1:9013}"
|
|
: "${KOOPA_SSH:=koopa}"
|
|
# When LAN Host "koopa" is unreachable, try WAN DNAT (see ~/.ssh/config Host koopa-external).
|
|
: "${KOOPA_SSH_FALLBACKS:=koopa-external}"
|
|
: "${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:4700}" # covers ATM ladder 20+50+100+200+4200 (paivana)
|
|
: "${TIMEOUT:=12}"
|
|
: "${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}"
|
|
# SSH must never hang the monitoring run
|
|
: "${SSH_CONNECT_TIMEOUT:=3}"
|
|
: "${SSH_CMD_TIMEOUT:=12}" # hard cap for whole remote script (seconds)
|
|
: "${SKIP_SSH:=0}"
|
|
# Expected currency for public /config checks (empty = report only, don't fail)
|
|
: "${EXPECT_CURRENCY:=GOA}"
|
|
# 1 = this is the local koopa/hacktivism stack (inside/e2e/SSH make sense)
|
|
: "${LOCAL_STACK:=1}"
|
|
# Probe merchant host candidates when applying a generic domain (0=off)
|
|
: "${TALER_DOMAIN_PROBE:=1}"
|
|
|
|
BANK_PUBLIC=${BANK_PUBLIC%/}
|
|
EXCHANGE_PUBLIC=${EXCHANGE_PUBLIC%/}
|
|
MERCHANT_PUBLIC=${MERCHANT_PUBLIC%/}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Domain profiles → bank / exchange / merchant base URLs
|
|
#
|
|
# Single place to declare a stack: scripts/taler-monitoring/domains.conf
|
|
# (or TALER_DOMAINS_CONF). Each profile names the three public endpoints.
|
|
#
|
|
# 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")
|
|
# 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
|
|
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://}"
|
|
d="${d#http://}"
|
|
d="${d%%/*}"
|
|
d="${d%%:*}"
|
|
# Strip service host prefix only if a real domain remains (has a dot).
|
|
# 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.*|monnaie.*)
|
|
rest="${d#*.}"
|
|
if [[ "$rest" == *.* ]]; then
|
|
d="$rest"
|
|
fi
|
|
;;
|
|
esac
|
|
printf '%s' "$d"
|
|
}
|
|
|
|
_probe_https_config() {
|
|
# 0 if https://$1/config returns 200
|
|
local host="$1" code
|
|
code=$(curl -skS --max-redirs 0 -m 4 -o /dev/null -w '%{http_code}' "https://${host}/config" 2>/dev/null || echo 000)
|
|
[ "$code" = "200" ]
|
|
}
|
|
|
|
apply_taler_domain() {
|
|
local raw="${1:-}"
|
|
local d
|
|
[ -n "$raw" ] || return 0
|
|
d=$(_normalize_domain "$raw")
|
|
TALER_DOMAIN="$d"
|
|
|
|
# 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}"
|
|
;;
|
|
TESTPAYSAN)
|
|
# Stage FrancPaysan: default instance + public templates (e2e-001/005/1)
|
|
MERCHANT_INSTANCE="${MERCHANT_INSTANCE:-default}"
|
|
WITHDRAW_AMT="${WITHDRAW_AMT:-TESTPAYSAN:20}"
|
|
PAY_AMT="${PAY_AMT:-TESTPAYSAN:0.01}"
|
|
CREDIT_AMT="${CREDIT_AMT:-TESTPAYSAN: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 "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
|
|
_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%/}
|
|
MERCHANT_PUBLIC=${MERCHANT_PUBLIC%/}
|
|
|
|
# Hard rule: only the local koopa/hacktivism stack may use SSH
|
|
if [ "${LOCAL_STACK}" != "1" ]; then
|
|
SKIP_SSH=1
|
|
fi
|
|
}
|
|
|
|
# Apply TALER_DOMAIN from env once (CLI exports TALER_DOMAIN_APPLIED=1 after overrides).
|
|
if [ "${TALER_DOMAIN_APPLIED:-0}" != "1" ] \
|
|
&& [ -n "${TALER_DOMAIN:-}" ] && [ "${TALER_DOMAIN}" != "hacktivism.ch" ]; then
|
|
apply_taler_domain "$TALER_DOMAIN"
|
|
TALER_DOMAIN_APPLIED=1
|
|
fi
|
|
|
|
# Safe SSH: publickey only, short connect, overall alarm so we never block forever.
|
|
SSH_BASE_OPTS=(
|
|
-o BatchMode=yes
|
|
-o ConnectTimeout="${SSH_CONNECT_TIMEOUT}"
|
|
-o ConnectionAttempts=1
|
|
-o ServerAliveInterval=3
|
|
-o ServerAliveCountMax=2
|
|
-o StrictHostKeyChecking=accept-new
|
|
-o PreferredAuthentications=publickey
|
|
-o PasswordAuthentication=no
|
|
-o KbdInteractiveAuthentication=no
|
|
-o GSSAPIAuthentication=no
|
|
-o NumberOfPasswordPrompts=0
|
|
)
|
|
|
|
# Hard wall-clock timeout so ssh/curl never block the monitoring run forever.
|
|
with_timeout() {
|
|
local secs="$1"; shift
|
|
if command -v gtimeout >/dev/null 2>&1; then
|
|
gtimeout --kill-after=2 "$secs" "$@"
|
|
return $?
|
|
fi
|
|
if command -v timeout >/dev/null 2>&1; then
|
|
timeout -k 2 "$secs" "$@" 2>/dev/null || timeout --kill-after=2 "$secs" "$@"
|
|
return $?
|
|
fi
|
|
# Portable: perl alarm + process group kill
|
|
perl -e '
|
|
use strict; use warnings;
|
|
my $secs = shift @ARGV;
|
|
my $pid = fork();
|
|
die "fork: $!" unless defined $pid;
|
|
if ($pid == 0) {
|
|
setpgrp(0, 0);
|
|
exec @ARGV;
|
|
exit 127;
|
|
}
|
|
$SIG{ALRM} = sub {
|
|
kill "TERM", -$pid;
|
|
select(undef, undef, undef, 1.0);
|
|
kill "KILL", -$pid;
|
|
exit 124;
|
|
};
|
|
alarm $secs;
|
|
waitpid($pid, 0);
|
|
my $code = $? >> 8;
|
|
alarm 0;
|
|
exit $code;
|
|
' "$secs" "$@"
|
|
}
|
|
|
|
# Pick a working SSH host: KOOPA_SSH first, then KOOPA_SSH_FALLBACKS (koopa-external).
|
|
# Sets KOOPA_SSH to the first host that answers. 0 = ok, 1 = none.
|
|
KOOPA_SSH_RESOLVED=0
|
|
resolve_koopa_ssh() {
|
|
[ "${SKIP_SSH:-0}" = "1" ] && return 1
|
|
if [ "${KOOPA_SSH_RESOLVED}" = "1" ]; then
|
|
return 0
|
|
fi
|
|
local cands=() c f seen=" "
|
|
cands+=("${KOOPA_SSH}")
|
|
# shellcheck disable=SC2086
|
|
for f in ${KOOPA_SSH_FALLBACKS}; do
|
|
case "$seen" in *" $f "*) continue ;; esac
|
|
cands+=("$f")
|
|
seen="$seen$f "
|
|
done
|
|
for c in "${cands[@]}"; do
|
|
if with_timeout $((SSH_CONNECT_TIMEOUT + 5)) \
|
|
ssh "${SSH_BASE_OPTS[@]}" "$c" 'echo ok' >/dev/null 2>&1; then
|
|
if [ "$c" != "${KOOPA_SSH}" ]; then
|
|
# surface once so operators know we used WAN jump
|
|
printf '[INFO] SSH host %s unreachable — using %s\n' "${KOOPA_SSH}" "$c" >&2 || true
|
|
fi
|
|
KOOPA_SSH="$c"
|
|
KOOPA_SSH_RESOLVED=1
|
|
export KOOPA_SSH
|
|
return 0
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
# Probe: 0 if any koopa SSH host works quickly
|
|
koopa_ssh_ok() {
|
|
[ "${SKIP_SSH}" = "1" ] && return 1
|
|
resolve_koopa_ssh
|
|
}
|
|
|
|
# Run remote bash -s with optional stdin script; hard-capped
|
|
# usage: koopa_ssh_bash [timeout_secs] <<'EOF' ... EOF
|
|
# or: koopa_ssh_run timeout_secs 'remote command'
|
|
koopa_ssh_run() {
|
|
local t="${1:-$SSH_CMD_TIMEOUT}"
|
|
shift
|
|
resolve_koopa_ssh || return 1
|
|
with_timeout "$t" ssh "${SSH_BASE_OPTS[@]}" "${KOOPA_SSH}" "$@"
|
|
}
|
|
|
|
koopa_ssh_bash() {
|
|
local t="${1:-$SSH_CMD_TIMEOUT}"
|
|
resolve_koopa_ssh || return 1
|
|
with_timeout "$t" ssh "${SSH_BASE_OPTS[@]}" "${KOOPA_SSH}" 'bash -s'
|
|
}
|
|
|
|
# stdin → remote python3 - (for metrics load probe)
|
|
koopa_ssh_python() {
|
|
local t="${1:-60}"
|
|
resolve_koopa_ssh || return 1
|
|
with_timeout "$t" ssh "${SSH_BASE_OPTS[@]}" "${KOOPA_SSH}" python3 -
|
|
}
|
|
|
|
# ANSI colours — boxed badges (fg+bg) + body (label bold, detail dim).
|
|
# Off: NO_COLOR=1 or CLICOLOR=0.
|
|
if [ "${NO_COLOR:-0}" = "1" ] || [ "${CLICOLOR:-1}" = "0" ]; then
|
|
G= R= Y= C= M= D= W= B= N=
|
|
BG_OK= BG_INFO= BG_WARN= BG_ERR= BG_BLK= BG_SEC=
|
|
BOX=0
|
|
else
|
|
G=$'\e[1;32m' # green text (totals)
|
|
R=$'\e[1;31m' # red text
|
|
Y=$'\e[1;33m' # yellow text
|
|
C=$'\e[1;36m' # cyan text
|
|
M=$'\e[1;35m' # magenta text
|
|
D=$'\e[2m' # dim tid / detail
|
|
W=$'\e[1;37m' # bold white label
|
|
B=$'\e[1m' # bold section
|
|
N=$'\e[0m' # reset
|
|
# Badge fill: bright text on solid background
|
|
# WARN = yellow only (never red). ERROR/BLOCKER use red/magenta.
|
|
BG_OK=$'\e[1;30;42m' # black on green
|
|
BG_INFO=$'\e[1;30;46m' # black on cyan
|
|
BG_WARN=$'\e[1;30;103m' # black on bright yellow (clearly not red)
|
|
BG_ERR=$'\e[1;37;41m' # white on red — errors only
|
|
BG_BLK=$'\e[1;37;45m' # white on magenta — blockers
|
|
BG_SEC=$'\e[1;37;44m' # white on blue (section)
|
|
BOX=1
|
|
fi
|
|
|
|
PASS_N=0
|
|
FAIL_N=0
|
|
WARN_N=0
|
|
INFO_N=0
|
|
BLOCKERS=() # human-readable payment/withdraw blockers
|
|
ERRORS=() # all ERROR lines (component scope)
|
|
|
|
# Grouped test IDs: area.group-NN (e.g. www.exchange-01, e2e.pay-03)
|
|
# plus global run number: #042 (monotonic for the whole ./taler-monitoring.sh run)
|
|
# set_area www # phase: www | e2e | inside | versions | …
|
|
# set_group exchange # → www.exchange-01
|
|
# set_group bank # → www.bank-01
|
|
# Without set_group: area-01, area-02, … (flat within area)
|
|
# Line shape: ┌ OK ┐ #003 www.exchange-02 label · detail
|
|
TEST_AREA=""
|
|
TEST_GROUP=""
|
|
TEST_N=0
|
|
LAST_TID=""
|
|
# Global / progress: may continue across check_*.sh processes via TALER_MON_STATE
|
|
GLOBAL_N="${GLOBAL_N:-0}"
|
|
LAST_GLOBAL=""
|
|
PROGRESS_DONE="${PROGRESS_DONE:-0}"
|
|
PROGRESS_TOTAL="${PROGRESS_TOTAL:-0}"
|
|
PROGRESS_SHOW_EVERY="${PROGRESS_SHOW_EVERY:-8}"
|
|
PROGRESS_LAST_SHOWN="${PROGRESS_LAST_SHOWN:-0}"
|
|
_mon_state_load() {
|
|
[ -n "${TALER_MON_STATE:-}" ] && [ -f "${TALER_MON_STATE}" ] || return 0
|
|
# shellcheck disable=SC1090
|
|
. "${TALER_MON_STATE}"
|
|
}
|
|
_mon_state_save() {
|
|
[ -n "${TALER_MON_STATE:-}" ] || return 0
|
|
{
|
|
printf 'GLOBAL_N=%s\n' "$GLOBAL_N"
|
|
printf 'PROGRESS_DONE=%s\n' "$PROGRESS_DONE"
|
|
printf 'PROGRESS_TOTAL=%s\n' "$PROGRESS_TOTAL"
|
|
printf 'PROGRESS_LAST_SHOWN=%s\n' "$PROGRESS_LAST_SHOWN"
|
|
} >"${TALER_MON_STATE}"
|
|
}
|
|
_mon_state_load
|
|
# Per-group counters so re-entering www.exchange after perf continues NN
|
|
# (shell vars TEST_CNT_<area>_<group>).
|
|
_tid_key() {
|
|
# $1=area $2=group → safe identifier
|
|
printf 'TEST_CNT_%s_%s' "$1" "$2" | tr -c 'A-Za-z0-9_' '_'
|
|
}
|
|
_tid_save() {
|
|
[ -z "${TEST_AREA:-}" ] || [ -z "${TEST_GROUP:-}" ] && return 0
|
|
local k
|
|
k=$(_tid_key "$TEST_AREA" "$TEST_GROUP")
|
|
eval "$k=\$TEST_N"
|
|
}
|
|
_tid_load() {
|
|
local k
|
|
k=$(_tid_key "$TEST_AREA" "$1")
|
|
eval "TEST_N=\${$k:-0}"
|
|
}
|
|
set_area() {
|
|
# leave previous group counter saved
|
|
_tid_save
|
|
TEST_AREA="$1"
|
|
TEST_GROUP=""
|
|
TEST_N=0
|
|
LAST_TID=""
|
|
}
|
|
# Start a logical sub-group. Short stable names:
|
|
# exchange bank merchant perf stats landing paivana
|
|
# prereq wallet atm settle pay shop dig load report
|
|
# ssh caddy outside inside compare withdraw
|
|
# Counter continues if the same group is re-entered later in the area.
|
|
# Prints a compact group chip so log + issue text map cleanly (www.bank-03).
|
|
set_group() {
|
|
local g="$1"
|
|
# no-op if same group already active (e.g. inside emit loop)
|
|
if [ "$g" = "${TEST_GROUP:-}" ] && [ -n "$g" ]; then
|
|
return 0
|
|
fi
|
|
_tid_save
|
|
TEST_GROUP="$g"
|
|
LAST_TID=""
|
|
if [ -z "${TEST_AREA:-}" ] || [ -z "$g" ]; then
|
|
TEST_N=0
|
|
return 0
|
|
fi
|
|
_tid_load "$g"
|
|
local tag="${TEST_AREA}.${g}"
|
|
if [ "${BOX:-0}" = "1" ]; then
|
|
# cyan-outline group chip: ┌ www.exchange ┐
|
|
printf '%s%s┌ %s ┐%s\n' "$D" "$C" "$tag" "$N"
|
|
else
|
|
printf -- '-- %s --\n' "$tag"
|
|
fi
|
|
# show progress when entering a new group (if totals known)
|
|
_progress_maybe_show 1
|
|
}
|
|
set_progress_total() {
|
|
# Optional: expected number of numbered checks in this run.
|
|
# PROGRESS_TOTAL=0 → no percent, only "done=N".
|
|
# Does not reset GLOBAL_N / PROGRESS_DONE if already mid-run (state file).
|
|
local n="${1:-0}"
|
|
PROGRESS_TOTAL="$n"
|
|
if [ "${PROGRESS_DONE:-0}" -eq 0 ]; then
|
|
PROGRESS_LAST_SHOWN=0
|
|
fi
|
|
_mon_state_save
|
|
}
|
|
add_progress_total() {
|
|
local n="${1:-0}"
|
|
PROGRESS_TOTAL=$((PROGRESS_TOTAL + n))
|
|
_mon_state_save
|
|
}
|
|
_progress_bar_line() {
|
|
local done="$1" total="$2" width=24 pct=0 filled empty i bar
|
|
if [ "$total" -gt 0 ]; then
|
|
pct=$((done * 100 / total))
|
|
[ "$pct" -gt 100 ] && pct=100
|
|
filled=$((pct * width / 100))
|
|
[ "$filled" -gt "$width" ] && filled=$width
|
|
empty=$((width - filled))
|
|
bar=""
|
|
i=0
|
|
while [ "$i" -lt "$filled" ]; do bar="${bar}█"; i=$((i + 1)); done
|
|
i=0
|
|
while [ "$i" -lt "$empty" ]; do bar="${bar}░"; i=$((i + 1)); done
|
|
if [ "${BOX:-0}" = "1" ]; then
|
|
printf '%s%s┌ PROG ┐%s %s%s%s %s%3d%%%s %s%d/%d%s\n' \
|
|
"$D" "$C" "$N" "$C" "$bar" "$N" "$W" "$pct" "$N" "$D" "$done" "$total" "$N"
|
|
else
|
|
printf -- '[ PROG ] [%s] %3d%% %d/%d\n' "$bar" "$pct" "$done" "$total"
|
|
fi
|
|
else
|
|
if [ "${BOX:-0}" = "1" ]; then
|
|
printf '%s%s┌ PROG ┐%s %sdone=%d%s (total unknown — set PROGRESS_TOTAL=)\n' \
|
|
"$D" "$C" "$N" "$D" "$done" "$N"
|
|
else
|
|
printf -- '[ PROG ] done=%d (total unknown)\n' "$done"
|
|
fi
|
|
fi
|
|
}
|
|
_progress_maybe_show() {
|
|
local force="${1:-0}"
|
|
[ "${PROGRESS_OFF:-0}" = "1" ] && return 0
|
|
[ "$PROGRESS_DONE" -le 0 ] && [ "$force" != "1" ] && return 0
|
|
if [ "$force" = "1" ] || \
|
|
[ $((PROGRESS_DONE - PROGRESS_LAST_SHOWN)) -ge "$PROGRESS_SHOW_EVERY" ] || \
|
|
{ [ "$PROGRESS_TOTAL" -gt 0 ] && [ "$PROGRESS_DONE" -ge "$PROGRESS_TOTAL" ]; }; then
|
|
_progress_bar_line "$PROGRESS_DONE" "$PROGRESS_TOTAL"
|
|
PROGRESS_LAST_SHOWN=$PROGRESS_DONE
|
|
fi
|
|
}
|
|
# Assign next id into LAST_TID (must not run in a subshell).
|
|
_take_tid() {
|
|
LAST_TID=""
|
|
LAST_GLOBAL=""
|
|
if [ -z "${TEST_AREA:-}" ]; then
|
|
return
|
|
fi
|
|
# Global monotonic number for the whole monitoring run (#001 …)
|
|
GLOBAL_N=$((GLOBAL_N + 1))
|
|
LAST_GLOBAL=$(printf '#%03d' "$GLOBAL_N")
|
|
TEST_N=$((TEST_N + 1))
|
|
if [ -n "${TEST_GROUP:-}" ]; then
|
|
LAST_TID=$(printf '%s.%s-%02d' "$TEST_AREA" "$TEST_GROUP" "$TEST_N")
|
|
_tid_save
|
|
else
|
|
LAST_TID=$(printf '%s-%02d' "$TEST_AREA" "$TEST_N")
|
|
fi
|
|
PROGRESS_DONE=$((PROGRESS_DONE + 1))
|
|
_mon_state_save
|
|
_progress_maybe_show 0
|
|
}
|
|
# Dim ids: "#003 www.exchange-01 " or empty
|
|
_fmt_tid() {
|
|
if [ -n "${LAST_GLOBAL:-}" ]; then
|
|
printf '%s%s%s ' "$D" "$LAST_GLOBAL" "$N"
|
|
fi
|
|
if [ -n "${LAST_TID:-}" ]; then
|
|
printf '%s%s%s ' "$D" "$LAST_TID" "$N"
|
|
fi
|
|
}
|
|
# Boxed badge cell: ┌ OK ┐ with filled bg (tag left-padded, width 7 for BLOCKER)
|
|
# $1=badge style $2=tag text
|
|
_fmt_badge() {
|
|
local badge="$1" tag="$2" inner
|
|
inner=$(printf -- '%-7s' "$tag")
|
|
if [ "${BOX:-0}" = "1" ]; then
|
|
printf '%s┌ %s┐%s' "$badge" "$inner" "$N"
|
|
else
|
|
printf '%s[ %s]%s' "$badge" "$inner" "$N"
|
|
fi
|
|
}
|
|
# One concrete line: ┌ OK ┐ tid label · detail
|
|
# $1=badge style $2=tag text $3=label colour $4=label $5=detail
|
|
_msg_line() {
|
|
local badge="$1" tag="$2" lcol="$3" label="$4" detail="${5:-}"
|
|
if [ -n "$detail" ]; then
|
|
printf -- '%s %s%s%s%s %s·%s %s%s%s\n' \
|
|
"$(_fmt_badge "$badge" "$tag")" "$(_fmt_tid)" "$lcol" "$label" "$N" "$D" "$N" "$D" "$detail" "$N"
|
|
else
|
|
printf -- '%s %s%s%s%s\n' \
|
|
"$(_fmt_badge "$badge" "$tag")" "$(_fmt_tid)" "$lcol" "$label" "$N"
|
|
fi
|
|
}
|
|
|
|
ok() {
|
|
# ok "what is good" ["concrete evidence: HTTP 200 · 12ms · …"]
|
|
local label="$1" detail="${2:-}"
|
|
_take_tid
|
|
_msg_line "$BG_OK" "OK" "$W" "$label" "$detail"
|
|
PASS_N=$((PASS_N + 1))
|
|
}
|
|
# component-scoped error: err bank "what failed" "why / HTTP / path"
|
|
err() {
|
|
local comp="$1" msg="$2" detail="${3:-}"
|
|
_take_tid
|
|
_msg_line "$BG_ERR" "ERROR" "$W" "${comp}: ${msg}" "$detail"
|
|
FAIL_N=$((FAIL_N + 1))
|
|
ERRORS+=("${LAST_TID:+$LAST_TID }[$comp] $msg${detail:+ · $detail}")
|
|
}
|
|
# fail "what failed" ["why / HTTP code / path"]
|
|
fail() {
|
|
local label="$1" detail="${2:-}"
|
|
_take_tid
|
|
_msg_line "$BG_ERR" "ERROR" "$W" "$label" "$detail"
|
|
FAIL_N=$((FAIL_N + 1))
|
|
ERRORS+=("${LAST_TID:+$LAST_TID }$label${detail:+ · $detail}")
|
|
}
|
|
warn() {
|
|
# warn "what is soft-bad" ["why still ok to continue"]
|
|
# warn component "what" "why"
|
|
local a1="${1:-}" a2="${2:-}" a3="${3:-}"
|
|
local head detail
|
|
_take_tid
|
|
if [ -n "$a3" ]; then
|
|
head="${a1}: ${a2}"
|
|
detail="$a3"
|
|
elif [ -n "$a2" ]; then
|
|
head="$a1"
|
|
detail="$a2"
|
|
else
|
|
head="$a1"
|
|
detail=""
|
|
fi
|
|
# Label in yellow text; badge yellow — never red (red = ERROR only)
|
|
_msg_line "$BG_WARN" "WARN" "$Y" "$head" "$detail"
|
|
WARN_N=$((WARN_N + 1))
|
|
}
|
|
info() {
|
|
# info "topic" ["concrete fact / value / next step"]
|
|
local label="$1" detail="${2:-}"
|
|
_take_tid
|
|
_msg_line "$BG_INFO" "INFO" "$W" "$label" "$detail"
|
|
INFO_N=$((INFO_N + 1))
|
|
}
|
|
blocker() {
|
|
# Hard stop on pay/withdraw path: blocker "step" "why it cannot continue"
|
|
local step="$1" msg="$2"
|
|
_take_tid
|
|
_msg_line "$BG_BLK" "BLOCKER" "$W" "${step}" "$msg"
|
|
BLOCKERS+=("${LAST_TID:+$LAST_TID }[$step] $msg")
|
|
FAIL_N=$((FAIL_N + 1))
|
|
ERRORS+=("BLOCKER ${LAST_TID:+$LAST_TID }[$step] $msg")
|
|
}
|
|
section() {
|
|
# Boxed section header (3 lines when colour on)
|
|
local title="$*"
|
|
local w=${#title}
|
|
[ "$w" -lt 24 ] && w=24
|
|
[ "$w" -gt 56 ] && w=56
|
|
local pad line i
|
|
pad=$(printf -- "%-${w}s" "$title")
|
|
if [ "${BOX:-0}" = "1" ]; then
|
|
line=""
|
|
i=0
|
|
while [ "$i" -lt $((w + 2)) ]; do
|
|
line="${line}═"
|
|
i=$((i + 1))
|
|
done
|
|
printf -- '\n%s╔%s╗%s\n' "$BG_SEC" "$line" "$N"
|
|
printf -- '%s║%s %s%s%s %s║%s\n' "$BG_SEC" "$N" "$B" "$pad" "$N" "$BG_SEC" "$N"
|
|
printf -- '%s╚%s╝%s\n' "$BG_SEC" "$line" "$N"
|
|
else
|
|
printf -- '\n== %s ==\n' "$title"
|
|
fi
|
|
}
|
|
|
|
summary() {
|
|
echo ""
|
|
# final progress line (no _take_tid — plain printf)
|
|
if [ "${PROGRESS_OFF:-0}" != "1" ] && [ "$PROGRESS_DONE" -gt 0 ]; then
|
|
_progress_bar_line "$PROGRESS_DONE" "$PROGRESS_TOTAL"
|
|
fi
|
|
if [ "$GLOBAL_N" -gt 0 ]; then
|
|
printf -- '%s numbered checks this run: #001…#%03d\n' "$D" "$GLOBAL_N"
|
|
fi
|
|
if [ "${#BLOCKERS[@]}" -gt 0 ]; then
|
|
if [ "${BOX:-0}" = "1" ]; then
|
|
printf -- '%s┌ BLOCKERS · pay/withdraw cannot finish ┐%s\n' "$BG_BLK" "$N"
|
|
else
|
|
printf -- '--- BLOCKERS (pay/withdraw cannot finish) ---\n'
|
|
fi
|
|
local b
|
|
for b in "${BLOCKERS[@]}"; do
|
|
printf -- '%s •%s %s%s%s\n' "$M" "$N" "$W" "$b" "$N"
|
|
done
|
|
fi
|
|
if [ "${#ERRORS[@]}" -gt 0 ] && [ "${#BLOCKERS[@]}" -lt "${#ERRORS[@]}" ]; then
|
|
if [ "${BOX:-0}" = "1" ]; then
|
|
printf -- '%s┌ ERRORS · failed checks ┐%s\n' "$BG_ERR" "$N"
|
|
else
|
|
printf -- '--- ERRORS (failed checks) ---\n'
|
|
fi
|
|
local e
|
|
for e in "${ERRORS[@]}"; do
|
|
case "$e" in BLOCKER*) continue ;; esac
|
|
printf -- '%s •%s %s%s%s\n' "$R" "$N" "$W" "$e" "$N"
|
|
done
|
|
fi
|
|
printf -- '%stotals:%s %s%d OK%s' "$D" "$N" "$G" "$PASS_N" "$N"
|
|
[ "$FAIL_N" -gt 0 ] && printf -- ', %s%d ERROR%s' "$R" "$FAIL_N" "$N"
|
|
[ "$WARN_N" -gt 0 ] && printf -- ', %s%d WARN%s' "$Y" "$WARN_N" "$N"
|
|
[ "$INFO_N" -gt 0 ] && printf -- ', %s%d INFO%s' "$C" "$INFO_N" "$N"
|
|
[ "${#BLOCKERS[@]}" -gt 0 ] && printf -- ', %s%d BLOCKER%s' "$M" "${#BLOCKERS[@]}" "$N"
|
|
printf '\n'
|
|
[ "$FAIL_N" -eq 0 ]
|
|
}
|
|
|
|
http_code() {
|
|
local url="$1"; shift
|
|
curl -skS --max-redirs 0 -m "${TIMEOUT}" -o /dev/null -w '%{http_code}' "$@" "$url" 2>/dev/null || echo 000
|
|
}
|
|
|
|
http_body() {
|
|
local url="$1" out="$2"; shift 2
|
|
curl -skS --max-redirs 0 -m "${TIMEOUT}" -o "$out" -w '%{http_code}' "$@" "$url" 2>/dev/null || echo 000
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Currency unit map checks (wallet codec: alt_unit_names must include "0")
|
|
# ---------------------------------------------------------------------------
|
|
# Returns 0 if JSON body at $1 has usable alt_unit_names.
|
|
# Supports:
|
|
# - exchange/bank: currency_specification.alt_unit_names
|
|
# - merchant: currencies.<CODE>.alt_unit_names for each code
|
|
# Optional $2 = required currency code for currency_specification.currency
|
|
json_has_alt_unit_names() {
|
|
local file="$1" want_cur="${2:-}"
|
|
python3 - "$file" "$want_cur" <<'PY'
|
|
import json, sys
|
|
path, want = sys.argv[1], sys.argv[2]
|
|
try:
|
|
d = json.load(open(path))
|
|
except Exception as e:
|
|
print(f"json-error: {e}")
|
|
sys.exit(2)
|
|
|
|
def check_au(au, label):
|
|
if not isinstance(au, dict) or not au:
|
|
print(f"{label}: missing/empty alt_unit_names")
|
|
return False
|
|
if "0" not in au or not str(au.get("0") or "").strip():
|
|
print(f"{label}: alt_unit_names missing non-empty key \"0\" (have {sorted(au.keys())})")
|
|
return False
|
|
print(f"{label}: alt_unit_names ok (0={au.get('0')!r}, n={len(au)})")
|
|
return True
|
|
|
|
ok = True
|
|
cs = d.get("currency_specification")
|
|
if isinstance(cs, dict):
|
|
if want and cs.get("currency") and cs.get("currency") != want:
|
|
print(f"currency_specification.currency={cs.get('currency')!r} want {want!r}")
|
|
ok = False
|
|
if not check_au(cs.get("alt_unit_names"), "currency_specification"):
|
|
ok = False
|
|
elif "currency_specification" in d:
|
|
print("currency_specification: not an object")
|
|
ok = False
|
|
|
|
curs = d.get("currencies")
|
|
if isinstance(curs, dict) and curs:
|
|
for code, spec in curs.items():
|
|
if not isinstance(spec, dict):
|
|
print(f"currencies.{code}: not an object")
|
|
ok = False
|
|
continue
|
|
if not check_au(spec.get("alt_unit_names"), f"currencies.{code}"):
|
|
ok = False
|
|
|
|
if not isinstance(cs, dict) and not (isinstance(curs, dict) and curs):
|
|
# neither shape — fail
|
|
print("no currency_specification or currencies map")
|
|
ok = False
|
|
|
|
sys.exit(0 if ok else 1)
|
|
PY
|
|
}
|
|
|
|
# Check one exchange base URL's /config for alt_unit_names.
|
|
# $1=label $2=base_url $3=expected currency (optional) $4=strict(1) or soft(0)
|
|
check_exchange_alt_units() {
|
|
local label="$1" base="$2" want_cur="${3:-}" strict="${4:-1}"
|
|
local f code
|
|
base="${base%/}"
|
|
f=$(mktemp)
|
|
code=$(http_body "${base}/config" "$f")
|
|
if [ "$code" != "200" ]; then
|
|
rm -f "$f"
|
|
if [ "$strict" = "1" ]; then
|
|
fail "$label /config" "HTTP $code ($base)"
|
|
else
|
|
warn "$label /config" "HTTP $code ($base)"
|
|
fi
|
|
return
|
|
fi
|
|
local out ec
|
|
set +e
|
|
out=$(json_has_alt_unit_names "$f" "$want_cur" 2>&1)
|
|
ec=$?
|
|
set -e
|
|
rm -f "$f"
|
|
if [ "$ec" -eq 0 ]; then
|
|
ok "$label alt_unit_names" "$(echo "$out" | tr '\n' '; ' | sed 's/; $//')"
|
|
else
|
|
if [ "$strict" = "1" ]; then
|
|
fail "$label alt_unit_names" "$(echo "$out" | tr '\n' '; ' | sed 's/; $//')"
|
|
else
|
|
warn "$label alt_unit_names" "$(echo "$out" | tr '\n' '; ' | sed 's/; $//')"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# From merchant /config JSON file: walk exchanges[] and check each base_url/config.
|
|
# Local stack hosts (hacktivism.ch or $EXCHANGE_PUBLIC host) are strict; others soft.
|
|
check_merchant_listed_exchanges_alt_units() {
|
|
local mer_json="$1"
|
|
local list
|
|
list=$(python3 - "$mer_json" <<'PY'
|
|
import json, sys
|
|
d = json.load(open(sys.argv[1]))
|
|
for e in d.get("exchanges") or []:
|
|
if not isinstance(e, dict):
|
|
continue
|
|
u = (e.get("base_url") or e.get("url") or "").rstrip("/")
|
|
c = e.get("currency") or ""
|
|
if u:
|
|
print(f"{c}\t{u}")
|
|
PY
|
|
)
|
|
if [ -z "$list" ]; then
|
|
fail "merchant exchanges[]" "empty — no exchanges to check for alt_unit_names"
|
|
return
|
|
fi
|
|
local line cur url strict host
|
|
while IFS=$'\t' read -r cur url; do
|
|
[ -n "$url" ] || continue
|
|
host="${url#https://}"; host="${host#http://}"; host="${host%%/*}"
|
|
strict=1
|
|
case "$host" in
|
|
*hacktivism.ch) strict=1 ;;
|
|
*)
|
|
# foreign exchange (e.g. taler-ops) — soft unless it is our configured EXCHANGE_PUBLIC
|
|
if [ "$url" = "${EXCHANGE_PUBLIC}" ] || [ "$url" = "${EXCHANGE_PUBLIC}/" ]; then
|
|
strict=1
|
|
else
|
|
strict=0
|
|
fi
|
|
;;
|
|
esac
|
|
check_exchange_alt_units "exchange ${cur:-?} ${host}" "$url" "$cur" "$strict"
|
|
done <<<"$list"
|
|
}
|
|
|
|
# Live bank/merchant passwords: sibling **koopa-admin-secrets** (never in admin-log).
|
|
# Override: SECRETS_ROOT=/path/to/koopa-admin-secrets/koopa/host-root
|
|
# or KOOPA_ADMIN_SECRETS=/path/to/koopa-admin-secrets
|
|
SECRETS_ROOT="${SECRETS_ROOT:-}"
|
|
_secrets_search_log() { :; } # placeholder if we later want debug
|
|
if [ -z "$SECRETS_ROOT" ]; then
|
|
_mon_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
_admin_log="$(cd "${_mon_dir}/../.." && pwd)"
|
|
# Prefer explicit env pointing at the secrets *repo* root
|
|
if [ -n "${KOOPA_ADMIN_SECRETS:-}" ] && [ -d "${KOOPA_ADMIN_SECRETS}/koopa/host-root/taler-bank" ]; then
|
|
SECRETS_ROOT="${KOOPA_ADMIN_SECRETS}/koopa/host-root"
|
|
fi
|
|
fi
|
|
if [ -z "$SECRETS_ROOT" ]; then
|
|
_mon_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
_admin_log="$(cd "${_mon_dir}/../.." && pwd)"
|
|
for d in \
|
|
"${_admin_log}/../koopa-admin-secrets/koopa/host-root" \
|
|
"${_admin_log}/../../koopa-admin-secrets/koopa/host-root" \
|
|
"${HOME}/taler/src/koopa-admin-secrets/koopa/host-root" \
|
|
"${HOME}/src/koopa/koopa-admin-secrets/koopa/host-root" \
|
|
"${HOME}/koopa-admin-secrets/koopa/host-root" \
|
|
"/Users/newkamek/src/koopa/koopa-admin-secrets/koopa/host-root" \
|
|
"$(cd "${_admin_log}/../.." 2>/dev/null && pwd)/koopa-admin-secrets/koopa/host-root"
|
|
do
|
|
# resolve .. without requiring the dir to exist first for cd
|
|
if [ -d "$d/taler-bank" ]; then
|
|
SECRETS_ROOT=$(cd "$d" && pwd)
|
|
break
|
|
fi
|
|
done
|
|
unset _mon_dir _admin_log d
|
|
fi
|
|
|
|
read_secret() {
|
|
local rel="$1" base val=""
|
|
base=$(basename "$rel")
|
|
if [ -n "${SECRETS_ROOT:-}" ]; then
|
|
if [ -f "${SECRETS_ROOT}/${rel}" ]; then
|
|
tr -d '\n\r' <"${SECRETS_ROOT}/${rel}"
|
|
return 0
|
|
fi
|
|
# also accept flat layout under host-root/
|
|
if [ -f "${SECRETS_ROOT}/${base}" ]; then
|
|
tr -d '\n\r' <"${SECRETS_ROOT}/${base}"
|
|
return 0
|
|
fi
|
|
fi
|
|
# Optional: copy under ~/.config/taler-landing/ (landing-stats style)
|
|
if [ -f "${HOME}/.config/taler-landing/${base}" ]; then
|
|
tr -d '\n\r' <"${HOME}/.config/taler-landing/${base}"
|
|
return 0
|
|
fi
|
|
# Live on koopa host /root (same basenames as deploy)
|
|
if koopa_ssh_ok; then
|
|
val=$(koopa_ssh_run 12 "tr -d '\\n\\r' </root/${base} 2>/dev/null || true" 2>/dev/null || true)
|
|
if [ -n "$val" ]; then
|
|
printf '%s' "$val"
|
|
return 0
|
|
fi
|
|
# container path used by some installs
|
|
val=$(koopa_ssh_run 12 \
|
|
"podman exec taler-hacktivism-bank tr -d '\\n\\r' </root/${base} 2>/dev/null || true" 2>/dev/null || true)
|
|
if [ -n "$val" ]; then
|
|
printf '%s' "$val"
|
|
return 0
|
|
fi
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
# Human hint when secrets missing (e2e prereq)
|
|
secrets_hint() {
|
|
cat <<EOF
|
|
Prefer a local secrets file (not committed):
|
|
cp scripts/taler-monitoring/secrets.env.example scripts/taler-monitoring/secrets.env
|
|
\$EDITOR scripts/taler-monitoring/secrets.env
|
|
Or set SECRETS_ROOT=\$HOME/src/koopa/koopa-admin-secrets/koopa/host-root
|
|
Or export E2E_BANK_ADMIN_PASS / E2E_MERCHANT_TOKEN for one shot.
|
|
EOF
|
|
}
|
|
|
|
# Load optional secrets.env next to this suite (gitignored). Safe KEY=value only.
|
|
# Call once from taler-monitoring.sh after source lib.sh (or any check_*.sh entry).
|
|
load_monitoring_secrets_env() {
|
|
local mon_dir f
|
|
mon_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
f="${MONITORING_SECRETS_ENV:-${mon_dir}/secrets.env}"
|
|
if [ ! -f "$f" ]; then
|
|
return 0
|
|
fi
|
|
# shellcheck disable=SC1090
|
|
set -a
|
|
# Only simple KEY=VALUE lines; ignore comments / blanks
|
|
# shellcheck disable=SC1091
|
|
. "$f"
|
|
set +a
|
|
# Map common aliases → e2e vars if not already set
|
|
: "${E2E_BANK_ADMIN_PASS:=${BANK_ADMIN_PASS:-${BANK_ADMIN_PASSWORD:-}}}"
|
|
: "${E2E_MERCHANT_TOKEN:=${MERCHANT_TOKEN:-${MERCHANT_INSTANCE_PASSWORD:-}}}"
|
|
: "${SECRETS_ROOT:=${KOOPA_SECRETS_HOST_ROOT:-}}"
|
|
if [ -z "${SECRETS_ROOT:-}" ] && [ -n "${KOOPA_ADMIN_SECRETS:-}" ]; then
|
|
if [ -d "${KOOPA_ADMIN_SECRETS}/koopa/host-root/taler-bank" ]; then
|
|
SECRETS_ROOT="${KOOPA_ADMIN_SECRETS}/koopa/host-root"
|
|
fi
|
|
fi
|
|
export E2E_BANK_ADMIN_PASS E2E_MERCHANT_TOKEN SECRETS_ROOT KOOPA_ADMIN_SECRETS
|
|
}
|
|
|
|
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 \
|
|
/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
|
|
}
|