atomic_server_lib/
config.rs

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