Prepare the disk

This is the first step that writes to the target machine. Up to now everything lived in RAM and a power cycle undid it; this step destroys whatever was on the disk you picked.

What it builds

A single GPT disk with two partitions:

Partition Size Format Mounted at Holds
1 1 GiB FAT32 /boot The bootloader, kernel and initramfs
2 rest btrfs / etc. Everything else, optionally encrypted

The same two partitions serve both architectures, for different reasons. On an x86_64 PC the first is the EFI System Partition the firmware reads. On a Raspberry Pi 5 it is the firmware partition, where the Pi’s bootloader expects config.txt, the kernel and the device trees; the Pi ignores partition type codes and simply uses the first FAT partition. A gibibyte is far more than one kernel needs, and that is the point: boot partitions that are too small are a well-worn regret.

Everything else goes on btrfs, split into subvolumes. A subvolume is an independently mountable tree inside one filesystem: they all draw from the same free space, so there is no sizing decision to regret, but each can be snapshotted, mounted or rolled back on its own.

Subvolume Mounted at Why it is separate
@ / The system itself – the thing you roll back
@home /home Your data, which must survive a rollback of the system
@snapshots /.snapshots Where snapshots are kept; cannot live inside @
@log /var/log Logs of what went wrong should outlive the rollback
@pkg /var/cache/pacman/pkg A re-downloadable cache, not worth snapshotting
@swap /swap A swapfile cannot sit on a subvolume that gets snapshotted
@nix /nix The Nix store, if you install the desktop: big, and Nix’s own

Every subvolume except @swap is mounted noatime,compress=zstd:1. noatime stops a write on every read, which is pure write amplification on a workstation. zstd at level 1 is fast enough to be effectively free, while higher levels cost noticeably more CPU for a few percent more space. @swap is mounted without compression, because a swapfile must be a plain, uncompressed, un-copy-on-write extent.

Run it

Run this on the live system:
setup disk

It reads the host file and shows you what it is about to destroy before it destroys it:

================ Review =================
Host:         myhost
Disk:         /dev/disk/by-id/nvme-XP1000F256G_2213E5A1B2C3
              (nvme0n1 238.5G XP1000F256G)
Currently:    2 partition(s): nvme0n1p1 vfat ESP; nvme0n1p2 btrfs arch;
Will create:  1G ESP (vfat) + rest btrfs, no encryption
Subvolumes:   @ @home @snapshots @log @pkg @swap @nix
Swap:         8g swapfile
=========================================

This ERASES /dev/nvme0n1. Type the hostname to continue: myhost

The Currently: line is there so that you notice when the disk you picked is not the one you thought it was – on a machine you are operating blind, over SSH, from another room, it is worth being slower than feels necessary. Typing the hostname rather than y is deliberate friction, and --yes does not answer it. To script it, pass --confirm-wipe myhost.

Before it gets this far, setup disk refuses to run at all if the machine booted in legacy BIOS mode, if any partition on the disk is mounted or in use as swap, or if the disk is the stick itself.

Then it runs, echoing each command as it goes:

(stdout)
+ sgdisk --zap-all /dev/nvme0n1
+ sgdisk --clear --new=1:0:+1G --typecode=1:ef00 --change-name=1:ESP --new=2:0:0 --typecode=2:8300 --change-name=2:root /dev/nvme0n1
+ partprobe /dev/nvme0n1
+ udevadm settle
+ wipefs --all --quiet /dev/nvme0n1p1 /dev/nvme0n1p2
+ mkfs.fat -F32 -n ESP /dev/nvme0n1p1
+ mkfs.btrfs --quiet -L arch /dev/nvme0n1p2
+ mount /dev/nvme0n1p2 /mnt
+ btrfs subvolume create /mnt/@
  ... six more subvolumes, then umount /mnt and the eight mounts
+ btrfs filesystem mkswapfile --size 8g --uuid clear /mnt/swap/swapfile
+ swapon /mnt/swap/swapfile

✓ disk       8 mounts under /mnt, 8g swap active
Next: setup install

Reading it across:

  • sgdisk --zap-all destroys the old GPT and any leftover MBR. --new=1:0:+1G makes partition 1 a gibibyte from the first aligned sector, --new=2:0:0 gives partition 2 the rest, and the type codes mark them as an EFI System Partition (ef00) and a Linux filesystem (8300).
  • wipefs is the step most install guides leave out. The new partitions start exactly where the old ones did, so on a reinstall the old btrfs signature is still sitting at the start of partition 2, and mkfs.btrfs refuses to format over it.
  • The filesystem labels (ESP, arch) are a convenience: they make lsblk readable.
  • The subvolumes are created at the top level of the filesystem, which is then unmounted and never used again except as their container. @ is mounted first, because the mount points for the others are directories inside it.
  • The ESP is mounted fmask=0077,dmask=0077, readable by root only, and genfstab carries that into the fstab. FAT has no permissions of its own, and with the default masks systemd-boot’s random seed is world-readable, which bootctl reports at every boot as “a security hole”.
  • The swap is turned on now, not just created, for two reasons: on a low-memory machine it is what keeps the install from running out, and the fstab is generated from what is active, so a swapfile that is not on would be silently left out of it.
Page size on the Raspberry Pi

On the Pi, mkswapfile reports page size 16384 rather than 4096, because linux-rpi-16k is built with 16 KiB pages. That is expected. It does leave one trap: a swapfile records the page size it was made with, and a kernel with a different page size refuses it. If you ever move this disk to a generic 4 KiB-page arm64 kernel, swapon fails with Invalid argument; delete the swapfile and make a new one.

With encryption

If the host file says ENCRYPT=yes, two commands join the list before mkfs.btrfs:

(stdout)
+ cryptsetup luksFormat --type luks2 --batch-mode --verify-passphrase /dev/nvme0n1p2
+ cryptsetup open /dev/nvme0n1p2 cryptroot

The first asks you to set and confirm the passphrase; the second asks for it again to unlock the container, which makes /dev/mapper/cryptroot appear, and everything after that formats and mounts the mapper device instead of the partition. LUKS2’s defaults are good ones – aes-xts-plain64, and argon2id as the key derivation function, which is memory-hard and therefore expensive to attack with GPUs – so setup does not choose any of them for you.

The passphrase cannot be recovered

There is no backdoor and no reset. If you forget it, the data is gone – that is the entire purpose. Put it in a password manager before you reboot, not after.

Running it again

If /mnt already holds the layout setup disk would build, it says so and touches nothing:

(stdout)
/mnt already has the layout for myhost. Nothing to do.

If the layout exists on the disk but is not mounted – the live system rebooted, or you are coming back to finish a half-done install – --mount puts it back without formatting, unlocking LUKS first if the host file says the disk is encrypted:

Run this on the live system:
setup disk --mount

That is also the rescue path for a machine that will not boot: boot the stick, run setup host --from ... and setup disk --mount, and arch-chroot /mnt puts you inside it.

// settings
theme:
fx: