---
title: Vérifier un guide Linux sans machine Linux
description: Un Mac, aucun constructeur Linux, et un guide d’installation NixOS à vérifier jusqu’au démarrage. Quatre paliers de vérification, et ce que chacun sait voir que le précédent ne peut pas.
publishedAt: 2026-11-05
tags:
  - nix
  - qemu
  - methode
---

J’avais un guide d’installation NixOS à vérifier, et une machine incapable d’exécuter Linux. Pas de constructeur Linux, pas de serveur sous la main, un Mac.

Le [guide en question](/blog/deux-ssd-en-miroir-sous-nixos) promettait deux SSD en miroir et un démarrage qui survit à la mort d’un disque. Pour le prendre au sérieux il fallait répondre à des questions comme « un micrologiciel UEFI sait-il lire cette partition » ou « cette machine redémarre-t-elle avec un disque en moins », et aucune ne se tranche en relisant du texte.

Quatre paliers, donc. Et la partie intéressante n’est pas qu’ils coûtent de plus en plus cher, c’est qu’ils ne répondent pas à la même question.

## Quatre questions, pas quatre degrés de réalisme

La tentation, en montant un banc de test, est d’y voir une échelle de fidélité : plus c’est cher, plus c’est proche du réel, et on s’arrête quand le budget est épuisé. Mauvaise façon de penser. Elle laisse croire qu’un palier cher sait faire tout ce que fait le palier bon marché, en mieux.

Chaque palier interroge un interlocuteur différent :

| Palier | À qui on pose la question | Ce qu’il sait répondre |
|---|---|---|
| Évaluation Nix | au système de modules | cette configuration est-elle cohérente |
| Conteneur privilégié | au noyau Linux | que fait vraiment le pilote md, que fait btrfs |
| QEMU et vrai micrologiciel | à EDK2 ou SeaBIOS | que voit une machine avant tout système |
| Installation réelle | à l’assemblage complet | est-ce que ça démarre |

Un palier ne peut pas répondre à la place d’un autre. Demander à Linux si un micrologiciel sait lire une partition n’a aucun sens : Linux a le pilote md, il répondra oui, et il aura raison pour lui. C’est le défaut central du guide que j’examinais, et c’est aussi le piège dans lequel tombe n’importe quel banc de test monté sur un seul étage.

## Palier 1 : évaluer sans rien exécuter

Le moins cher, et de loin le plus rentable. NixOS est un système de modules : soumettez-lui une configuration et lisez ce qu’il en pense, sans rien construire ni exécuter.

```bash
nix-instantiate --eval --strict -E '
let n = import <nixpkgs/nixos> { configuration = ./configuration.nix; };
in {
  drv = n.config.system.build.toplevel.drvPath;
  warnings = n.config.warnings;
  failedAssertions = builtins.filter (a: !a.assertion) n.config.assertions;
}'
```

Trois champs, trois natures de défaut. `drvPath` force l’évaluation complète, donc une option inconnue ou un type incorrect explose ici. `warnings` ramasse ce que les modules signalent sans bloquer, c’est-à-dire précisément ce que personne ne lit dans un terminal qui défile. `assertions` donne les incompatibilités que nixpkgs a pris la peine d’interdire.

Quelques secondes, sur n’importe quelle machine où Nix s’évalue, macOS compris, puisque rien n’est jamais construit pour Linux.

Sur le guide examiné, ce palier a sorti un avertissement mdadm que son auteur n’avait pas vu, deux options renommées depuis des mois, deux paquets qui n’existent plus sous ce nom. Rien de spectaculaire, mais c’est ce qu’un lecteur rencontre au premier essai.

Une variante vaut le détour : plutôt qu’une copie de la configuration, j’extrais le bloc du Markdown publié et j’évalue **ce texte-là**. Trois lignes de Python, et toute la classe des erreurs où l’article et le fichier testé ont divergé disparaît.

