From 337d253691a9432331c192b43fd4cc76fa472b05 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Hern=C3=A2ni=20Marques?=
Date: Wed, 16 Sep 2026 23:55:47 +0200
Subject: [PATCH] ops: GOA vanilla landings stats wdGet + collect greening
---
configs/bank-landing/index.html | 20 +-
configs/bank-landing/nginx-landing.conf | 10 +
configs/caddy/Caddyfile | 13 +
configs/exchange-landing/index.html | 24 +-
configs/merchant-landing/index.html | 24 +-
configs/shared/landing-stack-versions.json | 79 ++++
configs/shared/landing-stack.json | 35 ++
configs/shared/landing-version.json | 36 +-
host/caddy/Caddyfile | 13 +
scripts/caddy/Caddyfile.koopa-live-wdGet | 354 ++++++++++++++++++
scripts/caddy/README.md | 18 +
scripts/caddy/apply-goa-wdGet-full-replace.sh | 80 ++++
scripts/caddy/wdGet-bank-snippet.caddy | 18 +
scripts/taler-bank/README.md | 30 +-
scripts/taler-bank/demo-withdraw-api.py | 62 ++-
.../taler-bank/install-demo-withdraw-api.sh | 75 ++--
scripts/taler-bank/landing-stats.sh | 13 +-
.../taler-exchange/landing-stats-exchange.sh | 38 +-
.../taler-landing/collect-landing-stats.sh | 10 +-
scripts/taler-landing/collect_bank_stats.py | 10 +-
scripts/taler-landing/deploy-landings.sh | 21 +-
scripts/taler-landing/profiles/goa.env | 10 +-
scripts/taler-landing/stamp-landing-stack.sh | 179 +++++++++
.../taler-shared/goa-live-backup-upgrade.sh | 9 +-
24 files changed, 1074 insertions(+), 107 deletions(-)
create mode 100644 configs/shared/landing-stack-versions.json
create mode 100644 configs/shared/landing-stack.json
create mode 100644 scripts/caddy/Caddyfile.koopa-live-wdGet
create mode 100755 scripts/caddy/apply-goa-wdGet-full-replace.sh
create mode 100644 scripts/caddy/wdGet-bank-snippet.caddy
create mode 100644 scripts/taler-landing/stamp-landing-stack.sh
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