arch-zbm/README.md

10 KiB

arch-zbm

Helper script to install Arch Linux with ZFSBootMenu from within a running Arch Linux live CD ISO image

Prep

We expect minimal prep on your end. Please make sure that before execution the following conditions are met.

  • Arch Linux live CD ISO image sees exactly one partition with partition type code BF00 ("Solaris root")
  • Arch Linux live CD ISO image sees exactly one partition with partition type code EF00 ("EFI system partition")
  • No ZFS zpool exists

The script will create a single ZFS zpool zpool on the BF00 partition with dataset child zpool/root which itself has one child zpool/root/archlinux, that's where Arch Linux gets installed. Parallel to zpool/root it'll create zpool/data with a zpool/data/home child dataset that gets mounted at /home.

The script will use the EF00 partition to install a ZFSBootMenu EFI executable if efibootmgr says that no such ZFSBootMenu entry exists. If ZFSBootMenu gets added to the EFI partition it'll become primary boot option.

How to run this?

  • Boot an Arch Linux live CD ISO image
  • Run:
    export SCRIPT_URL='https://quico.space/quico-os-setup/arch-zbm/raw/branch/main/setup.sh'
    curl -s "${SCRIPT_URL}" | bash
    
    During execution the script will call itself when it changes into its chroot, that's why we export SCRIPT_URL. Feel free to update "${SCRIPT_URL}" with whatever branch or revision you want to use from quico.space/quico-os-setup/arch-zbm. Typically .../branch/main/setup.sh as shown above is what you want.

Steps

The scripts takes the following installation steps.

  1. Install ZFS tools and kernel module with github.com/eoli3n/archiso-zfs
  2. Create one encrypted ZFS zpool on top of BF00 partition, password password
  3. Create dataset for Arch Linux and /home
  4. Install Arch Linux into pool
  5. Add ZFSBootMenu to EF00 partition if it doesn't exist already
  6. Exit into Arch Linux live CD ISO image shell for you to reboot and frolick

Flavor choices

We make the following opinionated flavor choices. Feel free to change them to your liking.

  • Arch Linux locale is set to en_US.UTF-8
  • Keymap is de-latin1
    • Consult /etc/vconsole.conf
    • Change zfs set org.zfsbootmenu:commandline=...
  • No X.Org Server, Wayland compositors or other GUI elements get installed
  • Timezone is Etc/UTC
    • Check timedatectl set-timezone <tzdata-zone>

Post-run manual steps

After installation you're going to want to at least touch these points in your new Arch Linux install:

  • Package manager hook: pacman does not have a hook to do ZFS snapshots
  • Hostname: Installation chose a pseudo-randomly generated 8-character string with pwgen
    • Check hostnamectl set-hostname <hostname>
  • Unprivileged user accounts: The OS was installed with root and unprivileged build users
  • Passwords
    • ZFS: The password for all datasets underneath zpool is password.
    • Local root account: The local root account's password is password.
  • Arch User Repository (AUR) helper: We installed paru as our AUR helper, we installed from GitHub via makepkg -si.

ZFS setup explained

