filite/src/setup.rs

324 lines
9.4 KiB
Rust
Raw Normal View History

//! Utilities used during the initial setup
2019-10-08 00:56:38 +00:00
use crate::{globals::KEY, Pool};
2019-10-09 13:14:47 +00:00
use actix_web::middleware::Logger;
2019-10-21 18:29:39 +00:00
use diesel::{
r2d2::{self, ConnectionManager},
sqlite::SqliteConnection,
};
use std::{env, path::PathBuf};
2019-10-08 00:56:38 +00:00
#[cfg(not(feature = "dev"))]
use dialoguer::{Confirmation, PasswordInput};
#[cfg(not(feature = "dev"))]
2019-10-17 18:15:39 +00:00
use dirs;
#[cfg(feature = "dev")]
2019-10-08 00:56:38 +00:00
use dotenv;
#[cfg(feature = "dev")]
use std::str::FromStr;
#[cfg(not(feature = "dev"))]
use std::{fs, process};
2019-10-21 18:29:39 +00:00
#[cfg(not(feature = "dev"))]
2019-10-17 18:15:39 +00:00
use toml;
2019-10-08 00:56:38 +00:00
/// Returns a path to the directory storing application data
#[cfg(not(feature = "dev"))]
2019-10-08 00:56:38 +00:00
pub fn get_data_dir() -> PathBuf {
let base_dir = dirs::data_dir().expect("Unable to determine the data directory");
base_dir.join(env!("CARGO_PKG_NAME"))
}
/// Returns a path to the directory storing application config
#[cfg(not(feature = "dev"))]
pub fn get_config_dir() -> PathBuf {
let base_dir = dirs::config_dir().expect("Unable to determine the config directory");
base_dir.join(env!("CARGO_PKG_NAME"))
2019-10-08 00:56:38 +00:00
}
/// Returns a path to the configuration file
#[cfg(not(feature = "dev"))]
2019-10-08 00:56:38 +00:00
fn get_config_path() -> PathBuf {
get_config_dir().join("config.toml")
2019-10-08 00:56:38 +00:00
}
2019-10-21 15:19:54 +00:00
/// Returns a path to the bearer token hash
#[cfg(not(feature = "dev"))]
2019-10-25 03:39:34 +00:00
pub fn get_password_path() -> PathBuf {
get_config_dir().join("passwd")
2019-10-21 15:19:54 +00:00
}
/// Returns the BLAKE2b digest of the input string
pub fn hash(input: &[u8]) -> Vec<u8> {
blake3::keyed_hash(KEY, input).as_bytes().to_vec()
2019-10-21 15:19:54 +00:00
}
2019-10-09 15:59:52 +00:00
/// Returns an environment variable and panic if it isn't found
#[cfg(feature = "dev")]
2019-10-21 15:19:54 +00:00
#[macro_export]
2019-10-09 15:59:52 +00:00
macro_rules! get_env {
($k:literal) => {
2019-10-21 16:31:25 +00:00
std::env::var($k).expect(&format!("Can't find {} environment variable", $k));
2019-10-09 15:59:52 +00:00
};
}
/// Returns a parsed environment variable and panic if it isn't found or is not parsable
#[cfg(feature = "dev")]
2019-10-09 15:59:52 +00:00
macro_rules! parse_env {
($k:literal) => {
2019-10-21 16:31:25 +00:00
get_env!($k).parse().expect(&format!("Invalid {}", $k))
2019-10-09 15:59:52 +00:00
};
}
2019-10-08 00:56:38 +00:00
/// Application configuration
2019-10-08 16:06:30 +00:00
#[derive(Serialize, Deserialize, Clone)]
#[cfg_attr(not(feature = "dev"), serde(default))]
2019-10-08 00:56:38 +00:00
pub struct Config {
2019-10-08 01:38:29 +00:00
/// Port to listen on
2019-10-08 02:37:25 +00:00
pub port: u16,
2019-10-08 00:56:38 +00:00
/// SQLite database connection url
2019-10-08 02:37:25 +00:00
pub database_url: String,
2019-10-08 00:56:38 +00:00
/// SQLite database connection pool size
2019-10-08 02:37:25 +00:00
pub pool_size: u32,
2019-10-08 00:56:38 +00:00
/// Directory where to store static files
2019-10-08 02:37:25 +00:00
pub files_dir: PathBuf,
2020-01-15 06:50:54 +00:00
/// Highlight.js configuration
pub highlight: HighlightConfig,
}
#[derive(Serialize, Deserialize, Clone)]
#[cfg_attr(not(feature = "dev"), serde(default))]
pub struct HighlightConfig {
/// Theme to use
pub theme: String,
/// Additional languages to include
pub languages: Vec<String>,
2019-10-08 00:56:38 +00:00
}
#[cfg(not(feature = "dev"))]
2019-10-08 00:56:38 +00:00
impl Default for Config {
fn default() -> Self {
2019-10-08 01:38:29 +00:00
let port = 8080;
2019-10-08 00:56:38 +00:00
let database_url = {
let path = get_data_dir().join("database.db");
2019-10-08 01:38:29 +00:00
path.to_str()
2019-10-21 16:31:25 +00:00
.expect("Can't convert database path to string")
2019-10-08 01:38:29 +00:00
.to_owned()
2019-10-08 00:56:38 +00:00
};
2021-09-19 10:05:43 +00:00
let pool_size = std::cmp::max(2, num_cpus::get() as u32 / 2);
let files_dir = get_data_dir().join("files");
2019-10-08 00:56:38 +00:00
2020-01-15 06:50:54 +00:00
Self {
2019-10-08 01:38:29 +00:00
port,
2019-10-08 00:56:38 +00:00
database_url,
pool_size,
files_dir,
2020-01-15 06:50:54 +00:00
highlight: HighlightConfig::default(),
}
}
}
impl Default for HighlightConfig {
fn default() -> Self {
Self {
theme: "github".to_owned(),
languages: vec!["rust".to_owned()],
2019-10-08 00:56:38 +00:00
}
}
}
impl Config {
/// Deserialize the config file
#[cfg(not(feature = "dev"))]
2019-10-08 00:56:38 +00:00
pub fn read_file() -> Result<Self, &'static str> {
let path = get_config_path();
let contents = if let Ok(contents) = fs::read_to_string(&path) {
contents
} else {
return Err("Can't read config file.");
};
let result = toml::from_str(&contents);
2019-10-09 15:59:52 +00:00
if result.is_err() {
return Err("Invalid config file.");
}
let mut result: Config = result.unwrap();
if result.files_dir.is_absolute() {
2019-10-25 23:41:11 +00:00
if fs::create_dir_all(&result.files_dir).is_err() {
2019-10-09 15:59:52 +00:00
return Err("Can't create files_dir.");
}
result.files_dir = match result.files_dir.canonicalize() {
Ok(path) => path,
Err(_) => return Err("Invalid files_dir."),
}
} else {
let files_dir = get_data_dir().join(&result.files_dir);
2019-10-09 15:59:52 +00:00
if fs::create_dir_all(&files_dir).is_err() {
2019-10-09 15:59:52 +00:00
return Err("Can't create files_dir.");
}
result.files_dir = match files_dir.canonicalize() {
2019-10-09 15:59:52 +00:00
Ok(path) => path,
Err(_) => return Err("Invalid files_dir."),
}
2019-10-08 00:56:38 +00:00
}
2019-10-09 15:59:52 +00:00
Ok(result)
2019-10-08 00:56:38 +00:00
}
/// Serialize the config file
#[cfg(not(feature = "dev"))]
2019-10-08 00:56:38 +00:00
pub fn write_file(&self) -> Result<(), &'static str> {
let path = get_config_path();
let contents = toml::to_string(&self).expect("Can't serialize config.");
match fs::write(&path, &contents) {
Ok(_) => Ok(()),
Err(_) => Err("Can't write config file."),
}
}
2019-10-08 01:38:29 +00:00
/// Creates a config from environment variables
#[cfg(feature = "dev")]
2019-10-08 01:38:29 +00:00
pub fn debug() -> Self {
dotenv::dotenv().ok();
2019-10-09 15:59:52 +00:00
let port = parse_env!("PORT");
let database_url = get_env!("DATABASE_URL");
let pool_size = parse_env!("POOL_SIZE");
2019-10-08 01:38:29 +00:00
let files_dir = {
2019-10-09 15:59:52 +00:00
let files_dir = get_env!("FILES_DIR");
2019-10-09 02:49:38 +00:00
let path = PathBuf::from_str(&files_dir).expect("Can't convert files dir to path");
if path.is_absolute() {
path.canonicalize().expect("Invalid FILES_DIR")
} else {
let cargo_manifest_dir = env!("CARGO_MANIFEST_DIR");
let mut cargo_manifest_dir = PathBuf::from_str(cargo_manifest_dir)
2019-10-21 16:31:25 +00:00
.expect("Can't convert cargo manifest dir to path");
2019-10-09 02:49:38 +00:00
cargo_manifest_dir.push(&path);
cargo_manifest_dir
.canonicalize()
.expect("Invalid FILES_DIR")
}
2019-10-08 01:38:29 +00:00
};
2020-01-15 06:50:54 +00:00
Self {
2019-10-08 01:38:29 +00:00
port,
database_url,
pool_size,
files_dir,
2020-01-15 06:50:54 +00:00
highlight: HighlightConfig::default(),
2019-10-08 01:38:29 +00:00
}
}
2019-10-08 00:56:38 +00:00
}
/// Creates a SQLite database connection pool
pub fn create_pool(url: &str, size: u32) -> Pool {
let manager = ConnectionManager::<SqliteConnection>::new(url);
r2d2::Pool::builder()
.max_size(size)
.build(manager)
2019-10-21 16:31:25 +00:00
.expect("Can't create pool")
2019-10-08 00:56:38 +00:00
}
2019-10-08 02:17:11 +00:00
/// Initializes the logger
pub fn init_logger() {
if cfg!(feature = "dev") && env::var_os("RUST_LOG").is_none() {
2019-10-08 02:17:11 +00:00
env::set_var("RUST_LOG", "actix_web=debug");
} else if !cfg!(feature = "dev") {
2019-10-09 02:49:38 +00:00
env::set_var("RUST_LOG", "actix_web=info");
2019-10-08 02:17:11 +00:00
}
env_logger::init();
}
2019-10-09 13:14:47 +00:00
2019-10-15 12:04:34 +00:00
/// Returns the logger middleware
2019-10-09 13:14:47 +00:00
pub fn logger_middleware() -> Logger {
#[cfg(feature = "dev")]
2019-10-15 12:04:34 +00:00
{
dotenv::dotenv().ok();
if let Ok(format) = env::var("LOG_FORMAT") {
Logger::new(&format)
2019-10-09 13:14:47 +00:00
} else {
Logger::default()
}
}
2019-10-15 12:04:34 +00:00
#[cfg(not(feature = "dev"))]
2019-10-15 12:04:34 +00:00
{
Logger::default()
}
2019-10-09 13:14:47 +00:00
}
2019-10-17 18:55:59 +00:00
/// Performs the initial setup
2020-03-09 19:35:18 +00:00
// mode: 0 = normal, 1 = reset password, 2 = reset everything
#[cfg(not(feature = "dev"))]
2020-03-09 19:35:18 +00:00
pub fn init(mode: u8) -> Config {
fs::create_dir_all(get_config_dir()).unwrap_or_else(|e| {
eprintln!("Can't create config directory: {}.", e);
process::exit(1);
});
2019-10-17 19:12:42 +00:00
let password_path = get_password_path();
2020-03-09 19:35:18 +00:00
if mode > 0 {
let mut password;
loop {
password = PasswordInput::new()
.with_prompt("Enter password")
.with_confirmation("Confirm password", "Mismatched passwords")
.allow_empty_password(true)
.interact()
.unwrap_or_else(|e| {
eprintln!("Can't read password: {}", e);
process::exit(1);
});
if !password.is_empty() {
break;
}
let keep_empty = Confirmation::new()
.with_text("Are you sure you want to leave an empty password? This will disable authentication.")
.default(false)
.interact()
.unwrap_or_else(|e| {
eprintln!("Can't read password: {}", e);
process::exit(1);
});
if keep_empty {
break;
}
}
let password_hash = hash(password.as_bytes());
2019-10-25 03:39:34 +00:00
fs::write(&password_path, password_hash.as_slice()).unwrap_or_else(|e| {
eprintln!("Can't write password: {}", e);
2019-10-17 19:12:42 +00:00
process::exit(1);
});
2020-01-16 05:40:03 +00:00
} else if !get_password_path().exists() {
2020-03-09 19:35:18 +00:00
eprintln!("No password file found. Try running `filite init` or `filite passwd`.");
2020-01-16 05:40:03 +00:00
process::exit(1);
2019-10-17 18:55:59 +00:00
}
let config_path = get_config_path();
2020-03-09 19:35:18 +00:00
if mode > 1 {
println!("Generating config file at {}", config_path.display());
let config = Config::default();
config.write_file().unwrap_or_else(|e| {
eprintln!("Can't write config file: {}", e);
process::exit(1);
});
2020-01-16 05:40:03 +00:00
} else if !config_path.exists() {
eprintln!("No config file found. Try running `filite init`.");
process::exit(1);
}
2020-03-09 19:35:18 +00:00
if mode > 0 {
2020-01-16 05:40:03 +00:00
process::exit(0);
}
2019-10-17 18:55:59 +00:00
Config::read_file().unwrap_or_else(|e| {
eprintln!("{}", e);
process::exit(1);
})
}