Add bank maintenance script to raise all account debit limits.
mytops-style dry-run tool; PATCH debit_threshold for every libeufin account.
This commit is contained in:
parent
97b2fcce46
commit
36ef637501
3 changed files with 337 additions and 0 deletions
280
scripts/taler-bank/maintenance/raise-debit-limits.sh
Executable file
280
scripts/taler-bank/maintenance/raise-debit-limits.sh
Executable file
|
|
@ -0,0 +1,280 @@
|
|||
#!/usr/bin/env bash
|
||||
# Raise libeufin-bank debit thresholds for all accounts (and optionally the default).
|
||||
#
|
||||
# Style: like mytops-admin-log scripts/taler-merchant/maintenance/*.sh
|
||||
# dry-run by default; pass --no-dry to apply.
|
||||
#
|
||||
# Where to run:
|
||||
# - on koopa host (preferred): talks to BANK_URL (default http://127.0.0.1:9012)
|
||||
# - or: podman exec -u root -i taler-hacktivism-bank bash -s < this-script -- --no-dry
|
||||
#
|
||||
# Auth (first match wins):
|
||||
# BANK_ADMIN_PASS
|
||||
# /root/bank-admin-password.txt (inside bank container or host root secrets mirror)
|
||||
# $KOOPA_SECRETS/.../bank-admin-password.txt
|
||||
# ../koopa-admin-secrets/koopa/host-root/taler-bank/bank-admin-password.txt (relative to repo)
|
||||
#
|
||||
# Env / flags:
|
||||
# --no-dry apply changes
|
||||
# --amount GOA:N threshold (default: libeufin ceiling GOA:4503599627370496)
|
||||
# --set-default also set DEFAULT_DEBT_LIMIT in bank-overrides.conf (+ bank restart)
|
||||
# --only USER[,USER] only these usernames
|
||||
# BANK_URL default http://127.0.0.1:9012
|
||||
# BANK_CTR if set and BANK not reachable on host, use podman exec into container
|
||||
# (default: taler-hacktivism-bank when host curl fails)
|
||||
set -euo pipefail
|
||||
|
||||
echo "[INFO] raise-debit-limits — libeufin-bank account debit_threshold"
|
||||
echo "[INFO] Dry-run unless --no-dry. Tested against bank.hacktivism.ch (GOA)."
|
||||
|
||||
DRY_RUN=true
|
||||
SET_DEFAULT=false
|
||||
AMOUNT="${AMOUNT:-GOA:4503599627370496}"
|
||||
ONLY_USERS=""
|
||||
BANK_URL="${BANK_URL:-http://127.0.0.1:9012}"
|
||||
BANK_URL="${BANK_URL%/}"
|
||||
BANK_CTR="${BANK_CTR:-taler-hacktivism-bank}"
|
||||
OVERRIDE_CONF="${OVERRIDE_CONF:-/etc/libeufin/bank-overrides.conf}"
|
||||
|
||||
usage() {
|
||||
sed -n '2,30p' "$0" | sed 's/^# \{0,1\}//'
|
||||
exit 0
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--no-dry) DRY_RUN=false; echo "[WARN] Dry-run disabled — will PATCH accounts."; shift ;;
|
||||
--set-default) SET_DEFAULT=true; shift ;;
|
||||
--amount) AMOUNT="${2:?}"; shift 2 ;;
|
||||
--only) ONLY_USERS="${2:?}"; shift 2 ;;
|
||||
-h|--help) usage ;;
|
||||
*)
|
||||
echo "[ERROR] unknown arg: $1" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if $DRY_RUN; then
|
||||
echo "[INFO] Dry-run active (no HTTP PATCH, no conf write)."
|
||||
else
|
||||
echo "[WARN] APPLY mode amount=${AMOUNT}"
|
||||
fi
|
||||
|
||||
# --- admin password ---
|
||||
find_admin_pass() {
|
||||
if [[ -n "${BANK_ADMIN_PASS:-}" ]]; then
|
||||
printf '%s' "$BANK_ADMIN_PASS"
|
||||
return 0
|
||||
fi
|
||||
local f
|
||||
for f in \
|
||||
/root/bank-admin-password.txt \
|
||||
"${KOOPA_SECRETS:-}/koopa/host-root/taler-bank/bank-admin-password.txt" \
|
||||
"$(cd "$(dirname "$0")/../../../.." 2>/dev/null && pwd)/koopa-admin-secrets/koopa/host-root/taler-bank/bank-admin-password.txt" \
|
||||
"$HOME/src/koopa/koopa-admin-secrets/koopa/host-root/taler-bank/bank-admin-password.txt"
|
||||
do
|
||||
[[ -n "$f" && -f "$f" && -r "$f" ]] || continue
|
||||
tr -d '\n' <"$f"
|
||||
return 0
|
||||
done
|
||||
# hernani@koopa: secret lives in the bank container
|
||||
if command -v podman >/dev/null 2>&1 \
|
||||
&& podman inspect -f '{{.State.Running}}' "${BANK_CTR:-taler-hacktivism-bank}" 2>/dev/null | grep -qx true; then
|
||||
podman exec "${BANK_CTR:-taler-hacktivism-bank}" cat /root/bank-admin-password.txt 2>/dev/null | tr -d '\n'
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
ADMIN_PASS="$(find_admin_pass)" || {
|
||||
echo "[ERROR] no admin password (BANK_ADMIN_PASS or bank-admin-password.txt)" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Prefer host URL; fall back to podman exec curl inside bank container
|
||||
USE_PODMAN=false
|
||||
if ! curl -sf -m 3 "${BANK_URL}/config" >/dev/null 2>&1; then
|
||||
if command -v podman >/dev/null 2>&1 && podman inspect -f '{{.State.Running}}' "$BANK_CTR" 2>/dev/null | grep -qx true; then
|
||||
echo "[INFO] ${BANK_URL} not reachable — using podman exec ${BANK_CTR}"
|
||||
USE_PODMAN=true
|
||||
BANK_URL="http://127.0.0.1:9012"
|
||||
else
|
||||
echo "[ERROR] bank not reachable at ${BANK_URL} and container ${BANK_CTR} not running" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
bank_curl() {
|
||||
# bank_curl [curl-args...] URL_PATH_or_absolute
|
||||
# Last arg is URL path starting with / or full URL
|
||||
local args=("$@")
|
||||
local n=$((${#args[@]} - 1))
|
||||
local url="${args[$n]}"
|
||||
unset "args[$n]"
|
||||
if [[ "$url" != http* ]]; then
|
||||
url="${BANK_URL}${url}"
|
||||
fi
|
||||
if $USE_PODMAN; then
|
||||
podman exec -u root -i "$BANK_CTR" curl -sS -m 30 "${args[@]}" "$url"
|
||||
else
|
||||
curl -sS -m 30 "${args[@]}" "$url"
|
||||
fi
|
||||
}
|
||||
|
||||
bank_curl_code() {
|
||||
# like bank_curl but print HTTP code on stdout after body to fd3... simpler: write body to file
|
||||
local out="$1"; shift
|
||||
local args=("$@")
|
||||
local n=$((${#args[@]} - 1))
|
||||
local url="${args[$n]}"
|
||||
unset "args[$n]"
|
||||
if [[ "$url" != http* ]]; then
|
||||
url="${BANK_URL}${url}"
|
||||
fi
|
||||
if $USE_PODMAN; then
|
||||
podman exec -u root -i "$BANK_CTR" curl -sS -m 30 -o /tmp/raise-debt-body -w '%{http_code}' "${args[@]}" "$url"
|
||||
else
|
||||
curl -sS -m 30 -o "$out" -w '%{http_code}' "${args[@]}" "$url"
|
||||
fi
|
||||
}
|
||||
|
||||
echo "[INFO] bank=${BANK_URL} podman=${USE_PODMAN} amount=${AMOUNT}"
|
||||
|
||||
TOK_JSON=$(bank_curl -u "admin:${ADMIN_PASS}" -H 'Content-Type: application/json' \
|
||||
-d '{"scope":"readwrite"}' /accounts/admin/token)
|
||||
TOKEN=$(printf '%s' "$TOK_JSON" | python3 -c 'import sys,json; print(json.load(sys.stdin)["access_token"])')
|
||||
[[ -n "$TOKEN" ]] || { echo "[ERROR] admin token failed: $TOK_JSON" >&2; exit 1; }
|
||||
echo "[INFO] admin token OK"
|
||||
|
||||
ACCS_JSON=$(bank_curl -H "Authorization: Bearer ${TOKEN}" "/accounts?limit=500")
|
||||
export ACCS_JSON AMOUNT ONLY_USERS DRY_RUN TOKEN
|
||||
export BANK_URL USE_PODMAN BANK_CTR
|
||||
|
||||
python3 <<'PY'
|
||||
import json, os, sys, subprocess, urllib.request
|
||||
|
||||
amount = os.environ["AMOUNT"]
|
||||
only = {u.strip() for u in os.environ.get("ONLY_USERS", "").split(",") if u.strip()}
|
||||
dry = os.environ.get("DRY_RUN", "true").lower() in ("1", "true", "yes")
|
||||
token = os.environ["TOKEN"]
|
||||
bank = os.environ["BANK_URL"].rstrip("/")
|
||||
use_podman = os.environ.get("USE_PODMAN", "false").lower() in ("1", "true", "yes")
|
||||
ctr = os.environ.get("BANK_CTR", "taler-hacktivism-bank")
|
||||
|
||||
accs = json.loads(os.environ["ACCS_JSON"]).get("accounts") or []
|
||||
if only:
|
||||
accs = [a for a in accs if a.get("username") in only]
|
||||
print(f"[INFO] accounts to process: {len(accs)}")
|
||||
|
||||
def http_patch(user: str, body: dict) -> int:
|
||||
data = json.dumps(body).encode()
|
||||
url = f"{bank}/accounts/{user}"
|
||||
if use_podman:
|
||||
# avoid putting token in process list longer than needed — still visible
|
||||
cmd = [
|
||||
"podman", "exec", "-u", "root", "-i", ctr,
|
||||
"curl", "-sS", "-m", "30", "-o", "/dev/null", "-w", "%{http_code}",
|
||||
"-X", "PATCH",
|
||||
"-H", f"Authorization: Bearer {token}",
|
||||
"-H", "Content-Type: application/json",
|
||||
"-d", json.dumps(body),
|
||||
url.replace(bank, "http://127.0.0.1:9012") if bank.startswith("http") else f"http://127.0.0.1:9012/accounts/{user}",
|
||||
]
|
||||
# always use in-container localhost for podman path
|
||||
cmd[-1] = f"http://127.0.0.1:9012/accounts/{user}"
|
||||
out = subprocess.check_output(cmd, text=True).strip()
|
||||
return int(out)
|
||||
req = urllib.request.Request(
|
||||
url,
|
||||
data=data,
|
||||
method="PATCH",
|
||||
headers={
|
||||
"Authorization": f"Bearer {token}",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=30) as r:
|
||||
return r.status
|
||||
except Exception as e:
|
||||
code = getattr(e, "code", None)
|
||||
if code is not None:
|
||||
return int(code)
|
||||
print(f"[ERROR] {user}: {e}", file=sys.stderr)
|
||||
return 0
|
||||
|
||||
ok = fail = skip = 0
|
||||
for a in accs:
|
||||
u = a["username"]
|
||||
old = a.get("debit_threshold") or "?"
|
||||
bal = a.get("balance") or {}
|
||||
if old == amount:
|
||||
print(f"[SKIP] {u}: already {amount}")
|
||||
skip += 1
|
||||
continue
|
||||
if dry:
|
||||
print(f"[DRY] {u}: {old} -> {amount} (balance={bal})")
|
||||
ok += 1
|
||||
continue
|
||||
code = http_patch(u, {"debit_threshold": amount})
|
||||
if code in (200, 204):
|
||||
print(f"[EXECUTE] {u}: {old} -> {amount} HTTP {code}")
|
||||
ok += 1
|
||||
else:
|
||||
print(f"[FAIL] {u}: {old} -> {amount} HTTP {code}")
|
||||
fail += 1
|
||||
|
||||
print(f"[INFO] done ok={ok} skip={skip} fail={fail} dry={dry}")
|
||||
sys.exit(1 if fail else 0)
|
||||
PY
|
||||
|
||||
# Optional: DEFAULT_DEBT_LIMIT in overrides (new accounts)
|
||||
if $SET_DEFAULT; then
|
||||
echo "[INFO] --set-default: DEFAULT_DEBT_LIMIT in ${OVERRIDE_CONF}"
|
||||
if $USE_PODMAN; then
|
||||
conf_path="$OVERRIDE_CONF"
|
||||
if $DRY_RUN; then
|
||||
echo "[DRY] sed DEFAULT_DEBT_LIMIT = ${AMOUNT} in container:${conf_path}"
|
||||
echo "[DRY] restart libeufin-bank serve"
|
||||
else
|
||||
podman exec -u root "$BANK_CTR" bash -lc "
|
||||
set -e
|
||||
conf='$OVERRIDE_CONF'
|
||||
cp -a \"\$conf\" \"\${conf}.bak-\$(date +%Y%m%d%H%M%S)\"
|
||||
if grep -q '^DEFAULT_DEBT_LIMIT' \"\$conf\"; then
|
||||
sed -i 's/^DEFAULT_DEBT_LIMIT = .*/DEFAULT_DEBT_LIMIT = ${AMOUNT}/' \"\$conf\"
|
||||
else
|
||||
echo 'DEFAULT_DEBT_LIMIT = ${AMOUNT}' >>\"\$conf\"
|
||||
fi
|
||||
grep '^DEFAULT_DEBT_LIMIT' \"\$conf\"
|
||||
# restart bank process (manual stack)
|
||||
pids=\$(ps -eo pid=,args= | awk '/libeufin-bank serve/ && !/awk/ {print \$1}')
|
||||
for p in \$pids; do kill \$p 2>/dev/null || true; done
|
||||
sleep 1
|
||||
runuser -u libeufin-bank -- nohup /usr/bin/libeufin-bank serve -c /etc/libeufin/libeufin-bank.conf \
|
||||
>>/var/log/libeufin-bank/serve.log 2>&1 &
|
||||
for i in \$(seq 1 30); do
|
||||
curl -sf -m 2 http://127.0.0.1:9012/config >/dev/null && break
|
||||
sleep 0.5
|
||||
done
|
||||
curl -sS http://127.0.0.1:9012/config | python3 -c 'import sys,json; d=json.load(sys.stdin); print(\"[INFO] default_debit_threshold\", d.get(\"default_debit_threshold\"))'
|
||||
"
|
||||
echo "[EXECUTE] DEFAULT_DEBT_LIMIT + bank restart"
|
||||
fi
|
||||
else
|
||||
if [[ ! -f "$OVERRIDE_CONF" ]]; then
|
||||
echo "[WARN] ${OVERRIDE_CONF} not on host — skip --set-default (use from inside container or with podman path)"
|
||||
elif $DRY_RUN; then
|
||||
echo "[DRY] sed DEFAULT_DEBT_LIMIT = ${AMOUNT} in ${OVERRIDE_CONF}"
|
||||
else
|
||||
cp -a "$OVERRIDE_CONF" "${OVERRIDE_CONF}.bak-$(date +%Y%m%d%H%M%S)"
|
||||
sed -i "s/^DEFAULT_DEBT_LIMIT = .*/DEFAULT_DEBT_LIMIT = ${AMOUNT}/" "$OVERRIDE_CONF"
|
||||
echo "[EXECUTE] wrote ${OVERRIDE_CONF}"
|
||||
grep '^DEFAULT_DEBT_LIMIT' "$OVERRIDE_CONF" || true
|
||||
echo "[WARN] restart libeufin-bank yourself if conf is mounted from host"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "[INFO] Done."
|
||||
Loading…
Add table
Add a link
Reference in a new issue