Compare commits
3 Commits
feat/docke
...
feat/herme
| Author | SHA1 | Date | |
|---|---|---|---|
| bb64234223 | |||
| 9fbfc1dc51 | |||
| 7e3afe6630 |
Submodule assets/compose updated: fb0f2cbe84...ba8634b770
21
docker/hermes/Dockerfile
Normal file
21
docker/hermes/Dockerfile
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# Custom Hermes Agent image with voice support and ROCm for AMD GPU
|
||||||
|
# Builds on upstream image and adds local STT capabilities
|
||||||
|
FROM nousresearch/hermes-agent:latest AS base
|
||||||
|
|
||||||
|
# Install system dependencies for audio capture
|
||||||
|
USER root
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install -y --no-install-recommends \
|
||||||
|
libportaudio2 \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Install Python packages for voice support
|
||||||
|
# Using uv for consistency with Hermes build process
|
||||||
|
RUN uv pip install --no-cache-dir \
|
||||||
|
sounddevice \
|
||||||
|
numpy \
|
||||||
|
faster-whisper
|
||||||
|
|
||||||
|
# Health check for voice readiness
|
||||||
|
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
|
||||||
|
CMD python3 -c "import sounddevice, faster_whisper; print('voice deps OK')" || exit 1
|
||||||
102
docker/hermes/entrypoint.sh
Executable file
102
docker/hermes/entrypoint.sh
Executable file
@@ -0,0 +1,102 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Docker/Podman entrypoint: bootstrap config files into the mounted volume, then run hermes.
|
||||||
|
set -e
|
||||||
|
|
||||||
|
HERMES_HOME="${HERMES_HOME:-/opt/data}"
|
||||||
|
INSTALL_DIR="/opt/hermes"
|
||||||
|
|
||||||
|
# --- Privilege dropping via gosu ---
|
||||||
|
# When started as root (the default for Docker, or fakeroot in rootless Podman),
|
||||||
|
# optionally remap the hermes user/group to match host-side ownership, fix volume
|
||||||
|
# permissions, then re-exec as hermes.
|
||||||
|
if [ "$(id -u)" = "0" ]; then
|
||||||
|
if [ -n "$HERMES_UID" ] && [ "$HERMES_UID" != "$(id -u hermes)" ]; then
|
||||||
|
echo "Changing hermes UID to $HERMES_UID"
|
||||||
|
usermod -u "$HERMES_UID" hermes
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$HERMES_GID" ] && [ "$HERMES_GID" != "$(id -g hermes)" ]; then
|
||||||
|
echo "Changing hermes GID to $HERMES_GID"
|
||||||
|
# -o allows non-unique GID (e.g. macOS GID 20 "staff" may already exist
|
||||||
|
# as "dialout" in the Debian-based container image)
|
||||||
|
groupmod -o -g "$HERMES_GID" hermes 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Fix ownership of the data volume. When HERMES_UID remaps the hermes user,
|
||||||
|
# files created by previous runs (under the old UID) become inaccessible.
|
||||||
|
# Always chown -R when UID was remapped; otherwise only if top-level is wrong.
|
||||||
|
actual_hermes_uid=$(id -u hermes)
|
||||||
|
needs_chown=false
|
||||||
|
if [ -n "$HERMES_UID" ] && [ "$HERMES_UID" != "10000" ]; then
|
||||||
|
needs_chown=true
|
||||||
|
elif [ "$(stat -c %u "$HERMES_HOME" 2>/dev/null)" != "$actual_hermes_uid" ]; then
|
||||||
|
needs_chown=true
|
||||||
|
fi
|
||||||
|
if [ "$needs_chown" = true ]; then
|
||||||
|
echo "Fixing ownership of $HERMES_HOME to hermes ($actual_hermes_uid)"
|
||||||
|
# In rootless Podman the container's "root" is mapped to an unprivileged
|
||||||
|
# host UID — chown will fail. That's fine: the volume is already owned
|
||||||
|
# by the mapped user on the host side.
|
||||||
|
chown -R hermes:hermes "$HERMES_HOME" 2>/dev/null || \
|
||||||
|
echo "Warning: chown failed (rootless container?) — continuing anyway"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Dropping root privileges"
|
||||||
|
exec gosu hermes "$0" "$@"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Running as hermes from here ---
|
||||||
|
source "${INSTALL_DIR}/.venv/bin/activate"
|
||||||
|
|
||||||
|
# Create essential directory structure. Cache and platform directories
|
||||||
|
# (cache/images, cache/audio, platforms/whatsapp, etc.) are created on
|
||||||
|
# demand by the application — don't pre-create them here so new installs
|
||||||
|
# get the consolidated layout from get_hermes_dir().
|
||||||
|
# The "home/" subdirectory is a per-profile HOME for subprocesses (git,
|
||||||
|
# ssh, gh, npm …). Without it those tools write to /root which is
|
||||||
|
# ephemeral and shared across profiles. See issue #4426.
|
||||||
|
mkdir -p "$HERMES_HOME"/{cron,sessions,logs,hooks,memories,skills,skins,plans,workspace,home}
|
||||||
|
|
||||||
|
# .env
|
||||||
|
if [ ! -f "$HERMES_HOME/.env" ]; then
|
||||||
|
cp "$INSTALL_DIR/.env.example" "$HERMES_HOME/.env"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# config.yaml
|
||||||
|
if [ ! -f "$HERMES_HOME/config.yaml" ]; then
|
||||||
|
cp "$INSTALL_DIR/cli-config.yaml.example" "$HERMES_HOME/config.yaml"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Ensure the main config file remains accessible to the hermes runtime user
|
||||||
|
# even if it was edited on the host after initial ownership setup.
|
||||||
|
if [ -f "$HERMES_HOME/config.yaml" ]; then
|
||||||
|
chown hermes:hermes "$HERMES_HOME/config.yaml"
|
||||||
|
chmod 640 "$HERMES_HOME/config.yaml"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# SOUL.md
|
||||||
|
if [ ! -f "$HERMES_HOME/SOUL.md" ]; then
|
||||||
|
cp "$INSTALL_DIR/docker/SOUL.md" "$HERMES_HOME/SOUL.md"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Sync bundled skills (manifest-based so user edits are preserved)
|
||||||
|
if [ -d "$INSTALL_DIR/skills" ]; then
|
||||||
|
python3 "$INSTALL_DIR/tools/skills_sync.py"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Final exec: two supported invocation patterns.
|
||||||
|
#
|
||||||
|
# docker run <image> -> exec `hermes` with no args (legacy default)
|
||||||
|
# docker run <image> chat -q "..." -> exec `hermes chat -q "..."` (legacy wrap)
|
||||||
|
# docker run <image> sleep infinity -> exec `sleep infinity` directly
|
||||||
|
# docker run <image> bash -> exec `bash` directly
|
||||||
|
#
|
||||||
|
# If the first positional arg resolves to an executable on PATH, we assume the
|
||||||
|
# caller wants to run it directly (needed by the launcher which runs long-lived
|
||||||
|
# `sleep infinity` sandbox containers — see tools/environments/docker.py).
|
||||||
|
# Otherwise we treat the args as a hermes subcommand and wrap with `hermes`,
|
||||||
|
# preserving the documented `docker run <image> <subcommand>` behavior.
|
||||||
|
if [ $# -gt 0 ] && command -v "$1" >/dev/null 2>&1; then
|
||||||
|
exec "$@"
|
||||||
|
fi
|
||||||
|
exec hermes "$@"
|
||||||
Reference in New Issue
Block a user