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.
Note
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!
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
):
Note
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.
In this tutorial the Synology DSM's native TFTP server is being used. To set it up accordingly, do as follows:
tftpboot
: Control Panel
→ Shared Folder
→ Create
→ Create Shared Folder
→ On Name
insert tftpboot
→ Next
→ Next
→ Next
→ Next
→ Leave default permissions, click Apply
.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.
Note
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. 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:
sudo apt-get install grub-common grub-efi-amd64-bin grub-efi-ia32-bin
To 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 cat
The according grubx64.efi
was being created here:
~/grubx64.efi
Move that file over to the TFTP server share.
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
).
Sidenote
.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.
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
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. <windowsBootPartitionUUID>
, …) with your custom values:
Note
/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 <windowsBootPartitionUUID> chainloader <windowsBootLoader>.efi } elif [ "$boot_target" = "linux" ] ; then menuentry "Linux" { insmod part_gpt search --no-floppy --fs-uuid --set=root <linuxBootPartitionUUID> chainloader <linuxBootLoader>.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
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.
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 <NetworkInterface>
with the logical network interface or VLAN interface your dualboot computer's network interface which should boot off of PXE):
Services
→ ISC DHCPv4
→ <NetworkInterface>
→ 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.
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 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 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.).
Appreciate my work?
Buy me a coffee or PayPal