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.
89 lines
2.5 KiB
JSON
89 lines
2.5 KiB
JSON
{
|
|
"title": "Zotero Desktop",
|
|
"uid": "zotero-desktop",
|
|
"tags": ["zotero", "desktop", "kasmvnc"],
|
|
"timezone": "browser",
|
|
"schemaVersion": 39,
|
|
"time": { "from": "now-6h", "to": "now" },
|
|
"refresh": "30s",
|
|
"panels": [
|
|
{
|
|
"id": 1,
|
|
"title": "Bridge down events (1h)",
|
|
"type": "stat",
|
|
"gridPos": { "x": 0, "y": 0, "w": 6, "h": 4 },
|
|
"datasource": { "uid": "loki" },
|
|
"targets": [
|
|
{
|
|
"expr": "sum(count_over_time({container=\"zotero\", zotero_event=\"bridge_down\"} [1h]))",
|
|
"queryType": "instant"
|
|
}
|
|
],
|
|
"options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "colorMode": "background" },
|
|
"fieldConfig": {
|
|
"defaults": {
|
|
"thresholds": {
|
|
"mode": "absolute",
|
|
"steps": [
|
|
{ "color": "green", "value": null },
|
|
{ "color": "red", "value": 1 }
|
|
]
|
|
}
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"id": 2,
|
|
"title": "Bridge start failures (1h)",
|
|
"type": "stat",
|
|
"gridPos": { "x": 6, "y": 0, "w": 6, "h": 4 },
|
|
"datasource": { "uid": "loki" },
|
|
"targets": [
|
|
{
|
|
"expr": "sum(count_over_time({container=\"zotero\", zotero_event=\"bridge_start_failed\"} [1h]))",
|
|
"queryType": "instant"
|
|
}
|
|
],
|
|
"options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "colorMode": "background" },
|
|
"fieldConfig": {
|
|
"defaults": {
|
|
"thresholds": {
|
|
"mode": "absolute",
|
|
"steps": [
|
|
{ "color": "green", "value": null },
|
|
{ "color": "red", "value": 1 }
|
|
]
|
|
}
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"id": 3,
|
|
"title": "Desktop-bridge events",
|
|
"type": "timeseries",
|
|
"gridPos": { "x": 12, "y": 0, "w": 12, "h": 8 },
|
|
"datasource": { "uid": "loki" },
|
|
"targets": [
|
|
{
|
|
"expr": "sum by (zotero_event) (count_over_time({container=\"zotero\"} | zotero_event=~\".+\" [5m]))",
|
|
"legendFormat": "{{zotero_event}}"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": 4,
|
|
"title": "Bridge + watchdog log tail",
|
|
"type": "logs",
|
|
"gridPos": { "x": 0, "y": 4, "w": 12, "h": 12 },
|
|
"datasource": { "uid": "loki" },
|
|
"targets": [
|
|
{
|
|
"expr": "{container=\"zotero\"} | zotero_event=~\".+\"",
|
|
"queryType": "range"
|
|
}
|
|
],
|
|
"options": { "showTime": true, "sortOrder": "Descending", "wrapLogMessage": true }
|
|
}
|
|
]
|
|
}
|