(Mis)using s6 to run complementary desktop applications
s6 is a small suite of programs for UNIX, designed to allow process supervision
Motivation and reasoning
Over the years, I have started accumulating applications that I rely on running in the background. In fact, those are:
artha
keepassxc
urxvtd
compton
(along withambient-dim.sh
)mpd
blueman
moltengamepad
- some ssh mount
mpd
transmission
viber
Why supervise (at user-level)?
- They crash. Proprietary applications like Viber I’d rather restart than fix.
- They don’t belong in my system’s boot process.
- These are “services” as far as I’m concerned. For example, my desktop isn’t functional without
urxvrd
so I can’t afford it being down. - Some of these applications log to stdout/stderr, but if you run them from
~/.xinitrc
, you can’t tell what’s outputting what (although, yes, you can manually use a logger likes6-log
).
Why not
supervisord
?supervisord
turned out to be a pain to use as one has to edit some lengthy config file to manage services. This is not the way I’m used to doing things at all withOpenRC
.s6
on the uses structured directories and supports logging out of the box.
Setup
Service groups
You will notice that some of these applications (eg mpd
) should only ever be ran once, while others are tied to the X
session. For that, I use two supervision trees, one of which is executed in ~/.xinitrc
so that $DISPLAY
is set.
All I needed to get things running was a directory per service:
$ tree -L 4 ~/.services
~/.services
├── available
│ ├── ambient
│ │ ├── log
│ │ │ └── run
│ │ └── run
│ ├── artha
│ │ ├── log
│ │ │ └── run
│ │ └── run -> /usr/bin/artha
│ ├── blueman
│ │ ├── finish
│ │ ├── log
│ │ │ └── run
│ │ └── run
│ ├── compton
│ │ ├── log
│ │ │ └── run
│ │ └── run
│ ├── keepassxc
│ │ ├── log
│ │ │ └── run
│ │ └── run
│ ├── moltengamepad
│ │ ├── run
│ ├── mount-s
│ │ ├── run
│ ├── mpd
│ │ ├── run
│ ├── transmission
│ │ ├── run
│ ├── urxvtd
│ │ ├── log
│ │ │ └── run
│ │ └── run
│ └── viber
│ ├── log
│ │ └── run
│ └── run
├── tty
│ ├── moltengamepad -> ../available/moltengamepad
│ ├── mount-s -> ../available/mount-s
│ ├── mpd -> ../available/mpd/
│ └── transmission -> ../available/transmission
└── xorg
├── ambient -> ../available/ambient
├── artha -> ../available/artha
├── blueman -> ../available/blueman
├── compton -> ../available/compton
├── keepassxc -> ../available/keepassxc
├── urxvtd -> ../available/urxvtd
└── viber -> ../available/viber
run
Each of which contains a run
file that is responsible for bringing up the service. For example:
#!/bin/sh
exec 2>&1
exec urxvtd -q -o
Note that they must all run in the foreground, for example you need to use -f
with sshfs
.
log
If you want a service to be logged, you let s6-svscan
know that by creating a log
directory. That will pipe run
’s stdout
to log/run
’s stdin
.
A simple log/run
that logs everything with a human-readable timestamp to the current directory would be:
#!/bin/sh
s6-log T ./
Running all Xorg
-tied applications
I prepare and supervise a supervision directory tree for a given $DISPLAY
like so:
mkdir -p "/tmp/services-$DISPLAY"
cp -Lr ~/.services/xorg/* "/tmp/services-$DISPLAY/"
s6-svscan /tmp/services-$DISPLAY &! # zshism
This results in the following process tree:
s6-svscan /tmp/services-:0
├─ s6-supervise artha
│ └─ run
├─ s6-supervise artha/log
│ └─ /bin/sh ./run
│ └─ s6-log T ./
├─ s6-supervise viber
│ └─ /opt/viber/Viber
│ └─ Viber
├─ s6-supervise viber/log
│ └─ /bin/sh ./run
│ └─ s6-log T ./
├─ s6-supervise ambient
│ └─ /bin/bash ~/src/compton/dbus-examples/ambient-dim.sh /dev/video0
│ └─ ffmpeg -hide_banner -f video4linux2 -s 640x480 -i /dev/video0 -filter:v fps=fps=30, showinfo -f null -
│ └─ /bin/bash ~/src/compton/dbus-examples/ambient-dim.sh /dev/video0
│ ├─ /bin/bash ~/src/compton/dbus-examples/ambient-dim.sh /dev/video0
│ └─ grep -Po (?<=mean:\[)[0-9]*
├─ s6-supervise ambient/log
│ └─ /bin/sh ./run
│ └─ s6-log T ./
├─ s6-supervise compton
│ └─ ~/src/compton/compton --dbus
├─ s6-supervise compton/log
│ └─ /bin/sh ./run
│ └─ s6-log T ./
├─ s6-supervise keepassxc
│ └─ keepassxc
├─ s6-supervise keepassxc/log
│ └─ /bin/sh ./run
│ └─ s6-log T ./
├─ s6-supervise blueman
│ └─ python3.4 /usr/bin/blueman-applet
├─ s6-supervise blueman/log
│ └─ /bin/sh ./run
│ └─ s6-log T ./
├─ s6-supervise urxvtd
│ └─ urxvtd -q -o
└─ s6-supervise urxvtd/log
└─ /bin/sh ./run
└─ s6-log T ./
~/.services/tty
does not need this preparation as there will only ever be one s6-svscan
for all of its contents.
Management
Bringing any service down is a matter of calling:
s6-svc -d /tmp/services-:0/compton
No fiddling with pkill
or dealing with accidentally running multiple instances of one thing.
Exiting
Given that I run s6-svscan through ~/.xinitrc
, upon leaving X, the unthinkable is done: the supervisor is brought down. After all, the majority of these applications are reliant on X running. However, s6
is designed with one purpose in life:
The services must remain up at all costs.
Therefore, one needs to explicitly ask all instances s6-supervise
to end the services. To do that I append the following after my WM in ~/.xinitrc
:
for svc in "/tmp/services-$DISPLAY"/*; do s6-svc -d "$svc"; done