CodeIssuesPull RequestsActionsSecurityInsights
✨ AI
More
Settings

fix(emergency-pat): dual-driver like src/db — works on self-hosted Postgres #5518

Merged⚡ AI-generatedXSccantynz wants to mergefix/emergency-pat-selfhostmainopened 10d ago
1 changed file+35−8
Modifiedscripts/emergency-pat.ts+35−8View fileUnifiedSplit
88// Run: bun run scripts/emergency-pat.ts
99// Required env: DATABASE_URL (from your local .env, or set inline)
1010//
11// Uses RAW SQL via @neondatabase/serverless rather than drizzle's
12// schema layer. Reason: when this script is needed, production is
13// usually missing recent migrations, so drizzle's SELECT (which lists
14// every schema column) blows up on missing columns. Raw SQL only
15// touches the columns we name, so schema drift doesn't matter.
11// Uses RAW SQL rather than drizzle's schema layer. Reason: when this
12// script is needed, production is usually missing recent migrations, so
13// drizzle's SELECT (which lists every schema column) blows up on missing
14// columns. Raw SQL only touches the columns we name, so schema drift
15// doesn't matter.
16//
17// Dual-driver like src/db (2026-08-22): this script hardcoded the Neon
18// HTTP driver, which on a self-hosted localhost Postgres tries
19// `https://localhost/sql` and dies with an inscrutable ConnectionRefused
20// — discovered the first time an operator minted on the Vapron box's
21// instance. Same isNeonUrl rule as the app: *.neon.tech → Neon HTTP,
22// everything else → postgres.js over TCP. Both drivers are tagged
23// templates returning row arrays, so the queries below are unchanged.
1624
1725import { neon } from "@neondatabase/serverless";
26import postgres from "postgres";
27import { isNeonUrl } from "../src/db";
1828
1929function generateToken(): string {
2030 const bytes = crypto.getRandomValues(new Uint8Array(32));
4050 console.error("DATABASE_URL is not set.");
4151 process.exit(1);
4252 }
43 const sql = neon(url);
53 const sql = isNeonUrl(url) ? neon(url) : postgres(url, { max: 1, prepare: false });
4454
4555 const requestedUser = process.env.EMERGENCY_PAT_USER?.trim();
4656
99109 console.log(`User: ${userRow.username}`);
100110 console.log(`Token: ${token}`);
101111 console.log("");
102 console.log("Copy the token NOW (only shown once). Then run these two:");
112 // Name the instance the token belongs to — the whole wrong-instance trap
113 // this script exists to avoid.
114 const host = (() => {
115 try {
116 return new URL(process.env.APP_BASE_URL || "https://gluecron.com").host;
117 } catch {
118 return "gluecron.com";
119 }
120 })();
121 console.log(`This token is valid ONLY on the instance whose DB this box uses (${host}).`);
122 console.log("Copy the token NOW (only shown once). Example push remote:");
103123 console.log("");
104 console.log(` git remote set-url gluecron "https://${userRow.username}:${token}@gluecron.com/ccantynz/Gluecron.com.git"`);
124 console.log(` git remote set-url gluecron "https://${userRow.username}:${token}@${host}/ccantynz/Gluecron.com.git"`);
105125 console.log(" git push gluecron main");
126 // postgres.js keeps a TCP pool open; close it so the process can exit
127 // cleanly on both drivers (neon's end() is a no-op shim).
128 try {
129 await (sql as { end?: (o?: { timeout?: number }) => Promise<void> }).end?.({ timeout: 2 });
130 } catch {
131 /* closing is best-effort; exit(0) below ends the process regardless */
132 }
106133 process.exit(0);
107134}
108135
109136
c comment · e edit title · m merge · a approve · r request changes · ? shortcuts