Updated workflow to: - Use Gitea token for repository checkout authentication - Use Gitea repository owner as username for registry login - Use PACKAGES_TOKEN secret for registry authentication - Follow proven pattern from working repository - Only build and push on main/master branches and version tags - Added proper job dependencies (test → build → docker)
NaviWatcher
English
NaviWatcher is an autonomous service daemon that monitors your Navidrome music collection, compares it against artist discographies from MusicBrainz, and notifies you about missing releases via Telegram and a built-in web interface.
How It Works
- Scans your Navidrome library via Subsonic API to get the list of artists and albums.
- Fetches full artist discographies from MusicBrainz (using Release Groups to avoid duplicate editions).
- Compares local collection with external data using fuzzy matching (configurable threshold, default 0.85).
- Syncs automatically on a periodic loop (
sync.interval, default 6h): Navidrome artist/album pull → lazy MusicBrainz ID resolution → discography cache → re-scan, so the dashboard and digests always reflect current state. - Notifies you about missing albums/singles/EPs through daily Telegram digests and a web dashboard.
How It Works Now
On startup NaviWatcher opens (or auto-migrates) a local SQLite database at naviwatcher.db in the working directory, performs one immediate sync+scan, then runs a ticker-driven periodic sync+scan goroutine until SIGTERM. The Web UI serves a basic-auth-protected dashboard; the Notifier runs a cron scheduler emitting a daily digest of newly-found missing releases (tracked via notifications_sent). The MusicBrainz ID for each artist is resolved lazily on first sync and cached on the artist_settings row.
Features
- Subsonic API compatible — works with Navidrome, Airsonic, Ampache, and other Subsonic-compatible servers.
- Fuzzy matching — smart string normalization (ignores remastered/deluxe/anniversary editions, year suffixes, special characters).
- Per-artist filters — opt out of Singles and Compilations per artist (via
artist_settings); type filtering includes only Album/Single/EP primary types (plus release groups whose secondary types include Single/EP/Compilation). - MusicBrainz caching — 24-hour TTL cache to minimize API calls and respect rate limits (1 req/sec).
- Telegram notifications — daily summary messages with links to the web UI, sent on the configured
telegram.cron_schedule. - Web dashboard — browse missing albums, ignore releases, manage artist-specific settings, with an archive of ignored releases.
- Single binary deployment — all HTML templates embedded via
//go:embed. - Docker support — ready for
docker composedeployment.
Technology Stack
- Language: Go 1.25+
- Database: SQLite 3
- HTTP Server: Go standard library (
net/http+html/template) - Key dependencies:
github.com/mattn/go-sqlite3— SQLite drivergithub.com/lithammer/fuzzysearch— fuzzy string matchinggopkg.in/yaml.v3— configuration parsing
Quick Start
-
Create a service user in Navidrome:
- Go to Settings → Users → Add User
- Set a username and password
- Ensure the user has access to your music libraries
-
Configure NaviWatcher:
cp config.yaml.example config.yaml # Edit config.yaml with your settings -
Build and run:
go build -o naviwatcher ./naviwatcher -
Or use Docker:
docker compose up -d -
Access the web UI at
http://localhost:8080
Note (current status): On startup the service opens a local SQLite database at
naviwatcher.dbin the working directory (existing databases are auto-migrated), performs one immediate sync+scan, then runs a periodic sync+scan loop (sync.interval, default 6h) until shutdown.musicbrainz.user_agentis required and validated at startup. The Web UI (basic-auth protected) and Telegram notifier (cron-scheduled) are wired in; settelegram.enabledto activate digests.
Configuration
server:
host: "0.0.0.0"
port: 8080
username: "admin"
password: "CHANGE_ME"
# Externally-reachable base URL for links in Telegram digests (e.g. behind a
# reverse proxy). If omitted, links are derived from host:port — but when host
# is 0.0.0.0 (the unspecified bind address) no link is emitted, since it is
# not reachable from outside the host.
public_url: "https://naviwatcher.example.com"
navidrome:
url: "http://localhost:4533"
user: "watcher_service"
password: "CHANGE_ME"
musicbrainz:
user_agent: "NaviWatcher/1.0 ( mail@example.com )"
cache_ttl: 24h
telegram:
enabled: true
token: "bot_token"
chat_id: "your_chat_id"
cron_schedule: "0 10 * * *"
scanner:
fuzzy_threshold: 0.85
# Periodic sync+scan loop frequency (Go duration, e.g. "6h", "30m"); defaults to 6h.
sync:
interval: 6h
See docs/Specification.md for the full configuration reference and architecture details.
Architecture
| Module | Purpose |
|---|---|
| Navidrome Client | Subsonic API v1.16.1 communication (token-based auth) |
| MusicBrainz Provider | Discography fetching with rate limiting and caching |
| Scanner Engine | String normalization and fuzzy comparison |
| Database Layer | SQLite persistence: artist_settings, local_albums (Navidrome sync), external_releases (MusicBrainz cache), notifications_sent |
| Notifier | Scheduled Telegram notifications |
| Web UI | Dashboard for browsing and managing missing releases |
Implementation Status
- Scanner Engine — implemented. The missing-release detection core is complete: string normalization lives in
internal/normalize, similarity scoring and the diff engine (FindMissingReleases,ScanArtist,ScanAll) ininternal/scanner. It uses the configurablescanner.fuzzy_threshold(default 0.85), normalizes titles (ignoring(Remastered)/year/special-char variants), and skips releases marked ignored. - Sync pipeline — implemented.
SyncAllpulls artists/albums from Navidrome into the DB, resolves each artist's MusicBrainz ID lazily (cached onartist_settings.mbid), syncs the MusicBrainz discography, then re-runs the scanner. Wired intomain.run()as an immediate + periodic (sync.interval) loop. - Notifier — implemented. A
Senderinterface with a Telegram implementation,FormatDigestfor daily summaries, and a cron scheduler honoringtelegram.cron_schedule(no-op whenenabled=false); sent-tracking vianotifications_sent. - Web UI — implemented. Basic-auth-protected
net/httpserver with//go:embedtemplates: dashboard with missing-release counts, artist detail with ignore actions, and an archive of ignored releases with restore.
License
WTFPL — Do What The Fuck You Want To Public License
Русский
NaviWatcher — это автономный сервис-демон для мониторинга музыкальной коллекции в Navidrome. Он сравнивает вашу медиатеку с полными дискографиями артистов из MusicBrainz и уведомляет об отсутствующих релизах через Telegram и веб-интерфейс.
Как это работает
- Сканирует библиотеку Navidrome через Subsonic API — получает список артистов и альбомов.
- Загружает полные дискографии артистов из MusicBrainz (использует Release Groups, чтобы избежать дубликатов изданий).
- Сравнивает локальную коллекцию с внешними данными через нечёткое сравнение строк (настраиваемый порог, по умолчанию 0.85).
- Синхронизируется автоматически по периодическому циклу (
sync.interval, по умолчанию 6h): выгрузка артистов/альбомов из Navidrome → ленивое разрешение MusicBrainz ID → кэш дискографии → повторное сканирование, чтобы панель и дайджесты всегда отражали текущее состояние. - Уведомляет об отсутствующих альбомах/синглах/EP через ежедневные дайджесты в Telegram и веб-панель.
Как это работает сейчас
При запуске NaviWatcher открывает (или автомигрирует) локальную SQLite-БД naviwatcher.db в рабочей директории, выполняет одну немедленную синхронизацию+сканирование, затем запускает управляемый тикером периодический цикл до получения SIGTERM. Веб-интерфейс — это панель под basic-auth; нотификатор запускает cron-планировщик, отправляющий ежедневный дайджест новых отсутствующих релизов (отслеживается через notifications_sent). MusicBrainz ID каждого артиста разрешается лениво при первой синхронизации и кэшируется в строке artist_settings.
Возможности
- Совместим с Subsonic API — работает с Navidrome, Airsonic, Ampache и другими Subsonic-совместимыми серверами.
- Нечёткое сравнение — умная нормализация строк (игнорирует ремастеры, deluxe/anniversary-издания, год в скобках, спецсимволы).
- Фильтры по артистам — отключение синглов и компиляций для конкретного артиста (через
artist_settings); фильтрация по типам включает только основные типы Album/Single/EP (а также группы релизов, чьи вторичные типы содержат Single/EP/Compilation). - Кэширование MusicBrainz — TTL 24 часа для минимизации запросов и соблюдения лимитов (1 запрос/сек).
- Уведомления в Telegram — ежедневные сводки со ссылками на веб-интерфейс, отправляемые по расписанию
telegram.cron_schedule. - Веб-панель — просмотр отсутствующих альбомов, игнорирование релизов, управление настройками артистов, архив проигнорированных релизов.
- Один бинарный файл — все HTML-шаблоны встроены через
//go:embed. - Поддержка Docker — готов к развёртыванию через
docker compose.
Технологический стек
- Язык: Go 1.21+
- База данных: SQLite 3
- HTTP-сервер: стандартная библиотека Go (
net/http+html/template) - Ключевые зависимости:
github.com/mattn/go-sqlite3— драйвер SQLitegithub.com/lithammer/fuzzysearch— нечёткое сравнение строкgopkg.in/yaml.v3— парсинг конфигурации
Быстрый старт
-
Создайте сервисного пользователя в Navidrome:
- Перейдите в Settings → Users → Add User
- Задайте имя пользователя и пароль
- Убедитесь, что у пользователя есть доступ к медиатекам
-
Настройте NaviWatcher:
cp config.yaml.example config.yaml # Отредактируйте config.yaml -
Соберите и запустите:
go build -o naviwatcher ./naviwatcher -
Или используйте Docker:
docker compose up -d -
Откройте веб-интерфейс по адресу
http://localhost:8080
Конфигурация
server:
host: "0.0.0.0"
port: 8080
username: "admin"
password: "CHANGE_ME"
# Внешний базовый URL для ссылок в дайджестах Telegram (напр. за обратным прокси).
# Если не задан, ссылки строятся из host:port — но при host 0.0.0.0 (несpecificированный
# адрес привязки) ссылка не генерируется, так как недоступна снаружи хоста.
public_url: "https://naviwatcher.example.com"
navidrome:
url: "http://localhost:4533"
user: "watcher_service"
password: "CHANGE_ME"
musicbrainz:
user_agent: "NaviWatcher/1.0 ( mail@example.com )"
cache_ttl: 24h
telegram:
enabled: true
token: "bot_token"
chat_id: "your_chat_id"
cron_schedule: "0 10 * * *"
scanner:
fuzzy_threshold: 0.85
# Частота периодического цикла синхронизации+сканирования (длительность Go, напр. "6h", "30m"); по умолчанию 6h.
sync:
interval: 6h
Полную справку по конфигурации и архитектуру см. в docs/Specification.md.
Архитектура
| Модуль | Назначение |
|---|---|
| Navidrome Client | Взаимодействие с Subsonic API v1.16.1 (токенная аутентификация) |
| MusicBrainz Provider | Загрузка дискографий с кэшированием и rate limiting |
| Scanner Engine | Нормализация строк и нечёткое сравнение |
| Database Layer | SQLite: artist_settings, local_albums (синхронизация из Navidrome), external_releases (кэш MusicBrainz), notifications_sent |
| Notifier | Планировщик уведомлений в Telegram |
| Web UI | Панель управления отсутствющими релизами |
Статус реализации
- Scanner Engine — реализован. Ядро поиска отсутствующих релизов готово: нормализация строк в
internal/normalize, оценка схожести и движок сравнения (FindMissingReleases,ScanArtist,ScanAll) вinternal/scanner. Используется настраиваемыйscanner.fuzzy_threshold(по умолчанию 0.85), игнорируются варианты(Remastered)/год/спецсимволы, пропускаются отмеченные как игнорируемые. - Sync pipeline — реализован.
SyncAllвыгружает артистов/альбомы из Navidrome в БД, лениво разрешает MusicBrainz ID каждого артиста (кэшируется вartist_settings.mbid), синхронизирует дискографию MusicBrainz, затем повторно запускает сканер. Подключён вmain.run()как немедленный + периодический (sync.interval) цикл. - Notifier — реализован. Интерфейс
Senderс Telegram-реализацией,FormatDigestдля ежедневных сводок и cron-планировщик поtelegram.cron_schedule(no-op приenabled=false); отслеживание отправок черезnotifications_sent. - Web UI — реализован. Защищённый basic-auth
net/httpсервер с//go:embedшаблонами: панель со счётчиками отсутствующих релизов, страница артиста с действиями игнорирования и архив проигнорированных релизов с восстановлением.
Лицензия
WTFPL — Do What The Fuck You Want To Public License