aboutsummaryrefslogtreecommitdiffstats
path: root/crates/server/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/server/src')
-rw-r--r--crates/server/src/database/db/mod.rs18
-rw-r--r--crates/server/src/database/db/wrappers.rs4
-rw-r--r--crates/server/src/database/mod.rs18
-rw-r--r--crates/server/src/database/models.rs4
-rw-r--r--crates/server/src/handlers/mod.rs14
-rw-r--r--crates/server/src/handlers/v0/mod.rs2
-rw-r--r--crates/server/src/handlers/v0/record.rs10
-rw-r--r--crates/server/src/lib.rs12
-rw-r--r--crates/server/src/main.rs61
-rw-r--r--crates/server/src/metrics.rs4
-rw-r--r--crates/server/src/router.rs17
-rw-r--r--crates/server/src/settings.rs30
12 files changed, 126 insertions, 68 deletions
diff --git a/crates/server/src/database/db/mod.rs b/crates/server/src/database/db/mod.rs
index 77bd0c61..c95a2ed4 100644
--- a/crates/server/src/database/db/mod.rs
+++ b/crates/server/src/database/db/mod.rs
@@ -2,11 +2,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 sqlx::postgres::PgPoolOptions;
+use turtle_common::record::{EncryptedData, HostId, Record, RecordIdx, RecordStatus};
use tracing::instrument;
use uuid::Uuid;
@@ -17,7 +15,7 @@ mod wrappers;
const MIN_PG_VERSION: u32 = 14;
#[derive(Clone)]
-pub(crate) struct ServerPostgres {
+pub struct ServerPostgres {
pool: sqlx::Pool<sqlx::postgres::Postgres>,
/// Optional read replica pool for read-only queries
read_pool: Option<sqlx::Pool<sqlx::postgres::Postgres>>,
@@ -32,7 +30,7 @@ impl ServerPostgres {
}
impl ServerPostgres {
- pub(crate) async fn new(settings: &DbSettings) -> DbResult<Self> {
+ pub async fn new(settings: &DbSettings) -> DbResult<Self> {
let pool = PgPoolOptions::new()
.max_connections(100)
.connect(settings.db_uri.as_str())
@@ -93,7 +91,7 @@ impl ServerPostgres {
}
#[instrument(skip_all)]
- pub(crate) async fn add_records(
+ pub async fn add_records(
&self,
user: &User,
records: &[Record<EncryptedData>],
@@ -110,7 +108,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(
"
@@ -169,7 +167,7 @@ impl ServerPostgres {
}
#[instrument(skip_all)]
- pub(crate) async fn next_records(
+ pub async fn next_records(
&self,
user: &User,
host: HostId,
@@ -222,7 +220,7 @@ impl ServerPostgres {
Ok(ret)
}
- pub(crate) async fn status(&self, user: &User) -> DbResult<RecordStatus> {
+ pub async fn status(&self, user: &User) -> DbResult<RecordStatus> {
// If IDX_CACHE_ROLLOUT is set, then we
// 1. Read the value of the var, use it as a % chance of using the cache
// 2. If we use the cache, just read from the cache table
diff --git a/crates/server/src/database/db/wrappers.rs b/crates/server/src/database/db/wrappers.rs
index 0315e331..8054289a 100644
--- a/crates/server/src/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 struct DbRecord(pub Record<EncryptedData>);
impl<'a> ::sqlx::FromRow<'a, PgRow> for DbRecord {
fn from_row(row: &'a PgRow) -> ::sqlx::Result<Self> {
diff --git a/crates/server/src/database/mod.rs b/crates/server/src/database/mod.rs
index 43fe5c3b..c05fa783 100644
--- a/crates/server/src/database/mod.rs
+++ b/crates/server/src/database/mod.rs
@@ -1,12 +1,12 @@
-pub(crate) mod db;
-pub(crate) mod models;
+pub mod db;
+pub mod models;
use std::fmt::{Debug, Display};
use serde::{Deserialize, Serialize};
#[derive(Debug)]
-pub(crate) enum DbError {
+pub enum DbError {
NotFound,
Other(eyre::Report),
}
@@ -43,24 +43,24 @@ impl From<sqlx::Error> for DbError {
impl std::error::Error for DbError {}
-pub(crate) type DbResult<T> = Result<T, DbError>;
+pub type DbResult<T> = Result<T, DbError>;
#[derive(Debug, PartialEq)]
-pub(crate) enum DbType {
+pub enum DbType {
Postgres,
Unknown,
}
#[derive(Clone, Deserialize, Serialize)]
-pub(crate) struct DbSettings {
- pub(crate) db_uri: String,
+pub struct DbSettings {
+ pub db_uri: String,
/// Optional URI for read replicas. If set, read-only queries will use this connection.
- pub(crate) read_db_uri: Option<String>,
+ pub read_db_uri: Option<String>,
}
impl DbSettings {
- pub(crate) fn db_type(&self) -> DbType {
+ pub fn db_type(&self) -> DbType {
if self.db_uri.starts_with("postgres://") || self.db_uri.starts_with("postgresql://") {
DbType::Postgres
} else {
diff --git a/crates/server/src/database/models.rs b/crates/server/src/database/models.rs
index 3fa6f471..9f6241ae 100644
--- a/crates/server/src/database/models.rs
+++ b/crates/server/src/database/models.rs
@@ -1,5 +1,5 @@
use uuid::Uuid;
-pub(crate) struct User {
- pub(crate) id: Uuid,
+pub struct User {
+ pub id: Uuid,
}
diff --git a/crates/server/src/handlers/mod.rs b/crates/server/src/handlers/mod.rs
index c4332f80..e24bad14 100644
--- a/crates/server/src/handlers/mod.rs
+++ b/crates/server/src/handlers/mod.rs
@@ -1,13 +1,13 @@
-use crate::atuin_common::api::{ErrorResponse, IndexResponse};
+use turtle_common::api::{ErrorResponse, IndexResponse};
use axum::{Json, extract::State, http, response::IntoResponse};
use crate::router::AppState;
-pub(crate) mod v0;
+pub mod v0;
const VERSION: &str = env!("CARGO_PKG_VERSION");
-pub(crate) async fn index(state: State<AppState>) -> Json<IndexResponse> {
+pub async fn index(state: State<AppState>) -> Json<IndexResponse> {
let homage = r#""Through the fathomless deeps of space swims the star turtle Great A'Tuin, bearing on its back the four giant elephants who carry on their shoulders the mass of the Discworld." -- Sir Terry Pratchett"#;
let version = state
@@ -28,12 +28,12 @@ impl IntoResponse for ErrorResponseStatus<'_> {
}
}
-pub(crate) struct ErrorResponseStatus<'a> {
- pub(crate) error: ErrorResponse<'a>,
- pub(crate) status: http::StatusCode,
+pub struct ErrorResponseStatus<'a> {
+ pub error: ErrorResponse<'a>,
+ pub status: http::StatusCode,
}
-pub(crate) trait RespExt<'a> {
+pub trait RespExt<'a> {
fn with_status(self, status: http::StatusCode) -> ErrorResponseStatus<'a>;
fn reply(reason: &'a str) -> Self;
}
diff --git a/crates/server/src/handlers/v0/mod.rs b/crates/server/src/handlers/v0/mod.rs
index 78fb47b8..2066636c 100644
--- a/crates/server/src/handlers/v0/mod.rs
+++ b/crates/server/src/handlers/v0/mod.rs
@@ -1 +1 @@
-pub(crate) mod record;
+pub mod record;
diff --git a/crates/server/src/handlers/v0/record.rs b/crates/server/src/handlers/v0/record.rs
index 0381ded8..f7758c1a 100644
--- a/crates/server/src/handlers/v0/record.rs
+++ b/crates/server/src/handlers/v0/record.rs
@@ -8,10 +8,10 @@ use crate::{
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(
+pub async fn post(
UserAuth(user): UserAuth,
state: State<AppState>,
Json(records): Json<Vec<Record<EncryptedData>>>,
@@ -50,7 +50,7 @@ pub(crate) async fn post(
}
#[instrument(skip_all, fields(user.id = user.id.to_string()))]
-pub(crate) async fn index(
+pub async fn index(
UserAuth(user): UserAuth,
state: State<AppState>,
) -> Result<Json<RecordStatus>, ErrorResponseStatus<'static>> {
@@ -75,7 +75,7 @@ pub(crate) async fn index(
}
#[derive(Deserialize)]
-pub(crate) struct NextParams {
+pub struct NextParams {
host: HostId,
tag: String,
start: Option<RecordIdx>,
@@ -83,7 +83,7 @@ pub(crate) struct NextParams {
}
#[instrument(skip_all, fields(user.id = user.id.to_string()))]
-pub(crate) async fn next(
+pub async fn next(
params: Query<NextParams>,
UserAuth(user): UserAuth,
state: State<AppState>,
diff --git a/crates/server/src/lib.rs b/crates/server/src/lib.rs
index a4b10acf..726afeea 100644
--- a/crates/server/src/lib.rs
+++ b/crates/server/src/lib.rs
@@ -5,14 +5,14 @@ use axum::{Router, serve};
use database::db::ServerPostgres;
use eyre::{Context, Result};
-pub(crate) mod database;
+pub mod database;
mod handlers;
mod metrics;
mod router;
-pub(crate) use settings::Settings;
+pub use settings::Settings;
-pub(crate) mod settings;
+pub mod settings;
use tokio::net::TcpListener;
use tokio::signal;
@@ -31,7 +31,7 @@ async fn shutdown_signal() {
eprintln!("Shutting down gracefully...");
}
-pub(crate) async fn launch(settings: Settings, addr: SocketAddr) -> Result<()> {
+pub async fn launch(settings: Settings, addr: SocketAddr) -> Result<()> {
launch_with_tcp_listener(
settings,
TcpListener::bind(addr)
@@ -42,7 +42,7 @@ pub(crate) async fn launch(settings: Settings, addr: SocketAddr) -> Result<()> {
.await
}
-pub(crate) async fn launch_with_tcp_listener(
+pub async fn launch_with_tcp_listener(
settings: Settings,
listener: TcpListener,
shutdown: impl Future<Output = ()> + Send + 'static,
@@ -58,7 +58,7 @@ pub(crate) async fn launch_with_tcp_listener(
// The separate listener means it's much easier to ensure metrics are not accidentally exposed to
// the public.
-pub(crate) async fn launch_metrics_server(host: String, port: u16) -> Result<()> {
+pub async fn launch_metrics_server(host: String, port: u16) -> Result<()> {
let listener = TcpListener::bind((host, port))
.await
.context("failed to bind metrics tcp")?;
diff --git a/crates/server/src/main.rs b/crates/server/src/main.rs
new file mode 100644
index 00000000..94d6d143
--- /dev/null
+++ b/crates/server/src/main.rs
@@ -0,0 +1,61 @@
+use std::net::SocketAddr;
+
+use turtle_server::{Settings, database::DbType, launch, launch_metrics_server};
+
+use clap::Parser;
+use eyre::{Context, Result, eyre};
+
+#[derive(Parser, Clone, Debug)]
+#[command(infer_subcommands = true)]
+pub 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 {
+ pub 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<()> {
+ Cmd::parse().run().await
+}
diff --git a/crates/server/src/metrics.rs b/crates/server/src/metrics.rs
index 6380bef1..987af807 100644
--- a/crates/server/src/metrics.rs
+++ b/crates/server/src/metrics.rs
@@ -7,7 +7,7 @@ use axum::{
};
use metrics_exporter_prometheus::{Matcher, PrometheusBuilder, PrometheusHandle};
-pub(crate) fn setup_metrics_recorder() -> PrometheusHandle {
+pub fn setup_metrics_recorder() -> PrometheusHandle {
const EXPONENTIAL_SECONDS: &[f64] = &[
0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0,
];
@@ -25,7 +25,7 @@ pub(crate) fn setup_metrics_recorder() -> PrometheusHandle {
/// Middleware to record some common HTTP metrics
/// Generic over B to allow for arbitrary body types (eg Vec<u8>, Streams, a deserialized thing, etc)
/// Someday tower-http might provide a metrics middleware: <https://github.com/tower-rs/tower-http/issues/57>
-pub(crate) async fn track_metrics(req: Request, next: Next) -> impl IntoResponse {
+pub async fn track_metrics(req: Request, next: Next) -> impl IntoResponse {
let start = Instant::now();
let path = req.extensions().get::<MatchedPath>().map_or_else(
diff --git a/crates/server/src/router.rs b/crates/server/src/router.rs
index 2a5c5f15..b0b98ecc 100644
--- a/crates/server/src/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},
@@ -22,7 +21,7 @@ use crate::{
settings::Settings,
};
-pub(crate) struct UserAuth(pub(crate) User);
+pub struct UserAuth(pub User);
impl FromRequestParts<AppState> for UserAuth {
type Rejection = ErrorResponseStatus<'static>;
@@ -66,12 +65,12 @@ async fn semver(request: Request, next: Next) -> Response {
}
#[derive(Clone)]
-pub(crate) struct AppState {
- pub(crate) database: ServerPostgres,
- pub(crate) settings: Settings,
+pub struct AppState {
+ pub database: ServerPostgres,
+ pub settings: Settings,
}
-pub(crate) fn router(database: ServerPostgres, settings: Settings) -> Router {
+pub fn router(database: ServerPostgres, settings: Settings) -> Router {
let routes = Router::new()
.route("/", get(handlers::index))
.route("/api/v0/{user_id}/record", post(handlers::v0::record::post))
diff --git a/crates/server/src/settings.rs b/crates/server/src/settings.rs
index 6a32fb9b..e6626b15 100644
--- a/crates/server/src/settings.rs
+++ b/crates/server/src/settings.rs
@@ -9,11 +9,11 @@ use tracing::info;
use crate::database::DbSettings;
#[derive(Clone, Debug, Deserialize, Serialize)]
-pub(crate) struct Metrics {
+pub struct Metrics {
#[serde(alias = "enabled")]
- pub(crate) enable: bool,
- pub(crate) host: String,
- pub(crate) port: u16,
+ pub enable: bool,
+ pub host: String,
+ pub port: u16,
}
impl Default for Metrics {
@@ -27,29 +27,29 @@ impl Default for Metrics {
}
#[derive(Clone, Debug, Deserialize, Serialize)]
-pub(crate) struct Settings {
- pub(crate) host: String,
- pub(crate) port: u16,
- pub(crate) path: String,
- pub(crate) max_history_length: usize,
- pub(crate) max_record_size: usize,
- pub(crate) page_size: i64,
- pub(crate) metrics: Metrics,
+pub struct Settings {
+ pub host: String,
+ pub port: u16,
+ pub path: String,
+ pub max_history_length: usize,
+ pub max_record_size: usize,
+ pub page_size: i64,
+ pub metrics: Metrics,
/// Advertise a version that is not what we are _actually_ running
/// Many clients compare their version with api.atuin.sh, and if they differ, notify the user
/// that an update is available.
/// Now that we take beta releases, we should be able to advertise a different version to avoid
/// notifying users when the server runs something that is not a stable release.
- pub(crate) fake_version: Option<String>,
+ pub fake_version: Option<String>,
#[serde(flatten)]
#[expect(clippy::struct_field_names)]
- pub(crate) db_settings: DbSettings,
+ pub db_settings: DbSettings,
}
impl Settings {
- pub(crate) fn new() -> Result<Self> {
+ pub fn new() -> Result<Self> {
// create the config file if it does not exist
let mut config_builder = Config::builder()
.set_default("host", "127.0.0.1")?