at master 11 kB view raw
1# This script is called by ./xen-dom0.nix to create the Xen boot entries. 2# shellcheck shell=bash 3 4export LC_ALL=C 5 6# Handle input argument and exit if the flag is invalid. See virtualisation.xen.boot.builderVerbosity below. 7[[ $# -ne 1 ]] && echo -e "\e[1;31merror:\e[0m xenBootBuilder must be called with exactly one verbosity argument. See the \e[1;34mvirtualisation.xen.boot.builderVerbosity\e[0m option." && exit 1 8 9case "$1" in 10 "quiet") true ;; 11 "default" | "info") echo -n "Installing Xen Project Hypervisor boot entries..." ;; 12 "debug") echo -e "\e[1;34mxenBootBuilder:\e[0m called with the '$1' flag" ;; 13 *) 14 echo -e "\e[1;31merror:\e[0m xenBootBuilder was called with an invalid argument. See the \e[1;34mvirtualisation.xen.boot.builderVerbosity\e[0m option." 15 exit 2 16 ;; 17esac 18 19# Get the current Xen generations and store them in an array. This will be used 20# for displaying the diff later, if xenBootBuilder was called with `info`. 21# We also delete the current Xen entries here, as they'll be rebuilt later if 22# the corresponding NixOS generation still exists. 23mapfile -t preGenerations < <(find "$efiMountPoint"/loader/entries -type f -name 'xen-*.conf' | sort -V | sed 's_/loader/entries/nixos_/loader/entries/xen_g;s_^.*/xen_xen_g;s_.conf$__g') 24if [ "$1" = "debug" ]; then 25 if ((${#preGenerations[@]} == 0)); then 26 echo -e "\e[1;34mxenBootBuilder:\e[0m no previous Xen entries." 27 else 28 echo -e "\e[1;34mxenBootBuilder:\e[0m deleting the following stale xen entries:" && for debugGen in "${preGenerations[@]}"; do echo " - $debugGen"; done 29 fi 30fi 31 32# Cleanup all Xen entries. 33rm -f "$efiMountPoint"/{loader/entries/xen-*.conf,efi/nixos/xen-*.efi} 34 35# Main array for storing which generations exist in $efiMountPoint after 36# systemd-boot-builder.py builds the main entries. 37mapfile -t gens < <(find "$efiMountPoint"/loader/entries -type f -name 'nixos-*.conf' | sort -V) 38[ "$1" = "debug" ] && echo -e "\e[1;34mxenBootBuilder:\e[0m found the following NixOS boot entries:" && for debugGen in "${gens[@]}"; do echo " - $debugGen"; done 39 40# This is the main loop that installs the Xen entries. 41for gen in "${gens[@]}"; do 42 43 # We discover the path to Bootspec through the init attribute in the entries, 44 # as it is equivalent to $toplevel/init. 45 bootspecFile="$(sed -nr 's/^options init=(.*)\/init.*$/\1/p' "$gen")/boot.json" 46 [ "$1" = "debug" ] && echo -e "\e[1;34mxenBootBuilder:\e[0m processing bootspec file $bootspecFile" 47 48 # We do nothing if the Bootspec for the current $gen does not contain the Xen 49 # extension, which is added as a configuration attribute below. 50 # We determine this by checking for the v1 or v2 bootspec extension, 51 # and setting the appropriate attributes based on version 52 xenSpecVer="" 53 xenParamVar="" 54 xenEfiPath="" 55 if grep -sq '"org.xenproject.bootspec.v1"' "$bootspecFile"; then 56 xenSpecVer="v1" 57 xenParamVar="xenParams" 58 xenEfiPath="xen" 59 fi 60 # We prefer the v2 extension, so if both are present somehow, 61 # we will use the v2 attributes 62 if grep -sq '"org.xenproject.bootspec.v2"' "$bootspecFile"; then 63 xenSpecVer="v2" 64 xenParamVar="params" 65 xenEfiPath="efiPath" 66 fi 67 # Check for a valid Xen spec being detected 68 if [ -n "$xenSpecVer" ]; then 69 [ "$1" = "debug" ] && echo -e " \e[1;32msuccess:\e[0m found $xenSpecVer Xen entries in $gen." 70 71 # TODO: Support DeviceTree booting. Xen has some special handling for DeviceTree 72 # attributes, which will need to be translated in a boot script similar to this 73 # one. Having a DeviceTree entry is rare, and it is not always required for a 74 # successful boot, so we don't fail here, only warn with `debug`. 75 if grep -sq '"devicetree"' "$bootspecFile"; then 76 echo -e "\n\e[1;33mwarning:\e[0m $gen has a \e[1;34morg.nixos.systemd-boot.devicetree\e[0m Bootspec entry. Xen currently does not support DeviceTree, so this value will be ignored in the Xen boot entries, which may cause them to \e[1;31mfail to boot\e[0m." 77 else 78 [ "$1" = "debug" ] && echo -e "\e[1;34mxenBootBuilder:\e[0m no DeviceTree entries found in $gen." 79 fi 80 81 # Prepare required attributes for `xen.cfg/xen.conf`. It inherits the name of 82 # the corresponding nixos generation, substituting `nixos` with `xen`: 83 # `xen-$profile-generation-$number-specialisation-$specialisation.{cfg,conf}` 84 xenGen=$(echo "$gen" | sed 's_/loader/entries/nixos_/loader/entries/xen_g;s_^.*/xen_xen_g;s_.conf$__g') 85 bootParams=$(jq -re ".\"org.xenproject.bootspec.$xenSpecVer\".$xenParamVar | join(\" \")" "$bootspecFile") 86 kernel=$(jq -re '."org.nixos.bootspec.v1".kernel | sub("^/nix/store/"; "") | sub("/bzImage"; "-bzImage.efi")' "$bootspecFile") 87 kernelParams=$(jq -re '."org.nixos.bootspec.v1".kernelParams | join(" ")' "$bootspecFile") 88 initrd=$(jq -re '."org.nixos.bootspec.v1".initrd | sub("^/nix/store/"; "") | sub("/initrd"; "-initrd.efi")' "$bootspecFile") 89 init=$(jq -re '."org.nixos.bootspec.v1".init' "$bootspecFile") 90 title=$(sed -nr 's/^title (.*)$/\1/p' "$gen") 91 version=$(sed -nr 's/^version (.*)$/\1/p' "$gen") 92 machineID=$(sed -nr 's/^machine-id (.*)$/\1/p' "$gen") 93 sortKey=$(sed -nr 's/^sort-key (.*)$/\1/p' "$gen") 94 95 # Write `xen.cfg` to a temporary location prior to UKI creation. 96 tmpCfg=$(mktemp) 97 [ "$1" = "debug" ] && echo -ne "\e[1;34mxenBootBuilder:\e[0m writing $xenGen.cfg to temporary file..." 98 cat > "$tmpCfg" << EOF 99[global] 100default=xen 101 102[xen] 103options=$bootParams 104kernel=$kernel init=$init $kernelParams 105ramdisk=$initrd 106EOF 107 [ "$1" = "debug" ] && echo -e "done." 108 109 # Create Xen UKI for $generation. Most of this is lifted from 110 # https://xenbits.xenproject.org/docs/unstable/misc/efi.html. 111 [ "$1" = "debug" ] && echo -e "\e[1;34mxenBootBuilder:\e[0m making Xen UKI..." 112 xenEfi=$(jq -re ".\"org.xenproject.bootspec.$xenSpecVer\".$xenEfiPath" "$bootspecFile") 113 finalSection=$(objdump --header --wide "$xenEfi" | tail -n +6 | sort --key="4,4" | tail -n 1 | grep -Eo '\.[a-z]*') 114 padding=$(objdump --header --section="$finalSection" "$xenEfi" | awk -v section="$finalSection" '$0 ~ section { printf("0x%016x\n", and(strtonum("0x"$3) + strtonum("0x"$4) + 0xfff, compl(0xfff)))};') 115 [ "$1" = "debug" ] && echo " - padding: $padding" 116 objcopy \ 117 --add-section .config="$tmpCfg" \ 118 --change-section-vma .config="$padding" \ 119 "$xenEfi" \ 120 "$efiMountPoint"/EFI/nixos/"$xenGen".efi 121 [ "$1" = "debug" ] && echo -e " - \e[1;32msuccessfully built\e[0m $xenGen.efi" 122 rm -f "$tmpCfg" 123 124 # Write `xen.conf`. 125 [ "$1" = "debug" ] && echo -ne "\e[1;34mxenBootBuilder:\e[0m writing $xenGen.conf to EFI System Partition..." 126 cat > "$efiMountPoint"/loader/entries/"$xenGen".conf << EOF 127title $title (with Xen Hypervisor) 128version $version 129efi /EFI/nixos/$xenGen.efi 130machine-id $machineID 131sort-key $sortKey 132EOF 133 [ "$1" = "debug" ] && echo -e "done." 134 135 # Sometimes, garbage collection weirdness causes a generation to still exist in 136 # the loader entries, but its Bootspec file was deleted. We consider such a 137 # generation to be invalid, but we don't write extra code to handle this 138 # situation, as supressing grep's error messages above is quite enough, and the 139 # error message below is still technically correct, as no Xen can be found in 140 # something that does not exist. 141 else 142 [ "$1" = "debug" ] && echo -e " \e[1;33mwarning:\e[0m \e[1;31mno Xen found\e[0m in $gen." 143 fi 144done 145 146# Counterpart to the preGenerations array above. We use it to diff the 147# generations created/deleted when callled with the `info` argument. 148mapfile -t postGenerations < <(find "$efiMountPoint"/loader/entries -type f -name 'xen-*.conf' | sort -V | sed 's_/loader/entries/nixos_/loader/entries/xen_g;s_^.*/xen_xen_g;s_.conf$__g') 149 150# In the event the script does nothing, guide the user to debug, as it'll only 151# ever run when Xen is enabled, and it makes no sense to enable Xen and not have 152# any hypervisor boot entries. 153if ((${#postGenerations[@]} == 0)); then 154 case "$1" in 155 "default" | "info") echo "none found." && echo -e "If you believe this is an error, set the \e[1;34mvirtualisation.xen.boot.builderVerbosity\e[0m option to \e[1;34m\"debug\"\e[0m and rebuild to print debug logs." ;; 156 "debug") echo -e "\e[1;34mxenBootBuilder:\e[0m wrote \e[1;31mno generations\e[0m. Most likely, there were no generations with a valid \e[1;34morg.xenproject.bootspec.v1\e[0m or \e[1;34morg.xenproject.bootspec.v2\e[0m entry." ;; 157 esac 158 159# If the script is successful, change the default boot, say "done.", write a 160# diff, or print the total files written, depending on the argument this script 161# was called with. We use some dumb dependencies here, like `diff` or `bat` for 162# colourisation, but they're only included with the `info` argument. 163# 164# It's also fine to change the default here, as this runs after the 165# `systemd-boot-builder.py` script, which overwrites the file, and this script 166# does not run after an user disables the Xen module. 167else 168 sed --in-place 's/^default nixos-/default xen-/g' "$efiMountPoint"/loader/loader.conf 169 case "$1" in 170 "default" | "info") echo "done." ;; 171 "debug") echo -e "\e[1;34mxenBootBuilder:\e[0m \e[1;32msuccessfully wrote\e[0m the following generations:" && for debugGen in "${postGenerations[@]}"; do echo " - $debugGen"; done ;; 172 esac 173 if [ "$1" = "info" ]; then 174 if [[ ${#preGenerations[@]} == "${#postGenerations[@]}" ]]; then 175 echo -e "\e[1;33mNo Change:\e[0m Xen Project Hypervisor boot entries were refreshed, but their contents are identical." 176 else 177 echo -e "\e[1;32mSuccess:\e[0m Changed the following boot entries:" 178 # We briefly unset errexit and pipefail here, as GNU diff has no option to not fail when files differ. 179 set +o errexit 180 set +o pipefail 181 diff <(echo "${preGenerations[*]}" | tr ' ' '\n') <(echo "${postGenerations[*]}" | tr ' ' '\n') -U 0 | grep --invert-match --extended-regexp '^(@@|---|\+\+\+).*' | sed '1{/^-$/d}' | bat --language diff --theme ansi --paging=never --plain 182 true 183 set -o errexit 184 set -o pipefail 185 fi 186 fi 187fi