From 03616e9e3d545e30d49e8c9d5304efcd6cc43004 Mon Sep 17 00:00:00 2001 From: Hermes Date: Tue, 12 May 2026 18:02:51 -0400 Subject: [PATCH] feat: add Himalaya email CLI to Hermes Docker image --- ai/hermes/Dockerfile | 92 ++++++++++++++++++++++------------------ ai/hermes/himalaya-ro.sh | 73 +++++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+), 42 deletions(-) create mode 100644 ai/hermes/himalaya-ro.sh diff --git a/ai/hermes/Dockerfile b/ai/hermes/Dockerfile index 042e1db..eab8258 100644 --- a/ai/hermes/Dockerfile +++ b/ai/hermes/Dockerfile @@ -1,52 +1,34 @@ -# 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 . +# 1. On récupère la version la plus récente d'UV +FROM ghcr.io/astral-sh/uv:latest AS uv_source -# ---------- Base: official Hermes image (system deps, npm, uv, Playwright) ---------- +# 2. Image officielle Hermes Agent de NousResearch +# Contient déjà: Python, Node.js, npm, Playwright/Chromium, venv, tts_tool.py, etc. 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 && \ - rsync -a --delete fork/ /opt/hermes/ \ - --exclude node_modules \ - --exclude .venv \ - --exclude .git && \ - 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 && \ - uv pip install --no-cache-dir --no-deps -e /opt/hermes - -# ---------- Extra system deps ---------- +# ---------- System dependencies ---------- +# The official hermes-agent image already has: git, curl, ffmpeg, python3, +# gcc, build-essential, openssh-client, procps, tini, ripgrep, docker-cli, +# libportaudio2, ca-certificates, etc. USER root RUN apt-get update && \ apt-get install -y --no-install-recommends \ - libportaudio2 ca-certificates poppler-utils imagemagick \ - texlive-latex-base texlive-latex-extra texlive-fonts-recommended \ - texlive-xetex texlive-science \ - qemu-user-static binfmt-support emacs-nox && \ + poppler-utils \ + imagemagick \ + texlive-latex-base \ + texlive-latex-extra \ + texlive-fonts-recommended \ + texlive-xetex \ + texlive-science && \ rm -rf /var/lib/apt/lists/* -# ---------- UV ---------- -COPY --chmod=0755 --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/ +# ---------- UV (hyperfast pip alternative) ---------- +COPY --chmod=0755 --from=uv_source /uv /usr/local/bin/ + +WORKDIR /opt/hermes + +# ---------- Extra Python deps ---------- +RUN . /opt/hermes/.venv/bin/activate && \ + uv pip install --no-cache-dir httpx # ---------- Piper TTS ---------- RUN . /opt/hermes/.venv/bin/activate && \ @@ -59,8 +41,32 @@ 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') +print('Piper voice downloaded') 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 himalaya-ro wrapper ---------- +COPY --chmod=0755 himalaya-ro.sh /usr/local/bin/himalaya-ro + +# ---------- Patch tts_tool.py: remplacer Edge TTS par Piper ---------- +# Edge TTS appelle les serveurs Microsoft — on ne veut jamais ça. +# Piper roule localement sur CPU, aucun cloud, aucune donnée qui sort. +COPY patch_tts_tool.py /tmp/patch_tts_tool.py +RUN /opt/hermes/.venv/bin/python3 /tmp/patch_tts_tool.py && rm /tmp/patch_tts_tool.py + # ---------- Runtime ---------- USER hermes ENV HERMES_HOME=/opt/data @@ -70,5 +76,7 @@ ENV CHROME_EXECUTABLE=/opt/hermes/.playwright/chromium/chrome-linux/chrome VOLUME [ "/opt/data" ] +# Startup permission fix + config generation + TTS patch COPY --chmod=0755 fix-permissions.sh /opt/hermes/fix-permissions.sh -ENTRYPOINT [ "/usr/bin/tini", "-g", "--", "/opt/hermes/fix-permissions.sh" ] \ No newline at end of file + +ENTRYPOINT [ "/usr/bin/tini", "-g", "--", "/opt/hermes/fix-permissions.sh" ] diff --git a/ai/hermes/himalaya-ro.sh b/ai/hermes/himalaya-ro.sh new file mode 100644 index 0000000..212f1ae --- /dev/null +++ b/ai/hermes/himalaya-ro.sh @@ -0,0 +1,73 @@ +#!/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" "$@"