Mise en place du noyau

La dernière (mais pas la moindre) chose qu'il manque à notre système, c'est un noyau.

Notre noyau sera installé depuis le paquet debian ordinaire, mais celà ne suffit pas : le noyau est installé directement dans /boot, qui dans notre installation se trouve dans le volume chiffré. Il ne sera donc pas accessible lors du démarrage et ne pourra donc pas être chargé. Nous allons donc mettre en place un système permettant, à partir du noyau packagé par Debian, d'avoir un fichier amorçable par l'UEFI.

Création d'une image de noyau unifiée (UKI) amorçable

Pour permettre le démarrage du système, nous allons regrouper tout ce qui est nécessaire, c'est à dire principalement le noyau Linux et son système de fichier initial (initrd), en un seul fichier directement amorçable par l'UEFI, qui sera installé sur la partition EFI. Ce type de fichier est connu sous le nom d'UKI, pour Unified Kernel Image ou, en français, image de noyau unifiée. Pour plus de détails sur ce format, on pourra consulté, par exemple, le wiki Arch. Ce fichier sera signé avec notre clé SecureBoot mise en place à l'étape précédente, ce qui lui permettra d'être chargée directement, sans passer par un chargeur d'amorçage.

On commence par installer les paquets dont nous aurons besoin pour cette étape :

apt-get install systemd-ukify sbsigntool efibootmgr systemd-boot-efi cryptsetup-initramfs

Pour que cette génération se fasse automatiquement à chaque mise à jour du noyau, nous allons la mettre en place dans un script placé dans le répertoire /etc/kernel/postinst.d/ qui sera donc exécuté après chaque installation d'un noyau. Nous allons donc créer un fichier /etc/kernel/postinst.d/zz-1-update-uki (le préfixe permet de contrôler l'ordre d'exécution, dans le cas où d'autres scripts seraient présents) avec le contenu suivant :

#!/bin/sh -e

version="$1"

/usr/lib/systemd/ukify build \
    --linux /boot/vmlinuz-${version} \
    --initrd /boot/initrd.img-${version} \
    --os-release @/usr/lib/os-release \
    --uname ${version} \
    --cmdline @/etc/cmdline \
    --output /boot/efi/EFI/debian/Linux-${version}.efi

/usr/bin/sbsign \
    --key /root/Rocannon-SecureBoot.key \
    --cert /root/Rocannon-SecureBoot.crt \
    --output /boot/efi/EFI/debian/Linux-${version}.efi \
    /boot/efi/EFI/debian/Linux-${version}.efi

Ce script fait référence à plusieurs fichiers externes qui doivent donc être présents et dont les chemins sont potentiellement à adapter :

  • /usr/lib/os-release est installé par le paquet base-files, qui fait partie de l'installation de base,
  • /etc/cmdline est un fichier que nous allons créer et qui nous permet de configurer la ligne de commande de démarrage du noyau. Il se compose de la ligne suivante (à adapter, en particulier selon le nom du volume group choisi lors du partitionnement) :
    root=/dev/mapper/vg_rocannon-lv_racine ro rootflags=subvol=racine quiet loglevel=3 panic=0
    
  • /root/Rocannon-SecureBoot.key et /root/Rocannon-SecureBoot.crt correspondent respectivement à notre clé privée de type db, générée à l'étape précédente, et à son certificat de clé publique.

Notre UKI ne contient pas seulement le noyau, mais également l'initrd associé. Elle doit donc également être régénérée en cas de mise à jour de l'initrd. Pour cela, il nous suffit de créer un lien symbolique vers notre script dans /etc/initramfs/post-update.d/ :

mkdir -p /etc/initramfs/post-update.d
ln -s /etc/kernel/postinst.d/zz-1-update-uki /etc/initramfs/post-update.d/

Installation de l'UKI dans l'UEFI

Une fois notre image créée et signée dans la partition EFI, il faut encore créer une entrée correspondante dans la séquence de démarrage de l'UEFI. Cela se fait avec un second script, également dans /etc/kernel/postinst.d/, que nous allons nommer zz-2-install-uki, pour garantir qu'il s'exécute bien après le précédent :

#!/bin/sh -e

version="$1"

/usr/bin/efibootmgr \
    --disk /dev/nvme0n1 \
    --create \
    --label "Debian-Linux-${version}" \
    --loader "\EFI\debian\Linux-${version}.efi"

Contrairement au précédent, ce script n'a besoin d'être exécuté que lors de la première installation de chaque version du noyau, il ne faut donc pas créer de lien dans /etc/initramfs/post-update.d/.

Gestion de la désinstallation

Pour éviter de saturer notre partition EFI, il faut également supprimer les fichiers que nous y avons créés lors de la désinstallation du noyau correspondant, et mettre à jour la configuration UEFI en conséquence. De façon similaire à l'installation, nous allons mettre en place un script dans le répertoire /etc/kernel/postrm.d/, nommé zz-remove-uki :

#!/bin/sh -e

version="$1"

# recherche du numéro de l'entrée concernée dans l'ordre de démarrage UEFI
num=$(efibootmgr --disk /dev/nvme0n1 \
    | grep "Debian-Linux-${version}" \
    | cut -c 5-8)

if [ -n "$num" ]
then
    efibootmgr \
        --disk /dev/nvme0n1 \
        --delete-bootnum \
        --bootnum $num
fi

# l'option -f évite une erreur si le fichier n'existe pas
rm -f /boot/efi/EFI/debian/Linux-${version}.efi

Installation du noyau

Maintenant que nos scripts sont bien en place, nous pouvons installer le meta-paquet correspondent à l'architecture utilisée :

apt-get install linux-image-amd64
Les paquets linux-image-* suggèrent des paquets correspondant aux divers bootloaders, dont nous n'avons pas besoin. Vérifiez bien qu'apt est configuré pour ne pas installer automatiquement les suggestions.

La sortie de la commande d'installation, et l'examen du contenu de la partition EFI, permettent de vérifier que tout s'est bien passé.