438 lines
21 KiB
Markdown
438 lines
21 KiB
Markdown
# 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-эндпоинты для альтернативного доступа (опционально)
|