public inbox for pbs-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: Samuel Rufinatscha <s.rufinatscha@proxmox.com>
To: "Proxmox Backup Server development discussion"
	<pbs-devel@lists.proxmox.com>,
	"Fabian Grünbichler" <f.gruenbichler@proxmox.com>
Subject: Re: [pbs-devel] [PATCH proxmox-backup v5 4/5] acme: change API impls to use proxmox-acme-api handlers
Date: Tue, 13 Jan 2026 17:53:15 +0100	[thread overview]
Message-ID: <6a1c96d8-6d6a-4f70-8fd6-5acb99a12942@proxmox.com> (raw)
In-Reply-To: <1768309751.cha53x6agk.astroid@yuna.none>

On 1/13/26 2:45 PM, Fabian Grünbichler wrote:
> On January 8, 2026 12:26 pm, Samuel Rufinatscha wrote:
>> PBS currently uses its own ACME client and API logic, while PDM uses the
>> factored out proxmox-acme and proxmox-acme-api crates. This duplication
>> risks differences in behaviour and requires ACME maintenance in two
>> places. This patch is part of a series to move PBS over to the shared
>> ACME stack.
>>
>> Changes:
>> - Replace api2/config/acme.rs API logic with proxmox-acme-api handlers.
>> - Drop local caching and helper types that duplicate proxmox-acme-api.
>>
>> Signed-off-by: Samuel Rufinatscha <s.rufinatscha@proxmox.com>
>> ---
>>   src/api2/config/acme.rs                | 378 ++-----------------------
>>   src/api2/types/acme.rs                 |  16 --
>>   src/bin/proxmox_backup_manager/acme.rs |   6 +-
>>   src/config/acme/mod.rs                 |  44 +--
>>   4 files changed, 33 insertions(+), 411 deletions(-)
>>
>> diff --git a/src/api2/config/acme.rs b/src/api2/config/acme.rs
>> index 898f06dd..3314430c 100644
>> --- a/src/api2/config/acme.rs
>> +++ b/src/api2/config/acme.rs
>> @@ -1,29 +1,18 @@
>> -use std::fs;
>> -use std::ops::ControlFlow;
>> -use std::path::Path;
> 
> nit: this one is actually still used below

Ah, I see :) Good find!

