From 5920296ae4b4636dbedc929294f56d8911cb9149 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hern=C3=A2ni=20Marques?= Date: Tue, 30 Jun 2026 19:14:49 +0200 Subject: [PATCH] exchange: manual start/health + zz-hacktivism conf drop-in --- .../taler-exchange.conf.includes.txt | 3 + configs/taler-exchange/zz-hacktivism.conf | 2 + scripts/taler-exchange/archive/README.md | 35 ++ .../archive/exchange-bootstrap.sh | 451 ++++++++++++++++++ .../taler-exchange/check_exchange-health.sh | 88 ++++ .../offline-sign-upload-keys.sh | 62 +++ .../start_base_services_for_taler_exchange.sh | 148 ++++++ scripts/taler-exchange/start_exchange.sh | 104 ++++ scripts/taler-exchange/start_wire_helpers.sh | 43 ++ .../taler-exchange/wire-enable-and-upload.sh | 91 ++++ 10 files changed, 1027 insertions(+) create mode 100644 configs/taler-exchange/taler-exchange.conf.includes.txt create mode 100644 configs/taler-exchange/zz-hacktivism.conf create mode 100644 scripts/taler-exchange/archive/README.md create mode 100755 scripts/taler-exchange/archive/exchange-bootstrap.sh create mode 100755 scripts/taler-exchange/check_exchange-health.sh create mode 100755 scripts/taler-exchange/offline-sign-upload-keys.sh create mode 100755 scripts/taler-exchange/start_base_services_for_taler_exchange.sh create mode 100755 scripts/taler-exchange/start_exchange.sh create mode 100755 scripts/taler-exchange/start_wire_helpers.sh create mode 100755 scripts/taler-exchange/wire-enable-and-upload.sh diff --git a/configs/taler-exchange/taler-exchange.conf.includes.txt b/configs/taler-exchange/taler-exchange.conf.includes.txt new file mode 100644 index 0000000..f8392e8 --- /dev/null +++ b/configs/taler-exchange/taler-exchange.conf.includes.txt @@ -0,0 +1,3 @@ +32:@inline-matching@ conf.d/*.conf +35:@inline@ overrides.conf +50:@inline@ exchange-overrides.conf diff --git a/configs/taler-exchange/zz-hacktivism.conf b/configs/taler-exchange/zz-hacktivism.conf new file mode 100644 index 0000000..abd818f --- /dev/null +++ b/configs/taler-exchange/zz-hacktivism.conf @@ -0,0 +1,2 @@ +[exchange] +CURRENCY = GOA diff --git a/scripts/taler-exchange/archive/README.md b/scripts/taler-exchange/archive/README.md new file mode 100644 index 0000000..b3a2f83 --- /dev/null +++ b/scripts/taler-exchange/archive/README.md @@ -0,0 +1,35 @@ +# Exchange archive / bootstrap + +## `exchange-bootstrap.sh` (greenfield only) + +One-shot bootstrap for a **new** exchange container. **Not** used for daily ops. + +Includes current **GOA exploration currency**: + +- `exchange-overrides.conf` pattern (no `zz-*`) +- `PORT = 9011`, `SERVE = tcp` +- Unit names: Yotta…Atomic-GOA (scale −8…24) +- ATM `common_amounts` 10…1000 +- Full 1–2–5 **coin** ladder (Micro-GOA … 10 GOA) in `conf.d/exchange-coins.conf` + +After bootstrap: + +1. `/root/start_base_services_for_taler_exchange.sh` (root) +2. `./start_exchange.sh` as `taler-exchange-httpd` +3. Offline: wire enable + denom/signkey sign for `/keys` + Live helpers: `../wire-enable-and-upload.sh`, `../offline-sign-upload-keys.sh`, `../start_wire_helpers.sh` + +## Removed + +| Script | Why | +|--------|-----| +| `exchange-start-all.sh` | Redundant with base + `start_exchange.sh` | +| old zz-hacktivism bootstrap | Wrong ports/currency; superseded | + +## Daily layout (merchant model) + +| Path | User | +|------|------| +| `/root/start_base_services_for_taler_exchange.sh` | root | +| `/usr/local/bin/start_exchange.sh` | `taler-exchange-httpd` | +| `/usr/local/bin/check_exchange-health.sh` | httpd / any | diff --git a/scripts/taler-exchange/archive/exchange-bootstrap.sh b/scripts/taler-exchange/archive/exchange-bootstrap.sh new file mode 100755 index 0000000..96c0dc1 --- /dev/null +++ b/scripts/taler-exchange/archive/exchange-bootstrap.sh @@ -0,0 +1,451 @@ +#!/bin/bash +# Greenfield bootstrap for exchange.hacktivism.ch (GOA exploration currency). +# Run as root inside container. Not for daily ops — use start_base + start_exchange. +# +# Writes: +# /etc/taler-exchange/exchange-overrides.conf +# /etc/taler-exchange/conf.d/exchange-coins.conf +# Ensures main conf inlines exchange-overrides.conf (merchant pattern). +# Does NOT use conf.d/zz-*. +# +# After this: offline denom sign + wire still required for /keys withdraw. + +set -euo pipefail +CONF=/etc/taler-exchange/taler-exchange.conf +OV=/etc/taler-exchange/exchange-overrides.conf +COINS=/etc/taler-exchange/conf.d/exchange-coins.conf +export DEBIAN_FRONTEND=noninteractive + +if [ "$(id -u)" -ne 0 ]; then + echo "Run as root" >&2 + exit 1 +fi + +echo "=== 1. postgres ===" +/etc/init.d/postgresql start +sleep 2 +pg_isready || true + +echo "=== 2. master key (offline user) ===" +mkdir -p /var/lib/taler-exchange/offline +chown -R taler-exchange-offline:taler-exchange-offline /var/lib/taler-exchange/offline +chown taler-exchange-offline:taler-exchange-offline /var/lib/taler-exchange 2>/dev/null || true +OUT=$(runuser -u taler-exchange-offline -- taler-exchange-offline -c "$CONF" setup 2>&1) || true +echo "$OUT" +MASTER_PUB=$(echo "$OUT" | grep -oE '[A-Z0-9]{40,}' | tail -1) +if [ -z "${MASTER_PUB:-}" ]; then + echo "ERROR: no master public key from offline setup" >&2 + exit 1 +fi +echo "MASTER_PUBLIC_KEY=$MASTER_PUB" +ATTR_KEY=$(openssl rand -hex 32) +SECRETS_DIR=/etc/taler-exchange/secrets +mkdir -p "$SECRETS_DIR" +cat >"$SECRETS_DIR/exchange-attribute-encryption.secret.conf" </dev/null || true + +echo "=== 3. exchange-overrides.conf (site; not zz-*) ===" +# Ensure main conf inlines overrides (merchant pattern) +if ! grep -q 'exchange-overrides.conf' "$CONF" 2>/dev/null; then + printf '\n# Manual site overrides (merchant pattern)\n@inline@ exchange-overrides.conf\n' >>"$CONF" +fi +# Remove legacy zz drop-ins if present +rm -f /etc/taler-exchange/conf.d/zz-hacktivism.conf \ + /etc/taler-exchange/conf.d/zz-hacktivism-coins.conf 2>/dev/null || true + +cat >"$OV" </dev/null; then + mv "$COINS" "${COINS}.package-kudos" + echo "Moved package coins -> ${COINS}.package-kudos" +fi + +cat >"$COINS" << 'COINS_EOF' +# GOA denominations for exchange.hacktivism.ch +# Units: 1 GOA = 1000 mGOA = 1_000_000 uGOA +# VALUE uses base currency; coin sections cover 1–2–5 ladders at u/m/whole scale. + +# --- uGOA (10^-6 GOA) --- +[coin_goa_0_000001] +VALUE = GOA:0.000001 +DURATION_WITHDRAW = 7 days +DURATION_SPEND = 2 years +DURATION_LEGAL = 3 years +FEE_WITHDRAW = GOA:0 +FEE_DEPOSIT = GOA:0 +FEE_REFRESH = GOA:0 +FEE_REFUND = GOA:0 +RSA_KEYSIZE = 2048 +CIPHER = RSA + +[coin_goa_0_000002] +VALUE = GOA:0.000002 +DURATION_WITHDRAW = 7 days +DURATION_SPEND = 2 years +DURATION_LEGAL = 3 years +FEE_WITHDRAW = GOA:0 +FEE_DEPOSIT = GOA:0 +FEE_REFRESH = GOA:0 +FEE_REFUND = GOA:0 +RSA_KEYSIZE = 2048 +CIPHER = RSA + +[coin_goa_0_000005] +VALUE = GOA:0.000005 +DURATION_WITHDRAW = 7 days +DURATION_SPEND = 2 years +DURATION_LEGAL = 3 years +FEE_WITHDRAW = GOA:0 +FEE_DEPOSIT = GOA:0 +FEE_REFRESH = GOA:0 +FEE_REFUND = GOA:0 +RSA_KEYSIZE = 2048 +CIPHER = RSA + +[coin_goa_0_00001] +VALUE = GOA:0.00001 +DURATION_WITHDRAW = 7 days +DURATION_SPEND = 2 years +DURATION_LEGAL = 3 years +FEE_WITHDRAW = GOA:0 +FEE_DEPOSIT = GOA:0 +FEE_REFRESH = GOA:0 +FEE_REFUND = GOA:0 +RSA_KEYSIZE = 2048 +CIPHER = RSA + +[coin_goa_0_00002] +VALUE = GOA:0.00002 +DURATION_WITHDRAW = 7 days +DURATION_SPEND = 2 years +DURATION_LEGAL = 3 years +FEE_WITHDRAW = GOA:0 +FEE_DEPOSIT = GOA:0 +FEE_REFRESH = GOA:0 +FEE_REFUND = GOA:0 +RSA_KEYSIZE = 2048 +CIPHER = RSA + +[coin_goa_0_00005] +VALUE = GOA:0.00005 +DURATION_WITHDRAW = 7 days +DURATION_SPEND = 2 years +DURATION_LEGAL = 3 years +FEE_WITHDRAW = GOA:0 +FEE_DEPOSIT = GOA:0 +FEE_REFRESH = GOA:0 +FEE_REFUND = GOA:0 +RSA_KEYSIZE = 2048 +CIPHER = RSA + +[coin_goa_0_0001] +VALUE = GOA:0.0001 +DURATION_WITHDRAW = 7 days +DURATION_SPEND = 2 years +DURATION_LEGAL = 3 years +FEE_WITHDRAW = GOA:0 +FEE_DEPOSIT = GOA:0 +FEE_REFRESH = GOA:0 +FEE_REFUND = GOA:0 +RSA_KEYSIZE = 2048 +CIPHER = RSA + +[coin_goa_0_0002] +VALUE = GOA:0.0002 +DURATION_WITHDRAW = 7 days +DURATION_SPEND = 2 years +DURATION_LEGAL = 3 years +FEE_WITHDRAW = GOA:0 +FEE_DEPOSIT = GOA:0 +FEE_REFRESH = GOA:0 +FEE_REFUND = GOA:0 +RSA_KEYSIZE = 2048 +CIPHER = RSA + +[coin_goa_0_0005] +VALUE = GOA:0.0005 +DURATION_WITHDRAW = 7 days +DURATION_SPEND = 2 years +DURATION_LEGAL = 3 years +FEE_WITHDRAW = GOA:0 +FEE_DEPOSIT = GOA:0 +FEE_REFRESH = GOA:0 +FEE_REFUND = GOA:0 +RSA_KEYSIZE = 2048 +CIPHER = RSA + +# --- mGOA (10^-3 GOA) --- +[coin_goa_0_001] +VALUE = GOA:0.001 +DURATION_WITHDRAW = 7 days +DURATION_SPEND = 2 years +DURATION_LEGAL = 3 years +FEE_WITHDRAW = GOA:0 +FEE_DEPOSIT = GOA:0 +FEE_REFRESH = GOA:0 +FEE_REFUND = GOA:0 +RSA_KEYSIZE = 2048 +CIPHER = RSA + +[coin_goa_0_002] +VALUE = GOA:0.002 +DURATION_WITHDRAW = 7 days +DURATION_SPEND = 2 years +DURATION_LEGAL = 3 years +FEE_WITHDRAW = GOA:0 +FEE_DEPOSIT = GOA:0 +FEE_REFRESH = GOA:0 +FEE_REFUND = GOA:0 +RSA_KEYSIZE = 2048 +CIPHER = RSA + +[coin_goa_0_005] +VALUE = GOA:0.005 +DURATION_WITHDRAW = 7 days +DURATION_SPEND = 2 years +DURATION_LEGAL = 3 years +FEE_WITHDRAW = GOA:0 +FEE_DEPOSIT = GOA:0 +FEE_REFRESH = GOA:0 +FEE_REFUND = GOA:0 +RSA_KEYSIZE = 2048 +CIPHER = RSA + +[coin_goa_0_01] +VALUE = GOA:0.01 +DURATION_WITHDRAW = 7 days +DURATION_SPEND = 2 years +DURATION_LEGAL = 3 years +FEE_WITHDRAW = GOA:0 +FEE_DEPOSIT = GOA:0 +FEE_REFRESH = GOA:0 +FEE_REFUND = GOA:0 +RSA_KEYSIZE = 2048 +CIPHER = RSA + +[coin_goa_0_02] +VALUE = GOA:0.02 +DURATION_WITHDRAW = 7 days +DURATION_SPEND = 2 years +DURATION_LEGAL = 3 years +FEE_WITHDRAW = GOA:0 +FEE_DEPOSIT = GOA:0 +FEE_REFRESH = GOA:0 +FEE_REFUND = GOA:0 +RSA_KEYSIZE = 2048 +CIPHER = RSA + +[coin_goa_0_05] +VALUE = GOA:0.05 +DURATION_WITHDRAW = 7 days +DURATION_SPEND = 2 years +DURATION_LEGAL = 3 years +FEE_WITHDRAW = GOA:0 +FEE_DEPOSIT = GOA:0 +FEE_REFRESH = GOA:0 +FEE_REFUND = GOA:0 +RSA_KEYSIZE = 2048 +CIPHER = RSA + +[coin_goa_0_1] +VALUE = GOA:0.1 +DURATION_WITHDRAW = 7 days +DURATION_SPEND = 2 years +DURATION_LEGAL = 3 years +FEE_WITHDRAW = GOA:0 +FEE_DEPOSIT = GOA:0 +FEE_REFRESH = GOA:0 +FEE_REFUND = GOA:0 +RSA_KEYSIZE = 2048 +CIPHER = RSA + +[coin_goa_0_2] +VALUE = GOA:0.2 +DURATION_WITHDRAW = 7 days +DURATION_SPEND = 2 years +DURATION_LEGAL = 3 years +FEE_WITHDRAW = GOA:0 +FEE_DEPOSIT = GOA:0 +FEE_REFRESH = GOA:0 +FEE_REFUND = GOA:0 +RSA_KEYSIZE = 2048 +CIPHER = RSA + +[coin_goa_0_5] +VALUE = GOA:0.5 +DURATION_WITHDRAW = 7 days +DURATION_SPEND = 2 years +DURATION_LEGAL = 3 years +FEE_WITHDRAW = GOA:0 +FEE_DEPOSIT = GOA:0 +FEE_REFRESH = GOA:0 +FEE_REFUND = GOA:0 +RSA_KEYSIZE = 2048 +CIPHER = RSA + +# --- whole GOA --- +[coin_goa_1_0] +VALUE = GOA:1 +DURATION_WITHDRAW = 7 days +DURATION_SPEND = 2 years +DURATION_LEGAL = 3 years +FEE_WITHDRAW = GOA:0 +FEE_DEPOSIT = GOA:0 +FEE_REFRESH = GOA:0 +FEE_REFUND = GOA:0 +RSA_KEYSIZE = 2048 +CIPHER = RSA + +[coin_goa_2_0] +VALUE = GOA:2 +DURATION_WITHDRAW = 7 days +DURATION_SPEND = 2 years +DURATION_LEGAL = 3 years +FEE_WITHDRAW = GOA:0 +FEE_DEPOSIT = GOA:0 +FEE_REFRESH = GOA:0 +FEE_REFUND = GOA:0 +RSA_KEYSIZE = 2048 +CIPHER = RSA + +[coin_goa_5_0] +VALUE = GOA:5 +DURATION_WITHDRAW = 7 days +DURATION_SPEND = 2 years +DURATION_LEGAL = 3 years +FEE_WITHDRAW = GOA:0 +FEE_DEPOSIT = GOA:0 +FEE_REFRESH = GOA:0 +FEE_REFUND = GOA:0 +RSA_KEYSIZE = 2048 +CIPHER = RSA + +[coin_goa_10_0] +VALUE = GOA:10 +DURATION_WITHDRAW = 7 days +DURATION_SPEND = 2 years +DURATION_LEGAL = 3 years +FEE_WITHDRAW = GOA:0 +FEE_DEPOSIT = GOA:0 +FEE_REFRESH = GOA:0 +FEE_REFUND = GOA:0 +RSA_KEYSIZE = 2048 +CIPHER = RSA + +[coin_goa_20_0] +VALUE = GOA:20 +DURATION_WITHDRAW = 7 days +DURATION_SPEND = 2 years +DURATION_LEGAL = 3 years +FEE_WITHDRAW = GOA:0 +FEE_DEPOSIT = GOA:0 +FEE_REFRESH = GOA:0 +FEE_REFUND = GOA:0 +RSA_KEYSIZE = 2048 +CIPHER = RSA + +[coin_goa_50_0] +VALUE = GOA:50 +DURATION_WITHDRAW = 7 days +DURATION_SPEND = 2 years +DURATION_LEGAL = 3 years +FEE_WITHDRAW = GOA:0 +FEE_DEPOSIT = GOA:0 +FEE_REFRESH = GOA:0 +FEE_REFUND = GOA:0 +RSA_KEYSIZE = 2048 +CIPHER = RSA + +[coin_goa_100_0] +VALUE = GOA:100 +DURATION_WITHDRAW = 7 days +DURATION_SPEND = 2 years +DURATION_LEGAL = 3 years +FEE_WITHDRAW = GOA:0 +FEE_DEPOSIT = GOA:0 +FEE_REFRESH = GOA:0 +FEE_REFUND = GOA:0 +RSA_KEYSIZE = 2048 +CIPHER = RSA + +[coin_goa_200_0] +VALUE = GOA:200 +DURATION_WITHDRAW = 7 days +DURATION_SPEND = 2 years +DURATION_LEGAL = 3 years +FEE_WITHDRAW = GOA:0 +FEE_DEPOSIT = GOA:0 +FEE_REFRESH = GOA:0 +FEE_REFUND = GOA:0 +RSA_KEYSIZE = 2048 +CIPHER = RSA + +[coin_goa_1000_0] +VALUE = GOA:1000 +DURATION_WITHDRAW = 7 days +DURATION_SPEND = 2 years +DURATION_LEGAL = 3 years +FEE_WITHDRAW = GOA:0 +FEE_DEPOSIT = GOA:0 +FEE_REFRESH = GOA:0 +FEE_REFUND = GOA:0 +RSA_KEYSIZE = 2048 +CIPHER = RSA +COINS_EOF + +echo "Wrote $COINS ($(grep -c '^\[coin_' "$COINS") coin sections)" + +echo "=== 5. database ===" +taler-exchange-dbconfig 2>&1 || true +runuser -u taler-exchange-httpd -- taler-exchange-dbinit -c "$CONF" 2>&1 || true + +echo "=== 6. next steps (manual) ===" +echo " - /root/start_base_services_for_taler_exchange.sh # root: secmods + shell as httpd" +echo " - ./start_exchange.sh # as taler-exchange-httpd" +echo " - offline: sign denominations + wire accounts for /keys" +echo " - MASTER_PUBLIC_KEY=$MASTER_PUB" +echo "DONE bootstrap" diff --git a/scripts/taler-exchange/check_exchange-health.sh b/scripts/taler-exchange/check_exchange-health.sh new file mode 100755 index 0000000..3214745 --- /dev/null +++ b/scripts/taler-exchange/check_exchange-health.sh @@ -0,0 +1,88 @@ +#!/bin/bash +# Health check for manual taler-exchange (same style as check_merchant-health.sh). +# Runnable as taler-exchange-httpd (or any user that can see processes + curl localhost). + +CONF=/etc/taler-exchange/taler-exchange.conf +SOCK=/run/taler-exchange/httpd/exchange-http.sock + +green() { echo -e "\e[32m$1\e[0m"; } +red() { echo -e "\e[31m$1\e[0m"; } +yellow() { echo -e "\e[33m$1\e[0m"; } + +fail=0 +ok() { green "[OK] $1"; } +bad() { red "[FAIL] $1"; fail=1; } +warn() { yellow "[WARN] $1"; } + +echo "=== Taler Exchange Health Check ===" + +# 1. secmods (started by root base script) +for p in taler-exchange-secmod-rsa taler-exchange-secmod-cs taler-exchange-secmod-eddsa; do + if pgrep -f "$p" >/dev/null 2>&1; then + ok "process $p" + else + bad "process $p not running" + fi +done + +# 2. httpd +if pgrep -f taler-exchange-httpd >/dev/null 2>&1; then + ok "process taler-exchange-httpd" +else + bad "process taler-exchange-httpd is NOT running" +fi + +# 3. serving mode +SERVE=$(taler-exchange-config -c "$CONF" -s exchange -o SERVE 2>/dev/null || echo unix) +PORT=$(taler-exchange-config -c "$CONF" -s exchange -o PORT 2>/dev/null || echo 9011) + +if [ "$SERVE" = "tcp" ]; then + if curl -sf -m 3 "http://127.0.0.1:${PORT}/config" >/dev/null 2>&1; then + ok "HTTP /config on 127.0.0.1:${PORT}" + elif curl -sf -m 3 "http://127.0.0.1:${PORT}/keys" >/dev/null 2>&1; then + ok "HTTP /keys on 127.0.0.1:${PORT}" + else + bad "no HTTP response on 127.0.0.1:${PORT} (/config|/keys)" + fi +else + if [ -S "$SOCK" ]; then + ok "socket $SOCK" + else + bad "socket does NOT exist: $SOCK" + fi +fi + +# 4. wire helpers — ensure (no systemd) then require +if [ "${SKIP_ENSURE:-0}" != "1" ]; then + if [ -x /usr/local/bin/ensure_exchange_helpers.sh ]; then + echo "--- ensure_exchange_helpers ---" + /usr/local/bin/ensure_exchange_helpers.sh || warn "ensure_exchange_helpers exited non-zero" + elif [ -x "$(dirname "$0")/ensure_exchange_helpers.sh" ]; then + echo "--- ensure_exchange_helpers ---" + "$(dirname "$0")/ensure_exchange_helpers.sh" || warn "ensure_exchange_helpers exited non-zero" + elif [ -x /root/ensure_exchange_helpers.sh ]; then + echo "--- ensure_exchange_helpers ---" + /root/ensure_exchange_helpers.sh || warn "ensure_exchange_helpers exited non-zero" + fi +fi + +live_helper() { + local p="$1" + # COMM is 15 chars; use full cmdline match + pgrep -f "(^|/)(${p})( |$)" >/dev/null 2>&1 +} + +for p in taler-exchange-aggregator taler-exchange-wirewatch taler-exchange-transfer taler-exchange-closer; do + if live_helper "$p"; then + ok "process $p" + else + bad "process $p not running (settlement needs transfer+aggregator)" + fi +done + +if [ "$fail" -eq 0 ]; then + green "=== ALL CRITICAL CHECKS PASSED ===" + exit 0 +fi +red "=== SOME CHECKS FAILED ===" +exit 1 diff --git a/scripts/taler-exchange/offline-sign-upload-keys.sh b/scripts/taler-exchange/offline-sign-upload-keys.sh new file mode 100755 index 0000000..7dd4708 --- /dev/null +++ b/scripts/taler-exchange/offline-sign-upload-keys.sh @@ -0,0 +1,62 @@ +#!/bin/bash +# Offline: download future keys from local exchange, sign, upload. Restore public BASE_URL. +# Run as root inside taler-exchange-hacktivism. +set -euo pipefail +CONF=/etc/taler-exchange/taler-exchange.conf +OV=/etc/taler-exchange/exchange-overrides.conf +PUBLIC='https://exchange.hacktivism.ch/' +LOCAL='http://127.0.0.1:9011/' + +[ "$(id -u)" -eq 0 ] || { echo "root only"; exit 1; } + +sed -i "s|^BASE_URL = .*|BASE_URL = ${LOCAL}|" "$OV" +echo "BASE_URL=$(grep '^BASE_URL' "$OV")" + +echo "=== download ===" +if ! runuser -u taler-exchange-offline -- \ + taler-exchange-offline -c "$CONF" -L INFO download > /tmp/future-keys.json 2>/tmp/dl.err; then + echo "download failed:"; cat /tmp/dl.err + # still show size +fi +echo "dl_err:"; cat /tmp/dl.err | tail -30 +echo "dl_size=$(wc -c /dev/null || echo 0)" +head -c 400 /tmp/future-keys.json 2>/dev/null; echo + +if [ ! -s /tmp/future-keys.json ]; then + echo "empty download — check secmod connectivity / management API" + sed -i "s|^BASE_URL = .*|BASE_URL = ${PUBLIC}|" "$OV" + exit 1 +fi + +echo "=== sign ===" +runuser -u taler-exchange-offline -- \ + taler-exchange-offline -c "$CONF" -L INFO sign < /tmp/future-keys.json > /tmp/signed-keys.json 2>/tmp/sg.err +echo "sg_err:"; cat /tmp/sg.err | tail -20 +echo "sg_size=$(wc -c /tmp/up.err +echo "up_err:"; cat /tmp/up.err | tail -20 + +sed -i "s|^BASE_URL = .*|BASE_URL = ${PUBLIC}|" "$OV" +echo "BASE_URL restored=$(grep '^BASE_URL' "$OV")" + +echo "=== /keys probe ===" +sleep 1 +# wake suspended handlers with a tiny delay +for i in 1 2 3 4 5 6; do + code=$(curl -sS -m 12 -o /tmp/keys.json -w '%{http_code}' http://127.0.0.1:9011/keys || true) + sz=$(wc -c /dev/null || echo 0) + echo "try $i code=$code size=$sz" + if [ "$code" = "200" ] && [ "$sz" -gt 200 ]; then + echo KEYS_OK + head -c 300 /tmp/keys.json; echo + grep -oE '"master_public_key"[[:space:]]*:[[:space:]]*"[^"]+"' /tmp/keys.json | head -1 || true + exit 0 + fi + sleep 2 +done +echo KEYS_FAIL +tail -25 /var/log/taler-exchange/taler-exchange-httpd-*.log 2>/dev/null | tail -25 +exit 1 diff --git a/scripts/taler-exchange/start_base_services_for_taler_exchange.sh b/scripts/taler-exchange/start_base_services_for_taler_exchange.sh new file mode 100755 index 0000000..ad247e4 --- /dev/null +++ b/scripts/taler-exchange/start_base_services_for_taler_exchange.sh @@ -0,0 +1,148 @@ +#!/bin/bash +# Root: base services for manual exchange (like merchant start_base_services_for_taler.sh). +# Then interactive shell as taler-exchange-httpd → run start_exchange.sh there. +# +# Secmods run as dedicated users (not httpd) — started here as root. +# httpd is started by /usr/local/bin/start_exchange.sh as taler-exchange-httpd. +# +# Usage: +# /root/start_base_services_for_taler_exchange.sh +# /root/start_base_services_for_taler_exchange.sh --no-shell + +set -e +CONF=/etc/taler-exchange/taler-exchange.conf +LOG_DIR=/var/log/taler-exchange +PWD_BIN=/usr/local/bin +EXCHANGE_STARTER=start_exchange.sh + +if [ "$(id -u)" -ne 0 ]; then + echo "Run as root" >&2 + exit 1 +fi + +NO_SHELL=0 +for arg in "$@"; do + case "$arg" in + --no-shell|-n) NO_SHELL=1 ;; + --help|-h) + echo "Usage: $0 [--no-shell]" + exit 0 + ;; + esac +done + +# --- Debian postgresql defaults (pg_createcluster layout) --- +ensure_postgresql() { + echo " Debian perms on /etc/postgresql + data/log/run..." + if [ -d /etc/postgresql ]; then + chown -R root:postgres /etc/postgresql + find /etc/postgresql -type d -exec chmod 755 {} \; + find /etc/postgresql -type f -name '*.conf' -exec chmod 640 {} \; + fi + chown -R postgres:postgres /var/lib/postgresql /var/log/postgresql 2>/dev/null || true + mkdir -p /var/run/postgresql + chown postgres:postgres /var/run/postgresql + chmod 2775 /var/run/postgresql 2>/dev/null || chmod 775 /var/run/postgresql + + if pg_isready -q 2>/dev/null; then + echo " already accepting connections" + pg_isready || true + return 0 + fi + + rm -f /var/run/postgresql/.s.PGSQL.*.lock 2>/dev/null || true + if ! pgrep -u postgres -x postgres >/dev/null 2>&1; then + rm -f /var/lib/postgresql/*/main/postmaster.pid 2>/dev/null || true + fi + + if command -v pg_ctlcluster >/dev/null 2>&1 && command -v pg_lsclusters >/dev/null 2>&1; then + while read -r ver name _rest; do + [ -n "$ver" ] || continue + echo " pg_ctlcluster $ver $name start" + pg_ctlcluster "$ver" "$name" start 2>/dev/null || true + done < <(pg_lsclusters --no-header 2>/dev/null || true) + fi + if ! pg_isready -q 2>/dev/null; then + if [ -x /etc/init.d/postgresql ]; then + /etc/init.d/postgresql start || true + else + service postgresql start || true + fi + fi + sleep 1 + pg_isready || true +} + +echo "Create log + runtime dirs... giving permission to taler-exchange users:" +mkdir -p "$LOG_DIR" +mkdir -p /run/taler-exchange/secmod-rsa /run/taler-exchange/secmod-cs \ + /run/taler-exchange/secmod-eddsa /run/taler-exchange/httpd +chown root:root /run/taler-exchange +chmod 755 /run/taler-exchange +chown taler-exchange-secmod-rsa:taler-exchange-secmod /run/taler-exchange/secmod-rsa +chown taler-exchange-secmod-cs:taler-exchange-secmod /run/taler-exchange/secmod-cs +chown taler-exchange-secmod-eddsa:taler-exchange-secmod /run/taler-exchange/secmod-eddsa +chown taler-exchange-httpd:www-data /run/taler-exchange/httpd +chmod 755 /run/taler-exchange/secmod-rsa /run/taler-exchange/secmod-cs /run/taler-exchange/secmod-eddsa +chmod 750 /run/taler-exchange/httpd +chown taler-exchange-httpd: "$LOG_DIR" +chmod 755 "$LOG_DIR" + +# Package default: each secmod user owns its tree (keys/ must not be root-owned). +mkdir -p /var/lib/taler-exchange/secmod-rsa /var/lib/taler-exchange/secmod-cs \ + /var/lib/taler-exchange/secmod-eddsa +chown -R taler-exchange-secmod-rsa:taler-exchange-secmod /var/lib/taler-exchange/secmod-rsa +chown -R taler-exchange-secmod-cs:taler-exchange-secmod /var/lib/taler-exchange/secmod-cs +chown -R taler-exchange-secmod-eddsa:taler-exchange-secmod /var/lib/taler-exchange/secmod-eddsa +chmod 700 /var/lib/taler-exchange/secmod-rsa /var/lib/taler-exchange/secmod-cs \ + /var/lib/taler-exchange/secmod-eddsa + +echo "Start base services needed for Taler Exchange." +echo "" + +echo "1. postgresql:" +ensure_postgresql + +# Linux COMM is 15 chars — never pgrep -x for long names; match full path in args. +start_bg() { + local user="$1"; shift + local name="$1"; shift + local bin="$1" + if ps -eo args= 2>/dev/null | grep -F "$bin" | grep -v grep >/dev/null 2>&1; then + echo " already running: $name" + return 0 + fi + echo " start $name as $user" + nohup runuser -u "$user" -- "$@" >>"$LOG_DIR/${name}.log" 2>&1 /dev/null || true + sleep 0.3 +} + +echo "2. crypto secmods:" +start_bg taler-exchange-secmod-rsa taler-exchange-secmod-rsa \ + /usr/bin/taler-exchange-secmod-rsa -c "$CONF" -L INFO +start_bg taler-exchange-secmod-cs taler-exchange-secmod-cs \ + /usr/bin/taler-exchange-secmod-cs -c "$CONF" -L INFO +start_bg taler-exchange-secmod-eddsa taler-exchange-secmod-eddsa \ + /usr/bin/taler-exchange-secmod-eddsa -c "$CONF" -L INFO + +echo "3. wire/db helpers (need wire config to stay up):" +start_bg taler-exchange-aggregator taler-exchange-aggregator \ + /usr/bin/taler-exchange-aggregator -c "$CONF" -L INFO || true +start_bg taler-exchange-closer taler-exchange-closer \ + /usr/bin/taler-exchange-closer -c "$CONF" -L INFO || true +start_bg taler-exchange-wire taler-exchange-wirewatch \ + /usr/bin/taler-exchange-wirewatch -c "$CONF" -L INFO || true +start_bg taler-exchange-wire taler-exchange-transfer \ + /usr/bin/taler-exchange-transfer -c "$CONF" -L INFO || true + +if [ "$NO_SHELL" -eq 1 ]; then + echo "Base services started (--no-shell). Next: runuser -u taler-exchange-httpd -- $PWD_BIN/$EXCHANGE_STARTER [--restart]" + exit 0 +fi + +echo "4. Switching now to user taler-exchange-httpd, in $PWD_BIN; find executable $EXCHANGE_STARTER there!" +echo "" +cd "$PWD_BIN" +# same pattern as merchant: -u and -s/--shell are mutually exclusive on util-linux runuser +exec runuser -u taler-exchange-httpd -- bash diff --git a/scripts/taler-exchange/start_exchange.sh b/scripts/taler-exchange/start_exchange.sh new file mode 100755 index 0000000..48f8171 --- /dev/null +++ b/scripts/taler-exchange/start_exchange.sh @@ -0,0 +1,104 @@ +#!/bin/bash +# Start / restart taler-exchange-httpd (manual, no systemd). +# Run as: taler-exchange-httpd +# Same role as start_merchant.sh for the merchant. +# +# Usage: +# start_exchange.sh +# start_exchange.sh --restart | -r +# start_exchange.sh --help + +set -u + +usage() { + cat <<'EOF' +Usage: start_exchange.sh [--restart|-r] [--help|-h] + + (default) Start taler-exchange-httpd if not already running. + --restart Stop live taler-exchange-httpd, then start cleanly. + Does not touch postgres/secmods/wire helpers + (those come from /root/start_base_services_for_taler_exchange.sh). +EOF +} + +DO_RESTART=0 +for arg in "$@"; do + case "$arg" in + --restart|-r) DO_RESTART=1 ;; + --help|-h) usage; exit 0 ;; + *) echo "Unknown option: $arg" >&2; usage >&2; exit 2 ;; + esac +done + +if [ "$(id -un)" != "taler-exchange-httpd" ]; then + echo "This script must be run as user taler-exchange-httpd" >&2 + exit 1 +fi + +CONF=/etc/taler-exchange/taler-exchange.conf +LOG_DIR=/var/log/taler-exchange +PORT=$(taler-exchange-config -c "$CONF" -s exchange -o PORT 2>/dev/null || echo 9011) + +list_httpd_pids() { + ps -eo pid=,stat=,args= 2>/dev/null | while read -r pid stat args; do + case "$stat" in Z*) continue ;; esac + case "$args" in + *start_exchange.sh*) continue ;; + esac + case "$args" in + *taler-exchange-httpd\ *|taler-exchange-httpd\ *|/usr/bin/taler-exchange-httpd\ *) + echo "$pid" + ;; + esac + done | sort -u +} + +kill_httpd() { + local pids + pids=$(list_httpd_pids | tr '\n' ' ') + if [ -z "${pids// }" ]; then + echo "No live taler-exchange-httpd processes to stop." + return 0 + fi + echo "Stopping PIDs: $pids" + # shellcheck disable=SC2086 + kill -TERM $pids 2>/dev/null || true + sleep 2 + local left + left=$(list_httpd_pids | tr '\n' ' ') + if [ -n "${left// }" ]; then + echo "SIGKILL remaining: $left" + # shellcheck disable=SC2086 + kill -KILL $left 2>/dev/null || true + sleep 1 + fi + echo "taler-exchange-httpd stopped." +} + +if [ "$DO_RESTART" -eq 1 ]; then + echo "=== restart: kill taler-exchange-httpd ===" + kill_httpd +fi + +echo "Start taler-exchange-httpd:" +LOG_FILE="$LOG_DIR/taler-exchange-httpd-$(date +%Y-%m-%d).log" +mkdir -p "$LOG_DIR" +touch "$LOG_FILE" 2>/dev/null || true + +if [ "$DO_RESTART" -eq 0 ] && [ -n "$(list_httpd_pids)" ]; then + echo "taler-exchange-httpd already running" +else + nohup taler-exchange-httpd -c "$CONF" -L INFO >>"$LOG_FILE" 2>&1 & + disown 2>/dev/null || true + sleep 2 +fi + +echo "Live processes:" +ps -eo pid,stat,args 2>/dev/null | grep taler-exchange-httpd | grep -v grep | grep -v ' Z ' || true + +if [ -x /usr/local/bin/check_exchange-health.sh ]; then + /usr/local/bin/check_exchange-health.sh || exit 1 +elif [ -x ./check_exchange-health.sh ]; then + ./check_exchange-health.sh || exit 1 +fi +exit 0 diff --git a/scripts/taler-exchange/start_wire_helpers.sh b/scripts/taler-exchange/start_wire_helpers.sh new file mode 100755 index 0000000..4343267 --- /dev/null +++ b/scripts/taler-exchange/start_wire_helpers.sh @@ -0,0 +1,43 @@ +#!/bin/bash +# Start wire/db helpers only (root, no interactive shell, no systemd). +# Uses nohup so helpers survive the launching shell. +set -euo pipefail +ROOT=$(cd "$(dirname "$0")" && pwd) +if [ -x "$ROOT/ensure_exchange_helpers.sh" ]; then + exec "$ROOT/ensure_exchange_helpers.sh" +fi +if [ -x /usr/local/bin/ensure_exchange_helpers.sh ]; then + exec /usr/local/bin/ensure_exchange_helpers.sh +fi +# Fallback if ensure not installed yet +CONF=/etc/taler-exchange/taler-exchange.conf +LOG_DIR=/var/log/taler-exchange +[ "$(id -u)" -eq 0 ] || { echo "root only"; exit 1; } +mkdir -p "$LOG_DIR" + +start_bg() { + local user="$1"; shift + local name="$1"; shift + local bin="$1" + if pgrep -u "$user" -x "$(basename "$bin")" >/dev/null 2>&1; then + echo "already: $name" + return 0 + fi + echo "start: $name as $user" + nohup runuser -u "$user" -- "$@" >>"$LOG_DIR/${name}.log" 2>&1 /dev/null || true + sleep 0.4 +} + +start_bg taler-exchange-aggregator taler-exchange-aggregator \ + /usr/bin/taler-exchange-aggregator -c "$CONF" -L INFO +start_bg taler-exchange-closer taler-exchange-closer \ + /usr/bin/taler-exchange-closer -c "$CONF" -L INFO +start_bg taler-exchange-wire taler-exchange-wirewatch \ + /usr/bin/taler-exchange-wirewatch -c "$CONF" -L INFO +start_bg taler-exchange-wire taler-exchange-transfer \ + /usr/bin/taler-exchange-transfer -c "$CONF" -L INFO + +sleep 1 +pgrep -af 'taler-exchange-(aggregator|closer|wirewatch|transfer|httpd|secmod)' || true +[ -x /usr/local/bin/check_exchange-health.sh ] && /usr/local/bin/check_exchange-health.sh || true diff --git a/scripts/taler-exchange/wire-enable-and-upload.sh b/scripts/taler-exchange/wire-enable-and-upload.sh new file mode 100755 index 0000000..c5da9b2 --- /dev/null +++ b/scripts/taler-exchange/wire-enable-and-upload.sh @@ -0,0 +1,91 @@ +#!/bin/bash +# One-shot: offline enable wire account + fees, upload to local exchange, verify /keys. +# Run as root inside container taler-exchange-hacktivism. +set -euo pipefail + +CONF=/etc/taler-exchange/taler-exchange.conf +OV=/etc/taler-exchange/exchange-overrides.conf +PAYTO='payto://x-taler-bank/bank.hacktivism.ch/exchange?receiver-name=GOA%20Exchange' +LOCAL_BASE='http://127.0.0.1:9011/' +OPS=/tmp/offline-wire-ops-$$.json +OFFLINE_USER=taler-exchange-offline + +if [ "$(id -u)" -ne 0 ]; then + echo "Run as root in exchange container" >&2 + exit 1 +fi + +echo "=== 1. Ensure wire account stanza in exchange-overrides ===" +if ! grep -q '\[exchange-account-1\]' "$OV" 2>/dev/null; then + cat >>"$OV" <<'EOF' + +### Regional bank (libeufin, x-taler-bank) — no IBAN +[exchange-account-1] +PAYTO_URI = payto://x-taler-bank/bank.hacktivism.ch/exchange?receiver-name=GOA%20Exchange +ENABLE_CREDIT = YES +ENABLE_DEBIT = YES +@inline-secret@ exchange-accountcredentials-1 ../secrets/exchange-accountcredentials-1.secret.conf +EOF + echo "appended exchange-account-1" +else + echo "exchange-account-1 already present" +fi + +# credentials must be readable by wire helpers + httpd +if [ -f /etc/taler-exchange/secrets/exchange-accountcredentials-1.secret.conf ]; then + chown root:taler-exchange-wire /etc/taler-exchange/secrets/exchange-accountcredentials-1.secret.conf 2>/dev/null \ + || chown root:root /etc/taler-exchange/secrets/exchange-accountcredentials-1.secret.conf + chmod 640 /etc/taler-exchange/secrets/exchange-accountcredentials-1.secret.conf +fi + +echo "=== 2. Point BASE_URL at local httpd for offline upload ===" +# save public URL +PUBLIC_BASE=$(taler-config -c "$CONF" -s exchange -o BASE_URL 2>/dev/null || echo 'https://exchange.hacktivism.ch/') +# temporary override file (highest priority if inlined last — patch OV) +if grep -q '^BASE_URL' "$OV"; then + sed -i "s|^BASE_URL = .*|BASE_URL = ${LOCAL_BASE}|" "$OV" +else + echo "BASE_URL = ${LOCAL_BASE}" >>"$OV" +fi +echo "BASE_URL now: $(taler-config -c "$CONF" -s exchange -o BASE_URL)" + +echo "=== 3. Sign enable-account + wire-fee + global-fee (as offline user) ===" +# global-fee: year, history, account, purse, purse_timeout, history_expiration, max_free_purses +runuser -u "$OFFLINE_USER" -- taler-exchange-offline -c "$CONF" \ + enable-account "$PAYTO" \ + wire-fee now x-taler-bank GOA:0 GOA:0 \ + global-fee now GOA:0 GOA:0 GOA:0 '1 day' '1 year' 5 \ + >"$OPS" +echo "ops bytes: $(wc -c <"$OPS")" +head -c 200 "$OPS"; echo + +echo "=== 4. Upload ops to exchange ===" +runuser -u "$OFFLINE_USER" -- taler-exchange-offline -c "$CONF" upload <"$OPS" +echo "upload exit: $?" + +echo "=== 5. Restore public BASE_URL ===" +sed -i "s|^BASE_URL = .*|BASE_URL = ${PUBLIC_BASE}|" "$OV" +# ensure trailing slash style +if ! grep -q "BASE_URL = https://exchange.hacktivism.ch" "$OV"; then + sed -i "s|^BASE_URL = .*|BASE_URL = https://exchange.hacktivism.ch/|" "$OV" +fi +echo "BASE_URL restored: $(taler-config -c "$CONF" -s exchange -o BASE_URL)" + +echo "=== 6. Probe /keys (may need httpd already running) ===" +for i in 1 2 3 4 5; do + code=$(curl -sS -m 8 -o /tmp/keys-out.json -w '%{http_code}' http://127.0.0.1:9011/keys || echo fail) + sz=$(wc -c /dev/null || echo 0) + echo "try $i: http=$code size=$sz" + if [ "$code" = "200" ] && [ "${sz:-0}" -gt 100 ]; then + echo "KEYS_OK" + head -c 250 /tmp/keys-out.json; echo + # show accounts if present + grep -oE '"payto_uri"[^,}]+|"master_public_key"[^,}]+' /tmp/keys-out.json | head -10 || true + exit 0 + fi + sleep 2 +done + +echo "KEYS_NOT_YET — check logs" +tail -30 /var/log/taler-exchange/taler-exchange-httpd-*.log 2>/dev/null | tail -30 +exit 1