add workflow to build
This commit is contained in:
@@ -0,0 +1,437 @@
|
||||
# ytdlp-navidrome — сервис поиска и загрузки музыки
|
||||
|
||||
## Назначение
|
||||
|
||||
Сервис-компаньон к Navidrome. Интерфейс — **Matrix-бот**. Позволяет:
|
||||
1. Искать музыку по названию трека или имени исполнителя.
|
||||
2. Отправлять превью (файл аудио) в чат для прослушивания.
|
||||
3. По подтверждению пользователя скачивать трек в коллекцию Navidrome.
|
||||
|
||||
Весь трафик наружу (yt-dlp и Matrix) проходит через локальный ByeByeDPI-прокси (обход DPI).
|
||||
|
||||
Инфраструктура: k3s на Raspberry Pi. Музыка хранится на SSD:
|
||||
- **Рабочая директория** (превью): `/mnt/ssd/k3s/services/ytdlp_navidrome/tmp`
|
||||
- **Коллекция Navidrome** (финальный файл): `/mnt/ssd/k3s/services/navidrome/music/ytdlp`
|
||||
|
||||
---
|
||||
|
||||
## 1. Архитектура
|
||||
|
||||
```
|
||||
┌────────────┐ ┌────────────────────────────────┐
|
||||
│ Matrix │ Matrix CS API │ ytdlp-navidrome pod │
|
||||
│ homeserver │◄──────────────────►│ │
|
||||
│ │ │ ┌────────────┐ ┌───────────┐ │
|
||||
└────────────┘ │ │ Go-сервис │ │ ByeByeDPI │ │
|
||||
│ │ (Matrix │ │ (sidecar │ │
|
||||
│ │ бот + │ │ SOCKS5 │ │
|
||||
│ │ yt-dlp) │ │ прокси) │ │
|
||||
│ └─────┬──────┘ └─────▲─────┘ │
|
||||
│ │ │ :1080 │
|
||||
│ │ --proxy │ │
|
||||
│ └──────────────┘ │
|
||||
└───────────────┬────────────────┘
|
||||
│
|
||||
┌──────────────────────────┼──────────────┐
|
||||
│ /mnt/ssd (hostPath) │ │
|
||||
│ ▼ │
|
||||
│ ytdlp_navidrome/tmp/ navidrome/music/ytdlp/
|
||||
│ (превью, очищается) (финальные файлы)
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Стек
|
||||
|
||||
| Компонент | Технология | Обоснование |
|
||||
|-----------|-----------|-------------|
|
||||
| Язык | Go | Статический бинарник, минимум зависимостей для ARM. |
|
||||
| Фреймворк | Echo GO | Простой, быстрый |
|
||||
| Matrix SDK | [mautrix-go](https://github.com/mautrix/go) | Де-факто стандартный Go SDK для Matrix. |
|
||||
| Поиск | yt-dlp `--flat-playlist --print` | Только метаданные, без скачивания. |
|
||||
| Скачивание | yt-dlp + ffmpeg | Поддержка всех музыкальных источников. |
|
||||
| Прокси | ByeByeDPI (sidecar) | Обход DPI без внешнего VPN. Локальный SOCKS5. |
|
||||
| Хранение | hostPath | Общие директории с Navidrome и для превью. |
|
||||
|
||||
### Контейнеры в поде
|
||||
|
||||
| Контейнер | Образ | Порты | Назначение |
|
||||
|-----------|-------|-------|------------|
|
||||
| `ytdlp-bot` | Собственный (Go + yt-dlp + ffmpeg) | — | Основной сервис |
|
||||
| `byedpi` | `ghcr.io/hufrea/byedpi:latest` (или собственный) | `1080` (SOCKS5) | Обход DPI, локальный прокси |
|
||||
|
||||
---
|
||||
|
||||
## 2. Функциональные требования
|
||||
|
||||
### 2.1 Диалог с пользователем (Matrix-бот)
|
||||
|
||||
Пользователь общается с ботом в личном или групповом чате Matrix.
|
||||
|
||||
```
|
||||
Пользователь: /search Radiohead Creep
|
||||
Бот: Найдено 5 результатов:
|
||||
1. Radiohead - Creep (3:58)
|
||||
2. Radiohead - Creep [Live] (4:21)
|
||||
3. Radiohead - Creep [Acoustic] (3:45)
|
||||
...
|
||||
Ответьте номером для превью.
|
||||
|
||||
Пользователь: 1
|
||||
Бот: [прикреплённый аудиофайл]
|
||||
Добавить в коллекцию Navidrome? (да/нет)
|
||||
|
||||
Пользователь: да
|
||||
Бот: ✅ Добавлено: Radiohead - Creep.opus
|
||||
```
|
||||
|
||||
#### Команды бота
|
||||
|
||||
| Команда | Описание |
|
||||
|---------|----------|
|
||||
| `/search <запрос>` | Искать музыку. Возвращает список результатов с номерами. |
|
||||
| `<номер>` | Отправить аудиопревью трека из результатов поиска. |
|
||||
| `да` / `yes` / `добавить` | Сохранить трек в коллекцию Navidrome. |
|
||||
| `нет` / `no` / `отмена` | Отменить. Удалить превью. |
|
||||
| `/status` | Показать статус: место на диске, время работы, версия. |
|
||||
| `/proxy` | Показать текущие настройки прокси и статус ByeByeDPI. |
|
||||
| `/help` | Справка. |
|
||||
|
||||
#### Состояние диалога
|
||||
|
||||
Бот хранит состояние per-user (в памяти, не персистентное в v1):
|
||||
|
||||
| Состояние | Данные |
|
||||
|-----------|--------|
|
||||
| `idle` | — |
|
||||
| `search_results` | Запрос, список результатов, timestamp |
|
||||
| `preview_sent` | video_id, путь к превью-файлу, timestamp |
|
||||
|
||||
Таймаут состояния: 10 минут. После таймаута превью удаляется из tmp/.
|
||||
|
||||
### 2.2 Поиск (внутренний)
|
||||
|
||||
1. Бот формирует запрос к yt-dlp:
|
||||
```
|
||||
yt-dlp "ytsearch{limit}:{query}" \
|
||||
--flat-playlist --print "%(id)s\t%(title)s\t%(duration_string)s\t%(channel)s" \
|
||||
--proxy socks5://byedpi:1080
|
||||
```
|
||||
2. Парсит вывод, возвращает пронумерованный список.
|
||||
3. Таймаут: `SEARCH_TIMEOUT` (по умолчанию `10s`).
|
||||
|
||||
### 2.3 Превью аудио (внутренний)
|
||||
|
||||
1. Указанный трек скачивается во временную директорию:
|
||||
```
|
||||
yt-dlp "https://www.youtube.com/watch?v={id}" \
|
||||
-x --audio-format opus --audio-quality 5 \
|
||||
--output "{tmp_dir}/{id}.%(ext)s" \
|
||||
--proxy socks5://byedpi:1080
|
||||
```
|
||||
Качество `5` (среднее) — превью, сэкономить трафик и место.
|
||||
2. Бот отправляет файл в Matrix-чат как `m.audio`.
|
||||
3. Файл сохраняется в `tmp_dir` до решения пользователя (добавить / отменить).
|
||||
|
||||
### 2.4 Добавление в коллекцию (внутренний)
|
||||
|
||||
1. Окончательное скачивание (или конвертация превью) в коллекцию:
|
||||
```
|
||||
yt-dlp "https://www.youtube.com/watch?v={id}" \
|
||||
-x --audio-format opus --audio-quality 0 \
|
||||
--embed-thumbnail --add-metadata \
|
||||
--output "{music_dir}/{artist} - {title}.%(ext)s" \
|
||||
--proxy socks5://byedpi:1080
|
||||
```
|
||||
Качество `0` (лучшее) — финальный файл для Navidrome.
|
||||
2. Файл переименовывается в формат `<artist> - <title>.<ext>`.
|
||||
3. Превью из tmp/ удаляется.
|
||||
4. Navidrome обнаружит файл при следующем сканировании (раз в 6ч).
|
||||
|
||||
### 2.5 Health check
|
||||
|
||||
**Эндпоинт:** `GET /healthz` (для Kubernetes liveness/readiness probes)
|
||||
|
||||
**Ответ:** `200 OK` `{"status": "ok"}` — если:
|
||||
- Go-сервис запущен
|
||||
- yt-dlp доступен
|
||||
- ByeByeDPI sidecar отвечает (проверка через socks5://byedpi:1080)
|
||||
|
||||
---
|
||||
|
||||
## 3. Конфигурация
|
||||
|
||||
### 3.1 Переменные окружения
|
||||
|
||||
#### Matrix
|
||||
|
||||
| Переменная | Обязательная | По умолчанию | Описание |
|
||||
|------------|:------------:|--------------|----------|
|
||||
| `MATRIX_HOMESERVER` | да | — | URL homeserver (например `https://matrix.example.com`) |
|
||||
| `MATRIX_USER_ID` | да | — | `@bot:example.com` — ID бота |
|
||||
| `MATRIX_ACCESS_TOKEN` | да | — | Access token бота |
|
||||
| `MATRIX_DEVICE_ID` | нет | `YTDLBOT` | Device ID |
|
||||
| `MATRIX_ENCRYPTION` | нет | `false` | Использовать E2EE (требует libolm/curve25519-dalek) |
|
||||
|
||||
#### Поиск и скачивание
|
||||
|
||||
| Переменная | По умолчанию | Описание |
|
||||
|------------|--------------|----------|
|
||||
| `SEARCH_LIMIT` | `10` | Максимум результатов поиска |
|
||||
| `SEARCH_TIMEOUT` | `10s` | Таймаут на yt-dlp search |
|
||||
| `DOWNLOAD_TIMEOUT` | `120s` | Таймаут на скачивание |
|
||||
| `AUDIO_FORMAT` | `opus` | Целевой формат аудио |
|
||||
| `AUDIO_QUALITY_PREVIEW` | `5` | Качество превью (0=лучшее, 10=худшее) |
|
||||
| `AUDIO_QUALITY_FINAL` | `0` | Качество финального файла |
|
||||
| `PREVIEW_TTL` | `10m` | Время жизни превью-файла до автоудаления |
|
||||
| `ALLOWED_USERS` | — | Список Matrix ID через запятую (пустой = все) |
|
||||
|
||||
#### Прокси (пробрасывается в yt-dlp через `--proxy`)
|
||||
|
||||
| Переменная | По умолчанию | Описание |
|
||||
|------------|--------------|----------|
|
||||
| `PROXY_ENABLED` | `true` | Включить прокси для yt-dlp. `false` = без прокси. |
|
||||
| `PROXY_URL` | `socks5://localhost:1080` | URL прокси (SOCKS5 / HTTP). Если запущен sidecar — `socks5://byedpi:1080` |
|
||||
|
||||
#### ByeByeDPI (sidecar-контейнер)
|
||||
|
||||
Параметры ByeByeDPI передаются через `COMMAND` в Deployment (см. §4.2).
|
||||
|
||||
| Флаг | По умолчанию (рекомендация) | Описание |
|
||||
|------|---------------------------|----------|
|
||||
| `--ip` | `0.0.0.0` | Адрес для прослушивания |
|
||||
| `--port` | `1080` | Порт SOCKS5 |
|
||||
| `--disorder` | `1-1` | Fake packet для браузеров/доменов |
|
||||
| `--auto` | *(включён)* | Автоматический обход DPI |
|
||||
| `--ttl` | `3` | TTL для fake-пакетов |
|
||||
|
||||
Все флаги настраиваются в Deployment-манифесте. Если нужен другой обходчик DPI (naiveproxy, xray, sing-box),
|
||||
поменяйте image и `PROXY_URL`.
|
||||
|
||||
#### Пути
|
||||
|
||||
| Переменная | По умолчанию | Описание |
|
||||
|------------|--------------|----------|
|
||||
| `MUSIC_DIR` | `/music` | mountPath к коллекции Navidrome |
|
||||
| `MUSIC_SUBDIR` | `ytdlp` | Поддиректория для финальных файлов |
|
||||
| `TMP_DIR` | `/tmp/ytdlp-previews` | Рабочая директория для превью |
|
||||
| `YTDLP_PATH` | `yt-dlp` | Путь к бинарнику |
|
||||
|
||||
### 3.2 Kubernetes-манифесты
|
||||
|
||||
Каталог: `rasp/k3s/services/ytdlp_navidrome/`
|
||||
|
||||
```
|
||||
├── README.md # ← этот документ
|
||||
├── main.go # исходный код сервиса
|
||||
├── Dockerfile # мультистейдж: Go build + yt-dlp + ffmpeg
|
||||
├── service.yaml # Namespace + Deployment + PersistentVolumes
|
||||
└── go.mod
|
||||
```
|
||||
|
||||
#### Deployment — ключевые моменты
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
containers:
|
||||
- name: ytdlp-bot
|
||||
image: ytdlp-navidrome:latest # Собственный образ
|
||||
env:
|
||||
- name: MATRIX_HOMESERVER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: ytdlp-matrix-secret
|
||||
key: homeserver
|
||||
- name: MATRIX_USER_ID
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: ytdlp-matrix-secret
|
||||
key: user-id
|
||||
- name: MATRIX_ACCESS_TOKEN # секрет
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: ytdlp-matrix-secret
|
||||
key: access-token
|
||||
- name: PROXY_URL
|
||||
value: "socks5://localhost:1080" # sidecar
|
||||
- name: MUSIC_DIR
|
||||
value: "/music"
|
||||
volumeMounts:
|
||||
- name: music
|
||||
mountPath: /music/ytdlp # коллекция (Read-Write)
|
||||
- name: tmp
|
||||
mountPath: /tmp/ytdlp-previews
|
||||
resources:
|
||||
requests: { memory: "64Mi", cpu: "50m" }
|
||||
limits: { memory: "128Mi", cpu: "500m" }
|
||||
|
||||
- name: byedpi # ◀ sidecar
|
||||
image: ghcr.io/hufrea/byedpi:latest
|
||||
command: ["ciadpi"]
|
||||
args:
|
||||
- "--ip=0.0.0.0"
|
||||
- "--port=1080"
|
||||
- "--auto"
|
||||
- "--disorder=1-1"
|
||||
- "--ttl=3"
|
||||
ports:
|
||||
- containerPort: 1080
|
||||
name: sock5
|
||||
resources:
|
||||
requests: { memory: "16Mi", cpu: "10m" }
|
||||
limits: { memory: "32Mi", cpu: "100m" }
|
||||
# network=host не нужен — sidecar общается с основным
|
||||
# контейнером через localhost (shared network namespace в поде)
|
||||
|
||||
volumes:
|
||||
- name: music
|
||||
hostPath:
|
||||
path: /mnt/ssd/k3s/services/navidrome/music
|
||||
type: DirectoryOrCreate
|
||||
- name: tmp
|
||||
emptyDir: { medium: Memory, sizeLimit: 512Mi }
|
||||
# Превью хранятся в tmpfs — не нагружают SSD,
|
||||
# ограничены 512 MiB для безопасности
|
||||
```
|
||||
|
||||
**Service:** не нужен для бота (исходящие подключения к Matrix homeserver).
|
||||
Если нужен health endpoint для мониторинга — ClusterIP на порт 8080.
|
||||
|
||||
**Secret:**
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: ytdlp-matrix-secret
|
||||
namespace: ytdlp-navidrome
|
||||
type: Opaque
|
||||
stringData:
|
||||
homeserver: "https://matrix.example.com"
|
||||
user-id: "@bot:example.com"
|
||||
access-token: "syt_..." # ← ваш токен бота
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Нефункциональные требования
|
||||
|
||||
### 4.1 Формат аудио
|
||||
|
||||
| Параметр | Превью | Финальный файл |
|
||||
|----------|--------|----------------|
|
||||
| Кодек | Opus | Opus |
|
||||
| Качество | `5` (среднее) | `0` (лучшее) |
|
||||
| Обложка | нет | `--embed-thumbnail` |
|
||||
| Теги | минимальные | `--add-metadata` (title, artist, album) |
|
||||
| Пример размера | ~1–2 МБ на трек | ~3–5 МБ на трек |
|
||||
|
||||
Альтернатива: MP3 V0, если клиенты не поддерживают Opus.
|
||||
|
||||
### 4.2 Именование файлов
|
||||
|
||||
Директория на хосте: `/mnt/ssd/k3s/services/navidrome/music/ytdlp/`
|
||||
|
||||
Формат имени: `<artist> - <title>.<ext>`
|
||||
|
||||
Примеры:
|
||||
- `Radiohead - Creep.opus`
|
||||
- `Massive Attack - Teardrop.opus`
|
||||
- `unknown - dQw4w9WgXcQ.opus` (метаданные неизвестны)
|
||||
|
||||
Запрещённые символы в имени файла (`/ \ : * ? " < > |`) заменяются на `_`.
|
||||
|
||||
### 4.3 Производительность
|
||||
|
||||
| Метрика | Целевое значение |
|
||||
|---------|-----------------|
|
||||
| Поиск (10 результатов) | ≤ 5 секунд |
|
||||
| Превью (1 трек) | ≤ 30 секунд |
|
||||
| Финальное скачивание (1 трек) | ≤ 60 секунд |
|
||||
| Потребление RAM (простой) | ≤ 64 MiB |
|
||||
| Потребление RAM ( ByeByeDPI ) | ≤ 16 MiB |
|
||||
| Потребление CPU (простой) | ≤ 50m + 10m (bot + proxy) |
|
||||
|
||||
### 4.4 Доступ из Matrix
|
||||
|
||||
- Бот запускается как зарегистрированный пользователь homeserver.
|
||||
- Бот слушает события `m.room.message` и `/search`, реагирует на `m.text`.
|
||||
- Бот может быть ограничен `ALLOWED_USERS` — список Matrix ID через запятую (если пусто — отвечает всем).
|
||||
- В v1 без E2EE. E2EE поддерживается mautrix-go (опциональное включение через `MATRIX_ENCRYPTION=true`).
|
||||
|
||||
### 4.5 Безопасность
|
||||
|
||||
| Вопрос | Решение |
|
||||
|--------|---------|
|
||||
| Токен бота в секрете | `ytdlp-matrix-secret` — не в env-файле кластера |
|
||||
| Доступ к боту | Опционально `ALLOWED_USERS` |
|
||||
| Прокси — только для yt-dlp | ByeByeDPI слушает на localhost, не проброшен наружу |
|
||||
| Запись в коллекцию | runAsUser совпадает с Navidrome (совместимость прав) |
|
||||
|
||||
### 4.6 Ограничения (scope v1)
|
||||
|
||||
| Feature | Статус в v1 | Примечание |
|
||||
|---------|:-----------:|-----------|
|
||||
| Matrix-бот | ✅ входит | Основной интерфейс |
|
||||
| E2EE | ⚠️ опционально | По флагу `MATRIX_ENCRYPTION` |
|
||||
| REST API | ❌ не входит | Только Matrix. REST — при необходимости позже. |
|
||||
| Web UI | ❌ не входит | — |
|
||||
| Групповые чаты | ⚠️ basic | Бот отвечает на mention в группах |
|
||||
| Очередь загрузок | ❌ не входит | Синхронная загрузка |
|
||||
| SoundCloud/Bandcamp | ⚠️ best-effort | yt-dlp поддерживает, поиск может не работать |
|
||||
| Изменение прокси на лету | ❌ не входит | Требуется перезапуск пода |
|
||||
|
||||
---
|
||||
|
||||
## 5. Логирование
|
||||
|
||||
Формат: текстовый, в stdout.
|
||||
|
||||
```
|
||||
2026-07-02T14:30:00Z INFO search user=@alice:example.com query="Radiohead Creep" results=10 dur=2.1s
|
||||
2026-07-02T14:30:05Z INFO preview user=@alice:example.com video_id=XF2YMYBLnSw size=1.2MB dur=18s
|
||||
2026-07-02T14:30:20Z INFO download user=@alice:example.com file="Radiohead - Creep.opus" size=3.7MB dur=12s
|
||||
2026-07-02T14:30:20Z INFO cleanup preview=XF2YMYBLnSw removed=true
|
||||
2026-07-02T14:31:00Z WARN preview_ttl_expired video_id=dQw4w9WgXcQ
|
||||
2026-07-02T14:32:00Z INFO byedpi_check status=ok latency=3ms
|
||||
```
|
||||
|
||||
Уровень логирования: настраивается через `LOG_LEVEL`.
|
||||
|
||||
---
|
||||
|
||||
## 6. Тестирование
|
||||
|
||||
| Уровень | Что проверять |
|
||||
|---------|--------------|
|
||||
| Unit | Парсинг вывода yt-dlp, формирование имён файлов, санитизация, FSM состояний |
|
||||
| Integration | Запуск yt-dlp в subprocess с `/dev/null` прокси, проверка формата вывода |
|
||||
| Matrix | Взаимодействие с тестовым homeserver (Dendrite/Conduit) |
|
||||
| Manual | Полный сценарий: /search → номер → превью → да → файл в navidrome |
|
||||
|
||||
---
|
||||
|
||||
## 7. Стадии реализации
|
||||
|
||||
### Stage 1 — MVP
|
||||
- [ ] Matrix-бот: подключение через mautrix-go
|
||||
- [ ] Команда `/search` — парсинг вывода yt-dlp, отправка результатов
|
||||
- [ ] Превью: скачивание трека в tmp/, отправка аудио в чат
|
||||
- [ ] Добавление: финальное скачивание в коллекцию, очистка tmp
|
||||
- [ ] ByeByeDPI sidecar в Deployment
|
||||
- [ ] `--proxy` везде где yt-dlp обращается наружу
|
||||
- [ ] Dockerfile (Go + yt-dlp + ffmpeg)
|
||||
- [ ] service.yaml (Deployment + Secret)
|
||||
- [ ] Smoke-тест: /search → preview → да → файл появился в файловой системе
|
||||
|
||||
### Stage 2 — Улучшения
|
||||
- [ ] Авторизация через ALLOWED_USERS
|
||||
- [ ] E2EE (опционально)
|
||||
- [ ] Автоочистка tmp/ по расписанию (cronJob или timer в Go)
|
||||
- [ ] `/status` и `/proxy` команды
|
||||
- [ ] Обработка ошибок: не найдено, таймаут превью, нет места
|
||||
- [ ] Батч-загрузка (серия треков по команде)
|
||||
|
||||
### Stage 3 — Интеграция
|
||||
- [ ] Принудительное сканирование Navidrome после загрузки (Navidrome API)
|
||||
- [ ] Метрики Prometheus
|
||||
- [ ] Адаптация прокси под разные обходчики (naiveproxy, xray, sing-box)
|
||||
- [ ] REST-эндпоинты для альтернативного доступа (опционально)
|
||||
Reference in New Issue
Block a user