2026-08-08 17:59:45 -03:00
|
|
|
# syntax=docker/dockerfile:1
|
|
|
|
|
# dockerfiles/Dockerfile.hermes-webui
|
|
|
|
|
FROM ghcr.io/nesquena/hermes-webui@sha256:a83a3893111dcb250e7aa7aa657d3d6f4570b0e2fd00d9b7569246fc5e7339b2 AS webui
|
|
|
|
|
|
2026-08-11 20:22:26 -03:00
|
|
|
FROM registry.bstein.dev/bstein/hermes-agent@sha256:81970563e542f0720773e72297810b3a844b83e381e278f25c0916c78d930107
|
2026-08-08 17:59:45 -03:00
|
|
|
|
|
|
|
|
USER root
|
|
|
|
|
|
|
|
|
|
# Keep WebUI and Hermes pinned together. The WebUI imports Hermes internals,
|
|
|
|
|
# while the gateway remains the only process that owns an agent conversation.
|
|
|
|
|
COPY --from=webui /apptoo /opt/hermes-webui
|
|
|
|
|
|
2026-08-10 00:42:37 -03:00
|
|
|
# Add the Atlas voice bridge as a narrow integration layer. It activates only
|
|
|
|
|
# when a tenant's server-side STT capability reports the private Jetson route.
|
2026-08-23 01:08:35 +00:00
|
|
|
COPY dockerfiles/hermes-webui-base-patch.py /tmp/hermes-webui-base-patch.py
|
2026-08-10 00:42:37 -03:00
|
|
|
COPY dockerfiles/hermes-webui-atlas-patch.py /tmp/hermes-webui-atlas-patch.py
|
2026-08-21 11:12:15 +00:00
|
|
|
COPY dockerfiles/hermes-webui-stt-patch.py /tmp/hermes-webui-stt-patch.py
|
2026-08-16 15:59:09 -03:00
|
|
|
COPY dockerfiles/hermes-webui-telegram-project-patch.py /tmp/hermes-webui-telegram-project-patch.py
|
2026-08-10 00:42:37 -03:00
|
|
|
COPY dockerfiles/hermes-webui-atlas-voice.js /opt/hermes-webui/static/atlas-voice.js
|
2026-08-23 18:29:02 -03:00
|
|
|
COPY dockerfiles/hermes-webui-atlas-voice-worklet.js /opt/hermes-webui/static/atlas-voice-worklet.js
|
2026-08-20 23:13:51 +00:00
|
|
|
COPY dockerfiles/hermes-webui-atlas-voice.css /opt/hermes-webui/static/atlas-voice.css
|
2026-08-11 16:22:19 -03:00
|
|
|
COPY dockerfiles/hermes-webui-router-patch.py /tmp/hermes-webui-router-patch.py
|
|
|
|
|
COPY dockerfiles/hermes-webui-router.js /opt/hermes-webui/static/atlas-router.js
|
2026-08-23 01:08:35 +00:00
|
|
|
COPY dockerfiles/hermes-webui-brand-patch.py /tmp/hermes-webui-brand-patch.py
|
2026-08-23 04:42:08 +00:00
|
|
|
COPY dockerfiles/hermes-webui-manifest-patch.py /tmp/hermes-webui-manifest-patch.py
|
|
|
|
|
COPY dockerfiles/hermes-webui-smoke.py /tmp/hermes-webui-smoke.py
|
2026-08-23 01:08:35 +00:00
|
|
|
COPY dockerfiles/hermes-webui-brand.css /opt/hermes-webui/static/hermes-brand.css
|
2026-08-23 04:42:08 +00:00
|
|
|
COPY dockerfiles/hermes-webui-manifest.json /tmp/hermes-webui-manifest.json
|
2026-08-23 01:08:35 +00:00
|
|
|
COPY dockerfiles/hermes-webui-assets/hermes-agent.ico /opt/hermes-webui/static/hermes-agent.ico
|
|
|
|
|
COPY dockerfiles/hermes-webui-assets/hermes-agent-192.png /opt/hermes-webui/static/hermes-agent-192.png
|
|
|
|
|
COPY dockerfiles/hermes-webui-assets/hermes-agent-512.png /opt/hermes-webui/static/hermes-agent-512.png
|
2026-08-23 16:52:27 -03:00
|
|
|
RUN /opt/hermes/.venv/bin/python /tmp/hermes-webui-base-patch.py \
|
|
|
|
|
&& /opt/hermes/.venv/bin/python /tmp/hermes-webui-atlas-patch.py \
|
|
|
|
|
&& /opt/hermes/.venv/bin/python /tmp/hermes-webui-stt-patch.py \
|
|
|
|
|
&& /opt/hermes/.venv/bin/python /tmp/hermes-webui-telegram-project-patch.py \
|
|
|
|
|
&& /opt/hermes/.venv/bin/python /tmp/hermes-webui-router-patch.py \
|
|
|
|
|
&& /opt/hermes/.venv/bin/python /tmp/hermes-webui-brand-patch.py \
|
|
|
|
|
&& /opt/hermes/.venv/bin/python /tmp/hermes-webui-manifest-patch.py
|
2026-08-10 00:42:37 -03:00
|
|
|
|
2026-08-08 17:59:45 -03:00
|
|
|
RUN /opt/hermes/.venv/bin/python -c 'import cryptography, yaml' \
|
|
|
|
|
&& grep -Fq 'VALID_REASONING_EFFORTS = ("minimal", "low", "medium", "high", "xhigh")' \
|
|
|
|
|
/opt/hermes-webui/api/config.py \
|
2026-08-10 00:42:37 -03:00
|
|
|
&& ! grep -Fq 'data-effort="max"' /opt/hermes-webui/static/index.html \
|
2026-08-23 01:08:35 +00:00
|
|
|
&& grep -Fq 'res.status===401||res.status===403' /opt/hermes-webui/static/ui.js \
|
2026-08-10 00:42:37 -03:00
|
|
|
&& grep -Fq "window.location.assign('/oauth2/start?rd='" /opt/hermes-webui/static/ui.js \
|
|
|
|
|
&& grep -Fq "childrenExpanded?'▾ ':'▸ '" /opt/hermes-webui/static/sessions.js \
|
2026-08-16 15:59:09 -03:00
|
|
|
&& grep -Fq "TELEGRAM_PROJECT_NAME = 'Telegram'" /opt/hermes-webui/api/models.py \
|
2026-08-11 20:22:26 -03:00
|
|
|
&& grep -Fq "'atlas/auto/maximum': 'Automatic · Maximum'" /opt/hermes-webui/static/panels.js \
|
2026-08-10 00:42:37 -03:00
|
|
|
&& grep -Fq 'Atlas Jetson (private)' /opt/hermes-webui/static/index.html \
|
|
|
|
|
&& grep -Fq 'HERMES_WEBUI_ATLAS_TTS_URL' /opt/hermes-webui/api/routes.py \
|
2026-08-21 11:12:15 +00:00
|
|
|
&& grep -Fq 'Audio conversion failed: upload is invalid' /opt/hermes/tools/transcription_tools.py \
|
2026-08-11 16:22:19 -03:00
|
|
|
&& grep -Fq "capability.provider!=='local_command'" /opt/hermes-webui/static/atlas-voice.js \
|
2026-08-20 23:13:51 +00:00
|
|
|
&& grep -Fq 'prefers-reduced-motion: reduce' /opt/hermes-webui/static/atlas-voice.css \
|
|
|
|
|
&& grep -Fq 'id="voiceInstrumentStyles"' /opt/hermes-webui/static/index.html \
|
2026-08-11 16:22:19 -03:00
|
|
|
&& grep -Fq 'data-priority="maximum"' /opt/hermes-webui/static/index.html \
|
|
|
|
|
&& grep -Fq 'routing_priority:priority' /opt/hermes-webui/static/atlas-router.js \
|
2026-08-18 06:55:51 -03:00
|
|
|
&& grep -Fq "'atlas/auto/fast':'Automatic · Fast'" /opt/hermes-webui/static/atlas-router.js \
|
2026-08-11 16:22:19 -03:00
|
|
|
&& grep -Fq 'explicit_reasoning_effort' /opt/hermes-webui/api/gateway_chat.py \
|
2026-08-21 11:52:49 +00:00
|
|
|
&& grep -Fq '"language": detected_language' /opt/hermes/tools/transcription_tools.py \
|
|
|
|
|
&& grep -Fq "'language': detected" /opt/hermes-webui/api/upload.py \
|
|
|
|
|
&& grep -Fq 'def _atlas_tts_language(body):' /opt/hermes-webui/api/routes.py \
|
|
|
|
|
&& grep -Fq 'request_payload["language"] = _atlas_language' /opt/hermes-webui/api/routes.py \
|
|
|
|
|
&& grep -Fq 'takeSttLanguage(token)' /opt/hermes-webui/static/atlas-voice.js \
|
2026-08-23 18:29:02 -03:00
|
|
|
&& grep -Fq "registerProcessor('atlas-pcm-playback'" /opt/hermes-webui/static/atlas-voice-worklet.js \
|
|
|
|
|
&& grep -Fq "const TTS_STREAM_URL='/api/tts/stream'" /opt/hermes-webui/static/atlas-voice.js \
|
|
|
|
|
&& grep -Fq "const STT_STREAM_PATH='/api/transcribe/stream'" /opt/hermes-webui/static/atlas-voice.js \
|
2026-08-23 01:08:35 +00:00
|
|
|
&& grep -Fq '<title>Hermes Chat</title>' /opt/hermes-webui/static/index.html \
|
|
|
|
|
&& grep -Fq 'id="hermesBrandStyles"' /opt/hermes-webui/static/index.html \
|
|
|
|
|
&& grep -Fq 'static/hermes-agent-512.png' /opt/hermes-webui/static/index.html \
|
|
|
|
|
&& grep -Fq 'prefers-reduced-motion: reduce' /opt/hermes-webui/static/hermes-brand.css \
|
|
|
|
|
&& grep -Fq '"name": "Hermes Chat"' /opt/hermes-webui/static/manifest.json \
|
|
|
|
|
&& grep -Fq "'./static/hermes-agent-512.png'" /opt/hermes-webui/static/sw.js \
|
|
|
|
|
&& printf '%s %s\n' \
|
|
|
|
|
'aefe65e6574e6f46d3382588f46c508e1b3f3b3c9ce3dec6d335403a5374add9' \
|
|
|
|
|
'/opt/hermes-webui/static/hermes-agent.ico' \
|
|
|
|
|
| sha256sum -c - \
|
|
|
|
|
&& printf '%s %s\n' \
|
|
|
|
|
'0e4102cc715372058dd6ab55e9cde2567e46fee5ab6557b564cdd212ccd2616f' \
|
|
|
|
|
'/opt/hermes-webui/static/hermes-agent-192.png' \
|
|
|
|
|
| sha256sum -c - \
|
|
|
|
|
&& printf '%s %s\n' \
|
|
|
|
|
'6661e5ca0ecc690af213b9f85f961541e6d3d0e36946ede9d3c2c97dfbd3c23d' \
|
|
|
|
|
'/opt/hermes-webui/static/hermes-agent-512.png' \
|
|
|
|
|
| sha256sum -c - \
|
2026-08-11 16:22:19 -03:00
|
|
|
&& /opt/hermes/.venv/bin/python -m py_compile \
|
|
|
|
|
/opt/hermes-webui/api/routes.py \
|
2026-08-21 11:52:49 +00:00
|
|
|
/opt/hermes-webui/api/upload.py \
|
2026-08-21 11:12:15 +00:00
|
|
|
/opt/hermes-webui/api/gateway_chat.py \
|
|
|
|
|
/opt/hermes/tools/transcription_tools.py
|
2026-08-08 17:59:45 -03:00
|
|
|
|
2026-08-23 04:42:08 +00:00
|
|
|
# Exercise branded responses from the real upstream server process in the
|
|
|
|
|
# target architecture before publish. The manifest checks resolve icon paths
|
|
|
|
|
# from the URL the server actually returns; no filesystem-only assertion can
|
|
|
|
|
# satisfy this gate.
|
2026-08-08 17:59:45 -03:00
|
|
|
RUN set -eu; \
|
|
|
|
|
mkdir -p /tmp/hermes-webui-smoke/home /tmp/hermes-webui-smoke/state /tmp/hermes-webui-smoke/workspace; \
|
|
|
|
|
HERMES_HOME=/tmp/hermes-webui-smoke/home \
|
|
|
|
|
HOME=/tmp/hermes-webui-smoke/home \
|
|
|
|
|
HERMES_WEBUI_STATE_DIR=/tmp/hermes-webui-smoke/state \
|
|
|
|
|
HERMES_WEBUI_DEFAULT_WORKSPACE=/tmp/hermes-webui-smoke/workspace \
|
|
|
|
|
HERMES_WEBUI_HOST=127.0.0.1 \
|
|
|
|
|
HERMES_WEBUI_PORT=18787 \
|
|
|
|
|
HERMES_WEBUI_SKIP_ONBOARDING=1 \
|
|
|
|
|
/opt/hermes/.venv/bin/python /opt/hermes-webui/server.py >/tmp/hermes-webui-smoke.log 2>&1 & \
|
|
|
|
|
server_pid=$!; \
|
2026-08-23 04:42:08 +00:00
|
|
|
cleanup() { kill "${server_pid}" 2>/dev/null || true; wait "${server_pid}" 2>/dev/null || true; }; \
|
|
|
|
|
trap cleanup EXIT HUP INT TERM; \
|
2026-08-08 17:59:45 -03:00
|
|
|
ready=0; \
|
|
|
|
|
for attempt in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30; do \
|
|
|
|
|
if /opt/hermes/.venv/bin/python -c 'from urllib.request import urlopen; urlopen("http://127.0.0.1:18787/health", timeout=2).read()' >/dev/null 2>&1; then ready=1; break; fi; \
|
|
|
|
|
sleep 1; \
|
|
|
|
|
done; \
|
2026-08-23 04:42:08 +00:00
|
|
|
smoke_passed=0; \
|
|
|
|
|
if [ "${ready}" = "1" ] \
|
|
|
|
|
&& /opt/hermes/.venv/bin/python /tmp/hermes-webui-smoke.py http://127.0.0.1:18787/; then \
|
|
|
|
|
smoke_passed=1; \
|
|
|
|
|
fi; \
|
|
|
|
|
cleanup; \
|
|
|
|
|
trap - EXIT HUP INT TERM; \
|
|
|
|
|
if [ "${ready}" != "1" ] || [ "${smoke_passed}" != "1" ]; then \
|
|
|
|
|
cat /tmp/hermes-webui-smoke.log; \
|
|
|
|
|
exit 1; \
|
|
|
|
|
fi; \
|
2026-08-08 17:59:45 -03:00
|
|
|
rm -rf /tmp/hermes-webui-smoke /tmp/hermes-webui-smoke.log
|
|
|
|
|
|
|
|
|
|
ENV HERMES_WEBUI_AGENT_DIR=/opt/hermes \
|
|
|
|
|
HERMES_WEBUI_HOST=0.0.0.0 \
|
|
|
|
|
HERMES_WEBUI_PORT=8787 \
|
|
|
|
|
HERMES_WEBUI_CHAT_BACKEND=gateway \
|
|
|
|
|
HERMES_WEBUI_GATEWAY_BASE_URL=http://127.0.0.1:8642 \
|
|
|
|
|
HERMES_WEBUI_GATEWAY_USE_RUNS_API=true \
|
|
|
|
|
HERMES_WEBUI_SKIP_ONBOARDING=1 \
|
|
|
|
|
HERMES_WEBUI_SECURE=1 \
|
|
|
|
|
PYTHONDONTWRITEBYTECODE=1 \
|
|
|
|
|
PYTHONUNBUFFERED=1
|
|
|
|
|
|
|
|
|
|
WORKDIR /opt/hermes-webui
|
|
|
|
|
USER 10000:10000
|
|
|
|
|
EXPOSE 8787
|
|
|
|
|
ENTRYPOINT ["/opt/hermes/.venv/bin/python", "/opt/hermes-webui/server.py"]
|