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 "$@"