titan-iac/dockerfiles/Dockerfile.hermes-agent
2026-08-02 16:46:21 -03:00

85 lines
3.2 KiB
Docker

# syntax=docker/dockerfile:1
# dockerfiles/Dockerfile.hermes-agent
FROM nousresearch/hermes-agent@sha256:9c841866021c54c4596849f6135717e8a4d52ba510b7f52c50aef1de1a283973
USER root
# Keep dashboard chat sockets tied to the intended React mount and conversation.
# A resumed conversation needs a different PTY attachment key from a fresh chat;
# reconnects to that same conversation must keep using the same key.
RUN node <<'NODE'
const fs = require("node:fs");
const path = "/opt/hermes/web/src/pages/ChatPage.tsx";
let source = fs.readFileSync(path, "utf8");
const socketBefore = [
' const url = await api.buildWsUrl("/api/pty", params);',
' const ws = new WebSocket(url);',
].join("\n");
const socketAfter = [
' const url = await api.buildWsUrl("/api/pty", params);',
' if (unmounting) return;',
' const ws = new WebSocket(url);',
].join("\n");
const attachBefore = ' params.attach = ptyAttachToken(forceFresh);';
const attachAfter = [
' const attachScope = resumeParam',
' ? `resume:${resumeParam}:${scopedProfile ?? ""}`',
' : `fresh:${scopedProfile ?? ""}`;',
' params.attach = `${ptyAttachToken(forceFresh)}:${attachScope}`;',
].join("\n");
if (!source.includes(socketBefore)) {
throw new Error("Hermes ChatPage WebSocket patch context changed");
}
if (!source.includes(attachBefore)) {
throw new Error("Hermes ChatPage PTY attachment patch context changed");
}
source = source.replace(socketBefore, socketAfter);
source = source.replace(attachBefore, attachAfter);
fs.writeFileSync(path, source);
NODE
# The upstream self-hosted OIDC plugin authenticates users but deliberately
# treats the dashboard as one shared workstation. Allow a deployment to narrow
# that workstation to an explicit OIDC subject without changing default
# behavior for the operator instance.
RUN python - <<'PY'
from pathlib import Path
path = Path("/opt/hermes/plugins/dashboard_auth/self_hosted/__init__.py")
source = path.read_text()
before = ''' if not user_id:
raise ProviderError("ID token missing 'sub' (user_id) claim")
email = str(claims.get("email", "") or "")
'''
after = ''' if not user_id:
raise ProviderError("ID token missing 'sub' (user_id) claim")
allowed_user_ids = {
value.strip()
for value in os.environ.get(
"HERMES_DASHBOARD_OIDC_ALLOWED_USER_IDS", ""
).split(",")
if value.strip()
}
if allowed_user_ids and user_id not in allowed_user_ids:
raise ProviderError("This account is not authorized for this dashboard")
email = str(claims.get("email", "") or "")
'''
if before not in source:
raise SystemExit("Hermes self-hosted OIDC patch context changed")
path.write_text(source.replace(before, after))
PY
COPY dockerfiles/hermes-session-migrate.py /opt/hermes/bin/hermes-session-migrate
RUN cd /opt/hermes/web \
&& npm run build \
&& grep -Fq 'if (unmounting) return;' src/pages/ChatPage.tsx \
&& grep -Fq 'resume:${resumeParam}' src/pages/ChatPage.tsx \
&& grep -Fq 'HERMES_DASHBOARD_OIDC_ALLOWED_USER_IDS' \
/opt/hermes/plugins/dashboard_auth/self_hosted/__init__.py \
&& chmod 0755 /opt/hermes/bin/hermes-session-migrate