From f9afd79f3ecb0fd6ef1e0adeb0e4a10fdb5ecb07 Mon Sep 17 00:00:00 2001 From: Thierry Pouplier Date: Wed, 29 Apr 2026 02:19:24 +0000 Subject: [PATCH 01/20] fix: Add openai and matrix-nio dependencies for Hermes Matrix bridge --- ai/compose.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) mode change 100644 => 100755 ai/compose.yml diff --git a/ai/compose.yml b/ai/compose.yml old mode 100644 new mode 100755 index 460d44d..639df16 --- a/ai/compose.yml +++ b/ai/compose.yml @@ -30,7 +30,10 @@ services: container_name: hermes restart: always # Gateway run enables the internal API server on port 8642 - command: gateway run + # Install openai and matrix-nio for Matrix bridge compatibility on startup + # Uses uv (modern Python package manager) with --system flag for venv installation + entrypoint: > + sh -c "/opt/hermes/.venv/bin/uv pip install openai matrix-nio[encryption] --system -q && /opt/hermes/.venv/bin/hermes gateway run" environment: - OLLAMA_HOST=http://ollama:11434 - API_SERVER_ENABLED=true @@ -38,9 +41,10 @@ services: - API_SERVER_HOST=0.0.0.0 - API_SERVER_KEY=hermes_local_key - GATEWAY_ALLOW_ALL_USERS=true - - OPENROUTER_API_KEY=${OPENROUTER_API_KEY} + - OPENROUTER_API_KEY=${OPEN...KEY} volumes: - /mnt/HoardingCow_docker_data/Hermes/data:/opt/data + - /mnt/HoardingCow_docker_data/Hermes/venv:/opt/hermes/.venv devices: - /dev/kfd:/dev/kfd - /dev/dri:/dev/dri From a404f5e2c44411fac1c88fe07e9af724458012e2 Mon Sep 17 00:00:00 2001 From: Thierry Pouplier Date: Wed, 29 Apr 2026 02:43:35 +0000 Subject: [PATCH 02/20] fix: Correct OPENROUTER_API_KEY variable name --- ai/compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ai/compose.yml b/ai/compose.yml index 639df16..72ebf85 100755 --- a/ai/compose.yml +++ b/ai/compose.yml @@ -41,7 +41,7 @@ services: - API_SERVER_HOST=0.0.0.0 - API_SERVER_KEY=hermes_local_key - GATEWAY_ALLOW_ALL_USERS=true - - OPENROUTER_API_KEY=${OPEN...KEY} + - OPENROUTER_API_KEY=${OPENROUTER_API_KEY} volumes: - /mnt/HoardingCow_docker_data/Hermes/data:/opt/data - /mnt/HoardingCow_docker_data/Hermes/venv:/opt/hermes/.venv From 2aab06cc1a76b2b13400453f318fbc97b80d061f Mon Sep 17 00:00:00 2001 From: Thierry Pouplier Date: Wed, 29 Apr 2026 03:34:15 +0000 Subject: [PATCH 03/20] fix: use mautrix[encryption] instead of matrix-nio for Matrix bridge The Hermes Matrix gateway uses the mautrix SDK, not matrix-nio. This fixes E2EE support by installing the correct library. Refs: PR #2 --- ai/compose.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ai/compose.yml b/ai/compose.yml index 72ebf85..e96993f 100755 --- a/ai/compose.yml +++ b/ai/compose.yml @@ -30,10 +30,10 @@ services: container_name: hermes restart: always # Gateway run enables the internal API server on port 8642 - # Install openai and matrix-nio for Matrix bridge compatibility on startup + # Install openai and mautrix[encryption] for Matrix bridge with E2EE support on startup # Uses uv (modern Python package manager) with --system flag for venv installation entrypoint: > - sh -c "/opt/hermes/.venv/bin/uv pip install openai matrix-nio[encryption] --system -q && /opt/hermes/.venv/bin/hermes gateway run" + sh -c "/opt/hermes/.venv/bin/uv pip install openai mautrix[encryption] --system -q && /opt/hermes/.venv/bin/hermes gateway run" environment: - OLLAMA_HOST=http://ollama:11434 - API_SERVER_ENABLED=true From 4e566b24081c114c9be79418ec51121f43eb0d31 Mon Sep 17 00:00:00 2001 From: Hermes Date: Wed, 20 May 2026 13:14:10 -0400 Subject: [PATCH 04/20] fix: resolve Docker build errors and add Traefik routing for Hermes web UI - Replace rsync with cp -a (rsync unavailable in latest upstream base image) - Remove npm run build step (fork's package.json has no build script) - Remove himalaya-ro.sh from build context (deployed via install.sh) - Add hermes to ai_net network for Traefik access - Add Traefik labels routing hermes.lazyworkhorse.net to dashboard port 9119 --- ai/compose.yml | 18 ++++++++++ ai/hermes/Dockerfile | 14 ++------ ai/hermes/himalaya-ro.sh | 73 ---------------------------------------- 3 files changed, 20 insertions(+), 85 deletions(-) delete mode 100644 ai/hermes/himalaya-ro.sh diff --git a/ai/compose.yml b/ai/compose.yml index 1db7831..17d6170 100644 --- a/ai/compose.yml +++ b/ai/compose.yml @@ -66,6 +66,24 @@ services: - "26" networks: - ai_backend + - ai_net + labels: + - "traefik.enable=true" + - "traefik.docker.network=ai_net" + + # Router for HTTP + redirection to HTTPS + - "traefik.http.routers.hermes-web-http.rule=Host(`hermes.lazyworkhorse.net`)" + - "traefik.http.routers.hermes-web-http.entrypoints=web" + - "traefik.http.routers.hermes-web-http.middlewares=redirect-to-https" + + # Router for HTTPS with TLS + - "traefik.http.routers.hermes-web-https.rule=Host(`hermes.lazyworkhorse.net`)" + - "traefik.http.routers.hermes-web-https.entrypoints=websecure" + - "traefik.http.routers.hermes-web-https.tls=true" + - "traefik.http.routers.hermes-web-https.tls.certresolver=njalla" + + # Service Loadbalancer (dashboard port 9119) + - "traefik.http.services.hermes-web.loadbalancer.server.port=9119" syncthing: image: syncthing/syncthing:latest diff --git a/ai/hermes/Dockerfile b/ai/hermes/Dockerfile index a6edcfc..1b775e7 100644 --- a/ai/hermes/Dockerfile +++ b/ai/hermes/Dockerfile @@ -20,16 +20,10 @@ RUN --mount=type=ssh \ GIT_SSH_COMMAND='ssh -p 2222 -o StrictHostKeyChecking=no' \ git clone --depth 1 --branch main \ git@code.lazyworkhorse.net:gortium/hermes-agent.git fork && \ - rsync -a --delete fork/ /opt/hermes/ \ - --exclude node_modules \ - --exclude .venv \ - --exclude .git && \ + rm -rf fork/node_modules fork/.venv fork/.git && \ + cp -a fork/. /opt/hermes/ && \ rm -rf /tmp/fork /root/.ssh/ -# ---------- Rebuild web UI ---------- -# Source files changed; node_modules (from base image) reused. -RUN cd /opt/hermes && npm run build - # ---------- Reinstall Python package (editable) ---------- # Picks up source changes from our fork. RUN . /opt/hermes/.venv/bin/activate && \ @@ -75,10 +69,6 @@ os.remove(tgz) print('himalaya v1.2.0 installed') PYEOF -# ---------- Install himalaya-ro wrapper ---------- -COPY --chmod=0755 himalaya-ro.sh /usr/local/bin/himalaya-ro - - # ---------- Runtime ---------- USER hermes ENV HERMES_HOME=/opt/data diff --git a/ai/hermes/himalaya-ro.sh b/ai/hermes/himalaya-ro.sh deleted file mode 100644 index 212f1ae..0000000 --- a/ai/hermes/himalaya-ro.sh +++ /dev/null @@ -1,73 +0,0 @@ -#!/usr/bin/env bash -# ───────────────────────────────────────────────────────────── -# himalaya-ro — Read-only wrapper for himalaya -# -# Blocks destructive commands and logs audit trail. -# Pass-through for read-only commands (list, read, search). -# -# Usage: himalaya-ro [options] [args...] -# -# Install: place in PATH before the real himalaya, or use -# `ln -sf himalaya-ro /usr/local/bin/himalaya` -# ───────────────────────────────────────────────────────────── -set -o pipefail - -# ── Configuration ─────────────────────────────────────────── -HIMALAYA_BIN="${HIMALAYA_BIN:-/usr/local/bin/himalaya}" -AUDIT_LOG="${HIMALAYA_AUDIT_LOG:-/var/log/himalaya-audit.log}" - -# ── Destructive commands we block ────────────────────────── -BLOCKED_CMDS=( - "message move" - "message delete" - "message copy" - "flag add" - "flag remove" - "folder create" - "folder delete" - "folder rename" - "template send" - "account configure" - "account delete" -) - -# ── Determine the subcommand being invoked ───────────────── -# Strip leading options (--account, --output, etc.) to find the verb -ARGS=() -SKIP_NEXT=false -for arg in "$@"; do - if $SKIP_NEXT; then - SKIP_NEXT=false - continue - fi - if [[ "$arg" == --* ]]; then - case "$arg" in - --account|--output|--page|--page-size|--folder|--color|--format) - SKIP_NEXT=true ;; - esac - continue - fi - ARGS+=("$arg") -done - -# Build subcommand string and check against blocklist -CMD_STR="" -for ((i=0; i<${#ARGS[@]}; i++)); do - if [ -z "$CMD_STR" ]; then - CMD_STR="${ARGS[$i]}" - else - CMD_STR="$CMD_STR ${ARGS[$i]}" - fi - for blocked in "${BLOCKED_CMDS[@]}"; do - if [[ "$CMD_STR" == "$blocked" ]]; then - TS=$(date '+%Y-%m-%d %H:%M:%S') - echo "[AUDIT] $TS BLOCKED: himalaya $*" >> "$AUDIT_LOG" - echo "ERROR: Command 'himalaya $CMD_STR ...' is blocked by read-only policy." >&2 - echo " Audit log: $AUDIT_LOG" >&2 - exit 100 - fi - done -done - -# ── Allow pass-through ───────────────────────────────────── -exec "$HIMALAYA_BIN" "$@" From 5f25c87775aa11635e49162a036d2c10352c4c4e Mon Sep 17 00:00:00 2001 From: Hermes Date: Wed, 20 May 2026 13:32:18 -0400 Subject: [PATCH 05/20] fix: add missing USER root before chown step The chown -R hermes:hermes was running as non-root user 'hermes' since USER hermes was set earlier. The new upstream base image (v0.12.0+) has tools/ owned by root, so the chown fails. Previous base image happened to have tools/ owned by hermes, making the chown a silent no-op. --- ai/hermes/Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/ai/hermes/Dockerfile b/ai/hermes/Dockerfile index 1b775e7..c3a76fa 100644 --- a/ai/hermes/Dockerfile +++ b/ai/hermes/Dockerfile @@ -78,6 +78,7 @@ 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" ] \ No newline at end of file From ebad994d6096a720eadfe9b967b21c62644d4b16 Mon Sep 17 00:00:00 2001 From: Hermes Date: Wed, 20 May 2026 14:06:23 -0400 Subject: [PATCH 06/20] feat(hermes): enable dashboard (HERMES_DASHBOARD=1) + Authelia auth --- ai/compose.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/ai/compose.yml b/ai/compose.yml index 17d6170..8697395 100644 --- a/ai/compose.yml +++ b/ai/compose.yml @@ -39,6 +39,7 @@ services: command: gateway run environment: - OLLAMA_HOST=http://ollama:11434 + - HERMES_DASHBOARD=1 - API_SERVER_ENABLED=true - API_SERVER_PORT=8642 - API_SERVER_HOST=0.0.0.0 @@ -76,11 +77,17 @@ services: - "traefik.http.routers.hermes-web-http.entrypoints=web" - "traefik.http.routers.hermes-web-http.middlewares=redirect-to-https" - # Router for HTTPS with TLS + # Router for HTTPS with TLS — protected by Authelia - "traefik.http.routers.hermes-web-https.rule=Host(`hermes.lazyworkhorse.net`)" - "traefik.http.routers.hermes-web-https.entrypoints=websecure" - "traefik.http.routers.hermes-web-https.tls=true" - "traefik.http.routers.hermes-web-https.tls.certresolver=njalla" + - "traefik.http.routers.hermes-web-https.middlewares=hermes-auth" + + # Authelia forwardAuth + - "traefik.http.middlewares.hermes-auth.forwardauth.address=http://authelia:9091/api/verify?rd=https://auth.lazyworkhorse.net/" + - "traefik.http.middlewares.hermes-auth.forwardauth.trustforwardheader=true" + - "traefik.http.middlewares.hermes-auth.forwardauth.authresponseheaders=X-Forwarded-User,X-Forwarded-Groups" # Service Loadbalancer (dashboard port 9119) - "traefik.http.services.hermes-web.loadbalancer.server.port=9119" From 548e15d6b4a1d6aa53ddcdb14c015917000d0331 Mon Sep 17 00:00:00 2001 From: Hermes Date: Wed, 20 May 2026 20:02:26 -0400 Subject: [PATCH 07/20] feat(compose): add HERMES_PROFILES env var for multi-gateway mode --- ai/compose.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ai/compose.yml b/ai/compose.yml index 8697395..7d1c8c4 100644 --- a/ai/compose.yml +++ b/ai/compose.yml @@ -40,6 +40,10 @@ services: environment: - OLLAMA_HOST=http://ollama:11434 - HERMES_DASHBOARD=1 + # Multi-profile: comma-separated list of profiles to run as gateways. + # The entrypoint reads this and starts one gateway per profile. + # Add profiles here when they exist on disk (e.g. default,researcher,writer) + - HERMES_PROFILES=default - API_SERVER_ENABLED=true - API_SERVER_PORT=8642 - API_SERVER_HOST=0.0.0.0 From 7725830e6c180cf2e89c368334e891a27676e21e Mon Sep 17 00:00:00 2001 From: Hermes Date: Fri, 22 May 2026 12:27:47 -0400 Subject: [PATCH 08/20] feat: wire up HERMES_PROFILES to multi-gateway launcher script MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add run-multi-gateways.sh to /opt/data/hermes-tools/ that reads HERMES_PROFILES env var and spawns one gateway per profile - Update entrypoint to call the script before the main entrypoint - Set HERMES_PROFILES=ashley,claire,finn,matt,paul (was default) Closes PR #47 (feat/multi-profile-gateways). Builds on 548e15d's cleaner env-var-driven approach — compose.yml stays declarative, logic in script. --- ai/compose.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ai/compose.yml b/ai/compose.yml index 7d1c8c4..bc0cd4f 100644 --- a/ai/compose.yml +++ b/ai/compose.yml @@ -32,7 +32,7 @@ services: - default container_name: hermes entrypoint: ["/bin/bash", "-c", - "bash /opt/data/hermes-tools/install.sh && exec /usr/bin/tini -g -- /opt/hermes/docker/entrypoint.sh \"$@\"", + "bash /opt/data/hermes-tools/install.sh && bash /opt/data/hermes-tools/run-multi-gateways.sh && exec /usr/bin/tini -g -- /opt/hermes/docker/entrypoint.sh \"$@\"", "hermes-entrypoint"] restart: always # Gateway run enables the internal API server on port 8642 @@ -43,7 +43,7 @@ services: # Multi-profile: comma-separated list of profiles to run as gateways. # The entrypoint reads this and starts one gateway per profile. # Add profiles here when they exist on disk (e.g. default,researcher,writer) - - HERMES_PROFILES=default + - HERMES_PROFILES=ashley,claire,finn,matt,paul - API_SERVER_ENABLED=true - API_SERVER_PORT=8642 - API_SERVER_HOST=0.0.0.0 From 2d59bb44c37dcb37d6dd8de42f5cee6837660df3 Mon Sep 17 00:00:00 2001 From: Hermes Date: Fri, 22 May 2026 13:03:51 -0400 Subject: [PATCH 09/20] fix: remove venv volume mount conflicting with upstream entrypoint.sh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The volume mount /mnt/HoardingCow_docker_data/Hermes/venv overrides the container's built-in .venv with whatever is on the host. On a fresh start or after a clean build, an empty/missing venv directory causes entrypoint.sh line 62 (source .venv/bin/activate) to fail with set -e. The Docker image already builds a complete venv — persisting it on the host is unnecessary and fragile. --- ai/compose.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/ai/compose.yml b/ai/compose.yml index ca5f181..3126c2b 100755 --- a/ai/compose.yml +++ b/ai/compose.yml @@ -61,8 +61,6 @@ services: - /mnt/HoardingCow_docker_data/Syncthing/telos-ro:/opt/data/telos-ro:ro # Syncthing-shared inbox — write tasks here, they sync to user's laptop - /mnt/HoardingCow_docker_data/Syncthing/telos-rw:/opt/data/telos-rw:rw - # Persist Python venv across container recreation (Matrix bridge deps, etc.) - - /mnt/HoardingCow_docker_data/Hermes/venv:/opt/hermes/.venv devices: - /dev/kfd:/dev/kfd - /dev/dri:/dev/dri From e4117cd3d50a0d298798bc403761421ac6c204f9 Mon Sep 17 00:00:00 2001 From: Hermes Date: Fri, 22 May 2026 13:02:23 -0400 Subject: [PATCH 10/20] fix: remove venv volume mount conflicting with entrypoint.sh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The volume mount at /mnt/HoardingCow_docker_data/Hermes/venv overrides the container's built .venv with an empty or stale host directory, causing entrypoint.sh line 62 to fail on 'source .venv/bin/activate' (set -e). The Docker image already builds a complete venv — no need to persist it. --- ai/compose.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/ai/compose.yml b/ai/compose.yml index ca5f181..3126c2b 100755 --- a/ai/compose.yml +++ b/ai/compose.yml @@ -61,8 +61,6 @@ services: - /mnt/HoardingCow_docker_data/Syncthing/telos-ro:/opt/data/telos-ro:ro # Syncthing-shared inbox — write tasks here, they sync to user's laptop - /mnt/HoardingCow_docker_data/Syncthing/telos-rw:/opt/data/telos-rw:rw - # Persist Python venv across container recreation (Matrix bridge deps, etc.) - - /mnt/HoardingCow_docker_data/Hermes/venv:/opt/hermes/.venv devices: - /dev/kfd:/dev/kfd - /dev/dri:/dev/dri From bce336c4fd1bd4f6c12d38df186e6515e2c88467 Mon Sep 17 00:00:00 2001 From: Hermes Date: Fri, 22 May 2026 13:04:43 -0400 Subject: [PATCH 11/20] feat: bake Matrix bridge deps into Docker image instead of volume mount - Add libolm-dev system dep (required by mautrix[encryption]) - Add mautrix[encryption] + openai pip packages to build - These were previously installed inline at container startup and persisted via the fragile venv volume mount (now removed) --- ai/hermes/Dockerfile | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ai/hermes/Dockerfile b/ai/hermes/Dockerfile index c3a76fa..253b9b7 100644 --- a/ai/hermes/Dockerfile +++ b/ai/hermes/Dockerfile @@ -34,6 +34,7 @@ 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 && \ @@ -42,6 +43,12 @@ RUN apt-get update && \ # ---------- UV ---------- COPY --chmod=0755 --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/ +# ---------- 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 && \ From 567850bd13c7f5a54f007fd914feda4b6bcb7a2f Mon Sep 17 00:00:00 2001 From: Hermes Date: Fri, 22 May 2026 13:04:43 -0400 Subject: [PATCH 12/20] feat: bake Matrix bridge deps into Docker image instead of volume mount - Add libolm-dev system dep (required by mautrix[encryption]) - Add mautrix[encryption] + openai pip packages to build - These were previously installed inline at container startup and persisted via the fragile venv volume mount (now removed) --- ai/hermes/Dockerfile | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ai/hermes/Dockerfile b/ai/hermes/Dockerfile index c3a76fa..253b9b7 100644 --- a/ai/hermes/Dockerfile +++ b/ai/hermes/Dockerfile @@ -34,6 +34,7 @@ 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 && \ @@ -42,6 +43,12 @@ RUN apt-get update && \ # ---------- UV ---------- COPY --chmod=0755 --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/ +# ---------- 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 && \ From 45a224eb7fad685c7e9abee9b1a95d3514136e38 Mon Sep 17 00:00:00 2001 From: Hermes Date: Fri, 22 May 2026 13:22:58 -0400 Subject: [PATCH 13/20] fix: add missing command: gateway run to hermes service Without this, is empty and entrypoint.sh runs bare 'hermes' which defaults to interactive chat mode. With a non-TTY stdin this exits immediately with prompt_toolkit's 'Input is not a terminal' warning, causing a container restart loop. The profile gateways (run-multi-gateways.sh) were unaffected because the script passes 'gateway run' explicitly. --- ai/compose.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ai/compose.yml b/ai/compose.yml index 3126c2b..bc0cd4f 100755 --- a/ai/compose.yml +++ b/ai/compose.yml @@ -35,6 +35,8 @@ services: "bash /opt/data/hermes-tools/install.sh && bash /opt/data/hermes-tools/run-multi-gateways.sh && exec /usr/bin/tini -g -- /opt/hermes/docker/entrypoint.sh \"$@\"", "hermes-entrypoint"] restart: always + # Gateway run enables the internal API server on port 8642 + command: gateway run environment: - OLLAMA_HOST=http://ollama:11434 - HERMES_DASHBOARD=1 From d8a1ebcd96a204978bbfb027e4ea2cd867ed8763 Mon Sep 17 00:00:00 2001 From: Hermes Date: Fri, 22 May 2026 13:22:58 -0400 Subject: [PATCH 14/20] fix: add missing command: gateway run to hermes service Without this, is empty and entrypoint.sh runs bare 'hermes' which defaults to interactive chat mode. With a non-TTY stdin this exits immediately with prompt_toolkit's 'Input is not a terminal' warning, causing a container restart loop. The profile gateways (run-multi-gateways.sh) were unaffected because the script passes 'gateway run' explicitly. --- ai/compose.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ai/compose.yml b/ai/compose.yml index 3126c2b..bc0cd4f 100755 --- a/ai/compose.yml +++ b/ai/compose.yml @@ -35,6 +35,8 @@ services: "bash /opt/data/hermes-tools/install.sh && bash /opt/data/hermes-tools/run-multi-gateways.sh && exec /usr/bin/tini -g -- /opt/hermes/docker/entrypoint.sh \"$@\"", "hermes-entrypoint"] restart: always + # Gateway run enables the internal API server on port 8642 + command: gateway run environment: - OLLAMA_HOST=http://ollama:11434 - HERMES_DASHBOARD=1 From 146add2a64e077db5be1f735a9e332fbc6045c30 Mon Sep 17 00:00:00 2001 From: Hermes Date: Fri, 22 May 2026 13:34:12 -0400 Subject: [PATCH 15/20] fix: use full hermes path and gosu in multi-gateway launcher MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Use /opt/hermes/.venv/bin/hermes (full path) — not on PATH before entrypoint.sh sources the venv - Wrap with gosu hermes to avoid root guard in gateway run - Add error check if hermes binary doesn't exist --- run-multi-gateways.sh | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100755 run-multi-gateways.sh diff --git a/run-multi-gateways.sh b/run-multi-gateways.sh new file mode 100755 index 0000000..cedf365 --- /dev/null +++ b/run-multi-gateways.sh @@ -0,0 +1,33 @@ +#!/bin/bash +# Multi-gateway launcher for HERMES_PROFILES env var. +# Reads comma-separated profile names, spawns one gateway per profile. +# Designed to run before the main entrypoint — gateways run in background. +set -e + +if [ -z "${HERMES_PROFILES}" ]; then + echo "HERMES_PROFILES not set — skipping multi-gateway launch" + exit 0 +fi + +# Source venv to make 'hermes' available (entrypoint.sh sources it later, +# but we need it NOW for the background gateways) +HERMES_BIN="/opt/hermes/.venv/bin/hermes" +if [ ! -x "$HERMES_BIN" ]; then + echo "ERROR: hermes binary not found at $HERMES_BIN" + exit 1 +fi + +mkdir -p /opt/data/logs + +IFS=',' read -ra PROFILES <<< "${HERMES_PROFILES}" +for profile in "${PROFILES[@]}"; do + profile="$(echo "${profile}" | xargs)" # trim whitespace + [ -z "${profile}" ] && continue + + echo "Starting gateway for profile: ${profile}" + API_SERVER_ENABLED=false \ + nohup gosu hermes "$HERMES_BIN" --profile "${profile}" gateway run \ + >> "/opt/data/logs/gateway-${profile}.log" 2>&1 & +done + +echo "All gateways launched: ${HERMES_PROFILES}" From 645d519030a9a78ddac75ee6e224d1948b67dec6 Mon Sep 17 00:00:00 2001 From: Hermes Date: Fri, 22 May 2026 13:52:05 -0400 Subject: [PATCH 16/20] fix: use env to force API_SERVER_ENABLED=false in multi-gateway launcher Shell prefix didn't work with nohup+gosu chain - Docker compose env var API_SERVER_ENABLED=true leaked through. Using 'env' command guarantees the override is in the child process env. --- run-multi-gateways.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/run-multi-gateways.sh b/run-multi-gateways.sh index cedf365..26db250 100755 --- a/run-multi-gateways.sh +++ b/run-multi-gateways.sh @@ -25,8 +25,7 @@ for profile in "${PROFILES[@]}"; do [ -z "${profile}" ] && continue echo "Starting gateway for profile: ${profile}" - API_SERVER_ENABLED=false \ - nohup gosu hermes "$HERMES_BIN" --profile "${profile}" gateway run \ + nohup env API_SERVER_ENABLED=false gosu hermes "$HERMES_BIN" --profile "${profile}" gateway run \ >> "/opt/data/logs/gateway-${profile}.log" 2>&1 & done From e8075fb71bf317878ef52d53b3e825e2b4e3bae9 Mon Sep 17 00:00:00 2001 From: Hermes Date: Fri, 22 May 2026 13:53:53 -0400 Subject: [PATCH 17/20] fix: also clear API_SERVER_KEY for profile gateways Line 1521 in gateway/config.py: if api_server_enabled or api_server_key: The compose.yml sets API_SERVER_KEY=hermes_local_key, which was enough to enable the API server even with API_SERVER_ENABLED=false. --- run-multi-gateways.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run-multi-gateways.sh b/run-multi-gateways.sh index 26db250..f23ac78 100755 --- a/run-multi-gateways.sh +++ b/run-multi-gateways.sh @@ -25,7 +25,7 @@ for profile in "${PROFILES[@]}"; do [ -z "${profile}" ] && continue echo "Starting gateway for profile: ${profile}" - nohup env API_SERVER_ENABLED=false gosu hermes "$HERMES_BIN" --profile "${profile}" gateway run \ + nohup env API_SERVER_ENABLED=false API_SERVER_KEY= gosu hermes "$HERMES_BIN" --profile "${profile}" gateway run \ >> "/opt/data/logs/gateway-${profile}.log" 2>&1 & done From c1cd9d31e9fcd753f6d0c0f07b114f33eb212ebe Mon Sep 17 00:00:00 2001 From: Hermes Date: Fri, 22 May 2026 21:37:01 -0400 Subject: [PATCH 18/20] fix: move run-multi-gateways.sh into ai/hermes/ and bake into image --- ai/compose.yml | 2 +- ai/hermes/Dockerfile | 4 ++++ run-multi-gateways.sh => ai/hermes/run-multi-gateways.sh | 0 3 files changed, 5 insertions(+), 1 deletion(-) rename run-multi-gateways.sh => ai/hermes/run-multi-gateways.sh (100%) diff --git a/ai/compose.yml b/ai/compose.yml index bc0cd4f..89dceca 100755 --- a/ai/compose.yml +++ b/ai/compose.yml @@ -32,7 +32,7 @@ services: - default container_name: hermes entrypoint: ["/bin/bash", "-c", - "bash /opt/data/hermes-tools/install.sh && bash /opt/data/hermes-tools/run-multi-gateways.sh && exec /usr/bin/tini -g -- /opt/hermes/docker/entrypoint.sh \"$@\"", + "bash /opt/data/hermes-tools/install.sh && bash /usr/local/bin/run-multi-gateways.sh && exec /usr/bin/tini -g -- /opt/hermes/docker/entrypoint.sh \"$@\"", "hermes-entrypoint"] restart: always # Gateway run enables the internal API server on port 8642 diff --git a/ai/hermes/Dockerfile b/ai/hermes/Dockerfile index 253b9b7..dd044f9 100644 --- a/ai/hermes/Dockerfile +++ b/ai/hermes/Dockerfile @@ -76,6 +76,10 @@ 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 diff --git a/run-multi-gateways.sh b/ai/hermes/run-multi-gateways.sh similarity index 100% rename from run-multi-gateways.sh rename to ai/hermes/run-multi-gateways.sh From 317a5b23af348c21042d754a292a0f9ff2be8d10 Mon Sep 17 00:00:00 2001 From: Hermes Date: Sun, 24 May 2026 19:47:08 -0400 Subject: [PATCH 19/20] feat: drop fork, use official image + plugin URL build arg --- ai/compose.yml | 4 ++-- ai/hermes/Dockerfile | 45 +++++++++++++++++++------------------------- 2 files changed, 21 insertions(+), 28 deletions(-) diff --git a/ai/compose.yml b/ai/compose.yml index 89dceca..b5910a7 100755 --- a/ai/compose.yml +++ b/ai/compose.yml @@ -28,8 +28,8 @@ services: hermes: build: context: ./hermes - ssh: - - default + args: + HERMES_PLUGIN_URLS: "" container_name: hermes entrypoint: ["/bin/bash", "-c", "bash /opt/data/hermes-tools/install.sh && bash /usr/local/bin/run-multi-gateways.sh && exec /usr/bin/tini -g -- /opt/hermes/docker/entrypoint.sh \"$@\"", diff --git a/ai/hermes/Dockerfile b/ai/hermes/Dockerfile index dd044f9..eac4d47 100644 --- a/ai/hermes/Dockerfile +++ b/ai/hermes/Dockerfile @@ -1,33 +1,15 @@ # 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: +# Hermes Agent -- official image + custom plugins layered on top. +# No fork needed — customizations are pip-installable plugins from Gitea. # docker compose build hermes # Or manually: -# DOCKER_BUILDKIT=1 docker build --ssh default -t hermes-agent:custom . +# DOCKER_BUILDKIT=1 docker build --build-arg HERMES_PLUGIN_URLS="url1 url2" -t hermes-agent:custom . # ---------- Base: official Hermes image (system deps, npm, uv, Playwright) ---------- FROM nousresearch/hermes-agent:latest -# ---------- Overlay our forked source ---------- -# Uses SSH agent forwarding from the build host (no key baked into image). -# --exclude node_modules/.venv keeps the base image's pre-built layers intact. -# Only the Python source, web UI source, and config change. -RUN --mount=type=ssh \ - mkdir -p /root/.ssh && \ - ssh-keyscan -p 2222 code.lazyworkhorse.net >> /root/.ssh/known_hosts 2>/dev/null && \ - cd /tmp && \ - GIT_SSH_COMMAND='ssh -p 2222 -o StrictHostKeyChecking=no' \ - git clone --depth 1 --branch main \ - git@code.lazyworkhorse.net:gortium/hermes-agent.git fork && \ - rm -rf fork/node_modules fork/.venv fork/.git && \ - cp -a fork/. /opt/hermes/ && \ - rm -rf /tmp/fork /root/.ssh/ - -# ---------- Reinstall Python package (editable) ---------- -# Picks up source changes from our fork. -RUN . /opt/hermes/.venv/bin/activate && \ - uv pip install --no-cache-dir --no-deps -e /opt/hermes +# ---------- Plugin URLs (semicolon-separated, set via compose.yml build args) ---------- +ARG HERMES_PLUGIN_URLS="" # ---------- Extra system deps ---------- USER root @@ -44,8 +26,6 @@ RUN apt-get update && \ COPY --chmod=0755 --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/ # ---------- 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 @@ -76,6 +56,19 @@ os.remove(tgz) print('himalaya v1.2.0 installed') PYEOF +# ---------- Install custom plugins from URLs ---------- +# HERMES_PLUGIN_URLS is a semicolon-separated list of pip-installable +# package URLs (e.g. git+https:// or direct .tar.gz archives from Gitea). +# Each plugin is installed into the Hermes venv. +RUN if [ -n "$HERMES_PLUGIN_URLS" ]; then \ + . /opt/hermes/.venv/bin/activate && \ + IFS=';' read -ra URLS <<< "$HERMES_PLUGIN_URLS" && \ + for url in "${URLS[@]}"; do \ + echo "Installing plugin: $url" && \ + uv pip install --no-cache-dir "$url"; \ + done; \ + fi + # ---------- 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 @@ -92,4 +85,4 @@ ENV CHROME_EXECUTABLE=/opt/hermes/.playwright/chromium/chrome-linux/chrome USER root RUN chown -R hermes:hermes /opt/hermes/tools /opt/hermes/toolsets.py -VOLUME [ "/opt/data" ] \ No newline at end of file +VOLUME [ "/opt/data" ] From d1ba93fd389eae7d9a64c4b97e31149af80a6a92 Mon Sep 17 00:00:00 2001 From: Hermes Date: Sun, 24 May 2026 19:58:42 -0400 Subject: [PATCH 20/20] feat: add plugin URLs pointing to gortium account --- ai/compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ai/compose.yml b/ai/compose.yml index b5910a7..5c87263 100755 --- a/ai/compose.yml +++ b/ai/compose.yml @@ -29,7 +29,7 @@ services: build: context: ./hermes args: - HERMES_PLUGIN_URLS: "" + HERMES_PLUGIN_URLS: "git+https://code.lazyworkhorse.net/gortium/hermes-piper-plugin.git;git+https://code.lazyworkhorse.net/gortium/hermes-identity-plugin.git" container_name: hermes entrypoint: ["/bin/bash", "-c", "bash /opt/data/hermes-tools/install.sh && bash /usr/local/bin/run-multi-gateways.sh && exec /usr/bin/tini -g -- /opt/hermes/docker/entrypoint.sh \"$@\"",