Руководство по сборке локального AI-стека. Часть 5. Open WebUI поверх llama.cpp
На этом шаге мы добавляем Open WebUI — веб-интерфейс с аутентификацией, историей чатов и настройкой параметров моделей. Это финальный слой: llama-server продолжает работать как обычно (только на 127.0.0.1), а Open WebUI становится «парадной дверью» для пользователей из LAN.
Зачем нужен Open WebUI
Родной UI llama.cpp (Svelte) хорош для отладки, но у него нет:
Управления моделями — нельзя задать per-model системные промпты, параметры сэмплинга, отображение.
Многопользовательского режима — один пользователь = один контекст.
Open WebUI добавляет всё это, оставаясь тонкой надстройкой над llama-server. Он ходит к нему по OpenAI-совместимому API (/v1/chat/completions), так что llama.cpp даже не знает, что его использует другой клиент.
Архитектура
LAN 0.0.0.0:8080 (Open WebUI) -> Local 127.0.0.1:10000 llama-server
Ключевые моменты:
llama-serverслушает только127.0.0.1:10000. Снаружи к нему не подключиться.Open WebUI слушает
0.0.0.0:8080, доступен из LAN, требует логин.Секреты (API-ключ llama + сессионный ключ WebUI) хранятся в
.secrets(chmod 600), генерируются один раз.Оркестратор
run_webui.shподнимает оба процесса, ждёт готовностиllama-server, запускает WebUI, и корректно останавливает оба поCtrl+C.
Секреты: .secrets и init_secrets.sh
Перед первым запуском нужно создать .secrets — файл с двумя ключами:
Ключ | Для чего | Кто использует |
|---|---|---|
| Аутентификация запросов к | Open WebUI (передаёт в заголовке |
| Подпись JWT-токенов сессий Open WebUI | Сам Open WebUI (без него все пользователи разлогиниваются при перезапуске) |
Важно про WEBUI_SECRET_KEY. Если генерировать его при каждом запуске (secrets.token_hex(32) в Python), то при каждом перезапуске WebUI все сессии сбрасываются — пользователи вынуждены логиниться заново. Поэтому ключ хранится в файле и переиспользуется. init_secrets.sh создаёт файл один раз, при повторном запуске ничего не меняет.
Формат .secrets — простые KEY=VALUE, читаются через source в bash и через open() в Python:
# Секреты проекта. Не коммитить в git. chmod 600.
LLAMA_API_KEY=<48 hex-символов>
WEBUI_SECRET_KEY=<64 hex-символа>
Права 600 — только владелец может читать. Это критично: ключи дают полный доступ к серверу.
Установка Open WebUI
Open WebUI ставится в тот же venv, что и всё остальное (venv/). Это упрощает wheelhouse (один список пакетов) и не создаёт второй Python-интерпретатор.
./venv/bin/pip install --upgrade pip
./venv/bin/pip install torch --index-url https://download.pytorch.org/whl/cpu
./venv/bin/pip install open-webui
CPU-версия torch ставится до open-webui, иначе pip притянет CUDA-вариант с nvidia-* пакетами (более 2 ГБ лишнего).
Настройка Open WebUI: start_webui.py
Скрипт читает .secrets, выставляет env-переменные и запускает open-webui serve. Ключевые настройки:
Подключение к llama-server
os.environ["OPENAI_API_BASE_URL"] = "http://127.0.0.1:10000/v1"
os.environ["OPENAI_API_KEY"] = llama_api_key
os.environ["BYPASS_MODEL_ACCESS_CONTROL"] = "True"
OPENAI_API_BASE_URL— адресllama-server. Порт10000— фиксированный (задан вrun_webui.shчерезLLAMA_PORT), чтобы не конфликтовать с родным UIllama.cppна8080.OPENAI_API_KEY— ключ из.secrets. Open WebUI передаёт его в каждом запросе,llama-serverпроверяет.BYPASS_MODEL_ACCESS_CONTROL— все модели из роутера видны всем пользователям WebUI, без ручного «назначения прав на модель».
Оффлайн-режим
os.environ["OFFLINE_MODE"] = "true"
os.environ["HF_HUB_OFFLINE"] = "1"
os.environ["RAG_EMBEDDING_MODEL_AUTO_UPDATE"] = "false"
os.environ["RAG_RERANKING_MODEL_AUTO_UPDATE"] = "false"
os.environ["RAG_EMBEDDING_ENGINE"] = ""
os.environ["RAG_EMBEDDING_MODEL"] = ""
os.environ["WHISPER_MODEL_AUTO_UPDATE"] = "false"
os.environ["AUDIO_STT_ENGINE"] = ""
OFFLINE_MODE=true— отключает проверку обновлений и автоматические загрузки с Hugging Face.RAG_*— отключает RAG-эмбеддинги (иначе при первом запросе WebUI попытается скачать модель эмбеддингов ~500 МБ).AUDIO_STT_ENGINE=""— отключает Whisper (распознавание речи), чтобы не тянуть модель.
Ollama — отключаем
os.environ["ENABLE_OLLAMA_API"] = "false"
os.environ["OLLAMA_BASE_URL"] = ""
Зачем. Open WebUI по умолчанию пытается обнаружить Ollama на localhost:11434 и «подружиться» с ним. Если Ollama не установлена, в логах появляются ошибки подключения, а иногда WebUI «залипает» на попытках достучаться и не выдает список моделей из llama-cpp сервера. Отключение через ENABLE_OLLAMA_API=false убирает эту головную боль (мы уже обожглись на этом в старом проекте).
Веб-поиск — оставляем
os.environ["ENABLE_RAG_WEB_SEARCH"] = "true"
os.environ["ENABLE_WEB_SEARCH"] = "true"
os.environ["WEB_SEARCH_ENGINE"] = "duckduckgo"
DuckDuckGo — без API-ключа, работает «из коробки», если есть интернет. Модель может использовать результаты поиска для ответа (RAG-поиск в интернете). Если сети нет — просто не работает, но не ломает чат.
Прочие мелочи
os.environ["ENABLE_IMAGE_GENERATION"] = "false" # мы не подключаем sd-server к WebUI
os.environ["ENABLE_COMMUNITY_FUNCTIONS"] = "false" # не грузим сторонние плагины
os.environ["USER_AGENT"] = "OpenWebUI-Offline/1.0" # убирает предупреждение LangChain
os.environ["DATA_DIR"] = DATA_DIR # данные внутри проекта
Регистрация
# os.environ["ENABLE_SIGNUP"] = "false" # по умолчанию НЕ задаём
Логика по умолчанию:
ENABLE_SIGNUPне задаём -> регистрация открыта.Первый зарегистрировавшийся пользователь автоматически становится админом.
После создания первого админа Open WebUI автоматически закрывает регистрацию.
Чтобы добавить пользователя: админ заходит в Admin -> Users -> включает регистрацию на время -> создаёт аккаунт -> закрывает обратно.
Если жёстко поставить ENABLE_SIGNUP=false до первого запуска — даже админ не сможет зарегистрироваться. Поэтому оставляем пустым.
Оркестратор: run_webui.sh
Скрипт делает пять вещей:
Проверяет окружение —
venv,open-webui,.secrets,run_llama_mmap.sh,start_webui.py. Если чего-то нет — создаёт/устанавливает (первый запуск).Проверяет порты
10000и8080— если заняты, выходит с ошибкой.Опционально настраивает статический IP (через
nmcli), если пользователь ответит1. Полезно, чтобы адрес сервера не менялся после перезагрузки роутера.Запускает
llama-serverв фоне на127.0.0.1:10000черезPORT=10000 HOST=127.0.0.1 ./run_llama_mmap.sh. Ждёт/healthдо 5 минут.Запускает Open WebUI на
0.0.0.0:8080черезstart_webui.py. Уходит вwait.
Обработка Ctrl+C. trap cleanup EXIT INT TERM ловит сигнал и:
останавливает WebUI (
SIGTERM, ждёт 10 с,SIGKILL);останавливает
llama-server(шлётSIGTERMв bash-обёрткуrun_llama_mmap.sh, еёtrapделаетsystemctl --user stop, сервер умирает);при ошибке показывает хвосты логов обоих процессов.
Про модель. run_webui.sh принимает опциональный первый аргумент — имя модели. Если передать ./run_webui.sh Qwen3.5-9B-Q4_K_M, оно уйдёт в run_llama_mmap.sh -m ..., и сервер стартует в режиме одной модели. Если не передавать — run_llama_mmap.sh запустится в router mode (список моделей в дропдауне WebUI, загрузка по требованию).
Почему router mode по умолчанию: WebUI позволяет переключать модели без перезапуска сервера. Первая загрузка модели при выборе — долгая, но дальше — комфортно. Если нужна конкретная модель сразу — передайте её аргументом, загрузится только она.
Запуск и первый вход
./run_webui.sh
Вывод:
[INFO] Запуск llama-server на 127.0.0.1:10000...
[INFO] llama-server запущен (PID 12345)
[INFO] Жду готовности API (до 5 мин, на HDD — долго)...
[INFO] llama-server готов.
[INFO] Запуск Open WebUI на 0.0.0.0:8080...
[INFO] Открыть: http://127.0.0.1:8080
[INFO] или: http://192.168.1.100:8080
Первый вход:
Откройте
http://<IP>:8080в браузере.Нажмите «Sign up» — создайте аккаунт. Первый пользователь = админ.
После создания аккаунта регистрация закрывается автоматически.
В дропдауне моделей — список всех
.ggufизmodels/. Выберите нужную.Пишите в чат. История сохраняется автоматически.
Для добавления пользователя: зайдите как админ -> Admin Panel -> Users -> кнопка «Enable Signup» -> создайте аккаунт вручную или дайте человеку зарегистрироваться -> «Disable Signup».
Управление параметрами моделей в WebUI
Одно из главных преимуществ Open WebUI — per-model настройки, которых нет в родном UI llama.cpp:
Admin Panel -> Models — для каждой модели можно задать:
System Prompt — «Ты — ассистент, который отвечает кратко» и т.п.
Temperature, top_p, top_k, repeat_penalty — параметры сэмплинга.
Function calling, vision, file upload — включить/выключить.
Default model — какая модель выбирается при открытии нового чата.
Это позволяет «настроить и забыть»: например, для OxCoder-9B — temperature=0.2 (код точнее), для Gemma-4-E4B — temperature=0.9 (РП живее). В llama.cpp такие настройки пришлось бы передавать в каждом запросе.
Код init_secrets.sh
#!/usr/bin/env bash
# Создаёт .secrets с LLAMA_API_KEY и WEBUI_SECRET_KEY, если файла нет.
# Идемпотентен: повторный запуск ничего не меняет.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SECRETS_FILE="$SCRIPT_DIR/.secrets"
if [ -f "$SECRETS_FILE" ]; then
echo "[*] $SECRETS_FILE уже существует, ничего не меняю."
exit 0
fi
command -v openssl >/dev/null || { echo "[!] нужен openssl"; exit 1; }
LLAMA_API_KEY="$(openssl rand -hex 24)"
WEBUI_SECRET_KEY="$(openssl rand -hex 32)"
umask 077
cat > "$SECRETS_FILE" <<EOF
# Секреты проекта. Не коммитить в git. chmod 600.
LLAMA_API_KEY=$LLAMA_API_KEY
WEBUI_SECRET_KEY=$WEBUI_SECRET_KEY
EOF
chmod 600 "$SECRETS_FILE"
echo "[*] Создан $SECRETS_FILE"
echo " LLAMA_API_KEY=${LLAMA_API_KEY:0:8}... (первые 8 символов)"
echo " WEBUI_SECRET_KEY=${WEBUI_SECRET_KEY:0:8}..."
Код run_webui.sh
#!/usr/bin/env bash
# Запуск llama-server (порт 10000) + Open WebUI (порт 8080).
# При первом запуске: создаёт venv, ставит open-webui, генерирует .secrets.
#
# Usage: ./run_webui.sh [model_name]
# model_name — опционально; если не задан, run_llama_mmap.sh спросит интерактивно.
#
# Env:
# LLAMA_PORT=10000 порт llama-server (только localhost)
# WEBUI_PORT=8080 порт Open WebUI (0.0.0.0, доступ из LAN)
# ASK_FIX_IP=1 спрашивать про статический IP (0 — не спрашивать)
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
VENV_DIR="$SCRIPT_DIR/venv"
SECRETS_FILE="$SCRIPT_DIR/.secrets"
OFFLINE_PACKAGES_DIR="$SCRIPT_DIR/offline_packages"
LLAMA_SCRIPT="$SCRIPT_DIR/run_llama_mmap.sh"
WEBUI_SCRIPT="$SCRIPT_DIR/start_webui.py"
INIT_SECRETS="$SCRIPT_DIR/init_secrets.sh"
LLAMA_HOST="${LLAMA_HOST:-127.0.0.1}"
LLAMA_PORT="${LLAMA_PORT:-10000}"
WEBUI_PORT="${WEBUI_PORT:-8080}"
ASK_FIX_IP="${ASK_FIX_IP:-1}"
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m'
info() { echo -e "${GREEN}[INFO]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
error() { echo -e "${RED}[ERROR]${NC} $*" >&2; exit 1; }
# ==================================================================
# 1. Установка / проверка окружения
# ==================================================================
if ! command -v python3 &>/dev/null; then
error "python3 не найден. Установите: sudo apt install python3 python3-venv python3-pip"
fi
if [ ! -d "$VENV_DIR" ]; then
info "venv не найден, создаю $VENV_DIR..."
python3 -m venv "$VENV_DIR"
"$VENV_DIR/bin/pip" install --quiet --upgrade pip
fi
# open-webui: сначала оффлайн, потом онлайн
if [ ! -x "$VENV_DIR/bin/open-webui" ]; then
if [ -d "$OFFLINE_PACKAGES_DIR" ] && [ -n "$(ls -A "$OFFLINE_PACKAGES_DIR" 2>/dev/null)" ]; then
info "Ставлю open-webui из offline_packages..."
if ! "$VENV_DIR/bin/pip" install --no-index \
--find-links="$OFFLINE_PACKAGES_DIR" open-webui; then
warn "Оффлайн-установка не удалась, пробую из PyPI..."
if ! "$VENV_DIR/bin/pip" show torch &>/dev/null; then
"$VENV_DIR/bin/pip" install torch --index-url https://download.pytorch.org/whl/cpu
fi
"$VENV_DIR/bin/pip" install open-webui
fi
else
info "Ставлю open-webui из PyPI..."
"$VENV_DIR/bin/pip" install open-webui
fi
fi
[ -x "$VENV_DIR/bin/open-webui" ] || error "open-webui не установлен в venv"
# секреты
[ -x "$INIT_SECRETS" ] || [ -f "$INIT_SECRETS" ] || error "Нет $INIT_SECRETS"
if [ ! -f "$SECRETS_FILE" ]; then
info "Секреты не найдены, генерирую..."
bash "$INIT_SECRETS"
fi
[ -f "$LLAMA_SCRIPT" ] || error "Не найден $LLAMA_SCRIPT"
[ -f "$WEBUI_SCRIPT" ] || error "Не найден $WEBUI_SCRIPT"
# ==================================================================
# 2. Проверка занятости портов
# ==================================================================
for p in "$LLAMA_PORT" "$WEBUI_PORT"; do
if (echo >/dev/tcp/127.0.0.1/"$p") 2>/dev/null; then
error "Порт $p уже занят. Освободите или задайте LLAMA_PORT/WEBUI_PORT."
fi
done
# ==================================================================
# 3. Опциональная настройка статического IP
# ==================================================================
if [ "$ASK_FIX_IP" = "1" ] && command -v nmcli &>/dev/null; then
read -n1 -r -p "Сделать фиксированный IP для этого ПК? [0 - нет, 1 - да]: " fix_ip
echo ''
if [ "$fix_ip" = "1" ]; then
echo "Настройка статического IP..."
iface="$(ip route | grep default | cut -d' ' -f5 | head -1)"
[ -n "$iface" ] || error "Не удалось определить сетевой интерфейс"
echo "Интерфейс: $iface"
read -r -p "IP-адрес (например, 192.168.1.100/24): " ipaddr
read -r -p "Шлюз (Enter — авто): " gateway
read -r -p "DNS (Enter — авто): " dns
[ -n "$gateway" ] || gateway="$(ip route | grep default | cut -d' ' -f3 | head -1)"
[ -n "$dns" ] || dns="$(nmcli dev show | grep 'IP4.DNS' | tr -s ' ' | cut -d' ' -f2 | head -1)"
sudo nmcli con mod "$iface" ipv4.addresses "$ipaddr"
sudo nmcli con mod "$iface" ipv4.gateway "$gateway"
sudo nmcli con mod "$iface" ipv4.dns "$dns"
sudo nmcli con mod "$iface" ipv4.method manual
sudo nmcli con up "$iface"
info "Статический IP настроен. Текущий адрес: $(hostname -I)"
fi
fi
# ==================================================================
# 4. Логи и trap
# ==================================================================
LLAMA_LOG="$(mktemp -t llama-orch.XXXXXX.log)"
WEBUI_LOG="$(mktemp -t webui-orch.XXXXXX.log)"
LLAMA_PID=""
WEBUI_PID=""
cleanup() {
local ec=$?
trap - EXIT INT TERM
set +e
echo ""
info "Завершение работы..."
# WebUI
if [ -n "$WEBUI_PID" ] && kill -0 "$WEBUI_PID" 2>/dev/null; then
info "Останавливаю Open WebUI (PID $WEBUI_PID)..."
kill "$WEBUI_PID" 2>/dev/null
for _ in $(seq 1 10); do
kill -0 "$WEBUI_PID" 2>/dev/null || break
sleep 1
done
kill -9 "$WEBUI_PID" 2>/dev/null
wait "$WEBUI_PID" 2>/dev/null
fi
# llama (bash-обёртка). Внутри её trap сделает systemctl stop.
if [ -n "$LLAMA_PID" ] && kill -0 "$LLAMA_PID" 2>/dev/null; then
info "Останавливаю llama-server (PID $LLAMA_PID)..."
kill "$LLAMA_PID" 2>/dev/null
for _ in $(seq 1 15); do
kill -0 "$LLAMA_PID" 2>/dev/null || break
sleep 1
done
kill -9 "$LLAMA_PID" 2>/dev/null
wait "$LLAMA_PID" 2>/dev/null
fi
if [ "$ec" -ne 0 ] && [ "$ec" -ne 130 ]; then
echo ""; warn "Хвост лога llama:"; tail -n 20 "$LLAMA_LOG" 2>/dev/null
echo ""; warn "Хвост лога webui:"; tail -n 20 "$WEBUI_LOG" 2>/dev/null
fi
rm -f "$LLAMA_LOG" "$WEBUI_LOG"
info "Все сервисы остановлены."
exit "$ec"
}
trap cleanup EXIT INT TERM
# ==================================================================
# 5. Запуск llama-server в фоне
# ==================================================================
LLAMA_ARGS=()
[ $# -ge 1 ] && LLAMA_ARGS=(-m "$1")
info "Запуск llama-server на $LLAMA_HOST:$LLAMA_PORT..."
info "Лог: $LLAMA_LOG"
PORT="$LLAMA_PORT" HOST="$LLAMA_HOST" \
"$LLAMA_SCRIPT" "${LLAMA_ARGS[@]}" >"$LLAMA_LOG" 2>&1 &
LLAMA_PID=$!
info "llama-server запущен (PID $LLAMA_PID)"
# ==================================================================
# 6. Ожидание готовности API
# ==================================================================
info "Жду готовности API (до 5 мин, на HDD — долго)..."
READY=0
for i in $(seq 1 300); do
kill -0 "$LLAMA_PID" 2>/dev/null || {
warn "llama-server умер. Лог:"; tail -n 40 "$LLAMA_LOG"
exit 1
}
if curl -sf "http://$LLAMA_HOST:$LLAMA_PORT/health" >/dev/null 2>&1; then
READY=1; break
fi
[ $((i % 30)) -eq 0 ] && info " ... ждём ($i с)"
sleep 1
done
[ "$READY" -eq 1 ] || { warn "llama-server не поднялся за 5 мин. Лог:"; tail -n 40 "$LLAMA_LOG"; exit 1; }
info "llama-server готов."
# ==================================================================
# 7. Запуск Open WebUI (foreground по сути; wait ниже)
# ==================================================================
LAN_IP="$(hostname -I 2>/dev/null | awk '{print $1}')"
info "Запуск Open WebUI на 0.0.0.0:$WEBUI_PORT..."
info "Открыть: http://127.0.0.1:$WEBUI_PORT"
[ -n "$LAN_IP" ] && info " или: http://$LAN_IP:$WEBUI_PORT"
WEBUI_HOST=0.0.0.0 WEBUI_PORT="$WEBUI_PORT" \
"$VENV_DIR/bin/python" "$WEBUI_SCRIPT" 2>&1 | tee -a "$WEBUI_LOG" &
WEBUI_PID=$!
# Ждём завершения WebUI (Ctrl+C -> trap)
wait "$WEBUI_PID"
Код start_webui.py
#!/usr/bin/env python3
"""Запуск Open WebUI с настройками для локального llama-server."""
import os
import sys
import subprocess
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
SECRETS_FILE = os.path.join(SCRIPT_DIR, ".secrets")
DATA_DIR = os.path.join(SCRIPT_DIR, "open-webui-data")
# --- Чтение секретов из .secrets ---
def load_secrets(path):
result = {}
if not os.path.isfile(path):
return result
with open(path) as f:
for line in f:
line = line.strip()
if not line or line.startswith("#") or "=" not in line:
continue
key, _, val = line.partition("=")
result[key.strip()] = val.strip()
return result
secrets = load_secrets(SECRETS_FILE)
# WEBUI_SECRET_KEY — обязателен для стабильных сессий
if "WEBUI_SECRET_KEY" not in secrets:
print(f"Ошибка: в {SECRETS_FILE} нет WEBUI_SECRET_KEY. "
f"Запустите init_secrets.sh", file=sys.stderr)
sys.exit(1)
os.environ["WEBUI_SECRET_KEY"] = secrets["WEBUI_SECRET_KEY"]
# LLAMA_API_KEY — для доступа к llama-server
llama_api_key = secrets.get("LLAMA_API_KEY", "sk-no-key-required")
os.environ["OPENAI_API_KEY"] = llama_api_key
# --- Оффлайн-режим и отключение лишнего ---
os.environ["OFFLINE_MODE"] = "true"
os.environ["HF_HUB_OFFLINE"] = "1"
os.environ["RAG_EMBEDDING_MODEL_AUTO_UPDATE"] = "false"
os.environ["RAG_RERANKING_MODEL_AUTO_UPDATE"] = "false"
os.environ["RAG_EMBEDDING_ENGINE"] = ""
os.environ["RAG_EMBEDDING_MODEL"] = ""
os.environ["WHISPER_MODEL_AUTO_UPDATE"] = "false"
os.environ["AUDIO_STT_ENGINE"] = ""
os.environ["ENABLE_IMAGE_GENERATION"] = "false"
os.environ["ENABLE_COMMUNITY_FUNCTIONS"] = "false"
os.environ["USER_AGENT"] = "OpenWebUI-Offline/1.0"
# --- Ollama off (обожглись) ---
os.environ["ENABLE_OLLAMA_API"] = "false"
os.environ["OLLAMA_BASE_URL"] = ""
# --- Веб-поиск: оставляем (DuckDuckGo без ключа) ---
os.environ["ENABLE_RAG_WEB_SEARCH"] = "true"
os.environ["ENABLE_WEB_SEARCH"] = "true"
os.environ["WEB_SEARCH_ENGINE"] = "duckduckgo"
# --- Подключение к llama-server ---
os.environ["OPENAI_API_BASE_URL"] = os.environ.get(
"OPENAI_API_BASE_URL", "http://127.0.0.1:10000/v1")
os.environ["BYPASS_MODEL_ACCESS_CONTROL"] = "True"
# --- Директория данных ---
os.environ["DATA_DIR"] = DATA_DIR
# --- Запуск ---
venv_bin = os.path.dirname(sys.executable)
open_webui_cmd = os.path.join(venv_bin, "open-webui")
if not os.path.exists(open_webui_cmd):
print(f"Ошибка: не найден {open_webui_cmd}. Установлен ли open-webui в venv?",
file=sys.stderr)
sys.exit(1)
webui_port = os.environ.get("WEBUI_PORT", "8080")
webui_host = os.environ.get("WEBUI_HOST", "0.0.0.0")
cmd = [open_webui_cmd, "serve", "--host", webui_host, "--port", webui_port]
print(f"[*] DATA_DIR: {DATA_DIR}")
print(f"[*] OPENAI_BASE: {os.environ['OPENAI_API_BASE_URL']}")
print(f"[*] Ollama: отключён")
print(f"[*] Web search: DuckDuckGo")
print(f"[*] Команда: {' '.join(cmd)}\n")
try:
subprocess.run(cmd, check=True)
except KeyboardInterrupt:
print("\n[*] Open WebUI остановлен.")
except subprocess.CalledProcessError as e:
print(f"[!] Ошибка запуска Open WebUI: {e}", file=sys.stderr)
sys.exit(1)
Проверка после запуска
1. llama-server отвечает:
curl -sf http://127.0.0.1:10000/health && echo OK
2. Модели видны в API (и, значит, в WebUI):
curl -s http://127.0.0.1:10000/v1/models | jq '.data[].id'
Должен вывести список всех .gguf из models/.
3. WebUI отвечает:
curl -sf http://127.0.0.1:8080/ > /dev/null && echo OK
4. Память обоих процессов:
ps -o pid,comm,rss,%mem -C llama-server,python3
llama-server ~2–3 ГБ реально (RSS может быть больше из-за mmap), Python (WebUI) ~300–500 МБ.
5. Логи при ошибке:
# Если run_webui.sh упал, он покажет хвосты автоматически.
# Ручная диагностика:
journalctl --user -u llama-server-* --since "5 min ago" 2>/dev/null
Подводные камни
WEBUI_SECRET_KEYбез файла — все пользователи разлогиниваются при перезапуске. Решение:.secrets+init_secrets.sh(уже сделано).Ollama «стучится» по умолчанию — ошибки в логах, иногда «залипание». Решение:
ENABLE_OLLAMA_API=false(уже сделано).Регистрация — если случайно поставить
ENABLE_SIGNUP=falseдо первого пользователя, войти будет невозможно. Решение: не задаватьENABLE_SIGNUPвообще.Порт 8080 vs 8888 —
run_webui.shиспользует8080,run_sd_media.sh—8888. Если запустить оба одновременно, конфликта не будет. Ноrun_llama_mmap.shсам по себе (безrun_webui.sh) использует8080— если вы запускаете его отдельно и одновременно с WebUI, будет конфликт. Решение:run_llama_mmap.shотдельно — на8080, а с WebUI — на10000(это уже настроено вrun_webui.sh). Если ранее запускали и тестировалиrun_llama_mmap.shна родном UI, а потом запустили WebUI - может не отобразиться интерфейс (нужно очистить кэш браузера и локальные бд браузера на этой странице).mmapиmemory.pressure— при работе WebUI + llama под нагрузкой следите заmemory.currentчерез cgroup (как в части 2). ЕслиMEM_MAX=10Gмало для выбранной модели — уменьшитеCTX_SIZEили поднимите лимит.
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.