commit 299f6a9e4b98ebfe20e1c6d3a99d099ffb70a8ba
parent dd68ec93b38e0acbfa8f0a33afa94d5e0fe8d4aa
Author: triesap <tyson@radroots.org>
Date: Thu, 9 Apr 2026 03:55:32 +0000
rhi: extract runtime paths module
Diffstat:
6 files changed, 174 insertions(+), 165 deletions(-)
diff --git a/src/config.rs b/src/config.rs
@@ -1,21 +1,12 @@
-use anyhow::{Context, Result, bail};
+use anyhow::{Context, Result};
use radroots_nostr::prelude::RadrootsNostrMetadata;
use radroots_runtime::{BackoffConfig, 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 RHI_RUNTIME_ID: &str = "rhi";
-const SUBSCRIBER_STATE_DIR_NAME: &str = "trade-listing";
-const SUBSCRIBER_STATE_FILE_NAME: &str = "state.json";
-const RHI_PATHS_PROFILE_ENV: &str = "RHI_PATHS_PROFILE";
-const RHI_PATHS_REPO_LOCAL_ROOT_ENV: &str = "RHI_PATHS_REPO_LOCAL_ROOT";
-const RHI_DEFAULT_SHARED_SECRET_BACKEND: &str = "encrypted_file";
-const RHI_ALLOWED_PROFILES: [&str; 3] = ["interactive_user", "service_host", "repo_local"];
-const RHI_ALLOWED_SHARED_SECRET_BACKENDS: [&str; 1] = ["encrypted_file"];
+use crate::paths::{
+ RhiRuntimePaths, default_subscriber_state_path_for_process, resolve_runtime_paths_with_resolver,
+};
fn default_replay_window_secs() -> u64 {
24 * 60 * 60
@@ -25,26 +16,6 @@ fn default_replay_overlap_secs() -> u64 {
5 * 60
}
-#[derive(Debug, Clone, PartialEq, Eq)]
-struct RhiRuntimePaths {
- config_path: PathBuf,
- logs_dir: PathBuf,
- identity_path: PathBuf,
- subscriber_state_path: PathBuf,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
-pub struct RhiRuntimeContractOutput {
- 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_subscriber_state_path: PathBuf,
-}
-
#[derive(Debug, Deserialize, Clone, Default)]
struct RawServiceConfig {
#[serde(default)]
@@ -185,125 +156,10 @@ pub struct Settings {
pub config: Configuration,
}
-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!(
- "{RHI_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(RHI_PATHS_PROFILE_ENV) {
- Ok(value) => parse_path_profile(&value)?,
- Err(std::env::VarError::NotPresent) => RadrootsPathProfile::InteractiveUser,
- Err(std::env::VarError::NotUnicode(_)) => {
- bail!("{RHI_PATHS_PROFILE_ENV} must be valid utf-8 when set")
- }
- };
- let repo_local_root = std::env::var_os(RHI_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!(
- "{RHI_PATHS_REPO_LOCAL_ROOT_ENV} must be set when {RHI_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<RhiRuntimePaths> {
- let namespace = RadrootsRuntimeNamespace::worker(RHI_RUNTIME_ID)
- .map_err(|error| anyhow::anyhow!("resolve rhi namespace: {error}"))?;
- let overrides = path_overrides_for(profile, repo_local_root)?;
- let namespaced = resolver
- .resolve(profile, &overrides)
- .map_err(|error| anyhow::anyhow!("resolve rhi runtime paths: {error}"))?
- .namespaced(&namespace);
- Ok(RhiRuntimePaths {
- config_path: namespaced.config.join(DEFAULT_CONFIG_FILE_NAME),
- logs_dir: namespaced.logs,
- identity_path: namespaced.secrets.join(DEFAULT_SERVICE_IDENTITY_FILE_NAME),
- subscriber_state_path: namespaced
- .data
- .join(SUBSCRIBER_STATE_DIR_NAME)
- .join(SUBSCRIBER_STATE_FILE_NAME),
- })
-}
-
-fn default_runtime_paths_for_process() -> Result<RhiRuntimePaths> {
- 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 default_subscriber_state_path_for_process() -> Result<PathBuf> {
- Ok(default_runtime_paths_for_process()?.subscriber_state_path)
-}
-
-pub fn runtime_contract_for_process() -> Result<RhiRuntimeContractOutput> {
- 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<RhiRuntimeContractOutput> {
- let paths = resolve_runtime_paths_with_resolver(resolver, profile, repo_local_root)?;
- Ok(RhiRuntimeContractOutput {
- active_profile: profile.to_string(),
- allowed_profiles: RHI_ALLOWED_PROFILES
- .into_iter()
- .map(str::to_owned)
- .collect(),
- default_shared_secret_backend: RHI_DEFAULT_SHARED_SECRET_BACKEND.to_owned(),
- allowed_shared_secret_backends: RHI_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_subscriber_state_path: paths.subscriber_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 paths = resolve_runtime_paths_with_resolver(resolver, profile, repo_local_root)?;
@@ -315,10 +171,10 @@ fn load_settings_from_path_with_resolver(
}
pub fn load_settings_from_path(path: &Path) -> Result<Settings> {
- let (profile, repo_local_root) = process_path_selection()?;
+ let (profile, repo_local_root) = crate::paths::process_path_selection()?;
load_settings_from_path_with_resolver(
path,
- &RadrootsPathResolver::current(),
+ &radroots_runtime_paths::RadrootsPathResolver::current(),
profile,
repo_local_root.as_deref(),
)
@@ -326,9 +182,10 @@ pub fn load_settings_from_path(path: &Path) -> Result<Settings> {
#[cfg(test)]
mod tests {
- use super::{
- default_subscriber_state_path_for_process, load_settings_from_path_with_resolver,
- resolve_runtime_paths_with_resolver, runtime_contract_with_resolver,
+ use super::load_settings_from_path_with_resolver;
+ use crate::paths::{
+ default_subscriber_state_path_for_process, resolve_runtime_paths_with_resolver,
+ runtime_contract_with_resolver,
};
use radroots_runtime_paths::{
RadrootsHostEnvironment, RadrootsPathOverrides, RadrootsPathProfile, RadrootsPathResolver,
diff --git a/src/features/trade_listing/state.rs b/src/features/trade_listing/state.rs
@@ -83,7 +83,7 @@ struct PersistedTradeListingState {
impl Default for TradeListingRuntimeConfig {
fn default() -> Self {
Self {
- state_path: crate::config::default_subscriber_state_path_for_process()
+ state_path: crate::paths::default_subscriber_state_path_for_process()
.expect("resolve canonical rhi trade-listing state path"),
replay_window_secs: 24 * 60 * 60,
replay_overlap_secs: 5 * 60,
diff --git a/src/identity_storage.rs b/src/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::config::default_identity_path_for_process()
+ crate::paths::default_identity_path_for_process()
.expect("resolve canonical rhi identity path")
})
}
diff --git a/src/lib.rs b/src/lib.rs
@@ -5,6 +5,7 @@ pub mod cli;
pub mod config;
pub mod features;
pub mod identity_storage;
+pub mod paths;
pub mod rhi;
pub use cli::Args as cli_args;
diff --git a/src/main.rs b/src/main.rs
@@ -5,7 +5,7 @@ use anyhow::Context;
use anyhow::Result;
#[cfg(not(test))]
use clap::Parser;
-use rhi::{cli_args, config, run_rhi};
+use rhi::{cli_args, config, paths, run_rhi};
use std::path::PathBuf;
use std::process::ExitCode;
use tracing::info;
@@ -78,7 +78,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(
@@ -92,7 +92,7 @@ fn load_args_and_settings() -> Result<(cli_args, config::Settings)> {
fn runtime_startup_report(
args: &cli_args,
settings: &config::Settings,
- contract: &config::RhiRuntimeContractOutput,
+ contract: &paths::RhiRuntimeContractOutput,
) -> RhiRuntimeStartupReport {
RhiRuntimeStartupReport {
active_profile: contract.active_profile.clone(),
@@ -140,8 +140,7 @@ 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);
}
@@ -160,7 +159,7 @@ mod tests {
};
use radroots_nostr::prelude::{RadrootsNostrClient, RadrootsNostrKeys};
use rhi::features::trade_listing::state::TradeListingRuntime;
- use rhi::{cli_args, config};
+ use rhi::{cli_args, config, paths};
use std::path::PathBuf;
use std::process::ExitCode;
@@ -182,8 +181,8 @@ mod tests {
}
}
- fn sample_runtime_contract() -> config::RhiRuntimeContractOutput {
- config::RhiRuntimeContractOutput {
+ fn sample_runtime_contract() -> paths::RhiRuntimeContractOutput {
+ paths::RhiRuntimeContractOutput {
active_profile: "interactive_user".to_string(),
allowed_profiles: vec![
"interactive_user".to_string(),
diff --git a/src/paths.rs b/src/paths.rs
@@ -0,0 +1,152 @@
+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 RHI_RUNTIME_ID: &str = "rhi";
+const SUBSCRIBER_STATE_DIR_NAME: &str = "trade-listing";
+const SUBSCRIBER_STATE_FILE_NAME: &str = "state.json";
+const RHI_PATHS_PROFILE_ENV: &str = "RHI_PATHS_PROFILE";
+const RHI_PATHS_REPO_LOCAL_ROOT_ENV: &str = "RHI_PATHS_REPO_LOCAL_ROOT";
+const RHI_DEFAULT_SHARED_SECRET_BACKEND: &str = "encrypted_file";
+const RHI_ALLOWED_PROFILES: [&str; 3] = ["interactive_user", "service_host", "repo_local"];
+const RHI_ALLOWED_SHARED_SECRET_BACKENDS: [&str; 1] = ["encrypted_file"];
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub(crate) struct RhiRuntimePaths {
+ pub(crate) config_path: PathBuf,
+ pub(crate) logs_dir: PathBuf,
+ pub(crate) identity_path: PathBuf,
+ pub(crate) subscriber_state_path: PathBuf,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
+pub struct RhiRuntimeContractOutput {
+ 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_subscriber_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!(
+ "{RHI_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(RHI_PATHS_PROFILE_ENV) {
+ Ok(value) => parse_path_profile(&value)?,
+ Err(std::env::VarError::NotPresent) => RadrootsPathProfile::InteractiveUser,
+ Err(std::env::VarError::NotUnicode(_)) => {
+ bail!("{RHI_PATHS_PROFILE_ENV} must be valid utf-8 when set")
+ }
+ };
+ let repo_local_root = std::env::var_os(RHI_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!(
+ "{RHI_PATHS_REPO_LOCAL_ROOT_ENV} must be set when {RHI_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<RhiRuntimePaths> {
+ let namespace = RadrootsRuntimeNamespace::worker(RHI_RUNTIME_ID)
+ .map_err(|error| anyhow::anyhow!("resolve rhi namespace: {error}"))?;
+ let overrides = path_overrides_for(profile, repo_local_root)?;
+ let namespaced = resolver
+ .resolve(profile, &overrides)
+ .map_err(|error| anyhow::anyhow!("resolve rhi runtime paths: {error}"))?
+ .namespaced(&namespace);
+ Ok(RhiRuntimePaths {
+ config_path: namespaced.config.join(DEFAULT_CONFIG_FILE_NAME),
+ logs_dir: namespaced.logs,
+ identity_path: namespaced.secrets.join(DEFAULT_SERVICE_IDENTITY_FILE_NAME),
+ subscriber_state_path: namespaced
+ .data
+ .join(SUBSCRIBER_STATE_DIR_NAME)
+ .join(SUBSCRIBER_STATE_FILE_NAME),
+ })
+}
+
+pub(crate) fn default_runtime_paths_for_process() -> Result<RhiRuntimePaths> {
+ 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 default_subscriber_state_path_for_process() -> Result<PathBuf> {
+ Ok(default_runtime_paths_for_process()?.subscriber_state_path)
+}
+
+pub fn runtime_contract_for_process() -> Result<RhiRuntimeContractOutput> {
+ 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<RhiRuntimeContractOutput> {
+ let paths = resolve_runtime_paths_with_resolver(resolver, profile, repo_local_root)?;
+ Ok(RhiRuntimeContractOutput {
+ active_profile: profile.to_string(),
+ allowed_profiles: RHI_ALLOWED_PROFILES
+ .into_iter()
+ .map(str::to_owned)
+ .collect(),
+ default_shared_secret_backend: RHI_DEFAULT_SHARED_SECRET_BACKEND.to_owned(),
+ allowed_shared_secret_backends: RHI_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_subscriber_state_path: paths.subscriber_state_path,
+ })
+}