From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id CF2F0BC6D0 for ; Thu, 28 Mar 2024 14:56:48 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id B1771E1CC for ; Thu, 28 Mar 2024 14:56:48 +0100 (CET) Received: from proxmox-new.maurer-it.com (proxmox-new.maurer-it.com [94.136.29.106]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by firstgate.proxmox.com (Proxmox) with ESMTPS for ; Thu, 28 Mar 2024 14:56:47 +0100 (CET) Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id C65A042905 for ; Thu, 28 Mar 2024 14:50:35 +0100 (CET) From: Aaron Lauterer To: pve-devel@lists.proxmox.com Date: Thu, 28 Mar 2024 14:50:18 +0100 Message-Id: <20240328135028.504520-21-a.lauterer@proxmox.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20240328135028.504520-1-a.lauterer@proxmox.com> References: <20240328135028.504520-1-a.lauterer@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL -0.059 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record URIBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [sysinfo.rs, utils.rs, mod.rs] Subject: [pve-devel] [PATCH v3 20/30] auto-installer: fetch: add gathering of system identifiers and restructure code X-BeenThere: pve-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox VE development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Thu, 28 Mar 2024 13:56:48 -0000 They will be used as payload when POSTing a request for an answer file. The idea is, that with this information, it should be possible to identify the system and generate a matching answer file on the fly. Many of these properties can also be found on the machine or packaging of the machine and could therefore be scanned into a database. Identifiers are the following properties from `dmidecode` sections 1, 2, and 3: * Asset Tag * Product Name * Serial Number * SKU Number * UUID As well as a list of the MAC addresses of all the NICs and the product type: pve, pmg, pbs. Since we now have more than a simple utils.rs module in the fetch plugins, it, and the additional fetch plugin utilities are placed in their own directory. Signed-off-by: Aaron Lauterer --- .../src/fetch_plugins/mod.rs | 2 +- .../src/fetch_plugins/utils.rs | 90 -------------- .../src/fetch_plugins/utils/mod.rs | 113 ++++++++++++++++++ .../src/fetch_plugins/utils/sysinfo.rs | 81 +++++++++++++ 4 files changed, 195 insertions(+), 91 deletions(-) delete mode 100644 proxmox-auto-installer/src/fetch_plugins/utils.rs create mode 100644 proxmox-auto-installer/src/fetch_plugins/utils/mod.rs create mode 100644 proxmox-auto-installer/src/fetch_plugins/utils/sysinfo.rs diff --git a/proxmox-auto-installer/src/fetch_plugins/mod.rs b/proxmox-auto-installer/src/fetch_plugins/mod.rs index 11d6937..6f1e8a2 100644 --- a/proxmox-auto-installer/src/fetch_plugins/mod.rs +++ b/proxmox-auto-installer/src/fetch_plugins/mod.rs @@ -1,2 +1,2 @@ pub mod partition; -mod utils; +pub mod utils; diff --git a/proxmox-auto-installer/src/fetch_plugins/utils.rs b/proxmox-auto-installer/src/fetch_plugins/utils.rs deleted file mode 100644 index 82cd3e0..0000000 --- a/proxmox-auto-installer/src/fetch_plugins/utils.rs +++ /dev/null @@ -1,90 +0,0 @@ -use anyhow::{bail, Result}; -use log::{info, warn}; -use std::{ - fs::create_dir_all, - path::{Path, PathBuf}, - process::Command, -}; - -/// Searches for upper and lower case existence of the partlabel in the search_path -/// -/// # Arguemnts -/// * `partlabel_lower` - Partition Label in lower case -/// * `search_path` - Path where to search for the partiiton label -/// search_path: String -pub fn scan_partlabels(partlabel_lower: &str, search_path: &str) -> Result { - let partlabel = partlabel_lower.to_uppercase(); - let path = Path::new(search_path).join(partlabel.clone()); - match path.try_exists() { - Ok(true) => { - info!("Found partition with label '{}'", partlabel); - return Ok(path); - } - Ok(false) => info!("Did not detect partition with label '{}'", partlabel), - Err(err) => info!("Encountered issue, accessing '{}': {}", path.display(), err), - } - - let partlabel = partlabel_lower.to_lowercase(); - let path = Path::new(search_path).join(partlabel.clone()); - match path.try_exists() { - Ok(true) => { - info!("Found partition with label '{}'", partlabel); - return Ok(path); - } - Ok(false) => info!("Did not detect partition with label '{}'", partlabel), - Err(err) => info!("Encountered issue, accessing '{}': {}", path.display(), err), - } - bail!( - "Could not detect upper or lower case labels for '{}'", - partlabel_lower - ); -} - -/// Will mount source path to target_path -/// -/// # Arguments -/// * `source` - `PathBuf` of the source location -/// * `target_path` - Location where to mount, will be created -pub fn mount_part(source: PathBuf, target_path: &str) -> Result<()> { - info!("Mounting partition at {target_path}"); - // create dir for mountpoint - create_dir_all(target_path)?; - match Command::new("mount") - .args(["-o", "ro"]) - .arg(source) - .arg(target_path) - .output() - { - Ok(output) => { - if output.status.success() { - Ok(()) - } else { - warn!("Error mounting: {}", String::from_utf8(output.stderr)?); - Ok(()) - } - } - Err(err) => bail!("Error mounting: {}", err), - } -} - -/// Tries to unmount the specified path. Will warn on errors, but not fail. -/// -/// # Arguemnts -/// * `target_path` - path to unmount -pub fn umount_part(target_path: &str) -> Result<()> { - info!("Unmounting partitiona at {target_path}"); - match Command::new("umount").arg(target_path).output() { - Ok(output) => { - if output.status.success() { - Ok(()) - } else { - warn!("Error unmounting: {}", String::from_utf8(output.stderr)?); - Ok(()) - } - } - Err(err) => { - warn!("Error unmounting: {}", err); - Ok(()) - } - } -} diff --git a/proxmox-auto-installer/src/fetch_plugins/utils/mod.rs b/proxmox-auto-installer/src/fetch_plugins/utils/mod.rs new file mode 100644 index 0000000..37341ee --- /dev/null +++ b/proxmox-auto-installer/src/fetch_plugins/utils/mod.rs @@ -0,0 +1,113 @@ +use anyhow::{Error, Result}; +use log::{info, warn}; +use serde::Deserialize; +use serde_json; +use std::{ + fs::{self, create_dir_all}, + path::{Path, PathBuf}, + process::Command, +}; + +static ANSWER_MP: &str = "/mnt/answer"; +static PARTLABEL: &str = "proxmoxinst"; +static SEARCH_PATH: &str = "/dev/disk/by-label"; + +pub mod sysinfo; + +/// Searches for upper and lower case existence of the partlabel in the search_path +/// +/// # Arguemnts +/// * `partlabel_lower` - Partition Label in lower case +/// * `search_path` - Path where to search for the partiiton label +/// search_path: String +pub fn scan_partlabels(partlabel_lower: &str, search_path: &str) -> Result { + let partlabel = partlabel_lower.to_uppercase(); + let path = Path::new(search_path).join(partlabel.clone()); + match path.try_exists() { + Ok(true) => { + info!("Found partition with label '{}'", partlabel); + return Ok(path); + } + Ok(false) => info!("Did not detect partition with label '{}'", partlabel), + Err(err) => info!("Encountered issue, accessing '{}': {}", path.display(), err), + } + + let partlabel = partlabel_lower.to_lowercase(); + let path = Path::new(search_path).join(partlabel.clone()); + match path.try_exists() { + Ok(true) => { + info!("Found partition with label '{}'", partlabel); + return Ok(path); + } + Ok(false) => info!("Did not detect partition with label '{}'", partlabel), + Err(err) => info!("Encountered issue, accessing '{}': {}", path.display(), err), + } + Err(Error::msg(format!( + "Could not detect upper or lower case labels for '{partlabel_lower}'" + ))) +} + +/// Will search and mount a partition/FS labeled proxmoxinst in lower or uppercase to ANSWER_MP; +pub fn mount_proxmoxinst_part() -> Result { + if let Ok(true) = check_if_mounted(ANSWER_MP) { + info!("Skipping: '{ANSWER_MP}' is already mounted."); + return Ok(ANSWER_MP.into()); + } + let part_path = scan_partlabels(PARTLABEL, SEARCH_PATH)?; + info!("Mounting partition at {ANSWER_MP}"); + // create dir for mountpoint + create_dir_all(ANSWER_MP)?; + match Command::new("mount") + .args(["-o", "ro"]) + .arg(part_path) + .arg(ANSWER_MP) + .output() + { + Ok(output) => { + if output.status.success() { + Ok(ANSWER_MP.into()) + } else { + warn!("Error mounting: {}", String::from_utf8(output.stderr)?); + Ok(ANSWER_MP.into()) + } + } + Err(err) => Err(Error::msg(format!("Error mounting: {err}"))), + } +} + +fn check_if_mounted(target_path: &str) -> Result { + let mounts = fs::read_to_string("/proc/mounts")?; + for line in mounts.lines() { + if let Some(mp) = line.split(' ').nth(1) { + if mp == target_path { + return Ok(true); + } + } + } + Ok(false) +} + +#[derive(Deserialize, Debug)] +struct IpLinksUdevInfo { + ifname: String, +} + +/// Returns vec of usable NICs +pub fn get_nic_list() -> Result> { + let ip_output = Command::new("/usr/sbin/ip") + .arg("-j") + .arg("link") + .output()?; + let parsed_links: Vec = + serde_json::from_str(String::from_utf8(ip_output.stdout)?.as_str())?; + let mut links: Vec = Vec::new(); + + for link in parsed_links { + if link.ifname == *"lo" { + continue; + } + links.push(link.ifname); + } + + Ok(links) +} diff --git a/proxmox-auto-installer/src/fetch_plugins/utils/sysinfo.rs b/proxmox-auto-installer/src/fetch_plugins/utils/sysinfo.rs new file mode 100644 index 0000000..8c57283 --- /dev/null +++ b/proxmox-auto-installer/src/fetch_plugins/utils/sysinfo.rs @@ -0,0 +1,81 @@ +use anyhow::{bail, Result}; +use proxmox_installer_common::setup::SetupInfo; +use serde::Serialize; +use std::{collections::HashMap, fs, io, path::Path}; + +use super::get_nic_list; + +const DMI_PATH: &str = "/sys/devices/virtual/dmi/id"; + +pub fn get_sysinfo(pretty: bool) -> Result { + let system_files = vec![ + "product_serial", + "product_sku", + "product_uuid", + "product_name", + ]; + let baseboard_files = vec!["board_asset_tag", "board_serial", "board_name"]; + let chassis_files = vec!["chassis_serial", "chassis_sku", "chassis_asset_tag"]; + + let system = get_dmi_infos(system_files)?; + let baseboard = get_dmi_infos(baseboard_files)?; + let chassis = get_dmi_infos(chassis_files)?; + + let mut mac_addresses: Vec = Vec::new(); + let links = get_nic_list()?; + for link in links { + let address = fs::read_to_string(format!("/sys/class/net/{link}/address"))?; + let address = String::from(address.trim()); + mac_addresses.push(address); + } + + let iso_info = Path::new("/run/proxmox-installer/iso-info.json"); + let mut product = String::from("Not available. Would be one of the following: pve, pmg, pbs"); + if iso_info.exists() { + let file = fs::File::open("/run/proxmox-installer/iso-info.json")?; + let reader = io::BufReader::new(file); + let setup_info: SetupInfo = serde_json::from_reader(reader)?; + product = setup_info.config.product.to_string(); + } + + let sysinfo = SysInfo { + product, + system, + baseboard, + chassis, + mac_addresses, + }; + if pretty { + return Ok(serde_json::to_string_pretty(&sysinfo)?); + } + Ok(serde_json::to_string(&sysinfo)?) +} + +#[derive(Debug, Serialize)] +struct SysInfo { + product: String, + system: HashMap, + baseboard: HashMap, + chassis: HashMap, + mac_addresses: Vec, +} + +fn get_dmi_infos(files: Vec<&str>) -> Result> { + let mut res: HashMap = HashMap::new(); + + for file in files { + let path = format!("{DMI_PATH}/{file}"); + let content = match fs::read_to_string(&path) { + Err(ref err) if err.kind() == std::io::ErrorKind::NotFound => continue, + Err(ref err) if err.kind() == std::io::ErrorKind::PermissionDenied => { + bail!("Could not read data. Are you running as root or with sudo?") + } + Err(err) => bail!("Error: '{err}' on '{path}'"), + Ok(content) => content.trim().into(), + }; + let key = file.splitn(2, '_').last().unwrap(); + res.insert(key.into(), content); + } + + Ok(res) +} -- 2.39.2