mirror of
https://github.com/edera-dev/sprout.git
synced 2025-12-20 08:10:18 +00:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
1171959a52
|
|||
|
c5ec8dc6a6
|
|||
|
c749c8d38e
|
|||
|
3d2c31ee1a
|
|||
|
a02ee88afd
|
|||
|
b59626888e
|
|||
|
e3bae1dc63
|
|||
|
3cd3491df0
|
|||
|
e08f6e629f
|
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -61,7 +61,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "edera-sprout"
|
name = "edera-sprout"
|
||||||
version = "0.0.4"
|
version = "0.0.7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"image",
|
"image",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
name = "edera-sprout"
|
name = "edera-sprout"
|
||||||
description = "Modern UEFI bootloader"
|
description = "Modern UEFI bootloader"
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
version = "0.0.4"
|
version = "0.0.7"
|
||||||
homepage = "https://sprout.edera.dev"
|
homepage = "https://sprout.edera.dev"
|
||||||
repository = "https://github.com/edera-dev/sprout"
|
repository = "https://github.com/edera-dev/sprout"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|||||||
31
README.md
31
README.md
@@ -18,6 +18,17 @@ existing UEFI bootloader or booted by the hardware directly.
|
|||||||
|
|
||||||
Sprout is licensed under Apache 2.0 and is open to modifications and contributions.
|
Sprout is licensed under Apache 2.0 and is open to modifications and contributions.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
- [Fedora Setup Guide]
|
||||||
|
- [Generic Linux Setup Guide]
|
||||||
|
- [Windows Setup Guide]
|
||||||
|
- [Development Guide]
|
||||||
|
- [Contributing Guide]
|
||||||
|
- [Sprout License]
|
||||||
|
- [Code of Conduct]
|
||||||
|
- [Security Policy]
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
NOTE: Currently, Sprout is experimental and is not intended for production use. For example, it doesn't currently
|
NOTE: Currently, Sprout is experimental and is not intended for production use. For example, it doesn't currently
|
||||||
@@ -62,6 +73,17 @@ See [Configuration](#configuration) for how to configure sprout.
|
|||||||
|
|
||||||
Sprout is configured using a TOML file at `\sprout.toml` on the root of the EFI partition sprout was booted from.
|
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"
|
||||||
|
```
|
||||||
|
|
||||||
### Boot Linux from ESP
|
### Boot Linux from ESP
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
@@ -116,3 +138,12 @@ chainload.path = "$boot\\$chainload"
|
|||||||
chainload.options = ["$options"]
|
chainload.options = ["$options"]
|
||||||
chainload.linux-initrd = "$boot\\$initrd"
|
chainload.linux-initrd = "$boot\\$initrd"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
[Fedora Setup Guide]: ./docs/fedora-setup.md
|
||||||
|
[Generic Linux Setup Guide]: ./docs/generic-linux-setup.md
|
||||||
|
[Windows Setup Guide]: ./docs/windows-setup.md
|
||||||
|
[Development Guide]: ./DEVELOPMENT.md
|
||||||
|
[Contributing Guide]: ./CONTRIBUTING.md
|
||||||
|
[Sprout License]: ./LICENSE
|
||||||
|
[Code of Conduct]: ./CODE_OF_CONDUCT.md
|
||||||
|
[Security Policy]: ./SECURITY.md
|
||||||
|
|||||||
134
docs/fedora-setup.md
Normal file
134
docs/fedora-setup.md
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
# Setup Sprout on Fedora
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Modern Fedora release: tested on Fedora Workstation 42 and 43
|
||||||
|
- EFI System Partition mounted on `/boot/efi` (the default)
|
||||||
|
- ext4 or FAT32/exFAT formatted `/boot` partition
|
||||||
|
|
||||||
|
## Step 1: Base Installation
|
||||||
|
|
||||||
|
Download the latest sprout.efi release from the [GitHub releases page](https://github.com/edera-dev/sprout/releases).
|
||||||
|
For x86_64 systems, download the `sprout-x86_64.efi` file, and for ARM systems, download the `sprout-aarch64.efi` file.
|
||||||
|
Copy the downloaded `sprout.efi` file to `/boot/efi/EFI/BOOT/sprout.efi` on your EFI System Partition.
|
||||||
|
|
||||||
|
Additionally, you will want to install the `edk2-ext4` package, which provides the ext4 filesystem support for Sprout.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install the edk2-ext4 package which provides ext4 support for Sprout.
|
||||||
|
$ sudo dnf install edk2-ext4
|
||||||
|
# Create a directory for sprout drivers.
|
||||||
|
$ sudo mkdir -p /boot/efi/sprout/drivers
|
||||||
|
# For x86_64 systems, copy the ext4x64.efi driver to the sprout drivers directory.
|
||||||
|
$ sudo cp /usr/share/edk2/drivers/ext4x64.efi /boot/efi/sprout/drivers/ext4.efi
|
||||||
|
# For ARM64 systems, copy the ext4aa64.efi driver to the sprout drivers directory.
|
||||||
|
$ sudo cp /usr/share/edk2/drivers/ext4aa64.efi /boot/efi/sprout/drivers/ext4.efi
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step 2: Configure Sprout
|
||||||
|
|
||||||
|
Since Fedora uses the BLS specification, you can use the `bls` generator to autoconfigure Sprout for Fedora.
|
||||||
|
|
||||||
|
Write the following file to `/boot/efi/sprout.toml`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# sprout configuration: version 1
|
||||||
|
version = 1
|
||||||
|
|
||||||
|
# load an EFI driver for ext4.
|
||||||
|
[drivers.ext4]
|
||||||
|
path = "\\sprout\\drivers\\ext4.efi"
|
||||||
|
|
||||||
|
# extract the full path of the first filesystem
|
||||||
|
# that contains \loader\entries as a directory
|
||||||
|
# into the value called "boot"
|
||||||
|
[extractors.boot.filesystem-device-match]
|
||||||
|
has-item = "\\loader\\entries"
|
||||||
|
|
||||||
|
# use the sprout bls module to scan a bls
|
||||||
|
# directory for entries and load them as boot
|
||||||
|
# entries in sprout, using the entry template
|
||||||
|
# as specified here. the bls action below will
|
||||||
|
# be passed the extracted values from bls.
|
||||||
|
[generators.boot.bls]
|
||||||
|
path = "$boot\\loader\\entries"
|
||||||
|
entry.title = "$title"
|
||||||
|
entry.actions = ["bls"]
|
||||||
|
|
||||||
|
# the action that is used for each bls entry above.
|
||||||
|
[actions.bls]
|
||||||
|
chainload.path = "$boot\\$chainload"
|
||||||
|
chainload.options = ["$options"]
|
||||||
|
chainload.linux-initrd = "$boot\\$initrd"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step 3, Option 1: Configure GRUB to load Sprout (recommended)
|
||||||
|
|
||||||
|
You can configure GRUB to add a boot entry for Sprout, so you can continue to use GRUB without interruption.
|
||||||
|
|
||||||
|
GRUB needs to be configured with the chainloader module to load Sprout.
|
||||||
|
|
||||||
|
### x86_64
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install x86_64 GRUB modules.
|
||||||
|
$ sudo dnf install grub2-efi-x64-modules
|
||||||
|
# Copy x86_64 GRUB modules to /boot/grub2 for use by GRUB if it isn't installed already.
|
||||||
|
$ [ ! -d /boot/grub2/x86_64-efi ] && sudo cp -r /usr/lib/grub/x86_64-efi /boot/grub2/x86_64-efi
|
||||||
|
```
|
||||||
|
|
||||||
|
### ARM64
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install ARM64 GRUB modules.
|
||||||
|
$ sudo dnf install grub2-efi-aa64-modules
|
||||||
|
# Copy ARM64 GRUB modules to /boot/grub2 for use by GRUB if it isn't installed already.
|
||||||
|
$ [ ! -d /boot/grub2/arm64-efi ] && sudo cp -r /usr/lib/grub/arm64-efi /boot/grub2/x86_64-efi
|
||||||
|
```
|
||||||
|
|
||||||
|
You will need to find the UUID of your EFI System Partition. You can do this by running the following command:
|
||||||
|
```bash
|
||||||
|
$ grep "/boot/efi" /etc/fstab | awk '{print $1}' | awk -F '=' '{print $2}'
|
||||||
|
SAMPLE-VALUE
|
||||||
|
```
|
||||||
|
|
||||||
|
The GRUB configuration for Sprout is as follows, replace `SAMPLE-VALUE` with the UUID of your EFI System Partition:
|
||||||
|
|
||||||
|
```grub
|
||||||
|
menuentry 'Sprout' $menuentry_id_option 'sprout' {
|
||||||
|
insmod part_gpt
|
||||||
|
insmod fat
|
||||||
|
insmod chain
|
||||||
|
search --no-floppy --fs-uuid --set=root SAMPLE-VALUE
|
||||||
|
chainloader /EFI/BOOT/sprout.efi
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You can append this to `/etc/grub.d/40_custom` and run the following command to update your configuration:
|
||||||
|
```bash
|
||||||
|
$ sudo grub2-mkconfig -o /boot/grub2/grub.cfg
|
||||||
|
```
|
||||||
|
|
||||||
|
To update your GRUB configuration.
|
||||||
|
|
||||||
|
Make sure to update your GRUB environment to show the menu:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ sudo grub2-editenv - set menu_auto_hide=0
|
||||||
|
```
|
||||||
|
|
||||||
|
You may now reboot your system and select Sprout from the GRUB menu.
|
||||||
|
|
||||||
|
## Step 3, Option 2: Configure your EFI firmware for Sprout
|
||||||
|
|
||||||
|
You can configure your EFI boot menu to show Sprout as an option.
|
||||||
|
|
||||||
|
To do so, please find the partition device of your EFI System Partition and run the following:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ sudo efibootmgr -d /dev/esp_partition_here -C -L 'Sprout' -l '\EFI\BOOT\sprout.efi'
|
||||||
|
```
|
||||||
|
|
||||||
|
This will add a new entry to your EFI boot menu called `Sprout` that will boot Sprout with your configuration.
|
||||||
|
|
||||||
|
Now if you boot into your UEFI firmware, you should see Sprout as an option to boot.
|
||||||
62
docs/generic-linux-setup.md
Normal file
62
docs/generic-linux-setup.md
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
# Setup Sprout to boot Linux
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- EFI System Partition mounted on a known path
|
||||||
|
- Linux kernel installed with an optional initramfs
|
||||||
|
- Linux kernel must support the EFI stub (most distro kernels)
|
||||||
|
|
||||||
|
## Step 1: Base Installation
|
||||||
|
|
||||||
|
First, identify the path to your EFI System Partition. On most systems, this is `/boot/efi`.
|
||||||
|
|
||||||
|
Download the latest sprout.efi release from the [GitHub releases page](https://github.com/edera-dev/sprout/releases).
|
||||||
|
For x86_64 systems, download the `sprout-x86_64.efi` file, and for ARM systems, download the `sprout-aarch64.efi` file.
|
||||||
|
Copy the downloaded `sprout.efi` file to `/EFI/BOOT/sprout.efi` on your EFI System Partition.
|
||||||
|
|
||||||
|
## Step 2: Copy kernel and optional initramfs
|
||||||
|
|
||||||
|
Copy the Linux kernel to `/vmlinuz-sprout` on your EFI System Partition.
|
||||||
|
If needed, copy the initramfs to `/initramfs-sprout` on your EFI System Partition.
|
||||||
|
|
||||||
|
## Step 3: Configure Sprout
|
||||||
|
|
||||||
|
Write the following file to `/sprout.toml` on your EFI System Partition,
|
||||||
|
paying attention to place the correct values:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# sprout configuration: version 1
|
||||||
|
version = 1
|
||||||
|
|
||||||
|
# add a boot entry for booting linux
|
||||||
|
# which will run the boot-linux action.
|
||||||
|
[entries.boot-linux]
|
||||||
|
title = "Boot Linux"
|
||||||
|
actions = ["boot-linux"]
|
||||||
|
|
||||||
|
# use the chainload action to boot linux via the efi stub.
|
||||||
|
# the options below are passed to the efi stub as the
|
||||||
|
# kernel command line. the initrd is loaded using the efi stub
|
||||||
|
# initrd loader mechanism.
|
||||||
|
[actions.boot-linux]
|
||||||
|
chainload.path = "\\vmlinuz-sprout"
|
||||||
|
chainload.options = ["root=/dev/sda1", "my-kernel-option"]
|
||||||
|
chainload.linux-initrd = "\\initramfs-sprout"
|
||||||
|
```
|
||||||
|
|
||||||
|
You can specify any kernel command line options you want on the chainload options line.
|
||||||
|
They will be concatenated by a space and passed to the kernel.
|
||||||
|
|
||||||
|
## Step 4: Configure EFI firmware to boot Sprout
|
||||||
|
|
||||||
|
Since Sprout is still experimental, the following commands will add a boot entry to your EFI firmware for sprout but
|
||||||
|
intentionally do not set it as the default boot entry.
|
||||||
|
|
||||||
|
To add the entry, please find the partition device of your EFI System Partition and run the following:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ sudo efibootmgr -d /dev/esp_partition_here -C -L 'Sprout' -l '\EFI\BOOT\sprout.efi'
|
||||||
|
```
|
||||||
|
|
||||||
|
This will add a new entry to your EFI boot menu called `Sprout` that will boot Sprout with your configuration.
|
||||||
|
Now if you boot into your UEFI firmware, you should see Sprout as an option to boot.
|
||||||
52
docs/windows-setup.md
Normal file
52
docs/windows-setup.md
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
# Setup Sprout to boot Windows
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Secure Boot disabled
|
||||||
|
- UEFI Windows installation
|
||||||
|
|
||||||
|
## Step 1: Base Installation
|
||||||
|
|
||||||
|
First, mount the EFI System Partition on your Windows installation:
|
||||||
|
|
||||||
|
In an administrator command prompt, run:
|
||||||
|
|
||||||
|
```batch
|
||||||
|
> mountvol X: /s
|
||||||
|
```
|
||||||
|
|
||||||
|
This will mount the EFI System Partition to the drive letter `X:`.
|
||||||
|
|
||||||
|
Please note that Windows Explorer will not let you see the drive letter `X:` where the ESP is mounted.
|
||||||
|
You will need to use the command prompt or PowerShell to access the ESP.
|
||||||
|
Standard editors can, however, be used to edit files on the ESP.
|
||||||
|
|
||||||
|
Download the latest sprout.efi release from the [GitHub releases page](https://github.com/edera-dev/sprout/releases).
|
||||||
|
For x86_64 systems, download the `sprout-x86_64.efi` file, and for ARM systems, download the `sprout-aarch64.efi` file.
|
||||||
|
Copy the downloaded `sprout.efi` file to `X:\EFI\BOOT\sprout.efi` on your EFI System Partition.
|
||||||
|
|
||||||
|
## Step 3: Configure Sprout
|
||||||
|
|
||||||
|
Write the following file to `X:\sprout.toml`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# sprout configuration: version 1
|
||||||
|
version = 1
|
||||||
|
|
||||||
|
# add a boot entry for booting Windows
|
||||||
|
# which will run the boot-windows action.
|
||||||
|
[entries.windows]
|
||||||
|
title = "Windows"
|
||||||
|
actions = ["boot-windows"]
|
||||||
|
|
||||||
|
# use the chainload action to boot the Windows bootloader.
|
||||||
|
[actions.boot-windows]
|
||||||
|
chainload.path = "\\EFI\\Microsoft\\Boot\\bootmgfw.efi"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step 4: Configure EFI Firmware to boot Sprout
|
||||||
|
|
||||||
|
It is not trivial to add an EFI boot entry inside Windows.
|
||||||
|
However, most firmware lets you load arbitrary EFI files from the firmware settings.
|
||||||
|
|
||||||
|
You can boot `\EFI\BOOT\sprout.efi` from firmware to boot Sprout.
|
||||||
11
hack/dev/configs/shell.sprout.toml
Normal file
11
hack/dev/configs/shell.sprout.toml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
version = 1
|
||||||
|
|
||||||
|
[extractors.boot.filesystem-device-match]
|
||||||
|
has-item = "\\EFI\\BOOT\\shell.efi"
|
||||||
|
|
||||||
|
[actions.chainload-shell]
|
||||||
|
chainload.path = "$boot\\EFI\\BOOT\\shell.efi"
|
||||||
|
|
||||||
|
[entries.xen]
|
||||||
|
title = "Boot Shell"
|
||||||
|
actions = ["chainload-shell"]
|
||||||
@@ -1,31 +1,36 @@
|
|||||||
use crate::config::{RootConfiguration, latest_version};
|
use crate::config::{RootConfiguration, latest_version};
|
||||||
|
use crate::options::SproutOptions;
|
||||||
use crate::utils;
|
use crate::utils;
|
||||||
use anyhow::{Context, Result, bail};
|
use anyhow::{Context, Result, bail};
|
||||||
|
use log::info;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use toml::Value;
|
use toml::Value;
|
||||||
use uefi::proto::device_path::LoadedImageDevicePath;
|
use uefi::proto::device_path::LoadedImageDevicePath;
|
||||||
|
|
||||||
/// Loads the raw configuration from the sprout.toml file as data.
|
/// Loads the raw configuration from the sprout config file as data.
|
||||||
fn load_raw_config() -> Result<Vec<u8>> {
|
fn load_raw_config(options: &SproutOptions) -> Result<Vec<u8>> {
|
||||||
// Open the LoadedImageDevicePath protocol to get the path to the current image.
|
// Open the LoadedImageDevicePath protocol to get the path to the current image.
|
||||||
let current_image_device_path_protocol =
|
let current_image_device_path_protocol =
|
||||||
uefi::boot::open_protocol_exclusive::<LoadedImageDevicePath>(uefi::boot::image_handle())
|
uefi::boot::open_protocol_exclusive::<LoadedImageDevicePath>(uefi::boot::image_handle())
|
||||||
.context("unable to get loaded image device path")?;
|
.context("unable to get loaded image device path")?;
|
||||||
// Acquire the device path as a boxed device path.
|
// Acquire the device path as a boxed device path.
|
||||||
let path = current_image_device_path_protocol.deref().to_boxed();
|
let path = current_image_device_path_protocol.deref().to_boxed();
|
||||||
// Read the contents of the sprout.toml file.
|
|
||||||
let content = utils::read_file_contents(&path, "sprout.toml")
|
info!("configuration file: {}", options.config);
|
||||||
.context("unable to read sprout.toml file")?;
|
|
||||||
// Return the contents of the sprout.toml file.
|
// Read the contents of the sprout config file.
|
||||||
|
let content = utils::read_file_contents(&path, &options.config)
|
||||||
|
.context("unable to read sprout config file")?;
|
||||||
|
// Return the contents of the sprout config file.
|
||||||
Ok(content)
|
Ok(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Loads the [RootConfiguration] for Sprout.
|
/// Loads the [RootConfiguration] for Sprout.
|
||||||
pub fn load() -> Result<RootConfiguration> {
|
pub fn load(options: &SproutOptions) -> Result<RootConfiguration> {
|
||||||
// Load the raw configuration from the sprout.toml file.
|
// Load the raw configuration from the sprout config file.
|
||||||
let content = load_raw_config()?;
|
let content = load_raw_config(options)?;
|
||||||
// Parse the raw configuration into a toml::Value which can represent any TOML file.
|
// Parse the raw configuration into a toml::Value which can represent any TOML file.
|
||||||
let value: Value = toml::from_slice(&content).context("unable to parse sprout.toml file")?;
|
let value: Value = toml::from_slice(&content).context("unable to parse sprout config file")?;
|
||||||
|
|
||||||
// Check the version of the configuration without parsing the full configuration.
|
// Check the version of the configuration without parsing the full configuration.
|
||||||
let version = value
|
let version = value
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use crate::actions::ActionDeclaration;
|
use crate::actions::ActionDeclaration;
|
||||||
|
use crate::options::SproutOptions;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use std::collections::{BTreeMap, BTreeSet};
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
@@ -13,15 +14,18 @@ pub struct RootContext {
|
|||||||
actions: BTreeMap<String, ActionDeclaration>,
|
actions: BTreeMap<String, ActionDeclaration>,
|
||||||
/// The device path of the loaded Sprout image.
|
/// The device path of the loaded Sprout image.
|
||||||
loaded_image_path: Option<Box<DevicePath>>,
|
loaded_image_path: Option<Box<DevicePath>>,
|
||||||
|
/// The global options of Sprout.
|
||||||
|
options: SproutOptions,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RootContext {
|
impl RootContext {
|
||||||
/// Creates a new root context with the `loaded_image_device_path` which will be stored
|
/// Creates a new root context with the `loaded_image_device_path` which will be stored
|
||||||
/// in the context for easy access.
|
/// in the context for easy access.
|
||||||
pub fn new(loaded_image_device_path: Box<DevicePath>) -> Self {
|
pub fn new(loaded_image_device_path: Box<DevicePath>, options: SproutOptions) -> Self {
|
||||||
Self {
|
Self {
|
||||||
actions: BTreeMap::new(),
|
actions: BTreeMap::new(),
|
||||||
loaded_image_path: Some(loaded_image_device_path),
|
loaded_image_path: Some(loaded_image_device_path),
|
||||||
|
options,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,6 +45,11 @@ impl RootContext {
|
|||||||
.as_deref()
|
.as_deref()
|
||||||
.ok_or_else(|| anyhow!("no loaded image path"))
|
.ok_or_else(|| anyhow!("no loaded image path"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Access the global Sprout options.
|
||||||
|
pub fn options(&self) -> &SproutOptions {
|
||||||
|
&self.options
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A context of Sprout. This is passed around different parts of Sprout and represents
|
/// A context of Sprout. This is passed around different parts of Sprout and represents
|
||||||
|
|||||||
@@ -33,6 +33,13 @@ fn default_bls_path() -> String {
|
|||||||
BLS_TEMPLATE_PATH.to_string()
|
BLS_TEMPLATE_PATH.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(azenla): remove this once variable substitution is implemented.
|
||||||
|
/// This function is used to remove the `tuned_initrd` variable from entry values.
|
||||||
|
/// Fedora uses tuned which adds an initrd that shouldn't be used.
|
||||||
|
fn quirk_initrd_remove_tuned(input: String) -> String {
|
||||||
|
input.replace("$tuned_initrd", "").trim().to_string()
|
||||||
|
}
|
||||||
|
|
||||||
/// Generates entries from the BLS entries directory using the specified `bls` configuration and
|
/// Generates entries from the BLS entries directory using the specified `bls` configuration and
|
||||||
/// `context`. The BLS conversion is best-effort and will ignore any unsupported entries.
|
/// `context`. The BLS conversion is best-effort and will ignore any unsupported entries.
|
||||||
pub fn generate(
|
pub fn generate(
|
||||||
@@ -108,10 +115,18 @@ pub fn generate(
|
|||||||
|
|
||||||
// Produce a new sprout context for the entry with the extracted values.
|
// Produce a new sprout context for the entry with the extracted values.
|
||||||
let mut context = context.fork();
|
let mut context = context.fork();
|
||||||
context.set("title", entry.title().unwrap_or(name));
|
|
||||||
context.set("chainload", entry.chainload_path().unwrap_or_default());
|
let title = entry.title().unwrap_or(name);
|
||||||
context.set("options", entry.options().unwrap_or_default());
|
let chainload = entry.chainload_path().unwrap_or_default();
|
||||||
context.set("initrd", entry.initrd_path().unwrap_or_default());
|
let options = entry.options().unwrap_or_default();
|
||||||
|
|
||||||
|
// Put the initrd through a quirk modifier to support Fedora.
|
||||||
|
let initrd = quirk_initrd_remove_tuned(entry.initrd_path().unwrap_or_default());
|
||||||
|
|
||||||
|
context.set("title", title);
|
||||||
|
context.set("chainload", chainload);
|
||||||
|
context.set("options", options);
|
||||||
|
context.set("initrd", initrd);
|
||||||
|
|
||||||
// Add the entry to the list with a frozen context.
|
// Add the entry to the list with a frozen context.
|
||||||
entries.push((context.freeze(), bls.entry.clone()));
|
entries.push((context.freeze(), bls.entry.clone()));
|
||||||
|
|||||||
23
src/main.rs
23
src/main.rs
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
use crate::context::{RootContext, SproutContext};
|
use crate::context::{RootContext, SproutContext};
|
||||||
use crate::phases::phase;
|
use crate::phases::phase;
|
||||||
use anyhow::{Context, Result, bail};
|
use anyhow::{Context, Result};
|
||||||
use log::info;
|
use log::info;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
@@ -37,6 +37,9 @@ pub mod phases;
|
|||||||
/// setup: Code that initializes the UEFI environment for Sprout.
|
/// setup: Code that initializes the UEFI environment for Sprout.
|
||||||
pub mod setup;
|
pub mod setup;
|
||||||
|
|
||||||
|
/// options: Parse the options of the Sprout executable.
|
||||||
|
pub mod options;
|
||||||
|
|
||||||
/// utils: Utility functions that are used by other parts of Sprout.
|
/// utils: Utility functions that are used by other parts of Sprout.
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
|
||||||
@@ -47,10 +50,13 @@ fn main() -> Result<()> {
|
|||||||
// Initialize the basic UEFI environment.
|
// Initialize the basic UEFI environment.
|
||||||
setup::init()?;
|
setup::init()?;
|
||||||
|
|
||||||
|
// Parse the options to the sprout executable.
|
||||||
|
let options = options::parse().context("unable to parse options")?;
|
||||||
|
|
||||||
// Load the configuration of sprout.
|
// Load the configuration of sprout.
|
||||||
// At this point, the configuration has been validated and the specified
|
// At this point, the configuration has been validated and the specified
|
||||||
// version is checked to ensure compatibility.
|
// version is checked to ensure compatibility.
|
||||||
let config = config::loader::load()?;
|
let config = config::loader::load(&options)?;
|
||||||
|
|
||||||
// Load the root context.
|
// Load the root context.
|
||||||
// This is done in a block to ensure the release of the LoadedImageDevicePath protocol.
|
// This is done in a block to ensure the release of the LoadedImageDevicePath protocol.
|
||||||
@@ -64,7 +70,7 @@ fn main() -> Result<()> {
|
|||||||
"loaded image path: {}",
|
"loaded image path: {}",
|
||||||
loaded_image_path.to_string(DisplayOnly(false), AllowShortcuts(false))?
|
loaded_image_path.to_string(DisplayOnly(false), AllowShortcuts(false))?
|
||||||
);
|
);
|
||||||
RootContext::new(loaded_image_path)
|
RootContext::new(loaded_image_path, options)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Insert the configuration actions into the root context.
|
// Insert the configuration actions into the root context.
|
||||||
@@ -144,9 +150,14 @@ fn main() -> Result<()> {
|
|||||||
// Execute the late phase.
|
// Execute the late phase.
|
||||||
phase(context.clone(), &config.phases.late).context("unable to execute late phase")?;
|
phase(context.clone(), &config.phases.late).context("unable to execute late phase")?;
|
||||||
|
|
||||||
// Pick the first entry from the list of final entries until a boot menu is implemented.
|
// Use the boot option if possible, otherwise pick the first entry.
|
||||||
let Some((context, entry)) = final_entries.first() else {
|
let (context, entry) = if let Some(ref boot) = context.root().options().boot {
|
||||||
bail!("no entries found");
|
final_entries
|
||||||
|
.iter()
|
||||||
|
.find(|(_context, entry)| &entry.title == boot)
|
||||||
|
.context(format!("unable to find entry: {boot}"))?
|
||||||
|
} else {
|
||||||
|
final_entries.first().context("no entries found")?
|
||||||
};
|
};
|
||||||
|
|
||||||
// Execute all the actions for the selected entry.
|
// Execute all the actions for the selected entry.
|
||||||
|
|||||||
117
src/options.rs
Normal file
117
src/options.rs
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
use anyhow::{Context, Result, bail};
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
/// Default configuration file path.
|
||||||
|
const DEFAULT_CONFIG_PATH: &str = "\\sprout.toml";
|
||||||
|
|
||||||
|
/// The parsed options of sprout.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct SproutOptions {
|
||||||
|
/// Path to a configuration file to load.
|
||||||
|
pub config: String,
|
||||||
|
/// Entry to boot without showing the boot menu.
|
||||||
|
pub boot: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The default Sprout options.
|
||||||
|
impl Default for SproutOptions {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
config: DEFAULT_CONFIG_PATH.to_string(),
|
||||||
|
boot: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// For minimalism, we don't want a full argument parser. Instead, we use
|
||||||
|
/// a simple --xyz = xyz: None and --abc 123 = abc: Some("123") format.
|
||||||
|
/// We also support --abc=123 = abc: Some("123") format.
|
||||||
|
fn parse_raw() -> Result<BTreeMap<String, Option<String>>> {
|
||||||
|
// Collect all the arguments to Sprout.
|
||||||
|
// Skip the first argument which is the path to our executable.
|
||||||
|
let args = std::env::args().skip(1).collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// Represent options as key-value pairs.
|
||||||
|
let mut options = BTreeMap::new();
|
||||||
|
|
||||||
|
// Iterators makes this way easier.
|
||||||
|
let mut iterator = args.into_iter().peekable();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
// Consume the next option, if any.
|
||||||
|
let Some(option) = iterator.next() else {
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
|
||||||
|
// If the doesn't start with --, that is invalid.
|
||||||
|
if !option.starts_with("--") {
|
||||||
|
bail!("invalid option: {option}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strip the -- prefix off.
|
||||||
|
let mut option = option["--".len()..].trim().to_string();
|
||||||
|
|
||||||
|
// An optional value.
|
||||||
|
let mut value = None;
|
||||||
|
|
||||||
|
// Check if the option is of the form --abc=123
|
||||||
|
if option.contains("=") {
|
||||||
|
let Some((part_key, part_value)) = option.split_once("=") else {
|
||||||
|
bail!("invalid option: {option}");
|
||||||
|
};
|
||||||
|
|
||||||
|
let part_key = part_key.to_string();
|
||||||
|
let part_value = part_value.to_string();
|
||||||
|
option = part_key;
|
||||||
|
value = Some(part_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if value.is_none() {
|
||||||
|
// Check for the next value.
|
||||||
|
let maybe_next = iterator.peek();
|
||||||
|
|
||||||
|
// If the next value isn't another option, set the value to the next value.
|
||||||
|
// Otherwise, it is an empty string.
|
||||||
|
value = if let Some(next) = maybe_next
|
||||||
|
&& !next.starts_with("--")
|
||||||
|
{
|
||||||
|
iterator.next()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error on empty option names.
|
||||||
|
if option.is_empty() {
|
||||||
|
bail!("invalid empty option: {option}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert the option and the value into the map.
|
||||||
|
options.insert(option, value);
|
||||||
|
}
|
||||||
|
Ok(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse the arguments to Sprout as a [SproutOptions] structure.
|
||||||
|
pub fn parse() -> Result<SproutOptions> {
|
||||||
|
// Use the default value of sprout options and have the raw options be parsed into it.
|
||||||
|
let mut result = SproutOptions::default();
|
||||||
|
let options = parse_raw().context("unable to parse options")?;
|
||||||
|
|
||||||
|
for (key, value) in options {
|
||||||
|
match key.as_str() {
|
||||||
|
"config" => {
|
||||||
|
// The configuration file to load.
|
||||||
|
result.config = value.context("--config option requires a value")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
"boot" => {
|
||||||
|
// The entry to boot.
|
||||||
|
result.boot = Some(value.context("--boot option requires a value")?);
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => bail!("unknown option: --{key}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user