From c16417d7b6fd95fb71e12a8cdf164eef8cd7b32a Mon Sep 17 00:00:00 2001 From: peterrabbit <peterrabbit@msi.home> Date: Fri, 19 Aug 2022 10:50:02 +0200 Subject: [PATCH] parse cli args --- Cargo.lock | 132 ++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + src/app_state.rs | 106 +++++++++++++++++++++++++++++++++++ src/main.rs | 61 ++++++-------------- src/static_files.rs | 65 ++++++++++++---------- src/tls_config.rs | 24 ++++++++ src/website.rs | 13 +++++ 7 files changed, 330 insertions(+), 72 deletions(-) create mode 100644 src/app_state.rs create mode 100644 src/tls_config.rs diff --git a/Cargo.lock b/Cargo.lock index 00086a3..28d94fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -277,6 +277,26 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -370,6 +390,21 @@ dependencies = [ "generic-array", ] +[[package]] +name = "clap" +version = "2.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + [[package]] name = "cms_rust" version = "0.1.0" @@ -382,6 +417,7 @@ dependencies = [ "rustls-pemfile", "serde", "serde_json", + "structopt", ] [[package]] @@ -622,6 +658,15 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "hermit-abi" version = "0.1.19" @@ -723,6 +768,12 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + [[package]] name = "libc" version = "0.2.127" @@ -901,6 +952,30 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.43" @@ -1164,6 +1239,36 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "structopt" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" +dependencies = [ + "clap", + "lazy_static", + "structopt-derive", +] + +[[package]] +name = "structopt-derive" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "subtle" version = "2.4.1" @@ -1181,6 +1286,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + [[package]] name = "thiserror" version = "1.0.32" @@ -1327,6 +1441,18 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" + +[[package]] +name = "unicode-width" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" + [[package]] name = "universal-hash" version = "0.4.1" @@ -1355,6 +1481,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + [[package]] name = "version_check" version = "0.9.4" diff --git a/Cargo.toml b/Cargo.toml index f90e2a7..a9896f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,3 +14,4 @@ serde_json = "1.0.83" regex = "1.6" fs_extra = "1.2" dirs = "4.0" +structopt = "0.3" diff --git a/src/app_state.rs b/src/app_state.rs new file mode 100644 index 0000000..e31841f --- /dev/null +++ b/src/app_state.rs @@ -0,0 +1,106 @@ +use std::path::PathBuf; +use structopt::StructOpt; + +#[derive(Clone)] +pub enum AppContext { + Debug, + Production, +} + +#[derive(Clone, StructOpt)] +pub struct AppArgs { + #[structopt(short = "c", long = "ctx", default_value = "debug")] + pub context: String, + + #[structopt(short = "d", long = "dir")] + pub app_storage_root: Option<PathBuf>, + + #[structopt(long)] + pub load: Option<PathBuf>, + + #[structopt(short, long, default_value = "localhost")] + pub host: String, + + #[structopt(short, long, default_value = "8080")] + pub port: u16, + + #[structopt(long = "ptls", default_value = "8443")] + pub port_tls: u16, + + #[structopt(long = "certs_dir", default_value = "/etc/letsencrypt/live")] + pub ssl_certs_dir: PathBuf, +} + +#[derive(Clone)] +pub struct AppConfig { + pub exec_path: PathBuf, + pub exec_name: String, + pub context: AppContext, + pub storage_dir: PathBuf, + pub host: String, + pub port: u16, + pub port_tls: u16, + pub load: Option<PathBuf>, + pub ssl_certs_dir: PathBuf, +} + +impl AppConfig { + pub fn new() -> Self { + let app_args = AppArgs::from_args(); + let exec_path = std::env::current_exe().unwrap(); + let exec_name = exec_path + .file_name() + .expect("Unable to get executable name") + .to_str() + .unwrap() + .to_string(); + + let context = match app_args.context.as_str() { + "prod" => AppContext::Production, + _ => AppContext::Debug, + }; + + let storage_dir = match &app_args.app_storage_root { + Some(dir) => dir.join(&exec_name), + None => match context { + AppContext::Production => PathBuf::from("/var").join(&exec_name), + AppContext::Debug => dirs::home_dir() + .expect("Unable to get home dir") + .join(&exec_name), + }, + }; + + let ssl_certs_dir = app_args.ssl_certs_dir.join(&app_args.host); + + AppConfig { + exec_path: std::path::PathBuf::from(exec_path), + exec_name, + context, + storage_dir, + host: app_args.host, + port: app_args.port, + port_tls: app_args.port_tls, + load: app_args.load, + ssl_certs_dir, + } + } +} + +#[derive(Clone)] +pub struct AppState { + pub config: AppConfig, + pub preferences: AppPreference, + // +} + +#[derive(Clone)] +pub struct AppPreference {} + +impl AppState { + pub fn new() -> Self { + AppState { + config: AppConfig::new(), + preferences: AppPreference {}, + } + } +} diff --git a/src/main.rs b/src/main.rs index 72b4826..ce42528 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,19 +1,14 @@ +mod app_state; mod static_files; +mod tls_config; mod website; use actix_web::{get, App, HttpResponse, HttpServer, Responder}; +use app_state::AppState; use static_files::StaticFilesManager; use std::path::PathBuf; +use tls_config::tls_config; use website::WebSite; -fn load_website_template() -> WebSite { - let site_template_path = std::env::current_dir() - .unwrap() - .join("templates") - .join("new_website.json"); - let site_template = std::fs::read_to_string(site_template_path).unwrap(); - WebSite::from_json_str(&site_template) -} - #[get("/{pth:.*}")] async fn page( website: actix_web::web::Data<WebSite>, @@ -38,57 +33,35 @@ async fn admin_login() -> impl Responder { #[actix_web::main] async fn main() -> std::io::Result<()> { - let website = load_website_template(); - let site_name = "rust_cms".to_string(); // Get from arg - let mut static_files_manager = StaticFilesManager::new(&site_name).unwrap(); + let app_state = AppState::new(); + let website = WebSite::load(&app_state.config); + let mut static_files_manager = StaticFilesManager::new(&app_state).unwrap(); static_files_manager.build_index().unwrap(); - // GET HOST AND CERTS DIR FROM CLI ARGUMENT - // Get port from arg, or get context from arg and define default port, or set default port to standard - - // LOAD A WEBSITE SCHEMA (JSON) FROM CLI ARGUMENT PATH OR search in /var/{sitename} and load it CREATE A NEW ONE - // create pages resources with templates and the contents from the json file - // Save the resources in an appstate - - // create the static dir in standard location if doesn't exist (like /var/{sitename}/static) - // create the static files index (like Arc<Mutex<StaticFilesIndex>>) - - let host = "localhost"; - let certs_dir = PathBuf::from("/etc/letsencrypt/live").join(host); - let cert_file = - &mut std::io::BufReader::new(std::fs::File::open(certs_dir.join("fullchain.pem")).unwrap()); - let key_file = - &mut std::io::BufReader::new(std::fs::File::open(certs_dir.join("privkey.pem")).unwrap()); - let cert = rustls::Certificate(rustls_pemfile::certs(cert_file).unwrap().remove(0).to_vec()); + let host = app_state.config.host.clone(); + let port = app_state.config.port; + let port_tls = app_state.config.port_tls; - let key = rustls::PrivateKey( - rustls_pemfile::pkcs8_private_keys(key_file) - .unwrap() - .remove(0) - .to_vec(), - ); - - let srv_conf = rustls::ServerConfig::builder() - .with_safe_defaults() - .with_no_client_auth() - .with_single_cert(vec![cert], key) - .expect("bad certificate/key"); + let srv_conf = tls_config(&app_state.config); let static_files_manager = actix_web::web::Data::new(std::sync::Mutex::new(static_files_manager)); + let app_state = actix_web::web::Data::new(std::sync::Mutex::new(app_state)); + HttpServer::new(move || { App::new() .wrap(actix_web::middleware::Logger::default()) .wrap(actix_web::middleware::Compress::default()) - .app_data(actix_web::web::Data::new(website.clone())) + .app_data(actix_web::web::Data::clone(&app_state)) .app_data(actix_web::web::Data::clone(&static_files_manager)) + .app_data(actix_web::web::Data::new(website.clone())) .service(admin_dashboard) .service(admin_login) .service(page) }) - .bind("127.0.0.1:8080")? - .bind_rustls("127.0.0.1:8443", srv_conf)? + .bind(format!("{}:{}", host, port))? + .bind_rustls(format!("{}:{}", host, port_tls), srv_conf)? .run() .await } diff --git a/src/static_files.rs b/src/static_files.rs index ced5de4..f8eb2fc 100644 --- a/src/static_files.rs +++ b/src/static_files.rs @@ -1,48 +1,57 @@ +use crate::app_state::AppState; use std::path::PathBuf; -#[derive(Clone)] pub struct StaticFilesManager { + dir: PathBuf, index: Vec<String>, } impl StaticFilesManager { - fn create_dir_if_missing(app_dir: &PathBuf) -> Result<(), String> { - let static_path = app_dir.join("static"); - - if !static_path.exists() { - match std::fs::create_dir_all(static_path) { - Ok(_) => return Self::copy_default(&app_dir), + fn create_dir_if_missing(app_dir: &PathBuf) -> Result<PathBuf, String> { + let static_dir = app_dir.join("static"); + + if !static_dir.exists() { + match std::fs::create_dir_all(&static_dir) { + Ok(_) => { + if let Err(err) = Self::copy_default(&static_dir) { + return Err(format!("{}", err)); + } + } Err(err) => return Err(format!("{}", err)), } } - Ok(()) + Ok(static_dir) } - fn copy_default(app_dir: &PathBuf) -> Result<(), String> { - let local_default = std::env::current_dir().unwrap().join("default_static"); - let standard_default = app_dir.join("static").join("default"); - if let Err(err) = std::fs::create_dir_all(&standard_default) { - return Err(format!("{}", err)); + fn copy_default(static_dir: &PathBuf) -> Result<(), String> { + let local_default_static = std::env::current_dir().unwrap().join("default_static"); + let default_static = static_dir.join("default"); + match std::fs::create_dir_all(&default_static) { + Err(err) => Err(format!("{}", err)), + Ok(_) => { + let mut copy_default_options = fs_extra::dir::CopyOptions::new(); + copy_default_options.content_only = true; + match fs_extra::dir::copy( + local_default_static, + default_static, + ©_default_options, + ) { + Err(err) => Err(format!("{}", err)), + Ok(_) => Ok(()), + } + } } - - let mut copy_default_options = fs_extra::dir::CopyOptions::new(); - copy_default_options.content_only = true; - if let Err(err) = - fs_extra::dir::copy(local_default, standard_default, ©_default_options) - { - return Err(format!("{}", err)); - }; - Ok(()) } - pub fn new(website_name: &String) -> Result<Self, String> { - if let Err(err) = Self::create_dir_if_missing(&dirs::home_dir().unwrap().join(website_name)) - { - return Err(err); + pub fn new(app_state: &AppState) -> Result<Self, String> { + match Self::create_dir_if_missing(&app_state.config.storage_dir) { + Ok(dir) => Ok(StaticFilesManager { + index: Vec::new(), + dir, + }), + Err(msg) => Err(msg), } - - Ok(StaticFilesManager { index: Vec::new() }) } pub fn build_index(&mut self) -> Result<(), String> { diff --git a/src/tls_config.rs b/src/tls_config.rs new file mode 100644 index 0000000..61c23e8 --- /dev/null +++ b/src/tls_config.rs @@ -0,0 +1,24 @@ +use crate::app_state::AppConfig; + +pub fn tls_config(app_config: &AppConfig) -> rustls::ServerConfig { + let certs_dir = app_config.ssl_certs_dir.clone(); + let cert_file = + &mut std::io::BufReader::new(std::fs::File::open(certs_dir.join("fullchain.pem")).unwrap()); + let key_file = + &mut std::io::BufReader::new(std::fs::File::open(certs_dir.join("privkey.pem")).unwrap()); + + let cert = rustls::Certificate(rustls_pemfile::certs(cert_file).unwrap().remove(0).to_vec()); + + let key = rustls::PrivateKey( + rustls_pemfile::pkcs8_private_keys(key_file) + .unwrap() + .remove(0) + .to_vec(), + ); + + rustls::ServerConfig::builder() + .with_safe_defaults() + .with_no_client_auth() + .with_single_cert(vec![cert], key) + .expect("bad certificate/key") +} diff --git a/src/website.rs b/src/website.rs index 9e2f6c7..5d0b95c 100644 --- a/src/website.rs +++ b/src/website.rs @@ -1,3 +1,4 @@ +use crate::app_state::AppConfig; use regex::{Captures, Regex}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -102,6 +103,18 @@ impl WebSite { WebSite::new(serde_json::from_str(json).unwrap()) } + pub fn load(config: &AppConfig) -> WebSite { + let file_path = match &config.load { + None => std::env::current_dir() + .unwrap() + .join("templates") + .join("new_website.json"), + Some(pth) => pth.clone(), + }; + + WebSite::from_json_str(&std::fs::read_to_string(file_path).unwrap()) + } + fn create_index_by_url( index: &mut HashMap<PathBuf, WebPage>, pages_tree: &PagesTree, -- GitLab