---
title: Deux SSD en miroir sous NixOS
description: Installer NixOS sur deux disques miroir, en UEFI comme en BIOS. La manière évidente de rendre le démarrage redondant produit une machine parfaitement installée et définitivement incapable de démarrer, et aucune vérification d’usage ne le signale.
publishedAt: 2026-10-28
tags:
  - nixos
  - btrfs
  - uefi
---

Deux SSD, tout en miroir, et une machine qui continue de démarrer quand un disque meurt. C’est l’objectif, et il est à moitié atteignable.

Je suis parti d’un guide écrit par un modèle de langage, près de deux mille cinq cents lignes, mieux rangé que la plupart des tutoriels humains. Je l’ai mesuré couche par couche, puis installé pour de bon dans une machine virtuelle. Sa disposition UEFI ne démarre sur aucune machine, et aucune de ses vérifications ne le dit.

Autant annoncer tout de suite la moitié qui ne marche pas : **une machine à qui il manque un disque ne redémarre pas seule**, et aucune option d’une ligne n’y change rien. Je l’ai cru, je l’avais écrit, l’installation réelle m’a détrompé.

## Le piège : ne pas mettre l’ESP en RAID sous UEFI

C’est la décision structurante, et elle se prend avant la première commande.

L’idée séduisante consiste à réunir les deux partitions EFI dans un RAID 1 logiciel. Le noyau les voit comme un volume unique, les écritures sont dupliquées, et si un disque lâche l’autre contient déjà tout. Plus de `/boot` à synchroniser à la main.

Le problème est un problème d’ordre. Quand une machine UEFI démarre, le micrologiciel doit lire la partition EFI avant qu’aucun noyau n’existe. Il n’a pas de pilote md. Il ouvre la partition, lit son premier secteur, et s’attend à y trouver l’en-tête d’un système de fichiers FAT.

Or `mdadm` écrit ses propres métadonnées sur chaque membre de la grappe, et `mdadm --create` sans `--metadata` choisit la version 1.2, celle qui place son superbloc à 4 kio du début. Deux grappes identiques, une seule variable :

| | métadonnées 1.2 (le défaut) | métadonnées 1.0 |
|---|---|---|
| Premiers octets du membre | `00 00 00 00` | `eb 58 90 6d` |
| Octet 510, signature d’amorçage | `00 00` | `55 aa` |
| En-tête FAT32 trouvé à | nulle part | octet 0 |
| Superbloc md | octet 4096, depuis le début | 8192 octets avant la fin |
| `mdadm --examine` | `Data Offset : 2048 sectors` | aucun décalage |

`eb 58 90` est l’instruction de saut qui ouvre un secteur d’amorçage FAT32, `55 aa` sa signature de fermeture. En 1.2 il n’y a ni l’une ni l’autre : le superbloc md occupe la place, et les données commencent un mébioctet plus loin que là où le micrologiciel regarde.

Démarré sous EDK2, avec un vrai chargeur d’amorçage au chemin de repli `\EFI\BOOT\BOOTX64.EFI`, le résultat est sans ambiguïté :

```
BdsDxe: failed to load Boot0001 "UEFI Misc Device" from PciRoot(0x0)/Pci(0x1,0x0): Not Found
BdsDxe: failed to load Boot0002 "UEFI Misc Device 2" from PciRoot(0x0)/Pci(0x2,0x0): Not Found
BdsDxe: loading Boot0003 "EFI Internal Shell"
```

La table de correspondance de ce shell tranche la question : elle liste `BLK0:` à `BLK5:`, donc les disques **et** leurs partitions GPT, ESP comprise. Il n’y a aucune entrée `FS0:`. Le micrologiciel a parfaitement vu les partitions. Il n’a trouvé de système de fichiers sur aucune.

Passer en `--metadata 1.0` déplace le superbloc à la fin et rend la lecture possible, je l’ai vérifié. Je ne le recommande pas : le micrologiciel peut alors écrire dans l’ESP sans passer par md, pour ses variables d’amorçage ou une mise à jour, et désynchroniser le miroir en silence. Raisonnement, pas mesure, mais il suffit à préférer autre chose.

