29 KiB
Configuring Sprout
Sprout reads a TOML file, \sprout.toml, from the root of the EFI partition it was loaded from. This page
covers everything that file can hold, the command line options that override it, and the BLS behavior that
sits next to it. For installing Sprout, see the setup guides.
- Command line options
- The file at a glance
- Values
optionsmenu-stylesdriversextractorsactionsentriesgeneratorsphases- Autoconfiguration
- Bootloader Specification support
- Strict mode
Sprout reads the file once at startup. A key it doesn't know is ignored with a warning that names the key, so
a typo shows up in the boot log instead of silently doing nothing. A version other than 1 stops Sprout.
Command line options
These are passed to sprout.efi as load options, which is how GRUB or efibootmgr hand them over. They
override the matching setting in the file.
| Option | What it does |
|---|---|
--config=PATH |
Load a configuration file other than \sprout.toml. |
--autoconfigure |
Skip the configuration file and build one from what Sprout finds. |
--boot=ENTRY |
Boot an entry without showing the menu. See entries for matching. |
--force-menu |
Show the menu even when --boot picked an entry. |
--menu-style=STYLE |
basic, simple or graphical. |
--menu-timeout=SECONDS |
How long the menu waits before booting the default entry. |
--retain-boot-console |
Don't clear the screen before handing off to the entry. |
--bls-strict-mode |
Turn on strict mode. |
--help |
Print the options. |
# Boot a specific configuration file.
$ sprout.efi --config=\path\to\config.toml
# Boot "Boot Xen" at once, but only show the menu if it can't be found.
$ sprout.efi --boot="Boot Xen"
# Show the graphical menu for 10 seconds.
$ sprout.efi --menu-style=graphical --menu-timeout=10
An argument Sprout can't parse is dropped with a warning and the rest are used, since a typo in a boot entry shouldn't leave a machine unbootable.
The file at a glance
Every section is optional. An empty file is a valid configuration that has no entries.
version = 1
[options] # settings for Sprout itself
[menu-styles.x] # settings for a style of menu
[values] # named strings for use anywhere with $name
[drivers.x] # EFI drivers to load
[extractors.x] # values worked out at boot
[actions.x] # things Sprout can do
[entries.x] # items in the boot menu
[generators.x] # makes entries from data
[[phases.early]] # actions to run at set points
The pieces fit together like this: an entry is a menu item that runs actions; an action is configured
with strings that can contain values; values come from the [values] table, from extractors, and
from generators. Drivers run first so the filesystems the rest of the file points at exist.
Order at boot:
phases.early- drivers load
- autoconfiguration, if enabled
- extractors run
phases.startup- entries and generators produce the menu
- an entry is chosen
phases.late, then the entry's actions
Values
A value is a named string. Anywhere a string is accepted in an entry, action, driver path or phase, $name
is replaced with the value called name.
[values]
root-options = "root=/dev/sda2 ro"
kernel = "\\vmlinuz"
[actions.boot]
chainload.path = "$kernel"
chainload.options = ["$root-options", "quiet"]
Some details that matter in practice:
- Values can refer to other values. Sprout keeps substituting until nothing changes, so
a = "$b",b = "x"givesxfor both. A value that never settles, such as one that contains itself, is an error once it hits an iteration or length limit. - When two keys share a prefix, the longer one wins, so
$kernel-optionsis never read as$kernelplus-options. - A
$namethat has no value is left in the string as written. - Values are layered. A key in a generator or entry overrides the same key set globally, and only for that
entry. From weakest to strongest:
[values], extractor results, generator values (andvariants), then thevaluesof the entry itself. - Windows-style paths need doubled backslashes in TOML strings:
"\\EFI\\Linux". Literal strings ('\EFI\Linux') work too.
options
[options]
default-entry = "boot-linux"
menu-timeout = 5
menu-style = "graphical"
autoconfigure = false
bls-strict-mode = false
| Key | Type | Default | Meaning |
|---|---|---|---|
default-entry |
string | none | The entry that is selected first, and booted when the timeout ends. Without it, the first entry in the menu is. |
menu-timeout |
integer | see below | Seconds to show the menu before booting the default entry. |
menu-style |
string | simple |
basic prints entries and takes a number. simple is a full-screen list moved with the arrow keys. graphical is a menu drawn on the screen (the mouse is off unless you enable it, see menu-styles). |
autoconfigure |
boolean | false |
Add entries for what Sprout finds. See autoconfiguration. |
bls-strict-mode |
boolean | false |
See strict mode. |
When menu-timeout is not set anywhere, the menu shows for 10 seconds, or is hidden in strict mode. The
sources are ranked in The bootloader interface.
menu-styles
Settings for a particular menu style. Only the graphical menu has any.
[menu-styles.graphical]
enable-mouse = true
enable-logo = false
enable-animation = false
| Key | Type | Default | Meaning |
|---|---|---|---|
enable-mouse |
boolean | false |
Select entries with the mouse and show the cursor. Without it, or when the firmware has no pointing device, the menu takes the keyboard only. |
enable-logo |
boolean | true |
Show the Sprout logo next to the name. Without it, only the name is shown. |
enable-animation |
boolean | true |
Bounce the logo. Without it, the logo stays still. |
drivers
Drivers are EFI executables that add features, most often a filesystem driver for something the firmware can't read, such as ext4. Each is started once and expected to return. Afterwards Sprout reconnects every controller so the new filesystems appear. Drivers have no defined load order.
[drivers.ext4]
path = "\\sprout\\drivers\\ext4.efi"
| Key | Meaning |
|---|---|
path |
Path to the driver image. Required. Can use values. |
The name (ext4 above) is only for error messages and doesn't have to match the file.
extractors
An extractor works out a value when Sprout boots, so a configuration doesn't need to hardcode where things are. The name of the extractor is the name of the value it sets.
There is one kind, filesystem-device-match, which finds a filesystem and returns its device root. The
result has a trailing slash, so you append a path to it directly: $boot\\vmlinuz.
[extractors.boot]
filesystem-device-match.has-label = "BOOT"
filesystem-device-match.fallback = "PciRoot(0x0)/Pci(0x1,0x1)/Sata(0,0,0)/HD(1,GPT,c2a4f1b6-0000-0000-0000-000000000000)/"
| Key | Meaning |
|---|---|
has-label |
The filesystem volume label equals this. |
has-item |
This file or directory exists on the filesystem. |
has-partition-uuid |
The unique GUID of the partition. |
has-partition-type-uuid |
The partition type GUID, such as the one for XBOOTLDR. |
fallback |
What to use when no filesystem matches. Without it, no match is an error. |
Every criterion you give must match, and you must give at least one. The first matching filesystem wins.
has-label and has-item can use values.
actions
An action is a named, configured piece of work. Entries and phases refer to actions by name, and an action can be used by as many of them as you like.
Each action sets exactly one of chainload, print or edera. If you set more than one Sprout warns and
uses the first of them in that order.
chainload
Loads and starts another EFI image. That covers Linux through its EFI stub, Windows, and any other EFI application. If the image returns, Sprout returns to its own flow.
[actions.boot-linux]
chainload.path = "\\vmlinuz"
chainload.options = ["root=/dev/sda1", "quiet"]
chainload.linux-initrd = "\\initrd"
| Key | Type | Meaning |
|---|---|---|
path |
string | The image to start. Required. |
options |
list of strings | Joined with spaces and given to the image as its command line. |
linux-initrd |
string | One initrd, handed to the Linux EFI stub through the initrd media loader. |
linux-initrd-chain |
list of strings | Several initrds, concatenated in order into one. Can't be used together with linux-initrd. |
devicetree |
string | A flattened devicetree to install until the image returns. |
Notes:
linux-initrdis better than aninitrd=option, because it doesn't depend on the stub resolving a path.- Entries in
linux-initrd-chainthat are empty after substitution are skipped, which is what makes a chain with spare slots safe. - The devicetree is ignored with a warning when Secure Boot is on, as Sprout can't verify it. If it can't be installed, Sprout warns and boots without it, unless strict mode is on.
- When the image is a Windows boot manager or another non-Linux image, leave the initrd keys out.
print
Writes a line to the console. It's mostly for phases, such as a message while drivers load.
[actions.hello]
print.text = "Booting $version"
edera
Boots the Edera hypervisor and its root operating system, as an extension of the Xen EFI stub. Sprout hands the Xen config, dom0 kernel and optional initrd to Xen as media loaders, then starts Xen.
[actions.edera]
edera.xen = "\\EFI\\edera\\xen.efi"
edera.kernel = "\\EFI\\edera\\kernel"
edera.initrd = "\\EFI\\edera\\initrd"
edera.xen-options = ["dom0_mem=512M"]
edera.kernel-options = ["console=hvc0"]
| Key | Type | Meaning |
|---|---|---|
xen |
string | The Xen EFI image. Required. |
kernel |
string | The dom0 kernel. Required. |
initrd |
string | The dom0 initrd. |
initrd-chain |
list of strings | Several initrds, concatenated in order. Can't be used with initrd. |
kernel-options |
list of strings | Command line of the dom0 kernel. |
xen-options |
list of strings | Command line of Xen. |
When a TPM is present, the Xen options and dom0 command line are measured into PCR 12, the kernel into PCR 11, and the initrd into PCR 9.
entries
An entry is something the user can pick from the menu. The table name identifies it.
[entries.boot-linux]
title = "Boot Linux"
actions = ["boot-linux"]
values.kernel = "\\vmlinuz"
sort-key = "10"
| Key | Type | Meaning |
|---|---|---|
title |
string | The text in the menu. Required, and can use values. |
actions |
list of strings | Actions to run, in order, when the entry is chosen. |
values |
table | Values that exist only for this entry. |
sort-key |
string | Orders the entry in the menu. Compared as versions, not as text, so 10 sorts after 9. Defaults to the entry's name. |
The menu lists entries in reverse sort order, newest first. That is what you want for kernel versions and
is why sort-key = "$version" gives a sensible menu.
--boot and the bootloader interface find an entry by its name, by its title, or by its position in the
menu counted from 0. A * in the pattern matches anything.
If an entry fails Sprout reports the error and waits ten seconds. Then it exits to the firmware, or resets the
machine when reboot-on-error applies (see boot counting).
generators
A generator makes entries from data when Sprout starts. Each has a kind (matrix, list or bls), and
optionally variants and exclude, which work on the result of any kind. Set only one kind per generator.
The entries a generator makes are named <generator>-<n>, with n counting from 0, and a BLS generator
uses the name of the entry file instead. Every one of them is built from the entry template you give,
which takes the same keys as an entry.
matrix
Makes an entry for every combination of the lists you give.
[generators.kernels]
matrix.entry.title = "Boot $kernel with $console"
matrix.entry.actions = ["boot-kernel"]
matrix.values.kernel = ["\\vmlinuz", "\\vmlinuz-lts"]
matrix.values.console = ["ttyS0", "tty0"]
That is four entries. A list of two and a list of three give six.
list
Makes one entry per item. Each item is a table of values. Use it when the combinations aren't a clean grid.
[generators.systems]
list.entry.title = "Boot $name"
list.entry.actions = ["boot-system"]
list.values = [
{ name = "Alpine", kernel = "\\alpine\\vmlinuz" },
{ name = "Debian", kernel = "\\debian\\vmlinuz" },
]
bls
Makes entries from the files of the Bootloader Specification.
[generators.bls]
bls.path = "\\loader"
bls.entry.title = "$title"
bls.entry.actions = ["boot-bls"]
| Key | Type | Default | Meaning |
|---|---|---|---|
entry |
table | The template entry. Required. | |
path |
string | \loader |
The directory that holds the entries directory. Can include a device, like $boot\\loader. |
uki-path |
string | \EFI\Linux on the device of path |
Where to look for unified kernel images. An empty string turns them off. |
xbootldr |
boolean | false |
Also read the extended boot loader partition of the same disk. |
pin-names |
boolean | true |
Keep the entry file's name as the entry name, instead of prefixing the generator name. |
Two generators reading the same directory produce entries with the same name. Sprout warns about it, since
picking by name becomes ambiguous. pin-names is what makes names match the ids that bootctl uses.
The values each BLS entry provides are listed under BLS values.
variants
Variants multiply the entries of a generator. Each key is an axis, and each axis is a list of choices. Every entry is made once for each combination of choices, taking one from each axis.
[generators.kernels]
matrix.entry.title = "Boot $kernel ($console)"
matrix.entry.actions = ["boot-kernel"]
matrix.values.kernel = ["\\vmlinuz", "\\vmlinuz-lts"]
variants.console = [
{ name = "serial", values.console-options = "console=ttyS0" },
{ name = "graphics", values.console-options = "console=tty0" },
]
This makes "Boot \vmlinuz (serial)", "Boot \vmlinuz (graphics)" and the same two for \vmlinuz-lts. Four
entries from a matrix of two kernels and two choices.
A choice has a name and optional values. The axis name becomes a value holding the choice's name
($console is serial here), and the choice's own values are added next to it. Names are appended to the
entry name, so the entries above are called kernels-0-serial, kernels-0-graphics and so on. Axes apply in
alphabetical order, choices in the order written. An axis with no choices, or two choices that share a name,
is an error.
exclude
Drops generated entries. It's a list of rules, and a rule is a table of value names to patterns, where *
matches anything. An entry is dropped if every value in at least one rule matches. It runs after variants,
and compares values before they are substituted.
[generators.bls]
# ...
exclude = [
{ version = "0-rescue-*" },
{ console = "graphics", mode = "debug" },
]
The first rule drops BLS rescue entries. The second drops only the one combination.
phases
Phases run actions at fixed points in the boot. Each phase is a list, and each item names actions and can add values for them.
| Phase | Runs |
|---|---|
early |
Before drivers load. |
startup |
After drivers, autoconfiguration and extractors, before the menu is built. |
late |
After an entry is chosen, before its actions run. |
[[phases.early]]
actions = ["splash"]
[[phases.startup]]
actions = ["announce"]
values.message = "drivers are loaded"
Items run in order, and an action that fails stops the boot with an error. Note the double brackets: each item is one element of a list.
Autoconfiguration
With autoconfigure = true, or --autoconfigure, Sprout looks at every filesystem it can read and adds
configuration for what it finds. With the option, the result is added to your file. With the flag, your file
is not read at all, which makes it a good way to get a machine booting before you've written one.
For each filesystem:
- BLS: if it has
\loader\loader.conf, a\loader\entriesdirectory with.conffiles, or.efifiles in\EFI\Linux, Sprout adds ablsgenerator and the action that boots its entries. These are namedauto-bls-<hash>. - Linux: if there was no BLS, kernels in
\bootand\are found by name (vmlinuzorImage, optionally followed by-and a version), paired with aninitramfs,initrdorinitrd.imgof the same suffix. The entries are namedauto-linux-<hash>and titled "Boot Linux" plus the path. Command line options come from the valuelinux-options, which you can set in[values]and which otherwise is the placeholderplaceholder. - Windows: if
\EFI\Microsoft\Boot\bootmgfw.efiexists, an entry "Boot Windows" namedauto-windows-<hash>chainloads it.
<hash> is a short hash of the filesystem's device path, so two disks never collide. Strict
mode narrows which filesystems the BLS part reads.
Bootloader Specification support
Sprout implements the Boot Loader Specification to read the entries that distributions install, and sorts them as the specification says.
It reads Type #1 entries from \loader\entries and Type #2 unified kernel images (UKIs) from \EFI\Linux. A
UKI with several profiles is one entry per profile. Entries for another architecture than the machine's are
hidden. In a Type #1 entry Sprout uses the keys title, version, machine-id, sort-key, linux, efi,
uki, initrd, options, devicetree, architecture and profile.
The simplest way to get this is autoconfigure = true. To write it out by hand, you need a bls generator
and an action that boots what it makes:
[generators.bls]
bls.entry.title = "$title"
bls.entry.actions = ["boot-bls"]
bls.xbootldr = true
[actions.boot-bls]
chainload.path = "$entry-root\\$chainload"
chainload.options = ["$options"]
chainload.devicetree = "$entry-root\\$devicetree"
chainload.linux-initrd-chain = [
"$entry-root\\$initrd-0",
"$entry-root\\$initrd-1",
"$entry-root\\$initrd-2",
"$entry-root\\$initrd-3",
]
An entry can have up to 32 initrds, and Sprout warns when it has one that the chain doesn't list, because the kernel likely can't then find its root. Autoconfiguration lists all 32. Unused slots are empty and skipped, so listing more than you need costs nothing.
BLS values
| Value | Holds |
|---|---|
$title |
The title, with the version added unless it already contains it. |
$title-base |
The title as written in the entry. |
$version |
The version field. |
$machine-id |
The machine-id field. |
$chainload |
The path of the linux, efi or uki file. |
$options |
The options field, with the profile prepended for a UKI profile. |
$cmdline |
The command line embedded in a UKI. |
$uname |
The kernel version embedded in a UKI. |
$devicetree |
The devicetree path. |
$initrd-0 to $initrd-31 |
The initrds, in order. A slot with none is empty. |
$initrd |
The last initrd. |
$entry-root |
The device the entry's files are on. Empty for Sprout's own partition. |
$entry-root matters once xbootldr is on, since an entry on the XBOOTLDR partition has its files there,
not next to Sprout. Always write BLS paths as $entry-root\\$chainload.
Boot counting
A file named like fedora+3.conf or fedora+3.efi has three tries. Each boot renames it, say to
fedora+2-1.conf, and tells the system the new path in LoaderBootCountPath so systemd-bless-boot can
remove the counter once the boot works.
Entries with no tries left sort last and are never the default, but can still be booted by hand. If an entry
fails to start after it used a try, and it had tries left, the machine resets so the next boot can use the
next try or pick another entry. This is the reboot-on-error setting of loader.conf. Without boot counting
a failure to start goes back to the firmware.
loader.conf
Sprout reads \loader\loader.conf from the partition it was loaded from, as systemd-boot does. It is
measured into the TPM, since it changes how Sprout boots.
| Key | Value |
|---|---|
default |
A pattern for the id of the default entry, or @saved. |
preferred |
Like default, but entries with no boot counter tries are skipped. |
timeout |
Seconds, menu-hidden, menu-disabled or menu-force. |
reboot-on-error |
auto (the default), yes or no. |
The id of an entry is its file name without the boot counter, like fedora.conf or fedora.efi. Patterns
ignore case and can use *, ? and [a-z]. With @saved the entry booted last is the default.
reboot-on-error = yes resets after every failed start, which can loop forever. auto only resets when a
try was used up and some were left. Other keys are ignored with a warning.
A hidden menu, from a timeout of zero or menu-hidden, still opens when a key is pressed. menu-disabled
does not.
The bootloader interface
Sprout uses the same firmware variables as systemd-boot, so bootctl, systemctl reboot --boot-loader-entry
and systemd-bless-boot work with it. It publishes LoaderEntries, LoaderEntrySelected,
LoaderBootCountPath, LoaderFeatures and LoaderInfo, and reads LoaderEntryDefault,
LoaderEntryPreferred, LoaderEntryOneShot, LoaderEntryLastBooted, LoaderConfigTimeout and
LoaderConfigTimeoutOneShot.
What bootctl sets outranks sprout.toml, which outranks loader.conf. The default entry is the first of
these that matches an entry:
LoaderEntryPreferred, thenpreferredinloader.confLoaderEntryDefaultdefault-entryinsprout.tomldefaultinloader.conf
The menu timeout is the first of these that is set:
- the one-shot timeout,
LoaderConfigTimeoutOneShot --menu-timeoutLoaderConfigTimeoutmenu-timeoutinsprout.tomltimeoutinloader.conf
A one-shot entry (LoaderEntryOneShot) is booted at once and without the menu, unless strict mode is on.
Strict mode
By default Sprout departs from systemd-boot in a few places, where it is friendlier or safer for a
bootloader that reads the same files. Each departure is logged when it applies. --bls-strict-mode, or
bls-strict-mode = true under [options], turns all of them off.
| Behavior | By default | In strict mode |
|---|---|---|
One-shot entry (LoaderEntryOneShot) |
Booted at once, without the menu. | Only the default for this boot. The menu and its timeout still apply. |
| Menu timeout that nothing sets | The menu is shown for 10 seconds. | The menu is hidden. |
| Default entry with no boot counter tries | Skipped for another entry. | Used, as default ignores the tries. |
Entry with more than one of linux, efi, uki |
Boots the first of them. | Hidden. |
| Entry whose file does not exist | Shown, and it fails when booted. | Hidden. |
| UKI without a name | Named after its file. | Hidden. |
| Name and version of a UKI | Name is PRETTY_NAME or ID. Version is IMAGE_VERSION, VERSION_ID, BUILD_ID, then .uname. |
What systemd-boot uses. Name is PRETTY_NAME, IMAGE_ID, NAME or ID. Version is IMAGE_VERSION, VERSION, VERSION_ID or BUILD_ID. |
Boot counter of a plain efi entry |
Counted. | Not counted, but its tries still make it bad. |
| Extended boot loader partition | Read when xbootldr is set on the generator. |
Always read. |
| Autoconfiguration | Reads BLS entries from every disk, one generator each. | Reads BLS entries from Sprout's partition and the XBOOTLDR of its disk, as one generator. Windows and Linux entries are found as before. |
| Boot entry that returns | Sprout exits to the firmware. | The menu is shown again. |
| Devicetree that can't be installed | Warns and boots without it. | The entry fails. |
In strict mode the actions of a hand-written generator have to use $entry-root for the files of an entry,
as entries can come from the extended boot loader partition.