wiki/structured/linux.systemd.org
Gabriel Arazas 7c75fc2531 Update notes on the cloud computing course and Nix
Was able to finally pass through the ArgoCD installation among other
things. I also updated more exercises to tangle my solutions into a file.
2021-07-04 11:55:02 +08:00

274 lines
8.6 KiB
Org Mode

#+title: Exploring systemd features
#+date: "2021-05-20 22:37:22 +08:00"
#+date_modified: "2021-07-01 11:54:34 +08:00"
#+language: en
#+property: header-args :eval no
systemd is a big tool for a big system.
Let's explore some of them from a perspective of a wannabe power user.
* systemd at user-level
systemd has the ability to run at user-level empowering the user to manage their own system with their own settings.
It immensely helps separating user-specific settings from the system-wide settings.
systemd looks for the units from certain paths.
You can look for them from the =systemd.unit.5= manual page.
To run systemd as a user instance, simply add a =--user= flag beforehand for =systemctl= and other systemd binaries, if applicable.
#+begin_src shell :results none
# See how different the output when run at user- and system-level.
systemctl --user show-units
systemctl show-units
systemctl --user show-environment
systemctl show-environment
systemctl --user start $SERVICE
#+end_src
* Timers as cron replacement
You can schedule tasks with timers.
If systemd is compiled with the feature, it makes cron unnecessary.
systemd has different ways to denote time.
- Timespans denote the duration — e.g., =100 seconds=, =5M 3w=.
- Timestamps refer to a specific point in time — e.g., =2021-04-02=, =today=, =now=.
- Calendar events can refer to more than one point of time — e.g., =*-*-4/2=, =Sun,Wed,Fri *-1/2-1/8=.
Here's an example of setting a timer for an example backup service.
The following timer unit sets it to execute every day at 18:00.
#+begin_src ini
[Unit]
Description=A deduplicated backup from my computer
Documentation=man:borg(1) https://borgbackup.readthedocs.io/
[Timer]
Unit=borg-backup.service
OnCalendar=*-*-* 18:00:00
Persistent=true
[Install]
WantedBy=graphical.target
#+end_src
This will trigger =borg-backup.service= from the load path.
But you can omit it if you named the timer unit file similarly (e.g., =borg-backup.timer= with =borg-backup.service=).
You can find more information about it from the =systemd.time.5= manual page.
Furthermore, systemd has a testing tool for time with ~systemd-analyze {timespan,timestamp,calendar}~.
#+begin_src shell :eval yes
printf "Timespan example:\n"
printf "..............\n"
systemd-analyze timespan 4000min
printf "..............\n\n"
printf "Timestamp example:\n"
printf "..............\n"
systemd-analyze timestamp 2021-07-01
printf "..............\n\n"
printf "Calendar example:\n"
printf "..............\n"
systemd-analyze calendar "*-1/4-5 0/2:00:00"
printf "..............\n\n"
#+end_src
#+results:
#+begin_example
Timespan example:
..............
Original: 4000min
μs: 240000000000
Human: 2d 18h 40min
..............
Timestamp example:
..............
Original form: 2021-07-01
Normalized form: Thu 2021-07-01 00:00:00 PST
(in UTC): Wed 2021-06-30 16:00:00 UTC
UNIX seconds: @1625068800
From now: 11h ago
..............
Calendar example:
..............
Original form: *-1/4-5 0/2:00:00
Normalized form: *-01/4-05 00/2:00:00
Next elapse: Sun 2021-09-05 00:00:00 PST
(in UTC): Sat 2021-09-04 16:00:00 UTC
From now: 2 months 4 days left
..............
#+end_example
* Unit templates
You can create unit templates which is useful for simple services that only requires an argument.
Rather than creating individual simple service files, let systemd handle it.
For example, you may want to spawn a service for Borgmatic with multiple repos.
If you don't know templates, the dumb way to serve multiple repos is to create individual unit files for each.
If you want to schedule them, you also have to create a timer unit for each.
The more efficient solution is to use templates.
To make a unit template, there are only a handful of requirements:
- Addition of =%i= to represent the template value.
- The unit file name has to end with =@= (e.g., =unit-name@.service=, =unit-name@.timer=).
This could be compressed into a template for a service unit.
The following code shows how to create one.
#+begin_src ini
[Unit]
Description=Periodic safety backup for %i
Documentation=man:borg(1) https://www.borgbackup.org/
[Service]
Type=simple
ExecStart=borgmatic --config %i --verbose
[Install]
WantedBy=default.target
#+end_src
To use the service, you have to give it a value — e.g., ~systemctl --user start borg-backup@test.yaml.service~.
That's all good but what about scheduling them?
What if you want to create an archive every hour starting at 08:00?
You can just create a templated timer unit.
#+begin_src ini
[Unit]
Description=Periodic safety backup for %i
Documentation=man:borg(1) https://www.borgbackup.org/
[Timer]
Unit=borg-backup@%i.service
Calendar=08/1:00:00
Persistent=true
[Install]
WantedBy=default.target
#+end_src
* Transient units
You can create units on-the-go with =systemd-run=.
It generates transient unit files.
Though, this is oriented around service units, making it useful for one-time configurations and task scheduling.
Like most systemd-related binaries, this can configure in system- and user-level.
#+begin_src shell
# This will create a user-level service file with the given command as the task.
systemd-run --user borgmatic --config emergency-config.yaml --verbose
# Create a transient timer for the service.
systemd-run --user borg-backup@external-drive.service --on-calendar=12:00
#+end_src
* Service management
One of the functions of the system suite is service management.
Like most of the components, it can be used at user-level with their set locations, managing the service daemon, and all.
Just plop down a service unit file at one of the search paths and you can start managing right away.
For more information, see the manual page (i.e., =systemd.service.5=).
A summarized version can be found at [[Service configuration]].
Here's an example of a user service resided as =$HOME/.config/systemd/user/drive-backup.service=.
#+begin_src ini
[Unit]
Description=Periodic safety backup for my external drive
Documentation=man:borg(1) https://www.borgbackup.org/ https://torsion.org/borgmatic/
[Service]
Type=oneshot
ExecStart=%h/.nix-profile/bin/borgmatic --config %h/dotfiles/borgmatic/personal-drive.yaml --verbosity 2 create
ExecStart=%h/.nix-profile/bin/borgmatic --config %h/dotfiles/borgmatic/personal-drive.yaml --verbosity 2 prune
ExecStart=%h/.nix-profile/bin/borgmatic --config %h/dotfiles/borgmatic/personal-drive.yaml --verbosity 2 check
[Install]
WantedBy=default.target
#+end_src
You can then start the service with:
#+begin_src shell :eval no
systemctl --user start drive-backup.service
#+end_src
You can also stop it with the =stop= subcommand (e.g., ~systemctl --user stop drive-backup.service~) and restart it with =restart= (e.g., ~systemctl --user restart drive-backup.service~).
If you want to enable it at startup, you can go with =enable= subcommand.
(To disable it, use the =disable= subcommand.)
#+begin_src shell :eval no
systemctl --user enable drive-backup.service
#+end_src
systemd will use the configuration file as-is by the time it is started/enabled.
Which means if the config file has been modified after activation, it will not take effect until you restarted it.
For this, you can reload the daemon with =daemon-reload= subcommand.
But for simpler cases, you can use the =reload= subcommand without fully restarting the daemon.
#+begin_src shell :eval no
systemctl --user reload drive-backup.service
# You could also use...
# systemctl --user daemon-reload
# ...if you need a stronger option.
#+end_src
** Service configuration
There are different types of services.
- The most common type of service is =simple= which considers the unit active after the main process is forked (e.g., =Service.ExecStart=).
This is the recommended type for long-running processes.
- =oneshot= marks the service resolved after the main process exits.
Due to the behavior, it will directly go from activating to deactivating instead of active.
- =exec= considers the service active after the binary has been executed.
Aside from types, each service may have one or more commands although the behavior is set depending on the type.
- =ExecStart= which is usually the main command and most services will throw an error if it's missing.
All services, unless specified as a =oneshot= service, only have one of these values.
- =ExecStop= only executes after the main command successfully starts.
- =ExecStartPre= and =ExecStartPost= gives you additional commands that will be executed before and after the main command, respectively.
- =ExecStopPre= and =ExecStopPost= is similar to the pre- and post-start commands except for the stop command.
- =Reload= sets whether the service restarts on fail.
Values accepted are =no=, =on-failure=, and =on-success=.