ZBook Strix Halo Debian installation

January 29, 2026

Introduction

I’ve just bought a laptop, having had my current machine for around a decade. I decided I wanted to get a machine built around AMDs Zen 5 architecture, partly because they seem to be very powerful, cores numerous, and to get access to the AVX-512 instruction set that Zen 5 makes available (usually these are only really available on higher end server processors).

In the end, I opted for an HP ZBook G1a, built around AMD’s Strix Halo APUs. The machine is a complete monster, sporting 16 Zen 5 cores (32 hyperthreads), 128GB of unified memory and a 2TB disk. Completely excessive. It’s also well built, with a decent screen and sound.

I believe the machine is offered with Ubuntu linux somewhere in the world, so linux compatibility ought to be good, but mine came with Windows, which I promptly erased and replaced with a Debian installation. The Debian live CD (with KDE) left an immediately great impression (using Windows at work, I don’t understand why anybody puts up with it anymore…), being snappy and rendering beautifully.

Being quite fussy about system setup, this documents the manual bootstrap process for this systems Debian installation. This gives

Suspend works, wireless works, as does audio via pipewire. Turns out secure booting the debian kernel disables hibernation (part of kernel_lockdown), but I’d only want this once the laptop has been suspended and run down the attery almost completely; likely quite rare, and in that case, I can probably tolerate a fresh boot.

As an additional bonus, it makes use of systemd-creds to store secrets on the encrypted drive sealed against the TPM. This should make this secrets usable only from the machine itself, should the secrets be extracted from the disk somehow.

Initial bootstrap

Boot a live CD and open a terminal. I’m using Debian 13 (Trixie).

Disk setup

Prepare the disk by blasting it with random noise (makes it less clear where encrypted data resides).

As root (or by prepending sudo)

cryptsetup open --type plain --key-file /dev/urandom --sector-size 4096 /dev/nvme0n1 wipe
dd if=/dev/zero of=/dev/mapper/wipe bs=1M status=progress
cryptsetup close wipe

I’ve always done this, as a matter of course, but apparently this is pointless if you later enable trim because the SSD will just zero blocks on trim. I haven’t done this yet, but might stop doing this drive preparation in future.

Use fdisk on the device to create a new GPT partition table, and provision a 1G EFI boot partition, and another partition for the rest of the disk. I originally allowed 130GB for swap, before realising that a secure booted debian kernel can’t be hibernated. I retroactively removed it.

Create a LUKS (encrypted) container on the main disk

cryptsetup luksFormat --pbkdf=pbkdf2 /dev/nvme0n1p2
cryptsetup luksOpen /dev/nvme0n1p2 cry_root

The KDF needs specifying because GRUB can’t unlock the device if you use the default argon2i KDF. If you mess this up (as I did), and GRUB won’t boot from the disk despite entering the correct password, you can check the KDF and convert to pbkdf2 with

cryptsetup luksDump /dev/nvme0n1p2
cryptsetup luksConvertKey --hash sha256 --pbkdf pbkdf2 /dev/nvme0n1p2

Format the main filesystems

mkfs.fat -F32 /dev/nvme0n1p1
mkfs.btrfs -O block-group-tree -L root /dev/mapper/cry_root

block-group-tree is stable, but not yet a default, so is specified explicitly.

btrfs has lots of options to optimize disk usage (and which are selling points for using it, for example compression), but the wisdom contained in the Debian wiki is to avoid most of them for a stable and reliable system. Fortunately snapshots and subvolumes are stable, and I want to use them.

So we provision some subvolumes. Subvolumes are the units of snapshots. The root subvolume (created as part of creating the filesystem) needs to be mounted to create subvolumes. Rather than nesting subvolumes to mirror the mount structure, I’ve created them all under the root subvolume. They can be mounted to a different place.

