Skip to content

Commit

Permalink
Remove envy and parse env vars manually
Browse files Browse the repository at this point in the history
Have a single source for all things user entries
Doc updates to follow later
  • Loading branch information
dormant-user committed Feb 20, 2024
1 parent 2b01380 commit e545d89
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 84 deletions.
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ Cargo.lock

# Secrets mapping
.env
# todo: remove me
config.json

# Certificates
*.pem
1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,5 @@ regex = "1.5"
walkdir = "2.3.2"
openssl = "0.10"
dotenv = "0.15.0"
envy = "0.4"
[target.'cfg(target_os = "linux")'.dependencies]
openssl = { version = "0.10", features = ["vendored"] }
8 changes: 2 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,10 @@ curl -o RuStream-Windows-x86_64.zip -LH "Accept: application/octet-stream" "http
```
</details>

#### Arguments
- `debug` - Enable debug level logging
#### Config file

#### Flags
- `--filename` / `-f` - Filename (JSON) for the secrets' config. Defaults to `config.json`
- `--version` / `-v` - Get package version
[//]: # (todo: update to use .env)

#### Config file
[RuStream][repo] requires a JSON file with secrets loaded as key-value paris.

**Mandatory**
Expand Down
39 changes: 13 additions & 26 deletions src/squire/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,63 +2,49 @@ use std::{path, thread};
use std::collections::HashMap;
use std::net::ToSocketAddrs;

use serde::{Deserialize, Serialize};

/// Represents the configuration parameters for RuStream.
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct Config {
/// Dictionary of key-value pairs for authorization (username and password).
pub authorization: HashMap<String, String>,
/// Source path for video files.
pub video_source: path::PathBuf,

/// Debug flag to enable debug level logging.
#[serde(default = "default_debug")]
pub debug: bool,
/// Host IP address for video hosting.
#[serde(default = "default_video_host")]
pub video_host: String,
/// Port number for hosting the application.
#[serde(default = "default_video_port")]
pub video_port: i32,
/// Duration of a session in seconds.
#[serde(default = "default_session_duration")]
pub session_duration: i32,
/// List of supported video file formats.
#[serde(default = "default_file_formats")]
pub file_formats: Vec<String>,

/// Number of worker threads to spin up the server.
#[serde(default = "default_workers")]
pub workers: i32,
/// Maximum number of concurrent connections.
#[serde(default = "default_max_connections")]
pub max_connections: i32,
/// List of websites (supports regex) to add to CORS configuration.
#[serde(default = "default_websites")]
pub websites: Vec<String>,

// Boolean flag to restrict session_token to be sent only via HTTPS
#[serde(default = "default_secure_session")]
/// Boolean flag to restrict session_token to be sent only via HTTPS
pub secure_session: bool,

// Path to the private key file for SSL certificate
#[serde(default = "default_ssl")]
/// Path to the private key file for SSL certificate
pub key_file: path::PathBuf,
// Path to the full certificate chain file for SSL certificate
#[serde(default = "default_ssl")]
/// Path to the full certificate chain file for SSL certificate
pub cert_file: path::PathBuf,
}

/// Returns the default value for debug flag
fn default_debug() -> bool { false }
pub fn default_debug() -> bool { false }


/// Returns the default value for ssl files
fn default_ssl() -> path::PathBuf { path::PathBuf::new() }
pub fn default_ssl() -> path::PathBuf { path::PathBuf::new() }

/// Returns the default video host based on the local machine's IP address.
fn default_video_host() -> String {
pub fn default_video_host() -> String {
let hostname = "localhost";
match (hostname, 0).to_socket_addrs() {
Ok(mut addrs) => {
Expand All @@ -74,18 +60,18 @@ fn default_video_host() -> String {
}

/// Returns the default video port (8000).
fn default_video_port() -> i32 { 8000 }
pub fn default_video_port() -> i32 { 8000 }

/// Returns the default session duration (3600 seconds).
fn default_session_duration() -> i32 { 3600 }
pub fn default_session_duration() -> i32 { 3600 }

/// Returns the default supported file formats (.mp4 and .mov).
///
/// Set as public, since this function is re-used in `startup.rs`
pub fn default_file_formats() -> Vec<String> { vec!["mp4".to_string(), "mov".to_string()] }

/// Returns the default number of worker threads (half of logical cores).
fn default_workers() -> i32 {
pub fn default_workers() -> i32 {
let logical_cores = thread::available_parallelism();
match logical_cores {
Ok(cores) => cores.get() as i32 / 2,
Expand All @@ -97,9 +83,10 @@ fn default_workers() -> i32 {
}

/// Returns the default maximum number of concurrent connections (3).
fn default_max_connections() -> i32 { 3 }
pub fn default_max_connections() -> i32 { 3 }

/// Returns an empty list as the default website (CORS configuration).
fn default_websites() -> Vec<String> { Vec::new() }
pub fn default_websites() -> Vec<String> { Vec::new() }

fn default_secure_session() -> bool { false }
/// Returns the default value for secure_session
pub fn default_secure_session() -> bool { false }
192 changes: 143 additions & 49 deletions src/squire/startup.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use std::env;
use std::sync::Arc;
use std;

use crate::squire::settings::Config;
use crate::squire::settings;

/// Initializes the logger based on the provided debug flag and cargo information.
///
Expand All @@ -11,73 +10,168 @@ use crate::squire::settings::Config;
/// * `cargo` - A reference to the Cargo struct containing information about the application.
pub fn init_logger(debug: bool, crate_name: &String) {
if debug {
env::set_var("RUST_LOG", format!(
std::env::set_var("RUST_LOG", format!(
"actix_web=debug,actix_server=info,{}=debug", crate_name
));
env::set_var("RUST_BACKTRACE", "1");
std::env::set_var("RUST_BACKTRACE", "1");
} else {
// Set Actix logging to warning mode since it becomes too noisy when streaming a giant video file.
env::set_var("RUST_LOG", format!(
std::env::set_var("RUST_LOG", format!(
"actix_web=warn,actix_server=warn,{}=info", crate_name
));
env::set_var("RUST_BACKTRACE", "0");
std::env::set_var("RUST_BACKTRACE", "0");
}
env_logger::init();
}

fn mandatory_vars() -> (std::collections::HashMap<String, String>, std::path::PathBuf) {
let authorization_str = match std::env::var("authorization") {
Ok(val) => val,
Err(_) => {
panic!(
"\nauthorization\n\texpected a HashMap, received null [value=missing]\n",
);
}
};
let authorization: std::collections::HashMap<String, String> =
match serde_json::from_str(&authorization_str) {
Ok(val) => val,
Err(_) => {
panic!(
"\nauthorization\n\terror parsing JSON [value=invalid]\n",
);
}
};
let video_source_str = match std::env::var("video_source") {
Ok(val) => val,
Err(_) => {
panic!(
"\nvideo_source\n\texpected a directory path, received null [value=missing]\n",
);
}
};
(authorization, std::path::PathBuf::from(video_source_str))
}

fn parse_bool(key: &str) -> Option<bool> {
match std::env::var(key) {
Ok(val) => match val.parse() {
Ok(parsed) => Some(parsed),
Err(_) => {
panic!("\n{}\n\texpected bool, received '{}' [value=invalid]\n", key, val);
}
},
Err(_) => None,
}
}

fn parse_i32(key: &str) -> Option<i32> {
match std::env::var(key) {
Ok(val) => match val.parse() {
Ok(parsed) => Some(parsed),
Err(_) => {
panic!("\n{}\n\texpected i32, received '{}' [value=invalid]\n", key, val);
}
},
Err(_) => None,
}
}

fn parse_vec(key: &str) -> Option<Vec<String>> {
match std::env::var(key) {
Ok(val) => match serde_json::from_str::<Vec<String>>(&val) {
Ok(parsed) => Some(parsed),
Err(_) => {
panic!("\n{}\n\texpected vec, received '{}' [value=invalid]\n", key, val);
}
},
Err(_) => None,
}
}

fn parse_path(key: &str) -> Option<std::path::PathBuf> {
match std::env::var(key) {
Ok(value) => {
Some(std::path::PathBuf::from(value))
}
Err(_) => {
None
}
}
}

fn load_env_vars() -> settings::Config {
let (authorization, video_source) = mandatory_vars();
let debug = parse_bool("debug").unwrap_or(settings::default_debug());
let video_host = std::env::var("video_host").unwrap_or(settings::default_video_host());
let video_port = parse_i32("video_port").unwrap_or(settings::default_video_port());
let session_duration = parse_i32("session_duration").unwrap_or(settings::default_session_duration());
let file_formats = parse_vec("file_formats").unwrap_or(settings::default_file_formats());
let workers = parse_i32("workers").unwrap_or(settings::default_workers());
let max_connections = parse_i32("max_connections").unwrap_or(settings::default_max_connections());
let websites = parse_vec("websites").unwrap_or(settings::default_websites());
let secure_session = parse_bool("secure_session").unwrap_or(settings::default_secure_session());
let key_file = parse_path("key_file").unwrap_or(settings::default_ssl());
let cert_file = parse_path("cert_file").unwrap_or(settings::default_ssl());
settings::Config {
authorization,
video_source,
debug,
video_host,
video_port,
session_duration,
file_formats,
workers,
max_connections,
websites,
secure_session,
key_file,
cert_file,
}
}

/// Retrieves the configuration from the provided command-line arguments.
///
/// # Returns
///
/// An `Arc` of the Config struct containing the application configuration.
pub fn get_config() -> Arc<Config> {
let env_file = env::var("env_file").unwrap_or(".env".to_string());
let env_file_path = env::current_dir()
pub fn get_config() -> std::sync::Arc<settings::Config> {
// todo: Update docs to be explicit about what `env_file` means
let env_file = std::env::var("env_file").unwrap_or(".env".to_string());
let env_file_path = std::env::current_dir()
.unwrap_or_default()
.join(env_file);
match dotenv::from_path(env_file_path.as_path()) {
Ok(_) => {
// todo: fix envy parsing or parse manually
// todo: neatly format the message in panic
// println!("Env vars loaded successfully");
// let authorization_str = env::var("authorization").expect("authorization not set in .env");
// let authorization: HashMap<String, String> =
// serde_json::from_str(&authorization_str).expect("Error parsing JSON");
// println!("AUTH::{:?}", authorization);

match envy::from_env::<Config>() {
Ok(config) => {
let mut errors = "".to_owned();
if !config.video_source.exists() || !config.video_source.is_dir() {
let err1 = format!(
"\nvideo_source\n\tInput [{}] is not a valid directory [value=invalid]\n",
config.video_source.to_string_lossy()
);
errors.push_str(&err1);
}
for (username, password) in &config.authorization {
if username.len() < 4 {
let err2 = format!(
"\nauthorization\n\t[{}: {}] username should be at least 4 or more characters [value=invalid]\n",
username, "*".repeat(password.len())
);
errors.push_str(&err2);
}
if password.len() < 8 {
let err3 = format!(
"\nauthorization\n\t[{}: {}] password should be at least 8 or more characters [value=invalid]\n",
username, "*".repeat(password.len())
);
errors.push_str(&err3);
}
}
if !errors.is_empty() {
panic!("{}", errors);
}
return Arc::new(config);
let config = load_env_vars();
let mut errors = "".to_owned();
if !config.video_source.exists() || !config.video_source.is_dir() {
let err1 = format!(
"\nvideo_source\n\tInput [{}] is not a valid directory [value=invalid]\n",
config.video_source.to_string_lossy()
);
errors.push_str(&err1);
}
for (username, password) in &config.authorization {
if username.len() < 4 {
let err2 = format!(
"\nauthorization\n\t[{}: {}] username should be at least 4 or more characters [value=invalid]\n",
username, "*".repeat(password.len())
);
errors.push_str(&err2);
}
if password.len() < 8 {
let err3 = format!(
"\nauthorization\n\t[{}: {}] password should be at least 8 or more characters [value=invalid]\n",
username, "*".repeat(password.len())
);
errors.push_str(&err3);
}
Err(err) => panic!("Error parsing environment variables: {}", err)
}
if !errors.is_empty() {
panic!("{}", errors);
}
return std::sync::Arc::new(config);
}
Err(err) => panic!("Error loading environment variables: {}", err)
}
Expand Down

0 comments on commit e545d89

Please sign in to comment.