Libérez votre NAS D-link avec Debian + Btrfs

Un NAS pour faire des sauvegardes c'est bien, mais avec un système en logiciel libre et à jour c'est mieux.

D-Link propose des NAS de bonne facture pour des sommes modiques, qu'il est possible de passer intégralement sous Debian.

Le DNS-325, crédit: D-LINK

EDIT 12/04/2021 : utilisation des commandes btrfs récentes, suppression d'un délai au démarrage (/boot)

EDIT 27/11/2019 : mise à jour pour Debian Stretch puis Buster, cf notes pour la mise à niveau.

EDIT 13/11/2016 : mise à jour pour Debian Jessie, cf notes pour la mise à niveau.

À l'époque (en 2012), j'avais acheté un NAS 2 baies, le DNS-325 pour une centaine d'euros, quelques caractéristiques :

  • 2 emplacements pour disques sata 3"5 (non fournis)
  • boîtier métal de bonne facture
  • 256MiO de RAM
  • processeur ARM kirkwood à 1.2GHz
  • une mémoire NAND de 128MiO (le firmware dlink initial est stocké sur cette mémoire)
  • Faible consommation électrique
  • ~90€ en 2012

Ce modèle, facile à libérer (supporté par GNU/Linux et u-boot sans aucun patch) ne semble plus produit, plusieurs alternatives s'offrent à vous :

  • en acheter un d'occasion pour ~70€
  • utiliser un modèle légèrement inférieur mais facilement hackable : DNS-320 ou DNS-323
  • utiliser le DNS-327L qui est le remplaçant du DNS-325, et est lui aussi hackable quoiqu'un peu plus difficilement. il se trouve autour de 120€ et dispose du double de RAM (512MiO).

L'avantage de cette gamme est que les modèles sont deux fois moins coûteux que leurs équivalents chez synology, de bonne facture, et d'une architecture (ARM kirkwood) bien supportée par Linux. c'est donc hackable à souhait :-). Ce tutoriel doit fonctionner, à quelques ajustements prêt, pour d'autres modèles de la gamme.

NOTE 10/4/2017: Ce tutoriel, réalisé pour le DNS-325, semble s'appliquer également pour les DNS-320, cf commentaire de CamilleBC pour les détails.

Objectif

L'objectif de ce guide est d'obtenir :

  • un NAS faisant tourner la version complète de Debian (testé avec Jessie et Wheezy)
  • Une redondance des disques (RAID1)
  • Un système de snapshots efficace en cpu et en espace disque
  • un système de secours et une procédure pour démarrer et réparer même lorsque les disques sont HS.

