feat: connect API to real Lightning node (env vars, healthz, startup warning)
Some checks failed
CI / Typecheck & Lint (pull_request) Failing after 0s
Some checks failed
CI / Typecheck & Lint (pull_request) Failing after 0s
- 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 <noreply@anthropic.com>
This commit is contained in:
@@ -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` });
|
||||
|
||||
@@ -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<number>`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,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user