* [pdm-devel] [PATCH datacenter-manager 0/3] fix #7179: expose ACME commands inside admin CLI
@ 2026-01-23 17:29 Shan Shaji
2026-01-23 17:29 ` [pdm-devel] [PATCH datacenter-manager 1/3] cli: admin: make cli handling async Shan Shaji
` (2 more replies)
0 siblings, 3 replies; 4+ messages in thread
From: Shan Shaji @ 2026-01-23 17:29 UTC (permalink / raw)
To: pdm-devel
Previously, ACME commands were not exposed through the admin CLI.
Added the necessary functionality to manage ACME settings directly
via the command line. The changes are done by taking reference from
the proxmox-backup codebase.
Since i am using the completion handlers from the proxmox-acme-api
crate the following patch [1] needs to be applied before testing or merging
the changes of this patch series.
**note**: Sending this series only for an initial review as the completions
are not working and also to know if i have missed anything :).
Testing
=======
In general i have verified the following commands ie,
- account (deactivate, info, list, update)
- certificate (order, revoke)
- plugin (add, config, list, remove, set)
### Certifcate Creation
http-01 challenge:
-----------------
I have tested the http-01 challenge verification using a test
pebble server.
Steps followed to test the changes:
1. Installed the changes inside a PDM VM.
2. install Pebble from Let's Encrypt [2] on the same VM:
cd
apt update
apt install -y golang git
git clone https://github.com/letsencrypt/pebble
cd pebble
go build ./cmd/pebble
then, download and trust the Pebble cert:
wget https://raw.githubusercontent.com/letsencrypt/pebble/main/test/certs/pebble.minica.pem
cp pebble.minica.pem /usr/local/share/ca-certificates/pebble.minica.crt
update-ca-certificates
We want Pebble to perform HTTP-01 validation against port 80, because
PDM's standalone plugin will bind port 80. Set httpPort to 80.
nano ./test/config/pebble-config.json
Start the Pebble server in the background:
./pebble -config ./test/config/pebble-config.json &
Created a Pebble ACME account:
proxmox-datacenter-manager-admin acme account register default admin@example.com --directory 'https://127.0.0.1:14000/dir'
Added a new ACME domain pdm.proxmox.com with HTTP challenge type. Then
ran the following command.
proxmox-datacenter-manager admin acme certificate order --force true
Checked if the certificate is validated by the pebble CA.
Ran the revoke command and verified if the certificate is self-signed
after force refresh.
---
DNS-01 challenge:
----------------
I tested the changes with my domain using the cloudflare plugin.
Steps followed to test the changes:
1. Created an ACME account using let's encrypt staging API.
2. Add a new plugin using the following command
proxmox-datacenter-manager-admin acme plugin add dns cloudflare --api cf --data ./cf_tokens
cf_tokens had the following credentials:
- CF_Account_ID=""
- CF_Token=""
3. Added my cloudflare managed domain under ACME Domains using the UI.
4. Ordered the certificate using the following command.
proxmox-datacenter-manager-admin acme certificate order --force true
5. Force refreshed the browser and verified that the new certificate is
verified by (STAGING) Let's Encrypt
6. Revoked the certificate using the following command.
proxmox-datacenter-manager-admin acme certificate revoke
7. Verified the new certificate is self-signed.
[1] - https://lore.proxmox.com/pbs-devel/20260116112859.194016-2-s.rufinatscha@proxmox.com/
[2] - https://github.com/letsencrypt/pebble
Shan Shaji (3):
cli: admin: make cli handling async
fix #7179: cli: admin: expose acme commands
chore: update proxmox-acme to version 1
Cargo.toml | 2 +-
cli/admin/Cargo.toml | 7 +-
cli/admin/src/acme.rs | 442 ++++++++++++++++++++++++++++++++++++++++++
cli/admin/src/main.rs | 54 ++++--
4 files changed, 484 insertions(+), 21 deletions(-)
create mode 100644 cli/admin/src/acme.rs
--
2.47.3
_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel
^ permalink raw reply [flat|nested] 4+ messages in thread
* [pdm-devel] [PATCH datacenter-manager 1/3] cli: admin: make cli handling async
2026-01-23 17:29 [pdm-devel] [PATCH datacenter-manager 0/3] fix #7179: expose ACME commands inside admin CLI Shan Shaji
@ 2026-01-23 17:29 ` Shan Shaji
2026-01-23 17:29 ` [pdm-devel] [PATCH datacenter-manager 2/3] fix #7179: cli: admin: expose acme commands Shan Shaji
2026-01-23 17:29 ` [pdm-devel] [PATCH datacenter-manager 3/3] chore: update proxmox-acme to version 1 Shan Shaji
2 siblings, 0 replies; 4+ messages in thread
From: Shan Shaji @ 2026-01-23 17:29 UTC (permalink / raw)
To: pdm-devel
The acme API methods internally create workers to process API requests.
An error was thrown if the methods invoked without initializing the
worker tasks.
Since `init_worker_tasks` needs to be called from an async runtime.
Moved the content of the main function to async `run` function and
wrapped using `proxmox_async::runtime::main`, which creates a Tokio
runtime.
Signed-off-by: Shan Shaji <s.shaji@proxmox.com>
---
cli/admin/Cargo.toml | 3 +++
cli/admin/src/main.rs | 51 ++++++++++++++++++++++++++-----------------
2 files changed, 34 insertions(+), 20 deletions(-)
diff --git a/cli/admin/Cargo.toml b/cli/admin/Cargo.toml
index 0dec423..e566b39 100644
--- a/cli/admin/Cargo.toml
+++ b/cli/admin/Cargo.toml
@@ -19,6 +19,9 @@ proxmox-product-config.workspace = true
proxmox-router = { workspace = true, features = [ "cli" ], default-features = false }
proxmox-schema = { workspace = true, features = [ "api-macro" ] }
proxmox-access-control.workspace = true
+proxmox-rest-server.workspace = true
+proxmox-sys.workspace = true
+proxmox-daemon.workspace = true
pdm-api-types.workspace = true
pdm-config.workspace = true
diff --git a/cli/admin/src/main.rs b/cli/admin/src/main.rs
index f698fa2..bcaa0a6 100644
--- a/cli/admin/src/main.rs
+++ b/cli/admin/src/main.rs
@@ -1,35 +1,31 @@
+use anyhow::Error;
use serde_json::{json, Value};
use proxmox_router::cli::{
- default_table_format_options, format_and_print_result_full, get_output_format, run_cli_command,
- CliCommand, CliCommandMap, CliEnvironment, ColumnConfig, OUTPUT_FORMAT,
+ default_table_format_options, format_and_print_result_full, get_output_format,
+ run_async_cli_command, CliCommand, CliCommandMap, CliEnvironment, ColumnConfig, OUTPUT_FORMAT,
};
use proxmox_router::RpcEnvironment;
-
use proxmox_schema::api;
+use proxmox_sys::fs::CreateOptions;
mod remotes;
mod support_status;
-fn main() {
- //pbs_tools::setup_libc_malloc_opts(); // TODO: move from PBS to proxmox-sys and uncomment
-
- let api_user = pdm_config::api_user().expect("cannot get api user");
- let priv_user = pdm_config::priv_user().expect("cannot get privileged user");
- proxmox_product_config::init(api_user, priv_user);
+async fn run() -> Result<(), Error> {
+ let api_user = pdm_config::api_user()?;
+ let priv_user = pdm_config::priv_user()?;
+ proxmox_product_config::init(api_user.clone(), priv_user);
proxmox_access_control::init::init(
&pdm_api_types::AccessControlConfig,
pdm_buildcfg::configdir!("/access"),
- )
- .expect("failed to setup access control config");
-
+ )?;
proxmox_log::Logger::from_env("PDM_LOG", proxmox_log::LevelFilter::INFO)
.stderr()
- .init()
- .expect("failed to set up logger");
+ .init()?;
- server::context::init().expect("could not set up server context");
+ server::context::init()?;
let cmd_def = CliCommandMap::new()
.insert("remote", remotes::cli())
@@ -40,14 +36,29 @@ fn main() {
.insert("support-status", support_status::cli())
.insert("versions", CliCommand::new(&API_METHOD_GET_VERSIONS));
+ let args: Vec<String> = std::env::args().collect();
+ let avoid_init = args.len() >= 2 && (args[1] == "bashcomplete" || args[1] == "printdoc");
+
+ if !avoid_init {
+ let file_opts = CreateOptions::new().owner(api_user.uid).group(api_user.gid);
+ proxmox_rest_server::init_worker_tasks(pdm_buildcfg::PDM_LOG_DIR_M!().into(), file_opts)?;
+
+ let mut command_sock = proxmox_daemon::command_socket::CommandSocket::new(api_user.gid);
+ proxmox_rest_server::register_task_control_commands(&mut command_sock)?;
+ command_sock.spawn(proxmox_rest_server::last_worker_future())?;
+ }
+
let mut rpcenv = CliEnvironment::new();
rpcenv.set_auth_id(Some("root@pam".into()));
- run_cli_command(
- cmd_def,
- rpcenv,
- Some(|future| proxmox_async::runtime::main(future)),
- );
+ run_async_cli_command(cmd_def, rpcenv).await;
+
+ Ok(())
+}
+
+fn main() -> Result<(), Error> {
+ //pbs_tools::setup_libc_malloc_opts(); // TODO: move from PBS to proxmox-sys and uncomment
+ proxmox_async::runtime::main(run())
}
#[api(
--
2.47.3
_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel
^ permalink raw reply [flat|nested] 4+ messages in thread
* [pdm-devel] [PATCH datacenter-manager 2/3] fix #7179: cli: admin: expose acme commands
2026-01-23 17:29 [pdm-devel] [PATCH datacenter-manager 0/3] fix #7179: expose ACME commands inside admin CLI Shan Shaji
2026-01-23 17:29 ` [pdm-devel] [PATCH datacenter-manager 1/3] cli: admin: make cli handling async Shan Shaji
@ 2026-01-23 17:29 ` Shan Shaji
2026-01-23 17:29 ` [pdm-devel] [PATCH datacenter-manager 3/3] chore: update proxmox-acme to version 1 Shan Shaji
2 siblings, 0 replies; 4+ messages in thread
From: Shan Shaji @ 2026-01-23 17:29 UTC (permalink / raw)
To: pdm-devel
Previously, ACME commands were not exposed through the admin CLI.
Added the necessary functionality to manage ACME settings directly
via the command line.
Signed-off-by: Shan Shaji <s.shaji@proxmox.com>
---
cli/admin/Cargo.toml | 4 +-
cli/admin/src/acme.rs | 442 ++++++++++++++++++++++++++++++++++++++++++
cli/admin/src/main.rs | 5 +
3 files changed, 450 insertions(+), 1 deletion(-)
create mode 100644 cli/admin/src/acme.rs
diff --git a/cli/admin/Cargo.toml b/cli/admin/Cargo.toml
index e566b39..01afc88 100644
--- a/cli/admin/Cargo.toml
+++ b/cli/admin/Cargo.toml
@@ -22,7 +22,9 @@ proxmox-access-control.workspace = true
proxmox-rest-server.workspace = true
proxmox-sys.workspace = true
proxmox-daemon.workspace = true
-
+proxmox-acme.workspace = true
+proxmox-acme-api.workspace = true
+proxmox-base64.workspace = true
pdm-api-types.workspace = true
pdm-config.workspace = true
pdm-buildcfg.workspace = true
diff --git a/cli/admin/src/acme.rs b/cli/admin/src/acme.rs
new file mode 100644
index 0000000..81c63f9
--- /dev/null
+++ b/cli/admin/src/acme.rs
@@ -0,0 +1,442 @@
+use std::io::Write;
+
+use anyhow::{bail, Error};
+use serde_json::Value;
+
+use proxmox_acme::async_client::AcmeClient;
+use proxmox_acme_api::{completion::*, AcmeAccountName, DnsPluginCore, KNOWN_ACME_DIRECTORIES};
+use proxmox_rest_server::wait_for_local_worker;
+use proxmox_router::{cli::*, ApiHandler, RpcEnvironment};
+use proxmox_schema::api;
+use proxmox_sys::fs::file_get_contents;
+
+use server::api as dc_api;
+
+pub fn acme_mgmt_cli() -> CommandLineInterface {
+ let cmd_def = CliCommandMap::new()
+ .insert("account", account_cli())
+ .insert("plugin", plugin_cli())
+ .insert("certificate", cert_cli());
+
+ cmd_def.into()
+}
+
+#[api(
+ input: {
+ properties: {
+ "output-format": {
+ schema: OUTPUT_FORMAT,
+ optional: true,
+ }
+ }
+ }
+)]
+/// List ACME accounts.
+fn list_accounts(param: Value, rpcenv: &mut dyn RpcEnvironment) -> Result<(), Error> {
+ let output_format = get_output_format(¶m);
+ let info = &dc_api::config::acme::API_METHOD_LIST_ACCOUNTS;
+ let mut data = match info.handler {
+ ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
+ _ => unreachable!(),
+ };
+
+ let options = default_table_format_options();
+ format_and_print_result_full(&mut data, &info.returns, &output_format, &options);
+
+ Ok(())
+}
+
+#[api(
+ input: {
+ properties: {
+ name: { type: AcmeAccountName },
+ "output-format": {
+ schema: OUTPUT_FORMAT,
+ optional: true,
+ },
+ }
+ }
+)]
+/// Show ACME account information.
+async fn get_account(param: Value, rpcenv: &mut dyn RpcEnvironment) -> Result<(), Error> {
+ let output_format = get_output_format(¶m);
+
+ let info = &dc_api::config::acme::API_METHOD_GET_ACCOUNT;
+ let mut data = match info.handler {
+ ApiHandler::Async(handler) => (handler)(param, info, rpcenv).await?,
+ _ => unreachable!(),
+ };
+
+ let options = default_table_format_options()
+ .column(
+ ColumnConfig::new("account")
+ .renderer(|value, _record| Ok(serde_json::to_string_pretty(value)?)),
+ )
+ .column(ColumnConfig::new("directory"))
+ .column(ColumnConfig::new("location"))
+ .column(ColumnConfig::new("tos"));
+ format_and_print_result_full(&mut data, &info.returns, &output_format, &options);
+
+ Ok(())
+}
+
+#[api(
+ input: {
+ properties: {
+ name: { type: AcmeAccountName },
+ contact: {
+ description: "List of email addresses.",
+ },
+ directory: {
+ type: String,
+ description: "The ACME Directory.",
+ optional: true, },
+ }
+ }
+)]
+///Register an ACME account.
+async fn register_account(
+ name: AcmeAccountName,
+ contact: String,
+ directory: Option<String>,
+) -> Result<(), Error> {
+ let (directory_url, custom_directory) = match directory {
+ Some(directory) => (directory, true),
+ None => {
+ println!("Directory endpoints:");
+ for (i, dir) in KNOWN_ACME_DIRECTORIES.iter().enumerate() {
+ println!("{}) {}", i, dir.url);
+ }
+
+ println!("{}) Custom", KNOWN_ACME_DIRECTORIES.len());
+ let mut attempt = 0;
+ loop {
+ print!("Enter selection: ");
+ std::io::stdout().flush()?;
+
+ let mut input = String::new();
+ std::io::stdin().read_line(&mut input)?;
+
+ match input.trim().parse::<usize>() {
+ Ok(n) if n < KNOWN_ACME_DIRECTORIES.len() => {
+ break (KNOWN_ACME_DIRECTORIES[n].url.to_string(), false);
+ }
+ Ok(n) if n == KNOWN_ACME_DIRECTORIES.len() => {
+ input.clear();
+ print!("Enter custom directory URI: ");
+ std::io::stdout().flush()?;
+ std::io::stdin().read_line(&mut input)?;
+ break (input.trim().to_owned(), true);
+ }
+ _ => eprintln!("Invalid selection."),
+ }
+
+ attempt += 1;
+ if attempt >= 3 {
+ bail!("Aborting.");
+ }
+ }
+ }
+ };
+
+ println!("Attempting to fetch Terms of Service from {directory_url:?}");
+ let mut client = AcmeClient::new(directory_url.clone());
+ let directory = client.directory().await?;
+ let tos_agreed = if let Some(tos_url) = directory.terms_of_service_url() {
+ println!("Terms of Service: {tos_url}");
+ print!("Do you agree to the above terms? [y|N]: ");
+ std::io::stdout().flush()?;
+ let mut input = String::new();
+ std::io::stdin().read_line(&mut input)?;
+ input.trim().eq_ignore_ascii_case("y")
+ } else {
+ println!("No Terms of Service found, proceeding.");
+ true
+ };
+
+ let mut eab_enabled = directory.external_account_binding_required();
+ if !eab_enabled && custom_directory {
+ print!("Do you want to use external account binding? [y|N]: ");
+ std::io::stdout().flush()?;
+ let mut input = String::new();
+ std::io::stdin().read_line(&mut input)?;
+ eab_enabled = input.trim().eq_ignore_ascii_case("y");
+ } else if eab_enabled {
+ println!("The CA requires external account binding.");
+ }
+
+ let eab_creds = if eab_enabled {
+ println!("You should have received a key id and a key from your CA.");
+
+ print!("Enter EAB key id: ");
+ std::io::stdout().flush()?;
+ let mut eab_kid = String::new();
+ std::io::stdin().read_line(&mut eab_kid)?;
+
+ print!("Enter EAB key: ");
+ std::io::stdout().flush()?;
+ let mut eab_hmac_key = String::new();
+ std::io::stdin().read_line(&mut eab_hmac_key)?;
+
+ Some((eab_kid.trim().to_owned(), eab_hmac_key.trim().to_owned()))
+ } else {
+ None
+ };
+
+ let tos_url = tos_agreed
+ .then(|| directory.terms_of_service_url().map(str::to_owned))
+ .flatten();
+
+ println!("Registering ACME account '{}'...", &name,);
+ let location =
+ proxmox_acme_api::register_account(&name, contact, tos_url, Some(directory_url), eab_creds)
+ .await?;
+ println!("Registration successful, account URL: {}", location);
+
+ Ok(())
+}
+
+#[api(
+ input: {
+ properties: {
+ name: { type: AcmeAccountName },
+ contact: {
+ description: "List of email addresses.",
+ type: String,
+ optional: true,
+ }
+ }
+ }
+)]
+/// Update an ACME Account.
+async fn update_account(param: Value, rpcenv: &mut dyn RpcEnvironment) -> Result<(), Error> {
+ let info = &dc_api::config::acme::API_METHOD_UPDATE_ACCOUNT;
+ let result = match info.handler {
+ ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
+ _ => unreachable!(),
+ };
+
+ wait_for_local_worker(result.as_str().unwrap()).await?;
+
+ Ok(())
+}
+
+#[api(
+ input: {
+ properties: {
+ name: { type: AcmeAccountName },
+ force: {
+ description: "Delete account data even if the server refuses to deactivate the account.",
+ type: Boolean,
+ optional: true,
+ default: true,
+ }
+ }
+ }
+)]
+/// Deactivate an ACME account.
+async fn deactivate_account(param: Value, rpcenv: &mut dyn RpcEnvironment) -> Result<(), Error> {
+ let info = &dc_api::config::acme::API_METHOD_DEACTIVATE_ACCOUNT;
+ let result = match info.handler {
+ ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
+ _ => unreachable!(),
+ };
+
+ wait_for_local_worker(result.as_str().unwrap()).await?;
+
+ Ok(())
+}
+
+fn account_cli() -> CommandLineInterface {
+ let cmd_def = CliCommandMap::new()
+ .insert("list", CliCommand::new(&API_METHOD_LIST_ACCOUNTS))
+ .insert(
+ "register",
+ CliCommand::new(&API_METHOD_REGISTER_ACCOUNT).arg_param(&["name", "contact"]),
+ )
+ .insert(
+ "deactivate",
+ CliCommand::new(&API_METHOD_DEACTIVATE_ACCOUNT)
+ .arg_param(&["name"])
+ .completion_cb("name", complete_acme_account),
+ )
+ .insert(
+ "info",
+ CliCommand::new(&API_METHOD_GET_ACCOUNT)
+ .arg_param(&["name"])
+ .completion_cb("name", complete_acme_account),
+ )
+ .insert(
+ "update",
+ CliCommand::new(&API_METHOD_UPDATE_ACCOUNT)
+ .arg_param(&["name", "contact"])
+ .completion_cb("name", complete_acme_account),
+ );
+
+ cmd_def.into()
+}
+
+#[api(
+ input: {
+ properties: {
+ "output-format": {
+ schema: OUTPUT_FORMAT,
+ optional: true,
+ },
+ }
+ }
+)]
+/// List ACME plugins.
+fn list_plugins(param: Value, rpcenv: &mut dyn RpcEnvironment) -> Result<(), Error> {
+ let output_format = get_output_format(¶m);
+
+ let info = &dc_api::config::acme::API_METHOD_LIST_PLUGINS;
+ let mut data = match info.handler {
+ ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
+ _ => unreachable!(),
+ };
+
+ let options = default_table_format_options();
+ format_and_print_result_full(&mut data, &info.returns, &output_format, &options);
+
+ Ok(())
+}
+
+#[api(
+ input: {
+ properties: {
+ id: {
+ type: String,
+ description: "Plugin ID",
+ },
+ "output-format": {
+ schema: OUTPUT_FORMAT,
+ optional: true,
+ },
+ }
+ }
+)]
+/// Show ACME plugin information.
+fn get_plugin(param: Value, rpcenv: &mut dyn RpcEnvironment) -> Result<(), Error> {
+ let output_format = get_output_format(¶m);
+
+ let info = &dc_api::config::acme::API_METHOD_GET_PLUGIN;
+ let mut data = match info.handler {
+ ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
+ _ => unreachable!(),
+ };
+
+ let options = default_table_format_options();
+ format_and_print_result_full(&mut data, &info.returns, &output_format, &options);
+
+ Ok(())
+}
+
+#[api(input: {
+ properties: {
+ type: {
+ type: String,
+ description: "The ACME challenge plugin type."
+ },
+ core: {
+ type: DnsPluginCore,
+ flatten: true,
+ },
+ data: {
+ type: String,
+ description: "File containing the plugin data."
+ }
+ }
+})]
+/// Add ACME plugin configuration.
+fn add_plugin(r#type: String, core: DnsPluginCore, data: String) -> Result<(), Error> {
+ let data = proxmox_base64::encode(file_get_contents(data)?);
+ dc_api::config::acme::add_plugin(r#type, core, data)?;
+ Ok(())
+}
+
+pub fn plugin_cli() -> CommandLineInterface {
+ let cmd_def = CliCommandMap::new()
+ .insert("list", CliCommand::new(&API_METHOD_LIST_PLUGINS))
+ .insert(
+ "config",
+ CliCommand::new(&API_METHOD_GET_PLUGIN)
+ .arg_param(&["id"])
+ .completion_cb("id", complete_acme_plugin),
+ )
+ .insert(
+ "add",
+ CliCommand::new(&API_METHOD_ADD_PLUGIN)
+ .arg_param(&["type", "id"])
+ .completion_cb("api", complete_acme_api_challenge_type)
+ .completion_cb("type", complete_acme_plugin_type),
+ )
+ .insert(
+ "remove",
+ CliCommand::new(&dc_api::config::acme::API_METHOD_DELETE_PLUGIN)
+ .arg_param(&["id"])
+ .completion_cb("id", complete_acme_plugin),
+ )
+ .insert(
+ "set",
+ CliCommand::new(&dc_api::config::acme::API_METHOD_UPDATE_PLUGIN)
+ .arg_param(&["id"])
+ .completion_cb("id", complete_acme_plugin),
+ );
+
+ cmd_def.into()
+}
+
+#[api(
+ input: {
+ properties: {
+ force: {
+ description: "Force renewal even if the certificate does not expire soon.",
+ type: Boolean,
+ optional: true,
+ default: false,
+ },
+ },
+ },
+)]
+/// Order a new ACME certificate.
+async fn order_acme_cert(param: Value, rpcenv: &mut dyn RpcEnvironment) -> Result<(), Error> {
+ if !param["force"].as_bool().unwrap_or(false)
+ && !dc_api::nodes::certificates::cert_expires_soon()?
+ {
+ println!("Certificate does not expire within the next 30 days, not renewing.");
+ return Ok(());
+ }
+
+ let info = &dc_api::nodes::certificates::API_METHOD_RENEW_ACME_CERT;
+ let result = match info.handler {
+ ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
+ _ => unreachable!(),
+ };
+
+ wait_for_local_worker(result.as_str().unwrap()).await?;
+
+ Ok(())
+}
+
+#[api]
+/// Revoke ACME certificate.
+async fn revoke_acme_cert(param: Value, rpcenv: &mut dyn RpcEnvironment) -> Result<(), Error> {
+ let info = &dc_api::nodes::certificates::API_METHOD_REVOKE_ACME_CERT;
+ let result = match info.handler {
+ ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
+ _ => unreachable!(),
+ };
+
+ wait_for_local_worker(result.as_str().unwrap()).await?;
+
+ Ok(())
+}
+
+pub fn cert_cli() -> CommandLineInterface {
+ let cmd_def = CliCommandMap::new()
+ .insert("order", CliCommand::new(&API_METHOD_ORDER_ACME_CERT))
+ .insert("revoke", CliCommand::new(&API_METHOD_REVOKE_ACME_CERT));
+
+ cmd_def.into()
+}
diff --git a/cli/admin/src/main.rs b/cli/admin/src/main.rs
index bcaa0a6..15114c9 100644
--- a/cli/admin/src/main.rs
+++ b/cli/admin/src/main.rs
@@ -9,6 +9,7 @@ use proxmox_router::RpcEnvironment;
use proxmox_schema::api;
use proxmox_sys::fs::CreateOptions;
+mod acme;
mod remotes;
mod support_status;
@@ -21,13 +22,17 @@ async fn run() -> Result<(), Error> {
&pdm_api_types::AccessControlConfig,
pdm_buildcfg::configdir!("/access"),
)?;
+ proxmox_acme_api::init(pdm_buildcfg::configdir!("/acme"), false)?;
+
proxmox_log::Logger::from_env("PDM_LOG", proxmox_log::LevelFilter::INFO)
+ .stderr_on_no_workertask()
.stderr()
.init()?;
server::context::init()?;
let cmd_def = CliCommandMap::new()
+ .insert("acme", acme::acme_mgmt_cli())
.insert("remote", remotes::cli())
.insert(
"report",
--
2.47.3
_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel
^ permalink raw reply [flat|nested] 4+ messages in thread
* [pdm-devel] [PATCH datacenter-manager 3/3] chore: update proxmox-acme to version 1
2026-01-23 17:29 [pdm-devel] [PATCH datacenter-manager 0/3] fix #7179: expose ACME commands inside admin CLI Shan Shaji
2026-01-23 17:29 ` [pdm-devel] [PATCH datacenter-manager 1/3] cli: admin: make cli handling async Shan Shaji
2026-01-23 17:29 ` [pdm-devel] [PATCH datacenter-manager 2/3] fix #7179: cli: admin: expose acme commands Shan Shaji
@ 2026-01-23 17:29 ` Shan Shaji
2 siblings, 0 replies; 4+ messages in thread
From: Shan Shaji @ 2026-01-23 17:29 UTC (permalink / raw)
To: pdm-devel
Signed-off-by: Shan Shaji <s.shaji@proxmox.com>
---
Cargo.toml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Cargo.toml b/Cargo.toml
index ed9ce60..5c2de75 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -68,7 +68,7 @@ proxmox-upgrade-checks = "1"
proxmox-uuid = "1"
# other proxmox crates
-proxmox-acme = "0.5"
+proxmox-acme = "1.0"
proxmox-openid = "1.0.2"
# api implementation creates
--
2.47.3
_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-01-23 17:29 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-01-23 17:29 [pdm-devel] [PATCH datacenter-manager 0/3] fix #7179: expose ACME commands inside admin CLI Shan Shaji
2026-01-23 17:29 ` [pdm-devel] [PATCH datacenter-manager 1/3] cli: admin: make cli handling async Shan Shaji
2026-01-23 17:29 ` [pdm-devel] [PATCH datacenter-manager 2/3] fix #7179: cli: admin: expose acme commands Shan Shaji
2026-01-23 17:29 ` [pdm-devel] [PATCH datacenter-manager 3/3] chore: update proxmox-acme to version 1 Shan Shaji
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.