lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README | LICENSE

commit f8ad4cf3e380cdffba39b5e8f3bf59fa850578b3
parent ecef6d2c169ce679a243db2f6f1be2185cf9a509
Author: triesap <tyson@radroots.org>
Date:   Wed,  8 Apr 2026 15:54:34 +0000

runtime: tighten shared bootstrap helpers

Diffstat:
Mcrates/net-core/src/error.rs | 4++++
Mcrates/net-core/src/keys.rs | 120+++++++++++++++++++++++++++-----------------------------------------------------
Mcrates/runtime/src/cli.rs | 4++--
Mcrates/runtime/src/lib.rs | 7++-----
Mcrates/runtime/src/service.rs | 26+-------------------------
Mcrates/runtime/src/tracing.rs | 56+++++++++++---------------------------------------------
6 files changed, 60 insertions(+), 157 deletions(-)

diff --git a/crates/net-core/src/error.rs b/crates/net-core/src/error.rs @@ -31,6 +31,9 @@ pub enum NetError { #[error("overwrite denied")] OverwriteDenied, + #[error("persistence path required")] + PersistencePathRequired, + #[error("persistence unsupported")] PersistenceUnsupported, @@ -59,6 +62,7 @@ impl Clone for NetError { NetError::InvalidKeyFile => NetError::InvalidKeyFile, NetError::KeyIo => NetError::KeyIo, NetError::OverwriteDenied => NetError::OverwriteDenied, + NetError::PersistencePathRequired => NetError::PersistencePathRequired, NetError::PersistenceUnsupported => NetError::PersistenceUnsupported, NetError::LoggingInit(s) => NetError::LoggingInit(s), } diff --git a/crates/net-core/src/keys.rs b/crates/net-core/src/keys.rs @@ -7,10 +7,6 @@ use radroots_nostr::prelude::{ RadrootsNostrKeys, RadrootsNostrSecp256k1SecretKey, RadrootsNostrSecretKey, RadrootsNostrToBech32, }; -#[cfg(all(feature = "nostr-client", feature = "fs-persistence"))] -use radroots_runtime_paths::{ - RadrootsPathOverrides, RadrootsPathProfile, RadrootsPathResolver, default_shared_identity_path, -}; #[cfg(feature = "nostr-client")] use serde::Deserialize; #[cfg(feature = "nostr-client")] @@ -267,43 +263,8 @@ impl KeysManager { } #[cfg(feature = "fs-persistence")] - pub fn default_key_path() -> Option<PathBuf> { - default_key_path_for( - &RadrootsPathResolver::current(), - RadrootsPathProfile::InteractiveUser, - &RadrootsPathOverrides::default(), - ) - } - - #[cfg(feature = "fs-persistence")] - pub fn persist_best_practice(&self) -> Result<PathBuf> { - let path = Self::default_key_path().ok_or(NetError::PersistenceUnsupported)?; - if path.exists() { - return Err(NetError::OverwriteDenied); - } - self.save_to_path_with_format(&path, KeyFormat::Json, true)?; - Ok(path) - } - - #[cfg(not(feature = "fs-persistence"))] - pub fn persist_best_practice(&self) -> Result<PathBuf> { - Err(NetError::PersistenceUnsupported) - } - - #[cfg(feature = "fs-persistence")] pub fn persist_with_config(&self, cfg: &KeyPersistenceConfig) -> Result<PathBuf> { - let path = if let Some(p) = &cfg.path { - p.clone() - } else { - #[cfg(feature = "fs-persistence")] - { - Self::default_key_path().ok_or(NetError::PersistenceUnsupported)? - } - #[cfg(not(feature = "fs-persistence"))] - { - return Err(NetError::PersistenceUnsupported); - } - }; + let path = cfg.path.clone().ok_or(NetError::PersistencePathRequired)?; self.save_to_path_with_format(&path, cfg.format, cfg.no_overwrite)?; Ok(path) } @@ -314,15 +275,6 @@ impl KeysManager { } } -#[cfg(all(feature = "nostr-client", feature = "fs-persistence"))] -fn default_key_path_for( - resolver: &RadrootsPathResolver, - profile: RadrootsPathProfile, - overrides: &RadrootsPathOverrides, -) -> Option<PathBuf> { - default_shared_identity_path(resolver, profile, overrides).ok() -} - #[cfg(feature = "nostr-client")] fn write_secret_atomically_noclobber(path: &Path, data: &[u8]) -> crate::error::Result<()> { use std::io::Write; @@ -355,41 +307,49 @@ fn write_secret_atomically_noclobber(path: &Path, data: &[u8]) -> crate::error:: #[cfg(all(test, feature = "nostr-client", feature = "fs-persistence"))] mod tests { - use std::path::PathBuf; + use tempfile::tempdir; - use radroots_identity::RadrootsIdentity; - use radroots_runtime_paths::{ - RadrootsHostEnvironment, RadrootsPathOverrides, RadrootsPathProfile, RadrootsPathResolver, - RadrootsPlatform, + use crate::{ + config::{KeyFormat, KeyPersistenceConfig}, + error::NetError, }; - use super::default_key_path_for; + use super::KeysManager; + + #[test] + fn persist_with_config_requires_explicit_path() { + let mut manager = KeysManager::new(); + let _ = manager.generate_in_memory(); + let cfg = KeyPersistenceConfig { + path: None, + format: KeyFormat::Json, + no_overwrite: true, + }; + + let err = manager + .persist_with_config(&cfg) + .expect_err("implicit default persistence path should be rejected"); + + assert!(matches!(err, NetError::PersistencePathRequired)); + } #[test] - fn default_key_path_matches_identity_default_path() { - let resolver = RadrootsPathResolver::new( - RadrootsPlatform::Linux, - RadrootsHostEnvironment { - home_dir: Some(PathBuf::from("/home/treesap")), - ..RadrootsHostEnvironment::default() - }, - ); - let overrides = RadrootsPathOverrides::default(); - - let net_core_path = - default_key_path_for(&resolver, RadrootsPathProfile::InteractiveUser, &overrides) - .expect("net-core default key path should resolve"); - let identity_path = RadrootsIdentity::default_path_for( - &resolver, - RadrootsPathProfile::InteractiveUser, - &overrides, - ) - .expect("identity default path should resolve"); - - assert_eq!(net_core_path, identity_path); - assert_eq!( - net_core_path, - PathBuf::from("/home/treesap/.radroots/secrets/shared/identities/default.json") - ); + fn persist_with_config_writes_only_to_explicit_path() { + let dir = tempdir().expect("tempdir"); + let path = dir.path().join("keys.json"); + let mut manager = KeysManager::new(); + let _ = manager.generate_in_memory(); + let cfg = KeyPersistenceConfig { + path: Some(path.clone()), + format: KeyFormat::Json, + no_overwrite: true, + }; + + let written = manager + .persist_with_config(&cfg) + .expect("explicit persistence path should succeed"); + + assert_eq!(written, path); + assert!(written.exists()); } } diff --git a/crates/runtime/src/cli.rs b/crates/runtime/src/cli.rs @@ -52,7 +52,7 @@ where FL: Fn(&C) -> &str, { let (args, cfg) = parse_and_load_path::<Args, C, FP>(path_of)?; - crate::tracing::init_with(logs_dir_of(&cfg), default_level)?; + crate::tracing::init_with_logs_dir(Path::new(logs_dir_of(&cfg)), default_level)?; Ok((args, cfg)) } @@ -75,7 +75,7 @@ where env_prefix, overrides_of, )?; - crate::tracing::init_with(logs_dir_of(&cfg), default_level)?; + crate::tracing::init_with_logs_dir(Path::new(logs_dir_of(&cfg)), default_level)?; Ok((args, cfg)) } diff --git a/crates/runtime/src/lib.rs b/crates/runtime/src/lib.rs @@ -30,11 +30,8 @@ pub use secret_file::{local_wrapping_key_path, open_local_secret_file, seal_loca pub use service::RadrootsNostrServiceConfig; #[cfg(feature = "cli")] pub use service::RadrootsServiceCliArgs; -pub use service::{ - DEFAULT_SERVICE_IDENTITY_PATH, default_service_bootstrap_paths, default_service_config_path, - default_service_identity_path, default_service_logs_dir, service_bootstrap_paths_for, -}; +pub use service::{DEFAULT_SERVICE_IDENTITY_PATH, service_bootstrap_paths_for}; pub use signals::shutdown_signal; pub use tracing::{ - default_shared_runtime_logs_dir, default_shared_runtime_logs_dir_for, init, init_with, + default_shared_runtime_logs_dir, default_shared_runtime_logs_dir_for, init, init_with_logs_dir, }; diff --git a/crates/runtime/src/service.rs b/crates/runtime/src/service.rs @@ -1,4 +1,5 @@ use serde::{Deserialize, Serialize}; +#[cfg(any(feature = "cli", test))] use std::path::PathBuf; #[cfg(feature = "cli")] @@ -27,31 +28,6 @@ pub fn service_bootstrap_paths_for( ) } -pub fn default_service_bootstrap_paths( - runtime_id: &str, -) -> Result<RadrootsBootstrapPaths, RadrootsRuntimePathsError> { - service_bootstrap_paths_for( - &RadrootsPathResolver::current(), - RadrootsPathProfile::InteractiveUser, - &RadrootsPathOverrides::default(), - runtime_id, - ) -} - -pub fn default_service_config_path(runtime_id: &str) -> Result<PathBuf, RadrootsRuntimePathsError> { - Ok(default_service_bootstrap_paths(runtime_id)?.config_path) -} - -pub fn default_service_logs_dir(runtime_id: &str) -> Result<PathBuf, RadrootsRuntimePathsError> { - Ok(default_service_bootstrap_paths(runtime_id)?.logs_dir) -} - -pub fn default_service_identity_path( - runtime_id: &str, -) -> Result<PathBuf, RadrootsRuntimePathsError> { - Ok(default_service_bootstrap_paths(runtime_id)?.identity_path) -} - #[cfg(feature = "cli")] #[derive(Args, Debug, Clone)] pub struct RadrootsServiceCliArgs { diff --git a/crates/runtime/src/tracing.rs b/crates/runtime/src/tracing.rs @@ -9,7 +9,7 @@ use crate::error::RuntimeTracingError; pub fn init() -> Result<(), RuntimeTracingError> { let logs_dir = default_shared_runtime_logs_dir()?; - init_with(logs_dir, None) + init_with_logs_dir(logs_dir, None) } pub fn default_shared_runtime_logs_dir() -> Result<PathBuf, RadrootsRuntimePathsError> { @@ -28,17 +28,15 @@ pub fn default_shared_runtime_logs_dir_for( resolve_shared_runtime_logs_dir(resolver, profile, overrides) } -pub fn init_with( +pub fn init_with_logs_dir( logs_dir: impl AsRef<Path>, default_level: Option<&str>, ) -> Result<(), RuntimeTracingError> { let logs_dir = logs_dir.as_ref(); - let env_dir = env_path("LOG_DIR").or_else(|| env_path("RADROOTS_LOG_DIR")); let env_file = env_value("LOG_FILE").or_else(|| env_value("RADROOTS_LOG_FILE")); let env_level = env_value("LOG_LEVEL").or_else(|| env_value("RUST_LOG")); - let dir = resolve_log_dir(logs_dir, env_dir); let opts = LoggingOptions { - dir, + dir: Some(logs_dir.to_path_buf()), file_name: env_file.unwrap_or_else(default_log_file_name), stdout: true, default_level: resolve_default_level(env_level, default_level), @@ -100,10 +98,6 @@ fn env_value(key: &str) -> Option<String> { normalize_env_value(&value) } -fn env_path(key: &str) -> Option<PathBuf> { - env_value(key).map(PathBuf::from) -} - fn normalize_env_value(value: &str) -> Option<String> { let trimmed = value.trim(); if trimmed.is_empty() { @@ -113,16 +107,6 @@ fn normalize_env_value(value: &str) -> Option<String> { } } -fn resolve_log_dir(logs_dir: &Path, env_dir: Option<PathBuf>) -> Option<PathBuf> { - env_dir.or_else(|| { - if logs_dir.as_os_str().is_empty() { - None - } else { - Some(logs_dir.to_path_buf()) - } - }) -} - fn resolve_default_level(env_level: Option<String>, default_level: Option<&str>) -> Option<String> { if let Some(level) = env_level { Some(level) @@ -135,15 +119,14 @@ fn resolve_default_level(env_level: Option<String>, default_level: Option<&str>) mod tests { use super::{ default_log_file_name, default_log_file_name_from_exe_name, - default_shared_runtime_logs_dir_for, env_path, env_value, init_with, log_name_from_path, - log_name_from_stem, normalize_env_value, resolve_default_level, resolve_log_dir, - test_hooks, + default_shared_runtime_logs_dir_for, env_value, init_with_logs_dir, log_name_from_path, + log_name_from_stem, normalize_env_value, resolve_default_level, test_hooks, }; use radroots_runtime_paths::{ RadrootsHostEnvironment, RadrootsPathOverrides, RadrootsPathProfile, RadrootsPathResolver, RadrootsPlatform, }; - use std::path::{Path, PathBuf}; + use std::path::PathBuf; use tempfile::tempdir; #[test] @@ -158,8 +141,6 @@ mod tests { assert_eq!(env_value("RADROOTS_RUNTIME_TEST_MISSING_KEY"), None); let home = env_value("HOME").expect("home env"); assert!(!home.is_empty()); - let home_path = env_path("HOME").expect("home path"); - assert_eq!(home_path, PathBuf::from(home)); } #[test] @@ -195,19 +176,6 @@ mod tests { } #[test] - fn resolve_log_dir_prefers_env_and_handles_empty_logs_dir() { - let fallback = resolve_log_dir(Path::new("logs"), None); - assert_eq!(fallback, Some(PathBuf::from("logs"))); - - let empty = resolve_log_dir(Path::new(""), None); - assert_eq!(empty, None); - - let env_dir = PathBuf::from("env-logs"); - let resolved = resolve_log_dir(Path::new("logs"), Some(env_dir.clone())); - assert_eq!(resolved, Some(env_dir)); - } - - #[test] fn resolve_default_level_prefers_env_then_fallback() { let env_value = resolve_default_level(Some("warn".to_string()), Some("info")); assert_eq!(env_value, Some("warn".to_string())); @@ -246,18 +214,16 @@ mod tests { test_hooks::set_ignore_env(true); let invalid = dir.path().join("not-a-dir"); std::fs::write(&invalid, "file").expect("write invalid path"); - let err_path = init_with(invalid.as_path(), Some("info")); + let err_path = init_with_logs_dir(invalid.as_path(), Some("info")); assert!(err_path.is_err()); let invalid_str = invalid.to_string_lossy().to_string(); - let err_str = init_with(invalid_str.as_str(), Some("info")); + let err_str = init_with_logs_dir(invalid_str.as_str(), Some("info")); assert!(err_str.is_err()); - test_hooks::set_ignore_env(false); - let first = init_with(dir.path(), Some("info")); + let first = init_with_logs_dir(dir.path(), Some("info")); assert!(first.is_ok()); let owned_path = dir.path().to_path_buf(); - let third = init_with(owned_path.as_path(), None); + let third = init_with_logs_dir(owned_path.as_path(), None); assert!(third.is_ok()); - let fourth = init_with("logs", Some("debug")); - assert!(fourth.is_ok()); + test_hooks::set_ignore_env(false); } }