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
-
encrypted disks
-
secure boot support (I’d traditionally just turn this off, but thought I might embrace it this time)
-
BTRFS for the filesystem
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