ops: GOA vanilla landings stats wdGet + collect greening

This commit is contained in:
Hernâni Marques 2026-09-16 23:55:47 +02:00
parent 0d881cd726
commit 337d253691
No known key found for this signature in database
GPG key ID: CB5738652768F7E9
24 changed files with 1074 additions and 107 deletions

View file

@ -1118,12 +1118,18 @@
<script src="/intro/qrcode.min.js"></script> <script src="/intro/qrcode.min.js"></script>
</head> </head>
<body data-shop-theme="bank"> <body data-shop-theme="bank">
<p class="stack-bar" role="note" title="Taler software on this bank service (links → git.taler.net tags)"> <!-- stack-bar -->
<p class="stack-bar" role="note" title="Taler software on this bank service (goa-regio-ng vanilla)">
<strong>stack</strong><span class="sep">·</span> <strong>stack</strong><span class="sep">·</span>
<a href="https://git.taler.net/taler/libeufin/commit/ab00480dd9b3be50704fb449b750c764fd4c1de0.html" target="_blank" rel="noopener noreferrer" title="libeufin tag v1.6.6">libeufin-bank&nbsp;1.6.6</a> <a href="https://git.taler.net/taler-deployment.git/tree/?h=dev/hernani/goa-regio-ng-vanilla" target="_blank" rel="noopener noreferrer" title="taler-deployment branch dev/hernani/goa-regio-ng-vanilla">vanilla</a>
<span class="sep">·</span> <span class="sep">·</span>
<a href="https://git.taler.net/taler/libeufin/commit/ab00480dd9b3be50704fb449b750c764fd4c1de0.html" target="_blank" rel="noopener noreferrer" title="libeufin tag v1.6.6">libeufin-common&nbsp;1.6.6</a> libeufin-bank&nbsp;1.6.10
<span class="sep">·</span>
libeufin-common&nbsp;1.6.10
<span class="sep">·</span>
libeufin-bank-webui&nbsp;1.6.38
</p> </p>
<!-- /stack-bar -->
<main> <main>
<header class="hero"> <header class="hero">
<span class="badge">Intergalactic · GOA · no IBAN</span> <span class="badge">Intergalactic · GOA · no IBAN</span>
@ -1558,10 +1564,10 @@ tw run-until-done &amp;&amp; tw balance</pre>
<a href="https://bank.hacktivism.ch/privacy" style="color:#5eead4">/privacy</a>. <a href="https://bank.hacktivism.ch/privacy" style="color:#5eead4">/privacy</a>.
</p> </p>
<!-- landing-rev --> <!-- landing-rev -->
<p class="landing-rev" data-landing-site="bank" data-landing-version="44" data-landing-commit="689ee59"> <p class="landing-rev" data-landing-site="bank" data-landing-version="45" data-landing-commit="0d881cd">
Bank landing <strong>v44</strong> Bank landing <strong>v45</strong>
· content <time datetime="2026-07-17T18:31:49+02:00">2026-07-17 18:31 CEST</time> · content <time datetime="2026-09-16T23:19:33+02:00">2026-09-16 23:19 CEST</time>
· <a href="https://git.hacktivism.ch/hernani/koopa-admin-log/commit/689ee595e08f3880c1e4068aac8e972574e20d21" title="koopa-admin-log 689ee595e08f3880c1e4068aac8e972574e20d21">689ee59</a> · <a href="https://git.hacktivism.ch/hernani/koopa-admin-log/commit/0d881cd726da8d298c98114678253e3cbb7aa5e6" title="koopa-admin-log 0d881cd726da8d298c98114678253e3cbb7aa5e6">0d881cd</a>
</p> </p>
<!-- /landing-rev --> <!-- /landing-rev -->
</footer> </footer>

View file

