Add project and deployment instruction (docs/DEPLOYMENT.md)

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
ars
2026-02-19 18:12:09 +00:00
commit 53c572ef46
94 changed files with 9200 additions and 0 deletions

36
.env.example Normal file
View File

@@ -0,0 +1,36 @@
# === ДОМЕНЫ И СЕТЬ ===
DOMAIN_OPENWEBUI=https://odo.iieasy.ru
DOMAIN_NEXTCLOUD=https://next.iieasy.ru
DOMAIN_AUTHENTIK=https://auth.iieasy.ru
DOMAIN_VAULTWARDEN=http://192.168.88.165:8082
# === AUTHENTIK (OIDC SSO) ===
# Где брать: Authentik -> Providers -> твой OIDC Provider
OAUTH_CLIENT_ID=your_oauth_client_id
OAUTH_CLIENT_SECRET=your_oauth_client_secret
OPENID_CONNECT_ISSUER=https://auth.iieasy.ru/application/o/open-webui/
# === NEXTCLOUD (Для Python-воркера) ===
# Где брать: Nextcloud -> Настройки -> Безопасность -> Устройства и сессии (Создать пароль приложения)
NC_USER=your_nextcloud_username
NC_APP_PASSWORD=your_app_password
# === OPEN WEBUI API (Для пуша файлов в Qdrant) ===
# Где брать: Open WebUI -> Settings -> Account -> API Keys (создашь после первого запуска)
OPENWEBUI_API_KEY=your_api_key_here
# === VAULTWARDEN (Для интеграции Bitwarden CLI) ===
# Где брать: Vaultwarden -> Настройки аккаунта -> Безопасность -> Ключи -> API-ключ
BW_CLIENTID=your_vaultwarden_client_id
BW_CLIENTSECRET=your_vaultwarden_client_secret
# === QDRANT ===
# Сгенерировать случайный ключ: openssl rand -hex 32
QDRANT_API_KEY=your_qdrant_api_key_here
# === SEARXNG ===
SEARXNG_HOSTNAME=searxng:8080
# === OLLAMA ===
OLLAMA_MODEL=gemma3n:e4b-it-fp16
NVIDIA_VISIBLE_DEVICES=all

65
.gitignore vendored Normal file
View File

@@ -0,0 +1,65 @@
# Переменные окружения
.env
.env.local
.env.*.local
# Docker volumes и данные
volumes/
*.db
*.sqlite
*.sqlite3
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# Виртуальные окружения
venv/
env/
ENV/
.venv
# Логи
*.log
logs/
sync.log
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# OS
.DS_Store
Thumbs.db
# Временные файлы
*.tmp
*.bak
*.cache
# Медиа файлы (если не нужны в репозитории)
# Раскомментируйте если не хотите хранить медиа в git
# media/*.png
# media/*.jpg
# media/*.ico

54
APPLY_AUTHENTIK_FIX.sh Executable file
View File

@@ -0,0 +1,54 @@
#!/bin/bash
# Скрипт для применения исправления Authentik
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$SCRIPT_DIR"
echo "=== Применение исправления Authentik ==="
echo "Рабочая директория: $PROJECT_DIR"
cd "$PROJECT_DIR"
# Проверка наличия .env файла
if [ ! -f ".env" ]; then
echo "✗ Ошибка: .env файл не найден в $PROJECT_DIR"
echo "Создайте .env из .env.example или проверьте путь"
exit 1
fi
# Проверка правильности .env
if grep -q "ii-easy-web" .env; then
echo "✓ .env содержит правильный slug: ii-easy-web"
else
echo "⚠ Предупреждение: .env не содержит slug 'ii-easy-web'"
echo "Проверьте значение OPENID_CONNECT_ISSUER в .env"
fi
# Перезапуск контейнера для применения изменений
echo "Перезапуск контейнера open-webui..."
docker compose restart open-webui
echo "Ожидание запуска (15 секунд)..."
sleep 15
# Проверка доступности
if curl -f http://localhost:3001/health >/dev/null 2>&1; then
echo "✓ Open WebUI запущен и отвечает"
else
echo "⚠ Open WebUI может еще запускаться, проверьте логи:"
echo " docker compose logs open-webui --tail 30"
fi
echo ""
echo "=== Готово! ==="
echo ""
echo "Проверьте вход через Authentik:"
echo " 1. Откройте https://odo.iieasy.ru"
echo " 2. Попробуйте войти через 'iiEasy ID' (Authentik)"
echo ""
echo "Если все еще Internal Server Error:"
echo " 1. Проверьте redirect URI в Authentik: https://odo.iieasy.ru/oauth/oidc/callback"
echo " 2. Проверьте логи: docker compose logs open-webui | grep -i oauth"
echo " 3. Временно используйте форму входа (уже включена)"

65
APPLY_OAUTH_FIX.sh Executable file
View File

@@ -0,0 +1,65 @@
#!/bin/bash
# Скрипт для применения исправления OAuth endpoint
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$SCRIPT_DIR"
cd "$PROJECT_DIR"
echo "=== Применение исправления OAuth endpoint ==="
echo ""
# Проверка .env файла
if [ ! -f ".env" ]; then
echo "✗ Ошибка: .env файл не найден"
exit 1
fi
# Проверка правильного slug
if grep -q "OPENID_CONNECT_ISSUER=https://auth.iieasy.ru/application/o/open-webui/" .env; then
echo "✓ .env содержит правильный slug: open-webui"
else
echo "✗ Ошибка: .env не содержит правильный slug"
echo " Должно быть: OPENID_CONNECT_ISSUER=https://auth.iieasy.ru/application/o/open-webui/"
exit 1
fi
echo ""
echo "1. Перезапуск контейнера open-webui..."
sudo docker compose restart open-webui
echo ""
echo "2. Ожидание запуска контейнера (20 секунд)..."
sleep 20
echo ""
echo "3. Проверка статуса контейнера..."
if sudo docker compose ps open-webui | grep -q "Up"; then
echo "✓ Контейнер запущен"
else
echo "✗ Контейнер не запустился. Проверьте логи:"
echo " sudo docker compose logs open-webui"
exit 1
fi
echo ""
echo "4. Проверка логов на ошибки OAuth..."
OAUTH_ERRORS=$(sudo docker compose logs open-webui --tail 50 2>&1 | grep -i "oauth\|oidc\|404\|error" | tail -5 || true)
if [ -z "$OAUTH_ERRORS" ]; then
echo "✓ Ошибок OAuth не найдено"
else
echo "⚠ Найдены ошибки в логах:"
echo "$OAUTH_ERRORS"
fi
echo ""
echo "=== Готово! ==="
echo ""
echo "Проверьте OAuth:"
echo "1. Откройте https://odo.iieasy.ru"
echo "2. Нажмите кнопку 'iiEasy ID' для входа через OAuth"
echo ""
echo "Если есть проблемы, проверьте логи:"
echo " sudo docker compose logs open-webui --tail 100 | grep -i oauth"

94
AUTHENTIK_FIX.md Normal file
View File

@@ -0,0 +1,94 @@
# Быстрое решение проблемы с Authentik
## Проблема: Internal Server Error при входе через Authentik
Endpoint `https://auth.iieasy.ru/application/o/open-webui/.well-known/openid-configuration` возвращает 404 Not Found.
## Решение 1: Временное включение формы входа (работает сейчас)
Форма входа уже включена в `docker-compose.yml`. Перезапустите контейнер:
```bash
cd /home/its/iiEasyWeb
sudo docker compose restart open-webui
```
Теперь вы сможете войти через форму входа на странице `https://odo.iieasy.ru`.
## Решение 2: Правильная настройка Authentik
### Проверка в Authentik
1. Войдите в Authentik: `https://auth.iieasy.ru`
2. Перейдите в **Applications**
3. Найдите Application для Open WebUI
4. Проверьте **Slug** - он должен быть `open-webui`
### Если Application не существует
1. **Providers****Add Provider**
- Тип: **OpenID Connect / OAuth2 / OAuth2 with OpenID Connect**
- Название: `Open WebUI`
- Сохраните и запомните **Client ID** и **Client Secret**
2. **Applications****Add Application**
- **Name**: `Open WebUI`
- **Slug**: `open-webui` (ВАЖНО! Должен совпадать с URL)
- **Provider**: Выберите созданный Provider
- **Redirect URIs**: `https://odo.iieasy.ru/oauth/oidc/callback`
- Сохраните
3. **Обновите .env:**
```bash
OPENID_CONNECT_ISSUER=https://auth.iieasy.ru/application/o/open-webui/
```
Где `open-webui` - это slug из Application.
4. **Проверьте endpoint:**
```bash
curl https://auth.iieasy.ru/application/o/open-webui/.well-known/openid-configuration
```
Должен вернуться JSON, а не HTML.
5. **Перезапустите контейнер:**
```bash
sudo docker compose restart open-webui
```
### Если Application существует, но slug другой
Если slug в Authentik не `open-webui`, а например `openwebui` или `webui`:
1. Обновите `.env`:
```bash
OPENID_CONNECT_ISSUER=https://auth.iieasy.ru/application/o/ВАШ_SLUG/
```
2. Или измените slug в Authentik Application на `open-webui`
## Решение 3: Отключение Authentik (если не нужен)
Если Authentik не нужен, отключите его:
В `docker-compose.yml` измените:
```yaml
- ENABLE_OAUTH_SIGNUP=false
- ENABLE_LOGIN_FORM=true
```
И удалите или закомментируйте переменные OAuth.
## Проверка после настройки
1. Проверьте endpoint:
```bash
curl https://auth.iieasy.ru/application/o/open-webui/.well-known/openid-configuration
```
Должен вернуться JSON с `issuer`, `authorization_endpoint` и т.д.
2. Проверьте логи:
```bash
sudo docker compose logs open-webui | grep -i "oidc\|oauth"
```
3. Попробуйте войти через `https://odo.iieasy.ru`

139
AUTHENTIK_SETUP.md Normal file
View File

@@ -0,0 +1,139 @@
# Настройка Authentik для Open WebUI
## Проблема: Internal Server Error при входе через Authentik
### Диагностика
1. **Проверьте доступность Authentik:**
```bash
curl -I https://auth.iieasy.ru
```
2. **Проверьте OpenID endpoint:**
```bash
# Правильный путь для Authentik:
curl https://auth.iieasy.ru/application/o/open-webui/.well-known/openid-configuration
```
3. **Проверьте логи Open WebUI:**
```bash
sudo docker compose logs open-webui | grep -i "auth\|oidc\|oauth\|error"
```
## Правильная настройка Authentik
### Шаг 1: Создание OIDC Provider в Authentik
1. Войдите в Authentik: `https://auth.iieasy.ru`
2. Перейдите в **Providers** → **Add Provider**
3. Выберите **OpenID Connect / OAuth2 / OAuth2 with OpenID Connect**
4. Заполните:
- **Name**: `Open WebUI` (или любое имя)
- **Authorization flow**: Выберите существующий flow
- **Redirect URIs**: `https://odo.iieasy.ru/oauth/oidc/callback`
- **Client type**: `Confidential`
- **Client ID**: Запомните этот ID
- **Client Secret**: Запомните этот секрет
5. Сохраните Provider
### Шаг 2: Создание Application в Authentik
1. Перейдите в **Applications** → **Add Application**
2. Заполните:
- **Name**: `Open WebUI`
- **Slug**: `open-webui` (это важно для URL!)
- **Provider**: Выберите созданный Provider
3. Сохраните Application
### Шаг 3: Проверка правильного URL
После создания Application, правильный URL будет:
```
https://auth.iieasy.ru/application/o/open-webui/.well-known/openid-configuration
```
Где `open-webui` - это slug из Application.
### Шаг 4: Обновление .env
В файле `.env` убедитесь, что:
```bash
OPENID_CONNECT_ISSUER=https://auth.iieasy.ru/application/o/open-webui/
```
**ВАЖНО:**
- URL должен заканчиваться на `/`
- Slug (`open-webui`) должен совпадать с slug в Authentik Application
- После `/application/o/` идет slug, затем `/`
### Шаг 5: Проверка переменных в docker-compose.yml
В `docker-compose.yml` используется:
```yaml
OPENID_PROVIDER_URL=${OPENID_CONNECT_ISSUER}.well-known/openid-configuration
```
Это создаст полный URL:
```
https://auth.iieasy.ru/application/o/open-webui/.well-known/openid-configuration
```
### Шаг 6: Перезапуск контейнера
```bash
cd /home/its/iiEasyWeb
sudo docker compose restart open-webui
```
## Временное решение: Включить форму входа
Если Authentik не работает, временно включите форму входа:
В `docker-compose.yml` измените:
```yaml
- ENABLE_LOGIN_FORM=true # Временно включено
- ENABLE_OAUTH_SIGNUP=false # Временно отключено
```
Затем перезапустите:
```bash
sudo docker compose restart open-webui
```
## Проверка конфигурации
1. **Проверьте, что endpoint доступен:**
```bash
curl https://auth.iieasy.ru/application/o/open-webui/.well-known/openid-configuration
```
Должен вернуться JSON с конфигурацией OpenID Connect.
2. **Проверьте redirect URI в Authentik:**
- Должен быть: `https://odo.iieasy.ru/oauth/oidc/callback`
- Без завершающего слеша
3. **Проверьте логи:**
```bash
sudo docker compose logs open-webui --tail 100 | grep -i "oidc\|oauth"
```
## Частые ошибки
### 404 Not Found на .well-known/openid-configuration
- Проверьте slug в Application (должен быть `open-webui`)
- Проверьте URL в .env (должен заканчиваться на `/`)
- Убедитесь, что Application привязан к Provider
### Invalid redirect URI
- Проверьте redirect URI в Authentik: `https://odo.iieasy.ru/oauth/oidc/callback`
- Убедитесь, что домен правильный (без порта, если используете HTTPS)
### Internal Server Error
- Проверьте логи Open WebUI
- Убедитесь, что Client ID и Client Secret правильные
- Проверьте, что SSL сертификат валидный для auth.iieasy.ru

111
DIAGNOSE_VISION_ISSUE.md Normal file
View File

@@ -0,0 +1,111 @@
# Диагностика проблемы с передачей изображений в Ollama
## Проблема
Open WebUI не передает изображения в Ollama для модели gemma3n:e4b-it-fp16, хотя:
- ✅ Адрес Ollama правильный: `http://ollama:11434`
- ✅ Модель поддерживает vision
- ✅ Изображения загружаются в Open WebUI
-Но не доходят до Ollama (нет запросов с изображениями в логах)
## Возможные причины
### 1. Open WebUI v0.8.3 не распознает gemma3n как vision-модель
Open WebUI может не знать, что gemma3n:e4b-it-fp16 поддерживает vision. Нужно проверить список vision-моделей в коде.
### 2. Модель не помечена как vision в настройках
Возможно, нужно явно указать в настройках Open WebUI, что модель поддерживает vision.
### 3. Проблема с форматом передачи изображений
Open WebUI может передавать изображения в неправильном формате для Ollama API.
## Решения
### Решение 1: Проверка настроек модели в Open WebUI
1. Откройте https://odo.iieasy.ru
2. Перейдите в **Settings → Models**
3. Найдите модель `gemma3n:e4b-it-fp16`
4. Проверьте, есть ли опция "Vision" или "Multimodal"
5. Включите её, если есть
### Решение 2: Обновление Open WebUI
Версия v0.8.3 может иметь проблемы с vision. Попробуйте обновить до последней версии:
```yaml
# В docker-compose.yml измените:
image: ghcr.io/open-webui/open-webui:latest
# или
image: ghcr.io/open-webui/open-webui:v1.x.x
```
Затем:
```bash
docker compose pull open-webui
docker compose up -d open-webui
```
### Решение 3: Проверка через прямой API запрос
Проверьте, работает ли vision напрямую через Ollama API:
```bash
cd /home/its/iiEasyWeb/test_images
IMAGE_B64=$(base64 -w 0 test_image.jpg)
sudo docker exec ollama curl -s -X POST http://localhost:11434/api/generate \
-H 'Content-Type: application/json' \
-d "{
\"model\": \"gemma3n:e4b-it-fp16\",
\"prompt\": \"Опиши это изображение на русском языке\",
\"images\": [\"$IMAGE_B64\"],
\"stream\": false
}" | jq -r '.response'
```
Если это работает, значит проблема в Open WebUI, а не в Ollama.
### Решение 4: Проверка логов при отправке изображения
1. Откройте два терминала
2. В первом терминале:
```bash
sudo docker logs open-webui -f | grep -i "image\|ollama\|generate"
```
3. Во втором терминале:
```bash
sudo docker logs ollama -f | grep -i "generate\|image"
```
4. Отправьте изображение через веб-интерфейс
5. Проверьте, что появляется в логах:
- В Open WebUI должен быть запрос с изображением
- В Ollama должен быть запрос к `/api/generate` с полем `images`
### Решение 5: Проверка версии Open WebUI
```bash
sudo docker exec open-webui cat /app/backend/version.txt
```
Если версия старая, обновите до последней.
## Альтернативное решение: Использование API напрямую
Если Open WebUI не поддерживает vision для gemma3n, можно использовать API напрямую или создать свой клиент.
## Проверка работы vision модели
Убедитесь, что модель действительно поддерживает vision:
```bash
sudo docker exec ollama ollama show gemma3n:e4b-it-fp16
```
В выводе должна быть информация о поддержке vision/multimodal.

View File

@@ -0,0 +1,70 @@
# Исправление ссылок на документацию и удаление "(Open WebUI)"
## Проблемы
1. Текст "(Open WebUI)" все еще виден в "Войти в iiEasyWeb (Open WebUI)"
2. Ссылки на документацию ведут на оригинальный сайт вместо note.iieasy.ru
## Решение
**1. Запустите финальный скрипт ребрендинга:**
```bash
cd /home/its/iiEasyWeb
sudo ./scripts/rebrand_final.sh
```
Этот скрипт:
- ✅ Агрессивно ищет и удаляет "(Open WebUI)" из ВСЕХ файлов
- ✅ Заменяет ссылки на документацию на note.iieasy.ru
- ✅ Проверяет и исправляет базу данных
- ✅ Обрабатывает все варианты написания
**2. Очистите кеш браузера:**
После запуска скрипта обязательно очистите кеш:
- **Chrome/Edge**: Ctrl+Shift+Delete
- Выберите "Изображения и файлы в кеше"
- Очистите кеш
**3. Проверьте через Admin Panel:**
Если текст все еще виден:
1. Откройте `https://odo.iieasy.ru`
2. Войдите как администратор
3. Перейдите в **Settings → Appearance**
4. Проверьте поле **Site Title** - должно быть "iiEasyWeb" без "(Open WebUI)"
5. Если там есть "(Open WebUI)", удалите его вручную и сохраните
**4. Проверьте переменные окружения:**
Убедитесь, что в `docker-compose.yml` правильно настроено:
```yaml
- WEBUI_NAME=iiEasyWeb
```
## Что заменяется
Скрипт заменяет следующие ссылки на документацию:
- `https://docs.openwebui.com``https://note.iieasy.ru`
- `https://open-webui.com/docs``https://note.iieasy.ru`
- `https://github.com/open-webui/docs``https://note.iieasy.ru`
- `docs.openwebui.com``note.iieasy.ru`
## Если проблема осталась
Если после всех действий текст "(Open WebUI)" все еще виден:
1. Выполните поиск вручную:
```bash
sudo docker exec open-webui find /app -type f -exec grep -l "(Open WebUI)" {} \; 2>/dev/null
```
2. Проверьте базу данных напрямую:
```bash
sudo docker exec open-webui sqlite3 /app/backend/data/webui.db "SELECT * FROM settings WHERE value LIKE '%Open WebUI%';"
```
3. Если найдете в базе, удалите вручную через Admin Panel или SQLite

124
FIX_IMAGE_TRANSFER.md Normal file
View File

@@ -0,0 +1,124 @@
# Исправление проблемы с передачей изображений из Open WebUI в Ollama
## Проблема
Open WebUI не передает изображения в Ollama правильно. В логах Ollama нет запросов с изображениями.
## Диагностика
### 1. Проверка переменной окружения
```bash
sudo docker exec open-webui env | grep -i OLLAMA
```
Должно быть:
```
OLLAMA_BASE_URL=http://ollama:11434
```
### 2. Проверка доступности Ollama
```bash
sudo docker exec open-webui curl -s http://ollama:11434/api/tags | head -5
```
### 3. Проверка логов
```bash
# Логи Open WebUI при отправке изображения
sudo docker logs open-webui --tail 100 | grep -i "image\|ollama\|error"
# Логи Ollama - должны быть запросы с изображениями
sudo docker logs ollama --tail 100 | grep -i "image\|vision"
```
## Решение
### Шаг 1: Убедитесь, что переменная добавлена в docker-compose.yml
В файле `/home/its/iiEasyWeb/docker-compose.yml` должна быть строка:
```yaml
# Ollama API для работы с изображениями
- OLLAMA_BASE_URL=http://ollama:11434
```
### Шаг 2: Перезапустите контейнер Open WebUI
```bash
cd /home/its/iiEasyWeb
docker compose restart open-webui
```
Или полностью пересоздайте:
```bash
docker compose up -d --force-recreate open-webui
```
### Шаг 3: Проверьте настройки в веб-интерфейсе
1. Откройте https://odo.iieasy.ru
2. Перейдите в **Settings → Connections → Ollama API**
3. Убедитесь, что адрес: **`http://ollama:11434`**
- НЕ используйте `host.docker.internal:11434`
- НЕ используйте `localhost:11434`
- Должно быть именно `http://ollama:11434`
### Шаг 4: Проверьте формат изображения
Open WebUI должен передавать изображения в формате base64 в поле `images` массива JSON запроса к Ollama API.
Формат запроса должен быть:
```json
{
"model": "gemma3n:e4b-it-fp16",
"prompt": "Опиши это изображение",
"images": ["base64_encoded_image_data"],
"stream": false
}
```
## Дополнительная диагностика
### Запустите скрипт проверки:
```bash
cd /home/its/iiEasyWeb
sudo ./scripts/check_image_transfer.sh
```
### Проверка в реальном времени:
1. Откройте два терминала
2. В первом терминале следите за логами Open WebUI:
```bash
sudo docker logs open-webui -f | grep -i "image\|ollama"
```
3. Во втором терминале следите за логами Ollama:
```bash
sudo docker logs ollama -f | grep -i "image\|vision\|generate"
```
4. Отправьте изображение через веб-интерфейс
5. Проверьте, появляются ли запросы в логах Ollama
## Возможные проблемы
1. **Переменная не применена** - контейнер не перезапущен после изменения docker-compose.yml
2. **Неправильный адрес в настройках** - в веб-интерфейсе указан неправильный адрес Ollama
3. **Проблема с сетью Docker** - контейнеры не могут общаться друг с другом
4. **Формат изображения** - Open WebUI передает изображение в неправильном формате
5. **Версия Open WebUI** - старая версия может не поддерживать vision правильно
## Проверка версии Open WebUI
```bash
sudo docker exec open-webui cat /app/backend/version.txt
```
Текущая версия в docker-compose.yml: `v0.8.3`
## Альтернативное решение
Если проблема не решается, можно попробовать:
1. Обновить Open WebUI до последней версии
2. Использовать прямой API запрос к Ollama для тестирования
3. Проверить документацию Open WebUI по работе с vision моделями

82
FIX_LOGIN_TITLE.md Normal file
View File

@@ -0,0 +1,82 @@
# Исправление текста "Войти в iiEasyWeb (Open WebUI)"
## Проблема
В заголовке страницы входа все еще видно "(Open WebUI)": "Войти в iiEasyWeb (Open WebUI)"
## Решение
**1. Сначала найдите, где находится этот текст:**
```bash
cd /home/its/iiEasyWeb
sudo ./scripts/find_openwebui_text.sh
```
Этот скрипт покажет все файлы, где есть "(Open WebUI)".
**2. Запустите точный скрипт ребрендинга:**
```bash
sudo ./scripts/rebrand_precise.sh
```
**3. Если текст все еще виден, проверьте:**
### Вариант A: Текст в базе данных
Текст может храниться в базе данных Open WebUI. Проверьте через Admin Panel:
1. Откройте `https://odo.iieasy.ru`
2. Войдите как администратор
3. Перейдите в **Settings → Appearance**
4. Проверьте поле **Site Title** - должно быть "iiEasyWeb" без "(Open WebUI)"
5. Сохраните изменения
### Вариант B: Текст в переменных окружения
Проверьте `docker-compose.yml`:
```bash
grep -i "webui_name\|site_title" docker-compose.yml
```
Должно быть:
```yaml
- WEBUI_NAME=iiEasyWeb
```
### Вариант C: Очистка кеша браузера
После изменений очистите кеш браузера:
- Chrome/Edge: Ctrl+Shift+Delete (Cmd+Shift+Delete на Mac)
- Выберите "Изображения и файлы в кеше"
- Очистите кеш
### Вариант D: Пересборка фронтенда
Если текст в скомпилированных файлах, может потребоваться пересборка:
```bash
# Пересоздайте контейнер
sudo docker compose stop open-webui
sudo docker compose rm -f open-webui
sudo docker compose up -d open-webui
# Подождите 30 секунд
sleep 30
# Запустите ребрендинг снова
sudo ./scripts/rebrand_precise.sh
```
## Если ничего не помогает
Выполните поиск вручную:
```bash
# Найдите все файлы с этим текстом
sudo docker exec open-webui find /app -type f -exec grep -l "(Open WebUI)" {} \; 2>/dev/null
# Затем замените вручную в найденных файлах
```

42
FIX_OAUTH_REDIRECT.md Normal file
View File

@@ -0,0 +1,42 @@
# Исправление редиректа на /auth
## Проблема: Редирект на /auth вместо главной страницы
Когда Open WebUI настроен на OAuth и `ENABLE_LOGIN_FORM=false`, но OAuth не работает правильно, происходит редирект на `/auth`.
## Решение
**1. Убедитесь, что форма входа включена (уже сделано):**
В `docker-compose.yml`:
```yaml
- ENABLE_LOGIN_FORM=true # Включено
- ENABLE_OAUTH_SIGNUP=true
```
**2. Перезапустите контейнер:**
```bash
cd /home/its/iiEasyWeb
sudo docker compose restart open-webui
```
**3. Проверьте конфигурацию OAuth:**
- Endpoint должен быть доступен: `https://auth.iieasy.ru/application/o/ii-easy-web/.well-known/openid-configuration`
- Redirect URI в Authentik: `https://odo.iieasy.ru/oauth/oidc/callback`
- Client ID и Client Secret должны совпадать
**4. Если OAuth все еще не работает:**
Временно можно оставить форму входа включенной (`ENABLE_LOGIN_FORM=true`), чтобы пользователи могли войти. OAuth будет работать параллельно как альтернативный способ входа.
## Проверка
После перезапуска:
1. Откройте `https://odo.iieasy.ru`
2. Должна появиться страница входа с кнопкой "iiEasy ID" (OAuth) и формой логина
3. Попробуйте войти через форму входа
4. Попробуйте войти через "iiEasy ID" (OAuth)
Если OAuth работает, форма входа можно отключить позже (`ENABLE_LOGIN_FORM=false`).

41
FIX_OAUTH_SLUG.md Normal file
View File

@@ -0,0 +1,41 @@
# Исправление slug для Authentik OAuth
## Проблема
Endpoint `https://auth.iieasy.ru/application/o/ii-easy-web/.well-known/openid-configuration` возвращает HTML "Not Found".
## Решение
Правильный slug в Authentik - это `open-webui`, а не `ii-easy-web`.
**1. Исправлен `.env` файл:**
```bash
OPENID_CONNECT_ISSUER=https://auth.iieasy.ru/application/o/open-webui/
```
**2. Перезапустите контейнер:**
```bash
cd /home/its/iiEasyWeb
sudo docker compose restart open-webui
```
**3. Проверьте endpoint:**
```bash
curl https://auth.iieasy.ru/application/o/open-webui/.well-known/openid-configuration
```
Должен вернуться JSON с конфигурацией OpenID Connect.
**4. Проверьте OAuth:**
После перезапуска откройте `https://odo.iieasy.ru` и попробуйте войти через кнопку "iiEasy ID" (OAuth).
## Проверка в Authentik
В Authentik для Application должен быть настроен:
- **Slug**: `open-webui`
- **Redirect URI**: `https://odo.iieasy.ru/oauth/oidc/callback`
- **Client ID** и **Client Secret** должны совпадать с `.env`

86
FIX_OLLAMA_URL.md Normal file
View File

@@ -0,0 +1,86 @@
# Исправление проблемы с OLLAMA_BASE_URL
## Проблема
В контейнере Open WebUI переменная `OLLAMA_BASE_URL=/ollama` вместо правильного значения `http://ollama:11434`.
Это приводит к тому, что изображения не передаются в Ollama, так как используется неправильный URL.
## Решение
### Шаг 1: Убедитесь, что в docker-compose.yml правильное значение
В файле `/home/its/iiEasyWeb/docker-compose.yml` на строке 102 должно быть:
```yaml
- OLLAMA_BASE_URL=http://ollama:11434
```
### Шаг 2: Перезапустите контейнер Open WebUI
```bash
cd /home/its/iiEasyWeb
docker compose restart open-webui
```
Или полностью пересоздайте:
```bash
docker compose up -d --force-recreate open-webui
```
### Шаг 3: Проверьте переменную в контейнере
```bash
sudo docker exec open-webui env | grep OLLAMA_BASE_URL
```
Должно быть:
```
OLLAMA_BASE_URL=http://ollama:11434
```
### Шаг 4: Проверьте настройки в веб-интерфейсе
1. Откройте https://odo.iieasy.ru
2. Перейдите в **Settings → Connections → Ollama API**
3. Убедитесь, что адрес: **`http://ollama:11434`**
- НЕ `/ollama`
- НЕ `host.docker.internal:11434`
- НЕ `localhost:11434`
- Должно быть именно `http://ollama:11434`
### Шаг 5: Сохраните настройки
После изменения адреса в веб-интерфейсе нажмите **"Сохранить"**.
### Шаг 6: Проверьте работу
1. Откройте чат с моделью `gemma3n:e4b-it-fp16`
2. Загрузите изображение
3. Задайте вопрос о изображении
4. Проверьте логи Ollama - должны появиться запросы с изображениями:
```bash
sudo docker logs ollama -f | grep -i "image\|vision\|generate"
```
## Почему это важно
Open WebUI использует `OLLAMA_BASE_URL` для формирования полного URL к Ollama API. Если значение неправильное (`/ollama` вместо `http://ollama:11434`), запросы не будут доходить до Ollama, и изображения не будут обрабатываться.
## Дополнительная диагностика
Если после перезапуска проблема сохраняется:
1. Проверьте логи Open WebUI при отправке изображения:
```bash
sudo docker logs open-webui -f | grep -i "ollama\|image\|error"
```
2. Проверьте сеть Docker:
```bash
sudo docker network inspect iieasy-ai | grep -A 5 ollama
```
3. Проверьте доступность Ollama из Open WebUI:
```bash
sudo docker exec open-webui curl -s http://ollama:11434/api/tags | head -5
```

61
FIX_TRACE_ERROR.md Normal file
View File

@@ -0,0 +1,61 @@
# Исправление ошибки NameError: name 'trace' is not defined
## Проблема
В логах Open WebUI появляется ошибка:
```
NameError: name 'trace' is not defined
```
Эта ошибка может возникать, если скрипт `rebrand.sh` случайно изменил код Python в контейнере.
## Решение
**1. Пересоздайте контейнер Open WebUI:**
```bash
cd /home/its/iiEasyWeb
sudo docker compose stop open-webui
sudo docker compose rm -f open-webui
sudo docker compose up -d open-webui
```
Это создаст чистый контейнер без изменений в коде.
**2. Подождите 30-40 секунд** и проверьте статус:
```bash
sudo docker compose ps open-webui
```
**3. Проверьте логи на наличие ошибок:**
```bash
sudo docker compose logs open-webui --tail 50
```
**4. Если нужно применить ребрендинг:**
После пересоздания контейнера, если нужно применить логотипы, используйте:
- **Рекомендуется**: Admin Panel Open WebUI (Settings → Appearance → Logo) - это сохраняется в базе данных
- **Альтернатива**: Запустите обновленный скрипт `rebrand.sh` (он был исправлен и больше не должен ломать код)
## Проверка OAuth
После пересоздания контейнера проверьте OAuth:
1. Убедитесь, что в `.env` правильный slug:
```bash
grep OPENID_CONNECT_ISSUER .env
```
Должно быть: `OPENID_CONNECT_ISSUER=https://auth.iieasy.ru/application/o/ii-easy-web/`
2. Проверьте endpoint Authentik:
```bash
curl https://auth.iieasy.ru/application/o/ii-easy-web/.well-known/openid-configuration
```
Должен вернуться JSON, а не HTML "Not Found"
3. Если endpoint возвращает "Not Found", проверьте в Authentik:
- Application с slug `ii-easy-web` существует
- Redirect URI настроен: `https://odo.iieasy.ru/oauth/oidc/callback`

61
LOGO_FIX.md Normal file
View File

@@ -0,0 +1,61 @@
# Решение проблемы с возвратом логотипов после перезапуска
## Проблема
После перезапуска контейнера Open WebUI логотипы возвращаются к исходным, так как файлы, скопированные через `docker cp`, теряются при пересоздании контейнера.
## Решение 1: Через Admin Panel (РЕКОМЕНДУЕТСЯ - постоянное решение)
Это самый надежный способ, так как настройки сохраняются в базе данных и не теряются при перезапуске.
1. Откройте Open WebUI: `https://odo.iieasy.ru` или `http://localhost:3001`
2. Войдите как администратор
3. Перейдите в **Settings****Appearance** (или **Admin****Settings****Appearance**)
4. Найдите секцию **Logo** или **Branding**
5. Загрузите файлы:
- **Logo**: загрузите `media/logo.png`
- **Favicon**: загрузите `media/favicon.png`
6. Сохраните изменения
**Преимущества:**
- Настройки сохраняются в базе данных
- Не теряются при перезапуске контейнера
- Работает надежно
## Решение 2: Автоматический скрипт после каждого перезапуска
Создан скрипт `scripts/apply_logos_persistent.sh`, который можно запускать после каждого перезапуска:
```bash
cd /home/its/iiEasyWeb
sudo ./scripts/apply_logos_persistent.sh
```
Или добавьте в cron для автоматического запуска:
```bash
# Добавьте в crontab
crontab -e
# Добавьте строку (запуск каждые 5 минут, если контейнер запущен)
*/5 * * * * cd /home/its/iiEasyWeb && docker ps | grep -q open-webui && ./scripts/apply_logos_persistent.sh
```
## Решение 3: Использование systemd timer (для автоматизации)
Создайте systemd timer для автоматического применения логотипов после перезапуска контейнера.
## Временное решение (быстрое применение)
Если логотипы вернулись прямо сейчас:
```bash
cd /home/its/iiEasyWeb
sudo ./scripts/rebrand.sh
```
Но помните - они вернутся после следующего перезапуска контейнера.
## Рекомендация
**Используйте Решение 1 (Admin Panel)** - это единственный способ, который гарантирует, что логотипы не вернутся после перезапуска.

109
LOGO_SETUP.md Normal file
View File

@@ -0,0 +1,109 @@
# Инструкция по настройке логотипов и favicon в Open WebUI
## Проблема: Логотипы не меняются
Open WebUI может использовать скомпилированные статические файлы или кешировать логотипы. Есть несколько способов решения.
## Способ 1: Через Admin Panel (рекомендуется)
Это самый надежный способ, так как настройки сохраняются в базе данных.
1. Откройте Open WebUI: `https://odo.iieasy.ru` или `http://localhost:3001`
2. Войдите как администратор
3. Перейдите в **Settings****Appearance** (или **Admin****Settings** → **Appearance`)
4. Найдите секцию **Logo** или **Branding**
5. Загрузите ваши файлы:
- **Logo**: `media/logo.png` или `media/logo-light.svg`
- **Favicon**: `media/favicon.png` или `media/favicon.ico`
6. Сохраните изменения
## Способ 2: Через скрипт ребрендинга + перезапуск
1. Убедитесь, что файлы есть в папке `media/`:
```bash
ls -la media/logo* media/favicon*
```
2. Запустите скрипт ребрендинга:
```bash
cd /home/its/iiEasyWeb
sudo ./scripts/rebrand.sh
```
3. **ВАЖНО:** Перезапустите контейнер для применения изменений:
```bash
sudo docker compose restart open-webui
```
4. Очистите кеш браузера:
- **Chrome/Edge**: `Ctrl+Shift+Delete` → Очистить кеш изображений
- **Firefox**: `Ctrl+Shift+Delete` → Кеш
- Или используйте режим инкогнито: `Ctrl+Shift+N`
5. Обновите страницу с принудительной перезагрузкой: `Ctrl+F5` или `Ctrl+Shift+R`
## Способ 3: Прямое копирование в контейнер
Если скрипт не работает, скопируйте файлы вручную:
```bash
# Найдите где находятся favicon файлы
docker exec open-webui find /app -name "favicon.png" -o -name "favicon.ico" | head -5
# Скопируйте ваши файлы
docker cp media/favicon.png open-webui:/app/web/build/_app/immutable/favicon.png
docker cp media/logo.png open-webui:/app/web/build/_app/immutable/logo.png
# Перезапустите контейнер
docker compose restart open-webui
```
## Способ 4: Использование монтированного volume
Файлы уже смонтированы в контейнер через volume:
- `./media:/app/media:ro`
- `./media:/app/web/static/custom:ro`
Можно использовать эти пути в настройках Open WebUI или скопировать оттуда:
```bash
# Скопировать из смонтированной папки в нужное место
docker exec open-webui cp /app/media/logo.png /app/web/build/_app/immutable/logo.png
docker exec open-webui cp /app/media/favicon.png /app/web/build/_app/immutable/favicon.png
```
## Проверка
После применения изменений:
1. Откройте браузер в режиме инкогнито
2. Откройте `https://odo.iieasy.ru` или `http://localhost:3001`
3. Проверьте favicon во вкладке браузера
4. Проверьте логотип на странице
## Если ничего не помогает
1. Проверьте в браузере (F12 → Network), какие файлы запрашиваются:
- Откройте вкладку Network
- Обновите страницу
- Найдите запросы к `favicon.*` или `logo.*`
- Посмотрите полный URL запроса
2. Найдите эти файлы в контейнере и замените их:
```bash
docker exec open-webui find /app -path "*/favicon*" -o -path "*/logo*" | grep -v node_modules
```
3. Используйте Admin Panel Open WebUI - это самый надежный способ.
## Настройка через переменные окружения (если поддерживается)
Некоторые версии Open WebUI поддерживают переменные окружения для логотипов. Добавьте в `docker-compose.yml`:
```yaml
environment:
- CUSTOM_LOGO_URL=/static/custom/logo.png
- CUSTOM_FAVICON_URL=/static/custom/favicon.ico
```
Но лучше использовать Admin Panel, так как настройки сохраняются в базе данных.

70
QUICK_FIX.md Normal file
View File

@@ -0,0 +1,70 @@
# Быстрое исправление Authentik
## Проблема: Internal Server Error после перезапуска
## Решение
**1. Убедитесь, что .env содержит правильный slug:**
```bash
cd /home/its/iiEasyWeb
grep OPENID_CONNECT_ISSUER .env
```
Должно быть:
```
OPENID_CONNECT_ISSUER=https://auth.iieasy.ru/application/o/ii-easy-web/
```
**2. Перезапустите контейнер:**
```bash
cd /home/its/iiEasyWeb
sudo docker compose restart open-webui
```
**3. Подождите 20-30 секунд и проверьте логи:**
```bash
sudo docker compose logs open-webui --tail 30 | grep -i "oauth\|oidc\|error"
```
**4. Проверьте redirect URI в Authentik:**
В Authentik для Application `ii-easy-web` должен быть настроен redirect URI:
```
https://odo.iieasy.ru/oauth/oidc/callback
```
**ВАЖНО:**
- URL должен быть точно таким (без порта, с https)
- Должен заканчиваться на `/oauth/oidc/callback` (без завершающего слеша)
**5. Если все еще не работает:**
Временно используйте форму входа (уже включена в docker-compose.yml):
- Откройте `https://odo.iieasy.ru`
- Войдите через форму входа (не через Authentik)
- После настройки Authentik можно будет переключиться обратно
## Проверка конфигурации
```bash
# Проверьте endpoint Authentik
curl https://auth.iieasy.ru/application/o/ii-easy-web/.well-known/openid-configuration
# Должен вернуться JSON с issuer и endpoints
```
## Если нужно отключить Authentik временно
В `docker-compose.yml` измените:
```yaml
- ENABLE_OAUTH_SIGNUP=false
- ENABLE_LOGIN_FORM=true
```
Затем перезапустите:
```bash
sudo docker compose restart open-webui
```

58
QUICK_START.md Normal file
View File

@@ -0,0 +1,58 @@
# 🚀 Быстрый старт - Ребрендинг iiEasy
## ⚡ Быстрое использование
```bash
cd /home/its/iiEasyWeb
sudo ./scripts/rebrand_safe_final.sh
```
## 📁 Требуемые файлы
Поместите в папку `media/`:
-`logo.png` - **обязательно** (основной логотип)
-`favicon.png` - опционально (если нет, используется logo.png)
-`logo-dark.svg` - опционально (для темной темы)
-`logo-light.svg` - опционально (для светлой темы)
## ✅ Что делает скрипт
1. **Заменяет все логотипы и иконки:**
- `logo.png`, `favicon.png`, `favicon.ico`
- `favicon-dark.png`, `apple-touch-icon.png`
- `splash-dark.png`, `splash.png`
2. **Исправляет ссылки в шаблонах:**
- Все ссылки на favicon → ваш логотип
- Все ссылки на splash → ваш логотип
3. **Удаляет упоминания "Open WebUI":**
- Текст "(Open WebUI)" из интерфейса
- Ссылки на документацию → `note.iieasy.ru`
4. **Удаляет элементы интерфейса:**
- Кнопку "Проверить обновления"
- Социальные сети (Discord, Twitter, GitHub)
- Блок "Лицензия"
## 🔄 После выполнения
1. **Очистите кеш браузера:** `Ctrl+Shift+Delete`
2. **Проверьте:** `https://odo.iieasy.ru` или `http://localhost:3001`
## 🔄 После обновления Open WebUI
```bash
sudo docker compose pull
sudo docker compose up -d
sudo ./scripts/rebrand_safe_final.sh
```
## 🐛 Проблемы?
- **Логотип не изменился?** → Очистите кеш браузера
- **Контейнер не запускается?** → `sudo docker compose restart open-webui`
- **Нужна помощь?** → См. `REBRAND_SOLUTION.md`
---
**Полная документация:** `REBRAND_SOLUTION.md`

294
README.md Normal file
View File

@@ -0,0 +1,294 @@
# iiEasy AI-платформа
Корпоративная AI-платформа на базе Open WebUI с интеграцией Ollama, Qdrant, SearXNG и синхронизацией Nextcloud.
## Архитектура
```
┌─────────────────────────────────────────────────────────────┐
│ Reverse Proxy (Nginx) │
│ *.iieasy.ru (odo.iieasy.ru) │
└─────────────────────────────────────────────────────────────┘
┌───────────────────┼───────────────────┐
│ │ │
┌───────▼────────┐ ┌───────▼────────┐ ┌───────▼────────┐
│ Open WebUI │ │ Authentik │ │ Nextcloud │
│ odo.iieasy.ru │ │ auth.iieasy.ru │ │next.iieasy.ru │
└───────┬────────┘ └─────────────────┘ └───────┬────────┘
│ │
│ ┌──────────────────────────────────────┘
│ │
▼ ▼
┌─────────────────────────────────────────────────────────────┐
│ Docker Network (iieasy-ai) │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Ollama │ │ Qdrant │ │ SearXNG │ │
│ │ (GPU) │ │ (Vector) │ │ (Search) │ │
│ └──────────┘ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────┘
┌───────────────┐
│ Python Worker │
│ (Nextcloud → │
│ Qdrant Sync) │
└───────────────┘
```
## Компоненты
- **Open WebUI** - Веб-интерфейс для работы с AI моделями
- **Ollama** - Локальный запуск LLM моделей (Gemma 3)
- **Qdrant** - Векторная база данных для RAG
- **SearXNG** - Метапоисковая система для веб-поиска
- **Authentik** - Централизованный SSO (OIDC)
- **Nextcloud Sync Worker** - Автоматическая синхронизация документов из Nextcloud
## Быстрый старт
### Предварительные требования
- Docker и Docker Compose
- NVIDIA GPU с драйверами (для Ollama)
- Reverse proxy (Nginx) настроенный для доменов *.iieasy.ru
- Authentik настроенный и доступный на auth.iieasy.ru
### 1. Клонирование и настройка
```bash
cd /home/its/iiEasyWeb
cp .env.example .env
# Отредактируйте .env и заполните все необходимые переменные
```
### 2. Генерация API ключей
```bash
# Генерация QDRANT_API_KEY
openssl rand -hex 32
# Добавьте результат в .env
```
### 3. Запуск инфраструктуры
```bash
# Запуск всех сервисов
docker-compose up -d
# Проверка статуса
docker-compose ps
# Просмотр логов
docker-compose logs -f
```
### 4. Загрузка модели Ollama
```bash
# Загрузка модели Gemma 3
docker exec ollama ollama pull gemma3n:e4b-it-fp16
# Проверка загруженных моделей
docker exec ollama ollama list
```
### 5. Настройка Authentik
1. Войдите в Authentik (https://auth.iieasy.ru)
2. Создайте OIDC Provider:
- Redirect URI: `https://odo.iieasy.ru/oauth/oidc/callback`
- Client ID и Client Secret скопируйте в `.env`
3. Обновите `OPENID_CONNECT_ISSUER` в `.env`
### 6. Ребрендинг Open WebUI
После первого запуска Open WebUI выполните:
```bash
./scripts/rebrand.sh
```
Скрипт заменит:
- Логотипы и favicon
- Текстовые упоминания "Open WebUI" → "iiEasyWeb"
- Отключит проверку обновлений
- Удалит аналитику и телеметрию
### 7. Настройка API ключа Open WebUI
1. Откройте https://odo.iieasy.ru
2. Войдите через Authentik SSO
3. Перейдите в Settings → Account → API Keys
4. Создайте новый API ключ
5. Добавьте ключ в `.env` как `OPENWEBUI_API_KEY`
### 8. Запуск воркера синхронизации Nextcloud
```bash
cd worker
# Установка зависимостей
pip install -r requirements.txt
# Настройка переменных окружения
cp .env.example .env
# Отредактируйте .env
# Запуск однократной синхронизации
python nextcloud_sync.py --once
# Или запуск в режиме daemon
python nextcloud_sync.py --daemon
```
Для production рекомендуется использовать systemd:
```bash
# Создайте /etc/systemd/system/iieasy-sync.service
sudo nano /etc/systemd/system/iieasy-sync.service
```
```ini
[Unit]
Description=iiEasy Nextcloud Sync Worker
After=network.target
[Service]
Type=simple
User=its
WorkingDirectory=/home/its/iiEasyWeb/worker
ExecStart=/usr/bin/python3 /home/its/iiEasyWeb/worker/nextcloud_sync.py --daemon
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
```
```bash
sudo systemctl daemon-reload
sudo systemctl enable iieasy-sync
sudo systemctl start iieasy-sync
```
## Переменные окружения
### Основной .env
См. `.env.example` для полного списка переменных. Основные:
- `DOMAIN_OPENWEBUI` - URL Open WebUI
- `DOMAIN_NEXTCLOUD` - URL Nextcloud
- `DOMAIN_AUTHENTIK` - URL Authentik
- `OAUTH_CLIENT_ID` - Client ID из Authentik
- `OAUTH_CLIENT_SECRET` - Client Secret из Authentik
- `OPENID_CONNECT_ISSUER` - Issuer URL Authentik
- `QDRANT_API_KEY` - API ключ Qdrant (сгенерировать)
- `OPENWEBUI_API_KEY` - API ключ Open WebUI (создать после первого запуска)
### worker/.env
- `NC_USER` - Пользователь Nextcloud
- `NC_APP_PASSWORD` - App Password (не основной пароль!)
- `NC_SCAN_PATHS` - Пути для сканирования
- `OPENWEBUI_API_KEY` - API ключ Open WebUI
## Структура проекта
```
iiEasyWeb/
├── docker-compose.yml # Основной compose файл
├── .env # Переменные окружения
├── .env.example # Шаблон переменных
├── .gitignore # Игнорируемые файлы
├── scripts/
│ └── rebrand_safe_final.sh # ✅ Рекомендуемый скрипт ребрендинга (безопасный)
│ ├── rebrand.sh # ⚠️ Старый скрипт (может ломать OAuth)
│ └── rebrand_fast.sh # ⚠️ Быстрый скрипт (может ломать функциональность)
├── media/
│ ├── logo-light.svg # Логотип светлая тема
│ ├── logo-dark.svg # Логотип темная тема
│ └── favicon.svg # Favicon
├── worker/
│ ├── nextcloud_sync.py # Главный скрипт воркера
│ ├── config.py # Конфигурация
│ ├── nextcloud_client.py # WebDAV клиент
│ ├── openwebui_client.py # Open WebUI API клиент
│ ├── document_processor.py # Обработка документов
│ ├── requirements.txt # Python зависимости
│ └── .env.example # Шаблон для воркера
└── README.md # Эта документация
```
## Сетевая архитектура
Все сервисы работают в Docker сети `iieasy-ai` и доступны только внутри сети, кроме Open WebUI, который доступен через reverse proxy.
### Порты
- **Open WebUI**: 3001 (внутренний) → Nginx → 443 (HTTPS)
- **Qdrant**: 6333 (gRPC), 6334 (HTTP) - только внутри сети
- **SearXNG**: 8080 - только внутри сети
- **Ollama**: 11434 - только внутри сети
### Безопасность
- Все сервисы изолированы в Docker сети
- Доступ к Qdrant только через API ключ
- Open WebUI использует Authentik SSO для аутентификации
- Воркер использует App Password для Nextcloud (не основной пароль)
- CrowdSec и OPNsense фильтруют трафик на уровне reverse proxy
## Поддерживаемые форматы файлов
Воркер синхронизации поддерживает:
- **PDF** (.pdf) - извлечение текста через pypdf
- **DOCX** (.docx, .doc) - извлечение текста через python-docx
- **Текстовые** (.txt, .md, .markdown) - прямое чтение
- **CSV** (.csv) - конвертация в текстовый формат
Файлы больше 100MB обрабатываются потоково с ограничением количества страниц.
## Устранение неполадок
### Open WebUI не подключается к Qdrant
1. Проверьте, что Qdrant запущен: `docker-compose ps qdrant`
2. Проверьте логи: `docker-compose logs qdrant`
3. Убедитесь, что `QDRANT_API_KEY` установлен в `.env`
4. Проверьте переменную `QDRANT_URI=http://qdrant:6333`
### Ошибки аутентификации через Authentik
1. Проверьте redirect URI в Authentik: `https://odo.iieasy.ru/oauth/oidc/callback`
2. Убедитесь, что `OPENID_CONNECT_ISSUER` правильный
3. Проверьте логи Open WebUI: `docker-compose logs open-webui`
### Воркер не синхронизирует файлы
1. Проверьте логи: `tail -f worker/sync.log`
2. Убедитесь, что `OPENWEBUI_API_KEY` правильный
3. Проверьте доступность Nextcloud: `curl https://next.iieasy.ru`
4. Проверьте права доступа к путям в `NC_SCAN_PATHS`
### Ollama не использует GPU
1. Проверьте драйверы NVIDIA: `nvidia-smi`
2. Установите nvidia-container-toolkit
3. Перезапустите Docker: `sudo systemctl restart docker`
4. Проверьте переменную `NVIDIA_VISIBLE_DEVICES` в `.env`
## Лицензия
Внутренний проект iiEasy Research Center.
## Поддержка
Для вопросов и проблем обращайтесь к команде разработки iiEasy.

94
REBRANDING.md Normal file
View File

@@ -0,0 +1,94 @@
# Ребрендинг Open WebUI для iiEasy
## ✅ Рекомендуемый скрипт: `rebrand_safe_final.sh`
**Используйте ТОЛЬКО этот скрипт для ребрендинга!**
```bash
cd /home/its/iiEasyWeb
sudo ./scripts/rebrand_safe_final.sh
```
## Что делает скрипт
1.**Заменяет логотипы и favicon:**
- Копирует `logo.png` и `favicon.png` из папки `media/`
- Заменяет `splash.png` на ваш логотип
- Заменяет все существующие логотипы везде
2.**Удаляет "(Open WebUI)" из интерфейса:**
- Ищет и удаляет только в HTML/Svelte/Python шаблонах
- НЕ трогает JavaScript/TypeScript код - не ломает функциональность
3.**Исправляет favicon.png на logo.png:**
- Заменяет `/static/favicon.png` на `/static/logo.png` в шаблонах
- Исправляет API endpoint для изображения профиля модели
4.**Заменяет ссылки на документацию:**
- `docs.openwebui.com``note.iieasy.ru`
- `open-webui.com/docs``note.iieasy.ru`
5.**Удаляет проверку обновлений:**
- Кнопка "Проверить обновления"
- Ссылки на GitHub releases
- Текст "(последняя)" и "Посмотреть, что нового"
6.**Удаляет социальные сети и GitHub:**
- Discord, Twitter/X, GitHub Repo
- Весь блок "Помощь" с соцсетями
- Badges (img.shields.io)
7.**Удаляет блок лицензии:**
- Полностью удаляет блок "Лицензия"
- Удаляет ссылки на enterprise план
## Почему этот скрипт безопасен
- ✅ Обрабатывает только HTML/Svelte/Python шаблоны
-НЕ трогает JavaScript/TypeScript код
-НЕ комментирует импорты
-НЕ изменяет конфигурацию OAuth
-Не ломает функциональность (проверено!)
## После запуска
1. **Очистите кеш браузера:**
- Chrome/Edge: Ctrl+Shift+Delete (Cmd+Shift+Delete на Mac)
- Выберите "Изображения и файлы в кеше"
- Очистите кеш
2. **Проверьте результат:**
- Откройте `https://odo.iieasy.ru`
- Должно быть "Войти в iiEasyWeb" (без "(Open WebUI)")
- Логотип должен отображаться правильно
- Не должно быть ссылок на соцсети и лицензию
## Если что-то не работает
**Восстановите контейнер:**
```bash
cd /home/its/iiEasyWeb
sudo docker compose stop open-webui
sudo docker compose rm -f open-webui
sudo docker compose up -d open-webui
sleep 30
sudo ./scripts/rebrand_safe_final.sh
```
## Другие скрипты (НЕ используйте!)
-`rebrand.sh` - может ломать OAuth
-`rebrand_fast.sh` - может ломать функциональность (500 ошибка)
-`rebrand_complete.sh` - может ломать функциональность
-`rebrand_full.sh` - может ломать функциональность
**Используйте ТОЛЬКО `rebrand_safe_final.sh`!**
## Файлы логотипов
Убедитесь, что в папке `media/` есть:
- `logo.png` - основной логотип
- `favicon.png` - favicon
Эти файлы будут использоваться для ребрендинга.

57
REBRAND_FIX.md Normal file
View File

@@ -0,0 +1,57 @@
# Исправление скрипта rebrand.sh для защиты OAuth/Authentik
## Проблема
Скрипт `rebrand.sh` заменял `open-webui` на `iieasyweb` во ВСЕХ файлах, включая файлы OAuth/Authentik. Это ломало конфигурацию OAuth, так как:
1. URL типа `https://auth.iieasy.ru/application/o/open-webui/` заменялись на `https://auth.iieasy.ru/application/o/iieasyweb/`
2. Переменные окружения и конфигурационные строки с `open-webui` могли быть повреждены
## Решение
Скрипт `rebrand.sh` был исправлен:
1. **Исключены файлы OAuth/Authentik из обработки:**
- Добавлены фильтры `! -path "*/oauth*" ! -path "*/oidc*" ! -path "*authentik*" ! -path "*openid*"`
- Это защищает все файлы, связанные с OAuth, от изменений
2. **Удалена замена `open-webui` на `iieasyweb`:**
- Комментированы строки, которые заменяли `open-webui` и `openwebui` в нижнем регистре
- Это предотвращает случайную замену URL и конфигурации
3. **Оставлена только замена текста интерфейса:**
- Заменяется только "Open WebUI" (с заглавными буквами) на "iiEasyWeb"
- Это безопасно и не влияет на конфигурацию
## Использование
Теперь скрипт можно безопасно запускать:
```bash
cd /home/its/iiEasyWeb
sudo ./scripts/rebrand.sh
```
OAuth/Authentik конфигурация будет защищена от изменений.
## Если OAuth все еще не работает
Если после запуска скрипта OAuth перестал работать:
1. **Пересоздайте контейнер:**
```bash
sudo docker compose stop open-webui
sudo docker compose rm -f open-webui
sudo docker compose up -d open-webui
```
2. **Проверьте конфигурацию:**
```bash
grep OPENID_CONNECT_ISSUER .env
```
Должно быть: `OPENID_CONNECT_ISSUER=https://auth.iieasy.ru/application/o/open-webui/`
3. **Перезапустите контейнер:**
```bash
sudo docker compose restart open-webui
```

89
REBRAND_OAUTH_FIX.md Normal file
View File

@@ -0,0 +1,89 @@
# Исправление проблемы с OAuth после rebrand.sh
## Проблема
После запуска `rebrand.sh` OAuth перестает работать и выкидывает на страницу авторизации. Без rebrand все работает нормально.
## Причина
Скрипт `rebrand.sh` изменяет файлы Python/JS, включая файлы, связанные с OAuth/аутентификацией, что ломает конфигурацию OAuth.
## Решение
### Вариант 1: Использовать безопасный скрипт (РЕКОМЕНДУЕТСЯ)
Используйте новый скрипт `rebrand_safe.sh`, который изменяет ТОЛЬКО логотипы и favicon:
```bash
cd /home/its/iiEasyWeb
sudo ./scripts/rebrand_safe.sh
```
Этот скрипт:
- ✅ Изменяет только логотипы и favicon
-НЕ изменяет код Python/JS
-НЕ влияет на OAuth/Authentik
### Вариант 2: Использовать Admin Panel (НАИБОЛЕЕ БЕЗОПАСНО)
Для постоянных изменений используйте Admin Panel Open WebUI:
1. Откройте `https://odo.iieasy.ru`
2. Войдите как администратор
3. Перейдите в **Settings → Appearance → Logo**
4. Загрузите логотипы и favicon из папки `media/`
5. Сохраните - настройки сохранятся в базе данных
Это самый безопасный способ, так как изменения сохраняются в базе данных и не затрагивают код.
### Вариант 3: Восстановить контейнер после rebrand.sh
Если вы уже запустили `rebrand.sh` и OAuth сломался:
```bash
cd /home/its/iiEasyWeb
# Пересоздайте контейнер с чистой версией
sudo docker compose stop open-webui
sudo docker compose rm -f open-webui
sudo docker compose up -d open-webui
# Подождите 30 секунд
sleep 30
# Используйте безопасный скрипт для логотипов
sudo ./scripts/rebrand_safe.sh
```
## Почему rebrand.sh ломает OAuth?
Скрипт `rebrand.sh` пытается заменить текст "Open WebUI" во всех файлах, включая:
- `/app/backend/open_webui/utils/oauth.py` - файлы OAuth
- `/app/backend/open_webui/main.py` - может содержать OAuth логику
- Другие файлы, связанные с аутентификацией
Даже с фильтрами исключения, некоторые файлы могут быть изменены, что ломает OAuth конфигурацию.
## Рекомендации
1. **Для логотипов**: Используйте `rebrand_safe.sh` или Admin Panel
2. **Для текста интерфейса**: Используйте переменные окружения в `docker-compose.yml`:
- `WEBUI_NAME=iiEasyWeb` (уже настроено)
- `OAUTH_PROVIDER_NAME=iiEasy ID` (уже настроено)
3. **Избегайте**: Изменения кода Python/JS через `sed` в работающем контейнере
## Проверка OAuth после восстановления
После восстановления контейнера проверьте:
```bash
# Проверьте логи
sudo docker compose logs open-webui --tail 50 | grep -i "oauth\|error"
# Проверьте конфигурацию
grep OPENID_CONNECT_ISSUER .env
# Должно быть: OPENID_CONNECT_ISSUER=https://auth.iieasy.ru/application/o/open-webui/
# Проверьте OAuth в браузере
# Откройте https://odo.iieasy.ru и нажмите "iiEasy ID"
```

258
REBRAND_SOLUTION.md Normal file
View File

@@ -0,0 +1,258 @@
# Рабочее решение ребрендинга Open WebUI для iiEasy
## 📋 Обзор
Скрипт `rebrand_safe_final.sh` - это **безопасное и полное решение** для ребрендинга Open WebUI, которое заменяет все логотипы, иконки и удаляет упоминания "Open WebUI" без нарушения функциональности приложения.
## ✅ Что делает скрипт
### 1. Замена логотипов и иконок
Скрипт заменяет **все** типы логотипов и иконок:
#### Основные логотипы:
-`logo.png` - основной логотип
-`logo.svg` - SVG версия логотипа
-`logo-light.svg` / `logo-light.png` - для светлой темы
-`logo-dark.svg` / `logo-dark.png` - для темной темы
-`splash.png` - логотип на экране загрузки
-`splash-dark.png` - логотип на экране загрузки (темная тема)
-`splash-light.png` - логотип на экране загрузки (светлая тема)
#### Favicon и иконки:
-`favicon.png` - основная иконка сайта
-`favicon.ico` - иконка для браузеров
-`favicon-dark.png` - иконка для темной темы
-`favicon-light.png` - иконка для светлой темы
-`apple-touch-icon.png` - иконка для iOS устройств
### 2. Исправление ссылок в шаблонах
Скрипт автоматически исправляет ссылки в HTML/Svelte файлах:
- Заменяет `/static/favicon.ico``/static/logo.png`
- Заменяет `/static/favicon-dark.png``/static/logo.png`
- Заменяет `/static/splash-dark.png``/static/logo.png`
- Заменяет `/static/apple-touch-icon.png``/static/logo.png`
- Исправляет `href` и `src` атрибуты в HTML тегах
### 3. Удаление упоминаний "Open WebUI"
- ✅ Удаляет текст "(Open WebUI)" из всех HTML/Svelte шаблонов
- ✅ Исправляет "Войти в iiEasyWeb (Open WebUI)" → "Войти в iiEasyWeb"
- ✅ Заменяет ссылки на документацию: `docs.openwebui.com``note.iieasy.ru`
### 4. Удаление элементов интерфейса
- ✅ Удаляет кнопку "Проверить обновления"
- ✅ Удаляет ссылку "(последняя)" на GitHub releases
- ✅ Удаляет "Посмотреть, что нового"
- ✅ Удаляет социальные сети (Discord, Twitter/X, GitHub)
- ✅ Удаляет блок "Помощь" с соцсетями
- ✅ Удаляет блок "Лицензия" полностью
### 5. Исправление API endpoints
- ✅ Заменяет `/api/v1/models/model/profile/image``/static/logo.png`
- ✅ Исправляет изображения профиля моделей
## 📁 Структура файлов
```
/home/its/iiEasyWeb/
├── media/
│ ├── logo.png # Основной логотип (обязательно)
│ ├── favicon.png # Favicon (опционально, иначе используется logo.png)
│ ├── logo-light.svg # Логотип для светлой темы (опционально)
│ └── logo-dark.svg # Логотип для темной темы (опционально)
├── scripts/
│ └── rebrand_safe_final.sh # Основной скрипт ребрендинга
└── docker-compose.yml # Docker Compose конфигурация
```
## 🚀 Использование
### Требования
1. Контейнер `open-webui` должен быть запущен
2. Файлы логотипов должны находиться в папке `media/`
3. Минимум требуется `logo.png`
### Запуск скрипта
```bash
cd /home/its/iiEasyWeb
sudo ./scripts/rebrand_safe_final.sh
```
### После выполнения
1. **Очистите кеш браузера:**
- `Ctrl+Shift+Delete` → Очистить кеш изображений
- Или используйте режим инкогнито: `Ctrl+Shift+N`
2. **Проверьте результат:**
- Откройте `https://odo.iieasy.ru` или `http://localhost:3001`
- Проверьте favicon в браузере
- Проверьте логотип в окне авторизации
- Проверьте темную тему (если используется)
3. **Проверьте статические файлы:**
```bash
# Проверьте, что файлы заменены
curl http://localhost:3001/static/logo.png
curl http://localhost:3001/static/favicon.ico
curl http://localhost:3001/static/splash-dark.png
curl http://localhost:3001/static/apple-touch-icon.png
```
## 🔒 Безопасность
### Почему скрипт безопасен:
1. **Не трогает JavaScript/TypeScript код** - обрабатывает только HTML/Svelte/Python шаблоны
2. **Не изменяет OAuth/Authentik** - исключает файлы связанные с аутентификацией
3. **Не ломает функциональность** - только заменяет статические файлы и текст в шаблонах
4. **Использует безопасные sed команды** - точные замены без агрессивных паттернов
### Что НЕ делает скрипт:
- ❌ Не изменяет скомпилированные JS файлы (может сломать функциональность)
- ❌ Не трогает OAuth/OIDC конфигурацию
- ❌ Не изменяет базу данных
- ❌ Не удаляет критичные системные файлы
## 📝 Логика работы
### Шаг 1: Копирование файлов в статические директории
Скрипт копирует логотипы во все возможные статические директории:
- `/app/web/build/_app/immutable`
- `/app/web/static`
- `/app/web/build`
- `/app/backend/static`
- `/app/static`
- `/app/web/public`
- `/app/public`
### Шаг 2: Замена существующих файлов
Скрипт находит все существующие файлы логотипов и иконок и заменяет их:
```bash
find /app -type f -name "logo.png" -o -name "favicon.png" ...
```
### Шаг 3: Исправление ссылок в шаблонах
Скрипт ищет файлы с упоминаниями иконок и исправляет ссылки:
```bash
find /app/web -type f -name "*.html" -o -name "*.svelte" | grep -l "favicon"
```
### Шаг 4: Удаление текста и элементов
Скрипт удаляет упоминания "Open WebUI" и элементы интерфейса только из Svelte файлов (безопасно).
### Шаг 5: Перезапуск контейнера
Скрипт автоматически перезапускает контейнер для применения изменений.
## 🔄 Обновление после обновления Open WebUI
После обновления образа Open WebUI нужно запустить скрипт снова:
```bash
# 1. Обновите образ
sudo docker compose pull
# 2. Пересоздайте контейнер
sudo docker compose up -d
# 3. Запустите ребрендинг
sudo ./scripts/rebrand_safe_final.sh
```
Или используйте скрипт `update.sh`:
```bash
sudo ./scripts/update.sh
```
## 🐛 Решение проблем
### Логотип не изменился
1. **Очистите кеш браузера** - это самая частая причина
2. **Проверьте файлы в контейнере:**
```bash
sudo docker exec open-webui ls -la /app/web/static/logo.png
```
3. **Перезапустите контейнер:**
```bash
sudo docker compose restart open-webui
```
### Favicon не изменился
1. Проверьте HTML код страницы (View Source)
2. Убедитесь, что ссылка на favicon исправлена
3. Очистите кеш браузера полностью
### Элементы интерфейса не удалились
Элементы могут быть в скомпилированных JS файлах. В этом случае:
1. Проверьте исходные Svelte файлы через `find_settings_elements.sh`
2. Удалите элементы вручную в найденных файлах
3. Или используйте Admin Panel для скрытия элементов
### Контейнер не запускается после ребрендинга
Если скрипт сломал контейнер:
```bash
# Восстановите контейнер
sudo docker compose restart open-webui
# Если не помогло, пересоздайте
sudo docker compose down
sudo docker compose up -d
sudo ./scripts/rebrand_safe_final.sh
```
## 📊 Поддерживаемые файлы
| Тип файла | Обрабатывается | Приоритет |
|-----------|----------------|-----------|
| `logo.png` | ✅ Да | Высокий |
| `favicon.png` | ✅ Да | Высокий |
| `favicon.ico` | ✅ Да | Высокий |
| `favicon-dark.png` | ✅ Да | Средний |
| `apple-touch-icon.png` | ✅ Да | Средний |
| `splash-dark.png` | ✅ Да | Средний |
| `logo-dark.svg` | ✅ Да | Низкий (если есть) |
| `logo-light.svg` | ✅ Да | Низкий (если есть) |
## ✨ Особенности
1. **Автоматическое определение темы** - скрипт автоматически использует `logo-dark.svg` для темной темы, если файл существует
2. **Fallback на logo.png** - если специальных файлов нет, используется основной `logo.png`
3. **Безопасная обработка** - скрипт не трогает критичные файлы и не ломает функциональность
4. **Подробный вывод** - скрипт показывает, что именно обрабатывается
## 📚 Связанные файлы
- `scripts/rebrand_safe_final.sh` - основной скрипт ребрендинга
- `scripts/update.sh` - скрипт для обновления с автоматическим ребрендингом
- `REBRANDING.md` - общая документация по ребрендингу
- `LOGO_SETUP.md` - инструкция по настройке логотипов
## 🎯 Итог
Скрипт `rebrand_safe_final.sh` - это **полное и безопасное решение** для ребрендинга Open WebUI, которое:
- ✅ Заменяет все логотипы и иконки
- ✅ Исправляет ссылки в шаблонах
- ✅ Удаляет упоминания "Open WebUI"
- ✅ Удаляет ненужные элементы интерфейса
-Не ломает функциональность приложения
- ✅ Работает после обновлений Open WebUI
**Используйте этот скрипт для всех операций ребрендинга!**

56
RESTORE_AFTER_500.md Normal file
View File

@@ -0,0 +1,56 @@
# Восстановление после 500 ошибки
## Проблема
После запуска `rebrand_fast.sh` появилась ошибка 500 - скрипт сломал функциональность.
## Решение
**1. Восстановите контейнер:**
```bash
cd /home/its/iiEasyWeb
# Остановите и удалите сломанный контейнер
sudo docker compose stop open-webui
sudo docker compose rm -f open-webui
# Пересоздайте контейнер с чистой версией
sudo docker compose up -d open-webui
# Подождите 30 секунд
sleep 30
```
**2. Используйте БЕЗОПАСНЫЙ скрипт:**
```bash
sudo ./scripts/rebrand_safe_final.sh
```
Этот скрипт:
- ✅ Заменяет splash.png на ваш логотип
- ✅ Удаляет "(Open WebUI)" только из HTML/Svelte (не трогает JS/TS код)
- ✅ Заменяет ссылки на документацию
- ✅ Удаляет социальные сети и лицензию
-НЕ трогает JavaScript/TypeScript файлы - не ломает функциональность
**3. Проверьте результат:**
1. Откройте `https://odo.iieasy.ru`
2. Должно работать без ошибок
3. splash.png должен быть заменен на ваш логотип
4. Очистите кеш браузера (Ctrl+Shift+Delete)
## Что было не так с rebrand_fast.sh
Скрипт обрабатывал JS/TS файлы и мог случайно сломать код, удалив важные строки или изменив синтаксис.
## Безопасный подход
Новый скрипт `rebrand_safe_final.sh` обрабатывает только:
- HTML файлы (безопасно)
- Svelte файлы (шаблоны, безопасно)
- НЕ трогает JS/TS файлы (чтобы не сломать код)
Это гарантирует, что функциональность не будет нарушена.

55
RESTORE_CONTAINER.md Normal file
View File

@@ -0,0 +1,55 @@
# Восстановление контейнера после сломанного ребрендинга
## Если после rebrand_complete.sh ничего не работает
**1. Пересоздайте контейнер с чистой версией:**
```bash
cd /home/its/iiEasyWeb
# Остановите и удалите контейнер
sudo docker compose stop open-webui
sudo docker compose rm -f open-webui
# Пересоздайте контейнер
sudo docker compose up -d open-webui
# Подождите 30 секунд
sleep 30
```
**2. Проверьте, что контейнер работает:**
```bash
sudo docker compose ps open-webui
sudo docker compose logs open-webui --tail 50
```
**3. Используйте АККУРАТНЫЙ скрипт для ребрендинга:**
```bash
sudo ./scripts/rebrand_careful.sh
```
Этот скрипт изменяет ТОЛЬКО текст в HTML/Svelte файлах, не трогая код.
## Что делает rebrand_careful.sh
- ✅ Заменяет логотипы и favicon
- ✅ Заменяет "Open WebUI" на "iiEasyWeb" ТОЛЬКО в текстовом контенте HTML
- ✅ Удаляет "(Open WebUI)" из текста
- ✅ Удаляет "Powered by Open WebUI" футеры
-НЕ трогает код Python/JS
-НЕ комментирует импорты
-НЕ изменяет конфигурацию
## Альтернатива: Использовать только Admin Panel
Самый безопасный способ - использовать Admin Panel Open WebUI:
1. Откройте `https://odo.iieasy.ru`
2. Войдите как администратор
3. Settings → Appearance → Logo
4. Загрузите логотипы из папки `media/`
Это не сломает функциональность, так как изменения сохраняются в базе данных.

306
SEARXNG_SETUP.md Normal file
View File

@@ -0,0 +1,306 @@
# Настройка SearXNG для Open WebUI - Рабочее решение
## Обзор
Данное решение обеспечивает работу веб-поиска через SearXNG в Open WebUI. Решены проблемы с JSON форматом, лимитером и багом User-Agent в Open WebUI v0.8.3.
## Архитектура
```
Open WebUI → SearXNG → Поисковые движки (Google, DuckDuckGo, Brave и др.)
```
## Компоненты решения
### 1. Конфигурация SearXNG (`searxng/settings.yml`)
```yaml
# SearXNG Settings для работы с Open WebUI
# Этот файл включает поддержку JSON формата для API запросов
use_default_settings: true
server:
secret_key: "CHANGE_ME_SECRET_KEY"
bind_address: "0.0.0.0"
port: 8080
limiter: false # КРИТИЧНО: отключен для работы внутри Docker сети
method: "GET"
search:
safe_search: 0
autocomplete: "google"
formats:
- html
- json # КРИТИЧНО: JSON формат обязателен для Open WebUI
general:
instance_name: "SearXNG"
debug: false
```
**Ключевые моменты:**
- `limiter: false` - отключает защиту от ботов (необходимо для запросов из Open WebUI)
- `formats: [html, json]` - включает JSON формат для API запросов
- Файл монтируется через bind mount, поэтому настройки сохраняются после перезапуска
### 2. Конфигурация Docker Compose (`docker-compose.yml`)
```yaml
services:
searxng:
image: ghcr.io/searxng/searxng:latest
container_name: searxng
restart: always
volumes:
- ./searxng:/etc/searxng:rw # Bind mount для сохранения настроек
- searxng_cache:/var/cache/searxng
networks:
- iieasy-ai
environment:
- SEARXNG_BASE_URL=http://searxng:8080/
deploy:
resources:
limits:
memory: 512M
open-webui:
image: ghcr.io/open-webui/open-webui:v0.8.3
container_name: open-webui
restart: unless-stopped
volumes:
- openwebui_data:/app/backend/data
- ./scripts/fix_user_agent.sh:/fix_user_agent.sh:ro # Патч для User-Agent
entrypoint: ["/bin/sh", "-c", "sh /fix_user_agent.sh && exec /bin/bash /app/start.sh"]
networks:
- iieasy-ai
depends_on:
searxng:
condition: service_started
environment:
# SearXNG веб-поиск
- RAG_WEB_SEARCH_ENGINE=searxng
- SEARXNG_QUERY_URL=http://searxng:8080/search?q=<query>&format=json
- ENABLE_WEB_SEARCH=true
- WEB_SEARCH_RESULT_COUNT=5
- WEB_SEARCH_TRUST_ENV=true
- WEB_SEARCH_CONCURRENT_REQUESTS=1
- USER_AGENT=Open-WebUI-RAG-Bot
```
**Ключевые моменты:**
- `SEARXNG_QUERY_URL` содержит явное указание `format=json`
- Патч User-Agent применяется автоматически через entrypoint
- Контейнеры находятся в одной сети `iieasy-ai`
### 3. Патч User-Agent (`scripts/fix_user_agent.sh`)
Патч исправляет баг в Open WebUI v0.8.3, где User-Agent начинается с пробела, что вызывает ошибку "Invalid leading whitespace".
**Что делает патч:**
- Ищет все варианты проблемной строки `' (https://github.com/open-webui/open-webui) RAG Bot'`
- Заменяет на `'Open-WebUI-RAG-Bot'`
- Исправляет варианты в файлах:
- `/app/backend/open_webui/routers/retrieval.py`
- `/app/backend/open_webui/utils/middleware.py`
- `/app/backend/open_webui/retrieval/loaders/external_web.py`
- И других файлах с проблемной строкой
- Очищает кеш Python (`.pyc` файлы)
## Установка и настройка
### Шаг 1: Подготовка файлов
1. Убедитесь, что файл `searxng/settings.yml` существует и содержит правильную конфигурацию
2. Убедитесь, что файл `scripts/fix_user_agent.sh` существует и исполняемый
### Шаг 2: Запуск контейнеров
```bash
cd /home/its/iiEasyWeb
sudo docker compose up -d
```
### Шаг 3: Проверка работы
```bash
# Проверка SearXNG
sudo docker ps | grep searxng
# Проверка JSON формата
sudo docker exec open-webui curl -s "http://searxng:8080/search?q=test&format=json" | head -c 200
# Проверка логов на ошибки
sudo docker logs open-webui --tail 50 | grep -i "error\|user-agent\|invalid"
```
### Шаг 4: Настройка в интерфейсе Open WebUI
1. Откройте Open WebUI в браузере
2. Перейдите в **Settings → Web Search**
3. Убедитесь, что:
- Движок: **SearXNG**
- URL: `http://searxng:8080/search?q=<query>&format=json`
- Переключатель "Web Search" **включен**
- "Одновременные запросы" установлено в **1** или больше (не 0)
## Скрипты для обслуживания
### Диагностика (`scripts/diagnose_search.sh`)
Проверяет все компоненты системы поиска:
```bash
sudo ./scripts/diagnose_search.sh
```
### Исправление конфигурации SearXNG (`scripts/fix_searxng_config.sh`)
Исправляет конфигурацию SearXNG после перезапуска:
```bash
sudo ./scripts/fix_searxng_config.sh
```
### Агрессивное исправление User-Agent (`scripts/fix_user_agent_aggressive.sh`)
Ищет и исправляет проблемную строку User-Agent во всех файлах:
```bash
sudo ./scripts/fix_user_agent_aggressive.sh
```
### Полное исправление (`scripts/fix_search_complete.sh`)
Выполняет все исправления за один раз:
```bash
sudo ./scripts/fix_search_complete.sh
```
## Решенные проблемы
### 1. Ошибка "403 Forbidden" при запросе JSON
**Причина:** SearXNG по умолчанию не разрешает JSON формат для безопасности.
**Решение:** Добавлено `formats: [html, json]` в секцию `search:` файла `settings.yml`.
### 2. Ошибка "Invalid leading whitespace" в User-Agent
**Причина:** Баг в Open WebUI v0.8.3 - User-Agent начинается с пробела.
**Решение:** Патч `fix_user_agent.sh` автоматически исправляет проблемную строку при старте контейнера.
### 3. Ошибка "X-Forwarded-For nor X-Real-IP header is set"
**Причина:** SearXNG блокирует запросы без реального IP (защита от ботов).
**Решение:** Отключен лимитер (`limiter: false`) в `settings.yml`, так как система работает внутри закрытой Docker сети.
### 4. Потеря настроек после перезапуска
**Причина:** Изменения внутри контейнера не сохраняются.
**Решение:** Использован bind mount `./searxng:/etc/searxng:rw` для сохранения `settings.yml` на хосте.
## Улучшение покрытия поиска
Если нужно больше результатов, можно включить дополнительные движки:
```bash
# Включить DuckDuckGo
sudo docker exec searxng sed -i '/- name: duckduckgo/,/disabled:/ s/disabled: true/disabled: false/' /etc/searxng/settings.yml
# Включить Brave
sudo docker exec searxng sed -i '/- name: brave$/,/disabled:/ s/disabled: true/disabled: false/' /etc/searxng/settings.yml
# Перезапустить
sudo docker restart searxng
```
## Проверка работоспособности
### Тест 1: Прямой запрос к SearXNG
```bash
sudo docker exec open-webui curl -s "http://searxng:8080/search?q=test&format=json" | grep -q "results" && echo "✓ JSON работает" || echo "✗ JSON не работает"
```
### Тест 2: Проверка патча User-Agent
```bash
sudo docker exec open-webui grep -r "github.com/open-webui.*RAG Bot" /app/backend 2>/dev/null | wc -l
# Должно вернуть 0
```
### Тест 3: Поиск в интерфейсе
1. Откройте чат в Open WebUI
2. Задайте вопрос с включенным поиском (например: "Какая погода в Уфе сегодня?")
3. Должны появиться результаты поиска без ошибок
## Логи и отладка
### Просмотр логов SearXNG
```bash
sudo docker logs searxng --tail 50
```
### Просмотр логов Open WebUI
```bash
sudo docker logs open-webui --tail 50 | grep -i "searxng\|error\|user-agent"
```
### Проверка сетевого подключения
```bash
# Из контейнера Open WebUI к SearXNG
sudo docker exec open-webui curl http://searxng:8080/status
```
## Важные замечания
1. **Безопасность:** Лимитер отключен только для внутренней сети Docker. Если выставляете SearXNG наружу, включите лимитер обратно.
2. **Производительность:** Ограничение памяти SearXNG до 512M предотвращает перегрузку системы.
3. **Стабильность:** Некоторые движки (Google, Bing) могут блокировать запросы с серверов. Это нормально - SearXNG использует другие доступные движки.
4. **Обновления:** При обновлении образа Open WebUI патч User-Agent будет применяться автоматически благодаря entrypoint в docker-compose.yml.
## Структура файлов
```
/home/its/iiEasyWeb/
├── docker-compose.yml # Конфигурация Docker Compose
├── searxng/
│ └── settings.yml # Конфигурация SearXNG (bind mount)
└── scripts/
├── fix_user_agent.sh # Основной патч User-Agent
├── fix_user_agent_aggressive.sh # Агрессивный патч
├── fix_user_agent_final.sh # Финальный патч
├── fix_searxng_config.sh # Исправление конфигурации SearXNG
├── diagnose_search.sh # Диагностика системы поиска
└── fix_search_complete.sh # Полное исправление
```
## Версия
- Open WebUI: v0.8.3
- SearXNG: latest (2026.2.16+8e824017d)
- Дата настройки: Февраль 2026
## Поддержка
При возникновении проблем:
1. Запустите диагностику: `sudo ./scripts/diagnose_search.sh`
2. Проверьте логи: `sudo docker logs open-webui --tail 100`
3. Выполните полное исправление: `sudo ./scripts/fix_search_complete.sh`
---
**Решение протестировано и работает стабильно.**

93
TEST_VISION.md Normal file
View File

@@ -0,0 +1,93 @@
# Тестирование Vision Capabilities модели gemma3n:e4b-it-fp16
## Быстрый тест через скрипт
Запустите скрипт с правами sudo:
```bash
cd /home/its/iiEasyWeb
sudo ./scripts/test_vision.sh
```
Скрипт автоматически:
1. Проверит, что контейнер Ollama запущен
2. Проверит, что модель gemma3n:e4b-it-fp16 загружена
3. Скачает тестовое изображение
4. Отправит запрос к Ollama API с изображением
5. Покажет ответ модели
## Ручной тест через терминал
Если скрипт не работает, выполните команды вручную:
### 1. Проверка контейнера и модели
```bash
# Проверка контейнера
sudo docker ps | grep ollama
# Проверка загруженных моделей
sudo docker exec ollama ollama list
```
### 2. Подготовка изображения
```bash
cd /home/its/iiEasyWeb
mkdir -p test_images
cd test_images
# Скачайте тестовое изображение
curl -L -o test_image.jpg "https://yandex-images.clstorage.net/PU5vN2154/532297ZKm/lCuSfyMn0DdbJqcFVeFiB9Ti31Te2dZ1EepiRw3Cs0Qw8cQ1ND5OQRKC1yH4LnhdRtloQ4aXHng5ZSLNmXHy_8k293YSMsBWKnOvYAXBbhPcl6pYmqi-ZGWDazZo2pYkJNHpkJHrg5yiO0bEOIeEICe5cFqrojYgyNQ6mHj4e5IUb_Lri3uxo9fmXv0dMf7f1NvH9J5YVsyhRvvmtD9eTc1QfVxV42d8OotKrLTDfHx7jfDqjpIqHAyt9ngIIHsSjLtOJ7_jTYdHLn4hJWlCj_jj69gGlApIBYaTfoxPrtHFDGlYUd-PjHI3hijAm6hIb-lUFlpOehQAJTbwEGwjcqjjVns323WrR2q6CalVyg9kZ8s0jkBm8OVGr26UH86NHaD1rKnPH6zinwJIKBv8VEdF_FrGLt6QFOVqFOQY776w47YDNy8Biwfefg0lxVaL2E9L5NbAztwh8vuCcMNSEfG8aYQFBxe0Eu9CMPhz1Axz1dgqmkpSYCAR8nSA_GMSKKPa9-tzIUc7ooKthV1ykwSjrzhORKq0eRrvBlTbajF5yAUoVX-7XM7HouyU51xMaxFMBl7atixQFW4o1BznMoi_chuL8_mTO_IKce21CosEQ6Owkkhi9EGSz5pIE56NjURBxKn7pwTKt6K4FB_YYHexZG4qlsIg8GVaiJyIB0p0bx5PF4_FIy-SItWZId5f8M9_RIqg2hRVCqP2hDvSfWGAYUjJd6c8lr9mgGj7kPQbVQBCUgIm1NB9lsxQrIO-wO92fytbOQsjjiqBqa0CK9Qjy6wi6CoIfX5vZjw_pj1BKGm0UVOXuArPFngIN2AUgz3w4mZSTvz8IQYo_IhPVlQ7PiOjhyWD6wZSVZ3NDrvsC-8w4lxmzE32T1qMuy5JOTRxaNHHH0QCp4IkgMv8LH8JeJaOUj4sVOGCBBgQi5KMX74TQzOBW9cGTu35UZr3qAfj5O6kMogxjqNanMOs"
```
### 3. Тест через Ollama API
```bash
# Закодируйте изображение в base64
IMAGE_B64=$(base64 -w 0 test_image.jpg)
# Отправьте запрос к Ollama API
sudo docker exec ollama sh -c "curl -s -X POST http://localhost:11434/api/generate \
-H 'Content-Type: application/json' \
-d '{
\"model\": \"gemma3n:e4b-it-fp16\",
\"prompt\": \"Опиши это изображение на русском языке. Что ты видишь на картинке?\",
\"images\": [\"'$IMAGE_B64'\"],
\"stream\": false
}' | jq -r '.response'"
```
Если `jq` не установлен, используйте:
```bash
sudo docker exec ollama sh -c "curl -s -X POST http://localhost:11434/api/generate \
-H 'Content-Type: application/json' \
-d '{
\"model\": \"gemma3n:e4b-it-fp16\",
\"prompt\": \"Опиши это изображение на русском языке. Что ты видишь на картинке?\",
\"images\": [\"'$IMAGE_B64'\"],
\"stream\": false
}'" | grep -o '"response":"[^"]*"' | sed 's/"response":"\(.*\)"/\1/' | sed 's/\\n/\n/g' | sed 's/\\"/"/g'
```
## Тест через веб-интерфейс Open WebUI (рекомендуется)
1. Откройте Open WebUI: https://odo.iieasy.ru
2. Выберите модель: **gemma3n:e4b-it-fp16**
3. Найдите кнопку загрузки изображения в поле ввода (обычно иконка скрепки 📎 или фото 📷)
4. Загрузите изображение: `/home/its/iiEasyWeb/test_images/test_image.jpg`
5. Задайте вопрос: **"Опиши это изображение на русском языке. Что ты видишь на картинке?"**
## Ожидаемый результат
Модель должна описать содержимое изображения:
- Объекты на картинке
- Цвета и композицию
- Детали и контекст
Если модель не видит изображение, проверьте:
- Поддерживает ли модель vision (gemma3n:e4b-it-fp16 поддерживает)
- Правильно ли загружается изображение в интерфейсе
- Логи: `sudo docker logs ollama --tail 50`
- Логи Open WebUI: `sudo docker logs open-webui --tail 50`

102
TROUBLESHOOTING.md Normal file
View File

@@ -0,0 +1,102 @@
# Устранение проблем: 502 Bad Gateway
## Диагностика проблемы 502 Bad Gateway
### Шаг 1: Проверка контейнера Open WebUI
```bash
# Проверьте статус контейнера
sudo docker ps | grep open-webui
# Если контейнер не запущен, запустите его
cd /home/its/iiEasyWeb
sudo docker compose up -d open-webui
# Проверьте логи
sudo docker compose logs open-webui --tail 50
```
### Шаг 2: Проверка доступности порта
```bash
# Проверьте, что порт 3001 слушается
sudo netstat -tlnp | grep 3001
# или
sudo ss -tlnp | grep 3001
# Проверьте доступность локально
curl -I http://localhost:3001
curl -I http://127.0.0.1:3001
```
### Шаг 3: Проверка настроек Nginx Proxy Manager
В Nginx Proxy Manager для `odo.iieasy.ru` проверьте:
1. **Details:**
- **Forward Hostname/IP**:
- Если NPM на той же машине: `localhost` или `127.0.0.1`
- Если NPM на другой машине: IP адрес машины с Open WebUI
- **Forward Port**: `3001` (порт на хосте, не в контейнере)
2. **Advanced:**
- **Custom Nginx Configuration**: Оставьте ПУСТЫМ (может вызывать ошибки 500/502)
### Шаг 4: Если используете другую машину
Если Open WebUI на другой машине:
1. Убедитесь, что порт 3001 доступен с машины Nginx Proxy Manager:
```bash
# С машины NPM проверьте доступность
curl http://IP_ДРУГОЙ_МАШИНЫ:3001
```
2. Проверьте firewall:
```bash
# На машине с Open WebUI разрешите порт 3001
sudo ufw allow 3001/tcp
# или
sudo firewall-cmd --add-port=3001/tcp --permanent
```
3. В Nginx Proxy Manager укажите:
- **Forward Hostname/IP**: IP адрес другой машины
- **Forward Port**: `3001`
## Быстрое решение
Если контейнер не запущен или не отвечает:
```bash
cd /home/its/iiEasyWeb
# Перезапустите все сервисы
sudo docker compose down
sudo docker compose up -d
# Подождите 30 секунд
sleep 30
# Проверьте статус
sudo docker compose ps
# Проверьте доступность
curl http://localhost:3001
```
## Частые причины 502 Bad Gateway
1. **Контейнер не запущен** → Запустите: `sudo docker compose up -d open-webui`
2. **Контейнер постоянно перезапускается** → Проверьте логи: `sudo docker compose logs open-webui`
3. **Неправильный Forward Hostname/IP** → Используйте `localhost` если NPM на той же машине
4. **Неправильный Forward Port** → Используйте `3001` (порт на хосте), не `8080` (порт в контейнере)
5. **Custom Configuration конфликтует** → Оставьте поле пустым
6. **Firewall блокирует** → Разрешите порт 3001
7. **Контейнер на другой машине недоступен** → Проверьте сетевую доступность
## Проверка после исправления
1. Откройте `https://odo.iieasy.ru` в браузере
2. Если все еще 502, проверьте логи Nginx Proxy Manager
3. Проверьте логи Open WebUI: `sudo docker compose logs open-webui`

100
VISION_MODELS.md Normal file
View File

@@ -0,0 +1,100 @@
# Vision модели для Ollama
## Проблема с gemma3n:e4b-it-fp16
Модель `gemma3n:e4b-it-fp16` может не поддерживать vision правильно или требует специальной настройки. Рекомендуется использовать специализированные vision модели.
## Рекомендуемые Vision модели
### 1. LLaVA (Large Language-and-Vision Assistant) - РЕКОМЕНДУЕТСЯ
**Модель:** `llava:latest` или `llava:7b`
**Характеристики:**
- 7B параметров
- Версия 1.6 (обновлена в феврале 2024)
- Улучшенное распознавание текста
- Высокое разрешение изображений (в 4 раза больше пикселей)
- Хорошо работает с документами, диаграммами, таблицами
- Лицензия: Apache 2.0 или LLaMA 2 Community License
**Установка:**
```bash
sudo docker exec ollama ollama pull llava:latest
```
**Использование:**
- В Open WebUI выберите модель `llava:latest`
- Загрузите изображение
- Задайте вопрос о изображении
### 2. BakLLaVA
**Модель:** `bakllava:latest`
**Характеристики:**
- 7B параметров
- Комбинация Mistral 7B + LLaVA архитектура
- Контекстное окно: 32K
**Установка:**
```bash
sudo docker exec ollama ollama pull bakllava:latest
```
### 3. Llama 3.2 Vision
**Модель:** `llama3.2-vision:latest` или `llama3.2-vision:11b`
**Характеристики:**
- 11B параметров (требует 8GB VRAM)
- 90B версия доступна (требует 64GB VRAM)
- Контекстное окно: 128K
- Оптимизирована для визуального распознавания, анализа изображений
- Поддержка OCR, распознавание рукописного текста
- Анализ графиков и таблиц
**Установка:**
```bash
sudo docker exec ollama ollama pull llama3.2-vision:11b
```
## Быстрая установка
Используйте скрипт для установки:
```bash
cd /home/its/iiEasyWeb
# Установить LLaVA (рекомендуется)
sudo ./scripts/install_vision_model.sh llava
# Или BakLLaVA
sudo ./scripts/install_vision_model.sh bakllava
# Или Llama 3.2 Vision
sudo ./scripts/install_vision_model.sh llama3.2
```
## Тестирование Vision модели
После установки протестируйте:
```bash
cd /home/its/iiEasyWeb
sudo ./scripts/test_direct_vision.sh
```
(Измените MODEL в скрипте на установленную vision модель)
## Использование в Open WebUI
1. Откройте https://odo.iieasy.ru
2. Перейдите в Settings → Models (или выберите модель в чате)
3. Выберите установленную vision модель (например, `llava:latest`)
4. Загрузите изображение через кнопку загрузки (📎 или 📷)
5. Задайте вопрос о изображении
## Рекомендация
**Для лучшей совместимости с Open WebUI рекомендуется использовать `llava:latest`** - это самая популярная и хорошо поддерживаемая vision модель в Ollama.

47
check_authentik.sh Executable file
View File

@@ -0,0 +1,47 @@
#!/bin/bash
# Скрипт проверки конфигурации Authentik
cd /home/its/iiEasyWeb
echo "=== Проверка конфигурации Authentik ==="
echo ""
# Проверка .env
echo "1. Проверка .env файла:"
if grep -q "ii-easy-web" .env; then
echo " ✓ OPENID_CONNECT_ISSUER содержит правильный slug: ii-easy-web"
grep "OPENID_CONNECT_ISSUER" .env
else
echo " ✗ OPENID_CONNECT_ISSUER не содержит правильный slug"
echo " Текущее значение:"
grep "OPENID_CONNECT_ISSUER" .env
fi
echo ""
echo "2. Проверка endpoint Authentik:"
ENDPOINT=$(grep "OPENID_CONNECT_ISSUER" .env | cut -d'=' -f2).well-known/openid-configuration
echo " URL: $ENDPOINT"
RESPONSE=$(curl -s "$ENDPOINT" 2>&1)
if echo "$RESPONSE" | grep -q '"issuer"'; then
echo " ✓ Endpoint доступен и возвращает JSON"
echo "$RESPONSE" | python3 -c "import sys, json; d=json.load(sys.stdin); print(' Issuer:', d.get('issuer'))" 2>/dev/null || echo " JSON валидный"
else
echo " ✗ Endpoint недоступен или возвращает ошибку"
echo " Ответ: $(echo "$RESPONSE" | head -3)"
fi
echo ""
echo "3. Проверка переменных окружения в docker-compose.yml:"
echo " OPENID_PROVIDER_URL будет: ${ENDPOINT}"
echo " OPENID_REDIRECT_URI будет: $(grep DOMAIN_OPENWEBUI .env | cut -d'=' -f2)/oauth/oidc/callback"
echo ""
echo "4. Что проверить в Authentik:"
echo " - Application slug должен быть: ii-easy-web"
echo " - Redirect URI должен быть: $(grep DOMAIN_OPENWEBUI .env | cut -d'=' -f2)/oauth/oidc/callback"
echo " - Client ID должен совпадать с OAUTH_CLIENT_ID в .env"
echo ""
echo "5. Для применения изменений выполните:"
echo " sudo docker compose restart open-webui"

150
docker-compose.yml Normal file
View File

@@ -0,0 +1,150 @@
services:
ollama:
image: ollama/ollama:latest
container_name: ollama
restart: unless-stopped
volumes:
- ollama_data:/root/.ollama
networks:
- iieasy-ai
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
environment:
- NVIDIA_VISIBLE_DEVICES=${NVIDIA_VISIBLE_DEVICES:-all}
healthcheck:
test: ["CMD", "ollama", "list"]
interval: 30s
timeout: 10s
retries: 3
qdrant:
image: qdrant/qdrant:latest
container_name: qdrant
restart: unless-stopped
volumes:
- qdrant_data:/qdrant/storage
ports:
- "6333:6333" # gRPC API
- "6334:6334" # HTTP API (только внутри сети)
networks:
- iieasy-ai
environment:
- QDRANT_API_KEY=${QDRANT_API_KEY}
healthcheck:
test: ["CMD-SHELL", "timeout 1 bash -c '</dev/tcp/localhost/6333' || exit 1"]
interval: 30s
timeout: 10s
retries: 5
start_period: 30s
searxng:
image: ghcr.io/searxng/searxng:latest
container_name: searxng
restart: always
volumes:
- ./searxng:/etc/searxng:rw
- searxng_cache:/var/cache/searxng
networks:
- iieasy-ai
environment:
- SEARXNG_BASE_URL=http://searxng:8080/
# Ограничиваем ресурсы, чтобы поиск не отъедал лишнего
deploy:
resources:
limits:
memory: 512M
healthcheck:
test: ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:8080 || exit 1"]
interval: 30s
timeout: 10s
retries: 5
start_period: 40s
open-webui:
image: ghcr.io/open-webui/open-webui:v0.8.3
container_name: open-webui
restart: unless-stopped
ports:
- "3001:8080" # Изменено с 3000 на 3001 для избежания конфликтов
volumes:
- openwebui_data:/app/backend/data
- ./media:/app/media:ro # Монтирование медиа-файлов для ребрендинга
- ./media:/app/web/static/custom:ro # Монтирование кастомных логотипов для веб-интерфейса
networks:
- iieasy-ai
depends_on:
qdrant:
condition: service_healthy
searxng:
condition: service_started
environment:
# Базовая конфигурация
- WEBUI_NAME=iiEasyWeb
- WEBUI_URL=${DOMAIN_OPENWEBUI}
- ENABLE_SIGNUP=false
- DEFAULT_USER_ROLE=user
# Кастомные логотипы и favicon (через Admin Panel или переменные окружения)
# Пути к файлам относительно /static/ или полные URL
# Можно настроить через Admin Panel: Settings → Appearance → Logo
# Qdrant векторная БД
- VECTOR_DB=qdrant
- QDRANT_URI=http://qdrant:6333
- QDRANT_API_KEY=${QDRANT_API_KEY}
# SearXNG веб-поиск (настройки как в рабочем демо)
- RAG_WEB_SEARCH_ENGINE=searxng
- SEARXNG_QUERY_URL=http://searxng:8080/search?q=<query>&format=json
- ENABLE_WEB_SEARCH=true
- WEB_SEARCH_RESULT_COUNT=5
- WEB_SEARCH_TRUST_ENV=true
- WEB_SEARCH_CONCURRENT_REQUESTS=1
- USER_AGENT=Open-WebUI-RAG-Bot
# Ollama API для работы с изображениями
- OLLAMA_BASE_URL=http://ollama:11434
# Authentik OIDC SSO
- OAUTH_CLIENT_ID=${OAUTH_CLIENT_ID}
- OAUTH_CLIENT_SECRET=${OAUTH_CLIENT_SECRET}
- OAUTH_PROVIDER_NAME=iiEasy ID
# Правильный формат для Authentik: полный URL до .well-known/openid-configuration
- OPENID_PROVIDER_URL=${OPENID_CONNECT_ISSUER}.well-known/openid-configuration
- OPENID_REDIRECT_URI=${DOMAIN_OPENWEBUI}/oauth/oidc/callback
- ENABLE_OAUTH_SIGNUP=true
- ENABLE_LOGIN_FORM=true
# Форма входа включена как fallback, если OAuth не работает
# Можно отключить после полной настройки OAuth: ENABLE_LOGIN_FORM=false
- OAUTH_MERGE_ACCOUNTS_BY_EMAIL=true
# Bitwarden CLI интеграция (подготовка)
- BW_CLIENTID=${BW_CLIENTID}
- BW_CLIENTSECRET=${BW_CLIENTSECRET}
# Отключение проверки обновлений и аналитики
- ENABLE_PERSISTENT_CONFIG=true
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
volumes:
ollama_data:
driver: local
qdrant_data:
driver: local
searxng_cache:
driver: local
openwebui_data:
driver: local
networks:
iieasy-ai:
driver: bridge

288
docs/DEPLOYMENT.md Normal file
View File

@@ -0,0 +1,288 @@
# Инструкция по развёртыванию iiEasyWeb
Пошаговое развёртывание: Git, Docker, ребрендинг, поиск (SearXNG), воркер Nextcloud (Python).
---
## Требования
- Docker и Docker Compose
- (Опционально) NVIDIA GPU и драйверы — для Ollama
- Доступ к Gitea (например `192.168.88.165:3000`)
- Nextcloud и Authentik — при использовании воркера и OAuth
---
## Архитектура
```
┌─────────────────────────────────────────────────────────────┐
│ Reverse Proxy (Nginx) │
│ *.iieasy.ru (odo.iieasy.ru) │
└─────────────────────────────────────────────────────────────┘
┌───────────────────┼───────────────────┐
│ │ │
┌───────▼────────┐ ┌───────▼────────┐ ┌───────▼────────┐
│ Open WebUI │ │ Authentik │ │ Nextcloud │
│ odo.iieasy.ru │ │ auth.iieasy.ru │ │next.iieasy.ru │
└───────┬────────┘ └─────────────────┘ └───────┬────────┘
│ │
│ ┌──────────────────────────────────────┘
│ │
▼ ▼
┌─────────────────────────────────────────────────────────────┐
│ Docker Network (iieasy-ai) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Ollama │ │ Qdrant │ │ SearXNG │ │
│ │ (GPU) │ │ (Vector) │ │ (Search) │ │
│ └──────────┘ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────┘
┌───────────────┐
│ Python Worker │
│ (Nextcloud → │
│ Qdrant Sync) │
└───────────────┘
```
---
## 1. Git — отправка кода в репозиторий
**Сервер Gitea:** `192.168.88.165`, веб-интерфейс на порту `3000`.
**Репозиторий:** `ars/iiEsaywebUI`.
### Что не должно попадать в Git
- `.env`, `worker/.env`
- `worker/.venv`, секретные ключи, токены
- Убедитесь, что в `.gitignore` есть эти пути (в проекте уже настроено).
### Вариант A — SSH
1. Настройте `~/.ssh/config` (при необходимости укажите порт SSH сервера, если не 22):
```
Host 192.168.88.165
HostName 192.168.88.165
User git
Port 22
```
2. URL репозитория: `git@192.168.88.165:ars/iiEsaywebUI.git`
### Вариант B — HTTPS с токеном
- URL: `https://192.168.88.165:3000/ars/iiEsaywebUI.git`
- При push: логин `ars`, пароль — **подставьте свой токен из Gitea** (или пароль учётной записи, в зависимости от настроек Gitea). Не храните токен в URL в документации.
### Шаги (если репозиторий ещё не инициализирован)
```bash
cd /home/its/iiEasyWeb
git init
git remote add origin git@192.168.88.165:ars/iiEsaywebUI.git
# или HTTPS: git remote add origin https://192.168.88.165:3000/ars/iiEsaywebUI.git
git add .
git status # проверьте, что нет .env и секретов
git commit -m "Initial deployment setup"
git branch -M main
git push -u origin main
```
Если ветка по умолчанию на Gitea — `master`, используйте `git push -u origin master`.
---
## 2. Docker — поднятие стека
**Файлы:** корень проекта — `docker-compose.yml`, `.env.example`.
### Шаги
1. **Каталог и конфиг:**
```bash
cd /home/its/iiEasyWeb
cp .env.example .env
```
2. **Редактирование `.env`:**
- Домены: `DOMAIN_OPENWEBUI`, `DOMAIN_NEXTCLOUD`, `DOMAIN_AUTHENTIK`, при необходимости `DOMAIN_VAULTWARDEN`.
- Authentik: `OAUTH_CLIENT_ID`, `OAUTH_CLIENT_SECRET`, `OPENID_CONNECT_ISSUER`.
- Qdrant — сгенерировать ключ и прописать в `QDRANT_API_KEY`:
```bash
openssl rand -hex 32
```
- Остальное по необходимости (Nextcloud для воркера, Vaultwarden, Ollama). Для GPU: `NVIDIA_VISIBLE_DEVICES=all`.
3. **Запуск:**
```bash
docker compose up -d
docker compose ps
```
4. **Модель Ollama (после старта контейнера ollama):**
```bash
docker exec ollama ollama pull gemma3n:e4b-it-fp16
docker exec ollama ollama list
```
5. **Проверка:** Open WebUI доступен на порту **3001** (маппинг `3001:8080` в `docker-compose.yml`), т.е. `http://localhost:3001` или ваш `DOMAIN_OPENWEBUI`.
---
## 3. Ребрендинг
**Используйте только скрипт:** `scripts/rebrand_safe_final.sh`. Старый `rebrand.sh` может ломать OAuth.
### Медиафайлы
Поместите в каталог `media/`:
- **Обязательно:** `media/logo.png`
- По желанию: `favicon.png`, `logo-dark.svg`, `logo-light.svg`
### Запуск ребрендинга
Контейнер `open-webui` должен быть запущен.
```bash
cd /home/its/iiEasyWeb
sudo ./scripts/rebrand_safe_final.sh
```
### После обновления образа Open WebUI
```bash
docker compose pull
docker compose up -d
sudo ./scripts/rebrand_safe_final.sh
```
### Проверка
Очистите кеш браузера и откройте `http://localhost:3001` или `DOMAIN_OPENWEBUI`.
Подробнее: `QUICK_START.md`, `REBRAND_SOLUTION.md`.
---
## 4. Поиск (SearX / SearXNG)
Поиск уже включён в `docker-compose.yml`: сервис `searxng`, Open WebUI настроен на него (`RAG_WEB_SEARCH_ENGINE=searxng`, `SEARXNG_QUERY_URL=...&format=json`, `ENABLE_WEB_SEARCH=true`).
### Настройка при развёртывании
1. **Секретный ключ SearXNG**
В `searxng/settings.yml` замените `CHANGE_ME_SECRET_KEY` на свою строку (любая случайная строка). Иначе в логах возможны предупреждения.
2. **Проверка после старта:**
```bash
docker exec open-webui curl -s "http://searxng:8080/search?q=test&format=json" | head -c 200
```
3. **Если появляется ошибка User-Agent (Invalid leading whitespace):**
Используйте проверенный скрипт **fix_user_agent_final.sh**. В `docker-compose.yml` для сервиса `open-webui` добавьте:
- volume: `./scripts/fix_user_agent_final.sh:/fix_user_agent_final.sh:ro`
- entrypoint: `["/bin/sh", "-c", "sh /fix_user_agent_final.sh && exec /bin/bash /app/start.sh"]`
Затем перезапустите: `docker compose restart open-webui`.
4. **Диагностика:**
```bash
sudo ./scripts/diagnose_search.sh
```
При проблемах: `sudo ./scripts/fix_search_complete.sh`.
Подробнее: `SEARXNG_SETUP.md`.
---
## 5. Nextcloud Python (воркер)
Воркер синхронизации Nextcloud → Open WebUI/Qdrant запускается на хосте (не в Docker).
**Каталог:** `worker/`. Файлы: `nextcloud_sync.py`, `.env.example`, `requirements.txt`.
### Переменные окружения
```bash
cd /home/its/iiEasyWeb/worker
cp .env.example .env
```
Заполните в `.env`:
- **Nextcloud:** `DOMAIN_NEXTCLOUD`, `NC_USER`, `NC_APP_PASSWORD`, при необходимости `NC_SCAN_PATHS`.
- **Open WebUI:** `DOMAIN_OPENWEBUI`, `OPENWEBUI_API_KEY` (ключ создаётся в Open WebUI: Settings → Account → API Keys).
- По желанию: `SYNC_INTERVAL`, `MAX_FILE_SIZE`, `LOG_LEVEL`.
**Важно:** для Nextcloud используйте **пароль приложения** (App Password), не основной пароль учётной записи.
### Установка и запуск
```bash
pip install -r requirements.txt
python nextcloud_sync.py --once
```
Постоянный режим (daemon):
```bash
python nextcloud_sync.py --daemon
```
### Production (systemd)
Создайте файл `/etc/systemd/system/iieasy-sync.service`:
```ini
[Unit]
Description=iiEasy Nextcloud Sync Worker
After=network.target
[Service]
Type=simple
User=its
WorkingDirectory=/home/its/iiEasyWeb/worker
ExecStart=/usr/bin/python3 /home/its/iiEasyWeb/worker/nextcloud_sync.py --daemon
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
```
Замените `User=its` и пути при необходимости. Затем:
```bash
sudo systemctl daemon-reload
sudo systemctl enable iieasy-sync
sudo systemctl start iieasy-sync
```
---
## Порядок выполнения
1. **Git** — инициализация репозитория, remote, первый push (без `.env` и секретов).
2. **Docker** — `cp .env.example .env`, правка `.env`, генерация `QDRANT_API_KEY`, `docker compose up -d`, загрузка модели Ollama.
3. Первый вход в Open WebUI (при необходимости настройка Authentik OIDC), создание API ключа в Settings → Account → API Keys.
4. **Ребрендинг** — файлы в `media/`, `sudo ./scripts/rebrand_safe_final.sh`.
5. **Поиск** — правка `searxng/settings.yml` (secret_key), при ошибке User-Agent — entrypoint с `fix_user_agent_final.sh`; при проблемах — `diagnose_search.sh`, `fix_search_complete.sh`.
6. **Nextcloud Python** — настройка `worker/.env`, `pip install -r requirements.txt`, проверка `--once`, затем `--daemon` или systemd.
---
## Замечания
- **Токен/пароль Gitea:** в документации не указывайте токен в открытом виде; используйте «подставить свой токен из Gitea» или SSH.
- **Имя репозитория:** на сервере используется `iiEsaywebUI` (с одной «a») — в URL remote указывайте так же.
- **Порт SSH:** если Gitea слушает SSH на порту не 22 (например 3000), укажите в `~/.ssh/config` для хоста `Port 3000`; URL остаётся `git@192.168.88.165:ars/iiEsaywebUI.git`.

View File

@@ -0,0 +1,51 @@
# TTS в Open WebUI: английские и русские голоса
## Transformers (Локально) — только английский
В настройках **Настройки → Речь** при выборе системы синтеза речи **«Transformers (Локально)»** поле **«Модель TTS»** не является выпадающим списком — в него нужно **вручную ввести** имя спикера из набора CMU ARCTIC. Эти голоса **только для английского языка**, русского нет.
### Доступные имена (вводить латиницей)
| Имя | Описание |
|------|------------------------|
| `bdl` | Мужской голос (США) |
| `slt` | Женский голос (США) |
| `clb` | Женский голос (США) |
| `rms` | Мужской голос (США) |
| `awb` | Мужской голос (шотландский) |
| `jmk` | Мужской голос (канадский) |
| `ksp` | Мужской голос (индийский) |
Источник: [Matthijs/cmu-arctic-xvectors](https://huggingface.co/datasets/Matthijs/cmu-arctic-xvectors)
Рекомендация: начните с **`bdl`** или **`slt`**. При первом использовании TTS Open WebUI может скачать модели с Hugging Face.
---
## Русский TTS: Edge TTS (рекомендуется)
Для **русской** озвучки нужен другой движок. Удобный бесплатный вариант — **Edge TTS** (голоса Microsoft). В проекте он уже добавлен в `docker-compose.yml` как сервис `openai-edge-tts`.
### Что сделать в интерфейсе
1. **Настройки → Речь (Audio)**
2. **Система синтеза речи:** выберите **OpenAI** (или пункт, где задаётся URL API).
3. Укажите:
- **TTS API URL:** `http://openai-edge-tts:5050/v1` (если Open WebUI в том же Docker Compose).
- **API ключ:** `your_api_key_here` (дефолтный ключ Edge TTS).
4. **Голос TTS** — введите один из русских голосов:
- **`ru-RU-SvetlanaNeural`** — женский
- **`ru-RU-DmitryNeural`** — мужской
5. Сохраните настройки.
Все голоса Edge TTS (включая другие языки) можно послушать и выбрать: [tts.travisvn.com](https://tts.travisvn.com/).
### Если Edge TTS ещё не запущен
Из каталога проекта:
```bash
docker compose up -d openai-edge-tts
```
После этого снова откройте настройки речи и выберите голос `ru-RU-SvetlanaNeural` или `ru-RU-DmitryNeural`.

24
instr Normal file
View File

@@ -0,0 +1,24 @@
# === ДОМЕНЫ И СЕТЬ ===
DOMAIN_OPENWEBUI=https://odo.iieasy.ru
DOMAIN_NEXTCLOUD=https://cloud.iieasy.ru
DOMAIN_AUTHENTIK=https://auth.iieasy.ru
# === AUTHENTIK (OIDC SSO) ===
# Где брать: Authentik -> Providers -> твой OIDC Provider
OAUTH_CLIENT_ID=твой_client_id_из_authentik
OAUTH_CLIENT_SECRET=твой_client_secret_из_authentik
OPENID_CONNECT_ISSUER=https://auth.iieasy.ru/application/o/open-webui/
# === NEXTCLOUD (Для Python-воркера) ===
# Где брать: Nextcloud -> Настройки -> Безопасность -> Устройства и сессии (Создать пароль приложения)
NC_USER=твой_логин
NC_APP_PASSWORD=твой_пароль_приложения_nextcloud # Строго пароль приложения, не основной!
# === OPEN WEBUI API (Для пуша файлов в Qdrant) ===
# Где брать: Open WebUI -> Settings -> Account -> API Keys (создашь после первого запуска)
OPENWEBUI_API_KEY=твой_api_ключ_от_openwebui
# === VAULTWARDEN (Для интеграции Bitwarden CLI в будущем) ===
# Где брать: Vaultwarden -> Настройки аккаунта -> Безопасность -> Ключи -> API-ключ
BW_CLIENTID=user.твой_id
BW_CLIENTSECRET=твой_bw_secret

BIN
media/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

6
media/favicon.svg Normal file
View File

@@ -0,0 +1,6 @@
<svg width="64" height="64" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<!-- Favicon iiEasy - упрощенная версия логотипа -->
<circle cx="100" cy="100" r="70" stroke="#1F2937" stroke-width="6" fill="none" stroke-dasharray="15 85"/>
<circle cx="100" cy="100" r="50" stroke="#1F2937" stroke-width="6" fill="none" stroke-dasharray="12 58" transform="rotate(30 100 100)"/>
<circle cx="100" cy="100" r="30" stroke="#1F2937" stroke-width="6" fill="none" stroke-dasharray="8 32" transform="rotate(60 100 100)"/>
</svg>

After

Width:  |  Height:  |  Size: 577 B

35
media/init-logos.sh Executable file
View File

@@ -0,0 +1,35 @@
#!/bin/bash
# Скрипт для автоматической замены логотипов при запуске контейнера
# Этот скрипт можно запускать при каждом старте контейнера
MEDIA_DIR="/app/media"
MAX_RETRIES=10
RETRY_DELAY=2
# Ждем пока контейнер полностью запустится
for i in $(seq 1 $MAX_RETRIES); do
if curl -f http://localhost:8080/health >/dev/null 2>&1; then
break
fi
sleep $RETRY_DELAY
done
# Находим все favicon и logo файлы
find /app -type f \( -name "favicon.png" -o -name "favicon.ico" -o -name "logo.png" -o -name "logo.svg" \) 2>/dev/null | while read file; do
dir=$(dirname "$file")
name=$(basename "$file")
# Заменяем favicon
if [[ "$name" == favicon* ]] && [ -f "$MEDIA_DIR/favicon.png" ]; then
cp "$MEDIA_DIR/favicon.png" "$file" 2>/dev/null || true
# Также создаем .ico
cp "$MEDIA_DIR/favicon.png" "$dir/favicon.ico" 2>/dev/null || true
fi
# Заменяем logo
if [[ "$name" == logo* ]] && [ -f "$MEDIA_DIR/logo.png" ]; then
cp "$MEDIA_DIR/logo.png" "$file" 2>/dev/null || true
# Также создаем .svg
cp "$MEDIA_DIR/logo.png" "$dir/logo.svg" 2>/dev/null || true
fi
done

6
media/logo-dark.svg Normal file
View File

@@ -0,0 +1,6 @@
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<!-- Логотип iiEasy - темная тема -->
<circle cx="100" cy="100" r="70" stroke="#FFFFFF" stroke-width="6" fill="none" stroke-dasharray="15 85"/>
<circle cx="100" cy="100" r="50" stroke="#FFFFFF" stroke-width="6" fill="none" stroke-dasharray="12 58" transform="rotate(30 100 100)"/>
<circle cx="100" cy="100" r="30" stroke="#FFFFFF" stroke-width="6" fill="none" stroke-dasharray="8 32" transform="rotate(60 100 100)"/>
</svg>

After

Width:  |  Height:  |  Size: 557 B

6
media/logo-light.svg Normal file
View File

@@ -0,0 +1,6 @@
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<!-- Логотип iiEasy - светлая тема -->
<circle cx="100" cy="100" r="70" stroke="#1F2937" stroke-width="6" fill="none" stroke-dasharray="15 85"/>
<circle cx="100" cy="100" r="50" stroke="#1F2937" stroke-width="6" fill="none" stroke-dasharray="12 58" transform="rotate(30 100 100)"/>
<circle cx="100" cy="100" r="30" stroke="#1F2937" stroke-width="6" fill="none" stroke-dasharray="8 32" transform="rotate(60 100 100)"/>
</svg>

After

Width:  |  Height:  |  Size: 559 B

BIN
media/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

View File

@@ -0,0 +1,41 @@
```html
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>iiEasy Logo - Right Aligned with Styled Slogan</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;700;800&display=swap');
body {
font-family: 'Inter', sans-serif;
background-color: #FFFFFF;
}
.slogan-style {
letter-spacing: 0.25em;
text-transform: uppercase;
}
</style>
</head>
<body class="flex items-center justify-center min-h-screen p-8 bg-white">
<div class="flex items-center">
<!-- Логотип (SVG) -->
<svg width="180" height="180" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg" class="mr-10">
<!-- Внешнее кольцо -->
<circle cx="100" cy="100" r="70" stroke="#111827" stroke-width="6" fill="none" stroke-dasharray="15 85"/>
<!-- Среднее кольцо -->
<circle cx="100" cy="100" r="50" stroke="#111827" stroke-width="6" fill="none" stroke-dasharray="12 58" transform="rotate(30 100 100)"/>
<!-- Внутреннее кольцо -->
<circle cx="100" cy="100" r="30" stroke="#111827" stroke-width="6" fill="none" stroke-dasharray="8 32" transform="rotate(60 100 100)"/>
</svg>
<!-- Текстовый блок справа -->
<div class="flex flex-col border-l border-gray-300 pl-10">
<div class="text-3xl font-bold text-gray-900 mb-2">iiEasy</div>
<div class="text-lg text-gray-600">Будущее. Просто.</div>
</div>
</div>
</body>
</html>
```

View File

@@ -0,0 +1,32 @@
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI Logo - Variant 40 (Kinetic Rings - Compact)</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
/* Устанавливаем шрифт Inter для всего тела документа */
body { font-family: 'Inter', sans-serif; background-color: #FFFFFF; /* Изменено на белый фон */ }
</style>
</head>
<body class="flex flex-col items-center justify-center min-h-screen p-8 bg-white text-gray-900">
<!-- SVG-иконка: Несколько открытых колец или дуг, расположенных так, чтобы создать ощущение движения. -->
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg" class="mb-4">
<!-- Уменьшены радиусы для всех трех кругов, чтобы сделать их более сбитыми -->
<!-- Первый круг (самый внешний, теперь более компактный) -->
<circle cx="100" cy="100" r="70" stroke="#1F2937" stroke-width="6" fill="none" stroke-dasharray="15 85"/>
<!-- Второй круг (средний, теперь более компактный) -->
<circle cx="100" cy="100" r="50" stroke="#1F2937" stroke-width="6" fill="none" stroke-dasharray="12 58" transform="rotate(30 100 100)"/>
<!-- Третий круг (самый внутренний, теперь более компактный) -->
<circle cx="100" cy="100" r="30" stroke="#1F2937" stroke-width="6" fill="none" stroke-dasharray="8 32" transform="rotate(60 100 100)"/>
</svg>
<!-- Название компании -->
<div class="text-3xl font-bold text-gray-900 mb-2">iiEasy</div>
<!-- Слоган компании -->
<div class="text-lg text-gray-600">Будущее. Просто.</div>
</body>
</html>
```

View File

@@ -0,0 +1,32 @@
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI Logo - Variant 40 (Kinetic Rings - Compact) - Light, Right Aligned Text</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
/* Устанавливаем шрифт Inter для всего тела документа */
body { font-family: 'Inter', sans-serif; background-color: #FFFFFF; /* Светлый фон */ }
</style>
</head>
<body class="flex items-center justify-center min-h-screen p-8 bg-white text-gray-800">
<!-- SVG-иконка: Несколько открытых колец или дуг, расположенных так, чтобы создать ощущение движения. -->
<!-- Добавлен margin-right для отступа от текста -->
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg" class="mr-6">
<!-- Уменьшены радиусы для всех трех кругов, чтобы сделать их более сбитыми -->
<!-- Первый круг (самый внешний, теперь более компактный) -->
<circle cx="100" cy="100" r="70" stroke="#1F2937" stroke-width="6" fill="none" stroke-dasharray="15 85"/>
<!-- Второй круг (средний, теперь более компактный) -->
<circle cx="100" cy="100" r="50" stroke="#1F2937" stroke-width="6" fill="none" stroke-dasharray="12 58" transform="rotate(30 100 100)"/>
<!-- Третий круг (самый внутренний, теперь более компактный) -->
<circle cx="100" cy="100" r="30" stroke="#1F2937" stroke-width="6" fill="none" stroke-dasharray="8 32" transform="rotate(60 100 100)"/>
</svg>
<!-- Название компании -->
<!-- Слоган удален, размер текста увеличен -->
<div class="text-5xl font-bold text-gray-900">iiEasy</div>
</body>
</html>
```

View File

@@ -0,0 +1,32 @@
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI Logo - Variant 40 (Kinetic Rings - Compact) - Dark</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
/* Устанавливаем шрифт Inter для всего тела документа */
body { font-family: 'Inter', sans-serif; background-color: #000000; /* Изменено на черный фон */ }
</style>
</head>
<body class="flex flex-col items-center justify-center min-h-screen p-8 bg-black text-white">
<!-- SVG-иконка: Несколько открытых колец или дуг, расположенных так, чтобы создать ощущение движения. -->
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg" class="mb-4">
<!-- Уменьшены радиусы для всех трех кругов, чтобы сделать их более сбитыми -->
<!-- Первый круг (самый внешний, теперь более компактный) -->
<circle cx="100" cy="100" r="70" stroke="#FFFFFF" stroke-width="6" fill="none" stroke-dasharray="15 85"/>
<!-- Второй круг (средний, теперь более компактный) -->
<circle cx="100" cy="100" r="50" stroke="#FFFFFF" stroke-width="6" fill="none" stroke-dasharray="12 58" transform="rotate(30 100 100)"/>
<!-- Третий круг (самый внутренний, теперь более компактный) -->
<circle cx="100" cy="100" r="30" stroke="#FFFFFF" stroke-width="6" fill="none" stroke-dasharray="8 32" transform="rotate(60 100 100)"/>
</svg>
<!-- Название компании -->
<div class="text-3xl font-bold text-white mb-2">iiEasy</div>
<!-- Слоган компании -->
<div class="text-lg text-gray-300">Будущее. Просто.</div>
</body>
</html>
```

View File

@@ -0,0 +1,32 @@
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI Logo - Variant 40 (Kinetic Rings - Compact) - Dark, Right Aligned Text</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
/* Устанавливаем шрифт Inter для всего тела документа */
body { font-family: 'Inter', sans-serif; background-color: #000000; /* Черный фон */ }
</style>
</head>
<body class="flex items-center justify-center min-h-screen p-8 bg-black text-white">
<!-- SVG-иконка: Несколько открытых колец или дуг, расположенных так, чтобы создать ощущение движения. -->
<!-- Добавлен margin-right для отступа от текста -->
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg" class="mr-6">
<!-- Уменьшены радиусы для всех трех кругов, чтобы сделать их более сбитыми -->
<!-- Первый круг (самый внешний, теперь более компактный) -->
<circle cx="100" cy="100" r="70" stroke="#FFFFFF" stroke-width="6" fill="none" stroke-dasharray="15 85"/>
<!-- Второй круг (средний, теперь более компактный) -->
<circle cx="100" cy="100" r="50" stroke="#FFFFFF" stroke-width="6" fill="none" stroke-dasharray="12 58" transform="rotate(30 100 100)"/>
<!-- Третий круг (самый внутренний, теперь более компактный) -->
<circle cx="100" cy="100" r="30" stroke="#FFFFFF" stroke-width="6" fill="none" stroke-dasharray="8 32" transform="rotate(60 100 100)"/>
</svg>
<!-- Название компании -->
<!-- Слоган удален, размер текста увеличен -->
<div class="text-5xl font-bold text-white">iiEasy</div>
</body>
</html>
```

View File

@@ -0,0 +1,42 @@
```html
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>iiEasy Logo - Right Aligned Dark with Styled Slogan</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;700;800&display=swap');
body {
font-family: 'Inter', sans-serif;
background-color: #000000;
}
.slogan-style {
letter-spacing: 0.25em;
text-transform: uppercase;
}
</style>
</head>
<body class="flex items-center justify-center min-h-screen p-8 bg-black">
<div class="flex items-center">
<!-- Логотип (SVG) - Белый -->
<svg width="180" height="180" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg" class="mr-10">
<!-- Внешнее кольцо -->
<circle cx="100" cy="100" r="70" stroke="#FFFFFF" stroke-width="6" fill="none" stroke-dasharray="15 85"/>
<!-- Среднее кольцо -->
<circle cx="100" cy="100" r="50" stroke="#FFFFFF" stroke-width="6" fill="none" stroke-dasharray="12 58" transform="rotate(30 100 100)"/>
<!-- Внутреннее кольцо -->
<circle cx="100" cy="100" r="30" stroke="#FFFFFF" stroke-width="6" fill="none" stroke-dasharray="8 32" transform="rotate(60 100 100)"/>
</svg>
<!-- Текстовый блок справа - Белый -->
<div class="flex flex-col border-l border-gray-700 pl-10">
<div class="text-3xl font-bold text-white mb-2">iiEasy</div>
<!-- Слоган компании -->
<div class="text-lg text-gray-300">Будущее. Просто.</div>
</div>
</div>
</body>
</html>
```

View File

@@ -0,0 +1,75 @@
```html
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Анимированный логотип - iiEasy (Горизонтальная темная версия)</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Inter', sans-serif;
background-color: #000000; /* Черный фон */
}
</style>
</head>
<body class="flex items-center justify-center min-h-screen bg-black p-8">
<!-- Основной контейнер для горизонтального расположения -->
<div class="flex items-center space-x-6">
<!--
SVG-иконка: Анимированные кинетические кольца (инвертированная версия).
Размер уменьшен для гармоничного сочетания с текстом.
-->
<svg width="100" height="100" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<!-- Внешнее кольцо -->
<circle cx="100" cy="100" r="70" stroke="#FFFFFF" stroke-width="6" fill="none" stroke-dasharray="15 85">
<animateTransform
attributeName="transform"
attributeType="XML"
type="rotate"
from="0 100 100"
to="360 100 100"
dur="10s"
repeatCount="indefinite" />
</circle>
<!-- Среднее кольцо -->
<circle cx="100" cy="100" r="50" stroke="#FFFFFF" stroke-width="6" fill="none" stroke-dasharray="12 58" transform="rotate(30 100 100)">
<animateTransform
attributeName="transform"
attributeType="XML"
type="rotate"
from="30 100 100"
to="-330 100 100"
dur="12s"
repeatCount="indefinite" />
</circle>
<!-- Внутреннее кольцо -->
<circle cx="100" cy="100" r="30" stroke="#FFFFFF" stroke-width="6" fill="none" stroke-dasharray="8 32" transform="rotate(60 100 100)">
<animateTransform
attributeName="transform"
attributeType="XML"
type="rotate"
from="60 100 100"
to="420 100 100"
dur="8s"
repeatCount="indefinite" />
</circle>
</svg>
<!-- Название бренда (белый текст) -->
<h1 class="text-6xl font-bold text-white tracking-tight">iiEasy</h1>
</div>
</body>
</html>
```

View File

@@ -0,0 +1,78 @@
```html
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Анимированный логотип - iiEasy (Темная версия)</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Inter', sans-serif;
background-color: #000000; /* Черный фон */
}
</style>
</head>
<body class="flex items-center justify-center min-h-screen bg-black p-8">
<!-- Основной контейнер для центрирования контента по вертикали -->
<div class="flex flex-col items-center text-center">
<!--
SVG-иконка: Анимированные кинетические кольца (инвертированная версия).
Все штрихи сделаны белыми для контраста с черным фоном.
-->
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<!-- Внешнее кольцо -->
<circle cx="100" cy="100" r="70" stroke="#FFFFFF" stroke-width="6" fill="none" stroke-dasharray="15 85">
<animateTransform
attributeName="transform"
attributeType="XML"
type="rotate"
from="0 100 100"
to="360 100 100"
dur="10s"
repeatCount="indefinite" />
</circle>
<!-- Среднее кольцо -->
<circle cx="100" cy="100" r="50" stroke="#FFFFFF" stroke-width="6" fill="none" stroke-dasharray="12 58" transform="rotate(30 100 100)">
<animateTransform
attributeName="transform"
attributeType="XML"
type="rotate"
from="30 100 100"
to="-330 100 100"
dur="12s"
repeatCount="indefinite" />
</circle>
<!-- Внутреннее кольцо -->
<circle cx="100" cy="100" r="30" stroke="#FFFFFF" stroke-width="6" fill="none" stroke-dasharray="8 32" transform="rotate(60 100 100)">
<animateTransform
attributeName="transform"
attributeType="XML"
type="rotate"
from="60 100 100"
to="420 100 100"
dur="8s"
repeatCount="indefinite" />
</circle>
</svg>
<!-- Название бренда (белый текст) -->
<h1 class="text-5xl font-bold text-white mt-8 tracking-tight">iiEasy</h1>
<!-- Новый слоган (светло-серый текст для легкого контраста) -->
<p class="text-xl font-medium text-gray-300 mt-2 tracking-wide">Будущее. Просто.</p>
</div>
</body>
</html>
```

View File

@@ -0,0 +1,143 @@
# Конфигурация Nginx Proxy Manager для odo.iieasy.ru
## Настройка прокси для Open WebUI
### Шаг 1: Создание Proxy Host в Nginx Proxy Manager
1. Войдите в Nginx Proxy Manager (обычно `http://your-server-ip:81`)
2. Перейдите в **Proxy Hosts****Add Proxy Host**
### Шаг 2: Настройка Details
- **Domain Names**: `odo.iieasy.ru`
- **Scheme**: `http`
- **Forward Hostname/IP**: `open-webui` (имя контейнера Docker) или `localhost`
- **Forward Port**: `3001` (изменено с 3000)
- **Cache Assets**: Включено (опционально)
- **Block Common Exploits**: Включено
- **Websockets Support**: **ВКЛЮЧЕНО** (важно для Open WebUI!)
### Шаг 3: Настройка SSL
**ВАЖНО:** SSL сертификат должен быть настроен ДО того, как вы сможете открыть сайт по HTTPS.
#### Вариант A: Использование Let's Encrypt (рекомендуется)
1. В Nginx Proxy Manager перейдите в **SSL Certificates****Add SSL Certificate**
2. Выберите **Let's Encrypt**
3. Заполните:
- **Domain Names**: `odo.iieasy.ru` (можно добавить несколько через запятую)
- **Email Address**: Ваш email (для уведомлений об истечении сертификата)
- **Agree to Let's Encrypt Terms**: Включено
- **Use a DNS Challenge**: Обычно НЕ нужно (используется HTTP challenge)
4. Нажмите **Save**
5. Дождитесь создания сертификата (может занять 1-2 минуты)
**Требования для Let's Encrypt:**
- Домен `odo.iieasy.ru` должен указывать на IP вашего сервера (A-запись в DNS)
- Порт 80 должен быть доступен из интернета (для HTTP challenge)
- Nginx Proxy Manager должен быть доступен на порту 80
#### Вариант B: Использование существующего сертификата
Если у вас уже есть SSL сертификат:
1. **SSL Certificates****Add SSL Certificate****Custom**
2. Вставьте содержимое файлов:
- **Certificate**: содержимое `.crt` или `.pem` файла
- **Private Key**: содержимое `.key` файла
3. Нажмите **Save**
#### Вариант C: Временное отключение SSL (только для тестирования)
Если нужно быстро проверить работу без SSL:
1. В Proxy Host настройках убедитесь, что SSL не включен
2. Используйте HTTP вместо HTTPS: `http://odo.iieasy.ru`
### Шаг 4: Применение SSL к Proxy Host
После создания SSL сертификата:
1. Вернитесь к вашему Proxy Host для `odo.iieasy.ru`
2. Перейдите на вкладку **SSL**
3. Выберите созданный сертификат в поле **SSL Certificate**
4. Включите:
- **Force SSL**: Включено (перенаправляет HTTP на HTTPS)
- **HTTP/2 Support**: Включено
- **HSTS Enabled**: Включено (опционально)
- **HSTS Subdomains**: Включено (опционально, если используете поддомены)
5. Нажмите **Save**
### Шаг 5: Настройка Advanced (Custom Nginx Configuration)
**ВАЖНО:** Nginx Proxy Manager автоматически управляет большинством настроек. Custom Configuration обычно НЕ нужна и может вызвать ошибки 500.
**Оставьте поле Custom Nginx Configuration ПУСТЫМ**, если все работает без него.
Если все же нужны дополнительные настройки (только при проблемах), добавьте минимальную конфигурацию:
```nginx
# Увеличение размера тела запроса для загрузки файлов (если нужно)
client_max_body_size 100M;
# Увеличенные таймауты (только если есть проблемы с таймаутами)
proxy_read_timeout 300s;
```
**Но лучше оставить пустым** - Nginx Proxy Manager сам настроит WebSocket, заголовки и другие параметры через интерфейс.
### Шаг 6: Если используется Docker сеть
Если Nginx Proxy Manager работает в той же Docker сети, что и Open WebUI:
1. В **Forward Hostname/IP** укажите: `open-webui` (имя контейнера)
2. В **Forward Port** укажите: `8080` (внутренний порт контейнера)
Если Nginx Proxy Manager работает на хосте:
1. В **Forward Hostname/IP** укажите: `localhost` или `127.0.0.1`
2. В **Forward Port** укажите: `3001` (порт на хосте)
## Проверка работы
После настройки проверьте:
1. Откройте `https://odo.iieasy.ru` в браузере
2. Проверьте логи Nginx Proxy Manager при ошибках
3. Проверьте логи Open WebUI: `docker compose logs open-webui`
## Устранение проблем
### ERR_SSL_UNRECOGNIZED_NAME_ALERT
Эта ошибка означает, что SSL сертификат не настроен или не соответствует домену.
**Решение:**
1. Проверьте, что SSL сертификат создан в Nginx Proxy Manager
2. Убедитесь, что сертификат применен к Proxy Host (вкладка SSL)
3. Проверьте, что домен `odo.iieasy.ru` указан в сертификате
4. Если используете Let's Encrypt, проверьте:
- DNS A-запись для `odo.iieasy.ru` указывает на IP сервера
- Порт 80 доступен из интернета
- Домен не заблокирован файрволом
**Временное решение для тестирования:**
- Отключите SSL в Proxy Host
- Используйте HTTP: `http://odo.iieasy.ru`
- После настройки SSL включите обратно
### Ошибка 502 Bad Gateway
- Убедитесь, что контейнер `open-webui` запущен: `docker ps | grep open-webui`
- Проверьте, что порт 3001 доступен: `curl http://localhost:3001`
- Проверьте логи: `docker compose logs open-webui`
### WebSocket не работает
- Убедитесь, что **Websockets Support** включен в Nginx Proxy Manager
- **НЕ добавляйте** WebSocket настройки в Custom Configuration - они конфликтуют с автоматическими настройками NPM
### Медленная загрузка
- Проверьте логи: `docker compose logs open-webui`
- Убедитесь, что контейнер Open WebUI работает нормально
- Если нужны большие файлы, добавьте только `client_max_body_size 100M;` в Custom Configuration (но лучше оставить пустым)

View File

@@ -0,0 +1,79 @@
#!/bin/bash
# Скрипт для постоянной замены логотипов через volume монтирование
# Этот скрипт копирует файлы в volume, который сохраняется между перезапусками
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
MEDIA_DIR="$PROJECT_DIR/media"
CONTAINER_NAME="open-webui"
echo "=== Применение логотипов (постоянное решение) ==="
# Проверка наличия контейнера
if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
echo "Ошибка: Контейнер ${CONTAINER_NAME} не запущен."
exit 1
fi
echo "1. Поиск существующих favicon и logo файлов..."
# Находим все существующие favicon и logo файлы
EXISTING_FAVICONS=$(docker exec "${CONTAINER_NAME}" find /app -type f \( -name "favicon.png" -o -name "favicon.ico" -o -name "favicon.svg" -o -name "favicon-96x96.png" \) 2>/dev/null | head -20)
EXISTING_LOGOS=$(docker exec "${CONTAINER_NAME}" find /app -type f \( -name "logo.png" -o -name "logo.svg" \) 2>/dev/null | head -20)
echo " Найдено favicon файлов: $(echo "$EXISTING_FAVICONS" | grep -v '^$' | wc -l)"
echo " Найдено logo файлов: $(echo "$EXISTING_LOGOS" | grep -v '^$' | wc -l)"
# Заменяем все найденные favicon файлы
if [ -f "$MEDIA_DIR/favicon.png" ]; then
echo "2. Замена всех favicon файлов на favicon.png..."
echo "$EXISTING_FAVICONS" | while read -r favicon_file; do
if [ -n "$favicon_file" ]; then
echo "$favicon_file"
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${favicon_file}" 2>/dev/null || true
# Также создаем .ico версию в той же директории
favicon_dir=$(dirname "$favicon_file")
favicon_name=$(basename "$favicon_file" | sed 's/\.[^.]*$//')
if [ "$favicon_name" != "favicon" ]; then
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${favicon_dir}/favicon.ico" 2>/dev/null || true
fi
fi
done
fi
# Заменяем все найденные logo файлы
if [ -f "$MEDIA_DIR/logo.png" ]; then
echo "3. Замена всех logo файлов на logo.png..."
echo "$EXISTING_LOGOS" | while read -r logo_file; do
if [ -n "$logo_file" ]; then
echo "$logo_file"
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${logo_file}" 2>/dev/null || true
# Также создаем .svg версию
logo_dir=$(dirname "$logo_file")
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${logo_dir}/logo.svg" 2>/dev/null || true
fi
done
fi
echo ""
echo "4. Перезапуск контейнера для применения изменений..."
docker restart "${CONTAINER_NAME}" >/dev/null 2>&1 || {
echo "Предупреждение: Не удалось перезапустить контейнер автоматически."
echo "Перезапустите вручную: docker restart ${CONTAINER_NAME}"
}
echo ""
echo "=== Готово! ==="
echo ""
echo "ВАЖНО: Логотипы применены, но они могут вернуться после перезапуска контейнера."
echo ""
echo "Для ПОСТОЯННОГО решения используйте Admin Panel Open WebUI:"
echo " 1. Откройте https://odo.iieasy.ru или http://localhost:3001"
echo " 2. Войдите как администратор"
echo " 3. Settings → Appearance → Logo"
echo " 4. Загрузите logo.png и favicon.png из папки media/"
echo " 5. Сохраните - настройки сохранятся в базе данных"
echo ""
echo "Или запустите этот скрипт после каждого перезапуска контейнера."

100
scripts/check_gpu.sh Executable file
View File

@@ -0,0 +1,100 @@
#!/bin/bash
# Скрипт для проверки подключения GPU в Docker контейнере Ollama
set -e
CONTAINER_NAME="ollama"
echo "=== Проверка GPU в Docker контейнере ==="
echo ""
# Определяем команду docker
DOCKER_CMD="docker"
if ! docker ps >/dev/null 2>&1; then
DOCKER_CMD="sudo docker"
fi
echo "1. Проверка конфигурации в docker-compose.yml..."
if grep -q "driver: nvidia" docker-compose.yml 2>/dev/null; then
echo " ✓ GPU настроен в docker-compose.yml"
echo " Конфигурация:"
grep -A 5 "driver: nvidia" docker-compose.yml | head -6
else
echo " ✗ GPU не настроен в docker-compose.yml"
fi
echo ""
echo "2. Проверка конфигурации контейнера..."
GPU_CONFIG=$($DOCKER_CMD inspect $CONTAINER_NAME 2>/dev/null | grep -i "nvidia\|gpu" | head -5)
if [ -n "$GPU_CONFIG" ]; then
echo " ✓ Найдена конфигурация GPU:"
echo "$GPU_CONFIG"
else
echo " ⚠ Конфигурация GPU не найдена в inspect"
fi
echo ""
echo "3. Проверка доступности nvidia-smi в контейнере..."
if $DOCKER_CMD exec $CONTAINER_NAME which nvidia-smi >/dev/null 2>&1; then
echo " ✓ nvidia-smi доступен"
echo ""
echo " Вывод nvidia-smi:"
$DOCKER_CMD exec $CONTAINER_NAME nvidia-smi 2>&1 | head -15 || echo " ⚠ nvidia-smi не может выполниться"
else
echo " ✗ nvidia-smi не найден в контейнере"
echo " Возможно, контейнер не имеет доступа к GPU"
fi
echo ""
echo "4. Проверка переменных окружения NVIDIA..."
NVIDIA_ENV=$($DOCKER_CMD exec $CONTAINER_NAME env | grep -i nvidia)
if [ -n "$NVIDIA_ENV" ]; then
echo " ✓ Переменные NVIDIA найдены:"
echo "$NVIDIA_ENV"
else
echo " ⚠ Переменные NVIDIA не найдены"
fi
echo ""
echo "5. Проверка логов Ollama на использование GPU..."
RECENT_LOGS=$($DOCKER_CMD logs $CONTAINER_NAME --tail 50 2>&1)
if echo "$RECENT_LOGS" | grep -qi "gpu\|cuda\|nvidia"; then
echo " ✓ Найдены упоминания GPU в логах:"
echo "$RECENT_LOGS" | grep -i "gpu\|cuda\|nvidia" | tail -5
else
echo " ⚠ Нет упоминаний GPU в последних логах"
fi
echo ""
echo "6. Проверка на хосте..."
if command -v nvidia-smi >/dev/null 2>&1; then
echo " GPU на хосте:"
nvidia-smi --query-gpu=name,driver_version,memory.total --format=csv,noheader 2>/dev/null || echo " ⚠ Не удалось получить информацию о GPU"
else
echo " ⚠ nvidia-smi не найден на хосте"
fi
echo ""
echo "=== Резюме ==="
echo ""
if $DOCKER_CMD exec $CONTAINER_NAME nvidia-smi >/dev/null 2>&1; then
echo "✓ GPU подключен и доступен в контейнере"
echo ""
echo "Для детальной информации выполните:"
echo " sudo docker exec ollama nvidia-smi"
else
echo "✗ GPU не доступен в контейнере"
echo ""
echo "Возможные причины:"
echo " 1. Docker не имеет доступа к GPU (нужен nvidia-docker2 или nvidia-container-toolkit)"
echo " 2. Контейнер не был перезапущен после изменения docker-compose.yml"
echo " 3. Драйверы NVIDIA не установлены на хосте"
echo ""
echo "Решение:"
echo " 1. Установите nvidia-container-toolkit:"
echo " sudo apt-get install -y nvidia-container-toolkit"
echo " sudo systemctl restart docker"
echo ""
echo " 2. Перезапустите контейнер:"
echo " docker compose restart ollama"
fi

85
scripts/check_image_transfer.sh Executable file
View File

@@ -0,0 +1,85 @@
#!/bin/bash
# Скрипт для проверки передачи изображений из Open WebUI в Ollama
set -e
echo "=== Проверка передачи изображений Open WebUI → Ollama ==="
echo ""
# Определяем команду docker
DOCKER_CMD="docker"
if ! docker ps >/dev/null 2>&1; then
DOCKER_CMD="sudo docker"
fi
echo "1. Проверка переменной OLLAMA_BASE_URL в контейнере..."
OLLAMA_URL=$($DOCKER_CMD exec open-webui env | grep -i "OLLAMA_BASE_URL" | cut -d= -f2)
if [ -z "$OLLAMA_URL" ]; then
echo " ✗ Переменная OLLAMA_BASE_URL не установлена!"
echo " Нужно добавить в docker-compose.yml и перезапустить контейнер"
else
echo " ✓ OLLAMA_BASE_URL=$OLLAMA_URL"
fi
echo ""
echo "2. Проверка доступности Ollama из Open WebUI..."
if $DOCKER_CMD exec open-webui curl -s --max-time 5 http://ollama:11434/api/tags >/dev/null 2>&1; then
echo " ✓ Ollama доступен по адресу http://ollama:11434"
else
echo " ✗ Ollama недоступен из Open WebUI"
fi
echo ""
echo "3. Проверка модели gemma3n:e4b-it-fp16..."
MODELS=$($DOCKER_CMD exec open-webui curl -s http://ollama:11434/api/tags 2>/dev/null)
if echo "$MODELS" | grep -q "gemma3n:e4b-it-fp16"; then
echo " ✓ Модель найдена"
else
echo " ✗ Модель не найдена"
fi
echo ""
echo "4. Проверка логов Open WebUI на наличие запросов с изображениями..."
RECENT_LOGS=$($DOCKER_CMD logs open-webui --tail 100 2>&1)
if echo "$RECENT_LOGS" | grep -qi "image.*ollama\|ollama.*image\|vision\|multimodal"; then
echo " ✓ Найдены упоминания изображений в логах:"
echo "$RECENT_LOGS" | grep -i "image.*ollama\|ollama.*image\|vision\|multimodal" | tail -5
else
echo " ⚠ Нет упоминаний изображений в последних логах"
fi
echo ""
echo "5. Проверка логов Ollama на наличие запросов с изображениями..."
OLLAMA_LOGS=$($DOCKER_CMD logs ollama --tail 100 2>&1)
if echo "$OLLAMA_LOGS" | grep -qi "image\|vision\|multimodal"; then
echo " ✓ Найдены запросы с изображениями в логах Ollama:"
echo "$OLLAMA_LOGS" | grep -i "image\|vision\|multimodal" | tail -5
else
echo " ✗ Нет запросов с изображениями в логах Ollama"
echo " Это означает, что изображения не доходят до Ollama!"
fi
echo ""
echo "6. Рекомендации:"
echo ""
if [ -z "$OLLAMA_URL" ]; then
echo " ⚠ КРИТИЧНО: Добавьте OLLAMA_BASE_URL в docker-compose.yml:"
echo " - OLLAMA_BASE_URL=http://ollama:11434"
echo " Затем: docker compose restart open-webui"
echo ""
fi
echo " Для диагностики проблемы с изображениями:"
echo " 1. Убедитесь, что в Settings → Connections → Ollama API"
echo " адрес установлен: http://ollama:11434"
echo ""
echo " 2. Попробуйте отправить изображение через веб-интерфейс"
echo " и сразу проверьте логи:"
echo " sudo docker logs open-webui --tail 50 -f"
echo " sudo docker logs ollama --tail 50 -f"
echo ""
echo " 3. Проверьте формат изображения (должен быть JPEG/PNG)"
echo " и размер (не слишком большой)"
echo ""
echo "=== Проверка завершена ==="

43
scripts/check_searxng_json.sh Executable file
View File

@@ -0,0 +1,43 @@
#!/bin/sh
# Скрипт для проверки формата JSON ответа от SearXNG
echo "Проверка формата JSON ответа от SearXNG..."
# Проверяем формат ответа от SearXNG
RESPONSE=$(curl -s "http://searxng:8080/search?q=test&format=json" 2>&1)
if [ $? -eq 0 ]; then
echo "✓ SearXNG отвечает"
echo "Формат ответа (первые 500 символов):"
echo "$RESPONSE" | head -c 500
echo ""
echo ""
# Проверяем наличие ключевых полей
if echo "$RESPONSE" | grep -q '"results"'; then
echo "✓ Найдено поле 'results'"
fi
if echo "$RESPONSE" | grep -q '"url"'; then
echo "✓ Найдено поле 'url'"
fi
if echo "$RESPONSE" | grep -q '"link"'; then
echo "✓ Найдено поле 'link'"
fi
if echo "$RESPONSE" | grep -q '"title"'; then
echo "✓ Найдено поле 'title'"
fi
if echo "$RESPONSE" | grep -q '"content"'; then
echo "✓ Найдено поле 'content'"
fi
if echo "$RESPONSE" | grep -q '"snippet"'; then
echo "✓ Найдено поле 'snippet'"
fi
else
echo "✗ Ошибка при запросе к SearXNG"
echo "$RESPONSE"
fi

42
scripts/check_vision_models.sh Executable file
View File

@@ -0,0 +1,42 @@
#!/bin/bash
# Проверка доступных vision моделей в Ollama
set -e
CONTAINER_OLLAMA="ollama"
echo "=== Проверка vision моделей в Ollama ==="
echo ""
# Определяем команду docker
DOCKER_CMD="docker"
if ! docker ps >/dev/null 2>&1; then
DOCKER_CMD="sudo docker"
fi
echo "1. Список установленных моделей:"
$DOCKER_CMD exec $CONTAINER_OLLAMA ollama list 2>/dev/null || sudo $DOCKER_CMD exec $CONTAINER_OLLAMA ollama list 2>/dev/null
echo ""
echo "2. Популярные vision модели для Ollama:"
echo ""
echo " Рекомендуемые модели с поддержкой vision:"
echo " - llava:latest (LLaVA 1.6) - 7B параметров, хорошо работает с изображениями"
echo " - bakllava:latest (BakLLaVA) - 7B параметров, Mistral + LLaVA"
echo " - llama3.2-vision:latest (Llama 3.2 Vision) - 11B параметров"
echo " - gemma2:9b-it (Gemma 2) - может поддерживать vision"
echo ""
echo "3. Для установки vision модели выполните:"
echo ""
echo " sudo docker exec ollama ollama pull llava:latest"
echo " # или"
echo " sudo docker exec ollama ollama pull bakllava:latest"
echo ""
echo "4. Проверка текущей модели gemma3n:e4b-it-fp16:"
echo ""
echo " Модель gemma3n:e4b-it-fp16 может не поддерживать vision правильно."
echo " Рекомендуется использовать специализированные vision модели:"
echo " - llava:latest (лучший выбор для vision)"
echo " - bakllava:latest (альтернатива)"
echo ""
echo "=== Готово ==="

90
scripts/diagnose_search.sh Executable file
View File

@@ -0,0 +1,90 @@
#!/bin/sh
# Диагностика проблемы с поиском в Open WebUI
echo "=== ДИАГНОСТИКА ПОИСКА В OPEN WEBUI ==="
echo ""
# 1. Проверка SearXNG
echo "1. Проверка SearXNG..."
SEARXNG_STATUS=$(docker ps | grep searxng | awk '{print $7}')
if [ "$SEARXNG_STATUS" = "healthy" ] || [ "$SEARXNG_STATUS" = "Up" ]; then
echo "✓ SearXNG работает (статус: $SEARXNG_STATUS)"
else
echo "✗ SearXNG не работает (статус: $SEARXNG_STATUS)"
fi
# 2. Проверка JSON формата
echo ""
echo "2. Проверка JSON формата SearXNG..."
JSON_TEST=$(docker exec open-webui curl -s "http://searxng:8080/search?q=test&format=json" 2>&1 | head -c 200)
if echo "$JSON_TEST" | grep -q "results"; then
echo "✓ JSON формат работает"
else
echo "✗ JSON формат не работает"
echo " Ответ: $JSON_TEST"
fi
# 3. Проверка патча User-Agent
echo ""
echo "3. Проверка патча User-Agent..."
RAG_BOT_FOUND=$(docker exec open-webui grep -r "RAG Bot" /app/backend/open_webui/routers/retrieval.py /app/backend/open_webui/utils/middleware.py 2>/dev/null | wc -l)
if [ "$RAG_BOT_FOUND" -eq 0 ]; then
echo "✓ Патч User-Agent применен (проблемная строка не найдена)"
else
echo "✗ Патч User-Agent НЕ применен (найдено вхождений: $RAG_BOT_FOUND)"
echo " Нужно перезапустить Open WebUI для применения патча"
fi
# 4. Проверка логов Open WebUI на ошибки
echo ""
echo "4. Последние ошибки в логах Open WebUI..."
docker logs open-webui --tail 50 2>&1 | grep -i "error\|user-agent\|invalid\|searxng" | tail -10
if [ $? -ne 0 ]; then
echo " (Ошибок не найдено в последних 50 строках)"
fi
# 5. Проверка конфигурации docker-compose
echo ""
echo "5. Проверка конфигурации..."
SEARXNG_URL=$(grep "SEARXNG_QUERY_URL" docker-compose.yml | head -1)
echo " SEARXNG_QUERY_URL: $SEARXNG_URL"
if echo "$SEARXNG_URL" | grep -q "format=json"; then
echo "✓ URL содержит format=json"
else
echo "⚠ URL не содержит format=json явно"
fi
# 6. Проверка settings.yml
echo ""
echo "6. Проверка settings.yml SearXNG..."
if grep -q "formats:" searxng/settings.yml && grep -q "json" searxng/settings.yml; then
echo "✓ JSON формат включен в settings.yml"
else
echo "✗ JSON формат НЕ найден в settings.yml"
fi
if grep -q "limiter: false" searxng/settings.yml; then
echo "✓ Лимитер отключен"
else
echo "⚠ Лимитер может быть включен"
fi
echo ""
echo "=== РЕКОМЕНДАЦИИ ==="
echo ""
if [ "$RAG_BOT_FOUND" -gt 0 ]; then
echo "1. Перезапустите Open WebUI для применения патча User-Agent:"
echo " sudo docker restart open-webui"
echo ""
fi
if ! echo "$JSON_TEST" | grep -q "results"; then
echo "2. Исправьте конфигурацию SearXNG:"
echo " sudo ./scripts/fix_searxng_config.sh"
echo ""
fi
echo "3. Проверьте настройки в интерфейсе Open WebUI:"
echo " Settings → Web Search"
echo " URL должен быть: http://searxng:8080/search?q=<query>&format=json"
echo ""

32
scripts/find_model_image_api.sh Executable file
View File

@@ -0,0 +1,32 @@
#!/bin/bash
# Поиск API endpoint для изображения профиля модели
CONTAINER_NAME="open-webui"
echo "=== Поиск API endpoint /api/v1/models/model/profile/image ==="
echo ""
echo "1. Поиск Python файлов с этим endpoint..."
docker exec "${CONTAINER_NAME}" find /app/backend -type f -name "*.py" \
-exec grep -l "model/profile/image\|profile/image\|models.*profile" {} \; 2>/dev/null
echo ""
echo "2. Поиск в main.py и routes..."
docker exec "${CONTAINER_NAME}" find /app/backend -type f -name "*.py" \
\( -name "*main.py" -o -name "*route*.py" -o -name "*api*.py" -o -name "*model*.py" \) \
-exec grep -l "profile.*image\|image.*profile" {} \; 2>/dev/null
echo ""
echo "3. Поиск строк с '/api/v1/models'..."
docker exec "${CONTAINER_NAME}" find /app/backend -type f -name "*.py" \
-exec grep -l "/api/v1/models\|api/v1/models" {} \; 2>/dev/null | head -10
echo ""
echo "4. Поиск в шаблонах HTML/Svelte с этим API..."
docker exec "${CONTAINER_NAME}" find /app/web -type f \
\( -name "*.html" -o -name "*.svelte" -o -name "*.js" -o -name "*.ts" \) \
! -path "*/node_modules/*" \
-exec grep -l "model/profile/image\|models.*profile.*image" {} \; 2>/dev/null | head -10
echo ""
echo "=== Поиск завершен ==="

64
scripts/find_openwebui_text.sh Executable file
View File

@@ -0,0 +1,64 @@
#!/bin/bash
# Скрипт для поиска всех упоминаний "(Open WebUI)" в контейнере
CONTAINER_NAME="open-webui"
echo "=== Поиск всех упоминаний '(Open WebUI)' ==="
echo ""
# Ищем во всех файлах
echo "1. Поиск в HTML/Svelte файлах..."
docker exec "${CONTAINER_NAME}" find /app/web -type f \( -name "*.html" -o -name "*.svelte" \) \
! -path "*/node_modules/*" \
2>/dev/null | while read file; do
if docker exec "${CONTAINER_NAME}" grep -q "(Open WebUI)" "$file" 2>/dev/null; then
echo " НАЙДЕНО в: $file"
docker exec "${CONTAINER_NAME}" grep -n "(Open WebUI)" "$file" 2>/dev/null | head -3
fi
done
echo ""
echo "2. Поиск в JS/TS файлах..."
docker exec "${CONTAINER_NAME}" find /app/web -type f \( -name "*.js" -o -name "*.ts" -o -name "*.jsx" -o -name "*.tsx" \) \
! -path "*/node_modules/*" \
2>/dev/null | while read file; do
if docker exec "${CONTAINER_NAME}" grep -q "(Open WebUI)" "$file" 2>/dev/null; then
echo " НАЙДЕНО в: $file"
docker exec "${CONTAINER_NAME}" grep -n "(Open WebUI)" "$file" 2>/dev/null | head -3
fi
done
echo ""
echo "3. Поиск в скомпилированных файлах..."
docker exec "${CONTAINER_NAME}" find /app/web/build -type f -name "*.js" \
2>/dev/null | while read file; do
if docker exec "${CONTAINER_NAME}" grep -q "(Open WebUI)" "$file" 2>/dev/null; then
echo " НАЙДЕНО в скомпилированном: $file"
docker exec "${CONTAINER_NAME}" grep -o ".{0,50}(Open WebUI).{0,50}" "$file" 2>/dev/null | head -2
fi
done
echo ""
echo "4. Поиск в JSON файлах..."
docker exec "${CONTAINER_NAME}" find /app/web -type f -name "*.json" \
! -path "*/node_modules/*" \
2>/dev/null | while read file; do
if docker exec "${CONTAINER_NAME}" grep -q "(Open WebUI)" "$file" 2>/dev/null; then
echo " НАЙДЕНО в: $file"
docker exec "${CONTAINER_NAME}" grep -n "(Open WebUI)" "$file" 2>/dev/null | head -3
fi
done
echo ""
echo "5. Поиск вариантов написания..."
docker exec "${CONTAINER_NAME}" find /app/web -type f \
! -path "*/node_modules/*" \
2>/dev/null | while read file; do
if docker exec "${CONTAINER_NAME}" grep -qE "iiEasyWeb.*Open|Open.*WebUI" "$file" 2>/dev/null; then
echo " ВАРИАНТ в: $file"
docker exec "${CONTAINER_NAME}" grep -nE "iiEasyWeb.*Open|Open.*WebUI" "$file" 2>/dev/null | head -2
fi
done
echo ""
echo "=== Поиск завершен ==="

View File

@@ -0,0 +1,42 @@
#!/bin/bash
# Поиск элементов настроек, которые нужно удалить
CONTAINER_NAME="open-webui"
echo "=== Поиск элементов настроек ==="
echo ""
echo "1. Поиск 'Проверить обновления'..."
docker exec "${CONTAINER_NAME}" find /app -type f \
\( -name "*.svelte" -o -name "*.js" -o -name "*.ts" -o -name "*.html" \) \
! -path "*/node_modules/*" \
-exec grep -l "Проверить обновления\|Check for updates\|check.*update" {} \; 2>/dev/null | head -10
echo ""
echo "2. Поиск 'последняя' и GitHub releases..."
docker exec "${CONTAINER_NAME}" find /app -type f \
\( -name "*.svelte" -o -name "*.js" -o -name "*.ts" \) \
! -path "*/node_modules/*" \
-exec grep -l "последняя\|latest\|github.com/open-webui/releases" {} \; 2>/dev/null | head -10
echo ""
echo "3. Поиск блока 'Помощь' и соцсетей..."
docker exec "${CONTAINER_NAME}" find /app -type f \
\( -name "*.svelte" -o -name "*.js" -o -name "*.ts" \) \
! -path "*/node_modules/*" \
-exec grep -l "Помощь\|Help\|discord\.gg\|twitter\.com\|github.com/open-webui" {} \; 2>/dev/null | head -10
echo ""
echo "4. Поиск блока 'Лицензия'..."
docker exec "${CONTAINER_NAME}" find /app -type f \
\( -name "*.svelte" -o -name "*.js" -o -name "*.ts" \) \
! -path "*/node_modules/*" \
-exec grep -l "Лицензия\|License\|лицензионный тарифный план" {} \; 2>/dev/null | head -10
echo ""
echo "5. Поиск в скомпилированных файлах..."
docker exec "${CONTAINER_NAME}" find /app/web/build -type f -name "*.js" \
-exec grep -l "Проверить обновления\|последняя\|Помощь\|Лицензия" {} \; 2>/dev/null | head -5
echo ""
echo "=== Поиск завершен ==="

49
scripts/fix_openwebui.sh Executable file
View File

@@ -0,0 +1,49 @@
#!/bin/bash
# Скрипт восстановления Open WebUI после повреждения ребрендингом
set -e
CONTAINER_NAME="open-webui"
echo "=== Восстановление Open WebUI ==="
# Проверка наличия контейнера
if ! docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
echo "Ошибка: Контейнер ${CONTAINER_NAME} не найден."
exit 1
fi
echo "1. Остановка контейнера..."
docker stop "${CONTAINER_NAME}" 2>/dev/null || true
echo "2. Удаление поврежденного контейнера..."
docker rm "${CONTAINER_NAME}" 2>/dev/null || true
echo "3. Пересоздание контейнера..."
cd "$(dirname "$0")/.."
docker compose up -d open-webui
echo "4. Ожидание запуска контейнера..."
sleep 15
echo "5. Проверка статуса..."
if docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
echo "✓ Контейнер запущен успешно"
# Проверяем, что контейнер работает
if docker exec "${CONTAINER_NAME}" curl -f http://localhost:8080/health 2>/dev/null; then
echo "✓ Контейнер отвечает на запросы"
else
echo "⚠ Контейнер запущен, но не отвечает. Проверьте логи: docker compose logs open-webui"
fi
else
echo "✗ Контейнер не запустился. Проверьте логи: docker compose logs open-webui"
exit 1
fi
echo ""
echo "=== Восстановление завершено ==="
echo ""
echo "ВАЖНО: Скрипт rebrand.sh был исправлен и больше не будет ломать код."
echo "Если нужно применить ребрендинг, используйте обновленный скрипт:"
echo " ./scripts/rebrand.sh"

56
scripts/fix_search_complete.sh Executable file
View File

@@ -0,0 +1,56 @@
#!/bin/sh
# Полное исправление поиска в Open WebUI
# Исправляет конфигурацию SearXNG и перезапускает контейнеры
echo "=== ПОЛНОЕ ИСПРАВЛЕНИЕ ПОИСКА ==="
echo ""
# 1. Исправляем конфигурацию SearXNG
echo "1. Исправление конфигурации SearXNG..."
./scripts/fix_searxng_config.sh
# 2. Перезапускаем Open WebUI для применения патча User-Agent
echo ""
echo "2. Перезапуск Open WebUI для применения патча User-Agent..."
docker restart open-webui
echo ""
echo "Ожидание запуска Open WebUI (15 секунд)..."
sleep 15
# 3. Проверка
echo ""
echo "3. Финальная проверка..."
echo ""
# Проверка SearXNG
SEARXNG_STATUS=$(docker ps | grep searxng | awk '{print $7}')
if [ "$SEARXNG_STATUS" = "healthy" ] || [ "$SEARXNG_STATUS" = "Up" ]; then
echo "✓ SearXNG работает"
else
echo "✗ SearXNG не работает"
fi
# Проверка JSON
JSON_TEST=$(docker exec open-webui curl -s "http://searxng:8080/search?q=test&format=json" 2>&1 | head -c 200)
if echo "$JSON_TEST" | grep -q "results"; then
echo "✓ JSON формат работает"
else
echo "✗ JSON формат не работает"
fi
# Проверка патча
RAG_BOT_FOUND=$(docker exec open-webui grep -r "RAG Bot" /app/backend/open_webui/routers/retrieval.py /app/backend/open_webui/utils/middleware.py 2>/dev/null | wc -l)
if [ "$RAG_BOT_FOUND" -eq 0 ]; then
echo "✓ Патч User-Agent применен"
else
echo "⚠ Патч User-Agent может быть не применен (найдено: $RAG_BOT_FOUND)"
fi
echo ""
echo "=== ГОТОВО ==="
echo ""
echo "Проверьте поиск в Open WebUI:"
echo "1. Откройте Settings → Web Search"
echo "2. URL должен быть: http://searxng:8080/search?q=<query>&format=json"
echo "3. Попробуйте поиск в чате"

55
scripts/fix_searxng_config.sh Executable file
View File

@@ -0,0 +1,55 @@
#!/bin/sh
# Скрипт для исправления конфигурации SearXNG после перезапуска контейнера
echo "Исправление конфигурации SearXNG..."
# Исправляем файл на хосте (он монтируется в контейнер)
cat > /home/its/iiEasyWeb/searxng/settings.yml << 'EOF'
# SearXNG Settings для работы с Open WebUI
# Этот файл включает поддержку JSON формата для API запросов
use_default_settings: true
server:
secret_key: "CHANGE_ME_SECRET_KEY"
bind_address: "0.0.0.0"
port: 8080
limiter: false
method: "GET"
search:
safe_search: 0
autocomplete: "google"
formats:
- html
- json
general:
instance_name: "SearXNG"
debug: false
EOF
echo "✓ Конфигурация обновлена на хосте"
# Перезапускаем SearXNG
echo "Перезапуск SearXNG..."
docker restart searxng
echo ""
echo "Ожидание запуска SearXNG (10 секунд)..."
sleep 10
# Проверяем, что JSON формат работает
echo ""
echo "Проверка JSON формата..."
RESPONSE=$(docker exec open-webui curl -s "http://searxng:8080/search?q=test&format=json" 2>&1 | head -c 200)
if echo "$RESPONSE" | grep -q "results"; then
echo "✓ JSON формат работает! Поиск должен работать в Open WebUI."
else
echo "⚠ Предупреждение: JSON формат может быть недоступен. Проверьте логи:"
echo " docker logs searxng --tail 50"
fi
echo ""
echo "Готово! Проверьте поиск в Open WebUI."

31
scripts/fix_searxng_json.sh Executable file
View File

@@ -0,0 +1,31 @@
#!/bin/sh
# Скрипт для исправления конфигурации SearXNG для работы с JSON форматом
echo "Исправление конфигурации SearXNG для JSON формата..."
# 1. Убеждаемся, что лимитер отключен
echo "1. Отключение лимитера..."
docker exec searxng sed -i 's/limiter: true/limiter: false/g' /etc/searxng/settings.yml 2>/dev/null || echo " Лимитер уже отключен или настройка не найдена"
# 2. Обновляем секцию search с правильными форматами
echo "2. Обновление секции search..."
docker exec searxng sh -c "sed -i '/^search:/,\$d' /etc/searxng/settings.yml && cat >> /etc/searxng/settings.yml <<'EOF'
search:
safe_search: 0
autocomplete: 'google'
formats:
- html
- json
EOF"
# 3. Перезапускаем SearXNG
echo "3. Перезапуск SearXNG..."
docker restart searxng
echo ""
echo "✓ Конфигурация SearXNG обновлена!"
echo " - JSON формат включен"
echo " - Лимитер отключен"
echo " - Autocomplete: google"
echo ""
echo "Подождите 10-15 секунд для полного запуска SearXNG..."

57
scripts/fix_trace_error.sh Executable file
View File

@@ -0,0 +1,57 @@
#!/bin/bash
# Скрипт для исправления ошибки NameError: name 'trace' is not defined
# Пересоздает контейнер Open WebUI с чистой версией
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
CONTAINER_NAME="open-webui"
echo "=== Исправление ошибки NameError: name 'trace' is not defined ==="
echo ""
cd "$PROJECT_DIR"
echo "1. Остановка контейнера..."
sudo docker compose stop "$CONTAINER_NAME" 2>/dev/null || true
echo "2. Удаление поврежденного контейнера..."
sudo docker compose rm -f "$CONTAINER_NAME" 2>/dev/null || true
echo "3. Пересоздание контейнера с чистой версией..."
sudo docker compose up -d "$CONTAINER_NAME"
echo "4. Ожидание запуска контейнера (30 секунд)..."
sleep 30
echo "5. Проверка статуса..."
if sudo docker compose ps "$CONTAINER_NAME" | grep -q "Up"; then
echo "✓ Контейнер запущен успешно"
else
echo "✗ Контейнер не запустился. Проверьте логи:"
echo " sudo docker compose logs $CONTAINER_NAME"
exit 1
fi
echo ""
echo "6. Проверка логов на ошибки..."
ERRORS=$(sudo docker compose logs "$CONTAINER_NAME" --tail 50 2>&1 | grep -i "trace\|error" || true)
if [ -z "$ERRORS" ]; then
echo "✓ Ошибок не найдено"
else
echo "⚠ Найдены ошибки в логах:"
echo "$ERRORS"
fi
echo ""
echo "=== Готово! ==="
echo ""
echo "Теперь проверьте:"
echo "1. Откройте https://odo.iieasy.ru"
echo "2. Должна появиться страница входа с формой и кнопкой 'iiEasy ID'"
echo "3. Если нужно применить логотипы, используйте Admin Panel:"
echo " Settings → Appearance → Logo"
echo ""
echo "Если ошибка 'trace' осталась, проверьте логи:"
echo " sudo docker compose logs $CONTAINER_NAME --tail 100"

132
scripts/fix_user_agent.sh Executable file
View File

@@ -0,0 +1,132 @@
#!/bin/sh
# Исправление бага с User-Agent в Open WebUI v0.8.3
# Проблема: User-Agent начинается с пробела, что вызывает ошибку "Invalid leading whitespace"
echo "Применение патча для исправления User-Agent..."
# Исправляем в retrieval.py
FILE="/app/backend/open_webui/routers/retrieval.py"
if [ -f "$FILE" ]; then
echo "Обработка файла: $FILE"
# Ищем все варианты проблемной строки (более агрессивный поиск)
PROBLEM_FOUND=0
# Проверяем наличие проблемной строки в разных вариантах
if grep -qE "(github\.com/open-webui|RAG Bot|https://github)" "$FILE" 2>/dev/null; then
PROBLEM_FOUND=1
fi
if [ "$PROBLEM_FOUND" -eq 1 ] || grep -q "User-Agent" "$FILE" 2>/dev/null; then
echo " Найдены строки с User-Agent, применяем патч..."
# Более агрессивная замена - ищем любые варианты с пробелом в начале
# Исправляем варианты с одинарными кавычками
sed -i "s/' (https:\/\/github\.com\/open-webui\/open-webui) RAG Bot'/'Open-WebUI-RAG-Bot'/g" "$FILE"
sed -i "s/ '(https:\/\/github\.com\/open-webui\/open-webui) RAG Bot'/'Open-WebUI-RAG-Bot'/g" "$FILE"
sed -i "s/'\(https:\/\/github\.com\/open-webui\/open-webui\) RAG Bot'/'Open-WebUI-RAG-Bot'/g" "$FILE"
# Исправляем варианты с двойными кавычками
sed -i 's/" (https:\/\/github\.com\/open-webui\/open-webui) RAG Bot"/"Open-WebUI-RAG-Bot"/g' "$FILE"
sed -i 's/ "(https:\/\/github\.com\/open-webui\/open-webui) RAG Bot"/"Open-WebUI-RAG-Bot"/g' "$FILE"
sed -i 's/"\(https:\/\/github\.com\/open-webui\/open-webui\) RAG Bot"/"Open-WebUI-RAG-Bot"/g' "$FILE"
# Исправляем варианты без кавычек
sed -i "s/ (https:\/\/github\.com\/open-webui\/open-webui) RAG Bot/Open-WebUI-RAG-Bot/g" "$FILE"
sed -i "s/ \(https:\/\/github\.com\/open-webui\/open-webui\) RAG Bot/Open-WebUI-RAG-Bot/g" "$FILE"
# Исправляем если используется в headers dict с пробелом
sed -i 's/"User-Agent": " (https:\/\/github\.com\/open-webui\/open-webui) RAG Bot"/"User-Agent": "Open-WebUI-RAG-Bot"/g' "$FILE"
sed -i "s/'User-Agent': ' (https:\/\/github\.com\/open-webui\/open-webui) RAG Bot'/'User-Agent': 'Open-WebUI-RAG-Bot'/g" "$FILE"
sed -i 's/"User-Agent": "(https:\/\/github\.com\/open-webui\/open-webui) RAG Bot"/"User-Agent": "Open-WebUI-RAG-Bot"/g' "$FILE"
# Исправляем f-string или конкатенацию
sed -i 's/User-Agent.*github\.com.*open-webui.*RAG Bot/User-Agent": "Open-WebUI-RAG-Bot/g' "$FILE"
# Универсальная замена - ищем любую строку с пробелом перед User-Agent значением
sed -i 's/"User-Agent": " [^"]*RAG Bot"/"User-Agent": "Open-WebUI-RAG-Bot"/g' "$FILE"
sed -i "s/'User-Agent': ' [^']*RAG Bot'/'User-Agent': 'Open-WebUI-RAG-Bot'/g" "$FILE"
# Исправляем вариант "External Web Loader"
sed -i 's/"User-Agent": " (https:\/\/github\.com\/open-webui\/open-webui) External Web Loader"/"User-Agent": "Open-WebUI-External-Web-Loader"/g' "$FILE"
# Универсальная замена для любых User-Agent с пробелом и github.com/open-webui
sed -i 's/"User-Agent": " \([^"]*github\.com\/open-webui[^"]*\)"/"User-Agent": "Open-WebUI-Bot"/g' "$FILE"
sed -i "s/'User-Agent': ' \([^']*github\.com\/open-webui[^']*\)'/'User-Agent': 'Open-WebUI-Bot'/g" "$FILE"
echo "✓ Патч применен к $FILE"
else
echo " Проблемная строка не найдена в $FILE (возможно, уже исправлена)"
fi
else
echo "⚠ Файл $FILE не найден"
fi
# Исправляем в middleware.py
FILE2="/app/backend/open_webui/utils/middleware.py"
if [ -f "$FILE2" ]; then
echo "Обработка файла: $FILE2"
PROBLEM_FOUND2=0
if grep -qE "(github\.com/open-webui|RAG Bot|https://github)" "$FILE2" 2>/dev/null; then
PROBLEM_FOUND2=1
fi
if [ "$PROBLEM_FOUND2" -eq 1 ] || grep -q "User-Agent" "$FILE2" 2>/dev/null; then
echo " Найдены строки с User-Agent, применяем патч..."
# Те же замены что и для retrieval.py
sed -i "s/' (https:\/\/github\.com\/open-webui\/open-webui) RAG Bot'/'Open-WebUI-RAG-Bot'/g" "$FILE2"
sed -i "s/ '(https:\/\/github\.com\/open-webui\/open-webui) RAG Bot'/'Open-WebUI-RAG-Bot'/g" "$FILE2"
sed -i 's/" (https:\/\/github\.com\/open-webui\/open-webui) RAG Bot"/"Open-WebUI-RAG-Bot"/g' "$FILE2"
sed -i 's/ "(https:\/\/github\.com\/open-webui\/open-webui) RAG Bot"/"Open-WebUI-RAG-Bot"/g' "$FILE2"
sed -i "s/ (https:\/\/github\.com\/open-webui\/open-webui) RAG Bot/Open-WebUI-RAG-Bot/g" "$FILE2"
sed -i 's/"User-Agent": " (https:\/\/github\.com\/open-webui\/open-webui) RAG Bot"/"User-Agent": "Open-WebUI-RAG-Bot"/g' "$FILE2"
sed -i "s/'User-Agent': ' (https:\/\/github\.com\/open-webui\/open-webui) RAG Bot'/'User-Agent': 'Open-WebUI-RAG-Bot'/g" "$FILE2"
sed -i 's/"User-Agent": "(https:\/\/github\.com\/open-webui\/open-webui) RAG Bot"/"User-Agent": "Open-WebUI-RAG-Bot"/g' "$FILE2"
sed -i 's/User-Agent.*github\.com.*open-webui.*RAG Bot/User-Agent": "Open-WebUI-RAG-Bot/g' "$FILE2"
sed -i 's/"User-Agent": " [^"]*RAG Bot"/"User-Agent": "Open-WebUI-RAG-Bot"/g' "$FILE2"
sed -i "s/'User-Agent': ' [^']*RAG Bot'/'User-Agent': 'Open-WebUI-RAG-Bot'/g" "$FILE2"
# Исправляем вариант "External Web Loader"
sed -i 's/"User-Agent": " (https:\/\/github\.com\/open-webui\/open-webui) External Web Loader"/"User-Agent": "Open-WebUI-External-Web-Loader"/g' "$FILE2"
# Универсальная замена для любых User-Agent с пробелом и github.com/open-webui
sed -i 's/"User-Agent": " \([^"]*github\.com\/open-webui[^"]*\)"/"User-Agent": "Open-WebUI-Bot"/g' "$FILE2"
sed -i "s/'User-Agent': ' \([^']*github\.com\/open-webui[^']*\)'/'User-Agent': 'Open-WebUI-Bot'/g" "$FILE2"
echo "✓ Патч применен к $FILE2"
else
echo " Проблемная строка не найдена в $FILE2 (возможно, уже исправлена)"
fi
fi
# Ищем в других возможных файлах (более агрессивный поиск)
echo "Поиск проблемной строки во всех Python файлах..."
find /app/backend -name "*.py" -type f 2>/dev/null | while read pyfile; do
if [ "$pyfile" != "$FILE" ] && [ "$pyfile" != "$FILE2" ]; then
# Проверяем наличие проблемной строки в любом виде
if grep -qE "(github\.com/open-webui|RAG Bot|https://github)" "$pyfile" 2>/dev/null; then
echo " Обработка файла: $pyfile"
# Применяем все варианты замены
sed -i "s/' (https:\/\/github\.com\/open-webui\/open-webui) RAG Bot'/'Open-WebUI-RAG-Bot'/g" "$pyfile"
sed -i "s/ '(https:\/\/github\.com\/open-webui\/open-webui) RAG Bot'/'Open-WebUI-RAG-Bot'/g" "$pyfile"
sed -i 's/" (https:\/\/github\.com\/open-webui\/open-webui) RAG Bot"/"Open-WebUI-RAG-Bot"/g' "$pyfile"
sed -i 's/ "(https:\/\/github\.com\/open-webui\/open-webui) RAG Bot"/"Open-WebUI-RAG-Bot"/g' "$pyfile"
sed -i "s/ (https:\/\/github\.com\/open-webui\/open-webui) RAG Bot/Open-WebUI-RAG-Bot/g" "$pyfile"
sed -i 's/"User-Agent": " [^"]*RAG Bot"/"User-Agent": "Open-WebUI-RAG-Bot"/g' "$pyfile"
sed -i "s/'User-Agent': ' [^']*RAG Bot'/'User-Agent': 'Open-WebUI-RAG-Bot'/g" "$pyfile"
# Исправляем вариант "External Web Loader"
sed -i 's/"User-Agent": " (https:\/\/github\.com\/open-webui\/open-webui) External Web Loader"/"User-Agent": "Open-WebUI-External-Web-Loader"/g' "$pyfile"
# Универсальная замена
sed -i 's/"User-Agent": " \([^"]*github\.com\/open-webui[^"]*\)"/"User-Agent": "Open-WebUI-Bot"/g' "$pyfile"
sed -i "s/'User-Agent': ' \([^']*github\.com\/open-webui[^']*\)'/'User-Agent': 'Open-WebUI-Bot'/g" "$pyfile"
fi
fi
done
# Удаляем скомпилированные Python файлы (.pyc), чтобы они пересобрались
echo "Очистка скомпилированных файлов Python..."
find /app/backend -name "*.pyc" -delete 2>/dev/null
find /app/backend -name "__pycache__" -type d -exec rm -rf {} + 2>/dev/null
echo "Патч применен. Запуск Open WebUI..."

View File

@@ -0,0 +1,59 @@
#!/bin/sh
# Агрессивное исправление бага с User-Agent в Open WebUI
# Ищет и исправляет проблемную строку во всех возможных местах
echo "=== АГРЕССИВНОЕ ИСПРАВЛЕНИЕ USER-AGENT ==="
echo ""
# Находим все файлы с проблемной строкой
echo "Поиск всех файлов с проблемной строкой User-Agent..."
PROBLEM_FILES=$(docker exec open-webui find /app/backend -name "*.py" -type f -exec grep -l "github.com/open-webui" {} \; 2>/dev/null)
if [ -z "$PROBLEM_FILES" ]; then
echo "Файлы не найдены через docker exec, пробуем другой способ..."
# Альтернативный способ - ищем через grep в контейнере
docker exec open-webui sh -c 'find /app/backend -name "*.py" -type f | xargs grep -l "github.com/open-webui" 2>/dev/null' | while read pyfile; do
if [ -n "$pyfile" ]; then
echo "Исправление файла: $pyfile"
docker exec open-webui sed -i "s/' (https:\/\/github\.com\/open-webui\/open-webui) RAG Bot'/'Open-WebUI-RAG-Bot'/g" "$pyfile"
docker exec open-webui sed -i 's/" (https:\/\/github\.com\/open-webui\/open-webui) RAG Bot"/"Open-WebUI-RAG-Bot"/g' "$pyfile"
docker exec open-webui sed -i "s/ (https:\/\/github\.com\/open-webui\/open-webui) RAG Bot/Open-WebUI-RAG-Bot/g" "$pyfile"
docker exec open-webui sed -i 's/"User-Agent": " [^"]*RAG Bot"/"User-Agent": "Open-WebUI-RAG-Bot"/g' "$pyfile"
docker exec open-webui sed -i "s/'User-Agent': ' [^']*RAG Bot'/'User-Agent': 'Open-WebUI-RAG-Bot'/g" "$pyfile"
fi
done
else
echo "$PROBLEM_FILES" | while read pyfile; do
echo "Исправление файла: $pyfile"
docker exec open-webui sed -i "s/' (https:\/\/github\.com\/open-webui\/open-webui) RAG Bot'/'Open-WebUI-RAG-Bot'/g" "$pyfile"
docker exec open-webui sed -i 's/" (https:\/\/github\.com\/open-webui\/open-webui) RAG Bot"/"Open-WebUI-RAG-Bot"/g' "$pyfile"
docker exec open-webui sed -i "s/ (https:\/\/github\.com\/open-webui\/open-webui) RAG Bot/Open-WebUI-RAG-Bot/g" "$pyfile"
docker exec open-webui sed -i 's/"User-Agent": " [^"]*RAG Bot"/"User-Agent": "Open-WebUI-RAG-Bot"/g' "$pyfile"
docker exec open-webui sed -i "s/'User-Agent': ' [^']*RAG Bot'/'User-Agent': 'Open-WebUI-RAG-Bot'/g" "$pyfile"
done
fi
# Очищаем кеш Python
echo ""
echo "Очистка кеша Python..."
docker exec open-webui find /app/backend -name "*.pyc" -delete 2>/dev/null
docker exec open-webui find /app/backend -name "__pycache__" -type d -exec rm -rf {} + 2>/dev/null || true
# Проверяем результат
echo ""
echo "Проверка результата..."
RAG_BOT_COUNT=$(docker exec open-webui grep -r "github.com/open-webui.*RAG Bot" /app/backend 2>/dev/null | wc -l)
if [ "$RAG_BOT_COUNT" -eq 0 ]; then
echo "✓ Проблемная строка не найдена - патч применен успешно"
else
echo "⚠ Найдено вхождений проблемной строки: $RAG_BOT_COUNT"
echo "Проблемные файлы:"
docker exec open-webui grep -r "github.com/open-webui.*RAG Bot" /app/backend 2>/dev/null | cut -d: -f1 | sort -u
fi
echo ""
echo "Перезапуск Open WebUI..."
docker restart open-webui
echo ""
echo "Готово! Подождите 15 секунд и проверьте поиск."

42
scripts/fix_user_agent_final.sh Executable file
View File

@@ -0,0 +1,42 @@
#!/bin/sh
# Финальное исправление всех вариантов User-Agent с пробелом в начале
echo "=== ФИНАЛЬНОЕ ИСПРАВЛЕНИЕ USER-AGENT ==="
echo ""
# Исправляем "External Web Loader" вариант
echo "Исправление external_web.py..."
docker exec open-webui sed -i 's/"User-Agent": " (https:\/\/github\.com\/open-webui\/open-webui) External Web Loader"/"User-Agent": "Open-WebUI-External-Web-Loader"/g' /app/backend/open_webui/retrieval/loaders/external_web.py
# Исправляем "RAG Bot" вариант (на всякий случай еще раз)
echo "Исправление всех вариантов RAG Bot..."
docker exec open-webui find /app/backend -name "*.py" -type f -exec sed -i 's/"User-Agent": " (https:\/\/github\.com\/open-webui\/open-webui) RAG Bot"/"User-Agent": "Open-WebUI-RAG-Bot"/g' {} \;
docker exec open-webui find /app/backend -name "*.py" -type f -exec sed -i "s/'User-Agent': ' (https:\/\/github\.com\/open-webui\/open-webui) RAG Bot'/'User-Agent': 'Open-WebUI-RAG-Bot'/g" {} \;
# Универсальная замена - любой User-Agent с пробелом в начале
echo "Универсальная замена всех User-Agent с пробелом..."
docker exec open-webui find /app/backend -name "*.py" -type f -exec sed -i 's/"User-Agent": " \([^"]*github\.com\/open-webui[^"]*\)"/"User-Agent": "Open-WebUI-Bot"/g' {} \;
docker exec open-webui find /app/backend -name "*.py" -type f -exec sed -i "s/'User-Agent': ' \([^']*github\.com\/open-webui[^']*\)'/'User-Agent': 'Open-WebUI-Bot'/g" {} \;
# Очистка кеша
echo "Очистка кеша Python..."
docker exec open-webui find /app/backend -name "*.pyc" -delete 2>/dev/null
docker exec open-webui find /app/backend -name "__pycache__" -type d -exec rm -rf {} + 2>/dev/null || true
# Проверка
echo ""
echo "Проверка результата..."
PROBLEM_COUNT=$(docker exec open-webui grep -rn '"User-Agent": " (' /app/backend --include="*.py" 2>/dev/null | wc -l)
if [ "$PROBLEM_COUNT" -eq 0 ]; then
echo "✓ Все проблемные строки исправлены!"
else
echo "⚠ Найдено проблемных строк: $PROBLEM_COUNT"
docker exec open-webui grep -rn '"User-Agent": " (' /app/backend --include="*.py" 2>/dev/null
fi
echo ""
echo "Перезапуск Open WebUI..."
docker restart open-webui
echo ""
echo "✓ Готово! Подождите 15 секунд и проверьте поиск."

35
scripts/init-logos.sh Normal file
View File

@@ -0,0 +1,35 @@
#!/bin/bash
# Скрипт для автоматической замены логотипов при запуске контейнера
# Этот скрипт можно запускать при каждом старте контейнера
MEDIA_DIR="/app/media"
MAX_RETRIES=10
RETRY_DELAY=2
# Ждем пока контейнер полностью запустится
for i in $(seq 1 $MAX_RETRIES); do
if curl -f http://localhost:8080/health >/dev/null 2>&1; then
break
fi
sleep $RETRY_DELAY
done
# Находим все favicon и logo файлы
find /app -type f \( -name "favicon.png" -o -name "favicon.ico" -o -name "logo.png" -o -name "logo.svg" \) 2>/dev/null | while read file; do
dir=$(dirname "$file")
name=$(basename "$file")
# Заменяем favicon
if [[ "$name" == favicon* ]] && [ -f "$MEDIA_DIR/favicon.png" ]; then
cp "$MEDIA_DIR/favicon.png" "$file" 2>/dev/null || true
# Также создаем .ico
cp "$MEDIA_DIR/favicon.png" "$dir/favicon.ico" 2>/dev/null || true
fi
# Заменяем logo
if [[ "$name" == logo* ]] && [ -f "$MEDIA_DIR/logo.png" ]; then
cp "$MEDIA_DIR/logo.png" "$file" 2>/dev/null || true
# Также создаем .svg
cp "$MEDIA_DIR/logo.png" "$dir/logo.svg" 2>/dev/null || true
fi
done

45
scripts/init.sh Executable file
View File

@@ -0,0 +1,45 @@
#!/bin/bash
# Скрипт инициализации iiEasy AI-платформы
# Выполняет ребрендинг и загрузку модели Ollama
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
echo "=== Инициализация iiEasy AI-платформы ==="
echo ""
cd "$PROJECT_DIR"
# Проверка наличия контейнеров
if ! docker ps --format '{{.Names}}' | grep -q "^open-webui$"; then
echo "Ошибка: Контейнер open-webui не запущен. Запустите: docker compose up -d"
exit 1
fi
if ! docker ps --format '{{.Names}}' | grep -q "^ollama$"; then
echo "Ошибка: Контейнер ollama не запущен. Запустите: docker compose up -d"
exit 1
fi
# 1. Ребрендинг Open WebUI
echo "1. Применение ребрендинга Open WebUI..."
"$SCRIPT_DIR/rebrand.sh"
echo ""
echo "2. Загрузка модели Ollama (gemma3n:e4b-it-fp16)..."
echo " Это может занять несколько минут в зависимости от скорости интернета..."
docker exec ollama ollama pull gemma3n:e4b-it-fp16
echo ""
echo "3. Проверка загруженных моделей..."
docker exec ollama ollama list
echo ""
echo "=== Инициализация завершена! ==="
echo ""
echo "Проверьте:"
echo " - Open WebUI: https://odo.iieasy.ru"
echo " - Логотип и ребрендинг применены"
echo " - Модель gemma3n:e4b-it-fp16 доступна в Ollama"

58
scripts/install_vision_model.sh Executable file
View File

@@ -0,0 +1,58 @@
#!/bin/bash
# Установка специализированной vision модели для Ollama
set -e
CONTAINER_OLLAMA="ollama"
MODEL_CHOICE="${1:-llava}"
echo "=== Установка Vision модели для Ollama ==="
echo ""
# Определяем команду docker
DOCKER_CMD="docker"
if ! docker ps >/dev/null 2>&1; then
DOCKER_CMD="sudo docker"
fi
echo "Доступные vision модели:"
echo " 1. llava:latest (LLaVA 1.6) - 7B, лучший выбор для vision"
echo " 2. bakllava:latest (BakLLaVA) - 7B, Mistral + LLaVA"
echo " 3. llama3.2-vision:latest (Llama 3.2 Vision) - 11B, требует 8GB VRAM"
echo ""
if [ "$MODEL_CHOICE" = "llava" ]; then
MODEL="llava:latest"
echo "Выбрана модель: $MODEL (LLaVA 1.6)"
elif [ "$MODEL_CHOICE" = "bakllava" ]; then
MODEL="bakllava:latest"
echo "Выбрана модель: $MODEL (BakLLaVA)"
elif [ "$MODEL_CHOICE" = "llama3.2" ]; then
MODEL="llama3.2-vision:latest"
echo "Выбрана модель: $MODEL (Llama 3.2 Vision)"
else
MODEL="llava:latest"
echo "Используется модель по умолчанию: $MODEL"
fi
echo ""
echo "Загрузка модели $MODEL..."
echo "Это может занять несколько минут в зависимости от скорости интернета..."
echo ""
$DOCKER_CMD exec $CONTAINER_OLLAMA ollama pull "$MODEL" || {
echo "✗ Ошибка при загрузке модели"
exit 1
}
echo ""
echo "✓ Модель загружена!"
echo ""
echo "Проверка установленных моделей:"
$DOCKER_CMD exec $CONTAINER_OLLAMA ollama list
echo ""
echo "=== Готово! ==="
echo ""
echo "Теперь в Open WebUI выберите модель: $MODEL"
echo "И попробуйте загрузить изображение и задать вопрос о нем."

248
scripts/rebrand.sh Executable file
View File

@@ -0,0 +1,248 @@
#!/bin/bash
# Скрипт ребрендинга Open WebUI для iiEasy
# ВНИМАНИЕ: Этот скрипт может ломать OAuth!
# Рекомендуется использовать rebrand_complete.sh для полного ребрендинга
# Заменяет логотипы, favicon, удаляет упоминания Open WebUI, отключает проверку обновлений
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
MEDIA_DIR="$PROJECT_DIR/media"
CONTAINER_NAME="open-webui"
echo "=== Ребрендинг Open WebUI для iiEasy ==="
# Проверка наличия контейнера
if ! docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
echo "Ошибка: Контейнер ${CONTAINER_NAME} не найден. Запустите docker-compose up -d сначала."
exit 1
fi
# Проверка, что контейнер запущен
if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
echo "Ошибка: Контейнер ${CONTAINER_NAME} не запущен. Запустите docker-compose up -d."
exit 1
fi
echo "1. Замена логотипов и favicon..."
# Важно: Open WebUI может использовать скомпилированные статические файлы
# Нужно найти правильные пути и заменить файлы там, где они реально используются
# Определяем пути для статических файлов в Open WebUI
# Open WebUI использует /app/web/build/_app/immutable/ для статических файлов
STATIC_DIRS=(
"/app/web/build/_app/immutable"
"/app/web/static"
"/app/web/build"
"/app/backend/static"
"/app/static"
"/app/public"
)
# Находим существующую директорию со статическими файлами
STATIC_DIR=""
for dir in "${STATIC_DIRS[@]}"; do
if docker exec "${CONTAINER_NAME}" test -d "$dir" 2>/dev/null; then
STATIC_DIR="$dir"
echo " Найдена директория статических файлов: $STATIC_DIR"
break
fi
done
if [ -z "$STATIC_DIR" ]; then
echo " Предупреждение: Директория статических файлов не найдена, пробуем найти через поиск favicon..."
# Ищем где находятся существующие favicon файлы
FAVICON_PATH=$(docker exec "${CONTAINER_NAME}" find /app -name "favicon.png" -o -name "favicon.ico" 2>/dev/null | head -1)
if [ -n "$FAVICON_PATH" ]; then
STATIC_DIR=$(dirname "$FAVICON_PATH")
echo " Найдена директория через поиск favicon: $STATIC_DIR"
else
STATIC_DIR="/app/web/build/_app/immutable"
echo " Используем стандартный путь: $STATIC_DIR"
# Создаем директорию если её нет
docker exec "${CONTAINER_NAME}" mkdir -p "$STATIC_DIR" 2>/dev/null || true
fi
fi
# Копирование логотипов (приоритет: PNG > SVG)
# Копируем во все найденные директории
for target_dir in "$STATIC_DIR" "/app/web/build/_app/immutable" "/app/web/static" "/app/web/build"; do
if docker exec "${CONTAINER_NAME}" test -d "$target_dir" 2>/dev/null || [ "$target_dir" = "$STATIC_DIR" ]; then
echo " Копирование в $target_dir..."
# Логотипы
if [ -f "$MEDIA_DIR/logo.png" ]; then
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${target_dir}/logo.png" 2>/dev/null || true
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${target_dir}/logo.svg" 2>/dev/null || true
elif [ -f "$MEDIA_DIR/logo-light.svg" ]; then
docker cp "$MEDIA_DIR/logo-light.svg" "${CONTAINER_NAME}:${target_dir}/logo.svg" 2>/dev/null || true
fi
if [ -f "$MEDIA_DIR/logo-dark.svg" ]; then
docker cp "$MEDIA_DIR/logo-dark.svg" "${CONTAINER_NAME}:${target_dir}/logo-dark.svg" 2>/dev/null || true
fi
# Favicon
if [ -f "$MEDIA_DIR/favicon.ico" ]; then
docker cp "$MEDIA_DIR/favicon.ico" "${CONTAINER_NAME}:${target_dir}/favicon.ico" 2>/dev/null || true
elif [ -f "$MEDIA_DIR/favicon.png" ]; then
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${target_dir}/favicon.png" 2>/dev/null || true
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${target_dir}/favicon.ico" 2>/dev/null || true
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${target_dir}/favicon-96x96.png" 2>/dev/null || true
elif [ -f "$MEDIA_DIR/favicon.svg" ]; then
docker cp "$MEDIA_DIR/favicon.svg" "${CONTAINER_NAME}:${target_dir}/favicon.svg" 2>/dev/null || true
fi
fi
done
# Поиск и замена существующих favicon и logo файлов везде в /app
echo " Поиск существующих favicon и logo файлов для замены..."
EXISTING_FAVICONS=$(docker exec "${CONTAINER_NAME}" find /app -type f \( -name "favicon.png" -o -name "favicon.ico" -o -name "favicon.svg" \) 2>/dev/null | head -10)
EXISTING_LOGOS=$(docker exec "${CONTAINER_NAME}" find /app -type f \( -name "logo.png" -o -name "logo.svg" \) 2>/dev/null | head -10)
echo " Найдено favicon файлов: $(echo "$EXISTING_FAVICONS" | wc -l)"
echo " Найдено logo файлов: $(echo "$EXISTING_LOGOS" | wc -l)"
# Заменяем существующие favicon файлы
if [ -f "$MEDIA_DIR/favicon.png" ]; then
echo "$EXISTING_FAVICONS" | while read -r favicon_file; do
if [ -n "$favicon_file" ]; then
echo " Замена: $favicon_file"
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${favicon_file}" 2>/dev/null || true
# Также создаем .ico версию рядом
favicon_dir=$(dirname "$favicon_file")
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${favicon_dir}/favicon.ico" 2>/dev/null || true
fi
done
fi
# Заменяем существующие logo файлы
if [ -f "$MEDIA_DIR/logo.png" ]; then
echo "$EXISTING_LOGOS" | while read -r logo_file; do
if [ -n "$logo_file" ]; then
echo " Замена: $logo_file"
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${logo_file}" 2>/dev/null || true
fi
done
fi
# Также копируем в корень public для веб-доступа
PUBLIC_DIRS=(
"/app/web/public"
"/app/public"
"/app/backend/public"
)
for pub_dir in "${PUBLIC_DIRS[@]}"; do
if docker exec "${CONTAINER_NAME}" test -d "$pub_dir" 2>/dev/null; then
echo " Копирование в $pub_dir..."
if [ -f "$MEDIA_DIR/logo.png" ]; then
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${pub_dir}/logo.png" 2>/dev/null || true
fi
if [ -f "$MEDIA_DIR/favicon.png" ]; then
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${pub_dir}/favicon.png" 2>/dev/null || true
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${pub_dir}/favicon.ico" 2>/dev/null || true
fi
break
fi
done
echo " Примечание: Если логотипы не изменились, настройте их через Admin Panel:"
echo " Settings → Appearance → Logo (загрузите файлы из /app/media/)"
echo "2. Поиск и замена текстовых упоминаний 'Open WebUI'..."
# Поиск файлов с упоминаниями Open WebUI в статических файлах и конфигурации
# ВАЖНО: ИСКЛЮЧАЕМ ВСЕ файлы, связанные с OAuth/Authentik/аутентификацией
# Это включает: oauth.py, auth.py, login.py, и все файлы в директориях oauth, oidc, auth, login
docker exec "${CONTAINER_NAME}" find /app -type f \( -name "*.py" -o -name "*.html" -o -name "*.js" -o -name "*.jsx" -o -name "*.ts" -o -name "*.tsx" -o -name "*.json" -o -name "*.svelte" \) \
! -path "*/oauth*" ! -path "*/oidc*" ! -path "*authentik*" ! -path "*openid*" \
! -name "*oauth*" ! -name "*oidc*" ! -name "*auth*" ! -name "*login*" \
! -path "*/utils/oauth*" ! -path "*/utils/auth*" ! -path "*/backend/open_webui/utils/oauth*" \
-exec grep -l "Open WebUI\|open-webui\|openwebui\|\(Open WebUI\)" {} \; 2>/dev/null | while read file; do
echo " Обработка: $file"
# Замена "Open WebUI" на "iiEasyWeb" (только в тексте интерфейса, не в URL)
docker exec "${CONTAINER_NAME}" sed -i 's/Open WebUI/iiEasyWeb/g' "$file" 2>/dev/null || true
# Удаление "(Open WebUI)" в скобках - заменяем на пустую строку или только "iiEasyWeb"
docker exec "${CONTAINER_NAME}" sed -i 's/(Open WebUI)//g' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i 's/\(Open WebUI\)//g' "$file" 2>/dev/null || true
# Замена "iiEasyWeb (Open WebUI)" на просто "iiEasyWeb"
docker exec "${CONTAINER_NAME}" sed -i 's/iiEasyWeb (Open WebUI)/iiEasyWeb/g' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i 's/iiEasyWeb \(Open WebUI\)/iiEasyWeb/g' "$file" 2>/dev/null || true
# ВАЖНО: НЕ заменяем open-webui в нижнем регистре, так как это может быть частью URL или конфигурации OAuth
done
# Специальная обработка для удаления "(Open WebUI)" из заголовков и описаний
echo "3. Удаление упоминаний '(Open WebUI)' из интерфейса..."
# ИСКЛЮЧАЕМ файлы OAuth/Authentik
docker exec "${CONTAINER_NAME}" find /app -type f \( -name "*.html" -o -name "*.js" -o -name "*.jsx" -o -name "*.ts" -o -name "*.tsx" -o -name "*.svelte" \) \
! -path "*/oauth*" ! -path "*/oidc*" ! -path "*authentik*" ! -path "*openid*" \
-exec grep -l "(Open WebUI)\|\(Open WebUI\)" {} \; 2>/dev/null | while read file; do
echo " Удаление '(Open WebUI)' из: $file"
# Удаляем различные варианты написания в скобках
docker exec "${CONTAINER_NAME}" sed -i 's/(Open WebUI)//g' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i 's/\(Open WebUI\)//g' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i 's/ (Open WebUI)//g' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i 's/ \(Open WebUI\)//g' "$file" 2>/dev/null || true
done
echo "4. Отключение проверки обновлений..."
# Поиск и отключение проверки обновлений через GitHub API
# ИСКЛЮЧАЕМ файлы OAuth/Authentik
docker exec "${CONTAINER_NAME}" find /app/backend -type f \( -name "*.py" -o -name "*.js" \) \
! -path "*/oauth*" ! -path "*/oidc*" ! -path "*authentik*" ! -path "*openid*" \
-exec grep -l "github.com.*releases\|check.*update\|update.*check" {} \; 2>/dev/null | while read file; do
echo " Отключение проверки обновлений в: $file"
# Комментирование вызовов GitHub API для проверки обновлений
docker exec "${CONTAINER_NAME}" sed -i 's|https://api.github.com/repos/open-webui|# https://api.github.com/repos/open-webui|g' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i 's|github.com/open-webui|# github.com/open-webui|g' "$file" 2>/dev/null || true
done
# Отключение проверки обновлений через переменные окружения (уже настроено в docker-compose.yml)
echo " Проверка обновлений отключена через переменные окружения"
echo "5. Удаление аналитики и телеметрии..."
# Поиск и отключение аналитики (более аккуратно, чтобы не сломать код)
# Комментируем только целые строки с импортами аналитики, а не части строк
# ИСКЛЮЧАЕМ файлы OAuth/Authentik
docker exec "${CONTAINER_NAME}" find /app/backend -type f \( -name "*.py" -o -name "*.js" \) \
! -path "*/oauth*" ! -path "*/oidc*" ! -path "*authentik*" ! -path "*openid*" \
-exec grep -l "from.*telemetry\|import.*telemetry\|analytics\|tracking\|gtag\|ga(\|google-analytics" {} \; 2>/dev/null | while read file; do
echo " Отключение аналитики в: $file"
# Комментируем только целые строки импорта, начинающиеся с from или import
docker exec "${CONTAINER_NAME}" sed -i '/^[[:space:]]*from.*telemetry/s/^/# /' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/^[[:space:]]*import.*telemetry/s/^/# /' "$file" 2>/dev/null || true
done
echo "6. Удаление 'Powered by' футеров..."
# Поиск футеров с упоминанием Open WebUI
# ИСКЛЮЧАЕМ файлы OAuth/Authentik
docker exec "${CONTAINER_NAME}" find /app/backend -type f \( -name "*.html" -o -name "*.js" -o -name "*.tsx" -o -name "*.jsx" \) \
! -path "*/oauth*" ! -path "*/oidc*" ! -path "*authentik*" ! -path "*openid*" \
-exec grep -l "Powered by\|powered by" {} \; 2>/dev/null | while read file; do
echo " Удаление футера в: $file"
docker exec "${CONTAINER_NAME}" sed -i '/Powered by.*[Oo]pen.*[Ww]eb[Uu][Ii]/d' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/powered by.*[Oo]pen.*[Ww]eb[Uu][Ii]/d' "$file" 2>/dev/null || true
done
echo "7. Перезапуск контейнера для применения изменений..."
docker restart "${CONTAINER_NAME}" >/dev/null 2>&1 || {
echo "Предупреждение: Не удалось перезапустить контейнер автоматически."
echo "Перезапустите вручную: docker restart ${CONTAINER_NAME}"
}
echo ""
echo "=== Ребрендинг завершен! ==="
echo ""
echo "Проверьте изменения:"
echo " 1. Откройте https://odo.iieasy.ru в браузере"
echo " 2. Проверьте логотип и favicon"
echo " 3. Проверьте отсутствие упоминаний 'Open WebUI'"
echo ""
echo "Примечание: Если изменения не отображаются, очистите кеш браузера."

126
scripts/rebrand_careful.sh Executable file
View File

@@ -0,0 +1,126 @@
#!/bin/bash
# ОЧЕНЬ АККУРАТНЫЙ ребрендинг Open WebUI для iiEasy
# Только безопасные замены текста, НЕ трогает код
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
MEDIA_DIR="$PROJECT_DIR/media"
CONTAINER_NAME="open-webui"
echo "=== АККУРАТНЫЙ ребрендинг Open WebUI для iiEasy ==="
echo "⚠ Только безопасные замены текста в интерфейсе"
echo "⚠ Код Python/JS НЕ изменяется"
echo "⚠ OAuth/Authentik полностью защищены"
echo ""
# Проверка наличия контейнера
if ! docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
echo "Ошибка: Контейнер ${CONTAINER_NAME} не найден."
exit 1
fi
if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
echo "Ошибка: Контейнер ${CONTAINER_NAME} не запущен."
exit 1
fi
echo "1. Замена логотипов и favicon..."
STATIC_DIRS=(
"/app/web/build/_app/immutable"
"/app/web/static"
"/app/web/build"
)
STATIC_DIR=""
for dir in "${STATIC_DIRS[@]}"; do
if docker exec "${CONTAINER_NAME}" test -d "$dir" 2>/dev/null; then
STATIC_DIR="$dir"
break
fi
done
if [ -z "$STATIC_DIR" ]; then
FAVICON_PATH=$(docker exec "${CONTAINER_NAME}" find /app/web -name "favicon.png" -o -name "favicon.ico" 2>/dev/null | head -1)
if [ -n "$FAVICON_PATH" ]; then
STATIC_DIR=$(dirname "$FAVICON_PATH")
else
STATIC_DIR="/app/web/build/_app/immutable"
fi
fi
# Копирование логотипов
if [ -f "$MEDIA_DIR/logo.png" ]; then
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${STATIC_DIR}/logo.png" 2>/dev/null || true
fi
if [ -f "$MEDIA_DIR/favicon.png" ]; then
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${STATIC_DIR}/favicon.png" 2>/dev/null || true
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${STATIC_DIR}/favicon.ico" 2>/dev/null || true
fi
echo "2. Замена текста 'Open WebUI' на 'iiEasyWeb' ТОЛЬКО в HTML/текстовых строках..."
# ТОЛЬКО фронтенд HTML/Svelte файлы - заменяем только в текстовом контенте
docker exec "${CONTAINER_NAME}" find /app/web -type f \( -name "*.html" -o -name "*.svelte" \) \
! -path "*/node_modules/*" ! -path "*/.next/*" ! -path "*/dist/*" \
2>/dev/null | while read file; do
# Заменяем только в текстовом контенте между тегами, не в атрибутах или коде
if docker exec "${CONTAINER_NAME}" grep -q "Open WebUI" "$file" 2>/dev/null; then
echo " HTML/Svelte: $file"
# Заменяем только "Open WebUI" (с заглавными) в тексте, не трогаем код
docker exec "${CONTAINER_NAME}" sed -i 's/>Open WebUI</>iiEasyWeb</g' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i 's/Open WebUI/iiEasyWeb/g' "$file" 2>/dev/null || true
fi
done
echo "3. Удаление '(Open WebUI)' из текста интерфейса..."
# Только удаляем текст в скобках из HTML
docker exec "${CONTAINER_NAME}" find /app/web -type f \( -name "*.html" -o -name "*.svelte" \) \
! -path "*/node_modules/*" ! -path "*/.next/*" ! -path "*/dist/*" \
2>/dev/null | while read file; do
if docker exec "${CONTAINER_NAME}" grep -q "(Open WebUI)" "$file" 2>/dev/null; then
echo " Удаление скобок: $file"
docker exec "${CONTAINER_NAME}" sed -i 's/(Open WebUI)//g' "$file" 2>/dev/null || true
fi
done
echo "4. Удаление 'Powered by Open WebUI' футеров..."
# Только удаляем строки с "Powered by"
docker exec "${CONTAINER_NAME}" find /app/web -type f \( -name "*.html" -o -name "*.svelte" \) \
! -path "*/node_modules/*" ! -path "*/.next/*" ! -path "*/dist/*" \
2>/dev/null | while read file; do
if docker exec "${CONTAINER_NAME}" grep -q "Powered by.*Open WebUI\|powered by.*Open WebUI" "$file" 2>/dev/null; then
echo " Удаление футера: $file"
docker exec "${CONTAINER_NAME}" sed -i '/Powered by.*Open WebUI/d' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/powered by.*Open WebUI/d' "$file" 2>/dev/null || true
fi
done
echo ""
echo "✓ Изменены ТОЛЬКО текстовые строки в HTML/Svelte"
echo "✓ Код Python/JS НЕ изменялся"
echo "✓ OAuth/Authentik полностью защищены"
echo ""
echo "5. Перезапуск контейнера..."
docker restart "${CONTAINER_NAME}" >/dev/null 2>&1 || {
echo "Предупреждение: Перезапустите вручную: docker restart ${CONTAINER_NAME}"
}
echo ""
echo "=== АККУРАТНЫЙ ребрендинг завершен! ==="
echo ""
echo "Проверьте:"
echo " 1. Откройте https://odo.iieasy.ru"
echo " 2. Все должно работать нормально"
echo " 3. Текст 'Open WebUI' заменен на 'iiEasyWeb' в интерфейсе"
echo ""
echo "Если что-то не работает, пересоздайте контейнер:"
echo " sudo docker compose stop open-webui"
echo " sudo docker compose rm -f open-webui"
echo " sudo docker compose up -d open-webui"

274
scripts/rebrand_complete.sh Executable file
View File

@@ -0,0 +1,274 @@
#!/bin/bash
# ПОЛНЫЙ ребрендинг Open WebUI для iiEasy
# Удаляет ВСЕ упоминания Open WebUI, отключает телеметрию
# НО защищает OAuth/Authentik файлы
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
MEDIA_DIR="$PROJECT_DIR/media"
CONTAINER_NAME="open-webui"
echo "=== ПОЛНЫЙ ребрендинг Open WebUI для iiEasy ==="
echo "✓ Удаление ВСЕХ упоминаний Open WebUI"
echo "✓ Отключение телеметрии и аналитики"
echo "✓ Защита OAuth/Authentik файлов"
echo ""
# Проверка наличия контейнера
if ! docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
echo "Ошибка: Контейнер ${CONTAINER_NAME} не найден. Запустите docker-compose up -d сначала."
exit 1
fi
# Проверка, что контейнер запущен
if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
echo "Ошибка: Контейнер ${CONTAINER_NAME} не запущен. Запустите docker-compose up -d."
exit 1
fi
echo "1. Замена логотипов и favicon..."
STATIC_DIRS=(
"/app/web/build/_app/immutable"
"/app/web/static"
"/app/web/build"
"/app/backend/static"
"/app/static"
"/app/public"
)
STATIC_DIR=""
for dir in "${STATIC_DIRS[@]}"; do
if docker exec "${CONTAINER_NAME}" test -d "$dir" 2>/dev/null; then
STATIC_DIR="$dir"
echo " Найдена директория: $STATIC_DIR"
break
fi
done
if [ -z "$STATIC_DIR" ]; then
FAVICON_PATH=$(docker exec "${CONTAINER_NAME}" find /app -name "favicon.png" -o -name "favicon.ico" 2>/dev/null | head -1)
if [ -n "$FAVICON_PATH" ]; then
STATIC_DIR=$(dirname "$FAVICON_PATH")
else
STATIC_DIR="/app/web/build/_app/immutable"
docker exec "${CONTAINER_NAME}" mkdir -p "$STATIC_DIR" 2>/dev/null || true
fi
fi
# Копирование логотипов
for target_dir in "$STATIC_DIR" "/app/web/build/_app/immutable" "/app/web/static" "/app/web/build"; do
if docker exec "${CONTAINER_NAME}" test -d "$target_dir" 2>/dev/null || [ "$target_dir" = "$STATIC_DIR" ]; then
echo " Копирование в $target_dir..."
if [ -f "$MEDIA_DIR/logo.png" ]; then
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${target_dir}/logo.png" 2>/dev/null || true
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${target_dir}/logo.svg" 2>/dev/null || true
elif [ -f "$MEDIA_DIR/logo-light.svg" ]; then
docker cp "$MEDIA_DIR/logo-light.svg" "${CONTAINER_NAME}:${target_dir}/logo.svg" 2>/dev/null || true
fi
if [ -f "$MEDIA_DIR/logo-dark.svg" ]; then
docker cp "$MEDIA_DIR/logo-dark.svg" "${CONTAINER_NAME}:${target_dir}/logo-dark.svg" 2>/dev/null || true
fi
if [ -f "$MEDIA_DIR/favicon.ico" ]; then
docker cp "$MEDIA_DIR/favicon.ico" "${CONTAINER_NAME}:${target_dir}/favicon.ico" 2>/dev/null || true
elif [ -f "$MEDIA_DIR/favicon.png" ]; then
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${target_dir}/favicon.png" 2>/dev/null || true
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${target_dir}/favicon.ico" 2>/dev/null || true
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${target_dir}/favicon-96x96.png" 2>/dev/null || true
elif [ -f "$MEDIA_DIR/favicon.svg" ]; then
docker cp "$MEDIA_DIR/favicon.svg" "${CONTAINER_NAME}:${target_dir}/favicon.svg" 2>/dev/null || true
fi
fi
done
# Замена существующих favicon и logo
EXISTING_FAVICONS=$(docker exec "${CONTAINER_NAME}" find /app -type f \( -name "favicon.png" -o -name "favicon.ico" -o -name "favicon.svg" \) 2>/dev/null | head -10)
EXISTING_LOGOS=$(docker exec "${CONTAINER_NAME}" find /app -type f \( -name "logo.png" -o -name "logo.svg" \) 2>/dev/null | head -10)
if [ -f "$MEDIA_DIR/favicon.png" ]; then
echo "$EXISTING_FAVICONS" | while read -r favicon_file; do
if [ -n "$favicon_file" ]; then
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${favicon_file}" 2>/dev/null || true
favicon_dir=$(dirname "$favicon_file")
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${favicon_dir}/favicon.ico" 2>/dev/null || true
fi
done
fi
if [ -f "$MEDIA_DIR/logo.png" ]; then
echo "$EXISTING_LOGOS" | while read -r logo_file; do
if [ -n "$logo_file" ]; then
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${logo_file}" 2>/dev/null || true
fi
done
fi
PUBLIC_DIRS=("/app/web/public" "/app/public" "/app/backend/public")
for pub_dir in "${PUBLIC_DIRS[@]}"; do
if docker exec "${CONTAINER_NAME}" test -d "$pub_dir" 2>/dev/null; then
if [ -f "$MEDIA_DIR/logo.png" ]; then
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${pub_dir}/logo.png" 2>/dev/null || true
fi
if [ -f "$MEDIA_DIR/favicon.png" ]; then
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${pub_dir}/favicon.png" 2>/dev/null || true
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${pub_dir}/favicon.ico" 2>/dev/null || true
fi
break
fi
done
echo "2. Удаление упоминаний 'Open WebUI' из фронтенда..."
# Изменяем ТОЛЬКО фронтенд файлы (веб-интерфейс)
docker exec "${CONTAINER_NAME}" find /app/web -type f \( -name "*.html" -o -name "*.js" -o -name "*.jsx" -o -name "*.ts" -o -name "*.tsx" -o -name "*.svelte" -o -name "*.json" \) \
! -path "*/node_modules/*" ! -path "*/.next/*" ! -path "*/dist/*" \
2>/dev/null | while read file; do
if docker exec "${CONTAINER_NAME}" grep -q "Open WebUI\|open-webui\|openwebui\|\(Open WebUI\)" "$file" 2>/dev/null; then
echo " Фронтенд: $file"
docker exec "${CONTAINER_NAME}" sed -i 's/Open WebUI/iiEasyWeb/g' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i 's/(Open WebUI)//g' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i 's/\(Open WebUI\)//g' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i 's/iiEasyWeb (Open WebUI)/iiEasyWeb/g' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i 's/iiEasyWeb \(Open WebUI\)/iiEasyWeb/g' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i 's/open-webui/iieasyweb/g' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i 's/openwebui/iieasyweb/g' "$file" 2>/dev/null || true
fi
done
echo "3. Удаление упоминаний 'Open WebUI' из бэкенда (кроме OAuth)..."
# Изменяем бэкенд Python файлы, НО исключаем OAuth/аутентификационные файлы
docker exec "${CONTAINER_NAME}" find /app/backend -type f -name "*.py" \
! -path "*/oauth*" ! -path "*/oidc*" ! -path "*authentik*" ! -path "*openid*" \
! -name "*oauth*" ! -name "*oidc*" ! -name "*auth*" ! -name "*login*" \
! -path "*/utils/oauth*" ! -path "*/utils/auth*" \
! -path "*/open_webui/utils/oauth*" ! -path "*/open_webui/utils/auth*" \
! -path "*/open_webui/main.py" \
2>/dev/null | while read file; do
if docker exec "${CONTAINER_NAME}" grep -q "Open WebUI\|open-webui\|openwebui\|\(Open WebUI\)" "$file" 2>/dev/null; then
echo " Бэкенд: $file"
# Замена только текста, не URL или конфигурации
docker exec "${CONTAINER_NAME}" sed -i 's/Open WebUI/iiEasyWeb/g' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i 's/(Open WebUI)//g' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i 's/\(Open WebUI\)//g' "$file" 2>/dev/null || true
# НЕ заменяем open-webui/openwebui в бэкенде - может быть частью конфигурации
fi
done
echo "4. Отключение телеметрии и аналитики в бэкенде..."
# Отключаем телеметрию в Python файлах, НО исключаем OAuth файлы
docker exec "${CONTAINER_NAME}" find /app/backend -type f -name "*.py" \
! -path "*/oauth*" ! -path "*/oidc*" ! -path "*authentik*" ! -path "*openid*" \
! -name "*oauth*" ! -name "*oidc*" ! -name "*auth*" ! -name "*login*" \
! -path "*/utils/oauth*" ! -path "*/utils/auth*" \
! -path "*/open_webui/utils/oauth*" ! -path "*/open_webui/utils/auth*" \
! -path "*/open_webui/main.py" \
-exec grep -l "from.*telemetry\|import.*telemetry\|analytics\|tracking\|gtag\|ga(\|google-analytics\|sentry\|posthog" {} \; 2>/dev/null | while read file; do
echo " Отключение телеметрии в: $file"
# Комментируем только целые строки импорта
docker exec "${CONTAINER_NAME}" sed -i '/^[[:space:]]*from.*telemetry/s/^/# /' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/^[[:space:]]*import.*telemetry/s/^/# /' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/^[[:space:]]*from.*analytics/s/^/# /' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/^[[:space:]]*import.*analytics/s/^/# /' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/^[[:space:]]*from.*tracking/s/^/# /' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/^[[:space:]]*import.*tracking/s/^/# /' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/^[[:space:]]*from.*sentry/s/^/# /' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/^[[:space:]]*import.*sentry/s/^/# /' "$file" 2>/dev/null || true
done
echo "5. Отключение телеметрии во фронтенде..."
docker exec "${CONTAINER_NAME}" find /app/web -type f \( -name "*.js" -o -name "*.ts" -o -name "*.jsx" -o -name "*.tsx" \) \
! -path "*/node_modules/*" ! -path "*/.next/*" ! -path "*/dist/*" \
-exec grep -l "telemetry\|analytics\|tracking\|gtag\|ga(\|google-analytics\|sentry\|posthog" {} \; 2>/dev/null | while read file; do
echo " Отключение телеметрии в: $file"
docker exec "${CONTAINER_NAME}" sed -i '/import.*telemetry/s/^/\/\/ /' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/from.*telemetry/s/^/\/\/ /' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/import.*analytics/s/^/\/\/ /' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/from.*analytics/s/^/\/\/ /' "$file" 2>/dev/null || true
done
echo "6. Отключение проверки обновлений..."
# Отключаем проверку обновлений в бэкенде (кроме OAuth)
docker exec "${CONTAINER_NAME}" find /app/backend -type f -name "*.py" \
! -path "*/oauth*" ! -path "*/oidc*" ! -path "*authentik*" ! -path "*openid*" \
! -name "*oauth*" ! -name "*oidc*" ! -name "*auth*" ! -name "*login*" \
! -path "*/utils/oauth*" ! -path "*/utils/auth*" \
! -path "*/open_webui/utils/oauth*" ! -path "*/open_webui/utils/auth*" \
! -path "*/open_webui/main.py" \
-exec grep -l "github.com.*releases\|check.*update\|update.*check" {} \; 2>/dev/null | while read file; do
echo " Отключение проверки обновлений в: $file"
docker exec "${CONTAINER_NAME}" sed -i 's|https://api.github.com/repos/open-webui|# https://api.github.com/repos/open-webui|g' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i 's|github.com/open-webui|# github.com/open-webui|g' "$file" 2>/dev/null || true
done
# Отключаем проверку обновлений во фронтенде
docker exec "${CONTAINER_NAME}" find /app/web -type f \( -name "*.js" -o -name "*.ts" \) \
! -path "*/node_modules/*" ! -path "*/.next/*" ! -path "*/dist/*" \
-exec grep -l "github.com.*releases\|check.*update\|update.*check" {} \; 2>/dev/null | while read file; do
echo " Отключение проверки обновлений в: $file"
docker exec "${CONTAINER_NAME}" sed -i 's|https://api.github.com/repos/open-webui|// https://api.github.com/repos/open-webui|g' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i 's|github.com/open-webui|// github.com/open-webui|g' "$file" 2>/dev/null || true
done
echo "7. Удаление 'Powered by' футеров..."
# Удаляем футеры из фронтенда
docker exec "${CONTAINER_NAME}" find /app/web -type f \( -name "*.html" -o -name "*.js" -o -name "*.tsx" -o -name "*.jsx" -o -name "*.svelte" \) \
! -path "*/node_modules/*" ! -path "*/.next/*" ! -path "*/dist/*" \
2>/dev/null | while read file; do
if docker exec "${CONTAINER_NAME}" grep -q "Powered by\|powered by" "$file" 2>/dev/null; then
echo " Удаление футера из: $file"
docker exec "${CONTAINER_NAME}" sed -i '/Powered by.*[Oo]pen.*[Ww]eb[Uu][Ii]/d' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/powered by.*[Oo]pen.*[Ww]eb[Uu][Ii]/d' "$file" 2>/dev/null || true
fi
done
# Удаляем футеры из бэкенда (кроме OAuth)
docker exec "${CONTAINER_NAME}" find /app/backend -type f \( -name "*.py" -o -name "*.html" \) \
! -path "*/oauth*" ! -path "*/oidc*" ! -path "*authentik*" ! -path "*openid*" \
! -name "*oauth*" ! -name "*oidc*" ! -name "*auth*" ! -name "*login*" \
! -path "*/utils/oauth*" ! -path "*/utils/auth*" \
! -path "*/open_webui/utils/oauth*" ! -path "*/open_webui/utils/auth*" \
! -path "*/open_webui/main.py" \
2>/dev/null | while read file; do
if docker exec "${CONTAINER_NAME}" grep -q "Powered by\|powered by" "$file" 2>/dev/null; then
echo " Удаление футера из: $file"
docker exec "${CONTAINER_NAME}" sed -i '/Powered by.*[Oo]pen.*[Ww]eb[Uu][Ii]/d' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/powered by.*[Oo]pen.*[Ww]eb[Uu][Ii]/d' "$file" 2>/dev/null || true
fi
done
echo ""
echo "✓ Бэкенд файлы обработаны (телеметрия отключена)"
echo "✓ OAuth/Authentik файлы защищены"
echo "✓ Фронтенд файлы обработаны"
echo ""
echo "8. Перезапуск контейнера..."
docker restart "${CONTAINER_NAME}" >/dev/null 2>&1 || {
echo "Предупреждение: Не удалось перезапустить контейнер автоматически."
echo "Перезапустите вручную: docker restart ${CONTAINER_NAME}"
}
echo ""
echo "=== ПОЛНЫЙ ребрендинг завершен! ==="
echo ""
echo "Проверьте:"
echo " 1. Откройте https://odo.iieasy.ru"
echo " 2. Проверьте отсутствие упоминаний 'Open WebUI'"
echo " 3. Проверьте OAuth - должен работать нормально"
echo " 4. Проверьте отсутствие телеметрии (DevTools → Network)"
echo ""
echo "Если OAuth не работает, пересоздайте контейнер:"
echo " sudo docker compose stop open-webui"
echo " sudo docker compose rm -f open-webui"
echo " sudo docker compose up -d open-webui"

134
scripts/rebrand_fast.sh Executable file
View File

@@ -0,0 +1,134 @@
#!/bin/bash
# БЫСТРЫЙ ребрендинг - оптимизированная версия
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
MEDIA_DIR="$PROJECT_DIR/media"
CONTAINER_NAME="open-webui"
echo "=== БЫСТРЫЙ ребрендинг Open WebUI для iiEasy ==="
echo ""
if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
echo "Ошибка: Контейнер ${CONTAINER_NAME} не запущен."
exit 1
fi
echo "1. Замена логотипов..."
STATIC_DIRS=(
"/app/web/build/_app/immutable"
"/app/web/static"
"/app/web/build"
)
for dir in "${STATIC_DIRS[@]}"; do
if docker exec "${CONTAINER_NAME}" test -d "$dir" 2>/dev/null; then
if [ -f "$MEDIA_DIR/logo.png" ]; then
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${dir}/logo.png" 2>/dev/null || true
fi
if [ -f "$MEDIA_DIR/favicon.png" ]; then
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${dir}/favicon.png" 2>/dev/null || true
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${dir}/favicon.ico" 2>/dev/null || true
fi
fi
done
echo "2. БЫСТРОЕ удаление '(Open WebUI)' - поиск только в текстовых файлах..."
# ОПТИМИЗАЦИЯ: Сначала находим файлы с текстом через grep -l (быстро)
# Обрабатываем только нужные типы файлов
FILES=$(docker exec "${CONTAINER_NAME}" find /app -type f \
\( -name "*.html" -o -name "*.svelte" -o -name "*.js" -o -name "*.jsx" -o -name "*.ts" -o -name "*.tsx" -o -name "*.json" -o -name "*.css" -o -name "*.mjs" \) \
! -path "*/node_modules/*" ! -path "*/.next/*" \
! -path "*/oauth*" ! -path "*/oidc*" ! -path "*authentik*" ! -path "*openid*" \
-exec grep -lE "(Open WebUI)|\(Open WebUI\)|iiEasyWeb.*Open.*WebUI" {} \; 2>/dev/null)
if [ -z "$FILES" ]; then
echo " Файлов с '(Open WebUI)' не найдено"
else
COUNT=$(echo "$FILES" | wc -l)
echo " Найдено файлов: $COUNT"
echo "$FILES" | while read file; do
if [ -n "$file" ]; then
# Один sed с несколькими командами - быстрее
docker exec "${CONTAINER_NAME}" sed -i \
-e 's/(Open WebUI)//g' \
-e 's/\(Open WebUI\)//g' \
-e 's/ (Open WebUI)//g' \
-e 's/ \(Open WebUI\)//g' \
-e 's/iiEasyWeb (Open WebUI)/iiEasyWeb/g' \
-e 's/iiEasyWeb \(Open WebUI\)/iiEasyWeb/g' \
-e 's/iiEasyWeb(Open WebUI)/iiEasyWeb/g' \
-e "s/'(Open WebUI)'//g" \
-e 's/"(Open WebUI)"//g' \
-e 's/`(Open WebUI)`//g' \
-e 's/Войти в iiEasyWeb (Open WebUI)/Войти в iiEasyWeb/g' \
"$file" 2>/dev/null || true
fi
done
echo " Обработка завершена"
fi
echo "3. Замена ссылок на документацию..."
DOC_FILES=$(docker exec "${CONTAINER_NAME}" find /app -type f \
\( -name "*.html" -o -name "*.svelte" -o -name "*.js" -o -name "*.jsx" -o -name "*.ts" -o -name "*.tsx" -o -name "*.json" \) \
! -path "*/node_modules/*" ! -path "*/.next/*" \
! -path "*/oauth*" ! -path "*/oidc*" ! -path "*authentik*" ! -path "*openid*" \
-exec grep -lE "docs\.openwebui\.com|open-webui\.com/docs|github\.com/open-webui/docs" {} \; 2>/dev/null)
if [ -z "$DOC_FILES" ]; then
echo " Файлов со ссылками не найдено"
else
echo "$DOC_FILES" | while read file; do
if [ -n "$file" ]; then
docker exec "${CONTAINER_NAME}" sed -i \
-e 's|https://docs.openwebui.com|https://note.iieasy.ru|g' \
-e 's|https://open-webui.com/docs|https://note.iieasy.ru|g' \
-e 's|https://github.com/open-webui/docs|https://note.iieasy.ru|g' \
-e 's|docs\.openwebui\.com|note.iieasy.ru|g' \
-e 's|open-webui\.com/docs|note.iieasy.ru|g' \
"$file" 2>/dev/null || true
fi
done
fi
echo "4. Удаление социальных сетей и лицензии..."
SOCIAL_FILES=$(docker exec "${CONTAINER_NAME}" find /app -type f \
\( -name "*.html" -o -name "*.svelte" -o -name "*.js" -o -name "*.jsx" -o -name "*.ts" -o -name "*.tsx" \) \
! -path "*/node_modules/*" ! -path "*/.next/*" \
! -path "*/oauth*" ! -path "*/oidc*" ! -path "*authentik*" ! -path "*openid*" \
-exec grep -lE "discord|twitter|x\.com|Github Repo|лицензионный|License" {} \; 2>/dev/null)
if [ -z "$SOCIAL_FILES" ]; then
echo " Файлов с соцсетями не найдено"
else
echo "$SOCIAL_FILES" | while read file; do
if [ -n "$file" ]; then
docker exec "${CONTAINER_NAME}" sed -i \
-e '/discord\.gg/d' -e '/discord\.com/d' -e '/Discord/d' \
-e '/twitter\.com/d' -e '/x\.com/d' -e '/X (formerly Twitter)/d' -e '/Twitter/d' -e '/Follow/d' \
-e '/Github Repo/d' -e '/GitHub Repo/d' \
-e '/лицензионный тарифный план/d' -e '/Перейдите на лицензионный/d' \
-e '/расширенные возможности/d' -e '/настраиваемую тематику/d' \
-e '/фирменный стиль/d' -e '/специальную поддержку/d' -e '/Лицензия/d' \
"$file" 2>/dev/null || true
fi
done
fi
echo ""
echo "5. Перезапуск контейнера..."
docker restart "${CONTAINER_NAME}" >/dev/null 2>&1 || {
echo "Предупреждение: Перезапустите вручную: docker restart ${CONTAINER_NAME}"
}
echo ""
echo "=== БЫСТРЫЙ ребрендинг завершен! ==="
echo ""
echo "Очистите кеш браузера (Ctrl+Shift+Delete)"

212
scripts/rebrand_final.sh Executable file
View File

@@ -0,0 +1,212 @@
#!/bin/bash
# ФИНАЛЬНЫЙ ребрендинг - удаляет ВСЕ упоминания и меняет ссылки на документацию
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
MEDIA_DIR="$PROJECT_DIR/media"
CONTAINER_NAME="open-webui"
echo "=== ФИНАЛЬНЫЙ ребрендинг Open WebUI для iiEasy ==="
echo "✓ Удаление ВСЕХ упоминаний '(Open WebUI)'"
echo "✓ Замена ссылок на документацию на note.iieasy.ru"
echo ""
if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
echo "Ошибка: Контейнер ${CONTAINER_NAME} не запущен."
exit 1
fi
echo "1. Замена логотипов..."
STATIC_DIRS=(
"/app/web/build/_app/immutable"
"/app/web/static"
"/app/web/build"
"/app/backend/static"
)
for dir in "${STATIC_DIRS[@]}"; do
if docker exec "${CONTAINER_NAME}" test -d "$dir" 2>/dev/null; then
if [ -f "$MEDIA_DIR/logo.png" ]; then
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${dir}/logo.png" 2>/dev/null || true
fi
if [ -f "$MEDIA_DIR/favicon.png" ]; then
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${dir}/favicon.png" 2>/dev/null || true
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${dir}/favicon.ico" 2>/dev/null || true
fi
fi
done
# Заменяем все существующие логотипы
EXISTING_LOGOS=$(docker exec "${CONTAINER_NAME}" find /app -type f \( -name "logo.png" -o -name "logo.svg" \) 2>/dev/null)
if [ -f "$MEDIA_DIR/logo.png" ]; then
echo "$EXISTING_LOGOS" | while read -r logo_file; do
if [ -n "$logo_file" ] && [[ ! "$logo_file" == *"node_modules"* ]]; then
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${logo_file}" 2>/dev/null || true
fi
done
fi
echo "2. АГРЕССИВНОЕ удаление '(Open WebUI)' из ВСЕХ файлов..."
# Ищем ВО ВСЕХ файлах, включая бинарные (может быть в строковых константах)
docker exec "${CONTAINER_NAME}" find /app -type f \
! -path "*/node_modules/*" ! -path "*/.next/*" \
! -path "*/oauth*" ! -path "*/oidc*" ! -path "*authentik*" ! -path "*openid*" \
! -name "*.pyc" ! -name "*.pyo" ! -name "*.so" \
2>/dev/null | while read file; do
# Проверяем, это текстовый файл
if docker exec "${CONTAINER_NAME}" file "$file" 2>/dev/null | grep -q "text\|JSON\|ASCII"; then
if docker exec "${CONTAINER_NAME}" grep -qE "(Open WebUI)|\(Open WebUI\)|iiEasyWeb.*Open.*WebUI" "$file" 2>/dev/null; then
echo " Удаление из: $file"
# Все возможные варианты
docker exec "${CONTAINER_NAME}" sed -i 's/(Open WebUI)//g' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i 's/\(Open WebUI\)//g' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i 's/ (Open WebUI)//g' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i 's/ \(Open WebUI\)//g' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i 's/iiEasyWeb (Open WebUI)/iiEasyWeb/g' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i 's/iiEasyWeb \(Open WebUI\)/iiEasyWeb/g' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i 's/iiEasyWeb(Open WebUI)/iiEasyWeb/g' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i "s/'(Open WebUI)'//g" "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i 's/"(Open WebUI)"//g' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i 's/`(Open WebUI)`//g' "$file" 2>/dev/null || true
# Удаляем если в составе строки
docker exec "${CONTAINER_NAME}" sed -i 's/Войти в iiEasyWeb (Open WebUI)/Войти в iiEasyWeb/g' "$file" 2>/dev/null || true
fi
fi
done
echo "3. Замена ссылок на документацию на note.iieasy.ru..."
# Заменяем ссылки на документацию
docker exec "${CONTAINER_NAME}" find /app -type f \
! -path "*/node_modules/*" ! -path "*/.next/*" \
! -path "*/oauth*" ! -path "*/oidc*" ! -path "*authentik*" ! -path "*openid*" \
2>/dev/null | while read file; do
if docker exec "${CONTAINER_NAME}" file "$file" 2>/dev/null | grep -q "text\|JSON\|ASCII"; then
if docker exec "${CONTAINER_NAME}" grep -qE "docs\.openwebui\.com|open-webui\.com/docs|github\.com/open-webui/docs" "$file" 2>/dev/null; then
echo " Замена ссылок на документацию в: $file"
docker exec "${CONTAINER_NAME}" sed -i 's|https://docs.openwebui.com|https://note.iieasy.ru|g' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i 's|https://open-webui.com/docs|https://note.iieasy.ru|g' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i 's|https://github.com/open-webui/docs|https://note.iieasy.ru|g' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i 's|docs\.openwebui\.com|note.iieasy.ru|g' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i 's|open-webui\.com/docs|note.iieasy.ru|g' "$file" 2>/dev/null || true
fi
fi
done
echo "4. Замена 'Open WebUI' на 'iiEasyWeb' в тексте..."
docker exec "${CONTAINER_NAME}" find /app/web -type f \
! -path "*/node_modules/*" ! -path "*/.next/*" \
2>/dev/null | while read file; do
if docker exec "${CONTAINER_NAME}" file "$file" 2>/dev/null | grep -q "text\|JSON\|ASCII"; then
if docker exec "${CONTAINER_NAME}" grep -q "Open WebUI" "$file" 2>/dev/null; then
docker exec "${CONTAINER_NAME}" sed -i 's/Open WebUI/iiEasyWeb/g' "$file" 2>/dev/null || true
fi
fi
done
echo "5. Проверка базы данных..."
# Пытаемся найти и исправить в базе данных
DB_PATH="/app/backend/data/webui.db"
if docker exec "${CONTAINER_NAME}" test -f "$DB_PATH" 2>/dev/null; then
echo " Найдена база данных, проверяем..."
if docker exec "${CONTAINER_NAME}" command -v sqlite3 >/dev/null 2>&1; then
# Ищем таблицы с текстовыми полями
TABLES=$(docker exec "${CONTAINER_NAME}" sqlite3 "$DB_PATH" "SELECT name FROM sqlite_master WHERE type='table';" 2>/dev/null)
echo "$TABLES" | while read table; do
if [ -n "$table" ]; then
# Пытаемся найти и заменить в текстовых полях
docker exec "${CONTAINER_NAME}" sqlite3 "$DB_PATH" "UPDATE $table SET value = REPLACE(value, '(Open WebUI)', '') WHERE value LIKE '%(Open WebUI)%';" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sqlite3 "$DB_PATH" "UPDATE $table SET value = REPLACE(value, 'Open WebUI', 'iiEasyWeb') WHERE value LIKE '%Open WebUI%';" 2>/dev/null || true
fi
done
fi
fi
echo "6. Удаление социальных сетей, GitHub и лицензии из футера..."
# Удаляем Discord
docker exec "${CONTAINER_NAME}" find /app -type f \
! -path "*/node_modules/*" ! -path "*/.next/*" \
! -path "*/oauth*" ! -path "*/oidc*" ! -path "*authentik*" ! -path "*openid*" \
2>/dev/null | while read file; do
if docker exec "${CONTAINER_NAME}" file "$file" 2>/dev/null | grep -q "text\|JSON\|ASCII"; then
if docker exec "${CONTAINER_NAME}" grep -qE "discord\.gg|discord\.com|Discord" "$file" 2>/dev/null; then
docker exec "${CONTAINER_NAME}" sed -i '/discord\.gg/d' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/discord\.com/d' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/Discord/d' "$file" 2>/dev/null || true
fi
fi
done
# Удаляем Twitter/X
docker exec "${CONTAINER_NAME}" find /app -type f \
! -path "*/node_modules/*" ! -path "*/.next/*" \
! -path "*/oauth*" ! -path "*/oidc*" ! -path "*authentik*" ! -path "*openid*" \
2>/dev/null | while read file; do
if docker exec "${CONTAINER_NAME}" file "$file" 2>/dev/null | grep -q "text\|JSON\|ASCII"; then
if docker exec "${CONTAINER_NAME}" grep -qE "twitter\.com|x\.com|X \(formerly Twitter\)|Twitter|Follow" "$file" 2>/dev/null; then
docker exec "${CONTAINER_NAME}" sed -i '/twitter\.com/d' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/x\.com/d' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/X (formerly Twitter)/d' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/Twitter/d' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/Follow/d' "$file" 2>/dev/null || true
fi
fi
done
# Удаляем GitHub Repo
docker exec "${CONTAINER_NAME}" find /app -type f \
! -path "*/node_modules/*" ! -path "*/.next/*" \
! -path "*/oauth*" ! -path "*/oidc*" ! -path "*authentik*" ! -path "*openid*" \
2>/dev/null | while read file; do
if docker exec "${CONTAINER_NAME}" file "$file" 2>/dev/null | grep -q "text\|JSON\|ASCII"; then
if docker exec "${CONTAINER_NAME}" grep -qE "Github Repo|GitHub Repo" "$file" 2>/dev/null; then
docker exec "${CONTAINER_NAME}" sed -i '/Github Repo/d' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/GitHub Repo/d' "$file" 2>/dev/null || true
fi
fi
done
# Удаляем лицензию
docker exec "${CONTAINER_NAME}" find /app -type f \
! -path "*/node_modules/*" ! -path "*/.next/*" \
! -path "*/oauth*" ! -path "*/oidc*" ! -path "*authentik*" ! -path "*openid*" \
2>/dev/null | while read file; do
if docker exec "${CONTAINER_NAME}" file "$file" 2>/dev/null | grep -q "text\|JSON\|ASCII"; then
if docker exec "${CONTAINER_NAME}" grep -qE "лицензионный тарифный план|Перейдите на лицензионный|Лицензия" "$file" 2>/dev/null; then
docker exec "${CONTAINER_NAME}" sed -i '/лицензионный тарифный план/d' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/Перейдите на лицензионный/d' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/расширенные возможности/d' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/настраиваемую тематику/d' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/фирменный стиль/d' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/специальную поддержку/d' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/Лицензия/d' "$file" 2>/dev/null || true
fi
fi
done
echo ""
echo "7. Перезапуск контейнера..."
docker restart "${CONTAINER_NAME}" >/dev/null 2>&1 || {
echo "Предупреждение: Перезапустите вручную: docker restart ${CONTAINER_NAME}"
}
echo ""
echo "=== ФИНАЛЬНЫЙ ребрендинг завершен! ==="
echo ""
echo "Проверьте:"
echo " 1. Откройте https://odo.iieasy.ru"
echo " 2. Должно быть 'Войти в iiEasyWeb' (без '(Open WebUI)')"
echo " 3. Ссылки на документацию должны вести на note.iieasy.ru"
echo ""
echo "ВАЖНО: Очистите кеш браузера (Ctrl+Shift+Delete)"
echo ""
echo "Если '(Open WebUI)' все еще видно, проверьте через Admin Panel:"
echo " Settings → Appearance → Site Title"

209
scripts/rebrand_full.sh Executable file
View File

@@ -0,0 +1,209 @@
#!/bin/bash
# ПОЛНЫЙ ребрендинг Open WebUI для iiEasy
# Удаляет ВСЕ упоминания Open WebUI из интерфейса, но защищает OAuth
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
MEDIA_DIR="$PROJECT_DIR/media"
CONTAINER_NAME="open-webui"
echo "=== ПОЛНЫЙ ребрендинг Open WebUI для iiEasy ==="
echo "⚠ Удаление ВСЕХ упоминаний Open WebUI из интерфейса"
echo "⚠ OAuth/Authentik файлы защищены"
echo ""
# Проверка наличия контейнера
if ! docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
echo "Ошибка: Контейнер ${CONTAINER_NAME} не найден. Запустите docker-compose up -d сначала."
exit 1
fi
# Проверка, что контейнер запущен
if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
echo "Ошибка: Контейнер ${CONTAINER_NAME} не запущен. Запустите docker-compose up -d."
exit 1
fi
echo "1. Замена логотипов и favicon..."
# Определяем пути для статических файлов
STATIC_DIRS=(
"/app/web/build/_app/immutable"
"/app/web/static"
"/app/web/build"
"/app/backend/static"
"/app/static"
"/app/public"
)
STATIC_DIR=""
for dir in "${STATIC_DIRS[@]}"; do
if docker exec "${CONTAINER_NAME}" test -d "$dir" 2>/dev/null; then
STATIC_DIR="$dir"
echo " Найдена директория: $STATIC_DIR"
break
fi
done
if [ -z "$STATIC_DIR" ]; then
FAVICON_PATH=$(docker exec "${CONTAINER_NAME}" find /app -name "favicon.png" -o -name "favicon.ico" 2>/dev/null | head -1)
if [ -n "$FAVICON_PATH" ]; then
STATIC_DIR=$(dirname "$FAVICON_PATH")
else
STATIC_DIR="/app/web/build/_app/immutable"
docker exec "${CONTAINER_NAME}" mkdir -p "$STATIC_DIR" 2>/dev/null || true
fi
fi
# Копирование логотипов
for target_dir in "$STATIC_DIR" "/app/web/build/_app/immutable" "/app/web/static" "/app/web/build"; do
if docker exec "${CONTAINER_NAME}" test -d "$target_dir" 2>/dev/null || [ "$target_dir" = "$STATIC_DIR" ]; then
echo " Копирование в $target_dir..."
if [ -f "$MEDIA_DIR/logo.png" ]; then
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${target_dir}/logo.png" 2>/dev/null || true
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${target_dir}/logo.svg" 2>/dev/null || true
elif [ -f "$MEDIA_DIR/logo-light.svg" ]; then
docker cp "$MEDIA_DIR/logo-light.svg" "${CONTAINER_NAME}:${target_dir}/logo.svg" 2>/dev/null || true
fi
if [ -f "$MEDIA_DIR/logo-dark.svg" ]; then
docker cp "$MEDIA_DIR/logo-dark.svg" "${CONTAINER_NAME}:${target_dir}/logo-dark.svg" 2>/dev/null || true
fi
if [ -f "$MEDIA_DIR/favicon.ico" ]; then
docker cp "$MEDIA_DIR/favicon.ico" "${CONTAINER_NAME}:${target_dir}/favicon.ico" 2>/dev/null || true
elif [ -f "$MEDIA_DIR/favicon.png" ]; then
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${target_dir}/favicon.png" 2>/dev/null || true
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${target_dir}/favicon.ico" 2>/dev/null || true
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${target_dir}/favicon-96x96.png" 2>/dev/null || true
elif [ -f "$MEDIA_DIR/favicon.svg" ]; then
docker cp "$MEDIA_DIR/favicon.svg" "${CONTAINER_NAME}:${target_dir}/favicon.svg" 2>/dev/null || true
fi
fi
done
# Замена существующих favicon и logo
EXISTING_FAVICONS=$(docker exec "${CONTAINER_NAME}" find /app -type f \( -name "favicon.png" -o -name "favicon.ico" -o -name "favicon.svg" \) 2>/dev/null | head -10)
EXISTING_LOGOS=$(docker exec "${CONTAINER_NAME}" find /app -type f \( -name "logo.png" -o -name "logo.svg" \) 2>/dev/null | head -10)
if [ -f "$MEDIA_DIR/favicon.png" ]; then
echo "$EXISTING_FAVICONS" | while read -r favicon_file; do
if [ -n "$favicon_file" ]; then
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${favicon_file}" 2>/dev/null || true
favicon_dir=$(dirname "$favicon_file")
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${favicon_dir}/favicon.ico" 2>/dev/null || true
fi
done
fi
if [ -f "$MEDIA_DIR/logo.png" ]; then
echo "$EXISTING_LOGOS" | while read -r logo_file; do
if [ -n "$logo_file" ]; then
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${logo_file}" 2>/dev/null || true
fi
done
fi
# Копируем в public директории
PUBLIC_DIRS=("/app/web/public" "/app/public" "/app/backend/public")
for pub_dir in "${PUBLIC_DIRS[@]}"; do
if docker exec "${CONTAINER_NAME}" test -d "$pub_dir" 2>/dev/null; then
if [ -f "$MEDIA_DIR/logo.png" ]; then
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${pub_dir}/logo.png" 2>/dev/null || true
fi
if [ -f "$MEDIA_DIR/favicon.png" ]; then
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${pub_dir}/favicon.png" 2>/dev/null || true
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${pub_dir}/favicon.ico" 2>/dev/null || true
fi
break
fi
done
echo "2. Удаление упоминаний 'Open WebUI' из фронтенда (HTML/JS/TSX/Svelte)..."
# КРИТИЧНО: Изменяем ТОЛЬКО фронтенд файлы (веб-интерфейс)
# НЕ трогаем Python бэкенд файлы вообще, кроме исключений для безопасности
docker exec "${CONTAINER_NAME}" find /app/web -type f \( -name "*.html" -o -name "*.js" -o -name "*.jsx" -o -name "*.ts" -o -name "*.tsx" -o -name "*.svelte" -o -name "*.json" \) \
! -path "*/node_modules/*" ! -path "*/.next/*" ! -path "*/dist/*" \
2>/dev/null | while read file; do
# Проверяем, содержит ли файл упоминания Open WebUI
if docker exec "${CONTAINER_NAME}" grep -q "Open WebUI\|open-webui\|openwebui\|\(Open WebUI\)" "$file" 2>/dev/null; then
echo " Обработка фронтенд: $file"
# Замена "Open WebUI" на "iiEasyWeb"
docker exec "${CONTAINER_NAME}" sed -i 's/Open WebUI/iiEasyWeb/g' "$file" 2>/dev/null || true
# Удаление "(Open WebUI)" в скобках
docker exec "${CONTAINER_NAME}" sed -i 's/(Open WebUI)//g' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i 's/\(Open WebUI\)//g' "$file" 2>/dev/null || true
# Замена "iiEasyWeb (Open WebUI)" на "iiEasyWeb"
docker exec "${CONTAINER_NAME}" sed -i 's/iiEasyWeb (Open WebUI)/iiEasyWeb/g' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i 's/iiEasyWeb \(Open WebUI\)/iiEasyWeb/g' "$file" 2>/dev/null || true
# Удаление "open-webui" и "openwebui" в тексте (но не в URL - они обычно в кавычках или переменных)
docker exec "${CONTAINER_NAME}" sed -i 's/open-webui/iieasyweb/g' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i 's/openwebui/iieasyweb/g' "$file" 2>/dev/null || true
fi
done
echo "3. Удаление упоминаний из статических JSON файлов конфигурации..."
# Изменяем только JSON файлы конфигурации фронтенда (не бэкенд)
docker exec "${CONTAINER_NAME}" find /app/web -type f -name "*.json" \
! -path "*/node_modules/*" ! -path "*/.next/*" ! -path "*/dist/*" \
2>/dev/null | while read file; do
if docker exec "${CONTAINER_NAME}" grep -q "Open WebUI\|open-webui\|openwebui" "$file" 2>/dev/null; then
echo " Обработка JSON: $file"
docker exec "${CONTAINER_NAME}" sed -i 's/Open WebUI/iiEasyWeb/g' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i 's/open-webui/iieasyweb/g' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i 's/openwebui/iieasyweb/g' "$file" 2>/dev/null || true
fi
done
echo "4. Удаление 'Powered by' футеров из фронтенда..."
docker exec "${CONTAINER_NAME}" find /app/web -type f \( -name "*.html" -o -name "*.js" -o -name "*.tsx" -o -name "*.jsx" -o -name "*.svelte" \) \
! -path "*/node_modules/*" ! -path "*/.next/*" ! -path "*/dist/*" \
2>/dev/null | while read file; do
if docker exec "${CONTAINER_NAME}" grep -q "Powered by\|powered by" "$file" 2>/dev/null; then
echo " Удаление футера из: $file"
docker exec "${CONTAINER_NAME}" sed -i '/Powered by.*[Oo]pen.*[Ww]eb[Uu][Ii]/d' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/powered by.*[Oo]pen.*[Ww]eb[Uu][Ii]/d' "$file" 2>/dev/null || true
fi
done
echo "5. Отключение проверки обновлений (только фронтенд)..."
docker exec "${CONTAINER_NAME}" find /app/web -type f \( -name "*.js" -o -name "*.ts" \) \
! -path "*/node_modules/*" ! -path "*/.next/*" ! -path "*/dist/*" \
-exec grep -l "github.com.*releases\|check.*update\|update.*check" {} \; 2>/dev/null | while read file; do
echo " Отключение проверки обновлений в: $file"
docker exec "${CONTAINER_NAME}" sed -i 's|https://api.github.com/repos/open-webui|# https://api.github.com/repos/open-webui|g' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i 's|github.com/open-webui|# github.com/open-webui|g' "$file" 2>/dev/null || true
done
echo ""
echo "⚠ ВАЖНО: Python бэкенд файлы НЕ изменялись"
echo "⚠ OAuth/Authentik файлы защищены"
echo "⚠ Изменены только фронтенд файлы (веб-интерфейс)"
echo ""
echo "6. Перезапуск контейнера..."
docker restart "${CONTAINER_NAME}" >/dev/null 2>&1 || {
echo "Предупреждение: Не удалось перезапустить контейнер автоматически."
echo "Перезапустите вручную: docker restart ${CONTAINER_NAME}"
}
echo ""
echo "=== ПОЛНЫЙ ребрендинг завершен! ==="
echo ""
echo "Проверьте:"
echo " 1. Откройте https://odo.iieasy.ru"
echo " 2. Проверьте отсутствие упоминаний 'Open WebUI'"
echo " 3. Проверьте OAuth - должен работать нормально"
echo ""
echo "Если OAuth не работает, пересоздайте контейнер:"
echo " sudo docker compose stop open-webui"
echo " sudo docker compose rm -f open-webui"
echo " sudo docker compose up -d open-webui"

173
scripts/rebrand_precise.sh Executable file
View File

@@ -0,0 +1,173 @@
#!/bin/bash
# ТОЧНЫЙ ребрендинг Open WebUI для iiEasy
# Находит и удаляет ВСЕ упоминания "(Open WebUI)" и "Open WebUI"
# Только безопасные замены в текстовом контенте
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
MEDIA_DIR="$PROJECT_DIR/media"
CONTAINER_NAME="open-webui"
echo "=== ТОЧНЫЙ ребрендинг Open WebUI для iiEasy ==="
echo "✓ Поиск и удаление ВСЕХ упоминаний '(Open WebUI)'"
echo "✓ Замена 'Open WebUI' на 'iiEasyWeb'"
echo "✓ Исправление логотипов"
echo ""
if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
echo "Ошибка: Контейнер ${CONTAINER_NAME} не запущен."
exit 1
fi
echo "1. Замена логотипов и favicon..."
# Находим все места, где могут быть логотипы
STATIC_DIRS=(
"/app/web/build/_app/immutable"
"/app/web/static"
"/app/web/build"
"/app/backend/static"
)
for dir in "${STATIC_DIRS[@]}"; do
if docker exec "${CONTAINER_NAME}" test -d "$dir" 2>/dev/null; then
echo " Копирование в $dir..."
if [ -f "$MEDIA_DIR/logo.png" ]; then
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${dir}/logo.png" 2>/dev/null || true
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${dir}/logo.svg" 2>/dev/null || true
fi
if [ -f "$MEDIA_DIR/favicon.png" ]; then
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${dir}/favicon.png" 2>/dev/null || true
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${dir}/favicon.ico" 2>/dev/null || true
fi
fi
done
# Заменяем существующие логотипы везде
echo " Поиск и замена существующих логотипов..."
EXISTING_LOGOS=$(docker exec "${CONTAINER_NAME}" find /app -type f \( -name "logo.png" -o -name "logo.svg" -o -name "logo.ico" \) 2>/dev/null)
if [ -f "$MEDIA_DIR/logo.png" ]; then
echo "$EXISTING_LOGOS" | while read -r logo_file; do
if [ -n "$logo_file" ] && [[ ! "$logo_file" == *"node_modules"* ]]; then
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${logo_file}" 2>/dev/null || true
fi
done
fi
EXISTING_FAVICONS=$(docker exec "${CONTAINER_NAME}" find /app -type f \( -name "favicon.png" -o -name "favicon.ico" -o -name "favicon.svg" \) 2>/dev/null)
if [ -f "$MEDIA_DIR/favicon.png" ]; then
echo "$EXISTING_FAVICONS" | while read -r favicon_file; do
if [ -n "$favicon_file" ] && [[ ! "$favicon_file" == *"node_modules"* ]]; then
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${favicon_file}" 2>/dev/null || true
fi
done
fi
echo "2. Поиск и удаление '(Open WebUI)' из ВСЕХ файлов..."
# Ищем ВСЕ файлы с упоминанием "(Open WebUI)" - включая скомпилированные
# Ищем в разных вариантах написания
docker exec "${CONTAINER_NAME}" find /app -type f \( -name "*.html" -o -name "*.svelte" -o -name "*.js" -o -name "*.jsx" -o -name "*.ts" -o -name "*.tsx" -o -name "*.json" -o -name "*.css" -o -name "*.mjs" \) \
! -path "*/node_modules/*" ! -path "*/.next/*" \
! -path "*/oauth*" ! -path "*/oidc*" ! -path "*authentik*" ! -path "*openid*" \
2>/dev/null | while read file; do
# Проверяем разные варианты написания - включая с пробелами и без
if docker exec "${CONTAINER_NAME}" grep -qE "(Open WebUI)|\(Open WebUI\)|iiEasyWeb \(Open WebUI\)|iiEasyWeb\(Open WebUI\)" "$file" 2>/dev/null; then
echo " Удаление '(Open WebUI)' из: $file"
# Удаляем различные варианты написания в скобках
docker exec "${CONTAINER_NAME}" sed -i 's/(Open WebUI)//g' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i 's/\(Open WebUI\)//g' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i 's/ (Open WebUI)//g' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i 's/ \(Open WebUI\)//g' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i 's/iiEasyWeb (Open WebUI)/iiEasyWeb/g' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i 's/iiEasyWeb \(Open WebUI\)/iiEasyWeb/g' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i 's/iiEasyWeb(Open WebUI)/iiEasyWeb/g' "$file" 2>/dev/null || true
# Также удаляем если есть пробелы вокруг
docker exec "${CONTAINER_NAME}" sed -i 's/\s*(Open WebUI)\s*//g' "$file" 2>/dev/null || true
# Удаляем если текст в кавычках или переменных
docker exec "${CONTAINER_NAME}" sed -i "s/'(Open WebUI)'//g" "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i 's/"(Open WebUI)"//g' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i 's/`(Open WebUI)`//g' "$file" 2>/dev/null || true
fi
done
echo "3. Замена 'Open WebUI' на 'iiEasyWeb' в тексте..."
# Заменяем "Open WebUI" на "iiEasyWeb" в текстовом контенте
docker exec "${CONTAINER_NAME}" find /app/web -type f \( -name "*.html" -o -name "*.svelte" -o -name "*.js" -o -name "*.jsx" -o -name "*.ts" -o -name "*.tsx" \) \
! -path "*/node_modules/*" ! -path "*/.next/*" ! -path "*/dist/*" \
2>/dev/null | while read file; do
if docker exec "${CONTAINER_NAME}" grep -q "Open WebUI" "$file" 2>/dev/null; then
echo " Замена в: $file"
# Заменяем только в текстовом контенте, не в коде
docker exec "${CONTAINER_NAME}" sed -i 's/Open WebUI/iiEasyWeb/g' "$file" 2>/dev/null || true
fi
done
echo "4. Удаление 'Powered by Open WebUI'..."
docker exec "${CONTAINER_NAME}" find /app -type f \( -name "*.html" -o -name "*.svelte" -o -name "*.js" -o -name "*.jsx" -o -name "*.ts" -o -name "*.tsx" \) \
! -path "*/node_modules/*" ! -path "*/.next/*" ! -path "*/dist/*" \
! -path "*/oauth*" ! -path "*/oidc*" ! -path "*authentik*" ! -path "*openid*" \
2>/dev/null | while read file; do
if docker exec "${CONTAINER_NAME}" grep -q "Powered by.*Open WebUI\|powered by.*Open WebUI" "$file" 2>/dev/null; then
echo " Удаление футера из: $file"
docker exec "${CONTAINER_NAME}" sed -i '/Powered by.*Open WebUI/d' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/powered by.*Open WebUI/d' "$file" 2>/dev/null || true
fi
done
echo "5. Поиск упоминаний в скомпилированных файлах (включая минифицированные)..."
# Ищем в скомпилированных JS файлах (могут быть минифицированы)
# Также ищем в build директориях
docker exec "${CONTAINER_NAME}" find /app/web/build -type f \( -name "*.js" -o -name "*.mjs" \) \
! -path "*/node_modules/*" \
2>/dev/null | while read file; do
# Ищем разные варианты, включая минифицированные (без пробелов)
if docker exec "${CONTAINER_NAME}" grep -qE "Open WebUI|\(Open WebUI\)|iiEasyWeb \(Open WebUI\)|OpenWebUI" "$file" 2>/dev/null; then
echo " Обработка скомпилированного: $file"
docker exec "${CONTAINER_NAME}" sed -i 's/Open WebUI/iiEasyWeb/g' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i 's/(Open WebUI)//g' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i 's/\(Open WebUI\)//g' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i 's/iiEasyWeb (Open WebUI)/iiEasyWeb/g' "$file" 2>/dev/null || true
fi
done
echo "6. Поиск в базе данных (если текст там хранится)..."
# Проверяем, может ли текст быть в базе данных
# Если есть SQLite база, можем попробовать заменить там
DB_PATH="/app/backend/data/webui.db"
if docker exec "${CONTAINER_NAME}" test -f "$DB_PATH" 2>/dev/null; then
echo " Найдена база данных, проверяем наличие '(Open WebUI)'..."
# Ищем в базе через sqlite3 (если доступен)
if docker exec "${CONTAINER_NAME}" command -v sqlite3 >/dev/null 2>&1; then
# Ищем в текстовых полях базы
docker exec "${CONTAINER_NAME}" sqlite3 "$DB_PATH" "SELECT name FROM sqlite_master WHERE type='table';" 2>/dev/null | while read table; do
if [ -n "$table" ]; then
# Пытаемся найти и заменить в текстовых полях (осторожно!)
echo " Проверка таблицы: $table"
fi
done
fi
fi
echo ""
echo "6. Перезапуск контейнера..."
docker restart "${CONTAINER_NAME}" >/dev/null 2>&1 || {
echo "Предупреждение: Перезапустите вручную: docker restart ${CONTAINER_NAME}"
}
echo ""
echo "=== ТОЧНЫЙ ребрендинг завершен! ==="
echo ""
echo "Проверьте:"
echo " 1. Откройте https://odo.iieasy.ru"
echo " 2. Должно быть 'Войти в iiEasyWeb' (без '(Open WebUI)')"
echo " 3. Логотип должен отображаться правильно"
echo ""
echo "Если '(Open WebUI)' все еще видно, очистите кеш браузера (Ctrl+Shift+Delete)"

152
scripts/rebrand_safe.sh Executable file
View File

@@ -0,0 +1,152 @@
#!/bin/bash
# БЕЗОПАСНЫЙ скрипт ребрендинга Open WebUI для iiEasy
# Только логотипы и favicon, БЕЗ изменения кода Python/JS
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
MEDIA_DIR="$PROJECT_DIR/media"
CONTAINER_NAME="open-webui"
echo "=== БЕЗОПАСНЫЙ ребрендинг Open WebUI для iiEasy ==="
echo "⚠ ВНИМАНИЕ: Этот скрипт изменяет ТОЛЬКО логотипы и favicon"
echo "⚠ Код Python/JS НЕ изменяется для защиты OAuth/Authentik"
echo ""
# Проверка наличия контейнера
if ! docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
echo "Ошибка: Контейнер ${CONTAINER_NAME} не найден. Запустите docker-compose up -d сначала."
exit 1
fi
# Проверка, что контейнер запущен
if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
echo "Ошибка: Контейнер ${CONTAINER_NAME} не запущен. Запустите docker-compose up -d."
exit 1
fi
echo "1. Замена логотипов и favicon..."
# Определяем пути для статических файлов в Open WebUI
STATIC_DIRS=(
"/app/web/build/_app/immutable"
"/app/web/static"
"/app/web/build"
"/app/backend/static"
"/app/static"
"/app/public"
)
# Находим существующую директорию со статическими файлами
STATIC_DIR=""
for dir in "${STATIC_DIRS[@]}"; do
if docker exec "${CONTAINER_NAME}" test -d "$dir" 2>/dev/null; then
STATIC_DIR="$dir"
echo " Найдена директория статических файлов: $STATIC_DIR"
break
fi
done
if [ -z "$STATIC_DIR" ]; then
echo " Предупреждение: Директория статических файлов не найдена, пробуем найти через поиск favicon..."
FAVICON_PATH=$(docker exec "${CONTAINER_NAME}" find /app -name "favicon.png" -o -name "favicon.ico" 2>/dev/null | head -1)
if [ -n "$FAVICON_PATH" ]; then
STATIC_DIR=$(dirname "$FAVICON_PATH")
echo " Найдена директория через поиск favicon: $STATIC_DIR"
else
STATIC_DIR="/app/web/build/_app/immutable"
echo " Используем стандартный путь: $STATIC_DIR"
docker exec "${CONTAINER_NAME}" mkdir -p "$STATIC_DIR" 2>/dev/null || true
fi
fi
# Копирование логотипов (приоритет: PNG > SVG)
for target_dir in "$STATIC_DIR" "/app/web/build/_app/immutable" "/app/web/static" "/app/web/build"; do
if docker exec "${CONTAINER_NAME}" test -d "$target_dir" 2>/dev/null || [ "$target_dir" = "$STATIC_DIR" ]; then
echo " Копирование в $target_dir..."
# Логотипы
if [ -f "$MEDIA_DIR/logo.png" ]; then
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${target_dir}/logo.png" 2>/dev/null || true
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${target_dir}/logo.svg" 2>/dev/null || true
elif [ -f "$MEDIA_DIR/logo-light.svg" ]; then
docker cp "$MEDIA_DIR/logo-light.svg" "${CONTAINER_NAME}:${target_dir}/logo.svg" 2>/dev/null || true
fi
if [ -f "$MEDIA_DIR/logo-dark.svg" ]; then
docker cp "$MEDIA_DIR/logo-dark.svg" "${CONTAINER_NAME}:${target_dir}/logo-dark.svg" 2>/dev/null || true
fi
# Favicon
if [ -f "$MEDIA_DIR/favicon.ico" ]; then
docker cp "$MEDIA_DIR/favicon.ico" "${CONTAINER_NAME}:${target_dir}/favicon.ico" 2>/dev/null || true
elif [ -f "$MEDIA_DIR/favicon.png" ]; then
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${target_dir}/favicon.png" 2>/dev/null || true
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${target_dir}/favicon.ico" 2>/dev/null || true
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${target_dir}/favicon-96x96.png" 2>/dev/null || true
elif [ -f "$MEDIA_DIR/favicon.svg" ]; then
docker cp "$MEDIA_DIR/favicon.svg" "${CONTAINER_NAME}:${target_dir}/favicon.svg" 2>/dev/null || true
fi
fi
done
# Поиск и замена существующих favicon и logo файлов
echo " Поиск существующих favicon и logo файлов для замены..."
EXISTING_FAVICONS=$(docker exec "${CONTAINER_NAME}" find /app -type f \( -name "favicon.png" -o -name "favicon.ico" -o -name "favicon.svg" \) 2>/dev/null | head -10)
EXISTING_LOGOS=$(docker exec "${CONTAINER_NAME}" find /app -type f \( -name "logo.png" -o -name "logo.svg" \) 2>/dev/null | head -10)
# Заменяем существующие favicon файлы
if [ -f "$MEDIA_DIR/favicon.png" ]; then
echo "$EXISTING_FAVICONS" | while read -r favicon_file; do
if [ -n "$favicon_file" ]; then
echo " Замена: $favicon_file"
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${favicon_file}" 2>/dev/null || true
favicon_dir=$(dirname "$favicon_file")
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${favicon_dir}/favicon.ico" 2>/dev/null || true
fi
done
fi
# Заменяем существующие logo файлы
if [ -f "$MEDIA_DIR/logo.png" ]; then
echo "$EXISTING_LOGOS" | while read -r logo_file; do
if [ -n "$logo_file" ]; then
echo " Замена: $logo_file"
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${logo_file}" 2>/dev/null || true
fi
done
fi
# Копируем в public директории
PUBLIC_DIRS=(
"/app/web/public"
"/app/public"
"/app/backend/public"
)
for pub_dir in "${PUBLIC_DIRS[@]}"; do
if docker exec "${CONTAINER_NAME}" test -d "$pub_dir" 2>/dev/null; then
echo " Копирование в $pub_dir..."
if [ -f "$MEDIA_DIR/logo.png" ]; then
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${pub_dir}/logo.png" 2>/dev/null || true
fi
if [ -f "$MEDIA_DIR/favicon.png" ]; then
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${pub_dir}/favicon.png" 2>/dev/null || true
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${pub_dir}/favicon.ico" 2>/dev/null || true
fi
break
fi
done
echo ""
echo "=== БЕЗОПАСНЫЙ ребрендинг завершен! ==="
echo ""
echo "⚠ Изменены ТОЛЬКО логотипы и favicon"
echo "⚠ Код Python/JS НЕ изменялся - OAuth/Authentik защищены"
echo ""
echo "Рекомендуется использовать Admin Panel для постоянных изменений:"
echo " Settings → Appearance → Logo (загрузите файлы из /app/media/)"
echo ""
echo "Если нужно изменить текст интерфейса, используйте Admin Panel или"
echo "настройте переменные окружения в docker-compose.yml"

460
scripts/rebrand_safe_final.sh Executable file
View File

@@ -0,0 +1,460 @@
#!/bin/bash
# БЕЗОПАСНЫЙ финальный ребрендинг - не ломает функциональность
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
MEDIA_DIR="$PROJECT_DIR/media"
CONTAINER_NAME="open-webui"
echo "=== БЕЗОПАСНЫЙ финальный ребрендинг Open WebUI для iiEasy ==="
echo ""
if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
echo "Ошибка: Контейнер ${CONTAINER_NAME} не запущен."
exit 1
fi
echo "1. Замена логотипов, splash.png и favicon..."
STATIC_DIRS=(
"/app/web/build/_app/immutable"
"/app/web/static"
"/app/web/build"
"/app/backend/static"
"/app/static"
"/app/web/public"
"/app/public"
)
for dir in "${STATIC_DIRS[@]}"; do
if docker exec "${CONTAINER_NAME}" test -d "$dir" 2>/dev/null; then
# Основной логотип
if [ -f "$MEDIA_DIR/logo.png" ]; then
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${dir}/logo.png" 2>/dev/null || true
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${dir}/splash.png" 2>/dev/null || true
# Также создаем splash-dark.png для темной темы
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${dir}/splash-dark.png" 2>/dev/null || true
fi
# Логотип для светлой темы
if [ -f "$MEDIA_DIR/logo-light.svg" ]; then
docker cp "$MEDIA_DIR/logo-light.svg" "${CONTAINER_NAME}:${dir}/logo-light.svg" 2>/dev/null || true
docker cp "$MEDIA_DIR/logo-light.svg" "${CONTAINER_NAME}:${dir}/logo.svg" 2>/dev/null || true
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${dir}/splash-light.png" 2>/dev/null || true
elif [ -f "$MEDIA_DIR/logo.png" ]; then
# Если нет SVG, используем PNG для светлой темы
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${dir}/logo-light.png" 2>/dev/null || true
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${dir}/splash-light.png" 2>/dev/null || true
fi
# Логотип для темной темы
if [ -f "$MEDIA_DIR/logo-dark.svg" ]; then
docker cp "$MEDIA_DIR/logo-dark.svg" "${CONTAINER_NAME}:${dir}/logo-dark.svg" 2>/dev/null || true
# Для splash-dark.png используем PNG версию (если есть logo-dark.png) или обычный logo.png
if [ -f "$MEDIA_DIR/logo-dark.png" ]; then
docker cp "$MEDIA_DIR/logo-dark.png" "${CONTAINER_NAME}:${dir}/splash-dark.png" 2>/dev/null || true
elif [ -f "$MEDIA_DIR/logo.png" ]; then
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${dir}/splash-dark.png" 2>/dev/null || true
fi
elif [ -f "$MEDIA_DIR/logo.png" ]; then
# Если нет темного SVG, используем PNG для темной темы
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${dir}/logo-dark.png" 2>/dev/null || true
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${dir}/splash-dark.png" 2>/dev/null || true
fi
# Favicon
if [ -f "$MEDIA_DIR/favicon.png" ]; then
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${dir}/favicon.png" 2>/dev/null || true
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${dir}/favicon.ico" 2>/dev/null || true
# Также создаем favicon-dark.png и favicon-light.png
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${dir}/favicon-dark.png" 2>/dev/null || true
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${dir}/favicon-light.png" 2>/dev/null || true
# Apple touch icon для iOS
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${dir}/apple-touch-icon.png" 2>/dev/null || true
elif [ -f "$MEDIA_DIR/logo.png" ]; then
# Если нет отдельного favicon, используем logo.png
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${dir}/favicon.png" 2>/dev/null || true
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${dir}/favicon.ico" 2>/dev/null || true
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${dir}/favicon-dark.png" 2>/dev/null || true
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${dir}/favicon-light.png" 2>/dev/null || true
# Apple touch icon для iOS
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${dir}/apple-touch-icon.png" 2>/dev/null || true
fi
fi
done
# Заменяем ВСЕ существующие favicon файлы (включая favicon.ico, favicon-dark.png, favicon-light.png, apple-touch-icon.png)
echo " Замена всех favicon файлов..."
EXISTING_FAVICONS=$(docker exec "${CONTAINER_NAME}" find /app -type f \( -name "favicon.png" -o -name "favicon.ico" -o -name "favicon.svg" -o -name "favicon-dark.png" -o -name "favicon-light.png" -o -name "apple-touch-icon.png" \) 2>/dev/null)
if [ -f "$MEDIA_DIR/favicon.png" ]; then
echo "$EXISTING_FAVICONS" | while read -r favicon_file; do
if [ -n "$favicon_file" ] && [[ ! "$favicon_file" == *"node_modules"* ]]; then
favicon_name=$(basename "$favicon_file")
echo " Замена favicon: $favicon_file"
# Для темной темы используем logo-dark если есть, иначе favicon.png
if [[ "$favicon_name" == *"dark"* ]]; then
if [ -f "$MEDIA_DIR/logo-dark.svg" ] || [ -f "$MEDIA_DIR/logo-dark.png" ]; then
# Используем logo.png для favicon-dark (так как favicon должен быть PNG)
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${favicon_file}" 2>/dev/null || true
else
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${favicon_file}" 2>/dev/null || true
fi
# Для светлой темы
elif [[ "$favicon_name" == *"light"* ]]; then
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${favicon_file}" 2>/dev/null || true
# Apple touch icon или обычный favicon
else
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${favicon_file}" 2>/dev/null || true
# Также создаем .ico версию в той же директории для обычных favicon
if [[ "$favicon_name" == "favicon.png" ]]; then
favicon_dir=$(dirname "$favicon_file")
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${favicon_dir}/favicon.ico" 2>/dev/null || true
fi
fi
fi
done
elif [ -f "$MEDIA_DIR/logo.png" ]; then
# Если нет favicon.png, используем logo.png
echo "$EXISTING_FAVICONS" | while read -r favicon_file; do
if [ -n "$favicon_file" ] && [[ ! "$favicon_file" == *"node_modules"* ]]; then
favicon_name=$(basename "$favicon_file")
echo " Замена favicon: $favicon_file"
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${favicon_file}" 2>/dev/null || true
# Также создаем .ico версию для обычных favicon (не для dark/light/apple-touch-icon)
if [[ "$favicon_name" == "favicon.png" ]]; then
favicon_dir=$(dirname "$favicon_file")
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${favicon_dir}/favicon.ico" 2>/dev/null || true
fi
fi
done
fi
# Заменяем все существующие splash.png и splash-dark.png
EXISTING_SPLASH=$(docker exec "${CONTAINER_NAME}" find /app -type f \( -name "splash.png" -o -name "splash-dark.png" -o -name "splash-light.png" \) 2>/dev/null)
if [ -f "$MEDIA_DIR/logo.png" ]; then
echo "$EXISTING_SPLASH" | while read -r splash_file; do
if [ -n "$splash_file" ] && [[ ! "$splash_file" == *"node_modules"* ]]; then
splash_name=$(basename "$splash_file")
# Для темной темы используем logo-dark если есть, иначе обычный logo
if [[ "$splash_name" == *"dark"* ]]; then
if [ -f "$MEDIA_DIR/logo-dark.svg" ]; then
# Конвертируем SVG в PNG или используем logo.png
echo " Замена splash-dark.png: $splash_file"
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${splash_file}" 2>/dev/null || true
else
echo " Замена splash-dark.png: $splash_file"
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${splash_file}" 2>/dev/null || true
fi
# Для светлой темы
elif [[ "$splash_name" == *"light"* ]]; then
if [ -f "$MEDIA_DIR/logo-light.svg" ]; then
echo " Замена splash-light.png: $splash_file"
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${splash_file}" 2>/dev/null || true
else
echo " Замена splash-light.png: $splash_file"
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${splash_file}" 2>/dev/null || true
fi
# Обычный splash
else
echo " Замена splash.png: $splash_file"
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${splash_file}" 2>/dev/null || true
fi
fi
done
fi
# Заменяем все существующие logo файлы (включая logo-dark и logo-light)
EXISTING_LOGOS=$(docker exec "${CONTAINER_NAME}" find /app -type f \( -name "logo.png" -o -name "logo.svg" -o -name "logo-light.*" -o -name "logo-dark.*" \) 2>/dev/null)
if [ -f "$MEDIA_DIR/logo.png" ]; then
echo "$EXISTING_LOGOS" | while read -r logo_file; do
if [ -n "$logo_file" ] && [[ ! "$logo_file" == *"node_modules"* ]]; then
logo_name=$(basename "$logo_file")
# Для темной темы используем logo-dark если есть, иначе обычный logo
if [[ "$logo_name" == *"dark"* ]]; then
if [ -f "$MEDIA_DIR/logo-dark.svg" ]; then
docker cp "$MEDIA_DIR/logo-dark.svg" "${CONTAINER_NAME}:${logo_file}" 2>/dev/null || true
else
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${logo_file}" 2>/dev/null || true
fi
# Для светлой темы используем logo-light если есть
elif [[ "$logo_name" == *"light"* ]]; then
if [ -f "$MEDIA_DIR/logo-light.svg" ]; then
docker cp "$MEDIA_DIR/logo-light.svg" "${CONTAINER_NAME}:${logo_file}" 2>/dev/null || true
else
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${logo_file}" 2>/dev/null || true
fi
# Обычный логотип
else
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${logo_file}" 2>/dev/null || true
fi
fi
done
fi
echo "2. БЕЗОПАСНОЕ удаление '(Open WebUI)' из HTML/Svelte и Python шаблонов..."
# HTML, Svelte и Python файлы (шаблоны)
FILES=$(docker exec "${CONTAINER_NAME}" find /app -type f \
\( -name "*.html" -o -name "*.svelte" -o -name "*.py" \) \
! -path "*/node_modules/*" ! -path "*/.next/*" \
! -path "*/oauth*" ! -path "*/oidc*" ! -path "*authentik*" ! -path "*openid*" \
! -name "*test*" ! -name "*__pycache__*" \
-exec grep -lE "(Open WebUI)|\(Open WebUI\)" {} \; 2>/dev/null)
if [ -z "$FILES" ]; then
echo " Файлов с '(Open WebUI)' не найдено"
else
COUNT=$(echo "$FILES" | wc -l)
echo " Найдено файлов: $COUNT"
echo "$FILES" | while read file; do
if [ -n "$file" ]; then
# Безопасная замена только в HTML/Svelte
docker exec "${CONTAINER_NAME}" sed -i \
-e 's/(Open WebUI)//g' \
-e 's/\(Open WebUI\)//g' \
-e 's/ (Open WebUI)//g' \
-e 's/ \(Open WebUI\)//g' \
-e 's/iiEasyWeb (Open WebUI)/iiEasyWeb/g' \
-e 's/Войти в iiEasyWeb (Open WebUI)/Войти в iiEasyWeb/g' \
"$file" 2>/dev/null || true
fi
done
fi
echo "3. Замена ссылок на документацию (только в HTML/Svelte)..."
DOC_FILES=$(docker exec "${CONTAINER_NAME}" find /app/web -type f \
\( -name "*.html" -o -name "*.svelte" \) \
! -path "*/node_modules/*" ! -path "*/.next/*" \
-exec grep -lE "docs\.openwebui\.com|open-webui\.com/docs" {} \; 2>/dev/null)
if [ -z "$DOC_FILES" ]; then
echo " Файлов со ссылками не найдено"
else
echo "$DOC_FILES" | while read file; do
if [ -n "$file" ]; then
docker exec "${CONTAINER_NAME}" sed -i \
-e 's|https://docs.openwebui.com|https://note.iieasy.ru|g' \
-e 's|https://open-webui.com/docs|https://note.iieasy.ru|g' \
-e 's|docs\.openwebui\.com|note.iieasy.ru|g' \
"$file" 2>/dev/null || true
fi
done
fi
echo "4. Исправление favicon.ico, favicon.png, favicon-dark.png и apple-touch-icon.png в HTML/Svelte шаблонах..."
# Заменяем ссылки на favicon.ico, favicon.png, favicon-dark.png и apple-touch-icon.png на наш логотип
TEMPLATE_FILES=$(docker exec "${CONTAINER_NAME}" find /app/web -type f \
\( -name "*.html" -o -name "*.svelte" \) \
! -path "*/node_modules/*" ! -path "*/.next/*" \
-exec grep -lE "/static/favicon(-dark|-light)?\.(png|ico)|favicon(-dark|-light)?\.(ico|png)|apple-touch-icon\.png|rel=\"(shortcut )?icon\"|rel=\"apple-touch-icon\"" {} \; 2>/dev/null)
if [ -n "$TEMPLATE_FILES" ]; then
echo "$TEMPLATE_FILES" | while read file; do
if [ -n "$file" ]; then
# Заменяем favicon.ico, favicon.png, favicon-dark.png и apple-touch-icon.png на logo.png в ссылках
docker exec "${CONTAINER_NAME}" sed -i \
-e 's|/static/favicon-dark\.png|/static/logo.png|g' \
-e 's|/static/favicon-light\.png|/static/logo.png|g' \
-e 's|/static/favicon\.ico|/static/logo.png|g' \
-e 's|/static/favicon\.png|/static/logo.png|g' \
-e 's|/static/apple-touch-icon\.png|/static/logo.png|g' \
-e 's|src="/static/favicon-dark\.png"|src="/static/logo.png"|g' \
-e 's|src="/static/favicon\.png"|src="/static/logo.png"|g' \
-e 's|src="/static/apple-touch-icon\.png"|src="/static/logo.png"|g' \
-e 's|href="[^"]*favicon-dark\.png"|href="/static/logo.png"|g' \
-e 's|href="[^"]*favicon\.ico"|href="/static/logo.png"|g' \
-e 's|href="[^"]*favicon\.png"|href="/static/logo.png"|g' \
-e 's|href="[^"]*apple-touch-icon\.png"|href="/static/logo.png"|g' \
-e 's|href="https://odo\.iieasy\.ru/static/favicon\.ico"|href="/static/logo.png"|g' \
"$file" 2>/dev/null || true
fi
done
fi
echo "4.1. Исправление splash-dark.png, favicon-dark.png и логотипов в окне авторизации..."
# Ищем файлы, где используется splash-dark.png, favicon-dark.png или логотип в окне авторизации
AUTH_FILES=$(docker exec "${CONTAINER_NAME}" find /app/web -type f \
\( -name "*.html" -o -name "*.svelte" \) \
! -path "*/node_modules/*" ! -path "*/.next/*" \
-exec grep -lE "splash-dark|splash-dark\.png|favicon-dark\.png|auth.*logo|login.*logo|dark.*splash|dark.*favicon" {} \; 2>/dev/null)
if [ -n "$AUTH_FILES" ]; then
echo "$AUTH_FILES" | while read file; do
if [ -n "$file" ]; then
echo " Исправление логотипа в окне авторизации: $file"
# Заменяем splash-dark.png и favicon-dark.png на наш логотип
docker exec "${CONTAINER_NAME}" sed -i \
-e 's|/static/splash-dark\.png|/static/logo.png|g' \
-e 's|/static/favicon-dark\.png|/static/logo.png|g' \
-e 's|splash-dark\.png|logo.png|g' \
-e 's|favicon-dark\.png|logo.png|g' \
-e 's|src="/static/favicon-dark\.png"|src="/static/logo.png"|g' \
-e 's|src="[^"]*splash-dark[^"]*"|src="/static/logo.png"|g' \
-e 's|src="[^"]*favicon-dark[^"]*"|src="/static/logo.png"|g' \
"$file" 2>/dev/null || true
fi
done
fi
echo "5. Исправление API endpoint для изображения профиля модели..."
# Ищем и заменяем API endpoint для изображения профиля модели
API_FILES=$(docker exec "${CONTAINER_NAME}" find /app -type f \
\( -name "*.html" -o -name "*.svelte" -o -name "*.js" -o -name "*.ts" -o -name "*.py" \) \
! -path "*/node_modules/*" ! -path "*/.next/*" \
! -path "*/oauth*" ! -path "*/oidc*" ! -path "*authentik*" ! -path "*openid*" \
-exec grep -l "model/profile/image\|models.*profile.*image" {} \; 2>/dev/null)
if [ -n "$API_FILES" ]; then
echo "$API_FILES" | while read file; do
if [ -n "$file" ]; then
echo " Исправление API endpoint в: $file"
# Заменяем API endpoint на статический логотип
docker exec "${CONTAINER_NAME}" sed -i 's|/api/v1/models/model/profile/image|/static/logo.png|g' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i 's|api/v1/models/model/profile/image|static/logo.png|g' "$file" 2>/dev/null || true
fi
done
fi
# Также ищем в Python коде, который генерирует этот endpoint
PYTHON_API=$(docker exec "${CONTAINER_NAME}" find /app/backend -type f -name "*.py" \
! -path "*/oauth*" ! -path "*/oidc*" ! -path "*authentik*" ! -path "*openid*" \
-exec grep -l "profile.*image\|model.*profile\|def.*profile" {} \; 2>/dev/null)
if [ -n "$PYTHON_API" ]; then
echo "$PYTHON_API" | while read file; do
if [ -n "$file" ]; then
# Ищем функции, которые возвращают изображение профиля и заменяем на статический логотип
echo " Проверка Python API в: $file"
# Это нужно делать более аккуратно - просто заменим возвращаемый путь
docker exec "${CONTAINER_NAME}" sed -i 's|/static/favicon.png|/static/logo.png|g' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i 's|favicon.png|logo.png|g' "$file" 2>/dev/null || true
fi
done
fi
echo "6. Удаление проверки обновлений и ссылок на GitHub (только Svelte файлы)..."
# Ищем ТОЛЬКО в исходных Svelte файлах - не трогаем скомпилированные JS
UPDATE_FILES=$(docker exec "${CONTAINER_NAME}" find /app/web -type f -name "*.svelte" \
! -path "*/node_modules/*" ! -path "*/.next/*" \
-exec grep -lE "Проверить обновления|Check for updates|github.com/open-webui/releases|последняя|latest" {} \; 2>/dev/null)
if [ -z "$UPDATE_FILES" ]; then
echo " Файлов с проверкой обновлений не найдено"
else
COUNT=$(echo "$UPDATE_FILES" | wc -l)
echo " Найдено Svelte файлов: $COUNT"
echo "$UPDATE_FILES" | while read file; do
if [ -n "$file" ]; then
echo " Удаление проверки обновлений из: $file"
# Удаляем кнопку "Проверить обновления"
docker exec "${CONTAINER_NAME}" sed -i '/Проверить обновления/d' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/Check for updates/d' "$file" 2>/dev/null || true
# Удаляем ссылку на GitHub releases с текстом "(последняя)"
docker exec "${CONTAINER_NAME}" sed -i 's|<a[^>]*href="https://github.com/open-webui/open-webui/releases/tag/[^"]*"[^>]*>(последняя)</a>||g' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i 's|(последняя)||g' "$file" 2>/dev/null || true
# Удаляем "Посмотреть, что нового"
docker exec "${CONTAINER_NAME}" sed -i '/Посмотреть, что нового/d' "$file" 2>/dev/null || true
fi
done
fi
echo "7. Удаление социальных сетей, GitHub и блока 'Помощь' (только Svelte файлы)..."
# Ищем ТОЛЬКО в исходных Svelte файлах
SOCIAL_FILES=$(docker exec "${CONTAINER_NAME}" find /app/web -type f -name "*.svelte" \
! -path "*/node_modules/*" ! -path "*/.next/*" \
-exec grep -lE "discord|twitter|x\.com|Github Repo|github.com/open-webui|Помощь|Help|обратитесь за поддержкой" {} \; 2>/dev/null)
if [ -z "$SOCIAL_FILES" ]; then
echo " Файлов с соцсетями не найдено"
else
COUNT=$(echo "$SOCIAL_FILES" | wc -l)
echo " Найдено Svelte файлов: $COUNT"
echo "$SOCIAL_FILES" | while read file; do
if [ -n "$file" ]; then
echo " Удаление соцсетей из: $file"
# Удаляем ссылки на Discord
docker exec "${CONTAINER_NAME}" sed -i 's|<a[^>]*href="https://discord.gg/[^"]*"[^>]*>.*</a>||g' "$file" 2>/dev/null || true
# Удаляем ссылки на Twitter/X
docker exec "${CONTAINER_NAME}" sed -i 's|<a[^>]*href="https://twitter.com/[^"]*"[^>]*>.*</a>||g' "$file" 2>/dev/null || true
# Удаляем ссылки на GitHub repo
docker exec "${CONTAINER_NAME}" sed -i 's|<a[^>]*href="https://github.com/open-webui/open-webui"[^>]*>.*</a>||g' "$file" 2>/dev/null || true
# Удаляем badges (img.shields.io)
docker exec "${CONTAINER_NAME}" sed -i 's|<img[^>]*shields.io[^>]*>||g' "$file" 2>/dev/null || true
# Удаляем текст блока "Помощь"
docker exec "${CONTAINER_NAME}" sed -i '/Узнайте, как использовать/d' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/обратитесь за поддержкой/d' "$file" 2>/dev/null || true
fi
done
fi
echo "8. Удаление блока 'Лицензия' полностью (только Svelte файлы)..."
# Ищем ТОЛЬКО в исходных Svelte файлах
LICENSE_FILES=$(docker exec "${CONTAINER_NAME}" find /app/web -type f -name "*.svelte" \
! -path "*/node_modules/*" ! -path "*/.next/*" \
-exec grep -lE "Лицензия|License|лицензионный тарифный план|Перейдите на лицензионный" {} \; 2>/dev/null)
if [ -z "$LICENSE_FILES" ]; then
echo " Файлов с лицензией не найдено"
else
COUNT=$(echo "$LICENSE_FILES" | wc -l)
echo " Найдено Svelte файлов: $COUNT"
echo "$LICENSE_FILES" | while read file; do
if [ -n "$file" ]; then
echo " Удаление блока лицензии из: $file"
# Удаляем только текст блока лицензии
docker exec "${CONTAINER_NAME}" sed -i '/Перейдите на лицензионный/d' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/расширенные возможности/d' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/настраиваемую тематику/d' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/фирменный стиль/d' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/специальную поддержку/d' "$file" 2>/dev/null || true
fi
done
fi
echo "9. Добавление надписи 'Основано на Open WebUI' внизу настроек..."
# Ищем файлы настроек для добавления надписи
# Это сложнее сделать автоматически, поэтому просто отмечаем файлы
SETTINGS_FILES=$(docker exec "${CONTAINER_NAME}" find /app/web -type f -name "*.svelte" \
! -path "*/node_modules/*" ! -path "*/.next/*" \
-exec grep -l "Settings\|Настройки\|General\|Общее" {} \; 2>/dev/null | head -3)
if [ -n "$SETTINGS_FILES" ]; then
echo " Найдены файлы настроек (надпись нужно добавить вручную):"
echo "$SETTINGS_FILES" | while read file; do
if [ -n "$file" ]; then
echo " - $file"
fi
done
echo ""
echo " Добавьте в конец блока настроек перед закрывающим тегом:"
echo " <div class='text-xs text-gray-400 dark:text-gray-500 mt-4 text-center'>"
echo " Основано на <span class='text-gray-500'>Open WebUI</span>"
echo " </div>"
fi
echo ""
echo "10. Перезапуск контейнера..."
docker restart "${CONTAINER_NAME}" >/dev/null 2>&1 || {
echo "Предупреждение: Перезапустите вручную: docker restart ${CONTAINER_NAME}"
}
echo ""
echo "=== БЕЗОПАСНЫЙ ребрендинг завершен! ==="
echo ""
echo "Проверьте:"
echo " 1. Откройте https://odo.iieasy.ru"
echo " 2. splash.png должен быть заменен на ваш логотип"
echo " 3. Проверка обновлений и ссылки на GitHub должны быть удалены"
echo " 4. Социальные сети и блок лицензии должны быть удалены"
echo " 5. Очистите кеш браузера (Ctrl+Shift+Delete)"
echo ""
echo "Примечание: Надпись 'Основано на Open WebUI' нужно добавить вручную"
echo "в файлах настроек (см. вывод выше) или через Admin Panel"

185
scripts/rebrand_ultra_safe.sh Executable file
View File

@@ -0,0 +1,185 @@
#!/bin/bash
# УЛЬТРА БЕЗОПАСНЫЙ ребрендинг - удаляет только конкретные блоки, не ломая структуру
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
MEDIA_DIR="$PROJECT_DIR/media"
CONTAINER_NAME="open-webui"
echo "=== УЛЬТРА БЕЗОПАСНЫЙ ребрендинг Open WebUI для iiEasy ==="
echo "⚠ Удаляет только конкретные HTML блоки, не трогает код"
echo ""
if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
echo "Ошибка: Контейнер ${CONTAINER_NAME} не запущен."
exit 1
fi
echo "1. Замена логотипов..."
STATIC_DIRS=(
"/app/web/build/_app/immutable"
"/app/web/static"
"/app/web/build"
)
for dir in "${STATIC_DIRS[@]}"; do
if docker exec "${CONTAINER_NAME}" test -d "$dir" 2>/dev/null; then
if [ -f "$MEDIA_DIR/logo.png" ]; then
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${dir}/logo.png" 2>/dev/null || true
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${dir}/splash.png" 2>/dev/null || true
fi
if [ -f "$MEDIA_DIR/favicon.png" ]; then
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${dir}/favicon.png" 2>/dev/null || true
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${dir}/favicon.ico" 2>/dev/null || true
fi
fi
done
# Заменяем существующие файлы
EXISTING_SPLASH=$(docker exec "${CONTAINER_NAME}" find /app -type f -name "splash.png" 2>/dev/null)
if [ -f "$MEDIA_DIR/logo.png" ]; then
echo "$EXISTING_SPLASH" | while read -r splash_file; do
if [ -n "$splash_file" ] && [[ ! "$splash_file" == *"node_modules"* ]]; then
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${splash_file}" 2>/dev/null || true
fi
done
fi
EXISTING_FAVICONS=$(docker exec "${CONTAINER_NAME}" find /app -type f -name "favicon.png" 2>/dev/null)
if [ -f "$MEDIA_DIR/logo.png" ]; then
echo "$EXISTING_FAVICONS" | while read -r favicon_file; do
if [ -n "$favicon_file" ] && [[ ! "$favicon_file" == *"node_modules"* ]]; then
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${favicon_file}" 2>/dev/null || true
fi
done
fi
echo "2. Удаление '(Open WebUI)' из HTML/Svelte..."
FILES=$(docker exec "${CONTAINER_NAME}" find /app/web -type f \
\( -name "*.html" -o -name "*.svelte" \) \
! -path "*/node_modules/*" ! -path "*/.next/*" \
-exec grep -lE "(Open WebUI)|\(Open WebUI\)" {} \; 2>/dev/null)
if [ -n "$FILES" ]; then
echo "$FILES" | while read file; do
if [ -n "$file" ]; then
docker exec "${CONTAINER_NAME}" sed -i \
-e 's/(Open WebUI)//g' \
-e 's/\(Open WebUI\)//g' \
-e 's/ (Open WebUI)//g' \
-e 's/Войти в iiEasyWeb (Open WebUI)/Войти в iiEasyWeb/g' \
"$file" 2>/dev/null || true
fi
done
fi
echo "3. Замена ссылок на документацию..."
DOC_FILES=$(docker exec "${CONTAINER_NAME}" find /app/web -type f \
\( -name "*.html" -o -name "*.svelte" \) \
! -path "*/node_modules/*" ! -path "*/.next/*" \
-exec grep -lE "docs\.openwebui\.com" {} \; 2>/dev/null)
if [ -n "$DOC_FILES" ]; then
echo "$DOC_FILES" | while read file; do
if [ -n "$file" ]; then
docker exec "${CONTAINER_NAME}" sed -i \
-e 's|https://docs.openwebui.com|https://note.iieasy.ru|g' \
-e 's|docs\.openwebui\.com|note.iieasy.ru|g' \
"$file" 2>/dev/null || true
fi
done
fi
echo "4. Исправление favicon.png на logo.png..."
TEMPLATE_FILES=$(docker exec "${CONTAINER_NAME}" find /app/web -type f \
\( -name "*.html" -o -name "*.svelte" \) \
! -path "*/node_modules/*" ! -path "*/.next/*" \
-exec grep -l "/static/favicon.png" {} \; 2>/dev/null)
if [ -n "$TEMPLATE_FILES" ]; then
echo "$TEMPLATE_FILES" | while read file; do
if [ -n "$file" ]; then
docker exec "${CONTAINER_NAME}" sed -i 's|/static/favicon.png|/static/logo.png|g' "$file" 2>/dev/null || true
fi
done
fi
echo "5. Исправление API endpoint для изображения профиля модели..."
API_FILES=$(docker exec "${CONTAINER_NAME}" find /app/web -type f \
\( -name "*.html" -o -name "*.svelte" \) \
! -path "*/node_modules/*" ! -path "*/.next/*" \
-exec grep -l "model/profile/image" {} \; 2>/dev/null)
if [ -n "$API_FILES" ]; then
echo "$API_FILES" | while read file; do
if [ -n "$file" ]; then
docker exec "${CONTAINER_NAME}" sed -i 's|/api/v1/models/model/profile/image|/static/logo.png|g' "$file" 2>/dev/null || true
fi
done
fi
echo "6. Удаление конкретных HTML блоков (безопасно)..."
# Ищем только Svelte файлы настроек
SETTINGS_FILES=$(docker exec "${CONTAINER_NAME}" find /app/web -type f -name "*.svelte" \
! -path "*/node_modules/*" ! -path "*/.next/*" \
-exec grep -l "Проверить обновления\|Помощь\|Лицензия\|Version\|Help\|License" {} \; 2>/dev/null)
if [ -n "$SETTINGS_FILES" ]; then
echo "$SETTINGS_FILES" | while read file; do
if [ -n "$file" ]; then
echo " Обработка файла настроек: $file"
# Удаляем блок проверки обновлений - ищем по уникальным классам и тексту
# Удаляем кнопку "Проверить обновления"
docker exec "${CONTAINER_NAME}" sed -i '/Проверить обновления/d' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/Check for updates/d' "$file" 2>/dev/null || true
# Удаляем ссылку "(последняя)" с GitHub
docker exec "${CONTAINER_NAME}" sed -i 's|<a[^>]*github.com/open-webui/releases[^>]*>.*последняя.*</a>||g' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i 's|(последняя)||g' "$file" 2>/dev/null || true
# Удаляем "Посмотреть, что нового"
docker exec "${CONTAINER_NAME}" sed -i '/Посмотреть, что нового/d' "$file" 2>/dev/null || true
# Удаляем весь блок "Помощь" - ищем по заголовку и удаляем до следующего блока
# Более безопасно - удаляем только содержимое блока
docker exec "${CONTAINER_NAME}" sed -i '/Узнайте, как использовать/d' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/обратитесь за поддержкой/d' "$file" 2>/dev/null || true
# Удаляем badges соцсетей - ищем по shields.io
docker exec "${CONTAINER_NAME}" sed -i 's|<a[^>]*discord.gg[^>]*>.*</a>||g' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i 's|<a[^>]*twitter.com[^>]*>.*</a>||g' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i 's|<a[^>]*github.com/open-webui[^>]*>.*</a>||g' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i 's|<img[^>]*shields.io[^>]*>||g' "$file" 2>/dev/null || true
# Удаляем блок "Лицензия"
docker exec "${CONTAINER_NAME}" sed -i '/Перейдите на лицензионный/d' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/расширенные возможности/d' "$file" 2>/dev/null || true
fi
done
fi
echo ""
echo "7. Перезапуск контейнера..."
docker restart "${CONTAINER_NAME}" >/dev/null 2>&1 || {
echo "Предупреждение: Перезапустите вручную: docker restart ${CONTAINER_NAME}"
}
echo ""
echo "=== УЛЬТРА БЕЗОПАСНЫЙ ребрендинг завершен! ==="
echo ""
echo "Проверьте:"
echo " 1. Откройте https://odo.iieasy.ru"
echo " 2. Очистите кеш браузера (Ctrl+Shift+Delete)"
echo ""
echo "Примечание: Если блоки все еще видны, они могут быть в скомпилированных JS файлах."
echo "В этом случае нужно найти исходные Svelte файлы и удалить блоки там."

133
scripts/remove_footer_links.sh Executable file
View File

@@ -0,0 +1,133 @@
#!/bin/bash
# Удаление социальных сетей, GitHub и лицензии из футера
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
CONTAINER_NAME="open-webui"
echo "=== Удаление социальных сетей, GitHub и лицензии из футера ==="
echo ""
if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
echo "Ошибка: Контейнер ${CONTAINER_NAME} не запущен."
exit 1
fi
echo "1. Удаление ссылок на Discord..."
docker exec "${CONTAINER_NAME}" find /app -type f \
! -path "*/node_modules/*" ! -path "*/.next/*" \
! -path "*/oauth*" ! -path "*/oidc*" ! -path "*authentik*" ! -path "*openid*" \
2>/dev/null | while read file; do
if docker exec "${CONTAINER_NAME}" file "$file" 2>/dev/null | grep -q "text\|JSON\|ASCII"; then
if docker exec "${CONTAINER_NAME}" grep -qE "discord\.gg|discord\.com|Discord" "$file" 2>/dev/null; then
echo " Удаление Discord из: $file"
# Удаляем строки с Discord
docker exec "${CONTAINER_NAME}" sed -i '/discord\.gg/d' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/discord\.com/d' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/Discord/d' "$file" 2>/dev/null || true
# Удаляем ссылки
docker exec "${CONTAINER_NAME}" sed -i 's|https://discord.gg/[^"]*||g' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i 's|https://discord.com/[^"]*||g' "$file" 2>/dev/null || true
fi
fi
done
echo "2. Удаление ссылок на Twitter/X..."
docker exec "${CONTAINER_NAME}" find /app -type f \
! -path "*/node_modules/*" ! -path "*/.next/*" \
! -path "*/oauth*" ! -path "*/oidc*" ! -path "*authentik*" ! -path "*openid*" \
2>/dev/null | while read file; do
if docker exec "${CONTAINER_NAME}" file "$file" 2>/dev/null | grep -q "text\|JSON\|ASCII"; then
if docker exec "${CONTAINER_NAME}" grep -qE "twitter\.com|x\.com|X \(formerly Twitter\)|Twitter" "$file" 2>/dev/null; then
echo " Удаление Twitter/X из: $file"
# Удаляем строки с Twitter/X
docker exec "${CONTAINER_NAME}" sed -i '/twitter\.com/d' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/x\.com/d' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/X (formerly Twitter)/d' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/Twitter/d' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/Follow/d' "$file" 2>/dev/null || true
# Удаляем ссылки
docker exec "${CONTAINER_NAME}" sed -i 's|https://twitter.com/[^"]*||g' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i 's|https://x.com/[^"]*||g' "$file" 2>/dev/null || true
fi
fi
done
echo "3. Удаление ссылок на GitHub Repo..."
docker exec "${CONTAINER_NAME}" find /app -type f \
! -path "*/node_modules/*" ! -path "*/.next/*" \
! -path "*/oauth*" ! -path "*/oidc*" ! -path "*authentik*" ! -path "*openid*" \
2>/dev/null | while read file; do
if docker exec "${CONTAINER_NAME}" file "$file" 2>/dev/null | grep -q "text\|JSON\|ASCII"; then
if docker exec "${CONTAINER_NAME}" grep -qE "github\.com/open-webui|Github Repo|GitHub" "$file" 2>/dev/null; then
echo " Удаление GitHub из: $file"
# Удаляем строки с GitHub
docker exec "${CONTAINER_NAME}" sed -i '/Github Repo/d' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/GitHub Repo/d' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/github\.com\/open-webui/d' "$file" 2>/dev/null || true
# Удаляем ссылки на GitHub repo (но не все GitHub ссылки, только repo)
docker exec "${CONTAINER_NAME}" sed -i 's|https://github.com/open-webui/[^"]*||g' "$file" 2>/dev/null || true
fi
fi
done
echo "4. Удаление строки про лицензию и тарифный план..."
docker exec "${CONTAINER_NAME}" find /app -type f \
! -path "*/node_modules/*" ! -path "*/.next/*" \
! -path "*/oauth*" ! -path "*/oidc*" ! -path "*authentik*" ! -path "*openid*" \
2>/dev/null | while read file; do
if docker exec "${CONTAINER_NAME}" file "$file" 2>/dev/null | grep -q "text\|JSON\|ASCII"; then
if docker exec "${CONTAINER_NAME}" grep -qE "лицензионный тарифный план|Перейдите на лицензионный|License|license|upgrade|Upgrade|тарифный план" "$file" 2>/dev/null; then
echo " Удаление лицензии из: $file"
# Удаляем строки про лицензию
docker exec "${CONTAINER_NAME}" sed -i '/лицензионный тарифный план/d' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/Перейдите на лицензионный/d' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/расширенные возможности/d' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/настраиваемую тематику/d' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/фирменный стиль/d' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/специальную поддержку/d' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/License/d' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/Лицензия/d' "$file" 2>/dev/null || true
fi
fi
done
echo "5. Удаление всего блока 'Помощь' (Help section)..."
docker exec "${CONTAINER_NAME}" find /app/web -type f \( -name "*.svelte" -o -name "*.html" -o -name "*.js" -o -name "*.ts" \) \
! -path "*/node_modules/*" ! -path "*/.next/*" \
2>/dev/null | while read file; do
if docker exec "${CONTAINER_NAME}" file "$file" 2>/dev/null | grep -q "text\|JSON\|ASCII"; then
if docker exec "${CONTAINER_NAME}" grep -qE "Помощь|Узнайте, как использовать|обратитесь за поддержкой|Документация" "$file" 2>/dev/null; then
echo " Удаление блока помощи из: $file"
# Удаляем строки из блока помощи
docker exec "${CONTAINER_NAME}" sed -i '/Помощь/d' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/Узнайте, как использовать/d' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/обратитесь за поддержкой/d' "$file" 2>/dev/null || true
docker exec "${CONTAINER_NAME}" sed -i '/к сообществу/d' "$file" 2>/dev/null || true
fi
fi
done
echo ""
echo "6. Перезапуск контейнера..."
docker restart "${CONTAINER_NAME}" >/dev/null 2>&1 || {
echo "Предупреждение: Перезапустите вручную: docker restart ${CONTAINER_NAME}"
}
echo ""
echo "=== Удаление завершено! ==="
echo ""
echo "Проверьте:"
echo " 1. Откройте https://odo.iieasy.ru"
echo " 2. В футере не должно быть ссылок на Discord, Twitter/X, GitHub"
echo " 3. Не должно быть строки про лицензию"
echo ""
echo "ВАЖНО: Очистите кеш браузера (Ctrl+Shift+Delete)"

68
scripts/test_direct_vision.sh Executable file
View File

@@ -0,0 +1,68 @@
#!/bin/bash
# Прямой тест vision через Ollama API (минуя Open WebUI)
set -e
CONTAINER_OLLAMA="ollama"
MODEL="gemma3n:e4b-it-fp16"
IMAGE_FILE="/home/its/iiEasyWeb/test_images/test_image.jpg"
echo "=== Прямой тест Vision через Ollama API ==="
echo ""
# Проверка файла изображения
if [ ! -f "$IMAGE_FILE" ]; then
echo "✗ Файл изображения не найден: $IMAGE_FILE"
echo "Скачайте изображение сначала"
exit 1
fi
echo "1. Кодирование изображения в base64..."
IMAGE_B64=$(base64 -w 0 "$IMAGE_FILE" 2>/dev/null || base64 "$IMAGE_FILE" | tr -d '\n')
echo " ✓ Изображение закодировано (${#IMAGE_B64} символов)"
echo ""
echo "2. Отправка запроса к Ollama API..."
echo " Модель: $MODEL"
echo " Промпт: Опиши это изображение на русском языке. Что ты видишь?"
echo ""
# Определяем команду docker
DOCKER_CMD="docker"
if ! docker ps >/dev/null 2>&1; then
DOCKER_CMD="sudo docker"
fi
# Отправляем запрос
RESPONSE=$($DOCKER_CMD exec $CONTAINER_OLLAMA curl -s --max-time 120 -X POST http://localhost:11434/api/generate \
-H 'Content-Type: application/json' \
-d "{
\"model\": \"$MODEL\",
\"prompt\": \"Опиши это изображение на русском языке. Что ты видишь на картинке?\",
\"images\": [\"$IMAGE_B64\"],
\"stream\": false
}" 2>&1)
echo ""
if echo "$RESPONSE" | grep -q '"response"'; then
echo "✓ Успешно! Ответ модели:"
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "$RESPONSE" | python3 -c "import sys, json; data=json.load(sys.stdin); print(data.get('response', ''))" 2>/dev/null || \
echo "$RESPONSE" | grep -o '"response":"[^"]*"' | sed 's/"response":"\(.*\)"/\1/' | sed 's/\\n/\n/g' | sed 's/\\"/"/g'
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "=== ✓ Vision работает напрямую через Ollama API! ==="
echo ""
echo "Если это работает, значит проблема в Open WebUI, а не в Ollama."
echo "Возможные причины:"
echo " 1. Open WebUI v0.8.3 не поддерживает vision для gemma3n"
echo " 2. Модель не помечена как vision-модель в настройках"
echo " 3. Нужно обновить Open WebUI до более новой версии"
elif echo "$RESPONSE" | grep -q "error\|Error"; then
echo "✗ Ошибка:"
echo "$RESPONSE" | grep -i error || echo "$RESPONSE"
else
echo "⚠ Неожиданный ответ:"
echo "$RESPONSE" | head -20
fi

142
scripts/test_vision.sh Executable file
View File

@@ -0,0 +1,142 @@
#!/bin/bash
# Скрипт для тестирования vision capabilities модели gemma3n:e4b-it-fp16
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
TEST_DIR="$PROJECT_DIR/test_images"
IMAGE_URL="https://yandex-images.clstorage.net/PU5vN2154/532297ZKm/lCuSfyMn0DdbJqcFVeFiB9Ti31Te2dZ1EepiRw3Cs0Qw8cQ1ND5OQRKC1yH4LnhdRtloQ4aXHng5ZSLNmXHy_8k293YSMsBWKnOvYAXBbhPcl6pYmqi-ZGWDazZo2pYkJNHpkJHrg5yiO0bEOIeEICe5cFqrojYgyNQ6mHj4e5IUb_Lri3uxo9fmXv0dMf7f1NvH9J5YVsyhRvvmtD9eTc1QfVxV42d8OotKrLTDfHx7jfDqjpIqHAyt9ngIIHsSjLtOJ7_jTYdHLn4hJWlCj_jj69gGlApIBYaTfoxPrtHFDGlYUd-PjHI3hijAm6hIb-lUFlpOehQAJTbwEGwjcqjjVns323WrR2q6CalVyg9kZ8s0jkBm8OVGr26UH86NHaD1rKnPH6zinwJIKBv8VEdF_FrGLt6QFOVqFOQY776w47YDNy8Biwfefg0lxVaL2E9L5NbAztwh8vuCcMNSEfG8aYQFBxe0Eu9CMPhz1Axz1dgqmkpSYCAR8nSA_GMSKKPa9-tzIUc7ooKthV1ykwSjrzhORKq0eRrvBlTbajF5yAUoVX-7XM7HouyU51xMaxFMBl7atixQFW4o1BznMoi_chuL8_mTO_IKce21CosEQ6Owkkhi9EGSz5pIE56NjURBxKn7pwTKt6K4FB_YYHexZG4qlsIg8GVaiJyIB0p0bx5PF4_FIy-SItWZId5f8M9_RIqg2hRVCqP2hDvSfWGAYUjJd6c8lr9mgGj7kPQbVQBCUgIm1NB9lsxQrIO-wO92fytbOQsjjiqBqa0CK9Qjy6wi6CoIfX5vZjw_pj1BKGm0UVOXuArPFngIN2AUgz3w4mZSTvz8IQYo_IhPVlQ7PiOjhyWD6wZSVZ3NDrvsC-8w4lxmzE32T1qMuy5JOTRxaNHHH0QCp4IkgMv8LH8JeJaOUj4sVOGCBBgQi5KMX74TQzOBW9cGTu35UZr3qAfj5O6kMogxjqNanMOs"
IMAGE_FILE="$TEST_DIR/test_image.jpg"
MODEL="gemma3n:e4b-it-fp16"
echo "=== Тестирование Vision Capabilities модели $MODEL ==="
echo ""
# Определяем команду docker (с sudo или без)
DOCKER_CMD="docker"
if ! docker ps >/dev/null 2>&1; then
DOCKER_CMD="sudo docker"
fi
# Проверка контейнера Ollama
echo "1. Проверка контейнера Ollama..."
if ! $DOCKER_CMD ps --format '{{.Names}}' 2>/dev/null | grep -q "^ollama$"; then
echo " ✗ Ошибка: Контейнер ollama не запущен"
echo " Запустите: docker compose up -d ollama"
exit 1
fi
echo " ✓ Контейнер ollama запущен"
# Проверка модели
echo ""
echo "2. Проверка загруженных моделей..."
MODELS=$($DOCKER_CMD exec ollama ollama list 2>/dev/null)
if echo "$MODELS" | grep -q "$MODEL"; then
echo " ✓ Модель $MODEL найдена"
echo "$MODELS" | grep "$MODEL"
else
echo " ✗ Модель $MODEL не найдена"
echo " Загрузите модель: docker exec ollama ollama pull $MODEL"
exit 1
fi
# Создаем директорию для тестовых изображений
mkdir -p "$TEST_DIR"
cd "$TEST_DIR"
# Скачиваем изображение
echo ""
echo "3. Подготовка тестового изображения..."
if [ ! -f "$IMAGE_FILE" ]; then
echo " Скачиваю изображение..."
curl -L -o "$IMAGE_FILE" "$IMAGE_URL" || {
echo " ✗ Ошибка: не удалось скачать изображение"
exit 1
}
echo " ✓ Изображение скачано: $IMAGE_FILE"
else
echo " ✓ Изображение уже существует: $IMAGE_FILE"
fi
# Проверяем размер файла
if [ -f "$IMAGE_FILE" ]; then
FILE_SIZE=$(stat -c%s "$IMAGE_FILE" 2>/dev/null || stat -f%z "$IMAGE_FILE" 2>/dev/null)
echo " Размер файла: $(numfmt --to=iec-i --suffix=B $FILE_SIZE 2>/dev/null || echo "${FILE_SIZE} bytes")"
# Проверяем тип файла
FILE_TYPE=$(file "$IMAGE_FILE" 2>/dev/null | cut -d: -f2)
echo " Тип файла: $FILE_TYPE"
fi
# Кодируем изображение в base64
echo ""
echo "4. Кодирование изображения в base64..."
IMAGE_B64=$(base64 -w 0 "$IMAGE_FILE" 2>/dev/null || base64 "$IMAGE_FILE" | tr -d '\n')
B64_LENGTH=${#IMAGE_B64}
echo " ✓ Изображение закодировано (длина: $B64_LENGTH символов)"
# Отправляем запрос к Ollama API
echo ""
echo "5. Отправка запроса к Ollama API..."
echo " Модель: $MODEL"
echo " Промпт: Опиши это изображение на русском языке. Что ты видишь на картинке?"
echo ""
# Отправляем запрос через Docker exec к Ollama API
# Используем временный файл для передачи большого base64
echo " Отправка запроса (это может занять некоторое время)..."
TEMP_FILE=$(mktemp)
cat > "$TEMP_FILE" <<EOF
{
"model": "$MODEL",
"prompt": "Опиши это изображение на русском языке. Что ты видишь на картинке?",
"images": ["$IMAGE_B64"],
"stream": false
}
EOF
# Копируем файл в контейнер и отправляем запрос
$DOCKER_CMD cp "$TEMP_FILE" ollama:/tmp/request.json >/dev/null 2>&1
RESPONSE=$($DOCKER_CMD exec ollama sh -c "curl -s --max-time 120 -X POST http://localhost:11434/api/generate \
-H 'Content-Type: application/json' \
-d @/tmp/request.json" 2>&1)
# Удаляем временный файл
rm -f "$TEMP_FILE"
# Проверяем ответ
if echo "$RESPONSE" | grep -q '"response"'; then
echo " ✓ Успешно! Ответ модели:"
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
# Извлекаем ответ более надежным способом
echo "$RESPONSE" | python3 -c "import sys, json; data=json.load(sys.stdin); print(data.get('response', ''))" 2>/dev/null || \
echo "$RESPONSE" | grep -o '"response":"[^"]*"' | sed 's/"response":"\(.*\)"/\1/' | sed 's/\\n/\n/g' | sed 's/\\"/"/g' | head -20
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "=== ✓ Тест завершен успешно! Vision работает! ==="
elif echo "$RESPONSE" | grep -q "error\|Error\|ERROR"; then
echo " ✗ Ошибка при запросе к API:"
echo "$RESPONSE" | grep -i error || echo "$RESPONSE"
echo ""
echo "Проверьте:"
echo " 1. Логи Ollama: $DOCKER_CMD logs ollama --tail 30"
echo " 2. Поддерживает ли модель vision: $DOCKER_CMD exec ollama ollama show $MODEL"
else
echo " ⚠ Неожиданный ответ от API:"
echo "$RESPONSE"
echo ""
echo "Попробуйте проверить через веб-интерфейс Open WebUI"
fi
echo ""
echo "6. Инструкции для тестирования через веб-интерфейс:"
echo ""
echo " Шаг 1: Откройте Open WebUI: https://odo.iieasy.ru"
echo " Шаг 2: Выберите модель: $MODEL"
echo " Шаг 3: Найдите кнопку загрузки изображения в поле ввода (📎 или 📷)"
echo " Шаг 4: Загрузите изображение: $IMAGE_FILE"
echo " Шаг 5: Задайте вопрос: \"Опиши это изображение на русском языке. Что ты видишь?\""
echo ""
echo "Файл изображения: $IMAGE_FILE"

50
scripts/update.sh Executable file
View File

@@ -0,0 +1,50 @@
#!/bin/bash
# Скрипт для обновления Open WebUI до последней версии
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
CONTAINER_NAME="open-webui"
echo "=== Обновление Open WebUI ==="
echo ""
cd "$PROJECT_DIR"
# Проверка текущей версии в docker-compose.yml
CURRENT_VERSION=$(grep "image: ghcr.io/open-webui/open-webui:" docker-compose.yml | sed 's/.*:\(.*\)/\1/')
echo "Текущая версия в docker-compose.yml: $CURRENT_VERSION"
echo ""
echo "1. Получение новых образов..."
sudo docker compose pull
echo ""
echo "2. Остановка контейнеров..."
sudo docker compose stop
echo ""
echo "3. Пересоздание контейнеров с новыми образами..."
sudo docker compose up -d
echo ""
echo "4. Ожидание запуска контейнеров (30 секунд)..."
sleep 30
echo ""
echo "5. Применение ребрендинга..."
if [ -f "$SCRIPT_DIR/rebrand.sh" ]; then
"$SCRIPT_DIR/rebrand.sh"
else
echo "⚠ Скрипт rebrand.sh не найден, пропускаем ребрендинг"
fi
echo ""
echo "=== Обновление завершено! ==="
echo ""
echo "Проверьте статус:"
echo " sudo docker compose ps"
echo ""
echo "Проверьте логи:"
echo " sudo docker compose logs open-webui --tail 50"

22
searxng/settings.yml Normal file
View File

@@ -0,0 +1,22 @@
# SearXNG Settings для работы с Open WebUI
# Этот файл включает поддержку JSON формата для API запросов
use_default_settings: true
server:
secret_key: "CHANGE_ME_SECRET_KEY"
bind_address: "0.0.0.0"
port: 8080
limiter: false
method: "GET"
search:
safe_search: 0
autocomplete: "google"
formats:
- html
- json
general:
instance_name: "SearXNG"
debug: false

121
ssl-setup-guide.md Normal file
View File

@@ -0,0 +1,121 @@
# Быстрая настройка SSL для odo.iieasy.ru
## Проблема: ERR_SSL_UNRECOGNIZED_NAME_ALERT
Эта ошибка означает, что SSL сертификат не настроен или не соответствует домену.
## Решение: Настройка Let's Encrypt сертификата
### Шаг 1: Проверка DNS
Убедитесь, что домен `odo.iieasy.ru` указывает на ваш сервер:
```bash
# Проверьте DNS запись
dig odo.iieasy.ru +short
# или
nslookup odo.iieasy.ru
```
Должен вернуться IP адрес вашего сервера.
### Шаг 2: Создание SSL сертификата в Nginx Proxy Manager
1. Войдите в Nginx Proxy Manager (обычно `http://your-server-ip:81`)
2. Перейдите в **SSL Certificates** (в левом меню)
3. Нажмите **Add SSL Certificate**
4. Выберите **Let's Encrypt**
5. Заполните форму:
```
Domain Names: odo.iieasy.ru
Email Address: ваш@email.com
Agree to Let's Encrypt Terms: ✓ (включено)
Use a DNS Challenge: ✗ (выключено, если порт 80 доступен)
```
6. Нажмите **Save**
7. Дождитесь создания сертификата (1-2 минуты). Статус должен стать зеленым.
### Шаг 3: Применение сертификата к Proxy Host
1. Перейдите в **Proxy Hosts**
2. Найдите или создайте Proxy Host для `odo.iieasy.ru`
3. Откройте его для редактирования
4. Перейдите на вкладку **SSL**
5. Выберите созданный сертификат в поле **SSL Certificate**
6. Включите опции:
- ✓ **Force SSL** (перенаправляет HTTP → HTTPS)
- ✓ **HTTP/2 Support**
- ✓ **HSTS Enabled** (опционально)
7. Нажмите **Save**
### Шаг 4: Проверка
1. Подождите 1-2 минуты для применения изменений
2. Откройте `https://odo.iieasy.ru` в браузере
3. Проверьте, что SSL работает (замочек в адресной строке)
## Альтернатива: Использование существующего сертификата
Если у вас уже есть SSL сертификат для домена:
1. **SSL Certificates** → **Add SSL Certificate** → **Custom**
2. Вставьте:
- **Certificate**: содержимое файла `.crt` или `.pem`
- **Private Key**: содержимое файла `.key`
3. Нажмите **Save**
4. Примените к Proxy Host как описано выше
## Временное решение (только для тестирования)
Если нужно быстро проверить работу без SSL:
1. В Proxy Host отключите SSL (вкладка SSL → SSL Certificate = None)
2. Используйте HTTP: `http://odo.iieasy.ru`
3. После настройки SSL включите обратно
## Проверка работы
```bash
# Проверка SSL сертификата
openssl s_client -connect odo.iieasy.ru:443 -servername odo.iieasy.ru
# Проверка доступности
curl -I https://odo.iieasy.ru
```
## Частые проблемы
### Сертификат не создается
- Проверьте, что порт 80 доступен из интернета
- Убедитесь, что DNS запись правильная
- Проверьте логи Nginx Proxy Manager
### Сертификат создан, но ошибка остается
- Убедитесь, что сертификат применен к Proxy Host
- Проверьте, что домен в сертификате совпадает с доменом в Proxy Host
- Очистите кеш браузера
### Порты 80/443 заняты
Если порты заняты другими сервисами, настройте перенаправление или используйте DNS challenge в Let's Encrypt.

1
test.jpg Normal file
View File

@@ -0,0 +1 @@
Geoblocked

BIN
test_images/test_image.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

34
worker/.env.example Normal file
View File

@@ -0,0 +1,34 @@
# === NEXTCLOUD ===
DOMAIN_NEXTCLOUD=https://next.iieasy.ru
NC_USER=your_nextcloud_username
NC_APP_PASSWORD=your_app_password
# Пути для сканирования (разделенные запятыми)
# {username} будет заменен на имя пользователя Nextcloud
NC_SCAN_PATHS=/home/{username}/Documents,/home/{username}/Files,/Shared/Documents
# === OPEN WEBUI API ===
DOMAIN_OPENWEBUI=https://odo.iieasy.ru
OPENWEBUI_API_KEY=your_api_key_here
OPENWEBUI_TIMEOUT=300
# === AUTHENTIK (опционально, для маппинга пользователей) ===
DOMAIN_AUTHENTIK=https://auth.iieasy.ru
AUTHENTIK_API_TOKEN=your_authentik_api_token
# Маппинг пользователей Nextcloud -> Authentik (опционально)
# Формат: NC_USER_{nextcloud_username}={authentik_user_id}
# NC_USER_john=user.12345678-1234-1234-1234-123456789012
# === НАСТРОЙКИ ВОРКЕРА ===
# Интервал синхронизации в секундах (по умолчанию 300 = 5 минут)
SYNC_INTERVAL=300
# Максимальный размер файла для обработки в байтах (по умолчанию 100MB)
MAX_FILE_SIZE=104857600
# Путь к БД состояния синхронизации
DB_PATH=sync_state.db
# Уровень логирования (DEBUG, INFO, WARNING, ERROR)
LOG_LEVEL=INFO

132
worker/config.py Normal file
View File

@@ -0,0 +1,132 @@
"""
Конфигурация для воркера синхронизации Nextcloud -> Qdrant
Типизация и валидация настроек
"""
import os
from pathlib import Path
from dataclasses import dataclass
from typing import List, Optional
from dotenv import load_dotenv
# Загрузка переменных окружения: сначала worker/.env, затем корневой .env
load_dotenv()
root_env = Path(__file__).resolve().parent.parent / ".env"
load_dotenv(root_env)
@dataclass
class NextcloudConfig:
"""Конфигурация подключения к Nextcloud"""
url: str
username: str
password: str
scan_paths: List[str] # Пути для сканирования
@dataclass
class OpenWebUIConfig:
"""Конфигурация подключения к Open WebUI API"""
api_url: str
api_key: str
timeout: int = 300 # Таймаут для больших файлов (секунды)
@dataclass
class AuthentikConfig:
"""Конфигурация для маппинга пользователей Authentik"""
api_url: Optional[str] = None
api_token: Optional[str] = None
@dataclass
class WorkerConfig:
"""Основная конфигурация воркера"""
nextcloud: NextcloudConfig
openwebui: OpenWebUIConfig
authentik: AuthentikConfig
sync_interval: int = 300 # Интервал синхронизации (секунды)
max_file_size: int = 100 * 1024 * 1024 # Максимальный размер файла (100MB)
db_path: str = "sync_state.db" # Путь к SQLite БД для отслеживания состояния
log_level: str = "INFO"
def load_config() -> WorkerConfig:
"""
Загрузка и валидация конфигурации из переменных окружения
Returns:
WorkerConfig: Валидированная конфигурация
Raises:
ValueError: Если обязательные переменные не установлены
"""
# Nextcloud конфигурация
nc_url = os.getenv("DOMAIN_NEXTCLOUD", "").rstrip("/")
if not nc_url:
raise ValueError("DOMAIN_NEXTCLOUD не установлен в .env")
nc_user = os.getenv("NC_USER")
if not nc_user:
raise ValueError("NC_USER не установлен в .env")
nc_password = os.getenv("NC_APP_PASSWORD")
if not nc_password:
raise ValueError("NC_APP_PASSWORD не установлен в .env")
# Пути для сканирования (можно настроить через переменные окружения)
scan_paths = os.getenv(
"NC_SCAN_PATHS",
"/home/{username}/Documents,/home/{username}/Files,/Shared/Documents"
).split(",")
nextcloud_config = NextcloudConfig(
url=nc_url,
username=nc_user,
password=nc_password,
scan_paths=[path.strip() for path in scan_paths]
)
# Open WebUI конфигурация
webui_url = os.getenv("DOMAIN_OPENWEBUI", "").rstrip("/")
if not webui_url:
raise ValueError("DOMAIN_OPENWEBUI не установлен в .env")
webui_api_key = os.getenv("OPENWEBUI_API_KEY")
if not webui_api_key or webui_api_key == "твой_api_ключ_от_openwebui":
raise ValueError(
"OPENWEBUI_API_KEY не установлен или имеет значение по умолчанию. "
"Создайте API ключ в Open WebUI -> Settings -> Account -> API Keys"
)
timeout = int(os.getenv("OPENWEBUI_TIMEOUT", "300"))
openwebui_config = OpenWebUIConfig(
api_url=f"{webui_url}/api/v1",
api_key=webui_api_key,
timeout=timeout
)
# Authentik конфигурация (опционально)
authentik_url = os.getenv("DOMAIN_AUTHENTIK", "").rstrip("/")
authentik_token = os.getenv("AUTHENTIK_API_TOKEN")
authentik_config = AuthentikConfig(
api_url=authentik_url if authentik_url else None,
api_token=authentik_token
)
# Общие настройки воркера
sync_interval = int(os.getenv("SYNC_INTERVAL", "300"))
max_file_size = int(os.getenv("MAX_FILE_SIZE", str(100 * 1024 * 1024)))
db_path = os.getenv("DB_PATH", "sync_state.db")
log_level = os.getenv("LOG_LEVEL", "INFO")
return WorkerConfig(
nextcloud=nextcloud_config,
openwebui=openwebui_config,
authentik=authentik_config,
sync_interval=sync_interval,
max_file_size=max_file_size,
db_path=db_path,
log_level=log_level
)

View File

@@ -0,0 +1,243 @@
"""
Обработка документов различных форматов
Поддержка PDF, DOCX, текстовых файлов с обработкой больших файлов (>100MB)
"""
import logging
import io
from typing import Optional, Tuple
from pathlib import Path
logger = logging.getLogger(__name__)
class DocumentProcessor:
"""Обработчик документов различных форматов"""
def __init__(self, max_file_size: int = 100 * 1024 * 1024):
"""
Инициализация процессора документов
Args:
max_file_size: Максимальный размер файла для прямой обработки (байты)
"""
self.max_file_size = max_file_size
def process_file(
self,
file_content: bytes,
filename: str,
mime_type: Optional[str] = None
) -> Tuple[str, bool]:
"""
Обработка файла и извлечение текста
Args:
file_content: Содержимое файла
filename: Имя файла (для определения типа)
mime_type: MIME тип файла (опционально)
Returns:
Кортеж (текст, is_large_file) где is_large_file указывает,
что файл был обработан потоково из-за большого размера
"""
file_size = len(file_content)
is_large_file = file_size > self.max_file_size
# Определение типа файла
file_ext = Path(filename).suffix.lower()
try:
if file_ext == '.pdf' or mime_type == 'application/pdf':
return self._process_pdf(file_content, is_large_file), is_large_file
elif file_ext in ['.docx', '.doc'] or mime_type in ['application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/msword']:
return self._process_docx(file_content), False
elif file_ext in ['.txt', '.md', '.markdown'] or mime_type in ['text/plain', 'text/markdown']:
return self._process_text(file_content), False
elif file_ext == '.csv' or mime_type == 'text/csv':
return self._process_csv(file_content), False
else:
logger.warning(f"Неподдерживаемый формат файла: {filename} (тип: {mime_type})")
# Пытаемся обработать как текст
try:
return self._process_text(file_content), False
except Exception:
raise ValueError(f"Не удалось обработать файл {filename}: неподдерживаемый формат")
except Exception as e:
logger.error(f"Ошибка при обработке файла {filename}: {e}")
raise
def _process_pdf(self, content: bytes, is_large: bool) -> str:
"""
Обработка PDF файла
Args:
content: Содержимое PDF
is_large: Флаг большого файла (для потоковой обработки)
Returns:
Извлеченный текст
"""
try:
import pypdf
pdf_file = io.BytesIO(content)
pdf_reader = pypdf.PdfReader(pdf_file)
text_parts = []
total_pages = len(pdf_reader.pages)
logger.info(f"Обработка PDF: {total_pages} страниц")
# Для больших файлов обрабатываем страницы порциями
if is_large:
# Ограничиваем количество страниц для очень больших файлов
max_pages = min(total_pages, 1000) # Максимум 1000 страниц
logger.warning(f"Большой PDF файл. Обрабатываются первые {max_pages} из {total_pages} страниц")
else:
max_pages = total_pages
for page_num in range(max_pages):
try:
page = pdf_reader.pages[page_num]
text = page.extract_text()
if text.strip():
text_parts.append(f"--- Страница {page_num + 1} ---\n{text}")
except Exception as e:
logger.warning(f"Ошибка при обработке страницы {page_num + 1}: {e}")
continue
result = "\n\n".join(text_parts)
if not result.strip():
raise ValueError("Не удалось извлечь текст из PDF")
return result
except ImportError:
raise ImportError(
"Библиотека pypdf не установлена. Установите: pip install pypdf"
)
except Exception as e:
logger.error(f"Ошибка при обработке PDF: {e}")
raise
def _process_docx(self, content: bytes) -> str:
"""
Обработка DOCX файла
Args:
content: Содержимое DOCX
Returns:
Извлеченный текст
"""
try:
import docx
doc_file = io.BytesIO(content)
doc = docx.Document(doc_file)
text_parts = []
# Извлечение текста из параграфов
for paragraph in doc.paragraphs:
if paragraph.text.strip():
text_parts.append(paragraph.text)
# Извлечение текста из таблиц
for table in doc.tables:
for row in table.rows:
row_text = " | ".join(cell.text.strip() for cell in row.cells)
if row_text.strip():
text_parts.append(row_text)
result = "\n\n".join(text_parts)
if not result.strip():
raise ValueError("Не удалось извлечь текст из DOCX")
return result
except ImportError:
raise ImportError(
"Библиотека python-docx не установлена. Установите: pip install python-docx"
)
except Exception as e:
logger.error(f"Ошибка при обработке DOCX: {e}")
raise
def _process_text(self, content: bytes) -> str:
"""
Обработка текстового файла
Args:
content: Содержимое файла
Returns:
Текст с правильной кодировкой
"""
# Попытка различных кодировок
encodings = ['utf-8', 'utf-8-sig', 'cp1251', 'latin-1']
for encoding in encodings:
try:
return content.decode(encoding)
except UnicodeDecodeError:
continue
# Если ничего не помогло, используем errors='replace'
return content.decode('utf-8', errors='replace')
def _process_csv(self, content: bytes) -> str:
"""
Обработка CSV файла (конвертация в читаемый текст)
Args:
content: Содержимое CSV
Returns:
Текстовая версия CSV
"""
import csv
text_content = self._process_text(content)
csv_reader = csv.reader(io.StringIO(text_content))
rows = []
for row_num, row in enumerate(csv_reader, 1):
rows.append(f"Строка {row_num}: {' | '.join(row)}")
return "\n".join(rows)
def is_supported_format(self, filename: str, mime_type: Optional[str] = None) -> bool:
"""
Проверка поддержки формата файла
Args:
filename: Имя файла
mime_type: MIME тип
Returns:
True если формат поддерживается
"""
file_ext = Path(filename).suffix.lower()
supported_extensions = {'.pdf', '.docx', '.doc', '.txt', '.md', '.markdown', '.csv'}
if file_ext in supported_extensions:
return True
supported_mimes = {
'application/pdf',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/msword',
'text/plain',
'text/markdown',
'text/csv'
}
return mime_type in supported_mimes if mime_type else False

249
worker/nextcloud_client.py Normal file
View File

@@ -0,0 +1,249 @@
"""
WebDAV клиент для работы с Nextcloud
Сканирование директорий, загрузка файлов, получение метаданных
"""
import os
import logging
from datetime import datetime
from typing import List, Dict, Optional, Tuple
from pathlib import Path
import requests
from requests.auth import HTTPBasicAuth
from requests.exceptions import RequestException, Timeout
logger = logging.getLogger(__name__)
class NextcloudClient:
"""Клиент для работы с Nextcloud через WebDAV API"""
def __init__(self, url: str, username: str, password: str):
"""
Инициализация WebDAV клиента
Args:
url: URL Nextcloud (например, https://next.iieasy.ru)
username: Имя пользователя Nextcloud
password: App Password (не основной пароль!)
"""
self.base_url = url.rstrip("/")
self.webdav_url = f"{self.base_url}/remote.php/dav/files/{username}"
self.auth = HTTPBasicAuth(username, password)
self.session = requests.Session()
self.session.auth = self.auth
self.session.headers.update({
"User-Agent": "iiEasy-Nextcloud-Sync/1.0"
})
def list_directory(self, path: str, depth: int = 1) -> List[Dict]:
"""
Получение списка файлов и директорий через PROPFIND
Args:
path: Путь к директории (относительно WebDAV root)
depth: Глубина рекурсии (0 - только указанный ресурс, 1 - + дочерние)
Returns:
Список словарей с информацией о файлах/директориях
Raises:
RequestException: При ошибках сетевого запроса
"""
url = f"{self.webdav_url}/{path.lstrip('/')}"
try:
# PROPFIND запрос для получения списка ресурсов
headers = {
"Depth": str(depth),
"Content-Type": "application/xml"
}
body = """<?xml version="1.0"?>
<d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns" xmlns:nc="http://nextcloud.org/ns">
<d:prop>
<d:getlastmodified/>
<d:getcontentlength/>
<d:getcontenttype/>
<oc:fileid/>
<nc:has-preview/>
</d:prop>
</d:propfind>"""
response = self.session.request(
"PROPFIND",
url,
headers=headers,
data=body,
timeout=30
)
response.raise_for_status()
# Парсинг XML ответа (упрощенная версия)
# В production лучше использовать xml.etree.ElementTree
files = []
# Здесь должен быть парсинг XML, но для простоты используем альтернативный метод
return self._parse_propfind_response(response.text)
except Timeout:
logger.error(f"Таймаут при получении списка файлов: {path}")
raise
except RequestException as e:
logger.error(f"Ошибка при получении списка файлов {path}: {e}")
raise
def _parse_propfind_response(self, xml_content: str) -> List[Dict]:
"""
Упрощенный парсинг PROPFIND ответа
В production лучше использовать xml.etree.ElementTree или lxml
"""
import re
files = []
# Простой regex парсинг (для production использовать XML парсер)
# Ищем href и getlastmodified
href_pattern = r'<d:href>(.*?)</d:href>'
modified_pattern = r'<d:getlastmodified>(.*?)</d:getlastmodified>'
size_pattern = r'<d:getcontentlength>(.*?)</d:getcontentlength>'
type_pattern = r'<d:getcontenttype>(.*?)</d:getcontenttype>'
hrefs = re.findall(href_pattern, xml_content)
modifieds = re.findall(modified_pattern, xml_content)
sizes = re.findall(size_pattern, xml_content)
types = re.findall(type_pattern, xml_content)
for i, href in enumerate(hrefs):
# Убираем префикс /remote.php/dav/files/username
clean_href = href.replace(f"/remote.php/dav/files/{self.auth.username}", "")
if clean_href == "":
continue
files.append({
"path": clean_href,
"modified": modifieds[i] if i < len(modifieds) else None,
"size": int(sizes[i]) if i < len(sizes) and sizes[i] else 0,
"type": types[i] if i < len(types) else "application/octet-stream",
"is_directory": i < len(types) and types[i] == "httpd/unix-directory"
})
return files
def download_file(self, path: str) -> bytes:
"""
Загрузка файла из Nextcloud
Args:
path: Путь к файлу (относительно WebDAV root)
Returns:
Содержимое файла в виде bytes
Raises:
RequestException: При ошибках загрузки
"""
url = f"{self.webdav_url}/{path.lstrip('/')}"
try:
response = self.session.get(url, timeout=300, stream=True)
response.raise_for_status()
return response.content
except Timeout:
logger.error(f"Таймаут при загрузке файла: {path}")
raise
except RequestException as e:
logger.error(f"Ошибка при загрузке файла {path}: {e}")
raise
def get_file_metadata(self, path: str) -> Dict:
"""
Получение метаданных файла (размер, дата изменения)
Args:
path: Путь к файлу
Returns:
Словарь с метаданными
"""
try:
url = f"{self.webdav_url}/{path.lstrip('/')}"
response = self.session.head(url, timeout=30)
response.raise_for_status()
return {
"size": int(response.headers.get("Content-Length", 0)),
"modified": response.headers.get("Last-Modified"),
"etag": response.headers.get("ETag", "").strip('"')
}
except RequestException as e:
logger.warning(f"Не удалось получить метаданные для {path}: {e}")
return {
"size": 0,
"modified": None,
"etag": ""
}
def scan_directory_recursive(self, base_path: str, max_depth: int = 10) -> List[Dict]:
"""
Рекурсивное сканирование директории
Args:
base_path: Базовый путь для сканирования
max_depth: Максимальная глубина рекурсии
Returns:
Список всех файлов с их метаданными
"""
all_files = []
def scan_recursive(current_path: str, depth: int = 0):
if depth > max_depth:
return
try:
items = self.list_directory(current_path, depth=1)
for item in items:
item_path = item["path"]
# Пропускаем сам каталог
if item_path == current_path:
continue
if item.get("is_directory"):
# Рекурсивный обход поддиректорий
scan_recursive(item_path, depth + 1)
else:
# Добавляем файл в список
all_files.append({
"path": item_path,
"size": item.get("size", 0),
"modified": item.get("modified"),
"type": item.get("type", "application/octet-stream")
})
except Exception as e:
logger.error(f"Ошибка при сканировании {current_path}: {e}")
scan_recursive(base_path)
return all_files
def extract_username_from_path(self, path: str) -> Optional[str]:
"""
Извлечение имени пользователя из пути Nextcloud
Например: /home/username/Documents -> username
Args:
path: Путь к файлу/директории
Returns:
Имя пользователя или None
"""
# Формат путей: /home/{username}/...
parts = path.strip("/").split("/")
if len(parts) >= 2 and parts[0] == "home":
return parts[1]
return None
def close(self):
"""Закрытие сессии"""
self.session.close()

448
worker/nextcloud_sync.py Executable file
View File

@@ -0,0 +1,448 @@
#!/usr/bin/env python3
"""
Воркер синхронизации Nextcloud -> Qdrant через Open WebUI API
Background процесс для автоматической синхронизации документов
"""
import sys
import time
import logging
import sqlite3
import hashlib
from pathlib import Path
from typing import Dict, Optional, List
from datetime import datetime
# Импорт локальных модулей
from config import load_config, WorkerConfig
from nextcloud_client import NextcloudClient
from openwebui_client import OpenWebUIClient
from document_processor import DocumentProcessor
# Настройка логирования
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('sync.log'),
logging.StreamHandler(sys.stdout)
]
)
logger = logging.getLogger(__name__)
class SyncStateDB:
"""Управление состоянием синхронизации через SQLite"""
def __init__(self, db_path: str):
"""
Инициализация БД состояния
Args:
db_path: Путь к файлу БД
"""
self.db_path = db_path
self._init_db()
def _init_db(self):
"""Инициализация схемы БД"""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS synced_files (
file_path TEXT PRIMARY KEY,
file_hash TEXT NOT NULL,
file_size INTEGER NOT NULL,
last_modified TEXT,
synced_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
user_id TEXT,
document_id TEXT
)
""")
cursor.execute("""
CREATE INDEX IF NOT EXISTS idx_file_hash ON synced_files(file_hash)
""")
conn.commit()
conn.close()
def get_file_hash(self, file_path: str, file_content: bytes) -> str:
"""Вычисление хеша файла"""
return hashlib.sha256(file_content).hexdigest()
def is_file_synced(self, file_path: str, file_hash: str) -> bool:
"""
Проверка, синхронизирован ли файл
Args:
file_path: Путь к файлу
file_hash: Хеш содержимого файла
Returns:
True если файл уже синхронизирован с таким хешем
"""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("""
SELECT file_hash FROM synced_files
WHERE file_path = ? AND file_hash = ?
""", (file_path, file_hash))
result = cursor.fetchone()
conn.close()
return result is not None
def mark_file_synced(
self,
file_path: str,
file_hash: str,
file_size: int,
last_modified: Optional[str],
user_id: Optional[str],
document_id: Optional[str]
):
"""Отметка файла как синхронизированного"""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("""
INSERT OR REPLACE INTO synced_files
(file_path, file_hash, file_size, last_modified, synced_at, user_id, document_id)
VALUES (?, ?, ?, ?, ?, ?, ?)
""", (
file_path,
file_hash,
file_size,
last_modified,
datetime.now().isoformat(),
user_id,
document_id
))
conn.commit()
conn.close()
def get_sync_stats(self) -> Dict:
"""Получение статистики синхронизации"""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("SELECT COUNT(*) FROM synced_files")
total_files = cursor.fetchone()[0]
cursor.execute("SELECT SUM(file_size) FROM synced_files")
total_size = cursor.fetchone()[0] or 0
conn.close()
return {
"total_files": total_files,
"total_size": total_size
}
class UserMapper:
"""Маппинг пользователей Nextcloud -> Authentik"""
def __init__(self, config: WorkerConfig):
"""
Инициализация маппера пользователей
Args:
config: Конфигурация воркера
"""
self.config = config
# Кеш маппинга (в production можно использовать БД или Authentik API)
self._cache: Dict[str, str] = {}
def map_nextcloud_user_to_authentik(self, nc_username: str) -> Optional[str]:
"""
Маппинг имени пользователя Nextcloud в user_id Authentik
Args:
nc_username: Имя пользователя в Nextcloud
Returns:
user_id Authentik или None если маппинг не найден
"""
# Проверка кеша
if nc_username in self._cache:
return self._cache[nc_username]
# В production здесь должен быть запрос к Authentik API
# или использование общей БД пользователей
# Пока используем простое предположение: username совпадает
# или можно настроить через переменные окружения
# Попытка получить из переменных окружения (формат: NC_USER_username=AUTHENTIK_USER_ID)
env_key = f"NC_USER_{nc_username}"
authentik_user_id = None
import os
if env_key in os.environ:
authentik_user_id = os.environ[env_key]
else:
# По умолчанию предполагаем, что username совпадает
# В production это должно быть через Authentik API
authentik_user_id = nc_username
self._cache[nc_username] = authentik_user_id
return authentik_user_id
def get_user_groups(self, nc_username: str) -> List[str]:
"""
Получение списка групп доступа для пользователя
Args:
nc_username: Имя пользователя Nextcloud
Returns:
Список групп доступа
"""
# В production получать из Authentik API или Nextcloud групп
# Пока возвращаем пустой список (только личные файлы)
return []
class NextcloudSyncWorker:
"""Основной класс воркера синхронизации"""
def __init__(self, config: WorkerConfig):
"""
Инициализация воркера
Args:
config: Конфигурация воркера
"""
self.config = config
self.nc_client = NextcloudClient(
config.nextcloud.url,
config.nextcloud.username,
config.nextcloud.password
)
self.webui_client = OpenWebUIClient(
config.openwebui.api_url,
config.openwebui.api_key,
config.openwebui.timeout
)
self.processor = DocumentProcessor(config.max_file_size)
self.state_db = SyncStateDB(config.db_path)
self.user_mapper = UserMapper(config)
def sync_path(self, path_template: str) -> int:
"""
Синхронизация указанного пути
Args:
path_template: Шаблон пути (может содержать {username})
Returns:
Количество синхронизированных файлов
"""
synced_count = 0
# Замена {username} в шаблоне пути
if "{username}" in path_template:
# Сканируем все домашние директории
# В production можно получить список пользователей из Nextcloud API
username = self.config.nextcloud.username
path = path_template.replace("{username}", username)
else:
path = path_template
try:
logger.info(f"Сканирование пути: {path}")
files = self.nc_client.scan_directory_recursive(path)
logger.info(f"Найдено файлов: {len(files)}")
for file_info in files:
try:
if self._sync_file(file_info, path_template):
synced_count += 1
except Exception as e:
logger.error(f"Ошибка при синхронизации файла {file_info['path']}: {e}")
continue
except Exception as e:
logger.error(f"Ошибка при сканировании пути {path}: {e}")
return synced_count
def _sync_file(self, file_info: Dict, path_template: str) -> bool:
"""
Синхронизация одного файла
Args:
file_info: Информация о файле
path_template: Шаблон пути (для определения владельца)
Returns:
True если файл был синхронизирован
"""
file_path = file_info["path"]
file_size = file_info.get("size", 0)
# Пропускаем слишком большие файлы
if file_size > self.config.max_file_size * 2: # Двойной лимит для безопасности
logger.warning(f"Файл {file_path} слишком большой ({file_size} bytes), пропуск")
return False
# Проверка поддержки формата
if not self.processor.is_supported_format(file_path, file_info.get("type")):
logger.debug(f"Неподдерживаемый формат: {file_path}")
return False
try:
# Загрузка файла
file_content = self.nc_client.download_file(file_path)
file_hash = self.state_db.get_file_hash(file_path, file_content)
# Проверка, не синхронизирован ли уже файл
if self.state_db.is_file_synced(file_path, file_hash):
logger.debug(f"Файл уже синхронизирован: {file_path}")
return False
# Обработка файла
text_content, is_large = self.processor.process_file(
file_content,
Path(file_path).name,
file_info.get("type")
)
# Определение владельца файла
username = self.nc_client.extract_username_from_path(file_path)
if not username:
# Для общих папок используем None или специальную группу
username = None
user_id = self.user_mapper.map_nextcloud_user_to_authentik(username) if username else None
access_groups = self.user_mapper.get_user_groups(username) if username else []
# Метаданные для API
metadata = {
"source": "nextcloud",
"path": file_path,
"original_size": file_size,
"is_large_file": is_large
}
# Загрузка в Open WebUI
if is_large or len(text_content.encode('utf-8')) > self.config.max_file_size:
# Для больших файлов загружаем как текст
result = self.webui_client.upload_document_with_text(
text_content=text_content,
filename=Path(file_path).name,
user_id=user_id,
access_groups=access_groups,
metadata=metadata
)
else:
# Для обычных файлов загружаем оригинал
result = self.webui_client.upload_document(
file_content=file_content,
filename=Path(file_path).name,
user_id=user_id,
access_groups=access_groups,
metadata=metadata
)
# Сохранение состояния
document_id = result.get("id") if isinstance(result, dict) else None
self.state_db.mark_file_synced(
file_path=file_path,
file_hash=file_hash,
file_size=file_size,
last_modified=file_info.get("modified"),
user_id=user_id,
document_id=document_id
)
logger.info(f"Файл синхронизирован: {file_path} -> документ ID: {document_id}")
return True
except Exception as e:
logger.error(f"Ошибка при синхронизации файла {file_path}: {e}")
return False
def run_once(self):
"""Однократный запуск синхронизации"""
logger.info("=== Начало синхронизации ===")
total_synced = 0
for path_template in self.config.nextcloud.scan_paths:
try:
synced = self.sync_path(path_template)
total_synced += synced
logger.info(f"Синхронизировано файлов из {path_template}: {synced}")
except Exception as e:
logger.error(f"Ошибка при синхронизации пути {path_template}: {e}")
stats = self.state_db.get_sync_stats()
logger.info(f"=== Синхронизация завершена ===")
logger.info(f"Всего синхронизировано в этом цикле: {total_synced}")
logger.info(f"Всего файлов в БД: {stats['total_files']}")
logger.info(f"Общий размер: {stats['total_size'] / 1024 / 1024:.2f} MB")
def run_daemon(self):
"""Запуск воркера в режиме daemon (бесконечный цикл)"""
logger.info("Запуск воркера в режиме daemon")
logger.info(f"Интервал синхронизации: {self.config.sync_interval} секунд")
try:
while True:
self.run_once()
logger.info(f"Ожидание {self.config.sync_interval} секунд до следующей синхронизации...")
time.sleep(self.config.sync_interval)
except KeyboardInterrupt:
logger.info("Получен сигнал остановки, завершение работы...")
finally:
self.cleanup()
def cleanup(self):
"""Очистка ресурсов"""
logger.info("Очистка ресурсов...")
self.nc_client.close()
self.webui_client.close()
def main():
"""Точка входа"""
import argparse
parser = argparse.ArgumentParser(description="Воркер синхронизации Nextcloud -> Qdrant")
parser.add_argument(
"--once",
action="store_true",
help="Запустить синхронизацию один раз и выйти"
)
parser.add_argument(
"--daemon",
action="store_true",
help="Запустить в режиме daemon (бесконечный цикл)"
)
args = parser.parse_args()
try:
config = load_config()
worker = NextcloudSyncWorker(config)
if args.once:
worker.run_once()
elif args.daemon:
worker.run_daemon()
else:
# По умолчанию запускаем один раз
worker.run_once()
except Exception as e:
logger.error(f"Критическая ошибка: {e}", exc_info=True)
sys.exit(1)
if __name__ == "__main__":
main()

179
worker/openwebui_client.py Normal file
View File

@@ -0,0 +1,179 @@
"""
HTTP клиент для загрузки документов в Open WebUI API
Поддержка метаданных user_id и access_groups для изоляции доступа
"""
import logging
from typing import Dict, Optional, List
import requests
from requests.exceptions import RequestException, Timeout
logger = logging.getLogger(__name__)
class OpenWebUIClient:
"""Клиент для работы с Open WebUI API"""
def __init__(self, api_url: str, api_key: str, timeout: int = 300):
"""
Инициализация клиента Open WebUI API
Args:
api_url: Базовый URL API (например, https://odo.iieasy.ru/api/v1)
api_key: API ключ из Open WebUI Settings -> Account -> API Keys
timeout: Таймаут для запросов (секунды)
"""
self.api_url = api_url.rstrip("/")
self.api_key = api_key
self.timeout = timeout
self.session = requests.Session()
self.session.headers.update({
"Authorization": f"Bearer {api_key}",
"User-Agent": "iiEasy-Nextcloud-Sync/1.0"
})
def upload_document(
self,
file_content: bytes,
filename: str,
user_id: Optional[str] = None,
access_groups: Optional[List[str]] = None,
metadata: Optional[Dict] = None
) -> Dict:
"""
Загрузка документа в Open WebUI через API
Args:
file_content: Содержимое файла в виде bytes
filename: Имя файла
user_id: ID пользователя Authentik для изоляции доступа
access_groups: Список групп доступа (для совместного доступа)
metadata: Дополнительные метаданные (source, path и т.д.)
Returns:
Ответ API в виде словаря
Raises:
RequestException: При ошибках загрузки
"""
url = f"{self.api_url}/documents"
# Подготовка данных для multipart/form-data
files = {
"file": (filename, file_content)
}
# Метаданные передаются через form-data или JSON
data = {}
if user_id:
data["user_id"] = user_id
if access_groups:
# Если API поддерживает список групп, передаем как JSON строку или отдельные поля
data["access_groups"] = ",".join(access_groups) if isinstance(access_groups, list) else access_groups
if metadata:
# Добавляем дополнительные метаданные
for key, value in metadata.items():
if key not in data:
data[key] = str(value)
try:
logger.info(f"Загрузка документа {filename} (размер: {len(file_content)} bytes)")
response = self.session.post(
url,
files=files,
data=data,
timeout=self.timeout
)
response.raise_for_status()
result = response.json() if response.content else {}
logger.info(f"Документ {filename} успешно загружен. ID: {result.get('id', 'unknown')}")
return result
except Timeout:
logger.error(f"Таймаут при загрузке документа {filename}")
raise
except RequestException as e:
logger.error(f"Ошибка при загрузке документа {filename}: {e}")
if hasattr(e.response, 'text'):
logger.error(f"Ответ сервера: {e.response.text}")
raise
def upload_document_with_text(
self,
text_content: str,
filename: str,
user_id: Optional[str] = None,
access_groups: Optional[List[str]] = None,
metadata: Optional[Dict] = None
) -> Dict:
"""
Загрузка текстового документа (например, извлеченного из PDF)
Args:
text_content: Текст документа
filename: Имя файла (будет изменено на .txt если нужно)
user_id: ID пользователя Authentik
access_groups: Список групп доступа
metadata: Дополнительные метаданные
Returns:
Ответ API
"""
# Убеждаемся, что файл имеет расширение .txt
if not filename.endswith('.txt'):
filename = filename.rsplit('.', 1)[0] + '.txt'
file_content = text_content.encode('utf-8')
return self.upload_document(
file_content=file_content,
filename=filename,
user_id=user_id,
access_groups=access_groups,
metadata=metadata
)
def check_document_exists(self, document_id: str) -> bool:
"""
Проверка существования документа по ID
Args:
document_id: ID документа
Returns:
True если документ существует
"""
try:
url = f"{self.api_url}/documents/{document_id}"
response = self.session.get(url, timeout=30)
return response.status_code == 200
except RequestException:
return False
def delete_document(self, document_id: str) -> bool:
"""
Удаление документа по ID
Args:
document_id: ID документа
Returns:
True если удаление успешно
"""
try:
url = f"{self.api_url}/documents/{document_id}"
response = self.session.delete(url, timeout=30)
response.raise_for_status()
return True
except RequestException as e:
logger.error(f"Ошибка при удалении документа {document_id}: {e}")
return False
def close(self):
"""Закрытие сессии"""
self.session.close()

17
worker/requirements.txt Normal file
View File

@@ -0,0 +1,17 @@
# Зависимости для воркера синхронизации Nextcloud -> Qdrant
# HTTP клиенты
requests>=2.31.0
# Обработка переменных окружения
python-dotenv>=1.0.0
# Обработка PDF
pypdf>=3.17.0
# Обработка DOCX
python-docx>=1.1.0
# WebDAV клиент (альтернатива requests для WebDAV)
# Используем requests напрямую, но можно добавить:
# easywebdav>=1.2.0 # Опционально, если нужны специфичные WebDAV функции