diff --git a/configs/bank-landing/index.html b/configs/bank-landing/index.html index 09d1c5d..4d1b748 100644 --- a/configs/bank-landing/index.html +++ b/configs/bank-landing/index.html @@ -1118,12 +1118,18 @@ -

+ +

stack· - libeufin-bank 1.6.6 + vanilla · - libeufin-common 1.6.6 + libeufin-bank 1.6.10 + · + libeufin-common 1.6.10 + · + libeufin-bank-webui 1.6.38

+
Intergalactic · GOA · no IBAN @@ -1558,10 +1564,10 @@ tw run-until-done && tw balance /privacy.

-

- Bank landing v44 - · content - · 689ee59 +

+ Bank landing v45 + · content + · 0d881cd

diff --git a/configs/bank-landing/nginx-landing.conf b/configs/bank-landing/nginx-landing.conf index 251a713..e0ff9fb 100644 --- a/configs/bank-landing/nginx-landing.conf +++ b/configs/bank-landing/nginx-landing.conf @@ -49,6 +49,16 @@ server { add_header Cache-Control "no-store" always; add_header Access-Control-Allow-Origin * always; } + # FP pattern: GET Integration via demo-api (inject suggested/required_exchange) + # Caddy @wdGet → :9013 → here → :19096; POST/other Integration stays on :9012 + location /taler-integration/withdrawal-operation/ { + proxy_pass http://127.0.0.1:19096; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_read_timeout 70s; + add_header Cache-Control "no-store" always; + add_header Access-Control-Allow-Origin * always; + } location /intro/ { alias /var/www/bank-landing/; } diff --git a/configs/caddy/Caddyfile b/configs/caddy/Caddyfile index 295ba94..ee8dee5 100644 --- a/configs/caddy/Caddyfile +++ b/configs/caddy/Caddyfile @@ -204,6 +204,19 @@ bank.hacktivism.ch { } } + # FP pattern (francpaysan-admin-log files/caddy/Caddyfile @wdGet): + # GET withdrawal-operation → landing :9013 → demo-api :19096 + # (injects suggested_exchange / required_exchange for Android/iOS) + @wdGet { + method GET + path /taler-integration/withdrawal-operation/* + } + handle @wdGet { + reverse_proxy 127.0.0.1:9013 { + import proxy_public + } + } + reverse_proxy 127.0.0.1:9012 { import proxy_public } diff --git a/configs/exchange-landing/index.html b/configs/exchange-landing/index.html index c57b486..75908bb 100644 --- a/configs/exchange-landing/index.html +++ b/configs/exchange-landing/index.html @@ -289,18 +289,22 @@ -

+ +

stack· - taler-exchange 1.6.6 + vanilla · - libtalerexchange 1.6.6 + taler-exchange 1.6.15 · - taler-exchange-database 1.6.6 + libtalerexchange 1.6.15 · - taler-exchange-offline 1.6.6 + taler-exchange-database 1.6.15 · - taler-terms-generator 1.6.6 + taler-exchange-offline 1.6.15 + · + taler-terms-generator 1.6.15

+
GOA · Exchange @@ -500,10 +504,10 @@ sudo apt-get install -y taler-wallet-cli /privacy.

-

- Exchange landing v43 - · content - · d0594cb +

+ Exchange landing v44 + · content + · 0d881cd

diff --git a/configs/merchant-landing/index.html b/configs/merchant-landing/index.html index 77330f8..14af99e 100644 --- a/configs/merchant-landing/index.html +++ b/configs/merchant-landing/index.html @@ -530,18 +530,22 @@ -

+ +

stack· - taler-merchant 1.6.9 + vanilla · - taler-merchant-webui 1.6.8~dev6 + taler-merchant 1.6.23 · - libtalermerchant 1.6.9 + taler-merchant-webui 1.6.38 · - libtalerexchange 1.6.7~dev2 + libtalermerchant 1.6.23 · - taler-terms-generator 1.6.7~dev2 + libtalerexchange 1.6.15 + · + taler-terms-generator 1.6.15

+
Merchant · dual currency @@ -833,10 +837,10 @@ sudo apt-get install -y taler-wallet-cli /privacy.

-

- Merchant landing v42 - · content - · 37ea4bb +

+ Merchant landing v43 + · content + · 0d881cd

