upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/http
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2026-01-12 20:30:13 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2026-01-12 20:30:13 +0000
commita12927181c571fc1641772ad44dd4c6a4ab209d9 (patch)
treed7cb99fa87606e9fb13d91305cda8a0f919e6528 /src/http
parentc29191b1e1239e931c575a926ec9480e594476d6 (diff)
feat(grasp-05): add read-only mode with auto-enable for archive configs
Implements NGIT_ARCHIVE_READ_ONLY configuration option that defaults to true when archive mode is enabled, allowing relays to operate as read-only syncs of archived repositories. Key changes: - Add NGIT_ARCHIVE_READ_ONLY config option (defaults to true if archive enabled) - NIP-11 advertises GRASP-05 support and includes curation field when read-only - Validation logic rejects non-whitelisted repos in read-only mode - Comprehensive tests for read-only behavior and defaults - Full documentation in config reference, .env.example, and NixOS module Read-only mode enables passive mirroring without being listed in announcements, useful for backup/archive operations while preventing accidental write acceptance.
Diffstat (limited to 'src/http')
-rw-r--r--src/http/nip11.rs87
1 files changed, 85 insertions, 2 deletions
diff --git a/src/http/nip11.rs b/src/http/nip11.rs
index b756d9c..71cadb1 100644
--- a/src/http/nip11.rs
+++ b/src/http/nip11.rs
@@ -56,6 +56,41 @@ pub struct RelayInformationDocument {
56impl RelayInformationDocument { 56impl RelayInformationDocument {
57 /// Create NIP-11 relay information document from configuration 57 /// Create NIP-11 relay information document from configuration
58 pub fn from_config(config: &Config) -> Self { 58 pub fn from_config(config: &Config) -> Self {
59 // Determine if archive mode is enabled
60 let archive_config = config.archive_config().ok();
61 let archive_enabled = archive_config
62 .as_ref()
63 .map(|ac| ac.enabled())
64 .unwrap_or(false);
65 let archive_read_only = archive_config
66 .as_ref()
67 .map(|ac| ac.read_only)
68 .unwrap_or(false);
69
70 // Build supported_grasps list
71 let mut supported_grasps = vec!["GRASP-01".to_string()];
72 if archive_enabled {
73 supported_grasps.push("GRASP-05".to_string());
74 }
75 supported_grasps.push("GRASP-02".to_string());
76
77 // Build curation field for archive read-only mode
78 let curation = if archive_read_only {
79 if let Some(ref ac) = archive_config {
80 if ac.archive_all {
81 Some("Read-only sync of all repositories found on network".to_string())
82 } else if !ac.whitelist.is_empty() {
83 Some("Read-only sync of whitelisted repositories and maintainers".to_string())
84 } else {
85 None
86 }
87 } else {
88 None
89 }
90 } else {
91 None
92 };
93
59 Self { 94 Self {
60 name: config.relay_name(), 95 name: config.relay_name(),
61 description: config.relay_description.clone(), 96 description: config.relay_description.clone(),
@@ -75,9 +110,9 @@ impl RelayInformationDocument {
75 icon: Some(format!("https://{}/icon.png", config.domain)), 110 icon: Some(format!("https://{}/icon.png", config.domain)),
76 111
77 // GRASP Extensions 112 // GRASP Extensions
78 supported_grasps: vec!["GRASP-01".to_string(), "GRASP-02".to_string()], 113 supported_grasps,
79 repo_acceptance_criteria: "None".to_string(), 114 repo_acceptance_criteria: "None".to_string(),
80 curation: None, // Not a curated relay - only SPAM prevention via GRASP-01 policy 115 curation,
81 } 116 }
82 } 117 }
83 118
@@ -90,6 +125,7 @@ impl RelayInformationDocument {
90#[cfg(test)] 125#[cfg(test)]
91mod tests { 126mod tests {
92 use super::*; 127 use super::*;
128 use nostr_sdk::nips::nip19::ToBech32;
93 129
94 #[test] 130 #[test]
95 fn test_relay_information_document_structure() { 131 fn test_relay_information_document_structure() {
@@ -112,6 +148,7 @@ mod tests {
112 assert!(doc.supported_nips.contains(&11)); 148 assert!(doc.supported_nips.contains(&11));
113 assert!(doc.supported_nips.contains(&34)); 149 assert!(doc.supported_nips.contains(&34));
114 assert!(doc.supported_nips.contains(&77)); 150 assert!(doc.supported_nips.contains(&77));
151 // Without archive mode, only GRASP-01 and GRASP-02
115 assert_eq!(doc.supported_grasps, vec!["GRASP-01", "GRASP-02"]); 152 assert_eq!(doc.supported_grasps, vec!["GRASP-01", "GRASP-02"]);
116 assert!(doc.repo_acceptance_criteria.contains("None")); 153 assert!(doc.repo_acceptance_criteria.contains("None"));
117 assert!(doc.curation.is_none()); 154 assert!(doc.curation.is_none());
@@ -147,4 +184,50 @@ mod tests {
147 assert_eq!(parsed["supported_grasps"][1], "GRASP-02"); 184 assert_eq!(parsed["supported_grasps"][1], "GRASP-02");
148 assert_eq!(parsed["icon"], "https://relay.example.com/icon.png"); 185 assert_eq!(parsed["icon"], "https://relay.example.com/icon.png");
149 } 186 }
187
188 #[test]
189 fn test_nip11_with_archive_mode() {
190 let mut config = Config::for_testing();
191 config.domain = "relay.example.com".to_string();
192 config.relay_name_override = Some("Archive Relay".to_string());
193 config.archive_all = true;
194 config.archive_read_only = Some(true);
195
196 let doc = RelayInformationDocument::from_config(&config);
197
198 // Archive mode enabled: should include GRASP-05
199 assert_eq!(
200 doc.supported_grasps,
201 vec!["GRASP-01", "GRASP-05", "GRASP-02"]
202 );
203 // Archive read-only: should have curation field
204 assert!(doc.curation.is_some());
205 assert!(doc
206 .curation
207 .unwrap()
208 .contains("Read-only sync of all repositories"));
209 }
210
211 #[test]
212 fn test_nip11_with_whitelist_archive() {
213 let keys = nostr_sdk::Keys::generate();
214 let test_npub = keys.public_key().to_bech32().unwrap();
215 let mut config = Config::for_testing();
216 config.domain = "relay.example.com".to_string();
217 config.archive_whitelist = format!("{},bitcoin-core", test_npub);
218
219 let doc = RelayInformationDocument::from_config(&config);
220
221 // Archive whitelist enabled: should include GRASP-05
222 assert_eq!(
223 doc.supported_grasps,
224 vec!["GRASP-01", "GRASP-05", "GRASP-02"]
225 );
226 // Archive read-only defaults to true: should have curation field
227 assert!(doc.curation.is_some());
228 assert!(doc
229 .curation
230 .unwrap()
231 .contains("Read-only sync of whitelisted"));
232 }
150} 233}