CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history
emergency-pat.ts
Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.
| 0dc3291 | 1 | // Emergency PAT creator — bypasses the live site entirely. |
| 2 | // | |
| 3 | // Use case: the deployed site is broken (e.g. SW reload loop) such that | |
| 4 | // you can't reach /settings/tokens in the browser, but you need a PAT | |
| 5 | // to `git push` the fix. This script writes a PAT row directly into | |
| 6 | // the production database via DATABASE_URL. | |
| 7 | // | |
| 8 | // Run: bun run scripts/emergency-pat.ts | |
| 9 | // Required env: DATABASE_URL (from your local .env) | |
| 10 | // | |
| 11 | // The script: | |
| 12 | // 1. Loads .env | |
| 13 | // 2. Finds the first admin user (or a user matching $EMERGENCY_PAT_USER) | |
| 14 | // 3. Inserts a fresh PAT with `admin` scope | |
| 15 | // 4. Prints the raw token — you only see it once, so copy it now | |
| 16 | ||
| 17 | import { db } from "../src/db/index"; | |
| 18 | import { users, apiTokens } from "../src/db/schema"; | |
| 19 | import { eq } from "drizzle-orm"; | |
| 20 | ||
| 21 | function generateToken(): string { | |
| 22 | const bytes = crypto.getRandomValues(new Uint8Array(32)); | |
| 23 | return ( | |
| 24 | "glc_" + | |
| 25 | Array.from(bytes) | |
| 26 | .map((b) => b.toString(16).padStart(2, "0")) | |
| 27 | .join("") | |
| 28 | ); | |
| 29 | } | |
| 30 | ||
| 31 | async function hashToken(token: string): Promise<string> { | |
| 32 | const data = new TextEncoder().encode(token); | |
| 33 | const hash = await crypto.subtle.digest("SHA-256", data); | |
| 34 | return Array.from(new Uint8Array(hash)) | |
| 35 | .map((b) => b.toString(16).padStart(2, "0")) | |
| 36 | .join(""); | |
| 37 | } | |
| 38 | ||
| 39 | async function main() { | |
| 40 | const requestedUser = process.env.EMERGENCY_PAT_USER?.trim(); | |
| 41 | let user: typeof users.$inferSelect | undefined; | |
| 42 | ||
| 43 | if (requestedUser) { | |
| 44 | [user] = await db | |
| 45 | .select() | |
| 46 | .from(users) | |
| 47 | .where(eq(users.username, requestedUser)) | |
| 48 | .limit(1); | |
| 49 | if (!user) { | |
| 50 | console.error(`No user with username "${requestedUser}".`); | |
| 51 | process.exit(1); | |
| 52 | } | |
| 53 | } else { | |
| 54 | [user] = await db | |
| 55 | .select() | |
| 56 | .from(users) | |
| 57 | .where(eq(users.isAdmin, true)) | |
| 58 | .limit(1); | |
| 59 | if (!user) { | |
| 60 | console.error( | |
| 61 | "No admin user found. Set EMERGENCY_PAT_USER=<username> and rerun." | |
| 62 | ); | |
| 63 | process.exit(1); | |
| 64 | } | |
| 65 | } | |
| 66 | ||
| 67 | const token = generateToken(); | |
| 68 | const tokenHash = await hashToken(token); | |
| 69 | await db.insert(apiTokens).values({ | |
| 70 | userId: user.id, | |
| 71 | name: "emergency-deploy-fix", | |
| 72 | tokenHash, | |
| 73 | tokenPrefix: token.slice(0, 12), | |
| 74 | scopes: "admin", | |
| 75 | }); | |
| 76 | ||
| 77 | console.log(`User: ${user.username} (id ${user.id})`); | |
| 78 | console.log(`Token: ${token}`); | |
| 79 | console.log(""); | |
| 80 | console.log("Copy the token above NOW — it is hashed in the DB and"); | |
| 81 | console.log("cannot be recovered later. Then push with:"); | |
| 82 | console.log(""); | |
| 83 | console.log(` git remote set-url gluecron "https://${user.username}:${token}@gluecron.com/ccantynz/Gluecron.com.git"`); | |
| 84 | console.log(" git push gluecron main"); | |
| 85 | process.exit(0); | |
| 86 | } | |
| 87 | ||
| 88 | main().catch((err) => { | |
| 89 | console.error("Failed:", err); | |
| 90 | process.exit(1); | |
| 91 | }); |