From a5562cf5fb4a9d4857307f2870a0367c73ea85ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hern=C3=A2ni=20Marques?= Date: Sun, 19 Jul 2026 05:15:28 +0200 Subject: [PATCH 01/36] release 1.15.6: aptdeploy errors self-contained (systemd/journal detail) --- VERSION | 2 +- VERSIONS.md | 1 + check_apt_deploy.sh | 104 +++++++++++++++++++++++++++++++++++++++----- 3 files changed, 94 insertions(+), 13 deletions(-) diff --git a/VERSION b/VERSION index d324349..04cc999 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.15.5 +1.15.6 diff --git a/VERSIONS.md b/VERSIONS.md index 47658fb..9ca820d 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.15.6** | 2026-07-19 | **Bugfix:** aptdeploy ERROR jump list is self-contained — systemd httpd failures include before/after state, Result/SubState, failed units, and journal snippets (no more vague «see systemd lines»); ldd/--version errors embed concrete missing libs / exit output. | | **v1.15.5** | 2026-07-19 | **Bugfix:** devtesting CHF ladder — `ssh` was draining ladder stdin (only rung 1/23 ran → progress 45/98 then snap 52/52); use `ssh -n` + `mapfile`; incomplete ladder is WARN not false complete. | | **v1.15.4** | 2026-07-19 | **Bugfix:** French sticky titles — page label *surveillance*, stack summary *public + interne*, SUMMARY verdict/totals via i18n (`tout est clair`, `totaux :`). | | **v1.15.3** | 2026-07-19 | **Bugfix:** console language auto-**fr** for `*lefrancpaysan*` domains again — env `TALER_MON_LANG=en` no longer locks English; only `--lang` / `TALER_MON_LANG_SET=1` is explicit. | diff --git a/check_apt_deploy.sh b/check_apt_deploy.sh index 54d5704..e7bc488 100755 --- a/check_apt_deploy.sh +++ b/check_apt_deploy.sh @@ -127,7 +127,7 @@ _merchant_ver() { check_ldd_deep() { local name=$1 - local out + local out missing out=$("$PODMAN_BIN" exec "$name" bash -lc ' set +e bin=$(command -v taler-merchant-httpd) @@ -147,15 +147,48 @@ check_ldd_deep() { info "ldd" "$line" done <<<"$out" if echo "$out" | grep -q 'not found'; then - err "ldd" "shared library missing in $name" "see ldd lines above" + missing=$(echo "$out" | grep 'not found' | tr '\n' ' ' | head -c 280) + err "ldd" "shared library missing in $name" "${missing:-not found (see ldd INFO above)}" return 1 fi return 0 } +# Build a single-line diagnosis for err() so jump lists are self-contained +# (operators should not need to hunt nearby INFO lines). +_apt_systemd_diagnose() { + local out="$1" + local before after failed journal status_line result + before=$(echo "$out" | sed -n 's/^httpd_active: //p' | tail -1) + after=$(echo "$out" | sed -n 's/^after_start_httpd: //p' | tail -1) + failed=$(echo "$out" | sed -n 's/^failed_taler: //p' | grep -v '^none$' | tr '\n' ';' | sed 's/;$//') + [ -z "$failed" ] && failed=$(echo "$out" | grep -E '^\s*●\s+taler-' | head -3 | tr '\n' ';' | sed 's/;$//') + result=$(echo "$out" | sed -n 's/^httpd_Result: //p' | tail -1) + [ -z "$result" ] && result=$(echo "$out" | sed -n 's/^ *Result: //p' | head -1) + status_line=$(echo "$out" | sed -n 's/^httpd_status_line: //p' | tail -1) + journal=$(echo "$out" | sed -n 's/^httpd_journal: //p' | tr '\n' ' ' | head -c 220) + # fallback: first non-meta journal-ish line + if [ -z "$journal" ]; then + journal=$(echo "$out" | grep -E 'taler-merchant-httpd\[[0-9]+\]|Failed|error|Error|NRestarts|Main PID' | head -3 | tr '\n' ' ' | head -c 220) + fi + local parts=() + [ -n "$before" ] && parts+=("before=${before}") + [ -n "$after" ] && parts+=("after_start=${after}") + [ -n "$result" ] && parts+=("Result=${result}") + [ -n "$status_line" ] && parts+=("status=${status_line}") + [ -n "$failed" ] && [ "$failed" != "none" ] && parts+=("failed_units=${failed}") + [ -n "$journal" ] && parts+=("journal=${journal}") + if [ "${#parts[@]}" -eq 0 ]; then + printf '%s' "no status/journal captured (podman exec empty?)" + return + fi + local IFS=' · ' + printf '%s' "${parts[*]}" +} + check_systemd() { local name=$1 - local out active_httpd + local out active_httpd before_httpd detail out=$("$PODMAN_BIN" exec "$name" bash -lc ' set +e echo "system: $(systemctl is-system-running 2>&1)" @@ -163,17 +196,50 @@ check_systemd() { echo "target_active: $(systemctl is-active taler-merchant.target 2>&1)" echo "httpd_active: $(systemctl is-active taler-merchant-httpd.service 2>&1)" echo "httpd_enabled: $(systemctl is-enabled taler-merchant-httpd.service 2>&1)" - systemctl start taler-merchant.target 2>&1 | tail -5 - sleep 2 + # unit properties help when is-active is only "activating"/"failed" + echo "httpd_Result: $(systemctl show -p Result --value taler-merchant-httpd.service 2>&1)" + echo "httpd_SubState: $(systemctl show -p SubState --value taler-merchant-httpd.service 2>&1)" + echo "httpd_NRestarts: $(systemctl show -p NRestarts --value taler-merchant-httpd.service 2>&1)" + echo "httpd_ExecMainStatus: $(systemctl show -p ExecMainStatus --value taler-merchant-httpd.service 2>&1)" + echo "httpd_ExecMainCode: $(systemctl show -p ExecMainCode --value taler-merchant-httpd.service 2>&1)" + # one-line status (Active: failed / activating (start) …) + systemctl status taler-merchant-httpd.service --no-pager -l 2>&1 | head -12 | while IFS= read -r sl; do + echo "httpd_status: $sl" + done + # compact single-line for err detail + st1=$(systemctl status taler-merchant-httpd.service --no-pager -l 2>&1 | sed -n "s/^ *Active: //p" | head -1) + echo "httpd_status_line: ${st1:-?}" + systemctl start taler-merchant.target 2>&1 | tail -8 | while IFS= read -r sl; do + echo "start_target: $sl" + done + # wait longer: "activating" often clears only after dbinit + sleep 5 echo "after_start_target: $(systemctl is-active taler-merchant.target 2>&1)" echo "after_start_httpd: $(systemctl is-active taler-merchant-httpd.service 2>&1)" - systemctl --failed --no-legend 2>/dev/null | grep -i taler || echo "failed_taler: none" + echo "after_httpd_Result: $(systemctl show -p Result --value taler-merchant-httpd.service 2>&1)" + echo "after_httpd_SubState: $(systemctl show -p SubState --value taler-merchant-httpd.service 2>&1)" + # journal: last failure reasons (self-contained for ERROR jump list) + journalctl -u taler-merchant-httpd.service -n 12 --no-pager -o cat 2>/dev/null \ + | sed "/^$/d" | tail -8 | while IFS= read -r jl; do + echo "httpd_journal: $jl" + done + journalctl -u taler-merchant-dbinit.service -n 6 --no-pager -o cat 2>/dev/null \ + | sed "/^$/d" | tail -4 | while IFS= read -r jl; do + echo "dbinit_journal: $jl" + done + ft=$(systemctl --failed --no-legend 2>/dev/null | grep -i taler || true) + if [ -n "$ft" ]; then + echo "$ft" | while IFS= read -r fl; do echo "failed_taler: $fl"; done + else + echo "failed_taler: none" + fi ' 2>/dev/null || echo "systemd probe failed") while IFS= read -r line; do [ -n "$line" ] || continue info "systemd" "$line" done <<<"$out" + before_httpd=$(echo "$out" | sed -n 's/^httpd_active: //p' | tail -1) active_httpd=$(echo "$out" | sed -n 's/^after_start_httpd: //p' | tail -1) # export for caller board APT_LAST_HTTPD="${active_httpd:-?}" @@ -181,7 +247,17 @@ check_systemd() { ok "systemd httpd" "taler-merchant-httpd active after start taler-merchant.target" return 0 fi - err "systemd httpd" "taler-merchant-httpd not active" "after_start_httpd=${active_httpd:-?} · see systemd lines" + detail=$(_apt_systemd_diagnose "$out") + # Prefer after_* Result if present + local after_result after_sub + after_result=$(echo "$out" | sed -n 's/^after_httpd_Result: //p' | tail -1) + after_sub=$(echo "$out" | sed -n 's/^after_httpd_SubState: //p' | tail -1) + [ -n "$after_result" ] && [ "$after_result" != "success" ] && detail="${detail} · after_Result=${after_result}" + [ -n "$after_sub" ] && detail="${detail} · after_SubState=${after_sub}" + # Human one-liner for jump list (no "see …" without content) + err "systemd httpd" \ + "taler-merchant-httpd not active (container=${name})" \ + "${detail}" return 1 } @@ -221,18 +297,21 @@ check_merchant_basics() { done <<<"$out" if echo "$out" | grep -qi 'error while loading shared libraries'; then - err "httpd" "shared library error on --version" "see basics/ldd" + local lib_err + lib_err=$(echo "$out" | grep -i 'error while loading shared libraries' | head -1 | tr '\n' ' ' | head -c 240) + err "httpd" "shared library error on --version" "${lib_err:-see basics INFO lines}" return 1 fi set +e - "$PODMAN_BIN" exec "$name" taler-merchant-httpd --version >/dev/null 2>&1 - local ec=$? + local ver_out ec + ver_out=$("$PODMAN_BIN" exec "$name" taler-merchant-httpd --version 2>&1) + ec=$? set -e if [ "$ec" -eq 0 ]; then ok "httpd --version" "exit 0" else - err "httpd --version" "exit $ec" + err "httpd --version" "exit $ec" "$(printf '%s' "$ver_out" | tr '\n' ' ' | head -c 240)" return 1 fi @@ -308,9 +387,10 @@ check_one() { if ! check_systemd "$name"; then c_fail=1 httpd="${APT_LAST_HTTPD:-FAIL}" + note="${note:+$note; }httpd=${httpd}" # deeper ldd if httpd failed if [ "$ldd" != "FAIL" ]; then - check_ldd_deep "$name" || { ldd="FAIL"; c_fail=1; } + check_ldd_deep "$name" || { ldd="FAIL"; c_fail=1; note="${note:+$note; }ldd"; } fi else httpd="active" From b72a4f21cb196fb49f8cc80f4b160dda4f66352b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hern=C3=A2ni=20Marques?= Date: Sun, 19 Jul 2026 05:20:41 +0200 Subject: [PATCH 02/36] release 1.15.7: mon HTML SUMMARY classify + no converter noise --- VERSION | 2 +- VERSIONS.md | 1 + host-agent/run-host-report.sh | 12 +++-- site-gen/console_to_html.py | 88 +++++++++++++++++++++++++++++------ 4 files changed, 84 insertions(+), 19 deletions(-) diff --git a/VERSION b/VERSION index 04cc999..545fd57 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.15.6 +1.15.7 diff --git a/VERSIONS.md b/VERSIONS.md index 9ca820d..b4fb134 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.15.7** | 2026-07-19 | **Bugfix:** mon HTML — do not paint «perf/ladder summary» as SUMMARY header (restore 2 INFO lines); drop converter SyntaxWarning/noise from public log; «wrote» on stderr; host-agent PYTHONWARNINGS for convert. | | **v1.15.6** | 2026-07-19 | **Bugfix:** aptdeploy ERROR jump list is self-contained — systemd httpd failures include before/after state, Result/SubState, failed units, and journal snippets (no more vague «see systemd lines»); ldd/--version errors embed concrete missing libs / exit output. | | **v1.15.5** | 2026-07-19 | **Bugfix:** devtesting CHF ladder — `ssh` was draining ladder stdin (only rung 1/23 ran → progress 45/98 then snap 52/52); use `ssh -n` + `mapfile`; incomplete ladder is WARN not false complete. | | **v1.15.4** | 2026-07-19 | **Bugfix:** French sticky titles — page label *surveillance*, stack summary *public + interne*, SUMMARY verdict/totals via i18n (`tout est clair`, `totaux :`). | diff --git a/host-agent/run-host-report.sh b/host-agent/run-host-report.sh index 8b9ac20..e26113c 100755 --- a/host-agent/run-host-report.sh +++ b/host-agent/run-host-report.sh @@ -353,7 +353,13 @@ htmlify_host() { 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" \ + # PYTHONWARNINGS: never let SyntaxWarning leak into the suite log (tee). + # stderr → agent log file only, not the mon console stream. + _mon_html_py() { + PYTHONWARNINGS=ignore::SyntaxWarning \ + python3 "$SITE_GEN/console_to_html.py" "$@" 2>>"${LOG_DIR:-/tmp}/console_to_html.err" + } + _mon_html_py \ --lang "${TALER_MON_LANG:-en}" \ --log "$LOG" \ --out "$mon_err" \ @@ -368,7 +374,7 @@ htmlify_host() { --path-err "$HTML_URL_ERR" \ --link-other "$HTML_URL_OK" if [ "$ec" -eq 0 ]; then - python3 "$SITE_GEN/console_to_html.py" \ + _mon_html_py \ --lang "${TALER_MON_LANG:-en}" \ --log "$LOG" \ --out "$mon" \ @@ -385,7 +391,7 @@ htmlify_host() { 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" \ + _mon_html_py \ --lang "${TALER_MON_LANG:-en}" \ --log "$LOG" \ --out "$mon" \ diff --git a/site-gen/console_to_html.py b/site-gen/console_to_html.py index 0874c1e..6adf11a 100755 --- a/site-gen/console_to_html.py +++ b/site-gen/console_to_html.py @@ -4,8 +4,9 @@ from __future__ import annotations import argparse import html -import re import os +import re +import sys from datetime import datetime, timezone from pathlib import Path @@ -69,16 +70,25 @@ def ui(lang: str, key: str, **kwargs) -> str: "jump_first_warn": "Jump to first warning", "filter_errors": "Filter: errors + a few dimmed context lines", "filter_warns": "Filter: warnings + a few dimmed context lines", + "filter_infos": "Filter: info lines + a few dimmed context lines", + "filter_oks": "Filter: OK lines + a few dimmed context lines", "filter_clear": "Clear filter · show all lines", "filter_active_err": "Filter: errors + context", "filter_active_warn": "Filter: warnings + context", + "filter_active_info": "Filter: info + context", + "filter_active_ok": "Filter: OK only + context", "filter_show_all": "Show all", + "info_one": "{n} info", + "info_many": "{n} info", + "ok_one": "{n} ok", + "ok_many": "{n} ok", "env_context": "Monitoring env context", "env_context_title": "Expand: agent env, suite pin, domain hosts & flags (from run header)", "env_context_empty": "No run header in log", "generated": "generated", "version": "version", "version_tree_link": "open this run’s git tree (commit)", + "suite_version_title": "taler-monitoring suite version · exact run tree", "source": "source", "what_monitors": "What this monitors", @@ -147,7 +157,7 @@ def ui(lang: str, key: str, **kwargs) -> str: "mail_i1": "Outside-in phase mail: MX, SMTP/IMAP ports and handshakes, SPF/DMARC", "mail_i2": "Mail hosts: firefly.gnunet.org (taler.net/gnunet.org) and anastasis.taler-systems.com", "mail_i3": "Reported on taler-monitoring-surface HTML (no separate /taler-monitoring-mail* page)", - "footer": "Console-style render of taler-monitoring output. Sticky bar: green = clean · yellow = warnings · red = errors. Click error/warning counts to filter the log (match lines plus a few dimmed neighbours for context). Monitoring env context (agent header) is collapsed by default. Commit pins the exact tree used for this run.", + "footer": "Console-style render of taler-monitoring output. Sticky bar: green = clean · yellow = warnings · red = errors. Click error / warning / info (blue) / ok counts to filter the log (match lines plus dimmed neighbours). Suite version sits under the status pill. Monitoring env context is collapsed by default.", "redirect_fail": "has failures.", "redirect_see": "See {link} for the full console log, error index, and sticky status bar.", "ago_s": "{n}s ago", @@ -168,16 +178,25 @@ def ui(lang: str, key: str, **kwargs) -> str: "jump_first_warn": "Aller au premier avertissement", "filter_errors": "Filtrer : erreurs + quelques lignes de contexte grisées", "filter_warns": "Filtrer : avertissements + quelques lignes de contexte grisées", + "filter_infos": "Filtrer : infos + quelques lignes de contexte grisées", + "filter_oks": "Filtrer : lignes OK + quelques lignes de contexte grisées", "filter_clear": "Effacer le filtre · tout afficher", "filter_active_err": "Filtre : erreurs + contexte", "filter_active_warn": "Filtre : avertissements + contexte", + "filter_active_info": "Filtre : infos + contexte", + "filter_active_ok": "Filtre : OK seulement + contexte", "filter_show_all": "Tout afficher", + "info_one": "{n} info", + "info_many": "{n} infos", + "ok_one": "{n} ok", + "ok_many": "{n} ok", "env_context": "Contexte d'env. de monitoring", "env_context_title": "Déplier : env agent, pin de suite, hôtes domaine et flags (en-tête d'exécution)", "env_context_empty": "Pas d'en-tête d'exécution dans le journal", "generated": "généré", "version": "version", "version_tree_link": "ouvrir l'arbre git de cette exécution (commit)", + "suite_version_title": "version de la suite taler-monitoring · arbre exact de l'exécution", "source": "source", "what_monitors": "Ce que cette page contrôle", @@ -246,7 +265,7 @@ def ui(lang: str, key: str, **kwargs) -> str: "mail_i1": "Phase outside-in mail : MX, ports SMTP/IMAP et handshakes, SPF/DMARC", "mail_i2": "Hôtes mail : firefly.gnunet.org (taler.net/gnunet.org) et anastasis.taler-systems.com", "mail_i3": "Rapporté dans le HTML taler-monitoring-surface (pas de page /taler-monitoring-mail*)", - "footer": "Rendu console de taler-monitoring. Barre collante : vert = OK · jaune = avertissements · rouge = erreurs. Compteurs = filtre. Contexte d'env. (en-tête agent) replié par défaut. Le commit fixe l'arbre exact de cette exécution.", + "footer": "Rendu console de taler-monitoring. Barre collante : vert = OK · jaune = avertissements · rouge = erreurs. Compteurs erreur / avertissement / info (bleu) / ok = filtres. Version de la suite sous le badge d'état. Contexte d'env. replié par défaut.", "redirect_fail": "a des échecs.", "redirect_see": "Voir {link} pour le journal console complet, l'index d'erreurs et la barre de statut.", "ago_s": "il y a {n}s", @@ -289,10 +308,14 @@ def is_numbered_check_line(line: str) -> bool: def classify(line: str) -> str: u = line.upper() low = line.lower() - # SUMMARY box header only when the word SUMMARY is present (not phase section() boxes). - # Phase section() also uses ╔═║╚ with blue BG_SEC — those must stay class "section". - if "SUMMARY" in u: - return "sum-header" + # Real SUMMARY *box* only (╔/║/╚ frames or bare [ SUMMARY ] / [ RESUME ]). + # Never match free text like "perf summary" / "ladder summary" (those are INFO checks). + if re.search(r"\bSUMMARY\b", u) or re.search(r"\bRESUME\b", u): + if re.search(r"[╔║╚]", line) or re.match( + r"^\s*\[\s*(SUMMARY|RESUME)\s*\]", u + ): + return "sum-header" + # else fall through to badge classifiers (INFO/OK/…) # Double-line box frames from section() → section (no sum-* paint) if re.match(r"^\s*[╔╚].*═", line) or re.match(r"^\s*║\s", line): return "section" @@ -534,12 +557,18 @@ def extract_errors(lines: list[str]) -> list[str]: return out -def count_status(lines: list[str]) -> tuple[int, int, int | None, int | None]: - """Return (error_count, warn_count, first_error_idx, first_warn_idx).""" +def count_status( + lines: list[str], +) -> tuple[int, int, int, int, int | None, int | None, int | None, int | None]: + """Return (n_err, n_warn, n_info, n_ok, first_err, first_warn, first_info, first_ok).""" n_err = 0 n_warn = 0 + n_info = 0 + n_ok = 0 first_err: int | None = None first_warn: int | None = None + first_info: int | None = None + first_ok: int | None = None for i, ln in enumerate(lines): kind = classify(ln) if kind in ("error", "blocker"): @@ -552,7 +581,15 @@ def count_status(lines: list[str]) -> tuple[int, int, int | None, int | None]: n_warn += 1 if first_warn is None: first_warn = i - return n_err, n_warn, first_err, first_warn + elif kind == "info": + n_info += 1 + if first_info is None: + first_info = i + elif kind == "ok": + n_ok += 1 + if first_ok is None: + first_ok = i + return n_err, n_warn, n_info, n_ok, first_err, first_warn, first_info, first_ok def slug_error(i: int, text: str) -> str: @@ -965,6 +1002,19 @@ def is_log_noise_line(ln: str) -> bool: return True if "ihr branch ist" in low or "bereits auf" in low: return True + # Converter / host-agent noise that tee appends into the run log after SUMMARY + if "syntaxwarning" in low or "invalid escape sequence" in low: + return True + if "console_to_html.py:" in low: + return True + if re.match(r"^\s*if\s*\(/\^\[╔", s): + return True + if low.lstrip().startswith("wrote ") and "errors=" in low: + return True + if re.match(r"^html\s+\S+", low) and ("→" in s or "->" in s): + return True + if "public url (expected)" in low: + return True return False @@ -1567,7 +1617,8 @@ STICKY_JS = """ return true; } var t = (el.textContent || "").replace(/\u00a0/g, " ").trim(); - if (/^[╔╗╚╝║═\-\s│┌┐└┘]+$/.test(t)) return true; + // box-drawing / pure chrome only (JS char class; escapes doubled for Python). + if (/^[╔╗╚╝║═│┌┐└┘\\s-]+$/.test(t)) return true; return false; } var keep = new Array(lines.length); @@ -1739,8 +1790,9 @@ def build_html( # Split monitoring env context (collapsible, default closed) from check log env_lines, body_raw, env_hint = extract_env_context(raw_lines) # Sticky counts from full log; first-error anchors index into body only - n_err, n_warn, _, _ = count_status(raw_lines) - _, _, first_err_i, first_warn_i = count_status(body_raw) + n_err, n_warn, _n_info, _n_ok, _, _, _, _ = count_status(raw_lines) + _e2, _w2, _i2, _o2, first_err_i, first_warn_i, _fi, _fo = count_status(body_raw) + del _e2, _w2, _i2, _o2, _fi, _fo, _n_info, _n_ok errors = extract_errors(raw_lines) err_slugs: dict[str, str] = {} @@ -2227,7 +2279,8 @@ def main() -> None: for l in log_text.splitlines() if not is_log_noise_line(strip_ansi(l)) ] - n_err, n_warn, _, _ = count_status(raw_for_counts) + n_err, n_warn, _n_info, _n_ok, _, _, _, _ = count_status(raw_for_counts) + del _n_info, _n_ok if args.mode == "redirect": html_out = build_redirect_html( @@ -2262,7 +2315,12 @@ def main() -> None: ) atomic_write_text(args.out, html_out, encoding="utf-8") - print(f"wrote {args.out} (errors={n_err} warnings={n_warn} level={status_level(n_err, n_warn)})") + # stderr only — stdout is often teed into the suite mon log + print( + f"wrote {args.out} (errors={n_err} warnings={n_warn} " + f"level={status_level(n_err, n_warn)})", + file=sys.stderr, + ) if __name__ == "__main__": From 6dd30ce4b8537e2aea431354165012e9e31140e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hern=C3=A2ni=20Marques?= Date: Sun, 19 Jul 2026 05:21:59 +0200 Subject: [PATCH 03/36] release 1.15.8: kill SyntaxWarning box-drawing noise in mon HTML JS --- VERSION | 2 +- VERSIONS.md | 1 + site-gen/console_to_html.py | 13 ++++++++++--- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/VERSION b/VERSION index 545fd57..98e863c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.15.7 +1.15.8 diff --git a/VERSIONS.md b/VERSIONS.md index b4fb134..2cdc5a5 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.15.8** | 2026-07-19 | **Bugfix:** mon HTML filter JS — drop literal box-drawing / `\-` regex that caused Python SyntaxWarning (and weird glyphs in suite logs); use Unicode code-point loop instead. | | **v1.15.7** | 2026-07-19 | **Bugfix:** mon HTML — do not paint «perf/ladder summary» as SUMMARY header (restore 2 INFO lines); drop converter SyntaxWarning/noise from public log; «wrote» on stderr; host-agent PYTHONWARNINGS for convert. | | **v1.15.6** | 2026-07-19 | **Bugfix:** aptdeploy ERROR jump list is self-contained — systemd httpd failures include before/after state, Result/SubState, failed units, and journal snippets (no more vague «see systemd lines»); ldd/--version errors embed concrete missing libs / exit output. | | **v1.15.5** | 2026-07-19 | **Bugfix:** devtesting CHF ladder — `ssh` was draining ladder stdin (only rung 1/23 ran → progress 45/98 then snap 52/52); use `ssh -n` + `mapfile`; incomplete ladder is WARN not false complete. | diff --git a/site-gen/console_to_html.py b/site-gen/console_to_html.py index 6adf11a..64463f5 100755 --- a/site-gen/console_to_html.py +++ b/site-gen/console_to_html.py @@ -1617,9 +1617,16 @@ STICKY_JS = """ return true; } var t = (el.textContent || "").replace(/\u00a0/g, " ").trim(); - // box-drawing / pure chrome only (JS char class; escapes doubled for Python). - if (/^[╔╗╚╝║═│┌┐└┘\\s-]+$/.test(t)) return true; - return false; + // Pure box-drawing / whitespace chrome (no literal box glyphs in source — + // those used to appear as "weird chars" in Python SyntaxWarning output). + if (!t.length) return true; + for (var bi = 0; bi < t.length; bi++) { + var bc = t.charCodeAt(bi); + if (bc === 32 || bc === 9 || bc === 10 || bc === 13 || bc === 0x2d) continue; // sp/tab/nl/cr/- + if (bc >= 0x2500 && bc <= 0x257f) continue; // box drawing + return false; + } + return true; } var keep = new Array(lines.length); var i, j, k, n, idx; From 2d7af7a960045056403d07d6fa484b2d0e599e17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hern=C3=A2ni=20Marques?= Date: Sun, 19 Jul 2026 05:49:42 +0200 Subject: [PATCH 04/36] release 1.16.0: mon sticky info/ok filters + suite version under pill --- VERSION | 2 +- VERSIONS.md | 1 + site-gen/console_to_html.py | 189 ++++++++++++++++++++++++++++-------- 3 files changed, 149 insertions(+), 43 deletions(-) diff --git a/VERSION b/VERSION index 98e863c..15b989e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.15.8 +1.16.0 diff --git a/VERSIONS.md b/VERSIONS.md index 2cdc5a5..ee6efdf 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.16.0** | 2026-07-19 | **Feature:** mon sticky — blue **info** + green **ok** filter badges; suite **version tag under status pill** (not blue badge); filters `#filter-info` / `#filter-ok`. | | **v1.15.8** | 2026-07-19 | **Bugfix:** mon HTML filter JS — drop literal box-drawing / `\-` regex that caused Python SyntaxWarning (and weird glyphs in suite logs); use Unicode code-point loop instead. | | **v1.15.7** | 2026-07-19 | **Bugfix:** mon HTML — do not paint «perf/ladder summary» as SUMMARY header (restore 2 INFO lines); drop converter SyntaxWarning/noise from public log; «wrote» on stderr; host-agent PYTHONWARNINGS for convert. | | **v1.15.6** | 2026-07-19 | **Bugfix:** aptdeploy ERROR jump list is self-contained — systemd httpd failures include before/after state, Result/SubState, failed units, and journal snippets (no more vague «see systemd lines»); ldd/--version errors embed concrete missing libs / exit output. | diff --git a/site-gen/console_to_html.py b/site-gen/console_to_html.py index 64463f5..184e525 100755 --- a/site-gen/console_to_html.py +++ b/site-gen/console_to_html.py @@ -1035,6 +1035,8 @@ def sticky_bar_html( scope: dict[str, object] | None = None, suite_version: str = "", suite_version_url: str = "", + n_info: int = 0, + n_ok: int = 0, ) -> str: level = status_level(n_err, n_warn) lang = "en" @@ -1082,6 +1084,30 @@ def sticky_bar_html( warn_lbl = ui(lang, "warn_many", n=n_warn) warn_stat = f'{html.escape(warn_lbl)}' + # Blue badge = INFO filter (v1.16.0) + if n_info: + info_lbl = ui(lang, "info_one", n=n_info) if n_info == 1 else ui(lang, "info_many", n=n_info) + info_stat = ( + f'{html.escape(info_lbl)}' + ) + else: + info_lbl = ui(lang, "info_many", n=n_info) + info_stat = f'{html.escape(info_lbl)}' + + # Green count badge = OK-only filter + if n_ok: + ok_lbl = ui(lang, "ok_one", n=n_ok) if n_ok == 1 else ui(lang, "ok_many", n=n_ok) + ok_stat = ( + f'{html.escape(ok_lbl)}' + ) + else: + ok_lbl = ui(lang, "ok_many", n=n_ok) + ok_stat = f'{html.escape(ok_lbl)}' + filter_chip = ( f'