**La bonne réponse sous NixOS s’appelle `mirroredBoots`.** Deux ESP indépendantes, chacune avec son propre FAT32, et NixOS écrit les deux à chaque `nixos-rebuild boot`. Le micrologiciel n’a plus rien à comprendre : il lit une partition FAT ordinaire, sur le disque qui lui reste.

Chaque ESP est autonome : NixOS y recopie le chargeur **et** les noyaux, une fois par entrée `mirroredBoots`. Sur la machine installée, les deux en portaient deux chacune.

Mesuré sous EDK2, NVRAM vierge :

| Disques attachés | Le micrologiciel trouve et exécute le chargeur |
|---|---|
| les deux | oui |
| disque A seul | oui |
| disque B seul | oui |

C’est l’étape exacte où la disposition du guide d’origine échoue.

**Le mode BIOS, lui, est un autre monde, et le RAID sur `/boot` y fonctionne vraiment.** Le micrologiciel y charge le `core.img` de GRUB depuis une petite partition dédiée, en secteurs bruts, sans aucun système de fichiers. Ce `core.img` embarque les modules `mdraid1x` et `fat` : GRUB assemble la grappe lui-même et lit `/boot` dessus. Démarré sous SeaBIOS, avec les métadonnées 1.2, la trace série contient les marqueurs placés dans un `grub.cfg` qui n’existe que sur la grappe :

```
MARKER-GRUB-READ-GRUB-CFG-FROM-MD-ARRAY
MARKER-ROOT-IS-md/0
```

GRUB a résolu sa racine en `md/0` tout seul, et avec chaque disque seul également. L’erreur du guide d’origine n’a donc rien à voir avec mdadm. Elle porte sur la couche qui fait la lecture.

## Partitionner

Les deux disques vont être effacés. Repérez-les par identifiant stable, pas par `/dev/sda` : l’ordre d’énumération peut changer d’un démarrage à l’autre, et ces noms vont finir écrits dans votre configuration.

```bash
ls -l /dev/disk/by-id/ | grep -v part
DISK_A=/dev/disk/by-id/nvme-SAMSUNG_XXXX_YYYY
DISK_B=/dev/disk/by-id/nvme-SAMSUNG_XXXX_ZZZZ
```

Le nettoyage demande deux passes, et l’ordre n’est pas négociable :

```bash
mdadm --stop --scan 2>/dev/null || true

# les superblocs md d'une ancienne installation vivent DANS les partitions
for p in "$DISK_A"-part* "$DISK_B"-part*; do
  [ -e "$p" ] || continue
  mdadm --zero-superblock "$p" 2>/dev/null || true
  wipefs --all --force "$p"
done

wipefs --all --force "$DISK_A"
wipefs --all --force "$DISK_B"
```

J’avais d’abord condensé cela en un `wipefs` par disque, en me disant que détruire la table de partitions suffisait. C’est faux : un superbloc md vit à l’intérieur de la région de l’ancienne partition, là où un effacement du disque entier ne va pas le chercher.

```
après un wipefs du disque entier seulement :
   mdadm --examine trouve un superbloc : oui
après un wipefs des partitions, puis du disque :
   mdadm --examine trouve un superbloc : non
```

Le guide d’origine effaçait bien les superblocs partition par partition. Sur ce point il avait raison et j’avais tort de simplifier.

En revanche il proposait aussi d’écraser la fin de chaque disque au `dd`, pour la table GPT de secours, et cette étape-là est réellement inutile : `wipefs` efface déjà les trois signatures.

```
/dev/loop0: 8 bytes were erased at offset 0x00000200 (gpt)
/dev/loop0: 8 bytes were erased at offset 0x3ffffe00 (gpt)
/dev/loop0: 2 bytes were erased at offset 0x000001fe (PMBR)
```

La deuxième ligne est l’en-tête de secours, en fin de disque. Sans `wipefs`, le `dd` de début seul laisse cette copie intacte et `sgdisk` régénère l’en-tête principal à partir d’elle.

**En UEFI**, une ESP par disque et le reste pour les données :

```bash
for d in "$DISK_A" "$DISK_B"; do
  parted -s "$d" mklabel gpt
  parted -s "$d" mkpart ESP fat32 1MiB 512MiB
  parted -s "$d" set 1 esp on
  parted -s "$d" mkpart primary 512MiB 100%
done

mkfs.fat -F 32 -n ESP-A "$DISK_A-part1"
mkfs.fat -F 32 -n ESP-B "$DISK_B-part1"

DATA_A="$DISK_A-part2"
DATA_B="$DISK_B-part2"
```