mount -o noatime,nodiscard /dev/mapper/cry_root /mnt
btrfs subvolume create /mnt/@root
btrfs subvolume create /mnt/@home
btrfs subvolume create /mnt/@log
btrfs subvolume create /mnt/@cache
btrfs subvolume create /mnt/@snap
umount /mnt

# Setup the root filessystem
mount -o noatime,nodiscard,subvol=@root /dev/mapper/cry_root /mnt
mkdir /mnt/home
mkdir /mnt/var/log
mkdir /mnt/var/cache
mkdir /mnt/.snap
mount -o noatime,nodiscard,subvol=@home /dev/mapper/cry_root /mnt/home
mount -o noatime,nodiscard,subvol=@log /dev/mapper/cry_root /mnt/var/log
mount -o noatime,nodiscard,subvol=@cache /dev/mapper/cry_root /mnt/var/cache
mount -o noatime,nodiscard,subvol=@snap /dev/mapper/cry_root /mnt/.snap

mkdir -p /mnt/boot/efi
mount /dev/nvme0n1p1 /mnt/boot/efi

Bootstrap

Do the initial bootstrap

apt install debootstrap
debootstrap trixie /mnt

Chroot package installation

Setup the chroot and enter it

mount -t proc proc /mnt/proc
mount -t sysfs sys /mnt/sys
mount -B /dev /mnt/dev
mount -t devpts pts /mnt/dev/pts
chroot /bin/bash /mnt

Edit /etc/hostname and /etc/hosts.

Setup apt sources in /etc/apt/sources.list:

deb http://deb.debian.org/debian trixie main contrib non-free-firmware
deb-src http://deb.debian.org/debian trixie main contrib non-free-firmware

deb http://deb.debian.org/debian-security trixie-security main contrib non-free-firmware
deb-src http://deb.debian.org/debian-security trixie-security main contrib non-free-firmware

deb http://deb.debian.org/debian trixie-updates main contrib non-free-firmware
deb-src http://deb.debian.org/debian trixie-updates main contrib non-free-firmware

deb http://deb.debian.org/debian/ trixie-backports main contrib non-free-firmware
deb-src http://deb.debian.org/debian/ trixie-backports main contrib non-free-firmware

Install a minimal set of packages that allow the system to boot and to complete installation from there. Since this is new hardware, I’m using the kernel from backports. The backport firmware is needed for the mediatek wireless.

apt install -t trixie-backports linux-image-amd64 firmware-linux
apt install console-setup         \
            cryptsetup            \
            cryptsetup-initramfs  \
            dosfstools            \
            efibootmgr            \
            locales               \
            sudo                  \
            btrfs-progs           \
            amd64-microcode       \
            systemd-cryptsetup    \
            systemd-resolved      \
            systemd-timesyncd     \
            tpm2-tools            \
            iwd

Populate /etc/crypttab and /etc/fstab (include a tmpfs mounted to /tmp).

Enable the installed services

systemctl enable systemd-timesyncd
systemctl enable systemd-resolved

Configure your locale and timezone

dpkg-reconfigure locales
dpkg-reconfigure tzdata

Include at least en_US.UTF=8 for broad compatibility, and whatever else you need (en_GB.UTF-8 in my case).

Bootloader setup

We want to enable secure boot, which Debian does using a small bootloader, called shim. This is signed by Microsoft, and lets Debian chainload GRUB (or other bootloaders) and use its own keys for verification.

apt install shim-signed grub-efi-amd64-signed

Add some settings to help debug GRUB and the boot process if it fails. These can be removed later. In /etc/default/grub.d/99-debug.cfg

# Settings to aid debugging

GRUB_CMDLINE_LINUX_DEFAULT=""
GRUB_TERMINAL=console

and enable GRUB to decrypt the root partition to find /boot; in /etc/default/grub.d/99-debug.cfg

GRUB_ENABLE_CRYPTODISK=y

Now run

mkdir /boot/grub
grub-probe
update-grub

