From a8e411faa048bff46c5cb3447485d9e4c81cf115 Mon Sep 17 00:00:00 2001 From: SkateSpots Date: Mon, 12 Oct 2026 16:36:56 +0700 Subject: [PATCH] feat(cordn): embedded coordinator on the relay, crews default to it, disclosure Newlay runs its embedded Cordn coordinator (identity cordn.key, uid 10001). SKATESPOTS_COORDINATOR is the provided default: Settings -> Coordinator shows a 'Use SkateSpots coordinator' block and CrewForm prefills it for new crews; BYO coordinator stays client-side. Disclosure copy (coordinators see ciphertext + metadata, never contents) in socialUi.coordinator.ourDefault* + existing privacyNote. --- AGENTS.md | 12 ++++++++++++ src/components/CoordinatorSettingsCard.tsx | 15 +++++++++++++++ src/components/CrewForm.tsx | 5 +++-- src/i18n/en.ts | 3 +++ src/i18n/es.ts | 3 +++ src/lib/coordinator.ts | 9 +++++++++ 6 files changed, 45 insertions(+), 2 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 4f95ae8..f5dd8f5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2123,6 +2123,18 @@ The community relay moved off the hosted relay.tools instance (`lib/imageFallback.ts`) includes it so reads fall back across all three. Verified end-to-end (signed PUT /upload → 201, GET / → 200). To fully self-host media later, drop the two public hosts from both lists. +- **Cordn coordinator (2026-10-12):** the relay also runs Newlay's **embedded Cordn + coordinator** for crew MLS chat (`[cordn] enabled`, `mode = "embedded"`, + `data_dir = /var/lib/newlay/data/cordn` inside the volume, `relay_url = + ws://127.0.0.1:7777`). Its identity is `cordn.key` (mounted read-only) — pubkey + `02816a05…` — ⚠️ the file must be owned by the container's `newlay` user + (uid 10001, `chown 10001:10001`), else the relay crashes on + `/etc/newlay/cordn.key: Permission denied`. BYO coordinator stays client-side. + In the app: `SKATESPOTS_COORDINATOR` (`src/lib/coordinator.ts`) is the provided + default; **Settings → Coordinator** shows a "Use SkateSpots coordinator" block + and **CrewForm** prefills it for new crews. Disclosure copy lives in + `socialUi.coordinator.ourDefault*` + the existing `privacyNote` (coordinators see + ciphertext + metadata, never contents). - **Cutover (2026-10-12):** `SKATE_RELAY` (`src/lib/types.ts`) → the new relay; the default pools, the DM/CVM/zap relay arrays, the `cvm-server` + `push-service` defaults, and the archive default all point at the new relay. diff --git a/src/components/CoordinatorSettingsCard.tsx b/src/components/CoordinatorSettingsCard.tsx index 36852f1..e2f1358 100644 --- a/src/components/CoordinatorSettingsCard.tsx +++ b/src/components/CoordinatorSettingsCard.tsx @@ -24,6 +24,7 @@ import { coordinatorAddUrl, parseCoordinatorInput, shortNpub, + SKATESPOTS_COORDINATOR, } from '@/lib/coordinator'; import { cn } from '@/lib/utils'; @@ -198,6 +199,20 @@ export function CoordinatorSettingsCard() { )} + {/* ── SkateSpots' provided default ───────────────────────────────── */} +
+

+ + {t('socialUi.coordinator.ourDefaultTitle')} +

+

+ {t('socialUi.coordinator.ourDefaultDesc')} +

+ +
+ {/* ── Add / change ────────────────────────────────────────────────── */}

{coordinator ? t('socialUi.coordinator.change') : t('socialUi.coordinator.addTitle')}

