Skip to main content

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::PerCommit,
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}