Use clap for configuration

This commit is contained in:
Simon Bruder 2024-07-24 11:45:06 +02:00
parent bcbb7dfc67
commit 1e5f7930ab
Signed by: simon
GPG key ID: 347FF8699CDA0776
6 changed files with 109 additions and 21 deletions

55
Cargo.lock generated
View file

@ -540,6 +540,46 @@ dependencies = [
"inout", "inout",
] ]
[[package]]
name = "clap"
version = "4.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f6b81fb3c84f5563d509c59b5a48d935f689e993afa90fe39047f05adef9142"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ca6706fd5224857d9ac5eb9355f6683563cc0541c7cd9d014043b57cbec78ac"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085"
dependencies = [
"heck 0.5.0",
"proc-macro2",
"quote",
"syn 2.0.68",
]
[[package]]
name = "clap_lex"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70"
[[package]] [[package]]
name = "colorchoice" name = "colorchoice"
version = "1.0.1" version = "1.0.1"
@ -1016,6 +1056,12 @@ dependencies = [
"unicode-segmentation", "unicode-segmentation",
] ]
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]] [[package]]
name = "hex" name = "hex"
version = "0.4.3" version = "0.4.3"
@ -1170,6 +1216,7 @@ dependencies = [
"actix-web", "actix-web",
"barcoders", "barcoders",
"base64 0.22.1", "base64 0.22.1",
"clap",
"datamatrix", "datamatrix",
"enum-iterator", "enum-iterator",
"env_logger", "env_logger",
@ -2042,7 +2089,7 @@ checksum = "5833ef53aaa16d860e92123292f1f6a3d53c34ba8b1969f152ef1a7bb803f3c8"
dependencies = [ dependencies = [
"dotenvy", "dotenvy",
"either", "either",
"heck", "heck 0.4.1",
"hex", "hex",
"once_cell", "once_cell",
"proc-macro2", "proc-macro2",
@ -2180,6 +2227,12 @@ dependencies = [
"unicode-properties", "unicode-properties",
] ]
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]] [[package]]
name = "subtle" name = "subtle"
version = "2.6.1" version = "2.6.1"

View file

@ -15,6 +15,7 @@ actix-session = { version = "0.9.0", features = ["cookie-session"] }
actix-web = { version = "4.8.0", features = ["cookies"] } actix-web = { version = "4.8.0", features = ["cookies"] }
barcoders = { version = "2.0.0", default-features = false, features = ["std"] } barcoders = { version = "2.0.0", default-features = false, features = ["std"] }
base64 = "0.22.1" base64 = "0.22.1"
clap = { version = "4.5.10", features = ["derive", "env"] }
datamatrix = "0.3.1" datamatrix = "0.3.1"
enum-iterator = "2.1.0" enum-iterator = "2.1.0"
env_logger = "0.11.3" env_logger = "0.11.3"

34
src/config.rs Normal file
View file

@ -0,0 +1,34 @@
// SPDX-FileCopyrightText: 2024 Simon Bruder <simon@sbruder.de>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
use std::net::{IpAddr, Ipv6Addr};
use clap::Parser;
/// A lightweight inventory management system
#[derive(Clone, Parser, Debug)]
#[command(version, about)]
pub struct Config {
/// Database URL of PostgreSQL database
#[arg(long, env)]
pub database_url: String,
/// Secret key for encrypting session cookie
///
/// Can be generated with head -c 64 /dev/urandom | base64 -w0
#[arg(long, env)]
pub secret_key: Option<String>,
/// Address for HTTP server to listen on
#[arg(long, env, default_value_t = IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)))]
pub listen_address: std::net::IpAddr,
/// Port for HTTP server to listen on
#[arg(long, env, default_value_t = 8080)]
pub listen_port: u16,
/// Superuser password
#[arg(long, env)]
pub superuser_password: String,
}

View file

