taler-monitoring v1.13.12

Multi-phase global SUMMARY, warn-filter dimmed context, SUMMARY chrome
as meta; FP stage host-agent via francpaysan-stage-user (stagepaysan);
monpages GOA + FP stage.
This commit is contained in:
Hernâni Marques 2026-07-19 04:21:16 +02:00
commit 5a5431bfa9
No known key found for this signature in database
GPG key ID: CB5738652768F7E9
88 changed files with 21996 additions and 0 deletions

193
host-agent/README.md Normal file
View file

@ -0,0 +1,193 @@
# host-agent — reporting generation (GOA + FP + any stack)
**Global pipeline** lives in **`run-host-report.sh`** (all stacks share it):
| Behaviour | Default |
|-----------|---------|
| Suite refresh | **`update-suite.sh` every run** → `git fetch --tags` + hard reset to `origin/main` |
| Update strict | `SUITE_UPDATE_STRICT=1` (default): **abort** if fetch/reset fails — no silent old tree |
| Wall clock | `RUN_TIMEOUT=600` |
| Log | line-buffered `run-*.log` (continuous write) |
| HTML | always (first run / empty tree too) + commit URL; **bootstrap install stubs are monpages ERROR** |
| Timeout notice | end of log + top jump on `/monitoring_err/` |
**Rule:** public mon pages must show a **current suite tag/commit**, not install bootstrap or months-old trees. Timers exist so this keeps working without manual SSH.
Thin wrappers only set stack defaults:
| Wrapper | Stack | Typical user |
|---------|-------|----------------|
| `run-hacktivism-monitoring.sh` | GOA `hacktivism.ch` | `hernani` @ koopa |
| `run-fp-stage-monitoring.sh` | FP stage | `stagepaysan` |
| `run-host-report.sh` + `taler-monitoring-mytops-stage.{service,timer}` | mytops **stage / betel** only | `taler-monitoring` @ betel (4h) |
| `run-aptdeploy.sh` | CLI: ensure + **aptdeploy** (no HTML) | local if hostname=koopa, else **ssh $DEPLOY_SSH** |
| `run-aptdeploy-monitoring.sh` | apt-src tests → `/taler-monitoring-aptdeploy*` | `hernani` @ koopa (4h) |
| `run-surface-monitoring.sh` | **surface** → only `/taler-monitoring-surface*` (ecosystem + mail + mattermost + versions) | `hernani` @ koopa (hourly) |
Deprecated mail/mattermost wrappers: **`../meta/deprecated/`** (not installed).
### apt-src deploy tests (hacktivism `/monitoring`)
| Container | Mode |
|-----------|------|
| `koopa-taler-deploy-test-apt-src-trixie` | **fresh**: recreate when repo package fingerprint changes |
| `koopa-taler-deploy-test-apt-src-trixie-testing` | **fresh** for `trixie-testing` |
| `koopa-taler-deploy-test-apt-src-trixie-upgrade` | **upgrade**: initial install, then mytops-style `apt install` + `upgrade` |
| `koopa-taler-deploy-test-apt-src-trixie-testing-upgrade` | **upgrade** for testing suite |
```bash
# from ops workstation → triggers on koopa-external
./host-agent/run-aptdeploy.sh
./host-agent/run-aptdeploy.sh upgrade
./host-agent/run-aptdeploy.sh fresh
./host-agent/run-aptdeploy.sh check-only
# on koopa
./taler-monitoring.sh -d hacktivism.ch aptdeploy
```
Checks: package table, `httpd --version`, libtalerutil/`ldd` on failure, `systemctl` for
`taler-merchant.target`, basic local `/config` probe. No nginx/HTTPS in containers.
### Public HTML layout (v1.8.0 simplified)
| URL | Role |
|-----|------|
| `https://{bank,exchange,merchant}/monitoring/` (+ `_err`) | **Landing-stack** report — GOA 3 + FP **stage** 3 (+ mytops stage as configured) |
| `https://taler.hacktivism.ch/taler-monitoring-surface/` (+ `_err`) | **Only** ecosystem page: software/versions in the Taler universe (nmap OS, mail firefly+anastasis, mattermost, …) |
No separate public pages for mail/mattermost. Staging under `~/monitoring-sites-staging/`.
```bash
systemctl --user enable --now taler-monitoring-surface.timer
systemctl --user start taler-monitoring-surface.service
```
Env examples: **`env/*.env.example`**. FP stage: `run-fp-stage-monitoring.sh` (same `run-host-report.sh` pipeline).
## GOA (koopa) — host-podman
Runs **as user `hernani`** (systemd user, linger). **Primary** mode:
- **`INSIDE_PODMAN=1`** / **`INSIDE_MODE=local-podman`**: access mode **`host-podman`** —
`podman exec` on this host into
`taler-hacktivism-bank` / `taler-hacktivism-exchange-ansible` / `taler-hacktivism`
(IDs `inside.host-*`, not `inside.ssh-*`).
From an **ops workstation**, leave `INSIDE_PODMAN` off → access mode **`ssh`** via `KOOPA_SSH`
(`inside.ssh-*`).
- Public **`urls` + `versions`** for the stack (`-d hacktivism.ch`).
- Writes console HTML for all three sites under
`~/monitoring-sites-staging/{bank,exchange,taler}.hacktivism.ch/`.
Caddy (host root) can serve that tree at `/monitoring` — you configure Caddy.
## Latest software + config isolation
**Source of truth is always Forgejo** (`git.hacktivism.ch`), not a laptop rsync copy.
Every host-agent / aptdeploy run:
1. **`update-suite.sh`**: `git fetch` + **`SUITE_UPDATE_MODE=reset`** (default) → `origin/main`
into `SUITE_DIR` (prefer `~/src/taler-monitoring`).
2. **Re-exec** host-agent scripts from that tree so the running code matches `COMMIT_URL`.
3. Local overrides only in **`~/.config/taler-monitoring/env`** — never overwritten by git.
4. HTML footer = exact commit:
`https://git.hacktivism.ch/hernani/taler-monitoring/src/commit/<full-sha>`
5. **`RUN_TIMEOUT=600`**, line-buffered logs, HTML always written.
If fetch fails → previous tree + WARN.
**To ship features (aptdeploy, surface, …): push to `origin/main`.** Local-only edits vanish on the next reset.
If pull fails (no network / auth), the previous tree is used and a WARN is logged.
## Install (on koopa as hernani)
```bash
# once: local config (optional)
mkdir -p ~/.config/taler-monitoring
cp ~/src/taler-monitoring/host-agent/env.example \
~/.config/taler-monitoring/env
# ops workstation:
./host-agent/install-host-agent.sh --remote $DEPLOY_SSH
# or on koopa:
~/src/taler-monitoring/host-agent/install-host-agent.sh
```
Units:
| Unit | Role |
|------|------|
| `taler-monitoring-hacktivism.path` | fires on software stamp (`touch-software-stamp.sh`) |
| `taler-monitoring-hacktivism.timer` | every **4h** fallback |
| `taler-monitoring-hacktivism.service` | oneshot → `/monitoring*` (urls inside versions) |
| `taler-monitoring-surface.timer` | every **1h** |
| `taler-monitoring-surface.service` | → `/taler-monitoring-surface*` (taler only) |
| `taler-monitoring-aptdeploy.timer` | every **4h** |
| `taler-monitoring-aptdeploy.service` | → `/taler-monitoring-aptdeploy*` (taler only) |
## After package / image / landing upgrades
```bash
~/src/taler-monitoring/host-agent/touch-software-stamp.sh
# → path unit starts the service
```
## Manual
```bash
systemctl --user start taler-monitoring-hacktivism.service
systemctl --user status taler-monitoring-hacktivism.service --no-pager
journalctl --user -u taler-monitoring-hacktivism.service -n 50 --no-pager
ls ~/monitoring-sites-staging/bank.hacktivism.ch/monitoring_err/
```
## Relation to optional outside-only timer
| | host-agent (koopa) | outside-runner site-gen |
|--|--------------------|-------------------|
| Access | **podman on host** | public HTTPS only |
| Phases | urls **inside** versions | urls versions |
| Trigger | software path + 4h | *could* launchd 4h |
| Primary for GOA | **yes** | **off** (optional only) |
**outside-runner is not scheduled** right now (launchd disabled). Scripts under
`site-gen/` can still one-shot a public run if needed; see `site-gen/README.md`.
No secrets required for default phases. `secrets.env` optional for e2e later.
## Git commit on HTML pages
Header of each generated page includes a link like:
`https://git.hacktivism.ch/hernani/taler-monitoring/src/commit/<full-sha>`
That SHA is the tree **after** `update-suite.sh` for that run.
## Packages on koopa
See **[../DEPENDENCIES.md](../DEPENDENCIES.md)**.
**Default host-agent needs no new packages** if curl/python3/podman/git are present (they are).
**Root once** only for full QR checks in `urls`:
```bash
zypper in qrencode zbar
```
Without them, monitoring still runs; QR group WARNs and skips encode/decode.
Caddy `/var/www/monitoring-sites` is separate (you configured hernani write access).
## Exit codes (v1.7.1+)
Host-agent jobs default to **`STRICT_EXIT=1`**: non-zero exit if suite checks fail, `DEPLOY_WWW_ROOT` is not writable, or public monpages (FQDN) fail.
Set `STRICT_EXIT=0` only if you want timers to stay green while still logging errors (not recommended for production).
## Monitoring pages (suite)
Public sticky-bar HTML under `/monitoring*` and `/taler-monitoring-*` is part of **taler-monitoring** (not a separate product). See main [README — Monitoring pages](../README.md#monitoring-pages-part-of-the-suite). Phase **monpages** is obligatory (**ERROR** if missing): GOA checks the full suite inventory; FP only FP mon hosts.

View file

@ -0,0 +1,74 @@
# Root: make monitoring pages live on koopa (GOA)
## Detection (v1.3.1+)
Suite phase **`monpages`** and the host-agent **post-check** curl each public
FQDN (`https://taler.hacktivism.ch/monitoring/` …). Merchant JSON **code 21**
or non-HTML is an **ERROR** until this apply is done.
## Symptom if not applied
Public URLs return **merchant/bank JSON** like:
```json
{ "code": 21, "hint": "There is no endpoint defined for the URL provided..." }
```
or a **404 HTML** page from the app reverse_proxy.
Cause: live `/etc/caddy/Caddyfile` has **no** `handle /monitoring*` (falls through to
API). Prepared config is only under **`$KOOPA_CADDY_DIR/Caddyfile`**.
## One-shot as root (recommended)
```bash
# absolute path required: as root, ~ is /root (not /home/hernani)
sudo $APPLY_MONITORING_LIVE
# or already root:
# $APPLY_MONITORING_LIVE
```
**Caddy bare-path redir (v1.9.2+):** inside a `handle` block use
`redir * /path/ 302` — never `redir /path/ 302` alone (Caddy treats the first
token as matcher and sets `Location: 302`, so bare URLs fall through to the
merchant API as JSON **code 21**).
That script:
1. `rsync` staging HTML → `/var/www/monitoring-sites/` (incl. surface + aptdeploy)
2. Makes tree writable for `hernani` (group `caddy` + ACL) so later runs self-sync
3. Installs prepared Caddyfile → `/etc/caddy/Caddyfile`, validates, reloads Caddy
4. Smoke-curls all monitoring URLs (must **not** be JSON `code:21`)
## Paths (only three families — suite name ≠ URL)
There is **no** public page `/taler-monitoring`. Each family needs **bare and slash**
(`redir * /path/ 302` inside Caddy `handle`):
| Family | Paths | Host | Source timer |
|--------|-------|------|----------------|
| **monitoring** | `/monitoring` · `/monitoring/` · `/monitoring_err` · `/monitoring_err/` | bank + exchange + taler (and other landings) | `taler-monitoring-hacktivism.timer` (4h) |
| **taler-monitoring-surface** | bare + slash + `_err` | **taler.hacktivism.ch only** | `taler-monitoring-surface.timer` (1h) |
| **taler-monitoring-aptdeploy** | bare + slash + `_err` | **taler.hacktivism.ch only** | `taler-monitoring-aptdeploy.timer` (4h) |
HTML is generated as **hernani** into
`$DEPLOY_STAGING (or $HOME/monitoring-sites-staging)/`.
## Manual equivalent
```bash
rsync -a --delete $DEPLOY_STAGING (or $HOME/monitoring-sites-staging)/ /var/www/monitoring-sites/
chown -R hernani:caddy /var/www/monitoring-sites && chmod -R g+rwX /var/www/monitoring-sites
sudo $KOOPA_CADDY_DIR/apply.sh # or apply-monitoring-live.sh
```
Expect **200** (HTML with sticky bar). Not merchant JSON `code:21`.
## After each monitoring run (if www is not hernani-writable)
```bash
rsync -a --delete $DEPLOY_STAGING (or $HOME/monitoring-sites-staging)/ /var/www/monitoring-sites/
chown -R caddy:caddy /var/www/monitoring-sites
```
Or grant `hernani` write on `/var/www/monitoring-sites` once so the host-agent can rsync itself.

View file

@ -0,0 +1,91 @@
#!/usr/bin/env bash
# ONE-SHOT as root: publish monitoring HTML + enable Caddy handles
#
# Suite copy (taler-monitoring host-agent). Live install path on koopa:
# ${KOOPA_CADDY_DIR}/apply-monitoring-live.sh
# Prefer absolute path ( ~ expands to /root when already root ):
# sudo ${KOOPA_CADDY_DIR}/apply-monitoring-live.sh
# ${KOOPA_CADDY_DIR}/apply-monitoring-live.sh # if already root
#
# After editing here: rsync to koopa ~/koopa-caddy/ before root apply.
set -euo pipefail
if [[ "$(id -u)" -ne 0 ]]; then
exec sudo -E "$0" "$@"
fi
STAGING="${DEPLOY_STAGING:-${HTML_OUT:-$HOME/monitoring-sites-staging}}"
WWW="${DEPLOY_WWW_ROOT:?set DEPLOY_WWW_ROOT in env}"
SRC="${KOOPA_CADDY_DIR:?set KOOPA_CADDY_DIR in env}/Caddyfile"
DST=/etc/caddy/Caddyfile
if [[ ! -f "$SRC" ]]; then
echo "ERROR: missing Caddyfile source: $SRC" >&2
exit 1
fi
if [[ ! -d "$STAGING" ]]; then
echo "ERROR: missing staging HTML: $STAGING" >&2
exit 1
fi
echo "== 1) HTML staging → www =="
mkdir -p "$WWW"
rsync -a --delete "$STAGING/" "$WWW/"
# allow hernani to update future runs without root
chown -R hernani:caddy "$WWW"
chmod -R g+rwX "$WWW"
find "$WWW" -type d -exec chmod g+s {} \;
# ACL default if available
if command -v setfacl >/dev/null 2>&1; then
setfacl -R -m u:hernani:rwx,g:caddy:rwx "$WWW" || true
setfacl -R -d -m u:hernani:rwx,g:caddy:rwx "$WWW" || true
fi
ls -la "$WWW" "$WWW/taler.hacktivism.ch" || true
echo "== 2) Caddyfile (backup + install) =="
ts=$(date +%Y%m%d-%H%M%S)
cp -a "$DST" "/etc/caddy/Caddyfile.bak-monitoring-${ts}"
cp -a "$SRC" "$DST"
chown root:caddy "$DST" 2>/dev/null || chown root:root "$DST"
chmod 644 "$DST"
caddy validate --config "$DST"
systemctl reload caddy
systemctl is-active caddy
echo "== 3) smoke =="
# bash: every continued line except the last must end with \
urls=(
https://taler.hacktivism.ch/monitoring/
https://bank.hacktivism.ch/monitoring/
https://exchange.hacktivism.ch/monitoring/
https://taler.hacktivism.ch/taler-monitoring-surface
https://taler.hacktivism.ch/taler-monitoring-surface/
https://taler.hacktivism.ch/taler-monitoring-surface_err/
https://taler.hacktivism.ch/taler-monitoring-aptdeploy/
https://taler.hacktivism.ch/taler-monitoring-aptdeploy_err/
)
smoke_fail=0
for url in "${urls[@]}"; do
code=$(curl -sS -o /tmp/monchk -w '%{http_code}' -L --max-redirs 3 -m 12 "$url" || echo ERR)
ct=$(file -b /tmp/monchk 2>/dev/null | head -c 40)
echo " $code $url ($ct)"
if grep -qE '"code":[[:space:]]*21' /tmp/monchk 2>/dev/null; then
echo " STILL merchant JSON code 21 — Caddy handles not active?"
smoke_fail=1
fi
done
# bare path must redirect (or land on HTML), not merchant JSON
bare_code=$(curl -sS -o /tmp/monbare -w '%{http_code}' --max-redirs 0 -m 12 \
https://taler.hacktivism.ch/taler-monitoring-surface || echo ERR)
if grep -qE '"code":[[:space:]]*21' /tmp/monbare 2>/dev/null; then
echo "ERROR: bare /taler-monitoring-surface still merchant code 21 (HTTP $bare_code)" >&2
smoke_fail=1
elif [[ "$bare_code" != "302" && "$bare_code" != "301" && "$bare_code" != "200" ]]; then
echo "WARN: bare /taler-monitoring-surface HTTP $bare_code (want 302/301/200 HTML)" >&2
fi
echo " bare_no_follow=$bare_code https://taler.hacktivism.ch/taler-monitoring-surface"
if [[ "$smoke_fail" -ne 0 ]]; then
echo "ERROR: smoke failed" >&2
exit 1
fi
echo "OK apply-monitoring-live done"

View file

@ -0,0 +1,368 @@
#!/usr/bin/env bash
# ensure-apt-deploy-test-containers.sh
#
# Four rootless podman containers on koopa (no nginx / no HTTPS):
#
# Fresh rebuild when repo fingerprint changes:
# koopa-taler-deploy-test-apt-src-trixie
# koopa-taler-deploy-test-apt-src-trixie-testing
#
# Persistent upgrade path (mytops upgrade.sh style):
# koopa-taler-deploy-test-apt-src-trixie-upgrade
# koopa-taler-deploy-test-apt-src-trixie-testing-upgrade
#
# Run as hernani on koopa. From elsewhere use run-aptdeploy.sh (ssh $DEPLOY_SSH or $APT_DEPLOY_SSH).
#
set -euo pipefail
PODMAN_BIN="${APT_DEPLOY_PODMAN:-podman}"
IMG="${APT_DEPLOY_BASE_IMAGE:-docker.io/library/debian:trixie}"
STATE_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/taler-apt-deploy"
URI_BASE="${APT_DEPLOY_URI:-https://deb.taler.net/apt/debian}"
mkdir -p "$STATE_DIR"
# Packages we track for "new version → rebuild fresh containers"
TRACK_PKGS="${APT_DEPLOY_TRACK_PKGS:-taler-merchant libtalermerchant libtalerexchange libdonau}"
# Canonical four containers (keep). Everything else matching legacy names is removed.
CANONICAL_CONTAINERS=(
koopa-taler-deploy-test-apt-src-trixie
koopa-taler-deploy-test-apt-src-trixie-testing
koopa-taler-deploy-test-apt-src-trixie-upgrade
koopa-taler-deploy-test-apt-src-trixie-testing-upgrade
)
# Legacy / typo names from earlier iterations — always delete if present
LEGACY_CONTAINERS=(
koopa-taler-build-test-apt-trixie
koopa-taler-build-test-apt-trixie-testing
koopa-taler-deploy-test-apt-trixie
koopa-taler-deploy-test-apt-trixie-testing
koopa-taler-buidl-test-apt-trixie
koopa-taler-buidl-test-apt-trixie-testing
)
cleanup_legacy() {
local n img
echo "======== cleanup legacy containers / images ========"
for n in "${LEGACY_CONTAINERS[@]}"; do
if "$PODMAN_BIN" container exists "$n" 2>/dev/null; then
echo "remove legacy container $n"
"$PODMAN_BIN" rm -f "$n" 2>/dev/null || true
fi
# old setup images for that name
if "$PODMAN_BIN" image exists "localhost/${n}:setup" 2>/dev/null; then
echo "remove legacy image localhost/${n}:setup"
"$PODMAN_BIN" rmi -f "localhost/${n}:setup" 2>/dev/null || true
fi
done
# dangling setup images not in canonical set
while read -r img; do
[ -n "$img" ] || continue
local base keep=0
base=${img#localhost/}
base=${base%:setup}
for n in "${CANONICAL_CONTAINERS[@]}"; do
[ "$base" = "$n" ] && keep=1 && break
done
if [ "$keep" -eq 0 ] && [[ "$img" == localhost/koopa-taler-*-test-apt* ]]; then
echo "remove non-canonical image $img"
"$PODMAN_BIN" rmi -f "$img" 2>/dev/null || true
fi
done < <("$PODMAN_BIN" images --format '{{.Repository}}:{{.Tag}}' 2>/dev/null | grep -E 'localhost/koopa-taler-.*-test-apt' || true)
}
# Prefer single suite tree: ~/src/taler-monitoring vs ~/taler-monitoring
# (legacy koopa-admin-log paths are not used). Keep the newer git clone; symlink the other.
dedupe_suite_repos() {
local a="$HOME/src/taler-monitoring" b="$HOME/taler-monitoring"
local ta tb
[ "${APT_DEPLOY_DEDUPE_REPOS:-1}" = "1" ] || return 0
if [ -d "$a/.git" ] && [ -d "$b/.git" ] && [ ! -L "$a" ] && [ ! -L "$b" ] \
&& [ "$(readlink -f "$a" 2>/dev/null || echo "$a")" != "$(readlink -f "$b" 2>/dev/null || echo "$b")" ]; then
ta=$(git -C "$a" log -1 --format=%ct 2>/dev/null || echo 0)
tb=$(git -C "$b" log -1 --format=%ct 2>/dev/null || echo 0)
echo "======== dedupe suite repos (both are git clones) ========"
echo " $a HEAD_ts=$ta"
echo " $b HEAD_ts=$tb"
if [ "$ta" -ge "$tb" ]; then
echo "keep $a · remove $b · symlink $b$a"
rm -rf "$b"
ln -sfn "$a" "$b"
else
echo "keep $b · remove $a · symlink $a$b"
rm -rf "$a"
mkdir -p "$(dirname "$a")"
ln -sfn "$b" "$a"
fi
elif [ -d "$a/.git" ] && [ ! -e "$b" ]; then
ln -sfn "$a" "$b"
elif [ -d "$b/.git" ] && [ ! -e "$a" ]; then
mkdir -p "$(dirname "$a")"
ln -sfn "$b" "$a"
fi
}
cleanup_legacy
dedupe_suite_repos
# ---------------------------------------------------------------------------
# Repo fingerprint: Version: lines from suite Packages index
# ---------------------------------------------------------------------------
repo_pkg_version() {
local suite=$1 pkg=$2
curl -fsS --max-time 45 \
"${URI_BASE}/dists/${suite}/main/binary-amd64/Packages" 2>/dev/null \
| awk -v p="$pkg" '
$0 == "Package: " p { want=1; next }
/^Package: / { want=0 }
want && /^Version: / { print $2; exit }
'
}
repo_fingerprint() {
local suite=$1 pkg v line=""
for pkg in $TRACK_PKGS; do
v=$(repo_pkg_version "$suite" "$pkg" || true)
[ -n "$v" ] || v="?"
line="${line}${pkg}=${v};"
done
printf '%s' "$line"
}
container_fingerprint() {
local name=$1
"$PODMAN_BIN" exec "$name" bash -lc '
for p in '"$TRACK_PKGS"'; do
v=$(dpkg-query -W -f="\${Version}" "$p" 2>/dev/null || echo missing)
printf "%s=%s;" "$p" "$v"
done
' 2>/dev/null || echo "missing"
}
# ---------------------------------------------------------------------------
# Bootstrap: install base + taler source + packages (shared)
# ---------------------------------------------------------------------------
install_base_and_merchant() {
local name=$1 suite=$2 srcfile=$3
"$PODMAN_BIN" exec -u root "$name" bash -lc '
set -euo pipefail
export DEBIAN_FRONTEND=noninteractive
apt-get update -qq
apt-get install -y -qq ca-certificates curl wget gnupg systemd systemd-sysv \
dbus procps iproute2
'
"$PODMAN_BIN" exec -u root "$name" bash -lc "
set -euo pipefail
export DEBIAN_FRONTEND=noninteractive
mkdir -p /etc/apt/keyrings
wget -q -O /etc/apt/keyrings/taler-systems.gpg https://taler.net/taler-systems.gpg
cat >/etc/apt/sources.list.d/${srcfile} <<EOF
Architectures: amd64
Components: main
X-Repolib-Name: Taler
Signed-By: /etc/apt/keyrings/taler-systems.gpg
Suites: ${suite}
Types: deb
URIs: ${URI_BASE}
EOF
apt-get update -qq
apt-get install -y \
taler-merchant \
taler-merchant-typst \
taler-merchant-webui \
taler-terms-generator \
postgresql \
s-nail \
jq
"
}
commit_and_systemd() {
local name=$1
"$PODMAN_BIN" stop "$name"
"$PODMAN_BIN" commit "$name" "localhost/${name}:setup"
"$PODMAN_BIN" rm "$name"
"$PODMAN_BIN" run -d --name "$name" \
--hostname "$name" \
--systemd=always \
--cgroupns=host \
"localhost/${name}:setup" \
/lib/systemd/systemd
sleep 5
# enable merchant stack if unit exists
"$PODMAN_BIN" exec "$name" bash -lc '
set +e
systemctl daemon-reload 2>/dev/null
systemctl enable taler-merchant.target 2>/dev/null
systemctl start taler-merchant.target 2>/dev/null
sleep 2
systemctl is-active taler-merchant-httpd.service 2>/dev/null || true
' || true
}
create_from_scratch() {
local name=$1 suite=$2 srcfile=$3
echo "======== CREATE (fresh) $name Suites: $suite ========"
"$PODMAN_BIN" pull "$IMG" >/dev/null
"$PODMAN_BIN" rm -f "$name" 2>/dev/null || true
# drop previous setup image so we do not accumulate duplicates
"$PODMAN_BIN" rmi -f "localhost/${name}:setup" 2>/dev/null || true
"$PODMAN_BIN" run -d --name "$name" --hostname "$name" "$IMG" sleep infinity
install_base_and_merchant "$name" "$suite" "$srcfile"
commit_and_systemd "$name"
printf '%s\n' "$(repo_fingerprint "$suite")" >"$STATE_DIR/${name}.fp"
printf '%s\n' "$(container_fingerprint "$name")" >"$STATE_DIR/${name}.installed"
echo "created $name fp=$(cat "$STATE_DIR/${name}.fp")"
}
# ---------------------------------------------------------------------------
# Upgrade path (mytops-admin-log upgrade.sh merchant packages, no restic/nginx)
# Prints a markdown-ish table of before → after versions.
# ---------------------------------------------------------------------------
upgrade_inside() {
local name=$1
local before after pkg v0 v1
echo "======== UPGRADE $name (mytops-style apt install + upgrade) ========"
before=$("$PODMAN_BIN" exec "$name" bash -lc '
for p in taler-merchant libtalermerchant libtalerexchange libdonau libgnunet \
taler-merchant-webui taler-merchant-typst taler-terms-generator; do
v=$(dpkg-query -W -f="\${Version}" "$p" 2>/dev/null || echo "-")
printf "%s %s\n" "$p" "$v"
done
' 2>/dev/null || true)
# capture upgrade output
set +e
"$PODMAN_BIN" exec -u root "$name" bash -lc '
set -euo pipefail
export DEBIAN_FRONTEND=noninteractive
systemctl stop taler-merchant.target 2>/dev/null || true
systemctl stop taler-merchant-donaukeyupdate.service 2>/dev/null || true
apt-get update -qq
apt-get install -y \
taler-merchant \
taler-merchant-typst \
taler-merchant-webui \
taler-terms-generator \
|| true
if dpkg -l challenger-httpd 2>/dev/null | grep -q "^ii"; then
apt-get remove -y challenger-httpd || true
fi
apt-get upgrade -y
systemctl daemon-reload 2>/dev/null || true
systemctl enable taler-merchant.target 2>/dev/null || true
# try dbconfig if present (may no-op without full config)
command -v taler-merchant-dbconfig >/dev/null && taler-merchant-dbconfig 2>/dev/null || true
systemctl reset-failed "taler-merchant*" 2>/dev/null || true
systemctl start taler-merchant.target 2>/dev/null || true
sleep 2
' 2>&1 | tee "$STATE_DIR/${name}.upgrade.log" | tail -30
set -e
after=$("$PODMAN_BIN" exec "$name" bash -lc '
for p in taler-merchant libtalermerchant libtalerexchange libdonau libgnunet \
taler-merchant-webui taler-merchant-typst taler-terms-generator; do
v=$(dpkg-query -W -f="\${Version}" "$p" 2>/dev/null || echo "-")
printf "%s %s\n" "$p" "$v"
done
' 2>/dev/null || true)
echo
echo "| package | before | after | change |"
echo "|---------|--------|-------|--------|"
while read -r pkg v0; do
[ -n "${pkg:-}" ] || continue
v1=$(printf '%s\n' "$after" | awk -v p="$pkg" '$1==p {print $2; exit}')
v1=${v1:--}
if [ "$v0" = "$v1" ]; then
ch="="
else
ch="UPGRADED"
fi
echo "| $pkg | $v0 | $v1 | $ch |"
done <<<"$before"
printf '%s\n' "$(container_fingerprint "$name")" >"$STATE_DIR/${name}.installed"
}
# ---------------------------------------------------------------------------
# Fresh mode: recreate if missing / not running / repo fp changed
# ---------------------------------------------------------------------------
ensure_fresh() {
local name=$1 suite=$2 srcfile=$3
local want have running
want=$(repo_fingerprint "$suite")
echo "repo fingerprint [$suite]: $want"
if ! "$PODMAN_BIN" container exists "$name" 2>/dev/null; then
create_from_scratch "$name" "$suite" "$srcfile"
return
fi
running=$("$PODMAN_BIN" inspect -f '{{.State.Running}}' "$name" 2>/dev/null || echo false)
have=$(container_fingerprint "$name")
echo "installed in $name: $have"
if [ "$running" != "true" ] \
|| ! "$PODMAN_BIN" exec "$name" bash -lc 'command -v taler-merchant-httpd' >/dev/null 2>&1 \
|| [ "$want" != "$have" ]; then
echo "rebuild $name (running=$running fp_match=$([ "$want" = "$have" ] && echo yes || echo NO))"
create_from_scratch "$name" "$suite" "$srcfile"
else
echo "OK $name up-to-date (fresh container matches repo)"
fi
}
# ---------------------------------------------------------------------------
# Upgrade mode: create once, then always upgrade_inside
# ---------------------------------------------------------------------------
ensure_upgrade() {
local name=$1 suite=$2 srcfile=$3
local running
if ! "$PODMAN_BIN" container exists "$name" 2>/dev/null; then
create_from_scratch "$name" "$suite" "$srcfile"
echo "(initial install for upgrade-track container $name)"
return
fi
running=$("$PODMAN_BIN" inspect -f '{{.State.Running}}' "$name" 2>/dev/null || echo false)
if [ "$running" != "true" ]; then
echo "start $name"
"$PODMAN_BIN" start "$name" || {
echo "recreate broken $name"
create_from_scratch "$name" "$suite" "$srcfile"
return
}
sleep 3
fi
if ! "$PODMAN_BIN" exec "$name" bash -lc 'command -v taler-merchant-httpd' >/dev/null 2>&1; then
echo "recreate $name (no httpd after start)"
create_from_scratch "$name" "$suite" "$srcfile"
return
fi
upgrade_inside "$name"
}
MODE="${1:-all}" # all | fresh | upgrade
case "$MODE" in
all|fresh)
ensure_fresh koopa-taler-deploy-test-apt-src-trixie trixie taler.sources
ensure_fresh koopa-taler-deploy-test-apt-src-trixie-testing trixie-testing taler-testing.sources
;;&
all|upgrade)
ensure_upgrade koopa-taler-deploy-test-apt-src-trixie-upgrade trixie taler.sources
ensure_upgrade koopa-taler-deploy-test-apt-src-trixie-testing-upgrade trixie-testing taler-testing.sources
;;
*)
echo "usage: $0 [all|fresh|upgrade]" >&2
exit 2
;;
esac
echo "done ensure-apt-deploy-test-containers mode=$MODE"

26
host-agent/env.example Normal file
View file

@ -0,0 +1,26 @@
# Copy to: ~/.config/taler-monitoring/env
# This file is NOT inside the git tree and is never overwritten by update-suite.sh.
#
# Full stack examples (same reporting generation for all):
# env/hacktivism.env.example
# env/stagepaysan.env.example
#
# SUITE_GIT_URL=https://git.hacktivism.ch/hernani/taler-monitoring.git
# SUITE_GIT_REF=main
# SUITE_DIR=$HOME/src/taler-monitoring
# SUITE_UPDATE_MODE=reset
#
# TALER_DOMAIN=hacktivism.ch
# INSIDE_PODMAN=1
# INSIDE_MODE=local-podman
# CONTINUE_ON_ERROR=1
# PHASES for the *default* hacktivism timer only (urls inside versions).
# Dedicated wrappers always override PHASES + HTML paths:
# run-surface-monitoring.sh → /taler-monitoring-surface*
# run-aptdeploy-monitoring.sh → /taler-monitoring-aptdeploy*
# PHASES="urls inside versions"
# RUN_TIMEOUT=600
# HTML_OUT=$HOME/monitoring-sites-staging
# MON_HOSTS="bank.hacktivism.ch exchange.hacktivism.ch taler.hacktivism.ch"
# SOURCE_REPO_WEB=https://git.hacktivism.ch/hernani/taler-monitoring
# TALER_MON_LANG=en # en default; fr for FrancPaysan (auto if domain *lefrancpaysan*)

26
host-agent/env/hacktivism.env.example vendored Normal file
View file

@ -0,0 +1,26 @@
# Copy to: ~/.config/taler-monitoring/env (user hernani on koopa)
# Shared reporting generation: RUN_TIMEOUT, line-buffered log, always HTML.
TALER_DOMAIN=hacktivism.ch
INSIDE_PODMAN=1
INSIDE_MODE=local-podman
LOCAL_STACK=1
CONTINUE_ON_ERROR=1
PHASES="urls inside versions"
# aptdeploy HTML: run-aptdeploy-monitoring.sh → /taler-monitoring-aptdeploy*
RUN_TIMEOUT=600
# APT_DEPLOY_ENSURE=1
# APT_DEPLOY_SKIP=0
HTML_OUT=$HOME/monitoring-sites-staging
MON_HOSTS="bank.hacktivism.ch exchange.hacktivism.ch taler.hacktivism.ch"
SUITE_DIR=$HOME/src/taler-monitoring
SUITE_GIT_URL=https://git.hacktivism.ch/hernani/taler-monitoring.git
SUITE_GIT_REF=main
SUITE_UPDATE_MODE=reset
SOURCE_REPO_WEB=https://git.hacktivism.ch/hernani/taler-monitoring
DEPLOY_WWW_ROOT=/var/www/monitoring-sites
TALER_MON_LANG=en

35
host-agent/env/mytops-stage.env.example vendored Normal file
View file

@ -0,0 +1,35 @@
# Copy to: ~/.config/taler-monitoring/env (user taler-monitoring @ betel)
# mytops CHF stage — NOT lifeline / production
#
SUITE_GIT_URL=https://git.hacktivism.ch/hernani/taler-monitoring.git
SUITE_GIT_REF=main
SUITE_DIR=$HOME/src/taler-monitoring
TALER_DOMAIN=stage.taler-ops.ch
MON_HOSTS="stage.my.taler-ops.ch stage.taler-ops.ch"
HTML_OUT=$HOME/monitoring-sites-staging
DEPLOY_WWW_ROOT=/var/www/monitoring-sites
HTML_URL_OK=/monitoring/
HTML_URL_ERR=/monitoring_err/
HTML_OK_DIR=monitoring
HTML_ERR_DIR=monitoring_err
# urls + monpages + fake-franken CHF (rusty.taler-ops.ch)
PHASES="urls monpages devtesting"
SKIP_SSH=1
LOCAL_STACK=0
INSIDE_PODMAN=0
CONTINUE_ON_ERROR=1
RUN_TIMEOUT=600
STRICT_EXIT=1
MONPAGES_REQUIRE_PUBLIC=1
MONPAGES_INVENTORY=job
TALER_MON_LANG=en
AGENT_LABEL=mytops-stage-host-agent
STATE_NAME=taler-monitoring-mytops-stage
SUITE_UPDATE_STRICT=1
CHECK_BANK=0
MERCHANT_REQUIRED=1
CHECK_LANDING=0
# rusty devtesting (SSH Host taler-rusty-devtesting → devtesting@rusty.taler-ops.ch)
DEVTESTING_SSH=taler-rusty-devtesting
DEVTESTING_AMOUNT=CHF:1.00
# DEVTESTING_SKIP=1

30
host-agent/env/stagepaysan.env.example vendored Normal file
View file

@ -0,0 +1,30 @@
# Copy to: ~/.config/taler-monitoring/env (user stagepaysan)
# Never inside the git tree — update-suite never overwrites this file.
#
# Shared reporting generation (run-host-report.sh / run-fp-stage-monitoring.sh):
# RUN_TIMEOUT, line-buffered log, always HTML, commit link on git.hacktivism.ch
TALER_DOMAIN=stage.lefrancpaysan.ch
INSIDE_PODMAN=1
INSIDE_MODE=local-podman
INSIDE_PROFILE=stage-lfp
LOCAL_STACK=0
CONTINUE_ON_ERROR=1
PHASES="urls inside versions"
RUN_TIMEOUT=600
HTML_OUT=$HOME/monitoring-sites-staging
MON_HOSTS="stage.bank.lefrancpaysan.ch stage.exchange.lefrancpaysan.ch stage.monnaie.lefrancpaysan.ch"
# Git clone of taler-monitoring (repo root, not …/taler-monitoring (repo root))
SUITE_DIR=$HOME/src/taler-monitoring
SUITE_GIT_URL=https://git.hacktivism.ch/hernani/taler-monitoring.git
SUITE_GIT_REF=main
SUITE_UPDATE_MODE=reset
SOURCE_REPO_WEB=https://git.hacktivism.ch/hernani/taler-monitoring
# Optional public docroot if this user can write it
# DEPLOY_WWW_ROOT=/var/www/monitoring-sites
# UI language for console + HTML sticky bar (en|fr)
TALER_MON_LANG=fr

114
host-agent/install-host-agent.sh Executable file
View file

@ -0,0 +1,114 @@
#!/usr/bin/env bash
# install-host-agent.sh — install hernani user systemd units on koopa
#
# # on koopa as hernani:
# ~/src/taler-monitoring/host-agent/install-host-agent.sh
# # or from laptop:
# ./install-host-agent.sh --remote $DEPLOY_SSH
#
# Suite tree is ONLY ~/src/taler-monitoring (standalone repo). No koopa-admin-log.
#
set -euo pipefail
ROOT=$(cd "$(dirname "$0")" && pwd)
REMOTE="${1:-}"
[ "${1:-}" = "--remote" ] && REMOTE="${2:-${DEPLOY_SSH:-}}"
SUITE_URL="${SUITE_GIT_URL:-https://git.hacktivism.ch/hernani/taler-monitoring.git}"
install_local() {
local mon home_unit
mon=$(cd "$ROOT/.." && pwd)
if [ -d "$HOME/src/taler-monitoring/.git" ] || [ -d "$HOME/src/taler-monitoring" ]; then
mon="$HOME/src/taler-monitoring"
fi
home_unit="$HOME/.config/systemd/user"
mkdir -p "$home_unit" \
"$HOME/.local/state/taler-hacktivism-monitoring" \
"$HOME/monitoring-sites-staging" \
"$HOME/.config/taler-monitoring" \
"$HOME/src"
# Local env (never in git) — create from example if missing
if [ ! -f "$HOME/.config/taler-monitoring/env" ] && [ -f "$ROOT/env.example" ]; then
cp "$ROOT/env.example" "$HOME/.config/taler-monitoring/env"
echo "created ~/.config/taler-monitoring/env"
fi
# Ensure suite git clone at canonical path
if [ ! -d "$HOME/src/taler-monitoring/.git" ]; then
if [ -d "$mon/.git" ] && [ "$mon" != "$HOME/src/taler-monitoring" ]; then
mkdir -p "$HOME/src"
ln -sfn "$mon" "$HOME/src/taler-monitoring"
echo "symlink $HOME/src/taler-monitoring → $mon"
else
echo "clone $SUITE_URL$HOME/src/taler-monitoring"
git clone "$SUITE_URL" "$HOME/src/taler-monitoring" \
|| echo "WARN: clone failed (auth/network) — using tree at $mon if present"
fi
fi
mon="$HOME/src/taler-monitoring"
# Convenience link (update-suite.sh also creates this)
ln -sfn "$mon" "$HOME/taler-monitoring" 2>/dev/null || true
# Ensure suite is executable
chmod +x "$mon"/taler-monitoring.sh "$mon"/check_*.sh 2>/dev/null || true
chmod +x "$mon/host-agent"/*.sh 2>/dev/null || true
chmod +x "$mon/site-gen"/*.sh "$mon/site-gen/console_to_html.py" 2>/dev/null || true
install -m 644 "$ROOT/taler-monitoring-hacktivism.service" "$home_unit/"
install -m 644 "$ROOT/taler-monitoring-hacktivism.path" "$home_unit/"
install -m 644 "$ROOT/taler-monitoring-hacktivism.timer" "$home_unit/"
install -m 644 "$ROOT/taler-monitoring-surface.service" "$home_unit/"
install -m 644 "$ROOT/taler-monitoring-surface.timer" "$home_unit/"
# optional aptdeploy (not a public mon page layout — v1.8.0 simplified overview)
install -m 644 "$ROOT/taler-monitoring-aptdeploy.service" "$home_unit/"
install -m 644 "$ROOT/taler-monitoring-aptdeploy.timer" "$home_unit/"
systemctl --user daemon-reload
systemctl --user enable --now taler-monitoring-hacktivism.path
systemctl --user enable --now taler-monitoring-hacktivism.timer
systemctl --user enable --now taler-monitoring-surface.timer
systemctl --user enable --now taler-monitoring-aptdeploy.timer
# Remove leftover mail/mattermost units if present (moved to meta/deprecated/)
systemctl --user disable --now taler-monitoring-mattermost.timer 2>/dev/null || true
systemctl --user disable --now taler-monitoring-mail.timer 2>/dev/null || true
rm -f "$home_unit/taler-monitoring-mattermost.service" \
"$home_unit/taler-monitoring-mattermost.timer" \
"$home_unit/taler-monitoring-mail.service" \
"$home_unit/taler-monitoring-mail.timer" 2>/dev/null || true
systemctl --user daemon-reload 2>/dev/null || true
# initial stamp so path exists
"$ROOT/touch-software-stamp.sh"
systemctl --user start taler-monitoring-hacktivism.service || true
systemctl --user start taler-monitoring-surface.service || true
systemctl --user start taler-monitoring-aptdeploy.service || true
echo "--- status ---"
systemctl --user status taler-monitoring-hacktivism.path --no-pager -l | head -15
systemctl --user status taler-monitoring-hacktivism.timer --no-pager -l | head -12
systemctl --user status taler-monitoring-surface.timer --no-pager -l | head -12
systemctl --user status taler-monitoring-aptdeploy.timer --no-pager -l | head -12
systemctl --user list-timers --all | grep taler-monitoring || true
echo "OK host-agent (v1.8.0 simplified):"
echo " · hacktivism 4h → /monitoring on bank/exchange/taler (landing stacks)"
echo " · surface 1h → /taler-monitoring-surface* (ecosystem versions/software)"
echo " · aptdeploy 4h (optional tests; not a public mon-page type)"
echo " · mail/mattermost: not installed (meta/deprecated/; content in surface)"
echo "suite: $mon"
echo "manual surface: systemctl --user start taler-monitoring-surface.service"
echo "after upgrades: $ROOT/touch-software-stamp.sh"
}
if [ -n "$REMOTE" ] && [ "$REMOTE" != "--remote" ]; then
echo "install on $REMOTE"
# ROOT = …/taler-monitoring/host-agent → suite root is parent
rsync -az --delete \
--exclude secrets.env \
--exclude 'android-test/artifacts' \
--exclude '.git' \
"$ROOT/../" \
"${REMOTE}:src/taler-monitoring/"
ssh -o BatchMode=yes "$REMOTE" \
'bash $HOME/src/taler-monitoring/host-agent/install-host-agent.sh'
else
install_local
fi

View file

@ -0,0 +1,45 @@
#!/usr/bin/env bash
# run-aptdeploy-monitoring.sh — apt-src deploy tests → public HTML
# Public only on taler.hacktivism.ch:
# https://taler.hacktivism.ch/taler-monitoring-aptdeploy/
# https://taler.hacktivism.ch/taler-monitoring-aptdeploy_err/
#
# CLI one-shot without HTML remains: run-aptdeploy.sh [all|fresh|upgrade|check-only]
#
set -uo pipefail
AGENT_DIR=$(cd "$(dirname "$0")" && pwd)
export AGENT_LABEL="${AGENT_LABEL:-aptdeploy-host-agent}"
export STATE_NAME="${STATE_NAME:-taler-aptdeploy-monitoring}"
export TALER_DOMAIN="${TALER_DOMAIN:-hacktivism.ch}"
# containers are local on koopa
export INSIDE_PODMAN=1
export INSIDE_MODE=local-podman
export LOCAL_STACK=1
export SKIP_SSH=0
export CONTINUE_ON_ERROR="${CONTINUE_ON_ERROR:-1}"
# four containers + checks; allow long wall
export RUN_TIMEOUT="${RUN_TIMEOUT:-1800}"
# dedicated job: always aptdeploy only (force — do not inherit main-timer env)
export PHASES="aptdeploy monpages"
# Public HTML only on taler.hacktivism.ch (not bank/exchange landings)
export MON_HOSTS="taler.hacktivism.ch"
export HTML_OUT="${HTML_OUT:-$HOME/monitoring-sites-staging}"
export DEPLOY_WWW_ROOT="${DEPLOY_WWW_ROOT:-/var/www/monitoring-sites}"
export HTML_OK_DIR="taler-monitoring-aptdeploy"
export HTML_ERR_DIR="taler-monitoring-aptdeploy_err"
export HTML_URL_OK="/taler-monitoring-aptdeploy/"
export HTML_URL_ERR="/taler-monitoring-aptdeploy_err/"
export PAGE_LABEL="taler-monitoring-aptdeploy"
# monpages: only this jobs paths on taler.hacktivism.ch
export MONPAGES_INVENTORY="job"
# Ensure all four apt-src containers (fresh rebuild + upgrade track)
if [ "${APT_DEPLOY_ENSURE:-1}" = "1" ] \
&& [ -x "$AGENT_DIR/ensure-apt-deploy-test-containers.sh" ]; then
bash "$AGENT_DIR/ensure-apt-deploy-test-containers.sh" all \
|| echo "WARN: ensure-apt-deploy-test-containers failed (aptdeploy may ERROR)" >&2
fi
exec bash "$AGENT_DIR/run-host-report.sh" "$@"

150
host-agent/run-aptdeploy.sh Executable file
View file

@ -0,0 +1,150 @@
#!/usr/bin/env bash
# run-aptdeploy.sh — ensure containers + aptdeploy phase only.
#
# Keep monitoring current:
# 1) Prefer Forgejo origin/main (update-suite reset)
# 2) If Forgejo is behind (no aptdeploy yet), re-apply suite-overlay saved
# from this agent tree before reset (rsync from laptop fills that tree)
# 3) Run ensure + taler-monitoring.sh aptdeploy
#
# Host: on koopa → local; else ssh APT_DEPLOY_SSH + rsync mon tree first.
#
set -euo pipefail
AGENT_DIR=$(cd "$(dirname "$0")" && pwd)
BOOT_MON=$(cd "$AGENT_DIR/.." && pwd)
MODE="${1:-all}"
APT_DEPLOY_SSH="${APT_DEPLOY_SSH:-${DEPLOY_SSH:-}}"
OVERLAY_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/taler-monitoring/suite-overlay"
on_koopa() {
local h
h=$(hostname -s 2>/dev/null || hostname 2>/dev/null || echo "")
case "$h" in koopa|koopa.*) return 0 ;; esac
[ "${KOOPA_FORCE_LOCAL:-0}" = "1" ] && return 0
return 1
}
mon_has_aptdeploy() {
local mon=$1
[ -f "$mon/taler-monitoring.sh" ] && grep -qE 'aptdeploy\|apt-deploy' "$mon/taler-monitoring.sh"
}
save_overlay_from() {
local src=$1
mon_has_aptdeploy "$src" || return 0
mkdir -p "$OVERLAY_DIR"
rsync -a --delete \
--exclude secrets.env \
--exclude 'android-test/artifacts' \
--exclude 'android-test/apks' \
--exclude 'android-test/out*' \
--exclude '__pycache__' \
"$src/" "$OVERLAY_DIR/"
echo "saved suite-overlay from $src$OVERLAY_DIR"
}
apply_overlay_to() {
local dest=$1
[ -f "$OVERLAY_DIR/taler-monitoring.sh" ] || return 1
mon_has_aptdeploy "$OVERLAY_DIR" || return 1
mkdir -p "$dest"
rsync -a \
--exclude secrets.env \
--exclude 'android-test/artifacts' \
--exclude 'android-test/apks' \
--exclude 'android-test/out*' \
--exclude '__pycache__' \
"$OVERLAY_DIR/" "$dest/"
echo "applied suite-overlay → $dest (Forgejo lag; push origin/main when possible)"
return 0
}
pick_mon_tree() {
# Preserve working mon before reset wipes tracked files
save_overlay_from "$BOOT_MON"
# shellcheck source=update-suite.sh
source "$AGENT_DIR/update-suite.sh"
local suite_mon="$SUITE_DIR"
mkdir -p "$suite_mon"
if mon_has_aptdeploy "$suite_mon"; then
MON_DIR="$suite_mon"
echo "using Forgejo suite mon @ ${COMMIT_SHORT:-?} · $MON_DIR"
elif apply_overlay_to "$suite_mon" && mon_has_aptdeploy "$suite_mon"; then
MON_DIR="$suite_mon"
echo "using suite mon + overlay @ ${COMMIT_SHORT:-?} · $MON_DIR"
elif mon_has_aptdeploy "$BOOT_MON"; then
MON_DIR="$BOOT_MON"
echo "WARN: using boot mon $MON_DIR (overlay apply failed)" >&2
else
echo "ERROR: no mon tree with aptdeploy (Forgejo + overlay empty)" >&2
echo " rsync suite from laptop or push to git.hacktivism.ch" >&2
exit 2
fi
AGENT_DIR="$MON_DIR/host-agent"
export MON_DIR AGENT_DIR SUITE_DIR COMMIT COMMIT_SHORT COMMIT_URL
chmod +x "$AGENT_DIR"/*.sh "$MON_DIR"/taler-monitoring.sh "$MON_DIR"/check_*.sh 2>/dev/null || true
}
run_local() {
export PATH="${HOME}/.local/bin:/usr/local/bin:/usr/bin:/bin:${PATH:-}"
export PYTHONUNBUFFERED=1
export PROGRESS_OFF="${PROGRESS_OFF:-0}"
echo "======== aptdeploy local on $(hostname) mode=$MODE ========"
pick_mon_tree
if [ "$MODE" != "check-only" ]; then
bash "$AGENT_DIR/ensure-apt-deploy-test-containers.sh" "$MODE"
fi
cd "$MON_DIR"
export CONTINUE_ON_ERROR="${CONTINUE_ON_ERROR:-1}"
export INSIDE_PODMAN="${INSIDE_PODMAN:-1}"
export LOCAL_STACK="${LOCAL_STACK:-1}"
export RUN_TIMEOUT="${RUN_TIMEOUT:-600}"
echo "run: $MON_DIR/taler-monitoring.sh -d hacktivism.ch aptdeploy · suite=${COMMIT_SHORT:-?}"
./taler-monitoring.sh -d hacktivism.ch aptdeploy
}
run_remote() {
echo "======== aptdeploy remote via ssh ${APT_DEPLOY_SSH} mode=$MODE ========"
if [ "${APT_DEPLOY_RSYNC:-1}" = "1" ]; then
echo "rsync mon suite → ${APT_DEPLOY_SSH}:src/taler-monitoring/"
rsync -az \
--exclude secrets.env \
--exclude 'android-test/artifacts' \
--exclude 'android-test/apks' \
--exclude 'android-test/out*' \
--exclude '__pycache__' \
"$BOOT_MON/" \
"${APT_DEPLOY_SSH}:src/taler-monitoring/" \
|| echo "WARN: rsync failed" >&2
fi
ssh -o BatchMode=yes -o ConnectTimeout=25 "$APT_DEPLOY_SSH" bash -s -- "$MODE" <<'EOS'
set -euo pipefail
MODE=${1:-all}
export PATH="${HOME}/.local/bin:/usr/local/bin:/usr/bin:/bin:${PATH:-}"
export KOOPA_FORCE_LOCAL=1
AGENT="$HOME/src/taler-monitoring/host-agent"
if [ ! -x "$AGENT/run-aptdeploy.sh" ]; then
AGENT="$HOME/src/taler-monitoring/host-agent"
fi
if [ ! -x "$AGENT/run-aptdeploy.sh" ]; then
echo "ERROR: run-aptdeploy.sh missing on remote" >&2
exit 1
fi
echo "remote AGENT=$AGENT"
exec bash "$AGENT/run-aptdeploy.sh" "$MODE"
EOS
}
if on_koopa; then
run_local
else
run_remote
fi

View file

@ -0,0 +1,27 @@
#!/usr/bin/env bash
# FrancPaysan STAGE host-agent (user stagepaysan via francpaysan-stage-user).
# Shared reporting: run-host-report.sh (timeout, line-buffered log, first-run HTML).
set -uo pipefail
# Language: en|fr (selectable). Default for this wrapper: fr
export TALER_MON_LANG="${TALER_MON_LANG:-fr}"
export TALER_MON_LANG_SET=1
AGENT_DIR=$(cd "$(dirname "$0")" && pwd)
export AGENT_LABEL="${AGENT_LABEL:-fp-stage-host-agent}"
export STATE_NAME="${STATE_NAME:-taler-monitoring-stage-lfp}"
export TALER_DOMAIN="${TALER_DOMAIN:-stage.lefrancpaysan.ch}"
export INSIDE_PODMAN="${INSIDE_PODMAN:-1}"
export INSIDE_MODE="${INSIDE_MODE:-local-podman}"
export INSIDE_PROFILE="${INSIDE_PROFILE:-stage-lfp}"
export LOCAL_STACK="${LOCAL_STACK:-0}"
export CONTINUE_ON_ERROR="${CONTINUE_ON_ERROR:-1}"
# monpages obligatory (ERROR): only FP stage mon hosts below — never GOA inventory
export MONPAGES_REQUIRE_PUBLIC="${MONPAGES_REQUIRE_PUBLIC:-1}"
export RUN_TIMEOUT="${RUN_TIMEOUT:-600}"
export PHASES="${PHASES:-urls inside versions monpages}"
export MON_HOSTS="${MON_HOSTS:-stage.bank.lefrancpaysan.ch stage.exchange.lefrancpaysan.ch stage.monnaie.lefrancpaysan.ch}"
export HTML_OUT="${HTML_OUT:-$HOME/monitoring-sites-staging}"
export DEPLOY_WWW_ROOT="${DEPLOY_WWW_ROOT:-/var/www/monitoring-sites}"
exec bash "$AGENT_DIR/run-host-report.sh" "$@"

View file

@ -0,0 +1,31 @@
#!/usr/bin/env bash
# GOA / hacktivism host-agent on koopa (hernani).
# Stack defaults only — reporting pipeline is shared: run-host-report.sh
set -uo pipefail
# Language: en|fr (selectable). Default for this wrapper: en
export TALER_MON_LANG="${TALER_MON_LANG:-en}"
export TALER_MON_LANG_SET=1
AGENT_DIR=$(cd "$(dirname "$0")" && pwd)
export AGENT_LABEL="${AGENT_LABEL:-hacktivism-host-agent}"
export STATE_NAME="${STATE_NAME:-taler-hacktivism-monitoring}"
export TALER_DOMAIN="${TALER_DOMAIN:-hacktivism.ch}"
export INSIDE_PODMAN="${INSIDE_PODMAN:-1}"
export INSIDE_MODE="${INSIDE_MODE:-local-podman}"
export LOCAL_STACK="${LOCAL_STACK:-1}"
export CONTINUE_ON_ERROR="${CONTINUE_ON_ERROR:-1}"
export RUN_TIMEOUT="${RUN_TIMEOUT:-600}"
# stack checks + full monpages inventory (landings + surface + aptdeploy bare/slash).
# Specialized pages also have their own timers; full outside-in inventory lives here.
# run-aptdeploy-monitoring.sh → monpages job-only for /taler-monitoring-aptdeploy*
# run-surface-monitoring.sh → monpages job-only for /taler-monitoring-surface*
export PHASES="${PHASES:-urls inside versions monpages}"
export MON_HOSTS="${MON_HOSTS:-bank.hacktivism.ch exchange.hacktivism.ch taler.hacktivism.ch}"
export HTML_OUT="${HTML_OUT:-$HOME/monitoring-sites-staging}"
export DEPLOY_WWW_ROOT="${DEPLOY_WWW_ROOT:-/var/www/monitoring-sites}"
export APT_DEPLOY_ENSURE=0
# full GOA mon catalog (not job-only)
export MONPAGES_INVENTORY="${MONPAGES_INVENTORY:-auto}"
exec bash "$AGENT_DIR/run-host-report.sh" "$@"

495
host-agent/run-host-report.sh Executable file
View file

@ -0,0 +1,495 @@
#!/usr/bin/env bash
# run-host-report.sh — global reporting generation for all stacks
# (GOA/hacktivism, FP stage, mytops stage, …).
#
# Shared behaviour (always):
# 1) update-suite → latest git.hacktivism.ch (local ~/.config never overwritten)
# 2) line-buffered run log (written continuously)
# 3) RUN_TIMEOUT wall clock for taler-monitoring.sh (default 600s)
# 4) always write HTML (first run / empty staging too) + Forgejo commit link
# 5) rsync to DEPLOY_WWW_ROOT (ERROR if not writable — v1.3.1)
# 6) monpages post-check: public FQDN must serve monitoring HTML
# 7) exit code: STRICT_EXIT default 1 (v1.7.1) — unit fails if publish/public pages fail
#
# Configure via env file (not in git):
# ${XDG_CONFIG_HOME:-$HOME/.config}/taler-monitoring/env
# or TALER_MONITORING_ENV=/path/to/env
#
# Thin wrappers only set stack defaults then exec this script:
# run-hacktivism-monitoring.sh
# run-fp-stage-monitoring.sh
#
set -uo pipefail
export PATH="${HOME}/.local/bin:/usr/local/bin:/usr/bin:/bin:${PATH:-}"
export PYTHONUNBUFFERED=1
AGENT_DIR=$(cd "$(dirname "$0")" && pwd)
AGENT_MON=$(cd "$AGENT_DIR/.." && pwd)
CFG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/taler-monitoring"
ENV_FILE="${TALER_MONITORING_ENV:-$CFG_DIR/env}"
mkdir -p "$CFG_DIR"
# Thin wrappers (surface, aptdeploy-only, …) export stack knobs before exec.
# Snapshot them so ~/.config/taler-monitoring/env cannot clobber dedicated jobs
# (env often has PHASES="urls inside versions aptdeploy" for the main timer).
_wrap_keys=(
PHASES HTML_OUT HTML_OK_DIR HTML_ERR_DIR HTML_URL_OK HTML_URL_ERR PAGE_LABEL
RUN_TIMEOUT STATE_NAME AGENT_LABEL MON_HOSTS TALER_DOMAIN DEPLOY_WWW_ROOT
INSIDE_PODMAN INSIDE_MODE LOCAL_STACK SKIP_SSH CONTINUE_ON_ERROR AUTH401_CONTINUE
APT_DEPLOY_ENSURE SURFACE_SCOPE TALER_DOMAIN_FROM_CLI STRICT_EXIT SOURCE_SUITE_PATH
MONPAGES_INVENTORY MONPAGES_CHECK_BARE MONPAGES_BARE_STRICT MONPAGES_REQUIRE_PUBLIC
CHECK_BANK MERCHANT_REQUIRED CHECK_LANDING
)
for _k in "${_wrap_keys[@]}"; do
if [ -n "${!_k+x}" ]; then
printf -v "_WRAP_SET_${_k}" '%s' 1
printf -v "_WRAP_VAL_${_k}" '%s' "${!_k}"
fi
done
unset _k
if [ -f "$ENV_FILE" ]; then
# shellcheck disable=SC1090
set -a
# shellcheck source=/dev/null
source "$ENV_FILE"
set +a
fi
# Tell update-suite.sh not to re-source env (would clobber wrapper PHASES etc.)
export _TALER_MON_ENV_LOADED=1
# Restore wrapper overrides (dedicated jobs win over shared env file)
for _k in "${_wrap_keys[@]}"; do
_set_var="_WRAP_SET_${_k}"
_val_var="_WRAP_VAL_${_k}"
if [ "${!_set_var:-}" = "1" ]; then
printf -v "$_k" '%s' "${!_val_var}"
export "$_k"
fi
done
unset _k _set_var _val_var
# --- global report defaults (env file / wrapper wins if already set) ---
export CONTINUE_ON_ERROR="${CONTINUE_ON_ERROR:-1}"
export AUTH401_CONTINUE="${AUTH401_CONTINUE:-${CONTINUE_ON_ERROR}}"
export RUN_TIMEOUT="${RUN_TIMEOUT:-600}"
export TALER_DOMAIN="${TALER_DOMAIN:-hacktivism.ch}"
# UI language: selectable via TALER_MON_LANG / --lang (en|fr).
# If unset, auto: FrancPaysan domains → fr, else en.
if [ "${TALER_MON_LANG_SET:-0}" != "1" ] && [ -z "${TALER_MON_LANG:-}" ]; then
case "$TALER_DOMAIN" in
*lefrancpaysan*|*francpaysan*) export TALER_MON_LANG=fr ;;
*) export TALER_MON_LANG=en ;;
esac
elif [ -n "${TALER_MON_LANG:-}" ]; then
case "$TALER_MON_LANG" in
fr|FR|fra|french) export TALER_MON_LANG=fr ;;
*) export TALER_MON_LANG=en ;;
esac
export TALER_MON_LANG_SET=1
fi
export TALER_MON_LANG
PHASES="${PHASES:-urls inside versions monpages}"
HTML_BASE="${HTML_OUT:-$HOME/monitoring-sites-staging}"
MON_HOSTS="${MON_HOSTS:-}"
SUITE_PATH="${SOURCE_SUITE_PATH:-.}"
AGENT_LABEL="${AGENT_LABEL:-host-report}"
STATE_NAME="${STATE_NAME:-taler-monitoring-${TALER_DOMAIN}}"
STATE_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/${STATE_NAME}"
LOG_DIR="$STATE_DIR/logs"
DEPLOY_WWW="${DEPLOY_WWW_ROOT:-/var/www/monitoring-sites}"
# HTML path layout under each host (monitoring / surface / …)
HTML_OK_DIR="${HTML_OK_DIR:-monitoring}"
HTML_ERR_DIR="${HTML_ERR_DIR:-monitoring_err}"
HTML_URL_OK="${HTML_URL_OK:-/monitoring/}"
HTML_URL_ERR="${HTML_URL_ERR:-/monitoring_err/}"
PAGE_LABEL="${PAGE_LABEL:-monitoring}"
SUITE_VERSION="${SUITE_VERSION:-}"
SUITE_VERSION_URL="${SUITE_VERSION_URL:-}"
if [ -z "$SUITE_VERSION" ] && [ -d "${ROOT:-$AGENT_MON}/.git" ]; then
_vd="${ROOT:-$AGENT_MON}"
SUITE_VERSION=$(git -C "$_vd" describe --tags --exact-match 2>/dev/null || git -C "$_vd" describe --tags --abbrev=0 2>/dev/null || true)
fi
if [ -z "$SUITE_VERSION" ] && [ -f "${ROOT:-$AGENT_MON}/VERSION" ]; then
SUITE_VERSION="v$(tr -d '[:space:]' <"${ROOT:-$AGENT_MON}/VERSION" | sed 's/^v//')"
fi
[ -z "$SUITE_VERSION" ] && SUITE_VERSION="unknown"
case "$SUITE_VERSION" in unknown|v*) ;; *) SUITE_VERSION="v$SUITE_VERSION" ;; esac
# Prefer commit URL over /src/tag/… (tags lag push → sticky version 404 on Forgejo).
if [ -z "$SUITE_VERSION_URL" ]; then
_web="${SOURCE_REPO_WEB:-https://git.hacktivism.ch/hernani/taler-monitoring}"
if [ -n "${COMMIT:-}" ] && [ "$COMMIT" != "unknown" ]; then
SUITE_VERSION_URL="${_web}/src/commit/${COMMIT}"
elif [ "$SUITE_VERSION" != "unknown" ]; then
SUITE_VERSION_URL="${_web}/src/tag/${SUITE_VERSION}"
fi
fi
export SUITE_VERSION SUITE_VERSION_URL
# Sensible MON_HOSTS from domain if not set
if [ -z "$MON_HOSTS" ]; then
case "$TALER_DOMAIN" in
hacktivism.ch|koopa)
MON_HOSTS="bank.hacktivism.ch exchange.hacktivism.ch taler.hacktivism.ch"
;;
stage.lefrancpaysan.ch|stage.*lefrancpaysan*)
MON_HOSTS="stage.bank.lefrancpaysan.ch stage.exchange.lefrancpaysan.ch stage.monnaie.lefrancpaysan.ch"
;;
lefrancpaysan.ch)
MON_HOSTS="bank.lefrancpaysan.ch exchange.lefrancpaysan.ch monnaie.lefrancpaysan.ch"
;;
*)
MON_HOSTS="${TALER_DOMAIN}"
;;
esac
fi
mkdir -p "$LOG_DIR" "$HTML_BASE"
STAMP=$(date +%Y%m%d-%H%M%S)
LOG="$LOG_DIR/run-${STAMP}.log"
UPD_LOG="$LOG_DIR/suite-update-${STAMP}.log"
: >"$LOG"
# --- suite auto-upgrade BEFORE the HTML log (git noise must not pollute mon pages) ---
OVERLAY_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/taler-monitoring/suite-overlay"
if [ -f "$AGENT_MON/taler-monitoring.sh" ]; then
mkdir -p "$OVERLAY_DIR"
rsync -a \
--exclude secrets.env --exclude 'android-test/artifacts' --exclude 'android-test/apks' \
--exclude 'android-test/out*' --exclude '__pycache__' \
"$AGENT_MON/" "$OVERLAY_DIR/" 2>/dev/null || true
fi
# shellcheck source=update-suite.sh
if ! source "$AGENT_DIR/update-suite.sh" >"$UPD_LOG" 2>&1; then
echo "ERROR: suite auto-update failed — refusing to run with stale tree" >&2
echo " detail: $UPD_LOG" >&2
tail -n 30 "$UPD_LOG" >&2 || true
echo " fix git/network or set SUITE_UPDATE_STRICT=0 only as emergency" >&2
exit 1
fi
if [ -d "${SUITE_DIR:-}/host-agent" ]; then
AGENT_DIR="${SUITE_DIR}/host-agent"
fi
# Now open the public HTML log (clean agent header + suite summary only)
if command -v stdbuf >/dev/null 2>&1; then
exec > >(stdbuf -oL -eL tee -a "$LOG") 2>&1
else
exec > >(tee -a "$LOG") 2>&1
fi
echo "======== $(date -Iseconds 2>/dev/null || date) ${AGENT_LABEL} ========"
echo "env_file=$ENV_FILE"
echo "domain=$TALER_DOMAIN · phases=$PHASES · RUN_TIMEOUT=${RUN_TIMEOUT}s"
echo "hosts=$MON_HOSTS"
echo "flags INSIDE_PODMAN=${INSIDE_PODMAN:-} INSIDE_MODE=${INSIDE_MODE:-} LOCAL_STACK=${LOCAL_STACK:-} SKIP_SSH=${SKIP_SSH:-} INSIDE_PROFILE=${INSIDE_PROFILE:-}"
# One-line suite pin (no git M/checkout noise)
echo "suite ${SUITE_VERSION:-?} @ ${COMMIT_SHORT:-?} ${COMMIT_URL:-}"
echo " dir=${SUITE_DIR:-}"
SUITE_MON="${SUITE_DIR}"
ROOT="$SUITE_MON"
_need_overlay=0
# Detect missing features in Forgejo tree (untracked helper files alone are not enough —
# taler-monitoring.sh must accept the phase name).
case " $PHASES " in
*" aptdeploy "*|*" apt-deploy "*|*" apt_src "*)
if ! grep -qE 'aptdeploy\|apt-deploy' "$SUITE_MON/taler-monitoring.sh" 2>/dev/null; then
_need_overlay=1
fi
;;
esac
case " $PHASES " in
*" surface "*|*" ecosystem "*)
if ! grep -qE 'surface\|ecosystem' "$SUITE_MON/taler-monitoring.sh" 2>/dev/null \
|| [ ! -f "$SUITE_MON/check_surface.sh" ]; then
_need_overlay=1
fi
;;
esac
# Wrapper-preserve + HTML path knobs may live only in local overlay until pushed
if [ -f "$OVERLAY_DIR/host-agent/run-host-report.sh" ] \
&& ! grep -q '_wrap_keys\|_WRAP_SET_' "$SUITE_MON/host-agent/run-host-report.sh" 2>/dev/null \
&& grep -q '_wrap_keys\|_WRAP_SET_' "$OVERLAY_DIR/host-agent/run-host-report.sh" 2>/dev/null; then
_need_overlay=1
fi
if [ "$_need_overlay" = "1" ] && [ -f "$OVERLAY_DIR/taler-monitoring.sh" ]; then
echo "WARN: Forgejo @ ${COMMIT_SHORT:-?} lagging local suite-overlay (phases=[$PHASES])" >&2
echo " re-applying suite-overlay → $SUITE_MON" >&2
mkdir -p "$SUITE_MON"
rsync -a \
--exclude secrets.env --exclude 'android-test/artifacts' --exclude 'android-test/apks' \
--exclude 'android-test/out*' --exclude '__pycache__' \
"$OVERLAY_DIR/" "$SUITE_MON/"
ROOT="$SUITE_MON"
elif [ "$_need_overlay" = "1" ]; then
echo "WARN: Forgejo missing features and no suite-overlay — using agent mon $AGENT_MON" >&2
ROOT="$AGENT_MON"
else
echo "using suite mon @ ${COMMIT_SHORT:-?} · $ROOT"
# Do NOT rsync suite-overlay over a fresh Forgejo tree — that reintroduced
# stale site-gen/host-agent (bootstrap sticky, old monpages) on stage/betel.
fi
unset _need_overlay
if [ -f "$ROOT/site-gen/console_to_html.py" ]; then
SITE_GEN="$ROOT/site-gen"
elif [ -f "$AGENT_MON/site-gen/console_to_html.py" ]; then
SITE_GEN="$AGENT_MON/site-gen"
echo "note: using agent site-gen at $SITE_GEN"
elif [ -f "$SUITE_MON/site-gen/console_to_html.py" ]; then
SITE_GEN="$SUITE_MON/site-gen"
else
SITE_GEN=""
fi
if [ ! -x "$ROOT/taler-monitoring.sh" ]; then
if [ -x "$AGENT_MON/taler-monitoring.sh" ]; then
ROOT="$AGENT_MON"
echo "note: using agent mon tree $ROOT"
else
echo "ERROR: suite missing at $ROOT" >&2
COMMIT="${COMMIT:-unknown}"
COMMIT_SHORT="${COMMIT_SHORT:-unknown}"
COMMIT_URL="${COMMIT_URL:-}"
fi
fi
echo "commit=${COMMIT_SHORT:-?} · ${COMMIT_URL:-}"
ec=1
if [ -x "${ROOT:-}/taler-monitoring.sh" ]; then
cd "$ROOT" || exit 1
chmod +x taler-monitoring.sh check_*.sh host-agent/*.sh site-gen/*.sh site-gen/*.py 2>/dev/null || true
# Overlay newer inside/versions host-podman bits if suite clone is stale
for f in check_inside.sh check_versions.sh taler-monitoring.sh lib.sh; do
if [ -f "$AGENT_MON/$f" ] && [ -f "$ROOT/$f" ]; then
if ! grep -q 'host-podman\|INSIDE_ACCESS\|RUN_TIMEOUT' "$ROOT/$f" 2>/dev/null \
&& grep -q 'host-podman\|INSIDE_ACCESS\|RUN_TIMEOUT' "$AGENT_MON/$f" 2>/dev/null; then
echo "note: overlay $f from agent install"
cp -f "$AGENT_MON/$f" "$ROOT/$f"
chmod +x "$ROOT/$f" 2>/dev/null || true
fi
fi
done
# monpages inside PHASES runs *before* htmlify/publish — content failures would
# race with concurrent publishes or reflect last run's incomplete redirect stub.
# Soft-content there; post-check after rsync stays hard (authoritative).
case " $PHASES " in
*" monpages "*|*" pages "*) export MONPAGES_PRE_PUBLISH=1 ;;
*) unset MONPAGES_PRE_PUBLISH 2>/dev/null || true ;;
esac
# shellcheck disable=SC2086
./taler-monitoring.sh -d "$TALER_DOMAIN" $PHASES
ec=$?
unset MONPAGES_PRE_PUBLISH 2>/dev/null || true
fi
# --- always generate HTML (first run / missing pages / every run) ---
write_bootstrap_html() {
local out="$1" host="$2" mode="$3"
mkdir -p "$(dirname "$out")"
local cshort="${COMMIT_SHORT:-unknown}"
local curl="${COMMIT_URL:-}"
local clink="$cshort"
local gen iso
# Local wall clock (CEST/CET · MESZ/MEZ on CH/DE hosts), not UTC Z
if TZ=Europe/Zurich date +"%Y-%m-%d %H:%M:%S %Z" >/dev/null 2>&1; then
gen=$(TZ=Europe/Zurich date +"%Y-%m-%d %H:%M:%S %Z")
iso=$(TZ=Europe/Zurich date +"%Y-%m-%dT%H:%M:%S%z" | sed 's/\([+-][0-9][0-9]\)\([0-9][0-9]\)$/\1:\2/')
else
gen=$(date +"%Y-%m-%d %H:%M:%S %Z" 2>/dev/null || date)
iso=$(date -Iseconds 2>/dev/null || date)
fi
if [ -n "$curl" ]; then
clink="<a href=\"$(printf '%s' "$curl" | sed 's/&/\&amp;/g')\">$cshort</a>"
fi
local tmp
tmp=$(mktemp "${out}.XXXXXX")
cat >"$tmp" <<EOF
<!DOCTYPE html>
<html lang="en"><head><meta charset="utf-8"/>
<meta name="generated" content="${iso}"/>
<meta name="taler-mon-page" content="incomplete"/>
<title>monitoring ${mode} · ${host}</title>
<style>
body{margin:0;background:#0c0c0c;color:#c8c8c8;font-family:ui-monospace,monospace}
a{color:#61afef}.err{color:#ff5c5c}
.sticky-bar{position:sticky;top:0;z-index:100;padding:10px 14px;background:#2a1010f2;border-bottom:2px solid #a33}
.sticky-bar .pill{font-weight:800;color:#ff5c5c;margin-right:8px}
.version-link{color:#61afef}
main{padding:1.5rem}
</style></head><body>
<!-- taler-mon:top -->
<div class="sticky-bar" id="status-bar">
<span class="pill">BOOTSTRAP</span>
${PAGE_LABEL:-monitoring} · ${host} ·
<span class="version-link muted">bootstrap</span> ·
<span data-generated-iso="${iso}">generated ${gen}</span>
· source ${clink}
</div>
<main>
<p class="err">Bootstrap page (converter missing or first install). See host-agent log.</p>
<pre style="white-space:pre-wrap;font-size:12px">$(tail -n 80 "$LOG" 2>/dev/null | sed 's/&/\&amp;/g;s/</\&lt;/g' || true)</pre>
</main>
<!-- taler-mon:bottom:incomplete -->
</body></html>
EOF
mv -f "$tmp" "$out"
echo "bootstrap html → $out"
}
htmlify_host() {
local host="$1"
local mon="$HTML_BASE/$host/${HTML_OK_DIR}/index.html"
local mon_err="$HTML_BASE/$host/${HTML_ERR_DIR}/index.html"
mkdir -p "$HTML_BASE/$host/${HTML_OK_DIR}" "$HTML_BASE/$host/${HTML_ERR_DIR}"
if [ -n "$SITE_GEN" ] && [ -f "$SITE_GEN/console_to_html.py" ]; then
python3 "$SITE_GEN/console_to_html.py" \
--lang "${TALER_MON_LANG:-en}" \
--log "$LOG" \
--out "$mon_err" \
--hostname "$host" \
--mode err \
--commit "${COMMIT:-}" \
--commit-url "${COMMIT_URL:-}" \
--suite-version "${SUITE_VERSION:-unknown}" \
--suite-version-url "${SUITE_VERSION_URL:-}" \
--suite-path "$SUITE_PATH" \
--page-label "$PAGE_LABEL" \
--path-err "$HTML_URL_ERR" \
--link-other "$HTML_URL_OK"
if [ "$ec" -eq 0 ]; then
python3 "$SITE_GEN/console_to_html.py" \
--lang "${TALER_MON_LANG:-en}" \
--log "$LOG" \
--out "$mon" \
--hostname "$host" \
--mode ok \
--commit "${COMMIT:-}" \
--commit-url "${COMMIT_URL:-}" \
--suite-version "${SUITE_VERSION:-unknown}" \
--suite-version-url "${SUITE_VERSION_URL:-}" \
--suite-path "$SUITE_PATH" \
--page-label "$PAGE_LABEL" \
--path-err "$HTML_URL_ERR" \
--link-other ""
rm -rf "$HTML_BASE/$host/${HTML_ERR_DIR}"
echo "html $host${HTML_URL_OK} only (clean · ${COMMIT_SHORT:-?})"
else
python3 "$SITE_GEN/console_to_html.py" \
--lang "${TALER_MON_LANG:-en}" \
--log "$LOG" \
--out "$mon" \
--hostname "$host" \
--mode redirect \
--commit "${COMMIT:-}" \
--commit-url "${COMMIT_URL:-}" \
--suite-version "${SUITE_VERSION:-unknown}" \
--suite-version-url "${SUITE_VERSION_URL:-}" \
--suite-path "$SUITE_PATH" \
--page-label "$PAGE_LABEL" \
--path-err "$HTML_URL_ERR"
echo "html $host${HTML_URL_OK} stub + ${HTML_URL_ERR} (ec=$ec · ${COMMIT_SHORT:-?})"
fi
else
echo "WARN: console_to_html.py missing — bootstrap pages"
write_bootstrap_html "$mon_err" "$host" "err"
if [ "$ec" -eq 0 ]; then
write_bootstrap_html "$mon" "$host" "ok"
rm -rf "$HTML_BASE/$host/${HTML_ERR_DIR}"
else
write_bootstrap_html "$mon" "$host" "redirect"
fi
fi
if [ ! -f "$mon" ]; then
write_bootstrap_html "$mon" "$host" "err"
fi
printf '%s\n' "${COMMIT:-unknown}" >"$HTML_BASE/$host/COMMIT"
printf '%s\n' "${COMMIT_URL:-}" >"$HTML_BASE/$host/COMMIT_URL"
}
for host in $MON_HOSTS; do
htmlify_host "$host"
echo "public URL (expected): https://${host}${HTML_URL_OK}"
done
# Publish to live web root when possible (v1.3.1+: never silent-skip)
if [ -n "$DEPLOY_WWW" ]; then
if [ -w "$DEPLOY_WWW" ] 2>/dev/null; then
# --delay-updates: write to temp names then rename (less partial-file exposure)
if rsync -a --delete --delay-updates "$HTML_BASE/" "$DEPLOY_WWW/"; then
echo "synced → $DEPLOY_WWW/ (delay-updates)"
# brief settle so concurrent monpages curls do not hit mid-rename FS views
sleep "${MONPAGES_PUBLISH_SETTLE_S:-0.3}" 2>/dev/null || sleep 1
else
echo "ERROR: rsync to DEPLOY_WWW_ROOT=$DEPLOY_WWW failed" >&2
ec=1
fi
elif [ -e "$DEPLOY_WWW" ]; then
echo "ERROR: DEPLOY_WWW_ROOT=$DEPLOY_WWW exists but is not writable by $(id -un)" >&2
echo " public FQDN pages will stay missing/stale — fix ownership or run:" >&2
echo " sudo ${APPLY_MONITORING_LIVE}" >&2
ec=1
else
echo "ERROR: DEPLOY_WWW_ROOT=$DEPLOY_WWW missing — public pages not published" >&2
echo " create tree + Caddy handles: sudo ${APPLY_MONITORING_LIVE}" >&2
ec=1
fi
else
echo "WARN: DEPLOY_WWW_ROOT empty — skipping live publish (staging only: $HTML_BASE)" >&2
fi
# Post-publish: monpages obligatory (v1.7.6+) — GOA full inventory / FP only FP hosts
_monpages_failed=0
if [ "${MONPAGES_POSTCHECK:-1}" = "1" ] && [ -x "$ROOT/check_monitoring_pages.sh" ]; then
echo "--- monpages post-check (public FQDN, obligatory) ---"
export MON_HOSTS HTML_URL_OK HTML_URL_ERR TALER_DOMAIN HTML_OUT HTML_BASE
export MONPAGES_STAGING_BASE="${HTML_BASE}"
export DEPLOY_WWW_ROOT="${DEPLOY_WWW:-${DEPLOY_WWW_ROOT:-}}"
export MONPAGES_REQUIRE_PUBLIC="${MONPAGES_REQUIRE_PUBLIC:-1}"
# auto: GOA = all suite mon sites; FP = only FP mon hosts; job paths always included
export MONPAGES_INVENTORY="${MONPAGES_INVENTORY:-auto}"
# Hard check (not pre-publish soft)
unset MONPAGES_PRE_PUBLISH 2>/dev/null || true
export MONPAGES_PRE_PUBLISH=0
if ! bash "$ROOT/check_monitoring_pages.sh"; then
echo "ERROR: public monitoring page(s) missing or not suite HTML via FQDN" >&2
echo " staging HTML_OUT=${HTML_BASE} · DEPLOY_WWW=${DEPLOY_WWW:-}" >&2
ec=1
_monpages_failed=1
fi
unset MONPAGES_PRE_PUBLISH 2>/dev/null || true
fi
# If public check failed after first HTML write, rewrite pages so sticky bar shows monpages ERRORs
if [ "$_monpages_failed" = "1" ]; then
echo "--- re-htmlify after monpages failure (include public outage in report) ---"
# append marker to log so converter sees ERROR lines if monpages was post-only
{
echo ""
echo "ERROR monpages: public FQDN monitoring HTML missing or merchant JSON code 21"
echo "ERROR monpages: fix with sudo ${APPLY_MONITORING_LIVE} (www + Caddy handles)"
} >>"$LOG"
for host in $MON_HOSTS; do
htmlify_host "$host"
done
fi
ls -1t "$LOG_DIR"/run-*.log 2>/dev/null | tail -n +30 | xargs rm -f 2>/dev/null || true
echo "======== done ec=$ec · log=$LOG · ${COMMIT_URL:-} ========"
# v1.7.1: default STRICT_EXIT=1 so systemd/timer reflects publish/public failures
if [ "${STRICT_EXIT:-1}" = "1" ]; then
exit "$ec"
fi
exit 0

View file

@ -0,0 +1,51 @@
#!/usr/bin/env bash
# run-surface-monitoring.sh — ecosystem surface report (v1.8.0+ simplified)
#
# Public HTML only on taler.hacktivism.ch:
# https://taler.hacktivism.ch/taler-monitoring-surface/
# https://taler.hacktivism.ch/taler-monitoring-surface_err/
#
# This page covers remote ecosystem inventory: host/port probes, nmap OS
# fingerprint, Mattermost, mail (firefly + anastasis), package/version signals.
# There are NO separate public pages for mail / mattermost (folded here).
# Apt-src deploy tests keep their own page + timer:
# run-aptdeploy-monitoring.sh → /taler-monitoring-aptdeploy(+_err)/
#
# Landing-stack reports stay on each of the 9 fronts as /monitoring(/_err).
#
set -uo pipefail
AGENT_DIR=$(cd "$(dirname "$0")" && pwd)
export AGENT_LABEL="${AGENT_LABEL:-surface-host-agent}"
export STATE_NAME="${STATE_NAME:-taler-surface-monitoring}"
export TALER_DOMAIN="${TALER_DOMAIN:-hacktivism.ch}"
# surface is primarily remote; versions may use host podman when available
export INSIDE_PODMAN="${INSIDE_PODMAN:-1}"
export LOCAL_STACK="${LOCAL_STACK:-0}"
export SKIP_SSH="${SKIP_SSH:-1}"
export INSIDE_MODE="${INSIDE_MODE:-local-podman}"
export CONTINUE_ON_ERROR="${CONTINUE_ON_ERROR:-1}"
export RUN_TIMEOUT="${RUN_TIMEOUT:-2400}"
# All ecosystem checks → one surface HTML report (force — do not inherit main-timer env)
export PHASES="surface mattermost mail versions monpages"
export MON_HOSTS="taler.hacktivism.ch"
export HTML_OUT="${HTML_OUT:-$HOME/monitoring-sites-staging}"
export DEPLOY_WWW_ROOT="${DEPLOY_WWW_ROOT:-/var/www/monitoring-sites}"
export HTML_OK_DIR="taler-monitoring-surface"
export HTML_ERR_DIR="taler-monitoring-surface_err"
export HTML_URL_OK="/taler-monitoring-surface/"
export HTML_URL_ERR="/taler-monitoring-surface_err/"
export PAGE_LABEL="taler-monitoring-surface"
export SURFACE_SCOPE="${SURFACE_SCOPE:-ecosystem}"
export TALER_DOMAIN_FROM_CLI=0
export APT_DEPLOY_ENSURE=0
# monpages job-only for surface paths on taler.hacktivism.ch
export MONPAGES_INVENTORY="job"
# package behind → ERROR (v1.8.0)
export TALER_PKG_BEHIND="${TALER_PKG_BEHIND:-error}"
exec bash "$AGENT_DIR/run-host-report.sh" "$@"

View file

@ -0,0 +1,18 @@
[Unit]
Description=Taler monitoring aptdeploy (apt-src container deploy tests)
Documentation=file:%h/src/taler-monitoring/host-agent/README.md
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
Environment=PATH=%h/.local/bin:/usr/local/bin:/usr/bin:/bin
Environment=CONTINUE_ON_ERROR=1
Environment=RUN_TIMEOUT=1800
WorkingDirectory=%h/src/taler-monitoring
ExecStart=%h/src/taler-monitoring/host-agent/run-aptdeploy-monitoring.sh
Nice=10
TimeoutStartSec=45min
[Install]
WantedBy=default.target

View file

@ -0,0 +1,14 @@
[Unit]
Description=Periodic taler-monitoring aptdeploy (apt-src deploy tests)
Documentation=file:%h/src/taler-monitoring/host-agent/README.md
[Timer]
OnBootSec=35min
OnUnitActiveSec=4h
AccuracySec=5min
Persistent=true
RandomizedDelaySec=10min
Unit=taler-monitoring-aptdeploy.service
[Install]
WantedBy=timers.target

View file

@ -0,0 +1,15 @@
[Unit]
Description=Watch hacktivism software/config changes → re-run monitoring
Documentation=file:%h/src/taler-monitoring/host-agent/README.md
[Path]
# Explicit stamp after package/image upgrades (touch-software-stamp.sh)
# Landing/caddy ops live outside this suite (host admin-log) — touch the stamp there.
PathChanged=%h/.local/state/taler-hacktivism-monitoring/software.stamp
PathModified=%h/.local/state/taler-hacktivism-monitoring/software.stamp
# Debounce: wait for quiet period before firing
TriggerLimitIntervalSec=120
TriggerLimitBurst=3
[Install]
WantedBy=default.target

View file

@ -0,0 +1,21 @@
[Unit]
Description=Taler monitoring GOA/hacktivism (host-podman inside + public urls)
Documentation=file:%h/src/taler-monitoring/host-agent/README.md
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
# Primary: host can podman-exec into taler-hacktivism* containers
Environment=INSIDE_PODMAN=1
Environment=CONTINUE_ON_ERROR=1
Environment=RUN_TIMEOUT=600
Environment=PATH=%h/.local/bin:/usr/local/bin:/usr/bin:/bin
WorkingDirectory=%h/src/taler-monitoring
ExecStart=%h/src/taler-monitoring/host-agent/run-hacktivism-monitoring.sh
Nice=10
# Suite budget is RUN_TIMEOUT (10m) + git/HTML overhead
TimeoutStartSec=15min
[Install]
WantedBy=default.target

View file

@ -0,0 +1,14 @@
[Unit]
Description=Periodic hacktivism monitoring (fallback if no path events)
Documentation=file:%h/src/taler-monitoring/host-agent/README.md
[Timer]
# Every 4h + boot
OnBootSec=10min
OnUnitActiveSec=4h
AccuracySec=5min
Persistent=true
Unit=taler-monitoring-hacktivism.service
[Install]
WantedBy=timers.target

View file

@ -0,0 +1,20 @@
[Unit]
Description=taler-monitoring mytops stage (betel) — auto-update suite + urls/monpages
Documentation=file:%h/src/taler-monitoring/host-agent/README.md
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
# Env: ~/.config/taler-monitoring/env (SUITE_GIT_*, MON_HOSTS, PHASES=urls monpages, …)
EnvironmentFile=-%h/.config/taler-monitoring/env
Environment=PATH=%h/.local/bin:/usr/local/bin:/usr/bin:/bin
Environment=SUITE_UPDATE_STRICT=1
WorkingDirectory=%h/src/taler-monitoring
# run-host-report sources update-suite.sh first (strict fetch/reset to main)
ExecStart=%h/src/taler-monitoring/host-agent/run-host-report.sh
Nice=10
TimeoutStartSec=20min
[Install]
WantedBy=default.target

View file

@ -0,0 +1,11 @@
[Unit]
Description=taler-monitoring mytops stage (betel) every 4h — suite auto-upgrade + mon HTML
[Timer]
OnBootSec=5min
OnUnitActiveSec=4h
Persistent=true
Unit=taler-monitoring-mytops-stage.service
[Install]
WantedBy=timers.target

View file

@ -0,0 +1,18 @@
[Unit]
Description=Taler monitoring surface (remote ecosystem inventory)
Documentation=file:%h/src/taler-monitoring/host-agent/README.md
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
Environment=PATH=%h/.local/bin:/usr/local/bin:/usr/bin:/bin
Environment=CONTINUE_ON_ERROR=1
Environment=RUN_TIMEOUT=2400
WorkingDirectory=%h/src/taler-monitoring
ExecStart=%h/src/taler-monitoring/host-agent/run-surface-monitoring.sh
Nice=10
TimeoutStartSec=50min
[Install]
WantedBy=default.target

View file

@ -0,0 +1,14 @@
[Unit]
Description=Hourly taler-monitoring surface scan (ecosystem)
Documentation=file:%h/src/taler-monitoring/host-agent/README.md
[Timer]
OnBootSec=20min
OnUnitActiveSec=1h
AccuracySec=2min
Persistent=true
RandomizedDelaySec=5min
Unit=taler-monitoring-surface.service
[Install]
WantedBy=timers.target

View file

@ -0,0 +1,17 @@
#!/usr/bin/env bash
# Call after upgrading hacktivism packages / redeploying landings / image rebuilds.
# systemd path unit watches this stamp → re-runs monitoring.
set -euo pipefail
STATE="${XDG_STATE_HOME:-$HOME/.local/state}/taler-hacktivism-monitoring"
mkdir -p "$STATE"
STAMP="$STATE/software.stamp"
date -Iseconds >"$STAMP"
# also record container image ids for debugging
if command -v podman >/dev/null 2>&1; then
{
echo "# images $(date -Iseconds)"
podman images --format '{{.Repository}}:{{.Tag}} {{.ID}} {{.Digest}}' 2>/dev/null | grep -i hacktivism || true
podman ps --format '{{.Names}} {{.ImageID}} {{.Status}}' 2>/dev/null | grep -i hacktivism || true
} >"$STATE/software-images.txt" || true
fi
echo "touched $STAMP"

132
host-agent/update-suite.sh Executable file
View file

@ -0,0 +1,132 @@
#!/usr/bin/env bash
# update-suite.sh — pull latest taler-monitoring from Forgejo (standalone repo root).
#
# Sourced by run-host-report.sh / wrappers. Every host-agent run must land on
# current origin/$SUITE_GIT_REF (default main). Stale clones are not acceptable.
#
# Env:
# SUITE_GIT_URL default https://git.hacktivism.ch/hernani/taler-monitoring.git
# SUITE_GIT_REF default main
# SUITE_DIR default ~/src/taler-monitoring
# SUITE_UPDATE_MODE reset (default) | pull
# SUITE_UPDATE_STRICT 1 (default) = fetch/reset failure aborts the agent run
# 0 = warn and continue on old tree (emergency only)
#
set -uo pipefail
CFG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/taler-monitoring"
ENV_FILE="${TALER_MONITORING_ENV:-$CFG_DIR/env}"
if [ -f "$ENV_FILE" ] && [ "${_TALER_MON_ENV_LOADED:-0}" != "1" ]; then
set -a; source "$ENV_FILE"; set +a
fi
SUITE_GIT_URL="${SUITE_GIT_URL:-https://git.hacktivism.ch/hernani/taler-monitoring.git}"
SUITE_GIT_REF="${SUITE_GIT_REF:-main}"
SUITE_UPDATE_MODE="${SUITE_UPDATE_MODE:-reset}"
SUITE_UPDATE_STRICT="${SUITE_UPDATE_STRICT:-1}"
if [ -z "${SUITE_DIR:-}" ]; then
if [ -d "$HOME/src/taler-monitoring/.git" ]; then SUITE_DIR="$HOME/src/taler-monitoring"
elif [ -d "$HOME/taler-monitoring/.git" ]; then SUITE_DIR="$HOME/taler-monitoring"
else SUITE_DIR="$HOME/src/taler-monitoring"; fi
fi
export SUITE_DIR
_upd_die() {
echo "ERROR suite-update: $*" >&2
if [ "${SUITE_UPDATE_STRICT}" = "1" ]; then
return 1 2>/dev/null || exit 1
fi
echo "WARN suite-update: continuing on existing tree (SUITE_UPDATE_STRICT=0)" >&2
return 0
}
mkdir -p "$(dirname "$SUITE_DIR")" "$CFG_DIR"
if [ ! -d "$SUITE_DIR/.git" ]; then
echo "clone $SUITE_GIT_URL$SUITE_DIR"
if ! git clone --branch "$SUITE_GIT_REF" "$SUITE_GIT_URL" "$SUITE_DIR" \
&& ! git clone "$SUITE_GIT_URL" "$SUITE_DIR"; then
_upd_die "git clone failed"
return 1 2>/dev/null || exit 1
fi
fi
cd "$SUITE_DIR" || { _upd_die "cd $SUITE_DIR failed"; return 1 2>/dev/null || exit 1; }
git remote set-url origin "$SUITE_GIT_URL" 2>/dev/null || true
_before=$(git rev-parse --short=12 HEAD 2>/dev/null || echo none)
_before_ver=$(git describe --tags --exact-match 2>/dev/null \
|| git describe --tags --abbrev=0 2>/dev/null \
|| echo unknown)
echo "fetch origin ($SUITE_GIT_REF) + tags …"
if ! git fetch --tags --force --prune origin 2>&1; then
_upd_die "git fetch failed — suite would stay at ${_before} (${_before_ver})"
# fall through only if STRICT=0
else
case "$SUITE_UPDATE_MODE" in
reset)
if ! git checkout -B "$SUITE_GIT_REF" "origin/$SUITE_GIT_REF" 2>/dev/null \
&& ! git checkout -B "$SUITE_GIT_REF" FETCH_HEAD; then
_upd_die "git checkout $SUITE_GIT_REF failed"
fi
if ! git reset --hard "origin/$SUITE_GIT_REF" 2>/dev/null \
&& ! git reset --hard FETCH_HEAD; then
_upd_die "git reset --hard origin/$SUITE_GIT_REF failed"
fi
;;
*)
if ! git pull --ff-only origin "$SUITE_GIT_REF" 2>&1; then
_upd_die "git pull --ff-only failed"
fi
;;
esac
fi
COMMIT=$(git rev-parse HEAD)
COMMIT_SHORT=$(git rev-parse --short=12 HEAD)
SOURCE_REPO_WEB="${SOURCE_REPO_WEB:-https://git.hacktivism.ch/hernani/taler-monitoring}"
COMMIT_URL="${SOURCE_REPO_WEB}/src/commit/${COMMIT}"
export COMMIT COMMIT_SHORT COMMIT_URL SOURCE_REPO_WEB
# Release tag for sticky bar (exact tag if HEAD is tagged, else nearest)
SUITE_VERSION=""
if command -v git >/dev/null 2>&1; then
SUITE_VERSION=$(git describe --tags --exact-match 2>/dev/null || true)
if [ -z "$SUITE_VERSION" ]; then
SUITE_VERSION=$(git describe --tags --abbrev=0 2>/dev/null || true)
fi
fi
if [ -z "$SUITE_VERSION" ] && [ -f "$SUITE_DIR/VERSION" ]; then
SUITE_VERSION="v$(tr -d '[:space:]' <"$SUITE_DIR/VERSION" | sed 's/^v//')"
fi
[ -z "$SUITE_VERSION" ] && SUITE_VERSION="unknown"
case "$SUITE_VERSION" in
unknown|v*) ;;
*) SUITE_VERSION="v${SUITE_VERSION}" ;;
esac
# Sticky version badge: prefer commit URL (exact tree of this run).
# Tag URLs (/src/tag/vX.Y.Z) 404 when the tag is not yet pushed to Forgejo;
# commit URLs work as soon as main (or the tree) is on the remote.
SUITE_VERSION_URL=""
if [ -n "$SOURCE_REPO_WEB" ] && [ -n "${COMMIT:-}" ] && [ "$COMMIT" != "unknown" ]; then
SUITE_VERSION_URL="${SOURCE_REPO_WEB}/src/commit/${COMMIT}"
elif [ -n "$SOURCE_REPO_WEB" ] && [ "$SUITE_VERSION" != "unknown" ]; then
SUITE_VERSION_URL="${SOURCE_REPO_WEB}/src/tag/${SUITE_VERSION}"
fi
export SUITE_VERSION SUITE_VERSION_URL
if [ "$_before" != "$COMMIT_SHORT" ]; then
echo " upgraded ${_before} (${_before_ver}) → ${COMMIT_SHORT} (${SUITE_VERSION})"
else
echo " already at ${COMMIT_SHORT} (${SUITE_VERSION})"
fi
echo " version=$SUITE_VERSION ${SUITE_VERSION_URL:$SUITE_VERSION_URL}"
echo "suite @ $COMMIT_SHORT $COMMIT_URL"
echo " dir=$SUITE_DIR"
# Sanity: must have suite entrypoint after update
if [ ! -x "$SUITE_DIR/taler-monitoring.sh" ] && [ ! -f "$SUITE_DIR/taler-monitoring.sh" ]; then
_upd_die "taler-monitoring.sh missing after update"
return 1 2>/dev/null || exit 1
fi
ln -sfn "$SUITE_DIR" "$HOME/taler-monitoring" 2>/dev/null || true
chmod +x taler-monitoring.sh check_*.sh host-agent/*.sh site-gen/*.sh site-gen/*.py 2>/dev/null || true
return 0 2>/dev/null || true