aboutsummaryrefslogtreecommitdiff
path: root/content/blog/2024-08-18-user-services-with-systemd.md
blob: b79c02b454b8e4c6aca1735dbb2d703ceab7dffc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
+++
title = "User Services With Systemd"
date = "2024-08-18T19:05:10-04:00"

tags = ["systemd","selfhosting"]
+++

# Your very own `systemd`

Recently, I've started using `systemd` order to manage personal services on my servers. 

Before that, I had set up classic system-wide unit files or, for more ad-hoc services, started the process inside of a `screen` session, then detaching, hoping the process didn't crash overnight.

Using a user instance of `systemd` is way better, since you get stuff like `journalctl` to view logs and `RestartAlways` keep things running after random crashes. Plus, all configuration now lives in `$HOME` making it easy to edit and back up.    

In this post, I will show you how to set this up on any modern Debian-based system (or any distro that uses `systemd`, really).

_Debian 12.6 (bookworm) was used at the time of writing this post._

# Caveats

There are a few things to note with this approach:

* User units **cannot** depend on system-wide units, or vice versa. For example, your user service cannot depend on system-wide `redis-server`. To specify dependencies between your services, you'll need to run all of those dependencies as user services. 

* User units are run without privileges so naturally they can't do `root` things like edit files outside of `$HOME` and listen below port `1024`.

* Environment variables set in `~/.zshrc`, `~/.bashrc`, etc are not inherited. See the [environment variables section](https://wiki.archlinux.org/title/Systemd/User#Environment_variables) for more information on how to set/import environment variables for user units.

# Getting Started

## Enable linger for your user

By default, `systemd` user instances are only started after the first login of that user then killed once their last session is closed. This behavior makes sense in a desktop environment but not so much for a server environment where you typically want your daemons running at all times. 

In order to change this behavior, you have to enable _linger_ for your user by running the following: 

    $ loginctl enable-linger

You can verify if lingering is enabled by ensuring the column for `LINGER` says `yes` for your user when running:

    $ loginctl list-users

## Create configuration directory
The [unit files](https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html) for our services will live in `$HOME/.config/systemd/user/`. Ensure it's created using by using `mkdir -p`, like so:

    $ mkdir -p $HOME/.config/systemd/user/

## Setting up an example service

We can now place this `example.service` unit in `$HOME/.config/systemd/user/`:

```
[Unit]
Description=Example Service

[Service]
Restart=always
RestartSec=1s
WorkingDirectory=/home/me/my-service-dir
ExecStart=racket hello-world-server.rkt

[Install]
WantedBy=default.target
```

## Reload unit files
Just like system-wide `systemd`, if you edit or create new unit files for your user, you'll need to reload your copy of `systemd` by running `systemctl daemon-reload`, like so:

    $ systemctl --user daemon-reload

### Start service on boot

This starts `example` as soon as your copy `systemd` is launched (which should be at boot, since we enabled lingering):

    $ systemctl --user enable example

### Starting, Stopping and Status

You can now run the same `systemctl` commands you are used to, except they are run as your user (instead of `root`) and now require the `--user` argument:

    $ systemctl --user start example

    $ systemctl --user stop example
    
    $ systemctl --user status example

    $ systemctl --user restart example

### Logs

Similar to `systemctl`, you'll need to pass `--user` to `journalctl`:

    $ journalctl --user --unit=example


# More information

You can read more about this feature on the [Arch Linux Wiki: systemd/User](https://wiki.archlinux.org/title/Systemd/User).