Debian-Live Netzbootsystem

Aus Knowledgebase
Version vom 12. Mai 2024, 14:33 Uhr von PoC (Diskussion | Beiträge) (→‎TFTP-Server: Hinweis verschoben.)
(Unterschied) ← Nächstältere Version | Aktuelle Version (Unterschied) | Nächstjüngere Version → (Unterschied)
Zur Navigation springen Zur Suche springen

Die Möglichkeit, hinreichend aktuelle Maschinen (allgemein, also VMs oder auch Blech) über das LAN zu booten ist eine feine Sache. Bare Metal Restore, oder auch die Offline-Korrektur von Fehlern wie z. B. zu kleinen Partitionen, etc. wird damit zum Kinderspiel.

Das Debian-Live-Projekt hat für Wiederherstellungszwecke eine Live-Boot CD-Serie bereitgestellt. Allerdings haben diese Images ein paar (teils subjektive) Nachteile:

  • Gewohnte Konfigurationstools wurden durch modernere Varianten mit eigener Syntax abgelöst; ein Paradebeispiel ist das Fehlen von ifconfig.
  • Hardware welche proprietäre Firmware-Binärdateien benötigt ist — vor allem wenn es um Netzwerkkarten geht — außen vor; ein Booten vom Netz geht spätestens nach dem Starten des Kernels schief.
  • Viele weitere Pakete mit fast immer benötigten Tools fehlen und müssen immer und immer wieder per Hand nachinstalliert werden.

Der Artikel beschreibt die notwendigen Schritte, dieses Netzbootsystem auf einem existierenden Debian Linux 12 (Bookworm) aufzusetzen. Ob die zu startende Maschine ein älteres BIOS-basiertes System ist oder ein neueres EFI-System, ist egal: Beides wird dynamisch anhand von DHCP-Optionen passend konfiguriert. Dabei kann zum Bootzeitpunkt ausgewählt werden, ob ein 32- oder ein 64-Bit-System gestartet werden soll.

Es ist selbstverständlich möglich, weitere via PXE zu startende Betriebssysteme einzubinden. Wichtig ist zu beachten, dass die via TFTP bereitgestellten Dateien in den entsprechenden Unterverzeichnissen für die verschiedenen Architekturen zu hinterlegen sind (bios, efi32, efi64).

Hinweis: Das von mir benutzte System ist traditionell mit SysV-init ausgerüstet. Systemd wird nicht benutzt. Die hier aufgeführten Schritte sind ansonsten so generisch wie möglich gehalten.

Benötigte Komponenten

  • DHCP-Server für die Zuweisung grundlegender Netzwerkparameter,
  • TFTP-Server für den initialen Download von Bootloader, Menü und anschließend Kernel und Initrd,
  • NFS-Server für den Zugriff auf das komprimierte Root-Dateisystem,
  • Scripte zur Erstellung des übers Netz geladenen Root-Dateisystems.

Installation:

apt-get install openbsd-inetd isc-dhcp-server tftp-hpa tftpd-hpa nfs-kernel-server pxelinux syslinux-common syslinux-efi debootstrap squashfs-tools

DHCP-Konfiguration

Hinweis: Im lokalen Netzwerksegment soll nur ein DHCP-Server aktiv sein. Sind es mehrere, ist es mehr oder weniger Zufall, welches Antwortpaket der anfragende Client zuerst bearbeitet. Sollte also schon ein DHCP-Server existieren, muss die gezeigte Konfiguration in die bestehende Konfiguration eingebaut werden. Durch die Benutzung von DHCP-Klassen ("class") ist dies im Regelfall aber sehr einfach möglich.

Der erste Start des noch nicht unkonfigurierten DHCP-Servers geht erfahrungsgemäß schief, dies kann ignoriert werden.

Weil hier nur IPv4 berücksichtigt wird, muss in /etc/default/isc-dhcp-server die Zeile INTERFACESv6 gelöscht werden. Ebenso muss in INTERFACESv4 das für DHCP zu verwendenden Netzwerkinterface eingetragen werden. Beispiel:

