Перейти к содержанию

Open WebUI API наружу для Perplexity и alatyr-service

Публичный API-endpoint для интеграции Perplexity Computer и server-side помощников alatyr-service с локальным Open WebUI на ragserver.

Внедрено 09.08.2026, маршрут обновлён 24.08.2026. Внешний тест из Perplexity Computer: HTTP 200, коллекция alatyr-infra-kb, 45 файлов, write_access=false. После переоформления peer адрес изменён на 10.8.1.12.

Фактическая реализация 09.08.2026

  • DNS: rag-api.alatyr-service.ru A 46.17.99.183; AAAA отсутствует.
  • TLS: Let's Encrypt, сертификат действует до 07.11.2026, автообновление Certbot включено.
  • Ragserver: Open WebUI опубликован одновременно на 127.0.0.1:3000 и 10.8.1.12:3000.
  • Маршрут VPS: host не имеет интерфейса 10.8.1.x, потому трафик идёт через Docker-контейнер amnezia-awg2 (172.29.172.2) и bridge amn0.
  • Постоянство маршрута: /usr/local/sbin/alatyr-rag-api-route.sh + enabled oneshot unit /etc/systemd/system/alatyr-rag-api-route.service.
  • SNAT внутри контейнера: только 172.29.172.1/32 → 10.8.1.12/32, выход awg0, MASQUERADE.
  • nginx: /etc/nginx/sites-available/rag-api.alatyr-service.ru, наружу проксируется только location ^~ /api/; /healthz возвращает 200 без auth, / возвращает 404.
  • Auth: X-Edge-Auth проверяется nginx; Authorization: Bearer ... проверяется Open WebUI.
  • Open WebUI: отдельный обычный пользователь perplexity-computer, группа API Integrations, API Keys permission и read-only grant к knowledge alatyr-infra-kb.
  • Perplexity custom credential: тип headers, в единственное поле Headers передаётся один JSON-объект с обоими заголовками. Отдельные поля или строки не работают.
{
  "Authorization": "Bearer <OpenWebUI API key>",
  "X-Edge-Auth": "<Edge token>"
}

Секреты хранятся в Vaultwarden и Perplexity Credentials, в Git их нет.

Интеграция object assistant в alatyr-service

На 27.08.2026 PR alatyr-service #95 развёрнут в production, merge-коммит b0f97d1. Это второй изолированный клиент API наряду с perplexity-computer.

Текущий контур

  • service user Open WebUI: alatyr-service-assistant;
  • группа: API Integrations;
  • API Keys permission выдаётся группе;
  • доступ к модели должен быть ограничен private model wrapper, целевой Model ID alatyr-object-assistant, базовая локальная модель llama3.2:3b;
  • общая knowledge alatyr-infra-kb к object assistant не прикладывается;
  • CRM-контекст формируется на VPS для одного объекта и передаётся отдельным недоверенным user message;
  • ответы сохраняются в object_assistant_messages;
  • assistant работает read-only и не меняет CRM;
  • контрольный запрос после настройки пока не выполнялся по решению владельца.

Наличие private wrapper, его точный Model ID и grant service user должны быть подтверждены в рамках CRM-15 до признания интеграции полностью проверенной.

Runtime-конфигурация

Systemd-unit alatyr-service подключает:

/etc/alatyr-service.env

Там заданы только server-side переменные:

RAG_API_BASE_URL
RAG_API_MODEL
RAG_API_OPENWEBUI_TOKEN
RAG_API_EDGE_TOKEN
RAG_API_TIMEOUT_MS

Имена переменных подтверждены в окружении запущенного процесса. Значения токенов не выводятся и не фиксируются в KB. Для service user используется отдельная запись Vaultwarden, рекомендуемое имя: OpenWebUI API · alatyr-service.

Исторические документы могли называть edge-секрет RAG_API_EDGE_BEARER; фактический код object assistant ожидает RAG_API_EDGE_TOKEN.

Граница доверия

  • браузер не получает токены и не обращается к Open WebUI напрямую;
  • alatyr-service отправляет запрос только по HTTPS;
  • чувствительные response body не попадают в application logs;
  • нет общей коллекции клиентских файлов;
  • содержимое бинарных документов пока не передаётся, доступны только метаданные;
  • будущий OpenAI API подключается только через server-side model router, классификацию данных, лимиты и audit.

