{% markdown %}
The Linux init system [systemd](https://en.wikipedia.org/wiki/Systemd) has its controversies, but it also has some pretty neat features, my favorite of which is _Dynamic Users_.
Lennart Poettering's blog has a [full rundown of the ins and outs](http://0pointer.net/blog/dynamic-users-with-systemd.html), but I'll summarize the key points below, and document a few tricks.
contents…
[TOC]
## Users and groups and Name Service Switch, oh my!
On Linux, when you create a user it is usually added to `/etc/passwd`, and groups it is in are added to `/etc/groups`.
However, these databases and others can actually come from multiple sources, controlled by the [Name Service Switch](https://en.wikipedia.org/wiki/Name_Service_Switch), and on modern Linux one of those sources is systemd.
When configured to, systemd will create a user for the lifetime of a Unit to run that Unit.
The user is never added to `/etc/passwd`, but exists only at runtime, and when the Unit finishes the user goes away.
Although they are very locked down by default, this user is a full Linux user, and can be added to groups, write to disk, or be given capabilities.
In particular, systemd provides a system for _State Directories_, which will be writable by the created user, persist after the Unit finishes, and will be fixed-up the next time the Unit runs to be writeable by the next dynamically created user, which can be used to store service data, or to simulate a home directory.
## Simple services
A very useful result of Dynamic Users is to simplify the packaging and running of services.
Instead of having to create a user to run a service when it is installed and remove it later when the service is uninstalled, the service package only needs to install a service file as the user is created at runtime.
For example, below is the configuration for a web service of mine:
```sh
$ cat /lib/systemd/system/log.eth.moe
[Unit]
Description=Backend server for https://log.eth.moe
[Service]
# Create a user at runtime.
# It will have a random name.
DynamicUser=yes
# Set the user's primary group to www-data.
Group=www-data
# Create a temporary runtime directory at /run/log-eth-moe/.
RuntimeDirectory=log-eth-moe
# Create a persistent state directory at /var/lib/log-eth-moe/.
StateDirectory=log-eth-moe
ExecStart=/usr/bin/log.eth.moe -socket /run/log-eth-moe/listen.sock -dir /var/lib/log-eth-moe/
[Install]
WantedBy=multi-user.target
```
It's also generally useful any time you want a non-human user to run something.
For example, below is [my configuration](https://github.com/ethulhu/kodi-systemd-service/) for running the [Kodi media center](https://kodi.tv/) on a Raspberry Pi:
```sh
$ cat /lib/systemd/system/kodi.service
[Unit]
Description=Kodi Media Center
After=systemd-user-sessions.service network.target sound.target
[Service]
# Create a user at runtime.
DynamicUser=yes
# Call that user "kodi".
User=kodi
# Add it to useful groups for a media center.
# Note that these are all supplementary groups,
# and the dynamic user has no primary group.
SupplementaryGroups=audio
SupplementaryGroups=input
SupplementaryGroups=plugdev
SupplementaryGroups=video
# Create a persistent state directory at /var/lib/kodi.
StateDirectory=kodi
# Set the home directory of the dynamic user to /var/lib/kodi.
Environment=HOME=/var/lib/kodi
ExecStart=/usr/bin/kodi-standalone
[Install]
WantedBy=multi-user.target
```
## Sandboxed Steam?
[Steam](https://store.steampowered.com/) is great, but it also requires running other people's code, so it's best to sandbox it a little.
While [some people](https://blog.jessfraz.com/post/docker-containers-on-the-desktop/) might go to the lengths of [running Steam in Docker](http://blog.drwahl.me/steam-running-in-docker-lxc/), I use systemd to achieve similar ends.
The steps of this approach are:
- Create a group to share your display with.
For myself, `eth`, I created `eth-x11`.
- Allow members of that group to access your display.
- Run Steam as a member of that group, directed to use your display.
### Implementation
After creating your group, grant that group permission to use your display, substituting `eth-x11` for your group:
```sh
$ xhost +si:localgroup:eth-x11
```
This will need to be done every X11 session, and should probably be added to your `.xinitrc` or `.xsession`.
Then create a script to run Steam under `systemd-run`:
- Run the command with a TTY so that Steam's log messages are output to your terminal for debugging.
- Create a user at runtime.
- Set the _primary_ group of the user to be `eth-x11`.
The `xhost` command above requires it to be the primary group.
- Add the user to the groups `audio`, `input`, and `video`.
- Create a `StateDirectory` called `steam`, which `systemd` will put at `/var/lib/steam/`.
- Set the `$DISPLAY` to be the current X11 display.
- Set the `$HOME` of the running process to `/var/lib/steam/`.
```sh
$ cat /usr/local/bin/steam
#!/bin/sh
set -eux
systemd-run \
--pty \
--property=DynamicUser=yes \
--property=Group=eth-x11 \
--property=SupplementaryGroups=audio \
--property=SupplementaryGroups=input \
--property=SupplementaryGroups=video \
--property=StateDirectory=steam \
--property=Environment=DISPLAY=${DISPLAY} \
--property=Environment=HOME=/var/lib/steam \
/usr/games/steam
```
Unfortunately, to use `systemd-run` you must either be root or authenticate yourself to systemd, but the experience can be improved with `sudoers` or setuid on the script.
{% endmarkdown %}
{% endblock %}