#!/bin/bash # ============================================================================================== # ━━━ AI Retrieval Server ━━━ # ============================================================================================== # # PURPOSE # ============================================================================================== # Starts ai_serve.js, the retrieval-only HTTP bridge that lets Open-WebUI — which runs in its # own container and cannot see Varaverk's filesystem — search the AI index for grounding # context. # # ============================================================================================== # # OPERATIONAL MODEL # ============================================================================================== # Mirrors Arrs_Stack/start_webhook_listener.sh, because it solves the same problem: a caller # outside Unraid's nginx needs to reach Varaverk, and nginx applies auth_request to everything # it serves. Run from ARRAY_START_SCRIPTS and exec's node, so the process this script becomes # is the server itself — no PID file to go stale. # # Retrieval only. Generation stays in ai_query.sh; Open-WebUI has its own model loaded and # generates from the chunks this returns. # # ============================================================================================== # # DESIGN PRINCIPLES # ============================================================================================== # Every precondition is checked before exec, not after. A failure at array start should name # its cause in the log rather than surface as an exec error once the setup has already run. # # Disabled is a first-class state. AI_HTTP_PORT=0 or AI_ENABLED=false exits 0 without warning, # so a host that does not want the bridge is not a host reporting a failed start script. # # The index is not built here. This serves an index; ai_index.sh creates one. A server that # silently indexed on boot would turn a restart into an unbounded embedding run. # # ============================================================================================== # # OPERATIONAL SAFEGUARDS # ============================================================================================== # # Root Required # Writes the generated secret back into master.conf and logs under /var/log/varaverk. # # Refuses to Serve a Missing Index # The endpoint would answer every query with a retrieval error and Open-WebUI would render # it as "nothing found" — indistinguishable from a corpus that genuinely lacks the answer. # Exits with a message naming ai_index.sh instead. # # Secret Generated Once, and Verified Persisted # An unset AI_HTTP_SECRET is generated with openssl and written to master.conf. If the # write-back cannot be confirmed the start is failed: a secret that exists only in this # process changes on every restart, silently breaking the tool registered in Open-WebUI. # Same failure mode, and same guard, as the webhook listener's secret. # # Single Instance # acquire_lock "continuous" — an array stop/start without a reboot leaves the old node # process holding the port, and a second bind would fail with EADDRINUSE and log a false # failure against array_started.sh. # # Binds Only Where It Must # Defaults to 0.0.0.0 because the caller is a container on the docker bridge and cannot # reach a loopback-bound socket. That is the reason the secret exists, and why the served # surface is a single read-only verb over documentation already in git. # # ============================================================================================== # # CONFIGURATION # ============================================================================================== # AI_ENABLED master.conf — false exits without starting # AI_HTTP_PORT master.conf — 0 disables the bridge # AI_HTTP_SECRET master.conf — auto-generated on first start if empty # AI_HTTP_BIND master.conf — bind address, default 0.0.0.0 # AI_INDEX_DB master.conf — index served # _OLLAMA_URL host conf — embedding endpoint # _OLLAMA_EMBED_MODEL host conf — embedding model # # ============================================================================================== # # RUNTIME MODES # ============================================================================================== # start_ai_server.sh exec's the server in the foreground; run from ARRAY_START_SCRIPTS # # ============================================================================================== set -uo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ECOSYSTEM_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" source "$ECOSYSTEM_ROOT/load_config.sh" if [[ "$EUID" -ne 0 ]]; then error "Must be run as root" exit 1 fi if [[ "${AI_ENABLED:-false}" != "true" ]]; then echo "[ai_serve] AI_ENABLED is not true — retrieval bridge disabled" exit 0 fi [[ "${AI_HTTP_PORT:-0}" -eq 0 ]] && { echo "[ai_serve] AI_HTTP_PORT=0 — retrieval bridge disabled" exit 0 } if ! command -v node >/dev/null 2>&1; then error "node not found — required to run ai_serve.js" notify "AI retrieval bridge failed to start on $(hostname) — node not installed" \ "AI Retrieval" "warning" exit 1 fi detect_hosts _url_var="${MY_ID}_OLLAMA_URL" _emb_var="${MY_ID}_OLLAMA_EMBED_MODEL" OLLAMA_URL="${!_url_var:-}" EMBED_MODEL="${!_emb_var:-nomic-embed-text}" DB="${AI_INDEX_DB:-${DATA_DIR}/ai_index.db}" if [[ -z "$OLLAMA_URL" ]]; then error "${MY_ID}_OLLAMA_URL is empty — cannot embed queries" exit 1 fi # Serving an absent index answers every question with a retrieval error, which Open-WebUI # renders as "nothing found" — the same thing an empty corpus looks like. if [[ ! -f "$DB" ]]; then error "No index at $DB — run AI/ai_index.sh first" notify "AI retrieval bridge not started on $(hostname) — index missing" \ "AI Retrieval" "warning" exit 1 fi acquire_lock "continuous" if [[ -z "${AI_HTTP_SECRET:-}" ]]; then if ! command -v openssl >/dev/null 2>&1; then error "openssl not found — cannot generate AI_HTTP_SECRET" exit 1 fi GENERATED=$(openssl rand -hex 32) MASTER_CONF="$ECOSYSTEM_ROOT/Configurations/master.conf" sed -i "s/AI_HTTP_SECRET=\"\"/AI_HTTP_SECRET=\"$GENERATED\"/" "$MASTER_CONF" AI_HTTP_SECRET="$GENERATED" # A secret held only in this process would differ on the next start, silently breaking # the tool already registered in Open-WebUI. Same guard as the webhook listener. if ! grep -q "AI_HTTP_SECRET=\"$GENERATED\"" "$MASTER_CONF" 2>/dev/null; then error "Generated AI_HTTP_SECRET but could not persist it to $MASTER_CONF" error "Set AI_HTTP_SECRET manually — a non-persisted secret changes on every restart" notify "AI retrieval secret not persisted on $(hostname)" "AI Retrieval" "warning" exit 1 fi echo "[ai_serve] Generated AI_HTTP_SECRET — add it to the Open-WebUI tool" fi mkdir -p /var/log/varaverk exec node "$ECOSYSTEM_ROOT/AI/ai_serve.js" \ "$AI_HTTP_PORT" "$AI_HTTP_SECRET" "${AI_HTTP_BIND:-0.0.0.0}" \ "$DB" "$OLLAMA_URL" "$EMBED_MODEL" \ >> /var/log/varaverk/ai_serve.log 2>&1