diff --git a/scripts/ops/cleanup-koopa-all.sh b/scripts/ops/cleanup-koopa-all.sh new file mode 100755 index 0000000..5285676 --- /dev/null +++ b/scripts/ops/cleanup-koopa-all.sh @@ -0,0 +1,106 @@ +#!/bin/bash +# Clean agent scratch on koopa host (/root, /home/hernani, /tmp) + taler containers. +# KEEP: passwords, koopa-admin-log, user media, container configs/data/logs essential. +# Run as root on koopa. +set +e +[ "$(id -u)" -eq 0 ] || { echo "root only"; exit 1; } +OUT="${1:-/tmp/cleanup-koopa-all.out}" +exec > >(tee "$OUT") 2>&1 +n=0 +rmf() { + for f in "$@"; do + [ -e "$f" ] || [ -L "$f" ] || continue + rm -rf -- "$f" && n=$((n+1)) && echo "RM $f" + done +} + +echo "=== A. /tmp agent files ===" +# delete known agent patterns only (not systemd private dirs) +find /tmp -maxdepth 1 -type f \( \ + -name 'bench-*' -o -name 'settle-*' -o -name 'extreme-*' -o -name 'deploy-*' \ + -o -name 'force-*' -o -name 'fix-*' -o -name 'fix2*' -o -name 'wd*' \ + -o -name 'ord*' -o -name 'ex-*' -o -name 'tok*' -o -name 'facade*' \ + -o -name 'rev*' -o -name 'acc*' -o -name 'acw*' -o -name 'at.*' \ + -o -name 'et-*' -o -name '*settle*' -o -name '*wire*' -o -name '*offline*' \ + -o -name '*denom*' -o -name '*probe*' -o -name '*diag*' -o -name '*e2e*' \ + -o -name 'oid*' -o -name 'otok*' -o -name 'wuri*' -o -name 'wid*' \ + -o -name 'pay*' -o -name 'mk-*' -o -name 'koopa-*' -o -name 'incont*' \ + -o -name 'in-container*' -o -name 'credit*' -o -name 'apply-*' -o -name 'tos*' \ + -o -name 'redeploy*' -o -name 'ensure*' -o -name 'xfer*' -o -name 'tpl*' \ + -o -name 'future-keys.json' -o -name 'signed-keys.json' -o -name 'keys.json' \ + -o -name 'ex-acc*' -o -name 'bpw*' -o -name 'mpw*' -o -name 'wg*' \ + -o -name 'cleanup-*' -o -name 'koopa-clean-*' -o -name 'force-confirm*' \ + -o -name 'denom-probe*' -o -name 'deploy-large*' -o -name 'bench-setup*' \ + -o -name '*.out' -o -name '*.sh' -o -name '*.json' -o -name '*.sql' \ + -o -name '*.uri' \ + \) -print -delete 2>/dev/null +# leftover txt from agent (not system) +find /tmp -maxdepth 1 -type f -name '*.txt' \( -user root -o -user hernani \) -print -delete 2>/dev/null +echo "tmp files left: $(find /tmp -maxdepth 1 -type f 2>/dev/null | wc -l)" + +echo "=== B. /root loose agent files (keep *password*) ===" +for f in /root/*; do + base=$(basename "$f") + [ -e "$f" ] || continue + case "$base" in + *password*|*secret*|koopa-admin-log|.ssh|.bashrc|.profile|.bash_history) + echo "KEEP $f"; continue ;; + esac + # keep directories that look like system (not agent one-offs) + if [ -d "$f" ]; then + case "$base" in + koopa-admin-log|go|.cache|.config|.local) echo "KEEP dir $f"; continue ;; + esac + # only remove empty agent-looking dirs + continue + fi + case "$base" in + *.sh|*.out|*.json|*.uri|*.py|*.sql|*.log|*.conf) + rmf "$f" ;; + c1|chk-order|mk-order|d3|d4|d5|d6|d7|d8|dd|dd2|f1|fdo|fsn|ps|rl|sc|ss|ww4) + rmf "$f" ;; + esac +done +rmf /root/.tmp /root/.tmp-cleanup + +echo "=== C. /home/hernani agent staging ===" +H=/home/hernani +rmf "$H/ex-acc.secret.conf" "$H/ex-acc2.secret.conf" +rmf "$H/bank-bench-fat-password.txt" +find "$H" -maxdepth 1 -type f \( \ + -name '*.sh' -o -name '*.out' -o -name '*.json' -o -name '*.uri' -o -name '*.sql' \ + -o -name 'ex-acc*' -o -name 'exchange-coins.conf' -o -name 'bench-*' \ + -o -name 'oid-*' -o -name 'otok-*' -o -name 'wuri.txt' -o -name 'wid.txt' \ + -o -name 'pay*.uri' -o -name 'e2e-*' -o -name 'd[0-9]*' -o -name 'f[0-9]*' \ + -o -name 'ww*' -o -name 'dd*' -o -name 'fix-*' -o -name 'mk-*' \ + -o -name 'cycle-*' -o -name 'inside-*' -o -name 'curl' -o -name 'et-long.json' \ + -o -name 'find-delay*' -o -name 'deploy-delays*' -o -name 'offline-sign*' \ + -o -name 'in-container*' -o -name 'facade*' -o -name '*password*' \ + \) -print -delete 2>/dev/null +# chown anything we might have left wrong +chown -R hernani:hernani "$H" 2>/dev/null || true + +echo "=== D. containers (tmp/scratch only) ===" +# list +su - hernani -c 'podman ps --format "{{.Names}}"' | tee /tmp/pod-names.txt +while read -r c; do + [ -n "$c" ] || continue + echo "--- clean container $c ---" + su - hernani -c "podman exec -u root $c bash -c ' + set +e + # agent scratch in /tmp and /root + find /tmp -maxdepth 1 -type f \( -name \"*.out\" -o -name \"*.sh\" -o -name \"*.json\" -o -name \"future-keys.json\" -o -name \"signed-keys.json\" -o -name \"bench*\" -o -name \"settle*\" -o -name \"facade*\" -o -name \"ex-acc*\" -o -name \"wd*\" -o -name \"ord*\" -o -name \"tpl*\" -o -name \"keys.json\" -o -name \"in-container*\" -o -name \"offline*\" \) -delete 2>/dev/null + find /root -maxdepth 1 -type f \( -name \"*.out\" -o -name \"*.sh\" -o -name \"*.json\" -o -name \"in-container*\" -o -name \"offline-sign*\" -o -name \"bench*\" -o -name \"facade*\" \) ! -name \".bashrc\" ! -name \".profile\" -delete 2>/dev/null + # never delete /etc configs, /var/lib data, password secrets under /etc + echo tmp_files=\$(find /tmp -maxdepth 1 -type f 2>/dev/null | wc -l) + echo root_loose=\$(find /root -maxdepth 1 -type f 2>/dev/null | wc -l) + '" 2>&1 | tail -20 +done < /tmp/pod-names.txt + +echo "=== E. summary host ===" +echo "root:"; ls -la /root | head -40 +echo "hernani top:"; ls -la /home/hernani | head -30 +echo "tmp count: $(find /tmp -maxdepth 1 -type f 2>/dev/null | wc -l)" +echo "passwords:"; ls -la /root/*password* 2>/dev/null +echo "n_rm_hostish=$n" +echo CLEAN_ALL_OK diff --git a/scripts/ops/cleanup-root-scratch.sh b/scripts/ops/cleanup-root-scratch.sh new file mode 100644 index 0000000..99dc3e2 --- /dev/null +++ b/scripts/ops/cleanup-root-scratch.sh @@ -0,0 +1,84 @@ +#!/bin/bash +# Clean agent/ops clutter under /root on koopa (host). +# Preferred scratch is LOCAL: koopa-admin-log/.tmp/ (see ops/ROOT_HYGIENE.md). +# This script only removes leftover one-offs that landed in /root by mistake. +# +# Usage (as root on koopa): +# cleanup-root-scratch.sh +# cleanup-root-scratch.sh --dry-run +set -euo pipefail +[ "$(id -u)" -eq 0 ] || { echo "root only"; exit 1; } + +DRY=0 +[ "${1:-}" = "--dry-run" ] && DRY=1 + +# Staging on host only to delete (not the canonical scratch location) +TMP_BASE=/root/.tmp-cleanup +SESSION_DIR="$TMP_BASE/session-$(date -u +%Y%m%d-%H%M)-cleanup" +mkdir -p "$SESSION_DIR" + +# One-off probe filenames created during taler settle/withdraw debugging (not durable). +JUNK_GLOBS=( + c1.sh chk-order.sh mk-order.sh mk-secret.py + d3.sh d4.sh d5.sh d6.sh d7.sh d8.sh dd.sh dd2.sh + f1.sh fdo.sh fsn.sh fix-perm2.sh fix-ww.sh + ps.sh rl.sh sc.sh ss.sh ww4.sh + exchange-overrides.conf +) + +run() { + if [ "$DRY" -eq 1 ]; then + echo "DRY: $*" + else + eval "$@" + fi +} + +echo "=== move junk scripts to $SESSION_DIR ===" +mkdir -p "$SESSION_DIR" +moved=0 +for name in "${JUNK_GLOBS[@]}"; do + f="/root/$name" + if [ -e "$f" ]; then + run "mv -f $(printf %q "$f") $(printf %q "$SESSION_DIR/")" + echo " moved $name" + moved=$((moved + 1)) + fi +done +echo "moved=$moved" + +# Durable scripts that belong in admin-log — re-install from tree if present, remove loose copies +ADMIN="${KOOPA_ADMIN_LOG:-/root/koopa-admin-log}" +if [ ! -d "$ADMIN/scripts" ]; then + ADMIN=/home/hernani/koopa-admin-log +fi +for pair in \ + "auto-confirm-withdrawals.sh:taler-bank/auto-confirm-withdrawals.sh" \ + "make-demo-withdraw-qr.sh:taler-bank/make-demo-withdraw-qr.sh" +do + base="${pair%%:*}" + rel="${pair#*:}" + src="$ADMIN/scripts/$rel" + if [ -f "/root/$base" ]; then + if [ -f "$src" ]; then + echo " /root/$base is durable → install to /usr/local/bin, remove from /root" + run "install -m 755 $(printf %q "$src") /usr/local/bin/$base" + run "rm -f /root/$base" + else + echo " keep /root/$base (no admin-log copy at $src); move to $ADMIN/scripts if you can" + fi + fi +done + +echo "=== delete staged junk (canonical scratch is local koopa-admin-log/.tmp/) ===" +if [ "$DRY" -eq 1 ]; then + ls -la "$SESSION_DIR" 2>/dev/null || true +else + rm -rf "$TMP_BASE" + # remove mistaken host scratch if present + rm -rf /root/.tmp +fi + +echo "=== remaining non-dot files in /root ===" +ls -la /root | grep -v '^\.' | head -60 +echo "DONE" diff --git a/scripts/taler-dns/README.md b/scripts/taler-dns/README.md new file mode 100644 index 0000000..e77a83c --- /dev/null +++ b/scripts/taler-dns/README.md @@ -0,0 +1,48 @@ +# Container DNS pins (hacktivism.ch) + +Pasta networking often has **no usable DNS**. Wirewatch needs: + +```text +https://bank.hacktivism.ch/accounts/exchange/taler-wire-gateway/ +``` + +## Bake into images (preferred) + +On koopa: + +```bash +# copy bake assets, then: +SRC_DIR=/tmp/taler-dns-bake ./bake-hosts-into-images.sh +``` + +This: + +1. Installs `/usr/local/bin/pin-hacktivism-hosts` **inside** each container +2. Hooks `start_base_services_for_taler*.sh` (bank + merchant) +3. Enables systemd `pin-hacktivism-hosts.service` (exchange, before wirewatch) +4. **Commits** images: + - `localhost/taler-hacktivism-banking:live` (+ `:hosts-pinned`) + - `localhost/taler-hacktivism-live:landing` (+ `:hosts-pinned`) + - `localhost/taler-hacktivism-exchange-ansible:landing` (+ `:hosts-pinned`) + +Pin IP default: `212.51.151.254` (`PIN_IP=` to override). + +## One-shot without bake + +```bash +./pin-container-hosts.sh # apply now +./pin-container-hosts.sh --check # report resolve + /config +``` + +## After recreate + +If you start from an **old** image without the bake, run `pin-container-hosts.sh` once. +If you use `:hosts-pinned` / updated `:live`/`:landing` images, start_base / systemd re-pin on boot. + +Optional podman flags when recreating (extra safety): + +```bash +--add-host=bank.hacktivism.ch:212.51.151.254 \ +--add-host=exchange.hacktivism.ch:212.51.151.254 \ +--add-host=taler.hacktivism.ch:212.51.151.254 +``` diff --git a/scripts/taler-dns/bake-hosts-into-images.sh b/scripts/taler-dns/bake-hosts-into-images.sh new file mode 100644 index 0000000..8323ed5 --- /dev/null +++ b/scripts/taler-dns/bake-hosts-into-images.sh @@ -0,0 +1,116 @@ +#!/bin/bash +# Install in-container pin script (+ systemd on exchange), hook start_base scripts, +# commit images so recreate keeps the bake. +# +# Run on koopa: +# ./bake-hosts-into-images.sh +set -euo pipefail + +PIN_IP="${PIN_IP:-212.51.151.254}" +SRC_DIR="${SRC_DIR:-/tmp/taler-dns-bake}" +mkdir -p "$SRC_DIR" + +# expect these next to us or in SRC_DIR +SCRIPT_IN="${SCRIPT_IN:-$SRC_DIR/pin-hacktivism-hosts.in-container.sh}" +UNIT_IN="${UNIT_IN:-$SRC_DIR/pin-hacktivism-hosts.service}" + +[ -f "$SCRIPT_IN" ] || { echo "missing $SCRIPT_IN" >&2; exit 1; } + +install_into() { + local C="$1" + if ! podman ps --format '{{.Names}}' | grep -qx "$C"; then + echo "SKIP $C (not running)" + return 0 + fi + echo "=== install into $C ===" + podman cp "$SCRIPT_IN" "$C:/usr/local/bin/pin-hacktivism-hosts" + podman exec "$C" chmod 755 /usr/local/bin/pin-hacktivism-hosts + # run once now + podman exec -e PIN_IP="$PIN_IP" "$C" /usr/local/bin/pin-hacktivism-hosts + podman exec "$C" grep hacktivism /etc/hosts || true +} + +hook_start_base() { + local C="$1" base="$2" + podman exec "$C" bash -lc " + set -e + f='$base' + if [ ! -f \"\$f\" ]; then echo \"no \$f\"; exit 0; fi + if grep -q pin-hacktivism-hosts \"\$f\"; then + echo \"already hooked: \$f\" + exit 0 + fi + # insert right after shebang / first line block + cp \"\$f\" \"\$f.bak-before-pin\" + { + head -n 1 \"\$f\" + echo '' + echo '# Pin public GOA hostnames (pasta DNS). Baked by bake-hosts-into-images.sh' + echo 'if [ -x /usr/local/bin/pin-hacktivism-hosts ]; then' + echo ' PIN_IP=${PIN_IP} /usr/local/bin/pin-hacktivism-hosts || true' + echo 'fi' + tail -n +2 \"\$f\" + } > \"\$f.new\" + mv \"\$f.new\" \"\$f\" + chmod +x \"\$f\" + echo \"hooked \$f\" + " +} + +# --- bank --- +install_into taler-hacktivism-bank +hook_start_base taler-hacktivism-bank /root/start_base_services_for_taler_bank.sh + +# --- merchant --- +install_into taler-hacktivism +hook_start_base taler-hacktivism /root/start_base_services_for_taler.sh + +# --- exchange (systemd) --- +install_into taler-hacktivism-exchange-ansible +if [ -f "$UNIT_IN" ]; then + podman cp "$UNIT_IN" taler-hacktivism-exchange-ansible:/etc/systemd/system/pin-hacktivism-hosts.service + podman exec taler-hacktivism-exchange-ansible bash -lc ' + sed -i "s/PIN_IP=212.51.151.254/PIN_IP='"${PIN_IP}"'/" /etc/systemd/system/pin-hacktivism-hosts.service + systemctl daemon-reload + systemctl enable pin-hacktivism-hosts.service + systemctl start pin-hacktivism-hosts.service + systemctl is-enabled pin-hacktivism-hosts.service + # ensure wirewatch starts after pin + mkdir -p /etc/systemd/system/taler-exchange-wirewatch.service.d + cat > /etc/systemd/system/taler-exchange-wirewatch.service.d/pin-hosts.conf </dev/null || true + ' +fi + +# --- commit images --- +echo "=== commit images ===" +podman commit taler-hacktivism-bank \ + localhost/taler-hacktivism-banking:live +podman commit taler-hacktivism-bank \ + localhost/taler-hacktivism-banking:hosts-pinned + +podman commit taler-hacktivism \ + localhost/taler-hacktivism-live:landing +podman commit taler-hacktivism \ + localhost/taler-hacktivism-live:hosts-pinned + +podman commit taler-hacktivism-exchange-ansible \ + localhost/taler-hacktivism-exchange-ansible:landing +podman commit taler-hacktivism-exchange-ansible \ + localhost/taler-hacktivism-exchange-ansible:hosts-pinned + +echo +echo "Images updated:" +podman images | grep -E 'hacktivism|hosts-pinned' | head -20 +echo +echo "Verify:" +for C in taler-hacktivism-bank taler-hacktivism-exchange-ansible taler-hacktivism; do + echo -n "$C: " + podman exec "$C" getent ahostsv4 bank.hacktivism.ch 2>/dev/null | head -1 || echo FAIL +done +echo "done." diff --git a/scripts/taler-dns/pin-container-hosts.sh b/scripts/taler-dns/pin-container-hosts.sh new file mode 100755 index 0000000..619e5ac --- /dev/null +++ b/scripts/taler-dns/pin-container-hosts.sh @@ -0,0 +1,82 @@ +#!/bin/bash +# Pin public GOA hostnames inside Taler containers (pasta often has no useful DNS). +# Required so wirewatch can reach https://bank.hacktivism.ch/... wire-gateway. +# +# Run on koopa (host): +# ./pin-container-hosts.sh # apply + show resolve +# ./pin-container-hosts.sh --check # only show +# PIN_IP=212.51.151.254 ./pin-container-hosts.sh +set -euo pipefail + +CHECK_ONLY=0 +[ "${1:-}" = "--check" ] && CHECK_ONLY=1 + +DOMAINS=(bank.hacktivism.ch exchange.hacktivism.ch taler.hacktivism.ch) +CONTAINERS=( + taler-hacktivism-bank + taler-hacktivism-exchange-ansible + taler-hacktivism +) + +PIN_IP="${PIN_IP:-}" +if [ -z "$PIN_IP" ]; then + PIN_IP=$(getent ahostsv4 bank.hacktivism.ch 2>/dev/null | awk '{print $1; exit}' || true) +fi +[ -n "$PIN_IP" ] || PIN_IP=212.51.151.254 + +echo "PIN_IP=$PIN_IP" +echo "domains: ${DOMAINS[*]}" +echo + +pin_one() { + local C="$1" + if ! podman ps --format '{{.Names}}' | grep -qx "$C"; then + echo "=== $C === SKIP (not running)" + return 0 + fi + if [ "$CHECK_ONLY" != "1" ]; then + # Keep localhost lines; drop previous hacktivism pins; append ours + podman exec "$C" bash -lc " + set -e + TMP=\$(mktemp) + # keep non-hacktivism lines + if [ -f /etc/hosts ]; then + grep -vE 'hacktivism\\.ch|[[:space:]]bank\\.hacktivism|[[:space:]]exchange\\.hacktivism|[[:space:]]taler\\.hacktivism' /etc/hosts > \"\$TMP\" || true + fi + # ensure localhost + grep -qE '^127\\.0\\.0\\.1[[:space:]]+localhost' \"\$TMP\" 2>/dev/null || echo '127.0.0.1 localhost' >> \"\$TMP\" + grep -qE '^::1[[:space:]]' \"\$TMP\" 2>/dev/null || echo '::1 localhost ip6-localhost ip6-loopback' >> \"\$TMP\" + echo '${PIN_IP} bank.hacktivism.ch exchange.hacktivism.ch taler.hacktivism.ch' >> \"\$TMP\" + # de-dupe consecutive blank lines lightly + cat \"\$TMP\" > /etc/hosts + rm -f \"\$TMP\" + " + # wirewatch needs bank DNS after pin + if [ "$C" = "taler-hacktivism-exchange-ansible" ]; then + podman exec "$C" systemctl try-restart taler-exchange-wirewatch 2>/dev/null || true + fi + fi + + echo "=== $C ===" + podman exec "$C" grep -E 'hacktivism|localhost' /etc/hosts 2>/dev/null || true + for h in "${DOMAINS[@]}"; do + # ahostsv4 first (matches what wire tools need) + ip=$(podman exec "$C" getent ahostsv4 "$h" 2>/dev/null | awk '{print $1; exit}' || true) + [ -n "$ip" ] || ip=$(podman exec "$C" getent hosts "$h" 2>/dev/null | awk '{print $1; exit}' || true) + code=$(podman exec "$C" curl -skS -m 4 -o /dev/null -w '%{http_code}' "https://${h}/config" 2>/dev/null || echo 000) + if [ -n "$ip" ] && [ "$code" = "200" ]; then + echo " OK $h → $ip /config=$code" + elif [ -n "$ip" ]; then + echo " WARN $h → $ip /config=$code" + else + echo " FAIL $h → (no resolve) /config=$code" + fi + done + echo +} + +for C in "${CONTAINERS[@]}"; do + pin_one "$C" +done + +echo "done (CHECK_ONLY=$CHECK_ONLY)" diff --git a/scripts/taler-dns/pin-hacktivism-hosts.in-container.sh b/scripts/taler-dns/pin-hacktivism-hosts.in-container.sh new file mode 100644 index 0000000..abffe67 --- /dev/null +++ b/scripts/taler-dns/pin-hacktivism-hosts.in-container.sh @@ -0,0 +1,31 @@ +#!/bin/bash +# Runs *inside* a Taler container. Pasta regenerates /etc/hosts on start — +# call this from systemd (exchange) or start_base_*.sh (merchant/bank). +set -euo pipefail + +PIN_IP="${PIN_IP:-212.51.151.254}" +MARKER="# hacktivism-goa-pin" + +TMP=$(mktemp) +if [ -f /etc/hosts ]; then + # drop any previous pin marker lines and lines that name our domains + grep -vE "${MARKER}|hacktivism\\.ch" /etc/hosts >"$TMP" || true +else + : >"$TMP" +fi + +grep -qE '^127\.0\.0\.1[[:space:]]' "$TMP" 2>/dev/null \ + || echo '127.0.0.1 localhost' >>"$TMP" +grep -qE '^::1[[:space:]]' "$TMP" 2>/dev/null \ + || echo '::1 localhost ip6-localhost ip6-loopback' >>"$TMP" + +{ + cat "$TMP" + echo "$MARKER" + echo "${PIN_IP} bank.hacktivism.ch exchange.hacktivism.ch taler.hacktivism.ch" + echo "$MARKER" +} > /etc/hosts +rm -f "$TMP" + +logger -t pin-hacktivism-hosts "pinned ${PIN_IP} → bank/exchange/taler.hacktivism.ch" 2>/dev/null || true +exit 0 diff --git a/scripts/taler-dns/pin-hacktivism-hosts.service b/scripts/taler-dns/pin-hacktivism-hosts.service new file mode 100644 index 0000000..d944f86 --- /dev/null +++ b/scripts/taler-dns/pin-hacktivism-hosts.service @@ -0,0 +1,16 @@ +[Unit] +Description=Pin bank/exchange/taler.hacktivism.ch in /etc/hosts (pasta DNS) +DefaultDependencies=no +After=local-fs.target +Before=network-online.target taler-exchange-wirewatch.service taler-exchange-httpd.service +Wants=network-pre.target + +[Service] +Type=oneshot +RemainAfterExit=yes +Environment=PIN_IP=212.51.151.254 +ExecStart=/usr/local/bin/pin-hacktivism-hosts + +[Install] +WantedBy=multi-user.target +WantedBy=taler-exchange-wirewatch.service diff --git a/scripts/taler-sanity/README.md b/scripts/taler-sanity/README.md new file mode 100644 index 0000000..ad6a566 --- /dev/null +++ b/scripts/taler-sanity/README.md @@ -0,0 +1,31 @@ +# Taler sanity checks (koopa) + +Run **on koopa as root** (reads `/root/*-password.txt`, may use `podman` as `hernani`). + +| Script | What it checks | +|--------|----------------| +| `check_helpers-running.sh` | **No systemd:** starts missing exchange/merchant helpers (`ensure_*`), then requires transfer/aggregator/wirewatch/… | +| `check_stack-health.sh` | Public + local `/config` and exchange `/keys` HTTP 200; merchant has CHF+GOA | +| `check_merchant-delays.sh` | Global + instance delays are short (pay/refund/wire ≤ 120s) | +| `check_exchange-wirewatch.sh` | Wire gateway reachable with **bearer**; auth method; hosts pin | +| `check_settlement.sh` | Paid orders: `wired`, deposit_total, bank balances/transfers | +| `run_all.sh` | Runs all of the above | + +Helper ensure scripts (in containers, no systemd): + +| Script | Role | +|--------|------| +| `../taler-exchange/ensure_exchange_helpers.sh` | `nohup` aggregator, closer, wirewatch, **transfer** | +| `../taler-merchant/ensure_merchant_helpers.sh` | `nohup` wirewatch, depositcheck, kyccheck, webhook, … | +| Health: `check_*-health.sh` | Calls ensure (unless `SKIP_ENSURE=1`), then **FAIL** if still missing | + +```bash +# on koopa +cd /path/to/koopa-admin-log/scripts/taler-sanity +./run_all.sh +./check_settlement.sh 2026.190-03V36SPAZ8CK6 +``` + +Env overrides: `BANK_URL`, `MERCHANT_URL`, `EXCHANGE_PUBLIC`, `BANK_PUBLIC`, `MERCHANT_PUBLIC`, `MERCHANT_INSTANCE`. + +See also: `../taler-wallet-cli/` for end-to-end withdraw → pay → settlement. diff --git a/scripts/taler-sanity/check_exchange-wirewatch.sh b/scripts/taler-sanity/check_exchange-wirewatch.sh new file mode 100644 index 0000000..89dbb7c --- /dev/null +++ b/scripts/taler-sanity/check_exchange-wirewatch.sh @@ -0,0 +1,92 @@ +#!/bin/bash +# Sanity: exchange can read bank wire gateway (bearer + public URL). +# Run on koopa as root (needs /root/bank-exchange-password.txt + podman). +set -euo pipefail +ROOT=$(cd "$(dirname "$0")" && pwd) +# shellcheck source=lib.sh +source "$ROOT/lib.sh" +need_root + +echo "=== Exchange wirewatch / wire gateway ===" + +if ! read_pw EPW /root/bank-exchange-password.txt; then + fail "missing /root/bank-exchange-password.txt" + exit 1 +fi + +ET=$(bank_token exchange "$EPW") +if [ -z "$ET" ]; then + fail "could not get bank token for exchange user" + exit 1 +fi +pass "bank token for exchange user" + +# History must work with Bearer (Basic is rejected by libeufin on history) +tmp=$(mktemp) +code=$(curl -sk -m 15 -o "$tmp" -w '%{http_code}' \ + -H "Authorization: Bearer ${ET}" \ + "${BANK_PUBLIC}/accounts/exchange/taler-wire-gateway/history/incoming?delta=-10") +if [ "$code" = "200" ]; then + pass "wire gateway history via ${BANK_PUBLIC} (HTTP $code)" + python3 -c 'import json,sys; d=json.load(open(sys.argv[1])); print(" incoming_count", len(d.get("incoming_transactions") or []))' "$tmp" +else + # fallback local + code2=$(curl -sS -m 15 -o "$tmp" -w '%{http_code}' \ + -H "Authorization: Bearer ${ET}" \ + "${BANK_URL}/accounts/exchange/taler-wire-gateway/history/incoming?delta=-10") + if [ "$code2" = "200" ]; then + warn "public wire gateway HTTP $code; local BANK_URL OK ($code2) — check DNS/hosts in exchange container" + else + fail "wire gateway history public=$code local=$code2" + fi +fi +rm -f "$tmp" + +# Basic must fail on history (documents expected auth mode) +code_b=$(curl -sk -m 10 -o /dev/null -w '%{http_code}' \ + -u "exchange:${EPW}" \ + "${BANK_URL}/accounts/exchange/taler-wire-gateway/history/incoming?delta=-5") +if [ "$code_b" = "401" ]; then + pass "Basic auth correctly rejected on history ($code_b) — use bearer TOKEN" +else + warn "Basic on history returned $code_b (expected 401)" +fi + +# Container credential + wirewatch process +if su - hernani -c 'podman exec taler-exchange-hacktivism true' 2>/dev/null; then + su - hernani -c 'podman exec taler-exchange-hacktivism bash -c " + set +e + echo \"--- secret (redacted) ---\" + if [ -r /etc/taler-exchange/secrets/exchange-accountcredentials-1.secret.conf ]; then + sed \"s/secret-token:.*/secret-token:***/; s/^TOKEN = .*/TOKEN = ***/; s/^PASSWORD = .*/PASSWORD = ***/\" \ + /etc/taler-exchange/secrets/exchange-accountcredentials-1.secret.conf + else + # root-only file: cat as root via outer exec + true + fi + echo \"--- hosts bank ---\" + grep bank.hacktivism.ch /etc/hosts || echo \"(no hosts pin)\" + echo \"--- wirewatch ---\" + ps -eo pid,stat,args | grep \"[w]irewatch\" || echo \"(no wirewatch process)\" + "' 2>&1 || warn "podman exec exchange failed" + + AUTH_METHOD=$(su - hernani -c 'podman exec -u root taler-exchange-hacktivism \ + grep -E "^WIRE_GATEWAY_AUTH_METHOD" /etc/taler-exchange/secrets/exchange-accountcredentials-1.secret.conf' 2>/dev/null | awk '{print $3}') + GW_URL=$(su - hernani -c 'podman exec -u root taler-exchange-hacktivism \ + grep -E "^WIRE_GATEWAY_URL" /etc/taler-exchange/secrets/exchange-accountcredentials-1.secret.conf' 2>/dev/null | awk '{print $3}') + info "WIRE_GATEWAY_URL=$GW_URL" + info "WIRE_GATEWAY_AUTH_METHOD=$AUTH_METHOD" + if [ "$AUTH_METHOD" = "bearer" ]; then pass "auth method bearer" + else fail "auth method is '$AUTH_METHOD' (want bearer + TOKEN=)" + fi + case "$GW_URL" in + https://bank.hacktivism.ch/*) pass "gateway uses public bank URL" ;; + http://127.*|http://host.containers*) warn "gateway uses local URL: $GW_URL" ;; + *) warn "unexpected gateway URL: $GW_URL" ;; + esac +else + warn "exchange container not reachable via podman" +fi + +echo "=== summary fails=$FAILS ===" +exit "$FAILS" diff --git a/scripts/taler-sanity/check_helpers-running.sh b/scripts/taler-sanity/check_helpers-running.sh new file mode 100755 index 0000000..e4042f3 --- /dev/null +++ b/scripts/taler-sanity/check_helpers-running.sh @@ -0,0 +1,81 @@ +#!/bin/bash +# Sanity: ensure exchange + merchant helpers are up (no systemd). +# Runs on koopa as root; uses podman into both containers. +set -euo pipefail +ROOT=$(cd "$(dirname "$0")" && pwd) +# shellcheck source=lib.sh +source "$ROOT/lib.sh" +need_root + +fail=0 +pass() { echo "OK $1"; } +warn() { echo "WARN $1"; } +bad() { echo "FAIL $1"; fail=$((fail + 1)); } + +EXC_CT="${EXCHANGE_CONTAINER:-taler-exchange-hacktivism}" +MER_CT="${MERCHANT_CONTAINER:-taler-hacktivism}" + +echo "=== ensure + check helpers (no systemd) ===" + +# Deploy/ensure scripts if present on host under admin-log mount or /tmp +deploy_into() { + local ct="$1" + local src="$2" + local dst="$3" + if [ -f "$src" ]; then + su - hernani -c "podman cp $(printf %q "$src") ${ct}:${dst}" 2>/dev/null \ + || podman cp "$src" "${ct}:${dst}" 2>/dev/null || true + su - hernani -c "podman exec ${ct} chmod 755 ${dst}" 2>/dev/null || true + fi +} + +ADMIN="${KOOPA_ADMIN_LOG:-/home/hernani/koopa-admin-log}" +# Prefer workspace copy if synced; else scripts next to this file +EX_SCRIPTS="$ROOT/../taler-exchange" +MER_SCRIPTS="$ROOT/../taler-merchant" +[ -d "$ADMIN/scripts/taler-exchange" ] && EX_SCRIPTS="$ADMIN/scripts/taler-exchange" +[ -d "$ADMIN/scripts/taler-merchant" ] && MER_SCRIPTS="$ADMIN/scripts/taler-merchant" + +deploy_into "$EXC_CT" "$EX_SCRIPTS/ensure_exchange_helpers.sh" /usr/local/bin/ensure_exchange_helpers.sh +deploy_into "$EXC_CT" "$EX_SCRIPTS/check_exchange-health.sh" /usr/local/bin/check_exchange-health.sh +deploy_into "$MER_CT" "$MER_SCRIPTS/ensure_merchant_helpers.sh" /usr/local/bin/ensure_merchant_helpers.sh +deploy_into "$MER_CT" "$MER_SCRIPTS/check_merchant-health.sh" /usr/local/bin/check_merchant-health.sh + +echo "--- exchange container: ensure ---" +if su - hernani -c "podman exec $EXC_CT /usr/local/bin/ensure_exchange_helpers.sh"; then + pass "exchange helpers ensure" +else + bad "exchange helpers ensure failed" +fi + +echo "--- merchant container: ensure ---" +if su - hernani -c "podman exec $MER_CT /usr/local/bin/ensure_merchant_helpers.sh"; then + pass "merchant helpers ensure" +else + bad "merchant helpers ensure failed" +fi + +echo "--- process presence ---" +check_ct_proc() { + local ct="$1" + local p="$2" + # COMM is 15 chars — match full cmdline + if su - hernani -c "podman exec $ct pgrep -f '(^|/)${p}( |$)'" >/dev/null 2>&1; then + pass "$ct: $p" + else + bad "$ct: $p missing" + fi +} + +for p in taler-exchange-httpd taler-exchange-aggregator taler-exchange-transfer \ + taler-exchange-wirewatch taler-exchange-closer; do + check_ct_proc "$EXC_CT" "$p" +done +for p in taler-merchant-httpd taler-merchant-wirewatch taler-merchant-depositcheck \ + taler-merchant-webhook taler-merchant-kyccheck \ + taler-merchant-exchangekeyupdate taler-merchant-reconciliation; do + check_ct_proc "$MER_CT" "$p" +done + +echo "=== summary fails=$fail ===" +exit "$fail" diff --git a/scripts/taler-sanity/check_merchant-delays.sh b/scripts/taler-sanity/check_merchant-delays.sh new file mode 100644 index 0000000..10b47ef --- /dev/null +++ b/scripts/taler-sanity/check_merchant-delays.sh @@ -0,0 +1,72 @@ +#!/bin/bash +# Sanity: merchant global + demo instance delays are demo-short (pay/refund/wire). +# Run on koopa (network to :9010 / public merchant). +set -euo pipefail +ROOT=$(cd "$(dirname "$0")" && pwd) +# shellcheck source=lib.sh +source "$ROOT/lib.sh" + +# Expected upper bounds (seconds) — fail if longer (package defaults 1d/15d/1w) +MAX_PAY_S=${MAX_PAY_S:-120} +MAX_REFUND_S=${MAX_REFUND_S:-120} +MAX_WIRE_S=${MAX_WIRE_S:-120} + +echo "=== Merchant delay defaults (expect short demo values) ===" + +tmp=$(mktemp) +curl -sk -m 12 -o "$tmp" "${MERCHANT_PUBLIC}/config" || curl -sk -m 12 -o "$tmp" "${MERCHANT_URL}/config" +python3 - "$tmp" "$MAX_PAY_S" "$MAX_REFUND_S" "$MAX_WIRE_S" <<'PY' +import json,sys +d=json.load(open(sys.argv[1])) +max_pay, max_ref, max_wire = map(float, sys.argv[2:5]) +fails=0 +for key, mx in ( + ("default_pay_delay", max_pay), + ("default_refund_delay", max_ref), + ("default_wire_transfer_delay", max_wire), +): + us=d.get(key,{}).get("d_us") + s=(us or 0)/1e6 + ok = us is not None and s <= mx + print(f"{'OK' if ok else 'FAIL'} {key}: {s:.0f}s (max {mx:.0f}s)") + if not ok: fails+=1 +print("currency", d.get("currency")) +sys.exit(fails) +PY +ec=$? +rm -f "$tmp" +FAILS=$((FAILS + ec)) + +# Instance goa-demo if password present +if [ -f /root/merchant-goa-demo-cp4zqk-password.txt ]; then + MPW=$(tr -d '\n' = wd and not d.get("wired"): + print(" FAIL wire deadline passed but wired=false") + sys.exit(2) + if now < wd: + print(" INFO still before wire deadline — settlement not due yet") + if d.get("wired"): + print(" OK wired=true") +sys.exit(0) +PY + ec=$? + [ "$ec" -eq 2 ] && FAILS=$((FAILS + 1)) + rm -f "$ot" +done + +echo "=== private/transfers ===" +tr=$(mktemp) +curl -sk -m 15 -H "$AUTH" -o "$tr" \ + "${MERCHANT_URL}/instances/${INST}/private/transfers" +python3 -c 'import json,sys; d=json.load(open(sys.argv[1])); t=d.get("transfers") or []; print(f" transfers={len(t)}"); +[print(" ", x) for x in t[:10]]' "$tr" +rm -f "$tr" "$tmp" + +# Bank balances +BANK_USER="${BANK_USER:-$INST}" +if read_pw BPW "/root/bank-${BANK_USER}-password.txt" || read_pw BPW /root/bank-goa-demo-cp4zqk-password.txt; then + BT=$(bank_token "$BANK_USER" "$BPW") + if [ -n "$BT" ]; then + curl -sS -m 12 -H "Authorization: Bearer ${BT}" \ + "${BANK_URL}/accounts/${BANK_USER}" | python3 -c 'import sys,json; d=json.load(sys.stdin); print("merchant_bank_balance", d.get("balance"))' + curl -sS -m 12 -H "Authorization: Bearer ${BT}" \ + "${BANK_URL}/accounts/${BANK_USER}/transactions?delta=-10" \ + | python3 -c 'import sys,json; d=json.load(sys.stdin); txs=d.get("transactions") or []; print(f"merchant_bank_tx={len(txs)}"); +[print(f" {t.get(\"direction\")} {t.get(\"amount\")} {t.get(\"subject\",\"\")[:60]}") for t in txs[:8]]' + fi +fi + +if read_pw EPW /root/bank-exchange-password.txt; then + ET=$(bank_token exchange "$EPW") + if [ -n "$ET" ]; then + curl -sS -m 12 -H "Authorization: Bearer ${ET}" \ + "${BANK_URL}/accounts/exchange" | python3 -c 'import sys,json; d=json.load(sys.stdin); print("exchange_bank_balance", d.get("balance"))' + fi +fi + +echo "=== summary fails=$FAILS ===" +exit "$FAILS" diff --git a/scripts/taler-sanity/check_stack-health.sh b/scripts/taler-sanity/check_stack-health.sh new file mode 100644 index 0000000..1d39fe0 --- /dev/null +++ b/scripts/taler-sanity/check_stack-health.sh @@ -0,0 +1,73 @@ +#!/bin/bash +# Sanity: public + local Taler endpoints respond (GOA stack). +# Run on koopa as root (or any user with network to services). +set -euo pipefail +ROOT=$(cd "$(dirname "$0")" && pwd) +# shellcheck source=lib.sh +source "$ROOT/lib.sh" + +echo "=== Taler stack health ===" + +# Public HTTPS (via Caddy / VeciGate) +for url in \ + "${EXCHANGE_PUBLIC}/config" \ + "${EXCHANGE_PUBLIC}/keys" \ + "${BANK_PUBLIC}/config" \ + "${MERCHANT_PUBLIC}/config" +do + code=$(http_code "$url") + if [ "$code" = "200" ]; then pass "$url -> $code" + else fail "$url -> $code" + fi +done + +# Local loopback ports (containers via pasta) +for spec in \ + "bank ${BANK_URL}/config" \ + "merchant ${MERCHANT_URL}/config" \ + "exchange-via-public ${EXCHANGE_PUBLIC}/config" +do + name=${spec%% *} + url=${spec#* } + code=$(http_code "$url") + if [ "$code" = "200" ]; then pass "local $name -> $code" + else fail "local $name -> $code" + fi +done + +# Merchant multi-currency +tmp=$(mktemp) +curl -sk -m 12 -o "$tmp" "${MERCHANT_PUBLIC}/config" || true +if python3 - "$tmp" <<'PY' +import json,sys +d=json.load(open(sys.argv[1])) +ex=[e.get("currency") for e in d.get("exchanges") or []] +cur=list((d.get("currencies") or {}).keys()) +ok = "GOA" in ex and "CHF" in ex and "GOA" in cur +print("currency_default", d.get("currency")) +print("exchanges", ex) +print("currencies", cur) +sys.exit(0 if ok else 1) +PY +then pass "merchant has CHF + GOA exchanges" +else fail "merchant missing CHF/GOA in /config" +fi +rm -f "$tmp" + +# Exchange currency GOA +tmp=$(mktemp) +curl -sk -m 12 -o "$tmp" "${EXCHANGE_PUBLIC}/config" || true +if python3 - "$tmp" <<'PY' +import json,sys +d=json.load(open(sys.argv[1])) +c=(d.get("currency") or d.get("currency_specification",{}).get("currency")) +print("exchange_currency", c) +sys.exit(0 if c=="GOA" else 1) +PY +then pass "exchange currency GOA" +else fail "exchange currency not GOA" +fi +rm -f "$tmp" + +echo "=== summary fails=$FAILS ===" +exit "$FAILS" diff --git a/scripts/taler-sanity/lib.sh b/scripts/taler-sanity/lib.sh new file mode 100644 index 0000000..851fa9d --- /dev/null +++ b/scripts/taler-sanity/lib.sh @@ -0,0 +1,60 @@ +# shellcheck shell=bash +# Common helpers for Taler sanity checks (run on koopa host as root). +# shellcheck disable=SC2034 + +: "${BANK_URL:=http://127.0.0.1:9012}" +: "${MERCHANT_URL:=https://127.0.0.1:9010}" +: "${EXCHANGE_PUBLIC:=https://exchange.hacktivism.ch}" +: "${BANK_PUBLIC:=https://bank.hacktivism.ch}" +: "${MERCHANT_PUBLIC:=https://taler.hacktivism.ch}" + +pass() { echo "OK $*"; } +fail() { echo "FAIL $*"; FAILS=$((FAILS + 1)); } +warn() { echo "WARN $*"; } +info() { echo "INFO $*"; } + +FAILS=0 + +need_root() { + if [ "$(id -u)" -ne 0 ]; then + echo "This check expects root on koopa (password files under /root)." >&2 + exit 2 + fi +} + +read_pw() { + # read_pw VAR path + local _v="$1" _p="$2" + if [ ! -f "$_p" ]; then + eval "$_v=" + return 1 + fi + eval "$_v=\$(tr -d '\\n' <\"$_p\")" +} + +bank_token() { + # bank_token USER PASS -> prints access_token + local user="$1" pass="$2" + curl -sS -m 15 -u "${user}:${pass}" \ + -H 'Content-Type: application/json' \ + -d '{"scope":"readwrite","refreshable":true}' \ + "${BANK_URL}/accounts/${user}/token" \ + | python3 -c 'import sys,json; print(json.load(sys.stdin).get("access_token",""))' +} + +merchant_auth_header() { + # merchant_auth_header PASSWORD + printf 'Authorization: Bearer secret-token:%s' "$1" +} + +http_code() { + # http_code URL [curl args...] + local url="$1" + shift + curl -sk -m 12 -o /dev/null -w '%{http_code}' "$@" "$url" 2>/dev/null || echo "000" +} + +json_get() { + # json_get FILE python-expr-on-d + python3 -c "import json,sys; d=json.load(open(sys.argv[1])); print($2)" "$1" +} diff --git a/scripts/taler-sanity/run_all.sh b/scripts/taler-sanity/run_all.sh new file mode 100644 index 0000000..e33f68d --- /dev/null +++ b/scripts/taler-sanity/run_all.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# Run all sanity checks; exit non-zero if any failed. +set -euo pipefail +ROOT=$(cd "$(dirname "$0")" && pwd) +ec=0 +for s in \ + check_helpers-running.sh \ + check_stack-health.sh \ + check_merchant-delays.sh \ + check_exchange-wirewatch.sh \ + check_settlement.sh +do + echo "" + echo "########## $s ##########" + if bash "$ROOT/$s"; then + echo "PASS $s" + else + echo "FAIL $s (exit $?)" + ec=1 + fi +done +exit $ec diff --git a/scripts/taler-wallet-cli/run-amount-ladder-bench.sh b/scripts/taler-wallet-cli/run-amount-ladder-bench.sh new file mode 100755 index 0000000..a06aa0e --- /dev/null +++ b/scripts/taler-wallet-cli/run-amount-ladder-bench.sh @@ -0,0 +1,3 @@ +#!/bin/bash +# Compatibility wrapper — canonical benchmark lives under benchmarks/amount-ladder/ +exec "$(cd "$(dirname "$0")/../.." && pwd)/benchmarks/amount-ladder/run.sh" "$@" diff --git a/scripts/taler-wallet-cli/run-extreme-amount-tests.sh b/scripts/taler-wallet-cli/run-extreme-amount-tests.sh new file mode 100755 index 0000000..1774304 --- /dev/null +++ b/scripts/taler-wallet-cli/run-extreme-amount-tests.sh @@ -0,0 +1,350 @@ +#!/bin/bash +# Extreme amount tests for GOA: largest practical price vs atomic/tiny coin. +# +# Live denoms (exchange.hacktivism.ch): GOA:0.000001 … GOA:10 +# Currency TINY / round unit: GOA:0.00000001 (Atomic-GOA) +# +# Run on the local machine (wallet) with ssh BatchMode to koopa for bank/merchant ops. +# Requires: monorepo taler-wallet-cli, passwords on koopa /root/*-password.txt +# +# Usage: +# ./run-extreme-amount-tests.sh +# HIGH_AMOUNT=GOA:500 TINY_AMOUNT=GOA:0.000001 ./run-extreme-amount-tests.sh +set -euo pipefail + +ROOT=$(cd "$(dirname "$0")/../.." && pwd) +SCRATCH="${ROOT}/.tmp/extreme-$(date -u +%Y%m%d-%H%M%S)" +mkdir -p "$SCRATCH" +chmod 700 "$SCRATCH" + +MONO="${MONO:-/Users/newkamek/src/taler/taler-typescript-core/packages/taler-wallet-cli/bin/taler-wallet-cli.mjs}" +WDB="${WDB:-$SCRATCH/wallet.sqlite3}" +EX="${EXCHANGE_URL:-https://exchange.hacktivism.ch/}" +KOOPA="${KOOPA_HOST:-koopa}" +INST="${MERCHANT_INSTANCE:-goa-demo-cp4zqk}" +BANK_PUBLIC="${BANK_PUBLIC:-https://bank.hacktivism.ch}" +MER_PUBLIC="${MER_PUBLIC:-https://taler.hacktivism.ch}" + +# High price: many max-denom (GOA:10) coins. Tiny: smallest issued coin. +HIGH_AMOUNT="${HIGH_AMOUNT:-GOA:1000}" +TINY_AMOUNT="${TINY_AMOUNT:-GOA:0.000001}" +# True atomic (round unit) — may fail if no coin below 1e-6 +ATOMIC_AMOUNT="${ATOMIC_AMOUNT:-GOA:0.00000001}" +# Fat bank balance for withdraw buffer +FAT_CREDIT="${FAT_CREDIT:-GOA:50000}" +FAT_USER="${FAT_USER:-extreme-fat}" +# withdraw a bit more than HIGH +WITHDRAW_AMOUNT="${WITHDRAW_AMOUNT:-GOA:1500}" + +wcli() { node "$MONO" --wallet-db="$WDB" --no-throttle --skip-defaults "$@"; } + +log() { printf '%s %s\n' "$(date -u +%H:%M:%S)" "$*"; } +die() { echo "FAIL: $*" >&2; exit 1; } + +command -v node >/dev/null || die "node missing" +[ -f "$MONO" ] || die "wallet missing: $MONO" +ssh -o BatchMode=yes -o ConnectTimeout=10 "$KOOPA" 'echo ok' >/dev/null || die "ssh $KOOPA failed" + +log "scratch=$SCRATCH" +log "HIGH=$HIGH_AMOUNT TINY=$TINY_AMOUNT ATOMIC=$ATOMIC_AMOUNT WITHDRAW=$WITHDRAW_AMOUNT FAT=$FAT_USER+$FAT_CREDIT" + +# --- 1) koopa: bank account + credit + templates + orders --- +log "=== koopa setup (admin credit, templates, orders) ===" +# Generate bank password locally (not committed) +FAT_PW=$(python3 -c 'import secrets; print(secrets.token_urlsafe(12))') +echo "$FAT_PW" >"$SCRATCH/fat-bank.password" +chmod 600 "$SCRATCH/fat-bank.password" + +cat >"$SCRATCH/koopa-setup.sh" < /tmp/extreme-setup.out 2>&1 +set -x +INST=$INST +FAT_USER=$FAT_USER +FAT_PW='$(printf %s "$FAT_PW" | sed "s/'/'\\\\''/g")' +FAT_CREDIT=$FAT_CREDIT +HIGH_AMOUNT=$HIGH_AMOUNT +TINY_AMOUNT=$TINY_AMOUNT +ATOMIC_AMOUNT=$ATOMIC_AMOUNT +BANK=http://127.0.0.1:9012 +MER=https://127.0.0.1:9010 + +ADMIN_PASS=\$(tr -d '\\n' =5: + bits-=5; o.append(a[(v>>bits)&31]) +if bits: o.append(a[(v<<(5-bits))&31]) +print("".join(o))') + curl -sS -H "Authorization: Bearer \${AT}" -H 'Content-Type: application/json' \\ + -d "{\\"payto_uri\\":\\"\${PAYTO}\\",\\"amount\\":\\"\${FAT_CREDIT}\\",\\"request_uid\\":\\"\${UID}\\"}" \\ + "\${BANK}/accounts/admin/transactions" | tee /tmp/credit.out + echo +fi + +# balance +curl -sS -H "Authorization: Bearer \${AT}" "\${BANK}/accounts/\${FAT_USER}" | tee /tmp/fat-bal.json +echo +python3 -c 'import json;d=json.load(open("/tmp/fat-bal.json"));print("FAT_BALANCE", (d.get("balance") or {}).get("amount"), (d.get("balance") or {}).get("credit_debit_indicator"))' + +# store bank pw for host-side withdraw +printf '%s' "\$FAT_PW" > /root/bank-\${FAT_USER}-password.txt +chmod 600 /root/bank-\${FAT_USER}-password.txt + +# templates +for id_summary_amount in \\ + "extreme-high|extreme high price|\${HIGH_AMOUNT}" \\ + "extreme-tiny|extreme tiny coin|\${TINY_AMOUNT}" \\ + "extreme-atomic|true atomic round unit|\${ATOMIC_AMOUNT}" +do + IFS='|' read -r TID SUM AMT <<<"\$id_summary_amount" + body=\$(python3 -c "import json; print(json.dumps({ + 'template_id': '\$TID', + 'template_description': '\$SUM', + 'editable_defaults': False, + 'template_contract': { + 'summary': '\$SUM', + 'amount': '\$AMT', + 'minimum_age': 0, + }, + }))") + # try POST then PATCH + code=\$(curl -sk -o /tmp/tpl.json -w '%{http_code}' -X POST -H "\$AUTH" -H 'Content-Type: application/json' \\ + -d "\$body" "\${MER}/instances/\${INST}/private/templates") + echo "template_\$TID post=\$code \$(head -c 120 /tmp/tpl.json)" + if [ "\$code" = "409" ] || [ "\$code" = "400" ]; then + curl -sk -X PATCH -H "\$AUTH" -H 'Content-Type: application/json' \\ + -d "\$body" "\${MER}/instances/\${INST}/private/templates/\${TID}" -w " patch=%{http_code}\\n" + fi +done + +# also list templates +curl -sk -H "\$AUTH" "\${MER}/instances/\${INST}/private/templates" | python3 -m json.tool | head -80 + +# create two firm orders (high + tiny) for deterministic pay URIs +create_order() { + local sum="\$1" amt="\$2" tag="\$3" + curl -sk -X POST -H "\$AUTH" -H 'Content-Type: application/json' \\ + -d "{\\"order\\":{\\"summary\\":\\"\${sum}\\",\\"amount\\":\\"\${amt}\\",\\"fulfillment_message\\":\\"extreme-ok\\"},\\"create_token\\":true}" \\ + "\${MER}/instances/\${INST}/private/orders" > "/tmp/ord-\${tag}.json" + python3 - "\$tag" <<'PY' +import json,sys +tag=sys.argv[1] +d=json.load(open(f"/tmp/ord-{tag}.json")) +print(f"ORDER_{tag.upper()}", d.get("order_id"), "token", (d.get("token") or "")[:12]) +open(f"/tmp/oid-{tag}.txt","w").write(d.get("order_id") or "") +open(f"/tmp/otok-{tag}.txt","w").write(d.get("token") or "") +PY +} +create_order "extreme-high-order" "\$HIGH_AMOUNT" high +create_order "extreme-tiny-order" "\$TINY_AMOUNT" tiny +create_order "extreme-atomic-order" "\$ATOMIC_AMOUNT" atomic + +# create bank integrated withdrawal for fat user +BT=\$(curl -sS -u "\${FAT_USER}:\${FAT_PW}" -H 'Content-Type: application/json' \\ + -d '{"scope":"readwrite"}' "\${BANK}/accounts/\${FAT_USER}/token" \\ + | python3 -c 'import sys,json;print(json.load(sys.stdin).get("access_token",""))') +curl -sS -H "Authorization: Bearer \${BT}" -H 'Content-Type: application/json' \\ + -d "{\\"amount\\":\\"\$WITHDRAW_AMOUNT\\",\\"exchange_url\\":\\"https://exchange.hacktivism.ch/\\"}" \\ + "\${BANK}/accounts/\${FAT_USER}/withdrawals" | tee /tmp/wd-create.json +echo +python3 - <<'PY' +import json +d=json.load(open("/tmp/wd-create.json")) +wid=d.get("withdrawal_id") or d.get("id") +uri=d.get("taler_withdraw_uri") +print("WID", wid) +print("WURI", uri) +open("/tmp/wid.txt","w").write(wid or "") +open("/tmp/wuri.txt","w").write(uri or "") +PY + +# publish artifacts for hernani-readable pull +cp /tmp/oid-*.txt /tmp/otok-*.txt /tmp/wuri.txt /tmp/wid.txt /tmp/fat-bal.json /tmp/extreme-setup.out /home/hernani/ 2>/dev/null || true +chmod 644 /home/hernani/oid-*.txt /home/hernani/otok-*.txt /home/hernani/wuri.txt /home/hernani/wid.txt 2>/dev/null || true + +echo SETUP_EXTREME_OK +EOS + +# inject WITHDRAW_AMOUNT into script (heredoc already expanded partially - fix) +# The setup script uses $WITHDRAW_AMOUNT as shell var on koopa - need to export into script +sed -i.bak "s|\$WITHDRAW_AMOUNT|$WITHDRAW_AMOUNT|g" "$SCRATCH/koopa-setup.sh" 2>/dev/null || \ + sed -i '' "s|\$WITHDRAW_AMOUNT|$WITHDRAW_AMOUNT|g" "$SCRATCH/koopa-setup.sh" + +scp -o BatchMode=yes "$SCRATCH/koopa-setup.sh" "${KOOPA}:/tmp/extreme-setup.sh" +# also scp credit-account for reliable credit +scp -o BatchMode=yes "$ROOT/scripts/taler-bank/credit-account.sh" "${KOOPA}:/tmp/credit-account.sh" || true + +# run via screen window 0 (root@koopa) — screen-run cannot source local-machine paths +SESSION=$(screen -ls 2>/dev/null | awk 'match($0, /[0-9]+\.[A-Za-z0-9._-]+/) { print substr($0, RSTART, RLENGTH); exit }') +[ -n "$SESSION" ] || die "no screen session" +screen -S "$SESSION" -p 0 -X stuff $'bash /tmp/extreme-setup.sh; chmod 644 /tmp/extreme-setup.out /home/hernani/*.txt 2>/dev/null\n' + +for i in $(seq 1 90); do + if ssh -o BatchMode=yes "$KOOPA" 'grep -q SETUP_EXTREME_OK /tmp/extreme-setup.out 2>/dev/null'; then + log "setup ready @${i}s" + break + fi + sleep 1 + [ "$i" -eq 90 ] && { ssh -o BatchMode=yes "$KOOPA" 'tail -80 /tmp/extreme-setup.out' || true; die "setup timeout"; } +done +ssh -o BatchMode=yes "$KOOPA" 'cat /tmp/extreme-setup.out' >"$SCRATCH/koopa-setup.out" || true +# pull artifacts (hernani-readable) +scp -o BatchMode=yes "${KOOPA}:/home/hernani/wuri.txt" "${KOOPA}:/home/hernani/wid.txt" \ + "${KOOPA}:/home/hernani/oid-high.txt" "${KOOPA}:/home/hernani/oid-tiny.txt" \ + "${KOOPA}:/home/hernani/oid-atomic.txt" \ + "${KOOPA}:/home/hernani/otok-high.txt" "${KOOPA}:/home/hernani/otok-tiny.txt" \ + "${KOOPA}:/home/hernani/otok-atomic.txt" \ + "$SCRATCH/" 2>/dev/null || true + +WURI=$(tr -d '\n' <"$SCRATCH/wuri.txt" 2>/dev/null || true) +WID=$(tr -d '\n' <"$SCRATCH/wid.txt" 2>/dev/null || true) +OID_HIGH=$(tr -d '\n' <"$SCRATCH/oid-high.txt" 2>/dev/null || true) +OID_TINY=$(tr -d '\n' <"$SCRATCH/oid-tiny.txt" 2>/dev/null || true) +OID_ATOMIC=$(tr -d '\n' <"$SCRATCH/oid-atomic.txt" 2>/dev/null || true) +TOK_HIGH=$(tr -d '\n' <"$SCRATCH/otok-high.txt" 2>/dev/null || true) +TOK_TINY=$(tr -d '\n' <"$SCRATCH/otok-tiny.txt" 2>/dev/null || true) +TOK_ATOMIC=$(tr -d '\n' <"$SCRATCH/otok-atomic.txt" 2>/dev/null || true) + +log "WURI=$WURI" +log "orders high=$OID_HIGH tiny=$OID_TINY atomic=$OID_ATOMIC" +[ -n "$WURI" ] || die "no withdraw URI" +[ -n "$OID_HIGH" ] && [ -n "$OID_TINY" ] || die "orders missing" + +# ensure auto-confirm watches this withdrawal +ssh -o BatchMode=yes "$KOOPA" "echo $WID >> /var/www/bank-landing/withdraw-watch.ids 2>/dev/null; sort -u /var/www/bank-landing/withdraw-watch.ids -o /var/www/bank-landing/withdraw-watch.ids 2>/dev/null; true" +# root confirm via screen +screen -S "$SESSION" -p 0 -X stuff $'BPW=$(tr -d "\\n" &1 | tee "$SCRATCH/ex-add.out" || true +wcli exchanges update "$EX" 2>&1 | tee "$SCRATCH/ex-upd.out" || true +wcli exchanges tos "$EX" 2>&1 | tee "$SCRATCH/ex-tos.out" || true +wcli exchanges accept-tos "$EX" 2>&1 | tee "$SCRATCH/ex-accept.out" || true + +log "=== withdraw accept-uri ===" +wcli withdraw accept-uri --exchange "$EX" "$WURI" 2>&1 | tee "$SCRATCH/accept-uri.out" || true + +for round in $(seq 1 20); do + log "run-until-done round $round" + wcli run-until-done 2>&1 | tee -a "$SCRATCH/rud.out" || true + wcli balance 2>&1 | tee "$SCRATCH/bal-$round.out" + if grep -qE "GOA:" "$SCRATCH/bal-$round.out"; then + # check non-zero available + if python3 -c 'import re,sys; t=open(sys.argv[1]).read(); +m=re.search(r"available[^0-9A-Z]*GOA:([0-9.]+)", t) +print(m.group(1) if m else ""); sys.exit(0 if m and float(m.group(1))>0 else 1)' "$SCRATCH/bal-$round.out" 2>/dev/null; then + log "coins available" + break + fi + fi + # re-confirm withdraw on bank + screen -S "$SESSION" -p 0 -X stuff $'BPW=$(tr -d "\\n" /dev/null; echo\n' + sleep 3 +done + +wcli balance 2>&1 | tee "$SCRATCH/balance-after-withdraw.out" +wcli transactions 2>&1 | tee "$SCRATCH/tx-after-withdraw.out" | head -100 + +# --- 3) pay high + tiny (+ try atomic) --- +pay_order() { + local tag="$1" oid="$2" tok="$3" + local uri="taler://pay/taler.hacktivism.ch/instances/${INST}/${oid}/?c=${tok}" + echo "$uri" >"$SCRATCH/pay-${tag}.uri" + log "=== pay $tag order=$oid ===" + log "uri=$uri" + wcli handle-uri "$uri" 2>&1 | tee "$SCRATCH/pay-${tag}-handle.out" || true + # some wallets use payments prepare/confirm + wcli run-until-done 2>&1 | tee -a "$SCRATCH/pay-${tag}-rud.out" || true + # check merchant + ssh -o BatchMode=yes "$KOOPA" "MPW=\$(tr -d '\\n' "$SCRATCH/order-${tag}-status.json" 2>/dev/null || true + python3 - "$SCRATCH/order-${tag}-status.json" "$tag" <<'PY' || true +import json,sys +p,tag=sys.argv[1],sys.argv[2] +try: + d=json.load(open(p)) +except Exception as e: + print(f"STATUS_{tag} unreadable", e); raise SystemExit +print(f"STATUS_{tag}", "order_status=", d.get("order_status"), "paid=", d.get("paid"), + "wired=", d.get("wired"), "deposit_total=", d.get("deposit_total"), + "amount=", (d.get("contract_terms") or {}).get("amount")) +PY +} + +pay_order high "$OID_HIGH" "$TOK_HIGH" +wcli balance 2>&1 | tee "$SCRATCH/balance-after-high.out" + +pay_order tiny "$OID_TINY" "$TOK_TINY" +wcli balance 2>&1 | tee "$SCRATCH/balance-after-tiny.out" + +if [ -n "$OID_ATOMIC" ] && [ -n "$TOK_ATOMIC" ]; then + pay_order atomic "$OID_ATOMIC" "$TOK_ATOMIC" || true + wcli balance 2>&1 | tee "$SCRATCH/balance-after-atomic.out" || true +fi + +# template URIs for docs +{ + echo "taler://pay-template/taler.hacktivism.ch/instances/${INST}/extreme-high/" + echo "taler://pay-template/taler.hacktivism.ch/instances/${INST}/extreme-tiny/" + echo "taler://pay-template/taler.hacktivism.ch/instances/${INST}/extreme-atomic/" +} | tee "$SCRATCH/template-uris.txt" + +log "=== summary ===" +python3 - <&1 | tee "$SCRATCH/ex-add.out" || true +wcli exchanges update "$EX" 2>&1 | tee "$SCRATCH/ex-upd.out" || true +wcli exchanges tos "$EX" 2>&1 | tee "$SCRATCH/ex-tos.out" || true +wcli exchanges accept-tos "$EX" 2>&1 | tee "$SCRATCH/ex-accept-tos.out" + +if [ -z "$URI" ]; then + URI=$(ssh -o BatchMode=yes koopa 'tr -d "\n" "$SCRATCH/withdraw.uri" + +echo "=== withdraw accept-uri ===" +wcli withdraw accept-uri --exchange "$EX" "$URI" 2>&1 | tee "$SCRATCH/accept-uri.out" + +WID=$(basename "$URI") +echo "=== ensure watch + auto-confirm on koopa ===" +ssh -o BatchMode=yes koopa "echo $WID >> /var/www/bank-landing/withdraw-watch.ids; sort -u /var/www/bank-landing/withdraw-watch.ids -o /var/www/bank-landing/withdraw-watch.ids" +# one-shot confirm as root via ssh if hernani can sudo? usually not — use screen later +# public status +curl -sS "https://bank.hacktivism.ch/taler-integration/withdrawals/${WID}" 2>/dev/null | head -c 500 || true +echo + +for round in 1 2 3 4 5 6 7 8; do + echo "=== run-until-done round $round ===" + wcli run-until-done & + PID=$! + ( sleep 20; kill $PID 2>/dev/null ) & + wait $PID 2>/dev/null || true + wcli balance 2>&1 | tee "$SCRATCH/bal-$round.out" + wcli transactions 2>&1 | tee "$SCRATCH/tx-$round.out" | head -80 + if grep -qE '"available": "GOA:[1-9]' "$SCRATCH/bal-$round.out"; then + echo "OK coins received" + exit 0 + fi + sleep 3 +done +echo "FAIL no balance" >&2 +exit 1