@ -49,6 +49,16 @@ server {
add_header Cache-Control "no-store" always; add_header Cache-Control "no-store" always;
add_header Access-Control-Allow-Origin * 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/ { location /intro/ {
alias /var/www/bank-landing/; alias /var/www/bank-landing/;
} }

View file

@ -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 { reverse_proxy 127.0.0.1:9012 {
import proxy_public import proxy_public
} }

View file

@ -289,18 +289,22 @@
</style> </style>
</head> </head>
<body> <body>
<p class="stack-bar" role="note" title="Taler software on this exchange service (links → git.taler.net tags)"> <!-- stack-bar -->
<p class="stack-bar" role="note" title="Taler software on this exchange service (goa-regio-ng vanilla)">
<strong>stack</strong><span class="sep">·</span> <strong>stack</strong><span class="sep">·</span>
<a href="https://git.taler.net/taler/exchange/commit/6dbfa41e3fc6657eff28d11166d24301cdfa93ff.html" target="_blank" rel="noopener noreferrer" title="exchange tag v1.6.6">taler-exchange&nbsp;1.6.6</a> <a href="https://git.taler.net/taler-deployment.git/tree/?h=dev/hernani/goa-regio-ng-vanilla" target="_blank" rel="noopener noreferrer" title="taler-deployment branch dev/hernani/goa-regio-ng-vanilla">vanilla</a>
<span class="sep">·</span> <span class="sep">·</span>
<a href="https://git.taler.net/taler/exchange/commit/6dbfa41e3fc6657eff28d11166d24301cdfa93ff.html" target="_blank" rel="noopener noreferrer" title="exchange tag v1.6.6">libtalerexchange&nbsp;1.6.6</a> taler-exchange&nbsp;1.6.15
<span class="sep">·</span> <span class="sep">·</span>
<a href="https://git.taler.net/taler/exchange/commit/6dbfa41e3fc6657eff28d11166d24301cdfa93ff.html" target="_blank" rel="noopener noreferrer" title="exchange tag v1.6.6">taler-exchange-database&nbsp;1.6.6</a> libtalerexchange&nbsp;1.6.15
<span class="sep">·</span> <span class="sep">·</span>
<a href="https://git.taler.net/taler/exchange/commit/6dbfa41e3fc6657eff28d11166d24301cdfa93ff.html" target="_blank" rel="noopener noreferrer" title="exchange tag v1.6.6">taler-exchange-offline&nbsp;1.6.6</a> taler-exchange-database&nbsp;1.6.15
<span class="sep">·</span> <span class="sep">·</span>
<a href="https://git.taler.net/taler/exchange/commit/6dbfa41e3fc6657eff28d11166d24301cdfa93ff.html" target="_blank" rel="noopener noreferrer" title="exchange tag v1.6.6">taler-terms-generator&nbsp;1.6.6</a> taler-exchange-offline&nbsp;1.6.15
<span class="sep">·</span>
taler-terms-generator&nbsp;1.6.15
</p> </p>
<!-- /stack-bar -->
<main> <main>
<header class="hero"> <header class="hero">
<span class="badge">GOA · Exchange</span> <span class="badge">GOA · Exchange</span>
@ -500,10 +504,10 @@ sudo apt-get install -y taler-wallet-cli</pre>
<a href="https://exchange.hacktivism.ch/privacy" style="color:#5eead4">/privacy</a>. <a href="https://exchange.hacktivism.ch/privacy" style="color:#5eead4">/privacy</a>.
</p> </p>
<!-- landing-rev --> <!-- landing-rev -->
<p class="landing-rev" data-landing-site="exchange" data-landing-version="43" data-landing-commit="d0594cb"> <p class="landing-rev" data-landing-site="exchange" data-landing-version="44" data-landing-commit="0d881cd">
Exchange landing <strong>v43</strong> Exchange landing <strong>v44</strong>
· content <time datetime="2026-07-17T18:33:13+02:00">2026-07-17 18:33 CEST</time> · content <time datetime="2026-09-16T23:19:33+02:00">2026-09-16 23:19 CEST</time>
· <a href="https://git.hacktivism.ch/hernani/koopa-admin-log/commit/d0594cb44ffb1bec2e009f6812167cbbf216eda9" title="koopa-admin-log d0594cb44ffb1bec2e009f6812167cbbf216eda9">d0594cb</a> · <a href="https://git.hacktivism.ch/hernani/koopa-admin-log/commit/0d881cd726da8d298c98114678253e3cbb7aa5e6" title="koopa-admin-log 0d881cd726da8d298c98114678253e3cbb7aa5e6">0d881cd</a>
</p> </p>
<!-- /landing-rev --> <!-- /landing-rev -->
</footer> </footer>

View file

@ -530,18 +530,22 @@
</style> </style>
</head> </head>
<body> <body>
<p class="stack-bar" role="note" title="Taler software on this merchant service (links → git.taler.net tags)"> <!-- stack-bar -->
<p class="stack-bar" role="note" title="Taler software on this merchant service (goa-regio-ng vanilla)">
<strong>stack</strong><span class="sep">·</span> <strong>stack</strong><span class="sep">·</span>
<a href="https://git.taler.net/taler/merchant/commit/b5af1c99052f4c5053070cd5c6cc39b68eca2a20.html" target="_blank" rel="noopener noreferrer" title="merchant tag v1.6.9">taler-merchant&nbsp;1.6.9</a> <a href="https://git.taler.net/taler-deployment.git/tree/?h=dev/hernani/goa-regio-ng-vanilla" target="_blank" rel="noopener noreferrer" title="taler-deployment branch dev/hernani/goa-regio-ng-vanilla">vanilla</a>
<span class="sep">·</span> <span class="sep">·</span>
<a href="https://git.taler.net/taler/taler-typescript-core/commit/d7ab425239226b798a95dbf6742fe4b4a2e30e47.html" target="_blank" rel="noopener noreferrer" title="taler-typescript-core tag v1.6.8-dev.6">taler-merchant-webui&nbsp;1.6.8~dev6</a> taler-merchant&nbsp;1.6.23
<span class="sep">·</span> <span class="sep">·</span>
<a href="https://git.taler.net/taler/merchant/commit/b5af1c99052f4c5053070cd5c6cc39b68eca2a20.html" target="_blank" rel="noopener noreferrer" title="merchant tag v1.6.9">libtalermerchant&nbsp;1.6.9</a> taler-merchant-webui&nbsp;1.6.38
<span class="sep">·</span> <span class="sep">·</span>
<a href="https://git.taler.net/taler/exchange/commit/fed7d0e782ea4d36b2d2e337d93bfd2723c6ed64.html" target="_blank" rel="noopener noreferrer" title="exchange tag v1.6.7-dev.2">libtalerexchange&nbsp;1.6.7~dev2</a> libtalermerchant&nbsp;1.6.23
<span class="sep">·</span> <span class="sep">·</span>
<a href="https://git.taler.net/taler/exchange/commit/fed7d0e782ea4d36b2d2e337d93bfd2723c6ed64.html" target="_blank" rel="noopener noreferrer" title="exchange tag v1.6.7-dev.2">taler-terms-generator&nbsp;1.6.7~dev2</a> libtalerexchange&nbsp;1.6.15
<span class="sep">·</span>
taler-terms-generator&nbsp;1.6.15
</p> </p>
<!-- /stack-bar -->
<main> <main>
<header class="hero"> <header class="hero">
<span class="badge">Merchant · dual currency</span> <span class="badge">Merchant · dual currency</span>
@ -833,10 +837,10 @@ sudo apt-get install -y taler-wallet-cli</pre>
<a href="https://taler.hacktivism.ch/privacy" style="color:#5eead4">/privacy</a>. <a href="https://taler.hacktivism.ch/privacy" style="color:#5eead4">/privacy</a>.
</p> </p>
<!-- landing-rev --> <!-- landing-rev -->
<p class="landing-rev" data-landing-site="merchant" data-landing-version="42" data-landing-commit="37ea4bb"> <p class="landing-rev" data-landing-site="merchant" data-landing-version="43" data-landing-commit="0d881cd">
Merchant landing <strong>v42</strong> Merchant landing <strong>v43</strong>
· content <time datetime="2026-07-17T18:27:48+02:00">2026-07-17 18:27 CEST</time> · content <time datetime="2026-09-16T23:19:33+02:00">2026-09-16 23:19 CEST</time>
· <a href="https://git.hacktivism.ch/hernani/koopa-admin-log/commit/37ea4bb8396691e3ea69ae61374e6623bf6c5ea7" title="koopa-admin-log 37ea4bb8396691e3ea69ae61374e6623bf6c5ea7">37ea4bb</a> · <a href="https://git.hacktivism.ch/hernani/koopa-admin-log/commit/0d881cd726da8d298c98114678253e3cbb7aa5e6" title="koopa-admin-log 0d881cd726da8d298c98114678253e3cbb7aa5e6">0d881cd</a>
</p> </p>
<!-- /landing-rev --> <!-- /landing-rev -->
</footer> </footer>

View file

@ -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"
}
}
}
}

