[PATCH proxmox-backup v4 7/8] config: move node config into pbs-config, including helper tools

Christian Ebner
2026-03-27T12:13:53Z

None of the node config helpers and tools are required to live in
proxmox-backup directly anymore, all can be encapsulated in the
pbs-config crate, aligning the node config code layout to that of
other configs.

Adapts all callsides to use pbs_config::node to interact with the
node config.

Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
---
pbs-config/Cargo.toml | 1 +
src/tools/config.rs => pbs-config/src/key_value.rs | 0
pbs-config/src/lib.rs | 2 ++
{src/config => pbs-config/src}/node.rs | 11 ++++++-----
src/api2/config/remote.rs | 2 +-
src/api2/node/apt.rs | 2 +-
src/api2/node/certificates.rs | 4 ++--
src/api2/node/config.rs | 8 ++++----
src/api2/node/subscription.rs | 2 +-
src/bin/proxmox-backup-proxy.rs | 9 ++++-----
src/bin/proxmox-daily-update.rs | 2 +-
src/config/mod.rs | 1 -
src/tools/mod.rs | 1 -
13 files changed, 23 insertions(+), 22 deletions(-)
rename src/tools/config.rs => pbs-config/src/key_value.rs (100%)
rename {src/config => pbs-config/src}/node.rs (80%)

diff --git a/pbs-config/Cargo.toml b/pbs-config/Cargo.toml
index eb81ce004..2b5eb782c 100644
--- a/pbs-config/Cargo.toml
+++ b/pbs-config/Cargo.toml
@@ -18,6 +18,7 @@ regex.workspace = true
serde.workspace = true
serde_json.workspace = true

+proxmox-http.workspace = true
proxmox-notify.workspace = true
proxmox-router = { workspace = true, default-features = false }
proxmox-s3-client.workspace = true
diff --git a/src/tools/config.rs b/pbs-config/src/key_value.rs
similarity index 100%
rename from src/tools/config.rs
rename to pbs-config/src/key_value.rs
diff --git a/pbs-config/src/lib.rs b/pbs-config/src/lib.rs
index 1ed472385..3be5582e8 100644
--- a/pbs-config/src/lib.rs
+++ b/pbs-config/src/lib.rs
@@ -4,8 +4,10 @@ pub use cached_user_info::CachedUserInfo;
pub mod datastore;
pub mod domains;
pub mod drive;
+pub mod key_value;
pub mod media_pool;
pub mod metrics;
+pub mod node;
pub mod notifications;
pub mod prune;
pub mod remote;
diff --git a/src/config/node.rs b/pbs-config/src/node.rs
similarity index 80%
rename from src/config/node.rs
rename to pbs-config/src/node.rs
index 6e44495f8..1aeef9080 100644
--- a/src/config/node.rs
+++ b/pbs-config/src/node.rs
@@ -8,7 +8,8 @@ use proxmox_http::ProxyConfig;
use proxmox_schema::ApiType;

use pbs_buildcfg::configdir;
-use pbs_config::{open_backup_lockfile, BackupLockGuard};
+
+use crate::{open_backup_lockfile, BackupLockGuard};

const CONF_FILE: &str = configdir!("/node.cfg");
const LOCK_FILE: &str = configdir!("/.node.lck");
@@ -22,7 +23,7 @@ pub fn config() -> Result<(NodeConfig, [u8; 32]), Error> {
let content = proxmox_sys::fs::file_read_optional_string(CONF_FILE)?.unwrap_or_default();

let digest = openssl::sha::sha256(content.as_bytes());
- let data: NodeConfig = crate::tools::config::from_str(&content, &NodeConfig::API_SCHEMA)?;
+ let data: NodeConfig = crate::key_value::from_str(&content, &NodeConfig::API_SCHEMA)?;

Ok((data, digest))
}
@@ -44,11 +45,11 @@ pub fn save_config(config: &NodeConfig) -> Result<(), Error> {
dummy_acceptor.set_cipher_list(ciphers)?;
}

- let raw = crate::tools::config::to_bytes(config, &NodeConfig::API_SCHEMA)?;
- pbs_config::replace_backup_config(CONF_FILE, &raw)
+ let raw = crate::key_value::to_bytes(config, &NodeConfig::API_SCHEMA)?;
+ crate::replace_backup_config(CONF_FILE, &raw)
}

