12 KiB
ADC Retivo
Самохостинговая альтернатива Spotify: загрузка своей музыки, автоматическое обогащение метаданных из открытых источников, модерация, публичные/приватные плейлисты, подписки, лайки, каскадные комментарии, общий поиск и постоянный плеер с очередью и текстами песен.
Стек:
- Backend: Node.js + Express + TypeScript, Drizzle ORM (PostgreSQL)
- Frontend: React + Vite + TypeScript + Tailwind (раздаётся Express-ом в проде)
- Инфраструктура: PostgreSQL, Redis (сессии + очередь BullMQ), MinIO (S3)
- Метаданные: MusicBrainz, AcoustID (Chromaprint/fpcalc), Cover Art Archive, LRCLIB, Wikipedia
Возможности
- Вход только по аккаунту (экран логина для гостей). Регистрация — из админ-панели.
- Первый супер-админ создаётся автоматически из переменных окружения.
- Гибкие разрешения на пользователя: создание плейлистов, загрузка треков, автопринятие, модерация, управление пользователями/библиотекой, просмотр всех плейлистов.
- Загрузка треков: определение дубликатов (SHA-256), сохранение в S3, асинхронный сбор метаданных (название, исполнитель, альбом, обложка, дата, текст с таймкодами), модерация.
- Несколько исполнителей на трек:
A, B,A feat. B,A & Bразбираются в отдельные кредиты (main/featured) со ссылками на страницы каждого артиста; редактируется в админке. - Плейлисты: публичные / по ссылке / приватные; добавление в библиотеку ссылкой или копией; соавторы; настройка «разрешить редактирование из библиотеки».
- Подписки на артистов, лайки треков, каскадные комментарии с ответами.
- Общий поиск: треки, артисты, альбомы, публичные плейлисты, пользователи.
- Плеер не прерывается при переходах: мини-бар и развёрнутый вид, очередь с редактированием и перемешиванием, повтор, текст песни (синхронизированный).
- Системные медиа-контролы (Media Session): обложка, название, исполнители и переключение треков с локскрина/OS-плеера.
- Публичные страницы артиста/альбома/трека/пользователя/плейлиста + кнопка «Поделиться».
- Адаптивный минималистичный интерфейс (blur/glass) на русском.
Быстрый старт (Docker, dev)
cp .env.dev.example .env.dev
docker compose --env-file .env.dev -f docker-compose.dev.yml up --build
Почему
--env-file: Docker Compose использует.env.devдвумя разными путями.env_fileв сервисах кладёт переменные внутрь контейнеров, но подстановка${VAR}в самом compose-файле читается только из.env(или из файла, указанного через--env-file). ЧтобыMINIO_IMAGE/MINIO_COMMAND/порты точно подхватились, передавайте файл явно, как выше.
- Frontend (Vite, hot reload): http://localhost:5173
- Backend API: http://localhost:3001/api
- MinIO Console: http://localhost:9001
Первый админ: admin@adc.retivo / admin123 (задаётся в .env.dev, смените после входа).
Быстрый старт (Docker, prod)
cp .env.example .env
# обязательно смените SESSION_SECRET, пароли и ADMIN_PASSWORD
docker compose up --build -d
Приложение (API + собранный SPA) будет доступно на http://localhost:3000.
Локальная разработка без Docker
Нужны локальные PostgreSQL, Redis и S3-совместимое хранилище (MinIO).
npm install
cp .env.example .env # укажите localhost-хосты и порты
npm run db:migrate -w @adc-retivo/server
npm run db:seed -w @adc-retivo/server
npm run dev # server (3001) + web (5173)
Перед сборкой прод-образов нужно сгенерировать миграции:
npm run db:generate -w @adc-retivo/server.
Переменные окружения
См. .env.example (prod) и .env.dev.example (dev). Ключевые:
| Переменная | Назначение |
|---|---|
DATABASE_URL |
строка подключения к PostgreSQL |
REDIS_URL |
подключение к Redis (сессии, очередь) |
MINIO_* |
доступ к S3/MinIO (endpoint, ключи, bucket) |
MINIO_IMAGE |
docker-образ объектного хранилища (upstream minio/minio удалён из Docker Hub — укажите форк) |
MINIO_COMMAND |
команда контейнера хранилища. Пусто = использовать entrypoint образа (форк). Для stock: server /data --console-address ":9001" |
SESSION_SECRET |
секрет для cookie-сессий |
MB_USER_AGENT |
описательный User-Agent для MusicBrainz (с контактом) |
ACOUSTID_API_KEY |
ключ AcoustID (необязательно; без него fingerprint пропускается) |
FPCALC_PATH |
путь к fpcalc из Chromaprint (по умолчанию fpcalc) |
ADMIN_* |
данные первого супер-админа |
Как работает конвейер загрузки
POST /api/uploads(multipart) → SHA-256, поиск дубликата по хэшу.- Файл кладётся в S3 (
tracks/...), создаётся запись трека (pendingлибоapproved, если у автора естьcanAutoApprove), задача ставится в очередь BullMQ. - Воркер:
- читает теги (
music-metadata) и встроенную обложку; - считает аудио-отпечаток (
fpcalc) и спрашивает AcoustID; - тянет канонические данные из MusicBrainz, обложку из Cover Art Archive;
- берёт биографию/фото артиста из Википедии;
- находит текст и синхронизацию из LRCLIB (LRC → таймкоды);
- создаёт/обновляет артиста и альбом, сохраняет метаданные в трек.
- читает теги (
- Если автопринятие выключено — трек ждёт модерации в админ-панели.
Сетевые источники необязательны: при ошибке/404 соответствующее поле просто остаётся пустым.
Основные разделы API
POST /api/auth/login|logout,GET /api/auth/session|meGET/PATCH /api/me,POST /api/me/password,POST /api/me/avatarGET /api/tracks,/api/tracks/random,/api/tracks/:id,/api/tracks/:id/stream(Range),/api/tracks/:id/lyrics,/api/tracks/:id/comments,/api/tracks/:id/likePOST /api/uploads,GET /api/uploads/mine,POST /api/uploads/:id/retryGET /api/artists/:id,/api/albums/:id,/api/playlists/:id,/api/library,/api/searchPOST/PATCH/DELETE /api/playlists,/api/playlists/:id/tracks,/order,/library,/collaboratorsGET /api/admin/stats|users|moderation|tracks|playlistsPOST /api/admin/users,PATCH/DELETE /api/admin/users/:idPOST /api/admin/tracks/:id/approve|reject
Структура проекта
adc_retivo/
├─ apps/
│ ├─ server/ # Express API + воркер метаданных + раздача SPA
│ │ ├─ src/
│ │ │ ├─ db/ # схема Drizzle, миграции, seed
│ │ │ ├─ lib/ # env, redis, session, storage, queue, metadata/*, presenters
│ │ │ ├─ middleware/ # auth/permissions
│ │ │ ├─ routes/ # REST-роуты
│ │ │ ├─ services/ # ingest, доступ к плейлистам
│ │ │ └─ workers/ # BullMQ-воркер обогащения
│ │ ├─ drizzle/ # сгенерированные SQL-миграции
│ │ └─ Dockerfile(.dev)
│ └─ web/ # React SPA
│ └─ src/{components,pages,store,lib}
├─ docker-compose.yml # prod (app раздаёт SPA)
└─ docker-compose.dev.yml # dev (Vite + watcher)
Полезные команды
npm run typecheck # проверка типов обоих воркспейсов
npm run build # собрать web + server
npm run db:generate -w @adc-retivo/server # сгенерировать миграции из схемы
npm run db:migrate -w @adc-retivo/server # применить миграции
npm run db:seed -w @adc-retivo/server # создать первого админа (если база пуста)
Частые проблемы
exec: "server": executable file not found in $PATH (MinIO).
Upstream-образ minio/minio удалён из Docker Hub, поэтому вы используете форк. У многих форков
свой entrypoint (start.sh), который сам запускает сервер, и им НЕ нужно передавать
server /data .... Решение: задайте MINIO_IMAGE=<ваш-форк> и MINIO_COMMAND= (пусто) —
тогда compose отдаёт command: [] и образ запускается своим entrypoint. Для настоящего
stock-образа, наоборот, укажите MINIO_COMMAND=server /data --console-address ":9001".
.env.dev «не подхватывается».
Так и есть: env_file в сервисах кладёт переменные в контейнеры, но подстановка ${VAR}
в compose-файле читается из .env (или из файла, переданного через --env-file).
Запускайте dev так:
docker compose --env-file .env.dev -f docker-compose.dev.yml up --build
А в prod-режиме можно просто скопировать .env.example в .env — он читается автоматически.
Объектное хранилище недоступно / бакет не создаётся.
Bucket создаётся самим приложением при старте (ensureBucket), отдельный minio-init не нужен.
Проверьте, что MINIO_ENDPOINT/PORT/ключи совпадают с сервисом хранилища.
Заметки по безопасности
- Сессии хранятся в Redis, cookie
httpOnly+sameSite=lax(+secureв проде). - Пароли — bcrypt.
- Стриминг аудио проксируется через API с проверкой доступа и поддержкой HTTP Range.
- Обязательно смените
SESSION_SECRETи пароль первого админа перед публикацией.