Poradnik: systemd — cz. 2
Wstęp
Poprzednia część jest tutajKolejność poszukiwania plików unitów
Szczególnym typem unitu jest "service", czyli unit odpowiedzialny za uruchamianie usług. A co jeśli chcemy uruchomić starszą usługę, która nie ma własnego unitu zgodnego z systemd? Możemy to zrobić, ale ma to swoje ograniczenia. Znacznie lepszym pomysłem jest stworzenie dedykowanego dla tej usługi unitu. Systemd zachowuje częściową kompatybilność ze skryptami z rodziny SysV, czyli tymi, które zwyczajowo były umieszczane w /etc/init.d/. Częściową, ponieważ korzystając z nich możemy napotkać problemy następującej natury:
- mamy ograniczony wpływ na to jakie usługi zostaną uruchomione przed, a jakie po naszej usłudze
- monitorowanie procesów naszej usługi może być utrudnione
- zbieranie logów z naszej usługi również może być utrudnione (usługa journald teraz to robi)
Jeśli systemd jest zmuszony uruchomić skrypt "legacy" z /etc/init.d/, to gdy sprawdzimy status tej usługi w opisie usługi zobaczymy "LSB" (Linux Standard Base) i ścieżkę do pliku startowego.
To jest kolejność przeszukiwania katalogów w celu znalezienia pliku unitu:
/run -> /etc -> /usr -> /etc/init.d
Załóżmy, że chcemy uruchomić serwis o nazwie "abc", wtedy systemd przeszukuje "swoje" katalogi w kolejności:
- /run - jeśli serwis jest już uruchomiony, to nie trzeba nic robić, koniec
- /etc - jeśli admin ma swoją wersją, to ona jest uruchamiana, jeśli nie, szukamy dalej
- /usr - jeśli serwis został dostarczony przez pakiety, to on zostanie uruchomiony
- /etc/init.d - w ostateczności
Typy serwisów
- simple - zwykły proces, który uruchomiony w terminalu nie zwraca znaku zachęty
- forking - serwis, który po uruchomieniu "forkuje się" np: apache, a potem działa dalej... (uruchomiony w terminalu przechodzi w tło, odpina się od termianala i zwraca prompt)
- oneshot - Jak "simple", ale systemd oczekuje na jego zakończenie, aby kontynuować uruchamianie dalszych/zależnych unitów
- dbus - Jak "simple", ale systemd czeka aż serwis podłączy się do D-Bus, potem systemd kontynuuje uruchamianie dalszych/zależnych unitów
- notify - Jak "simple", ale systemd czeka aż serwis wyśle wiadomość potwierdzającą zakończenie startu usługi (patrz polecenie: "systemd-notify --ready" ), potem systemd kontynuuje uruchamianie dalszych/zależnych unitów
- idle - Jak "simple", ale uruchomienie serwisu zostaje opóźnione do czasu, aż uruchamianie innych usług nie zostaną zakończone
Tworzenie własnych serwisów
Przeczytaj CAŁY rozdział zanim zaczniesz tworzyć własne unity...
Wstęp
Najprostszy serwis:
[Service] ExecStart=/usr/local/bin/script.sh
Metody zmiany konfiguracji serwisów/unitów:
- Kopia z /usr do /etc i poprawiamy plik w /etc - plik w /etc całkowicie zastępuje plik w /usr
- W pliku z /etc robimy "include" pliku z /usr i dopisujemy tylko zmiany między nimi
- Tworzymy w /etc/ katalog "drop-in" np: name.service.d, a w nim wiele plików "*.conf", które po nałożeniu ich (kolejność jest ważna) na plik serwisu z /usr razem dają docelową konfigurację (zalecany sposób). Podczas sprawdzania statusu takiej usługi, będzie podana lista plików konfiguracyjnych.
Jeśli używamy katalogu "drop-in" i chcemy nadpisać parametr, który może występować wielokrotnie (np: ExecStart=; omówimy te parametry poniżej) w pliku w /usr, to aby poprawnie zastąpić je nową wartością, poprzedzamy nowy wpis pustym wpisem... np:
[Service] ExecStart= ExecStart=/new/command
Brzmi skomplikowanie??? Przykład: Co uruchamia sshd.service?
# grep ExecStart= /usr/lib/systemd/system/sshd.service ExecStart=/usr/bin/sshd -D
Powiedzmy, że chcesz uruchomić sshd inaczej, np tak:
ExecStart=/usr/bin/sshd -4 -D
Jak to zrobić??? Najprościej: kopiujesz cały /usr/lib/systemd/system/sshd.service do /etc/systemd/system/ i tam dokonujesz zmiany, ale po co się tak dręczyć... Lepiej zrobić tak:
# mkdir /etc/systemd/system/sshd.service.d/ # cat <<EOF > /etc/systemd/system/sshd.service.d/10-Service.conf [Service] ExecStart= ExecStart=/usr/bin/sshd -4 -D EOF # systemctl daemon-reload # systemctl restart sshd
Zwróć uwagę na wpis "Drop-In":
# systemctl status sshd sshd.service - OpenSSH Daemon Loaded: loaded (/usr/lib/systemd/system/sshd.service; enabled; vendor preset: disabled) Drop-In: /etc/systemd/system/sshd.service.d L 10-Service.conf Active: active (running) since czw 2015-11-05 15:12:18 CET; 1s ago Main PID: 24028 (sshd) CGroup: /system.slice/sshd.service L 24028 /usr/bin/sshd -4 -D lis 05 15:12:18 archer systemd[1]: Stopped OpenSSH Daemon. lis 05 15:12:18 archer systemd[1]: Started OpenSSH Daemon. lis 05 15:12:18 archer sshd[24028]: Server listening on 0.0.0.0 port 22.
Polecenie "systemctl edit" ułatwi Ci życie.
Utworzenie katalogu "drop-in" w /etc/systemd/system/name.type.d:
# systemctl edit name.type
Zamiast katalogu drop-in przekopiowanie pliku unitu z /usr /do /etc:
# systemctl edit --full name.type
Zmiany tymczasowe: (konfiguracja pobrana z /run, zginie po restarcie)
# systemctl edit --runtime name.type
Dodatkowe opcje polecenia "systemctl edit":
- --system (domyślnie) - katalog docelowy to /etc/systemd/system/
- --user - zwykły użytkownik może zmienić definicję unitów z /usr/lib/systemd/user/ (lub stworzyć nową) umieszczając ją w ${HOME}/.config/systemd/user/
- --global - root może zmodyfikować środowisko wszystkich użytkowników (/usr/lib/systemd/user/) w katalogu /etc/systemd/user/
Polecenie "systemd-delta" pokazuje wszystkie różnice/powiązania między unitami z /usr a /etc:
# systemd-delta [EXTENDED] /usr/lib/systemd/system/libvirtd.socket › /etc/systemd/system/libvirtd.socket.d/99-Install.conf [EXTENDED] /usr/lib/systemd/system/libvirtd.socket › /etc/systemd/system/libvirtd.socket.d/50-Socket.conf [EQUIVALENT] /etc/systemd/system/default.target › /usr/lib/systemd/system/default.target [EXTENDED] /usr/lib/systemd/system/sshd.service › /etc/systemd/system/sshd.service.d/10-Service.conf
Czy mogę teraz zobaczyć za jednym zamachem całość konfiguracji ssh.service?:
$ systemctl cat sshd.service # /usr/lib/systemd/system/sshd.service [Unit] Description=OpenSSH Daemon Wants=sshdgenkeys.service After=sshdgenkeys.service After=network.target [Service] ExecStart=/usr/bin/sshd -D ExecReload=/bin/kill -HUP $MAINPID KillMode=process Restart=always [Install] WantedBy=multi-user.target # This service file runs an SSH daemon that forks for each incoming connection. # If you prefer to spawn on-demand daemons, use sshd.socket and sshd@.service. # /etc/systemd/system/sshd.service.d/10-Service.conf [Service] ExecStart= ExecStart=/usr/bin/sshd -4 -D
Czyli wiemy już JAK zmieniać ustawienia, pytanie: CO możemy zmieniać?
Przykładowy serwis
Przykład usługi wykorzystującej plik modyfikujący środowisko aplikacji oraz jawną deklarację nowej zmiennej:
[Service] Environment=MY_OPTION=1410 EnvironmentFile=/etc/sysconfig/myapp.conf ExecStart=/path/myapp $OPTION_FROM_FILE $MY_OPTION
Opcje:
- ExecStartPre= - polecenie te zostanie uruchomione przed ExecStart (można używać wielokrotnie)
- ExecStart= - główne polecenie uruchamiające naszą aplikację (można używać wielokrotnie, ale tylko w "oneshot")
- ExecStartPost= - polecenie te zostanie uruchomione po zakończeniu uruchamiania wszystkich ExecStart (można używać wielokrotnie)
- ExecReload= - polecenie te zostanie uruchomione, gdy wykonamy "systemctl reload name.service"
- ExecStop= - polecenie te zostanie uruchomione, gdy wykonamy "systemctl stop foo.service" (można używać wielokrotnie)
- ExecStopPost= - polecenie te zostanie uruchomione niezależnie, czy ExecStop= był zdefiniowany i wykonany, a także w przypadku niespodziewanego zakończenia ExecStart= (można używać wielokrotnie)
- Restart= - określa, czy i kiedy usługa ma być restartowana. Przyjmuje wartości: "no" (domyślnie), "on-success", "on-failure", "on-abnormal", "on-watchdog", "on -abort", "always"
- RestartSec=- czas po którym usługa zostanie uruchomiona ponownie (zabezpiecza zepsuty serwis przed próbami restartu co 100ms)
- SuccessExitStatus= - jeśli aplikacja nie ma w zwyczaju kończyć się kodem wyjścia 0, tylko 5, to możemy powiedzieć, że 5 jest ok :)
- OnFailure= - lista (oddzielona spacjami) unitów, która zostanie aktywowana po awarii unitu
- PidFile= - jeśli serwis ma własny plik z PIDami swoich procesów, można skazać ten plik
- TimeoutStartSec= - po jakim maksymalnie czasie od uruchomienia serwis powinien być gotowy do pracy (default=90) - jeśli nie jest, zostanie ubity
- RemainAfterExit= - "yes" - jeśli chcemy aby systemd uważał, że po zakończeniu działania serwis jest ciągle uruchomiony
Czas na konkrety...
Utwórz trzy pliki: testowy skrypt, plik konfiguracyjny i testową usługę:
# cat /etc/myscript.conf OPTION_FROM_FILE=666 # cat /usr/local/bin/myscript.sh #!/bin/bash echo "$@" # cat /etc/systemd/system/myscript.service [Unit] Description=Moj kozacki skrypt [Service] # Type=oneshot Environment=MY_OPTION=1410 EnvironmentFile=/etc/myscript.conf ExecStartPre=/usr/local/bin/myscript.sh ExecStartPre= 1 $OPTION_FROM_FILE $MY_OPTION ExecStartPre=/usr/local/bin/myscript.sh ExecStartPre= 2 $OPTION_FROM_FILE $MY_OPTION ExecStartPre=/usr/local/bin/myscript.sh ExecStartPre= 3 $OPTION_FROM_FILE $MY_OPTION ExecStart=/usr/local/bin/myscript.sh ExecStart= 1 $OPTION_FROM_FILE $MY_OPTION # ExecStart=/usr/local/bin/myscript.sh ExecStart= 2 $OPTION_FROM_FILE $MY_OPTION # ExecStart=/usr/local/bin/myscript.sh ExecStart= 3 $OPTION_FROM_FILE $MY_OPTION ExecStartPost=/usr/local/bin/myscript.sh ExecStartPost= 1 $OPTION_FROM_FILE $MY_OPTION ExecStartPost=/usr/local/bin/myscript.sh ExecStartPost= 2 $OPTION_FROM_FILE $MY_OPTION ExecStartPost=/usr/local/bin/myscript.sh ExecStartPost= 3 $OPTION_FROM_FILE $MY_OPTION ExecStop=/usr/local/bin/myscript.sh ExecStop= 1 ExecStop=/usr/local/bin/myscript.sh ExecStop= 2 ExecStop=/usr/local/bin/myscript.sh ExecStop= 3 ExecStopPost=/usr/local/bin/myscript.sh ExecStopPost= 1 ExecStopPost=/usr/local/bin/myscript.sh ExecStopPost= 2 ExecStopPost=/usr/local/bin/myscript.sh ExecStopPost= 3 RemainAfterExit=yes [Install] WantedBy=multi-user.target # chmod +x /etc/systemd/system/myscript.service # ls -al /etc/myscript.conf /usr/local/bin/myscript.sh /etc/systemd/system/myscript.service -rw-r--r-- 1 root root 21 11-06 12:02 /etc/myscript.conf -rw-r--r-- 1 root root 1254 11-06 12:24 /etc/systemd/system/myscript.service -rwxr-xr-x 1 root root 23 11-06 11:50 /usr/local/bin/myscript.sh
Teraz możemy uruchomić usługę myscript.service:
# systemctl daemon-reload # systemctl start myscript # systemctl status myscript -l myscript.service - Moj kozacki skrypt Loaded: loaded (/etc/systemd/system/myscript.service; disabled; vendor preset: disabled) Active: active (exited) since pią 2015-11-06 12:38:45 CET; 7s ago Process: 32458 ExecStartPost=/usr/local/bin/myscript.sh ExecStartPost= 3 $OPTION_FROM_FILE $MY_OPTION (code=exited, status=0/SUCCESS) Process: 32455 ExecStartPost=/usr/local/bin/myscript.sh ExecStartPost= 2 $OPTION_FROM_FILE $MY_OPTION (code=exited, status=0/SUCCESS) Process: 32452 ExecStartPost=/usr/local/bin/myscript.sh ExecStartPost= 1 $OPTION_FROM_FILE $MY_OPTION (code=exited, status=0/SUCCESS) Process: 32451 ExecStart=/usr/local/bin/myscript.sh ExecStart= 1 $OPTION_FROM_FILE $MY_OPTION (code=exited, status=0/SUCCESS) Process: 32448 ExecStartPre=/usr/local/bin/myscript.sh ExecStartPre= 3 $OPTION_FROM_FILE $MY_OPTION (code=exited, status=0/SUCCESS) Process: 32445 ExecStartPre=/usr/local/bin/myscript.sh ExecStartPre= 2 $OPTION_FROM_FILE $MY_OPTION (code=exited, status=0/SUCCESS) Process: 32442 ExecStartPre=/usr/local/bin/myscript.sh ExecStartPre= 1 $OPTION_FROM_FILE $MY_OPTION (code=exited, status=0/SUCCESS) Main PID: 32451 (code=exited, status=0/SUCCESS) lis 06 12:38:45 archer systemd[1]: Starting Moj kozacki skrypt... lis 06 12:38:45 archer myscript.sh[32442]: ExecStartPre= 1 666 1410 lis 06 12:38:45 archer myscript.sh[32445]: ExecStartPre= 2 666 1410 lis 06 12:38:45 archer myscript.sh[32448]: ExecStartPre= 3 666 1410 lis 06 12:38:45 archer myscript.sh[32451]: ExecStart= 1 666 1410 lis 06 12:38:45 archer myscript.sh[32452]: ExecStartPost= 1 666 1410 lis 06 12:38:45 archer myscript.sh[32455]: ExecStartPost= 2 666 1410 lis 06 12:38:45 archer myscript.sh[32458]: ExecStartPost= 3 666 1410 lis 06 12:38:45 archer systemd[1]: Started Moj kozacki skrypt.
A teraz możemy wyłączyć usługę myscript.service:
[root@archer system]# systemctl stop myscript [root@archer system]# systemctl status myscript -l myscript.service - Moj kozacki skrypt Loaded: loaded (/etc/systemd/system/myscript.service; disabled; vendor preset: disabled) Active: inactive (dead) lis 06 12:38:45 archer myscript.sh[32458]: ExecStartPost= 3 666 1410 lis 06 12:38:45 archer systemd[1]: Started Moj kozacki skrypt. lis 06 12:39:05 archer systemd[1]: Stopping Moj kozacki skrypt... lis 06 12:39:05 archer myscript.sh[32469]: ExecStop= 1 lis 06 12:39:05 archer myscript.sh[32472]: ExecStop= 2 lis 06 12:39:05 archer myscript.sh[32474]: ExecStop= 3 lis 06 12:39:05 archer myscript.sh[32479]: ExecStopPost= 1 lis 06 12:39:05 archer myscript.sh[32482]: ExecStopPost= 2 lis 06 12:39:05 archer myscript.sh[32486]: ExecStopPost= 3 lis 06 12:39:05 archer systemd[1]: Stopped Moj kozacki skrypt.
Gratuluję!!! Właśnie odpaliłeś swój pierwszy serwis typu "simple" :) Jako zadanie domowe "odkomentuj" trzy "zakomentowane" linie w myscript.service i sprawdź, jak działa serwis typu "oneshot".
Uruchamianie warunkowe
Jeśli nasza usługa wymaga zamontowanego systemu plików, to dodajemy to wymaganie do naszego serwisu - wpływa to na kolejność uruchamiania usług i musi być spełnione:
[Unit] RequiresMountsFor=/var/tmp
A co jeśli chcemy przed uruchomieniem coś sprawdzić i zależnie od wyniku uruchomić usługę??? Czyli uruchomić ją tylko wtedy, gdy jakieś warunki będą spełnione... Warunkowe uruchamianie na przykładzie /lib/systemd/system/sshdgenkeys.service:
[Unit] Description=SSH Key Generation ConditionPathExists=|!/etc/ssh/ssh_host_key ConditionPathExists=|!/etc/ssh/ssh_host_key.pub ConditionPathExists=|!/etc/ssh/ssh_host_rsa_key ConditionPathExists=|!/etc/ssh/ssh_host_rsa_key.pub ConditionPathExists=|!/etc/ssh/ssh_host_dsa_key ConditionPathExists=|!/etc/ssh/ssh_host_dsa_key.pub [Service] ExecStart=/usr/bin/ssh-keygen -A Type=oneshot RemainAfterExit=yes
Jak to rozumieć? Najpierw pokażmy jeszcze kilka (man systemd.unit) innych warunków:
- ConditionPathExists= - czy istnieje tak ścieżka (plik. katalog, link,..., cokolwiek)
- ConditionPathIsDirectory= - czy istnieje katalog
- ConditionPathIsSymbolicLink= - czy istnieje symboliczny link
- ConditionPathIsMountPoint= - czy istnieje punkt montowania
- ConditionPathIsReadWrite= - czy można modyfikować system plików, na którym znajduje się wskazany obiekt
- ConditionDirectoryNotEmpty= - czy katalog istnieje i jest niepusty
- ConditionFileNotEmpty= - czy plik istnieje i jest niepusty
- ConditionFileIsExecutable= - czy istnieje plik i ma ustawiony atrybut "eXecutable"
Wszystkie warunki muszą być spełnione (AND):
Condition*=WARUNEK1 Condition*=WARUNEK2 Condition*=WARUNEK3
Jeśli chcemy aby conajmniej jeden był spełniony, dodajemy "|" (OR):
Condition*=|WARUNEK1 Condition*=|WARUNEK2 Condition*=|WARUNEK3
Negację: (not AND): i not OR )
Condition*=!WARUNEK1 Condition*=!WARUNEK2 Condition*=!WARUNEK3
Negację: (not OR):
Condition*=|!WARUNEK1 Condition*=|!WARUNEK2 Condition*=|!WARUNEK3
Zatem nasz przykład:
ConditionPathExists=|!/etc/ssh/ssh_host_key ConditionPathExists=|!/etc/ssh/ssh_host_key.pub ConditionPathExists=|!/etc/ssh/ssh_host_rsa_key ConditionPathExists=|!/etc/ssh/ssh_host_rsa_key.pub ConditionPathExists=|!/etc/ssh/ssh_host_dsa_key ConditionPathExists=|!/etc/ssh/ssh_host_dsa_key.pub
oznacza, że usługa ta zostanie uruchomiona, jeśli zabraknie chociaż jednego z tych plików...
Zależności
Przykład: /usr/lib/systemd/system/sshd.service
[Unit] Description=OpenSSH Daemon Wants=sshdgenkeys.service After=sshdgenkeys.service After=network.target [Service] ExecStart=/usr/bin/sshd -D ExecReload=/bin/kill -HUP $MAINPID KillMode=process Restart=always [Install] WantedBy=multi-user.target
- Requires= - jeśli nasz unit zostanie włączony, to wymienione tu unity zostaną włączone również. Jeśli któryś z nich przestanie działać, nasz unit również. Nie mamy wpływu na kolejność włączania i wyłączania. Jeśli chcesz mieć wpływ na kolejność użyd dodatkowo opcji: After= lub/i Before= - bez nich unity zostaną uruchomione równolegle i bez żadnych opóźnień. Opcja Wants= jest bardziej elastyczna...
- Wants= - wymienione tu unity będą uruchomione, jeśli są przewidziane do startu (enabled). Nasz unit wystartuje poprawnie, jeśli nawet któryś z wymienionych unitów nie włączy się poprawnie. Zalecany sposób na wskazanie zależności między unitami.
- BindsTo= - jak Wants=, ale jeśli któryś z wymienionych unitów przestanie działać, również nasz unit przestaje działać...
- Conflicts= - lista unitów które zostaną wyłączony, gdy nasz unit zostanie włączony. Opcje: After= i Before= mają zastosowanie.
- Before=, After= - wskazanie kolejności uruchamiania unitów, które są przeznaczone do uruchomienia
- PartOf= - przypinamy się do unita, czyli podczas zatrzymywania go lub restartu nasz unit robi to samo, ale jest to relacja jednostronna.
Template
Co to jest "template"? Czyli jak przekazać parametr do unitu...
1. W systemie mamy zwykle wirtualne konsole od tty1 do tty6. Warto tworzyć 6 serwisów o prawie tej samej treści? 2. Od czasu do czasu (np: przy starcie systemu) trzeba sprawdzać (fsck) spójność systemu plików, często wielu. Jak wskazać który? 3. Może chcesz uruchomić np: SSHD,vsftpd,vpnc na konkretnym adresie IP? Jak wskazać na którym IP? 4. Może potrzebujesz uruchomić klienta DHCP na konkretnym interfejsie sieciowym? Jak wskazać na którym?
Jak wygląda "template"? Template to plik unitu o nazwie:
name@.type
np: /usr/lib/systemd/system/getty@.service Nie możesz jednak po prostu uruchomić serwisu "getty@.service"!!!
Sprawdźmy jak radzi sobie "target" getty.
# ls -al /etc/systemd/system/getty.target.wants/ razem 8 drwxr-xr-x 2 root root 4096 06-23 12:13 . drwxr-xr-x 9 root root 4096 08-12 14:16 .. lrwxrwxrwx 1 root root 38 06-23 12:13 getty@tty1.service -> /usr/lib/systemd/system/getty@.service
Sprawdźmy, czy faktycznie usługa getty@tty1.service jest uruchomiona:
# systemctl status getty@tty1.service getty@tty1.service - Getty on tty1 Loaded: loaded (/usr/lib/systemd/system/getty@.service; enabled; vendor preset: enabled) Active: active (running) since pon 2015-08-10 18:48:36 CEST; 2 days ago
Zatem jeśli chcemy wykorzystać "template" musimy po znaku '@' podać parametr (nazwa instancji).
Co mamy we wzorcu?
# grep Exec /lib/systemd/system/getty@.service ExecStart=-/sbin/agetty --noclear %I $TERM
Parametr %I (patrz: man systemd.unit) to właśnie nazwa instancji, zatem wywołanie usługi getty@tty3.service spowoduje wykonanie polecenia:
ExecStart=-/sbin/agetty --noclear tty3 $TERM
Sprawdźmy:
# ps -eo tty,command | grep '^tty1 ' tty1 /sbin/agetty --noclear tty1 linux
Sprawa trochę jest trudniejsza, jeśli nasza nazwa instancji ma postać np: ścieżki do pliku lub urządzenia.
Weźmy ten przykład (w bashu znak ucieczki, "escape character", to '\', więc "\\" znaczy '\')
# systemctl | grep systemd-fsck systemd-fsck@dev-disk-by\x2dlabel-DATA.service loaded active exited File System Check on /dev/disk/by-label/DATA # systemctl cat systemd-fsck@dev-disk-by\\x2dlabel-DATA.service | grep Exec ExecStart=/usr/lib/systemd/systemd-fsck %f
Czyli nazwa instancji to: 'dev-disk-by\x2dlabel-DATA'. "Odszyfrowanie" ścieżki:
# systemd-escape -p --unescape 'dev-disk-by\x2dlabel-DATA' /dev/disk/by-label/DATA
"Zaszyfrowanie" ścieżki:
$ systemd-escape -p /dev/disk/by-label/DATA dev-disk-by\x2dlabel-DATA
Zatem w naszym przykładzie (%f = "odszyfrowana" ścieżka) polecenie do uruchomienia usługa ma postać:
ExecStart=/usr/lib/systemd/systemd-fsck /dev/disk/by-label/DATA
Poznaliśmy zmienne/parametry %I i %f, ale jest ich dużo więcej - patrz: man systemd.unit.
Podsumowanie
Potrafimy już napisać prosty serwis i mamy już pojęcie o tym, jak można to skomplikować.
Wkrótce następne części telenoweli - stay tuned :)