View file

@ -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"
]
}
}
}

View file

@ -2,34 +2,34 @@
"note": "Per-site landing content versions (start 42). Bump: scripts/taler-landing/stamp-landing-version.sh --bump bank|exchange|merchant", "note": "Per-site landing content versions (start 42). Bump: scripts/taler-landing/stamp-landing-version.sh --bump bank|exchange|merchant",
"sites": { "sites": {
"bank": { "bank": {
"version": 44, "version": 45,
"label": "Bank landing", "label": "Bank landing",
"path": "configs/bank-landing", "path": "configs/bank-landing",
"updated_iso": "2026-07-17T18:31:49+02:00", "updated_iso": "2026-09-16T23:19:33+02:00",
"updated_human": "2026-07-17 18:31 CEST", "updated_human": "2026-09-16 23:19 CEST",
"commit": "689ee595e08f3880c1e4068aac8e972574e20d21", "commit": "0d881cd726da8d298c98114678253e3cbb7aa5e6",
"commit_short": "689ee59", "commit_short": "0d881cd",
"repo_commit_url": "https://git.hacktivism.ch/hernani/koopa-admin-log/commit/689ee595e08f3880c1e4068aac8e972574e20d21" "repo_commit_url": "https://git.hacktivism.ch/hernani/koopa-admin-log/commit/0d881cd726da8d298c98114678253e3cbb7aa5e6"
}, },
"exchange": { "exchange": {
"version": 43, "version": 44,
"label": "Exchange landing", "label": "Exchange landing",
"path": "configs/exchange-landing", "path": "configs/exchange-landing",
"updated_iso": "2026-07-17T18:33:13+02:00", "updated_iso": "2026-09-16T23:19:33+02:00",
"updated_human": "2026-07-17 18:33 CEST", "updated_human": "2026-09-16 23:19 CEST",
"commit": "d0594cb44ffb1bec2e009f6812167cbbf216eda9", "commit": "0d881cd726da8d298c98114678253e3cbb7aa5e6",
"commit_short": "d0594cb", "commit_short": "0d881cd",
"repo_commit_url": "https://git.hacktivism.ch/hernani/koopa-admin-log/commit/d0594cb44ffb1bec2e009f6812167cbbf216eda9" "repo_commit_url": "https://git.hacktivism.ch/hernani/koopa-admin-log/commit/0d881cd726da8d298c98114678253e3cbb7aa5e6"
}, },
"merchant": { "merchant": {
"version": 42, "version": 43,
"label": "Merchant landing", "label": "Merchant landing",
"path": "configs/merchant-landing", "path": "configs/merchant-landing",
"updated_iso": "2026-07-17T18:27:48+02:00", "updated_iso": "2026-09-16T23:19:33+02:00",
"updated_human": "2026-07-17 18:27 CEST", "updated_human": "2026-09-16 23:19 CEST",
"commit": "37ea4bb8396691e3ea69ae61374e6623bf6c5ea7", "commit": "0d881cd726da8d298c98114678253e3cbb7aa5e6",
"commit_short": "37ea4bb", "commit_short": "0d881cd",
"repo_commit_url": "https://git.hacktivism.ch/hernani/koopa-admin-log/commit/37ea4bb8396691e3ea69ae61374e6623bf6c5ea7" "repo_commit_url": "https://git.hacktivism.ch/hernani/koopa-admin-log/commit/0d881cd726da8d298c98114678253e3cbb7aa5e6"
} }
} }
} }

