diff --git a/NETWORK.md b/NETWORK.md new file mode 100644 index 0000000..07bbcc7 --- /dev/null +++ b/NETWORK.md @@ -0,0 +1,380 @@ +# Network Architecture: IPNS DNSLink + Cloudflare IPFS Gateway + +Integration guide for Sonr's decentralized storage layer using IPNS DNSLink with Cloudflare's enterprise IPFS gateway. + +## Architecture + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ SONR NETWORK TOPOLOGY │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────────┐ ┌─────────────────────────────┐ │ +│ │ Browser Client │ │ Cloudflare Edge (330+) │ │ +│ │ │ HTTPS │ │ │ +│ │ vault.sonr.org ────┼───────────────────►│ IPFS Gateway + CDN Cache │ │ +│ │ │ │ │ │ +│ └─────────────────────┘ └──────────────┬──────────────┘ │ +│ │ │ │ +│ │ Circuit Relay │ Bitswap │ +│ ▼ ▼ │ +│ ┌─────────────────────┐ ┌─────────────────────────────┐ │ +│ │ Sonr Relay Node │◄──────────────────►│ IPFS Network │ │ +│ │ relay.sonr.org │ P2P/DHT │ │ │ +│ │ │ │ • Content Discovery │ │ +│ │ • Helia + Coord │ │ • Block Exchange │ │ +│ │ • IPNS Publishing │ │ • DHT Routing │ │ +│ └─────────────────────┘ └─────────────────────────────┘ │ +│ │ │ +│ │ IPNS Publish │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────────────────┐ │ +│ │ Cloudflare DNS (sonr.org) │ │ +│ │ │ │ +│ │ _dnslink.vault.sonr.org TXT "dnslink=/ipns/" │ │ +│ │ vault.sonr.org CNAME cloudflare-ipfs.com (proxied) │ │ +│ │ │ │ +│ └─────────────────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +## Cloudflare Enterprise Features + +### Plan Comparison + +| Feature | Free/Pro/Business | Enterprise | +|---------|-------------------|------------| +| Total Gateways | 15 | Unlimited | +| Gateway Types | DNSLink only | DNSLink + Universal Path | +| Included Bandwidth | 50 GB | 100 GB | +| File Size Limit | None | None | +| SLA | Standard | Enterprise SLA | +| Support | Ticket | 24/7 Dedicated | +| Peering Priority | Standard | Custom peering configuration | + +### Enterprise Benefits + +| Benefit | Description | +|---------|-------------| +| Unlimited Gateways | No limit on custom domain gateways | +| Universal Path Gateway | Access any CID via `gateway.sonr.org/ipfs/` | +| Custom Peering | Direct peering with Sonr relay nodes | +| Edge Caching | 330+ PoPs, automatic cache invalidation on IPNS update | +| Origin Isolation | XSS protection via subdomain isolation | + +## DNS Configuration + +### Required Records + +| Type | Name | Value | Proxy | +|------|------|-------|-------| +| CNAME | `vault.sonr.org` | `cloudflare-ipfs.com` | Proxied | +| TXT | `_dnslink.vault.sonr.org` | `dnslink=/ipns/` | N/A | +| CNAME | `gateway.sonr.org` | `cloudflare-ipfs.com` | Proxied | +| TXT | `_dnslink.gateway.sonr.org` | `dnslink=/ipns/` | N/A | + +### DNSLink Format + +``` +dnslink=/ipns/ +dnslink=/ipns/ +dnslink=/ipfs/ +``` + +| Format | Use Case | +|--------|----------| +| `/ipns/` | Mutable content, updates without DNS change | +| `/ipns/` | Recursive resolution to another DNSLink | +| `/ipfs/` | Immutable content, requires DNS update | + +### Verification + +```bash +dig +short TXT _dnslink.vault.sonr.org +``` + +## Gateway Types + +### DNSLink Gateway (Restricted) + +Serves content from a single IPNS/CID specified in DNS. + +``` +https://vault.sonr.org/ + └── Resolves _dnslink.vault.sonr.org + └── /ipns/k51qzi5uqu5... + └── /ipfs/bafy... +``` + +### Universal Path Gateway (Enterprise) + +Serves any content by CID or IPNS path. + +``` +https://gateway.sonr.org/ipfs/ +https://gateway.sonr.org/ipns/ +https://gateway.sonr.org/ipns/ +``` + +## IPNS Publishing + +### Helia Integration + +```typescript +import { createHelia } from 'helia' +import { ipns } from '@helia/ipns' +import { unixfs } from '@helia/unixfs' +import { generateKeyPair } from '@libp2p/crypto/keys' + +const helia = await createHelia() +const name = ipns(helia) +const fs = unixfs(helia) + +const cid = await fs.addBytes(encryptedEnclaveBlob) + +await name.publish(privateKey, cid, { + lifetime: 24 * 60 * 60 * 1000 +}) +``` + +### DNSLink Resolution + +```typescript +import { dnsLink } from '@helia/dnslink' + +const resolver = dnsLink(helia) +const result = await resolver.resolve('vault.sonr.org') +``` + +## TTL Configuration + +### DNS TTL + +| Use Case | TTL | Rationale | +|----------|-----|-----------| +| Frequently updated content | 60s | Fast propagation | +| Stable content | 3600s | Reduce DNS queries | +| Production default | 300s | Balance between freshness and efficiency | + +### IPNS Lifetime + +| Parameter | Value | Description | +|-----------|-------|-------------| +| `lifetime` | 24h | Record validity period | +| `ttl` | 1h | Suggested cache duration for resolvers | + +```typescript +await name.publish(privateKey, cid, { + lifetime: 24 * 60 * 60 * 1000, + ttl: 60 * 60 * 1000 +}) +``` + +## Cloudflare API Integration + +### Create Web3 Gateway + +```bash +curl "https://api.cloudflare.com/client/v4/zones/{zone_id}/web3/hostnames" \ + --header "Authorization: Bearer " \ + --header "Content-Type: application/json" \ + --data '{ + "name": "vault.sonr.org", + "description": "Sonr Vault IPFS Gateway", + "target": "ipfs", + "dnslink": "/ipns/" + }' +``` + +### Update DNSLink + +```bash +curl "https://api.cloudflare.com/client/v4/zones/{zone_id}/web3/hostnames/{gateway_id}" \ + --request PATCH \ + --header "Authorization: Bearer " \ + --header "Content-Type: application/json" \ + --data '{ + "dnslink": "/ipns/" + }' +``` + +### Create TXT Record + +```bash +curl "https://api.cloudflare.com/client/v4/zones/{zone_id}/dns_records" \ + --header "Authorization: Bearer " \ + --header "Content-Type: application/json" \ + --data '{ + "type": "TXT", + "name": "_dnslink.vault.sonr.org", + "content": "dnslink=/ipns/", + "ttl": 300 + }' +``` + +## Domain Strategy + +### Subdomain Allocation + +| Subdomain | Type | Purpose | +|-----------|------|---------| +| `vault.sonr.org` | DNSLink | User enclave data (IPNS) | +| `did.sonr.org` | DNSLink | DID document resolution (IPNS) | +| `gateway.sonr.org` | Universal | General IPFS access (Enterprise) | +| `relay.sonr.org` | Direct | Circuit Relay server (WSS) | + +### Resolution Flow + +``` +User Request: https://vault.sonr.org/ + │ + ▼ +Cloudflare Edge + │ + ├── Check edge cache + │ └── Hit: Return cached content + │ + ├── DNS Lookup: _dnslink.vault.sonr.org + │ └── TXT: dnslink=/ipns/k51qzi5uqu5... + │ + ├── IPNS Resolution + │ └── k51qzi5uqu5... → /ipfs/bafy... + │ + ├── IPFS Fetch (Bitswap) + │ └── Retrieve blocks from network + │ + └── Cache + Return +``` + +## Caching Behavior + +### Edge Cache + +| Trigger | Action | +|---------|--------| +| First request | Fetch from IPFS, cache at edge | +| Subsequent requests | Serve from edge cache | +| IPNS update | Cache invalidated on TTL expiry | +| Manual purge | API-triggered cache clear | + +### Cache Headers + +``` +Cache-Control: public, max-age=3600 +X-IPFS-Path: /ipfs/bafy... +X-IPFS-Roots: bafy... +ETag: "bafy..." +``` + +## Content Update Flow + +### Without DNS Change (IPNS) + +``` +1. Encrypt new enclave blob +2. Add to Helia: cid = await fs.addBytes(blob) +3. Publish IPNS: await name.publish(key, cid) +4. Cloudflare resolves new CID on next request (after TTL) +``` + +### With DNS Change (CID) + +``` +1. Add content to IPFS +2. Update _dnslink TXT record via Cloudflare API +3. Wait for DNS propagation (TTL) +4. Cloudflare serves new content +``` + +## Security + +### Transport Security + +| Layer | Protection | +|-------|------------| +| Client → Cloudflare | TLS 1.3 | +| Cloudflare → IPFS | Bitswap (encrypted channels) | +| Content | AES-256-GCM (client-side) | + +### Content Integrity + +| Mechanism | Guarantee | +|-----------|-----------| +| CID | SHA-256 hash of content | +| IPNS Signature | Ed25519 signed record | +| DNSLink | DNSSEC (if enabled) | + +### Access Control + +| Method | Implementation | +|--------|----------------| +| Private content | Encrypt before upload | +| Rate limiting | Cloudflare WAF rules | +| Blocklist | Universal gateway content filtering | + +## Monitoring + +### Cloudflare Analytics + +| Metric | Source | +|--------|--------| +| Requests | Web3 Gateway dashboard | +| Bandwidth | Zone analytics | +| Cache hit ratio | Cache analytics | +| Error rates | Firewall events | + +### IPNS Health + +```typescript +const status = { + peerId: helia.libp2p.peerId.toString(), + peers: helia.libp2p.getPeers().length, + ipnsRecords: await getPublishedRecords() +} +``` + +## Implementation Checklist + +### Cloudflare Setup + +- [ ] Enable Web3 gateway on zone +- [ ] Create DNSLink gateway for `vault.sonr.org` +- [ ] Create DNSLink gateway for `did.sonr.org` +- [ ] Create Universal gateway for `gateway.sonr.org` (Enterprise) +- [ ] Configure TXT records for each subdomain +- [ ] Verify CNAME proxying enabled +- [ ] Set appropriate TTLs + +### Relay Node Setup + +- [ ] Deploy `ipfs-service-provider` with relay enabled +- [ ] Generate persistent IPNS keypair +- [ ] Configure IPNS publishing on content update +- [ ] Set up monitoring for IPNS record health +- [ ] Configure peering with Cloudflare (Enterprise) + +### SDK Integration + +- [ ] Add `@helia/ipns` for publishing +- [ ] Add `@helia/dnslink` for resolution +- [ ] Implement enclave CID tracking +- [ ] Add IPNS publish on enclave save +- [ ] Handle offline/online sync + +## Dependencies + +```json +{ + "@helia/ipns": "^8.0.0", + "@helia/dnslink": "^4.0.0", + "@helia/unixfs": "^4.0.0", + "helia": "^5.0.0" +} +``` + +## References + +- [Cloudflare Web3 Gateway](https://developers.cloudflare.com/web3/) +- [IPFS DNSLink Specification](https://dnslink.dev/) +- [Helia IPNS Package](https://github.com/ipfs/helia/tree/main/packages/ipns) +- [Cloudflare API - Web3](https://developers.cloudflare.com/api/resources/web3/) diff --git a/STORAGE.md b/STORAGE.md new file mode 100644 index 0000000..44e8430 --- /dev/null +++ b/STORAGE.md @@ -0,0 +1,404 @@ +# IPFS Storage Architecture for Motr Enclave + +Decentralized storage layer for Nebula wallet's encrypted key enclave using IPFS via Helia with Circuit Relay connectivity. + +## Architecture + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ NEBULA WALLET STORAGE │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────────────┐ ┌─────────────────────────────────┐ │ +│ │ Browser Environment │ │ Sonr Relay Infrastructure │ │ +│ │ │ │ │ │ +│ │ ┌───────────────────┐ │ WSS │ ┌───────────────────────────┐ │ │ +│ │ │ Motr Enclave │ │◄───────►│ │ ipfs-service-provider │ │ │ +│ │ │ (WASM Plugin) │ │ │ │ + circuitRelayServer() │ │ │ +│ │ └───────────────────┘ │ │ └───────────────────────────┘ │ │ +│ │ │ │ │ │ │ │ +│ │ ▼ │ │ ▼ │ │ +│ │ ┌───────────────────┐ │ │ ┌───────────────────────────┐ │ │ +│ │ │ Helia Client │ │ │ │ Helia Node │ │ │ +│ │ │ (Browser SDK) │──┼─────────┼──│ (Content Pinning) │ │ │ +│ │ └───────────────────┘ │ Relay │ └───────────────────────────┘ │ │ +│ │ │ │ │ │ │ │ +│ └───────────┼─────────────┘ └──────────────┼──────────────────┘ │ +│ │ │ │ +│ │ /p2p-circuit │ DHT / Bitswap │ +│ ▼ ▼ │ +│ ┌─────────────────────────────────────────────────────────────────────┐ │ +│ │ IPFS Network │ │ +│ └─────────────────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +## Data Classification + +### MUST Store on IPFS + +| Data Type | Format | Mutability | +|-----------|--------|------------| +| Encrypted DB Blob | Raw bytes | Immutable (new CID per version) | +| DID Document | DAG-CBOR | Mutable via IPNS | +| UCAN Delegations | DAG-CBOR | Immutable CID references | +| Verification Methods | DAG-CBOR | Part of DID Document | + +### MUST NOT Store on IPFS + +| Data Type | Reason | +|-----------|--------| +| MPC Key Shares | Core secret material | +| WebAuthn PRF Output | Decryption key derivation | +| Session Tokens | Ephemeral, local-only | +| Plaintext Credentials | Security violation | + +--- + +## Browser Client Requirements + +### Transport Configuration + +The browser Helia client MUST configure these transports: + +```typescript +transports: [ + webSockets({ filter: filters.all }), + webRTC(), + circuitRelayTransport({ discoverRelays: 2 }) +] +``` + +### Listen Addresses + +The client MUST listen on: + +```typescript +addresses: { + listen: ['/p2p-circuit', '/webrtc'] +} +``` + +### Required Services + +The `identify` service is MANDATORY for Circuit Relay v2: + +```typescript +services: { + identify: identify() +} +``` + +### Bootstrap Peers + +The client MUST bootstrap with at least 2 Sonr relay multiaddrs: + +```typescript +peerDiscovery: [ + bootstrap({ + list: [ + '/dns4/relay1.sonr.io/tcp/443/wss/p2p/', + '/dns4/relay2.sonr.io/tcp/443/wss/p2p/' + ] + }) +] +``` + +### Complete Client Implementation + +```typescript +import { createHelia } from 'helia' +import { circuitRelayTransport } from '@libp2p/circuit-relay-v2' +import { webRTC } from '@libp2p/webrtc' +import { webSockets } from '@libp2p/websockets' +import { bootstrap } from '@libp2p/bootstrap' +import { identify } from '@libp2p/identify' +import { noise } from '@chainsafe/libp2p-noise' +import { yamux } from '@chainsafe/libp2p-yamux' +import * as filters from '@libp2p/websockets/filters' + +const SONR_RELAYS = [ + '/dns4/relay1.sonr.io/tcp/443/wss/p2p/', + '/dns4/relay2.sonr.io/tcp/443/wss/p2p/' +] + +export async function createStorageClient(): Promise { + return await createHelia({ + libp2p: { + addresses: { + listen: ['/p2p-circuit', '/webrtc'] + }, + transports: [ + webSockets({ filter: filters.all }), + webRTC(), + circuitRelayTransport({ discoverRelays: 2 }) + ], + connectionEncrypters: [noise()], + streamMuxers: [yamux()], + peerDiscovery: [ + bootstrap({ list: SONR_RELAYS }) + ], + services: { + identify: identify() + }, + connectionGater: { + denyDialMultiaddr: () => false + } + } + }) +} +``` + +--- + +## Relay Server Requirements + +### Circuit Relay Configuration + +The relay server MUST enable `circuitRelayServer` with these constraints: + +```javascript +import { circuitRelayServer } from '@libp2p/circuit-relay-v2' + +services: { + relay: circuitRelayServer({ + hopTimeout: 30 * 1000, + reservations: { + maxReservations: 100, + reservationClearInterval: 300 * 1000, + applyDefaultLimit: true, + defaultDurationLimit: 2 * 60 * 1000, + defaultDataLimit: BigInt(1 << 20) + }, + maxInboundHopStreams: 64, + maxOutboundHopStreams: 128 + }) +} +``` + +### Listen Addresses + +The relay MUST listen on: + +```javascript +addresses: { + listen: [ + '/ip4/0.0.0.0/tcp/4001', + '/ip4/0.0.0.0/tcp/4003/ws', + '/ip4/0.0.0.0/udp/4001/quic-v1', + '/webrtc' + ] +} +``` + +### Environment Variables + +| Variable | Required | Description | +|----------|----------|-------------| +| `ENABLE_CIRCUIT_RELAY` | Yes | Set to `1` | +| `CR_DOMAIN` | Yes | Relay domain (e.g., `relay.sonr.org`) | +| `IPFS_TCP_PORT` | Yes | TCP port (default: `4001`) | +| `IPFS_WS_PORT` | Yes | WebSocket port (default: `4003`) | +| `COORD_NAME` | No | Node identifier for coordination | + +### Infrastructure Requirements + +| Requirement | Specification | +|-------------|---------------| +| SSL/TLS | Valid certificate for WSS | +| Reverse Proxy | Nginx forwarding 443 → `IPFS_WS_PORT` | +| Redundancy | Minimum 2 relay nodes | +| Uptime | 99.9% availability target | + +--- + +## Storage Operations + +### Enclave Persistence + +```typescript +import { unixfs } from '@helia/unixfs' +import type { CID } from 'multiformats/cid' +import type { Helia } from 'helia' + +export class EnclaveStorage { + private fs: UnixFS + + constructor(private helia: Helia) { + this.fs = unixfs(helia) + } + + async persist(encryptedBlob: Uint8Array): Promise { + const cid = await this.fs.addBytes(encryptedBlob) + await this.helia.pins.add(cid) + return cid + } + + async retrieve(cid: CID): Promise { + const chunks: Uint8Array[] = [] + for await (const chunk of this.fs.cat(cid)) { + chunks.push(chunk) + } + return concatUint8Arrays(chunks) + } +} +``` + +### DID Publishing + +```typescript +import { ipns } from '@helia/ipns' +import { dagCbor } from '@helia/dag-cbor' +import type { PrivateKey } from '@libp2p/interface' + +export class DIDPublisher { + private name: IPNS + private cbor: DagCBOR + + constructor(private helia: Helia) { + this.name = ipns(helia) + this.cbor = dagCbor(helia) + } + + async publish(didDocument: object, key: PrivateKey): Promise { + const cid = await this.cbor.add(didDocument) + await this.name.publish(key, cid) + return `/ipns/${key.publicKey.toCID()}` + } + + async resolve(ipnsName: string): Promise { + const result = await this.name.resolve(ipnsName) + return await this.cbor.get(result.cid) + } +} +``` + +--- + +## Security Requirements + +### Encryption + +| Layer | Requirement | +|-------|-------------| +| Transport | TLS 1.3 (WSS) + libp2p Noise protocol | +| Storage | AES-256-GCM with WebAuthn PRF-derived key | +| Integrity | SHA-256 content hash (CID) | + +### Relay Trust Model + +| Property | Guarantee | +|----------|-----------| +| Zero-Knowledge | Relay MUST NOT see plaintext data | +| No Persistence | Relay MUST NOT store relayed content | +| Rate Limiting | Relay MUST enforce per-peer limits | + +### Client Responsibilities + +| Responsibility | Requirement | +|----------------|-------------| +| Encrypt before upload | All data MUST be encrypted client-side | +| Verify on download | Client MUST verify CID matches content | +| Local CID storage | Latest CID MUST persist in IndexedDB | +| Key isolation | Decryption keys MUST NOT leave browser | + +--- + +## API Specification + +### StorageClient Interface + +```typescript +interface StorageClient { + connect(): Promise + disconnect(): Promise + isConnected(): boolean + + persistEnclave(encrypted: Uint8Array): Promise + retrieveEnclave(cid: CID): Promise + + publishDID(doc: DIDDocument, key: PrivateKey): Promise + resolveDID(name: string): Promise + + getStatus(): StorageStatus +} + +interface StorageStatus { + peerId: string + connected: boolean + peerCount: number + relayCount: number +} +``` + +### Relay API Endpoints + +| Endpoint | Method | Response | +|----------|--------|----------| +| `/ipfs` | GET | `{ ipfsId, peers, relays }` | +| `/ipfs/node` | GET | Full node configuration | +| `/ipfs/relays` | POST | Known relay list | + +--- + +## Dependencies + +### Browser SDK + +```json +{ + "helia": "^5.0.0", + "@helia/unixfs": "^4.0.0", + "@helia/ipns": "^8.0.0", + "@helia/dag-cbor": "^4.0.0", + "@libp2p/circuit-relay-v2": "^3.0.0", + "@libp2p/webrtc": "^5.0.0", + "@libp2p/websockets": "^9.0.0", + "@libp2p/bootstrap": "^11.0.0", + "@libp2p/identify": "^3.0.0", + "@chainsafe/libp2p-noise": "^16.0.0", + "@chainsafe/libp2p-yamux": "^7.0.0", + "multiformats": "^13.0.0" +} +``` + +### Relay Server + +```json +{ + "helia": "^5.0.0", + "helia-coord": "^3.0.0", + "@libp2p/circuit-relay-v2": "^3.0.0", + "@libp2p/tcp": "^10.0.0", + "@libp2p/websockets": "^9.0.0", + "@libp2p/webrtc": "^5.0.0" +} +``` + +--- + +## File Structure + +``` +src/ +├── storage/ +│ ├── client.ts # Helia client factory +│ ├── enclave.ts # Enclave persistence +│ ├── did.ts # DID/IPNS operations +│ └── types.ts # TypeScript interfaces +deploy/ +├── relay/ +│ ├── docker-compose.yml +│ └── config.env +└── nginx/ + └── relay.conf # WSS proxy config +``` + +--- + +## References + +- [Helia](https://github.com/ipfs/helia) +- [libp2p Circuit Relay v2 Spec](https://github.com/libp2p/specs/blob/master/relay/circuit-v2.md) +- [ipfs-service-provider](https://github.com/Permissionless-Software-Foundation/ipfs-service-provider) +- [UCAN Spec v1.0.0-rc.1](https://github.com/ucan-wg/spec) diff --git a/deploy/nginx/relay.conf b/deploy/nginx/relay.conf new file mode 100644 index 0000000..e071bd1 --- /dev/null +++ b/deploy/nginx/relay.conf @@ -0,0 +1,86 @@ +limit_req_zone $binary_remote_addr zone=ws_limit:10m rate=10r/s; + +upstream relay_ws { + server relay:4003; + keepalive 64; +} + +upstream relay_api { + server relay:5020; + keepalive 32; +} + +server { + listen 80; + listen [::]:80; + server_name relay.sonr.org; + + location /.well-known/acme-challenge/ { + root /var/www/certbot; + } + + location / { + return 301 https://$host$request_uri; + } +} + +server { + listen 443 ssl http2; + listen [::]:443 ssl http2; + server_name relay.sonr.org; + + ssl_certificate /etc/nginx/certs/fullchain.pem; + ssl_certificate_key /etc/nginx/certs/privkey.pem; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384; + ssl_prefer_server_ciphers off; + ssl_session_timeout 1d; + ssl_session_cache shared:SSL:50m; + ssl_session_tickets off; + ssl_stapling on; + ssl_stapling_verify on; + + add_header Strict-Transport-Security "max-age=63072000" always; + add_header X-Frame-Options DENY; + add_header X-Content-Type-Options nosniff; + + location / { + limit_req zone=ws_limit burst=20 nodelay; + + proxy_pass http://relay_ws; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_connect_timeout 60s; + proxy_send_timeout 300s; + proxy_read_timeout 300s; + proxy_buffering off; + proxy_buffer_size 8k; + } + + location /ipfs { + limit_req zone=ws_limit burst=10 nodelay; + + proxy_pass http://relay_api; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_connect_timeout 30s; + proxy_send_timeout 30s; + proxy_read_timeout 30s; + } + + location /health { + proxy_pass http://relay_api/ipfs; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_connect_timeout 5s; + proxy_read_timeout 5s; + } +} diff --git a/deploy/relay/Dockerfile b/deploy/relay/Dockerfile new file mode 100644 index 0000000..12b7290 --- /dev/null +++ b/deploy/relay/Dockerfile @@ -0,0 +1,26 @@ +FROM node:20-alpine + +LABEL org.opencontainers.image.source="https://github.com/sonr-io/motr-enclave" +LABEL org.opencontainers.image.description="Sonr IPFS Circuit Relay Server" + +RUN apk add --no-cache git python3 make g++ + +WORKDIR /app + +RUN git clone --depth 1 https://github.com/Permissionless-Software-Foundation/ipfs-service-provider.git . + +RUN npm ci --only=production + +ENV NODE_ENV=production +ENV PORT=5020 +ENV ENABLE_CIRCUIT_RELAY=1 +ENV IPFS_TCP_PORT=4001 +ENV IPFS_WS_PORT=4003 +ENV DEBUG_LEVEL=1 + +EXPOSE 5020 4001 4003 + +HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \ + CMD wget --no-verbose --tries=1 --spider http://localhost:${PORT}/ipfs || exit 1 + +CMD ["npm", "start"] diff --git a/deploy/relay/config.env b/deploy/relay/config.env new file mode 100644 index 0000000..afdde05 --- /dev/null +++ b/deploy/relay/config.env @@ -0,0 +1,16 @@ +NODE_ENV=production + +ENABLE_CIRCUIT_RELAY=1 +CR_DOMAIN=relay.sonr.org + +PORT=5020 +API_PORT=5020 +IPFS_TCP_PORT=4001 +IPFS_WS_PORT=4003 + +COORD_NAME=sonr-relay-1 +DEBUG_LEVEL=1 + +MAX_RESERVATIONS=100 +RESERVATION_DURATION=120000 +RESERVATION_DATA_LIMIT=1048576 diff --git a/deploy/relay/docker-compose.yml b/deploy/relay/docker-compose.yml new file mode 100644 index 0000000..763a88f --- /dev/null +++ b/deploy/relay/docker-compose.yml @@ -0,0 +1,53 @@ +version: "3.8" + +services: + relay: + build: + context: . + dockerfile: Dockerfile + container_name: sonr-relay + restart: unless-stopped + env_file: + - config.env + ports: + - "${IPFS_TCP_PORT:-4001}:4001" + - "${IPFS_TCP_PORT:-4001}:4001/udp" + - "${IPFS_WS_PORT:-4003}:4003" + - "${API_PORT:-5020}:5020" + volumes: + - relay_data:/app/.ipfs + networks: + - sonr-network + healthcheck: + test: ["CMD", "wget", "--spider", "-q", "http://localhost:5020/ipfs"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + logging: + driver: json-file + options: + max-size: "10m" + max-file: "3" + + nginx: + image: nginx:alpine + container_name: sonr-relay-proxy + restart: unless-stopped + ports: + - "80:80" + - "443:443" + volumes: + - ../nginx/relay.conf:/etc/nginx/conf.d/default.conf:ro + - ./certs:/etc/nginx/certs:ro + depends_on: + - relay + networks: + - sonr-network + +volumes: + relay_data: + +networks: + sonr-network: + driver: bridge