use crate::realms::RealmInfo;
use serde::Deserialize;
#[derive(Debug, Clone, Deserialize, Default)]
pub struct EncryptionPaths {
pub own_certificate: Option<String>,
pub own_private_key: Option<String>,
pub trusted_cert_dir: Option<String>,
pub trusted_crl_dir: Option<String>,
pub rejected_dir: Option<String>,
pub issuer_cert_dir: Option<String>,
pub issuer_crl_dir: Option<String>,
}
#[derive(Debug, Clone, Deserialize, Default)]
pub struct TrustPolicyPathsConfig {
pub observed_dir: Option<String>,
}
#[derive(Debug, Clone, Deserialize, Default)]
pub struct EncryptionNoiseConfig {
pub pattern: Option<String>,
pub curve: Option<String>,
pub cipher: Option<String>,
pub hash: Option<String>,
pub static_key_path: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct TrustPolicyConfig {
pub mode: Option<String>,
pub accept_self_signed: Option<bool>,
pub store_new_certs: Option<String>,
pub reject_expired: Option<bool>,
pub reject_before_valid: Option<bool>,
pub enforce_ca_chain: Option<bool>,
pub pin_subjects: Option<Vec<String>>,
pub pin_fingerprints: Option<Vec<String>>,
pub pin_fp_algo: Option<String>,
pub realm_subject_binding: Option<bool>,
pub paths: Option<TrustPolicyPathsConfig>,
}
impl Default for TrustPolicyConfig {
fn default() -> Self {
Self {
mode: Some("open".to_string()),
accept_self_signed: Some(false),
store_new_certs: Some("none".to_string()),
reject_expired: Some(false),
reject_before_valid: Some(false),
enforce_ca_chain: Some(false),
pin_subjects: Some(vec![]),
pin_fingerprints: Some(vec![]),
pin_fp_algo: Some("sha256".to_string()),
realm_subject_binding: Some(false),
paths: Some(TrustPolicyPathsConfig::default()),
}
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct EncryptionConfig {
pub enabled: bool,
pub backend: Option<String>,
pub noise: Option<EncryptionNoiseConfig>,
pub mtls: Option<bool>,
pub accept_self_signed: Option<bool>,
pub paths: Option<EncryptionPaths>,
pub trust_policy: Option<TrustPolicyConfig>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct Config {
pub port: u16,
pub encryption: Option<EncryptionConfig>,
pub bootstrap_nodes: Option<Vec<String>>,
pub realm: Option<RealmInfo>,
pub app_name: Option<String>,
pub logging: Option<LoggingConfig>,
pub node: Option<NodeConfig>,
pub discovery: Option<DiscoveryConfig>,
pub realm_access: Option<RealmAccessConfig>,
pub network: Option<NetworkConfig>,
}
#[derive(Debug, Clone, Default)]
pub struct ConfigDefaults {
pub port: Option<u16>,
pub realm: Option<RealmInfo>,
pub app_name: Option<String>,
pub encryption: Option<EncryptionConfig>,
pub bootstrap_nodes: Option<Vec<String>>,
pub bootstrap_nodes_extend: Option<Vec<String>>,
pub logging: Option<LoggingConfig>,
pub node: Option<NodeConfig>,
pub discovery: Option<DiscoveryConfig>,
}
impl ConfigDefaults {
pub fn apply(self, cfg: &mut Config) {
if let Some(p) = self.port {
cfg.port = p;
}
if let Some(r) = self.realm {
cfg.realm = Some(r);
}
if let Some(a) = self.app_name {
cfg.app_name = Some(a);
}
if let Some(e) = self.encryption {
cfg.encryption = Some(e);
}
if let Some(b) = self.bootstrap_nodes {
cfg.bootstrap_nodes = Some(b);
}
if let Some(l) = self.logging {
cfg.logging = Some(l);
}
if let Some(n) = self.node {
cfg.node = Some(n);
}
if let Some(d) = self.discovery {
cfg.discovery = Some(d);
}
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct LoggingConfig {
pub json_path: Option<String>,
pub json_max_bytes: Option<usize>,
pub json_rotate: Option<u32>,
pub disable_console: Option<bool>,
}
impl Default for EncryptionConfig {
fn default() -> Self {
Self {
enabled: false,
backend: None,
noise: None,
mtls: Some(false),
accept_self_signed: Some(false),
paths: Some(EncryptionPaths::default()),
trust_policy: Some(TrustPolicyConfig::default()),
}
}
}
impl Default for Config {
fn default() -> Self {
Self {
port: 50000,
encryption: Some(EncryptionConfig::default()),
bootstrap_nodes: None,
realm: Some(RealmInfo::default()),
app_name: None,
logging: None,
node: Some(NodeConfig::default()),
discovery: Some(DiscoveryConfig::default()),
realm_access: None,
network: Some(NetworkConfig {
persistence: Some(NetworkPersistenceConfig::default()),
relay: Some(RelayConfig::default()),
}),
}
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct NodeConfig {
pub id: Option<String>,
pub state_dir: Option<String>,
pub id_file: Option<String>,
pub allow_ephemeral: Option<bool>,
pub node_type: Option<String>,
}
impl Default for NodeConfig {
fn default() -> Self {
Self {
id: None,
state_dir: Some("data".to_string()),
id_file: Some("node_id".to_string()),
allow_ephemeral: Some(true),
node_type: Some("daemon".to_string()),
}
}
}
impl NodeConfig {
pub fn resolve_node_id(&self) -> String {
fn warn(msg: &str) {
eprintln!("⚠️ {}", msg);
}
if let Some(id) = &self.id {
if Self::valid_id(id) {
return id.clone();
}
warn("Invalid characters in configured node.id; falling back to persisted/generated");
}
let state_dir = self.state_dir.clone().unwrap_or_else(|| "data".into());
let id_file_name = self.id_file.clone().unwrap_or_else(|| "node_id".into());
let path = std::path::Path::new(&state_dir).join(&id_file_name);
if let Ok(contents) = std::fs::read_to_string(&path) {
let trimmed = contents.trim();
if !trimmed.is_empty() && Self::valid_id(trimmed) {
return trimmed.to_string();
}
warn("Persisted node_id file invalid or empty; regenerating");
}
let new_id = uuid::Uuid::new_v4().to_string();
if std::fs::create_dir_all(&state_dir).is_ok() {
let tmp = path.with_extension("tmp");
if std::fs::write(&tmp, &new_id).is_ok() && std::fs::rename(&tmp, &path).is_ok() {
return new_id;
}
}
if self.allow_ephemeral.unwrap_or(true) {
warn("Using ephemeral node id (not persisted)");
return new_id; }
"unknown-node".to_string()
}
fn valid_id(id: &str) -> bool {
id.len() <= 128
&& id
.chars()
.all(|c| c.is_ascii_alphanumeric() || matches!(c, '-' | '_' | '.'))
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct DiscoveryConfig {
pub enabled: bool,
pub request_interval_secs: Option<u64>,
pub request_want: Option<u16>,
}
impl Default for DiscoveryConfig {
fn default() -> Self {
Self {
enabled: true,
request_interval_secs: Some(90),
request_want: Some(16),
}
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct RealmAccessConfig {
pub allowed_node_types: Option<Vec<String>>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct NetworkPersistenceConfig {
pub enabled: Option<bool>,
pub path: Option<String>,
pub max_entries: Option<usize>,
pub ttl_secs: Option<u64>,
pub save_interval_secs: Option<u64>,
}
impl Default for NetworkPersistenceConfig {
fn default() -> Self {
Self {
enabled: Some(false),
path: None,
max_entries: Some(1024),
ttl_secs: Some(7 * 24 * 3600),
save_interval_secs: Some(60),
}
}
}
#[derive(Debug, Clone, Deserialize, Default)]
pub struct NetworkConfig {
pub persistence: Option<NetworkPersistenceConfig>,
pub relay: Option<RelayConfig>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct RelayConfig {
pub enabled: Option<bool>,
pub store_forward: Option<bool>,
pub queue_max_per_target: Option<usize>,
pub queue_max_global: Option<usize>,
pub selection: Option<String>,
}
impl Default for RelayConfig {
fn default() -> Self {
Self {
enabled: Some(false),
store_forward: Some(false),
queue_max_per_target: Some(1024),
queue_max_global: Some(8192),
selection: Some("none".to_string()),
}
}
}
#[derive(Copy, Clone, Debug)]
pub enum LockConflictPolicy {
Error,
WarnAndOverride,
}
#[derive(Clone, Debug, Default)]
pub struct ConfigLocks {
pub port: Option<u16>,
pub realm: Option<RealmInfo>,
pub app_name: Option<String>,
pub encryption_enabled: Option<bool>,
pub node: Option<NodeLocks>,
}
#[derive(Clone, Debug, Default)]
pub struct NodeLocks {
pub node_type: Option<String>,
}
impl ConfigLocks {
pub fn enforce(&self, cfg: &mut Config, policy: LockConflictPolicy) -> anyhow::Result<()> {
fn handle_conflict<T: std::fmt::Display + Clone + PartialEq>(
field: &str,
current: T,
locked: T,
policy: LockConflictPolicy,
apply: impl FnOnce(T),
) -> anyhow::Result<()> {
if current == locked {
return Ok(());
}
match policy {
LockConflictPolicy::Error => Err(anyhow::anyhow!(
"Config lock violation for '{}': config='{}' locked='{}'",
field,
current,
locked
)),
LockConflictPolicy::WarnAndOverride => {
eprintln!(
"⚠️ Config lock override for '{}': using locked='{}' (was '{}')",
field, locked, current
);
apply(locked);
Ok(())
}
}
}
if let Some(p) = self.port {
let cur = cfg.port;
handle_conflict("port", cur, p, policy, |v| cfg.port = v)?;
}
if let Some(ref r) = self.realm {
let cur = cfg.realm.clone();
match cur {
Some(ref c) => handle_conflict(
"realm",
format!("{}:{}", c.name, c.version),
format!("{}:{}", r.name, r.version),
policy,
|_v| cfg.realm = Some(r.clone()),
)?,
None => {
cfg.realm = Some(r.clone());
}
}
}
if let Some(ref name) = self.app_name {
let cur = cfg.app_name.clone();
match cur {
Some(ref c) => {
handle_conflict("app_name", c.clone(), name.clone(), policy, |_v| {
cfg.app_name = Some(name.clone())
})?
}
None => {
cfg.app_name = Some(name.clone());
}
}
}
if let Some(enabled) = self.encryption_enabled {
if cfg.encryption.is_none() {
cfg.encryption = Some(EncryptionConfig::default());
}
if let Some(ref mut enc) = cfg.encryption {
let cur = enc.enabled;
handle_conflict("encryption.enabled", cur, enabled, policy, |v| {
enc.enabled = v
})?;
}
}
if let Some(ref nl) = self.node {
if cfg.node.is_none() {
cfg.node = Some(NodeConfig::default());
}
if let Some(ref mut node) = cfg.node {
if let Some(ref t) = nl.node_type {
let cur = node.node_type.clone().unwrap_or_default();
handle_conflict("node.node_type", cur.clone(), t.clone(), policy, |_v| {
node.node_type = Some(t.clone())
})?;
}
}
}
Ok(())
}
}
#[derive(Clone, Debug, Default)]
pub struct SimpleHardcoded {
pub port: Option<(u16, bool)>,
pub realm: Option<(RealmInfo, bool)>,
pub app_name: Option<(String, bool)>,
pub encryption_enabled: Option<(bool, bool)>,
pub node_node_type: Option<(String, bool)>,
}
impl SimpleHardcoded {
pub fn new() -> Self {
Self::default()
}
pub fn port(mut self, port: u16, overridable: bool) -> Self {
self.port = Some((port, overridable));
self
}
pub fn realm(mut self, realm: RealmInfo, overridable: bool) -> Self {
self.realm = Some((realm, overridable));
self
}
pub fn app_name<T: Into<String>>(mut self, name: T, overridable: bool) -> Self {
self.app_name = Some((name.into(), overridable));
self
}
pub fn encryption_enabled(mut self, enabled: bool, overridable: bool) -> Self {
self.encryption_enabled = Some((enabled, overridable));
self
}
pub fn node_type<T: Into<String>>(mut self, t: T, overridable: bool) -> Self {
self.node_node_type = Some((t.into(), overridable));
self
}
pub fn apply(&self, cfg: &mut Config) -> anyhow::Result<()> {
let mut defaults = ConfigDefaults::default();
if let Some((p, true)) = self.port {
defaults.port = Some(p);
}
if let Some((ref r, true)) = self.realm {
defaults.realm = Some(r.clone());
}
if let Some((ref n, true)) = self.app_name {
defaults.app_name = Some(n.clone());
}
if let Some((enabled, true)) = self.encryption_enabled {
let mut enc = cfg.encryption.clone().unwrap_or_default();
enc.enabled = enabled;
defaults.encryption = Some(enc);
}
if let Some((ref t, true)) = self.node_node_type {
let mut node = cfg.node.clone().unwrap_or_default();
node.node_type = Some(t.clone());
defaults.node = Some(node);
}
defaults.apply(cfg);
let mut locks = ConfigLocks::default();
if let Some((p, false)) = self.port {
locks.port = Some(p);
}
if let Some((ref r, false)) = self.realm {
locks.realm = Some(r.clone());
}
if let Some((ref n, false)) = self.app_name {
locks.app_name = Some(n.clone());
}
if let Some((enabled, false)) = self.encryption_enabled {
locks.encryption_enabled = Some(enabled);
}
if let Some((ref t, false)) = self.node_node_type {
locks.node = Some(NodeLocks {
node_type: Some(t.clone()),
});
}
locks.enforce(cfg, LockConflictPolicy::WarnAndOverride)
}
}