Booting Archiso over a network

PXE and booting the Archlinux ISO over a network

About

This is a small article covering how to boot the archiso over the network achieved with isc-dhcp-server, tftp-hpa and Nginx.

ISOs

Booting ISOs

ISOs can be booted in any number of ways; You can keep it traditional and burn them to a CD or block-level-copy them to a USB drive and boot that. Or most commonly, select the file for a virtual cd rom drive for a virtual machine to boot from as if it were real.

Upon booting any of these methods the system will notice the bootable ISO content like a normal installed operating system and give the option to boot from it. In the case of Archlinux this is a live environment.

How the maintainers of any given major distro decide to handle booting their installation medium is up to them and often unique. In the case of this Archlinux ISO I have here: archlinux-2024.09.01-x86_64.iso booting it enters systemd-boot and the default menu option has these arguments: archisobasedir=arch archisosearchuuid=2024-09-01-12-40-01-00 and the ISO's partition UUID happens to be that. RedHat has its own special way and flags for doing so and I cover some of these in my autoProvisioner project which has a few .ipxe examples for Archlinux, CentOS 7 and Rocky 9.2 showing each their own unique boot argument implementations and differences in process.

Referencing the installation media by a partition UUID has the benefit of not needing to care how the ISO was presented to the system, be it a cdrom, usb stick or even NVMe for some reason - the partition UUID will always appear in the same place regardless after the kernel reads information about the partition.

This can be seen any time by running sudo losetup -Pf archlinux-2024.09.01-x86_64.iso which sets the ISO up on a loop device causing the system to enumerate it like an attached drive. This causes /dev/disk/by-uuid/2024-09-01-12-40-01-00 to appear (This loop can be removed again with losetup -d loop0).

In the case of this bootable ISO we're hitting the systemd-boot bootloader which has been preconfigured to boot the rest of the environment.

Booting Archiso

Most Linux ISOs including the Archiso support traditional BIOS booting as a hybrid ISO however we'll stick to the context of UEFI.

Mounting the archiso to a directory reveals close to 110 files. Many related to Syslinux (A common bootloader for ISOs), a memtest.efi (which is one of the boot options) and a top level directory named arch/ which contains yet another bootloader and initramfs inside plus arch/x86_64/airootfs.sfs which is the in-memory rootfs we end up loading to with a command prompt once the Archiso finishes booting.

The ISO has EFI/BOOT/BOOTx64.EFI which is the default path a UEFI system searches for on EFI partitions and these are automatically booted from in device order when no other boot options are configured. It's common to place a copy of the intended bootloader at this path for this case. Windows systems create BOOTx64.EFI as a copy of bootmgfw.efi, the intended target.

This chain of events starts at the top with the UEFI system seeing the ISO's bootable partition and starting up EFI/BOOT/BOOTx64.EFI which is systemd-boot (Previously gummiboot). These three boot options can be seen in loader/entries/ on the mounted ISO.

loader/entries/01-archiso-x86_64-linux.conf shows that it intends to boot arch/boot/x86_64/vmlinuz-linux using initrd arch/boot/x86_64/initramfs-linux.img with options archisobasedir=arch archisosearchuuid=2024-09-01-12-40-01-00. This won't be able to help us as we will not have any form of the ISO available for it to reference the content from.

Thanks to the wiki network-booting this distribution is well documented and officially supported. We still need archisobasedir=arch which is the subdirectory of the ISO with all of the content but we can specify archiso_http_srv with a http address to fetch the bootable content from.

There is also cms_verify which seems to be repsonsible for validating airootfs.sfs which comes with a sha512 and a .sig signature file the live environment can validate against.

PXE booting and limitations

PXE booting has been around for decades and any typical UEFI system you run into will support it. When a computer tries to PXE boot your DHCP server can respond with hints of an address and file path for the computer to request over TFTP and boot.

The ROM of PCIe Network cards is often flashed with some PXE-capable program which appears as another boot option. If the provided ROM isn't up to scratch it's common to flash network cards with iPXE. The gold standard.

We could jump right in and modify our dhcp server to load up the above vmlinuz-linux file from some TFTP server but there's no way to specify an initrd file or kernel arguments for the kernel we're about to boot. Because of this limitation we must seek the help of iPXE who can act as our network bootloader for this case - allowing us to boot the kernel with an initramfs image and arguments we want included.