Les deux ESP reçoivent deux étiquettes distinctes, et c’est volontaire : ce sont deux systèmes de fichiers séparés, que NixOS doit pouvoir monter séparément.

**En BIOS**, trois partitions, dont une de deux mébioctets que GRUB utilise en interne et qui n’est ni formatée ni montée :

```bash
for d in "$DISK_A" "$DISK_B"; do
  parted -s "$d" mklabel gpt
  parted -s "$d" mkpart primary 1MiB 3MiB
  parted -s "$d" set 1 bios_grub on
  parted -s "$d" mkpart primary 3MiB 515MiB
  parted -s "$d" mkpart primary 515MiB 100%
done

mdadm --create /dev/md0 --level=1 --raid-devices=2 --name=boot \
  "$DISK_A-part2" "$DISK_B-part2"
mkfs.fat -F 32 -n BOOT /dev/md0

DATA_A="$DISK_A-part3"
DATA_B="$DISK_B-part3"
```

## btrfs RAID 1 et les sous-volumes

Identique dans les deux modes, sauf que la partition de données change de numéro : la deuxième en UEFI, la troisième en BIOS. D’où les `DATA_A` et `DATA_B` des blocs précédents. L’inattention coûte cher ici : en BIOS, `part2` est un membre du RAID de `/boot`, et un `mkfs.btrfs` dessus détruirait l’amorçage.

```bash
mkfs.btrfs -L nixos -d raid1 -m raid1 "$DATA_A" "$DATA_B"

mount /dev/disk/by-label/nixos /mnt
btrfs subvolume create /mnt/@
btrfs subvolume create /mnt/@home
btrfs subvolume create /mnt/@snapshots
umount /mnt
```

`-d raid1 -m raid1` met données et métadonnées en miroir. Les sous-volumes séparent ce qui se sauvegarde de ce qui se remplace : `@` la racine, `@home` les comptes, `@snapshots` les instantanés.

## Monter

```bash
mount -o subvol=@,compress=zstd,noatime /dev/disk/by-label/nixos /mnt
mkdir -p /mnt/{home,.snapshots}
mount -o subvol=@home,compress=zstd,noatime /dev/disk/by-label/nixos /mnt/home
mount -o subvol=@snapshots,compress=zstd,noatime /dev/disk/by-label/nixos /mnt/.snapshots
```

Puis les partitions d’amorçage, selon le mode. En UEFI, les deux ESP :

```bash
mkdir -p /mnt/boot /mnt/boot-fallback
mount -o fmask=0077,dmask=0077 /dev/disk/by-label/ESP-A /mnt/boot
mount -o fmask=0077,dmask=0077 /dev/disk/by-label/ESP-B /mnt/boot-fallback
```

En BIOS, la grappe :

```bash
mkdir -p /mnt/boot
mount -o fmask=0077,dmask=0077 /dev/md0 /mnt/boot
```

`fmask=0077` et `dmask=0077` réservent la lecture à root. FAT32 ne porte pas de permissions Unix, ces masques sont donc le seul levier, et la partition contient le noyau et l’initrd. NixOS y fait aussi pointer la graine aléatoire de systemd, quel que soit le chargeur.

## La configuration, sans écraser le matériel détecté

```bash
nixos-generate-config --root /mnt
```

Deux fichiers, et le plus gros piège du guide d’origine.

`hardware-configuration.nix` ne contient pas que du matériel : il contient **votre disposition de systèmes de fichiers**, relevée sur ce qui est monté à cet instant. D’où l’ordre des étapes précédentes. Voici ce qu’il a écrit lors de mon installation :

```nix
  boot.initrd.availableKernelModules = [ "virtio_pci" "virtio_scsi" "sr_mod" ];
  fileSystems."/" =
    { device = "/dev/disk/by-uuid/05a364d3-6496-4908-9a0d-6f0e3dc84948";
      fsType = "btrfs";
      options = [ "subvol=@" ];
    };
  fileSystems."/boot" =
    { device = "/dev/disk/by-uuid/C3C1-28D9";
      fsType = "vfat";
      options = [ "fmask=0077" "dmask=0077" ];
    };
```

Il désigne les volumes par UUID : les étiquettes n’étaient qu’une commodité pour taper les montages. Et il a gardé `subvol=` et les masques FAT, mais **laissé tomber `compress=zstd` et `noatime`**.

J’ai installé une machine ainsi, sans rien ajouter. `findmnt`, sur le système démarré :

```
/              rw,relatime,discard=async,space_cache=v2,subvolid=256,subvol=/@
/home          rw,relatime,discard=async,space_cache=v2,subvolid=257,subvol=/@home
/boot          rw,relatime,fmask=0077,dmask=0077,codepage=437,...
```

Pas de compression, `relatime` au lieu de `noatime`. Les masques FAT ont survécu. Qui s’arrête là installe un système sans compression, et rien ne le lui dira.

Le guide d’origine remplaçait `configuration.nix` par un `cat > ...`, écrasant la ligne `imports = [ ./hardware-configuration.nix ]` écrite quelques secondes plus tôt. L’extrait ci-dessus montre le prix : le profil de virtualisation et les modules `virtio_*` de l’initrd, et avec eux la racine, introuvable dans une machine virtuelle. Sur ses configurations j’ai mesuré la même perte en trois endroits : initrd réduit à `ahci nvme sd_mod md_mod raid1`, microcode plus mis à jour, micrologiciels redistribuables plus installés.

Garder l’import **et** redéclarer les `fileSystems` ne marche pas davantage. Je m’y suis cassé le nez à ma première tentative :

```
error: The option `fileSystems."/".device' has conflicting definition values:
- In `/mnt/etc/nixos/configuration.nix': "/dev/disk/by-label/nixos"
- In `/mnt/etc/nixos/hardware-configuration.nix': "/dev/disk/by-uuid/05a364d3-..."
```

Le guide affirme que « la version de configuration.nix prendra le dessus ». Elle entre en conflit, et l’évaluation s’arrête net.

La sortie tient à une différence de type entre deux options voisines. `device` est une chaîne : deux définitions se contredisent. `options` est une liste : deux définitions **fusionnent**. On ajoute donc ce qui manque, sans jamais redéclarer un périphérique :

```nix
  fileSystems."/".options = [ "compress=zstd" "noatime" ];
  fileSystems."/home".options = [ "compress=zstd" "noatime" ];
  fileSystems."/.snapshots".options = [ "compress=zstd" "noatime" ];
