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.