Introduce domain::Config
This commit is contained in:
parent
80e96c47fb
commit
7c68702ab8
@ -1,9 +1,11 @@
|
||||
pub mod acme;
|
||||
pub mod checker;
|
||||
mod config;
|
||||
pub mod manager;
|
||||
mod name;
|
||||
pub mod store;
|
||||
|
||||
pub use config::*;
|
||||
pub use name::*;
|
||||
|
||||
use crate::error::unexpected::{self, Mappable};
|
||||
|
@ -33,7 +33,7 @@ pub struct ManagerImpl {
|
||||
impl ManagerImpl {
|
||||
pub async fn new<Store: acme::store::Store + Send + Sync + 'static>(
|
||||
store: Store,
|
||||
contact_email: &str,
|
||||
config: &domain::ConfigACME,
|
||||
) -> Result<Self, unexpected::Error> {
|
||||
let dir = acme2::DirectoryBuilder::new(LETS_ENCRYPT_URL.to_string())
|
||||
.build()
|
||||
@ -41,7 +41,7 @@ impl ManagerImpl {
|
||||
.or_unexpected_while("creating acme2 directory builder")?;
|
||||
|
||||
let mut contact = String::from("mailto:");
|
||||
contact.push_str(contact_email);
|
||||
contact.push_str(config.contact_email.as_str());
|
||||
|
||||
let mut builder = acme2::AccountBuilder::new(dir);
|
||||
builder.contact(vec![contact]);
|
||||
|
@ -8,15 +8,6 @@ use trust_dns_client::client::{AsyncClient, ClientHandle};
|
||||
use trust_dns_client::rr::{DNSClass, Name, RData, RecordType};
|
||||
use trust_dns_client::udp;
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum NewDNSCheckerError {
|
||||
#[error("invalid resolver address")]
|
||||
InvalidResolverAddress,
|
||||
|
||||
#[error(transparent)]
|
||||
Unexpected(#[from] unexpected::Error),
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum CheckDomainError {
|
||||
#[error("target A not set")]
|
||||
@ -37,19 +28,19 @@ pub struct DNSChecker {
|
||||
}
|
||||
|
||||
impl DNSChecker {
|
||||
pub async fn new(
|
||||
target_a: net::Ipv4Addr,
|
||||
resolver_addr: &str,
|
||||
) -> Result<Self, NewDNSCheckerError> {
|
||||
let resolver_addr = resolver_addr
|
||||
.parse()
|
||||
.map_err(|_| NewDNSCheckerError::InvalidResolverAddress)?;
|
||||
|
||||
let stream = udp::UdpClientStream::<tokio::net::UdpSocket>::new(resolver_addr);
|
||||
pub async fn new(config: &domain::ConfigDNS) -> Result<Self, unexpected::Error> {
|
||||
let target_a = match config
|
||||
.target_records
|
||||
.get(0)
|
||||
.expect("at least one target record expected")
|
||||
{
|
||||
domain::ConfigDNSTargetRecord::A { addr } => addr.clone(),
|
||||
};
|
||||
|
||||
let stream = udp::UdpClientStream::<tokio::net::UdpSocket>::new(config.resolver_addr);
|
||||
let (client, bg) = AsyncClient::connect(stream).await.or_unexpected()?;
|
||||
|
||||
tokio::spawn(bg);
|
||||
// TODO there should be a mechanism to clean this up
|
||||
|
||||
Ok(Self {
|
||||
target_a,
|
||||
|
26
src/domain/config.rs
Normal file
26
src/domain/config.rs
Normal file
@ -0,0 +1,26 @@
|
||||
use std::{net, path};
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub enum ConfigDNSTargetRecord {
|
||||
A { addr: net::Ipv4Addr },
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ConfigDNS {
|
||||
pub resolver_addr: net::SocketAddr,
|
||||
pub target_records: Vec<ConfigDNSTargetRecord>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ConfigACME {
|
||||
pub contact_email: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Config {
|
||||
pub store_dir_path: path::PathBuf,
|
||||
pub dns: ConfigDNS,
|
||||
pub acme: Option<ConfigACME>,
|
||||
}
|
@ -163,7 +163,7 @@ pub trait Manager: Sync + Send + rustls::server::ResolvesServerCert {
|
||||
|
||||
pub struct ManagerImpl {
|
||||
origin_store: Box<dyn origin::Store + Send + Sync>,
|
||||
domain_config_store: Box<dyn store::Store + Send + Sync>,
|
||||
domain_store: Box<dyn store::Store + Send + Sync>,
|
||||
domain_checker: checker::DNSChecker,
|
||||
acme_manager: Option<Box<dyn acme::manager::Manager + Send + Sync>>,
|
||||
}
|
||||
@ -171,18 +171,18 @@ pub struct ManagerImpl {
|
||||
impl ManagerImpl {
|
||||
pub fn new<
|
||||
OriginStore: origin::Store + Send + Sync + 'static,
|
||||
DomainConfigStore: store::Store + Send + Sync + 'static,
|
||||
DomainStore: store::Store + Send + Sync + 'static,
|
||||
AcmeManager: acme::manager::Manager + Send + Sync + 'static,
|
||||
>(
|
||||
task_stack: &mut util::TaskStack<unexpected::Error>,
|
||||
origin_store: OriginStore,
|
||||
domain_config_store: DomainConfigStore,
|
||||
domain_store: DomainStore,
|
||||
domain_checker: checker::DNSChecker,
|
||||
acme_manager: Option<AcmeManager>,
|
||||
) -> sync::Arc<Self> {
|
||||
let manager = sync::Arc::new(ManagerImpl {
|
||||
origin_store: Box::from(origin_store),
|
||||
domain_config_store: Box::from(domain_config_store),
|
||||
domain_store: Box::from(domain_store),
|
||||
domain_checker,
|
||||
acme_manager: acme_manager
|
||||
.map(|m| Box::new(m) as Box<dyn acme::manager::Manager + Send + Sync>),
|
||||
@ -225,7 +225,7 @@ impl ManagerImpl {
|
||||
|
||||
impl Manager for ManagerImpl {
|
||||
fn get_config(&self, domain: &domain::Name) -> Result<domain::Domain, GetConfigError> {
|
||||
Ok(self.domain_config_store.get(domain)?)
|
||||
Ok(self.domain_store.get(domain)?)
|
||||
}
|
||||
|
||||
fn get_file<'store>(
|
||||
@ -233,7 +233,7 @@ impl Manager for ManagerImpl {
|
||||
domain: &domain::Name,
|
||||
path: &str,
|
||||
) -> Result<util::BoxByteStream, GetFileError> {
|
||||
let config = self.domain_config_store.get(domain)?;
|
||||
let config = self.domain_store.get(domain)?;
|
||||
let f = self.origin_store.get_file(&config.origin_descr, path)?;
|
||||
Ok(f)
|
||||
}
|
||||
@ -267,7 +267,7 @@ impl Manager for ManagerImpl {
|
||||
|
||||
self.origin_store.sync(&config.origin_descr)?;
|
||||
|
||||
self.domain_config_store.set(&domain, &config)?;
|
||||
self.domain_store.set(&domain, &config)?;
|
||||
|
||||
self.sync_cert(domain).await?;
|
||||
|
||||
@ -287,7 +287,7 @@ impl Manager for ManagerImpl {
|
||||
}
|
||||
|
||||
fn all_domains(&self) -> Result<Vec<domain::Name>, unexpected::Error> {
|
||||
self.domain_config_store.all_domains()
|
||||
self.domain_store.all_domains()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::path;
|
||||
use std::str::FromStr;
|
||||
use std::{fs, io};
|
||||
|
||||
@ -28,22 +28,22 @@ pub trait Store {
|
||||
}
|
||||
|
||||
pub struct FSStore {
|
||||
dir_path: PathBuf,
|
||||
dir_path: path::PathBuf,
|
||||
}
|
||||
|
||||
impl FSStore {
|
||||
pub fn new(dir_path: &Path) -> io::Result<Self> {
|
||||
pub fn new(dir_path: &path::Path) -> io::Result<Self> {
|
||||
fs::create_dir_all(dir_path)?;
|
||||
Ok(Self {
|
||||
dir_path: dir_path.into(),
|
||||
})
|
||||
}
|
||||
|
||||
fn config_dir_path(&self, domain: &domain::Name) -> PathBuf {
|
||||
fn config_dir_path(&self, domain: &domain::Name) -> path::PathBuf {
|
||||
self.dir_path.join(domain.as_str())
|
||||
}
|
||||
|
||||
fn config_file_path(&self, domain: &domain::Name) -> PathBuf {
|
||||
fn config_file_path(&self, domain: &domain::Name) -> path::PathBuf {
|
||||
self.config_dir_path(domain).join("config.json")
|
||||
}
|
||||
}
|
||||
|
73
src/main.rs
73
src/main.rs
@ -33,8 +33,7 @@ struct Cli {
|
||||
long,
|
||||
help = "E.g. '[::]:443', if given then SSL certs will automatically be retrieved for all domains using LetsEncrypt",
|
||||
env = "DOMANI_HTTPS_LISTEN_ADDR",
|
||||
requires = "domain_acme_contact_email",
|
||||
requires = "domain_acme_store_dir_path"
|
||||
requires = "domain_acme_contact_email"
|
||||
)]
|
||||
https_listen_addr: Option<SocketAddr>,
|
||||
|
||||
@ -53,9 +52,6 @@ struct Cli {
|
||||
#[arg(long, required = true, env = "DOMANI_DOMAIN_CONFIG_STORE_DIR_PATH")]
|
||||
domain_config_store_dir_path: path::PathBuf,
|
||||
|
||||
#[arg(long, env = "DOMANI_DOMAIN_ACME_STORE_DIR_PATH")]
|
||||
domain_acme_store_dir_path: Option<path::PathBuf>,
|
||||
|
||||
#[arg(long, env = "DOMANI_DOMAIN_ACME_CONTACT_EMAIL")]
|
||||
domain_acme_contact_email: Option<String>,
|
||||
}
|
||||
@ -77,38 +73,57 @@ async fn main() {
|
||||
store_dir_path: config.origin_store_git_dir_path,
|
||||
};
|
||||
|
||||
let domain_config = domani::domain::Config {
|
||||
store_dir_path: config.domain_config_store_dir_path,
|
||||
dns: domani::domain::ConfigDNS {
|
||||
resolver_addr: std::net::SocketAddr::from_str(
|
||||
config.domain_checker_resolver_addr.as_str(),
|
||||
)
|
||||
.expect("parsing resolver addr"),
|
||||
|
||||
target_records: vec![domani::domain::ConfigDNSTargetRecord::A {
|
||||
addr: config.domain_checker_target_a,
|
||||
}],
|
||||
},
|
||||
acme: config
|
||||
.https_listen_addr
|
||||
.and(Some(domani::domain::ConfigACME {
|
||||
contact_email: config.domain_acme_contact_email.unwrap(),
|
||||
})),
|
||||
};
|
||||
|
||||
let service_http_config = domani::service::http::Config {
|
||||
http_addr: config.http_listen_addr,
|
||||
https_addr: config.https_listen_addr,
|
||||
primary_domain: config.http_domain,
|
||||
target_a: config.domain_checker_target_a,
|
||||
passphrase: config.passphrase,
|
||||
};
|
||||
|
||||
let origin_store = domani::origin::git::FSStore::new(&origin_config)
|
||||
.expect("git origin store initialization failed");
|
||||
|
||||
let domain_checker = domani::domain::checker::DNSChecker::new(
|
||||
config.domain_checker_target_a,
|
||||
&config.domain_checker_resolver_addr,
|
||||
)
|
||||
.await
|
||||
.expect("domain checker initialization failed");
|
||||
let domain_checker = domani::domain::checker::DNSChecker::new(&domain_config.dns)
|
||||
.await
|
||||
.expect("domain checker initialization failed");
|
||||
|
||||
let domain_config_store =
|
||||
domani::domain::store::FSStore::new(&config.domain_config_store_dir_path)
|
||||
domani::domain::store::FSStore::new(&domain_config.store_dir_path.join("domains"))
|
||||
.expect("domain config store initialization failed");
|
||||
|
||||
let domain_acme_manager = if config.https_listen_addr.is_some() {
|
||||
let domain_acme_store_dir_path = config.domain_acme_store_dir_path.unwrap();
|
||||
let domain_acme_manager = if service_http_config.https_addr.is_some() {
|
||||
let acme_config = domain_config
|
||||
.acme
|
||||
.expect("acme configuration must be set if https is enabled");
|
||||
|
||||
let domain_acme_store =
|
||||
domani::domain::acme::store::FSStore::new(&domain_acme_store_dir_path)
|
||||
domani::domain::acme::store::FSStore::new(&domain_config.store_dir_path.join("acme"))
|
||||
.expect("domain acme store initialization failed");
|
||||
|
||||
// if https_listen_addr is set then domain_acme_contact_email is required, see the Cli/clap
|
||||
// settings.
|
||||
let domain_acme_contact_email = config.domain_acme_contact_email.unwrap();
|
||||
|
||||
Some(
|
||||
domani::domain::acme::manager::ManagerImpl::new(
|
||||
domain_acme_store,
|
||||
&domain_acme_contact_email,
|
||||
)
|
||||
.await
|
||||
.expect("domain acme manager initialization failed"),
|
||||
domani::domain::acme::manager::ManagerImpl::new(domain_acme_store, &acme_config)
|
||||
.await
|
||||
.expect("domain acme manager initialization failed"),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
@ -128,13 +143,7 @@ async fn main() {
|
||||
&mut task_stack,
|
||||
domain_manager.clone(),
|
||||
domain_manager.clone(),
|
||||
domani::service::http::Config {
|
||||
http_addr: config.http_listen_addr,
|
||||
https_addr: config.https_listen_addr,
|
||||
primary_domain: config.http_domain,
|
||||
target_a: config.domain_checker_target_a,
|
||||
passphrase: config.passphrase,
|
||||
},
|
||||
service_http_config,
|
||||
);
|
||||
|
||||
let mut signals =
|
||||
|
Loading…
Reference in New Issue
Block a user