From 9622c20fe02a11857ebf9b32eb5d12be6e3e858f Mon Sep 17 00:00:00 2001 From: Jordan Doyle Date: Wed, 20 Oct 2021 22:43:47 +0100 Subject: [PATCH] chartered-git configuration file --- Cargo.lock | 1 + chartered-git/.gitignore | 1 + chartered-git/Cargo.toml | 1 + chartered-db/src/lib.rs | 35 +++++++++++++++++++++++++++++++++++ chartered-git/src/config.rs | 9 +++++++++ chartered-git/src/main.rs | 40 +++++++++++++++++++++++++++++++++++++++- chartered-web/src/config.rs | 1 + chartered-web/src/main.rs | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++-- book/src/guide/config-reference.md | 40 ++++++++++++++++++++++++++++++++++++++++ 9 files changed, 157 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3ea4769..cca583d 100644 --- a/Cargo.lock +++ a/Cargo.lock @@ -515,6 +515,7 @@ "thrussh-keys", "tokio", "tokio-util", + "toml", "tracing", "tracing-subscriber", "url", diff --git a/chartered-git/.gitignore b/chartered-git/.gitignore new file mode 100644 index 0000000..5b6c096 100644 --- /dev/null +++ a/chartered-git/.gitignore @@ -1,0 +1,1 @@ +config.toml diff --git a/chartered-git/Cargo.toml b/chartered-git/Cargo.toml index 6c9bfff..ecc51b2 100644 --- a/chartered-git/Cargo.toml +++ a/chartered-git/Cargo.toml @@ -32,6 +32,7 @@ thrussh-keys = "0.21" tokio = { version = "1", features = ["full"] } tokio-util = { version = "0.6", features = ["codec"] } +toml = "0.5" tracing = "0.1" tracing-subscriber = "0.2" url = "2" diff --git a/chartered-db/src/lib.rs b/chartered-db/src/lib.rs index b506c72..d58cd18 100644 --- a/chartered-db/src/lib.rs +++ a/chartered-db/src/lib.rs @@ -57,8 +57,11 @@ pub type Connection = diesel_tracing::sqlite::InstrumentedSqliteConnection; #[cfg(feature = "postgres")] -pub type Connection = diesel_tracing::postgres::InstrumentedPostgresConnection; +pub type Connection = diesel_tracing::pg::InstrumentedPgConnection; +#[cfg(all(feature = "sqlite", feature = "postgres"))] +compile_error!("Only one database backend must be enabled using --features [sqlite|postgres]"); + #[cfg(not(any(feature = "sqlite", feature = "postgres")))] compile_error!( "At least one database backend must be enabled using `--features [sqlite|postgres]`" @@ -71,16 +74,42 @@ embed_migrations!(); -pub fn init() -> Result { - let pool = Pool::new(ConnectionManager::new("chartered.db"))?; +pub fn init(connection_uri: &str) -> Result { + let connection_uri = parse_connection_uri(connection_uri)?; + let pool = Pool::new(ConnectionManager::new(connection_uri))?; embedded_migrations::run_with_output(&pool.get()?, &mut std::io::stdout())?; Ok(Arc::new(pool)) +} + +#[cfg(feature = "sqlite")] +pub fn parse_connection_uri(connection_uri: &str) -> Result<&str> { + if connection_uri.starts_with("sqlite://") { + Ok(connection_uri.trim_start_matches("sqlite://")) + } else { + Err(Error::SqliteConnectionUri) + } +} + +#[cfg(feature = "postgres")] +pub fn parse_connection_uri(connection_uri: &str) -> Result<&str> { + if connection_uri.starts_with("postgres://") { + Ok(connection_uri) + } else { + Err(Error::PostgresConnectionUri) + } } #[derive(Error, Display, Debug)] pub enum Error { + /// connection_uri must be in the format `sqlite:///path/to/file.db` or `sqlite://:memory:` + SqliteConnectionUri, + /** + * connection_uri must be a postgres connection uri as described in: + * https://www.postgresql.org/docs/9.4/libpq-connect.html#LIBPQ-CONNSTRING + */ + PostgresConnectionUri, /// Failed to initialise to database connection pool Connection(#[from] diesel::r2d2::PoolError), /// Failed to run migrations to bring database schema up-to-date: {0} diff --git a/chartered-git/src/config.rs b/chartered-git/src/config.rs new file mode 100644 index 0000000..8103e91 100644 --- /dev/null +++ a/chartered-git/src/config.rs @@ -1,0 +1,9 @@ +use serde::Deserialize; +use std::net::SocketAddr; + +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Config { + pub bind_address: SocketAddr, + pub database_uri: String, +} diff --git a/chartered-git/src/main.rs b/chartered-git/src/main.rs index a6a1607..d7d4675 100644 --- a/chartered-git/src/main.rs +++ a/chartered-git/src/main.rs @@ -1,6 +1,7 @@ #![deny(clippy::pedantic)] #![deny(rust_2018_idioms)] mod command_handlers; +mod config; mod generators; mod tree; @@ -19,8 +20,9 @@ use arrayvec::ArrayVec; use bytes::BytesMut; +use clap::Parser; use futures::future::Future; -use std::{fmt::Write, pin::Pin, sync::Arc}; +use std::{fmt::Write, path::PathBuf, pin::Pin, sync::Arc}; use thrussh::{ server::{self, Auth, Session}, ChannelId, CryptoVec, @@ -30,24 +32,48 @@ use tracing::{debug, error, info, warn, Instrument}; use url::Url; +#[derive(Parser)] +#[clap(version = clap::crate_version!(), author = clap::crate_authors!())] +pub struct Opts { + #[clap(short, long, parse(from_occurrences))] + verbose: i32, + #[clap(short, long)] + config: PathBuf, +} + #[tokio::main] #[allow(clippy::semicolon_if_nothing_returned)] // broken clippy lint -async fn main() { +async fn main() -> anyhow::Result<()> { + let opts: Opts = Opts::parse(); + + std::env::set_var( + "RUST_LOG", + match opts.verbose { + 1 => "debug", + 2 => "trace", + _ => "info", + }, + ); + + let config: config::Config = toml::from_slice(&std::fs::read(&opts.config)?)?; + tracing_subscriber::fmt::init(); - let config = Arc::new(thrussh::server::Config { + let trussh_config = Arc::new(thrussh::server::Config { methods: thrussh::MethodSet::PUBLICKEY, keys: vec![key::KeyPair::generate_ed25519().unwrap()], ..thrussh::server::Config::default() }); let server = Server { - db: chartered_db::init().unwrap(), + db: chartered_db::init(&config.database_uri)?, }; + + info!("SSH server listening on {}", config.bind_address); + + thrussh::server::run(trussh_config, &config.bind_address.to_string(), server).await?; - thrussh::server::run(config, "127.0.0.1:2233", server) - .await - .unwrap(); + Ok(()) } #[derive(Clone)] diff --git a/chartered-web/src/config.rs b/chartered-web/src/config.rs index e6fcd0e..20fbacb 100644 --- a/chartered-web/src/config.rs +++ a/chartered-web/src/config.rs @@ -20,6 +20,7 @@ #[serde(deny_unknown_fields)] pub struct Config { pub bind_address: SocketAddr, + pub database_uri: String, pub storage_uri: String, pub auth: AuthConfig, #[serde(deserialize_with = "deserialize_encryption_key")] diff --git a/chartered-web/src/main.rs b/chartered-web/src/main.rs index f13872c..7ab6c49 100644 --- a/chartered-web/src/main.rs +++ a/chartered-web/src/main.rs @@ -12,10 +12,11 @@ AddExtensionLayer, Router, }; use clap::Parser; -use std::path::PathBuf; -use std::sync::Arc; +use std::{fmt::Formatter, path::PathBuf, sync::Arc}; +use thiserror::Error; use tower::ServiceBuilder; use tower_http::cors::{Any, CorsLayer}; +use tracing::info; #[derive(Parser)] #[clap(version = clap::crate_version!(), author = clap::crate_authors!())] @@ -56,15 +57,24 @@ #[tokio::main] #[allow(clippy::semicolon_if_nothing_returned)] // lint breaks with tokio::main -async fn main() { +async fn main() -> Result<(), InitError> { let opts: Opts = Opts::parse(); - let config: config::Config = toml::from_slice(&std::fs::read(&opts.config).unwrap()).unwrap(); - let bind_address = config.bind_address; + std::env::set_var( + "RUST_LOG", + match opts.verbose { + 1 => "debug", + 2 => "trace", + _ => "info", + }, + ); + + let config: config::Config = toml::from_slice(&std::fs::read(&opts.config)?)?; tracing_subscriber::fmt::init(); - let pool = chartered_db::init().unwrap(); + let bind_address = config.bind_address; + let pool = chartered_db::init(&config.database_uri)?; let middleware_stack = ServiceBuilder::new() .layer_fn(middleware::logging::LoggingMiddleware) @@ -107,15 +117,39 @@ ) .layer(AddExtensionLayer::new(pool)) .layer(AddExtensionLayer::new(Arc::new( - config.create_oidc_clients().await.unwrap(), + config.create_oidc_clients().await?, ))) .layer(AddExtensionLayer::new(Arc::new( - config.get_file_system().await.unwrap(), + config.get_file_system().await?, ))) .layer(AddExtensionLayer::new(Arc::new(config))); + + info!("HTTP server listening on {}", bind_address); axum::Server::bind(&bind_address) .serve(app.into_make_service_with_connect_info::()) .await - .unwrap(); + .map_err(|e| InitError::ServerSpawn(Box::new(e)))?; + + Ok(()) +} + +#[derive(Error)] +pub enum InitError { + #[error("Failed to read configuration: {0}")] + ConfigRead(#[from] std::io::Error), + #[error("Failed to parse configuration: {0}")] + ConfigParse(#[from] toml::de::Error), + #[error("Configuration error: {0}")] + Config(#[from] config::Error), + #[error("Database error: {0}")] + Database(#[from] chartered_db::Error), + #[error("Failed to spawn HTTP server: {0}")] + ServerSpawn(Box), +} + +impl std::fmt::Debug for InitError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self) + } } diff --git a/book/src/guide/config-reference.md b/book/src/guide/config-reference.md index 681d2aa..a6e9bf8 100644 --- a/book/src/guide/config-reference.md +++ a/book/src/guide/config-reference.md @@ -1,14 +1,41 @@ # Configuration Reference An exhaustive list of all configuration values in chartered. -## chartered-web +Configuration files are written in the TOML format, with simple key-value pairs inside of sections (tables). The following +is a quick overview of all settings, with detailed descriptions found below +## chartered-git + ### Configuration format -Configuration files are written in the TOML format, with simple key-value pairs inside of sections (tables). The following -is a quick overview of all settings, with detailed descriptions found below +```toml +bind_address = "127.0.0.1:2233" +database_uri = "postgres://user:password@localhost/chartered" # can also be `sqlite://` +``` + +### Configuration keys + +#### `bind_address` +- Type: string +The IP address and port the web server should be bound to. + +#### `database_uri` +- Type: string + +A connection string for the backing database, either `postgres` or `sqlite`, either in the +format of `postgres://user:password@localhost/chartered` (a [postgres connection URI][pg-uri]), +`sqlite:///path/to/chartered.db` or `sqlite://:memory:`. + +[pg-uri]: https://www.postgresql.org/docs/9.4/libpq-connect.html#LIBPQ-CONNSTRING + +--- + +## chartered-web + +### Configuration format + ```toml bind_address = "127.0.0.1:8080" database_uri = "postgres://user:password@localhost/chartered" # can also be `sqlite://` @@ -33,8 +60,13 @@ The IP address and port the web server should be bound to. #### `database_uri` +- Type: string + +A connection string for the backing database, either `postgres` or `sqlite`, either in the +format of `postgres://user:password@localhost/chartered` (a [postgres connection URI][pg-uri]), +`sqlite:///path/to/chartered.db` or `sqlite://:memory:`. -A JDBC-like connection string for the backing database, either `postgres` or `sqlite`. +[pg-uri]: https://www.postgresql.org/docs/9.4/libpq-connect.html#LIBPQ-CONNSTRING #### `storage_uri` - Type: string -- rgit 0.1.3