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}