INTERFACESv4="eth0"

Dies ist ein Beispiel für eine DHCP-Konfiguration! Insbesondere der group-Abschnitt muss auf die lokalen Gegebenheiten angepasst werden!

authoritative;
ddns-updates off;
default-lease-time 3600;
log-facility daemon;
max-lease-time 86400;
min-lease-time 300;
min-secs 0;

option architecture-type code 93 = unsigned integer 16; # RFC4578

# Route different kinds of requesting clients to distinct PXE bootloaders.
class "pxeclients" {
    match if substring (option vendor-class-identifier, 0, 9) = "PXEClient";

    if option architecture-type = 00:00 {
        filename "/var/tftpboot/bios/lpxelinux.0";
    } elsif option architecture-type = 00:06 {
        filename "/var/tftpboot/efi32/syslinux.efi";
    } elsif option architecture-type = 00:07 {
        filename "/var/tftpboot/efi64/syslinux.efi";
    } elsif option architecture-type = 00:09 {
        filename "/var/tftpboot/efi64/syslinux.efi";
    }

    # This is the TFTP server's IP address.
    next-server 192.168.111.10;
}

# Ethernet
group {
    option broadcast-address 192.168.111.0;
    option domain-name "pocnet.net";
    option domain-name-servers 192.168.111.10;
    option ntp-servers 192.168.111.10;
    option routers 192.168.111.1;
    option subnet-mask 255.255.255.0;

    subnet 192.168.111.0 netmask 255.255.255.0 {
        range 192.168.111.120 192.168.111.126;
    }
}

Nun kann der DHCP-Server gestartet werden:

service isc-dhcp-server start

TFTP-Server

Die Konfiguration des Servers findet sich in /etc/default/tftpd-hpa. Dort müssen folgende Änderungen durchgeführt werden:

  • Ändern des Pfades /srv/tftp nach /var/tftpboot,[1]
  • Ändern der TFTP_OPTIONS:[2]
    • Löschen von --secure,
    • Hinzufügen von --permissive -v.

Nun kann der alte Pfad gelöscht werden und der neue angelegt:

rmdir /srv/tftp
mkdir /var/tftpboot

Als letztes sollte der TFTP-Server neu gestartet werden:

service tftpd-hpa restart

Inhalte für /var/tftpboot

Zuerst werden ein paar grundlegende Verzeichnisse für die benötigten Dateien angelegt:

cd /var/tftpboot
mkdir -p deb-live/amd64 deb-live/i686 bios efi32 efi64 pxelinux.cfg

Für die einzelnen Architekturen werden nun die benötigten Bootdateien über Symlinks bereit gestellt. Symlinks brauchen weniger Speicher und wenn das Original (wegen Aktualisierungen) ausgetauscht wird, braucht man nicht mehr manuell nachbessern.

# BIOS-Loaders
cd bios
ln -s ../deb-live .
ln -s ../pxelinux.cfg .
ln -s ../../../usr/lib/syslinux/modules/bios/ldlinux.c32 .
ln -s ../../../usr/lib/syslinux/modules/bios/libcom32.c32 .
ln -s ../../../usr/lib/syslinux/modules/bios/libutil.c32 .
ln -s ../../../usr/lib/syslinux/modules/bios/mboot.c32 .
ln -s ../../../usr/lib/syslinux/modules/bios/menu.c32 .
ln -s ../../../usr/lib/syslinux/modules/bios/pxechn.c32 .
ln -s ../../../usr/lib/syslinux/modules/bios/vesamenu.c32 .
ln -s ../../../usr/lib/PXELINUX/lpxelinux.0 .

# EFI32-Loaders
cd ../efi32
ln -s ../deb-live .
ln -s ../pxelinux.cfg .
ln -s ../../../usr/lib/syslinux/modules/efi32/ldlinux.e32 .
ln -s ../../../usr/lib/syslinux/modules/efi32/libcom32.c32 .
ln -s ../../../usr/lib/syslinux/modules/efi32/libutil.c32 .
ln -s ../../../usr/lib/syslinux/modules/efi32/mboot.c32 .
ln -s ../../../usr/lib/syslinux/modules/efi32/menu.c32 .
ln -s ../../../usr/lib/syslinux/modules/efi32/vesamenu.c32 .
ln -s ../../../usr/lib/SYSLINUX.EFI/efi32/syslinux.efi .

