diff --git a/src/hive_config.rs b/src/hive_config.rs index 1a0b16f..91c8ad3 100644 --- a/src/hive_config.rs +++ b/src/hive_config.rs @@ -35,7 +35,7 @@ pub struct HiveConfig { #[derive(Debug, Serialize, Deserialize)] pub struct KernelConfig { /// kernel cmd string that will be passed as kernel boot parameter. - cmd: String, + pub cmd: Option, } #[derive(Debug, Serialize, Deserialize)] diff --git a/src/hive_system.rs b/src/hive_system.rs index 49ecbbd..2858c74 100644 --- a/src/hive_system.rs +++ b/src/hive_system.rs @@ -20,15 +20,19 @@ use std::fs; use std::process::Command; +use std::vec; use time_format; - -const SNAPS_DIR: &str = "/bzz_snaps"; +const SNAPS_DIR: &str = "/bzz-snaps"; const EFI_BOOT_MOUNT_POINT: &str = "/beefi"; +const UKI_IMAGE_NAME: &str = "/beefi/EFI/kira-linux-lts.efi"; +const UKI_IMAGE_BACK_NAME: &str = "/beefi/EFI/kira-linux-lts-back.efi"; const EFI_PARTLABEL: &str = "bee-efi"; -const BOOT_MENU_LABEL: &str = "Kira OS"; -const UKI_IMAGE_NAME: &str = "\\EFI\\kira-linux-lts.efi"; +const BOOT_MENU_LABEL: &str = "Kira OS"; +const BOOT_MENU_LABEL_BACK: &str = "Kira OS Fallback"; +const BOOT_UKI_IMAGE_NAME: &str = "\\EFI\\kira-linux-lts.efi"; +const BOOT_UKI_IMAGE_NAME_BACK: &str = "\\EFI\\kira-linux-lts-back.efi"; #[derive(Debug)] pub struct CommandFailed { @@ -309,7 +313,6 @@ pub fn find_partlabel(partlabel: &str) -> Result, ExecCommandErro } } - /// /// We need to use lsblk to find out parent device, or read topology from sysfs /// (basename $(readlink -f /sys/class/block/sda1/..)) @@ -318,9 +321,12 @@ pub fn find_partlabel(partlabel: &str) -> Result, ExecCommandErro /// lsblk -P -p -Q 'PARTLABEL=="nyazefi"' -o NAME,PARTLABEL,PARTN,PKNAME /// Returns: touple of partition number and parent device as Strings (part_num, par_dev) /// Returns Error if no efi partition was found -/// - -fn get_efi_part_info() -> Result<(String,String), ExecCommandError> { +/// +/// We need more complex logic for it. +/// Need to alterate between two boot records. +/// +/// +fn get_efi_part_info() -> Result<(String, String), ExecCommandError> { use regex::Regex; let re = Regex::new(r#"^PARTN="([0-9]+)" PKNAME="([0-9a-Z]+)"$"#).unwrap(); @@ -332,35 +338,174 @@ fn get_efi_part_info() -> Result<(String,String), ExecCommandError> { // let part_no = captured[1]; // let parent_dev = captured[2]; Ok((captured[1].to_string(), captured[2].to_string())) + } else { + Err(ExecCommandError::Other(format!( + "No EFI partition (with PARTLABEL={}) was found!", + EFI_PARTLABEL + ))) + } +} + +/// +/// List all boot entries hex numbers +/// Boot0000* Nyan +/// +fn list_efi_bootnums() -> Result, ExecCommandError> { + use regex::Regex; + let re = Regex::new(r#"^Boot([0-9A-F]{4})\* "#).unwrap(); + let str_res = exec_command_out("efibootmgr", &vec![])?; + + // for each line in output match bootnum + // and collect to vector + Ok(str_res + .lines() + .filter_map(|hs| re.captures(hs)) + .map(|captured| captured[1].to_string()) + .collect()) +} + +/// +/// sudo efibootmgr -B -b 0001 +/// -B (--delete-bootnum) +/// +fn delete_efi_bootnum(bootnum: &str) -> Result<(), ExecCommandError> { + exec_command("efibootmgr", &vec!["-B", "-b", bootnum]) +} + +/// +/// Delets all EFI bootnums +/// Returns number of deleted bootnums on success +/// +fn delete_all_efi_bootnums() -> Result { + let bootnums = list_efi_bootnums()?; + for bootnum in &bootnums { + delete_efi_bootnum(bootnum)?; + } + Ok(bootnums.len()) +} + +/// +/// Creates EFI boot record +/// boot_index - inserting it at specified boot index (bootorder) +/// Assuming boot partition has "bee-efi" PARTLABEL +/// +fn efi_boot_record_create(boot_index: u16, boot_label: &str, uki_path: &str, maybe_kernel_cmd: &Option) -> Result<(), ExecCommandError> { + + let (efi_partno, efi_parent_dev) = get_efi_part_info()?; + + //let local_date = time_format::strftime_local("%Y-%m-%d", time_format::now().unwrap()).unwrap(); + let string_boot_index = boot_index.to_string(); + + let mut efibootmgr_args: Vec<&str> = vec![ + "--create", + "--index", + &string_boot_index, + "--disk", + &efi_parent_dev, + "--part", + &efi_partno, + "--label", + boot_label, + "--loader", + uki_path, + ]; + if let Some(kernel_cmd) = maybe_kernel_cmd { + efibootmgr_args.push("--unicode"); + efibootmgr_args.push(kernel_cmd); + } + exec_command("efibootmgr", &efibootmgr_args)?; + + Ok(()) +} + + + +/// +/// Deletes all existing boot records and create a default +/// +pub fn init_efi_boot_record(maybe_kernel_cmd: &Option) -> Result<(), ExecCommandError> { + // 1 delete all bootnums + delete_all_efi_bootnums()?; + // 2 create defaul + efi_boot_record_create(0, BOOT_MENU_LABEL, BOOT_UKI_IMAGE_NAME, maybe_kernel_cmd)?; + efi_boot_record_create(1, BOOT_MENU_LABEL_BACK, BOOT_UKI_IMAGE_NAME_BACK, maybe_kernel_cmd)?; + Ok(()) +} + +/// +/// List installed kernels by inspecting /usr/lib/modules directory +/// Result is a sorted vector of string +/// +fn list_installed_kernels() -> Result, ExecCommandError> { + use regex::Regex; + + let re = Regex::new(r#"^[0-9]+\.[0-9]+\.[0-9]+-[0-9]-kira-lts"#).unwrap(); + let mut kernel_dirs: Vec = Vec::new(); + + // Oh boy we roll + // finding first dir in /usr/lib/modules that matches common kernel name + for r_d_entry in fs::read_dir("/usr/lib/modules")? { + let d_entry = r_d_entry?; + if d_entry.file_type()?.is_dir() { + let maybe_file_name = d_entry.file_name().to_str().map(|s|s.to_string()); + if let Some(file_name) = maybe_file_name { + if re.is_match(&file_name) { + kernel_dirs.push(file_name); + } + } + } + } + kernel_dirs.sort(); + Ok(kernel_dirs) +} + +pub fn init_efi_boot_partition_content() -> Result<(), ExecCommandError> { + let kernel_dirs = list_installed_kernels()?; + // check if kernel even exist lol + if let Some(kernel_dir) = kernel_dirs.last() { + let kernel_img_path = format!("/usr/lib/modules/{}/vmlinuz", kernel_dir); + let efi_dir = format!("{}/EFI", EFI_BOOT_MOUNT_POINT); + println!("Romoving efi dir {}",efi_dir); + fs::remove_dir_all(&efi_dir)?; + println!("Creating efi dir {}",efi_dir); + fs::create_dir(efi_dir)?; + println!("Copy {} to {}", kernel_img_path, UKI_IMAGE_NAME); + fs::copy(&kernel_img_path, UKI_IMAGE_NAME)?; + println!("Copy {} to {}", kernel_img_path, UKI_IMAGE_BACK_NAME); + fs::copy(&kernel_img_path, UKI_IMAGE_BACK_NAME)?; + Ok(()) } else { - Err(ExecCommandError::Other(format!("No EFI partition (with PARTLABEL={}) was found!", EFI_PARTLABEL))) + Err(ExecCommandError::Other("Cannot find kernel files under /usr/lib/modules dir!".into())) } } /// -/// efibootmgr -/// Assuming boot partition has "bee-efi" PARTLABEL -/// -/// Generaly lets just add new record with date. Label modification not allways works? -/// May add deletion of old record latel -/// sudo efibootmgr --create --disk /dev/nvme0n1 --part 1 --label "Kira OS" --loader '\EFI\nyanlinux\kira-bzz.efi' --unicode 'initrd=\EFI\initrd.img' - -pub fn boot_record_update(maybe_kernel_cmd: Option<&str>) -> Result<(), ExecCommandError> { - - let (efi_partno, efi_parent_dev) = get_efi_part_info()?; - - let local_date = time_format::strftime_local("%Y-%m-%d", time_format::now().unwrap()).unwrap(); - let boot_label = format!("{} {}", BOOT_MENU_LABEL, local_date); +/// Ok. Keep it simple kira. +/// Lets put new kernel as kira-linux-lts.efi to EFI part. +/// Should we save old kernel? Somehow. +/// lets rename kira-linux-lts.efi to kira-linux-lts-back.efi +/// Lets do not do much else. +/// sudo cp /usr/lib/modules/6.18.46-1-kira-lts/vmlinuz /beefi/EFI/kira-linux-lts.efi +/// +pub fn boot_update() -> Result<(), ExecCommandError> { - let mut efibootmgr_args: Vec<&str> = vec!["--create", "--disk", &efi_parent_dev, "--part", &efi_partno, "--label", &boot_label, "--loader", UKI_IMAGE_NAME]; - if let Some(kernel_cmd) = maybe_kernel_cmd { - efibootmgr_args.push("--unicode"); - efibootmgr_args.push(kernel_cmd); - } - exec_command("efibootmgr", &efibootmgr_args)?; - - Ok(()) - + // find vmlinuz path in filesystem + let kernel_dirs = list_installed_kernels()?; + + // check if kernel even exist lol + if let Some(kernel_dir) = kernel_dirs.last() { + let kernel_img_path = format!("/usr/lib/modules/{}/vmlinuz", kernel_dir); + // rename old kernel to + println!("Renaming old kernel {} to {}",UKI_IMAGE_NAME,UKI_IMAGE_BACK_NAME); + fs::rename(UKI_IMAGE_NAME, UKI_IMAGE_BACK_NAME)?; + // copy new kernel image + println!("Copy new kernel from {} to {}",kernel_img_path,UKI_IMAGE_NAME); + fs::copy(&kernel_img_path, UKI_IMAGE_NAME)?; + Ok(()) + } + else { + Err(ExecCommandError::Other("Cannot find kernel files under /usr/lib/modules dir!".into())) + } } diff --git a/src/main.rs b/src/main.rs index cc5ade6..77c5e2d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,10 +15,10 @@ // along with this program. If not, see . use clap::{Args, Parser, Subcommand}; -use signal_hook::{consts::SIGINT, iterator::Signals}; +use signal_hook; use std; use std::sync::Arc; -use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::atomic::{AtomicBool}; mod hive_system; @@ -129,6 +129,12 @@ struct CliArgs { /// Install new kernel into boot partition #[arg(long)] update_boot: bool, + /// Cleans up and recreates UFI boot menu + #[arg(long)] + init_boot_menu: bool, + /// Cleans up and recreates UFI boot menu and /EFI dir content + #[arg(long)] + init_boot: bool, } #[derive(Subcommand, Debug)] @@ -198,7 +204,17 @@ fn main() -> Result<(), HiveUpError> { if cli_args.upgrade { cli_upgrade(bzz_config)?; } else if cli_args.update_boot { - + println!("Updating boot..."); + hive_system::boot_update()?; + } else if cli_args.init_boot_menu { + println!("Init EFI boot menu..."); + hive_system::init_efi_boot_record(&bzz_config.kernel.cmd)?; + } else if cli_args.init_boot { + println!("Init EFI boot menu and EFI partition content..."); + println!("Init EFI Partition content..."); + hive_system::init_efi_boot_record(&bzz_config.kernel.cmd)?; + println!("Init EFI boot menu..."); + hive_system::init_efi_boot_partition_content()?; } } HiveUpMode::Remote(remote_args) => {