--- /dev/null
+.\" Copyright 2016 Roderick W. Smith (rodsmith@rodsbooks.com)
+.\" May be distributed under the GNU Free Documentation License version 1.3 or
+any later version
+.TH "REFIND-MKDEFAULT" "8" "0.10.3" "Roderick W. Smith" "rEFInd Manual"
+.SH "NAME"
+refind-mkdefault \- Set rEFInd as the default EFI boot option
+.SH "SYNOPSIS"
+.BI "refind-mkdefault "
+[ \-L|\-\-label <name> ]
+
+.SH "DESCRIPTION"
+
+EFI booting normally relies on boot manager entries stored in NVRAM, which
+describe the locations of EFI boot programs and the order in which the
+firmware will attempt to launch them. In Linux, these entries can be
+created, deleted, and manipulated with the \fIefibootmgr\fR utility.
+
+Many OSes and Linux packages assume that they should control the boot
+process, and so both create NVRAM boot entries for themselves and set these
+entries first in the boot order. If you intend rEFInd to control the boot
+process, though, such changes are undesirable and require adjustment via
+\fIefibootmgr\fR. Such adjustments are annoying to make and can be
+intimidating to non-experts.
+
+The \fIrefind-mkdefault\fR script simplifies matters: Running this script
+with no options sets rEFInd as the default boot program. The details of what
+the script does depends on the current state of the boot order list and
+existing boot entries:
+
+.TP
+.B *
+If a rEFInd entry already exists in the boot order and is already first
+in the list, no changes are made.
+
+.TP
+.B *
+If a rEFInd entry already exists in the boot order but is not first in
+the list, that entry is moved to the first position in the boot order.
+
+.TP
+.B *
+If more than one rEFInd entry exists in the boot order,
+\fIrefind-mkdefault\fR moves the one that comes earliest to the front of the
+boot order list.
+
+.TP
+.B *
+If no rEFInd entry exists in the boot order but a rEFInd boot entry
+can be found in the list of \fBBoot####\fR entries, it is added to the boot
+order and placed at the front of the list.
+
+.TP
+.B *
+If multiple rEFInd boot entries exist but none is in the boot order, all the
+entries are added to the boot order, but which one is first is uncontrolled.
+
+.PP
+
+A rEFInd entry is defined as one that contains the string \fBrefind\fR
+(case-insensitive). This string could exist in the description or in the
+filename. The string used to define the rEFInd entry can be changed via the
+\fI\-\-label\fR (\fI\-L\fR) option.
+
+The intent is that \fIrefind-mkdefault\fR can be called after booting via
+GRUB or some other means to restore rEFInd as the default boot program. It
+can also be placed in a startup and/or shutdown script to restore rEFInd to
+its default position automatically. Because it does not re-write the boot
+order if rEFInd is listed as the first boot entry, this practice should be
+low in risk.
+
+.SH "OPTIONS"
+
+.TP
+.B \-L | \-\-label \fI<name>\fR
+Instead of searching for the string \fBrefind\fR in \fIefibootmgr\fR output
+as a way to identify rEFInd, search for the string \fBname\fR.
+
+.SH "LIMITATIONS"
+
+.TP
+.B *
+\fIrefind-mkdefault\fR does not work when booted in BIOS mode (including
+via a Compatibility Support Module, or CSM, on an EFI-based computer).
+Similarly, it does not work if \fIefibootmgr\fR is not installed or fails
+to work for any reason.
+
+.TP
+.B *
+The script uses a very simple algorithm to determine what to move to
+the start of the boot order list. This algorithm may fail if the system
+has redundant or non-functional rEFInd boot entries or if those entries
+are not named in an expected fashion. Cleaning up the boot entries by
+manual use of \fIefibootmgr\fR may be necessary in such cases.
+
+.SH "AUTHORS"
+Primary author: Roderick W. Smith (rodsmith@rodsbooks.com)
+
+.SH "SEE ALSO"
+\fBmvrefind (8)\fR,
+\fBmkrlconf (8)\fR,
+\fBrefind-install (8)\fR,
+\fBefibootmgr (8)\fR
+
+\fIhttp://www.rodsbooks.com/refind/\fR
+
+.SH "AVAILABILITY"
+The \fBrefind-mkdefault\fR command is part of the \fIrEFInd\fR package and is
+available from Roderick W. Smith.
--- /dev/null
+#!/usr/bin/env python3
+
+"""
+Set rEFInd as the default boot loader, using Linux's efibootmgr tool.
+
+Copyright (c) 2016 Roderick W. Smith
+
+Authors:
+ Roderick W. Smith <rodsmith@rodsbooks.com>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 3, or
+(at your option) any later version, as published by the Free Software
+Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+"""
+
+import os
+import shutil
+import sys
+
+from subprocess import Popen, PIPE
+from argparse import ArgumentParser
+
+
+def discover_data():
+ """Extract boot entry and boot order information.
+
+ :returns:
+ boot_entries, boot_order
+ """
+ command = "efibootmgr -v"
+ bootinfo_bytes = (Popen(command, stdout=PIPE, shell=True)
+ .communicate()[0])
+ bootinfo = (bootinfo_bytes.decode(encoding="utf-8", errors="ignore")
+ .splitlines())
+ boot_entries = {}
+ boot_order = []
+ if len(bootinfo) > 1:
+ for s in bootinfo:
+ if "BootOrder" in s:
+ try:
+ boot_order = s.split(":")[1].replace(" ", "").split(",")
+ except IndexError:
+ pass
+ else:
+ # On Boot#### lines, #### is characters 4-8....
+ hex_value = s[4:8]
+ # ....and the description starts at character 10
+ name = s[10:]
+ try:
+ # In normal efibootmgr output, only Boot#### entries
+ # have characters 4-8 that can be interpreted as
+ # hex values, so this will harmlessly error out on all
+ # but Boot#### entries....
+ int(hex_value, 16)
+ boot_entries[hex_value] = name
+ except ValueError:
+ pass
+ return boot_entries, boot_order
+
+
+def add_unordered_entry(boot_entries, boot_order, label):
+ """Find a rEFInd boot_entry and add it to the boot_order list.
+
+ Run if the boot_order list includes no rEFInd entry, in the
+ hopes of finding an existing rEFInd boot_entry that can be
+ used.
+ :param boot_entries:
+ Dictionary of boot entries, with string (hex-encoded number) as
+ key and description as value
+ :param boot_order:
+ List of boot numbers as strings, in boot order
+ :param label:
+ String used to identify rEFInd entry in efibootmgr output
+ :returns:
+ True if an entry was added, False otherwise
+ """
+ added = False
+ for boot_num, description in boot_entries.items():
+ if label.lower() in description.lower():
+ print("Adding Boot{} from boot options list.".format(boot_num))
+ boot_order.insert(0, boot_num)
+ added = True
+ return added
+
+
+def set_refind_first(boot_entries, boot_order, label):
+ """Adjust boot_order so that rEFInd is first.
+
+ :param boot_entries:
+ Dictionary of boot entries, with string (hex-encoded number) as
+ key and description as value
+ :param boot_order:
+ List of boot numbers as strings, in boot order
+ :param label:
+ String used to identify rEFInd entry in efibootmgr output
+ :returns:
+ True if order adjusted, False otherwise
+ """
+ first_refind_number = i = -1
+ changed_order = False
+ found_first_refind = ""
+ show_multiple_warning = True
+ for entry in boot_order:
+ i += 1
+ if label.lower() in boot_entries[entry].lower():
+ if found_first_refind:
+ if show_multiple_warning:
+ print("Found multiple {} entries! The earliest in the boot order will be made".format(label))
+ print("the default, but this may not be what you want. Manually checking with")
+ print("efibootmgr is advisable!\n")
+ show_multiple_warning = False
+ else:
+ found_first_refind = entry
+ first_refind_number = i
+ if first_refind_number == -1:
+ if add_unordered_entry(boot_entries, boot_order, label):
+ changed_order = True
+ else:
+ print("{} was not found in the boot options list!".format(label))
+ print("You should create a {} entry with efibootmgr or by re-installing".format(label))
+ print("(with refind-install, for example)")
+ elif first_refind_number == 0:
+ print("{} is already the first entry".format(label))
+ elif first_refind_number > 0:
+ del boot_order[first_refind_number]
+ boot_order.insert(0, found_first_refind)
+ changed_order = True
+
+ print("{} is not the first boot entry; adjusting....".format(label))
+ return changed_order
+
+
+def save_changes(boot_order):
+ """Save an altered boot_order.
+
+ :returns:
+ True if there were no problems, False otherwise
+ """
+ order_string = ",".join(boot_order)
+ command = "efibootmgr -o {}".format(order_string)
+ print("Setting a boot order of {}".format(order_string))
+ try:
+ Popen(command, stdout=PIPE, shell=True).communicate()[0]
+ except:
+ print("An error occurred setting the new boot order!")
+
+
+def main():
+ """Set rEFInd as the default boot option."""
+ description = "Sets rEFInd as the default EFI boot option"
+ parser = ArgumentParser(description=description)
+ parser.add_argument("-L", "--label",
+ default="rEFInd",
+ help=("The label used to identify rEFInd"))
+ args = parser.parse_args()
+
+ if sys.platform != "linux":
+ print("This program is useful only under Linux; exiting!")
+ return(1)
+ if shutil.which("efibootmgr") is None:
+ print("The efibootmgr utility is not installed; exiting!")
+ return(1)
+ if not os.geteuid() == 0:
+ print("You must be root to run this program")
+ return(1)
+
+ problems = False
+ retval = 0
+ boot_entries, boot_order = discover_data()
+ if boot_entries == {}:
+ problems = True
+ print("No EFI boot entries available. This may indicate a firmware problem.")
+ if boot_order == []:
+ problems = True
+ print("The EFI BootOrder variable is not available. This may indicate a firmware")
+ print("problem.")
+ if (boot_entries != {} and boot_order != [] and
+ set_refind_first(boot_entries, boot_order, args.label)):
+ save_changes(boot_order)
+ else:
+ if problems:
+ retval = 1
+ print("No changes saved.")
+ return(retval)
+
+if __name__ == '__main__':
+ sys.exit(main())