add workflow to build

This commit is contained in:
roman.nikolsky
2026-07-04 01:56:10 +03:00
commit af098e6ec4
14 changed files with 1701 additions and 0 deletions
+437
View File
@@ -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-эндпоинты для альтернативного доступа (опционально)