Switch to using a config file

This commit is contained in:
Brian Picciano 2023-07-09 16:09:00 +02:00
parent 1bc3420930
commit 57b56934a9
13 changed files with 146 additions and 93 deletions

10
.dev-config.yml Normal file
View File

@ -0,0 +1,10 @@
origin:
store_dir_path: /tmp/domani_dev_env/origin
domain:
store_dir_path: /tmp/domani_dev_env/domain
dns:
target_records:
- type: A
addr: 127.0.0.1
service:
passphrase: foobar

View File

@ -1,6 +1 @@
export DOMANI_HTTP_DOMAIN=localhost
export DOMANI_PASSPHRASE=foobar
export DOMANI_ORIGIN_STORE_GIT_DIR_PATH=/tmp/domani_dev_env/origin/git
export DOMANI_DOMAIN_CHECKER_TARGET_A=127.0.0.1
export DOMANI_DOMAIN_CONFIG_STORE_DIR_PATH=/tmp/domani_dev_env/domain/config
export DOMANI_DOMAIN_ACME_STORE_DIR_PATH=/tmp/domani_dev_env/domain/acme
export DOMANI_CONFIG_PATH=./.dev-config.yml

46
Cargo.lock generated
View File

@ -467,6 +467,7 @@ dependencies = [
"serde_json",
"serde_urlencoded",
"serde_with",
"serde_yaml",
"sha2",
"signal-hook",
"signal-hook-tokio",
@ -537,6 +538,12 @@ dependencies = [
"termcolor",
]
[[package]]
name = "equivalent"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1"
[[package]]
name = "errno"
version = "0.3.1"
@ -1362,7 +1369,7 @@ dependencies = [
"futures-sink",
"futures-util",
"http",
"indexmap",
"indexmap 1.9.3",
"slab",
"tokio",
"tokio-util",
@ -1396,6 +1403,12 @@ version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
[[package]]
name = "hashbrown"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
[[package]]
name = "heck"
version = "0.4.1"
@ -1597,6 +1610,16 @@ dependencies = [
"serde",
]
[[package]]
name = "indexmap"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
dependencies = [
"equivalent",
"hashbrown 0.14.0",
]
[[package]]
name = "instant"
version = "0.1.12"
@ -2552,7 +2575,7 @@ dependencies = [
"base64 0.21.0",
"chrono",
"hex",
"indexmap",
"indexmap 1.9.3",
"serde",
"serde_json",
"serde_with_macros",
@ -2571,6 +2594,19 @@ dependencies = [
"syn 2.0.15",
]
[[package]]
name = "serde_yaml"
version = "0.9.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "452e67b9c20c37fa79df53201dc03839651086ed9bbe92b3ca585ca9fdaa7d85"
dependencies = [
"indexmap 2.0.0",
"itoa",
"ryu",
"serde",
"unsafe-libyaml",
]
[[package]]
name = "sha1_smol"
version = "1.0.0"
@ -3041,6 +3077,12 @@ dependencies = [
"tinyvec",
]
[[package]]
name = "unsafe-libyaml"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1865806a559042e51ab5414598446a5871b561d21b6764f2eabb0dd481d880a6"
[[package]]
name = "untrusted"
version = "0.7.1"

View File

@ -41,3 +41,4 @@ tls-listener = { version = "0.7.0", features = [ "rustls", "hyper-h1" ]}
tokio-rustls = "0.24.0"
log = "0.4.19"
env_logger = "0.10.0"
serde_yaml = "0.9.22"

8
src/config.rs Normal file
View File

@ -0,0 +1,8 @@
use serde::Deserialize;
#[derive(Deserialize)]
pub struct Config {
pub origin: crate::origin::Config,
pub domain: crate::domain::Config,
pub service: crate::service::Config,
}

View File

@ -1,14 +1,20 @@
use std::{net, path};
use std::{net, path, str::FromStr};
use serde::Deserialize;
#[derive(Deserialize)]
#[serde(tag = "type")]
pub enum ConfigDNSTargetRecord {
A { addr: net::Ipv4Addr },
}
fn default_resolver_addr() -> net::SocketAddr {
net::SocketAddr::from_str("1.1.1.1:53").unwrap()
}
#[derive(Deserialize)]
pub struct ConfigDNS {
#[serde(default = "default_resolver_addr")]
pub resolver_addr: net::SocketAddr,
pub target_records: Vec<ConfigDNSTargetRecord>,
}

View File

@ -2,6 +2,7 @@
#![feature(iterator_try_collect)]
#![feature(iter_collect_into)]
pub mod config;
pub mod domain;
pub mod error;
pub mod origin;

View File

@ -4,9 +4,7 @@ use clap::Parser;
use futures::stream::StreamExt;
use signal_hook_tokio::Signals;
use std::net::SocketAddr;
use std::path;
use std::str::FromStr;
#[derive(Parser, Debug)]
#[command(version)]
@ -23,101 +21,54 @@ struct Cli {
#[arg(long, default_value_t = false, env = "DOMANI_LOG_TIMESTAMP")]
log_timestamp: bool,
#[arg(long, required = true, env = "DOMANI_HTTP_DOMAIN")]
http_domain: domani::domain::Name,
#[arg(long, default_value_t = SocketAddr::from_str("[::]:3030").unwrap(), env = "DOMANI_HTTP_LISTEN_ADDR")]
http_listen_addr: SocketAddr,
#[arg(
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"
help = "Path to config file",
required = true,
env = "DOMANI_CONFIG_PATH"
)]
https_listen_addr: Option<SocketAddr>,
#[arg(long, required = true, env = "DOMANI_PASSPHRASE")]
passphrase: String,
#[arg(long, required = true, env = "DOMANI_ORIGIN_STORE_GIT_DIR_PATH")]
origin_store_git_dir_path: path::PathBuf,
#[arg(long, required = true, env = "DOMANI_DOMAIN_CHECKER_TARGET_A")]
domain_checker_target_a: std::net::Ipv4Addr,
#[arg(long, default_value_t = String::from("1.1.1.1:53"), env = "DOMANI_DOMAIN_CHECKER_RESOLVER_ADDR")]
domain_checker_resolver_addr: String,
#[arg(long, required = true, env = "DOMANI_DOMAIN_CONFIG_STORE_DIR_PATH")]
domain_config_store_dir_path: path::PathBuf,
#[arg(long, env = "DOMANI_DOMAIN_ACME_CONTACT_EMAIL")]
domain_acme_contact_email: Option<String>,
config_path: path::PathBuf,
}
#[tokio::main]
async fn main() {
let config = Cli::parse();
let cli = Cli::parse();
let config: domani::config::Config = {
let path = &cli.config_path;
let f = std::fs::File::open(path)
.unwrap_or_else(|e| panic!("failed to open config file at {}: {e}", path.display()));
serde_yaml::from_reader(f)
.unwrap_or_else(|e| panic!("failed to parse config file at {}: {e}", path.display()))
};
env_logger::Builder::new()
.filter_level(config.log_level)
.filter_level(cli.log_level)
.format_timestamp(
config
.log_timestamp
cli.log_timestamp
.then_some(env_logger::TimestampPrecision::Micros),
)
.init();
let origin_config = domani::origin::Config {
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)
let origin_store = domani::origin::git::FSStore::new(&config.origin)
.expect("git origin store initialization failed");
let domain_checker = domani::domain::checker::DNSChecker::new(&domain_config.dns)
let domain_checker = domani::domain::checker::DNSChecker::new(&config.domain.dns)
.await
.expect("domain checker initialization failed");
let domain_config_store =
domani::domain::store::FSStore::new(&domain_config.store_dir_path.join("domains"))
domani::domain::store::FSStore::new(&config.domain.store_dir_path.join("domains"))
.expect("domain config store initialization failed");
let domain_acme_manager = if service_http_config.https_addr.is_some() {
let acme_config = domain_config
let domain_acme_manager = if config.service.http.https_addr.is_some() {
let acme_config = config
.domain
.acme
.expect("acme configuration must be set if https is enabled");
let domain_acme_store =
domani::domain::acme::store::FSStore::new(&domain_config.store_dir_path.join("acme"))
domani::domain::acme::store::FSStore::new(&config.domain.store_dir_path.join("acme"))
.expect("domain acme store initialization failed");
Some(
@ -143,7 +94,8 @@ async fn main() {
&mut task_stack,
domain_manager.clone(),
domain_manager.clone(),
service_http_config,
config.service,
config.domain.dns.target_records,
);
let mut signals =

View File

@ -35,7 +35,7 @@ pub struct FSStore {
impl FSStore {
pub fn new(config: &origin::Config) -> io::Result<Self> {
let dir_path = config.store_dir_path.clone();
let dir_path = config.store_dir_path.join("git");
fs::create_dir_all(&dir_path)?;
Ok(Self {
dir_path,

View File

@ -1,2 +1,19 @@
pub mod http;
mod util;
use crate::domain;
use serde::Deserialize;
use std::str::FromStr;
fn default_primary_domain() -> domain::Name {
domain::Name::from_str("localhost").unwrap()
}
#[derive(Deserialize)]
pub struct Config {
#[serde(default = "default_primary_domain")]
pub primary_domain: domain::Name,
pub passphrase: String,
#[serde(default)]
pub http: self::http::Config,
}

View File

@ -17,21 +17,24 @@ pub struct Service {
domain_manager: sync::Arc<dyn domain::manager::Manager>,
cert_resolver: sync::Arc<dyn rustls::server::ResolvesServerCert>,
handlebars: handlebars::Handlebars<'static>,
config: Config,
config: service::Config,
dns_target_records: Vec<domain::ConfigDNSTargetRecord>,
}
pub fn new(
task_stack: &mut util::TaskStack<unexpected::Error>,
domain_manager: sync::Arc<dyn domain::manager::Manager>,
cert_resolver: sync::Arc<dyn rustls::server::ResolvesServerCert>,
config: Config,
config: service::Config,
dns_target_records: Vec<domain::ConfigDNSTargetRecord>,
) -> sync::Arc<Service> {
let https_enabled = config.https_addr.is_some();
let https_enabled = config.http.https_addr.is_some();
let service = sync::Arc::new(Service {
domain_manager: domain_manager.clone(),
cert_resolver,
handlebars: tpl::get(),
config,
dns_target_records,
});
task_stack.push_spawn(|canceller| tasks::listen_http(service.clone(), canceller));
@ -248,12 +251,20 @@ impl<'svc> Service {
}
};
let target_a = match self
.dns_target_records
.get(0)
.expect("at least one target record expected")
{
domain::ConfigDNSTargetRecord::A { addr } => addr.clone(),
};
self.render_page(
"/domain_init.html",
Response {
domain: args.domain,
flat_config: config.into(),
target_a: self.config.target_a,
target_a: target_a,
challenge_token: config_hash,
},
)

View File

@ -1,12 +1,22 @@
use crate::domain;
use serde::Deserialize;
use std::net;
use std::{net, str::FromStr};
fn default_http_addr() -> net::SocketAddr {
net::SocketAddr::from_str("[::]:3030").unwrap()
}
#[derive(Deserialize)]
pub struct Config {
#[serde(default = "default_http_addr")]
pub http_addr: net::SocketAddr,
pub https_addr: Option<net::SocketAddr>,
pub primary_domain: domain::Name,
pub target_a: net::Ipv4Addr,
pub passphrase: String,
}
impl Default for Config {
fn default() -> Self {
Self {
http_addr: default_http_addr(),
https_addr: None,
}
}
}

View File

@ -10,7 +10,7 @@ pub async fn listen_http(
service: sync::Arc<service::http::Service>,
canceller: CancellationToken,
) -> Result<(), unexpected::Error> {
let addr = service.config.http_addr.clone();
let addr = service.config.http.http_addr.clone();
let primary_domain = service.config.primary_domain.clone();
let make_service = hyper::service::make_service_fn(move |_| {
@ -45,7 +45,7 @@ pub async fn listen_https(
canceller: CancellationToken,
) -> Result<(), unexpected::Error> {
let cert_resolver = service.cert_resolver.clone();
let addr = service.config.https_addr.unwrap().clone();
let addr = service.config.http.https_addr.unwrap().clone();
let primary_domain = service.config.primary_domain.clone();
let make_service = hyper::service::make_service_fn(move |_| {