-pub(crate) fn node_http_proxy_config() -> Result<Option<ProxyConfig>, Error> {
+pub fn node_http_proxy_config() -> Result<Option<ProxyConfig>, Error> {
let (node_config, _digest) = self::config()?;
Ok(node_config.http_proxy())
}
diff --git a/src/api2/config/remote.rs b/src/api2/config/remote.rs
index 1996ee91b..03eae24dc 100644
--- a/src/api2/config/remote.rs
+++ b/src/api2/config/remote.rs
@@ -320,7 +320,7 @@ pub fn remote_client_config(
options = options.rate_limit(limit);
}
if remote.config.use_node_proxy.unwrap_or(false) {
- let proxy = crate::config::node::node_http_proxy_config()?;
+ let proxy = pbs_config::node::node_http_proxy_config()?;
options = options.proxy(proxy);
}

diff --git a/src/api2/node/apt.rs b/src/api2/node/apt.rs
index c696adb1e..32dfe7def 100644
--- a/src/api2/node/apt.rs
+++ b/src/api2/node/apt.rs
@@ -15,7 +15,7 @@ use proxmox_sys::fs::{replace_file, CreateOptions};

use pbs_api_types::{NODE_SCHEMA, PRIV_SYS_AUDIT, PRIV_SYS_MODIFY, UPID_SCHEMA};

-use crate::config::node;
+use pbs_config::node;

#[api(
input: {
diff --git a/src/api2/node/certificates.rs b/src/api2/node/certificates.rs
index e5761e5f5..a69f6511f 100644
--- a/src/api2/node/certificates.rs
+++ b/src/api2/node/certificates.rs
@@ -328,7 +328,7 @@ fn spawn_certificate_worker(
// custom one too hard... We keep the parameter because the widget-toolkit may be using it...
let _ = force;

- let (node_config, _digest) = crate::config::node::config()?;
+ let (node_config, _digest) = pbs_config::node::config()?;

let auth_id = rpcenv.get_auth_id().unwrap();

@@ -381,7 +381,7 @@ fn spawn_certificate_worker(
/// Renew the current ACME certificate if it expires within 30 days (or always if the `force`
/// parameter is set).
pub fn revoke_acme_cert(rpcenv: &mut dyn RpcEnvironment) -> Result<String, Error> {
- let (node_config, _digest) = crate::config::node::config()?;
+ let (node_config, _digest) = pbs_config::node::config()?;

let cert_pem = get_certificate_pem()?;

diff --git a/src/api2/node/config.rs b/src/api2/node/config.rs
index 619e2b022..fbec79118 100644
--- a/src/api2/node/config.rs
+++ b/src/api2/node/config.rs
@@ -28,7 +28,7 @@ pub const ROUTER: Router = Router::new()
)]
/// Get the node configuration
pub fn get_node_config(rpcenv: &mut dyn RpcEnvironment) -> Result<NodeConfig, Error> {
- let (config, digest) = crate::config::node::config()?;
+ let (config, digest) = pbs_config::node::config()?;
rpcenv["digest"] = hex::encode(digest).into();
Ok(config)
}
@@ -104,8 +104,8 @@ pub fn update_node_config(
delete: Option<Vec<DeletableProperty>>,
digest: Option<String>,
) -> Result<(), Error> {
- let _lock = crate::config::node::lock()?;
- let (mut config, expected_digest) = crate::config::node::config()?;
+ let _lock = pbs_config::node::lock()?;
+ let (mut config, expected_digest) = pbs_config::node::config()?;
if let Some(digest) = digest {
// FIXME: GUI doesn't handle our non-inlined digest part here properly...
if !digest.is_empty() {
@@ -206,7 +206,7 @@ pub fn update_node_config(
config.consent_text = update.consent_text;
}

- crate::config::node::save_config(&config)?;
+ pbs_config::node::save_config(&config)?;

update_apt_proxy_config(config.http_proxy().as_ref())?;

diff --git a/src/api2/node/subscription.rs b/src/api2/node/subscription.rs
index 7d9678fc5..03d613ee2 100644
--- a/src/api2/node/subscription.rs
+++ b/src/api2/node/subscription.rs
@@ -12,10 +12,10 @@ use pbs_api_types::{
Authid, NODE_SCHEMA, PRIV_SYS_AUDIT, PRIV_SYS_MODIFY, SUBSCRIPTION_KEY_SCHEMA,
};

-use crate::config::node;
use crate::tools::{DEFAULT_USER_AGENT_STRING, PROXMOX_BACKUP_TCP_KEEPALIVE_TIME};

use pbs_buildcfg::PROXMOX_BACKUP_SUBSCRIPTION_FN;
+use pbs_config::node;
use pbs_config::CachedUserInfo;

const PRODUCT_URL: &str = "https://www.proxmox.com/en/proxmox-backup-server/pricing";
diff --git a/src/bin/proxmox-backup-proxy.rs b/src/bin/proxmox-backup-proxy.rs
index c1fe3ac15..b04eb7e8e 100644
--- a/src/bin/proxmox-backup-proxy.rs
+++ b/src/bin/proxmox-backup-proxy.rs
@@ -45,7 +45,6 @@ use pbs_api_types::{
};

use proxmox_backup::auth_helpers::*;
-use proxmox_backup::config;
use proxmox_backup::server::{self, metric_collection};
use proxmox_backup::tools::PROXMOX_BACKUP_TCP_KEEPALIVE_TIME;

@@ -80,7 +79,7 @@ fn get_language(headers: &hyper::http::HeaderMap) -> String {

match cookie_from_header(headers, "PBSLangCookie") {
Some(cookie_lang) if exists(&cookie_lang) => cookie_lang,
- _ => match config::node::config().map(|(cfg, _)| cfg.default_lang) {
+ _ => match pbs_config::node::config().map(|(cfg, _)| cfg.default_lang) {
Ok(Some(default_lang)) if exists(&default_lang) => default_lang,
_ => String::from(""),
},
@@ -145,7 +144,7 @@ async fn get_index_future(env: RestEnvironment, parts: Parts) -> Response

let theme = get_theme(&parts.headers);

- let consent = config::node::config()
+ let consent = pbs_config::node::config()
.ok()
.and_then(|config| config.0.consent_text)
.unwrap_or("".to_string());
@@ -430,7 +429,7 @@ fn make_tls_acceptor() -> Result<SslAcceptor, Error> {
let key_path = configdir!("/proxy.key");
let cert_path = configdir!("/proxy.pem");

- let (config, _) = config::node::config()?;
+ let (config, _) = pbs_config::node::config()?;
let ciphers_tls_1_3 = config.ciphers_tls_1_3;
let ciphers_tls_1_2 = config.ciphers_tls_1_2;

@@ -799,7 +798,7 @@ async fn schedule_task_log_rotate() {
let max_size = 512 * 1024 - 1; // an entry has ~ 100b, so > 5000 entries/file
let max_files = 20; // times twenty files gives > 100000 task entries

- let max_days = proxmox_backup::config::node::config()
+ let max_days = pbs_config::node::config()
.map(|(cfg, _)| cfg.task_log_max_days)
.ok()
.flatten();
diff --git a/src/bin/proxmox-daily-update.rs b/src/bin/proxmox-daily-update.rs
index 025eb47fd..c4d68e306 100644
--- a/src/bin/proxmox-daily-update.rs
+++ b/src/bin/proxmox-daily-update.rs
@@ -67,7 +67,7 @@ async fn do_update(rpcenv: &mut dyn RpcEnvironment) -> Result<(), Error> {
}

async fn check_acme_certificates(rpcenv: &mut dyn RpcEnvironment) -> Result<(), Error> {
- let (config, _) = proxmox_backup::config::node::config()?;
+ let (config, _) = pbs_config::node::config()?;

// do we even have any acme domains configures?
if config.acme_domains().next().is_none() {
diff --git a/src/config/mod.rs b/src/config/mod.rs
index f05af90d8..2de76bb14 100644
--- a/src/config/mod.rs
+++ b/src/config/mod.rs
@@ -15,7 +15,6 @@ use proxmox_lang::try_block;
use pbs_api_types::{PamRealmConfig, PbsRealmConfig};
use pbs_buildcfg::{self, configdir};

-pub mod node;
pub mod tfa;

/// Check configuration directory permissions
diff --git a/src/tools/mod.rs b/src/tools/mod.rs
index 6a975bde2..05225a843 100644
--- a/src/tools/mod.rs
+++ b/src/tools/mod.rs
@@ -13,7 +13,6 @@ use proxmox_http::{client::Client, HttpOptions, ProxyConfig};
use pbs_datastore::backup_info::{BackupDir, BackupInfo};
use pbs_datastore::manifest::BackupManifest;

-pub mod config;
pub mod disks;
pub mod fs;
pub mod statistics;
--
2.47.3