From 1c42d8ff5307849b3c450a5536f641739e220227 Mon Sep 17 00:00:00 2001 From: Zhen Liu Date: Tue, 14 Apr 2026 17:46:30 +0800 Subject: [PATCH] docs: add Open WebUI bootstrap script --- scripts/setup_open_webui.sh | 349 ++++++++++++++++++ .../docs/user-guide/messaging/open-webui.md | 44 ++- 2 files changed, 392 insertions(+), 1 deletion(-) create mode 100755 scripts/setup_open_webui.sh diff --git a/scripts/setup_open_webui.sh b/scripts/setup_open_webui.sh new file mode 100755 index 0000000000..0cca44ddd7 --- /dev/null +++ b/scripts/setup_open_webui.sh @@ -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" </dev/null || true +} + +install_launchd_service() { + local plist="$HOME/Library/LaunchAgents/ai.openwebui.hermes.plist" + mkdir -p "$(dirname "$plist")" + cat > "$plist" < + + + + Label + ai.openwebui.hermes + ProgramArguments + + /bin/bash + ${LAUNCHER_PATH} + + RunAtLoad + + KeepAlive + + WorkingDirectory + ${HOME} + StandardOutPath + ${LOG_DIR}/openwebui.log + StandardErrorPath + ${LOG_DIR}/openwebui.error.log + + +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" </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 "$@" diff --git a/website/docs/user-guide/messaging/open-webui.md b/website/docs/user-guide/messaging/open-webui.md index 9c90eb7998..4366a0e65e 100644 --- a/website/docs/user-guide/messaging/open-webui.md +++ b/website/docs/user-guide/messaging/open-webui.md @@ -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.