SHA256
Initial commit.
This commit is contained in:
+77
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user