OpenLabs

Build a library that outlives the platform

mediastorage 2026-08-23

A media library is a database that happens to be made of files. Get the schema wrong at the start and you will be fixing it by hand at 2 a.m. three years later, with 40,000 files and no memory of what you were thinking.

1. The layout is the schema

Every scraper, player and tagger in this space reads the same conventions. Fight them and nothing matches; follow them and metadata mostly just appears.

Films/
  Blade Runner (1982)/
    Blade Runner (1982) - 2160p.mkv

TV/
  The Expanse/
    Season 01/
      The Expanse - S01E01 - Dulcinea.mkv

Music/
  Artist/
    Album (Year)/
      01 - Track Title.flac

Three rules that matter more than they look:

Keep films and television in separate roots. They are matched against different databases, and a mixed root produces episodes identified as feature films with striking confidence.

2. Metadata lives in the files

If your only copy of “which album is this” is a row in an application's SQLite file, your library is hostage to that application. Write tags into the files. Keep artwork alongside as cover.jpg and poster.jpg. Then any tool you migrate to in five years reads the same truth, and rebuilding the index is an afternoon rather than a project.

Corollary: never let a media server be the only thing that knows your filesystem layout. You should be able to walk the tree with find and understand everything you see.

3. Hardlinks, or you will store everything twice

This one silently costs people terabytes. If your download client finishes a file and your library then copies it into place, you now hold two full copies — one seeding, one in the library — and you pay that tax on every item forever.

A hardlink is a second name for the same data. Same bytes, one allocation, both paths fully valid, and the space is only released when the last name is removed. The catch is that hardlinks cannot cross filesystems, which means:

Downloads and library must live on the same filesystem. Not the same disk — the same filesystem. If they are separate mounts, or separate bind-mounts inside a container that resolve to different filesystems, every “move” silently becomes a copy. In containers, mount one shared parent (/data) and use subdirectories under it, rather than mounting /downloads and /media separately.

Verify rather than assume. A file with two names reports a link count of 2:

stat -c '%h %n' /data/media/films/Blade\ Runner*/*.mkv
# 2 /data/media/films/Blade Runner (1982)/Blade Runner (1982) - 2160p.mkv

A count of 1 means you copied. Also check that df usage stops tracking the sum of both trees — that is the real proof.

4. Automate the boring half

The *arr stack (Sonarr, Radarr, Lidarr, Prowlarr) is fundamentally a rules engine: quality profiles, naming templates, and an import step that renames into your schema and hardlinks into place. Its value is not that it finds things — it is that everything lands in identical shape without you deciding anything at 2 a.m.

Two hard-won cautions:

5. Sources: not all indexers are equal

Open public indexers are useful for finding out that something exists. They are a poor thing to point unattended automation at: listings are unmoderated, fake releases are common, and “video file” is a claim rather than a fact. A padded executable dressed as a 4K remux is a well-worn trick and it works precisely because nobody is watching at the moment it imports.

A practical split that holds up:

SourceAutomated grabsInteractive search
Private / curated trackersYesYes
Open public indexersNoYes, eyes on the release

And regardless of source: check what actually landed. file and mediainfo tell you what something is, not what it was named. Anything that fails that check gets deleted, not opened.

6. Seeding is part of the design, not an afterthought

If you take from a swarm, you hold the swarm up afterwards. Beyond the ethics, private trackers enforce it, and ratio debt is far more annoying to dig out of than to avoid. Practically: keep a staging area sized for the seeding you intend to do, and understand that its usage will swing wildly by design. That is the buffer working, not a disk filling up.

One trap worth naming: removing a torrent from your client does not necessarily delete its files. Do that enough times and you accumulate orphans — data on disk that nothing references and nothing seeds. Audit occasionally by diffing what the client knows about against what is actually on the filesystem.

7. Backups, and being honest about tiers

Not everything deserves the same protection, and pretending otherwise is how people end up with no backups at all.

Two things people get wrong here. RAID and ZFS mirrors are not backups — they protect against a disk dying, not against rm -rf, a bad script, ransomware, or a flood. And an untested backup is a rumour. Restore something at random, on a schedule, or you do not know what you have.

Snapshots are the cheapest genuine insurance available: on ZFS or btrfs they cost nearly nothing, they are instant, and they undo the mistake you make right after you finish reading this. Set an automatic policy, then set an alert for snapshots going stale — a snapshot job that silently stopped six weeks ago is the classic way to discover you had no protection at the exact moment you needed it.


None of this is clever. It is a naming convention, one filesystem, tags in files, tiered backups, and alerts when something stops. Do those five and the library keeps working long after whatever software you started with is gone.