all lists on lists.proxmox.com
 help / color / mirror / Atom feed
From: Christian Ebner <c.ebner@proxmox.com>
To: pbs-devel@lists.proxmox.com
Subject: [PATCH proxmox v3 2/2] pbs-api-types: move over NodeConfig and related api type from PBS
Date: Thu, 12 Mar 2026 12:42:02 +0100	[thread overview]
Message-ID: <20260312114208.514373-3-c.ebner@proxmox.com> (raw)
In-Reply-To: <20260312114208.514373-1-c.ebner@proxmox.com>

In preparation for refactoring the node config in PBS.

In order to move over the code, the previously used
proxmox_backup::tools::config::from_property_string()
helpers are replaced by analogous implementations by inlining the
serde_json::from_value() calls.

Further, the NodeConfig::validate() was only used by the node's
save_config() implementation and has therefore been inlined on the
PBS side to avoid a dependency on openssl crate.

Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
---
 Cargo.toml                |   1 +
 pbs-api-types/Cargo.toml  |   3 +
 pbs-api-types/src/node.rs | 256 +++++++++++++++++++++++++++++++++++++-
 3 files changed, 259 insertions(+), 1 deletion(-)

diff --git a/Cargo.toml b/Cargo.toml
index e66ffb78..7ad10ae5 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -144,6 +144,7 @@ zstd = "0.13"
 
 # workspace dependencies
 proxmox-access-control = { version = "1.3.0", path = "proxmox-access-control" }
+proxmox-acme-api = { version = "1.0.2", path = "proxmox-acme-api" }
 proxmox-acme = {  version = "1.1.0", path = "proxmox-acme", default-features = false }
 proxmox-api-macro = { version = "1.4.3", path = "proxmox-api-macro" }
 proxmox-apt = { version = "0.99.6", path = "proxmox-apt" }
diff --git a/pbs-api-types/Cargo.toml b/pbs-api-types/Cargo.toml
index 4eabe81e..01813f85 100644
--- a/pbs-api-types/Cargo.toml
+++ b/pbs-api-types/Cargo.toml
@@ -15,10 +15,13 @@ percent-encoding.workspace = true
 regex.workspace = true
 serde.workspace = true
 serde_plain.workspace = true
+serde_json.workspace = true
 
+proxmox-acme-api.workspace = true
 proxmox-auth-api = { workspace = true, features = [ "api-types" ] }
 proxmox-apt-api-types.workspace = true
 proxmox-fixed-string.workspace = true
+proxmox-http = { workspace = true, features = [ "api-types" ] }
 proxmox-human-byte.workspace = true
 proxmox-lang.workspace=true
 proxmox-s3-client = { workspace = true, features = [ "api-types" ] }
diff --git a/pbs-api-types/src/node.rs b/pbs-api-types/src/node.rs
index e5b3526c..da3e4118 100644
--- a/pbs-api-types/src/node.rs
+++ b/pbs-api-types/src/node.rs
@@ -1,13 +1,19 @@
 use std::ffi::OsStr;
 
+use anyhow::Error;
 use serde::{Deserialize, Serialize};
 
+use proxmox_acme_api::{AcmeConfig, AcmeDomain, ACME_DOMAIN_PROPERTY_SCHEMA};
 use proxmox_auth_api::types::Authid;
 #[cfg(feature = "enum-fallback")]
 use proxmox_fixed_string::FixedString;
+use proxmox_http::{ProxyConfig, HTTP_PROXY_SCHEMA};
 use proxmox_schema::*;
 
-use crate::StorageStatus;
+use crate::{
+    StorageStatus, EMAIL_SCHEMA, MULTI_LINE_COMMENT_SCHEMA, OPENSSL_CIPHERS_TLS_1_2_SCHEMA,
+    OPENSSL_CIPHERS_TLS_1_3_SCHEMA,
+};
 
 #[api]
 #[derive(Serialize, Deserialize, Default)]
@@ -199,3 +205,251 @@ pub struct NodeShellTicket {
     /// user or authid encoded in the ticket
     pub user: Authid,
 }
+
+/// 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<String>,
+
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub acmedomain0: Option<String>,
+
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub acmedomain1: Option<String>,
+
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub acmedomain2: Option<String>,
+
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub acmedomain3: Option<String>,
+
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub acmedomain4: Option<String>,
+
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub http_proxy: Option<String>,
+
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub email_from: Option<String>,
+
+    /// 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<String>,
+
+    /// 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<String>,
+
+    /// Default language used in the GUI
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub default_lang: Option<String>,
+
+    /// Node description
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub description: Option<String>,
+
+    /// Maximum days to keep Task logs
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub task_log_max_days: Option<usize>,
+
+    /// Consent banner text
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub consent_text: Option<String>,
+}
+
+impl NodeConfig {
+    pub fn acme_config(&self) -> Result<AcmeConfig, Error> {
+        self.acme
+            .as_deref()
+            .map(|config| {
+                Ok(serde_json::from_value(
+                    AcmeConfig::API_SCHEMA.parse_property_string(config)?,
+                )?)
+            })
+            .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<ProxyConfig> {
+        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<String>) {
+        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<AcmeDomain, Error>;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        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(
+            AcmeDomain::API_SCHEMA
+                .parse_property_string(domain)
+                .and_then(|domain| Ok(serde_json::from_value(domain)?)),
+        )
+    }
+}
-- 
2.47.3





  parent reply	other threads:[~2026-03-12 11:43 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-03-12 11:42 [PATCH proxmox{,-backup} v3 0/8] fix #6716: Add support for http proxy configuration for S3 endpoints Christian Ebner
2026-03-12 11:42 ` [PATCH proxmox v3 1/2] http: move http proxy schema from PBS to crate's api types Christian Ebner
2026-03-12 11:42 ` Christian Ebner [this message]
2026-03-12 11:42 ` [PATCH proxmox-backup v3 1/6] pbs-config: use http proxy schema moved to proxmox-http crate Christian Ebner
2026-03-12 11:42 ` [PATCH proxmox-backup v3 2/6] config: inline NodeConfig::validate() to its only call side Christian Ebner
2026-03-12 11:42 ` [PATCH proxmox-backup v3 3/6] config: use moved NodeConfig definitions in pbs-api-types Christian Ebner
2026-03-12 11:42 ` [PATCH proxmox-backup v3 4/6] tools: drop unused from_property_string() helper Christian Ebner
2026-03-12 11:42 ` [PATCH proxmox-backup v3 5/6] config: move node config into pbs-config, including helper tools Christian Ebner
2026-03-12 11:42 ` [PATCH proxmox-backup v3 6/6] fix #6716: pass node http proxy config to s3 backend Christian Ebner

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260312114208.514373-3-c.ebner@proxmox.com \
    --to=c.ebner@proxmox.com \
    --cc=pbs-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal