Domani connects your domain to whatever you want to host on it, all with no account needed
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
domani/src/main.rs

218 lines
6.7 KiB

use clap::Parser;
use std::path;
#[derive(Parser, Debug)]
#[command(version)]
#[command(about = "A domani to another dimension")]
struct Cli {
#[arg(
long,
help = "OFF, ERROR, WARN, INFO, DEBUG, or TRACE",
default_value_t = log::LevelFilter::Info,
env = "DOMANI_LOG_LEVEL"
)]
log_level: log::LevelFilter,
#[arg(long, default_value_t = false, env = "DOMANI_LOG_TIMESTAMP")]
log_timestamp: bool,
#[arg(
long,
help = "Path to config file",
required = true,
env = "DOMANI_CONFIG_PATH"
)]
config_path: path::PathBuf,
#[arg(long, help = "Dump the full process configuration to stdout and exit")]
dump_config: bool,
}
#[tokio::main]
async fn main() {
let cli = Cli::parse();
env_logger::Builder::new()
.filter_level(cli.log_level)
.format_timestamp(
cli.log_timestamp
.then_some(env_logger::TimestampPrecision::Micros),
)
.init();
let config = {
let mut 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())
})
};
// interface_cname is a CNAME record which points to the interface domain of the service.
// Since the interface domain _must_ point to the service (otherwise it wouldn't work) it's
// reasonable to assume that a CNAME on any domain would suffice to point that domain to
// the service.
if let Some(ref interface_domain) = config.domain.interface_domain {
let interface_cname = domani::service::ConfigDNSRecord::CNAME {
name: interface_domain.clone(),
};
let dns_records_have_interface_cname = config
.service
.dns_records
.iter()
.any(|r| r == &interface_cname);
if !dns_records_have_interface_cname {
log::info!("Adding 'CNAME {interface_domain}' to service.dns_records");
config.service.dns_records.push(interface_cname);
}
}
config
};
if cli.dump_config {
let stdout = std::io::stdout().lock();
serde_yaml::to_writer(stdout, &config).expect("writing config to stdout");
return;
};
let http_enabled = config.service.http.http_addr.is_some();
let https_enabled = config.service.http.https_addr.is_some();
let gemini_enabled = config.service.gemini.gemini_addr.is_some();
let external_domains_enabled = !config.domain.external_domains.is_empty();
if https_enabled && !http_enabled {
panic!("http is disabled, but is required by https");
}
if external_domains_enabled && !http_enabled {
panic!("http is disabled, but is required by external_domains");
}
let origin_store = domani::origin::git::Proxy::new();
let domain_checker = domani::domain::checker::DNSChecker::new(
domani::token::MemStore::new(),
&config.domain.dns,
)
.await
.expect("domain checker initialization failed");
let domain_store =
domani::domain::store::FSStore::new(&config.domain.store_dir_path.join("domains"))
.expect("domain config store initialization failed");
let domain_acme_manager = if https_enabled || external_domains_enabled {
let acme_config = config
.domain
.acme
.clone()
.expect("acme configuration must be set if https is enabled");
let acme_dir_path = config.domain.store_dir_path.join("acme");
let domain_acme_store = {
let dir_path = acme_dir_path.join("certificates");
domani::domain::acme::store::JSONFSStore::new(&dir_path).unwrap_or_else(|e| {
panic!(
"failed to initialize acme cert store at {}: {e}",
dir_path.display()
)
})
};
let domain_acme_account_key_store = domani::domain::acme::account_key_store::FSStore::new(
&acme_dir_path,
)
.unwrap_or_else(|e| {
panic!(
"failed to initialize account key store at {}: {e}",
acme_dir_path.display()
)
});
Some(
domani::domain::acme::manager::ManagerImpl::new(
domain_acme_store,
domani::token::MemStore::new(),
domain_acme_account_key_store,
|config| {
Ok(Box::from(domani::domain::acme::store::DirectFSStore::new(
&config.tls_key_path,
&config.tls_cert_path,
)))
},
&acme_config,
)
.await
.expect("domain acme manager initialization failed"),
)
} else {
None
};
let domain_gemini_store = if gemini_enabled {
Some(
domani::domain::gemini::FSStore::new(&config.domain.store_dir_path.join("gemini"))
.unwrap_or_else(|e| panic!("domain gemini store initialization failed: {e}")),
)
} else {
None
};
let mut task_stack = domani::task_stack::TaskStack::new();
let domain_manager = domani::domain::manager::ManagerImpl::new(
&mut task_stack,
origin_store,
domain_store,
domain_checker,
domain_acme_manager,
domain_gemini_store,
config.domain.clone(),
);
if http_enabled {
let _ = domani::service::http::Service::new(
&mut task_stack,
domain_manager.clone(),
domani::domain::manager::HttpsCertResolver::from(domain_manager.clone()),
config.service.clone(),
);
}
if gemini_enabled {
let _ = domani::service::gemini::Service::new(
&mut task_stack,
domain_manager.clone(),
domani::domain::manager::GeminiCertResolver::from(domain_manager.clone()),
config.service.gemini.clone(),
);
}
tokio::signal::ctrl_c()
.await
.expect("failed to listen for kill signal");
log::info!("Gracefully shutting down...");
tokio::spawn(async move {
tokio::signal::ctrl_c()
.await
.expect("failed to listen for kill signal");
log::warn!("Forcefully shutting down");
std::process::exit(1);
});
task_stack
.stop()
.await
.expect("failed to stop all background tasks");
log::info!("Graceful shutdown complete");
}