Getting started

For this we will need these packages from pacman: dhcp git nginx tftp-hpa

DHCPD

Start with the DHCP server. Create or modify an existing /etc/dhcpd.conf and add filename "ipxe.efi"; as one of the options and restart the service.

Without specifying additional configuration such as option tftp-server-address it will be assumed that the TFTP server is running on the router.

A very lightweight example dhcpd.conf might look like this:

authoritative;
default-lease-time 1800;
max-lease-time     3600;

subnet 10.99.0.0 netmask 255.255.255.224 {
  option domain-name-servers      1.1.1.1;
  option routers                  10.99.0.1;
  option tftp-server-address      10.99.0.1;
  range dynamic-bootp             10.99.0.10 10.99.0.100;
  filename                       "/ipxe.efi";
}

Keep in mind dhcpd will not issue IPs for a subnet until that network is visible on an active interface. So the above small configuration example is harmless until an IP in that range exists on an interface.

TFTP

Start and enable tftp-hpa with: systemctl enable --now tftpd`. This TFTP server operates in /srv/tftp by default.

Create a directory to mount your ISO likemkdir -p /srv/tftp/archlinux and mount the latest ISO to that directory: mount ~/archlinux-2024.09.01-x86_64.iso /srv/tftp/archlinux/. This exposes the ISO contents for the client to reference.

NGINX

Create an example vhost which points to /srv/tftp with autoindexing enabled:

# vi /etc/nginx/conf.d/tftp.conf

server {
  listen       80;
  http2        on;
  server_name  tftpServerHostnameHere;
  autoindex    on;
  root         /srv/tftp;

  access_log /var/log/nginx/access.theHostname.log;
  error_log  /var/log/nginx/error.theHostname.log;
}

Making sure to set server_name to the network hostname or IP address of your TFTP server.

Start and enable Nginx with: systemctl enable --now nginx` and try browsing to the server_name to confirm the directory contents are loading and displaying correctly.

iPXE

Everything is set and we need one final piece of the puzzle. An iPXE image which knows how to boot the Archiso.

git clone https://github.com/ipxe/ipxe and cd ipxe/src

In here create a file like script.ipxe with the below content:

#!ipxe
set baseUrl http://tftpServerHostnameHere
dhcp || shell
initrd ${baseUrl}/archlinux/arch/boot/x86_64/initramfs-linux.img || shell
kernel ${baseUrl}/archlinux/arch/boot/x86_64/vmlinuz-linux ip=dhcp archiso_http_srv=${baseUrl}/archlinux/ archisobasedir=arch cms_verify=y script=${baseUrl}/script.cfg || shell
boot || shell

These iPXE commands prepare iPXE to boot the Archiso environment just like the bootloader in the ISO would do. The || shell case on the end of each line specifies that if any of these commands should fail, fall back to the iPXE shell for debugging. || reboot is also valid but in this testing case, it would reboot too quickly on error for us to read an error message.

This ipxe script includes special arguments for reaching out to a http server for the required content instead of the cdrom partition uuid. It also includes script=, which can be used to reference a shell script after the live environment starts up. You can create /srv/tftp/script.cfg with shell commands inside to initiate say, an automated unattended installation with archinstall or any number of custom commands you may wish to prep the live environment with. This could include some form of .sig pgp validation of the script itself or reaching out to a secure trusted source for a PXE client to follow.

Finally build the iPXE efi binary with our script embedded inside. Then move it to the tftp/webroot directory: make bin-x86_64-efi/ipxe.efi -j$(nproc) EMBED=script.ipxe cp bin-x86_64-efi/ipxe.efi /srv/tftp/

PXE Booting

At this point with our TFTP and Webserver running we can boot the PXE client. If everything has been followed correctly it should grab our custom iPXE efi binary from the TFTP server.

iPXE will then follow our embedded script, fetching the kernel image, initramfs and setting our desired kernel arguments. The initramfs environment has been designed for the Archlinux ISO and will find, verify and unpack airootfs.sfs before taking you to a terminal prompt.

If you create /srv/tftp/script.cfg and leave the script= argument in from the above iPXE script example you can have it automatically append your ssh key to /root/.ssh/authorized_keys and start the sshd service for remote access to the live environment for installing by hand, or troubleshooting problems with the host using SSH.