I recently acquired a monome grid, a set of futuristic flashing buttons which can be used for controlling software, making music, and/or playing the popular 90’s game Lights Out.
There’s no sound from the device itself, all it outputs is a USB serial connection. Software instruments connect to the grid to receive button presses and control the lights via the widely-supported protocol Open Sound Control protocol. I am using monome-rs to convert the grid signals into MIDI, send them to Bitwig Studio and make interesting noises, which I am excited to share with you in the future, but first we need to talk about software packaging.
Monome provide a system service named serialosc, which connects to the grid hardware (over USB-serial) and provides the Open Sound Control endpoint. This program is not packaged in by Linux distributions and that is fine, it’s rather niche hardware and distro maintainers shouldn’t have support every last weird device. On the other hand, it’s rather crude to build it from source myself, install it into /usr/local, add a system service, etc. etc. Is there a better way?
First I tried bundling serialosc along with my control app using Flatpak, which is semi-possible – you can build and run the service, but it can’t see the device unless you set the super-insecure “–devices=all” mode, and it still can’t detect the device because udev is not available, so you would have to hardcode the driver name /dev/ttyACM0 and hotplug no longer works … basically this is not a good option.
Then I read about systemd’s Portable Services. This is a way to ship system services in containers, which sounds like the correct way to treat something like serialosc. So I followed the portable walkthrough and within a couple of hours the job was done: here’s a PR that could add Portable Services packaging upstream: https://github.com/monome/serialosc/pull/62
I really like the concept here, it has some pretty clear advantages as a way to distribute system services:
- Upstreams can deliver Linux binaries of services in a (relatively) distro-independent way
- It works on immutable-/usr systems like Fedora Silverblue and Endless OS
- It encourages containerized builds, which can lower the barrier for developing local improvements.
This is quite a new technology and I have mixed feelings about the current implementation. Firstly, when I went to screenshot the service for this blog post, i discovered it had broken:
Fixing the error was a matter of – disabling SELinux. On my Fedora 35 machine the SELinux profile seems totally unready for Portable Services, as evidenced in this bug I reported, and this similar bug someone else reported a year ago which was closed without fixing. I accept that you get a great OS like Fedora for free in return for being a beta tester of SELinux, but this suggests portable services are not yet ready widespread use.
I used the recommended mkosi build to create the serialosc container, which worked as documented and was pretty quick. All
mkosi operations have to run as
root. This is unfortunate and also interacts badly with the Git safe.directory feature (the source trees are owned by me, not root, so Git’s default config raises an error).
It’s a little surprising the standard OCI container format isn’t supported, only disk images or filesystem trees. Initially I built a 27MB squashfs, but this didn’t work as the container rootfs has to be read/write (another surprise) so I deployed a tree of files in the end. The service container is Fedora-based and comes out at 76MB across 5,200 files – that’s significant bloat around a service implemented in a few 1000 lines of C code. If mkosi supported Alpine Linux as base OS we could likely reduce this overhead significantly.
The build/test workflow could be optimised but is already workable, the following steps take about 30 seconds with a warm
sudo mkosi -t directory -o /opt/serialosc --force
sudo portablectl reattach --enable --now /opt/serialosc --profile trusted
We are using “Trusted” profile because the service needs access to udev and /dev, by the way.
All in all, the core pieces are already in place for a very promising new technology that should make it easier for 3rd parties to provide Linux system-level software in a safe and convenient way, well done to the systemd team for a well executed concept. All it lacks is some polish around the tooling and integration.
4 thoughts on “Trying out systemd’s Portable Services”
Nice write-up – could you share more details about this point:
> Initially I build a 27MB squashfs, but this didn’t work as the container rootfs has to be read/write (another surprise)
That should definitely, 100% work, so if there’s a bug somewhere I’d like to know so I can fix it – or if it’s a configuration problem, we can improve the documentation. Thanks!
> It’s a little surprising the standard OCI container format isn’t supported
it’s because the squashfs / directory is supported by the Linux kernel natively to mount, and OCI is not. portablectl does nothing special but add RootDirectory=/RootImage= as a drop-in for the service. Supporting OCI would be more involved, probably would require unpacking and flattening the OCI to a directory.
> I used the recommended mkosi build to create the serialosc container,
another interesting way of building portable images is with nixos.
here’s me building a php application as a portable service https://github.com/gdamjan/tt-rss-service
and my effort to add the functionality in nixos itself https://github.com/NixOS/nixpkgs/pull/161278