@ -59,12 +59,10 @@ async fn login(
req: HttpRequest, req: HttpRequest,
form: web::Form<LoginForm>, form: web::Form<LoginForm>,
query: web::Query<LoginQuery>, query: web::Query<LoginQuery>,
config: web::Data<crate::Config>,
) -> Result<impl Responder, error::Error> { ) -> Result<impl Responder, error::Error> {
// Very basic authentication for now (only password, hardcoded in environment variable) // Very basic authentication for now (only password, hardcoded in configuration)
if form.password if form.password == config.superuser_password {
== std::env::var("SUPERUSER_PASSWORD")
.map_err(|_| error::ErrorInternalServerError("login disabled (no password set)"))?
{
Identity::login(&req.extensions(), "superuser".into()) Identity::login(&req.extensions(), "superuser".into())
.map_err(error::ErrorInternalServerError)?; .map_err(error::ErrorInternalServerError)?;
Ok( Ok(

View file

@ -2,6 +2,9 @@
// //
// SPDX-License-Identifier: AGPL-3.0-or-later // SPDX-License-Identifier: AGPL-3.0-or-later
mod config;
pub mod frontend; pub mod frontend;
pub mod label; pub mod label;
pub mod middleware; pub mod middleware;
pub use config::Config;

View file

@ -2,17 +2,18 @@
// //
// SPDX-License-Identifier: AGPL-3.0-or-later // SPDX-License-Identifier: AGPL-3.0-or-later
use std::env;
use actix_identity::IdentityMiddleware; use actix_identity::IdentityMiddleware;
use actix_session::{storage::CookieSessionStore, SessionMiddleware}; use actix_session::{storage::CookieSessionStore, SessionMiddleware};
use actix_web::middleware::ErrorHandlers; use actix_web::middleware::ErrorHandlers;
use actix_web::{cookie::Key, http::StatusCode, web, App, HttpResponse, HttpServer}; use actix_web::{cookie::Key, http::StatusCode, web, App, HttpResponse, HttpServer};
use base64::prelude::{Engine as _, BASE64_STANDARD}; use base64::prelude::{Engine as _, BASE64_STANDARD};
use clap::Parser;
use log::{info, warn}; use log::{info, warn};
use mime_guess::from_path; use mime_guess::from_path;
use rust_embed::Embed; use rust_embed::Embed;
use li7y::Config;
#[derive(Embed)] #[derive(Embed)]
#[folder = "static"] #[folder = "static"]
struct Static; struct Static;
@ -21,39 +22,37 @@ struct Static;
async fn main() -> std::io::Result<()> { async fn main() -> std::io::Result<()> {
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
// generate a secret key with head -c 64 /dev/urandom | base64 -w0 let config = Config::parse();
let secret_key = match env::var("SECRET_KEY") {
Ok(encoded) => Key::from( let secret_key = match config.secret_key {
Some(ref encoded) => Key::from(
&BASE64_STANDARD &BASE64_STANDARD
.decode(encoded) .decode(encoded)
.expect("failed to decode base64 in SECRET_KEY"), .expect("failed to decode base64 in SECRET_KEY"),
), ),
Err(_) => { None => {
warn!("SECRET_KEY was not specified, using randomly generated key"); warn!("SECRET_KEY was not specified, using randomly generated key");
Key::generate() Key::generate()
} }
}; };
let pool: sqlx::PgPool = sqlx::Pool::<sqlx::postgres::Postgres>::connect( let pool: sqlx::PgPool = sqlx::Pool::<sqlx::postgres::Postgres>::connect(&config.database_url)
&env::var("DATABASE_URL").expect("DATABASE_URL must be set"), .await
) .expect("failed to connect to database");
.await
.expect("failed to connect to database");
sqlx::migrate!() sqlx::migrate!()
.run(&pool) .run(&pool)
.await .await
.expect("failed to run migrations"); .expect("failed to run migrations");
let address = env::var("LISTEN_ADDRESS").unwrap_or("::1".to_string()); let address = config.listen_address;
let port = env::var("LISTEN_PORT").map_or(8080, |s| { let port = config.listen_port;
s.parse::<u16>().expect("failed to parse LISTEN_PORT")
});
info!("Starting on {address}:{port}"); info!("Starting on {address}:{port}");
HttpServer::new(move || { HttpServer::new(move || {
App::new() App::new()
.app_data(web::Data::new(config.clone()))
.app_data(web::Data::new(pool.clone())) .app_data(web::Data::new(pool.clone()))
.service(web::scope("/static").route( .service(web::scope("/static").route(
"/{_:.*}", "/{_:.*}",