diff options
Diffstat (limited to '')
| -rw-r--r-- | crates/server/src/database/db/mod.rs (renamed from crates/turtle/src/atuin_server/database/db/mod.rs) | 13 | ||||
| -rw-r--r-- | crates/server/src/database/db/wrappers.rs (renamed from crates/turtle/src/atuin_server/database/db/wrappers.rs) | 4 | ||||
| -rw-r--r-- | crates/server/src/database/mod.rs (renamed from crates/turtle/src/atuin_server/database/mod.rs) | 0 | ||||
| -rw-r--r-- | crates/server/src/database/models.rs (renamed from crates/turtle/src/atuin_server/database/models.rs) | 0 | ||||
| -rw-r--r-- | crates/server/src/handlers/mod.rs (renamed from crates/turtle/src/atuin_server/handlers/mod.rs) | 4 | ||||
| -rw-r--r-- | crates/server/src/handlers/v0/mod.rs (renamed from crates/turtle/src/atuin_server/handlers/v0/mod.rs) | 0 | ||||
| -rw-r--r-- | crates/server/src/handlers/v0/record.rs (renamed from crates/turtle/src/atuin_server/handlers/v0/record.rs) | 4 | ||||
| -rw-r--r-- | crates/server/src/main.rs | 146 | ||||
| -rw-r--r-- | crates/server/src/metrics.rs (renamed from crates/turtle/src/atuin_server/metrics.rs) | 0 | ||||
| -rw-r--r-- | crates/server/src/router.rs (renamed from crates/turtle/src/atuin_server/router.rs) | 9 | ||||
| -rw-r--r-- | crates/server/src/settings.rs (renamed from crates/turtle/src/atuin_server/settings.rs) | 46 |
11 files changed, 179 insertions, 47 deletions
diff --git a/crates/turtle/src/atuin_server/database/db/mod.rs b/crates/server/src/database/db/mod.rs index 77bd0c61..19a1fb3b 100644 --- a/crates/turtle/src/atuin_server/database/db/mod.rs +++ b/crates/server/src/database/db/mod.rs @@ -1,12 +1,9 @@ use std::collections::HashMap; -use rand::Rng; - -use crate::{ - atuin_common::record::{EncryptedData, HostId, Record, RecordIdx, RecordStatus}, - atuin_server::database::{DbError, DbResult, DbSettings, models::User}, -}; +use crate::database::{DbError, DbResult, DbSettings, models::User}; +use rand::RngExt; use sqlx::postgres::PgPoolOptions; +use turtle_common::record::{EncryptedData, HostId, Record, RecordIdx, RecordStatus}; use tracing::instrument; use uuid::Uuid; @@ -110,7 +107,7 @@ impl ServerPostgres { let mut heads = HashMap::<(HostId, &str), u64>::new(); for i in records { - let id = crate::atuin_common::utils::uuid_v7(); + let id = turtle_common::utils::uuid_v7(); let result = sqlx::query( " @@ -232,7 +229,7 @@ impl ServerPostgres { let idx_cache_rollout = std::env::var("IDX_CACHE_ROLLOUT").unwrap_or_else(|_| "0".to_string()); let idx_cache_rollout = idx_cache_rollout.parse::<f64>().unwrap_or(0.0); - let use_idx_cache = rand::thread_rng().gen_bool(idx_cache_rollout / 100.0); + let use_idx_cache = rand::rng().random_bool(idx_cache_rollout / 100.0); let mut res: Vec<(Uuid, String, i64)> = if use_idx_cache { tracing::debug!("using idx cache for user {}", user.id); diff --git a/crates/turtle/src/atuin_server/database/db/wrappers.rs b/crates/server/src/database/db/wrappers.rs index 0315e331..6bacf47f 100644 --- a/crates/turtle/src/atuin_server/database/db/wrappers.rs +++ b/crates/server/src/database/db/wrappers.rs @@ -1,7 +1,7 @@ -use crate::atuin_common::record::{EncryptedData, Host, Record}; +use turtle_common::record::{EncryptedData, Host, Record}; use sqlx::{Row, postgres::PgRow}; -pub(crate) struct DbRecord(pub Record<EncryptedData>); +pub(crate) struct DbRecord(pub(crate) Record<EncryptedData>); impl<'a> ::sqlx::FromRow<'a, PgRow> for DbRecord { fn from_row(row: &'a PgRow) -> ::sqlx::Result<Self> { diff --git a/crates/turtle/src/atuin_server/database/mod.rs b/crates/server/src/database/mod.rs index 43fe5c3b..43fe5c3b 100644 --- a/crates/turtle/src/atuin_server/database/mod.rs +++ b/crates/server/src/database/mod.rs diff --git a/crates/turtle/src/atuin_server/database/models.rs b/crates/server/src/database/models.rs index 3fa6f471..3fa6f471 100644 --- a/crates/turtle/src/atuin_server/database/models.rs +++ b/crates/server/src/database/models.rs diff --git a/crates/turtle/src/atuin_server/handlers/mod.rs b/crates/server/src/handlers/mod.rs index b73df27b..5fb5d0f2 100644 --- a/crates/turtle/src/atuin_server/handlers/mod.rs +++ b/crates/server/src/handlers/mod.rs @@ -1,7 +1,7 @@ -use crate::atuin_common::api::{ErrorResponse, IndexResponse}; +use turtle_common::api::{ErrorResponse, IndexResponse}; use axum::{Json, extract::State, http, response::IntoResponse}; -use crate::atuin_server::router::AppState; +use crate::router::AppState; pub(crate) mod v0; diff --git a/crates/turtle/src/atuin_server/handlers/v0/mod.rs b/crates/server/src/handlers/v0/mod.rs index 78fb47b8..78fb47b8 100644 --- a/crates/turtle/src/atuin_server/handlers/v0/mod.rs +++ b/crates/server/src/handlers/v0/mod.rs diff --git a/crates/turtle/src/atuin_server/handlers/v0/record.rs b/crates/server/src/handlers/v0/record.rs index 9350e1c8..61fa2946 100644 --- a/crates/turtle/src/atuin_server/handlers/v0/record.rs +++ b/crates/server/src/handlers/v0/record.rs @@ -3,12 +3,12 @@ use metrics::counter; use serde::Deserialize; use tracing::{error, instrument}; -use crate::atuin_server::{ +use crate::{ handlers::{ErrorResponse, ErrorResponseStatus, RespExt}, router::{AppState, UserAuth}, }; -use crate::atuin_common::record::{EncryptedData, HostId, Record, RecordIdx, RecordStatus}; +use turtle_common::record::{EncryptedData, HostId, Record, RecordIdx, RecordStatus}; #[instrument(skip_all, fields(user.id = user.id.to_string()))] pub(crate) async fn post( diff --git a/crates/server/src/main.rs b/crates/server/src/main.rs new file mode 100644 index 00000000..56c44d2d --- /dev/null +++ b/crates/server/src/main.rs @@ -0,0 +1,146 @@ +use std::future::Future; +use std::net::SocketAddr; + +use axum::{Router, serve}; +use clap::Parser; +use database::db::ServerPostgres; +use eyre::{Context, Result, eyre}; +use tokio::net::TcpListener; +use tokio::signal; +use tracing_subscriber::util::SubscriberInitExt; + +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(crate) enum Cmd { + /// Start the server + Start { + /// The host address to bind + #[clap(long)] + host: Option<String>, + + /// The port to bind + #[clap(long, short)] + port: Option<u16>, + }, + + /// Print server example configuration + DefaultConfig, +} + +impl Cmd { + async fn run(self) -> Result<()> { + match self { + Self::Start { host, port } => { + let settings = Settings::new().wrap_err("could not load server settings")?; + let host = host.as_ref().unwrap_or(&settings.host).clone(); + let port = port.unwrap_or(settings.port); + let addr = SocketAddr::new(host.parse()?, port); + + if settings.metrics.enable { + tokio::spawn(launch_metrics_server( + settings.metrics.host.clone(), + settings.metrics.port, + )); + } + + match settings.db_settings.db_type() { + DbType::Postgres => launch(settings, addr).await, + DbType::Unknown => { + Err(eyre!("db_uri must start with postgres:// or sqlite://")) + } + } + } + Self::DefaultConfig => { + // TODO(@bpeetz): Add this back <2026-06-11> + println!("TODO"); + Ok(()) + } + } + } +} + +#[tokio::main] +async fn main() -> Result<()> { + if let Err(e) = tracing_subscriber::registry().try_init() { + eprintln!("failed to initialize logging: {e}"); + } + + 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) +} diff --git a/crates/turtle/src/atuin_server/metrics.rs b/crates/server/src/metrics.rs index 6380bef1..6380bef1 100644 --- a/crates/turtle/src/atuin_server/metrics.rs +++ b/crates/server/src/metrics.rs diff --git a/crates/turtle/src/atuin_server/router.rs b/crates/server/src/router.rs index d6fe9482..5ba15e62 100644 --- a/crates/turtle/src/atuin_server/router.rs +++ b/crates/server/src/router.rs @@ -1,7 +1,6 @@ -use crate::{ - atuin_common::api::{ATUIN_CARGO_VERSION, ATUIN_HEADER_VERSION, ErrorResponse}, - atuin_server::database::{db::ServerPostgres, models::User}, -}; +use crate::database::{db::ServerPostgres, models::User}; +use turtle_common::api::{ATUIN_CARGO_VERSION, ATUIN_HEADER_VERSION, ErrorResponse}; + use axum::{ Router, extract::{FromRequestParts, Path, Request}, @@ -16,7 +15,7 @@ use tower_http::trace::TraceLayer; use uuid::Uuid; use super::handlers; -use crate::atuin_server::{ +use crate::{ handlers::{ErrorResponseStatus, RespExt}, metrics, settings::Settings, diff --git a/crates/turtle/src/atuin_server/settings.rs b/crates/server/src/settings.rs index 73fc5e42..60837525 100644 --- a/crates/turtle/src/atuin_server/settings.rs +++ b/crates/server/src/settings.rs @@ -6,7 +6,7 @@ use fs_err::create_dir_all; use serde::{Deserialize, Serialize}; use tracing::info; -use crate::atuin_server::database::DbSettings; +use crate::database::DbSettings; #[derive(Clone, Debug, Deserialize, Serialize)] pub(crate) struct Metrics { @@ -50,18 +50,6 @@ pub(crate) struct Settings { impl Settings { pub(crate) fn new() -> Result<Self> { - let mut config_file = std::env::var("ATUIN_CONFIG_DIR").map_or_else( - |_| { - let mut config_file = PathBuf::new(); - let config_dir = crate::atuin_common::utils::config_dir(); - config_file.push(config_dir); - config_file - }, - PathBuf::from, - ); - - config_file.push("server.toml"); - // create the config file if it does not exist let mut config_builder = Config::builder() .set_default("host", "127.0.0.1")? @@ -79,23 +67,25 @@ impl Settings { .separator("__"), ); - config_builder = if config_file.exists() { - config_builder.add_source(ConfigFile::new( - config_file.to_str().unwrap(), - FileFormat::Toml, - )) - } else { - // TODO(@bpeetz): Rework the config handling, so that we can actually auto-write a - // file with defaults. <2026-06-13> - create_dir_all(config_file.parent().unwrap())?; + if let Ok(config_file) = std::env::var("TURTLE_SERVER_CONFIG").map(PathBuf::from) { + config_builder = if config_file.exists() { + config_builder.add_source(ConfigFile::new( + config_file.to_str().unwrap(), + FileFormat::Toml, + )) + } else { + // TODO(@bpeetz): Rework the config handling, so that we can actually auto-write a + // file with defaults. <2026-06-13> + create_dir_all(config_file.parent().unwrap())?; - info!( - "No config file at: `{}`. Not adding one.", - config_file.display() - ); + info!( + "No config file at: `{}`. Not adding one.", + config_file.display() + ); - config_builder - }; + config_builder + }; + } let config = config_builder.build()?; |
