Une clé USB pour les booter vraiment tous

sam. 30 sept. 2017 by Marmotte

Lorsqu'on bidouille des machines, il arrive que le système ne démarre plus. Que seul le boot loader soit cassé, ou que ce soit le système complet, il est donc important d'avoir à disposition un moyen de démarrer la machine, pour réparer les dégâts.

J'explique ici la manière dont j'ai configuré ma clé USB de boot, qui me permet de démarrer toutes les machines que j'utilise, en utilisant grub, depuis un système Debian. La configuration d'un boot loader étant parfois capricieuse, la méthode expliquée ici n'est peut être pas universelle, mais elle fonctionne pour moi sur des machines très différentes.

Note : Cet article est une mise à jour d'un précédent article.

Différences avec l'article précédent

La partition en ext2 ne semble plus être nécessaire avec les versions récentes de grub, je l'ai donc retirée, pour simplifier la procédure initiale et la maintenance. N'avoir qu'une seule partition, en vfat, permet aussi de modifier le contenu depuis une machine sous Windows ou MacOS, ce qui peut être utile pour ceux qui n'ont pas de machine sous Linux disponible.

De plus, le menu du grub est maintenant en grande partie généré dynamiquement. Il nécessite donc beaucoup moins de modifications manuelles, et n'affiche plus que les éléments réellement disponibles.

La configuration dynamique est en partie inspirée du projet Super Grub Disk 2.

Préparation de la clé USB

Ma clé USB de 128 Mo ayant cessé de fonctionner, je l'ai remplacée par la suivante, qui propose une capacité de 256 Mo. Le partitionnement est simple, puisqu'il suffit de créer une unique partition, occupant tout l'espace de la clé USB, formatée en vfat :

$ sudo cfdisk /dev/sdX
$ sudo mkfs.vfat /dev/sdX1

/dev/sdX correspond à la clé USB.

Note : Il n'est pas nécessaire d'activer le flag boot sur la partition. En effet, contrairement à ce que je pensais lors de la rédaction du précédent article, ce flag n'est pas utilisé par grub.

Installation de grub

Il faut tout d'abord installer les paquets binaires nécessaires. Ceux-ci contiennent les fichiers qui seront copiés sur la clé USB lors de l'installation de grub.

$ sudo apt-get install grub-pc-bin grub-efi-amd64-bin

Si ce n'est pas déjà le cas, la partition de la clé USB doit être montée, pour permettre à l'installeur de grub d'y copier les fichiers nécessaires.

$ # Création du répertoire de montage
$ sudo mkdir /mnt/usb
$ sudo mount /dev/sdX1 /mnt/usb

Enfin, il ne reste plus qu'à mettre les targets grub en place sur la clé USB.

$ sudo grub-install -v --no-floppy --boot-directory=/mnt/usb --removable --locales= --fonts= --target=i386-pc /dev/sdX
$ sudo grub-install -v --no-floppy --boot-directory=/mnt/usb --removable --locales= --fonts= --target=x86_64-efi --efi-directory=/mnt/usb --no-uefi-secure-boot

Note : Les paramètres --locales= et --fonts= permettent de n'inclure aucune locale, et aucune police, lors de l'installation. Cela permet de réduire la place occupée par grub sur la clé USB de moitié.

Note : Je n'ai pas encore réussi à permettre le démarrage sur un système avec secure boot activé. Pour les machines sur lesquelles j'active le secure boot (Ubuntu propose un noyau signé), je le désactive temporairement pour pouvoir démarrer sur la clé USB.

Configuration de grub

Le boot loader installé n'a actuellement pas de fichier de configuration. Il affichera donc juste une invite de commande lors du démarrage, ce qui n'est pas vraiment pratique à utiliser.

Il est possible de définir une configuration, dans un fichier de configuration nommé grub.cfg, qui sera placé dans le répertoire grub de la clé USB. Cette configuration était statique dans le précédent article, mais j'ai récemment trouvé comment la rendre dynamique, ce qui permet de détecter de manière automatique les fichiers ISO, configurations de grub, noyaux Linux... De cette façon, seuls les éléments présents sont affichés, et il n'est plus nécessaire de mettre à jour la configuration de la clé USB pour chaque machine à démarrer, ni lors de la mise à jour du noyau des machines.

La structure de ma configuration dynamique est la suivante :

for dev in (*); do
    # List matching files
    filelist=
    for file in $dev/pattern $dev/other/pattern; do
        if [ -f $file ]; then
            filelist="$file $filelist"
        fi
    done
    if [ ! -z $filelist ]; then
        probe --set uuid -u $dev
        submenu "Autodetected FILETYPE on $dev" $uuid "$filelist" {
            set uuid="$2"
            set filelist="$3"

            # Add an entry for each matching file in the menu
            for file in $filelist; do
                menuentry 'Entry for file $file' $file $uuid {
                    set file="$2"
                    set uuid="$3"
                    search --no-floppy --fs-uuid --set=root $uuid

                    # Boot instructions
                }
            done
        }
    fi
done

La boucle for dev in (*) qui englobe la totalité du script permet de parcourir tous les périphériques, partitions, volumes logiques, etc.

La seconde boucle for file in $dev/pattern $dev/other/pattern permet de chercher des fichiers sur le périphérique en cours de traitement, selon un format de chemin. Tester l'existence du chemin reste nécessaire, puisque, lorsqu'aucun fichier ne correspond, grub entre quand même dans la boucle, en assignant le pattern dans la variable $file. La seule instruction de l'exemple ajoute le nom du fichier trouvé dans une liste, sous forme d'une chaîne de caractères, mais il est parfois nécessaire d'effectuer d'autres vérifications sur le fichier, comme dans le cas d'un fichier ISO.

Le bloc suivant n'est exécuté que si au moins un fichier a été trouvé, ce qui permet d'éviter d'afficher une ligne dans le menu pour chaque périphérique, alors que la plupart ne contiennent pas les fichiers recherchés. Les instructions de ce bloc génèrent un sous-menu, dans lequel une entrée sera créée par fichier trouvé, afin de regrouper les entrées dans le menu par couple périphérique/type. Le scope des variables est différent dans chaque sous-menu et entrée de menu, il est donc nécessaire de les passer en paramètre à chaque niveau.

Enfin, la boucle for file in $filelist permet de créer une entrée de menu par fichier trouvé. L'instruction search est souvent nécessaire, puisqu'elle permet de définir le périphérique sur lequel le démarrage est effectué comme racine, lors de l'exécution de cette entrée de menu.

Enfin, il est nécessaire de charger certains modules de grub pour lui permettre d'accéder à tous les périphériques, dont :

  • part_msdos : Accès aux partitions d'un disque utilisant une table de partition MBR
  • part_gpt : Accès aux partitions d'un disque utilisant une table de partition GPT
  • lvm : Accès aux volumes logiques LVM présents sur les partitions du disque
  • iso9660 : Accès au contenu des fichiers ISO
  • loopback : Permet le montage de fichiers comme périphériques pour en explorer le contenu

Exemple de configuration

A titre d'exemple, voici le fichier de configuration actuel de ma clé USB.

Cette configuration cherche :

  • Les images ISO au format casper (le format utilisé par Ubuntu) dans un répertoire /linux-iso
  • Les fichiers de configuration grub.cfg placés dans les répertoires /grub ou /boot/grub
  • Les noyaux Linux placés dans le répertoire /boot ou à la racine des périphériques