├── .gitignore ├── i18n.toml ├── src ├── version │ ├── mod.rs │ └── generic_version.rs ├── macros.rs ├── i18n.rs ├── kernel │ ├── mod.rs │ └── generic_kernel.rs ├── cli.rs ├── util.rs ├── kernel_manager.rs ├── main.rs └── config.rs ├── install-assets.sh ├── LICENSE ├── README.md ├── .gitlab-ci.yml ├── Cargo.toml ├── i18n ├── zh-CN │ └── systemd_boot_friend_rs.ftl ├── zh-TW │ └── systemd_boot_friend_rs.ftl └── en-US │ └── systemd_boot_friend_rs.ftl ├── completions ├── sbf.fish ├── _sbf └── sbf.bash └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /.idea 3 | -------------------------------------------------------------------------------- /i18n.toml: -------------------------------------------------------------------------------- 1 | fallback_language = "en-US" 2 | 3 | # Use the fluent localization system. 4 | [fluent] 5 | # The paths inside the assets directory should be structured like so: 6 | # `assets_dir/{language}/{domain}.ftl` 7 | assets_dir = "i18n" 8 | -------------------------------------------------------------------------------- /src/version/mod.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use std::fmt::Display; 3 | 4 | pub trait Version: Display + Sized { 5 | fn parse(input: &str) -> Result; 6 | } 7 | 8 | #[cfg(feature = "generic")] 9 | pub mod generic_version; 10 | -------------------------------------------------------------------------------- /install-assets.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | 3 | PREFIX="${PREFIX:-/usr/local}" 4 | 5 | # install completions 6 | install -dv "${PREFIX}/share/zsh/functions/Completion/Linux/" 7 | install -Dvm644 completions/_sbf "${PREFIX}/share/zsh/functions/Completion/Linux/" 8 | install -dv "${PREFIX}/share/fish/vendor_completions.d/" 9 | install -Dvm644 completions/sbf.fish "${PREFIX}/share/fish/vendor_completions.d/" 10 | install -dv "${PREFIX}/share/bash-completion/completions/" 11 | install -Dvm644 completions/sbf.bash "${PREFIX}/share/bash-completion/completions/" 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 OriginCode 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Your systemd-boot's best friend ever (hopefully) 2 | 3 | A kernel version manager for systemd-boot 4 | 5 | ## Usage 6 | 7 | First initialize friend and systemd-boot, this will also 8 | install the newest kernel to the specific path for systemd-boot. 9 | 10 | ```bash 11 | sbf init 12 | ``` 13 | 14 | You can also manually select the kernel(s) you would like to register as boot 15 | entry(s). 16 | 17 | ```bash 18 | sbf install-kernel 19 | ``` 20 | 21 | Subcommands are also supported, you may look up for them by 22 | executing the following command. 23 | 24 | ```bash 25 | sbf --help 26 | ``` 27 | 28 | For further information, visit https://wiki.aosc.io/software/systemd-boot-friend/ 29 | 30 | ## Installation 31 | 32 | ```bash 33 | cargo build --release 34 | install -Dm755 target/release/systemd-boot-friend /usr/local/bin/systemd-boot-friend 35 | PREFIX=/usr/local ./install-assets.sh 36 | ``` 37 | 38 | Or from crates.io 39 | 40 | ```bash 41 | cargo install systemd-boot-friend-rs 42 | ``` 43 | 44 | ## Dependencies 45 | 46 | Building: 47 | 48 | - Rust w/ Cargo 49 | - C compiler 50 | - make (when GCC LTO is used, not needed for Clang) 51 | 52 | Runtime: 53 | 54 | - Systemd 55 | -------------------------------------------------------------------------------- /src/macros.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! println_with_prefix { 3 | ($($arg:tt)+) => { 4 | eprint!("{}", console::style("[systemd-boot-friend] ").bold()); 5 | eprintln!($($arg)+); 6 | }; 7 | } 8 | 9 | #[macro_export] 10 | macro_rules! println_with_fl { 11 | ($message_id:literal) => { 12 | eprintln!("{}", fl!($message_id)) 13 | }; 14 | 15 | ($message_id:literal, $($args:expr), *) => { 16 | eprintln!("{}", fl!($message_id, $($args), *)) 17 | } 18 | } 19 | 20 | #[macro_export] 21 | macro_rules! print_block_with_fl { 22 | ($message_id:literal) => { 23 | eprintln!("\n{}\n", fl!($message_id)) 24 | }; 25 | 26 | ($message_id:literal, $($args:expr), *) => { 27 | eprintln!("\n{}\n", fl!($message_id, $($args), *)) 28 | } 29 | } 30 | 31 | #[macro_export] 32 | macro_rules! println_with_prefix_and_fl { 33 | ($message_id:literal) => { 34 | for line in fl!($message_id).lines() { 35 | println_with_prefix!("{}", line); 36 | } 37 | }; 38 | 39 | ($message_id:literal, $($args:expr), *) => { 40 | for line in fl!($message_id, $($args), *).lines() { 41 | println_with_prefix!("{}", line); 42 | } 43 | }; 44 | } 45 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | # To contribute improvements to CI/CD templates, please follow the Development guide at: 2 | # https://docs.gitlab.com/ee/development/cicd/templates.html 3 | # This specific template is located at: 4 | # https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Rust.gitlab-ci.yml 5 | 6 | # Official language image. Look for the different tagged releases at: 7 | # https://hub.docker.com/r/library/rust/tags/ 8 | image: "rust:latest" 9 | 10 | # Optional: Pick zero or more services to be used on all builds. 11 | # Only needed when using a docker container to run your tests in. 12 | # Check out: http://docs.gitlab.com/ee/ci/docker/using_docker_images.html#what-is-a-service 13 | # services: 14 | # - mysql:latest 15 | # - redis:latest 16 | # - postgres:latest 17 | 18 | # Optional: Install a C compiler, cmake and git into the container. 19 | # You will often need this when you (or any of your dependencies) depends on C code. 20 | # before_script: 21 | # - apt-get update -yqq 22 | # - apt-get install -yqq --no-install-recommends build-essential 23 | 24 | # Use cargo to test the project 25 | test:cargo: 26 | script: 27 | - rustc --version && cargo --version # Print version info for debugging 28 | - cargo test --workspace --verbose 29 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "systemd-boot-friend-rs" 3 | version = "0.27.5" 4 | license = "MIT" 5 | authors = ["Kaiyang Wu "] 6 | edition = "2024" 7 | description = "Kernel version manager for systemd-boot" 8 | repository = "https://github.com/AOSC-Dev/systemd-boot-friend-rs" 9 | homepage = "https://github.com/AOSC-Dev/systemd-boot-friend-rs" 10 | readme = "README.md" 11 | 12 | [[bin]] 13 | name = "sbf" 14 | path = "src/main.rs" 15 | 16 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 17 | 18 | [dependencies] 19 | serde = { version = "1.0", features = ["derive", "rc"] } 20 | serde_json = "1.0" 21 | toml = "0.9" 22 | anyhow = "1.0" 23 | clap = { version = "4.0", features = ["derive"] } 24 | console = "0.16" 25 | dialoguer = "0.11" 26 | lazy_static = "1.4" 27 | regex = "1.5" 28 | nom = "8.0" 29 | libsdbootconf = "0.11" 30 | same-file = "1" 31 | # i18n 32 | i18n-embed = { version = "0.16", features = ["fluent-system", "desktop-requester"]} 33 | i18n-embed-fl = "0.10" 34 | rust-embed = "8.0" 35 | unic-langid = "0.9" 36 | 37 | [build-dependencies] 38 | clap = { version = "4.0", features = ["derive"] } 39 | clap_complete = "4.0" 40 | 41 | [features] 42 | default = ["generic"] 43 | generic = [] 44 | 45 | [profile.release] 46 | lto = true 47 | -------------------------------------------------------------------------------- /src/i18n.rs: -------------------------------------------------------------------------------- 1 | // From AOSC-Dev/atm 2 | 3 | use anyhow::Result; 4 | use i18n_embed::{ 5 | fluent::{fluent_language_loader, FluentLanguageLoader}, 6 | DesktopLanguageRequester, LanguageLoader, 7 | }; 8 | use lazy_static::lazy_static; 9 | use rust_embed::RustEmbed; 10 | use unic_langid::LanguageIdentifier; 11 | 12 | #[macro_export] 13 | macro_rules! fl { 14 | ($message_id:literal) => {{ 15 | i18n_embed_fl::fl!($crate::I18N_LOADER, $message_id) 16 | }}; 17 | 18 | ($message_id:literal, $($args:expr),*) => {{ 19 | i18n_embed_fl::fl!($crate::I18N_LOADER, $message_id, $($args), *) 20 | }}; 21 | } 22 | 23 | lazy_static! { 24 | pub static ref I18N_LOADER: FluentLanguageLoader = 25 | load_i18n().expect("Unable to load i18n strings."); 26 | } 27 | 28 | #[derive(RustEmbed)] 29 | #[folder = "i18n"] 30 | struct Localizations; 31 | 32 | fn load_i18n() -> Result { 33 | let language_loader: FluentLanguageLoader = fluent_language_loader!(); 34 | let requested_languages = DesktopLanguageRequester::requested_languages(); 35 | let fallback_language: Vec = vec!["en-US".parse().unwrap()]; 36 | let languages: Vec = requested_languages 37 | .into_iter() 38 | .chain(fallback_language) 39 | .collect(); 40 | language_loader.load_languages(&Localizations, &languages)?; 41 | 42 | Ok(language_loader) 43 | } 44 | -------------------------------------------------------------------------------- /src/kernel/mod.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use libsdbootconf::SystemdBootConf; 3 | use same_file::is_same_file; 4 | use std::{cell::RefCell, fmt::Display, fs, path::Path, rc::Rc}; 5 | 6 | use crate::config::Config; 7 | 8 | const REL_ENTRY_PATH: &str = "loader/entries/"; 9 | 10 | pub trait Kernel: Display + Clone + PartialEq { 11 | fn parse( 12 | config: &Config, 13 | kernel_name: &str, 14 | sbconf: Rc>, 15 | ) -> Result; 16 | fn install(&self) -> Result<()>; 17 | fn remove(&self) -> Result<()>; 18 | fn make_config(&self, force_write: bool) -> Result<()>; 19 | fn set_default(&self) -> Result<()>; 20 | fn remove_default(&self) -> Result<()>; 21 | fn ask_set_default(&self) -> Result<()>; 22 | fn is_default(&self) -> Result; 23 | fn install_and_make_config(&self, force_write: bool) -> Result<()>; 24 | fn list(config: &Config, sbconf: Rc>) -> Result>; 25 | fn list_installed(config: &Config, sbconf: Rc>) -> Result>; 26 | } 27 | 28 | pub fn file_copy(src: P, dest: Q) -> Result<()> 29 | where 30 | P: AsRef, 31 | Q: AsRef, 32 | { 33 | // Only copy if the dest file is missing / different 34 | if !dest.as_ref().exists() || !is_same_file(&src, &dest)? { 35 | fs::copy(&src, &dest)?; 36 | } 37 | 38 | Ok(()) 39 | } 40 | 41 | #[cfg(feature = "generic")] 42 | pub mod generic_kernel; 43 | -------------------------------------------------------------------------------- /src/cli.rs: -------------------------------------------------------------------------------- 1 | use clap::{Parser, Subcommand}; 2 | 3 | #[derive(Parser, Debug)] 4 | #[command(about, author, version, arg_required_else_help(true))] 5 | pub struct Opts { 6 | #[command(subcommand)] 7 | pub subcommands: Option, 8 | } 9 | 10 | #[derive(Subcommand, Debug)] 11 | pub enum SubCommands { 12 | /// Initialize systemd-boot-friend 13 | #[command(display_order = 1)] 14 | Init, 15 | /// Install all kernels and update boot entries 16 | #[command(display_order = 2)] 17 | Update, 18 | /// Install the kernels specified 19 | #[command(display_order = 3)] 20 | InstallKernel { 21 | targets: Vec, 22 | /// Force overwrite the entry config or not 23 | #[arg(long, short)] 24 | force: bool, 25 | }, 26 | /// Remove the kernels specified 27 | #[command(display_order = 4)] 28 | RemoveKernel { targets: Vec }, 29 | /// Select kernels to install or remove 30 | #[command(display_order = 5)] 31 | Select, 32 | /// List all available kernels 33 | #[command(display_order = 6)] 34 | ListAvailable, 35 | /// List all installed kernels 36 | #[command(display_order = 7)] 37 | ListInstalled, 38 | /// Configure systemd-boot 39 | #[command(display_order = 8)] 40 | Config, 41 | /// Set the default kernel 42 | #[command(display_order = 9)] 43 | SetDefault { target: Option }, 44 | /// Set the boot menu timeout 45 | #[command(display_order = 10)] 46 | SetTimeout { timeout: Option }, 47 | } 48 | -------------------------------------------------------------------------------- /i18n/zh-CN/systemd_boot_friend_rs.ftl: -------------------------------------------------------------------------------- 1 | conf_default = { $conf_path } 不存在!正在生成配置样例... 2 | conf_old = 检测到旧的配置文件,正在更新... 3 | edit_conf = 在继续操作前,您可能需要修改 { $conf_path }。 4 | empty_list = 内核列表为空 5 | invalid_esp = ESP_MOUNTPOINT 不正确 6 | invalid_index = 内核序号不正确 7 | no_kernel = 找不到内核 8 | invalid_kernel_filename = 内核文件名不正确 9 | info_path_not_exist = 10 | systemd-boot-friend 似乎尚未初始化。运行 `systemd-boot-friend init` 即可安装并配 11 | 置 systemd-boot。 12 | err_path_not_exist = { $path } 不存在 13 | skip_incomplete_kernel = 已跳过不完整的内核 { $kernel } ... 14 | skip_unidentified_kernel = 已跳过不明内核 { $kernel } ... 15 | no_space = 设备上没有空间 16 | edit_bootarg = 请使用任意文本编辑器编辑 { $config } 中的 `BOOTARG=` 条目 17 | invalid_dirname = 目录名不正确: 18 | require_default = { $conf_path } 中必须包含 "default" (默认)启动参数配置 19 | create_folder = 正在建立 friend 目录结构... 20 | note_copy_files = 注意:systemd-boot-friend 将把内核文件复制到您的 EFI 系统分区 21 | install = 正在登记内核 { $kernel } ... 22 | install_ucode = 检测到 intel-ucode。正在登记... 23 | no_overwrite = 文件未作修改。 24 | overwrite = 正在覆盖 { $entry } ... 25 | create_entry = 正在建立启动项 { $kernel } ... 26 | remove_kernel = 正在移除内核 { $kernel } ... 27 | remove_entry = 正在移除启动项 { $kernel } ... 28 | set_default = 正在将 { $kernel } 设为默认启动项... 29 | remove_default = 正在移除启动项 { $kernel } ... 30 | init = 正在安装并初始化 systemd-boot ... 31 | notice_init = 32 | systemd-boot-friend 即将安装及初始化 systemd-boot,并将其设置为默认 EFI 启动项。完成 33 | 后,您依旧可以从 EFI 启动管理器中访问其他启动引导器,如 GRUB 或 Windows 启动管理器。 34 | update = 正在更新启动项 ... 35 | skip_update = 您可以随时运行 `systemd-boot-friend update` 来登记启动项。 36 | notice_empty_bootarg = 37 | systemd-boot-friend 在您的配置中检测到了空的 `BOOTARG=` 条目,这有可能导致系统启 38 | 动失败。 39 | current_bootarg = 检测到了当前使用的启动参数(内核命令行): 40 | current_root = 检测到了当前的根目录分区: { $root } 41 | note_list_available = "*" 表示已登记的内核 42 | note_list_installed = "*" 表示默认内核 43 | ask_overwrite = { $entry } 已存在。是否覆盖该文件? 44 | ask_set_default = 是否将 { $kernel } 设为默认启动项? 45 | select_install = 要登记启动项的内核 46 | select_remove = 要从启动菜单移除的内核 47 | select = 要在启动菜单登记或移除的内核 48 | select_default = 默认内核 49 | ask_init = 是否安装并初始化 systemd-boot-friend? 50 | prompt_update = 51 | systemd-boot 已成功初始化。是否要让 systemd-boot-friend 搜索 `{ $src_path }` 中的内核 52 | 并将其登记至 systemd-boot 配置中? 53 | ask_update = 是否安装所有内核并登记启动项? 54 | ask_empty_bootarg = 是否自动生成启动参数? 55 | ask_current_bootarg = 是否将上述启动参数设为 systemd-boot 默认启动参数? 56 | ask_current_root = 是否将 `root={ $root } rw` 设为 systemd-boot 默认启动参数? 57 | input_timeout = 启动菜单显示时长(秒) 58 | -------------------------------------------------------------------------------- /i18n/zh-TW/systemd_boot_friend_rs.ftl: -------------------------------------------------------------------------------- 1 | conf_default = { $conf_path } 不存在!正在產生模板... 2 | conf_old = 偵測到舊的設定檔,正在更新... 3 | edit_conf = 在繼續操作前,您可能需要修改 { $conf_path }。 4 | empty_list = 核心列表為空 5 | invalid_esp = ESP_MOUNTPOINT 不正確 6 | invalid_index = 核心編號不正確 7 | no_kernel = 找不到核心 8 | invalid_kernel_filename = 核心檔案名稱不正確 9 | info_path_not_exist = 10 | systemd-boot-friend 似乎尚未初始化。執行 `systemd-boot-friend init` 即可安裝 11 | 並設定 systemd-boot。 12 | err_path_not_exist = { $path } 不存在 13 | skip_incomplete_kernel = 已跳過不完整的核心 { $kernel } ... 14 | skip_unidentified_kernel = 已跳過不明核心 { $kernel } ... 15 | no_space = 裝置上已無多餘空間 16 | edit_bootarg = 請使用任意文字編輯器編輯 { $config } 中的 `BOOTARG=` 項目 17 | invalid_dirname = 目錄名稱不正確: 18 | require_default = { $conf_path } 中必須包含 "default" (預設)開機引數設定 19 | create_folder = 正在建立 friend 資料夾結構... 20 | note_copy_files = 注意:systemd-boot-friend 將把核心檔案複製到您的 EFI 系統分割區 21 | install = 正在登記核心 { $kernel } ... 22 | install_ucode = 偵測到 intel-ucode。正在登記... 23 | no_overwrite = 檔案未作修改。 24 | overwrite = 正在覆寫 { $entry } ... 25 | create_entry = 正在建立開機選項 { $kernel } ... 26 | remove_kernel = 正在刪除核心 { $kernel } ... 27 | remove_entry = 正在刪除開機選項 { $kernel } ... 28 | set_default = 正在將 { $kernel } 設為預設開機選項... 29 | remove_default = 正在刪除預設開機選項 { $kernel } ... 30 | init = 正在安裝並初始化 systemd-boot ... 31 | notice_init = 32 | systemd-boot-friend 即將安裝及初始化 systemd-boot,並將其設定為預設 EFI 開機選項。完 33 | 成後,您依舊可以從 EFI 開機管理程式中存取其他開機載入器,如 GRUB 或 Windows開機管理 34 | 器。 35 | update = 正在更新開機選項 ... 36 | skip_update = 您可以隨時執行 `systemd-boot-friend update` 以登記開機選項。 37 | notice_empty_bootarg = 38 | systemd-boot-friend 在您的設定檔中偵測到了空的 `BOOTARG=` 項目,這有可能導致系統開機 39 | 失敗。 40 | current_bootarg = 偵測到了目前使用的開機引數(核心命令列): 41 | current_root = 偵測到了目前的根目錄分割區:{ $root } 42 | note_list_available = "*" 表示已登記的核心 43 | note_list_installed = "*" 表示預設核心 44 | ask_overwrite = { $entry } 已存在。是否覆寫該檔案? 45 | ask_set_default = 是否將 { $kernel } 設為預設開機選項? 46 | select_install = 要登記開機選項的核心 47 | select_remove = 要從開機選單移除的核心 48 | select = 要在開機選單登記或移除的核心 49 | select_default = 預設核心 50 | ask_init = 是否安裝並初始化 systemd-boot? 51 | prompt_update = 52 | systemd-boot 已成功初始化。是否要讓 systemd-boot-friend 搜尋 `{ $src_path }` 中的核心 53 | 並將其登記至 systemd-boot 設定檔中? 54 | ask_update = 是否安裝所有核心並登記開機選項? 55 | ask_empty_bootarg = 是否自動產生開機引數? 56 | ask_current_bootarg = 是否將上述開機引數設為 systemd-boot 預設開機引數? 57 | ask_current_root = 是否將 `root={ $root } rw` 設為 systemd-boot 預設開機引數? 58 | input_timeout = 開機選單顯示時長(秒) 59 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | use crate::{config::Config, fl, kernel::Kernel}; 2 | use anyhow::{bail, Result}; 3 | use dialoguer::{theme::ColorfulTheme, MultiSelect, Select}; 4 | use libsdbootconf::SystemdBootConf; 5 | use std::{cell::RefCell, rc::Rc}; 6 | 7 | pub fn multiselect_kernel( 8 | kernels: &[K], 9 | installed_kernels: &[K], 10 | prompt: &str, 11 | ) -> Result> { 12 | if kernels.is_empty() { 13 | bail!(fl!("empty_list")); 14 | } 15 | 16 | // build dialoguer MultiSelect for kernel selection 17 | Ok(MultiSelect::with_theme(&ColorfulTheme::default()) 18 | .with_prompt(prompt) 19 | .items(kernels) 20 | .defaults( 21 | &kernels 22 | .iter() 23 | .map(|k| installed_kernels.contains(k)) 24 | .collect::>(), 25 | ) 26 | .interact()? 27 | .iter() 28 | .map(|n| kernels[*n].clone()) 29 | .collect()) 30 | } 31 | 32 | /// Choose a kernel using dialoguer 33 | pub fn select_kernel(kernels: &[K], prompt: &str) -> Result { 34 | if kernels.is_empty() { 35 | bail!(fl!("empty_list")); 36 | } 37 | 38 | // build dialoguer MultiSelect for kernel selection 39 | Ok(kernels[Select::with_theme(&ColorfulTheme::default()) 40 | .with_prompt(prompt) 41 | .items(kernels) 42 | .interact()?] 43 | .clone()) 44 | } 45 | 46 | pub fn specify_or_multiselect( 47 | kernels: &[K], 48 | config: &Config, 49 | arg: &[String], 50 | prompt: &str, 51 | sbconf: Rc>, 52 | ) -> Result> { 53 | if arg.is_empty() { 54 | // select the kernels when no target is given 55 | multiselect_kernel(kernels, &[], prompt) 56 | } else { 57 | let mut kernels = Vec::new(); 58 | 59 | for target in arg { 60 | kernels.push(K::parse(config, target, sbconf.clone())?); 61 | } 62 | 63 | Ok(kernels) 64 | } 65 | } 66 | 67 | pub fn specify_or_select( 68 | kernels: &[K], 69 | config: &Config, 70 | arg: &Option, 71 | prompt: &str, 72 | sbconf: Rc>, 73 | ) -> Result { 74 | match arg { 75 | // parse the kernel name when a target is given 76 | Some(n) => Ok(K::parse(config, n, sbconf)?), 77 | // select the kernel when no target is given 78 | None => select_kernel(kernels, prompt), 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/kernel_manager.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use console::style; 3 | 4 | use crate::{ 5 | fl, kernel::Kernel, print_block_with_fl, println_with_fl, println_with_prefix, 6 | println_with_prefix_and_fl, Config, 7 | }; 8 | 9 | /// Manage kernels 10 | pub struct KernelManager<'a, K: Kernel> { 11 | kernels: &'a [K], 12 | installed_kernels: &'a [K], 13 | } 14 | 15 | impl<'a, K: Kernel> KernelManager<'a, K> { 16 | /// Create a new Kernel Manager 17 | pub fn new(kernels: &'a [K], installed_kernels: &'a [K]) -> Self { 18 | Self { 19 | kernels, 20 | installed_kernels, 21 | } 22 | } 23 | 24 | /// Update systemd-boot kernels and entries 25 | pub fn update(&self, config: &Config) -> Result<()> { 26 | println_with_prefix_and_fl!("update"); 27 | print_block_with_fl!("note_copy_files"); 28 | 29 | let keep = config 30 | .keep 31 | .unwrap_or(self.kernels.len()) 32 | .min(self.kernels.len()); 33 | 34 | let to_be_installed = &self.kernels[..keep]; 35 | 36 | // Remove obsoleted kernels 37 | self.installed_kernels.iter().try_for_each(|k| { 38 | if !to_be_installed.contains(k) { 39 | k.remove() 40 | } else { 41 | Ok(()) 42 | } 43 | })?; 44 | 45 | // Install all kernels 46 | self.kernels 47 | .iter() 48 | .take(keep) 49 | .try_for_each(|k| k.install_and_make_config(true))?; 50 | 51 | // Set the newest kernel as default entry 52 | if keep > 0 { 53 | if let Some(k) = self.kernels.first() { 54 | k.set_default()?; 55 | } 56 | } 57 | 58 | Ok(()) 59 | } 60 | 61 | #[inline] 62 | pub fn install(kernel: &K, force: bool) -> Result<()> { 63 | print_block_with_fl!("note_copy_files"); 64 | 65 | kernel.install_and_make_config(force)?; 66 | kernel.ask_set_default()?; 67 | 68 | Ok(()) 69 | } 70 | 71 | /// Print all the available kernels 72 | pub fn list_available(&self) { 73 | if !self.kernels.is_empty() { 74 | for k in self.kernels.iter() { 75 | if self.installed_kernels.contains(k) { 76 | print!("{} ", style("[*]").green()); 77 | } else { 78 | print!("[ ] "); 79 | } 80 | println!("{}", k); 81 | } 82 | println!(); 83 | println_with_fl!("note_list_available"); 84 | } 85 | } 86 | 87 | /// Print all the installed kernels 88 | pub fn list_installed(&self) -> Result<()> { 89 | if !self.installed_kernels.is_empty() { 90 | for k in self.installed_kernels.iter() { 91 | if k.is_default()? { 92 | print!("{} ", style("[*]").green()); 93 | } else { 94 | print!("[ ] "); 95 | } 96 | println!("{}", k); 97 | } 98 | println!(); 99 | println_with_fl!("note_list_installed"); 100 | } 101 | 102 | Ok(()) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /i18n/en-US/systemd_boot_friend_rs.ftl: -------------------------------------------------------------------------------- 1 | conf_default = { $conf_path } is missing! Generating a template ... 2 | conf_old = Old configuration detected, updating ... 3 | edit_conf = You may need to edit { $conf_path } before continuing. 4 | empty_list = Empty kernel list 5 | invalid_esp = Invalid ESP_MOUNTPOINT 6 | invalid_index = Invalid kernel index 7 | no_kernel = No kernel found 8 | invalid_kernel_filename = Invalid kernel filename 9 | info_path_not_exist = 10 | It seems that you have not initialized systemd-boot-friend yet. 11 | systemd-boot-friend can help you install and configure systemd-boot. 12 | Simply execute `systemd-boot-friend init`. 13 | err_path_not_exist = { $path } not found 14 | skip_incomplete_kernel = Skipping incomplete kernel { $kernel } ... 15 | skip_unidentified_kernel = Skipping unidentified kernel { $kernel } ... 16 | no_space = No space left on device 17 | edit_bootarg = Please use your favorite text editor to edit `BOOTARG=` entry in { $config } 18 | invalid_dirname = Invalid directory name: 19 | require_default = Require a boot argument profile named "default" in { $conf_path } 20 | create_folder = Creating folder structure for friend ... 21 | note_copy_files = Note: systemd-boot-friend will copy Kernel file(s) to your EFI System Partition 22 | install = Installing kernel { $kernel } ... 23 | install_ucode = intel-ucode detected. Installing ... 24 | no_overwrite = Doing nothing on this file. 25 | overwrite = Overwriting { $entry } ... 26 | create_entry = Creating boot entry { $kernel } ... 27 | remove_kernel = Removing kernel { $kernel } ... 28 | remove_entry = Removing boot entry { $kernel } ... 29 | set_default = Setting { $kernel } as default boot entry ... 30 | remove_default = Removing default boot entry { $kernel } ... 31 | init = Installing and initializing systemd-boot ... 32 | notice_init = 33 | systemd-boot-friend will now install and initialize systemd-boot, which will 34 | become the default EFI boot option on your system. If you already have GRUB or 35 | other bootloaders (such as Windows Boot Manager) installed, they will remain 36 | accessible from your EFI Boot Manager. 37 | update = Updating boot entries ... 38 | skip_update = You can add them later by running `systemd-boot-friend update`. 39 | notice_empty_bootarg = 40 | systemd-boot-friend detected an empty `BOOTARG=` field in your configuration. 41 | This may cause system boot failures. 42 | current_bootarg = Detected current boot arguments (kernel command line): 43 | current_root = Detected current root partition: { $root } 44 | note_list_available = "*" denotes the installed kernel(s) 45 | note_list_installed = "*" denotes the default kernel 46 | ask_overwrite = { $entry } already exists. Overwrite? 47 | ask_set_default = Set { $kernel } as the default boot entry? 48 | select_install = Kernel(s) to install as boot entry(s) 49 | select_remove = Kernel(s) to remove from the boot menu 50 | select = Kernel(s) to install or remove from the bootloader 51 | select_default = Default kernel to boot from 52 | ask_init = Proceed with installing and initializing systemd-boot? 53 | prompt_update = 54 | Successfully initialized systemd-boot. Would you like systemd-boot-friend to 55 | search your `{ $src_path }` directory for kernels and install them in systemd-boot 56 | configuration? 57 | ask_update = Proceed with searching and creating boot entries? 58 | ask_empty_bootarg = Automatically generate the boot arguments? 59 | ask_current_bootarg = Use the boot arguments above as the systemd-boot defaults? 60 | ask_current_root = Use `root={ $root } rw` as the default systemd-boot boot arguments? 61 | input_timeout = Boot menu timeout (seconds) 62 | -------------------------------------------------------------------------------- /src/version/generic_version.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, Result}; 2 | use nom::{ 3 | bytes::complete::{tag, take_until}, 4 | character::complete::digit1, 5 | combinator::{map_res, opt}, 6 | sequence::preceded, 7 | IResult, Parser, 8 | }; 9 | use std::fmt; 10 | 11 | use super::Version; 12 | use crate::fl; 13 | 14 | #[derive(Debug, Default, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] 15 | pub struct GenericVersion { 16 | pub major: u64, 17 | pub minor: u64, 18 | pub patch: u64, 19 | pub rc: Option, 20 | pub rel: Option, 21 | pub localversion: String, 22 | } 23 | 24 | impl fmt::Display for GenericVersion { 25 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 26 | write!( 27 | f, 28 | "{}.{}.{}{}{}{}", 29 | self.major, 30 | self.minor, 31 | self.patch, 32 | self.rc 33 | .as_ref() 34 | .map_or_else(|| "".to_owned(), |s| format!("-{}", s)), 35 | self.rel 36 | .as_ref() 37 | .map_or_else(|| "".to_owned(), |s| format!("-{}", s)), 38 | self.localversion 39 | ) 40 | } 41 | } 42 | 43 | fn version_digit(input: &str) -> IResult<&str, u64> { 44 | map_res(digit1, |x: &str| x.parse()).parse(input) 45 | } 46 | 47 | fn digit_after_dot(input: &str) -> IResult<&str, u64> { 48 | preceded(tag("."), version_digit).parse(input) 49 | } 50 | 51 | fn rc(input: &str) -> IResult<&str, u64> { 52 | preceded(tag("-rc"), version_digit).parse(input) 53 | } 54 | 55 | fn rel(input: &str) -> IResult<&str, u64> { 56 | map_res(preceded(tag("-"), take_until("-")), |x: &str| x.parse()).parse(input) 57 | } 58 | 59 | impl Version for GenericVersion { 60 | fn parse(input: &str) -> Result { 61 | ( 62 | version_digit, // Major 63 | digit_after_dot, // Minor 64 | opt(digit_after_dot), // Optional Patch 65 | opt(rc), // Optional RC 66 | opt(rel), // Optional Rel 67 | ) 68 | .parse(input) 69 | .map_or_else( 70 | |_| Err(anyhow!(fl!("invalid_kernel_filename"))), 71 | |(next, res)| { 72 | let (major, minor, patch, rc, rel) = res; 73 | let version = GenericVersion { 74 | major, 75 | minor, 76 | patch: patch.unwrap_or_default(), 77 | rc, 78 | rel, 79 | localversion: next.into(), 80 | }; 81 | 82 | Ok(version) 83 | }, 84 | ) 85 | } 86 | } 87 | 88 | #[cfg(test)] 89 | mod tests { 90 | use super::*; 91 | #[test] 92 | fn test_aosc_version() { 93 | assert_eq!( 94 | GenericVersion::parse("5.12.0-rc3-aosc-main").unwrap(), 95 | GenericVersion { 96 | major: 5, 97 | minor: 12, 98 | patch: 0, 99 | rc: Some(3), 100 | rel: None, 101 | localversion: "-aosc-main".to_owned(), 102 | } 103 | ); 104 | assert_eq!( 105 | GenericVersion::parse("5.12-aosc-main").unwrap(), 106 | GenericVersion { 107 | major: 5, 108 | minor: 12, 109 | patch: 0, 110 | rc: None, 111 | rel: None, 112 | localversion: "-aosc-main".to_owned(), 113 | } 114 | ); 115 | } 116 | 117 | #[test] 118 | fn test_fedora_version() { 119 | assert_eq!( 120 | GenericVersion::parse("5.15.12-100.fc34.x86_64").unwrap(), 121 | GenericVersion { 122 | major: 5, 123 | minor: 15, 124 | patch: 12, 125 | rc: None, 126 | rel: None, 127 | localversion: "-100.fc34.x86_64".to_owned(), 128 | } 129 | ); 130 | } 131 | 132 | #[test] 133 | fn test_debian_version() { 134 | assert_eq!( 135 | GenericVersion::parse("5.10.0-11-amd64").unwrap(), 136 | GenericVersion { 137 | major: 5, 138 | minor: 10, 139 | patch: 0, 140 | rc: None, 141 | rel: Some(11), 142 | localversion: "-amd64".to_owned(), 143 | } 144 | ); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /completions/sbf.fish: -------------------------------------------------------------------------------- 1 | # Print an optspec for argparse to handle cmd's options that are independent of any subcommand. 2 | function __fish_sbf_global_optspecs 3 | string join \n h/help V/version 4 | end 5 | 6 | function __fish_sbf_needs_command 7 | # Figure out if the current invocation already has a command. 8 | set -l cmd (commandline -opc) 9 | set -e cmd[1] 10 | argparse -s (__fish_sbf_global_optspecs) -- $cmd 2>/dev/null 11 | or return 12 | if set -q argv[1] 13 | # Also print the command, so this can be used to figure out what it is. 14 | echo $argv[1] 15 | return 1 16 | end 17 | return 0 18 | end 19 | 20 | function __fish_sbf_using_subcommand 21 | set -l cmd (__fish_sbf_needs_command) 22 | test -z "$cmd" 23 | and return 1 24 | contains -- $cmd[1] $argv 25 | end 26 | 27 | complete -c sbf -n "__fish_sbf_needs_command" -s h -l help -d 'Print help' 28 | complete -c sbf -n "__fish_sbf_needs_command" -s V -l version -d 'Print version' 29 | complete -c sbf -n "__fish_sbf_needs_command" -f -a "init" -d 'Initialize systemd-boot-friend' 30 | complete -c sbf -n "__fish_sbf_needs_command" -f -a "update" -d 'Install all kernels and update boot entries' 31 | complete -c sbf -n "__fish_sbf_needs_command" -f -a "install-kernel" -d 'Install the kernels specified' 32 | complete -c sbf -n "__fish_sbf_needs_command" -f -a "remove-kernel" -d 'Remove the kernels specified' 33 | complete -c sbf -n "__fish_sbf_needs_command" -f -a "select" -d 'Select kernels to install or remove' 34 | complete -c sbf -n "__fish_sbf_needs_command" -f -a "list-available" -d 'List all available kernels' 35 | complete -c sbf -n "__fish_sbf_needs_command" -f -a "list-installed" -d 'List all installed kernels' 36 | complete -c sbf -n "__fish_sbf_needs_command" -f -a "config" -d 'Configure systemd-boot' 37 | complete -c sbf -n "__fish_sbf_needs_command" -f -a "set-default" -d 'Set the default kernel' 38 | complete -c sbf -n "__fish_sbf_needs_command" -f -a "set-timeout" -d 'Set the boot menu timeout' 39 | complete -c sbf -n "__fish_sbf_needs_command" -f -a "help" -d 'Print this message or the help of the given subcommand(s)' 40 | complete -c sbf -n "__fish_sbf_using_subcommand init" -s h -l help -d 'Print help' 41 | complete -c sbf -n "__fish_sbf_using_subcommand update" -s h -l help -d 'Print help' 42 | complete -c sbf -n "__fish_sbf_using_subcommand install-kernel" -s f -l force -d 'Force overwrite the entry config or not' 43 | complete -c sbf -n "__fish_sbf_using_subcommand install-kernel" -s h -l help -d 'Print help' 44 | complete -c sbf -n "__fish_sbf_using_subcommand remove-kernel" -s h -l help -d 'Print help' 45 | complete -c sbf -n "__fish_sbf_using_subcommand select" -s h -l help -d 'Print help' 46 | complete -c sbf -n "__fish_sbf_using_subcommand list-available" -s h -l help -d 'Print help' 47 | complete -c sbf -n "__fish_sbf_using_subcommand list-installed" -s h -l help -d 'Print help' 48 | complete -c sbf -n "__fish_sbf_using_subcommand config" -s h -l help -d 'Print help' 49 | complete -c sbf -n "__fish_sbf_using_subcommand set-default" -s h -l help -d 'Print help' 50 | complete -c sbf -n "__fish_sbf_using_subcommand set-timeout" -s h -l help -d 'Print help' 51 | complete -c sbf -n "__fish_sbf_using_subcommand help; and not __fish_seen_subcommand_from init update install-kernel remove-kernel select list-available list-installed config set-default set-timeout help" -f -a "init" -d 'Initialize systemd-boot-friend' 52 | complete -c sbf -n "__fish_sbf_using_subcommand help; and not __fish_seen_subcommand_from init update install-kernel remove-kernel select list-available list-installed config set-default set-timeout help" -f -a "update" -d 'Install all kernels and update boot entries' 53 | complete -c sbf -n "__fish_sbf_using_subcommand help; and not __fish_seen_subcommand_from init update install-kernel remove-kernel select list-available list-installed config set-default set-timeout help" -f -a "install-kernel" -d 'Install the kernels specified' 54 | complete -c sbf -n "__fish_sbf_using_subcommand help; and not __fish_seen_subcommand_from init update install-kernel remove-kernel select list-available list-installed config set-default set-timeout help" -f -a "remove-kernel" -d 'Remove the kernels specified' 55 | complete -c sbf -n "__fish_sbf_using_subcommand help; and not __fish_seen_subcommand_from init update install-kernel remove-kernel select list-available list-installed config set-default set-timeout help" -f -a "select" -d 'Select kernels to install or remove' 56 | complete -c sbf -n "__fish_sbf_using_subcommand help; and not __fish_seen_subcommand_from init update install-kernel remove-kernel select list-available list-installed config set-default set-timeout help" -f -a "list-available" -d 'List all available kernels' 57 | complete -c sbf -n "__fish_sbf_using_subcommand help; and not __fish_seen_subcommand_from init update install-kernel remove-kernel select list-available list-installed config set-default set-timeout help" -f -a "list-installed" -d 'List all installed kernels' 58 | complete -c sbf -n "__fish_sbf_using_subcommand help; and not __fish_seen_subcommand_from init update install-kernel remove-kernel select list-available list-installed config set-default set-timeout help" -f -a "config" -d 'Configure systemd-boot' 59 | complete -c sbf -n "__fish_sbf_using_subcommand help; and not __fish_seen_subcommand_from init update install-kernel remove-kernel select list-available list-installed config set-default set-timeout help" -f -a "set-default" -d 'Set the default kernel' 60 | complete -c sbf -n "__fish_sbf_using_subcommand help; and not __fish_seen_subcommand_from init update install-kernel remove-kernel select list-available list-installed config set-default set-timeout help" -f -a "set-timeout" -d 'Set the boot menu timeout' 61 | complete -c sbf -n "__fish_sbf_using_subcommand help; and not __fish_seen_subcommand_from init update install-kernel remove-kernel select list-available list-installed config set-default set-timeout help" -f -a "help" -d 'Print this message or the help of the given subcommand(s)' 62 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, bail, Result}; 2 | use clap::Parser; 3 | use core::default::Default; 4 | use dialoguer::{theme::ColorfulTheme, Confirm, Input}; 5 | use libsdbootconf::SystemdBootConf; 6 | use std::{ 7 | cell::RefCell, 8 | fs, 9 | process::{Command, Stdio}, 10 | rc::Rc, 11 | }; 12 | 13 | mod cli; 14 | mod config; 15 | mod i18n; 16 | mod kernel; 17 | mod kernel_manager; 18 | mod macros; 19 | mod util; 20 | mod version; 21 | 22 | use cli::{Opts, SubCommands}; 23 | use config::Config; 24 | use i18n::I18N_LOADER; 25 | use kernel::{generic_kernel::GenericKernel, Kernel}; 26 | use kernel_manager::KernelManager; 27 | use util::*; 28 | 29 | const REL_DEST_PATH: &str = "EFI/systemd-boot-friend/"; 30 | const SRC_PATH: &str = "/boot"; 31 | 32 | /// Initialize the default environment for friend 33 | fn init(config: &Config) -> Result<()> { 34 | // use bootctl to install systemd-boot 35 | println_with_prefix_and_fl!("init"); 36 | print_block_with_fl!("notice_init"); 37 | 38 | if !Confirm::with_theme(&ColorfulTheme::default()) 39 | .with_prompt(fl!("ask_init")) 40 | .default(false) 41 | .interact()? 42 | { 43 | return Ok(()); 44 | } 45 | 46 | let child_output = Command::new("bootctl") 47 | .arg("install") 48 | .arg( 49 | "--esp=".to_owned() 50 | + config 51 | .esp_mountpoint 52 | .to_str() 53 | .ok_or_else(|| anyhow!(fl!("invalid_esp")))?, 54 | ) 55 | .stderr(Stdio::piped()) 56 | .spawn()? 57 | .wait_with_output()?; 58 | 59 | if !child_output.status.success() { 60 | bail!(String::from_utf8(child_output.stderr)?); 61 | } 62 | 63 | let sbconf = Rc::new(RefCell::new(SystemdBootConf::new( 64 | config.esp_mountpoint.join("loader/"), 65 | libsdbootconf::Config::default(), 66 | Vec::new(), 67 | ))); 68 | 69 | // Initialize a default config for systemd-boot 70 | sbconf.borrow().write_all()?; 71 | // Set default timeout to 5 72 | sbconf.borrow_mut().config.timeout = Some(5u32); 73 | sbconf.borrow().write_config()?; 74 | 75 | let installed_kernels = GenericKernel::list_installed(config, sbconf.clone())?; 76 | let kernels = GenericKernel::list(config, sbconf)?; 77 | 78 | // create folder structure 79 | println_with_prefix_and_fl!("create_folder"); 80 | fs::create_dir_all(config.esp_mountpoint.join(REL_DEST_PATH))?; 81 | 82 | // Update systemd-boot kernels and entries 83 | print_block_with_fl!("prompt_update", src_path = SRC_PATH); 84 | if Confirm::with_theme(&ColorfulTheme::default()) 85 | .with_prompt(fl!("ask_update")) 86 | .default(false) 87 | .interact()? 88 | { 89 | KernelManager::new(&kernels, &installed_kernels).update(config)?; 90 | } else { 91 | println_with_prefix_and_fl!("skip_update"); 92 | } 93 | 94 | Ok(()) 95 | } 96 | 97 | /// Ask for the timeout of systemd-boot boot menu 98 | fn ask_set_timeout(timeout: Option, sbconf: Rc>) -> Result<()> { 99 | sbconf.borrow_mut().config.timeout = timeout.or_else(|| { 100 | Input::with_theme(&ColorfulTheme::default()) 101 | .with_prompt(fl!("input_timeout")) 102 | .default(5u32) 103 | .interact() 104 | .ok() 105 | }); 106 | sbconf.borrow().write_config()?; 107 | 108 | Ok(()) 109 | } 110 | 111 | fn main() -> Result<()> { 112 | // CLI 113 | let matches: Opts = Opts::parse(); 114 | 115 | // Read config, create a default one if the file is missing 116 | let config = Config::read()?; 117 | 118 | // Preprocess init subcommand 119 | if let Some(SubCommands::Init) = &matches.subcommands { 120 | init(&config)?; 121 | return Ok(()); 122 | } 123 | 124 | let sbconf = Rc::new(RefCell::new( 125 | SystemdBootConf::load(config.esp_mountpoint.join("loader/")) 126 | .map_err(|_| anyhow!(fl!("info_path_not_exist")))?, 127 | )); 128 | let installed_kernels = GenericKernel::list_installed(&config, sbconf.clone())?; 129 | let kernels = GenericKernel::list(&config, sbconf.clone())?; 130 | 131 | let kernel_manager = KernelManager::new(&kernels, &installed_kernels); 132 | 133 | // Switch table 134 | match matches.subcommands { 135 | Some(s) => match s { 136 | SubCommands::Init => unreachable!(), // Handled above 137 | SubCommands::Update => kernel_manager.update(&config)?, 138 | SubCommands::InstallKernel { targets, force } => { 139 | specify_or_multiselect(&kernels, &config, &targets, &fl!("select_install"), sbconf)? 140 | .iter() 141 | .try_for_each(|k| KernelManager::install(k, force))? 142 | } 143 | SubCommands::RemoveKernel { targets } => specify_or_multiselect( 144 | &installed_kernels, 145 | &config, 146 | &targets, 147 | &fl!("select_remove"), 148 | sbconf, 149 | )? 150 | .iter() 151 | .try_for_each(|k| k.remove())?, 152 | SubCommands::Select => { 153 | let new_kernels = 154 | &multiselect_kernel(&kernels, &installed_kernels, &fl!("select"))?; 155 | installed_kernels.iter().try_for_each(|k| { 156 | if !new_kernels.contains(k) { 157 | k.remove() 158 | } else { 159 | Ok(()) 160 | } 161 | })?; 162 | new_kernels.iter().try_for_each(|k| { 163 | if !installed_kernels.contains(k) { 164 | k.install_and_make_config(true) 165 | } else { 166 | Ok(()) 167 | } 168 | })?; 169 | } 170 | SubCommands::ListAvailable => kernel_manager.list_available(), 171 | SubCommands::ListInstalled => kernel_manager.list_installed()?, 172 | SubCommands::SetDefault { target } => { 173 | specify_or_select( 174 | &installed_kernels, 175 | &config, 176 | &target, 177 | &fl!("select_default"), 178 | sbconf, 179 | )? 180 | .set_default()?; 181 | } 182 | SubCommands::SetTimeout { timeout } => { 183 | ask_set_timeout(timeout, sbconf)?; 184 | } 185 | SubCommands::Config => { 186 | select_kernel(&installed_kernels, &fl!("select_default"))?.set_default()?; 187 | ask_set_timeout(None, sbconf)?; 188 | } 189 | }, 190 | None => unreachable!(), 191 | } 192 | 193 | Ok(()) 194 | } 195 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, Result}; 2 | use serde::{Deserialize, Serialize}; 3 | use std::{cell::RefCell, collections::HashMap, fs, path::PathBuf, rc::Rc}; 4 | 5 | use crate::{fl, println_with_prefix, println_with_prefix_and_fl}; 6 | 7 | const CONF_PATH: &str = "/etc/systemd-boot-friend.conf"; 8 | const MOUNTS: &str = "/proc/mounts"; 9 | // const CMDLINE: &str = "/proc/cmdline"; 10 | 11 | #[derive(Debug, Serialize, Deserialize)] 12 | pub struct Config { 13 | #[serde(alias = "VMLINUX", alias = "VMLINUZ")] 14 | pub vmlinux: String, 15 | #[serde(alias = "INITRD")] 16 | pub initrd: String, 17 | #[serde(alias = "DISTRO")] 18 | pub distro: Rc, 19 | #[serde(alias = "ESP_MOUNTPOINT")] 20 | pub esp_mountpoint: Rc, 21 | #[serde(alias = "KEEP")] 22 | pub keep: Option, 23 | #[serde(alias = "BOOTARG")] 24 | bootarg: Option, // for compatibility 25 | #[serde(alias = "BOOTARGS", default)] 26 | pub bootargs: Rc>>, 27 | } 28 | 29 | impl Default for Config { 30 | fn default() -> Self { 31 | Config { 32 | vmlinux: "vmlinuz-{VERSION}".to_owned(), 33 | initrd: "initramfs-{VERSION}.img".to_owned(), 34 | distro: Rc::new("Linux".to_owned()), 35 | esp_mountpoint: Rc::new(PathBuf::from("/efi")), 36 | keep: None, 37 | bootarg: None, 38 | bootargs: Rc::new(RefCell::new(HashMap::from([( 39 | "default".to_owned(), 40 | String::new(), 41 | )]))), 42 | } 43 | } 44 | } 45 | 46 | /// Detect current root partition, used for generating kernel cmdline 47 | fn detect_root_partition() -> Result { 48 | let mounts = fs::read_to_string(MOUNTS)?; 49 | let mut root_partition = String::new(); 50 | 51 | for line in mounts.lines() { 52 | let mut parts = line.split_whitespace(); 53 | let partition = parts.next().unwrap_or_default(); 54 | let mount = parts.next().unwrap_or_default(); 55 | if mount == "/" { 56 | partition.clone_into(&mut root_partition); 57 | } 58 | } 59 | 60 | Ok(root_partition) 61 | } 62 | 63 | /// Fill the necessary root cmdline and rw cmdline params if they are missing 64 | fn fill_necessary_bootarg(bootarg: &str) -> Result { 65 | let mut has_root = false; 66 | let mut has_rw = false; 67 | 68 | for param in bootarg.split_whitespace() { 69 | if param.starts_with("root=") { 70 | has_root = true; 71 | } else if param == "rw" || param == "ro" { 72 | has_rw = true; 73 | } 74 | } 75 | 76 | let mut filled_bootarg = String::from(bootarg.strip_suffix('\n').unwrap_or(bootarg)); 77 | 78 | if !has_root { 79 | filled_bootarg.push_str(" root="); 80 | filled_bootarg.push_str(&detect_root_partition()?) 81 | } 82 | 83 | if !has_rw { 84 | filled_bootarg.push_str(" rw"); 85 | } 86 | 87 | Ok(filled_bootarg) 88 | } 89 | 90 | impl Config { 91 | /// Write the current state to the configuration file 92 | fn write(&self) -> Result<()> { 93 | fs::create_dir_all(PathBuf::from(CONF_PATH).parent().unwrap())?; 94 | fs::write(CONF_PATH, toml::to_string_pretty(self)?)?; 95 | Ok(()) 96 | } 97 | 98 | /// Read the configuration file 99 | pub fn read() -> Result { 100 | match fs::read_to_string(CONF_PATH) { 101 | Ok(f) => { 102 | let mut config: Config = toml::from_str(&f)?; 103 | 104 | // Migrate from old configuration 105 | let old_conf = "{VERSION}-{LOCALVERSION}"; 106 | let new_conf = "{VERSION}"; 107 | 108 | if config.vmlinux.contains(old_conf) || config.initrd.contains(old_conf) { 109 | println_with_prefix_and_fl!("conf_old"); 110 | config.vmlinux = config.vmlinux.replace(old_conf, new_conf); 111 | config.initrd = config.initrd.replace(old_conf, new_conf); 112 | config.write()?; 113 | } 114 | 115 | // For compatibility 116 | if let Some(b) = config.bootarg { 117 | config.bootargs.borrow_mut().insert("default".to_owned(), b); 118 | config.bootarg = None; 119 | config.write()?; 120 | } 121 | 122 | if config.bootargs.borrow().is_empty() 123 | || config.bootargs.borrow().get("default").is_none() 124 | { 125 | config 126 | .bootargs 127 | .borrow_mut() 128 | .insert("default".to_owned(), String::new()); 129 | config.write()?; 130 | } 131 | 132 | for (_, bootarg) in config.bootargs.borrow_mut().iter_mut() { 133 | fill_necessary_bootarg(bootarg)?.trim().clone_into(bootarg); 134 | } 135 | 136 | Ok(config) 137 | } 138 | Err(_) => { 139 | println_with_prefix_and_fl!("conf_default", conf_path = CONF_PATH); 140 | Config::default().write()?; 141 | Err(anyhow!(fl!("edit_conf", conf_path = CONF_PATH))) 142 | } 143 | } 144 | } 145 | 146 | // /// Try to fill an empty BOOTARG option in Config 147 | // fn fill_empty_bootargs(&mut self) -> Result<()> { 148 | // print_block_with_fl!("notice_empty_bootarg"); 149 | // 150 | // if !Confirm::with_theme(&ColorfulTheme::default()) 151 | // .with_prompt(fl!("ask_empty_bootarg")) 152 | // .default(true) 153 | // .interact()? 154 | // { 155 | // return Ok(()); 156 | // } 157 | // 158 | // let current_bootarg = String::from_utf8(fs::read(CMDLINE)?)?; 159 | // 160 | // print_block_with_fl!("current_bootarg"); 161 | // 162 | // // print bootarg (kernel command line), wrap at col 80 163 | // for line in wrap( 164 | // ¤t_bootarg, 165 | // Options::new(80) 166 | // .word_separator(WordSeparator::AsciiSpace) 167 | // .word_splitter(WordSplitter::NoHyphenation), 168 | // ) { 169 | // eprintln!("{}", style(line).bold()); 170 | // } 171 | // 172 | // if Confirm::with_theme(&ColorfulTheme::default()) 173 | // .with_prompt(fl!("ask_current_bootarg")) 174 | // .default(true) 175 | // .interact()? 176 | // { 177 | // self.bootargs 178 | // .borrow_mut() 179 | // .insert("default".to_owned(), current_bootarg); 180 | // } else { 181 | // let root = detect_root_partition()?; 182 | // 183 | // print_block_with_fl!("current_root", root = root.as_str()); 184 | // 185 | // if !Confirm::with_theme(&ColorfulTheme::default()) 186 | // .with_prompt(fl!("ask_current_root", root = root.as_str())) 187 | // .default(true) 188 | // .interact()? 189 | // { 190 | // bail!(fl!("edit_bootarg", config = CONF_PATH)); 191 | // } 192 | // 193 | // self.bootargs 194 | // .borrow_mut() 195 | // .insert("default".to_owned(), format!("root={} rw", root)); 196 | // } 197 | // 198 | // self.write()?; 199 | // 200 | // Ok(()) 201 | // } 202 | } 203 | -------------------------------------------------------------------------------- /completions/_sbf: -------------------------------------------------------------------------------- 1 | #compdef sbf 2 | 3 | autoload -U is-at-least 4 | 5 | _sbf() { 6 | typeset -A opt_args 7 | typeset -a _arguments_options 8 | local ret=1 9 | 10 | if is-at-least 5.2; then 11 | _arguments_options=(-s -S -C) 12 | else 13 | _arguments_options=(-s -C) 14 | fi 15 | 16 | local context curcontext="$curcontext" state line 17 | _arguments "${_arguments_options[@]}" : \ 18 | '-h[Print help]' \ 19 | '--help[Print help]' \ 20 | '-V[Print version]' \ 21 | '--version[Print version]' \ 22 | ":: :_sbf_commands" \ 23 | "*::: :->systemd-boot-friend-rs" \ 24 | && ret=0 25 | case $state in 26 | (systemd-boot-friend-rs) 27 | words=($line[1] "${words[@]}") 28 | (( CURRENT += 1 )) 29 | curcontext="${curcontext%:*:*}:sbf-command-$line[1]:" 30 | case $line[1] in 31 | (init) 32 | _arguments "${_arguments_options[@]}" : \ 33 | '-h[Print help]' \ 34 | '--help[Print help]' \ 35 | && ret=0 36 | ;; 37 | (update) 38 | _arguments "${_arguments_options[@]}" : \ 39 | '-h[Print help]' \ 40 | '--help[Print help]' \ 41 | && ret=0 42 | ;; 43 | (install-kernel) 44 | _arguments "${_arguments_options[@]}" : \ 45 | '-f[Force overwrite the entry config or not]' \ 46 | '--force[Force overwrite the entry config or not]' \ 47 | '-h[Print help]' \ 48 | '--help[Print help]' \ 49 | '*::targets:_default' \ 50 | && ret=0 51 | ;; 52 | (remove-kernel) 53 | _arguments "${_arguments_options[@]}" : \ 54 | '-h[Print help]' \ 55 | '--help[Print help]' \ 56 | '*::targets:_default' \ 57 | && ret=0 58 | ;; 59 | (select) 60 | _arguments "${_arguments_options[@]}" : \ 61 | '-h[Print help]' \ 62 | '--help[Print help]' \ 63 | && ret=0 64 | ;; 65 | (list-available) 66 | _arguments "${_arguments_options[@]}" : \ 67 | '-h[Print help]' \ 68 | '--help[Print help]' \ 69 | && ret=0 70 | ;; 71 | (list-installed) 72 | _arguments "${_arguments_options[@]}" : \ 73 | '-h[Print help]' \ 74 | '--help[Print help]' \ 75 | && ret=0 76 | ;; 77 | (config) 78 | _arguments "${_arguments_options[@]}" : \ 79 | '-h[Print help]' \ 80 | '--help[Print help]' \ 81 | && ret=0 82 | ;; 83 | (set-default) 84 | _arguments "${_arguments_options[@]}" : \ 85 | '-h[Print help]' \ 86 | '--help[Print help]' \ 87 | '::target:_default' \ 88 | && ret=0 89 | ;; 90 | (set-timeout) 91 | _arguments "${_arguments_options[@]}" : \ 92 | '-h[Print help]' \ 93 | '--help[Print help]' \ 94 | '::timeout:_default' \ 95 | && ret=0 96 | ;; 97 | (help) 98 | _arguments "${_arguments_options[@]}" : \ 99 | ":: :_sbf__help_commands" \ 100 | "*::: :->help" \ 101 | && ret=0 102 | 103 | case $state in 104 | (help) 105 | words=($line[1] "${words[@]}") 106 | (( CURRENT += 1 )) 107 | curcontext="${curcontext%:*:*}:sbf-help-command-$line[1]:" 108 | case $line[1] in 109 | (init) 110 | _arguments "${_arguments_options[@]}" : \ 111 | && ret=0 112 | ;; 113 | (update) 114 | _arguments "${_arguments_options[@]}" : \ 115 | && ret=0 116 | ;; 117 | (install-kernel) 118 | _arguments "${_arguments_options[@]}" : \ 119 | && ret=0 120 | ;; 121 | (remove-kernel) 122 | _arguments "${_arguments_options[@]}" : \ 123 | && ret=0 124 | ;; 125 | (select) 126 | _arguments "${_arguments_options[@]}" : \ 127 | && ret=0 128 | ;; 129 | (list-available) 130 | _arguments "${_arguments_options[@]}" : \ 131 | && ret=0 132 | ;; 133 | (list-installed) 134 | _arguments "${_arguments_options[@]}" : \ 135 | && ret=0 136 | ;; 137 | (config) 138 | _arguments "${_arguments_options[@]}" : \ 139 | && ret=0 140 | ;; 141 | (set-default) 142 | _arguments "${_arguments_options[@]}" : \ 143 | && ret=0 144 | ;; 145 | (set-timeout) 146 | _arguments "${_arguments_options[@]}" : \ 147 | && ret=0 148 | ;; 149 | (help) 150 | _arguments "${_arguments_options[@]}" : \ 151 | && ret=0 152 | ;; 153 | esac 154 | ;; 155 | esac 156 | ;; 157 | esac 158 | ;; 159 | esac 160 | } 161 | 162 | (( $+functions[_sbf_commands] )) || 163 | _sbf_commands() { 164 | local commands; commands=( 165 | 'init:Initialize systemd-boot-friend' \ 166 | 'update:Install all kernels and update boot entries' \ 167 | 'install-kernel:Install the kernels specified' \ 168 | 'remove-kernel:Remove the kernels specified' \ 169 | 'select:Select kernels to install or remove' \ 170 | 'list-available:List all available kernels' \ 171 | 'list-installed:List all installed kernels' \ 172 | 'config:Configure systemd-boot' \ 173 | 'set-default:Set the default kernel' \ 174 | 'set-timeout:Set the boot menu timeout' \ 175 | 'help:Print this message or the help of the given subcommand(s)' \ 176 | ) 177 | _describe -t commands 'sbf commands' commands "$@" 178 | } 179 | (( $+functions[_sbf__config_commands] )) || 180 | _sbf__config_commands() { 181 | local commands; commands=() 182 | _describe -t commands 'sbf config commands' commands "$@" 183 | } 184 | (( $+functions[_sbf__help_commands] )) || 185 | _sbf__help_commands() { 186 | local commands; commands=( 187 | 'init:Initialize systemd-boot-friend' \ 188 | 'update:Install all kernels and update boot entries' \ 189 | 'install-kernel:Install the kernels specified' \ 190 | 'remove-kernel:Remove the kernels specified' \ 191 | 'select:Select kernels to install or remove' \ 192 | 'list-available:List all available kernels' \ 193 | 'list-installed:List all installed kernels' \ 194 | 'config:Configure systemd-boot' \ 195 | 'set-default:Set the default kernel' \ 196 | 'set-timeout:Set the boot menu timeout' \ 197 | 'help:Print this message or the help of the given subcommand(s)' \ 198 | ) 199 | _describe -t commands 'sbf help commands' commands "$@" 200 | } 201 | (( $+functions[_sbf__help__config_commands] )) || 202 | _sbf__help__config_commands() { 203 | local commands; commands=() 204 | _describe -t commands 'sbf help config commands' commands "$@" 205 | } 206 | (( $+functions[_sbf__help__help_commands] )) || 207 | _sbf__help__help_commands() { 208 | local commands; commands=() 209 | _describe -t commands 'sbf help help commands' commands "$@" 210 | } 211 | (( $+functions[_sbf__help__init_commands] )) || 212 | _sbf__help__init_commands() { 213 | local commands; commands=() 214 | _describe -t commands 'sbf help init commands' commands "$@" 215 | } 216 | (( $+functions[_sbf__help__install-kernel_commands] )) || 217 | _sbf__help__install-kernel_commands() { 218 | local commands; commands=() 219 | _describe -t commands 'sbf help install-kernel commands' commands "$@" 220 | } 221 | (( $+functions[_sbf__help__list-available_commands] )) || 222 | _sbf__help__list-available_commands() { 223 | local commands; commands=() 224 | _describe -t commands 'sbf help list-available commands' commands "$@" 225 | } 226 | (( $+functions[_sbf__help__list-installed_commands] )) || 227 | _sbf__help__list-installed_commands() { 228 | local commands; commands=() 229 | _describe -t commands 'sbf help list-installed commands' commands "$@" 230 | } 231 | (( $+functions[_sbf__help__remove-kernel_commands] )) || 232 | _sbf__help__remove-kernel_commands() { 233 | local commands; commands=() 234 | _describe -t commands 'sbf help remove-kernel commands' commands "$@" 235 | } 236 | (( $+functions[_sbf__help__select_commands] )) || 237 | _sbf__help__select_commands() { 238 | local commands; commands=() 239 | _describe -t commands 'sbf help select commands' commands "$@" 240 | } 241 | (( $+functions[_sbf__help__set-default_commands] )) || 242 | _sbf__help__set-default_commands() { 243 | local commands; commands=() 244 | _describe -t commands 'sbf help set-default commands' commands "$@" 245 | } 246 | (( $+functions[_sbf__help__set-timeout_commands] )) || 247 | _sbf__help__set-timeout_commands() { 248 | local commands; commands=() 249 | _describe -t commands 'sbf help set-timeout commands' commands "$@" 250 | } 251 | (( $+functions[_sbf__help__update_commands] )) || 252 | _sbf__help__update_commands() { 253 | local commands; commands=() 254 | _describe -t commands 'sbf help update commands' commands "$@" 255 | } 256 | (( $+functions[_sbf__init_commands] )) || 257 | _sbf__init_commands() { 258 | local commands; commands=() 259 | _describe -t commands 'sbf init commands' commands "$@" 260 | } 261 | (( $+functions[_sbf__install-kernel_commands] )) || 262 | _sbf__install-kernel_commands() { 263 | local commands; commands=() 264 | _describe -t commands 'sbf install-kernel commands' commands "$@" 265 | } 266 | (( $+functions[_sbf__list-available_commands] )) || 267 | _sbf__list-available_commands() { 268 | local commands; commands=() 269 | _describe -t commands 'sbf list-available commands' commands "$@" 270 | } 271 | (( $+functions[_sbf__list-installed_commands] )) || 272 | _sbf__list-installed_commands() { 273 | local commands; commands=() 274 | _describe -t commands 'sbf list-installed commands' commands "$@" 275 | } 276 | (( $+functions[_sbf__remove-kernel_commands] )) || 277 | _sbf__remove-kernel_commands() { 278 | local commands; commands=() 279 | _describe -t commands 'sbf remove-kernel commands' commands "$@" 280 | } 281 | (( $+functions[_sbf__select_commands] )) || 282 | _sbf__select_commands() { 283 | local commands; commands=() 284 | _describe -t commands 'sbf select commands' commands "$@" 285 | } 286 | (( $+functions[_sbf__set-default_commands] )) || 287 | _sbf__set-default_commands() { 288 | local commands; commands=() 289 | _describe -t commands 'sbf set-default commands' commands "$@" 290 | } 291 | (( $+functions[_sbf__set-timeout_commands] )) || 292 | _sbf__set-timeout_commands() { 293 | local commands; commands=() 294 | _describe -t commands 'sbf set-timeout commands' commands "$@" 295 | } 296 | (( $+functions[_sbf__update_commands] )) || 297 | _sbf__update_commands() { 298 | local commands; commands=() 299 | _describe -t commands 'sbf update commands' commands "$@" 300 | } 301 | 302 | if [ "$funcstack[1]" = "_sbf" ]; then 303 | _sbf "$@" 304 | else 305 | compdef _sbf sbf 306 | fi 307 | -------------------------------------------------------------------------------- /src/kernel/generic_kernel.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, bail, Result}; 2 | use dialoguer::{theme::ColorfulTheme, Confirm}; 3 | use libsdbootconf::{ 4 | entry::{EntryBuilder, Token}, 5 | SystemdBootConf, 6 | }; 7 | use regex::Regex; 8 | use std::{cell::RefCell, cmp::Ordering, collections::HashMap, fmt, fs, path::PathBuf, rc::Rc}; 9 | 10 | use super::{file_copy, Kernel, REL_ENTRY_PATH}; 11 | use crate::{ 12 | fl, print_block_with_fl, println_with_prefix, println_with_prefix_and_fl, 13 | version::{generic_version::GenericVersion, Version}, 14 | Config, REL_DEST_PATH, SRC_PATH, 15 | }; 16 | 17 | const MODULES_PATH: &str = "/usr/lib/modules/"; 18 | const UCODE: &str = "intel-ucode.img"; 19 | 20 | /// A kernel struct for parsing kernel filenames 21 | #[derive(Debug, Clone)] 22 | pub struct GenericKernel { 23 | version: GenericVersion, 24 | vmlinux: String, 25 | initrd: String, 26 | distro: Rc, 27 | esp_mountpoint: Rc, 28 | entry: String, 29 | bootargs: Rc>>, 30 | sbconf: Rc>, 31 | } 32 | 33 | impl PartialEq for GenericKernel { 34 | fn eq(&self, other: &Self) -> bool { 35 | self.version == other.version 36 | && self.vmlinux == other.vmlinux 37 | && self.initrd == other.initrd 38 | && self.entry == other.entry 39 | } 40 | } 41 | 42 | impl Eq for GenericKernel {} 43 | 44 | impl Ord for GenericKernel { 45 | fn cmp(&self, other: &Self) -> Ordering { 46 | self.version.cmp(&other.version) 47 | } 48 | } 49 | 50 | impl PartialOrd for GenericKernel { 51 | fn partial_cmp(&self, other: &Self) -> Option { 52 | Some(self.cmp(other)) 53 | } 54 | } 55 | 56 | impl fmt::Display for GenericKernel { 57 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 58 | write!(f, "{}", self.version) 59 | } 60 | } 61 | 62 | #[inline] 63 | fn warn(object: O, message: M) { 64 | eprintln!("Warning: {}: {}", object, message); 65 | } 66 | 67 | impl Kernel for GenericKernel { 68 | /// Parse a kernel filename 69 | fn parse( 70 | config: &Config, 71 | kernel_name: &str, 72 | sbconf: Rc>, 73 | ) -> Result { 74 | let version = GenericVersion::parse(kernel_name)?; 75 | let vmlinux = config.vmlinux.replace("{VERSION}", kernel_name); 76 | let initrd = config.initrd.replace("{VERSION}", kernel_name); 77 | let entry = kernel_name.to_owned(); 78 | 79 | Ok(Self { 80 | version, 81 | vmlinux, 82 | initrd, 83 | distro: config.distro.clone(), 84 | esp_mountpoint: config.esp_mountpoint.clone(), 85 | entry, 86 | bootargs: config.bootargs.clone(), 87 | sbconf, 88 | }) 89 | } 90 | 91 | /// Install a specific kernel to the esp using the given kernel filename 92 | fn install(&self) -> Result<()> { 93 | // if the path does not exist, ask the user for initializing friend 94 | let dest_path = self.esp_mountpoint.join(REL_DEST_PATH); 95 | let src_path = PathBuf::from(SRC_PATH); 96 | 97 | if !dest_path.exists() { 98 | print_block_with_fl!("info_path_not_exist"); 99 | bail!(fl!( 100 | "err_path_not_exist", 101 | path = dest_path.to_string_lossy() 102 | )); 103 | } 104 | 105 | // generate the path to the source files 106 | println_with_prefix_and_fl!("install", kernel = self.to_string()); 107 | 108 | // Copy the source files to the `install_path` using specific 109 | // filename format, remove the version parts of the files 110 | file_copy(src_path.join(&self.vmlinux), dest_path.join(&self.vmlinux))?; 111 | 112 | let initrd_path = src_path.join(&self.initrd); 113 | 114 | if initrd_path.exists() { 115 | file_copy(src_path.join(&self.initrd), dest_path.join(&self.initrd))?; 116 | } 117 | 118 | // copy Intel ucode if exists 119 | let ucode_path = src_path.join(UCODE); 120 | let ucode_dest_path = dest_path.join(UCODE); 121 | 122 | if ucode_path.exists() { 123 | println_with_prefix_and_fl!("install_ucode"); 124 | file_copy(ucode_path, ucode_dest_path)?; 125 | } else { 126 | fs::remove_file(ucode_dest_path).ok(); 127 | } 128 | 129 | Ok(()) 130 | } 131 | 132 | // Try to remove a kernel 133 | fn remove(&self) -> Result<()> { 134 | let kernel_path = self.esp_mountpoint.join(REL_DEST_PATH); 135 | 136 | println_with_prefix_and_fl!("remove_kernel", kernel = self.to_string()); 137 | let vmlinux = kernel_path.join(&self.vmlinux); 138 | let initrd = kernel_path.join(&self.initrd); 139 | 140 | fs::remove_file(&vmlinux) 141 | .map_err(|x| warn(vmlinux.display(), x)) 142 | .ok(); 143 | fs::remove_file(&initrd) 144 | .map_err(|x| warn(initrd.display(), x)) 145 | .ok(); 146 | 147 | println_with_prefix_and_fl!("remove_entry", kernel = self.to_string()); 148 | for profile in self.bootargs.borrow().keys() { 149 | let entry = self.esp_mountpoint.join(format!( 150 | "loader/entries/{}-{}.conf", 151 | self.entry, 152 | profile.replace(' ', "_") 153 | )); 154 | 155 | fs::remove_file(&entry) 156 | .map_err(|x| warn(entry.display(), x)) 157 | .ok(); 158 | } 159 | 160 | self.remove_default()?; 161 | 162 | Ok(()) 163 | } 164 | 165 | /// Create a systemd-boot entry config 166 | fn make_config(&self, force_write: bool) -> Result<()> { 167 | // if the path does not exist, ask the user for initializing friend 168 | let entries_path = self.esp_mountpoint.join(REL_ENTRY_PATH); 169 | 170 | if !entries_path.exists() { 171 | print_block_with_fl!("info_path_not_exist"); 172 | bail!(fl!( 173 | "err_path_not_exist", 174 | path = entries_path.to_string_lossy() 175 | )); 176 | } 177 | 178 | // do not override existed entry file until forced to do so 179 | let entry_path = entries_path.join(&self.entry); 180 | 181 | if entry_path.exists() && !force_write { 182 | let overwrite = Confirm::with_theme(&ColorfulTheme::default()) 183 | .with_prompt(fl!("ask_overwrite", entry = entry_path.to_string_lossy())) 184 | .default(false) 185 | .interact()?; 186 | 187 | if !&overwrite { 188 | println_with_prefix_and_fl!("no_overwrite"); 189 | return Ok(()); 190 | } 191 | 192 | println_with_prefix_and_fl!("overwrite", entry = entry_path.to_string_lossy()); 193 | self.make_config(overwrite)?; 194 | return Ok(()); 195 | } 196 | 197 | // Generate entry config 198 | println_with_prefix_and_fl!("create_entry", kernel = self.to_string()); 199 | 200 | let dest_path = self.esp_mountpoint.join(REL_DEST_PATH); 201 | let rel_dest_path = PathBuf::from(REL_DEST_PATH); 202 | let mut entries = Vec::new(); 203 | 204 | for (profile, bootarg) in self.bootargs.borrow().iter() { 205 | let mut entry = 206 | EntryBuilder::new(format!("{}-{}", self.entry, profile.replace(' ', "_"))) 207 | .title(format!("{} ({}) ({})", self.distro, self, profile)) 208 | .linux(rel_dest_path.join(&self.vmlinux)) 209 | .build(); 210 | 211 | dest_path 212 | .join(UCODE) 213 | .exists() 214 | .then(|| entry.tokens.push(Token::Initrd(rel_dest_path.join(UCODE)))); 215 | dest_path.join(&self.initrd).exists().then(|| { 216 | entry 217 | .tokens 218 | .push(Token::Initrd(rel_dest_path.join(&self.initrd))) 219 | }); 220 | entry.tokens.push(Token::Options(bootarg.to_owned())); 221 | entries.push(entry); 222 | } 223 | 224 | self.sbconf.borrow_mut().entries = entries; 225 | self.sbconf.borrow().write_entries()?; 226 | 227 | Ok(()) 228 | } 229 | 230 | // Set default entry 231 | fn set_default(&self) -> Result<()> { 232 | println_with_prefix_and_fl!("set_default", kernel = self.to_string()); 233 | self.sbconf.borrow_mut().config.default = Some(self.entry.to_owned() + "-default.conf"); 234 | self.sbconf.borrow().write_config()?; 235 | 236 | Ok(()) 237 | } 238 | 239 | // Remove default entry 240 | fn remove_default(&self) -> Result<()> { 241 | if self.sbconf.borrow().config.default == Some(self.entry.to_owned() + "-default.conf") { 242 | println_with_prefix_and_fl!("remove_default", kernel = self.to_string()); 243 | self.sbconf.borrow_mut().config.default = None; 244 | self.sbconf.borrow().write_config()?; 245 | } 246 | 247 | Ok(()) 248 | } 249 | 250 | #[inline] 251 | fn ask_set_default(&self) -> Result<()> { 252 | Confirm::with_theme(&ColorfulTheme::default()) 253 | .with_prompt(fl!("ask_set_default", kernel = self.to_string())) 254 | .default(false) 255 | .interact()? 256 | .then(|| self.set_default()) 257 | .transpose()?; 258 | 259 | Ok(()) 260 | } 261 | 262 | /// Check if the kernel is the default kernel 263 | #[inline] 264 | fn is_default(&self) -> Result { 265 | let entry = &self 266 | .sbconf 267 | .borrow() 268 | .config 269 | .default_entry(self.esp_mountpoint.join(REL_ENTRY_PATH))?; 270 | 271 | if let Some(entry) = entry { 272 | for token in entry.tokens.iter() { 273 | if let Token::Linux(p) = token { 274 | if *p == *PathBuf::from(REL_DEST_PATH).join(&self.vmlinux) { 275 | return Ok(true); 276 | } 277 | } 278 | } 279 | } 280 | 281 | Ok(false) 282 | } 283 | 284 | #[inline] 285 | fn install_and_make_config(&self, force_write: bool) -> Result<()> { 286 | self.install()?; 287 | self.make_config(force_write)?; 288 | 289 | Ok(()) 290 | } 291 | 292 | /// Generate a sorted vector of kernel filenames 293 | fn list(config: &Config, sbconf: Rc>) -> Result> { 294 | // read /usr/lib/modules to get kernel filenames 295 | let mut kernels = Vec::new(); 296 | 297 | for f in fs::read_dir(MODULES_PATH)? { 298 | let dirname = f? 299 | .file_name() 300 | .into_string() 301 | .map_err(|s| anyhow!("{} {:?}", fl!("invalid_dirname"), s))?; 302 | let dirpath = PathBuf::from(MODULES_PATH).join(&dirname); 303 | 304 | if dirpath.join("modules.dep").exists() 305 | && dirpath.join("modules.order").exists() 306 | && dirpath.join("modules.builtin").exists() 307 | { 308 | match Self::parse(config, &dirname, sbconf.clone()) { 309 | Ok(k) => kernels.push(k), 310 | Err(_) => { 311 | println_with_prefix_and_fl!("skip_unidentified_kernel", kernel = dirname); 312 | } 313 | } 314 | } else { 315 | println_with_prefix_and_fl!("skip_incomplete_kernel", kernel = dirname); 316 | } 317 | } 318 | 319 | // Sort the vector, thus the kernels are 320 | // arranged with versions from newer to older 321 | kernels.sort_by(|a, b| b.cmp(a)); 322 | 323 | Ok(kernels) 324 | } 325 | 326 | /// Generate installed kernel list 327 | fn list_installed(config: &Config, sbconf: Rc>) -> Result> { 328 | let mut installed_kernels = Vec::new(); 329 | 330 | // Construct regex for the template 331 | let re = Regex::new(&config.vmlinux.replace("{VERSION}", r"(?P.+)"))?; 332 | 333 | // Regex match group 334 | if let Ok(d) = fs::read_dir(config.esp_mountpoint.join(REL_DEST_PATH)) { 335 | for x in d { 336 | let filename = &x? 337 | .file_name() 338 | .into_string() 339 | .map_err(|_| anyhow!(fl!("invalid_kernel_filename")))?; 340 | 341 | if let Some(c) = re.captures(filename) { 342 | let version = c 343 | .name("version") 344 | .ok_or_else(|| anyhow!(fl!("invalid_kernel_filename")))? 345 | .as_str(); 346 | 347 | installed_kernels.push(Self::parse(config, version, sbconf.clone())?); 348 | } 349 | } 350 | } 351 | 352 | // Sort the vector, thus the kernels are 353 | // arranged with versions from newer to older 354 | installed_kernels.sort_by(|a, b| b.cmp(a)); 355 | 356 | Ok(installed_kernels) 357 | } 358 | } 359 | -------------------------------------------------------------------------------- /completions/sbf.bash: -------------------------------------------------------------------------------- 1 | _sbf() { 2 | local i cur prev opts cmd 3 | COMPREPLY=() 4 | if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then 5 | cur="$2" 6 | else 7 | cur="${COMP_WORDS[COMP_CWORD]}" 8 | fi 9 | prev="$3" 10 | cmd="" 11 | opts="" 12 | 13 | for i in "${COMP_WORDS[@]:0:COMP_CWORD}" 14 | do 15 | case "${cmd},${i}" in 16 | ",$1") 17 | cmd="sbf" 18 | ;; 19 | sbf,config) 20 | cmd="sbf__config" 21 | ;; 22 | sbf,help) 23 | cmd="sbf__help" 24 | ;; 25 | sbf,init) 26 | cmd="sbf__init" 27 | ;; 28 | sbf,install-kernel) 29 | cmd="sbf__install__kernel" 30 | ;; 31 | sbf,list-available) 32 | cmd="sbf__list__available" 33 | ;; 34 | sbf,list-installed) 35 | cmd="sbf__list__installed" 36 | ;; 37 | sbf,remove-kernel) 38 | cmd="sbf__remove__kernel" 39 | ;; 40 | sbf,select) 41 | cmd="sbf__select" 42 | ;; 43 | sbf,set-default) 44 | cmd="sbf__set__default" 45 | ;; 46 | sbf,set-timeout) 47 | cmd="sbf__set__timeout" 48 | ;; 49 | sbf,update) 50 | cmd="sbf__update" 51 | ;; 52 | sbf__help,config) 53 | cmd="sbf__help__config" 54 | ;; 55 | sbf__help,help) 56 | cmd="sbf__help__help" 57 | ;; 58 | sbf__help,init) 59 | cmd="sbf__help__init" 60 | ;; 61 | sbf__help,install-kernel) 62 | cmd="sbf__help__install__kernel" 63 | ;; 64 | sbf__help,list-available) 65 | cmd="sbf__help__list__available" 66 | ;; 67 | sbf__help,list-installed) 68 | cmd="sbf__help__list__installed" 69 | ;; 70 | sbf__help,remove-kernel) 71 | cmd="sbf__help__remove__kernel" 72 | ;; 73 | sbf__help,select) 74 | cmd="sbf__help__select" 75 | ;; 76 | sbf__help,set-default) 77 | cmd="sbf__help__set__default" 78 | ;; 79 | sbf__help,set-timeout) 80 | cmd="sbf__help__set__timeout" 81 | ;; 82 | sbf__help,update) 83 | cmd="sbf__help__update" 84 | ;; 85 | *) 86 | ;; 87 | esac 88 | done 89 | 90 | case "${cmd}" in 91 | sbf) 92 | opts="-h -V --help --version init update install-kernel remove-kernel select list-available list-installed config set-default set-timeout help" 93 | if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then 94 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 95 | return 0 96 | fi 97 | case "${prev}" in 98 | *) 99 | COMPREPLY=() 100 | ;; 101 | esac 102 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 103 | return 0 104 | ;; 105 | sbf__config) 106 | opts="-h --help" 107 | if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then 108 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 109 | return 0 110 | fi 111 | case "${prev}" in 112 | *) 113 | COMPREPLY=() 114 | ;; 115 | esac 116 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 117 | return 0 118 | ;; 119 | sbf__help) 120 | opts="init update install-kernel remove-kernel select list-available list-installed config set-default set-timeout help" 121 | if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then 122 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 123 | return 0 124 | fi 125 | case "${prev}" in 126 | *) 127 | COMPREPLY=() 128 | ;; 129 | esac 130 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 131 | return 0 132 | ;; 133 | sbf__help__config) 134 | opts="" 135 | if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then 136 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 137 | return 0 138 | fi 139 | case "${prev}" in 140 | *) 141 | COMPREPLY=() 142 | ;; 143 | esac 144 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 145 | return 0 146 | ;; 147 | sbf__help__help) 148 | opts="" 149 | if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then 150 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 151 | return 0 152 | fi 153 | case "${prev}" in 154 | *) 155 | COMPREPLY=() 156 | ;; 157 | esac 158 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 159 | return 0 160 | ;; 161 | sbf__help__init) 162 | opts="" 163 | if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then 164 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 165 | return 0 166 | fi 167 | case "${prev}" in 168 | *) 169 | COMPREPLY=() 170 | ;; 171 | esac 172 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 173 | return 0 174 | ;; 175 | sbf__help__install__kernel) 176 | opts="" 177 | if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then 178 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 179 | return 0 180 | fi 181 | case "${prev}" in 182 | *) 183 | COMPREPLY=() 184 | ;; 185 | esac 186 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 187 | return 0 188 | ;; 189 | sbf__help__list__available) 190 | opts="" 191 | if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then 192 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 193 | return 0 194 | fi 195 | case "${prev}" in 196 | *) 197 | COMPREPLY=() 198 | ;; 199 | esac 200 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 201 | return 0 202 | ;; 203 | sbf__help__list__installed) 204 | opts="" 205 | if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then 206 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 207 | return 0 208 | fi 209 | case "${prev}" in 210 | *) 211 | COMPREPLY=() 212 | ;; 213 | esac 214 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 215 | return 0 216 | ;; 217 | sbf__help__remove__kernel) 218 | opts="" 219 | if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then 220 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 221 | return 0 222 | fi 223 | case "${prev}" in 224 | *) 225 | COMPREPLY=() 226 | ;; 227 | esac 228 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 229 | return 0 230 | ;; 231 | sbf__help__select) 232 | opts="" 233 | if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then 234 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 235 | return 0 236 | fi 237 | case "${prev}" in 238 | *) 239 | COMPREPLY=() 240 | ;; 241 | esac 242 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 243 | return 0 244 | ;; 245 | sbf__help__set__default) 246 | opts="" 247 | if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then 248 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 249 | return 0 250 | fi 251 | case "${prev}" in 252 | *) 253 | COMPREPLY=() 254 | ;; 255 | esac 256 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 257 | return 0 258 | ;; 259 | sbf__help__set__timeout) 260 | opts="" 261 | if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then 262 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 263 | return 0 264 | fi 265 | case "${prev}" in 266 | *) 267 | COMPREPLY=() 268 | ;; 269 | esac 270 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 271 | return 0 272 | ;; 273 | sbf__help__update) 274 | opts="" 275 | if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then 276 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 277 | return 0 278 | fi 279 | case "${prev}" in 280 | *) 281 | COMPREPLY=() 282 | ;; 283 | esac 284 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 285 | return 0 286 | ;; 287 | sbf__init) 288 | opts="-h --help" 289 | if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then 290 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 291 | return 0 292 | fi 293 | case "${prev}" in 294 | *) 295 | COMPREPLY=() 296 | ;; 297 | esac 298 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 299 | return 0 300 | ;; 301 | sbf__install__kernel) 302 | opts="-f -h --force --help [TARGETS]..." 303 | if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then 304 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 305 | return 0 306 | fi 307 | case "${prev}" in 308 | *) 309 | COMPREPLY=() 310 | ;; 311 | esac 312 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 313 | return 0 314 | ;; 315 | sbf__list__available) 316 | opts="-h --help" 317 | if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then 318 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 319 | return 0 320 | fi 321 | case "${prev}" in 322 | *) 323 | COMPREPLY=() 324 | ;; 325 | esac 326 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 327 | return 0 328 | ;; 329 | sbf__list__installed) 330 | opts="-h --help" 331 | if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then 332 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 333 | return 0 334 | fi 335 | case "${prev}" in 336 | *) 337 | COMPREPLY=() 338 | ;; 339 | esac 340 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 341 | return 0 342 | ;; 343 | sbf__remove__kernel) 344 | opts="-h --help [TARGETS]..." 345 | if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then 346 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 347 | return 0 348 | fi 349 | case "${prev}" in 350 | *) 351 | COMPREPLY=() 352 | ;; 353 | esac 354 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 355 | return 0 356 | ;; 357 | sbf__select) 358 | opts="-h --help" 359 | if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then 360 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 361 | return 0 362 | fi 363 | case "${prev}" in 364 | *) 365 | COMPREPLY=() 366 | ;; 367 | esac 368 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 369 | return 0 370 | ;; 371 | sbf__set__default) 372 | opts="-h --help [TARGET]" 373 | if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then 374 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 375 | return 0 376 | fi 377 | case "${prev}" in 378 | *) 379 | COMPREPLY=() 380 | ;; 381 | esac 382 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 383 | return 0 384 | ;; 385 | sbf__set__timeout) 386 | opts="-h --help [TIMEOUT]" 387 | if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then 388 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 389 | return 0 390 | fi 391 | case "${prev}" in 392 | *) 393 | COMPREPLY=() 394 | ;; 395 | esac 396 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 397 | return 0 398 | ;; 399 | sbf__update) 400 | opts="-h --help" 401 | if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then 402 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 403 | return 0 404 | fi 405 | case "${prev}" in 406 | *) 407 | COMPREPLY=() 408 | ;; 409 | esac 410 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 411 | return 0 412 | ;; 413 | esac 414 | } 415 | 416 | if [[ "${BASH_VERSINFO[0]}" -eq 4 && "${BASH_VERSINFO[1]}" -ge 4 || "${BASH_VERSINFO[0]}" -gt 4 ]]; then 417 | complete -F _sbf -o nosort -o bashdefault -o default sbf 418 | else 419 | complete -F _sbf -o bashdefault -o default sbf 420 | fi 421 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.1.3" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "anstream" 16 | version = "0.6.20" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" 19 | dependencies = [ 20 | "anstyle", 21 | "anstyle-parse", 22 | "anstyle-query", 23 | "anstyle-wincon", 24 | "colorchoice", 25 | "is_terminal_polyfill", 26 | "utf8parse", 27 | ] 28 | 29 | [[package]] 30 | name = "anstyle" 31 | version = "1.0.11" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" 34 | 35 | [[package]] 36 | name = "anstyle-parse" 37 | version = "0.2.7" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" 40 | dependencies = [ 41 | "utf8parse", 42 | ] 43 | 44 | [[package]] 45 | name = "anstyle-query" 46 | version = "1.1.4" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" 49 | dependencies = [ 50 | "windows-sys 0.60.2", 51 | ] 52 | 53 | [[package]] 54 | name = "anstyle-wincon" 55 | version = "3.0.10" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" 58 | dependencies = [ 59 | "anstyle", 60 | "once_cell_polyfill", 61 | "windows-sys 0.60.2", 62 | ] 63 | 64 | [[package]] 65 | name = "anyhow" 66 | version = "1.0.99" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" 69 | 70 | [[package]] 71 | name = "arc-swap" 72 | version = "1.7.1" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" 75 | 76 | [[package]] 77 | name = "autocfg" 78 | version = "1.5.0" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" 81 | 82 | [[package]] 83 | name = "basic-toml" 84 | version = "0.1.10" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a" 87 | dependencies = [ 88 | "serde", 89 | ] 90 | 91 | [[package]] 92 | name = "bitflags" 93 | version = "2.9.2" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "6a65b545ab31d687cff52899d4890855fec459eb6afe0da6417b8a18da87aa29" 96 | 97 | [[package]] 98 | name = "block-buffer" 99 | version = "0.10.4" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 102 | dependencies = [ 103 | "generic-array", 104 | ] 105 | 106 | [[package]] 107 | name = "cfg-if" 108 | version = "1.0.1" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" 111 | 112 | [[package]] 113 | name = "clap" 114 | version = "4.5.45" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "1fc0e74a703892159f5ae7d3aac52c8e6c392f5ae5f359c70b5881d60aaac318" 117 | dependencies = [ 118 | "clap_builder", 119 | "clap_derive", 120 | ] 121 | 122 | [[package]] 123 | name = "clap_builder" 124 | version = "4.5.44" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "b3e7f4214277f3c7aa526a59dd3fbe306a370daee1f8b7b8c987069cd8e888a8" 127 | dependencies = [ 128 | "anstream", 129 | "anstyle", 130 | "clap_lex", 131 | "strsim", 132 | ] 133 | 134 | [[package]] 135 | name = "clap_complete" 136 | version = "4.5.57" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "4d9501bd3f5f09f7bbee01da9a511073ed30a80cd7a509f1214bb74eadea71ad" 139 | dependencies = [ 140 | "clap", 141 | ] 142 | 143 | [[package]] 144 | name = "clap_derive" 145 | version = "4.5.45" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "14cb31bb0a7d536caef2639baa7fad459e15c3144efefa6dbd1c84562c4739f6" 148 | dependencies = [ 149 | "heck", 150 | "proc-macro2", 151 | "quote", 152 | "syn", 153 | ] 154 | 155 | [[package]] 156 | name = "clap_lex" 157 | version = "0.7.5" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" 160 | 161 | [[package]] 162 | name = "colorchoice" 163 | version = "1.0.4" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" 166 | 167 | [[package]] 168 | name = "console" 169 | version = "0.15.11" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" 172 | dependencies = [ 173 | "encode_unicode", 174 | "libc", 175 | "once_cell", 176 | "unicode-width", 177 | "windows-sys 0.59.0", 178 | ] 179 | 180 | [[package]] 181 | name = "console" 182 | version = "0.16.0" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "2e09ced7ebbccb63b4c65413d821f2e00ce54c5ca4514ddc6b3c892fdbcbc69d" 185 | dependencies = [ 186 | "encode_unicode", 187 | "libc", 188 | "once_cell", 189 | "unicode-width", 190 | "windows-sys 0.60.2", 191 | ] 192 | 193 | [[package]] 194 | name = "cpufeatures" 195 | version = "0.2.17" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" 198 | dependencies = [ 199 | "libc", 200 | ] 201 | 202 | [[package]] 203 | name = "crypto-common" 204 | version = "0.1.6" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 207 | dependencies = [ 208 | "generic-array", 209 | "typenum", 210 | ] 211 | 212 | [[package]] 213 | name = "dialoguer" 214 | version = "0.11.0" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "658bce805d770f407bc62102fca7c2c64ceef2fbcb2b8bd19d2765ce093980de" 217 | dependencies = [ 218 | "console 0.15.11", 219 | "shell-words", 220 | "tempfile", 221 | "thiserror 1.0.69", 222 | "zeroize", 223 | ] 224 | 225 | [[package]] 226 | name = "digest" 227 | version = "0.10.7" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 230 | dependencies = [ 231 | "block-buffer", 232 | "crypto-common", 233 | ] 234 | 235 | [[package]] 236 | name = "displaydoc" 237 | version = "0.2.5" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 240 | dependencies = [ 241 | "proc-macro2", 242 | "quote", 243 | "syn", 244 | ] 245 | 246 | [[package]] 247 | name = "encode_unicode" 248 | version = "1.0.0" 249 | source = "registry+https://github.com/rust-lang/crates.io-index" 250 | checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" 251 | 252 | [[package]] 253 | name = "equivalent" 254 | version = "1.0.2" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 257 | 258 | [[package]] 259 | name = "errno" 260 | version = "0.3.13" 261 | source = "registry+https://github.com/rust-lang/crates.io-index" 262 | checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" 263 | dependencies = [ 264 | "libc", 265 | "windows-sys 0.60.2", 266 | ] 267 | 268 | [[package]] 269 | name = "fastrand" 270 | version = "2.3.0" 271 | source = "registry+https://github.com/rust-lang/crates.io-index" 272 | checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 273 | 274 | [[package]] 275 | name = "find-crate" 276 | version = "0.6.3" 277 | source = "registry+https://github.com/rust-lang/crates.io-index" 278 | checksum = "59a98bbaacea1c0eb6a0876280051b892eb73594fd90cf3b20e9c817029c57d2" 279 | dependencies = [ 280 | "toml 0.5.11", 281 | ] 282 | 283 | [[package]] 284 | name = "fluent" 285 | version = "0.17.0" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "8137a6d5a2c50d6b0ebfcb9aaa91a28154e0a70605f112d30cb0cd4a78670477" 288 | dependencies = [ 289 | "fluent-bundle", 290 | "unic-langid", 291 | ] 292 | 293 | [[package]] 294 | name = "fluent-bundle" 295 | version = "0.16.0" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "01203cb8918f5711e73891b347816d932046f95f54207710bda99beaeb423bf4" 298 | dependencies = [ 299 | "fluent-langneg", 300 | "fluent-syntax", 301 | "intl-memoizer", 302 | "intl_pluralrules", 303 | "rustc-hash", 304 | "self_cell", 305 | "smallvec", 306 | "unic-langid", 307 | ] 308 | 309 | [[package]] 310 | name = "fluent-langneg" 311 | version = "0.13.0" 312 | source = "registry+https://github.com/rust-lang/crates.io-index" 313 | checksum = "2c4ad0989667548f06ccd0e306ed56b61bd4d35458d54df5ec7587c0e8ed5e94" 314 | dependencies = [ 315 | "unic-langid", 316 | ] 317 | 318 | [[package]] 319 | name = "fluent-syntax" 320 | version = "0.12.0" 321 | source = "registry+https://github.com/rust-lang/crates.io-index" 322 | checksum = "54f0d287c53ffd184d04d8677f590f4ac5379785529e5e08b1c8083acdd5c198" 323 | dependencies = [ 324 | "memchr", 325 | "thiserror 2.0.15", 326 | ] 327 | 328 | [[package]] 329 | name = "generic-array" 330 | version = "0.14.7" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 333 | dependencies = [ 334 | "typenum", 335 | "version_check", 336 | ] 337 | 338 | [[package]] 339 | name = "getrandom" 340 | version = "0.3.3" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" 343 | dependencies = [ 344 | "cfg-if", 345 | "libc", 346 | "r-efi", 347 | "wasi", 348 | ] 349 | 350 | [[package]] 351 | name = "hashbrown" 352 | version = "0.15.5" 353 | source = "registry+https://github.com/rust-lang/crates.io-index" 354 | checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" 355 | 356 | [[package]] 357 | name = "heck" 358 | version = "0.5.0" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 361 | 362 | [[package]] 363 | name = "i18n-config" 364 | version = "0.4.8" 365 | source = "registry+https://github.com/rust-lang/crates.io-index" 366 | checksum = "3e06b90c8a0d252e203c94344b21e35a30f3a3a85dc7db5af8f8df9f3e0c63ef" 367 | dependencies = [ 368 | "basic-toml", 369 | "log", 370 | "serde", 371 | "serde_derive", 372 | "thiserror 1.0.69", 373 | "unic-langid", 374 | ] 375 | 376 | [[package]] 377 | name = "i18n-embed" 378 | version = "0.16.0" 379 | source = "registry+https://github.com/rust-lang/crates.io-index" 380 | checksum = "a217bbb075dcaefb292efa78897fc0678245ca67f265d12c351e42268fcb0305" 381 | dependencies = [ 382 | "arc-swap", 383 | "fluent", 384 | "fluent-langneg", 385 | "fluent-syntax", 386 | "i18n-embed-impl", 387 | "intl-memoizer", 388 | "log", 389 | "parking_lot", 390 | "rust-embed", 391 | "sys-locale", 392 | "thiserror 1.0.69", 393 | "unic-langid", 394 | "walkdir", 395 | ] 396 | 397 | [[package]] 398 | name = "i18n-embed-fl" 399 | version = "0.10.0" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "e598ed73b67db92f61e04672e599eef2991a262a40e1666735b8a86d2e7e9f30" 402 | dependencies = [ 403 | "find-crate", 404 | "fluent", 405 | "fluent-syntax", 406 | "i18n-config", 407 | "i18n-embed", 408 | "proc-macro-error2", 409 | "proc-macro2", 410 | "quote", 411 | "strsim", 412 | "syn", 413 | "unic-langid", 414 | ] 415 | 416 | [[package]] 417 | name = "i18n-embed-impl" 418 | version = "0.8.4" 419 | source = "registry+https://github.com/rust-lang/crates.io-index" 420 | checksum = "0f2cc0e0523d1fe6fc2c6f66e5038624ea8091b3e7748b5e8e0c84b1698db6c2" 421 | dependencies = [ 422 | "find-crate", 423 | "i18n-config", 424 | "proc-macro2", 425 | "quote", 426 | "syn", 427 | ] 428 | 429 | [[package]] 430 | name = "indexmap" 431 | version = "2.10.0" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" 434 | dependencies = [ 435 | "equivalent", 436 | "hashbrown", 437 | ] 438 | 439 | [[package]] 440 | name = "intl-memoizer" 441 | version = "0.5.3" 442 | source = "registry+https://github.com/rust-lang/crates.io-index" 443 | checksum = "310da2e345f5eb861e7a07ee182262e94975051db9e4223e909ba90f392f163f" 444 | dependencies = [ 445 | "type-map", 446 | "unic-langid", 447 | ] 448 | 449 | [[package]] 450 | name = "intl_pluralrules" 451 | version = "7.0.2" 452 | source = "registry+https://github.com/rust-lang/crates.io-index" 453 | checksum = "078ea7b7c29a2b4df841a7f6ac8775ff6074020c6776d48491ce2268e068f972" 454 | dependencies = [ 455 | "unic-langid", 456 | ] 457 | 458 | [[package]] 459 | name = "is_terminal_polyfill" 460 | version = "1.70.1" 461 | source = "registry+https://github.com/rust-lang/crates.io-index" 462 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 463 | 464 | [[package]] 465 | name = "itoa" 466 | version = "1.0.15" 467 | source = "registry+https://github.com/rust-lang/crates.io-index" 468 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 469 | 470 | [[package]] 471 | name = "lazy_static" 472 | version = "1.5.0" 473 | source = "registry+https://github.com/rust-lang/crates.io-index" 474 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 475 | 476 | [[package]] 477 | name = "libc" 478 | version = "0.2.175" 479 | source = "registry+https://github.com/rust-lang/crates.io-index" 480 | checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" 481 | 482 | [[package]] 483 | name = "libsdbootconf" 484 | version = "0.11.2" 485 | source = "registry+https://github.com/rust-lang/crates.io-index" 486 | checksum = "23446cb4321101ac3540037acfcf321e74d913eec7895e436587a684a4ce6c55" 487 | dependencies = [ 488 | "thiserror 1.0.69", 489 | ] 490 | 491 | [[package]] 492 | name = "linux-raw-sys" 493 | version = "0.9.4" 494 | source = "registry+https://github.com/rust-lang/crates.io-index" 495 | checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" 496 | 497 | [[package]] 498 | name = "lock_api" 499 | version = "0.4.13" 500 | source = "registry+https://github.com/rust-lang/crates.io-index" 501 | checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" 502 | dependencies = [ 503 | "autocfg", 504 | "scopeguard", 505 | ] 506 | 507 | [[package]] 508 | name = "log" 509 | version = "0.4.27" 510 | source = "registry+https://github.com/rust-lang/crates.io-index" 511 | checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" 512 | 513 | [[package]] 514 | name = "memchr" 515 | version = "2.7.5" 516 | source = "registry+https://github.com/rust-lang/crates.io-index" 517 | checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" 518 | 519 | [[package]] 520 | name = "nom" 521 | version = "8.0.0" 522 | source = "registry+https://github.com/rust-lang/crates.io-index" 523 | checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" 524 | dependencies = [ 525 | "memchr", 526 | ] 527 | 528 | [[package]] 529 | name = "once_cell" 530 | version = "1.21.3" 531 | source = "registry+https://github.com/rust-lang/crates.io-index" 532 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 533 | 534 | [[package]] 535 | name = "once_cell_polyfill" 536 | version = "1.70.1" 537 | source = "registry+https://github.com/rust-lang/crates.io-index" 538 | checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" 539 | 540 | [[package]] 541 | name = "parking_lot" 542 | version = "0.12.4" 543 | source = "registry+https://github.com/rust-lang/crates.io-index" 544 | checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" 545 | dependencies = [ 546 | "lock_api", 547 | "parking_lot_core", 548 | ] 549 | 550 | [[package]] 551 | name = "parking_lot_core" 552 | version = "0.9.11" 553 | source = "registry+https://github.com/rust-lang/crates.io-index" 554 | checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" 555 | dependencies = [ 556 | "cfg-if", 557 | "libc", 558 | "redox_syscall", 559 | "smallvec", 560 | "windows-targets 0.52.6", 561 | ] 562 | 563 | [[package]] 564 | name = "proc-macro-error-attr2" 565 | version = "2.0.0" 566 | source = "registry+https://github.com/rust-lang/crates.io-index" 567 | checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" 568 | dependencies = [ 569 | "proc-macro2", 570 | "quote", 571 | ] 572 | 573 | [[package]] 574 | name = "proc-macro-error2" 575 | version = "2.0.1" 576 | source = "registry+https://github.com/rust-lang/crates.io-index" 577 | checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" 578 | dependencies = [ 579 | "proc-macro-error-attr2", 580 | "proc-macro2", 581 | "quote", 582 | "syn", 583 | ] 584 | 585 | [[package]] 586 | name = "proc-macro2" 587 | version = "1.0.101" 588 | source = "registry+https://github.com/rust-lang/crates.io-index" 589 | checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" 590 | dependencies = [ 591 | "unicode-ident", 592 | ] 593 | 594 | [[package]] 595 | name = "quote" 596 | version = "1.0.40" 597 | source = "registry+https://github.com/rust-lang/crates.io-index" 598 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 599 | dependencies = [ 600 | "proc-macro2", 601 | ] 602 | 603 | [[package]] 604 | name = "r-efi" 605 | version = "5.3.0" 606 | source = "registry+https://github.com/rust-lang/crates.io-index" 607 | checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" 608 | 609 | [[package]] 610 | name = "redox_syscall" 611 | version = "0.5.17" 612 | source = "registry+https://github.com/rust-lang/crates.io-index" 613 | checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" 614 | dependencies = [ 615 | "bitflags", 616 | ] 617 | 618 | [[package]] 619 | name = "regex" 620 | version = "1.11.1" 621 | source = "registry+https://github.com/rust-lang/crates.io-index" 622 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 623 | dependencies = [ 624 | "aho-corasick", 625 | "memchr", 626 | "regex-automata", 627 | "regex-syntax", 628 | ] 629 | 630 | [[package]] 631 | name = "regex-automata" 632 | version = "0.4.9" 633 | source = "registry+https://github.com/rust-lang/crates.io-index" 634 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 635 | dependencies = [ 636 | "aho-corasick", 637 | "memchr", 638 | "regex-syntax", 639 | ] 640 | 641 | [[package]] 642 | name = "regex-syntax" 643 | version = "0.8.5" 644 | source = "registry+https://github.com/rust-lang/crates.io-index" 645 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 646 | 647 | [[package]] 648 | name = "rust-embed" 649 | version = "8.7.2" 650 | source = "registry+https://github.com/rust-lang/crates.io-index" 651 | checksum = "025908b8682a26ba8d12f6f2d66b987584a4a87bc024abc5bbc12553a8cd178a" 652 | dependencies = [ 653 | "rust-embed-impl", 654 | "rust-embed-utils", 655 | "walkdir", 656 | ] 657 | 658 | [[package]] 659 | name = "rust-embed-impl" 660 | version = "8.7.2" 661 | source = "registry+https://github.com/rust-lang/crates.io-index" 662 | checksum = "6065f1a4392b71819ec1ea1df1120673418bf386f50de1d6f54204d836d4349c" 663 | dependencies = [ 664 | "proc-macro2", 665 | "quote", 666 | "rust-embed-utils", 667 | "syn", 668 | "walkdir", 669 | ] 670 | 671 | [[package]] 672 | name = "rust-embed-utils" 673 | version = "8.7.2" 674 | source = "registry+https://github.com/rust-lang/crates.io-index" 675 | checksum = "f6cc0c81648b20b70c491ff8cce00c1c3b223bb8ed2b5d41f0e54c6c4c0a3594" 676 | dependencies = [ 677 | "sha2", 678 | "walkdir", 679 | ] 680 | 681 | [[package]] 682 | name = "rustc-hash" 683 | version = "2.1.1" 684 | source = "registry+https://github.com/rust-lang/crates.io-index" 685 | checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" 686 | 687 | [[package]] 688 | name = "rustix" 689 | version = "1.0.8" 690 | source = "registry+https://github.com/rust-lang/crates.io-index" 691 | checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" 692 | dependencies = [ 693 | "bitflags", 694 | "errno", 695 | "libc", 696 | "linux-raw-sys", 697 | "windows-sys 0.60.2", 698 | ] 699 | 700 | [[package]] 701 | name = "ryu" 702 | version = "1.0.20" 703 | source = "registry+https://github.com/rust-lang/crates.io-index" 704 | checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 705 | 706 | [[package]] 707 | name = "same-file" 708 | version = "1.0.6" 709 | source = "registry+https://github.com/rust-lang/crates.io-index" 710 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 711 | dependencies = [ 712 | "winapi-util", 713 | ] 714 | 715 | [[package]] 716 | name = "scopeguard" 717 | version = "1.2.0" 718 | source = "registry+https://github.com/rust-lang/crates.io-index" 719 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 720 | 721 | [[package]] 722 | name = "self_cell" 723 | version = "1.2.0" 724 | source = "registry+https://github.com/rust-lang/crates.io-index" 725 | checksum = "0f7d95a54511e0c7be3f51e8867aa8cf35148d7b9445d44de2f943e2b206e749" 726 | 727 | [[package]] 728 | name = "serde" 729 | version = "1.0.219" 730 | source = "registry+https://github.com/rust-lang/crates.io-index" 731 | checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 732 | dependencies = [ 733 | "serde_derive", 734 | ] 735 | 736 | [[package]] 737 | name = "serde_derive" 738 | version = "1.0.219" 739 | source = "registry+https://github.com/rust-lang/crates.io-index" 740 | checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 741 | dependencies = [ 742 | "proc-macro2", 743 | "quote", 744 | "syn", 745 | ] 746 | 747 | [[package]] 748 | name = "serde_json" 749 | version = "1.0.142" 750 | source = "registry+https://github.com/rust-lang/crates.io-index" 751 | checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" 752 | dependencies = [ 753 | "itoa", 754 | "memchr", 755 | "ryu", 756 | "serde", 757 | ] 758 | 759 | [[package]] 760 | name = "serde_spanned" 761 | version = "1.0.0" 762 | source = "registry+https://github.com/rust-lang/crates.io-index" 763 | checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" 764 | dependencies = [ 765 | "serde", 766 | ] 767 | 768 | [[package]] 769 | name = "sha2" 770 | version = "0.10.9" 771 | source = "registry+https://github.com/rust-lang/crates.io-index" 772 | checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" 773 | dependencies = [ 774 | "cfg-if", 775 | "cpufeatures", 776 | "digest", 777 | ] 778 | 779 | [[package]] 780 | name = "shell-words" 781 | version = "1.1.0" 782 | source = "registry+https://github.com/rust-lang/crates.io-index" 783 | checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" 784 | 785 | [[package]] 786 | name = "smallvec" 787 | version = "1.15.1" 788 | source = "registry+https://github.com/rust-lang/crates.io-index" 789 | checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" 790 | 791 | [[package]] 792 | name = "strsim" 793 | version = "0.11.1" 794 | source = "registry+https://github.com/rust-lang/crates.io-index" 795 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 796 | 797 | [[package]] 798 | name = "syn" 799 | version = "2.0.106" 800 | source = "registry+https://github.com/rust-lang/crates.io-index" 801 | checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" 802 | dependencies = [ 803 | "proc-macro2", 804 | "quote", 805 | "unicode-ident", 806 | ] 807 | 808 | [[package]] 809 | name = "sys-locale" 810 | version = "0.3.2" 811 | source = "registry+https://github.com/rust-lang/crates.io-index" 812 | checksum = "8eab9a99a024a169fe8a903cf9d4a3b3601109bcc13bd9e3c6fff259138626c4" 813 | dependencies = [ 814 | "libc", 815 | ] 816 | 817 | [[package]] 818 | name = "systemd-boot-friend-rs" 819 | version = "0.27.5" 820 | dependencies = [ 821 | "anyhow", 822 | "clap", 823 | "clap_complete", 824 | "console 0.16.0", 825 | "dialoguer", 826 | "i18n-embed", 827 | "i18n-embed-fl", 828 | "lazy_static", 829 | "libsdbootconf", 830 | "nom", 831 | "regex", 832 | "rust-embed", 833 | "same-file", 834 | "serde", 835 | "serde_json", 836 | "toml 0.9.5", 837 | "unic-langid", 838 | ] 839 | 840 | [[package]] 841 | name = "tempfile" 842 | version = "3.20.0" 843 | source = "registry+https://github.com/rust-lang/crates.io-index" 844 | checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" 845 | dependencies = [ 846 | "fastrand", 847 | "getrandom", 848 | "once_cell", 849 | "rustix", 850 | "windows-sys 0.59.0", 851 | ] 852 | 853 | [[package]] 854 | name = "thiserror" 855 | version = "1.0.69" 856 | source = "registry+https://github.com/rust-lang/crates.io-index" 857 | checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 858 | dependencies = [ 859 | "thiserror-impl 1.0.69", 860 | ] 861 | 862 | [[package]] 863 | name = "thiserror" 864 | version = "2.0.15" 865 | source = "registry+https://github.com/rust-lang/crates.io-index" 866 | checksum = "80d76d3f064b981389ecb4b6b7f45a0bf9fdac1d5b9204c7bd6714fecc302850" 867 | dependencies = [ 868 | "thiserror-impl 2.0.15", 869 | ] 870 | 871 | [[package]] 872 | name = "thiserror-impl" 873 | version = "1.0.69" 874 | source = "registry+https://github.com/rust-lang/crates.io-index" 875 | checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 876 | dependencies = [ 877 | "proc-macro2", 878 | "quote", 879 | "syn", 880 | ] 881 | 882 | [[package]] 883 | name = "thiserror-impl" 884 | version = "2.0.15" 885 | source = "registry+https://github.com/rust-lang/crates.io-index" 886 | checksum = "44d29feb33e986b6ea906bd9c3559a856983f92371b3eaa5e83782a351623de0" 887 | dependencies = [ 888 | "proc-macro2", 889 | "quote", 890 | "syn", 891 | ] 892 | 893 | [[package]] 894 | name = "tinystr" 895 | version = "0.8.1" 896 | source = "registry+https://github.com/rust-lang/crates.io-index" 897 | checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" 898 | dependencies = [ 899 | "displaydoc", 900 | "zerovec", 901 | ] 902 | 903 | [[package]] 904 | name = "toml" 905 | version = "0.5.11" 906 | source = "registry+https://github.com/rust-lang/crates.io-index" 907 | checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" 908 | dependencies = [ 909 | "serde", 910 | ] 911 | 912 | [[package]] 913 | name = "toml" 914 | version = "0.9.5" 915 | source = "registry+https://github.com/rust-lang/crates.io-index" 916 | checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8" 917 | dependencies = [ 918 | "indexmap", 919 | "serde", 920 | "serde_spanned", 921 | "toml_datetime", 922 | "toml_parser", 923 | "toml_writer", 924 | "winnow", 925 | ] 926 | 927 | [[package]] 928 | name = "toml_datetime" 929 | version = "0.7.0" 930 | source = "registry+https://github.com/rust-lang/crates.io-index" 931 | checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" 932 | dependencies = [ 933 | "serde", 934 | ] 935 | 936 | [[package]] 937 | name = "toml_parser" 938 | version = "1.0.2" 939 | source = "registry+https://github.com/rust-lang/crates.io-index" 940 | checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10" 941 | dependencies = [ 942 | "winnow", 943 | ] 944 | 945 | [[package]] 946 | name = "toml_writer" 947 | version = "1.0.2" 948 | source = "registry+https://github.com/rust-lang/crates.io-index" 949 | checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" 950 | 951 | [[package]] 952 | name = "type-map" 953 | version = "0.5.1" 954 | source = "registry+https://github.com/rust-lang/crates.io-index" 955 | checksum = "cb30dbbd9036155e74adad6812e9898d03ec374946234fbcebd5dfc7b9187b90" 956 | dependencies = [ 957 | "rustc-hash", 958 | ] 959 | 960 | [[package]] 961 | name = "typenum" 962 | version = "1.18.0" 963 | source = "registry+https://github.com/rust-lang/crates.io-index" 964 | checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" 965 | 966 | [[package]] 967 | name = "unic-langid" 968 | version = "0.9.6" 969 | source = "registry+https://github.com/rust-lang/crates.io-index" 970 | checksum = "a28ba52c9b05311f4f6e62d5d9d46f094bd6e84cb8df7b3ef952748d752a7d05" 971 | dependencies = [ 972 | "unic-langid-impl", 973 | ] 974 | 975 | [[package]] 976 | name = "unic-langid-impl" 977 | version = "0.9.6" 978 | source = "registry+https://github.com/rust-lang/crates.io-index" 979 | checksum = "dce1bf08044d4b7a94028c93786f8566047edc11110595914de93362559bc658" 980 | dependencies = [ 981 | "serde", 982 | "tinystr", 983 | ] 984 | 985 | [[package]] 986 | name = "unicode-ident" 987 | version = "1.0.18" 988 | source = "registry+https://github.com/rust-lang/crates.io-index" 989 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 990 | 991 | [[package]] 992 | name = "unicode-width" 993 | version = "0.2.1" 994 | source = "registry+https://github.com/rust-lang/crates.io-index" 995 | checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" 996 | 997 | [[package]] 998 | name = "utf8parse" 999 | version = "0.2.2" 1000 | source = "registry+https://github.com/rust-lang/crates.io-index" 1001 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 1002 | 1003 | [[package]] 1004 | name = "version_check" 1005 | version = "0.9.5" 1006 | source = "registry+https://github.com/rust-lang/crates.io-index" 1007 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 1008 | 1009 | [[package]] 1010 | name = "walkdir" 1011 | version = "2.5.0" 1012 | source = "registry+https://github.com/rust-lang/crates.io-index" 1013 | checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 1014 | dependencies = [ 1015 | "same-file", 1016 | "winapi-util", 1017 | ] 1018 | 1019 | [[package]] 1020 | name = "wasi" 1021 | version = "0.14.2+wasi-0.2.4" 1022 | source = "registry+https://github.com/rust-lang/crates.io-index" 1023 | checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" 1024 | dependencies = [ 1025 | "wit-bindgen-rt", 1026 | ] 1027 | 1028 | [[package]] 1029 | name = "winapi-util" 1030 | version = "0.1.9" 1031 | source = "registry+https://github.com/rust-lang/crates.io-index" 1032 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 1033 | dependencies = [ 1034 | "windows-sys 0.59.0", 1035 | ] 1036 | 1037 | [[package]] 1038 | name = "windows-link" 1039 | version = "0.1.3" 1040 | source = "registry+https://github.com/rust-lang/crates.io-index" 1041 | checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" 1042 | 1043 | [[package]] 1044 | name = "windows-sys" 1045 | version = "0.59.0" 1046 | source = "registry+https://github.com/rust-lang/crates.io-index" 1047 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 1048 | dependencies = [ 1049 | "windows-targets 0.52.6", 1050 | ] 1051 | 1052 | [[package]] 1053 | name = "windows-sys" 1054 | version = "0.60.2" 1055 | source = "registry+https://github.com/rust-lang/crates.io-index" 1056 | checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" 1057 | dependencies = [ 1058 | "windows-targets 0.53.3", 1059 | ] 1060 | 1061 | [[package]] 1062 | name = "windows-targets" 1063 | version = "0.52.6" 1064 | source = "registry+https://github.com/rust-lang/crates.io-index" 1065 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 1066 | dependencies = [ 1067 | "windows_aarch64_gnullvm 0.52.6", 1068 | "windows_aarch64_msvc 0.52.6", 1069 | "windows_i686_gnu 0.52.6", 1070 | "windows_i686_gnullvm 0.52.6", 1071 | "windows_i686_msvc 0.52.6", 1072 | "windows_x86_64_gnu 0.52.6", 1073 | "windows_x86_64_gnullvm 0.52.6", 1074 | "windows_x86_64_msvc 0.52.6", 1075 | ] 1076 | 1077 | [[package]] 1078 | name = "windows-targets" 1079 | version = "0.53.3" 1080 | source = "registry+https://github.com/rust-lang/crates.io-index" 1081 | checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" 1082 | dependencies = [ 1083 | "windows-link", 1084 | "windows_aarch64_gnullvm 0.53.0", 1085 | "windows_aarch64_msvc 0.53.0", 1086 | "windows_i686_gnu 0.53.0", 1087 | "windows_i686_gnullvm 0.53.0", 1088 | "windows_i686_msvc 0.53.0", 1089 | "windows_x86_64_gnu 0.53.0", 1090 | "windows_x86_64_gnullvm 0.53.0", 1091 | "windows_x86_64_msvc 0.53.0", 1092 | ] 1093 | 1094 | [[package]] 1095 | name = "windows_aarch64_gnullvm" 1096 | version = "0.52.6" 1097 | source = "registry+https://github.com/rust-lang/crates.io-index" 1098 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 1099 | 1100 | [[package]] 1101 | name = "windows_aarch64_gnullvm" 1102 | version = "0.53.0" 1103 | source = "registry+https://github.com/rust-lang/crates.io-index" 1104 | checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" 1105 | 1106 | [[package]] 1107 | name = "windows_aarch64_msvc" 1108 | version = "0.52.6" 1109 | source = "registry+https://github.com/rust-lang/crates.io-index" 1110 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 1111 | 1112 | [[package]] 1113 | name = "windows_aarch64_msvc" 1114 | version = "0.53.0" 1115 | source = "registry+https://github.com/rust-lang/crates.io-index" 1116 | checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" 1117 | 1118 | [[package]] 1119 | name = "windows_i686_gnu" 1120 | version = "0.52.6" 1121 | source = "registry+https://github.com/rust-lang/crates.io-index" 1122 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 1123 | 1124 | [[package]] 1125 | name = "windows_i686_gnu" 1126 | version = "0.53.0" 1127 | source = "registry+https://github.com/rust-lang/crates.io-index" 1128 | checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" 1129 | 1130 | [[package]] 1131 | name = "windows_i686_gnullvm" 1132 | version = "0.52.6" 1133 | source = "registry+https://github.com/rust-lang/crates.io-index" 1134 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 1135 | 1136 | [[package]] 1137 | name = "windows_i686_gnullvm" 1138 | version = "0.53.0" 1139 | source = "registry+https://github.com/rust-lang/crates.io-index" 1140 | checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" 1141 | 1142 | [[package]] 1143 | name = "windows_i686_msvc" 1144 | version = "0.52.6" 1145 | source = "registry+https://github.com/rust-lang/crates.io-index" 1146 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 1147 | 1148 | [[package]] 1149 | name = "windows_i686_msvc" 1150 | version = "0.53.0" 1151 | source = "registry+https://github.com/rust-lang/crates.io-index" 1152 | checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" 1153 | 1154 | [[package]] 1155 | name = "windows_x86_64_gnu" 1156 | version = "0.52.6" 1157 | source = "registry+https://github.com/rust-lang/crates.io-index" 1158 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 1159 | 1160 | [[package]] 1161 | name = "windows_x86_64_gnu" 1162 | version = "0.53.0" 1163 | source = "registry+https://github.com/rust-lang/crates.io-index" 1164 | checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" 1165 | 1166 | [[package]] 1167 | name = "windows_x86_64_gnullvm" 1168 | version = "0.52.6" 1169 | source = "registry+https://github.com/rust-lang/crates.io-index" 1170 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 1171 | 1172 | [[package]] 1173 | name = "windows_x86_64_gnullvm" 1174 | version = "0.53.0" 1175 | source = "registry+https://github.com/rust-lang/crates.io-index" 1176 | checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" 1177 | 1178 | [[package]] 1179 | name = "windows_x86_64_msvc" 1180 | version = "0.52.6" 1181 | source = "registry+https://github.com/rust-lang/crates.io-index" 1182 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 1183 | 1184 | [[package]] 1185 | name = "windows_x86_64_msvc" 1186 | version = "0.53.0" 1187 | source = "registry+https://github.com/rust-lang/crates.io-index" 1188 | checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" 1189 | 1190 | [[package]] 1191 | name = "winnow" 1192 | version = "0.7.12" 1193 | source = "registry+https://github.com/rust-lang/crates.io-index" 1194 | checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" 1195 | 1196 | [[package]] 1197 | name = "wit-bindgen-rt" 1198 | version = "0.39.0" 1199 | source = "registry+https://github.com/rust-lang/crates.io-index" 1200 | checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" 1201 | dependencies = [ 1202 | "bitflags", 1203 | ] 1204 | 1205 | [[package]] 1206 | name = "zerofrom" 1207 | version = "0.1.6" 1208 | source = "registry+https://github.com/rust-lang/crates.io-index" 1209 | checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" 1210 | 1211 | [[package]] 1212 | name = "zeroize" 1213 | version = "1.8.1" 1214 | source = "registry+https://github.com/rust-lang/crates.io-index" 1215 | checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 1216 | 1217 | [[package]] 1218 | name = "zerovec" 1219 | version = "0.11.4" 1220 | source = "registry+https://github.com/rust-lang/crates.io-index" 1221 | checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" 1222 | dependencies = [ 1223 | "zerofrom", 1224 | ] 1225 | --------------------------------------------------------------------------------