1 # This script is called by ./xen-dom0.nix to create the Xen boot entries.
2 # shellcheck shell=bash
4 # Handle input argument and exit if the flag is invalid. See virtualisation.xen.efi.bootBuilderVerbosity below.
5 [[ $# -ne 1 ]] && echo -e "\e[1;31merror:\e[0m xenBootBuilder must be called with exactly one verbosity argument. See the \e[1;34mvirtualisation.xen.efi.bootBuilderVerbosity\e[0m option." && exit 1
8 "default" |
"info") echo -n "Installing Xen Hypervisor boot entries..." ;;
9 "debug") echo -e "\e[1;34mxenBootBuilder:\e[0m called with the '$1' flag" ;;
11 echo -e "\e[1;31merror:\e[0m xenBootBuilder was called with an invalid argument. See the \e[1;34mvirtualisation.xen.efi.bootBuilderVerbosity\e[0m option."
16 # Get the current Xen generations and store them in an array. This will be used
17 # for displaying the diff later, if xenBootBuilder was called with `info`.
18 # We also delete the current Xen entries here, as they'll be rebuilt later if
19 # the corresponding NixOS generation still exists.
20 mapfile
-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')
21 if [ "$1" = "debug" ]; then
22 if ((${#preGenerations[@]} == 0)); then
23 echo -e "\e[1;34mxenBootBuilder:\e[0m no previous Xen entries."
25 echo -e "\e[1;34mxenBootBuilder:\e[0m deleting the following stale xen entries:" && for debugGen
in "${preGenerations[@]}"; do echo " - $debugGen"; done
29 # Cleanup all Xen entries.
30 rm -f "$efiMountPoint"/{loader
/entries
/xen-
*.conf
,efi
/nixos
/xen-
*.efi
}
32 # Main array for storing which generations exist in $efiMountPoint after
33 # systemd-boot-builder.py builds the main entries.
34 mapfile
-t gens
< <(find "$efiMountPoint"/loader
/entries
-type f
-name 'nixos-*.conf' |
sort -V)
35 [ "$1" = "debug" ] && echo -e "\e[1;34mxenBootBuilder:\e[0m found the following NixOS boot entries:" && for debugGen
in "${gens[@]}"; do echo " - $debugGen"; done
37 # This is the main loop that installs the Xen entries.
38 for gen
in "${gens[@]}"; do
40 # We discover the path to Bootspec through the init attribute in the entries,
41 # as it is equivalent to $toplevel/init.
42 bootspecFile
="$(sed -nr 's/^options init=(.*)\/init.*$/\1/p' "$gen")/boot.json"
43 [ "$1" = "debug" ] && echo -e "\e[1;34mxenBootBuilder:\e[0m processing bootspec file $bootspecFile"
45 # We do nothing if the Bootspec for the current $gen does not contain the Xen
46 # extension, which is added as a configuration attribute below.
47 if grep -sq '"org.xenproject.bootspec.v1"' "$bootspecFile"; then
48 [ "$1" = "debug" ] && echo -e " \e[1;32msuccess:\e[0m found Xen entries in $gen."
50 # TODO: Support DeviceTree booting. Xen has some special handling for DeviceTree
51 # attributes, which will need to be translated in a boot script similar to this
52 # one. Having a DeviceTree entry is rare, and it is not always required for a
53 # successful boot, so we don't fail here, only warn with `debug`.
54 if grep -sq '"devicetree"' "$bootspecFile"; then
55 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."
57 [ "$1" = "debug" ] && echo -e "\e[1;34mxenBootBuilder:\e[0m no DeviceTree entries found in $gen."
60 # Prepare required attributes for `xen.cfg/xen.conf`. It inherits the name of
61 # the corresponding nixos generation, substituting `nixos` with `xen`:
62 # `xen-$profile-generation-$number-specialisation-$specialisation.{cfg,conf}`
63 xenGen
=$
(echo "$gen" |
sed 's_/loader/entries/nixos_/loader/entries/xen_g;s_^.*/xen_xen_g;s_.conf$__g')
64 bootParams
=$
(jq
-re '."org.xenproject.bootspec.v1".xenParams | join(" ")' "$bootspecFile")
65 kernel
=$
(jq
-re '."org.nixos.bootspec.v1".kernel | sub("^/nix/store/"; "") | sub("/bzImage"; "-bzImage.efi")' "$bootspecFile")
66 kernelParams
=$
(jq
-re '."org.nixos.bootspec.v1".kernelParams | join(" ")' "$bootspecFile")
67 initrd
=$
(jq
-re '."org.nixos.bootspec.v1".initrd | sub("^/nix/store/"; "") | sub("/initrd"; "-initrd.efi")' "$bootspecFile")
68 init
=$
(jq
-re '."org.nixos.bootspec.v1".init' "$bootspecFile")
69 title
=$
(sed -nr 's/^title (.*)$/\1/p' "$gen")
70 version
=$
(sed -nr 's/^version (.*)$/\1/p' "$gen")
71 machineID
=$
(sed -nr 's/^machine-id (.*)$/\1/p' "$gen")
72 sortKey
=$
(sed -nr 's/^sort-key (.*)$/\1/p' "$gen")
74 # Write `xen.cfg` to a temporary location prior to UKI creation.
76 [ "$1" = "debug" ] && echo -ne "\e[1;34mxenBootBuilder:\e[0m writing $xenGen.cfg to temporary file..."
77 cat > "$tmpCfg" << EOF
83 kernel=$kernel init=$init $kernelParams
86 [ "$1" = "debug" ] && echo -e "done."
88 # Create Xen UKI for $generation. Most of this is lifted from
89 # https://xenbits.xenproject.org/docs/unstable/misc/efi.html.
90 [ "$1" = "debug" ] && echo -e "\e[1;34mxenBootBuilder:\e[0m making Xen UKI..."
91 xenEfi
=$
(jq
-re '."org.xenproject.bootspec.v1".xen' "$bootspecFile")
92 padding
=$
(objdump
--header --section=".pad" "$xenEfi" |
awk '/\.pad/ { printf("0x%016x\n", strtonum("0x"$3) + strtonum("0x"$4))};')
93 [ "$1" = "debug" ] && echo " - padding: $padding"
95 --add-section .config
="$tmpCfg" \
96 --change-section-vma .config
="$padding" \
98 "$efiMountPoint"/EFI
/nixos
/"$xenGen".efi
99 [ "$1" = "debug" ] && echo -e " - \e[1;32msuccessfully built\e[0m $xenGen.efi"
103 [ "$1" = "debug" ] && echo -ne "\e[1;34mxenBootBuilder:\e[0m writing $xenGen.conf to EFI System Partition..."
104 cat > "$efiMountPoint"/loader
/entries
/"$xenGen".conf
<< EOF
105 title $title (with Xen Hypervisor)
107 efi /EFI/nixos/$xenGen.efi
108 machine-id $machineID
111 [ "$1" = "debug" ] && echo -e "done."
113 # Sometimes, garbage collection weirdness causes a generation to still exist in
114 # the loader entries, but its Bootspec file was deleted. We consider such a
115 # generation to be invalid, but we don't write extra code to handle this
116 # situation, as supressing grep's error messages above is quite enough, and the
117 # error message below is still technically correct, as no Xen can be found in
118 # something that does not exist.
120 [ "$1" = "debug" ] && echo -e " \e[1;33mwarning:\e[0m \e[1;31mno Xen found\e[0m in $gen."
124 # Counterpart to the preGenerations array above. We use it to diff the
125 # generations created/deleted when callled with the `info` argument.
126 mapfile
-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')
128 # In the event the script does nothing, guide the user to debug, as it'll only
129 # ever run when Xen is enabled, and it makes no sense to enable Xen and not have
130 # any hypervisor boot entries.
131 if ((${#postGenerations[@]} == 0)); then
133 "default" |
"info") echo "none found." && echo -e "If you believe this is an error, set the \e[1;34mvirtualisation.xen.efi.bootBuilderVerbosity\e[0m option to \e[1;34m\"debug\"\e[0m and rebuild to print debug logs." ;;
134 "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 entry." ;;
137 # If the script is successful, change the default boot, say "done.", write a
138 # diff, or print the total files written, depending on the argument this script
139 # was called with. We use some dumb dependencies here, like `diff` or `bat` for
140 # colourisation, but they're only included with the `info` argument.
142 # It's also fine to change the default here, as this runs after the
143 # `systemd-boot-builder.py` script, which overwrites the file, and this script
144 # does not run after an user disables the Xen module.
146 sed --in-place 's/^default nixos-/default xen-/g' "$efiMountPoint"/loader
/loader.conf
148 "default" |
"info") echo "done." ;;
149 "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 ;;
151 if [ "$1" = "info" ]; then
152 if [[ ${#preGenerations[@]} == "${#postGenerations[@]}" ]]; then
153 echo -e "\e[1;33mNo Change:\e[0m Xen Hypervisor boot entries were refreshed, but their contents are identical."
155 echo -e "\e[1;32mSuccess:\e[0m Changed the following boot entries:"
156 # We briefly unset errexit and pipefail here, as GNU diff has no option to not fail when files differ.
159 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