Полная дорожная карта ролей, object-scoped RAG, OCR, сметчика, делопроизводителя, секретаря и управляемых действий: rag-openai-business-assistants-roadmap.md.

Задача

Дать Perplexity Computer возможность вызывать Open WebUI API (RAG-поиск по KB, чат с локальными LLM) — но так, чтобы:

  • Реальный ragserver оставался в LAN, недоступен из интернета напрямую
  • Публичный endpoint был на VPS (46.17.99.183) — на неё DPI Инетком не влияет
  • Трафик VPS ↔ ragserver шёл через существующий AmneziaWG VPN
  • Аутентификация — Bearer token для Perplexity как отдельного клиента (не тот же токен что для rag-sync)
  • Только явные изолированные service clients (perplexity-computer и alatyr-service-assistant) — не выставляем API всему миру и не используем admin-токен

Архитектура

Perplexity Computer
      │ HTTPS + Bearer sk-perplexity-...
┌──────────────────────────────────────┐
│  VPS 46.17.99.183 (публичный)         │
│  nginx: rag-api.alatyr-service.ru     │
│  → auth (Bearer check)                │
│  → rate limit                         │
│  → proxy_pass                         │
└──────────┬────────────────────────────┘
           │ через AmneziaWG (10.8.1.0/24)
           │ VPS host → amn0 → amnezia-awg2 → awg0 → ragserver: 10.8.1.12
┌──────────────────────────────────────┐
│  ragserver (192.168.1.200)            │
│  Open WebUI :3000                     │
│  → validates Bearer token             │
│  → выполняет запрос                   │
└──────────────────────────────────────┘

Ключевые идеи:

  1. Два слоя auth: nginx на VPS проверяет свой Bearer (защита от случайного трафика), Open WebUI проверяет свой Bearer (боевой контроль доступа). Токены разные.
  2. Отдельный поддомен rag-api.alatyr-service.ru — чтобы не смешивать с rag.alatyr-service.ru (интерфейс, только через VPN).
  3. DNS-запись rag-api резолвится в публичный IP VPS (46.17.99.183), не в LAN.

Шаг 1 — Проверить связность VPS ↔ ragserver через VPN

На VPS (историческая исходная проверка; прямого маршрута с host изначально нет):

ping -c 3 10.8.1.12

Если не пингуется, сначала проверять доступ из контейнера amnezia-awg2. В фактической схеме VPS-host использует отдельный /32 route и SNAT, описанные выше. - Проверить sudo awg show awg0 — есть ли peer awg-rag - Проверить sudo systemctl status amneziawg на VPS - Проверить что на ragserver AmneziaWG-клиент активен: sudo awg show

Проверить Open WebUI слышит на LAN-адресе (не только localhost):

На ragserver:

sudo ss -tlnp | grep :3000

Ожидание: LISTEN 0 511 *:3000 или 0.0.0.0:3000. Если 127.0.0.1:3000 — docker-контейнер слушает только на loopback, надо править docker-compose.yml.

Из VPS проверить что API отвечает:

curl -v http://10.8.1.12:3000/api/config 2>&1 | head -20

Если 200 OK → идём дальше. Если connection refused — Open WebUI слушает только 127.0.0.1, надо открыть на 10.8.1.12.

Как открыть OpenWebUI на VPN-IP (если сейчас только localhost)

На ragserver отредактировать /opt/rag-platform/docker-compose.yml, сервис openwebui:

services:
  openwebui:
    ports:
      # было: "127.0.0.1:3000:8080"
      # стало (слушать и на loopback для nginx, и на VPN):
      - "127.0.0.1:3000:8080"      # для локального nginx (интерфейс)
      - "10.8.1.12:3000:8080"      # для VPS через VPN

Или если проще — публиковать на все интерфейсы:

    ports:
      - "3000:8080"

Но тогда обязательно: sudo ufw deny 3000 наружу, sudo ufw allow from 10.8.1.0/24 to any port 3000.

Применить: sudo docker compose -f /opt/rag-platform/docker-compose.yml up -d openwebui.