View file

@ -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 { reverse_proxy 127.0.0.1:9012 {
import proxy_public import proxy_public
} }

View file

@ -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
}

View file

@ -10,6 +10,24 @@ sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy 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) ## dossiers.ngi* — basic_auth (htaccess-in-front)
Caddy **ignores** Apache `.htaccess`. Use: Caddy **ignores** Apache `.htaccess`. Use:

View file

@ -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

View file

@ -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
}
}

View file

@ -38,24 +38,48 @@ Details + JSON schema: `configs/bank-landing/README.md`.
## Demo withdraw + auto-account API ## 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 | | Path | Behaviour |
|------|-----------| |------|-----------|
| `GET /demo-withdraw.json` | Mint one-shot withdraw from shared **`explorer`** pool; write `withdraw.uri` + watch ids | | `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-<random>`** user + password containing **pleasechangeme**; **balance GOA:0**; return credentials once | | `GET /auto-account.json` | Public `POST /accounts` with generated **`goa-account-<random>`** 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 ```bash
./install-demo-withdraw-api.sh ./install-demo-withdraw-api.sh
# Public checks: # Public checks:
curl -sS https://bank.hacktivism.ch/intro/demo-withdraw.json | head 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 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` 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) ### Auto-confirm (explorer only)

View file

@ -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("/") BANK_PUBLIC = os.environ.get("BANK_PUBLIC", "https://bank.hacktivism.ch").rstrip("/")
USER = os.environ.get("BANK_USER", "explorer") USER = os.environ.get("BANK_USER", "explorer")
AMOUNT = os.environ.get("AMOUNT", "GOA:10") 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")) LANDING = Path(os.environ.get("LANDING_DIR", "/var/www/bank-landing"))
LISTEN = ("127.0.0.1", int(os.environ.get("DEMO_WITHDRAW_PORT", "19096"))) 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]} return e.code, {"raw": raw[:500]}
def mint_withdraw() -> dict: def mint_withdraw(amount: str | None = None) -> dict:
pw = load_pass() pw = load_pass()
code, tok = http_json( code, tok = http_json(
"POST", "POST",
@ -123,10 +127,14 @@ def mint_withdraw() -> dict:
if code != 200 or not tok.get("access_token"): if code != 200 or not tok.get("access_token"):
raise RuntimeError(f"token failed HTTP {code}: {tok}") raise RuntimeError(f"token failed HTTP {code}: {tok}")
access = tok["access_token"] 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( code, wd = http_json(
"POST", "POST",
f"{BANK}/accounts/{USER}/withdrawals", f"{BANK}/accounts/{USER}/withdrawals",
{"suggested_amount": AMOUNT}, {"amount": amt, "exchange_url": EXCHANGE},
headers={"Authorization": f"Bearer {access}"}, headers={"Authorization": f"Bearer {access}"},
) )
if code not in (200, 201): if code not in (200, 201):
@ -142,7 +150,7 @@ def mint_withdraw() -> dict:
uri = normalize_taler_withdraw_uri(str(uri).strip()) uri = normalize_taler_withdraw_uri(str(uri).strip())
LANDING.mkdir(parents=True, exist_ok=True) LANDING.mkdir(parents=True, exist_ok=True)
(LANDING / "withdraw.uri").write_text(uri + "\n") (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( (LANDING / "withdraw.created").write_text(
time.strftime("%Y-%m-%dT%H:%MZ", time.gmtime()) + "\n" time.strftime("%Y-%m-%dT%H:%MZ", time.gmtime()) + "\n"
) )
@ -151,7 +159,8 @@ def mint_withdraw() -> dict:
"ok": True, "ok": True,
"taler_withdraw_uri": uri, "taler_withdraw_uri": uri,
"withdrawal_id": wid, "withdrawal_id": wid,
"amount": AMOUNT, "amount": amt,
"exchange_url": EXCHANGE,
"pool_account": USER, "pool_account": USER,
"taler_integration_base": f"{BANK_PUBLIC}/taler-integration/", "taler_integration_base": f"{BANK_PUBLIC}/taler-integration/",
"hint": "Open in GNU Taler Wallet (iOS/Android/desktop). No bank registration.", "hint": "Open in GNU Taler Wallet (iOS/Android/desktop). No bank registration.",
@ -301,15 +310,54 @@ class Handler(BaseHTTPRequestHandler):
self._cors() self._cors()
self.end_headers() self.end_headers()
def do_GET(self): def _proxy_wd(self, parsed):
path = self.path.split("?", 1)[0] """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: 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 ( if path in (
"/", "/",
"/demo-withdraw.json", "/demo-withdraw.json",
"/intro/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 ( elif path in (
"/auto-account.json", "/auto-account.json",
"/intro/auto-account.json", "/intro/auto-account.json",

View file

@ -14,61 +14,86 @@ podman exec -u root "$CTR" chmod 755 \
/usr/local/bin/auto-confirm-withdrawals.sh \ /usr/local/bin/auto-confirm-withdrawals.sh \
/usr/local/bin/refresh-demo-withdraw.sh /usr/local/bin/refresh-demo-withdraw.sh
# nginx: proxy demo-withdraw.json # nginx: ensure demo-withdraw / auto-account / Integration → :19096
NGX=/etc/nginx/sites-available/bank-landing # FP pattern: Caddy @wdGet GET → :9013 → this location → demo-api
# (single in-CTR python; no nested podman)
podman exec -u root "$CTR" bash -lc ' podman exec -u root "$CTR" bash -lc '
set -e set -e
f=/etc/nginx/sites-available/bank-landing
if ! grep -q demo-withdraw.json "$f"; then
# insert before location /intro/
python3 - <<PY python3 - <<PY
from pathlib import Path from pathlib import Path
p = Path("/etc/nginx/sites-available/bank-landing") p = Path("/etc/nginx/sites-available/bank-landing")
t = p.read_text() t = p.read_text()
block=""" location = /intro/demo-withdraw.json { changed = False
anchor = " location /intro/ {"
blocks = []
if "demo-withdraw.json" not in t:
blocks.append(""" location = /intro/demo-withdraw.json {
proxy_pass http://127.0.0.1:19096/demo-withdraw.json; proxy_pass http://127.0.0.1:19096/demo-withdraw.json;
proxy_http_version 1.1; proxy_http_version 1.1;
proxy_set_header Host \$host; proxy_set_header Host \$host;
add_header Cache-Control "no-store" always; add_header Cache-Control "no-store" always;
add_header Access-Control-Allow-Origin * always; add_header Access-Control-Allow-Origin * always;
} }
""" """)
if "demo-withdraw.json" not in t: print("nginx demo-withdraw location added")
t=t.replace(" location /intro/ {", block+" location /intro/ {", 1)
p.write_text(t)
print("nginx location added")
else: else:
print("nginx already has demo-withdraw") print("nginx already has demo-withdraw")
if "auto-account.json" not in t: if "auto-account.json" not in t:
t=p.read_text() blocks.append(""" location = /intro/auto-account.json {
block2=""" location = /intro/auto-account.json {
proxy_pass http://127.0.0.1:19096/auto-account.json; proxy_pass http://127.0.0.1:19096/auto-account.json;
proxy_http_version 1.1; proxy_http_version 1.1;
proxy_set_header Host \$host; proxy_set_header Host \$host;
add_header Cache-Control "no-store" always; add_header Cache-Control "no-store" always;
add_header Access-Control-Allow-Origin * always; add_header Access-Control-Allow-Origin * always;
} }
""" """)
t=t.replace(" location /intro/ {", block2+" location /intro/ {", 1)
p.write_text(t)
print("nginx auto-account location added") print("nginx auto-account location added")
else: else:
print("nginx already has auto-account") print("nginx already has auto-account")
if "taler-integration/withdrawal-operation" not in t:
blocks.append(""" # FP pattern: GET Integration via demo-api (suggested/required_exchange)
# Caddy @wdGet → :9013 → here → :19096; POST/other 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;
}
""")
print("nginx Integration withdrawal-operation location added")
else:
print("nginx already has Integration withdrawal-operation")
if blocks:
if anchor not in t:
raise SystemExit("nginx anchor location /intro/ not found")
t = t.replace(anchor, "".join(blocks) + anchor, 1)
p.write_text(t)
changed = True
if not changed:
print("nginx locations already complete")
PY PY
nginx -t && nginx -s reload || true nginx -t && nginx -s reload || true
else
echo "nginx already configured"
fi
' '
# start/restart API + single auto-confirm loop (flock inside script) # start/restart API + single auto-confirm loop (flock inside script)
podman exec -u root "$CTR" bash -lc ' podman exec -u root "$CTR" bash -lc '
# stop demo-withdraw by pid (avoid pkill -f self-match) # stop demo-withdraw by pid (avoid pkill -f self-match); wait for :19096
ps -eo pid=,args= | awk "/demo-withdraw-api\\.py/ && !/awk/ {print \$1}" | while read p; do kill \$p 2>/dev/null || true; done ps -eo pid=,args= | awk "/demo-withdraw-api\\.py/ && !/awk/ {print \$1}" | while read p; do kill \$p 2>/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 \ nohup python3 /usr/local/bin/demo-withdraw-api.py \
>>/var/log/demo-withdraw-api.log 2>&1 </dev/null & >>/var/log/demo-withdraw-api.log 2>&1 </dev/null &
echo "api pid $!" echo "api pid $!"
# fail loud if bind lost to a leftover listener
sleep 0.5
ps -eo pid=,args= | awk "/demo-withdraw-api\\.py/ && !/awk/" || {
echo "ERROR: demo-withdraw-api failed to stay up (port busy?)" >&2
tail -20 /var/log/demo-withdraw-api.log >&2 || true
exit 1
}
# stop auto-confirm by pid # 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 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 sleep 0.5

View file

@ -203,8 +203,11 @@ if [ -n "$ADMIN_TOKEN" ]; then
u=$(printf '%s' "$line" | sed -n 's/.*"username"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p') u=$(printf '%s' "$line" | sed -n 's/.*"username"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')
[ -n "$u" ] && echo "$u" [ -n "$u" ] && echo "$u"
done >"$WORKDIR/usernames.txt" || true done >"$WORKDIR/usernames.txt" || true
ACCOUNTS_N=$(grep -cve '^\s*$' "$WORKDIR/usernames.txt" 2>/dev/null || echo 0) # grep -c exits 1 on zero matches; do not `|| echo 0` (would print 0\n0 and break JSON)
ACCOUNTS_USERS=$(grep -Eve '^(admin|exchange)$' "$WORKDIR/usernames.txt" 2>/dev/null | grep -cve '^\s*$' || echo 0) 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 else
echo "$BANK_USER" >"$WORKDIR/usernames.txt" echo "$BANK_USER" >"$WORKDIR/usernames.txt"
ACCOUNTS_N=1 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" || cp "$WORKDIR/all-in.tsv" "$WORKDIR/all-in-sorted.tsv"
# Unique reserves = individual wallet withdraws (each wallet reserve_pub) # 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 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 # Aggregates
NOW=$(now_unix) NOW=$(now_unix)

View file

@ -2,7 +2,8 @@
# Run INSIDE taler-hacktivism-exchange-ansible. # Run INSIDE taler-hacktivism-exchange-ansible.
# Writes /var/www/exchange-landing/stats.json # 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, …) # (reserves, reserves_in, known_coins, withdraw, denominations, …)
# #
# IMPORTANT: never use psql -F$'\t' -v ON_ERROR_STOP=1 # 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" OUT="$LANDING_DIR/stats.json"
RUN="$LANDING_DIR/stats-run.json" RUN="$LANDING_DIR/stats-run.json"
TMP="${OUT}.tmp.$$" 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}" BASE_URL="${EXCHANGE_BASE_URL:-https://exchange.hacktivism.ch}"
ERRLOG="${LANDING_STATS_ERRLOG:-/var/log/landing-stats-exchange.err}" ERRLOG="${LANDING_STATS_ERRLOG:-/var/log/landing-stats-exchange.err}"
mkdir -p "$LANDING_DIR" mkdir -p "$LANDING_DIR"
@ -253,7 +285,7 @@ cat >"$TMP" <<EOF
{ {
"ok": true, "ok": true,
"source": "exchange-db", "source": "exchange-db",
"schema": "exchange.* @ taler-exchange", "schema": "exchange.* @ ${DB}",
"focus": "coins", "focus": "coins",
"timezone": $(json_str "$TZ"), "timezone": $(json_str "$TZ"),
"generated_at": $(json_str "$GEN"), "generated_at": $(json_str "$GEN"),

View file

@ -56,9 +56,9 @@ COLLECT_MERCHANT="${COLLECT_MERCHANT:-1}"
COLLECT_RESOURCES="${COLLECT_RESOURCES:-1}" COLLECT_RESOURCES="${COLLECT_RESOURCES:-1}"
# Containers (empty = skip podman publish for that role) # Containers (empty = skip podman publish for that role)
BANK_CTR="${BANK_CTR:-taler-hacktivism-bank}" BANK_CTR="${BANK_CTR:-goa-regio-ng}"
EX_CTR="${EX_CTR:-taler-hacktivism-exchange-ansible}" EX_CTR="${EX_CTR:-goa-regio-ng}"
MER_CTR="${MER_CTR:-taler-hacktivism}" MER_CTR="${MER_CTR:-goa-regio-ng}"
BANK_URL="${BANK_URL:-http://127.0.0.1:9012}" BANK_URL="${BANK_URL:-http://127.0.0.1:9012}"
BANK_PUBLIC_URL="${BANK_PUBLIC_URL:-https://bank.hacktivism.ch}" BANK_PUBLIC_URL="${BANK_PUBLIC_URL:-https://bank.hacktivism.ch}"
@ -322,10 +322,14 @@ run_incontainer_stats() {
fi fi
log "$label: run $script inside $ctr" log "$label: run $script inside $ctr"
set +e set +e
# Pass optional DB overrides (vanilla regio-ng: EXCHANGE_DB=exchange)
podman exec \ podman exec \
-e LANDING_DIR="$landing" \ -e LANDING_DIR="$landing" \
-e TZ=Europe/Zurich \ -e TZ=Europe/Zurich \
-e PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" \ -e PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" \
${EXCHANGE_DB:+-e EXCHANGE_DB="$EXCHANGE_DB"} \
${MERCHANT_DB:+-e MERCHANT_DB="$MERCHANT_DB"} \
${EXCHANGE_BASE_URL:+-e EXCHANGE_BASE_URL="$EXCHANGE_BASE_URL"} \
"$ctr" bash "$script" >>"$LOG_DIR/${label}.log" 2>&1 "$ctr" bash "$script" >>"$LOG_DIR/${label}.log" 2>&1
local ec=$? local ec=$?
set -e set -e

View file

@ -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]: def list_all_accounts(bank: str, token: str, delta: int) -> List[str]:
"""List accounts; page with start if needed.""" """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] = [] names: List[str] = []
seen: Set[str] = set() seen: Set[str] = set()
start: Optional[int] = None start: Optional[int] = None
@ -213,9 +218,10 @@ def iter_transactions(
pages = 0 pages = 0
safety = max_pages if max_pages > 0 else 10_000 safety = max_pages if max_pages > 0 else 10_000
page = min(abs(page), 1024) or 1024
while pages < safety: while pages < safety:
pages += 1 pages += 1
q = f"delta=-{abs(page)}" q = f"delta=-{page}"
if start is not None: if start is not None:
q += f"&start={start}" q += f"&start={start}"
code, data, raw = http_json( code, data, raw = http_json(
@ -364,7 +370,7 @@ def main() -> int:
ap.add_argument( ap.add_argument(
"--accounts-delta", "--accounts-delta",
type=int, 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("--recent", type=int, default=int(env("RECENT_WD_N", "10") or "10"))
ap.add_argument("--public-base", default=env("BANK_PUBLIC_URL", "https://bank.hacktivism.ch")) ap.add_argument("--public-base", default=env("BANK_PUBLIC_URL", "https://bank.hacktivism.ch"))

View file

@ -3,9 +3,11 @@
# listens on 9014 (exchange) / 9015 (merchant). # listens on 9014 (exchange) / 9015 (merchant).
# #
# Port map (wire these in Caddy / podman -p): # Port map (wire these in Caddy / podman -p):
# 9013 bank landing (already on taler-hacktivism-bank) # 9013 bank landing
# 9014 exchange landing (taler-hacktivism-exchange-ansible) # 9014 exchange landing
# 9015 merchant landing (taler-hacktivism) # 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. # Run on koopa (host) with podman access.
set -euo pipefail 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_QR="${SRC_QR:-$ROOT/configs/bank-landing/qrcode.min.js}"
SRC_GOA_AMT="${SRC_GOA_AMT:-$ROOT/configs/shared/goa-amount.js}" SRC_GOA_AMT="${SRC_GOA_AMT:-$ROOT/configs/shared/goa-amount.js}"
C_EX=taler-hacktivism-exchange-ansible LANDING_CTR="${LANDING_CTR:-goa-regio-ng}"
C_MER=taler-hacktivism C_EX="${C_EX:-$LANDING_CTR}"
C_BANK="${C_BANK:-taler-hacktivism-bank}" C_MER="${C_MER:-$LANDING_CTR}"
C_BANK="${C_BANK:-$LANDING_CTR}"
# Prefer per-landing copy, then shared (keeps bank/exchange/merchant in sync) # Prefer per-landing copy, then shared (keeps bank/exchange/merchant in sync)
resolve_goa_amount() { 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
echo "PORTS TO WIRE (host → Caddy / firewall / podman -p):" echo "PORTS TO WIRE (host → Caddy / firewall / podman -p):"
echo " 9013 bank landing (taler-hacktivism-bank) already published" echo " 9013 bank landing ($C_BANK)"
echo " 9014 exchange landing (taler-hacktivism-exchange-ansible) NEEDS -p 9014:9014" echo " 9014 exchange landing ($C_EX)"
echo " 9015 merchant landing (taler-hacktivism) NEEDS -p 9015:9015" echo " 9015 merchant landing ($C_MER)"
echo echo
echo "If host curl to :9014/:9015 fails, republish pasta ports (commit+replace) — see README." echo "If host curl to :9014/:9015 fails, republish pasta ports (commit+replace) — see README."

View file

@ -1,8 +1,8 @@
# Stack profile: GOA / hacktivism # Stack profile: GOA / goa-regio-ng vanilla (9010-9015)
# systemd EnvironmentFile: KEY=value only (no export) # systemd EnvironmentFile: KEY=value only (no export)
BANK_CTR=taler-hacktivism-bank BANK_CTR=goa-regio-ng
EX_CTR=taler-hacktivism-exchange-ansible EX_CTR=goa-regio-ng
MER_CTR=taler-hacktivism MER_CTR=goa-regio-ng
BANK_URL=http://127.0.0.1:9012 BANK_URL=http://127.0.0.1:9012
BANK_PUBLIC_URL=https://bank.hacktivism.ch BANK_PUBLIC_URL=https://bank.hacktivism.ch
EXCHANGE_CONFIG_URL=https://exchange.hacktivism.ch/config EXCHANGE_CONFIG_URL=https://exchange.hacktivism.ch/config
@ -15,4 +15,4 @@ COLLECT_EXCHANGE=1
COLLECT_MERCHANT=1 COLLECT_MERCHANT=1
COLLECT_RESOURCES=1 COLLECT_RESOURCES=1
PUBLISH_PODMAN=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"

View file

@ -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 (<!-- stack-bar --> 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 = '<span class="sep">·</span>'
def render_block(site: str) -> str:
meta = stack["sites"][site]
parts = [
f' <!-- stack-bar -->',
f' <p class="stack-bar" role="note" title="{meta["title"]}">',
f' <strong>stack</strong>{sep}',
f' <a href="{vanilla_href}" target="_blank" rel="noopener noreferrer" title="{vanilla_title}">vanilla</a>',
]
for pkg in meta["packages"]:
disp = versions[pkg]["display"]
parts.append(f" {sep}")
parts.append(f" {pkg}&nbsp;{disp}")
parts.append(" </p>")
parts.append(" <!-- /stack-bar -->")
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]*<!-- stack-bar -->.*?<!-- /stack-bar -->\n?", re.S)
bare_re = re.compile(
r"[ \t]*<p class=\"stack-bar\"[^>]*>.*?</p>\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}"

View file

@ -3,6 +3,7 @@
# Host: koopa (hernani, podman). No Freigabe-bypass — caller must have Freigabe. # Host: koopa (hernani, podman). No Freigabe-bypass — caller must have Freigabe.
# #
# ./goa-live-backup-upgrade.sh dry-run # ./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 apply # backup + apt + commit + smoke
# ./goa-live-backup-upgrade.sh status # ./goa-live-backup-upgrade.sh status
# #
@ -312,6 +313,12 @@ cmd_status() {
case "$MODE" in case "$MODE" in
dry-run|dry) cmd_dry_run ;; 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 ;; apply) cmd_apply ;;
status) cmd_status ;; status) cmd_status ;;
-h|--help|help) -h|--help|help)
@ -319,7 +326,7 @@ case "$MODE" in
exit 0 exit 0
;; ;;
*) *)
echo "usage: $0 dry-run|apply|status" >&2 echo "usage: $0 dry-run|backup|apply|status" >&2
exit 2 exit 2
;; ;;
esac esac