Techniquement cela donnera :

  • Un u-boot à jour pour gérer le démarrage
  • le /boot (contenant initramfs et le noyau Linux) stockés sur la NAND (mémoire interne du NAS) ;
  • le reste du système Debian/GNU/linux ainsi que les données stockés sur les disques ;
  • Le système de fichiers btrfs prenant en charge RAID 1, taille dynamique des partitions et snapshots (évite d'empiler lvm, mtd et ext4).

Notons au passage qu'on obtient une configuration ne faisant plus tourner aucun code initialement fourni par D-LINK, si les backdoors constructeurs vous préoccupent, cela peut vous intéresser...

Choix

Plusieurs voies sont possibles pour installer Debian, nous choisissons ici :

  • utilisation du port série du NAS
  • pas de serveur TFTP ou NFS à configurer

Console série

Il faut commencer par avoir un accès à la console série du NAS, on peut difficilement s'en sortir sans et en cas de pépin, seule la console série permet d'intervenir quel que soit l'ampleur des dégâts.

Il vous faudra :

  • Un ordinateur avec gnu screen ;
  • un tournevis cruciforme ;
  • des petits fils ou nappe d'électronique (ça se récupère aisément) ;
  • un petit fer à souder ;
  • un adaptateur USB<->RS232 sur du 3.3V, j'utilise par exemple le JTAG multifonction de chez GlobalScale en mode UART mais il y a largement moins cher.

On y va :

  1. Éjecter les disque du NAS si besoin,
  2. retirer les 4 tampons de caoutchouc sur la façade arrière,
  3. retirer les vis ainsi révélées,
  4. retirer la façade arrière
  5. faire glisser gentiment le circuit imprimé en dehors du boîtier
  6. repérer les trous permettant de connecter les fils du port série sur le circuit imprimé (cf photo) Les connecteurs sont dans l'ordre suivant:

    RX (espace) 3.3v Masse TX

  7. Souder une nappe ou des fils pour « sortir » les fils du circuit imprimé du NAS.

  8. Connectez-y votre adaptateur série à ces fils.

Connecteur série DNS-325, crédit: Jamie Lentin

Pour tester,

  • Connecter l'adaptateur USB à votre ordinateur ;
  • lancer : screen /dev/ttyUSB0 115200 ;
  • démarrer le NAS.

La séquence de boot du NAS devrait défiler à votre écran, c'est gagné :-)

u-boot

Nous allons remplacer u-boot, le chargeur de démarrage installé de base par Marvell (fabricant de la puce) par une version récente et non modifiée d'u-boot. Pourquoi ? La version initialement fournie :

  • comporte des limitations « de sécurité » pénibles
  • ne permet pas d'utiliser ubifs pour stocker des fichiers sur la NAND
  • ne permet pas d'utiliser un initramfs pour booter (indispensable pour démarrer sur un disque formatté en autre-chose qu'ext3 et pour avoir une console de secours en cas de pépin)
  • ne supporte pas de deviceTree, ce qui oblige à utiliser un noyau patché.

Ça vaut le coup non ? C'est parti !

Vous avez le choix entre récupérer une version déjà compilée de u-boot pour la bonne architecture ou la compiler vous-même. dans tous les cas, on considère qu'on obtient un fichier u-boot-dns-325.kwb.

  • Formatter une clef en ext2 et y copier u-boot-dns-325.kwb
  • Brancher la clef sur le NAS
  • démarrer le NAS (avec la console série branchée et GNU screen lancé)
  • appuyer sur espace puis 1 à l'instant ou s'affiche « Hit any key to stop autoboot »
  • charger l'image d'u-boot depuis la clef usb puis l'écrire sur la NAND du NAS et redémarrer ce dernier :
    Marvell>> usb reset ; ext2load usb 0:1 0x1000000 /u-boot-dns-325.kwb
    Marvell>> nand erase 0x000000 0xe0000
    Marvell>> nand write 0x1000000 0x000000 0xe0000
    Marvell>> reset
    

Le NAS redémarre, interrompre de nouveau le boot en appuyant sur n'importe quelle touche lorsque proposé, nous voici avec l'u-boot nouveau qui n'indique plus « Marvell>> » mais « => ».

Fixer l'adresse MAC de la carte réseau (présente sur l'étiquette de votre NAS, ou peut-être choisi arbitrairement) :

=> setenv ethaddr 00:50:43:xx:xx:xx

