release 1.10.0: aptdeploy container overview board (active/errors)

This commit is contained in:
Hernâni Marques 2026-07-19 01:24:36 +02:00
parent 229ef9383e
commit 321df897ef
No known key found for this signature in database
GPG key ID: CB5738652768F7E9
4 changed files with 282 additions and 23 deletions

View file

@ -1 +1 @@
1.9.4
1.10.0

View file

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

View file

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

View file

@ -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'<section class="apt-board empty" id="container-overview">\n'
f"<h2>{html.escape(title)}</h2>\n"
f"<p class=\"muted\">{html.escape(ui(lang, 'apt_board_empty'))}</p>\n"
f"</section>\n"
)
head = (
"<tr><th>short</th><th>suite</th><th>mode</th><th>podman</th>"
"<th>merchant</th><th>httpd</th><th>ldd</th><th>verdict</th><th>note</th></tr>"
)
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'<tr class="{vclass}">'
f'<td class="short">{html.escape(r.get("short", ""))}</td>'
f'<td>{html.escape(r.get("suite", ""))}</td>'
f'<td>{html.escape(r.get("mode", ""))}</td>'
f'<td>{html.escape(r.get("state", ""))}</td>'
f'<td class="mono">{html.escape(r.get("merchant", ""))}</td>'
f'<td>{html.escape(r.get("httpd", ""))}</td>'
f'<td>{html.escape(r.get("ldd", ""))}</td>'
f'<td class="verdict">{html.escape(r.get("verdict", ""))}</td>'
f'<td class="note">{html.escape(r.get("note", ""))}</td>'
f"</tr>"
)
summary = f"{len(rows)} containers · {n_ok} OK · {n_err} ERROR · {n_miss} missing"
return (
f'<section class="apt-board" id="container-overview">\n'
f"<h2>{html.escape(title)}</h2>\n"
f'<p class="board-summary">{html.escape(summary)}</p>\n'
f'<div class="board-scroll"><table class="apt-board-table">\n'
f"<thead>{head}</thead>\n<tbody>\n"
+ "\n".join(body_parts)
+ "\n</tbody></table></div>\n</section>\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"""<!DOCTYPE html>
<html lang="{html.escape(lang if "lang" in dir() else "en")}">
<head>
@ -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;