service.rs (4628B)
1 use serde::{Deserialize, Serialize}; 2 #[cfg(feature = "cli")] 3 use std::path::PathBuf; 4 5 #[cfg(feature = "cli")] 6 use clap::{ArgAction, Args, ValueHint}; 7 use radroots_runtime_paths::{ 8 DEFAULT_SERVICE_IDENTITY_FILE_NAME, RadrootsBootstrapPaths, RadrootsPathOverrides, 9 RadrootsPathProfile, RadrootsPathResolver, RadrootsRuntimeNamespace, RadrootsRuntimePathsError, 10 default_namespaced_bootstrap_paths, 11 }; 12 13 pub const DEFAULT_SERVICE_IDENTITY_PATH: &str = DEFAULT_SERVICE_IDENTITY_FILE_NAME; 14 15 pub fn service_bootstrap_paths_for( 16 resolver: &RadrootsPathResolver, 17 profile: RadrootsPathProfile, 18 overrides: &RadrootsPathOverrides, 19 runtime_id: &str, 20 ) -> Result<RadrootsBootstrapPaths, RadrootsRuntimePathsError> { 21 let namespace = RadrootsRuntimeNamespace::service(runtime_id)?; 22 default_namespaced_bootstrap_paths( 23 resolver, 24 profile, 25 overrides, 26 &namespace, 27 DEFAULT_SERVICE_IDENTITY_PATH, 28 ) 29 } 30 31 #[cfg(feature = "cli")] 32 #[derive(Args, Debug, Clone)] 33 pub struct RadrootsServiceCliArgs { 34 #[arg( 35 long, 36 value_name = "PATH", 37 value_hint = ValueHint::FilePath, 38 help = "Path to the daemon configuration file; no implicit cwd-rooted default is used" 39 )] 40 pub config: Option<PathBuf>, 41 42 #[arg( 43 long, 44 value_name = "PATH", 45 value_hint = ValueHint::FilePath, 46 help = "Path to the daemon encrypted identity envelope; callers may resolve a canonical namespaced default ending in identity.secret.json with a sibling .key wrapping key file" 47 )] 48 pub identity: Option<PathBuf>, 49 50 #[arg( 51 long, 52 action = ArgAction::SetTrue, 53 help = "Allow generating a new encrypted identity envelope when the configured path is missing; if not set and the identity is absent, the daemon will fail" 54 )] 55 pub allow_generate_identity: bool, 56 } 57 58 #[derive(Debug, Serialize, Deserialize, Clone)] 59 pub struct RadrootsNostrServiceConfig { 60 pub logs_dir: String, 61 #[serde(default)] 62 pub relays: Vec<String>, 63 #[serde(default)] 64 pub nip89_identifier: Option<String>, 65 #[serde(default)] 66 pub nip89_extra_tags: Vec<Vec<String>>, 67 } 68 69 #[cfg(test)] 70 mod tests { 71 use std::path::PathBuf; 72 73 use radroots_runtime_paths::{ 74 RadrootsHostEnvironment, RadrootsPathOverrides, RadrootsPathProfile, RadrootsPathResolver, 75 RadrootsPlatform, 76 }; 77 78 use super::{RadrootsNostrServiceConfig, service_bootstrap_paths_for}; 79 80 #[test] 81 fn service_config_defaults_optional_fields() { 82 let cfg: RadrootsNostrServiceConfig = toml::from_str( 83 r#" 84 logs_dir = "logs" 85 "#, 86 ) 87 .expect("service config should parse"); 88 89 assert_eq!(cfg.logs_dir, "logs"); 90 assert!(cfg.relays.is_empty()); 91 assert_eq!(cfg.nip89_identifier, None); 92 assert!(cfg.nip89_extra_tags.is_empty()); 93 } 94 95 #[test] 96 fn service_bootstrap_paths_follow_runtime_paths_contract() { 97 let resolver = RadrootsPathResolver::new( 98 RadrootsPlatform::Linux, 99 RadrootsHostEnvironment { 100 home_dir: Some(PathBuf::from("/home/treesap")), 101 ..RadrootsHostEnvironment::default() 102 }, 103 ); 104 105 let paths = service_bootstrap_paths_for( 106 &resolver, 107 RadrootsPathProfile::InteractiveUser, 108 &RadrootsPathOverrides::default(), 109 "radrootsd", 110 ) 111 .expect("service bootstrap paths should resolve"); 112 113 assert_eq!( 114 paths.config_path, 115 PathBuf::from("/home/treesap/.radroots/config/services/radrootsd/config.toml") 116 ); 117 assert_eq!( 118 paths.logs_dir, 119 PathBuf::from("/home/treesap/.radroots/logs/services/radrootsd") 120 ); 121 assert_eq!( 122 paths.identity_path, 123 PathBuf::from( 124 "/home/treesap/.radroots/secrets/services/radrootsd/identity.secret.json" 125 ) 126 ); 127 } 128 129 #[test] 130 fn service_bootstrap_paths_reject_invalid_runtime_ids() { 131 let resolver = RadrootsPathResolver::new( 132 RadrootsPlatform::Linux, 133 RadrootsHostEnvironment { 134 home_dir: Some(PathBuf::from("/home/treesap")), 135 ..RadrootsHostEnvironment::default() 136 }, 137 ); 138 139 let err = service_bootstrap_paths_for( 140 &resolver, 141 RadrootsPathProfile::InteractiveUser, 142 &RadrootsPathOverrides::default(), 143 "", 144 ) 145 .expect_err("empty runtime ids must fail"); 146 147 assert!(!err.to_string().is_empty()); 148 } 149 }