scripts: sanity/dns/wallet-cli helpers; ops cleanup

This commit is contained in:
Hernâni Marques 2026-07-09 19:57:14 +02:00
parent 1181680a4b
commit be05666737
No known key found for this signature in database
18 changed files with 1440 additions and 0 deletions

106
scripts/ops/cleanup-koopa-all.sh Executable file
View file

@ -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

View file

@ -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"

View file

@ -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
```

View file

@ -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 <<EOF
[Unit]
After=pin-hacktivism-hosts.service
Requires=pin-hacktivism-hosts.service
EOF
systemctl daemon-reload
systemctl restart taler-exchange-wirewatch 2>/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."

View file

@ -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)"

View file

@ -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

View file

@ -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

View file

@ -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.

View file

@ -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"

View file

@ -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"

View file

@ -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' </root/merchant-goa-demo-cp4zqk-password.txt)
tmp=$(mktemp)
curl -sk -m 12 -H "Authorization: Bearer secret-token:${MPW}" \
-o "$tmp" "${MERCHANT_URL}/instances/goa-demo-cp4zqk/private/" || true
if [ -s "$tmp" ]; then
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'} instance {key}: {s:.0f}s")
if not ok: fails+=1
sys.exit(fails)
PY
FAILS=$((FAILS + $?))
else
warn "could not load instance goa-demo-cp4zqk"
fi
rm -f "$tmp"
fi
echo "=== summary fails=$FAILS ==="
exit "$FAILS"

View file

@ -0,0 +1,119 @@
#!/bin/bash
# Sanity / ops: check paid orders wired? bank transfers? balances?
# Run on koopa as root.
#
# Usage:
# check_settlement.sh # all paid orders for goa-demo
# check_settlement.sh ORDER_ID # one order
# MERCHANT_INSTANCE=foo check_settlement.sh
set -euo pipefail
ROOT=$(cd "$(dirname "$0")" && pwd)
# shellcheck source=lib.sh
source "$ROOT/lib.sh"
need_root
INST="${MERCHANT_INSTANCE:-goa-demo-cp4zqk}"
ORDER_ID="${1:-}"
if ! read_pw MPW "/root/merchant-${INST}-password.txt"; then
read_pw MPW /root/merchant-goa-demo-cp4zqk-password.txt || true
fi
if [ -z "${MPW:-}" ]; then
fail "no merchant password for $INST"
exit 1
fi
AUTH=$(merchant_auth_header "$MPW")
echo "=== Settlement check instance=$INST ==="
tmp=$(mktemp)
curl -sk -m 15 -H "$AUTH" -o "$tmp" \
"${MERCHANT_URL}/instances/${INST}/private/orders?paid=YES"
python3 - "$tmp" "$ORDER_ID" <<'PY'
import json,sys,time
d=json.load(open(sys.argv[1]))
want=sys.argv[2] or None
orders=d.get("orders") or []
if want:
orders=[o for o in orders if o.get("order_id")==want]
if not orders:
print("FAIL no paid orders" + (f" matching {want}" if want else ""))
sys.exit(1)
print(f"INFO paid_orders={len(orders)}")
for o in orders:
print(f" - {o.get('order_id')} amount={o.get('amount')} summary={o.get('summary')!r} paid={o.get('paid')}")
sys.exit(0)
PY
ec=$?
[ "$ec" -eq 0 ] || FAILS=$((FAILS + 1))
# Detail each order
mapfile -t OIDS < <(python3 -c 'import json,sys; d=json.load(open(sys.argv[1])); want=sys.argv[2] or None
oids=[o["order_id"] for o in d.get("orders",[])]
print("\n".join([x for x in oids if not want or x==want]))' "$tmp" "$ORDER_ID")
for oid in "${OIDS[@]}"; do
echo "--- order $oid ---"
ot=$(mktemp)
curl -sk -m 15 -H "$AUTH" -o "$ot" \
"${MERCHANT_URL}/instances/${INST}/private/orders/${oid}"
python3 - "$ot" <<'PY'
import json,sys,time
d=json.load(open(sys.argv[1]))
ct=d.get("contract_terms") or {}
ts=(ct.get("timestamp") or {}).get("t_s")
rd=(ct.get("refund_deadline") or {}).get("t_s")
wd=(ct.get("wire_transfer_deadline") or {}).get("t_s")
now=int(time.time())
print(" status", d.get("order_status"), "wired", d.get("wired"), "deposit_total", d.get("deposit_total"))
print(" amount", ct.get("amount"), "summary", ct.get("summary"))
if ts and rd and wd:
print(f" age_s={now-ts} refund_in_s={rd-now} wire_in_s={wd-now}")
print(f" windows: refund={rd-ts}s wire_from_pay={wd-ts}s (wire_after_refund={wd-rd}s)")
if now >= 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"

View file

@ -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"

View file

@ -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"
}

View file

@ -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

View file

@ -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" "$@"

View file

@ -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" <<EOS
#!/bin/bash
set +e
exec > /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' </root/bank-admin-password.txt)
MPW=\$(tr -d '\\n' </root/merchant-\${INST}-password.txt)
AUTH="Authorization: Bearer secret-token:\${MPW}"
# admin token
AT=\$(curl -sS -u "admin:\${ADMIN_PASS}" -H 'Content-Type: application/json' \\
-d '{"scope":"readwrite"}' "\${BANK}/accounts/admin/token" \\
| python3 -c 'import sys,json;print(json.load(sys.stdin).get("access_token",""))')
[ -n "\$AT" ] || { echo FAIL_admin_token; exit 1; }
# create or reuse bank account
code=\$(curl -sS -o /tmp/acc-create.json -w '%{http_code}' -X POST \\
-H "Authorization: Bearer \${AT}" -H 'Content-Type: application/json' \\
-d "{\\"username\\":\\"\${FAT_USER}\\",\\"password\\":\\"\${FAT_PW}\\",\\"name\\":\\"Extreme Fat Wallet\\",\\"is_public\\":false,\\"is_taler_exchange\\":false,\\"debit_threshold\\":\\"GOA:1000000\\"}" \\
"\${BANK}/accounts")
echo create_account_http=\$code
cat /tmp/acc-create.json; echo
# if exists (409), reset password via admin if possible
if [ "\$code" != "200" ] && [ "\$code" != "201" ] && [ "\$code" != "204" ]; then
echo "account may exist; try credit anyway"
fi
# credit from admin (idempotent enough — we just need lots of GOA)
python3 - <<'PY'
import json, os, urllib.request, base64
bank=os.environ.get("BANK","http://127.0.0.1:9012")
PY
# use host credit-account if present
if [ -x /root/koopa-admin-log/scripts/taler-bank/credit-account.sh ]; then
/root/koopa-admin-log/scripts/taler-bank/credit-account.sh "\$FAT_USER" "\$FAT_CREDIT" || true
elif [ -x /Users/newkamek/src/koopa/koopa-admin-log/scripts/taler-bank/credit-account.sh ]; then
true
else
# inline credit
PAYTO="payto://x-taler-bank/bank.hacktivism.ch/\${FAT_USER}?receiver-name=\${FAT_USER}&message=extreme-credit"
UID=\$(python3 -c 'import os; a="0123456789ABCDEFGHJKMNPQRSTVWXYZ"; b=os.urandom(32); bits=0;v=0;o=[]
for byte in b:
v=(v<<8)|byte; bits+=8
while bits>=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" </root/bank-'"$FAT_USER"'-password.txt); BT=$(curl -sS -u "'"$FAT_USER"':${BPW}" -H "Content-Type: application/json" -d "{\"scope\":\"readwrite\"}" http://127.0.0.1:9012/accounts/'"$FAT_USER"'/token | python3 -c "import sys,json;print(json.load(sys.stdin).get(\"access_token\",\"\"))"); curl -sS -X POST -H "Authorization: Bearer ${BT}" http://127.0.0.1:9012/accounts/'"$FAT_USER"'/withdrawals/'"$WID"'/confirm; echo CONF_DONE\n'
# --- 2) wallet withdraw ---
log "=== wallet: add exchange + ToS ==="
wcli exchanges add "$EX" 2>&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" </root/bank-'"$FAT_USER"'-password.txt); BT=$(curl -sS -u "'"$FAT_USER"':${BPW}" -H "Content-Type: application/json" -d "{\"scope\":\"readwrite\"}" http://127.0.0.1:9012/accounts/'"$FAT_USER"'/token | python3 -c "import sys,json;print(json.load(sys.stdin).get(\"access_token\",\"\"))"); curl -sS -X POST -H "Authorization: Bearer ${BT}" http://127.0.0.1:9012/accounts/'"$FAT_USER"'/withdrawals/'"$WID"'/confirm 2>/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' </root/merchant-${INST}-password.txt); curl -sk -H \"Authorization: Bearer secret-token:\${MPW}\" https://127.0.0.1:9010/instances/${INST}/private/orders/${oid}" \
>"$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 - <<PY
import json,glob,os
scratch="$SCRATCH"
print("scratch", scratch)
for tag in ("high","tiny","atomic"):
p=f"{scratch}/order-{tag}-status.json"
if not os.path.isfile(p):
print(tag, "no status file"); continue
try:
d=json.load(open(p))
print(tag, "paid=", d.get("paid"), "status=", d.get("order_status"),
"amount=", (d.get("contract_terms") or {}).get("amount"),
"deposit_total=", d.get("deposit_total"))
except Exception as e:
print(tag, "err", e)
print("template_uris:")
print(open(f"{scratch}/template-uris.txt").read())
PY
log "done — details in $SCRATCH"

View file

@ -0,0 +1,54 @@
#!/bin/bash
# Local machine: withdraw GOA via bank-integrated QR1 URI (explorer demo account).
# Requires: monorepo wallet, ssh koopa, auto-confirm on host.
set -euo pipefail
ROOT=$(cd "$(dirname "$0")/../.." && pwd)
SCRATCH="${ROOT}/.tmp/session-withdraw-$(date -u +%Y%m%d-%H%M%S)"
mkdir -p "$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/}"
URI="${1:-}"
wcli() { node "$MONO" --wallet-db="$WDB" --no-throttle --skip-defaults "$@"; }
echo "scratch=$SCRATCH"
echo "=== add + accept ToS for $EX ==="
wcli exchanges add "$EX" 2>&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" </var/www/bank-landing/withdraw.uri')
fi
echo "URI=$URI"
echo "$URI" >"$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