From 321df897ef9c81fd6e7d418f25ca36990c36f4b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hern=C3=A2ni=20Marques?= Date: Sun, 19 Jul 2026 01:24:36 +0200 Subject: [PATCH] release 1.10.0: aptdeploy container overview board (active/errors) --- VERSION | 2 +- VERSIONS.md | 1 + check_apt_deploy.sh | 147 +++++++++++++++++++++++++++++----- site-gen/console_to_html.py | 155 +++++++++++++++++++++++++++++++++++- 4 files changed, 282 insertions(+), 23 deletions(-) diff --git a/VERSION b/VERSION index d615fd0..81c871d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.9.4 +1.10.0 diff --git a/VERSIONS.md b/VERSIONS.md index 4a9b77a..35fa01e 100644 --- a/VERSIONS.md +++ b/VERSIONS.md @@ -17,6 +17,7 @@ Git tags: `vMAJOR.FEATURE.FIX` (e.g. `v1.8.0`). File `VERSION` omits the `v` pre | Tag | Date (UTC) | Notes | |-----|------------|--------| +| **v1.10.0** | 2026-07-19 | **Feature:** aptdeploy **container overview board** — inventory + per-pod matrix (state, merchant, httpd, ldd, OK/ERROR) in log and HTML sticky/top (`#container-overview`). | | **v1.9.4** | 2026-07-19 | **Fix:** restore **taler-monitoring-aptdeploy(+_err)** as first-class GOA mon page (catalog + monpages path allow + surface docs). Mail/mattermost stay folded into surface. | | **v1.9.3** | 2026-07-19 | **Bugfix (suite):** monpages merchant **code 21** always ERROR (bare no longer soft-WARN); apply hints use absolute `/home/hernani/koopa-caddy/…`; ship `host-agent/apply-monitoring-live.sh` (smoke array, bare check); fix ROOT-ON-KOOPA wrong `redir /path/ 302` examples. | | **v1.9.2** | 2026-07-19 | **Bugfix:** Caddy bare-path redir footgun — inside `handle` use `redir * /path/ 302` (not `redir /path/ 302`, which becomes `Location: 302` and bare URLs fall through to merchant **code 21**). Snippet + ROOT-APPLY: absolute apply path (`/home/hernani/koopa-caddy/…`, not `~` as root). | diff --git a/check_apt_deploy.sh b/check_apt_deploy.sh index 5240e82..54d5704 100755 --- a/check_apt_deploy.sh +++ b/check_apt_deploy.sh @@ -9,6 +9,9 @@ # koopa-taler-deploy-test-apt-src-trixie-upgrade # koopa-taler-deploy-test-apt-src-trixie-testing-upgrade # +# Emits a compact container board (board · key=value) for the HTML sticky/top +# overview: which pods are active and which fail (httpd / ldd / …). +# # Env: # APT_DEPLOY_CONTAINERS "name:suite:mode …" mode=fresh|upgrade (optional, default fresh) # APT_DEPLOY_SKIP=1 @@ -44,6 +47,58 @@ ok "podman" "$("$PODMAN_BIN" --version 2>/dev/null | head -1)" info "host" "$(hostname 2>/dev/null || echo unknown) · whoami=$(whoami)" fail_any=0 +# board rows for end overview (human table + machine board · lines) +BOARD_ROWS=() + +_short_name() { + # koopa-taler-deploy-test-apt-src-trixie-testing-upgrade → trixie-testing-upgrade + local n="$1" + n="${n#koopa-taler-deploy-test-apt-src-}" + printf '%s' "$n" +} + +# Record one container for the top-of-page board (HTML parses "board · key=value"). +board_record() { + local short="$1" name="$2" suite="$3" mode="$4" state="$5" + local merchant="$6" httpd="$7" ldd="$8" verdict="$9" note="${10:-}" + local kv + kv="short=${short} name=${name} suite=${suite} mode=${mode} state=${state}" + kv+=" merchant=${merchant} httpd=${httpd} ldd=${ldd} verdict=${verdict}" + # note: no spaces (HTML/parser splits on whitespace) + if [ -n "$note" ]; then + note_safe=$(printf '%s' "$note" | tr ' ' '_' | tr -d '|') + kv+=" note=${note_safe}" + fi + info "board" "$kv" + BOARD_ROWS+=("$short|$suite|$mode|$state|$merchant|$httpd|$ldd|$verdict|$note") +} + +print_board_table() { + section "aptdeploy · container overview (active / errors)" + info "board-table" "| short | suite | mode | podman | merchant | httpd | ldd | verdict | note |" + info "board-table" "|-------|-------|------|--------|----------|-------|-----|---------|------|" + local r short suite mode state merchant httpd ldd verdict note + local n_ok=0 n_err=0 n_miss=0 + for r in "${BOARD_ROWS[@]+"${BOARD_ROWS[@]}"}"; do + IFS='|' read -r short suite mode state merchant httpd ldd verdict note <<<"$r" + info "board-table" "| ${short} | ${suite} | ${mode} | ${state} | ${merchant} | ${httpd} | ${ldd} | ${verdict} | ${note} |" + case "$verdict" in + OK|ok) n_ok=$((n_ok + 1)) ;; + MISSING|missing) n_miss=$((n_miss + 1)) ;; + *) n_err=$((n_err + 1)) ;; + esac + done + info "board-summary" "containers=${#BOARD_ROWS[@]} ok=${n_ok} error=${n_err} missing=${n_miss}" + # one-line human skim (INFO/OK only — do not re-emit ERROR, details already counted) + for r in "${BOARD_ROWS[@]+"${BOARD_ROWS[@]}"}"; do + IFS='|' read -r short suite mode state merchant httpd ldd verdict note <<<"$r" + if [ "$verdict" = "OK" ]; then + ok "overview" "${short}: ${state} · merchant=${merchant} · httpd=${httpd} · ldd=${ldd}" + else + info "overview" "${short}: ${verdict} · state=${state} merchant=${merchant} httpd=${httpd} ldd=${ldd}${note:+ · ${note}}" + fi + done +} print_pkg_table() { local name=$1 @@ -58,13 +113,18 @@ print_pkg_table() { done ' 2>/dev/null || echo "| (query failed) | |") info "pkg-table" "$name" - # emit as plain lines so HTML log keeps the table while IFS= read -r line; do [ -n "$line" ] || continue info "pkg" "$line" done <<<"$rows" } +# merchant package version only (for board) +_merchant_ver() { + local name=$1 + "$PODMAN_BIN" exec "$name" dpkg-query -W -f='${Version}' taler-merchant 2>/dev/null || echo "?" +} + check_ldd_deep() { local name=$1 local out @@ -95,7 +155,7 @@ check_ldd_deep() { check_systemd() { local name=$1 - local out active_target active_httpd enabled + local out active_httpd out=$("$PODMAN_BIN" exec "$name" bash -lc ' set +e echo "system: $(systemctl is-system-running 2>&1)" @@ -115,19 +175,19 @@ check_systemd() { done <<<"$out" active_httpd=$(echo "$out" | sed -n 's/^after_start_httpd: //p' | tail -1) + # export for caller board + APT_LAST_HTTPD="${active_httpd:-?}" if [ "$active_httpd" = "active" ]; then ok "systemd httpd" "taler-merchant-httpd active after start taler-merchant.target" return 0 fi - # soft if target not shipped as enabled in container — still ERROR for deploy smoke err "systemd httpd" "taler-merchant-httpd not active" "after_start_httpd=${active_httpd:-?} · see systemd lines" return 1 } check_merchant_basics() { local name=$1 - local out code - # unix socket or curl localhost if httpd listens + local out out=$("$PODMAN_BIN" exec "$name" bash -lc ' set +e echo "=== binaries ===" @@ -135,7 +195,6 @@ check_merchant_basics() { echo "=== --version ===" taler-merchant-httpd --version 2>&1 echo "=== config probe ===" - # try common paths without TLS for u in \ http://127.0.0.1:9966/config \ http://127.0.0.1:8081/config \ @@ -165,10 +224,6 @@ check_merchant_basics() { err "httpd" "shared library error on --version" "see basics/ldd" return 1 fi - if ! echo "$out" | grep -q 'taler-merchant-httpd --version\|v\.\|taler-merchant-httpd'; then - # version line varies; success if exit was ok — check via separate exec - : - fi set +e "$PODMAN_BIN" exec "$name" taler-merchant-httpd --version >/dev/null 2>&1 @@ -181,7 +236,6 @@ check_merchant_basics() { return 1 fi - # /config is nice-to-have if stack is fully configured if echo "$out" | grep -qE 'curl .* -> 200'; then ok "merchant /config" "HTTP 200 (local)" else @@ -192,7 +246,10 @@ check_merchant_basics() { check_one() { local name="$1" expect_suite="$2" mode="${3:-fresh}" - local st suite_line pkgs + local st short merchant="-" httpd="-" ldd="-" verdict="OK" note="" c_fail=0 + + short="$(_short_name "$name")" + APT_LAST_HTTPD="-" # group ids: aptdeploy.trixie-01 / aptdeploy.trixie-testing-upgrade-01 set_group "${expect_suite}${mode:+-$mode}" @@ -200,12 +257,14 @@ check_one() { if ! "$PODMAN_BIN" container exists "$name" 2>/dev/null; then err "container" "$name missing" "run ensure-apt-deploy-test-containers.sh" + board_record "$short" "$name" "$expect_suite" "$mode" "missing" "-" "-" "-" "MISSING" "ensure containers" fail_any=1 return fi st=$("$PODMAN_BIN" inspect -f '{{.State.Status}}' "$name" 2>/dev/null || echo unknown) if [ "$st" != "running" ]; then err "container" "$name not running" "status=$st" + board_record "$short" "$name" "$expect_suite" "$mode" "$st" "-" "-" "-" "ERROR" "not running" fail_any=1 return fi @@ -217,31 +276,47 @@ check_one() { ok "apt suite" "$expect_suite" else err "apt suite" "expected $expect_suite" "got: ${suite_line:-empty}" - fail_any=1 + c_fail=1 + note="suite mismatch" fi + merchant="$(_merchant_ver "$name")" print_pkg_table "$name" if ! check_merchant_basics "$name"; then - fail_any=1 - check_ldd_deep "$name" || fail_any=1 + c_fail=1 + if ! check_ldd_deep "$name"; then + ldd="FAIL" + c_fail=1 + else + ldd="OK" + fi else - # still show ldd summary as info when ok ldd_out=$("$PODMAN_BIN" exec "$name" bash -lc \ 'ldd "$(command -v taler-merchant-httpd)" 2>&1 | grep -E "libtalerutil|not found" || true' 2>/dev/null || true) if echo "$ldd_out" | grep -q 'not found'; then err "libtalerutil" "not found" "$ldd_out" check_ldd_deep "$name" || true - fail_any=1 + ldd="FAIL" + c_fail=1 else + ldd="OK" info "libtalerutil" "$(echo "$ldd_out" | tr '\n' ' ')" fi fi if ! check_systemd "$name"; then - fail_any=1 - check_ldd_deep "$name" || true + c_fail=1 + httpd="${APT_LAST_HTTPD:-FAIL}" + # deeper ldd if httpd failed + if [ "$ldd" != "FAIL" ]; then + check_ldd_deep "$name" || { ldd="FAIL"; c_fail=1; } + fi + else + httpd="active" fi + # normalize httpd cell + [ -z "$httpd" ] || [ "$httpd" = "-" ] && httpd="${APT_LAST_HTTPD:-?}" # Disk free inside this deploy-test container (+ host once, first container only) if [ "${_APT_DEPLOY_HOST_DISK_DONE:-0}" != "1" ]; then @@ -251,12 +326,39 @@ check_one() { fi set_group disk if ! mon_disk_check_podman "$name" "$PODMAN_BIN"; then - fail_any=1 + c_fail=1 + note="${note:+$note; }disk" fi + + if [ "$c_fail" -ne 0 ]; then + verdict="ERROR" + fail_any=1 + else + verdict="OK" + fi + board_record "$short" "$name" "$expect_suite" "$mode" "running" "$merchant" "$httpd" "$ldd" "$verdict" "$note" } +# --- inventory first (what exists before checks) --- +section "aptdeploy · inventory (podman)" +info "inventory" "expected tracks: trixie/trixie-testing × fresh/upgrade (4 containers)" +for entry in $LIST; do + cname=${entry%%:*} + rest=${entry#*:} + case "$rest" in + *:*) suite=${rest%%:*}; mode=${rest#*:} ;; + *) suite=$rest; mode=fresh ;; + esac + short="$(_short_name "$cname")" + if "$PODMAN_BIN" container exists "$cname" 2>/dev/null; then + st=$("$PODMAN_BIN" inspect -f '{{.State.Status}}' "$cname" 2>/dev/null || echo unknown) + ok "inventory" "${short}: ${st} (${suite}/${mode})" + else + warn "inventory" "${short}: missing (${suite}/${mode})" "will ERROR in check" + fi +done + for entry in $LIST; do - # name:suite or name:suite:mode cname=${entry%%:*} rest=${entry#*:} case "$rest" in @@ -272,6 +374,9 @@ for entry in $LIST; do check_one "$cname" "$suite" "$mode" done +# Compact matrix + overview ERROR lines (jump targets for HTML) +print_board_table + summary if [ "$fail_any" -ne 0 ]; then exit 1 diff --git a/site-gen/console_to_html.py b/site-gen/console_to_html.py index f708d31..9db83b0 100755 --- a/site-gen/console_to_html.py +++ b/site-gen/console_to_html.py @@ -53,6 +53,9 @@ def ui(lang: str, key: str, **kwargs) -> str: "apt_i5": "Checks: packages, taler-merchant-httpd --version / ldd, systemd unit, optional local /config probe — not public nginx/HTTPS", "apt_i6": "HTML published only on taler.hacktivism.ch (this page host: {host})", "apt_i7": "Timer: taler-monitoring-aptdeploy.timer (4h)", + "apt_i8": "Top board: per-container podman state, merchant version, httpd, ldd, OK/ERROR (see CONTAINER OVERVIEW)", + "apt_board_title": "Container overview — active pods & failures", + "apt_board_empty": "No board · lines in log (re-run aptdeploy with suite ≥ v1.10.0).", "focus_bank": "Libeufin bank public HTTPS + container inside checks", "focus_exchange": "Exchange public HTTPS + container inside checks", "focus_merchant": "Merchant backend / SPA public HTTPS + container inside checks", @@ -125,6 +128,9 @@ def ui(lang: str, key: str, **kwargs) -> str: "apt_i5": "Contrôles : paquets, taler-merchant-httpd --version / ldd, unité systemd, sonde locale /config optionnelle — pas de nginx/HTTPS public", "apt_i6": "HTML publié uniquement sur taler.hacktivism.ch (hôte de cette page : {host})", "apt_i7": "Minuterie : taler-monitoring-aptdeploy.timer (4 h)", + "apt_i8": "Tableau haut : état podman, version merchant, httpd, ldd, OK/ERROR par conteneur", + "apt_board_title": "Vue conteneurs — pods actifs et erreurs", + "apt_board_empty": "Pas de lignes board · dans le journal (relancer aptdeploy suite ≥ v1.10.0).", "focus_bank": "Banque Libeufin HTTPS public + contrôles inside conteneur", "focus_exchange": "Exchange HTTPS public + contrôles inside conteneur", "focus_merchant": "Backend marchand / SPA HTTPS public + contrôles inside conteneur", @@ -210,6 +216,90 @@ def is_run_timeout_text(text: str) -> bool: return "RUN.TIMEOUT" in u or "RUN_TIMEOUT" in u or "RUN TIMEOUT" in u +def extract_aptdeploy_board(log_text: str) -> list[dict[str, str]]: + """Parse `board · short=… suite=… verdict=…` lines from check_apt_deploy.""" + rows: list[dict[str, str]] = [] + seen: set[str] = set() + for ln in strip_ansi(log_text or "").splitlines(): + if "board ·" not in ln and " board · " not in ln: + # also match "board ·" after tid + if not re.search(r"\bboard\b.*\bshort=", ln): + continue + # detail after last "board ·" or "board " + m = re.search(r"\bboard\b\s*·\s*(.+)$", ln) + if not m: + m = re.search(r"\bboard\b\s+(.+)$", ln) + if not m: + continue + detail = m.group(1).strip() + if "short=" not in detail: + continue + rec: dict[str, str] = {} + for part in detail.split(): + if "=" in part: + k, v = part.split("=", 1) + rec[k] = v + short = rec.get("short", "") + if not short or short in seen: + continue + seen.add(short) + rows.append(rec) + return rows + + +def aptdeploy_board_html(log_text: str, lang: str = "en") -> str: + """Compact HTML table: which containers are active and which fail.""" + rows = extract_aptdeploy_board(log_text) + title = ui(lang, "apt_board_title") + if not rows: + return ( + f'
\n' + f"

