diff --git a/Cargo.lock b/Cargo.lock index 934a77c..2299a68 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -125,6 +125,28 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + [[package]] name = "heck" version = "0.5.0" @@ -138,7 +160,21 @@ dependencies = [ "clap", "nix", "regex", + "serde", + "serde_json", + "signal-hook", "time-format", + "toml", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown", ] [[package]] @@ -147,6 +183,12 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + [[package]] name = "libc" version = "0.2.189" @@ -224,6 +266,78 @@ version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + +[[package]] +name = "signal-hook" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a0c28ca5908dbdbcd52e6fdaa00358ab88637f8ab33e1f188dd510eb44b53d" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + [[package]] name = "strsim" version = "0.11.1" @@ -247,6 +361,45 @@ version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "caa08af685f978227b475da86d38312cd1acd5024338bf53a1ab9a799219507d" +[[package]] +name = "toml" +version = "1.1.4+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3aace63f4bbcdfc2c965b059de67119c89c4017a70d633be6c104910f67056f5" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_parser" +version = "1.1.3+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" + [[package]] name = "unicode-ident" version = "1.0.24" @@ -273,3 +426,15 @@ checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ "windows-link", ] + +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/Cargo.toml b/Cargo.toml index 92561e0..fc6ec49 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,4 +7,8 @@ edition = "2024" clap = { version = "4.6", features = ["derive"] } nix = { version = "0", features = ["user"] } regex = "1" -time-format = "1.2.2" +serde = { version = "1", features = ["derive"] } +serde_json = "1.0.151" +signal-hook = "0.4" +time-format = "1.2" +toml = "1.1.4" diff --git a/src/daemon.rs b/src/daemon.rs new file mode 100644 index 0000000..0b6c7d2 --- /dev/null +++ b/src/daemon.rs @@ -0,0 +1,122 @@ +// +// Copyright (C) <2026> + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +/* + hive-up daemon logic +*/ + +use std::fs; +use std::process::Command; +use std::sync::Arc; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::thread; +use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; +use time_format; + +use crate::hive_config::HiveConfig; +use crate::hive_db; +use crate::hive_system; + +#[derive(Debug)] +pub enum DaemonError { + IoError(std::io::Error), + DBError(hive_db::DBError), + ExecCommandError(hive_system::ExecCommandError), + TimestampError, +} + +// 2. Implement From for each wrapped error type +impl From for DaemonError { + fn from(err: std::io::Error) -> Self { + DaemonError::IoError(err) + } +} + +impl From for DaemonError { + fn from(err: hive_db::DBError) -> Self { + DaemonError::DBError(err) + } +} + +impl From for DaemonError { + fn from(err: hive_system::ExecCommandError) -> Self { + DaemonError::ExecCommandError(err) + } +} + +impl std::fmt::Display for DaemonError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + DaemonError::IoError(e) => write!(f, "IO Error: {}", e), + DaemonError::DBError(e) => { + write!(f, "Database error: {}", e) + }, + DaemonError::ExecCommandError(e) => { + write!(f, "Exec Command Error: {}", e) + }, + DaemonError::TimestampError => write!(f, "Timestamp Error"), + } + } +} + +// Implement std::error::Error +impl std::error::Error for DaemonError {} + +fn get_timestamp_sec() -> u64 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time went backwards") + .as_secs() +} + +pub fn run( + config: HiveConfig, + term_flag: Arc, + reload_flag: Arc, +) -> Result<(), DaemonError> { + let duration = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time went backwards"); + + let mut bzz_db = hive_db::read_db()?; + + //duration.as_secs() + let keep_going = true; + + //last_check. + while keep_going { + thread::sleep(Duration::from_secs(1)); + + // if updates check is turned on + if config.update.updates_check_interval != 0 { + let timestamp = get_timestamp_sec(); + let hours_since_update_check = timestamp + .checked_sub(bzz_db.last_update_check) + .ok_or(DaemonError::TimestampError)? + / 3600; + + if hours_since_update_check > config.update.updates_check_interval { + // perform update check + let packages_num = hive_system::check_updates()?; + // updating las tupdate timestamp in db + bzz_db.last_update_check = get_timestamp_sec(); + hive_db::write_db(&bzz_db)?; + } + } + } + + Ok(()) +} diff --git a/src/hive_config.rs b/src/hive_config.rs new file mode 100644 index 0000000..b332d63 --- /dev/null +++ b/src/hive_config.rs @@ -0,0 +1,109 @@ +// +// Copyright (C) <2026> + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +/* + Functions fot system interaction +*/ + + +use serde::{Serialize, Deserialize}; +use std::{fs, io}; +use toml; + +#[derive(Debug, Serialize, Deserialize)] +pub struct HiveConfig { + // Matches [kernel] in TOML + pub kernel: KernelConfig, + // Updates process config + pub update: UpdateConfig, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct KernelConfig { + /// kernel cmd string that will be passed as kernel boot parameter. + cmd: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct UpdateConfig { + /// Interval between updates check in hours. 0 - no auto check for updates. + pub updates_check_interval: u64, + /// Tells hive-up to automatically download updates when awailable. + pub download_updates: bool, + /// Tells hive-up to automatically perform updates during system reboot. + pub auto_update: bool, +} + + +#[derive(Debug)] +pub enum ConfigError { + IoError(std::io::Error), + TomlParseError(toml::de::Error), + TomlSerError(toml::ser::Error), +} + +// 2. Implement From for each wrapped error type +impl From for ConfigError { + fn from(err: std::io::Error) -> Self { + ConfigError::IoError(err) + } +} + +impl From for ConfigError { + fn from(err: toml::de::Error) -> Self { + ConfigError::TomlParseError(err) + } +} + +impl From for ConfigError { + fn from(err: toml::ser::Error) -> Self { + ConfigError::TomlSerError(err) + } +} + +impl std::fmt::Display for ConfigError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + ConfigError::IoError(e) => write!(f, "IO Error: {}", e), + ConfigError::TomlParseError(e) => { + write!(f, "TOML parse error: {}", e) + }, + ConfigError::TomlSerError(e) => { + write!(f, "TOML serialize error: {}", e) + }, + } + } +} + +// Implement std::error::Error +impl std::error::Error for ConfigError {} + +pub fn read_config(conf_file: &str) -> Result { + let file_content = fs::read_to_string(conf_file)?; + let config:HiveConfig = toml::from_str(&file_content)?; + Ok(config) +} + + +pub fn change_updates_check_interval(conf_file: &str, new_interval: u64) -> Result { + let mut config: HiveConfig = read_config(conf_file)?; + config.update.updates_check_interval = new_interval; + let conf_str = toml::to_string_pretty(&config)?; + fs::write(conf_file, conf_str)?; + + Ok(config) + +} \ No newline at end of file diff --git a/src/hive_db.rs b/src/hive_db.rs new file mode 100644 index 0000000..54ab8d6 --- /dev/null +++ b/src/hive_db.rs @@ -0,0 +1,112 @@ +// +// Copyright (C) <2026> + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +/* + Functions for reading and writyng daemon and updates state +*/ + +const DB_PATH: &str = "/var/lib/hive-up/hyve-db.json"; + +use serde::{Deserialize, Serialize}; +use std::fs; + +#[derive(Debug)] +pub enum DBError { + IoError(std::io::Error), + JsonError(serde_json::Error), +} + +// 2. Implement From for each wrapped error type +impl From for DBError { + fn from(err: std::io::Error) -> Self { + DBError::IoError(err) + } +} + +impl From for DBError { + fn from(err: serde_json::Error) -> Self { + DBError::JsonError(err) + } +} + +impl std::fmt::Display for DBError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + DBError::IoError(e) => write!(f, "IO Error: {}", e), + DBError::JsonError(e) => { + write!(f, "Json parse error: {}", e) + } + } + } +} + +// Implement std::error::Error +impl std::error::Error for DBError {} + +#[derive(Serialize, Deserialize, Debug)] +pub struct HiveDb { + pub last_update_check: u64, + pub update_sucess_status: bool, + pub last_update_timestamp: String, +} + +pub fn read_db() -> Result { + let file_content = fs::read_to_string(DB_PATH)?; + let db: HiveDb = serde_json::from_str(&file_content)?; + Ok(db) +} + +pub fn write_db(db: &HiveDb) -> Result<(), DBError> { + let serialized_db = serde_json::to_string(db)?; + fs::write(DB_PATH, serialized_db)?; + Ok(()) +} + +/// +/// writes new last update check time to db +/// and returns old db value on success +/// +pub fn update_last_check_update_time(new_timestamp: u64) -> Result { + let mut db = read_db()?; + let old_value = db.last_update_check; + db.last_update_check = new_timestamp; + write_db(&db)?; + Ok(old_value) +} + +/// +/// writes new update_status to db +/// and returns old db value on success +/// +pub fn update_update_status(sucess_status: bool) -> Result { + let mut db = read_db()?; + let old_value = db.update_sucess_status; + db.update_sucess_status = sucess_status; + write_db(&db)?; + Ok(old_value) +} + +/// +/// writes new last_update_timestamp to db +/// and returns old value on success +/// +pub fn update_last_update_timestamp(new_timestamp: String) -> Result { + let mut db = read_db()?; + let old_value = db.last_update_timestamp; + db.last_update_timestamp = new_timestamp; + write_db(&db)?; + Ok(old_value) +} diff --git a/src/nyan_system.rs b/src/hive_system.rs similarity index 88% rename from src/nyan_system.rs rename to src/hive_system.rs index d95b5d6..e114e00 100644 --- a/src/nyan_system.rs +++ b/src/hive_system.rs @@ -1,4 +1,4 @@ -// +// // Copyright (C) <2026> // This program is free software: you can redistribute it and/or modify @@ -148,14 +148,32 @@ pub fn check_updates() -> Result { } /// -/// bcachefs subvolume snapshot / /snap1 +/// sudo pacman -Suw --noconfirm --noprogressbar +/// Dopwnload updates (need to check for updates first) +/// +pub fn download_updates() -> Result<(), ExecCommandError> { + exec_command("pacman", &vec!["-Suw", "--noconfirm", "--noprogressbar"]) +} + +/// +/// sudo pacman -Su --noconfirm --noprogressbar +/// Perform updates +/// +pub fn do_updates() -> Result<(), ExecCommandError> { + exec_command("pacman", &vec!["-Su", "--noconfirm", "--noprogressbar"]) +} + + +/// +/// bcachefs subvolume snapshot -r / /snap1 +/// -r creates read only snapshot /// returns snapshot name on sucess -pub fn snapshoot_root() -> Result { +pub fn snapshot_root() -> Result { let ts = time_format::now().unwrap(); let local_time = time_format::strftime_local("%Y-%m-%dT%H:%M:%S", ts).unwrap(); let snapshot_name = format!("{}/bzz-{}",SNAPS_DIR,local_time); - exec_command("bcachefs", &vec!["subvolume", "snapshot", "/", &snapshot_name])?; + exec_command("bcachefs", &vec!["subvolume", "snapshot", "-r", "/", &snapshot_name])?; Ok(snapshot_name) } diff --git a/src/main.rs b/src/main.rs index bd6f5b6..c0324bb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,14 +14,23 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +use clap::{Args, Parser}; +use signal_hook::{consts::SIGINT, iterator::Signals}; +use std::{error::Error, thread, time::Duration}; +use std::sync::Arc; +use std::sync::atomic::{AtomicBool, Ordering}; -use clap::{Parser, Args}; +mod hive_system; +use hive_system::ExecCommandError; -mod nyan_system; -use nyan_system::ExecCommandError; +mod hive_config; +use hive_config::{HiveConfig, KernelConfig}; +mod hive_db; -const CONFIG_PATH_STR: &str = "kira_config.toml"; +mod daemon; + +const CONFIG_PATH_STR: &str = "/etc/hive_up.toml"; // Define the mutually exclusive group #[derive(Args, Debug)] @@ -34,10 +43,14 @@ struct HiveUpMode { /// Perform system update #[arg(long)] update: bool, - - /// Try to rollback unsuccessful update if any + + /// Boot update #[arg(long)] - fix: bool, + boot_update: bool, + + /// Run hive-up in daemon mode + #[arg(long)] + daemon: bool, } /// Update program fot Kira Linux :3 @@ -50,28 +63,53 @@ struct Cli { } fn main() { - + // registering signals handling + let term_flag: Arc = Arc::new(AtomicBool::new(false)); + let reload_flag = Arc::new(AtomicBool::new(false)); + signal_hook::flag::register(signal_hook::consts::SIGTERM, Arc::clone(&term_flag)).unwrap(); + signal_hook::flag::register(signal_hook::consts::SIGINT, Arc::clone(&term_flag)).unwrap(); + signal_hook::flag::register(signal_hook::consts::SIGHUP, Arc::clone(&reload_flag)).unwrap(); + let args = Cli::parse(); - if !nyan_system::check_if_run_as_root() { + if !hive_system::check_if_run_as_root() { eprintln!("Error: This program requires root privileges."); std::process::exit(1); } + // read config + let hive_config = match hive_config::read_config(CONFIG_PATH_STR) { + Ok(conf) => conf, + Err(ex) => { + println!("Error reading \"{}\" hive-up config file: {}", CONFIG_PATH_STR, ex); + std::process::exit(2); + } + + }; + // Logic to check which one was set if args.mode.check { - match nyan_system::check_updates() { - Ok(up_num) => println!("Updates awailable: {}", up_num), - Err(ex) => println!("Error: {}", ex) - } + match hive_system::check_updates() { + Ok(up_num) => println!("Updates awailable: {}", up_num), + Err(ex) => println!("Error: {}", ex), + } + } else if args.mode.boot_update { + // update boot + println!("update mode"); } else if args.mode.update { println!("update mode"); - } else if args.mode.fix { - let snaps = nyan_system::list_bzzpss_snaps().unwrap(); + } else if args.mode.daemon { + let snaps = hive_system::list_bzzpss_snaps().unwrap(); println!("Snaps num: {}", snaps.len()); for sn in snaps { println!("Snap: {}", sn); } } - + + while !term_flag.load(Ordering::Relaxed) { + // Do some time-limited stuff here + // (if this could block forever, then there's no guarantee the signal will have any + // effect). + } + }