6 Commits
8 changed files with 403 additions and 59 deletions
Generated
+8 -8
View File
@@ -235,9 +235,9 @@ dependencies = [
[[package]]
name = "indexmap"
version = "2.14.0"
version = "2.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
checksum = "07aa2048142242915a31d35844fb311e0e53fcca590c3a0a40dcf1b841fa09eb"
dependencies = [
"equivalent",
"hashbrown",
@@ -470,9 +470,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "3.0.3"
version = "3.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f"
dependencies = [
"proc-macro2",
"quote",
@@ -501,15 +501,15 @@ dependencies = [
[[package]]
name = "time-format"
version = "1.2.2"
version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "caa08af685f978227b475da86d38312cd1acd5024338bf53a1ab9a799219507d"
checksum = "fb54c799611991d3d4a66fee3ff17b9f600c2a0f8c0d34e8209dbd153ff0a87b"
[[package]]
name = "toml"
version = "1.1.4+spec-1.1.0"
version = "1.1.5+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3aace63f4bbcdfc2c965b059de67119c89c4017a70d633be6c104910f67056f5"
checksum = "12c0ba9680044b4ce98d391a62094047eada0d64860b80166c39f4a6b5640785"
dependencies = [
"indexmap",
"serde_core",
+11
View File
@@ -13,3 +13,14 @@ serde_json = "1.0"
signal-hook = "0.4"
time-format = "1.2"
toml = "1.1"
[profile.release]
lto = true
debug = false
incremental = false
codegen-units = 1
opt-level = 2
strip = true
[registry]
global-min-publish-age = "14 days"
+12
View File
@@ -0,0 +1,12 @@
[Trigger]
Operation = Upgrade
Operation = Install
Type = Package
Target = linux-kira-lts
Target = linux-kira-virt-lts
Target = linux-kira-server-lts
[Action]
Description = Running custom command after kernel update
When = PostTransaction
Exec = /usr/bin/hive-up cli --update-boot
+6
View File
@@ -0,0 +1,6 @@
type = process
command = /usr/bin/hive-up daemon
stop-command = /usr/bin/hive-up cli --upgrade
depends-on = network.target
logfile = /var/log/dinit/hive-up.log
log-type = file
+8 -8
View File
@@ -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<String>,
}
#[derive(Debug, Serialize, Deserialize)]
@@ -97,11 +97,11 @@ pub fn read_config() -> Result<HiveConfig, ConfigError> {
Ok(config)
}
pub fn change_updates_check_interval(new_interval: u64) -> Result<HiveConfig, ConfigError> {
let mut config: HiveConfig = read_config()?;
config.update.updates_check_interval = new_interval;
let conf_str = toml::to_string_pretty(&config)?;
fs::write(CONFIG_PATH, conf_str)?;
// pub fn change_updates_check_interval(new_interval: u64) -> Result<HiveConfig, ConfigError> {
// let mut config: HiveConfig = read_config()?;
// config.update.updates_check_interval = new_interval;
// let conf_str = toml::to_string_pretty(&config)?;
// fs::write(CONFIG_PATH, conf_str)?;
Ok(config)
}
// Ok(config)
// }
+9
View File
@@ -194,3 +194,12 @@ pub fn client_check_for_updates() -> Result<u32, IntercomError> {
_ => Err(IntercomError::UnexpectedResponse),
}
}
pub fn client_schedule_update() -> Result<(), IntercomError> {
let resp = send_and_recv(BzzReq::ScheduleUpdate)?;
match resp {
BzzResp::Finish => Ok(()),
BzzResp::Error(ex) => Err(IntercomError::ServerError(ex)),
_ => Err(IntercomError::UnexpectedResponse),
}
}
+255 -1
View File
@@ -20,9 +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/linux-kira-lts.efi";
const UKI_IMAGE_BACK_NAME: &str = "/beefi/EFI/linux-kira-lts-back.efi";
const EFI_PARTLABEL: &str = "bee-efi";
const BOOT_MENU_LABEL: &str = "Kira OS";
const BOOT_MENU_LABEL_BACK: &str = "Kira OS Fallback";
const BOOT_UKI_IMAGE_NAME: &str = "\\EFI\\linux-kira-lts.efi";
const BOOT_UKI_IMAGE_NAME_BACK: &str = "\\EFI\\linux-kira-lts-back.efi";
#[derive(Debug)]
pub struct CommandFailed {
@@ -268,3 +278,247 @@ pub fn sys_upgrade() -> Result<String, SysUpgradeError> {
Err(ex) => Err(SysUpgradeError::PkgUpgradeErr((snap_time, ex))),
}
}
/// Gets device node aka /dev/sda1 from gpr partition label
/// blkid -s PARTLABEL --match-token PARTLABEL="bzz-primary"
pub fn find_partlabel(partlabel: &str) -> Result<Option<String>, ExecCommandError> {
let out = Command::new("/usr/bin/blkid")
.args([
"-s",
"PARTLABEL",
"-t",
format!("PARTLABEL=\"{}\"", partlabel).as_str(),
])
.output()?;
if out.status.success() {
let out_text = String::from_utf8(out.stdout)?;
let split_res = out_text.split_once(':').ok_or_else(|| {
ExecCommandError::Other(format!("Unexpected blkid output format: \"{}\"", out_text))
})?;
Ok(Some(split_res.0.to_string()))
} else {
if let Some(code) = out.status.code()
&& code == 2
{
Ok(None)
} else {
Err(ExecCommandError::Other(format!(
"Error while looking for partlabel={}: \"{}\"",
partlabel,
String::from_utf8(out.stderr).unwrap_or_else(|ex| ex.to_string())
)))
}
}
}
///
/// We need to use lsblk to find out parent device, or read topology from sysfs
/// (basename $(readlink -f /sys/class/block/sda1/..))
/// But lsblk approach looks more reasonable
/// NAME="/dev/nvme0n1p1" PARTLABEL="nyazefi" PARTN="1" PKNAME="/dev/nvme0n1"
/// 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
///
/// 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();
let lsblk_query = format!("PARTLABEL==\"{}\"", EFI_PARTLABEL);
let lsblk_args: Vec<&str> = vec!["-P", "-p", "-Q", &lsblk_query, "-o", "PARTN,PKNAME"];
let lsblk_res = exec_command_out("lsblk", &lsblk_args)?;
if let Some(captured) = re.captures(&lsblk_res) {
// 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<Vec<String>, 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<usize, ExecCommandError> {
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<String>,
) -> 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<String>) -> 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<Vec<String>, 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<String> = 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(
"Cannot find kernel files under /usr/lib/modules dir!".into(),
))
}
}
///
/// 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> {
// 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(),
))
}
}
+94 -42
View File
@@ -14,16 +14,17 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use clap::{Args, Parser};
use signal_hook::{consts::SIGINT, iterator::Signals};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use clap::{Args, Parser, Subcommand};
use signal_hook;
use std;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
mod hive_system;
mod hive_config;
use crate::hive_config::HiveConfig;
use crate::hive_system::SysUpgradeError;
mod daemon;
@@ -108,34 +109,82 @@ impl std::error::Error for HiveUpError {}
// Define the mutually exclusive group
#[derive(Args, Debug)]
#[group(required = true, multiple = false)] // 'multiple = false' enforces exclusivity
struct HiveUpMode {
/// Check for updates, prints number of updates available
#[group(required = true, multiple = false)]
struct RemoteArgs {
/// Ask daemon to check for updates
#[arg(long)]
check_updates: bool,
/// Check for available updates, prints number of updates available
/// Ask daemon if there is any updates available
#[arg(long)]
updates_available: bool,
updates_avail: bool,
/// Ask daemon to perform system update on next reboot
#[arg(long)]
schedule_update: bool,
}
#[derive(Args, Debug)]
#[group(required = true, multiple = false)]
struct CliArgs {
/// Perform system upgrade
#[arg(long)]
upgrade: bool,
/// Run hive-up in daemon mode
/// Install new kernel into boot partition
#[arg(long)]
daemon: bool,
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)]
#[group(required = true)] // 'multiple = false' enforces exclusivity
enum HiveUpMode {
/// Perform local operations
Cli(CliArgs),
/// Ask hive-up daemon something
Remote(RemoteArgs),
/// Run hive-up in daemon mode
Daemon,
}
/// Update program fot Kira Linux :3
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
#[command(author, version, about)]
struct Cli {
/// hive-up operation mode
#[command(flatten)]
#[command(subcommand)]
mode: HiveUpMode,
}
fn cli_upgrade(bzz_config: HiveConfig) -> Result<(), HiveUpError> {
let mut bzz_db = hive_db::read_db()?;
if bzz_db.update_on_reboot || bzz_config.update.auto_update {
println!("Starting full system upgrade");
match hive_system::sys_upgrade() {
Ok(snap_time) => {
bzz_db.update_status = true;
bzz_db.update_on_reboot = false;
bzz_db.last_snapshot_time = snap_time;
hive_db::write_db(&bzz_db)?;
}
Err(SysUpgradeError::PkgUpgradeErr((snap_time, ex))) => {
bzz_db.update_status = false;
bzz_db.update_on_reboot = false;
bzz_db.last_snapshot_time = snap_time;
hive_db::write_db(&bzz_db)?;
return Err(ex.into());
}
Err(ex) => return Err(ex.to_string().into()),
}
}
Ok(())
}
fn main() -> Result<(), HiveUpError> {
// registering signals handling
let term_flag: Arc<AtomicBool> = Arc::new(AtomicBool::new(false));
@@ -153,36 +202,39 @@ fn main() -> Result<(), HiveUpError> {
// read config
let bzz_config = hive_config::read_config()?;
// Check CMD flags
if args.mode.check_updates {
let up_num = hive_intercom::client_check_for_updates()?;
println!("Updates awailable: {}", up_num);
} else if args.mode.updates_available {
let up_num = hive_intercom::client_updates_available()?;
println!("Updates awailable: {}", up_num);
} else if args.mode.upgrade {
let mut bzz_db = hive_db::read_db()?;
if bzz_db.update_on_reboot {
println!("Starting full system upgrade");
match hive_system::sys_upgrade() {
Ok(snap_time) => {
bzz_db.update_status = true;
bzz_db.update_on_reboot = false;
bzz_db.last_snapshot_time = snap_time;
hive_db::write_db(&bzz_db)?;
}
Err(SysUpgradeError::PkgUpgradeErr((snap_time, ex))) => {
bzz_db.update_status = false;
bzz_db.update_on_reboot = false;
bzz_db.last_snapshot_time = snap_time;
hive_db::write_db(&bzz_db)?;
return Err(ex.into());
}
Err(ex) => return Err(ex.to_string().into()),
match args.mode {
HiveUpMode::Daemon => daemon::run(bzz_config, term_flag.clone(), reload_flag.clone())?,
HiveUpMode::Cli(cli_args) => {
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) => {
if remote_args.check_updates {
let up_num = hive_intercom::client_check_for_updates()?;
println!("Updates awailable: {}", up_num);
} else if remote_args.updates_avail {
let up_num = hive_intercom::client_updates_available()?;
println!("Updates awailable: {}", up_num);
} else if remote_args.schedule_update {
println!("Scheduling system update...");
hive_intercom::client_schedule_update()?;
print!("Done!")
}
}
} else if args.mode.daemon {
daemon::run(bzz_config, term_flag.clone(), reload_flag.clone())?;
}
Ok(())
}