I am trying to build a full-disk encryption scheme in ArchLinux. I found that GRUB cannot pass header parameters, so I think I need to write a custom hook script to guide the LUKS header and key file.
My core problem is actually how to mount the USB flash drive at startup and successfully unlock LUKS, that is, the LUKS header and key file problem.
I am attempting to set up a custom mkinitcpio hook on Arch Linux to decrypt my root partition using a key file and header file from a USB drive during system boot.
However, I am encountering an issue: despite confirming that the files exist and are configured correctly, mkinitcpio continues to report an error stating that the hook cannot be found.
Here is my device architecture:
AMD R7 H255
24GB DDR5 4800MHz RAM
A TF card inserted into a card reader forms the USB
The hard drive is a 1TB NVMe drive
These are the image parameters I used to install the system:
archlinux-2025.08.01-x86_64
[root@archiso hooks]# uname -a
Linux archiso 6.15.8-arch1-2 #1 SMP PREEMPT_DYNAMIC Tue, 29 Jul 2025 15:05:00 +0000 x86_64 GNU/Linux
[root@archiso hooks]#
My plan is to construct a full-disk encryption system with the EFI boot keyfile and LUKS header all located on the USB drive in a secure architecture.
I have check the chmod +x command to these file
/dev/sda1 for EFI
/dev/sda2 for /boot
/dev/sda3 for keyfile
/dev/sda4 for LUKS header
/dev/nvme0n1
One Full Encrypt Disk but there is not a LUKS header on it:
[root@archiso etc]# ls /key/
keyfile
[root@archiso etc]# ls /header/
0header.img
[root@archiso etc]#
The following is some data I collected in the arch-chroot environment of ArchISO.
Due to character limitations, I have posted the mkinitcpio.conf code separately in another section.
I also checked his binary data to prevent invisible characters from causing interference.
1.Partition structure
[root@archiso ~]# lsblk -f
NAME FSTYPE FSVER LABEL UUID FSAVAIL FSUSE% MOUNTPOINTS
loop0 squashfs 4.0
sda
├─sda1 vfat FAT32 899B-AC94 1021.8M 0% /efi
├─sda2 ext4 1.0 70da482e-f647-47c2-acd6-c1b208c8a2a8 1.5G 15% /boot
├─sda3 vfat FAT32 8A4D-AE92 10G 0% /key
└─sda4 vfat FAT32 8A76-DBEB 10G 0% /header
sdb
├─sdb1 ntfs Ventoy 58C04941C049269A
│ └─ventoy iso9660 Joliet Ex ARCH_202508 2025-08-01-13-39-26-00
└─sdb2 vfat FAT16 VTOYEFI 223C-F3F8
nvme0n1
└─crypt_system
│ LVM2_member LVM2 001 FonZj4-Enqi-PWvJ-kTJc-T9MI-HSuQ-sdYXfN
├─system-lv_root
│ ext4 1.0 4d338a11-4fa6-4465-8ac6-6913b22c64b5 369.8G 1% /
├─system-lv_home
│ ext4 1.0 2b4b5ef1-9e04-4eb0-8820-72147db656f5 139.1G 0% /home
└─system-lv_vm
ext4 1.0 d126b9b3-70bb-4c3a-981a-b6d1b1ee569d 372.6G 0% /vm
[root@archiso ~]#
2.hook script
luks_unlock(/etc/initcpio/hooks)
[root@archiso hooks]# pwd
/etc/initcpio/hooks
[root@archiso hooks]# cat luks_unlock
#!/bin/sh
readonly KEY_UUID="8A4D-AE92"
readonly HEADER_UUID="8A76-DBEB"
readonly KEY_MOUNT_POINT="/key"
readonly HEADER_MOUNT_POINT="/header"
install() {
install_bin '/usr/bin/mount'
install_bin '/usr/bin/mkdir'
install_bin '/usr/bin/lsblk'
install_bin '/usr/bin/cryptsetup'
install_script '/etc/initcpio/install/luks_unlock_exec' '/usr/bin/luks_unlock_exec'
}
run_hook() {
/usr/bin/luks_unlock_exec
}
if [ -n "$run_hook" ]; then
run_hook
else
install
fi
root@archiso hooks]# hexdump -C -n 1024 luks_unlock
00000000 23 21 2f 62 69 6e 2f 73 68 0a 0a 72 65 61 64 6f |#!/bin/sh..reado|
00000010 6e 6c 79 20 4b 45 59 5f 55 55 49 44 3d 22 38 41 |nly KEY_UUID="8A|
00000020 34 44 2d 41 45 39 32 22 0a 72 65 61 64 6f 6e 6c |4D-AE92".readonl|
00000030 79 20 48 45 41 44 45 52 5f 55 55 49 44 3d 22 38 |y HEADER_UUID="8|
00000040 41 37 36 2d 44 42 45 42 22 0a 72 65 61 64 6f 6e |A76-DBEB".readon|
00000050 6c 79 20 4b 45 59 5f 4d 4f 55 4e 54 5f 50 4f 49 |ly KEY_MOUNT_POI|
00000060 4e 54 3d 22 2f 6b 65 79 22 0a 72 65 61 64 6f 6e |NT="/key".readon|
00000070 6c 79 20 48 45 41 44 45 52 5f 4d 4f 55 4e 54 5f |ly HEADER_MOUNT_|
00000080 50 4f 49 4e 54 3d 22 2f 68 65 61 64 65 72 22 0a |POINT="/header".|
00000090 0a 69 6e 73 74 61 6c 6c 28 29 20 7b 0a 0a 20 20 |.install() {.. |
000000a0 20 20 69 6e 73 74 61 6c 6c 5f 62 69 6e 20 27 2f | install_bin '/|
000000b0 75 73 72 2f 62 69 6e 2f 6d 6f 75 6e 74 27 0a 20 |usr/bin/mount'. |
000000c0 20 20 20 69 6e 73 74 61 6c 6c 5f 62 69 6e 20 27 | install_bin '|
000000d0 2f 75 73 72 2f 62 69 6e 2f 6d 6b 64 69 72 27 0a |/usr/bin/mkdir'.|
000000e0 20 20 20 20 69 6e 73 74 61 6c 6c 5f 62 69 6e 20 | install_bin |
000000f0 27 2f 75 73 72 2f 62 69 6e 2f 6c 73 62 6c 6b 27 |'/usr/bin/lsblk'|
00000100 0a 20 20 20 20 69 6e 73 74 61 6c 6c 5f 62 69 6e |. install_bin|
00000110 20 27 2f 75 73 72 2f 62 69 6e 2f 63 72 79 70 74 | '/usr/bin/crypt|
00000120 73 65 74 75 70 27 0a 0a 20 20 20 20 69 6e 73 74 |setup'.. inst|
00000130 61 6c 6c 5f 73 63 72 69 70 74 20 27 2f 65 74 63 |all_script '/etc|
00000140 2f 69 6e 69 74 63 70 69 6f 2f 69 6e 73 74 61 6c |/initcpio/instal|
00000150 6c 2f 6c 75 6b 73 5f 75 6e 6c 6f 63 6b 5f 65 78 |l/luks_unlock_ex|
00000160 65 63 27 20 27 2f 75 73 72 2f 62 69 6e 2f 6c 75 |ec' '/usr/bin/lu|
00000170 6b 73 5f 75 6e 6c 6f 63 6b 5f 65 78 65 63 27 0a |ks_unlock_exec'.|
00000180 0a 7d 0a 0a 72 75 6e 5f 68 6f 6f 6b 28 29 20 7b |.}..run_hook() {|
00000190 0a 0a 20 20 20 20 2f 75 73 72 2f 62 69 6e 2f 6c |.. /usr/bin/l|
000001a0 75 6b 73 5f 75 6e 6c 6f 63 6b 5f 65 78 65 63 0a |uks_unlock_exec.|
000001b0 7d 0a 0a 69 66 20 5b 20 2d 6e 20 22 24 72 75 6e |}..if [ -n "$run|
000001c0 5f 68 6f 6f 6b 22 20 5d 3b 20 74 68 65 6e 0a 20 |_hook" ]; then. |
000001d0 20 20 20 72 75 6e 5f 68 6f 6f 6b 0a 65 6c 73 65 | run_hook.else|
000001e0 0a 20 20 20 20 69 6e 73 74 61 6c 6c 0a 66 69 0a |. install.fi.|
000001f0
[root@archiso hooks]#
luks_unlock_exec
[root@archiso install]# pwd
/etc/initcpio/install
[root@archiso install]# cat luks_unlock_exec
#!/bin/sh
log_message() {
echo "Custom LUKS Hook: $1"
}
log_message "Searching for device with UUID $KEY_UUID..."
KEY_DEV=$(lsblk -o UUID,NAME | grep "$KEY_UUID" | awk '{print $2}')
if [ -z "$KEY_DEV" ]; then
log_message "Device with UUID $KEY_UUID not found. Aborting."
exit 1
fi
log_message "Found key device: /dev/$KEY_DEV. Attempting to mount..."
mkdir -p "$KEY_MOUNT_POINT"
mount -o ro "/dev/$KEY_DEV" "$KEY_MOUNT_POINT"
if [ $? -ne 0 ]; then
log_message "Failed to mount key device. Aborting."
exit 1
fi
log_message "Successfully mounted key device."
log_message "Searching for device with UUID $HEADER_UUID..."
HEADER_DEV=$(lsblk -o UUID,NAME | grep "$HEADER_UUID" | awk '{print $2}')
if [ -z "$HEADER_DEV" ]; then
log_message "Device with UUID $HEADER_UUID not found. Aborting."
exit 1
fi
log_message "Found header device: /dev/$HEADER_DEV. Attempting to mount..."
mkdir -p "$HEADER_MOUNT_POINT"
mount -o ro "/dev/$HEADER_DEV" "$HEADER_MOUNT_POINT"
if [ $? -ne 0 ]; then
log_message "Failed to mount header device. Aborting."
umount "$KEY_MOUNT_POINT"
exit 1
fi
log_message "Successfully mounted header device."
log_message "Executing cryptsetup luksOpen..."
cryptsetup --header "$HEADER_MOUNT_POINT/0header.img" --key-file "$KEY_MOUNT_POINT/keyfile" luksOpen /dev/nvme0n1 crypt_system
if [ $? -ne 0 ]; then
log_message "cryptsetup failed. Aborting."
umount "$KEY_MOUNT_POINT"
umount "$HEADER_MOUNT_POINT"
exit 1
fi
[root@archiso install]# hexdump -C -n 1024 luks_unlock_exec
00000000 23 21 2f 62 69 6e 2f 73 68 0a 0a 6c 6f 67 5f 6d |#!/bin/sh..log_m|
00000010 65 73 73 61 67 65 28 29 20 7b 0a 20 20 20 20 65 |essage() {. e|
00000020 63 68 6f 20 22 43 75 73 74 6f 6d 20 4c 55 4b 53 |cho "Custom LUKS|
00000030 20 48 6f 6f 6b 3a 20 24 31 22 0a 7d 0a 0a 23 20 | Hook: $1".}..# |
00000040 31 2e 53 65 61 72 63 68 20 66 69 72 73 74 20 55 |1.Search first U|
00000050 64 69 73 6b 20 70 61 72 74 0a 0a 6c 6f 67 5f 6d |disk part..log_m|
00000060 65 73 73 61 67 65 20 22 53 65 61 72 63 68 69 6e |essage "Searchin|
00000070 67 20 66 6f 72 20 64 65 76 69 63 65 20 77 69 74 |g for device wit|
00000080 68 20 55 55 49 44 20 24 4b 45 59 5f 55 55 49 44 |h UUID $KEY_UUID|
00000090 2e 2e 2e 22 0a 4b 45 59 5f 44 45 56 3d 24 28 6c |...".KEY_DEV=$(l|
000000a0 73 62 6c 6b 20 2d 6f 20 55 55 49 44 2c 4e 41 4d |sblk -o UUID,NAM|
000000b0 45 20 7c 20 67 72 65 70 20 22 24 4b 45 59 5f 55 |E | grep "$KEY_U|
000000c0 55 49 44 22 20 7c 20 61 77 6b 20 27 7b 70 72 69 |UID" | awk '{pri|
000000d0 6e 74 20 24 32 7d 27 29 0a 0a 69 66 20 5b 20 2d |nt $2}')..if [ -|
000000e0 7a 20 22 24 4b 45 59 5f 44 45 56 22 20 5d 3b 20 |z "$KEY_DEV" ]; |
000000f0 74 68 65 6e 0a 20 20 20 20 6c 6f 67 5f 6d 65 73 |then. log_mes|
00000100 73 61 67 65 20 22 44 65 76 69 63 65 20 77 69 74 |sage "Device wit|
00000110 68 20 55 55 49 44 20 24 4b 45 59 5f 55 55 49 44 |h UUID $KEY_UUID|
00000120 20 6e 6f 74 20 66 6f 75 6e 64 2e 20 41 62 6f 72 | not found. Abor|
00000130 74 69 6e 67 2e 22 0a 20 20 20 20 65 78 69 74 20 |ting.". exit |
00000140 31 0a 66 69 0a 0a 6c 6f 67 5f 6d 65 73 73 61 67 |1.fi..log_messag|
00000150 65 20 22 46 6f 75 6e 64 20 6b 65 79 20 64 65 76 |e "Found key dev|
00000160 69 63 65 3a 20 2f 64 65 76 2f 24 4b 45 59 5f 44 |ice: /dev/$KEY_D|
00000170 45 56 2e 20 41 74 74 65 6d 70 74 69 6e 67 20 74 |EV. Attempting t|
00000180 6f 20 6d 6f 75 6e 74 2e 2e 2e 22 0a 6d 6b 64 69 |o mount...".mkdi|
00000190 72 20 2d 70 20 22 24 4b 45 59 5f 4d 4f 55 4e 54 |r -p "$KEY_MOUNT|
000001a0 5f 50 4f 49 4e 54 22 0a 6d 6f 75 6e 74 20 2d 6f |_POINT".mount -o|
000001b0 20 72 6f 20 22 2f 64 65 76 2f 24 4b 45 59 5f 44 | ro "/dev/$KEY_D|
000001c0 45 56 22 20 22 24 4b 45 59 5f 4d 4f 55 4e 54 5f |EV" "$KEY_MOUNT_|
000001d0 50 4f 49 4e 54 22 0a 0a 69 66 20 5b 20 24 3f 20 |POINT"..if [ $? |
000001e0 2d 6e 65 20 30 20 5d 3b 20 74 68 65 6e 0a 20 20 |-ne 0 ]; then. |
000001f0 20 20 6c 6f 67 5f 6d 65 73 73 61 67 65 20 22 46 | log_message "F|
00000200 61 69 6c 65 64 20 74 6f 20 6d 6f 75 6e 74 20 6b |ailed to mount k|
00000210 65 79 20 64 65 76 69 63 65 2e 20 41 62 6f 72 74 |ey device. Abort|
00000220 69 6e 67 2e 22 0a 20 20 20 20 65 78 69 74 20 31 |ing.". exit 1|
00000230 0a 66 69 0a 6c 6f 67 5f 6d 65 73 73 61 67 65 20 |.fi.log_message |
00000240 22 53 75 63 63 65 73 73 66 75 6c 6c 79 20 6d 6f |"Successfully mo|
00000250 75 6e 74 65 64 20 6b 65 79 20 64 65 76 69 63 65 |unted key device|
00000260 2e 22 0a 0a 0a 23 20 32 2e 53 65 61 72 63 68 20 |."...# 2.Search |
00000270 53 65 63 6f 6e 64 20 55 64 69 73 6b 20 70 61 72 |Second Udisk par|
00000280 74 20 0a 0a 6c 6f 67 5f 6d 65 73 73 61 67 65 20 |t ..log_message |
00000290 22 53 65 61 72 63 68 69 6e 67 20 66 6f 72 20 64 |"Searching for d|
000002a0 65 76 69 63 65 20 77 69 74 68 20 55 55 49 44 20 |evice with UUID |
000002b0 24 48 45 41 44 45 52 5f 55 55 49 44 2e 2e 2e 22 |$HEADER_UUID..."|
000002c0 0a 48 45 41 44 45 52 5f 44 45 56 3d 24 28 6c 73 |.HEADER_DEV=$(ls|
000002d0 62 6c 6b 20 2d 6f 20 55 55 49 44 2c 4e 41 4d 45 |blk -o UUID,NAME|
000002e0 20 7c 20 67 72 65 70 20 22 24 48 45 41 44 45 52 | | grep "$HEADER|
000002f0 5f 55 55 49 44 22 20 7c 20 61 77 6b 20 27 7b 70 |_UUID" | awk '{p|
00000300 72 69 6e 74 20 24 32 7d 27 29 0a 0a 69 66 20 5b |rint $2}')..if [|
00000310 20 2d 7a 20 22 24 48 45 41 44 45 52 5f 44 45 56 | -z "$HEADER_DEV|
00000320 22 20 5d 3b 20 74 68 65 6e 0a 20 20 20 20 6c 6f |" ]; then. lo|
00000330 67 5f 6d 65 73 73 61 67 65 20 22 44 65 76 69 63 |g_message "Devic|
00000340 65 20 77 69 74 68 20 55 55 49 44 20 24 48 45 41 |e with UUID $HEA|
00000350 44 45 52 5f 55 55 49 44 20 6e 6f 74 20 66 6f 75 |DER_UUID not fou|
00000360 6e 64 2e 20 41 62 6f 72 74 69 6e 67 2e 22 0a 20 |nd. Aborting.". |
00000370 20 20 20 65 78 69 74 20 31 0a 66 69 0a 0a 6c 6f | exit 1.fi..lo|
00000380 67 5f 6d 65 73 73 61 67 65 20 22 46 6f 75 6e 64 |g_message "Found|
00000390 20 68 65 61 64 65 72 20 64 65 76 69 63 65 3a 20 | header device: |
000003a0 2f 64 65 76 2f 24 48 45 41 44 45 52 5f 44 45 56 |/dev/$HEADER_DEV|
000003b0 2e 20 41 74 74 65 6d 70 74 69 6e 67 20 74 6f 20 |. Attempting to |
000003c0 6d 6f 75 6e 74 2e 2e 2e 22 0a 6d 6b 64 69 72 20 |mount...".mkdir |
000003d0 2d 70 20 22 24 48 45 41 44 45 52 5f 4d 4f 55 4e |-p "$HEADER_MOUN|
000003e0 54 5f 50 4f 49 4e 54 22 0a 6d 6f 75 6e 74 20 2d |T_POINT".mount -|
000003f0 6f 20 72 6f 20 22 2f 64 65 76 2f 24 48 45 41 44 |o ro "/dev/$HEAD|
00000400
[root@archiso install]#
I read ArchWiki again and follow the part of dm-crypt/specialties
https://wiki.archlinux.org/title/Dm-crypt/Specialties#Encrypted_/boot_and_a_detached_LUKS_header_on_USB
Part 9 Encrypted /boot and a detached LUKS header on USB
The script in the xxx/install/ is a build script The script in the xxx/hooks/ is a run script
I make another script I copy /usr/lib/initcpio/install/encrypt as /etc/initcpio/install/luks_unlock I delete the part of help in it.
Then I make a other script (/etc/initcpio/hooks/luks_unlock)
#/bin/bash
key_part="usb-Generic_STORAGE_DEVICE_000000000819-0:0-part3"
header_part="usb-Generic_STORAGE_DEVICE_000000000819-0:0-part4"
key_mount="/key"
header_mount="/header"
run_hook() {
modprobe -a -q dm-crypt >/dev/null 2>&1
modprobe loop
[ "${quiet}" = "y" ] && CSQUIET=">/dev/null"
while [ ! -L "/dev/disk/by-id/$key_part" ]; do
echo "Waiting for key_part"
sleep 1
done
while [ ! -L "/dev/disk/by-id/$header_part" ]; do
echo "Waiting for header_part"
sleep 1
done
mkdir -p "$key_mount"
mkdir -p "$header_mount"
mount "/dev/disk/by-id/$key_part" "$key_mount"
mount "/dev/disk/by-id/$header_part" "$header_mount"
cryptsetup --header "$header_mount/0header.img" --key-file "$key_mount/keyfile" luksOpen /dev/nvme0n1 crypt_sys>
}
run_hook
There a point need to notice about mkinitcpio.conf,too.
1.Add "vfat" into module()
2.Make sure there are not systemd or encrypt in hook()
3.Make sure there are lvm2 in hook()
Then it could boot system