#!/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 "$@"