Formatter la mémoire interne en 3 partitions (une pour u-boot, une pour sa configuration et une en ubifs contenant le /boot :

=> setenv mtdparts'mtdparts=orion_nand:896k(u-boot),128k(u-boot-env),-(root)'

Sauvegarder

=> saveenv

Préparation des disques

On va préparer les disques sur une autre machine avant de les « transplanter » dans le NAS.

On branche donc les disques sur un ordinateur avec des câbles SATA. Supposons qu'ils apparaissent en tant que /dev/sdb et /dev/sdc.

On commence par les formater en btrfs :

mkfs.btrfs -f -d raid1 /dev/sdb /dev/sdc
mount /dev/sdb /mnt/nas

Création d'un rootfs

Le rootfs c'est simplement l'ensemble des fichiers présents sur le système de fichiers qui constituent notre distribution Debian installée.

Toujours depuis notre machine tierce :

debootstrap --verbose --foreign --arch=armel --variant=minbase\
    --include=module-init-tools,locales,udev,dialog,ifupdown,mtd-utils\
              procps,iproute,iputils-ping,isc-dhcp-client,nano,wget,netbase,\
              btrfs-tools,acl,openssh-server,avahi-daemon,python-minimal,\
              u-boot-tools\
     buster /mnt/nas http://http.debian.net/debian

Les quelques paquets sélectionnés dans include installent de quoi configurer le réseau, un éditeur de texte, un serveur SSH, et de quoi commander le NAS via ansible, libre à vous de personnaliser cette base.

Nous allons ensuite prendre le contrôle de cette installation basique depuis notre ordinateur via un chroot. Comme l'architecture de votre PC (x86, amd64…) et celle du NAS (ARM) sont probablement différentes, il y faut installer de quoi virtualiser :

apt install qemu-user-static
cp /usr/bin/qemu-arm-static /mnt/nas/usr/bin/
chroot rootfs

Une fois dans le chroot on finit l'installation initiale et on rentre dans le chroot :

./debootstrap/debootstrap --second-stage

On configure les dépôts :

cat > /etc/apt/sources.list <<EOF
# Mises-à-jour de sécurité

deb http://security.debian.org/ buster/updates main deb
deb http://http.debian.net/debian/ buster-updates main

EOF

On indique les modules nécessaires au démarrage

cat > /etc/modules <<EOF
# Load required modules
gpio-fan kirkwood_thermal btrfs
EOF

On configure le système pour que lorsque un nouveau noyau est installé via APT, le système prépare une version de ce dernier ainsi qu'une version de l'initramfs acceptées par u-boot (il ne restera plus qu'à copier ces derniers dans la NAND, cf plus loin).

cat > /etc/kernel/postinst.d/local-kirkwood <<EOF
#!/bin/sh
set -e # passing the kernel version is required
version="$1"
[ -z "${version}" ] && exit 0

cp /usr/lib/linux-image-${version}/kirkwood-dns325.dtb /boot/kirkwood-dns325.dtb

/usr/bin/mkimage -A arm -O linux -T kernel -C none -n uImage\
  -a 0x00008000 -e 0x00008000\
  -d /boot/vmlinuz-${version} /boot/uImage-${version}
cp /boot/uImage-${version} /boot/uImage

/usr/bin/mkimage -A arm -O linux -T ramdisk -C gzip -n uInitrd\
  -d /boot/initrd.img-${version} /boot/uInitrd-${version}
cp /boot/uInitrd-${version} /boot/uInitrd

echo "`date` copied new image ${version} to nand" \
  >> /var/log/local-kirkwood.log

 EOF

 chmod a+x /etc/kernel/postinst.d/local-kirkwood

On installe un noyau compatible avec le NAS :

apt install linux-image-kirkwood

On prépare /etc/fstab :

cat <<EOF > /etc/fstab
/dev/root       /              auto    defaults  0  0
#/dev/ubi0_0     /boot          ubifs   noauto,defaults  0  0
EOF

Notez la dernière ligne commentée : malgré le noauto, systemd essaye de monter le le /boot au démarrage, en même temps que les autres systèmes de fichiers (sous Jessie et suivantes, systemd échoue à le faire si tôt).

On ajoute donc la ligne suivante au /etc/rc.local (avant le exit 0) pour le monter à la fin du processus de démarrage :

grep -q ' /boot ' /etc/mtab || mount /dev/ubi0_0 /boot -t ubifs  -o rw

Et enfin, on configure les paramètres locaux et on définit un mot de passe root :

dpkg-reconfigure tzdata dpkg-reconfigure locales
pwconv
passwd

On peut ici sortir du chroot, nos disques sont prêts à être transplantés !, mais il faut les laisser branchés pour copier le /boot.

exit

Créer le /boot

Le /boot, à l'opposé du reste du système de fichiers sera sur la mémoire NAND du NAS formatée en ubifs et non sur les disques.

On commence par préparer une image ubifs qui contient l'intégralité de notre /boot

apt-get install mtd-utils

On crée l'image

/usr/sbin/mkfs.ubifs -v -r /mnt/nas/boot -m 2048 -e 129024 -c 1016 -x zlib \
                     -o ubifs.img

On la copie sur notre clef usb formatée en ext2, puis on branche la clef usb sur le NAS, puis depuis la console u-boot, on la flashe sur la nand :

=> nand erase.part root
=> ubi part root
=> ubi create rootfs
=> ext2load usb 0:2 0x1000000 /ubifs.img
Loading file "/ubifs.img" from usb device 0:1
60512256 bytes read

Des erreurs UHCI (contrôleur USB) peuvent apparaître à la commande ext2load, tenter de changer de périphérique de stockage utilisé au besoin (après avoir essayé une clef USB et une carte SD, j'ai fini par y parvenir avec un disque dur externe)

Notez bien le nombre d'octets écrits (ici 60512256) et convertissez-là en hexadécimal (ici ça donne 39B5800), on en a besoin pour la suite :

=> ubi write 0x1000000 rootfs 39B5800
=> ubifsmount ubi:rootfs
=> ubifsls

Cette dernière commande devrait si tout s'est bien passé, nous lister le contenu du /boot que l'on vient de copier (supposé être le même que dans notre /mnt/nas/boot).

On configure ensuite u-boot pour démarrer avec les 3 fichiers intéressants de notre /boot :

  • le noyau copié (uImage)
  • l'initramfs (uInitrd)
  • le deviceTree (kirkwood-dns325.dtb)

On indique également quelle sera la partition racine ; cette information sera passée à l'initramfs qui se chargera de monter la partition racine (présente sur les disque durs) puis de lancer le système proprement dit.

setenv load_ubifs 'ubi part root; ubifsmount ubi:rootfs; ubifsload 0x0900000 /kirkwood-dns325.dtb ; ubifsload 0x1000000 /uImage;  ubifsload 0x1200000 /uInitrd'
setenv console 'ttyS0,115200'
setenv optargs 'ubi.mtd=2'
setenv root_hdd 'root=/dev/sda rootflags=device=/dev/sda,device=/dev/sdb,defaults,noatime rootfstype=btrfs'
setenv bootcmd 'setenv bootargs console=${console} ${optargs} ${mtdparts} ${root_hdd} ; run load\_ubifs ; bootm 0x1000000 0x1200000 0x0900000'

Pour synthétiser ce qui se passera lors du boot :

  1. Mise sous tension
  2. le système charge u-boot depuis la première partition de la NAND
  3. u-boot monte la 3e partition de la NAND (ubifs)
  4. u-boot charge noyau, l'initramfs et le devicetree depuis cette partition ubifs
  5. u-boot démarre et passe la main au système en initramfs
  6. l'initramfs monte le système de fichiers btrfs présent sur les disques durs
  7. l'initramfs lance et passe la main au système présent sur ce système de fichiers.
  8. welcome home :-)

Pourquoi un initramfs ? u-boot ne sait pas gérer directement les systèmes btrfs. Cet initramfs permet en outre d'avoir un système de secours stocké en dehors des disques durs pour aller réparer les disques le jour où ceux-ci sont en panne ou absents du NAS.

Procédures

Quelques procédures testées pour pouvoir agir sans panique, lorsque tout ça sera un peu loin effacé dans les mémoires :

Un des deux disques est mort ou arraché (procédure à chaud)

Identifier le disque qui défaille (smartctl peut aider), nous considérerons ici qu'il s'agit de /dev/sdb, le retirer à chaud et le remplacer par un nouveau disque, on considère que ce dernier est reconnu comme /dev/sdz

# /!\ À tester (en tout cas les précédentes instructions ne fonctionnaient pas)
# Repérer le devid qui est marqué comme « missing » (ndlr: on a retiré le disque fautif)
btrfs filesystem show -d
[]

# Ça devrait mentionner le devid (un nombre) qui est « missing ». Par exemple 42
btrfs device replace 42 /dev/sdz

Un des deux disques est mort ou arraché (procédure à froid)

Réparation (à froid, depuis le système éteint)

On considère que le disque défaillant a été identifié (smartctl peut vous aider) et retiré du NAS.

  • Brancher le port série et lancer screen
  • Retirer le disque défaillant du NAS,
  • démarrer avec un seul disque, le boot va échouer, et on se retrouve sur la console de l'initramfs.

Inspectons :

btrfs filesystem show

Label: none uuid: d12685c2-8800-4270-8eee-cde102506e7f Total devices 2
FS bytes used 409.04MB devid 1 size 2.73TB used 2.03GB path /dev/sda
*** Some devices missing

Insérer (à chaud) le disque flambant neuf, il sera reconnu par exemple comme /dev/sdb

Montons le système de fichiers sans le deuxième disque (option degraded):

mount /dev/sda -odegraded /root

Remplaçons (ces commandes peuvent prendre un peu de temps) :

# NB: devrait fonctionner avec btrfs replace également
btrfs device add /dev/sdb /root
btrfs device delete missing /root

Vérifions :

(initramfs) btrfs fi sho Label: none uuid:
d12685c2-8800-4270-8eee-cde102506e7f Total devices 3 FS bytes used
409.12MB devid 3 size 1.82TB used 2.03GB path /dev/sdb devid 1 size
2.73TB used 2.05GB path /dev/sda *** Some devices missing

(initramfs) btrfs fi df /root Data, RAID1: total=1.00GB, used=369.41MB
Data: total=8.00MB, used=0.00 System, RAID1: total=32.00MB, used=16.00KB
System: total=4.00MB, used=0.00 Metadata, RAID1: total=1.00GB,
used=39.69MB Metadata: total=8.00MB, used=0.00 : total=16.00MB,
used=0.00

Et rebootons :-)

Booter temporairement le système « normalement »

(à éviter dans la mesure du possible)

Si vous n'avez pas de nouveau disque sous la main mais devez tout de même démarrer le système.

Btrfs refuse de booter si un des disques du RAID manque, à moins de lui spécifier une option, pour cela, il faut interrompre le boot (en connectant le port série et en pressant une touche), puis spécifier l'option degraded :

=> setenv optargs 'root=/dev/sda rootflags=device=/dev/sda,device=/dev/sdb,defaults,noatime,degraded rootfstype=btrfs'
=> boot

On ne fait volontairement pas de saveenv pour que le prochain boot n'inclue pas cette option.

Mise à jour du noyau

Il n'y a rien de particulier à faire : le /boot de notre système est sur la flash et le script update-initramfs est configuré de telle sorte qu'un nouveau noyau deviendra le noyau par défaut au démarrage.

Pensez simplement à vérifier qu'il n'y a pas trop de noyaux installés, la flash ne fait que 126MiO, la commande df -h permet de s'assurer qu'il reste suffisamment d'espace.

De même, il est recommandé d'éviter de tester des noyaux en permanence, une flash n'étant pas censée être écrite fréquemment.

Revenir à un ancien noyau

À chaud

Si le système est démarré, il suffit de régénérer la configuration du noyau que l'on souhaite booter.

Par exemple, pour revenir au noyau 3.16 :

dpkg-reconfigure linux-image-3.16.0-0.bpo.4-kirkwood

À froid (busybox)

Si le système ne démarre pas (ex: noyau défectueux), il faut connecter la console série et, depuis la busybox de l'initramfs monter la partition ubifs et écraser les fichiers de kernel et d'initramfs avec ceux du kernel souhaité (ici 3.16) :

ubiattach /dev/ubi_ctrl -m 2 # plugs /dev/mtd2 as /dev/ubi0
mount /dev/ubi0_0 /boot/
cd /boot
cp uInitrd-3.16.0-0.bpo.4-kirkwood uInitrd
cp uImage-3.16.0-0.bpo.4-kirkwood uImage
reboot

À froid (uboot)

Si le noyau ne démare pas du tout, vous n'aurez pas la busybox. Il reste quand même possible de démarrer sur un ancien noyau directement de puis uboot, par exemple pour démarrer sur le kernel 4.19.0-6-marvell :

=> ubifsload 0x1000000 /uImage-4.19.0-6-marvell ; ubifsload 0x1200000 /uInitrd-4.19.0-6-marvell ; bootm 0x1000000 0x1200000 0x0900000

Cela démarrera temporairement sur l'ancien noyau, appliquer ensuite la procédure à chaud.

Conclusion

Nous avons une vraie machine basse conso au stockage redondant sous Debian.

L'aspect logiciel NAS de la chose est en dehors du champ de cet article, mais pourrait inclure :

  • création de sous-volumes (btrfs)
  • gestion des snapshots (btrfs)
  • serveur samba
  • installation de services avec yunohost

Il est possible que la partie sous-volumes/snapshots fasse l'objet d'un article ultérieur...

Bonus

Gestion du ventilateur

wget https://github.com/lentinj/dns-nas-utils/raw/master/dns-nas-utils.deb
dpkg -i dns-nas-utils.deb

Lire/écrire les paramètres u-boot depuis le système

Une fois le NAS installé, pour éviter de devoir sortir le câble série à chaque petite modification dans les paramètres d'u-boot.

Lister les partitions:

# cat /proc/mtd
dev:    size   erasesize  name
mtd0: 000e0000 00020000 "u-boot"
mtd1: 00020000 00020000 "u-boot-env"
mtd2: 07f00000 00020000 "root"

Indiquer la partition de configuration u-boot d'après les paramètres précédents.

/etc/fw_env.config

# dev:                  Device offset   size            ereasesize
/dev/mtd1               0x00000         0x20000         0x20000

Lire :

fw_printenv

Mettre fooval dans la variable foo :

fw_setenv foo "fooval"

Notes de mise à niveau Debian

Les mises à niveau se font bien. Je n'ai jamais réinstallé Debian (historique du NAS : Wheezy, Jessie, Stretch, Buster).

Il y a quelques fois des petits points spécifiques à ce matériel qu'il convient de prendre en compte auxquels je me suis frotté. J'écris donc ces notes dans l'espoir de t'éviter de sortir le cable série pour un dépanage dans l'urgence.

Notes généralistes : passage d'une version de Debian à une autre

Attention à l'espace dans /boot ; celui-ci étant physiquement sur la NAND, sa capacité est limitée à 114Mio.

Conseil : ne garder que deux noyaux Linux (paquets linux-image-*) ; donc faire le ménage avant une mise à niveau pour n'en garder qu'un (l'actuel), la mise à niveau en installera un nouveau.

Mise à niveau Debian Buster

Lire aussi les notes de version officielles pour Debian Buster.

L'argument mtdparts passé par u-boot au noyau a été renommé en cmdlinepart.mtdparts (détails dans le bug #831352).

Si on ne s'adapte pas à cette modification, le /boot ne se monte plus au démarrage (on peut également constater que le périphérique /dev/ubi0 n'existe plus).

Pour éviter tout désagrément, juste après la mise à niveau mais avant de redémarrer, penser à faire :

fw_setenv bootcmd 'setenv bootargs console=${console} ${optargs} cmdlinepart.${mtdparts} ${root_hdd} ; run load_ubifs ; bootm 0x1000000 0x1200000 0x0900000'

Si tu as oublié de le faire avant de redémarrer, il faudra aller corriger ça à l'aide du câble série directement dans la console u-boot:

setenv bootcmd 'setenv bootargs console=${console} ${optargs} cmdlinepart.${mtdparts} ${root_hdd} ; run load_ubifs ; bootm 0x1000000 0x1200000 0x0900000'
saveenv
boot

En modif notable (mais optionelle), il y a le renommage des interfaces réseau… J'ai choisi de ne pas franchir le pas pour l'instant, attendant la prochaine Debian (là, ça sera obligatoire).

Mise à niveau Debian Stretch

Lire aussi les notes de version officielles pour Debian Stretch.

  • Le paquet pump (client DHCP) n'existe plus, donc pense à installer isc-dhcp-client en remplacement sous peine de perdre la connectivité IPv4 du NAS.

  • Les paquets linux-image-kirkwood* sont renommés/remplacés par linux-image-marvell, vérifier qu'un paquet linux-image-marvell est installé avant de rebooter.

Mise à niveau Debian Jessie

Lire aussi les notes de version officielles pour Debian Jessie.

Il y a un changement à faire dans le fichier /etc/fstab pour le montage du /boot.

Références