{{ :supportukraine.gif|}} ====== GRUB: PXE-booting into specific OS on multiboot systems ====== {{en:tux.png |Hey, my name is "Tux"!}} **Did you ever wonder how you can boot automatically into a specific OS via PXE-boot on a multiboot system?** \\ \\ Hey, my name is "Tux" and in this tutorial I will show you an example on how to boot automatically into a specific OS via PXE-boot on a dualboot system based on //Linux// and //Windows//. ---- \\ ===== Start of tutorial ===== This tutorial is based on //x86_64// systems. \\ \\ The appliances/systems being used in this tutorial are: * DHCP server: //ISC DHCPv4// server service (Provided natively by //OPNsense 25.7.1_1-amd64//). * TFTP server: //Synology// TFTP server (Provided natively by //Synology DSM 7.2.2-72806 Update 4//). * Bootloader: //GRUB// 2.12 bootloader (Optional: Built on a //Ubuntu server// 24.04 VM. The VM is just needed temporarily to build a custom GRUB bootloader, after that, the VM can be deleted). * Dualboot computer: This is the target computer. In this example it has a custom //Linux// ([[https://buildroot.org|Buildroot]]) OS and //Windows 11// OS installed. Make sure //Secure boot// is disabled in BIOS/UEFI settings unless you plan to digitally sign the custom GRUB bootloader file (which is not part of this tutorial) we are going to generate in this tutorial or alternatively use an already signed GRUB bootloader out of an existing //Linux// installation (There are chances that it may not work because GRUB modules are missing, that's why we are building our own GRUB bootloader in this tutorial.). ==== Dualboot computer ==== === First thoughts === Typically, every existing OS brings its own dedicated bootloader partition with it. This is mostly done automatically when the OS gets installed. From a compatibility perspective in this tutorial it does not really matter whether the OSes are installed on the same physical disk, you can indeed have multiple bootloader partitions installed on the same physical disk. But it is strongly recommended to have every OS on its dedicated physical disk installed because chances are much better to not run into any unexpected issues and you will be much more flexible in the future. You have been warned! === Boot partition UUIDs === First of all, on your dualboot target computer, it is necessary to determine the UUIDs of the according boot partitions. My recommended way to do this is to use //Linux//. So, if one of your installed OSes is //Linux//, boot into //Linux// now. If not, you could alternatively create a temporary bootable //Linux// USB stick (e.g. //Debian//) and boot from the USB stick, which is not part of this tutorial. \\ Once on the //Linux// CLI, let's determine the according boot partitions by executing the following command, which lists up all the existing disk partitions and filters them for typical keywords indicating the boot partitions (e.g. ''EFI'', ''boot'', ''vfat''): Please be aware that anything in //Linux// is case sensitive and boot partition setups can vary between //Linux// distributions, e.g. boot partition keywords and where the boot partitions are mounted. The values being used in this tutorial are just a working example out of my personal setup, they may or may not be the same in your setup. Typical keywords could be: * EFI * efi * boot * Boot * BOOT * vfat * ... findmnt -o UUID,SOURCE,FSTYPE,TARGET,PARTLABEL | grep 'EFI\|boot' The output should be something similiar to the following: 264A-F71E /dev/nvme0n1p1 vfat |-/boot vfat D259-CAE5 /dev/nvme2n1p1 vfat | |-/media/NO_LABEL EFI system partition The output is split up into 5 rows (from left to right) which should help you to determine the according boot partitions: * ''UUID'': These are the values we are looking for. To make sure the listed UUIDs are really the according boot partitions, have a look on all the additional rows: * ''SOURCE'': Represents the physical hard drive(s) the partition is installed on. Check if it makes sense. * ''FSTYPE'': The partition's filesystem type, mostly ''vfat'' for boot partitions. * ''TARGET'': The partition's mount point. Check if it makes sense by searching for typical boot partition files or file structures by navigating through the filesystem, especially on //Linux// (e.g. ''/boot/EFI/bootx64.efi''). For //Windows//, the EFI boot partition with its bootloader file is always the same: ''/EFI/Microsoft/Boot/bootmgfw.efi'', so just look out for that. * ''PARTLABEL'': Shows the partition's label. Check if it makes sense. Now if the boot partitions are determined, write them down somewhere, they are needed on the following steps. Furthermore write down where the according ''.efi''-files are located at, these are the bootloader files needed for chainload configuration later. ==== TFTP server ==== In this tutorial the //Synology DSM//'s native TFTP server is being used. To set it up accordingly, do as follows: * Login to //Synology DSM//'s web interface. * Create a new shared folder called ''tftpboot'': \\ ''Control Panel'' → ''Shared Folder'' → ''Create'' → ''Create Shared Folder'' → On ''Name'' insert ''tftpboot'' → ''Next'' → ''Next'' → ''Next'' → ''Next'' → Leave default permissions, click ''Apply''. * Enable TFTP service: \\ ''Control Panel'' → ''File Services'' → ''Advanced'' → ''TFTP'' → Tick ''Enable TFTP service'' → Click on ''Select'' and select the according ''tftpboot'' shared folder → ''Select'' → ''Advanced Settings'' → ''Privileges setup'' → Set ''TFTP client permission:'' to ''Writeable'' → ''Save'' → ''Apply''. That's it. You have the TFTP server up an running and if you have the according SMB service enabled and set up accordingly, you can access it additionally via SMB protocol to transfer your files to later, which is not necessarily required but recommended. Don't forget to set the according firewall rules on all the firewalls involved on the according network chain (e.g. //OPNsense// firewall, //Synology//'s native firewall, ...). Please note that for this initial TFTP setup it is only necessary to let your dualboot computer access the TFTP server share via TFTP protocol (Default: Port ''69/UDP''), the DHCP server itself (e.g. //OPNsense//) does not need to access the TFTP server in any way, it just passes the information about the TFTP server via DHCP protocol to the dualboot computer. \\ \\ Also note, that for the TFTP share itself you don't need to set any additional share permissions. The TFTP protocol is very simple and primitive and has no real advanced security features (e.g. authentication), the //Synology// TFTP service handles the special TFTP access permissions itself. It is intended to be used for "simple" purposes so it is a good idea to keep it in an isolated environment from a (network) security perspective. ==== GRUB ==== === GRUB bootloader binary file === Now it is required to get the //GRUB// bootloader binary file which is the //GRUB// bootloader executable itself. You can either download the working one provided by me or build it by yourself: * Option 1: Download the GRUB bootloader binary file from here: \\ {{ :grubx64.zip |}} * Option 2: Build the GRUB bootloader binary by yourself: Fire up the //Linux// computer where you want to create the according //GRUB// bootloader binary file. In this tutorial an //Ubuntu server// VM is being used for this, but could be any common //Linux// distribution or also the //Linux// distribution installed on your dualboot computer. For //Ubuntu//, install the required dependencies by executing the following CLI command with higher privileges:sudo apt-get install grub-common grub-efi-amd64-bin grub-efi-ia32-binTo create the GRUB bootloader binary including the required modules, execute (Don't change anything!):cd ~ && sudo grub-mkimage -o grubx64.efi -p "(tftp)/" -O x86_64-efi tftp configfile linux normal chain boot echo search search_fs_uuid efinet efi_gop efi_uga gfxterm part_gpt ntfs net fat part_msdos ext2 test help catThe according ''grubx64.efi'' was being created here: \\ ''~/grubx64.efi'' \\ Move that file over to the TFTP server share. === GRUB bootloader config files === On execution the GRUB bootloader binary looks automatically for a config file called ''grub.cfg''. In addition to that file we will create a flag file, which only holds the information about which OS to boot from (e.g. ''Linux'' or ''Windows''). Generally, the GRUB bootloader handles ''.cfg''-files as config files, meaning the boot process reads and executes ''.cfg''-files like parts of a script in its own scripting language. So as long as ''.cgf'' file extensions are being used, those script files can be split up into multiple files. == .cfg flag file == On the TFTP server share, create a file called ''boot_target.cfg'', with the following content (We just set //Windows// to default for now, you'll be able to change this dynamically later with your custom setup as mentioned on the end of this tutorial.): set boot_target=windows == .cfg main file == In addition to that file, create another file called ''grub.cfg'' on the TFTP server share, with the following content (Replace the according placeholders (e.g. '''', ...) with your custom values: For the following config file, you always need to think from //GRUB//'s early stage perspective when it comes into path configurations. For example, it is important to understand that for the //GRUB// chainloader binary file's path on //Linux// boot partitions, it is required to set the path to ''/EFI/boot/bootx64.efi'' instead of ''/boot/EFI/boot/bootx64.efi'', which is a relative path instead of the absolute full path. set timeout=2 source (tftp)/boot_target.cfg if [ "$boot_target" = "windows" ] ; then menuentry "Windows" { insmod part_gpt search --no-floppy --fs-uuid --set=root chainloader .efi } elif [ "$boot_target" = "linux" ] ; then menuentry "Linux" { insmod part_gpt search --no-floppy --fs-uuid --set=root chainloader .efi } fi The following is my productive working example: set timeout=2 source (tftp)/boot_target.cfg if [ "$boot_target" = "windows" ] ; then menuentry "Windows" { insmod part_gpt search --no-floppy --fs-uuid --set=root D259-CAE5 chainloader /EFI/Microsoft/Boot/bootmgfw.efi } elif [ "$boot_target" = "linux" ] ; then menuentry "Linux" { insmod part_gpt search --no-floppy --fs-uuid --set=root 264A-F71E chainloader /EFI/boot/bootx64.efi } fi === UEFI/BIOS settings === Now make sure that PXE-boot is enabled on your BIOS/UEFI settings. The way to enable PXE-boot mostly differs by any mainboard type, so this will not be covered in this tutorial. Just look out for a setting like "Network boot" or "PXE-boot". When enabled, you typically can choose a network interface to boot from. If your have multiple ones, take the one that you want to boot from (mostly dependent on your network setup). Once enabled, set PXE-boot option as the first one in the BIOS/UEFI boot order settings. ==== DHCP server ==== You will need a PXE-boot capable DHCP server, which basically means, it needs to be capable of providing network booting information to PXE-boot client computers (e.g. your dualboot computer). In this tutorial the according DHCP server is provided natively by //OPNsense// router/firewall. //OPNsense// does provide multiple DHCP server services, in this tutorial the //ISC DHCPv4 server// service option is being used. It is assumed you have already set up the basic DHCP service, this tutorial only covers on how to enable network boot to make PXE-boot possible. On the //OPNsense// web interface GUI navigate to (Replace '''' with the logical network interface or VLAN interface your dualboot computer's network interface which should boot off of PXE): \\ ''Services'' → ''ISC DHCPv4'' → '''' → ''Network booting'' → ''Advanced'' → Tick ''Enable network booting'' → On ''Set next-server IP'' input field insert the TFTP server's IP address (e.g. ''192.168.1.100'') → On ''Set x64 UEFI/EBC (64-bit) filename'' input field insert the according GRUB binary's filename (e.g. ''grubx64.efi'') → ''Save'' \\ \\ That's it for the basic setup! \\ Everything is set up to boot via PXE into a specific OS on a dualboot computer. ==== Remote system(s) ==== Now you may still wonder on how to set the according options to decide which OS to boot from. Well, for that there are many possibilities, that's why it does not make much sense to cover more here and this is why this tutorial is abstracted from here on. \\ The important thing to understand is: The main file where GRUB is looking into on PXE-boot stage is the ''boot_target.cfg'' flag file. We have initially set it up by default to boot into the //Windows// boot partition. If you want to switch booting between boot partitions dynamically, the according value has to be changed inside the file (e.g. from ''set boot_target=windows'' to ''set boot_target=linux'' and vice versa). This typically is done via a remote computer that can access the TFTP server share either via TFTP, SMB, NFS or SPC/SSH protocol. \\ In my case I did this via a [[https://www.home-assistant.io/|Home Assistant OS]] VM and a Debian VM as follows: \\ \\ In case of an according event (e.g. a physical button press on a smart device), the Home Assistant VM connects to the Debian VM via SSH and updates the ''boot_target.cfg'' flag file's content (the file's full path on the Debian VM is: ''/home/user/tftpboot/boot_target.cfg''). Then, the Debian VM sends/overwrites that file via TFTP protocol to the //Synology// TFTP server share. All this is done via a single shell command, here's an example of the two according //Home Assistant// [[https://www.home-assistant.io/integrations/shell_command/|Shell Command integration]] commands I use to switch between //Windows// and //Linux// boot partitions: shell_command: # desktop01_tftpboot: pc1_tftpboot_windows: ssh -i /config/.ssh/id_rsa -o 'StrictHostKeyChecking=no' user@debian01.local 'echo "set boot_target=windows" > /home/user/tftpboot/boot_target.cfg && cd /home/user/tftpboot && tftp synology01.local -c put boot_target.cfg boot_target.cfg'> /dev/null 2>&1 & pc1_tftpboot_linux: ssh -i /config/.ssh/id_rsa -o 'StrictHostKeyChecking=no' user@debian01.local 'echo "set boot_target=linux" > /home/user/tftpboot/boot_target.cfg && cd /home/user/tftpboot && tftp synology01.local -c put boot_target.cfg boot_target.cfg'> /dev/null 2>&1 & By using this two commands, it is possible to create automations in //Home Assistant// depending on how it is required to use them. In my case, this works like a charm! There's a ton of possibilities on how you can change the content of the ''boot_target.cfg'' flag file remotely, you'll get the idea! Just keep in mind to set all the according required firewall rules between all firewalls involved in the according network chain, depending on the network services/protocols you want to use. Also, if you are changing the content of the ''boot_target.cfg'' flag file remotely only via SSH protocol and not via TFTP protocol, you may also set the according permissions to ''Read only'' on the //Synology// TFTP server service settings (Remember, we set it to ''Writeable'' before.). ===== End of tutorial ===== \\ \\ Appreciate my work? \\ [[https://www.buymeacoffee.com/fabioU|Buy me a coffee]] {{:buymeacoffee.png|}} or [[https://www.paypal.com/donate/?hosted_button_id=TH8Q3NTJCAJBA|PayPal]] {{:paypal.png|}} \\ \\ {{htmlmetatags>metatag-robots=()}}