As a Linux hacker-type I am often searching for some way to apply my rather specialized skillset to a real world problem. And I am always after some sovereignty over my music collection. So I came up with the idea to make some kind of music player using a Raspberry Pi in an old radio case.
In 2020 I got as far as setting up the Pi, in a cardboard box rather than the imagined retro radio case. It’s worked mostly ok since then, I always had some friction with the Raspbian Linux distribution though; I mean Debian is not the most up-to-date distro, and Raspbian is not even up to date with the latest Debian release. But Raspbian was the only OS with sufficient driver support to be useful for workloads involving graphics, audio, or Bluetooth.
Until now, it seems! After lots of great work from various folk, Fedora now has full Pi support, using a UEFI bootloader, with graphics acceleration, etc.
Recently I did that thing where you try to mix packages from Debian repos, thinking you might be able to do it safely this time, and the system winds up broken beyond repair, as usual. So I decided to throw it away and start again on top of the rpm-ostree based Fedora IoT distro.
The Pi is now back in action and mostly working, and the setup experience was tolerable. Here’s a summary of what I did.
The Base
It’s a Pi 4 connected to:
- a TV via HDMI
- an external HD via USB
- ethernet via a cable
The Fedora IoT install instructions are good, although somehow I didn’t get the right SSH key installed and instead had to set init=/bin/sh
on first boot to gain access.
I created a regular user named “pi” and did the usual prep for a toy system: disable SELinux and disable firewalld, to avoid losing precious motivation on those.
I want to access the Pi by name; so I enabled Multicast DNS in the systemd-resolved
config, and now I can refer to it with a name like pi.local
on my home network.
All of the setup is automated in Ansible playbooks, when I break the machine I can rebuild it without having to go back to this blog post.
Audio
The Pi’s analogue audio output isn’t supported in Fedora IoT 38, but HDMI audio is. HDMI audio is handled by Broadcom VideoCore 4 hardware, driven by the vc4
driver.
As of Fedora IoT 38 this driver is blocklisted on the kernel commandline, and HDMI audio devices only appear after running modprobe vc4
. Removing the block caused Bluetooth to stop working; I don’t really want to know what’s going on here, so instead I added a systemd unit to run modprobe vc4
after basic.target
, and moved on.
I also had to add my pi
user to the audio
system group, done using systemd-sysusers in `/etc/sysusers.d`. Now I can see the audio devices, and (once installed) so can Pipewire and Wireplumber:
> aplay -l
**** List of PLAYBACK Hardware Devices ****
card 0: vc4hdmi0 [vc4-hdmi-0], device 0: MAI PCM i2s-hifi-0 [MAI PCM i2s-hifi-0]
Subdevices: 1/1
Subdevice #0: subdevice #0
card 1: vc4hdmi1 [vc4-hdmi-1], device 0: MAI PCM i2s-hifi-0 [MAI PCM i2s-hifi-0]
Subdevices: 1/1
Subdevice #0: subdevice #0
Amazingly, using aplay
I can now play audio from my TV.
Bluetooth
The Pi has a combined Bluetooth+Wifi adapter, the BCM4345C0. By default this needs manual probing with bcattach
to make it appear. Following the overlays README, we can add this line to cat /boot/efi/config.txt
for it to appear automatically on boot:
dtparam=krnbt=on
You can confirm it’s working by checking if /sys/class/bluetooth/
is populated with the hc0
controller.
From there I followed this guide from our friends at Collabora, “Using a Raspberry Pi as a Bluetooth speaker with PipeWire” and, rather amazingly, I managed to cast from my phone to the television a couple of times, after manually trusting and connecting the device from bluetoothctl
on the commandline.
Ideally I want to be able to stream audio without having to get out my laptop and log in to the speaker via SSH each time I arrive home so that it connects to my phone. Another short Python script named bluetooth-autoconnect solves that by watching for trusted devices to appear and connecting with them automatically. So I only have to SSH into the machine once per device to set it as trusted.
I still get connection failures sometimes, restarting Wireplumber usually works around those.
External hard disk
The external HD full of music and media doesn’t get automatically mounted, so I installed UDisks2 and udiskie to automatically mount it. I added a storage
group containing my ‘pi’ user, and a service running udiskie as ‘pi’ so that the external disk is mounted by this user.
The external disk I used was NTFS-formatted, which was a terrible idea. Every time the machine booted the filesystem would be marked dirty, no matter how cleanly I unmounted it before shutdown. I gave up on NTFS and copied the data to a second disk which is ext4-formatted.
NTFS: just say no.
Kodi
I figured getting the Kodi media server to work would be the biggest challenge. On Raspbian it’s as simple as apt install kodi
and everything just works, but Fedora don’t even package Kodi (presumably due to patent issues?).
The right way to do this is to use Flatpak. The ARM64 build of Kodi is now ready. (Pro tip, you can push a draft PR to test your changes on the Flathub ARM builder rather than spending a week building it on an emulated ARM device on a small laptop.)
Flatpak needs a display server to work with, and my plan is to run a minimal Weston compositor following these instructions.
Other services
For completeness, here are the other services I’m running. These are all containers, in keeping with the design of Fedora IoT.
- Caddy web server (using official Alpine-based image)
- Jellyfin media server (using official container image)
- Samba (using ServerContainers/samba image)
One very nice thing of switching from Docker to Podman is the improved systemd integration. If you just deploy a container then it doesn’t persist across reboots, so you need a systemd service as well. Podman can generate these, and Ansible’s containers.podman.podman_container module makes it easy. The Ansible playbook to deploy Samba looks like this:
- name: Samba container
containers.podman.podman_container:
name: samba
...
# Let systemd manage service
restart_policy: "no"
generate_systemd:
path: ~/.config/systemd/user
restart_policy: on-failure
- name: Start systemd unit
systemd_service: daemon_reload=true name=container-samba state=restarted scope=user
No need to manually create a systemd unit for each container any more \o/
Calliope + Beets + Tracker Miner FS will be the next step, but first I need to bulid an ARM64 compatible container containing all the necessary pieces.
And perhaps that retro radio case – I do have some old cassette walkmans that might do the job.
Have you got any small home servers running? Show me!