Reboot to Windows

My PC dual-boots Windows and Linux, but I use Linux ~95% of the time. Since I like my PC to boot as quickly as possible, I’ve settled on the following setup:

If you’re curious how any of this works, read on!

Prerequisites

Before I dive into the details, please note that these solutions require that your machine:

  1. boots using UEFI,
  2. has separate EFI boot entries for Linux and Windows,
  3. doesn’t use a fancy boot menu that remembers your previous selection.

The first two criteria are easy to verify from Linux: run efibootmgr, and check that there are entries for both Linux and Windows.

$ efibootmgr
BootCurrent: 0000
Timeout: 0 seconds
BootOrder: 0000,0002,0001,0003,0004,0005,0006
Boot0000* Arch Linux	HD(2,GPT,47be9262-66d7-7743-be45-99e62de[…]
Boot0001* UEFI OS	HD(1,GPT,dd9ddf9f-afcd-4776-9d72-db7cc81[…]
Boot0002* Windows Boot Manager	HD(2,GPT,7dd2e191-7af4-4540-a427[…]
[…]

You can see that, for my machine, 0000 is Linux and 0002 is Windows, and 0000 is the one that will be booted first.

The third criterion is important because these solutions assume that the machine will boot directly into Linux by default. If your Linux installation came with an interactive boot menu (such as GRUB), you can likely reconfigure that boot menu to automatically default to Linux.

BootNext

UEFI supports overriding the boot order for the next boot only by setting the BootNext EFI variable. For example, if I want my machine to boot into Windows next, I can run:

$ sudo efibootmgr --bootnext 0002
BootNext: 0002
BootCurrent: 0000
[…remaining output same as before]

reboot-to-windows script

In the above sections, we saw that my machine’s boot ID for Windows is 0002. I didn’t want to hard-code this ID (since it could change if I re-install in the future), so I wrote a script to find it and reboot the machine.

/home/colin/bin/reboot-to-windows
#!/bin/bash

if output=$(efibootmgr | grep Windows) && [[ $output =~ Boot([[:xdigit:]]{4}) ]]; then windows_id=${BASH_REMATCH[1]} else echo >&2 "No EFI boot entry found matching 'Windows'" exit 1 fi
if ! sudo efibootmgr --bootnext "$windows_id"; then echo >&2 "Failed to set next boot; aborting" exit 1 fi
systemctl --quiet --no-block reboot

sudo configuration

efibootmgr needs elevated privileges to modify EFI variables. To avoid being prompted for a password, I added the following to /etc/sudoers.

/etc/sudoers (excerpt)
%wheel ALL=(ALL:ALL) NOPASSWD: /usr/bin/efibootmgr ^--bootnext [[:xdigit:]]+$

This allows any member of the wheel group to run this specific command without providing a password. In case you’re not familiar with configuring sudo, note that you should always use the visudo command to do so.

Login manager configuration

I use greetd as my login manager, but this should work for any login manager that supports Wayland sessions.

/usr/share/wayland-sessions/reboot-to-windows.desktop
[Desktop Entry]
Name=Reboot to Windows
Exec=/home/colin/bin/reboot-to-windows
Type=Application

Automatic reboot to Windows

I achieve this with a systemd service and timer.

/etc/systemd/system/reboot-to-windows.service
[Service]
Type=oneshot
ExecCondition=bash -c "! loginctl list-sessions --json=short | jq -e '.[]|select(.seat != null && .user != \"greeter\")'"
ExecStart=/home/colin/bin/reboot-to-windows

The interesting part of this service is the ExecCondition, which uses jq to parse the JSON from loginctl and look for any active sessions for users other than greeter (the user that my login manager runs as). The -e flag makes jq’s exit code reflect whether a session was found, and the ! at the start of the pipeline negates the result so that the condition fails if any session was found (meaning the reboot should not proceed).

The corresponding timer triggers this service to start two minutes after the machine boots.

/etc/systemd/system/reboot-to-windows.timer
[Timer]
OnActiveSec=2m

[Install] WantedBy=multi-user.target

Remember to enable the timer with systemctl enable reboot-to-windows.timer.