diff options
| -rw-r--r-- | .env.example | 101 | ||||
| -rw-r--r-- | Cargo.lock | 1 | ||||
| -rw-r--r-- | Cargo.toml | 1 | ||||
| -rw-r--r-- | README.md | 74 | ||||
| -rw-r--r-- | docs/GETTING_STARTED.md | 437 | ||||
| -rw-r--r-- | src/config.rs | 223 | ||||
| -rw-r--r-- | src/http/landing.rs | 8 | ||||
| -rw-r--r-- | src/http/mod.rs | 4 | ||||
| -rw-r--r-- | src/http/nip11.rs | 12 | ||||
| -rw-r--r-- | src/main.rs | 18 | ||||
| -rw-r--r-- | src/nostr/builder.rs | 15 |
11 files changed, 361 insertions, 533 deletions
diff --git a/.env.example b/.env.example index 796415e..0a93b1f 100644 --- a/.env.example +++ b/.env.example | |||
| @@ -1,34 +1,91 @@ | |||
| 1 | # ngit-grasp Configuration | 1 | # ngit-grasp Configuration |
| 2 | # | ||
| 3 | # Configuration Priority (highest to lowest): | ||
| 4 | # 1. CLI flags (e.g., --domain example.com) | ||
| 5 | # 2. Environment variables (e.g., NGIT_DOMAIN=example.com) | ||
| 6 | # 3. This .env file | ||
| 7 | # 4. Built-in defaults | ||
| 8 | # | ||
| 9 | # Run `ngit-grasp --help` for all CLI options | ||
| 2 | 10 | ||
| 3 | # Domain where this instance is hosted (used in GRASP validation) | 11 | # ============================================================================ |
| 4 | NGIT_DOMAIN=gitnostr.com | 12 | # REQUIRED |
| 13 | # ============================================================================ | ||
| 5 | 14 | ||
| 6 | # Owner's npub (for relay info) | 15 | # Domain where this instance is hosted (required, used in GRASP validation) |
| 7 | NGIT_OWNER_NPUB=npub1... | 16 | # CLI: --domain <domain> |
| 17 | # No default - must be set | ||
| 18 | # NGIT_DOMAIN= | ||
| 8 | 19 | ||
| 9 | # Relay information (NIP-11) | 20 | # ============================================================================ |
| 10 | NGIT_RELAY_NAME=My GRASP Relay | 21 | # SERVER CONFIGURATION |
| 11 | NGIT_RELAY_DESCRIPTION=A GRASP-compliant Git relay with Nostr authorization | 22 | # ============================================================================ |
| 12 | 23 | ||
| 13 | # Storage paths | 24 | # Server bind address (IP:PORT) |
| 14 | NGIT_GIT_DATA_PATH=./data/git | 25 | # CLI: --bind-address <address> |
| 15 | NGIT_RELAY_DATA_PATH=./data/relay | 26 | # Default: 127.0.0.1:8080 |
| 27 | # NGIT_BIND_ADDRESS=127.0.0.1:8080 | ||
| 16 | 28 | ||
| 17 | # Database backend (memory, nostrdb, lmdb) | 29 | # ============================================================================ |
| 18 | # - memory: In-memory database (default, fastest, no persistence) | 30 | # RELAY INFORMATION (NIP-11) |
| 19 | # - nostrdb: NostrDB backend (persistent, optimized for Nostr) [Not yet implemented] | 31 | # ============================================================================ |
| 20 | # - lmdb: LMDB backend (persistent, general purpose) | ||
| 21 | NGIT_DATABASE_BACKEND=memory | ||
| 22 | 32 | ||
| 23 | # Server configuration | 33 | # Owner's npub (optional, for relay info in NIP-11) |
| 24 | NGIT_BIND_ADDRESS=127.0.0.1:8080 | 34 | # CLI: --owner-npub <npub> |
| 35 | # Default: (none) | ||
| 36 | # NGIT_OWNER_NPUB=npub1... | ||
| 25 | 37 | ||
| 26 | # Logging | 38 | # Relay name shown in NIP-11 information document |
| 27 | RUST_LOG=info | 39 | # CLI: --relay-name <name> |
| 40 | # Default: ${domain} grasp relay (e.g., "gitnostr.com grasp relay") | ||
| 41 | # NGIT_RELAY_NAME=My GRASP Relay | ||
| 28 | 42 | ||
| 29 | # Optional: Proactive sync settings (GRASP-02) | 43 | # Relay description shown in NIP-11 information document |
| 44 | # CLI: --relay-description <description> | ||
| 45 | # Default: Git Nostr Relay - a grasp implementation | ||
| 46 | # NGIT_RELAY_DESCRIPTION="A GRASP-compliant Git relay with Nostr authorization" | ||
| 47 | |||
| 48 | # ============================================================================ | ||
| 49 | # STORAGE | ||
| 50 | # ============================================================================ | ||
| 51 | |||
| 52 | # Path to store Git repositories | ||
| 53 | # CLI: --git-data-path <path> | ||
| 54 | # Default: ./data/git | ||
| 55 | # NGIT_GIT_DATA_PATH=./data/git | ||
| 56 | |||
| 57 | # Path to store Nostr relay data | ||
| 58 | # CLI: --relay-data-path <path> | ||
| 59 | # Default: ./data/relay | ||
| 60 | # NGIT_RELAY_DATA_PATH=./data/relay | ||
| 61 | |||
| 62 | # Database backend for Nostr events | ||
| 63 | # CLI: --database-backend <backend> | ||
| 64 | # Options: lmdb, memory, nostrdb | ||
| 65 | # Default: lmdb | ||
| 66 | # - lmdb: LMDB backend (persistent, general purpose) - RECOMMENDED | ||
| 67 | # - memory: In-memory database (fastest, no persistence, uses temp dirs) | ||
| 68 | # - nostrdb: NostrDB backend (persistent, Nostr-optimized) [Not yet implemented] | ||
| 69 | # | ||
| 70 | # Note: When using 'memory' backend, git_data_path and relay_data_path | ||
| 71 | # are automatically set to temporary directories for ephemeral testing. | ||
| 72 | # NGIT_DATABASE_BACKEND=lmdb | ||
| 73 | |||
| 74 | # ============================================================================ | ||
| 75 | # LOGGING | ||
| 76 | # ============================================================================ | ||
| 77 | |||
| 78 | # Rust log level (not a ngit-grasp config, but useful for debugging) | ||
| 79 | # Options: error, warn, info, debug, trace | ||
| 80 | # RUST_LOG=info | ||
| 81 | |||
| 82 | # ============================================================================ | ||
| 83 | # FUTURE/PLANNED OPTIONS (not yet implemented) | ||
| 84 | # ============================================================================ | ||
| 85 | |||
| 86 | # Proactive sync settings (GRASP-02) | ||
| 30 | # NGIT_PROACTIVE_SYNC_ENABLED=true | 87 | # NGIT_PROACTIVE_SYNC_ENABLED=true |
| 31 | # NGIT_PROACTIVE_SYNC_INTERVAL_SECS=3600 | 88 | # NGIT_PROACTIVE_SYNC_INTERVAL_SECS=3600 |
| 32 | 89 | ||
| 33 | # Optional: Archive mode (GRASP-05) | 90 | # Archive mode (GRASP-05) |
| 34 | # NGIT_ARCHIVE_MODE=false | 91 | # NGIT_ARCHIVE_MODE=false \ No newline at end of file |
| @@ -1352,6 +1352,7 @@ version = "0.1.0" | |||
| 1352 | dependencies = [ | 1352 | dependencies = [ |
| 1353 | "anyhow", | 1353 | "anyhow", |
| 1354 | "base64 0.22.1", | 1354 | "base64 0.22.1", |
| 1355 | "clap", | ||
| 1355 | "dotenvy", | 1356 | "dotenvy", |
| 1356 | "flate2", | 1357 | "flate2", |
| 1357 | "futures-util", | 1358 | "futures-util", |
| @@ -38,6 +38,7 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] } | |||
| 38 | 38 | ||
| 39 | # Configuration | 39 | # Configuration |
| 40 | dotenvy = "0.15" | 40 | dotenvy = "0.15" |
| 41 | clap = { version = "4.5", features = ["derive", "env"] } | ||
| 41 | 42 | ||
| 42 | # Error handling | 43 | # Error handling |
| 43 | anyhow = "1.0" | 44 | anyhow = "1.0" |
| @@ -100,15 +100,71 @@ nix develop -c cargo test --lib | |||
| 100 | 100 | ||
| 101 | ## Configuration | 101 | ## Configuration |
| 102 | 102 | ||
| 103 | Environment variables (see `.env.example`): | 103 | Configuration is loaded with the following priority (highest to lowest): |
| 104 | 104 | ||
| 105 | - `NGIT_DOMAIN`: Your domain (e.g., `gitnostr.com`) | 105 | 1. **CLI flags** (e.g., `--domain example.com`) |
| 106 | - `NGIT_OWNER_NPUB`: Relay owner's npub | 106 | 2. **Environment variables** (e.g., `NGIT_DOMAIN=example.com`) |
| 107 | - `NGIT_RELAY_NAME`: Relay name for NIP-11 | 107 | 3. **.env file** (loaded automatically if present) |
| 108 | - `NGIT_RELAY_DESCRIPTION`: Relay description | 108 | 4. **Built-in defaults** |
| 109 | - `NGIT_GIT_DATA_PATH`: Path to store Git repositories | 109 | |
| 110 | - `NGIT_RELAY_DATA_PATH`: Path to store Nostr events | 110 | This means CLI flags always take precedence over environment variables, which take precedence over `.env` file values. |
| 111 | - `NGIT_BIND_ADDRESS`: Server bind address (default: `127.0.0.1:8080`) | 111 | |
| 112 | ### CLI Usage | ||
| 113 | |||
| 114 | ```bash | ||
| 115 | # View all options with defaults | ||
| 116 | ngit-grasp --help | ||
| 117 | |||
| 118 | # Run with CLI flags (override everything else) | ||
| 119 | ngit-grasp --domain relay.example.com --owner-npub npub1... --bind-address 0.0.0.0:8080 | ||
| 120 | |||
| 121 | # Mix CLI flags with environment variables | ||
| 122 | NGIT_OWNER_NPUB=npub1... ngit-grasp --domain relay.example.com | ||
| 123 | ``` | ||
| 124 | |||
| 125 | ### Configuration Options | ||
| 126 | |||
| 127 | | Option | CLI Flag | Environment Variable | Default | | ||
| 128 | | ----------------- | --------------------- | ------------------------ | -------------------------------------------- | | ||
| 129 | | Domain | `--domain` | `NGIT_DOMAIN` | (required) | | ||
| 130 | | Owner npub | `--owner-npub` | `NGIT_OWNER_NPUB` | (optional) | | ||
| 131 | | Relay name | `--relay-name` | `NGIT_RELAY_NAME` | `${domain} grasp relay` | | ||
| 132 | | Relay description | `--relay-description` | `NGIT_RELAY_DESCRIPTION` | `Git Nostr Relay - a grasp implementation` | | ||
| 133 | | Git data path | `--git-data-path` | `NGIT_GIT_DATA_PATH` | `./data/git` (temp dir for memory backend) | | ||
| 134 | | Relay data path | `--relay-data-path` | `NGIT_RELAY_DATA_PATH` | `./data/relay` (temp dir for memory backend) | | ||
| 135 | | Bind address | `--bind-address` | `NGIT_BIND_ADDRESS` | `127.0.0.1:8080` | | ||
| 136 | | Database backend | `--database-backend` | `NGIT_DATABASE_BACKEND` | `lmdb` | | ||
| 137 | |||
| 138 | ### Database Backends | ||
| 139 | |||
| 140 | - `lmdb`: LMDB backend (default, persistent, general purpose) | ||
| 141 | - `memory`: In-memory database (fastest, no persistence - uses temp directories) | ||
| 142 | - `nostrdb`: NostrDB backend (persistent, optimized for Nostr) [Not yet implemented] | ||
| 143 | |||
| 144 | > **Note:** When using the `memory` backend, git data are automatically stored in temporary directories for ephemeral testing. This is useful for development and CI/CD pipelines. | ||
| 145 | |||
| 146 | ### Example: Production Deployment | ||
| 147 | |||
| 148 | ```bash | ||
| 149 | # Using environment variables (recommended for production) | ||
| 150 | export NGIT_DOMAIN=gitnostr.com | ||
| 151 | export NGIT_OWNER_NPUB=npub1... | ||
| 152 | export NGIT_BIND_ADDRESS=0.0.0.0:8080 | ||
| 153 | export NGIT_DATABASE_BACKEND=lmdb | ||
| 154 | ngit-grasp | ||
| 155 | ``` | ||
| 156 | |||
| 157 | ### Example: Development | ||
| 158 | |||
| 159 | ```bash | ||
| 160 | # Using .env file | ||
| 161 | cp .env.example .env | ||
| 162 | # Edit .env with your settings | ||
| 163 | ngit-grasp | ||
| 164 | |||
| 165 | # Or override specific values with CLI flags | ||
| 166 | ngit-grasp --domain localhost:3000 --bind-address 127.0.0.1:3000 | ||
| 167 | ``` | ||
| 112 | 168 | ||
| 113 | ## Documentation | 169 | ## Documentation |
| 114 | 170 | ||
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! 🚀 | ||
diff --git a/src/config.rs b/src/config.rs index 9b0d0b8..d095178 100644 --- a/src/config.rs +++ b/src/config.rs | |||
| @@ -1,74 +1,205 @@ | |||
| 1 | use anyhow::{Context, Result}; | 1 | use anyhow::Result; |
| 2 | use clap::{Parser, ValueEnum}; | ||
| 2 | use serde::{Deserialize, Serialize}; | 3 | use serde::{Deserialize, Serialize}; |
| 3 | use std::env; | ||
| 4 | 4 | ||
| 5 | /// Database backend type for the relay | 5 | /// Database backend type for the relay |
| 6 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] | 6 | #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default, ValueEnum)] |
| 7 | #[serde(rename_all = "lowercase")] | 7 | #[serde(rename_all = "lowercase")] |
| 8 | #[derive(Default)] | ||
| 9 | pub enum DatabaseBackend { | 8 | pub enum DatabaseBackend { |
| 10 | /// In-memory database (default, fastest, no persistence) | 9 | /// LMDB backend (persistent, general purpose) |
| 11 | #[default] | 10 | #[default] |
| 12 | Memory, | 11 | Lmdb, |
| 13 | /// NostrDB backend (persistent, optimized for Nostr) | 12 | /// NostrDB backend (persistent, optimized for Nostr) |
| 14 | NostrDb, | 13 | NostrDb, |
| 15 | /// LMDB backend (persistent, general purpose) | 14 | /// In-memory database (fastest, no persistence - uses temp directory for git data) |
| 16 | Lmdb, | 15 | Memory, |
| 17 | } | 16 | } |
| 18 | 17 | ||
| 19 | impl std::str::FromStr for DatabaseBackend { | 18 | impl std::fmt::Display for DatabaseBackend { |
| 20 | type Err = anyhow::Error; | 19 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| 21 | 20 | match self { | |
| 22 | fn from_str(s: &str) -> Result<Self> { | 21 | Self::Memory => write!(f, "memory"), |
| 23 | match s.to_lowercase().as_str() { | 22 | Self::NostrDb => write!(f, "nostrdb"), |
| 24 | "memory" => Ok(Self::Memory), | 23 | Self::Lmdb => write!(f, "lmdb"), |
| 25 | "nostrdb" => Ok(Self::NostrDb), | ||
| 26 | "lmdb" => Ok(Self::Lmdb), | ||
| 27 | _ => Err(anyhow::anyhow!( | ||
| 28 | "Invalid database backend: {}. Valid options: memory, nostrdb, lmdb", | ||
| 29 | s | ||
| 30 | )), | ||
| 31 | } | 24 | } |
| 32 | } | 25 | } |
| 33 | } | 26 | } |
| 34 | 27 | ||
| 35 | #[derive(Debug, Clone, Serialize, Deserialize)] | 28 | /// ngit-grasp - A GRASP (Git Relays Authorized via Signed-Nostr Proofs) implementation |
| 29 | /// | ||
| 30 | /// Configuration is loaded with the following priority (highest to lowest): | ||
| 31 | /// 1. CLI flags (e.g., --domain example.com) | ||
| 32 | /// 2. Environment variables (e.g., NGIT_DOMAIN=example.com) | ||
| 33 | /// 3. .env file (loaded automatically if present) | ||
| 34 | /// 4. Built-in defaults | ||
| 35 | #[derive(Debug, Clone, Serialize, Deserialize, Parser)] | ||
| 36 | #[command(author, version, about, long_about = None)] | ||
| 37 | #[command(propagate_version = true)] | ||
| 36 | pub struct Config { | 38 | pub struct Config { |
| 39 | /// Domain where this instance is hosted (required, used in GRASP validation) | ||
| 40 | #[arg(long, env = "NGIT_DOMAIN")] | ||
| 37 | pub domain: String, | 41 | pub domain: String, |
| 38 | pub owner_npub: String, | 42 | |
| 39 | pub relay_name: String, | 43 | /// Owner's npub (optional, for relay info in NIP-11) |
| 44 | #[arg(long, env = "NGIT_OWNER_NPUB")] | ||
| 45 | pub owner_npub: Option<String>, | ||
| 46 | |||
| 47 | /// Relay name for NIP-11 information document (defaults to "${domain} grasp relay") | ||
| 48 | #[arg(long = "relay-name", env = "NGIT_RELAY_NAME")] | ||
| 49 | pub relay_name_override: Option<String>, | ||
| 50 | |||
| 51 | /// Relay description for NIP-11 information document | ||
| 52 | #[arg( | ||
| 53 | long, | ||
| 54 | env = "NGIT_RELAY_DESCRIPTION", | ||
| 55 | default_value = "Git Nostr Relay - a grasp implementation" | ||
| 56 | )] | ||
| 40 | pub relay_description: String, | 57 | pub relay_description: String, |
| 58 | |||
| 59 | /// Path to store Git repositories | ||
| 60 | #[arg(long, env = "NGIT_GIT_DATA_PATH", default_value = "./data/git")] | ||
| 41 | pub git_data_path: String, | 61 | pub git_data_path: String, |
| 62 | |||
| 63 | /// Path to store Nostr relay data | ||
| 64 | #[arg(long, env = "NGIT_RELAY_DATA_PATH", default_value = "./data/relay")] | ||
| 42 | pub relay_data_path: String, | 65 | pub relay_data_path: String, |
| 66 | |||
| 67 | /// Server bind address (IP:PORT) | ||
| 68 | #[arg(long, env = "NGIT_BIND_ADDRESS", default_value = "127.0.0.1:8080")] | ||
| 43 | pub bind_address: String, | 69 | pub bind_address: String, |
| 70 | |||
| 71 | /// Database backend type | ||
| 72 | #[arg(long, env = "NGIT_DATABASE_BACKEND", value_enum, default_value_t = DatabaseBackend::Lmdb)] | ||
| 44 | pub database_backend: DatabaseBackend, | 73 | pub database_backend: DatabaseBackend, |
| 45 | } | 74 | } |
| 46 | 75 | ||
| 47 | impl Config { | 76 | impl Config { |
| 48 | pub fn from_env() -> Result<Self> { | 77 | /// Load configuration from CLI args, environment variables, and defaults. |
| 49 | // Load .env file if present | 78 | /// |
| 79 | /// Priority (highest to lowest): | ||
| 80 | /// 1. CLI flags | ||
| 81 | /// 2. Environment variables | ||
| 82 | /// 3. .env file | ||
| 83 | /// 4. Built-in defaults | ||
| 84 | pub fn load() -> Result<Self> { | ||
| 85 | // Load .env file if present (before clap parses, so env vars are available) | ||
| 50 | dotenvy::dotenv().ok(); | 86 | dotenvy::dotenv().ok(); |
| 51 | 87 | ||
| 52 | // Parse database backend from environment | 88 | // Parse CLI args (clap automatically handles env var fallback) |
| 53 | let database_backend = env::var("NGIT_DATABASE_BACKEND") | 89 | let config = Self::parse(); |
| 54 | .ok() | 90 | |
| 55 | .and_then(|s| s.parse().ok()) | 91 | Ok(config) |
| 56 | .unwrap_or_default(); | 92 | } |
| 57 | 93 | ||
| 58 | Ok(Config { | 94 | /// Get relay name (defaults to "${domain} grasp relay" if not set) |
| 59 | domain: env::var("NGIT_DOMAIN").unwrap_or_else(|_| "localhost:8080".to_string()), | 95 | pub fn relay_name(&self) -> String { |
| 60 | owner_npub: env::var("NGIT_OWNER_NPUB").context("NGIT_OWNER_NPUB must be set")?, | 96 | self.relay_name_override |
| 61 | relay_name: env::var("NGIT_RELAY_NAME") | 97 | .clone() |
| 62 | .unwrap_or_else(|_| "ngit-grasp relay".to_string()), | 98 | .unwrap_or_else(|| format!("{} grasp relay", self.domain)) |
| 63 | relay_description: env::var("NGIT_RELAY_DESCRIPTION") | 99 | } |
| 64 | .unwrap_or_else(|_| "A GRASP-compliant Nostr relay for Git".to_string()), | 100 | |
| 65 | git_data_path: env::var("NGIT_GIT_DATA_PATH") | 101 | /// Get effective git data path |
| 66 | .unwrap_or_else(|_| "./data/git".to_string()), | 102 | /// Returns a temp directory when using memory backend, otherwise the configured path |
| 67 | relay_data_path: env::var("NGIT_RELAY_DATA_PATH") | 103 | pub fn effective_git_data_path(&self) -> String { |
| 68 | .unwrap_or_else(|_| "./data/relay".to_string()), | 104 | if self.database_backend == DatabaseBackend::Memory { |
| 69 | bind_address: env::var("NGIT_BIND_ADDRESS") | 105 | std::env::temp_dir() |
| 70 | .unwrap_or_else(|_| "127.0.0.1:8080".to_string()), | 106 | .join("ngit-grasp-git") |
| 71 | database_backend, | 107 | .to_string_lossy() |
| 72 | }) | 108 | .into_owned() |
| 109 | } else { | ||
| 110 | self.git_data_path.clone() | ||
| 111 | } | ||
| 112 | } | ||
| 113 | |||
| 114 | /// Create config for testing | ||
| 115 | #[cfg(test)] | ||
| 116 | pub fn for_testing() -> Self { | ||
| 117 | Self { | ||
| 118 | domain: "localhost:8080".to_string(), | ||
| 119 | owner_npub: Some("npub1test".to_string()), | ||
| 120 | relay_name_override: Some("test relay".to_string()), | ||
| 121 | relay_description: "test description".to_string(), | ||
| 122 | git_data_path: "./test_data/git".to_string(), | ||
| 123 | relay_data_path: "./test_data/relay".to_string(), | ||
| 124 | bind_address: "127.0.0.1:8080".to_string(), | ||
| 125 | database_backend: DatabaseBackend::Memory, | ||
| 126 | } | ||
| 127 | } | ||
| 128 | } | ||
| 129 | |||
| 130 | #[cfg(test)] | ||
| 131 | mod tests { | ||
| 132 | use super::*; | ||
| 133 | |||
| 134 | #[test] | ||
| 135 | fn test_default_values() { | ||
| 136 | let config = Config::for_testing(); | ||
| 137 | assert_eq!(config.domain, "localhost:8080"); | ||
| 138 | assert_eq!(config.bind_address, "127.0.0.1:8080"); | ||
| 139 | // for_testing() uses Memory, but the actual default is Lmdb | ||
| 140 | assert_eq!(config.database_backend, DatabaseBackend::Memory); | ||
| 141 | } | ||
| 142 | |||
| 143 | #[test] | ||
| 144 | fn test_lmdb_is_default() { | ||
| 145 | // Verify the actual default via the enum's Default trait | ||
| 146 | assert_eq!(DatabaseBackend::default(), DatabaseBackend::Lmdb); | ||
| 147 | } | ||
| 148 | |||
| 149 | #[test] | ||
| 150 | fn test_memory_backend_uses_temp_dir() { | ||
| 151 | let config = Config { | ||
| 152 | database_backend: DatabaseBackend::Memory, | ||
| 153 | ..Config::for_testing() | ||
| 154 | }; | ||
| 155 | let git_path = config.effective_git_data_path(); | ||
| 156 | assert!(git_path.contains("ngit-grasp-git")); | ||
| 157 | } | ||
| 158 | |||
| 159 | #[test] | ||
| 160 | fn test_lmdb_backend_uses_configured_path() { | ||
| 161 | let config = Config { | ||
| 162 | database_backend: DatabaseBackend::Lmdb, | ||
| 163 | git_data_path: "./my/git/path".to_string(), | ||
| 164 | relay_data_path: "./my/relay/path".to_string(), | ||
| 165 | ..Config::for_testing() | ||
| 166 | }; | ||
| 167 | assert_eq!(config.effective_git_data_path(), "./my/git/path"); | ||
| 168 | } | ||
| 169 | |||
| 170 | #[test] | ||
| 171 | fn test_database_backend_display() { | ||
| 172 | assert_eq!(DatabaseBackend::Memory.to_string(), "memory"); | ||
| 173 | assert_eq!(DatabaseBackend::NostrDb.to_string(), "nostrdb"); | ||
| 174 | assert_eq!(DatabaseBackend::Lmdb.to_string(), "lmdb"); | ||
| 175 | } | ||
| 176 | |||
| 177 | #[test] | ||
| 178 | fn test_relay_name_default() { | ||
| 179 | let config = Config { | ||
| 180 | domain: "example.com".to_string(), | ||
| 181 | relay_name_override: None, | ||
| 182 | ..Config::for_testing() | ||
| 183 | }; | ||
| 184 | assert_eq!(config.relay_name(), "example.com grasp relay"); | ||
| 185 | } | ||
| 186 | |||
| 187 | #[test] | ||
| 188 | fn test_relay_name_override() { | ||
| 189 | let config = Config { | ||
| 190 | domain: "example.com".to_string(), | ||
| 191 | relay_name_override: Some("My Custom Relay".to_string()), | ||
| 192 | ..Config::for_testing() | ||
| 193 | }; | ||
| 194 | assert_eq!(config.relay_name(), "My Custom Relay"); | ||
| 195 | } | ||
| 196 | |||
| 197 | #[test] | ||
| 198 | fn test_owner_npub_optional() { | ||
| 199 | let config = Config { | ||
| 200 | owner_npub: None, | ||
| 201 | ..Config::for_testing() | ||
| 202 | }; | ||
| 203 | assert!(config.owner_npub.is_none()); | ||
| 73 | } | 204 | } |
| 74 | } | 205 | } |
diff --git a/src/http/landing.rs b/src/http/landing.rs index b978851..f9fca5b 100644 --- a/src/http/landing.rs +++ b/src/http/landing.rs | |||
| @@ -282,7 +282,7 @@ pub fn get_html(config: &Config) -> String { | |||
| 282 | format!( | 282 | format!( |
| 283 | include_str!("../../templates/landing.html"), | 283 | include_str!("../../templates/landing.html"), |
| 284 | base_css = get_base_css(), | 284 | base_css = get_base_css(), |
| 285 | relay_name = config.relay_name, | 285 | relay_name = config.relay_name(), |
| 286 | relay_description = config.relay_description, | 286 | relay_description = config.relay_description, |
| 287 | version = get_version(), | 287 | version = get_version(), |
| 288 | curation = curation, | 288 | curation = curation, |
| @@ -357,7 +357,7 @@ pub fn get_generic_404_html(config: &Config, path: &str) -> String { | |||
| 357 | </body> | 357 | </body> |
| 358 | </html>"##, | 358 | </html>"##, |
| 359 | base_css = get_base_css(), | 359 | base_css = get_base_css(), |
| 360 | relay_name = config.relay_name, | 360 | relay_name = config.relay_name(), |
| 361 | path = path, | 361 | path = path, |
| 362 | version = get_version(), | 362 | version = get_version(), |
| 363 | footer_script = get_footer_script(), | 363 | footer_script = get_footer_script(), |
| @@ -456,7 +456,7 @@ pub fn get_404_html(config: &Config, npub: &str, identifier: &str) -> String { | |||
| 456 | </body> | 456 | </body> |
| 457 | </html>"##, | 457 | </html>"##, |
| 458 | base_css = get_base_css(), | 458 | base_css = get_base_css(), |
| 459 | relay_name = config.relay_name, | 459 | relay_name = config.relay_name(), |
| 460 | npub = npub, | 460 | npub = npub, |
| 461 | identifier = identifier, | 461 | identifier = identifier, |
| 462 | version = get_version(), | 462 | version = get_version(), |
| @@ -598,7 +598,7 @@ pub fn get_repo_html(config: &Config, npub: &str, identifier: &str) -> String { | |||
| 598 | </body> | 598 | </body> |
| 599 | </html>"##, | 599 | </html>"##, |
| 600 | base_css = get_base_css(), | 600 | base_css = get_base_css(), |
| 601 | relay_name = config.relay_name, | 601 | relay_name = config.relay_name(), |
| 602 | npub = npub, | 602 | npub = npub, |
| 603 | identifier = identifier, | 603 | identifier = identifier, |
| 604 | version = get_version(), | 604 | version = get_version(), |
diff --git a/src/http/mod.rs b/src/http/mod.rs index 4665281..8b1f687 100644 --- a/src/http/mod.rs +++ b/src/http/mod.rs | |||
| @@ -118,7 +118,7 @@ impl Service<Request<Incoming>> for HttpService { | |||
| 118 | let path = req.uri().path().to_string(); | 118 | let path = req.uri().path().to_string(); |
| 119 | let query = req.uri().query().map(|s| s.to_string()); | 119 | let query = req.uri().query().map(|s| s.to_string()); |
| 120 | let method = req.method().clone(); | 120 | let method = req.method().clone(); |
| 121 | let git_data_path = self.config.git_data_path.clone(); | 121 | let git_data_path = self.config.effective_git_data_path(); |
| 122 | let database = self.database.clone(); | 122 | let database = self.database.clone(); |
| 123 | 123 | ||
| 124 | // Handle OPTIONS preflight requests (CORS) | 124 | // Handle OPTIONS preflight requests (CORS) |
| @@ -427,7 +427,7 @@ pub async fn run_server( | |||
| 427 | let bind_addr: SocketAddr = config.bind_address.parse()?; | 427 | let bind_addr: SocketAddr = config.bind_address.parse()?; |
| 428 | 428 | ||
| 429 | tracing::info!("Starting HTTP server on {}", bind_addr); | 429 | tracing::info!("Starting HTTP server on {}", bind_addr); |
| 430 | tracing::info!("Relay name: {}", config.relay_name); | 430 | tracing::info!("Relay name: {}", config.relay_name()); |
| 431 | tracing::info!("Domain: {}", config.domain); | 431 | tracing::info!("Domain: {}", config.domain); |
| 432 | 432 | ||
| 433 | let listener = TcpListener::bind(&bind_addr).await?; | 433 | let listener = TcpListener::bind(&bind_addr).await?; |
diff --git a/src/http/nip11.rs b/src/http/nip11.rs index ecb9769..e6a1e46 100644 --- a/src/http/nip11.rs +++ b/src/http/nip11.rs | |||
| @@ -57,9 +57,9 @@ impl RelayInformationDocument { | |||
| 57 | /// Create NIP-11 relay information document from configuration | 57 | /// Create NIP-11 relay information document from configuration |
| 58 | pub fn from_config(config: &Config) -> Self { | 58 | pub fn from_config(config: &Config) -> Self { |
| 59 | Self { | 59 | Self { |
| 60 | name: config.relay_name.clone(), | 60 | name: config.relay_name(), |
| 61 | description: config.relay_description.clone(), | 61 | description: config.relay_description.clone(), |
| 62 | pubkey: Some(config.owner_npub.clone()), | 62 | pubkey: config.owner_npub.clone(), |
| 63 | contact: None, // Could be added to config if needed | 63 | contact: None, // Could be added to config if needed |
| 64 | supported_nips: vec![ | 64 | supported_nips: vec![ |
| 65 | 1, // NIP-01: Basic protocol flow | 65 | 1, // NIP-01: Basic protocol flow |
| @@ -98,8 +98,8 @@ mod tests { | |||
| 98 | fn test_relay_information_document_structure() { | 98 | fn test_relay_information_document_structure() { |
| 99 | let config = Config { | 99 | let config = Config { |
| 100 | domain: "relay.example.com".to_string(), | 100 | domain: "relay.example.com".to_string(), |
| 101 | owner_npub: "npub1test".to_string(), | 101 | owner_npub: Some("npub1test".to_string()), |
| 102 | relay_name: "Test Relay".to_string(), | 102 | relay_name_override: Some("Test Relay".to_string()), |
| 103 | relay_description: "A test relay".to_string(), | 103 | relay_description: "A test relay".to_string(), |
| 104 | git_data_path: "./data/git".to_string(), | 104 | git_data_path: "./data/git".to_string(), |
| 105 | relay_data_path: "./data/relay".to_string(), | 105 | relay_data_path: "./data/relay".to_string(), |
| @@ -128,8 +128,8 @@ mod tests { | |||
| 128 | fn test_relay_information_document_json() { | 128 | fn test_relay_information_document_json() { |
| 129 | let config = Config { | 129 | let config = Config { |
| 130 | domain: "relay.example.com".to_string(), | 130 | domain: "relay.example.com".to_string(), |
| 131 | owner_npub: "npub1test".to_string(), | 131 | owner_npub: Some("npub1test".to_string()), |
| 132 | relay_name: "Test Relay".to_string(), | 132 | relay_name_override: Some("Test Relay".to_string()), |
| 133 | relay_description: "A test relay".to_string(), | 133 | relay_description: "A test relay".to_string(), |
| 134 | git_data_path: "./data/git".to_string(), | 134 | git_data_path: "./data/git".to_string(), |
| 135 | relay_data_path: "./data/relay".to_string(), | 135 | relay_data_path: "./data/relay".to_string(), |
diff --git a/src/main.rs b/src/main.rs index 1f18ab2..f80e920 100644 --- a/src/main.rs +++ b/src/main.rs | |||
| @@ -2,7 +2,10 @@ use anyhow::Result; | |||
| 2 | use tracing::{info, Level}; | 2 | use tracing::{info, Level}; |
| 3 | use tracing_subscriber::FmtSubscriber; | 3 | use tracing_subscriber::FmtSubscriber; |
| 4 | 4 | ||
| 5 | use ngit_grasp::{config::Config, http, nostr}; | 5 | use ngit_grasp::{ |
| 6 | config::{Config, DatabaseBackend}, | ||
| 7 | http, nostr, | ||
| 8 | }; | ||
| 6 | 9 | ||
| 7 | #[tokio::main] | 10 | #[tokio::main] |
| 8 | async fn main() -> Result<()> { | 11 | async fn main() -> Result<()> { |
| @@ -14,10 +17,17 @@ async fn main() -> Result<()> { | |||
| 14 | 17 | ||
| 15 | info!("Starting ngit-grasp with nostr-relay-builder..."); | 18 | info!("Starting ngit-grasp with nostr-relay-builder..."); |
| 16 | 19 | ||
| 17 | // Load configuration | 20 | // Load configuration (priority: CLI flags > env vars > .env file > defaults) |
| 18 | let config = Config::from_env()?; | 21 | let config = Config::load()?; |
| 22 | |||
| 19 | info!("Configuration loaded: {}", config.bind_address); | 23 | info!("Configuration loaded: {}", config.bind_address); |
| 20 | info!("Git data directory: {}", config.git_data_path); | 24 | info!("Domain: {}", config.domain); |
| 25 | info!("Relay name: {}", config.relay_name()); | ||
| 26 | info!("Git data directory: {}", config.effective_git_data_path()); | ||
| 27 | if config.database_backend != DatabaseBackend::Memory { | ||
| 28 | info!("Relay data directory: {}", config.relay_data_path); | ||
| 29 | } | ||
| 30 | info!("Database backend: {}", config.database_backend); | ||
| 21 | 31 | ||
| 22 | // Create Nostr relay with NIP-34 validation | 32 | // Create Nostr relay with NIP-34 validation |
| 23 | // Returns both the relay and database for direct queries in handlers | 33 | // Returns both the relay and database for direct queries in handlers |
diff --git a/src/nostr/builder.rs b/src/nostr/builder.rs index eabb38f..904cba4 100644 --- a/src/nostr/builder.rs +++ b/src/nostr/builder.rs | |||
| @@ -1203,22 +1203,31 @@ pub fn create_relay(config: &Config) -> Result<RelayWithDatabase> { | |||
| 1203 | tracing::info!("Using LMDB backend at: {}", db_path.display()); | 1203 | tracing::info!("Using LMDB backend at: {}", db_path.display()); |
| 1204 | // Ensure the database directory exists | 1204 | // Ensure the database directory exists |
| 1205 | std::fs::create_dir_all(db_path).map_err(|e| { | 1205 | std::fs::create_dir_all(db_path).map_err(|e| { |
| 1206 | anyhow::anyhow!("Failed to create LMDB directory {}: {}", db_path.display(), e) | 1206 | anyhow::anyhow!( |
| 1207 | "Failed to create LMDB directory {}: {}", | ||
| 1208 | db_path.display(), | ||
| 1209 | e | ||
| 1210 | ) | ||
| 1207 | })?; | 1211 | })?; |
| 1208 | Arc::new(NostrLMDB::open(db_path).map_err(|e| { | 1212 | Arc::new(NostrLMDB::open(db_path).map_err(|e| { |
| 1209 | anyhow::anyhow!("Failed to open LMDB database at {}: {}", db_path.display(), e) | 1213 | anyhow::anyhow!( |
| 1214 | "Failed to open LMDB database at {}: {}", | ||
| 1215 | db_path.display(), | ||
| 1216 | e | ||
| 1217 | ) | ||
| 1210 | })?) | 1218 | })?) |
| 1211 | } | 1219 | } |
| 1212 | }; | 1220 | }; |
| 1213 | 1221 | ||
| 1214 | // Build relay with GRASP-01 validation | 1222 | // Build relay with GRASP-01 validation |
| 1215 | // Clone Arc for the write policy so both relay and policy can access the database | 1223 | // Clone Arc for the write policy so both relay and policy can access the database |
| 1224 | let git_data_path = config.effective_git_data_path(); | ||
| 1216 | let builder = RelayBuilder::default() | 1225 | let builder = RelayBuilder::default() |
| 1217 | .database(database.clone()) | 1226 | .database(database.clone()) |
| 1218 | .write_policy(Nip34WritePolicy::new( | 1227 | .write_policy(Nip34WritePolicy::new( |
| 1219 | &config.domain, | 1228 | &config.domain, |
| 1220 | database.clone(), | 1229 | database.clone(), |
| 1221 | &config.git_data_path, | 1230 | &git_data_path, |
| 1222 | )); | 1231 | )); |
| 1223 | 1232 | ||
| 1224 | tracing::info!( | 1233 | tracing::info!( |