Launching a user-level systemd unit on boot

Complete all the steps described in the initial setup page before starting this tutorial. Make sure you have create file ssh-key.pub following the instructions provided in the prerequisites for the tutorial. We will use this key in the Butane configuration file that we are about to write.

In this tutorial, we will set up a user level systemd unit for an unprivileged user. There are times when it’s helpful to launch a user-level systemd unit without having to log in. For example, you may wish to launch a container that provides a network service or run an HPC job. For this setup, we will add the following to a Butane config:

  • A user level systemd unit: /home/sleeper/.config/systemd/user/linger-example.service.

  • Enable it as a user level systemd service.

Setting up the systemd unit

In this example, we will launch a systemd service for the user sleeper. First, let’s create a user:

variant: fcos
version: 1.5.0
passwd:
  users:
    - name: sleeper

This will also create the home directory for the sleeper user. Then we can add the systemd unit:

variant: fcos
version: 1.5.0
storage:
  files:
    - path: /home/sleeper/.config/systemd/user/linger-example.service
      mode: 0644
      contents:
        inline: |
          [Unit]
          Description=A systemd user unit demo
          [Service]
          ExecStart=/usr/bin/sleep infinity
      user:
        name: sleeper
      group:
        name: sleeper

System services can be directly enabled in Butane configs but user level services have to be manually enabled for now:

variant: fcos
version: 1.5.0
storage:
  directories:
    - path: /home/sleeper/.config/systemd/user/default.target.wants
      mode: 0755
      user:
        name: sleeper
      group:
        name: sleeper
  links:
    - path: /home/sleeper/.config/systemd/user/default.target.wants/linger-example.service
      user:
        name: sleeper
      group:
        name: sleeper
      target: /home/sleeper/.config/systemd/user/linger-example.service
      hard: false

We set up lingering for the systemd user level instance so that it gets started directly on boot and stays running:

variant: fcos
version: 1.5.0
storage:
  files:
    - path: /var/lib/systemd/linger/sleeper
      mode: 0644

As the following directories do not exist yet, we will have to create them to tell Ignition to set the right ownership and permissions:

variant: fcos
version: 1.5.0
storage:
  directories:
    - path: /home/sleeper/.config
      mode: 0755
      user:
        name: sleeper
      group:
        name: sleeper
    - path: /home/sleeper/.config/systemd
      mode: 0755
      user:
        name: sleeper
      group:
        name: sleeper
    - path: /home/sleeper/.config/systemd/user
      mode: 0755
      user:
        name: sleeper
      group:
        name: sleeper
    - path: /home/sleeper/.config/systemd/user/default.target.wants
      mode: 0755
      user:
        name: sleeper
      group:
        name: sleeper

Writing the Butane config and converting to Ignition

The final Butane config, stored in user.bu, will be:

variant: fcos
version: 1.5.0
passwd:
  users:
    - name: core
      ssh_authorized_keys_local:
        - ssh-key.pub
    - name: sleeper
storage:
  directories:
    - path: /home/sleeper/.config
      mode: 0755
      user:
        name: sleeper
      group:
        name: sleeper
    - path: /home/sleeper/.config/systemd
      mode: 0755
      user:
        name: sleeper
      group:
        name: sleeper
    - path: /home/sleeper/.config/systemd/user
      mode: 0755
      user:
        name: sleeper
      group:
        name: sleeper
    - path: /home/sleeper/.config/systemd/user/default.target.wants
      mode: 0755
      user:
        name: sleeper
      group:
        name: sleeper
  files:
    - path: /var/lib/systemd/linger/sleeper
      mode: 0644
    - path: /home/sleeper/.config/systemd/user/linger-example.service
      mode: 0644
      contents:
        inline: |
          [Unit]
          Description=A systemd user unit demo
          [Service]
          ExecStart=/usr/bin/sleep infinity
      user:
        name: sleeper
      group:
        name: sleeper
  links:
    - path: /home/sleeper/.config/systemd/user/default.target.wants/linger-example.service
      user:
        name: sleeper
      group:
        name: sleeper
      target: /home/sleeper/.config/systemd/user/linger-example.service
      hard: false

This config can be converted to Ignition:

butane --pretty --strict --files-dir=./ user.bu --output user.ign

Testing

Just as before we will use the following to boot the instance:

# Setup the correct SELinux label to allow access to the config
chcon --verbose --type svirt_home_t user.ign

# Start a Fedora CoreOS virtual machine
virt-install --name=fcos --vcpus=2 --ram=2048 --os-variant=fedora-coreos-stable \
    --import --network=bridge=virbr0 --graphics=none \
    --qemu-commandline="-fw_cfg name=opt/com.coreos/config,file=${PWD}/user.ign" \
    --disk=size=20,backing_store=${PWD}/fedora-coreos.qcow2

We can then verify that the unit has been started under the sleeper systemd user instance:

[core@localhost ~]$ sudo machinectl shell sleeper@
Connected to the local host. Press ^] three times within 1s to exit session.
[sleeper@localhost ~]$ systemctl --user status
● localhost.localdomain
    State: running
    Units: 157 loaded (incl. loaded aliases)
     Jobs: 0 queued
   Failed: 0 units
    Since: Thu 2023-08-03 18:31:27 UTC; 23s ago
  systemd: 253.4-1.fc38
   CGroup: /user.slice/user-1001.slice/user@1001.service
           ├─app.slice
           │ └─linger-example.service
           │   └─1589 /usr/bin/sleep infinity
           └─init.scope
             ├─1489 /usr/lib/systemd/systemd --user
             └─1496 "(sd-pam)"
[sleeper@localhost ~]$ systemctl --user status linger-example.service
● linger-example.service - A systemd user unit demo
     Loaded: loaded (/var/home/sleeper/.config/systemd/user/linger-example.service; enabled; preset: disabled)
    Drop-In: /usr/lib/systemd/user/service.d
             └─10-timeout-abort.conf
     Active: active (running) since Thu 2023-08-03 18:31:27 UTC; 38s ago
   Main PID: 1589 (sleep)
      Tasks: 1 (limit: 2238)
     Memory: 224.0K
        CPU: 1ms
     CGroup: /user.slice/user-1001.slice/user@1001.service/app.slice/linger-example.service
             └─1589 /usr/bin/sleep infinity

Aug 03 18:31:27 localhost.localdomain systemd[1489]: Started linger-example.service - A systemd user unit demo.

Cleanup

You can then take down the instance. First, disconnect from the serial console by pressing CTRL + ] and then destroy the machine:

virsh destroy fcos
virsh undefine --remove-all-storage fcos

You may now proceed with the next tutorial.