feat: added flexible configuration system using figment

This commit is contained in:
Jef Roosens 2025-03-08 22:07:57 +01:00
parent f16cdfdfff
commit f9ffc21a3f
No known key found for this signature in database
GPG key ID: 21FD3D77D56BAF49
11 changed files with 227 additions and 74 deletions

View file

@ -16,16 +16,16 @@ pub enum AddCommand {
}
impl DbCommand {
pub fn run(&self, cli: &super::Cli) -> u8 {
pub fn run(&self, config: &crate::config::Config) -> u8 {
match self {
DbCommand::Add(cmd) => cmd.run(cli),
DbCommand::Add(cmd) => cmd.run(config),
}
}
}
impl AddCommand {
pub fn run(&self, cli: &super::Cli) -> u8 {
match self.run_err(cli) {
pub fn run(&self, config: &crate::config::Config) -> u8 {
match self.run_err(config) {
Ok(()) => 0,
Err(err) => {
eprintln!("An error occured: {}", err.stack());
@ -35,8 +35,8 @@ impl AddCommand {
}
}
pub fn run_err(&self, cli: &super::Cli) -> DbResult<()> {
let pool = crate::db::initialize_db(cli.data_dir.join(crate::DB_FILENAME), false)?;
pub fn run_err(&self, config: &crate::config::Config) -> DbResult<()> {
let pool = crate::db::initialize_db(config.data_dir.join(crate::DB_FILENAME), false)?;
match self {
Self::User { username, password } => {

View file

@ -3,36 +3,78 @@ mod serve;
use std::path::PathBuf;
use clap::{Parser, Subcommand};
use clap::{Args, Parser, Subcommand};
use figment::{
providers::{Env, Format, Serialized, Toml},
Figment,
};
use serde::Serialize;
/// Otter is a lightweight implementation of the Gpodder API, designed to be used for small
/// personal deployments.
#[derive(Parser)]
pub struct Cli {
#[arg(
long = "data",
default_value = "./data",
value_name = "DATA_DIR",
env = "OTTER_DATA_DIR"
)]
pub data_dir: PathBuf,
#[command(flatten)]
pub config: ClapConfig,
#[command(subcommand)]
pub cmd: Command,
}
#[derive(Serialize, Args, Clone)]
pub struct ClapConfig {
#[arg(
short,
long = "config",
env = "OTTER_CONFIG_FILE",
value_name = "CONFIG_FILE"
)]
config_file: Option<PathBuf>,
#[serde(skip_serializing_if = "Option::is_none")]
#[arg(long = "data", value_name = "DATA_DIR")]
data_dir: Option<PathBuf>,
#[serde(skip_serializing_if = "Option::is_none")]
#[arg(short, long, value_name = "DOMAIN")]
domain: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[arg(short, long, value_name = "PORT")]
port: Option<u16>,
}
#[derive(Subcommand)]
pub enum Command {
Serve(serve::ServeCommand),
Serve,
#[command(subcommand)]
Db(db::DbCommand),
}
impl Cli {
pub fn run(&self) -> u8 {
let mut figment = Figment::new();
if let Some(config_path) = &self.config.config_file {
figment = figment.merge(Toml::file(config_path));
}
let config: crate::config::Config = match figment
.merge(Env::prefixed("OTTER_"))
.merge(Serialized::defaults(self.config.clone()))
.extract()
{
Ok(config) => config,
Err(err) => {
eprintln!("{}", err);
return 1;
}
};
match &self.cmd {
Command::Serve(cmd) => cmd.run(self),
Command::Db(cmd) => cmd.run(self),
Command::Serve => serve::serve(&config),
Command::Db(cmd) => cmd.run(&config),
}
}
}

View file

@ -1,57 +1,31 @@
use clap::Args;
use crate::{db, server};
/// Run the Otter web server
#[derive(Args)]
pub struct ServeCommand {
#[arg(
short,
long,
default_value = "127.0.0.1",
value_name = "DOMAIN",
env = "OTTER_DOMAIN"
)]
domain: String,
pub fn serve(config: &crate::config::Config) -> u8 {
tracing_subscriber::fmt::init();
#[arg(
short,
long,
default_value = "8080",
value_name = "PORT",
env = "OTTER_PORT"
)]
port: u16,
}
impl ServeCommand {
pub fn run(&self, cli: &super::Cli) -> u8 {
tracing_subscriber::fmt::init();
tracing::info!("Initializing database and running migrations");
let pool = db::initialize_db(cli.data_dir.join(crate::DB_FILENAME), true).unwrap();
let ctx = server::Context {
repo: db::SqliteRepository::from(pool),
};
let app = server::app(ctx);
let rt = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap();
let address = format!("{}:{}", self.domain, self.port);
tracing::info!("Starting server on {address}");
rt.block_on(async {
let listener = tokio::net::TcpListener::bind(address).await.unwrap();
axum::serve(listener, app.into_make_service())
.await
.unwrap()
});
0
}
tracing::info!("Initializing database and running migrations");
let pool = db::initialize_db(config.data_dir.join(crate::DB_FILENAME), true).unwrap();
let ctx = server::Context {
repo: db::SqliteRepository::from(pool),
};
let app = server::app(ctx);
let rt = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap();
let address = format!("{}:{}", config.domain, config.port);
tracing::info!("Starting server on {address}");
rt.block_on(async {
let listener = tokio::net::TcpListener::bind(address).await.unwrap();
axum::serve(listener, app.into_make_service())
.await
.unwrap()
});
0
}