{html.escape(title)}

\n" + f"

{html.escape(ui(lang, 'apt_board_empty'))}

\n" + f"
\n" + ) + head = ( + "shortsuitemodepodman" + "merchanthttpdlddverdictnote" + ) + body_parts = [] + n_ok = n_err = n_miss = 0 + for r in rows: + v = (r.get("verdict") or "?").upper() + if v == "OK": + n_ok += 1 + vclass = "v-ok" + elif v == "MISSING": + n_miss += 1 + vclass = "v-miss" + else: + n_err += 1 + vclass = "v-err" + body_parts.append( + f'' + f'{html.escape(r.get("short", ""))}' + f'{html.escape(r.get("suite", ""))}' + f'{html.escape(r.get("mode", ""))}' + f'{html.escape(r.get("state", ""))}' + f'{html.escape(r.get("merchant", ""))}' + f'{html.escape(r.get("httpd", ""))}' + f'{html.escape(r.get("ldd", ""))}' + f'{html.escape(r.get("verdict", ""))}' + f'{html.escape(r.get("note", ""))}' + f"" + ) + summary = f"{len(rows)} containers · {n_ok} OK · {n_err} ERROR · {n_miss} missing" + return ( + f'
\n' + f"

{html.escape(title)}

\n" + f'

{html.escape(summary)}

