NiYaWe's Blog

Archiv

Arch Linux vom USB-Stick booten und in den RAM laden

14.10.2017

Ich brauche ein Linux mit verschiedenen Tools, das ich vom USB-Stick booten kann. Zusätzlich möchte ich die Tools eventuell gleichzeitig auf mehreren Rechnern sowohl mit als auch ohne UEFI verwenden.

Der Plan ist beim Booten mit zramctl ein komprimiertes Blockdevice im RAM anzulegen und auf dieses das System zu kopieren, sodass der USB-Stick anschließend entfernt werden kann.

Zuerst erstelle ich mir einen leeren Ordner und installiere in diesem mit pacstrap ein Archlinux. Dazu benötige ich folgende Pakete auf meinem Rechner: sudo pacman -Sy arch-install-scripts syslinux pigz exfat-utils dosfstools gptfdisk

mkdir stick
pacstrap -d stick base vim bash-completion pv 

Anschließend wechsle ich mit sudo arch-chroot stick in das neu installierte System und richte es fertig ein.

echo myhost > /etc/hostname
echo LANG=de_DE.UTF-8 > /etc/locale.conf
echo LC_COLLATE=C >> /etc/locale.conf
echo LANGUAGE=de_DE >> /etc/locale.conf
echo KEYMAP=de-latin1 > /etc/vconsole.conf
echo FONT=lat9w-16 >> /etc/vconsole.conf
ln -sf /usr/share/zoneinfo/Europe/Berlin /etc/localtime
vim /etc/locale.gen
locale-gen
....

Damit das System später in den RAM geladen wird, führe ich in der Initial Ramdisk ein Script aus. Dieses besteht aus zwei Teilen: Dem eigentlichen Script, welches beim Booten ausgeführt wird und einer Liste von Abhängigkeiten die bei der Generierung der Initial Ramdisk kopiert werden. Bei der Erstellung des Scripts habe ich mich an diesem AUR Paket orientiert.

Das eigentliche Script liegt in dem neu installierten System unter usr/lib/initcpio/hooks/tar-root:

#!/bin/ash

