encryptionlinux-kernelarchlinuxbootinitramfs

mkinitcpio ERROR: Hook 'luks_unlock' cannot be found


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"
}

1.Search first Udisk part

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."

2.Search Second Udisk part

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]#

Solution

  • 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