aboutsummaryrefslogtreecommitdiffstats
path: root/crates/server/src/main.rs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--crates/server/src/main.rs88
1 files changed, 84 insertions, 4 deletions
diff --git a/crates/server/src/main.rs b/crates/server/src/main.rs
index 94d6d143..fb587754 100644
--- a/crates/server/src/main.rs
+++ b/crates/server/src/main.rs
@@ -1,13 +1,25 @@
+use std::future::Future;
use std::net::SocketAddr;
-use turtle_server::{Settings, database::DbType, launch, launch_metrics_server};
-
+use axum::{Router, serve};
use clap::Parser;
+use database::db::ServerPostgres;
use eyre::{Context, Result, eyre};
+use tokio::net::TcpListener;
+use tokio::signal;
+
+use crate::database::DbType;
+use crate::settings::Settings;
+
+mod database;
+mod handlers;
+mod metrics;
+mod router;
+mod settings;
#[derive(Parser, Clone, Debug)]
#[command(infer_subcommands = true)]
-pub enum Cmd {
+pub(crate) enum Cmd {
/// Start the server
Start {
/// The host address to bind
@@ -24,7 +36,7 @@ pub enum Cmd {
}
impl Cmd {
- pub async fn run(self) -> Result<()> {
+ async fn run(self) -> Result<()> {
match self {
Self::Start { host, port } => {
let settings = Settings::new().wrap_err("could not load server settings")?;
@@ -59,3 +71,71 @@ impl Cmd {
async fn main() -> Result<()> {
Cmd::parse().run().await
}
+
+#[cfg(target_family = "unix")]
+async fn shutdown_signal() {
+ let mut term = signal::unix::signal(signal::unix::SignalKind::terminate())
+ .expect("failed to register signal handler");
+ let mut interrupt = signal::unix::signal(signal::unix::SignalKind::interrupt())
+ .expect("failed to register signal handler");
+
+ tokio::select! {
+ _ = term.recv() => {},
+ _ = interrupt.recv() => {},
+ };
+ eprintln!("Shutting down gracefully...");
+}
+
+async fn launch(settings: Settings, addr: SocketAddr) -> Result<()> {
+ launch_with_tcp_listener(
+ settings,
+ TcpListener::bind(addr)
+ .await
+ .context("could not connect to socket")?,
+ shutdown_signal(),
+ )
+ .await
+}
+
+async fn launch_with_tcp_listener(
+ settings: Settings,
+ listener: TcpListener,
+ shutdown: impl Future<Output = ()> + Send + 'static,
+) -> Result<()> {
+ let r = make_router(settings).await?;
+
+ serve(listener, r.into_make_service())
+ .with_graceful_shutdown(shutdown)
+ .await?;
+
+ Ok(())
+}
+
+// The separate listener means it's much easier to ensure metrics are not accidentally exposed to
+// the public.
+async fn launch_metrics_server(host: String, port: u16) -> Result<()> {
+ let listener = TcpListener::bind((host, port))
+ .await
+ .context("failed to bind metrics tcp")?;
+
+ let recorder_handle = metrics::setup_metrics_recorder();
+
+ let router = Router::new().route(
+ "/metrics",
+ axum::routing::get(move || std::future::ready(recorder_handle.render())),
+ );
+
+ serve(listener, router.into_make_service())
+ .with_graceful_shutdown(shutdown_signal())
+ .await?;
+
+ Ok(())
+}
+
+async fn make_router(settings: Settings) -> Result<Router, eyre::Error> {
+ let db = ServerPostgres::new(&settings.db_settings)
+ .await
+ .wrap_err_with(|| format!("failed to connect to db: {:?}", settings.db_settings))?;
+ let r = router::router(db, settings);
+ Ok(r)
+}