2026-05-18 13:43:03 +02:00
2026-05-18 13:31:31 +02:00
2026-05-13 23:22:26 +02:00
2026-05-18 13:31:31 +02:00
2026-05-13 20:34:14 +02:00
2026-05-17 15:44:31 +02:00
2026-05-13 20:34:14 +02:00
2026-05-13 21:50:25 +02:00
2010-07-16 18:39:16 +01:00
2026-05-17 13:44:20 +02:00
2026-05-17 13:43:12 +02:00
2026-05-13 23:22:26 +02:00

MusicFS

A read-only FUSE filesystem that presents your music library organized by metadata — artist, album, track — regardless of how files are stored on disk.

Browse /Artist/Album/Track.flac in any media player or file manager. Original files are never touched.


What It Does

MusicFS mounts as a virtual filesystem. Point it at your music storage (local drive, NFS share, S3 bucket, SFTP server) and it exposes a clean metadata-based directory tree:

/mnt/music/
├── Metallica/
│   └── 72 Seasons (2023) [FLAC]/
│       ├── 01 - 72 Seasons.flac
│       ├── 02 - Shadows Follow.flac
│       └── cover.jpg
├── Pink Floyd/
│   └── The Wall (1979) [FLAC]/
│       ├── 01 - In the Flesh?.flac
│       └── ...
└── .search/
    └── (full-text search — see Search section)

Files are read directly from origin storage with local chunk caching. Once cached, playback works entirely offline. Write operations return EROFS — origin files are always safe.


Features

Feature Details
Instant mount O(1) regardless of library size (<500ms)
Metadata-organized paths Configurable path templates via $artist, $album, $year, etc.
Multi-origin federation Local, NFS, SMB, S3, SFTP — automatic failover by priority
Content-addressable cache Chunk-level deduplication, LRU eviction, delta sync (>90% bandwidth savings)
Full-text search /.search/metallica/ returns instant results across 1M+ tracks
Metadata overlay Set/override tags in the virtual layer without modifying originals
Album art Virtual cover.jpg per album, extracted from embedded tags
Plugin system Native .so and WASM plugins for custom origins, formats, metadata sources
gRPC control API Cache stats, origin health, live event streaming, metadata management
systemd integration sd_notify ready, journald logging, clean SIGTERM handling

Supported formats: FLAC, MP3, OGG, WAV, M4A, AAC, Opus


Quick Start

# 1. Enter dev environment (provides Rust, FUSE3, SQLite, everything)
nix develop

# 2. Build
cargo build

# 3. Mount your music library
./target/debug/musicfs mount /mnt/music --origin /path/to/your/music

# 4. Browse
ls /mnt/music
mpv /mnt/music/Artist/Album/01\ -\ Track.flac

# 5. Unmount
fusermount -u /mnt/music

No rustup, no apt install. The Nix flake provides the full toolchain.


Installation

# Development shell — everything you need
nix develop

# Or install the binary into your profile
nix profile install .#musicfs

From Source

Prerequisites (non-Nix):

  • Rust 1.75+
  • libfuse3-dev / fuse3 (package name varies by distro)
  • libsqlite3-dev
  • libssl-dev
  • protobuf-compiler (for gRPC)
  • clang + lld
git clone https://github.com/user/musicfs
cd musicfs/musicfs
cargo build --release
sudo cp target/release/musicfs /usr/local/bin/

System Requirements

Resource Minimum Recommended
CPU 1 core 4 cores
RAM 256 MB 2 GB
Disk (cache) 1 GB 50 GB
Linux kernel 4.x+ 5.x+
FUSE module required

Configuration

MusicFS can be configured via file (--config), CLI flags, or environment variables (RUST_LOG for log level).

Minimal Config

mount_point = "/mnt/music"
cache_dir   = "/home/user/.cache/musicfs"

[[origins]]
id          = "local"
origin_type = "local"
priority    = 1
path        = "/mnt/nas/music"
musicfs mount --config /etc/musicfs/config.toml

Full Config Reference

# MusicFS Configuration
# Copy to /etc/musicfs/config.toml or ~/.config/musicfs/config.toml

