--- title: systemd Dynamic Users date: 2020-04-09 --- {% extends 'templates/base.html' %} {% block body %}

{{ title }}

{% 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 %}