run_hook() {
	if [ "${tarroot}" ]; then
		fsck_root() {
			echo &>/dev/null
		}

		tardev=$(resolve_device "${tarroot%%:*}")
		echo "tardev: $tardev"
		mount -t ext4 "$tardev" /tar-dev
		tarfile=${tarroot##*:}
		echo "tarfile: $tarfile"

		if [ ! -b "/dev/zram0" ]; then
			modprobe zram num_devices=$(($(nproc)+2))
		fi;
		mem_size=$(free -m | awk '/Mem/ {print int($2)}');
		echo "mem_size: $mem_size"
		tarroot_device=$(zramctl -f -s $(($mem_size*2))M -a lzo -t $(nproc))
		echo "tarroot_device: $tarroot_device"

		mkfs.ext4 "$tarroot_device";
		echo "mounting $tarroot_device"
		mount "$tarroot_device" /tar-root;
		unset mem_size;

		pv "/tar-dev$tarfile" | bsdtar -xzf - -C /tar-root;
		umount /tar-dev;
		umount /tar-root;
		export tarroot_device;
		tarroot_mount() {
			mount "${tarroot_device}" "$1"
		}
		mount_handler=tarroot_mount

	fi;
}

Die Liste mit den Abhängigkeiten liegt unter usr/lib/initcpio/install/tar-root:

#!/bin/ash

build() {
  add_dir /tar-dev
  add_dir /tar-root
  add_binary zramctl
  add_binary free
  add_binary mkfs.ext4
  add_binary nproc
  add_binary pv
  add_binary bsdtar
  add_binary gzip
  add_runscript
}

help() {
  cat <<HELPEOF
This hoook loads a tar file into ram as rootfs
HELPEOF
}

Damit das Script beim Booten auch ausgeführt wird, muss es in der /etc/mkinitcpio.conf in der HOOKS liste stehen. Meine HOOKS-liste sieht wie folgt aus: HOOKS="base udev modconf block keyboard tar-root". Da das gepackte System später auf einer ext4-partition liegen wird Packe ich mit MODULES="ext4" noch das dafür benötigte Modul dazu. Anschließend erstelle ich mit mkinitcpio -p linux die neue Initial Ramdisk und verlasse anschließend das arch-chroot

Damit der USB-Stick sowohl mit als auch ohne UEFI funktioniert habe ich mich an diesem gist orientiert. Der USB-Stick wird in den Code-Schnipseln als /dev/sdX bezeichnet.

Auf dem USB-Stick sind am Ende drei Partitionen: * Eine 512MB große FAT32 EFI-Partition * Eine 1536MB große ext4 Partition für das tar-gepackte System * Der Rest mit einem Dateisystem der Wahl (hier exFAT) um Daten zu speichern

Für die Partitionierung verwende ich gdisk: sudo gdisk /dev/sdX. Zuerst erstelle ich mit o eine neue Partitionstabelle. Dann erstelle ich mit n die Partitionen. Bei der ersten gebe ich beim Last Sector +512M und beim Typ ef00 an, bei der zweiten +1536M und 8300 und bei der dritten nichts (default) und 0700. Anschließend wechsle ich mit x in den Expertenmodus, setze mit a für die erste Partition das legacy BIOS bootable flag, erstelle mit n den MBR und schreibe die Änderungen mit w auf den Stick.

Dann formatiere ich die Partitionen:

sudo mkfs.fat -F 32 /dev/sdX1
sudo mkfs.ext4 -O "^has_journal" /dev/sdX2
sudo mkfs.exfat -n LABEL /dev/sdX3

Als nächstes wird der Bootloader installiert. Zuerst mounte ich die EFI-Partition nach /mnt: sudo mount /dev/sdX1 /mnt. Für UEFI erstelle ich den Ordner /EFI/BOOT mkdir -p /mnt/EFI/BOOT und kopiere die syslinux-Dateien in diesen: cp -r /usr/lib/syslinux/efi64/* /mnt/EFI/BOOT/. Zusätzlich muss noch die syslinux.efi-Datei in BOOTX64.efi umbenannt werden: mv /mnt/EFI/BOOT/syslinux.efi /mnt/EFI/BOOT/BOOTX64.efi. Für den Legacy BIOS Support schreibe ich die /usr/lib/syslinux/bios/gptmbr.bin auf den Stick: sudo dd if=/usr/lib/syslinux/bios/gptmbr.bin of=/dev/sdX, führe sudo extlinux --install /mnt aus und kopiere die Syslinux-Dateien nach /syslinux:

mkdir /mnt/syslinux
cp -r /usr/lib/syslinux/bios/* /mnt/syslinux/

Für beide Boot-Methoden gibt es je eine syslinux.cfg, die sich nur durch die relativen Pfade zum Kernel und zur Initial Ramdisk unterscheiden. Zuerst finde ich mit sudo blkid die UUID der ext4-partition heraus:

/mnt/EFI/BOOT/syslinux.cfg:

DEFAULT arch
PROMPT 0        # Set to 1 if you always want to display the boot: prompt
TIMEOUT 50
UI menu.c32
MENU TITLE Arch Linux
MENU COLOR border       30;44   #40ffffff #a0000000 std
MENU COLOR title        1;36;44 #9033ccff #a0000000 std
MENU COLOR sel          7;37;40 #e0ffffff #20ffffff all
MENU COLOR unsel        37;44   #50ffffff #a0000000 std
MENU COLOR help         37;40   #c0ffffff #a0000000 std
MENU COLOR timeout_msg  37;40   #80ffffff #00000000 std
MENU COLOR timeout      1;37;40 #c0ffffff #00000000 std
MENU COLOR msg07        37;40   #90ffffff #a0000000 std
MENU COLOR tabmsg       31;40   #30ffffff #00000000 std

LABEL arch
    MENU LABEL Arch Linux
    LINUX ../../vmlinuz-linux
    APPEND tarroot=UUID=<UUID>:/root.tgz
    INITRD ../../initramfs-linux.img

LABEL hdt
        MENU LABEL HDT (Hardware Detection Tool)
        COM32 hdt.c32

LABEL reboot
        MENU LABEL Reboot
        COM32 reboot.c32

LABEL poweroff
        MENU LABEL Poweroff
        COM32 poweroff.c32

/mnt/syslinux/syslinux.cfg:

DEFAULT arch
PROMPT 0        # Set to 1 if you always want to display the boot: prompt
TIMEOUT 50
UI menu.c32
MENU TITLE Arch Linux
MENU COLOR border       30;44   #40ffffff #a0000000 std
MENU COLOR title        1;36;44 #9033ccff #a0000000 std
MENU COLOR sel          7;37;40 #e0ffffff #20ffffff all
MENU COLOR unsel        37;44   #50ffffff #a0000000 std
MENU COLOR help         37;40   #c0ffffff #a0000000 std
MENU COLOR timeout_msg  37;40   #80ffffff #00000000 std
MENU COLOR timeout      1;37;40 #c0ffffff #00000000 std
MENU COLOR msg07        37;40   #90ffffff #a0000000 std
MENU COLOR tabmsg       31;40   #30ffffff #00000000 std

LABEL arch
    MENU LABEL Arch Linux
    LINUX ../vmlinuz-linux
    APPEND tarroot=UUID=<UUID>:/root.tgz
    INITRD ../initramfs-linux.img

LABEL hdt
        MENU LABEL HDT (Hardware Detection Tool)
        COM32 hdt.c32

LABEL reboot
        MENU LABEL Reboot
        COM32 reboot.c32

LABEL poweroff
        MENU LABEL Poweroff
        COM32 poweroff.c32

Jetzt müssen nur noch Kernel, Initial Ramdisk und das Dateisystem (root.tgz) auf den Stick kopiert werden. Hierzu verwende ich ein Script, weshalb ich erst einmal die EFI-Partition wieder aushänge: sudo umount /mnt

Da ich zum Beispiel den Pacman-cache nicht in die Tar-Datei kopieren möchte, erstelle ich mir eine Blacklist: $HOME/stick-tar-exclude

./var/cache/pacman/pkg/*
./boot/*

Um die benötigten Dateien zu erstellen und später den Stick zu updaten nutze ich folgendes Script: $HOME/update-stick.sh

#!/bin/bash

echo "Mounting boot to /mnt..."
mount /dev/disk/by-uuid/<EFI UUID> /mnt
echo "Copying Kernel and initramfs..."
cp $HOME/stick/boot/vmlinuz-linux $HOME/stick/boot/initramfs-linux.img /mnt
echo "Unmounting boot..."
umount /mnt

echo "Mounting stick to /mnt..."
mount /dev/disk/by-uuid/<ext4 UUID> /mnt
echo "Packing rootfs..."
bsdtar cpf - --exclude-from=$HOME/stick-tar-exclude -C $HOME/stick/ . | pigz > /mnt/root.tgz
echo "Sync..."
sync
echo "Unmounting stick..."
umount /mnt
echo "done"

Abschließend führe ich es aus: sudo ./update-stick.sh

Nun habe ich einen USB-Stick, der beim booten das Root-Dateisystem in den RAM schreibt und anschließend gefahrlos abgezogen werden kann.

Um das System upzudaten chroote ich in den Ordner und update das System normal mit pacman. Anschließend nutze ich das Script um die Änderungen auf den Stick zu schreiben:

sudo arch-chroot stick
pacman -Syu
exit
sudo ./update-sitck.sh
Impressum, Datenschutz