1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284
//! Parse CLI options, setup on boot, read .env values
use crate::errors::AtomicServerResult;
use clap::Parser;
use dotenv::dotenv;
use std::net::IpAddr;
use std::path::PathBuf;
/// Store and share Atomic Data! Visit https://atomicdata.dev for more info. Pass no subcommands to launch the server. The `.env` of your current directory will be read.
#[derive(Clone, Parser, Debug)]
#[clap(about, author, version)]
pub struct Opts {
/// The subcommand being run
#[clap(subcommand)]
pub command: Option<Command>,
/// Recreates the `/setup` Invite for creating a new Root User. Also re-runs various populate commands, and re-builds the index
#[clap(long, env = "ATOMIC_INITIALIZE")]
pub initialize: bool,
/// Re-builds the indexes. Parses all the resources.
/// Do this when updating requires it, or if you have issues with Collections / Queries / Search.
#[clap(long, env = "ATOMIC_REBUILD_INDEX")]
pub rebuild_indexes: bool,
/// Use staging environments for services like LetsEncrypt
#[clap(long, env = "ATOMIC_DEVELOPMENT")]
pub development: bool,
/// The origin domain where the app is hosted, without the port and schema values.
#[clap(long, default_value = "localhost", env = "ATOMIC_DOMAIN")]
pub domain: String,
// 9.883 is decimal for the `⚛` character.
/// The port where the HTTP app is available. Set to 80 if you want this to be available on the network.
#[clap(short, long, default_value = "9883", env = "ATOMIC_PORT")]
pub port: u32,
/// The port where the HTTPS app is available. Set to 443 if you want this to be available on the network.
#[clap(
long,
default_value = "9884",
env = "ATOMIC_PORT_HTTPS",
requires = "https"
)]
pub port_https: u32,
/// The IP address of the server. Set to :: if you want this to be available to other devices on your network.
#[clap(long, default_value = "::", env = "ATOMIC_IP")]
pub ip: IpAddr,
/// Use HTTPS instead of HTTP.
/// Will get certificates from LetsEncrypt fully automated.
#[clap(long, env = "ATOMIC_HTTPS")]
pub https: bool,
/// Initializes DNS-01 challenge for LetsEncrypt. Use this if you want to use subdomains.
#[clap(long, env = "ATOMIC_HTTPS_DNS", requires = "https")]
pub https_dns: bool,
/// The contact mail address for Let's Encrypt HTTPS setup
#[clap(long, env = "ATOMIC_EMAIL")]
pub email: Option<String>,
/// Custom JS script to include in the body of the HTML template
#[clap(long, default_value = "", env = "ATOMIC_SCRIPT")]
pub script: String,
/// Path for atomic data config directory. Defaults to "~/.config/atomic/""
#[clap(long, env = "ATOMIC_CONFIG_DIR")]
pub config_dir: Option<PathBuf>,
/// Path for atomic data store folder. Contains your Store, uploaded files and more. Default value depends on your OS.
#[clap(long, env = "ATOMIC_DATA_DIR")]
pub data_dir: Option<PathBuf>,
/// CAUTION: Skip authentication checks, making all data publicly readable. Improves performance.
#[clap(long, env = "ATOMIC_PUBLIC_MODE")]
pub public_mode: bool,
/// The full URL of the server. It should resolve to the home page. Set this if you use an external server or tunnel, instead of directly exposing atomic-server. If you leave this out, it will be generated from `domain`, `port` and `http` / `https`.
#[clap(long, env = "ATOMIC_SERVER_URL")]
pub server_url: Option<String>,
/// How much logs you want. Also influences what is sent to your trace service, if you've set one (e.g. OpenTelemetry)
#[clap(value_enum, long, default_value = "info", env = "RUST_LOG")]
pub log_level: LogLevel,
/// How you want to trace what's going on with the server. Useful for monitoring performance and errors in production.
/// Combine with `log_level` to get more or less data (`trace` is the most verbose)
#[clap(value_enum, long, env = "ATOMIC_TRACING", default_value = "stdout")]
pub trace: Tracing,
/// Introduces random delays in the server, to simulate a slow connection. Useful for testing.
#[clap(long, env = "ATOMIC_SLOW_MODE")]
pub slow_mode: bool,
}
#[derive(clap::ValueEnum, Clone, Debug)]
pub enum Tracing {
/// Log to STDOUT in your terminal
Stdout,
/// Create a file in the current directory with tracing data, that can be opened with the chrome://tracing/ URL
Chrome,
/// Log to a local OpenTelemetry service (e.g. Jaeger), using default ports
Opentelemetry,
}
#[derive(clap::ValueEnum, Clone, Debug)]
pub enum LogLevel {
Warn,
Info,
Debug,
Trace,
}
#[derive(Parser, Clone, Debug)]
pub enum Command {
/// Create and save a JSON-AD backup of the store.
#[clap(name = "export")]
Export(ExportOpts),
/// Import a JSON-AD file or stream to the store. By default creates Commits for all changes, maintaining version history. Use --force to allow importing other types of files.
#[clap(name = "import", trailing_var_arg = true)]
Import(ImportOpts),
/// Creates a `.env` file in your current directory that shows various options that you can set.
#[clap(name = "generate-dotenv")]
CreateDotEnv,
/// Returns the currently selected options, based on the passed flags and parsed environment variables.
#[clap(name = "show-config")]
ShowConfig,
/// Danger! Removes all data from the store.
#[clap(name = "reset")]
Reset,
}
#[derive(Parser, Clone, Debug)]
pub struct ExportOpts {
/// Where the exported file should be saved "~/.config/atomic/backups/{date}.json"
#[clap(short)]
pub path: Option<PathBuf>,
/// Do not export resources that are externally defined, which are cached by this Server.
#[clap(long)]
pub only_internal: bool,
}
#[derive(Parser, Clone, Debug)]
pub struct ImportOpts {
/// Path of the file to be imported.
#[clap(long)]
pub file: PathBuf,
/// The URL of the Importer (parent) Resource to be used.
/// This will set the hierarchical location of the imported items.
/// If not passed, the default Importer `/import` will be used.
#[clap(long)]
pub parent: Option<String>,
/// Skip checks, allows for importing things like Commits.
#[clap(long)]
pub force: bool,
}
/// Start atomic-server, oi mate
#[derive(Parser, Clone, Debug)]
pub struct ServerOpts {}
/// Configuration for the server.
/// These values are set when the server initializes, and do not change while running.
/// These are constructed from [Opts], which in turn are constructed from CLI arguments and ENV variables.
#[derive(Clone, Debug)]
pub struct Config {
/// Full domain + schema, e.g. `https://example.com`. Is either generated from `domain` and `schema`, or is the `custom_server_url`.
pub server_url: String,
/// CLI + ENV options
pub opts: Opts,
// === PATHS ===
/// Path for atomic data config. Used to construct most other paths.
pub config_dir: PathBuf,
/// Path where TLS key should be stored for HTTPS.
pub key_path: PathBuf,
/// Path where TLS certificate should be stored for HTTPS.
pub cert_path: PathBuf,
/// Path where TLS certificates should be stored for HTTPS.
pub https_path: PathBuf,
/// Path where config.toml is located, which contains info about the Agent
pub config_file_path: PathBuf,
/// Path where the public static files folder is located
pub static_path: PathBuf,
/// Path to where the store / database is located.
pub store_path: PathBuf,
/// Path to where the uploaded files are stored.
pub uploads_path: PathBuf,
/// Path to where the search index for tantivy full text search is located
pub search_index_path: PathBuf,
/// If true, the initialization scripts will be ran (create first Drive, Agent, indexing, etc)
pub initialize: bool,
}
/// Parse .env and CLI options
pub fn read_opts() -> Opts {
// Parse .env file (do this before parsing the CLI opts)
dotenv().ok();
// Parse CLI options, .env values, set defaults
Opts::parse()
}
/// Creates the server config, reads .env values and sets defaults
pub fn build_config(opts: Opts) -> AtomicServerResult<Config> {
// Directories & file system
let project_dirs = directories::ProjectDirs::from("", "", "atomic-data")
.expect("Could not find Project directories on your OS");
// Persistent user data
let data_dir = opts
.data_dir
.clone()
.unwrap_or_else(|| project_dirs.data_dir().to_owned());
let mut store_path = data_dir.clone();
store_path.push("store");
let mut uploads_path = data_dir.clone();
uploads_path.push("uploads");
let mut static_path = data_dir;
static_path.push("static");
// Config data
let config_dir = if let Some(dir) = &opts.config_dir {
dir.clone()
} else {
atomic_lib::config::default_config_dir_path()?
};
let config_file_path = config_dir.join("config.toml");
let mut https_path = config_dir.clone();
https_path.push("https");
let mut cert_path = config_dir.clone();
cert_path.push("https/cert.pem");
let mut key_path = config_dir.clone();
key_path.push("https/key.pem");
// Cache data
let cache_dir = project_dirs.cache_dir();
let mut search_index_path = cache_dir.to_owned();
search_index_path.push("search_index");
let initialize = !std::path::Path::exists(&store_path) || opts.initialize;
if opts.https & opts.email.is_none() {
return Err(
"The `--email` flag (or ATOMIC_EMAIL env) is required for getting an HTTPS certificate from letsencrypt.org."
.into(),
);
}
let schema = if opts.https { "https" } else { "http" };
// This logic could be a bit too complicated, but I'm not sure on how to make this simpler.
let server_url = if let Some(addr) = opts.server_url.clone() {
addr
} else if opts.https && opts.port_https == 443 || !opts.https && opts.port == 80 {
format!("{}://{}", schema, opts.domain)
} else {
format!("{}://{}:{}", schema, opts.domain, opts.port)
};
Ok(Config {
initialize,
opts,
cert_path,
config_dir,
config_file_path,
https_path,
key_path,
server_url,
static_path,
store_path,
search_index_path,
uploads_path,
})
}