# EFI64-Loaders
cd ../efi64
ln -s ../deb-live .
ln -s ../pxelinux.cfg .
ln -s ../../../usr/lib/syslinux/modules/efi64/ldlinux.e64 .
ln -s ../../../usr/lib/syslinux/modules/efi64/libcom32.c32 .
ln -s ../../../usr/lib/syslinux/modules/efi64/libutil.c32 .
ln -s ../../../usr/lib/syslinux/modules/efi64/mboot.c32 .
ln -s ../../../usr/lib/syslinux/modules/efi64/menu.c32 .
ln -s ../../../usr/lib/syslinux/modules/efi64/vesamenu.c32 .
ln -s ../../../usr/lib/SYSLINUX.EFI/efi64/syslinux.efi .

Um die Pflege des PXE-Bootmenüs zu vereinfachen, wird eine "globale" Konfiguration in /var/tftpboot/pxelinux.cfg (der Standardname, mit dem die PXE-Loader nach der Konfiguration suchen) für alle Architekturen via Symlinks zugänglich gemacht.

Die eigentliche Konfiguration für das Bootmenü findet sich in der Datei /var/tftpboot/pxelinux.cfg/default:

menu title Boot menu
menu tabmsg Press ENTER to boot or TAB to edit a menu entry
prompt 0
timeout 600

label deblive64
    menu label Debian Live ^amd64
    kernel deb-live/amd64/vmlinuz
    append initrd=deb-live/amd64/initrd.img net.ifnames=0 biosdevname=0 ip=:::::eth0:dhcp boot=live netboot=nfs root=/dev/nfs nfsroot=192.168.1.10:/var/nfsboot/deb-live/amd64 live-media-path=/ consoleblank=0 nfsrootdebug

label deblive32
    menu label Debian Live ^i686
    kernel deb-live/i686/vmlinuz
    append initrd=deb-live/i686/initrd.img net.ifnames=0 biosdevname=0 ip=:::::eth0:dhcp boot=live netboot=nfs root=/dev/nfs nfsroot=192.168.1.10:/var/nfsboot/deb-live/amd64 live-media-path=/ consoleblank=0 nfsrootdebug

label bootlocal
    menu label Boot ^local disk
    menu default
    localboot 0

ui menu.c32
  • net.ifnames=0 biosdevname=0 stellt die ursprüngliche, aufsteigende Nummerierung vom Kernel wieder her, damit die Netzwerkschnittstelle überall wieder eth0heißt und diese wiederum in der ip=-Konfigurationszeile statisch referenziert werden kann.[3]
  • Das Zirkumflex (^) dient als Hervorhebung für einen Buchstaben im Menü. Dieser Buchstabe auf der Tastatur eingegeben aktiviert diesen Menüeintrag.

NFS-Server

In der Datei /etc/exports wird definiert, welche Hosts auf welche Verzeichnisse Zugriff haben:

/var/nfsboot    192.168.1.0/24(ro,no_root_squash,insecure,no_subtree_check)

Nun müssen die Verzeichnisse für das Rootdateisystem noch angelegt werden:

mkdir -p /var/nfsboot/deb-live/amd64 /var/nfsboot/deb-live/i686

Damit die Änderungen ziehen, einmal den (mangels gültiger Einträge in exports sowieso nicht gestarteten) NFS-Server starten:

service nfs-kernel-server start

Root-Dateisystem

Dies wird über eine "frische" Installation von Debian von Grund auf neu erzeugt:

  • Debootstrap,
  • Konfigurationsanpassungen,
  • Squashfs-Build.

Das folgende Script kann mit den Parametern amd64 oder i686 aufgerufen werden und erzeugt jeweils ein Squashfs in den zuvor angelegten Verzeichnissen in /var/nfsboot.

