Subsonic to Ampache: My home streaming adventure

A glitched image of a music streaming app's icon.
BlogLink to blog
Blog
7 min read

What's worse than losing your favorite streaming service? Being the person who runs it. Cover generated using craiyon.com.


Contents


Introduction 

I’ve been running a personal music streaming service out of my home for nearly a decade. It started when I found Subsonic 🔗, a free (at the time) service that you could install on your computer, point to your music library, and have a fully functional web-based media player and streaming service that you could access from anywhere.

It was revolutionary! No longer did I have to sync my phone to my laptop or buy devices with hundreds of gigabytes of storage just for music! I could effortlessly stream any song I owned in an instant, and because it was my library and software running on my computer, I could control it however I wanted.

It’s not all sun and roses though. Self-hosting is great, but it requires expertise, constant monitoring, and a penchant for digging through logs and terminals to troubleshoot problems. It’s great when it works, but when it breaks, it can be a nightmare. That’s why I wrote this blog post: to chronicle one particular event that almost pushed me away from streaming altogether.

A quick Subsonic history lesson 

Subsonic is/was a surprisingly influential bit of software. The ability to host your own streaming service was pretty unique at the time, with Ampache 🔗 being the only real established alternative. Subsonic led to the creation of an entire ecosystem of streaming apps for PCs and Macs, smartphones, Rokus, Blackberries, Chromecasts, even smartwatches 🔗. It inspired over a dozen similar services, and even though Subsonic is no longer actively developed, many self-hosted streaming services still support the API that it created.

The main draw of Subsonic is, of course, that you control your music. You control who has access to it, how it’s streamed, where it lives, and even what quality it streams at. If you want to listen to high quality FLAC on your desktop and convert to 128 Kbps MP3 on your phone, you can do that. There are also no arbitrary restrictions like geolocation locks or paywalls. Everything is right there.

Other folks took this idea and ran with it, resulting in a plethora of Subsonic-like services like Madsonic, Navidrome, Koel, and Airsonic (we’ll come back to this one). And since they all used the same API, you can use each one interchangably with the same apps. In some cases, you could even migrate your data between services and retain your users, listening counts, ratings, playlists, etc. Airsonic was built specifically around Subsonic’s database and file formats, and migrating is as simple as renaming a few files from subsonic.extension to airsonic.extension.

My journey started with Subsonic. Subsonic follows an open core model, where the base server is open source, but more advanced features (like video streaming) are behind a paywall. Initially this paywall was a one-time moderate $10 charge, but eventually the creator went with a subscription model. After that change, I jumped ship to Airsonic, which worked great for years. Eventually though, development on Airsonic 🔗 slowed down, and the project was shuttered. A group of people had already become discouraged with Airsonic’s slow development process and started Airsonic Advanced 🔗. I’m sure you can see where this is going.

Airsonic: Crash and burn 

Airsonic Advanced worked great for the 2 or so years I’d been using it. There were some hiccups and crashes, but typically restarting the service was enough to get it working again.

One day, I ran a software update on my server and rebooted it. I started it back up and waited for my services to come back, and they did, except for Airsonic. I checked its status and saw that my server had tried restarting the service many times, only for it to crash each and every time. This happened dozens of times until my server eventually just gave up on restarting it.

Ok, I thought, no big deal. Problems like these are just part of self-hosting. I’ll look to see if I can troubleshoot the cause, and if I can’t, I’ll just revert to an older version. I pulled up a terminal and started digging through logs and restarting the service manually so I could monitor the output. The logs didn’t tell me anything, and even though Airsonic dumped an enormous stack trace into the terminal when it crashed, it told me little more than “there was an error.”

That didn’t give me much to go on, so I figured it was time to revert to an older version. I run Airsonic on Kubernetes, so reverting was as easy as changing the version number in a configuration file and re-deploying. Kubernetes deployed the “new” version, restarted the service, and…it also crashed.

Ok, I thought, a little more anxious now, maybe it’s Airsonic’s data files. Maybe there’s database corruption or something. I store the data files for each of my services in a common folder broken out into subfolders, with each subfolder named for the service that writes to it. For example, Airsonic stores all of its files in /storage/services/airsonic. I also have a backup program that makes nightly backups of this folder, so I simply restored the version of the folder from the day before, copied it over, restarted Airsonic, and…watched it crash again.

Now I was starting to panic. I tried again, this time with a backup from last month, and it crashed. I tried again with a backup from last year, and it crashed. I tried using different versions of the service combined with different backups, and every single time it crashed. This wasn’t supposed to be possible: I was using the exact same software, dependencies, and data files as before, yet I still couldn’t get it to run.

To this day, I have absolutely no idea what happened and why Airsonic died, but I had to give up. I still had my music, but I’d lost nearly a decade of music streaming habits, playlists, and ratings.

Ampache to the rescue 

It wasn’t a total loss though. I still had my music and could re-create my playlists from the Subsonic app on my phone. Coincidentally, I’d also been testing out Ampache. Ampache is much older and works differently than Subsonic, but generally tries to solve the same problems. I didn’t have any other ideas at the time, so I went all in.

I started writing a Kubernetes manifest for Ampache. I’ll post the (nearly) complete version below. This deploys the main Ampache service, as well as a dedicated database. It also creates a Kubernetes Service and Ingress that exposes Ampache’s web interface to other computers on the network. This isn’t a complete manifest and some things (particularly, the highlighted strings) will vary based on your setup, but it should provide a foundation:

# Media mounts
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: media-pv
  labels:
    type: local
spec:
  storageClassName: manual
  capacity:
    storage: 1Ti
  accessModes:
    - ReadWriteMany
  hostPath:
    path: "/storage/services/media"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: media-pvc
spec:
  volumeName: media-pv
  storageClassName: manual
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Ti
---
# DB section
apiVersion: v1
kind: Service
metadata:
  name: ampache-mariadb
spec:
  ports:
    - port: 3306
  selector:
    app: ampache
    tier: mariadb
  clusterIP: None
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: ampache-mariadb
  labels:
    app: ampache
    tier: mariadb
spec:
  selector:
    matchLabels:
      app: ampache
      tier: mariadb
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: ampache
        tier: mariadb
    spec:
      containers:
      - image: mariadb:10
        name: mariadb
        env:
        - name: MARIADB_ROOT_PASSWORD
          value: "AMPACHE_DATABASE_PASSWORD"
        ports:
        - containerPort: 3306
          name: mariadb
        volumeMounts:
        - name: mariadb-persistent-storage
          mountPath: /var/lib/mysql
          subPath: mariadb
      volumes:
      - name: mariadb-persistent-storage
        persistentVolumeClaim:
          claimName: ampache-data-pvc
# App section
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: ampache-data-pv
  labels:
    type: local
spec:
  storageClassName: manual
  capacity:
    storage: 5Gi
  accessModes:
    - ReadWriteMany
  hostPath:
    path: "/storage/services/ampache"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: ampache-data-pvc
spec:
  volumeName: ampache-data-pv
  storageClassName: manual
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 5Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: ampache
    tier: app
  name: ampache
spec:
  replicas: 1
  selector:
    matchLabels:
      app: ampache
      tier: app
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: ampache
        tier: app
    spec:
      containers:
        - image: ampache/ampache:nosql
          imagePullPolicy: Always
          name: ampache
          resources: {}
          volumeMounts:
            - mountPath: /var/www/config
              name: data
            - mountPath: /media
              name: media
      hostname: ampache
      restartPolicy: Always
      volumes:
        - name: data
          persistentVolumeClaim:
            claimName: ampache-data-pvc
        - name: media
          persistentVolumeClaim:
            claimName: media-pvc
status: {}
# Service and Ingress
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: ampache
  name: ampache
spec:
  ports:
    - name: "web"
      port: 80
      targetPort: 80
  selector:
    app: ampache
    tier: app
status:
  loadBalancer: {}
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ampache
  annotations:
    kubernetes.io/ingress.class: traefik
spec:
  rules:
  - host: "music.mydomain.com"
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: ampache
            port:
              name: web

Now I have a working streaming service again! More importantly, Ampache can survive a server reboot without crashing (trust me, I put it through its paces)! I can even use the same apps I was using with Airsonic Advanced, since Ampache supports the Subsonic API. It also has its own widely-supported API, but more options are always better.

Ampache might not be as clean or modern-looking as other services, but it works and it works well. If you’re looking for a self-hosted media streaming service, I’d highly recommend looking into it. It’s pretty painless to set up, works well with moderately large (100+ GB) libraries, and has a ton of customizability and controls. Check it out at ampache.org 🔗, and if you’re so inclined, you can contribute on GitHub 🔗 or donate to the developers 🔗.

Previous: "Fediquette: Your guide to Mastodon culture"Next: "markdown-novel-template: How to write a novel using Markdown, Pandoc, …"
art atmospheric breaks breakbeat buddhism chicago code disco fediverse fiction funk furry house house music kubernetes lgbt linux logseq mastodon mental health movies music nixos obsidian personal philosophy pkm poetry prompt second life social software soul technology writing