Шаг 2 — Создать API-токен в Open WebUI

В интерфейсе (фактический безопасный вариант): 1. Администратором создать обычного пользователя perplexity-computer. 2. Создать группу API Integrations, включить только permission API Keys, добавить пользователя. 3. Войти пользователем → Settings → Account → API Keys → создать ключ. 4. Администратором открыть knowledge alatyr-infra-kbAccess → выдать группе/пользователю только Read. 5. Положить API key в Vaultwarden. Не использовать API key администратора и не ротировать ключ rag-sync.

Запись в Vaultwarden

Поле Значение
Name OpenWebUI API · perplexity
Folder Alatyr / Integrations
Username perplexity
Password sk-... (сам токен)
URI 1 https://rag-api.alatyr-service.ru/

Custom fields:

Field Value
client perplexity-computer
owner_user admin (кто в Open WebUI создал)
permissions read: knowledge, chat
created_at 2026-08-XX
rotate_after 2027-02-XX (6 мес)

Notes:

Токен для Perplexity Computer → Open WebUI API.

Используется в custom-credential Perplexity:
  Host: rag-api.alatyr-service.ru
  Header: Authorization: Bearer <этот токен>

Через nginx VPS проксируется на ragserver 10.8.1.12:3000.
nginx имеет собственный Bearer для внешней защиты — см. отдельную запись 
`OpenWebUI Edge Bearer · perplexity`.

При ротации:
1. Создать новый ключ в Open WebUI → Settings → Account → API Keys
2. Обновить эту запись
3. Обновить custom-credential в Perplexity
4. Удалить старый ключ в Open WebUI


Шаг 3 — Создать Edge Bearer (второй слой на nginx VPS)

Это токен которым Perplexity подписывается на nginx VPS. nginx его проверит и только тогда передаст запрос дальше.

На VPS сгенерировать случайную строку:

openssl rand -base64 48 | tr -d '\n' | tr '+/' '-_'

Результат — что-то вроде sk-edge-8xJKn2p_... (64 символа, url-safe).

Запись в Vaultwarden

Поле Значение
Name OpenWebUI Edge Bearer · perplexity
Folder Alatyr / Integrations
Username perplexity-edge
Password сгенерированная строка
URI 1 https://rag-api.alatyr-service.ru/

Custom fields:

Field Value
env_var_name RAG_API_EDGE_BEARER
nginx_config /etc/nginx/sites-enabled/rag-api.alatyr-service.ru
created_at 2026-08-XX
rotate_after 2027-02-XX

Notes:

Edge-Bearer для nginx на VPS 46.17.99.183.

nginx проверяет header `X-Edge-Auth: <этот токен>` до того как проксировать
запрос на ragserver. Это защита от:
- Случайных сканов интернета
- Утечки основного OpenWebUI-токена (нужны оба)

Perplexity должен слать ДВА header:
  Authorization: Bearer <sk-... из записи OpenWebUI API · perplexity>
  X-Edge-Auth: <этот токен>


Шаг 4 — DNS-запись для поддомена

В панели Reg.ru (управление доменом alatyr-service.ru):

Добавить A-запись: - Тип: A - Имя: rag-api (полный: rag-api.alatyr-service.ru) - Значение: 46.17.99.183 - TTL: 3600

Проверить резолв:

dig +short rag-api.alatyr-service.ru
# Ожидание: 46.17.99.183

Пропагация: до 15 минут.


Шаг 5 — Let's Encrypt сертификат

На VPS:

sudo certbot certonly --nginx -d rag-api.alatyr-service.ru

Certbot использует HTTP challenge — порт 80 на VPS уже открыт для существующих доменов, ничего дополнительно не нужно.

Проверить:

sudo ls -la /etc/letsencrypt/live/rag-api.alatyr-service.ru/

Ожидание: fullchain.pem и privkey.pem появились. Автообновление через certbot.timer подхватит новый домен автоматически.


Шаг 6 — nginx-конфиг на VPS

Создать /etc/nginx/sites-available/rag-api.alatyr-service.ru:

# ═══════════════════════════════════════════════════════════════════
# Public API endpoint для Perplexity Computer → Open WebUI на ragserver
# 
# Задача 36. Роутинг:
#   Perplexity → HTTPS → VPS nginx → AmneziaWG туннель → ragserver:3000
# 
# Два слоя auth:
#   1. nginx: X-Edge-Auth header (Edge Bearer)
#   2. Open WebUI: Authorization: Bearer (нативный API-токен)
# ═══════════════════════════════════════════════════════════════════

# ─── Rate limit zone (отдельная для API) ───
limit_req_zone $binary_remote_addr zone=rag_api:10m rate=30r/m;

# ─── HTTP → HTTPS редирект ───
server {
    listen 80;
    listen [::]:80;
    server_name rag-api.alatyr-service.ru;

    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

# ─── HTTPS основной ───
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name rag-api.alatyr-service.ru;

    # ═══ TLS ═══
    ssl_certificate     /etc/letsencrypt/live/rag-api.alatyr-service.ru/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/rag-api.alatyr-service.ru/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers off;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;

    # ═══ Security headers ═══
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-Frame-Options "DENY" always;
    add_header Referrer-Policy "no-referrer" always;
    add_header X-Robots-Tag "noindex, nofollow" always;

    # ═══ Логи ═══
    access_log /var/log/nginx/rag-api-access.log;
    error_log  /var/log/nginx/rag-api-error.log warn;

    # ═══ Body size (для upload файлов в KB через API, если понадобится) ═══
    client_max_body_size 50M;
    client_body_timeout 300s;

    # ═══ Rate limit ═══
    limit_req zone=rag_api burst=10 nodelay;
    limit_req_status 429;

    # ═══ Edge auth check ═══
    # Проверяем header X-Edge-Auth. Если не совпадает — 401.
    # Значение подставится через $RAG_API_EDGE_BEARER (см. Шаг 7).
    set $edge_bearer "REPLACE_ME_EDGE_BEARER";

    if ($http_x_edge_auth != $edge_bearer) {
        return 401 '{"error":"missing or invalid X-Edge-Auth"}\n';
    }

    # ═══ Health check (без auth — для мониторинга) ═══
    location = /healthz {
        access_log off;
        return 200 '{"status":"ok"}\n';
        add_header Content-Type application/json;
    }

    # ═══ Proxy на Open WebUI через VPN ═══
    location / {
        # Через AmneziaWG-туннель на ragserver
        proxy_pass http://10.8.1.12:3000;
        proxy_http_version 1.1;

        # WebSocket upgrade (для streaming LLM ответов)
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        # Стандартные proxy headers
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;

        # Пробрасываем Authorization как есть — Open WebUI проверит свой Bearer
        proxy_set_header Authorization $http_authorization;

        # НЕ пробрасываем X-Edge-Auth дальше (внутри не нужен)
        proxy_set_header X-Edge-Auth "";

        # Таймауты (LLM может генерить долго)
        proxy_connect_timeout 10s;
        proxy_send_timeout 300s;
        proxy_read_timeout 300s;

        # Отключаем буферизацию — стриминг ответов
        proxy_buffering off;
        proxy_cache off;
    }
}

Обратить внимание: - REPLACE_ME_EDGE_BEARER — заменить на реальное значение из Vaultwarden OpenWebUI Edge Bearer · perplexity до nginx -t - Rate limit 30 запросов/минуту с burst 10 — консервативно, потом подкрутим по факту - WebSocket-заголовки — обязательны, иначе стриминг LLM отвалится


Шаг 7 — Активация nginx-конфига

# Подставить edge bearer
sudo nano /etc/nginx/sites-available/rag-api.alatyr-service.ru
# → заменить REPLACE_ME_EDGE_BEARER на реальное значение из Vaultwarden

# Symlink
sudo ln -s /etc/nginx/sites-available/rag-api.alatyr-service.ru \
           /etc/nginx/sites-enabled/rag-api.alatyr-service.ru

# Проверка синтаксиса
sudo nginx -t

# Reload
sudo systemctl reload nginx

Если nginx -t ругается — читай ошибку, скорее всего опечатка в имени файла сертификата.


Шаг 8 — Проверка вручную с VPS

Проверка что endpoint жив (без auth):

curl -s https://rag-api.alatyr-service.ru/healthz
# Ожидание: {"status":"ok"}

