diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2026-01-11 15:09:46 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2026-01-11 15:09:46 +0000 |
| commit | 51a3d810d2a73a6300c51d3baf6060bb33f6b92c (patch) | |
| tree | 8ee4cffadb787cfdbf96ce133c01fd2884048d57 /docs/how-to | |
| parent | 2b082868713cb889677332344b559e45cf6a3c6c (diff) | |
docs: add production deployment how-to guide
- Complete guide for deploying ngit-grasp to NixOS servers
- Step-by-step deployment instructions
- Configuration options reference
- Troubleshooting section
- Security hardening recommendations
- Multiple instance examples
- References nix/example-configuration.nix which has clear examples
Diffstat (limited to 'docs/how-to')
| -rw-r--r-- | docs/how-to/deploy.md | 487 |
1 files changed, 487 insertions, 0 deletions
diff --git a/docs/how-to/deploy.md b/docs/how-to/deploy.md new file mode 100644 index 0000000..6fe8776 --- /dev/null +++ b/docs/how-to/deploy.md | |||
| @@ -0,0 +1,487 @@ | |||
| 1 | # How-To: Deploy ngit-grasp to Production | ||
| 2 | |||
| 3 | **Purpose:** Deploy ngit-grasp to a production NixOS server | ||
| 4 | **Difficulty:** Intermediate | ||
| 5 | **Time:** 30-60 minutes | ||
| 6 | |||
| 7 | --- | ||
| 8 | |||
| 9 | ## Problem | ||
| 10 | |||
| 11 | You want to: | ||
| 12 | - Deploy ngit-grasp to a NixOS server | ||
| 13 | - Configure it as a systemd service | ||
| 14 | - Set up reverse proxy (Caddy) | ||
| 15 | - Ensure proper security and monitoring | ||
| 16 | |||
| 17 | --- | ||
| 18 | |||
| 19 | ## Prerequisites | ||
| 20 | |||
| 21 | - NixOS server with SSH access | ||
| 22 | - Flakes enabled on server and local machine | ||
| 23 | - Domain name configured (DNS pointing to server) | ||
| 24 | - Basic knowledge of NixOS configuration | ||
| 25 | |||
| 26 | --- | ||
| 27 | |||
| 28 | ## Solution | ||
| 29 | |||
| 30 | ### Step 1: Add ngit-grasp to Your Server's Flake | ||
| 31 | |||
| 32 | In your server's `flake.nix`, add ngit-grasp as an input: | ||
| 33 | |||
| 34 | ```nix | ||
| 35 | { | ||
| 36 | inputs = { | ||
| 37 | nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11"; | ||
| 38 | ngit-grasp.url = "github:DanConwayDev/ngit-grasp"; | ||
| 39 | # or use a specific git repository: | ||
| 40 | # ngit-grasp.url = "git+https://git.shakespeare.diy/npub.../ngit-grasp.git"; | ||
| 41 | }; | ||
| 42 | |||
| 43 | outputs = { self, nixpkgs, ngit-grasp, ... }@inputs: { | ||
| 44 | nixosConfigurations.your-hostname = nixpkgs.lib.nixosSystem { | ||
| 45 | system = "x86_64-linux"; | ||
| 46 | specialArgs = { inherit inputs; }; | ||
| 47 | modules = [ | ||
| 48 | ./configuration.nix | ||
| 49 | # ... other modules | ||
| 50 | ]; | ||
| 51 | }; | ||
| 52 | }; | ||
| 53 | } | ||
| 54 | ``` | ||
| 55 | |||
| 56 | --- | ||
| 57 | |||
| 58 | ### Step 2: Create Service Configuration | ||
| 59 | |||
| 60 | Create a new file for your ngit-grasp service (e.g., `services/ngit-grasp.nix`): | ||
| 61 | |||
| 62 | ```nix | ||
| 63 | { inputs, ... }: | ||
| 64 | |||
| 65 | { | ||
| 66 | imports = [ inputs.ngit-grasp.nixosModules.default ]; | ||
| 67 | |||
| 68 | services.ngit-grasp.production = { | ||
| 69 | enable = true; | ||
| 70 | domain = "ngit.example.com"; | ||
| 71 | |||
| 72 | # Network | ||
| 73 | bindAddress = "127.0.0.1"; | ||
| 74 | port = 8082; | ||
| 75 | |||
| 76 | # Storage | ||
| 77 | dataDir = "/persistent/ngit-grasp"; | ||
| 78 | |||
| 79 | # Identity | ||
| 80 | relayName = "My GRASP Relay"; | ||
| 81 | relayDescription = "A Rust GRASP implementation with proactive sync"; | ||
| 82 | relayOwnerNsecFile = "/persistent/ngit-grasp/relay-owner.nsec"; | ||
| 83 | |||
| 84 | # Sync - bootstrap from relay.ngit.dev | ||
| 85 | syncBootstrapRelayUrl = "wss://relay.ngit.dev"; | ||
| 86 | |||
| 87 | # Metrics | ||
| 88 | metricsEnabled = true; | ||
| 89 | |||
| 90 | # Logging | ||
| 91 | logLevel = "info"; | ||
| 92 | }; | ||
| 93 | |||
| 94 | # Caddy reverse proxy | ||
| 95 | services.caddy.virtualHosts."ngit.example.com" = { | ||
| 96 | extraConfig = '' | ||
| 97 | reverse_proxy 127.0.0.1:8082 { | ||
| 98 | header_down X-Real-IP {http.request.remote} | ||
| 99 | header_down X-Forwarded-For {http.request.remote} | ||
| 100 | } | ||
| 101 | ''; | ||
| 102 | }; | ||
| 103 | } | ||
| 104 | ``` | ||
| 105 | |||
| 106 | **Key configuration options:** | ||
| 107 | |||
| 108 | - **Instance name** (`production`): Can be any name. Used for systemd service (`ngit-grasp-production`) | ||
| 109 | - **domain**: Your relay's domain (used in GRASP validation) | ||
| 110 | - **port**: Local port (use reverse proxy for HTTPS) | ||
| 111 | - **dataDir**: Where git repos and database are stored | ||
| 112 | - **relayOwnerNsecFile**: Path to file containing relay owner's nsec | ||
| 113 | - If file doesn't exist, ngit-grasp will auto-generate one | ||
| 114 | - Alternative: `relayOwnerNsec = "nsec1..."` (less secure, in nix store) | ||
| 115 | - **syncBootstrapRelayUrl**: Bootstrap relay to sync from on startup | ||
| 116 | |||
| 117 | See [nix/example-configuration.nix](../../nix/example-configuration.nix) for more examples. | ||
| 118 | |||
| 119 | --- | ||
| 120 | |||
| 121 | ### Step 3: Import the Service | ||
| 122 | |||
| 123 | Import your service configuration in your main configuration file: | ||
| 124 | |||
| 125 | ```nix | ||
| 126 | # In configuration.nix or services/default.nix | ||
| 127 | { | ||
| 128 | imports = [ | ||
| 129 | ./services/ngit-grasp.nix | ||
| 130 | # ... other services | ||
| 131 | ]; | ||
| 132 | } | ||
| 133 | ``` | ||
| 134 | |||
| 135 | --- | ||
| 136 | |||
| 137 | ### Step 4: Update Flake Lock | ||
| 138 | |||
| 139 | ```bash | ||
| 140 | cd /path/to/server/config | ||
| 141 | nix flake update ngit-grasp | ||
| 142 | git add flake.lock | ||
| 143 | git commit -m "Add ngit-grasp and update flake.lock" | ||
| 144 | ``` | ||
| 145 | |||
| 146 | --- | ||
| 147 | |||
| 148 | ### Step 5: Validate Configuration | ||
| 149 | |||
| 150 | Before deploying, validate the configuration builds: | ||
| 151 | |||
| 152 | ```bash | ||
| 153 | nix flake check | ||
| 154 | ``` | ||
| 155 | |||
| 156 | --- | ||
| 157 | |||
| 158 | ### Step 6: Deploy to Server | ||
| 159 | |||
| 160 | Deploy the new configuration to your server: | ||
| 161 | |||
| 162 | ```bash | ||
| 163 | # Build and switch in one command (builds on server) | ||
| 164 | nixos-rebuild switch --flake .#your-hostname \ | ||
| 165 | --target-host user@server.example.com \ | ||
| 166 | --use-remote-sudo \ | ||
| 167 | --build-host user@server.example.com | ||
| 168 | ``` | ||
| 169 | |||
| 170 | **Alternative:** Build locally, then deploy: | ||
| 171 | |||
| 172 | ```bash | ||
| 173 | # Build locally | ||
| 174 | nixos-rebuild build --flake .#your-hostname | ||
| 175 | |||
| 176 | # Deploy to server | ||
| 177 | nixos-rebuild switch --flake .#your-hostname \ | ||
| 178 | --target-host user@server.example.com \ | ||
| 179 | --use-remote-sudo | ||
| 180 | ``` | ||
| 181 | |||
| 182 | **Note:** Building locally requires your machine to trust the server's nix signing key. | ||
| 183 | |||
| 184 | --- | ||
| 185 | |||
| 186 | ### Step 7: Verify Deployment | ||
| 187 | |||
| 188 | SSH to the server and check the service: | ||
| 189 | |||
| 190 | ```bash | ||
| 191 | ssh user@server.example.com | ||
| 192 | |||
| 193 | # Check service status | ||
| 194 | systemctl status ngit-grasp-production | ||
| 195 | |||
| 196 | # View logs | ||
| 197 | journalctl -u ngit-grasp-production -f | ||
| 198 | |||
| 199 | # Check if listening on port | ||
| 200 | ss -tlnp | grep 8082 | ||
| 201 | ``` | ||
| 202 | |||
| 203 | --- | ||
| 204 | |||
| 205 | ### Step 8: Test Functionality | ||
| 206 | |||
| 207 | From your local machine, test the relay: | ||
| 208 | |||
| 209 | ```bash | ||
| 210 | # Test NIP-11 relay info | ||
| 211 | curl https://ngit.example.com -H "Accept: application/nostr+json" | jq | ||
| 212 | |||
| 213 | # Test WebSocket connection | ||
| 214 | websocat wss://ngit.example.com | ||
| 215 | # Then type: ["REQ","test",{}] | ||
| 216 | # Should receive events | ||
| 217 | |||
| 218 | # Test git clone (if you have repos) | ||
| 219 | git ls-remote https://ngit.example.com/<npub>/<repo>.git | ||
| 220 | ``` | ||
| 221 | |||
| 222 | --- | ||
| 223 | |||
| 224 | ## Configuration Options | ||
| 225 | |||
| 226 | ### Required | ||
| 227 | - `enable` - Enable this instance | ||
| 228 | - `domain` - Domain where relay is hosted | ||
| 229 | |||
| 230 | ### Network | ||
| 231 | - `bindAddress` - IP to bind to (default: "127.0.0.1") | ||
| 232 | - `port` - Port to listen on (default: 8080) | ||
| 233 | |||
| 234 | ### Storage | ||
| 235 | - `dataDir` - Base directory for data (default: /var/lib/ngit-grasp-{name}) | ||
| 236 | - `databaseBackend` - "lmdb" | "nostr-db" | "memory" (default: "lmdb") | ||
| 237 | |||
| 238 | ### Identity | ||
| 239 | - `relayName` - Relay name for NIP-11 (default: "{domain} grasp relay") | ||
| 240 | - `relayDescription` - Relay description | ||
| 241 | - `relayOwnerNsecFile` - Path to file with relay owner nsec (recommended) | ||
| 242 | - `relayOwnerNsec` - Inline nsec (less secure) | ||
| 243 | |||
| 244 | ### Sync | ||
| 245 | - `syncBootstrapRelayUrl` - Bootstrap relay URL (optional) | ||
| 246 | - `syncDisableNegentropy` - Disable NIP-77 negentropy (default: false) | ||
| 247 | - `syncMaxBackoffSecs` - Max backoff for reconnection (default: 3600) | ||
| 248 | - `syncDisconnectCheckIntervalSecs` - Check interval (default: 60) | ||
| 249 | - `syncBaseBackoffSecs` - Base backoff time (default: 5) | ||
| 250 | |||
| 251 | ### Metrics | ||
| 252 | - `metricsEnabled` - Enable /metrics endpoint (default: true) | ||
| 253 | - `metricsConnectionPerIpAbuseThreshold` - Abuse threshold (default: 10) | ||
| 254 | - `metricsTopNRepos` - Number of top repos to track (default: 10) | ||
| 255 | |||
| 256 | ### Logging | ||
| 257 | - `logLevel` - "trace" | "debug" | "info" | "warn" | "error" (default: "info") | ||
| 258 | |||
| 259 | ### Security | ||
| 260 | - `user` - User to run as (default: "ngit-grasp-{name}") | ||
| 261 | - `group` - Group to run as (default: "ngit-grasp") | ||
| 262 | |||
| 263 | See [nix/module.nix](../../nix/module.nix) for complete option definitions. | ||
| 264 | |||
| 265 | --- | ||
| 266 | |||
| 267 | ## Systemd Service | ||
| 268 | |||
| 269 | The NixOS module creates a systemd service: `ngit-grasp-{instance-name}` | ||
| 270 | |||
| 271 | ```bash | ||
| 272 | # Start/stop/restart | ||
| 273 | systemctl start ngit-grasp-production | ||
| 274 | systemctl stop ngit-grasp-production | ||
| 275 | systemctl restart ngit-grasp-production | ||
| 276 | |||
| 277 | # Enable/disable autostart | ||
| 278 | systemctl enable ngit-grasp-production | ||
| 279 | systemctl disable ngit-grasp-production | ||
| 280 | |||
| 281 | # View logs | ||
| 282 | journalctl -u ngit-grasp-production -f | ||
| 283 | journalctl -u ngit-grasp-production --since "1 hour ago" | ||
| 284 | |||
| 285 | # Check status | ||
| 286 | systemctl status ngit-grasp-production | ||
| 287 | ``` | ||
| 288 | |||
| 289 | --- | ||
| 290 | |||
| 291 | ## Multiple Instances | ||
| 292 | |||
| 293 | You can run multiple instances on the same server: | ||
| 294 | |||
| 295 | ```nix | ||
| 296 | services.ngit-grasp = { | ||
| 297 | production = { | ||
| 298 | enable = true; | ||
| 299 | domain = "ngit.example.com"; | ||
| 300 | port = 8082; | ||
| 301 | dataDir = "/persistent/ngit-production"; | ||
| 302 | }; | ||
| 303 | |||
| 304 | staging = { | ||
| 305 | enable = true; | ||
| 306 | domain = "ngit-staging.example.com"; | ||
| 307 | port = 8083; | ||
| 308 | dataDir = "/persistent/ngit-staging"; | ||
| 309 | logLevel = "debug"; | ||
| 310 | }; | ||
| 311 | }; | ||
| 312 | ``` | ||
| 313 | |||
| 314 | Each instance: | ||
| 315 | - Runs as separate systemd service: `ngit-grasp-production`, `ngit-grasp-staging` | ||
| 316 | - Has its own user: `ngit-grasp-production`, `ngit-grasp-staging` | ||
| 317 | - Stores data in separate directory | ||
| 318 | - Can have different configuration | ||
| 319 | |||
| 320 | --- | ||
| 321 | |||
| 322 | ## Troubleshooting | ||
| 323 | |||
| 324 | ### Service won't start | ||
| 325 | |||
| 326 | **Check logs:** | ||
| 327 | ```bash | ||
| 328 | journalctl -u ngit-grasp-production -n 50 | ||
| 329 | ``` | ||
| 330 | |||
| 331 | **Common issues:** | ||
| 332 | - Port already in use: Check with `ss -tlnp | grep 8082` | ||
| 333 | - Data directory permissions: Should be owned by service user | ||
| 334 | - Invalid nsec file: Check file exists and contains valid nsec | ||
| 335 | |||
| 336 | ### Can't connect via WebSocket | ||
| 337 | |||
| 338 | **Check:** | ||
| 339 | - Service is running: `systemctl status ngit-grasp-production` | ||
| 340 | - Firewall allows connections: `nix-shell -p nmap --run "nmap -p 443 ngit.example.com"` | ||
| 341 | - Caddy is configured correctly: `systemctl status caddy` | ||
| 342 | - DNS resolves: `dig ngit.example.com` | ||
| 343 | |||
| 344 | ### Sync not working | ||
| 345 | |||
| 346 | **Check logs for sync errors:** | ||
| 347 | ```bash | ||
| 348 | journalctl -u ngit-grasp-production | grep -i sync | ||
| 349 | ``` | ||
| 350 | |||
| 351 | **Common issues:** | ||
| 352 | - Bootstrap relay URL incorrect or unreachable | ||
| 353 | - Network connectivity issues | ||
| 354 | - Bootstrap relay doesn't support negentropy (disable with `syncDisableNegentropy = true`) | ||
| 355 | |||
| 356 | ### High memory/CPU usage | ||
| 357 | |||
| 358 | **Monitor metrics:** | ||
| 359 | ```bash | ||
| 360 | curl http://localhost:8082/metrics | ||
| 361 | ``` | ||
| 362 | |||
| 363 | **Tune configuration:** | ||
| 364 | - Reduce `metricsTopNRepos` | ||
| 365 | - Increase `syncMaxBackoffSecs` | ||
| 366 | - Switch to `databaseBackend = "nostr-db"` for better performance | ||
| 367 | |||
| 368 | --- | ||
| 369 | |||
| 370 | ## Rollback | ||
| 371 | |||
| 372 | If deployment fails, rollback to previous configuration: | ||
| 373 | |||
| 374 | ```bash | ||
| 375 | # On the server | ||
| 376 | nixos-rebuild switch --rollback | ||
| 377 | |||
| 378 | # Or remotely | ||
| 379 | nixos-rebuild switch --rollback \ | ||
| 380 | --target-host user@server.example.com \ | ||
| 381 | --use-remote-sudo | ||
| 382 | ``` | ||
| 383 | |||
| 384 | --- | ||
| 385 | |||
| 386 | ## Upgrading | ||
| 387 | |||
| 388 | To upgrade ngit-grasp: | ||
| 389 | |||
| 390 | ```bash | ||
| 391 | # Update flake input | ||
| 392 | nix flake update ngit-grasp | ||
| 393 | |||
| 394 | # Review changes | ||
| 395 | git diff flake.lock | ||
| 396 | |||
| 397 | # Commit | ||
| 398 | git add flake.lock | ||
| 399 | git commit -m "Update ngit-grasp" | ||
| 400 | |||
| 401 | # Deploy | ||
| 402 | nixos-rebuild switch --flake .#your-hostname \ | ||
| 403 | --target-host user@server.example.com \ | ||
| 404 | --use-remote-sudo \ | ||
| 405 | --build-host user@server.example.com | ||
| 406 | ``` | ||
| 407 | |||
| 408 | --- | ||
| 409 | |||
| 410 | ## Security Hardening | ||
| 411 | |||
| 412 | The NixOS module includes systemd hardening: | ||
| 413 | |||
| 414 | - `NoNewPrivileges = true` - Prevents privilege escalation | ||
| 415 | - `ProtectSystem = "strict"` - Read-only filesystem except dataDir | ||
| 416 | - `ProtectHome = true` - No access to home directories | ||
| 417 | - `PrivateTmp = true` - Private /tmp | ||
| 418 | - `RestrictAddressFamilies` - Only allow needed network families | ||
| 419 | - `SystemCallFilter` - Restrict system calls | ||
| 420 | |||
| 421 | Additional recommendations: | ||
| 422 | |||
| 423 | 1. **Use nsec file instead of inline:** | ||
| 424 | ```nix | ||
| 425 | relayOwnerNsecFile = "/persistent/ngit-grasp/relay-owner.nsec"; | ||
| 426 | # NOT: relayOwnerNsec = "nsec1..."; # Ends up in nix store! | ||
| 427 | ``` | ||
| 428 | |||
| 429 | 2. **Restrict data directory permissions:** | ||
| 430 | ```bash | ||
| 431 | chmod 750 /persistent/ngit-grasp | ||
| 432 | chown ngit-grasp-production:ngit-grasp /persistent/ngit-grasp | ||
| 433 | ``` | ||
| 434 | |||
| 435 | 3. **Use HTTPS (reverse proxy required):** | ||
| 436 | - ngit-grasp binds to localhost by default | ||
| 437 | - Use Caddy/nginx for TLS termination | ||
| 438 | - Caddy handles certificates automatically | ||
| 439 | |||
| 440 | 4. **Monitor logs regularly:** | ||
| 441 | ```bash | ||
| 442 | journalctl -u ngit-grasp-production --since today | grep -i error | ||
| 443 | ``` | ||
| 444 | |||
| 445 | --- | ||
| 446 | |||
| 447 | ## Monitoring | ||
| 448 | |||
| 449 | ### Prometheus Metrics | ||
| 450 | |||
| 451 | ngit-grasp exposes Prometheus metrics at `/metrics`: | ||
| 452 | |||
| 453 | ```bash | ||
| 454 | curl http://localhost:8082/metrics | ||
| 455 | ``` | ||
| 456 | |||
| 457 | See [Prometheus Setup](./prometheus-setup.md) for complete monitoring guide. | ||
| 458 | |||
| 459 | ### Basic Health Checks | ||
| 460 | |||
| 461 | ```bash | ||
| 462 | # Check if service is running | ||
| 463 | systemctl is-active ngit-grasp-production | ||
| 464 | |||
| 465 | # Check if port is listening | ||
| 466 | nc -zv localhost 8082 | ||
| 467 | |||
| 468 | # Check relay info | ||
| 469 | curl https://ngit.example.com -H "Accept: application/nostr+json" | ||
| 470 | |||
| 471 | # Check disk usage | ||
| 472 | du -sh /persistent/ngit-grasp/* | ||
| 473 | ``` | ||
| 474 | |||
| 475 | --- | ||
| 476 | |||
| 477 | ## Related Documentation | ||
| 478 | |||
| 479 | - [Configuration Reference](../reference/configuration.md) - All configuration options | ||
| 480 | - [NixOS Module](../../nix/module.nix) - Module source code | ||
| 481 | - [Example Configuration](../../nix/example-configuration.nix) - More examples | ||
| 482 | - [Prometheus Setup](./prometheus-setup.md) - Monitoring guide | ||
| 483 | - [Nix Flakes How-To](./nix-flakes.md) - Nix development environment | ||
| 484 | |||
| 485 | --- | ||
| 486 | |||
| 487 | *Part of the [ngit-grasp how-to guides](./)* | ||