use std::io;
use clap::Parser;
use tracing::info;
use instant_acme::{
Account, AuthorizationStatus, ChallengeType, Identifier, LetsEncrypt, NewAccount, NewOrder,
OrderStatus, RetryPolicy,
};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
tracing_subscriber::fmt::init();
let opts = Options::parse();
let (account, credentials) = Account::builder()?
.create(
&NewAccount {
contact: &[],
terms_of_service_agreed: true,
only_return_existing: false,
},
LetsEncrypt::Staging.url().to_owned(),
None,
)
.await?;
info!(
"account credentials:\n\n{}",
serde_json::to_string_pretty(&credentials)?
);
let identifiers = opts
.names
.iter()
.map(|ident| Identifier::Dns(ident.clone()))
.collect::<Vec<_>>();
let mut order = account
.new_order(&NewOrder::new(identifiers.as_slice()))
.await?;
let state = order.state();
info!("order state: {:#?}", state);
assert!(matches!(state.status, OrderStatus::Pending));
let mut authorizations = order.authorizations();
while let Some(result) = authorizations.next().await {
let mut authz = result?;
match authz.status {
AuthorizationStatus::Pending => {}
AuthorizationStatus::Valid => continue,
_ => todo!(),
}
let mut challenge = authz
.challenge(ChallengeType::Dns01)
.ok_or_else(|| anyhow::anyhow!("no dns01 challenge found"))?;
println!("Please set the following DNS record then press the Return key:");
println!(
"_acme-challenge.{} IN TXT {}",
challenge.identifier(),
challenge.key_authorization().dns_value()
);
io::stdin().read_line(&mut String::new())?;
challenge.set_ready().await?;
}
let status = order.poll_ready(&RetryPolicy::default()).await?;
if status != OrderStatus::Ready {
return Err(anyhow::anyhow!("unexpected order status: {status:?}"));
}
let private_key_pem = order.finalize().await?;
let cert_chain_pem = order.poll_certificate(&RetryPolicy::default()).await?;
info!("certificate chain:\n\n{cert_chain_pem}");
info!("private key:\n\n{private_key_pem}");
Ok(())
}
#[derive(Parser)]
struct Options {
#[clap(long)]
names: Vec<String>,
}