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
|
|
@ -5,6 +5,8 @@
|
||||||
| `taler-merchant/` | podman `taler-hacktivism`: `/root`, `/usr/local/bin` |
|
| `taler-merchant/` | podman `taler-hacktivism`: `/root`, `/usr/local/bin` |
|
||||||
| `taler-exchange/` | podman `taler-hacktivism-exchange-ansible`: `/root`, `/usr/local/bin` |
|
| `taler-exchange/` | podman `taler-hacktivism-exchange-ansible`: `/root`, `/usr/local/bin` |
|
||||||
| `taler-hacktivism-bank/` | podman `taler-hacktivism-bank`: `/root`, `/usr/local/bin` |
|
| `taler-hacktivism-bank/` | podman `taler-hacktivism-bank`: `/root`, `/usr/local/bin` |
|
||||||
|
| `taler-bank/` | bank host helpers + **`maintenance/`** (debit limits, dry-run) |
|
||||||
|
| `taler-shared/` | host **ensure-taler-apps** (post-container app start + auto-confirm) |
|
||||||
| `taler-sanity/` | host root checks (stack, settlement, helpers) |
|
| `taler-sanity/` | host root checks (stack, settlement, helpers) |
|
||||||
| `taler-monitoring/` | **outside-in** public URL walk (`/config` → keys/terms/integration/webui) |
|
| `taler-monitoring/` | **outside-in** public URL walk (`/config` → keys/terms/integration/webui) |
|
||||||
| `monitoring/` | host `/home/hernani/scripts` (tor relay stats) |
|
| `monitoring/` | host `/home/hernani/scripts` (tor relay stats) |
|
||||||
|
|
@ -14,6 +16,14 @@
|
||||||
|
|
||||||
**Secrets:** never in this tree — sibling **`../koopa-admin-secrets`** (`koopa/host-root/<service>/` ↔ `/root/` on host; `containers/…/secrets/` for in-container).
|
**Secrets:** never in this tree — sibling **`../koopa-admin-secrets`** (`koopa/host-root/<service>/` ↔ `/root/` on host; `containers/…/secrets/` for in-container).
|
||||||
|
|
||||||
|
## Boot automation (host user systemd)
|
||||||
|
|
||||||
|
Merchant/bank containers stay **`sleep infinity`**. After the container unit
|
||||||
|
starts, **`taler-merchant-apps.service`** / **`taler-bank-apps.service`** run
|
||||||
|
`~/.local/bin/ensure-taler-apps.sh` (install via
|
||||||
|
`taler-shared/install-ensure-taler-apps.sh`). That runs the in-container
|
||||||
|
`start_base` + `start_*.sh` (and bank auto-confirm **2 s**).
|
||||||
|
|
||||||
## Manual start model (all three)
|
## Manual start model (all three)
|
||||||
|
|
||||||
1. **root** runs `/root/start_base_services_for_taler_*.sh`
|
1. **root** runs `/root/start_base_services_for_taler_*.sh`
|
||||||
|
|
|
||||||
47
scripts/taler-bank/maintenance/README.md
Normal file
47
scripts/taler-bank/maintenance/README.md
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
# taler-bank maintenance
|
||||||
|
|
||||||
|
One-shot ops tools for **libeufin-bank** on koopa (`taler-hacktivism-bank`).
|
||||||
|
Pattern matches **mytops-admin-log** `scripts/taler-merchant/maintenance/`: **dry-run by default**, `--no-dry` to apply.
|
||||||
|
|
||||||
|
## `raise-debit-limits.sh`
|
||||||
|
|
||||||
|
Raise **`debit_threshold`** on every bank account (overdraft / withdraw headroom).
|
||||||
|
Optionally set **`DEFAULT_DEBT_LIMIT`** for new accounts.
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|--|--|
|
||||||
|
| **Default amount** | `GOA:4503599627370496` (libeufin amount ceiling ≈ 2⁵²) |
|
||||||
|
| **Auth** | admin password: `BANK_ADMIN_PASS` or `bank-admin-password.txt` |
|
||||||
|
| **Bank** | `http://127.0.0.1:9012` on koopa, or `podman exec` into `taler-hacktivism-bank` |
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# on koopa (hernani), after bank is up:
|
||||||
|
cd ~/path/to/koopa-admin-log # or copy script
|
||||||
|
|
||||||
|
# preview
|
||||||
|
./scripts/taler-bank/maintenance/raise-debit-limits.sh
|
||||||
|
|
||||||
|
# apply to all accounts
|
||||||
|
./scripts/taler-bank/maintenance/raise-debit-limits.sh --no-dry
|
||||||
|
|
||||||
|
# apply + DEFAULT_DEBT_LIMIT in container overrides + bank restart
|
||||||
|
./scripts/taler-bank/maintenance/raise-debit-limits.sh --no-dry --set-default
|
||||||
|
|
||||||
|
# custom amount / single user
|
||||||
|
./scripts/taler-bank/maintenance/raise-debit-limits.sh --no-dry --amount GOA:1000000 --only explorer
|
||||||
|
```
|
||||||
|
|
||||||
|
Via container if host cannot reach :9012:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
podman exec -u root -i taler-hacktivism-bank bash -s -- --no-dry \
|
||||||
|
< scripts/taler-bank/maintenance/raise-debit-limits.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### Notes
|
||||||
|
|
||||||
|
- Does **not** invent balance; only raises how far accounts may go into **debit**.
|
||||||
|
- Exchange coin denominations (largest `GOA:1000`) are separate — large withdraws still work with many coins.
|
||||||
|
- Secrets never live in this repo — see `SECRETS.md` / `koopa-admin-secrets`.
|
||||||
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