merchant: start/health + certbot_renew (container nginx TLS :8081, ACME :8082)
This commit is contained in:
parent
ad85a60c62
commit
4823c8f602
13 changed files with 795 additions and 0 deletions
63
configs/taler-merchant/README.md
Normal file
63
configs/taler-merchant/README.md
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
# taler-merchant (container `taler-hacktivism`)
|
||||
|
||||
Image: `localhost/taler-hacktivism-live:9010`
|
||||
Publish: **9010/tcp only**
|
||||
Labels: `org.hacktivism.service=taler-merchant`, `host_port=9010`, `site=taler.hacktivism.ch`
|
||||
|
||||
## Config layout (same pattern as exchange)
|
||||
|
||||
| Path in container | Mirror in this dir |
|
||||
|-------------------|--------------------|
|
||||
| `taler-merchant.conf` | includes documented in `taler-merchant.conf.includes.txt` |
|
||||
| `overrides.conf` | tooling — do not edit (not mirrored) |
|
||||
| **`merchant-overrides.conf`** | **`merchant-overrides.conf`** |
|
||||
| `conf.d/merchant.conf` | `conf.d-merchant.conf` |
|
||||
| nginx `sites-available/taler-merchant` | `nginx-taler-merchant.conf` |
|
||||
|
||||
Secrets (`secrets/*.secret.conf`, SMTP password) are **not** mirrored.
|
||||
|
||||
## Manual start (merchant model)
|
||||
|
||||
| Path | User | Role |
|
||||
|------|------|------|
|
||||
| `/root/start_base_services_for_taler.sh` | **root** | logs, SMS env, postgres → shell as `taler-merchant-httpd` |
|
||||
| `/usr/local/bin/start_merchant.sh` | **taler-merchant-httpd** | start / `--restart` httpd + helpers |
|
||||
| `/usr/local/bin/check_merchant-health.sh` | any | health (socket + nginx) |
|
||||
|
||||
Also under `/usr/local/bin/`: email/SMS helpers, `stats--merchant-payments.sh`.
|
||||
Certbot loop: `/root/scripts/certbot_renew.sh` (started from base script).
|
||||
|
||||
Git scripts: `scripts/taler-merchant/`.
|
||||
|
||||
## Site settings (overrides)
|
||||
|
||||
- **Default currency:** CHF (`[taler]` / `[merchant]`)
|
||||
- **Exchanges (multi-currency):**
|
||||
- CHF → `exchange.taler-ops.ch` (package default `[merchant-exchange-chf]` in `tops.conf` — do not redeclare as `merchant-exchange-tops`)
|
||||
- GOA → `exchange.hacktivism.ch` (`[merchant-exchange-goa]` in `merchant-overrides.conf`)
|
||||
- **SERVE:** unix socket → nginx :9010 SSL → Caddy :9001
|
||||
- **Self-provisioning:** YES; **no mandatory TAN** (SMS off)
|
||||
- **Terms:** `TERMS_ETAG = merchant-tos-dual-v0` — short dual-currency notice (GOA explorational + CHF real). Sources in `terms-src/`; install with `scripts/taler-merchant/install_dual_terms.sh` inside the container, then restart `taler-merchant-httpd`. Public: `https://taler.hacktivism.ch/terms`
|
||||
|
||||
Wire status is **per account × exchange**: an `x-taler-bank` GOA payto shows `ready` for hacktivism and `unsupported` for taler-ops (expected). CHF/IBAN accounts use taler-ops.
|
||||
|
||||
### Container → public exchange (pasta)
|
||||
|
||||
Inside pasta, `exchange.hacktivism.ch` must not resolve to **127.0.0.1** (merchant nginx self-signed). Pin public A in container `/etc/hosts`:
|
||||
|
||||
```
|
||||
212.51.151.254 exchange.hacktivism.ch bank.hacktivism.ch taler.hacktivism.ch
|
||||
```
|
||||
|
||||
After conf/hosts changes: `taler-merchant-exchangekeyupdate -t` and `taler-merchant-kyccheck -t`.## Ports
|
||||
|
||||
| Port | Role |
|
||||
|------|------|
|
||||
| 9010 | nginx TLS in container (podman) |
|
||||
| unix sock | `taler-merchant-httpd` |
|
||||
|
||||
## Demo instance (2026-07-09)
|
||||
|
||||
Self-provisioned shop **`goa-demo-cp4zqk`**, bank account same username, payto linked, seed **GOA:5000**.
|
||||
|
||||
Details + credential paths: **`demo-instance-goa-demo-cp4zqk.md`**.
|
||||
7
configs/taler-merchant/conf.d-merchant.conf
Normal file
7
configs/taler-merchant/conf.d-merchant.conf
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# Package drop-in: /etc/taler-merchant/conf.d/merchant.conf
|
||||
# Read secret sections into configuration, but only
|
||||
# if we have permission to do so.
|
||||
@inline-secret@ merchantdb-postgres ../secrets/merchant-db.secret.conf
|
||||
|
||||
[merchant]
|
||||
SERVE = systemd
|
||||
71
configs/taler-merchant/merchant-overrides.conf
Normal file
71
configs/taler-merchant/merchant-overrides.conf
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
# Site overrides for taler.hacktivism.ch (container taler-hacktivism).
|
||||
# Multi-currency: CHF (taler-ops) + GOA (local exchange).
|
||||
# Do not edit overrides.conf (tooling).
|
||||
|
||||
[taler]
|
||||
CURRENCY = CHF
|
||||
|
||||
### Disable package demonstrator KUDOS only
|
||||
[merchant-exchange-kudos]
|
||||
DISABLED = YES
|
||||
|
||||
[currency-KUDOS]
|
||||
ENABLED = NO
|
||||
|
||||
[merchant]
|
||||
CURRENCY = CHF
|
||||
DATABASE = postgres
|
||||
# Dual-currency short notice (GOA explorational + CHF real).
|
||||
# Install: scripts/taler-merchant/install_dual_terms.sh (inside container).
|
||||
# Bump etag when text changes so wallets/browsers drop long-lived /terms cache.
|
||||
TERMS_ETAG = merchant-tos-dual-v2
|
||||
TERMS_DIR = ${TALER_DATA_HOME}terms/
|
||||
# Swiss FADP privacy (install: scripts/taler-merchant/install_swiss_privacy.sh)
|
||||
PRIVACY_ETAG = merchant-pp-swiss-v0
|
||||
PRIVACY_DIR = ${TALER_DATA_HOME}terms/
|
||||
|
||||
UNIXPATH = /var/run/taler-merchant/httpd/merchant-http.sock
|
||||
UNIXPATH_MODE = 660
|
||||
SERVE = unix
|
||||
|
||||
DEFAULT_PERSONA = point-of-sale
|
||||
|
||||
# Demo short deadlines (package: pay 1d, refund 15d, wire 1w).
|
||||
# Wire happens after refund deadline; refund/pay windows also kept short.
|
||||
DEFAULT_PAY_DELAY = 1 minute
|
||||
DEFAULT_REFUND_DELAY = 30 s
|
||||
DEFAULT_WIRE_TRANSFER_DELAY = 30 s
|
||||
|
||||
ENABLE_SELF_PROVISIONING = YES
|
||||
# No mandatory TAN (SMS auth off)
|
||||
MANDATORY_TAN_CHANNELS =
|
||||
HELPER_EMAIL = /usr/local/bin/taler-hacktivism-email-helper.sh
|
||||
# HELPER_SMS = /usr/local/bin/taler-hacktivism-sms-helper-wrapper.sh
|
||||
|
||||
[merchantdb-postgres]
|
||||
CONFIG = postgres:///taler-merchant
|
||||
|
||||
### CHF — package default in /usr/share/taler-merchant/config.d/tops.conf
|
||||
### section [merchant-exchange-chf] → https://exchange.taler-ops.ch/
|
||||
### Do NOT redeclare the same EXCHANGE_BASE_URL under another section name
|
||||
### (exchangekeyupdate: "configured in multiple sections").
|
||||
### Ensure package CHF stays enabled (only disable KUDOS above).
|
||||
|
||||
### GOA — local exchange.hacktivism.ch (x-taler-bank / bank.hacktivism.ch)
|
||||
[merchant-exchange-goa]
|
||||
EXCHANGE_BASE_URL = https://exchange.hacktivism.ch/
|
||||
MASTER_KEY = TW6K5FXF81VYCAH0YWYX0SX98KBBSJ42VX27WAX01FTFH400QG10
|
||||
CURRENCY = GOA
|
||||
|
||||
[currency-goa]
|
||||
ENABLED = YES
|
||||
name = "GOA exploration currency"
|
||||
code = GOA
|
||||
fractional_input_digits = 8
|
||||
fractional_normal_digits = 0
|
||||
fractional_trailing_zero_digits = 0
|
||||
# Keep in sync with exchange-overrides [currency-goa] (wallet compares unit maps).
|
||||
# Scale keys only in [-8, 24] (exchange rejects outside that range).
|
||||
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"
|
||||
50
configs/taler-merchant/nginx-taler-merchant.conf
Normal file
50
configs/taler-merchant/nginx-taler-merchant.conf
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
# Container: /etc/nginx/sites-available/taler-merchant
|
||||
# Enabled: sites-enabled/taler-merchant → this file
|
||||
# Host publish: 9010/tcp (podman pasta). Caddy terminates public HTTPS and
|
||||
# reverse_proxies to https://127.0.0.1:9010 (see configs/caddy/Caddyfile).
|
||||
|
||||
server {
|
||||
listen 9010 ssl;
|
||||
listen [::]:9010 ssl;
|
||||
|
||||
server_name taler.hacktivism.ch;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/taler.hacktivism.ch/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/taler.hacktivism.ch/privkey.pem;
|
||||
|
||||
access_log /var/log/nginx/merchant.log;
|
||||
error_log /var/log/nginx/merchant.err;
|
||||
|
||||
# Public landing first (Caddy should also redir / → /intro/; this covers :9010)
|
||||
location = / {
|
||||
return 302 /intro/;
|
||||
}
|
||||
|
||||
# Merchant-httpd only serves /terms and /privacy without trailing slash
|
||||
location = /terms/ {
|
||||
return 302 /terms;
|
||||
}
|
||||
location = /privacy/ {
|
||||
return 302 /privacy;
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_pass http://unix:/var/run/taler-merchant/httpd/merchant-http.sock;
|
||||
proxy_redirect off;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-Host "taler.hacktivism.ch";
|
||||
proxy_set_header X-Forwarded-Proto "https";
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
# ACME HTTP-01 is handled by Caddy :9000; this block kept for reference.
|
||||
# listen 9010;
|
||||
# listen [::]:9010;
|
||||
|
||||
server_name taler.hacktivism.ch;
|
||||
|
||||
location /.well-known/acme-challenge/ {
|
||||
alias /var/www/letsencrypt/.well-known/acme-challenge/;
|
||||
}
|
||||
}
|
||||
5
configs/taler-merchant/taler-merchant.conf.includes.txt
Normal file
5
configs/taler-merchant/taler-merchant.conf.includes.txt
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# From /etc/taler-merchant/taler-merchant.conf (include order)
|
||||
@inline-matching@ conf.d/*.conf
|
||||
@inline@ overrides.conf
|
||||
# Site overrides last (glob; file is merchant-overrides.conf)
|
||||
@inline-matching@ merchant-overrides.conf
|
||||
56
scripts/taler-merchant/README.md
Normal file
56
scripts/taler-merchant/README.md
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
# taler-merchant scripts
|
||||
|
||||
Mirrored from podman `taler-hacktivism`.
|
||||
|
||||
| File | Location in container | User |
|
||||
|------|----------------------|------|
|
||||
| `start_base_services_for_taler.sh` | `/root/` | root |
|
||||
| `start_merchant.sh` | `/usr/local/bin/` | `taler-merchant-httpd` |
|
||||
| `ensure_merchant_helpers.sh` | `/usr/local/bin/` | root → `taler-merchant-httpd` |
|
||||
| `setup_credit_facade.sh` | host root (`/root/koopa-admin-log/…`) | root on koopa |
|
||||
| `check_merchant-health.sh` | `/usr/local/bin/` | any |
|
||||
| `certbot_renew.sh` | `/root/scripts/` | root (bg from base) |
|
||||
| `stats--merchant-payments.sh` | `/usr/local/bin/` | ops |
|
||||
| `taler-hacktivism-email-helper.sh` | `/usr/local/bin/` | merchant (SMTP password via env in git mirror) |
|
||||
| `taler-hacktivism-sms-helper-wrapper.sh` | `/usr/local/bin/` | merchant |
|
||||
|
||||
### Settlement (wired / transfers)
|
||||
|
||||
Automatic import needs:
|
||||
|
||||
1. **`credit_facade_url`** = `…/accounts/$BANK_USER/taler-revenue/` (not `taler-wire-gateway/`)
|
||||
2. **Bearer** bank token in `credit_facade_credentials` (Basic fails on history)
|
||||
3. Running **`taler-merchant-wirewatch`** + **`taler-merchant-depositcheck`**
|
||||
|
||||
```bash
|
||||
# on koopa as root
|
||||
./setup_credit_facade.sh
|
||||
./ensure_merchant_helpers.sh # inside container or via start_merchant
|
||||
```
|
||||
|
||||
SMS backends are symlinks into `/var/taler-src/...` (not copied).
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
# root in container
|
||||
./start_base_services_for_taler.sh
|
||||
# then as taler-merchant-httpd in /usr/local/bin:
|
||||
./start_merchant.sh --restart
|
||||
# nginx (TLS frontend) if not already up — root:
|
||||
# /etc/init.d/nginx start
|
||||
./check_merchant-health.sh
|
||||
```
|
||||
|
||||
### `check_merchant-health.sh`
|
||||
|
||||
| Check | Severity |
|
||||
|-------|----------|
|
||||
| socket + listener + httpd + nginx | FAIL |
|
||||
| merchant `/config` (unix sock or `:9010`) | FAIL |
|
||||
| each enabled `[merchant-exchange-*]`: `GET …/keys` | FAIL |
|
||||
| `MASTER_KEY` matches `master_public_key` in `/keys` | FAIL |
|
||||
| helpers (webhook, exchangekeyupdate, depositcheck) | WARN |
|
||||
|
||||
Disabled exchanges (`DISABLED = YES`) are skipped.
|
||||
If public exchange URL is unreachable from the container, falls back to `http://127.0.0.1:9011/keys` (and pasta host IPs).
|
||||
5
scripts/taler-merchant/certbot_renew.sh
Executable file
5
scripts/taler-merchant/certbot_renew.sh
Executable file
|
|
@ -0,0 +1,5 @@
|
|||
#!/bin/sh
|
||||
while true; do
|
||||
certbot renew --quiet
|
||||
sleep 12h
|
||||
done
|
||||
222
scripts/taler-merchant/check_merchant-health.sh
Executable file
222
scripts/taler-merchant/check_merchant-health.sh
Executable file
|
|
@ -0,0 +1,222 @@
|
|||
#!/bin/bash
|
||||
# Health check for manual taler-merchant (container taler-hacktivism).
|
||||
# Style: check_exchange-health.sh — [OK] / [FAIL] / [WARN], exit 1 on critical fail.
|
||||
#
|
||||
# Keys checks: for each enabled [merchant-exchange-*] with EXCHANGE_BASE_URL,
|
||||
# GET …/keys and (if set) verify MASTER_KEY appears in the response.
|
||||
|
||||
CONF="${TALER_MERCHANT_CONFIG:-/etc/taler-merchant/taler-merchant.conf}"
|
||||
OVERRIDES=/etc/taler-merchant/merchant-overrides.conf
|
||||
SOCK="/var/run/taler-merchant/httpd/merchant-http.sock"
|
||||
SS_BIN=$(command -v ss)
|
||||
CURL_BIN=$(command -v curl)
|
||||
|
||||
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"; }
|
||||
|
||||
is_url() {
|
||||
case "$1" in
|
||||
http://*|https://*) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# GET /keys; try primary URL then optional host-local fallbacks for same exchange.
|
||||
# Writes body to $1 (path). Returns 0 on HTTP success with non-empty body.
|
||||
fetch_keys() {
|
||||
local out="$1"
|
||||
local primary="$2"
|
||||
shift 2
|
||||
local url
|
||||
for url in "$primary" "$@"; do
|
||||
[ -z "$url" ] && continue
|
||||
if $CURL_BIN -skf -m 6 "$url" -o "$out" 2>/dev/null \
|
||||
|| $CURL_BIN -sf -m 6 "$url" -o "$out" 2>/dev/null; then
|
||||
if [ -s "$out" ] && grep -qE 'master_public_key|"currency"' "$out" 2>/dev/null; then
|
||||
echo "$url"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
echo "=== Taler Merchant Health Check ==="
|
||||
|
||||
# --- 1–4: local service ---
|
||||
if [ -S "$SOCK" ]; then
|
||||
ok "socket exists: $SOCK"
|
||||
else
|
||||
bad "socket does NOT exist: $SOCK"
|
||||
fi
|
||||
|
||||
if [ -n "$SS_BIN" ] && $SS_BIN -xl 2>/dev/null | grep -q "$SOCK"; then
|
||||
ok "Merchant-HTTPD listening on socket"
|
||||
elif [ -n "$SS_BIN" ]; then
|
||||
bad "no listener on socket"
|
||||
else
|
||||
warn "ss not available — skip socket listener check"
|
||||
fi
|
||||
|
||||
if pgrep -f taler-merchant-httpd >/dev/null 2>&1; then
|
||||
ok "process taler-merchant-httpd"
|
||||
else
|
||||
bad "process taler-merchant-httpd is NOT running"
|
||||
fi
|
||||
|
||||
if pgrep -f "nginx: master" >/dev/null 2>&1; then
|
||||
ok "nginx master process"
|
||||
else
|
||||
bad "nginx master process is NOT running"
|
||||
fi
|
||||
|
||||
# --- 5: merchant /config ---
|
||||
if [ -n "$CURL_BIN" ]; then
|
||||
cfg_ok=0
|
||||
if [ -S "$SOCK" ] && $CURL_BIN -sf -m 3 --unix-socket "$SOCK" "http://localhost/config" >/dev/null 2>&1; then
|
||||
ok "merchant /config via unix socket"
|
||||
cfg_ok=1
|
||||
elif $CURL_BIN -skf -m 3 "https://127.0.0.1:9010/config" >/dev/null 2>&1; then
|
||||
ok "merchant /config via https://127.0.0.1:9010/config"
|
||||
cfg_ok=1
|
||||
elif $CURL_BIN -sf -m 3 "http://127.0.0.1:9010/config" >/dev/null 2>&1; then
|
||||
ok "merchant /config via http://127.0.0.1:9010/config"
|
||||
cfg_ok=1
|
||||
fi
|
||||
[ "$cfg_ok" -eq 0 ] && bad "merchant /config unreachable (socket and :9010)"
|
||||
else
|
||||
warn "curl missing — skip /config"
|
||||
fi
|
||||
|
||||
# --- 6: exchange /keys for configured exchanges ---
|
||||
echo "--- configured exchanges (/keys) ---"
|
||||
|
||||
# Emit lines: SECTION|DISABLED|BASE|MASTER (from overrides + optional taler-config)
|
||||
list_exchanges() {
|
||||
# Prefer site overrides file (authoritative for this host)
|
||||
if [ -f "$OVERRIDES" ]; then
|
||||
awk '
|
||||
BEGIN { sec=""; dis="NO"; base=""; master="" }
|
||||
/^\[merchant-exchange-/ {
|
||||
if (sec != "") printf "%s|%s|%s|%s\n", sec, dis, base, master
|
||||
sec=$0; gsub(/[\[\] \t\r]/, "", sec)
|
||||
dis="NO"; base=""; master=""
|
||||
next
|
||||
}
|
||||
/^\[/ {
|
||||
if (sec != "") printf "%s|%s|%s|%s\n", sec, dis, base, master
|
||||
sec=""; next
|
||||
}
|
||||
sec=="" { next }
|
||||
/^[ \t]*#/ { next }
|
||||
{
|
||||
line=$0
|
||||
sub(/[ \t]*#.*$/, "", line)
|
||||
if (match(line, /^[ \t]*DISABLED[ \t]*=[ \t]*/)) {
|
||||
dis=substr(line, RSTART+RLENGTH); gsub(/^[ \t]+|[ \t]+$/, "", dis)
|
||||
} else if (match(line, /^[ \t]*EXCHANGE_BASE_URL[ \t]*=[ \t]*/)) {
|
||||
base=substr(line, RSTART+RLENGTH); gsub(/^[ \t]+|[ \t]+$/, "", base)
|
||||
} else if (match(line, /^[ \t]*MASTER_KEY[ \t]*=[ \t]*/)) {
|
||||
master=substr(line, RSTART+RLENGTH); gsub(/^[ \t]+|[ \t]+$/, "", master)
|
||||
}
|
||||
}
|
||||
END { if (sec != "") printf "%s|%s|%s|%s\n", sec, dis, base, master }
|
||||
' "$OVERRIDES"
|
||||
fi
|
||||
}
|
||||
|
||||
if [ -z "$CURL_BIN" ]; then
|
||||
bad "curl missing — cannot check exchange /keys"
|
||||
else
|
||||
exch_count=0
|
||||
while IFS='|' read -r sec dis base master; do
|
||||
[ -z "$sec" ] && continue
|
||||
case "${dis:-NO}" in
|
||||
YES|yes|true|True|1)
|
||||
warn "exchange $sec: DISABLED — skip /keys"
|
||||
continue
|
||||
;;
|
||||
esac
|
||||
if ! is_url "$base"; then
|
||||
# package stubs without URL (e.g. kudos only DISABLED)
|
||||
warn "exchange $sec: no EXCHANGE_BASE_URL — skip"
|
||||
continue
|
||||
fi
|
||||
exch_count=$((exch_count + 1))
|
||||
base_slash="${base%/}/"
|
||||
primary="${base_slash}keys"
|
||||
keys_tmp=$(mktemp 2>/dev/null || echo "/tmp/m-keys-$$-${exch_count}.json")
|
||||
|
||||
# Fallbacks when public name is not reachable from container:
|
||||
# host loopback ports published by podman (exchange :9011).
|
||||
got_url=
|
||||
if got_url=$(fetch_keys "$keys_tmp" "$primary" \
|
||||
"http://127.0.0.1:9011/keys" \
|
||||
"http://host.containers.internal:9011/keys" \
|
||||
"http://10.0.2.2:9011/keys"); then
|
||||
ok "exchange $sec: /keys reachable ($got_url)"
|
||||
if [ -n "$master" ]; then
|
||||
if grep -qF "$master" "$keys_tmp" 2>/dev/null; then
|
||||
ok "exchange $sec: MASTER_KEY matches /keys"
|
||||
else
|
||||
mpks=$(grep -oE '"master_public_key"[[:space:]]*:[[:space:]]*"[^"]+"' "$keys_tmp" 2>/dev/null | head -2)
|
||||
bad "exchange $sec: MASTER_KEY does not match /keys (config MASTER_KEY=$master)"
|
||||
[ -n "$mpks" ] && warn " seen in /keys: $mpks"
|
||||
fi
|
||||
else
|
||||
warn "exchange $sec: no MASTER_KEY in config — skip key match"
|
||||
fi
|
||||
else
|
||||
bad "exchange $sec: no /keys from $primary (and local :9011 fallbacks)"
|
||||
fi
|
||||
rm -f "$keys_tmp" 2>/dev/null || true
|
||||
done <<EOF
|
||||
$(list_exchanges)
|
||||
EOF
|
||||
[ "$exch_count" -eq 0 ] && warn "no enabled exchanges with EXCHANGE_BASE_URL"
|
||||
fi
|
||||
|
||||
# --- 7: helpers — ensure (no systemd) then require ---
|
||||
if [ "${SKIP_ENSURE:-0}" != "1" ]; then
|
||||
if [ -x /usr/local/bin/ensure_merchant_helpers.sh ]; then
|
||||
echo "--- ensure_merchant_helpers ---"
|
||||
/usr/local/bin/ensure_merchant_helpers.sh || warn "ensure_merchant_helpers exited non-zero"
|
||||
elif [ -x "$(dirname "$0")/ensure_merchant_helpers.sh" ]; then
|
||||
echo "--- ensure_merchant_helpers ---"
|
||||
"$(dirname "$0")/ensure_merchant_helpers.sh" || warn "ensure_merchant_helpers exited non-zero"
|
||||
fi
|
||||
fi
|
||||
|
||||
live_helper() {
|
||||
local p="$1"
|
||||
pgrep -f "(^|/)(${p})( |$)" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# Settlement needs wirewatch + depositcheck; others needed for ops.
|
||||
for p in \
|
||||
taler-merchant-webhook \
|
||||
taler-merchant-kyccheck \
|
||||
taler-merchant-wirewatch \
|
||||
taler-merchant-depositcheck \
|
||||
taler-merchant-exchangekeyupdate \
|
||||
taler-merchant-reconciliation
|
||||
do
|
||||
if live_helper "$p"; then
|
||||
ok "process $p"
|
||||
else
|
||||
bad "process $p not running"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$fail" -eq 0 ]; then
|
||||
green "=== ALL CRITICAL CHECKS PASSED ==="
|
||||
exit 0
|
||||
fi
|
||||
red "=== SOME CHECKS FAILED ==="
|
||||
exit 1
|
||||
133
scripts/taler-merchant/start_base_services_for_taler.sh
Executable file
133
scripts/taler-merchant/start_base_services_for_taler.sh
Executable file
|
|
@ -0,0 +1,133 @@
|
|||
#!/bin/bash
|
||||
# Root: base services for manual merchant (no systemd).
|
||||
# Pattern (all three stacks): root base → shell as service user → start_*.sh
|
||||
#
|
||||
# Container: taler-hacktivism
|
||||
# Then as taler-merchant-httpd: /usr/local/bin/start_merchant.sh [--restart]
|
||||
#
|
||||
# Usage:
|
||||
# /root/start_base_services_for_taler.sh # interactive shell as service user
|
||||
# /root/start_base_services_for_taler.sh --no-shell # base only (automation)
|
||||
|
||||
set -e
|
||||
|
||||
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
|
||||
|
||||
PWD_BIN="/usr/local/bin"
|
||||
MERCHANT_STARTER="start_merchant.sh"
|
||||
LOG_DIR=/var/log/taler-merchant
|
||||
RUN_DIR=/var/run/taler-merchant/httpd
|
||||
|
||||
# --- 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
|
||||
# Debian package uses setgid sticky on run dir
|
||||
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
|
||||
|
||||
# Stale socket lock only when not accepting
|
||||
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 "Start certbot renewal (background)..."
|
||||
if [ -x /root/scripts/certbot_renew.sh ]; then
|
||||
/root/scripts/certbot_renew.sh &
|
||||
elif [ -x ./scripts/certbot_renew.sh ]; then
|
||||
./scripts/certbot_renew.sh &
|
||||
fi
|
||||
|
||||
echo "Create log + runtime dirs... permissions for taler-merchant-httpd / www-data:"
|
||||
mkdir -p "$LOG_DIR" /var/run/taler-merchant "$RUN_DIR"
|
||||
chown taler-merchant-httpd: "$LOG_DIR"
|
||||
chmod 755 "$LOG_DIR"
|
||||
chown taler-merchant-httpd:www-data /var/run/taler-merchant "$RUN_DIR"
|
||||
chmod 755 /var/run/taler-merchant "$RUN_DIR"
|
||||
# merchant app data (package default home)
|
||||
if [ -d /var/lib/taler-merchant ]; then
|
||||
chown -R taler-merchant-httpd:www-data /var/lib/taler-merchant 2>/dev/null || true
|
||||
fi
|
||||
|
||||
echo "Start base services needed for Taler Merchant."
|
||||
echo ""
|
||||
|
||||
if [ -f /root/.taler-secrets-env ]; then
|
||||
echo -n "Read secrets needed for SMS delivery:"
|
||||
set -a
|
||||
# shellcheck disable=SC1091
|
||||
source /root/.taler-secrets-env && echo " OK"
|
||||
set +a
|
||||
else
|
||||
echo "No /root/.taler-secrets-env (SMS may fail)"
|
||||
fi
|
||||
|
||||
echo "1. postgresql:"
|
||||
ensure_postgresql
|
||||
|
||||
echo "2. nginx:"
|
||||
if [ -x /etc/init.d/nginx ]; then
|
||||
/etc/init.d/nginx start 2>/dev/null || nginx || true
|
||||
else
|
||||
service nginx start 2>/dev/null || nginx || true
|
||||
fi
|
||||
|
||||
if [ "$NO_SHELL" -eq 1 ]; then
|
||||
echo "Base services started (--no-shell). Next: runuser -u taler-merchant-httpd -- $PWD_BIN/$MERCHANT_STARTER [--restart]"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "3. Switching now to user taler-merchant-httpd, in $PWD_BIN; find executable $MERCHANT_STARTER there!"
|
||||
echo ""
|
||||
cd "$PWD_BIN"
|
||||
# util-linux: -u and -s/--shell are mutually exclusive
|
||||
exec runuser -u taler-merchant-httpd -- env \
|
||||
CLICKSEND_API_KEY="${CLICKSEND_API_KEY:-}" \
|
||||
CLICKSEND_USERNAME="${CLICKSEND_USERNAME:-}" \
|
||||
TELESIGN_AUTH_TOKEN="${TELESIGN_AUTH_TOKEN:-}" \
|
||||
bash
|
||||
126
scripts/taler-merchant/start_merchant.sh
Executable file
126
scripts/taler-merchant/start_merchant.sh
Executable file
|
|
@ -0,0 +1,126 @@
|
|||
#!/bin/bash
|
||||
# Start / restart taler-merchant (manual, no systemd).
|
||||
# Run as: taler-merchant-httpd
|
||||
#
|
||||
# Usage:
|
||||
# start_merchant.sh
|
||||
# start_merchant.sh --restart | -r
|
||||
# start_merchant.sh --help
|
||||
|
||||
set -u
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: start_merchant.sh [--restart|-r] [--help|-h]
|
||||
|
||||
(default) Start merchant helpers + httpd.
|
||||
--restart Stop live taler-merchant-* daemons, then start cleanly.
|
||||
Does not touch postgres/nginx.
|
||||
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-merchant-httpd" ]; then
|
||||
echo "This script must be run as user taler-merchant-httpd" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
list_merchant_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_merchant.sh*) continue ;;
|
||||
esac
|
||||
case "$args" in
|
||||
*taler-merchant-httpd\ *|taler-merchant-httpd\ *)
|
||||
echo "$pid" ;;
|
||||
*taler-merchant-webhook*|*taler-merchant-kyccheck*|*taler-merchant-wirewatch*)
|
||||
echo "$pid" ;;
|
||||
*taler-merchant-depositcheck*|*taler-merchant-exchangekeyupdate*|*taler-merchant-reconciliation*)
|
||||
echo "$pid" ;;
|
||||
esac
|
||||
done | sort -u
|
||||
}
|
||||
|
||||
kill_taler_merchant() {
|
||||
local pids
|
||||
pids=$(list_merchant_pids | tr '\n' ' ')
|
||||
if [ -z "${pids// }" ]; then
|
||||
echo "No live taler-merchant 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_merchant_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-merchant daemons stopped."
|
||||
}
|
||||
|
||||
TIMESTAMP=$(date +"%Y%m%d_%H%M")
|
||||
BACKUP_DIR="/var/taler-backups"
|
||||
LOG_DIR="/var/log/taler-merchant"
|
||||
|
||||
if [ "$DO_RESTART" -eq 1 ]; then
|
||||
echo "=== restart: kill taler-merchant-* ==="
|
||||
kill_taler_merchant
|
||||
fi
|
||||
|
||||
BACKUP_NAME="taler-merchant-$TIMESTAMP.sql"
|
||||
echo -n "Backup taler-merchant DB: "
|
||||
mkdir -p "$BACKUP_DIR" 2>/dev/null || true
|
||||
if pg_dump taler-merchant >"$BACKUP_DIR/$BACKUP_NAME" 2>/dev/null; then
|
||||
echo "OK"
|
||||
else
|
||||
echo "SKIP/FAIL (postgres?)"
|
||||
fi
|
||||
echo "Start taler-merchant components:"
|
||||
|
||||
LOG_FILE="$LOG_DIR/taler-merchant-httpd-$(date +%Y-%m-%d).log"
|
||||
mkdir -p "$LOG_DIR"
|
||||
touch "$LOG_FILE"
|
||||
|
||||
# Prefer dedicated ensure script (nohup + logs per helper).
|
||||
if [ -x /usr/local/bin/ensure_merchant_helpers.sh ]; then
|
||||
/usr/local/bin/ensure_merchant_helpers.sh || true
|
||||
elif [ -x "$(dirname "$0")/ensure_merchant_helpers.sh" ]; then
|
||||
"$(dirname "$0")/ensure_merchant_helpers.sh" || true
|
||||
else
|
||||
nohup taler-merchant-httpd --log=info >>"$LOG_FILE" 2>&1 </dev/null &
|
||||
disown 2>/dev/null || true
|
||||
sleep 2
|
||||
nohup taler-merchant-webhook >>"$LOG_DIR/taler-merchant-webhook.log" 2>&1 </dev/null &
|
||||
nohup taler-merchant-kyccheck >>"$LOG_DIR/taler-merchant-kyccheck.log" 2>&1 </dev/null &
|
||||
nohup taler-merchant-wirewatch -c /etc/taler-merchant/taler-merchant.conf -L INFO \
|
||||
>>"$LOG_DIR/taler-merchant-wirewatch.log" 2>&1 </dev/null &
|
||||
nohup taler-merchant-depositcheck >>"$LOG_DIR/taler-merchant-depositcheck.log" 2>&1 </dev/null &
|
||||
nohup taler-merchant-exchangekeyupdate >>"$LOG_DIR/taler-merchant-exchangekeyupdate.log" 2>&1 </dev/null &
|
||||
nohup taler-merchant-reconciliation >>"$LOG_DIR/taler-merchant-reconciliation.log" 2>&1 </dev/null &
|
||||
disown 2>/dev/null || true
|
||||
sleep 1
|
||||
fi
|
||||
|
||||
echo "Live processes:"
|
||||
ps -eo pid,stat,args 2>/dev/null | grep taler-merchant | grep -v grep | grep -v ' Z ' || true
|
||||
|
||||
if [ -x /usr/local/bin/check_merchant-health.sh ]; then
|
||||
/usr/local/bin/check_merchant-health.sh || exit 1
|
||||
elif [ -x ./check_merchant-health.sh ]; then
|
||||
./check_merchant-health.sh || exit 1
|
||||
fi
|
||||
exit 0
|
||||
33
scripts/taler-merchant/stats--merchant-payments.sh
Executable file
33
scripts/taler-merchant/stats--merchant-payments.sh
Executable file
|
|
@ -0,0 +1,33 @@
|
|||
#!/usr/bin/env bash
|
||||
# Tested for DB scheme v38:
|
||||
|
||||
set -eu
|
||||
|
||||
runuser -u taler-merchant-httpd -- bash -c '
|
||||
printf "%-38s | %8s | %12s\n" "merchant_id" "payments" "total_amount"
|
||||
printf "%-38s-+-%-8s-+-%-12s\n" "--------------------------------------" "--------" "------------"
|
||||
|
||||
for s in $(psql -t -A taler-merchant -c "
|
||||
SELECT schemaname FROM pg_tables
|
||||
WHERE schemaname LIKE '\''merchant_instance_%'\''
|
||||
AND tablename = '\''merchant_deposits'\''
|
||||
"); do
|
||||
mid=$(psql -t -A taler-merchant -c "
|
||||
SELECT merchant_id FROM merchant.merchant_instances
|
||||
WHERE merchant_serial = ${s#merchant_instance_}
|
||||
" 2>/dev/null || echo "?")
|
||||
|
||||
psql -t -A taler-merchant -c "
|
||||
SELECT
|
||||
'\''$mid'\'',
|
||||
count(*),
|
||||
round(
|
||||
(COALESCE(sum((amount_with_fee).val), 0) +
|
||||
COALESCE(sum((amount_with_fee).frac), 0)::numeric / 1000000000)
|
||||
, 2)
|
||||
FROM $s.merchant_deposits
|
||||
" 2>/dev/null
|
||||
done | while read line; do
|
||||
printf "%-38s | %8s | %12s\n" $(echo "$line" | tr "|" " ")
|
||||
done
|
||||
'
|
||||
19
scripts/taler-merchant/taler-hacktivism-email-helper.sh
Executable file
19
scripts/taler-merchant/taler-hacktivism-email-helper.sh
Executable file
|
|
@ -0,0 +1,19 @@
|
|||
#!/bin/bash
|
||||
# Usage: echo "body text" | ./taler-hacktivism-email-helper.sh email@example.com
|
||||
# Needs ``swaks'' to be installed.
|
||||
TO="$1"
|
||||
SUBJECT="Taler Merchant Auth Code"
|
||||
BODY=$(cat)
|
||||
|
||||
# SMTP password: set SMTP_PASSWORD in the environment (not stored in git).
|
||||
# Live container may still use a literal in-file password — do not re-commit it.
|
||||
swaks --server mail.cyon.ch \
|
||||
--port 587 \
|
||||
--auth LOGIN \
|
||||
--auth-user taler-merchant@hacktivism.ch \
|
||||
--auth-password "${SMTP_PASSWORD:?set SMTP_PASSWORD}" \
|
||||
--tls \
|
||||
--from taler-merchant@hacktivism.ch \
|
||||
--to "$TO" \
|
||||
--header "Subject: $SUBJECT" \
|
||||
--body "$BODY"
|
||||
5
scripts/taler-merchant/taler-hacktivism-sms-helper-wrapper.sh
Executable file
5
scripts/taler-merchant/taler-hacktivism-sms-helper-wrapper.sh
Executable file
|
|
@ -0,0 +1,5 @@
|
|||
#!/bin/bash
|
||||
|
||||
PHONE_NO="$1"
|
||||
JSON_STRING="{\"CONTACT_PHONE\":\"${PHONE_NO}\"}"
|
||||
exec /usr/local/bin/taler-hacktivism-sms-helper.sh $JSON_STRING
|
||||
Loading…
Add table
Add a link
Reference in a new issue