```

Ce qui donne, à l’évaluation, la liste complète, le périphérique restant celui qu’a écrit la détection :

```
[ "x-initrd.mount" "compress=zstd" "noatime" "subvol=@" ]
```

`configuration.nix` ne redéclare donc aucun système de fichiers. Le voici en entier.

**En UEFI :**

```nix
{ config, pkgs, lib, ... }:
{
  imports = [ ./hardware-configuration.nix ];

  boot.loader.grub = {
    enable = true;
    efiSupport = true;
    efiInstallAsRemovable = true;
    mirroredBoots = [
      { path = "/boot"; devices = [ "/dev/disk/by-id/nvme-SAMSUNG_XXXX_YYYY" ]; }
      { path = "/boot-fallback"; devices = [ "/dev/disk/by-id/nvme-SAMSUNG_XXXX_ZZZZ" ]; }
    ];
  };
  boot.loader.efi.canTouchEfiVariables = false;

  boot.supportedFilesystems = [ "btrfs" ];
  boot.initrd.supportedFilesystems = [ "btrfs" ];

  fileSystems."/".options = [ "compress=zstd" "noatime" ];
  fileSystems."/home".options = [ "compress=zstd" "noatime" ];
  fileSystems."/.snapshots".options = [ "compress=zstd" "noatime" ];

  nix.package = pkgs.lix;

  services.btrfs.autoScrub = {
    enable = true;
    interval = "weekly";
    fileSystems = [ "/" ];
  };
  services.smartd = {
    enable = true;
    notifications.wall.enable = true;
  };

  networking.hostName = "nixos";
  networking.networkmanager.enable = true;
  time.timeZone = "Europe/Paris";
  i18n.defaultLocale = "fr_FR.UTF-8";

  users.users.someone = {
    isNormalUser = true;
    extraGroups = [ "wheel" "networkmanager" ];
  };

  environment.systemPackages = with pkgs; [
    vim git btrfs-progs compsize mdadm smartmontools
  ];

  services.openssh.enable = true;

  system.stateVersion = "26.05";
}
```

Trois lignes méritent un mot.

`efiInstallAsRemovable = true` fait écrire le chargeur au chemin de repli que le micrologiciel cherche quand aucune entrée NVRAM ne le guide. C’est ce drapeau qui le décide, pas GRUB par défaut. Deux installations sur une ESP neuve, le drapeau pour seule différence :

```
avec --removable :   <ESP>/EFI/BOOT/BOOTAA64.EFI
sans --removable :   <ESP>/EFI/NixOS/grubaa64.efi
```

Une entrée NVRAM désigne une partition précise par son identifiant et meurt avec le disque qui la porte, donc le second chemin n’est atteignable que par elle. Le chemin de repli se trouve tout seul sur n’importe quel disque restant, ce que le micrologiciel a fait dans le test à un disque.

En contrepartie, nixpkgs l’interdit avec les variables EFI, explicitement :

```
If you wish to to use boot.loader.grub.efiInstallAsRemovable,
then turn off boot.loader.efi.canTouchEfiVariables
```

`nix.package = pkgs.lix` installe Lix, en 2.95.4 au moment où j’écris. Le guide le promettait dans son nom de fichier et ne l’utilisait nulle part.

`system.stateVersion` porte la version depuis laquelle vous installez et ne change jamais ensuite. Ne reprenez pas la mienne sans vérifier la vôtre.

**En BIOS**, seul le bloc d’amorçage change, et `/boot-fallback` disparaît :

```nix
  boot.loader.grub = {
    enable = true;
    devices = [
      "/dev/disk/by-id/nvme-SAMSUNG_XXXX_YYYY"
      "/dev/disk/by-id/nvme-SAMSUNG_XXXX_ZZZZ"
    ];
  };

  boot.swraid = {
    enable = true;
    mdadmConf = ''
      ARRAY /dev/md0 metadata=1.2 name=boot
      MAILADDR root
    '';
  };

  fileSystems."/boot" = {
    device = "/dev/md0";
    fsType = "vfat";
    options = [ "fmask=0077" "dmask=0077" ];
  };
```

La ligne `MAILADDR` n’est pas décorative. Sans elle, le module mdadm prévient :

```
mdadm: Neither MAILADDR nor PROGRAM has been set. This will cause the `mdmon` service to crash.
```

Avec elle, les deux configurations ci-dessus s’évaluent sans un seul avertissement.

Si vous ajoutez GNOME, attention aux options renommées :

```nix
  services.xserver.enable = true;
  services.displayManager.gdm.enable = true;
  services.desktopManager.gnome.enable = true;
  environment.systemPackages = with pkgs; [ gnome-tweaks gnome-terminal ];
```

Les anciens chemins sous `services.xserver.` fonctionnent encore mais préviennent, et les paquets ne sont plus sous `gnome.`, le préfixe ayant été aplati.

## Installer

```bash
nixos-install
```

Puis, avant de redémarrer :

```bash
cat /proc/mdstat                      # en BIOS uniquement
ls -la /mnt/boot/EFI/BOOT/            # en UEFI, sur les deux ESP
ls -la /mnt/boot-fallback/EFI/BOOT/
```

Ces vérifications sont utiles mais faibles, et c’est le point que je voudrais faire passer. `grub-install` écrit dans un système de fichiers monté, et Linux monte parfaitement ce qu’aucun micrologiciel ne lira, puisqu’il a le pilote md. Sur l’ESP en RAID de la première section :

```
exit code: 0
Installation finished. No error reported.
```

Les fichiers sont là, et ce sont de vrais fichiers. Rien dans le chemin d’installation n’est un micrologiciel, donc rien n’a de raison de se demander si un micrologiciel atteindrait ces octets. L’installateur vérifie que l’écriture a eu lieu. La question portait sur qui saura la relire.

La seule vérification qui compte est un démarrage avec un disque débranché. Faites-la le jour de l’installation, pendant qu’il n’y a rien à perdre.

## Ce qui se passe vraiment quand un disque meurt

La redondance échoue à deux étages indépendants. Le premier, micrologiciel et chargeur, est réglé par ce qui précède. Le second est btrfs, et il est plus désagréable.

| Opération | Résultat |
|---|---|
| `mount` ordinaire d’un raid1 à deux disques, un disque absent | échoue |
| `mount -o degraded` | réussit, données lisibles |

Un btrfs RAID 1 à deux disques refuse un montage ordinaire quand un disque manque. Les options de racine du guide, `subvol=@,compress=zstd,noatime`, ne portent pas `degraded`. Le mot apparaît trois fois dans ses pages, chaque fois comme un état à constater, jamais comme l’option qui permet de démarrer.

Et le secours est moins accessible qu’avant. L’initrd systemd est actif par défaut, et l’accès d’urgence y est fermé par défaut :

```
emergencyAccess
  Set to true for unauthenticated emergency access, and false or
  null for no emergency access.
  default = false
