Moving to OpenRC's user services
This Sunday was highlighted by a sudden need to hack on my dotfiles. Specifically, trying to
move as much as possible from my .xinitrc and my custom clean shutdown script to OpenRC's brand new user services (well, stable since May 2025).
Pipewire §
First obvious target and the main reason the OpenRC devs (well, mostly @navi-desu, sincere thanks) started working on it, since it was made with systemd in mind all along (so it provides service files for it).
As the wiki said, it was as simple as this:
$ rc-update add --user pipewire default
$ rc-update add --user pipewire-pulse default
$ rc-update add --user wireplumber default
$ sed -i '/gentoo-pipewire-launch/d' ~/.xinitrcJust Werks™, one less hack needed.
D-Bus §
Not yet tested with a reboot and clearly less smooth than PipeWire:
$ rc-update --user add dbus default $ edit ~/.xinitrc # Replace `eval $(dbus-launch --sh-syntax --exit-with-session)` # with # export DBUS_SESSION_BUS_ADDRESS="unix:path=${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/bus" # https://wiki.gentoo.org/wiki/D-Bus#The_session_bus
FUSE unmounting §
Some stuff doesn't have any user script already premade, so open up your can of elbow grease! For example, orderly unmounting of FUSE mounts before shutdown (e.g. sshfs, gocryptfs):
$ cat ~/.config/rc/init.d/fuse-cleanup #!/sbin/openrc-run start() { true; } stop() { local status=0 mount | awk '$5 ~ /^fuse\..*/ {print $3}' while IFS= read -r mp do ebegin "Unmounting $mp..." fusermount3 -u "$mp" eend $? || status=1 done return $status }
Easy peasy lemon squeezy.
Music player §
Last item and a bit more painful for me, because I use my own and this migration required me to improve the IPC method used between it and its graphical interface (a lemonbar script) to allow fully asynchronous message sending.
But what relatively portable UNIX tool (available from the shell) is there for a message queue
that can buffer writes before the reader is online? UNIX sockets are out. Good ol' files (preferably
on tmpfs) locked via flock is a sensible solution I've used in the past, but because I wanted efficient blocking instead of polling on the listener side,
I stayed with my named pipes.
The key is then to always have a dummy reader (NB: that doesn't actually read anything) on the
pipe to avoid blocking writes/ EPIPE/SIGPIPE when there's no reader on
the receiving end and a dummy writer to never get EOF when the last writer is gone.
Quite simple, the side that appears first creates the pipe and the dummy process. In sh:
#!/bin/sh set -eu fifo=${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/mus_status.fifo ! [ -e "$fifo" ] && { mkfifo -m 600 "$fifo" sh -c ' exec 3<>"$1" while [ -e "$1" ]; do sleep 1; done ' "$fifo dummy reader/writer" "$fifo" & } cat >"$fifo"
And then it's just a matter of using OpenRC's supervise-daemon to babysit mus_daemon:
$ cat ~/.config/rc/init.d/mus #!/sbin/openrc-run depend(){ need pipewire } description="mus daemon" supervisor=supervise-daemon supervise_daemon_args="-I 2:0 -N -10 -2 ${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/mus.log" command="/usr/local/bin/mus_daemon" $ cat ~/.config/rc/conf.d/mus command_args="-s ~/Programming/lemonbar-tcl/mus_status.sh"