Spaces:
Paused
Paused
File size: 5,989 Bytes
c64420c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 | #!/bin/bash
# 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 "$@"
|