Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions scripts/release.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@
"hypnus.yuan@gmail.com": "Hypnus-Yuan",
"15558128926@qq.com": "xsfX20",
"binhnt.ht.92@gmail.com": "binhnt92",
"johnny@Jons-MBA-M4.local": "acesjohnny",
# Matrix parity salvage batch (April 2026)
"sr@samirusani": "samrusani",
"angelclaw@AngelMacBook.local": "angel12",
Expand Down
349 changes: 349 additions & 0 deletions scripts/setup_open_webui.sh
Original file line number Diff line number Diff line change
@@ -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 "$@"
Loading
Loading