automatically sync your local library with your music on Tidal
  • Go 82%
  • Nix 16.5%
  • Dockerfile 1%
  • Makefile 0.5%
Find a file
github-actions[bot] 855892009b
Merge pull request #10 from labi-le/dependabot/go_modules/golang.org/x/text-0.41.0
build(deps): bump golang.org/x/text from 0.40.0 to 0.41.0
2026-08-15 17:12:48 +00:00
.github ci: fix the automerge gate, drop the --auto race 2026-08-07 19:50:11 +03:00
benchmarks add benchmarks 2026-07-02 13:12:43 +03:00
cmd feat: expose Prometheus metrics endpoint with Grafana dashboard 2026-07-18 03:11:14 +03:00
deploy feat(nix): package the daemon and ship a native NixOS service module 2026-08-08 00:48:26 +03:00
docs Initial commit 2026-06-27 23:33:03 +03:00
internal feat: expose Prometheus metrics endpoint with Grafana dashboard 2026-07-18 03:11:14 +03:00
pkg/tidal fix: resolve architecture review findings across sync, download, store, cli 2026-07-18 02:01:14 +03:00
.dockerignore refactor 2026-06-28 15:51:43 +03:00
.gitignore add override config 2026-07-01 19:31:07 +03:00
.golangci.yml refactor 2026-06-28 22:25:34 +03:00
.goreleaser.yaml release: drop windows, internal/lock is unix-only 2026-08-07 19:34:56 +03:00
AGENTS.md map container to host uid/gid via PUID/PGID, add up/down/ps/logs/login targets 2026-06-30 16:19:07 +03:00
config.example.yaml feat: expose Prometheus metrics endpoint with Grafana dashboard 2026-07-18 03:11:14 +03:00
docker-compose.override.yml.example add override config 2026-07-01 19:31:07 +03:00
docker-compose.yml feat: expose Prometheus metrics endpoint with Grafana dashboard 2026-07-18 03:11:14 +03:00
Dockerfile feat: expose Prometheus metrics endpoint with Grafana dashboard 2026-07-18 03:11:14 +03:00
flake.nix feat(nix): package the daemon and ship a native NixOS service module 2026-08-08 00:48:26 +03:00
go.mod build(deps): bump golang.org/x/text from 0.40.0 to 0.41.0 2026-08-15 17:12:37 +00:00
go.sum build(deps): bump golang.org/x/text from 0.40.0 to 0.41.0 2026-08-15 17:12:37 +00:00
LICENSE Initial commit 2026-06-27 23:33:03 +03:00
Makefile feat(nix): package the daemon and ship a native NixOS service module 2026-08-08 00:48:26 +03:00
README.md feat: expose Prometheus metrics endpoint with Grafana dashboard 2026-07-18 03:11:14 +03:00
shell.nix add benchmarks 2026-07-02 13:12:43 +03:00

tidal-syncer

Sync your TIDAL library to local storage.

Quickstart (Docker)

Create your config file from the example and fill in your TIDAL credentials before the first run:

cp config.example.yaml config.yaml
# edit config.yaml: set tidal_auth.client_id and tidal_auth.client_secret
docker compose run --rm tidal-syncer login

The ./config.yaml:/app/config.yaml bind mount requires config.yaml to already exist as a real file. If it is missing, Docker auto-creates an empty directory at that path and startup fails. The image ships with no built-in credentials: loading the config fails fast unless tidal_auth.client_id and tidal_auth.client_secret are set in config.yaml.

Dependencies

  • Go 1.26+
  • ffmpeg (audio processing)

Install

make build

Usage

Usage:
  tidal-syncer [command]

Available Commands:
  daemon      Run tidal-syncer as a background daemon
  health      Check health of the tidal-syncer service
  login       Authenticate with TIDAL
  sync        Sync TIDAL library to local storage
  version     Print build version information

Flags:
      --config string   path to config file (default "/app/config.yaml")
      --verbose         verbose mode (Trace level + caller)

Examples

# Authenticate
tidal-syncer login

# One-off sync
tidal-syncer sync --config ./config.yaml

# Run as daemon
tidal-syncer daemon

Metrics (Prometheus & Grafana)

The daemon can expose a Prometheus /metrics endpoint with live library and sync statistics — track counts by status and quality, genre distribution, favorites, distinct artists/albums, plus per-cycle counters (cycles, failures by class, duration, last success) and the standard go_* / process_* runtime metrics. Every app metric is namespaced tidal_syncer_*.

Enable it in config.yaml (off by default):

metrics:
  enabled: true
  address: ":9101"

docker-compose.yml publishes the endpoint on 127.0.0.1:9101 (loopback), so a Prometheus already running on the host can scrape it:

# prometheus.yml
scrape_configs:
  - job_name: tidal-syncer
    static_configs:
      - targets: ["127.0.0.1:9101"]

NixOS

For a host managed with NixOS that already runs Prometheus + Grafana, this repo is a flake exposing a monitoring module that adds the scrape job and provisions the Grafana dashboard (deploy/grafana/tidal-syncer.json, which picks its Prometheus datasource via a template variable):

# flake.nix
inputs.tidal-syncer.url = "github:labi-le/tidal-syncer";

# configuration.nix
imports = [ inputs.tidal-syncer.nixosModules.monitoring ];

The dashboard JSON under deploy/grafana/ can also be imported into any Grafana instance by hand.

Querying the library (SQLite)

Sync state lives in data/tidal-syncer.db. To list your most recently favorited tracks with artist, album and genre:

WITH joined AS (
  SELECT f.name AS title, f.added_at, t.genre,
         substr(t.path, length('/app/Music/') + 1) AS rel
  FROM favorites_snapshot f
  JOIN tracks t ON t.tidal_id = f.tidal_id
  WHERE f.kind = 'tracks' AND f.added_at <> '' AND t.path <> ''
),
split AS (
  SELECT title, added_at, genre,
         substr(rel, 1, instr(rel, '/') - 1) AS artist,
         substr(rel, instr(rel, '/') + 1) AS after_artist
  FROM joined
)
SELECT artist,
       substr(after_artist, 1, instr(after_artist, '/') - 1) AS album,
       title, genre, added_at
FROM split
ORDER BY added_at DESC
LIMIT 300;

Run it with:

nix-shell --run "sqlite3 -header -column data/tidal-syncer.db"
# then paste the query, or pass it inline with -cmd
  • artist and album are parsed from tracks.path (/app/Music/{albumartist}/{album}/{NN} - {title}.flac).
  • genre is a ;-joined list — filter with e.g. WHERE genre LIKE '%Metal%'.
  • added_at is the favorite-add date; use ORDER BY t.updated_at DESC for download order instead. The join skips failed tracks (they have no file).