#!/bin/bash

# Must run as root.
if ! id |grep -Fq 'uid=0(root) gid=0(root) groups=0(root)'; then
    echo "Must run as root."
    exit 1
fi

# Must supply what to build.
if [ -z "${1}" ]; then
    echo "Give arch as argument: amd64 or i686."
    exit 0
fi

# Check what to build.
if [ "${1}" == "amd64" ]; then
    DST_ARCH="amd64"
elif [ "${1}" == "i686" ] || [ "${1}" == "i386" ]; then
    DST_ARCH="i686"
else
    echo "Give arch as argument: amd64 or i686."
    exit 0
fi

# Automatically clean up when we terminate.
trap '{ umount ${TMPMNT} && rmdir ${TMPMNT}; rm -f ${TMPIMG}; }' EXIT

# Terminate on error.
set -e


# Temporary image file and mount point to work within.
TMPIMG=$(mktemp --tmpdir=/var/tmp live-image.XXXXXXXXXX)
TMPMNT=$(mktemp -d --tmpdir=/var/tmp live-root.XXXXXXXXXX)


# Prepare image file and mount.
fallocate -v -l 4GiB "${TMPIMG}"
mke2fs -t ext4 -E lazy_itable_init=0 -O ^has_journal -i 16384 -L live-build -F "${TMPIMG}"
tune2fs -r0 -c0 -i12m -o '^acl,nobarrier' -e remount-ro -f "${TMPIMG}"
mount -o loop "${TMPIMG}" "${TMPMNT}"

# Install system.
if [ "${DST_ARCH}" == "amd64" ]; then
    ARCH_ARG="--arch=amd64"
    PKGLIST="console-setup, \
        dosfstools, \
        dump, \
        firmware-amd-graphics, \
        firmware-bnx2, \
        firmware-bnx2x, \
        firmware-linux-free, \
        firmware-misc-nonfree, \
        gdisk, \
        ifupdown, \
        isc-dhcp-client, \
        kbd, \
        keyboard-configuration, \
        linux-image-amd64, \
        live-boot, \
        locales, \
        lsscsi, \
        lvm2, \
        mdadm, \
        mt-st, \
        net-tools, \
        nfs-common, \
        openssh-client, \
        openssh-server, \
        orphan-sysvinit-scripts, \
        pciutils, \
        pv, \
        sg3-utils, \
        sysvinit-core"
elif [ "${DST_ARCH}" == "i686" ]; then
    ARCH_ARG="--arch=i386"
    PKGLIST="console-setup, \
        dump, \
        firmware-linux-free, \
        ifupdown, \
        isc-dhcp-client, \
        kbd, \
        keyboard-configuration, \
        linux-image-686-pae, \
        live-boot, \
        locales, \
        lsscsi, \
        lvm2, \
        mdadm, \
        mt-st, \
        net-tools, \
        nfs-common, \
        openssh-client, \
        openssh-server, \
        orphan-sysvinit-scripts, \
        pciutils, \
        pv, \
        sg3-utils, \
        sysvinit-core"
fi

# Parameter strings for debootstrap must not include blanks.
PKGLIST_CLEAN="$(echo "${PKGLIST}" |tr -d ' ')"
debootstrap "${ARCH_ARG}" --components=main,non-free,non-free-firmware,contrib \
    --include "${PKGLIST_CLEAN}" \
    --exclude nano,systemd-sysv,tasksel,tasksel-data \
    stable "${TMPMNT}" https://debian.inf.tu-dresden.de/debian/

# Prepare system.
echo "debian-live" > "${TMPMNT}"/etc/hostname
cat > "${TMPMNT}"/etc/default/keyboard <<-EOF
# Check /usr/share/doc/keyboard-configuration/README.Debian for
# documentation on what to do after having modified this file.

# The following variables describe your keyboard and can have the same
# values as the XkbModel, XkbLayout, XkbVariant and XkbOptions options
# in /etc/X11/xorg.conf.