Проверка что без Edge Bearer отбивает:

curl -s -w "\n%{http_code}\n" https://rag-api.alatyr-service.ru/api/config
# Ожидание: 401 {"error":"missing or invalid X-Edge-Auth"}

Проверка что с Edge Bearer но без OpenWebUI Bearer отбивает Open WebUI:

curl -s -w "\n%{http_code}\n" \
  -H "X-Edge-Auth: <edge_bearer из Vaultwarden>" \
  https://rag-api.alatyr-service.ru/api/config
# Ожидание: 401 от самого Open WebUI (или 200 если endpoint публичный — /api/config иногда открыт)

Проверка с обоими токенами:

curl -s \
  -H "X-Edge-Auth: <edge_bearer>" \
  -H "Authorization: Bearer <openwebui_token>" \
  https://rag-api.alatyr-service.ru/api/v1/knowledge/ | head -50
# Ожидание: JSON со списком knowledge collections


Шаг 9 — Настройка custom-credential в Perplexity

В Perplexity Computer: 1. Открыть Settings → Connectors → Custom Credentials 2. + Add credential 3. Host: rag-api.alatyr-service.ru 4. Headers: - Authorization: Bearer <sk-... из Vaultwarden запись OpenWebUI API · perplexity> - X-Edge-Auth: <edge bearer из Vaultwarden> 5. Save

Тест из Perplexity (после сохранения credential):

Спросить в чате: "фетчни https://rag-api.alatyr-service.ru/api/v1/knowledge/ и покажи список коллекций"

Должен вернуть JSON со всеми knowledge collections твоего Open WebUI.


Шаг 10 — Мониторинг и алерты

Логи в VPS:

sudo tail -f /var/log/nginx/rag-api-access.log
sudo tail -f /var/log/nginx/rag-api-error.log

Полезные grep-фильтры:

# Все 401 (попытки без auth или с неправильным auth)
grep " 401 " /var/log/nginx/rag-api-access.log

# Все 429 (rate limit)
grep " 429 " /var/log/nginx/rag-api-access.log

# Медленные запросы (>10 сек)
awk '$NF > 10' /var/log/nginx/rag-api-access.log

fail2ban jail для rag-api (задача-follow-up): Если словишь брутфорс — добавить в /etc/fail2ban/jail.d/rag-api.conf:

[nginx-rag-api-401]
enabled = true
port = https
filter = nginx-rag-api-401
logpath = /var/log/nginx/rag-api-access.log
maxretry = 5
findtime = 5m
bantime = 1h


Troubleshooting

502 Bad Gateway

  • ragserver недоступен через VPN. Проверить: ping 10.8.1.12 с VPS.
  • На ragserver проверить: sudo systemctl status awg-quick@awg-rag и sudo awg show awg-rag.
  • На VPS проверить peer: sudo docker exec amnezia-awg2 awg show awg0 и наличие 10.8.1.12/32 в allowed ips.

504 Gateway Timeout при генерации ответа LLM

  • Ollama долго генерит. Увеличить proxy_read_timeout 300s600s.
  • Проверить что proxy_buffering off — иначе стрим не работает

401 с валидным Bearer

  • Проверить header не сломался: некоторые прокси едят Authorization. В логах nginx смотреть переменную $http_authorization.
  • Проверить что Open WebUI видит header — временно add_header X-Debug-Auth "$http_authorization" always; (потом убрать).

WebSocket disconnect при streaming

  • Убедиться что есть proxy_set_header Upgrade и Connection "upgrade" в location /
  • proxy_http_version 1.1 обязательно

rate limit срабатывает при обычной работе

  • В логах: limiting requests, excess: 10.500 by zone "rag_api"
  • Увеличить rate=30r/mrate=60r/m в limit_req_zone

Что дальше

  • Мониторинг Uptime Kuma (задача 28) — добавить monitor на https://rag-api.alatyr-service.ru/healthz
  • fail2ban jail — если начнёт долбить кто-то извне
  • ротация токенов — через 6 месяцев (по данным Vaultwarden rotate_after)
  • аудит логов раз в неделю — сколько 401/429 накапало, кто пытался ломиться

Связанные файлы