From: "Shan Shaji" <s.shaji@proxmox.com>
To: "Lukas Wagner" <l.wagner@proxmox.com>, <pdm-devel@lists.proxmox.com>
Subject: Re: [PATCH datacenter-manager v2 3/4] fix #7179: cli: admin: expose acme commands
Date: Tue, 10 Feb 2026 11:41:08 +0100 [thread overview]
Message-ID: <DGB7VLCAMTDY.1T5MPKZ7F77RY@proxmox.com> (raw)
In-Reply-To: <DG73J5IF5S12.X4TR856Q2V7P@proxmox.com>
Hi Lukas, thank you so much for the nice improvement feedbacks.
Some comments inline.
On Thu Feb 5, 2026 at 3:26 PM CET, Lukas Wagner wrote:
> Hi Shan,
>
> thanks for this updated version of your patch series. Looking good from
> what I can, some comments inline.
>
> On Tue Feb 3, 2026 at 6:51 PM CET, Shan Shaji wrote:
>> 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>
>> ---
>>
[...]
>> +///Register an ACME account.
>> +async fn register_account(
>> + name: AcmeAccountName,
>> + contact: String,
>> + directory: Option<String>,
>> + rpcenv: &mut dyn RpcEnvironment,
>> +) -> 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 {
>> + let mut input = read_input("Enter selection")?;
>> + 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();
>> + input = read_input("Enter custom directory URI")?;
>> + 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}");
>> + let input = read_input("Do you agree to the above terms? [y|N]")?;
>> + 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 {
>> + let input = read_input("Do you want to use external account binding? [y|N]")?;
>> + 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.");
>> + let eab_kid = read_input("Enter EAB key id")?;
>> + let eab_hmac_key = read_input("Enter EAB 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();
>> +
>> + let (eab_kid, eab_hmac_key) = eab_creds.unzip();
>> + let parameters = AcmeRegistrationParams {
>> + name: Some(name),
>> + contact: contact,
>> + tos_url: tos_url,
>> + directory: Some(directory_url),
>> + eab_kid: eab_kid,
>> + eab_hmac_key: eab_hmac_key,
>> + };
>> + let param = serde_json::to_value(parameters)?;
>> +
>> + let info = &dc_api::config::acme::API_METHOD_REGISTER_ACCOUNT;
>> + let result = match info.handler {
>> + ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
>> + _ => unreachable!(),
>> + };
>> +
>> + wait_for_local_worker(result.as_str().unwrap()).await?;
>> +
>> + Ok(())
>> +}
>
> Two things came to mind when actually trying out this CLI:
>
> - The directory selection is a bit odd to use, since it uses 0-based
> indexing, something like
> 0.) Let's Encrypt
> 1.) Let's Encrypt Staging
> ....
>
> 1-based indexing probably is a bit nicer for users here.
I will add this in v3. Thanks!
> - Also, I wonder if there should be non-interactive version of this
> command as well, one that can be scripted, one where all parameters
> that are asked as parameters can be provided as flags.
> People can always use the API for this, but maybe it would still be
> nice to offer this in the CLI tool as well.
> Just thinking out loud, I'm aware that you just copied the approach
> from PBS, if we add something like this, this should be done in a
> separate series.
This is a nice improvement. We already have an option to provide custom
directory endpoint using --directory option. I will add additional options
to accept the TOS selection and EAB credentials.
Do we need to provide a flag to choose the Lets encrypt endpoint
as we already have a --directory option?
> - Might be worth moving this 'wizard' implementation to some helper
> module in proxmox-acme-api and then use the same implementation from
> both PDM and PBS. To avoid scope creep this can always be done in a
> follow-up series.
>
> I guess in this case the appropriate place for the new
> AcmeRegistrationParams is then also proxmox-acme-api, since the this
> would be the type that is returned from such a shared helper.
>
> (sorry, some of these I could have noticed in my earlier, less
> thorough review)
>
> All of these can be done in followup-patches, it's just some ideas for
> improvement over the status quo that we already have in PBS.
Thanks! I will move the wizard implementation to the proxmox-acme-api
crate and use the same on both PBS and PDM. Will send another series
once this series is applied.
>> +
>> +#[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(())
>> +}
>> +
>
> [...]
>
>> +
>> +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 02148e3..3fd3c2f 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;
>>
>> @@ -22,7 +23,11 @@ async fn run() -> Result<(), Error> {
>> pdm_buildcfg::configdir!("/access"),
>> )
>> .context("failed to setup access control config")?;
>> + proxmox_acme_api::init(pdm_buildcfg::configdir!("/acme"), false)
>> + .context("failed to initialize acme config")?;
>> +
>> proxmox_log::Logger::from_env("PDM_LOG", proxmox_log::LevelFilter::INFO)
>> + .tasklog()
>
> I'd use the 'old' tasklog_pbs function here for now, since then we can
> apply these patches without bumping proxmox-log first. Changing this to
> `tasklog` is trivial and can be done in a followup patch.
>
> We should be getting a deprecation warning when building PDM once
> proxmox-log has been bumped, so there should be little chance to forget
> it.
>
Will update it to `tasklog_pbs`. Thank You!
>> .stderr()
>> .init()
>> .context("failed to set-up logger")?;
>> @@ -30,6 +35,7 @@ async fn run() -> Result<(), Error> {
>> server::context::init().context("could not set-up server context")?;
>>
>> let cmd_def = CliCommandMap::new()
>> + .insert("acme", acme::acme_mgmt_cli())
>> .insert("remote", remotes::cli())
>> .insert(
>> "report",
next prev parent reply other threads:[~2026-02-10 10:40 UTC|newest]
Thread overview: 12+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-02-03 17:50 [PATCH datacenter-manager v2 0/4] fix #7179: expose ACME commands inside admin CLI Shan Shaji
2026-02-03 17:50 ` [PATCH datacenter-manager v2 1/4] cli: admin: make cli handling async Shan Shaji
2026-02-05 14:25 ` Lukas Wagner
2026-02-06 12:56 ` Shan Shaji
2026-02-03 17:50 ` [PATCH datacenter-manager v1 2/4] api: acme: define API type for ACME registration parameters Shan Shaji
2026-02-05 14:25 ` Lukas Wagner
2026-02-03 17:51 ` [PATCH datacenter-manager v2 3/4] fix #7179: cli: admin: expose acme commands Shan Shaji
2026-02-05 14:26 ` Lukas Wagner
2026-02-10 10:41 ` Shan Shaji [this message]
2026-02-10 16:36 ` Shan Shaji
2026-02-03 17:51 ` [PATCH datacenter-manager v2 4/4] chore: update proxmox-acme version to 1 Shan Shaji
2026-02-05 14:25 ` [PATCH datacenter-manager v2 0/4] fix #7179: expose ACME commands inside admin CLI Lukas Wagner
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=DGB7VLCAMTDY.1T5MPKZ7F77RY@proxmox.com \
--to=s.shaji@proxmox.com \
--cc=l.wagner@proxmox.com \
--cc=pdm-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