2 Commits
11 changed files with 201 additions and 155 deletions
Generated
+9 -41
View File
@@ -2,15 +2,6 @@
# 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"
@@ -220,12 +211,12 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hive-up"
version = "0.1.0"
version = "0.1.2"
dependencies = [
"clap",
"nix",
"postcard",
"regex",
"regex-lite",
"serde",
"serde_json",
"signal-hook",
@@ -235,9 +226,9 @@ dependencies = [
[[package]]
name = "indexmap"
version = "2.14.1"
version = "2.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07aa2048142242915a31d35844fb311e0e53fcca590c3a0a40dcf1b841fa09eb"
checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855"
dependencies = [
"equivalent",
"hashbrown",
@@ -326,33 +317,10 @@ dependencies = [
]
[[package]]
name = "regex"
version = "1.13.1"
name = "regex-lite"
version = "0.1.9"
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"
checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973"
[[package]]
name = "rustc_version"
@@ -470,9 +438,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "3.0.4"
version = "3.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f"
checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9"
dependencies = [
"proc-macro2",
"quote",
+3 -3
View File
@@ -1,13 +1,13 @@
[package]
name = "hive-up"
version = "0.1.0"
version = "0.1.2"
edition = "2024"
[dependencies]
clap = { version = "4.6", features = ["derive"] }
nix = { version = "0", features = ["user"] }
postcard = { version = "1.1", features = ["use-std"] }
regex = "1"
regex-lite = "0.1"
serde = { version = "1", features = ["derive"] }
serde_json = "1.0"
signal-hook = "0.4"
@@ -19,7 +19,7 @@ lto = true
debug = false
incremental = false
codegen-units = 1
opt-level = 2
opt-level = "s"
strip = true
[registry]
+57
View File
@@ -1,3 +1,60 @@
# hive-up
Program to manage update process of Kira Linux. Creating snapshots. Dealing with pacman fail, etc.
There should be some documantation here.
hive-up uses `/var/lib/hive-up/hyve-db.json` file as database for stuff
And `/etc/hive-up.toml` as config file.
As an daemon hive-up righ now doing only periodical checks for updates.
```text
Usage: hive-up <COMMAND>
Commands:
cli Perform local operations
remote Ask hive-up daemon something
daemon Run hive-up in daemon mode
help Print this message or the help of the given subcommand(s)
Options:
-h, --help Print help
-V, --version Print version
```
For cli mode:
```text
Usage: hive-up cli <--upgrade|--update-boot|--init-boot-menu|--init-boot>
Options:
--upgrade Perform system upgrade
--update-boot Install new kernel into boot partition
--init-boot-menu Cleans up and recreates UFI boot menu
--init-boot Cleans up and recreates UFI boot menu and /EFI dir content
-h, --help Print help
```
For remote mode:
```text
Usage: hive-up remote <--check-updates|--updates-avail|--schedule-update>
Options:
--check-updates Ask daemon to check for updates
--updates-avail Ask daemon if there is any updates available
--schedule-update Ask daemon to perform system update on next reboot
-h, --help Print help
```
`hive-up daemon` - do not have any flags for now.
`hive-up cli --upgrade` - generally not supposed to be called by user. It get called by system service manager (dinit) after hive-up service stop (on reboot or system shutdown)
When `hive-up cli --upgrade` is called, it checks if `update_on_reboot` flag is set in **DB** or `auto_update` flag checked in **config**, and if `updates_available` in **DB** is non zero. Update routine continue only if this conditios are met.
1. It creates snapshots of `/usr` and `/var` subvolumes
2. It sets `update_status` to **false** in **DB**
3. Performs update, and if everything was fine sets `update_status` to **true** in **DB**
After this, system reboots, and on system boot Nyanit figures up what to do with failed updates if any
+12
View File
@@ -0,0 +1,12 @@
# Section (Table) headers
[update]
# Interval between updates check in hours. 0 - no auto check for updates.
check_interval = 58
# Tells hive-up to automatically download updates when awailable.
download_updates = true
# Tells hive-up to automatically perform updates during system reboot
auto_update = false
# In [kernel] section user may specify kernel cmd
# [kernel]
# cmd = ""
+2 -2
View File
@@ -176,7 +176,7 @@ fn process_remoute(
// doing what daemons usually beezz
fn do_local_work(config: &HiveConfig, bzz_db: &mut HiveDb) -> Result<(), DaemonError> {
// if updates check is turned on
if config.update.updates_check_interval != 0 {
if config.update.check_interval != 0 {
let timestamp = get_timestamp_sec();
// calculate how many hours passed sinse last update
@@ -186,7 +186,7 @@ fn do_local_work(config: &HiveConfig, bzz_db: &mut HiveDb) -> Result<(), DaemonE
/ 3600;
// if more than updates_check_interval specifiyed in config - check for updates
if hours_since_update_check > config.update.updates_check_interval {
if hours_since_update_check > config.update.check_interval {
// perform update check
println!("Cheking for updates...");
// now that may fail because of bad internet and idealy we
+4 -4
View File
@@ -22,13 +22,13 @@ use serde::{Deserialize, Serialize};
use std::fs;
use toml;
const CONFIG_PATH: &str = "/etc/hive_up.toml";
const CONFIG_PATH: &str = "/etc/hive-up.toml";
#[derive(Debug, Serialize, Deserialize)]
pub struct HiveConfig {
// Matches [kernel] in TOML
pub kernel: KernelConfig,
// Updates process config
pub kernel: Option<KernelConfig>,
// Updates [update] in TOML
pub update: UpdateConfig,
}
@@ -41,7 +41,7 @@ pub struct KernelConfig {
#[derive(Debug, Serialize, Deserialize)]
pub struct UpdateConfig {
/// Interval between updates check in hours. 0 - no auto check for updates.
pub updates_check_interval: u64,
pub 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.
+21 -35
View File
@@ -62,7 +62,8 @@ pub struct HiveDb {
pub update_check_timestamp: u64,
/// non zero if there is updates that can bee applied, showing numbers of packages that can be updated
pub updates_available: u32,
/// true if last update fas successful
/// true if last update was successful
/// if it is false - nyanit need to roll back snapshots
pub update_status: bool,
/// datetime of last snapshot in format "%Y-%m-%dT%H:%M:%S"
pub last_snapshot_time: String,
@@ -70,11 +71,30 @@ pub struct HiveDb {
pub update_on_reboot: bool,
}
fn init_db() -> Result<HiveDb, DBError> {
let bzz_db = HiveDb{
update_check_timestamp: 0,
updates_available: 0,
update_status: false,
last_snapshot_time: "".into(),
update_on_reboot: false,
};
write_db(&bzz_db)?;
Ok(bzz_db)
}
pub fn read_db() -> Result<HiveDb, DBError> {
if fs::exists(DB_PATH)? {
let file_content = fs::read_to_string(DB_PATH)?;
let db: HiveDb = serde_json::from_str(&file_content)?;
Ok(db)
}
else {
println!("WARNING database file do not exist {}", DB_PATH);
print!("Creating default db file...");
init_db()
}
}
pub fn write_db(db: &HiveDb) -> Result<(), DBError> {
let serialized_db = serde_json::to_string(db)?;
@@ -82,38 +102,4 @@ pub fn write_db(db: &HiveDb) -> Result<(), DBError> {
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<u64, DBError> {
let mut db = read_db()?;
let old_value = db.update_check_timestamp;
db.update_check_timestamp = 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<bool, DBError> {
let mut db = read_db()?;
let old_value = db.update_status;
db.update_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<String, DBError> {
let mut db = read_db()?;
let old_value = db.last_snapshot_time;
db.last_snapshot_time = new_timestamp;
write_db(&db)?;
Ok(old_value)
}
+81 -63
View File
@@ -184,12 +184,31 @@ pub fn pkg_upgrade() -> Result<(), ExecCommandError> {
exec_command("pacman", &vec!["-Su", "--noconfirm", "--noprogressbar"])
}
///
/// bcachefs subvolume snapshot -r / /snap1
/// creates read only snapshoy of "subvolume" and places it into "target"]
/// bcachefs subvolume snapshot -r /subvolume /target
/// -r creates read only snapshot
/// returns snapshot time on sucess
///
pub fn snapshot_root() -> Result<String, ExecCommandError> {
pub fn snapshot(subvolume: &str, target: &str) -> Result<(), ExecCommandError> {
exec_command(
"bcachefs",
&vec!["subvolume", "snapshot", "-r", subvolume, target],
)?;
Ok(())
}
///
///
/// bcachefs subvolume snapshot -r /target /SNAPS_DIR/bzz-target-yyyy-mm-ddTHH-MM-SS
/// Creates read only snapshots of /usr and /var subvolumes
/// And places them to /SNAPS_DIR/bzz-usr-yyyy-mm-ddTHH-MM-SS and /SNAPS_DIR/bzz-var-yyyy-mm-ddTHH-MM-SS
/// returns snapshot time on sucess
///
pub fn snapshot_system() -> Result<String, ExecCommandError> {
use std::path::Path;
// if snaps dir path do not exist - create it
let snaps_path = Path::new(SNAPS_DIR);
@@ -199,43 +218,42 @@ pub fn snapshot_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);
let usr_snapshot_name = format!("{}/bzz-usr-{}", SNAPS_DIR, local_time);
let var_snapshot_name = format!("{}/bzz-var-{}", SNAPS_DIR, local_time);
exec_command(
"bcachefs",
&vec!["subvolume", "snapshot", "-r", "/", &snapshot_name],
)?;
snapshot("/usr", &usr_snapshot_name)?;
snapshot("/var", &var_snapshot_name)?;
Ok(local_time)
}
//const TMP_ROOT: &str = "/mnt/bee-root";
pub fn list_bzzpss_snaps() -> Result<Vec<String>, ExecCommandError> {
use regex::Regex;
use std::path::Path;
// 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();
// // root snapshoot in a form of bzz-yyyy-mm-ddTHH-MM-SS
// let re = Regex::new(r"^bzz-[a-z]+-[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);
}
// 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)
}
// 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;
@@ -271,7 +289,7 @@ impl std::error::Error for SysUpgradeError {}
///
pub fn sys_upgrade() -> Result<String, SysUpgradeError> {
// snapshooting root
let snap_time = snapshot_root().map_err(SysUpgradeError::SnapshotError)?;
let snap_time = snapshot_system().map_err(SysUpgradeError::SnapshotError)?;
// try to perform packages upgrade
match pkg_upgrade() {
Ok(_) => Ok(snap_time),
@@ -281,37 +299,37 @@ pub fn sys_upgrade() -> Result<String, SysUpgradeError> {
/// 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)?;
// 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))
})?;
// 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())
)))
}
}
}
// 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
@@ -327,7 +345,7 @@ pub fn find_partlabel(partlabel: &str) -> Result<Option<String>, ExecCommandErro
///
///
fn get_efi_part_info() -> Result<(String, String), ExecCommandError> {
use regex::Regex;
use regex_lite::Regex;
let re = Regex::new(r#"^PARTN="([0-9]+)" PKNAME="([0-9a-Z]+)"$"#).unwrap();
let lsblk_query = format!("PARTLABEL==\"{}\"", EFI_PARTLABEL);
@@ -351,7 +369,7 @@ fn get_efi_part_info() -> Result<(String, String), ExecCommandError> {
/// Boot0000* Nyan
///
fn list_efi_bootnums() -> Result<Vec<String>, ExecCommandError> {
use regex::Regex;
use regex_lite::Regex;
let re = Regex::new(r#"^Boot([0-9A-F]{4})\* "#).unwrap();
let str_res = exec_command_out("efibootmgr", &vec![])?;
@@ -444,7 +462,7 @@ pub fn init_efi_boot_record(maybe_kernel_cmd: &Option<String>) -> Result<(), Exe
/// Result is a sorted vector of string
///
fn list_installed_kernels() -> Result<Vec<String>, ExecCommandError> {
use regex::Regex;
use regex_lite::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();
+8 -3
View File
@@ -163,8 +163,11 @@ struct Cli {
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 {
if (bzz_db.updates_available !=0) && (bzz_db.update_on_reboot || bzz_config.update.auto_update) {
println!("Starting full system upgrade");
bzz_db.update_status = false;
hive_db::write_db(&bzz_db)?;
match hive_system::sys_upgrade() {
Ok(snap_time) => {
bzz_db.update_status = true;
@@ -212,11 +215,13 @@ fn main() -> Result<(), HiveUpError> {
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)?;
let kernel_cmd = bzz_config.kernel.and_then(|kk| kk.cmd.clone());
hive_system::init_efi_boot_record(&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)?;
let kernel_cmd = bzz_config.kernel.and_then(|kk| kk.cmd.clone());
hive_system::init_efi_boot_record(&kernel_cmd)?;
println!("Init EFI boot menu...");
hive_system::init_efi_boot_partition_content()?;
}