JMM’s notes on

systemd

systemd is possibly my favorite “Linux-only” program (or set of programs/utilities). It lets you harness a ton of Linux-kernel-specific features without having to write any C. Like, I use it to manage cgroups, network namespaces, and even to set alarm clock reminders. It’s got great documentation that’s also helped me learn about features like eBPF.

Look at man systemd.directives(7) for a list of interesting parameters.

Resource control

See man systemd.resource-control(5).

Here’s an example of limiting IO for a service that’s repairing the Nix store:

systemctl set-property repairing-store.service --runtime IOReadBandwidthMax="/nix/store/ 1M"

Here, /nix/store/ is used to resolve the backing device where read bandwidth will be limited. You can then see how many bytes have been read with something like:

watch --interval 0.2 systemctl show repairing-store.service --property=IOReadBytes

systemd-run shell

systemd-run can be used to create shells with limited resources. Here’s an example where the maximum number of processes is limited:

systemd-run --user --shell -p TasksMax=10

And here’s an example where the $HOME directory is mounted read-only, and the Downloads directory is replaced with a temporary file system:

systemd-run --user --shell -p BindReadOnlyPaths="$HOME" -p TemporaryFileSystem="$HOME/Downloads/"

These examples didn’t require special privileges, but some properties like IPAddressDeny=any won’t work unless run using the system service manager. Here’s an example that blocks internet:

$ systemd-run --uid=$(whoami) --shell -p IPAddressDeny=any

(pkttyagent:971142): GLib-ERROR **: 16:23:42.326: creating thread 'gmain': Error creating thread: Resource temporarily unavailable
Running as unit: run-u7876.service
Press ^] three times within 1s to disconnect TTY.
$ ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
^C
--- 8.8.8.8 ping statistics ---
4 packets transmitted, 0 received, 100% packet loss, time 3109ms

Though you can get a similar result without higher privileges by using the PrivateNetwork= directive:

systemd-run --user --shell -p PrivateNetwork=true

I’m surprised this works without elevated privileges (it probably requires unprivileged userns clone), but this is pretty useful. Note that trying to use NetworkNamespacePath= will not work with --user from when I tried on 2023-12-18. (Which is good, by the way, since some network namespaces might be privileged in some way.) You can, however, escape PrivateNetwork=true using NetworkNamespacePath=, but it seems like you can only go to the root namespace.

machinectl shell

$ machinectl shell --uid=someuser
Connected to the local host. Press ^] three times within 1s to exit session.
bash-5.2$ whoami
whoami
someuser
bash-5.2$ pwd
pwd
/home/someuser
bash-5.2$ logout
Connection to the local host terminated.

systemd-run

systemd-run is a handy way to run commands as different users, or with different resource control settings, or at specific times. Sometimes I prefer it to sudo because it can tell me the resource usage of a specific command.

Here’s an example of doing a dry run of Nix garbage collection, scheduled in 1 minute.

systemd-run --on-active=1m --timer-property=AccuracySec=1s --unit=nix-gc-dry-run --remain-after-exit nix-collect-garbage --delete-older-than 90d --dry-run
$ systemctl status nix-gc-dry-run
    ● nix-gc-dry-run.service - /run/current-system/sw/bin/nix-collect-garbage --delete-older-than 90d --dry-run
     Loaded: loaded (/run/systemd/transient/nix-gc-dry-run.service; transient)
  Transient: yes
     Active: active (exited) since Thu 2024-02-22 13:37:00 PST; 35s ago
TriggeredBy: ● nix-gc-dry-run.timer
    Process: 1932545 ExecStart=/run/current-system/sw/bin/nix-collect-garbage --delete-older-than 90d --dry-run (code=exited, status=0/SUCCESS)
   Main PID: 1932545 (code=exited, status=0/SUCCESS)
         IP: 0B in, 0B out
        CPU: 32ms

