mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-08 03:01:47 +00:00
docs: add Open WebUI bootstrap script
This commit is contained in:
parent
92a08c633f
commit
1c42d8ff53
2 changed files with 392 additions and 1 deletions
349
scripts/setup_open_webui.sh
Executable file
349
scripts/setup_open_webui.sh
Executable file
|
|
@ -0,0 +1,349 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Bootstrap Open WebUI against Hermes Agent's OpenAI-compatible API server.
|
||||
#
|
||||
# Idempotent by design:
|
||||
# - ensures ~/.hermes/.env has API server settings
|
||||
# - installs Open WebUI into ~/.local/open-webui-venv
|
||||
# - writes a reusable launcher at ~/.local/bin/start-open-webui-hermes.sh
|
||||
# - optionally installs a user service (launchd on macOS, systemd --user on Linux)
|
||||
#
|
||||
# Usage:
|
||||
# bash scripts/setup_open_webui.sh
|
||||
#
|
||||
# Optional environment overrides:
|
||||
# OPEN_WEBUI_PORT=8080
|
||||
# OPEN_WEBUI_HOST=127.0.0.1
|
||||
# OPEN_WEBUI_NAME='Johnny Hermes'
|
||||
# OPEN_WEBUI_ENABLE_SIGNUP=true
|
||||
# OPEN_WEBUI_ENABLE_SERVICE=auto # auto|true|false
|
||||
# OPEN_WEBUI_VENV=~/.local/open-webui-venv
|
||||
# OPEN_WEBUI_DATA_DIR=~/.local/share/open-webui/data
|
||||
# HERMES_API_PORT=8642
|
||||
# HERMES_API_HOST=127.0.0.1
|
||||
# HERMES_API_MODEL_NAME='Hermes Agent'
|
||||
|
||||
OPEN_WEBUI_PORT="${OPEN_WEBUI_PORT:-8080}"
|
||||
OPEN_WEBUI_HOST="${OPEN_WEBUI_HOST:-127.0.0.1}"
|
||||
OPEN_WEBUI_NAME="${OPEN_WEBUI_NAME:-Hermes Agent WebUI}"
|
||||
OPEN_WEBUI_ENABLE_SIGNUP="${OPEN_WEBUI_ENABLE_SIGNUP:-true}"
|
||||
OPEN_WEBUI_ENABLE_SERVICE="${OPEN_WEBUI_ENABLE_SERVICE:-auto}"
|
||||
OPEN_WEBUI_VENV="${OPEN_WEBUI_VENV:-$HOME/.local/open-webui-venv}"
|
||||
OPEN_WEBUI_DATA_DIR="${OPEN_WEBUI_DATA_DIR:-$HOME/.local/share/open-webui/data}"
|
||||
HERMES_ENV_FILE="${HERMES_ENV_FILE:-$HOME/.hermes/.env}"
|
||||
HERMES_API_PORT="${HERMES_API_PORT:-8642}"
|
||||
HERMES_API_HOST="${HERMES_API_HOST:-127.0.0.1}"
|
||||
HERMES_API_CONNECT_HOST="${HERMES_API_CONNECT_HOST:-127.0.0.1}"
|
||||
HERMES_API_MODEL_NAME="${HERMES_API_MODEL_NAME:-Hermes Agent}"
|
||||
HERMES_API_BASE_URL="http://${HERMES_API_CONNECT_HOST}:${HERMES_API_PORT}/v1"
|
||||
LAUNCHER_PATH="$HOME/.local/bin/start-open-webui-hermes.sh"
|
||||
LOG_DIR="$HOME/.hermes/logs"
|
||||
|
||||
log() {
|
||||
printf '[open-webui-bootstrap] %s\n' "$*"
|
||||
}
|
||||
|
||||
require_cmd() {
|
||||
if ! command -v "$1" >/dev/null 2>&1; then
|
||||
echo "Missing required command: $1" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
choose_python() {
|
||||
if command -v python3.11 >/dev/null 2>&1; then
|
||||
echo python3.11
|
||||
elif command -v python3 >/dev/null 2>&1; then
|
||||
echo python3
|
||||
else
|
||||
echo "Python 3 is required." >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
upsert_env() {
|
||||
local key="$1"
|
||||
local value="$2"
|
||||
local file="$3"
|
||||
|
||||
mkdir -p "$(dirname "$file")"
|
||||
touch "$file"
|
||||
|
||||
python3 - "$file" "$key" "$value" <<'PY'
|
||||
from pathlib import Path
|
||||
import sys
|
||||
path = Path(sys.argv[1])
|
||||
key = sys.argv[2]
|
||||
value = sys.argv[3]
|
||||
lines = path.read_text().splitlines() if path.exists() else []
|
||||
out = []
|
||||
seen = False
|
||||
for raw in lines:
|
||||
stripped = raw.strip()
|
||||
if stripped.startswith(f"{key}="):
|
||||
if not seen:
|
||||
out.append(f"{key}={value}")
|
||||
seen = True
|
||||
continue
|
||||
out.append(raw)
|
||||
if not seen:
|
||||
if out and out[-1] != "":
|
||||
out.append("")
|
||||
out.append(f"{key}={value}")
|
||||
path.write_text("\n".join(out).rstrip() + "\n")
|
||||
PY
|
||||
}
|
||||
|
||||
get_env_value() {
|
||||
local key="$1"
|
||||
local file="$2"
|
||||
python3 - "$file" "$key" <<'PY'
|
||||
from pathlib import Path
|
||||
import sys
|
||||
path = Path(sys.argv[1])
|
||||
key = sys.argv[2]
|
||||
if not path.exists():
|
||||
raise SystemExit(0)
|
||||
for raw in path.read_text().splitlines():
|
||||
line = raw.strip()
|
||||
if line.startswith(f"{key}="):
|
||||
print(line.split("=", 1)[1])
|
||||
raise SystemExit(0)
|
||||
PY
|
||||
}
|
||||
|
||||
generate_secret() {
|
||||
python3 - <<'PY'
|
||||
import secrets
|
||||
print(secrets.token_urlsafe(32))
|
||||
PY
|
||||
}
|
||||
|
||||
shell_quote() {
|
||||
python3 - "$1" <<'PY'
|
||||
import shlex
|
||||
import sys
|
||||
print(shlex.quote(sys.argv[1]))
|
||||
PY
|
||||
}
|
||||
|
||||
can_use_systemd_user() {
|
||||
[[ "$(uname -s)" == "Linux" ]] || return 1
|
||||
command -v systemctl >/dev/null 2>&1 || return 1
|
||||
|
||||
local uid runtime_dir bus_path
|
||||
uid="$(id -u)"
|
||||
runtime_dir="${XDG_RUNTIME_DIR:-/run/user/$uid}"
|
||||
bus_path="$runtime_dir/bus"
|
||||
|
||||
if [[ -z "${XDG_RUNTIME_DIR:-}" && -d "$runtime_dir" ]]; then
|
||||
export XDG_RUNTIME_DIR="$runtime_dir"
|
||||
fi
|
||||
if [[ -z "${DBUS_SESSION_BUS_ADDRESS:-}" && -S "$bus_path" ]]; then
|
||||
export DBUS_SESSION_BUS_ADDRESS="unix:path=$bus_path"
|
||||
fi
|
||||
|
||||
systemctl --user show-environment >/dev/null 2>&1
|
||||
}
|
||||
|
||||
install_macos_dependencies() {
|
||||
if [[ "$(uname -s)" == "Darwin" ]] && command -v brew >/dev/null 2>&1; then
|
||||
if ! command -v pandoc >/dev/null 2>&1; then
|
||||
log 'Installing pandoc with Homebrew (recommended by Open WebUI docs)...'
|
||||
brew install pandoc
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
install_open_webui() {
|
||||
local py
|
||||
py="$(choose_python)"
|
||||
log "Using Python interpreter: $py"
|
||||
"$py" -m venv "$OPEN_WEBUI_VENV"
|
||||
# shellcheck disable=SC1090
|
||||
source "$OPEN_WEBUI_VENV/bin/activate"
|
||||
python -m pip install --upgrade pip setuptools wheel
|
||||
python -m pip install open-webui
|
||||
}
|
||||
|
||||
write_launcher() {
|
||||
mkdir -p "$(dirname "$LAUNCHER_PATH")" "$OPEN_WEBUI_DATA_DIR" "$LOG_DIR"
|
||||
|
||||
local quoted_data_dir quoted_name quoted_base_url quoted_host quoted_port quoted_venv
|
||||
quoted_data_dir="$(shell_quote "$OPEN_WEBUI_DATA_DIR")"
|
||||
quoted_name="$(shell_quote "$OPEN_WEBUI_NAME")"
|
||||
quoted_base_url="$(shell_quote "$HERMES_API_BASE_URL")"
|
||||
quoted_host="$(shell_quote "$OPEN_WEBUI_HOST")"
|
||||
quoted_port="$(shell_quote "$OPEN_WEBUI_PORT")"
|
||||
quoted_venv="$(shell_quote "$OPEN_WEBUI_VENV")"
|
||||
|
||||
cat > "$LAUNCHER_PATH" <<EOF
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
|
||||
API_KEY=\$(python3 - <<'PY'
|
||||
from pathlib import Path
|
||||
p = Path.home()/'.hermes'/'.env'
|
||||
for raw in p.read_text().splitlines():
|
||||
line = raw.strip()
|
||||
if line.startswith('API_SERVER_KEY='):
|
||||
print(line.split('=', 1)[1])
|
||||
break
|
||||
PY
|
||||
)
|
||||
export DATA_DIR=${quoted_data_dir}
|
||||
export WEBUI_NAME=${quoted_name}
|
||||
export ENABLE_SIGNUP=${OPEN_WEBUI_ENABLE_SIGNUP}
|
||||
export ENABLE_PUBLIC_ACTIVE_USERS_COUNT=False
|
||||
export ENABLE_VERSION_UPDATE_CHECK=False
|
||||
export OPENAI_API_BASE_URL=${quoted_base_url}
|
||||
export OPENAI_API_KEY="\$API_KEY"
|
||||
export ENABLE_OPENAI_API=True
|
||||
export ENABLE_OLLAMA_API=False
|
||||
export OFFLINE_MODE=True
|
||||
export BYPASS_EMBEDDING_AND_RETRIEVAL=True
|
||||
export RAG_EMBEDDING_MODEL_AUTO_UPDATE=False
|
||||
export RAG_RERANKING_MODEL_AUTO_UPDATE=False
|
||||
export SCARF_NO_ANALYTICS=true
|
||||
export DO_NOT_TRACK=true
|
||||
export ANONYMIZED_TELEMETRY=false
|
||||
export HOST=${quoted_host}
|
||||
export PORT=${quoted_port}
|
||||
source ${quoted_venv}/bin/activate
|
||||
exec open-webui serve
|
||||
EOF
|
||||
|
||||
chmod +x "$LAUNCHER_PATH"
|
||||
}
|
||||
|
||||
ensure_env_permissions() {
|
||||
chmod 600 "$HERMES_ENV_FILE" 2>/dev/null || true
|
||||
}
|
||||
|
||||
install_launchd_service() {
|
||||
local plist="$HOME/Library/LaunchAgents/ai.openwebui.hermes.plist"
|
||||
mkdir -p "$(dirname "$plist")"
|
||||
cat > "$plist" <<EOF
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>ai.openwebui.hermes</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/bin/bash</string>
|
||||
<string>${LAUNCHER_PATH}</string>
|
||||
</array>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
<key>KeepAlive</key>
|
||||
<true/>
|
||||
<key>WorkingDirectory</key>
|
||||
<string>${HOME}</string>
|
||||
<key>StandardOutPath</key>
|
||||
<string>${LOG_DIR}/openwebui.log</string>
|
||||
<key>StandardErrorPath</key>
|
||||
<string>${LOG_DIR}/openwebui.error.log</string>
|
||||
</dict>
|
||||
</plist>
|
||||
EOF
|
||||
launchctl bootout "gui/$(id -u)" "$plist" >/dev/null 2>&1 || true
|
||||
launchctl bootstrap "gui/$(id -u)" "$plist"
|
||||
launchctl enable "gui/$(id -u)/ai.openwebui.hermes"
|
||||
launchctl kickstart -k "gui/$(id -u)/ai.openwebui.hermes"
|
||||
}
|
||||
|
||||
install_systemd_user_service() {
|
||||
require_cmd systemctl
|
||||
local unit_dir="$HOME/.config/systemd/user"
|
||||
local unit="$unit_dir/openwebui-hermes.service"
|
||||
mkdir -p "$unit_dir"
|
||||
cat > "$unit" <<EOF
|
||||
[Unit]
|
||||
Description=Open WebUI connected to Hermes Agent
|
||||
After=default.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/bin/bash %h/.local/bin/start-open-webui-hermes.sh
|
||||
Restart=always
|
||||
RestartSec=3
|
||||
WorkingDirectory=%h
|
||||
StandardOutput=append:%h/.hermes/logs/openwebui.log
|
||||
StandardError=append:%h/.hermes/logs/openwebui.error.log
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
EOF
|
||||
systemctl --user daemon-reload
|
||||
systemctl --user enable --now openwebui-hermes.service
|
||||
}
|
||||
|
||||
start_foreground_hint() {
|
||||
log "Launcher created at: ${LAUNCHER_PATH}"
|
||||
log "Start Open WebUI manually with: ${LAUNCHER_PATH}"
|
||||
}
|
||||
|
||||
main() {
|
||||
require_cmd hermes
|
||||
require_cmd curl
|
||||
require_cmd python3
|
||||
|
||||
install_macos_dependencies
|
||||
|
||||
local api_key
|
||||
api_key="$(get_env_value API_SERVER_KEY "$HERMES_ENV_FILE")"
|
||||
if [[ -z "$api_key" ]]; then
|
||||
api_key="$(generate_secret)"
|
||||
fi
|
||||
|
||||
log 'Ensuring Hermes API server is configured...'
|
||||
upsert_env API_SERVER_ENABLED true "$HERMES_ENV_FILE"
|
||||
upsert_env API_SERVER_HOST "$HERMES_API_HOST" "$HERMES_ENV_FILE"
|
||||
upsert_env API_SERVER_PORT "$HERMES_API_PORT" "$HERMES_ENV_FILE"
|
||||
upsert_env API_SERVER_MODEL_NAME "$HERMES_API_MODEL_NAME" "$HERMES_ENV_FILE"
|
||||
upsert_env API_SERVER_KEY "$api_key" "$HERMES_ENV_FILE"
|
||||
ensure_env_permissions
|
||||
|
||||
log 'Restarting Hermes gateway so API server settings take effect...'
|
||||
hermes gateway restart >/dev/null 2>&1 || true
|
||||
sleep 4
|
||||
if ! curl -fsS "http://${HERMES_API_CONNECT_HOST}:${HERMES_API_PORT}/health" >/dev/null; then
|
||||
log 'Hermes API server did not answer on the first check. Trying to start gateway in the background...'
|
||||
nohup hermes gateway run >/dev/null 2>&1 &
|
||||
sleep 6
|
||||
fi
|
||||
curl -fsS "http://${HERMES_API_CONNECT_HOST}:${HERMES_API_PORT}/health" >/dev/null
|
||||
|
||||
log 'Installing Open WebUI into a dedicated virtualenv...'
|
||||
install_open_webui
|
||||
write_launcher
|
||||
|
||||
case "$OPEN_WEBUI_ENABLE_SERVICE" in
|
||||
true|auto)
|
||||
if [[ "$(uname -s)" == "Darwin" ]]; then
|
||||
install_launchd_service
|
||||
elif can_use_systemd_user; then
|
||||
install_systemd_user_service
|
||||
else
|
||||
log 'No usable user service manager detected; falling back to the launcher script.'
|
||||
start_foreground_hint
|
||||
fi
|
||||
;;
|
||||
false)
|
||||
start_foreground_hint
|
||||
;;
|
||||
*)
|
||||
echo "OPEN_WEBUI_ENABLE_SERVICE must be one of: auto, true, false" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
log "Done. Open WebUI should be available at: http://${OPEN_WEBUI_HOST}:${OPEN_WEBUI_PORT}"
|
||||
log "Hermes API endpoint: ${HERMES_API_BASE_URL}"
|
||||
log 'Important: Open WebUI persists connection settings after first launch. If you later save a wrong API key in the Admin UI, update/delete that connection there or reset its database.'
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
|
@ -24,6 +24,44 @@ Open WebUI talks to Hermes server-to-server, so you do not need `API_SERVER_CORS
|
|||
|
||||
## Quick Setup
|
||||
|
||||
### One-command local bootstrap (macOS/Linux, no Docker)
|
||||
|
||||
If you want Hermes + Open WebUI wired together locally with a reusable launcher, run:
|
||||
|
||||
```bash
|
||||
cd ~/.hermes/hermes-agent
|
||||
bash scripts/setup_open_webui.sh
|
||||
```
|
||||
|
||||
What the script does:
|
||||
|
||||
- ensures `~/.hermes/.env` contains `API_SERVER_ENABLED`, `API_SERVER_HOST`, `API_SERVER_KEY`, `API_SERVER_PORT`, and `API_SERVER_MODEL_NAME`
|
||||
- restarts the Hermes gateway so the API server comes up
|
||||
- installs Open WebUI into `~/.local/open-webui-venv`
|
||||
- writes a launcher at `~/.local/bin/start-open-webui-hermes.sh`
|
||||
- on macOS, installs a `launchd` user service; on Linux with `systemd --user`, installs a user service there
|
||||
|
||||
Defaults:
|
||||
|
||||
- Hermes API: `http://127.0.0.1:8642/v1`
|
||||
- Open WebUI: `http://127.0.0.1:8080`
|
||||
- model name advertised to Open WebUI: `Hermes Agent`
|
||||
|
||||
Useful overrides:
|
||||
|
||||
```bash
|
||||
OPEN_WEBUI_NAME='My Hermes UI' \
|
||||
OPEN_WEBUI_ENABLE_SIGNUP=true \
|
||||
HERMES_API_MODEL_NAME='My Hermes Agent' \
|
||||
bash scripts/setup_open_webui.sh
|
||||
```
|
||||
|
||||
On Linux, automatic background service setup requires a working `systemd --user` session. If you are on a headless SSH box and want to skip service installation, run:
|
||||
|
||||
```bash
|
||||
OPEN_WEBUI_ENABLE_SERVICE=false bash scripts/setup_open_webui.sh
|
||||
```
|
||||
|
||||
### 1. Enable the API server
|
||||
|
||||
```bash
|
||||
|
|
@ -124,7 +162,7 @@ If you prefer to configure the connection through the UI instead of environment
|
|||
5. Click **+ Add New Connection**
|
||||
6. Enter:
|
||||
- **URL**: `http://host.docker.internal:8642/v1`
|
||||
- **API Key**: your key or any non-empty value (e.g., `not-needed`)
|
||||
- **API Key**: the exact same value as `API_SERVER_KEY` in Hermes
|
||||
7. Click the **checkmark** to verify the connection
|
||||
8. **Save**
|
||||
|
||||
|
|
@ -219,6 +257,10 @@ Hermes Agent may be executing multiple tool calls (reading files, running comman
|
|||
|
||||
Make sure your `OPENAI_API_KEY` in Open WebUI matches the `API_SERVER_KEY` in Hermes Agent.
|
||||
|
||||
:::warning
|
||||
Open WebUI persists OpenAI-compatible connection settings in its own database after first launch. If you accidentally saved a wrong key in the Admin UI, fixing the environment variables alone is not enough — update or delete the saved connection in **Admin Settings → Connections**, or reset the Open WebUI data directory / database.
|
||||
:::
|
||||
|
||||
## Multi-User Setup with Profiles
|
||||
|
||||
To run separate Hermes instances per user — each with their own config, memory, and skills — use [profiles](/docs/user-guide/profiles). Each profile runs its own API server on a different port and automatically advertises the profile name as the model in Open WebUI.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue