Journal Aujourd'hui c'est déjà demain : systemd dans l'initrd sous Arch Linux

Posté par  (site web personnel) . Licence CC By‑SA.
Étiquettes :
33
16
oct.
2014

Sommaire

Introduction

Nous allons voir dans ce petit article comment utiliser systemd dans l'initrd. La construction d'un initrd étant spécifique à la distribution, nous verrons comment l'utiliser avec Arch Linux mais avec un peu de travail cela devrait pouvoir donner le principe général de fonctionnement et être adaptable sur d'autres distributions.

ATTENTION : nous allons modifier une partie très importante du démarrage de la machine, et il est probable que la machine ne démarre plus. Prévoyez donc un live-cd quelconque pour pouvoir réparer en cas de soucis !

Quelques généralités sur l'initrd

L'initrd, c'est une archive cpio qui est chargée en RAM par le noyau juste après son lancement. Elle est montée sur /, et le fichier /init est lancé. Ce fichier est censé s'occuper de lancer tout ce qui est nécessaire au montage du vrai / et qui n'est pas en dur dans le noyau, comme le chargement de modules noyau, lvm, le déchiffrement, montage de /usr s'il est sur une partition séparée…

Traditionnellement, ce fichier init est un script shell construit par des outils spécifiques à la distribution.

La construction classique de l'initrd sous Arch Linux

Sous Arch Linux, la construction d'un initrd se fait à l'aide du logiciel mkinitcpio. Il se configure en utilisant le fichier /etc/mkinitcpio.conf. La variable HOOKS est celle qui nous intéresse présentement.

Pour chaque hook présent dans cette variable, deux fichiers seront utilisés, un script install qui sera chargé d'ajouter les fichiers nécessaire à l'initrd lors de la construction, et un script hook qui sera lancé par le script shell init lors du boot. L'ordre d'apparition des hooks dans la variable HOOKS est important car c'est l'ordre utilisé pour lancer les scripts lors du boot.

L'utilisation de systemd dans l'initrd

Lorsqu'on utilise systemd dans l'initrd, le fichier /init de l'initrd n'est plus un script shell, mais directement systemd.

Ainsi, lors de la construction de l'initrd avec mkinitcpio, le script hook n'a plus aucun effet, et le script install est chargé, outre les fichiers nécessaires tels les démons et modules noyaux, d'installer les unités systemd nécessaire à monter la vraie racine. L'ordre d'aparition des hooks dans la variable HOOKS n'a plus d'importance (a une exception près que nous verrons plus bas), car ce sont les unités systemd qui détermineront l'ordre de lancement des différentes unités (et donc de tous les binaires lancés, car tout est lancé via systemd).

systemd dans l'initrd, ça sert à quoi ?

Il y a sûrement des avantages secondaires, comme la parallèlisation, une configuration de démarrage unique et centralisée, et peut-être d'autres, mais l'avantage principal est quand même de faire rager les anti-systemd en le mettant partout et à toutes les sauces.

Configuration du système pour utiliser systemd

Le problème principal est que le support d'Arch Linux pour systemd dans l'initrd est un peu balbutiant, et il faut donc mettre un peu les mains dans le cambouis pour que tout fonctionne. Nous verrons ici comment configurer un système utilisant une partition chiffrée contenant du lvm et plymouth, puisque c'est ma configuration. L'ajout du raid ne doit pas être très compliqué.

La base

Les hooks base, usr, udev et timestamp ne sont plus nécessaires et sont remplacés par le hook systemd. Les hooks autodetect, block, filesystems, btrfs et keyboard sont toujours nécessaires (au moins sur ma machine). Les autres hooks (lvm, encrypt, plymouth, etc) seront par la suite remplacés par des équivalents sd-machin.

Les hooks sd-machin doivent toujours être après le hook systemd. En effet, ce hook ajoute une fonction utilisable dans les autres hooks, add_systemd_unit. Cette fonction ajoute dans l'initrd l'unité passée en paramètre, ainsi que toutes ces dépendances.

Patch 1 : unités dans /etc

Cette fonction a un problème : elle n'utilise que le dossier /usr/lib/systemd/system, et ignore donc totalement les unités crées par l'utilisateurs situées dans le dossier /etc/systemd/system (voir le bug report).

Nous allons donc modifier le hook, en copiant /usr/lib/initcpio/install/systemd vers /etc/initcpio/install/systemd. Nous allons donc pouvoir le modifier sans risquer de voir nos modifications écrasées par une mise-à-jour.

Le patch est le suivant

--- /usr/lib/initcpio/install/systemd   2014-09-02 00:11:28.000000000 +0630
+++ /etc/initcpio/install/systemd       2014-10-16 13:39:11.291460470 +0630
@@ -51,7 +51,7 @@

     local unit= rule= entry= key= value= binary= dep=

-    unit=$(PATH=/usr/lib/systemd/system:/lib/systemd/system type -P "$1")
+    unit=$(PATH=/etc/systemd/system:/usr/lib/systemd/system:/lib/systemd/system type -P "$1")
     if [[ -z $unit ]]; then
         # complain about not found unit file
         return 1
@@ -78,18 +78,23 @@
     done <"$unit"

     # preserve reverse soft dependency
-    for dep in {/usr,}/lib/systemd/system/*.wants/${unit##*/}; do
+    for dep in {{/usr,}/lib,/etc}/systemd/system/*.wants/${unit##*/}; do
         if [[ -L $dep ]]; then
             add_symlink "$dep"
         fi
     done

     # add hard dependencies
-    if [[ -d $unit.requires ]]; then
-        for dep in "$unit".requires/*; do
-            add_systemd_unit ${dep##*/}
-        done
-    fi
+    for dir in {{/usr,}/lib,/etc}/systemd/system/${unit##*/}.requires; do
+        if [[ -d "$dir" ]]; then
+            for dep in "$dir"/*; do
+                if [[ -L $dep ]]; then
+                    add_symlink "$dep"
+                fi
+                add_systemd_unit ${dep##*/}
+            done
+        fi
+    done
 }

 build() {

Patch 2 : emergency.target

Second problème de ce hook, il n'ajoute pas les utilitaires nécessaire au fonctionnement de emergency.target, une unité spéciale qui est lancée lorsque le boot plante et donne un shell à l'utilisateur pour essayer de sauver les meubles (voir un bug report et un autre). On va donc le modifier pour rajouter sulogin ainsi que les utilitaires de busybox.

--- /etc/initcpio/install/systemd.old   2014-10-16 13:44:23.135993657 +0630
+++ /etc/initcpio/install/systemd       2014-10-16 13:43:23.657715653 +0630
@@ -98,12 +98,22 @@
 }

 build() {
-    local rules unit
+    local rules unit applet

     # from base
     add_binary /bin/mount
     add_binary /usr/bin/kmod /usr/bin/modprobe

+    add_binary /usr/lib/initcpio/busybox /bin/busybox
+    for applet in $(/usr/lib/initcpio/busybox --list); do
+      add_symlink "/usr/bin/$applet" busybox
+    done
+
+    # sulogin is needed for emergency target
+    add_binary /sbin/sulogin
+    add_file /etc/shadow
+    add_file /etc/gshadow
+
     # systemd
     add_binary /usr/lib/systemd/systemd /init
     add_binary /usr/bin/systemd-tmpfiles

À ce stade, si l'on a une partition racine simple, on a terminé.

Partition chiffrée

S'il y a une partition chiffrée nécessaire au démarrage, il faut ajouter le hook sd-encrypt.

Attention, les paramètres de ligne de commande du noyau (probablement configurés dans /etc/default/grub) pour indiquer les partitions et leurs options ont changé. Le paramètre cryptdevice est remplacé par luks.machin (man systemd-cryptsetup-generator pour plus d'infos).

Cependant, plutôt que ces paramètres, il est plus utile d'utiliser le fichier /etc/crypttab.initramfs, qui suit la syntaxe de /etc/crypttab.

LVM

Il suffit d'ajouter le fichier sd-lvm. Aucune configuration particulière, car un scan est lancé dès l'apparition d'un nouveau block device, et activé si celui est un lvm (donc on peut avoir une partition chiffrée dans un lvm et un lvm dans une partition chiffrée sans avoir à préciser d'ordre particulier, et même avoir une partition chiffrée dans un lvm dans une partition chiffrée).

Resume

Il n'y a rien dans la version stable de systemd pour s'occuper de sortir le système de l'hibernation. C'est prévu pour la prochaine version, mais en attendant, il va falloir faire les choses à la main.

On créé donc une unité /etc/systemd/system/resume.target

[Unit]
Description=Resume from disk
Before=initrd-root-fs.target sysroot.mount

[Install]
WantedBy=initrd.target

Et on l'active avec systemctl enable resume.target.

Puis une unité générique /etc/systemd/system/resume@.service

[Unit]
Description=Resume from disk using %I
Before=resume.target
DefaultDependencies=no
BindsTo=%i.device
After=%i.device

[Service]
Type=oneshot
ExecStart=/bin/sh -c "echo $(mountpoint -x %I) > /sys/power/resume"

[Install]
RequiredBy=resume.target

On l'active en utilisant le chemin de sa partition de swap, dans mon cas systemctl enable resume@dev-main-swap.service.

On créé enfin le hook mkinitcpio /etc/initcpio/install/sd-resume

#!/bin/bash

build() {
    add_systemd_unit resume.target
}

help() {
    cat <<HELPEOF
This hook adds resume capabilities to the initramfs.
HELPEOF
}

# vim: set ft=sh ts=4 sw=4 et:

Et on l'active en ajoutant sd-resume dans la variable HOOKS.

Plymouth

Si l'on utilise le AUR plymouth, cela demande un peu de travail.

Création du hook

Il n'y a pas de hook sd-plymouth, donc il va falloir en créer un à partir du hook plymouth. On copie donc /usr/lib/initcpio/install/plymouth vers /etc/initcpio/install/sd-plymouth, puis on patch pour ajouter les unités systemd.

--- /usr/lib/initcpio/install/plymouth  2014-10-16 09:38:06.000000000 +0630
+++ /etc/initcpio/install/sd-plymouth   2014-10-16 14:05:01.237713438 +0630
@@ -1,7 +1,6 @@
 build() {
        add_dir /dev/pts
        add_dir /usr/share/plymouth/themes
-       add_dir /var/run/plymouth

        DATADIR="/usr/share"
        PLYMOUTH_LOGO_FILE="${DATADIR}/plymouth/arch-logo.png"
@@ -49,7 +48,15 @@
        add_binary "$(readlink -e /lib/libnss_files.so.2)"
        add_file /lib/libnss_files.so.2

-       add_runscript
+       add_systemd_unit systemd-ask-password-plymouth.path
+       add_systemd_unit systemd-ask-password-plymouth.service
+
+       map add_systemd_unit plymouth-switch-root.service \
+           plymouth-start.service \
+           plymouth-reboot.service \
+           plymouth-kexec.service \
+           plymouth-poweroff.service \
+           plymouth-halt.service
 }

 help() {

Utilisation avec systemd-ask-password

Lorsqu'on utilise plymouth avec un système ayant besoin d'un mot de passe (comme une partition chiffrée), il faut que le mot de passe soit demandée via plymouth et non sur la console. systemd possède un système plutôt malin pour gérer ce genre de choses, sauf que l'unité plymouth-start.service est cassée et donc la détection du l'utilisation de plymouth ne marche pas. On copie donc l'unité /usr/lib/systemd/system/plymouth-start.service vers /etc/systemd/system/plymouth-start.service et on patch pour corriger le chemin du pidfile.

--- /usr/lib/systemd/system/plymouth-start.service      2014-10-16 14:15:55.005953827 +0630
+++ /etc/systemd/system/plymouth-start.service  2014-10-16 14:06:53.054169994 +0630
@@ -7,7 +7,8 @@
 ConditionKernelCommandLine=!plymouth.enable=0

 [Service]
-ExecStart=/usr/bin/plymouthd --mode=boot --pid-file=/var/run/plymouth/pid --attach-to-session
+ExecStartPre=/bin/mkdir /run/plymouth
+ExecStart=/usr/bin/plymouthd --mode=boot --pid-file=/run/plymouth/pid --attach-to-session
 ExecStartPost=-/usr/bin/plymouth show-splash
 Type=forking
 KillMode=none

Création de l'initrd

On créé l'initrd avec mkinitcpio -p linux, on redémarre, et on croise les doigts :)

Pour s'amuser un peu

Pour s'amuser un peu, on peut créer des unités et supprimer de son système /etc/crypttab, /etc/cryttab.initramfs, /etc/fstab, et certains paramètres de ligne de commande du noyau (comme root par exemple).

Ainsi, toutes la configuration de démarrage du système est centralisée dans des unités systemd, et non plus éparpillée dans différents endroits.

Suppression de /etc/crypttab et des paramètres luks.machin du noyau

Le plus simple est d'utiliser directement le générateur /usr/lib/systemd/system-generators/systemd-cryptsetup-generator /etc/systemd/system /etc/systemd/system /etc/systemd/system, puis d'analyser les fichiers pour éventuellement les simplifier.

On peut ensuite déplacer /etc/crypttab.initramfs vers /etc/crypttab, puis relancer la commande, et on peut maintenant se débarasser des deux fichiers.

Pour les paramètres luks.machin du noyau, le plus simple est de les transformer en une entrée de /etc/crypttab avant de lancer le générateur.

Suppression de /etc/fstab

Même principe en utilisant le fichier /usr/lib/systemd/system-generators/systemd-fstab-generator.

Suppression du paramètre root du noyau

Cette partie est normalement gérée par le générateur fstab, mais celui-ci génère l'unité correspondant au paramètre root du noyau seulement s'il est lancé dans l'initrd. Il faudra donc créer l'unité à la main.

Cette unité doit s'appeler /etc/systemd/system/sysroot.mount et doit contenir les options habituelles d'une unité mount (man systemd.mount pour des détails). Cependant, les lignes suivantes sont indispensables :

[Install]
RequiredBy=initrd-root-fs.target

[Unit]
Before=initrd-root-fs.target

[Mount]
Where=/sysroot

Il faut bien sûr ajouter les lignes spécifiques à votre système, notamment What et peut-être Type dans la section [Mount].

Ne pas oublier d'activer l'unité avec systemctl enable sysroot.mount.

Conclusion

Cette solution de gestion de l'initrd, qui en est à ses débuts, sera sûrement une solution standard à l'avenir et par défaut sur les distributions majeures.

À vos trolls !

  • # plymouth et kdm

    Posté par  (site web personnel) . Évalué à 4.

    Et si vous utilisez plymouth et kdm sur Arch Linux, n'oubliez pas d'installer le AUR kdebase-workspace-plymouth.

  • # interêt?

    Posté par  . Évalué à 10.

    Il y a sûrement des avantages secondaires, comme la parallèlisation, une configuration de démarrage unique et centralisée, et peut-être d'autres, mais l'avantage principal est quand même de faire rager les anti-systemd en le mettant partout et à toutes les sauces.

    Je kiffe.

  • # Et BSD ?!

    Posté par  . Évalué à 10.

    Ca y est maintenant même les initrd ne sont pas compatibles avec les BSD ! Marre de ce linux-centrique !

    Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)

    • [^] # Re: Et BSD ?!

      Posté par  (site web personnel) . Évalué à 2.

      En même temps, l'initrd, c'est un peu l'endroit où on met les modules du noyau dont on a besoin pour le démarrage. C'est pas non plus compatible BSD :)

      • [^] # Re: Et BSD ?!

        Posté par  (site web personnel) . Évalué à 10.

        Expliciter une blague, c'est vraiment moche !

        • [^] # Re: Et BSD ?!

          Posté par  . Évalué à 4.

          ou alors ça permet au béotien de rigoler aussi (un peu) mais tout seul et trop tard … mais bon comme on est pas autour d'une table, ça va, j'ai pas eu l'air trop c*n xD

          Ce n'est pas parce que les choses sont difficiles que nous n'osons pas. C'est parce que nous n'osons pas qu'elles sont difficiles. - Sénéque

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.