```

Vérifié sur la machine installée, démarrée avec un seul disque. Le micrologiciel a lancé le chargeur, l’étage d’amorçage a tenu. Puis :

```
Dependency failed for /sysroot.
Dependency failed for Initrd Root File System.
[  OK  ] Started Emergency Shell.
[  OK  ] Reached target Emergency Mode.
You are in emergency mode. After logging in, type "journalctl -xb" to view
Cannot open access to console, the root account is locked.
```

La dernière ligne est tout le problème. Le shell de secours existe, et il est hors d’atteinte.

### `degraded` dans les options ne suffit pas

C’est ici que j’ai dû corriger ce que j’allais publier. Puisque les options fusionnent, ajouter `degraded` tient sur une ligne, et c’est ce que tout le monde propose :

```nix
  fileSystems."/".options = [ "degraded" ];
```

Je l’ai appliquée à la machine, rebâtie avec `nixos-rebuild boot`, puis redémarrée avec un seul disque. Elle n’a pas démarré. Voici pourquoi :

```
         Expecting device /dev/disk/by-uuid…64d3-6496-4908-9a0d-6f0e3dc84948...
[ TIME ] Timed out waiting for device /dev/…a364d3-6496-4908-9a0d-6f0e3dc84948.
[DEPEND] Dependency failed for /sysroot.
```

Les points de suspension sont ceux de systemd, qui raccourcit les noms d’unités trop longs. Le montage n’est jamais tenté. udev ne publie le lien `by-uuid` d’un btrfs que lorsque **tous** ses membres sont présents : systemd attend un nœud qui n’apparaîtra jamais, et l’option qu’il aurait utilisée ne joue aucun rôle. Elle est correcte et arrive trop tard.

J’ai essayé l’échappatoire évidente, désigner la racine par une partition plutôt que par l’UUID, avec le `lib.mkForce` que le message d’erreur suggérait lui-même. Elle échoue aussi, pour une raison indépendante : l’initrd ne crée pas les liens `by-id`.

```
         Expecting device /dev/disk/by-id/virtio-DISKA-part2...
[ TIME ] Timed out waiting for device /dev/disk/by-id/virtio-DISKA-part2.
```

Et ce disque-là était présent.

Aucune option d’une ligne ne fait redémarrer cette machine seule sur un disque. Le choix n’est pas entre « sans intervention » et « à la main », mais entre « à la main, préparé » et « à la main, enfermé dehors ».

### Ce qui marche, et que j’ai mesuré

Une seule ligne compte vraiment, et c’est celle qui vous laisse entrer :

```nix
  boot.initrd.systemd.emergencyAccess = "$y$j9T$...";
```

Un mot de passe haché, ou `true` pour un shell sans authentification si la machine est physiquement hors d’atteinte. Le même démarrage à un disque donne alors un shell, et la récupération tient en deux commandes :

```
# btrfs device scan
Scanning for Btrfs filesystems
registered: /dev/vda2

# mount -o degraded,subvol=@ /dev/vda2 /m
RC=0

# ls /m
bin   boot  boot-fallback  dev  etc  home  lib  nix
proc  root  run  srv  sys   tmp  usr  var

# btrfs filesystem show /m
Label: 'nixos'  uuid: 05a364d3-6496-4908-9a0d-6f0e3dc84948
	Total devices 2 FS bytes used 1.24GiB
	devid    1 size 15.50GiB used 3.26GiB path /dev/vda2
	devid    2 size 0 used 0 path  MISSING