XKBMODEL="pc105"
XKBLAYOUT="de"
XKBVARIANT=""
XKBOPTIONS=""

# If you don't want to use the XKB layout on the console, you can
# specify an alternative keymap.  Make sure it will be accessible
# before /usr is mounted.
# KMAP=/etc/console-setup/defkeymap.kmap.gz
BACKSPACE="guess"
EOF

# Clean apt lists to save space. They might be outdated anyway and need to be retransferred.
rm -f "${TMPMNT}"/var/lib/apt/lists/* 2>/dev/null || true

# Set default locale.
echo "LANG=en_US.UTF-8" > "${TMPMNT}"/etc/environment

# Add default password entry.
sed '/^root:/ d' "${TMPMNT}"/etc/shadow > "${TMPMNT}"/etc/shadow~
echo 'root::17852:0:99999:7:::' > "${TMPMNT}"/etc/shadow
cat "${TMPMNT}"/etc/shadow~ >> "${TMPMNT}"/etc/shadow

# Help with ssh-remote-logins.
echo "PermitRootLogin yes" >> "${TMPMNT}"/etc/ssh/sshd_config
echo "PermitEmptyPasswords yes" >> "${TMPMNT}"/etc/ssh/sshd_config

# Switch initrd to gzip.
sed -Ei 's/^COMPRESS=zstd$/COMPRESS=gzip/' "${TMPMNT}"/etc/initramfs-tools/initramfs.conf

# Prepare system to rebuild initramfs
mount -o bind /dev "${TMPMNT}"/dev
mount -o bind /dev/pts "${TMPMNT}"/dev/pts
mount -t proc none "${TMPMNT}"/proc
mount -t sysfs none "${TMPMNT}"/sys
echo "Please run these commands:"
# FIXME: How to automate this manual process?
echo ' echo "locales	locales/default_environment_locale	select	en_US.UTF-8" |debconf-set-selections'
echo ' echo "locales	locales/locales_to_be_generated	multiselect	en_US.UTF-8 UTF-8" |debconf-set-selections'
echo " dpkg-reconfigure locales"
echo " update-initramfs -u"
echo "then exit chroot."
chroot "${TMPMNT}"

# Script resumes after chroot exit.
umount "${TMPMNT}"/dev/pts "${TMPMNT}"/dev "${TMPMNT}"/proc "${TMPMNT}"/sys

# Copy Kernel and initramfs for TFTP
cp -v "${TMPMNT}"/vmlinuz "${TMPMNT}"/initrd.img /var/tftpboot/deb-live/"${DST_ARCH}"

# Compress into a file for NFS serving
mksquashfs "${TMPMNT}" /var/nfsboot/deb-live/"${DST_ARCH}"/filesystem.squashfs -noappend

# vim: tabstop=4 shiftwidth=4 autoindent expandtab
  • Die Paketliste in $PKGLIST ist darauf zugeschnitten, die für eine schnelle Wiederherstellung notwendigen Komponenten zu beinhalten.

Test

Wenn nichts falsch gemacht wurde, so bezieht der Client nun beim Netzboot eine IP-Adresse vom DHCP-Server und lädt die PXE-Dateien, um im Anschluss ein Menü anzuzeigen, was denn nun gestartet werden soll. Nach der Auswahl eines Punktes werden Kernel und Initrd per TFTP übermittelt und der Kernel gestartet. Per erneuter DHCP-Anfrage erhält der Kernel gültige Netzparameter und mountet nun das per NFS zugängliche Squashfs als root. Ab dann ist das Debian-Live-System aktiv.

In /var/log/daemon.log finden sich Meldungen vom TFTP-Server, was hilfreich bei der Fehlersuche sein kann.

Weblinks

Fußnoten

  1. Ist kein Muss, aber in dieser Anleitung wird dieser Pfad vorausgesetzt.
  2. Dies ist notwendig, damit die später angelegten Symlinks auch aufgelöst werden.
  3. Früher hat die Autokonfiguration bei mehreren vorhandenen Netzwerkschnittstellen nicht zuverlässig funktioniert, deswegen der gezeigte Workaround.