The Jerusalem PostEgypt approves military support for Saudi Arabia after Houthi attacks escalatePunchPolice arrest two over deaths of minors in BauchiInquirerUndas 2026: 4,790 police personnel to be deployed in SoccsksargenESPNThunder star Williams stretchered off after suffering significant leg injuryInquirer EntertainmentAndrea Brillantes bares diving mishap while shooting ‘Laya’ESPN DeportesReal Madrid, a 3 puntos del Barça: así va la tabla de LaLigaCollider'Deadpool's Director Is Presiding Over the Biggest Short Film Contest Ever [Exclusive]ZDF heuteEntdecken Sie das ZDF-NachrichtenstudioAnime News NetworkRomelia War Chronicle ‒ Episode 4BBC NewsHow a movement of students brought down Serbia's VučićMintBrazil election 2026 results: What’s the net worth of Lula Da Silva as he gets re-elected as president for fourth term?Digital SpyUniversity Challenge contestants hit out at "absurd" BBC decision to edit out pride badges
The Daily Newsstand · Free, Always
Monday, October 26, 2026

Руководство по сборке локального AI-стека. Часть 5. Open WebUI поверх llama.cpp

Translate

На этом шаге мы добавляем 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 — файл с двумя ключами:

Ключ

Для чего

Кто использует

LLAMA_API_KEY

Аутентификация запросов к llama-server

Open WebUI (передаёт в заголовке Authorization: Bearer ...), внешние клиенты

WEBUI_SECRET_KEY

Подпись 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), чтобы не конфликтовать с родным UI llama.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

Скрипт делает пять вещей:

  1. Проверяет окружение — venv, open-webui, .secrets, run_llama_mmap.sh, start_webui.py. Если чего-то нет — создаёт/устанавливает (первый запуск).

  2. Проверяет порты 10000 и 8080 — если заняты, выходит с ошибкой.

  3. Опционально настраивает статический IP (через nmcli), если пользователь ответит 1. Полезно, чтобы адрес сервера не менялся после перезагрузки роутера.

  4. Запускает llama-server в фоне на 127.0.0.1:10000 через PORT=10000 HOST=127.0.0.1 ./run_llama_mmap.sh. Ждёт /health до 5 минут.

  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

Первый вход:

  1. Откройте http://<IP>:8080 в браузере.

  2. Нажмите «Sign up» — создайте аккаунт. Первый пользователь = админ.

  3. После создания аккаунта регистрация закрывается автоматически.

  4. В дропдауне моделей — список всех .gguf из models/. Выберите нужную.

  5. Пишите в чат. История сохраняется автоматически.

Для добавления пользователя: зайдите как админ -> 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

Подводные камни

  1. WEBUI_SECRET_KEY без файла — все пользователи разлогиниваются при перезапуске. Решение: .secrets + init_secrets.sh (уже сделано).

  2. Ollama «стучится» по умолчанию — ошибки в логах, иногда «залипание». Решение: ENABLE_OLLAMA_API=false (уже сделано).

  3. Регистрация — если случайно поставить ENABLE_SIGNUP=false до первого пользователя, войти будет невозможно. Решение: не задавать ENABLE_SIGNUP вообще.

  4. Порт 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 - может не отобразиться интерфейс (нужно очистить кэш браузера и локальные бд браузера на этой странице).

  5. mmap и memory.pressure — при работе WebUI + llama под нагрузкой следите за memory.current через cgroup (как в части 2). Если MEM_MAX=10G мало для выбранной модели — уменьшите CTX_SIZE или поднимите лимит.

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.