Leserartikel Installation von Arch Linux mit ZFS-root auf x86 QNAP von 2010

scooter010

Commander
Registriert
Sep. 2014
Beiträge
2.532
Moin Forum!

Ich habe mich jetzt einige Stunden damit beschäftigt, wie ich meinem alten x86-QNAP wieder leben Einhauchen kann und bin im Netz darauf gestoßen, dass man auf die Geräte auch ein eigenes Betriebssystem installieren kann.
Ich habe es auch "fast" geschafft, ich habe noch ein kleines Problem bzgl. des Bootens und hoffe auch auf einen Tipp, um anschließend den Leserartikel abzurunden. Dieser Leserartikel richtet sich an etwas fortgeschrittene Linux-Benutzer. Ich werde nicht jeden Schritt im Kleinsten erklären/begründen.

Das Ziel
ist, das mein altes QNAP 559 Pro II (x86 Dual-Core Atom CPU mit 1 GB RAM und 500MB DOM) mit einem RAIDz2 mit 5 Stück 4TB-Platten bei meinen Eltern als ZFS-Receiver zu betreiben und dort wöchentlich meine Backups/Snapshots hin zu senden, einige 100 km von meinem Wohnort und dem Standort meines hemischen TrueNAS-Servers (auch ZFS) entfernt.

Detailziele
  • ZFS-Receive Target
  • Automatische DynDNS-Aktualisierung zur Erreichbarkeit des QNAP
  • Verschlüsseltes Backup (auch am Ziel verschlüsselt und kein Schlüssel "vor Ort")
  • Ansteuerung des QNAP-Displays (ohne besonderen Zweck)
  • Zeit/Triggergesteuertes Starten des QNAP mit Shutdown nach erfolgtem Backup
Problemstellungen
Das QNAP hat ein intern per USB angebundenen Disk On Module (DOM) Speicher mit rund 500 MB Fassungsvermögen. Das AMI-BIOS (nicht UEFI) kann scheinbar nur von USB-Medien (mit Master Boot Record (MBR)) booten, also dem DOM und normalen USB-Sticks.
Weiterhin möchte ich die Installation möglichst "headless" also ohne an das NAS angeschlossenen Monitor durchführen, da ich keinen VGA-Monitor mehr besitze und meine Frau nicht sehr begeistert ist, wenn auf dem Beamer die ganze Zeit ein schwarzes Bild mit viel weißem text angezeigt wird... WAF, ihr kennt es.
Wer auf die headless-Installation verzichten kann, kann Schritt 1 quasi überspringen und braucht nur einen Arch Install-Stick.

ZFS hat die Eigenschaft, dass man snapshots sehr bequem mittels
Bash:
zfs snapshotname send | ssh qnap zfs receive
(Syntax ist falsch, aber das Prinzip ist veranschaulicht) übertragen kann. Es werden nur die geänderten Daten übertragen UND der Empfänger erhält die Daten nur in verschlüsselter Form. Damit beziehe ich mich nicht auf die Verschlüsselung im SSH-Tunnel, sondern das Dataset auf dem Empfänger liegt dort verschlüsselt und wird zu keinem Zeitpunkt gemountet und der Key ist auch nicht auf dem Empfängersystem.

Vorgehen

Ich habe mich für ein Arch als Betriebssystem entschieden, weil es dazu so viele schöne Quellen wie dem Arch-Wiki gibt und gefühlt jeder schon mal alles für/in Arch gemacht hat. Weiterhin führe ich die Arbeiten von einem Linux Rechner aus durch. Ich weiß nicht, ob es für Wndows vergleichbare Tools zur Erstellung des Iso-Files gibt, aber die eigentliche Installation kann dann via SSH auch von Windows aus erfolgen.

Schritt 1: Erstellung des Installationsmedium
  1. Von einem beliebigen Mirror ein Arch-Iso herunterladen
  2. Auf dem eigenen Linux-System dosfstools, mtools und libisoburn (oder vergleichbare Tools) installieren
  3. Eine Leere Datei mit dem Namen meta-data erstellen
  4. Eine Datei user-data mit folgendem Inhalt erstellen
    Bash:
    #cloud-configusers:
      - name: root
        ssh_authorized_keys:
          - ssh-ed25519 {string hier rein}
  5. Folgende Befehle ausführen (leerer USB-Stick erforderlich)
    Bash:
    mkfs.fat -C -n CIDATA cloud-init.img 2048 # 2 MiB File "cloud-image.img" wird erstellt
    mcopy -i cloud-init.img user-data ::. # Datei wird in das Image kopiert
    mcopy -i cloud-init.img meta-data ::.
    # Die Image-Datei wird in das Arch-Image integriert
    xorriso -indev archlinux-version-x86_64.iso -outdev archlinux-version-x86_64-with-cidata.iso -append_partition 3 0x0c cloud-init.img -boot_image any replay
  6. Das erstellte Iso mit belibiegen Iso-Schreiber auf den USB-Stick schreiben
