diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2026-02-03 14:50:22 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2026-02-03 15:18:23 +0000 |
| commit | 874a8abe1d076cfafd9baf919ec23d7d58200698 (patch) | |
| tree | dce0d0d36bddc496ff32f8555a8790d8dc7be7e4 /src/config.rs | |
| parent | 9fd4350c57bbe986ebf65bf3ea4c996572e81884 (diff) | |
| parent | 92a9a3bfe0bc522e8ae411991a366a3a6310d525 (diff) | |
Merge relay.ngit.dev migration: bug fixes and migration tooling
This merge includes critical bug fixes and comprehensive migration tooling
developed during the relay.ngit.dev migration effort.
Bug Fixes:
- Fix git protocol error handling to return HTTP 200 with ERR pkt-line
- Fix naughty list false positives and DNS failure identification
- Fix database query filters in load_existing_events (remove .since())
- Fix OID fetch tracking to distinguish 0 OIDs from successful fetches
- Fix purgatory event source tracking for filtered expiry logging
- Implement OID retry logic for 'not our ref' errors
Migration Tools & Documentation:
- Complete 5-phase migration analysis pipeline with orchestration script
- Phase 1: Event fetching from source relay
- Phase 2: Git sync verification
- Phase 3: Categorization and relay comparison
- Phase 4: Log extraction (parse failures, purgatory expiry)
- Phase 5: Action classification for migration decisions
- Comprehensive migration guide with lessons learned
- Troubleshooting guide for permission and corruption issues
Configuration:
- Add NGIT_LOG_LEVEL configuration option
- Update git throttle limits to 60/minute
- Improve logging throughout for better observability
Diffstat (limited to 'src/config.rs')
| -rw-r--r-- | src/config.rs | 74 |
1 files changed, 26 insertions, 48 deletions
diff --git a/src/config.rs b/src/config.rs index 271a340..dd7b1e3 100644 --- a/src/config.rs +++ b/src/config.rs | |||
| @@ -109,22 +109,25 @@ impl WhitelistEntry { | |||
| 109 | } | 109 | } |
| 110 | 110 | ||
| 111 | /// GRASP-05 Archive mode configuration | 111 | /// GRASP-05 Archive mode configuration |
| 112 | #[derive(Debug, Clone, Serialize, Deserialize)] | 112 | #[derive(Debug, Clone, Serialize, Deserialize, Default)] |
| 113 | pub struct ArchiveConfig { | 113 | pub struct ArchiveConfig { |
| 114 | /// Accept all repository announcements (no filtering) | 114 | /// Accept all repository announcements (no filtering) |
| 115 | /// | 115 | /// |
| 116 | /// WARNING: Setting this to true allows anyone to mirror any repository | 116 | /// WARNING: Setting this to true allows anyone to mirror any repository |
| 117 | /// to this relay, potentially causing storage/bandwidth exhaustion. | 117 | /// to this relay, potentially causing storage/bandwidth exhaustion. |
| 118 | #[serde(default)] | ||
| 118 | pub archive_all: bool, | 119 | pub archive_all: bool, |
| 119 | 120 | ||
| 120 | /// Whitelist entries for selective archiving | 121 | /// Whitelist entries for selective archiving |
| 121 | /// | 122 | /// |
| 122 | /// If empty and archive_all is false, GRASP-05 is disabled (GRASP-01 strict mode). | 123 | /// If empty and archive_all is false, GRASP-05 is disabled (GRASP-01 strict mode). |
| 124 | #[serde(default)] | ||
| 123 | pub whitelist: Vec<WhitelistEntry>, | 125 | pub whitelist: Vec<WhitelistEntry>, |
| 124 | 126 | ||
| 125 | /// GRASP server domains to archive (archive all repositories from these domains) | 127 | /// GRASP server domains to archive (archive all repositories from these domains) |
| 126 | /// | 128 | /// |
| 127 | /// If non-empty, archives all repositories from the specified GRASP server domains. | 129 | /// If non-empty, archives all repositories from the specified GRASP server domains. |
| 130 | #[serde(default)] | ||
| 128 | pub grasp_services: Vec<String>, | 131 | pub grasp_services: Vec<String>, |
| 129 | 132 | ||
| 130 | /// Read-only archive mode: relay is a read-only sync of archived repositories | 133 | /// Read-only archive mode: relay is a read-only sync of archived repositories |
| @@ -132,6 +135,7 @@ pub struct ArchiveConfig { | |||
| 132 | /// When true, the relay ONLY accepts announcements matching the archive whitelist/all. | 135 | /// When true, the relay ONLY accepts announcements matching the archive whitelist/all. |
| 133 | /// Announcements listing the relay but not in the whitelist are rejected. | 136 | /// Announcements listing the relay but not in the whitelist are rejected. |
| 134 | /// When false, the relay operates in GRASP-01 mode for unwhitelisted repos. | 137 | /// When false, the relay operates in GRASP-01 mode for unwhitelisted repos. |
| 138 | #[serde(default)] | ||
| 135 | pub read_only: bool, | 139 | pub read_only: bool, |
| 136 | } | 140 | } |
| 137 | 141 | ||
| @@ -146,6 +150,7 @@ impl ArchiveConfig { | |||
| 146 | /// Returns true if: | 150 | /// Returns true if: |
| 147 | /// - archive_all is true, OR | 151 | /// - archive_all is true, OR |
| 148 | /// - announcement matches any whitelist entry | 152 | /// - announcement matches any whitelist entry |
| 153 | /// | ||
| 149 | /// Note: grasp_services matching is handled via matches_grasp_services() | 154 | /// Note: grasp_services matching is handled via matches_grasp_services() |
| 150 | pub fn matches(&self, npub: &str, identifier: &str) -> bool { | 155 | pub fn matches(&self, npub: &str, identifier: &str) -> bool { |
| 151 | if self.archive_all { | 156 | if self.archive_all { |
| @@ -171,23 +176,13 @@ impl ArchiveConfig { | |||
| 171 | } | 176 | } |
| 172 | } | 177 | } |
| 173 | 178 | ||
| 174 | impl Default for ArchiveConfig { | ||
| 175 | fn default() -> Self { | ||
| 176 | Self { | ||
| 177 | archive_all: false, | ||
| 178 | whitelist: Vec::new(), | ||
| 179 | grasp_services: Vec::new(), | ||
| 180 | read_only: false, | ||
| 181 | } | ||
| 182 | } | ||
| 183 | } | ||
| 184 | |||
| 185 | /// Repository whitelist configuration | 179 | /// Repository whitelist configuration |
| 186 | #[derive(Debug, Clone, Serialize, Deserialize)] | 180 | #[derive(Debug, Clone, Serialize, Deserialize, Default)] |
| 187 | pub struct RepositoryConfig { | 181 | pub struct RepositoryConfig { |
| 188 | /// Whitelist entries for selective repository acceptance | 182 | /// Whitelist entries for selective repository acceptance |
| 189 | /// | 183 | /// |
| 190 | /// If empty, all repositories listing the service are accepted (GRASP-01 mode). | 184 | /// If empty, all repositories listing the service are accepted (GRASP-01 mode). |
| 185 | #[serde(default)] | ||
| 191 | pub whitelist: Vec<WhitelistEntry>, | 186 | pub whitelist: Vec<WhitelistEntry>, |
| 192 | } | 187 | } |
| 193 | 188 | ||
| @@ -207,21 +202,14 @@ impl RepositoryConfig { | |||
| 207 | } | 202 | } |
| 208 | } | 203 | } |
| 209 | 204 | ||
| 210 | impl Default for RepositoryConfig { | ||
| 211 | fn default() -> Self { | ||
| 212 | Self { | ||
| 213 | whitelist: Vec::new(), | ||
| 214 | } | ||
| 215 | } | ||
| 216 | } | ||
| 217 | |||
| 218 | /// Repository blacklist configuration | 205 | /// Repository blacklist configuration |
| 219 | #[derive(Debug, Clone, Serialize, Deserialize)] | 206 | #[derive(Debug, Clone, Serialize, Deserialize, Default)] |
| 220 | pub struct BlacklistConfig { | 207 | pub struct BlacklistConfig { |
| 221 | /// Blacklist entries for blocking specific repositories | 208 | /// Blacklist entries for blocking specific repositories |
| 222 | /// | 209 | /// |
| 223 | /// If empty, no repositories are blacklisted. | 210 | /// If empty, no repositories are blacklisted. |
| 224 | /// Blacklist takes precedence over both archive and repository whitelists. | 211 | /// Blacklist takes precedence over both archive and repository whitelists. |
| 212 | #[serde(default)] | ||
| 225 | pub blacklist: Vec<WhitelistEntry>, | 213 | pub blacklist: Vec<WhitelistEntry>, |
| 226 | } | 214 | } |
| 227 | 215 | ||
| @@ -256,21 +244,14 @@ impl BlacklistConfig { | |||
| 256 | } | 244 | } |
| 257 | } | 245 | } |
| 258 | 246 | ||
| 259 | impl Default for BlacklistConfig { | ||
| 260 | fn default() -> Self { | ||
| 261 | Self { | ||
| 262 | blacklist: Vec::new(), | ||
| 263 | } | ||
| 264 | } | ||
| 265 | } | ||
| 266 | |||
| 267 | /// Event blacklist configuration for blocking events by author npub | 247 | /// Event blacklist configuration for blocking events by author npub |
| 268 | #[derive(Debug, Clone, Serialize, Deserialize)] | 248 | #[derive(Debug, Clone, Serialize, Deserialize, Default)] |
| 269 | pub struct EventBlacklistConfig { | 249 | pub struct EventBlacklistConfig { |
| 270 | /// Blacklisted npubs - events from these authors are rejected | 250 | /// Blacklisted npubs - events from these authors are rejected |
| 271 | /// | 251 | /// |
| 272 | /// If empty, no events are blacklisted by author. | 252 | /// If empty, no events are blacklisted by author. |
| 273 | /// Applies to ALL event types, preventing events from reaching both the relay and purgatory. | 253 | /// Applies to ALL event types, preventing events from reaching both the relay and purgatory. |
| 254 | #[serde(default)] | ||
| 274 | pub blacklisted_npubs: Vec<String>, | 255 | pub blacklisted_npubs: Vec<String>, |
| 275 | } | 256 | } |
| 276 | 257 | ||
| @@ -292,14 +273,6 @@ impl EventBlacklistConfig { | |||
| 292 | } | 273 | } |
| 293 | } | 274 | } |
| 294 | 275 | ||
| 295 | impl Default for EventBlacklistConfig { | ||
| 296 | fn default() -> Self { | ||
| 297 | Self { | ||
| 298 | blacklisted_npubs: Vec::new(), | ||
| 299 | } | ||
| 300 | } | ||
| 301 | } | ||
| 302 | |||
| 303 | /// Database backend type for the relay | 276 | /// Database backend type for the relay |
| 304 | #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default, ValueEnum)] | 277 | #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default, ValueEnum)] |
| 305 | #[serde(rename_all = "lowercase")] | 278 | #[serde(rename_all = "lowercase")] |
| @@ -500,6 +473,10 @@ pub struct Config { | |||
| 500 | /// Prevents connection exhaustion DoS attacks | 473 | /// Prevents connection exhaustion DoS attacks |
| 501 | #[arg(long, env = "NGIT_MAX_CONNECTIONS", default_value_t = 4096)] | 474 | #[arg(long, env = "NGIT_MAX_CONNECTIONS", default_value_t = 4096)] |
| 502 | pub max_connections: usize, | 475 | pub max_connections: usize, |
| 476 | |||
| 477 | /// Log level for application logging | ||
| 478 | #[arg(long, env = "NGIT_LOG_LEVEL", default_value = "info")] | ||
| 479 | pub log_level: String, | ||
| 503 | } | 480 | } |
| 504 | 481 | ||
| 505 | impl Config { | 482 | impl Config { |
| @@ -782,6 +759,7 @@ impl Config { | |||
| 782 | repository_blacklist: String::new(), | 759 | repository_blacklist: String::new(), |
| 783 | event_blacklist: String::new(), | 760 | event_blacklist: String::new(), |
| 784 | max_connections: 500, | 761 | max_connections: 500, |
| 762 | log_level: "debug".to_string(), | ||
| 785 | } | 763 | } |
| 786 | } | 764 | } |
| 787 | } | 765 | } |
| @@ -1103,14 +1081,14 @@ mod tests { | |||
| 1103 | fn test_archive_read_only_defaults() { | 1081 | fn test_archive_read_only_defaults() { |
| 1104 | // Default: false when no archive mode | 1082 | // Default: false when no archive mode |
| 1105 | let config = Config::for_testing(); | 1083 | let config = Config::for_testing(); |
| 1106 | assert_eq!(config.archive_config().read_only, false); | 1084 | assert!(!config.archive_config().read_only); |
| 1107 | 1085 | ||
| 1108 | // Default: true when archive_all is set | 1086 | // Default: true when archive_all is set |
| 1109 | let config = Config { | 1087 | let config = Config { |
| 1110 | archive_all: true, | 1088 | archive_all: true, |
| 1111 | ..Config::for_testing() | 1089 | ..Config::for_testing() |
| 1112 | }; | 1090 | }; |
| 1113 | assert_eq!(config.archive_config().read_only, true); | 1091 | assert!(config.archive_config().read_only); |
| 1114 | 1092 | ||
| 1115 | // Default: true when archive_whitelist is set | 1093 | // Default: true when archive_whitelist is set |
| 1116 | let keys = Keys::generate(); | 1094 | let keys = Keys::generate(); |
| @@ -1119,7 +1097,7 @@ mod tests { | |||
| 1119 | archive_whitelist: test_npub, | 1097 | archive_whitelist: test_npub, |
| 1120 | ..Config::for_testing() | 1098 | ..Config::for_testing() |
| 1121 | }; | 1099 | }; |
| 1122 | assert_eq!(config.archive_config().read_only, true); | 1100 | assert!(config.archive_config().read_only); |
| 1123 | } | 1101 | } |
| 1124 | 1102 | ||
| 1125 | #[test] | 1103 | #[test] |
| @@ -1130,7 +1108,7 @@ mod tests { | |||
| 1130 | archive_read_only: Some(true), | 1108 | archive_read_only: Some(true), |
| 1131 | ..Config::for_testing() | 1109 | ..Config::for_testing() |
| 1132 | }; | 1110 | }; |
| 1133 | assert_eq!(config.archive_config().read_only, true); | 1111 | assert!(config.archive_config().read_only); |
| 1134 | 1112 | ||
| 1135 | // Explicit false with archive_all (unusual but allowed) | 1113 | // Explicit false with archive_all (unusual but allowed) |
| 1136 | let config = Config { | 1114 | let config = Config { |
| @@ -1138,14 +1116,14 @@ mod tests { | |||
| 1138 | archive_read_only: Some(false), | 1116 | archive_read_only: Some(false), |
| 1139 | ..Config::for_testing() | 1117 | ..Config::for_testing() |
| 1140 | }; | 1118 | }; |
| 1141 | assert_eq!(config.archive_config().read_only, false); | 1119 | assert!(!config.archive_config().read_only); |
| 1142 | 1120 | ||
| 1143 | // Explicit false without archive mode | 1121 | // Explicit false without archive mode |
| 1144 | let config = Config { | 1122 | let config = Config { |
| 1145 | archive_read_only: Some(false), | 1123 | archive_read_only: Some(false), |
| 1146 | ..Config::for_testing() | 1124 | ..Config::for_testing() |
| 1147 | }; | 1125 | }; |
| 1148 | assert_eq!(config.archive_config().read_only, false); | 1126 | assert!(!config.archive_config().read_only); |
| 1149 | } | 1127 | } |
| 1150 | 1128 | ||
| 1151 | #[test] | 1129 | #[test] |
| @@ -1548,7 +1526,7 @@ mod tests { | |||
| 1548 | }; | 1526 | }; |
| 1549 | let archive_config = config.archive_config(); | 1527 | let archive_config = config.archive_config(); |
| 1550 | assert!(archive_config.enabled()); | 1528 | assert!(archive_config.enabled()); |
| 1551 | assert_eq!(archive_config.read_only, true); // Default to true | 1529 | assert!(archive_config.read_only); // Default to true |
| 1552 | } | 1530 | } |
| 1553 | 1531 | ||
| 1554 | #[test] | 1532 | #[test] |
| @@ -1558,7 +1536,7 @@ mod tests { | |||
| 1558 | archive_grasp_services: "git.example.com".to_string(), | 1536 | archive_grasp_services: "git.example.com".to_string(), |
| 1559 | ..Config::for_testing() | 1537 | ..Config::for_testing() |
| 1560 | }; | 1538 | }; |
| 1561 | assert_eq!(config.archive_config().read_only, true); | 1539 | assert!(config.archive_config().read_only); |
| 1562 | } | 1540 | } |
| 1563 | 1541 | ||
| 1564 | #[test] | 1542 | #[test] |
| @@ -1569,7 +1547,7 @@ mod tests { | |||
| 1569 | archive_read_only: Some(false), | 1547 | archive_read_only: Some(false), |
| 1570 | ..Config::for_testing() | 1548 | ..Config::for_testing() |
| 1571 | }; | 1549 | }; |
| 1572 | assert_eq!(config.archive_config().read_only, false); | 1550 | assert!(!config.archive_config().read_only); |
| 1573 | } | 1551 | } |
| 1574 | 1552 | ||
| 1575 | #[test] | 1553 | #[test] |