Skip to content

Instantly share code, notes, and snippets.

@ibressler
Last active March 10, 2024 22:49
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ibressler/338e25b981f8f1a5b9445e685ac5758b to your computer and use it in GitHub Desktop.
Save ibressler/338e25b981f8f1a5b9445e685ac5758b to your computer and use it in GitHub Desktop.
Creates an early grub core image for ESP to cryptmount a LUKS+LVM partition where the grub files /boot/ and the kernel is stored
#!/bin/dash
# Creates a standalone core image with grub to be started by EFI.
# It complements the excellent cryptroot tutorial at
# https://community.linuxmint.com/tutorial/view/2061
# for grub on BTRFS inside LVM inside LUKS.
#
# It initiates decryption of the root device which contains
# LVM with BTRFS on the root volume where grub is installed.
# Therefore, it allows to load grub from an LUKS encrypted BTRFS
# root file system.
#
# This script can be called automatically at the end of grub-update.
# Create a file /etc/grub.d/99_earlygrubcryptoimg with the contents:
# <-- snip -->
# # wait a moment to let grub.cfg be created before parsing it for some info
# (sleep 2 && echo && \
# <abs. path to this script> <abs. path to secure boot signing key>) 1>&2 &
# <-- snap -->
#
# Tested with Mint 18.2 on x86_64
#
# No Warranty! Use at your own risk!
# This script works for me, but it might not work for you!
# Oct. 2017, Ingo Breßler (dev at ingobressler.net)
# License: GPLv3
EFILABEL="Linux" # label in EFI boot menu
KEYFILE="$1" # path to secure boot signing key *.key, assumes certificate *.crt
ESPPATH="/boot/efi/" # path to partition containing EFI files
GRUBCFG="/boot/grub/grub.cfg" # common grub config generated by update-grub
is_btrfs () {
[ "x$(stat -f --format=%T "$GRUBCFG")" = "xbtrfs" ]
}
if ! is_btrfs; then
echo "Grub is not installed on BTRFS, this core image might not be needed!"
read -p "Continue anyway? [y/N] " answer
[ -z "$answer" ] && answer=n # default is no
(echo -n "$answer" | grep '^\s*[yY]\s*$') || exit 1
fi
echo "Building early grub core image for crypt root access ..."
echo "($0)"
# get a temporary working directory
WD="$(mktemp -d)"
# generate the current keyboard layout for grub
# get the current keyboard layout
XKBLAYOUT="$(setxkbmap -query | grep layout | awk '{print $2}')"
LAYOUTFILE="layout.gkb"
KEYMAP=""
if [ x"$XKBLAYOUT" != x ]; then
ckbcomp "$XKBLAYOUT" | grub-mklayout > "$WD/$LAYOUTFILE"
KEYMAP="keymap /$LAYOUTFILE"
fi
# get the graphics mode/resolution
GFXMODE="$(grep -o 'set\s\+gfxmode=.*$' "$GRUBCFG")"
# get the appropriate cryptomount command&UUID
CRYPTTAB="/etc/crypttab"
CRYPTDEVUUID="$(cat "$CRYPTTAB" 2> /dev/null \
| awk -F '=' '{print $2}' \
| awk '{gsub("-",""); print $1}')"
CRYPTOMOUNT="cryptomount -u $CRYPTDEVUUID"
SEARCHFS="$(grep -m1 -o 'search.*fs-uuid.*$' "$GRUBCFG")"
if [ "x$CRYPTDEVUUID" = "x" ]; then # no crypted device found
echo "No UUID of the crypted root device found in '$CRYPTTAB'!"
echo "Skipping early grub core image!"
exit 1
fi
# get abs. path to grub config dir from grub config
# the line typically starts with 'linux ...'
PREFIX="$(grep -oEm1 '(/[^/]+)*/boot/' "$GRUBCFG")${GRUBCFG#/boot/}"
PREFIX="${PREFIX%/*.*}" # strip the trailing file name component
# create the core image grub config
CORECFG="grub.cfg" # temp cfg file embedded in grubx64.efi
cat <<EOF > "$WD/$CORECFG"
root=(memdisk)
prefix=(\$root)/
terminal_input at_keyboard
$KEYMAP
$GFXMODE
insmod all_video
insmod gfxterm
terminal_output gfxterm
insmod luks
$CRYPTOMOUNT
insmod lvm
$SEARCHFS
set prefix=(\$root)'$PREFIX'
configfile \$prefix/grub.cfg
EOF
# build memdisk for core image
MEMDISK="memdisk.tar"
(cd "$WD" && tar cf "$MEMDISK" "$CORECFG" "$LAYOUTFILE")
# create the core image finally
OUTFILE="grubx64.efi"
grub-mkimage -c "$WD/${CORECFG}" -o "$WD/${OUTFILE}" -O x86_64-efi \
-m "$WD/${MEMDISK}" all_video at_keyboard boot btrfs cat chain configfile \
crypto cryptodisk disk diskfilter echo efifwsetup efinet ext2 fat font \
gettext gcry_arcfour gcry_blowfish gcry_camellia gcry_cast5 gcry_crc \
gcry_des gcry_dsa gcry_idea gcry_md4 gcry_md5 gcry_rfc2268 gcry_rijndael \
gcry_rmd160 gcry_rsa gcry_seed gcry_serpent gcry_sha1 gcry_sha256 \
gcry_sha512 gcry_tiger gcry_twofish gcry_whirlpool gfxmenu gfxterm \
gfxterm_background gzio halt hfsplus iso9660 jpeg keylayouts keystatus \
loadenv loopback linux linuxefi lsefi lsefimmap lsefisystab lssal luks \
lvm mdraid09 mdraid1x memdisk minicmd normal part_apple part_msdos \
part_gpt password_pbkdf2 png raid5rec raid6rec reboot search \
search_fs_uuid search_fs_file search_label sleep squash4 tar test true \
verify video
# sign the created grub image for secure boot possibly
if [ ! -z "$KEYFILE" ]; then
if [ -f "$KEYFILE" ]; then
CRTFILE="${KEYFILE%.*}.crt"
sbsign --key "$KEYFILE" --cert "$CRTFILE" --output "$WD/${OUTFILE}" \
"$WD/${OUTFILE}"
sbverify --cert "$CRTFILE" "$WD/${OUTFILE}"
else echo "Secure Boot keyfile '$1' not found!"
fi
else echo "No Secure Boot key argument for signing provided! Skipping."
fi
# copy the core image to the EFI directory
if [ ! -d "$ESPPATH" ]; then
echo "ESP path '$ESPPATH' does not exist! Giving up."
else
if [ "x$(stat -f --format=%T "$ESPPATH")" != "xmsdos" ]; then
echo "WARNING: ESP path '$ESPPATH' is not FAT formatted!"
fi
# copy to EFI partition
EFIPATH="$ESPPATH/EFI/$EFILABEL"
mkdir -p "$EFIPATH"
cp "$WD/$OUTFILE" "$EFIPATH/$OUTFILE"
# update EFI boot entries
DEVPART="$(basename "$(mount | grep "${ESPPATH%/}" | awk '{print $1}')")"
DEVEFI="$(basename "$(readlink -f "/sys/class/block/$DEVPART/..")")"
BOOTNUM="$(efibootmgr | grep -i " $EFILABEL\$" | grep -oE '[0-9]+')"
[ "x$BOOTNUM" != "x" ] && efibootmgr -q -d /dev/$DEVEFI -b $BOOTNUM -B
efibootmgr -q -d /dev/$DEVEFI -c -L "$EFILABEL" -l \\EFI\\$EFILABEL\\$OUTFILE
fi
# clean up temporary files
if [ -d "$WD" ]; then
# rm -R "$WD"
echo "Creating temporary working directory $WD"
echo "(removed on next boot)"
ls -lah "$WD"
fi
echo "done."
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment