Add project and deployment instruction (docs/DEPLOYMENT.md)
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
36
.env.example
Normal file
36
.env.example
Normal file
@@ -0,0 +1,36 @@
|
||||
# === ДОМЕНЫ И СЕТЬ ===
|
||||
DOMAIN_OPENWEBUI=https://odo.iieasy.ru
|
||||
DOMAIN_NEXTCLOUD=https://next.iieasy.ru
|
||||
DOMAIN_AUTHENTIK=https://auth.iieasy.ru
|
||||
DOMAIN_VAULTWARDEN=http://192.168.88.165:8082
|
||||
|
||||
# === AUTHENTIK (OIDC SSO) ===
|
||||
# Где брать: Authentik -> Providers -> твой OIDC Provider
|
||||
OAUTH_CLIENT_ID=your_oauth_client_id
|
||||
OAUTH_CLIENT_SECRET=your_oauth_client_secret
|
||||
OPENID_CONNECT_ISSUER=https://auth.iieasy.ru/application/o/open-webui/
|
||||
|
||||
# === NEXTCLOUD (Для Python-воркера) ===
|
||||
# Где брать: Nextcloud -> Настройки -> Безопасность -> Устройства и сессии (Создать пароль приложения)
|
||||
NC_USER=your_nextcloud_username
|
||||
NC_APP_PASSWORD=your_app_password
|
||||
|
||||
# === OPEN WEBUI API (Для пуша файлов в Qdrant) ===
|
||||
# Где брать: Open WebUI -> Settings -> Account -> API Keys (создашь после первого запуска)
|
||||
OPENWEBUI_API_KEY=your_api_key_here
|
||||
|
||||
# === VAULTWARDEN (Для интеграции Bitwarden CLI) ===
|
||||
# Где брать: Vaultwarden -> Настройки аккаунта -> Безопасность -> Ключи -> API-ключ
|
||||
BW_CLIENTID=your_vaultwarden_client_id
|
||||
BW_CLIENTSECRET=your_vaultwarden_client_secret
|
||||
|
||||
# === QDRANT ===
|
||||
# Сгенерировать случайный ключ: openssl rand -hex 32
|
||||
QDRANT_API_KEY=your_qdrant_api_key_here
|
||||
|
||||
# === SEARXNG ===
|
||||
SEARXNG_HOSTNAME=searxng:8080
|
||||
|
||||
# === OLLAMA ===
|
||||
OLLAMA_MODEL=gemma3n:e4b-it-fp16
|
||||
NVIDIA_VISIBLE_DEVICES=all
|
||||
65
.gitignore
vendored
Normal file
65
.gitignore
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
# Переменные окружения
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Docker volumes и данные
|
||||
volumes/
|
||||
*.db
|
||||
*.sqlite
|
||||
*.sqlite3
|
||||
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# Виртуальные окружения
|
||||
venv/
|
||||
env/
|
||||
ENV/
|
||||
.venv
|
||||
|
||||
# Логи
|
||||
*.log
|
||||
logs/
|
||||
sync.log
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Временные файлы
|
||||
*.tmp
|
||||
*.bak
|
||||
*.cache
|
||||
|
||||
# Медиа файлы (если не нужны в репозитории)
|
||||
# Раскомментируйте если не хотите хранить медиа в git
|
||||
# media/*.png
|
||||
# media/*.jpg
|
||||
# media/*.ico
|
||||
54
APPLY_AUTHENTIK_FIX.sh
Executable file
54
APPLY_AUTHENTIK_FIX.sh
Executable file
@@ -0,0 +1,54 @@
|
||||
#!/bin/bash
|
||||
# Скрипт для применения исправления Authentik
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$SCRIPT_DIR"
|
||||
|
||||
echo "=== Применение исправления Authentik ==="
|
||||
echo "Рабочая директория: $PROJECT_DIR"
|
||||
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
# Проверка наличия .env файла
|
||||
if [ ! -f ".env" ]; then
|
||||
echo "✗ Ошибка: .env файл не найден в $PROJECT_DIR"
|
||||
echo "Создайте .env из .env.example или проверьте путь"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Проверка правильности .env
|
||||
if grep -q "ii-easy-web" .env; then
|
||||
echo "✓ .env содержит правильный slug: ii-easy-web"
|
||||
else
|
||||
echo "⚠ Предупреждение: .env не содержит slug 'ii-easy-web'"
|
||||
echo "Проверьте значение OPENID_CONNECT_ISSUER в .env"
|
||||
fi
|
||||
|
||||
# Перезапуск контейнера для применения изменений
|
||||
echo "Перезапуск контейнера open-webui..."
|
||||
docker compose restart open-webui
|
||||
|
||||
echo "Ожидание запуска (15 секунд)..."
|
||||
sleep 15
|
||||
|
||||
# Проверка доступности
|
||||
if curl -f http://localhost:3001/health >/dev/null 2>&1; then
|
||||
echo "✓ Open WebUI запущен и отвечает"
|
||||
else
|
||||
echo "⚠ Open WebUI может еще запускаться, проверьте логи:"
|
||||
echo " docker compose logs open-webui --tail 30"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== Готово! ==="
|
||||
echo ""
|
||||
echo "Проверьте вход через Authentik:"
|
||||
echo " 1. Откройте https://odo.iieasy.ru"
|
||||
echo " 2. Попробуйте войти через 'iiEasy ID' (Authentik)"
|
||||
echo ""
|
||||
echo "Если все еще Internal Server Error:"
|
||||
echo " 1. Проверьте redirect URI в Authentik: https://odo.iieasy.ru/oauth/oidc/callback"
|
||||
echo " 2. Проверьте логи: docker compose logs open-webui | grep -i oauth"
|
||||
echo " 3. Временно используйте форму входа (уже включена)"
|
||||
65
APPLY_OAUTH_FIX.sh
Executable file
65
APPLY_OAUTH_FIX.sh
Executable file
@@ -0,0 +1,65 @@
|
||||
#!/bin/bash
|
||||
# Скрипт для применения исправления OAuth endpoint
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$SCRIPT_DIR"
|
||||
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
echo "=== Применение исправления OAuth endpoint ==="
|
||||
echo ""
|
||||
|
||||
# Проверка .env файла
|
||||
if [ ! -f ".env" ]; then
|
||||
echo "✗ Ошибка: .env файл не найден"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Проверка правильного slug
|
||||
if grep -q "OPENID_CONNECT_ISSUER=https://auth.iieasy.ru/application/o/open-webui/" .env; then
|
||||
echo "✓ .env содержит правильный slug: open-webui"
|
||||
else
|
||||
echo "✗ Ошибка: .env не содержит правильный slug"
|
||||
echo " Должно быть: OPENID_CONNECT_ISSUER=https://auth.iieasy.ru/application/o/open-webui/"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "1. Перезапуск контейнера open-webui..."
|
||||
sudo docker compose restart open-webui
|
||||
|
||||
echo ""
|
||||
echo "2. Ожидание запуска контейнера (20 секунд)..."
|
||||
sleep 20
|
||||
|
||||
echo ""
|
||||
echo "3. Проверка статуса контейнера..."
|
||||
if sudo docker compose ps open-webui | grep -q "Up"; then
|
||||
echo "✓ Контейнер запущен"
|
||||
else
|
||||
echo "✗ Контейнер не запустился. Проверьте логи:"
|
||||
echo " sudo docker compose logs open-webui"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "4. Проверка логов на ошибки OAuth..."
|
||||
OAUTH_ERRORS=$(sudo docker compose logs open-webui --tail 50 2>&1 | grep -i "oauth\|oidc\|404\|error" | tail -5 || true)
|
||||
if [ -z "$OAUTH_ERRORS" ]; then
|
||||
echo "✓ Ошибок OAuth не найдено"
|
||||
else
|
||||
echo "⚠ Найдены ошибки в логах:"
|
||||
echo "$OAUTH_ERRORS"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== Готово! ==="
|
||||
echo ""
|
||||
echo "Проверьте OAuth:"
|
||||
echo "1. Откройте https://odo.iieasy.ru"
|
||||
echo "2. Нажмите кнопку 'iiEasy ID' для входа через OAuth"
|
||||
echo ""
|
||||
echo "Если есть проблемы, проверьте логи:"
|
||||
echo " sudo docker compose logs open-webui --tail 100 | grep -i oauth"
|
||||
94
AUTHENTIK_FIX.md
Normal file
94
AUTHENTIK_FIX.md
Normal file
@@ -0,0 +1,94 @@
|
||||
# Быстрое решение проблемы с Authentik
|
||||
|
||||
## Проблема: Internal Server Error при входе через Authentik
|
||||
|
||||
Endpoint `https://auth.iieasy.ru/application/o/open-webui/.well-known/openid-configuration` возвращает 404 Not Found.
|
||||
|
||||
## Решение 1: Временное включение формы входа (работает сейчас)
|
||||
|
||||
Форма входа уже включена в `docker-compose.yml`. Перезапустите контейнер:
|
||||
|
||||
```bash
|
||||
cd /home/its/iiEasyWeb
|
||||
sudo docker compose restart open-webui
|
||||
```
|
||||
|
||||
Теперь вы сможете войти через форму входа на странице `https://odo.iieasy.ru`.
|
||||
|
||||
## Решение 2: Правильная настройка Authentik
|
||||
|
||||
### Проверка в Authentik
|
||||
|
||||
1. Войдите в Authentik: `https://auth.iieasy.ru`
|
||||
2. Перейдите в **Applications**
|
||||
3. Найдите Application для Open WebUI
|
||||
4. Проверьте **Slug** - он должен быть `open-webui`
|
||||
|
||||
### Если Application не существует
|
||||
|
||||
1. **Providers** → **Add Provider**
|
||||
- Тип: **OpenID Connect / OAuth2 / OAuth2 with OpenID Connect**
|
||||
- Название: `Open WebUI`
|
||||
- Сохраните и запомните **Client ID** и **Client Secret**
|
||||
|
||||
2. **Applications** → **Add Application**
|
||||
- **Name**: `Open WebUI`
|
||||
- **Slug**: `open-webui` (ВАЖНО! Должен совпадать с URL)
|
||||
- **Provider**: Выберите созданный Provider
|
||||
- **Redirect URIs**: `https://odo.iieasy.ru/oauth/oidc/callback`
|
||||
- Сохраните
|
||||
|
||||
3. **Обновите .env:**
|
||||
```bash
|
||||
OPENID_CONNECT_ISSUER=https://auth.iieasy.ru/application/o/open-webui/
|
||||
```
|
||||
Где `open-webui` - это slug из Application.
|
||||
|
||||
4. **Проверьте endpoint:**
|
||||
```bash
|
||||
curl https://auth.iieasy.ru/application/o/open-webui/.well-known/openid-configuration
|
||||
```
|
||||
Должен вернуться JSON, а не HTML.
|
||||
|
||||
5. **Перезапустите контейнер:**
|
||||
```bash
|
||||
sudo docker compose restart open-webui
|
||||
```
|
||||
|
||||
### Если Application существует, но slug другой
|
||||
|
||||
Если slug в Authentik не `open-webui`, а например `openwebui` или `webui`:
|
||||
|
||||
1. Обновите `.env`:
|
||||
```bash
|
||||
OPENID_CONNECT_ISSUER=https://auth.iieasy.ru/application/o/ВАШ_SLUG/
|
||||
```
|
||||
|
||||
2. Или измените slug в Authentik Application на `open-webui`
|
||||
|
||||
## Решение 3: Отключение Authentik (если не нужен)
|
||||
|
||||
Если Authentik не нужен, отключите его:
|
||||
|
||||
В `docker-compose.yml` измените:
|
||||
```yaml
|
||||
- ENABLE_OAUTH_SIGNUP=false
|
||||
- ENABLE_LOGIN_FORM=true
|
||||
```
|
||||
|
||||
И удалите или закомментируйте переменные OAuth.
|
||||
|
||||
## Проверка после настройки
|
||||
|
||||
1. Проверьте endpoint:
|
||||
```bash
|
||||
curl https://auth.iieasy.ru/application/o/open-webui/.well-known/openid-configuration
|
||||
```
|
||||
Должен вернуться JSON с `issuer`, `authorization_endpoint` и т.д.
|
||||
|
||||
2. Проверьте логи:
|
||||
```bash
|
||||
sudo docker compose logs open-webui | grep -i "oidc\|oauth"
|
||||
```
|
||||
|
||||
3. Попробуйте войти через `https://odo.iieasy.ru`
|
||||
139
AUTHENTIK_SETUP.md
Normal file
139
AUTHENTIK_SETUP.md
Normal file
@@ -0,0 +1,139 @@
|
||||
# Настройка Authentik для Open WebUI
|
||||
|
||||
## Проблема: Internal Server Error при входе через Authentik
|
||||
|
||||
### Диагностика
|
||||
|
||||
1. **Проверьте доступность Authentik:**
|
||||
```bash
|
||||
curl -I https://auth.iieasy.ru
|
||||
```
|
||||
|
||||
2. **Проверьте OpenID endpoint:**
|
||||
```bash
|
||||
# Правильный путь для Authentik:
|
||||
curl https://auth.iieasy.ru/application/o/open-webui/.well-known/openid-configuration
|
||||
```
|
||||
|
||||
3. **Проверьте логи Open WebUI:**
|
||||
```bash
|
||||
sudo docker compose logs open-webui | grep -i "auth\|oidc\|oauth\|error"
|
||||
```
|
||||
|
||||
## Правильная настройка Authentik
|
||||
|
||||
### Шаг 1: Создание OIDC Provider в Authentik
|
||||
|
||||
1. Войдите в Authentik: `https://auth.iieasy.ru`
|
||||
2. Перейдите в **Providers** → **Add Provider**
|
||||
3. Выберите **OpenID Connect / OAuth2 / OAuth2 with OpenID Connect**
|
||||
4. Заполните:
|
||||
- **Name**: `Open WebUI` (или любое имя)
|
||||
- **Authorization flow**: Выберите существующий flow
|
||||
- **Redirect URIs**: `https://odo.iieasy.ru/oauth/oidc/callback`
|
||||
- **Client type**: `Confidential`
|
||||
- **Client ID**: Запомните этот ID
|
||||
- **Client Secret**: Запомните этот секрет
|
||||
|
||||
5. Сохраните Provider
|
||||
|
||||
### Шаг 2: Создание Application в Authentik
|
||||
|
||||
1. Перейдите в **Applications** → **Add Application**
|
||||
2. Заполните:
|
||||
- **Name**: `Open WebUI`
|
||||
- **Slug**: `open-webui` (это важно для URL!)
|
||||
- **Provider**: Выберите созданный Provider
|
||||
3. Сохраните Application
|
||||
|
||||
### Шаг 3: Проверка правильного URL
|
||||
|
||||
После создания Application, правильный URL будет:
|
||||
```
|
||||
https://auth.iieasy.ru/application/o/open-webui/.well-known/openid-configuration
|
||||
```
|
||||
|
||||
Где `open-webui` - это slug из Application.
|
||||
|
||||
### Шаг 4: Обновление .env
|
||||
|
||||
В файле `.env` убедитесь, что:
|
||||
|
||||
```bash
|
||||
OPENID_CONNECT_ISSUER=https://auth.iieasy.ru/application/o/open-webui/
|
||||
```
|
||||
|
||||
**ВАЖНО:**
|
||||
- URL должен заканчиваться на `/`
|
||||
- Slug (`open-webui`) должен совпадать с slug в Authentik Application
|
||||
- После `/application/o/` идет slug, затем `/`
|
||||
|
||||
### Шаг 5: Проверка переменных в docker-compose.yml
|
||||
|
||||
В `docker-compose.yml` используется:
|
||||
```yaml
|
||||
OPENID_PROVIDER_URL=${OPENID_CONNECT_ISSUER}.well-known/openid-configuration
|
||||
```
|
||||
|
||||
Это создаст полный URL:
|
||||
```
|
||||
https://auth.iieasy.ru/application/o/open-webui/.well-known/openid-configuration
|
||||
```
|
||||
|
||||
### Шаг 6: Перезапуск контейнера
|
||||
|
||||
```bash
|
||||
cd /home/its/iiEasyWeb
|
||||
sudo docker compose restart open-webui
|
||||
```
|
||||
|
||||
## Временное решение: Включить форму входа
|
||||
|
||||
Если Authentik не работает, временно включите форму входа:
|
||||
|
||||
В `docker-compose.yml` измените:
|
||||
```yaml
|
||||
- ENABLE_LOGIN_FORM=true # Временно включено
|
||||
- ENABLE_OAUTH_SIGNUP=false # Временно отключено
|
||||
```
|
||||
|
||||
Затем перезапустите:
|
||||
```bash
|
||||
sudo docker compose restart open-webui
|
||||
```
|
||||
|
||||
## Проверка конфигурации
|
||||
|
||||
1. **Проверьте, что endpoint доступен:**
|
||||
```bash
|
||||
curl https://auth.iieasy.ru/application/o/open-webui/.well-known/openid-configuration
|
||||
```
|
||||
Должен вернуться JSON с конфигурацией OpenID Connect.
|
||||
|
||||
2. **Проверьте redirect URI в Authentik:**
|
||||
- Должен быть: `https://odo.iieasy.ru/oauth/oidc/callback`
|
||||
- Без завершающего слеша
|
||||
|
||||
3. **Проверьте логи:**
|
||||
```bash
|
||||
sudo docker compose logs open-webui --tail 100 | grep -i "oidc\|oauth"
|
||||
```
|
||||
|
||||
## Частые ошибки
|
||||
|
||||
### 404 Not Found на .well-known/openid-configuration
|
||||
|
||||
- Проверьте slug в Application (должен быть `open-webui`)
|
||||
- Проверьте URL в .env (должен заканчиваться на `/`)
|
||||
- Убедитесь, что Application привязан к Provider
|
||||
|
||||
### Invalid redirect URI
|
||||
|
||||
- Проверьте redirect URI в Authentik: `https://odo.iieasy.ru/oauth/oidc/callback`
|
||||
- Убедитесь, что домен правильный (без порта, если используете HTTPS)
|
||||
|
||||
### Internal Server Error
|
||||
|
||||
- Проверьте логи Open WebUI
|
||||
- Убедитесь, что Client ID и Client Secret правильные
|
||||
- Проверьте, что SSL сертификат валидный для auth.iieasy.ru
|
||||
111
DIAGNOSE_VISION_ISSUE.md
Normal file
111
DIAGNOSE_VISION_ISSUE.md
Normal file
@@ -0,0 +1,111 @@
|
||||
# Диагностика проблемы с передачей изображений в Ollama
|
||||
|
||||
## Проблема
|
||||
Open WebUI не передает изображения в Ollama для модели gemma3n:e4b-it-fp16, хотя:
|
||||
- ✅ Адрес Ollama правильный: `http://ollama:11434`
|
||||
- ✅ Модель поддерживает vision
|
||||
- ✅ Изображения загружаются в Open WebUI
|
||||
- ❌ Но не доходят до Ollama (нет запросов с изображениями в логах)
|
||||
|
||||
## Возможные причины
|
||||
|
||||
### 1. Open WebUI v0.8.3 не распознает gemma3n как vision-модель
|
||||
|
||||
Open WebUI может не знать, что gemma3n:e4b-it-fp16 поддерживает vision. Нужно проверить список vision-моделей в коде.
|
||||
|
||||
### 2. Модель не помечена как vision в настройках
|
||||
|
||||
Возможно, нужно явно указать в настройках Open WebUI, что модель поддерживает vision.
|
||||
|
||||
### 3. Проблема с форматом передачи изображений
|
||||
|
||||
Open WebUI может передавать изображения в неправильном формате для Ollama API.
|
||||
|
||||
## Решения
|
||||
|
||||
### Решение 1: Проверка настроек модели в Open WebUI
|
||||
|
||||
1. Откройте https://odo.iieasy.ru
|
||||
2. Перейдите в **Settings → Models**
|
||||
3. Найдите модель `gemma3n:e4b-it-fp16`
|
||||
4. Проверьте, есть ли опция "Vision" или "Multimodal"
|
||||
5. Включите её, если есть
|
||||
|
||||
### Решение 2: Обновление Open WebUI
|
||||
|
||||
Версия v0.8.3 может иметь проблемы с vision. Попробуйте обновить до последней версии:
|
||||
|
||||
```yaml
|
||||
# В docker-compose.yml измените:
|
||||
image: ghcr.io/open-webui/open-webui:latest
|
||||
# или
|
||||
image: ghcr.io/open-webui/open-webui:v1.x.x
|
||||
```
|
||||
|
||||
Затем:
|
||||
```bash
|
||||
docker compose pull open-webui
|
||||
docker compose up -d open-webui
|
||||
```
|
||||
|
||||
### Решение 3: Проверка через прямой API запрос
|
||||
|
||||
Проверьте, работает ли vision напрямую через Ollama API:
|
||||
|
||||
```bash
|
||||
cd /home/its/iiEasyWeb/test_images
|
||||
IMAGE_B64=$(base64 -w 0 test_image.jpg)
|
||||
|
||||
sudo docker exec ollama curl -s -X POST http://localhost:11434/api/generate \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d "{
|
||||
\"model\": \"gemma3n:e4b-it-fp16\",
|
||||
\"prompt\": \"Опиши это изображение на русском языке\",
|
||||
\"images\": [\"$IMAGE_B64\"],
|
||||
\"stream\": false
|
||||
}" | jq -r '.response'
|
||||
```
|
||||
|
||||
Если это работает, значит проблема в Open WebUI, а не в Ollama.
|
||||
|
||||
### Решение 4: Проверка логов при отправке изображения
|
||||
|
||||
1. Откройте два терминала
|
||||
|
||||
2. В первом терминале:
|
||||
```bash
|
||||
sudo docker logs open-webui -f | grep -i "image\|ollama\|generate"
|
||||
```
|
||||
|
||||
3. Во втором терминале:
|
||||
```bash
|
||||
sudo docker logs ollama -f | grep -i "generate\|image"
|
||||
```
|
||||
|
||||
4. Отправьте изображение через веб-интерфейс
|
||||
|
||||
5. Проверьте, что появляется в логах:
|
||||
- В Open WebUI должен быть запрос с изображением
|
||||
- В Ollama должен быть запрос к `/api/generate` с полем `images`
|
||||
|
||||
### Решение 5: Проверка версии Open WebUI
|
||||
|
||||
```bash
|
||||
sudo docker exec open-webui cat /app/backend/version.txt
|
||||
```
|
||||
|
||||
Если версия старая, обновите до последней.
|
||||
|
||||
## Альтернативное решение: Использование API напрямую
|
||||
|
||||
Если Open WebUI не поддерживает vision для gemma3n, можно использовать API напрямую или создать свой клиент.
|
||||
|
||||
## Проверка работы vision модели
|
||||
|
||||
Убедитесь, что модель действительно поддерживает vision:
|
||||
|
||||
```bash
|
||||
sudo docker exec ollama ollama show gemma3n:e4b-it-fp16
|
||||
```
|
||||
|
||||
В выводе должна быть информация о поддержке vision/multimodal.
|
||||
70
FIX_DOCUMENTATION_LINKS.md
Normal file
70
FIX_DOCUMENTATION_LINKS.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# Исправление ссылок на документацию и удаление "(Open WebUI)"
|
||||
|
||||
## Проблемы
|
||||
|
||||
1. Текст "(Open WebUI)" все еще виден в "Войти в iiEasyWeb (Open WebUI)"
|
||||
2. Ссылки на документацию ведут на оригинальный сайт вместо note.iieasy.ru
|
||||
|
||||
## Решение
|
||||
|
||||
**1. Запустите финальный скрипт ребрендинга:**
|
||||
|
||||
```bash
|
||||
cd /home/its/iiEasyWeb
|
||||
sudo ./scripts/rebrand_final.sh
|
||||
```
|
||||
|
||||
Этот скрипт:
|
||||
- ✅ Агрессивно ищет и удаляет "(Open WebUI)" из ВСЕХ файлов
|
||||
- ✅ Заменяет ссылки на документацию на note.iieasy.ru
|
||||
- ✅ Проверяет и исправляет базу данных
|
||||
- ✅ Обрабатывает все варианты написания
|
||||
|
||||
**2. Очистите кеш браузера:**
|
||||
|
||||
После запуска скрипта обязательно очистите кеш:
|
||||
- **Chrome/Edge**: Ctrl+Shift+Delete
|
||||
- Выберите "Изображения и файлы в кеше"
|
||||
- Очистите кеш
|
||||
|
||||
**3. Проверьте через Admin Panel:**
|
||||
|
||||
Если текст все еще виден:
|
||||
|
||||
1. Откройте `https://odo.iieasy.ru`
|
||||
2. Войдите как администратор
|
||||
3. Перейдите в **Settings → Appearance**
|
||||
4. Проверьте поле **Site Title** - должно быть "iiEasyWeb" без "(Open WebUI)"
|
||||
5. Если там есть "(Open WebUI)", удалите его вручную и сохраните
|
||||
|
||||
**4. Проверьте переменные окружения:**
|
||||
|
||||
Убедитесь, что в `docker-compose.yml` правильно настроено:
|
||||
|
||||
```yaml
|
||||
- WEBUI_NAME=iiEasyWeb
|
||||
```
|
||||
|
||||
## Что заменяется
|
||||
|
||||
Скрипт заменяет следующие ссылки на документацию:
|
||||
- `https://docs.openwebui.com` → `https://note.iieasy.ru`
|
||||
- `https://open-webui.com/docs` → `https://note.iieasy.ru`
|
||||
- `https://github.com/open-webui/docs` → `https://note.iieasy.ru`
|
||||
- `docs.openwebui.com` → `note.iieasy.ru`
|
||||
|
||||
## Если проблема осталась
|
||||
|
||||
Если после всех действий текст "(Open WebUI)" все еще виден:
|
||||
|
||||
1. Выполните поиск вручную:
|
||||
```bash
|
||||
sudo docker exec open-webui find /app -type f -exec grep -l "(Open WebUI)" {} \; 2>/dev/null
|
||||
```
|
||||
|
||||
2. Проверьте базу данных напрямую:
|
||||
```bash
|
||||
sudo docker exec open-webui sqlite3 /app/backend/data/webui.db "SELECT * FROM settings WHERE value LIKE '%Open WebUI%';"
|
||||
```
|
||||
|
||||
3. Если найдете в базе, удалите вручную через Admin Panel или SQLite
|
||||
124
FIX_IMAGE_TRANSFER.md
Normal file
124
FIX_IMAGE_TRANSFER.md
Normal file
@@ -0,0 +1,124 @@
|
||||
# Исправление проблемы с передачей изображений из Open WebUI в Ollama
|
||||
|
||||
## Проблема
|
||||
Open WebUI не передает изображения в Ollama правильно. В логах Ollama нет запросов с изображениями.
|
||||
|
||||
## Диагностика
|
||||
|
||||
### 1. Проверка переменной окружения
|
||||
```bash
|
||||
sudo docker exec open-webui env | grep -i OLLAMA
|
||||
```
|
||||
|
||||
Должно быть:
|
||||
```
|
||||
OLLAMA_BASE_URL=http://ollama:11434
|
||||
```
|
||||
|
||||
### 2. Проверка доступности Ollama
|
||||
```bash
|
||||
sudo docker exec open-webui curl -s http://ollama:11434/api/tags | head -5
|
||||
```
|
||||
|
||||
### 3. Проверка логов
|
||||
```bash
|
||||
# Логи Open WebUI при отправке изображения
|
||||
sudo docker logs open-webui --tail 100 | grep -i "image\|ollama\|error"
|
||||
|
||||
# Логи Ollama - должны быть запросы с изображениями
|
||||
sudo docker logs ollama --tail 100 | grep -i "image\|vision"
|
||||
```
|
||||
|
||||
## Решение
|
||||
|
||||
### Шаг 1: Убедитесь, что переменная добавлена в docker-compose.yml
|
||||
|
||||
В файле `/home/its/iiEasyWeb/docker-compose.yml` должна быть строка:
|
||||
```yaml
|
||||
# Ollama API для работы с изображениями
|
||||
- OLLAMA_BASE_URL=http://ollama:11434
|
||||
```
|
||||
|
||||
### Шаг 2: Перезапустите контейнер Open WebUI
|
||||
```bash
|
||||
cd /home/its/iiEasyWeb
|
||||
docker compose restart open-webui
|
||||
```
|
||||
|
||||
Или полностью пересоздайте:
|
||||
```bash
|
||||
docker compose up -d --force-recreate open-webui
|
||||
```
|
||||
|
||||
### Шаг 3: Проверьте настройки в веб-интерфейсе
|
||||
|
||||
1. Откройте https://odo.iieasy.ru
|
||||
2. Перейдите в **Settings → Connections → Ollama API**
|
||||
3. Убедитесь, что адрес: **`http://ollama:11434`**
|
||||
- НЕ используйте `host.docker.internal:11434`
|
||||
- НЕ используйте `localhost:11434`
|
||||
- Должно быть именно `http://ollama:11434`
|
||||
|
||||
### Шаг 4: Проверьте формат изображения
|
||||
|
||||
Open WebUI должен передавать изображения в формате base64 в поле `images` массива JSON запроса к Ollama API.
|
||||
|
||||
Формат запроса должен быть:
|
||||
```json
|
||||
{
|
||||
"model": "gemma3n:e4b-it-fp16",
|
||||
"prompt": "Опиши это изображение",
|
||||
"images": ["base64_encoded_image_data"],
|
||||
"stream": false
|
||||
}
|
||||
```
|
||||
|
||||
## Дополнительная диагностика
|
||||
|
||||
### Запустите скрипт проверки:
|
||||
```bash
|
||||
cd /home/its/iiEasyWeb
|
||||
sudo ./scripts/check_image_transfer.sh
|
||||
```
|
||||
|
||||
### Проверка в реальном времени:
|
||||
|
||||
1. Откройте два терминала
|
||||
|
||||
2. В первом терминале следите за логами Open WebUI:
|
||||
```bash
|
||||
sudo docker logs open-webui -f | grep -i "image\|ollama"
|
||||
```
|
||||
|
||||
3. Во втором терминале следите за логами Ollama:
|
||||
```bash
|
||||
sudo docker logs ollama -f | grep -i "image\|vision\|generate"
|
||||
```
|
||||
|
||||
4. Отправьте изображение через веб-интерфейс
|
||||
|
||||
5. Проверьте, появляются ли запросы в логах Ollama
|
||||
|
||||
## Возможные проблемы
|
||||
|
||||
1. **Переменная не применена** - контейнер не перезапущен после изменения docker-compose.yml
|
||||
2. **Неправильный адрес в настройках** - в веб-интерфейсе указан неправильный адрес Ollama
|
||||
3. **Проблема с сетью Docker** - контейнеры не могут общаться друг с другом
|
||||
4. **Формат изображения** - Open WebUI передает изображение в неправильном формате
|
||||
5. **Версия Open WebUI** - старая версия может не поддерживать vision правильно
|
||||
|
||||
## Проверка версии Open WebUI
|
||||
|
||||
```bash
|
||||
sudo docker exec open-webui cat /app/backend/version.txt
|
||||
```
|
||||
|
||||
Текущая версия в docker-compose.yml: `v0.8.3`
|
||||
|
||||
## Альтернативное решение
|
||||
|
||||
Если проблема не решается, можно попробовать:
|
||||
|
||||
1. Обновить Open WebUI до последней версии
|
||||
2. Использовать прямой API запрос к Ollama для тестирования
|
||||
3. Проверить документацию Open WebUI по работе с vision моделями
|
||||
82
FIX_LOGIN_TITLE.md
Normal file
82
FIX_LOGIN_TITLE.md
Normal file
@@ -0,0 +1,82 @@
|
||||
# Исправление текста "Войти в iiEasyWeb (Open WebUI)"
|
||||
|
||||
## Проблема
|
||||
|
||||
В заголовке страницы входа все еще видно "(Open WebUI)": "Войти в iiEasyWeb (Open WebUI)"
|
||||
|
||||
## Решение
|
||||
|
||||
**1. Сначала найдите, где находится этот текст:**
|
||||
|
||||
```bash
|
||||
cd /home/its/iiEasyWeb
|
||||
sudo ./scripts/find_openwebui_text.sh
|
||||
```
|
||||
|
||||
Этот скрипт покажет все файлы, где есть "(Open WebUI)".
|
||||
|
||||
**2. Запустите точный скрипт ребрендинга:**
|
||||
|
||||
```bash
|
||||
sudo ./scripts/rebrand_precise.sh
|
||||
```
|
||||
|
||||
**3. Если текст все еще виден, проверьте:**
|
||||
|
||||
### Вариант A: Текст в базе данных
|
||||
|
||||
Текст может храниться в базе данных Open WebUI. Проверьте через Admin Panel:
|
||||
|
||||
1. Откройте `https://odo.iieasy.ru`
|
||||
2. Войдите как администратор
|
||||
3. Перейдите в **Settings → Appearance**
|
||||
4. Проверьте поле **Site Title** - должно быть "iiEasyWeb" без "(Open WebUI)"
|
||||
5. Сохраните изменения
|
||||
|
||||
### Вариант B: Текст в переменных окружения
|
||||
|
||||
Проверьте `docker-compose.yml`:
|
||||
|
||||
```bash
|
||||
grep -i "webui_name\|site_title" docker-compose.yml
|
||||
```
|
||||
|
||||
Должно быть:
|
||||
```yaml
|
||||
- WEBUI_NAME=iiEasyWeb
|
||||
```
|
||||
|
||||
### Вариант C: Очистка кеша браузера
|
||||
|
||||
После изменений очистите кеш браузера:
|
||||
- Chrome/Edge: Ctrl+Shift+Delete (Cmd+Shift+Delete на Mac)
|
||||
- Выберите "Изображения и файлы в кеше"
|
||||
- Очистите кеш
|
||||
|
||||
### Вариант D: Пересборка фронтенда
|
||||
|
||||
Если текст в скомпилированных файлах, может потребоваться пересборка:
|
||||
|
||||
```bash
|
||||
# Пересоздайте контейнер
|
||||
sudo docker compose stop open-webui
|
||||
sudo docker compose rm -f open-webui
|
||||
sudo docker compose up -d open-webui
|
||||
|
||||
# Подождите 30 секунд
|
||||
sleep 30
|
||||
|
||||
# Запустите ребрендинг снова
|
||||
sudo ./scripts/rebrand_precise.sh
|
||||
```
|
||||
|
||||
## Если ничего не помогает
|
||||
|
||||
Выполните поиск вручную:
|
||||
|
||||
```bash
|
||||
# Найдите все файлы с этим текстом
|
||||
sudo docker exec open-webui find /app -type f -exec grep -l "(Open WebUI)" {} \; 2>/dev/null
|
||||
|
||||
# Затем замените вручную в найденных файлах
|
||||
```
|
||||
42
FIX_OAUTH_REDIRECT.md
Normal file
42
FIX_OAUTH_REDIRECT.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# Исправление редиректа на /auth
|
||||
|
||||
## Проблема: Редирект на /auth вместо главной страницы
|
||||
|
||||
Когда Open WebUI настроен на OAuth и `ENABLE_LOGIN_FORM=false`, но OAuth не работает правильно, происходит редирект на `/auth`.
|
||||
|
||||
## Решение
|
||||
|
||||
**1. Убедитесь, что форма входа включена (уже сделано):**
|
||||
|
||||
В `docker-compose.yml`:
|
||||
```yaml
|
||||
- ENABLE_LOGIN_FORM=true # Включено
|
||||
- ENABLE_OAUTH_SIGNUP=true
|
||||
```
|
||||
|
||||
**2. Перезапустите контейнер:**
|
||||
|
||||
```bash
|
||||
cd /home/its/iiEasyWeb
|
||||
sudo docker compose restart open-webui
|
||||
```
|
||||
|
||||
**3. Проверьте конфигурацию OAuth:**
|
||||
|
||||
- Endpoint должен быть доступен: `https://auth.iieasy.ru/application/o/ii-easy-web/.well-known/openid-configuration`
|
||||
- Redirect URI в Authentik: `https://odo.iieasy.ru/oauth/oidc/callback`
|
||||
- Client ID и Client Secret должны совпадать
|
||||
|
||||
**4. Если OAuth все еще не работает:**
|
||||
|
||||
Временно можно оставить форму входа включенной (`ENABLE_LOGIN_FORM=true`), чтобы пользователи могли войти. OAuth будет работать параллельно как альтернативный способ входа.
|
||||
|
||||
## Проверка
|
||||
|
||||
После перезапуска:
|
||||
1. Откройте `https://odo.iieasy.ru`
|
||||
2. Должна появиться страница входа с кнопкой "iiEasy ID" (OAuth) и формой логина
|
||||
3. Попробуйте войти через форму входа
|
||||
4. Попробуйте войти через "iiEasy ID" (OAuth)
|
||||
|
||||
Если OAuth работает, форма входа можно отключить позже (`ENABLE_LOGIN_FORM=false`).
|
||||
41
FIX_OAUTH_SLUG.md
Normal file
41
FIX_OAUTH_SLUG.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Исправление slug для Authentik OAuth
|
||||
|
||||
## Проблема
|
||||
|
||||
Endpoint `https://auth.iieasy.ru/application/o/ii-easy-web/.well-known/openid-configuration` возвращает HTML "Not Found".
|
||||
|
||||
## Решение
|
||||
|
||||
Правильный slug в Authentik - это `open-webui`, а не `ii-easy-web`.
|
||||
|
||||
**1. Исправлен `.env` файл:**
|
||||
|
||||
```bash
|
||||
OPENID_CONNECT_ISSUER=https://auth.iieasy.ru/application/o/open-webui/
|
||||
```
|
||||
|
||||
**2. Перезапустите контейнер:**
|
||||
|
||||
```bash
|
||||
cd /home/its/iiEasyWeb
|
||||
sudo docker compose restart open-webui
|
||||
```
|
||||
|
||||
**3. Проверьте endpoint:**
|
||||
|
||||
```bash
|
||||
curl https://auth.iieasy.ru/application/o/open-webui/.well-known/openid-configuration
|
||||
```
|
||||
|
||||
Должен вернуться JSON с конфигурацией OpenID Connect.
|
||||
|
||||
**4. Проверьте OAuth:**
|
||||
|
||||
После перезапуска откройте `https://odo.iieasy.ru` и попробуйте войти через кнопку "iiEasy ID" (OAuth).
|
||||
|
||||
## Проверка в Authentik
|
||||
|
||||
В Authentik для Application должен быть настроен:
|
||||
- **Slug**: `open-webui`
|
||||
- **Redirect URI**: `https://odo.iieasy.ru/oauth/oidc/callback`
|
||||
- **Client ID** и **Client Secret** должны совпадать с `.env`
|
||||
86
FIX_OLLAMA_URL.md
Normal file
86
FIX_OLLAMA_URL.md
Normal file
@@ -0,0 +1,86 @@
|
||||
# Исправление проблемы с OLLAMA_BASE_URL
|
||||
|
||||
## Проблема
|
||||
В контейнере Open WebUI переменная `OLLAMA_BASE_URL=/ollama` вместо правильного значения `http://ollama:11434`.
|
||||
|
||||
Это приводит к тому, что изображения не передаются в Ollama, так как используется неправильный URL.
|
||||
|
||||
## Решение
|
||||
|
||||
### Шаг 1: Убедитесь, что в docker-compose.yml правильное значение
|
||||
|
||||
В файле `/home/its/iiEasyWeb/docker-compose.yml` на строке 102 должно быть:
|
||||
```yaml
|
||||
- OLLAMA_BASE_URL=http://ollama:11434
|
||||
```
|
||||
|
||||
### Шаг 2: Перезапустите контейнер Open WebUI
|
||||
|
||||
```bash
|
||||
cd /home/its/iiEasyWeb
|
||||
docker compose restart open-webui
|
||||
```
|
||||
|
||||
Или полностью пересоздайте:
|
||||
```bash
|
||||
docker compose up -d --force-recreate open-webui
|
||||
```
|
||||
|
||||
### Шаг 3: Проверьте переменную в контейнере
|
||||
|
||||
```bash
|
||||
sudo docker exec open-webui env | grep OLLAMA_BASE_URL
|
||||
```
|
||||
|
||||
Должно быть:
|
||||
```
|
||||
OLLAMA_BASE_URL=http://ollama:11434
|
||||
```
|
||||
|
||||
### Шаг 4: Проверьте настройки в веб-интерфейсе
|
||||
|
||||
1. Откройте https://odo.iieasy.ru
|
||||
2. Перейдите в **Settings → Connections → Ollama API**
|
||||
3. Убедитесь, что адрес: **`http://ollama:11434`**
|
||||
- НЕ `/ollama`
|
||||
- НЕ `host.docker.internal:11434`
|
||||
- НЕ `localhost:11434`
|
||||
- Должно быть именно `http://ollama:11434`
|
||||
|
||||
### Шаг 5: Сохраните настройки
|
||||
|
||||
После изменения адреса в веб-интерфейсе нажмите **"Сохранить"**.
|
||||
|
||||
### Шаг 6: Проверьте работу
|
||||
|
||||
1. Откройте чат с моделью `gemma3n:e4b-it-fp16`
|
||||
2. Загрузите изображение
|
||||
3. Задайте вопрос о изображении
|
||||
4. Проверьте логи Ollama - должны появиться запросы с изображениями:
|
||||
|
||||
```bash
|
||||
sudo docker logs ollama -f | grep -i "image\|vision\|generate"
|
||||
```
|
||||
|
||||
## Почему это важно
|
||||
|
||||
Open WebUI использует `OLLAMA_BASE_URL` для формирования полного URL к Ollama API. Если значение неправильное (`/ollama` вместо `http://ollama:11434`), запросы не будут доходить до Ollama, и изображения не будут обрабатываться.
|
||||
|
||||
## Дополнительная диагностика
|
||||
|
||||
Если после перезапуска проблема сохраняется:
|
||||
|
||||
1. Проверьте логи Open WebUI при отправке изображения:
|
||||
```bash
|
||||
sudo docker logs open-webui -f | grep -i "ollama\|image\|error"
|
||||
```
|
||||
|
||||
2. Проверьте сеть Docker:
|
||||
```bash
|
||||
sudo docker network inspect iieasy-ai | grep -A 5 ollama
|
||||
```
|
||||
|
||||
3. Проверьте доступность Ollama из Open WebUI:
|
||||
```bash
|
||||
sudo docker exec open-webui curl -s http://ollama:11434/api/tags | head -5
|
||||
```
|
||||
61
FIX_TRACE_ERROR.md
Normal file
61
FIX_TRACE_ERROR.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# Исправление ошибки NameError: name 'trace' is not defined
|
||||
|
||||
## Проблема
|
||||
|
||||
В логах Open WebUI появляется ошибка:
|
||||
```
|
||||
NameError: name 'trace' is not defined
|
||||
```
|
||||
|
||||
Эта ошибка может возникать, если скрипт `rebrand.sh` случайно изменил код Python в контейнере.
|
||||
|
||||
## Решение
|
||||
|
||||
**1. Пересоздайте контейнер Open WebUI:**
|
||||
|
||||
```bash
|
||||
cd /home/its/iiEasyWeb
|
||||
sudo docker compose stop open-webui
|
||||
sudo docker compose rm -f open-webui
|
||||
sudo docker compose up -d open-webui
|
||||
```
|
||||
|
||||
Это создаст чистый контейнер без изменений в коде.
|
||||
|
||||
**2. Подождите 30-40 секунд** и проверьте статус:
|
||||
|
||||
```bash
|
||||
sudo docker compose ps open-webui
|
||||
```
|
||||
|
||||
**3. Проверьте логи на наличие ошибок:**
|
||||
|
||||
```bash
|
||||
sudo docker compose logs open-webui --tail 50
|
||||
```
|
||||
|
||||
**4. Если нужно применить ребрендинг:**
|
||||
|
||||
После пересоздания контейнера, если нужно применить логотипы, используйте:
|
||||
- **Рекомендуется**: Admin Panel Open WebUI (Settings → Appearance → Logo) - это сохраняется в базе данных
|
||||
- **Альтернатива**: Запустите обновленный скрипт `rebrand.sh` (он был исправлен и больше не должен ломать код)
|
||||
|
||||
## Проверка OAuth
|
||||
|
||||
После пересоздания контейнера проверьте OAuth:
|
||||
|
||||
1. Убедитесь, что в `.env` правильный slug:
|
||||
```bash
|
||||
grep OPENID_CONNECT_ISSUER .env
|
||||
```
|
||||
Должно быть: `OPENID_CONNECT_ISSUER=https://auth.iieasy.ru/application/o/ii-easy-web/`
|
||||
|
||||
2. Проверьте endpoint Authentik:
|
||||
```bash
|
||||
curl https://auth.iieasy.ru/application/o/ii-easy-web/.well-known/openid-configuration
|
||||
```
|
||||
Должен вернуться JSON, а не HTML "Not Found"
|
||||
|
||||
3. Если endpoint возвращает "Not Found", проверьте в Authentik:
|
||||
- Application с slug `ii-easy-web` существует
|
||||
- Redirect URI настроен: `https://odo.iieasy.ru/oauth/oidc/callback`
|
||||
61
LOGO_FIX.md
Normal file
61
LOGO_FIX.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# Решение проблемы с возвратом логотипов после перезапуска
|
||||
|
||||
## Проблема
|
||||
|
||||
После перезапуска контейнера Open WebUI логотипы возвращаются к исходным, так как файлы, скопированные через `docker cp`, теряются при пересоздании контейнера.
|
||||
|
||||
## Решение 1: Через Admin Panel (РЕКОМЕНДУЕТСЯ - постоянное решение)
|
||||
|
||||
Это самый надежный способ, так как настройки сохраняются в базе данных и не теряются при перезапуске.
|
||||
|
||||
1. Откройте Open WebUI: `https://odo.iieasy.ru` или `http://localhost:3001`
|
||||
2. Войдите как администратор
|
||||
3. Перейдите в **Settings** → **Appearance** (или **Admin** → **Settings** → **Appearance**)
|
||||
4. Найдите секцию **Logo** или **Branding**
|
||||
5. Загрузите файлы:
|
||||
- **Logo**: загрузите `media/logo.png`
|
||||
- **Favicon**: загрузите `media/favicon.png`
|
||||
6. Сохраните изменения
|
||||
|
||||
**Преимущества:**
|
||||
- Настройки сохраняются в базе данных
|
||||
- Не теряются при перезапуске контейнера
|
||||
- Работает надежно
|
||||
|
||||
## Решение 2: Автоматический скрипт после каждого перезапуска
|
||||
|
||||
Создан скрипт `scripts/apply_logos_persistent.sh`, который можно запускать после каждого перезапуска:
|
||||
|
||||
```bash
|
||||
cd /home/its/iiEasyWeb
|
||||
sudo ./scripts/apply_logos_persistent.sh
|
||||
```
|
||||
|
||||
Или добавьте в cron для автоматического запуска:
|
||||
|
||||
```bash
|
||||
# Добавьте в crontab
|
||||
crontab -e
|
||||
|
||||
# Добавьте строку (запуск каждые 5 минут, если контейнер запущен)
|
||||
*/5 * * * * cd /home/its/iiEasyWeb && docker ps | grep -q open-webui && ./scripts/apply_logos_persistent.sh
|
||||
```
|
||||
|
||||
## Решение 3: Использование systemd timer (для автоматизации)
|
||||
|
||||
Создайте systemd timer для автоматического применения логотипов после перезапуска контейнера.
|
||||
|
||||
## Временное решение (быстрое применение)
|
||||
|
||||
Если логотипы вернулись прямо сейчас:
|
||||
|
||||
```bash
|
||||
cd /home/its/iiEasyWeb
|
||||
sudo ./scripts/rebrand.sh
|
||||
```
|
||||
|
||||
Но помните - они вернутся после следующего перезапуска контейнера.
|
||||
|
||||
## Рекомендация
|
||||
|
||||
**Используйте Решение 1 (Admin Panel)** - это единственный способ, который гарантирует, что логотипы не вернутся после перезапуска.
|
||||
109
LOGO_SETUP.md
Normal file
109
LOGO_SETUP.md
Normal file
@@ -0,0 +1,109 @@
|
||||
# Инструкция по настройке логотипов и favicon в Open WebUI
|
||||
|
||||
## Проблема: Логотипы не меняются
|
||||
|
||||
Open WebUI может использовать скомпилированные статические файлы или кешировать логотипы. Есть несколько способов решения.
|
||||
|
||||
## Способ 1: Через Admin Panel (рекомендуется)
|
||||
|
||||
Это самый надежный способ, так как настройки сохраняются в базе данных.
|
||||
|
||||
1. Откройте Open WebUI: `https://odo.iieasy.ru` или `http://localhost:3001`
|
||||
2. Войдите как администратор
|
||||
3. Перейдите в **Settings** → **Appearance** (или **Admin** → **Settings** → **Appearance`)
|
||||
4. Найдите секцию **Logo** или **Branding**
|
||||
5. Загрузите ваши файлы:
|
||||
- **Logo**: `media/logo.png` или `media/logo-light.svg`
|
||||
- **Favicon**: `media/favicon.png` или `media/favicon.ico`
|
||||
6. Сохраните изменения
|
||||
|
||||
## Способ 2: Через скрипт ребрендинга + перезапуск
|
||||
|
||||
1. Убедитесь, что файлы есть в папке `media/`:
|
||||
```bash
|
||||
ls -la media/logo* media/favicon*
|
||||
```
|
||||
|
||||
2. Запустите скрипт ребрендинга:
|
||||
```bash
|
||||
cd /home/its/iiEasyWeb
|
||||
sudo ./scripts/rebrand.sh
|
||||
```
|
||||
|
||||
3. **ВАЖНО:** Перезапустите контейнер для применения изменений:
|
||||
```bash
|
||||
sudo docker compose restart open-webui
|
||||
```
|
||||
|
||||
4. Очистите кеш браузера:
|
||||
- **Chrome/Edge**: `Ctrl+Shift+Delete` → Очистить кеш изображений
|
||||
- **Firefox**: `Ctrl+Shift+Delete` → Кеш
|
||||
- Или используйте режим инкогнито: `Ctrl+Shift+N`
|
||||
|
||||
5. Обновите страницу с принудительной перезагрузкой: `Ctrl+F5` или `Ctrl+Shift+R`
|
||||
|
||||
## Способ 3: Прямое копирование в контейнер
|
||||
|
||||
Если скрипт не работает, скопируйте файлы вручную:
|
||||
|
||||
```bash
|
||||
# Найдите где находятся favicon файлы
|
||||
docker exec open-webui find /app -name "favicon.png" -o -name "favicon.ico" | head -5
|
||||
|
||||
# Скопируйте ваши файлы
|
||||
docker cp media/favicon.png open-webui:/app/web/build/_app/immutable/favicon.png
|
||||
docker cp media/logo.png open-webui:/app/web/build/_app/immutable/logo.png
|
||||
|
||||
# Перезапустите контейнер
|
||||
docker compose restart open-webui
|
||||
```
|
||||
|
||||
## Способ 4: Использование монтированного volume
|
||||
|
||||
Файлы уже смонтированы в контейнер через volume:
|
||||
- `./media:/app/media:ro`
|
||||
- `./media:/app/web/static/custom:ro`
|
||||
|
||||
Можно использовать эти пути в настройках Open WebUI или скопировать оттуда:
|
||||
|
||||
```bash
|
||||
# Скопировать из смонтированной папки в нужное место
|
||||
docker exec open-webui cp /app/media/logo.png /app/web/build/_app/immutable/logo.png
|
||||
docker exec open-webui cp /app/media/favicon.png /app/web/build/_app/immutable/favicon.png
|
||||
```
|
||||
|
||||
## Проверка
|
||||
|
||||
После применения изменений:
|
||||
|
||||
1. Откройте браузер в режиме инкогнито
|
||||
2. Откройте `https://odo.iieasy.ru` или `http://localhost:3001`
|
||||
3. Проверьте favicon во вкладке браузера
|
||||
4. Проверьте логотип на странице
|
||||
|
||||
## Если ничего не помогает
|
||||
|
||||
1. Проверьте в браузере (F12 → Network), какие файлы запрашиваются:
|
||||
- Откройте вкладку Network
|
||||
- Обновите страницу
|
||||
- Найдите запросы к `favicon.*` или `logo.*`
|
||||
- Посмотрите полный URL запроса
|
||||
|
||||
2. Найдите эти файлы в контейнере и замените их:
|
||||
```bash
|
||||
docker exec open-webui find /app -path "*/favicon*" -o -path "*/logo*" | grep -v node_modules
|
||||
```
|
||||
|
||||
3. Используйте Admin Panel Open WebUI - это самый надежный способ.
|
||||
|
||||
## Настройка через переменные окружения (если поддерживается)
|
||||
|
||||
Некоторые версии Open WebUI поддерживают переменные окружения для логотипов. Добавьте в `docker-compose.yml`:
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
- CUSTOM_LOGO_URL=/static/custom/logo.png
|
||||
- CUSTOM_FAVICON_URL=/static/custom/favicon.ico
|
||||
```
|
||||
|
||||
Но лучше использовать Admin Panel, так как настройки сохраняются в базе данных.
|
||||
70
QUICK_FIX.md
Normal file
70
QUICK_FIX.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# Быстрое исправление Authentik
|
||||
|
||||
## Проблема: Internal Server Error после перезапуска
|
||||
|
||||
## Решение
|
||||
|
||||
**1. Убедитесь, что .env содержит правильный slug:**
|
||||
|
||||
```bash
|
||||
cd /home/its/iiEasyWeb
|
||||
grep OPENID_CONNECT_ISSUER .env
|
||||
```
|
||||
|
||||
Должно быть:
|
||||
```
|
||||
OPENID_CONNECT_ISSUER=https://auth.iieasy.ru/application/o/ii-easy-web/
|
||||
```
|
||||
|
||||
**2. Перезапустите контейнер:**
|
||||
|
||||
```bash
|
||||
cd /home/its/iiEasyWeb
|
||||
sudo docker compose restart open-webui
|
||||
```
|
||||
|
||||
**3. Подождите 20-30 секунд и проверьте логи:**
|
||||
|
||||
```bash
|
||||
sudo docker compose logs open-webui --tail 30 | grep -i "oauth\|oidc\|error"
|
||||
```
|
||||
|
||||
**4. Проверьте redirect URI в Authentik:**
|
||||
|
||||
В Authentik для Application `ii-easy-web` должен быть настроен redirect URI:
|
||||
```
|
||||
https://odo.iieasy.ru/oauth/oidc/callback
|
||||
```
|
||||
|
||||
**ВАЖНО:**
|
||||
- URL должен быть точно таким (без порта, с https)
|
||||
- Должен заканчиваться на `/oauth/oidc/callback` (без завершающего слеша)
|
||||
|
||||
**5. Если все еще не работает:**
|
||||
|
||||
Временно используйте форму входа (уже включена в docker-compose.yml):
|
||||
- Откройте `https://odo.iieasy.ru`
|
||||
- Войдите через форму входа (не через Authentik)
|
||||
- После настройки Authentik можно будет переключиться обратно
|
||||
|
||||
## Проверка конфигурации
|
||||
|
||||
```bash
|
||||
# Проверьте endpoint Authentik
|
||||
curl https://auth.iieasy.ru/application/o/ii-easy-web/.well-known/openid-configuration
|
||||
|
||||
# Должен вернуться JSON с issuer и endpoints
|
||||
```
|
||||
|
||||
## Если нужно отключить Authentik временно
|
||||
|
||||
В `docker-compose.yml` измените:
|
||||
```yaml
|
||||
- ENABLE_OAUTH_SIGNUP=false
|
||||
- ENABLE_LOGIN_FORM=true
|
||||
```
|
||||
|
||||
Затем перезапустите:
|
||||
```bash
|
||||
sudo docker compose restart open-webui
|
||||
```
|
||||
58
QUICK_START.md
Normal file
58
QUICK_START.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# 🚀 Быстрый старт - Ребрендинг iiEasy
|
||||
|
||||
## ⚡ Быстрое использование
|
||||
|
||||
```bash
|
||||
cd /home/its/iiEasyWeb
|
||||
sudo ./scripts/rebrand_safe_final.sh
|
||||
```
|
||||
|
||||
## 📁 Требуемые файлы
|
||||
|
||||
Поместите в папку `media/`:
|
||||
- ✅ `logo.png` - **обязательно** (основной логотип)
|
||||
- ⚪ `favicon.png` - опционально (если нет, используется logo.png)
|
||||
- ⚪ `logo-dark.svg` - опционально (для темной темы)
|
||||
- ⚪ `logo-light.svg` - опционально (для светлой темы)
|
||||
|
||||
## ✅ Что делает скрипт
|
||||
|
||||
1. **Заменяет все логотипы и иконки:**
|
||||
- `logo.png`, `favicon.png`, `favicon.ico`
|
||||
- `favicon-dark.png`, `apple-touch-icon.png`
|
||||
- `splash-dark.png`, `splash.png`
|
||||
|
||||
2. **Исправляет ссылки в шаблонах:**
|
||||
- Все ссылки на favicon → ваш логотип
|
||||
- Все ссылки на splash → ваш логотип
|
||||
|
||||
3. **Удаляет упоминания "Open WebUI":**
|
||||
- Текст "(Open WebUI)" из интерфейса
|
||||
- Ссылки на документацию → `note.iieasy.ru`
|
||||
|
||||
4. **Удаляет элементы интерфейса:**
|
||||
- Кнопку "Проверить обновления"
|
||||
- Социальные сети (Discord, Twitter, GitHub)
|
||||
- Блок "Лицензия"
|
||||
|
||||
## 🔄 После выполнения
|
||||
|
||||
1. **Очистите кеш браузера:** `Ctrl+Shift+Delete`
|
||||
2. **Проверьте:** `https://odo.iieasy.ru` или `http://localhost:3001`
|
||||
|
||||
## 🔄 После обновления Open WebUI
|
||||
|
||||
```bash
|
||||
sudo docker compose pull
|
||||
sudo docker compose up -d
|
||||
sudo ./scripts/rebrand_safe_final.sh
|
||||
```
|
||||
|
||||
## 🐛 Проблемы?
|
||||
|
||||
- **Логотип не изменился?** → Очистите кеш браузера
|
||||
- **Контейнер не запускается?** → `sudo docker compose restart open-webui`
|
||||
- **Нужна помощь?** → См. `REBRAND_SOLUTION.md`
|
||||
|
||||
---
|
||||
**Полная документация:** `REBRAND_SOLUTION.md`
|
||||
294
README.md
Normal file
294
README.md
Normal file
@@ -0,0 +1,294 @@
|
||||
# iiEasy AI-платформа
|
||||
|
||||
Корпоративная AI-платформа на базе Open WebUI с интеграцией Ollama, Qdrant, SearXNG и синхронизацией Nextcloud.
|
||||
|
||||
## Архитектура
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Reverse Proxy (Nginx) │
|
||||
│ *.iieasy.ru (odo.iieasy.ru) │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌───────────────────┼───────────────────┐
|
||||
│ │ │
|
||||
┌───────▼────────┐ ┌───────▼────────┐ ┌───────▼────────┐
|
||||
│ Open WebUI │ │ Authentik │ │ Nextcloud │
|
||||
│ odo.iieasy.ru │ │ auth.iieasy.ru │ │next.iieasy.ru │
|
||||
└───────┬────────┘ └─────────────────┘ └───────┬────────┘
|
||||
│ │
|
||||
│ ┌──────────────────────────────────────┘
|
||||
│ │
|
||||
▼ ▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Docker Network (iieasy-ai) │
|
||||
│ │
|
||||
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
||||
│ │ Ollama │ │ Qdrant │ │ SearXNG │ │
|
||||
│ │ (GPU) │ │ (Vector) │ │ (Search) │ │
|
||||
│ └──────────┘ └──────────┘ └──────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌───────────────┐
|
||||
│ Python Worker │
|
||||
│ (Nextcloud → │
|
||||
│ Qdrant Sync) │
|
||||
└───────────────┘
|
||||
```
|
||||
|
||||
## Компоненты
|
||||
|
||||
- **Open WebUI** - Веб-интерфейс для работы с AI моделями
|
||||
- **Ollama** - Локальный запуск LLM моделей (Gemma 3)
|
||||
- **Qdrant** - Векторная база данных для RAG
|
||||
- **SearXNG** - Метапоисковая система для веб-поиска
|
||||
- **Authentik** - Централизованный SSO (OIDC)
|
||||
- **Nextcloud Sync Worker** - Автоматическая синхронизация документов из Nextcloud
|
||||
|
||||
## Быстрый старт
|
||||
|
||||
### Предварительные требования
|
||||
|
||||
- Docker и Docker Compose
|
||||
- NVIDIA GPU с драйверами (для Ollama)
|
||||
- Reverse proxy (Nginx) настроенный для доменов *.iieasy.ru
|
||||
- Authentik настроенный и доступный на auth.iieasy.ru
|
||||
|
||||
### 1. Клонирование и настройка
|
||||
|
||||
```bash
|
||||
cd /home/its/iiEasyWeb
|
||||
cp .env.example .env
|
||||
# Отредактируйте .env и заполните все необходимые переменные
|
||||
```
|
||||
|
||||
### 2. Генерация API ключей
|
||||
|
||||
```bash
|
||||
# Генерация QDRANT_API_KEY
|
||||
openssl rand -hex 32
|
||||
# Добавьте результат в .env
|
||||
```
|
||||
|
||||
### 3. Запуск инфраструктуры
|
||||
|
||||
```bash
|
||||
# Запуск всех сервисов
|
||||
docker-compose up -d
|
||||
|
||||
# Проверка статуса
|
||||
docker-compose ps
|
||||
|
||||
# Просмотр логов
|
||||
docker-compose logs -f
|
||||
```
|
||||
|
||||
### 4. Загрузка модели Ollama
|
||||
|
||||
```bash
|
||||
# Загрузка модели Gemma 3
|
||||
docker exec ollama ollama pull gemma3n:e4b-it-fp16
|
||||
|
||||
# Проверка загруженных моделей
|
||||
docker exec ollama ollama list
|
||||
```
|
||||
|
||||
### 5. Настройка Authentik
|
||||
|
||||
1. Войдите в Authentik (https://auth.iieasy.ru)
|
||||
2. Создайте OIDC Provider:
|
||||
- Redirect URI: `https://odo.iieasy.ru/oauth/oidc/callback`
|
||||
- Client ID и Client Secret скопируйте в `.env`
|
||||
3. Обновите `OPENID_CONNECT_ISSUER` в `.env`
|
||||
|
||||
### 6. Ребрендинг Open WebUI
|
||||
|
||||
После первого запуска Open WebUI выполните:
|
||||
|
||||
```bash
|
||||
./scripts/rebrand.sh
|
||||
```
|
||||
|
||||
Скрипт заменит:
|
||||
- Логотипы и favicon
|
||||
- Текстовые упоминания "Open WebUI" → "iiEasyWeb"
|
||||
- Отключит проверку обновлений
|
||||
- Удалит аналитику и телеметрию
|
||||
|
||||
### 7. Настройка API ключа Open WebUI
|
||||
|
||||
1. Откройте https://odo.iieasy.ru
|
||||
2. Войдите через Authentik SSO
|
||||
3. Перейдите в Settings → Account → API Keys
|
||||
4. Создайте новый API ключ
|
||||
5. Добавьте ключ в `.env` как `OPENWEBUI_API_KEY`
|
||||
|
||||
### 8. Запуск воркера синхронизации Nextcloud
|
||||
|
||||
```bash
|
||||
cd worker
|
||||
|
||||
# Установка зависимостей
|
||||
pip install -r requirements.txt
|
||||
|
||||
# Настройка переменных окружения
|
||||
cp .env.example .env
|
||||
# Отредактируйте .env
|
||||
|
||||
# Запуск однократной синхронизации
|
||||
python nextcloud_sync.py --once
|
||||
|
||||
# Или запуск в режиме daemon
|
||||
python nextcloud_sync.py --daemon
|
||||
```
|
||||
|
||||
Для production рекомендуется использовать systemd:
|
||||
|
||||
```bash
|
||||
# Создайте /etc/systemd/system/iieasy-sync.service
|
||||
sudo nano /etc/systemd/system/iieasy-sync.service
|
||||
```
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=iiEasy Nextcloud Sync Worker
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=its
|
||||
WorkingDirectory=/home/its/iiEasyWeb/worker
|
||||
ExecStart=/usr/bin/python3 /home/its/iiEasyWeb/worker/nextcloud_sync.py --daemon
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
```bash
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable iieasy-sync
|
||||
sudo systemctl start iieasy-sync
|
||||
```
|
||||
|
||||
## Переменные окружения
|
||||
|
||||
### Основной .env
|
||||
|
||||
См. `.env.example` для полного списка переменных. Основные:
|
||||
|
||||
- `DOMAIN_OPENWEBUI` - URL Open WebUI
|
||||
- `DOMAIN_NEXTCLOUD` - URL Nextcloud
|
||||
- `DOMAIN_AUTHENTIK` - URL Authentik
|
||||
- `OAUTH_CLIENT_ID` - Client ID из Authentik
|
||||
- `OAUTH_CLIENT_SECRET` - Client Secret из Authentik
|
||||
- `OPENID_CONNECT_ISSUER` - Issuer URL Authentik
|
||||
- `QDRANT_API_KEY` - API ключ Qdrant (сгенерировать)
|
||||
- `OPENWEBUI_API_KEY` - API ключ Open WebUI (создать после первого запуска)
|
||||
|
||||
### worker/.env
|
||||
|
||||
- `NC_USER` - Пользователь Nextcloud
|
||||
- `NC_APP_PASSWORD` - App Password (не основной пароль!)
|
||||
- `NC_SCAN_PATHS` - Пути для сканирования
|
||||
- `OPENWEBUI_API_KEY` - API ключ Open WebUI
|
||||
|
||||
## Структура проекта
|
||||
|
||||
```
|
||||
iiEasyWeb/
|
||||
├── docker-compose.yml # Основной compose файл
|
||||
├── .env # Переменные окружения
|
||||
├── .env.example # Шаблон переменных
|
||||
├── .gitignore # Игнорируемые файлы
|
||||
│
|
||||
├── scripts/
|
||||
│ └── rebrand_safe_final.sh # ✅ Рекомендуемый скрипт ребрендинга (безопасный)
|
||||
│ ├── rebrand.sh # ⚠️ Старый скрипт (может ломать OAuth)
|
||||
│ └── rebrand_fast.sh # ⚠️ Быстрый скрипт (может ломать функциональность)
|
||||
│
|
||||
├── media/
|
||||
│ ├── logo-light.svg # Логотип светлая тема
|
||||
│ ├── logo-dark.svg # Логотип темная тема
|
||||
│ └── favicon.svg # Favicon
|
||||
│
|
||||
├── worker/
|
||||
│ ├── nextcloud_sync.py # Главный скрипт воркера
|
||||
│ ├── config.py # Конфигурация
|
||||
│ ├── nextcloud_client.py # WebDAV клиент
|
||||
│ ├── openwebui_client.py # Open WebUI API клиент
|
||||
│ ├── document_processor.py # Обработка документов
|
||||
│ ├── requirements.txt # Python зависимости
|
||||
│ └── .env.example # Шаблон для воркера
|
||||
│
|
||||
└── README.md # Эта документация
|
||||
```
|
||||
|
||||
## Сетевая архитектура
|
||||
|
||||
Все сервисы работают в Docker сети `iieasy-ai` и доступны только внутри сети, кроме Open WebUI, который доступен через reverse proxy.
|
||||
|
||||
### Порты
|
||||
|
||||
- **Open WebUI**: 3001 (внутренний) → Nginx → 443 (HTTPS)
|
||||
- **Qdrant**: 6333 (gRPC), 6334 (HTTP) - только внутри сети
|
||||
- **SearXNG**: 8080 - только внутри сети
|
||||
- **Ollama**: 11434 - только внутри сети
|
||||
|
||||
### Безопасность
|
||||
|
||||
- Все сервисы изолированы в Docker сети
|
||||
- Доступ к Qdrant только через API ключ
|
||||
- Open WebUI использует Authentik SSO для аутентификации
|
||||
- Воркер использует App Password для Nextcloud (не основной пароль)
|
||||
- CrowdSec и OPNsense фильтруют трафик на уровне reverse proxy
|
||||
|
||||
## Поддерживаемые форматы файлов
|
||||
|
||||
Воркер синхронизации поддерживает:
|
||||
|
||||
- **PDF** (.pdf) - извлечение текста через pypdf
|
||||
- **DOCX** (.docx, .doc) - извлечение текста через python-docx
|
||||
- **Текстовые** (.txt, .md, .markdown) - прямое чтение
|
||||
- **CSV** (.csv) - конвертация в текстовый формат
|
||||
|
||||
Файлы больше 100MB обрабатываются потоково с ограничением количества страниц.
|
||||
|
||||
## Устранение неполадок
|
||||
|
||||
### Open WebUI не подключается к Qdrant
|
||||
|
||||
1. Проверьте, что Qdrant запущен: `docker-compose ps qdrant`
|
||||
2. Проверьте логи: `docker-compose logs qdrant`
|
||||
3. Убедитесь, что `QDRANT_API_KEY` установлен в `.env`
|
||||
4. Проверьте переменную `QDRANT_URI=http://qdrant:6333`
|
||||
|
||||
### Ошибки аутентификации через Authentik
|
||||
|
||||
1. Проверьте redirect URI в Authentik: `https://odo.iieasy.ru/oauth/oidc/callback`
|
||||
2. Убедитесь, что `OPENID_CONNECT_ISSUER` правильный
|
||||
3. Проверьте логи Open WebUI: `docker-compose logs open-webui`
|
||||
|
||||
### Воркер не синхронизирует файлы
|
||||
|
||||
1. Проверьте логи: `tail -f worker/sync.log`
|
||||
2. Убедитесь, что `OPENWEBUI_API_KEY` правильный
|
||||
3. Проверьте доступность Nextcloud: `curl https://next.iieasy.ru`
|
||||
4. Проверьте права доступа к путям в `NC_SCAN_PATHS`
|
||||
|
||||
### Ollama не использует GPU
|
||||
|
||||
1. Проверьте драйверы NVIDIA: `nvidia-smi`
|
||||
2. Установите nvidia-container-toolkit
|
||||
3. Перезапустите Docker: `sudo systemctl restart docker`
|
||||
4. Проверьте переменную `NVIDIA_VISIBLE_DEVICES` в `.env`
|
||||
|
||||
## Лицензия
|
||||
|
||||
Внутренний проект iiEasy Research Center.
|
||||
|
||||
## Поддержка
|
||||
|
||||
Для вопросов и проблем обращайтесь к команде разработки iiEasy.
|
||||
94
REBRANDING.md
Normal file
94
REBRANDING.md
Normal file
@@ -0,0 +1,94 @@
|
||||
# Ребрендинг Open WebUI для iiEasy
|
||||
|
||||
## ✅ Рекомендуемый скрипт: `rebrand_safe_final.sh`
|
||||
|
||||
**Используйте ТОЛЬКО этот скрипт для ребрендинга!**
|
||||
|
||||
```bash
|
||||
cd /home/its/iiEasyWeb
|
||||
sudo ./scripts/rebrand_safe_final.sh
|
||||
```
|
||||
|
||||
## Что делает скрипт
|
||||
|
||||
1. ✅ **Заменяет логотипы и favicon:**
|
||||
- Копирует `logo.png` и `favicon.png` из папки `media/`
|
||||
- Заменяет `splash.png` на ваш логотип
|
||||
- Заменяет все существующие логотипы везде
|
||||
|
||||
2. ✅ **Удаляет "(Open WebUI)" из интерфейса:**
|
||||
- Ищет и удаляет только в HTML/Svelte/Python шаблонах
|
||||
- НЕ трогает JavaScript/TypeScript код - не ломает функциональность
|
||||
|
||||
3. ✅ **Исправляет favicon.png на logo.png:**
|
||||
- Заменяет `/static/favicon.png` на `/static/logo.png` в шаблонах
|
||||
- Исправляет API endpoint для изображения профиля модели
|
||||
|
||||
4. ✅ **Заменяет ссылки на документацию:**
|
||||
- `docs.openwebui.com` → `note.iieasy.ru`
|
||||
- `open-webui.com/docs` → `note.iieasy.ru`
|
||||
|
||||
5. ✅ **Удаляет проверку обновлений:**
|
||||
- Кнопка "Проверить обновления"
|
||||
- Ссылки на GitHub releases
|
||||
- Текст "(последняя)" и "Посмотреть, что нового"
|
||||
|
||||
6. ✅ **Удаляет социальные сети и GitHub:**
|
||||
- Discord, Twitter/X, GitHub Repo
|
||||
- Весь блок "Помощь" с соцсетями
|
||||
- Badges (img.shields.io)
|
||||
|
||||
7. ✅ **Удаляет блок лицензии:**
|
||||
- Полностью удаляет блок "Лицензия"
|
||||
- Удаляет ссылки на enterprise план
|
||||
|
||||
## Почему этот скрипт безопасен
|
||||
|
||||
- ✅ Обрабатывает только HTML/Svelte/Python шаблоны
|
||||
- ✅ НЕ трогает JavaScript/TypeScript код
|
||||
- ✅ НЕ комментирует импорты
|
||||
- ✅ НЕ изменяет конфигурацию OAuth
|
||||
- ✅ Не ломает функциональность (проверено!)
|
||||
|
||||
## После запуска
|
||||
|
||||
1. **Очистите кеш браузера:**
|
||||
- Chrome/Edge: Ctrl+Shift+Delete (Cmd+Shift+Delete на Mac)
|
||||
- Выберите "Изображения и файлы в кеше"
|
||||
- Очистите кеш
|
||||
|
||||
2. **Проверьте результат:**
|
||||
- Откройте `https://odo.iieasy.ru`
|
||||
- Должно быть "Войти в iiEasyWeb" (без "(Open WebUI)")
|
||||
- Логотип должен отображаться правильно
|
||||
- Не должно быть ссылок на соцсети и лицензию
|
||||
|
||||
## Если что-то не работает
|
||||
|
||||
**Восстановите контейнер:**
|
||||
|
||||
```bash
|
||||
cd /home/its/iiEasyWeb
|
||||
sudo docker compose stop open-webui
|
||||
sudo docker compose rm -f open-webui
|
||||
sudo docker compose up -d open-webui
|
||||
sleep 30
|
||||
sudo ./scripts/rebrand_safe_final.sh
|
||||
```
|
||||
|
||||
## Другие скрипты (НЕ используйте!)
|
||||
|
||||
- ❌ `rebrand.sh` - может ломать OAuth
|
||||
- ❌ `rebrand_fast.sh` - может ломать функциональность (500 ошибка)
|
||||
- ❌ `rebrand_complete.sh` - может ломать функциональность
|
||||
- ❌ `rebrand_full.sh` - может ломать функциональность
|
||||
|
||||
**Используйте ТОЛЬКО `rebrand_safe_final.sh`!**
|
||||
|
||||
## Файлы логотипов
|
||||
|
||||
Убедитесь, что в папке `media/` есть:
|
||||
- `logo.png` - основной логотип
|
||||
- `favicon.png` - favicon
|
||||
|
||||
Эти файлы будут использоваться для ребрендинга.
|
||||
57
REBRAND_FIX.md
Normal file
57
REBRAND_FIX.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# Исправление скрипта rebrand.sh для защиты OAuth/Authentik
|
||||
|
||||
## Проблема
|
||||
|
||||
Скрипт `rebrand.sh` заменял `open-webui` на `iieasyweb` во ВСЕХ файлах, включая файлы OAuth/Authentik. Это ломало конфигурацию OAuth, так как:
|
||||
|
||||
1. URL типа `https://auth.iieasy.ru/application/o/open-webui/` заменялись на `https://auth.iieasy.ru/application/o/iieasyweb/`
|
||||
2. Переменные окружения и конфигурационные строки с `open-webui` могли быть повреждены
|
||||
|
||||
## Решение
|
||||
|
||||
Скрипт `rebrand.sh` был исправлен:
|
||||
|
||||
1. **Исключены файлы OAuth/Authentik из обработки:**
|
||||
- Добавлены фильтры `! -path "*/oauth*" ! -path "*/oidc*" ! -path "*authentik*" ! -path "*openid*"`
|
||||
- Это защищает все файлы, связанные с OAuth, от изменений
|
||||
|
||||
2. **Удалена замена `open-webui` на `iieasyweb`:**
|
||||
- Комментированы строки, которые заменяли `open-webui` и `openwebui` в нижнем регистре
|
||||
- Это предотвращает случайную замену URL и конфигурации
|
||||
|
||||
3. **Оставлена только замена текста интерфейса:**
|
||||
- Заменяется только "Open WebUI" (с заглавными буквами) на "iiEasyWeb"
|
||||
- Это безопасно и не влияет на конфигурацию
|
||||
|
||||
## Использование
|
||||
|
||||
Теперь скрипт можно безопасно запускать:
|
||||
|
||||
```bash
|
||||
cd /home/its/iiEasyWeb
|
||||
sudo ./scripts/rebrand.sh
|
||||
```
|
||||
|
||||
OAuth/Authentik конфигурация будет защищена от изменений.
|
||||
|
||||
## Если OAuth все еще не работает
|
||||
|
||||
Если после запуска скрипта OAuth перестал работать:
|
||||
|
||||
1. **Пересоздайте контейнер:**
|
||||
```bash
|
||||
sudo docker compose stop open-webui
|
||||
sudo docker compose rm -f open-webui
|
||||
sudo docker compose up -d open-webui
|
||||
```
|
||||
|
||||
2. **Проверьте конфигурацию:**
|
||||
```bash
|
||||
grep OPENID_CONNECT_ISSUER .env
|
||||
```
|
||||
Должно быть: `OPENID_CONNECT_ISSUER=https://auth.iieasy.ru/application/o/open-webui/`
|
||||
|
||||
3. **Перезапустите контейнер:**
|
||||
```bash
|
||||
sudo docker compose restart open-webui
|
||||
```
|
||||
89
REBRAND_OAUTH_FIX.md
Normal file
89
REBRAND_OAUTH_FIX.md
Normal file
@@ -0,0 +1,89 @@
|
||||
# Исправление проблемы с OAuth после rebrand.sh
|
||||
|
||||
## Проблема
|
||||
|
||||
После запуска `rebrand.sh` OAuth перестает работать и выкидывает на страницу авторизации. Без rebrand все работает нормально.
|
||||
|
||||
## Причина
|
||||
|
||||
Скрипт `rebrand.sh` изменяет файлы Python/JS, включая файлы, связанные с OAuth/аутентификацией, что ломает конфигурацию OAuth.
|
||||
|
||||
## Решение
|
||||
|
||||
### Вариант 1: Использовать безопасный скрипт (РЕКОМЕНДУЕТСЯ)
|
||||
|
||||
Используйте новый скрипт `rebrand_safe.sh`, который изменяет ТОЛЬКО логотипы и favicon:
|
||||
|
||||
```bash
|
||||
cd /home/its/iiEasyWeb
|
||||
sudo ./scripts/rebrand_safe.sh
|
||||
```
|
||||
|
||||
Этот скрипт:
|
||||
- ✅ Изменяет только логотипы и favicon
|
||||
- ✅ НЕ изменяет код Python/JS
|
||||
- ✅ НЕ влияет на OAuth/Authentik
|
||||
|
||||
### Вариант 2: Использовать Admin Panel (НАИБОЛЕЕ БЕЗОПАСНО)
|
||||
|
||||
Для постоянных изменений используйте Admin Panel Open WebUI:
|
||||
|
||||
1. Откройте `https://odo.iieasy.ru`
|
||||
2. Войдите как администратор
|
||||
3. Перейдите в **Settings → Appearance → Logo**
|
||||
4. Загрузите логотипы и favicon из папки `media/`
|
||||
5. Сохраните - настройки сохранятся в базе данных
|
||||
|
||||
Это самый безопасный способ, так как изменения сохраняются в базе данных и не затрагивают код.
|
||||
|
||||
### Вариант 3: Восстановить контейнер после rebrand.sh
|
||||
|
||||
Если вы уже запустили `rebrand.sh` и OAuth сломался:
|
||||
|
||||
```bash
|
||||
cd /home/its/iiEasyWeb
|
||||
|
||||
# Пересоздайте контейнер с чистой версией
|
||||
sudo docker compose stop open-webui
|
||||
sudo docker compose rm -f open-webui
|
||||
sudo docker compose up -d open-webui
|
||||
|
||||
# Подождите 30 секунд
|
||||
sleep 30
|
||||
|
||||
# Используйте безопасный скрипт для логотипов
|
||||
sudo ./scripts/rebrand_safe.sh
|
||||
```
|
||||
|
||||
## Почему rebrand.sh ломает OAuth?
|
||||
|
||||
Скрипт `rebrand.sh` пытается заменить текст "Open WebUI" во всех файлах, включая:
|
||||
- `/app/backend/open_webui/utils/oauth.py` - файлы OAuth
|
||||
- `/app/backend/open_webui/main.py` - может содержать OAuth логику
|
||||
- Другие файлы, связанные с аутентификацией
|
||||
|
||||
Даже с фильтрами исключения, некоторые файлы могут быть изменены, что ломает OAuth конфигурацию.
|
||||
|
||||
## Рекомендации
|
||||
|
||||
1. **Для логотипов**: Используйте `rebrand_safe.sh` или Admin Panel
|
||||
2. **Для текста интерфейса**: Используйте переменные окружения в `docker-compose.yml`:
|
||||
- `WEBUI_NAME=iiEasyWeb` (уже настроено)
|
||||
- `OAUTH_PROVIDER_NAME=iiEasy ID` (уже настроено)
|
||||
3. **Избегайте**: Изменения кода Python/JS через `sed` в работающем контейнере
|
||||
|
||||
## Проверка OAuth после восстановления
|
||||
|
||||
После восстановления контейнера проверьте:
|
||||
|
||||
```bash
|
||||
# Проверьте логи
|
||||
sudo docker compose logs open-webui --tail 50 | grep -i "oauth\|error"
|
||||
|
||||
# Проверьте конфигурацию
|
||||
grep OPENID_CONNECT_ISSUER .env
|
||||
# Должно быть: OPENID_CONNECT_ISSUER=https://auth.iieasy.ru/application/o/open-webui/
|
||||
|
||||
# Проверьте OAuth в браузере
|
||||
# Откройте https://odo.iieasy.ru и нажмите "iiEasy ID"
|
||||
```
|
||||
258
REBRAND_SOLUTION.md
Normal file
258
REBRAND_SOLUTION.md
Normal file
@@ -0,0 +1,258 @@
|
||||
# Рабочее решение ребрендинга Open WebUI для iiEasy
|
||||
|
||||
## 📋 Обзор
|
||||
|
||||
Скрипт `rebrand_safe_final.sh` - это **безопасное и полное решение** для ребрендинга Open WebUI, которое заменяет все логотипы, иконки и удаляет упоминания "Open WebUI" без нарушения функциональности приложения.
|
||||
|
||||
## ✅ Что делает скрипт
|
||||
|
||||
### 1. Замена логотипов и иконок
|
||||
|
||||
Скрипт заменяет **все** типы логотипов и иконок:
|
||||
|
||||
#### Основные логотипы:
|
||||
- ✅ `logo.png` - основной логотип
|
||||
- ✅ `logo.svg` - SVG версия логотипа
|
||||
- ✅ `logo-light.svg` / `logo-light.png` - для светлой темы
|
||||
- ✅ `logo-dark.svg` / `logo-dark.png` - для темной темы
|
||||
- ✅ `splash.png` - логотип на экране загрузки
|
||||
- ✅ `splash-dark.png` - логотип на экране загрузки (темная тема)
|
||||
- ✅ `splash-light.png` - логотип на экране загрузки (светлая тема)
|
||||
|
||||
#### Favicon и иконки:
|
||||
- ✅ `favicon.png` - основная иконка сайта
|
||||
- ✅ `favicon.ico` - иконка для браузеров
|
||||
- ✅ `favicon-dark.png` - иконка для темной темы
|
||||
- ✅ `favicon-light.png` - иконка для светлой темы
|
||||
- ✅ `apple-touch-icon.png` - иконка для iOS устройств
|
||||
|
||||
### 2. Исправление ссылок в шаблонах
|
||||
|
||||
Скрипт автоматически исправляет ссылки в HTML/Svelte файлах:
|
||||
- Заменяет `/static/favicon.ico` → `/static/logo.png`
|
||||
- Заменяет `/static/favicon-dark.png` → `/static/logo.png`
|
||||
- Заменяет `/static/splash-dark.png` → `/static/logo.png`
|
||||
- Заменяет `/static/apple-touch-icon.png` → `/static/logo.png`
|
||||
- Исправляет `href` и `src` атрибуты в HTML тегах
|
||||
|
||||
### 3. Удаление упоминаний "Open WebUI"
|
||||
|
||||
- ✅ Удаляет текст "(Open WebUI)" из всех HTML/Svelte шаблонов
|
||||
- ✅ Исправляет "Войти в iiEasyWeb (Open WebUI)" → "Войти в iiEasyWeb"
|
||||
- ✅ Заменяет ссылки на документацию: `docs.openwebui.com` → `note.iieasy.ru`
|
||||
|
||||
### 4. Удаление элементов интерфейса
|
||||
|
||||
- ✅ Удаляет кнопку "Проверить обновления"
|
||||
- ✅ Удаляет ссылку "(последняя)" на GitHub releases
|
||||
- ✅ Удаляет "Посмотреть, что нового"
|
||||
- ✅ Удаляет социальные сети (Discord, Twitter/X, GitHub)
|
||||
- ✅ Удаляет блок "Помощь" с соцсетями
|
||||
- ✅ Удаляет блок "Лицензия" полностью
|
||||
|
||||
### 5. Исправление API endpoints
|
||||
|
||||
- ✅ Заменяет `/api/v1/models/model/profile/image` → `/static/logo.png`
|
||||
- ✅ Исправляет изображения профиля моделей
|
||||
|
||||
## 📁 Структура файлов
|
||||
|
||||
```
|
||||
/home/its/iiEasyWeb/
|
||||
├── media/
|
||||
│ ├── logo.png # Основной логотип (обязательно)
|
||||
│ ├── favicon.png # Favicon (опционально, иначе используется logo.png)
|
||||
│ ├── logo-light.svg # Логотип для светлой темы (опционально)
|
||||
│ └── logo-dark.svg # Логотип для темной темы (опционально)
|
||||
├── scripts/
|
||||
│ └── rebrand_safe_final.sh # Основной скрипт ребрендинга
|
||||
└── docker-compose.yml # Docker Compose конфигурация
|
||||
```
|
||||
|
||||
## 🚀 Использование
|
||||
|
||||
### Требования
|
||||
|
||||
1. Контейнер `open-webui` должен быть запущен
|
||||
2. Файлы логотипов должны находиться в папке `media/`
|
||||
3. Минимум требуется `logo.png`
|
||||
|
||||
### Запуск скрипта
|
||||
|
||||
```bash
|
||||
cd /home/its/iiEasyWeb
|
||||
sudo ./scripts/rebrand_safe_final.sh
|
||||
```
|
||||
|
||||
### После выполнения
|
||||
|
||||
1. **Очистите кеш браузера:**
|
||||
- `Ctrl+Shift+Delete` → Очистить кеш изображений
|
||||
- Или используйте режим инкогнито: `Ctrl+Shift+N`
|
||||
|
||||
2. **Проверьте результат:**
|
||||
- Откройте `https://odo.iieasy.ru` или `http://localhost:3001`
|
||||
- Проверьте favicon в браузере
|
||||
- Проверьте логотип в окне авторизации
|
||||
- Проверьте темную тему (если используется)
|
||||
|
||||
3. **Проверьте статические файлы:**
|
||||
```bash
|
||||
# Проверьте, что файлы заменены
|
||||
curl http://localhost:3001/static/logo.png
|
||||
curl http://localhost:3001/static/favicon.ico
|
||||
curl http://localhost:3001/static/splash-dark.png
|
||||
curl http://localhost:3001/static/apple-touch-icon.png
|
||||
```
|
||||
|
||||
## 🔒 Безопасность
|
||||
|
||||
### Почему скрипт безопасен:
|
||||
|
||||
1. **Не трогает JavaScript/TypeScript код** - обрабатывает только HTML/Svelte/Python шаблоны
|
||||
2. **Не изменяет OAuth/Authentik** - исключает файлы связанные с аутентификацией
|
||||
3. **Не ломает функциональность** - только заменяет статические файлы и текст в шаблонах
|
||||
4. **Использует безопасные sed команды** - точные замены без агрессивных паттернов
|
||||
|
||||
### Что НЕ делает скрипт:
|
||||
|
||||
- ❌ Не изменяет скомпилированные JS файлы (может сломать функциональность)
|
||||
- ❌ Не трогает OAuth/OIDC конфигурацию
|
||||
- ❌ Не изменяет базу данных
|
||||
- ❌ Не удаляет критичные системные файлы
|
||||
|
||||
## 📝 Логика работы
|
||||
|
||||
### Шаг 1: Копирование файлов в статические директории
|
||||
|
||||
Скрипт копирует логотипы во все возможные статические директории:
|
||||
- `/app/web/build/_app/immutable`
|
||||
- `/app/web/static`
|
||||
- `/app/web/build`
|
||||
- `/app/backend/static`
|
||||
- `/app/static`
|
||||
- `/app/web/public`
|
||||
- `/app/public`
|
||||
|
||||
### Шаг 2: Замена существующих файлов
|
||||
|
||||
Скрипт находит все существующие файлы логотипов и иконок и заменяет их:
|
||||
```bash
|
||||
find /app -type f -name "logo.png" -o -name "favicon.png" ...
|
||||
```
|
||||
|
||||
### Шаг 3: Исправление ссылок в шаблонах
|
||||
|
||||
Скрипт ищет файлы с упоминаниями иконок и исправляет ссылки:
|
||||
```bash
|
||||
find /app/web -type f -name "*.html" -o -name "*.svelte" | grep -l "favicon"
|
||||
```
|
||||
|
||||
### Шаг 4: Удаление текста и элементов
|
||||
|
||||
Скрипт удаляет упоминания "Open WebUI" и элементы интерфейса только из Svelte файлов (безопасно).
|
||||
|
||||
### Шаг 5: Перезапуск контейнера
|
||||
|
||||
Скрипт автоматически перезапускает контейнер для применения изменений.
|
||||
|
||||
## 🔄 Обновление после обновления Open WebUI
|
||||
|
||||
После обновления образа Open WebUI нужно запустить скрипт снова:
|
||||
|
||||
```bash
|
||||
# 1. Обновите образ
|
||||
sudo docker compose pull
|
||||
|
||||
# 2. Пересоздайте контейнер
|
||||
sudo docker compose up -d
|
||||
|
||||
# 3. Запустите ребрендинг
|
||||
sudo ./scripts/rebrand_safe_final.sh
|
||||
```
|
||||
|
||||
Или используйте скрипт `update.sh`:
|
||||
|
||||
```bash
|
||||
sudo ./scripts/update.sh
|
||||
```
|
||||
|
||||
## 🐛 Решение проблем
|
||||
|
||||
### Логотип не изменился
|
||||
|
||||
1. **Очистите кеш браузера** - это самая частая причина
|
||||
2. **Проверьте файлы в контейнере:**
|
||||
```bash
|
||||
sudo docker exec open-webui ls -la /app/web/static/logo.png
|
||||
```
|
||||
3. **Перезапустите контейнер:**
|
||||
```bash
|
||||
sudo docker compose restart open-webui
|
||||
```
|
||||
|
||||
### Favicon не изменился
|
||||
|
||||
1. Проверьте HTML код страницы (View Source)
|
||||
2. Убедитесь, что ссылка на favicon исправлена
|
||||
3. Очистите кеш браузера полностью
|
||||
|
||||
### Элементы интерфейса не удалились
|
||||
|
||||
Элементы могут быть в скомпилированных JS файлах. В этом случае:
|
||||
1. Проверьте исходные Svelte файлы через `find_settings_elements.sh`
|
||||
2. Удалите элементы вручную в найденных файлах
|
||||
3. Или используйте Admin Panel для скрытия элементов
|
||||
|
||||
### Контейнер не запускается после ребрендинга
|
||||
|
||||
Если скрипт сломал контейнер:
|
||||
```bash
|
||||
# Восстановите контейнер
|
||||
sudo docker compose restart open-webui
|
||||
|
||||
# Если не помогло, пересоздайте
|
||||
sudo docker compose down
|
||||
sudo docker compose up -d
|
||||
sudo ./scripts/rebrand_safe_final.sh
|
||||
```
|
||||
|
||||
## 📊 Поддерживаемые файлы
|
||||
|
||||
| Тип файла | Обрабатывается | Приоритет |
|
||||
|-----------|----------------|-----------|
|
||||
| `logo.png` | ✅ Да | Высокий |
|
||||
| `favicon.png` | ✅ Да | Высокий |
|
||||
| `favicon.ico` | ✅ Да | Высокий |
|
||||
| `favicon-dark.png` | ✅ Да | Средний |
|
||||
| `apple-touch-icon.png` | ✅ Да | Средний |
|
||||
| `splash-dark.png` | ✅ Да | Средний |
|
||||
| `logo-dark.svg` | ✅ Да | Низкий (если есть) |
|
||||
| `logo-light.svg` | ✅ Да | Низкий (если есть) |
|
||||
|
||||
## ✨ Особенности
|
||||
|
||||
1. **Автоматическое определение темы** - скрипт автоматически использует `logo-dark.svg` для темной темы, если файл существует
|
||||
2. **Fallback на logo.png** - если специальных файлов нет, используется основной `logo.png`
|
||||
3. **Безопасная обработка** - скрипт не трогает критичные файлы и не ломает функциональность
|
||||
4. **Подробный вывод** - скрипт показывает, что именно обрабатывается
|
||||
|
||||
## 📚 Связанные файлы
|
||||
|
||||
- `scripts/rebrand_safe_final.sh` - основной скрипт ребрендинга
|
||||
- `scripts/update.sh` - скрипт для обновления с автоматическим ребрендингом
|
||||
- `REBRANDING.md` - общая документация по ребрендингу
|
||||
- `LOGO_SETUP.md` - инструкция по настройке логотипов
|
||||
|
||||
## 🎯 Итог
|
||||
|
||||
Скрипт `rebrand_safe_final.sh` - это **полное и безопасное решение** для ребрендинга Open WebUI, которое:
|
||||
|
||||
- ✅ Заменяет все логотипы и иконки
|
||||
- ✅ Исправляет ссылки в шаблонах
|
||||
- ✅ Удаляет упоминания "Open WebUI"
|
||||
- ✅ Удаляет ненужные элементы интерфейса
|
||||
- ✅ Не ломает функциональность приложения
|
||||
- ✅ Работает после обновлений Open WebUI
|
||||
|
||||
**Используйте этот скрипт для всех операций ребрендинга!**
|
||||
56
RESTORE_AFTER_500.md
Normal file
56
RESTORE_AFTER_500.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# Восстановление после 500 ошибки
|
||||
|
||||
## Проблема
|
||||
|
||||
После запуска `rebrand_fast.sh` появилась ошибка 500 - скрипт сломал функциональность.
|
||||
|
||||
## Решение
|
||||
|
||||
**1. Восстановите контейнер:**
|
||||
|
||||
```bash
|
||||
cd /home/its/iiEasyWeb
|
||||
|
||||
# Остановите и удалите сломанный контейнер
|
||||
sudo docker compose stop open-webui
|
||||
sudo docker compose rm -f open-webui
|
||||
|
||||
# Пересоздайте контейнер с чистой версией
|
||||
sudo docker compose up -d open-webui
|
||||
|
||||
# Подождите 30 секунд
|
||||
sleep 30
|
||||
```
|
||||
|
||||
**2. Используйте БЕЗОПАСНЫЙ скрипт:**
|
||||
|
||||
```bash
|
||||
sudo ./scripts/rebrand_safe_final.sh
|
||||
```
|
||||
|
||||
Этот скрипт:
|
||||
- ✅ Заменяет splash.png на ваш логотип
|
||||
- ✅ Удаляет "(Open WebUI)" только из HTML/Svelte (не трогает JS/TS код)
|
||||
- ✅ Заменяет ссылки на документацию
|
||||
- ✅ Удаляет социальные сети и лицензию
|
||||
- ❌ НЕ трогает JavaScript/TypeScript файлы - не ломает функциональность
|
||||
|
||||
**3. Проверьте результат:**
|
||||
|
||||
1. Откройте `https://odo.iieasy.ru`
|
||||
2. Должно работать без ошибок
|
||||
3. splash.png должен быть заменен на ваш логотип
|
||||
4. Очистите кеш браузера (Ctrl+Shift+Delete)
|
||||
|
||||
## Что было не так с rebrand_fast.sh
|
||||
|
||||
Скрипт обрабатывал JS/TS файлы и мог случайно сломать код, удалив важные строки или изменив синтаксис.
|
||||
|
||||
## Безопасный подход
|
||||
|
||||
Новый скрипт `rebrand_safe_final.sh` обрабатывает только:
|
||||
- HTML файлы (безопасно)
|
||||
- Svelte файлы (шаблоны, безопасно)
|
||||
- НЕ трогает JS/TS файлы (чтобы не сломать код)
|
||||
|
||||
Это гарантирует, что функциональность не будет нарушена.
|
||||
55
RESTORE_CONTAINER.md
Normal file
55
RESTORE_CONTAINER.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# Восстановление контейнера после сломанного ребрендинга
|
||||
|
||||
## Если после rebrand_complete.sh ничего не работает
|
||||
|
||||
**1. Пересоздайте контейнер с чистой версией:**
|
||||
|
||||
```bash
|
||||
cd /home/its/iiEasyWeb
|
||||
|
||||
# Остановите и удалите контейнер
|
||||
sudo docker compose stop open-webui
|
||||
sudo docker compose rm -f open-webui
|
||||
|
||||
# Пересоздайте контейнер
|
||||
sudo docker compose up -d open-webui
|
||||
|
||||
# Подождите 30 секунд
|
||||
sleep 30
|
||||
```
|
||||
|
||||
**2. Проверьте, что контейнер работает:**
|
||||
|
||||
```bash
|
||||
sudo docker compose ps open-webui
|
||||
sudo docker compose logs open-webui --tail 50
|
||||
```
|
||||
|
||||
**3. Используйте АККУРАТНЫЙ скрипт для ребрендинга:**
|
||||
|
||||
```bash
|
||||
sudo ./scripts/rebrand_careful.sh
|
||||
```
|
||||
|
||||
Этот скрипт изменяет ТОЛЬКО текст в HTML/Svelte файлах, не трогая код.
|
||||
|
||||
## Что делает rebrand_careful.sh
|
||||
|
||||
- ✅ Заменяет логотипы и favicon
|
||||
- ✅ Заменяет "Open WebUI" на "iiEasyWeb" ТОЛЬКО в текстовом контенте HTML
|
||||
- ✅ Удаляет "(Open WebUI)" из текста
|
||||
- ✅ Удаляет "Powered by Open WebUI" футеры
|
||||
- ❌ НЕ трогает код Python/JS
|
||||
- ❌ НЕ комментирует импорты
|
||||
- ❌ НЕ изменяет конфигурацию
|
||||
|
||||
## Альтернатива: Использовать только Admin Panel
|
||||
|
||||
Самый безопасный способ - использовать Admin Panel Open WebUI:
|
||||
|
||||
1. Откройте `https://odo.iieasy.ru`
|
||||
2. Войдите как администратор
|
||||
3. Settings → Appearance → Logo
|
||||
4. Загрузите логотипы из папки `media/`
|
||||
|
||||
Это не сломает функциональность, так как изменения сохраняются в базе данных.
|
||||
306
SEARXNG_SETUP.md
Normal file
306
SEARXNG_SETUP.md
Normal file
@@ -0,0 +1,306 @@
|
||||
# Настройка SearXNG для Open WebUI - Рабочее решение
|
||||
|
||||
## Обзор
|
||||
|
||||
Данное решение обеспечивает работу веб-поиска через SearXNG в Open WebUI. Решены проблемы с JSON форматом, лимитером и багом User-Agent в Open WebUI v0.8.3.
|
||||
|
||||
## Архитектура
|
||||
|
||||
```
|
||||
Open WebUI → SearXNG → Поисковые движки (Google, DuckDuckGo, Brave и др.)
|
||||
```
|
||||
|
||||
## Компоненты решения
|
||||
|
||||
### 1. Конфигурация SearXNG (`searxng/settings.yml`)
|
||||
|
||||
```yaml
|
||||
# SearXNG Settings для работы с Open WebUI
|
||||
# Этот файл включает поддержку JSON формата для API запросов
|
||||
|
||||
use_default_settings: true
|
||||
|
||||
server:
|
||||
secret_key: "CHANGE_ME_SECRET_KEY"
|
||||
bind_address: "0.0.0.0"
|
||||
port: 8080
|
||||
limiter: false # КРИТИЧНО: отключен для работы внутри Docker сети
|
||||
method: "GET"
|
||||
|
||||
search:
|
||||
safe_search: 0
|
||||
autocomplete: "google"
|
||||
formats:
|
||||
- html
|
||||
- json # КРИТИЧНО: JSON формат обязателен для Open WebUI
|
||||
|
||||
general:
|
||||
instance_name: "SearXNG"
|
||||
debug: false
|
||||
```
|
||||
|
||||
**Ключевые моменты:**
|
||||
- `limiter: false` - отключает защиту от ботов (необходимо для запросов из Open WebUI)
|
||||
- `formats: [html, json]` - включает JSON формат для API запросов
|
||||
- Файл монтируется через bind mount, поэтому настройки сохраняются после перезапуска
|
||||
|
||||
### 2. Конфигурация Docker Compose (`docker-compose.yml`)
|
||||
|
||||
```yaml
|
||||
services:
|
||||
searxng:
|
||||
image: ghcr.io/searxng/searxng:latest
|
||||
container_name: searxng
|
||||
restart: always
|
||||
volumes:
|
||||
- ./searxng:/etc/searxng:rw # Bind mount для сохранения настроек
|
||||
- searxng_cache:/var/cache/searxng
|
||||
networks:
|
||||
- iieasy-ai
|
||||
environment:
|
||||
- SEARXNG_BASE_URL=http://searxng:8080/
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 512M
|
||||
|
||||
open-webui:
|
||||
image: ghcr.io/open-webui/open-webui:v0.8.3
|
||||
container_name: open-webui
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- openwebui_data:/app/backend/data
|
||||
- ./scripts/fix_user_agent.sh:/fix_user_agent.sh:ro # Патч для User-Agent
|
||||
entrypoint: ["/bin/sh", "-c", "sh /fix_user_agent.sh && exec /bin/bash /app/start.sh"]
|
||||
networks:
|
||||
- iieasy-ai
|
||||
depends_on:
|
||||
searxng:
|
||||
condition: service_started
|
||||
environment:
|
||||
# SearXNG веб-поиск
|
||||
- RAG_WEB_SEARCH_ENGINE=searxng
|
||||
- SEARXNG_QUERY_URL=http://searxng:8080/search?q=<query>&format=json
|
||||
- ENABLE_WEB_SEARCH=true
|
||||
- WEB_SEARCH_RESULT_COUNT=5
|
||||
- WEB_SEARCH_TRUST_ENV=true
|
||||
- WEB_SEARCH_CONCURRENT_REQUESTS=1
|
||||
- USER_AGENT=Open-WebUI-RAG-Bot
|
||||
```
|
||||
|
||||
**Ключевые моменты:**
|
||||
- `SEARXNG_QUERY_URL` содержит явное указание `format=json`
|
||||
- Патч User-Agent применяется автоматически через entrypoint
|
||||
- Контейнеры находятся в одной сети `iieasy-ai`
|
||||
|
||||
### 3. Патч User-Agent (`scripts/fix_user_agent.sh`)
|
||||
|
||||
Патч исправляет баг в Open WebUI v0.8.3, где User-Agent начинается с пробела, что вызывает ошибку "Invalid leading whitespace".
|
||||
|
||||
**Что делает патч:**
|
||||
- Ищет все варианты проблемной строки `' (https://github.com/open-webui/open-webui) RAG Bot'`
|
||||
- Заменяет на `'Open-WebUI-RAG-Bot'`
|
||||
- Исправляет варианты в файлах:
|
||||
- `/app/backend/open_webui/routers/retrieval.py`
|
||||
- `/app/backend/open_webui/utils/middleware.py`
|
||||
- `/app/backend/open_webui/retrieval/loaders/external_web.py`
|
||||
- И других файлах с проблемной строкой
|
||||
- Очищает кеш Python (`.pyc` файлы)
|
||||
|
||||
## Установка и настройка
|
||||
|
||||
### Шаг 1: Подготовка файлов
|
||||
|
||||
1. Убедитесь, что файл `searxng/settings.yml` существует и содержит правильную конфигурацию
|
||||
2. Убедитесь, что файл `scripts/fix_user_agent.sh` существует и исполняемый
|
||||
|
||||
### Шаг 2: Запуск контейнеров
|
||||
|
||||
```bash
|
||||
cd /home/its/iiEasyWeb
|
||||
sudo docker compose up -d
|
||||
```
|
||||
|
||||
### Шаг 3: Проверка работы
|
||||
|
||||
```bash
|
||||
# Проверка SearXNG
|
||||
sudo docker ps | grep searxng
|
||||
|
||||
# Проверка JSON формата
|
||||
sudo docker exec open-webui curl -s "http://searxng:8080/search?q=test&format=json" | head -c 200
|
||||
|
||||
# Проверка логов на ошибки
|
||||
sudo docker logs open-webui --tail 50 | grep -i "error\|user-agent\|invalid"
|
||||
```
|
||||
|
||||
### Шаг 4: Настройка в интерфейсе Open WebUI
|
||||
|
||||
1. Откройте Open WebUI в браузере
|
||||
2. Перейдите в **Settings → Web Search**
|
||||
3. Убедитесь, что:
|
||||
- Движок: **SearXNG**
|
||||
- URL: `http://searxng:8080/search?q=<query>&format=json`
|
||||
- Переключатель "Web Search" **включен**
|
||||
- "Одновременные запросы" установлено в **1** или больше (не 0)
|
||||
|
||||
## Скрипты для обслуживания
|
||||
|
||||
### Диагностика (`scripts/diagnose_search.sh`)
|
||||
|
||||
Проверяет все компоненты системы поиска:
|
||||
|
||||
```bash
|
||||
sudo ./scripts/diagnose_search.sh
|
||||
```
|
||||
|
||||
### Исправление конфигурации SearXNG (`scripts/fix_searxng_config.sh`)
|
||||
|
||||
Исправляет конфигурацию SearXNG после перезапуска:
|
||||
|
||||
```bash
|
||||
sudo ./scripts/fix_searxng_config.sh
|
||||
```
|
||||
|
||||
### Агрессивное исправление User-Agent (`scripts/fix_user_agent_aggressive.sh`)
|
||||
|
||||
Ищет и исправляет проблемную строку User-Agent во всех файлах:
|
||||
|
||||
```bash
|
||||
sudo ./scripts/fix_user_agent_aggressive.sh
|
||||
```
|
||||
|
||||
### Полное исправление (`scripts/fix_search_complete.sh`)
|
||||
|
||||
Выполняет все исправления за один раз:
|
||||
|
||||
```bash
|
||||
sudo ./scripts/fix_search_complete.sh
|
||||
```
|
||||
|
||||
## Решенные проблемы
|
||||
|
||||
### 1. Ошибка "403 Forbidden" при запросе JSON
|
||||
|
||||
**Причина:** SearXNG по умолчанию не разрешает JSON формат для безопасности.
|
||||
|
||||
**Решение:** Добавлено `formats: [html, json]` в секцию `search:` файла `settings.yml`.
|
||||
|
||||
### 2. Ошибка "Invalid leading whitespace" в User-Agent
|
||||
|
||||
**Причина:** Баг в Open WebUI v0.8.3 - User-Agent начинается с пробела.
|
||||
|
||||
**Решение:** Патч `fix_user_agent.sh` автоматически исправляет проблемную строку при старте контейнера.
|
||||
|
||||
### 3. Ошибка "X-Forwarded-For nor X-Real-IP header is set"
|
||||
|
||||
**Причина:** SearXNG блокирует запросы без реального IP (защита от ботов).
|
||||
|
||||
**Решение:** Отключен лимитер (`limiter: false`) в `settings.yml`, так как система работает внутри закрытой Docker сети.
|
||||
|
||||
### 4. Потеря настроек после перезапуска
|
||||
|
||||
**Причина:** Изменения внутри контейнера не сохраняются.
|
||||
|
||||
**Решение:** Использован bind mount `./searxng:/etc/searxng:rw` для сохранения `settings.yml` на хосте.
|
||||
|
||||
## Улучшение покрытия поиска
|
||||
|
||||
Если нужно больше результатов, можно включить дополнительные движки:
|
||||
|
||||
```bash
|
||||
# Включить DuckDuckGo
|
||||
sudo docker exec searxng sed -i '/- name: duckduckgo/,/disabled:/ s/disabled: true/disabled: false/' /etc/searxng/settings.yml
|
||||
|
||||
# Включить Brave
|
||||
sudo docker exec searxng sed -i '/- name: brave$/,/disabled:/ s/disabled: true/disabled: false/' /etc/searxng/settings.yml
|
||||
|
||||
# Перезапустить
|
||||
sudo docker restart searxng
|
||||
```
|
||||
|
||||
## Проверка работоспособности
|
||||
|
||||
### Тест 1: Прямой запрос к SearXNG
|
||||
|
||||
```bash
|
||||
sudo docker exec open-webui curl -s "http://searxng:8080/search?q=test&format=json" | grep -q "results" && echo "✓ JSON работает" || echo "✗ JSON не работает"
|
||||
```
|
||||
|
||||
### Тест 2: Проверка патча User-Agent
|
||||
|
||||
```bash
|
||||
sudo docker exec open-webui grep -r "github.com/open-webui.*RAG Bot" /app/backend 2>/dev/null | wc -l
|
||||
# Должно вернуть 0
|
||||
```
|
||||
|
||||
### Тест 3: Поиск в интерфейсе
|
||||
|
||||
1. Откройте чат в Open WebUI
|
||||
2. Задайте вопрос с включенным поиском (например: "Какая погода в Уфе сегодня?")
|
||||
3. Должны появиться результаты поиска без ошибок
|
||||
|
||||
## Логи и отладка
|
||||
|
||||
### Просмотр логов SearXNG
|
||||
|
||||
```bash
|
||||
sudo docker logs searxng --tail 50
|
||||
```
|
||||
|
||||
### Просмотр логов Open WebUI
|
||||
|
||||
```bash
|
||||
sudo docker logs open-webui --tail 50 | grep -i "searxng\|error\|user-agent"
|
||||
```
|
||||
|
||||
### Проверка сетевого подключения
|
||||
|
||||
```bash
|
||||
# Из контейнера Open WebUI к SearXNG
|
||||
sudo docker exec open-webui curl http://searxng:8080/status
|
||||
```
|
||||
|
||||
## Важные замечания
|
||||
|
||||
1. **Безопасность:** Лимитер отключен только для внутренней сети Docker. Если выставляете SearXNG наружу, включите лимитер обратно.
|
||||
|
||||
2. **Производительность:** Ограничение памяти SearXNG до 512M предотвращает перегрузку системы.
|
||||
|
||||
3. **Стабильность:** Некоторые движки (Google, Bing) могут блокировать запросы с серверов. Это нормально - SearXNG использует другие доступные движки.
|
||||
|
||||
4. **Обновления:** При обновлении образа Open WebUI патч User-Agent будет применяться автоматически благодаря entrypoint в docker-compose.yml.
|
||||
|
||||
## Структура файлов
|
||||
|
||||
```
|
||||
/home/its/iiEasyWeb/
|
||||
├── docker-compose.yml # Конфигурация Docker Compose
|
||||
├── searxng/
|
||||
│ └── settings.yml # Конфигурация SearXNG (bind mount)
|
||||
└── scripts/
|
||||
├── fix_user_agent.sh # Основной патч User-Agent
|
||||
├── fix_user_agent_aggressive.sh # Агрессивный патч
|
||||
├── fix_user_agent_final.sh # Финальный патч
|
||||
├── fix_searxng_config.sh # Исправление конфигурации SearXNG
|
||||
├── diagnose_search.sh # Диагностика системы поиска
|
||||
└── fix_search_complete.sh # Полное исправление
|
||||
```
|
||||
|
||||
## Версия
|
||||
|
||||
- Open WebUI: v0.8.3
|
||||
- SearXNG: latest (2026.2.16+8e824017d)
|
||||
- Дата настройки: Февраль 2026
|
||||
|
||||
## Поддержка
|
||||
|
||||
При возникновении проблем:
|
||||
|
||||
1. Запустите диагностику: `sudo ./scripts/diagnose_search.sh`
|
||||
2. Проверьте логи: `sudo docker logs open-webui --tail 100`
|
||||
3. Выполните полное исправление: `sudo ./scripts/fix_search_complete.sh`
|
||||
|
||||
---
|
||||
|
||||
**Решение протестировано и работает стабильно.**
|
||||
93
TEST_VISION.md
Normal file
93
TEST_VISION.md
Normal file
@@ -0,0 +1,93 @@
|
||||
# Тестирование Vision Capabilities модели gemma3n:e4b-it-fp16
|
||||
|
||||
## Быстрый тест через скрипт
|
||||
|
||||
Запустите скрипт с правами sudo:
|
||||
|
||||
```bash
|
||||
cd /home/its/iiEasyWeb
|
||||
sudo ./scripts/test_vision.sh
|
||||
```
|
||||
|
||||
Скрипт автоматически:
|
||||
1. Проверит, что контейнер Ollama запущен
|
||||
2. Проверит, что модель gemma3n:e4b-it-fp16 загружена
|
||||
3. Скачает тестовое изображение
|
||||
4. Отправит запрос к Ollama API с изображением
|
||||
5. Покажет ответ модели
|
||||
|
||||
## Ручной тест через терминал
|
||||
|
||||
Если скрипт не работает, выполните команды вручную:
|
||||
|
||||
### 1. Проверка контейнера и модели
|
||||
|
||||
```bash
|
||||
# Проверка контейнера
|
||||
sudo docker ps | grep ollama
|
||||
|
||||
# Проверка загруженных моделей
|
||||
sudo docker exec ollama ollama list
|
||||
```
|
||||
|
||||
### 2. Подготовка изображения
|
||||
|
||||
```bash
|
||||
cd /home/its/iiEasyWeb
|
||||
mkdir -p test_images
|
||||
cd test_images
|
||||
|
||||
# Скачайте тестовое изображение
|
||||
curl -L -o test_image.jpg "https://yandex-images.clstorage.net/PU5vN2154/532297ZKm/lCuSfyMn0DdbJqcFVeFiB9Ti31Te2dZ1EepiRw3Cs0Qw8cQ1ND5OQRKC1yH4LnhdRtloQ4aXHng5ZSLNmXHy_8k293YSMsBWKnOvYAXBbhPcl6pYmqi-ZGWDazZo2pYkJNHpkJHrg5yiO0bEOIeEICe5cFqrojYgyNQ6mHj4e5IUb_Lri3uxo9fmXv0dMf7f1NvH9J5YVsyhRvvmtD9eTc1QfVxV42d8OotKrLTDfHx7jfDqjpIqHAyt9ngIIHsSjLtOJ7_jTYdHLn4hJWlCj_jj69gGlApIBYaTfoxPrtHFDGlYUd-PjHI3hijAm6hIb-lUFlpOehQAJTbwEGwjcqjjVns323WrR2q6CalVyg9kZ8s0jkBm8OVGr26UH86NHaD1rKnPH6zinwJIKBv8VEdF_FrGLt6QFOVqFOQY776w47YDNy8Biwfefg0lxVaL2E9L5NbAztwh8vuCcMNSEfG8aYQFBxe0Eu9CMPhz1Axz1dgqmkpSYCAR8nSA_GMSKKPa9-tzIUc7ooKthV1ykwSjrzhORKq0eRrvBlTbajF5yAUoVX-7XM7HouyU51xMaxFMBl7atixQFW4o1BznMoi_chuL8_mTO_IKce21CosEQ6Owkkhi9EGSz5pIE56NjURBxKn7pwTKt6K4FB_YYHexZG4qlsIg8GVaiJyIB0p0bx5PF4_FIy-SItWZId5f8M9_RIqg2hRVCqP2hDvSfWGAYUjJd6c8lr9mgGj7kPQbVQBCUgIm1NB9lsxQrIO-wO92fytbOQsjjiqBqa0CK9Qjy6wi6CoIfX5vZjw_pj1BKGm0UVOXuArPFngIN2AUgz3w4mZSTvz8IQYo_IhPVlQ7PiOjhyWD6wZSVZ3NDrvsC-8w4lxmzE32T1qMuy5JOTRxaNHHH0QCp4IkgMv8LH8JeJaOUj4sVOGCBBgQi5KMX74TQzOBW9cGTu35UZr3qAfj5O6kMogxjqNanMOs"
|
||||
```
|
||||
|
||||
### 3. Тест через Ollama API
|
||||
|
||||
```bash
|
||||
# Закодируйте изображение в base64
|
||||
IMAGE_B64=$(base64 -w 0 test_image.jpg)
|
||||
|
||||
# Отправьте запрос к Ollama API
|
||||
sudo docker exec ollama sh -c "curl -s -X POST http://localhost:11434/api/generate \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
\"model\": \"gemma3n:e4b-it-fp16\",
|
||||
\"prompt\": \"Опиши это изображение на русском языке. Что ты видишь на картинке?\",
|
||||
\"images\": [\"'$IMAGE_B64'\"],
|
||||
\"stream\": false
|
||||
}' | jq -r '.response'"
|
||||
```
|
||||
|
||||
Если `jq` не установлен, используйте:
|
||||
|
||||
```bash
|
||||
sudo docker exec ollama sh -c "curl -s -X POST http://localhost:11434/api/generate \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
\"model\": \"gemma3n:e4b-it-fp16\",
|
||||
\"prompt\": \"Опиши это изображение на русском языке. Что ты видишь на картинке?\",
|
||||
\"images\": [\"'$IMAGE_B64'\"],
|
||||
\"stream\": false
|
||||
}'" | grep -o '"response":"[^"]*"' | sed 's/"response":"\(.*\)"/\1/' | sed 's/\\n/\n/g' | sed 's/\\"/"/g'
|
||||
```
|
||||
|
||||
## Тест через веб-интерфейс Open WebUI (рекомендуется)
|
||||
|
||||
1. Откройте Open WebUI: https://odo.iieasy.ru
|
||||
2. Выберите модель: **gemma3n:e4b-it-fp16**
|
||||
3. Найдите кнопку загрузки изображения в поле ввода (обычно иконка скрепки 📎 или фото 📷)
|
||||
4. Загрузите изображение: `/home/its/iiEasyWeb/test_images/test_image.jpg`
|
||||
5. Задайте вопрос: **"Опиши это изображение на русском языке. Что ты видишь на картинке?"**
|
||||
|
||||
## Ожидаемый результат
|
||||
|
||||
Модель должна описать содержимое изображения:
|
||||
- Объекты на картинке
|
||||
- Цвета и композицию
|
||||
- Детали и контекст
|
||||
|
||||
Если модель не видит изображение, проверьте:
|
||||
- Поддерживает ли модель vision (gemma3n:e4b-it-fp16 поддерживает)
|
||||
- Правильно ли загружается изображение в интерфейсе
|
||||
- Логи: `sudo docker logs ollama --tail 50`
|
||||
- Логи Open WebUI: `sudo docker logs open-webui --tail 50`
|
||||
102
TROUBLESHOOTING.md
Normal file
102
TROUBLESHOOTING.md
Normal file
@@ -0,0 +1,102 @@
|
||||
# Устранение проблем: 502 Bad Gateway
|
||||
|
||||
## Диагностика проблемы 502 Bad Gateway
|
||||
|
||||
### Шаг 1: Проверка контейнера Open WebUI
|
||||
|
||||
```bash
|
||||
# Проверьте статус контейнера
|
||||
sudo docker ps | grep open-webui
|
||||
|
||||
# Если контейнер не запущен, запустите его
|
||||
cd /home/its/iiEasyWeb
|
||||
sudo docker compose up -d open-webui
|
||||
|
||||
# Проверьте логи
|
||||
sudo docker compose logs open-webui --tail 50
|
||||
```
|
||||
|
||||
### Шаг 2: Проверка доступности порта
|
||||
|
||||
```bash
|
||||
# Проверьте, что порт 3001 слушается
|
||||
sudo netstat -tlnp | grep 3001
|
||||
# или
|
||||
sudo ss -tlnp | grep 3001
|
||||
|
||||
# Проверьте доступность локально
|
||||
curl -I http://localhost:3001
|
||||
curl -I http://127.0.0.1:3001
|
||||
```
|
||||
|
||||
### Шаг 3: Проверка настроек Nginx Proxy Manager
|
||||
|
||||
В Nginx Proxy Manager для `odo.iieasy.ru` проверьте:
|
||||
|
||||
1. **Details:**
|
||||
- **Forward Hostname/IP**:
|
||||
- Если NPM на той же машине: `localhost` или `127.0.0.1`
|
||||
- Если NPM на другой машине: IP адрес машины с Open WebUI
|
||||
- **Forward Port**: `3001` (порт на хосте, не в контейнере)
|
||||
|
||||
2. **Advanced:**
|
||||
- **Custom Nginx Configuration**: Оставьте ПУСТЫМ (может вызывать ошибки 500/502)
|
||||
|
||||
### Шаг 4: Если используете другую машину
|
||||
|
||||
Если Open WebUI на другой машине:
|
||||
|
||||
1. Убедитесь, что порт 3001 доступен с машины Nginx Proxy Manager:
|
||||
```bash
|
||||
# С машины NPM проверьте доступность
|
||||
curl http://IP_ДРУГОЙ_МАШИНЫ:3001
|
||||
```
|
||||
|
||||
2. Проверьте firewall:
|
||||
```bash
|
||||
# На машине с Open WebUI разрешите порт 3001
|
||||
sudo ufw allow 3001/tcp
|
||||
# или
|
||||
sudo firewall-cmd --add-port=3001/tcp --permanent
|
||||
```
|
||||
|
||||
3. В Nginx Proxy Manager укажите:
|
||||
- **Forward Hostname/IP**: IP адрес другой машины
|
||||
- **Forward Port**: `3001`
|
||||
|
||||
## Быстрое решение
|
||||
|
||||
Если контейнер не запущен или не отвечает:
|
||||
|
||||
```bash
|
||||
cd /home/its/iiEasyWeb
|
||||
|
||||
# Перезапустите все сервисы
|
||||
sudo docker compose down
|
||||
sudo docker compose up -d
|
||||
|
||||
# Подождите 30 секунд
|
||||
sleep 30
|
||||
|
||||
# Проверьте статус
|
||||
sudo docker compose ps
|
||||
|
||||
# Проверьте доступность
|
||||
curl http://localhost:3001
|
||||
```
|
||||
|
||||
## Частые причины 502 Bad Gateway
|
||||
|
||||
1. **Контейнер не запущен** → Запустите: `sudo docker compose up -d open-webui`
|
||||
2. **Контейнер постоянно перезапускается** → Проверьте логи: `sudo docker compose logs open-webui`
|
||||
3. **Неправильный Forward Hostname/IP** → Используйте `localhost` если NPM на той же машине
|
||||
4. **Неправильный Forward Port** → Используйте `3001` (порт на хосте), не `8080` (порт в контейнере)
|
||||
5. **Custom Configuration конфликтует** → Оставьте поле пустым
|
||||
6. **Firewall блокирует** → Разрешите порт 3001
|
||||
7. **Контейнер на другой машине недоступен** → Проверьте сетевую доступность
|
||||
|
||||
## Проверка после исправления
|
||||
|
||||
1. Откройте `https://odo.iieasy.ru` в браузере
|
||||
2. Если все еще 502, проверьте логи Nginx Proxy Manager
|
||||
3. Проверьте логи Open WebUI: `sudo docker compose logs open-webui`
|
||||
100
VISION_MODELS.md
Normal file
100
VISION_MODELS.md
Normal file
@@ -0,0 +1,100 @@
|
||||
# Vision модели для Ollama
|
||||
|
||||
## Проблема с gemma3n:e4b-it-fp16
|
||||
|
||||
Модель `gemma3n:e4b-it-fp16` может не поддерживать vision правильно или требует специальной настройки. Рекомендуется использовать специализированные vision модели.
|
||||
|
||||
## Рекомендуемые Vision модели
|
||||
|
||||
### 1. LLaVA (Large Language-and-Vision Assistant) - РЕКОМЕНДУЕТСЯ
|
||||
|
||||
**Модель:** `llava:latest` или `llava:7b`
|
||||
|
||||
**Характеристики:**
|
||||
- 7B параметров
|
||||
- Версия 1.6 (обновлена в феврале 2024)
|
||||
- Улучшенное распознавание текста
|
||||
- Высокое разрешение изображений (в 4 раза больше пикселей)
|
||||
- Хорошо работает с документами, диаграммами, таблицами
|
||||
- Лицензия: Apache 2.0 или LLaMA 2 Community License
|
||||
|
||||
**Установка:**
|
||||
```bash
|
||||
sudo docker exec ollama ollama pull llava:latest
|
||||
```
|
||||
|
||||
**Использование:**
|
||||
- В Open WebUI выберите модель `llava:latest`
|
||||
- Загрузите изображение
|
||||
- Задайте вопрос о изображении
|
||||
|
||||
### 2. BakLLaVA
|
||||
|
||||
**Модель:** `bakllava:latest`
|
||||
|
||||
**Характеристики:**
|
||||
- 7B параметров
|
||||
- Комбинация Mistral 7B + LLaVA архитектура
|
||||
- Контекстное окно: 32K
|
||||
|
||||
**Установка:**
|
||||
```bash
|
||||
sudo docker exec ollama ollama pull bakllava:latest
|
||||
```
|
||||
|
||||
### 3. Llama 3.2 Vision
|
||||
|
||||
**Модель:** `llama3.2-vision:latest` или `llama3.2-vision:11b`
|
||||
|
||||
**Характеристики:**
|
||||
- 11B параметров (требует 8GB VRAM)
|
||||
- 90B версия доступна (требует 64GB VRAM)
|
||||
- Контекстное окно: 128K
|
||||
- Оптимизирована для визуального распознавания, анализа изображений
|
||||
- Поддержка OCR, распознавание рукописного текста
|
||||
- Анализ графиков и таблиц
|
||||
|
||||
**Установка:**
|
||||
```bash
|
||||
sudo docker exec ollama ollama pull llama3.2-vision:11b
|
||||
```
|
||||
|
||||
## Быстрая установка
|
||||
|
||||
Используйте скрипт для установки:
|
||||
|
||||
```bash
|
||||
cd /home/its/iiEasyWeb
|
||||
|
||||
# Установить LLaVA (рекомендуется)
|
||||
sudo ./scripts/install_vision_model.sh llava
|
||||
|
||||
# Или BakLLaVA
|
||||
sudo ./scripts/install_vision_model.sh bakllava
|
||||
|
||||
# Или Llama 3.2 Vision
|
||||
sudo ./scripts/install_vision_model.sh llama3.2
|
||||
```
|
||||
|
||||
## Тестирование Vision модели
|
||||
|
||||
После установки протестируйте:
|
||||
|
||||
```bash
|
||||
cd /home/its/iiEasyWeb
|
||||
sudo ./scripts/test_direct_vision.sh
|
||||
```
|
||||
|
||||
(Измените MODEL в скрипте на установленную vision модель)
|
||||
|
||||
## Использование в Open WebUI
|
||||
|
||||
1. Откройте https://odo.iieasy.ru
|
||||
2. Перейдите в Settings → Models (или выберите модель в чате)
|
||||
3. Выберите установленную vision модель (например, `llava:latest`)
|
||||
4. Загрузите изображение через кнопку загрузки (📎 или 📷)
|
||||
5. Задайте вопрос о изображении
|
||||
|
||||
## Рекомендация
|
||||
|
||||
**Для лучшей совместимости с Open WebUI рекомендуется использовать `llava:latest`** - это самая популярная и хорошо поддерживаемая vision модель в Ollama.
|
||||
47
check_authentik.sh
Executable file
47
check_authentik.sh
Executable file
@@ -0,0 +1,47 @@
|
||||
#!/bin/bash
|
||||
# Скрипт проверки конфигурации Authentik
|
||||
|
||||
cd /home/its/iiEasyWeb
|
||||
|
||||
echo "=== Проверка конфигурации Authentik ==="
|
||||
echo ""
|
||||
|
||||
# Проверка .env
|
||||
echo "1. Проверка .env файла:"
|
||||
if grep -q "ii-easy-web" .env; then
|
||||
echo " ✓ OPENID_CONNECT_ISSUER содержит правильный slug: ii-easy-web"
|
||||
grep "OPENID_CONNECT_ISSUER" .env
|
||||
else
|
||||
echo " ✗ OPENID_CONNECT_ISSUER не содержит правильный slug"
|
||||
echo " Текущее значение:"
|
||||
grep "OPENID_CONNECT_ISSUER" .env
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "2. Проверка endpoint Authentik:"
|
||||
ENDPOINT=$(grep "OPENID_CONNECT_ISSUER" .env | cut -d'=' -f2).well-known/openid-configuration
|
||||
echo " URL: $ENDPOINT"
|
||||
|
||||
RESPONSE=$(curl -s "$ENDPOINT" 2>&1)
|
||||
if echo "$RESPONSE" | grep -q '"issuer"'; then
|
||||
echo " ✓ Endpoint доступен и возвращает JSON"
|
||||
echo "$RESPONSE" | python3 -c "import sys, json; d=json.load(sys.stdin); print(' Issuer:', d.get('issuer'))" 2>/dev/null || echo " JSON валидный"
|
||||
else
|
||||
echo " ✗ Endpoint недоступен или возвращает ошибку"
|
||||
echo " Ответ: $(echo "$RESPONSE" | head -3)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "3. Проверка переменных окружения в docker-compose.yml:"
|
||||
echo " OPENID_PROVIDER_URL будет: ${ENDPOINT}"
|
||||
echo " OPENID_REDIRECT_URI будет: $(grep DOMAIN_OPENWEBUI .env | cut -d'=' -f2)/oauth/oidc/callback"
|
||||
|
||||
echo ""
|
||||
echo "4. Что проверить в Authentik:"
|
||||
echo " - Application slug должен быть: ii-easy-web"
|
||||
echo " - Redirect URI должен быть: $(grep DOMAIN_OPENWEBUI .env | cut -d'=' -f2)/oauth/oidc/callback"
|
||||
echo " - Client ID должен совпадать с OAUTH_CLIENT_ID в .env"
|
||||
|
||||
echo ""
|
||||
echo "5. Для применения изменений выполните:"
|
||||
echo " sudo docker compose restart open-webui"
|
||||
150
docker-compose.yml
Normal file
150
docker-compose.yml
Normal file
@@ -0,0 +1,150 @@
|
||||
services:
|
||||
ollama:
|
||||
image: ollama/ollama:latest
|
||||
container_name: ollama
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ollama_data:/root/.ollama
|
||||
networks:
|
||||
- iieasy-ai
|
||||
deploy:
|
||||
resources:
|
||||
reservations:
|
||||
devices:
|
||||
- driver: nvidia
|
||||
count: all
|
||||
capabilities: [gpu]
|
||||
environment:
|
||||
- NVIDIA_VISIBLE_DEVICES=${NVIDIA_VISIBLE_DEVICES:-all}
|
||||
healthcheck:
|
||||
test: ["CMD", "ollama", "list"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
qdrant:
|
||||
image: qdrant/qdrant:latest
|
||||
container_name: qdrant
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- qdrant_data:/qdrant/storage
|
||||
ports:
|
||||
- "6333:6333" # gRPC API
|
||||
- "6334:6334" # HTTP API (только внутри сети)
|
||||
networks:
|
||||
- iieasy-ai
|
||||
environment:
|
||||
- QDRANT_API_KEY=${QDRANT_API_KEY}
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "timeout 1 bash -c '</dev/tcp/localhost/6333' || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
start_period: 30s
|
||||
|
||||
searxng:
|
||||
image: ghcr.io/searxng/searxng:latest
|
||||
container_name: searxng
|
||||
restart: always
|
||||
volumes:
|
||||
- ./searxng:/etc/searxng:rw
|
||||
- searxng_cache:/var/cache/searxng
|
||||
networks:
|
||||
- iieasy-ai
|
||||
environment:
|
||||
- SEARXNG_BASE_URL=http://searxng:8080/
|
||||
# Ограничиваем ресурсы, чтобы поиск не отъедал лишнего
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 512M
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:8080 || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
start_period: 40s
|
||||
|
||||
open-webui:
|
||||
image: ghcr.io/open-webui/open-webui:v0.8.3
|
||||
container_name: open-webui
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "3001:8080" # Изменено с 3000 на 3001 для избежания конфликтов
|
||||
volumes:
|
||||
- openwebui_data:/app/backend/data
|
||||
- ./media:/app/media:ro # Монтирование медиа-файлов для ребрендинга
|
||||
- ./media:/app/web/static/custom:ro # Монтирование кастомных логотипов для веб-интерфейса
|
||||
networks:
|
||||
- iieasy-ai
|
||||
depends_on:
|
||||
qdrant:
|
||||
condition: service_healthy
|
||||
searxng:
|
||||
condition: service_started
|
||||
environment:
|
||||
# Базовая конфигурация
|
||||
- WEBUI_NAME=iiEasyWeb
|
||||
- WEBUI_URL=${DOMAIN_OPENWEBUI}
|
||||
- ENABLE_SIGNUP=false
|
||||
- DEFAULT_USER_ROLE=user
|
||||
|
||||
# Кастомные логотипы и favicon (через Admin Panel или переменные окружения)
|
||||
# Пути к файлам относительно /static/ или полные URL
|
||||
# Можно настроить через Admin Panel: Settings → Appearance → Logo
|
||||
|
||||
# Qdrant векторная БД
|
||||
- VECTOR_DB=qdrant
|
||||
- QDRANT_URI=http://qdrant:6333
|
||||
- QDRANT_API_KEY=${QDRANT_API_KEY}
|
||||
|
||||
# SearXNG веб-поиск (настройки как в рабочем демо)
|
||||
- RAG_WEB_SEARCH_ENGINE=searxng
|
||||
- SEARXNG_QUERY_URL=http://searxng:8080/search?q=<query>&format=json
|
||||
- ENABLE_WEB_SEARCH=true
|
||||
- WEB_SEARCH_RESULT_COUNT=5
|
||||
- WEB_SEARCH_TRUST_ENV=true
|
||||
- WEB_SEARCH_CONCURRENT_REQUESTS=1
|
||||
- USER_AGENT=Open-WebUI-RAG-Bot
|
||||
|
||||
# Ollama API для работы с изображениями
|
||||
- OLLAMA_BASE_URL=http://ollama:11434
|
||||
|
||||
# Authentik OIDC SSO
|
||||
- OAUTH_CLIENT_ID=${OAUTH_CLIENT_ID}
|
||||
- OAUTH_CLIENT_SECRET=${OAUTH_CLIENT_SECRET}
|
||||
- OAUTH_PROVIDER_NAME=iiEasy ID
|
||||
# Правильный формат для Authentik: полный URL до .well-known/openid-configuration
|
||||
- OPENID_PROVIDER_URL=${OPENID_CONNECT_ISSUER}.well-known/openid-configuration
|
||||
- OPENID_REDIRECT_URI=${DOMAIN_OPENWEBUI}/oauth/oidc/callback
|
||||
- ENABLE_OAUTH_SIGNUP=true
|
||||
- ENABLE_LOGIN_FORM=true
|
||||
# Форма входа включена как fallback, если OAuth не работает
|
||||
# Можно отключить после полной настройки OAuth: ENABLE_LOGIN_FORM=false
|
||||
- OAUTH_MERGE_ACCOUNTS_BY_EMAIL=true
|
||||
|
||||
# Bitwarden CLI интеграция (подготовка)
|
||||
- BW_CLIENTID=${BW_CLIENTID}
|
||||
- BW_CLIENTSECRET=${BW_CLIENTSECRET}
|
||||
|
||||
# Отключение проверки обновлений и аналитики
|
||||
- ENABLE_PERSISTENT_CONFIG=true
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
volumes:
|
||||
ollama_data:
|
||||
driver: local
|
||||
qdrant_data:
|
||||
driver: local
|
||||
searxng_cache:
|
||||
driver: local
|
||||
openwebui_data:
|
||||
driver: local
|
||||
|
||||
networks:
|
||||
iieasy-ai:
|
||||
driver: bridge
|
||||
288
docs/DEPLOYMENT.md
Normal file
288
docs/DEPLOYMENT.md
Normal file
@@ -0,0 +1,288 @@
|
||||
# Инструкция по развёртыванию iiEasyWeb
|
||||
|
||||
Пошаговое развёртывание: Git, Docker, ребрендинг, поиск (SearXNG), воркер Nextcloud (Python).
|
||||
|
||||
---
|
||||
|
||||
## Требования
|
||||
|
||||
- Docker и Docker Compose
|
||||
- (Опционально) NVIDIA GPU и драйверы — для Ollama
|
||||
- Доступ к Gitea (например `192.168.88.165:3000`)
|
||||
- Nextcloud и Authentik — при использовании воркера и OAuth
|
||||
|
||||
---
|
||||
|
||||
## Архитектура
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Reverse Proxy (Nginx) │
|
||||
│ *.iieasy.ru (odo.iieasy.ru) │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌───────────────────┼───────────────────┐
|
||||
│ │ │
|
||||
┌───────▼────────┐ ┌───────▼────────┐ ┌───────▼────────┐
|
||||
│ Open WebUI │ │ Authentik │ │ Nextcloud │
|
||||
│ odo.iieasy.ru │ │ auth.iieasy.ru │ │next.iieasy.ru │
|
||||
└───────┬────────┘ └─────────────────┘ └───────┬────────┘
|
||||
│ │
|
||||
│ ┌──────────────────────────────────────┘
|
||||
│ │
|
||||
▼ ▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Docker Network (iieasy-ai) │
|
||||
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
||||
│ │ Ollama │ │ Qdrant │ │ SearXNG │ │
|
||||
│ │ (GPU) │ │ (Vector) │ │ (Search) │ │
|
||||
│ └──────────┘ └──────────┘ └──────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌───────────────┐
|
||||
│ Python Worker │
|
||||
│ (Nextcloud → │
|
||||
│ Qdrant Sync) │
|
||||
└───────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 1. Git — отправка кода в репозиторий
|
||||
|
||||
**Сервер Gitea:** `192.168.88.165`, веб-интерфейс на порту `3000`.
|
||||
**Репозиторий:** `ars/iiEsaywebUI`.
|
||||
|
||||
### Что не должно попадать в Git
|
||||
|
||||
- `.env`, `worker/.env`
|
||||
- `worker/.venv`, секретные ключи, токены
|
||||
- Убедитесь, что в `.gitignore` есть эти пути (в проекте уже настроено).
|
||||
|
||||
### Вариант A — SSH
|
||||
|
||||
1. Настройте `~/.ssh/config` (при необходимости укажите порт SSH сервера, если не 22):
|
||||
|
||||
```
|
||||
Host 192.168.88.165
|
||||
HostName 192.168.88.165
|
||||
User git
|
||||
Port 22
|
||||
```
|
||||
|
||||
2. URL репозитория: `git@192.168.88.165:ars/iiEsaywebUI.git`
|
||||
|
||||
### Вариант B — HTTPS с токеном
|
||||
|
||||
- URL: `https://192.168.88.165:3000/ars/iiEsaywebUI.git`
|
||||
- При push: логин `ars`, пароль — **подставьте свой токен из Gitea** (или пароль учётной записи, в зависимости от настроек Gitea). Не храните токен в URL в документации.
|
||||
|
||||
### Шаги (если репозиторий ещё не инициализирован)
|
||||
|
||||
```bash
|
||||
cd /home/its/iiEasyWeb
|
||||
git init
|
||||
git remote add origin git@192.168.88.165:ars/iiEsaywebUI.git
|
||||
# или HTTPS: git remote add origin https://192.168.88.165:3000/ars/iiEsaywebUI.git
|
||||
git add .
|
||||
git status # проверьте, что нет .env и секретов
|
||||
git commit -m "Initial deployment setup"
|
||||
git branch -M main
|
||||
git push -u origin main
|
||||
```
|
||||
|
||||
Если ветка по умолчанию на Gitea — `master`, используйте `git push -u origin master`.
|
||||
|
||||
---
|
||||
|
||||
## 2. Docker — поднятие стека
|
||||
|
||||
**Файлы:** корень проекта — `docker-compose.yml`, `.env.example`.
|
||||
|
||||
### Шаги
|
||||
|
||||
1. **Каталог и конфиг:**
|
||||
|
||||
```bash
|
||||
cd /home/its/iiEasyWeb
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
2. **Редактирование `.env`:**
|
||||
- Домены: `DOMAIN_OPENWEBUI`, `DOMAIN_NEXTCLOUD`, `DOMAIN_AUTHENTIK`, при необходимости `DOMAIN_VAULTWARDEN`.
|
||||
- Authentik: `OAUTH_CLIENT_ID`, `OAUTH_CLIENT_SECRET`, `OPENID_CONNECT_ISSUER`.
|
||||
- Qdrant — сгенерировать ключ и прописать в `QDRANT_API_KEY`:
|
||||
|
||||
```bash
|
||||
openssl rand -hex 32
|
||||
```
|
||||
|
||||
- Остальное по необходимости (Nextcloud для воркера, Vaultwarden, Ollama). Для GPU: `NVIDIA_VISIBLE_DEVICES=all`.
|
||||
|
||||
3. **Запуск:**
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
docker compose ps
|
||||
```
|
||||
|
||||
4. **Модель Ollama (после старта контейнера ollama):**
|
||||
|
||||
```bash
|
||||
docker exec ollama ollama pull gemma3n:e4b-it-fp16
|
||||
docker exec ollama ollama list
|
||||
```
|
||||
|
||||
5. **Проверка:** Open WebUI доступен на порту **3001** (маппинг `3001:8080` в `docker-compose.yml`), т.е. `http://localhost:3001` или ваш `DOMAIN_OPENWEBUI`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Ребрендинг
|
||||
|
||||
**Используйте только скрипт:** `scripts/rebrand_safe_final.sh`. Старый `rebrand.sh` может ломать OAuth.
|
||||
|
||||
### Медиафайлы
|
||||
|
||||
Поместите в каталог `media/`:
|
||||
- **Обязательно:** `media/logo.png`
|
||||
- По желанию: `favicon.png`, `logo-dark.svg`, `logo-light.svg`
|
||||
|
||||
### Запуск ребрендинга
|
||||
|
||||
Контейнер `open-webui` должен быть запущен.
|
||||
|
||||
```bash
|
||||
cd /home/its/iiEasyWeb
|
||||
sudo ./scripts/rebrand_safe_final.sh
|
||||
```
|
||||
|
||||
### После обновления образа Open WebUI
|
||||
|
||||
```bash
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
sudo ./scripts/rebrand_safe_final.sh
|
||||
```
|
||||
|
||||
### Проверка
|
||||
|
||||
Очистите кеш браузера и откройте `http://localhost:3001` или `DOMAIN_OPENWEBUI`.
|
||||
|
||||
Подробнее: `QUICK_START.md`, `REBRAND_SOLUTION.md`.
|
||||
|
||||
---
|
||||
|
||||
## 4. Поиск (SearX / SearXNG)
|
||||
|
||||
Поиск уже включён в `docker-compose.yml`: сервис `searxng`, Open WebUI настроен на него (`RAG_WEB_SEARCH_ENGINE=searxng`, `SEARXNG_QUERY_URL=...&format=json`, `ENABLE_WEB_SEARCH=true`).
|
||||
|
||||
### Настройка при развёртывании
|
||||
|
||||
1. **Секретный ключ SearXNG**
|
||||
В `searxng/settings.yml` замените `CHANGE_ME_SECRET_KEY` на свою строку (любая случайная строка). Иначе в логах возможны предупреждения.
|
||||
|
||||
2. **Проверка после старта:**
|
||||
|
||||
```bash
|
||||
docker exec open-webui curl -s "http://searxng:8080/search?q=test&format=json" | head -c 200
|
||||
```
|
||||
|
||||
3. **Если появляется ошибка User-Agent (Invalid leading whitespace):**
|
||||
Используйте проверенный скрипт **fix_user_agent_final.sh**. В `docker-compose.yml` для сервиса `open-webui` добавьте:
|
||||
- volume: `./scripts/fix_user_agent_final.sh:/fix_user_agent_final.sh:ro`
|
||||
- entrypoint: `["/bin/sh", "-c", "sh /fix_user_agent_final.sh && exec /bin/bash /app/start.sh"]`
|
||||
Затем перезапустите: `docker compose restart open-webui`.
|
||||
|
||||
4. **Диагностика:**
|
||||
|
||||
```bash
|
||||
sudo ./scripts/diagnose_search.sh
|
||||
```
|
||||
|
||||
При проблемах: `sudo ./scripts/fix_search_complete.sh`.
|
||||
|
||||
Подробнее: `SEARXNG_SETUP.md`.
|
||||
|
||||
---
|
||||
|
||||
## 5. Nextcloud Python (воркер)
|
||||
|
||||
Воркер синхронизации Nextcloud → Open WebUI/Qdrant запускается на хосте (не в Docker).
|
||||
|
||||
**Каталог:** `worker/`. Файлы: `nextcloud_sync.py`, `.env.example`, `requirements.txt`.
|
||||
|
||||
### Переменные окружения
|
||||
|
||||
```bash
|
||||
cd /home/its/iiEasyWeb/worker
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
Заполните в `.env`:
|
||||
- **Nextcloud:** `DOMAIN_NEXTCLOUD`, `NC_USER`, `NC_APP_PASSWORD`, при необходимости `NC_SCAN_PATHS`.
|
||||
- **Open WebUI:** `DOMAIN_OPENWEBUI`, `OPENWEBUI_API_KEY` (ключ создаётся в Open WebUI: Settings → Account → API Keys).
|
||||
- По желанию: `SYNC_INTERVAL`, `MAX_FILE_SIZE`, `LOG_LEVEL`.
|
||||
|
||||
**Важно:** для Nextcloud используйте **пароль приложения** (App Password), не основной пароль учётной записи.
|
||||
|
||||
### Установка и запуск
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
python nextcloud_sync.py --once
|
||||
```
|
||||
|
||||
Постоянный режим (daemon):
|
||||
|
||||
```bash
|
||||
python nextcloud_sync.py --daemon
|
||||
```
|
||||
|
||||
### Production (systemd)
|
||||
|
||||
Создайте файл `/etc/systemd/system/iieasy-sync.service`:
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=iiEasy Nextcloud Sync Worker
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=its
|
||||
WorkingDirectory=/home/its/iiEasyWeb/worker
|
||||
ExecStart=/usr/bin/python3 /home/its/iiEasyWeb/worker/nextcloud_sync.py --daemon
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
Замените `User=its` и пути при необходимости. Затем:
|
||||
|
||||
```bash
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable iieasy-sync
|
||||
sudo systemctl start iieasy-sync
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Порядок выполнения
|
||||
|
||||
1. **Git** — инициализация репозитория, remote, первый push (без `.env` и секретов).
|
||||
2. **Docker** — `cp .env.example .env`, правка `.env`, генерация `QDRANT_API_KEY`, `docker compose up -d`, загрузка модели Ollama.
|
||||
3. Первый вход в Open WebUI (при необходимости настройка Authentik OIDC), создание API ключа в Settings → Account → API Keys.
|
||||
4. **Ребрендинг** — файлы в `media/`, `sudo ./scripts/rebrand_safe_final.sh`.
|
||||
5. **Поиск** — правка `searxng/settings.yml` (secret_key), при ошибке User-Agent — entrypoint с `fix_user_agent_final.sh`; при проблемах — `diagnose_search.sh`, `fix_search_complete.sh`.
|
||||
6. **Nextcloud Python** — настройка `worker/.env`, `pip install -r requirements.txt`, проверка `--once`, затем `--daemon` или systemd.
|
||||
|
||||
---
|
||||
|
||||
## Замечания
|
||||
|
||||
- **Токен/пароль Gitea:** в документации не указывайте токен в открытом виде; используйте «подставить свой токен из Gitea» или SSH.
|
||||
- **Имя репозитория:** на сервере используется `iiEsaywebUI` (с одной «a») — в URL remote указывайте так же.
|
||||
- **Порт SSH:** если Gitea слушает SSH на порту не 22 (например 3000), укажите в `~/.ssh/config` для хоста `Port 3000`; URL остаётся `git@192.168.88.165:ars/iiEsaywebUI.git`.
|
||||
51
docs/TTS_CMU_ARCTIC_SPEAKERS.md
Normal file
51
docs/TTS_CMU_ARCTIC_SPEAKERS.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# TTS в Open WebUI: английские и русские голоса
|
||||
|
||||
## Transformers (Локально) — только английский
|
||||
|
||||
В настройках **Настройки → Речь** при выборе системы синтеза речи **«Transformers (Локально)»** поле **«Модель TTS»** не является выпадающим списком — в него нужно **вручную ввести** имя спикера из набора CMU ARCTIC. Эти голоса **только для английского языка**, русского нет.
|
||||
|
||||
### Доступные имена (вводить латиницей)
|
||||
|
||||
| Имя | Описание |
|
||||
|------|------------------------|
|
||||
| `bdl` | Мужской голос (США) |
|
||||
| `slt` | Женский голос (США) |
|
||||
| `clb` | Женский голос (США) |
|
||||
| `rms` | Мужской голос (США) |
|
||||
| `awb` | Мужской голос (шотландский) |
|
||||
| `jmk` | Мужской голос (канадский) |
|
||||
| `ksp` | Мужской голос (индийский) |
|
||||
|
||||
Источник: [Matthijs/cmu-arctic-xvectors](https://huggingface.co/datasets/Matthijs/cmu-arctic-xvectors)
|
||||
|
||||
Рекомендация: начните с **`bdl`** или **`slt`**. При первом использовании TTS Open WebUI может скачать модели с Hugging Face.
|
||||
|
||||
---
|
||||
|
||||
## Русский TTS: Edge TTS (рекомендуется)
|
||||
|
||||
Для **русской** озвучки нужен другой движок. Удобный бесплатный вариант — **Edge TTS** (голоса Microsoft). В проекте он уже добавлен в `docker-compose.yml` как сервис `openai-edge-tts`.
|
||||
|
||||
### Что сделать в интерфейсе
|
||||
|
||||
1. **Настройки → Речь (Audio)**
|
||||
2. **Система синтеза речи:** выберите **OpenAI** (или пункт, где задаётся URL API).
|
||||
3. Укажите:
|
||||
- **TTS API URL:** `http://openai-edge-tts:5050/v1` (если Open WebUI в том же Docker Compose).
|
||||
- **API ключ:** `your_api_key_here` (дефолтный ключ Edge TTS).
|
||||
4. **Голос TTS** — введите один из русских голосов:
|
||||
- **`ru-RU-SvetlanaNeural`** — женский
|
||||
- **`ru-RU-DmitryNeural`** — мужской
|
||||
5. Сохраните настройки.
|
||||
|
||||
Все голоса Edge TTS (включая другие языки) можно послушать и выбрать: [tts.travisvn.com](https://tts.travisvn.com/).
|
||||
|
||||
### Если Edge TTS ещё не запущен
|
||||
|
||||
Из каталога проекта:
|
||||
|
||||
```bash
|
||||
docker compose up -d openai-edge-tts
|
||||
```
|
||||
|
||||
После этого снова откройте настройки речи и выберите голос `ru-RU-SvetlanaNeural` или `ru-RU-DmitryNeural`.
|
||||
24
instr
Normal file
24
instr
Normal file
@@ -0,0 +1,24 @@
|
||||
# === ДОМЕНЫ И СЕТЬ ===
|
||||
DOMAIN_OPENWEBUI=https://odo.iieasy.ru
|
||||
DOMAIN_NEXTCLOUD=https://cloud.iieasy.ru
|
||||
DOMAIN_AUTHENTIK=https://auth.iieasy.ru
|
||||
|
||||
# === AUTHENTIK (OIDC SSO) ===
|
||||
# Где брать: Authentik -> Providers -> твой OIDC Provider
|
||||
OAUTH_CLIENT_ID=твой_client_id_из_authentik
|
||||
OAUTH_CLIENT_SECRET=твой_client_secret_из_authentik
|
||||
OPENID_CONNECT_ISSUER=https://auth.iieasy.ru/application/o/open-webui/
|
||||
|
||||
# === NEXTCLOUD (Для Python-воркера) ===
|
||||
# Где брать: Nextcloud -> Настройки -> Безопасность -> Устройства и сессии (Создать пароль приложения)
|
||||
NC_USER=твой_логин
|
||||
NC_APP_PASSWORD=твой_пароль_приложения_nextcloud # Строго пароль приложения, не основной!
|
||||
|
||||
# === OPEN WEBUI API (Для пуша файлов в Qdrant) ===
|
||||
# Где брать: Open WebUI -> Settings -> Account -> API Keys (создашь после первого запуска)
|
||||
OPENWEBUI_API_KEY=твой_api_ключ_от_openwebui
|
||||
|
||||
# === VAULTWARDEN (Для интеграции Bitwarden CLI в будущем) ===
|
||||
# Где брать: Vaultwarden -> Настройки аккаунта -> Безопасность -> Ключи -> API-ключ
|
||||
BW_CLIENTID=user.твой_id
|
||||
BW_CLIENTSECRET=твой_bw_secret
|
||||
BIN
media/favicon.png
Normal file
BIN
media/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 49 KiB |
6
media/favicon.svg
Normal file
6
media/favicon.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg width="64" height="64" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- Favicon iiEasy - упрощенная версия логотипа -->
|
||||
<circle cx="100" cy="100" r="70" stroke="#1F2937" stroke-width="6" fill="none" stroke-dasharray="15 85"/>
|
||||
<circle cx="100" cy="100" r="50" stroke="#1F2937" stroke-width="6" fill="none" stroke-dasharray="12 58" transform="rotate(30 100 100)"/>
|
||||
<circle cx="100" cy="100" r="30" stroke="#1F2937" stroke-width="6" fill="none" stroke-dasharray="8 32" transform="rotate(60 100 100)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 577 B |
35
media/init-logos.sh
Executable file
35
media/init-logos.sh
Executable file
@@ -0,0 +1,35 @@
|
||||
#!/bin/bash
|
||||
# Скрипт для автоматической замены логотипов при запуске контейнера
|
||||
# Этот скрипт можно запускать при каждом старте контейнера
|
||||
|
||||
MEDIA_DIR="/app/media"
|
||||
MAX_RETRIES=10
|
||||
RETRY_DELAY=2
|
||||
|
||||
# Ждем пока контейнер полностью запустится
|
||||
for i in $(seq 1 $MAX_RETRIES); do
|
||||
if curl -f http://localhost:8080/health >/dev/null 2>&1; then
|
||||
break
|
||||
fi
|
||||
sleep $RETRY_DELAY
|
||||
done
|
||||
|
||||
# Находим все favicon и logo файлы
|
||||
find /app -type f \( -name "favicon.png" -o -name "favicon.ico" -o -name "logo.png" -o -name "logo.svg" \) 2>/dev/null | while read file; do
|
||||
dir=$(dirname "$file")
|
||||
name=$(basename "$file")
|
||||
|
||||
# Заменяем favicon
|
||||
if [[ "$name" == favicon* ]] && [ -f "$MEDIA_DIR/favicon.png" ]; then
|
||||
cp "$MEDIA_DIR/favicon.png" "$file" 2>/dev/null || true
|
||||
# Также создаем .ico
|
||||
cp "$MEDIA_DIR/favicon.png" "$dir/favicon.ico" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Заменяем logo
|
||||
if [[ "$name" == logo* ]] && [ -f "$MEDIA_DIR/logo.png" ]; then
|
||||
cp "$MEDIA_DIR/logo.png" "$file" 2>/dev/null || true
|
||||
# Также создаем .svg
|
||||
cp "$MEDIA_DIR/logo.png" "$dir/logo.svg" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
6
media/logo-dark.svg
Normal file
6
media/logo-dark.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- Логотип iiEasy - темная тема -->
|
||||
<circle cx="100" cy="100" r="70" stroke="#FFFFFF" stroke-width="6" fill="none" stroke-dasharray="15 85"/>
|
||||
<circle cx="100" cy="100" r="50" stroke="#FFFFFF" stroke-width="6" fill="none" stroke-dasharray="12 58" transform="rotate(30 100 100)"/>
|
||||
<circle cx="100" cy="100" r="30" stroke="#FFFFFF" stroke-width="6" fill="none" stroke-dasharray="8 32" transform="rotate(60 100 100)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 557 B |
6
media/logo-light.svg
Normal file
6
media/logo-light.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- Логотип iiEasy - светлая тема -->
|
||||
<circle cx="100" cy="100" r="70" stroke="#1F2937" stroke-width="6" fill="none" stroke-dasharray="15 85"/>
|
||||
<circle cx="100" cy="100" r="50" stroke="#1F2937" stroke-width="6" fill="none" stroke-dasharray="12 58" transform="rotate(30 100 100)"/>
|
||||
<circle cx="100" cy="100" r="30" stroke="#1F2937" stroke-width="6" fill="none" stroke-dasharray="8 32" transform="rotate(60 100 100)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 559 B |
BIN
media/logo.png
Normal file
BIN
media/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 49 KiB |
41
media/Лого светлый справа слоган.md
Normal file
41
media/Лого светлый справа слоган.md
Normal file
@@ -0,0 +1,41 @@
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>iiEasy Logo - Right Aligned with Styled Slogan</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;700;800&display=swap');
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
.slogan-style {
|
||||
letter-spacing: 0.25em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="flex items-center justify-center min-h-screen p-8 bg-white">
|
||||
<div class="flex items-center">
|
||||
<!-- Логотип (SVG) -->
|
||||
<svg width="180" height="180" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg" class="mr-10">
|
||||
<!-- Внешнее кольцо -->
|
||||
<circle cx="100" cy="100" r="70" stroke="#111827" stroke-width="6" fill="none" stroke-dasharray="15 85"/>
|
||||
<!-- Среднее кольцо -->
|
||||
<circle cx="100" cy="100" r="50" stroke="#111827" stroke-width="6" fill="none" stroke-dasharray="12 58" transform="rotate(30 100 100)"/>
|
||||
<!-- Внутреннее кольцо -->
|
||||
<circle cx="100" cy="100" r="30" stroke="#111827" stroke-width="6" fill="none" stroke-dasharray="8 32" transform="rotate(60 100 100)"/>
|
||||
</svg>
|
||||
|
||||
<!-- Текстовый блок справа -->
|
||||
<div class="flex flex-col border-l border-gray-300 pl-10">
|
||||
<div class="text-3xl font-bold text-gray-900 mb-2">iiEasy</div>
|
||||
<div class="text-lg text-gray-600">Будущее. Просто.</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
32
media/Логотип светлый svg.md
Normal file
32
media/Логотип светлый svg.md
Normal file
@@ -0,0 +1,32 @@
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>AI Logo - Variant 40 (Kinetic Rings - Compact)</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<style>
|
||||
/* Устанавливаем шрифт Inter для всего тела документа */
|
||||
body { font-family: 'Inter', sans-serif; background-color: #FFFFFF; /* Изменено на белый фон */ }
|
||||
</style>
|
||||
</head>
|
||||
<body class="flex flex-col items-center justify-center min-h-screen p-8 bg-white text-gray-900">
|
||||
<!-- SVG-иконка: Несколько открытых колец или дуг, расположенных так, чтобы создать ощущение движения. -->
|
||||
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg" class="mb-4">
|
||||
<!-- Уменьшены радиусы для всех трех кругов, чтобы сделать их более сбитыми -->
|
||||
<!-- Первый круг (самый внешний, теперь более компактный) -->
|
||||
<circle cx="100" cy="100" r="70" stroke="#1F2937" stroke-width="6" fill="none" stroke-dasharray="15 85"/>
|
||||
<!-- Второй круг (средний, теперь более компактный) -->
|
||||
<circle cx="100" cy="100" r="50" stroke="#1F2937" stroke-width="6" fill="none" stroke-dasharray="12 58" transform="rotate(30 100 100)"/>
|
||||
<!-- Третий круг (самый внутренний, теперь более компактный) -->
|
||||
<circle cx="100" cy="100" r="30" stroke="#1F2937" stroke-width="6" fill="none" stroke-dasharray="8 32" transform="rotate(60 100 100)"/>
|
||||
</svg>
|
||||
|
||||
<!-- Название компании -->
|
||||
<div class="text-3xl font-bold text-gray-900 mb-2">iiEasy</div>
|
||||
<!-- Слоган компании -->
|
||||
<div class="text-lg text-gray-600">Будущее. Просто.</div>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
32
media/Логотип светлый справа svg.md
Normal file
32
media/Логотип светлый справа svg.md
Normal file
@@ -0,0 +1,32 @@
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>AI Logo - Variant 40 (Kinetic Rings - Compact) - Light, Right Aligned Text</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<style>
|
||||
/* Устанавливаем шрифт Inter для всего тела документа */
|
||||
body { font-family: 'Inter', sans-serif; background-color: #FFFFFF; /* Светлый фон */ }
|
||||
</style>
|
||||
</head>
|
||||
<body class="flex items-center justify-center min-h-screen p-8 bg-white text-gray-800">
|
||||
<!-- SVG-иконка: Несколько открытых колец или дуг, расположенных так, чтобы создать ощущение движения. -->
|
||||
<!-- Добавлен margin-right для отступа от текста -->
|
||||
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg" class="mr-6">
|
||||
<!-- Уменьшены радиусы для всех трех кругов, чтобы сделать их более сбитыми -->
|
||||
<!-- Первый круг (самый внешний, теперь более компактный) -->
|
||||
<circle cx="100" cy="100" r="70" stroke="#1F2937" stroke-width="6" fill="none" stroke-dasharray="15 85"/>
|
||||
<!-- Второй круг (средний, теперь более компактный) -->
|
||||
<circle cx="100" cy="100" r="50" stroke="#1F2937" stroke-width="6" fill="none" stroke-dasharray="12 58" transform="rotate(30 100 100)"/>
|
||||
<!-- Третий круг (самый внутренний, теперь более компактный) -->
|
||||
<circle cx="100" cy="100" r="30" stroke="#1F2937" stroke-width="6" fill="none" stroke-dasharray="8 32" transform="rotate(60 100 100)"/>
|
||||
</svg>
|
||||
|
||||
<!-- Название компании -->
|
||||
<!-- Слоган удален, размер текста увеличен -->
|
||||
<div class="text-5xl font-bold text-gray-900">iiEasy</div>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
32
media/Логотип тёмный svg.md
Normal file
32
media/Логотип тёмный svg.md
Normal file
@@ -0,0 +1,32 @@
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>AI Logo - Variant 40 (Kinetic Rings - Compact) - Dark</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<style>
|
||||
/* Устанавливаем шрифт Inter для всего тела документа */
|
||||
body { font-family: 'Inter', sans-serif; background-color: #000000; /* Изменено на черный фон */ }
|
||||
</style>
|
||||
</head>
|
||||
<body class="flex flex-col items-center justify-center min-h-screen p-8 bg-black text-white">
|
||||
<!-- SVG-иконка: Несколько открытых колец или дуг, расположенных так, чтобы создать ощущение движения. -->
|
||||
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg" class="mb-4">
|
||||
<!-- Уменьшены радиусы для всех трех кругов, чтобы сделать их более сбитыми -->
|
||||
<!-- Первый круг (самый внешний, теперь более компактный) -->
|
||||
<circle cx="100" cy="100" r="70" stroke="#FFFFFF" stroke-width="6" fill="none" stroke-dasharray="15 85"/>
|
||||
<!-- Второй круг (средний, теперь более компактный) -->
|
||||
<circle cx="100" cy="100" r="50" stroke="#FFFFFF" stroke-width="6" fill="none" stroke-dasharray="12 58" transform="rotate(30 100 100)"/>
|
||||
<!-- Третий круг (самый внутренний, теперь более компактный) -->
|
||||
<circle cx="100" cy="100" r="30" stroke="#FFFFFF" stroke-width="6" fill="none" stroke-dasharray="8 32" transform="rotate(60 100 100)"/>
|
||||
</svg>
|
||||
|
||||
<!-- Название компании -->
|
||||
<div class="text-3xl font-bold text-white mb-2">iiEasy</div>
|
||||
<!-- Слоган компании -->
|
||||
<div class="text-lg text-gray-300">Будущее. Просто.</div>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
32
media/Логотип тёмный справа avg.md
Normal file
32
media/Логотип тёмный справа avg.md
Normal file
@@ -0,0 +1,32 @@
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>AI Logo - Variant 40 (Kinetic Rings - Compact) - Dark, Right Aligned Text</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<style>
|
||||
/* Устанавливаем шрифт Inter для всего тела документа */
|
||||
body { font-family: 'Inter', sans-serif; background-color: #000000; /* Черный фон */ }
|
||||
</style>
|
||||
</head>
|
||||
<body class="flex items-center justify-center min-h-screen p-8 bg-black text-white">
|
||||
<!-- SVG-иконка: Несколько открытых колец или дуг, расположенных так, чтобы создать ощущение движения. -->
|
||||
<!-- Добавлен margin-right для отступа от текста -->
|
||||
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg" class="mr-6">
|
||||
<!-- Уменьшены радиусы для всех трех кругов, чтобы сделать их более сбитыми -->
|
||||
<!-- Первый круг (самый внешний, теперь более компактный) -->
|
||||
<circle cx="100" cy="100" r="70" stroke="#FFFFFF" stroke-width="6" fill="none" stroke-dasharray="15 85"/>
|
||||
<!-- Второй круг (средний, теперь более компактный) -->
|
||||
<circle cx="100" cy="100" r="50" stroke="#FFFFFF" stroke-width="6" fill="none" stroke-dasharray="12 58" transform="rotate(30 100 100)"/>
|
||||
<!-- Третий круг (самый внутренний, теперь более компактный) -->
|
||||
<circle cx="100" cy="100" r="30" stroke="#FFFFFF" stroke-width="6" fill="none" stroke-dasharray="8 32" transform="rotate(60 100 100)"/>
|
||||
</svg>
|
||||
|
||||
<!-- Название компании -->
|
||||
<!-- Слоган удален, размер текста увеличен -->
|
||||
<div class="text-5xl font-bold text-white">iiEasy</div>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
42
media/лого темный справа слоган.md
Normal file
42
media/лого темный справа слоган.md
Normal file
@@ -0,0 +1,42 @@
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>iiEasy Logo - Right Aligned Dark with Styled Slogan</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;700;800&display=swap');
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
background-color: #000000;
|
||||
}
|
||||
.slogan-style {
|
||||
letter-spacing: 0.25em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="flex items-center justify-center min-h-screen p-8 bg-black">
|
||||
<div class="flex items-center">
|
||||
<!-- Логотип (SVG) - Белый -->
|
||||
<svg width="180" height="180" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg" class="mr-10">
|
||||
<!-- Внешнее кольцо -->
|
||||
<circle cx="100" cy="100" r="70" stroke="#FFFFFF" stroke-width="6" fill="none" stroke-dasharray="15 85"/>
|
||||
<!-- Среднее кольцо -->
|
||||
<circle cx="100" cy="100" r="50" stroke="#FFFFFF" stroke-width="6" fill="none" stroke-dasharray="12 58" transform="rotate(30 100 100)"/>
|
||||
<!-- Внутреннее кольцо -->
|
||||
<circle cx="100" cy="100" r="30" stroke="#FFFFFF" stroke-width="6" fill="none" stroke-dasharray="8 32" transform="rotate(60 100 100)"/>
|
||||
</svg>
|
||||
|
||||
<!-- Текстовый блок справа - Белый -->
|
||||
<div class="flex flex-col border-l border-gray-700 pl-10">
|
||||
<div class="text-3xl font-bold text-white mb-2">iiEasy</div>
|
||||
<!-- Слоган компании -->
|
||||
<div class="text-lg text-gray-300">Будущее. Просто.</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
75
media/логотип анимация .md
Normal file
75
media/логотип анимация .md
Normal file
@@ -0,0 +1,75 @@
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Анимированный логотип - iiEasy (Горизонтальная темная версия)</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
background-color: #000000; /* Черный фон */
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="flex items-center justify-center min-h-screen bg-black p-8">
|
||||
|
||||
<!-- Основной контейнер для горизонтального расположения -->
|
||||
<div class="flex items-center space-x-6">
|
||||
|
||||
<!--
|
||||
SVG-иконка: Анимированные кинетические кольца (инвертированная версия).
|
||||
Размер уменьшен для гармоничного сочетания с текстом.
|
||||
-->
|
||||
<svg width="100" height="100" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
|
||||
<!-- Внешнее кольцо -->
|
||||
<circle cx="100" cy="100" r="70" stroke="#FFFFFF" stroke-width="6" fill="none" stroke-dasharray="15 85">
|
||||
<animateTransform
|
||||
attributeName="transform"
|
||||
attributeType="XML"
|
||||
type="rotate"
|
||||
from="0 100 100"
|
||||
to="360 100 100"
|
||||
dur="10s"
|
||||
repeatCount="indefinite" />
|
||||
</circle>
|
||||
|
||||
<!-- Среднее кольцо -->
|
||||
<circle cx="100" cy="100" r="50" stroke="#FFFFFF" stroke-width="6" fill="none" stroke-dasharray="12 58" transform="rotate(30 100 100)">
|
||||
<animateTransform
|
||||
attributeName="transform"
|
||||
attributeType="XML"
|
||||
type="rotate"
|
||||
from="30 100 100"
|
||||
to="-330 100 100"
|
||||
dur="12s"
|
||||
repeatCount="indefinite" />
|
||||
</circle>
|
||||
|
||||
<!-- Внутреннее кольцо -->
|
||||
<circle cx="100" cy="100" r="30" stroke="#FFFFFF" stroke-width="6" fill="none" stroke-dasharray="8 32" transform="rotate(60 100 100)">
|
||||
<animateTransform
|
||||
attributeName="transform"
|
||||
attributeType="XML"
|
||||
type="rotate"
|
||||
from="60 100 100"
|
||||
to="420 100 100"
|
||||
dur="8s"
|
||||
repeatCount="indefinite" />
|
||||
</circle>
|
||||
|
||||
</svg>
|
||||
|
||||
<!-- Название бренда (белый текст) -->
|
||||
<h1 class="text-6xl font-bold text-white tracking-tight">iiEasy</h1>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
78
media/логотип анимация2.md
Normal file
78
media/логотип анимация2.md
Normal file
@@ -0,0 +1,78 @@
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Анимированный логотип - iiEasy (Темная версия)</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
background-color: #000000; /* Черный фон */
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="flex items-center justify-center min-h-screen bg-black p-8">
|
||||
|
||||
<!-- Основной контейнер для центрирования контента по вертикали -->
|
||||
<div class="flex flex-col items-center text-center">
|
||||
|
||||
<!--
|
||||
SVG-иконка: Анимированные кинетические кольца (инвертированная версия).
|
||||
Все штрихи сделаны белыми для контраста с черным фоном.
|
||||
-->
|
||||
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
|
||||
<!-- Внешнее кольцо -->
|
||||
<circle cx="100" cy="100" r="70" stroke="#FFFFFF" stroke-width="6" fill="none" stroke-dasharray="15 85">
|
||||
<animateTransform
|
||||
attributeName="transform"
|
||||
attributeType="XML"
|
||||
type="rotate"
|
||||
from="0 100 100"
|
||||
to="360 100 100"
|
||||
dur="10s"
|
||||
repeatCount="indefinite" />
|
||||
</circle>
|
||||
|
||||
<!-- Среднее кольцо -->
|
||||
<circle cx="100" cy="100" r="50" stroke="#FFFFFF" stroke-width="6" fill="none" stroke-dasharray="12 58" transform="rotate(30 100 100)">
|
||||
<animateTransform
|
||||
attributeName="transform"
|
||||
attributeType="XML"
|
||||
type="rotate"
|
||||
from="30 100 100"
|
||||
to="-330 100 100"
|
||||
dur="12s"
|
||||
repeatCount="indefinite" />
|
||||
</circle>
|
||||
|
||||
<!-- Внутреннее кольцо -->
|
||||
<circle cx="100" cy="100" r="30" stroke="#FFFFFF" stroke-width="6" fill="none" stroke-dasharray="8 32" transform="rotate(60 100 100)">
|
||||
<animateTransform
|
||||
attributeName="transform"
|
||||
attributeType="XML"
|
||||
type="rotate"
|
||||
from="60 100 100"
|
||||
to="420 100 100"
|
||||
dur="8s"
|
||||
repeatCount="indefinite" />
|
||||
</circle>
|
||||
|
||||
</svg>
|
||||
|
||||
<!-- Название бренда (белый текст) -->
|
||||
<h1 class="text-5xl font-bold text-white mt-8 tracking-tight">iiEasy</h1>
|
||||
|
||||
<!-- Новый слоган (светло-серый текст для легкого контраста) -->
|
||||
<p class="text-xl font-medium text-gray-300 mt-2 tracking-wide">Будущее. Просто.</p>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
143
nginx-proxy-manager-config.md
Normal file
143
nginx-proxy-manager-config.md
Normal file
@@ -0,0 +1,143 @@
|
||||
# Конфигурация Nginx Proxy Manager для odo.iieasy.ru
|
||||
|
||||
## Настройка прокси для Open WebUI
|
||||
|
||||
### Шаг 1: Создание Proxy Host в Nginx Proxy Manager
|
||||
|
||||
1. Войдите в Nginx Proxy Manager (обычно `http://your-server-ip:81`)
|
||||
2. Перейдите в **Proxy Hosts** → **Add Proxy Host**
|
||||
|
||||
### Шаг 2: Настройка Details
|
||||
|
||||
- **Domain Names**: `odo.iieasy.ru`
|
||||
- **Scheme**: `http`
|
||||
- **Forward Hostname/IP**: `open-webui` (имя контейнера Docker) или `localhost`
|
||||
- **Forward Port**: `3001` (изменено с 3000)
|
||||
- **Cache Assets**: Включено (опционально)
|
||||
- **Block Common Exploits**: Включено
|
||||
- **Websockets Support**: **ВКЛЮЧЕНО** (важно для Open WebUI!)
|
||||
|
||||
### Шаг 3: Настройка SSL
|
||||
|
||||
**ВАЖНО:** SSL сертификат должен быть настроен ДО того, как вы сможете открыть сайт по HTTPS.
|
||||
|
||||
#### Вариант A: Использование Let's Encrypt (рекомендуется)
|
||||
|
||||
1. В Nginx Proxy Manager перейдите в **SSL Certificates** → **Add SSL Certificate**
|
||||
2. Выберите **Let's Encrypt**
|
||||
3. Заполните:
|
||||
- **Domain Names**: `odo.iieasy.ru` (можно добавить несколько через запятую)
|
||||
- **Email Address**: Ваш email (для уведомлений об истечении сертификата)
|
||||
- **Agree to Let's Encrypt Terms**: Включено
|
||||
- **Use a DNS Challenge**: Обычно НЕ нужно (используется HTTP challenge)
|
||||
4. Нажмите **Save**
|
||||
5. Дождитесь создания сертификата (может занять 1-2 минуты)
|
||||
|
||||
**Требования для Let's Encrypt:**
|
||||
- Домен `odo.iieasy.ru` должен указывать на IP вашего сервера (A-запись в DNS)
|
||||
- Порт 80 должен быть доступен из интернета (для HTTP challenge)
|
||||
- Nginx Proxy Manager должен быть доступен на порту 80
|
||||
|
||||
#### Вариант B: Использование существующего сертификата
|
||||
|
||||
Если у вас уже есть SSL сертификат:
|
||||
1. **SSL Certificates** → **Add SSL Certificate** → **Custom**
|
||||
2. Вставьте содержимое файлов:
|
||||
- **Certificate**: содержимое `.crt` или `.pem` файла
|
||||
- **Private Key**: содержимое `.key` файла
|
||||
3. Нажмите **Save**
|
||||
|
||||
#### Вариант C: Временное отключение SSL (только для тестирования)
|
||||
|
||||
Если нужно быстро проверить работу без SSL:
|
||||
1. В Proxy Host настройках убедитесь, что SSL не включен
|
||||
2. Используйте HTTP вместо HTTPS: `http://odo.iieasy.ru`
|
||||
|
||||
### Шаг 4: Применение SSL к Proxy Host
|
||||
|
||||
После создания SSL сертификата:
|
||||
|
||||
1. Вернитесь к вашему Proxy Host для `odo.iieasy.ru`
|
||||
2. Перейдите на вкладку **SSL**
|
||||
3. Выберите созданный сертификат в поле **SSL Certificate**
|
||||
4. Включите:
|
||||
- **Force SSL**: Включено (перенаправляет HTTP на HTTPS)
|
||||
- **HTTP/2 Support**: Включено
|
||||
- **HSTS Enabled**: Включено (опционально)
|
||||
- **HSTS Subdomains**: Включено (опционально, если используете поддомены)
|
||||
5. Нажмите **Save**
|
||||
|
||||
### Шаг 5: Настройка Advanced (Custom Nginx Configuration)
|
||||
|
||||
**ВАЖНО:** Nginx Proxy Manager автоматически управляет большинством настроек. Custom Configuration обычно НЕ нужна и может вызвать ошибки 500.
|
||||
|
||||
**Оставьте поле Custom Nginx Configuration ПУСТЫМ**, если все работает без него.
|
||||
|
||||
Если все же нужны дополнительные настройки (только при проблемах), добавьте минимальную конфигурацию:
|
||||
|
||||
```nginx
|
||||
# Увеличение размера тела запроса для загрузки файлов (если нужно)
|
||||
client_max_body_size 100M;
|
||||
|
||||
# Увеличенные таймауты (только если есть проблемы с таймаутами)
|
||||
proxy_read_timeout 300s;
|
||||
```
|
||||
|
||||
**Но лучше оставить пустым** - Nginx Proxy Manager сам настроит WebSocket, заголовки и другие параметры через интерфейс.
|
||||
|
||||
### Шаг 6: Если используется Docker сеть
|
||||
|
||||
Если Nginx Proxy Manager работает в той же Docker сети, что и Open WebUI:
|
||||
|
||||
1. В **Forward Hostname/IP** укажите: `open-webui` (имя контейнера)
|
||||
2. В **Forward Port** укажите: `8080` (внутренний порт контейнера)
|
||||
|
||||
Если Nginx Proxy Manager работает на хосте:
|
||||
|
||||
1. В **Forward Hostname/IP** укажите: `localhost` или `127.0.0.1`
|
||||
2. В **Forward Port** укажите: `3001` (порт на хосте)
|
||||
|
||||
## Проверка работы
|
||||
|
||||
После настройки проверьте:
|
||||
|
||||
1. Откройте `https://odo.iieasy.ru` в браузере
|
||||
2. Проверьте логи Nginx Proxy Manager при ошибках
|
||||
3. Проверьте логи Open WebUI: `docker compose logs open-webui`
|
||||
|
||||
## Устранение проблем
|
||||
|
||||
### ERR_SSL_UNRECOGNIZED_NAME_ALERT
|
||||
|
||||
Эта ошибка означает, что SSL сертификат не настроен или не соответствует домену.
|
||||
|
||||
**Решение:**
|
||||
1. Проверьте, что SSL сертификат создан в Nginx Proxy Manager
|
||||
2. Убедитесь, что сертификат применен к Proxy Host (вкладка SSL)
|
||||
3. Проверьте, что домен `odo.iieasy.ru` указан в сертификате
|
||||
4. Если используете Let's Encrypt, проверьте:
|
||||
- DNS A-запись для `odo.iieasy.ru` указывает на IP сервера
|
||||
- Порт 80 доступен из интернета
|
||||
- Домен не заблокирован файрволом
|
||||
|
||||
**Временное решение для тестирования:**
|
||||
- Отключите SSL в Proxy Host
|
||||
- Используйте HTTP: `http://odo.iieasy.ru`
|
||||
- После настройки SSL включите обратно
|
||||
|
||||
### Ошибка 502 Bad Gateway
|
||||
|
||||
- Убедитесь, что контейнер `open-webui` запущен: `docker ps | grep open-webui`
|
||||
- Проверьте, что порт 3001 доступен: `curl http://localhost:3001`
|
||||
- Проверьте логи: `docker compose logs open-webui`
|
||||
|
||||
### WebSocket не работает
|
||||
|
||||
- Убедитесь, что **Websockets Support** включен в Nginx Proxy Manager
|
||||
- **НЕ добавляйте** WebSocket настройки в Custom Configuration - они конфликтуют с автоматическими настройками NPM
|
||||
|
||||
### Медленная загрузка
|
||||
|
||||
- Проверьте логи: `docker compose logs open-webui`
|
||||
- Убедитесь, что контейнер Open WebUI работает нормально
|
||||
- Если нужны большие файлы, добавьте только `client_max_body_size 100M;` в Custom Configuration (но лучше оставить пустым)
|
||||
79
scripts/apply_logos_persistent.sh
Executable file
79
scripts/apply_logos_persistent.sh
Executable file
@@ -0,0 +1,79 @@
|
||||
#!/bin/bash
|
||||
# Скрипт для постоянной замены логотипов через volume монтирование
|
||||
# Этот скрипт копирует файлы в volume, который сохраняется между перезапусками
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
MEDIA_DIR="$PROJECT_DIR/media"
|
||||
CONTAINER_NAME="open-webui"
|
||||
|
||||
echo "=== Применение логотипов (постоянное решение) ==="
|
||||
|
||||
# Проверка наличия контейнера
|
||||
if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
||||
echo "Ошибка: Контейнер ${CONTAINER_NAME} не запущен."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "1. Поиск существующих favicon и logo файлов..."
|
||||
|
||||
# Находим все существующие favicon и logo файлы
|
||||
EXISTING_FAVICONS=$(docker exec "${CONTAINER_NAME}" find /app -type f \( -name "favicon.png" -o -name "favicon.ico" -o -name "favicon.svg" -o -name "favicon-96x96.png" \) 2>/dev/null | head -20)
|
||||
EXISTING_LOGOS=$(docker exec "${CONTAINER_NAME}" find /app -type f \( -name "logo.png" -o -name "logo.svg" \) 2>/dev/null | head -20)
|
||||
|
||||
echo " Найдено favicon файлов: $(echo "$EXISTING_FAVICONS" | grep -v '^$' | wc -l)"
|
||||
echo " Найдено logo файлов: $(echo "$EXISTING_LOGOS" | grep -v '^$' | wc -l)"
|
||||
|
||||
# Заменяем все найденные favicon файлы
|
||||
if [ -f "$MEDIA_DIR/favicon.png" ]; then
|
||||
echo "2. Замена всех favicon файлов на favicon.png..."
|
||||
echo "$EXISTING_FAVICONS" | while read -r favicon_file; do
|
||||
if [ -n "$favicon_file" ]; then
|
||||
echo " → $favicon_file"
|
||||
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${favicon_file}" 2>/dev/null || true
|
||||
# Также создаем .ico версию в той же директории
|
||||
favicon_dir=$(dirname "$favicon_file")
|
||||
favicon_name=$(basename "$favicon_file" | sed 's/\.[^.]*$//')
|
||||
if [ "$favicon_name" != "favicon" ]; then
|
||||
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${favicon_dir}/favicon.ico" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Заменяем все найденные logo файлы
|
||||
if [ -f "$MEDIA_DIR/logo.png" ]; then
|
||||
echo "3. Замена всех logo файлов на logo.png..."
|
||||
echo "$EXISTING_LOGOS" | while read -r logo_file; do
|
||||
if [ -n "$logo_file" ]; then
|
||||
echo " → $logo_file"
|
||||
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${logo_file}" 2>/dev/null || true
|
||||
# Также создаем .svg версию
|
||||
logo_dir=$(dirname "$logo_file")
|
||||
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${logo_dir}/logo.svg" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "4. Перезапуск контейнера для применения изменений..."
|
||||
docker restart "${CONTAINER_NAME}" >/dev/null 2>&1 || {
|
||||
echo "Предупреждение: Не удалось перезапустить контейнер автоматически."
|
||||
echo "Перезапустите вручную: docker restart ${CONTAINER_NAME}"
|
||||
}
|
||||
|
||||
echo ""
|
||||
echo "=== Готово! ==="
|
||||
echo ""
|
||||
echo "ВАЖНО: Логотипы применены, но они могут вернуться после перезапуска контейнера."
|
||||
echo ""
|
||||
echo "Для ПОСТОЯННОГО решения используйте Admin Panel Open WebUI:"
|
||||
echo " 1. Откройте https://odo.iieasy.ru или http://localhost:3001"
|
||||
echo " 2. Войдите как администратор"
|
||||
echo " 3. Settings → Appearance → Logo"
|
||||
echo " 4. Загрузите logo.png и favicon.png из папки media/"
|
||||
echo " 5. Сохраните - настройки сохранятся в базе данных"
|
||||
echo ""
|
||||
echo "Или запустите этот скрипт после каждого перезапуска контейнера."
|
||||
100
scripts/check_gpu.sh
Executable file
100
scripts/check_gpu.sh
Executable file
@@ -0,0 +1,100 @@
|
||||
#!/bin/bash
|
||||
# Скрипт для проверки подключения GPU в Docker контейнере Ollama
|
||||
|
||||
set -e
|
||||
|
||||
CONTAINER_NAME="ollama"
|
||||
|
||||
echo "=== Проверка GPU в Docker контейнере ==="
|
||||
echo ""
|
||||
|
||||
# Определяем команду docker
|
||||
DOCKER_CMD="docker"
|
||||
if ! docker ps >/dev/null 2>&1; then
|
||||
DOCKER_CMD="sudo docker"
|
||||
fi
|
||||
|
||||
echo "1. Проверка конфигурации в docker-compose.yml..."
|
||||
if grep -q "driver: nvidia" docker-compose.yml 2>/dev/null; then
|
||||
echo " ✓ GPU настроен в docker-compose.yml"
|
||||
echo " Конфигурация:"
|
||||
grep -A 5 "driver: nvidia" docker-compose.yml | head -6
|
||||
else
|
||||
echo " ✗ GPU не настроен в docker-compose.yml"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "2. Проверка конфигурации контейнера..."
|
||||
GPU_CONFIG=$($DOCKER_CMD inspect $CONTAINER_NAME 2>/dev/null | grep -i "nvidia\|gpu" | head -5)
|
||||
if [ -n "$GPU_CONFIG" ]; then
|
||||
echo " ✓ Найдена конфигурация GPU:"
|
||||
echo "$GPU_CONFIG"
|
||||
else
|
||||
echo " ⚠ Конфигурация GPU не найдена в inspect"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "3. Проверка доступности nvidia-smi в контейнере..."
|
||||
if $DOCKER_CMD exec $CONTAINER_NAME which nvidia-smi >/dev/null 2>&1; then
|
||||
echo " ✓ nvidia-smi доступен"
|
||||
echo ""
|
||||
echo " Вывод nvidia-smi:"
|
||||
$DOCKER_CMD exec $CONTAINER_NAME nvidia-smi 2>&1 | head -15 || echo " ⚠ nvidia-smi не может выполниться"
|
||||
else
|
||||
echo " ✗ nvidia-smi не найден в контейнере"
|
||||
echo " Возможно, контейнер не имеет доступа к GPU"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "4. Проверка переменных окружения NVIDIA..."
|
||||
NVIDIA_ENV=$($DOCKER_CMD exec $CONTAINER_NAME env | grep -i nvidia)
|
||||
if [ -n "$NVIDIA_ENV" ]; then
|
||||
echo " ✓ Переменные NVIDIA найдены:"
|
||||
echo "$NVIDIA_ENV"
|
||||
else
|
||||
echo " ⚠ Переменные NVIDIA не найдены"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "5. Проверка логов Ollama на использование GPU..."
|
||||
RECENT_LOGS=$($DOCKER_CMD logs $CONTAINER_NAME --tail 50 2>&1)
|
||||
if echo "$RECENT_LOGS" | grep -qi "gpu\|cuda\|nvidia"; then
|
||||
echo " ✓ Найдены упоминания GPU в логах:"
|
||||
echo "$RECENT_LOGS" | grep -i "gpu\|cuda\|nvidia" | tail -5
|
||||
else
|
||||
echo " ⚠ Нет упоминаний GPU в последних логах"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "6. Проверка на хосте..."
|
||||
if command -v nvidia-smi >/dev/null 2>&1; then
|
||||
echo " GPU на хосте:"
|
||||
nvidia-smi --query-gpu=name,driver_version,memory.total --format=csv,noheader 2>/dev/null || echo " ⚠ Не удалось получить информацию о GPU"
|
||||
else
|
||||
echo " ⚠ nvidia-smi не найден на хосте"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== Резюме ==="
|
||||
echo ""
|
||||
if $DOCKER_CMD exec $CONTAINER_NAME nvidia-smi >/dev/null 2>&1; then
|
||||
echo "✓ GPU подключен и доступен в контейнере"
|
||||
echo ""
|
||||
echo "Для детальной информации выполните:"
|
||||
echo " sudo docker exec ollama nvidia-smi"
|
||||
else
|
||||
echo "✗ GPU не доступен в контейнере"
|
||||
echo ""
|
||||
echo "Возможные причины:"
|
||||
echo " 1. Docker не имеет доступа к GPU (нужен nvidia-docker2 или nvidia-container-toolkit)"
|
||||
echo " 2. Контейнер не был перезапущен после изменения docker-compose.yml"
|
||||
echo " 3. Драйверы NVIDIA не установлены на хосте"
|
||||
echo ""
|
||||
echo "Решение:"
|
||||
echo " 1. Установите nvidia-container-toolkit:"
|
||||
echo " sudo apt-get install -y nvidia-container-toolkit"
|
||||
echo " sudo systemctl restart docker"
|
||||
echo ""
|
||||
echo " 2. Перезапустите контейнер:"
|
||||
echo " docker compose restart ollama"
|
||||
fi
|
||||
85
scripts/check_image_transfer.sh
Executable file
85
scripts/check_image_transfer.sh
Executable file
@@ -0,0 +1,85 @@
|
||||
#!/bin/bash
|
||||
# Скрипт для проверки передачи изображений из Open WebUI в Ollama
|
||||
|
||||
set -e
|
||||
|
||||
echo "=== Проверка передачи изображений Open WebUI → Ollama ==="
|
||||
echo ""
|
||||
|
||||
# Определяем команду docker
|
||||
DOCKER_CMD="docker"
|
||||
if ! docker ps >/dev/null 2>&1; then
|
||||
DOCKER_CMD="sudo docker"
|
||||
fi
|
||||
|
||||
echo "1. Проверка переменной OLLAMA_BASE_URL в контейнере..."
|
||||
OLLAMA_URL=$($DOCKER_CMD exec open-webui env | grep -i "OLLAMA_BASE_URL" | cut -d= -f2)
|
||||
if [ -z "$OLLAMA_URL" ]; then
|
||||
echo " ✗ Переменная OLLAMA_BASE_URL не установлена!"
|
||||
echo " Нужно добавить в docker-compose.yml и перезапустить контейнер"
|
||||
else
|
||||
echo " ✓ OLLAMA_BASE_URL=$OLLAMA_URL"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "2. Проверка доступности Ollama из Open WebUI..."
|
||||
if $DOCKER_CMD exec open-webui curl -s --max-time 5 http://ollama:11434/api/tags >/dev/null 2>&1; then
|
||||
echo " ✓ Ollama доступен по адресу http://ollama:11434"
|
||||
else
|
||||
echo " ✗ Ollama недоступен из Open WebUI"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "3. Проверка модели gemma3n:e4b-it-fp16..."
|
||||
MODELS=$($DOCKER_CMD exec open-webui curl -s http://ollama:11434/api/tags 2>/dev/null)
|
||||
if echo "$MODELS" | grep -q "gemma3n:e4b-it-fp16"; then
|
||||
echo " ✓ Модель найдена"
|
||||
else
|
||||
echo " ✗ Модель не найдена"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "4. Проверка логов Open WebUI на наличие запросов с изображениями..."
|
||||
RECENT_LOGS=$($DOCKER_CMD logs open-webui --tail 100 2>&1)
|
||||
if echo "$RECENT_LOGS" | grep -qi "image.*ollama\|ollama.*image\|vision\|multimodal"; then
|
||||
echo " ✓ Найдены упоминания изображений в логах:"
|
||||
echo "$RECENT_LOGS" | grep -i "image.*ollama\|ollama.*image\|vision\|multimodal" | tail -5
|
||||
else
|
||||
echo " ⚠ Нет упоминаний изображений в последних логах"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "5. Проверка логов Ollama на наличие запросов с изображениями..."
|
||||
OLLAMA_LOGS=$($DOCKER_CMD logs ollama --tail 100 2>&1)
|
||||
if echo "$OLLAMA_LOGS" | grep -qi "image\|vision\|multimodal"; then
|
||||
echo " ✓ Найдены запросы с изображениями в логах Ollama:"
|
||||
echo "$OLLAMA_LOGS" | grep -i "image\|vision\|multimodal" | tail -5
|
||||
else
|
||||
echo " ✗ Нет запросов с изображениями в логах Ollama"
|
||||
echo " Это означает, что изображения не доходят до Ollama!"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "6. Рекомендации:"
|
||||
echo ""
|
||||
if [ -z "$OLLAMA_URL" ]; then
|
||||
echo " ⚠ КРИТИЧНО: Добавьте OLLAMA_BASE_URL в docker-compose.yml:"
|
||||
echo " - OLLAMA_BASE_URL=http://ollama:11434"
|
||||
echo " Затем: docker compose restart open-webui"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
echo " Для диагностики проблемы с изображениями:"
|
||||
echo " 1. Убедитесь, что в Settings → Connections → Ollama API"
|
||||
echo " адрес установлен: http://ollama:11434"
|
||||
echo ""
|
||||
echo " 2. Попробуйте отправить изображение через веб-интерфейс"
|
||||
echo " и сразу проверьте логи:"
|
||||
echo " sudo docker logs open-webui --tail 50 -f"
|
||||
echo " sudo docker logs ollama --tail 50 -f"
|
||||
echo ""
|
||||
echo " 3. Проверьте формат изображения (должен быть JPEG/PNG)"
|
||||
echo " и размер (не слишком большой)"
|
||||
|
||||
echo ""
|
||||
echo "=== Проверка завершена ==="
|
||||
43
scripts/check_searxng_json.sh
Executable file
43
scripts/check_searxng_json.sh
Executable file
@@ -0,0 +1,43 @@
|
||||
#!/bin/sh
|
||||
# Скрипт для проверки формата JSON ответа от SearXNG
|
||||
|
||||
echo "Проверка формата JSON ответа от SearXNG..."
|
||||
|
||||
# Проверяем формат ответа от SearXNG
|
||||
RESPONSE=$(curl -s "http://searxng:8080/search?q=test&format=json" 2>&1)
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✓ SearXNG отвечает"
|
||||
echo "Формат ответа (первые 500 символов):"
|
||||
echo "$RESPONSE" | head -c 500
|
||||
echo ""
|
||||
echo ""
|
||||
|
||||
# Проверяем наличие ключевых полей
|
||||
if echo "$RESPONSE" | grep -q '"results"'; then
|
||||
echo "✓ Найдено поле 'results'"
|
||||
fi
|
||||
|
||||
if echo "$RESPONSE" | grep -q '"url"'; then
|
||||
echo "✓ Найдено поле 'url'"
|
||||
fi
|
||||
|
||||
if echo "$RESPONSE" | grep -q '"link"'; then
|
||||
echo "✓ Найдено поле 'link'"
|
||||
fi
|
||||
|
||||
if echo "$RESPONSE" | grep -q '"title"'; then
|
||||
echo "✓ Найдено поле 'title'"
|
||||
fi
|
||||
|
||||
if echo "$RESPONSE" | grep -q '"content"'; then
|
||||
echo "✓ Найдено поле 'content'"
|
||||
fi
|
||||
|
||||
if echo "$RESPONSE" | grep -q '"snippet"'; then
|
||||
echo "✓ Найдено поле 'snippet'"
|
||||
fi
|
||||
else
|
||||
echo "✗ Ошибка при запросе к SearXNG"
|
||||
echo "$RESPONSE"
|
||||
fi
|
||||
42
scripts/check_vision_models.sh
Executable file
42
scripts/check_vision_models.sh
Executable file
@@ -0,0 +1,42 @@
|
||||
#!/bin/bash
|
||||
# Проверка доступных vision моделей в Ollama
|
||||
|
||||
set -e
|
||||
|
||||
CONTAINER_OLLAMA="ollama"
|
||||
|
||||
echo "=== Проверка vision моделей в Ollama ==="
|
||||
echo ""
|
||||
|
||||
# Определяем команду docker
|
||||
DOCKER_CMD="docker"
|
||||
if ! docker ps >/dev/null 2>&1; then
|
||||
DOCKER_CMD="sudo docker"
|
||||
fi
|
||||
|
||||
echo "1. Список установленных моделей:"
|
||||
$DOCKER_CMD exec $CONTAINER_OLLAMA ollama list 2>/dev/null || sudo $DOCKER_CMD exec $CONTAINER_OLLAMA ollama list 2>/dev/null
|
||||
|
||||
echo ""
|
||||
echo "2. Популярные vision модели для Ollama:"
|
||||
echo ""
|
||||
echo " Рекомендуемые модели с поддержкой vision:"
|
||||
echo " - llava:latest (LLaVA 1.6) - 7B параметров, хорошо работает с изображениями"
|
||||
echo " - bakllava:latest (BakLLaVA) - 7B параметров, Mistral + LLaVA"
|
||||
echo " - llama3.2-vision:latest (Llama 3.2 Vision) - 11B параметров"
|
||||
echo " - gemma2:9b-it (Gemma 2) - может поддерживать vision"
|
||||
echo ""
|
||||
echo "3. Для установки vision модели выполните:"
|
||||
echo ""
|
||||
echo " sudo docker exec ollama ollama pull llava:latest"
|
||||
echo " # или"
|
||||
echo " sudo docker exec ollama ollama pull bakllava:latest"
|
||||
echo ""
|
||||
echo "4. Проверка текущей модели gemma3n:e4b-it-fp16:"
|
||||
echo ""
|
||||
echo " Модель gemma3n:e4b-it-fp16 может не поддерживать vision правильно."
|
||||
echo " Рекомендуется использовать специализированные vision модели:"
|
||||
echo " - llava:latest (лучший выбор для vision)"
|
||||
echo " - bakllava:latest (альтернатива)"
|
||||
echo ""
|
||||
echo "=== Готово ==="
|
||||
90
scripts/diagnose_search.sh
Executable file
90
scripts/diagnose_search.sh
Executable file
@@ -0,0 +1,90 @@
|
||||
#!/bin/sh
|
||||
# Диагностика проблемы с поиском в Open WebUI
|
||||
|
||||
echo "=== ДИАГНОСТИКА ПОИСКА В OPEN WEBUI ==="
|
||||
echo ""
|
||||
|
||||
# 1. Проверка SearXNG
|
||||
echo "1. Проверка SearXNG..."
|
||||
SEARXNG_STATUS=$(docker ps | grep searxng | awk '{print $7}')
|
||||
if [ "$SEARXNG_STATUS" = "healthy" ] || [ "$SEARXNG_STATUS" = "Up" ]; then
|
||||
echo "✓ SearXNG работает (статус: $SEARXNG_STATUS)"
|
||||
else
|
||||
echo "✗ SearXNG не работает (статус: $SEARXNG_STATUS)"
|
||||
fi
|
||||
|
||||
# 2. Проверка JSON формата
|
||||
echo ""
|
||||
echo "2. Проверка JSON формата SearXNG..."
|
||||
JSON_TEST=$(docker exec open-webui curl -s "http://searxng:8080/search?q=test&format=json" 2>&1 | head -c 200)
|
||||
if echo "$JSON_TEST" | grep -q "results"; then
|
||||
echo "✓ JSON формат работает"
|
||||
else
|
||||
echo "✗ JSON формат не работает"
|
||||
echo " Ответ: $JSON_TEST"
|
||||
fi
|
||||
|
||||
# 3. Проверка патча User-Agent
|
||||
echo ""
|
||||
echo "3. Проверка патча User-Agent..."
|
||||
RAG_BOT_FOUND=$(docker exec open-webui grep -r "RAG Bot" /app/backend/open_webui/routers/retrieval.py /app/backend/open_webui/utils/middleware.py 2>/dev/null | wc -l)
|
||||
if [ "$RAG_BOT_FOUND" -eq 0 ]; then
|
||||
echo "✓ Патч User-Agent применен (проблемная строка не найдена)"
|
||||
else
|
||||
echo "✗ Патч User-Agent НЕ применен (найдено вхождений: $RAG_BOT_FOUND)"
|
||||
echo " Нужно перезапустить Open WebUI для применения патча"
|
||||
fi
|
||||
|
||||
# 4. Проверка логов Open WebUI на ошибки
|
||||
echo ""
|
||||
echo "4. Последние ошибки в логах Open WebUI..."
|
||||
docker logs open-webui --tail 50 2>&1 | grep -i "error\|user-agent\|invalid\|searxng" | tail -10
|
||||
if [ $? -ne 0 ]; then
|
||||
echo " (Ошибок не найдено в последних 50 строках)"
|
||||
fi
|
||||
|
||||
# 5. Проверка конфигурации docker-compose
|
||||
echo ""
|
||||
echo "5. Проверка конфигурации..."
|
||||
SEARXNG_URL=$(grep "SEARXNG_QUERY_URL" docker-compose.yml | head -1)
|
||||
echo " SEARXNG_QUERY_URL: $SEARXNG_URL"
|
||||
if echo "$SEARXNG_URL" | grep -q "format=json"; then
|
||||
echo "✓ URL содержит format=json"
|
||||
else
|
||||
echo "⚠ URL не содержит format=json явно"
|
||||
fi
|
||||
|
||||
# 6. Проверка settings.yml
|
||||
echo ""
|
||||
echo "6. Проверка settings.yml SearXNG..."
|
||||
if grep -q "formats:" searxng/settings.yml && grep -q "json" searxng/settings.yml; then
|
||||
echo "✓ JSON формат включен в settings.yml"
|
||||
else
|
||||
echo "✗ JSON формат НЕ найден в settings.yml"
|
||||
fi
|
||||
|
||||
if grep -q "limiter: false" searxng/settings.yml; then
|
||||
echo "✓ Лимитер отключен"
|
||||
else
|
||||
echo "⚠ Лимитер может быть включен"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== РЕКОМЕНДАЦИИ ==="
|
||||
echo ""
|
||||
if [ "$RAG_BOT_FOUND" -gt 0 ]; then
|
||||
echo "1. Перезапустите Open WebUI для применения патча User-Agent:"
|
||||
echo " sudo docker restart open-webui"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
if ! echo "$JSON_TEST" | grep -q "results"; then
|
||||
echo "2. Исправьте конфигурацию SearXNG:"
|
||||
echo " sudo ./scripts/fix_searxng_config.sh"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
echo "3. Проверьте настройки в интерфейсе Open WebUI:"
|
||||
echo " Settings → Web Search"
|
||||
echo " URL должен быть: http://searxng:8080/search?q=<query>&format=json"
|
||||
echo ""
|
||||
32
scripts/find_model_image_api.sh
Executable file
32
scripts/find_model_image_api.sh
Executable file
@@ -0,0 +1,32 @@
|
||||
#!/bin/bash
|
||||
# Поиск API endpoint для изображения профиля модели
|
||||
|
||||
CONTAINER_NAME="open-webui"
|
||||
|
||||
echo "=== Поиск API endpoint /api/v1/models/model/profile/image ==="
|
||||
echo ""
|
||||
|
||||
echo "1. Поиск Python файлов с этим endpoint..."
|
||||
docker exec "${CONTAINER_NAME}" find /app/backend -type f -name "*.py" \
|
||||
-exec grep -l "model/profile/image\|profile/image\|models.*profile" {} \; 2>/dev/null
|
||||
|
||||
echo ""
|
||||
echo "2. Поиск в main.py и routes..."
|
||||
docker exec "${CONTAINER_NAME}" find /app/backend -type f -name "*.py" \
|
||||
\( -name "*main.py" -o -name "*route*.py" -o -name "*api*.py" -o -name "*model*.py" \) \
|
||||
-exec grep -l "profile.*image\|image.*profile" {} \; 2>/dev/null
|
||||
|
||||
echo ""
|
||||
echo "3. Поиск строк с '/api/v1/models'..."
|
||||
docker exec "${CONTAINER_NAME}" find /app/backend -type f -name "*.py" \
|
||||
-exec grep -l "/api/v1/models\|api/v1/models" {} \; 2>/dev/null | head -10
|
||||
|
||||
echo ""
|
||||
echo "4. Поиск в шаблонах HTML/Svelte с этим API..."
|
||||
docker exec "${CONTAINER_NAME}" find /app/web -type f \
|
||||
\( -name "*.html" -o -name "*.svelte" -o -name "*.js" -o -name "*.ts" \) \
|
||||
! -path "*/node_modules/*" \
|
||||
-exec grep -l "model/profile/image\|models.*profile.*image" {} \; 2>/dev/null | head -10
|
||||
|
||||
echo ""
|
||||
echo "=== Поиск завершен ==="
|
||||
64
scripts/find_openwebui_text.sh
Executable file
64
scripts/find_openwebui_text.sh
Executable file
@@ -0,0 +1,64 @@
|
||||
#!/bin/bash
|
||||
# Скрипт для поиска всех упоминаний "(Open WebUI)" в контейнере
|
||||
|
||||
CONTAINER_NAME="open-webui"
|
||||
|
||||
echo "=== Поиск всех упоминаний '(Open WebUI)' ==="
|
||||
echo ""
|
||||
|
||||
# Ищем во всех файлах
|
||||
echo "1. Поиск в HTML/Svelte файлах..."
|
||||
docker exec "${CONTAINER_NAME}" find /app/web -type f \( -name "*.html" -o -name "*.svelte" \) \
|
||||
! -path "*/node_modules/*" \
|
||||
2>/dev/null | while read file; do
|
||||
if docker exec "${CONTAINER_NAME}" grep -q "(Open WebUI)" "$file" 2>/dev/null; then
|
||||
echo " НАЙДЕНО в: $file"
|
||||
docker exec "${CONTAINER_NAME}" grep -n "(Open WebUI)" "$file" 2>/dev/null | head -3
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "2. Поиск в JS/TS файлах..."
|
||||
docker exec "${CONTAINER_NAME}" find /app/web -type f \( -name "*.js" -o -name "*.ts" -o -name "*.jsx" -o -name "*.tsx" \) \
|
||||
! -path "*/node_modules/*" \
|
||||
2>/dev/null | while read file; do
|
||||
if docker exec "${CONTAINER_NAME}" grep -q "(Open WebUI)" "$file" 2>/dev/null; then
|
||||
echo " НАЙДЕНО в: $file"
|
||||
docker exec "${CONTAINER_NAME}" grep -n "(Open WebUI)" "$file" 2>/dev/null | head -3
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "3. Поиск в скомпилированных файлах..."
|
||||
docker exec "${CONTAINER_NAME}" find /app/web/build -type f -name "*.js" \
|
||||
2>/dev/null | while read file; do
|
||||
if docker exec "${CONTAINER_NAME}" grep -q "(Open WebUI)" "$file" 2>/dev/null; then
|
||||
echo " НАЙДЕНО в скомпилированном: $file"
|
||||
docker exec "${CONTAINER_NAME}" grep -o ".{0,50}(Open WebUI).{0,50}" "$file" 2>/dev/null | head -2
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "4. Поиск в JSON файлах..."
|
||||
docker exec "${CONTAINER_NAME}" find /app/web -type f -name "*.json" \
|
||||
! -path "*/node_modules/*" \
|
||||
2>/dev/null | while read file; do
|
||||
if docker exec "${CONTAINER_NAME}" grep -q "(Open WebUI)" "$file" 2>/dev/null; then
|
||||
echo " НАЙДЕНО в: $file"
|
||||
docker exec "${CONTAINER_NAME}" grep -n "(Open WebUI)" "$file" 2>/dev/null | head -3
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "5. Поиск вариантов написания..."
|
||||
docker exec "${CONTAINER_NAME}" find /app/web -type f \
|
||||
! -path "*/node_modules/*" \
|
||||
2>/dev/null | while read file; do
|
||||
if docker exec "${CONTAINER_NAME}" grep -qE "iiEasyWeb.*Open|Open.*WebUI" "$file" 2>/dev/null; then
|
||||
echo " ВАРИАНТ в: $file"
|
||||
docker exec "${CONTAINER_NAME}" grep -nE "iiEasyWeb.*Open|Open.*WebUI" "$file" 2>/dev/null | head -2
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "=== Поиск завершен ==="
|
||||
42
scripts/find_settings_elements.sh
Executable file
42
scripts/find_settings_elements.sh
Executable file
@@ -0,0 +1,42 @@
|
||||
#!/bin/bash
|
||||
# Поиск элементов настроек, которые нужно удалить
|
||||
|
||||
CONTAINER_NAME="open-webui"
|
||||
|
||||
echo "=== Поиск элементов настроек ==="
|
||||
echo ""
|
||||
|
||||
echo "1. Поиск 'Проверить обновления'..."
|
||||
docker exec "${CONTAINER_NAME}" find /app -type f \
|
||||
\( -name "*.svelte" -o -name "*.js" -o -name "*.ts" -o -name "*.html" \) \
|
||||
! -path "*/node_modules/*" \
|
||||
-exec grep -l "Проверить обновления\|Check for updates\|check.*update" {} \; 2>/dev/null | head -10
|
||||
|
||||
echo ""
|
||||
echo "2. Поиск 'последняя' и GitHub releases..."
|
||||
docker exec "${CONTAINER_NAME}" find /app -type f \
|
||||
\( -name "*.svelte" -o -name "*.js" -o -name "*.ts" \) \
|
||||
! -path "*/node_modules/*" \
|
||||
-exec grep -l "последняя\|latest\|github.com/open-webui/releases" {} \; 2>/dev/null | head -10
|
||||
|
||||
echo ""
|
||||
echo "3. Поиск блока 'Помощь' и соцсетей..."
|
||||
docker exec "${CONTAINER_NAME}" find /app -type f \
|
||||
\( -name "*.svelte" -o -name "*.js" -o -name "*.ts" \) \
|
||||
! -path "*/node_modules/*" \
|
||||
-exec grep -l "Помощь\|Help\|discord\.gg\|twitter\.com\|github.com/open-webui" {} \; 2>/dev/null | head -10
|
||||
|
||||
echo ""
|
||||
echo "4. Поиск блока 'Лицензия'..."
|
||||
docker exec "${CONTAINER_NAME}" find /app -type f \
|
||||
\( -name "*.svelte" -o -name "*.js" -o -name "*.ts" \) \
|
||||
! -path "*/node_modules/*" \
|
||||
-exec grep -l "Лицензия\|License\|лицензионный тарифный план" {} \; 2>/dev/null | head -10
|
||||
|
||||
echo ""
|
||||
echo "5. Поиск в скомпилированных файлах..."
|
||||
docker exec "${CONTAINER_NAME}" find /app/web/build -type f -name "*.js" \
|
||||
-exec grep -l "Проверить обновления\|последняя\|Помощь\|Лицензия" {} \; 2>/dev/null | head -5
|
||||
|
||||
echo ""
|
||||
echo "=== Поиск завершен ==="
|
||||
49
scripts/fix_openwebui.sh
Executable file
49
scripts/fix_openwebui.sh
Executable file
@@ -0,0 +1,49 @@
|
||||
#!/bin/bash
|
||||
# Скрипт восстановления Open WebUI после повреждения ребрендингом
|
||||
|
||||
set -e
|
||||
|
||||
CONTAINER_NAME="open-webui"
|
||||
|
||||
echo "=== Восстановление Open WebUI ==="
|
||||
|
||||
# Проверка наличия контейнера
|
||||
if ! docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
||||
echo "Ошибка: Контейнер ${CONTAINER_NAME} не найден."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "1. Остановка контейнера..."
|
||||
docker stop "${CONTAINER_NAME}" 2>/dev/null || true
|
||||
|
||||
echo "2. Удаление поврежденного контейнера..."
|
||||
docker rm "${CONTAINER_NAME}" 2>/dev/null || true
|
||||
|
||||
echo "3. Пересоздание контейнера..."
|
||||
cd "$(dirname "$0")/.."
|
||||
docker compose up -d open-webui
|
||||
|
||||
echo "4. Ожидание запуска контейнера..."
|
||||
sleep 15
|
||||
|
||||
echo "5. Проверка статуса..."
|
||||
if docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
||||
echo "✓ Контейнер запущен успешно"
|
||||
|
||||
# Проверяем, что контейнер работает
|
||||
if docker exec "${CONTAINER_NAME}" curl -f http://localhost:8080/health 2>/dev/null; then
|
||||
echo "✓ Контейнер отвечает на запросы"
|
||||
else
|
||||
echo "⚠ Контейнер запущен, но не отвечает. Проверьте логи: docker compose logs open-webui"
|
||||
fi
|
||||
else
|
||||
echo "✗ Контейнер не запустился. Проверьте логи: docker compose logs open-webui"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== Восстановление завершено ==="
|
||||
echo ""
|
||||
echo "ВАЖНО: Скрипт rebrand.sh был исправлен и больше не будет ломать код."
|
||||
echo "Если нужно применить ребрендинг, используйте обновленный скрипт:"
|
||||
echo " ./scripts/rebrand.sh"
|
||||
56
scripts/fix_search_complete.sh
Executable file
56
scripts/fix_search_complete.sh
Executable file
@@ -0,0 +1,56 @@
|
||||
#!/bin/sh
|
||||
# Полное исправление поиска в Open WebUI
|
||||
# Исправляет конфигурацию SearXNG и перезапускает контейнеры
|
||||
|
||||
echo "=== ПОЛНОЕ ИСПРАВЛЕНИЕ ПОИСКА ==="
|
||||
echo ""
|
||||
|
||||
# 1. Исправляем конфигурацию SearXNG
|
||||
echo "1. Исправление конфигурации SearXNG..."
|
||||
./scripts/fix_searxng_config.sh
|
||||
|
||||
# 2. Перезапускаем Open WebUI для применения патча User-Agent
|
||||
echo ""
|
||||
echo "2. Перезапуск Open WebUI для применения патча User-Agent..."
|
||||
docker restart open-webui
|
||||
|
||||
echo ""
|
||||
echo "Ожидание запуска Open WebUI (15 секунд)..."
|
||||
sleep 15
|
||||
|
||||
# 3. Проверка
|
||||
echo ""
|
||||
echo "3. Финальная проверка..."
|
||||
echo ""
|
||||
|
||||
# Проверка SearXNG
|
||||
SEARXNG_STATUS=$(docker ps | grep searxng | awk '{print $7}')
|
||||
if [ "$SEARXNG_STATUS" = "healthy" ] || [ "$SEARXNG_STATUS" = "Up" ]; then
|
||||
echo "✓ SearXNG работает"
|
||||
else
|
||||
echo "✗ SearXNG не работает"
|
||||
fi
|
||||
|
||||
# Проверка JSON
|
||||
JSON_TEST=$(docker exec open-webui curl -s "http://searxng:8080/search?q=test&format=json" 2>&1 | head -c 200)
|
||||
if echo "$JSON_TEST" | grep -q "results"; then
|
||||
echo "✓ JSON формат работает"
|
||||
else
|
||||
echo "✗ JSON формат не работает"
|
||||
fi
|
||||
|
||||
# Проверка патча
|
||||
RAG_BOT_FOUND=$(docker exec open-webui grep -r "RAG Bot" /app/backend/open_webui/routers/retrieval.py /app/backend/open_webui/utils/middleware.py 2>/dev/null | wc -l)
|
||||
if [ "$RAG_BOT_FOUND" -eq 0 ]; then
|
||||
echo "✓ Патч User-Agent применен"
|
||||
else
|
||||
echo "⚠ Патч User-Agent может быть не применен (найдено: $RAG_BOT_FOUND)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== ГОТОВО ==="
|
||||
echo ""
|
||||
echo "Проверьте поиск в Open WebUI:"
|
||||
echo "1. Откройте Settings → Web Search"
|
||||
echo "2. URL должен быть: http://searxng:8080/search?q=<query>&format=json"
|
||||
echo "3. Попробуйте поиск в чате"
|
||||
55
scripts/fix_searxng_config.sh
Executable file
55
scripts/fix_searxng_config.sh
Executable file
@@ -0,0 +1,55 @@
|
||||
#!/bin/sh
|
||||
# Скрипт для исправления конфигурации SearXNG после перезапуска контейнера
|
||||
|
||||
echo "Исправление конфигурации SearXNG..."
|
||||
|
||||
# Исправляем файл на хосте (он монтируется в контейнер)
|
||||
cat > /home/its/iiEasyWeb/searxng/settings.yml << 'EOF'
|
||||
# SearXNG Settings для работы с Open WebUI
|
||||
# Этот файл включает поддержку JSON формата для API запросов
|
||||
|
||||
use_default_settings: true
|
||||
|
||||
server:
|
||||
secret_key: "CHANGE_ME_SECRET_KEY"
|
||||
bind_address: "0.0.0.0"
|
||||
port: 8080
|
||||
limiter: false
|
||||
method: "GET"
|
||||
|
||||
search:
|
||||
safe_search: 0
|
||||
autocomplete: "google"
|
||||
formats:
|
||||
- html
|
||||
- json
|
||||
|
||||
general:
|
||||
instance_name: "SearXNG"
|
||||
debug: false
|
||||
EOF
|
||||
|
||||
echo "✓ Конфигурация обновлена на хосте"
|
||||
|
||||
# Перезапускаем SearXNG
|
||||
echo "Перезапуск SearXNG..."
|
||||
docker restart searxng
|
||||
|
||||
echo ""
|
||||
echo "Ожидание запуска SearXNG (10 секунд)..."
|
||||
sleep 10
|
||||
|
||||
# Проверяем, что JSON формат работает
|
||||
echo ""
|
||||
echo "Проверка JSON формата..."
|
||||
RESPONSE=$(docker exec open-webui curl -s "http://searxng:8080/search?q=test&format=json" 2>&1 | head -c 200)
|
||||
|
||||
if echo "$RESPONSE" | grep -q "results"; then
|
||||
echo "✓ JSON формат работает! Поиск должен работать в Open WebUI."
|
||||
else
|
||||
echo "⚠ Предупреждение: JSON формат может быть недоступен. Проверьте логи:"
|
||||
echo " docker logs searxng --tail 50"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Готово! Проверьте поиск в Open WebUI."
|
||||
31
scripts/fix_searxng_json.sh
Executable file
31
scripts/fix_searxng_json.sh
Executable file
@@ -0,0 +1,31 @@
|
||||
#!/bin/sh
|
||||
# Скрипт для исправления конфигурации SearXNG для работы с JSON форматом
|
||||
|
||||
echo "Исправление конфигурации SearXNG для JSON формата..."
|
||||
|
||||
# 1. Убеждаемся, что лимитер отключен
|
||||
echo "1. Отключение лимитера..."
|
||||
docker exec searxng sed -i 's/limiter: true/limiter: false/g' /etc/searxng/settings.yml 2>/dev/null || echo " Лимитер уже отключен или настройка не найдена"
|
||||
|
||||
# 2. Обновляем секцию search с правильными форматами
|
||||
echo "2. Обновление секции search..."
|
||||
docker exec searxng sh -c "sed -i '/^search:/,\$d' /etc/searxng/settings.yml && cat >> /etc/searxng/settings.yml <<'EOF'
|
||||
search:
|
||||
safe_search: 0
|
||||
autocomplete: 'google'
|
||||
formats:
|
||||
- html
|
||||
- json
|
||||
EOF"
|
||||
|
||||
# 3. Перезапускаем SearXNG
|
||||
echo "3. Перезапуск SearXNG..."
|
||||
docker restart searxng
|
||||
|
||||
echo ""
|
||||
echo "✓ Конфигурация SearXNG обновлена!"
|
||||
echo " - JSON формат включен"
|
||||
echo " - Лимитер отключен"
|
||||
echo " - Autocomplete: google"
|
||||
echo ""
|
||||
echo "Подождите 10-15 секунд для полного запуска SearXNG..."
|
||||
57
scripts/fix_trace_error.sh
Executable file
57
scripts/fix_trace_error.sh
Executable file
@@ -0,0 +1,57 @@
|
||||
#!/bin/bash
|
||||
# Скрипт для исправления ошибки NameError: name 'trace' is not defined
|
||||
# Пересоздает контейнер Open WebUI с чистой версией
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
CONTAINER_NAME="open-webui"
|
||||
|
||||
echo "=== Исправление ошибки NameError: name 'trace' is not defined ==="
|
||||
echo ""
|
||||
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
echo "1. Остановка контейнера..."
|
||||
sudo docker compose stop "$CONTAINER_NAME" 2>/dev/null || true
|
||||
|
||||
echo "2. Удаление поврежденного контейнера..."
|
||||
sudo docker compose rm -f "$CONTAINER_NAME" 2>/dev/null || true
|
||||
|
||||
echo "3. Пересоздание контейнера с чистой версией..."
|
||||
sudo docker compose up -d "$CONTAINER_NAME"
|
||||
|
||||
echo "4. Ожидание запуска контейнера (30 секунд)..."
|
||||
sleep 30
|
||||
|
||||
echo "5. Проверка статуса..."
|
||||
if sudo docker compose ps "$CONTAINER_NAME" | grep -q "Up"; then
|
||||
echo "✓ Контейнер запущен успешно"
|
||||
else
|
||||
echo "✗ Контейнер не запустился. Проверьте логи:"
|
||||
echo " sudo docker compose logs $CONTAINER_NAME"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "6. Проверка логов на ошибки..."
|
||||
ERRORS=$(sudo docker compose logs "$CONTAINER_NAME" --tail 50 2>&1 | grep -i "trace\|error" || true)
|
||||
if [ -z "$ERRORS" ]; then
|
||||
echo "✓ Ошибок не найдено"
|
||||
else
|
||||
echo "⚠ Найдены ошибки в логах:"
|
||||
echo "$ERRORS"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== Готово! ==="
|
||||
echo ""
|
||||
echo "Теперь проверьте:"
|
||||
echo "1. Откройте https://odo.iieasy.ru"
|
||||
echo "2. Должна появиться страница входа с формой и кнопкой 'iiEasy ID'"
|
||||
echo "3. Если нужно применить логотипы, используйте Admin Panel:"
|
||||
echo " Settings → Appearance → Logo"
|
||||
echo ""
|
||||
echo "Если ошибка 'trace' осталась, проверьте логи:"
|
||||
echo " sudo docker compose logs $CONTAINER_NAME --tail 100"
|
||||
132
scripts/fix_user_agent.sh
Executable file
132
scripts/fix_user_agent.sh
Executable file
@@ -0,0 +1,132 @@
|
||||
#!/bin/sh
|
||||
# Исправление бага с User-Agent в Open WebUI v0.8.3
|
||||
# Проблема: User-Agent начинается с пробела, что вызывает ошибку "Invalid leading whitespace"
|
||||
|
||||
echo "Применение патча для исправления User-Agent..."
|
||||
|
||||
# Исправляем в retrieval.py
|
||||
FILE="/app/backend/open_webui/routers/retrieval.py"
|
||||
if [ -f "$FILE" ]; then
|
||||
echo "Обработка файла: $FILE"
|
||||
# Ищем все варианты проблемной строки (более агрессивный поиск)
|
||||
PROBLEM_FOUND=0
|
||||
|
||||
# Проверяем наличие проблемной строки в разных вариантах
|
||||
if grep -qE "(github\.com/open-webui|RAG Bot|https://github)" "$FILE" 2>/dev/null; then
|
||||
PROBLEM_FOUND=1
|
||||
fi
|
||||
|
||||
if [ "$PROBLEM_FOUND" -eq 1 ] || grep -q "User-Agent" "$FILE" 2>/dev/null; then
|
||||
echo " Найдены строки с User-Agent, применяем патч..."
|
||||
|
||||
# Более агрессивная замена - ищем любые варианты с пробелом в начале
|
||||
# Исправляем варианты с одинарными кавычками
|
||||
sed -i "s/' (https:\/\/github\.com\/open-webui\/open-webui) RAG Bot'/'Open-WebUI-RAG-Bot'/g" "$FILE"
|
||||
sed -i "s/ '(https:\/\/github\.com\/open-webui\/open-webui) RAG Bot'/'Open-WebUI-RAG-Bot'/g" "$FILE"
|
||||
sed -i "s/'\(https:\/\/github\.com\/open-webui\/open-webui\) RAG Bot'/'Open-WebUI-RAG-Bot'/g" "$FILE"
|
||||
|
||||
# Исправляем варианты с двойными кавычками
|
||||
sed -i 's/" (https:\/\/github\.com\/open-webui\/open-webui) RAG Bot"/"Open-WebUI-RAG-Bot"/g' "$FILE"
|
||||
sed -i 's/ "(https:\/\/github\.com\/open-webui\/open-webui) RAG Bot"/"Open-WebUI-RAG-Bot"/g' "$FILE"
|
||||
sed -i 's/"\(https:\/\/github\.com\/open-webui\/open-webui\) RAG Bot"/"Open-WebUI-RAG-Bot"/g' "$FILE"
|
||||
|
||||
# Исправляем варианты без кавычек
|
||||
sed -i "s/ (https:\/\/github\.com\/open-webui\/open-webui) RAG Bot/Open-WebUI-RAG-Bot/g" "$FILE"
|
||||
sed -i "s/ \(https:\/\/github\.com\/open-webui\/open-webui\) RAG Bot/Open-WebUI-RAG-Bot/g" "$FILE"
|
||||
|
||||
# Исправляем если используется в headers dict с пробелом
|
||||
sed -i 's/"User-Agent": " (https:\/\/github\.com\/open-webui\/open-webui) RAG Bot"/"User-Agent": "Open-WebUI-RAG-Bot"/g' "$FILE"
|
||||
sed -i "s/'User-Agent': ' (https:\/\/github\.com\/open-webui\/open-webui) RAG Bot'/'User-Agent': 'Open-WebUI-RAG-Bot'/g" "$FILE"
|
||||
sed -i 's/"User-Agent": "(https:\/\/github\.com\/open-webui\/open-webui) RAG Bot"/"User-Agent": "Open-WebUI-RAG-Bot"/g' "$FILE"
|
||||
|
||||
# Исправляем f-string или конкатенацию
|
||||
sed -i 's/User-Agent.*github\.com.*open-webui.*RAG Bot/User-Agent": "Open-WebUI-RAG-Bot/g' "$FILE"
|
||||
|
||||
# Универсальная замена - ищем любую строку с пробелом перед User-Agent значением
|
||||
sed -i 's/"User-Agent": " [^"]*RAG Bot"/"User-Agent": "Open-WebUI-RAG-Bot"/g' "$FILE"
|
||||
sed -i "s/'User-Agent': ' [^']*RAG Bot'/'User-Agent': 'Open-WebUI-RAG-Bot'/g" "$FILE"
|
||||
|
||||
# Исправляем вариант "External Web Loader"
|
||||
sed -i 's/"User-Agent": " (https:\/\/github\.com\/open-webui\/open-webui) External Web Loader"/"User-Agent": "Open-WebUI-External-Web-Loader"/g' "$FILE"
|
||||
|
||||
# Универсальная замена для любых User-Agent с пробелом и github.com/open-webui
|
||||
sed -i 's/"User-Agent": " \([^"]*github\.com\/open-webui[^"]*\)"/"User-Agent": "Open-WebUI-Bot"/g' "$FILE"
|
||||
sed -i "s/'User-Agent': ' \([^']*github\.com\/open-webui[^']*\)'/'User-Agent': 'Open-WebUI-Bot'/g" "$FILE"
|
||||
|
||||
echo "✓ Патч применен к $FILE"
|
||||
else
|
||||
echo " Проблемная строка не найдена в $FILE (возможно, уже исправлена)"
|
||||
fi
|
||||
else
|
||||
echo "⚠ Файл $FILE не найден"
|
||||
fi
|
||||
|
||||
# Исправляем в middleware.py
|
||||
FILE2="/app/backend/open_webui/utils/middleware.py"
|
||||
if [ -f "$FILE2" ]; then
|
||||
echo "Обработка файла: $FILE2"
|
||||
PROBLEM_FOUND2=0
|
||||
|
||||
if grep -qE "(github\.com/open-webui|RAG Bot|https://github)" "$FILE2" 2>/dev/null; then
|
||||
PROBLEM_FOUND2=1
|
||||
fi
|
||||
|
||||
if [ "$PROBLEM_FOUND2" -eq 1 ] || grep -q "User-Agent" "$FILE2" 2>/dev/null; then
|
||||
echo " Найдены строки с User-Agent, применяем патч..."
|
||||
|
||||
# Те же замены что и для retrieval.py
|
||||
sed -i "s/' (https:\/\/github\.com\/open-webui\/open-webui) RAG Bot'/'Open-WebUI-RAG-Bot'/g" "$FILE2"
|
||||
sed -i "s/ '(https:\/\/github\.com\/open-webui\/open-webui) RAG Bot'/'Open-WebUI-RAG-Bot'/g" "$FILE2"
|
||||
sed -i 's/" (https:\/\/github\.com\/open-webui\/open-webui) RAG Bot"/"Open-WebUI-RAG-Bot"/g' "$FILE2"
|
||||
sed -i 's/ "(https:\/\/github\.com\/open-webui\/open-webui) RAG Bot"/"Open-WebUI-RAG-Bot"/g' "$FILE2"
|
||||
sed -i "s/ (https:\/\/github\.com\/open-webui\/open-webui) RAG Bot/Open-WebUI-RAG-Bot/g" "$FILE2"
|
||||
sed -i 's/"User-Agent": " (https:\/\/github\.com\/open-webui\/open-webui) RAG Bot"/"User-Agent": "Open-WebUI-RAG-Bot"/g' "$FILE2"
|
||||
sed -i "s/'User-Agent': ' (https:\/\/github\.com\/open-webui\/open-webui) RAG Bot'/'User-Agent': 'Open-WebUI-RAG-Bot'/g" "$FILE2"
|
||||
sed -i 's/"User-Agent": "(https:\/\/github\.com\/open-webui\/open-webui) RAG Bot"/"User-Agent": "Open-WebUI-RAG-Bot"/g' "$FILE2"
|
||||
sed -i 's/User-Agent.*github\.com.*open-webui.*RAG Bot/User-Agent": "Open-WebUI-RAG-Bot/g' "$FILE2"
|
||||
sed -i 's/"User-Agent": " [^"]*RAG Bot"/"User-Agent": "Open-WebUI-RAG-Bot"/g' "$FILE2"
|
||||
sed -i "s/'User-Agent': ' [^']*RAG Bot'/'User-Agent': 'Open-WebUI-RAG-Bot'/g" "$FILE2"
|
||||
|
||||
# Исправляем вариант "External Web Loader"
|
||||
sed -i 's/"User-Agent": " (https:\/\/github\.com\/open-webui\/open-webui) External Web Loader"/"User-Agent": "Open-WebUI-External-Web-Loader"/g' "$FILE2"
|
||||
|
||||
# Универсальная замена для любых User-Agent с пробелом и github.com/open-webui
|
||||
sed -i 's/"User-Agent": " \([^"]*github\.com\/open-webui[^"]*\)"/"User-Agent": "Open-WebUI-Bot"/g' "$FILE2"
|
||||
sed -i "s/'User-Agent': ' \([^']*github\.com\/open-webui[^']*\)'/'User-Agent': 'Open-WebUI-Bot'/g" "$FILE2"
|
||||
|
||||
echo "✓ Патч применен к $FILE2"
|
||||
else
|
||||
echo " Проблемная строка не найдена в $FILE2 (возможно, уже исправлена)"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Ищем в других возможных файлах (более агрессивный поиск)
|
||||
echo "Поиск проблемной строки во всех Python файлах..."
|
||||
find /app/backend -name "*.py" -type f 2>/dev/null | while read pyfile; do
|
||||
if [ "$pyfile" != "$FILE" ] && [ "$pyfile" != "$FILE2" ]; then
|
||||
# Проверяем наличие проблемной строки в любом виде
|
||||
if grep -qE "(github\.com/open-webui|RAG Bot|https://github)" "$pyfile" 2>/dev/null; then
|
||||
echo " Обработка файла: $pyfile"
|
||||
# Применяем все варианты замены
|
||||
sed -i "s/' (https:\/\/github\.com\/open-webui\/open-webui) RAG Bot'/'Open-WebUI-RAG-Bot'/g" "$pyfile"
|
||||
sed -i "s/ '(https:\/\/github\.com\/open-webui\/open-webui) RAG Bot'/'Open-WebUI-RAG-Bot'/g" "$pyfile"
|
||||
sed -i 's/" (https:\/\/github\.com\/open-webui\/open-webui) RAG Bot"/"Open-WebUI-RAG-Bot"/g' "$pyfile"
|
||||
sed -i 's/ "(https:\/\/github\.com\/open-webui\/open-webui) RAG Bot"/"Open-WebUI-RAG-Bot"/g' "$pyfile"
|
||||
sed -i "s/ (https:\/\/github\.com\/open-webui\/open-webui) RAG Bot/Open-WebUI-RAG-Bot/g" "$pyfile"
|
||||
sed -i 's/"User-Agent": " [^"]*RAG Bot"/"User-Agent": "Open-WebUI-RAG-Bot"/g' "$pyfile"
|
||||
sed -i "s/'User-Agent': ' [^']*RAG Bot'/'User-Agent': 'Open-WebUI-RAG-Bot'/g" "$pyfile"
|
||||
# Исправляем вариант "External Web Loader"
|
||||
sed -i 's/"User-Agent": " (https:\/\/github\.com\/open-webui\/open-webui) External Web Loader"/"User-Agent": "Open-WebUI-External-Web-Loader"/g' "$pyfile"
|
||||
# Универсальная замена
|
||||
sed -i 's/"User-Agent": " \([^"]*github\.com\/open-webui[^"]*\)"/"User-Agent": "Open-WebUI-Bot"/g' "$pyfile"
|
||||
sed -i "s/'User-Agent': ' \([^']*github\.com\/open-webui[^']*\)'/'User-Agent': 'Open-WebUI-Bot'/g" "$pyfile"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# Удаляем скомпилированные Python файлы (.pyc), чтобы они пересобрались
|
||||
echo "Очистка скомпилированных файлов Python..."
|
||||
find /app/backend -name "*.pyc" -delete 2>/dev/null
|
||||
find /app/backend -name "__pycache__" -type d -exec rm -rf {} + 2>/dev/null
|
||||
|
||||
echo "Патч применен. Запуск Open WebUI..."
|
||||
59
scripts/fix_user_agent_aggressive.sh
Executable file
59
scripts/fix_user_agent_aggressive.sh
Executable file
@@ -0,0 +1,59 @@
|
||||
#!/bin/sh
|
||||
# Агрессивное исправление бага с User-Agent в Open WebUI
|
||||
# Ищет и исправляет проблемную строку во всех возможных местах
|
||||
|
||||
echo "=== АГРЕССИВНОЕ ИСПРАВЛЕНИЕ USER-AGENT ==="
|
||||
echo ""
|
||||
|
||||
# Находим все файлы с проблемной строкой
|
||||
echo "Поиск всех файлов с проблемной строкой User-Agent..."
|
||||
PROBLEM_FILES=$(docker exec open-webui find /app/backend -name "*.py" -type f -exec grep -l "github.com/open-webui" {} \; 2>/dev/null)
|
||||
|
||||
if [ -z "$PROBLEM_FILES" ]; then
|
||||
echo "Файлы не найдены через docker exec, пробуем другой способ..."
|
||||
# Альтернативный способ - ищем через grep в контейнере
|
||||
docker exec open-webui sh -c 'find /app/backend -name "*.py" -type f | xargs grep -l "github.com/open-webui" 2>/dev/null' | while read pyfile; do
|
||||
if [ -n "$pyfile" ]; then
|
||||
echo "Исправление файла: $pyfile"
|
||||
docker exec open-webui sed -i "s/' (https:\/\/github\.com\/open-webui\/open-webui) RAG Bot'/'Open-WebUI-RAG-Bot'/g" "$pyfile"
|
||||
docker exec open-webui sed -i 's/" (https:\/\/github\.com\/open-webui\/open-webui) RAG Bot"/"Open-WebUI-RAG-Bot"/g' "$pyfile"
|
||||
docker exec open-webui sed -i "s/ (https:\/\/github\.com\/open-webui\/open-webui) RAG Bot/Open-WebUI-RAG-Bot/g" "$pyfile"
|
||||
docker exec open-webui sed -i 's/"User-Agent": " [^"]*RAG Bot"/"User-Agent": "Open-WebUI-RAG-Bot"/g' "$pyfile"
|
||||
docker exec open-webui sed -i "s/'User-Agent': ' [^']*RAG Bot'/'User-Agent': 'Open-WebUI-RAG-Bot'/g" "$pyfile"
|
||||
fi
|
||||
done
|
||||
else
|
||||
echo "$PROBLEM_FILES" | while read pyfile; do
|
||||
echo "Исправление файла: $pyfile"
|
||||
docker exec open-webui sed -i "s/' (https:\/\/github\.com\/open-webui\/open-webui) RAG Bot'/'Open-WebUI-RAG-Bot'/g" "$pyfile"
|
||||
docker exec open-webui sed -i 's/" (https:\/\/github\.com\/open-webui\/open-webui) RAG Bot"/"Open-WebUI-RAG-Bot"/g' "$pyfile"
|
||||
docker exec open-webui sed -i "s/ (https:\/\/github\.com\/open-webui\/open-webui) RAG Bot/Open-WebUI-RAG-Bot/g" "$pyfile"
|
||||
docker exec open-webui sed -i 's/"User-Agent": " [^"]*RAG Bot"/"User-Agent": "Open-WebUI-RAG-Bot"/g' "$pyfile"
|
||||
docker exec open-webui sed -i "s/'User-Agent': ' [^']*RAG Bot'/'User-Agent': 'Open-WebUI-RAG-Bot'/g" "$pyfile"
|
||||
done
|
||||
fi
|
||||
|
||||
# Очищаем кеш Python
|
||||
echo ""
|
||||
echo "Очистка кеша Python..."
|
||||
docker exec open-webui find /app/backend -name "*.pyc" -delete 2>/dev/null
|
||||
docker exec open-webui find /app/backend -name "__pycache__" -type d -exec rm -rf {} + 2>/dev/null || true
|
||||
|
||||
# Проверяем результат
|
||||
echo ""
|
||||
echo "Проверка результата..."
|
||||
RAG_BOT_COUNT=$(docker exec open-webui grep -r "github.com/open-webui.*RAG Bot" /app/backend 2>/dev/null | wc -l)
|
||||
if [ "$RAG_BOT_COUNT" -eq 0 ]; then
|
||||
echo "✓ Проблемная строка не найдена - патч применен успешно"
|
||||
else
|
||||
echo "⚠ Найдено вхождений проблемной строки: $RAG_BOT_COUNT"
|
||||
echo "Проблемные файлы:"
|
||||
docker exec open-webui grep -r "github.com/open-webui.*RAG Bot" /app/backend 2>/dev/null | cut -d: -f1 | sort -u
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Перезапуск Open WebUI..."
|
||||
docker restart open-webui
|
||||
|
||||
echo ""
|
||||
echo "Готово! Подождите 15 секунд и проверьте поиск."
|
||||
42
scripts/fix_user_agent_final.sh
Executable file
42
scripts/fix_user_agent_final.sh
Executable file
@@ -0,0 +1,42 @@
|
||||
#!/bin/sh
|
||||
# Финальное исправление всех вариантов User-Agent с пробелом в начале
|
||||
|
||||
echo "=== ФИНАЛЬНОЕ ИСПРАВЛЕНИЕ USER-AGENT ==="
|
||||
echo ""
|
||||
|
||||
# Исправляем "External Web Loader" вариант
|
||||
echo "Исправление external_web.py..."
|
||||
docker exec open-webui sed -i 's/"User-Agent": " (https:\/\/github\.com\/open-webui\/open-webui) External Web Loader"/"User-Agent": "Open-WebUI-External-Web-Loader"/g' /app/backend/open_webui/retrieval/loaders/external_web.py
|
||||
|
||||
# Исправляем "RAG Bot" вариант (на всякий случай еще раз)
|
||||
echo "Исправление всех вариантов RAG Bot..."
|
||||
docker exec open-webui find /app/backend -name "*.py" -type f -exec sed -i 's/"User-Agent": " (https:\/\/github\.com\/open-webui\/open-webui) RAG Bot"/"User-Agent": "Open-WebUI-RAG-Bot"/g' {} \;
|
||||
docker exec open-webui find /app/backend -name "*.py" -type f -exec sed -i "s/'User-Agent': ' (https:\/\/github\.com\/open-webui\/open-webui) RAG Bot'/'User-Agent': 'Open-WebUI-RAG-Bot'/g" {} \;
|
||||
|
||||
# Универсальная замена - любой User-Agent с пробелом в начале
|
||||
echo "Универсальная замена всех User-Agent с пробелом..."
|
||||
docker exec open-webui find /app/backend -name "*.py" -type f -exec sed -i 's/"User-Agent": " \([^"]*github\.com\/open-webui[^"]*\)"/"User-Agent": "Open-WebUI-Bot"/g' {} \;
|
||||
docker exec open-webui find /app/backend -name "*.py" -type f -exec sed -i "s/'User-Agent': ' \([^']*github\.com\/open-webui[^']*\)'/'User-Agent': 'Open-WebUI-Bot'/g" {} \;
|
||||
|
||||
# Очистка кеша
|
||||
echo "Очистка кеша Python..."
|
||||
docker exec open-webui find /app/backend -name "*.pyc" -delete 2>/dev/null
|
||||
docker exec open-webui find /app/backend -name "__pycache__" -type d -exec rm -rf {} + 2>/dev/null || true
|
||||
|
||||
# Проверка
|
||||
echo ""
|
||||
echo "Проверка результата..."
|
||||
PROBLEM_COUNT=$(docker exec open-webui grep -rn '"User-Agent": " (' /app/backend --include="*.py" 2>/dev/null | wc -l)
|
||||
if [ "$PROBLEM_COUNT" -eq 0 ]; then
|
||||
echo "✓ Все проблемные строки исправлены!"
|
||||
else
|
||||
echo "⚠ Найдено проблемных строк: $PROBLEM_COUNT"
|
||||
docker exec open-webui grep -rn '"User-Agent": " (' /app/backend --include="*.py" 2>/dev/null
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Перезапуск Open WebUI..."
|
||||
docker restart open-webui
|
||||
|
||||
echo ""
|
||||
echo "✓ Готово! Подождите 15 секунд и проверьте поиск."
|
||||
35
scripts/init-logos.sh
Normal file
35
scripts/init-logos.sh
Normal file
@@ -0,0 +1,35 @@
|
||||
#!/bin/bash
|
||||
# Скрипт для автоматической замены логотипов при запуске контейнера
|
||||
# Этот скрипт можно запускать при каждом старте контейнера
|
||||
|
||||
MEDIA_DIR="/app/media"
|
||||
MAX_RETRIES=10
|
||||
RETRY_DELAY=2
|
||||
|
||||
# Ждем пока контейнер полностью запустится
|
||||
for i in $(seq 1 $MAX_RETRIES); do
|
||||
if curl -f http://localhost:8080/health >/dev/null 2>&1; then
|
||||
break
|
||||
fi
|
||||
sleep $RETRY_DELAY
|
||||
done
|
||||
|
||||
# Находим все favicon и logo файлы
|
||||
find /app -type f \( -name "favicon.png" -o -name "favicon.ico" -o -name "logo.png" -o -name "logo.svg" \) 2>/dev/null | while read file; do
|
||||
dir=$(dirname "$file")
|
||||
name=$(basename "$file")
|
||||
|
||||
# Заменяем favicon
|
||||
if [[ "$name" == favicon* ]] && [ -f "$MEDIA_DIR/favicon.png" ]; then
|
||||
cp "$MEDIA_DIR/favicon.png" "$file" 2>/dev/null || true
|
||||
# Также создаем .ico
|
||||
cp "$MEDIA_DIR/favicon.png" "$dir/favicon.ico" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Заменяем logo
|
||||
if [[ "$name" == logo* ]] && [ -f "$MEDIA_DIR/logo.png" ]; then
|
||||
cp "$MEDIA_DIR/logo.png" "$file" 2>/dev/null || true
|
||||
# Также создаем .svg
|
||||
cp "$MEDIA_DIR/logo.png" "$dir/logo.svg" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
45
scripts/init.sh
Executable file
45
scripts/init.sh
Executable file
@@ -0,0 +1,45 @@
|
||||
#!/bin/bash
|
||||
# Скрипт инициализации iiEasy AI-платформы
|
||||
# Выполняет ребрендинг и загрузку модели Ollama
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
|
||||
echo "=== Инициализация iiEasy AI-платформы ==="
|
||||
echo ""
|
||||
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
# Проверка наличия контейнеров
|
||||
if ! docker ps --format '{{.Names}}' | grep -q "^open-webui$"; then
|
||||
echo "Ошибка: Контейнер open-webui не запущен. Запустите: docker compose up -d"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! docker ps --format '{{.Names}}' | grep -q "^ollama$"; then
|
||||
echo "Ошибка: Контейнер ollama не запущен. Запустите: docker compose up -d"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 1. Ребрендинг Open WebUI
|
||||
echo "1. Применение ребрендинга Open WebUI..."
|
||||
"$SCRIPT_DIR/rebrand.sh"
|
||||
|
||||
echo ""
|
||||
echo "2. Загрузка модели Ollama (gemma3n:e4b-it-fp16)..."
|
||||
echo " Это может занять несколько минут в зависимости от скорости интернета..."
|
||||
docker exec ollama ollama pull gemma3n:e4b-it-fp16
|
||||
|
||||
echo ""
|
||||
echo "3. Проверка загруженных моделей..."
|
||||
docker exec ollama ollama list
|
||||
|
||||
echo ""
|
||||
echo "=== Инициализация завершена! ==="
|
||||
echo ""
|
||||
echo "Проверьте:"
|
||||
echo " - Open WebUI: https://odo.iieasy.ru"
|
||||
echo " - Логотип и ребрендинг применены"
|
||||
echo " - Модель gemma3n:e4b-it-fp16 доступна в Ollama"
|
||||
58
scripts/install_vision_model.sh
Executable file
58
scripts/install_vision_model.sh
Executable file
@@ -0,0 +1,58 @@
|
||||
#!/bin/bash
|
||||
# Установка специализированной vision модели для Ollama
|
||||
|
||||
set -e
|
||||
|
||||
CONTAINER_OLLAMA="ollama"
|
||||
MODEL_CHOICE="${1:-llava}"
|
||||
|
||||
echo "=== Установка Vision модели для Ollama ==="
|
||||
echo ""
|
||||
|
||||
# Определяем команду docker
|
||||
DOCKER_CMD="docker"
|
||||
if ! docker ps >/dev/null 2>&1; then
|
||||
DOCKER_CMD="sudo docker"
|
||||
fi
|
||||
|
||||
echo "Доступные vision модели:"
|
||||
echo " 1. llava:latest (LLaVA 1.6) - 7B, лучший выбор для vision"
|
||||
echo " 2. bakllava:latest (BakLLaVA) - 7B, Mistral + LLaVA"
|
||||
echo " 3. llama3.2-vision:latest (Llama 3.2 Vision) - 11B, требует 8GB VRAM"
|
||||
echo ""
|
||||
|
||||
if [ "$MODEL_CHOICE" = "llava" ]; then
|
||||
MODEL="llava:latest"
|
||||
echo "Выбрана модель: $MODEL (LLaVA 1.6)"
|
||||
elif [ "$MODEL_CHOICE" = "bakllava" ]; then
|
||||
MODEL="bakllava:latest"
|
||||
echo "Выбрана модель: $MODEL (BakLLaVA)"
|
||||
elif [ "$MODEL_CHOICE" = "llama3.2" ]; then
|
||||
MODEL="llama3.2-vision:latest"
|
||||
echo "Выбрана модель: $MODEL (Llama 3.2 Vision)"
|
||||
else
|
||||
MODEL="llava:latest"
|
||||
echo "Используется модель по умолчанию: $MODEL"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Загрузка модели $MODEL..."
|
||||
echo "Это может занять несколько минут в зависимости от скорости интернета..."
|
||||
echo ""
|
||||
|
||||
$DOCKER_CMD exec $CONTAINER_OLLAMA ollama pull "$MODEL" || {
|
||||
echo "✗ Ошибка при загрузке модели"
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo ""
|
||||
echo "✓ Модель загружена!"
|
||||
echo ""
|
||||
echo "Проверка установленных моделей:"
|
||||
$DOCKER_CMD exec $CONTAINER_OLLAMA ollama list
|
||||
|
||||
echo ""
|
||||
echo "=== Готово! ==="
|
||||
echo ""
|
||||
echo "Теперь в Open WebUI выберите модель: $MODEL"
|
||||
echo "И попробуйте загрузить изображение и задать вопрос о нем."
|
||||
248
scripts/rebrand.sh
Executable file
248
scripts/rebrand.sh
Executable file
@@ -0,0 +1,248 @@
|
||||
#!/bin/bash
|
||||
# Скрипт ребрендинга Open WebUI для iiEasy
|
||||
# ВНИМАНИЕ: Этот скрипт может ломать OAuth!
|
||||
# Рекомендуется использовать rebrand_complete.sh для полного ребрендинга
|
||||
# Заменяет логотипы, favicon, удаляет упоминания Open WebUI, отключает проверку обновлений
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
MEDIA_DIR="$PROJECT_DIR/media"
|
||||
CONTAINER_NAME="open-webui"
|
||||
|
||||
echo "=== Ребрендинг Open WebUI для iiEasy ==="
|
||||
|
||||
# Проверка наличия контейнера
|
||||
if ! docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
||||
echo "Ошибка: Контейнер ${CONTAINER_NAME} не найден. Запустите docker-compose up -d сначала."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Проверка, что контейнер запущен
|
||||
if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
||||
echo "Ошибка: Контейнер ${CONTAINER_NAME} не запущен. Запустите docker-compose up -d."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "1. Замена логотипов и favicon..."
|
||||
|
||||
# Важно: Open WebUI может использовать скомпилированные статические файлы
|
||||
# Нужно найти правильные пути и заменить файлы там, где они реально используются
|
||||
|
||||
# Определяем пути для статических файлов в Open WebUI
|
||||
# Open WebUI использует /app/web/build/_app/immutable/ для статических файлов
|
||||
STATIC_DIRS=(
|
||||
"/app/web/build/_app/immutable"
|
||||
"/app/web/static"
|
||||
"/app/web/build"
|
||||
"/app/backend/static"
|
||||
"/app/static"
|
||||
"/app/public"
|
||||
)
|
||||
|
||||
# Находим существующую директорию со статическими файлами
|
||||
STATIC_DIR=""
|
||||
for dir in "${STATIC_DIRS[@]}"; do
|
||||
if docker exec "${CONTAINER_NAME}" test -d "$dir" 2>/dev/null; then
|
||||
STATIC_DIR="$dir"
|
||||
echo " Найдена директория статических файлов: $STATIC_DIR"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -z "$STATIC_DIR" ]; then
|
||||
echo " Предупреждение: Директория статических файлов не найдена, пробуем найти через поиск favicon..."
|
||||
# Ищем где находятся существующие favicon файлы
|
||||
FAVICON_PATH=$(docker exec "${CONTAINER_NAME}" find /app -name "favicon.png" -o -name "favicon.ico" 2>/dev/null | head -1)
|
||||
if [ -n "$FAVICON_PATH" ]; then
|
||||
STATIC_DIR=$(dirname "$FAVICON_PATH")
|
||||
echo " Найдена директория через поиск favicon: $STATIC_DIR"
|
||||
else
|
||||
STATIC_DIR="/app/web/build/_app/immutable"
|
||||
echo " Используем стандартный путь: $STATIC_DIR"
|
||||
# Создаем директорию если её нет
|
||||
docker exec "${CONTAINER_NAME}" mkdir -p "$STATIC_DIR" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
|
||||
# Копирование логотипов (приоритет: PNG > SVG)
|
||||
# Копируем во все найденные директории
|
||||
for target_dir in "$STATIC_DIR" "/app/web/build/_app/immutable" "/app/web/static" "/app/web/build"; do
|
||||
if docker exec "${CONTAINER_NAME}" test -d "$target_dir" 2>/dev/null || [ "$target_dir" = "$STATIC_DIR" ]; then
|
||||
echo " Копирование в $target_dir..."
|
||||
|
||||
# Логотипы
|
||||
if [ -f "$MEDIA_DIR/logo.png" ]; then
|
||||
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${target_dir}/logo.png" 2>/dev/null || true
|
||||
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${target_dir}/logo.svg" 2>/dev/null || true
|
||||
elif [ -f "$MEDIA_DIR/logo-light.svg" ]; then
|
||||
docker cp "$MEDIA_DIR/logo-light.svg" "${CONTAINER_NAME}:${target_dir}/logo.svg" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
if [ -f "$MEDIA_DIR/logo-dark.svg" ]; then
|
||||
docker cp "$MEDIA_DIR/logo-dark.svg" "${CONTAINER_NAME}:${target_dir}/logo-dark.svg" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Favicon
|
||||
if [ -f "$MEDIA_DIR/favicon.ico" ]; then
|
||||
docker cp "$MEDIA_DIR/favicon.ico" "${CONTAINER_NAME}:${target_dir}/favicon.ico" 2>/dev/null || true
|
||||
elif [ -f "$MEDIA_DIR/favicon.png" ]; then
|
||||
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${target_dir}/favicon.png" 2>/dev/null || true
|
||||
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${target_dir}/favicon.ico" 2>/dev/null || true
|
||||
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${target_dir}/favicon-96x96.png" 2>/dev/null || true
|
||||
elif [ -f "$MEDIA_DIR/favicon.svg" ]; then
|
||||
docker cp "$MEDIA_DIR/favicon.svg" "${CONTAINER_NAME}:${target_dir}/favicon.svg" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# Поиск и замена существующих favicon и logo файлов везде в /app
|
||||
echo " Поиск существующих favicon и logo файлов для замены..."
|
||||
EXISTING_FAVICONS=$(docker exec "${CONTAINER_NAME}" find /app -type f \( -name "favicon.png" -o -name "favicon.ico" -o -name "favicon.svg" \) 2>/dev/null | head -10)
|
||||
EXISTING_LOGOS=$(docker exec "${CONTAINER_NAME}" find /app -type f \( -name "logo.png" -o -name "logo.svg" \) 2>/dev/null | head -10)
|
||||
|
||||
echo " Найдено favicon файлов: $(echo "$EXISTING_FAVICONS" | wc -l)"
|
||||
echo " Найдено logo файлов: $(echo "$EXISTING_LOGOS" | wc -l)"
|
||||
|
||||
# Заменяем существующие favicon файлы
|
||||
if [ -f "$MEDIA_DIR/favicon.png" ]; then
|
||||
echo "$EXISTING_FAVICONS" | while read -r favicon_file; do
|
||||
if [ -n "$favicon_file" ]; then
|
||||
echo " Замена: $favicon_file"
|
||||
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${favicon_file}" 2>/dev/null || true
|
||||
# Также создаем .ico версию рядом
|
||||
favicon_dir=$(dirname "$favicon_file")
|
||||
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${favicon_dir}/favicon.ico" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Заменяем существующие logo файлы
|
||||
if [ -f "$MEDIA_DIR/logo.png" ]; then
|
||||
echo "$EXISTING_LOGOS" | while read -r logo_file; do
|
||||
if [ -n "$logo_file" ]; then
|
||||
echo " Замена: $logo_file"
|
||||
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${logo_file}" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Также копируем в корень public для веб-доступа
|
||||
PUBLIC_DIRS=(
|
||||
"/app/web/public"
|
||||
"/app/public"
|
||||
"/app/backend/public"
|
||||
)
|
||||
|
||||
for pub_dir in "${PUBLIC_DIRS[@]}"; do
|
||||
if docker exec "${CONTAINER_NAME}" test -d "$pub_dir" 2>/dev/null; then
|
||||
echo " Копирование в $pub_dir..."
|
||||
if [ -f "$MEDIA_DIR/logo.png" ]; then
|
||||
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${pub_dir}/logo.png" 2>/dev/null || true
|
||||
fi
|
||||
if [ -f "$MEDIA_DIR/favicon.png" ]; then
|
||||
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${pub_dir}/favicon.png" 2>/dev/null || true
|
||||
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${pub_dir}/favicon.ico" 2>/dev/null || true
|
||||
fi
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
echo " Примечание: Если логотипы не изменились, настройте их через Admin Panel:"
|
||||
echo " Settings → Appearance → Logo (загрузите файлы из /app/media/)"
|
||||
|
||||
echo "2. Поиск и замена текстовых упоминаний 'Open WebUI'..."
|
||||
|
||||
# Поиск файлов с упоминаниями Open WebUI в статических файлах и конфигурации
|
||||
# ВАЖНО: ИСКЛЮЧАЕМ ВСЕ файлы, связанные с OAuth/Authentik/аутентификацией
|
||||
# Это включает: oauth.py, auth.py, login.py, и все файлы в директориях oauth, oidc, auth, login
|
||||
docker exec "${CONTAINER_NAME}" find /app -type f \( -name "*.py" -o -name "*.html" -o -name "*.js" -o -name "*.jsx" -o -name "*.ts" -o -name "*.tsx" -o -name "*.json" -o -name "*.svelte" \) \
|
||||
! -path "*/oauth*" ! -path "*/oidc*" ! -path "*authentik*" ! -path "*openid*" \
|
||||
! -name "*oauth*" ! -name "*oidc*" ! -name "*auth*" ! -name "*login*" \
|
||||
! -path "*/utils/oauth*" ! -path "*/utils/auth*" ! -path "*/backend/open_webui/utils/oauth*" \
|
||||
-exec grep -l "Open WebUI\|open-webui\|openwebui\|\(Open WebUI\)" {} \; 2>/dev/null | while read file; do
|
||||
echo " Обработка: $file"
|
||||
# Замена "Open WebUI" на "iiEasyWeb" (только в тексте интерфейса, не в URL)
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/Open WebUI/iiEasyWeb/g' "$file" 2>/dev/null || true
|
||||
# Удаление "(Open WebUI)" в скобках - заменяем на пустую строку или только "iiEasyWeb"
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/(Open WebUI)//g' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/\(Open WebUI\)//g' "$file" 2>/dev/null || true
|
||||
# Замена "iiEasyWeb (Open WebUI)" на просто "iiEasyWeb"
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/iiEasyWeb (Open WebUI)/iiEasyWeb/g' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/iiEasyWeb \(Open WebUI\)/iiEasyWeb/g' "$file" 2>/dev/null || true
|
||||
# ВАЖНО: НЕ заменяем open-webui в нижнем регистре, так как это может быть частью URL или конфигурации OAuth
|
||||
done
|
||||
|
||||
# Специальная обработка для удаления "(Open WebUI)" из заголовков и описаний
|
||||
echo "3. Удаление упоминаний '(Open WebUI)' из интерфейса..."
|
||||
# ИСКЛЮЧАЕМ файлы OAuth/Authentik
|
||||
docker exec "${CONTAINER_NAME}" find /app -type f \( -name "*.html" -o -name "*.js" -o -name "*.jsx" -o -name "*.ts" -o -name "*.tsx" -o -name "*.svelte" \) \
|
||||
! -path "*/oauth*" ! -path "*/oidc*" ! -path "*authentik*" ! -path "*openid*" \
|
||||
-exec grep -l "(Open WebUI)\|\(Open WebUI\)" {} \; 2>/dev/null | while read file; do
|
||||
echo " Удаление '(Open WebUI)' из: $file"
|
||||
# Удаляем различные варианты написания в скобках
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/(Open WebUI)//g' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/\(Open WebUI\)//g' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/ (Open WebUI)//g' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/ \(Open WebUI\)//g' "$file" 2>/dev/null || true
|
||||
done
|
||||
|
||||
echo "4. Отключение проверки обновлений..."
|
||||
|
||||
# Поиск и отключение проверки обновлений через GitHub API
|
||||
# ИСКЛЮЧАЕМ файлы OAuth/Authentik
|
||||
docker exec "${CONTAINER_NAME}" find /app/backend -type f \( -name "*.py" -o -name "*.js" \) \
|
||||
! -path "*/oauth*" ! -path "*/oidc*" ! -path "*authentik*" ! -path "*openid*" \
|
||||
-exec grep -l "github.com.*releases\|check.*update\|update.*check" {} \; 2>/dev/null | while read file; do
|
||||
echo " Отключение проверки обновлений в: $file"
|
||||
# Комментирование вызовов GitHub API для проверки обновлений
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's|https://api.github.com/repos/open-webui|# https://api.github.com/repos/open-webui|g' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's|github.com/open-webui|# github.com/open-webui|g' "$file" 2>/dev/null || true
|
||||
done
|
||||
|
||||
# Отключение проверки обновлений через переменные окружения (уже настроено в docker-compose.yml)
|
||||
echo " Проверка обновлений отключена через переменные окружения"
|
||||
|
||||
echo "5. Удаление аналитики и телеметрии..."
|
||||
|
||||
# Поиск и отключение аналитики (более аккуратно, чтобы не сломать код)
|
||||
# Комментируем только целые строки с импортами аналитики, а не части строк
|
||||
# ИСКЛЮЧАЕМ файлы OAuth/Authentik
|
||||
docker exec "${CONTAINER_NAME}" find /app/backend -type f \( -name "*.py" -o -name "*.js" \) \
|
||||
! -path "*/oauth*" ! -path "*/oidc*" ! -path "*authentik*" ! -path "*openid*" \
|
||||
-exec grep -l "from.*telemetry\|import.*telemetry\|analytics\|tracking\|gtag\|ga(\|google-analytics" {} \; 2>/dev/null | while read file; do
|
||||
echo " Отключение аналитики в: $file"
|
||||
# Комментируем только целые строки импорта, начинающиеся с from или import
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/^[[:space:]]*from.*telemetry/s/^/# /' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/^[[:space:]]*import.*telemetry/s/^/# /' "$file" 2>/dev/null || true
|
||||
done
|
||||
|
||||
echo "6. Удаление 'Powered by' футеров..."
|
||||
|
||||
# Поиск футеров с упоминанием Open WebUI
|
||||
# ИСКЛЮЧАЕМ файлы OAuth/Authentik
|
||||
docker exec "${CONTAINER_NAME}" find /app/backend -type f \( -name "*.html" -o -name "*.js" -o -name "*.tsx" -o -name "*.jsx" \) \
|
||||
! -path "*/oauth*" ! -path "*/oidc*" ! -path "*authentik*" ! -path "*openid*" \
|
||||
-exec grep -l "Powered by\|powered by" {} \; 2>/dev/null | while read file; do
|
||||
echo " Удаление футера в: $file"
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/Powered by.*[Oo]pen.*[Ww]eb[Uu][Ii]/d' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/powered by.*[Oo]pen.*[Ww]eb[Uu][Ii]/d' "$file" 2>/dev/null || true
|
||||
done
|
||||
|
||||
echo "7. Перезапуск контейнера для применения изменений..."
|
||||
|
||||
docker restart "${CONTAINER_NAME}" >/dev/null 2>&1 || {
|
||||
echo "Предупреждение: Не удалось перезапустить контейнер автоматически."
|
||||
echo "Перезапустите вручную: docker restart ${CONTAINER_NAME}"
|
||||
}
|
||||
|
||||
echo ""
|
||||
echo "=== Ребрендинг завершен! ==="
|
||||
echo ""
|
||||
echo "Проверьте изменения:"
|
||||
echo " 1. Откройте https://odo.iieasy.ru в браузере"
|
||||
echo " 2. Проверьте логотип и favicon"
|
||||
echo " 3. Проверьте отсутствие упоминаний 'Open WebUI'"
|
||||
echo ""
|
||||
echo "Примечание: Если изменения не отображаются, очистите кеш браузера."
|
||||
126
scripts/rebrand_careful.sh
Executable file
126
scripts/rebrand_careful.sh
Executable file
@@ -0,0 +1,126 @@
|
||||
#!/bin/bash
|
||||
# ОЧЕНЬ АККУРАТНЫЙ ребрендинг Open WebUI для iiEasy
|
||||
# Только безопасные замены текста, НЕ трогает код
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
MEDIA_DIR="$PROJECT_DIR/media"
|
||||
CONTAINER_NAME="open-webui"
|
||||
|
||||
echo "=== АККУРАТНЫЙ ребрендинг Open WebUI для iiEasy ==="
|
||||
echo "⚠ Только безопасные замены текста в интерфейсе"
|
||||
echo "⚠ Код Python/JS НЕ изменяется"
|
||||
echo "⚠ OAuth/Authentik полностью защищены"
|
||||
echo ""
|
||||
|
||||
# Проверка наличия контейнера
|
||||
if ! docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
||||
echo "Ошибка: Контейнер ${CONTAINER_NAME} не найден."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
||||
echo "Ошибка: Контейнер ${CONTAINER_NAME} не запущен."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "1. Замена логотипов и favicon..."
|
||||
|
||||
STATIC_DIRS=(
|
||||
"/app/web/build/_app/immutable"
|
||||
"/app/web/static"
|
||||
"/app/web/build"
|
||||
)
|
||||
|
||||
STATIC_DIR=""
|
||||
for dir in "${STATIC_DIRS[@]}"; do
|
||||
if docker exec "${CONTAINER_NAME}" test -d "$dir" 2>/dev/null; then
|
||||
STATIC_DIR="$dir"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -z "$STATIC_DIR" ]; then
|
||||
FAVICON_PATH=$(docker exec "${CONTAINER_NAME}" find /app/web -name "favicon.png" -o -name "favicon.ico" 2>/dev/null | head -1)
|
||||
if [ -n "$FAVICON_PATH" ]; then
|
||||
STATIC_DIR=$(dirname "$FAVICON_PATH")
|
||||
else
|
||||
STATIC_DIR="/app/web/build/_app/immutable"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Копирование логотипов
|
||||
if [ -f "$MEDIA_DIR/logo.png" ]; then
|
||||
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${STATIC_DIR}/logo.png" 2>/dev/null || true
|
||||
fi
|
||||
if [ -f "$MEDIA_DIR/favicon.png" ]; then
|
||||
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${STATIC_DIR}/favicon.png" 2>/dev/null || true
|
||||
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${STATIC_DIR}/favicon.ico" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
echo "2. Замена текста 'Open WebUI' на 'iiEasyWeb' ТОЛЬКО в HTML/текстовых строках..."
|
||||
|
||||
# ТОЛЬКО фронтенд HTML/Svelte файлы - заменяем только в текстовом контенте
|
||||
docker exec "${CONTAINER_NAME}" find /app/web -type f \( -name "*.html" -o -name "*.svelte" \) \
|
||||
! -path "*/node_modules/*" ! -path "*/.next/*" ! -path "*/dist/*" \
|
||||
2>/dev/null | while read file; do
|
||||
# Заменяем только в текстовом контенте между тегами, не в атрибутах или коде
|
||||
if docker exec "${CONTAINER_NAME}" grep -q "Open WebUI" "$file" 2>/dev/null; then
|
||||
echo " HTML/Svelte: $file"
|
||||
# Заменяем только "Open WebUI" (с заглавными) в тексте, не трогаем код
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/>Open WebUI</>iiEasyWeb</g' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/Open WebUI/iiEasyWeb/g' "$file" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
|
||||
echo "3. Удаление '(Open WebUI)' из текста интерфейса..."
|
||||
|
||||
# Только удаляем текст в скобках из HTML
|
||||
docker exec "${CONTAINER_NAME}" find /app/web -type f \( -name "*.html" -o -name "*.svelte" \) \
|
||||
! -path "*/node_modules/*" ! -path "*/.next/*" ! -path "*/dist/*" \
|
||||
2>/dev/null | while read file; do
|
||||
if docker exec "${CONTAINER_NAME}" grep -q "(Open WebUI)" "$file" 2>/dev/null; then
|
||||
echo " Удаление скобок: $file"
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/(Open WebUI)//g' "$file" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
|
||||
echo "4. Удаление 'Powered by Open WebUI' футеров..."
|
||||
|
||||
# Только удаляем строки с "Powered by"
|
||||
docker exec "${CONTAINER_NAME}" find /app/web -type f \( -name "*.html" -o -name "*.svelte" \) \
|
||||
! -path "*/node_modules/*" ! -path "*/.next/*" ! -path "*/dist/*" \
|
||||
2>/dev/null | while read file; do
|
||||
if docker exec "${CONTAINER_NAME}" grep -q "Powered by.*Open WebUI\|powered by.*Open WebUI" "$file" 2>/dev/null; then
|
||||
echo " Удаление футера: $file"
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/Powered by.*Open WebUI/d' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/powered by.*Open WebUI/d' "$file" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "✓ Изменены ТОЛЬКО текстовые строки в HTML/Svelte"
|
||||
echo "✓ Код Python/JS НЕ изменялся"
|
||||
echo "✓ OAuth/Authentik полностью защищены"
|
||||
echo ""
|
||||
|
||||
echo "5. Перезапуск контейнера..."
|
||||
|
||||
docker restart "${CONTAINER_NAME}" >/dev/null 2>&1 || {
|
||||
echo "Предупреждение: Перезапустите вручную: docker restart ${CONTAINER_NAME}"
|
||||
}
|
||||
|
||||
echo ""
|
||||
echo "=== АККУРАТНЫЙ ребрендинг завершен! ==="
|
||||
echo ""
|
||||
echo "Проверьте:"
|
||||
echo " 1. Откройте https://odo.iieasy.ru"
|
||||
echo " 2. Все должно работать нормально"
|
||||
echo " 3. Текст 'Open WebUI' заменен на 'iiEasyWeb' в интерфейсе"
|
||||
echo ""
|
||||
echo "Если что-то не работает, пересоздайте контейнер:"
|
||||
echo " sudo docker compose stop open-webui"
|
||||
echo " sudo docker compose rm -f open-webui"
|
||||
echo " sudo docker compose up -d open-webui"
|
||||
274
scripts/rebrand_complete.sh
Executable file
274
scripts/rebrand_complete.sh
Executable file
@@ -0,0 +1,274 @@
|
||||
#!/bin/bash
|
||||
# ПОЛНЫЙ ребрендинг Open WebUI для iiEasy
|
||||
# Удаляет ВСЕ упоминания Open WebUI, отключает телеметрию
|
||||
# НО защищает OAuth/Authentik файлы
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
MEDIA_DIR="$PROJECT_DIR/media"
|
||||
CONTAINER_NAME="open-webui"
|
||||
|
||||
echo "=== ПОЛНЫЙ ребрендинг Open WebUI для iiEasy ==="
|
||||
echo "✓ Удаление ВСЕХ упоминаний Open WebUI"
|
||||
echo "✓ Отключение телеметрии и аналитики"
|
||||
echo "✓ Защита OAuth/Authentik файлов"
|
||||
echo ""
|
||||
|
||||
# Проверка наличия контейнера
|
||||
if ! docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
||||
echo "Ошибка: Контейнер ${CONTAINER_NAME} не найден. Запустите docker-compose up -d сначала."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Проверка, что контейнер запущен
|
||||
if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
||||
echo "Ошибка: Контейнер ${CONTAINER_NAME} не запущен. Запустите docker-compose up -d."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "1. Замена логотипов и favicon..."
|
||||
|
||||
STATIC_DIRS=(
|
||||
"/app/web/build/_app/immutable"
|
||||
"/app/web/static"
|
||||
"/app/web/build"
|
||||
"/app/backend/static"
|
||||
"/app/static"
|
||||
"/app/public"
|
||||
)
|
||||
|
||||
STATIC_DIR=""
|
||||
for dir in "${STATIC_DIRS[@]}"; do
|
||||
if docker exec "${CONTAINER_NAME}" test -d "$dir" 2>/dev/null; then
|
||||
STATIC_DIR="$dir"
|
||||
echo " Найдена директория: $STATIC_DIR"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -z "$STATIC_DIR" ]; then
|
||||
FAVICON_PATH=$(docker exec "${CONTAINER_NAME}" find /app -name "favicon.png" -o -name "favicon.ico" 2>/dev/null | head -1)
|
||||
if [ -n "$FAVICON_PATH" ]; then
|
||||
STATIC_DIR=$(dirname "$FAVICON_PATH")
|
||||
else
|
||||
STATIC_DIR="/app/web/build/_app/immutable"
|
||||
docker exec "${CONTAINER_NAME}" mkdir -p "$STATIC_DIR" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
|
||||
# Копирование логотипов
|
||||
for target_dir in "$STATIC_DIR" "/app/web/build/_app/immutable" "/app/web/static" "/app/web/build"; do
|
||||
if docker exec "${CONTAINER_NAME}" test -d "$target_dir" 2>/dev/null || [ "$target_dir" = "$STATIC_DIR" ]; then
|
||||
echo " Копирование в $target_dir..."
|
||||
|
||||
if [ -f "$MEDIA_DIR/logo.png" ]; then
|
||||
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${target_dir}/logo.png" 2>/dev/null || true
|
||||
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${target_dir}/logo.svg" 2>/dev/null || true
|
||||
elif [ -f "$MEDIA_DIR/logo-light.svg" ]; then
|
||||
docker cp "$MEDIA_DIR/logo-light.svg" "${CONTAINER_NAME}:${target_dir}/logo.svg" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
if [ -f "$MEDIA_DIR/logo-dark.svg" ]; then
|
||||
docker cp "$MEDIA_DIR/logo-dark.svg" "${CONTAINER_NAME}:${target_dir}/logo-dark.svg" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
if [ -f "$MEDIA_DIR/favicon.ico" ]; then
|
||||
docker cp "$MEDIA_DIR/favicon.ico" "${CONTAINER_NAME}:${target_dir}/favicon.ico" 2>/dev/null || true
|
||||
elif [ -f "$MEDIA_DIR/favicon.png" ]; then
|
||||
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${target_dir}/favicon.png" 2>/dev/null || true
|
||||
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${target_dir}/favicon.ico" 2>/dev/null || true
|
||||
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${target_dir}/favicon-96x96.png" 2>/dev/null || true
|
||||
elif [ -f "$MEDIA_DIR/favicon.svg" ]; then
|
||||
docker cp "$MEDIA_DIR/favicon.svg" "${CONTAINER_NAME}:${target_dir}/favicon.svg" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# Замена существующих favicon и logo
|
||||
EXISTING_FAVICONS=$(docker exec "${CONTAINER_NAME}" find /app -type f \( -name "favicon.png" -o -name "favicon.ico" -o -name "favicon.svg" \) 2>/dev/null | head -10)
|
||||
EXISTING_LOGOS=$(docker exec "${CONTAINER_NAME}" find /app -type f \( -name "logo.png" -o -name "logo.svg" \) 2>/dev/null | head -10)
|
||||
|
||||
if [ -f "$MEDIA_DIR/favicon.png" ]; then
|
||||
echo "$EXISTING_FAVICONS" | while read -r favicon_file; do
|
||||
if [ -n "$favicon_file" ]; then
|
||||
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${favicon_file}" 2>/dev/null || true
|
||||
favicon_dir=$(dirname "$favicon_file")
|
||||
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${favicon_dir}/favicon.ico" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [ -f "$MEDIA_DIR/logo.png" ]; then
|
||||
echo "$EXISTING_LOGOS" | while read -r logo_file; do
|
||||
if [ -n "$logo_file" ]; then
|
||||
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${logo_file}" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
PUBLIC_DIRS=("/app/web/public" "/app/public" "/app/backend/public")
|
||||
for pub_dir in "${PUBLIC_DIRS[@]}"; do
|
||||
if docker exec "${CONTAINER_NAME}" test -d "$pub_dir" 2>/dev/null; then
|
||||
if [ -f "$MEDIA_DIR/logo.png" ]; then
|
||||
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${pub_dir}/logo.png" 2>/dev/null || true
|
||||
fi
|
||||
if [ -f "$MEDIA_DIR/favicon.png" ]; then
|
||||
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${pub_dir}/favicon.png" 2>/dev/null || true
|
||||
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${pub_dir}/favicon.ico" 2>/dev/null || true
|
||||
fi
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
echo "2. Удаление упоминаний 'Open WebUI' из фронтенда..."
|
||||
|
||||
# Изменяем ТОЛЬКО фронтенд файлы (веб-интерфейс)
|
||||
docker exec "${CONTAINER_NAME}" find /app/web -type f \( -name "*.html" -o -name "*.js" -o -name "*.jsx" -o -name "*.ts" -o -name "*.tsx" -o -name "*.svelte" -o -name "*.json" \) \
|
||||
! -path "*/node_modules/*" ! -path "*/.next/*" ! -path "*/dist/*" \
|
||||
2>/dev/null | while read file; do
|
||||
if docker exec "${CONTAINER_NAME}" grep -q "Open WebUI\|open-webui\|openwebui\|\(Open WebUI\)" "$file" 2>/dev/null; then
|
||||
echo " Фронтенд: $file"
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/Open WebUI/iiEasyWeb/g' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/(Open WebUI)//g' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/\(Open WebUI\)//g' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/iiEasyWeb (Open WebUI)/iiEasyWeb/g' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/iiEasyWeb \(Open WebUI\)/iiEasyWeb/g' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/open-webui/iieasyweb/g' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/openwebui/iieasyweb/g' "$file" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
|
||||
echo "3. Удаление упоминаний 'Open WebUI' из бэкенда (кроме OAuth)..."
|
||||
|
||||
# Изменяем бэкенд Python файлы, НО исключаем OAuth/аутентификационные файлы
|
||||
docker exec "${CONTAINER_NAME}" find /app/backend -type f -name "*.py" \
|
||||
! -path "*/oauth*" ! -path "*/oidc*" ! -path "*authentik*" ! -path "*openid*" \
|
||||
! -name "*oauth*" ! -name "*oidc*" ! -name "*auth*" ! -name "*login*" \
|
||||
! -path "*/utils/oauth*" ! -path "*/utils/auth*" \
|
||||
! -path "*/open_webui/utils/oauth*" ! -path "*/open_webui/utils/auth*" \
|
||||
! -path "*/open_webui/main.py" \
|
||||
2>/dev/null | while read file; do
|
||||
if docker exec "${CONTAINER_NAME}" grep -q "Open WebUI\|open-webui\|openwebui\|\(Open WebUI\)" "$file" 2>/dev/null; then
|
||||
echo " Бэкенд: $file"
|
||||
# Замена только текста, не URL или конфигурации
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/Open WebUI/iiEasyWeb/g' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/(Open WebUI)//g' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/\(Open WebUI\)//g' "$file" 2>/dev/null || true
|
||||
# НЕ заменяем open-webui/openwebui в бэкенде - может быть частью конфигурации
|
||||
fi
|
||||
done
|
||||
|
||||
echo "4. Отключение телеметрии и аналитики в бэкенде..."
|
||||
|
||||
# Отключаем телеметрию в Python файлах, НО исключаем OAuth файлы
|
||||
docker exec "${CONTAINER_NAME}" find /app/backend -type f -name "*.py" \
|
||||
! -path "*/oauth*" ! -path "*/oidc*" ! -path "*authentik*" ! -path "*openid*" \
|
||||
! -name "*oauth*" ! -name "*oidc*" ! -name "*auth*" ! -name "*login*" \
|
||||
! -path "*/utils/oauth*" ! -path "*/utils/auth*" \
|
||||
! -path "*/open_webui/utils/oauth*" ! -path "*/open_webui/utils/auth*" \
|
||||
! -path "*/open_webui/main.py" \
|
||||
-exec grep -l "from.*telemetry\|import.*telemetry\|analytics\|tracking\|gtag\|ga(\|google-analytics\|sentry\|posthog" {} \; 2>/dev/null | while read file; do
|
||||
echo " Отключение телеметрии в: $file"
|
||||
# Комментируем только целые строки импорта
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/^[[:space:]]*from.*telemetry/s/^/# /' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/^[[:space:]]*import.*telemetry/s/^/# /' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/^[[:space:]]*from.*analytics/s/^/# /' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/^[[:space:]]*import.*analytics/s/^/# /' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/^[[:space:]]*from.*tracking/s/^/# /' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/^[[:space:]]*import.*tracking/s/^/# /' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/^[[:space:]]*from.*sentry/s/^/# /' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/^[[:space:]]*import.*sentry/s/^/# /' "$file" 2>/dev/null || true
|
||||
done
|
||||
|
||||
echo "5. Отключение телеметрии во фронтенде..."
|
||||
|
||||
docker exec "${CONTAINER_NAME}" find /app/web -type f \( -name "*.js" -o -name "*.ts" -o -name "*.jsx" -o -name "*.tsx" \) \
|
||||
! -path "*/node_modules/*" ! -path "*/.next/*" ! -path "*/dist/*" \
|
||||
-exec grep -l "telemetry\|analytics\|tracking\|gtag\|ga(\|google-analytics\|sentry\|posthog" {} \; 2>/dev/null | while read file; do
|
||||
echo " Отключение телеметрии в: $file"
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/import.*telemetry/s/^/\/\/ /' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/from.*telemetry/s/^/\/\/ /' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/import.*analytics/s/^/\/\/ /' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/from.*analytics/s/^/\/\/ /' "$file" 2>/dev/null || true
|
||||
done
|
||||
|
||||
echo "6. Отключение проверки обновлений..."
|
||||
|
||||
# Отключаем проверку обновлений в бэкенде (кроме OAuth)
|
||||
docker exec "${CONTAINER_NAME}" find /app/backend -type f -name "*.py" \
|
||||
! -path "*/oauth*" ! -path "*/oidc*" ! -path "*authentik*" ! -path "*openid*" \
|
||||
! -name "*oauth*" ! -name "*oidc*" ! -name "*auth*" ! -name "*login*" \
|
||||
! -path "*/utils/oauth*" ! -path "*/utils/auth*" \
|
||||
! -path "*/open_webui/utils/oauth*" ! -path "*/open_webui/utils/auth*" \
|
||||
! -path "*/open_webui/main.py" \
|
||||
-exec grep -l "github.com.*releases\|check.*update\|update.*check" {} \; 2>/dev/null | while read file; do
|
||||
echo " Отключение проверки обновлений в: $file"
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's|https://api.github.com/repos/open-webui|# https://api.github.com/repos/open-webui|g' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's|github.com/open-webui|# github.com/open-webui|g' "$file" 2>/dev/null || true
|
||||
done
|
||||
|
||||
# Отключаем проверку обновлений во фронтенде
|
||||
docker exec "${CONTAINER_NAME}" find /app/web -type f \( -name "*.js" -o -name "*.ts" \) \
|
||||
! -path "*/node_modules/*" ! -path "*/.next/*" ! -path "*/dist/*" \
|
||||
-exec grep -l "github.com.*releases\|check.*update\|update.*check" {} \; 2>/dev/null | while read file; do
|
||||
echo " Отключение проверки обновлений в: $file"
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's|https://api.github.com/repos/open-webui|// https://api.github.com/repos/open-webui|g' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's|github.com/open-webui|// github.com/open-webui|g' "$file" 2>/dev/null || true
|
||||
done
|
||||
|
||||
echo "7. Удаление 'Powered by' футеров..."
|
||||
|
||||
# Удаляем футеры из фронтенда
|
||||
docker exec "${CONTAINER_NAME}" find /app/web -type f \( -name "*.html" -o -name "*.js" -o -name "*.tsx" -o -name "*.jsx" -o -name "*.svelte" \) \
|
||||
! -path "*/node_modules/*" ! -path "*/.next/*" ! -path "*/dist/*" \
|
||||
2>/dev/null | while read file; do
|
||||
if docker exec "${CONTAINER_NAME}" grep -q "Powered by\|powered by" "$file" 2>/dev/null; then
|
||||
echo " Удаление футера из: $file"
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/Powered by.*[Oo]pen.*[Ww]eb[Uu][Ii]/d' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/powered by.*[Oo]pen.*[Ww]eb[Uu][Ii]/d' "$file" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
|
||||
# Удаляем футеры из бэкенда (кроме OAuth)
|
||||
docker exec "${CONTAINER_NAME}" find /app/backend -type f \( -name "*.py" -o -name "*.html" \) \
|
||||
! -path "*/oauth*" ! -path "*/oidc*" ! -path "*authentik*" ! -path "*openid*" \
|
||||
! -name "*oauth*" ! -name "*oidc*" ! -name "*auth*" ! -name "*login*" \
|
||||
! -path "*/utils/oauth*" ! -path "*/utils/auth*" \
|
||||
! -path "*/open_webui/utils/oauth*" ! -path "*/open_webui/utils/auth*" \
|
||||
! -path "*/open_webui/main.py" \
|
||||
2>/dev/null | while read file; do
|
||||
if docker exec "${CONTAINER_NAME}" grep -q "Powered by\|powered by" "$file" 2>/dev/null; then
|
||||
echo " Удаление футера из: $file"
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/Powered by.*[Oo]pen.*[Ww]eb[Uu][Ii]/d' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/powered by.*[Oo]pen.*[Ww]eb[Uu][Ii]/d' "$file" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "✓ Бэкенд файлы обработаны (телеметрия отключена)"
|
||||
echo "✓ OAuth/Authentik файлы защищены"
|
||||
echo "✓ Фронтенд файлы обработаны"
|
||||
echo ""
|
||||
|
||||
echo "8. Перезапуск контейнера..."
|
||||
|
||||
docker restart "${CONTAINER_NAME}" >/dev/null 2>&1 || {
|
||||
echo "Предупреждение: Не удалось перезапустить контейнер автоматически."
|
||||
echo "Перезапустите вручную: docker restart ${CONTAINER_NAME}"
|
||||
}
|
||||
|
||||
echo ""
|
||||
echo "=== ПОЛНЫЙ ребрендинг завершен! ==="
|
||||
echo ""
|
||||
echo "Проверьте:"
|
||||
echo " 1. Откройте https://odo.iieasy.ru"
|
||||
echo " 2. Проверьте отсутствие упоминаний 'Open WebUI'"
|
||||
echo " 3. Проверьте OAuth - должен работать нормально"
|
||||
echo " 4. Проверьте отсутствие телеметрии (DevTools → Network)"
|
||||
echo ""
|
||||
echo "Если OAuth не работает, пересоздайте контейнер:"
|
||||
echo " sudo docker compose stop open-webui"
|
||||
echo " sudo docker compose rm -f open-webui"
|
||||
echo " sudo docker compose up -d open-webui"
|
||||
134
scripts/rebrand_fast.sh
Executable file
134
scripts/rebrand_fast.sh
Executable file
@@ -0,0 +1,134 @@
|
||||
#!/bin/bash
|
||||
# БЫСТРЫЙ ребрендинг - оптимизированная версия
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
MEDIA_DIR="$PROJECT_DIR/media"
|
||||
CONTAINER_NAME="open-webui"
|
||||
|
||||
echo "=== БЫСТРЫЙ ребрендинг Open WebUI для iiEasy ==="
|
||||
echo ""
|
||||
|
||||
if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
||||
echo "Ошибка: Контейнер ${CONTAINER_NAME} не запущен."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "1. Замена логотипов..."
|
||||
|
||||
STATIC_DIRS=(
|
||||
"/app/web/build/_app/immutable"
|
||||
"/app/web/static"
|
||||
"/app/web/build"
|
||||
)
|
||||
|
||||
for dir in "${STATIC_DIRS[@]}"; do
|
||||
if docker exec "${CONTAINER_NAME}" test -d "$dir" 2>/dev/null; then
|
||||
if [ -f "$MEDIA_DIR/logo.png" ]; then
|
||||
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${dir}/logo.png" 2>/dev/null || true
|
||||
fi
|
||||
if [ -f "$MEDIA_DIR/favicon.png" ]; then
|
||||
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${dir}/favicon.png" 2>/dev/null || true
|
||||
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${dir}/favicon.ico" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
echo "2. БЫСТРОЕ удаление '(Open WebUI)' - поиск только в текстовых файлах..."
|
||||
|
||||
# ОПТИМИЗАЦИЯ: Сначала находим файлы с текстом через grep -l (быстро)
|
||||
# Обрабатываем только нужные типы файлов
|
||||
FILES=$(docker exec "${CONTAINER_NAME}" find /app -type f \
|
||||
\( -name "*.html" -o -name "*.svelte" -o -name "*.js" -o -name "*.jsx" -o -name "*.ts" -o -name "*.tsx" -o -name "*.json" -o -name "*.css" -o -name "*.mjs" \) \
|
||||
! -path "*/node_modules/*" ! -path "*/.next/*" \
|
||||
! -path "*/oauth*" ! -path "*/oidc*" ! -path "*authentik*" ! -path "*openid*" \
|
||||
-exec grep -lE "(Open WebUI)|\(Open WebUI\)|iiEasyWeb.*Open.*WebUI" {} \; 2>/dev/null)
|
||||
|
||||
if [ -z "$FILES" ]; then
|
||||
echo " Файлов с '(Open WebUI)' не найдено"
|
||||
else
|
||||
COUNT=$(echo "$FILES" | wc -l)
|
||||
echo " Найдено файлов: $COUNT"
|
||||
echo "$FILES" | while read file; do
|
||||
if [ -n "$file" ]; then
|
||||
# Один sed с несколькими командами - быстрее
|
||||
docker exec "${CONTAINER_NAME}" sed -i \
|
||||
-e 's/(Open WebUI)//g' \
|
||||
-e 's/\(Open WebUI\)//g' \
|
||||
-e 's/ (Open WebUI)//g' \
|
||||
-e 's/ \(Open WebUI\)//g' \
|
||||
-e 's/iiEasyWeb (Open WebUI)/iiEasyWeb/g' \
|
||||
-e 's/iiEasyWeb \(Open WebUI\)/iiEasyWeb/g' \
|
||||
-e 's/iiEasyWeb(Open WebUI)/iiEasyWeb/g' \
|
||||
-e "s/'(Open WebUI)'//g" \
|
||||
-e 's/"(Open WebUI)"//g' \
|
||||
-e 's/`(Open WebUI)`//g' \
|
||||
-e 's/Войти в iiEasyWeb (Open WebUI)/Войти в iiEasyWeb/g' \
|
||||
"$file" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
echo " Обработка завершена"
|
||||
fi
|
||||
|
||||
echo "3. Замена ссылок на документацию..."
|
||||
|
||||
DOC_FILES=$(docker exec "${CONTAINER_NAME}" find /app -type f \
|
||||
\( -name "*.html" -o -name "*.svelte" -o -name "*.js" -o -name "*.jsx" -o -name "*.ts" -o -name "*.tsx" -o -name "*.json" \) \
|
||||
! -path "*/node_modules/*" ! -path "*/.next/*" \
|
||||
! -path "*/oauth*" ! -path "*/oidc*" ! -path "*authentik*" ! -path "*openid*" \
|
||||
-exec grep -lE "docs\.openwebui\.com|open-webui\.com/docs|github\.com/open-webui/docs" {} \; 2>/dev/null)
|
||||
|
||||
if [ -z "$DOC_FILES" ]; then
|
||||
echo " Файлов со ссылками не найдено"
|
||||
else
|
||||
echo "$DOC_FILES" | while read file; do
|
||||
if [ -n "$file" ]; then
|
||||
docker exec "${CONTAINER_NAME}" sed -i \
|
||||
-e 's|https://docs.openwebui.com|https://note.iieasy.ru|g' \
|
||||
-e 's|https://open-webui.com/docs|https://note.iieasy.ru|g' \
|
||||
-e 's|https://github.com/open-webui/docs|https://note.iieasy.ru|g' \
|
||||
-e 's|docs\.openwebui\.com|note.iieasy.ru|g' \
|
||||
-e 's|open-webui\.com/docs|note.iieasy.ru|g' \
|
||||
"$file" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
echo "4. Удаление социальных сетей и лицензии..."
|
||||
|
||||
SOCIAL_FILES=$(docker exec "${CONTAINER_NAME}" find /app -type f \
|
||||
\( -name "*.html" -o -name "*.svelte" -o -name "*.js" -o -name "*.jsx" -o -name "*.ts" -o -name "*.tsx" \) \
|
||||
! -path "*/node_modules/*" ! -path "*/.next/*" \
|
||||
! -path "*/oauth*" ! -path "*/oidc*" ! -path "*authentik*" ! -path "*openid*" \
|
||||
-exec grep -lE "discord|twitter|x\.com|Github Repo|лицензионный|License" {} \; 2>/dev/null)
|
||||
|
||||
if [ -z "$SOCIAL_FILES" ]; then
|
||||
echo " Файлов с соцсетями не найдено"
|
||||
else
|
||||
echo "$SOCIAL_FILES" | while read file; do
|
||||
if [ -n "$file" ]; then
|
||||
docker exec "${CONTAINER_NAME}" sed -i \
|
||||
-e '/discord\.gg/d' -e '/discord\.com/d' -e '/Discord/d' \
|
||||
-e '/twitter\.com/d' -e '/x\.com/d' -e '/X (formerly Twitter)/d' -e '/Twitter/d' -e '/Follow/d' \
|
||||
-e '/Github Repo/d' -e '/GitHub Repo/d' \
|
||||
-e '/лицензионный тарифный план/d' -e '/Перейдите на лицензионный/d' \
|
||||
-e '/расширенные возможности/d' -e '/настраиваемую тематику/d' \
|
||||
-e '/фирменный стиль/d' -e '/специальную поддержку/d' -e '/Лицензия/d' \
|
||||
"$file" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "5. Перезапуск контейнера..."
|
||||
|
||||
docker restart "${CONTAINER_NAME}" >/dev/null 2>&1 || {
|
||||
echo "Предупреждение: Перезапустите вручную: docker restart ${CONTAINER_NAME}"
|
||||
}
|
||||
|
||||
echo ""
|
||||
echo "=== БЫСТРЫЙ ребрендинг завершен! ==="
|
||||
echo ""
|
||||
echo "Очистите кеш браузера (Ctrl+Shift+Delete)"
|
||||
212
scripts/rebrand_final.sh
Executable file
212
scripts/rebrand_final.sh
Executable file
@@ -0,0 +1,212 @@
|
||||
#!/bin/bash
|
||||
# ФИНАЛЬНЫЙ ребрендинг - удаляет ВСЕ упоминания и меняет ссылки на документацию
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
MEDIA_DIR="$PROJECT_DIR/media"
|
||||
CONTAINER_NAME="open-webui"
|
||||
|
||||
echo "=== ФИНАЛЬНЫЙ ребрендинг Open WebUI для iiEasy ==="
|
||||
echo "✓ Удаление ВСЕХ упоминаний '(Open WebUI)'"
|
||||
echo "✓ Замена ссылок на документацию на note.iieasy.ru"
|
||||
echo ""
|
||||
|
||||
if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
||||
echo "Ошибка: Контейнер ${CONTAINER_NAME} не запущен."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "1. Замена логотипов..."
|
||||
|
||||
STATIC_DIRS=(
|
||||
"/app/web/build/_app/immutable"
|
||||
"/app/web/static"
|
||||
"/app/web/build"
|
||||
"/app/backend/static"
|
||||
)
|
||||
|
||||
for dir in "${STATIC_DIRS[@]}"; do
|
||||
if docker exec "${CONTAINER_NAME}" test -d "$dir" 2>/dev/null; then
|
||||
if [ -f "$MEDIA_DIR/logo.png" ]; then
|
||||
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${dir}/logo.png" 2>/dev/null || true
|
||||
fi
|
||||
if [ -f "$MEDIA_DIR/favicon.png" ]; then
|
||||
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${dir}/favicon.png" 2>/dev/null || true
|
||||
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${dir}/favicon.ico" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# Заменяем все существующие логотипы
|
||||
EXISTING_LOGOS=$(docker exec "${CONTAINER_NAME}" find /app -type f \( -name "logo.png" -o -name "logo.svg" \) 2>/dev/null)
|
||||
if [ -f "$MEDIA_DIR/logo.png" ]; then
|
||||
echo "$EXISTING_LOGOS" | while read -r logo_file; do
|
||||
if [ -n "$logo_file" ] && [[ ! "$logo_file" == *"node_modules"* ]]; then
|
||||
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${logo_file}" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
echo "2. АГРЕССИВНОЕ удаление '(Open WebUI)' из ВСЕХ файлов..."
|
||||
|
||||
# Ищем ВО ВСЕХ файлах, включая бинарные (может быть в строковых константах)
|
||||
docker exec "${CONTAINER_NAME}" find /app -type f \
|
||||
! -path "*/node_modules/*" ! -path "*/.next/*" \
|
||||
! -path "*/oauth*" ! -path "*/oidc*" ! -path "*authentik*" ! -path "*openid*" \
|
||||
! -name "*.pyc" ! -name "*.pyo" ! -name "*.so" \
|
||||
2>/dev/null | while read file; do
|
||||
# Проверяем, это текстовый файл
|
||||
if docker exec "${CONTAINER_NAME}" file "$file" 2>/dev/null | grep -q "text\|JSON\|ASCII"; then
|
||||
if docker exec "${CONTAINER_NAME}" grep -qE "(Open WebUI)|\(Open WebUI\)|iiEasyWeb.*Open.*WebUI" "$file" 2>/dev/null; then
|
||||
echo " Удаление из: $file"
|
||||
# Все возможные варианты
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/(Open WebUI)//g' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/\(Open WebUI\)//g' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/ (Open WebUI)//g' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/ \(Open WebUI\)//g' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/iiEasyWeb (Open WebUI)/iiEasyWeb/g' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/iiEasyWeb \(Open WebUI\)/iiEasyWeb/g' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/iiEasyWeb(Open WebUI)/iiEasyWeb/g' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i "s/'(Open WebUI)'//g" "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/"(Open WebUI)"//g' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/`(Open WebUI)`//g' "$file" 2>/dev/null || true
|
||||
# Удаляем если в составе строки
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/Войти в iiEasyWeb (Open WebUI)/Войти в iiEasyWeb/g' "$file" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
echo "3. Замена ссылок на документацию на note.iieasy.ru..."
|
||||
|
||||
# Заменяем ссылки на документацию
|
||||
docker exec "${CONTAINER_NAME}" find /app -type f \
|
||||
! -path "*/node_modules/*" ! -path "*/.next/*" \
|
||||
! -path "*/oauth*" ! -path "*/oidc*" ! -path "*authentik*" ! -path "*openid*" \
|
||||
2>/dev/null | while read file; do
|
||||
if docker exec "${CONTAINER_NAME}" file "$file" 2>/dev/null | grep -q "text\|JSON\|ASCII"; then
|
||||
if docker exec "${CONTAINER_NAME}" grep -qE "docs\.openwebui\.com|open-webui\.com/docs|github\.com/open-webui/docs" "$file" 2>/dev/null; then
|
||||
echo " Замена ссылок на документацию в: $file"
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's|https://docs.openwebui.com|https://note.iieasy.ru|g' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's|https://open-webui.com/docs|https://note.iieasy.ru|g' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's|https://github.com/open-webui/docs|https://note.iieasy.ru|g' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's|docs\.openwebui\.com|note.iieasy.ru|g' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's|open-webui\.com/docs|note.iieasy.ru|g' "$file" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
echo "4. Замена 'Open WebUI' на 'iiEasyWeb' в тексте..."
|
||||
|
||||
docker exec "${CONTAINER_NAME}" find /app/web -type f \
|
||||
! -path "*/node_modules/*" ! -path "*/.next/*" \
|
||||
2>/dev/null | while read file; do
|
||||
if docker exec "${CONTAINER_NAME}" file "$file" 2>/dev/null | grep -q "text\|JSON\|ASCII"; then
|
||||
if docker exec "${CONTAINER_NAME}" grep -q "Open WebUI" "$file" 2>/dev/null; then
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/Open WebUI/iiEasyWeb/g' "$file" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
echo "5. Проверка базы данных..."
|
||||
|
||||
# Пытаемся найти и исправить в базе данных
|
||||
DB_PATH="/app/backend/data/webui.db"
|
||||
if docker exec "${CONTAINER_NAME}" test -f "$DB_PATH" 2>/dev/null; then
|
||||
echo " Найдена база данных, проверяем..."
|
||||
if docker exec "${CONTAINER_NAME}" command -v sqlite3 >/dev/null 2>&1; then
|
||||
# Ищем таблицы с текстовыми полями
|
||||
TABLES=$(docker exec "${CONTAINER_NAME}" sqlite3 "$DB_PATH" "SELECT name FROM sqlite_master WHERE type='table';" 2>/dev/null)
|
||||
echo "$TABLES" | while read table; do
|
||||
if [ -n "$table" ]; then
|
||||
# Пытаемся найти и заменить в текстовых полях
|
||||
docker exec "${CONTAINER_NAME}" sqlite3 "$DB_PATH" "UPDATE $table SET value = REPLACE(value, '(Open WebUI)', '') WHERE value LIKE '%(Open WebUI)%';" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sqlite3 "$DB_PATH" "UPDATE $table SET value = REPLACE(value, 'Open WebUI', 'iiEasyWeb') WHERE value LIKE '%Open WebUI%';" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "6. Удаление социальных сетей, GitHub и лицензии из футера..."
|
||||
|
||||
# Удаляем Discord
|
||||
docker exec "${CONTAINER_NAME}" find /app -type f \
|
||||
! -path "*/node_modules/*" ! -path "*/.next/*" \
|
||||
! -path "*/oauth*" ! -path "*/oidc*" ! -path "*authentik*" ! -path "*openid*" \
|
||||
2>/dev/null | while read file; do
|
||||
if docker exec "${CONTAINER_NAME}" file "$file" 2>/dev/null | grep -q "text\|JSON\|ASCII"; then
|
||||
if docker exec "${CONTAINER_NAME}" grep -qE "discord\.gg|discord\.com|Discord" "$file" 2>/dev/null; then
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/discord\.gg/d' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/discord\.com/d' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/Discord/d' "$file" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# Удаляем Twitter/X
|
||||
docker exec "${CONTAINER_NAME}" find /app -type f \
|
||||
! -path "*/node_modules/*" ! -path "*/.next/*" \
|
||||
! -path "*/oauth*" ! -path "*/oidc*" ! -path "*authentik*" ! -path "*openid*" \
|
||||
2>/dev/null | while read file; do
|
||||
if docker exec "${CONTAINER_NAME}" file "$file" 2>/dev/null | grep -q "text\|JSON\|ASCII"; then
|
||||
if docker exec "${CONTAINER_NAME}" grep -qE "twitter\.com|x\.com|X \(formerly Twitter\)|Twitter|Follow" "$file" 2>/dev/null; then
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/twitter\.com/d' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/x\.com/d' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/X (formerly Twitter)/d' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/Twitter/d' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/Follow/d' "$file" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# Удаляем GitHub Repo
|
||||
docker exec "${CONTAINER_NAME}" find /app -type f \
|
||||
! -path "*/node_modules/*" ! -path "*/.next/*" \
|
||||
! -path "*/oauth*" ! -path "*/oidc*" ! -path "*authentik*" ! -path "*openid*" \
|
||||
2>/dev/null | while read file; do
|
||||
if docker exec "${CONTAINER_NAME}" file "$file" 2>/dev/null | grep -q "text\|JSON\|ASCII"; then
|
||||
if docker exec "${CONTAINER_NAME}" grep -qE "Github Repo|GitHub Repo" "$file" 2>/dev/null; then
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/Github Repo/d' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/GitHub Repo/d' "$file" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# Удаляем лицензию
|
||||
docker exec "${CONTAINER_NAME}" find /app -type f \
|
||||
! -path "*/node_modules/*" ! -path "*/.next/*" \
|
||||
! -path "*/oauth*" ! -path "*/oidc*" ! -path "*authentik*" ! -path "*openid*" \
|
||||
2>/dev/null | while read file; do
|
||||
if docker exec "${CONTAINER_NAME}" file "$file" 2>/dev/null | grep -q "text\|JSON\|ASCII"; then
|
||||
if docker exec "${CONTAINER_NAME}" grep -qE "лицензионный тарифный план|Перейдите на лицензионный|Лицензия" "$file" 2>/dev/null; then
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/лицензионный тарифный план/d' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/Перейдите на лицензионный/d' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/расширенные возможности/d' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/настраиваемую тематику/d' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/фирменный стиль/d' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/специальную поддержку/d' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/Лицензия/d' "$file" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "7. Перезапуск контейнера..."
|
||||
|
||||
docker restart "${CONTAINER_NAME}" >/dev/null 2>&1 || {
|
||||
echo "Предупреждение: Перезапустите вручную: docker restart ${CONTAINER_NAME}"
|
||||
}
|
||||
|
||||
echo ""
|
||||
echo "=== ФИНАЛЬНЫЙ ребрендинг завершен! ==="
|
||||
echo ""
|
||||
echo "Проверьте:"
|
||||
echo " 1. Откройте https://odo.iieasy.ru"
|
||||
echo " 2. Должно быть 'Войти в iiEasyWeb' (без '(Open WebUI)')"
|
||||
echo " 3. Ссылки на документацию должны вести на note.iieasy.ru"
|
||||
echo ""
|
||||
echo "ВАЖНО: Очистите кеш браузера (Ctrl+Shift+Delete)"
|
||||
echo ""
|
||||
echo "Если '(Open WebUI)' все еще видно, проверьте через Admin Panel:"
|
||||
echo " Settings → Appearance → Site Title"
|
||||
209
scripts/rebrand_full.sh
Executable file
209
scripts/rebrand_full.sh
Executable file
@@ -0,0 +1,209 @@
|
||||
#!/bin/bash
|
||||
# ПОЛНЫЙ ребрендинг Open WebUI для iiEasy
|
||||
# Удаляет ВСЕ упоминания Open WebUI из интерфейса, но защищает OAuth
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
MEDIA_DIR="$PROJECT_DIR/media"
|
||||
CONTAINER_NAME="open-webui"
|
||||
|
||||
echo "=== ПОЛНЫЙ ребрендинг Open WebUI для iiEasy ==="
|
||||
echo "⚠ Удаление ВСЕХ упоминаний Open WebUI из интерфейса"
|
||||
echo "⚠ OAuth/Authentik файлы защищены"
|
||||
echo ""
|
||||
|
||||
# Проверка наличия контейнера
|
||||
if ! docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
||||
echo "Ошибка: Контейнер ${CONTAINER_NAME} не найден. Запустите docker-compose up -d сначала."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Проверка, что контейнер запущен
|
||||
if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
||||
echo "Ошибка: Контейнер ${CONTAINER_NAME} не запущен. Запустите docker-compose up -d."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "1. Замена логотипов и favicon..."
|
||||
|
||||
# Определяем пути для статических файлов
|
||||
STATIC_DIRS=(
|
||||
"/app/web/build/_app/immutable"
|
||||
"/app/web/static"
|
||||
"/app/web/build"
|
||||
"/app/backend/static"
|
||||
"/app/static"
|
||||
"/app/public"
|
||||
)
|
||||
|
||||
STATIC_DIR=""
|
||||
for dir in "${STATIC_DIRS[@]}"; do
|
||||
if docker exec "${CONTAINER_NAME}" test -d "$dir" 2>/dev/null; then
|
||||
STATIC_DIR="$dir"
|
||||
echo " Найдена директория: $STATIC_DIR"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -z "$STATIC_DIR" ]; then
|
||||
FAVICON_PATH=$(docker exec "${CONTAINER_NAME}" find /app -name "favicon.png" -o -name "favicon.ico" 2>/dev/null | head -1)
|
||||
if [ -n "$FAVICON_PATH" ]; then
|
||||
STATIC_DIR=$(dirname "$FAVICON_PATH")
|
||||
else
|
||||
STATIC_DIR="/app/web/build/_app/immutable"
|
||||
docker exec "${CONTAINER_NAME}" mkdir -p "$STATIC_DIR" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
|
||||
# Копирование логотипов
|
||||
for target_dir in "$STATIC_DIR" "/app/web/build/_app/immutable" "/app/web/static" "/app/web/build"; do
|
||||
if docker exec "${CONTAINER_NAME}" test -d "$target_dir" 2>/dev/null || [ "$target_dir" = "$STATIC_DIR" ]; then
|
||||
echo " Копирование в $target_dir..."
|
||||
|
||||
if [ -f "$MEDIA_DIR/logo.png" ]; then
|
||||
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${target_dir}/logo.png" 2>/dev/null || true
|
||||
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${target_dir}/logo.svg" 2>/dev/null || true
|
||||
elif [ -f "$MEDIA_DIR/logo-light.svg" ]; then
|
||||
docker cp "$MEDIA_DIR/logo-light.svg" "${CONTAINER_NAME}:${target_dir}/logo.svg" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
if [ -f "$MEDIA_DIR/logo-dark.svg" ]; then
|
||||
docker cp "$MEDIA_DIR/logo-dark.svg" "${CONTAINER_NAME}:${target_dir}/logo-dark.svg" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
if [ -f "$MEDIA_DIR/favicon.ico" ]; then
|
||||
docker cp "$MEDIA_DIR/favicon.ico" "${CONTAINER_NAME}:${target_dir}/favicon.ico" 2>/dev/null || true
|
||||
elif [ -f "$MEDIA_DIR/favicon.png" ]; then
|
||||
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${target_dir}/favicon.png" 2>/dev/null || true
|
||||
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${target_dir}/favicon.ico" 2>/dev/null || true
|
||||
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${target_dir}/favicon-96x96.png" 2>/dev/null || true
|
||||
elif [ -f "$MEDIA_DIR/favicon.svg" ]; then
|
||||
docker cp "$MEDIA_DIR/favicon.svg" "${CONTAINER_NAME}:${target_dir}/favicon.svg" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# Замена существующих favicon и logo
|
||||
EXISTING_FAVICONS=$(docker exec "${CONTAINER_NAME}" find /app -type f \( -name "favicon.png" -o -name "favicon.ico" -o -name "favicon.svg" \) 2>/dev/null | head -10)
|
||||
EXISTING_LOGOS=$(docker exec "${CONTAINER_NAME}" find /app -type f \( -name "logo.png" -o -name "logo.svg" \) 2>/dev/null | head -10)
|
||||
|
||||
if [ -f "$MEDIA_DIR/favicon.png" ]; then
|
||||
echo "$EXISTING_FAVICONS" | while read -r favicon_file; do
|
||||
if [ -n "$favicon_file" ]; then
|
||||
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${favicon_file}" 2>/dev/null || true
|
||||
favicon_dir=$(dirname "$favicon_file")
|
||||
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${favicon_dir}/favicon.ico" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [ -f "$MEDIA_DIR/logo.png" ]; then
|
||||
echo "$EXISTING_LOGOS" | while read -r logo_file; do
|
||||
if [ -n "$logo_file" ]; then
|
||||
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${logo_file}" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Копируем в public директории
|
||||
PUBLIC_DIRS=("/app/web/public" "/app/public" "/app/backend/public")
|
||||
for pub_dir in "${PUBLIC_DIRS[@]}"; do
|
||||
if docker exec "${CONTAINER_NAME}" test -d "$pub_dir" 2>/dev/null; then
|
||||
if [ -f "$MEDIA_DIR/logo.png" ]; then
|
||||
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${pub_dir}/logo.png" 2>/dev/null || true
|
||||
fi
|
||||
if [ -f "$MEDIA_DIR/favicon.png" ]; then
|
||||
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${pub_dir}/favicon.png" 2>/dev/null || true
|
||||
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${pub_dir}/favicon.ico" 2>/dev/null || true
|
||||
fi
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
echo "2. Удаление упоминаний 'Open WebUI' из фронтенда (HTML/JS/TSX/Svelte)..."
|
||||
|
||||
# КРИТИЧНО: Изменяем ТОЛЬКО фронтенд файлы (веб-интерфейс)
|
||||
# НЕ трогаем Python бэкенд файлы вообще, кроме исключений для безопасности
|
||||
docker exec "${CONTAINER_NAME}" find /app/web -type f \( -name "*.html" -o -name "*.js" -o -name "*.jsx" -o -name "*.ts" -o -name "*.tsx" -o -name "*.svelte" -o -name "*.json" \) \
|
||||
! -path "*/node_modules/*" ! -path "*/.next/*" ! -path "*/dist/*" \
|
||||
2>/dev/null | while read file; do
|
||||
# Проверяем, содержит ли файл упоминания Open WebUI
|
||||
if docker exec "${CONTAINER_NAME}" grep -q "Open WebUI\|open-webui\|openwebui\|\(Open WebUI\)" "$file" 2>/dev/null; then
|
||||
echo " Обработка фронтенд: $file"
|
||||
# Замена "Open WebUI" на "iiEasyWeb"
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/Open WebUI/iiEasyWeb/g' "$file" 2>/dev/null || true
|
||||
# Удаление "(Open WebUI)" в скобках
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/(Open WebUI)//g' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/\(Open WebUI\)//g' "$file" 2>/dev/null || true
|
||||
# Замена "iiEasyWeb (Open WebUI)" на "iiEasyWeb"
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/iiEasyWeb (Open WebUI)/iiEasyWeb/g' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/iiEasyWeb \(Open WebUI\)/iiEasyWeb/g' "$file" 2>/dev/null || true
|
||||
# Удаление "open-webui" и "openwebui" в тексте (но не в URL - они обычно в кавычках или переменных)
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/open-webui/iieasyweb/g' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/openwebui/iieasyweb/g' "$file" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
|
||||
echo "3. Удаление упоминаний из статических JSON файлов конфигурации..."
|
||||
|
||||
# Изменяем только JSON файлы конфигурации фронтенда (не бэкенд)
|
||||
docker exec "${CONTAINER_NAME}" find /app/web -type f -name "*.json" \
|
||||
! -path "*/node_modules/*" ! -path "*/.next/*" ! -path "*/dist/*" \
|
||||
2>/dev/null | while read file; do
|
||||
if docker exec "${CONTAINER_NAME}" grep -q "Open WebUI\|open-webui\|openwebui" "$file" 2>/dev/null; then
|
||||
echo " Обработка JSON: $file"
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/Open WebUI/iiEasyWeb/g' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/open-webui/iieasyweb/g' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/openwebui/iieasyweb/g' "$file" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
|
||||
echo "4. Удаление 'Powered by' футеров из фронтенда..."
|
||||
|
||||
docker exec "${CONTAINER_NAME}" find /app/web -type f \( -name "*.html" -o -name "*.js" -o -name "*.tsx" -o -name "*.jsx" -o -name "*.svelte" \) \
|
||||
! -path "*/node_modules/*" ! -path "*/.next/*" ! -path "*/dist/*" \
|
||||
2>/dev/null | while read file; do
|
||||
if docker exec "${CONTAINER_NAME}" grep -q "Powered by\|powered by" "$file" 2>/dev/null; then
|
||||
echo " Удаление футера из: $file"
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/Powered by.*[Oo]pen.*[Ww]eb[Uu][Ii]/d' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/powered by.*[Oo]pen.*[Ww]eb[Uu][Ii]/d' "$file" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
|
||||
echo "5. Отключение проверки обновлений (только фронтенд)..."
|
||||
|
||||
docker exec "${CONTAINER_NAME}" find /app/web -type f \( -name "*.js" -o -name "*.ts" \) \
|
||||
! -path "*/node_modules/*" ! -path "*/.next/*" ! -path "*/dist/*" \
|
||||
-exec grep -l "github.com.*releases\|check.*update\|update.*check" {} \; 2>/dev/null | while read file; do
|
||||
echo " Отключение проверки обновлений в: $file"
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's|https://api.github.com/repos/open-webui|# https://api.github.com/repos/open-webui|g' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's|github.com/open-webui|# github.com/open-webui|g' "$file" 2>/dev/null || true
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "⚠ ВАЖНО: Python бэкенд файлы НЕ изменялись"
|
||||
echo "⚠ OAuth/Authentik файлы защищены"
|
||||
echo "⚠ Изменены только фронтенд файлы (веб-интерфейс)"
|
||||
echo ""
|
||||
|
||||
echo "6. Перезапуск контейнера..."
|
||||
|
||||
docker restart "${CONTAINER_NAME}" >/dev/null 2>&1 || {
|
||||
echo "Предупреждение: Не удалось перезапустить контейнер автоматически."
|
||||
echo "Перезапустите вручную: docker restart ${CONTAINER_NAME}"
|
||||
}
|
||||
|
||||
echo ""
|
||||
echo "=== ПОЛНЫЙ ребрендинг завершен! ==="
|
||||
echo ""
|
||||
echo "Проверьте:"
|
||||
echo " 1. Откройте https://odo.iieasy.ru"
|
||||
echo " 2. Проверьте отсутствие упоминаний 'Open WebUI'"
|
||||
echo " 3. Проверьте OAuth - должен работать нормально"
|
||||
echo ""
|
||||
echo "Если OAuth не работает, пересоздайте контейнер:"
|
||||
echo " sudo docker compose stop open-webui"
|
||||
echo " sudo docker compose rm -f open-webui"
|
||||
echo " sudo docker compose up -d open-webui"
|
||||
173
scripts/rebrand_precise.sh
Executable file
173
scripts/rebrand_precise.sh
Executable file
@@ -0,0 +1,173 @@
|
||||
#!/bin/bash
|
||||
# ТОЧНЫЙ ребрендинг Open WebUI для iiEasy
|
||||
# Находит и удаляет ВСЕ упоминания "(Open WebUI)" и "Open WebUI"
|
||||
# Только безопасные замены в текстовом контенте
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
MEDIA_DIR="$PROJECT_DIR/media"
|
||||
CONTAINER_NAME="open-webui"
|
||||
|
||||
echo "=== ТОЧНЫЙ ребрендинг Open WebUI для iiEasy ==="
|
||||
echo "✓ Поиск и удаление ВСЕХ упоминаний '(Open WebUI)'"
|
||||
echo "✓ Замена 'Open WebUI' на 'iiEasyWeb'"
|
||||
echo "✓ Исправление логотипов"
|
||||
echo ""
|
||||
|
||||
if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
||||
echo "Ошибка: Контейнер ${CONTAINER_NAME} не запущен."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "1. Замена логотипов и favicon..."
|
||||
|
||||
# Находим все места, где могут быть логотипы
|
||||
STATIC_DIRS=(
|
||||
"/app/web/build/_app/immutable"
|
||||
"/app/web/static"
|
||||
"/app/web/build"
|
||||
"/app/backend/static"
|
||||
)
|
||||
|
||||
for dir in "${STATIC_DIRS[@]}"; do
|
||||
if docker exec "${CONTAINER_NAME}" test -d "$dir" 2>/dev/null; then
|
||||
echo " Копирование в $dir..."
|
||||
if [ -f "$MEDIA_DIR/logo.png" ]; then
|
||||
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${dir}/logo.png" 2>/dev/null || true
|
||||
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${dir}/logo.svg" 2>/dev/null || true
|
||||
fi
|
||||
if [ -f "$MEDIA_DIR/favicon.png" ]; then
|
||||
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${dir}/favicon.png" 2>/dev/null || true
|
||||
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${dir}/favicon.ico" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# Заменяем существующие логотипы везде
|
||||
echo " Поиск и замена существующих логотипов..."
|
||||
EXISTING_LOGOS=$(docker exec "${CONTAINER_NAME}" find /app -type f \( -name "logo.png" -o -name "logo.svg" -o -name "logo.ico" \) 2>/dev/null)
|
||||
if [ -f "$MEDIA_DIR/logo.png" ]; then
|
||||
echo "$EXISTING_LOGOS" | while read -r logo_file; do
|
||||
if [ -n "$logo_file" ] && [[ ! "$logo_file" == *"node_modules"* ]]; then
|
||||
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${logo_file}" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
EXISTING_FAVICONS=$(docker exec "${CONTAINER_NAME}" find /app -type f \( -name "favicon.png" -o -name "favicon.ico" -o -name "favicon.svg" \) 2>/dev/null)
|
||||
if [ -f "$MEDIA_DIR/favicon.png" ]; then
|
||||
echo "$EXISTING_FAVICONS" | while read -r favicon_file; do
|
||||
if [ -n "$favicon_file" ] && [[ ! "$favicon_file" == *"node_modules"* ]]; then
|
||||
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${favicon_file}" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
echo "2. Поиск и удаление '(Open WebUI)' из ВСЕХ файлов..."
|
||||
|
||||
# Ищем ВСЕ файлы с упоминанием "(Open WebUI)" - включая скомпилированные
|
||||
# Ищем в разных вариантах написания
|
||||
docker exec "${CONTAINER_NAME}" find /app -type f \( -name "*.html" -o -name "*.svelte" -o -name "*.js" -o -name "*.jsx" -o -name "*.ts" -o -name "*.tsx" -o -name "*.json" -o -name "*.css" -o -name "*.mjs" \) \
|
||||
! -path "*/node_modules/*" ! -path "*/.next/*" \
|
||||
! -path "*/oauth*" ! -path "*/oidc*" ! -path "*authentik*" ! -path "*openid*" \
|
||||
2>/dev/null | while read file; do
|
||||
# Проверяем разные варианты написания - включая с пробелами и без
|
||||
if docker exec "${CONTAINER_NAME}" grep -qE "(Open WebUI)|\(Open WebUI\)|iiEasyWeb \(Open WebUI\)|iiEasyWeb\(Open WebUI\)" "$file" 2>/dev/null; then
|
||||
echo " Удаление '(Open WebUI)' из: $file"
|
||||
# Удаляем различные варианты написания в скобках
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/(Open WebUI)//g' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/\(Open WebUI\)//g' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/ (Open WebUI)//g' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/ \(Open WebUI\)//g' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/iiEasyWeb (Open WebUI)/iiEasyWeb/g' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/iiEasyWeb \(Open WebUI\)/iiEasyWeb/g' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/iiEasyWeb(Open WebUI)/iiEasyWeb/g' "$file" 2>/dev/null || true
|
||||
# Также удаляем если есть пробелы вокруг
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/\s*(Open WebUI)\s*//g' "$file" 2>/dev/null || true
|
||||
# Удаляем если текст в кавычках или переменных
|
||||
docker exec "${CONTAINER_NAME}" sed -i "s/'(Open WebUI)'//g" "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/"(Open WebUI)"//g' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/`(Open WebUI)`//g' "$file" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
|
||||
echo "3. Замена 'Open WebUI' на 'iiEasyWeb' в тексте..."
|
||||
|
||||
# Заменяем "Open WebUI" на "iiEasyWeb" в текстовом контенте
|
||||
docker exec "${CONTAINER_NAME}" find /app/web -type f \( -name "*.html" -o -name "*.svelte" -o -name "*.js" -o -name "*.jsx" -o -name "*.ts" -o -name "*.tsx" \) \
|
||||
! -path "*/node_modules/*" ! -path "*/.next/*" ! -path "*/dist/*" \
|
||||
2>/dev/null | while read file; do
|
||||
if docker exec "${CONTAINER_NAME}" grep -q "Open WebUI" "$file" 2>/dev/null; then
|
||||
echo " Замена в: $file"
|
||||
# Заменяем только в текстовом контенте, не в коде
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/Open WebUI/iiEasyWeb/g' "$file" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
|
||||
echo "4. Удаление 'Powered by Open WebUI'..."
|
||||
|
||||
docker exec "${CONTAINER_NAME}" find /app -type f \( -name "*.html" -o -name "*.svelte" -o -name "*.js" -o -name "*.jsx" -o -name "*.ts" -o -name "*.tsx" \) \
|
||||
! -path "*/node_modules/*" ! -path "*/.next/*" ! -path "*/dist/*" \
|
||||
! -path "*/oauth*" ! -path "*/oidc*" ! -path "*authentik*" ! -path "*openid*" \
|
||||
2>/dev/null | while read file; do
|
||||
if docker exec "${CONTAINER_NAME}" grep -q "Powered by.*Open WebUI\|powered by.*Open WebUI" "$file" 2>/dev/null; then
|
||||
echo " Удаление футера из: $file"
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/Powered by.*Open WebUI/d' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/powered by.*Open WebUI/d' "$file" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
|
||||
echo "5. Поиск упоминаний в скомпилированных файлах (включая минифицированные)..."
|
||||
|
||||
# Ищем в скомпилированных JS файлах (могут быть минифицированы)
|
||||
# Также ищем в build директориях
|
||||
docker exec "${CONTAINER_NAME}" find /app/web/build -type f \( -name "*.js" -o -name "*.mjs" \) \
|
||||
! -path "*/node_modules/*" \
|
||||
2>/dev/null | while read file; do
|
||||
# Ищем разные варианты, включая минифицированные (без пробелов)
|
||||
if docker exec "${CONTAINER_NAME}" grep -qE "Open WebUI|\(Open WebUI\)|iiEasyWeb \(Open WebUI\)|OpenWebUI" "$file" 2>/dev/null; then
|
||||
echo " Обработка скомпилированного: $file"
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/Open WebUI/iiEasyWeb/g' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/(Open WebUI)//g' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/\(Open WebUI\)//g' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's/iiEasyWeb (Open WebUI)/iiEasyWeb/g' "$file" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
|
||||
echo "6. Поиск в базе данных (если текст там хранится)..."
|
||||
|
||||
# Проверяем, может ли текст быть в базе данных
|
||||
# Если есть SQLite база, можем попробовать заменить там
|
||||
DB_PATH="/app/backend/data/webui.db"
|
||||
if docker exec "${CONTAINER_NAME}" test -f "$DB_PATH" 2>/dev/null; then
|
||||
echo " Найдена база данных, проверяем наличие '(Open WebUI)'..."
|
||||
# Ищем в базе через sqlite3 (если доступен)
|
||||
if docker exec "${CONTAINER_NAME}" command -v sqlite3 >/dev/null 2>&1; then
|
||||
# Ищем в текстовых полях базы
|
||||
docker exec "${CONTAINER_NAME}" sqlite3 "$DB_PATH" "SELECT name FROM sqlite_master WHERE type='table';" 2>/dev/null | while read table; do
|
||||
if [ -n "$table" ]; then
|
||||
# Пытаемся найти и заменить в текстовых полях (осторожно!)
|
||||
echo " Проверка таблицы: $table"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "6. Перезапуск контейнера..."
|
||||
|
||||
docker restart "${CONTAINER_NAME}" >/dev/null 2>&1 || {
|
||||
echo "Предупреждение: Перезапустите вручную: docker restart ${CONTAINER_NAME}"
|
||||
}
|
||||
|
||||
echo ""
|
||||
echo "=== ТОЧНЫЙ ребрендинг завершен! ==="
|
||||
echo ""
|
||||
echo "Проверьте:"
|
||||
echo " 1. Откройте https://odo.iieasy.ru"
|
||||
echo " 2. Должно быть 'Войти в iiEasyWeb' (без '(Open WebUI)')"
|
||||
echo " 3. Логотип должен отображаться правильно"
|
||||
echo ""
|
||||
echo "Если '(Open WebUI)' все еще видно, очистите кеш браузера (Ctrl+Shift+Delete)"
|
||||
152
scripts/rebrand_safe.sh
Executable file
152
scripts/rebrand_safe.sh
Executable file
@@ -0,0 +1,152 @@
|
||||
#!/bin/bash
|
||||
# БЕЗОПАСНЫЙ скрипт ребрендинга Open WebUI для iiEasy
|
||||
# Только логотипы и favicon, БЕЗ изменения кода Python/JS
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
MEDIA_DIR="$PROJECT_DIR/media"
|
||||
CONTAINER_NAME="open-webui"
|
||||
|
||||
echo "=== БЕЗОПАСНЫЙ ребрендинг Open WebUI для iiEasy ==="
|
||||
echo "⚠ ВНИМАНИЕ: Этот скрипт изменяет ТОЛЬКО логотипы и favicon"
|
||||
echo "⚠ Код Python/JS НЕ изменяется для защиты OAuth/Authentik"
|
||||
echo ""
|
||||
|
||||
# Проверка наличия контейнера
|
||||
if ! docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
||||
echo "Ошибка: Контейнер ${CONTAINER_NAME} не найден. Запустите docker-compose up -d сначала."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Проверка, что контейнер запущен
|
||||
if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
||||
echo "Ошибка: Контейнер ${CONTAINER_NAME} не запущен. Запустите docker-compose up -d."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "1. Замена логотипов и favicon..."
|
||||
|
||||
# Определяем пути для статических файлов в Open WebUI
|
||||
STATIC_DIRS=(
|
||||
"/app/web/build/_app/immutable"
|
||||
"/app/web/static"
|
||||
"/app/web/build"
|
||||
"/app/backend/static"
|
||||
"/app/static"
|
||||
"/app/public"
|
||||
)
|
||||
|
||||
# Находим существующую директорию со статическими файлами
|
||||
STATIC_DIR=""
|
||||
for dir in "${STATIC_DIRS[@]}"; do
|
||||
if docker exec "${CONTAINER_NAME}" test -d "$dir" 2>/dev/null; then
|
||||
STATIC_DIR="$dir"
|
||||
echo " Найдена директория статических файлов: $STATIC_DIR"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -z "$STATIC_DIR" ]; then
|
||||
echo " Предупреждение: Директория статических файлов не найдена, пробуем найти через поиск favicon..."
|
||||
FAVICON_PATH=$(docker exec "${CONTAINER_NAME}" find /app -name "favicon.png" -o -name "favicon.ico" 2>/dev/null | head -1)
|
||||
if [ -n "$FAVICON_PATH" ]; then
|
||||
STATIC_DIR=$(dirname "$FAVICON_PATH")
|
||||
echo " Найдена директория через поиск favicon: $STATIC_DIR"
|
||||
else
|
||||
STATIC_DIR="/app/web/build/_app/immutable"
|
||||
echo " Используем стандартный путь: $STATIC_DIR"
|
||||
docker exec "${CONTAINER_NAME}" mkdir -p "$STATIC_DIR" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
|
||||
# Копирование логотипов (приоритет: PNG > SVG)
|
||||
for target_dir in "$STATIC_DIR" "/app/web/build/_app/immutable" "/app/web/static" "/app/web/build"; do
|
||||
if docker exec "${CONTAINER_NAME}" test -d "$target_dir" 2>/dev/null || [ "$target_dir" = "$STATIC_DIR" ]; then
|
||||
echo " Копирование в $target_dir..."
|
||||
|
||||
# Логотипы
|
||||
if [ -f "$MEDIA_DIR/logo.png" ]; then
|
||||
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${target_dir}/logo.png" 2>/dev/null || true
|
||||
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${target_dir}/logo.svg" 2>/dev/null || true
|
||||
elif [ -f "$MEDIA_DIR/logo-light.svg" ]; then
|
||||
docker cp "$MEDIA_DIR/logo-light.svg" "${CONTAINER_NAME}:${target_dir}/logo.svg" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
if [ -f "$MEDIA_DIR/logo-dark.svg" ]; then
|
||||
docker cp "$MEDIA_DIR/logo-dark.svg" "${CONTAINER_NAME}:${target_dir}/logo-dark.svg" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Favicon
|
||||
if [ -f "$MEDIA_DIR/favicon.ico" ]; then
|
||||
docker cp "$MEDIA_DIR/favicon.ico" "${CONTAINER_NAME}:${target_dir}/favicon.ico" 2>/dev/null || true
|
||||
elif [ -f "$MEDIA_DIR/favicon.png" ]; then
|
||||
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${target_dir}/favicon.png" 2>/dev/null || true
|
||||
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${target_dir}/favicon.ico" 2>/dev/null || true
|
||||
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${target_dir}/favicon-96x96.png" 2>/dev/null || true
|
||||
elif [ -f "$MEDIA_DIR/favicon.svg" ]; then
|
||||
docker cp "$MEDIA_DIR/favicon.svg" "${CONTAINER_NAME}:${target_dir}/favicon.svg" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# Поиск и замена существующих favicon и logo файлов
|
||||
echo " Поиск существующих favicon и logo файлов для замены..."
|
||||
EXISTING_FAVICONS=$(docker exec "${CONTAINER_NAME}" find /app -type f \( -name "favicon.png" -o -name "favicon.ico" -o -name "favicon.svg" \) 2>/dev/null | head -10)
|
||||
EXISTING_LOGOS=$(docker exec "${CONTAINER_NAME}" find /app -type f \( -name "logo.png" -o -name "logo.svg" \) 2>/dev/null | head -10)
|
||||
|
||||
# Заменяем существующие favicon файлы
|
||||
if [ -f "$MEDIA_DIR/favicon.png" ]; then
|
||||
echo "$EXISTING_FAVICONS" | while read -r favicon_file; do
|
||||
if [ -n "$favicon_file" ]; then
|
||||
echo " Замена: $favicon_file"
|
||||
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${favicon_file}" 2>/dev/null || true
|
||||
favicon_dir=$(dirname "$favicon_file")
|
||||
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${favicon_dir}/favicon.ico" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Заменяем существующие logo файлы
|
||||
if [ -f "$MEDIA_DIR/logo.png" ]; then
|
||||
echo "$EXISTING_LOGOS" | while read -r logo_file; do
|
||||
if [ -n "$logo_file" ]; then
|
||||
echo " Замена: $logo_file"
|
||||
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${logo_file}" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Копируем в public директории
|
||||
PUBLIC_DIRS=(
|
||||
"/app/web/public"
|
||||
"/app/public"
|
||||
"/app/backend/public"
|
||||
)
|
||||
|
||||
for pub_dir in "${PUBLIC_DIRS[@]}"; do
|
||||
if docker exec "${CONTAINER_NAME}" test -d "$pub_dir" 2>/dev/null; then
|
||||
echo " Копирование в $pub_dir..."
|
||||
if [ -f "$MEDIA_DIR/logo.png" ]; then
|
||||
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${pub_dir}/logo.png" 2>/dev/null || true
|
||||
fi
|
||||
if [ -f "$MEDIA_DIR/favicon.png" ]; then
|
||||
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${pub_dir}/favicon.png" 2>/dev/null || true
|
||||
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${pub_dir}/favicon.ico" 2>/dev/null || true
|
||||
fi
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "=== БЕЗОПАСНЫЙ ребрендинг завершен! ==="
|
||||
echo ""
|
||||
echo "⚠ Изменены ТОЛЬКО логотипы и favicon"
|
||||
echo "⚠ Код Python/JS НЕ изменялся - OAuth/Authentik защищены"
|
||||
echo ""
|
||||
echo "Рекомендуется использовать Admin Panel для постоянных изменений:"
|
||||
echo " Settings → Appearance → Logo (загрузите файлы из /app/media/)"
|
||||
echo ""
|
||||
echo "Если нужно изменить текст интерфейса, используйте Admin Panel или"
|
||||
echo "настройте переменные окружения в docker-compose.yml"
|
||||
460
scripts/rebrand_safe_final.sh
Executable file
460
scripts/rebrand_safe_final.sh
Executable file
@@ -0,0 +1,460 @@
|
||||
#!/bin/bash
|
||||
# БЕЗОПАСНЫЙ финальный ребрендинг - не ломает функциональность
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
MEDIA_DIR="$PROJECT_DIR/media"
|
||||
CONTAINER_NAME="open-webui"
|
||||
|
||||
echo "=== БЕЗОПАСНЫЙ финальный ребрендинг Open WebUI для iiEasy ==="
|
||||
echo ""
|
||||
|
||||
if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
||||
echo "Ошибка: Контейнер ${CONTAINER_NAME} не запущен."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "1. Замена логотипов, splash.png и favicon..."
|
||||
|
||||
STATIC_DIRS=(
|
||||
"/app/web/build/_app/immutable"
|
||||
"/app/web/static"
|
||||
"/app/web/build"
|
||||
"/app/backend/static"
|
||||
"/app/static"
|
||||
"/app/web/public"
|
||||
"/app/public"
|
||||
)
|
||||
|
||||
for dir in "${STATIC_DIRS[@]}"; do
|
||||
if docker exec "${CONTAINER_NAME}" test -d "$dir" 2>/dev/null; then
|
||||
# Основной логотип
|
||||
if [ -f "$MEDIA_DIR/logo.png" ]; then
|
||||
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${dir}/logo.png" 2>/dev/null || true
|
||||
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${dir}/splash.png" 2>/dev/null || true
|
||||
# Также создаем splash-dark.png для темной темы
|
||||
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${dir}/splash-dark.png" 2>/dev/null || true
|
||||
fi
|
||||
# Логотип для светлой темы
|
||||
if [ -f "$MEDIA_DIR/logo-light.svg" ]; then
|
||||
docker cp "$MEDIA_DIR/logo-light.svg" "${CONTAINER_NAME}:${dir}/logo-light.svg" 2>/dev/null || true
|
||||
docker cp "$MEDIA_DIR/logo-light.svg" "${CONTAINER_NAME}:${dir}/logo.svg" 2>/dev/null || true
|
||||
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${dir}/splash-light.png" 2>/dev/null || true
|
||||
elif [ -f "$MEDIA_DIR/logo.png" ]; then
|
||||
# Если нет SVG, используем PNG для светлой темы
|
||||
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${dir}/logo-light.png" 2>/dev/null || true
|
||||
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${dir}/splash-light.png" 2>/dev/null || true
|
||||
fi
|
||||
# Логотип для темной темы
|
||||
if [ -f "$MEDIA_DIR/logo-dark.svg" ]; then
|
||||
docker cp "$MEDIA_DIR/logo-dark.svg" "${CONTAINER_NAME}:${dir}/logo-dark.svg" 2>/dev/null || true
|
||||
# Для splash-dark.png используем PNG версию (если есть logo-dark.png) или обычный logo.png
|
||||
if [ -f "$MEDIA_DIR/logo-dark.png" ]; then
|
||||
docker cp "$MEDIA_DIR/logo-dark.png" "${CONTAINER_NAME}:${dir}/splash-dark.png" 2>/dev/null || true
|
||||
elif [ -f "$MEDIA_DIR/logo.png" ]; then
|
||||
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${dir}/splash-dark.png" 2>/dev/null || true
|
||||
fi
|
||||
elif [ -f "$MEDIA_DIR/logo.png" ]; then
|
||||
# Если нет темного SVG, используем PNG для темной темы
|
||||
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${dir}/logo-dark.png" 2>/dev/null || true
|
||||
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${dir}/splash-dark.png" 2>/dev/null || true
|
||||
fi
|
||||
# Favicon
|
||||
if [ -f "$MEDIA_DIR/favicon.png" ]; then
|
||||
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${dir}/favicon.png" 2>/dev/null || true
|
||||
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${dir}/favicon.ico" 2>/dev/null || true
|
||||
# Также создаем favicon-dark.png и favicon-light.png
|
||||
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${dir}/favicon-dark.png" 2>/dev/null || true
|
||||
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${dir}/favicon-light.png" 2>/dev/null || true
|
||||
# Apple touch icon для iOS
|
||||
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${dir}/apple-touch-icon.png" 2>/dev/null || true
|
||||
elif [ -f "$MEDIA_DIR/logo.png" ]; then
|
||||
# Если нет отдельного favicon, используем logo.png
|
||||
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${dir}/favicon.png" 2>/dev/null || true
|
||||
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${dir}/favicon.ico" 2>/dev/null || true
|
||||
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${dir}/favicon-dark.png" 2>/dev/null || true
|
||||
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${dir}/favicon-light.png" 2>/dev/null || true
|
||||
# Apple touch icon для iOS
|
||||
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${dir}/apple-touch-icon.png" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# Заменяем ВСЕ существующие favicon файлы (включая favicon.ico, favicon-dark.png, favicon-light.png, apple-touch-icon.png)
|
||||
echo " Замена всех favicon файлов..."
|
||||
EXISTING_FAVICONS=$(docker exec "${CONTAINER_NAME}" find /app -type f \( -name "favicon.png" -o -name "favicon.ico" -o -name "favicon.svg" -o -name "favicon-dark.png" -o -name "favicon-light.png" -o -name "apple-touch-icon.png" \) 2>/dev/null)
|
||||
if [ -f "$MEDIA_DIR/favicon.png" ]; then
|
||||
echo "$EXISTING_FAVICONS" | while read -r favicon_file; do
|
||||
if [ -n "$favicon_file" ] && [[ ! "$favicon_file" == *"node_modules"* ]]; then
|
||||
favicon_name=$(basename "$favicon_file")
|
||||
echo " Замена favicon: $favicon_file"
|
||||
# Для темной темы используем logo-dark если есть, иначе favicon.png
|
||||
if [[ "$favicon_name" == *"dark"* ]]; then
|
||||
if [ -f "$MEDIA_DIR/logo-dark.svg" ] || [ -f "$MEDIA_DIR/logo-dark.png" ]; then
|
||||
# Используем logo.png для favicon-dark (так как favicon должен быть PNG)
|
||||
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${favicon_file}" 2>/dev/null || true
|
||||
else
|
||||
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${favicon_file}" 2>/dev/null || true
|
||||
fi
|
||||
# Для светлой темы
|
||||
elif [[ "$favicon_name" == *"light"* ]]; then
|
||||
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${favicon_file}" 2>/dev/null || true
|
||||
# Apple touch icon или обычный favicon
|
||||
else
|
||||
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${favicon_file}" 2>/dev/null || true
|
||||
# Также создаем .ico версию в той же директории для обычных favicon
|
||||
if [[ "$favicon_name" == "favicon.png" ]]; then
|
||||
favicon_dir=$(dirname "$favicon_file")
|
||||
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${favicon_dir}/favicon.ico" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
done
|
||||
elif [ -f "$MEDIA_DIR/logo.png" ]; then
|
||||
# Если нет favicon.png, используем logo.png
|
||||
echo "$EXISTING_FAVICONS" | while read -r favicon_file; do
|
||||
if [ -n "$favicon_file" ] && [[ ! "$favicon_file" == *"node_modules"* ]]; then
|
||||
favicon_name=$(basename "$favicon_file")
|
||||
echo " Замена favicon: $favicon_file"
|
||||
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${favicon_file}" 2>/dev/null || true
|
||||
# Также создаем .ico версию для обычных favicon (не для dark/light/apple-touch-icon)
|
||||
if [[ "$favicon_name" == "favicon.png" ]]; then
|
||||
favicon_dir=$(dirname "$favicon_file")
|
||||
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${favicon_dir}/favicon.ico" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Заменяем все существующие splash.png и splash-dark.png
|
||||
EXISTING_SPLASH=$(docker exec "${CONTAINER_NAME}" find /app -type f \( -name "splash.png" -o -name "splash-dark.png" -o -name "splash-light.png" \) 2>/dev/null)
|
||||
if [ -f "$MEDIA_DIR/logo.png" ]; then
|
||||
echo "$EXISTING_SPLASH" | while read -r splash_file; do
|
||||
if [ -n "$splash_file" ] && [[ ! "$splash_file" == *"node_modules"* ]]; then
|
||||
splash_name=$(basename "$splash_file")
|
||||
# Для темной темы используем logo-dark если есть, иначе обычный logo
|
||||
if [[ "$splash_name" == *"dark"* ]]; then
|
||||
if [ -f "$MEDIA_DIR/logo-dark.svg" ]; then
|
||||
# Конвертируем SVG в PNG или используем logo.png
|
||||
echo " Замена splash-dark.png: $splash_file"
|
||||
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${splash_file}" 2>/dev/null || true
|
||||
else
|
||||
echo " Замена splash-dark.png: $splash_file"
|
||||
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${splash_file}" 2>/dev/null || true
|
||||
fi
|
||||
# Для светлой темы
|
||||
elif [[ "$splash_name" == *"light"* ]]; then
|
||||
if [ -f "$MEDIA_DIR/logo-light.svg" ]; then
|
||||
echo " Замена splash-light.png: $splash_file"
|
||||
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${splash_file}" 2>/dev/null || true
|
||||
else
|
||||
echo " Замена splash-light.png: $splash_file"
|
||||
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${splash_file}" 2>/dev/null || true
|
||||
fi
|
||||
# Обычный splash
|
||||
else
|
||||
echo " Замена splash.png: $splash_file"
|
||||
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${splash_file}" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Заменяем все существующие logo файлы (включая logo-dark и logo-light)
|
||||
EXISTING_LOGOS=$(docker exec "${CONTAINER_NAME}" find /app -type f \( -name "logo.png" -o -name "logo.svg" -o -name "logo-light.*" -o -name "logo-dark.*" \) 2>/dev/null)
|
||||
if [ -f "$MEDIA_DIR/logo.png" ]; then
|
||||
echo "$EXISTING_LOGOS" | while read -r logo_file; do
|
||||
if [ -n "$logo_file" ] && [[ ! "$logo_file" == *"node_modules"* ]]; then
|
||||
logo_name=$(basename "$logo_file")
|
||||
# Для темной темы используем logo-dark если есть, иначе обычный logo
|
||||
if [[ "$logo_name" == *"dark"* ]]; then
|
||||
if [ -f "$MEDIA_DIR/logo-dark.svg" ]; then
|
||||
docker cp "$MEDIA_DIR/logo-dark.svg" "${CONTAINER_NAME}:${logo_file}" 2>/dev/null || true
|
||||
else
|
||||
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${logo_file}" 2>/dev/null || true
|
||||
fi
|
||||
# Для светлой темы используем logo-light если есть
|
||||
elif [[ "$logo_name" == *"light"* ]]; then
|
||||
if [ -f "$MEDIA_DIR/logo-light.svg" ]; then
|
||||
docker cp "$MEDIA_DIR/logo-light.svg" "${CONTAINER_NAME}:${logo_file}" 2>/dev/null || true
|
||||
else
|
||||
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${logo_file}" 2>/dev/null || true
|
||||
fi
|
||||
# Обычный логотип
|
||||
else
|
||||
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${logo_file}" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
echo "2. БЕЗОПАСНОЕ удаление '(Open WebUI)' из HTML/Svelte и Python шаблонов..."
|
||||
|
||||
# HTML, Svelte и Python файлы (шаблоны)
|
||||
FILES=$(docker exec "${CONTAINER_NAME}" find /app -type f \
|
||||
\( -name "*.html" -o -name "*.svelte" -o -name "*.py" \) \
|
||||
! -path "*/node_modules/*" ! -path "*/.next/*" \
|
||||
! -path "*/oauth*" ! -path "*/oidc*" ! -path "*authentik*" ! -path "*openid*" \
|
||||
! -name "*test*" ! -name "*__pycache__*" \
|
||||
-exec grep -lE "(Open WebUI)|\(Open WebUI\)" {} \; 2>/dev/null)
|
||||
|
||||
if [ -z "$FILES" ]; then
|
||||
echo " Файлов с '(Open WebUI)' не найдено"
|
||||
else
|
||||
COUNT=$(echo "$FILES" | wc -l)
|
||||
echo " Найдено файлов: $COUNT"
|
||||
echo "$FILES" | while read file; do
|
||||
if [ -n "$file" ]; then
|
||||
# Безопасная замена только в HTML/Svelte
|
||||
docker exec "${CONTAINER_NAME}" sed -i \
|
||||
-e 's/(Open WebUI)//g' \
|
||||
-e 's/\(Open WebUI\)//g' \
|
||||
-e 's/ (Open WebUI)//g' \
|
||||
-e 's/ \(Open WebUI\)//g' \
|
||||
-e 's/iiEasyWeb (Open WebUI)/iiEasyWeb/g' \
|
||||
-e 's/Войти в iiEasyWeb (Open WebUI)/Войти в iiEasyWeb/g' \
|
||||
"$file" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
echo "3. Замена ссылок на документацию (только в HTML/Svelte)..."
|
||||
|
||||
DOC_FILES=$(docker exec "${CONTAINER_NAME}" find /app/web -type f \
|
||||
\( -name "*.html" -o -name "*.svelte" \) \
|
||||
! -path "*/node_modules/*" ! -path "*/.next/*" \
|
||||
-exec grep -lE "docs\.openwebui\.com|open-webui\.com/docs" {} \; 2>/dev/null)
|
||||
|
||||
if [ -z "$DOC_FILES" ]; then
|
||||
echo " Файлов со ссылками не найдено"
|
||||
else
|
||||
echo "$DOC_FILES" | while read file; do
|
||||
if [ -n "$file" ]; then
|
||||
docker exec "${CONTAINER_NAME}" sed -i \
|
||||
-e 's|https://docs.openwebui.com|https://note.iieasy.ru|g' \
|
||||
-e 's|https://open-webui.com/docs|https://note.iieasy.ru|g' \
|
||||
-e 's|docs\.openwebui\.com|note.iieasy.ru|g' \
|
||||
"$file" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
echo "4. Исправление favicon.ico, favicon.png, favicon-dark.png и apple-touch-icon.png в HTML/Svelte шаблонах..."
|
||||
|
||||
# Заменяем ссылки на favicon.ico, favicon.png, favicon-dark.png и apple-touch-icon.png на наш логотип
|
||||
TEMPLATE_FILES=$(docker exec "${CONTAINER_NAME}" find /app/web -type f \
|
||||
\( -name "*.html" -o -name "*.svelte" \) \
|
||||
! -path "*/node_modules/*" ! -path "*/.next/*" \
|
||||
-exec grep -lE "/static/favicon(-dark|-light)?\.(png|ico)|favicon(-dark|-light)?\.(ico|png)|apple-touch-icon\.png|rel=\"(shortcut )?icon\"|rel=\"apple-touch-icon\"" {} \; 2>/dev/null)
|
||||
|
||||
if [ -n "$TEMPLATE_FILES" ]; then
|
||||
echo "$TEMPLATE_FILES" | while read file; do
|
||||
if [ -n "$file" ]; then
|
||||
# Заменяем favicon.ico, favicon.png, favicon-dark.png и apple-touch-icon.png на logo.png в ссылках
|
||||
docker exec "${CONTAINER_NAME}" sed -i \
|
||||
-e 's|/static/favicon-dark\.png|/static/logo.png|g' \
|
||||
-e 's|/static/favicon-light\.png|/static/logo.png|g' \
|
||||
-e 's|/static/favicon\.ico|/static/logo.png|g' \
|
||||
-e 's|/static/favicon\.png|/static/logo.png|g' \
|
||||
-e 's|/static/apple-touch-icon\.png|/static/logo.png|g' \
|
||||
-e 's|src="/static/favicon-dark\.png"|src="/static/logo.png"|g' \
|
||||
-e 's|src="/static/favicon\.png"|src="/static/logo.png"|g' \
|
||||
-e 's|src="/static/apple-touch-icon\.png"|src="/static/logo.png"|g' \
|
||||
-e 's|href="[^"]*favicon-dark\.png"|href="/static/logo.png"|g' \
|
||||
-e 's|href="[^"]*favicon\.ico"|href="/static/logo.png"|g' \
|
||||
-e 's|href="[^"]*favicon\.png"|href="/static/logo.png"|g' \
|
||||
-e 's|href="[^"]*apple-touch-icon\.png"|href="/static/logo.png"|g' \
|
||||
-e 's|href="https://odo\.iieasy\.ru/static/favicon\.ico"|href="/static/logo.png"|g' \
|
||||
"$file" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
echo "4.1. Исправление splash-dark.png, favicon-dark.png и логотипов в окне авторизации..."
|
||||
|
||||
# Ищем файлы, где используется splash-dark.png, favicon-dark.png или логотип в окне авторизации
|
||||
AUTH_FILES=$(docker exec "${CONTAINER_NAME}" find /app/web -type f \
|
||||
\( -name "*.html" -o -name "*.svelte" \) \
|
||||
! -path "*/node_modules/*" ! -path "*/.next/*" \
|
||||
-exec grep -lE "splash-dark|splash-dark\.png|favicon-dark\.png|auth.*logo|login.*logo|dark.*splash|dark.*favicon" {} \; 2>/dev/null)
|
||||
|
||||
if [ -n "$AUTH_FILES" ]; then
|
||||
echo "$AUTH_FILES" | while read file; do
|
||||
if [ -n "$file" ]; then
|
||||
echo " Исправление логотипа в окне авторизации: $file"
|
||||
# Заменяем splash-dark.png и favicon-dark.png на наш логотип
|
||||
docker exec "${CONTAINER_NAME}" sed -i \
|
||||
-e 's|/static/splash-dark\.png|/static/logo.png|g' \
|
||||
-e 's|/static/favicon-dark\.png|/static/logo.png|g' \
|
||||
-e 's|splash-dark\.png|logo.png|g' \
|
||||
-e 's|favicon-dark\.png|logo.png|g' \
|
||||
-e 's|src="/static/favicon-dark\.png"|src="/static/logo.png"|g' \
|
||||
-e 's|src="[^"]*splash-dark[^"]*"|src="/static/logo.png"|g' \
|
||||
-e 's|src="[^"]*favicon-dark[^"]*"|src="/static/logo.png"|g' \
|
||||
"$file" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
echo "5. Исправление API endpoint для изображения профиля модели..."
|
||||
|
||||
# Ищем и заменяем API endpoint для изображения профиля модели
|
||||
API_FILES=$(docker exec "${CONTAINER_NAME}" find /app -type f \
|
||||
\( -name "*.html" -o -name "*.svelte" -o -name "*.js" -o -name "*.ts" -o -name "*.py" \) \
|
||||
! -path "*/node_modules/*" ! -path "*/.next/*" \
|
||||
! -path "*/oauth*" ! -path "*/oidc*" ! -path "*authentik*" ! -path "*openid*" \
|
||||
-exec grep -l "model/profile/image\|models.*profile.*image" {} \; 2>/dev/null)
|
||||
|
||||
if [ -n "$API_FILES" ]; then
|
||||
echo "$API_FILES" | while read file; do
|
||||
if [ -n "$file" ]; then
|
||||
echo " Исправление API endpoint в: $file"
|
||||
# Заменяем API endpoint на статический логотип
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's|/api/v1/models/model/profile/image|/static/logo.png|g' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's|api/v1/models/model/profile/image|static/logo.png|g' "$file" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Также ищем в Python коде, который генерирует этот endpoint
|
||||
PYTHON_API=$(docker exec "${CONTAINER_NAME}" find /app/backend -type f -name "*.py" \
|
||||
! -path "*/oauth*" ! -path "*/oidc*" ! -path "*authentik*" ! -path "*openid*" \
|
||||
-exec grep -l "profile.*image\|model.*profile\|def.*profile" {} \; 2>/dev/null)
|
||||
|
||||
if [ -n "$PYTHON_API" ]; then
|
||||
echo "$PYTHON_API" | while read file; do
|
||||
if [ -n "$file" ]; then
|
||||
# Ищем функции, которые возвращают изображение профиля и заменяем на статический логотип
|
||||
echo " Проверка Python API в: $file"
|
||||
# Это нужно делать более аккуратно - просто заменим возвращаемый путь
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's|/static/favicon.png|/static/logo.png|g' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's|favicon.png|logo.png|g' "$file" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
echo "6. Удаление проверки обновлений и ссылок на GitHub (только Svelte файлы)..."
|
||||
|
||||
# Ищем ТОЛЬКО в исходных Svelte файлах - не трогаем скомпилированные JS
|
||||
UPDATE_FILES=$(docker exec "${CONTAINER_NAME}" find /app/web -type f -name "*.svelte" \
|
||||
! -path "*/node_modules/*" ! -path "*/.next/*" \
|
||||
-exec grep -lE "Проверить обновления|Check for updates|github.com/open-webui/releases|последняя|latest" {} \; 2>/dev/null)
|
||||
|
||||
if [ -z "$UPDATE_FILES" ]; then
|
||||
echo " Файлов с проверкой обновлений не найдено"
|
||||
else
|
||||
COUNT=$(echo "$UPDATE_FILES" | wc -l)
|
||||
echo " Найдено Svelte файлов: $COUNT"
|
||||
echo "$UPDATE_FILES" | while read file; do
|
||||
if [ -n "$file" ]; then
|
||||
echo " Удаление проверки обновлений из: $file"
|
||||
# Удаляем кнопку "Проверить обновления"
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/Проверить обновления/d' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/Check for updates/d' "$file" 2>/dev/null || true
|
||||
# Удаляем ссылку на GitHub releases с текстом "(последняя)"
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's|<a[^>]*href="https://github.com/open-webui/open-webui/releases/tag/[^"]*"[^>]*>(последняя)</a>||g' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's|(последняя)||g' "$file" 2>/dev/null || true
|
||||
# Удаляем "Посмотреть, что нового"
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/Посмотреть, что нового/d' "$file" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
echo "7. Удаление социальных сетей, GitHub и блока 'Помощь' (только Svelte файлы)..."
|
||||
|
||||
# Ищем ТОЛЬКО в исходных Svelte файлах
|
||||
SOCIAL_FILES=$(docker exec "${CONTAINER_NAME}" find /app/web -type f -name "*.svelte" \
|
||||
! -path "*/node_modules/*" ! -path "*/.next/*" \
|
||||
-exec grep -lE "discord|twitter|x\.com|Github Repo|github.com/open-webui|Помощь|Help|обратитесь за поддержкой" {} \; 2>/dev/null)
|
||||
|
||||
if [ -z "$SOCIAL_FILES" ]; then
|
||||
echo " Файлов с соцсетями не найдено"
|
||||
else
|
||||
COUNT=$(echo "$SOCIAL_FILES" | wc -l)
|
||||
echo " Найдено Svelte файлов: $COUNT"
|
||||
echo "$SOCIAL_FILES" | while read file; do
|
||||
if [ -n "$file" ]; then
|
||||
echo " Удаление соцсетей из: $file"
|
||||
# Удаляем ссылки на Discord
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's|<a[^>]*href="https://discord.gg/[^"]*"[^>]*>.*</a>||g' "$file" 2>/dev/null || true
|
||||
# Удаляем ссылки на Twitter/X
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's|<a[^>]*href="https://twitter.com/[^"]*"[^>]*>.*</a>||g' "$file" 2>/dev/null || true
|
||||
# Удаляем ссылки на GitHub repo
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's|<a[^>]*href="https://github.com/open-webui/open-webui"[^>]*>.*</a>||g' "$file" 2>/dev/null || true
|
||||
# Удаляем badges (img.shields.io)
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's|<img[^>]*shields.io[^>]*>||g' "$file" 2>/dev/null || true
|
||||
# Удаляем текст блока "Помощь"
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/Узнайте, как использовать/d' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/обратитесь за поддержкой/d' "$file" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
echo "8. Удаление блока 'Лицензия' полностью (только Svelte файлы)..."
|
||||
|
||||
# Ищем ТОЛЬКО в исходных Svelte файлах
|
||||
LICENSE_FILES=$(docker exec "${CONTAINER_NAME}" find /app/web -type f -name "*.svelte" \
|
||||
! -path "*/node_modules/*" ! -path "*/.next/*" \
|
||||
-exec grep -lE "Лицензия|License|лицензионный тарифный план|Перейдите на лицензионный" {} \; 2>/dev/null)
|
||||
|
||||
if [ -z "$LICENSE_FILES" ]; then
|
||||
echo " Файлов с лицензией не найдено"
|
||||
else
|
||||
COUNT=$(echo "$LICENSE_FILES" | wc -l)
|
||||
echo " Найдено Svelte файлов: $COUNT"
|
||||
echo "$LICENSE_FILES" | while read file; do
|
||||
if [ -n "$file" ]; then
|
||||
echo " Удаление блока лицензии из: $file"
|
||||
# Удаляем только текст блока лицензии
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/Перейдите на лицензионный/d' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/расширенные возможности/d' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/настраиваемую тематику/d' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/фирменный стиль/d' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/специальную поддержку/d' "$file" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
echo "9. Добавление надписи 'Основано на Open WebUI' внизу настроек..."
|
||||
|
||||
# Ищем файлы настроек для добавления надписи
|
||||
# Это сложнее сделать автоматически, поэтому просто отмечаем файлы
|
||||
SETTINGS_FILES=$(docker exec "${CONTAINER_NAME}" find /app/web -type f -name "*.svelte" \
|
||||
! -path "*/node_modules/*" ! -path "*/.next/*" \
|
||||
-exec grep -l "Settings\|Настройки\|General\|Общее" {} \; 2>/dev/null | head -3)
|
||||
|
||||
if [ -n "$SETTINGS_FILES" ]; then
|
||||
echo " Найдены файлы настроек (надпись нужно добавить вручную):"
|
||||
echo "$SETTINGS_FILES" | while read file; do
|
||||
if [ -n "$file" ]; then
|
||||
echo " - $file"
|
||||
fi
|
||||
done
|
||||
echo ""
|
||||
echo " Добавьте в конец блока настроек перед закрывающим тегом:"
|
||||
echo " <div class='text-xs text-gray-400 dark:text-gray-500 mt-4 text-center'>"
|
||||
echo " Основано на <span class='text-gray-500'>Open WebUI</span>"
|
||||
echo " </div>"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "10. Перезапуск контейнера..."
|
||||
|
||||
docker restart "${CONTAINER_NAME}" >/dev/null 2>&1 || {
|
||||
echo "Предупреждение: Перезапустите вручную: docker restart ${CONTAINER_NAME}"
|
||||
}
|
||||
|
||||
echo ""
|
||||
echo "=== БЕЗОПАСНЫЙ ребрендинг завершен! ==="
|
||||
echo ""
|
||||
echo "Проверьте:"
|
||||
echo " 1. Откройте https://odo.iieasy.ru"
|
||||
echo " 2. splash.png должен быть заменен на ваш логотип"
|
||||
echo " 3. Проверка обновлений и ссылки на GitHub должны быть удалены"
|
||||
echo " 4. Социальные сети и блок лицензии должны быть удалены"
|
||||
echo " 5. Очистите кеш браузера (Ctrl+Shift+Delete)"
|
||||
echo ""
|
||||
echo "Примечание: Надпись 'Основано на Open WebUI' нужно добавить вручную"
|
||||
echo "в файлах настроек (см. вывод выше) или через Admin Panel"
|
||||
185
scripts/rebrand_ultra_safe.sh
Executable file
185
scripts/rebrand_ultra_safe.sh
Executable file
@@ -0,0 +1,185 @@
|
||||
#!/bin/bash
|
||||
# УЛЬТРА БЕЗОПАСНЫЙ ребрендинг - удаляет только конкретные блоки, не ломая структуру
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
MEDIA_DIR="$PROJECT_DIR/media"
|
||||
CONTAINER_NAME="open-webui"
|
||||
|
||||
echo "=== УЛЬТРА БЕЗОПАСНЫЙ ребрендинг Open WebUI для iiEasy ==="
|
||||
echo "⚠ Удаляет только конкретные HTML блоки, не трогает код"
|
||||
echo ""
|
||||
|
||||
if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
||||
echo "Ошибка: Контейнер ${CONTAINER_NAME} не запущен."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "1. Замена логотипов..."
|
||||
|
||||
STATIC_DIRS=(
|
||||
"/app/web/build/_app/immutable"
|
||||
"/app/web/static"
|
||||
"/app/web/build"
|
||||
)
|
||||
|
||||
for dir in "${STATIC_DIRS[@]}"; do
|
||||
if docker exec "${CONTAINER_NAME}" test -d "$dir" 2>/dev/null; then
|
||||
if [ -f "$MEDIA_DIR/logo.png" ]; then
|
||||
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${dir}/logo.png" 2>/dev/null || true
|
||||
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${dir}/splash.png" 2>/dev/null || true
|
||||
fi
|
||||
if [ -f "$MEDIA_DIR/favicon.png" ]; then
|
||||
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${dir}/favicon.png" 2>/dev/null || true
|
||||
docker cp "$MEDIA_DIR/favicon.png" "${CONTAINER_NAME}:${dir}/favicon.ico" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# Заменяем существующие файлы
|
||||
EXISTING_SPLASH=$(docker exec "${CONTAINER_NAME}" find /app -type f -name "splash.png" 2>/dev/null)
|
||||
if [ -f "$MEDIA_DIR/logo.png" ]; then
|
||||
echo "$EXISTING_SPLASH" | while read -r splash_file; do
|
||||
if [ -n "$splash_file" ] && [[ ! "$splash_file" == *"node_modules"* ]]; then
|
||||
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${splash_file}" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
EXISTING_FAVICONS=$(docker exec "${CONTAINER_NAME}" find /app -type f -name "favicon.png" 2>/dev/null)
|
||||
if [ -f "$MEDIA_DIR/logo.png" ]; then
|
||||
echo "$EXISTING_FAVICONS" | while read -r favicon_file; do
|
||||
if [ -n "$favicon_file" ] && [[ ! "$favicon_file" == *"node_modules"* ]]; then
|
||||
docker cp "$MEDIA_DIR/logo.png" "${CONTAINER_NAME}:${favicon_file}" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
echo "2. Удаление '(Open WebUI)' из HTML/Svelte..."
|
||||
|
||||
FILES=$(docker exec "${CONTAINER_NAME}" find /app/web -type f \
|
||||
\( -name "*.html" -o -name "*.svelte" \) \
|
||||
! -path "*/node_modules/*" ! -path "*/.next/*" \
|
||||
-exec grep -lE "(Open WebUI)|\(Open WebUI\)" {} \; 2>/dev/null)
|
||||
|
||||
if [ -n "$FILES" ]; then
|
||||
echo "$FILES" | while read file; do
|
||||
if [ -n "$file" ]; then
|
||||
docker exec "${CONTAINER_NAME}" sed -i \
|
||||
-e 's/(Open WebUI)//g' \
|
||||
-e 's/\(Open WebUI\)//g' \
|
||||
-e 's/ (Open WebUI)//g' \
|
||||
-e 's/Войти в iiEasyWeb (Open WebUI)/Войти в iiEasyWeb/g' \
|
||||
"$file" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
echo "3. Замена ссылок на документацию..."
|
||||
|
||||
DOC_FILES=$(docker exec "${CONTAINER_NAME}" find /app/web -type f \
|
||||
\( -name "*.html" -o -name "*.svelte" \) \
|
||||
! -path "*/node_modules/*" ! -path "*/.next/*" \
|
||||
-exec grep -lE "docs\.openwebui\.com" {} \; 2>/dev/null)
|
||||
|
||||
if [ -n "$DOC_FILES" ]; then
|
||||
echo "$DOC_FILES" | while read file; do
|
||||
if [ -n "$file" ]; then
|
||||
docker exec "${CONTAINER_NAME}" sed -i \
|
||||
-e 's|https://docs.openwebui.com|https://note.iieasy.ru|g' \
|
||||
-e 's|docs\.openwebui\.com|note.iieasy.ru|g' \
|
||||
"$file" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
echo "4. Исправление favicon.png на logo.png..."
|
||||
|
||||
TEMPLATE_FILES=$(docker exec "${CONTAINER_NAME}" find /app/web -type f \
|
||||
\( -name "*.html" -o -name "*.svelte" \) \
|
||||
! -path "*/node_modules/*" ! -path "*/.next/*" \
|
||||
-exec grep -l "/static/favicon.png" {} \; 2>/dev/null)
|
||||
|
||||
if [ -n "$TEMPLATE_FILES" ]; then
|
||||
echo "$TEMPLATE_FILES" | while read file; do
|
||||
if [ -n "$file" ]; then
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's|/static/favicon.png|/static/logo.png|g' "$file" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
echo "5. Исправление API endpoint для изображения профиля модели..."
|
||||
|
||||
API_FILES=$(docker exec "${CONTAINER_NAME}" find /app/web -type f \
|
||||
\( -name "*.html" -o -name "*.svelte" \) \
|
||||
! -path "*/node_modules/*" ! -path "*/.next/*" \
|
||||
-exec grep -l "model/profile/image" {} \; 2>/dev/null)
|
||||
|
||||
if [ -n "$API_FILES" ]; then
|
||||
echo "$API_FILES" | while read file; do
|
||||
if [ -n "$file" ]; then
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's|/api/v1/models/model/profile/image|/static/logo.png|g' "$file" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
echo "6. Удаление конкретных HTML блоков (безопасно)..."
|
||||
|
||||
# Ищем только Svelte файлы настроек
|
||||
SETTINGS_FILES=$(docker exec "${CONTAINER_NAME}" find /app/web -type f -name "*.svelte" \
|
||||
! -path "*/node_modules/*" ! -path "*/.next/*" \
|
||||
-exec grep -l "Проверить обновления\|Помощь\|Лицензия\|Version\|Help\|License" {} \; 2>/dev/null)
|
||||
|
||||
if [ -n "$SETTINGS_FILES" ]; then
|
||||
echo "$SETTINGS_FILES" | while read file; do
|
||||
if [ -n "$file" ]; then
|
||||
echo " Обработка файла настроек: $file"
|
||||
|
||||
# Удаляем блок проверки обновлений - ищем по уникальным классам и тексту
|
||||
# Удаляем кнопку "Проверить обновления"
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/Проверить обновления/d' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/Check for updates/d' "$file" 2>/dev/null || true
|
||||
|
||||
# Удаляем ссылку "(последняя)" с GitHub
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's|<a[^>]*github.com/open-webui/releases[^>]*>.*последняя.*</a>||g' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's|(последняя)||g' "$file" 2>/dev/null || true
|
||||
|
||||
# Удаляем "Посмотреть, что нового"
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/Посмотреть, что нового/d' "$file" 2>/dev/null || true
|
||||
|
||||
# Удаляем весь блок "Помощь" - ищем по заголовку и удаляем до следующего блока
|
||||
# Более безопасно - удаляем только содержимое блока
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/Узнайте, как использовать/d' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/обратитесь за поддержкой/d' "$file" 2>/dev/null || true
|
||||
|
||||
# Удаляем badges соцсетей - ищем по shields.io
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's|<a[^>]*discord.gg[^>]*>.*</a>||g' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's|<a[^>]*twitter.com[^>]*>.*</a>||g' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's|<a[^>]*github.com/open-webui[^>]*>.*</a>||g' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's|<img[^>]*shields.io[^>]*>||g' "$file" 2>/dev/null || true
|
||||
|
||||
# Удаляем блок "Лицензия"
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/Перейдите на лицензионный/d' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/расширенные возможности/d' "$file" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "7. Перезапуск контейнера..."
|
||||
|
||||
docker restart "${CONTAINER_NAME}" >/dev/null 2>&1 || {
|
||||
echo "Предупреждение: Перезапустите вручную: docker restart ${CONTAINER_NAME}"
|
||||
}
|
||||
|
||||
echo ""
|
||||
echo "=== УЛЬТРА БЕЗОПАСНЫЙ ребрендинг завершен! ==="
|
||||
echo ""
|
||||
echo "Проверьте:"
|
||||
echo " 1. Откройте https://odo.iieasy.ru"
|
||||
echo " 2. Очистите кеш браузера (Ctrl+Shift+Delete)"
|
||||
echo ""
|
||||
echo "Примечание: Если блоки все еще видны, они могут быть в скомпилированных JS файлах."
|
||||
echo "В этом случае нужно найти исходные Svelte файлы и удалить блоки там."
|
||||
133
scripts/remove_footer_links.sh
Executable file
133
scripts/remove_footer_links.sh
Executable file
@@ -0,0 +1,133 @@
|
||||
#!/bin/bash
|
||||
# Удаление социальных сетей, GitHub и лицензии из футера
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
CONTAINER_NAME="open-webui"
|
||||
|
||||
echo "=== Удаление социальных сетей, GitHub и лицензии из футера ==="
|
||||
echo ""
|
||||
|
||||
if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
||||
echo "Ошибка: Контейнер ${CONTAINER_NAME} не запущен."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "1. Удаление ссылок на Discord..."
|
||||
|
||||
docker exec "${CONTAINER_NAME}" find /app -type f \
|
||||
! -path "*/node_modules/*" ! -path "*/.next/*" \
|
||||
! -path "*/oauth*" ! -path "*/oidc*" ! -path "*authentik*" ! -path "*openid*" \
|
||||
2>/dev/null | while read file; do
|
||||
if docker exec "${CONTAINER_NAME}" file "$file" 2>/dev/null | grep -q "text\|JSON\|ASCII"; then
|
||||
if docker exec "${CONTAINER_NAME}" grep -qE "discord\.gg|discord\.com|Discord" "$file" 2>/dev/null; then
|
||||
echo " Удаление Discord из: $file"
|
||||
# Удаляем строки с Discord
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/discord\.gg/d' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/discord\.com/d' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/Discord/d' "$file" 2>/dev/null || true
|
||||
# Удаляем ссылки
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's|https://discord.gg/[^"]*||g' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's|https://discord.com/[^"]*||g' "$file" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
echo "2. Удаление ссылок на Twitter/X..."
|
||||
|
||||
docker exec "${CONTAINER_NAME}" find /app -type f \
|
||||
! -path "*/node_modules/*" ! -path "*/.next/*" \
|
||||
! -path "*/oauth*" ! -path "*/oidc*" ! -path "*authentik*" ! -path "*openid*" \
|
||||
2>/dev/null | while read file; do
|
||||
if docker exec "${CONTAINER_NAME}" file "$file" 2>/dev/null | grep -q "text\|JSON\|ASCII"; then
|
||||
if docker exec "${CONTAINER_NAME}" grep -qE "twitter\.com|x\.com|X \(formerly Twitter\)|Twitter" "$file" 2>/dev/null; then
|
||||
echo " Удаление Twitter/X из: $file"
|
||||
# Удаляем строки с Twitter/X
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/twitter\.com/d' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/x\.com/d' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/X (formerly Twitter)/d' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/Twitter/d' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/Follow/d' "$file" 2>/dev/null || true
|
||||
# Удаляем ссылки
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's|https://twitter.com/[^"]*||g' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's|https://x.com/[^"]*||g' "$file" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
echo "3. Удаление ссылок на GitHub Repo..."
|
||||
|
||||
docker exec "${CONTAINER_NAME}" find /app -type f \
|
||||
! -path "*/node_modules/*" ! -path "*/.next/*" \
|
||||
! -path "*/oauth*" ! -path "*/oidc*" ! -path "*authentik*" ! -path "*openid*" \
|
||||
2>/dev/null | while read file; do
|
||||
if docker exec "${CONTAINER_NAME}" file "$file" 2>/dev/null | grep -q "text\|JSON\|ASCII"; then
|
||||
if docker exec "${CONTAINER_NAME}" grep -qE "github\.com/open-webui|Github Repo|GitHub" "$file" 2>/dev/null; then
|
||||
echo " Удаление GitHub из: $file"
|
||||
# Удаляем строки с GitHub
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/Github Repo/d' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/GitHub Repo/d' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/github\.com\/open-webui/d' "$file" 2>/dev/null || true
|
||||
# Удаляем ссылки на GitHub repo (но не все GitHub ссылки, только repo)
|
||||
docker exec "${CONTAINER_NAME}" sed -i 's|https://github.com/open-webui/[^"]*||g' "$file" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
echo "4. Удаление строки про лицензию и тарифный план..."
|
||||
|
||||
docker exec "${CONTAINER_NAME}" find /app -type f \
|
||||
! -path "*/node_modules/*" ! -path "*/.next/*" \
|
||||
! -path "*/oauth*" ! -path "*/oidc*" ! -path "*authentik*" ! -path "*openid*" \
|
||||
2>/dev/null | while read file; do
|
||||
if docker exec "${CONTAINER_NAME}" file "$file" 2>/dev/null | grep -q "text\|JSON\|ASCII"; then
|
||||
if docker exec "${CONTAINER_NAME}" grep -qE "лицензионный тарифный план|Перейдите на лицензионный|License|license|upgrade|Upgrade|тарифный план" "$file" 2>/dev/null; then
|
||||
echo " Удаление лицензии из: $file"
|
||||
# Удаляем строки про лицензию
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/лицензионный тарифный план/d' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/Перейдите на лицензионный/d' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/расширенные возможности/d' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/настраиваемую тематику/d' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/фирменный стиль/d' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/специальную поддержку/d' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/License/d' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/Лицензия/d' "$file" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
echo "5. Удаление всего блока 'Помощь' (Help section)..."
|
||||
|
||||
docker exec "${CONTAINER_NAME}" find /app/web -type f \( -name "*.svelte" -o -name "*.html" -o -name "*.js" -o -name "*.ts" \) \
|
||||
! -path "*/node_modules/*" ! -path "*/.next/*" \
|
||||
2>/dev/null | while read file; do
|
||||
if docker exec "${CONTAINER_NAME}" file "$file" 2>/dev/null | grep -q "text\|JSON\|ASCII"; then
|
||||
if docker exec "${CONTAINER_NAME}" grep -qE "Помощь|Узнайте, как использовать|обратитесь за поддержкой|Документация" "$file" 2>/dev/null; then
|
||||
echo " Удаление блока помощи из: $file"
|
||||
# Удаляем строки из блока помощи
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/Помощь/d' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/Узнайте, как использовать/d' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/обратитесь за поддержкой/d' "$file" 2>/dev/null || true
|
||||
docker exec "${CONTAINER_NAME}" sed -i '/к сообществу/d' "$file" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "6. Перезапуск контейнера..."
|
||||
|
||||
docker restart "${CONTAINER_NAME}" >/dev/null 2>&1 || {
|
||||
echo "Предупреждение: Перезапустите вручную: docker restart ${CONTAINER_NAME}"
|
||||
}
|
||||
|
||||
echo ""
|
||||
echo "=== Удаление завершено! ==="
|
||||
echo ""
|
||||
echo "Проверьте:"
|
||||
echo " 1. Откройте https://odo.iieasy.ru"
|
||||
echo " 2. В футере не должно быть ссылок на Discord, Twitter/X, GitHub"
|
||||
echo " 3. Не должно быть строки про лицензию"
|
||||
echo ""
|
||||
echo "ВАЖНО: Очистите кеш браузера (Ctrl+Shift+Delete)"
|
||||
68
scripts/test_direct_vision.sh
Executable file
68
scripts/test_direct_vision.sh
Executable file
@@ -0,0 +1,68 @@
|
||||
#!/bin/bash
|
||||
# Прямой тест vision через Ollama API (минуя Open WebUI)
|
||||
|
||||
set -e
|
||||
|
||||
CONTAINER_OLLAMA="ollama"
|
||||
MODEL="gemma3n:e4b-it-fp16"
|
||||
IMAGE_FILE="/home/its/iiEasyWeb/test_images/test_image.jpg"
|
||||
|
||||
echo "=== Прямой тест Vision через Ollama API ==="
|
||||
echo ""
|
||||
|
||||
# Проверка файла изображения
|
||||
if [ ! -f "$IMAGE_FILE" ]; then
|
||||
echo "✗ Файл изображения не найден: $IMAGE_FILE"
|
||||
echo "Скачайте изображение сначала"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "1. Кодирование изображения в base64..."
|
||||
IMAGE_B64=$(base64 -w 0 "$IMAGE_FILE" 2>/dev/null || base64 "$IMAGE_FILE" | tr -d '\n')
|
||||
echo " ✓ Изображение закодировано (${#IMAGE_B64} символов)"
|
||||
|
||||
echo ""
|
||||
echo "2. Отправка запроса к Ollama API..."
|
||||
echo " Модель: $MODEL"
|
||||
echo " Промпт: Опиши это изображение на русском языке. Что ты видишь?"
|
||||
echo ""
|
||||
|
||||
# Определяем команду docker
|
||||
DOCKER_CMD="docker"
|
||||
if ! docker ps >/dev/null 2>&1; then
|
||||
DOCKER_CMD="sudo docker"
|
||||
fi
|
||||
|
||||
# Отправляем запрос
|
||||
RESPONSE=$($DOCKER_CMD exec $CONTAINER_OLLAMA curl -s --max-time 120 -X POST http://localhost:11434/api/generate \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d "{
|
||||
\"model\": \"$MODEL\",
|
||||
\"prompt\": \"Опиши это изображение на русском языке. Что ты видишь на картинке?\",
|
||||
\"images\": [\"$IMAGE_B64\"],
|
||||
\"stream\": false
|
||||
}" 2>&1)
|
||||
|
||||
echo ""
|
||||
if echo "$RESPONSE" | grep -q '"response"'; then
|
||||
echo "✓ Успешно! Ответ модели:"
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "$RESPONSE" | python3 -c "import sys, json; data=json.load(sys.stdin); print(data.get('response', ''))" 2>/dev/null || \
|
||||
echo "$RESPONSE" | grep -o '"response":"[^"]*"' | sed 's/"response":"\(.*\)"/\1/' | sed 's/\\n/\n/g' | sed 's/\\"/"/g'
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
echo "=== ✓ Vision работает напрямую через Ollama API! ==="
|
||||
echo ""
|
||||
echo "Если это работает, значит проблема в Open WebUI, а не в Ollama."
|
||||
echo "Возможные причины:"
|
||||
echo " 1. Open WebUI v0.8.3 не поддерживает vision для gemma3n"
|
||||
echo " 2. Модель не помечена как vision-модель в настройках"
|
||||
echo " 3. Нужно обновить Open WebUI до более новой версии"
|
||||
elif echo "$RESPONSE" | grep -q "error\|Error"; then
|
||||
echo "✗ Ошибка:"
|
||||
echo "$RESPONSE" | grep -i error || echo "$RESPONSE"
|
||||
else
|
||||
echo "⚠ Неожиданный ответ:"
|
||||
echo "$RESPONSE" | head -20
|
||||
fi
|
||||
142
scripts/test_vision.sh
Executable file
142
scripts/test_vision.sh
Executable file
@@ -0,0 +1,142 @@
|
||||
#!/bin/bash
|
||||
# Скрипт для тестирования vision capabilities модели gemma3n:e4b-it-fp16
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
TEST_DIR="$PROJECT_DIR/test_images"
|
||||
IMAGE_URL="https://yandex-images.clstorage.net/PU5vN2154/532297ZKm/lCuSfyMn0DdbJqcFVeFiB9Ti31Te2dZ1EepiRw3Cs0Qw8cQ1ND5OQRKC1yH4LnhdRtloQ4aXHng5ZSLNmXHy_8k293YSMsBWKnOvYAXBbhPcl6pYmqi-ZGWDazZo2pYkJNHpkJHrg5yiO0bEOIeEICe5cFqrojYgyNQ6mHj4e5IUb_Lri3uxo9fmXv0dMf7f1NvH9J5YVsyhRvvmtD9eTc1QfVxV42d8OotKrLTDfHx7jfDqjpIqHAyt9ngIIHsSjLtOJ7_jTYdHLn4hJWlCj_jj69gGlApIBYaTfoxPrtHFDGlYUd-PjHI3hijAm6hIb-lUFlpOehQAJTbwEGwjcqjjVns323WrR2q6CalVyg9kZ8s0jkBm8OVGr26UH86NHaD1rKnPH6zinwJIKBv8VEdF_FrGLt6QFOVqFOQY776w47YDNy8Biwfefg0lxVaL2E9L5NbAztwh8vuCcMNSEfG8aYQFBxe0Eu9CMPhz1Axz1dgqmkpSYCAR8nSA_GMSKKPa9-tzIUc7ooKthV1ykwSjrzhORKq0eRrvBlTbajF5yAUoVX-7XM7HouyU51xMaxFMBl7atixQFW4o1BznMoi_chuL8_mTO_IKce21CosEQ6Owkkhi9EGSz5pIE56NjURBxKn7pwTKt6K4FB_YYHexZG4qlsIg8GVaiJyIB0p0bx5PF4_FIy-SItWZId5f8M9_RIqg2hRVCqP2hDvSfWGAYUjJd6c8lr9mgGj7kPQbVQBCUgIm1NB9lsxQrIO-wO92fytbOQsjjiqBqa0CK9Qjy6wi6CoIfX5vZjw_pj1BKGm0UVOXuArPFngIN2AUgz3w4mZSTvz8IQYo_IhPVlQ7PiOjhyWD6wZSVZ3NDrvsC-8w4lxmzE32T1qMuy5JOTRxaNHHH0QCp4IkgMv8LH8JeJaOUj4sVOGCBBgQi5KMX74TQzOBW9cGTu35UZr3qAfj5O6kMogxjqNanMOs"
|
||||
IMAGE_FILE="$TEST_DIR/test_image.jpg"
|
||||
MODEL="gemma3n:e4b-it-fp16"
|
||||
|
||||
echo "=== Тестирование Vision Capabilities модели $MODEL ==="
|
||||
echo ""
|
||||
|
||||
# Определяем команду docker (с sudo или без)
|
||||
DOCKER_CMD="docker"
|
||||
if ! docker ps >/dev/null 2>&1; then
|
||||
DOCKER_CMD="sudo docker"
|
||||
fi
|
||||
|
||||
# Проверка контейнера Ollama
|
||||
echo "1. Проверка контейнера Ollama..."
|
||||
if ! $DOCKER_CMD ps --format '{{.Names}}' 2>/dev/null | grep -q "^ollama$"; then
|
||||
echo " ✗ Ошибка: Контейнер ollama не запущен"
|
||||
echo " Запустите: docker compose up -d ollama"
|
||||
exit 1
|
||||
fi
|
||||
echo " ✓ Контейнер ollama запущен"
|
||||
|
||||
# Проверка модели
|
||||
echo ""
|
||||
echo "2. Проверка загруженных моделей..."
|
||||
MODELS=$($DOCKER_CMD exec ollama ollama list 2>/dev/null)
|
||||
if echo "$MODELS" | grep -q "$MODEL"; then
|
||||
echo " ✓ Модель $MODEL найдена"
|
||||
echo "$MODELS" | grep "$MODEL"
|
||||
else
|
||||
echo " ✗ Модель $MODEL не найдена"
|
||||
echo " Загрузите модель: docker exec ollama ollama pull $MODEL"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Создаем директорию для тестовых изображений
|
||||
mkdir -p "$TEST_DIR"
|
||||
cd "$TEST_DIR"
|
||||
|
||||
# Скачиваем изображение
|
||||
echo ""
|
||||
echo "3. Подготовка тестового изображения..."
|
||||
if [ ! -f "$IMAGE_FILE" ]; then
|
||||
echo " Скачиваю изображение..."
|
||||
curl -L -o "$IMAGE_FILE" "$IMAGE_URL" || {
|
||||
echo " ✗ Ошибка: не удалось скачать изображение"
|
||||
exit 1
|
||||
}
|
||||
echo " ✓ Изображение скачано: $IMAGE_FILE"
|
||||
else
|
||||
echo " ✓ Изображение уже существует: $IMAGE_FILE"
|
||||
fi
|
||||
|
||||
# Проверяем размер файла
|
||||
if [ -f "$IMAGE_FILE" ]; then
|
||||
FILE_SIZE=$(stat -c%s "$IMAGE_FILE" 2>/dev/null || stat -f%z "$IMAGE_FILE" 2>/dev/null)
|
||||
echo " Размер файла: $(numfmt --to=iec-i --suffix=B $FILE_SIZE 2>/dev/null || echo "${FILE_SIZE} bytes")"
|
||||
|
||||
# Проверяем тип файла
|
||||
FILE_TYPE=$(file "$IMAGE_FILE" 2>/dev/null | cut -d: -f2)
|
||||
echo " Тип файла: $FILE_TYPE"
|
||||
fi
|
||||
|
||||
# Кодируем изображение в base64
|
||||
echo ""
|
||||
echo "4. Кодирование изображения в base64..."
|
||||
IMAGE_B64=$(base64 -w 0 "$IMAGE_FILE" 2>/dev/null || base64 "$IMAGE_FILE" | tr -d '\n')
|
||||
B64_LENGTH=${#IMAGE_B64}
|
||||
echo " ✓ Изображение закодировано (длина: $B64_LENGTH символов)"
|
||||
|
||||
# Отправляем запрос к Ollama API
|
||||
echo ""
|
||||
echo "5. Отправка запроса к Ollama API..."
|
||||
echo " Модель: $MODEL"
|
||||
echo " Промпт: Опиши это изображение на русском языке. Что ты видишь на картинке?"
|
||||
echo ""
|
||||
|
||||
# Отправляем запрос через Docker exec к Ollama API
|
||||
# Используем временный файл для передачи большого base64
|
||||
echo " Отправка запроса (это может занять некоторое время)..."
|
||||
TEMP_FILE=$(mktemp)
|
||||
cat > "$TEMP_FILE" <<EOF
|
||||
{
|
||||
"model": "$MODEL",
|
||||
"prompt": "Опиши это изображение на русском языке. Что ты видишь на картинке?",
|
||||
"images": ["$IMAGE_B64"],
|
||||
"stream": false
|
||||
}
|
||||
EOF
|
||||
|
||||
# Копируем файл в контейнер и отправляем запрос
|
||||
$DOCKER_CMD cp "$TEMP_FILE" ollama:/tmp/request.json >/dev/null 2>&1
|
||||
RESPONSE=$($DOCKER_CMD exec ollama sh -c "curl -s --max-time 120 -X POST http://localhost:11434/api/generate \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d @/tmp/request.json" 2>&1)
|
||||
|
||||
# Удаляем временный файл
|
||||
rm -f "$TEMP_FILE"
|
||||
|
||||
# Проверяем ответ
|
||||
if echo "$RESPONSE" | grep -q '"response"'; then
|
||||
echo " ✓ Успешно! Ответ модели:"
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
# Извлекаем ответ более надежным способом
|
||||
echo "$RESPONSE" | python3 -c "import sys, json; data=json.load(sys.stdin); print(data.get('response', ''))" 2>/dev/null || \
|
||||
echo "$RESPONSE" | grep -o '"response":"[^"]*"' | sed 's/"response":"\(.*\)"/\1/' | sed 's/\\n/\n/g' | sed 's/\\"/"/g' | head -20
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
echo "=== ✓ Тест завершен успешно! Vision работает! ==="
|
||||
elif echo "$RESPONSE" | grep -q "error\|Error\|ERROR"; then
|
||||
echo " ✗ Ошибка при запросе к API:"
|
||||
echo "$RESPONSE" | grep -i error || echo "$RESPONSE"
|
||||
echo ""
|
||||
echo "Проверьте:"
|
||||
echo " 1. Логи Ollama: $DOCKER_CMD logs ollama --tail 30"
|
||||
echo " 2. Поддерживает ли модель vision: $DOCKER_CMD exec ollama ollama show $MODEL"
|
||||
else
|
||||
echo " ⚠ Неожиданный ответ от API:"
|
||||
echo "$RESPONSE"
|
||||
echo ""
|
||||
echo "Попробуйте проверить через веб-интерфейс Open WebUI"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "6. Инструкции для тестирования через веб-интерфейс:"
|
||||
echo ""
|
||||
echo " Шаг 1: Откройте Open WebUI: https://odo.iieasy.ru"
|
||||
echo " Шаг 2: Выберите модель: $MODEL"
|
||||
echo " Шаг 3: Найдите кнопку загрузки изображения в поле ввода (📎 или 📷)"
|
||||
echo " Шаг 4: Загрузите изображение: $IMAGE_FILE"
|
||||
echo " Шаг 5: Задайте вопрос: \"Опиши это изображение на русском языке. Что ты видишь?\""
|
||||
echo ""
|
||||
echo "Файл изображения: $IMAGE_FILE"
|
||||
50
scripts/update.sh
Executable file
50
scripts/update.sh
Executable file
@@ -0,0 +1,50 @@
|
||||
#!/bin/bash
|
||||
# Скрипт для обновления Open WebUI до последней версии
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
CONTAINER_NAME="open-webui"
|
||||
|
||||
echo "=== Обновление Open WebUI ==="
|
||||
echo ""
|
||||
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
# Проверка текущей версии в docker-compose.yml
|
||||
CURRENT_VERSION=$(grep "image: ghcr.io/open-webui/open-webui:" docker-compose.yml | sed 's/.*:\(.*\)/\1/')
|
||||
echo "Текущая версия в docker-compose.yml: $CURRENT_VERSION"
|
||||
|
||||
echo ""
|
||||
echo "1. Получение новых образов..."
|
||||
sudo docker compose pull
|
||||
|
||||
echo ""
|
||||
echo "2. Остановка контейнеров..."
|
||||
sudo docker compose stop
|
||||
|
||||
echo ""
|
||||
echo "3. Пересоздание контейнеров с новыми образами..."
|
||||
sudo docker compose up -d
|
||||
|
||||
echo ""
|
||||
echo "4. Ожидание запуска контейнеров (30 секунд)..."
|
||||
sleep 30
|
||||
|
||||
echo ""
|
||||
echo "5. Применение ребрендинга..."
|
||||
if [ -f "$SCRIPT_DIR/rebrand.sh" ]; then
|
||||
"$SCRIPT_DIR/rebrand.sh"
|
||||
else
|
||||
echo "⚠ Скрипт rebrand.sh не найден, пропускаем ребрендинг"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== Обновление завершено! ==="
|
||||
echo ""
|
||||
echo "Проверьте статус:"
|
||||
echo " sudo docker compose ps"
|
||||
echo ""
|
||||
echo "Проверьте логи:"
|
||||
echo " sudo docker compose logs open-webui --tail 50"
|
||||
22
searxng/settings.yml
Normal file
22
searxng/settings.yml
Normal file
@@ -0,0 +1,22 @@
|
||||
# SearXNG Settings для работы с Open WebUI
|
||||
# Этот файл включает поддержку JSON формата для API запросов
|
||||
|
||||
use_default_settings: true
|
||||
|
||||
server:
|
||||
secret_key: "CHANGE_ME_SECRET_KEY"
|
||||
bind_address: "0.0.0.0"
|
||||
port: 8080
|
||||
limiter: false
|
||||
method: "GET"
|
||||
|
||||
search:
|
||||
safe_search: 0
|
||||
autocomplete: "google"
|
||||
formats:
|
||||
- html
|
||||
- json
|
||||
|
||||
general:
|
||||
instance_name: "SearXNG"
|
||||
debug: false
|
||||
121
ssl-setup-guide.md
Normal file
121
ssl-setup-guide.md
Normal file
@@ -0,0 +1,121 @@
|
||||
# Быстрая настройка SSL для odo.iieasy.ru
|
||||
|
||||
## Проблема: ERR_SSL_UNRECOGNIZED_NAME_ALERT
|
||||
|
||||
Эта ошибка означает, что SSL сертификат не настроен или не соответствует домену.
|
||||
|
||||
## Решение: Настройка Let's Encrypt сертификата
|
||||
|
||||
### Шаг 1: Проверка DNS
|
||||
|
||||
Убедитесь, что домен `odo.iieasy.ru` указывает на ваш сервер:
|
||||
|
||||
```bash
|
||||
# Проверьте DNS запись
|
||||
dig odo.iieasy.ru +short
|
||||
# или
|
||||
nslookup odo.iieasy.ru
|
||||
```
|
||||
|
||||
Должен вернуться IP адрес вашего сервера.
|
||||
|
||||
### Шаг 2: Создание SSL сертификата в Nginx Proxy Manager
|
||||
|
||||
1. Войдите в Nginx Proxy Manager (обычно `http://your-server-ip:81`)
|
||||
|
||||
2. Перейдите в **SSL Certificates** (в левом меню)
|
||||
|
||||
3. Нажмите **Add SSL Certificate**
|
||||
|
||||
4. Выберите **Let's Encrypt**
|
||||
|
||||
5. Заполните форму:
|
||||
```
|
||||
Domain Names: odo.iieasy.ru
|
||||
Email Address: ваш@email.com
|
||||
Agree to Let's Encrypt Terms: ✓ (включено)
|
||||
Use a DNS Challenge: ✗ (выключено, если порт 80 доступен)
|
||||
```
|
||||
|
||||
6. Нажмите **Save**
|
||||
|
||||
7. Дождитесь создания сертификата (1-2 минуты). Статус должен стать зеленым.
|
||||
|
||||
### Шаг 3: Применение сертификата к Proxy Host
|
||||
|
||||
1. Перейдите в **Proxy Hosts**
|
||||
|
||||
2. Найдите или создайте Proxy Host для `odo.iieasy.ru`
|
||||
|
||||
3. Откройте его для редактирования
|
||||
|
||||
4. Перейдите на вкладку **SSL**
|
||||
|
||||
5. Выберите созданный сертификат в поле **SSL Certificate**
|
||||
|
||||
6. Включите опции:
|
||||
- ✓ **Force SSL** (перенаправляет HTTP → HTTPS)
|
||||
- ✓ **HTTP/2 Support**
|
||||
- ✓ **HSTS Enabled** (опционально)
|
||||
|
||||
7. Нажмите **Save**
|
||||
|
||||
### Шаг 4: Проверка
|
||||
|
||||
1. Подождите 1-2 минуты для применения изменений
|
||||
|
||||
2. Откройте `https://odo.iieasy.ru` в браузере
|
||||
|
||||
3. Проверьте, что SSL работает (замочек в адресной строке)
|
||||
|
||||
## Альтернатива: Использование существующего сертификата
|
||||
|
||||
Если у вас уже есть SSL сертификат для домена:
|
||||
|
||||
1. **SSL Certificates** → **Add SSL Certificate** → **Custom**
|
||||
|
||||
2. Вставьте:
|
||||
- **Certificate**: содержимое файла `.crt` или `.pem`
|
||||
- **Private Key**: содержимое файла `.key`
|
||||
|
||||
3. Нажмите **Save**
|
||||
|
||||
4. Примените к Proxy Host как описано выше
|
||||
|
||||
## Временное решение (только для тестирования)
|
||||
|
||||
Если нужно быстро проверить работу без SSL:
|
||||
|
||||
1. В Proxy Host отключите SSL (вкладка SSL → SSL Certificate = None)
|
||||
|
||||
2. Используйте HTTP: `http://odo.iieasy.ru`
|
||||
|
||||
3. После настройки SSL включите обратно
|
||||
|
||||
## Проверка работы
|
||||
|
||||
```bash
|
||||
# Проверка SSL сертификата
|
||||
openssl s_client -connect odo.iieasy.ru:443 -servername odo.iieasy.ru
|
||||
|
||||
# Проверка доступности
|
||||
curl -I https://odo.iieasy.ru
|
||||
```
|
||||
|
||||
## Частые проблемы
|
||||
|
||||
### Сертификат не создается
|
||||
|
||||
- Проверьте, что порт 80 доступен из интернета
|
||||
- Убедитесь, что DNS запись правильная
|
||||
- Проверьте логи Nginx Proxy Manager
|
||||
|
||||
### Сертификат создан, но ошибка остается
|
||||
|
||||
- Убедитесь, что сертификат применен к Proxy Host
|
||||
- Проверьте, что домен в сертификате совпадает с доменом в Proxy Host
|
||||
- Очистите кеш браузера
|
||||
|
||||
### Порты 80/443 заняты
|
||||
|
||||
Если порты заняты другими сервисами, настройте перенаправление или используйте DNS challenge в Let's Encrypt.
|
||||
BIN
test_images/test_image.jpg
Normal file
BIN
test_images/test_image.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
34
worker/.env.example
Normal file
34
worker/.env.example
Normal file
@@ -0,0 +1,34 @@
|
||||
# === NEXTCLOUD ===
|
||||
DOMAIN_NEXTCLOUD=https://next.iieasy.ru
|
||||
NC_USER=your_nextcloud_username
|
||||
NC_APP_PASSWORD=your_app_password
|
||||
|
||||
# Пути для сканирования (разделенные запятыми)
|
||||
# {username} будет заменен на имя пользователя Nextcloud
|
||||
NC_SCAN_PATHS=/home/{username}/Documents,/home/{username}/Files,/Shared/Documents
|
||||
|
||||
# === OPEN WEBUI API ===
|
||||
DOMAIN_OPENWEBUI=https://odo.iieasy.ru
|
||||
OPENWEBUI_API_KEY=your_api_key_here
|
||||
OPENWEBUI_TIMEOUT=300
|
||||
|
||||
# === AUTHENTIK (опционально, для маппинга пользователей) ===
|
||||
DOMAIN_AUTHENTIK=https://auth.iieasy.ru
|
||||
AUTHENTIK_API_TOKEN=your_authentik_api_token
|
||||
|
||||
# Маппинг пользователей Nextcloud -> Authentik (опционально)
|
||||
# Формат: NC_USER_{nextcloud_username}={authentik_user_id}
|
||||
# NC_USER_john=user.12345678-1234-1234-1234-123456789012
|
||||
|
||||
# === НАСТРОЙКИ ВОРКЕРА ===
|
||||
# Интервал синхронизации в секундах (по умолчанию 300 = 5 минут)
|
||||
SYNC_INTERVAL=300
|
||||
|
||||
# Максимальный размер файла для обработки в байтах (по умолчанию 100MB)
|
||||
MAX_FILE_SIZE=104857600
|
||||
|
||||
# Путь к БД состояния синхронизации
|
||||
DB_PATH=sync_state.db
|
||||
|
||||
# Уровень логирования (DEBUG, INFO, WARNING, ERROR)
|
||||
LOG_LEVEL=INFO
|
||||
132
worker/config.py
Normal file
132
worker/config.py
Normal file
@@ -0,0 +1,132 @@
|
||||
"""
|
||||
Конфигурация для воркера синхронизации Nextcloud -> Qdrant
|
||||
Типизация и валидация настроек
|
||||
"""
|
||||
import os
|
||||
from pathlib import Path
|
||||
from dataclasses import dataclass
|
||||
from typing import List, Optional
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Загрузка переменных окружения: сначала worker/.env, затем корневой .env
|
||||
load_dotenv()
|
||||
root_env = Path(__file__).resolve().parent.parent / ".env"
|
||||
load_dotenv(root_env)
|
||||
|
||||
|
||||
@dataclass
|
||||
class NextcloudConfig:
|
||||
"""Конфигурация подключения к Nextcloud"""
|
||||
url: str
|
||||
username: str
|
||||
password: str
|
||||
scan_paths: List[str] # Пути для сканирования
|
||||
|
||||
|
||||
@dataclass
|
||||
class OpenWebUIConfig:
|
||||
"""Конфигурация подключения к Open WebUI API"""
|
||||
api_url: str
|
||||
api_key: str
|
||||
timeout: int = 300 # Таймаут для больших файлов (секунды)
|
||||
|
||||
|
||||
@dataclass
|
||||
class AuthentikConfig:
|
||||
"""Конфигурация для маппинга пользователей Authentik"""
|
||||
api_url: Optional[str] = None
|
||||
api_token: Optional[str] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class WorkerConfig:
|
||||
"""Основная конфигурация воркера"""
|
||||
nextcloud: NextcloudConfig
|
||||
openwebui: OpenWebUIConfig
|
||||
authentik: AuthentikConfig
|
||||
sync_interval: int = 300 # Интервал синхронизации (секунды)
|
||||
max_file_size: int = 100 * 1024 * 1024 # Максимальный размер файла (100MB)
|
||||
db_path: str = "sync_state.db" # Путь к SQLite БД для отслеживания состояния
|
||||
log_level: str = "INFO"
|
||||
|
||||
|
||||
def load_config() -> WorkerConfig:
|
||||
"""
|
||||
Загрузка и валидация конфигурации из переменных окружения
|
||||
|
||||
Returns:
|
||||
WorkerConfig: Валидированная конфигурация
|
||||
|
||||
Raises:
|
||||
ValueError: Если обязательные переменные не установлены
|
||||
"""
|
||||
# Nextcloud конфигурация
|
||||
nc_url = os.getenv("DOMAIN_NEXTCLOUD", "").rstrip("/")
|
||||
if not nc_url:
|
||||
raise ValueError("DOMAIN_NEXTCLOUD не установлен в .env")
|
||||
|
||||
nc_user = os.getenv("NC_USER")
|
||||
if not nc_user:
|
||||
raise ValueError("NC_USER не установлен в .env")
|
||||
|
||||
nc_password = os.getenv("NC_APP_PASSWORD")
|
||||
if not nc_password:
|
||||
raise ValueError("NC_APP_PASSWORD не установлен в .env")
|
||||
|
||||
# Пути для сканирования (можно настроить через переменные окружения)
|
||||
scan_paths = os.getenv(
|
||||
"NC_SCAN_PATHS",
|
||||
"/home/{username}/Documents,/home/{username}/Files,/Shared/Documents"
|
||||
).split(",")
|
||||
|
||||
nextcloud_config = NextcloudConfig(
|
||||
url=nc_url,
|
||||
username=nc_user,
|
||||
password=nc_password,
|
||||
scan_paths=[path.strip() for path in scan_paths]
|
||||
)
|
||||
|
||||
# Open WebUI конфигурация
|
||||
webui_url = os.getenv("DOMAIN_OPENWEBUI", "").rstrip("/")
|
||||
if not webui_url:
|
||||
raise ValueError("DOMAIN_OPENWEBUI не установлен в .env")
|
||||
|
||||
webui_api_key = os.getenv("OPENWEBUI_API_KEY")
|
||||
if not webui_api_key or webui_api_key == "твой_api_ключ_от_openwebui":
|
||||
raise ValueError(
|
||||
"OPENWEBUI_API_KEY не установлен или имеет значение по умолчанию. "
|
||||
"Создайте API ключ в Open WebUI -> Settings -> Account -> API Keys"
|
||||
)
|
||||
|
||||
timeout = int(os.getenv("OPENWEBUI_TIMEOUT", "300"))
|
||||
|
||||
openwebui_config = OpenWebUIConfig(
|
||||
api_url=f"{webui_url}/api/v1",
|
||||
api_key=webui_api_key,
|
||||
timeout=timeout
|
||||
)
|
||||
|
||||
# Authentik конфигурация (опционально)
|
||||
authentik_url = os.getenv("DOMAIN_AUTHENTIK", "").rstrip("/")
|
||||
authentik_token = os.getenv("AUTHENTIK_API_TOKEN")
|
||||
|
||||
authentik_config = AuthentikConfig(
|
||||
api_url=authentik_url if authentik_url else None,
|
||||
api_token=authentik_token
|
||||
)
|
||||
|
||||
# Общие настройки воркера
|
||||
sync_interval = int(os.getenv("SYNC_INTERVAL", "300"))
|
||||
max_file_size = int(os.getenv("MAX_FILE_SIZE", str(100 * 1024 * 1024)))
|
||||
db_path = os.getenv("DB_PATH", "sync_state.db")
|
||||
log_level = os.getenv("LOG_LEVEL", "INFO")
|
||||
|
||||
return WorkerConfig(
|
||||
nextcloud=nextcloud_config,
|
||||
openwebui=openwebui_config,
|
||||
authentik=authentik_config,
|
||||
sync_interval=sync_interval,
|
||||
max_file_size=max_file_size,
|
||||
db_path=db_path,
|
||||
log_level=log_level
|
||||
)
|
||||
243
worker/document_processor.py
Normal file
243
worker/document_processor.py
Normal file
@@ -0,0 +1,243 @@
|
||||
"""
|
||||
Обработка документов различных форматов
|
||||
Поддержка PDF, DOCX, текстовых файлов с обработкой больших файлов (>100MB)
|
||||
"""
|
||||
import logging
|
||||
import io
|
||||
from typing import Optional, Tuple
|
||||
from pathlib import Path
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DocumentProcessor:
|
||||
"""Обработчик документов различных форматов"""
|
||||
|
||||
def __init__(self, max_file_size: int = 100 * 1024 * 1024):
|
||||
"""
|
||||
Инициализация процессора документов
|
||||
|
||||
Args:
|
||||
max_file_size: Максимальный размер файла для прямой обработки (байты)
|
||||
"""
|
||||
self.max_file_size = max_file_size
|
||||
|
||||
def process_file(
|
||||
self,
|
||||
file_content: bytes,
|
||||
filename: str,
|
||||
mime_type: Optional[str] = None
|
||||
) -> Tuple[str, bool]:
|
||||
"""
|
||||
Обработка файла и извлечение текста
|
||||
|
||||
Args:
|
||||
file_content: Содержимое файла
|
||||
filename: Имя файла (для определения типа)
|
||||
mime_type: MIME тип файла (опционально)
|
||||
|
||||
Returns:
|
||||
Кортеж (текст, is_large_file) где is_large_file указывает,
|
||||
что файл был обработан потоково из-за большого размера
|
||||
"""
|
||||
file_size = len(file_content)
|
||||
is_large_file = file_size > self.max_file_size
|
||||
|
||||
# Определение типа файла
|
||||
file_ext = Path(filename).suffix.lower()
|
||||
|
||||
try:
|
||||
if file_ext == '.pdf' or mime_type == 'application/pdf':
|
||||
return self._process_pdf(file_content, is_large_file), is_large_file
|
||||
|
||||
elif file_ext in ['.docx', '.doc'] or mime_type in ['application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/msword']:
|
||||
return self._process_docx(file_content), False
|
||||
|
||||
elif file_ext in ['.txt', '.md', '.markdown'] or mime_type in ['text/plain', 'text/markdown']:
|
||||
return self._process_text(file_content), False
|
||||
|
||||
elif file_ext == '.csv' or mime_type == 'text/csv':
|
||||
return self._process_csv(file_content), False
|
||||
|
||||
else:
|
||||
logger.warning(f"Неподдерживаемый формат файла: {filename} (тип: {mime_type})")
|
||||
# Пытаемся обработать как текст
|
||||
try:
|
||||
return self._process_text(file_content), False
|
||||
except Exception:
|
||||
raise ValueError(f"Не удалось обработать файл {filename}: неподдерживаемый формат")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при обработке файла {filename}: {e}")
|
||||
raise
|
||||
|
||||
def _process_pdf(self, content: bytes, is_large: bool) -> str:
|
||||
"""
|
||||
Обработка PDF файла
|
||||
|
||||
Args:
|
||||
content: Содержимое PDF
|
||||
is_large: Флаг большого файла (для потоковой обработки)
|
||||
|
||||
Returns:
|
||||
Извлеченный текст
|
||||
"""
|
||||
try:
|
||||
import pypdf
|
||||
|
||||
pdf_file = io.BytesIO(content)
|
||||
pdf_reader = pypdf.PdfReader(pdf_file)
|
||||
|
||||
text_parts = []
|
||||
total_pages = len(pdf_reader.pages)
|
||||
|
||||
logger.info(f"Обработка PDF: {total_pages} страниц")
|
||||
|
||||
# Для больших файлов обрабатываем страницы порциями
|
||||
if is_large:
|
||||
# Ограничиваем количество страниц для очень больших файлов
|
||||
max_pages = min(total_pages, 1000) # Максимум 1000 страниц
|
||||
logger.warning(f"Большой PDF файл. Обрабатываются первые {max_pages} из {total_pages} страниц")
|
||||
else:
|
||||
max_pages = total_pages
|
||||
|
||||
for page_num in range(max_pages):
|
||||
try:
|
||||
page = pdf_reader.pages[page_num]
|
||||
text = page.extract_text()
|
||||
if text.strip():
|
||||
text_parts.append(f"--- Страница {page_num + 1} ---\n{text}")
|
||||
except Exception as e:
|
||||
logger.warning(f"Ошибка при обработке страницы {page_num + 1}: {e}")
|
||||
continue
|
||||
|
||||
result = "\n\n".join(text_parts)
|
||||
|
||||
if not result.strip():
|
||||
raise ValueError("Не удалось извлечь текст из PDF")
|
||||
|
||||
return result
|
||||
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
"Библиотека pypdf не установлена. Установите: pip install pypdf"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при обработке PDF: {e}")
|
||||
raise
|
||||
|
||||
def _process_docx(self, content: bytes) -> str:
|
||||
"""
|
||||
Обработка DOCX файла
|
||||
|
||||
Args:
|
||||
content: Содержимое DOCX
|
||||
|
||||
Returns:
|
||||
Извлеченный текст
|
||||
"""
|
||||
try:
|
||||
import docx
|
||||
|
||||
doc_file = io.BytesIO(content)
|
||||
doc = docx.Document(doc_file)
|
||||
|
||||
text_parts = []
|
||||
|
||||
# Извлечение текста из параграфов
|
||||
for paragraph in doc.paragraphs:
|
||||
if paragraph.text.strip():
|
||||
text_parts.append(paragraph.text)
|
||||
|
||||
# Извлечение текста из таблиц
|
||||
for table in doc.tables:
|
||||
for row in table.rows:
|
||||
row_text = " | ".join(cell.text.strip() for cell in row.cells)
|
||||
if row_text.strip():
|
||||
text_parts.append(row_text)
|
||||
|
||||
result = "\n\n".join(text_parts)
|
||||
|
||||
if not result.strip():
|
||||
raise ValueError("Не удалось извлечь текст из DOCX")
|
||||
|
||||
return result
|
||||
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
"Библиотека python-docx не установлена. Установите: pip install python-docx"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при обработке DOCX: {e}")
|
||||
raise
|
||||
|
||||
def _process_text(self, content: bytes) -> str:
|
||||
"""
|
||||
Обработка текстового файла
|
||||
|
||||
Args:
|
||||
content: Содержимое файла
|
||||
|
||||
Returns:
|
||||
Текст с правильной кодировкой
|
||||
"""
|
||||
# Попытка различных кодировок
|
||||
encodings = ['utf-8', 'utf-8-sig', 'cp1251', 'latin-1']
|
||||
|
||||
for encoding in encodings:
|
||||
try:
|
||||
return content.decode(encoding)
|
||||
except UnicodeDecodeError:
|
||||
continue
|
||||
|
||||
# Если ничего не помогло, используем errors='replace'
|
||||
return content.decode('utf-8', errors='replace')
|
||||
|
||||
def _process_csv(self, content: bytes) -> str:
|
||||
"""
|
||||
Обработка CSV файла (конвертация в читаемый текст)
|
||||
|
||||
Args:
|
||||
content: Содержимое CSV
|
||||
|
||||
Returns:
|
||||
Текстовая версия CSV
|
||||
"""
|
||||
import csv
|
||||
|
||||
text_content = self._process_text(content)
|
||||
csv_reader = csv.reader(io.StringIO(text_content))
|
||||
|
||||
rows = []
|
||||
for row_num, row in enumerate(csv_reader, 1):
|
||||
rows.append(f"Строка {row_num}: {' | '.join(row)}")
|
||||
|
||||
return "\n".join(rows)
|
||||
|
||||
def is_supported_format(self, filename: str, mime_type: Optional[str] = None) -> bool:
|
||||
"""
|
||||
Проверка поддержки формата файла
|
||||
|
||||
Args:
|
||||
filename: Имя файла
|
||||
mime_type: MIME тип
|
||||
|
||||
Returns:
|
||||
True если формат поддерживается
|
||||
"""
|
||||
file_ext = Path(filename).suffix.lower()
|
||||
supported_extensions = {'.pdf', '.docx', '.doc', '.txt', '.md', '.markdown', '.csv'}
|
||||
|
||||
if file_ext in supported_extensions:
|
||||
return True
|
||||
|
||||
supported_mimes = {
|
||||
'application/pdf',
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'application/msword',
|
||||
'text/plain',
|
||||
'text/markdown',
|
||||
'text/csv'
|
||||
}
|
||||
|
||||
return mime_type in supported_mimes if mime_type else False
|
||||
249
worker/nextcloud_client.py
Normal file
249
worker/nextcloud_client.py
Normal file
@@ -0,0 +1,249 @@
|
||||
"""
|
||||
WebDAV клиент для работы с Nextcloud
|
||||
Сканирование директорий, загрузка файлов, получение метаданных
|
||||
"""
|
||||
import os
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from typing import List, Dict, Optional, Tuple
|
||||
from pathlib import Path
|
||||
import requests
|
||||
from requests.auth import HTTPBasicAuth
|
||||
from requests.exceptions import RequestException, Timeout
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NextcloudClient:
|
||||
"""Клиент для работы с Nextcloud через WebDAV API"""
|
||||
|
||||
def __init__(self, url: str, username: str, password: str):
|
||||
"""
|
||||
Инициализация WebDAV клиента
|
||||
|
||||
Args:
|
||||
url: URL Nextcloud (например, https://next.iieasy.ru)
|
||||
username: Имя пользователя Nextcloud
|
||||
password: App Password (не основной пароль!)
|
||||
"""
|
||||
self.base_url = url.rstrip("/")
|
||||
self.webdav_url = f"{self.base_url}/remote.php/dav/files/{username}"
|
||||
self.auth = HTTPBasicAuth(username, password)
|
||||
self.session = requests.Session()
|
||||
self.session.auth = self.auth
|
||||
self.session.headers.update({
|
||||
"User-Agent": "iiEasy-Nextcloud-Sync/1.0"
|
||||
})
|
||||
|
||||
def list_directory(self, path: str, depth: int = 1) -> List[Dict]:
|
||||
"""
|
||||
Получение списка файлов и директорий через PROPFIND
|
||||
|
||||
Args:
|
||||
path: Путь к директории (относительно WebDAV root)
|
||||
depth: Глубина рекурсии (0 - только указанный ресурс, 1 - + дочерние)
|
||||
|
||||
Returns:
|
||||
Список словарей с информацией о файлах/директориях
|
||||
|
||||
Raises:
|
||||
RequestException: При ошибках сетевого запроса
|
||||
"""
|
||||
url = f"{self.webdav_url}/{path.lstrip('/')}"
|
||||
|
||||
try:
|
||||
# PROPFIND запрос для получения списка ресурсов
|
||||
headers = {
|
||||
"Depth": str(depth),
|
||||
"Content-Type": "application/xml"
|
||||
}
|
||||
|
||||
body = """<?xml version="1.0"?>
|
||||
<d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns" xmlns:nc="http://nextcloud.org/ns">
|
||||
<d:prop>
|
||||
<d:getlastmodified/>
|
||||
<d:getcontentlength/>
|
||||
<d:getcontenttype/>
|
||||
<oc:fileid/>
|
||||
<nc:has-preview/>
|
||||
</d:prop>
|
||||
</d:propfind>"""
|
||||
|
||||
response = self.session.request(
|
||||
"PROPFIND",
|
||||
url,
|
||||
headers=headers,
|
||||
data=body,
|
||||
timeout=30
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
# Парсинг XML ответа (упрощенная версия)
|
||||
# В production лучше использовать xml.etree.ElementTree
|
||||
files = []
|
||||
# Здесь должен быть парсинг XML, но для простоты используем альтернативный метод
|
||||
return self._parse_propfind_response(response.text)
|
||||
|
||||
except Timeout:
|
||||
logger.error(f"Таймаут при получении списка файлов: {path}")
|
||||
raise
|
||||
except RequestException as e:
|
||||
logger.error(f"Ошибка при получении списка файлов {path}: {e}")
|
||||
raise
|
||||
|
||||
def _parse_propfind_response(self, xml_content: str) -> List[Dict]:
|
||||
"""
|
||||
Упрощенный парсинг PROPFIND ответа
|
||||
В production лучше использовать xml.etree.ElementTree или lxml
|
||||
"""
|
||||
import re
|
||||
files = []
|
||||
|
||||
# Простой regex парсинг (для production использовать XML парсер)
|
||||
# Ищем href и getlastmodified
|
||||
href_pattern = r'<d:href>(.*?)</d:href>'
|
||||
modified_pattern = r'<d:getlastmodified>(.*?)</d:getlastmodified>'
|
||||
size_pattern = r'<d:getcontentlength>(.*?)</d:getcontentlength>'
|
||||
type_pattern = r'<d:getcontenttype>(.*?)</d:getcontenttype>'
|
||||
|
||||
hrefs = re.findall(href_pattern, xml_content)
|
||||
modifieds = re.findall(modified_pattern, xml_content)
|
||||
sizes = re.findall(size_pattern, xml_content)
|
||||
types = re.findall(type_pattern, xml_content)
|
||||
|
||||
for i, href in enumerate(hrefs):
|
||||
# Убираем префикс /remote.php/dav/files/username
|
||||
clean_href = href.replace(f"/remote.php/dav/files/{self.auth.username}", "")
|
||||
if clean_href == "":
|
||||
continue
|
||||
|
||||
files.append({
|
||||
"path": clean_href,
|
||||
"modified": modifieds[i] if i < len(modifieds) else None,
|
||||
"size": int(sizes[i]) if i < len(sizes) and sizes[i] else 0,
|
||||
"type": types[i] if i < len(types) else "application/octet-stream",
|
||||
"is_directory": i < len(types) and types[i] == "httpd/unix-directory"
|
||||
})
|
||||
|
||||
return files
|
||||
|
||||
def download_file(self, path: str) -> bytes:
|
||||
"""
|
||||
Загрузка файла из Nextcloud
|
||||
|
||||
Args:
|
||||
path: Путь к файлу (относительно WebDAV root)
|
||||
|
||||
Returns:
|
||||
Содержимое файла в виде bytes
|
||||
|
||||
Raises:
|
||||
RequestException: При ошибках загрузки
|
||||
"""
|
||||
url = f"{self.webdav_url}/{path.lstrip('/')}"
|
||||
|
||||
try:
|
||||
response = self.session.get(url, timeout=300, stream=True)
|
||||
response.raise_for_status()
|
||||
return response.content
|
||||
|
||||
except Timeout:
|
||||
logger.error(f"Таймаут при загрузке файла: {path}")
|
||||
raise
|
||||
except RequestException as e:
|
||||
logger.error(f"Ошибка при загрузке файла {path}: {e}")
|
||||
raise
|
||||
|
||||
def get_file_metadata(self, path: str) -> Dict:
|
||||
"""
|
||||
Получение метаданных файла (размер, дата изменения)
|
||||
|
||||
Args:
|
||||
path: Путь к файлу
|
||||
|
||||
Returns:
|
||||
Словарь с метаданными
|
||||
"""
|
||||
try:
|
||||
url = f"{self.webdav_url}/{path.lstrip('/')}"
|
||||
response = self.session.head(url, timeout=30)
|
||||
response.raise_for_status()
|
||||
|
||||
return {
|
||||
"size": int(response.headers.get("Content-Length", 0)),
|
||||
"modified": response.headers.get("Last-Modified"),
|
||||
"etag": response.headers.get("ETag", "").strip('"')
|
||||
}
|
||||
except RequestException as e:
|
||||
logger.warning(f"Не удалось получить метаданные для {path}: {e}")
|
||||
return {
|
||||
"size": 0,
|
||||
"modified": None,
|
||||
"etag": ""
|
||||
}
|
||||
|
||||
def scan_directory_recursive(self, base_path: str, max_depth: int = 10) -> List[Dict]:
|
||||
"""
|
||||
Рекурсивное сканирование директории
|
||||
|
||||
Args:
|
||||
base_path: Базовый путь для сканирования
|
||||
max_depth: Максимальная глубина рекурсии
|
||||
|
||||
Returns:
|
||||
Список всех файлов с их метаданными
|
||||
"""
|
||||
all_files = []
|
||||
|
||||
def scan_recursive(current_path: str, depth: int = 0):
|
||||
if depth > max_depth:
|
||||
return
|
||||
|
||||
try:
|
||||
items = self.list_directory(current_path, depth=1)
|
||||
|
||||
for item in items:
|
||||
item_path = item["path"]
|
||||
|
||||
# Пропускаем сам каталог
|
||||
if item_path == current_path:
|
||||
continue
|
||||
|
||||
if item.get("is_directory"):
|
||||
# Рекурсивный обход поддиректорий
|
||||
scan_recursive(item_path, depth + 1)
|
||||
else:
|
||||
# Добавляем файл в список
|
||||
all_files.append({
|
||||
"path": item_path,
|
||||
"size": item.get("size", 0),
|
||||
"modified": item.get("modified"),
|
||||
"type": item.get("type", "application/octet-stream")
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при сканировании {current_path}: {e}")
|
||||
|
||||
scan_recursive(base_path)
|
||||
return all_files
|
||||
|
||||
def extract_username_from_path(self, path: str) -> Optional[str]:
|
||||
"""
|
||||
Извлечение имени пользователя из пути Nextcloud
|
||||
Например: /home/username/Documents -> username
|
||||
|
||||
Args:
|
||||
path: Путь к файлу/директории
|
||||
|
||||
Returns:
|
||||
Имя пользователя или None
|
||||
"""
|
||||
# Формат путей: /home/{username}/...
|
||||
parts = path.strip("/").split("/")
|
||||
if len(parts) >= 2 and parts[0] == "home":
|
||||
return parts[1]
|
||||
return None
|
||||
|
||||
def close(self):
|
||||
"""Закрытие сессии"""
|
||||
self.session.close()
|
||||
448
worker/nextcloud_sync.py
Executable file
448
worker/nextcloud_sync.py
Executable file
@@ -0,0 +1,448 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Воркер синхронизации Nextcloud -> Qdrant через Open WebUI API
|
||||
Background процесс для автоматической синхронизации документов
|
||||
"""
|
||||
import sys
|
||||
import time
|
||||
import logging
|
||||
import sqlite3
|
||||
import hashlib
|
||||
from pathlib import Path
|
||||
from typing import Dict, Optional, List
|
||||
from datetime import datetime
|
||||
|
||||
# Импорт локальных модулей
|
||||
from config import load_config, WorkerConfig
|
||||
from nextcloud_client import NextcloudClient
|
||||
from openwebui_client import OpenWebUIClient
|
||||
from document_processor import DocumentProcessor
|
||||
|
||||
# Настройка логирования
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
handlers=[
|
||||
logging.FileHandler('sync.log'),
|
||||
logging.StreamHandler(sys.stdout)
|
||||
]
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SyncStateDB:
|
||||
"""Управление состоянием синхронизации через SQLite"""
|
||||
|
||||
def __init__(self, db_path: str):
|
||||
"""
|
||||
Инициализация БД состояния
|
||||
|
||||
Args:
|
||||
db_path: Путь к файлу БД
|
||||
"""
|
||||
self.db_path = db_path
|
||||
self._init_db()
|
||||
|
||||
def _init_db(self):
|
||||
"""Инициализация схемы БД"""
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS synced_files (
|
||||
file_path TEXT PRIMARY KEY,
|
||||
file_hash TEXT NOT NULL,
|
||||
file_size INTEGER NOT NULL,
|
||||
last_modified TEXT,
|
||||
synced_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
user_id TEXT,
|
||||
document_id TEXT
|
||||
)
|
||||
""")
|
||||
|
||||
cursor.execute("""
|
||||
CREATE INDEX IF NOT EXISTS idx_file_hash ON synced_files(file_hash)
|
||||
""")
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def get_file_hash(self, file_path: str, file_content: bytes) -> str:
|
||||
"""Вычисление хеша файла"""
|
||||
return hashlib.sha256(file_content).hexdigest()
|
||||
|
||||
def is_file_synced(self, file_path: str, file_hash: str) -> bool:
|
||||
"""
|
||||
Проверка, синхронизирован ли файл
|
||||
|
||||
Args:
|
||||
file_path: Путь к файлу
|
||||
file_hash: Хеш содержимого файла
|
||||
|
||||
Returns:
|
||||
True если файл уже синхронизирован с таким хешем
|
||||
"""
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute("""
|
||||
SELECT file_hash FROM synced_files
|
||||
WHERE file_path = ? AND file_hash = ?
|
||||
""", (file_path, file_hash))
|
||||
|
||||
result = cursor.fetchone()
|
||||
conn.close()
|
||||
|
||||
return result is not None
|
||||
|
||||
def mark_file_synced(
|
||||
self,
|
||||
file_path: str,
|
||||
file_hash: str,
|
||||
file_size: int,
|
||||
last_modified: Optional[str],
|
||||
user_id: Optional[str],
|
||||
document_id: Optional[str]
|
||||
):
|
||||
"""Отметка файла как синхронизированного"""
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute("""
|
||||
INSERT OR REPLACE INTO synced_files
|
||||
(file_path, file_hash, file_size, last_modified, synced_at, user_id, document_id)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
""", (
|
||||
file_path,
|
||||
file_hash,
|
||||
file_size,
|
||||
last_modified,
|
||||
datetime.now().isoformat(),
|
||||
user_id,
|
||||
document_id
|
||||
))
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def get_sync_stats(self) -> Dict:
|
||||
"""Получение статистики синхронизации"""
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute("SELECT COUNT(*) FROM synced_files")
|
||||
total_files = cursor.fetchone()[0]
|
||||
|
||||
cursor.execute("SELECT SUM(file_size) FROM synced_files")
|
||||
total_size = cursor.fetchone()[0] or 0
|
||||
|
||||
conn.close()
|
||||
|
||||
return {
|
||||
"total_files": total_files,
|
||||
"total_size": total_size
|
||||
}
|
||||
|
||||
|
||||
class UserMapper:
|
||||
"""Маппинг пользователей Nextcloud -> Authentik"""
|
||||
|
||||
def __init__(self, config: WorkerConfig):
|
||||
"""
|
||||
Инициализация маппера пользователей
|
||||
|
||||
Args:
|
||||
config: Конфигурация воркера
|
||||
"""
|
||||
self.config = config
|
||||
# Кеш маппинга (в production можно использовать БД или Authentik API)
|
||||
self._cache: Dict[str, str] = {}
|
||||
|
||||
def map_nextcloud_user_to_authentik(self, nc_username: str) -> Optional[str]:
|
||||
"""
|
||||
Маппинг имени пользователя Nextcloud в user_id Authentik
|
||||
|
||||
Args:
|
||||
nc_username: Имя пользователя в Nextcloud
|
||||
|
||||
Returns:
|
||||
user_id Authentik или None если маппинг не найден
|
||||
"""
|
||||
# Проверка кеша
|
||||
if nc_username in self._cache:
|
||||
return self._cache[nc_username]
|
||||
|
||||
# В production здесь должен быть запрос к Authentik API
|
||||
# или использование общей БД пользователей
|
||||
# Пока используем простое предположение: username совпадает
|
||||
# или можно настроить через переменные окружения
|
||||
|
||||
# Попытка получить из переменных окружения (формат: NC_USER_username=AUTHENTIK_USER_ID)
|
||||
env_key = f"NC_USER_{nc_username}"
|
||||
authentik_user_id = None
|
||||
|
||||
import os
|
||||
if env_key in os.environ:
|
||||
authentik_user_id = os.environ[env_key]
|
||||
else:
|
||||
# По умолчанию предполагаем, что username совпадает
|
||||
# В production это должно быть через Authentik API
|
||||
authentik_user_id = nc_username
|
||||
|
||||
self._cache[nc_username] = authentik_user_id
|
||||
return authentik_user_id
|
||||
|
||||
def get_user_groups(self, nc_username: str) -> List[str]:
|
||||
"""
|
||||
Получение списка групп доступа для пользователя
|
||||
|
||||
Args:
|
||||
nc_username: Имя пользователя Nextcloud
|
||||
|
||||
Returns:
|
||||
Список групп доступа
|
||||
"""
|
||||
# В production получать из Authentik API или Nextcloud групп
|
||||
# Пока возвращаем пустой список (только личные файлы)
|
||||
return []
|
||||
|
||||
|
||||
class NextcloudSyncWorker:
|
||||
"""Основной класс воркера синхронизации"""
|
||||
|
||||
def __init__(self, config: WorkerConfig):
|
||||
"""
|
||||
Инициализация воркера
|
||||
|
||||
Args:
|
||||
config: Конфигурация воркера
|
||||
"""
|
||||
self.config = config
|
||||
self.nc_client = NextcloudClient(
|
||||
config.nextcloud.url,
|
||||
config.nextcloud.username,
|
||||
config.nextcloud.password
|
||||
)
|
||||
self.webui_client = OpenWebUIClient(
|
||||
config.openwebui.api_url,
|
||||
config.openwebui.api_key,
|
||||
config.openwebui.timeout
|
||||
)
|
||||
self.processor = DocumentProcessor(config.max_file_size)
|
||||
self.state_db = SyncStateDB(config.db_path)
|
||||
self.user_mapper = UserMapper(config)
|
||||
|
||||
def sync_path(self, path_template: str) -> int:
|
||||
"""
|
||||
Синхронизация указанного пути
|
||||
|
||||
Args:
|
||||
path_template: Шаблон пути (может содержать {username})
|
||||
|
||||
Returns:
|
||||
Количество синхронизированных файлов
|
||||
"""
|
||||
synced_count = 0
|
||||
|
||||
# Замена {username} в шаблоне пути
|
||||
if "{username}" in path_template:
|
||||
# Сканируем все домашние директории
|
||||
# В production можно получить список пользователей из Nextcloud API
|
||||
username = self.config.nextcloud.username
|
||||
path = path_template.replace("{username}", username)
|
||||
else:
|
||||
path = path_template
|
||||
|
||||
try:
|
||||
logger.info(f"Сканирование пути: {path}")
|
||||
files = self.nc_client.scan_directory_recursive(path)
|
||||
logger.info(f"Найдено файлов: {len(files)}")
|
||||
|
||||
for file_info in files:
|
||||
try:
|
||||
if self._sync_file(file_info, path_template):
|
||||
synced_count += 1
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при синхронизации файла {file_info['path']}: {e}")
|
||||
continue
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при сканировании пути {path}: {e}")
|
||||
|
||||
return synced_count
|
||||
|
||||
def _sync_file(self, file_info: Dict, path_template: str) -> bool:
|
||||
"""
|
||||
Синхронизация одного файла
|
||||
|
||||
Args:
|
||||
file_info: Информация о файле
|
||||
path_template: Шаблон пути (для определения владельца)
|
||||
|
||||
Returns:
|
||||
True если файл был синхронизирован
|
||||
"""
|
||||
file_path = file_info["path"]
|
||||
file_size = file_info.get("size", 0)
|
||||
|
||||
# Пропускаем слишком большие файлы
|
||||
if file_size > self.config.max_file_size * 2: # Двойной лимит для безопасности
|
||||
logger.warning(f"Файл {file_path} слишком большой ({file_size} bytes), пропуск")
|
||||
return False
|
||||
|
||||
# Проверка поддержки формата
|
||||
if not self.processor.is_supported_format(file_path, file_info.get("type")):
|
||||
logger.debug(f"Неподдерживаемый формат: {file_path}")
|
||||
return False
|
||||
|
||||
try:
|
||||
# Загрузка файла
|
||||
file_content = self.nc_client.download_file(file_path)
|
||||
file_hash = self.state_db.get_file_hash(file_path, file_content)
|
||||
|
||||
# Проверка, не синхронизирован ли уже файл
|
||||
if self.state_db.is_file_synced(file_path, file_hash):
|
||||
logger.debug(f"Файл уже синхронизирован: {file_path}")
|
||||
return False
|
||||
|
||||
# Обработка файла
|
||||
text_content, is_large = self.processor.process_file(
|
||||
file_content,
|
||||
Path(file_path).name,
|
||||
file_info.get("type")
|
||||
)
|
||||
|
||||
# Определение владельца файла
|
||||
username = self.nc_client.extract_username_from_path(file_path)
|
||||
if not username:
|
||||
# Для общих папок используем None или специальную группу
|
||||
username = None
|
||||
|
||||
user_id = self.user_mapper.map_nextcloud_user_to_authentik(username) if username else None
|
||||
access_groups = self.user_mapper.get_user_groups(username) if username else []
|
||||
|
||||
# Метаданные для API
|
||||
metadata = {
|
||||
"source": "nextcloud",
|
||||
"path": file_path,
|
||||
"original_size": file_size,
|
||||
"is_large_file": is_large
|
||||
}
|
||||
|
||||
# Загрузка в Open WebUI
|
||||
if is_large or len(text_content.encode('utf-8')) > self.config.max_file_size:
|
||||
# Для больших файлов загружаем как текст
|
||||
result = self.webui_client.upload_document_with_text(
|
||||
text_content=text_content,
|
||||
filename=Path(file_path).name,
|
||||
user_id=user_id,
|
||||
access_groups=access_groups,
|
||||
metadata=metadata
|
||||
)
|
||||
else:
|
||||
# Для обычных файлов загружаем оригинал
|
||||
result = self.webui_client.upload_document(
|
||||
file_content=file_content,
|
||||
filename=Path(file_path).name,
|
||||
user_id=user_id,
|
||||
access_groups=access_groups,
|
||||
metadata=metadata
|
||||
)
|
||||
|
||||
# Сохранение состояния
|
||||
document_id = result.get("id") if isinstance(result, dict) else None
|
||||
self.state_db.mark_file_synced(
|
||||
file_path=file_path,
|
||||
file_hash=file_hash,
|
||||
file_size=file_size,
|
||||
last_modified=file_info.get("modified"),
|
||||
user_id=user_id,
|
||||
document_id=document_id
|
||||
)
|
||||
|
||||
logger.info(f"Файл синхронизирован: {file_path} -> документ ID: {document_id}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при синхронизации файла {file_path}: {e}")
|
||||
return False
|
||||
|
||||
def run_once(self):
|
||||
"""Однократный запуск синхронизации"""
|
||||
logger.info("=== Начало синхронизации ===")
|
||||
|
||||
total_synced = 0
|
||||
|
||||
for path_template in self.config.nextcloud.scan_paths:
|
||||
try:
|
||||
synced = self.sync_path(path_template)
|
||||
total_synced += synced
|
||||
logger.info(f"Синхронизировано файлов из {path_template}: {synced}")
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при синхронизации пути {path_template}: {e}")
|
||||
|
||||
stats = self.state_db.get_sync_stats()
|
||||
logger.info(f"=== Синхронизация завершена ===")
|
||||
logger.info(f"Всего синхронизировано в этом цикле: {total_synced}")
|
||||
logger.info(f"Всего файлов в БД: {stats['total_files']}")
|
||||
logger.info(f"Общий размер: {stats['total_size'] / 1024 / 1024:.2f} MB")
|
||||
|
||||
def run_daemon(self):
|
||||
"""Запуск воркера в режиме daemon (бесконечный цикл)"""
|
||||
logger.info("Запуск воркера в режиме daemon")
|
||||
logger.info(f"Интервал синхронизации: {self.config.sync_interval} секунд")
|
||||
|
||||
try:
|
||||
while True:
|
||||
self.run_once()
|
||||
logger.info(f"Ожидание {self.config.sync_interval} секунд до следующей синхронизации...")
|
||||
time.sleep(self.config.sync_interval)
|
||||
except KeyboardInterrupt:
|
||||
logger.info("Получен сигнал остановки, завершение работы...")
|
||||
finally:
|
||||
self.cleanup()
|
||||
|
||||
def cleanup(self):
|
||||
"""Очистка ресурсов"""
|
||||
logger.info("Очистка ресурсов...")
|
||||
self.nc_client.close()
|
||||
self.webui_client.close()
|
||||
|
||||
|
||||
def main():
|
||||
"""Точка входа"""
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="Воркер синхронизации Nextcloud -> Qdrant")
|
||||
parser.add_argument(
|
||||
"--once",
|
||||
action="store_true",
|
||||
help="Запустить синхронизацию один раз и выйти"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--daemon",
|
||||
action="store_true",
|
||||
help="Запустить в режиме daemon (бесконечный цикл)"
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
config = load_config()
|
||||
worker = NextcloudSyncWorker(config)
|
||||
|
||||
if args.once:
|
||||
worker.run_once()
|
||||
elif args.daemon:
|
||||
worker.run_daemon()
|
||||
else:
|
||||
# По умолчанию запускаем один раз
|
||||
worker.run_once()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Критическая ошибка: {e}", exc_info=True)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
179
worker/openwebui_client.py
Normal file
179
worker/openwebui_client.py
Normal file
@@ -0,0 +1,179 @@
|
||||
"""
|
||||
HTTP клиент для загрузки документов в Open WebUI API
|
||||
Поддержка метаданных user_id и access_groups для изоляции доступа
|
||||
"""
|
||||
import logging
|
||||
from typing import Dict, Optional, List
|
||||
import requests
|
||||
from requests.exceptions import RequestException, Timeout
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class OpenWebUIClient:
|
||||
"""Клиент для работы с Open WebUI API"""
|
||||
|
||||
def __init__(self, api_url: str, api_key: str, timeout: int = 300):
|
||||
"""
|
||||
Инициализация клиента Open WebUI API
|
||||
|
||||
Args:
|
||||
api_url: Базовый URL API (например, https://odo.iieasy.ru/api/v1)
|
||||
api_key: API ключ из Open WebUI Settings -> Account -> API Keys
|
||||
timeout: Таймаут для запросов (секунды)
|
||||
"""
|
||||
self.api_url = api_url.rstrip("/")
|
||||
self.api_key = api_key
|
||||
self.timeout = timeout
|
||||
self.session = requests.Session()
|
||||
self.session.headers.update({
|
||||
"Authorization": f"Bearer {api_key}",
|
||||
"User-Agent": "iiEasy-Nextcloud-Sync/1.0"
|
||||
})
|
||||
|
||||
def upload_document(
|
||||
self,
|
||||
file_content: bytes,
|
||||
filename: str,
|
||||
user_id: Optional[str] = None,
|
||||
access_groups: Optional[List[str]] = None,
|
||||
metadata: Optional[Dict] = None
|
||||
) -> Dict:
|
||||
"""
|
||||
Загрузка документа в Open WebUI через API
|
||||
|
||||
Args:
|
||||
file_content: Содержимое файла в виде bytes
|
||||
filename: Имя файла
|
||||
user_id: ID пользователя Authentik для изоляции доступа
|
||||
access_groups: Список групп доступа (для совместного доступа)
|
||||
metadata: Дополнительные метаданные (source, path и т.д.)
|
||||
|
||||
Returns:
|
||||
Ответ API в виде словаря
|
||||
|
||||
Raises:
|
||||
RequestException: При ошибках загрузки
|
||||
"""
|
||||
url = f"{self.api_url}/documents"
|
||||
|
||||
# Подготовка данных для multipart/form-data
|
||||
files = {
|
||||
"file": (filename, file_content)
|
||||
}
|
||||
|
||||
# Метаданные передаются через form-data или JSON
|
||||
data = {}
|
||||
|
||||
if user_id:
|
||||
data["user_id"] = user_id
|
||||
|
||||
if access_groups:
|
||||
# Если API поддерживает список групп, передаем как JSON строку или отдельные поля
|
||||
data["access_groups"] = ",".join(access_groups) if isinstance(access_groups, list) else access_groups
|
||||
|
||||
if metadata:
|
||||
# Добавляем дополнительные метаданные
|
||||
for key, value in metadata.items():
|
||||
if key not in data:
|
||||
data[key] = str(value)
|
||||
|
||||
try:
|
||||
logger.info(f"Загрузка документа {filename} (размер: {len(file_content)} bytes)")
|
||||
|
||||
response = self.session.post(
|
||||
url,
|
||||
files=files,
|
||||
data=data,
|
||||
timeout=self.timeout
|
||||
)
|
||||
|
||||
response.raise_for_status()
|
||||
|
||||
result = response.json() if response.content else {}
|
||||
logger.info(f"Документ {filename} успешно загружен. ID: {result.get('id', 'unknown')}")
|
||||
|
||||
return result
|
||||
|
||||
except Timeout:
|
||||
logger.error(f"Таймаут при загрузке документа {filename}")
|
||||
raise
|
||||
except RequestException as e:
|
||||
logger.error(f"Ошибка при загрузке документа {filename}: {e}")
|
||||
if hasattr(e.response, 'text'):
|
||||
logger.error(f"Ответ сервера: {e.response.text}")
|
||||
raise
|
||||
|
||||
def upload_document_with_text(
|
||||
self,
|
||||
text_content: str,
|
||||
filename: str,
|
||||
user_id: Optional[str] = None,
|
||||
access_groups: Optional[List[str]] = None,
|
||||
metadata: Optional[Dict] = None
|
||||
) -> Dict:
|
||||
"""
|
||||
Загрузка текстового документа (например, извлеченного из PDF)
|
||||
|
||||
Args:
|
||||
text_content: Текст документа
|
||||
filename: Имя файла (будет изменено на .txt если нужно)
|
||||
user_id: ID пользователя Authentik
|
||||
access_groups: Список групп доступа
|
||||
metadata: Дополнительные метаданные
|
||||
|
||||
Returns:
|
||||
Ответ API
|
||||
"""
|
||||
# Убеждаемся, что файл имеет расширение .txt
|
||||
if not filename.endswith('.txt'):
|
||||
filename = filename.rsplit('.', 1)[0] + '.txt'
|
||||
|
||||
file_content = text_content.encode('utf-8')
|
||||
return self.upload_document(
|
||||
file_content=file_content,
|
||||
filename=filename,
|
||||
user_id=user_id,
|
||||
access_groups=access_groups,
|
||||
metadata=metadata
|
||||
)
|
||||
|
||||
def check_document_exists(self, document_id: str) -> bool:
|
||||
"""
|
||||
Проверка существования документа по ID
|
||||
|
||||
Args:
|
||||
document_id: ID документа
|
||||
|
||||
Returns:
|
||||
True если документ существует
|
||||
"""
|
||||
try:
|
||||
url = f"{self.api_url}/documents/{document_id}"
|
||||
response = self.session.get(url, timeout=30)
|
||||
return response.status_code == 200
|
||||
except RequestException:
|
||||
return False
|
||||
|
||||
def delete_document(self, document_id: str) -> bool:
|
||||
"""
|
||||
Удаление документа по ID
|
||||
|
||||
Args:
|
||||
document_id: ID документа
|
||||
|
||||
Returns:
|
||||
True если удаление успешно
|
||||
"""
|
||||
try:
|
||||
url = f"{self.api_url}/documents/{document_id}"
|
||||
response = self.session.delete(url, timeout=30)
|
||||
response.raise_for_status()
|
||||
return True
|
||||
except RequestException as e:
|
||||
logger.error(f"Ошибка при удалении документа {document_id}: {e}")
|
||||
return False
|
||||
|
||||
def close(self):
|
||||
"""Закрытие сессии"""
|
||||
self.session.close()
|
||||
17
worker/requirements.txt
Normal file
17
worker/requirements.txt
Normal file
@@ -0,0 +1,17 @@
|
||||
# Зависимости для воркера синхронизации Nextcloud -> Qdrant
|
||||
|
||||
# HTTP клиенты
|
||||
requests>=2.31.0
|
||||
|
||||
# Обработка переменных окружения
|
||||
python-dotenv>=1.0.0
|
||||
|
||||
# Обработка PDF
|
||||
pypdf>=3.17.0
|
||||
|
||||
# Обработка DOCX
|
||||
python-docx>=1.1.0
|
||||
|
||||
# WebDAV клиент (альтернатива requests для WebDAV)
|
||||
# Используем requests напрямую, но можно добавить:
|
||||
# easywebdav>=1.2.0 # Опционально, если нужны специфичные WebDAV функции
|
||||
Reference in New Issue
Block a user