diff --git a/ai/compose.yml b/ai/compose.yml index 60b1d2e..c36f2d0 100755 --- a/ai/compose.yml +++ b/ai/compose.yml @@ -32,13 +32,18 @@ services: - default container_name: hermes entrypoint: ["/bin/bash", "-c", - "bash /opt/data/hermes-tools/install.sh && . /opt/hermes/.venv/bin/activate && uv pip install openai 'mautrix[encryption]' -q && 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 command: gateway run 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=ashley,claire,finn,matt,paul - API_SERVER_ENABLED=true - API_SERVER_PORT=8642 - API_SERVER_HOST=0.0.0.0 @@ -68,8 +73,32 @@ services: - "26" networks: - ai_backend + - ai_net depends_on: - honcho + 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 — 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" syncthing: image: syncthing/syncthing:latest diff --git a/ai/hermes/Dockerfile b/ai/hermes/Dockerfile index fe09d62..adabcdc 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 && \ @@ -77,10 +71,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 @@ -90,6 +80,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 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" "$@"