docs(deploy): add host-specific production paths

The production guide was NixOS-only despite presenting itself as the general deployment entry point, and its examples referenced an unavailable GitHub source and a hardening control the module does not set.

Turn the entry point into an environment chooser, preserve the corrected NixOS material in its own guide, add a hardened generic systemd unit and repeatable Linux installation, document the preferred unprivileged Proxmox layout, and update repository navigation and architecture references.

Each path assumes the shared deployment contract from the container change. Kubernetes automation, remote host mutation, and changes to the existing NixOS module are deliberately excluded.

Validated the canonical Git remote with git ls-remote, parsed and scored the systemd unit with systemd-analyze, checked all new deployment-guide links, removed trailing whitespace, scanned the staged diff for key-shaped nsec values, and ran git diff --check.
This commit is contained in:
DanConwayDev
2026-08-20 19:38:04 +00:00
parent 28c881ba97
commit 22bbd48537
13 changed files with 866 additions and 592 deletions
+1 -1
View File
@@ -59,7 +59,7 @@ forces `ExecStart`, which coerces `src = ../.`; a standalone local path is not
Git-filtered and may hash or copy ignored `target/` and worktree data. Use the
Git-backed flake module, or a builder stub that ignores all build attributes and
keeps `src` lazy. See
[`docs/how-to/deploy.md`](docs/how-to/deploy.md#resource-safe-module-validation)
[`docs/how-to/deploy-nixos.md`](docs/how-to/deploy-nixos.md#resource-safe-module-validation)
for resource-safe validation and deployment.
### Testing ngit-grasp (Main Project)
+19 -4
View File
@@ -406,9 +406,22 @@ This a useful feature of other git servers.
## Quick Start
Tagged releases provide a statically linked x86_64 Linux archive accompanied
by `SHA256SUMS`. The archive can be installed without a Rust or Nix toolchain;
on a Linux flake system, build the same output with `nix build .#static`.
For production, start with the [deployment chooser](docs/how-to/deploy.md).
The repository ships Docker and Compose configurations, a NixOS module, a
hardened systemd unit, and templates for selected managed hosts. All supported
paths preserve the same
[deployment contract](docs/reference/deployment-contract.md).
The shortest fresh-VPS path uses Docker Compose and Caddy:
```bash
cp deploy.env.example .env
# Set NGIT_DOMAIN in .env and point DNS at this server.
docker compose -f compose.yaml -f compose.caddy.yaml up --build -d
scripts/verify-deployment.sh https://ngit.example.com
```
For development from source:
```bash
# install ngit
@@ -443,7 +456,9 @@ nix develop -c cargo test --lib
- Purgatory system activates, ready to hunt for missing git data
- Prometheus metrics exposed at `<base-path>/metrics`
**Don't have Nix?** See [Getting Started Tutorial](docs/tutorials/getting-started.md) for alternative setup methods.
**Don't have Nix?** Use the container path above or see the
[Getting Started Tutorial](docs/tutorials/getting-started.md) for development
alternatives.
## Configuration
+15
View File
@@ -0,0 +1,15 @@
# Required canonical public hostname, without a scheme or path.
NGIT_DOMAIN=ngit.example.com
NGIT_BASE_PATH=/
NGIT_BIND_ADDRESS=127.0.0.1:7334
NGIT_GIT_DATA_PATH=/var/lib/ngit-grasp/git
NGIT_RELAY_DATA_PATH=/var/lib/ngit-grasp/relay
NGIT_DATABASE_BACKEND=lmdb
NGIT_LOG_LEVEL=info
# Optional initial relay. Sync discovers additional relays automatically.
# NGIT_SYNC_BOOTSTRAP_RELAY_URL=wss://relay.ngit.dev
# Prefer /var/lib/ngit-grasp/.relay-owner.nsec or a systemd credential over an
# environment secret. Never put NGIT_RELAY_OWNER_NSEC in this example file.
+38
View File
@@ -0,0 +1,38 @@
[Unit]
Description=ngit-grasp GRASP relay
Documentation=https://gitworkshop.dev/danconwaydev.com/ngit-grasp
Wants=network-online.target
After=network-online.target
[Service]
Type=simple
User=ngit-grasp
Group=ngit-grasp
WorkingDirectory=/var/lib/ngit-grasp
EnvironmentFile=/etc/ngit-grasp/ngit-grasp.env
Environment=PATH=/usr/local/bin:/usr/bin:/bin
ExecStart=/usr/local/bin/ngit-grasp
Restart=on-failure
RestartSec=10s
TimeoutStopSec=300s
UMask=0077
NoNewPrivileges=true
PrivateDevices=true
PrivateTmp=true
ProtectControlGroups=true
ProtectHome=true
ProtectKernelModules=true
ProtectKernelTunables=true
ProtectSystem=strict
ReadWritePaths=/var/lib/ngit-grasp
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
RestrictNamespaces=true
RestrictRealtime=true
RestrictSUIDSGID=true
CapabilityBoundingSet=
AmbientCapabilities=
LockPersonality=true
[Install]
WantedBy=multi-user.target
+4 -3
View File
@@ -36,7 +36,8 @@ WORKING │ How-To │ Reference │
**For:** Users with basic knowledge solving real problems
**Style:** Practical recipes and solutions
- **[Deploy ngit-grasp](how-to/deploy.md)** - Production deployment guide
- **[Deploy ngit-grasp](how-to/deploy.md)** - Choose Docker, NixOS, Linux, Proxmox, or managed hosting
- **[Deployment contract](reference/deployment-contract.md)** - Shared runtime and persistence requirements
- **[Configure Nix Flakes](how-to/nix-flakes.md)** - Nix development environment
- **[Run Compliance Tests](how-to/test-compliance.md)** - GRASP compliance testing
- **[Upgrade nostr-sdk](how-to/upgrade-nostr-sdk.md)** - Handling SDK upgrades
@@ -74,8 +75,8 @@ WORKING │ How-To │ Reference │
### I want to deploy ngit-grasp
1. Review [Configuration Reference](reference/configuration.md)
2. Follow [Deployment How-To](how-to/deploy.md)
3. Set up monitoring and backups
2. Choose an environment in [Deploy ngit-grasp](how-to/deploy.md)
3. Verify the deployment and test its backup
### I want to develop on ngit-grasp
1. Follow [Getting Started Tutorial](tutorials/getting-started.md)
+11 -38
View File
@@ -868,47 +868,20 @@ Relax the write policy to accept all repository announcements regardless of clon
## Deployment
### Single Binary
The runtime remains one binary plus Git, but persistence, identity, proxying,
and single-writer behavior are part of the production boundary. The normative
[deployment contract](../reference/deployment-contract.md) owns those shared
requirements.
```bash
cargo build --release
NGIT_RELAY_OWNER_NSEC=nsec1... \
./target/release/ngit-grasp --domain example.com
```
Supported artifacts are maintained alongside their operating guides:
### Docker
- the root `Dockerfile` and Compose configurations;
- `deploy/systemd/ngit-grasp.service` for conventional Linux;
- `nix/module.nix` for declarative NixOS instances; and
- managed-host templates for single-instance deployments.
```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
```
See the [deployment chooser](../how-to/deploy.md) rather than copying an
illustrative container or service definition from this architecture document.
## Security Considerations
+12 -12
View File
@@ -24,6 +24,18 @@ How-to guides are **recipes** that show you how to solve specific problems or ac
## Available How-To Guides
### [Deploy ngit-grasp](deploy.md)
**Problem:** Run a durable production relay in a supported hosting environment
**You'll learn:**
- Choose Docker, NixOS, systemd Linux, Proxmox, or managed hosting
- Preserve the relay identity, Git repositories, and LMDB state
- Configure TLS, verify protocols, back up, and upgrade safely
---
### [Upgrade from v2 to v3 Git family storage](upgrade-git-family-storage.md)
**Problem:** Perform the one-way identifier-family storage migration safely
@@ -64,18 +76,6 @@ How-to guides are **recipes** that show you how to solve specific problems or ac
## Planned How-To Guides
### Deploy ngit-grasp
**Status:** 🔜 Planned (waiting for main server)
**Problem:** Deploy to production
**You'll learn:**
- Server requirements
- Reverse proxy setup (nginx/Caddy)
- SSL/TLS configuration
- Monitoring and logging
---
### Run Compliance Tests
**Status:** 🔜 Planned
+102
View File
@@ -0,0 +1,102 @@
# Deploy a static binary with systemd
Use this path for a conventional Debian, Ubuntu, Fedora, or other systemd Linux
server when containers are unnecessary. Build the portable binary on a Nix
build machine, then copy only the binary and service files to the server.
## Build a pinned binary
From a clean checkout of the reviewed tag or revision:
```bash
nix build .#static
file result/bin/ngit-grasp
```
Copy `result/bin/ngit-grasp`, `deploy/systemd/ngit-grasp.service`, and
`deploy/systemd/ngit-grasp.env.example` to the server through your normal
authenticated deployment channel.
The deployment host does not need Nix or Rust. It does need Git and trusted CA
certificates:
```bash
sudo apt-get update
sudo apt-get install -y ca-certificates git
```
Use the equivalent packages on non-Debian distributions.
## Install
```bash
getent group ngit-grasp >/dev/null || sudo groupadd --system ngit-grasp
id -u ngit-grasp >/dev/null 2>&1 || \
sudo useradd --system --gid ngit-grasp --home-dir /var/lib/ngit-grasp \
--create-home --shell /usr/sbin/nologin ngit-grasp
sudo install -Dm755 ngit-grasp /usr/local/bin/ngit-grasp
sudo install -Dm644 ngit-grasp.service \
/etc/systemd/system/ngit-grasp.service
sudo install -Dm640 -o root -g ngit-grasp ngit-grasp.env.example \
/etc/ngit-grasp/ngit-grasp.env
sudo install -d -m 0750 -o ngit-grasp -g ngit-grasp \
/var/lib/ngit-grasp \
/var/lib/ngit-grasp/git \
/var/lib/ngit-grasp/relay
```
Edit `/etc/ngit-grasp/ngit-grasp.env` and set `NGIT_DOMAIN`.
If restoring an existing identity, install `.relay-owner.nsec` as mode `0600`
owned by `ngit-grasp` under `/var/lib/ngit-grasp`. Otherwise the first start
generates it there.
## Reverse proxy and TLS
Keep the service bound to `127.0.0.1:7334`. For Caddy, a domain-root virtual
host is:
```caddyfile
ngit.example.com {
reverse_proxy 127.0.0.1:7334
}
```
Point DNS at the server and replace `ngit.example.com` in both Caddy and the
ngit-grasp environment file. Caddy preserves WebSocket upgrades automatically.
## Start and verify
On the server:
```bash
sudo systemctl daemon-reload
sudo systemctl enable --now ngit-grasp
sudo systemctl status ngit-grasp --no-pager
sudo journalctl -u ngit-grasp -n 50 --no-pager
```
From the repository checkout on the operator workstation:
```bash
scripts/verify-deployment.sh https://ngit.example.com
```
The service unit applies a restrictive umask, filesystem protection, private
temporary directory, empty capability set, and bounded five-minute shutdown.
## Upgrade and rollback
Build the new pinned binary before touching the server. Read `CHANGELOG.md`,
stop the service, snapshot `/var/lib/ngit-grasp`, install the new binary, and
start the service:
```bash
sudo systemctl stop ngit-grasp
sudo install -Dm755 ngit-grasp /usr/local/bin/ngit-grasp
sudo systemctl start ngit-grasp
```
Run the verifier and inspect startup integrity summaries. If the release
changed storage, restoring the old binary also requires restoring its matching
state snapshot.
+570
View File
@@ -0,0 +1,570 @@
# Deploy ngit-grasp on NixOS
**Purpose:** Deploy ngit-grasp to a production NixOS server
**Difficulty:** Intermediate
**Time:** 30-60 minutes
This guide implements the shared
[deployment contract](../reference/deployment-contract.md) with the repository's
NixOS module. For another environment, return to the
[deployment chooser](deploy.md).
---
## Problem
You want to:
- Deploy ngit-grasp to a NixOS server
- Configure it as a systemd service
- Set up reverse proxy (Caddy)
- Ensure proper security and monitoring
---
## Prerequisites
- NixOS server with SSH access
- Flakes enabled on server and local machine
- Domain name configured (DNS pointing to server)
- Basic knowledge of NixOS configuration
---
## Solution
### Step 1: Add ngit-grasp to Your Server's Flake
In your server's `flake.nix`, add ngit-grasp as an input:
```nix
{
inputs = {
# Keep the nixpkgs input already used by this server configuration.
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
ngit-grasp.url =
"git+https://gitnostr.com/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit-grasp.git";
};
outputs = { self, nixpkgs, ngit-grasp, ... }@inputs: {
nixosConfigurations.your-hostname = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
specialArgs = { inherit inputs; };
modules = [
./configuration.nix
# ... other modules
];
};
};
}
```
---
### Step 2: Create Service Configuration
Create a new file for your ngit-grasp service (e.g., `services/ngit-grasp.nix`):
```nix
{ inputs, ... }:
{
imports = [ inputs.ngit-grasp.nixosModules.default ];
services.ngit-grasp.production = {
enable = true;
domain = "ngit.example.com";
# Network
bindAddress = "127.0.0.1";
port = 8082;
# Only Caddy can reach the loopback backend, so its forwarded client IP is trusted.
trustedProxyCidrs = [ "127.0.0.1/32" ];
# Storage
dataDir = "/persistent/ngit-grasp";
# Identity
relayName = "My GRASP Relay";
relayDescription = "A Rust GRASP implementation with proactive sync";
relayOwnerNsecFile = "/run/agenix/ngit-grasp-relay-owner-nsec";
# Sync - bootstrap from relay.ngit.dev
syncBootstrapRelayUrl = "wss://relay.ngit.dev";
# Metrics
metricsEnabled = true;
# Logging
logLevel = "info";
};
# Caddy reverse proxy
services.caddy.virtualHosts."ngit.example.com" = {
extraConfig = ''
reverse_proxy 127.0.0.1:8082 {
# Caddy manages X-Forwarded-For automatically.
header_up X-Real-IP {remote_host}
}
'';
};
}
```
**Key configuration options:**
- **Instance name** (`production`): Can be any name. Used for systemd service (`ngit-grasp-production`)
- **domain**: Your relay's domain (used in GRASP validation)
- **port**: Local port (use reverse proxy for HTTPS)
- **trustedProxyCidrs**: Proxy source ranges allowed to supply the client IP
- Keep empty for a directly exposed listener
- Keep the backend private; trusting a public-facing source range permits spoofed headers
- Caddy automatically maintains `X-Forwarded-For`; `header_up`, not `header_down`,
changes headers sent to the backend
- **dataDir**: Where git repos and database are stored
- **relayOwnerNsecFile**: Path to file containing relay owner's nsec
- Passed to ngit-grasp as a protected systemd credential, not a process argument
- The runtime secret file must already exist (for example through agenix or sops-nix)
- Permissions on that external source file remain the operator or secret manager's responsibility
- Alternative: `relayOwnerNsec = "nsec1..."` (less secure, in nix store)
- If neither option is set, ngit-grasp loads or creates `.relay-owner.nsec` in `dataDir`
- **syncBootstrapRelayUrl**: Bootstrap relay to sync from on startup
See [nix/example-configuration.nix](../../nix/example-configuration.nix) for more examples.
---
### Step 3: Import the Service
Import your service configuration in your main configuration file:
```nix
# In configuration.nix or services/default.nix
{
imports = [
./services/ngit-grasp.nix
# ... other services
];
}
```
---
### Step 4: Update Flake Lock
```bash
cd /path/to/server/config
nix flake update ngit-grasp
git add flake.lock
git commit -m "Add ngit-grasp and update flake.lock"
```
---
### Step 5: Validate Configuration
Before deploying, validate that the flake evaluates without starting its builds:
```bash
nix flake check --no-build
```
#### Resource-safe module validation
Nix copies path-valued build inputs into the store when they are forced. A Git
flake is materialized from its tracked files first, but a standalone path into a
working tree does not inherit that Git filtering.
This matters when testing ngit-grasp's NixOS module locally. Importing
`nix/module.nix` is lazy by itself, but rendering an enabled service forces the
module-built package through `ExecStart`. The package's `src = ../.` then
resolves relative to that module. If the module was imported directly from a
working tree, Nix may recursively hash or copy ignored `target/`, `.git`, and
linked-worktree data while it appears to be evaluating the configuration.
Use `inputs.ngit-grasp.nixosModules.default` from a Git-backed flake input, as
shown above. For local module changes, commit them to a temporary Git branch and
use that Git source, or replace `buildRustPackage` with a test stub that ignores
all build attributes so `src` remains unforced. Do not use a direct
working-tree module import for a test that enables an instance.
Inspect the derivation plan before starting a build:
```bash
nixos-rebuild dry-build --flake .#your-hostname
```
Multiple ngit-grasp instances should normally share one ngit-grasp package
derivation. Avoid service-level `ExecStart` overrides that force another flake
package or Rust toolchain. If distinct versions are intentional, build them
sequentially or on appropriately sized remote builders. For an initial local
build, constrain Nix while confirming the plan behaves as expected:
```bash
nixos-rebuild build --flake .#your-hostname --max-jobs 1 --cores 2
```
---
### Step 6: Deploy to Server
Deploy the new configuration to your server:
```bash
# Build and switch in one command (builds on server)
nixos-rebuild switch --flake .#your-hostname \
--target-host user@server.example.com \
--use-remote-sudo \
--build-host user@server.example.com
```
**Alternative:** Build locally, then deploy:
```bash
# Build locally
nixos-rebuild build --flake .#your-hostname
# Deploy to server
nixos-rebuild switch --flake .#your-hostname \
--target-host user@server.example.com \
--use-remote-sudo
```
**Note:** Building locally requires your machine to trust the server's nix signing key.
---
### Step 7: Verify Deployment
SSH to the server and check the service:
```bash
ssh user@server.example.com
# Check service status
systemctl status ngit-grasp-production
# View recent logs
journalctl -u ngit-grasp-production -n 50 --no-pager
# Check if listening on port
ss -tlnp | grep 8082
```
---
### Step 8: Test Functionality
From your local machine, test the relay:
```bash
# Test NIP-11 relay info
curl https://ngit.example.com -H "Accept: application/nostr+json" | jq
# Test WebSocket connection
websocat wss://ngit.example.com
# Then type: ["REQ","test",{}]
# Should receive events
# Test git clone (if you have repos)
git ls-remote https://ngit.example.com/<npub>/<repo>.git
```
---
## Configuration Options
### Required
- `enable` - Enable this instance
- `domain` - Domain where relay is hosted
### Network
- `basePath` - Public URL mount path (default: `/`)
- `bindAddress` - IP to bind to (default: "127.0.0.1")
- `port` - Port to listen on (default: 7334)
- `trustedProxyCidrs` - Proxy networks allowed to provide the WebSocket client
IP (default: empty; forwarded headers ignored)
### Storage
- `dataDir` - Base directory for data (default: /var/lib/ngit-grasp-{name})
- `databaseBackend` - "lmdb" | "memory" (default: "lmdb")
See [Upgrade Git family storage](upgrade-git-family-storage.md) before updating
an existing instance to a release that enables identifier-family storage.
### Identity
- `relayName` - Relay name for NIP-11 (default: "{domain} grasp relay")
- `relayDescription` - Relay description
- `relayOwnerNsecFile` - Runtime secret file loaded as a systemd credential (recommended)
- `relayOwnerNsec` - Inline nsec (less secure)
### Sync
- `syncBootstrapRelayUrl` - Bootstrap relay URL (optional)
- `syncDisableNegentropy` - Disable NIP-77 negentropy (default: false)
- `syncMaxBackoffSecs` - Max backoff for reconnection (default: 3600)
- `syncDisconnectCheckIntervalSecs` - Check interval (default: 60)
- `syncBaseBackoffSecs` - Base backoff time (default: 5)
### Metrics
- `metricsEnabled` - Enable `/metrics` below the configured base path (default: true)
- `metricsConnectionPerIpAbuseThreshold` - Abuse threshold (default: 10)
- `metricsTopNRepos` - Number of top repos to track (default: 10)
### Logging
- `logLevel` - "trace" | "debug" | "info" | "warn" | "error" (default: "info")
### Security
- `user` - User to run as (default: "ngit-grasp-{name}")
- `group` - Group to run as (default: "ngit-grasp")
See [nix/module.nix](../../nix/module.nix) for complete option definitions.
---
## Systemd Service
The NixOS module creates a systemd service: `ngit-grasp-{instance-name}`
```bash
# Start/stop/restart
systemctl start ngit-grasp-production
systemctl stop ngit-grasp-production
systemctl restart ngit-grasp-production
# Enable/disable autostart
systemctl enable ngit-grasp-production
systemctl disable ngit-grasp-production
# View logs
journalctl -u ngit-grasp-production -f
journalctl -u ngit-grasp-production --since "1 hour ago"
# Check status
systemctl status ngit-grasp-production
```
---
## Multiple Instances
You can run multiple instances on the same server:
```nix
services.ngit-grasp = {
production = {
enable = true;
domain = "ngit.example.com";
port = 8082;
dataDir = "/persistent/ngit-production";
};
staging = {
enable = true;
domain = "ngit-staging.example.com";
port = 8083;
dataDir = "/persistent/ngit-staging";
logLevel = "debug";
};
};
```
Each instance:
- Runs as separate systemd service: `ngit-grasp-production`, `ngit-grasp-staging`
- Has its own user: `ngit-grasp-production`, `ngit-grasp-staging`
- Stores data in separate directory
- Can have different configuration
---
## Troubleshooting
### Service won't start
**Check logs:**
```bash
journalctl -u ngit-grasp-production -n 50
```
**Common issues:**
- Port already in use: Check with `ss -tlnp | grep 8082`
- Data directory permissions: Should be owned by service user
- Invalid nsec file: Check file exists and contains valid nsec
### Can't connect via WebSocket
**Check:**
- Service is running: `systemctl status ngit-grasp-production`
- Firewall allows connections: `nix run nixpkgs#nmap -- -p 443 ngit.example.com`
- Caddy is configured correctly: `systemctl status caddy`
- DNS resolves: `dig ngit.example.com`
### Sync not working
**Check logs for sync errors:**
```bash
journalctl -u ngit-grasp-production | grep -i sync
```
**Common issues:**
- Bootstrap relay URL incorrect or unreachable
- Network connectivity issues
- Bootstrap relay doesn't support negentropy (disable with `syncDisableNegentropy = true`)
### High memory/CPU usage
**Monitor metrics:**
```bash
curl http://localhost:8082/metrics
```
**Tune configuration:**
- Reduce `metricsTopNRepos`
- Increase `syncMaxBackoffSecs`
- Tune `syncMaxBackoffSecs` for your network conditions
---
## Rollback
If deployment fails, rollback to previous configuration:
```bash
# On the server
nixos-rebuild switch --rollback
# Or remotely
nixos-rebuild switch --rollback \
--target-host user@server.example.com \
--use-remote-sudo
```
If the release changed on-disk storage, a NixOS generation rollback is not
enough. Restore the matching pre-upgrade snapshot of the complete `dataDir`
before starting the older service. See the
[deployment contract](../reference/deployment-contract.md#upgrade-and-rollback).
---
## Upgrading
To upgrade ngit-grasp:
```bash
# Update flake input
nix flake update ngit-grasp
# Review changes
git diff flake.lock
# Commit
git add flake.lock
git commit -m "Update ngit-grasp"
# Deploy
nixos-rebuild switch --flake .#your-hostname \
--target-host user@server.example.com \
--use-remote-sudo \
--build-host user@server.example.com
```
---
## Security Hardening
The NixOS module includes systemd hardening:
- `NoNewPrivileges = true` - Prevents privilege escalation
- `ProtectSystem = "strict"` - Read-only filesystem except dataDir
- `ProtectHome = true` - No access to home directories
- `PrivateTmp = true` - Private /tmp
- `RestrictAddressFamilies` - Only allow needed network families
Additional recommendations:
1. **Use a runtime secret file instead of an inline key:**
```nix
relayOwnerNsecFile = "/run/agenix/ngit-grasp-relay-owner-nsec";
# NOT: relayOwnerNsec = "nsec1..."; # Ends up in nix store!
```
The module exposes the file to ngit-grasp as the `relay_owner_nsec`
systemd credential. The key does not appear in `ExecStart` or the process
command line. ngit-grasp does not modify the external source file; keep its
ownership and permissions restricted through your secret manager.
2. **Restrict data directory permissions:**
```bash
chmod 750 /persistent/ngit-grasp
chown ngit-grasp-production:ngit-grasp /persistent/ngit-grasp
```
3. **Use HTTPS (reverse proxy required):**
- ngit-grasp binds to localhost by default
- Use Caddy/nginx for TLS termination
- Caddy handles certificates automatically
4. **Monitor logs regularly:**
```bash
journalctl -u ngit-grasp-production --since today | grep -i error
```
---
## Monitoring
### Prometheus Metrics
ngit-grasp exposes Prometheus metrics at `/metrics`:
```bash
curl http://localhost:8082/metrics
```
See [Prometheus Setup](./prometheus-setup.md) for complete monitoring guide.
### Basic Health Checks
```bash
# Check if service is running
systemctl is-active ngit-grasp-production
# Check if port is listening
nc -zv localhost 8082
# Check relay info
curl https://ngit.example.com -H "Accept: application/nostr+json"
# Check disk usage
du -sh /persistent/ngit-grasp/*
```
## Backup
Back up the complete `dataDir`, including `.relay-owner.nsec`, `git/`, and
`relay/`, from one point in time. For a portable consistent backup, stop the
instance before taking the snapshot:
```bash
systemctl stop ngit-grasp-production
# Snapshot or back up /persistent/ngit-grasp with the host's storage tooling.
systemctl start ngit-grasp-production
```
Keep an off-host copy and test restoration into an isolated, non-public
instance. Never start the restored copy alongside production with the same
domain and relay identity.
---
## Related Documentation
- [Configuration Reference](../reference/configuration.md) - All configuration options
- [NixOS Module](../../nix/module.nix) - Module source code
- [Example Configuration](../../nix/example-configuration.nix) - More examples
- [Prometheus Setup](./prometheus-setup.md) - Monitoring guide
- [Nix Flakes How-To](./nix-flakes.md) - Nix development environment
- [Deployment Contract](../reference/deployment-contract.md) - Shared runtime and persistence rules
- [Deployment Chooser](deploy.md) - Other supported environments
---
*Part of the [ngit-grasp how-to guides](./)*
+58
View File
@@ -0,0 +1,58 @@
# Deploy in a Proxmox LXC container or VM
ngit-grasp has no kernel-virtualization or nested-container requirement. The
preferred Proxmox layout is an unprivileged Debian or Ubuntu LXC running the
static binary as a systemd service.
## Container requirements
- an unprivileged LXC or ordinary VM with systemd
- network access to public HTTPS and WebSocket relays
- inbound HTTP/HTTPS through the Proxmox network or an external proxy
- durable storage sized for Git repositories, LMDB, holding data, and backups
Docker nesting is not required for the direct binary path. Leave it disabled
unless using the Compose alternative below.
## Direct systemd path
Follow [Deploy a static binary with systemd](deploy-linux.md) inside the guest.
Keep `/var/lib/ngit-grasp` on storage included in the guest's snapshot and
backup policy.
When the Proxmox host bind-mounts a dataset into an unprivileged LXC, map its
ownership to the container's `ngit-grasp` UID/GID before starting the service.
Verify this from inside the container:
```bash
sudo -u ngit-grasp test -w /var/lib/ngit-grasp
sudo -u ngit-grasp git --version
```
Terminate TLS either inside the guest with Caddy or at an upstream proxy. If
the upstream proxy connects directly to ngit-grasp, add only that private
source address to `NGIT_TRUSTED_PROXY_CIDRS` and prevent other clients from
reaching port 7334.
## Compose alternative
If the guest already operates Docker or Podman, follow the
[Docker guide](deploy-docker.md). Docker inside LXC generally requires the
Proxmox nesting feature; the static binary path avoids that extra layer.
Do not mount the host Docker socket into the relay container. ngit-grasp needs
Git, not a container daemon.
## Backup and upgrade
For a simple consistent backup:
1. stop `ngit-grasp` inside the guest;
2. snapshot or back up the guest and its attached state storage;
3. start the service; and
4. verify the public endpoint.
Proxmox snapshots are not a substitute for an off-host backup. Before a
storage-changing upgrade, confirm that the state volume participates in the
snapshot and that the snapshot can be restored without starting a second
writer against the production domain.
+27 -533
View File
@@ -1,543 +1,37 @@
# How-To: Deploy ngit-grasp to Production
# Deploy ngit-grasp
**Purpose:** Deploy ngit-grasp to a production NixOS server
**Difficulty:** Intermediate
**Time:** 30-60 minutes
ngit-grasp supports several single-instance production layouts. Choose the
guide matching the host you already operate; every guide implements the same
[deployment contract](../reference/deployment-contract.md).
---
| Environment | Start here | Supplied artifact |
| --- | --- | --- |
| Docker or Podman host | [Docker and Compose](deploy-docker.md) | `Dockerfile`, `compose.yaml`, optional Caddy overlay |
| NixOS | [NixOS module](deploy-nixos.md) | `nixosModules.default` |
| Debian, Ubuntu, or another systemd Linux | [Static binary and systemd](deploy-linux.md) | Static flake package and service unit |
| Proxmox LXC or VM | [Proxmox](deploy-proxmox-lxc.md) | Direct systemd or Compose path |
| Railway, Render, or Fly.io | [Managed hosting](deploy-paas.md) | Provider configuration templates |
## Problem
You want to:
- Deploy ngit-grasp to a NixOS server
- Configure it as a systemd service
- Set up reverse proxy (Caddy)
- Ensure proper security and monitoring
---
## Prerequisites
- NixOS server with SSH access
- Flakes enabled on server and local machine
- Domain name configured (DNS pointing to server)
- Basic knowledge of NixOS configuration
---
## Solution
### Step 1: Add ngit-grasp to Your Server's Flake
In your server's `flake.nix`, add ngit-grasp as an input:
```nix
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
ngit-grasp.url = "github:DanConwayDev/ngit-grasp";
# or use a specific git repository:
# ngit-grasp.url = "git+https://git.shakespeare.diy/npub.../ngit-grasp.git";
};
outputs = { self, nixpkgs, ngit-grasp, ... }@inputs: {
nixosConfigurations.your-hostname = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
specialArgs = { inherit inputs; };
modules = [
./configuration.nix
# ... other modules
];
};
};
}
```
---
### Step 2: Create Service Configuration
Create a new file for your ngit-grasp service (e.g., `services/ngit-grasp.nix`):
```nix
{ inputs, ... }:
{
imports = [ inputs.ngit-grasp.nixosModules.default ];
services.ngit-grasp.production = {
enable = true;
domain = "ngit.example.com";
# Network
bindAddress = "127.0.0.1";
port = 8082;
# Only Caddy can reach the loopback backend, so its forwarded client IP is trusted.
trustedProxyCidrs = [ "127.0.0.1/32" ];
# Storage
dataDir = "/persistent/ngit-grasp";
# Identity
relayName = "My GRASP Relay";
relayDescription = "A Rust GRASP implementation with proactive sync";
relayOwnerNsecFile = "/run/agenix/ngit-grasp-relay-owner-nsec";
# Sync - bootstrap from relay.ngit.dev
syncBootstrapRelayUrl = "wss://relay.ngit.dev";
# Metrics
metricsEnabled = true;
# Logging
logLevel = "info";
};
# Caddy reverse proxy
services.caddy.virtualHosts."ngit.example.com" = {
extraConfig = ''
reverse_proxy 127.0.0.1:8082 {
# Caddy manages X-Forwarded-For automatically.
header_up X-Real-IP {remote_host}
}
'';
};
}
```
**Key configuration options:**
- **Instance name** (`production`): Can be any name. Used for systemd service (`ngit-grasp-production`)
- **domain**: Your relay's domain (used in GRASP validation)
- **port**: Local port (use reverse proxy for HTTPS)
- **trustedProxyCidrs**: Proxy source ranges allowed to supply the client IP
- Keep empty for a directly exposed listener
- Keep the backend private; trusting a public-facing source range permits spoofed headers
- Caddy automatically maintains `X-Forwarded-For`; `header_up`, not `header_down`,
changes headers sent to the backend
- **dataDir**: Where git repos and database are stored
- **relayOwnerNsecFile**: Path to file containing relay owner's nsec
- Passed to ngit-grasp as a protected systemd credential, not a process argument
- The runtime secret file must already exist (for example through agenix or sops-nix)
- Permissions on that external source file remain the operator or secret manager's responsibility
- Alternative: `relayOwnerNsec = "nsec1..."` (less secure, in nix store)
- If neither option is set, ngit-grasp loads or creates `.relay-owner.nsec` in `dataDir`
- **syncBootstrapRelayUrl**: Bootstrap relay to sync from on startup
See [nix/example-configuration.nix](../../nix/example-configuration.nix) for more examples.
---
### Step 3: Import the Service
Import your service configuration in your main configuration file:
```nix
# In configuration.nix or services/default.nix
{
imports = [
./services/ngit-grasp.nix
# ... other services
];
}
```
---
### Step 4: Update Flake Lock
For a fresh internet-facing VPS, the shortest supported path is Docker Compose
with the Caddy overlay:
```bash
cd /path/to/server/config
nix flake update ngit-grasp
git add flake.lock
git commit -m "Add ngit-grasp and update flake.lock"
cp deploy.env.example .env
# Set NGIT_DOMAIN in .env and point its DNS records at this server.
docker compose -f compose.yaml -f compose.caddy.yaml up --build -d
scripts/verify-deployment.sh https://ngit.example.com
```
---
The Caddy path requires ports 80 and 443. If the host already has a reverse
proxy, follow the loopback-only path in the Docker guide instead.
### Step 5: Validate Configuration
## Unsupported layouts
Before deploying, validate that the flake evaluates without starting its builds:
Do not deploy ngit-grasp to serverless functions, an ephemeral filesystem, or
multiple replicas. It owns long-lived WebSockets, background synchronization,
local Git repositories, LMDB state, and a durable relay identity.
```bash
nix flake check --no-build
```
#### Resource-safe module validation
Nix copies path-valued build inputs into the store when they are forced. A Git
flake is materialized from its tracked files first, but a standalone path into a
working tree does not inherit that Git filtering.
This matters when testing ngit-grasp's NixOS module locally. Importing
`nix/module.nix` is lazy by itself, but rendering an enabled service forces the
module-built package through `ExecStart`. The package's `src = ../.` then
resolves relative to that module. If the module was imported directly from a
working tree, Nix may recursively hash or copy ignored `target/`, `.git`, and
linked-worktree data while it appears to be evaluating the configuration.
Use `inputs.ngit-grasp.nixosModules.default` from a Git-backed flake input, as
shown above. For local module changes, commit them to a temporary Git branch and
use that Git source, or replace `buildRustPackage` with a test stub that ignores
all build attributes so `src` remains unforced. Do not use a direct
working-tree module import for a test that enables an instance.
Inspect the derivation plan before starting a build:
```bash
nixos-rebuild dry-build --flake .#your-hostname
```
Multiple ngit-grasp instances should normally share one ngit-grasp package
derivation. Avoid service-level `ExecStart` overrides that force another flake
package or Rust toolchain. If distinct versions are intentional, build them
sequentially or on appropriately sized remote builders. For an initial local
build, constrain Nix while confirming the plan behaves as expected:
```bash
nixos-rebuild build --flake .#your-hostname --max-jobs 1 --cores 2
```
---
### Step 6: Deploy to Server
Deploy the new configuration to your server:
```bash
# Build and switch in one command (builds on server)
nixos-rebuild switch --flake .#your-hostname \
--target-host user@server.example.com \
--use-remote-sudo \
--build-host user@server.example.com
```
**Alternative:** Build locally, then deploy:
```bash
# Build locally
nixos-rebuild build --flake .#your-hostname
# Deploy to server
nixos-rebuild switch --flake .#your-hostname \
--target-host user@server.example.com \
--use-remote-sudo
```
**Note:** Building locally requires your machine to trust the server's nix signing key.
---
### Step 7: Verify Deployment
SSH to the server and check the service:
```bash
ssh user@server.example.com
# Check service status
systemctl status ngit-grasp-production
# View logs
journalctl -u ngit-grasp-production -f
# Check if listening on port
ss -tlnp | grep 8082
```
---
### Step 8: Test Functionality
From your local machine, test the relay:
```bash
# Test NIP-11 relay info
curl https://ngit.example.com -H "Accept: application/nostr+json" | jq
# Test WebSocket connection
websocat wss://ngit.example.com
# Then type: ["REQ","test",{}]
# Should receive events
# Test git clone (if you have repos)
git ls-remote https://ngit.example.com/<npub>/<repo>.git
```
---
## Configuration Options
### Required
- `enable` - Enable this instance
- `domain` - Domain where relay is hosted
### Network
- `basePath` - Public URL mount path (default: `/`)
- `bindAddress` - IP to bind to (default: "127.0.0.1")
- `port` - Port to listen on (default: 7334)
- `trustedProxyCidrs` - Proxy networks allowed to provide the WebSocket client
IP (default: empty; forwarded headers ignored)
### Storage
- `dataDir` - Base directory for data (default: /var/lib/ngit-grasp-{name})
- `databaseBackend` - "lmdb" | "memory" (default: "lmdb")
See [Upgrade Git family storage](upgrade-git-family-storage.md) before updating
an existing instance to a release that enables identifier-family storage.
### Identity
- `relayName` - Relay name for NIP-11 (default: "{domain} grasp relay")
- `relayDescription` - Relay description
- `relayOwnerNsecFile` - Runtime secret file loaded as a systemd credential (recommended)
- `relayOwnerNsec` - Inline nsec (less secure)
### Sync
- `syncBootstrapRelayUrl` - Bootstrap relay URL (optional)
- `syncDisableNegentropy` - Disable NIP-77 negentropy (default: false)
- `syncMaxBackoffSecs` - Max backoff for reconnection (default: 3600)
- `syncDisconnectCheckIntervalSecs` - Check interval (default: 60)
- `syncBaseBackoffSecs` - Base backoff time (default: 5)
### Metrics
- `metricsEnabled` - Enable `/metrics` below the configured base path (default: true)
- `metricsConnectionPerIpAbuseThreshold` - Abuse threshold (default: 10)
- `metricsTopNRepos` - Number of top repos to track (default: 10)
### Logging
- `logLevel` - "trace" | "debug" | "info" | "warn" | "error" (default: "info")
### Security
- `user` - User to run as (default: "ngit-grasp-{name}")
- `group` - Group to run as (default: "ngit-grasp")
See [nix/module.nix](../../nix/module.nix) for complete option definitions.
---
## Systemd Service
The NixOS module creates a systemd service: `ngit-grasp-{instance-name}`
```bash
# Start/stop/restart
systemctl start ngit-grasp-production
systemctl stop ngit-grasp-production
systemctl restart ngit-grasp-production
# Enable/disable autostart
systemctl enable ngit-grasp-production
systemctl disable ngit-grasp-production
# View logs
journalctl -u ngit-grasp-production -f
journalctl -u ngit-grasp-production --since "1 hour ago"
# Check status
systemctl status ngit-grasp-production
```
---
## Multiple Instances
You can run multiple instances on the same server:
```nix
services.ngit-grasp = {
production = {
enable = true;
domain = "ngit.example.com";
port = 8082;
dataDir = "/persistent/ngit-production";
};
staging = {
enable = true;
domain = "ngit-staging.example.com";
port = 8083;
dataDir = "/persistent/ngit-staging";
logLevel = "debug";
};
};
```
Each instance:
- Runs as separate systemd service: `ngit-grasp-production`, `ngit-grasp-staging`
- Has its own user: `ngit-grasp-production`, `ngit-grasp-staging`
- Stores data in separate directory
- Can have different configuration
---
## Troubleshooting
### Service won't start
**Check logs:**
```bash
journalctl -u ngit-grasp-production -n 50
```
**Common issues:**
- Port already in use: Check with `ss -tlnp | grep 8082`
- Data directory permissions: Should be owned by service user
- Invalid nsec file: Check file exists and contains valid nsec
### Can't connect via WebSocket
**Check:**
- Service is running: `systemctl status ngit-grasp-production`
- Firewall allows connections: `nix-shell -p nmap --run "nmap -p 443 ngit.example.com"`
- Caddy is configured correctly: `systemctl status caddy`
- DNS resolves: `dig ngit.example.com`
### Sync not working
**Check logs for sync errors:**
```bash
journalctl -u ngit-grasp-production | grep -i sync
```
**Common issues:**
- Bootstrap relay URL incorrect or unreachable
- Network connectivity issues
- Bootstrap relay doesn't support negentropy (disable with `syncDisableNegentropy = true`)
### High memory/CPU usage
**Monitor metrics:**
```bash
curl http://localhost:8082/metrics
```
**Tune configuration:**
- Reduce `metricsTopNRepos`
- Increase `syncMaxBackoffSecs`
- Tune `syncMaxBackoffSecs` for your network conditions
---
## Rollback
If deployment fails, rollback to previous configuration:
```bash
# On the server
nixos-rebuild switch --rollback
# Or remotely
nixos-rebuild switch --rollback \
--target-host user@server.example.com \
--use-remote-sudo
```
---
## Upgrading
To upgrade ngit-grasp:
```bash
# Update flake input
nix flake update ngit-grasp
# Review changes
git diff flake.lock
# Commit
git add flake.lock
git commit -m "Update ngit-grasp"
# Deploy
nixos-rebuild switch --flake .#your-hostname \
--target-host user@server.example.com \
--use-remote-sudo \
--build-host user@server.example.com
```
---
## Security Hardening
The NixOS module includes systemd hardening:
- `NoNewPrivileges = true` - Prevents privilege escalation
- `ProtectSystem = "strict"` - Read-only filesystem except dataDir
- `ProtectHome = true` - No access to home directories
- `PrivateTmp = true` - Private /tmp
- `RestrictAddressFamilies` - Only allow needed network families
- `SystemCallFilter` - Restrict system calls
Additional recommendations:
1. **Use a runtime secret file instead of an inline key:**
```nix
relayOwnerNsecFile = "/run/agenix/ngit-grasp-relay-owner-nsec";
# NOT: relayOwnerNsec = "nsec1..."; # Ends up in nix store!
```
The module exposes the file to ngit-grasp as the `relay_owner_nsec`
systemd credential. The key does not appear in `ExecStart` or the process
command line. ngit-grasp does not modify the external source file; keep its
ownership and permissions restricted through your secret manager.
2. **Restrict data directory permissions:**
```bash
chmod 750 /persistent/ngit-grasp
chown ngit-grasp-production:ngit-grasp /persistent/ngit-grasp
```
3. **Use HTTPS (reverse proxy required):**
- ngit-grasp binds to localhost by default
- Use Caddy/nginx for TLS termination
- Caddy handles certificates automatically
4. **Monitor logs regularly:**
```bash
journalctl -u ngit-grasp-production --since today | grep -i error
```
---
## Monitoring
### Prometheus Metrics
ngit-grasp exposes Prometheus metrics at `/metrics`:
```bash
curl http://localhost:8082/metrics
```
See [Prometheus Setup](./prometheus-setup.md) for complete monitoring guide.
### Basic Health Checks
```bash
# Check if service is running
systemctl is-active ngit-grasp-production
# Check if port is listening
nc -zv localhost 8082
# Check relay info
curl https://ngit.example.com -H "Accept: application/nostr+json"
# Check disk usage
du -sh /persistent/ngit-grasp/*
```
---
## Related Documentation
- [Configuration Reference](../reference/configuration.md) - All configuration options
- [NixOS Module](../../nix/module.nix) - Module source code
- [Example Configuration](../../nix/example-configuration.nix) - More examples
- [Prometheus Setup](./prometheus-setup.md) - Monitoring guide
- [Nix Flakes How-To](./nix-flakes.md) - Nix development environment
---
*Part of the [ngit-grasp how-to guides](./)*
Kubernetes can run the container as a one-replica StatefulSet with a
ReadWriteOnce volume, but the repository does not yet ship or promise a Helm
chart. A container host or systemd service is simpler unless Kubernetes is an
existing operational requirement.
+7
View File
@@ -24,6 +24,13 @@ Reference documentation provides **factual, technical information** that you loo
## Available Reference Documentation
### [Deployment Contract](deployment-contract.md)
Shared process, endpoint, persistence, identity, backup, single-writer, health,
and upgrade requirements for every supported hosting environment.
---
### [Configuration](configuration.md)
**Complete reference for all configuration options**
+2 -1
View File
@@ -2,7 +2,8 @@
#
# Usage:
# 1. Add to your server's flake.nix inputs:
# inputs.ngit-grasp.url = "github:DanConwayDev/ngit-grasp";
# inputs.ngit-grasp.url =
# "git+https://gitnostr.com/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit-grasp.git";
#
# 2. Import the module in your configuration:
# imports = [ inputs.ngit-grasp.nixosModules.default ];