From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [IPv6:2a01:7e0:0:424::9]) by lore.proxmox.com (Postfix) with ESMTPS id 6D71C1FF13F for ; Thu, 12 Mar 2026 12:43:02 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 0E3E2125D1; Thu, 12 Mar 2026 12:42:56 +0100 (CET) From: Christian Ebner To: pbs-devel@lists.proxmox.com Subject: [PATCH proxmox-backup v3 3/6] config: use moved NodeConfig definitions in pbs-api-types Date: Thu, 12 Mar 2026 12:42:05 +0100 Message-ID: <20260312114208.514373-6-c.ebner@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260312114208.514373-1-c.ebner@proxmox.com> References: <20260312114208.514373-1-c.ebner@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1773315704229 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.057 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 RCVD_IN_MSPIKE_H2 0.001 Average reputation (+2) SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Message-ID-Hash: BK5GVYAAPWBHJ55OOEJQEXV7N63G2RXI X-Message-ID-Hash: BK5GVYAAPWBHJ55OOEJQEXV7N63G2RXI X-MailFrom: c.ebner@proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox Backup Server development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: These have been moved as preparation in an effort to move the node config to pbs-config. That is required in order to read the node's proxy config from the datastore implementation. Signed-off-by: Christian Ebner --- src/api2/node/config.rs | 3 +- src/config/node.rs | 259 +--------------------------------------- src/tools/config.rs | 4 +- 3 files changed, 6 insertions(+), 260 deletions(-) diff --git a/src/api2/node/config.rs b/src/api2/node/config.rs index 19ede24b7..619e2b022 100644 --- a/src/api2/node/config.rs +++ b/src/api2/node/config.rs @@ -5,10 +5,9 @@ use hex::FromHex; use proxmox_router::{Permission, Router, RpcEnvironment}; use proxmox_schema::api; -use pbs_api_types::{NODE_SCHEMA, PRIV_SYS_AUDIT, PRIV_SYS_MODIFY}; +use pbs_api_types::{NodeConfig, NodeConfigUpdater, NODE_SCHEMA, PRIV_SYS_AUDIT, PRIV_SYS_MODIFY}; use crate::api2::node::apt::update_apt_proxy_config; -use crate::config::node::{NodeConfig, NodeConfigUpdater}; pub const ROUTER: Router = Router::new() .get(&API_METHOD_GET_NODE_CONFIG) diff --git a/src/config/node.rs b/src/config/node.rs index 93173e66f..6e44495f8 100644 --- a/src/config/node.rs +++ b/src/config/node.rs @@ -2,15 +2,10 @@ use std::collections::HashSet; use anyhow::{bail, Error}; use openssl::ssl::{SslAcceptor, SslMethod}; -use serde::{Deserialize, Serialize}; -use pbs_api_types::{ - EMAIL_SCHEMA, MULTI_LINE_COMMENT_SCHEMA, OPENSSL_CIPHERS_TLS_1_2_SCHEMA, - OPENSSL_CIPHERS_TLS_1_3_SCHEMA, -}; -use proxmox_acme_api::{AcmeConfig, AcmeDomain, ACME_DOMAIN_PROPERTY_SCHEMA}; -use proxmox_http::{ProxyConfig, HTTP_PROXY_SCHEMA}; -use proxmox_schema::{api, ApiStringFormat, ApiType, Updater}; +use pbs_api_types::NodeConfig; +use proxmox_http::ProxyConfig; +use proxmox_schema::ApiType; use pbs_buildcfg::configdir; use pbs_config::{open_backup_lockfile, BackupLockGuard}; @@ -53,254 +48,6 @@ pub fn save_config(config: &NodeConfig) -> Result<(), Error> { pbs_config::replace_backup_config(CONF_FILE, &raw) } -/// All available languages in Proxmox. Taken from proxmox-i18n repository. -/// pt_BR, zh_CN, and zh_TW use the same case in the translation files. -// TODO: auto-generate from available translations -#[api] -#[derive(Serialize, Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum Translation { - /// Arabic - Ar, - /// Catalan - Ca, - /// Danish - Da, - /// German - De, - /// English - En, - /// Spanish - Es, - /// Euskera - Eu, - /// Persian (Farsi) - Fa, - /// French - Fr, - /// Galician - Gl, - /// Hebrew - He, - /// Hungarian - Hu, - /// Italian - It, - /// Japanese - Ja, - /// Korean - Kr, - /// Norwegian (Bokmal) - Nb, - /// Dutch - Nl, - /// Norwegian (Nynorsk) - Nn, - /// Polish - Pl, - /// Portuguese (Brazil) - #[serde(rename = "pt_BR")] - PtBr, - /// Russian - Ru, - /// Slovenian - Sl, - /// Swedish - Sv, - /// Turkish - Tr, - /// Chinese (simplified) - #[serde(rename = "zh_CN")] - ZhCn, - /// Chinese (traditional) - #[serde(rename = "zh_TW")] - ZhTw, -} - -#[api( - properties: { - acme: { - optional: true, - type: String, - format: &ApiStringFormat::PropertyString(&AcmeConfig::API_SCHEMA), - }, - acmedomain0: { - schema: ACME_DOMAIN_PROPERTY_SCHEMA, - optional: true, - }, - acmedomain1: { - schema: ACME_DOMAIN_PROPERTY_SCHEMA, - optional: true, - }, - acmedomain2: { - schema: ACME_DOMAIN_PROPERTY_SCHEMA, - optional: true, - }, - acmedomain3: { - schema: ACME_DOMAIN_PROPERTY_SCHEMA, - optional: true, - }, - acmedomain4: { - schema: ACME_DOMAIN_PROPERTY_SCHEMA, - optional: true, - }, - "http-proxy": { - schema: HTTP_PROXY_SCHEMA, - optional: true, - }, - "email-from": { - schema: EMAIL_SCHEMA, - optional: true, - }, - "ciphers-tls-1.3": { - schema: OPENSSL_CIPHERS_TLS_1_3_SCHEMA, - optional: true, - }, - "ciphers-tls-1.2": { - schema: OPENSSL_CIPHERS_TLS_1_2_SCHEMA, - optional: true, - }, - "default-lang" : { - schema: Translation::API_SCHEMA, - optional: true, - }, - "description" : { - optional: true, - schema: MULTI_LINE_COMMENT_SCHEMA, - }, - "consent-text" : { - optional: true, - type: String, - max_length: 64 * 1024, - } - }, -)] -#[derive(Deserialize, Serialize, Updater)] -#[serde(rename_all = "kebab-case")] -/// Node specific configuration. -pub struct NodeConfig { - /// The acme account to use on this node. - #[serde(skip_serializing_if = "Option::is_none")] - pub acme: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - pub acmedomain0: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - pub acmedomain1: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - pub acmedomain2: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - pub acmedomain3: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - pub acmedomain4: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - pub http_proxy: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - pub email_from: Option, - - /// List of TLS ciphers for TLS 1.3 that will be used by the proxy. (Proxy has to be restarted for changes to take effect) - #[serde(skip_serializing_if = "Option::is_none", rename = "ciphers-tls-1.3")] - pub ciphers_tls_1_3: Option, - - /// List of TLS ciphers for TLS <= 1.2 that will be used by the proxy. (Proxy has to be restarted for changes to take effect) - #[serde(skip_serializing_if = "Option::is_none", rename = "ciphers-tls-1.2")] - pub ciphers_tls_1_2: Option, - - /// Default language used in the GUI - #[serde(skip_serializing_if = "Option::is_none")] - pub default_lang: Option, - - /// Node description - #[serde(skip_serializing_if = "Option::is_none")] - pub description: Option, - - /// Maximum days to keep Task logs - #[serde(skip_serializing_if = "Option::is_none")] - pub task_log_max_days: Option, - - /// Consent banner text - #[serde(skip_serializing_if = "Option::is_none")] - pub consent_text: Option, -} - -impl NodeConfig { - pub fn acme_config(&self) -> Result { - self.acme - .as_deref() - .map(|config| { - crate::tools::config::from_property_string::( - config, - &AcmeConfig::API_SCHEMA, - ) - }) - .unwrap_or_else(|| proxmox_acme_api::parse_acme_config_string("account=default")) - } - - pub fn acme_domains(&'_ self) -> AcmeDomainIter<'_> { - AcmeDomainIter::new(self) - } - - /// Returns the parsed ProxyConfig - pub fn http_proxy(&self) -> Option { - if let Some(http_proxy) = &self.http_proxy { - ProxyConfig::parse_proxy_url(http_proxy).ok() - } else { - None - } - } - - /// Sets the HTTP proxy configuration - pub fn set_http_proxy(&mut self, http_proxy: Option) { - self.http_proxy = http_proxy; - } -} - -pub struct AcmeDomainIter<'a> { - config: &'a NodeConfig, - index: usize, -} - -impl<'a> AcmeDomainIter<'a> { - fn new(config: &'a NodeConfig) -> Self { - Self { config, index: 0 } - } -} - -impl Iterator for AcmeDomainIter<'_> { - type Item = Result; - - fn next(&mut self) -> Option { - let domain = loop { - let index = self.index; - self.index += 1; - - let domain = match index { - 0 => self.config.acmedomain0.as_deref(), - 1 => self.config.acmedomain1.as_deref(), - 2 => self.config.acmedomain2.as_deref(), - 3 => self.config.acmedomain3.as_deref(), - 4 => self.config.acmedomain4.as_deref(), - _ => return None, - }; - - if let Some(domain) = domain { - break domain; - } - }; - - Some(crate::tools::config::from_property_string( - domain, - &AcmeDomain::API_SCHEMA, - )) - } -} - pub(crate) fn node_http_proxy_config() -> Result, Error> { let (node_config, _digest) = self::config()?; Ok(node_config.http_proxy()) diff --git a/src/tools/config.rs b/src/tools/config.rs index 64fe981aa..2c1078676 100644 --- a/src/tools/config.rs +++ b/src/tools/config.rs @@ -177,7 +177,7 @@ fn test() { use proxmox_schema::ApiType; // let's just reuse some schema we actually have available: - use crate::config::node::NodeConfig; + use pbs_api_types::NodeConfig; const NODE_CONFIG: &str = "\ acme: account=pebble\n\ @@ -198,7 +198,7 @@ fn test_with_comment() { use proxmox_schema::ApiType; // let's just reuse some schema we actually have available: - use crate::config::node::NodeConfig; + use pbs_api_types::NodeConfig; const NODE_INPUT: &str = "\ #this should\n\ -- 2.47.3