diff options
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/GETTING_STARTED.md | 437 |
1 files changed, 0 insertions, 437 deletions
diff --git a/docs/GETTING_STARTED.md b/docs/GETTING_STARTED.md deleted file mode 100644 index 7fea590..0000000 --- a/docs/GETTING_STARTED.md +++ /dev/null | |||
| @@ -1,437 +0,0 @@ | |||
| 1 | # Getting Started with Implementation | ||
| 2 | |||
| 3 | This guide helps you start implementing ngit-grasp based on the architecture design. | ||
| 4 | |||
| 5 | ## Prerequisites | ||
| 6 | |||
| 7 | - Rust 1.75 or later | ||
| 8 | - Git 2.x | ||
| 9 | - Basic understanding of async Rust (tokio) | ||
| 10 | - Familiarity with actix-web (helpful) | ||
| 11 | - Understanding of Nostr basics (helpful) | ||
| 12 | |||
| 13 | ## Step 1: Initialize Cargo Project | ||
| 14 | |||
| 15 | ```bash | ||
| 16 | # Create new binary project | ||
| 17 | cargo init --name ngit-grasp | ||
| 18 | |||
| 19 | # Or if already created: | ||
| 20 | cargo build | ||
| 21 | ``` | ||
| 22 | |||
| 23 | ## Step 2: Add Dependencies | ||
| 24 | |||
| 25 | Edit `Cargo.toml`: | ||
| 26 | |||
| 27 | ```toml | ||
| 28 | [package] | ||
| 29 | name = "ngit-grasp" | ||
| 30 | version = "0.1.0" | ||
| 31 | edition = "2021" | ||
| 32 | rust-version = "1.75" | ||
| 33 | |||
| 34 | [dependencies] | ||
| 35 | # HTTP Server | ||
| 36 | actix-web = "4" | ||
| 37 | actix-cors = "0.7" | ||
| 38 | |||
| 39 | # Async Runtime | ||
| 40 | tokio = { version = "1", features = ["full"] } | ||
| 41 | |||
| 42 | # Git Protocol | ||
| 43 | git-http-backend = "0.1.3" | ||
| 44 | |||
| 45 | # Nostr | ||
| 46 | nostr-sdk = { version = "0.43", features = ["all-nips"] } | ||
| 47 | nostr-relay-builder = "0.43" | ||
| 48 | |||
| 49 | # Serialization | ||
| 50 | serde = { version = "1", features = ["derive"] } | ||
| 51 | serde_json = "1" | ||
| 52 | |||
| 53 | # Error Handling | ||
| 54 | anyhow = "1" | ||
| 55 | thiserror = "1" | ||
| 56 | |||
| 57 | # Logging | ||
| 58 | tracing = "0.1" | ||
| 59 | tracing-subscriber = { version = "0.3", features = ["env-filter"] } | ||
| 60 | |||
| 61 | # Environment | ||
| 62 | dotenv = "0.15" | ||
| 63 | |||
| 64 | # Utilities | ||
| 65 | async-trait = "0.1" | ||
| 66 | futures = "0.3" | ||
| 67 | bytes = "1" | ||
| 68 | |||
| 69 | [dev-dependencies] | ||
| 70 | tokio-test = "0.4" | ||
| 71 | ``` | ||
| 72 | |||
| 73 | ## Step 3: Project Structure | ||
| 74 | |||
| 75 | Create the directory structure: | ||
| 76 | |||
| 77 | ```bash | ||
| 78 | mkdir -p src/{git,nostr,storage} | ||
| 79 | mkdir -p tests/{integration,fixtures} | ||
| 80 | mkdir -p data/{git,relay} | ||
| 81 | ``` | ||
| 82 | |||
| 83 | ## Step 4: Configuration Module | ||
| 84 | |||
| 85 | Create `src/config.rs`: | ||
| 86 | |||
| 87 | ```rust | ||
| 88 | use anyhow::Result; | ||
| 89 | use std::env; | ||
| 90 | use std::net::SocketAddr; | ||
| 91 | use std::path::PathBuf; | ||
| 92 | |||
| 93 | #[derive(Debug, Clone)] | ||
| 94 | pub struct Config { | ||
| 95 | pub domain: String, | ||
| 96 | pub owner_npub: String, | ||
| 97 | pub relay_name: String, | ||
| 98 | pub relay_description: String, | ||
| 99 | pub git_data_path: PathBuf, | ||
| 100 | pub relay_data_path: PathBuf, | ||
| 101 | pub bind_address: SocketAddr, | ||
| 102 | } | ||
| 103 | |||
| 104 | impl Config { | ||
| 105 | pub fn from_env() -> Result<Self> { | ||
| 106 | dotenv::dotenv().ok(); | ||
| 107 | |||
| 108 | Ok(Config { | ||
| 109 | domain: env::var("NGIT_DOMAIN")?, | ||
| 110 | owner_npub: env::var("NGIT_OWNER_NPUB")?, | ||
| 111 | relay_name: env::var("NGIT_RELAY_NAME")?, | ||
| 112 | relay_description: env::var("NGIT_RELAY_DESCRIPTION")?, | ||
| 113 | git_data_path: PathBuf::from( | ||
| 114 | env::var("NGIT_GIT_DATA_PATH") | ||
| 115 | .unwrap_or_else(|_| "./data/git".to_string()) | ||
| 116 | ), | ||
| 117 | relay_data_path: PathBuf::from( | ||
| 118 | env::var("NGIT_RELAY_DATA_PATH") | ||
| 119 | .unwrap_or_else(|_| "./data/relay".to_string()) | ||
| 120 | ), | ||
| 121 | bind_address: env::var("NGIT_BIND_ADDRESS") | ||
| 122 | .unwrap_or_else(|_| "127.0.0.1:8080".to_string()) | ||
| 123 | .parse()?, | ||
| 124 | }) | ||
| 125 | } | ||
| 126 | } | ||
| 127 | ``` | ||
| 128 | |||
| 129 | ## Step 5: Core Types | ||
| 130 | |||
| 131 | Create `src/git/types.rs`: | ||
| 132 | |||
| 133 | ```rust | ||
| 134 | use serde::{Deserialize, Serialize}; | ||
| 135 | |||
| 136 | #[derive(Debug, Clone, Serialize, Deserialize)] | ||
| 137 | pub struct RefUpdate { | ||
| 138 | pub old_oid: String, | ||
| 139 | pub new_oid: String, | ||
| 140 | pub ref_name: String, | ||
| 141 | } | ||
| 142 | |||
| 143 | impl RefUpdate { | ||
| 144 | pub fn is_create(&self) -> bool { | ||
| 145 | self.old_oid == "0000000000000000000000000000000000000000" | ||
| 146 | } | ||
| 147 | |||
| 148 | pub fn is_delete(&self) -> bool { | ||
| 149 | self.new_oid == "0000000000000000000000000000000000000000" | ||
| 150 | } | ||
| 151 | |||
| 152 | pub fn is_update(&self) -> bool { | ||
| 153 | !self.is_create() && !self.is_delete() | ||
| 154 | } | ||
| 155 | } | ||
| 156 | |||
| 157 | #[derive(Debug, thiserror::Error)] | ||
| 158 | pub enum GitError { | ||
| 159 | #[error("Invalid pkt-line format")] | ||
| 160 | InvalidPktLine, | ||
| 161 | |||
| 162 | #[error("Invalid ref update format")] | ||
| 163 | InvalidRefUpdate, | ||
| 164 | |||
| 165 | #[error("Repository not found: {0}")] | ||
| 166 | RepositoryNotFound(String), | ||
| 167 | |||
| 168 | #[error("Invalid repository path")] | ||
| 169 | InvalidPath, | ||
| 170 | } | ||
| 171 | ``` | ||
| 172 | |||
| 173 | ## Step 6: Main Application State | ||
| 174 | |||
| 175 | Create `src/main.rs`: | ||
| 176 | |||
| 177 | ```rust | ||
| 178 | use actix_web::{web, App, HttpServer}; | ||
| 179 | use anyhow::Result; | ||
| 180 | use std::sync::Arc; | ||
| 181 | use tracing::info; | ||
| 182 | |||
| 183 | mod config; | ||
| 184 | mod git; | ||
| 185 | mod nostr; | ||
| 186 | mod storage; | ||
| 187 | |||
| 188 | use config::Config; | ||
| 189 | |||
| 190 | #[derive(Clone)] | ||
| 191 | pub struct AppState { | ||
| 192 | pub config: Arc<Config>, | ||
| 193 | // TODO: Add NostrClient, RepositoryManager, etc. | ||
| 194 | } | ||
| 195 | |||
| 196 | #[actix_web::main] | ||
| 197 | async fn main() -> Result<()> { | ||
| 198 | // Initialize logging | ||
| 199 | tracing_subscriber::fmt() | ||
| 200 | .with_env_filter( | ||
| 201 | tracing_subscriber::EnvFilter::from_default_env() | ||
| 202 | ) | ||
| 203 | .init(); | ||
| 204 | |||
| 205 | // Load configuration | ||
| 206 | let config = Config::from_env()?; | ||
| 207 | info!("Starting ngit-grasp on {}", config.bind_address); | ||
| 208 | |||
| 209 | // Create application state | ||
| 210 | let state = AppState { | ||
| 211 | config: Arc::new(config.clone()), | ||
| 212 | }; | ||
| 213 | |||
| 214 | // Start HTTP server | ||
| 215 | HttpServer::new(move || { | ||
| 216 | App::new() | ||
| 217 | .app_data(web::Data::new(state.clone())) | ||
| 218 | .configure(git::routes::configure) | ||
| 219 | .configure(nostr::routes::configure) | ||
| 220 | }) | ||
| 221 | .bind(config.bind_address)? | ||
| 222 | .run() | ||
| 223 | .await?; | ||
| 224 | |||
| 225 | Ok(()) | ||
| 226 | } | ||
| 227 | ``` | ||
| 228 | |||
| 229 | ## Step 7: Git Module Skeleton | ||
| 230 | |||
| 231 | Create `src/git/mod.rs`: | ||
| 232 | |||
| 233 | ```rust | ||
| 234 | pub mod routes; | ||
| 235 | pub mod handler; | ||
| 236 | pub mod parser; | ||
| 237 | pub mod authorization; | ||
| 238 | pub mod types; | ||
| 239 | |||
| 240 | pub use types::{RefUpdate, GitError}; | ||
| 241 | ``` | ||
| 242 | |||
| 243 | Create `src/git/routes.rs`: | ||
| 244 | |||
| 245 | ```rust | ||
| 246 | use actix_web::web; | ||
| 247 | |||
| 248 | pub fn configure(cfg: &mut web::ServiceConfig) { | ||
| 249 | cfg.service( | ||
| 250 | web::scope("/{npub}/{identifier}.git") | ||
| 251 | .route("/info/refs", web::get().to(super::handler::info_refs)) | ||
| 252 | .route("/git-upload-pack", web::post().to(super::handler::git_upload_pack)) | ||
| 253 | .route("/git-receive-pack", web::post().to(super::handler::git_receive_pack)) | ||
| 254 | ); | ||
| 255 | } | ||
| 256 | ``` | ||
| 257 | |||
| 258 | ## Step 8: First Test | ||
| 259 | |||
| 260 | Create `tests/integration/basic_test.rs`: | ||
| 261 | |||
| 262 | ```rust | ||
| 263 | use actix_web::{test, App}; | ||
| 264 | |||
| 265 | #[actix_web::test] | ||
| 266 | async fn test_server_starts() { | ||
| 267 | // TODO: Initialize test app | ||
| 268 | // TODO: Make test request | ||
| 269 | assert!(true); | ||
| 270 | } | ||
| 271 | ``` | ||
| 272 | |||
| 273 | Run tests: | ||
| 274 | |||
| 275 | ```bash | ||
| 276 | cargo test | ||
| 277 | ``` | ||
| 278 | |||
| 279 | ## Step 9: Implementation Order | ||
| 280 | |||
| 281 | Follow this order for implementation: | ||
| 282 | |||
| 283 | ### Phase 1: Basic Infrastructure (Week 1) | ||
| 284 | 1. ✅ Config module | ||
| 285 | 2. ✅ Main server setup | ||
| 286 | 3. ✅ Core types | ||
| 287 | 4. ⏭️ Git pkt-line parser | ||
| 288 | 5. ⏭️ Ref update parser | ||
| 289 | 6. ⏭️ Parser tests | ||
| 290 | |||
| 291 | ### Phase 2: Git Protocol (Week 2) | ||
| 292 | 1. ⏭️ Git upload-pack handler (read-only) | ||
| 293 | 2. ⏭️ Repository manager | ||
| 294 | 3. ⏭️ Path validation and security | ||
| 295 | 4. ⏭️ Integration tests for cloning | ||
| 296 | |||
| 297 | ### Phase 3: Nostr Relay (Week 2-3) | ||
| 298 | 1. ⏭️ Nostr relay setup with nostr-relay-builder | ||
| 299 | 2. ⏭️ Repository announcement policy | ||
| 300 | 3. ⏭️ Event hooks for repo creation | ||
| 301 | 4. ⏭️ NIP-11 configuration | ||
| 302 | |||
| 303 | ### Phase 4: Authorization (Week 3-4) | ||
| 304 | 1. ⏭️ Maintainer resolution logic | ||
| 305 | 2. ⏭️ State validation logic | ||
| 306 | 3. ⏭️ Git receive-pack with inline validation | ||
| 307 | 4. ⏭️ Integration tests for pushing | ||
| 308 | |||
| 309 | ### Phase 5: Polish (Week 4-6) | ||
| 310 | 1. ⏭️ Error handling improvements | ||
| 311 | 2. ⏭️ Logging and observability | ||
| 312 | 3. ⏭️ Performance optimization | ||
| 313 | 4. ⏭️ GRASP-01 compliance testing | ||
| 314 | 5. ⏭️ Documentation updates | ||
| 315 | |||
| 316 | ## Development Workflow | ||
| 317 | |||
| 318 | ### Running Locally | ||
| 319 | |||
| 320 | ```bash | ||
| 321 | # Copy environment template | ||
| 322 | cp .env.example .env | ||
| 323 | |||
| 324 | # Edit configuration | ||
| 325 | vim .env | ||
| 326 | |||
| 327 | # Run in development mode | ||
| 328 | cargo run | ||
| 329 | |||
| 330 | # With debug logging | ||
| 331 | RUST_LOG=debug cargo run | ||
| 332 | ``` | ||
| 333 | |||
| 334 | ### Testing | ||
| 335 | |||
| 336 | ```bash | ||
| 337 | # Run all tests | ||
| 338 | cargo test | ||
| 339 | |||
| 340 | # Run with output | ||
| 341 | cargo test -- --nocapture | ||
| 342 | |||
| 343 | # Run specific test | ||
| 344 | cargo test test_parse_ref_updates | ||
| 345 | |||
| 346 | # Run integration tests only | ||
| 347 | cargo test --test '*' | ||
| 348 | ``` | ||
| 349 | |||
| 350 | ### Code Quality | ||
| 351 | |||
| 352 | ```bash | ||
| 353 | # Format code | ||
| 354 | cargo fmt | ||
| 355 | |||
| 356 | # Check formatting | ||
| 357 | cargo fmt --check | ||
| 358 | |||
| 359 | # Lint | ||
| 360 | cargo clippy | ||
| 361 | |||
| 362 | # Lint with all features | ||
| 363 | cargo clippy --all-features -- -D warnings | ||
| 364 | ``` | ||
| 365 | |||
| 366 | ## Debugging Tips | ||
| 367 | |||
| 368 | ### Enable Detailed Logging | ||
| 369 | |||
| 370 | ```bash | ||
| 371 | RUST_LOG=trace cargo run | ||
| 372 | ``` | ||
| 373 | |||
| 374 | ### Test with Real Git Client | ||
| 375 | |||
| 376 | ```bash | ||
| 377 | # In another terminal, after server is running | ||
| 378 | mkdir test-repo && cd test-repo | ||
| 379 | git init | ||
| 380 | echo "test" > README.md | ||
| 381 | git add . && git commit -m "test" | ||
| 382 | |||
| 383 | # Try to push (will fail without Nostr setup) | ||
| 384 | git remote add origin http://localhost:8080/npub.../test.git | ||
| 385 | git push origin main | ||
| 386 | ``` | ||
| 387 | |||
| 388 | ### Use curl for HTTP Testing | ||
| 389 | |||
| 390 | ```bash | ||
| 391 | # Test info/refs endpoint | ||
| 392 | curl -v http://localhost:8080/npub.../test.git/info/refs?service=git-upload-pack | ||
| 393 | ``` | ||
| 394 | |||
| 395 | ## Common Issues | ||
| 396 | |||
| 397 | ### "Repository not found" | ||
| 398 | - Check that repository announcement was sent to Nostr relay | ||
| 399 | - Verify repository was created in git_data_path | ||
| 400 | - Check logs for repo creation | ||
| 401 | |||
| 402 | ### "Push rejected" | ||
| 403 | - Verify state event exists on relay | ||
| 404 | - Check state event matches push refs | ||
| 405 | - Verify maintainer list includes pusher | ||
| 406 | |||
| 407 | ### "Cannot connect to relay" | ||
| 408 | - Check relay is running | ||
| 409 | - Verify WebSocket endpoint | ||
| 410 | - Check firewall/network settings | ||
| 411 | |||
| 412 | ## Next Steps | ||
| 413 | |||
| 414 | After basic setup: | ||
| 415 | |||
| 416 | 1. Implement pkt-line parser (see [GIT_PROTOCOL.md](GIT_PROTOCOL.md)) | ||
| 417 | 2. Add comprehensive tests | ||
| 418 | 3. Implement Nostr relay policies | ||
| 419 | 4. Add authorization logic | ||
| 420 | 5. Test with ngit CLI | ||
| 421 | |||
| 422 | ## Resources | ||
| 423 | |||
| 424 | - [ARCHITECTURE.md](ARCHITECTURE.md) - Detailed design | ||
| 425 | - [GIT_PROTOCOL.md](GIT_PROTOCOL.md) - Git protocol reference | ||
| 426 | - [actix-web docs](https://actix.rs/docs/) | ||
| 427 | - [nostr-sdk docs](https://docs.rs/nostr-sdk/) | ||
| 428 | - [tokio docs](https://docs.rs/tokio/) | ||
| 429 | |||
| 430 | ## Getting Help | ||
| 431 | |||
| 432 | - Check existing documentation in `docs/` | ||
| 433 | - Review reference implementation at `../ngit-relay` | ||
| 434 | - Open an issue for questions | ||
| 435 | - Read GRASP protocol spec | ||
| 436 | |||
| 437 | Good luck! 🚀 | ||