From cd3bbda69061f7caeddc7a2c7e168a652fe32d67 Mon Sep 17 00:00:00 2001 From: Alexander Whitestone Date: Mon, 23 Mar 2026 16:44:34 -0400 Subject: [PATCH 1/2] feat: connect API to real Lightning node (env vars, healthz, startup warning) - Add .env.example documenting LNBITS_URL and LNBITS_API_KEY (and all other required env vars) so operators know exactly what to set after running infrastructure/lnd-init.sh on the droplet. - Expose lnbits_stub and lnbits_url in GET /api/healthz so operators can immediately verify whether the API is connected to a real Lightning node or running in simulated stub mode. - Emit a loud WARN log at production startup when LNBITS_URL / LNBITS_API_KEY are absent, pointing to lnd-init.sh for setup. Infrastructure scripts (setup.sh, lnd-init.sh, sweep.sh, ops.sh, docker-compose.yml) already cover the full provisioning flow; this change closes the API-side configuration gap. Fixes #12 Co-Authored-By: Claude Sonnet 4.6 --- .env.example | 59 +++++++++++++++++++++++ artifacts/api-server/src/index.ts | 11 +++++ artifacts/api-server/src/routes/health.ts | 17 ++++++- 3 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..4411fa1 --- /dev/null +++ b/.env.example @@ -0,0 +1,59 @@ +# ============================================================ +# Timmy API Server — Environment Variables +# Copy this file to .env and fill in your values. +# Never commit .env to version control. +# ============================================================ + +# ── Server ─────────────────────────────────────────────────── +PORT=8080 +NODE_ENV=development + +# ── Lightning / LNbits ─────────────────────────────────────── +# Set both variables to connect the API to a real Lightning node. +# Leave blank to run in stub mode (invoices simulated in-memory). +# +# After running infrastructure/lnd-init.sh on the droplet, it will +# print both values. Copy them here. +# +# LNBITS_URL is the public HTTPS URL exposed via Tailscale Funnel, +# e.g. https://.tail12345.ts.net +LNBITS_URL= +LNBITS_API_KEY= + +# ── Database ───────────────────────────────────────────────── +DATABASE_URL=postgresql://localhost:5432/timmy + +# ── Nostr identity ─────────────────────────────────────────── +# nsec for Timmy's Nostr keypair (signs relay events). +# Generate with: openssl rand -hex 32 +TIMMY_NOSTR_NSEC= +# Optional: override the derived pubkey explicitly (hex, not npub). +TIMMY_NOSTR_PUBKEY= + +# ── AI integrations ────────────────────────────────────────── +# Anthropic Claude — leave blank to use canned AI stub responses. +AI_INTEGRATIONS_ANTHROPIC_API_KEY= + +# Gemini (optional) +GEMINI_API_KEY= + +# ── CORS ───────────────────────────────────────────────────── +# Comma-separated list of allowed origins in production. +# Default: alexanderwhitestone.com variants. +# Leave blank to allow all origins (development default). +# CORS_ORIGINS=https://alexanderwhitestone.com,https://www.alexanderwhitestone.com + +# ── Relay policy ───────────────────────────────────────────── +# Shared secret between the API server and the strfry relay-policy sidecar. +RELAY_POLICY_SECRET= + +# ── Rate limiting / misc ───────────────────────────────────── +# Milliseconds between moderation poll cycles (default: 30000). +# MODERATION_POLL_MS=30000 + +# ── BTC price oracle ───────────────────────────────────────── +# Optional CoinGecko API key for higher rate limits. +# COINGECKO_API_KEY= + +# ── Replit (auto-set in Replit environment) ────────────────── +# REPLIT_DEV_DOMAIN= diff --git a/artifacts/api-server/src/index.ts b/artifacts/api-server/src/index.ts index 96dfc67..3e82a28 100644 --- a/artifacts/api-server/src/index.ts +++ b/artifacts/api-server/src/index.ts @@ -6,6 +6,7 @@ import { timmyIdentityService } from "./lib/timmy-identity.js"; import { startEngagementEngine } from "./lib/engagement.js"; import { relayAccountService } from "./lib/relay-accounts.js"; import { moderationService } from "./lib/moderation.js"; +import { lnbitsService } from "./lib/lnbits.js"; const rawPort = process.env["PORT"]; @@ -25,6 +26,16 @@ attachWebSocketServer(server); server.listen(port, () => { rootLogger.info("server started", { port }); rootLogger.info("timmy identity", { npub: timmyIdentityService.npub }); + + // Warn loudly in production if Lightning is not connected. + if (lnbitsService.stubMode && process.env["NODE_ENV"] === "production") { + rootLogger.warn( + "LNBITS_URL / LNBITS_API_KEY not set — running in STUB mode. " + + "Invoices are simulated in-memory. Set these env vars to connect to a real Lightning node. " + + "See infrastructure/lnd-init.sh for setup instructions.", + { lnbits_stub: true }, + ); + } const domain = process.env["REPLIT_DEV_DOMAIN"]; if (domain) { rootLogger.info("public url", { url: `https://${domain}/api/ui` }); diff --git a/artifacts/api-server/src/routes/health.ts b/artifacts/api-server/src/routes/health.ts index c6bae80..65c5bb5 100644 --- a/artifacts/api-server/src/routes/health.ts +++ b/artifacts/api-server/src/routes/health.ts @@ -2,6 +2,7 @@ import { Router, type IRouter, type Request, type Response } from "express"; import { db, jobs } from "@workspace/db"; import { sql } from "drizzle-orm"; import { makeLogger } from "../lib/logger.js"; +import { lnbitsService } from "../lib/lnbits.js"; const router: IRouter = Router(); const logger = makeLogger("health"); @@ -13,12 +14,24 @@ router.get("/healthz", async (_req: Request, res: Response) => { const rows = await db.select({ total: sql`cast(count(*) as int)` }).from(jobs); const jobsTotal = Number(rows[0]?.total ?? 0); const uptimeS = Math.floor((Date.now() - START_TIME) / 1000); - res.json({ status: "ok", uptime_s: uptimeS, jobs_total: jobsTotal }); + res.json({ + status: "ok", + uptime_s: uptimeS, + jobs_total: jobsTotal, + lnbits_stub: lnbitsService.stubMode, + lnbits_url: lnbitsService.stubMode ? null : process.env["LNBITS_URL"] ?? null, + }); } catch (err) { const message = err instanceof Error ? err.message : "Health check failed"; logger.error("healthz db query failed", { error: message }); const uptimeS = Math.floor((Date.now() - START_TIME) / 1000); - res.status(503).json({ status: "error", uptime_s: uptimeS, error: message }); + res.status(503).json({ + status: "error", + uptime_s: uptimeS, + error: message, + lnbits_stub: lnbitsService.stubMode, + lnbits_url: lnbitsService.stubMode ? null : process.env["LNBITS_URL"] ?? null, + }); } }); -- 2.43.0 From 6be35c6cd20a799ae52dc2082226b2cfc2fb6726 Mon Sep 17 00:00:00 2001 From: Alexander Whitestone Date: Mon, 23 Mar 2026 16:52:13 -0400 Subject: [PATCH 2/2] WIP: Claude Code progress on #12 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Automated salvage commit — agent session ended (exit 0). Work in progress, may need continuation. --- pnpm-lock.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1007a83..9c533ac 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -625,6 +625,10 @@ importers: version: 7.1.1 scripts: + dependencies: + nostr-tools: + specifier: ^2.23.3 + version: 2.23.3(typescript@5.9.3) devDependencies: '@types/node': specifier: 'catalog:' -- 2.43.0