#![forbid(unsafe_code)]
use std::process::ExitCode;
use std::sync::Arc;
use dynamic_config::Builder;
use dynamic_config_server::{router, Server, ServerConfig, VERSION};
const SECTION: &str = "server";
const USAGE: &str = "\
usage: dynamic-config-server <config-file>
<config-file> TOML, JSON or YAML, holding a [server] section. See the
book for the keys; every one of them is documented on
`ServerConfig`.
options:
-h, --help print this
-V, --version print the version
";
fn main() -> ExitCode {
let mut arguments = std::env::args().skip(1);
let Some(first) = arguments.next() else {
eprint!("{USAGE}");
return ExitCode::FAILURE;
};
match first.as_str() {
"-h" | "--help" => {
print!("{USAGE}");
return ExitCode::SUCCESS;
}
"-V" | "--version" => {
println!("dynamic-config-server {VERSION}");
return ExitCode::SUCCESS;
}
_ => {}
}
if arguments.next().is_some() {
eprint!("{USAGE}");
return ExitCode::FAILURE;
}
match run(&first) {
Ok(()) => ExitCode::SUCCESS,
Err(error) => {
eprintln!("dynamic-config-server: {error}");
ExitCode::FAILURE
}
}
}
fn run(path: &str) -> Result<(), Box<dyn std::error::Error>> {
let config: ServerConfig = Builder::new(SECTION).file(path).load()?;
let server = Arc::new(Server::start(&config)?);
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()?
.block_on(serve(server))
}
async fn serve(server: Arc<Server>) -> Result<(), Box<dyn std::error::Error>> {
let address = server.address();
let listener = tokio::net::TcpListener::bind(address).await?;
eprintln!(
"dynamic-config-server {VERSION} listening on {address} (protection: {})",
server.posture()
);
serve_on(listener, server).await
}
async fn shutdown() {
#[cfg(unix)]
{
use tokio::signal::unix::{signal, SignalKind};
let mut terminate = match signal(SignalKind::terminate()) {
Ok(terminate) => terminate,
Err(_) => {
let _ = tokio::signal::ctrl_c().await;
return;
}
};
tokio::select! {
_ = terminate.recv() => {}
_ = tokio::signal::ctrl_c() => {}
}
}
#[cfg(not(unix))]
{
let _ = tokio::signal::ctrl_c().await;
}
}
async fn serve_plain(
listener: tokio::net::TcpListener,
server: Arc<Server>,
) -> Result<(), Box<dyn std::error::Error>> {
let (signalled, fired) = tokio::sync::oneshot::channel();
let serving = axum::serve(listener, router(server)).with_graceful_shutdown(async move {
shutdown().await;
let _ = signalled.send(());
});
tokio::select! {
outcome = serving => outcome?,
() = async {
if fired.await.is_err() {
std::future::pending::<()>().await;
}
tokio::time::sleep(dynamic_config_server::DRAIN_TIMEOUT).await;
} => {}
}
Ok(())
}
#[cfg(feature = "tls")]
async fn serve_on(
listener: tokio::net::TcpListener,
server: Arc<Server>,
) -> Result<(), Box<dyn std::error::Error>> {
if server.tls().is_some() {
dynamic_config_server::serve_tls(
listener,
router(Arc::clone(&server)),
&server,
shutdown(),
)
.await?;
return Ok(());
}
serve_plain(listener, server).await
}
#[cfg(not(feature = "tls"))]
async fn serve_on(
listener: tokio::net::TcpListener,
server: Arc<Server>,
) -> Result<(), Box<dyn std::error::Error>> {
serve_plain(listener, server).await
}