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,
+                    &copy_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, &copy_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