Skip to content

Installation

Ghaf installer ISO images can be downloaded or built locally as described below.

Currently, only x86_64-linux systems are supported.

To build, for example, the Ghaf installer for Intel laptops (debug variant), run:

Terminal window
nix build .#intel-laptop-debug-installer

Plug in a USB stick or external drive large enough to hold the image (16 GB recommended), then follow the instructions for your platform.

  1. Identify the device with lsblk or fdisk -l. Use the device name (e.g. sdb) in place of sdX in the steps below.

  2. Unmount all partitions on the device:

    Terminal window
    sudo umount /dev/sdX*
  3. Write the image to the drive, replacing path_to_ghaf.iso with the actual path:

    Terminal window
    sudo dd if=path_to_ghaf.iso of=/dev/sdX bs=32M status=progress; sync
  1. Identify the device with diskutil list. Use the device name (e.g. disk1) in place of diskX in the steps below.

  2. Unmount all partitions on the device:

    Terminal window
    diskutil unmountDisk diskX
  3. Write the image to the drive, replacing path_to_ghaf.iso with the actual path:

    Terminal window
    sudo dd if=path_to_ghaf.iso of=/dev/diskX
  4. Eject the disk once complete:

    Terminal window
    diskutil eject /dev/diskX
  1. Download and open USBwriter.
  2. Set the Ghaf ISO as Source.
  3. Set the USB drive as Target.
  4. Click Write.
  5. When finished, safely eject the USB drive.

Insert the installation media into the target device and boot from it. The interactive installer starts automatically and guides you through the process.

The installer supports the following actions:

  • Install Ghaf — write the image to a target device, with optional full-disk encryption and Secure Boot enrollment.
  • Wipe disk only — erase a target device without installing.
  • Shutdown / Reboot — power options available from the main menu.

If the installer does not start automatically, launch it manually:

Terminal window
sudo ghaf-installer-tui

A non-interactive installer is also available for scripted use:

Terminal window
sudo ghaf-installer

Instead of writing a ~7 GB ISO to a USB stick, the target can boot the installer over PXE and fetch the disk image over HTTP at install time. The boot artefacts are around 650 MB, contain no disk image, and are target-independent — one kernel and initrd serve the whole fleet, and only the image URL differs per target.

  • The target must expose a network boot entry in its firmware, and the firmware must have a UEFI driver for the NIC you intend to boot from. On Lenovo machines this appears as PXE BOOT in the boot menu. Check from a running system with efibootmgr -v.
  • The build host and the target must be on the same layer-2 network. PXE uses broadcast; it does not cross a router.
  • The build host needs root (DHCP/PXE require ports 67, 69 and 4011).
Terminal window
nix build .#intel-laptop-debug-netboot-installer -o result-netboot
nix build .#intel-laptop-debug -o result

Always start with --dry-run. It validates the arguments, prints the exact kernel command line and boot plan, and starts nothing:

Terminal window
nix develop --command ghaf-netboot \
--interface eth0 --mac aa:bb:cc:dd:ee:ff \
--netboot result-netboot --image result \
--dry-run

When the plan looks right, drop --dry-run:

Terminal window
sudo ghaf-netboot \
--interface eth0 --mac aa:bb:cc:dd:ee:ff \
--netboot result-netboot --image result \
--open-firewall --exit-after-serve

Key options:

  • --mac is an allowlist and is mandatory. Machines not on it are answered with a 404, which PXE treats as “ignore me”. On a shared network this is the only thing preventing an unrelated machine from booting your installer.
  • --open-firewall opens ports 67, 69, 4011 and the HTTP port for the duration of the run, then closes them again on exit. Without it, a host that filters inbound traffic drops every PXE request before the server sees it — the server logs nothing and the target times out.
  • --exit-after-serve stops the server once the image has been transferred in full. It is on by default with --install-target, because an unattended install reboots itself when it finishes: if the target has network boot ahead of its disk, a server left running catches that reboot and reinstalls in a loop. --no-exit-after-serve opts out and warns. For interactive runs it is off, since nothing fetches the image and the server needs to stay up while an operator works through the TUI.
  • --force-interface is required when the interface facing the target also carries the default route, which is the normal case on a shared lab network.
  • --ipxe selects the iPXE binary served to 64-bit UEFI clients. The default is the snponly.efi Ghaf builds itself and is almost always what you want; see The iPXE binary below before changing it.

Adding --install-target writes to the given device with no prompts:

Terminal window
sudo ghaf-netboot ... --install-target /dev/nvme0n1

When the write finishes, the installer prints a ten-second notice and reboots into the system it just wrote — there is no installation medium to remove and nobody there to remove it. Boot with ghaf.install_noreboot to stay in the installer instead, which is what you want when diagnosing an install that went wrong, since otherwise the evidence reboots away.

The server runs Pixiecore in ProxyDHCP mode, so it never assigns addresses and cannot disturb an existing DHCP server. Pixiecore asks a small local HTTP API what to do with each machine that tries to boot; the API answers only for allowlisted MACs. The installer then fetches ghaf-image.raw.zst and ghaf-image.bmap over HTTP and writes the image with bmaptool, which verifies a SHA-256 per range as it copies. A missing .bmap is fatal rather than falling back to an unverified copy.

The target’s firmware does not boot the kernel directly. It first loads iPXE over TFTP, and that iPXE then fetches the boot script, kernel and initrd over HTTP. Which iPXE build gets served matters more than it sounds, because the two obvious choices each fail in a different way:

iPXE build NIC driver Failure
Pixiecore’s built-in iPXE’s own native drivers Broadcast DHCP replies never arrive on some adapters. iPXE retries ten times, reboots, and repeats forever.
A stock snp.efi / snponly.efi firmware SNP Identifies as user-class iPXE, which Pixiecore reads as plain UEFI firmware, so it re-serves the iPXE binary in an endless chainload loop.

Ghaf therefore builds its own: bin-x86_64-efi/snponly.efi, which drives the NIC through the firmware’s SNP driver, with an embedded script that sets user-class pixiecore. That user-class string is the entire test Pixiecore applies to decide it has already chainloaded a client, so setting it is what avoids the loop while keeping the MAC allowlist, the 404, and the assembled kernel command line on their normal path.

This is the default, and on a machine whose firmware enumerates the NIC at all it should need no thought. Two escape hatches exist:

  • --ipxe builtin falls back to Pixiecore’s own iPXE. Worth trying if the firmware’s SNP does not cover the adapter, at the cost of the broadcast-DHCP problem above.
  • --ipxe <FILE> serves a binary you supply. It must carry an embedded script that sets user-class pixiecore, or it will chainload-loop.

On a non-x86_64 build host the custom binary is not built, and the server falls back to Pixiecore’s built-in iPXE. Such a host can still serve an x86_64 target; it just inherits the first row of the table.