mirror of
https://relay.ngit.dev/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit-grasp.git
synced 2026-10-05 15:08:24 +00:00
ok 2 prompts, the second one was about the test strategy so we could reuse it. I was thinking of a tool like blossom audit. but i didnt mention it specifically.
8.8 KiB
8.8 KiB
Getting Started with Implementation
This guide helps you start implementing ngit-grasp based on the architecture design.
Prerequisites
- Rust 1.75 or later
- Git 2.x
- Basic understanding of async Rust (tokio)
- Familiarity with actix-web (helpful)
- Understanding of Nostr basics (helpful)
Step 1: Initialize Cargo Project
# Create new binary project
cargo init --name ngit-grasp
# Or if already created:
cargo build
Step 2: Add Dependencies
Edit Cargo.toml:
[package]
name = "ngit-grasp"
version = "0.1.0"
edition = "2021"
rust-version = "1.75"
[dependencies]
# HTTP Server
actix-web = "4"
actix-cors = "0.7"
# Async Runtime
tokio = { version = "1", features = ["full"] }
# Git Protocol
git-http-backend = "0.1.3"
# Nostr
nostr-sdk = { version = "0.43", features = ["all-nips"] }
nostr-relay-builder = "0.43"
# Serialization
serde = { version = "1", features = ["derive"] }
serde_json = "1"
# Error Handling
anyhow = "1"
thiserror = "1"
# Logging
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
# Environment
dotenv = "0.15"
# Utilities
async-trait = "0.1"
futures = "0.3"
bytes = "1"
[dev-dependencies]
tokio-test = "0.4"
Step 3: Project Structure
Create the directory structure:
mkdir -p src/{git,nostr,storage}
mkdir -p tests/{integration,fixtures}
mkdir -p data/{git,relay}
Step 4: Configuration Module
Create src/config.rs:
use anyhow::Result;
use std::env;
use std::net::SocketAddr;
use std::path::PathBuf;
#[derive(Debug, Clone)]
pub struct Config {
pub domain: String,
pub owner_npub: String,
pub relay_name: String,
pub relay_description: String,
pub git_data_path: PathBuf,
pub relay_data_path: PathBuf,
pub bind_address: SocketAddr,
}
impl Config {
pub fn from_env() -> Result<Self> {
dotenv::dotenv().ok();
Ok(Config {
domain: env::var("NGIT_DOMAIN")?,
owner_npub: env::var("NGIT_OWNER_NPUB")?,
relay_name: env::var("NGIT_RELAY_NAME")?,
relay_description: env::var("NGIT_RELAY_DESCRIPTION")?,
git_data_path: PathBuf::from(
env::var("NGIT_GIT_DATA_PATH")
.unwrap_or_else(|_| "./data/git".to_string())
),
relay_data_path: PathBuf::from(
env::var("NGIT_RELAY_DATA_PATH")
.unwrap_or_else(|_| "./data/relay".to_string())
),
bind_address: env::var("NGIT_BIND_ADDRESS")
.unwrap_or_else(|_| "127.0.0.1:8080".to_string())
.parse()?,
})
}
}
Step 5: Core Types
Create src/git/types.rs:
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RefUpdate {
pub old_oid: String,
pub new_oid: String,
pub ref_name: String,
}
impl RefUpdate {
pub fn is_create(&self) -> bool {
self.old_oid == "0000000000000000000000000000000000000000"
}
pub fn is_delete(&self) -> bool {
self.new_oid == "0000000000000000000000000000000000000000"
}
pub fn is_update(&self) -> bool {
!self.is_create() && !self.is_delete()
}
}
#[derive(Debug, thiserror::Error)]
pub enum GitError {
#[error("Invalid pkt-line format")]
InvalidPktLine,
#[error("Invalid ref update format")]
InvalidRefUpdate,
#[error("Repository not found: {0}")]
RepositoryNotFound(String),
#[error("Invalid repository path")]
InvalidPath,
}
Step 6: Main Application State
Create src/main.rs:
use actix_web::{web, App, HttpServer};
use anyhow::Result;
use std::sync::Arc;
use tracing::info;
mod config;
mod git;
mod nostr;
mod storage;
use config::Config;
#[derive(Clone)]
pub struct AppState {
pub config: Arc<Config>,
// TODO: Add NostrClient, RepositoryManager, etc.
}
#[actix_web::main]
async fn main() -> Result<()> {
// Initialize logging
tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::EnvFilter::from_default_env()
)
.init();
// Load configuration
let config = Config::from_env()?;
info!("Starting ngit-grasp on {}", config.bind_address);
// Create application state
let state = AppState {
config: Arc::new(config.clone()),
};
// Start HTTP server
HttpServer::new(move || {
App::new()
.app_data(web::Data::new(state.clone()))
.configure(git::routes::configure)
.configure(nostr::routes::configure)
})
.bind(config.bind_address)?
.run()
.await?;
Ok(())
}
Step 7: Git Module Skeleton
Create src/git/mod.rs:
pub mod routes;
pub mod handler;
pub mod parser;
pub mod authorization;
pub mod types;
pub use types::{RefUpdate, GitError};
Create src/git/routes.rs:
use actix_web::web;
pub fn configure(cfg: &mut web::ServiceConfig) {
cfg.service(
web::scope("/{npub}/{identifier}.git")
.route("/info/refs", web::get().to(super::handler::info_refs))
.route("/git-upload-pack", web::post().to(super::handler::git_upload_pack))
.route("/git-receive-pack", web::post().to(super::handler::git_receive_pack))
);
}
Step 8: First Test
Create tests/integration/basic_test.rs:
use actix_web::{test, App};
#[actix_web::test]
async fn test_server_starts() {
// TODO: Initialize test app
// TODO: Make test request
assert!(true);
}
Run tests:
cargo test
Step 9: Implementation Order
Follow this order for implementation:
Phase 1: Basic Infrastructure (Week 1)
- ✅ Config module
- ✅ Main server setup
- ✅ Core types
- ⏭️ Git pkt-line parser
- ⏭️ Ref update parser
- ⏭️ Parser tests
Phase 2: Git Protocol (Week 2)
- ⏭️ Git upload-pack handler (read-only)
- ⏭️ Repository manager
- ⏭️ Path validation and security
- ⏭️ Integration tests for cloning
Phase 3: Nostr Relay (Week 2-3)
- ⏭️ Nostr relay setup with nostr-relay-builder
- ⏭️ Repository announcement policy
- ⏭️ Event hooks for repo creation
- ⏭️ NIP-11 configuration
Phase 4: Authorization (Week 3-4)
- ⏭️ Maintainer resolution logic
- ⏭️ State validation logic
- ⏭️ Git receive-pack with inline validation
- ⏭️ Integration tests for pushing
Phase 5: Polish (Week 4-6)
- ⏭️ Error handling improvements
- ⏭️ Logging and observability
- ⏭️ Performance optimization
- ⏭️ GRASP-01 compliance testing
- ⏭️ Documentation updates
Development Workflow
Running Locally
# Copy environment template
cp .env.example .env
# Edit configuration
vim .env
# Run in development mode
cargo run
# With debug logging
RUST_LOG=debug cargo run
Testing
# Run all tests
cargo test
# Run with output
cargo test -- --nocapture
# Run specific test
cargo test test_parse_ref_updates
# Run integration tests only
cargo test --test '*'
Code Quality
# Format code
cargo fmt
# Check formatting
cargo fmt --check
# Lint
cargo clippy
# Lint with all features
cargo clippy --all-features -- -D warnings
Debugging Tips
Enable Detailed Logging
RUST_LOG=trace cargo run
Test with Real Git Client
# In another terminal, after server is running
mkdir test-repo && cd test-repo
git init
echo "test" > README.md
git add . && git commit -m "test"
# Try to push (will fail without Nostr setup)
git remote add origin http://localhost:8080/npub.../test.git
git push origin main
Use curl for HTTP Testing
# Test info/refs endpoint
curl -v http://localhost:8080/npub.../test.git/info/refs?service=git-upload-pack
Common Issues
"Repository not found"
- Check that repository announcement was sent to Nostr relay
- Verify repository was created in git_data_path
- Check logs for repo creation
"Push rejected"
- Verify state event exists on relay
- Check state event matches push refs
- Verify maintainer list includes pusher
"Cannot connect to relay"
- Check relay is running
- Verify WebSocket endpoint
- Check firewall/network settings
Next Steps
After basic setup:
- Implement pkt-line parser (see GIT_PROTOCOL.md)
- Add comprehensive tests
- Implement Nostr relay policies
- Add authorization logic
- Test with ngit CLI
Resources
- ARCHITECTURE.md - Detailed design
- GIT_PROTOCOL.md - Git protocol reference
- actix-web docs
- nostr-sdk docs
- tokio docs
Getting Help
- Check existing documentation in
docs/ - Review reference implementation at
../ngit-relay - Open an issue for questions
- Read GRASP protocol spec
Good luck! 🚀