zotero.fhirworx.io rendered a blank desktop: KDE+Zotero draw on Xvfb :20, but kasmvnc serves :21, and the kasmxproxy mirror that bridges them never started. The stock kasmvnc-entrypoint.sh blocks on `until [ -S /tmp/.X11-unix/X21 ]` (a filesystem socket path) while entrypoint.sh's concurrent `rm -rf /tmp/.X*` unlinks it — Xvnc keeps its fd + abstract socket open and serves a blank :21 forever. A startup race, hence the intermittent "doesn't open" with every process reporting healthy. - kasmxproxy-guard.sh: supervised watchdog that probes :20/:21 via xdpyinfo (not the racy socket file) and (re)launches kasmxproxy, self-healing if it dies. Emits structured `zotero_event=` logs to container stdout + a heartbeat file. - zotero-healthcheck.sh + compose healthcheck: detect the real failure mode (bridge down / :21 unreachable / KDE dead / stale heartbeat) that container/nginx/Xvnc liveness all miss. - promtail: extract zotero_event into a Loki label. - Grafana: Zotero Desktop dashboard + alert on sustained bridge failure. - otel-cli baked in; OTLP tracing dormant by default (enabling it requires attaching zotero to the observability network — documented opt-in, left off to preserve gateway-only isolation). Verified live: guard self-heals on kasmxproxy kill; healthcheck passes healthy / fails on stale heartbeat.
26 lines
1.1 KiB
Bash
Executable File
26 lines
1.1 KiB
Bash
Executable File
#!/bin/bash
|
|
# Zotero container healthcheck.
|
|
#
|
|
# The container is only "healthy" if the desktop the browser actually sees
|
|
# (KasmVNC display :21) is being driven. This probes the real failure mode the
|
|
# stock checks miss: a blank :21 because the :20->:21 kasmxproxy bridge died
|
|
# (see kasmxproxy-guard.sh). Container-up / nginx-up / Xvnc-up are all true in
|
|
# that failure, which is exactly why it went unnoticed.
|
|
set -u
|
|
|
|
DST_DISPLAY="${KASMVNC_DISPLAY:-:21}"
|
|
HEARTBEAT="${ZOTERO_HEARTBEAT:-/tmp/kasmxproxy-guard.heartbeat}"
|
|
MAX_AGE="${ZOTERO_HEARTBEAT_MAX_AGE:-30}"
|
|
|
|
fail() { echo "unhealthy: $1" >&2; exit 1; }
|
|
|
|
pgrep -x kasmxproxy >/dev/null 2>&1 || fail "kasmxproxy (desktop bridge) not running"
|
|
DISPLAY="$DST_DISPLAY" xdpyinfo >/dev/null 2>&1 || fail "KasmVNC display ${DST_DISPLAY} unreachable"
|
|
pgrep -x plasmashell >/dev/null 2>&1 || fail "KDE session (plasmashell) not running"
|
|
[ -f "$HEARTBEAT" ] || fail "guard heartbeat missing"
|
|
|
|
age=$(( $(date +%s) - $(stat -c %Y "$HEARTBEAT") ))
|
|
[ "$age" -lt "$MAX_AGE" ] || fail "guard heartbeat stale (${age}s >= ${MAX_AGE}s)"
|
|
|
|
echo "healthy: bridge up, ${DST_DISPLAY} reachable, session alive (heartbeat ${age}s)"
|