diff --git a/configs/systemd/user/taler-landing-stats.service b/configs/systemd/user/taler-landing-stats.service index 6273a72..cc6bd88 100644 --- a/configs/systemd/user/taler-landing-stats.service +++ b/configs/systemd/user/taler-landing-stats.service @@ -14,12 +14,14 @@ TimeoutStartSec=480 # Do not keep "active" after run — timer may fire again cleanly RemainAfterExit=no Environment=TZ=Europe/Zurich +# Stack overrides (goa / stage-testpaysan / custom) — optional +EnvironmentFile=-%h/.config/taler-landing/stack.env +# GOA defaults if stack.env absent Environment=ADMIN_LOG=%h/src/koopa/koopa-admin-log Environment=SECRETS_BANK=%h/src/koopa/koopa-admin-secrets/koopa/host-root/taler-bank Environment=BANK_URL=http://127.0.0.1:9012 Environment=BANK_PUBLIC_URL=https://bank.hacktivism.ch Environment=EXCHANGE_CONFIG_URL=https://exchange.hacktivism.ch/config -# All accounts except exchange (double-count of withdraw credits on exchange ledger) Environment=SCAN_SKIP=exchange Environment=TX_PAGE=500 Environment=MAX_TX_PAGES=0 diff --git a/scripts/taler-landing/README.md b/scripts/taler-landing/README.md index 0876695..f266ec7 100644 --- a/scripts/taler-landing/README.md +++ b/scripts/taler-landing/README.md @@ -1,12 +1,21 @@ -# taler-landing — central stats (hernani user systemd) +# taler-landing — generic multi-stack landing stats -Public landing numbers on +One collector (`collect_bank_stats.py` + `collect-landing-stats.sh`) for any +currency / stack. **Defaults = GOA / hacktivism**; stage TESTPAYSAN uses a +profile file. + +Public landing numbers (example GOA): - https://bank.hacktivism.ch/intro/ → `stats.json` - https://exchange.hacktivism.ch/intro/ → `stats.json` - https://taler.hacktivism.ch/intro/ → `stats.json` -are produced by **one host process** as user **`hernani`**, not by three unrelated in-container crons. +Stage TESTPAYSAN (shared bank feed on all three hosts): + +- https://stage.bank.lefrancpaysan.ch/intro/stats.json + (same file also under stage.exchange / stage.monnaie landings) + +Produced by **one host process** (user systemd timer), not three crons. ## Why host / user systemd @@ -77,16 +86,37 @@ Preferred order: Explorer password optional (shared-pool balance). +## Profiles + +| File | Stack | +|------|--------| +| `profiles/goa.env` | hacktivism GOA (container publish) | +| `profiles/stage-testpaysan.env` | FrancPaysan stage; **shared** host `stats.json` | + +```bash +# Stage (on stagepaysan): +STACK_PROFILE=stage-testpaysan ./scripts/taler-landing/install-landing-stats-host.sh +# or: francpaysan-admin-log/scripts/stagepaysan/install-landing-stats.sh +``` + +`~/.config/taler-landing/stack.env` is sourced on each run. + ## Env overrides | Env | Default | Meaning | |-----|---------|---------| | `BANK_URL` | `http://127.0.0.1:9012` | libeufin loopback | +| `BANK_CURRENCY` | *(auto)* | `GOA` / `TESTPAYSAN` / … | | `SCAN_SKIP` | `exchange` | usernames excluded from flow | +| `COLLECT_BANK` / `_EXCHANGE` / `_MERCHANT` | `1` | phase switches | +| `COLLECT_RESOURCES` | `1` | container RSS/loadavg merge | +| `HOST_STATS_DIRS` | empty | space-separated host dirs for **shared** bank `stats.json` | +| `PUBLISH_PODMAN` | `1` | also `podman cp` into container landings | | `TX_PAGE` | `500` | transactions page size | | `MAX_TX_PAGES` | `0` | `0` = unlimited pages / account | | `ACCOUNTS_DELTA` | `-10000` | account list window | | `ADMIN_LOG` | `%h/src/koopa/koopa-admin-log` | refresh in-container scripts | +| `STATS_SOURCE_LABEL` | `host collect_bank_stats.py` | `stats.source` field | ## UI alt names diff --git a/scripts/taler-landing/collect-landing-stats.sh b/scripts/taler-landing/collect-landing-stats.sh index e49b00f..d3baa3b 100755 --- a/scripts/taler-landing/collect-landing-stats.sh +++ b/scripts/taler-landing/collect-landing-stats.sh @@ -1,10 +1,14 @@ #!/usr/bin/env bash -# Central landing-stats collector for hernani@koopa (user systemd timer). +# Generic landing-stats orchestrator (any Taler stack). # -# - Bank: full account+ledger scan via collect_bank_stats.py → bank container -# - Exchange / merchant: existing in-container scripts, then amount_alt enrich -# - All three: container RSS / loadavg / top procs via mem-snapshot (podman exec) -# - Never wipes a good stats.json on failure (writes stats-run.json only) +# Defaults = GOA / hacktivism on koopa. Override via env or EnvironmentFile +# (see profiles: goa | stage-testpaysan). +# +# - Bank: collect_bank_stats.py (currency-agnostic) → stats.json +# - Optional: exchange / merchant in-container generators +# - Optional: container RSS/loadavg merge +# - Publish: podman into container landings and/or host dirs (shared bank stats) +# - Never wipes a good stats.json on failure (stats-run.json only) # # Install: scripts/taler-landing/install-landing-stats-host.sh set -euo pipefail @@ -12,6 +16,20 @@ set -euo pipefail export TZ="${TZ:-Europe/Zurich}" export PATH="${HOME}/.local/bin:/usr/local/bin:/usr/bin:/bin${PATH:+:$PATH}" +# Optional stack profile: ~/.config/taler-landing/stack.env or STACK_ENV_FILE +if [ -n "${STACK_ENV_FILE:-}" ] && [ -f "${STACK_ENV_FILE}" ]; then + # shellcheck disable=SC1090 + set -a + # shellcheck source=/dev/null + . "${STACK_ENV_FILE}" + set +a +elif [ -f "${HOME}/.config/taler-landing/stack.env" ]; then + set -a + # shellcheck source=/dev/null + . "${HOME}/.config/taler-landing/stack.env" + set +a +fi + log() { printf '%s %s\n' "$(date -Iseconds)" "$*"; } ROOT="$(cd "$(dirname "$0")" && pwd)" @@ -22,6 +40,8 @@ elif [ -f "${HOME}/.local/lib/taler-landing/collect_bank_stats.py" ]; then LIB="${HOME}/.local/lib/taler-landing" elif [ -f "${HOME}/src/koopa/koopa-admin-log/scripts/taler-landing/collect_bank_stats.py" ]; then LIB="${HOME}/src/koopa/koopa-admin-log/scripts/taler-landing" +elif [ -f "${HOME}/taler/src/koopa-admin-log/scripts/taler-landing/collect_bank_stats.py" ]; then + LIB="${HOME}/taler/src/koopa-admin-log/scripts/taler-landing" else LIB="$ROOT" fi @@ -29,6 +49,13 @@ fi ADMIN_LOG="${ADMIN_LOG:-${HOME}/src/koopa/koopa-admin-log}" SECRETS_BANK="${SECRETS_BANK:-${HOME}/src/koopa/koopa-admin-secrets/koopa/host-root/taler-bank}" +# Which phases to run (1/0) +COLLECT_BANK="${COLLECT_BANK:-1}" +COLLECT_EXCHANGE="${COLLECT_EXCHANGE:-1}" +COLLECT_MERCHANT="${COLLECT_MERCHANT:-1}" +COLLECT_RESOURCES="${COLLECT_RESOURCES:-1}" + +# Containers (empty = skip podman publish for that role) BANK_CTR="${BANK_CTR:-taler-hacktivism-bank}" EX_CTR="${EX_CTR:-taler-hacktivism-exchange-ansible}" MER_CTR="${MER_CTR:-taler-hacktivism}" @@ -36,10 +63,19 @@ MER_CTR="${MER_CTR:-taler-hacktivism}" BANK_URL="${BANK_URL:-http://127.0.0.1:9012}" BANK_PUBLIC_URL="${BANK_PUBLIC_URL:-https://bank.hacktivism.ch}" EXCHANGE_CONFIG_URL="${EXCHANGE_CONFIG_URL:-https://exchange.hacktivism.ch/config}" +BANK_CURRENCY="${BANK_CURRENCY:-}" # empty → auto from bank/exchange /config +STATS_SOURCE_LABEL="${STATS_SOURCE_LABEL:-host collect_bank_stats.py}" +# In-container landing roots (podman publish) BANK_LANDING_IN="${BANK_LANDING_IN:-/var/www/bank-landing}" EX_LANDING_IN="${EX_LANDING_IN:-/var/www/exchange-landing}" MER_LANDING_IN="${MER_LANDING_IN:-/var/www/merchant-landing}" +PUBLISH_PODMAN="${PUBLISH_PODMAN:-1}" + +# Host dirs that should get the *same* bank stats.json (shared public feed). +# Colon-separated (systemd EnvironmentFile-safe). Example stage: +# HOST_STATS_DIRS=/var/www/lfp-stage/bank:/var/www/lfp-stage/exchange:/var/www/lfp-stage/merchant +HOST_STATS_DIRS="${HOST_STATS_DIRS:-}" WORKDIR="${LANDING_STATS_WORKDIR:-${XDG_RUNTIME_DIR:-/tmp}/taler-landing-stats}" mkdir -p "$WORKDIR" @@ -64,7 +100,9 @@ MEM_SRC="${MEM_SNAPSHOT_SRC:-$ADMIN_LOG/scripts/taler-shared/mem-snapshot.sh}" merge_container_resources() { local label="$1" ctr="$2" stats_file="$3" + [ "${COLLECT_RESOURCES}" = "1" ] || return 0 [ -f "$stats_file" ] || return 0 + [ -n "$ctr" ] || return 0 if ! ctr_running "$ctr"; then log "WARN: $label resources: $ctr not running" return 1 @@ -95,6 +133,36 @@ merge_container_resources() { return 0 } +# Write stats.json into host directories (shared feed: same file everywhere). +publish_host_stats() { + local src_stats="$1" src_run="${2:-}" + [ -n "${HOST_STATS_DIRS:-}" ] || return 0 + [ -f "$src_stats" ] || return 0 + local d paths + # Accept colon or space separators + paths=$(printf '%s' "$HOST_STATS_DIRS" | tr ': ' '\n' | sed '/^$/d') + while IFS= read -r d; do + [ -n "$d" ] || continue + if [ -d "$d" ] || mkdir -p "$d" 2>/dev/null; then + if cp -f "$src_stats" "${d}/stats.json" 2>/dev/null; then + chmod a+r "${d}/stats.json" 2>/dev/null || true + [ -n "$src_run" ] && [ -f "$src_run" ] && cp -f "$src_run" "${d}/stats-run.json" 2>/dev/null || true + log "host: published ${d}/stats.json" + continue + fi + fi + if command -v sudo >/dev/null 2>&1 && sudo -n true 2>/dev/null; then + sudo mkdir -p "$d" 2>/dev/null || true + sudo cp -f "$src_stats" "${d}/stats.json" + sudo chmod a+r "${d}/stats.json" 2>/dev/null || true + [ -n "$src_run" ] && [ -f "$src_run" ] && sudo cp -f "$src_run" "${d}/stats-run.json" 2>/dev/null || true + log "host: published ${d}/stats.json (sudo)" + else + log "WARN: cannot write ${d}/stats.json (need write or passwordless sudo)" + fi + done <<<"$paths" +} + read_pass() { local name="$1" f for f in \ @@ -107,11 +175,25 @@ read_pass() { return 0 fi done - # container (hernani can podman exec root files inside owned containers) - if podman inspect -f '{{.State.Running}}' "$BANK_CTR" 2>/dev/null | grep -qx true; then - if podman exec "$BANK_CTR" test -r "/root/${name}" 2>/dev/null; then - podman exec "$BANK_CTR" cat "/root/${name}" 2>/dev/null | tr -d '\n' - return 0 + # container secrets: /root/… (GOA) or /data/secrets/… (FrancPaysan stage) + if [ -n "${BANK_CTR:-}" ] && podman inspect -f '{{.State.Running}}' "$BANK_CTR" 2>/dev/null | grep -qx true; then + for path in "/root/${name}" "/data/secrets/${name}" "/data/secrets/bank-admin-password.txt"; do + case "$path" in + *bank-admin-password.txt) + [ "$name" = "bank-admin-password.txt" ] || continue + ;; + esac + if podman exec "$BANK_CTR" test -r "$path" 2>/dev/null; then + podman exec "$BANK_CTR" cat "$path" 2>/dev/null | tr -d '\n\r' + return 0 + fi + done + # stage explorer secret naming + if [ "$name" = "bank-explorer-password.txt" ]; then + if podman exec "$BANK_CTR" test -r /data/secrets/bank-explorer-password.txt 2>/dev/null; then + podman exec "$BANK_CTR" cat /data/secrets/bank-explorer-password.txt 2>/dev/null | tr -d '\n\r' + return 0 + fi fi fi return 1 @@ -123,6 +205,8 @@ ctr_running() { publish_json() { local ctr="$1" dest_dir="$2" src_stats="$3" src_run="$4" + [ "${PUBLISH_PODMAN}" = "1" ] || return 0 + [ -n "$ctr" ] || return 0 if ! ctr_running "$ctr"; then log "WARN: $ctr not running — skip publish $dest_dir" return 1 @@ -166,6 +250,7 @@ collect_bank() { BANK_URL="$BANK_URL" \ BANK_PUBLIC_URL="$BANK_PUBLIC_URL" \ EXCHANGE_CONFIG_URL="$EXCHANGE_CONFIG_URL" \ + BANK_CURRENCY="${BANK_CURRENCY:-}" \ BANK_ADMIN_PASS="$admin_pass" \ BANK_EXPLORER_PASS="$explorer_pass" \ DEMO_DIR="${demo_dir}" \ @@ -173,6 +258,7 @@ collect_bank() { TX_PAGE="${TX_PAGE:-500}" \ MAX_TX_PAGES="${MAX_TX_PAGES:-0}" \ ACCOUNTS_DELTA="${ACCOUNTS_DELTA:--10000}" \ + STATS_SOURCE_LABEL="$STATS_SOURCE_LABEL" \ "$PY" "$LIB/collect_bank_stats.py" \ --out "$WORKDIR/bank-stats.json" \ --run-out "$WORKDIR/bank-stats-run.json" \ @@ -181,11 +267,13 @@ collect_bank() { set -e if [ "$ec_bank" -eq 0 ] && [ -f "$WORKDIR/bank-stats.json" ]; then - # Always attach in-container RSS/loadavg (not host /proc) + # Optional: attach in-container RSS/loadavg (not host /proc) merge_container_resources bank "$BANK_CTR" "$WORKDIR/bank-stats.json" || true publish_json "$BANK_CTR" "$BANK_LANDING_IN" \ "$WORKDIR/bank-stats.json" "$WORKDIR/bank-stats-run.json" - log "bank: OK → ${BANK_CTR}:${BANK_LANDING_IN}/stats.json" + # Shared public feed: same bank stats on all HOST_STATS_DIRS (bank/ex/merchant landings) + publish_host_stats "$WORKDIR/bank-stats.json" "$WORKDIR/bank-stats-run.json" + log "bank: OK (podman=${BANK_CTR:-none} host_dirs=${HOST_STATS_DIRS:-none})" else log "ERROR: bank collect failed (ec=$ec_bank) — previous stats.json kept" [ -f "$WORKDIR/bank-stats-run.json" ] || \ @@ -272,13 +360,26 @@ collect_merchant() { # --------------------------------------------------------------------------- main() { - log "=== taler-landing-stats start (user=$(id -un) lib=$LIB) ===" - collect_bank || ec_bank=$? - collect_exchange || ec_ex=$? - collect_merchant || ec_mer=$? + log "=== taler-landing-stats start (user=$(id -un) lib=$LIB currency=${BANK_CURRENCY:-auto}) ===" + log "phases bank=${COLLECT_BANK} exchange=${COLLECT_EXCHANGE} merchant=${COLLECT_MERCHANT} resources=${COLLECT_RESOURCES}" + if [ "${COLLECT_BANK}" = "1" ]; then + collect_bank || ec_bank=$? + else + log "skip bank collect" + fi + if [ "${COLLECT_EXCHANGE}" = "1" ]; then + collect_exchange || ec_ex=$? + else + log "skip exchange collect" + fi + if [ "${COLLECT_MERCHANT}" = "1" ]; then + collect_merchant || ec_mer=$? + else + log "skip merchant collect" + fi log "=== done bank=$ec_bank exchange=$ec_ex merchant=$ec_mer ===" # non-zero if bank failed (primary public flow numbers); soft on ex/mer - if [ "$ec_bank" -ne 0 ]; then + if [ "${COLLECT_BANK}" = "1" ] && [ "$ec_bank" -ne 0 ]; then return 1 fi return 0 diff --git a/scripts/taler-landing/collect_bank_stats.py b/scripts/taler-landing/collect_bank_stats.py index 096eae7..b8d4398 100755 --- a/scripts/taler-landing/collect_bank_stats.py +++ b/scripts/taler-landing/collect_bank_stats.py @@ -1,23 +1,22 @@ #!/usr/bin/env python3 """Full-scan bank landing stats (all accounts, all ledger money). -Run on koopa as hernani (or anywhere with admin API access). Writes stats.json -compatible with bank.hacktivism.ch/intro/ plus amount_alt fields for compact UI. +Generic for any currency stack (GOA, TESTPAYSAN, KUDOS, …). Run on the ops host +(with admin API access). Writes stats.json for public landings + amount_alt. Env (selected): BANK_URL default http://127.0.0.1:9012 + BANK_CURRENCY optional; else from exchange /config or first ledger amount BANK_ADMIN_USER default admin BANK_ADMIN_PASS or BANK_ADMIN_PASS_FILE / pass via --admin-pass-file BANK_EXPLORER_USER default explorer BANK_EXPLORER_PASS optional (balance_explorer) - EXCHANGE_CONFIG_URL for alt_unit_names (default https://exchange.hacktivism.ch/config) + EXCHANGE_CONFIG_URL for alt_unit_names + currency + BANK_PUBLIC_URL public base for latency probes OUT output path (default stdout if -) SCAN_SKIP comma usernames excluded from flow (default: exchange) - TX_PAGE page size for transactions delta (default 500) - MAX_TX_PAGES 0 = unlimited pages per account (default 0) - ACCOUNTS_DELTA GET /accounts?delta= (default -10000) - RECENT_WD_N recent withdraws (default 10) - TZ default Europe/Zurich + TX_PAGE / MAX_TX_PAGES / ACCOUNTS_DELTA / RECENT_WD_N / TZ + STATS_SOURCE_LABEL optional string in stats.source field """ from __future__ import annotations @@ -343,6 +342,11 @@ def main() -> int: "--exchange-config", default=env("EXCHANGE_CONFIG_URL", "https://exchange.hacktivism.ch/config"), ) + ap.add_argument( + "--currency", + default=env("BANK_CURRENCY", ""), + help="ledger currency code (optional; else exchange /config or first amount)", + ) ap.add_argument("--out", default=env("OUT", "-")) ap.add_argument("--run-out", default=env("RUN_OUT", "")) ap.add_argument( @@ -364,6 +368,11 @@ def main() -> int: ) ap.add_argument("--recent", type=int, default=int(env("RECENT_WD_N", "10") or "10")) ap.add_argument("--public-base", default=env("BANK_PUBLIC_URL", "https://bank.hacktivism.ch")) + ap.add_argument( + "--source-label", + default=env("STATS_SOURCE_LABEL", "host collect_bank_stats.py"), + help="stats.source field (identify stack / collector)", + ) args = ap.parse_args() tz_name = env("TZ", "Europe/Zurich") or "Europe/Zurich" @@ -378,6 +387,7 @@ def main() -> int: bank = args.bank.rstrip("/") skip = {s.strip() for s in (args.skip or "").split(",") if s.strip()} + currency_hint = (args.currency or "").strip().upper() def write_run(ok: bool, err: Optional[str] = None) -> None: if not args.run_out: @@ -397,6 +407,24 @@ def main() -> int: raise RuntimeError("no admin password (BANK_ADMIN_PASS or --admin-pass-file)") token = get_token(bank, args.admin_user, admin_pass) alt = load_alt_from_config(args.exchange_config) + # Currency: explicit → exchange /config → first ledger amount → GOA + currency = currency_hint + if not currency: + try: + code_c, cfg_d, _ = http_json(args.exchange_config, timeout=12) + if code_c == 200 and isinstance(cfg_d, dict) and cfg_d.get("currency"): + currency = str(cfg_d["currency"]).strip().upper() + except Exception: + pass + if not currency: + try: + code_c, cfg_d, _ = http_json(f"{bank}/config", timeout=12) + if code_c == 200 and isinstance(cfg_d, dict) and cfg_d.get("currency"): + currency = str(cfg_d["currency"]).strip().upper() + except Exception: + pass + if not currency: + currency = "GOA" if not alt: alt = dict(DEFAULT_ALT) @@ -442,13 +470,24 @@ def main() -> int: except Exception: continue low = (subject or "").lower() + # Normalise bare numbers to stack currency + if ":" not in str(amount): + amount = f"{currency}:{amount}" + else: + # adopt ledger currency if we only had a hint + try: + cur0, _n0 = parse_amount(amount) + if cur0 and currency_hint == "" and cur0.upper() != "GOA": + currency = cur0.upper() + except Exception: + pass if direction == "credit": total_in += n n_incoming += 1 incomings.append( { "kind": "incoming", - "amount": amount if ":" in amount else f"GOA:{amount}", + "amount": amount, "at_unix": ts, "account": uname, "subject": subject, @@ -460,7 +499,7 @@ def main() -> int: withdraws.append( { "kind": "withdraw", - "amount": amount if ":" in amount else f"GOA:{amount}", + "amount": amount, "at_unix": ts, "account": uname, "subject": subject, @@ -503,7 +542,7 @@ def main() -> int: users_n = sum(1 for u in accounts if u not in ("admin", "exchange")) def pack_amt(d: Decimal) -> Dict[str, Any]: - f = format_amount_alt(f"GOA:{d}", alt) + f = format_amount_alt(f"{currency}:{d}", alt) return { "amount": f["amount"], "amount_alt": f["amount_alt"], @@ -567,10 +606,11 @@ def main() -> int: } ) - # explorer balance - balance = "GOA:0" - bal_alt = "GOA:0" - bal_full = "GOA:0" + # explorer / pool balance (shared demo account when present) + zero = f"{currency}:0" + balance = zero + bal_alt = zero + bal_full = zero if explorer_pass: try: etok = get_token(bank, args.explorer_user, explorer_pass) @@ -583,7 +623,7 @@ def main() -> int: balance = str( data.get("balance", {}).get("amount") if isinstance(data.get("balance"), dict) - else data.get("amount") or data.get("balance") or "GOA:0" + else data.get("amount") or data.get("balance") or zero ) # sometimes balance is object with amount if isinstance(data.get("balance"), dict) and data["balance"].get( @@ -608,7 +648,7 @@ def main() -> int: ) if code == 200 and isinstance(data, dict): if isinstance(data.get("balance"), dict): - balance = str(data["balance"].get("amount") or "GOA:0") + balance = str(data["balance"].get("amount") or zero) elif data.get("amount"): balance = str(data["amount"]) bf = format_amount_alt(balance, alt) @@ -637,12 +677,12 @@ def main() -> int: stats = { "ok": True, - "currency": "GOA", + "currency": currency, "timezone": tz_name, "generated_at": gen_iso, "generated_at_human": gen_human, "generated_at_unix": now_u, - "source": "host collect_bank_stats.py (hernani systemd)", + "source": str(args.source_label or "host collect_bank_stats.py"), "bank_url": bank, "scan": { "tx_page": args.tx_page, diff --git a/scripts/taler-landing/install-landing-stats-host.sh b/scripts/taler-landing/install-landing-stats-host.sh index 7c572f0..4a26217 100755 --- a/scripts/taler-landing/install-landing-stats-host.sh +++ b/scripts/taler-landing/install-landing-stats-host.sh @@ -1,35 +1,41 @@ #!/usr/bin/env bash -# Install central landing-stats collector as hernani user systemd timer. +# Install generic landing-stats collector as user systemd timer. # -# On koopa: +# GOA / koopa (default): # cd ~/src/koopa/koopa-admin-log # ./scripts/taler-landing/install-landing-stats-host.sh -# systemctl --user start taler-landing-stats.service # one-shot now -# systemctl --user status taler-landing-stats.timer # -# Requires: linger enabled for hernani (loginctl enable-linger hernani) -# so the timer runs without an interactive login. +# Stage TESTPAYSAN (stagepaysan on francpaysan host): +# STACK_PROFILE=stage-testpaysan ./scripts/taler-landing/install-landing-stats-host.sh +# # or from francpaysan-admin-log: ./scripts/stagepaysan/install-landing-stats.sh +# +# Optional: STACK_ENV_FILE=/path/to/profile.env +# Requires linger: sudo loginctl enable-linger $USER set -euo pipefail ROOT="$(cd "$(dirname "$0")/../.." && pwd)" ADMIN_LOG="${ADMIN_LOG:-$ROOT}" SRC="$ADMIN_LOG/scripts/taler-landing" UNIT_SRC="$ADMIN_LOG/configs/systemd/user" +PROFILE="${STACK_PROFILE:-goa}" BIN_DST="${HOME}/.local/bin" LIB_DST="${HOME}/.local/lib/taler-landing" UNIT_DST="${HOME}/.config/systemd/user" STATE_DST="${HOME}/.local/state/taler-landing-stats" +CFG_DST="${HOME}/.config/taler-landing" -mkdir -p "$BIN_DST" "$LIB_DST" "$UNIT_DST" "$STATE_DST" +mkdir -p "$BIN_DST" "$LIB_DST" "$UNIT_DST" "$STATE_DST" "$CFG_DST" install -m 0755 "$SRC/collect-landing-stats.sh" "$BIN_DST/collect-landing-stats.sh" install -m 0755 "$SRC/collect_bank_stats.py" "$LIB_DST/collect_bank_stats.py" install -m 0755 "$SRC/enrich_stats_alt.py" "$LIB_DST/enrich_stats_alt.py" install -m 0755 "$SRC/merge_resources.py" "$LIB_DST/merge_resources.py" install -m 0755 "$SRC/collect_container_resources.sh" "$LIB_DST/collect_container_resources.sh" -install -m 0755 "$SRC/test-landing-stats.sh" "$BIN_DST/test-landing-stats.sh" 2>/dev/null || \ - install -m 0755 "$SRC/test-landing-stats.sh" "$LIB_DST/test-landing-stats.sh" +if [ -f "$SRC/test-landing-stats.sh" ]; then + install -m 0755 "$SRC/test-landing-stats.sh" "$BIN_DST/test-landing-stats.sh" 2>/dev/null || \ + install -m 0755 "$SRC/test-landing-stats.sh" "$LIB_DST/test-landing-stats.sh" +fi install -m 0644 "$SRC/goa_amounts.py" "$LIB_DST/goa_amounts.py" # mem-snapshot helper lives in taler-shared (copied into containers at collect time) if [ -f "$ADMIN_LOG/scripts/taler-shared/mem-snapshot.sh" ]; then @@ -37,18 +43,34 @@ if [ -f "$ADMIN_LOG/scripts/taler-shared/mem-snapshot.sh" ]; then "$LIB_DST/mem-snapshot.sh" fi -# Wrapper always uses installed lib next to itself when LIB discovery works; -# also point ADMIN_LOG for in-container script refresh. +# Stack profile → ~/.config/taler-landing/stack.env (sourced by collector) +if [ -n "${STACK_ENV_FILE:-}" ] && [ -f "${STACK_ENV_FILE}" ]; then + install -m 0644 "${STACK_ENV_FILE}" "$CFG_DST/stack.env" +elif [ -f "$SRC/profiles/${PROFILE}.env" ]; then + install -m 0644 "$SRC/profiles/${PROFILE}.env" "$CFG_DST/stack.env" +else + echo "WARN: no profile ${PROFILE}.env — collector uses built-in GOA defaults" >&2 +fi + install -m 0644 "$UNIT_SRC/taler-landing-stats.service" "$UNIT_DST/" install -m 0644 "$UNIT_SRC/taler-landing-stats.timer" "$UNIT_DST/" -# Rewrite ExecStart to installed binary if unit uses %h path — units already do. +# Point unit EnvironmentFile at stack profile +if grep -q '^EnvironmentFile=' "$UNIT_DST/taler-landing-stats.service" 2>/dev/null; then + : +else + # inject after [Service] + sed -i '/^\[Service\]/a EnvironmentFile=-%h/.config/taler-landing/stack.env' \ + "$UNIT_DST/taler-landing-stats.service" +fi + systemctl --user daemon-reload systemctl --user enable --now taler-landing-stats.timer -echo "Installed (user=$(id -un)):" +echo "Installed (user=$(id -un) profile=${PROFILE}):" echo " $BIN_DST/collect-landing-stats.sh" echo " $LIB_DST/{collect_bank_stats,enrich_stats_alt,goa_amounts}.py" +echo " $CFG_DST/stack.env" echo " $UNIT_DST/taler-landing-stats.{service,timer} (timer enabled)" echo " logs: $STATE_DST/" echo @@ -61,8 +83,6 @@ echo "Run once now:" echo " systemctl --user start taler-landing-stats.service" echo " journalctl --user -u taler-landing-stats.service -n 40 --no-pager" echo -echo "Optional secrets (if not readable via podman exec bank /root/…):" -echo " mkdir -p ~/.config/taler-landing" -echo " cp …/bank-admin-password.txt ~/.config/taler-landing/" -echo " cp …/bank-explorer-password.txt ~/.config/taler-landing/" -echo " chmod 600 ~/.config/taler-landing/*" +echo "Secrets (if not via podman exec bank):" +echo " mkdir -p ~/.config/taler-landing && chmod 700 ~/.config/taler-landing" +echo " # bank-admin-password.txt bank-explorer-password.txt" diff --git a/scripts/taler-landing/profiles/goa.env b/scripts/taler-landing/profiles/goa.env new file mode 100644 index 0000000..08c357a --- /dev/null +++ b/scripts/taler-landing/profiles/goa.env @@ -0,0 +1,18 @@ +# Stack profile: GOA / hacktivism +# systemd EnvironmentFile: KEY=value only (no export) +BANK_CTR=taler-hacktivism-bank +EX_CTR=taler-hacktivism-exchange-ansible +MER_CTR=taler-hacktivism +BANK_URL=http://127.0.0.1:9012 +BANK_PUBLIC_URL=https://bank.hacktivism.ch +EXCHANGE_CONFIG_URL=https://exchange.hacktivism.ch/config +BANK_CURRENCY=GOA +BANK_LANDING_IN=/var/www/bank-landing +EX_LANDING_IN=/var/www/exchange-landing +MER_LANDING_IN=/var/www/merchant-landing +COLLECT_BANK=1 +COLLECT_EXCHANGE=1 +COLLECT_MERCHANT=1 +COLLECT_RESOURCES=1 +PUBLISH_PODMAN=1 +STATS_SOURCE_LABEL="host collect_bank_stats.py goa hacktivism" diff --git a/scripts/taler-landing/profiles/stage-testpaysan.env b/scripts/taler-landing/profiles/stage-testpaysan.env new file mode 100644 index 0000000..b63a58c --- /dev/null +++ b/scripts/taler-landing/profiles/stage-testpaysan.env @@ -0,0 +1,22 @@ +# Stack profile: FrancPaysan stage / TESTPAYSAN (ops user stagepaysan) +# systemd EnvironmentFile: KEY=value only (no export, no shell expansion) +BANK_CTR=stage-lfp-bank +EX_CTR=stage-lfp-exchange-ansible +MER_CTR=stage-lfp-merchant +BANK_URL=http://127.0.0.1:9032 +BANK_PUBLIC_URL=https://stage.bank.lefrancpaysan.ch +EXCHANGE_CONFIG_URL=https://stage.exchange.lefrancpaysan.ch/config +BANK_CURRENCY=TESTPAYSAN +SCAN_SKIP=exchange +BANK_LANDING_IN=/data/var/www +EX_LANDING_IN=/data/var/www +MER_LANDING_IN=/data/var/www +# Shared public feed: one bank scan → same stats.json on all three Caddy landings +# Colon-separated for systemd EnvironmentFile +HOST_STATS_DIRS=/var/www/lfp-stage/bank:/var/www/lfp-stage/exchange:/var/www/lfp-stage/merchant +COLLECT_BANK=1 +COLLECT_EXCHANGE=0 +COLLECT_MERCHANT=0 +COLLECT_RESOURCES=0 +PUBLISH_PODMAN=1 +STATS_SOURCE_LABEL="host collect_bank_stats.py stage TESTPAYSAN shared"