6 Commits

Author SHA1 Message Date
527ce4b1b4 sprout: version 0.0.13 2025-10-27 22:44:21 -04:00
ebd3c07bf5 fix(autoconfigure): reinject values after configuration changes 2025-10-27 22:43:37 -04:00
e8b7b967fa chore(docs): change windows setup guide to use autoconfiguration 2025-10-27 21:36:48 -04:00
2bf4013938 feat(autoconfigure): improved linux support and windows support 2025-10-27 19:47:21 -04:00
6819e55e23 Merge pull request #19 from edera-dev/dependabot/docker/docker-updates-d0b0844295
chore(deps): bump rustlang/rust from `141e9a7` to `7cba2ed` in the docker-updates group
2025-10-27 19:03:00 -04:00
dependabot[bot]
3ffda86544 chore(deps): bump rustlang/rust in the docker-updates group
Bumps the docker-updates group with 1 update: rustlang/rust.


Updates `rustlang/rust` from `141e9a7` to `7cba2ed`

---
updated-dependencies:
- dependency-name: rustlang/rust
  dependency-version: nightly-alpine
  dependency-type: direct:production
  dependency-group: docker-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-27 12:48:29 +00:00
9 changed files with 121 additions and 24 deletions

2
Cargo.lock generated
View File

@@ -116,7 +116,7 @@ dependencies = [
[[package]] [[package]]
name = "edera-sprout" name = "edera-sprout"
version = "0.0.12" version = "0.0.13"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"image", "image",

View File

@@ -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.12" version = "0.0.13"
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"

View File

@@ -2,7 +2,7 @@
ARG RUST_PROFILE=release ARG RUST_PROFILE=release
ARG RUST_TARGET_SUBDIR=release ARG RUST_TARGET_SUBDIR=release
FROM --platform=$BUILDPLATFORM rustlang/rust:nightly-alpine@sha256:141e9a7f13f77237dd4d462364c3a1b21cb8a6791d8924c409573e77b788af5e AS build FROM --platform=$BUILDPLATFORM rustlang/rust:nightly-alpine@sha256:7cba2edabb6ba0e92cd806cd1e0acae99d50f63e5b9c9ad842766d13c896d68c AS build
RUN apk --no-cache add musl-dev busybox-static RUN apk --no-cache add musl-dev busybox-static
ARG RUST_PROFILE ARG RUST_PROFILE
RUN adduser -S -s /bin/sh build RUN adduser -S -s /bin/sh build

View File

@@ -33,15 +33,10 @@ Write the following file to `X:\sprout.toml`:
# sprout configuration: version 1 # sprout configuration: version 1
version = 1 version = 1
# add a boot entry for booting Windows # global options.
# which will run the boot-windows action. [options]
[entries.windows] # enable autoconfiguration to detect Windows.
title = "Windows" autoconfigure = true
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 ## Step 4: Configure EFI Firmware to boot Sprout

View File

@@ -3,7 +3,7 @@ use crate::utils;
use crate::utils::media_loader::MediaLoaderHandle; use crate::utils::media_loader::MediaLoaderHandle;
use crate::utils::media_loader::constants::linux::LINUX_EFI_INITRD_MEDIA_GUID; use crate::utils::media_loader::constants::linux::LINUX_EFI_INITRD_MEDIA_GUID;
use anyhow::{Context, Result, bail}; use anyhow::{Context, Result, bail};
use log::{error, info}; use log::error;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::rc::Rc; use std::rc::Rc;
use uefi::CString16; use uefi::CString16;
@@ -69,8 +69,6 @@ pub fn chainload(context: Rc<SproutContext>, configuration: &ChainloadConfigurat
.context("unable to convert chainloader options to CString16")?, .context("unable to convert chainloader options to CString16")?,
); );
info!("options: {}", options);
if options.num_bytes() > u32::MAX as usize { if options.num_bytes() > u32::MAX as usize {
bail!("chainloader options too large"); bail!("chainloader options too large");
} }
@@ -85,14 +83,17 @@ pub fn chainload(context: Rc<SproutContext>, configuration: &ChainloadConfigurat
options_holder = Some(options); options_holder = Some(options);
} }
// Stamp the initrd path, if provided.
let initrd = configuration
.linux_initrd
.as_ref()
.map(|item| context.stamp(item));
// The initrd can be None or empty, so we need to collapse that into a single Option. // The initrd can be None or empty, so we need to collapse that into a single Option.
let initrd = utils::empty_is_none(configuration.linux_initrd.as_ref()); let initrd = utils::empty_is_none(initrd);
// If an initrd is provided, register it with the EFI stack. // If an initrd is provided, register it with the EFI stack.
let mut initrd_handle = None; let mut initrd_handle = None;
if let Some(linux_initrd) = initrd { if let Some(linux_initrd) = initrd {
// Stamp the path to the initrd.
let linux_initrd = context.stamp(linux_initrd);
let content = utils::read_file_contents(context.root().loaded_image_path()?, &linux_initrd) let content = utils::read_file_contents(context.root().loaded_image_path()?, &linux_initrd)
.context("unable to read linux initrd")?; .context("unable to read linux initrd")?;
let handle = let handle =

View File

@@ -12,6 +12,9 @@ pub mod bls;
/// on BLS-enabled filesystems as it may make duplicate entries. /// on BLS-enabled filesystems as it may make duplicate entries.
pub mod linux; pub mod linux;
/// windows: autodetect and configure Windows boot configurations.
pub mod windows;
/// Generate a [RootConfiguration] based on the environment. /// Generate a [RootConfiguration] based on the environment.
/// Intakes a `config` to use as the basis of the autoconfiguration. /// Intakes a `config` to use as the basis of the autoconfiguration.
pub fn autoconfigure(config: &mut RootConfiguration) -> Result<()> { pub fn autoconfigure(config: &mut RootConfiguration) -> Result<()> {
@@ -44,6 +47,10 @@ pub fn autoconfigure(config: &mut RootConfiguration) -> Result<()> {
linux::scan(&mut filesystem, &root, config) linux::scan(&mut filesystem, &root, config)
.context("unable to scan for linux configurations")?; .context("unable to scan for linux configurations")?;
} }
// Always look for Windows configurations.
windows::scan(&mut filesystem, &root, config)
.context("unable to scan for windows configurations")?;
} }
Ok(()) Ok(())

View File

@@ -6,6 +6,7 @@ use crate::generators::GeneratorDeclaration;
use crate::generators::list::ListConfiguration; use crate::generators::list::ListConfiguration;
use crate::utils; use crate::utils;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use log::info;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use uefi::CString16; use uefi::CString16;
use uefi::fs::{FileSystem, Path}; use uefi::fs::{FileSystem, Path};
@@ -23,7 +24,7 @@ const SCAN_LOCATIONS: &[&str] = &["/boot", "/"];
const KERNEL_PREFIXES: &[&str] = &["vmlinuz"]; const KERNEL_PREFIXES: &[&str] = &["vmlinuz"];
/// Prefixes of initramfs files to match to. /// Prefixes of initramfs files to match to.
const INITRAMFS_PREFIXES: &[&str] = &["initramfs", "initrd"]; const INITRAMFS_PREFIXES: &[&str] = &["initramfs", "initrd", "initrd.img"];
/// Pair of kernel and initramfs. /// Pair of kernel and initramfs.
/// This is what scanning a directory is meant to find. /// This is what scanning a directory is meant to find.
@@ -158,7 +159,7 @@ pub fn scan(
// Kernel pairs are detected, generate a list configuration for it. // Kernel pairs are detected, generate a list configuration for it.
let generator = ListConfiguration { let generator = ListConfiguration {
entry: EntryDeclaration { entry: EntryDeclaration {
title: "Boot Linux $kernel".to_string(), title: "Boot Linux $name".to_string(),
actions: vec![chainload_action_name.clone()], actions: vec![chainload_action_name.clone()],
..Default::default() ..Default::default()
}, },
@@ -166,8 +167,14 @@ pub fn scan(
.into_iter() .into_iter()
.map(|pair| { .map(|pair| {
BTreeMap::from_iter(vec![ BTreeMap::from_iter(vec![
("kernel".to_string(), pair.kernel), ("name".to_string(), pair.kernel.clone()),
("initrd".to_string(), pair.initramfs.unwrap_or_default()), ("kernel".to_string(), format!("{}{}", root, pair.kernel)),
(
"initrd".to_string(),
pair.initramfs
.map(|initramfs| format!("{}{}", root, initramfs))
.unwrap_or_default(),
),
]) ])
}) })
.collect(), .collect(),
@@ -194,9 +201,9 @@ pub fn scan(
// Note that we don't need an extra \\ in the paths here. // Note that we don't need an extra \\ in the paths here.
// The root already contains a trailing slash. // The root already contains a trailing slash.
let chainload = ChainloadConfiguration { let chainload = ChainloadConfiguration {
path: format!("{}$kernel", root), path: "$kernel".to_string(),
options: vec!["$linux-options".to_string()], options: vec!["$linux-options".to_string()],
linux_initrd: Some(format!("{}$initrd", root)), linux_initrd: Some("$initrd".to_string()),
}; };
// Insert the chainload action into the configuration. // Insert the chainload action into the configuration.
@@ -208,6 +215,8 @@ pub fn scan(
}, },
); );
info!("{:?}", config);
// We had a Linux kernel, so return true to indicate something was found. // We had a Linux kernel, so return true to indicate something was found.
Ok(true) Ok(true)
} }

View File

@@ -0,0 +1,80 @@
use crate::actions::ActionDeclaration;
use crate::actions::chainload::ChainloadConfiguration;
use crate::config::RootConfiguration;
use crate::entries::EntryDeclaration;
use crate::utils;
use anyhow::{Context, Result};
use uefi::CString16;
use uefi::fs::{FileSystem, Path};
use uefi::proto::device_path::DevicePath;
use uefi::proto::device_path::text::{AllowShortcuts, DisplayOnly};
/// The name prefix of the Windows chainload action that will be used to boot Windows.
const WINDOWS_CHAINLOAD_ACTION_PREFIX: &str = "windows-chainload-";
/// Windows boot manager path.
const BOOTMGR_FW_PATH: &str = "\\EFI\\Microsoft\\Boot\\bootmgfw.efi";
/// Scan the specified `filesystem` for Windows configurations.
pub fn scan(
filesystem: &mut FileSystem,
root: &DevicePath,
config: &mut RootConfiguration,
) -> Result<bool> {
// Convert the boot manager firmware path to a path.
let bootmgr_fw_path =
CString16::try_from(BOOTMGR_FW_PATH).context("unable to convert path to CString16")?;
let bootmgr_fw_path = Path::new(&bootmgr_fw_path);
// Check if the boot manager firmware path exists, if it doesn't, return false.
if !filesystem
.try_exists(bootmgr_fw_path)
.context("unable to check if bootmgr firmware path exists")?
{
return Ok(false);
}
// Convert the device path root to a string we can use in the configuration.
let mut root = root
.to_string(DisplayOnly(false), AllowShortcuts(false))
.context("unable to convert device root to string")?
.to_string();
// Add a trailing slash to the root to ensure the path is valid.
root.push('/');
// Generate a unique hash of the root path.
let root_unique_hash = utils::unique_hash(&root);
// Generate a unique name for the Windows chainload action.
let chainload_action_name = format!("{}{}", WINDOWS_CHAINLOAD_ACTION_PREFIX, root_unique_hash,);
// Generate an entry name for Windows.
let entry_name = format!("autoconfigure-windows-{}", root_unique_hash,);
// Create an entry for Windows and insert it into the configuration.
let entry = EntryDeclaration {
title: "Boot Windows".to_string(),
actions: vec![chainload_action_name.clone()],
values: Default::default(),
};
config.entries.insert(entry_name, entry);
// Generate a chainload configuration for Windows.
let chainload = ChainloadConfiguration {
path: format!("{}{}", root, bootmgr_fw_path),
options: vec![],
..Default::default()
};
// Insert the chainload action into the configuration.
config.actions.insert(
chainload_action_name,
ActionDeclaration {
chainload: Some(chainload),
..Default::default()
},
);
// We have a Windows boot entry, so return true to indicate something was found.
Ok(true)
}

View File

@@ -118,6 +118,9 @@ fn run() -> Result<()> {
// Extend the root context with the autoconfigured actions. // Extend the root context with the autoconfigured actions.
root.actions_mut().extend(config.actions); root.actions_mut().extend(config.actions);
// Insert any modified root values.
context.insert(&config.values);
} }
// Refreeze the context to ensure that further operations can share the context. // Refreeze the context to ensure that further operations can share the context.
@@ -248,6 +251,8 @@ fn main() -> Result<()> {
for (index, stack) in error.chain().enumerate() { for (index, stack) in error.chain().enumerate() {
error!("[{}]: {}", index, stack); error!("[{}]: {}", index, stack);
} }
// Sleep for 10 seconds to allow the user to read the error.
uefi::boot::stall(Duration::from_secs(10));
} }
// Sprout doesn't necessarily guarantee anything was booted. // Sprout doesn't necessarily guarantee anything was booted.