diff --git a/Cargo.lock b/Cargo.lock index 5201b031..919c06d3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2112,6 +2112,7 @@ dependencies = [ "textwrap 0.16.1", "tokio", "unicode-segmentation", + "uuid", "walkdir", ] diff --git a/docs/global_config.rst b/docs/global_config.rst index 900c54b0..10c34bb5 100644 --- a/docs/global_config.rst +++ b/docs/global_config.rst @@ -169,6 +169,26 @@ and contains the following directives: and writable directories are limited to only a few. In this case *root* must be set according to the system setup. +``id.path`` + + By default, the minion Id is the ``/etc/machine-id``. However, this file is usually + present on a regular Linux server and desktop distributions, but practically never + on the embedded systems. For this reason, the alternative location of the ``machine-id`` + needs to be specified. On many embedded Linux systems and Android, usually ``/etc`` is + read-only, and very few places are allowed to be written. + + This option takes one of the following: + + - An absolute path to an existing ``machine-id`` file + - ``relative`` keyword, so it is ``$MINION_ROOT/machine-id``, which is ``/etc/sysinspect/machine-id`` + by default. + + .. code-block:: yaml + + id.path: |relative + + + ``master.ip`` Corresponds to ``bind.ip`` of Master node and should be identical. diff --git a/libsysinspect/Cargo.toml b/libsysinspect/Cargo.toml index 7b29a002..8ba9bb0c 100644 --- a/libsysinspect/Cargo.toml +++ b/libsysinspect/Cargo.toml @@ -32,4 +32,5 @@ tera = { version = "1.20.0", features = ["preserve_order", "date-locale"] } textwrap = { version = "0.16.1", features = ["hyphenation", "terminal_size"] } tokio = { version = "1.41.1", features = ["full"] } unicode-segmentation = "1.12.0" +uuid = { version = "1.11.0", features = ["v4"] } walkdir = "2.5.0" diff --git a/libsysinspect/src/cfg/mmconf.rs b/libsysinspect/src/cfg/mmconf.rs index e508c218..4960ac35 100644 --- a/libsysinspect/src/cfg/mmconf.rs +++ b/libsysinspect/src/cfg/mmconf.rs @@ -45,6 +45,9 @@ pub struct MinionConfig { /// Port of Master's fileserver. Default: 4201 #[serde(rename = "master.fileserver.port")] master_fileserver_port: Option, + + #[serde(rename = "id.path")] + machine_id: Option, } impl MinionConfig { @@ -89,6 +92,19 @@ impl MinionConfig { pub fn traits_dir(&self) -> PathBuf { self.root_dir().join(CFG_TRAITS_ROOT) } + + /// Return machine Id path + pub fn machine_id_path(&self) -> PathBuf { + if let Some(mid) = self.machine_id.clone() { + if mid.eq("relative") { + return self.root_dir().join("machine-id"); + } else { + return PathBuf::from(mid); + } + } + + PathBuf::from("/etc/machine-id") + } } #[derive(Debug, Serialize, Deserialize, Default, Clone)] diff --git a/libsysinspect/src/traits/systraits.rs b/libsysinspect/src/traits/systraits.rs index 99a23db2..17f1a3bc 100644 --- a/libsysinspect/src/traits/systraits.rs +++ b/libsysinspect/src/traits/systraits.rs @@ -13,7 +13,6 @@ use std::{ collections::HashMap, fs::{self}, os::unix::fs::PermissionsExt, - path::PathBuf, process::Command, }; @@ -125,10 +124,9 @@ impl SystemTraits { self.put(SYS_OS_DISTRO.to_string(), json!(sysinfo::System::distribution_id())); // Machine Id (not always there) - let mip = PathBuf::from("/etc/machine-id"); let mut mid = String::default(); - if mip.exists() { - if let Ok(id) = fs::read_to_string(mip) { + if self.cfg.machine_id_path().exists() { + if let Ok(id) = fs::read_to_string(self.cfg.machine_id_path()) { mid = id.trim().to_string(); } } diff --git a/libsysinspect/src/util/mod.rs b/libsysinspect/src/util/mod.rs index 03fef808..05a70f76 100644 --- a/libsysinspect/src/util/mod.rs +++ b/libsysinspect/src/util/mod.rs @@ -1,2 +1,27 @@ pub mod dataconv; pub mod iofs; + +use crate::SysinspectError; +use std::{fs, io, path::PathBuf}; +use uuid::Uuid; + +/// The `/etc/machine-id` is not always present, especially +/// on the custom embedded systems. However, this file is used +/// to identify a minion. +/// +/// Write the `/etc/machine-id` (or other location), if not any yet. +pub fn write_machine_id(p: Option) -> Result<(), SysinspectError> { + let p = p.unwrap_or(PathBuf::from("/etc/machine-id")); + if !p.exists() { + if let Err(err) = fs::write(p, Uuid::new_v4().to_string().replace("-", "")) { + return Err(SysinspectError::IoErr(err)); + } + } else { + return Err(SysinspectError::IoErr(io::Error::new( + io::ErrorKind::AlreadyExists, + format!("File \"{}\" already exists", p.to_str().unwrap_or_default()), + ))); + } + + Ok(()) +} diff --git a/sysinspect.conf b/sysinspect.conf index 1b056541..af17453b 100644 --- a/sysinspect.conf +++ b/sysinspect.conf @@ -27,5 +27,6 @@ config: # Root directory where minion keeps all data. # Default: /etc/sysinspect — same as for master root: /etc/sysinspect + id.path: relative master.ip: 192.168.2.31 master.port: 4200 diff --git a/sysminion/src/minion.rs b/sysminion/src/minion.rs index cc929e61..76852cda 100644 --- a/sysminion/src/minion.rs +++ b/sysminion/src/minion.rs @@ -11,7 +11,7 @@ use libsysinspect::{ }, rsa, traits::{self}, - util::dataconv, + util::{self, dataconv}, SysinspectError, }; use once_cell::sync::Lazy; @@ -63,8 +63,6 @@ impl SysMinion { } let cfg = MinionConfig::new(cfp)?; - traits::get_minion_traits(Some(&cfg)); - let (rstm, wstm) = TcpStream::connect(cfg.master()).await.unwrap().into_split(); let instance = SysMinion { cfg: cfg.clone(), @@ -83,6 +81,11 @@ impl SysMinion { /// This creates all directory structures if none etc. fn init(&self) -> Result<(), SysinspectError> { log::info!("Initialising minion"); + // Machine id? + if !self.cfg.machine_id_path().exists() { + util::write_machine_id(Some(self.cfg.machine_id_path()))?; + } + // Place for models if !self.cfg.models_dir().exists() { log::debug!( @@ -111,7 +114,7 @@ impl SysMinion { } let mut out: Vec = vec![]; - for t in traits::get_minion_traits(None).items() { + for t in traits::get_minion_traits(Some(&self.cfg)).items() { out.push(format!( "{}: {}", t.to_owned(),