diff --git a/configs/shared/landing-stack-versions.json b/configs/shared/landing-stack-versions.json new file mode 100644 index 0000000..a453057 --- /dev/null +++ b/configs/shared/landing-stack-versions.json @@ -0,0 +1,79 @@ +{ + "updated_iso": "2026-09-16T23:19:33+02:00", + "updated_human": "2026-09-16 23:19 CEST", + "ctr": "goa-regio-ng", + "ssh": "hernani@192.168.100.95", + "packages": { + "libeufin-bank": { + "dpkg": "1.6.10-0+trixie", + "display": "1.6.10" + }, + "libeufin-bank-webui": { + "dpkg": "1.6.38-0+trixie", + "display": "1.6.38" + }, + "libeufin-common": { + "dpkg": "1.6.10-0+trixie", + "display": "1.6.10" + }, + "libtalerexchange": { + "dpkg": "1.6.15-0+trixie", + "display": "1.6.15" + }, + "libtalermerchant": { + "dpkg": "1.6.23-0+trixie", + "display": "1.6.23" + }, + "taler-exchange": { + "dpkg": "1.6.15-0+trixie", + "display": "1.6.15" + }, + "taler-exchange-database": { + "dpkg": "1.6.15-0+trixie", + "display": "1.6.15" + }, + "taler-exchange-offline": { + "dpkg": "1.6.15-0+trixie", + "display": "1.6.15" + }, + "taler-merchant": { + "dpkg": "1.6.23-0+trixie", + "display": "1.6.23" + }, + "taler-merchant-webui": { + "dpkg": "1.6.38-0+trixie", + "display": "1.6.38" + }, + "taler-terms-generator": { + "dpkg": "1.6.15-0+trixie", + "display": "1.6.15" + } + }, + "sites": { + "bank": { + "packages": { + "libeufin-bank": "1.6.10", + "libeufin-common": "1.6.10", + "libeufin-bank-webui": "1.6.38" + } + }, + "exchange": { + "packages": { + "taler-exchange": "1.6.15", + "libtalerexchange": "1.6.15", + "taler-exchange-database": "1.6.15", + "taler-exchange-offline": "1.6.15", + "taler-terms-generator": "1.6.15" + } + }, + "merchant": { + "packages": { + "taler-merchant": "1.6.23", + "taler-merchant-webui": "1.6.38", + "libtalermerchant": "1.6.23", + "libtalerexchange": "1.6.15", + "taler-terms-generator": "1.6.15" + } + } + } +} diff --git a/configs/shared/landing-stack.json b/configs/shared/landing-stack.json new file mode 100644 index 0000000..0ab1749 --- /dev/null +++ b/configs/shared/landing-stack.json @@ -0,0 +1,35 @@ +{ + "note": "Stack-bar package lists per landing. Versions filled by scripts/taler-landing/stamp-landing-stack.sh from live CTR dpkg. Only hard link = vanilla branch.", + "vanilla_href": "https://git.taler.net/taler-deployment.git/tree/?h=dev/hernani/goa-regio-ng-vanilla", + "vanilla_title": "taler-deployment branch dev/hernani/goa-regio-ng-vanilla", + "sites": { + "bank": { + "title": "Taler software on this bank service (goa-regio-ng vanilla)", + "packages": [ + "libeufin-bank", + "libeufin-common", + "libeufin-bank-webui" + ] + }, + "exchange": { + "title": "Taler software on this exchange service (goa-regio-ng vanilla)", + "packages": [ + "taler-exchange", + "libtalerexchange", + "taler-exchange-database", + "taler-exchange-offline", + "taler-terms-generator" + ] + }, + "merchant": { + "title": "Taler software on this merchant service (goa-regio-ng vanilla)", + "packages": [ + "taler-merchant", + "taler-merchant-webui", + "libtalermerchant", + "libtalerexchange", + "taler-terms-generator" + ] + } + } +} diff --git a/configs/shared/landing-version.json b/configs/shared/landing-version.json index 722ff4e..59af0fd 100644 --- a/configs/shared/landing-version.json +++ b/configs/shared/landing-version.json @@ -2,34 +2,34 @@ "note": "Per-site landing content versions (start 42). Bump: scripts/taler-landing/stamp-landing-version.sh --bump bank|exchange|merchant", "sites": { "bank": { - "version": 44, + "version": 45, "label": "Bank landing", "path": "configs/bank-landing", - "updated_iso": "2026-07-17T18:31:49+02:00", - "updated_human": "2026-07-17 18:31 CEST", - "commit": "689ee595e08f3880c1e4068aac8e972574e20d21", - "commit_short": "689ee59", - "repo_commit_url": "https://git.hacktivism.ch/hernani/koopa-admin-log/commit/689ee595e08f3880c1e4068aac8e972574e20d21" + "updated_iso": "2026-09-16T23:19:33+02:00", + "updated_human": "2026-09-16 23:19 CEST", + "commit": "0d881cd726da8d298c98114678253e3cbb7aa5e6", + "commit_short": "0d881cd", + "repo_commit_url": "https://git.hacktivism.ch/hernani/koopa-admin-log/commit/0d881cd726da8d298c98114678253e3cbb7aa5e6" }, "exchange": { - "version": 43, + "version": 44, "label": "Exchange landing", "path": "configs/exchange-landing", - "updated_iso": "2026-07-17T18:33:13+02:00", - "updated_human": "2026-07-17 18:33 CEST", - "commit": "d0594cb44ffb1bec2e009f6812167cbbf216eda9", - "commit_short": "d0594cb", - "repo_commit_url": "https://git.hacktivism.ch/hernani/koopa-admin-log/commit/d0594cb44ffb1bec2e009f6812167cbbf216eda9" + "updated_iso": "2026-09-16T23:19:33+02:00", + "updated_human": "2026-09-16 23:19 CEST", + "commit": "0d881cd726da8d298c98114678253e3cbb7aa5e6", + "commit_short": "0d881cd", + "repo_commit_url": "https://git.hacktivism.ch/hernani/koopa-admin-log/commit/0d881cd726da8d298c98114678253e3cbb7aa5e6" }, "merchant": { - "version": 42, + "version": 43, "label": "Merchant landing", "path": "configs/merchant-landing", - "updated_iso": "2026-07-17T18:27:48+02:00", - "updated_human": "2026-07-17 18:27 CEST", - "commit": "37ea4bb8396691e3ea69ae61374e6623bf6c5ea7", - "commit_short": "37ea4bb", - "repo_commit_url": "https://git.hacktivism.ch/hernani/koopa-admin-log/commit/37ea4bb8396691e3ea69ae61374e6623bf6c5ea7" + "updated_iso": "2026-09-16T23:19:33+02:00", + "updated_human": "2026-09-16 23:19 CEST", + "commit": "0d881cd726da8d298c98114678253e3cbb7aa5e6", + "commit_short": "0d881cd", + "repo_commit_url": "https://git.hacktivism.ch/hernani/koopa-admin-log/commit/0d881cd726da8d298c98114678253e3cbb7aa5e6" } } } diff --git a/host/caddy/Caddyfile b/host/caddy/Caddyfile index 70d8b08..28800ea 100644 --- a/host/caddy/Caddyfile +++ b/host/caddy/Caddyfile @@ -204,6 +204,19 @@ bank.hacktivism.ch { } } + # FP pattern (francpaysan-admin-log files/caddy/Caddyfile @wdGet): + # GET withdrawal-operation → landing :9013 → demo-api :19096 + # (injects suggested_exchange / required_exchange for Android/iOS) + @wdGet { + method GET + path /taler-integration/withdrawal-operation/* + } + handle @wdGet { + reverse_proxy 127.0.0.1:9013 { + import proxy_public + } + } + reverse_proxy 127.0.0.1:9012 { import proxy_public } diff --git a/scripts/caddy/Caddyfile.koopa-live-wdGet b/scripts/caddy/Caddyfile.koopa-live-wdGet new file mode 100644 index 0000000..9339cc0 --- /dev/null +++ b/scripts/caddy/Caddyfile.koopa-live-wdGet @@ -0,0 +1,354 @@ +# Internal only (not in the browser URL): +# 9010 merchant API | 9011 exchange API | 9012 bank API +# 9013 bank landing | 9014 exchange landing | 9015 merchant landing +# 9020 castopod | 9021 bonfire | 9022 prime | 9023 bt | 9024 forgejo | 9025 paivana | 9026 lemmy | 9027 decidim | 9200 forgejo-ssh +# 9090 tops ng1 | 9091 tops ng2 | 9092 tops ng3 +{ + email info+koopa@hacktivism.ch + http_port 9000 + https_port 9001 + auto_https disable_redirects + # Caddy listens on 9001 behind VeciGate/https-proxy :443. + # Default HTTP/3 would send Alt-Svc: h3=":9001" — break public HTTPS on :443. + servers { + protocols h1 h2 + } +} + +(proxy_public) { + header_up Host {host} + header_up X-Forwarded-Port 443 + header_down Location "^https?://[^/]+:90[0-9]{2}(.*)$" "https://{host}$1" +} + +taler.hacktivism.ch { + header Alt-Svc "clear" + + # Public landing first + redir / /intro/ 302 + + handle /intro* { + reverse_proxy 127.0.0.1:9015 { + import proxy_public + } + } + + # surface + aptdeploy HTML (taler.hacktivism.ch only) + handle /taler-monitoring-surface_err { + redir /taler-monitoring-surface_err/ 302 + } + handle /taler-monitoring-surface_err* { + root * /var/www/monitoring-sites/taler.hacktivism.ch + file_server + } + handle /taler-monitoring-surface { + redir /taler-monitoring-surface/ 302 + } + handle /taler-monitoring-surface* { + root * /var/www/monitoring-sites/taler.hacktivism.ch + file_server + } + handle /taler-monitoring-aptdeploy_err { + redir /taler-monitoring-aptdeploy_err/ 302 + } + handle /taler-monitoring-aptdeploy_err* { + root * /var/www/monitoring-sites/taler.hacktivism.ch + file_server + } + handle /taler-monitoring-aptdeploy { + redir /taler-monitoring-aptdeploy/ 302 + } + handle /taler-monitoring-aptdeploy* { + root * /var/www/monitoring-sites/taler.hacktivism.ch + file_server + } + + handle /taler-monitoring-mattermost_err { + redir /taler-monitoring-mattermost_err/ 302 + } + handle /taler-monitoring-mattermost_err* { + root * /var/www/monitoring-sites/taler.hacktivism.ch + file_server + } + handle /taler-monitoring-mattermost { + redir /taler-monitoring-mattermost/ 302 + } + handle /taler-monitoring-mattermost* { + root * /var/www/monitoring-sites/taler.hacktivism.ch + file_server + } + + handle /taler-monitoring-mail_err { + redir /taler-monitoring-mail_err/ 302 + } + handle /taler-monitoring-mail_err* { + root * /var/www/monitoring-sites/taler.hacktivism.ch + file_server + } + handle /taler-monitoring-mail { + redir /taler-monitoring-mail/ 302 + } + handle /taler-monitoring-mail* { + root * /var/www/monitoring-sites/taler.hacktivism.ch + file_server + } + + # taler-monitoring console HTML (host static — not in app containers) + handle /monitoring_err { + redir /monitoring_err/ 302 + } + handle /monitoring_err* { + root * /var/www/monitoring-sites/taler.hacktivism.ch + file_server + } + handle /monitoring { + redir /monitoring/ 302 + } + handle /monitoring* { + root * /var/www/monitoring-sites/taler.hacktivism.ch + file_server + } + + # SPA: /webui → /webui/ + redir /webui /webui/ 302 + + # Merchant API + WebUI (nginx :9010 → unix socket) + reverse_proxy https://127.0.0.1:9010 { + transport http { + tls_insecure_skip_verify + } + import proxy_public + } +} + +exchange.hacktivism.ch { + header Alt-Svc "clear" + + # Public landing first + redir / /intro/ 302 + + handle /intro* { + reverse_proxy 127.0.0.1:9014 { + import proxy_public + } + } + + # taler-monitoring console HTML (host static — not in app containers) + handle /monitoring_err { + redir /monitoring_err/ 302 + } + handle /monitoring_err* { + root * /var/www/monitoring-sites/exchange.hacktivism.ch + file_server + } + handle /monitoring { + redir /monitoring/ 302 + } + handle /monitoring* { + root * /var/www/monitoring-sites/exchange.hacktivism.ch + file_server + } + + reverse_proxy 127.0.0.1:9011 { + import proxy_public + } +} + +bank.hacktivism.ch { + header Alt-Svc "clear" + + # Public landing first + redir / /intro/ 302 + + handle /intro* { + reverse_proxy 127.0.0.1:9013 { + import proxy_public + } + } + + # taler-monitoring console HTML (host static — not in app containers) + handle /monitoring_err { + redir /monitoring_err/ 302 + } + handle /monitoring_err* { + root * /var/www/monitoring-sites/bank.hacktivism.ch + file_server + } + handle /monitoring { + redir /monitoring/ 302 + } + handle /monitoring* { + root * /var/www/monitoring-sites/bank.hacktivism.ch + file_server + } + + # Static terms/privacy on landing nginx :9013 + handle /terms* { + reverse_proxy 127.0.0.1:9013 { + import proxy_public + } + } + handle /privacy* { + reverse_proxy 127.0.0.1:9013 { + import proxy_public + } + } + + # FP pattern (francpaysan-admin-log files/caddy/Caddyfile @wdGet): + # GET withdrawal-operation → landing :9013 → demo-api :19096 + # (injects suggested_exchange / required_exchange for Android/iOS) + @wdGet { + method GET + path /taler-integration/withdrawal-operation/* + } + handle @wdGet { + reverse_proxy 127.0.0.1:9013 { + import proxy_public + } + } + + reverse_proxy 127.0.0.1:9012 { + import proxy_public + } +} + +castopod.hacktivism.ch { + header Alt-Svc "clear" + reverse_proxy 127.0.0.1:9020 { + header_up Host {host} + } +} + +bonfire.hacktivism.ch { + header Alt-Svc "clear" + reverse_proxy 127.0.0.1:9021 { + header_up Host {host} + flush_interval -1 + } +} + +prime.hacktivism.ch { + header Alt-Svc "clear" + reverse_proxy 127.0.0.1:9022 { + header_up Host {host} + flush_interval -1 + } +} + +bt.hacktivism.ch { + header Alt-Svc "clear" + reverse_proxy 127.0.0.1:9023 { + header_up Host {host} + } +} + +# 9024 forgejo HTTP (SSH :9200 host-direct, not via Caddy) +git.hacktivism.ch { + header Alt-Svc "clear" + reverse_proxy 127.0.0.1:9024 { + header_up Host {host} + flush_interval -1 + transport http { + read_timeout 3600s + write_timeout 3600s + } + } +} + +# Taler Operations design previews (static nginx) +tops.ng1.hacktivism.ch { + header Alt-Svc "clear" + reverse_proxy 127.0.0.1:9090 { + header_up Host {host} + } +} + +tops.ng2.hacktivism.ch { + header Alt-Svc "clear" + reverse_proxy 127.0.0.1:9091 { + header_up Host {host} + } +} + +tops.ng3.hacktivism.ch { + header Alt-Svc "clear" + reverse_proxy 127.0.0.1:9092 { + header_up Host {host} + } +} + +# 9025 paivana-httpd (GOA paywall) +paivana.hacktivism.ch { + header Alt-Svc "clear" + reverse_proxy 127.0.0.1:9025 { + header_up Host {host} + header_up X-Forwarded-Port 443 + } +} + +# 9026 lemmy (+ ui/pictrs via internal nginx) +lemmy.hacktivism.ch { + header Alt-Svc "clear" + reverse_proxy 127.0.0.1:9026 { + header_up Host {host} + flush_interval -1 + transport http { + read_timeout 3600s + write_timeout 3600s + } + } +} + +# 9027 decidim (rails) +decidim.hacktivism.ch { + header Alt-Svc "clear" + reverse_proxy 127.0.0.1:9027 { + header_up Host {host} + header_up X-Forwarded-Port 443 + flush_interval -1 + transport http { + read_timeout 3600s + write_timeout 3600s + } + } +} + +http://taler.hacktivism.ch, http://exchange.hacktivism.ch, http://bank.hacktivism.ch, http://castopod.hacktivism.ch, http://bonfire.hacktivism.ch, http://prime.hacktivism.ch, http://bt.hacktivism.ch, http://git.hacktivism.ch, http://paivana.hacktivism.ch, http://tops.ng1.hacktivism.ch, http://tops.ng2.hacktivism.ch, http://tops.ng3.hacktivism.ch, http://lemmy.hacktivism.ch, http://decidim.hacktivism.ch { + handle /.well-known/acme-challenge/* { + root * /var/www/acme + file_server + } + handle { + redir https://{host}{uri} permanent + } +} + +# --- LLM Lightweight Monitoring (applied 2026-09-05T10:08:03+02:00) --- +monitoring.hacktivism.ch { + header Alt-Svc "clear" + root * /var/www/monitoring-sites/monitoring.hacktivism.ch + file_server + try_files {path} /index.html +} + +# ngi0-landing dossiers test sites (static) — numbering like tops.ng* +dossiers.ngi.hacktivism.ch { + header Alt-Svc "clear" + root * /var/www/monitoring-sites/dossiers.ngi.hacktivism.ch + file_server + try_files {path} /index.html +} + +dossiers.2.ngi.hacktivism.ch { + header Alt-Svc "clear" + root * /var/www/monitoring-sites/dossiers.2.ngi.hacktivism.ch + file_server + try_files {path} /index.html +} + +dossiers.3.ngi.hacktivism.ch { + header Alt-Svc "clear" + root * /var/www/monitoring-sites/dossiers.3.ngi.hacktivism.ch + file_server + try_files {path} /index.html +} diff --git a/scripts/caddy/README.md b/scripts/caddy/README.md index 362254f..4d1fc8c 100644 --- a/scripts/caddy/README.md +++ b/scripts/caddy/README.md @@ -10,6 +10,24 @@ sudo caddy validate --config /etc/caddy/Caddyfile sudo systemctl reload caddy ``` +Preferred live path: edit `~/koopa-caddy/Caddyfile`, then `sudo ~/bin/caddy-apply` +(copies → `/etc/caddy/Caddyfile`, validate, `systemctl reload caddy`). + +## GOA bank @wdGet (FP Android withdraw) — full replace + +Live snapshot + `@wdGet` (GET Integration → `:9013` → demo-api): + +- `Caddyfile.koopa-live-wdGet` — whole-file replacement (based on live koopa, not admin-log mirror) +- `apply-goa-wdGet-full-replace.sh` — replaces `~/koopa-caddy/Caddyfile` **and** `/etc/caddy/Caddyfile`, reload, smoke + +```bash +# on koopa (root) — scp both files first if no admin-log checkout: +sudo bash /tmp/goa-fp-port/caddy/apply-goa-wdGet-full-replace.sh \ + /tmp/goa-fp-port/caddy/Caddyfile.koopa-live-wdGet +``` + +FP SoT: `$HOME/git/admin-logs/taler/francpaysan-admin-log` `files/caddy/Caddyfile` `@wdGet`. + ## dossiers.ngi* — basic_auth (htaccess-in-front) Caddy **ignores** Apache `.htaccess`. Use: diff --git a/scripts/caddy/apply-goa-wdGet-full-replace.sh b/scripts/caddy/apply-goa-wdGet-full-replace.sh new file mode 100755 index 0000000..b61bb4e --- /dev/null +++ b/scripts/caddy/apply-goa-wdGet-full-replace.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash +# Full-replace koopa Caddyfile with live snapshot + FP @wdGet (GOA Android withdraw). +# +# Replaces BOTH: +# /home/hernani/koopa-caddy/Caddyfile (runtime SoT for ~/bin/caddy-apply) +# /etc/caddy/Caddyfile (active config) +# then validates + systemctl reload caddy. +# +# On koopa (root): +# sudo bash /path/to/apply-goa-wdGet-full-replace.sh +# # or with explicit file: +# sudo bash …/apply-goa-wdGet-full-replace.sh /path/to/Caddyfile.koopa-live-wdGet +# +# FP pattern: francpaysan-admin-log files/caddy/Caddyfile @wdGet +# Ship file: scripts/caddy/Caddyfile.koopa-live-wdGet (live koopa + @wdGet only) +set -euo pipefail + +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +SRC="${1:-$HERE/Caddyfile.koopa-live-wdGet}" +USER_CADDY="/home/hernani/koopa-caddy/Caddyfile" +ETC_CADDY="/etc/caddy/Caddyfile" +BACKUP_DIR="/etc/caddy" + +if [[ "$(id -u)" -ne 0 ]]; then + echo "ERROR: run as root: sudo bash $0${1:+ $1}" >&2 + exit 1 +fi + +[[ -f "$SRC" ]] || { echo "ERROR: missing source: $SRC" >&2; exit 1; } +[[ -d "$(dirname "$USER_CADDY")" ]] || { echo "ERROR: missing dir: $(dirname "$USER_CADDY")" >&2; exit 1; } +[[ -f "$ETC_CADDY" ]] || { echo "ERROR: missing $ETC_CADDY" >&2; exit 1; } +command -v caddy >/dev/null || { echo "ERROR: caddy not in PATH" >&2; exit 1; } + +if ! grep -q '@wdGet' "$SRC"; then + echo "ERROR: source lacks @wdGet — refusing replace: $SRC" >&2 + exit 1 +fi +if ! grep -q 'path /taler-integration/withdrawal-operation/\*' "$SRC"; then + echo "ERROR: source lacks Integration @wdGet path — refusing: $SRC" >&2 + exit 1 +fi + +ts=$(date +%Y%m%d-%H%M%S) +bak_etc="${BACKUP_DIR}/Caddyfile.bak-goa-wdGet-${ts}" +bak_user="${USER_CADDY}.bak-goa-wdGet-${ts}" + +cp -a "$ETC_CADDY" "$bak_etc" +echo "backup etc: $bak_etc" +if [[ -f "$USER_CADDY" ]]; then + cp -a "$USER_CADDY" "$bak_user" + echo "backup user: $bak_user" +fi + +install -o hernani -g hernani -m 644 "$SRC" "$USER_CADDY" +echo "wrote: $USER_CADDY" + +install -o root -g caddy -m 644 "$SRC" "$ETC_CADDY" 2>/dev/null \ + || install -o root -g root -m 644 "$SRC" "$ETC_CADDY" +echo "wrote: $ETC_CADDY" + +echo "validate..." +caddy validate --config "$ETC_CADDY" + +echo "reload..." +systemctl reload caddy +systemctl is-active caddy + +echo "spot-check @wdGet:" +grep -n '@wdGet\|withdrawal-operation\|reverse_proxy 127.0.0.1:9013\|reverse_proxy 127.0.0.1:9012' "$ETC_CADDY" | head -20 + +echo "smoke public Integration (expect suggested_exchange + required_exchange):" +smoke="$(curl -sS --max-time 10 \ + 'https://bank.hacktivism.ch/taler-integration/withdrawal-operation/00000000-0000-0000-0000-000000000001' || true)" +echo "$smoke" +if echo "$smoke" | grep -q 'suggested_exchange' && echo "$smoke" | grep -q 'required_exchange'; then + echo "OK: public Integration injects exchange fields" +else + echo "WARN: public smoke missing exchange fields — check nginx Integration + demo-api :19096" >&2 + exit 1 +fi diff --git a/scripts/caddy/wdGet-bank-snippet.caddy b/scripts/caddy/wdGet-bank-snippet.caddy new file mode 100644 index 0000000..93a1635 --- /dev/null +++ b/scripts/caddy/wdGet-bank-snippet.caddy @@ -0,0 +1,18 @@ +# Paste into live ~/koopa-caddy/Caddyfile inside bank.hacktivism.ch { ... } +# BEFORE the catch-all: reverse_proxy 127.0.0.1:9012 +# Then on koopa: sudo ~/bin/caddy-apply +# +# FP pattern: francpaysan-admin-log files/caddy/Caddyfile @wdGet +# GET Integration → landing :9013 → demo-api :19096 +# (injects suggested_exchange / required_exchange for Android/iOS) +# SoT mirrors: configs/caddy/Caddyfile + host/caddy/Caddyfile + + @wdGet { + method GET + path /taler-integration/withdrawal-operation/* + } + handle @wdGet { + reverse_proxy 127.0.0.1:9013 { + import proxy_public + } + } diff --git a/scripts/taler-bank/README.md b/scripts/taler-bank/README.md index bf18099..063261e 100644 --- a/scripts/taler-bank/README.md +++ b/scripts/taler-bank/README.md @@ -38,24 +38,48 @@ Details + JSON schema: `configs/bank-landing/README.md`. ## Demo withdraw + auto-account API -`demo-withdraw-api.py` listens on **127.0.0.1:19096** (proxied by nginx on the landing): +**FP reference SoT:** `$HOME/git/admin-logs/taler/francpaysan-admin-log` +(`files/testpaysan/demo-withdraw-api.py`, `files/caddy/Caddyfile` `@wdGet`). +GOA ports that pattern: mint `amount`+`exchange_url`, and GET Integration +injects `suggested_exchange` / `required_exchange` (Android/iOS). + +`demo-withdraw-api.py` listens on **127.0.0.1:19096** (in bank CTR; nginx on :9013): | Path | Behaviour | |------|-----------| | `GET /demo-withdraw.json` | Mint one-shot withdraw from shared **`explorer`** pool; write `withdraw.uri` + watch ids | | `GET /auto-account.json` | Public `POST /accounts` with generated **`goa-account-`** user + password containing **pleasechangeme**; **balance GOA:0**; return credentials once | +| `GET /taler-integration/withdrawal-operation/*` | Proxy libeufin + inject `suggested_exchange`/`required_exchange` | -Install / restart: +**Public path (Android):** Caddy `@wdGet` (GET only) → landing **:9013** → nginx → **:19096**. +POST / other Integration stays Caddy → **:9012** (raw libeufin). +FP stage proxies `@wdGet` straight to host `:19096`; GOA keeps 19096 CTR-only +and routes via published 9013 instead. + +Install / restart (host, podman → `taler-hacktivism-bank`): ```bash ./install-demo-withdraw-api.sh # Public checks: curl -sS https://bank.hacktivism.ch/intro/demo-withdraw.json | head curl -sS https://bank.hacktivism.ch/intro/auto-account.json | head # creates a real account +# After Caddy @wdGet + nginx Integration are live: +WID=… # from demo-withdraw.json +curl -sS "https://bank.hacktivism.ch/taler-integration/withdrawal-operation/$WID" \ + | python3 -c 'import sys,json; d=json.load(sys.stdin); print(d.get("suggested_exchange"), d.get("required_exchange"))' ``` Requires **python3** in the bank container. Env: `BANK_URL`, `BANK_USER`/`BANK_PASS` -(or `/root/bank-explorer-password.txt`), `AMOUNT` (default `GOA:10` for shared withdraws). +(or `/root/bank-explorer-password.txt`), `AMOUNT` (default `GOA:10`), +`EXCHANGE_URL` (default `https://exchange.hacktivism.ch/`). + +**Caddy (root on koopa — you apply):** insert `@wdGet` before the bank catch-all +`reverse_proxy 127.0.0.1:9012` in `~/koopa-caddy/Caddyfile` (SoT: +`configs/caddy/Caddyfile` / `host/caddy/Caddyfile`; paste-ready snippet: +`scripts/caddy/wdGet-bank-snippet.caddy`, also copied live as +`~/koopa-caddy/wdGet-bank-snippet.caddy`), then `sudo ~/bin/caddy-apply`. +Caddy alone is not enough — demo-api + nginx Integration must be installed +via `./install-demo-withdraw-api.sh` first. ### Auto-confirm (explorer only) diff --git a/scripts/taler-bank/demo-withdraw-api.py b/scripts/taler-bank/demo-withdraw-api.py index f31243e..292191f 100755 --- a/scripts/taler-bank/demo-withdraw-api.py +++ b/scripts/taler-bank/demo-withdraw-api.py @@ -34,6 +34,10 @@ BANK = os.environ.get("BANK_URL", "http://127.0.0.1:9012").rstrip("/") BANK_PUBLIC = os.environ.get("BANK_PUBLIC", "https://bank.hacktivism.ch").rstrip("/") USER = os.environ.get("BANK_USER", "explorer") AMOUNT = os.environ.get("AMOUNT", "GOA:10") +EXCHANGE = ( + os.environ.get("EXCHANGE_URL", "https://exchange.hacktivism.ch/").rstrip("/") + + "/" +) LANDING = Path(os.environ.get("LANDING_DIR", "/var/www/bank-landing")) LISTEN = ("127.0.0.1", int(os.environ.get("DEMO_WITHDRAW_PORT", "19096"))) @@ -112,7 +116,7 @@ def http_json(method: str, url: str, body=None, headers=None, auth=None): return e.code, {"raw": raw[:500]} -def mint_withdraw() -> dict: +def mint_withdraw(amount: str | None = None) -> dict: pw = load_pass() code, tok = http_json( "POST", @@ -123,10 +127,14 @@ def mint_withdraw() -> dict: if code != 200 or not tok.get("access_token"): raise RuntimeError(f"token failed HTTP {code}: {tok}") access = tok["access_token"] + amt = amount or AMOUNT + if ":" not in str(amt): + amt = f"GOA:{amt}" + # FP pattern: amount + exchange_url (not suggested_amount alone) code, wd = http_json( "POST", f"{BANK}/accounts/{USER}/withdrawals", - {"suggested_amount": AMOUNT}, + {"amount": amt, "exchange_url": EXCHANGE}, headers={"Authorization": f"Bearer {access}"}, ) if code not in (200, 201): @@ -142,7 +150,7 @@ def mint_withdraw() -> dict: uri = normalize_taler_withdraw_uri(str(uri).strip()) LANDING.mkdir(parents=True, exist_ok=True) (LANDING / "withdraw.uri").write_text(uri + "\n") - (LANDING / "withdraw.amount").write_text(AMOUNT + "\n") + (LANDING / "withdraw.amount").write_text(amt + "\n") (LANDING / "withdraw.created").write_text( time.strftime("%Y-%m-%dT%H:%MZ", time.gmtime()) + "\n" ) @@ -151,7 +159,8 @@ def mint_withdraw() -> dict: "ok": True, "taler_withdraw_uri": uri, "withdrawal_id": wid, - "amount": AMOUNT, + "amount": amt, + "exchange_url": EXCHANGE, "pool_account": USER, "taler_integration_base": f"{BANK_PUBLIC}/taler-integration/", "hint": "Open in GNU Taler Wallet (iOS/Android/desktop). No bank registration.", @@ -301,15 +310,54 @@ class Handler(BaseHTTPRequestHandler): self._cors() self.end_headers() - def do_GET(self): - path = self.path.split("?", 1)[0] + def _proxy_wd(self, parsed): + """GET withdrawal-operation: inject suggested/required exchange (Android/iOS).""" + q = ("?" + parsed.query) if parsed.query else "" + url = f"{BANK}{parsed.path}{q}" + ctx = ssl.create_default_context() + req = urllib.request.Request(url, method="GET") try: + with urllib.request.urlopen(req, context=ctx, timeout=70) as r: + raw = r.read() + status = r.status + except urllib.error.HTTPError as e: + raw = e.read() + status = e.code + except Exception as e: + raw = json.dumps({"ok": False, "error": str(e)}).encode() + status = 502 + try: + body = json.loads(raw.decode() or "{}") + if isinstance(body, dict): + body.setdefault("suggested_exchange", EXCHANGE) + body.setdefault("required_exchange", EXCHANGE) + raw = json.dumps(body).encode() + except Exception: + pass + self.send_response(status) + self._cors() + self.send_header("Content-Type", "application/json") + self.send_header("Content-Length", str(len(raw))) + self.end_headers() + self.wfile.write(raw) + + def do_GET(self): + from urllib.parse import parse_qs, urlparse + + parsed = urlparse(self.path) + path = parsed.path + qs = parse_qs(parsed.query) + try: + if path.startswith("/taler-integration/withdrawal-operation/"): + self._proxy_wd(parsed) + return if path in ( "/", "/demo-withdraw.json", "/intro/demo-withdraw.json", ): - body = mint_withdraw() + amt = (qs.get("amount") or qs.get("n") or [None])[0] + body = mint_withdraw(amt) elif path in ( "/auto-account.json", "/intro/auto-account.json", diff --git a/scripts/taler-bank/install-demo-withdraw-api.sh b/scripts/taler-bank/install-demo-withdraw-api.sh index c4cd2e8..66b5a37 100755 --- a/scripts/taler-bank/install-demo-withdraw-api.sh +++ b/scripts/taler-bank/install-demo-withdraw-api.sh @@ -14,61 +14,86 @@ podman exec -u root "$CTR" chmod 755 \ /usr/local/bin/auto-confirm-withdrawals.sh \ /usr/local/bin/refresh-demo-withdraw.sh -# nginx: proxy demo-withdraw.json -NGX=/etc/nginx/sites-available/bank-landing +# nginx: ensure demo-withdraw / auto-account / Integration → :19096 +# FP pattern: Caddy @wdGet GET → :9013 → this location → demo-api +# (single in-CTR python; no nested podman) podman exec -u root "$CTR" bash -lc ' set -e -f=/etc/nginx/sites-available/bank-landing -if ! grep -q demo-withdraw.json "$f"; then - # insert before location /intro/ - python3 - </dev/null || true; done -sleep 0.3 +sleep 1 +ps -eo pid=,args= | awk "/demo-withdraw-api\\.py/ && !/awk/ {print \$1}" | while read p; do kill -9 \$p 2>/dev/null || true; done +sleep 0.5 nohup python3 /usr/local/bin/demo-withdraw-api.py \ >>/var/log/demo-withdraw-api.log 2>&1 &2 + tail -20 /var/log/demo-withdraw-api.log >&2 || true + exit 1 +} # stop auto-confirm by pid ps -eo pid=,args= | awk "/auto-confirm-withdrawals\\.sh --loop/ && !/awk/ {print \$1}" | while read p; do kill \$p 2>/dev/null || true; done sleep 0.5 diff --git a/scripts/taler-bank/landing-stats.sh b/scripts/taler-bank/landing-stats.sh index 2eb22cd..a088e65 100755 --- a/scripts/taler-bank/landing-stats.sh +++ b/scripts/taler-bank/landing-stats.sh @@ -203,8 +203,11 @@ if [ -n "$ADMIN_TOKEN" ]; then u=$(printf '%s' "$line" | sed -n 's/.*"username"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p') [ -n "$u" ] && echo "$u" done >"$WORKDIR/usernames.txt" || true - ACCOUNTS_N=$(grep -cve '^\s*$' "$WORKDIR/usernames.txt" 2>/dev/null || echo 0) - ACCOUNTS_USERS=$(grep -Eve '^(admin|exchange)$' "$WORKDIR/usernames.txt" 2>/dev/null | grep -cve '^\s*$' || echo 0) + # grep -c exits 1 on zero matches; do not `|| echo 0` (would print 0\n0 and break JSON) + ACCOUNTS_N=$(grep -cve '^\s*$' "$WORKDIR/usernames.txt" 2>/dev/null || true) + ACCOUNTS_N=${ACCOUNTS_N:-0} + ACCOUNTS_USERS=$(grep -Eve '^(admin|exchange)$' "$WORKDIR/usernames.txt" 2>/dev/null | grep -cve '^\s*$' || true) + ACCOUNTS_USERS=${ACCOUNTS_USERS:-0} else echo "$BANK_USER" >"$WORKDIR/usernames.txt" ACCOUNTS_N=1 @@ -323,9 +326,11 @@ sort -t$'\t' -k2,2nr "$WORKDIR/all-in.tsv" -o "$WORKDIR/all-in-sorted.tsv" 2>/de || cp "$WORKDIR/all-in.tsv" "$WORKDIR/all-in-sorted.tsv" # Unique reserves = individual wallet withdraws (each wallet reserve_pub) -WALLETS_N=$(awk -F'\t' '$5!=""{print $5}' "$WORKDIR/all-wd-sorted.tsv" | sort -u | grep -cve '^\s*$' || echo 0) +WALLETS_N=$(awk -F'\t' '$5!=""{print $5}' "$WORKDIR/all-wd-sorted.tsv" | sort -u | grep -cve '^\s*$' || true) +WALLETS_N=${WALLETS_N:-0} # Accounts that funded at least one withdraw -ACCOUNTS_WITH_WD=$(awk -F'\t' '$4!=""{print $4}' "$WORKDIR/all-wd-sorted.tsv" | sort -u | grep -cve '^\s*$' || echo 0) +ACCOUNTS_WITH_WD=$(awk -F'\t' '$4!=""{print $4}' "$WORKDIR/all-wd-sorted.tsv" | sort -u | grep -cve '^\s*$' || true) +ACCOUNTS_WITH_WD=${ACCOUNTS_WITH_WD:-0} # Aggregates NOW=$(now_unix) diff --git a/scripts/taler-exchange/landing-stats-exchange.sh b/scripts/taler-exchange/landing-stats-exchange.sh index 987e631..6c77248 100644 --- a/scripts/taler-exchange/landing-stats-exchange.sh +++ b/scripts/taler-exchange/landing-stats-exchange.sh @@ -2,7 +2,8 @@ # Run INSIDE taler-hacktivism-exchange-ansible. # Writes /var/www/exchange-landing/stats.json # -# Data lives in Postgres DB taler-exchange, schema exchange.* +# Data lives in Postgres (DB name varies: vanilla regio-ng uses "exchange"; +# older hacktivism images use "taler-exchange"). Schema is exchange.*. # (reserves, reserves_in, known_coins, withdraw, denominations, …) # # IMPORTANT: never use psql -F$'\t' -v ON_ERROR_STOP=1 @@ -16,7 +17,38 @@ 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}" +# Resolve DB: EXCHANGE_DB env → CONFIG=postgres:///NAME in secret conf → fallbacks +_resolve_exchange_db() { + if [ -n "${EXCHANGE_DB:-}" ]; then + printf '%s' "$EXCHANGE_DB" + return + fi + local conf c name + for conf in \ + /etc/taler-exchange/secrets/exchange-db.secret.conf \ + /etc/taler/secrets/exchange-db.secret.conf; do + if [ -r "$conf" ]; then + c=$(grep -E '^[[:space:]]*CONFIG[[:space:]]*=' "$conf" | head -1 | sed 's/^[^=]*=[[:space:]]*//') + # postgres:///dbname or postgres://user@/dbname + name=$(printf '%s' "$c" | sed -n 's#.*postgres://[^/]*/\([^?]*\).*#\1#p') + if [ -n "$name" ]; then + printf '%s' "$name" + return + fi + fi + done + for name in exchange taler-exchange; do + if [ -d "/var/lib/postgresql" ] || command -v psql >/dev/null 2>&1; then + if runuser -u postgres -- psql -d "$name" -At -c 'SELECT 1' >/dev/null 2>&1 \ + || su -s /bin/bash postgres -c "psql -d $name -At -c 'SELECT 1'" >/dev/null 2>&1; then + printf '%s' "$name" + return + fi + fi + done + printf '%s' "taler-exchange" +} +DB="$(_resolve_exchange_db)" BASE_URL="${EXCHANGE_BASE_URL:-https://exchange.hacktivism.ch}" ERRLOG="${LANDING_STATS_ERRLOG:-/var/log/landing-stats-exchange.err}" mkdir -p "$LANDING_DIR" @@ -253,7 +285,7 @@ cat >"$TMP" <>"$LOG_DIR/${label}.log" 2>&1 local ec=$? set -e diff --git a/scripts/taler-landing/collect_bank_stats.py b/scripts/taler-landing/collect_bank_stats.py index b8d4398..42f2c56 100755 --- a/scripts/taler-landing/collect_bank_stats.py +++ b/scripts/taler-landing/collect_bank_stats.py @@ -130,6 +130,11 @@ def get_token(bank: str, user: str, password: str) -> str: def list_all_accounts(bank: str, token: str, delta: int) -> List[str]: """List accounts; page with start if needed.""" + # libeufin-bank: |delta| must be in 1..1024 (same floor as Param 'limit') + if delta == 0: + delta = -1024 + elif abs(delta) > 1024: + delta = -1024 if delta < 0 else 1024 names: List[str] = [] seen: Set[str] = set() start: Optional[int] = None @@ -213,9 +218,10 @@ def iter_transactions( pages = 0 safety = max_pages if max_pages > 0 else 10_000 + page = min(abs(page), 1024) or 1024 while pages < safety: pages += 1 - q = f"delta=-{abs(page)}" + q = f"delta=-{page}" if start is not None: q += f"&start={start}" code, data, raw = http_json( @@ -364,7 +370,7 @@ def main() -> int: ap.add_argument( "--accounts-delta", type=int, - default=int(env("ACCOUNTS_DELTA", "-10000") or "-10000"), + default=int(env("ACCOUNTS_DELTA", "-1024") or "-1024"), ) 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")) diff --git a/scripts/taler-landing/deploy-landings.sh b/scripts/taler-landing/deploy-landings.sh index 28aeb2b..71ab7f6 100755 --- a/scripts/taler-landing/deploy-landings.sh +++ b/scripts/taler-landing/deploy-landings.sh @@ -3,9 +3,11 @@ # listens on 9014 (exchange) / 9015 (merchant). # # Port map (wire these in Caddy / podman -p): -# 9013 bank landing (already on taler-hacktivism-bank) -# 9014 exchange landing (taler-hacktivism-exchange-ansible) -# 9015 merchant landing (taler-hacktivism) +# 9013 bank landing +# 9014 exchange landing +# 9015 merchant landing +# Unified vanilla CTR: set LANDING_CTR=goa-regio-ng (default) so all three +# landings land in the same container publishing 9010-9015. # # Run on koopa (host) with podman access. set -euo pipefail @@ -18,9 +20,10 @@ SRC_BANK="${SRC_BANK:-$ROOT/configs/bank-landing}" SRC_QR="${SRC_QR:-$ROOT/configs/bank-landing/qrcode.min.js}" SRC_GOA_AMT="${SRC_GOA_AMT:-$ROOT/configs/shared/goa-amount.js}" -C_EX=taler-hacktivism-exchange-ansible -C_MER=taler-hacktivism -C_BANK="${C_BANK:-taler-hacktivism-bank}" +LANDING_CTR="${LANDING_CTR:-goa-regio-ng}" +C_EX="${C_EX:-$LANDING_CTR}" +C_MER="${C_MER:-$LANDING_CTR}" +C_BANK="${C_BANK:-$LANDING_CTR}" # Prefer per-landing copy, then shared (keeps bank/exchange/merchant in sync) resolve_goa_amount() { @@ -104,8 +107,8 @@ podman exec "$C_MER" curl -sS -m 3 -o /dev/null -w "merchant_landing=%{http_code echo echo "PORTS TO WIRE (host → Caddy / firewall / podman -p):" -echo " 9013 bank landing (taler-hacktivism-bank) already published" -echo " 9014 exchange landing (taler-hacktivism-exchange-ansible) NEEDS -p 9014:9014" -echo " 9015 merchant landing (taler-hacktivism) NEEDS -p 9015:9015" +echo " 9013 bank landing ($C_BANK)" +echo " 9014 exchange landing ($C_EX)" +echo " 9015 merchant landing ($C_MER)" echo echo "If host curl to :9014/:9015 fails, republish pasta ports (commit+replace) — see README." diff --git a/scripts/taler-landing/profiles/goa.env b/scripts/taler-landing/profiles/goa.env index 08c357a..8ab7ffa 100644 --- a/scripts/taler-landing/profiles/goa.env +++ b/scripts/taler-landing/profiles/goa.env @@ -1,8 +1,8 @@ -# Stack profile: GOA / hacktivism +# Stack profile: GOA / goa-regio-ng vanilla (9010-9015) # systemd EnvironmentFile: KEY=value only (no export) -BANK_CTR=taler-hacktivism-bank -EX_CTR=taler-hacktivism-exchange-ansible -MER_CTR=taler-hacktivism +BANK_CTR=goa-regio-ng +EX_CTR=goa-regio-ng +MER_CTR=goa-regio-ng BANK_URL=http://127.0.0.1:9012 BANK_PUBLIC_URL=https://bank.hacktivism.ch EXCHANGE_CONFIG_URL=https://exchange.hacktivism.ch/config @@ -15,4 +15,4 @@ COLLECT_EXCHANGE=1 COLLECT_MERCHANT=1 COLLECT_RESOURCES=1 PUBLISH_PODMAN=1 -STATS_SOURCE_LABEL="host collect_bank_stats.py goa hacktivism" +STATS_SOURCE_LABEL="host collect_bank_stats.py goa regio-ng vanilla" diff --git a/scripts/taler-landing/stamp-landing-stack.sh b/scripts/taler-landing/stamp-landing-stack.sh new file mode 100644 index 0000000..a0073ef --- /dev/null +++ b/scripts/taler-landing/stamp-landing-stack.sh @@ -0,0 +1,179 @@ +#!/usr/bin/env bash +# Fill landing stack-bar package versions from live CTR dpkg (not hand-edited HTML). +# +# Reads: configs/shared/landing-stack.json (package lists + vanilla href) +# Writes: configs/{bank,exchange,merchant}-landing/index.html ( block) +# configs/shared/landing-stack-versions.json (last dpkg snapshot) +# +# Usage (from koopa-admin-log root, laptop or koopa): +# ./scripts/taler-landing/stamp-landing-stack.sh +# ./scripts/taler-landing/stamp-landing-stack.sh --ctr goa-regio-ng --ssh hernani@192.168.100.95 +# ./scripts/taler-landing/stamp-landing-stack.sh bank exchange +# +# After this, bump footer dates with: +# ./scripts/taler-landing/stamp-landing-version.sh --bump --all +# Then deploy (unified vanilla CTR): +# LANDING_CTR=goa-regio-ng ./scripts/taler-landing/deploy-landings.sh +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/../.." && pwd)" +STACK_FILE="$ROOT/configs/shared/landing-stack.json" +VERS_OUT="$ROOT/configs/shared/landing-stack-versions.json" + +CTR="${LANDING_CTR:-goa-regio-ng}" +SSH_HOST="${LANDING_SSH:-hernani@192.168.100.95}" +USE_SSH=1 +sites=() + +while [ $# -gt 0 ]; do + case "$1" in + --ctr) CTR="${2:-}"; shift 2 ;; + --ssh) SSH_HOST="${2:-}"; USE_SSH=1; shift 2 ;; + --local) USE_SSH=0; shift ;; + bank|exchange|merchant) sites+=("$1"); shift ;; + -h|--help) + sed -n '2,20p' "$0" + exit 0 + ;; + *) echo "unknown arg: $1" >&2; exit 2 ;; + esac +done + +if [ "${#sites[@]}" -eq 0 ]; then + sites=(bank exchange merchant) +fi + +[ -f "$STACK_FILE" ] || { echo "missing $STACK_FILE" >&2; exit 1; } + +# Collect unique package names from JSON for the selected sites +mapfile -t PKGS < <(python3 - "$STACK_FILE" "${sites[@]}" <<'PY' +import json, sys +data = json.load(open(sys.argv[1], encoding="utf-8")) +sites = sys.argv[2:] +seen = [] +for s in sites: + for p in data["sites"][s]["packages"]: + if p not in seen: + seen.append(p) +print("\n".join(seen)) +PY +) + +if [ "${#PKGS[@]}" -eq 0 ]; then + echo "no packages configured" >&2 + exit 1 +fi + +pkg_args=$(printf '%q ' "${PKGS[@]}") +query_cmd="podman exec $(printf '%q' "$CTR") dpkg-query -W -f='\${Package}=\${Version}\n' ${pkg_args}" + +if [ "$USE_SSH" = 1 ]; then + echo "dpkg ← ${SSH_HOST} ctr=${CTR}" + # shellcheck disable=SC2086 + raw=$(ssh -o BatchMode=yes -o ConnectTimeout=12 "$SSH_HOST" "podman exec $(printf '%q' "$CTR") dpkg-query -W -f='\${Package}=\${Version}\n' ${pkg_args}") +else + echo "dpkg ← local ctr=${CTR}" + # shellcheck disable=SC2086 + raw=$(podman exec "$CTR" dpkg-query -W -f='${Package}=${Version}\n' "${PKGS[@]}") +fi + +python3 - "$STACK_FILE" "$VERS_OUT" "$ROOT" "$CTR" "$SSH_HOST" "$raw" "${sites[@]}" <<'PY' +import json, re, sys +from pathlib import Path +from datetime import datetime +from zoneinfo import ZoneInfo + +stack_path, vers_out, root, ctr, ssh_host, raw = sys.argv[1:7] +sites = sys.argv[7:] +stack = json.load(open(stack_path, encoding="utf-8")) +tz = ZoneInfo("Europe/Zurich") +now = datetime.now(tz) +iso = now.isoformat(timespec="seconds") +human = now.strftime("%Y-%m-%d %H:%M %Z") + +versions = {} +for line in raw.splitlines(): + line = line.strip() + if not line or "=" not in line: + continue + pkg, ver = line.split("=", 1) + # display upstream-ish: drop Debian revision (1.6.10-0+trixie → 1.6.10) + short = re.sub(r"-[0-9].*$", "", ver) + versions[pkg] = {"dpkg": ver, "display": short} + +missing = [] +for site in sites: + for pkg in stack["sites"][site]["packages"]: + if pkg not in versions: + missing.append(pkg) +if missing: + raise SystemExit(f"dpkg missing packages: {', '.join(sorted(set(missing)))}") + +vanilla_href = stack["vanilla_href"] +vanilla_title = stack.get("vanilla_title", "vanilla") +sep = '·' + +def render_block(site: str) -> str: + meta = stack["sites"][site] + parts = [ + f' ', + f'