Regenerate the initial ramdisk

update-initramfs -u -k all

Now we install GRUB. Firsts mount efivars

mount -t efivarfs efivars /sys/firmware/efi/efivars

Then run

grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=debian --uefi-secure-boot --recheck --no-floppy

Note the uefi-secure-boot flag which informs GRUB to use shim.

Finally set a root password

passwd

and reboot into the new system

reboot

First boot

Hopefully the system comes up clean. If not, repair work can be done by remounting the root filesystem live CD and fixing there (possibly inside the chroot). For example, I had to change the KDF for my encrypted disk, remove an unused swap partiton, and install missing firmware this way.

Add a user and lock root

Login as root, and add a new user

adduser USERNAME    # follow the wizard
usermod -a -G sudo USERNAME

logout, and login again as the new user, test sudo by locking the root account

sudo passwd -l root

Connecting to WiFi

iwd can be configured to connect to a wireless network. I used systemd-creds to store an encryption key for wireless keys sealed against a key on disk, and the TPM. Even though the on disk credential is useless on its own, I like to keep credentials stored outside of the root filesystem so backups don’t contain them. I added a vault subvolume for this purpose.

This kind of storage is useful for keys which can easily be rotated / regenerated on demand.

Vaulted credential store

To create new subvolume, we need to have the root subvolume mounted.

mount -o noatime,nodiscard /dev/mapper/cry_root /mnt
btrfs subvolume create /mnt/@vault
umount /mnt
mkdir /vault

Update /etc/fstab to mount subvolume to /vault

systemctl daemon-reload
mkdir /vault
chmod 600 /vault
mount -a

systemd stores credentials in /etc/credstore.encrypted, so we bind this directory to a directory in the vault.

mkdir /vault/credstore.encrypted

and in /etc/fstab

/vault/credstore.encrypted  /etc/credstore.encrypted	none	defaults.bind	0 0

Activate this mount with

systemctl daemon-reload
mount -a

Check this works by touching a file from /etc/credstore.encrypted and checking it appears in the vault subvolume.

iwd configuration

Check you can use the TPM with systemd-analyze has-tpm2, then generate a TPM locked credential (locked against PCR7, which relies on secure boot, and the certificate use to sign shim).

openssl rand -hex 32 | systemd-creds --tpm2-device=auto --tpm2-pcrs=7 --name=iwd-secret encrypt - /etc/credstore.encrypted/iwd-secret.cred
chmod 400 /etc/credstore.encrypted/iwd-secret.cred

PCR 7 should be relatively stable - if it changes for any reason, encrypted data is lost, and you’ll need to regenerate the key (or revert to a state where PCR7 is the same).

Tell iwd to use this secret to encrypt credentials

mkdir /etc/systemd/system/iwd.service.d

and in /etc/systemd/system/iwd.service.d/use-creds.conf add

[Service]
LoadCredentialEncrypted=iwd-secret:/etc/credstore.encrypted/iwd-secret.cred

In /etc/iwd/main.conf, in section [General]

SystemdEncrypt=iwd-secret
EnableNetworkConfiguration=true

while under [Network]

NameResolvingService=systemd

EnableNetworkConfiguration enables DHCP on the wireless interfaces, while NameResolvingService=systemd lets iwd instruct systemd-resolved about DNS configuration received over DHCP. You can also use resolvconf if you prefer, but systemd-resolvd is simple for easy use cases, and remains fairly simple for more complex arrangements (for example, using a DNS server over a VPN when available, for specific domains).

Now

systemctl daemon-reload
systemctl start iwd.service
systemctl enable iwd.service

should succeed (assuming you installed the needed firmware in firmware-linux), and you can use iwctl to configure access to your wireless network; see here for examples.

Firewalling

I did this later in the process so nftables was already installed. For the paranoid, do this before wireless setup, although if nothing is listening, there is nothing to firewall.

