mod checkpoint;
mod cluster;
mod cold_storage;
mod env;
mod observability;
mod tls;
pub use checkpoint::CheckpointSettings;
pub use cluster::ClusterSettings;
pub use cold_storage::ColdStorageSettings;
pub use env::{apply_env_overrides, parse_memory_size, parse_seed_nodes};
pub use observability::{
ObservabilityConfig, OtlpConfig, OtlpExportConfig, OtlpReceiverConfig, PromqlConfig,
apply_observability_env, validate_feature_availability,
};
pub use tls::{EncryptionSettings, TlsSettings};
use std::net::SocketAddr;
use std::path::PathBuf;
use nodedb_types::config::TuningConfig;
use serde::{Deserialize, Serialize};
use super::EngineConfig;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerConfig {
pub listen: SocketAddr,
pub pg_listen: SocketAddr,
pub http_listen: SocketAddr,
pub data_dir: PathBuf,
pub data_plane_cores: usize,
pub memory_limit: usize,
#[serde(default = "default_max_connections")]
pub max_connections: usize,
pub engines: EngineConfig,
#[serde(default)]
pub auth: super::AuthConfig,
#[serde(default)]
pub tls: Option<TlsSettings>,
#[serde(default)]
pub encryption: Option<EncryptionSettings>,
#[serde(default = "default_log_format")]
pub log_format: String,
#[serde(default)]
pub checkpoint: CheckpointSettings,
#[serde(default)]
pub resp_listen: Option<SocketAddr>,
#[serde(default)]
pub ilp_listen: Option<SocketAddr>,
#[serde(default)]
pub cluster: Option<ClusterSettings>,
#[serde(default)]
pub cold_storage: Option<ColdStorageSettings>,
#[serde(default)]
pub tuning: TuningConfig,
#[serde(default)]
pub observability: ObservabilityConfig,
}
impl Default for ServerConfig {
fn default() -> Self {
let cores = std::thread::available_parallelism()
.map(|n| n.get().saturating_sub(1).max(1))
.unwrap_or(1);
Self {
listen: SocketAddr::from(([127, 0, 0, 1], 6433)),
pg_listen: SocketAddr::from(([127, 0, 0, 1], 6432)),
http_listen: SocketAddr::from(([127, 0, 0, 1], 6480)),
data_dir: default_data_dir(),
data_plane_cores: cores,
max_connections: default_max_connections(),
memory_limit: 1024 * 1024 * 1024, engines: EngineConfig::default(),
auth: super::AuthConfig::default(),
tls: None,
encryption: None,
log_format: "text".into(),
checkpoint: CheckpointSettings::default(),
resp_listen: None,
ilp_listen: None,
cluster: None,
cold_storage: None,
tuning: TuningConfig::default(),
observability: ObservabilityConfig::default(),
}
}
}
impl ServerConfig {
pub fn from_file(path: &std::path::Path) -> crate::Result<Self> {
let content = std::fs::read_to_string(path).map_err(|e| crate::Error::Config {
detail: format!("failed to read config file {}: {e}", path.display()),
})?;
toml::from_str(&content).map_err(|e| crate::Error::Config {
detail: format!("invalid TOML config: {e}"),
})
}
pub fn wal_dir(&self) -> PathBuf {
self.data_dir.join("wal")
}
pub fn segments_dir(&self) -> PathBuf {
self.data_dir.join("segments")
}
pub fn catalog_path(&self) -> PathBuf {
self.data_dir.join("system.redb")
}
}
fn default_max_connections() -> usize {
4096
}
fn default_log_format() -> String {
"text".into()
}
fn default_data_dir() -> PathBuf {
if let Some(dir) = platform_data_dir() {
dir.join("nodedb")
} else {
PathBuf::from("nodedb-data")
}
}
fn platform_data_dir() -> Option<PathBuf> {
#[cfg(target_os = "linux")]
{
if let Ok(xdg) = std::env::var("XDG_DATA_HOME")
&& !xdg.is_empty()
{
return Some(PathBuf::from(xdg));
}
home_dir().map(|h| h.join(".local").join("share"))
}
#[cfg(target_os = "macos")]
{
home_dir().map(|h| h.join("Library").join("Application Support"))
}
#[cfg(target_os = "windows")]
{
if let Ok(local) = std::env::var("LOCALAPPDATA")
&& !local.is_empty()
{
return Some(PathBuf::from(local));
}
home_dir().map(|h| h.join("AppData").join("Local"))
}
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
{
home_dir().map(|h| h.join(".local").join("share"))
}
}
fn home_dir() -> Option<PathBuf> {
#[cfg(target_os = "windows")]
{
std::env::var("USERPROFILE").ok().map(PathBuf::from)
}
#[cfg(not(target_os = "windows"))]
{
std::env::var("HOME").ok().map(PathBuf::from)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_config_valid() {
let cfg = ServerConfig::default();
assert_eq!(cfg.listen.port(), 6433);
assert_eq!(cfg.pg_listen.port(), 6432);
assert_eq!(cfg.http_listen.port(), 6480);
assert!(cfg.data_plane_cores >= 1);
assert_eq!(cfg.memory_limit, 1024 * 1024 * 1024);
}
#[test]
fn config_roundtrip() {
let cfg = ServerConfig::default();
let toml_str = toml::to_string_pretty(&cfg).expect("serialize");
let _parsed: ServerConfig = toml::from_str(&toml_str).expect("deserialize");
}
}