```python
block = re.search(r'```nix\n(.*?)\n```', article[i:], re.S).group(1)
```

## Palier 2 : un noyau Linux sans provisionner de machine virtuelle

Pour la couche bloc, il faut un vrai noyau Linux avec `md` et btrfs. Pas forcément une machine virtuelle à installer : si vous avez podman sur un Mac, vous avez déjà une machine Linux qui tourne.

```bash
podman run --rm --privileged -v "$PWD/experiment.sh:/x.sh:z" alpine:latest sh /x.sh
```

Il manque deux choses dans ce conteneur, et elles se contournent.

Il n’y a pas de périphériques loop, donc on les crée. Le nombre majeur des loop est 7, et le mineur est le numéro :

```sh
for i in 0 1 2 3; do [ -e /dev/loop$i ] || mknod /dev/loop$i b 7 $i; done
```

Il n’y a pas d’udev non plus, donc personne ne crée les nœuds de partition : après un `parted`, aucun `/dev/loop0p1` n’apparaît. La parade est plus élégante que de relancer un démon : un périphérique loop peut viser exactement la région d’une partition.

```sh
losetup -o $((1024*1024)) --sizelimit $((511*1024*1024)) /dev/loop2 disk.raw
```

Ce `/dev/loop2` est, octet pour octet, ce que `mdadm` verrait d’un `sda1`. On peut créer une grappe dessus, la formater, la monter, mesurer où atterrit le superbloc.

Deux pièges m’ont coûté du temps. Le premier : `/nix/store` n’est pas partagé avec la machine podman, donc tout ce qui vient du store doit d’abord être copié sous le répertoire de travail avant d’être monté dans un conteneur. Le second : podman met ses images en cache **par nom**, pas par plateforme, donc une image amd64 récupérée plus tôt est silencieusement réutilisée quand on demande de l’arm64. Il faut l’expliciter, sous peine de voir `mdadm` échouer sans raison apparente :

```bash
podman run --rm --privileged --platform linux/arm64 ...
```

Ce palier a répondu à tout ce qui concerne le disque : la position du superbloc selon la version de métadonnées, le refus de btrfs de monter une grappe incomplète, l’échec de `btrfs device remove` sur un miroir à deux disques, et le fait qu’un `wipefs` sur le disque entier laisse derrière lui le superbloc md d’une installation précédente.

## Palier 3 : poser la question au micrologiciel

Vient la question que Linux ne peut pas trancher. Un micrologiciel UEFI lit la partition EFI avant qu’aucun noyau n’existe, et il n’a pas de pilote md. Savoir s’il y arrive demande un vrai micrologiciel.

QEMU en livre deux, et ils sont déjà dans le paquet : EDK2 pour l’UEFI, SeaBIOS pour le BIOS hérité. On construit une image disque, on la démarre sans aucun système d’exploitation, et on lit la console série.

```bash
qemu-system-x86_64 -machine q35 -m 512 -nodefaults -no-reboot \
  -drive if=pflash,format=raw,unit=0,readonly=on,file="$QEMU/share/qemu/edk2-x86_64-code.fd" \
  -drive if=pflash,format=raw,unit=1,file=vars.fd \
  -drive file=disk-a.raw,format=raw,if=virtio \
  -display none -serial file:serial.log -net none
```

Le micrologiciel annonce lui-même ce qu’il fait. S’il trouve un chargeur il écrit `starting Boot0001`, sinon `failed to load ... Not Found` puis il tombe dans son shell, dont la table de correspondance dit exactement quels systèmes de fichiers il a su reconnaître. C’est un témoin très direct, et qui n’a aucune complaisance.

Restait un obstacle que je croyais rédhibitoire : il me fallait un binaire EFI x86_64 à poser sur l’image, et je n’avais pas de constructeur Linux.

C’est là que j’avais tort, et c’est le déclic de tout ce banc. **Nix substitue les fermetures de n’importe quelle plateforme.** Construire pour `x86_64-linux` sur un Mac est impossible ; le télécharger tout construit depuis le cache ne l’est pas.

```bash
nix build --no-link --print-out-paths 'nixpkgs#legacyPackages.x86_64-linux.systemd'
```

Et surtout, ce binaire, je n’ai jamais besoin de l’**exécuter**. Je dois seulement le copier dans une partition FAT. C’est le micrologiciel émulé qui l’exécutera, et lui est x86_64. La distinction entre construire, exécuter et simplement copier m’avait échappé, et elle changeait tout.

L’émulation a quand même ses limites, et il faut savoir les contourner plutôt que de conclure trop vite. `qemu-user` traduit mal les ioctl de md : sous émulation, `mdadm --create` répond

```
failed to set array info: Not a tty
```

ce qui n’a aucun rapport avec le problème réel. La même chose arrive à `grub-install`, qui sonde la grappe avec un ioctl `RAID_VERSION`. J’ai d’abord déclaré le mode BIOS intestable à cause de ça. C’était faux : la chaîne se coupe en deux. `grub-mkimage` ne fait qu’empaqueter des modules et ne touche aucun périphérique bloc, donc elle tourne très bien en émulation ; `grub-bios-setup` écrit des secteurs mais se contente d’un répertoire ordinaire et ne sonde jamais la grappe. L’affirmation que je voulais tester, elle, reste intacte, parce que c’est le préfixe embarqué dans l’image qui enverra GRUB lire la grappe au démarrage.

Quand une étape échoue sous émulation, la question utile n’est pas « comment émuler mieux » mais « quelle partie de cette commande a réellement besoin du matériel ».

## Palier 4 : installer pour de bon

Les trois premiers paliers avaient répondu à toutes mes questions, et j’ai failli m’arrêter là. J’ai eu tort, et c’est le palier qui m’a le plus appris.

Je croyais avoir besoin d’une machine `x86_64-linux`. En fait non : le Mac est en aarch64, et un invité ARM64 y tourne avec l’accélération matérielle, donc à vitesse quasi native. Il me fallait une ISO d’installation aarch64, et NixOS en publie une qu’il suffit de télécharger au lieu de la construire.

```bash
curl -L -o nixos.iso \
  https://channels.nixos.org/nixos-26.05/latest-nixos-minimal-aarch64-linux.iso
qemu-system-aarch64 -machine virt,accel=hvf -cpu host -smp 4 -m 4096 ...
```

À partir de là, plus rien n’est émulé au sens approximatif du terme : un vrai udev, un vrai initrd systemd, un vrai `nixos-install`, de vrais redémarrages. Et débrancher un disque se fait en retirant une ligne de la ligne de commande QEMU, ce qui est nettement plus pratique qu’un tournevis.

Le coût réel, chiffres en main : une ISO de 1,7 Go, deux disques de 16 Gio créés creux, 16 démarrages de machine enregistrés, et 77 minutes entre le téléchargement et le dernier test. Un cycle démarrage plus vérification tient en une à trois minutes.

C’est peu pour ce que ça rapporte. Ce palier a trouvé trois erreurs, et deux d’entre elles étaient dans **ma** version corrigée du guide, pas dans l’original. J’allais publier un correctif d’une ligne qui ne corrige rien.

## Piloter une console série sans y laisser sa santé

Une ISO d’installation est interactive, et il n’y a pas d’autre canal que la console. J’ai écrit un petit pilote façon `expect` sur la socket série de QEMU.

```bash
qemu-system-aarch64 ... -serial unix:serial.sock,server,nowait
```

Deux pièges méritent d’être connus, parce qu’ils m’ont coûté une heure à eux deux et qu’aucun ne ressemble à ce qu’il est.

**Ne jamais cesser de lire.** QEMU écrit sur la série de manière bloquante. Si le pilote s’arrête de lire, ne serait-ce que pour un `sleep` de cinq secondes, le tampon se remplit et c’est **l’invité** qui se fige, en plein milieu d’une séquence d’échappement. Le symptôme est déroutant : le journal s’arrête net, et ses derniers octets sont une séquence de titre de terminal coupée en deux.

```
b'[?2004h\r\r\n\x1b[1;31m[\x1b]0;root@n'
```

L’invite allait s’écrire, le shell attendait, et il ne manquait que les octets suivants. Il faut donc vider la socket en continu, y compris entre deux frappes de clavier.

**Ne jamais se synchroniser sur un marqueur qu’on a tapé.** Mon premier pilote envoyait `commande ; echo FINI` puis attendait `FINI`. Il l’a trouvé immédiatement, parce que le terminal **renvoie en écho la ligne qu’on tape**, marqueur compris, avant même que la commande ne s’exécute. Le pilote partait alors en avance d’une commande et tout le reste se désynchronisait. La bonne synchronisation est l’invite du shell elle-même, qui n’apparaît qu’une fois la commande terminée.

Dernier conseil, qui supprime beaucoup de frappe : en mode réseau utilisateur, l’invité joint l’hôte à l’adresse `10.0.2.2`. Un serveur HTTP d’une ligne sur l’hôte suffit à livrer les scripts à l’invité, qui n’a plus qu’à faire `curl ... | sh`. On itère alors sur un fichier local au lieu de retaper deux cents lignes dans une console série.

## Ce que chaque palier a trouvé

| Palier | Trouvailles |
|---|---|
| Évaluation Nix | options renommées, paquets disparus, avertissement mdadm ignoré |
| Conteneur privilégié | décalage du superbloc, refus de montage dégradé, `device remove` impossible, nettoyage de disque incomplet |
| QEMU et micrologiciel | la partition EFI illisible par EDK2, le mode BIOS fonctionnel sous SeaBIOS |
| Installation réelle | conflit sur les `fileSystems`, options de montage perdues, correctif `degraded` inopérant |

La répartition m’a surpris. Les paliers bon marché ont trouvé le défaut spectaculaire, celui qui fait l’article : une machine parfaitement installée et incapable de démarrer. Le palier cher, lui, a trouvé les défauts discrets, ceux qu’on publie sans les voir parce qu’ils ressemblent à du bon sens.

Et c’est bien la raison de monter le dernier étage. Un correctif qui a l’air évident, qui s’évalue sans erreur et que tout le monde recommande peut malgré tout ne rien corriger. Il n’y avait qu’une manière de le savoir.

## Ce que j’en retiens

Les paliers ne forment pas une échelle qu’on gravit jusqu’à épuisement du budget. Ce sont quatre interlocuteurs, et on va voir celui qui détient la réponse.

La question à se poser devant un banc de test n’est donc pas « est-ce assez réaliste », mais « est-ce que cette mesure traverse la frontière où vit le problème ». Un guide qui vérifie son installation en interrogeant Linux ne saura jamais ce qu’un micrologiciel en pense. Un banc qui s’arrête à l’évaluation ne saura jamais si la machine démarre.

C’est exactement ce qui m’est arrivé, d’ailleurs, à une frontière que je n’avais pas vue. J’ai failli publier que `degraded` dans les options de montage suffisait à redémarrer sur un disque. L’option est correcte. Elle arrive simplement après le moment où tout se joue, et seul un vrai démarrage pouvait me le dire.

## Pour reproduire

- [QEMU](https://www.qemu.org/docs/master/system/target-arm.html) pour la machine `virt` ARM64 et ses options de firmware
- [Les ISO NixOS](https://nixos.org/download/), dont les images aarch64, à télécharger plutôt qu’à construire
- [losetup(8)](https://man7.org/linux/man-pages/man8/losetup.8.html), pour `--offset` et `--sizelimit`
- [Le manuel NixOS](https://nixos.org/manual/nixos/stable/), pour `nixos-install` et `nixos-generate-config`