# Required: where to mount the virtual filesystem
mount_point = "/mnt/music"

# Required: directory for cache data (CAS chunks, metadata, search index)
cache_dir = "/var/cache/musicfs"

# ------------------------------------------------------------------------------
# Origins - music sources (at least one required)
# Supported types: local, nfs, smb, s3, sftp
# Lower priority number = preferred source for failover
# ------------------------------------------------------------------------------

[[origins]]
id = "local-music"
origin_type = "local"
priority = 1
enabled = true
path = "/home/user/Music"

[[origins]]
id = "nas-nfs"
origin_type = "nfs"
priority = 2
enabled = true
path = "/mnt/nas/music"

[[origins]]
id = "nas-smb"
origin_type = "smb"
priority = 3
enabled = false
path = "/mnt/smb/music"

[[origins]]
id = "cloud-backup"
origin_type = "s3"
priority = 10
enabled = false
bucket = "my-music-backup"
region = "us-east-1"

[[origins]]
id = "remote-server"
origin_type = "sftp"
priority = 10
enabled = false
host = "music.example.com"
port = 22
user = "musicfs"
path = "/srv/music"

# ------------------------------------------------------------------------------
# Cache settings
# ------------------------------------------------------------------------------

[cache]
# In-memory metadata cache size (artist/album/track info)
metadata_cache_mb = 100

# On-disk content cache size (audio chunks)
content_cache_gb = 10

# ------------------------------------------------------------------------------
# Health monitoring for origin failover
# ------------------------------------------------------------------------------

[health]
# How often to check origin health
check_interval_secs = 30

# Timeout for health check probes
timeout_ms = 5000

# Consecutive failures before marking origin unhealthy
unhealthy_threshold = 3

# Per-origin type thresholds (overrides unhealthy_threshold)
[health.per_origin_thresholds]
local = 1
nfs = 3
smb = 3
s3 = 3
sftp = 3

# ------------------------------------------------------------------------------
# Logging
# ------------------------------------------------------------------------------

[logging]
# Directory for log files
log_dir = "/var/log/musicfs"

# Output logs as JSON (for log aggregators)
json_output = false

# Send logs to systemd journal
journald = true

# Log level filter (tracing format)
# Examples: "info", "debug", "musicfs=debug,warn", "musicfs_fuse=trace"
level = "musicfs=info,warn"

# Trace sampling rate for performance tracing (0.0 to 1.0)
trace_sample_rate = 1.0

Cache Layout on Disk

~/.cache/musicfs/
├── musicfs.db       # SQLite: file metadata, virtual tree, overlay data
├── musicfs.lock     # Single-instance lock
├── musicfs.pid      # Daemon PID
├── chunks/          # Content-addressable chunk files
│   ├── aa/          # 256 subdirs (first 2 hex chars of hash)
│   │   └── aa1b2c…  # 64 KB average chunk
│   └── ...
├── search.idx/      # Tantivy full-text search index
└── chunks.sled/     # Sled KV: content hash → chunk location

CLI Reference

musicfs [OPTIONS] <COMMAND>

OPTIONS:
  -l, --log-level <LEVEL>   Log verbosity [default: info]

mount — Start the filesystem

# From CLI flags (quick start)
musicfs mount /mnt/music --origin /path/to/music

# From config file
musicfs mount --config /etc/musicfs/config.toml

# All flags
musicfs mount [MOUNTPOINT] \
  --config <path>         # Config file (overrides flags)
  --origin <path>         # Source music directory
  --cache-dir <path>      # Cache location [default: ~/.cache/musicfs]
  --grpc-port <port>      # gRPC server port [default: 50052]

status — Daemon status

musicfs status

cache — Cache management

musicfs cache stats                    # Hit rate, size, dedup ratio
musicfs cache clear                    # Clear all caches
musicfs cache clear <origin-id>        # Clear cache for one origin
musicfs cache prefetch <path> [path…]  # Pre-warm cache for paths
musicfs search "metallica"             # Search across all metadata
musicfs search "dark side" --limit 20  # Limit results [default: 100]

Search results are also browsable as a virtual directory (see Search).

origin — Origin management