diff --git a/src/components/CrewForm.tsx b/src/components/CrewForm.tsx index 0b46600..dd03ee5 100644 --- a/src/components/CrewForm.tsx +++ b/src/components/CrewForm.tsx @@ -28,7 +28,7 @@ import { useCrewGoal } from '@/hooks/useCrewGoal'; import { useCoordinator, useCoordinatorHealth } from '@/hooks/useCoordinator'; import { normalizeUrlInput, sanitizeUrl } from '@/lib/sanitizeUrl'; import { Nip05HandlesEditor } from '@/components/Nip05HandlesEditor'; -import { coordinatorAddUrl, crewCoordinatorInputValue, parseCrewCoordinatorInput, shortNpub } from '@/lib/coordinator'; +import { coordinatorAddUrl, crewCoordinatorInputValue, parseCrewCoordinatorInput, shortNpub, SKATESPOTS_COORDINATOR } from '@/lib/coordinator'; import { useRelays } from '@/lib/relays'; import { cn } from '@/lib/utils'; import { Users, Upload, X, Loader2, MapPin, Plus, ShieldCheck, AtSign, HardHat, Camera, GraduationCap, Sparkles, Target, Megaphone, Music, Palette, Dumbbell, Eye, Store, Film, Check, ArrowRight, ArrowLeft as ArrowLeftIcon, Server, ChevronDown, Zap } from 'lucide-react'; @@ -332,7 +332,8 @@ export function CrewForm({ existing }: CrewFormProps) { // Sponsoring shop coordinate (30017:pubkey:d) and Cordn/MLS coordinator pointer. const [sponsorRef, setSponsorRef] = useState(existing?.sponsorRef ?? ''); const [coordinatorInput, setCoordinatorInput] = useState( - crewCoordinatorInputValue(existing?.coordinator), + crewCoordinatorInputValue(existing?.coordinator) || + (isEditing ? '' : crewCoordinatorInputValue({ url: SKATESPOTS_COORDINATOR.relays[0], pubkey: SKATESPOTS_COORDINATOR.pubkey })), ); // The coordinator the user set in Settings → Coordinator, for a one-tap prefill. const { coordinator: myCoordinator } = useCoordinator(); diff --git a/src/i18n/en.ts b/src/i18n/en.ts index 9266f69..64d62b2 100644 --- a/src/i18n/en.ts +++ b/src/i18n/en.ts @@ -3918,6 +3918,9 @@ socialUi: { runOwnDesc: "It can live on an old phone, a Raspberry Pi, or any small box you leave on. Your group keeps working while it's up — and you can move the group whenever you like.", onPhone: 'On a phone', onPhoneDesc: 'Use the Cordn Coordinator app, then scan its QR here to add it.', + ourDefaultTitle: 'SkateSpots coordinator', + ourDefaultDesc: 'SkateSpots runs a coordinator at relay.skate-spots.org. Use it by default, or run your own — a coordinator only ever sees ciphertext and metadata, never your messages.', + useOurs: 'Use SkateSpots coordinator', openCordn: 'Open Cordn', scanItsQR: 'Scan its QR', onServer: 'On a server', diff --git a/src/i18n/es.ts b/src/i18n/es.ts index b77f67b..f923af1 100644 --- a/src/i18n/es.ts +++ b/src/i18n/es.ts @@ -3918,6 +3918,9 @@ socialUi: { runOwnDesc: 'Puede vivir en un móvil viejo, una Raspberry Pi o cualquier cacharro que dejes encendido. Tu grupo sigue funcionando mientras esté activo — y puedes mover el grupo cuando quieras.', onPhone: 'En un móvil', onPhoneDesc: 'Usa la app Cordn Coordinator y escanea su QR aquí para añadirlo.', + ourDefaultTitle: 'Coordinador de SkateSpots', + ourDefaultDesc: 'SkateSpots ejecuta un coordinador en relay.skate-spots.org. Úsalo por defecto, o monta el tuyo — un coordinador solo ve texto cifrado y metadatos, nunca tus mensajes.', + useOurs: 'Usar el coordinador de SkateSpots', openCordn: 'Abrir Cordn', scanItsQR: 'Escanear su QR', onServer: 'En un servidor', diff --git a/src/lib/coordinator.ts b/src/lib/coordinator.ts index 512e495..c2477e6 100644 --- a/src/lib/coordinator.ts +++ b/src/lib/coordinator.ts @@ -40,6 +40,15 @@ export interface CoordinatorConfig { addedAt: number; } +/** SkateSpots' own coordinator — embedded in our relay at wss://relay.skate-spots.org. + * Provided as the default for crews and Settings → Coordinator; users may always + * run their own (a coordinator only ever sees ciphertext + metadata, never message + * contents). Pubkey = the relay's `cordn.key`. */ +export const SKATESPOTS_COORDINATOR: Pick = { + pubkey: '02816a05b1d4e70a7df9c712ef6f7272774269d65bd8a2f262ce0330d0885693', + relays: ['wss://relay.skate-spots.org'], +}; + const HEX64 = /^[0-9a-f]{64}$/i; function uniqueRelays(relays: Iterable): string[] {