Files
claudemesh/apps/broker/scripts/backfill-owner-pubkey.ts
Alejandro Gutiérrez fa23525c46 feat(broker): one-off owner_pubkey backfill script
Populates mesh.mesh.owner_pubkey for pre-18c rows by generating a
fresh ed25519 keypair per mesh + emitting the secret key to stdout
for out-of-band hand-off.

Idempotent: only patches rows WHERE owner_pubkey IS NULL. Machine-
readable output (tab-separated: mesh_id, slug, pubkey, secret_key)
so operators can pipe into a secure store.

Usage:
  DATABASE_URL=... bun apps/broker/scripts/backfill-owner-pubkey.ts > owners.tsv
  # then securely distribute secrets to mesh owners

Verified locally: nulled smoke-test mesh's owner_pubkey → ran backfill
→ fresh keypair written, secret emitted.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 23:08:07 +01:00

66 lines
2.0 KiB
TypeScript

#!/usr/bin/env bun
/**
* One-off backfill: populate `mesh.mesh.owner_pubkey` for meshes
* created before Step 18c landed.
*
* Runs idempotently: only touches rows where owner_pubkey IS NULL.
* Generates a fresh ed25519 keypair per mesh and writes the owner
* SECRET KEY to stdout (paired with mesh_id) so an operator can
* hand it back to the mesh owner out-of-band.
*
* Usage:
* DATABASE_URL=... bun apps/broker/scripts/backfill-owner-pubkey.ts
*
* Output format (per row): `<mesh_id> <mesh_slug> <owner_pubkey> <owner_secret_key>`
* Redirect stdout to a secure file — the secret keys grant admin
* invite-signing power and must be stored carefully.
*/
import sodium from "libsodium-wrappers";
import { eq, isNull } from "drizzle-orm";
import { db } from "../src/db";
import { mesh } from "@turbostarter/db/schema/mesh";
async function main(): Promise<void> {
await sodium.ready;
const missing = await db
.select({ id: mesh.id, slug: mesh.slug, name: mesh.name })
.from(mesh)
.where(isNull(mesh.ownerPubkey));
if (missing.length === 0) {
console.error("[backfill] no rows to patch");
return;
}
console.error(`[backfill] patching ${missing.length} mesh(es)`);
for (const row of missing) {
const kp = sodium.crypto_sign_keypair();
const pubHex = sodium.to_hex(kp.publicKey);
const secHex = sodium.to_hex(kp.privateKey);
await db
.update(mesh)
.set({ ownerPubkey: pubHex })
.where(eq(mesh.id, row.id));
// stdout: machine-readable, one mesh per line
console.log(`${row.id}\t${row.slug}\t${pubHex}\t${secHex}`);
console.error(
`[backfill] patched mesh "${row.slug}" (${row.id}) — save its secret key`,
);
}
console.error(
"[backfill] done. SECURELY HAND OFF secret keys to mesh owners.",
);
}
main()
.then(() => process.exit(0))
.catch((e) => {
console.error(
"[backfill] error:",
e instanceof Error ? e.message : String(e),
);
process.exit(1);
});