T_LU
New York

Work

Home Server

Movies, music, books and photos for my family. Most of it is software I did not write. The interesting parts are what I had to build when the rest didn't fit together.

The problem

The media server is the part people see. It is also the part I wrote the least of.

Jellyfin serves the films, Navidrome the music, Audiobookshelf the books, Immich the photos. I did not write any of them. What I built is the software between them: the pieces that exist because the alternative was asking my family to care which application does what.

Nine other people use it. None of them file bug reports. They say it's being weird, and then it is my job to find out what that means.

The scale matters because it is where the interesting failures come from. A system used by one person, occasionally, does not accumulate the kind of bug that takes four days to notice. This one does.

The engineering decision

Constraints

  • One place to look. A second application with a second login is a feature nobody in this house will use.
  • Survives updates. Anything I add has to come back by itself after the thing underneath it is replaced.
  • Fails visibly. A silent failure here is discovered weeks later by someone who assumed it had always been like that.

Options considered

  • Ask people to use each application directly Honest, and it moves the integration work onto nine people who did not sign up for it.
  • Fork the applications Every upstream release becomes a merge. The changes I want are small and the maintenance is not.
  • Write the software between them Small services that own one seam each, and a build that re-applies the client changes after every update.

Decision

Four pieces, each because something did not fit.

Implementation

A channel bridge turns a list of live streams into a guide the media server will accept, numbering channels with the same arithmetic on both sides so a channel is the same number everywhere it appears. A request service answers to the media server's own sessions, so asking for something absent happens where the person already is, with the account they already have. A patch pipeline rebuilds the web client after every update. Audiobook alignment matches a recording to its text so the words can be followed while listening.

Result

Nobody is asked to learn the architecture. The cost is that four small things now have to be maintained, and three of the four investigations below came out of them.

Architecture

  1. acquisition
  2. library servicesphotosaudiobooks
  3. the seams I wrote
  4. one web client
  5. nine other people

The interesting code is in the third step. The first two are configuration, and the last is the only one that reports bugs.

Patching software I don't own

The web client ships as a built file inside the container. There is no plugin interface for the things I wanted to change, so the changes are injected into that file directly: a build assembles about ninety independent blocks of CSS and JavaScript, applies them to a copy of the shipped page, and writes the bulk of the JavaScript out as a separate file so the browser can cache it between loads.

Two properties do most of the work. Every block removes its own previous version before injecting, so running the build against an already-patched file is safe and produces the same result. And nothing is deployed unverified: a syntax error in the generated JavaScript throws no error a user would ever see — it silently removes the entire custom interface — so the build refuses to copy anything that does not parse.

The rest is arithmetic about ownership. Files copied into a running container live in a layer that any recreate discards, so the whole thing re-applies itself on a schedule and after every update. It is not elegant. It is what patching software you do not own actually costs.

What broke

Three failures were interesting enough to write down.

  • A search that took twenty-two seconds, next to an upstream service configured with a twenty-second timeout. The coincidence was not the cause. A 22-second search, traced →
  • An aligner that ran out of memory on long chapters and recorded that as a skip. Every check downstream then passed, against a map covering a third of the book. The aligner that failed and said it succeeded →
  • A stack where every watchdog looked correct. Stopping a container on purpose showed that nothing would have restarted it — and that the monitoring which should have noticed was inside the same blast radius. Proving self-healing by breaking it →

Evidence

  • 12,000 items, six libraries films, television, music, audiobooks, books, photos
  • 91 patch blocks assembled, verified, then deployed — in that order
  • 10 accounts nine of them not mine

The item count is mostly television, which is the least interesting thing about it. The number that matters is the last one: every investigation below started with somebody who was not me saying that something was being weird.

What I would improve

  • The client patch has no automated visual check. The build proves the JavaScript parses; it cannot tell whether the page still looks right. A rendering break would be found by a family member, which is not a test strategy.
  • Read-along covers under half the audiobooks and will stay that way, because the missing half have no text to align against. The alignment code is not the limit here; the library is.
  • The request service has no retry story worth the name. A request made while something downstream is restarting is simply lost, and the person who made it finds out by noticing nothing happened.