The ZFS pool and dataset setup that makes this tick, explained in plain English.

  1. Create zpool with options:
    1. -R /mnt (aka -o cachefile=none -o altroot=/mnt). The pool is never cached, i.e. it's considered temporary. All pool and dataset mount paths have /mnt prepended. From man zpoolprops:

      This can be used when examining an unknown pool where the mount points cannot be trusted, or in an alternate boot environment, where the typical paths are not valid. altroot is not a persistent property. It is valid only while the system is up.

    2. -O canmount=off: Note the capital -O which makes this a file system property, not a pool property. File system cannot be mounted, and is ignored by zfs mount -a. This property is not inherited.
    3. -O mountpoint=none: What it says on the tin, the pool has no mountpoint configured.
    4. -O encryption=on: Makes this our encryptionroot and passes the encryption setting to all child datasets. Selecting encryption=on when creating a dataset indicates that the default encryption suite will be selected, which is currently aes-256-gcm.
    5. -O keylocation=file://...: This property is only set for encrypted datasets which are encryption roots. Controls where the user's encryption key will be loaded from by default for commands such as zfs load-key.
    6. -O keyformat=passphrase: Controls what format the user's encryption key will be provided as. Passphrases must be between 8 and 512 bytes long.
  2. At this time the newly created zpool is not mounted anywhere. Next we create the "root" dataset, that's an arbitary term for the parent dataset of all boot environments. Boot environments in your case may be for example different operating systems all of which live on separate datasets underneath the root.
    1. -o mountpoint=none: Same as above, the root dataset has - just like the pool - no mountpoint configured.
    2. zfs set org.zfsbootmenu:commandline=...: Set a common kernel command line for all boot environment such as "ro quiet".
  3. Neither the root dataset nor the pool are mounted at this time. We now create one boot environment dataset where we want to install Arch Linux.
    1. -o mountpoint=/: Our Arch Linux dataset will be mounted at /.
    2. -o canmount=noauto: When set to noauto, a dataset can only be mounted and unmounted explicitly. The dataset is not mounted automatically when the dataset is created or imported, nor is it mounted by the zfs mount -a command or unmounted by the zfs unmount -a command.
    3. We then zpool set bootfs="zpool/root/archlinux" zpool: ZFSBootMenu uses the bootfs property to identify suitable boot environments. If only one dataset has it - as is the case here - it'll be booted by default with a 10-second countdown allowing manual interaction in ZFSBootMenu.
    4. We explicitly mount the boot environment. Since the entire pool is still subject to our initial -R /mnt during creation a zfs mount zpool/root/archlinux will mount the Arch Linux dataset not into / but instead into /mnt.
  4. We also create a data dataset that - at least for now - we use to store only our /home data.
    1. For zpool/data:
      1. -o mountpoint=/: We use the mountpoint property here only for inheritance.
      2. -o canmount=off: The zpool/data dataset itself cannot actually be mounted.
    2. For a zpool/data/home child dataset:
      1. We do not specify any properties. Since canmount cannot be inherited the parent's canmount=off does not apply, it instead defaults to canmount=on. The parent's mountpoint=/ property on the other hand is inherited so for a home child dataset it conveniently equals mountpoint=/home.
      2. In effect this zpool/data/home dataset is subject to zfs mount -a and will happily automount into /home.
  5. We export the zpool once, we then reimport it by scanning only inside /dev/disk/by-id, again setting -R /mnt as we did during pool creation a moment ago and we do not mount any file systems.
  6. We zfs load-key <encryptionroot> which will load the key from keylocation after which the keystatus property for <encryptionroot> and all child datasets will change from unavailable to available.
  7. We mount our Arch Linux boot environment dataset. It automatically get prepended with -R /mnt since that's how we imported the pool.
  8. We zfs mount -a which automounts zpool/data/home into /home, which again gets auto-prepended by /mnt.
  9. We lastly mount our EFI partition into /mnt/efi.
  10. We instruct ZFS to save its pool configuration via zpool set cachefile=/etc/zfs/zpool.cache zpool.

The complete ZFS structure now exists and is mounted at /mnt ready for any pacstrap, debootstrap, dnf --installroot or other bootstrapping action.

Development

Conventional commits

This project uses Conventional Commits for its commit messages.

Commit types

Commit types besides fix and feat are:

  • build: Project structure, directory layout, build instructions for roll-out
  • refactor: Keeping functionality while streamlining or otherwise improving function flow
  • test: Working on test coverage
  • docs: Documentation for project or components

Commit scopes

The following scopes are known for this project. A Conventional Commits commit message may optionally use one of the following scopes or none:

  • iso: Changing Arch Linux ISO CD
  • zbm: Adjusting ZFSBootMenu's behavior
  • zfs: A change to how ZFS interacts with the system, either a pool or a dataset
  • os: Getting an perating system set up to correctly work in a ZFS boot environment
  • meta: Affects the project's repo layout, readme content, file names etc.

Credits

Most of what's here was shamelessly copied and slightly adapted for personal use from Jonathan Kirszling at GitHub.

Thanks to: