Files
stack/infra/images/zotero.Dockerfile
kert 225ce01d6b fix(zotero): heal kasmxproxy display-bridge race + add real healthcheck/observability
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.
2026-06-17 19:49:25 -04:00

68 lines
3.1 KiB
Docker

# syntax=docker/dockerfile:1
FROM ghcr.io/selkies-project/nvidia-egl-desktop:latest
USER root
# Security patches: upgrade all OS packages + remove vulnerable binaries
# we don't need in a Zotero-only container.
# ipp-usb — IPP-over-USB printer daemon (Go stdlib CVEs)
# yq — YAML processor (Go stdlib CVEs)
# firefox — base image browser; Zotero ships its own (expat CVEs)
RUN apt-get update \
&& apt-get upgrade -y \
&& apt-get purge -y --auto-remove ipp-usb firefox yq 2>/dev/null || true \
&& rm -f /usr/sbin/ipp-usb /usr/bin/yq \
&& rm -rf /var/lib/apt/lists/*
# Install Zotero
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
ca-certificates \
&& curl -sL https://raw.githubusercontent.com/retorquere/zotero-deb/master/install.sh | bash \
&& apt-get update && apt-get install -y --no-install-recommends \
zotero \
&& rm -rf /var/lib/apt/lists/*
# Patch Python packages with known CVEs from base image
RUN pip install --no-cache-dir --break-system-packages --upgrade "pillow>=12.1.1"
# Create desktop shortcut for Zotero
RUN mkdir -p /home/ubuntu/Desktop \
&& cp /usr/share/applications/zotero.desktop /home/ubuntu/Desktop/ \
&& chmod +x /home/ubuntu/Desktop/zotero.desktop \
&& chown -R ubuntu:ubuntu /home/ubuntu/Desktop
# Desktop-bridge watchdog + healthcheck deps.
# x11-utils → xdpyinfo (probe X reachability of :20/:21)
# procps → pgrep (detect kasmxproxy / plasmashell)
RUN apt-get update && apt-get install -y --no-install-recommends \
x11-utils \
procps \
&& rm -rf /var/lib/apt/lists/*
# otel-cli: enables OTLP span emission from the bridge watchdog. Dormant by
# default (the guard only emits when OTEL_EXPORTER_OTLP_ENDPOINT is set and
# this container is attached to the `observability` network — see compose.yml).
# Best-effort install so a release-asset hiccup never breaks the image build.
ARG OTEL_CLI_VERSION=0.4.5
RUN arch="$(dpkg --print-architecture | sed -e 's/amd64/amd64/' -e 's/arm64/arm64/')" \
&& curl -fsSL "https://github.com/equinix-labs/otel-cli/releases/download/v${OTEL_CLI_VERSION}/otel-cli_${OTEL_CLI_VERSION}_linux_${arch}.tar.gz" \
-o /tmp/otel-cli.tgz \
&& tar -xzf /tmp/otel-cli.tgz -C /usr/local/bin otel-cli \
&& chmod +x /usr/local/bin/otel-cli \
&& rm -f /tmp/otel-cli.tgz \
|| echo 'WARN: otel-cli install failed; OTLP tracing will be unavailable'
# Desktop-bridge watchdog (fixes the kasmxproxy startup race) + healthcheck.
COPY infra/images/zotero/kasmxproxy-guard.sh /usr/local/bin/kasmxproxy-guard.sh
COPY infra/images/zotero/zotero-healthcheck.sh /usr/local/bin/zotero-healthcheck
COPY infra/images/zotero/kasmxproxy-guard.conf /etc/supervisor/conf.d/kasmxproxy-guard.conf
RUN chmod +x /usr/local/bin/kasmxproxy-guard.sh /usr/local/bin/zotero-healthcheck
# Detect a blank desktop (dead :20->:21 bridge), which container/nginx/Xvnc
# liveness all miss. Compose overrides interval/start_period as needed.
HEALTHCHECK --interval=30s --timeout=10s --start-period=180s --retries=5 \
CMD /usr/local/bin/zotero-healthcheck
USER ubuntu