AMD Framework 13 with Nix, Secure Boot, & home-manager
Contents
I have spent the last week installing and configuring a Framework 13 laptop. My first attempt was to use NixOS (since that’s my distro of choice) but the Framework keyboard wasn’t detected by either the minimal or graphical NixOS iso. So I went with Arch Linux and installed Nix + Home-Manager to manage most of my system.
Hardware
I built
the hardware, meaning my Framework 13 had last-mile assembly conducted by the end-user. This gives a better sense of control & repairability and lets you customize the machine a little more, and it only takes a few minutes to assemble.
I read complaints online about build quality but I am happy to report that I haven’t personally noticed anything bothersome besides wishing the bezel were aluminum (a very minor thing).
Processor & GPU
The processor and GPU are AMD. I prefer AMD. Also the Radeon 860M can be used with ollama.
$ cat /proc/cpuinfo | grep '^model name' | head -n1 model name : AMD Ryzen AI 7 350 w/ Radeon 860M
This means that I have the AMD Pod Security Processor, AMD’s parallel to the Intel Management Enginge. The downside is, obviously, these secondary processors are often vulnerable and have privileged access to system hardware. The upside is this provides functionality such as Secure Boot.
As far as I know, it is not currently possible to use CoreBoot on the latest AMD Framework 13, but experimental support is in progress for the previous gen of AMD CPUs, leaving me hopeful that we will see it here eventually.
However, the default firmware supports Secure Boot with custom keys, which is good enough for me at the moment.
Trackpad
The trackpad impressed me. From what I hear, Apple has a patent on trackpads where the full tactile buttonpress can happen over the full Trackpad surface. So this trackpad lets you tap anywhere but the top half-centimeter or so. Not a bad compromise.
In my home-manager config for sway, I used this to get closer to Apple’s trackpad settings.
# framework laptop touchpad
"2362:628:PIXA3854:00_093A:0274_Touchpad" = {
scroll_factor = "0.5";
accel_profile = "adaptive";
pointer_accel = "-0.1";
dwt = "enabled";
click_method = "clickfinger"; };
Keyboard
The keyboard is good quality. I went with the blank keycaps for style points and to encourage memorizing my keyboard layout, which I’m happy with after some initial hurdles learning how the F-keys function.
Fingerprint reader
The power button is also a fingerprint reader! It works with out-of-box drivers and seldom fails to register my fingerprint. That’s all I could ask for, really.
Nix vs Arch
As mentioned earlier, NixOS doesn’t work on this machine—the keyboard drivers fail to load. I went with Arch Linux and all hardware functions out of the box. I’m happy with this. Then I install Nix (pacman -S nix
) and wrote a Flake for my home-manager configuration, reusing and improving configs from my NixOS desktop.
I had to come up with a dividing line
between Nix and Arch, since in many cases they can replace components of one another. I ended up with home-manager
managing nearly all of my homedir and pacman
or interactive configuration for the remainder.
OS Configuration
Partitioning and Encryption
The system has a TPM so I’ve been able to do some interesting things, like configure the TPM to store my dm-crypt keys.
I use a configuration where /boot
and /boot/efi
are on separate physical partitions and then there’s a giant /dev/nvme0n1p2
partition housing LUKS. Within LUKS is LVM, with the first vg housing (encrypted) swap and the second divided into /home
, /var/
and /
btrfs subvolumes.
$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
nvme0n1 259:0 0 1.8T 0 disk
├─nvme0n1p1 259:1 0 2G 0 part /boot
├─nvme0n1p2 259:2 0 1.8T 0 part
│ └─cryptroot 253:0 0 1.8T 0 crypt
│ ├─vg-swap 253:1 0 32G 0 lvm [SWAP]
│ └─vg-main 253:2 0 1.8T 0 lvm /home
│ /var
│ / └─nvme0n1p3 259:3 0 2G 0 part /boot/efi
Secure Boot
For Secure Boot I am using systemd-ukify
, sbsign
, and pacman hooks.
# sbctl status
Installed: ✓ sbctl is installed
Owner GUID: 9df5ea07-e33e-4361-9c03-51d46b0dd9ed
Setup Mode: ✓ Disabled
Secure Boot: ✓ Enabled Vendor Keys: none
To get here you need to generate keys:
# sbctl create-keys
This creates keys in /var/lib/sbctl/keys/
:
# ls /var/lib/sbctl/keys
db KEK PK
Then you reboot, put the secure-boot in setup mode (which allows writing keys), and enroll your keys with sbctl enroll-keys
.
sbctl and signing
When you enable secure boot, your bootloader and kernel images will require signing. I ensure this with a pacman hook:
# cat /etc/pacman.d/hooks/90-secureboot-systemd-update.hook
[Trigger]
Operation = Upgrade
Type = Package
Target = systemd # systemd-boot is part of the systemd package
[Action]
Description = Updating and signing systemd-boot on ESP for Secure Boot...
When = PostTransaction
Exec = /usr/local/bin/update-signed-bootloader NeedsTargets
Which calls a script /update-signed/bootloader
1.
The sbctl sign-all
signs the files enumerated in /var/lib/sbctl/files.json
:
# cat /var/lib/sbctl/files.json
{
"/boot/efi/EFI/BOOT/BOOTX64.EFI": {
"file": "/boot/efi/EFI/BOOT/BOOTX64.EFI",
"output_file": "/boot/efi/EFI/BOOT/BOOTX64.EFI"
},
"/boot/efi/EFI/Linux/arch-secure.efi": {
"file": "/boot/efi/EFI/Linux/arch-secure.efi",
"output_file": "/boot/efi/EFI/Linux/arch-secure.efi"
},
"/boot/efi/EFI/Linux/arch-troubleshoot.efi": {
"file": "/boot/efi/EFI/Linux/arch-troubleshoot.efi",
"output_file": "/boot/efi/EFI/Linux/arch-troubleshoot.efi"
},
"/boot/efi/EFI/systemd/systemd-bootx64.efi": {
"file": "/boot/efi/EFI/systemd/systemd-bootx64.efi",
"output_file": "/boot/efi/EFI/systemd/systemd-bootx64.efi"
} }
Files will be added here if you sign them manually ever. So you can edit the files.json
database with, for example, sbctl sign -s /boot/efi/EFI/Linux/arch-troubleshoot.efi
.
ukify
I’m using ukify
to generate unified kernel images with an embedded initramfs and kernel image. This is done via a file /etc/pacman.d/hooks/95-sbctl-ukify
:
[Trigger]
Operation = Upgrade
Type = Package
Target = linux
Target = amd-ucode # Add other relevant packages like 'systemd' if its updates affect initramfs build
[Action]
Description = Rebuilding UKIs and signing with sbctl...
When = PostTransaction
Exec = /usr/local/bin/rebuild-ukis-for-sbctl.sh NeedsTargets
This calls a script /usr/local/bin/rebuild-ukis-for-sbctl.sh
2.
TPM decryption
Once you have a standard dm-crypt/LUKS
configuration you can enroll it in the TPM. First ensure you have a TPM.
# ls /dev/tpm*
Then you can enroll the device with:
# systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=0,4,6,7 /dev/nvme0n1p2
You’ll note I specify --tpm2-pcrs=0,4,6,7
there. I do that because I want to measure the platform-code
, boot-loader-code
, host-platform
, and secure-boot-policy
and only release keys from the TPM if all match. This is an agressive config; most people only pick 7
.
You’ll see the TPM PCR unlock attached to our drive:
# cryptsetup luksDump /dev/nvme0n1p2 | grep pcr | head
0: systemd-tpm2
tpm2-hash-pcrs: 7
tpm2-pcr-bank: sha256
tpm2-pubkey:
tpm2-pubkey-pcrs: tpm2-primary-alg: ecc
For more on how to configure this, see ArchWiki: Encrypting an entire System#Luks on a partition with TPM2 and Secure Boot.
Note that the TPM will only grant access to the decryption key if the Secure Boot chain does not show signs of tampering.
Fingerprint Unlock
First install fprint
with pacman. Then add it to /etc/pam.d/system-auth
:
# diff -c10 /etc/pam.d/system-auth.bk /etc/pam.d/system-auth*** /etc/pam.d/system-auth.bk 2025-05-01 04:34:54.821598714 -0400
--- /etc/pam.d/system-auth 2025-04-29 23:22:27.302944835 -0400
***************
*** 1,16 ****
--- 1,17 ----
#%PAM-1.0
auth required pam_faillock.so preauth
# Optionally use requisite above if you do not want to prompt for the password
# on locked accounts.
-auth [success=2 default=ignore] pam_systemd_home.so+ auth sufficient pam_fprintd.so
auth [success=1 default=bad] pam_unix.so try_first_pass nullok
auth [default=die] pam_faillock.so authfail
auth optional pam_permit.so
auth required pam_env.so
auth required pam_faillock.so authsucc
# If you drop the above call to pam_faillock.so the lock will be done also
# on non-consecutive authentication failures.
-account [success=1 default=ignore] pam_systemd_home.so account required pam_unix.so
Now use fprintd-enroll
(optionally with -f right-middle-finger
and so forth) to enroll your fingers, which are stored in /var/lib/fprint
. You can now login and sudo
with your fingerprint.
Home Configuration
I now use nix
with home-manager
to manage my homedir, similar to my NixOS machine.
home-manager entrypoint
I have moved to Nix Flakes for my home-manager entrypoint. Flakes are still new to me, but they solve reproducibility issues present with regular Nix.
{
description = "My full system config (Arch + Home Manager)";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
home-manager = {
url = "github:nix-community/home-manager";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = { self, nixpkgs, home-manager, ... }: let
system = "x86_64-linux";
username = builtins.getEnv "USER";
homeDirectory = builtins.getEnv "HOME";
secrets = {
# ...
};
in {
homeConfigurations.${username} = home-manager.lib.homeManagerConfiguration {
pkgs = import nixpkgs {
inherit system;
};
modules = [
../home-manager/arch.nix
{
programs.home-manager.enable = true;
home.stateVersion = "24.11";
}
];
extraSpecialArgs = {
inherit system;
secrets = secrets;
username = username;
homeDirectory = homeDirectory;
};
};
}; }
I know that, in principle, I should have multiple entrypoints here and condition them based on the machine (so I use the same Flake for Darwin and Arch). I’m not there yet.
I run my home-manager
setup with this:
#!/usr/bin/env bash
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
. "$SCRIPT_DIR/secrets.sh"
export NIXPKGS_ALLOW_UNFREE=1 nix run github:nix-community/home-manager -- switch --impure --flake "${SCRIPT_DIR}"
It requires --impure
because it takes env vars as inputs, such as USER
, HOME
, USER_EMAIL
. I don’t want my user email cleartext in my dotfiles (though in practice, people can steal it from GitHub).
ollama
I’m currently using nix-shell
to run ollama. I had a hell of a time getting this to work with the Radeon 860M so here are my steps.
First, use rocminfo
to get some magic variables:
$ nix-shell -p "rocmPackages.rocminfo" --run "rocminfo" | grep "gfx"
Name: gfx1152 Name: amdgcn-amd-amdhsa--gfx1152
Now set the env vars with the magic make rocm work
values, the first of which is taken from rocminfo
:
#!/usr/bin/env bash
OLLAMA_PACKAGE=ollama-rocm
ENV_VARS='HCC_AMDGPU_DEVICES=gfx1152 HSA_OVERRIDE_GFX_VERSION="11.0.0" ROCR_VISIBLE_DEVICES=0'
COMMAND="$ENV_VARS ollama serve"
echo "running '${COMMAND}'" echo nix-shell -p "${OLLAMA_PACKAGE}" --command "${COMMAND}"
If it works, you will see output lines like this, indicating rocm
is working:
time=2025-05-06T23:21:04.985-04:00 level=INFO source=types.go:130 msg="inference compute" id=0 library=rocm variant="" compute=gfx1152 driver=0.0 name=1002:1114 total="8.0 GiB" available="7.9 GiB"
If you get an error, like msg=unsupported Radeon iGPU detected skipping" id=0 total="512.0 MiB"
then this means you need to reboot to your bootloader and change the iGPU settings to allocate static vRAM to your GPU. Sorry. I gave 8 GiB to mine, which is stolen permanently from RAM.
/usr/local/bin/update-signed-bootloader
:↩︎#!/usr/bin/env bash set -e ESP_PATH="/boot/efi" SYSTEMD_BOOT_SRC="/usr/lib/systemd/boot/efi/systemd-bootx64.efi" SYSTEMD_BOOT_DEST_MAIN="${ESP_PATH}/EFI/systemd/systemd-bootx64.efi" SYSTEMD_BOOT_DEST_FALLBACK="${ESP_PATH}/EFI/BOOT/BOOTX64.EFI" echo "Updating systemd-boot on ESP..." # Ensure destination directories exist mkdir -p "${ESP_PATH}/EFI/systemd" mkdir -p "${ESP_PATH}/EFI/BOOT" # Copy the new bootloader files to the ESP cp "${SYSTEMD_BOOT_SRC}" "${SYSTEMD_BOOT_DEST_MAIN}" cp "${SYSTEMD_BOOT_SRC}" "${SYSTEMD_BOOT_DEST_FALLBACK}" echo "Successfully copied systemd-boot to ESP." echo "Running sbctl sign-all to update signatures for bootloader..." # sbctl sign-all will detect that the files on the ESP have changed # (because their checksums are now different after the copy) # and re-sign them if they are tracked in its database. sbctl sign-all -g # Alternatively, for more explicit signing of just these files: # sbctl sign "${SYSTEMD_BOOT_DEST_MAIN}" # sbctl sign "${SYSTEMD_BOOT_DEST_FALLBACK}" echo "Bootloader update and signing process complete."
/usr/local/bin/rebuild-ukis-for-sbctl.sh
:↩︎#!/bin/bash set -e LUKS_UUID_VALUE=$(sudo blkid -s UUID -o value /dev/nvme0n1p2) # Or hardcode SECURE_UKI_PATH="/boot/efi/EFI/Linux/arch-secure.efi" TROUBLESHOOT_UKI_PATH="/boot/efi/EFI/Linux/arch-troubleshoot.efi" echo "Rebuilding ${SECURE_UKI_PATH}..." # Ensure your ukify/systemd-ukify command is correct here ukify build \ --linux /boot/vmlinuz-linux \ --initrd /boot/initramfs-linux.img \ --microcode /boot/amd-ucode.img \ --os-release /etc/os-release \ --cmdline "${COMMON_CMDLINE} rd.luks.options=${LUKS_UUID_VALUE}=tpm2-device=auto" \ --output "${SECURE_UKI_PATH}" echo "Rebuilding ${TROUBLESHOOT_UKI_PATH}..." ukify build \ --linux /boot/vmlinuz-linux \ --initrd /boot/initramfs-linux-fallback.img \ --microcode /boot/amd-ucode.img \ --os-release /etc/os-release \ --cmdline "${COMMON_CMDLINE}" \ --output "${TROUBLESHOOT_UKI_PATH}" echo "Running sbctl sign-all to update signatures..." # This assumes SECURE_UKI_PATH and TROUBLESHOOT_UKI_PATH have been added to sbctl's database # via `sbctl sign -s ...` at least once. # If not, or for explicit control, use: # sbctl sign "${SECURE_UKI_PATH}" # sbctl sign "${TROUBLESHOOT_UKI_PATH}" sbctl sign-all echo "UKIs rebuilt and signed." echo "------------------------------------------------------------------------------------" echo "IMPORTANT REMINDER:" echo "If the kernel or related files for '${SECURE_UKI_PATH}' changed," echo "you MUST re-enroll the TPM key after the next reboot (enter password once manually):" echo " sudo systemd-cryptenroll --wipe-slot=tpm2 /dev/nvme0n1p2" echo " sudo systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=0,6,7 /dev/nvme0n1p2" echo "------------------------------------------------------------------------------------"