281 lines
11 KiB
Bash
Executable file
281 lines
11 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# Sanity checks for bank · exchange · merchant (public + server-side).
|
|
# Sections are independent; continues after failures.
|
|
set -euo pipefail
|
|
ROOT=$(cd "$(dirname "$0")" && pwd)
|
|
# shellcheck source=lib.sh
|
|
source "$ROOT/lib.sh"
|
|
|
|
tmp=$(mktemp -d)
|
|
trap 'rm -rf "$tmp"' EXIT
|
|
|
|
expect_code() {
|
|
local label="$1" want="$2" url="$3"
|
|
local code
|
|
code=$(http_code "$url")
|
|
case ",$want," in
|
|
*",$code,"*) ok "$label" ;;
|
|
*) fail "$label" "HTTP $code (want $want) $url" ;;
|
|
esac
|
|
}
|
|
|
|
json_currency() {
|
|
python3 -c 'import json,sys; d=json.load(open(sys.argv[1])); print(d.get("currency") or "")' "$1" 2>/dev/null || true
|
|
}
|
|
|
|
# Area sanity-### — public + optional server-side per component
|
|
set_area sanity
|
|
|
|
# ---------------------------------------------------------------------------
|
|
section "sanity · bank"
|
|
# ---------------------------------------------------------------------------
|
|
expect_code "bank public /config" 200 "$BANK_PUBLIC/config"
|
|
expect_code "bank public /taler-integration/config" 200 "$BANK_PUBLIC/taler-integration/config"
|
|
expect_code "bank public /webui/" 200 "$BANK_PUBLIC/webui/"
|
|
|
|
code=$(http_body "$BANK_PUBLIC/config" "$tmp/bank-config.json")
|
|
if [ "$code" = "200" ]; then
|
|
cur=$(json_currency "$tmp/bank-config.json")
|
|
[ "$cur" = "GOA" ] && ok "bank currency GOA" || fail "bank currency" "got ${cur:-?}"
|
|
# wire type / name if present
|
|
python3 - "$tmp/bank-config.json" <<'PY' 2>/dev/null && ok "bank config JSON object" || fail "bank config JSON"
|
|
import json,sys
|
|
d=json.load(open(sys.argv[1]))
|
|
sys.exit(0 if isinstance(d, dict) and d.get("currency") else 1)
|
|
PY
|
|
if json_has_alt_unit_names "$tmp/bank-config.json" >/tmp/alt-bank-s.$$ 2>&1; then
|
|
ok "bank /config alt_unit_names" "$(tr '\n' '; ' </tmp/alt-bank-s.$$ | sed 's/; $//')"
|
|
else
|
|
fail "bank /config alt_unit_names" "$(tr '\n' '; ' </tmp/alt-bank-s.$$ | sed 's/; $//')"
|
|
fi
|
|
rm -f /tmp/alt-bank-s.$$
|
|
fi
|
|
|
|
# server-side bank
|
|
if koopa_ssh_ok; then
|
|
BOUT=$(koopa_ssh_bash 40 <<'REMOTE' || true
|
|
set +e
|
|
BANK=$(podman ps --format '{{.Names}}' | grep -iE 'hacktivism-bank|taler-bank' | head -1)
|
|
[ -z "$BANK" ] && BANK=$(podman ps --format '{{.Names}}' | grep -i bank | head -1)
|
|
echo "CTR=$BANK"
|
|
if [ -z "$BANK" ]; then echo "NOCTR"; exit 0; fi
|
|
code=$(curl -sS -m 5 -o /dev/null -w '%{http_code}' http://127.0.0.1:9012/config 2>/dev/null || echo 000)
|
|
echo "LOCAL_CONFIG=$code"
|
|
code2=$(curl -sS -m 5 -o /dev/null -w '%{http_code}' http://127.0.0.1:9012/taler-integration/config 2>/dev/null || echo 000)
|
|
echo "LOCAL_INT=$code2"
|
|
if podman exec "$BANK" bash -c 'pgrep -f "MainKt serve|libeufin-bank serve" >/dev/null' 2>/dev/null; then
|
|
echo "LIBEUFIN=1"
|
|
else
|
|
echo "LIBEUFIN=0"
|
|
fi
|
|
if podman exec "$BANK" bash -c 'pg_isready -q' 2>/dev/null; then
|
|
echo "PG=1"
|
|
else
|
|
echo "PG=0"
|
|
fi
|
|
# in-container health if present
|
|
if podman exec "$BANK" test -x /usr/local/bin/check_bank-health.sh 2>/dev/null; then
|
|
podman exec "$BANK" /usr/local/bin/check_bank-health.sh 2>&1 | sed 's/^/HEALTH /' | tail -20
|
|
echo "HEALTH_EC=${PIPESTATUS[0]}"
|
|
fi
|
|
REMOTE
|
|
)
|
|
echo "$BOUT" | grep -q '^CTR=.\+' && ok "bank container $(echo "$BOUT" | sed -n 's/^CTR=//p' | head -1)" || fail "bank container" "not found"
|
|
echo "$BOUT" | grep -q 'LOCAL_CONFIG=200' && ok "bank local :9012/config" || fail "bank local :9012/config"
|
|
echo "$BOUT" | grep -q 'LOCAL_INT=200' && ok "bank local :9012/taler-integration/config" || fail "bank local integration"
|
|
echo "$BOUT" | grep -q 'LIBEUFIN=1' && ok "bank libeufin-bank process" || fail "bank libeufin-bank process"
|
|
echo "$BOUT" | grep -q 'PG=1' && ok "bank postgres ready" || warn "bank postgres" "pg_isready failed"
|
|
if echo "$BOUT" | grep -q 'HEALTH '; then
|
|
if echo "$BOUT" | grep -qE 'HEALTH_EC=0|ALL CRITICAL CHECKS PASSED'; then
|
|
ok "bank check_bank-health.sh"
|
|
else
|
|
# health script may false-fail process grep; warn not fail if local config ok
|
|
warn "bank check_bank-health.sh" "non-zero or incomplete"
|
|
fi
|
|
fi
|
|
else
|
|
warn "bank server-side" "ssh ${KOOPA_SSH} unavailable"
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
section "sanity · exchange"
|
|
# ---------------------------------------------------------------------------
|
|
expect_code "exchange public /config" 200 "$EXCHANGE_PUBLIC/config"
|
|
expect_code "exchange public /keys" 200 "$EXCHANGE_PUBLIC/keys"
|
|
expect_code "exchange public /terms" 200 "$EXCHANGE_PUBLIC/terms"
|
|
|
|
code=$(http_body "$EXCHANGE_PUBLIC/config" "$tmp/ex-config.json")
|
|
if [ "$code" = "200" ]; then
|
|
cur=$(json_currency "$tmp/ex-config.json")
|
|
[ "$cur" = "GOA" ] && ok "exchange currency GOA" || fail "exchange currency" "got ${cur:-?}"
|
|
if json_has_alt_unit_names "$tmp/ex-config.json" "GOA" >/tmp/alt-ex-s.$$ 2>&1; then
|
|
ok "exchange /config alt_unit_names" "$(tr '\n' '; ' </tmp/alt-ex-s.$$ | sed 's/; $//')"
|
|
else
|
|
fail "exchange /config alt_unit_names" "$(tr '\n' '; ' </tmp/alt-ex-s.$$ | sed 's/; $//')"
|
|
fi
|
|
rm -f /tmp/alt-ex-s.$$
|
|
fi
|
|
|
|
code=$(http_body "$EXCHANGE_PUBLIC/keys" "$tmp/ex-keys.json")
|
|
if [ "$code" = "200" ]; then
|
|
python3 - "$tmp/ex-keys.json" <<'PY'
|
|
import json,sys,time
|
|
d=json.load(open(sys.argv[1]))
|
|
sk=d.get("signkeys") or []
|
|
acc=d.get("accounts") or []
|
|
den=d.get("denominations") or []
|
|
# count denoms roughly
|
|
n=0
|
|
for g in den:
|
|
if isinstance(g, dict):
|
|
n += len(g.get("denoms") or [])
|
|
print(f"signkeys={len(sk)} accounts={len(acc)} denom_groups={len(den)} denoms~={n}")
|
|
sys.exit(0 if sk and (acc or n) else 1)
|
|
PY
|
|
ec=$?
|
|
detail=$(python3 - "$tmp/ex-keys.json" <<'PY'
|
|
import json,sys
|
|
d=json.load(open(sys.argv[1]))
|
|
sk=d.get("signkeys") or []
|
|
acc=d.get("accounts") or []
|
|
den=d.get("denominations") or []
|
|
n=sum(len(g.get("denoms") or []) for g in den if isinstance(g, dict))
|
|
print(f"signkeys={len(sk)} accounts={len(acc)} denoms~={n}")
|
|
PY
|
|
)
|
|
[ "$ec" -eq 0 ] && ok "exchange /keys usable ($detail)" || fail "exchange /keys usable" "$detail"
|
|
fi
|
|
|
|
# /wire if exposed
|
|
wcode=$(http_code "$EXCHANGE_PUBLIC/wire")
|
|
if [ "$wcode" = "200" ]; then
|
|
ok "exchange public /wire"
|
|
else
|
|
warn "exchange public /wire" "HTTP $wcode (accounts may only be in /keys)"
|
|
fi
|
|
|
|
if koopa_ssh_ok; then
|
|
EOUT=$(koopa_ssh_bash 40 <<'REMOTE' || true
|
|
set +e
|
|
EX=$(podman ps --format '{{.Names}}' | grep -i exchange | head -1)
|
|
echo "CTR=$EX"
|
|
code=$(curl -sS -m 5 -o /dev/null -w '%{http_code}' http://127.0.0.1:9011/config 2>/dev/null || echo 000)
|
|
echo "LOCAL_CONFIG=$code"
|
|
codek=$(curl -sS -m 8 -o /dev/null -w '%{http_code}' http://127.0.0.1:9011/keys 2>/dev/null || echo 000)
|
|
echo "LOCAL_KEYS=$codek"
|
|
if [ -n "$EX" ]; then
|
|
if podman exec "$EX" bash -c 'pgrep -f taler-exchange-httpd >/dev/null' 2>/dev/null; then
|
|
echo "HTTPD=1"
|
|
else
|
|
echo "HTTPD=0"
|
|
fi
|
|
for p in taler-exchange-secmod-rsa taler-exchange-secmod-eddsa taler-exchange-wirewatch taler-exchange-aggregator; do
|
|
if podman exec "$EX" bash -c "pgrep -f $p >/dev/null" 2>/dev/null; then
|
|
echo "PROC_$p=1"
|
|
else
|
|
echo "PROC_$p=0"
|
|
fi
|
|
done
|
|
if podman exec "$EX" test -x /usr/local/bin/check_exchange-health.sh 2>/dev/null; then
|
|
SKIP_ENSURE=1 podman exec -e SKIP_ENSURE=1 "$EX" /usr/local/bin/check_exchange-health.sh 2>&1 | sed 's/^/HEALTH /' | tail -25
|
|
fi
|
|
fi
|
|
REMOTE
|
|
)
|
|
echo "$EOUT" | grep -q '^CTR=.\+' && ok "exchange container $(echo "$EOUT" | sed -n 's/^CTR=//p' | head -1)" || fail "exchange container"
|
|
echo "$EOUT" | grep -q 'LOCAL_CONFIG=200' && ok "exchange local :9011/config" || fail "exchange local :9011/config"
|
|
echo "$EOUT" | grep -q 'LOCAL_KEYS=200' && ok "exchange local :9011/keys" || fail "exchange local :9011/keys"
|
|
echo "$EOUT" | grep -q 'HTTPD=1' && ok "exchange-httpd process" || warn "exchange-httpd process" "not detected"
|
|
echo "$EOUT" | grep -q 'PROC_taler-exchange-wirewatch=1' && ok "exchange wirewatch" || warn "exchange wirewatch" "not running"
|
|
echo "$EOUT" | grep -q 'PROC_taler-exchange-aggregator=1' && ok "exchange aggregator" || warn "exchange aggregator" "not running"
|
|
else
|
|
warn "exchange server-side" "ssh unavailable"
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
section "sanity · merchant"
|
|
# ---------------------------------------------------------------------------
|
|
expect_code "merchant public /config" 200 "$MERCHANT_PUBLIC/config"
|
|
expect_code "merchant public /webui/" 200 "$MERCHANT_PUBLIC/webui/"
|
|
|
|
code=$(http_body "$MERCHANT_PUBLIC/config" "$tmp/mer-config.json")
|
|
if [ "$code" = "200" ]; then
|
|
if python3 - "$tmp/mer-config.json" "$EXCHANGE_PUBLIC" <<'PY'
|
|
import json,sys
|
|
d=json.load(open(sys.argv[1]))
|
|
want=sys.argv[2].rstrip("/")
|
|
curs=list((d.get("currencies") or {}).keys())
|
|
ex=d.get("exchanges") or []
|
|
urls=[]
|
|
for e in ex:
|
|
if isinstance(e, dict):
|
|
u=e.get("base_url") or e.get("url") or e.get("exchange_base_url") or ""
|
|
if u: urls.append(u.rstrip("/"))
|
|
elif isinstance(e, str):
|
|
urls.append(e.rstrip("/"))
|
|
ok_goa = "GOA" in curs or any((e.get("currency") if isinstance(e, dict) else None)=="GOA" for e in ex)
|
|
ok_ex = any(want in u or "exchange.hacktivism.ch" in u for u in urls)
|
|
print("currencies", curs)
|
|
print("exchanges", urls[:5])
|
|
sys.exit(0 if ok_goa and ok_ex else 1)
|
|
PY
|
|
then
|
|
ok "merchant config GOA + exchange.hacktivism.ch"
|
|
else
|
|
fail "merchant config GOA + exchange" "see currencies/exchanges"
|
|
fi
|
|
if json_has_alt_unit_names "$tmp/mer-config.json" >/tmp/alt-mer-s.$$ 2>&1; then
|
|
ok "merchant currencies alt_unit_names" "$(tr '\n' '; ' </tmp/alt-mer-s.$$ | sed 's/; $//')"
|
|
else
|
|
fail "merchant currencies alt_unit_names" "$(tr '\n' '; ' </tmp/alt-mer-s.$$ | sed 's/; $//')"
|
|
fi
|
|
rm -f /tmp/alt-mer-s.$$
|
|
# Each exchange listed on merchant /config must publish alt_unit_names on its own /config
|
|
check_merchant_listed_exchanges_alt_units "$tmp/mer-config.json"
|
|
fi
|
|
|
|
# demo instance reachable?
|
|
INST="${MERCHANT_INSTANCE}"
|
|
icode=$(http_code "$MERCHANT_PUBLIC/instances/${INST}/config")
|
|
if [ "$icode" = "200" ]; then
|
|
ok "merchant instance ${INST} /config"
|
|
else
|
|
# some deployments use private only
|
|
warn "merchant instance ${INST} /config" "HTTP $icode"
|
|
fi
|
|
|
|
if koopa_ssh_ok; then
|
|
MOUT=$(koopa_ssh_bash 40 <<'REMOTE' || true
|
|
set +e
|
|
MER=$(podman ps --format '{{.Names}}' | grep -E '^taler-hacktivism$' | head -1)
|
|
[ -z "$MER" ] && MER=$(podman ps --format '{{.Names}}' | grep -iE 'merchant|hacktivism' | grep -viE 'bank|exchange' | head -1)
|
|
echo "CTR=$MER"
|
|
code=$(curl -skS -m 5 -o /dev/null -w '%{http_code}' https://127.0.0.1:9010/config 2>/dev/null || echo 000)
|
|
echo "LOCAL_CONFIG=$code"
|
|
if [ -n "$MER" ]; then
|
|
if podman exec "$MER" bash -c 'pgrep -f taler-merchant-httpd >/dev/null' 2>/dev/null; then
|
|
echo "HTTPD=1"
|
|
else
|
|
echo "HTTPD=0"
|
|
fi
|
|
if podman exec "$MER" test -x /usr/local/bin/check_merchant-health.sh 2>/dev/null; then
|
|
SKIP_ENSURE=1 podman exec -e SKIP_ENSURE=1 "$MER" /usr/local/bin/check_merchant-health.sh 2>&1 | sed 's/^/HEALTH /' | tail -30
|
|
fi
|
|
fi
|
|
REMOTE
|
|
)
|
|
echo "$MOUT" | grep -q '^CTR=.\+' && ok "merchant container $(echo "$MOUT" | sed -n 's/^CTR=//p' | head -1)" || fail "merchant container"
|
|
echo "$MOUT" | grep -q 'LOCAL_CONFIG=200' && ok "merchant local :9010/config" || fail "merchant local :9010/config"
|
|
echo "$MOUT" | grep -q 'HTTPD=1' && ok "merchant-httpd process" || warn "merchant-httpd process" "not detected"
|
|
if echo "$MOUT" | grep -q 'HEALTH '; then
|
|
if echo "$MOUT" | grep -qiE 'ALL CRITICAL|HEALTH_EC=0|\[OK\]'; then
|
|
ok "merchant check_merchant-health.sh (sample OK)"
|
|
else
|
|
warn "merchant check_merchant-health.sh" "see remote output"
|
|
fi
|
|
fi
|
|
else
|
|
warn "merchant server-side" "ssh unavailable"
|
|
fi
|
|
|
|
summary
|