mirror of
https://relay.ngit.dev/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit-grasp.git
synced 2026-10-05 15:08:24 +00:00
619 lines
25 KiB
Markdown
619 lines
25 KiB
Markdown
# ngit-grasp Architecture
|
|
|
|
## Executive Summary
|
|
|
|
`ngit-grasp` implements the GRASP protocol in Rust with **inline authorization** rather than Git hooks. Git push operations are intercepted and validated at the HTTP handler level before reaching the Git repository, eliminating the need for pre-receive hooks.
|
|
|
|
## System Architecture
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ ngit-grasp │
|
|
│ (Single Rust Binary) │
|
|
├─────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ ┌──────────────────┐ ┌──────────────────┐ │
|
|
│ │ HTTP Router │ │ Nostr Relay │ │
|
|
│ │ (Hyper) │ │ (nostr-relay- │ │
|
|
│ │ │ │ builder) │ │
|
|
│ └────────┬─────────┘ └────────┬─────────┘ │
|
|
│ │ │ │
|
|
│ │ │ │
|
|
│ ┌────────▼──────────────────────────────────▼─────────┐ │
|
|
│ │ Shared State & Storage │ │
|
|
│ │ ┌──────────────┐ ┌──────────────┐ │ │
|
|
│ │ │ Repository │ │ Event Store │ │ │
|
|
│ │ │ Manager │ │ (LMDB/NDB) │ │ │
|
|
│ │ └──────────────┘ └──────────────┘ │ │
|
|
│ └─────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
│ ┌──────────────────────────────────────────────────────┐ │
|
|
│ │ Git Protocol Handler │ │
|
|
│ │ │ │
|
|
│ │ 1. Receive git-receive-pack request │ │
|
|
│ │ 2. Parse ref updates from request │ │
|
|
│ │ 3. Query Nostr relay for state event │ │
|
|
│ │ 4. Validate refs against state │ │
|
|
│ │ 5. If valid: spawn git-receive-pack │ │
|
|
│ │ 6. If invalid: return HTTP error │ │
|
|
│ │ │ │
|
|
│ └──────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
│ │
|
|
│ HTTP/Git │ WebSocket/Nostr
|
|
▼ ▼
|
|
Git Clients Nostr Clients
|
|
```
|
|
|
|
## Component Design
|
|
|
|
### 1. Main Server ([`src/main.rs`](src/main.rs))
|
|
|
|
**Responsibilities:**
|
|
|
|
- Initialize configuration from environment (clap + dotenvy)
|
|
- Set up Hyper HTTP server with request routing
|
|
- Initialize Nostr relay builder with custom [`Nip34WritePolicy`](src/nostr/builder.rs:51)
|
|
- Set up shared storage (LMDB or Memory)
|
|
- Handle WebSocket upgrades for Nostr relay
|
|
- Handle graceful shutdown
|
|
|
|
**Key Dependencies:**
|
|
|
|
```rust
|
|
hyper = "1"
|
|
tokio = { version = "1", features = ["full"] }
|
|
nostr-relay-builder = "0.43"
|
|
nostr-sdk = "0.43"
|
|
nostr-lmdb = "0.43"
|
|
```
|
|
|
|
### 2. HTTP Module ([`src/http/mod.rs`](src/http/mod.rs))
|
|
|
|
**Responsibilities:**
|
|
|
|
- Route HTTP requests to appropriate handlers
|
|
- WebSocket upgrade for Nostr relay at `/`
|
|
- Git Smart HTTP endpoints at `/<npub>/<identifier>.git/*`
|
|
- Landing pages and NIP-11 document serving
|
|
- CORS headers on all responses (GRASP-01 requirement)
|
|
|
|
**Key Implementation Details:**
|
|
|
|
```rust
|
|
// CORS headers required by GRASP-01 specification
|
|
const CORS_ALLOW_ORIGIN: &str = "*";
|
|
const CORS_ALLOW_METHODS: &str = "GET, POST";
|
|
const CORS_ALLOW_HEADERS: &str = "Content-Type";
|
|
|
|
/// Add CORS headers to a response builder
|
|
fn add_cors_headers(builder: http::response::Builder) -> http::response::Builder {
|
|
builder
|
|
.header("Access-Control-Allow-Origin", CORS_ALLOW_ORIGIN)
|
|
.header("Access-Control-Allow-Methods", CORS_ALLOW_METHODS)
|
|
.header("Access-Control-Allow-Headers", CORS_ALLOW_HEADERS)
|
|
}
|
|
```
|
|
|
|
See [`src/http/mod.rs:29-84`](src/http/mod.rs:29-84) for the full CORS implementation.
|
|
|
|
### 3. Git Module ([`src/git/`](src/git/))
|
|
|
|
#### [`handlers.rs`](src/git/handlers.rs) - Git HTTP Handlers
|
|
|
|
Implements handlers for Git Smart HTTP protocol:
|
|
|
|
```rust
|
|
/// Handle GET /info/refs?service=git-{upload,receive}-pack
|
|
pub async fn handle_info_refs(
|
|
repo_path: PathBuf,
|
|
service: GitService,
|
|
) -> Result<Response<Full<Bytes>>, GitError>
|
|
|
|
/// Handle POST /git-upload-pack (clone/fetch)
|
|
pub async fn handle_upload_pack(
|
|
repo_path: PathBuf,
|
|
body: Bytes,
|
|
) -> Result<Response<Full<Bytes>>, GitError>
|
|
|
|
/// Handle POST /git-receive-pack (push)
|
|
/// THIS IS WHERE THE MAGIC HAPPENS - validates against state before accepting
|
|
pub async fn handle_receive_pack(
|
|
repo_path: PathBuf,
|
|
body: Bytes,
|
|
database: SharedDatabase,
|
|
npub: &str,
|
|
identifier: &str,
|
|
) -> Result<Response<Full<Bytes>>, GitError>
|
|
```
|
|
|
|
See [`src/git/handlers.rs:22-98`](src/git/handlers.rs:22-98) for the info-refs implementation.
|
|
|
|
#### [`authorization.rs`](src/git/authorization.rs) - Push Validation
|
|
|
|
**Core Logic:**
|
|
|
|
```rust
|
|
/// Get authorization info for a repository owner
|
|
pub async fn get_authorization_for_owner(
|
|
database: &SharedDatabase,
|
|
pubkey: &PublicKey,
|
|
identifier: &str,
|
|
) -> Result<AuthorizationResult, AuthorizationError>
|
|
|
|
/// Validate that pushed refs match the authorized state
|
|
pub fn validate_push_refs(
|
|
pushed_refs: &[PushedRef],
|
|
state: &RepositoryState,
|
|
) -> Result<(), AuthorizationError>
|
|
|
|
/// Validate refs/nostr/<event-id> pushes
|
|
pub fn validate_nostr_ref_pushes(
|
|
pushed_refs: &[PushedRef],
|
|
database: &SharedDatabase,
|
|
) -> Result<(), AuthorizationError>
|
|
```
|
|
|
|
### 4. Nostr Module ([`src/nostr/`](src/nostr/))
|
|
|
|
#### [`builder.rs`](src/nostr/builder.rs) - Relay Configuration
|
|
|
|
The [`Nip34WritePolicy`](src/nostr/builder.rs:51) is the core event validation logic:
|
|
|
|
```rust
|
|
/// NIP-34 Write Policy with Full GRASP-01 Event Validation
|
|
///
|
|
/// Validates all events according to GRASP-01 specification:
|
|
/// - Repository announcements must list service in clone and relays tags
|
|
/// EXCEPTION: Recursive maintainer announcements are accepted even without
|
|
/// listing the service, to enable maintainer chain discovery and GRASP-02 sync
|
|
/// - Repository state announcements must have valid structure
|
|
/// - Other events must reference accepted repositories or events
|
|
/// - Forward references are supported (events referenced by accepted events)
|
|
/// - Orphan events with no valid references are rejected
|
|
pub struct Nip34WritePolicy {
|
|
domain: String,
|
|
database: SharedDatabase,
|
|
git_data_path: PathBuf,
|
|
}
|
|
```
|
|
|
|
See [`src/nostr/builder.rs:38-78`](src/nostr/builder.rs:38-78) for the full policy struct.
|
|
|
|
#### [`events.rs`](src/nostr/events.rs) - Event Parsing
|
|
|
|
Provides structures for parsing NIP-34 events:
|
|
|
|
```rust
|
|
/// Parsed repository announcement (Kind 30617)
|
|
pub struct RepositoryAnnouncement { ... }
|
|
|
|
/// Parsed repository state (Kind 30618)
|
|
pub struct RepositoryState { ... }
|
|
```
|
|
|
|
#### [`policy/state.rs`](src/nostr/policy/state.rs) - State Event Authorization
|
|
|
|
State events undergo authorization checks at multiple points:
|
|
|
|
```rust
|
|
/// State event authorization checks:
|
|
/// 1. Announcement must exist for the repository identifier
|
|
/// 2. Author must be in maintainer set of accepted announcement
|
|
/// 3. Validated on arrival, announcement acceptance, and git data arrival
|
|
```
|
|
|
|
**Defense-in-depth authorization:**
|
|
- **On arrival** (StatePolicy): Initial authorization check
|
|
- **On announcement acceptance**: Purgatory re-evaluation of waiting state events
|
|
- **On git data arrival**: Final authorization before database save
|
|
|
|
### 5. Purgatory System ([`src/purgatory/`](../../src/purgatory/))
|
|
|
|
The purgatory system solves two related problems:
|
|
|
|
1. **"Which arrives first?"** — Either nostr events or git pushes can arrive in any order. Purgatory holds events awaiting their git data counterparts.
|
|
2. **Misleading empty repository announcements** — New announcements are held in purgatory until git data arrives, ensuring clients are never served announcements for repos with no content.
|
|
|
|
**Design Document**: See [`purgatory-design.md`](purgatory-design.md) for complete design specifications.
|
|
|
|
#### Architecture
|
|
|
|
```rust
|
|
/// Main purgatory structure with separate stores per event type
|
|
pub struct Purgatory {
|
|
/// Announcement events (kind 30617) indexed by (owner, identifier)
|
|
/// Held until git data proves content exists
|
|
announcement_purgatory: DashMap<(PublicKey, String), AnnouncementPurgatoryEntry>,
|
|
|
|
/// State events (kind 30618) indexed by repository identifier
|
|
state_events: DashMap<String, Vec<StatePurgatoryEntry>>,
|
|
|
|
/// PR events (kind 1617/1618) or placeholders indexed by event ID
|
|
pr_events: DashMap<String, PrPurgatoryEntry>,
|
|
}
|
|
```
|
|
|
|
**Key Design Principles:**
|
|
|
|
1. **Separate Storage**: Each event type uses a different indexing strategy
|
|
- Announcements: Indexed by `(pubkey, identifier)` (unique per owner)
|
|
- State events: Indexed by `identifier` (multiple events can wait for same repo)
|
|
- PR events: Indexed by `event_id` (one-to-one mapping)
|
|
|
|
2. **Announcement Purgatory**: New announcements are held until git data arrives
|
|
- Bare repo created immediately so pushes can succeed
|
|
- Announcement promoted to database only when git data proves content exists
|
|
- Two-phase soft expiry: bare repo deleted at 30 min, event retained 24h for revival
|
|
|
|
3. **Late Binding**: State event refs are extracted at git push time, not event arrival
|
|
- Enables flexible matching when pushes arrive out-of-order
|
|
- Helper functions in [`helpers.rs`](../../src/purgatory/helpers.rs) handle ref extraction
|
|
|
|
4. **Bidirectional Waiting**: Either side can arrive first
|
|
- **Event-first**: Event waits for git push
|
|
- **Git-first**: Placeholder created, waits for event
|
|
|
|
5. **Automatic Expiry**: 30-minute default expiry, extensible during processing
|
|
- Background cleanup task runs every 60 seconds
|
|
- Removes expired entries from all stores
|
|
|
|
#### Data Types
|
|
|
|
See [`types.rs`](../../src/purgatory/types.rs) for complete definitions:
|
|
|
|
- **[`RefPair`](../../src/purgatory/types.rs:16)**: Ref name + object SHA pair
|
|
- **[`AnnouncementPurgatoryEntry`](../../src/purgatory/types.rs)**: Announcement with bare repo path, relays, and expiry
|
|
- **[`StatePurgatoryEntry`](../../src/purgatory/types.rs:29)**: State event with metadata
|
|
- **[`PrPurgatoryEntry`](../../src/purgatory/types.rs:52)**: PR event or placeholder with metadata
|
|
|
|
#### Integration Points
|
|
|
|
**Write Policy** ([`src/nostr/policy/`](../../src/nostr/policy/)):
|
|
- Announcement policy routes new announcements to purgatory; replacements accepted immediately
|
|
- State policy checks git data existence before adding to purgatory; checks purgatory announcements for authorization
|
|
- PR policy checks for placeholders before adding to purgatory
|
|
- Events return "purgatory: will not be served until git data arrives" message
|
|
|
|
**Git Handlers** ([`src/git/handlers.rs`](../../src/git/handlers.rs)):
|
|
- On git push: Promote announcement from purgatory to database if present
|
|
- On git push: Check purgatory for matching state events
|
|
- On refs/nostr/* push: Check purgatory for PR events or create placeholders
|
|
- Release events from purgatory when git data arrives
|
|
- Save released events to database
|
|
|
|
**Main.rs** ([`src/main.rs`](../../src/main.rs)):
|
|
- Creates `Arc<Purgatory>` at startup
|
|
- Passes to both write policy and git handlers
|
|
- Spawns background cleanup task (60-second interval)
|
|
|
|
#### Thread Safety
|
|
|
|
- Uses `Arc<DashMap>` for lock-free concurrent access
|
|
- Safe to share between HTTP handlers, WebSocket handlers, and background tasks
|
|
- No blocking locks in hot paths
|
|
|
|
### 6. Configuration ([`src/config.rs`](src/config.rs))
|
|
|
|
```rust
|
|
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,
|
|
pub database_backend: DatabaseBackend,
|
|
}
|
|
|
|
pub enum DatabaseBackend {
|
|
Lmdb, // Default, production use
|
|
NostrDb, // Alternative
|
|
Memory, // Testing
|
|
}
|
|
```
|
|
|
|
Configuration is loaded via **clap CLI > environment variables > .env > defaults**.
|
|
|
|
## Data Flow
|
|
|
|
### Push Operation Flow
|
|
|
|
```
|
|
1. Git Client → POST /<npub>/<id>.git/git-receive-pack
|
|
↓
|
|
2. HttpService routes to git::handlers::handle_receive_pack()
|
|
↓
|
|
3. Parse ref updates from request body (pkt-line format)
|
|
↓
|
|
4. Extract npub and identifier from URL
|
|
↓
|
|
5. authorization::get_authorization_for_owner()
|
|
├─ Query database for announcements
|
|
├─ Build recursive maintainer set
|
|
└─ Get latest authorized state
|
|
↓
|
|
6. authorization::validate_push_refs()
|
|
├─ Check each ref matches state
|
|
└─ Validate refs/nostr/ pushes
|
|
↓
|
|
7. If VALID:
|
|
├─ Spawn git-receive-pack subprocess
|
|
├─ Stream request body to git stdin
|
|
└─ Stream git stdout back to client
|
|
↓
|
|
8. If INVALID:
|
|
└─ Return HTTP 403 with error message
|
|
```
|
|
|
|
### Repository Announcement Flow
|
|
|
|
```
|
|
1. Nostr Client → EVENT (Kind 30617)
|
|
↓
|
|
2. Nostr relay receives event
|
|
↓
|
|
3. Nip34WritePolicy::admit_event()
|
|
├─ Check if instance in clone tags
|
|
├─ Check if instance in relays tags
|
|
├─ OR: Check if recursive maintainer
|
|
└─ Accept or reject
|
|
↓
|
|
4. If ACCEPTED:
|
|
├─ Is there an active announcement for (pubkey, identifier) in DB?
|
|
│ ├─ YES → Accept immediately (replacement, repo already proven)
|
|
│ └─ NO → Route to announcement purgatory
|
|
↓
|
|
5. Announcement Purgatory path:
|
|
├─ Bare Git repository created immediately at
|
|
│ <git_data_path>/<npub>/<identifier>.git
|
|
├─ Announcement held in purgatory (not served to clients)
|
|
└─ Awaiting git data to prove content exists
|
|
↓
|
|
6. When git data arrives (push or background sync):
|
|
├─ Announcement promoted from purgatory to database
|
|
├─ Event now served to clients
|
|
└─ SyncManager upgrades to Full sync level
|
|
↓
|
|
7. If no git data within 30 minutes:
|
|
├─ Bare repo deleted (soft expiry)
|
|
├─ Event retained 24h for potential revival
|
|
└─ Eventually discarded if no git data arrives
|
|
```
|
|
|
|
### State Event Flow
|
|
|
|
```
|
|
1. Nostr Client → EVENT (Kind 30618)
|
|
↓
|
|
2. Nostr relay receives event
|
|
↓
|
|
3. Nip34WritePolicy::admit_event()
|
|
├─ Check author is in maintainer set (DB + purgatory announcements)
|
|
├─ Validate state structure
|
|
└─ Accept or reject
|
|
↓
|
|
4. If ACCEPTED:
|
|
├─ Does git data already exist for this state?
|
|
│ ├─ YES → Save to database immediately
|
|
│ └─ NO → Add to state purgatory
|
|
↓
|
|
5. State Purgatory path:
|
|
├─ Event held in purgatory (not served to clients)
|
|
├─ Enqueued for background git data sync (3 min delay)
|
|
└─ Awaiting git push or background sync
|
|
↓
|
|
6. When git push arrives:
|
|
├─ Authorization checks both database AND purgatory
|
|
├─ If authorized via purgatory state: push proceeds
|
|
├─ After successful push: state event saved to database
|
|
└─ Removed from purgatory
|
|
```
|
|
|
|
## Testing Strategy
|
|
|
|
See [test-strategy.md](../reference/test-strategy.md) for comprehensive testing documentation.
|
|
|
|
### Quick Overview
|
|
|
|
**Integration Tests** ([`tests/`](tests/)):
|
|
|
|
- Use [`TestRelay`](tests/common/relay.rs:14) fixture for automatic relay lifecycle
|
|
- Each test file in [`tests/`](tests/) covers a GRASP-01 requirement
|
|
|
|
**Audit Tests** ([`grasp-audit/`](grasp-audit/)):
|
|
|
|
- Reusable compliance testing for any GRASP implementation
|
|
- Spec-mirrored structure in [`grasp-audit/src/specs/grasp01/`](grasp-audit/src/specs/grasp01/)
|
|
|
|
```rust
|
|
// Example: tests/nip01_compliance.rs
|
|
#[tokio::test]
|
|
async fn test_nip01_websocket_connection() {
|
|
let relay = TestRelay::start().await;
|
|
// Test NIP-01 compliance...
|
|
relay.stop().await;
|
|
}
|
|
```
|
|
|
|
## Performance Considerations
|
|
|
|
### 1. Async All The Way
|
|
|
|
- Use `tokio` for all I/O
|
|
- Non-blocking Git subprocess spawning via [`GitSubprocess`](src/git/subprocess.rs)
|
|
- Stream large pack files without buffering
|
|
|
|
### 2. Shared Database
|
|
|
|
- Single database instance shared between relay and Git handlers
|
|
- Direct queries for push authorization (no WebSocket round-trip)
|
|
|
|
### 3. Write Policy Caching
|
|
|
|
- Maintainer sets computed once per event validation
|
|
- State lookups use database indexes
|
|
|
|
## Proactive Sync (GRASP-02)
|
|
|
|
The ngit-grasp relay implements **Proactive Sync of Nostr Eevents**, which synchronizes repository data from external relays listed in 30617 repository announcements. This enables the relay to maintain complete repository graphs even when events are published to other listed relays.
|
|
|
|
**Key Features:**
|
|
|
|
- **Self-subscription** discovery - monitors own relay for announcements and root events to follow
|
|
- **Three-way diff** (`compute_actions`) determines new subscriptions needed
|
|
- **Smart reconnection** - uses `since` filter for quick reconnects (<15 min), fresh sync otherwise
|
|
- **Health tracking** with exponential backoff for failing relays
|
|
- **Daily sync** with random 23-25h timer to detect state drift
|
|
- **Filter consolidation** when count exceeds 70 to prevent subscription explosion
|
|
- **Rejected events index** - prevents wasteful re-fetching during negentropy sync
|
|
|
|
**Architecture:**
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ SyncManager │
|
|
├─────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ ┌──────────────────┐ ┌──────────────────┐ │
|
|
│ │ SelfSubscriber │──actions──▶ │ Main Event Loop │ │
|
|
│ │ (own relay) │ │ (Arc<Mutex>) │ │
|
|
│ └──────────────────┘ └────────┬─────────┘ │
|
|
│ │ │
|
|
│ ┌──────────────────┐ ┌────────▼─────────┐ │
|
|
│ │ Daily Timer │──────────────▶ RelayConnection │ │
|
|
│ │ (23-25h random) │ │ per external │ │
|
|
│ └──────────────────┘ │ relay │ │
|
|
│ └──────────────────┘ │
|
|
│ ┌──────────────────┐ │
|
|
│ │ Health Tracker │ Exponential backoff, dead detection │
|
|
│ │ (DashMap) │ │
|
|
│ └──────────────────┘ │
|
|
│ ┌──────────────────────────────────────────────────────┐ │
|
|
│ │ Rejected Events Index (Two-Tier) │ │
|
|
│ │ ┌────────────────┐ ┌──────────────────────┐ │ │
|
|
│ │ │ Hot Cache │───▶│ Cold Index │ │ │
|
|
│ │ │ (2 min) │ │ (7 days) │ │ │
|
|
│ │ │ Full events │ │ Metadata only │ │ │
|
|
│ │ └────────────────┘ └──────────────────────┘ │ │
|
|
│ └──────────────────────────────────────────────────────┘ │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
**Source Code:** [`src/sync/`](src/sync/)
|
|
|
|
For full design details, see [grasp-02-proactive-sync-v4.md](grasp-02-proactive-sync-v4.md).
|
|
|
|
### Rejected Events Index
|
|
|
|
The rejected events index solves two critical problems during sync:
|
|
|
|
1. **Negentropy sync efficiency**: Prevents repeatedly downloading events that will be rejected again
|
|
2. **Race condition resolution**: Enables immediate re-processing when event dependencies are satisfied
|
|
|
|
**Two-Tier Architecture:**
|
|
|
|
| Tier | Duration | Storage | Purpose |
|
|
|------|----------|---------|---------|
|
|
| Hot Cache | 2 minutes | Full events | Immediate re-processing when dependencies arrive |
|
|
| Cold Index | 7 days | Metadata only | Prevent re-fetch during negentropy sync |
|
|
|
|
**Event Flow:**
|
|
|
|
```
|
|
Event Rejected (e.g., maintainer before owner announcement)
|
|
│
|
|
├──▶ Store full event in Hot Cache (2 min expiry)
|
|
└──▶ Store metadata in Cold Index (7 day expiry)
|
|
|
|
Dependency Arrives (e.g., owner announcement accepted)
|
|
│
|
|
├──▶ Invalidate from Cold Index
|
|
├──▶ Retrieve from Hot Cache (if still available)
|
|
└──▶ Re-process immediately (<1 second vs 24 hours)
|
|
|
|
Negentropy Sync
|
|
│
|
|
└──▶ Exclude Cold Index IDs from "missing events" calculation
|
|
```
|
|
|
|
**Tracked Events:**
|
|
- Repository announcements (kind 30617) rejected for not listing this service or maintainer validation failure
|
|
- State events (kind 30618) rejected for missing announcements or unauthorized authors
|
|
|
|
**Source Code:** [`src/sync/rejected_index.rs`](../../src/sync/rejected_index.rs)
|
|
|
|
## Future Extensions
|
|
|
|
### GRASP-02: Proactive Sync
|
|
|
|
GRASP-02 is only partially implemented. still outstanding is the proactive sync of git data for 1. state event and 2. PRs / PR Update refs.
|
|
|
|
### GRASP-05: Archive
|
|
|
|
Relax the write policy to accept all repository announcements regardless of clone/relays tags.
|
|
|
|
## Deployment
|
|
|
|
### Single Binary
|
|
|
|
```bash
|
|
cargo build --release
|
|
./target/release/ngit-grasp --domain example.com --owner-npub npub1...
|
|
```
|
|
|
|
### Docker
|
|
|
|
```dockerfile
|
|
FROM rust:1.75 as builder
|
|
WORKDIR /app
|
|
COPY . .
|
|
RUN cargo build --release
|
|
|
|
FROM debian:bookworm-slim
|
|
RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/*
|
|
COPY --from=builder /app/target/release/ngit-grasp /usr/local/bin/
|
|
EXPOSE 7334
|
|
CMD ["ngit-grasp"]
|
|
```
|
|
|
|
### Systemd
|
|
|
|
```ini
|
|
[Unit]
|
|
Description=ngit-grasp GRASP server
|
|
After=network.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
User=git
|
|
WorkingDirectory=/opt/ngit-grasp
|
|
EnvironmentFile=/opt/ngit-grasp/.env
|
|
ExecStart=/usr/local/bin/ngit-grasp
|
|
Restart=on-failure
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
```
|
|
|
|
## Security Considerations
|
|
|
|
1. **Input Validation**: All npub/identifier inputs must be validated
|
|
2. **Path Traversal**: Prevent directory traversal in repository paths
|
|
3. **DoS Protection**: Rate limiting on both HTTP and WebSocket
|
|
4. **Resource Limits**: Limit pack file sizes, event sizes
|
|
5. **Nostr Event Validation**: Strict signature verification (handled by nostr-relay-builder)
|
|
|
|
## Conclusion
|
|
|
|
ngit-grasp uses inline authorization at the HTTP handler level, giving full control over request handling, WebSocket upgrades, and CORS headers while maintaining full GRASP-01 compliance. The purgatory system ensures that only repositories with actual git content are served to clients, and that events and git data are always consistent when released to the database.
|
|
|
|
## Related Documentation
|
|
|
|
- [Inline Authorization Explanation](inline-authorization.md) - Why we chose this approach
|
|
- [GRASP-02 Proactive Sync v4 Design](grasp-02-proactive-sync-v4.md) - Current production sync implementation
|
|
- [Test Strategy](../reference/test-strategy.md) - Comprehensive testing documentation
|
|
- [GRASP-01 Implementation Learnings](../learnings/grasp-01-implementation.md) - Patterns and lessons learned
|