> 
>> -use std::sync::{Arc, LazyLock, Mutex};
>> -use std::time::SystemTime;
>> -
>> -use anyhow::{bail, format_err, Error};
>> -use hex::FromHex;
>> -use serde::{Deserialize, Serialize};
>> -use serde_json::{json, Value};
>> -use tracing::{info, warn};
>> +use anyhow::Error;
>> +use tracing::info;
>>   
>>   use pbs_api_types::{Authid, PRIV_SYS_MODIFY};
>> -use proxmox_acme::async_client::AcmeClient;
>> -use proxmox_acme::types::AccountData as AcmeAccountData;
>> -use proxmox_acme_api::AcmeAccountName;
>> +use proxmox_acme_api::{
>> +    AccountEntry, AccountInfo, AcmeAccountName, AcmeChallengeSchema, ChallengeSchemaWrapper,
>> +    DeletablePluginProperty, DnsPluginCore, DnsPluginCoreUpdater, KnownAcmeDirectory, PluginConfig,
>> +    DEFAULT_ACME_DIRECTORY_ENTRY, PLUGIN_ID_SCHEMA,
>> +};
>> +use proxmox_config_digest::ConfigDigest;
>>   use proxmox_rest_server::WorkerTask;
>>   use proxmox_router::{
>>       http_bail, list_subdirs_api_method, Permission, Router, RpcEnvironment, SubdirMap,
>>   };
>> -use proxmox_schema::{api, param_bail};
>> -
>> -use crate::api2::types::{AcmeChallengeSchema, KnownAcmeDirectory};
>> -use crate::config::acme::plugin::{
>> -    self, DnsPlugin, DnsPluginCore, DnsPluginCoreUpdater, PLUGIN_ID_SCHEMA,
>> -};
>> +use proxmox_schema::api;
>>   
>>   pub(crate) const ROUTER: Router = Router::new()
>>       .get(&list_subdirs_api_method!(SUBDIRS))
>> @@ -65,19 +54,6 @@ const PLUGIN_ITEM_ROUTER: Router = Router::new()
>>       .put(&API_METHOD_UPDATE_PLUGIN)
>>       .delete(&API_METHOD_DELETE_PLUGIN);
>>   
>> -#[api(
>> -    properties: {
>> -        name: { type: AcmeAccountName },
>> -    },
>> -)]
>> -/// An ACME Account entry.
>> -///
>> -/// Currently only contains a 'name' property.
>> -#[derive(Serialize)]
>> -pub struct AccountEntry {
>> -    name: AcmeAccountName,
>> -}
>> -
>>   #[api(
>>       access: {
>>           permission: &Permission::Privilege(&["system", "certificates"], PRIV_SYS_MODIFY, false),
>> @@ -91,40 +67,7 @@ pub struct AccountEntry {
>>   )]
>>   /// List ACME accounts.
>>   pub fn list_accounts() -> Result<Vec<AccountEntry>, Error> {
>> -    let mut entries = Vec::new();
>> -    crate::config::acme::foreach_acme_account(|name| {
>> -        entries.push(AccountEntry { name });
>> -        ControlFlow::Continue(())
>> -    })?;
>> -    Ok(entries)
>> -}
>> -
>> -#[api(
>> -    properties: {
>> -        account: { type: Object, properties: {}, additional_properties: true },
>> -        tos: {
>> -            type: String,
>> -            optional: true,
>> -        },
>> -    },
>> -)]
>> -/// ACME Account information.
>> -///
>> -/// This is what we return via the API.
>> -#[derive(Serialize)]
>> -pub struct AccountInfo {
>> -    /// Raw account data.
>> -    account: AcmeAccountData,
>> -
>> -    /// The ACME directory URL the account was created at.
>> -    directory: String,
>> -
>> -    /// The account's own URL within the ACME directory.
>> -    location: String,
>> -
>> -    /// The ToS URL, if the user agreed to one.
>> -    #[serde(skip_serializing_if = "Option::is_none")]
>> -    tos: Option<String>,
>> +    proxmox_acme_api::list_accounts()
>>   }
>>   
>>   #[api(
>> @@ -141,23 +84,7 @@ pub struct AccountInfo {
>>   )]
>>   /// Return existing ACME account information.
>>   pub async fn get_account(name: AcmeAccountName) -> Result<AccountInfo, Error> {
>> -    let account_info = proxmox_acme_api::get_account(name).await?;
>> -
>> -    Ok(AccountInfo {
>> -        location: account_info.location,
>> -        tos: account_info.tos,
>> -        directory: account_info.directory,
>> -        account: AcmeAccountData {
>> -            only_return_existing: false, // don't actually write this out in case it's set
>> -            ..account_info.account
>> -        },
>> -    })
>> -}
>> -
>> -fn account_contact_from_string(s: &str) -> Vec<String> {
>> -    s.split(&[' ', ';', ',', '\0'][..])
>> -        .map(|s| format!("mailto:{s}"))
>> -        .collect()
>> +    proxmox_acme_api::get_account(name).await
>>   }
>>   
>>   #[api(
>> @@ -222,15 +149,11 @@ fn register_account(
>>           );
>>       }
>>   
>> -    if Path::new(&crate::config::acme::account_path(&name)).exists() {
>> +    if std::path::Path::new(&proxmox_acme_api::account_config_filename(&name)).exists() {
> 
> here ^
> 
>>           http_bail!(BAD_REQUEST, "account {} already exists", name);
>>       }
>>   
>> -    let directory = directory.unwrap_or_else(|| {
>> -        crate::config::acme::DEFAULT_ACME_DIRECTORY_ENTRY
>> -            .url
>> -            .to_owned()
>> -    });
>> +    let directory = directory.unwrap_or_else(|| DEFAULT_ACME_DIRECTORY_ENTRY.url.to_string());
>>   
>>       WorkerTask::spawn(
>>           "acme-register",
>> @@ -286,17 +209,7 @@ pub fn update_account(
>>           auth_id.to_string(),
>>           true,
>>           move |_worker| async move {
>> -            let data = match contact {
>> -                Some(data) => json!({
>> -                    "contact": account_contact_from_string(&data),
>> -                }),
>> -                None => json!({}),
>> -            };
>> -
>> -            proxmox_acme_api::load_client_with_account(&name)
>> -                .await?
>> -                .update_account(&data)
>> -                .await?;
>> +            proxmox_acme_api::update_account(&name, contact).await?;
>>   
>>               Ok(())
>>           },
>> @@ -334,18 +247,8 @@ pub fn deactivate_account(
>>           auth_id.to_string(),
>>           true,
>>           move |_worker| async move {
>> -            match proxmox_acme_api::load_client_with_account(&name)
>> -                .await?
>> -                .update_account(&json!({"status": "deactivated"}))
>> -                .await
>> -            {
>> -                Ok(_account) => (),
>> -                Err(err) if !force => return Err(err),
>> -                Err(err) => {
>> -                    warn!("error deactivating account {name}, proceeding anyway - {err}");
>> -                }
>> -            }
>> -            crate::config::acme::mark_account_deactivated(&name)?;
>> +            proxmox_acme_api::deactivate_account(&name, force).await?;
>> +
>>               Ok(())
>>           },
>>       )
>> @@ -372,15 +275,7 @@ pub fn deactivate_account(
>>   )]
>>   /// Get the Terms of Service URL for an ACME directory.
>>   async fn get_tos(directory: Option<String>) -> Result<Option<String>, Error> {
>> -    let directory = directory.unwrap_or_else(|| {
>> -        crate::config::acme::DEFAULT_ACME_DIRECTORY_ENTRY
>> -            .url
>> -            .to_owned()
>> -    });
>> -    Ok(AcmeClient::new(directory)
>> -        .terms_of_service_url()
>> -        .await?
>> -        .map(str::to_owned))
>> +    proxmox_acme_api::get_tos(directory).await
>>   }
>>   
>>   #[api(
>> @@ -395,52 +290,7 @@ async fn get_tos(directory: Option<String>) -> Result<Option<String>, Error> {
>>   )]
>>   /// Get named known ACME directory endpoints.
>>   fn get_directories() -> Result<&'static [KnownAcmeDirectory], Error> {
>> -    Ok(crate::config::acme::KNOWN_ACME_DIRECTORIES)
>> -}
>> -
>> -/// Wrapper for efficient Arc use when returning the ACME challenge-plugin schema for serializing
>> -struct ChallengeSchemaWrapper {
>> -    inner: Arc<Vec<AcmeChallengeSchema>>,
>> -}
>> -
>> -impl Serialize for ChallengeSchemaWrapper {
>> -    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
>> -    where
>> -        S: serde::Serializer,
>> -    {
>> -        self.inner.serialize(serializer)
>> -    }
>> -}
>> -
>> -struct CachedSchema {
>> -    schema: Arc<Vec<AcmeChallengeSchema>>,
>> -    cached_mtime: SystemTime,
>> -}
>> -
>> -fn get_cached_challenge_schemas() -> Result<ChallengeSchemaWrapper, Error> {
>> -    static CACHE: LazyLock<Mutex<Option<CachedSchema>>> = LazyLock::new(|| Mutex::new(None));
>> -
>> -    // the actual loading code
>> -    let mut last = CACHE.lock().unwrap();
>> -
>> -    let actual_mtime = fs::metadata(crate::config::acme::ACME_DNS_SCHEMA_FN)?.modified()?;
>> -
>> -    let schema = match &*last {
>> -        Some(CachedSchema {
>> -            schema,
>> -            cached_mtime,
>> -        }) if *cached_mtime >= actual_mtime => schema.clone(),
>> -        _ => {
>> -            let new_schema = Arc::new(crate::config::acme::load_dns_challenge_schema()?);
>> -            *last = Some(CachedSchema {
>> -                schema: Arc::clone(&new_schema),
>> -                cached_mtime: actual_mtime,
>> -            });
>> -            new_schema
>> -        }
>> -    };
>> -
>> -    Ok(ChallengeSchemaWrapper { inner: schema })
>> +    Ok(proxmox_acme_api::KNOWN_ACME_DIRECTORIES)
>>   }
>>   
>>   #[api(
>> @@ -455,69 +305,7 @@ fn get_cached_challenge_schemas() -> Result<ChallengeSchemaWrapper, Error> {
>>   )]
>>   /// Get named known ACME directory endpoints.
>>   fn get_challenge_schema() -> Result<ChallengeSchemaWrapper, Error> {
>> -    get_cached_challenge_schemas()
>> -}
>> -
>> -#[api]
>> -#[derive(Default, Deserialize, Serialize)]
>> -#[serde(rename_all = "kebab-case")]
>> -/// The API's format is inherited from PVE/PMG:
>> -pub struct PluginConfig {
>> -    /// Plugin ID.
>> -    plugin: String,
>> -
>> -    /// Plugin type.
>> -    #[serde(rename = "type")]
>> -    ty: String,
>> -
>> -    /// DNS Api name.
>> -    #[serde(skip_serializing_if = "Option::is_none", default)]
>> -    api: Option<String>,
>> -
>> -    /// Plugin configuration data.
>> -    #[serde(skip_serializing_if = "Option::is_none", default)]
>> -    data: Option<String>,
>> -
>> -    /// Extra delay in seconds to wait before requesting validation.
>> -    ///
>> -    /// Allows to cope with long TTL of DNS records.
>> -    #[serde(skip_serializing_if = "Option::is_none", default)]
>> -    validation_delay: Option<u32>,
>> -
>> -    /// Flag to disable the config.
>> -    #[serde(skip_serializing_if = "Option::is_none", default)]
>> -    disable: Option<bool>,
>> -}
>> -
>> -// See PMG/PVE's $modify_cfg_for_api sub
>> -fn modify_cfg_for_api(id: &str, ty: &str, data: &Value) -> PluginConfig {
>> -    let mut entry = data.clone();
>> -
>> -    let obj = entry.as_object_mut().unwrap();
>> -    obj.remove("id");
>> -    obj.insert("plugin".to_string(), Value::String(id.to_owned()));
>> -    obj.insert("type".to_string(), Value::String(ty.to_owned()));
>> -
>> -    // FIXME: This needs to go once the `Updater` is fixed.
>> -    // None of these should be able to fail unless the user changed the files by hand, in which
>> -    // case we leave the unmodified string in the Value for now. This will be handled with an error
>> -    // later.
>> -    if let Some(Value::String(ref mut data)) = obj.get_mut("data") {
>> -        if let Ok(new) = proxmox_base64::url::decode_no_pad(&data) {
>> -            if let Ok(utf8) = String::from_utf8(new) {
>> -                *data = utf8;
>> -            }
>> -        }
>> -    }
>> -
>> -    // PVE/PMG do this explicitly for ACME plugins...
>> -    // obj.insert("digest".to_string(), Value::String(digest.clone()));
>> -
>> -    serde_json::from_value(entry).unwrap_or_else(|_| PluginConfig {
>> -        plugin: "*Error*".to_string(),
>> -        ty: "*Error*".to_string(),
>> -        ..Default::default()
>> -    })
>> +    proxmox_acme_api::get_cached_challenge_schemas()
>>   }
>>   
>>   #[api(
>> @@ -533,12 +321,7 @@ fn modify_cfg_for_api(id: &str, ty: &str, data: &Value) -> PluginConfig {
>>   )]
>>   /// List ACME challenge plugins.
>>   pub fn list_plugins(rpcenv: &mut dyn RpcEnvironment) -> Result<Vec<PluginConfig>, Error> {
>> -    let (plugins, digest) = plugin::config()?;
>> -    rpcenv["digest"] = hex::encode(digest).into();
>> -    Ok(plugins
>> -        .iter()
>> -        .map(|(id, (ty, data))| modify_cfg_for_api(id, ty, data))
>> -        .collect())
>> +    proxmox_acme_api::list_plugins(rpcenv)
>>   }
>>   
>>   #[api(
>> @@ -555,13 +338,7 @@ pub fn list_plugins(rpcenv: &mut dyn RpcEnvironment) -> Result<Vec<PluginConfig>
>>   )]
>>   /// List ACME challenge plugins.
>>   pub fn get_plugin(id: String, rpcenv: &mut dyn RpcEnvironment) -> Result<PluginConfig, Error> {
>> -    let (plugins, digest) = plugin::config()?;
>> -    rpcenv["digest"] = hex::encode(digest).into();
>> -
>> -    match plugins.get(&id) {
>> -        Some((ty, data)) => Ok(modify_cfg_for_api(&id, ty, data)),
>> -        None => http_bail!(NOT_FOUND, "no such plugin"),
>> -    }
>> +    proxmox_acme_api::get_plugin(id, rpcenv)
>>   }
>>   
>>   // Currently we only have "the" standalone plugin and DNS plugins so we can just flatten a
>> @@ -593,30 +370,7 @@ pub fn get_plugin(id: String, rpcenv: &mut dyn RpcEnvironment) -> Result<PluginC
>>   )]
>>   /// Add ACME plugin configuration.
>>   pub fn add_plugin(r#type: String, core: DnsPluginCore, data: String) -> Result<(), Error> {
>> -    // Currently we only support DNS plugins and the standalone plugin is "fixed":
>> -    if r#type != "dns" {
>> -        param_bail!("type", "invalid ACME plugin type: {:?}", r#type);
>> -    }
>> -
>> -    let data = String::from_utf8(proxmox_base64::decode(data)?)
>> -        .map_err(|_| format_err!("data must be valid UTF-8"))?;
>> -
>> -    let id = core.id.clone();
>> -
>> -    let _lock = plugin::lock()?;
>> -
>> -    let (mut plugins, _digest) = plugin::config()?;
>> -    if plugins.contains_key(&id) {
>> -        param_bail!("id", "ACME plugin ID {:?} already exists", id);
>> -    }
>> -
>> -    let plugin = serde_json::to_value(DnsPlugin { core, data })?;
>> -
>> -    plugins.insert(id, r#type, plugin);
>> -
>> -    plugin::save_config(&plugins)?;
>> -
>> -    Ok(())
>> +    proxmox_acme_api::add_plugin(r#type, core, data)
>>   }
>>   
>>   #[api(
>> @@ -632,26 +386,7 @@ pub fn add_plugin(r#type: String, core: DnsPluginCore, data: String) -> Result<(
>>   )]
>>   /// Delete an ACME plugin configuration.
>>   pub fn delete_plugin(id: String) -> Result<(), Error> {
>> -    let _lock = plugin::lock()?;
>> -
>> -    let (mut plugins, _digest) = plugin::config()?;
>> -    if plugins.remove(&id).is_none() {
>> -        http_bail!(NOT_FOUND, "no such plugin");
>> -    }
>> -    plugin::save_config(&plugins)?;
>> -
>> -    Ok(())
>> -}
>> -
>> -#[api()]
>> -#[derive(Serialize, Deserialize)]
>> -#[serde(rename_all = "kebab-case")]
>> -/// Deletable property name
>> -pub enum DeletableProperty {
>> -    /// Delete the disable property
>> -    Disable,
>> -    /// Delete the validation-delay property
>> -    ValidationDelay,
>> +    proxmox_acme_api::delete_plugin(id)
>>   }
>>   
>>   #[api(
>> @@ -673,12 +408,12 @@ pub enum DeletableProperty {
>>                   type: Array,
>>                   optional: true,
>>                   items: {
>> -                    type: DeletableProperty,
>> +                    type: DeletablePluginProperty,
>>                   }
>>               },
>>               digest: {
>> -                description: "Digest to protect against concurrent updates",
>>                   optional: true,
>> +                type: ConfigDigest,
>>               },
>>           },
>>       },
>> @@ -692,65 +427,8 @@ pub fn update_plugin(
>>       id: String,
>>       update: DnsPluginCoreUpdater,
>>       data: Option<String>,
>> -    delete: Option<Vec<DeletableProperty>>,
>> -    digest: Option<String>,
>> +    delete: Option<Vec<DeletablePluginProperty>>,
>> +    digest: Option<ConfigDigest>,
>>   ) -> Result<(), Error> {
>> -    let data = data
>> -        .as_deref()
>> -        .map(proxmox_base64::decode)
>> -        .transpose()?
>> -        .map(String::from_utf8)
>> -        .transpose()
>> -        .map_err(|_| format_err!("data must be valid UTF-8"))?;
>> -
>> -    let _lock = plugin::lock()?;
>> -
>> -    let (mut plugins, expected_digest) = plugin::config()?;
>> -
>> -    if let Some(digest) = digest {
>> -        let digest = <[u8; 32]>::from_hex(digest)?;
>> -        crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?;
>> -    }
>> -
>> -    match plugins.get_mut(&id) {
>> -        Some((ty, ref mut entry)) => {
>> -            if ty != "dns" {
>> -                bail!("cannot update plugin of type {:?}", ty);
>> -            }
>> -
>> -            let mut plugin = DnsPlugin::deserialize(&*entry)?;
>> -
>> -            if let Some(delete) = delete {
>> -                for delete_prop in delete {
>> -                    match delete_prop {
>> -                        DeletableProperty::ValidationDelay => {
>> -                            plugin.core.validation_delay = None;
>> -                        }
>> -                        DeletableProperty::Disable => {
>> -                            plugin.core.disable = None;
>> -                        }
>> -                    }
>> -                }
>> -            }
>> -            if let Some(data) = data {
>> -                plugin.data = data;
>> -            }
>> -            if let Some(api) = update.api {
>> -                plugin.core.api = api;
>> -            }
>> -            if update.validation_delay.is_some() {
>> -                plugin.core.validation_delay = update.validation_delay;
>> -            }
>> -            if update.disable.is_some() {
>> -                plugin.core.disable = update.disable;
>> -            }
>> -
>> -            *entry = serde_json::to_value(plugin)?;
>> -        }
>> -        None => http_bail!(NOT_FOUND, "no such plugin"),
>> -    }
>> -
>> -    plugin::save_config(&plugins)?;
>> -
>> -    Ok(())
>> +    proxmox_acme_api::update_plugin(id, update, data, delete, digest)
>>   }
>> diff --git a/src/api2/types/acme.rs b/src/api2/types/acme.rs
>> index 64175aff..0ff496b6 100644
>> --- a/src/api2/types/acme.rs
>> +++ b/src/api2/types/acme.rs
>> @@ -43,22 +43,6 @@ pub const ACME_DOMAIN_PROPERTY_SCHEMA: Schema =
>>           .format(&ApiStringFormat::PropertyString(&AcmeDomain::API_SCHEMA))
>>           .schema();
>>   
>> -#[api(
>> -    properties: {
>> -        name: { type: String },
>> -        url: { type: String },
>> -    },
>> -)]
>> -/// An ACME directory endpoint with a name and URL.
>> -#[derive(Serialize)]
>> -pub struct KnownAcmeDirectory {
>> -    /// The ACME directory's name.
>> -    pub name: &'static str,
>> -
>> -    /// The ACME directory's endpoint URL.
>> -    pub url: &'static str,
>> -}
>> -
>>   #[api(
>>       properties: {
>>           schema: {
>> diff --git a/src/bin/proxmox_backup_manager/acme.rs b/src/bin/proxmox_backup_manager/acme.rs
>> index 6ed61560..d11d7498 100644
>> --- a/src/bin/proxmox_backup_manager/acme.rs
>> +++ b/src/bin/proxmox_backup_manager/acme.rs
>> @@ -4,14 +4,12 @@ use anyhow::{bail, Error};
>>   use serde_json::Value;
>>   
>>   use proxmox_acme::async_client::AcmeClient;
>> -use proxmox_acme_api::AcmeAccountName;
>> +use proxmox_acme_api::{AcmeAccountName, DnsPluginCore, KNOWN_ACME_DIRECTORIES};
>>   use proxmox_router::{cli::*, ApiHandler, RpcEnvironment};
>>   use proxmox_schema::api;
>>   use proxmox_sys::fs::file_get_contents;
>>   
>>   use proxmox_backup::api2;
>> -use proxmox_backup::config::acme::plugin::DnsPluginCore;
>> -use proxmox_backup::config::acme::KNOWN_ACME_DIRECTORIES;
>>   
>>   pub fn acme_mgmt_cli() -> CommandLineInterface {
>>       let cmd_def = CliCommandMap::new()
>> @@ -122,7 +120,7 @@ async fn register_account(
>>   
>>                   match input.trim().parse::<usize>() {
>>                       Ok(n) if n < KNOWN_ACME_DIRECTORIES.len() => {
>> -                        break (KNOWN_ACME_DIRECTORIES[n].url.to_owned(), false);
>> +                        break (KNOWN_ACME_DIRECTORIES[n].url.to_string(), false);
>>                       }
>>                       Ok(n) if n == KNOWN_ACME_DIRECTORIES.len() => {
>>                           input.clear();
>> diff --git a/src/config/acme/mod.rs b/src/config/acme/mod.rs
>> index e4639c53..01ab6223 100644
>> --- a/src/config/acme/mod.rs
>> +++ b/src/config/acme/mod.rs
>> @@ -1,16 +1,15 @@
>>   use std::collections::HashMap;
>>   use std::ops::ControlFlow;
>> -use std::path::Path;
>>   
>> -use anyhow::{bail, format_err, Error};
>> +use anyhow::Error;
>>   use serde_json::Value;
>>   
>>   use pbs_api_types::PROXMOX_SAFE_ID_REGEX;
>> -use proxmox_acme_api::AcmeAccountName;
>> +use proxmox_acme_api::{AcmeAccountName, KnownAcmeDirectory, KNOWN_ACME_DIRECTORIES};
>>   use proxmox_sys::error::SysError;
>>   use proxmox_sys::fs::{file_read_string, CreateOptions};
>>   
>> -use crate::api2::types::{AcmeChallengeSchema, KnownAcmeDirectory};
>> +use crate::api2::types::AcmeChallengeSchema;
>>   
>>   pub(crate) const ACME_DIR: &str = pbs_buildcfg::configdir!("/acme");
>>   pub(crate) const ACME_ACCOUNT_DIR: &str = pbs_buildcfg::configdir!("/acme/accounts");
>> @@ -35,23 +34,8 @@ pub(crate) fn make_acme_dir() -> Result<(), Error> {
>>       create_acme_subdir(ACME_DIR)
>>   }
>>   
>> -pub const KNOWN_ACME_DIRECTORIES: &[KnownAcmeDirectory] = &[
>> -    KnownAcmeDirectory {
>> -        name: "Let's Encrypt V2",
>> -        url: "https://acme-v02.api.letsencrypt.org/directory",
>> -    },
>> -    KnownAcmeDirectory {
>> -        name: "Let's Encrypt V2 Staging",
>> -        url: "https://acme-staging-v02.api.letsencrypt.org/directory",
>> -    },
>> -];
>> -
>>   pub const DEFAULT_ACME_DIRECTORY_ENTRY: &KnownAcmeDirectory = &KNOWN_ACME_DIRECTORIES[0];
>>   
>> -pub fn account_path(name: &str) -> String {
>> -    format!("{ACME_ACCOUNT_DIR}/{name}")
>> -}
>> -
>>   pub fn foreach_acme_account<F>(mut func: F) -> Result<(), Error>
>>   where
>>       F: FnMut(AcmeAccountName) -> ControlFlow<Result<(), Error>>,
>> @@ -82,28 +66,6 @@ where
>>       }
>>   }
>>   
>> -pub fn mark_account_deactivated(name: &str) -> Result<(), Error> {
>> -    let from = account_path(name);
>> -    for i in 0..100 {
>> -        let to = account_path(&format!("_deactivated_{name}_{i}"));
>> -        if !Path::new(&to).exists() {
>> -            return std::fs::rename(&from, &to).map_err(|err| {
>> -                format_err!(
>> -                    "failed to move account path {:?} to {:?} - {}",
>> -                    from,
>> -                    to,
>> -                    err
>> -                )
>> -            });
>> -        }
>> -    }
>> -    bail!(
>> -        "No free slot to rename deactivated account {:?}, please cleanup {:?}",
>> -        from,
>> -        ACME_ACCOUNT_DIR
>> -    );
>> -}
>> -
>>   pub fn load_dns_challenge_schema() -> Result<Vec<AcmeChallengeSchema>, Error> {
>>       let raw = file_read_string(ACME_DNS_SCHEMA_FN)?;
>>       let schemas: serde_json::Map<String, Value> = serde_json::from_str(&raw)?;
>> -- 
>> 2.47.3
>>
>>
>>
>> _______________________________________________
>> pbs-devel mailing list
>> pbs-devel@lists.proxmox.com
>> https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel
>>
>>
>>
> 
> 
> _______________________________________________
> pbs-devel mailing list
> pbs-devel@lists.proxmox.com
> https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel
> 
> 



_______________________________________________
pbs-devel mailing list
pbs-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel

  reply	other threads:[~2026-01-13 16:53 UTC|newest]

Thread overview: 23+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-01-08 11:26 [pbs-devel] [PATCH proxmox{, -backup} v5 0/9] fix #6939: acme: support servers returning 204 for nonce requests Samuel Rufinatscha
2026-01-08 11:26 ` [pbs-devel] [PATCH proxmox v5 1/4] acme: reduce visibility of Request type Samuel Rufinatscha
2026-01-13 13:46   ` Fabian Grünbichler
2026-01-08 11:26 ` [pbs-devel] [PATCH proxmox v5 2/4] acme: introduce http_status module Samuel Rufinatscha
2026-01-13 13:45   ` Fabian Grünbichler
2026-01-08 11:26 ` [pbs-devel] [PATCH proxmox v5 3/4] fix #6939: acme: support servers returning 204 for nonce requests Samuel Rufinatscha
2026-01-08 11:26 ` [pbs-devel] [PATCH proxmox v5 4/4] acme-api: add helper to load client for an account Samuel Rufinatscha
2026-01-13 13:45   ` Fabian Grünbichler
2026-01-13 16:57     ` Samuel Rufinatscha
2026-01-08 11:26 ` [pbs-devel] [PATCH proxmox-backup v5 1/5] acme: clean up ACME-related imports Samuel Rufinatscha
2026-01-13 13:45   ` [pbs-devel] applied: " Fabian Grünbichler
2026-01-08 11:26 ` [pbs-devel] [PATCH proxmox-backup v5 2/5] acme: include proxmox-acme-api dependency Samuel Rufinatscha
2026-01-13 13:45   ` Fabian Grünbichler
2026-01-13 16:41     ` Samuel Rufinatscha
2026-01-08 11:26 ` [pbs-devel] [PATCH proxmox-backup v5 3/5] acme: drop local AcmeClient Samuel Rufinatscha
2026-01-13 13:45   ` Fabian Grünbichler
2026-01-08 11:26 ` [pbs-devel] [PATCH proxmox-backup v5 4/5] acme: change API impls to use proxmox-acme-api handlers Samuel Rufinatscha
2026-01-13 13:45   ` Fabian Grünbichler
2026-01-13 16:53     ` Samuel Rufinatscha [this message]
2026-01-08 11:26 ` [pbs-devel] [PATCH proxmox-backup v5 5/5] acme: certificate ordering through proxmox-acme-api Samuel Rufinatscha
2026-01-13 13:45   ` Fabian Grünbichler
2026-01-13 16:51     ` Samuel Rufinatscha
2026-01-13 13:48 ` [pbs-devel] [PATCH proxmox{, -backup} v5 0/9] fix #6939: acme: support servers returning 204 for nonce requests Fabian Grünbichler

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=6a1c96d8-6d6a-4f70-8fd6-5acb99a12942@proxmox.com \
    --to=s.rufinatscha@proxmox.com \
    --cc=f.gruenbichler@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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal