From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) by lore.proxmox.com (Postfix) with ESMTPS id 8471B1FF187 for ; Mon, 3 Nov 2025 13:50:23 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 4811D1CBC7; Mon, 3 Nov 2025 13:51:01 +0100 (CET) Message-ID: <54dd2b06-86c1-43af-8a99-7d1f75be984c@proxmox.com> Date: Mon, 3 Nov 2025 13:50:24 +0100 MIME-Version: 1.0 User-Agent: Mozilla Thunderbird Beta To: Proxmox Datacenter Manager development discussion , Shannon Sterz References: <20251028164435.576642-1-s.sterz@proxmox.com> <20251028164435.576642-2-s.sterz@proxmox.com> Content-Language: en-US From: Dominik Csapak In-Reply-To: <20251028164435.576642-2-s.sterz@proxmox.com> X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1762174207923 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.027 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 KAM_SHORT 0.001 Use of a URL Shortener for very short URL RCVD_IN_VALIDITY_CERTIFIED_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. RCVD_IN_VALIDITY_RPBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. RCVD_IN_VALIDITY_SAFE_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. 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. [types.rs, lib.rs, api.rs, gnu.org, proxmox.com] Subject: Re: [pdm-devel] [PATCH proxmox 1/1] node-status: add node status crate X-BeenThere: pdm-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox Datacenter Manager development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: Proxmox Datacenter Manager development discussion Content-Transfer-Encoding: 7bit Content-Type: text/plain; charset="us-ascii"; Format="flowed" Errors-To: pdm-devel-bounces@lists.proxmox.com Sender: "pdm-devel" nit: would be nice to have in the commit message where this is from (proxmox-backup in this case) and that we might want to change from the implementation there to this. Aside from that and the nit inline, looks good to me, consider it: Reviewed-by: Dominik Csapak On 10/28/25 5:45 PM, Shannon Sterz wrote: > this includes api endpoints for querying api endpoints > > Signed-off-by: Shannon Sterz > --- > Cargo.toml | 1 + > proxmox-node-status/Cargo.toml | 37 +++++ > proxmox-node-status/debian/changelog | 5 + > proxmox-node-status/debian/control | 65 ++++++++ > proxmox-node-status/debian/copyright | 18 +++ > proxmox-node-status/debian/debcargo.toml | 7 + > proxmox-node-status/src/api.rs | 184 +++++++++++++++++++++++ > proxmox-node-status/src/lib.rs | 11 ++ > proxmox-node-status/src/types.rs | 184 +++++++++++++++++++++++ > 9 files changed, 512 insertions(+) > create mode 100644 proxmox-node-status/Cargo.toml > create mode 100644 proxmox-node-status/debian/changelog > create mode 100644 proxmox-node-status/debian/control > create mode 100644 proxmox-node-status/debian/copyright > create mode 100644 proxmox-node-status/debian/debcargo.toml > create mode 100644 proxmox-node-status/src/api.rs > create mode 100644 proxmox-node-status/src/lib.rs > create mode 100644 proxmox-node-status/src/types.rs > > diff --git a/Cargo.toml b/Cargo.toml > index 8091bf70..18c29afa 100644 > --- a/Cargo.toml > +++ b/Cargo.toml > @@ -26,6 +26,7 @@ members = [ > "proxmox-metrics", > "proxmox-network-api", > "proxmox-network-types", > + "proxmox-node-status", > "proxmox-notify", > "proxmox-openid", > "proxmox-product-config", > diff --git a/proxmox-node-status/Cargo.toml b/proxmox-node-status/Cargo.toml > new file mode 100644 > index 00000000..2976d6a0 > --- /dev/null > +++ b/proxmox-node-status/Cargo.toml > @@ -0,0 +1,37 @@ > +[package] > +name = "proxmox-node-status" > +description = "API implementation and types for querying a nodes status." > +version = "1.0.0" > + > +authors.workspace = true > +edition.workspace = true > +license.workspace = true > +repository.workspace = true > +homepage.workspace = true > +exclude.workspace = true > +rust-version.workspace = true > + > +[dependencies] > +anyhow = { workspace = true, optional = true } > +hex = { workspace = true, optional = true } > +nix = { workspace = true, optional = true } > +openssl = { workspace = true, optional = true } > +serde = { workspace = true, features = [ "derive" ] } > +serde_json.workspace = true > +tokio = { workspace = true, optional = true } > + > +proxmox-router = { workspace = true, optional = true } > +proxmox-schema = { workspace = true, features = [ "api-macro", "api-types" ] } > +proxmox-sys = { workspace = true, optional = true } > + > +[features] > +default = [] > +api = [ > + "dep:anyhow", > + "dep:hex", > + "dep:nix", > + "dep:openssl", > + "dep:proxmox-router", > + "dep:proxmox-sys", > + "dep:tokio" > +] > diff --git a/proxmox-node-status/debian/changelog b/proxmox-node-status/debian/changelog > new file mode 100644 > index 00000000..a7050f31 > --- /dev/null > +++ b/proxmox-node-status/debian/changelog > @@ -0,0 +1,5 @@ > +rust-proxmox-node-status (1.0.0-1) trixie; urgency=medium > + > + * Initial packaging > + > + -- Proxmox Support Team Wed, 22 Oct 2025 14:44:26 +0200 > diff --git a/proxmox-node-status/debian/control b/proxmox-node-status/debian/control > new file mode 100644 > index 00000000..48067be8 > --- /dev/null > +++ b/proxmox-node-status/debian/control > @@ -0,0 +1,65 @@ > +Source: rust-proxmox-node-status > +Section: rust > +Priority: optional > +Build-Depends: debhelper-compat (= 13), > + dh-sequence-cargo > +Build-Depends-Arch: cargo:native , > + rustc:native (>= 1.82) , > + libstd-rust-dev , > + librust-proxmox-schema-5+api-macro-dev , > + librust-proxmox-schema-5+api-types-dev , > + librust-proxmox-schema-5+default-dev , > + librust-serde-1+default-dev , > + librust-serde-1+derive-dev , > + librust-serde-json-1+default-dev > +Maintainer: Proxmox Support Team > +Standards-Version: 4.7.2 > +Vcs-Git: git://git.proxmox.com/git/proxmox.git > +Vcs-Browser: https://git.proxmox.com/?p=proxmox.git > +Homepage: https://proxmox.com > +X-Cargo-Crate: proxmox-node-status > + > +Package: librust-proxmox-node-status-dev > +Architecture: any > +Multi-Arch: same > +Depends: > + ${misc:Depends}, > + librust-proxmox-schema-5+api-macro-dev, > + librust-proxmox-schema-5+api-types-dev, > + librust-proxmox-schema-5+default-dev, > + librust-serde-1+default-dev, > + librust-serde-1+derive-dev, > + librust-serde-json-1+default-dev > +Suggests: > + librust-proxmox-node-status+api-dev (= ${binary:Version}) > +Provides: > + librust-proxmox-node-status+default-dev (= ${binary:Version}), > + librust-proxmox-node-status-1-dev (= ${binary:Version}), > + librust-proxmox-node-status-1+default-dev (= ${binary:Version}), > + librust-proxmox-node-status-1.0-dev (= ${binary:Version}), > + librust-proxmox-node-status-1.0+default-dev (= ${binary:Version}), > + librust-proxmox-node-status-1.0.0-dev (= ${binary:Version}), > + librust-proxmox-node-status-1.0.0+default-dev (= ${binary:Version}) > +Description: API implementation and types for querying a nodes status - Rust source code > + Source code for Debianized Rust crate "proxmox-node-status" > + > +Package: librust-proxmox-node-status+api-dev > +Architecture: any > +Multi-Arch: same > +Depends: > + ${misc:Depends}, > + librust-proxmox-node-status-dev (= ${binary:Version}), > + librust-anyhow-1+default-dev, > + librust-hex-0.4+default-dev, > + librust-nix-0.29+default-dev, > + librust-openssl-0.10+default-dev, > + librust-proxmox-router-3+default-dev (>= 3.2.2-~~), > + librust-proxmox-sys-1+default-dev, > + librust-tokio-1+default-dev (>= 1.6-~~) > +Provides: > + librust-proxmox-node-status-1+api-dev (= ${binary:Version}), > + librust-proxmox-node-status-1.0+api-dev (= ${binary:Version}), > + librust-proxmox-node-status-1.0.0+api-dev (= ${binary:Version}) > +Description: API implementation and types for querying a nodes status - feature "api" > + This metapackage enables feature "api" for the Rust proxmox-node-status crate, > + by pulling in any additional dependencies needed by that feature. > diff --git a/proxmox-node-status/debian/copyright b/proxmox-node-status/debian/copyright > new file mode 100644 > index 00000000..d6e3c304 > --- /dev/null > +++ b/proxmox-node-status/debian/copyright > @@ -0,0 +1,18 @@ > +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ > + > +Files: > + * > +Copyright: 2025 Proxmox Server Solutions GmbH > +License: AGPL-3.0-or-later > + This program is free software: you can redistribute it and/or modify it under > + the terms of the GNU Affero 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 Affero General Public License for more > + details. > + . > + You should have received a copy of the GNU Affero General Public License along > + with this program. If not, see . > diff --git a/proxmox-node-status/debian/debcargo.toml b/proxmox-node-status/debian/debcargo.toml > new file mode 100644 > index 00000000..b7864cdb > --- /dev/null > +++ b/proxmox-node-status/debian/debcargo.toml > @@ -0,0 +1,7 @@ > +overlay = "." > +crate_src_path = ".." > +maintainer = "Proxmox Support Team " > + > +[source] > +vcs_git = "git://git.proxmox.com/git/proxmox.git" > +vcs_browser = "https://git.proxmox.com/?p=proxmox.git" > diff --git a/proxmox-node-status/src/api.rs b/proxmox-node-status/src/api.rs > new file mode 100644 > index 00000000..93f6ae12 > --- /dev/null > +++ b/proxmox-node-status/src/api.rs > @@ -0,0 +1,184 @@ > +use std::path::{Path, PathBuf}; > +use std::process::Command; > +use std::sync::OnceLock; > + > +use anyhow::{bail, format_err, Error}; > + > +use proxmox_schema::api; > +use proxmox_schema::api_types::NODE_SCHEMA; > +use proxmox_sys::boot_mode; > +use proxmox_sys::linux::procfs; > + > +pub use crate::types::{ > + BootModeInformation, KernelVersionInformation, NodeCpuInformation, NodeInformation, > + NodeMemoryCounters, NodePowerCommand, NodeStatus, NodeSwapCounters, StorageStatus, > +}; > + > +static TLS_CERT_PATH: OnceLock = OnceLock::new(); > + > +pub fn init_node_status_api>(cert_path: P) -> Result<(), Error> { > + TLS_CERT_PATH > + .set(cert_path.as_ref().to_owned()) > + .map_err(|_e| format_err!("cannot initialize acl tree config twice!")) nit: the string is wrong, seems to be a leftover of a copy&paste > +} > + > +fn procfs_to_node_cpu_info(info: procfs::ProcFsCPUInfo) -> NodeCpuInformation { > + NodeCpuInformation { > + model: info.model, > + sockets: info.sockets, > + cpus: info.cpus, > + } > +} > + > +fn boot_mode_to_info(bm: boot_mode::BootMode, sb: boot_mode::SecureBoot) -> BootModeInformation { > + use boot_mode::BootMode; > + use boot_mode::SecureBoot; > + > + match (bm, sb) { > + (BootMode::Efi, SecureBoot::Enabled) => BootModeInformation { > + mode: crate::types::BootMode::Efi, > + secureboot: true, > + }, > + (BootMode::Efi, SecureBoot::Disabled) => BootModeInformation { > + mode: crate::types::BootMode::Efi, > + secureboot: false, > + }, > + (BootMode::Bios, _) => BootModeInformation { > + mode: crate::types::BootMode::LegacyBios, > + secureboot: false, > + }, > + } > +} > + > +fn certificate_fingerprint() -> Result { > + let cert_path = TLS_CERT_PATH.get().ok_or_else(|| { > + format_err!("certificate path needs to be set before calling node status endpoints") > + })?; > + let x509 = openssl::x509::X509::from_pem(&proxmox_sys::fs::file_get_contents(cert_path)?)?; > + let fp = x509.digest(openssl::hash::MessageDigest::sha256())?; > + > + Ok(hex::encode(fp) > + .as_bytes() > + .chunks(2) > + .map(|v| std::str::from_utf8(v).unwrap()) > + .collect::>() > + .join(":")) > +} > + > +#[api( > + input: { > + properties: { > + node: { > + schema: NODE_SCHEMA, > + }, > + }, > + }, > + returns: { > + type: NodeStatus, > + }, > +)] > +/// Read node memory, CPU and (root) disk usage > +pub async fn get_status() -> Result { > + let meminfo: procfs::ProcFsMemInfo = procfs::read_meminfo()?; > + let memory = NodeMemoryCounters { > + total: meminfo.memtotal, > + used: meminfo.memused, > + free: meminfo.memfree, > + }; > + > + let swap = NodeSwapCounters { > + total: meminfo.swaptotal, > + used: meminfo.swapused, > + free: meminfo.swapfree, > + }; > + > + let kstat: procfs::ProcFsStat = procfs::read_proc_stat()?; > + let cpu = kstat.cpu; > + let wait = kstat.iowait_percent; > + > + let loadavg = procfs::Loadavg::read()?; > + let loadavg = [loadavg.one(), loadavg.five(), loadavg.fifteen()]; > + > + let cpuinfo = procfs::read_cpuinfo()?; > + let cpuinfo = procfs_to_node_cpu_info(cpuinfo); > + > + let uname = nix::sys::utsname::uname()?; > + let kernel_version = KernelVersionInformation::from_uname_parts( > + uname.sysname(), > + uname.release(), > + uname.version(), > + uname.machine(), > + ); > + > + let disk = tokio::task::spawn_blocking(move || proxmox_sys::fs::fs_info(c"/")) > + .await > + .map_err(|err| format_err!("error waiting for fs_info call: {err}"))??; > + > + let boot_info = boot_mode_to_info(boot_mode::BootMode::query(), boot_mode::SecureBoot::query()); > + > + Ok(NodeStatus { > + memory, > + swap, > + root: StorageStatus { > + total: disk.total, > + used: disk.used, > + avail: disk.available, > + }, > + uptime: procfs::read_proc_uptime()?.0 as u64, > + loadavg, > + kversion: kernel_version.get_legacy(), > + current_kernel: kernel_version, > + cpuinfo, > + cpu, > + wait, > + info: NodeInformation { > + fingerprint: certificate_fingerprint()?, > + }, > + boot_info, > + }) > +} > + > +#[api( > + protected: true, > + input: { > + properties: { > + node: { > + schema: NODE_SCHEMA, > + }, > + command: { > + type: NodePowerCommand, > + }, > + } > + }, > +)] > +/// Reboot or shutdown the node. > +pub fn reboot_or_shutdown(command: NodePowerCommand) -> Result<(), Error> { > + let systemctl_command = match command { > + NodePowerCommand::Reboot => "reboot", > + NodePowerCommand::Shutdown => "poweroff", > + }; > + > + let output = Command::new("systemctl") > + .arg(systemctl_command) > + .output() > + .map_err(|err| format_err!("failed to execute systemctl - {err}"))?; > + > + if !output.status.success() { > + match output.status.code() { > + Some(code) => { > + let msg = String::from_utf8(output.stderr) > + .map(|m| { > + if m.is_empty() { > + String::from("no error message") > + } else { > + m > + } > + }) > + .unwrap_or_else(|_| String::from("non utf8 error message (suppressed)")); > + bail!("command failed with status code: {code} - {msg}"); > + } > + None => bail!("systemctl terminated by signal"), > + } > + } > + Ok(()) > +} > diff --git a/proxmox-node-status/src/lib.rs b/proxmox-node-status/src/lib.rs > new file mode 100644 > index 00000000..2372b569 > --- /dev/null > +++ b/proxmox-node-status/src/lib.rs > @@ -0,0 +1,11 @@ > + > +#[cfg(feature = "api")] > +mod api; > +#[cfg(feature = "api")] > +pub use crate::api::{init_node_status_api, API_METHOD_GET_STATUS, API_METHOD_REBOOT_OR_SHUTDOWN}; > + > +mod types; > +pub use crate::types::{ > + BootMode, BootModeInformation, KernelVersionInformation, NodeCpuInformation, NodeInformation, > + NodeMemoryCounters, NodePowerCommand, NodeStatus, NodeSwapCounters, StorageStatus, > +}; > diff --git a/proxmox-node-status/src/types.rs b/proxmox-node-status/src/types.rs > new file mode 100644 > index 00000000..cc0ba424 > --- /dev/null > +++ b/proxmox-node-status/src/types.rs > @@ -0,0 +1,184 @@ > +use std::ffi::OsStr; > + > +use serde::{Deserialize, Serialize}; > + > +use proxmox_schema::api; > + > +#[api] > +#[derive(Serialize, Deserialize, Copy, Clone)] > +#[serde(rename_all = "kebab-case")] > +/// The possible BootModes > +pub enum BootMode { > + /// The BootMode is EFI/UEFI > + Efi, > + /// The BootMode is Legacy BIOS > + LegacyBios, > +} > + > +#[api] > +#[derive(Serialize, Deserialize, Clone)] > +#[serde(rename_all = "lowercase")] > +/// Holds the Bootmodes > +pub struct BootModeInformation { > + /// The BootMode, either Efi or Bios > + pub mode: BootMode, > + /// SecureBoot status > + pub secureboot: bool, > +} > + > +#[api] > +#[derive(Serialize, Deserialize, Default)] > +#[serde(rename_all = "lowercase")] > +/// The current kernel version (output of `uname`) > +pub struct KernelVersionInformation { > + /// The systemname/nodename > + pub sysname: String, > + /// The kernel release number > + pub release: String, > + /// The kernel version > + pub version: String, > + /// The machine architecture > + pub machine: String, > +} > + > +impl KernelVersionInformation { > + pub fn from_uname_parts( > + sysname: &OsStr, > + release: &OsStr, > + version: &OsStr, > + machine: &OsStr, > + ) -> Self { > + KernelVersionInformation { > + sysname: sysname.to_str().map(String::from).unwrap_or_default(), > + release: release.to_str().map(String::from).unwrap_or_default(), > + version: version.to_str().map(String::from).unwrap_or_default(), > + machine: machine.to_str().map(String::from).unwrap_or_default(), > + } > + } > + > + pub fn get_legacy(&self) -> String { > + format!("{} {} {}", self.sysname, self.release, self.version) > + } > +} > + > +#[api] > +#[derive(Serialize, Deserialize, Default)] > +#[serde(rename_all = "kebab-case")] > +/// Information about the CPU > +pub struct NodeCpuInformation { > + /// The CPU model > + pub model: String, > + /// The number of CPU sockets > + pub sockets: usize, > + /// The number of CPU cores (incl. threads) > + pub cpus: usize, > +} > + > +#[api] > +#[derive(Serialize, Deserialize, Default)] > +#[serde(rename_all = "kebab-case")] > +/// Contains general node information such as the fingerprint` > +pub struct NodeInformation { > + /// The SSL Fingerprint > + pub fingerprint: String, > +} > + > +#[api] > +#[derive(Serialize, Deserialize, Default)] > +#[serde(rename_all = "kebab-case")] > +/// Node memory usage counters > +pub struct NodeMemoryCounters { > + /// Total memory > + pub total: u64, > + /// Used memory > + pub used: u64, > + /// Free memory > + pub free: u64, > +} > + > +#[api( > + properties: { > + memory: { > + type: NodeMemoryCounters, > + }, > + root: { > + type: StorageStatus, > + }, > + swap: { > + type: NodeSwapCounters, > + }, > + loadavg: { > + type: Array, > + items: { > + type: Number, > + description: "the load", > + } > + }, > + cpuinfo: { > + type: NodeCpuInformation, > + }, > + info: { > + type: NodeInformation, > + } > + }, > +)] > +#[derive(Serialize, Deserialize)] > +#[serde(rename_all = "kebab-case")] > +/// The Node status > +pub struct NodeStatus { > + pub memory: NodeMemoryCounters, > + pub root: StorageStatus, > + pub swap: NodeSwapCounters, > + /// The current uptime of the server. > + pub uptime: u64, > + /// Load for 1, 5 and 15 minutes. > + pub loadavg: [f64; 3], > + /// The current kernel version (NEW struct type). > + pub current_kernel: KernelVersionInformation, > + /// The current kernel version (LEGACY string type). > + pub kversion: String, > + /// Total CPU usage since last query. > + pub cpu: f64, > + /// Total IO wait since last query. > + pub wait: f64, > + pub cpuinfo: NodeCpuInformation, > + pub info: NodeInformation, > + /// Current boot mode > + pub boot_info: BootModeInformation, > +} > + > +#[api] > +#[derive(Serialize, Deserialize, Default)] > +#[serde(rename_all = "kebab-case")] > +/// Node swap usage counters > +pub struct NodeSwapCounters { > + /// Total swap > + pub total: u64, > + /// Used swap > + pub used: u64, > + /// Free swap > + pub free: u64, > +} > + > +#[api()] > +#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] > +#[serde(rename_all = "lowercase")] > +/// Node Power command type. > +pub enum NodePowerCommand { > + /// Restart the server > + Reboot, > + /// Shutdown the server > + Shutdown, > +} > + > +#[api()] > +#[derive(Default, Serialize, Deserialize)] > +/// Storage space usage information. > +pub struct StorageStatus { > + /// Total space (bytes). > + pub total: u64, > + /// Used space (bytes). > + pub used: u64, > + /// Available space (bytes). > + pub avail: u64, > +} _______________________________________________ pdm-devel mailing list pdm-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel