Notes on the *arr stack

This isn’t an explanation of what the *arr stack does; this is just a run-down of notes I’ve made, with solutions for snags I’ve hit in about a year and a half of running this software.

This is shared to help with the interesting technicalities of self-hosting, not to enable any sketchy activity. Obviously, obviously, do not pull media that isn’t very clearly legal to grab. There’s no grey area — just don’t do it.

Layout overview

My setup is probably very typical for low-power instances. My host is a Pi4, with an externally powered hard-drive mounted. The only relevant user-facing applications I have are qBittorrent, Plex and Overseer. Behind-the-scenes, I have Sonarr, Radarr, Prowlarr (after moving from Jackett), and glueTUN. The latter is used exclusively to tunnel Prowlarr and qBittorrent traffic.

There are two segregated sections of the drive available — archived and unsorted. Access is divided up as:

  • Overseer, Prowlarr and glueTUN have access to neither
  • Plex has access to archived only
  • Sonarr and Radarr have access to both
  • qBittorrent has access to unsorted only

The vast majority of the time I spend watching stuff hosted on this setup is via a Chromecast, but the iPad Plex app is well worth the tiny cost.


The remainder of this is broken up into issues that are specific to my hardware setup in some way, and those that might be run into more generally.

Hardware-specific

Formats

This is also the answer to “Why is it stuttering so much?”

My Chromecast is not an Ultra. This means it can’t natively handle h.265. This means, anything in h.265 has to be transcoded before being sent out. The Pi4 is simply not capable enough for most transcoding (and certainly not this case), and I’ve lost a lot of time trying to get it to work to no avail. The best way out of this mess is to tell Sonarr and Radarr to ignore h.265; h.264 streams fine to the standard Chromecast without transcoding. This can be set up in Settings > Profiles > Release Profiles; adding Must Not Contain: x265, h265 is sufficient.

Sometimes I found Plex would still try and transcode, even when the device requesting the stream could handle the codec fine. So for good measure, I also manually disabled video transcoding entirely in Plex’s settings (Settings > Transcoder > Disable video stream transcoding).

For what it’s worth, h.265 is a better file format, and there are plenty of cheap Chromecast alternatives that can handle h.265 with no issues. But I set out not to have to buy anything extra to get this set up.

Architecture

Do not install the (default!) 32-bit OS. Raspbian is fine, but make sure it is the 64-bit version.

If you download the wrong version, you’ll be restricted to armv7l (for a Pi4, at least) rather than arm64 – this is possible to work around, but there could be plenty of things you’ll want to run which demand a 64-bit OS. The people at linuxserver, for example, have deprecated the 32-bit versions of their builds because of dropped support in Mono. If you’re wanting to experiment with other services you’ll hit compatibility issues pretty quickly.

Drives

Any external drives need external power. The Pi4 only pumps out a max of 1.2A over USB, and this limit is across all the ports.

A cronjob to back things once a week has already saved me once from disaster. No-one is immune from drive failures!

Non-specific

Dockerising

In short, do it. It’s so much easier to bring services up and down and understand their relative network topology when containerised. And the Pi4 is more than powerful enough to run everything, so there’s no noticeable performance hit.

I found linuxserver images to be pretty good, but not for everything. In particular:

  • For Plex I’m using the official image; the arm64 one isn’t on dockerhub, but the dockerfiles are available here
  • For qBittorrent, I’m building it with my own dockerfile
  • For VPN connections, I’m using glueTUN

The advantage of dockerising is particularly clear for VPN connections; you can force all network traffic from a container to go via another, rather than worrying about which individual applications are connecting to the tun0 device.

Make use of a host volume for configuration files — just so that you can tear down images and not have to worry about reconfiguring everything. Something like /srv/<service> on the host mapping to /config in the container is pretty standard.

Make use of docker compose for everything. It is so (so) much easier.

Telegram bots

I like notifications when things happen on my machine. I only currently need communication out of the device, and Telegram bots make for an easy way to manage this. This was originally motivated by wanting notification when downloads are complete, but the principles are general.

  • Set up a telegram bot; this is staggeringly easy. You can kick things off by messaging @BotFather in-app
  • Start a chat with the bot, make a note of the chat ID. It’s easy to make this a group chat, if you want to send signals to multiple people at once
  • Set up an “announce” script, and put it somewhere accessible. I have this (very bare bones) script in my PATH on the host, and copy it in to /comm on docker containers which need it:
#!/bin/sh
IFS='
'
message="$*"
curl \
    --data-urlencode "chat_id=<chat-id>" \
    --data-urlencode "text=$message" \
    https://api.telegram.org/bot<token>/sendMessage

where the values are hardcoded so that I don’t need to worry about copying environment variables between containers.

As for how I propagate notifications for specifics:

  • For things happening in the host (e.g., info about cronjobs), I have shell scripts simply call announce "<message>"
  • To receive notification when downloads finish, I have the following in qBittorrent.conf:
[AutoRun]
enabled=true
program="\"/comm/announce.sh\" \"Heads up: a torrent just finished downloading.\" \"Name: %N\""

Auto-mounting

Not hard, just something to actually remember to do. Especially if you’re using docker, you can end up with race conditions unless the OS is mounting the external drives properly for you. Edit /etc/fstab to mount by default, to say /mnt/videos/, by adding something like

UUID=<disk-uuid> /mnt/videos ntfs defaults 0 0

where <disk-uuid> can be obtained with sudo blkid.

Indexer redundancy

Have more than one, especially if dependent on public sites; they go down fairly often. Connecting via a VPN helps with some types of unavailability.

Organisation, renaming

It was best to just let Sonarr and Radarr handle this, for me. If you’re containerising, remote path mappings are needed for both, so they don’t get confused about the disparity between their perceived filesystem and that of the other containers they’re communicating with.

Accessing torrent info on mobile

My download client qBittorrent (specifically, a docker container running qbittorrent-nox) is used for a lot more than this stack; I’ll often pass off any big downloads to it so I don’t have to keep my computer on. It has a great web-UI, if you’re on the desktop version. It doesn’t have a mobile one, which makes checking progress a pain. To deal with this, I have an alternative UI which I mount in the container, and enable from qBittorrent.conf. I’m using, and like, Vuetorrent, but it was a pretty arbitrary choice.

[Preferences]
WebUI\AlternativeUIEnabled=true
WebUI\RootFolder=/config/vuetorrent

Plex for music isn’t worth the effort

That’s all there is to say really. It works, but the Plex library is exceptionally big and slow to generate (though this is potentially exacerbated by the size of my music library). It also feels the need to download its own album art, despite my library having comprehensive metadata.