lora_server/config/mod.rs
1//! Runtime configuration for `lora-server`.
2//!
3//! Resolves the bind address (`host` + `port`) from, in order of precedence:
4//!
5//! 1. CLI flags: `--host <HOST>`, `--port <PORT>` (also accepts `--host=<HOST>`).
6//! 2. Environment variables: `LORA_SERVER_HOST`, `LORA_SERVER_PORT`.
7//! 3. Built-in defaults: `127.0.0.1:4747`.
8//!
9//! The default HTTP port for the local LoraDB server is `4747` — short,
10//! memorable, and outside the most common local development ports
11//! (3000/4000/5000/8000/8080/8443/9000) and standard database ports
12//! (Postgres 5432, Redis 6379, MongoDB 27017, Elasticsearch 9200, MySQL
13//! 3306) so it does not collide with typical side projects.
14//!
15//! The parser also understands `--help` / `--version`, which return a
16//! [`ConfigOutcome`] variant instead of a [`ServerConfig`] so the binary
17//! can print and exit before booting the runtime.
18//!
19//! Layout:
20//! - `errors` — [`ConfigError`] and its conversions.
21//! - `env` — [`EnvInputs`], `resolve`, `resolve_from_process` and the
22//! per-flag parse helpers.
23//! - `help` — `--help` and `--version` static text.
24//! - `tests` — unit tests for the resolution logic.
25
26mod env;
27mod errors;
28mod help;
29
30#[cfg(test)]
31mod tests;
32
33use lora_database::SyncMode;
34
35pub use env::{resolve, resolve_from_process, EnvInputs};
36pub use errors::ConfigError;
37pub use help::{help_text, version_text};
38
39pub const DEFAULT_HOST: &str = "127.0.0.1";
40pub const DEFAULT_PORT: u16 = 4747;
41pub const HOST_ENV: &str = "LORA_SERVER_HOST";
42pub const PORT_ENV: &str = "LORA_SERVER_PORT";
43pub const SNAPSHOT_PATH_ENV: &str = "LORA_SERVER_SNAPSHOT_PATH";
44pub const WAL_DIR_ENV: &str = "LORA_SERVER_WAL_DIR";
45pub const WAL_SYNC_MODE_ENV: &str = "LORA_SERVER_WAL_SYNC_MODE";
46
47/// Default segment target for WAL-enabled deployments. Matches the
48/// in-tree `WalConfig::enabled` constructor.
49pub const DEFAULT_WAL_SEGMENT_TARGET_BYTES: u64 = 8 * 1024 * 1024;
50
51#[derive(Debug, Clone, PartialEq, Eq)]
52pub struct ServerConfig {
53 pub host: String,
54 pub port: u16,
55 /// When set, the server mounts the `/admin/snapshot/{save,load}` routes
56 /// and wires them to this path. `None` means the admin surface is
57 /// disabled entirely — the default, so we never expose admin endpoints
58 /// on a network-reachable process unless the operator asks for it.
59 pub snapshot_path: Option<std::path::PathBuf>,
60 /// When `Some`, the server restores the graph from this path at boot.
61 /// Missing file at boot is treated as an empty graph (same as without
62 /// `--restore-from`). Independent of `snapshot_path` so operators can
63 /// restore from a read-only location and write back somewhere else.
64 pub restore_from: Option<std::path::PathBuf>,
65 /// When `Some`, the server attaches a WAL at this directory and
66 /// brackets every query with begin/commit/abort. Also unlocks the
67 /// `/admin/checkpoint`, `/admin/wal/status`, and
68 /// `/admin/wal/truncate` admin routes (only when `snapshot_path`
69 /// is also configured).
70 pub wal_dir: Option<std::path::PathBuf>,
71 /// Durability cadence for the WAL. Ignored when `wal_dir` is `None`.
72 pub wal_sync_mode: SyncMode,
73}
74
75impl Default for ServerConfig {
76 fn default() -> Self {
77 Self {
78 host: DEFAULT_HOST.to_string(),
79 port: DEFAULT_PORT,
80 snapshot_path: None,
81 restore_from: None,
82 wal_dir: None,
83 wal_sync_mode: SyncMode::default(),
84 }
85 }
86}
87
88impl ServerConfig {
89 pub fn bind_addr(&self) -> String {
90 if self.host.contains(':') && !self.host.starts_with('[') {
91 format!("[{}]:{}", self.host, self.port)
92 } else {
93 format!("{}:{}", self.host, self.port)
94 }
95 }
96}
97
98#[derive(Debug, Clone, PartialEq, Eq)]
99pub enum ConfigOutcome {
100 Run(ServerConfig),
101 Help(String),
102 Version(String),
103}