radrootsd

JSON-RPC bridge for Radroots event publishing
git clone https://radroots.dev/git/radrootsd.git
Log | Files | Refs | README | LICENSE

commit 8e8827a3eead103d1a9a7949c8dc3f24dce2bcb2
parent 31c079e490cbd3b0e4f0408ab4226518d20d5398
Author: triesap <tyson@radroots.org>
Date:   Thu,  9 Apr 2026 03:55:32 +0000

radrootsd: extract runtime paths module

Diffstat:
Msrc/app/config.rs | 169+++++++-------------------------------------------------------------------------
Msrc/app/identity_storage.rs | 2+-
Msrc/app/mod.rs | 1+
Asrc/app/paths.rs | 157+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/app/runtime.rs | 15+++++++--------
5 files changed, 179 insertions(+), 165 deletions(-)

diff --git a/src/app/config.rs b/src/app/config.rs @@ -1,21 +1,13 @@ use anyhow::{Context, Result, bail}; use radroots_nostr::prelude::RadrootsNostrMetadata; use radroots_runtime::RadrootsNostrServiceConfig; -use radroots_runtime_paths::{ - DEFAULT_CONFIG_FILE_NAME, DEFAULT_SERVICE_IDENTITY_FILE_NAME, RadrootsPathOverrides, - RadrootsPathProfile, RadrootsPathResolver, RadrootsRuntimeNamespace, -}; use serde::{Deserialize, Serialize}; use std::path::{Path, PathBuf}; -const RADROOTSD_RUNTIME_ID: &str = "radrootsd"; -const BRIDGE_STATE_DIR_NAME: &str = "bridge"; -const BRIDGE_STATE_FILE_NAME: &str = "bridge-jobs.json"; -const RADROOTSD_PATHS_PROFILE_ENV: &str = "RADROOTSD_PATHS_PROFILE"; -const RADROOTSD_PATHS_REPO_LOCAL_ROOT_ENV: &str = "RADROOTSD_PATHS_REPO_LOCAL_ROOT"; -const RADROOTSD_DEFAULT_SHARED_SECRET_BACKEND: &str = "encrypted_file"; -const RADROOTSD_ALLOWED_PROFILES: [&str; 3] = ["interactive_user", "service_host", "repo_local"]; -const RADROOTSD_ALLOWED_SHARED_SECRET_BACKENDS: [&str; 1] = ["encrypted_file"]; +use super::paths::{ + RadrootsdRuntimePaths, default_bridge_state_path, process_path_selection, + resolve_runtime_paths_with_resolver, +}; fn default_rpc_addr() -> String { "127.0.0.1:7070".to_string() @@ -81,32 +73,6 @@ fn default_bridge_job_status_retention() -> usize { 256 } -fn default_bridge_state_path() -> PathBuf { - default_runtime_paths_for_process() - .expect("resolve canonical radrootsd runtime paths") - .bridge_state_path -} - -#[derive(Debug, Clone, PartialEq, Eq)] -struct RadrootsdRuntimePaths { - config_path: PathBuf, - logs_dir: PathBuf, - identity_path: PathBuf, - bridge_state_path: PathBuf, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize)] -pub struct RadrootsdRuntimeContractOutput { - pub active_profile: String, - pub allowed_profiles: Vec<String>, - pub default_shared_secret_backend: String, - pub allowed_shared_secret_backends: Vec<String>, - pub canonical_config_path: PathBuf, - pub canonical_logs_dir: PathBuf, - pub canonical_identity_path: PathBuf, - pub canonical_bridge_state_path: PathBuf, -} - #[derive(Debug, Deserialize, Clone, Default)] struct RawServiceConfig { #[serde(default)] @@ -227,121 +193,10 @@ impl RawSettings { } } -fn parse_path_profile(value: &str) -> Result<RadrootsPathProfile> { - match value { - "interactive_user" => Ok(RadrootsPathProfile::InteractiveUser), - "service_host" => Ok(RadrootsPathProfile::ServiceHost), - "repo_local" => Ok(RadrootsPathProfile::RepoLocal), - _ => bail!( - "{RADROOTSD_PATHS_PROFILE_ENV} must be `interactive_user`, `service_host`, or `repo_local`" - ), - } -} - -fn process_path_selection() -> Result<(RadrootsPathProfile, Option<PathBuf>)> { - let profile = match std::env::var(RADROOTSD_PATHS_PROFILE_ENV) { - Ok(value) => parse_path_profile(&value)?, - Err(std::env::VarError::NotPresent) => RadrootsPathProfile::InteractiveUser, - Err(std::env::VarError::NotUnicode(_)) => { - bail!("{RADROOTSD_PATHS_PROFILE_ENV} must be valid utf-8 when set") - } - }; - let repo_local_root = std::env::var_os(RADROOTSD_PATHS_REPO_LOCAL_ROOT_ENV).map(PathBuf::from); - Ok((profile, repo_local_root)) -} - -fn path_overrides_for( - profile: RadrootsPathProfile, - repo_local_root: Option<&Path>, -) -> Result<RadrootsPathOverrides> { - match profile { - RadrootsPathProfile::RepoLocal => { - let repo_local_root = repo_local_root.context(format!( - "{RADROOTSD_PATHS_REPO_LOCAL_ROOT_ENV} must be set when {RADROOTSD_PATHS_PROFILE_ENV}=repo_local" - ))?; - Ok(RadrootsPathOverrides::repo_local(repo_local_root)) - } - _ => Ok(RadrootsPathOverrides::default()), - } -} - -fn resolve_runtime_paths_with_resolver( - resolver: &RadrootsPathResolver, - profile: RadrootsPathProfile, - repo_local_root: Option<&Path>, -) -> Result<RadrootsdRuntimePaths> { - let namespace = RadrootsRuntimeNamespace::service(RADROOTSD_RUNTIME_ID) - .map_err(|error| anyhow::anyhow!("resolve radrootsd namespace: {error}"))?; - let overrides = path_overrides_for(profile, repo_local_root)?; - let namespaced = resolver - .resolve(profile, &overrides) - .map_err(|error| anyhow::anyhow!("resolve radrootsd runtime paths: {error}"))? - .namespaced(&namespace); - Ok(RadrootsdRuntimePaths { - config_path: namespaced.config.join(DEFAULT_CONFIG_FILE_NAME), - logs_dir: namespaced.logs, - identity_path: namespaced.secrets.join(DEFAULT_SERVICE_IDENTITY_FILE_NAME), - bridge_state_path: namespaced - .data - .join(BRIDGE_STATE_DIR_NAME) - .join(BRIDGE_STATE_FILE_NAME), - }) -} - -fn default_runtime_paths_for_process() -> Result<RadrootsdRuntimePaths> { - let (profile, repo_local_root) = process_path_selection()?; - resolve_runtime_paths_with_resolver( - &RadrootsPathResolver::current(), - profile, - repo_local_root.as_deref(), - ) -} - -pub fn default_config_path_for_process() -> Result<PathBuf> { - Ok(default_runtime_paths_for_process()?.config_path) -} - -pub fn default_identity_path_for_process() -> Result<PathBuf> { - Ok(default_runtime_paths_for_process()?.identity_path) -} - -pub fn runtime_contract_for_process() -> Result<RadrootsdRuntimeContractOutput> { - let (profile, repo_local_root) = process_path_selection()?; - runtime_contract_with_resolver( - &RadrootsPathResolver::current(), - profile, - repo_local_root.as_deref(), - ) -} - -fn runtime_contract_with_resolver( - resolver: &RadrootsPathResolver, - profile: RadrootsPathProfile, - repo_local_root: Option<&Path>, -) -> Result<RadrootsdRuntimeContractOutput> { - let paths = resolve_runtime_paths_with_resolver(resolver, profile, repo_local_root)?; - Ok(RadrootsdRuntimeContractOutput { - active_profile: profile.to_string(), - allowed_profiles: RADROOTSD_ALLOWED_PROFILES - .into_iter() - .map(str::to_owned) - .collect(), - default_shared_secret_backend: RADROOTSD_DEFAULT_SHARED_SECRET_BACKEND.to_owned(), - allowed_shared_secret_backends: RADROOTSD_ALLOWED_SHARED_SECRET_BACKENDS - .into_iter() - .map(str::to_owned) - .collect(), - canonical_config_path: paths.config_path, - canonical_logs_dir: paths.logs_dir, - canonical_identity_path: paths.identity_path, - canonical_bridge_state_path: paths.bridge_state_path, - }) -} - fn load_settings_from_path_with_resolver( path: &Path, - resolver: &RadrootsPathResolver, - profile: RadrootsPathProfile, + resolver: &radroots_runtime_paths::RadrootsPathResolver, + profile: radroots_runtime_paths::RadrootsPathProfile, repo_local_root: Option<&Path>, ) -> Result<Settings> { let raw: RawSettings = radroots_runtime::load_required_file(path) @@ -357,7 +212,7 @@ pub fn load_settings_from_path(path: impl AsRef<Path>) -> Result<Settings> { let (profile, repo_local_root) = process_path_selection()?; load_settings_from_path_with_resolver( path, - &RadrootsPathResolver::current(), + &radroots_runtime_paths::RadrootsPathResolver::current(), profile, repo_local_root.as_deref(), ) @@ -534,10 +389,13 @@ impl Settings { mod tests { use std::path::PathBuf; + use crate::app::paths::{ + default_runtime_paths_for_process, resolve_runtime_paths_with_resolver, + runtime_contract_with_resolver, + }; use super::{ BridgeConfig, BridgeDeliveryPolicy, Configuration, Nip46Config, RpcConfig, - load_settings_from_path_with_resolver, resolve_runtime_paths_with_resolver, - runtime_contract_with_resolver, + load_settings_from_path_with_resolver, }; use radroots_runtime::RadrootsNostrServiceConfig; use radroots_runtime_paths::{ @@ -592,8 +450,7 @@ mod tests { #[test] fn bridge_defaults_are_expected() { - let paths = - super::default_runtime_paths_for_process().expect("resolve process runtime paths"); + let paths = default_runtime_paths_for_process().expect("resolve process runtime paths"); let cfg = BridgeConfig::default(); assert!(!cfg.enabled); assert!(cfg.bearer_token.is_none()); diff --git a/src/app/identity_storage.rs b/src/app/identity_storage.rs @@ -41,7 +41,7 @@ pub fn load_encrypted_identity(path: impl AsRef<Path>) -> Result<RadrootsIdentit fn resolved_identity_path(path: Option<&Path>) -> PathBuf { path.map(Path::to_path_buf).unwrap_or_else(|| { - crate::app::config::default_identity_path_for_process() + crate::app::paths::default_identity_path_for_process() .expect("resolve canonical radrootsd identity path") }) } diff --git a/src/app/mod.rs b/src/app/mod.rs @@ -1,6 +1,7 @@ pub mod cli; pub mod config; mod identity_storage; +mod paths; mod runtime; pub use cli::Args; diff --git a/src/app/paths.rs b/src/app/paths.rs @@ -0,0 +1,157 @@ +use std::path::{Path, PathBuf}; + +use anyhow::{Context, Result, bail}; +use radroots_runtime_paths::{ + DEFAULT_CONFIG_FILE_NAME, DEFAULT_SERVICE_IDENTITY_FILE_NAME, RadrootsPathOverrides, + RadrootsPathProfile, RadrootsPathResolver, RadrootsRuntimeNamespace, +}; +use serde::Serialize; + +const RADROOTSD_RUNTIME_ID: &str = "radrootsd"; +const BRIDGE_STATE_DIR_NAME: &str = "bridge"; +const BRIDGE_STATE_FILE_NAME: &str = "bridge-jobs.json"; +const RADROOTSD_PATHS_PROFILE_ENV: &str = "RADROOTSD_PATHS_PROFILE"; +const RADROOTSD_PATHS_REPO_LOCAL_ROOT_ENV: &str = "RADROOTSD_PATHS_REPO_LOCAL_ROOT"; +const RADROOTSD_DEFAULT_SHARED_SECRET_BACKEND: &str = "encrypted_file"; +const RADROOTSD_ALLOWED_PROFILES: [&str; 3] = ["interactive_user", "service_host", "repo_local"]; +const RADROOTSD_ALLOWED_SHARED_SECRET_BACKENDS: [&str; 1] = ["encrypted_file"]; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct RadrootsdRuntimePaths { + pub(crate) config_path: PathBuf, + pub(crate) logs_dir: PathBuf, + pub(crate) identity_path: PathBuf, + pub(crate) bridge_state_path: PathBuf, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub struct RadrootsdRuntimeContractOutput { + pub active_profile: String, + pub allowed_profiles: Vec<String>, + pub default_shared_secret_backend: String, + pub allowed_shared_secret_backends: Vec<String>, + pub canonical_config_path: PathBuf, + pub canonical_logs_dir: PathBuf, + pub canonical_identity_path: PathBuf, + pub canonical_bridge_state_path: PathBuf, +} + +fn parse_path_profile(value: &str) -> Result<RadrootsPathProfile> { + match value { + "interactive_user" => Ok(RadrootsPathProfile::InteractiveUser), + "service_host" => Ok(RadrootsPathProfile::ServiceHost), + "repo_local" => Ok(RadrootsPathProfile::RepoLocal), + _ => bail!( + "{RADROOTSD_PATHS_PROFILE_ENV} must be `interactive_user`, `service_host`, or `repo_local`" + ), + } +} + +pub(crate) fn process_path_selection() -> Result<(RadrootsPathProfile, Option<PathBuf>)> { + let profile = match std::env::var(RADROOTSD_PATHS_PROFILE_ENV) { + Ok(value) => parse_path_profile(&value)?, + Err(std::env::VarError::NotPresent) => RadrootsPathProfile::InteractiveUser, + Err(std::env::VarError::NotUnicode(_)) => { + bail!("{RADROOTSD_PATHS_PROFILE_ENV} must be valid utf-8 when set") + } + }; + let repo_local_root = + std::env::var_os(RADROOTSD_PATHS_REPO_LOCAL_ROOT_ENV).map(PathBuf::from); + Ok((profile, repo_local_root)) +} + +fn path_overrides_for( + profile: RadrootsPathProfile, + repo_local_root: Option<&Path>, +) -> Result<RadrootsPathOverrides> { + match profile { + RadrootsPathProfile::RepoLocal => { + let repo_local_root = repo_local_root.context(format!( + "{RADROOTSD_PATHS_REPO_LOCAL_ROOT_ENV} must be set when {RADROOTSD_PATHS_PROFILE_ENV}=repo_local" + ))?; + Ok(RadrootsPathOverrides::repo_local(repo_local_root)) + } + _ => Ok(RadrootsPathOverrides::default()), + } +} + +pub(crate) fn resolve_runtime_paths_with_resolver( + resolver: &RadrootsPathResolver, + profile: RadrootsPathProfile, + repo_local_root: Option<&Path>, +) -> Result<RadrootsdRuntimePaths> { + let namespace = RadrootsRuntimeNamespace::service(RADROOTSD_RUNTIME_ID) + .map_err(|error| anyhow::anyhow!("resolve radrootsd namespace: {error}"))?; + let overrides = path_overrides_for(profile, repo_local_root)?; + let namespaced = resolver + .resolve(profile, &overrides) + .map_err(|error| anyhow::anyhow!("resolve radrootsd runtime paths: {error}"))? + .namespaced(&namespace); + Ok(RadrootsdRuntimePaths { + config_path: namespaced.config.join(DEFAULT_CONFIG_FILE_NAME), + logs_dir: namespaced.logs, + identity_path: namespaced.secrets.join(DEFAULT_SERVICE_IDENTITY_FILE_NAME), + bridge_state_path: namespaced + .data + .join(BRIDGE_STATE_DIR_NAME) + .join(BRIDGE_STATE_FILE_NAME), + }) +} + +pub(crate) fn default_runtime_paths_for_process() -> Result<RadrootsdRuntimePaths> { + let (profile, repo_local_root) = process_path_selection()?; + resolve_runtime_paths_with_resolver( + &RadrootsPathResolver::current(), + profile, + repo_local_root.as_deref(), + ) +} + +pub(crate) fn default_bridge_state_path() -> PathBuf { + default_runtime_paths_for_process() + .expect("resolve canonical radrootsd runtime paths") + .bridge_state_path +} + +#[cfg_attr(test, allow(dead_code))] +pub fn default_config_path_for_process() -> Result<PathBuf> { + Ok(default_runtime_paths_for_process()?.config_path) +} + +pub fn default_identity_path_for_process() -> Result<PathBuf> { + Ok(default_runtime_paths_for_process()?.identity_path) +} + +#[cfg_attr(test, allow(dead_code))] +pub fn runtime_contract_for_process() -> Result<RadrootsdRuntimeContractOutput> { + let (profile, repo_local_root) = process_path_selection()?; + runtime_contract_with_resolver( + &RadrootsPathResolver::current(), + profile, + repo_local_root.as_deref(), + ) +} + +pub(crate) fn runtime_contract_with_resolver( + resolver: &RadrootsPathResolver, + profile: RadrootsPathProfile, + repo_local_root: Option<&Path>, +) -> Result<RadrootsdRuntimeContractOutput> { + let paths = resolve_runtime_paths_with_resolver(resolver, profile, repo_local_root)?; + Ok(RadrootsdRuntimeContractOutput { + active_profile: profile.to_string(), + allowed_profiles: RADROOTSD_ALLOWED_PROFILES + .into_iter() + .map(str::to_owned) + .collect(), + default_shared_secret_backend: RADROOTSD_DEFAULT_SHARED_SECRET_BACKEND.to_owned(), + allowed_shared_secret_backends: RADROOTSD_ALLOWED_SHARED_SECRET_BACKENDS + .into_iter() + .map(str::to_owned) + .collect(), + canonical_config_path: paths.config_path, + canonical_logs_dir: paths.logs_dir, + canonical_identity_path: paths.identity_path, + canonical_bridge_state_path: paths.bridge_state_path, + }) +} diff --git a/src/app/runtime.rs b/src/app/runtime.rs @@ -5,7 +5,7 @@ use std::time::Duration; use tracing::{info, warn}; use crate::app::identity_storage::load_service_identity; -use crate::app::{cli, config}; +use crate::app::{cli, config, paths}; use crate::core::Radrootsd; use crate::transport::jsonrpc; #[cfg(not(test))] @@ -136,7 +136,7 @@ fn load_args_and_settings() -> Result<(cli::Args, config::Settings)> { .config .clone() .map(Ok) - .unwrap_or_else(config::default_config_path_for_process)?; + .unwrap_or_else(paths::default_config_path_for_process)?; let settings = config::load_settings_from_path(&config_path).context("load configuration")?; radroots_runtime::init_with_logs_dir( @@ -150,7 +150,7 @@ fn load_args_and_settings() -> Result<(cli::Args, config::Settings)> { fn runtime_startup_report( args: &cli::Args, settings: &config::Settings, - contract: &config::RadrootsdRuntimeContractOutput, + contract: &paths::RadrootsdRuntimeContractOutput, ) -> RadrootsdRuntimeStartupReport { RadrootsdRuntimeStartupReport { active_profile: contract.active_profile.clone(), @@ -341,8 +341,7 @@ pub async fn run() -> Result<()> { #[cfg(not(test))] { - let contract = - config::runtime_contract_for_process().context("resolve runtime contract")?; + let contract = paths::runtime_contract_for_process().context("resolve runtime contract")?; let report = runtime_startup_report(&args, &settings, &contract); log_runtime_startup_report(&report); } @@ -413,7 +412,7 @@ mod tests { RadrootsdRuntimeStartupReport, RunWaitOutcome, run, run_bootstrap_hook, run_load_hook, run_start_rpc_hook, run_wait_hook, runtime_startup_report, }; - use crate::app::{cli, config}; + use crate::app::{cli, config, paths}; use crate::core::Radrootsd; use crate::transport::jsonrpc; use radroots_events::kinds::KIND_LISTING; @@ -492,8 +491,8 @@ mod tests { } } - fn sample_runtime_contract() -> config::RadrootsdRuntimeContractOutput { - config::RadrootsdRuntimeContractOutput { + fn sample_runtime_contract() -> paths::RadrootsdRuntimeContractOutput { + paths::RadrootsdRuntimeContractOutput { active_profile: "interactive_user".to_string(), allowed_profiles: vec![ "interactive_user".to_string(),