feat: added flexible configuration system using figment
This commit is contained in:
parent
f16cdfdfff
commit
f9ffc21a3f
11 changed files with 227 additions and 74 deletions
|
|
@ -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 } => {
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
25
src/config.rs
Normal file
25
src/config.rs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Config {
|
||||
#[serde(default = "default_data_dir")]
|
||||
pub data_dir: PathBuf,
|
||||
#[serde(default = "default_domain")]
|
||||
pub domain: String,
|
||||
#[serde(default = "default_port")]
|
||||
pub port: u16,
|
||||
}
|
||||
|
||||
fn default_data_dir() -> PathBuf {
|
||||
PathBuf::from("./data")
|
||||
}
|
||||
|
||||
fn default_domain() -> String {
|
||||
"127.0.0.1".to_string()
|
||||
}
|
||||
|
||||
fn default_port() -> u16 {
|
||||
8080
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
mod cli;
|
||||
mod config;
|
||||
mod db;
|
||||
mod gpodder;
|
||||
mod server;
|
||||
|
|
|
|||
|
|
@ -11,10 +11,7 @@ use axum::{
|
|||
RequestExt, Router,
|
||||
};
|
||||
use axum_extra::{
|
||||
extract::{
|
||||
cookie::{Cookie, Expiration},
|
||||
CookieJar,
|
||||
},
|
||||
extract::{cookie::Cookie, CookieJar},
|
||||
headers::{authorization::Basic, Authorization},
|
||||
TypedHeader,
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue