- Python 70.6%
- TypeScript 27.2%
- PowerShell 1.1%
- JavaScript 0.4%
- Shell 0.3%
- Other 0.2%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| embedder | ||
| eval | ||
| gateway | ||
| hub | ||
| navidrome | ||
| scripts | ||
| web | ||
| .dockerignore | ||
| .env.example | ||
| .gitattributes | ||
| .gitignore | ||
| config.example.yaml | ||
| docker-compose.yml | ||
| HomeMusic.bat | ||
| README.md | ||
HomeMusic
Личный музыкальный сервис на домашнем ПК: библиотека и стриминг — Navidrome, плеер на телефоне — Tempus, поиск, скачивание, загрузка своих файлов и ИИ-ассистент — свой сервис Хаб (FastAPI + PWA). API Хаба — hub/API.md.
Скачать Android-приложение: HomeMusic.apk (все версии — Releases). Приложению нужен свой сервер HomeMusic: вход только по приглашению его владельца.
Телефон ──HTTPS──> VPS 203.0.113.10 (Caddy, сертификат Let's Encrypt на IP)
:8444 → 127.0.0.1:4533 ─┐
:8445 → 127.0.0.1:8080 ─┤ обратный SSH-туннель (пользователь homemusic-tunnel)
▼
ПК (Docker Desktop): homemusic_tunnel → navidrome:4533, hub:8080
MUSIC_ROOT = B:\Music (library/, staging/, .trash/, .backups/)
| Что | Адрес снаружи | Адрес дома |
|---|---|---|
| Navidrome (Tempus, веб-плеер) | https://203.0.113.10:8444 |
http://192.168.1.10:4533 |
| Хаб (PWA) | https://203.0.113.10:8445 |
http://192.168.1.10:8080 |
В обычном браузере Хаб — весь сервис без приложения: Главная, библиотека Navidrome («Понравившиеся», плейлисты, исполнители, альбомы) и плеер с очередью. Внутри Android-приложения он открывается только своими разделами.
Логин и пароль везде одни — учётка Navidrome.
Запуск с нуля
- Docker Desktop (WSL2), Git. Docker Desktop должен стартовать при входе в Windows (сейчас так и есть). Сон ПК отключён (питание от сети → «никогда»).
- Папка музыки:
B:\Musicс подпапкамиlibrary,staging,.trash,.backups. - Настройки:
copy .env.example .env # заполнить секреты (см. комментарии в файле) copy config.example.yaml config.yamlLLM_API_KEY— ключ OpenRouter для ИИ-ассистента (без него работают ссылки и текстовые списки, но не скриншоты).LASTFM_API_KEY— ключ Last.fm для «Микса дня» (похожие треки и исполнители, теги для названия; без него микс собирается из остальных источников). - Ключ туннеля (один раз):
ssh-keygen -t ed25519 -N "" -C homemusic-tunnel -f gateway\tunnel\tunnel_key ssh-keyscan -t ed25519 203.0.113.10 > gateway\tunnel\known_hosts - VPS (один раз, идемпотентно; другие сервисы на VPS не трогает, все изменённые файлы бэкапит в
/root/homemusic-gateway-backup/):scp gateway\vps\setup-vps.sh myvps:/root/homemusic-setup-vps.sh ssh myvps "bash /root/homemusic-setup-vps.sh 203.0.113.10 '<содержимое gateway\tunnel\tunnel_key.pub>'" - Запуск:
docker compose up -d --build - Первый запуск Navidrome: админ создаётся в веб-интерфейсе
http://localhost:4533(или его уже создал установщик — логин/пароль в.env:NAVIDROME_ADMIN_USER/NAVIDROME_ADMIN_PASSWORD). Остальных пользователей заводит админ в Navidrome → «Пользователи». - Телефон:
- Tempus (https://github.com/eddyizm/tempus/releases,
app-tempus-release.apk): серверhttps://203.0.113.10:8444. - Хаб: открыть
https://203.0.113.10:8445в Chrome → меню → «Установить приложение». После этого Хаб есть в меню «Поделиться» Android: скриншот → ассистент, аудиофайл → загрузка в библиотеку.
- Tempus (https://github.com/eddyizm/tempus/releases,
Android-приложение HomeMusic (форк Tempus)
Исходники — android/ (отдельный git-репозиторий, ветка homemusic; в этот репозиторий не входит). Это Tempus с вкладкой
«Хаб» (Каталог, Загрузки, Ассистент, Мои файлы), «Поделиться» → Хаб, уведомлениями ассистента и обновлениями из Хаба.
Статусы загрузок на сервер видны в разделе «Загрузки». Android показывает прогресс по трекам и итог при ручном
скачивании на телефон. Во время автосинхронизации на телефон служба Android может показать общий статус синхронизации.
Ставится рядом с оригинальным Tempus (app.homemusic).
- Установить на телефон: открыть
https://203.0.113.10:8445/app/latest.apkв браузере телефона, разрешить установку. Вход: сервер (предложенhttps://203.0.113.10:8444) → имя и пароль, или «Регистрация по коду» (приглашение), или Google (см. «Аккаунты» ниже). Адрес Хаба по умолчанию уже прописан (настройки → «HomeMusic»). - Выпустить обновление: из корня проекта на чистых
masterи Androidhomemusicвыполнить одну команду:
Она сама выбирает следующую patch-версию иpwsh -File .\scripts\release-android.ps1 -Notes 'Что нового по-русски'versionCode, собирает release (тесты — см. ниже: не повторяются для коммита, который уже прошёл полный прогон; сама сборка — из кеша Gradle, если задачу закончили предсборкой, см. «Время»), находит APK поoutput-metadata.jsonэтой сборки и проверяет пакет, версию, одну подпись (тот же сертификат, что у прежнего релиза) и ровно одну универсальную запись. Дальше по неизменяемой копии APK она строитbsdiff40-v1-патчи от последних шести версий и каждый проверяет восстановлением точного APK, кладёт файлы вB:\Music\.apk\artifacts\(адреса по SHA-256), скачивает их через Хаб, ещё раз восстанавливает APK из принятых байтов и только потом атомарно заменяетlatest.json. Хаб должен быть уже выкачан с маршрутом/app/artifacts(иначе команда остановится до переключения). Для minor/major версии допускается-VersionName '1.15.0'.- Тесты.
testHomemusicDebugUnitTestне нужно гонять дважды. Перед слиянием вместо голого запуска Gradle выполняют предсборку (см. «Время»): она гоняет те же тесты. Полный прогон на чистом дереве записывает отметку для коммита (recordHomeMusicTestsPassedвapp/build.gradle, файлhomemusic-tests-passed/<коммит>в общем git-каталогеandroid/.git, он общий для всех worktree). Это прогон из правил проекта (после rebase): fast-forward вhomemusicсохраняет коммит, поэтому отметка доходит до выпуска. Команда пропускает тесты, только если отметка есть для точногоHEADи дерево чистое; иначе (другой коммит, грязное дерево, прогон с--tests, красные тесты) запускает их сама. - Время. В конце команда печатает таблицу времени по этапам. Патчи и их проверка (в том числе повторное восстановление
из байтов Хаба) идут параллельно, по процессу на каждую базу: байты патчей от этого не меняются. Результат
aaptиapksignerдля прежних APK кешируется по SHA-256 вB:\AndroidEmu\release-cache\apk-info.json(файл можно удалить — проверки просто выполнятся заново). Без кеша Gradle занимал 3,5–4 мин (R8 около 2,5 мин иlintVitalоколо 2,3 мин идут одновременно; ПК владельца). Теперь включён кеш сборки Gradle (org.gradle.caching=true, хранилище вB:\AndroidEmu\gradle-home\caches\build-cache-1; вход javac для Room сделан переносимым —RoomSchemaArgsвapp/build.gradle, поэтому ключ кеша не зависит от папки). Предсборка — последний шаг задачи перед слиянием, из корня worktree задачи на закоммиченном чистом дереве после rebase:
Она берёт из живогоpwsh -File .\scripts\release-android.ps1 -Prebuild -AndroidPath '<Android worktree>'B:\Music\.apk(только чтение, ничего не пишет и не блокирует) ту же следующую версию, что получит выпуск, гоняетtestHomemusicDebugUnitTest assembleHomemusicReleaseс этой версией и печатает версию и таблицу времени. Послеmerge --ff-onlyдерево не меняется, поэтому настоящий выпуск берёт Kotlin, javac, R8, lint и упаковку из кеша, а APK получается побайтно тем же. Если между предсборкой и выпуском вышла другая версия или изменился коммит, кеш промахивается: выпуск идёт обычным путём (дольше, но верно) и пишет подсказку, когда Gradle-этап занял больше минуты. Измерено на ПК владельца (R8, lint, Kotlin и javac целиком из кеша): предсборка с холодным кешем — 4 мин 4 с (тесты и сборка); сборка той же версии и коммита в другой папке — 13–25 с вместо 3,5–4 мин; вся команда выпуска вместе с шестью патчами — около 40 с. APK, собранный в worktree, отличается от собранного в основной папке одним файлом (META-INF/version-control-info.textproto, его пишет Gradle по git-каталогу папки), на работу не влияет. Если Gradle-этап выпуска всё равно длинный (скрипт напишет подсказку), а версия и коммит те же — ключ кеша разошёлся из-за содержимого файлов: например, в основной папке остался файл с CRLF, а git считает дерево чистым (core.autocrlf=input). Сравнить размеры отслеживаемых файлов основной папки и worktree; такие файлы пересоздать изHEAD(удалить иgit checkout -- <файл>). Так нашлись и исправленыnav_graph.xml,nav_drawer.xml,NavigationHelper.java. - Повтор. Исходники уже опубликованы — команда только сверяет файлы с
latest.jsonи ничего не меняет. После сбояlatest.jsonостаётся прежним (клиенты ничего не заметили); повтор убирает непубликованныйHomeMusic-*.apkсбойного запуска, берёт тот жеversionCodeи достраивает выпуск. Второй параллельный запуск останавливается на блокировке. Файлы вartifacts\, не нужные текущему релизу, удаляются через 7 дней. - Нужны Android SDK и JDK по путям из
android/CLAUDE.md, ключandroid/keystore/homemusic-release.jksиandroid/keystore.properties(вне git). Сохраните копию ключа: без него установить обновление поверх приложения нельзя. - Что получает телефон. Старые клиенты и первая установка — полный APK (
/app/latest.apk). Новые выбирают самый маленький проверенный патч для точного хеша установленного APK, без подходящего — полный; системная установка остаётся. На лимитной сети при сбое патча полный APK скачивается только после согласия. Размер патча зависит от изменений кода: DEX хранится без сжатия (packaging.dexвapp/build.gradle), патч на соседнюю версию 1,2–1,8 МБ (со сжатым DEX было 3,7 МБ), сборка без изменений кода — около 16 КиБ; полный APK 15,0 МБ (был 11,4 МБ). 10 КиБ — ориентир, не порог публикации.
- Тесты.
- Изолированный пробный выпуск:
pwsh -File .\scripts\release-android.ps1 -Notes 'Проверка' -StagePath '<отдельная папка>' -AndroidPath '<Android worktree>' [-HubUrl http://127.0.0.1:18080]. В папку staging можно заранее скопировать архивные APK иlatest.jsonдля проверки перехода (с-Prebuild -StagePathверсия для предсборки берётся из этой же папки). Живой каталог команда не использует; с-HubUrlраздачу проверяет указанный Хаб (например, временный контейнер с маршрутамиsystem.py).
Аккаунты: приглашения, регистрация по почте и вход через Google
Аккаунт — пользователь Navidrome. Новые аккаунты появляются по приглашению или, если настроена почта, по email (ниже, «Регистрация по почте»). Приглашение:
- Админ: Хаб → «Ещё» → «Пользователи и приглашения» → «Пригласить», вписать, для кого (имя попадёт в Navidrome), выбрать срок.
Появится одноразовый код вида
ABCD-EFGH— его можно скопировать или отправить кнопкой «Поделиться». - Человек ставит приложение, на экране входа оставляет предложенный сервер (
https://203.0.113.10:8444), нажимает «Далее», затем «Регистрация по коду», вводит код, имя и пароль. Хаб создаёт пользователя в Navidrome (черезNAVIDROME_ADMIN_USER/PASSWORD) и сразу входит. Код одноразовый, срок по умолчанию —INVITE_DAYS(7 дней).
Там же, в «Пользователях», админ видит каждого: сколько потратил на ИИ (всего и за 30 дней), сколько песен добавил и скачал, ♥ и плейлисты, когда был в Хабе и в плеере; по нажатию — расходы по месяцам и последние запросы.
Для обычных пользователей ассистент принимает 5 сообщений в день; для администратора лимита нет.
Лимит общий для всех чатов, скриншотов и ссылок и обновляется в полночь по часовому поясу Хаба
(TIMEZONE, по умолчанию Europe/Moscow). Новый чат, удаление истории и перезапуск Хаба лимит не сбрасывают.
Принятый запрос считается и при последующей ошибке ИИ; подтверждение готовых действий и просмотр истории бесплатны.
В одном сообщении — до 15 фотографий (каждая до 15 МБ) и 5000 символов текста, включая пробелы;
эти ограничения действуют и для администратора. Под полем ввода видны счётчики, превышение не расходует дневную квоту.
Регистрация по почте (по желанию, разовая настройка ~5 минут)
Без настройки её нет. С ней любой, кто знает адрес сервера, сам заводит аккаунт: «Регистрация по почте» на экране входа (приложение и PWA) → почта → «Получить код» → код из письма (6 цифр, 15 минут), имя и пароль. Админ ничего не подтверждает; потом можно входить и по имени, и по почте. Письма уходят через SMTP любого ящика — бесплатно через Gmail (до ~500 писем в сутки):
- Завести для сервера отдельный ящик Gmail (например,
homemusic.noreply@gmail.com) и включить в нём двухэтапную проверку: https://myaccount.google.com/security. - https://myaccount.google.com/apppasswords → имя
HomeMusic→ «Создать» → скопировать пароль из 16 букв. - В
.env:SMTP_HOST=smtp.gmail.com,SMTP_PORT=587,SMTP_USER=<ящик>,SMTP_PASSWORD=<пароль из шага 2>, затемdocker compose up -d hub.
Подойдёт и другой ящик (Яндекс: smtp.yandex.ru, порт 465, пароль приложения) или бесплатный тариф Brevo
(smtp-relay.brevo.com, 587, 300 писем в сутки). Защита: на один адрес — не чаще письма в минуту и 5 в час,
5 неверных кодов гасят письмо, всего за сутки — не больше EMAIL_DAILY_LIMIT (450, чтобы не упереться в лимит Gmail).
Если писем нет — docker logs homemusic_hub | Select-String letter покажет ответ почтового сервера.
Вход через Google (по желанию, разовая настройка ~15 минут)
Без настройки кнопки Google нет, остальное работает. Google не пускает вход на сайт по голому IP, поэтому вход через Google есть только в приложении; Хаб лишь проверяет выданный Google токен (секрета клиента нет).
- https://console.cloud.google.com → создать проект
HomeMusic. - «Google Auth Platform» (APIs & Services → OAuth consent screen) → «Get started»: имя
HomeMusic, ваша почта, Audience — External. Затем «Audience» → «Publish app» (для входа с почтой и именем проверка Google не нужна; иначе войти смогут только адреса из списка «Test users»). - «Clients» → «Create client» → Web application, имя
HomeMusic Hub, адреса не нужны → скопировать Client ID. - «Clients» → «Create client» → Android: package name
app.homemusic, SHA-128:F1:56:76:47:1B:30:9F:69:AB:86:BF:13:87:28:29:61:2A:BC:E3(отпечаток ключа подписи релизов; проверить:apksigner verify --print-certs <apk>). Этот клиент ничего не выдаёт — по нему Google узнаёт приложение. - В
.env:GOOGLE_CLIENT_ID=<Client ID из шага 3>, затемdocker compose up -d hub.
Приложение берёт ID из GET /api/auth/options — пересобирать его не нужно. Первый вход через Google просит код
приглашения (новый аккаунт) — или войти паролем и привязать Google в настройках приложения («HomeMusic» → «Вход через Google»).
Ошибка «Developer console is not set up correctly» значит, что не совпали package name или SHA-1 из шага 4.
Перенос музыки из Яндекс Музыки и Spotify
В приложении — вкладка «Файлы» (библиотека): значок ⇄ в шапке или карточка «Перенести музыку из Яндекс Музыки»; в браузере — ещё и «Ещё» → «Перенести музыку». Дальше «Войти через Яндекс»: Хаб показывает код, его вводят на https://ya.ru/device под своим Яндекс ID (подписка Плюс не нужна). Хаб читает только списки: «Мне нравится», плейлисты (и закрытые), любимые альбомы и исполнители, «Не нравится». Дальше — галочки, что перенести, и режим:
- Экономно (по умолчанию): что уже есть в библиотеке, сразу получает ♥ и встаёт в плейлисты Navidrome в порядке Яндекса; остальное слушается со страницы импорта и скачивается, когда трек послушали. «Скачать всё сейчас» — в любой момент.
- Скачать всё: всё качается в фоне (около 9 МБ на трек). Обычные загрузки и прослушивание идут первыми: импорт не занимает последний воркер очереди.
Без входа — ссылка на открытый профиль (music.yandex.ru/users/<логин>/playlists/3, без закрытых плейлистов и
дизлайков) или токен вручную. Вход (токен) хранится зашифрованным и удаляется после переноса, если не включить
«Запомнить вход» (тогда «Подтянуть новое из Яндекса» добирает новые лайки и треки плейлистов); кнопка «Удалить вход» —
на странице импорта. Ненайденное выбирается вручную в отчёте («Выбрать файл» → страница загрузки).
Вход идёт от имени приложения Яндекс Музыки (своего API у Яндекса нет): если Яндекс это закроет, останутся ссылка на
профиль и токен.
Spotify — без входа, по файлам экспорта: в настройках приватности Spotify запросить данные аккаунта (архив приходит
на почту, обычно за несколько дней), затем «Перенести музыку» → «Загрузить экспорт Spotify» и выбрать из архива
Playlist*.json и/или YourLibrary.json (до 32 файлов, 8 МБ). Хаб берёт из них плейлисты, понравившиеся треки,
сохранённые альбомы и исполнителей (подкасты пропускает), дальше — тот же предпросмотр, режимы и отчёт, что у Яндекса.
В экспорте только названия (без длительности), поэтому сомнительных совпадений в отчёте может быть больше. «Обновить»
у такого переноса нет: новое подтягивается новым экспортом.
Микс дня
Каждое утро (по умолчанию в 5:00 по TIMEZONE, Europe/Moscow) Хаб собирает каждому пользователю с историей
прослушиваний плейлист Navidrome «Микс дня» из 50 песен: часть знакомого (любимое, давно не слушанное, свои ♥ и
загрузки), часть нового — похожего на вкус (радио YouTube Music, Last.fm, Deezer, похожие Яндекса, новинки любимых
исполнителей, «Моя волна» Яндекса, если при импорте включён «Запомнить вход»). Нескачанные песни качаются тихо, в фоне
(в «Загрузках» их нет) и встают в плейлист по мере импорта; не скачалась — её место занимает запасная. Плейлист один
и тот же (id не меняется) — в приложении его можно держать офлайн. При следующем миксе песни, скачанные только ради
старого и никем не прослушанные (не ♥, не в плейлисте, не скачанные явно), уходят в .trash.
- Режим (
PUT /api/mixes/settings): «Обычный» — 40 % знакомого / 60 % нового, «Любимое» — 80/20, «Незнакомое» — 10/90; там же час сборки. Новому пользователю без истории — выбор 3–5 исполнителей (/api/taste/*); страница микса в PWA и карточка в приложении — следующие задачи (T5, T7). - Ключи и настройки (
.env):LASTFM_API_KEY(бесплатный ключ: https://www.last.fm/api/account/create),EMBEDDER_URL(сервис векторов звука, по умолчаниюhttp://embedder:8090; не отвечает — микс без «похожести по звуку»),TIMEZONE. Тонкие настройки —config.yaml, разделmixes:(размер, запас, веса источников). - Собрать заново сейчас (админ):
POST /api/mixes/rebuild({"user_id": N}— другому пользователю). Отчёт сборки (источники, сколько дали, ошибки, время) —GET /api/mixes/today→mix.generatorsи лог Хаба (mix N of user M).
Лёгкий звук для мобильной сети
Приложение в лимитной сети (настройка «Экономия мобильного трафика») просит у Хаба лёгкий вариант звука: GET /api/listen/stream?…&q=mobile.
Файл тяжелее 192 кбит/с (mp3 320, flac) Хаб перекодирует ffmpeg в Ogg Opus 160 кбит/с (~вдвое меньше, на слух почти то же),
файл легче отдаёт как есть. Копии лежат в staging/listen-light (на диске B:\Music\staging, до 2,5 ГБ, лишние стираются
по давности использования — можно очистить папку руками, копии сделаются заново), делаются заранее — для песен «Микса дня» и
треков «Моей волны» — и по первому запросу. Пока копии нет, поток идёт оригиналом (ответ помечен X-HomeMusic-Variant).
Перекодирование идёт в фоне, с низким приоритетом, не больше трёх ffmpeg сразу. Подробности — hub/API.md, PROGRESS.md.
Экономия мобильного трафика в приложении
Настройки → «HomeMusic» → «Экономия мобильного трафика» (по умолчанию включена). «Экономить» значит: настройка включена И
активная сеть лимитная по меткам Android (isActiveNetworkMetered, не по транспорту: Wi-Fi-точка другого телефона — лимитная, мобильный
безлимит — нет). В приложении это HomeTraffic.economy(); её же спрашивает «лёгкий звук» выше. Что меняется в такой сети:
- играющий трек, пока проиграно меньше 30 с, подгружается не больше чем на 40 с вперёд (ранний пропуск стоит ~2 МБ вместо всего файла: у mp3 320 кбит/с это было ~8 МБ); после 30-й секунды трек грузится целиком, как раньше (туннели, обрывы);
- предзагрузка — начало (10 с) одного следующего трека, а не трёх; в «Моей волне» — только после ответа Хаба на текущий трек (волна пересобирает очередь вперёд, и раньше начала заменённых треков выбрасывались);
- перед обновлением приложения показывается размер, первая кнопка — «Дождусь Wi-Fi».
Независимо от сети: кэш стриминга по умолчанию 1–2 ГБ (не больше десятой части свободного места; кто менял — не тронут), обложки Navidrome 500 px вместо исходных. «Микс дня на телефон» (настройка рядом, по умолчанию включена): когда телефон на зарядке и в безлимитном Wi-Fi, песни сегодняшнего микса (плейлист Navidrome) скачиваются в «Скачанное» по одной; при смене микса убираются скачанные ради прошлого микса, кроме ♥ и скачанного вами самим; свободного места остаётся не меньше 2 ГБ. Микс из загрузок играет без интернета. Там же счётчик «Мобильный трафик плеера»: сколько трафика в лимитной сети ушло на песни и на начала следующих сегодня и за 7 дней (плюс «всё приложение» по статистике Android, если она отдаётся).
Soulseek (по желанию)
Источник lossless-файлов для загрузок в библиотеку через slskd. По умолчанию
выключен: Хаб включает его, только когда в .env заданы все три значения — SLSKD_API_KEY (любая случайная строка,
например openssl rand -hex 24), SLSKD_SLSK_USERNAME и SLSKD_SLSK_PASSWORD (новый логин регистрируется сам при
первом входе). Контейнер slskd запускается с профилем: COMPOSE_PROFILES=soulseek в .env, затем docker compose up -d.
- Каждая загрузка в библиотеку (кнопка, ♥, импорт, Микс дня, Моя волна) сначала ищет файл на Soulseek и берёт его,
только если он лучше обычных источников: по умолчанию только FLAC (
soulseek.formatвconfig.yaml). Не нашёл, нет сети Soulseek или пир не отдал файл заsoulseek.transfer_timeout_s(90 с, с очередью) — загрузка идёт дальше обычным порядком (hitmoz → … → YouTube), Soulseek не ждут. - Прослушивание Soulseek не использует никогда (файл у пира качается целиком, с его очередью), вкладка «Файлы» —
тоже (поиск там занимает секунды; по имени —
provider=soulseekв API). - Файл проверяется по имени и папкам у пира (исполнитель, название, версия, длительность) и после загрузки — по формату. Для русских треков с матом, где важна версия без цензуры, файл Soulseek без пометки «без цензуры» не берётся.
- Готовый файл переносится в библиотеку и удаляется из тома slskd. Свои файлы в сеть не раздаются. Проверка — «Ещё» → «Проверить Soulseek» (у админа, когда источник включён).
Обновление
git pull
docker compose up -d --build # пересобрать Хаб (yt-dlp внутри образа обновляется пересборкой)
docker compose pull navidrome # после смены версии в docker-compose.yml
docker image prune -f # убрать прежние версии образов, оставшиеся без имени (<none>) после пересборки
Пересборка под тем же тегом не удаляет прежний образ: он остаётся без имени и занимает место в диске Docker
(образ embedder — около 7 ГБ). prune -f (без -a) удаляет только такие образы, у которых нет контейнера;
образы с тегом, контейнеры и тома не трогает. Запускайте после каждого docker compose build / up -d --build.
yt-dlp ломается после изменений YouTube — пересобирайте образ Хаба примерно раз в неделю
(версия yt-dlp закреплена в hub/pyproject.toml: поднять её и выполнить uv lock в hub/).
При обновлении bgutil-ytdlp-pot-provider одновременно меняйте тег сервиса youtube-pot в
docker-compose.yml: плагин и сервер должны иметь одинаковую версию.
YouTube просит проверку «не робот»
Sign in to confirm you’re not a bot означает, что YouTube отказал в выдаче файла: песня может находиться
поиском, но скачивание и прослушивание недоступны. Это не ошибка распознавания ассистента. Можно повторить
позже или выбрать файл из другого источника; повторный поиск того же ролика сам по себе проверку не снимает.
Хаб использует клиент mweb и автоматически получает PO-токены через
bgutil-ytdlp-pot-provider, как рекомендует
руководство yt-dlp. Сервис youtube-pot запускается
в Compose вместе с Хабом, доступен только внутри Docker и не получает файл cookies. Для диагностики доступности:
docker compose exec hub python -c "import urllib.request; print(urllib.request.urlopen('http://youtube-pot:4416/ping', timeout=5).status)".
PO-токены не гарантируют обход проверки входа, HTTP 403 и ограничений аккаунта/IP.
Обычный YouTube и поиск перезаливов делят два слота; yt-dlp делает паузу 1 с между запросами и 5 с перед
скачиванием, повторяет сетевые ошибки один раз. Успешные метаданные ролика переиспользуются 10 минут,
одновременные запросы одного ролика объединяются; существующий кэш прослушивания сохраняется. После проверки
«не робот», недействительной сессии или HTTP 429 новые обращения YouTube прекращаются на 15 минут;
загрузки могут перейти к резервному SoundCloud. Параметры — ytdlp в config.example.yaml.
Если ошибка сохраняется, владелец сервера может подключить cookies YouTube:
- Экспортировать cookies только
youtube.comв формате Netscape по инструкции yt-dlp: войти в YouTube в отдельном приватном окне, в той же вкладке открытьhttps://www.youtube.com/robots.txt, экспортировать cookies и закрыть приватное окно. yt-dlp предупреждает о риске блокировки аккаунта; используйте отдельный аккаунт и подключайте его только при необходимости. - Сохранить файл на серверном ПК как
B:\Music\.youtube-cookies.txt(при другомMUSIC_ROOT— в его корне). Cookies дают доступ к сессии: не отправлять их в чат и не добавлять в git. - В
.envдобавитьYTDLP_COOKIES_FILE=/music/.youtube-cookies.txt— это путь внутри контейнера. Применить окружение командойdocker compose up -d hub, дождавшись завершения активных загрузок. - Если yt-dlp сообщает
cookies are no longer valid/rotated, заново выполнить шаг 1; продление дат в старом файле сессию не восстанавливает. Сначала полностью сохранить новый экспорт в отдельный локальный файл вне репозитория и закрыть приватное окно, затем заменитьB:\Music\.youtube-cookies.txtготовым файлом (не редактировать его по строкам во время работы загрузчика). Ручной вход и CAPTCHA выполняет владелец; содержимое экспорта не отправлять агенту и не печатать в логах. - Повторить загрузку. Хаб замечает замену файла, сбрасывает паузу после отказа и кэш метаданных; перезапуск
не нужен. Он читает cookies в приватную копию и не перезаписывает экспорт; cookies других доменов
и SoundCloud не используются. Не экспортировать все cookies обычного браузера через
--cookies-from-browser. Для отключения убрать переменную из.envи снова выполнить команду шага 3.
Cookies не гарантируют снятие ограничений YouTube. Вход в Google в приложении HomeMusic авторизует пользователя Хаба и не передаёт YouTube-сессию серверному загрузчику.
Исходящий прокси
В .env укажите OUTBOUND_PROXY и список доменов через запятую в OUTBOUND_PROXY_HOSTS. Общий HTTP-клиент
Хаба направляет через прокси перечисленные домены и их поддомены. youtube.com включает прокси для YouTube
и YouTube Music, soundcloud.com — для SoundCloud. Для каждого из этих источников поиск, извлечение ссылок
и получение аудио идут через один прокси, включая CDN. Настройка применяется после пересоздания контейнера.
Если прокси запущен на машине с Docker, используйте, например, http://host.docker.internal:3128.
На Linux прокси должен слушать на интерфейсе, доступном из сети Docker. Compose добавляет адрес хоста
для Хаба и сервиса PO-токенов.
Полезное
HomeMusic.bat (ярлык на рабочем столе) включает и выключает HomeMusic одним запуском: если работают все
контейнеры — спрашивает подтверждение, предупреждает о загрузках в очереди и делает docker compose stop;
иначе запускает Docker Desktop (если он не запущен), docker compose up -d и ждёт ответа Хаба. Docker Desktop
не выключает (в нём могут работать и другие контейнеры). Выключенный так HomeMusic не поднимается сам после перезагрузки ПК.
docker compose ps
docker logs --tail 50 homemusic_hub # JSON-логи Хаба
docker logs --tail 20 homemusic_tunnel # подключения туннеля
docker compose restart tunnel
curl http://localhost:8080/healthz
- Бэкапы: база Хаба —
B:\Music\.backups\hub\(каждый день в 4:00, 7 копий), база Navidrome —B:\Music\.backups\navidrome\(в 4:30, 7 копий). - Удалённое из Хаба лежит в
B:\Music\.trash\<дата>\30 дней. - ID треков Navidrome стабильны: Хаб пишет в файлы теги
HOMEMUSIC_TRACK_ID/HOMEMUSIC_ALBUM_ID, из них Navidrome строит свои ID (navidrome/navidrome.toml). Не меняйтеPID.*без бэкапа базы Navidrome. - Источники и лимиты —
config.yaml(порядок источников для русской и остальной музыки — для русской по умолчанию hitmoz → hitmos.fm → SoundCloud → перезаливы YouTube → YouTube Music;sources.uploaded_before— файлы SoundCloud, загруженные с 2026-05-01, не берутся; источники перезаливов без цензурыsources.reuploads— YouTube и SoundCloud, домены hitmoz, пороги сопоставления).sources.download_fallback(по умолчанию[soundcloud],[]отключает) — резерв полной записи для загрузки и прослушивания после отказа обычных источников, в том числе проверки «не робот» YouTube. Действуют те же проверки исполнителя, версии и длительности; короткое превью полной песней не становится. Приложение с 1.14.1 запрашивает только полную запись (full=1): при её недоступности показывает ошибку вместо 30-секундного превью, а старый кэш превью больше не использует. ИИ-провайдер —.env(LLM_BASE_URL,LLM_MODEL; запасной вариант — Ollamaqwen3-vl:8b). - Оценка распознавания скриншотов — eval/README.md.
Если в вашем config.yaml явно перечислены зеркала hitmoz.domains, добавьте ru.hitmox.net после
ru.hitmoz.org, чтобы использовать его при недоступности основного адреса.
Сервис embedder (звуковые векторы для миксов)
Нейросеть MuQ-MuLan-large (embedder/, контейнер homemusic_embedder) превращает фрагмент песни (30 с из середины
файла библиотеки или превью Deezer) и текст в векторы одного пространства; Хаб сравнивает по ним кандидатов миксов
и «Моей волны» со вкусом пользователя. API — hub/API.md.
- Запуск:
docker compose up -d --build embedder, затемdocker image prune -f(прежний образ — ~7 ГБ — иначе остаётся без имени). Порт наружу не открыт: Хаб ходит вhttp://embedder:8090(EMBEDDER_URLв.env;EMBEDDER_TOKEN— по желанию, тот же в обоих сервисах). - Первая загрузка весов: при первом задании (или
POST /text) сервис скачивает с Hugging Face ~2,5 ГБ в томhomemusic_embedder-models— это несколько минут; дальше модель грузится из тома прямо на видеокарту за ~12 с (пик RAM контейнера ~3,4 ГБ, лимитmem_limit: 4g). Без работы 5 минут модель выгружается из памяти (EMBEDDER_IDLE_UNLOAD_S) — видеокарта общая. Веса подмоделей (models--OpenMuQ--MuQ-large-msd-iter,models--xlm-roberta-base, ~2,4 ГБ) сервису больше не нужны — из них берутся только конфиги и токенизатор. - Видеокарта: нужен Docker Desktop с WSL2 и драйвер NVIDIA на Windows. Проверка, что GPU виден в контейнере:
Если на видеокарте меньше 5 ГБ свободно (поdocker run --rm --gpus all nvidia/cuda:12.8.1-base-ubuntu24.04 nvidia-sminvidia-smi,EMBEDDER_MIN_FREE_GPU_MB), модель не грузится и ждёт карту: в/healthz—state: errorсGpuBusy, очередь стоит, попытка раз в минуту; Хаб тем временем строит миксы и волну без звука. Принудительно —EMBEDDER_DEVICE=cuda|cpu; на CPU модели нужно ~7 ГБ RAM (≈ 5 с на песню) — сначала поднятьmem_limit/memswap_limitвdocker-compose.yml. - Состояние:
docker exec homemusic_embedder python -c "import urllib.request as u; print(u.urlopen('http://localhost:8090/healthz').read().decode())"— модель, где она (device), очередь, скорость последней пачки. Замер на файлах библиотеки:docker compose run --rm embedder python -m app.bench --songs 64. - Тесты — в образе сервиса (модель в них — фейк):
С настоящей моделью (медленно): добавитьdocker run --rm -v "${PWD}\embedder:/src" -w /src homemusic-embedder:local bash -c "uv sync -q --frozen --extra gpu && uv run --no-sync pytest -q -p no:cacheprovider -n 6"--gpus all -v homemusic_embedder-models:/models -e EMBEDDER_REAL_MODEL_TESTS=1и-m gpuк pytest.
Разработка
cd hub
uv sync
uv run pytest -q # тесты; тестам с ffmpeg нужен Docker (см. ниже)
uv run uvicorn app.main:app --reload --port 8080
cd ..\web
npm install
npm run dev # PWA на :5173 с прокси на :8080
Тесты, которым нужен ffmpeg (теги, проверка файлов, полный сценарий), — в образе Хаба (docker compose build hub, затем docker image prune -f), из cmd:
docker run --rm -v homemusic-test-venv:/tmp/venv -e UV_PROJECT_ENVIRONMENT=/tmp/venv -e UV_LINK_MODE=copy -e PYTHONDONTWRITEBYTECODE= -v "%cd%\hub:/src" -w /src homemusic-hub:local bash -c "uv sync -q --frozen && uv run --no-sync pytest -q -p no:cacheprovider -n 6"
Откат шлюза на VPS
ssh myvps
systemctl disable --now homemusic-tunnel-watchdog homemusic-guard-firewall
rm -f /etc/systemd/system/homemusic-tunnel-watchdog.service /etc/systemd/system/homemusic-guard-firewall.service /usr/local/sbin/homemusic-tunnel-watchdog
rm -rf /etc/homemusic; nft delete table inet homemusic_guard 2>/dev/null; systemctl daemon-reload
rm -f /etc/ssh/sshd_config.d/61-homemusic-tunnel.conf && sshd -t && systemctl reload ssh
pkill -u homemusic-tunnel; userdel -r homemusic-tunnel
# в /etc/caddy/Caddyfile удалить блок между "# >>> homemusic" и "# <<< homemusic", затем: systemctl reload caddy