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.
This commit is contained in:
parent
b0c4684b4a
commit
a8e411faa0
6 changed files with 45 additions and 2 deletions
12
AGENTS.md
12
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 /<sha> → 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.
|
||||
|
|
|
|||
|
|
@ -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() {
|
|||
</div>
|
||||
)}
|
||||
|
||||
{/* ── SkateSpots' provided default ───────────────────────────────── */}
|
||||
<div className="rounded-2xl border border-white/8 bg-white/2 p-4 space-y-2.5">
|
||||
<p className="flex items-center gap-2 text-sm font-bold text-white">
|
||||
<Server className="h-4 w-4 text-cyan-400" />
|
||||
{t('socialUi.coordinator.ourDefaultTitle')}
|
||||
</p>
|
||||
<p className="text-xs leading-relaxed text-muted-foreground">
|
||||
{t('socialUi.coordinator.ourDefaultDesc')}
|
||||
</p>
|
||||
<Button size="sm" variant="outline" onClick={() => setCoordinator(SKATESPOTS_COORDINATOR)}>
|
||||
{t('socialUi.coordinator.useOurs')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* ── Add / change ────────────────────────────────────────────────── */}
|
||||
<div className="rounded-2xl border border-white/8 bg-white/2 p-4 space-y-3">
|
||||
<p className="text-sm font-bold text-white">{coordinator ? t('socialUi.coordinator.change') : t('socialUi.coordinator.addTitle')}</p>
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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<CoordinatorConfig, 'pubkey' | 'relays'> = {
|
||||
pubkey: '02816a05b1d4e70a7df9c712ef6f7272774269d65bd8a2f262ce0330d0885693',
|
||||
relays: ['wss://relay.skate-spots.org'],
|
||||
};
|
||||
|
||||
const HEX64 = /^[0-9a-f]{64}$/i;
|
||||
|
||||
function uniqueRelays(relays: Iterable<string>): string[] {
|
||||
|
|
|
|||
Loading…
Reference in a new issue