\n' + f'
\n' + f"{head}\n\n" + + "\n".join(body_parts) + + "\n
\n
\n" + ) + + def is_summary_error_line(ln: str) -> bool: """Skip summary/header lines that contain ERROR but are not checks.""" low = ln.lower() @@ -412,7 +502,21 @@ def monitoring_scope( ui(lang, "apt_i5"), ui(lang, "apt_i6", host=host), ui(lang, "apt_i7"), + ui(lang, "apt_i8"), ] + _pages_items(lang, host, label) + # Prefer live board verdicts in the scope list when log has board · lines + board = extract_aptdeploy_board(log_text or "") + if board: + live = [] + for r in board: + v = r.get("verdict", "?") + mark = "OK" if v.upper() == "OK" else ("MISS" if v.upper() == "MISSING" else "ERR") + live.append( + f"{mark} {r.get('short', '?')}: pod={r.get('state', '?')} " + f"merchant={r.get('merchant', '?')} httpd={r.get('httpd', '?')} " + f"ldd={r.get('ldd', '?')}" + ) + items = live + items return { "kind": "aptdeploy", "lang": lang, @@ -902,6 +1006,19 @@ def build_html( body_lines.append(render_line(ln, tid_to_slug, extra or None)) err_nav = "" + # Aptdeploy: container board first (active pods + per-pod verdict) + _is_apt = "aptdeploy" in (page_label or "").lower() or "apt-deploy" in ( + page_label or "" + ).lower() + if _is_apt: + _lang_board = ui_lang() + if re.search(r"lefrancpaysan|francpaysan", hostname + "\n" + log_text, re.I): + import os + + if not (os.environ.get("TALER_MON_LANG") or "").strip(): + _lang_board = "fr" + err_nav += aptdeploy_board_html(log_text, lang=_lang_board) + if errors and mode == "err": items = [] timeout_items = [] @@ -971,7 +1088,13 @@ def build_html( suite_version_url=suite_version_url, ) - kind = "surface" if "surface" in (page_label or "") else "monitoring" + pl = (page_label or "").lower() + if "aptdeploy" in pl or "apt-deploy" in pl: + kind = "aptdeploy" + elif "surface" in pl: + kind = "surface" + else: + kind = "monitoring" return f""" @@ -1023,6 +1146,36 @@ main {{ padding: 12px 14px 48px; max-width: 1200px; }} outline: 1px solid #a44; background: #1a0808; padding: 4px 6px; margin: 4px 0; border-radius: 3px; }} +/* aptdeploy container overview board */ +.apt-board {{ + background: var(--panel); border: 1px solid #345; + padding: 10px 12px; margin-bottom: 14px; border-radius: 4px; +}} +.apt-board h2 {{ margin: 0 0 6px; font-size: 13px; color: var(--info); }} +.apt-board .board-summary {{ margin: 0 0 8px; color: var(--dim); font-size: 12px; }} +.apt-board.empty p.muted {{ color: var(--dim); margin: 0; font-size: 12px; }} +.board-scroll {{ overflow-x: auto; }} +.apt-board-table {{ + width: 100%; border-collapse: collapse; font-size: 12px; + min-width: 720px; +}} +.apt-board-table th {{ + text-align: left; color: #9ab; font-weight: 700; font-size: 11px; + border-bottom: 1px solid var(--border); padding: 4px 8px; +}} +.apt-board-table td {{ + padding: 5px 8px; border-bottom: 1px solid #1a1a1a; vertical-align: top; +}} +.apt-board-table td.mono {{ font-variant-numeric: tabular-nums; }} +.apt-board-table td.short {{ font-weight: 700; color: #eee; }} +.apt-board-table td.verdict {{ font-weight: 800; letter-spacing: 0.04em; }} +.apt-board-table tr.v-ok td.verdict {{ color: var(--ok); }} +.apt-board-table tr.v-err td.verdict {{ color: var(--err); }} +.apt-board-table tr.v-miss td.verdict {{ color: var(--warn); }} +.apt-board-table tr.v-err {{ background: #1a0a0a; }} +.apt-board-table tr.v-ok {{ background: #0a120e; }} +.apt-board-table tr.v-miss {{ background: #1a1608; }} +.apt-board-table td.note {{ color: var(--dim); max-width: 14rem; }} .console {{ background: #0a0a0a; border: 1px solid var(--border); border-radius: 4px; padding: 8px 10px;