musicfs origin list                    # List all configured origins
musicfs origin health <id>             # Check health of one origin
musicfs origin rescan <id>             # Force re-scan and re-index

metadata — Metadata overlay

# Requires running daemon
musicfs metadata get "/Artist/Album/01 - Track.flac"
musicfs metadata get "/Artist/Album/01 - Track.flac" --field artist

musicfs metadata set "/Artist/Album/01 - Track.flac" \
  --title "New Title" \
  --artist "New Artist" \
  --album "New Album" \
  --track 1 \
  --genre "Rock" \
  --date "2023"

# Set from JSON
musicfs metadata set "/path/to/file.flac" --json '{"title":"foo","year":2023}'

# Show current (overlaid) metadata
musicfs metadata diff "/path/to/file.flac"

# Revert overlay — restore original metadata
musicfs metadata clear "/path/to/file.flac"

# Bulk import/export
musicfs metadata import library.csv
musicfs metadata import library.json
musicfs metadata export --output library.json
musicfs metadata export --output library.csv --query "artist:Metallica"

Note: --endpoint flag (default http://[::1]:50051) selects the gRPC server.

trash — Deleted file recovery

When files disappear from the origin, MusicFS moves them to a virtual trash rather than removing them immediately.

musicfs trash list --config /etc/musicfs/config.toml
musicfs trash list --since 7d              # Deleted in last 7 days
musicfs trash list --origin local          # Filter by origin
musicfs trash list --path "/Metallica"     # Filter by path prefix

musicfs trash restore "/Metallica/72 Seasons"  # Restore folder
musicfs trash restore --all                    # Restore everything

musicfs trash empty --older-than 30d       # Permanently delete old entries
musicfs trash empty --pattern "/Unknown*"  # Delete by pattern

events — Live event stream

musicfs events                         # All events
musicfs events --type file_added       # Filter by type
# Event types: file_added, file_removed, file_modified,
#              origin_connected, origin_disconnected,
#              sync_started, sync_completed, cache_eviction

shutdown — Stop the daemon

musicfs shutdown                       # Graceful (drain in-flight ops)
musicfs shutdown --graceful false      # Immediate
musicfs shutdown --timeout 60          # Max drain timeout seconds

Storage Origins

Local Filesystem

[[origins]]
id          = "local"
origin_type = "local"
priority    = 1
path        = "/mnt/nas/music"

Changes detected via inotify. Zero-latency access.

NFS

[[origins]]
id          = "nfs"
origin_type = "nfs"
priority    = 2
host        = "nas.local"
export      = "/exports/music"

SMB / CIFS

[[origins]]
id          = "smb"
origin_type = "smb"
priority    = 3
host        = "nas.local"
share       = "music"

S3 (stub — not yet functional)

[[origins]]
id          = "s3"
origin_type = "s3"
priority    = 4
bucket      = "my-music"
region      = "us-east-1"
# Credentials via AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY env vars

SFTP (stub — not yet functional)

[[origins]]
id          = "sftp"
origin_type = "sftp"
priority    = 4
host        = "server.example.com"
port        = 22
username    = "alice"
# Auth via SSH agent or key file — never store passwords in config

Multi-Origin Failover

Multiple origins are federates into a single virtual tree. MusicFS selects origins by priority, falling back automatically when one becomes unhealthy. Health is polled every check_interval_secs (default: 30s). When all origins for a file are unavailable, cached data is served seamlessly.


Virtual Filesystem Layout

Path Templates

The virtual path for each file is built from its audio metadata using a configurable template. Variables are sanitized (no /, \, :).

Default template:

$artist/$album ($year) [$format_upper]/$track - $title.$format

Template variables:

Variable Description Example
$artist Track artist Metallica
$album Album name 72 Seasons
$title Track title Lux Æterna
$track Track number (zero-padded) 03
$disc Disc number 1
$year Release year 2023
$genre Genre Metal
$format File extension (lowercase) flac
$format_upper File extension (uppercase) FLAC

Files with missing metadata fall back to Unknown Artist/Unknown Album/filename.

Album Art

Each album directory includes a virtual cover.jpg extracted from the embedded tags of the first track. No files are written to disk by MusicFS — the image is synthesized on read.

The /.search/ virtual directory exposes full-text search as filesystem paths:

# Search via filesystem — use the query as a directory name
ls "/mnt/music/.search/dark side of the moon/"
# → Returns matching tracks as symlinks to their virtual paths

# Or use the CLI
musicfs search "dark side of the moon"
musicfs search "artist:Metallica" --limit 50

Query syntax (powered by tantivy):

Syntax Example Matches
Simple terms metallica sandman All fields contain both words
Field-specific artist:Metallica Artist field only
Phrase album:"Master of Puppets" Exact phrase in album
Fuzzy metalica~1 Within Levenshtein distance 1
Range year:[1980 TO 1989] Numeric range
Boolean genre:Metal AND year:[1980 TO 1989] Combined conditions

Indexed fields: title, artist, album, album_artist, genre, composer, year. Results cached for 5 minutes. Max 1000 results per query. Queries capped at 256 characters.

Smart Collections

Built-in and custom query-based virtual folders appear alongside regular directories:

  • Recently Added — tracks added in the last 30 days
  • 80s Music — year 19801989
  • 90s Music — year 19901999

Custom collections can be defined via the gRPC API with compound boolean queries over any indexed field.


Metadata Overlay

MusicFS lets you override metadata in the virtual layer without touching origin files. Overlaid metadata is synthesized into the audio file header on read — players see your corrected tags, the origin file is unchanged.

# Fix a misnamed artist
musicfs metadata set "/Unknown/Best Of/01 - Track.flac" \
  --artist "The Beatles" \
  --album "Past Masters"

# Verify
musicfs metadata get "/The Beatles/Past Masters/01 - Track.flac"

# See what's been overlaid vs. original
musicfs metadata diff "/The Beatles/Past Masters/01 - Track.flac"

# Revert
musicfs metadata clear "/The Beatles/Past Masters/01 - Track.flac"

Supported fields: title, artist, album, album-artist, track, disc, genre, date, composer, comment, lyrics, copyright, compilation, sort fields (artist-sort, etc.), MusicBrainz IDs, ReplayGain values, and arbitrary custom tags.


Plugin Development

Plugins extend MusicFS without modifying core code. Three plugin types:

Type Purpose Examples
Origin Custom storage backends Google Drive, Dropbox, custom NAS protocol
Metadata External tag enrichment MusicBrainz, Discogs, Last.fm
Format Custom audio formats Game audio, proprietary codecs

Native Plugin (.so)

// Cargo.toml
[lib]
crate-type = ["cdylib"]

[dependencies]
musicfs-plugins = { path = "..." }
semver = "1"
serde_json = "1"
use musicfs_plugins::{declare_plugin, Plugin, PluginType, FormatPlugin};
use musicfs_core::AudioMeta;
use semver::Version;
use serde_json::Value;

struct MyFormatPlugin;

impl Plugin for MyFormatPlugin {
    fn name(&self) -> &str { "my-format" }
    fn version(&self) -> Version { Version::new(1, 0, 0) }
    fn plugin_type(&self) -> PluginType { PluginType::Format }
    fn init(&mut self, _config: Value) -> musicfs_plugins::Result<()> { Ok(()) }
    fn shutdown(&mut self) -> musicfs_plugins::Result<()> { Ok(()) }
}

impl FormatPlugin for MyFormatPlugin {
    fn extensions(&self) -> &[&str] { &["xyz"] }

    fn parse(&self, reader: &mut dyn std::io::Read) -> musicfs_plugins::Result<AudioMeta> {
        // Parse your format and return metadata
        todo!()
    }

    fn synthesize_header(&self, metadata: &AudioMeta) -> musicfs_plugins::Result<Vec<u8>> {
        // Build a new file header with updated metadata
        todo!()
    }
}

// Required export — MusicFS calls this to instantiate the plugin
declare_plugin!(MyFormatPlugin, MyFormatPlugin);
cargo build --release
# produces target/release/libmy_format_plugin.so

Loading Plugins

[plugins]
enabled = true
search_paths = ["/usr/lib/musicfs/plugins"]  # Auto-discover .so files here

[plugins.plugins.my-format]
path    = "/path/to/libmy_format_plugin.so"
enabled = true
config  = { key = "value" }  # Passed to Plugin::init()

WASM Plugins (experimental)

[plugins.wasm]
enabled       = true
max_memory_mb = 64
max_cpu_time_ms = 5000

Load a .wasm binary at runtime via the gRPC API or by placing it in a search path. WASM plugins run sandboxed inside wasmtime.

Plugin API Version

Current: 0.1.0. Breaking changes will increment the major version. MusicFS checks musicfs_plugin_api_version() before loading any native plugin.


Control API (gRPC)

MusicFS exposes a gRPC API for programmatic control. The server starts automatically with the daemon.

Default port: 50052 (override with --grpc-port) Proto definition: crates/musicfs-grpc/proto/musicfs.proto

Available RPCs

MusicFS service:
  GetStatus          → daemon version, uptime, mount state, open handles
  Shutdown           → graceful or forced stop
  GetCacheStats      → hit rate, chunk count, dedup ratio, per-tier breakdown
  ClearCache         → clear all or per-origin, per-tier, dry-run supported
  Prefetch           → pre-warm cache for paths or search queries
  ListOrigins        → all configured origins with file count and health
  GetOriginHealth    → health status and latency for one origin
  RescanOrigin       → force re-scan with streaming progress
  Search             → full-text search (paginated or streaming)
  SubscribeEvents    → server-streaming live event feed

MetadataService:
  GetMetadata        → all tags for a virtual path
  UpdateMetadata     → set overlay tags for a file
  ClearOverlay       → revert to original metadata
  ImportMetadata     → bulk import from CSV/JSON (streaming progress)

Query with grpcurl

# Daemon status
grpcurl -plaintext localhost:50052 musicfs.v1.MusicFS/GetStatus

# Search
grpcurl -plaintext -d '{"query": "metallica", "limit": 10}' \
  localhost:50052 musicfs.v1.MusicFS/Search

# Cache stats
grpcurl -plaintext localhost:50052 musicfs.v1.MusicFS/GetCacheStats

# List origins
grpcurl -plaintext localhost:50052 musicfs.v1.MusicFS/ListOrigins

# Trigger rescan with live progress
grpcurl -plaintext -d '{"origin_id": "local"}' \
  localhost:50052 musicfs.v1.MusicFS/RescanOrigin

# Live event stream
grpcurl -plaintext localhost:50052 musicfs.v1.MusicFS/SubscribeEvents

Production Deployment

systemd

sudo cp dist/musicfs.service /etc/systemd/system/

# Edit the service to match your paths:
# ExecStart=/usr/bin/musicfs mount --config /etc/musicfs/config.toml

sudo systemctl enable --now musicfs
sudo systemctl status musicfs
[Unit]
Description=MusicFS - Virtual FUSE Filesystem for Music
After=network.target

[Service]
ExecStart=/usr/bin/musicfs mount /mnt/music --origin /path/to/music
ExecStopPost=/usr/bin/fusermount -u /mnt/music
Restart=on-failure

[Install]
WantedBy=multi-user.target

MusicFS sends sd_notify(READY) when the mount is live and sd_notify(STOPPING) during shutdown. Use Type=notify for precise readiness tracking.

Signals

Signal Behavior
SIGTERM Graceful shutdown — drains in-flight ops, unmounts
SIGINT Graceful shutdown (same)
SIGHUP Process pending file restores from trash

Security Notes

  • Run as an unprivileged user — no root required.
  • Store remote credentials in the system keyring or environment variables. Never put them in the config file.
  • Credentials are redacted from logs and RUST_LOG output.
  • WASM plugins run sandboxed. Native .so plugins have full process access — only load plugins you trust.

Observability

Logs

# Set level at startup
musicfs mount ... --log-level debug
# or via env
RUST_LOG=musicfs=debug,warn musicfs mount ...
Level Content
error Unrecoverable failures, data corruption
warn Recoverable failures, origin timeouts, skipped files
info Mount/unmount, sync completion, config reload
debug Cache hits/misses, origin selection, file scans
trace Individual FUSE operations, chunk I/O

Log files rotate daily in log_dir (default: /var/log/musicfs/). Structured JSON available with json_output = true. On Linux, logs forward to journald by default (journald = true).

Prometheus Metrics

Metrics are exposed in Prometheus format via the gRPC API:

musicfs_fuse_ops_total{op="read"}           152341
musicfs_fuse_ops_total{op="readdir"}        8234
musicfs_fuse_latency_seconds{op="read",quantile="0.99"}  0.004
musicfs_cache_hits_total                    142107
musicfs_cache_misses_total                  10234
musicfs_cache_size_bytes                    5368709120
musicfs_origin_health{origin="local"}       1
musicfs_origin_health{origin="s3"}          0
musicfs_sync_files_changed{origin="local"}  15

Performance

Operation Target Maximum
Mount (any library size) <100ms 500ms
stat() cached <1ms 5ms
readdir() cached <10ms 50ms
open() cached <5ms 20ms
read() cached <1ms 5ms
read() cache miss, local <50ms 200ms
read() cache miss, remote <200ms 1000ms
Search (1M tracks) <500ms 1000ms
Sequential read (cached) >500 MB/s
Metadata ops >1000 ops/s

Memory: <50 MB idle, <200 MB with 1K files active, <500 MB peak. Scales to 10M+ files with O(1) mount and O(log n) lookups.


Known Limitations

These are tracked issues — see docs/v2/plans/ for details.

Issue Impact Workaround
No persistent state on mount Every restart does a full origin scan (O(N)). SQLite/search index persist but are not loaded on startup.
S3 and SFTP origins are stubs Only local, nfs, and smb have real implementations. Use NFS/SMB mount as proxy for remote storage.
No write-through for metadata Overlaid metadata exists only in MusicFS's database, not in the actual audio files. Use a tagger (beets, mp3tag) to write back if needed.
FUSE↔tokio deadlock risk block_on() in sync FUSE callbacks can stall under heavy concurrent load. Keep concurrent open handles below ~500.
No background task supervision Health monitor, watcher, and indexer are fire-and-forget. A crash silently stops background work. Restart the daemon periodically in critical deployments.

Architecture

MusicFS is a workspace of 11 Rust crates:

musicfs-cli       → binary, CLI parsing, startup wiring
musicfs-fuse      → FUSE operations (fuser), virtual tree serving
musicfs-core      → shared types, config, events, errors
musicfs-cache     → SQLite metadata DB, virtual tree, format handlers
musicfs-cas       → content-addressable chunk store (sled + xxHash64)
musicfs-origins   → origin backends (local, NFS, SMB, S3 stub, SFTP stub)
musicfs-metadata  → audio tag extraction (symphonia)
musicfs-sync      → delta sync, CDC chunking (FastCDC), inotify watcher
musicfs-search    → full-text index (tantivy), .search/ virtual dir
musicfs-grpc      → gRPC server (tonic + prost), proto codegen
musicfs-plugins   → plugin host, native .so loader, WASM sandbox

Data flow on a cache miss: FUSE read()VirtualPathResolverCAS (chunk lookup) → OriginFederation (fetch missing range) → CDC chunk → store → return.

Full design: docs/v2/architecture.md Requirements: docs/v2/requirements.md Roadmap: docs/v2/development-plan.md


Development

nix develop                    # Enter dev shell

cargo check                    # Fast compile check
cargo test                     # All 162 tests
cargo test -p musicfs-core     # Single crate
cargo clippy                   # Lint
cargo fmt                      # Format
cargo nextest run               # Parallel test runner (faster)
cargo watch -x check -x test   # Watch mode

# Cargo aliases
cargo t   # test
cargo c   # check
cargo b   # build

# gRPC codegen (runs via build.rs automatically)
cargo build -p musicfs-grpc

Pre-commit hooks (rustfmt + clippy) are installed automatically in the Nix dev shell.


License

MIT OR Apache-2.0 — see LICENSE-MIT and LICENSE-APACHE.

S
Description
No description provided
Readme 1.1 MiB
Languages
Rust 96.8%
Python 2.9%
Nix 0.3%