diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2026-01-08 12:02:59 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2026-01-08 12:14:27 +0000 |
| commit | 0356740d11c9fb6524060e6755ab0c2186096724 (patch) | |
| tree | db7ca39d1cb6b078d40fde14d3a84cfbf443eaab | |
| parent | 64eaf4407028d3727fe8854a9a8fb97a8660af95 (diff) | |
fix: sync-bootstrap-relay-url scheme optional
| -rw-r--r-- | README.md | 2 | ||||
| -rw-r--r-- | docs/reference/configuration.md | 55 | ||||
| -rw-r--r-- | src/config.rs | 2 | ||||
| -rw-r--r-- | src/sync/relay_connection.rs | 101 |
4 files changed, 148 insertions, 12 deletions
| @@ -321,7 +321,7 @@ NGIT_OWNER_NPUB=npub1... ngit-grasp --domain relay.example.com | |||
| 321 | 321 | ||
| 322 | **Sync Notes:** | 322 | **Sync Notes:** |
| 323 | 323 | ||
| 324 | - **Bootstrap relay**: Optional starting point for relay discovery. System automatically discovers additional relays from repository announcements. | 324 | - **Bootstrap relay**: Optional starting point for relay discovery. System automatically discovers additional relays from repository announcements. URL scheme is optional - if not provided, `wss://` is assumed (e.g., `git.shakespeare.diy` → `wss://git.shakespeare.diy`). |
| 325 | - **Backoff settings**: Controls exponential backoff for failed connections (`base * 2^(failures-1)`, capped at max). | 325 | - **Backoff settings**: Controls exponential backoff for failed connections (`base * 2^(failures-1)`, capped at max). |
| 326 | - **Negentropy**: Can be disabled for testing REQ+EOSE fallback behavior. | 326 | - **Negentropy**: Can be disabled for testing REQ+EOSE fallback behavior. |
| 327 | - **Batch window**: Self-subscriber batches events for this duration before triggering sync filters. | 327 | - **Batch window**: Self-subscriber batches events for this duration before triggering sync filters. |
diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index 204fbd1..ece14af 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md | |||
| @@ -29,6 +29,7 @@ Configuration is loaded at startup and validated before the server starts. | |||
| 29 | **Required:** No | 29 | **Required:** No |
| 30 | 30 | ||
| 31 | **Examples:** | 31 | **Examples:** |
| 32 | |||
| 32 | ```bash | 33 | ```bash |
| 33 | # Localhost only (development) | 34 | # Localhost only (development) |
| 34 | NGIT_BIND_ADDRESS=127.0.0.1:8080 | 35 | NGIT_BIND_ADDRESS=127.0.0.1:8080 |
| @@ -44,6 +45,7 @@ NGIT_BIND_ADDRESS=127.0.0.1:3000 | |||
| 44 | ``` | 45 | ``` |
| 45 | 46 | ||
| 46 | **Notes:** | 47 | **Notes:** |
| 48 | |||
| 47 | - Use `127.0.0.1` for local development | 49 | - Use `127.0.0.1` for local development |
| 48 | - Use `0.0.0.0` for production (behind reverse proxy) | 50 | - Use `0.0.0.0` for production (behind reverse proxy) |
| 49 | - Ensure firewall rules allow the port | 51 | - Ensure firewall rules allow the port |
| @@ -58,6 +60,7 @@ NGIT_BIND_ADDRESS=127.0.0.1:3000 | |||
| 58 | **Required:** Yes | 60 | **Required:** Yes |
| 59 | 61 | ||
| 60 | **Examples:** | 62 | **Examples:** |
| 63 | |||
| 61 | ```bash | 64 | ```bash |
| 62 | NGIT_DOMAIN=gitnostr.com | 65 | NGIT_DOMAIN=gitnostr.com |
| 63 | NGIT_DOMAIN=git.example.org | 66 | NGIT_DOMAIN=git.example.org |
| @@ -65,12 +68,14 @@ NGIT_DOMAIN=localhost:8080 # Development only | |||
| 65 | ``` | 68 | ``` |
| 66 | 69 | ||
| 67 | **Used for:** | 70 | **Used for:** |
| 71 | |||
| 68 | - NIP-11 relay information document | 72 | - NIP-11 relay information document |
| 69 | - Generating repository URLs | 73 | - Generating repository URLs |
| 70 | - CORS configuration | 74 | - CORS configuration |
| 71 | - Webhook URLs (future) | 75 | - Webhook URLs (future) |
| 72 | 76 | ||
| 73 | **Notes:** | 77 | **Notes:** |
| 78 | |||
| 74 | - Must be accessible from the internet for production | 79 | - Must be accessible from the internet for production |
| 75 | - Include port if non-standard (e.g., `localhost:8080`) | 80 | - Include port if non-standard (e.g., `localhost:8080`) |
| 76 | - Used in repository clone URLs: `https://{NGIT_DOMAIN}/{npub}/{repo}.git` | 81 | - Used in repository clone URLs: `https://{NGIT_DOMAIN}/{npub}/{repo}.git` |
| @@ -87,16 +92,19 @@ NGIT_DOMAIN=localhost:8080 # Development only | |||
| 87 | **Required:** Yes | 92 | **Required:** Yes |
| 88 | 93 | ||
| 89 | **Examples:** | 94 | **Examples:** |
| 95 | |||
| 90 | ```bash | 96 | ```bash |
| 91 | NGIT_OWNER_NPUB=npub1alice... | 97 | NGIT_OWNER_NPUB=npub1alice... |
| 92 | ``` | 98 | ``` |
| 93 | 99 | ||
| 94 | **Used for:** | 100 | **Used for:** |
| 101 | |||
| 95 | - NIP-11 relay information document | 102 | - NIP-11 relay information document |
| 96 | - Contact information | 103 | - Contact information |
| 97 | - Administrative operations (future) | 104 | - Administrative operations (future) |
| 98 | 105 | ||
| 99 | **Notes:** | 106 | **Notes:** |
| 107 | |||
| 100 | - Must be valid npub format (starts with `npub1`) | 108 | - Must be valid npub format (starts with `npub1`) |
| 101 | - Can be generated with Nostr tools | 109 | - Can be generated with Nostr tools |
| 102 | - Publicly visible in relay metadata | 110 | - Publicly visible in relay metadata |
| @@ -111,12 +119,14 @@ NGIT_OWNER_NPUB=npub1alice... | |||
| 111 | **Required:** No | 119 | **Required:** No |
| 112 | 120 | ||
| 113 | **Examples:** | 121 | **Examples:** |
| 122 | |||
| 114 | ```bash | 123 | ```bash |
| 115 | NGIT_RELAY_NAME="GitNostr Community Relay" | 124 | NGIT_RELAY_NAME="GitNostr Community Relay" |
| 116 | NGIT_RELAY_NAME="Alice's GRASP Server" | 125 | NGIT_RELAY_NAME="Alice's GRASP Server" |
| 117 | ``` | 126 | ``` |
| 118 | 127 | ||
| 119 | **Used for:** | 128 | **Used for:** |
| 129 | |||
| 120 | - NIP-11 relay information document | 130 | - NIP-11 relay information document |
| 121 | - Client display | 131 | - Client display |
| 122 | - Relay discovery | 132 | - Relay discovery |
| @@ -131,12 +141,14 @@ NGIT_RELAY_NAME="Alice's GRASP Server" | |||
| 131 | **Required:** No | 141 | **Required:** No |
| 132 | 142 | ||
| 133 | **Examples:** | 143 | **Examples:** |
| 144 | |||
| 134 | ```bash | 145 | ```bash |
| 135 | NGIT_RELAY_DESCRIPTION="Public GRASP relay for open source projects" | 146 | NGIT_RELAY_DESCRIPTION="Public GRASP relay for open source projects" |
| 136 | NGIT_RELAY_DESCRIPTION="Private relay for ACME Corp repositories" | 147 | NGIT_RELAY_DESCRIPTION="Private relay for ACME Corp repositories" |
| 137 | ``` | 148 | ``` |
| 138 | 149 | ||
| 139 | **Used for:** | 150 | **Used for:** |
| 151 | |||
| 140 | - NIP-11 relay information document | 152 | - NIP-11 relay information document |
| 141 | - User information | 153 | - User information |
| 142 | - Relay selection | 154 | - Relay selection |
| @@ -153,6 +165,7 @@ NGIT_RELAY_DESCRIPTION="Private relay for ACME Corp repositories" | |||
| 153 | **Required:** No | 165 | **Required:** No |
| 154 | 166 | ||
| 155 | **Examples:** | 167 | **Examples:** |
| 168 | |||
| 156 | ```bash | 169 | ```bash |
| 157 | # Relative path (development) | 170 | # Relative path (development) |
| 158 | NGIT_GIT_DATA_PATH=./data/git | 171 | NGIT_GIT_DATA_PATH=./data/git |
| @@ -165,6 +178,7 @@ NGIT_GIT_DATA_PATH=/mnt/storage/git-repos | |||
| 165 | ``` | 178 | ``` |
| 166 | 179 | ||
| 167 | **Storage structure:** | 180 | **Storage structure:** |
| 181 | |||
| 168 | ``` | 182 | ``` |
| 169 | {NGIT_GIT_DATA_PATH}/ | 183 | {NGIT_GIT_DATA_PATH}/ |
| 170 | ├── {npub1}/ | 184 | ├── {npub1}/ |
| @@ -178,6 +192,7 @@ NGIT_GIT_DATA_PATH=/mnt/storage/git-repos | |||
| 178 | ``` | 192 | ``` |
| 179 | 193 | ||
| 180 | **Notes:** | 194 | **Notes:** |
| 195 | |||
| 181 | - Directory must be writable by ngit-grasp process | 196 | - Directory must be writable by ngit-grasp process |
| 182 | - Ensure sufficient disk space | 197 | - Ensure sufficient disk space |
| 183 | - Consider backup strategy | 198 | - Consider backup strategy |
| @@ -193,6 +208,7 @@ NGIT_GIT_DATA_PATH=/mnt/storage/git-repos | |||
| 193 | **Required:** No | 208 | **Required:** No |
| 194 | 209 | ||
| 195 | **Examples:** | 210 | **Examples:** |
| 211 | |||
| 196 | ```bash | 212 | ```bash |
| 197 | # Relative path (development) | 213 | # Relative path (development) |
| 198 | NGIT_RELAY_DATA_PATH=./data/relay | 214 | NGIT_RELAY_DATA_PATH=./data/relay |
| @@ -205,6 +221,7 @@ NGIT_RELAY_DATA_PATH=/mnt/ssd/relay-data | |||
| 205 | ``` | 221 | ``` |
| 206 | 222 | ||
| 207 | **Storage structure:** | 223 | **Storage structure:** |
| 224 | |||
| 208 | ``` | 225 | ``` |
| 209 | {NGIT_RELAY_DATA_PATH}/ | 226 | {NGIT_RELAY_DATA_PATH}/ |
| 210 | ├── events/ | 227 | ├── events/ |
| @@ -217,6 +234,7 @@ NGIT_RELAY_DATA_PATH=/mnt/ssd/relay-data | |||
| 217 | ``` | 234 | ``` |
| 218 | 235 | ||
| 219 | **Notes:** | 236 | **Notes:** |
| 237 | |||
| 220 | - Directory must be writable | 238 | - Directory must be writable |
| 221 | - Consider SSD for better query performance | 239 | - Consider SSD for better query performance |
| 222 | - Size grows with event count | 240 | - Size grows with event count |
| @@ -232,11 +250,13 @@ NGIT_RELAY_DATA_PATH=/mnt/ssd/relay-data | |||
| 232 | **Required:** No | 250 | **Required:** No |
| 233 | 251 | ||
| 234 | **Valid Values:** | 252 | **Valid Values:** |
| 253 | |||
| 235 | - `memory` - In-memory database (default, fastest, no persistence) | 254 | - `memory` - In-memory database (default, fastest, no persistence) |
| 236 | - `nostrdb` - NostrDB backend (persistent, optimized for Nostr) [Not yet implemented] | 255 | - `nostrdb` - NostrDB backend (persistent, optimized for Nostr) [Not yet implemented] |
| 237 | - `lmdb` - LMDB backend (persistent, general purpose) [Not yet implemented] | 256 | - `lmdb` - LMDB backend (persistent, general purpose) [Not yet implemented] |
| 238 | 257 | ||
| 239 | **Examples:** | 258 | **Examples:** |
| 259 | |||
| 240 | ```bash | 260 | ```bash |
| 241 | # Development (default, no persistence) | 261 | # Development (default, no persistence) |
| 242 | NGIT_DATABASE_BACKEND=memory | 262 | NGIT_DATABASE_BACKEND=memory |
| @@ -250,13 +270,14 @@ NGIT_DATABASE_BACKEND=lmdb | |||
| 250 | 270 | ||
| 251 | **Comparison:** | 271 | **Comparison:** |
| 252 | 272 | ||
| 253 | | Backend | Persistence | Performance | Use Case | | 273 | | Backend | Persistence | Performance | Use Case | |
| 254 | |---------|-------------|-------------|----------| | 274 | | ------- | ----------- | ----------- | ---------------------------- | |
| 255 | | memory | No | Fastest | Development, testing | | 275 | | memory | No | Fastest | Development, testing | |
| 256 | | nostrdb | Yes | High | Production (Nostr-optimized) | | 276 | | nostrdb | Yes | High | Production (Nostr-optimized) | |
| 257 | | lmdb | Yes | High | Production (general purpose) | | 277 | | lmdb | Yes | High | Production (general purpose) | |
| 258 | 278 | ||
| 259 | **Notes:** | 279 | **Notes:** |
| 280 | |||
| 260 | - `memory` backend loses all data on restart | 281 | - `memory` backend loses all data on restart |
| 261 | - NostrDB and LMDB backends will use `NGIT_RELAY_DATA_PATH` for storage | 282 | - NostrDB and LMDB backends will use `NGIT_RELAY_DATA_PATH` for storage |
| 262 | - NostrDB and LMDB are planned features, not yet available | 283 | - NostrDB and LMDB are planned features, not yet available |
| @@ -277,6 +298,7 @@ These options configure the proactive sync feature that synchronizes events from | |||
| 277 | **Required:** No | 298 | **Required:** No |
| 278 | 299 | ||
| 279 | **Examples:** | 300 | **Examples:** |
| 301 | |||
| 280 | ```bash | 302 | ```bash |
| 281 | # Sync from a public relay | 303 | # Sync from a public relay |
| 282 | NGIT_SYNC_BOOTSTRAP_RELAY_URL=wss://relay.example.com | 304 | NGIT_SYNC_BOOTSTRAP_RELAY_URL=wss://relay.example.com |
| @@ -289,11 +311,12 @@ NGIT_SYNC_BOOTSTRAP_RELAY_URL=ws://127.0.0.1:8081 | |||
| 289 | ``` | 311 | ``` |
| 290 | 312 | ||
| 291 | **Notes:** | 313 | **Notes:** |
| 314 | |||
| 292 | - Bootstrap relay provides initial sync source on startup | 315 | - Bootstrap relay provides initial sync source on startup |
| 293 | - Additional relays are **automatically discovered** from repository announcements that list our service | 316 | - Additional relays are **automatically discovered** from repository announcements that list our service |
| 294 | - Even without a bootstrap relay, sync will discover relays from stored announcements | 317 | - Even without a bootstrap relay, sync will discover relays from stored announcements |
| 295 | - Synced events go through the same validation as directly-submitted events | 318 | - Synced events go through the same validation as directly-submitted events |
| 296 | - Use WebSocket protocol (`ws://` or `wss://`) | 319 | - Use WebSocket protocol (`ws://` or `wss://`) or defaults to wss:// |
| 297 | 320 | ||
| 298 | --- | 321 | --- |
| 299 | 322 | ||
| @@ -305,6 +328,7 @@ NGIT_SYNC_BOOTSTRAP_RELAY_URL=ws://127.0.0.1:8081 | |||
| 305 | **Required:** No | 328 | **Required:** No |
| 306 | 329 | ||
| 307 | **Examples:** | 330 | **Examples:** |
| 331 | |||
| 308 | ```bash | 332 | ```bash |
| 309 | # Default: 1 hour max backoff | 333 | # Default: 1 hour max backoff |
| 310 | NGIT_SYNC_MAX_BACKOFF_SECS=3600 | 334 | NGIT_SYNC_MAX_BACKOFF_SECS=3600 |
| @@ -317,6 +341,7 @@ NGIT_SYNC_MAX_BACKOFF_SECS=7200 | |||
| 317 | ``` | 341 | ``` |
| 318 | 342 | ||
| 319 | **Notes:** | 343 | **Notes:** |
| 344 | |||
| 320 | - Backoff starts at 5 seconds and doubles on each failure | 345 | - Backoff starts at 5 seconds and doubles on each failure |
| 321 | - Capped at this maximum value | 346 | - Capped at this maximum value |
| 322 | - After 24 hours of failures, relay is marked "dead" and retried daily | 347 | - After 24 hours of failures, relay is marked "dead" and retried daily |
| @@ -332,6 +357,7 @@ NGIT_SYNC_MAX_BACKOFF_SECS=7200 | |||
| 332 | **Required:** No | 357 | **Required:** No |
| 333 | 358 | ||
| 334 | **Examples:** | 359 | **Examples:** |
| 360 | |||
| 335 | ```bash | 361 | ```bash |
| 336 | # Default: 30 second delay | 362 | # Default: 30 second delay |
| 337 | NGIT_SYNC_STARTUP_DELAY_SECS=30 | 363 | NGIT_SYNC_STARTUP_DELAY_SECS=30 |
| @@ -344,6 +370,7 @@ NGIT_SYNC_STARTUP_DELAY_SECS=60 | |||
| 344 | ``` | 370 | ``` |
| 345 | 371 | ||
| 346 | **Notes:** | 372 | **Notes:** |
| 373 | |||
| 347 | - Allows connections to stabilize before catchup | 374 | - Allows connections to stabilize before catchup |
| 348 | - Reduces load on remote relays at startup | 375 | - Reduces load on remote relays at startup |
| 349 | - Set to 0 for immediate catchup (not recommended) | 376 | - Set to 0 for immediate catchup (not recommended) |
| @@ -358,6 +385,7 @@ NGIT_SYNC_STARTUP_DELAY_SECS=60 | |||
| 358 | **Required:** No | 385 | **Required:** No |
| 359 | 386 | ||
| 360 | **Examples:** | 387 | **Examples:** |
| 388 | |||
| 361 | ```bash | 389 | ```bash |
| 362 | # Default: 10 second delay | 390 | # Default: 10 second delay |
| 363 | NGIT_SYNC_RECONNECT_DELAY_SECS=10 | 391 | NGIT_SYNC_RECONNECT_DELAY_SECS=10 |
| @@ -370,6 +398,7 @@ NGIT_SYNC_RECONNECT_DELAY_SECS=30 | |||
| 370 | ``` | 398 | ``` |
| 371 | 399 | ||
| 372 | **Notes:** | 400 | **Notes:** |
| 401 | |||
| 373 | - Prevents rate limiting from remote relays | 402 | - Prevents rate limiting from remote relays |
| 374 | - Applied after each successful reconnection | 403 | - Applied after each successful reconnection |
| 375 | - Only catches up on recent events (see lookback days) | 404 | - Only catches up on recent events (see lookback days) |
| @@ -384,6 +413,7 @@ NGIT_SYNC_RECONNECT_DELAY_SECS=30 | |||
| 384 | **Required:** No | 413 | **Required:** No |
| 385 | 414 | ||
| 386 | **Examples:** | 415 | **Examples:** |
| 416 | |||
| 387 | ```bash | 417 | ```bash |
| 388 | # Default: 3 days lookback | 418 | # Default: 3 days lookback |
| 389 | NGIT_SYNC_RECONNECT_LOOKBACK_DAYS=3 | 419 | NGIT_SYNC_RECONNECT_LOOKBACK_DAYS=3 |
| @@ -396,6 +426,7 @@ NGIT_SYNC_RECONNECT_LOOKBACK_DAYS=7 | |||
| 396 | ``` | 426 | ``` |
| 397 | 427 | ||
| 398 | **Notes:** | 428 | **Notes:** |
| 429 | |||
| 399 | - Limits catchup queries to recent events only | 430 | - Limits catchup queries to recent events only |
| 400 | - Reduces load compared to full historical sync | 431 | - Reduces load compared to full historical sync |
| 401 | - Balance between completeness and performance | 432 | - Balance between completeness and performance |
| @@ -413,6 +444,7 @@ NGIT_SYNC_RECONNECT_LOOKBACK_DAYS=7 | |||
| 413 | **Required:** No | 444 | **Required:** No |
| 414 | 445 | ||
| 415 | **Examples:** | 446 | **Examples:** |
| 447 | |||
| 416 | ```bash | 448 | ```bash |
| 417 | # Simple levels | 449 | # Simple levels |
| 418 | RUST_LOG=error # Errors only | 450 | RUST_LOG=error # Errors only |
| @@ -429,6 +461,7 @@ RUST_LOG=debug,hyper=info,tokio=warn | |||
| 429 | ``` | 461 | ``` |
| 430 | 462 | ||
| 431 | **Log levels (most to least verbose):** | 463 | **Log levels (most to least verbose):** |
| 464 | |||
| 432 | 1. `trace` - Very detailed, performance impact | 465 | 1. `trace` - Very detailed, performance impact |
| 433 | 2. `debug` - Detailed debugging information | 466 | 2. `debug` - Detailed debugging information |
| 434 | 3. `info` - General information (default) | 467 | 3. `info` - General information (default) |
| @@ -436,6 +469,7 @@ RUST_LOG=debug,hyper=info,tokio=warn | |||
| 436 | 5. `error` - Errors only | 469 | 5. `error` - Errors only |
| 437 | 470 | ||
| 438 | **Production recommendation:** | 471 | **Production recommendation:** |
| 472 | |||
| 439 | ```bash | 473 | ```bash |
| 440 | RUST_LOG=info,ngit_grasp=debug | 474 | RUST_LOG=info,ngit_grasp=debug |
| 441 | ``` | 475 | ``` |
| @@ -452,6 +486,7 @@ RUST_LOG=info,ngit_grasp=debug | |||
| 452 | **Status:** 🔜 Planned | 486 | **Status:** 🔜 Planned |
| 453 | 487 | ||
| 454 | **Examples:** | 488 | **Examples:** |
| 489 | |||
| 455 | ```bash | 490 | ```bash |
| 456 | NGIT_AUTH_REQUIRED=true # Require auth | 491 | NGIT_AUTH_REQUIRED=true # Require auth |
| 457 | NGIT_AUTH_REQUIRED=false # Public relay | 492 | NGIT_AUTH_REQUIRED=false # Public relay |
| @@ -467,6 +502,7 @@ NGIT_AUTH_REQUIRED=false # Public relay | |||
| 467 | **Status:** 🔜 Planned | 502 | **Status:** 🔜 Planned |
| 468 | 503 | ||
| 469 | **Examples:** | 504 | **Examples:** |
| 505 | |||
| 470 | ```bash | 506 | ```bash |
| 471 | NGIT_RATE_LIMIT_ENABLED=true | 507 | NGIT_RATE_LIMIT_ENABLED=true |
| 472 | NGIT_RATE_LIMIT_ENABLED=false | 508 | NGIT_RATE_LIMIT_ENABLED=false |
| @@ -491,6 +527,7 @@ RUST_LOG=debug | |||
| 491 | ``` | 527 | ``` |
| 492 | 528 | ||
| 493 | **Notes:** | 529 | **Notes:** |
| 530 | |||
| 494 | - Never commit `.env` to version control | 531 | - Never commit `.env` to version control |
| 495 | - Use `.env.example` as a template | 532 | - Use `.env.example` as a template |
| 496 | - Environment variables override `.env` values | 533 | - Environment variables override `.env` values |
| @@ -510,6 +547,7 @@ Error: Invalid configuration | |||
| 510 | ``` | 547 | ``` |
| 511 | 548 | ||
| 512 | **Validation checks:** | 549 | **Validation checks:** |
| 550 | |||
| 513 | - Required fields are present | 551 | - Required fields are present |
| 514 | - Values have correct format | 552 | - Values have correct format |
| 515 | - Paths are accessible and writable | 553 | - Paths are accessible and writable |
| @@ -533,6 +571,7 @@ RUST_LOG=info,ngit_grasp=debug | |||
| 533 | ``` | 571 | ``` |
| 534 | 572 | ||
| 535 | **Additional production considerations:** | 573 | **Additional production considerations:** |
| 574 | |||
| 536 | - Use reverse proxy (nginx, Caddy) for HTTPS | 575 | - Use reverse proxy (nginx, Caddy) for HTTPS |
| 537 | - Set up log rotation | 576 | - Set up log rotation |
| 538 | - Configure monitoring | 577 | - Configure monitoring |
| @@ -573,6 +612,7 @@ RUST_LOG=debug | |||
| 573 | ``` | 612 | ``` |
| 574 | 613 | ||
| 575 | **Testing notes:** | 614 | **Testing notes:** |
| 615 | |||
| 576 | - Use temporary directories | 616 | - Use temporary directories |
| 577 | - Use non-standard ports | 617 | - Use non-standard ports |
| 578 | - Clean up after tests | 618 | - Clean up after tests |
| @@ -590,6 +630,7 @@ When multiple configuration sources exist: | |||
| 590 | 4. **Default values** (lowest priority) | 630 | 4. **Default values** (lowest priority) |
| 591 | 631 | ||
| 592 | **Example:** | 632 | **Example:** |
| 633 | |||
| 593 | ```bash | 634 | ```bash |
| 594 | # .env file | 635 | # .env file |
| 595 | NGIT_BIND_ADDRESS=127.0.0.1:8080 | 636 | NGIT_BIND_ADDRESS=127.0.0.1:8080 |
| @@ -610,4 +651,4 @@ NGIT_BIND_ADDRESS=0.0.0.0:3000 cargo run | |||
| 610 | 651 | ||
| 611 | --- | 652 | --- |
| 612 | 653 | ||
| 613 | *Part of the [ngit-grasp reference documentation](./)* | 654 | _Part of the [ngit-grasp reference documentation](./)_ |
diff --git a/src/config.rs b/src/config.rs index 7834a3f..94035e4 100644 --- a/src/config.rs +++ b/src/config.rs | |||
| @@ -94,6 +94,8 @@ pub struct Config { | |||
| 94 | 94 | ||
| 95 | /// URL of bootstrap relay to sync from on startup (optional) | 95 | /// URL of bootstrap relay to sync from on startup (optional) |
| 96 | /// Sync discovers additional relays from repository announcements that list our service | 96 | /// Sync discovers additional relays from repository announcements that list our service |
| 97 | /// If no scheme is provided (wss:// or ws://), wss:// is assumed | ||
| 98 | /// Examples: "relay.example.com" -> "wss://relay.example.com", "wss://relay.example.com" -> unchanged | ||
| 97 | #[arg(long, env = "NGIT_SYNC_BOOTSTRAP_RELAY_URL")] | 99 | #[arg(long, env = "NGIT_SYNC_BOOTSTRAP_RELAY_URL")] |
| 98 | pub sync_bootstrap_relay_url: Option<String>, | 100 | pub sync_bootstrap_relay_url: Option<String>, |
| 99 | 101 | ||
diff --git a/src/sync/relay_connection.rs b/src/sync/relay_connection.rs index bd9ab80..21d864d 100644 --- a/src/sync/relay_connection.rs +++ b/src/sync/relay_connection.rs | |||
| @@ -79,14 +79,38 @@ pub struct RelayConnection { | |||
| 79 | } | 79 | } |
| 80 | 80 | ||
| 81 | impl RelayConnection { | 81 | impl RelayConnection { |
| 82 | /// Normalize a relay URL to include a scheme (wss:// or ws://) | ||
| 83 | /// | ||
| 84 | /// If the URL already has a scheme, it's returned as-is. | ||
| 85 | /// If no scheme is provided, wss:// is assumed (secure by default). | ||
| 86 | /// | ||
| 87 | /// # Arguments | ||
| 88 | /// * `url` - The relay URL (with or without scheme) | ||
| 89 | /// | ||
| 90 | /// # Returns | ||
| 91 | /// The normalized URL with scheme | ||
| 92 | /// | ||
| 93 | /// # Examples | ||
| 94 | /// - `"relay.example.com"` -> `"wss://relay.example.com"` | ||
| 95 | /// - `"wss://relay.example.com"` -> `"wss://relay.example.com"` | ||
| 96 | /// - `"ws://relay.example.com"` -> `"ws://relay.example.com"` | ||
| 97 | fn normalize_url(url: &str) -> String { | ||
| 98 | if url.starts_with("wss://") || url.starts_with("ws://") { | ||
| 99 | url.to_string() | ||
| 100 | } else { | ||
| 101 | format!("wss://{}", url) | ||
| 102 | } | ||
| 103 | } | ||
| 104 | |||
| 82 | /// Create a new relay connection (not yet connected) | 105 | /// Create a new relay connection (not yet connected) |
| 83 | /// | 106 | /// |
| 84 | /// # Arguments | 107 | /// # Arguments |
| 85 | /// * `url` - The relay URL to connect to (e.g., "wss://relay.example.com") | 108 | /// * `url` - The relay URL to connect to (with or without scheme, e.g., "relay.example.com" or "wss://relay.example.com") |
| 86 | pub fn new(url: String) -> Self { | 109 | pub fn new(url: String) -> Self { |
| 110 | let normalized_url = Self::normalize_url(&url); | ||
| 87 | let client = Client::default(); | 111 | let client = Client::default(); |
| 88 | Self { | 112 | Self { |
| 89 | url, | 113 | url: normalized_url, |
| 90 | client, | 114 | client, |
| 91 | database: None, | 115 | database: None, |
| 92 | nip77_warning_logged: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)), | 116 | nip77_warning_logged: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)), |
| @@ -96,12 +120,13 @@ impl RelayConnection { | |||
| 96 | /// Create a new relay connection with database for negentropy sync | 120 | /// Create a new relay connection with database for negentropy sync |
| 97 | /// | 121 | /// |
| 98 | /// # Arguments | 122 | /// # Arguments |
| 99 | /// * `url` - The relay URL to connect to (e.g., "wss://relay.example.com") | 123 | /// * `url` - The relay URL to connect to (with or without scheme, e.g., "relay.example.com" or "wss://relay.example.com") |
| 100 | /// * `database` - Shared database for local event comparison during negentropy sync | 124 | /// * `database` - Shared database for local event comparison during negentropy sync |
| 101 | pub fn new_with_database(url: String, database: SharedDatabase) -> Self { | 125 | pub fn new_with_database(url: String, database: SharedDatabase) -> Self { |
| 126 | let normalized_url = Self::normalize_url(&url); | ||
| 102 | let client = Client::default(); | 127 | let client = Client::default(); |
| 103 | Self { | 128 | Self { |
| 104 | url, | 129 | url: normalized_url, |
| 105 | client, | 130 | client, |
| 106 | database: Some(database), | 131 | database: Some(database), |
| 107 | nip77_warning_logged: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)), | 132 | nip77_warning_logged: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)), |
| @@ -491,3 +516,71 @@ impl RelayConnection { | |||
| 491 | self.database.is_some() | 516 | self.database.is_some() |
| 492 | } | 517 | } |
| 493 | } | 518 | } |
| 519 | |||
| 520 | #[cfg(test)] | ||
| 521 | mod tests { | ||
| 522 | use super::*; | ||
| 523 | |||
| 524 | #[test] | ||
| 525 | fn test_normalize_url_with_wss_scheme() { | ||
| 526 | let url = "wss://relay.example.com"; | ||
| 527 | assert_eq!(RelayConnection::normalize_url(url), "wss://relay.example.com"); | ||
| 528 | } | ||
| 529 | |||
| 530 | #[test] | ||
| 531 | fn test_normalize_url_with_ws_scheme() { | ||
| 532 | let url = "ws://relay.example.com"; | ||
| 533 | assert_eq!(RelayConnection::normalize_url(url), "ws://relay.example.com"); | ||
| 534 | } | ||
| 535 | |||
| 536 | #[test] | ||
| 537 | fn test_normalize_url_without_scheme() { | ||
| 538 | let url = "relay.example.com"; | ||
| 539 | assert_eq!(RelayConnection::normalize_url(url), "wss://relay.example.com"); | ||
| 540 | } | ||
| 541 | |||
| 542 | #[test] | ||
| 543 | fn test_normalize_url_without_scheme_with_port() { | ||
| 544 | let url = "relay.example.com:8080"; | ||
| 545 | assert_eq!(RelayConnection::normalize_url(url), "wss://relay.example.com:8080"); | ||
| 546 | } | ||
| 547 | |||
| 548 | #[test] | ||
| 549 | fn test_normalize_url_with_path() { | ||
| 550 | let url = "relay.example.com/nostr"; | ||
| 551 | assert_eq!(RelayConnection::normalize_url(url), "wss://relay.example.com/nostr"); | ||
| 552 | } | ||
| 553 | |||
| 554 | #[test] | ||
| 555 | fn test_new_normalizes_url() { | ||
| 556 | let conn = RelayConnection::new("relay.example.com".to_string()); | ||
| 557 | assert_eq!(conn.url(), "wss://relay.example.com"); | ||
| 558 | } | ||
| 559 | |||
| 560 | #[test] | ||
| 561 | fn test_new_preserves_wss_scheme() { | ||
| 562 | let conn = RelayConnection::new("wss://relay.example.com".to_string()); | ||
| 563 | assert_eq!(conn.url(), "wss://relay.example.com"); | ||
| 564 | } | ||
| 565 | |||
| 566 | #[test] | ||
| 567 | fn test_new_preserves_ws_scheme() { | ||
| 568 | let conn = RelayConnection::new("ws://relay.example.com".to_string()); | ||
| 569 | assert_eq!(conn.url(), "ws://relay.example.com"); | ||
| 570 | } | ||
| 571 | |||
| 572 | #[test] | ||
| 573 | fn test_new_with_database_normalizes_url() { | ||
| 574 | // This test just verifies the URL normalization works | ||
| 575 | // We can't easily test with_database without a real database | ||
| 576 | let conn = RelayConnection::new("git.shakespeare.diy".to_string()); | ||
| 577 | assert_eq!(conn.url(), "wss://git.shakespeare.diy"); | ||
| 578 | } | ||
| 579 | |||
| 580 | #[test] | ||
| 581 | fn test_normalize_url_real_world_example() { | ||
| 582 | // Test the exact case from the bug report | ||
| 583 | let url = "git.shakespeare.diy"; | ||
| 584 | assert_eq!(RelayConnection::normalize_url(url), "wss://git.shakespeare.diy"); | ||
| 585 | } | ||
| 586 | } | ||