commit 53c572ef46cf1605eb9021ca1d6625569ffb8580 Author: ars Date: Thu Feb 19 18:12:09 2026 +0000 Add project and deployment instruction (docs/DEPLOYMENT.md) Co-authored-by: Cursor diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..07ee8c4 --- /dev/null +++ b/.env.example @@ -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 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b97e50a --- /dev/null +++ b/.gitignore @@ -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 diff --git a/APPLY_AUTHENTIK_FIX.sh b/APPLY_AUTHENTIK_FIX.sh new file mode 100755 index 0000000..da0a7a4 --- /dev/null +++ b/APPLY_AUTHENTIK_FIX.sh @@ -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. Временно используйте форму входа (уже включена)" diff --git a/APPLY_OAUTH_FIX.sh b/APPLY_OAUTH_FIX.sh new file mode 100755 index 0000000..44162cf --- /dev/null +++ b/APPLY_OAUTH_FIX.sh @@ -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" diff --git a/AUTHENTIK_FIX.md b/AUTHENTIK_FIX.md new file mode 100644 index 0000000..1f1c88e --- /dev/null +++ b/AUTHENTIK_FIX.md @@ -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` diff --git a/AUTHENTIK_SETUP.md b/AUTHENTIK_SETUP.md new file mode 100644 index 0000000..89f8ecc --- /dev/null +++ b/AUTHENTIK_SETUP.md @@ -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 diff --git a/DIAGNOSE_VISION_ISSUE.md b/DIAGNOSE_VISION_ISSUE.md new file mode 100644 index 0000000..779c4f6 --- /dev/null +++ b/DIAGNOSE_VISION_ISSUE.md @@ -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. diff --git a/FIX_DOCUMENTATION_LINKS.md b/FIX_DOCUMENTATION_LINKS.md new file mode 100644 index 0000000..8b9c68e --- /dev/null +++ b/FIX_DOCUMENTATION_LINKS.md @@ -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 diff --git a/FIX_IMAGE_TRANSFER.md b/FIX_IMAGE_TRANSFER.md new file mode 100644 index 0000000..94b2d13 --- /dev/null +++ b/FIX_IMAGE_TRANSFER.md @@ -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 моделями diff --git a/FIX_LOGIN_TITLE.md b/FIX_LOGIN_TITLE.md new file mode 100644 index 0000000..c3a16c3 --- /dev/null +++ b/FIX_LOGIN_TITLE.md @@ -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 + +# Затем замените вручную в найденных файлах +``` diff --git a/FIX_OAUTH_REDIRECT.md b/FIX_OAUTH_REDIRECT.md new file mode 100644 index 0000000..4c39a42 --- /dev/null +++ b/FIX_OAUTH_REDIRECT.md @@ -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`). diff --git a/FIX_OAUTH_SLUG.md b/FIX_OAUTH_SLUG.md new file mode 100644 index 0000000..68c4887 --- /dev/null +++ b/FIX_OAUTH_SLUG.md @@ -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` diff --git a/FIX_OLLAMA_URL.md b/FIX_OLLAMA_URL.md new file mode 100644 index 0000000..dca3504 --- /dev/null +++ b/FIX_OLLAMA_URL.md @@ -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 +``` diff --git a/FIX_TRACE_ERROR.md b/FIX_TRACE_ERROR.md new file mode 100644 index 0000000..ebc6b58 --- /dev/null +++ b/FIX_TRACE_ERROR.md @@ -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` diff --git a/LOGO_FIX.md b/LOGO_FIX.md new file mode 100644 index 0000000..b5dcfea --- /dev/null +++ b/LOGO_FIX.md @@ -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)** - это единственный способ, который гарантирует, что логотипы не вернутся после перезапуска. diff --git a/LOGO_SETUP.md b/LOGO_SETUP.md new file mode 100644 index 0000000..eb4bbe7 --- /dev/null +++ b/LOGO_SETUP.md @@ -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, так как настройки сохраняются в базе данных. diff --git a/QUICK_FIX.md b/QUICK_FIX.md new file mode 100644 index 0000000..53a9c00 --- /dev/null +++ b/QUICK_FIX.md @@ -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 +``` diff --git a/QUICK_START.md b/QUICK_START.md new file mode 100644 index 0000000..ba63ddb --- /dev/null +++ b/QUICK_START.md @@ -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` diff --git a/README.md b/README.md new file mode 100644 index 0000000..9a296a6 --- /dev/null +++ b/README.md @@ -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. diff --git a/REBRANDING.md b/REBRANDING.md new file mode 100644 index 0000000..6f3bab9 --- /dev/null +++ b/REBRANDING.md @@ -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 + +Эти файлы будут использоваться для ребрендинга. diff --git a/REBRAND_FIX.md b/REBRAND_FIX.md new file mode 100644 index 0000000..db71862 --- /dev/null +++ b/REBRAND_FIX.md @@ -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 + ``` diff --git a/REBRAND_OAUTH_FIX.md b/REBRAND_OAUTH_FIX.md new file mode 100644 index 0000000..b6d954c --- /dev/null +++ b/REBRAND_OAUTH_FIX.md @@ -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" +``` diff --git a/REBRAND_SOLUTION.md b/REBRAND_SOLUTION.md new file mode 100644 index 0000000..fe19472 --- /dev/null +++ b/REBRAND_SOLUTION.md @@ -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 + +**Используйте этот скрипт для всех операций ребрендинга!** diff --git a/RESTORE_AFTER_500.md b/RESTORE_AFTER_500.md new file mode 100644 index 0000000..07aaebe --- /dev/null +++ b/RESTORE_AFTER_500.md @@ -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 файлы (чтобы не сломать код) + +Это гарантирует, что функциональность не будет нарушена. diff --git a/RESTORE_CONTAINER.md b/RESTORE_CONTAINER.md new file mode 100644 index 0000000..bc3ae92 --- /dev/null +++ b/RESTORE_CONTAINER.md @@ -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/` + +Это не сломает функциональность, так как изменения сохраняются в базе данных. diff --git a/SEARXNG_SETUP.md b/SEARXNG_SETUP.md new file mode 100644 index 0000000..5fb625b --- /dev/null +++ b/SEARXNG_SETUP.md @@ -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=&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=&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` + +--- + +**Решение протестировано и работает стабильно.** diff --git a/TEST_VISION.md b/TEST_VISION.md new file mode 100644 index 0000000..0ed8be6 --- /dev/null +++ b/TEST_VISION.md @@ -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` diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md new file mode 100644 index 0000000..76200f9 --- /dev/null +++ b/TROUBLESHOOTING.md @@ -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` diff --git a/VISION_MODELS.md b/VISION_MODELS.md new file mode 100644 index 0000000..7c61884 --- /dev/null +++ b/VISION_MODELS.md @@ -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. diff --git a/check_authentik.sh b/check_authentik.sh new file mode 100755 index 0000000..22238dd --- /dev/null +++ b/check_authentik.sh @@ -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" diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..f094cee --- /dev/null +++ b/docker-compose.yml @@ -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 '&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 diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md new file mode 100644 index 0000000..76aebba --- /dev/null +++ b/docs/DEPLOYMENT.md @@ -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`. diff --git a/docs/TTS_CMU_ARCTIC_SPEAKERS.md b/docs/TTS_CMU_ARCTIC_SPEAKERS.md new file mode 100644 index 0000000..41e07f4 --- /dev/null +++ b/docs/TTS_CMU_ARCTIC_SPEAKERS.md @@ -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`. diff --git a/instr b/instr new file mode 100644 index 0000000..1786628 --- /dev/null +++ b/instr @@ -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 \ No newline at end of file diff --git a/media/favicon.png b/media/favicon.png new file mode 100644 index 0000000..14caa7d Binary files /dev/null and b/media/favicon.png differ diff --git a/media/favicon.svg b/media/favicon.svg new file mode 100644 index 0000000..a7ddeec --- /dev/null +++ b/media/favicon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/media/init-logos.sh b/media/init-logos.sh new file mode 100755 index 0000000..5ca3553 --- /dev/null +++ b/media/init-logos.sh @@ -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 diff --git a/media/logo-dark.svg b/media/logo-dark.svg new file mode 100644 index 0000000..bfe5ed6 --- /dev/null +++ b/media/logo-dark.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/media/logo-light.svg b/media/logo-light.svg new file mode 100644 index 0000000..c99cb26 --- /dev/null +++ b/media/logo-light.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/media/logo.png b/media/logo.png new file mode 100644 index 0000000..14caa7d Binary files /dev/null and b/media/logo.png differ diff --git a/media/Лого светлый справа слоган.md b/media/Лого светлый справа слоган.md new file mode 100644 index 0000000..e0fec7e --- /dev/null +++ b/media/Лого светлый справа слоган.md @@ -0,0 +1,41 @@ +```html + + + + + + iiEasy Logo - Right Aligned with Styled Slogan + + + + +
+ + + + + + + + + + + +
+
iiEasy
+
Будущее. Просто.
+
+
+ + +``` \ No newline at end of file diff --git a/media/Логотип светлый svg.md b/media/Логотип светлый svg.md new file mode 100644 index 0000000..09b022d --- /dev/null +++ b/media/Логотип светлый svg.md @@ -0,0 +1,32 @@ +```html + + + + + + AI Logo - Variant 40 (Kinetic Rings - Compact) + + + + + + + + + + + + + + + + +
iiEasy
+ +
Будущее. Просто.
+ + +``` \ No newline at end of file diff --git a/media/Логотип светлый справа svg.md b/media/Логотип светлый справа svg.md new file mode 100644 index 0000000..ca046e7 --- /dev/null +++ b/media/Логотип светлый справа svg.md @@ -0,0 +1,32 @@ +```html + + + + + + AI Logo - Variant 40 (Kinetic Rings - Compact) - Light, Right Aligned Text + + + + + + + + + + + + + + + + + + +
iiEasy
+ + +``` \ No newline at end of file diff --git a/media/Логотип тёмный svg.md b/media/Логотип тёмный svg.md new file mode 100644 index 0000000..75949b9 --- /dev/null +++ b/media/Логотип тёмный svg.md @@ -0,0 +1,32 @@ +```html + + + + + + AI Logo - Variant 40 (Kinetic Rings - Compact) - Dark + + + + + + + + + + + + + + + + +
iiEasy
+ +
Будущее. Просто.
+ + +``` \ No newline at end of file diff --git a/media/Логотип тёмный справа avg.md b/media/Логотип тёмный справа avg.md new file mode 100644 index 0000000..50a3a27 --- /dev/null +++ b/media/Логотип тёмный справа avg.md @@ -0,0 +1,32 @@ +```html + + + + + + AI Logo - Variant 40 (Kinetic Rings - Compact) - Dark, Right Aligned Text + + + + + + + + + + + + + + + + + + +
iiEasy
+ + +``` \ No newline at end of file diff --git a/media/лого темный справа слоган.md b/media/лого темный справа слоган.md new file mode 100644 index 0000000..0406582 --- /dev/null +++ b/media/лого темный справа слоган.md @@ -0,0 +1,42 @@ +```html + + + + + + iiEasy Logo - Right Aligned Dark with Styled Slogan + + + + +
+ + + + + + + + + + + +
+
iiEasy
+ +
Будущее. Просто.
+
+
+ + +``` \ No newline at end of file diff --git a/media/логотип анимация .md b/media/логотип анимация .md new file mode 100644 index 0000000..907f140 --- /dev/null +++ b/media/логотип анимация .md @@ -0,0 +1,75 @@ +```html + + + + + + Анимированный логотип - iiEasy (Горизонтальная темная версия) + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + +

iiEasy

+ +
+ + + +``` \ No newline at end of file diff --git a/media/логотип анимация2.md b/media/логотип анимация2.md new file mode 100644 index 0000000..fd0499c --- /dev/null +++ b/media/логотип анимация2.md @@ -0,0 +1,78 @@ +```html + + + + + + Анимированный логотип - iiEasy (Темная версия) + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + +

iiEasy

+ + +

Будущее. Просто.

+ +
+ + + +``` \ No newline at end of file diff --git a/nginx-proxy-manager-config.md b/nginx-proxy-manager-config.md new file mode 100644 index 0000000..1e605f4 --- /dev/null +++ b/nginx-proxy-manager-config.md @@ -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 (но лучше оставить пустым) diff --git a/scripts/apply_logos_persistent.sh b/scripts/apply_logos_persistent.sh new file mode 100755 index 0000000..ca957dd --- /dev/null +++ b/scripts/apply_logos_persistent.sh @@ -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 "Или запустите этот скрипт после каждого перезапуска контейнера." diff --git a/scripts/check_gpu.sh b/scripts/check_gpu.sh new file mode 100755 index 0000000..9ac32b6 --- /dev/null +++ b/scripts/check_gpu.sh @@ -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 diff --git a/scripts/check_image_transfer.sh b/scripts/check_image_transfer.sh new file mode 100755 index 0000000..0bc77eb --- /dev/null +++ b/scripts/check_image_transfer.sh @@ -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 "=== Проверка завершена ===" diff --git a/scripts/check_searxng_json.sh b/scripts/check_searxng_json.sh new file mode 100755 index 0000000..212eef2 --- /dev/null +++ b/scripts/check_searxng_json.sh @@ -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 diff --git a/scripts/check_vision_models.sh b/scripts/check_vision_models.sh new file mode 100755 index 0000000..6d1d6bc --- /dev/null +++ b/scripts/check_vision_models.sh @@ -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 "=== Готово ===" diff --git a/scripts/diagnose_search.sh b/scripts/diagnose_search.sh new file mode 100755 index 0000000..52a3b06 --- /dev/null +++ b/scripts/diagnose_search.sh @@ -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=&format=json" +echo "" diff --git a/scripts/find_model_image_api.sh b/scripts/find_model_image_api.sh new file mode 100755 index 0000000..72aa533 --- /dev/null +++ b/scripts/find_model_image_api.sh @@ -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 "=== Поиск завершен ===" diff --git a/scripts/find_openwebui_text.sh b/scripts/find_openwebui_text.sh new file mode 100755 index 0000000..a5efb56 --- /dev/null +++ b/scripts/find_openwebui_text.sh @@ -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 "=== Поиск завершен ===" diff --git a/scripts/find_settings_elements.sh b/scripts/find_settings_elements.sh new file mode 100755 index 0000000..635f9d5 --- /dev/null +++ b/scripts/find_settings_elements.sh @@ -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 "=== Поиск завершен ===" diff --git a/scripts/fix_openwebui.sh b/scripts/fix_openwebui.sh new file mode 100755 index 0000000..5ac51b0 --- /dev/null +++ b/scripts/fix_openwebui.sh @@ -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" diff --git a/scripts/fix_search_complete.sh b/scripts/fix_search_complete.sh new file mode 100755 index 0000000..54fd305 --- /dev/null +++ b/scripts/fix_search_complete.sh @@ -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=&format=json" +echo "3. Попробуйте поиск в чате" diff --git a/scripts/fix_searxng_config.sh b/scripts/fix_searxng_config.sh new file mode 100755 index 0000000..6b194f1 --- /dev/null +++ b/scripts/fix_searxng_config.sh @@ -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." diff --git a/scripts/fix_searxng_json.sh b/scripts/fix_searxng_json.sh new file mode 100755 index 0000000..8ccad75 --- /dev/null +++ b/scripts/fix_searxng_json.sh @@ -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..." diff --git a/scripts/fix_trace_error.sh b/scripts/fix_trace_error.sh new file mode 100755 index 0000000..bf5cb25 --- /dev/null +++ b/scripts/fix_trace_error.sh @@ -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" diff --git a/scripts/fix_user_agent.sh b/scripts/fix_user_agent.sh new file mode 100755 index 0000000..4d0ab86 --- /dev/null +++ b/scripts/fix_user_agent.sh @@ -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..." diff --git a/scripts/fix_user_agent_aggressive.sh b/scripts/fix_user_agent_aggressive.sh new file mode 100755 index 0000000..24df752 --- /dev/null +++ b/scripts/fix_user_agent_aggressive.sh @@ -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 секунд и проверьте поиск." diff --git a/scripts/fix_user_agent_final.sh b/scripts/fix_user_agent_final.sh new file mode 100755 index 0000000..af619d0 --- /dev/null +++ b/scripts/fix_user_agent_final.sh @@ -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 секунд и проверьте поиск." diff --git a/scripts/init-logos.sh b/scripts/init-logos.sh new file mode 100644 index 0000000..5ca3553 --- /dev/null +++ b/scripts/init-logos.sh @@ -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 diff --git a/scripts/init.sh b/scripts/init.sh new file mode 100755 index 0000000..8bc87fa --- /dev/null +++ b/scripts/init.sh @@ -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" diff --git a/scripts/install_vision_model.sh b/scripts/install_vision_model.sh new file mode 100755 index 0000000..d38cd92 --- /dev/null +++ b/scripts/install_vision_model.sh @@ -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 "И попробуйте загрузить изображение и задать вопрос о нем." diff --git a/scripts/rebrand.sh b/scripts/rebrand.sh new file mode 100755 index 0000000..d2a0490 --- /dev/null +++ b/scripts/rebrand.sh @@ -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 "Примечание: Если изменения не отображаются, очистите кеш браузера." diff --git a/scripts/rebrand_careful.sh b/scripts/rebrand_careful.sh new file mode 100755 index 0000000..430a65c --- /dev/null +++ b/scripts/rebrand_careful.sh @@ -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 WebUIiiEasyWeb/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" diff --git a/scripts/rebrand_complete.sh b/scripts/rebrand_complete.sh new file mode 100755 index 0000000..42ffa77 --- /dev/null +++ b/scripts/rebrand_complete.sh @@ -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" diff --git a/scripts/rebrand_fast.sh b/scripts/rebrand_fast.sh new file mode 100755 index 0000000..bc3cfb1 --- /dev/null +++ b/scripts/rebrand_fast.sh @@ -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)" diff --git a/scripts/rebrand_final.sh b/scripts/rebrand_final.sh new file mode 100755 index 0000000..8ad70af --- /dev/null +++ b/scripts/rebrand_final.sh @@ -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" diff --git a/scripts/rebrand_full.sh b/scripts/rebrand_full.sh new file mode 100755 index 0000000..b9d3cab --- /dev/null +++ b/scripts/rebrand_full.sh @@ -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" diff --git a/scripts/rebrand_precise.sh b/scripts/rebrand_precise.sh new file mode 100755 index 0000000..69b81ba --- /dev/null +++ b/scripts/rebrand_precise.sh @@ -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)" diff --git a/scripts/rebrand_safe.sh b/scripts/rebrand_safe.sh new file mode 100755 index 0000000..4877a0b --- /dev/null +++ b/scripts/rebrand_safe.sh @@ -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" diff --git a/scripts/rebrand_safe_final.sh b/scripts/rebrand_safe_final.sh new file mode 100755 index 0000000..9babbab --- /dev/null +++ b/scripts/rebrand_safe_final.sh @@ -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|]*href="https://github.com/open-webui/open-webui/releases/tag/[^"]*"[^>]*>(последняя)||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|]*href="https://discord.gg/[^"]*"[^>]*>.*||g' "$file" 2>/dev/null || true + # Удаляем ссылки на Twitter/X + docker exec "${CONTAINER_NAME}" sed -i 's|]*href="https://twitter.com/[^"]*"[^>]*>.*||g' "$file" 2>/dev/null || true + # Удаляем ссылки на GitHub repo + docker exec "${CONTAINER_NAME}" sed -i 's|]*href="https://github.com/open-webui/open-webui"[^>]*>.*||g' "$file" 2>/dev/null || true + # Удаляем badges (img.shields.io) + docker exec "${CONTAINER_NAME}" sed -i 's|]*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 "
" + echo " Основано на Open WebUI" + echo "
" +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" diff --git a/scripts/rebrand_ultra_safe.sh b/scripts/rebrand_ultra_safe.sh new file mode 100755 index 0000000..15e2ea7 --- /dev/null +++ b/scripts/rebrand_ultra_safe.sh @@ -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|]*github.com/open-webui/releases[^>]*>.*последняя.*||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|]*discord.gg[^>]*>.*||g' "$file" 2>/dev/null || true + docker exec "${CONTAINER_NAME}" sed -i 's|]*twitter.com[^>]*>.*||g' "$file" 2>/dev/null || true + docker exec "${CONTAINER_NAME}" sed -i 's|]*github.com/open-webui[^>]*>.*||g' "$file" 2>/dev/null || true + docker exec "${CONTAINER_NAME}" sed -i 's|]*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 файлы и удалить блоки там." diff --git a/scripts/remove_footer_links.sh b/scripts/remove_footer_links.sh new file mode 100755 index 0000000..49c1d96 --- /dev/null +++ b/scripts/remove_footer_links.sh @@ -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)" diff --git a/scripts/test_direct_vision.sh b/scripts/test_direct_vision.sh new file mode 100755 index 0000000..05cae3e --- /dev/null +++ b/scripts/test_direct_vision.sh @@ -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 diff --git a/scripts/test_vision.sh b/scripts/test_vision.sh new file mode 100755 index 0000000..83c75c0 --- /dev/null +++ b/scripts/test_vision.sh @@ -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" </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" diff --git a/scripts/update.sh b/scripts/update.sh new file mode 100755 index 0000000..b4aa706 --- /dev/null +++ b/scripts/update.sh @@ -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" diff --git a/searxng/settings.yml b/searxng/settings.yml new file mode 100644 index 0000000..2937d39 --- /dev/null +++ b/searxng/settings.yml @@ -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 diff --git a/ssl-setup-guide.md b/ssl-setup-guide.md new file mode 100644 index 0000000..450f620 --- /dev/null +++ b/ssl-setup-guide.md @@ -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. diff --git a/test.jpg b/test.jpg new file mode 100644 index 0000000..501b0eb --- /dev/null +++ b/test.jpg @@ -0,0 +1 @@ +Geoblocked \ No newline at end of file diff --git a/test_images/test_image.jpg b/test_images/test_image.jpg new file mode 100644 index 0000000..da7630e Binary files /dev/null and b/test_images/test_image.jpg differ diff --git a/worker/.env.example b/worker/.env.example new file mode 100644 index 0000000..5d09a73 --- /dev/null +++ b/worker/.env.example @@ -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 diff --git a/worker/config.py b/worker/config.py new file mode 100644 index 0000000..2c947fa --- /dev/null +++ b/worker/config.py @@ -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 + ) diff --git a/worker/document_processor.py b/worker/document_processor.py new file mode 100644 index 0000000..1630845 --- /dev/null +++ b/worker/document_processor.py @@ -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 diff --git a/worker/nextcloud_client.py b/worker/nextcloud_client.py new file mode 100644 index 0000000..78f7b6d --- /dev/null +++ b/worker/nextcloud_client.py @@ -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 = """ + + + + + + + + +""" + + 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'(.*?)' + modified_pattern = r'(.*?)' + size_pattern = r'(.*?)' + type_pattern = r'(.*?)' + + 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() diff --git a/worker/nextcloud_sync.py b/worker/nextcloud_sync.py new file mode 100755 index 0000000..3031b5b --- /dev/null +++ b/worker/nextcloud_sync.py @@ -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() diff --git a/worker/openwebui_client.py b/worker/openwebui_client.py new file mode 100644 index 0000000..0ec544e --- /dev/null +++ b/worker/openwebui_client.py @@ -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() diff --git a/worker/requirements.txt b/worker/requirements.txt new file mode 100644 index 0000000..3cd69ef --- /dev/null +++ b/worker/requirements.txt @@ -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 функции