From 247e796af3e33bf11981479c6daeec91e2c2057d Mon Sep 17 00:00:00 2001 From: Alexander Whitestone Date: Sun, 22 Mar 2026 21:50:11 -0400 Subject: [PATCH] feat: add Timmy Nostr keygen script + operator setup docs - Add scripts/generate-timmy-nsec.sh wrapper and TypeScript keygen script that outputs nsec/npub and a ready-to-paste export line - Add OPERATOR.md with persistent identity setup instructions - Document TIMMY_NOSTR_NSEC in replit.md secrets table - Startup log already uses INFO (persisted) vs WARN (ephemeral) Fixes #48 Co-Authored-By: Claude Opus 4.6 (1M context) --- OPERATOR.md | 45 ++++++++++++++++++++++++++++++ replit.md | 1 + scripts/generate-timmy-nsec.sh | 18 ++++++++++++ scripts/package.json | 4 +++ scripts/src/generate-timmy-nsec.ts | 28 +++++++++++++++++++ 5 files changed, 96 insertions(+) create mode 100644 OPERATOR.md create mode 100755 scripts/generate-timmy-nsec.sh create mode 100644 scripts/src/generate-timmy-nsec.ts diff --git a/OPERATOR.md b/OPERATOR.md new file mode 100644 index 0000000..92d3f29 --- /dev/null +++ b/OPERATOR.md @@ -0,0 +1,45 @@ +# Operator Setup Guide + +## Timmy's Nostr Identity + +Timmy uses a secp256k1 Nostr keypair for his on-chain identity. Without a +persisted key, Timmy generates a new ephemeral identity on every restart — +losing continuity with anyone who knew his previous `npub`. + +### One-time setup + +1. **Generate a keypair:** + + ```bash + bash scripts/generate-timmy-nsec.sh + ``` + + This prints the `nsec1...` (private key), `npub1...` (public key), and an + `export` line you can copy-paste. + +2. **Set the environment variable** in your deployment environment: + + - **Replit:** Add `TIMMY_NOSTR_NSEC` in the Secrets tab (padlock icon). + - **VPS (systemd):** Add to `/opt/timmy-tower/.env`: + ``` + TIMMY_NOSTR_NSEC=nsec1... + ``` + - **Local dev:** Export in your shell or add to a `.env` file: + ```bash + export TIMMY_NOSTR_NSEC="nsec1..." + ``` + +3. **Restart the API server.** On startup you should see an `INFO` log: + + ``` + timmy-identity INFO timmy identity loaded from env { npub: "npub1..." } + ``` + + If you see a `WARN` instead, the env var is missing or malformed. + +### Security + +- The `nsec` is a private key — treat it like a password. +- Never commit it to version control. +- Never log it or expose it in API responses. +- If compromised, generate a new keypair and update all references to the old `npub`. diff --git a/replit.md b/replit.md index 0740f24..e25f168 100644 --- a/replit.md +++ b/replit.md @@ -67,6 +67,7 @@ Every package extends `tsconfig.base.json` which sets `composite: true`. The roo |---|---|---| | `LNBITS_URL` | Base URL of your LNbits instance | `https://legend.lnbits.com` | | `LNBITS_API_KEY` | Invoice/Admin API key from your LNbits wallet | `a3f...` | +| `TIMMY_NOSTR_NSEC` | Timmy's persistent Nostr private key (run `bash scripts/generate-timmy-nsec.sh` to generate) | `nsec1...` | > **Note:** If `LNBITS_URL` and `LNBITS_API_KEY` are absent, `LNbitsService` automatically runs in **stub mode** — invoices are simulated in-memory and can be marked paid via `svc.stubMarkPaid(hash)`. This is intentional for development without a Lightning node. diff --git a/scripts/generate-timmy-nsec.sh b/scripts/generate-timmy-nsec.sh new file mode 100755 index 0000000..ffbed23 --- /dev/null +++ b/scripts/generate-timmy-nsec.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +# ============================================================================= +# Generate a fresh Nostr keypair for Timmy's persistent identity. +# +# Usage: +# bash scripts/generate-timmy-nsec.sh +# +# Requires: pnpm, Node.js, nostr-tools (installed via pnpm install) +# +# Output: nsec1... private key, npub1... public key, and an export line +# to copy-paste into your deployment environment. +# ============================================================================= +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" +cd "$REPO_ROOT" + +exec pnpm --filter @workspace/scripts run generate-timmy-nsec diff --git a/scripts/package.json b/scripts/package.json index 00acd21..b668ade 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -7,8 +7,12 @@ "hello": "tsx ./src/hello.ts", "timmy-watch": "tsx ./src/timmy-watch.ts", "timmy-report": "tsx ./src/timmy-report.ts", + "generate-timmy-nsec": "tsx ./src/generate-timmy-nsec.ts", "typecheck": "tsc -p tsconfig.json --noEmit" }, + "dependencies": { + "nostr-tools": "^2.23.3" + }, "devDependencies": { "@types/node": "catalog:", "tsx": "catalog:" diff --git a/scripts/src/generate-timmy-nsec.ts b/scripts/src/generate-timmy-nsec.ts new file mode 100644 index 0000000..652d3f8 --- /dev/null +++ b/scripts/src/generate-timmy-nsec.ts @@ -0,0 +1,28 @@ +/** + * generate-timmy-nsec.ts — Generate a fresh Nostr keypair for Timmy. + * + * Outputs the nsec (private) and npub (public) keys, plus a ready-to-paste + * export line for setting the TIMMY_NOSTR_NSEC environment variable. + * + * Usage: + * pnpm --filter @workspace/scripts run generate-timmy-nsec + * # or via the wrapper: + * bash scripts/generate-timmy-nsec.sh + */ +import { generateSecretKey, getPublicKey, nip19 } from "nostr-tools"; + +const sk = generateSecretKey(); +const pubkeyHex = getPublicKey(sk); +const nsec = nip19.nsecEncode(sk); +const npub = nip19.npubEncode(pubkeyHex); + +console.log("=== Timmy Nostr Identity ==="); +console.log(""); +console.log(` npub: ${npub}`); +console.log(` nsec: ${nsec}`); +console.log(""); +console.log("Add this to your deployment environment:"); +console.log(""); +console.log(` export TIMMY_NOSTR_NSEC="${nsec}"`); +console.log(""); +console.log("⚠ Keep the nsec secret — anyone with it can impersonate Timmy."); -- 2.43.0