Spaces:
Paused
Paused
| # Docker entrypoint: bootstrap config files into the mounted volume, then run hermes. | |
| set -e | |
| INSTALL_DIR="/opt/hermes" | |
| RUNTIME_HELPER="$INSTALL_DIR/docker/space_runtime.py" | |
| export HERMES_HOME="$(python3 "$RUNTIME_HELPER" home)" | |
| # --- Privilege dropping via gosu --- | |
| # When started as root (the default), optionally remap the hermes user/group | |
| # to match host-side ownership, fix volume permissions, then re-exec as hermes. | |
| if [ "$(id -u)" = "0" ]; then | |
| # HF Spaces run inside containers, but they don't always expose /.dockerenv. | |
| # hermes-web-ui uses that marker to choose detached "gateway run" mode | |
| # instead of system-service "gateway start", which times out in Spaces. | |
| if [ -n "${SPACE_ID:-}${SPACE_HOST:-}" ] && [ ! -e "/.dockerenv" ]; then | |
| touch "/.dockerenv" 2>/dev/null || true | |
| fi | |
| 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" | |
| groupmod -g "$HERMES_GID" hermes | |
| fi | |
| mkdir -p "$HERMES_HOME" | |
| actual_hermes_uid=$(id -u hermes) | |
| if [ "$(stat -c %u "$HERMES_HOME" 2>/dev/null)" != "$actual_hermes_uid" ]; then | |
| echo "$HERMES_HOME is not owned by $actual_hermes_uid, fixing" | |
| chown -R hermes:hermes "$HERMES_HOME" | |
| fi | |
| echo "Dropping root privileges" | |
| exec gosu hermes env HERMES_HOME="$HERMES_HOME" "$0" "$@" | |
| fi | |
| # --- Running as hermes from here --- | |
| export HERMES_HOME="$(python3 "$RUNTIME_HELPER" home)" | |
| 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} | |
| # hermes-web-ui currently resolves runtime files via ~/.hermes. | |
| # Mirror the active Hermes home into the hermes user's legacy home path | |
| # so the external UI sees the same config/.env/session files. | |
| LEGACY_HERMES_HOME="/opt/data/.hermes" | |
| if [ ! -e "$LEGACY_HERMES_HOME" ]; then | |
| ln -s "$HERMES_HOME" "$LEGACY_HERMES_HOME" | |
| fi | |
| # .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 | |
| # Optional Docker-local Ollama bootstrap. | |
| # | |
| # This keeps Hermes on its standard custom OpenAI-compatible provider path, | |
| # while using the host-reachable Docker URL instead of localhost inside the | |
| # container. Set HERMES_DOCKER_USE_LOCAL_OLLAMA=false to leave config untouched. | |
| if [ "${HERMES_DOCKER_USE_LOCAL_OLLAMA:-false}" = "true" ]; then | |
| python3 - "$HERMES_HOME/config.yaml" <<'PY' | |
| import os | |
| import sys | |
| from pathlib import Path | |
| import yaml | |
| config_path = Path(sys.argv[1]) | |
| base_url = os.getenv("HERMES_DOCKER_OLLAMA_BASE_URL", "http://host.docker.internal:11434/v1").strip() | |
| model_name = os.getenv("HERMES_DOCKER_OLLAMA_MODEL", "").strip() | |
| overwrite = os.getenv("HERMES_DOCKER_OLLAMA_OVERWRITE_CONFIG", "true").strip().lower() not in { | |
| "0", "false", "no", "off" | |
| } | |
| try: | |
| config = yaml.safe_load(config_path.read_text(encoding="utf-8")) or {} | |
| except Exception: | |
| config = {} | |
| model_cfg = config.get("model") | |
| if isinstance(model_cfg, str): | |
| model_cfg = {"default": model_cfg} | |
| elif not isinstance(model_cfg, dict): | |
| model_cfg = {} | |
| if overwrite or not model_cfg.get("base_url"): | |
| model_cfg["provider"] = "custom" | |
| model_cfg["base_url"] = base_url | |
| if model_name: | |
| model_cfg["default"] = model_name | |
| config["model"] = model_cfg | |
| config_path.write_text(yaml.safe_dump(config, sort_keys=False), encoding="utf-8") | |
| PY | |
| 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 | |
| if [ $# -eq 0 ] && { [ -n "${SPACE_ID:-}${SPACE_HOST:-}" ] || [ "${HERMES_WEB_UI_ENABLED:-false}" = "true" ]; }; then | |
| SHELL_EXEC="/bin/bash" | |
| if [ ! -x "$SHELL_EXEC" ]; then | |
| SHELL_EXEC="/bin/sh" | |
| fi | |
| UI_NODE="$(command -v node || command -v nodejs || true)" | |
| if [ -z "$UI_NODE" ]; then | |
| echo "Node.js runtime not found; hermes-web-ui cannot start" | |
| exit 1 | |
| fi | |
| HERMES_WEB_UI_ROOT="$(npm root -g)/hermes-web-ui" | |
| HERMES_WEB_UI_ENTRY="$HERMES_WEB_UI_ROOT/dist/server/index.js" | |
| if [ ! -f "$HERMES_WEB_UI_ENTRY" ]; then | |
| echo "hermes-web-ui entrypoint missing at $HERMES_WEB_UI_ENTRY" | |
| exit 1 | |
| fi | |
| HERMES_BIN="$INSTALL_DIR/.venv/bin/hermes" | |
| export HERMES_BIN | |
| export API_SERVER_ENABLED="${API_SERVER_ENABLED:-true}" | |
| export PORT="${PORT:-7860}" | |
| # Let hermes-web-ui's GatewayManager own port selection via config.yaml. | |
| # Exporting a default API_SERVER_PORT here forces child gateway processes | |
| # back onto 8642, which breaks health checks when the UI assigns 8643+. | |
| export UPSTREAM="${UPSTREAM:-$(python3 "$RUNTIME_HELPER" api-server-upstream)}" | |
| export AUTH_DISABLED="${AUTH_DISABLED:-true}" | |
| export AUTH_TOKEN="${AUTH_TOKEN:-P@ssw0rd}" | |
| WEB_UI_CMD="$UI_NODE \"$HERMES_WEB_UI_ENTRY\"" | |
| exec $SHELL_EXEC -lc "$WEB_UI_CMD" | |
| fi | |
| exec python3 "$RUNTIME_HELPER" exec "$@" | |