The Dockerfile was switching to USER root for the final chown but never switched back to USER hermes. This caused ALL container processes to run as root (uid 0) instead of the hermes user (uid 10000). The entrypoint's gosu privilege drop only caught the main exec chain, leaving backgrounded subprocesses (dashboard PTY sessions, workers with start_new_session=True) running as root — creating files owned by root in ExoKortex and breaking Syncthing sync. Adding USER hermes at the end ensures the container runs unprivileged and ALL child processes inherit uid 10000 from the start.
83 lines
3.2 KiB
Docker
83 lines
3.2 KiB
Docker
# syntax=docker/dockerfile:1
|
|
# Hermes Agent -- custom fork build
|
|
# Builds on top of official image + overlays our forked source from Gitea.
|
|
# Requires Docker BuildKit. Pass SSH agent for git clone:
|
|
# docker compose build hermes
|
|
# Or manually:
|
|
# DOCKER_BUILDKIT=1 docker build --ssh default -t hermes-agent:custom .
|
|
|
|
# ---------- Base: official Hermes image (system deps, npm, uv, Playwright) ----------
|
|
FROM nousresearch/hermes-agent:latest
|
|
|
|
# ---------- Extra system deps ----------
|
|
USER root
|
|
RUN apt-get update && \
|
|
apt-get install -y --no-install-recommends \
|
|
libportaudio2 ca-certificates poppler-utils imagemagick \
|
|
libolm-dev \
|
|
texlive-latex-base texlive-latex-extra texlive-fonts-recommended \
|
|
texlive-xetex texlive-science \
|
|
qemu-user-static binfmt-support emacs-nox && \
|
|
rm -rf /var/lib/apt/lists/*
|
|
|
|
# ---------- UV ----------
|
|
COPY --chmod=0755 --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/
|
|
|
|
WORKDIR /opt/hermes
|
|
|
|
# ---------- Matrix bridge + extra pip deps ----------
|
|
# Previously installed inline at container startup and persisted via volume mount.
|
|
# Now baked into the image so the fragile venv volume mount can be removed.
|
|
RUN . /opt/hermes/.venv/bin/activate && \
|
|
uv pip install --no-cache-dir 'mautrix[encryption]' openai
|
|
|
|
# ---------- Piper TTS ----------
|
|
RUN . /opt/hermes/.venv/bin/activate && \
|
|
uv pip install --no-cache-dir piper-tts sounddevice numpy && \
|
|
mkdir -p /opt/hermes/.venv/share/piper/voices
|
|
|
|
RUN /opt/hermes/.venv/bin/python3 /dev/stdin << 'PYEOF'
|
|
import urllib.request
|
|
base = '/opt/hermes/.venv/share/piper/voices'
|
|
url = 'https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/ryan/high/en_US-ryan-high.onnx'
|
|
urllib.request.urlretrieve(url, base + '/en_US-ryan-high.onnx')
|
|
urllib.request.urlretrieve(url + '.json', base + '/en_US-ryan-high.onnx.json')
|
|
PYEOF
|
|
|
|
# ---------- Install Himalaya email CLI ----------
|
|
RUN /opt/hermes/.venv/bin/python3 /dev/stdin << 'PYEOF'
|
|
import urllib.request, tarfile, os, shutil
|
|
url = 'https://github.com/pimalaya/himalaya/releases/download/v1.2.0/himalaya.x86_64-linux.tgz'
|
|
tgz = '/tmp/himalaya.tgz'
|
|
urllib.request.urlretrieve(url, tgz)
|
|
with tarfile.open(tgz) as t:
|
|
t.extractall('/tmp')
|
|
shutil.move('/tmp/himalaya', '/usr/local/bin/himalaya')
|
|
os.chmod('/usr/local/bin/himalaya', 0o755)
|
|
os.remove(tgz)
|
|
print('himalaya v1.2.0 installed')
|
|
PYEOF
|
|
|
|
# ---------- Install multi-gateway launcher ----------
|
|
# Launches one gateway process per profile (HERMES_PROFILES env var)
|
|
COPY --chmod=0755 run-multi-gateways.sh /usr/local/bin/run-multi-gateways.sh
|
|
|
|
# ---------- Runtime ----------
|
|
USER hermes
|
|
ENV HERMES_HOME=/opt/data
|
|
ENV PATH="/opt/data/.local/bin:${PATH}"
|
|
# Point browser tool to Playwright's Chromium (already in base image)
|
|
ENV CHROME_EXECUTABLE=/opt/hermes/.playwright/chromium/chrome-linux/chrome
|
|
|
|
# Ensure tools directory and toolsets.py are writable by the hermes runtime user
|
|
# so custom tools can be injected from the persistent volume at startup.
|
|
USER root
|
|
RUN chown -R hermes:hermes /opt/hermes/tools /opt/hermes/toolsets.py
|
|
|
|
VOLUME [ "/opt/data" ]
|
|
|
|
# Switch to the hermes user so the container runs unprivileged.
|
|
# All child processes inherit this user — no more orphan root processes
|
|
# that escape gosu privilege drops in the entrypoint.
|
|
USER hermes
|