```

Le volume se monte, les données sont là, btrfs nomme le disque absent.

Donc : « la machine démarre même si un disque meurt » est faux sur cette disposition. L’amorçage survit, les données survivent, la remise en route demande un clavier. C’est déjà beaucoup, et très différent de ce qu’annonce le guide.

Une légende à ne pas répéter, et j’étais sur le point de la reprendre : monter un btrfs RAID 1 dégradé en écriture ne condamne pas le volume. Les groupes `single` apparaissent, mais le second montage dégradé réussit toujours sur un noyau 7.1.3. Les noyaux actuels évaluent la tolérance groupe de blocs par groupe de blocs.

## Remplacer le disque

Partitionnez le disque neuf à l’identique, redéfinissez `DATA_B` selon votre mode, puis traitez les deux étages séparément.

L’amorçage, en UEFI : formatez la nouvelle ESP avec la bonne étiquette, montez-la, laissez NixOS la peupler.

```bash
mkfs.fat -F 32 -n ESP-B /dev/disk/by-id/<neuf>-part1
mount -o fmask=0077,dmask=0077 /dev/disk/by-label/ESP-B /boot-fallback
nixos-rebuild boot
```

En BIOS, c’est mdadm qui reconstruit :

```bash
mdadm --manage /dev/md0 --add /dev/disk/by-id/<neuf>-part2
watch cat /proc/mdstat
nixos-rebuild boot
```

Les données ensuite. Le guide proposait `btrfs device remove` puis `device add`. La première commande ne peut pas réussir sur une grappe à deux disques, dans aucun des deux états :

| Commande | Résultat |
|---|---|
| `device remove`, les deux disques vivants | `ERROR: unable to go below two devices on raid1` |
| `device remove`, contre le disque mort | `ERROR: Invalid argument` |
| `btrfs replace start` | réussit, données intactes |

La commande qui existe s’appelle `btrfs replace`, et le guide ne la mentionne jamais.

```bash
btrfs filesystem show /            # relever le devid du disque manquant
btrfs replace start <devid> "$DATA_B" /
btrfs replace status /
```

Le devid, pas le chemin : la documentation précise que ce format devient nécessaire dès que le disque source est déconnecté, ce qui est exactement le cas d’un disque mort. Le disque cible doit être de taille égale ou supérieure.

Son `balance` final, lui, est nécessaire, simplement pas là où il le mettait : les groupes `single` survivent à un remplacement.

```
profils juste après le replace :
   Data, single: total=963.81MiB, used=250.78MiB
   Data, RAID1:  total=1.52GiB,   used=549.22MiB

après balance -dconvert=raid1 -mconvert=raid1 :
   Data, RAID1:     total=980.00MiB, used=800.00MiB
   Metadata, RAID1: total=256.00MiB, used=1.16MiB
   System, RAID1:   total=32.00MiB,  used=16.00KiB
