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.
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 :
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 :
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 :
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.
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
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 :
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 :
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é
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 :
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 :
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 :
{ 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 :
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 :
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
nixos-install
Puis, avant de redémarrer :
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 :
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 :
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.
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 :
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.
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 :
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.
| 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), section
--metadata, pour la position du superbloc et le défaut de--create - Software RAID, guide d’administration du noyau
- btrfs replace et Volume management
- Le module GRUB de NixOS, pour
mirroredBootset ses assertions - Recherche d’options NixOS, 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.