docs: new root as prior history lost (orphan + GC); ~215 commits not recoverable
This commit is contained in:
commit
96961f23f5
268 changed files with 24161 additions and 0 deletions
35
scripts/taler-exchange/archive/README.md
Normal file
35
scripts/taler-exchange/archive/README.md
Normal 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 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 |
|
||||
451
scripts/taler-exchange/archive/exchange-bootstrap.sh
Executable file
451
scripts/taler-exchange/archive/exchange-bootstrap.sh
Executable 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 (1–2–5 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 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"
|
||||
88
scripts/taler-exchange/check_exchange-health.sh
Executable file
88
scripts/taler-exchange/check_exchange-health.sh
Executable 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
|
||||
75
scripts/taler-exchange/ensure_exchange_helpers.sh
Executable file
75
scripts/taler-exchange/ensure_exchange_helpers.sh
Executable file
|
|
@ -0,0 +1,75 @@
|
|||
#!/bin/bash
|
||||
# Ensure exchange wire/db helper processes are running (no systemd).
|
||||
# Root inside taler-exchange container (or host if packages installed there).
|
||||
#
|
||||
# Usage:
|
||||
# ensure_exchange_helpers.sh
|
||||
# ENSURE_ONLY=1 ensure_exchange_helpers.sh # start missing, no health summary
|
||||
set -euo pipefail
|
||||
|
||||
CONF="${TALER_EXCHANGE_CONFIG:-/etc/taler-exchange/taler-exchange.conf}"
|
||||
LOG_DIR="${TALER_EXCHANGE_LOG_DIR:-/var/log/taler-exchange}"
|
||||
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo "root only" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$LOG_DIR"
|
||||
chmod 755 "$LOG_DIR" 2>/dev/null || true
|
||||
|
||||
# Live process under user. Note: Linux COMM is 15 chars — never use pgrep -x
|
||||
# for long names like taler-exchange-aggregator / taler-exchange-wirewatch.
|
||||
is_running() {
|
||||
local user="$1"
|
||||
local bin="$2"
|
||||
local base
|
||||
base=$(basename "$bin")
|
||||
# [t] trick avoids matching this grep/pgrep itself
|
||||
ps -u "$user" -o args= 2>/dev/null | grep -qE "(^|/)[${base:0:1}]${base:1}( |$)" \
|
||||
|| pgrep -u "$user" -f "(^|/)(${base})( |$)" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# Start with nohup so SIGHUP from parent shell exit does not kill helpers.
|
||||
start_one() {
|
||||
local user="$1"
|
||||
local name="$2"
|
||||
local bin="$3"
|
||||
shift 3
|
||||
if is_running "$user" "$bin"; then
|
||||
echo "already: $name ($user)"
|
||||
return 0
|
||||
fi
|
||||
echo "start: $name as $user"
|
||||
# shellcheck disable=SC2086
|
||||
nohup runuser -u "$user" -- "$bin" "$@" >>"$LOG_DIR/${name}.log" 2>&1 </dev/null &
|
||||
disown 2>/dev/null || true
|
||||
local i
|
||||
for i in 1 2 3 4 5 6; do
|
||||
sleep 0.4
|
||||
if is_running "$user" "$bin"; then
|
||||
echo " ok: $name"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
echo " FAIL: $name did not stay up (see $LOG_DIR/${name}.log)" >&2
|
||||
tail -15 "$LOG_DIR/${name}.log" 2>/dev/null || true
|
||||
return 1
|
||||
}
|
||||
|
||||
ec=0
|
||||
start_one taler-exchange-aggregator taler-exchange-aggregator \
|
||||
/usr/bin/taler-exchange-aggregator -c "$CONF" -L INFO || ec=1
|
||||
start_one taler-exchange-closer taler-exchange-closer \
|
||||
/usr/bin/taler-exchange-closer -c "$CONF" -L INFO || ec=1
|
||||
start_one taler-exchange-wire taler-exchange-wirewatch \
|
||||
/usr/bin/taler-exchange-wirewatch -c "$CONF" -L INFO || ec=1
|
||||
start_one taler-exchange-wire taler-exchange-transfer \
|
||||
/usr/bin/taler-exchange-transfer -c "$CONF" -L INFO || ec=1
|
||||
|
||||
echo "--- live helpers ---"
|
||||
ps -eo pid,user,stat,etime,args 2>/dev/null \
|
||||
| grep -E 'taler-exchange-(aggregator|closer|wirewatch|transfer)' \
|
||||
| grep -vE 'grep| Z |ensure_exchange' || true
|
||||
|
||||
exit "$ec"
|
||||
263
scripts/taler-exchange/install_no_terms.sh
Executable file
263
scripts/taler-exchange/install_no_terms.sh
Executable file
|
|
@ -0,0 +1,263 @@
|
|||
#!/bin/bash
|
||||
# Install "No Terms Required" ToS + privacy for the exchange (styled like merchant terms).
|
||||
# Run as root inside the exchange container.
|
||||
set -euo pipefail
|
||||
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin${PATH:+:$PATH}"
|
||||
|
||||
CONF="${TALER_EXCHANGE_CONFIG:-/etc/taler-exchange/taler-exchange.conf}"
|
||||
DATA_HOME=$(taler-exchange-config -c "$CONF" -f -s PATHS -o TALER_DATA_HOME 2>/dev/null || true)
|
||||
DATA_HOME="${DATA_HOME:-/var/lib/taler-exchange/}"
|
||||
TERMS_DIR="${DATA_HOME%/}/terms"
|
||||
LANG_DIR="$TERMS_DIR/en"
|
||||
mkdir -p "$LANG_DIR"
|
||||
|
||||
# Bump when HTML/style changes (long Cache-Control on /terms).
|
||||
TOS_ETAG="${TERMS_ETAG:-no-terms-v2}"
|
||||
# Prefer Swiss FADP privacy install (install_swiss_privacy.sh); fallback etag here
|
||||
PP_ETAG="${PRIVACY_ETAG:-exchange-pp-swiss-v0}"
|
||||
|
||||
# Shared palette with merchant-tos-dual / bank terms pages
|
||||
COMMON_CSS='
|
||||
:root { color-scheme: dark light; }
|
||||
body {
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
max-width: 40rem; margin: 2rem auto; padding: 0 1.1rem 3rem;
|
||||
line-height: 1.5; color: #e8e6e3; background: #1a1520;
|
||||
}
|
||||
h1 { font-size: 1.35rem; font-weight: 800; margin: 0 0 1rem; color: #f5f0ea; }
|
||||
h2 { font-size: 1.05rem; margin: 1.4rem 0 0.5rem; color: #e8c878; }
|
||||
p, li { font-size: 0.98rem; }
|
||||
ul { padding-left: 1.2rem; }
|
||||
code, a { color: #5eead4; }
|
||||
a { text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
.badge {
|
||||
display: inline-block; font-size: 0.72rem; font-weight: 700;
|
||||
letter-spacing: 0.06em; text-transform: uppercase;
|
||||
color: #c4b5fd; border: 1px solid rgba(196,181,253,0.35);
|
||||
border-radius: 999px; padding: 0.2rem 0.65rem; margin-bottom: 0.85rem;
|
||||
}
|
||||
.cur {
|
||||
border-radius: 12px; padding: 0.75rem 0.9rem; margin: 0.85rem 0 1rem;
|
||||
border: 1px solid rgba(255,255,255,0.1); background: rgba(0,0,0,0.25);
|
||||
}
|
||||
.cur strong { display: block; font-size: 1.05rem; margin-bottom: 0.25rem; color: #5eead4; }
|
||||
.muted { color: #a39e98; font-size: 0.88rem; }
|
||||
footer { margin-top: 2rem; font-size: 0.85rem; color: #a39e98; }
|
||||
'
|
||||
|
||||
write_tos() {
|
||||
local base="$1"
|
||||
local title="No Terms Required"
|
||||
local body_txt body_md
|
||||
|
||||
body_txt='No Terms Required.
|
||||
|
||||
This is an experimental / exploration GNU Taler exchange for the currency GOA (hacktivism.ch).
|
||||
|
||||
No formal terms of service are required to use this service.
|
||||
|
||||
By withdrawing or using GOA coins you acknowledge that:
|
||||
- This service is for exploration and testing only.
|
||||
- GOA is not legal tender and has no guaranteed real-world value or redemption.
|
||||
- There is no guaranteed availability, support, or uptime.
|
||||
- Operators may reset balances or change configuration without notice.
|
||||
- Do not use real money you cannot afford to lose.
|
||||
|
||||
If you do not agree, do not use this exchange.
|
||||
|
||||
Related:
|
||||
- Bank: https://bank.hacktivism.ch/intro/
|
||||
- Merchant terms: https://taler.hacktivism.ch/terms
|
||||
- Bank terms: https://bank.hacktivism.ch/terms
|
||||
|
||||
Privacy:
|
||||
Processing under Swiss FADP (revDSG). What data is retained is listed at
|
||||
https://exchange.hacktivism.ch/privacy
|
||||
'
|
||||
|
||||
body_md='# No Terms Required
|
||||
|
||||
This is an **experimental / exploration** GNU Taler exchange for the currency **GOA** (hacktivism.ch).
|
||||
|
||||
**No formal terms of service** are required to use this service.
|
||||
|
||||
By withdrawing or using GOA coins you acknowledge that:
|
||||
|
||||
- This service is for exploration and testing only.
|
||||
- GOA is not legal tender and has no guaranteed real-world value or redemption.
|
||||
- There is no guaranteed availability, support, or uptime.
|
||||
- Operators may reset balances or change configuration without notice.
|
||||
- Do not use real money you cannot afford to lose.
|
||||
|
||||
If you do not agree, do not use this exchange.
|
||||
|
||||
## Related
|
||||
|
||||
- [Bank intro](https://bank.hacktivism.ch/intro/)
|
||||
- [Bank terms](https://bank.hacktivism.ch/terms)
|
||||
- [Merchant terms](https://taler.hacktivism.ch/terms)
|
||||
- [Exchange privacy](https://exchange.hacktivism.ch/privacy)
|
||||
|
||||
## Privacy
|
||||
|
||||
Processing under Swiss FADP (revDSG). What data is retained is listed on
|
||||
[exchange.hacktivism.ch/privacy](https://exchange.hacktivism.ch/privacy).
|
||||
'
|
||||
|
||||
printf '%s\n' "$body_txt" >"$LANG_DIR/${base}.txt"
|
||||
printf '%s\n' "$body_md" >"$LANG_DIR/${base}.md"
|
||||
cat >"$LANG_DIR/${base}.html" <<HTML
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<title>${title}</title>
|
||||
<style>
|
||||
${COMMON_CSS}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="badge">exchange.hacktivism.ch · GOA</div>
|
||||
<h1>${title}</h1>
|
||||
<p>This is an <strong>experimental / exploration</strong> GNU Taler exchange for the
|
||||
currency <strong>GOA</strong> at <code>exchange.hacktivism.ch</code> (hacktivism.ch).</p>
|
||||
<p><strong>No formal terms of service</strong> are required to use this service.</p>
|
||||
|
||||
<div class="cur">
|
||||
<strong>GOA · explorational</strong>
|
||||
Not legal tender. No guaranteed real-world value, redemption, or convertibility.
|
||||
Issued only for exploration and testing on this stack.
|
||||
</div>
|
||||
|
||||
<h2>By withdrawing or using GOA coins you acknowledge</h2>
|
||||
<ul>
|
||||
<li>This service is for exploration and testing only.</li>
|
||||
<li>There is no guaranteed availability, support, or uptime.</li>
|
||||
<li>Operators may reset balances or change configuration without notice.</li>
|
||||
<li>Do not use real money you cannot afford to lose.</li>
|
||||
<li>Software is provided as-is, without warranty.</li>
|
||||
</ul>
|
||||
<p>If you do not agree, do not use this exchange.</p>
|
||||
|
||||
<h2>Related</h2>
|
||||
<ul>
|
||||
<li><a href="https://bank.hacktivism.ch/intro/">Bank intro</a></li>
|
||||
<li><a href="https://bank.hacktivism.ch/terms">Bank terms</a></li>
|
||||
<li><a href="https://taler.hacktivism.ch/terms">Merchant terms</a></li>
|
||||
<li><a href="https://exchange.hacktivism.ch/intro/">Exchange intro</a></li>
|
||||
<li><a href="https://exchange.hacktivism.ch/privacy">Exchange privacy</a></li>
|
||||
</ul>
|
||||
|
||||
<h2>Privacy</h2>
|
||||
<p class="muted">Processing under Swiss FADP (revDSG). What data is retained
|
||||
(reserves, wire-in, coins, logs, …) is listed on
|
||||
<a href="https://exchange.hacktivism.ch/privacy">/privacy</a>.</p>
|
||||
<footer class="muted">Version ${base} · hacktivism.ch</footer>
|
||||
</body>
|
||||
</html>
|
||||
HTML
|
||||
}
|
||||
|
||||
write_pp() {
|
||||
# Legacy short PP only if not using install_swiss_privacy.sh etag
|
||||
local base="$1"
|
||||
local title="Privacy notice · GOA Exchange"
|
||||
local body_txt body_md
|
||||
|
||||
body_txt='No Privacy Policy Required.
|
||||
|
||||
This is an experimental / exploration GNU Taler exchange for GOA.
|
||||
|
||||
No formal privacy policy is required for this demo service.
|
||||
|
||||
High-level notes:
|
||||
- Wire transfers via the regional bank may identify bank account holders.
|
||||
- The exchange processes withdrawals, deposits, and related protocol operations.
|
||||
- Logs may be kept for operation and debugging.
|
||||
|
||||
Do not use this service if that is unacceptable.
|
||||
|
||||
Related:
|
||||
- Exchange terms: https://exchange.hacktivism.ch/terms
|
||||
'
|
||||
|
||||
body_md='# No Privacy Policy Required
|
||||
|
||||
This is an **experimental / exploration** GNU Taler exchange for **GOA**.
|
||||
|
||||
**No formal privacy policy** is required for this demo service.
|
||||
|
||||
## High-level notes
|
||||
|
||||
- Wire transfers via the regional bank may identify bank account holders.
|
||||
- The exchange processes withdrawals, deposits, and related protocol operations.
|
||||
- Logs may be kept for operation and debugging.
|
||||
|
||||
Do not use this service if that is unacceptable.
|
||||
|
||||
## Related
|
||||
|
||||
- [Exchange terms](https://exchange.hacktivism.ch/terms)
|
||||
'
|
||||
|
||||
printf '%s\n' "$body_txt" >"$LANG_DIR/${base}.txt"
|
||||
printf '%s\n' "$body_md" >"$LANG_DIR/${base}.md"
|
||||
cat >"$LANG_DIR/${base}.html" <<HTML
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<title>${title}</title>
|
||||
<style>
|
||||
${COMMON_CSS}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="badge">exchange.hacktivism.ch · privacy</div>
|
||||
<h1>${title}</h1>
|
||||
<p>This is an <strong>experimental / exploration</strong> GNU Taler exchange for
|
||||
<strong>GOA</strong> at <code>exchange.hacktivism.ch</code>.</p>
|
||||
<p><strong>No formal privacy policy</strong> is required for this demo service.</p>
|
||||
|
||||
<h2>High-level notes</h2>
|
||||
<ul>
|
||||
<li>Wire transfers via the regional bank may identify bank account holders.</li>
|
||||
<li>The exchange processes withdrawals, deposits, and related protocol operations.</li>
|
||||
<li>Logs may be kept for operation and debugging.</li>
|
||||
</ul>
|
||||
<p>Do not use this service if that is unacceptable.</p>
|
||||
|
||||
<h2>Related</h2>
|
||||
<ul>
|
||||
<li><a href="https://exchange.hacktivism.ch/terms">Exchange terms</a></li>
|
||||
<li><a href="https://bank.hacktivism.ch/terms">Bank terms</a></li>
|
||||
<li><a href="https://taler.hacktivism.ch/terms">Merchant terms</a></li>
|
||||
</ul>
|
||||
<footer class="muted">Version ${base}</footer>
|
||||
</body>
|
||||
</html>
|
||||
HTML
|
||||
}
|
||||
|
||||
write_tos "$TOS_ETAG"
|
||||
# Prefer Swiss FADP privacy installer when present (precise retention tables)
|
||||
if [ -x /usr/local/bin/install_swiss_privacy.sh ]; then
|
||||
PRIVACY_ETAG="$PP_ETAG" /usr/local/bin/install_swiss_privacy.sh
|
||||
else
|
||||
write_pp "$PP_ETAG"
|
||||
fi
|
||||
|
||||
chmod -R a+rX "$TERMS_DIR"
|
||||
if id taler-exchange-httpd >/dev/null 2>&1; then
|
||||
chown -R taler-exchange-httpd: "$TERMS_DIR" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
echo "Installed under $LANG_DIR:"
|
||||
ls -la "$LANG_DIR"/${TOS_ETAG}.* "$LANG_DIR"/${PP_ETAG}.* 2>/dev/null || ls -la "$LANG_DIR"
|
||||
echo "Config should set:"
|
||||
echo " TERMS_ETAG = ${TOS_ETAG}"
|
||||
echo " PRIVACY_ETAG = ${PP_ETAG}"
|
||||
echo " TERMS_DIR / PRIVACY_DIR = \${TALER_DATA_HOME}terms/"
|
||||
168
scripts/taler-exchange/install_swiss_privacy.sh
Normal file
168
scripts/taler-exchange/install_swiss_privacy.sh
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
#!/bin/bash
|
||||
# Swiss FADP privacy for exchange.hacktivism.ch — run inside exchange container.
|
||||
set -euo pipefail
|
||||
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin${PATH:+:$PATH}"
|
||||
|
||||
ETAG="${PRIVACY_ETAG:-exchange-pp-swiss-v0}"
|
||||
CONF="${TALER_EXCHANGE_CONFIG:-/etc/taler-exchange/taler-exchange.conf}"
|
||||
DATA_HOME=$(taler-exchange-config -c "$CONF" -f -s PATHS -o TALER_DATA_HOME 2>/dev/null || true)
|
||||
DATA_HOME="${DATA_HOME:-/var/lib/taler-exchange/}"
|
||||
DIR="${PRIVACY_DIR:-${DATA_HOME%/}/terms}/en"
|
||||
mkdir -p "$DIR"
|
||||
|
||||
cat >"$DIR/${ETAG}.txt" <<'EOF'
|
||||
Privacy notice — exchange.hacktivism.ch (Swiss FADP / revDSG)
|
||||
|
||||
Controller: operators of the hacktivism.ch GNU Taler stack.
|
||||
Service: experimental GOA exchange (not a licensed Swiss bank or e-money institution).
|
||||
|
||||
Data retained (precise):
|
||||
1) Reserves — reserve public keys, current remaining amounts, creation/close times: for service life or wipe.
|
||||
2) Wire-in (reserves_in) — credit amounts, timestamps, bank-side account reference as provided by the bank/wire gateway: for service life; links reserve funding to bank accounts.
|
||||
3) Coins — known coin public keys, denomination serials, remaining value, spend/deposit linkage as required by protocol: for service life.
|
||||
4) Withdraw / refresh / recoup / refund / deposit tables — protocol operation records and timestamps: for service life.
|
||||
5) Wire-out — transfers to merchant accounts (amounts, payto/target as stored, times): for service life.
|
||||
6) Keys / denominations — public denomination and signing keys (not personal data by themselves).
|
||||
7) Technical logs — HTTP and process logs (IP, path, status): days–weeks via rotation.
|
||||
8) Terms acceptance markers if stored by clients — as required by wallet software.
|
||||
|
||||
Not retained by the exchange as plaintext identity of wallet holders: GNU Taler is designed so coin spend is unlinkable to customer bank identity at the exchange when used as intended. Funding reserves via the bank may still identify the bank account holder on the bank side.
|
||||
|
||||
Purposes: operate GOA exchange protocol, settle with bank/merchants, security, operations.
|
||||
Legal basis (FADP): provision of requested exchange service; proportionate operation of experimental stack.
|
||||
Recipients: bank.hacktivism.ch (wire); merchants depositing GOA; host operators. No sale of data.
|
||||
Rights: access, rectification, deletion, objection (FADP); complaint to Swiss FDPIC (EDÖB).
|
||||
Security: TLS; experimental stack.
|
||||
|
||||
Related: https://exchange.hacktivism.ch/terms · https://bank.hacktivism.ch/intro/privacy.html · https://taler.hacktivism.ch/privacy
|
||||
EOF
|
||||
|
||||
cat >"$DIR/${ETAG}.md" <<'EOF'
|
||||
# Privacy notice · GOA Exchange · Swiss FADP
|
||||
|
||||
Controller: operators of **hacktivism.ch**. Service: experimental **GOA** exchange at `exchange.hacktivism.ch`.
|
||||
|
||||
Swiss Federal Act on Data Protection (**FADP / revDSG**, since 1 Sep 2023).
|
||||
|
||||
## Data retained
|
||||
|
||||
| Data | Examples | Retention |
|
||||
|------|----------|-----------|
|
||||
| Reserves | Reserve pubs, remaining amount, times | Service life or wipe |
|
||||
| Wire-in | Amounts, timestamps, bank account ref from wire path | Service life |
|
||||
| Coins | Coin pubs, denoms, remaining, deposit links | Service life |
|
||||
| Protocol ops | Withdraw, refresh, deposit, refund, recoup | Service life |
|
||||
| Wire-out | Merchant settlement amounts / targets | Service life |
|
||||
| Technical logs | IP, path, status | Days–weeks (rotation) |
|
||||
|
||||
**Design note:** Coin spend is intended to be unlinkable to the customer at the exchange. Funding a reserve via the bank may identify the **bank** account holder on the bank system.
|
||||
|
||||
## Purposes
|
||||
|
||||
Run the GOA exchange protocol; wire settlement; security and operations.
|
||||
|
||||
## Rights
|
||||
|
||||
FADP access, correction, deletion, objection. Complaint: Swiss **FDPIC / EDÖB**.
|
||||
|
||||
## Related
|
||||
|
||||
- [Exchange terms](https://exchange.hacktivism.ch/terms)
|
||||
- [Bank privacy](https://bank.hacktivism.ch/intro/privacy.html)
|
||||
- [Merchant privacy](https://taler.hacktivism.ch/privacy)
|
||||
EOF
|
||||
|
||||
cat >"$DIR/${ETAG}.html" <<'HTML'
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<title>Privacy notice · GOA Exchange · Swiss FADP</title>
|
||||
<style>
|
||||
:root { color-scheme: dark light; }
|
||||
body {
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
max-width: 42rem; margin: 2rem auto; padding: 0 1.1rem 3rem;
|
||||
line-height: 1.5; color: #e8e6e3; background: #1a1520;
|
||||
}
|
||||
h1 { font-size: 1.35rem; font-weight: 800; margin: 0 0 1rem; color: #f5f0ea; }
|
||||
h2 { font-size: 1.05rem; margin: 1.5rem 0 0.5rem; color: #e8c878; }
|
||||
p, li, td, th { font-size: 0.95rem; }
|
||||
ul { padding-left: 1.2rem; }
|
||||
code, a { color: #5eead4; }
|
||||
a { text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
.badge {
|
||||
display: inline-block; font-size: 0.72rem; font-weight: 700;
|
||||
letter-spacing: 0.06em; text-transform: uppercase;
|
||||
color: #c4b5fd; border: 1px solid rgba(196,181,253,0.35);
|
||||
border-radius: 999px; padding: 0.2rem 0.65rem; margin-bottom: 0.85rem;
|
||||
}
|
||||
.note {
|
||||
border-radius: 12px; padding: 0.75rem 0.9rem; margin: 0.85rem 0 1rem;
|
||||
border: 1px solid rgba(255,255,255,0.1); background: rgba(0,0,0,0.25);
|
||||
font-size: 0.9rem; color: #c8c4bf;
|
||||
}
|
||||
table { width: 100%; border-collapse: collapse; margin: 0.6rem 0 1rem; font-size: 0.88rem; }
|
||||
th, td { border: 1px solid rgba(255,255,255,0.12); padding: 0.45rem 0.55rem; text-align: left; vertical-align: top; }
|
||||
th { background: rgba(0,0,0,0.35); color: #e8c878; font-weight: 700; }
|
||||
.muted { color: #a39e98; font-size: 0.88rem; }
|
||||
footer { margin-top: 2rem; font-size: 0.85rem; color: #a39e98; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="badge">exchange.hacktivism.ch · privacy · CH</div>
|
||||
<h1>Privacy notice · GOA Exchange</h1>
|
||||
<p class="note">
|
||||
Swiss Federal Act on Data Protection (<strong>FADP / revDSG</strong>, since 1 Sep 2023).
|
||||
Experimental GOA exchange at <code>exchange.hacktivism.ch</code> — not a licensed bank.
|
||||
</p>
|
||||
|
||||
<h2>1. Controller</h2>
|
||||
<p>Operators of the hacktivism.ch GNU Taler stack. No separate DPO for this experimental service.</p>
|
||||
|
||||
<h2>2. Data retained</h2>
|
||||
<table>
|
||||
<thead><tr><th>Data</th><th>Examples</th><th>Typical retention</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>Reserves</td><td>Reserve public keys, remaining amount, times</td><td>Service life or wipe</td></tr>
|
||||
<tr><td>Wire-in</td><td>Amounts, timestamps, bank account reference from wire path</td><td>Service life</td></tr>
|
||||
<tr><td>Coins</td><td>Coin pubs, denominations, remaining value, deposit links</td><td>Service life</td></tr>
|
||||
<tr><td>Protocol operations</td><td>Withdraw, refresh, deposit, refund, recoup</td><td>Service life</td></tr>
|
||||
<tr><td>Wire-out</td><td>Merchant settlement amounts / targets</td><td>Service life</td></tr>
|
||||
<tr><td>Technical logs</td><td>IP, path, status</td><td>Days–weeks (rotation)</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p><strong>Design note:</strong> Coin spend is intended to be unlinkable to the customer
|
||||
<em>at the exchange</em>. Funding a reserve via the bank may identify the bank account
|
||||
holder on the <strong>bank</strong> system.</p>
|
||||
|
||||
<h2>3. Purposes</h2>
|
||||
<ul>
|
||||
<li>Operate the GOA GNU Taler exchange protocol</li>
|
||||
<li>Wire settlement with bank and merchants</li>
|
||||
<li>Security, debugging, capacity monitoring</li>
|
||||
</ul>
|
||||
|
||||
<h2>4. Your rights</h2>
|
||||
<p>Access, rectification, deletion, objection under the FADP. Complaint:
|
||||
Swiss <strong>FDPIC / EDÖB</strong>.</p>
|
||||
|
||||
<h2>Related</h2>
|
||||
<ul>
|
||||
<li><a href="https://exchange.hacktivism.ch/terms">Exchange terms</a></li>
|
||||
<li><a href="https://bank.hacktivism.ch/intro/privacy.html">Bank privacy</a></li>
|
||||
<li><a href="https://taler.hacktivism.ch/privacy">Merchant privacy</a></li>
|
||||
</ul>
|
||||
<footer class="muted">exchange-pp-swiss-v0 · Swiss FADP (revDSG)</footer>
|
||||
</body>
|
||||
</html>
|
||||
HTML
|
||||
|
||||
chmod -R a+rX "${DATA_HOME%/}/terms"
|
||||
if id taler-exchange-httpd >/dev/null 2>&1; then
|
||||
chown -R taler-exchange-httpd: "${DATA_HOME%/}/terms" 2>/dev/null || true
|
||||
fi
|
||||
echo "ok exchange privacy $ETAG -> $DIR"
|
||||
ls -la "$DIR"/${ETAG}.*
|
||||
300
scripts/taler-exchange/landing-stats-exchange.sh
Normal file
300
scripts/taler-exchange/landing-stats-exchange.sh
Normal file
|
|
@ -0,0 +1,300 @@
|
|||
#!/bin/bash
|
||||
# Run INSIDE taler-hacktivism-exchange-ansible.
|
||||
# Writes /var/www/exchange-landing/stats.json
|
||||
#
|
||||
# Data lives in Postgres DB taler-exchange, schema exchange.*
|
||||
# (reserves, reserves_in, known_coins, withdraw, denominations, …)
|
||||
#
|
||||
# IMPORTANT: never use psql -F$'\t' -v ON_ERROR_STOP=1
|
||||
# If the tab arg is lost, -F eats -v and ON_ERROR_STOP=1 becomes the
|
||||
# *username* → peer auth fails → silent empty counts (all zeros).
|
||||
# Never overwrite stats.json on failure — write stats-run.json instead.
|
||||
set -euo pipefail
|
||||
export TZ="${TZ:-Europe/Zurich}"
|
||||
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin${PATH:+:$PATH}"
|
||||
LANDING_DIR="${LANDING_DIR:-/var/www/exchange-landing}"
|
||||
OUT="$LANDING_DIR/stats.json"
|
||||
RUN="$LANDING_DIR/stats-run.json"
|
||||
TMP="${OUT}.tmp.$$"
|
||||
DB="${EXCHANGE_DB:-taler-exchange}"
|
||||
BASE_URL="${EXCHANGE_BASE_URL:-https://exchange.hacktivism.ch}"
|
||||
ERRLOG="${LANDING_STATS_ERRLOG:-/var/log/landing-stats-exchange.err}"
|
||||
mkdir -p "$LANDING_DIR"
|
||||
|
||||
now_iso() { date +%Y-%m-%dT%H:%M%z | sed -E 's/([+-][0-9]{2})([0-9]{2})$/\1:\2/'; }
|
||||
now_human() { date +"%Y-%m-%d %H:%M %Z"; }
|
||||
json_str() {
|
||||
printf '"%s"' "$(printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g' | tr '\n\r\t' ' ')"
|
||||
}
|
||||
|
||||
write_run() {
|
||||
local ok_json="$1" msg="${2:-}"
|
||||
cat >"$RUN" <<EOF
|
||||
{
|
||||
"ok": ${ok_json},
|
||||
"at": $(json_str "$(now_iso)"),
|
||||
"at_human": $(json_str "$(now_human)"),
|
||||
"error": $( [ -n "$msg" ] && json_str "$msg" || echo null )
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
abort() {
|
||||
write_run false "$*"
|
||||
printf '%s\n' "abort exchange-stats: $*" | tee -a "$ERRLOG" >&2
|
||||
rm -f "$TMP" 2>/dev/null || true
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Prefer runuser; fall back to su. Always absolute-ish via PATH.
|
||||
as_postgres() {
|
||||
if command -v runuser >/dev/null 2>&1; then
|
||||
runuser -u postgres -- "$@"
|
||||
elif command -v su >/dev/null 2>&1; then
|
||||
su -s /bin/bash postgres -c "$*"
|
||||
else
|
||||
return 127
|
||||
fi
|
||||
}
|
||||
|
||||
# Single value or one row with '|' separators (no -F, no -v flags).
|
||||
psqlq() {
|
||||
local sql="$1" out ec
|
||||
set +e
|
||||
out=$(as_postgres psql -d "$DB" -At -c "$sql" 2>>"$ERRLOG")
|
||||
ec=$?
|
||||
set -e
|
||||
if [ "$ec" -ne 0 ]; then
|
||||
abort "psql failed (ec=$ec): ${sql:0:120}"
|
||||
fi
|
||||
printf '%s' "$out"
|
||||
}
|
||||
|
||||
fmt_goa() {
|
||||
local v="${1:-0}" f="${2:-0}"
|
||||
v=${v//[^0-9-]/}; f=${f//[^0-9-]/}; v=${v:-0}; f=${f:-0}
|
||||
if [ "$f" = "0" ] || [ -z "$f" ]; then
|
||||
printf 'GOA:%s' "$v"
|
||||
return
|
||||
fi
|
||||
awk -v v="$v" -v f="$f" 'BEGIN{
|
||||
frac = sprintf("%08d", f+0); sub(/0+$/, "", frac)
|
||||
if (frac == "") printf "GOA:%d", v+0
|
||||
else printf "GOA:%d.%s", v+0, frac
|
||||
}'
|
||||
}
|
||||
|
||||
q_count() {
|
||||
local n
|
||||
n=$(psqlq "SELECT count(*)::text FROM $1;")
|
||||
# strip whitespace/newlines
|
||||
n=$(printf '%s' "$n" | tr -d '[:space:]')
|
||||
[[ "$n" =~ ^[0-9]+$ ]] || abort "bad count for $1: '$n'"
|
||||
printf '%s' "$n"
|
||||
}
|
||||
|
||||
# --- preflight ---
|
||||
command -v psql >/dev/null 2>&1 || abort "psql not in PATH ($PATH)"
|
||||
command -v runuser >/dev/null 2>&1 || command -v su >/dev/null 2>&1 || abort "neither runuser nor su in PATH"
|
||||
PROBE=$(psqlq "SELECT 1;")
|
||||
[ "$PROBE" = "1" ] || abort "postgres not reachable (SELECT 1 → '$PROBE')"
|
||||
|
||||
# --- coin lifecycle ---
|
||||
KNOWN_COINS=$(q_count "exchange.known_coins")
|
||||
COINS_LIVE=$(psqlq "SELECT count(*)::text FROM exchange.known_coins WHERE (remaining).val > 0 OR (remaining).frac > 0;")
|
||||
COINS_LIVE=$(printf '%s' "${COINS_LIVE:-0}" | tr -d '[:space:]')
|
||||
COINS_LIVE=${COINS_LIVE:-0}
|
||||
COINS_SPENT=$(( KNOWN_COINS - COINS_LIVE ))
|
||||
|
||||
REM_ROW=$(psqlq "SELECT coalesce(sum((remaining).val),0)::text || '|' || coalesce(sum((remaining).frac),0)::text FROM exchange.known_coins;")
|
||||
IFS='|' read -r REM_VAL REM_FRAC <<<"${REM_ROW:-0|0}"
|
||||
REMAINING_AMT=$(fmt_goa "${REM_VAL:-0}" "${REM_FRAC:-0}")
|
||||
|
||||
DENOMS_TOTAL=$(q_count "exchange.denominations")
|
||||
DENOM_VALUES=$(psqlq "SELECT count(DISTINCT ((coin).val, (coin).frac))::text FROM exchange.denominations;")
|
||||
DENOM_VALUES=$(printf '%s' "${DENOM_VALUES:-0}" | tr -d '[:space:]')
|
||||
|
||||
NOW_US=$(date +%s)000000
|
||||
DENOMS_WITHDRAWABLE=$(psqlq "SELECT count(*)::text FROM exchange.denominations WHERE valid_from <= ${NOW_US} AND expire_withdraw > ${NOW_US};")
|
||||
DENOMS_WITHDRAWABLE=$(printf '%s' "${DENOMS_WITHDRAWABLE:-0}" | tr -d '[:space:]')
|
||||
|
||||
RESERVES=$(q_count "exchange.reserves")
|
||||
RESERVES_IN=$(q_count "exchange.reserves_in")
|
||||
WIN_ROW=$(psqlq "SELECT coalesce(sum((credit).val),0)::text || '|' || coalesce(sum((credit).frac),0)::text FROM exchange.reserves_in;")
|
||||
IFS='|' read -r WIN_VAL WIN_FRAC <<<"${WIN_ROW:-0|0}"
|
||||
WIRE_IN_AMT=$(fmt_goa "${WIN_VAL:-0}" "${WIN_FRAC:-0}")
|
||||
|
||||
WITHDRAW_OPS=$(q_count "exchange.withdraw")
|
||||
WOUT_ROW=$(psqlq "SELECT coalesce(sum((amount_with_fee).val),0)::text || '|' || coalesce(sum((amount_with_fee).frac),0)::text FROM exchange.withdraw;")
|
||||
IFS='|' read -r WO_VAL WO_FRAC <<<"${WOUT_ROW:-0|0}"
|
||||
WITHDRAW_AMT=$(fmt_goa "${WO_VAL:-0}" "${WO_FRAC:-0}")
|
||||
|
||||
REFRESH_OPS=$(q_count "exchange.refresh")
|
||||
RECOUP=$(q_count "exchange.recoup")
|
||||
REFUNDS=$(q_count "exchange.refunds")
|
||||
COIN_DEPOSITS=$(q_count "exchange.coin_deposits")
|
||||
BATCH_DEPOSITS=$(q_count "exchange.batch_deposits")
|
||||
WIRE_OUT=$(q_count "exchange.wire_out")
|
||||
COIN_HISTORY=$(q_count "exchange.coin_history")
|
||||
WIRE_ACCTS=$(q_count "exchange.wire_accounts")
|
||||
|
||||
# known coins by denom value (pipe-separated)
|
||||
BY_DENOM_TSV=$(psqlq "
|
||||
SELECT (d.coin).val::text || '|' || (d.coin).frac::text || '|' || count(*)::text || '|' ||
|
||||
count(*) FILTER (WHERE (k.remaining).val > 0 OR (k.remaining).frac > 0)::text
|
||||
FROM exchange.known_coins k
|
||||
JOIN exchange.denominations d ON d.denominations_serial = k.denominations_serial
|
||||
GROUP BY (d.coin).val, (d.coin).frac
|
||||
ORDER BY (d.coin).val, (d.coin).frac;
|
||||
")
|
||||
|
||||
BY_DENOM_JSON="["
|
||||
bf=1
|
||||
while IFS='|' read -r dv df cnt live; do
|
||||
[ -z "${dv:-}" ] && continue
|
||||
amt=$(fmt_goa "$dv" "$df")
|
||||
if [ "$bf" = 1 ]; then bf=0; else BY_DENOM_JSON="${BY_DENOM_JSON},"; fi
|
||||
BY_DENOM_JSON="${BY_DENOM_JSON}
|
||||
{\"value\": $(json_str "$amt"), \"coins\": ${cnt:-0}, \"live\": ${live:-0}}"
|
||||
done <<<"$BY_DENOM_TSV"
|
||||
BY_DENOM_JSON="${BY_DENOM_JSON}
|
||||
]"
|
||||
|
||||
LADDER_TSV=$(psqlq "
|
||||
SELECT (coin).val::text || '|' || (coin).frac::text || '|' || count(*)::text
|
||||
FROM exchange.denominations
|
||||
GROUP BY (coin).val, (coin).frac
|
||||
ORDER BY (coin).val, (coin).frac;
|
||||
")
|
||||
LADDER_JSON="["
|
||||
lf=1
|
||||
while IFS='|' read -r dv df nkeys; do
|
||||
[ -z "${dv:-}" ] && continue
|
||||
amt=$(fmt_goa "$dv" "$df")
|
||||
if [ "$lf" = 1 ]; then lf=0; else LADDER_JSON="${LADDER_JSON},"; fi
|
||||
LADDER_JSON="${LADDER_JSON}
|
||||
{\"value\": $(json_str "$amt"), \"keys\": ${nkeys:-0}}"
|
||||
done <<<"$LADDER_TSV"
|
||||
LADDER_JSON="${LADDER_JSON}
|
||||
]"
|
||||
|
||||
# recent wire-in / withdraw activity (no personal names — reserve_pub hex truncated)
|
||||
RECENT_JSON="["
|
||||
rf=1
|
||||
while IFS='|' read -r ts val frac; do
|
||||
[ -z "${ts:-}" ] && continue
|
||||
sec=$(awk -v t="$ts" 'BEGIN{printf "%d", int(t/1000000)}')
|
||||
human=$(date -d "@${sec}" +"%Y-%m-%d %H:%M %Z" 2>/dev/null || echo "$sec")
|
||||
amt=$(fmt_goa "${val:-0}" "${frac:-0}")
|
||||
if [ "$rf" = 1 ]; then rf=0; else RECENT_JSON="${RECENT_JSON},"; fi
|
||||
RECENT_JSON="${RECENT_JSON}
|
||||
{\"kind\": \"wire_in\", \"amount\": $(json_str "$amt"), \"ts_human\": $(json_str "$human"), \"ts_us\": ${ts:-0}}"
|
||||
done < <(psqlq "
|
||||
SELECT execution_date::text || '|' || (credit).val::text || '|' || (credit).frac::text
|
||||
FROM exchange.reserves_in
|
||||
ORDER BY execution_date DESC
|
||||
LIMIT 8;
|
||||
")
|
||||
while IFS='|' read -r ts val frac; do
|
||||
[ -z "${ts:-}" ] && continue
|
||||
sec=$(awk -v t="$ts" 'BEGIN{printf "%d", int(t/1000000)}')
|
||||
human=$(date -d "@${sec}" +"%Y-%m-%d %H:%M %Z" 2>/dev/null || echo "$sec")
|
||||
amt=$(fmt_goa "${val:-0}" "${frac:-0}")
|
||||
if [ "$rf" = 1 ]; then rf=0; else RECENT_JSON="${RECENT_JSON},"; fi
|
||||
RECENT_JSON="${RECENT_JSON}
|
||||
{\"kind\": \"withdraw\", \"amount\": $(json_str "$amt"), \"ts_human\": $(json_str "$human"), \"ts_us\": ${ts:-0}}"
|
||||
done < <(psqlq "
|
||||
SELECT execution_date::text || '|' || (amount_with_fee).val::text || '|' || (amount_with_fee).frac::text
|
||||
FROM exchange.withdraw
|
||||
ORDER BY execution_date DESC
|
||||
LIMIT 6;
|
||||
")
|
||||
RECENT_JSON="${RECENT_JSON}
|
||||
]"
|
||||
|
||||
# live performance
|
||||
measure_ms() {
|
||||
local url="$1" t
|
||||
t=$(curl -sS -o /dev/null -m 8 -w '%{time_total}' "$url" 2>/dev/null || echo "")
|
||||
[ -z "$t" ] && { echo "null"; return; }
|
||||
awk -v t="$t" 'BEGIN{printf "%d", (t+0)*1000}'
|
||||
}
|
||||
KEYS_MS=$(measure_ms "${BASE_URL}/keys")
|
||||
CONFIG_MS=$(measure_ms "${BASE_URL}/config")
|
||||
KEYS_HTTP=$(curl -sS -o /dev/null -m 8 -w '%{http_code}' "${BASE_URL}/keys" 2>/dev/null || echo "000")
|
||||
CONFIG_HTTP=$(curl -sS -o /dev/null -m 8 -w '%{http_code}' "${BASE_URL}/config" 2>/dev/null || echo "000")
|
||||
LOADAVG=""
|
||||
[ -r /proc/loadavg ] && LOADAVG=$(awk '{print $1","$2","$3}' /proc/loadavg)
|
||||
|
||||
WW="false"
|
||||
pgrep -f taler-exchange-wirewatch >/dev/null 2>&1 && WW="true"
|
||||
|
||||
STAT_EVENTS=$(q_count "exchange.exchange_statistic_counter_event")
|
||||
|
||||
# Fail closed: zero denoms usually means broken query path (table always has keys)
|
||||
if [ "${DENOMS_TOTAL:-0}" = "0" ]; then
|
||||
abort "denominations count is 0 — refusing to publish (check peer auth / PATH)"
|
||||
fi
|
||||
|
||||
GEN=$(now_iso)
|
||||
HUMAN=$(now_human)
|
||||
num_or_null() { case "${1:-}" in ''|null) echo null ;; *) echo "$1" ;; esac; }
|
||||
|
||||
MEM_JSON='"container_rss_human": "—"'
|
||||
MEM_HELPER="${MEM_HELPER:-/usr/local/lib/landing-mem-snapshot.sh}"
|
||||
if [ -f "$MEM_HELPER" ]; then
|
||||
# shellcheck disable=SC1090
|
||||
. "$MEM_HELPER"
|
||||
mem_snapshot_json || true
|
||||
fi
|
||||
|
||||
cat >"$TMP" <<EOF
|
||||
{
|
||||
"ok": true,
|
||||
"source": "exchange-db",
|
||||
"schema": "exchange.* @ taler-exchange",
|
||||
"focus": "coins",
|
||||
"timezone": $(json_str "$TZ"),
|
||||
"generated_at": $(json_str "$GEN"),
|
||||
"generated_at_human": $(json_str "$HUMAN"),
|
||||
"reserves": ${RESERVES:-0},
|
||||
"wire_in_count": ${RESERVES_IN:-0},
|
||||
"wire_in_amount": $(json_str "$WIRE_IN_AMT"),
|
||||
"wire_out": ${WIRE_OUT:-0},
|
||||
"wire_accounts": ${WIRE_ACCTS:-0},
|
||||
"wirewatch_running": $WW,
|
||||
"known_coins": ${KNOWN_COINS:-0},
|
||||
"coins_live": ${COINS_LIVE:-0},
|
||||
"coins_spent": ${COINS_SPENT:-0},
|
||||
"coins_remaining_amount": $(json_str "$REMAINING_AMT"),
|
||||
"withdraw_ops": ${WITHDRAW_OPS:-0},
|
||||
"withdraw_amount": $(json_str "$WITHDRAW_AMT"),
|
||||
"refresh_ops": ${REFRESH_OPS:-0},
|
||||
"recoup": ${RECOUP:-0},
|
||||
"refunds": ${REFUNDS:-0},
|
||||
"coin_deposits": ${COIN_DEPOSITS:-0},
|
||||
"batch_deposits": ${BATCH_DEPOSITS:-0},
|
||||
"coin_history_events": ${COIN_HISTORY:-0},
|
||||
"denominations": ${DENOMS_TOTAL:-0},
|
||||
"denom_values": ${DENOM_VALUES:-0},
|
||||
"denoms_withdrawable": ${DENOMS_WITHDRAWABLE:-0},
|
||||
"by_denom": $BY_DENOM_JSON,
|
||||
"denom_ladder": $LADDER_JSON,
|
||||
"recent_activity": $RECENT_JSON,
|
||||
"performance": {
|
||||
"keys_http": $(json_str "$KEYS_HTTP"),
|
||||
"keys_ms": $(num_or_null "$KEYS_MS"),
|
||||
"config_http": $(json_str "$CONFIG_HTTP"),
|
||||
"config_ms": $(num_or_null "$CONFIG_MS"),
|
||||
"loadavg": $(json_str "${LOADAVG:-}"),
|
||||
"statistic_counter_events": ${STAT_EVENTS:-0},
|
||||
"memory": {
|
||||
${MEM_JSON}
|
||||
}
|
||||
}
|
||||
}
|
||||
EOF
|
||||
grep -q '"ok": true' "$TMP" || abort "tmp json missing ok:true"
|
||||
mv -f "$TMP" "$OUT"
|
||||
write_run true
|
||||
echo "ok exchange reserves=$RESERVES wire_in=$WIRE_IN_AMT coins=$KNOWN_COINS withdraw=$WITHDRAW_AMT denoms=$DENOMS_TOTAL keys_ms=$KEYS_MS -> $OUT"
|
||||
62
scripts/taler-exchange/offline-sign-upload-keys.sh
Executable file
62
scripts/taler-exchange/offline-sign-upload-keys.sh
Executable 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-hacktivism-exchange-ansible.
|
||||
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
|
||||
148
scripts/taler-exchange/start_base_services_for_taler_exchange.sh
Executable file
148
scripts/taler-exchange/start_base_services_for_taler_exchange.sh
Executable 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
|
||||
104
scripts/taler-exchange/start_exchange.sh
Executable file
104
scripts/taler-exchange/start_exchange.sh
Executable 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
|
||||
43
scripts/taler-exchange/start_wire_helpers.sh
Executable file
43
scripts/taler-exchange/start_wire_helpers.sh
Executable 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
|
||||
91
scripts/taler-exchange/wire-enable-and-upload.sh
Executable file
91
scripts/taler-exchange/wire-enable-and-upload.sh
Executable 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-hacktivism-exchange-ansible.
|
||||
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
|
||||
Loading…
Add table
Add a link
Reference in a new issue