diff --git a/VERSION b/VERSION index 84cc529..ec6d649 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.18.0 +1.18.1 diff --git a/VERSIONS.md b/VERSIONS.md index b8fb8d4..bb3d16d 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.18.1** | 2026-07-19 | **Bugfix:** (1) mon HTML sticky/err counts honor French badges **ERREUR** / **AVERT** (were plain → 0 errors on FP pages); (2) `stage-lfp` + `INSIDE_PODMAN=1` uses **host-podman** (no empty `INSIDE_SSH` fail); (3) reaffirm filter JS has no box-glyph/`\\-` regex (charCodeAt only — kills SyntaxWarning “geheimsprache” noise). | | **v1.18.0** | 2026-07-19 | **Feature:** mattermost — client compatibility matrix: **mobile**, **desktop**, **vendor support** floors (default ≥10.11.0, ERROR) + **web** WARN on EOL; env `MATTERMOST_DESKTOP/SUPPORT_MIN_SERVER`, `MATTERMOST_CLIENT_CHECK`. | | **v1.17.0** | 2026-07-19 | **Feature:** mattermost phase — check **server version** (`X-Version-Id`) against **mobile store min** (default **≥10.11.0**); ERROR when too old (newest Android/iOS apps break); env `MATTERMOST_MOBILE_MIN_SERVER` / `MATTERMOST_MOBILE_CHECK=0`. | | **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`. | diff --git a/check_inside.sh b/check_inside.sh index 08d3d5d..38f145f 100755 --- a/check_inside.sh +++ b/check_inside.sh @@ -29,12 +29,14 @@ if [ -z "$PROFILE" ]; then fi fi -# Resolve host-podman vs ssh before first check ID +# Resolve host-podman vs ssh before first check ID. +# stage-lfp on the FP host itself uses local podman (INSIDE_PODMAN=1 / +# INSIDE_MODE=local-podman); laptop → stage still uses INSIDE_SSH. _use_local_podman=0 -if [ "$PROFILE" != "stage-lfp" ]; then - if [ "${INSIDE_PODMAN:-0}" = "1" ] || [ "${INSIDE_MODE:-}" = "local-podman" ]; then - _use_local_podman=1 - elif command -v podman >/dev/null 2>&1 \ +if [ "${INSIDE_PODMAN:-0}" = "1" ] || [ "${INSIDE_MODE:-}" = "local-podman" ]; then + _use_local_podman=1 +elif [ "$PROFILE" != "stage-lfp" ]; then + if command -v podman >/dev/null 2>&1 \ && podman ps --format '{{.Names}}' 2>/dev/null | grep -qE 'taler-hacktivism'; then _use_local_podman=1 fi @@ -52,7 +54,7 @@ section "inside · collect (${PROFILE} · access=${INSIDE_ACCESS})" info "flags" "INSIDE_ACCESS=${INSIDE_ACCESS} INSIDE_PODMAN=${INSIDE_PODMAN:-0} INSIDE_MODE=${INSIDE_MODE:-} LOCAL_STACK=${LOCAL_STACK:-0} SKIP_SSH=${SKIP_SSH:-0} KOOPA_SSH=${KOOPA_SSH:-} INSIDE_SSH=${INSIDE_SSH:-}" # --------------------------------------------------------------------------- -# stage-lfp: low-priv stagepaysan on the FP stage host (INSIDE_SSH) +# stage-lfp: stagepaysan podman (host-local or SSH INSIDE_SSH from laptop) # --------------------------------------------------------------------------- if [ "$PROFILE" = "stage-lfp" ]; then SSH_HOST="${INSIDE_SSH:-}" @@ -69,15 +71,23 @@ if [ "$PROFILE" = "stage-lfp" ]; then STAGE_SSH_T="${INSIDE_SSH_TIMEOUT:-${SSH_CMD_TIMEOUT:-24}}" if [ "${STAGE_SSH_T}" -lt 24 ] 2>/dev/null; then STAGE_SSH_T=24; fi - if ! mon_ssh_ok "$SSH_HOST"; then - err "ssh" "cannot reach ${SSH_HOST} (stagepaysan low-priv) — set INSIDE_SSH= or SKIP" + if [ "$_use_local_podman" = "1" ]; then + if ! command -v podman >/dev/null 2>&1; then + err "host" "INSIDE_PODMAN/host-podman but podman missing" + summary + exit 1 + fi + ok "host→container" "podman exec on this host (INSIDE_ACCESS=host-podman · no SSH)" + elif ! mon_ssh_ok "$SSH_HOST"; then + err "ssh" "cannot reach ${SSH_HOST:-?} (stagepaysan low-priv) — set INSIDE_SSH= or INSIDE_PODMAN=1" summary exit 1 + else + ok "ssh ${SSH_HOST}" "stagepaysan (podman, no sudo)" fi - ok "ssh ${SSH_HOST}" "stagepaysan (podman, no sudo)" - # Inject names/ports into remote (ssh bash -s does not inherit local env). - RAW=$( + # Inject names/ports (ssh bash -s does not inherit local env; local bash does). + _stage_lfp_script() { { printf 'BANK_CTR=%q; EX_CTR=%q; MER_CTR=%q\n' "$BANK_CTR" "$EX_CTR" "$MER_CTR" printf 'BANK_PORT=%q; EX_PORT=%q; MER_PORT=%q\n' "$BANK_PORT" "$EX_PORT" "$MER_PORT" @@ -173,11 +183,20 @@ fi echo DONE REMOTE - } | mon_ssh_bash "$SSH_HOST" "${STAGE_SSH_T}" || true - ) + } + } + if [ "$_use_local_podman" = "1" ]; then + RAW=$(_stage_lfp_script | bash || true) + else + RAW=$(_stage_lfp_script | mon_ssh_bash "$SSH_HOST" "${STAGE_SSH_T}" || true) + fi if [ -z "$RAW" ] || ! echo "$RAW" | grep -q '^E|'; then - err "ssh" "stage remote timed out or empty (cap ${STAGE_SSH_T}s · host=${SSH_HOST})" + if [ "$_use_local_podman" = "1" ]; then + err "host" "stage collect timed out or empty (local podman)" + else + err "ssh" "stage remote timed out or empty (cap ${STAGE_SSH_T}s · host=${SSH_HOST})" + fi summary exit 1 fi @@ -189,7 +208,7 @@ REMOTE IFS='|' read -r _ comp level key detail <<<"$line" case "$comp" in bank|exchange|merchant|caddy) _g="$comp" ;; - *) _g="ssh" ;; + *) _g="$INSIDE_ACCESS" ;; esac if [ "$_g" != "$_last_inside_grp" ]; then set_group "$_g" @@ -242,7 +261,29 @@ REMOTE # Host load as stagepaysan (no container RSS from koopa metrics) set_group load section "inside · load (stagepaysan host)" - LOAD_LINE=$(mon_ssh_bash "$SSH_HOST" 8 <<'EOF' || true + _load_py() { + python3 - <<'PY' +import os +la=os.getloadavg() +print("loadavg=%.2f,%.2f,%.2f" % la) +try: + with open("/proc/meminfo") as f: + d={} + for line in f: + k,v=line.split(":")[0], line.split(":")[1].strip().split()[0] + d[k]=int(v) + total=d.get("MemTotal",0)/1024/1024 + avail=d.get("MemAvailable",0)/1024/1024 + used=total-avail + print("mem_used=%.2fGiB avail=%.2fGiB total=%.2fGiB" % (used, avail, total)) +except Exception: + print("mem=?") +PY + } + if [ "$_use_local_podman" = "1" ]; then + LOAD_LINE=$(_load_py || true) + else + LOAD_LINE=$(mon_ssh_bash "$SSH_HOST" 8 <<'EOF' || true python3 - <<'PY' import os la=os.getloadavg() @@ -262,6 +303,7 @@ except Exception: PY EOF ) + fi if [ -n "$LOAD_LINE" ]; then info "stage host" "$(echo "$LOAD_LINE" | tr '\n' ' ')" else @@ -270,7 +312,8 @@ EOF set_group disk section "inside · disk free space (stage host + containers)" - _disk_raw=$(mon_ssh_bash "$SSH_HOST" "${STAGE_SSH_T:-24}" <<'DISK' || true + _disk_script() { + cat <<'DISK' set +e echo "###HOST###" df -Pk / /var /home /tmp /mnt/data 2>/dev/null || df -Pk @@ -280,9 +323,16 @@ for c in $(podman ps --format '{{.Names}}' 2>/dev/null); do podman exec "$c" df -Pk / /var /tmp 2>/dev/null || podman exec "$c" df -Pk 2>/dev/null done DISK -) + } + if [ "$_use_local_podman" = "1" ]; then + _disk_raw=$(_disk_script | bash || true) + _disk_label="host" + else + _disk_raw=$(_disk_script | mon_ssh_bash "$SSH_HOST" "${STAGE_SSH_T:-24}" || true) + _disk_label="ssh:${SSH_HOST}" + fi _host_df=$(printf '%s\n' "$_disk_raw" | sed -n '/^###HOST###$/,/^###CTRS###$/p' | sed '1d;$d') - mon_disk_check_remote_text "ssh:${SSH_HOST}" "$_host_df" || true + mon_disk_check_remote_text "$_disk_label" "$_host_df" || true _ctr=""; _buf="" while IFS= read -r _line || [ -n "$_line" ]; do case "$_line" in diff --git a/host-agent/update-suite.sh b/host-agent/update-suite.sh index e93aa11..c24f7ce 100755 --- a/host-agent/update-suite.sh +++ b/host-agent/update-suite.sh @@ -8,7 +8,8 @@ # 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_MODE reset (default) | pull | keep +# keep = no fetch/reset (local deploy / unpushed fixes) # SUITE_UPDATE_STRICT 1 (default) = fetch/reset failure aborts the agent run # 0 = warn and continue on old tree (emergency only) # @@ -55,29 +56,41 @@ _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 +case "$SUITE_UPDATE_MODE" in + keep|skip|off|none) + echo "suite update skipped (SUITE_UPDATE_MODE=$SUITE_UPDATE_MODE) — keep local tree ${_before} (${_before_ver})" + ;; + *) + 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 + ;; + pull) + if ! git pull --ff-only origin "$SUITE_GIT_REF" 2>&1; then + _upd_die "git pull --ff-only failed" + fi + ;; + *) + if ! git pull --ff-only origin "$SUITE_GIT_REF" 2>&1; then + _upd_die "git pull --ff-only failed (mode=$SUITE_UPDATE_MODE)" + fi + ;; + esac + fi + ;; +esac COMMIT=$(git rev-parse HEAD) COMMIT_SHORT=$(git rev-parse --short=12 HEAD) diff --git a/site-gen/console_to_html.py b/site-gen/console_to_html.py index 7ec303f..4aab08d 100755 --- a/site-gen/console_to_html.py +++ b/site-gen/console_to_html.py @@ -323,7 +323,15 @@ def classify(line: str) -> str: # 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" - if "--- ERRORS" in u or "ERRORS ·" in u or "ERRORS (" in u: + # EN + FR section headers (i18n_tag ERROR→ERREUR, ERRORS→ERREURS) + if ( + "--- ERRORS" in u + or "--- ERREURS" in u + or "ERRORS ·" in u + or "ERREURS ·" in u + or "ERRORS (" in u + or "ERREURS (" in u + ): return "meta" if "numbered checks" in low: return "meta" @@ -350,14 +358,15 @@ def classify(line: str) -> str: return "sum-block" return "blocker" # Only real check badges (not free-text "ERROR" inside commit messages, - # and not SUMMARY count rows like " [ ERROR ] 3") - if re.search(r"┌\s*ERROR\b|\[\s*ERROR\b", u): + # and not SUMMARY count rows like " [ ERROR ] 3"). + # French console (i18n_tag): ERROR→ERREUR, WARN→AVERT (v1.18.1). + if re.search(r"┌\s*(?:ERROR|ERREUR)\b|\[\s*(?:ERROR|ERREUR)\b", u): if not is_numbered_check_line(line): return "sum-err" return "error" if re.search(r"\bERROR monpages\b", line, re.I): return "error" - if re.search(r"┌\s*WARN\b|\[\s*WARN\b", u): + if re.search(r"┌\s*(?:WARN|AVERT)\b|\[\s*(?:WARN|AVERT)\b", u): if not is_numbered_check_line(line): return "sum-warn" return "warn" @@ -508,11 +517,25 @@ def extract_errors(lines: list[str]) -> list[str]: timeout_detail = "" in_block = False for ln in lines: - if "--- ERRORS" in ln or "ERRORS (failed" in ln or "RUN TIMEOUT · extraordinary" in ln: + u = ln.upper() + if ( + "--- ERRORS" in u + or "--- ERREURS" in u + or "ERRORS (FAILED" in u + or "ERREURS (CONTROLES" in u + or "ERREURS (FAILED" in u + or "RUN TIMEOUT · EXTRAORDINARY" in u + ): in_block = True continue if in_block: - if ln.strip().startswith("---") or "[ SUMMARY" in ln or "totals:" in ln: + if ( + ln.strip().startswith("---") + or "[ SUMMARY" in ln + or "[ RESUME" in u + or "totals:" in ln.lower() + or "totaux :" in ln.lower() + ): in_block = False continue m = ERR_BULLET_RE.match(ln) @@ -522,10 +545,15 @@ def extract_errors(lines: list[str]) -> list[str]: timeout_detail = body else: errs.append(body) - elif "ERROR" in ln.upper() and ln.strip() and not is_run_timeout_text(ln): + elif ("ERROR" in u or "ERREUR" in u) and ln.strip() and not is_run_timeout_text(ln): errs.append(ln.strip()) # Only real check badges (not INFO text mentioning thresholds like "… ERROR (0=disable)") - if re.search(r"┌\s*ERROR\b|\[\s*ERROR\b", ln, re.I) and "--- ERRORS" not in ln: + # FR: i18n_tag ERROR→ERREUR (v1.18.1) + if ( + re.search(r"┌\s*(?:ERROR|ERREUR)\b|\[\s*(?:ERROR|ERREUR)\b", ln, re.I) + and "--- ERRORS" not in u + and "--- ERREURS" not in u + ): if is_run_timeout_text(ln): if "run.timeout-01" in ln.lower() or "RUN_TIMEOUT exceeded" in ln: timeout_detail = re.sub(r"\s+", " ", strip_ansi(ln)).strip() @@ -534,8 +562,10 @@ def extract_errors(lines: list[str]) -> list[str]: continue if classify(ln) not in ("error", "blocker"): continue - body = re.sub(r"^.*\bERROR\b\s*\]?\s*", "", ln, flags=re.I).strip(" ·") - if body and body not in errs and "failed — see" not in body.lower(): + body = re.sub( + r"^.*\b(?:ERROR|ERREUR)\b\s*[\]┐]?\s*", "", ln, flags=re.I + ).strip(" ·") + if body and body not in errs and "failed — see" not in body.lower() and "echec — voir" not in body.lower(): if re.search( r"#\d+|www\.|e2e\.|auth401\.|versions\.|inside\.|surface\.|aptdeploy\.", body, @@ -1699,13 +1729,15 @@ STICKY_JS = """ return true; } var t = (el.textContent || "").replace(/\u00a0/g, " ").trim(); - // Pure box-drawing / whitespace chrome (no literal box glyphs in source — - // those used to appear as "weird chars" in Python SyntaxWarning output). + // Skip pure chrome lines (spaces/tabs/newlines/hyphen + Unicode box-drawing). + // Do NOT use a JS character-class regex with box glyphs here: when this + // script is embedded in a Python string, Python emits SyntaxWarning for + // sequences like \\- and the glyphs show up as noise in mon logs. 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 + if (bc === 32 || bc === 9 || bc === 10 || bc === 13 || bc === 0x2d) continue; + if (bc >= 0x2500 && bc <= 0x257f) continue; // U+2500–U+257F box drawing return false; } return true;