docs(config): add CONFIG.md and move the configuration reference out of the README (#119)

CONFIG.md documents every section of sprout.toml, the command line options, BLS support and strict mode. The README keeps the overview and minimal examples and links to it. Adds an update-config-docs skill to keep the reference in sync with the code.
This commit is contained in:
alex authored and GitHub committed 2026-10-04 14:14:13 -07:00
1 parent 9ac55ada50
commit ea115d3a16
5 files changed
+670 -195

No files matched your search

@@ -0,0 +1,83 @@
---
name: update-config-docs
description: Bring CONFIG.md back in line with the code. Use after changing anything in crates/config, the command line options, loader.conf handling, strict mode, or autoconfiguration, or when asked to check that CONFIG.md is accurate.
---
# Update CONFIG.md
`CONFIG.md` is the reference for `sprout.toml`, the command line options, BLS support and strict mode.
The README only links to it, so a setting that is missing from CONFIG.md is undocumented.
## 1. Find what changed
Start from the diff, or from the whole config surface if there is none:
```bash
git diff main -- crates/config crates/boot/src/options.rs crates/boot/src/config crates/boot/src/autoconfigure crates/bls
```
The sources of truth, and the section of CONFIG.md each one feeds:
| Source | Section |
|-----------------------------------------------------------|-------------------------------------------|
| `crates/config/src/lib.rs` | file at a glance, `options` |
| `crates/config/src/drivers.rs` | `drivers` |
| `crates/config/src/extractors*` | `extractors` |
| `crates/config/src/actions*` | `actions` |
| `crates/config/src/entries.rs` | `entries` |
| `crates/config/src/generators*` | `generators`, `variants`, `exclude` |
| `crates/config/src/phases.rs` | `phases` |
| `crates/boot/src/options.rs` | command line options |
| `crates/boot/src/main.rs` | boot order, defaults, timeout and default entry order, strict mode |
| `crates/boot/src/autoconfigure*` | autoconfiguration |
| `crates/boot/src/generators/bls.rs`, `crates/bls` | BLS values, boot counting, `loader.conf` |
## 2. Check each key
For every struct field in `crates/config`, CONFIG.md needs its TOML name (look at `#[serde(rename)]`, as
most multi-word keys use dashes), its type, its default, and what it does. Read the code that uses it in
`crates/boot`, not only the doc comment, because the behavior is what the reader depends on.
Look for the usual drift:
- a field added, renamed or removed
- a default that changed (`#[serde(default = ...)]`, the `Default` impls, constants such as
`DEFAULT_MENU_TIMEOUT_SECONDS`)
- a new BLS value set with `context.set`, or a new option in `SproutOptions`
- a new row to add to, or remove from, the strict mode table
- a changed order: the default entry sources, the menu timeout sources, or the phases
- `LATEST_VERSION` changing, which the intro and `version` text must follow
Don't document what you can't confirm in the code. If a behavior is unclear, read the function that
implements it.
## 3. Write the change
- Keep each section's shape: a short explanation, a TOML example, then a table of keys.
- Examples must parse. Check that every key in one exists and is spelled the way serde expects.
- Update the table of contents if you add or rename a heading.
- Keep README.md short. It gets the feature list and the minimal examples, and CONFIG.md gets everything else.
- Don't duplicate the strict mode table or the default entry order anywhere else.
## 4. Write like a person
Plain sentences, said once. Skip these:
- filler like "robust", "seamless", "powerful", "leverage", "ensure", "note that", "it's important to"
- groups of three added for rhythm, and a summary sentence at the end of every section
- bullet lists for what a sentence says better, and bold used as decoration
- em dashes (use a comma, colon or parentheses)
Say what a setting does and what happens when it's unset or wrong. Name real things (`bootctl`, `PCR 12`,
`\EFI\Linux`) instead of describing them.
## 5. Verify
```bash
cargo build --target x86_64-unknown-uefi
cargo build --target aarch64-unknown-uefi
cargo fmt --all --check
```
Then re-read the diff of CONFIG.md against the code once more for any key whose name or default you
touched. If the change was only to docs, skip the builds.
+2 -1
View File
@@ -2,7 +2,8 @@
Sprout is a programmable UEFI bootloader written in Rust, for `x86_64` and `aarch64`. It is a `no_std` workspace
built for the `*-unknown-uefi` targets, so it cannot be run or tested like a normal binary.
See [README.md](README.md) for the configuration format and features.
See [README.md](README.md) for features and [CONFIG.md](CONFIG.md) for the configuration format.
After changing configuration, run the `update-config-docs` skill to keep CONFIG.md accurate.
## Layout
+576
View File
@@ -0,0 +1,576 @@
# 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](./docs/setup).
- [Command line options](#command-line-options)
- [The file at a glance](#the-file-at-a-glance)
- [Values](#values)
- [`options`](#options)
- [`menu-styles`](#menu-styles)
- [`drivers`](#drivers)
- [`extractors`](#extractors)
- [`actions`](#actions)
- [`entries`](#entries)
- [`generators`](#generators)
- [`phases`](#phases)
- [Autoconfiguration](#autoconfiguration)
- [Bootloader Specification support](#bootloader-specification-support)
- [Strict mode](#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](#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](#strict-mode). |
| `--help` | Print the options. |
```bash
# 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.
```toml
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:
1. `phases.early`
2. drivers load
3. autoconfiguration, if enabled
4. extractors run
5. `phases.startup`
6. entries and generators produce the menu
7. an entry is chosen
8. `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`.
```toml
[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"` gives `x` for 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-options` is never read as `$kernel` plus
`-options`.
- A `$name` that 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 (and `variants`), then the
`values` of the entry itself.
- Windows-style paths need doubled backslashes in TOML strings: `"\\EFI\\Linux"`. Literal strings
(`'\EFI\Linux'`) work too.
## `options`
```toml
[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`](#menu-styles)). |
| `autoconfigure` | boolean | `false` | Add entries for what Sprout finds. See [autoconfiguration](#autoconfiguration). |
| `bls-strict-mode` | boolean | `false` | See [strict mode](#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](#the-bootloader-interface).
## `menu-styles`
Settings for a particular menu style. Only the graphical menu has any.
```toml
[menu-styles.graphical]
enable-mouse = true
```
| 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. |
## `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.
```toml
[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`.
```toml
[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.
```toml
[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-initrd` is better than an `initrd=` option, because it doesn't depend on the stub resolving a path.
- Entries in `linux-initrd-chain` that 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](#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.
```toml
[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.
```toml
[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.
```toml
[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](#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](#entries).
### `matrix`
Makes an entry for every combination of the lists you give.
```toml
[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.
```toml
[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](#bootloader-specification-support).
```toml
[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](#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.
```toml
[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.
```toml
[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. |
```toml
[[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\entries` directory with `.conf` files, or `.efi` files
in `\EFI\Linux`, Sprout adds a `bls` generator and the action that boots its entries. These are named
`auto-bls-<hash>`.
- **Linux**: if there was no BLS, kernels in `\boot` and `\` are found by name (`vmlinuz` or `Image`,
optionally followed by `-` and a version), paired with an `initramfs`, `initrd` or `initrd.img` of the same
suffix. The entries are named `auto-linux-<hash>` and titled "Boot Linux" plus the path. Command line
options come from the value `linux-options`, which you can set in `[values]` and which otherwise is the
placeholder `placeholder`.
- **Windows**: if `\EFI\Microsoft\Boot\bootmgfw.efi` exists, an entry "Boot Windows" named
`auto-windows-<hash>` chainloads it.
`<hash>` is a short hash of the filesystem's device path, so two disks never collide. [Strict
mode](#strict-mode) narrows which filesystems the BLS part reads.
## Bootloader Specification support
Sprout implements the [Boot Loader Specification](https://uapi-group.org/specifications/specs/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:
```toml
[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:
1. `LoaderEntryPreferred`, then `preferred` in `loader.conf`
2. `LoaderEntryDefault`
3. `default-entry` in `sprout.toml`
4. `default` in `loader.conf`
The menu timeout is the first of these that is set:
1. the one-shot timeout, `LoaderConfigTimeoutOneShot`
2. `--menu-timeout`
3. `LoaderConfigTimeout`
4. `menu-timeout` in `sprout.toml`
5. `timeout` in `loader.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.
+8 -193
View File
@@ -58,6 +58,7 @@ We recommend running Sprout without Secure Boot for development, and with Secure
### Project Documentation
- [Configuration Reference]
- [Development Guide]
- [Contributing Guide]
- [Sprout License]
@@ -110,49 +111,9 @@ We recommend running Sprout without Secure Boot for development, and with Secure
Sprout is provided as a single EFI binary called `sprout.efi`.
It can be chainloaded from GRUB or other UEFI bootloaders or booted into directly.
Sprout will look for \sprout.toml in the root of the EFI partition it was loaded from.
See [Configuration](#configuration) for how to configure sprout.
Sprout reads its configuration from `\sprout.toml` in the root of the EFI partition it was loaded from.
## Configuration
Sprout is configured using a TOML file at `\sprout.toml` on the root of the EFI partition sprout was booted from.
### Command Line Options
Sprout supports some command line options that can be combined to modify behavior without the configuration file.
```bash
# Boot Sprout with a specific configuration file.
$ sprout.efi --config=\path\to\config.toml
# Boot a specific entry, bypassing the menu.
$ sprout.efi --boot="Boot Xen"
# Autoconfigure Sprout, without loading a configuration file.
$ sprout.efi --autoconfigure
# Use the basic boot menu instead of the simple one.
$ sprout.efi --menu-style=basic
# Use the graphical boot menu.
$ sprout.efi --menu-style=graphical
# Show the boot menu for 10 seconds before booting the default entry.
$ sprout.efi --menu-timeout=10
# Show the boot menu even if an entry was chosen with --boot.
$ sprout.efi --force-menu
# Keep the boot console as it is when an entry is booted.
$ sprout.efi --retain-boot-console
# Follow the BLS specification and systemd-boot exactly.
$ sprout.efi --bls-strict-mode
```
### Graphical Menu
The graphical menu selects entries with the keyboard. The mouse is off by default. To use it, enable it in
`sprout.toml`. The cursor is only shown when the mouse is enabled and the firmware has a pointing device.
```toml
[menu-styles.graphical]
enable-mouse = true
```
### Boot Linux from ESP
This is a configuration that boots a Linux kernel from the EFI partition:
```toml
# sprout configuration: version 1
@@ -174,170 +135,24 @@ chainload.options = ["root=/dev/sda1"]
chainload.linux-initrd = "\\initrd"
```
### Bootloader Specification (BLS) Support
On a system that follows the Boot Loader Specification, you may not need to write one. With an ext4 driver
loaded, autoconfiguration finds the entries on its own:
```toml
# sprout configuration: version 1
version = 1
# load an EFI driver for ext4.
[drivers.ext4]
path = "\\sprout\\drivers\\ext4.efi"
# global options.
[options]
# enable autoconfiguration by detecting bls enabled
# filesystems and generating boot entries for them.
autoconfigure = true
```
Sprout reads Type #1 entries from `\loader\entries` and Type #2 unified kernel images (UKIs) from
`\EFI\Linux`, and sorts them as the specification says. A unified kernel image with several profiles
is one entry for each profile. Entries that are not for the architecture of the machine are hidden.
Type #1 entries can use `linux`, `efi`, `uki`, `initrd`, `options`, `devicetree`, `architecture`,
`profile`, `sort-key`, `version`, `machine-id`, and `title`.
To set up the generator by hand instead of using autoconfiguration, add a generator and an action
that boots the entries. The entry values `$chainload`, `$options`, `$initrd-0` to `$initrd-7`,
`$devicetree`, `$cmdline`, `$title`, `$version`, and `$entry-root` are available.
```toml
[generators.bls]
# the directory that has the entries directory. this is the default.
bls.path = "\\loader"
# the directory of unified kernel images. by default, this is \EFI\Linux on
# the device of the path. an empty path turns unified kernel images off.
bls.uki-path = "\\EFI\\Linux"
# also read the extended boot loader partition (XBOOTLDR) of the same disk,
# which is sorted with the other entries. strict mode always does. its files are on that partition, so
# the action has to use $entry-root, which is empty for Sprout's own partition.
bls.xbootldr = false
# keep the name of the entry file as the name of the entry, so it matches
# the ids that bootctl uses.
bls.pin-names = true
bls.entry.title = "$title"
bls.entry.actions = ["boot-bls"]
[actions.boot-bls]
chainload.path = "$entry-root\\$chainload"
chainload.options = ["$options"]
chainload.devicetree = "$entry-root\\$devicetree"
# an entry can have up to 32 initrds, as $initrd-0 to $initrd-31. unused ones are skipped.
# only the first eight are listed here. add the others to boot an entry with more,
# as Sprout warns when an entry has an initrd that the chain does not list.
chainload.linux-initrd-chain = [
"$entry-root\\$initrd-0",
"$entry-root\\$initrd-1",
"$entry-root\\$initrd-2",
"$entry-root\\$initrd-3",
"$entry-root\\$initrd-4",
"$entry-root\\$initrd-5",
"$entry-root\\$initrd-6",
"$entry-root\\$initrd-7",
]
```
An entry that names a `devicetree` boots with that devicetree installed for the image, which is put
back when the image returns. It is not used when Secure Boot is enabled, as it can't be verified.
If it can't be installed, Sprout warns and boots without it, unless strict mode is on.
#### Boot counting
An entry file named like `fedora+3.conf` or `fedora+3.efi` has three tries. Each time Sprout boots it, the
file is renamed, such as to `fedora+2-1.conf`, and the new path is given to the system in
`LoaderBootCountPath` so `systemd-bless-boot` can remove the counter once the boot works.
Entries that have no tries left are sorted last and are not picked as the default entry, but they can still be
booted by hand. By default, if an entry fails to start after a try was used up and it had tries left, the machine
resets, so the next boot can use the next try or another entry. This is the `reboot-on-error` setting
in `loader.conf`. Without boot counting, a failure to start returns to the firmware.
#### loader.conf
Sprout reads `\loader\loader.conf` from the partition it was loaded from, as systemd-boot does.
| 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 the name of its file without the boot counter, such as `fedora.conf` or `fedora.efi`.
Patterns ignore case and can use `*`, `?`, and `[a-z]`. With `@saved`, the entry that was booted last is the
default. With `reboot-on-error`, `yes` always resets after an entry fails to start, which can loop forever,
and `auto` only does when a boot counter try was used up and there were tries 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.
#### Bootloader interface
Sprout uses the same variables as systemd-boot, so `bootctl`, `systemctl reboot --boot-loader-entry`, and
`systemd-bless-boot` can work with it. It publishes `LoaderEntries`, `LoaderEntrySelected`,
`LoaderBootCountPath`, `LoaderFeatures`, and `LoaderInfo`, and reads `LoaderEntryDefault`,
`LoaderEntryPreferred`, `LoaderEntryOneShot`, `LoaderEntryLastBooted`, `LoaderConfigTimeout`,
and `LoaderConfigTimeoutOneShot`.
The values that tools like `bootctl` set in the bootloader interface outrank `sprout.toml`, which outranks
`loader.conf`. In strict mode, `LoaderEntryOneShot` is tried before everything else for the default entry.
The default entry comes from the first of these that matches an entry:
1. `LoaderEntryPreferred`, then `preferred` in `loader.conf`
2. `LoaderEntryDefault`
3. `default-entry` in `sprout.toml`
4. `default` in `loader.conf`
The menu timeout comes from the first of the one-shot timeout, `--menu-timeout`, `LoaderConfigTimeout`,
`menu-timeout` in `sprout.toml`, and `loader.conf`.
#### Strict mode
By default, Sprout differs from systemd-boot in a few places that are friendlier or safer for a bootloader that
reads the same files. Each of them is logged when it applies. `--bls-strict-mode`, or `bls-strict-mode = true`
in the options of `sprout.toml`, removes all of them.
| 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. |
| Unified kernel image without a name | Named after its file. | Hidden. |
| Name and version of a unified kernel image | The name is `PRETTY_NAME` or `ID`, and the version is `IMAGE_VERSION`, `VERSION_ID`, `BUILD_ID`, then `.uname`. | The fields systemd-boot uses: the name is `PRETTY_NAME`, `IMAGE_ID`, `NAME` or `ID`, and the 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. |
| The 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.
### Generators
Generators make entries when Sprout starts. The `matrix` generator makes an entry for every combination of
its values, the `list` generator makes an entry for each item of a list, and the `bls` generator makes
entries from BLS files. Variants multiply the entries of any generator, and `exclude` removes some of them.
```toml
# makes an entry for each kernel and each console, such as "Boot \vmlinuz (serial)".
[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" },
]
[actions.boot-kernel]
chainload.path = "$kernel"
chainload.options = ["$console-options"]
```
The full list of settings, the command line options, generators, and how Sprout treats BLS entries,
`loader.conf` and strict mode are in [CONFIG.md](./CONFIG.md).
[Edera]: https://edera.dev
[Configuration Reference]: ./CONFIG.md
[Development Guide]: ./DEVELOPMENT.md
[Contributing Guide]: ./CONTRIBUTING.md
[Sprout License]: ./LICENSE
+1 -1
View File
@@ -97,7 +97,7 @@ pub struct OptionsConfiguration {
/// Makes Sprout follow the Boot Loader Specification and systemd-boot exactly where it
/// otherwise differs for convenience or safety. For example, the extended boot loader
/// partition is always read, a one-shot entry only sets the default entry, and entries with
/// missing files are hidden. See the README for everything it changes.
/// missing files are hidden. See CONFIG.md for everything it changes.
#[serde(rename = "bls-strict-mode", default)]
pub bls_strict_mode: bool,
}