upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src/config.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/config.rs')
-rw-r--r--src/config.rs100
1 files changed, 88 insertions, 12 deletions
diff --git a/src/config.rs b/src/config.rs
index 94035e4..c4a7b6c 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -1,6 +1,9 @@
1use anyhow::Result; 1use anyhow::{Context, Result};
2use clap::{Parser, ValueEnum}; 2use clap::{Parser, ValueEnum};
3use nostr_sdk::prelude::*;
3use serde::{Deserialize, Serialize}; 4use serde::{Deserialize, Serialize};
5use std::fs;
6use std::path::PathBuf;
4 7
5/// Database backend type for the relay 8/// Database backend type for the relay
6#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default, ValueEnum)] 9#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default, ValueEnum)]
@@ -40,9 +43,18 @@ pub struct Config {
40 #[arg(long, env = "NGIT_DOMAIN")] 43 #[arg(long, env = "NGIT_DOMAIN")]
41 pub domain: String, 44 pub domain: String,
42 45
43 /// Owner's npub (optional, for relay info in NIP-11) 46 /// Relay operator's nsec (private key) for signing and authentication
44 #[arg(long, env = "NGIT_OWNER_NPUB")] 47 ///
45 pub owner_npub: Option<String>, 48 /// Used for:
49 /// - NIP-11 relay information document (pubkey field derived from this nsec)
50 /// - NIP-42 authentication when syncing from other relays
51 /// - Future: signing events, WoT-based rate limiting of syncing relays
52 ///
53 /// If not provided via CLI/env, will be loaded from/saved to `.relay-owner.nsec` file
54 /// in the current directory. If the file doesn't exist, a new key will be generated
55 /// and saved automatically.
56 #[arg(long, env = "NGIT_RELAY_OWNER_NSEC")]
57 pub relay_owner_nsec: Option<String>,
46 58
47 /// Relay name for NIP-11 information document (defaults to "${domain} grasp relay") 59 /// Relay name for NIP-11 information document (defaults to "${domain} grasp relay")
48 #[arg(long = "relay-name", env = "NGIT_RELAY_NAME")] 60 #[arg(long = "relay-name", env = "NGIT_RELAY_NAME")]
@@ -127,6 +139,9 @@ pub struct Config {
127} 139}
128 140
129impl Config { 141impl Config {
142 /// Path to the relay owner key file
143 const RELAY_OWNER_KEY_FILE: &'static str = ".relay-owner.nsec";
144
130 /// Load configuration from CLI args, environment variables, and defaults. 145 /// Load configuration from CLI args, environment variables, and defaults.
131 /// 146 ///
132 /// Priority (highest to lowest): 147 /// Priority (highest to lowest):
@@ -139,11 +154,66 @@ impl Config {
139 dotenvy::dotenv().ok(); 154 dotenvy::dotenv().ok();
140 155
141 // Parse CLI args (clap automatically handles env var fallback) 156 // Parse CLI args (clap automatically handles env var fallback)
142 let config = Self::parse(); 157 let mut config = Self::parse();
158
159 // If relay_owner_nsec not provided, load from file or generate
160 if config.relay_owner_nsec.is_none() {
161 config.relay_owner_nsec = Some(Self::load_or_generate_relay_owner_key()?);
162 }
143 163
144 Ok(config) 164 Ok(config)
145 } 165 }
146 166
167 /// Load relay owner key from file, or generate and save a new one
168 fn load_or_generate_relay_owner_key() -> Result<String> {
169 let key_path = PathBuf::from(Self::RELAY_OWNER_KEY_FILE);
170
171 // Try to load existing key
172 if key_path.exists() {
173 let nsec = fs::read_to_string(&key_path)
174 .context("Failed to read relay owner key file")?
175 .trim()
176 .to_string();
177
178 // Validate it's a valid nsec
179 Keys::parse(&nsec).context("Invalid nsec in relay owner key file")?;
180
181 tracing::info!(
182 "Loaded relay owner key from {}",
183 key_path.display()
184 );
185 return Ok(nsec);
186 }
187
188 // Generate new key
189 let keys = Keys::generate();
190 let nsec = keys.secret_key().to_bech32()?;
191
192 // Save to file
193 fs::write(&key_path, &nsec)
194 .context("Failed to write relay owner key file")?;
195
196 tracing::info!(
197 "Generated new relay owner key and saved to {}",
198 key_path.display()
199 );
200
201 Ok(nsec)
202 }
203
204 /// Get the relay owner's Keys object
205 pub fn relay_owner_keys(&self) -> Result<Keys> {
206 let nsec = self.relay_owner_nsec.as_ref()
207 .context("relay_owner_nsec not set (should be set by Config::load())")?;
208 Keys::parse(nsec).context("Invalid relay_owner_nsec")
209 }
210
211 /// Get the relay owner's public key (npub format) for NIP-11
212 pub fn relay_owner_npub(&self) -> Result<String> {
213 let keys = self.relay_owner_keys()?;
214 Ok(keys.public_key().to_bech32()?)
215 }
216
147 /// Get relay name (defaults to "${domain} grasp relay" if not set) 217 /// Get relay name (defaults to "${domain} grasp relay" if not set)
148 pub fn relay_name(&self) -> String { 218 pub fn relay_name(&self) -> String {
149 self.relay_name_override 219 self.relay_name_override
@@ -167,9 +237,13 @@ impl Config {
167 /// Create config for testing 237 /// Create config for testing
168 #[cfg(test)] 238 #[cfg(test)]
169 pub fn for_testing() -> Self { 239 pub fn for_testing() -> Self {
240 // Generate a test key deterministically for consistent tests
241 let keys = Keys::generate();
242 let nsec = keys.secret_key().to_bech32().expect("Failed to generate test nsec");
243
170 Self { 244 Self {
171 domain: "localhost:8080".to_string(), 245 domain: "localhost:8080".to_string(),
172 owner_npub: Some("npub1test".to_string()), 246 relay_owner_nsec: Some(nsec),
173 relay_name_override: Some("test relay".to_string()), 247 relay_name_override: Some("test relay".to_string()),
174 relay_description: "test description".to_string(), 248 relay_description: "test description".to_string(),
175 git_data_path: "./test_data/git".to_string(), 249 git_data_path: "./test_data/git".to_string(),
@@ -256,12 +330,14 @@ mod tests {
256 } 330 }
257 331
258 #[test] 332 #[test]
259 fn test_owner_npub_optional() { 333 fn test_relay_owner_keys() {
260 let config = Config { 334 let config = Config::for_testing();
261 owner_npub: None, 335 let keys = config.relay_owner_keys().expect("Should have valid keys");
262 ..Config::for_testing() 336 let npub = config.relay_owner_npub().expect("Should derive npub");
263 }; 337
264 assert!(config.owner_npub.is_none()); 338 // Verify the npub matches the keys
339 assert_eq!(npub, keys.public_key().to_bech32().unwrap());
340 assert!(npub.starts_with("npub1"));
265 } 341 }
266 342
267 #[test] 343 #[test]