Adjust the workstation example rules to your taste and needs.

apt install nftables
cp /usr/share/doc/nftables/examples/workstation.nft /etc/nftables.conf
systemctl enable nftables
systemctl start nftables

Finish initial package installation

sudo apt update
sudo apt upgrade
sudo apt install tasksel
sudo tasksel --new-install

I select just the standard system utilities.

Keyboard

I like to swap caps lock and control keys.

In /etc/default/keyboard, edit and obtain

XKBOPTIONS="ctrl:swapcaps"

then run

sudo setupcon -k

See man keyboard, man x-keyboard-options for more information.

GUI setup

I’ve stubbornley stuck to X11 for years, but with the fancy new screen on this laptop I decided to give Wayland a go. I started with labwc (basically OpenBox for Wayland) since its packaged in Debian, and I can get 90% of the functionality I typically want from tiliing window managers. I’ll likely install a tiling window manager later, but having something stable and supported is worthwhile (since very few wayland tilers are packaged for Debian).

I won’t go into the details here, everyone has it a little different.

apt install labwc foot wlr-randr fuzzel

labwc can be started wth labwc (or, as I prefer exec labwc, and exited from the right click menu). Configure as desired

mkdir ~/.config/labwc
...

at minimum you’ll want to change the default terminal to foot.

Display manager

sddm is decent and low dependency, but the default theme (sddm-theme-debian-breeze) carries a dependency on KDE Plasma, which brings with it a tonne of dependencies (including network manager, just to completely scuff your network setup). Fortunately the theme is only a recommended dependency, so we can install it without.

apt install sddm --no-install-recommends

sddm will start on next reboot, or you can explicitly start the display-manager.service unit with systemctl. You should be able to launch labwc from here, although the interface is quite bare, and lacks any styling.

Basic tools

Tried and true.

apt install tmux
apt install firefox-esr

Audio

For audio we’ll use pipewire which is the default these days.

apt install pipewire-audio
systemctl enable --user pipewire
systemctl start --user pipewire

Various diagnostics suggested this should work, but I heard no sound. I thought this was my lack of familiarity with pipewire, but dmesg revealed missing firmware for my audio device.

sudo apt install -t trixie-backports firmware-cirrus

and reboot. Sound now works. It’s not bad.

Machine-owner keys

This is optional, I only did it because I wanted to run memtest.

Secure boot with shim relies on the debian signing key. To sign custom kernels or modules, or test tools like memtest86+, we need to enroll a machine owner key. In a root shell

mkdir -p /vault/boot/mok
cd /vault/boot/mok

openssl req -nodes -new -x509 -newkey rsa:2048 -keyout MOK.priv -outform DER -out MOK.der -days 36500 -subj "/CN=halo/"
openssl x509 -inform der -in MOK.der -out MOK.pem
mokutil --import /vault/boot/mok/MOK.der

This will ask for a (one-time) password which is used to confirm this key on reboot.

Test:

sudo mokutil --test-key /vault/boot/mok/MOK.der

This will display as queued for enrollment prior to reboot, and enrolled after reboot (assuming you successfully enrolled it).

I expected this to modify PCR7 and invalidate my iwd ecnryption key, but it turned out that MOK keys change the value of PCR14 instead.

Example: sign memtest86+ (assuming you’ve installed it).

apt install sbsigntool
sudo mv /boot/memtest86+x64.efi /boot/memtest86+x64.efi.unsigned    # backup old memtest86+
sbsign --key /vault/boot/mok/MOK.priv --cert /vault/boot/mok/MOK.pem "/boot/memtest86+x64.efi.unsigned" --output "/boot/memtest86+x64.efi"

The old version can be deleted once signed. This allowed memtest86+ to start working.

Take a read-only snapshot of the system

At this point, take a snapshot so you can roll back to this point if needed.

btrfs subvolume snapshot -r / /.snap/root/NAME_OF_SNAPSHOT