```

Donc, pour finir :

```bash
btrfs balance start -dconvert=raid1 -mconvert=raid1 /
btrfs filesystem usage /
```

Bon réflexe, mauvaise séquence.

## Surveiller

Trois choses suffisent, déjà dans la configuration plus haut. Le `autoScrub` hebdomadaire vérifie les sommes de contrôle et répare depuis la copie saine. `smartd` surveille l’état des disques. En BIOS, `MAILADDR` fait exister le service qui prévient quand la grappe se dégrade.

En UEFI il n’y a pas d’équivalent, faute de grappe md : un disque btrfs manquant ne se signale que par `btrfs device stats` ou par le scrub. La surveillance compte donc plus ici qu’ailleurs, puisque la perte d’un disque reste invisible jusqu’au prochain démarrage.

Aucun de ces outils ne vous dira si la machine démarrerait. Mettez-vous un rappel pour débrancher un disque une fois par an.

## Ce que j’ai vérifié, et comment

Sur un Mac sans constructeur Linux, en quatre paliers de coût croissant, plus la lecture des sources de nixpkgs là où l’affirmation porte sur ce que NixOS fait et non sur ce que le matériel répond.

Le quatrième palier est une installation réelle : ISO officielle NixOS 26.05 pour aarch64, machine QEMU avec deux disques vierges et firmware EDK2, ce guide déroulé commande par commande jusqu’à `nixos-install`, puis des redémarrages en débranchant des disques. C’est lui qui a trouvé les trois corrections les plus importantes de cet article, dont deux portaient sur ce que j’avais écrit. [La méthode a son propre article](/blog/verifier-un-guide-linux-sans-machine-linux).

| Affirmation | Établie par |
|---|---|
| Position du superbloc selon la version de métadonnées | mdadm réel, dans un conteneur privilégié |
| Un micrologiciel UEFI ne lit pas une ESP en RAID 1.2 | QEMU et EDK2, sans système d’exploitation |
| Le micrologiciel exécute le chargeur depuis chaque disque seul, deux ESP indépendantes | QEMU et EDK2, un disque attaché à la fois |
| GRUB lit `/boot` sur une grappe md en mode BIOS | QEMU et SeaBIOS, marqueurs dans `grub.cfg` |
| `grub-install` réussit sur une ESP illisible | grub-install réel, code de retour et fichiers écrits |
| Seul `--removable` écrit au chemin de repli | grub-install réel, deux installations comparées |
| Un btrfs RAID 1 dégradé exige `-o degraded` | btrfs réel, noyau 7.1.3 |
| `device remove` échoue, `replace` réussit | idem |
| `wipefs` efface la GPT de secours | idem, sur fichier en loop |
| Un wipefs du disque seul laisse un superbloc md | idem, ancienne installation reconstituée |
| Les configurations ci-dessus s’évaluent sans avertissement | évaluation Nix, nixpkgs 26.11pre |
| Les options GNOME ont été renommées | idem, avertissements de renommage capturés |
| Lix 2.95.4 s’installe par `nix.package` | idem |
| La configuration publiée s’évalue contre un `hardware-configuration.nix` réel | évaluation Nix du bloc extrait de cet article |
| Le déroulé complet installe un NixOS qui démarre | installation réelle, QEMU et EDK2, NVRAM vierge |
| Les deux ESP reçoivent chargeur et noyaux | installation réelle, `find` sur chacune |
| `nixos-generate-config` perd `compress=zstd` et `noatime` | installation réelle, `findmnt` après démarrage |
| Redéclarer `fileSystems` entre en conflit avec le fichier généré | installation réelle, l’évaluation s’arrête |
| `degraded` seul ne fait pas démarrer sur un disque | installation réelle, un seul disque attaché |
| L’accès d’urgence ouvert permet de monter et de lire | installation réelle, shell de secours |
| Chaque `mirroredBoots` reçoit noyaux et initrd | lecture de `grub.nix` et `install-grub.pl` |
| L’accès d’urgence de l’initrd est fermé par défaut | lecture de la définition de l’option |
| `--removable` est incompatible avec les variables EFI | assertion nixpkgs, déclenchée volontairement |

La limite honnête est l’architecture. L’installation réelle et les mesures `grub-install` ont tourné en `aarch64-linux`, seul natif sur cette machine, et le chargeur s’y nomme `BOOTAA64.EFI` au lieu de `BOOTX64.EFI`. Rien de ce qui est en jeu ici n’en dépend : `mirroredBoots`, la fusion des options de montage, l’attente d’un nœud `by-uuid` par systemd et le comportement de btrfs sont le même code des deux côtés. Le décalage du superbloc et le mode BIOS, eux, ont bien été mesurés sur x86.

Reste non testé l’exercice sur du matériel physique, avec un micrologiciel de carte mère plutôt qu’EDK2, et un disque qui meurt vraiment plutôt qu’un câble virtuel débranché.

## Sources

- [mdadm(8)](https://man7.org/linux/man-pages/man8/mdadm.8.html), section `--metadata`, pour la position du superbloc et le défaut de `--create`
- [Software RAID, guide d’administration du noyau](https://www.kernel.org/doc/html/latest/admin-guide/md.html)
- [btrfs replace](https://btrfs.readthedocs.io/en/latest/btrfs-replace.html) et [Volume management](https://btrfs.readthedocs.io/en/latest/Volume-management.html)
- [Le module GRUB de NixOS](https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/system/boot/loader/grub/grub.nix), pour `mirroredBoots` et ses assertions
- [Recherche d’options NixOS](https://search.nixos.org/options), pour vérifier les noms avant de les écrire

Le comportement des micrologiciels décrit ici n’est pas cité : il est mesuré, sous EDK2 et SeaBIOS.
