Initial commit.

This commit is contained in:
2026-08-09 19:25:55 +02:00
parent 5559c3a27d
commit 2b7a8ea8ec
5 changed files with 601 additions and 0 deletions
+77
View File
@@ -0,0 +1,77 @@
// <kira-update - update program fot Kira Linux.>
// Copyright (C) <2026> <Kira Foundation>
// 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 <https://www.gnu.org/licenses/>.
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);
}
}
}
+199
View File
@@ -0,0 +1,199 @@
// <kira-update - update program fot Kira Linux.>
// Copyright (C) <2026> <Kira Foundation>
// 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 <https://www.gnu.org/licenses/>.
/*
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<u8>,
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<u8>) -> 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<std::io::Error> for ExecCommandError {
fn from(err: std::io::Error) -> Self {
ExecCommandError::IoError(err)
}
}
impl From<std::string::FromUtf8Error> for ExecCommandError {
fn from(err: std::string::FromUtf8Error) -> Self {
ExecCommandError::FromUtf8Error(err)
}
}
impl From<CommandFailed> for ExecCommandError {
fn from(err: CommandFailed) -> Self {
ExecCommandError::CommandFailed(err)
}
}
impl From<String> 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<String, ExecCommandError> {
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<usize, ExecCommandError> {
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<String, ExecCommandError> {
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<Vec<String>, 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<String> = 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
}
}