From 0a7543cf321f3c01633fbd738fb71c1fa30d8d05 Mon Sep 17 00:00:00 2001 From: Dorian Date: Thu, 12 Feb 2026 12:33:22 +0000 Subject: [PATCH] Add Nostr relay + seed data to Docker deployment - Add nostr-rs-relay service to docker-compose for persistent comments, reactions, and profiles on the dev server - Add one-shot seeder container that auto-populates the relay with test personas, reactions, and comments on first deploy - Proxy WebSocket connections through nginx at /relay so the frontend connects to the relay on the same host (no CORS) - Make relay URL dynamic: reads from VITE_NOSTR_RELAYS in dev, auto-detects /relay proxy path in production Docker builds - Make seed scripts configurable via RELAY_URL and ORIGIN env vars - Add wait-for-relay script for reliable container orchestration - Add "Resume last played" hero banner on My List tab Co-authored-by: Cursor --- Dockerfile | 4 ++++ Dockerfile.seed | 23 ++++++++++++++++++++++ docker-compose.yml | 39 +++++++++++++++++++++++++++++++++---- nginx.conf | 16 +++++++++++++++ scripts/seed-activity.ts | 4 ++-- scripts/seed-profiles.ts | 2 +- scripts/wait-for-relay.mjs | 40 ++++++++++++++++++++++++++++++++++++++ src/lib/relay.ts | 23 ++++++++++++++++++++-- src/views/Browse.vue | 23 +++++++++++++++++++--- 9 files changed, 162 insertions(+), 12 deletions(-) create mode 100644 Dockerfile.seed create mode 100644 scripts/wait-for-relay.mjs diff --git a/Dockerfile b/Dockerfile index ba60d1a..1e475d4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,6 +12,10 @@ RUN npm ci # Copy source code COPY . . +# Clear VITE_NOSTR_RELAYS so the app auto-detects the relay +# via the /relay nginx proxy at runtime (instead of hardcoding localhost) +ENV VITE_NOSTR_RELAYS="" + # Build the application RUN npm run build diff --git a/Dockerfile.seed b/Dockerfile.seed new file mode 100644 index 0000000..0e0c790 --- /dev/null +++ b/Dockerfile.seed @@ -0,0 +1,23 @@ +# Seeder container — populates the Nostr relay with test profiles, +# reactions, and comments so the dev deployment has content. +# +# Runs once and exits. docker-compose "restart: no" keeps it from looping. + +FROM node:20-alpine + +WORKDIR /app + +# Install dependencies +COPY package*.json ./ +RUN npm ci --ignore-scripts + +# Copy only what the seed scripts need +COPY scripts/ ./scripts/ +COPY src/data/testPersonas.ts ./src/data/testPersonas.ts +COPY tsconfig.json ./ + +# Default env (overridden by docker-compose) +ENV RELAY_URL=ws://relay:8080 +ENV ORIGIN=http://localhost:7777 + +CMD ["sh", "-c", "node scripts/wait-for-relay.mjs && npx tsx scripts/seed-profiles.ts && npx tsx scripts/seed-activity.ts && echo '✅ Seeding complete!'"] diff --git a/docker-compose.yml b/docker-compose.yml index a194a65..a58a3da 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,8 @@ version: '3.8' services: - indeedhub: + # ── Frontend (nginx serving built Vue app) ─────────────────── + app: build: context: . dockerfile: Dockerfile @@ -9,19 +10,49 @@ services: restart: unless-stopped ports: - "7777:7777" - environment: - - NODE_ENV=production + depends_on: + seeder: + condition: service_completed_successfully networks: - indeedhub-network labels: - "com.centurylinklabs.watchtower.enable=true" healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:7777/"] + test: ["CMD", "curl", "-f", "http://localhost:7777/health"] interval: 30s timeout: 10s retries: 3 start_period: 40s + # ── Nostr Relay (stores comments, reactions, profiles) ─────── + relay: + image: scsibug/nostr-rs-relay:latest + container_name: indeedhub-relay + restart: unless-stopped + volumes: + - relay-data:/usr/src/app/db + networks: + - indeedhub-network + + # ── Seeder (one-shot: seeds test data into relay, then exits) ─ + # wait-for-relay.mjs handles readiness polling before seeding. + seeder: + build: + context: . + dockerfile: Dockerfile.seed + container_name: indeedhub-seeder + depends_on: + - relay + environment: + - RELAY_URL=ws://relay:8080 + - ORIGIN=http://localhost:7777 + networks: + - indeedhub-network + restart: "no" + networks: indeedhub-network: driver: bridge + +volumes: + relay-data: diff --git a/nginx.conf b/nginx.conf index 7d2887a..6df0653 100644 --- a/nginx.conf +++ b/nginx.conf @@ -27,6 +27,22 @@ server { add_header Cache-Control "public, immutable"; } + # WebSocket proxy to Nostr relay (Docker service) + location /relay { + resolver 127.0.0.11 valid=30s ipv6=off; + set $relay_upstream http://relay:8080; + + rewrite ^/relay(.*) /$1 break; + proxy_pass $relay_upstream; + 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_read_timeout 86400s; + proxy_send_timeout 86400s; + } + # Vue Router - SPA fallback location / { try_files $uri $uri/ /index.html; diff --git a/scripts/seed-activity.ts b/scripts/seed-activity.ts index cab793f..271d099 100644 --- a/scripts/seed-activity.ts +++ b/scripts/seed-activity.ts @@ -11,8 +11,8 @@ import { TASTEMAKER_PERSONAS, } from '../src/data/testPersonas.js' -const RELAY_URL = 'ws://localhost:7777' -const ORIGIN = 'http://localhost:5174' +const RELAY_URL = process.env.RELAY_URL || 'ws://localhost:7777' +const ORIGIN = process.env.ORIGIN || 'http://localhost:5174' // ── Content catalog (matching src/data/indeeHubFilms.ts) ────────── const CONTENT = [ diff --git a/scripts/seed-profiles.ts b/scripts/seed-profiles.ts index 4bed5ff..3002b5c 100644 --- a/scripts/seed-profiles.ts +++ b/scripts/seed-profiles.ts @@ -5,7 +5,7 @@ import { TASTEMAKER_PERSONAS, } from '../src/data/testPersonas.js' -const RELAY_URL = 'ws://localhost:7777' +const RELAY_URL = process.env.RELAY_URL || 'ws://localhost:7777' type Persona = { name: string; nsec: string; pubkey: string } diff --git a/scripts/wait-for-relay.mjs b/scripts/wait-for-relay.mjs new file mode 100644 index 0000000..496607e --- /dev/null +++ b/scripts/wait-for-relay.mjs @@ -0,0 +1,40 @@ +/** + * Waits for the Nostr relay to be reachable before seeding. + * Used by the Docker seeder container. + * + * Usage: node scripts/wait-for-relay.mjs + * Env: RELAY_URL (default ws://localhost:7777) + */ +import http from 'node:http' + +const wsUrl = process.env.RELAY_URL || 'ws://localhost:7777' +const httpUrl = wsUrl.replace('ws://', 'http://').replace('wss://', 'https://') +const maxAttempts = 30 +const intervalMs = 2000 + +console.log(`Waiting for relay at ${httpUrl} ...`) + +for (let i = 1; i <= maxAttempts; i++) { + const ok = await new Promise((resolve) => { + const req = http.get(httpUrl, (res) => { + res.resume() // drain response + resolve(res.statusCode >= 200 && res.statusCode < 400) + }) + req.on('error', () => resolve(false)) + req.setTimeout(3000, () => { + req.destroy() + resolve(false) + }) + }) + + if (ok) { + console.log(`Relay is ready! (attempt ${i}/${maxAttempts})`) + process.exit(0) + } + + console.log(` attempt ${i}/${maxAttempts} — not ready yet`) + await new Promise((r) => setTimeout(r, intervalMs)) +} + +console.error(`Relay did not become ready after ${maxAttempts} attempts`) +process.exit(1) diff --git a/src/lib/relay.ts b/src/lib/relay.ts index 3bd11bb..03a39ca 100644 --- a/src/lib/relay.ts +++ b/src/lib/relay.ts @@ -9,8 +9,27 @@ import { RelayPool } from 'applesauce-relay' // Relay pool for all WebSocket connections export const pool = new RelayPool() -// App relays (local dev relay) -export const APP_RELAYS = ['ws://localhost:7777'] +/** + * Determine app relay URLs at runtime. + * + * Priority: + * 1. VITE_NOSTR_RELAYS env var (set in .env for local dev) + * 2. Auto-detect: proxy through same host at /relay (Docker / production) + */ +function getAppRelays(): string[] { + const envRelays = import.meta.env.VITE_NOSTR_RELAYS as string | undefined + if (envRelays) { + const parsed = envRelays.split(',').map((r: string) => r.trim()).filter(Boolean) + if (parsed.length > 0) return parsed + } + + // Production / Docker: relay is proxied through nginx at /relay + const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:' + return [`${protocol}//${window.location.host}/relay`] +} + +// App relays (local dev relay or proxied production relay) +export const APP_RELAYS = getAppRelays() // Lookup relays for profile metadata export const LOOKUP_RELAYS = ['wss://purplepag.es'] diff --git a/src/views/Browse.vue b/src/views/Browse.vue index f04a766..00b15a5 100644 --- a/src/views/Browse.vue +++ b/src/views/Browse.vue @@ -20,6 +20,15 @@
+ + +
+ + + + Continue where you left off +
+

{{ featuredContent?.title || 'GOD BLESS BITCOIN' }} @@ -30,8 +39,16 @@ {{ featuredContent?.description || 'A groundbreaking documentary exploring the intersection of faith, finance, and the future of money through the lens of Bitcoin.' }}

- -
+ +
+
+
+
+ {{ resumeItem.progress }}% watched +
+ + +
{{ featuredContent.rating }} {{ featuredContent.releaseYear }} {{ featuredContent.duration }}min @@ -44,7 +61,7 @@ - Play + {{ isMyListTab && resumeItem ? 'Resume' : 'Play' }}