exchange: manual start/health + zz-hacktivism conf drop-in

This commit is contained in:
Hernâni Marques 2026-06-30 19:14:49 +02:00
parent 07e7131850
commit 5920296ae4
No known key found for this signature in database
10 changed files with 1027 additions and 0 deletions

View file

@ -0,0 +1,3 @@
32:@inline-matching@ conf.d/*.conf
35:@inline@ overrides.conf
50:@inline@ exchange-overrides.conf

View file

@ -0,0 +1,2 @@
[exchange]
CURRENCY = GOA

View file

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

View file

@ -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" <<SEOF
# generated by exchange-bootstrap.sh
[exchange]
ATTRIBUTE_ENCRYPTION_KEY = ${ATTR_KEY}
SEOF
chmod 640 "$SECRETS_DIR/exchange-attribute-encryption.secret.conf"
chown root:taler-exchange-httpd "$SECRETS_DIR/exchange-attribute-encryption.secret.conf" 2>/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" <<EOF
# Manual site overrides for exchange.hacktivism.ch (GOA exploration currency).
# Same role as /etc/taler-merchant/merchant-overrides.conf on taler-hacktivism.
# Do not edit overrides.conf (tooling). Do not edit package conf.d defaults.
# No conf.d/zz-* drop-ins.
[exchange]
CURRENCY = GOA
CURRENCY_ROUND_UNIT = GOA:0.00000001
TINY_AMOUNT = GOA:0.00000001
DEFAULT_P2P_EXPIRATION = 14 days
BASE_URL = https://exchange.hacktivism.ch/
SERVE = tcp
PORT = 9011
MASTER_PUBLIC_KEY = ${MASTER_PUB}
# ATTRIBUTE_ENCRYPTION_KEY via @inline-secret@ (see secrets/)
@inline-secret@ exchange-attribute-encryption ../secrets/exchange-attribute-encryption.secret.conf
[currency-goa]
ENABLED = YES
name = "GOA exploration currency"
code = GOA
# SI display units. taler-exchange rejects scale keys outside [-8, 24] (no Ronna/Quetta).
fractional_input_digits = 8
fractional_normal_digits = 0
fractional_trailing_zero_digits = 0
alt_unit_names_are_symbols = NO
alt_unit_names = {"24":"Yotta-GOA","21":"Zetta-GOA","18":"Exa-GOA","15":"Peta-GOA","12":"Tera-GOA","9":"Giga-GOA","6":"Mega-GOA","3":"Kilo-GOA","0":"GOA","-1":"Deci-GOA","-2":"Centi-GOA","-3":"Milli-GOA","-6":"Micro-GOA","-7":"Deci-Micro-GOA","-8":"Atomic-GOA"}
common_amounts = "GOA:10 GOA:20 GOA:50 GOA:100 GOA:200 GOA:1000"
### Disable package demonstrator currencies (like merchant-overrides)
[currency-kudos]
ENABLED = NO
[currency-testkudos]
ENABLED = NO
EOF
echo "Wrote $OV"
grep -E '^(name|code|PORT|CURRENCY|alt_unit|fractional_input|common_)' "$OV" || true
echo "=== 4. GOA coin denominations (125 ladder) ==="
if [ -f "$COINS" ] && ! grep -q 'VALUE = GOA' "$COINS" 2>/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 125 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"

View file

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

View file

@ -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 </tmp/future-keys.json 2>/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/signed-keys.json)"
echo "=== upload ==="
runuser -u taler-exchange-offline -- \
taler-exchange-offline -c "$CONF" -L INFO upload < /tmp/signed-keys.json 2>/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 </tmp/keys.json 2>/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

View file

@ -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 &
disown 2>/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

View file

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

View file

@ -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 &
disown 2>/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

View file

@ -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 </tmp/keys-out.json 2>/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