Launching a user-level systemd unit on boot

Make sure that you have completed the steps described in the initial setup page before starting this tutorial.

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: 0.15.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: 0.15.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: 0.15.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: 0.15.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: 0.15.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: 0.15.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.