rustream/squire/
startup.rs

1use std;
2use std::io::Write;
3
4use chrono::{DateTime, Local, Utc};
5use walkdir::WalkDir;
6
7use crate::{constant, squire};
8use crate::squire::settings;
9
10/// Initializes the logger based on the provided debug flag and cargo information.
11///
12/// # Arguments
13///
14/// * `debug` - A flag indicating whether to enable debug mode for detailed logging.
15/// * `crate_name` - Name of the crate loaded during compile time.
16pub fn init_logger(debug: bool, utc: bool, crate_name: &String) {
17    if debug {
18        std::env::set_var("RUST_LOG", format!(
19            "actix_web=debug,actix_server=info,{}=debug", crate_name
20        ));
21        std::env::set_var("RUST_BACKTRACE", "1");
22    } else {
23        // Set Actix logging to warning mode since it becomes too noisy when streaming large files
24        std::env::set_var("RUST_LOG", format!(
25            "actix_web=warn,actix_server=warn,{}=info", crate_name
26        ));
27        std::env::set_var("RUST_BACKTRACE", "0");
28    }
29    if utc {
30        env_logger::init();
31    } else {
32        env_logger::Builder::from_default_env()
33            .format(|buf, record| {
34                let local_time: DateTime<Local> = Local::now();
35                writeln!(
36                    buf,
37                    "[{} {} {}] - {}",
38                    local_time.format("%Y-%m-%dT%H:%M:%SZ"),
39                    record.level(),
40                    record.target(),
41                    record.args()
42                )
43            })
44            .init();
45    }
46}
47
48/// Extracts the mandatory env vars by key and parses it as `HashMap<String, String>` and `PathBuf`
49///
50/// # Returns
51///
52/// Returns a tuple of `HashMap<String, String>` and `PathBuf`.
53///
54/// # Panics
55///
56/// If the value is missing or if there is an error parsing the `HashMap`
57fn mandatory_vars() -> (std::collections::HashMap<String, String>, std::path::PathBuf) {
58    let authorization_str = match std::env::var("authorization") {
59        Ok(val) => val,
60        Err(_) => {
61            panic!(
62                "\nauthorization\n\texpected a HashMap, received null [value=missing]\n",
63            );
64        }
65    };
66    let authorization: std::collections::HashMap<String, String> =
67        match serde_json::from_str(&authorization_str) {
68            Ok(val) => val,
69            Err(_) => {
70                panic!(
71                    "\nauthorization\n\terror parsing JSON [value=invalid]\n",
72                );
73            }
74        };
75    let media_source_str = match std::env::var("media_source") {
76        Ok(val) => val,
77        Err(_) => {
78            panic!(
79                "\nmedia_source\n\texpected a directory path, received null [value=missing]\n",
80            );
81        }
82    };
83    (authorization, std::path::PathBuf::from(media_source_str))
84}
85
86/// Extracts the env var by key and parses it as a `bool`
87///
88/// # Arguments
89///
90/// * `key` - Key for the environment variable.
91///
92/// # Returns
93///
94/// Returns an `Option<bool>` if the value is available.
95///
96/// # Panics
97///
98/// If the value is present, but it is an invalid data-type.
99fn parse_bool(key: &str) -> Option<bool> {
100    match std::env::var(key) {
101        Ok(val) => match val.parse() {
102            Ok(parsed) => Some(parsed),
103            Err(_) => {
104                panic!("\n{}\n\texpected bool, received '{}' [value=invalid]\n", key, val);
105            }
106        },
107        Err(_) => None,
108    }
109}
110
111/// Extracts the env var by key and parses it as a `i64`
112///
113/// # Arguments
114///
115/// * `key` - Key for the environment variable.
116///
117/// # Returns
118///
119/// Returns an `Option<i64>` if the value is available.
120///
121/// # Panics
122///
123/// If the value is present, but it is an invalid data-type.
124fn parse_i64(key: &str) -> Option<i64> {
125    match std::env::var(key) {
126        Ok(val) => match val.parse() {
127            Ok(parsed) => Some(parsed),
128            Err(_) => {
129                panic!("\n{}\n\texpected i64, received '{}' [value=invalid]\n", key, val);
130            }
131        },
132        Err(_) => None,
133    }
134}
135
136/// Extracts the env var by key and parses it as a `u16`
137///
138/// # Arguments
139///
140/// * `key` - Key for the environment variable.
141///
142/// # Returns
143///
144/// Returns an `Option<u16>` if the value is available.
145///
146/// # Panics
147///
148/// If the value is present, but it is an invalid data-type.
149fn parse_u16(key: &str) -> Option<u16> {
150    match std::env::var(key) {
151        Ok(val) => match val.parse() {
152            Ok(parsed) => Some(parsed),
153            Err(_) => {
154                panic!("\n{}\n\texpected u16, received '{}' [value=invalid]\n", key, val);
155            }
156        },
157        Err(_) => None,
158    }
159}
160
161/// Extracts the env var by key and parses it as a `usize`
162///
163/// # Arguments
164///
165/// * `key` - Key for the environment variable.
166///
167/// # Returns
168///
169/// Returns an `Option<usize>` if the value is available.
170///
171/// # Panics
172///
173/// If the value is present, but it is an invalid data-type.
174fn parse_usize(key: &str) -> Option<usize> {
175    match std::env::var(key) {
176        Ok(val) => match val.parse() {
177            Ok(parsed) => Some(parsed),
178            Err(_) => {
179                panic!("\n{}\n\texpected usize, received '{}' [value=invalid]\n", key, val);
180            }
181        },
182        Err(_) => None,
183    }
184}
185
186/// Extracts the env var by key and parses it as a `Vec<String>`
187///
188/// # Arguments
189///
190/// * `key` - Key for the environment variable.
191///
192/// # Returns
193///
194/// Returns an `Option<Vec<String>>` if the value is available.
195///
196/// # Panics
197///
198/// If the value is present, but it is an invalid data-type.
199fn parse_vec(key: &str) -> Option<Vec<String>> {
200    match std::env::var(key) {
201        Ok(val) => match serde_json::from_str::<Vec<String>>(&val) {
202            Ok(parsed) => Some(parsed),
203            Err(_) => {
204                panic!("\n{}\n\texpected vec, received '{}' [value=invalid]\n", key, val);
205            }
206        },
207        Err(_) => None,
208    }
209}
210
211/// Extracts the env var by key and parses it as a `PathBuf`
212///
213/// # Arguments
214///
215/// * `key` - Key for the environment variable.
216///
217/// # Returns
218///
219/// Returns an option of `PathBuf` if the value is available.
220fn parse_path(key: &str) -> Option<std::path::PathBuf> {
221    match std::env::var(key) {
222        Ok(value) => {
223            Some(std::path::PathBuf::from(value))
224        }
225        Err(_) => {
226            None
227        }
228    }
229}
230
231/// Parses the maximum payload size from human-readable memory format to bytes.
232///
233/// - `key` - Key for the environment variable.
234///
235/// ## See Also
236///
237/// - This function handles internal panic gracefully, in the most detailed way possible.
238/// - Panic outputs are suppressed with a custom hook.
239/// - Custom hook is set before wrapping the potentially panicking function inside `catch_unwind`.
240/// - Custom hook is reset later, so the future panics and go uncaught.
241/// - Error message from panic payload is also further processed, to get a detailed reason for panic.
242///
243/// # Returns
244///
245/// Returns an option of usize if the value is parsable and within the allowed size limit.
246fn parse_max_payload(key: &str) -> Option<usize> {
247    match std::env::var(key) {
248        Ok(value) => {
249
250            let custom_hook = std::panic::take_hook();
251            std::panic::set_hook(Box::new(|_panic_info| {}));
252            let result = std::panic::catch_unwind(|| parse_memory(&value));
253            std::panic::set_hook(custom_hook);
254
255            match result {
256                Ok(output) => {
257                    if let Some(value) = output {
258                        Some(value)
259                    } else {
260                        panic!("\n{}\n\texpected format: '100 MB', received '{}' [value=invalid]\n",
261                               key, value);
262                    }
263                }
264                Err(panic_payload) => {
265                    if let Some(&error) = panic_payload.downcast_ref::<&str>() {
266                        panic!("\n{}\n\t{} [value=invalid]\n", key, error);
267                    } else if let Some(error) = panic_payload.downcast_ref::<String>() {
268                        panic!("\n{}\n\t{} [value=invalid]\n", key, error);
269                    } else if let Some(error) = panic_payload.downcast_ref::<Box<dyn std::fmt::Debug + Send + 'static>>() {
270                        panic!("\n{}\n\t{:?} [value=invalid]\n", key, error);
271                    } else {
272                        panic!("\n{}\n\tinvalid memory format! unable to parse panic payload [value=invalid]\n", key);
273                    }
274                }
275            }
276        }
277        Err(_) => {
278            None
279        }
280    }
281}
282
283fn parse_memory(memory: &str) -> Option<usize> {
284    let value = memory.trim();
285    let (size_str, unit) = value.split_at(value.len() - 2);
286    let size: usize = match size_str.strip_suffix(' ').unwrap_or_default().parse() {
287        Ok(num) => num,
288        Err(_) => return None,
289    };
290
291    match unit.to_lowercase().as_str() {
292        "zb" => Some(size * 1024 * 1024 * 1024 * 1024 * 1024),
293        "tb" => Some(size * 1024 * 1024 * 1024 * 1024),
294        "gb" => Some(size * 1024 * 1024 * 1024),
295        "mb" => Some(size * 1024 * 1024),
296        "kb" => Some(size * 1024),
297        _ => None,
298    }
299}
300
301/// Handler that's responsible to parse all the env vars.
302///
303/// # Returns
304///
305/// Instantiates the `Config` struct with the required parameters.
306fn load_env_vars() -> settings::Config {
307    let (authorization, media_source) = mandatory_vars();
308    let debug = parse_bool("debug").unwrap_or(settings::default_debug());
309    let utc_logging = parse_bool("utc_logging").unwrap_or(settings::default_utc_logging());
310    let media_host = std::env::var("media_host").unwrap_or(settings::default_media_host());
311    let media_port = parse_u16("media_port").unwrap_or(settings::default_media_port());
312    let session_duration = parse_i64("session_duration").unwrap_or(settings::default_session_duration());
313    let file_formats = parse_vec("file_formats").unwrap_or(settings::default_file_formats());
314    let workers = parse_usize("workers").unwrap_or(settings::default_workers());
315    let max_connections = parse_usize("max_connections").unwrap_or(settings::default_max_connections());
316    let websites = parse_vec("websites").unwrap_or(settings::default_websites());
317    let secure_session = parse_bool("secure_session").unwrap_or(settings::default_secure_session());
318    let key_file = parse_path("key_file").unwrap_or(settings::default_ssl());
319    let cert_file = parse_path("cert_file").unwrap_or(settings::default_ssl());
320    let max_payload_size = parse_max_payload("max_payload_size").unwrap_or(settings::default_max_payload_size());
321    settings::Config {
322        authorization,
323        media_source,
324        debug,
325        utc_logging,
326        media_host,
327        media_port,
328        session_duration,
329        file_formats,
330        workers,
331        max_connections,
332        max_payload_size,
333        websites,
334        secure_session,
335        key_file,
336        cert_file,
337    }
338}
339
340/// Get the current time in a specific format.
341///
342/// # Arguments
343///
344/// * `utc` - Boolean flag to return the time in UTC timezone.
345///
346/// # Returns
347///
348/// Returns the current datetime as a `String`.
349fn get_time(utc: bool) -> String {
350    if utc {
351        Utc::now().format("%Y-%m-%dT%H:%M:%SZ").to_string()
352    } else {
353        Local::now().format("%Y-%m-%dT%H:%M:%SZ").to_string()
354    }
355}
356
357/// Validates the directory structure to ensure that the secure index is present in media source's root.
358///
359/// # Arguments
360///
361/// * `config` - Configuration data for the application.
362/// * `metadata` - Struct containing metadata of the application.
363fn validate_dir_structure(config: &settings::Config, metadata: &constant::MetaData) {
364    let source = &config.media_source.to_string_lossy().to_string();
365    let mut errors = String::new();
366    for entry in WalkDir::new(&config.media_source).into_iter().filter_map(|e| e.ok()) {
367        let entry_path = entry.path();
368        if entry_path.is_dir() && entry_path.to_str().unwrap().ends_with(constant::SECURE_INDEX) {
369            let secure_index = entry_path.strip_prefix(source).unwrap();
370            let depth = secure_index.iter().count();
371            if depth != 1usize {
372                let index_vec = secure_index.iter().collect::<Vec<_>>();
373                let secure_dir = index_vec.last().unwrap();
374                // secure_parent_path is the secure index's location
375                let secure_parent_path = &index_vec[0..index_vec.len() - 1]
376                    .join(std::ffi::OsStr::new(std::path::MAIN_SEPARATOR_STR));
377                errors.push_str(&format!(
378                    "\n{:?}\n\tSecure index directory [{:?}] should be at the root [{:?}] [depth={}, valid=1]\n\
379                    \t> Hint: Either move {:?} within {:?}, [OR] set the 'media_source' to {:?}\n",
380                    secure_index,
381                    secure_dir,
382                    config.media_source,
383                    depth,
384                    secure_dir,
385                    config.media_source,
386                    config.media_source.join(secure_parent_path)
387                ));
388            }
389        }
390    }
391    if errors.is_empty() {
392        for username in config.authorization.keys() {
393            let secure_path = &config.media_source.join(format!("{}_{}", &username, constant::SECURE_INDEX));
394            if !secure_path.exists() {
395                match std::fs::create_dir(secure_path) {
396                    Ok(_) => {
397                        // keep formatting similar to logging
398                        if config.utc_logging {
399                            println!("[{}\x1b[32m INFO\x1b[0m  {}] '{}' has been created",
400                                     get_time(config.utc_logging), metadata.crate_name,
401                                     &secure_path.to_str().unwrap())
402                        } else {
403                            println!("[{} INFO  {}] '{}' has been created",
404                                     get_time(config.utc_logging), metadata.crate_name,
405                                     &secure_path.to_str().unwrap())
406                        }
407                    }
408                    Err(err) => panic!("{}", err)
409                }
410            }
411        }
412    } else {
413        panic!("{}", errors)
414    }
415}
416
417/// Validates all the required environment variables with the required settings.
418///
419/// # Arguments
420///
421/// * `metadata` - Struct containing metadata of the application.
422///
423/// # Returns
424///
425/// Returns the `Config` struct containing the required parameters.
426fn validate_vars(metadata: &constant::MetaData) -> settings::Config {
427    let config = load_env_vars();
428    let mut errors = "".to_owned();
429    if !config.media_source.exists() || !config.media_source.is_dir() {
430        let err1 = format!(
431            "\nmedia_source\n\tInput [{}] is not a valid directory [value=invalid]\n",
432            config.media_source.to_string_lossy()
433        );
434        errors.push_str(&err1);
435    }
436    for (username, password) in &config.authorization {
437        if username.len() < 4 {
438            let err2 = format!(
439                "\nauthorization\n\t[{}: {}] username should be at least 4 or more characters [value=invalid]\n",
440                username, "*".repeat(password.len())
441            );
442            errors.push_str(&err2);
443        }
444        if password.len() < 8 {
445            let err3 = format!(
446                "\nauthorization\n\t[{}: {}] password should be at least 8 or more characters [value=invalid]\n",
447                username, "*".repeat(password.len())
448            );
449            errors.push_str(&err3);
450        }
451    }
452    if !errors.is_empty() {
453        panic!("{}", errors);
454    }
455    validate_dir_structure(&config, metadata);
456    config
457}
458
459/// Retrieves the environment variables and parses as the data-type specified in Config struct.
460///
461/// # Arguments
462///
463/// * `metadata` - Struct containing metadata of the application.
464///
465/// # Returns
466///
467/// Converts the config struct into an `Arc` and returns it.
468pub fn get_config(metadata: &constant::MetaData) -> std::sync::Arc<settings::Config> {
469    let mut env_file = squire::parser::arguments(metadata);
470    if env_file.is_empty() {
471        env_file = std::env::var("env_file")
472            .unwrap_or(std::env::var("ENV_FILE")
473                .unwrap_or(".env".to_string()));
474    }
475    let env_file_path = std::env::current_dir()
476        .unwrap_or_default()
477        .join(env_file);
478    let _ = dotenv::from_path(env_file_path.as_path());
479    std::sync::Arc::new(validate_vars(metadata))
480}