Navidrome Scan

This role triggers music library scans in Navidrome using the Subsonic API.

ARA Ansible Bash Docker HTTPS JSON Navidrome Python

Navidrome Scan Role

Overview

This role triggers music library scans in Navidrome using the Subsonic API. It generates secure authentication tokens using MD5 hashing with random salt, calls the Navidrome startScan API endpoint, supports both incremental and full scan modes, and displays scan status and statistics (tracks found, folders scanned) after triggering the scan.

Purpose

  • Automated Library Scans: Trigger Navidrome scans via Ansible
  • Integration with Workflows: Scan after adding new music files
  • Incremental Scans: Detect new or modified files only
  • Full Scans: Re-scan entire library from scratch
  • Subsonic API: Uses standard Subsonic authentication protocol
  • Status Reporting: Display scan progress and statistics
  • Idempotent: Safe to run multiple times (starts new scan each time)

Requirements

  • Ansible 2.9 or higher
  • Navidrome server running and accessible via HTTPS
  • Navidrome user credentials stored in Ansible Vault
  • Network connectivity to Navidrome server
  • Navidrome configured with music library paths

What is Navidrome?

Navidrome is a self-hosted music streaming server that:

  • Organizes and streams your music collection
  • Implements the Subsonic API (compatible with many music apps)
  • Supports multiple audio formats (MP3, FLAC, OGG, etc.)
  • Provides web UI and mobile apps
  • Offers playlist management and scrobbling
  • Requires periodic library scans to detect new music

Role Variables

Required Variables

VariableRequiredDescription
vault_navidrome_joffrey_userYesNavidrome username (in vault)
vault_navidrome_joffrey_passwordYesNavidrome password (in vault)

Optional Variables

VariableDefaultDescription
navidrome_scan_urlhttps://server.homelabNavidrome server URL
navidrome_scan_fullfalseFull scan (true) or incremental (false)

Variable Details

vault_navidrome_joffrey_user and vault_navidrome_joffrey_password

Navidrome login credentials. Must be stored in Ansible Vault.

Example vault variables:

vault_navidrome_joffrey_user: "joffrey"
vault_navidrome_joffrey_password: "YourSecurePassword123"

The base URL to your Navidrome server.

Default:

navidrome_scan_url: "https://server.homelab"

Custom example:

navidrome_scan_url: "https://music.example.com"

Controls scan type: incremental or full.

Values:

  • false (default): Incremental scan (only new/modified files)
  • true: Full scan (re-scan entire library)

Incremental scan (default):

navidrome_scan_full: false

Full scan:

navidrome_scan_full: true

When to use full scan:

  • After major library reorganization
  • When metadata changes aren’t detected
  • Database corruption recovery
  • First scan of new library
  • Periodic maintenance (monthly)

When to use incremental scan:

  • After adding new music files
  • Daily/weekly automated scans
  • Quick updates
  • Minimal library changes

Dependencies

This role has no dependencies on other Ansible roles, but requires:

  • Navidrome server installed and running
  • Valid user account in Navidrome
  • Music library configured in Navidrome
  • Network access to Navidrome server

Example Playbook

Basic Usage (Incremental Scan)

---
- name: Trigger Navidrome Library Scan
  hosts: localhost
  become: false

  roles:
    - navidrome_scan

Full Library Scan

---
- name: Trigger Full Navidrome Library Scan
  hosts: localhost
  become: false

  vars:
    navidrome_scan_full: true

  roles:
    - navidrome_scan

Scan After Adding Music

---
- name: Copy Music and Scan Library
  hosts: media_server
  become: true

  tasks:
    - name: Copy new music files
      ansible.builtin.copy:
        src: /source/music/
        dest: /mnt/music/library/
        owner: music
        group: music

    - name: Wait for files to settle
      ansible.builtin.pause:
        seconds: 5

- name: Scan Navidrome Library
  hosts: localhost
  become: false

  roles:
    - navidrome_scan

Scheduled Weekly Scan

---
- name: Schedule Weekly Navidrome Scan
  hosts: localhost
  become: false

  tasks:
    - name: Create cron job for weekly library scan
      ansible.builtin.cron:
        name: "Weekly Navidrome Library Scan"
        minute: "0"
        hour: "3"
        weekday: "0"  # Sunday
        job: "ansible-playbook /path/to/navidrome_scan.yml"

What This Role Does

  1. Generates random salt (6-character hexadecimal)

    • Used for Subsonic API authentication
    • Prevents replay attacks
  2. Calculates MD5 token

    • Formula: MD5(password + salt)
    • Subsonic API authentication method
  3. Calls Subsonic API startScan endpoint:

    • URL: /rest/startScan
    • Method: GET
    • Authentication: Token + salt
    • Parameters: username, token, salt, format (json), version (1.8.0), client, fullScan
  4. Displays scan status:

    • Scanning status (true/false)
    • Scan type (incremental/full)
  5. Displays statistics:

    • Number of tracks found
    • Number of folders scanned

Subsonic API Authentication

Authentication Method

Navidrome implements the Subsonic API authentication protocol:

Traditional (not used by this role):

?u=username&p=password

Token-based (used by this role):

?u=username&t=token&s=salt

Where:

  • username: Navidrome username
  • token: MD5 hash of (password + salt)
  • salt: Random string

Why Token Authentication?

Security benefits:

  • Password never sent in clear text
  • Salt prevents replay attacks
  • Each request uses unique token
  • More secure than plain password in URL

Token Generation

# Pseudocode
salt = random_hex(6)  # e.g., "a1b2c3"
token = md5(password + salt)  # e.g., md5("MyPassword123a1b2c3")

# Final URL parameter:
# &t=d41d8cd98f00b204e9800998ecf8427e&s=a1b2c3

Subsonic API Endpoint

startScan Endpoint

GET /rest/startScan

Parameters:

ParameterValueDescription
uusernameNavidrome username
tMD5 hashAuthentication token
shex stringRandom salt
fjsonResponse format
v1.8.0Subsonic API version
cAnsibleScanClient name
fullScantrue/falseFull or incremental scan

Example URL:

https://server.homelab/rest/startScan?u=joffrey&t=abc123...&s=def456&f=json&v=1.8.0&c=AnsibleScan&fullScan=false

Response (200 OK):

{
  "subsonic-response": {
    "status": "ok",
    "version": "1.16.1",
    "type": "navidrome",
    "serverVersion": "0.49.3",
    "scanStatus": {
      "scanning": true,
      "count": 1234,
      "folderCount": 56,
      "lastScan": "2026-01-07T14:30:22Z",
      "scanType": "incremental"
    }
  }
}

Scan Types

Incremental Scan (Default)

What it does:

  • Scans only new or modified files
  • Checks file modification timestamps
  • Fast (seconds to minutes)
  • Doesn’t re-read existing files

Use cases:

  • Added new albums
  • Regular scheduled scans
  • Quick library updates
  • Daily/weekly automation

Performance:

  • Small library (100 albums): ~5-10 seconds
  • Medium library (1000 albums): ~30-60 seconds
  • Large library (10000+ albums): ~2-5 minutes

Full Scan

What it does:

  • Re-scans entire music library
  • Re-reads all file metadata
  • Updates all database entries
  • Slow (minutes to hours depending on library size)

Use cases:

  • Library reorganization
  • Fixing metadata issues
  • Database recovery
  • Periodic maintenance
  • Initial library setup

Performance:

  • Small library (100 albums): ~1-2 minutes
  • Medium library (1000 albums): ~10-20 minutes
  • Large library (10000+ albums): ~1-2 hours

Scan Status Information

Scan Response Fields

FieldDescription
scanningBoolean, true if scan in progress
countNumber of tracks found/scanned
folderCountNumber of folders scanned
lastScanTimestamp of last completed scan
scanType”incremental” or “full”

Example Output

TASK [Display scan response]
ok: [localhost] => {
    "msg": "Library scan triggered successfully. Scanning: true, Type: incremental"
}

TASK [Display scan statistics]
ok: [localhost] => {
    "msg": [
        "Tracks found: 1234",
        "Folders: 56"
    ]
}

Monitoring Scan Progress

Via Navidrome Web UI

  1. Log into Navidrome
  2. Go to Settings → Activity
  3. View scan progress in real-time

Via API (Manual Check)

# Get scan status
curl -X GET \
  "https://server.homelab/rest/getScanStatus?u=username&t=token&s=salt&f=json&v=1.8.0&c=curl"

# Response shows current scan status

Via Navidrome Logs

# Docker logs
docker logs navidrome -f

# Look for:
# [INFO] Starting library scan...
# [INFO] Scanned 1234 tracks in 56 folders
# [INFO] Scan completed in 45 seconds

Scan Behavior

Multiple Scans

  • Only one scan can run at a time
  • Starting a new scan while one is running has no effect
  • Wait for current scan to complete before starting another

Automatic Scans

Navidrome can be configured to scan automatically:

In Navidrome config:

# Auto-scan on startup
ScanSchedule: "@every 1h"  # Scan every hour

# Or disable auto-scan (manual only)
ScanSchedule: ""

This role triggers manual scans regardless of auto-scan settings.

Scan Triggers

Scans are typically triggered when:

  • New music files added
  • Files modified or deleted
  • Metadata updated
  • Playlists changed
  • Manual trigger (this role)
  • Scheduled auto-scan (Navidrome config)

Security Considerations

  • Credentials Protected: Username/password in Ansible Vault
  • Token Authentication: Password never in URL
  • Random Salt: Prevents replay attacks
  • HTTPS Required: Uses SSL/TLS for API calls
  • No Logging: Credentials not logged by role
  • Validate Certs: SSL certificate validation enabled

Tags

This role does not define any tags. Use playbook-level tags if needed:

- hosts: localhost
  roles:
    - navidrome_scan
  tags:
    - navidrome
    - scan
    - music
    - library