Feb 22 13:37:00 ░░░░░ systemd[1]: Started /run/current-system/sw/bin/nix-collect-garbage …y-run.
Feb 22 13:37:00 ░░░░░ nix-collect-garbage[1932545]: removing old generations of profile /ni…nels
Feb 22 13:37:00 ░░░░░ nix-collect-garbage[1932545]: removing old generations of profile /ni…nels
Feb 22 13:37:00 ░░░░░ nix-collect-garbage[1932545]: removing old generations of profile /ni…file
Feb 22 13:37:00 ░░░░░ nix-collect-garbage[1932545]: removing old generations of profile /ni…stem
Feb 22 13:37:00 ░░░░░ nix-collect-garbage[1932545]: removing old generations of profile /ni…nels
Hint: Some lines were ellipsized, use -l to show in full.
$ 

To “garbage collect” the RemainAfterExit service, run systemctl status nix-gc-dry-run.

systemd-run like sudo

Here’s an example of using systemd-run kind of like sudo to run a command, since systemd-run --shell doesn’t allow you to specify one:

systemd-run -tdG echo hello

systemd-nspawn

Here’s an example of pulling Debian 12 from Debian’s cloud images. I’m using genericcloud since it’s smaller, and I’m using --verify=no because systemd-nspawn expects SHA256SUMS whereas cloud.debian.org provides SHA512SUMS.

$ machinectl pull-raw --verify=no https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-amd64.qcow2
Enqueued transfer job 1. Press C-c to continue download in background.
Pulling 'https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-amd64.qcow2', saving as 'debian-12-genericcloud-amd64'.
HTTP request to https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-amd64.nspawn failed with code 404.
Settings file could not be retrieved, proceeding without.
HTTP request to https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-amd64.verity failed with code 404.
Verity integrity file could not be retrieved, proceeding without.
HTTP request to https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-amd64.roothash failed with code 404.
Root hash file could not be retrieved, proceeding without.
HTTP request to https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-amd64.roothash.p7s failed with code 404.
Root hash signature file could not be retrieved, proceeding without.
Downloading 267.1M for https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-amd64.qcow2.
Got 1% of https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-amd64.qcow2. ░░░░░░░ left at ░░░░░░/s.
...
Got 99% of https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-amd64.qcow2. ░░ left at ░░░░░░.
Acquired 267.1M.
Download of https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-amd64.qcow2 complete.
Unpacking QCOW2 file.
Created new local image 'debian-12-genericcloud-amd64'.
Operation completed successfully.
Exiting.

And here’s an example of starting it up and logging in. There’s a couple errors for some reason.

# systemd-nspawn -M debian-12-genericcloud-amd64
Spawning container debian-12-genericcloud-amd64 on /var/lib/machines/debian-12-genericcloud-amd64.raw.
Press Ctrl-] three times within 1s to kill container.
-bash: id: command not found
-bash: [: : integer expression expected
root@debian-12-genericcloud-amd64:~# passwd
New password: hunter2
Retype new password: *******
passwd: password updated successfully
root@debian-12-genericcloud-amd64:~# logout
Container debian-12-genericcloud-amd64 exited successfully.
$ machinectl start debian-12-genericcloud-amd64
$ machinectl list
MACHINE                      CLASS     SERVICE        OS     VERSION ADDRESSES
debian-12-genericcloud-amd64 container systemd-nspawn debian 12      -

1 machines listed.
$ machinectl login debian-12-genericcloud-amd64
Connected to machine debian-12-genericcloud-amd64. Press ^] three times within 1s to exit session.

Debian GNU/Linux 12 debian-12-genericcloud-amd64 pts/1

debian-12-genericcloud-amd64 login: root
Password:
Linux debian-12-genericcloud-amd64 6.6.4 #1-NixOS ░░░░░░░░

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
root@debian-12-genericcloud-amd64:~# which apt
/usr/bin/apt
root@debian-12-genericcloud-amd64:~#