From 2b7a8ea8ec937b49c27e0a2ae75193b43d4315c13e34faf7e4d758b2dbb66753 Mon Sep 17 00:00:00 2001 From: kira Date: Sun, 9 Aug 2026 19:25:55 +0200 Subject: [PATCH] Initial commit. --- .gitignore | 40 +++++++ Cargo.lock | 275 +++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 10 ++ src/main.rs | 77 +++++++++++++ src/nyan_system.rs | 199 ++++++++++++++++++++++++++++++++ 5 files changed, 601 insertions(+) create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/main.rs create mode 100644 src/nyan_system.rs diff --git a/.gitignore b/.gitignore index 79711dd..39f7357 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,43 @@ target/ # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ + + +# Added by cargo + +/target + + +# Generated by cargo mutants +# Contains mutation testing data +**/mutants.out*/ + +# RustRover +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +# Some stuff +*.tar +*.tar.* +*.jar +*.exe +*.msi +*.zip +*.tgz +*.log +*.log.* +*.sig + + +# ---> Archives +*.7z +*.rar +*.gz +*.bzip +*.bz2 +*.xz +*.lzma +*.cab diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..934a77c --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,275 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys", +] + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "clap" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hive-up" +version = "0.1.0" +dependencies = [ + "clap", + "nix", + "regex", + "time-format", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "nix" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "time-format" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa08af685f978227b475da86d38312cd1acd5024338bf53a1ab9a799219507d" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..92561e0 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "hive-up" +version = "0.1.0" +edition = "2024" + +[dependencies] +clap = { version = "4.6", features = ["derive"] } +nix = { version = "0", features = ["user"] } +regex = "1" +time-format = "1.2.2" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..bd6f5b6 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,77 @@ +// +// 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 . + + +use clap::{Parser, Args}; + +mod nyan_system; +use nyan_system::ExecCommandError; + + +const CONFIG_PATH_STR: &str = "kira_config.toml"; + +// 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 + #[arg(long)] + check: bool, + + /// Perform system update + #[arg(long)] + update: bool, + + /// Try to rollback unsuccessful update if any + #[arg(long)] + fix: bool, +} + +/// Update program fot Kira Linux :3 +#[derive(Parser, Debug)] +#[command(version, about, long_about = None)] +struct Cli { + /// hive-up operation mode + #[command(flatten)] + mode: HiveUpMode, +} + +fn main() { + + let args = Cli::parse(); + + if !nyan_system::check_if_run_as_root() { + eprintln!("Error: This program requires root privileges."); + std::process::exit(1); + } + + // 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) + } + } else if args.mode.update { + println!("update mode"); + } else if args.mode.fix { + let snaps = nyan_system::list_bzzpss_snaps().unwrap(); + println!("Snaps num: {}", snaps.len()); + for sn in snaps { + println!("Snap: {}", sn); + } + } + +} diff --git a/src/nyan_system.rs b/src/nyan_system.rs new file mode 100644 index 0000000..d95b5d6 --- /dev/null +++ b/src/nyan_system.rs @@ -0,0 +1,199 @@ +// +// 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 std::fs; +use std::process::Command; +use time_format; + + +const SNAPS_DIR: &str = "/bzz_snaps"; + +#[derive(Debug)] +pub struct CommandFailed { + pub stderr: Vec, + pub cmd: String, +} + +impl std::fmt::Display for CommandFailed { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + f, + "Command failed! Command: {}; Stderr: {}", + self.cmd, + String::from_utf8(self.stderr.clone()) + .unwrap_or_else(|_| { "STDEER IS NOT VALID UTF-8".to_string() }) + ) + } +} + +impl CommandFailed { + pub fn new(cmd: &str, args: &Vec<&str>, stderr: Vec) -> Self { + Self { + stderr: stderr, + cmd: format!("{} {}", cmd, args.join(" ")), + } + } +} + +#[derive(Debug)] +pub enum ExecCommandError { + IoError(std::io::Error), + FromUtf8Error(std::string::FromUtf8Error), + CommandFailed(CommandFailed), + Other(String), +} + +// 2. Implement From for each wrapped error type +impl From for ExecCommandError { + fn from(err: std::io::Error) -> Self { + ExecCommandError::IoError(err) + } +} + +impl From for ExecCommandError { + fn from(err: std::string::FromUtf8Error) -> Self { + ExecCommandError::FromUtf8Error(err) + } +} + +impl From for ExecCommandError { + fn from(err: CommandFailed) -> Self { + ExecCommandError::CommandFailed(err) + } +} + +impl From for ExecCommandError { + fn from(err: String) -> Self { + ExecCommandError::Other(err) + } +} + +impl From<&str> for ExecCommandError { + fn from(err: &str) -> Self { + ExecCommandError::Other(err.to_string()) + } +} + +impl std::fmt::Display for ExecCommandError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + ExecCommandError::IoError(e) => write!(f, "IO Error: {}", e), + ExecCommandError::FromUtf8Error(e) => { + write!(f, "IO streem to String parse Error: {}", e) + } + ExecCommandError::CommandFailed(ex) => { + write!(f, "{}", ex) + } + ExecCommandError::Other(msg) => write!(f, "Error: {}", msg), + } + } +} + +// Implement std::error::Error +impl std::error::Error for ExecCommandError {} + +/// exec given command with arguments, capturing stderr in case or failure +pub fn exec_command(cmd: &str, args: &Vec<&str>) -> Result<(), ExecCommandError> { + let out = Command::new(cmd).args(args).output()?; + if out.status.success() { + Ok(()) + } else { + Err(CommandFailed::new(cmd, args, out.stderr).into()) + } +} + +/// exec given command with arguments, returning stdout parsed to UTF-8 string +/// capturing stderr in case or failure +pub fn exec_command_out(cmd: &str, args: &Vec<&str>) -> Result { + let out = Command::new(cmd).args(args).output()?; + if out.status.success() { + Ok(String::from_utf8(out.stdout)?) + } else { + Err(CommandFailed::new(cmd, args, out.stderr).into()) + } +} + +/// +/// pacman -Sy +/// pacman -Qu +/// returns number of updatable packages, returns 0 if there is no updates awailable +pub fn check_updates() -> Result { + exec_command("pacman", &vec!["-Sy"])?; + let out = Command::new("pacman").arg("-Qu").output()?; + if out.status.success() { + Ok(String::from_utf8(out.stdout)?.lines().count()) + } else { + if let Some(code) = out.status.code() && code == 1 { Ok(0) } + else { + Err(ExecCommandError::Other(format!("pacman -Qu failed: {}", String::from_utf8(out.stderr)?))) + } + } +} + +/// +/// bcachefs subvolume snapshot / /snap1 +/// returns snapshot name on sucess +pub fn snapshoot_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])?; + + Ok(snapshot_name) +} + +//const TMP_ROOT: &str = "/mnt/bee-root"; +pub fn list_bzzpss_snaps() -> Result, ExecCommandError> { + use regex::Regex; + use std::path::Path; + + // root snapshoot in a form of bzz-yyyy-mm-ddTHH-MM-SS + let re = Regex::new(r"^bzz-[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}-[0-9]{2}-[0-9]{2}$").unwrap(); + + let mut res: Vec = Vec::new(); + // if snaps dir path do not exist return empty list + if !Path::new(SNAPS_DIR).exists() { + return Ok(res); + } + + for maybe_entry in fs::read_dir(SNAPS_DIR)? { + let entry = maybe_entry?; + if entry.file_type()?.is_dir() { + if let Some(dir_name) = entry.file_name().to_str() + && re.is_match(dir_name) + { + res.push(dir_name.to_string()); + } + } + } + res.sort(); + Ok(res) +} + + +pub fn check_if_run_as_root() -> bool { + use nix::unistd::Uid; + if Uid::effective().is_root() { + true + } else { + false + } +} \ No newline at end of file