Netbooting a customized Archiso via Preboot Execution Environment (PXE)

Arch Linux is arguably one of the easiest distributions to customize, there’s even a wiki entry on how to build your own customized iso, which could be helpful if you, for example, wanted to include rescue tools for your specific environment. But what if you’ve got a network with a bunch of computers, and wanted them all to boot up using that image - running around with countless usb drives or cds isn’t an option.

That’s what you’d typically use PXE (Preboot Execution Environment) for. It’s included in nearly every computer’s bios and allows it to boot to a full live operating system from network without even needing storage on the client.

You can easily find information about how to netboot the official arch linux image via PXE. Information is more rare if we’re talking about booting custom-built arch linux images with your own modifications from network.

I’ve tested a bit and this is my solution on how to do this. If you want to learn more about how this works, see the “Resources” section at the bottom of this article.

Step 1: Setup a suitable network

Booting via PXE has specific requirements regarding the setup of your network. Specifically, it’s required that there’s a dhcp-server running. In other environments, it’s also required that said dhcp server gives instructions to the pxe clients - in this case, because we’re using something called ProxyDHCP, the dhcp server can also be a dummy one. But it has to be there. In my test lab, I’m using Hyper-V. You can obviously also do this on another type of virtual or physical network. I’ll include my Hyper-V commands for reference.

Step 1.1: Create Hyper-V-VM-Network

In my case, this is a virtual Hyper-V-NatNetwork as described here.

New-VMSwitch -SwitchName "NatNetwork" -SwitchType Internal
New-NetIPAddress -IPAddress -PrefixLength 24 -InterfaceIndex 24 # Gateway IP, IP of the Hypervisor
New-NetNat -Name NatNetworkNat -InternalIPInterfaceAddressPrefix # Range of IPs Nat is allowed for (should match the network specified in New-NetIPAddress)

Step 1.2: Create virtual machines

Step 1.2.1: Install a DHCP server

You could also install a DHCP server on the Arch VM we’ll create later for building - in this case, for it to be easy, I’ll install OPNSense. This could also be any old FRITZ!Box - our pxe-provider “Pixiecore” doesn’t require any further DHCP configurations other than the standard ones, especially no pxe-specific ones. It just requires a DHCP server to be there.

Be sure to:

Step 1.2.2: Install an Arch Linux VM

This will later serve as our system to build our target Arch Linux “ISO” (i’ll explain later why I put the quotation marks there) to be booted by our PXE client. I’ll use archinstall for this.

Be sure to:

Step 1.2.3: Create a VM to boot from PXE

In the Hyper-V-Manger, one can just create a new VM, assign a hard drive in the process (yes, i know…) and near the end of the creation wizard select “Install an operating system from network”. That suffices. If you boot it now, it’ll give you an error as no PXE server is running.

Step 2: Create an arch “iso”

In the arch linux VM, you can use the mkarchiso command to create all the required files. This not only includes a .iso file, but the work directory stays on your hard drive after building, which contains the files for us to be booting off of. Make your customizations using a self-created profile or create an iso off the default baseline profile via:

sudo mkarchiso -v -o out/ /usr/share/archiso/configs/baseline/

File structure

You’ll find the following file structure (truncated):

├── out
│   └── archlinux-baseline-2023.01.28-x86_64.iso
├── work
│   ├── [...]
│   ├── BOOTIA32.EFI
│   ├── BOOTx64.EFI
│   ├── build._build_buildmode_iso
│   ├── build_date
│   ├── efiboot.img
│   ├── grub
│   │   └── grub.cfg
│   ├── grub-embed.cfg
│   ├── iso
│   │   ├── arch
│   │   │   ├── boot
│   │   │   │   └── x86_64
│   │   │   │       ├── initramfs-linux.img
│   │   │   │       └── vmlinuz-linux
│   │   │   ├── grubenv
│   │   │   ├── pkglist.x86_64.txt
│   │   │   ├── version
│   │   │   └── x86_64
│   │   │       ├── airootfs.erofs
│   │   │       └── airootfs.sha512
│   │   ├── EFI
│   │   │   └── BOOT
│   │   │       ├── BOOTIA32.EFI
│   │   │       ├── BOOTx64.EFI
│   │   │       └── grub.cfg
│   │   └── syslinux
│   │       ├── cat.c32
│   │       ├── [...]
│   ├── iso._build_iso_image
│   ├── iso.pacman.conf
│   └── x86_64
│       └── airootfs
│           ├── bin -> usr/bin
│           ├── boot
│           ├── [...]
│           └── version

The files of our interest are located in work/iso/arch: This kind of resembles the structure found on https://mirrors.edge.kernel.org/archlinux/iso/latest/arch. That’ll get interesting later on.

Step 3: Allow the PXE client to boot off the newly-created arch linux

We’ve got to make the costumized root file system (contained in airootfs.erofs) available via HTTP.

Step 3.1: Serve the newly created boot files via HTTP

I used nginx for that, but you can use whatever webserver you like to use. It’s important to use another port than 80 though, if you’re running this http server on the same vm as pixiecore. Pixiecore will want to use 80.

Here is a tree of my /var/www directory:

└── arch
    ├── boot
    │   └── x86_64
    │       ├── initramfs-linux.img
    │       └── vmlinuz-linux
    ├── grubenv
    ├── pkglist.x86_64.txt
    ├── version
    └── x86_64
        ├── airootfs.erofs
        └── airootfs.sha512

Step 3.2: Run Pixiecore

We’re almost there! Now, you’ll need to run the following command:

sudo pixiecore quick arch --dhcp-no-bind --cmdline "kernel initrd=initrd0 archisobasedir=arch archiso_http_srv=http://<builder's ip>:<builder's nginx's port>/ ip=dhcp verify=n net.ifnames=0 systemd.firstboot=off"

If you now start the PXE-client-VM created in Step 1.2.3, you’ll be able to watch arch linux boot up to a console!

The interesting part of this command is its --cmdline. Actually, it’s pretty default - most of it is already there if we boot up the official arch linux iso using sudo pixiecode quick arch --dhcp-no-bind. The only aspects I modified were the following:

Step 3.3 (optional): Download the offical arch linux iPXE-kernel

To be able to run this setup without any dependency on internet access, you need to download the initramfs-linux.img and vmlinuz-linux files from https://mirrors.edge.kernel.org/archlinux/iso/latest/arch/boot/x86_64/. You can then switch from the pixiecore command I told you before to this one:

sudo pixiecore boot vmlinuz-linux initramfs-linux.img --dhcp-no-bind --cmdline "kernel archisobasedir=arch archiso_http_srv=http://<builder's ip>:<builder's nginx's port>/ ip=dhcp verify=n net.ifnames=0 systemd.firstboot=off"

This replaces the online copies used in the quick command with our offline copies.

Wrap up

That’s it! You can now run your customized arch linux environment on as many network clients you want via PXE. You could use this for example to provide a consistent and known-to-you rescue system, in case something goes wrong and you need to recover.

For comments, please use this thread on Hacker News.