Notes

  • Role runs on localhost (API calls don’t require target host)
  • become: false recommended (no root needed for API calls)
  • Scan is asynchronous (role triggers, doesn’t wait for completion)
  • Multiple concurrent scans not supported by Navidrome
  • Salt is randomly generated for each run (6 hex characters)
  • API version 1.8.0 is standard Subsonic compatibility version
  • Client name set to “AnsibleScan” for identification

Troubleshooting

”Authentication failed” error

Cause: Invalid credentials or incorrect token

Solution:

# Verify credentials in vault
ansible-vault view group_vars/all/vault.yml

# Test login via Navidrome web UI
# Open: https://server.homelab

# Test API manually
curl -X GET \
  "https://server.homelab/rest/ping?u=username&p=enc:password&f=json&v=1.8.0&c=curl"

“Connection refused” errors

Cause: Navidrome not running or URL incorrect

Solution:

# Test Navidrome accessible
curl -I https://server.homelab

# Check Navidrome running
docker ps | grep navidrome

# Check Navidrome logs
docker logs navidrome

Scan shows “scanning: false” immediately

Cause: Scan already in progress or no changes detected

Solution:

  • Wait for current scan to complete
  • If incremental scan finds no changes, this is normal
  • Try full scan: navidrome_scan_full: true

No tracks found (count: 0)

Cause: Music library empty or path incorrect

Solution:

# Check Navidrome config
docker exec navidrome cat /data/navidrome.toml

# Look for MusicFolder setting
# Verify path exists and contains music files

# Check file permissions
ls -la /path/to/music/library/

SSL certificate errors

Cause: Self-signed certificate or CA not trusted

Solution: Set validate_certs: false (not recommended):

# In tasks/main.yml, modify uri task:
validate_certs: false

Or install CA certificate on Ansible control node.

Scan takes very long time

Normal for large libraries:

  • 10,000+ tracks: 30+ minutes for full scan
  • Check Navidrome logs for progress
  • Consider incremental scans for regular updates

Testing the Role

Verify Scan Triggered

Check role output:

TASK [Display scan response]
ok: [localhost] => {
    "msg": "Library scan triggered successfully. Scanning: true, Type: incremental"
}

Check Navidrome UI:

  1. Log into Navidrome
  2. Go to Settings → Activity
  3. Should show “Scanning library…”

Check via API:

# Get scan status
curl "https://server.homelab/rest/getScanStatus?u=username&p=enc:password&f=json&v=1.8.0&c=curl"

Verify New Music Detected

After scan completes:

  1. Check Navidrome for new albums
  2. Search for newly added artists
  3. Verify track count increased

Performance Considerations

  • Network Latency: API call is fast (~1 second)
  • Scan Duration: Depends on library size and scan type
  • CPU Usage: Navidrome uses CPU during scan (especially full scan)
  • Disk I/O: Reading file metadata uses disk I/O
  • No Impact: Role execution doesn’t affect scan performance

Best Practices

  1. Use incremental scans for regular updates (default)
  2. Schedule scans after adding music (not during playback)
  3. Full scans monthly for maintenance
  4. Monitor scan progress in Navidrome UI
  5. Store credentials in vault (never plaintext)
  6. Test with small library before large deployments
  7. Avoid concurrent scans (wait for completion)
  8. Check Navidrome logs for scan errors
  9. Document scan schedule (daily, weekly, etc.)
  10. Combine with music copy tasks (copy, then scan)

Integration Examples

Scan After Music Copy

---
- name: Add Music and Scan
  hosts: media_server
  become: true

  tasks:
    - name: Sync music from source
      ansible.posix.synchronize:
        src: /source/music/
        dest: /mnt/music/library/
        delete: false

- name: Trigger Navidrome Scan
  hosts: localhost
  become: false

  roles:
    - navidrome_scan

Scan After Metadata Update

---
- name: Update Music Metadata
  hosts: media_server
  become: true

  tasks:
    - name: Run beets import
      ansible.builtin.command:
        cmd: beet import -q /mnt/music/new/
        chdir: /home/music

- name: Scan Updated Library
  hosts: localhost
  become: false

  vars:
    navidrome_scan_full: true  # Full scan after metadata changes

  roles:
    - navidrome_scan

This role is often used with:

  • navidrome_playlists_backup: Backup playlists after scan
  • Music management roles: Copy/organize music files
  • Monitoring roles: Alert on scan failures

Alternative: Manual Scan

If you prefer manual scanning:

Via Navidrome UI:

  1. Log into Navidrome
  2. Go to Settings → Activity
  3. Click “Scan library now”

Via API (curl):

# Generate token
PASSWORD="your_password"
SALT=$(openssl rand -hex 3)
TOKEN=$(echo -n "${PASSWORD}${SALT}" | md5sum | cut -d' ' -f1)

# Trigger scan
curl -X GET \
  "https://server.homelab/rest/startScan?u=username&t=$TOKEN&s=$SALT&f=json&v=1.8.0&c=curl&fullScan=false"

License

MIT

Author

Created for homelab infrastructure management.