#!/bin/bash # Health check for manual taler-merchant (container taler-hacktivism). # Style: check_exchange-health.sh — [OK] / [FAIL] / [WARN], exit 1 on critical fail. # # Keys checks: for each enabled [merchant-exchange-*] with EXCHANGE_BASE_URL, # GET …/keys and (if set) verify MASTER_KEY appears in the response. CONF="${TALER_MERCHANT_CONFIG:-/etc/taler-merchant/taler-merchant.conf}" OVERRIDES=/etc/taler-merchant/merchant-overrides.conf SOCK="/var/run/taler-merchant/httpd/merchant-http.sock" SS_BIN=$(command -v ss) CURL_BIN=$(command -v curl) green() { echo -e "\e[32m$1\e[0m"; } red() { echo -e "\e[31m$1\e[0m"; } yellow() { echo -e "\e[33m$1\e[0m"; } fail=0 ok() { green "[OK] $1"; } bad() { red "[FAIL] $1"; fail=1; } warn() { yellow "[WARN] $1"; } is_url() { case "$1" in http://*|https://*) return 0 ;; *) return 1 ;; esac } # GET /keys; try primary URL then optional host-local fallbacks for same exchange. # Writes body to $1 (path). Returns 0 on HTTP success with non-empty body. fetch_keys() { local out="$1" local primary="$2" shift 2 local url for url in "$primary" "$@"; do [ -z "$url" ] && continue if $CURL_BIN -skf -m 6 "$url" -o "$out" 2>/dev/null \ || $CURL_BIN -sf -m 6 "$url" -o "$out" 2>/dev/null; then if [ -s "$out" ] && grep -qE 'master_public_key|"currency"' "$out" 2>/dev/null; then echo "$url" return 0 fi fi done return 1 } echo "=== Taler Merchant Health Check ===" # --- 1–4: local service --- if [ -S "$SOCK" ]; then ok "socket exists: $SOCK" else bad "socket does NOT exist: $SOCK" fi if [ -n "$SS_BIN" ] && $SS_BIN -xl 2>/dev/null | grep -q "$SOCK"; then ok "Merchant-HTTPD listening on socket" elif [ -n "$SS_BIN" ]; then bad "no listener on socket" else warn "ss not available — skip socket listener check" fi if pgrep -f taler-merchant-httpd >/dev/null 2>&1; then ok "process taler-merchant-httpd" else bad "process taler-merchant-httpd is NOT running" fi if pgrep -f "nginx: master" >/dev/null 2>&1; then ok "nginx master process" else bad "nginx master process is NOT running" fi # --- 5: merchant /config --- if [ -n "$CURL_BIN" ]; then cfg_ok=0 if [ -S "$SOCK" ] && $CURL_BIN -sf -m 3 --unix-socket "$SOCK" "http://localhost/config" >/dev/null 2>&1; then ok "merchant /config via unix socket" cfg_ok=1 elif $CURL_BIN -skf -m 3 "https://127.0.0.1:9010/config" >/dev/null 2>&1; then ok "merchant /config via https://127.0.0.1:9010/config" cfg_ok=1 elif $CURL_BIN -sf -m 3 "http://127.0.0.1:9010/config" >/dev/null 2>&1; then ok "merchant /config via http://127.0.0.1:9010/config" cfg_ok=1 fi [ "$cfg_ok" -eq 0 ] && bad "merchant /config unreachable (socket and :9010)" else warn "curl missing — skip /config" fi # --- 6: exchange /keys for configured exchanges --- echo "--- configured exchanges (/keys) ---" # Emit lines: SECTION|DISABLED|BASE|MASTER (from overrides + optional taler-config) list_exchanges() { # Prefer site overrides file (authoritative for this host) if [ -f "$OVERRIDES" ]; then awk ' BEGIN { sec=""; dis="NO"; base=""; master="" } /^\[merchant-exchange-/ { if (sec != "") printf "%s|%s|%s|%s\n", sec, dis, base, master sec=$0; gsub(/[\[\] \t\r]/, "", sec) dis="NO"; base=""; master="" next } /^\[/ { if (sec != "") printf "%s|%s|%s|%s\n", sec, dis, base, master sec=""; next } sec=="" { next } /^[ \t]*#/ { next } { line=$0 sub(/[ \t]*#.*$/, "", line) if (match(line, /^[ \t]*DISABLED[ \t]*=[ \t]*/)) { dis=substr(line, RSTART+RLENGTH); gsub(/^[ \t]+|[ \t]+$/, "", dis) } else if (match(line, /^[ \t]*EXCHANGE_BASE_URL[ \t]*=[ \t]*/)) { base=substr(line, RSTART+RLENGTH); gsub(/^[ \t]+|[ \t]+$/, "", base) } else if (match(line, /^[ \t]*MASTER_KEY[ \t]*=[ \t]*/)) { master=substr(line, RSTART+RLENGTH); gsub(/^[ \t]+|[ \t]+$/, "", master) } } END { if (sec != "") printf "%s|%s|%s|%s\n", sec, dis, base, master } ' "$OVERRIDES" fi } if [ -z "$CURL_BIN" ]; then bad "curl missing — cannot check exchange /keys" else exch_count=0 while IFS='|' read -r sec dis base master; do [ -z "$sec" ] && continue case "${dis:-NO}" in YES|yes|true|True|1) warn "exchange $sec: DISABLED — skip /keys" continue ;; esac if ! is_url "$base"; then # package stubs without URL (e.g. kudos only DISABLED) warn "exchange $sec: no EXCHANGE_BASE_URL — skip" continue fi exch_count=$((exch_count + 1)) base_slash="${base%/}/" primary="${base_slash}keys" keys_tmp=$(mktemp 2>/dev/null || echo "/tmp/m-keys-$$-${exch_count}.json") # Fallbacks when public name is not reachable from container: # host loopback ports published by podman (exchange :9011). got_url= if got_url=$(fetch_keys "$keys_tmp" "$primary" \ "http://127.0.0.1:9011/keys" \ "http://host.containers.internal:9011/keys" \ "http://10.0.2.2:9011/keys"); then ok "exchange $sec: /keys reachable ($got_url)" if [ -n "$master" ]; then if grep -qF "$master" "$keys_tmp" 2>/dev/null; then ok "exchange $sec: MASTER_KEY matches /keys" else mpks=$(grep -oE '"master_public_key"[[:space:]]*:[[:space:]]*"[^"]+"' "$keys_tmp" 2>/dev/null | head -2) bad "exchange $sec: MASTER_KEY does not match /keys (config MASTER_KEY=$master)" [ -n "$mpks" ] && warn " seen in /keys: $mpks" fi else warn "exchange $sec: no MASTER_KEY in config — skip key match" fi else bad "exchange $sec: no /keys from $primary (and local :9011 fallbacks)" fi rm -f "$keys_tmp" 2>/dev/null || true done </dev/null 2>&1 } # Settlement needs wirewatch + depositcheck; others needed for ops. for p in \ taler-merchant-webhook \ taler-merchant-kyccheck \ taler-merchant-wirewatch \ taler-merchant-depositcheck \ taler-merchant-exchangekeyupdate \ taler-merchant-reconciliation do if live_helper "$p"; then ok "process $p" else bad "process $p not running" fi done if [ "$fail" -eq 0 ]; then green "=== ALL CRITICAL CHECKS PASSED ===" exit 0 fi red "=== SOME CHECKS FAILED ===" exit 1