Bongo Twisty

Enough is Plenty

Setting Up a Personal Music Server

A few months ago I set my music free with an instance of the Navidrome Music Streaming Server on Pikapod. It was very easy. I was up and running in no time.

There was an old laptop that I could have used as a server but was put off by the thought of wrangling network settings. I had my fill of that with a Nextcloud instance on a Raspberry Pie some years ago.

Pikapod provides a great service at a good price and profit shares with the developers of the open source apps it hosts. All good. I was happy with the service.

Some time after I learned about Tailscale. It basically removes the pain of configuring a secure network to facilitate remote access to a local server. For my use case it’s free. What’s not to like?

What with an old unused laptop lying around, knowing I could do this and with a bit of time on my hands I decided to go for it. Guided by an LLM it was all quite straight forward. As I am writing this post the process of syncing several hundred GBs of music files to the local server is completing in the background. All working as expected. Streaming on local and remote networks.

Happy days. I’m now one of the increasing number of people whose hosting their own music streaming service.

Here’s a few details -


Server

  • Model: Lenovo IdeaPad Flex 15D
  • CPU: Intel Atom x7-Z8750 (4 cores, 1.6-2.56 GHz)
  • RAM: 4GB
  • OS: Ubuntu Server 24.04 LTS

CPU Constraints

  • No real-time transcoding - CPU will struggle with FLAC→MP3 conversion
  • Solution: Sync only MP3 files, pre-convert lossless formats
  • Limited concurrent streams - Max 2-3 simultaneous users
  • Power efficient: 2W TDP, suitable for 24/7 operation

rclone Filter File

# MP3-only filter (optimized for Atom processor - no transcoding needed)

# Include MP3 files ONLY
+ *.mp3
+ *.MP3

# Include album artwork
+ *.jpg
+ *.jpeg
+ *.png
+ *.JPG
+ *.JPEG
+ *.PNG
+ cover.*
+ folder.*
+ Cover.*
+ Folder.*

# Include playlist files
+ *.m3u
+ *.m3u8
+ *.M3U

# Exclude ALL other audio formats (prevent transcoding load)
- *.flac
- *.FLAC
- *.m4a
- *.M4A
- *.aac
- *.AAC
- *.ogg
- *.OGG
- *.opus
- *.OPUS
- *.wav
- *.WAV
- *.wma
- *.WMA
- *.ape
- *.alac
- *.aiff

# Exclude system files
- .DS_Store
- Thumbs.db
- desktop.ini
- .directory

# Exclude temporary/hidden files
- *.tmp
- *.temp
- *~
- .~*
- .*

# Exclude text/documentation
- *.txt
- *.pdf
- *.doc
- *.docx
- *.nfo
- *.log
- *.cue

# Include directories
+ */

Sync Script

  1#!/bin/bash
  2
  3################################################################################
  4# Navidrome Local Server Music Sync Script
  5# Syncs music from local storage to Tailscale-connected Navidrome server
  6# Optimized for low-power Atom processor
  7################################################################################
  8
  9# === CONFIGURATION ===
 10MUSIC_SOURCE="/your/music/directory"
 11RCLONE_REMOTE="what_you_named_your_navidrome_server:/your/music/directory"
 12FILTER_FILE="$HOME/.config/rclone/what_you_named_your_filter.txt"
 13
 14# Navidrome API settings
 15NAVIDROME_URL="http://XXX.XX.XX.XX:4533"
 16NAVIDROME_USER="YOUR USER NAME"
 17NAVIDROME_PASS="YOUR PASSWORD"
 18
 19# === SCRIPT START ===
 20echo "========================================"
 21echo "Navidrome Local Server Music Sync"
 22echo "Started: $(date)"
 23echo "========================================"
 24
 25# Check if source directory exists
 26if [ ! -d "$MUSIC_SOURCE" ]; then
 27    echo "ERROR: Music source directory not found: $MUSIC_SOURCE"
 28    exit 1
 29fi
 30
 31# Check if filter file exists
 32if [ ! -f "$FILTER_FILE" ]; then
 33    echo "WARNING: Filter file not found: $FILTER_FILE"
 34    echo "Proceeding without filters..."
 35    FILTER_ARG=""
 36else
 37    FILTER_ARG="--filter-from $FILTER_FILE"
 38fi
 39
 40# Perform rclone sync with optimized settings for local network
 41echo ""
 42echo "Starting music sync..."
 43echo "Source: $MUSIC_SOURCE"
 44echo "Destination: $RCLONE_REMOTE"
 45echo ""
 46
 47rclone sync "$MUSIC_SOURCE" "$RCLONE_REMOTE" \
 48    $FILTER_ARG \
 49    --progress \
 50    --stats 10s \
 51    --transfers 8 \
 52    --checkers 16 \
 53    --stats-one-line \
 54    --bwlimit-file 50M
 55
 56# Check sync result
 57SYNC_EXIT_CODE=$?
 58
 59if [ $SYNC_EXIT_CODE -eq 0 ]; then
 60    echo ""
 61    echo "✓ Sync completed successfully!"
 62    echo ""
 63    
 64    # Trigger Navidrome library scan
 65    echo "Triggering Navidrome library scan..."
 66    
 67    # Generate salt and MD5 token for authentication
 68    SALT=$(date +%s | md5sum | cut -d' ' -f1 | cut -c1-12)
 69    TOKEN=$(echo -n "${NAVIDROME_PASS}${SALT}" | md5sum | cut -d' ' -f1)
 70    
 71    # Call the startScan API
 72    SCAN_RESPONSE=$(curl -s "${NAVIDROME_URL}/rest/startScan?u=${NAVIDROME_USER}&t=${TOKEN}&s=${SALT}&v=1.16.1&c=rclone_sync&f=json")
 73    
 74    # Check response
 75    if echo "$SCAN_RESPONSE" | grep -q '"status":"ok"'; then
 76        if echo "$SCAN_RESPONSE" | grep -q '"scanning":true'; then
 77            echo "✓ Library scan triggered successfully!"
 78            echo "  Navidrome is now scanning your music library..."
 79        else
 80            echo "✓ Scan request accepted"
 81        fi
 82    elif echo "$SCAN_RESPONSE" | grep -q '"status":"failed"'; then
 83        ERROR_MSG=$(echo "$SCAN_RESPONSE" | grep -o '"message":"[^"]*"' | cut -d'"' -f4)
 84        echo "⚠ Scan failed: $ERROR_MSG"
 85    else
 86        echo "⚠ Unexpected response"
 87        echo "$SCAN_RESPONSE"
 88    fi
 89    
 90else
 91    echo ""
 92    echo "✗ Sync failed with exit code: $SYNC_EXIT_CODE"
 93    echo "Skipping Navidrome scan."
 94    exit 1
 95fi
 96
 97echo ""
 98echo "========================================"
 99echo "Completed: $(date)"
100echo "========================================"

Resources

More Recent Posts