diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2025-12-03 11:19:40 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2025-12-03 11:19:40 +0000 |
| commit | 2eaff5b79fed364d5eba5eb38e4b7bf76326884d (patch) | |
| tree | deacd6294f8860096ee82ee76930204efd65e33c /docs/archive/2025-11-04-evening/NEXT_SESSION_START_HERE.md | |
| parent | 57bc8cd9c021feaf08e139e8fb62800bc476068e (diff) | |
remove docs archive
Diffstat (limited to 'docs/archive/2025-11-04-evening/NEXT_SESSION_START_HERE.md')
| -rw-r--r-- | docs/archive/2025-11-04-evening/NEXT_SESSION_START_HERE.md | 650 |
1 files changed, 0 insertions, 650 deletions
diff --git a/docs/archive/2025-11-04-evening/NEXT_SESSION_START_HERE.md b/docs/archive/2025-11-04-evening/NEXT_SESSION_START_HERE.md deleted file mode 100644 index 890d5ed..0000000 --- a/docs/archive/2025-11-04-evening/NEXT_SESSION_START_HERE.md +++ /dev/null | |||
| @@ -1,650 +0,0 @@ | |||
| 1 | # Next Session Start Here | ||
| 2 | |||
| 3 | **Date:** November 4, 2025 | ||
| 4 | **Purpose:** Quick start guide for next development session | ||
| 5 | **Status:** Ready for actix-web integration | ||
| 6 | |||
| 7 | --- | ||
| 8 | |||
| 9 | ## 🎯 Immediate Goal | ||
| 10 | |||
| 11 | **Integrate actix-web to serve both Nostr relay (WebSocket) and Git HTTP on the SAME PORT.** | ||
| 12 | |||
| 13 | This is the critical architectural fix needed to match GRASP-01 requirements and ngit-relay's design. | ||
| 14 | |||
| 15 | --- | ||
| 16 | |||
| 17 | ## 🚨 Critical Understanding | ||
| 18 | |||
| 19 | ### Single Port Architecture (from ../ngit-relay) | ||
| 20 | |||
| 21 | ``` | ||
| 22 | ┌─────────────────────────────────────┐ | ||
| 23 | │ Single Port (8080) │ | ||
| 24 | │ │ | ||
| 25 | │ ┌─────────────────────────────┐ │ | ||
| 26 | │ │ HTTP/WebSocket Router │ │ | ||
| 27 | │ │ (nginx in ngit-relay) │ │ | ||
| 28 | │ │ (actix-web in ngit-grasp) │ │ | ||
| 29 | │ └──────────┬──────────────────┘ │ | ||
| 30 | │ │ │ | ||
| 31 | │ ┌──────┴──────┐ │ | ||
| 32 | │ ↓ ↓ │ | ||
| 33 | │ Git HTTP Nostr Relay │ | ||
| 34 | │ /<npub>/ / (WebSocket) │ | ||
| 35 | │ <id>.git │ | ||
| 36 | └─────────────────────────────────────┘ | ||
| 37 | ``` | ||
| 38 | |||
| 39 | **Key Points:** | ||
| 40 | 1. ONE port listens for all traffic | ||
| 41 | 2. Router inspects path and decides where to send request | ||
| 42 | 3. Git paths go to Git handler | ||
| 43 | 4. Everything else goes to Nostr relay (with WebSocket upgrade) | ||
| 44 | 5. CORS headers on ALL responses | ||
| 45 | |||
| 46 | --- | ||
| 47 | |||
| 48 | ## 📋 Step-by-Step Implementation Plan | ||
| 49 | |||
| 50 | ### Step 1: Add actix-web Dependencies | ||
| 51 | |||
| 52 | **File:** `Cargo.toml` | ||
| 53 | |||
| 54 | ```toml | ||
| 55 | [dependencies] | ||
| 56 | # Existing... | ||
| 57 | actix-web = "4" | ||
| 58 | actix-cors = "0.7" | ||
| 59 | actix-ws = "0.3" # For WebSocket support | ||
| 60 | |||
| 61 | # Git HTTP backend | ||
| 62 | git-http-backend = "0.2" # Check latest version | ||
| 63 | ``` | ||
| 64 | |||
| 65 | **Why:** | ||
| 66 | - `actix-web` - HTTP framework with routing | ||
| 67 | - `actix-cors` - Easy CORS middleware | ||
| 68 | - `actix-ws` - WebSocket support | ||
| 69 | - `git-http-backend` - Git Smart HTTP protocol | ||
| 70 | |||
| 71 | ### Step 2: Create HTTP Router Module | ||
| 72 | |||
| 73 | **File:** `src/http/mod.rs` (NEW) | ||
| 74 | |||
| 75 | ```rust | ||
| 76 | //! HTTP server with routing for Git and Nostr | ||
| 77 | |||
| 78 | use actix_web::{web, App, HttpServer}; | ||
| 79 | use actix_cors::Cors; | ||
| 80 | |||
| 81 | pub mod git; | ||
| 82 | pub mod nostr; | ||
| 83 | |||
| 84 | pub async fn run_server(config: Config, storage: Storage) -> Result<()> { | ||
| 85 | let bind_addr = config.bind_address.clone(); | ||
| 86 | |||
| 87 | HttpServer::new(move || { | ||
| 88 | App::new() | ||
| 89 | // CORS middleware (GRASP-01 requirement) | ||
| 90 | .wrap( | ||
| 91 | Cors::default() | ||
| 92 | .allow_any_origin() | ||
| 93 | .allowed_methods(vec!["GET", "POST"]) | ||
| 94 | .allowed_headers(vec!["Content-Type"]) | ||
| 95 | .max_age(3600) | ||
| 96 | ) | ||
| 97 | // Git HTTP routes | ||
| 98 | .service( | ||
| 99 | web::scope("/{npub}/{repo}") | ||
| 100 | .guard(guard::fn_guard(|ctx| { | ||
| 101 | // Only match *.git paths | ||
| 102 | ctx.head().uri.path().ends_with(".git") | ||
| 103 | })) | ||
| 104 | .route("", web::get().to(git::handle_git_request)) | ||
| 105 | .route("/{tail:.*}", web::to(git::handle_git_request)) | ||
| 106 | ) | ||
| 107 | // Nostr relay (WebSocket at /) | ||
| 108 | .route("/", web::get().to(nostr::handle_websocket)) | ||
| 109 | // Static files (optional) | ||
| 110 | .route("/", web::get().to(nostr::handle_http_root)) | ||
| 111 | }) | ||
| 112 | .bind(bind_addr)? | ||
| 113 | .run() | ||
| 114 | .await?; | ||
| 115 | |||
| 116 | Ok(()) | ||
| 117 | } | ||
| 118 | ``` | ||
| 119 | |||
| 120 | **Why:** | ||
| 121 | - Single HTTP server listening on one port | ||
| 122 | - Routes by URL path pattern | ||
| 123 | - CORS applied to all routes | ||
| 124 | - Git paths (*.git) go to Git handler | ||
| 125 | - Root path (/) handles WebSocket upgrade for Nostr | ||
| 126 | |||
| 127 | ### Step 3: Create Git HTTP Handler | ||
| 128 | |||
| 129 | **File:** `src/http/git.rs` (NEW) | ||
| 130 | |||
| 131 | ```rust | ||
| 132 | //! Git Smart HTTP handler | ||
| 133 | |||
| 134 | use actix_web::{web, HttpRequest, HttpResponse, Result}; | ||
| 135 | use git_http_backend::{GitHttpBackend, Method}; | ||
| 136 | |||
| 137 | pub async fn handle_git_request( | ||
| 138 | req: HttpRequest, | ||
| 139 | body: web::Bytes, | ||
| 140 | path: web::Path<(String, String)>, | ||
| 141 | ) -> Result<HttpResponse> { | ||
| 142 | let (npub, repo) = path.into_inner(); | ||
| 143 | |||
| 144 | // Construct repository path | ||
| 145 | let repo_path = format!("{}/{}/{}", | ||
| 146 | config.git_data_path, npub, repo); | ||
| 147 | |||
| 148 | // Check if repository exists | ||
| 149 | if !std::path::Path::new(&repo_path).exists() { | ||
| 150 | return Ok(HttpResponse::NotFound() | ||
| 151 | .body("Repository not found")); | ||
| 152 | } | ||
| 153 | |||
| 154 | // Parse Git HTTP request | ||
| 155 | let method = match *req.method() { | ||
| 156 | actix_web::http::Method::GET => Method::Get, | ||
| 157 | actix_web::http::Method::POST => Method::Post, | ||
| 158 | _ => return Ok(HttpResponse::MethodNotAllowed().finish()), | ||
| 159 | }; | ||
| 160 | |||
| 161 | // Use git-http-backend to handle request | ||
| 162 | let backend = GitHttpBackend::new(&repo_path); | ||
| 163 | let response = backend.handle(method, req.path(), &body)?; | ||
| 164 | |||
| 165 | // Convert to actix HttpResponse | ||
| 166 | Ok(HttpResponse::Ok() | ||
| 167 | .content_type(response.content_type) | ||
| 168 | .body(response.body)) | ||
| 169 | } | ||
| 170 | ``` | ||
| 171 | |||
| 172 | **Why:** | ||
| 173 | - Handles Git Smart HTTP protocol | ||
| 174 | - Serves from `{GIT_DATA_PATH}/{npub}/{repo}.git` | ||
| 175 | - Uses `git-http-backend` crate for protocol details | ||
| 176 | - Returns 404 if repo doesn't exist | ||
| 177 | |||
| 178 | ### Step 4: Create Nostr WebSocket Handler | ||
| 179 | |||
| 180 | **File:** `src/http/nostr.rs` (NEW) | ||
| 181 | |||
| 182 | ```rust | ||
| 183 | //! Nostr relay WebSocket handler | ||
| 184 | |||
| 185 | use actix_web::{web, HttpRequest, HttpResponse, Result}; | ||
| 186 | use actix_ws::Message; | ||
| 187 | |||
| 188 | pub async fn handle_websocket( | ||
| 189 | req: HttpRequest, | ||
| 190 | stream: web::Payload, | ||
| 191 | storage: web::Data<Storage>, | ||
| 192 | ) -> Result<HttpResponse> { | ||
| 193 | // Upgrade to WebSocket | ||
| 194 | let (response, mut session, mut msg_stream) = actix_ws::handle(&req, stream)?; | ||
| 195 | |||
| 196 | // Spawn task to handle WebSocket messages | ||
| 197 | actix_web::rt::spawn(async move { | ||
| 198 | while let Some(Ok(msg)) = msg_stream.next().await { | ||
| 199 | match msg { | ||
| 200 | Message::Text(text) => { | ||
| 201 | // Handle Nostr message (EVENT, REQ, CLOSE) | ||
| 202 | let responses = handle_nostr_message(&text, &storage).await; | ||
| 203 | for response in responses { | ||
| 204 | session.text(response).await.ok(); | ||
| 205 | } | ||
| 206 | } | ||
| 207 | Message::Ping(bytes) => { | ||
| 208 | session.pong(&bytes).await.ok(); | ||
| 209 | } | ||
| 210 | Message::Close(_) => break, | ||
| 211 | _ => {} | ||
| 212 | } | ||
| 213 | } | ||
| 214 | }); | ||
| 215 | |||
| 216 | Ok(response) | ||
| 217 | } | ||
| 218 | |||
| 219 | pub async fn handle_http_root() -> Result<HttpResponse> { | ||
| 220 | // Serve static HTML for browsers | ||
| 221 | Ok(HttpResponse::Ok() | ||
| 222 | .content_type("text/html") | ||
| 223 | .body("<html><body><h1>ngit-grasp</h1><p>Nostr relay at ws://</p></body></html>")) | ||
| 224 | } | ||
| 225 | ``` | ||
| 226 | |||
| 227 | **Why:** | ||
| 228 | - Handles WebSocket upgrade at `/` | ||
| 229 | - Reuses existing Nostr message handling logic | ||
| 230 | - Returns HTML for browsers (non-WebSocket requests) | ||
| 231 | |||
| 232 | ### Step 5: Update main.rs | ||
| 233 | |||
| 234 | **File:** `src/main.rs` | ||
| 235 | |||
| 236 | ```rust | ||
| 237 | use anyhow::Result; | ||
| 238 | use tracing::{info, Level}; | ||
| 239 | use tracing_subscriber::FmtSubscriber; | ||
| 240 | |||
| 241 | mod config; | ||
| 242 | mod http; // NEW | ||
| 243 | mod nostr; | ||
| 244 | mod storage; | ||
| 245 | |||
| 246 | use config::Config; | ||
| 247 | |||
| 248 | #[tokio::main] | ||
| 249 | async fn main() -> Result<()> { | ||
| 250 | // Initialize tracing | ||
| 251 | let subscriber = FmtSubscriber::builder() | ||
| 252 | .with_max_level(Level::DEBUG) | ||
| 253 | .finish(); | ||
| 254 | tracing::subscriber::set_global_default(subscriber)?; | ||
| 255 | |||
| 256 | info!("Starting ngit-grasp..."); | ||
| 257 | |||
| 258 | // Load configuration | ||
| 259 | let config = Config::from_env()?; | ||
| 260 | info!("Configuration: {}", config.bind_address); | ||
| 261 | |||
| 262 | // Initialize storage | ||
| 263 | let storage = storage::Storage::new(&config)?; | ||
| 264 | info!("Storage initialized at: {}", config.relay_data_path); | ||
| 265 | |||
| 266 | // Start HTTP server (Git + Nostr on same port) | ||
| 267 | info!("Starting server on {}", config.bind_address); | ||
| 268 | http::run_server(config, storage).await?; | ||
| 269 | |||
| 270 | Ok(()) | ||
| 271 | } | ||
| 272 | ``` | ||
| 273 | |||
| 274 | **Why:** | ||
| 275 | - Replaces separate relay with unified HTTP server | ||
| 276 | - Single entry point for all services | ||
| 277 | - Simpler architecture | ||
| 278 | |||
| 279 | ### Step 6: Update Configuration | ||
| 280 | |||
| 281 | **File:** `src/config.rs` | ||
| 282 | |||
| 283 | Add field for Git data path: | ||
| 284 | |||
| 285 | ```rust | ||
| 286 | pub struct Config { | ||
| 287 | pub bind_address: String, | ||
| 288 | pub domain: String, | ||
| 289 | pub relay_data_path: String, | ||
| 290 | pub git_data_path: String, // NEW | ||
| 291 | // ... other fields | ||
| 292 | } | ||
| 293 | |||
| 294 | impl Config { | ||
| 295 | pub fn from_env() -> Result<Self> { | ||
| 296 | Ok(Config { | ||
| 297 | bind_address: env::var("NGIT_BIND_ADDRESS") | ||
| 298 | .unwrap_or_else(|_| "127.0.0.1:8080".to_string()), | ||
| 299 | domain: env::var("NGIT_DOMAIN")?, | ||
| 300 | relay_data_path: env::var("NGIT_RELAY_DATA_PATH") | ||
| 301 | .unwrap_or_else(|_| "./data/relay".to_string()), | ||
| 302 | git_data_path: env::var("NGIT_GIT_DATA_PATH") // NEW | ||
| 303 | .unwrap_or_else(|_| "./data/repos".to_string()), | ||
| 304 | // ... | ||
| 305 | }) | ||
| 306 | } | ||
| 307 | } | ||
| 308 | ``` | ||
| 309 | |||
| 310 | **File:** `.env.example` | ||
| 311 | |||
| 312 | ```bash | ||
| 313 | # Service Configuration | ||
| 314 | NGIT_DOMAIN=example.com | ||
| 315 | NGIT_BIND_ADDRESS=127.0.0.1:8080 | ||
| 316 | |||
| 317 | # Relay Information | ||
| 318 | NGIT_RELAY_NAME="ngit-grasp instance" | ||
| 319 | NGIT_RELAY_DESCRIPTION="Rust GRASP implementation" | ||
| 320 | NGIT_OWNER_NPUB="npub1..." | ||
| 321 | |||
| 322 | # Storage Paths | ||
| 323 | NGIT_GIT_DATA_PATH=./data/repos | ||
| 324 | NGIT_RELAY_DATA_PATH=./data/relay | ||
| 325 | |||
| 326 | # Logging | ||
| 327 | NGIT_LOG_LEVEL=INFO | ||
| 328 | RUST_LOG=info | ||
| 329 | ``` | ||
| 330 | |||
| 331 | ### Step 7: Update Tests | ||
| 332 | |||
| 333 | **File:** `tests/common/relay.rs` | ||
| 334 | |||
| 335 | Update `start_with_port` to pass domain correctly: | ||
| 336 | |||
| 337 | ```rust | ||
| 338 | pub async fn start_with_port(port: u16) -> Self { | ||
| 339 | let bind_address = format!("127.0.0.1:{}", port); | ||
| 340 | let domain = format!("127.0.0.1:{}", port); // NEW | ||
| 341 | let url = format!("ws://{}", domain); | ||
| 342 | |||
| 343 | let process = Command::new(&binary_path) | ||
| 344 | .env("NGIT_BIND_ADDRESS", &bind_address) | ||
| 345 | .env("NGIT_DOMAIN", &domain) // UPDATED | ||
| 346 | .env("NGIT_GIT_DATA_PATH", "./test-data/repos") // NEW | ||
| 347 | .env("NGIT_RELAY_DATA_PATH", "./test-data/relay") // NEW | ||
| 348 | .env("RUST_LOG", "warn") | ||
| 349 | .stdout(Stdio::null()) | ||
| 350 | .stderr(Stdio::null()) | ||
| 351 | .spawn() | ||
| 352 | .expect("Failed to start relay process"); | ||
| 353 | |||
| 354 | // ... rest of method | ||
| 355 | } | ||
| 356 | ``` | ||
| 357 | |||
| 358 | **Why:** | ||
| 359 | - Domain must match bind address for announcement validation | ||
| 360 | - Separate test data directories | ||
| 361 | - Clean up test data after tests | ||
| 362 | |||
| 363 | ### Step 8: Add Git HTTP Tests | ||
| 364 | |||
| 365 | **File:** `tests/grasp01_git_http.rs` (NEW) | ||
| 366 | |||
| 367 | ```rust | ||
| 368 | //! GRASP-01 Git HTTP Integration Tests | ||
| 369 | //! | ||
| 370 | //! Reference: ../grasp/01.md lines 15-40 | ||
| 371 | //! | ||
| 372 | //! These tests verify Git Smart HTTP service requirements: | ||
| 373 | //! - Serve repos at /<npub>/<identifier>.git | ||
| 374 | //! - Accept pushes matching state announcements | ||
| 375 | //! - CORS support | ||
| 376 | |||
| 377 | mod common; | ||
| 378 | |||
| 379 | use common::TestRelay; | ||
| 380 | use std::process::Command; | ||
| 381 | |||
| 382 | #[tokio::test] | ||
| 383 | async fn test_git_clone_basic() { | ||
| 384 | // Reference: ../grasp/01.md line 15 | ||
| 385 | // MUST serve git repository via unauthenticated git smart http service | ||
| 386 | |||
| 387 | let relay = TestRelay::start().await; | ||
| 388 | let domain = relay.domain(); | ||
| 389 | |||
| 390 | // TODO: Create test repository announcement | ||
| 391 | // TODO: Clone via git clone http://{domain}/{npub}/{id}.git | ||
| 392 | |||
| 393 | relay.stop().await; | ||
| 394 | } | ||
| 395 | |||
| 396 | #[tokio::test] | ||
| 397 | async fn test_cors_headers() { | ||
| 398 | // Reference: ../grasp/01.md lines 32-40 | ||
| 399 | // MUST include CORS headers on all responses | ||
| 400 | |||
| 401 | let relay = TestRelay::start().await; | ||
| 402 | let url = format!("http://{}/", relay.domain()); | ||
| 403 | |||
| 404 | let response = reqwest::get(&url).await.unwrap(); | ||
| 405 | |||
| 406 | // Check CORS headers | ||
| 407 | assert_eq!( | ||
| 408 | response.headers().get("access-control-allow-origin"), | ||
| 409 | Some(&"*".parse().unwrap()) | ||
| 410 | ); | ||
| 411 | |||
| 412 | relay.stop().await; | ||
| 413 | } | ||
| 414 | ``` | ||
| 415 | |||
| 416 | **Why:** | ||
| 417 | - Tests reference GRASP protocol line numbers | ||
| 418 | - Verifies Git HTTP functionality | ||
| 419 | - Checks CORS compliance | ||
| 420 | |||
| 421 | --- | ||
| 422 | |||
| 423 | ## 🔍 Verification Steps | ||
| 424 | |||
| 425 | After implementing the above: | ||
| 426 | |||
| 427 | ### 1. Build and Run | ||
| 428 | |||
| 429 | ```bash | ||
| 430 | # Build | ||
| 431 | cargo build | ||
| 432 | |||
| 433 | # Run server | ||
| 434 | NGIT_DOMAIN=localhost:8080 \ | ||
| 435 | NGIT_BIND_ADDRESS=127.0.0.1:8080 \ | ||
| 436 | NGIT_GIT_DATA_PATH=./data/repos \ | ||
| 437 | NGIT_RELAY_DATA_PATH=./data/relay \ | ||
| 438 | cargo run | ||
| 439 | ``` | ||
| 440 | |||
| 441 | ### 2. Test Nostr Relay (WebSocket) | ||
| 442 | |||
| 443 | ```bash | ||
| 444 | # In another terminal | ||
| 445 | cd grasp-audit | ||
| 446 | cargo run -- --url ws://localhost:8080 | ||
| 447 | ``` | ||
| 448 | |||
| 449 | **Expected:** NIP-01 smoke tests should pass | ||
| 450 | |||
| 451 | ### 3. Test Git HTTP (Manual) | ||
| 452 | |||
| 453 | ```bash | ||
| 454 | # Create test repository | ||
| 455 | mkdir -p ./data/repos/npub1test/test-repo.git | ||
| 456 | cd ./data/repos/npub1test/test-repo.git | ||
| 457 | git init --bare | ||
| 458 | |||
| 459 | # Try to clone | ||
| 460 | git clone http://localhost:8080/npub1test/test-repo.git | ||
| 461 | ``` | ||
| 462 | |||
| 463 | **Expected:** Should clone successfully (even if empty) | ||
| 464 | |||
| 465 | ### 4. Test CORS | ||
| 466 | |||
| 467 | ```bash | ||
| 468 | curl -v http://localhost:8080/ -H "Origin: https://example.com" | ||
| 469 | ``` | ||
| 470 | |||
| 471 | **Expected:** Response should include: | ||
| 472 | ``` | ||
| 473 | access-control-allow-origin: * | ||
| 474 | access-control-allow-methods: GET, POST | ||
| 475 | access-control-allow-headers: Content-Type | ||
| 476 | ``` | ||
| 477 | |||
| 478 | ### 5. Run Integration Tests | ||
| 479 | |||
| 480 | ```bash | ||
| 481 | # All tests | ||
| 482 | cargo test | ||
| 483 | |||
| 484 | # Just NIP-01 | ||
| 485 | cargo test --test nip01_compliance | ||
| 486 | |||
| 487 | # Just Git HTTP (when implemented) | ||
| 488 | cargo test --test grasp01_git_http | ||
| 489 | ``` | ||
| 490 | |||
| 491 | **Expected:** All tests pass | ||
| 492 | |||
| 493 | --- | ||
| 494 | |||
| 495 | ## 🐛 Common Issues & Solutions | ||
| 496 | |||
| 497 | ### Issue: Port Already in Use | ||
| 498 | |||
| 499 | **Symptom:** "Address already in use" error | ||
| 500 | |||
| 501 | **Solution:** | ||
| 502 | ```bash | ||
| 503 | # Find process using port | ||
| 504 | lsof -i :8080 | ||
| 505 | |||
| 506 | # Kill it | ||
| 507 | kill -9 <PID> | ||
| 508 | |||
| 509 | # Or use different port | ||
| 510 | NGIT_BIND_ADDRESS=127.0.0.1:8081 cargo run | ||
| 511 | ``` | ||
| 512 | |||
| 513 | ### Issue: WebSocket Upgrade Fails | ||
| 514 | |||
| 515 | **Symptom:** WebSocket connection refused | ||
| 516 | |||
| 517 | **Solution:** | ||
| 518 | - Check actix-web WebSocket handling | ||
| 519 | - Verify `Upgrade: websocket` header is present | ||
| 520 | - Check actix-ws is properly configured | ||
| 521 | |||
| 522 | ### Issue: Git Clone Fails | ||
| 523 | |||
| 524 | **Symptom:** "repository not found" or protocol error | ||
| 525 | |||
| 526 | **Solution:** | ||
| 527 | - Verify repository exists at correct path | ||
| 528 | - Check git-http-backend configuration | ||
| 529 | - Ensure repository is bare (`git init --bare`) | ||
| 530 | - Check file permissions | ||
| 531 | |||
| 532 | ### Issue: CORS Headers Missing | ||
| 533 | |||
| 534 | **Symptom:** Browser console shows CORS error | ||
| 535 | |||
| 536 | **Solution:** | ||
| 537 | - Verify CORS middleware is applied | ||
| 538 | - Check middleware order (CORS should be first) | ||
| 539 | - Test with curl to see actual headers | ||
| 540 | |||
| 541 | --- | ||
| 542 | |||
| 543 | ## 📚 Key Resources | ||
| 544 | |||
| 545 | ### GRASP Protocol | ||
| 546 | - `../grasp/01.md` - **THE SPEC** - Read this first! | ||
| 547 | - Lines 1-14: Nostr relay requirements | ||
| 548 | - Lines 15-31: Git HTTP service requirements | ||
| 549 | - Lines 32-40: CORS requirements | ||
| 550 | |||
| 551 | ### Reference Implementation | ||
| 552 | - `../ngit-relay/src/nginx.conf` - **ROUTING PATTERN** | ||
| 553 | - Lines 8-13: Single port listener | ||
| 554 | - Lines 15-48: Git HTTP routing | ||
| 555 | - Lines 50-94: Nostr relay routing | ||
| 556 | - `../ngit-relay/docker-compose.yml` - Port configuration | ||
| 557 | - `../ngit-relay/.env.example` - Environment variables | ||
| 558 | |||
| 559 | ### actix-web Documentation | ||
| 560 | - [Routing](https://actix.rs/docs/url-dispatch/) | ||
| 561 | - [WebSocket](https://actix.rs/docs/websockets/) | ||
| 562 | - [CORS](https://docs.rs/actix-cors/) | ||
| 563 | |||
| 564 | ### git-http-backend Crate | ||
| 565 | - [Docs](https://docs.rs/git-http-backend/) | ||
| 566 | - [Examples](https://github.com/w4/git-http-backend/tree/master/examples) | ||
| 567 | |||
| 568 | --- | ||
| 569 | |||
| 570 | ## ✅ Success Criteria | ||
| 571 | |||
| 572 | You'll know this step is complete when: | ||
| 573 | |||
| 574 | 1. ✅ Server starts on single port | ||
| 575 | 2. ✅ WebSocket connects at `ws://localhost:8080/` | ||
| 576 | 3. ✅ NIP-01 smoke tests pass | ||
| 577 | 4. ✅ Can clone Git repo at `http://localhost:8080/npub.../repo.git` | ||
| 578 | 5. ✅ CORS headers present on all responses | ||
| 579 | 6. ✅ OPTIONS requests return 204 | ||
| 580 | 7. ✅ All integration tests pass | ||
| 581 | |||
| 582 | --- | ||
| 583 | |||
| 584 | ## 🎯 After This Step | ||
| 585 | |||
| 586 | Once actix-web integration is complete: | ||
| 587 | |||
| 588 | 1. **Repository Provisioning** | ||
| 589 | - Create repos when announcements received | ||
| 590 | - Initialize bare repositories | ||
| 591 | - Set up directory structure | ||
| 592 | |||
| 593 | 2. **Push Authorization** | ||
| 594 | - Intercept git-receive-pack | ||
| 595 | - Validate against state announcements | ||
| 596 | - Handle maintainer sets | ||
| 597 | |||
| 598 | 3. **Full GRASP-01 Compliance** | ||
| 599 | - All tests passing | ||
| 600 | - Ready for production testing | ||
| 601 | |||
| 602 | --- | ||
| 603 | |||
| 604 | ## 💡 Tips | ||
| 605 | |||
| 606 | 1. **Start Simple** | ||
| 607 | - Get basic HTTP routing working first | ||
| 608 | - Add WebSocket support second | ||
| 609 | - Add Git HTTP last | ||
| 610 | |||
| 611 | 2. **Test Incrementally** | ||
| 612 | - Test each component as you add it | ||
| 613 | - Don't wait until everything is done | ||
| 614 | |||
| 615 | 3. **Use curl for Debugging** | ||
| 616 | ```bash | ||
| 617 | # Test HTTP | ||
| 618 | curl -v http://localhost:8080/ | ||
| 619 | |||
| 620 | # Test CORS | ||
| 621 | curl -v http://localhost:8080/ -H "Origin: https://example.com" | ||
| 622 | |||
| 623 | # Test Git info/refs | ||
| 624 | curl http://localhost:8080/npub.../repo.git/info/refs?service=git-upload-pack | ||
| 625 | ``` | ||
| 626 | |||
| 627 | 4. **Check ngit-relay for Patterns** | ||
| 628 | - nginx.conf shows exact routing logic | ||
| 629 | - Copy the pattern, not the implementation | ||
| 630 | |||
| 631 | 5. **Keep Tests Running** | ||
| 632 | ```bash | ||
| 633 | # In one terminal | ||
| 634 | cargo watch -x 'test --test nip01_compliance' | ||
| 635 | |||
| 636 | # Make changes, tests auto-run | ||
| 637 | ``` | ||
| 638 | |||
| 639 | --- | ||
| 640 | |||
| 641 | **Ready to Start?** Begin with Step 1 (Add Dependencies) | ||
| 642 | |||
| 643 | **Questions?** Check `work/current_status.md` for context | ||
| 644 | |||
| 645 | **Stuck?** Review `../ngit-relay/src/nginx.conf` for routing pattern | ||
| 646 | |||
| 647 | --- | ||
| 648 | |||
| 649 | **Last Updated:** November 4, 2025 | ||
| 650 | **Next Update:** After actix-web integration complete | ||