Schritt 2: Vom Stick booten
Das QNAP ist etwas eigen, wenn es darum geht, es zum Booten vom USB-Stick zu überreden. Vor allem, wenn man keinen Monitor dran hat.
  1. Eine USB-Tastatur und den eben erstellten USB-Stick an die Rückseite des NAS anschließen
  2. Das NAS einschalten und die F11-Taste gedrückt halten
  3. Das NAS wird erst etwa 2-4 mal piepen und dann hat es 2 mal für je ca. eine Sekunde gebrummt
  4. F11 ca. 2 Sekunden nach Ende des zweiten Brummens los lassen (kommt nicht sehr drauf an, nur nicht zu früh los lassen)
  5. Pfeil-nach-unten-Taste ein mal drücken und anschließen Enter. Gleichzeitig mit dem Enter erfolgt ein lauterer Beep.
  6. Nach rund 3 Minuten ist das NAS hoch gefahren. Man kann ja ping laufen lassen, sofenr man weiß, welche IP der DHCP-Server dem NAS geben wird (Schau in die Router-Oberfläche).
  7. 20-30 Sekunden, nachdem der ping antwortet ist auch der SSH-Daemon am Laufen
  8. Login via SSH und Pubkey (nur Pubkey möglich)
Schritt 3: Partitionierung/Formatierung
Das sind einige Schritte, ich Haue das in Code-Tags und schreibe ein paar Kommentare dazu:
Bash:
###################################
# Erstmal Überblick verschaffenv# #
###################################

lsblk -f # Dateisysteme anzeigen lassen
ls /dev/disk/by-id # Hier nochmal die Kontrolle
DOM="usb-_USB_DISK_MODULE*" # Definitin Suchmaske für DOM
DATA_DISKS=`find /dev/disk/by-id/ ! -name "*-part*" -a -name "ata*"` # Alle ata-Platten
BOOT_DISK=`find /dev/disk/by-id/ ! -name "*-part*" -a -name $DOM` # Der DOM

#################################
# Alle Daten/Strukturen löschen #
#################################

MDs=`find /dev/ -name "md*"`
for i in $MDs; do
    mdadm --manage --stop $i; # Ggf. Vorhande Soft-RAIDs auflösen
done
for i in $DATA_DISKS $BOOT_DISK; do
    sgdisk --zap-all ${i} ; # Alle ata-Platten und DOM löschen
done
partprobe # Sicherstellen, dass die Änderungen an den Platten auch wirklich durchgeführt wurden UND der Kernel das auch mitbekommen hat

##################################
# Neu Partitionieren/Formatieren #
##################################

curl -s https://raw.githubusercontent.com/eoli3n/archiso-zfs/master/init | bash # isoarch zum Umgang mit ZFS befähigen
modprobe zfs
parted --align optimal --script -- $BOOT_DISK mklabel msdos mkpart primary 2048s 100% set 1 boot on # replace x with correct device
mkfs.ext2 $(BOOT_DISK)-part1 # DOM-Device den Anforderungen des QNAP entsprechend formatieren als DOS-MBR

for disk in $DATA_DISKS ; do
    parted --align optimal --script -- \  # ata-Disks von 2048MiB bis 100% Partitionen erzeugen
        ${disk} mklabel gpt \
        mkpart data 2048MiB 100% #
done


#######
# ZFS #
#######

zpool create \ # RAIDz2 Pool aus allen ata-Disks erzeugen
    -o ashift=12 \
    -o autotrim=on \
    -R /mnt \
    -O acltype=posixacl \
    -O canmount=off \
    -O compression=zstd \
    -O dnodesize=auto \
    -O normalization=formD \
    -O relatime=on \
    -O xattr=sa \
    -O mountpoint=/ \
    rpool \
    raidz2 \
    $(for i in ${DATA_DISKS}; do
       printf "$i-part1 ";
      done)
     
