#!/usr/bin/env node // ===================================================================== // Erzeugt die Schlüssel für eine self-gehostete Supabase-Instanz: // - JWT_SECRET (zufällig, >= 40 Zeichen) // - ANON_KEY (HS256-JWT, role=anon, 10 Jahre gültig) // - SERVICE_ROLE_KEY (HS256-JWT, role=service_role, 10 Jahre gültig) // // anon/service-Key sind KEINE Passwörter, sondern JWTs, die mit dem // JWT_SECRET signiert sind — exakt wie bei Supabase Cloud. PostgREST und // Storage validieren sie lokal gegen das Secret (kein GoTrue nötig). // // Nutzung: // node gen-keys.mjs # neues Secret + passende Keys // node gen-keys.mjs # Keys zu vorhandenem Secret // // Ausgabe ist direkt in die Supabase-.env kopierbar. // Pure Node-crypto, keine Abhängigkeiten. // ===================================================================== import { createHmac, randomBytes } from "node:crypto"; function b64url(input) { return Buffer.from(input) .toString("base64") .replace(/=/g, "") .replace(/\+/g, "-") .replace(/\//g, "_"); } function signJwt(payload, secret) { const header = { alg: "HS256", typ: "JWT" }; const head = b64url(JSON.stringify(header)); const body = b64url(JSON.stringify(payload)); const data = `${head}.${body}`; const sig = createHmac("sha256", secret).update(data).digest("base64") .replace(/=/g, "") .replace(/\+/g, "-") .replace(/\//g, "_"); return `${data}.${sig}`; } const secret = process.argv[2] || randomBytes(48).toString("base64url"); if (secret.length < 32) { console.error("JWT_SECRET muss mindestens 32 Zeichen lang sein."); process.exit(1); } const iat = Math.floor(Date.now() / 1000); const exp = iat + 60 * 60 * 24 * 365 * 10; // 10 Jahre const anon = signJwt({ role: "anon", iss: "supabase", iat, exp }, secret); const service = signJwt( { role: "service_role", iss: "supabase", iat, exp }, secret, ); console.log(`JWT_SECRET=${secret}`); console.log(`ANON_KEY=${anon}`); console.log(`SERVICE_ROLE_KEY=${service}`);