', + f' stack{sep}', + f' vanilla', + ] + for pkg in meta["packages"]: + disp = versions[pkg]["display"] + parts.append(f" {sep}") + parts.append(f" {pkg} {disp}") + parts.append("

") + parts.append(" ") + return "\n".join(parts) + "\n" + +site_html = { + "bank": Path(root) / "configs/bank-landing/index.html", + "exchange": Path(root) / "configs/exchange-landing/index.html", + "merchant": Path(root) / "configs/merchant-landing/index.html", +} + +marker_re = re.compile(r"[ \t]*.*?\n?", re.S) +bare_re = re.compile( + r"[ \t]*

]*>.*?

\n?", + re.S, +) + +for site in sites: + path = site_html[site] + text = path.read_text(encoding="utf-8") + block = render_block(site) + if marker_re.search(text): + text = marker_re.sub(block, text, count=1) + elif bare_re.search(text): + text = bare_re.sub(block, text, count=1) + else: + raise SystemExit(f"no stack-bar in {path}") + path.write_text(text, encoding="utf-8") + print(f"stack → {path.relative_to(root)} ({site})") + +out = { + "updated_iso": iso, + "updated_human": human, + "ctr": ctr, + "ssh": ssh_host if ssh_host else None, + "packages": versions, + "sites": { + site: { + "packages": { + pkg: versions[pkg]["display"] + for pkg in stack["sites"][site]["packages"] + } + } + for site in ("bank", "exchange", "merchant") + if site in stack["sites"] + }, +} +Path(vers_out).write_text(json.dumps(out, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") +print(f"versions → {Path(vers_out).relative_to(root)}") +print(f"OK stack stamped: {' '.join(sites)} @ {human}") +PY + +echo "OK stamp-landing-stack: ${sites[*]} ← ${CTR}" diff --git a/scripts/taler-shared/goa-live-backup-upgrade.sh b/scripts/taler-shared/goa-live-backup-upgrade.sh index 1db0a70..6bce47f 100755 --- a/scripts/taler-shared/goa-live-backup-upgrade.sh +++ b/scripts/taler-shared/goa-live-backup-upgrade.sh @@ -3,6 +3,7 @@ # Host: koopa (hernani, podman). No Freigabe-bypass — caller must have Freigabe. # # ./goa-live-backup-upgrade.sh dry-run +# ./goa-live-backup-upgrade.sh backup # stamp only (no apt / no commit) # ./goa-live-backup-upgrade.sh apply # backup + apt + commit + smoke # ./goa-live-backup-upgrade.sh status # @@ -312,6 +313,12 @@ cmd_status() { case "$MODE" in dry-run|dry) cmd_dry_run ;; + backup) + set_status BACKUP_RUNNING + cmd_backup + set_status BACKUP_OK "STAMP=$STAMP" "CURRENT=$ROOT/current" + log "BACKUP_OK" + ;; apply) cmd_apply ;; status) cmd_status ;; -h|--help|help) @@ -319,7 +326,7 @@ case "$MODE" in exit 0 ;; *) - echo "usage: $0 dry-run|apply|status" >&2 + echo "usage: $0 dry-run|backup|apply|status" >&2 exit 2 ;; esac