# Swap-Dataset erzeugen
zfs create \
    -V 8G \
    -b $(getconf PAGESIZE) \
    -o compression=zle \
    -o logbias=throughput \
    -o sync=always \
    -o primarycache=metadata \
    -o secondarycache=none \
    -o com.sun:auto-snapshot=false \
    rpool/swap

mkswap -f /dev/zvol/rpool/swap

# unencrypted root-dataset
zfs create \
 -o canmount=on \
 -o mountpoint=/ \
 rpool/root

# Create Encryptionkey
dd if=/dev/urandom of=/root/zfs_enc_data.key bs=32 count=1 # Keyfile for ZFS

# Verschlüsseltes Dataset (wofür auch immer, aber haben ist besser als brauchen)
zfs create \
 -o canmount=off \
 -o mountpoint=none \
 -o encryption=aes-256-gcm \
 -o keylocation=file:///root/zfs_enc_data.key \
 -o keyformat=raw \
 rpool/enc_data

# home-dir-Dataset innerhalb des verschlüsselten Datasets
zfs create \
 -o canmount=on \
 -o mountpoint=/home \
 rpool/enc_data/home

# Mount DOM to /mnt/boot
mkdir /mnt/boot
mount $BOOT_DISK /mnt/boot

Schritt 4: Installation Linux in root

Das ist relativ straight-forward und basiert von den persönlichen Anforderungen. nano, tmux

Bash:
pacstrap /mnt base vim openssh rsync grub efibootmgr mkinitcpio python-pipenv

CompatibleVer=$(pacman -Si zfs-linux | grep 'Depends On' | sed "s|.*linux=||" | awk '{ print $1 }')
pacstrap -U /mnt https://archive.archlinux.org/packages/l/linux/linux-${CompatibleVer}-x86_64.pkg.tar.zst

pacstrap /mnt zfs-linux zfs-utils

pacstrap /mnt linux-firmware intel-ucode amd-ucode
 
genfstab -U /mnt >> /mnt/etc/fstab
echo /dev/zvol/rpool/swap none swap defaults 0 0 >> /mnt/etc/fstab

Schritt 5: Konfiguration der Installation

Bash:
# mkinitcpio so konfigurieren, dass es auch zfs kann
mv /mnt/etc/mkinitcpio.conf /mnt/etc/mkinitcpio.conf.original
tee /mnt/etc/mkinitcpio.conf <<EOF
HOOKS=(base udev autodetect modconf block keyboard zfs filesystems)
EOF

# Das ArchZFS-Repo im installierten Arch unter /mnt hinzufügren
curl -L https://archzfs.com/archzfs.gpg |  pacman-key -a - --gpgdir /mnt/etc/pacman.d/gnupg
pacman-key --lsign-key --gpgdir /mnt/etc/pacman.d/gnupg $(curl -L https://git.io/JsfVS)
curl -L https://git.io/Jsfw2 > /mnt/etc/pacman.d/mirrorlist-archzfs

tee -a /mnt/etc/pacman.conf <<- 'EOF'

#[archzfs-testing]
#Include = /etc/pacman.d/mirrorlist-archzfs

[archzfs]
Include = /etc/pacman.d/mirrorlist-archzfs
EOF


# Diverse Dienste so konfigurieren, das sie automatisch starten:
systemctl enable zfs-import-scan.service zfs-mount zfs-import.target zfs-zed zfs.target --root=/mnt
systemctl enable systemd-networkd --root=/mnt
systemctl enable sshd --root=/mnt
systemctl enable systemd-resolved --root=/mnt
hwclock --systohc
systemctl enable systemd-timesyncd --root=/mnt

# Hier kopieren wir unseren ssh-root-key in die Installation
cp /root/.ssh/authorized_keys /mnt/root/.ssh/authorized_keys
# Der Key zum Entschlüsseln den verschlüsselten Datasets (noch nicht die optimale Lösung, aber ein Anfang)
cp /root/zfs_enc_data.key /mnt/root/

#################
# Standard-Kram #
#################
# Chrooting
history -w /mnt/home/sys-install-pre-chroot.txt
arch-chroot /mnt /usr/bin/env DOM="$DOM" bash

ln -sf /usr/share/zoneinfo/Europe/Berlin /etc/localtime
date

echo 'de_DE.UTF-8 UTF-8' >> /etc/locale.gen
locale-gen
echo 'LANG=de_DE.UTF-8' > /etc/locale.conf
echo 'KEYMAP=de-latin1' > /etc/vconsole.conf
echo 'QnapBackupTarget' > /etc/hostname
passwd
# TODO Add User for Backup
# TODO: Disable root-Login, etc., hardening-stuff

vim /etc/ssh/sshd_config # set without #
    # Port XXXXX # Ich empfehle, für einen Server im Netz trotz PubKey-Auth einen High-Port zu konfigurieren
    # PasswordAuthentication no # Ist klar
swapon -av

# Cache initialisieren und initrd erstellen
rm -f /etc/zfs/zpool.cache
touch /etc/zfs/zpool.cache
chmod a-w /etc/zfs/zpool.cache
chattr +i /etc/zfs/zpool.cache

mkinitcpio -P

###########################
# Grub-Workaround für ZFS #
###########################
echo 'export ZPOOL_VDEV_NAME_PATH=YES' >> /etc/profile.d/zpool_vdev_name_path.sh
source /etc/profile.d/zpool_vdev_name_path.sh

# GRUB fails to detect rpool name, hard code as "rpool"
sed -i "s|rpool=.*|rpool=rpool|"  /etc/grub.d/10_linux

echo GRUB_CMDLINE_LINUX=\"zfs_import_dir=/dev/disk/by-id/\" >> /etc/default/grub

# Und hier installieren/konfigurieren wir Grub auf dem DOM-Speicher
grub-install --target=i386-pc `find /dev/disk/by-id/ ! -name "*-part*" -a -name $DOM`
grub-mkconfig -o /boot/grub/grub.cfg


<Ctrl>+D  # Leave chroot


umount -Rl /mnt
zpool export -a
reboot now

Schritt 6: DynDNS-update
coming soon


Bitte um Hilfe!

So, das war es zu meiner Anleitung. Leider führt sie dazu, dass ich beim Booten eine Kernel-Panic (init gestorben) erhalte, weil der rpool/root Name nicht eindeutig sei und ich mittels der ID einbinden solle. Wenn ich live boote, kann ich den rpool aber problemlos importieren. Nur wenn ich im live-system rekursiv importieren möchte, kommt die selbe Fehlermeldung.
Dafür habe ich noch keine Lösung gefunden und hoffe auf die Schwarmintelligenz, damit ich das adaptieren, Testen und in diese Anleitung aufnehmen kann. Daher fehlt auch noch das DynDNS-Thema als auch die Ansteuerung des Displays, für die ich jeweils bereits Ansätze/Code habe.


Quellen: Ganz viel Arch-Wiki, viel Probieren (und gefühlt noch öfter scheitern) und https://openzfs.github.io/openzfs-docs/Getting Started/Arch Linux/Arch Linux Root on ZFS.html
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: polyphase und thuering
Naja, du setzt den Mount Point =/ zweimal, einmal auf rpool selber und dann auf rpool/Root. Das klingt zumindest suspekt.

Plus zfs bevorzugt explizit ganze Platten. Der Aufwand mit den Partitionen ist unnötig.
 
  • Gefällt mir
Reaktionen: madmax2010 und scooter010
Danke für den Hinweis mit den ganzen Platten. Zu dem Zeitpunkt war ich mir noch unsicher, ob ich swap als Dataset oder als Softraid5 ausführe.

Das mit dem doppeltem Mountpoint könnte sehr gut sein. Braucht der zpool überhaupt einen Mountpoint oder kann ich die Angabe für den pool einfach weg lassen?
 
Kommt drauf an(tm). Standard ist /<poolname>.
Mountpoint dann, wenn’s was anderes sein soll. Für / braucht man offensichtlich einen. Muß dann aber auf die Baum Struktur achten, da ja ein zfs dataset irgendwo sich befinden kann und aber oberhalb / nix ist.
 
Das war es jedenfalls nicht. Vielleicht wäre es noch zu einem Problem geworden, aber selbst das erneute durchgehen der Anleitung mit mountpount=none bei der Erstellung des pools führte zu der Ausgabe, dass die Poolbezeichnung rpool nicht eindeutig sei.

Ich gehe davon aus, dass es irgendwelche ZFS Verwaltungsinformationen geschafft haben, das sgdisk --zap-all zur "überleben" und in irgendeiner Backup-Verwaltungstabelle nun zwei Einträge für rpool vorhanden sind.

Ich mache, ohne geladenes zfs-Kernel-Modul, nun ein dd if=/dev/zero of=/dev/sdX bs=64M auf die Platten, dann sollte wirklich alles weg sein und ich kann nochmal von vorne beginnen. Dauert bei 4TB etwas...
 
Zurück
Oben