Changeset 0f09302 in Klonkt
- Timestamp:
- 08/21/2026 08:00:05 AM (3 weeks ago)
- Branches:
- main
- Children:
- bb9e54c
- Parents:
- 1294c36
- Files:
-
- 1 added
- 2 edited
-
deploy/MULTI-INSTANCE.md (modified) (1 diff)
-
scripts/reset-admin.mjs (modified) (2 diffs)
-
test/reset-admin.test.js (added)
Legend:
- Unmodified
- Added
- Removed
-
deploy/MULTI-INSTANCE.md
r1294c36 r0f09302 210 210 | `klonkt-update` | update the code, restart all instances | 211 211 | `ls /var/lib/klonkt` | which instances exist | 212 | `cd /opt/klonkt && npm run reset-admin -- --instance <slug>` | reset a forgotten admin password | 213 214 ### Why the password reset needs `--instance` 215 216 Every other command here names its instance, and this one is no different — but 217 the reason is easy to miss, because the app's own help page says only "run 218 `npm run reset-admin` from the project folder". That advice is written for the 219 one-instance layout, where the `.env` sits next to the code. 220 221 Here it does not. The code is shared and read-only at `/opt/klonkt`; the 222 configuration lives at `/var/lib/klonkt/<slug>/.env`, and **systemd** reads it 223 through `EnvironmentFile=`. A script started by hand gets none of that: it looks 224 for an `.env` beside the code, finds nothing, and is left without 225 `DATABASE_PATH`. `--instance` reads the same file systemd does. 226 227 Without it the script now stops and says which path it wanted, instead of 228 creating an empty database inside the shared code directory. If your data lives 229 somewhere other than `/var/lib/klonkt`, set `KLONKT_DATA_ROOT`. -
scripts/reset-admin.mjs
r1294c36 r0f09302 4 4 // 5 5 // Usage: 6 // npm run reset-admin # reset the (first) god user, print new password 7 // npm run reset-admin -- <user|email> # reset a specific user, print new password 8 // npm run reset-admin -- <user|email> <pw> # set a chosen password 6 // npm run reset-admin # reset the (first) god user, print new password 7 // npm run reset-admin -- <user|email> # reset a specific user, print new password 8 // npm run reset-admin -- <user|email> <pw> # set a chosen password 9 // npm run reset-admin -- --instance <slug> [...] # pick an instance (shared code, /var/lib/klonkt) 9 10 // 10 // Run from the project root so DATABASE_PATH/.env is loaded correctly. 11 // Run from the project root so DATABASE_PATH/.env is loaded correctly, or name 12 // the instance with --instance. 11 13 12 import 'dotenv/config'; 14 import fs from 'fs'; 15 import path from 'path'; 13 16 import crypto from 'crypto'; 17 import { fileURLToPath } from 'url'; 18 import dotenv from 'dotenv'; 14 19 import bcrypt from 'bcryptjs'; 15 import db from '../src/config/database.js';16 20 17 const arg = process.argv[2]; 18 const pwArg = process.argv[3]; 21 const __dirname = path.dirname(fileURLToPath(import.meta.url)); 22 const wortel = path.join(__dirname, '..'); 23 24 // De argumenten: --instance mag overal staan, de rest is stelling-afhankelijk. 25 const argv = process.argv.slice(2); 26 let slug = null; 27 const rest = []; 28 for (let i = 0; i < argv.length; i++) { 29 const a = argv[i]; 30 if (a === '--instance' || a === '-i') { slug = argv[++i] || null; continue; } 31 if (a.startsWith('--instance=')) { slug = a.slice('--instance='.length); continue; } 32 rest.push(a); 33 } 34 const [arg, pwArg] = rest; 35 36 if (slug !== null && !slug) { 37 console.error('--instance verwacht een slug, bijvoorbeeld: --instance boiert'); 38 process.exit(1); 39 } 40 41 // WAAR DE CONFIGURATIE VANDAAN KOMT, en waarom dit meer dan één regel is. 42 // 43 // Bij één-instantie-per-checkout staat de .env naast de code en vindt dotenv 44 // hem vanzelf. In de gedeelde opstelling van deploy/klonkt@.service niet: daar 45 // is de code gedeeld op /opt/klonkt en staat de configuratie per instantie in 46 // /var/lib/klonkt/<slug>/.env, waar SYSTEMD hem leest via EnvironmentFile. 47 // Dotenv kijkt in de werkmap en vindt daar niets, dus zonder --instance draait 48 // dit script met een lege DATABASE_PATH -- en dan wijst hij de verkeerde kant 49 // op. Vandaar deze schakelaar. 50 const DATA_ROOT = process.env.KLONKT_DATA_ROOT || '/var/lib/klonkt'; 51 if (slug) { 52 const envPad = path.join(DATA_ROOT, slug, '.env'); 53 if (!fs.existsSync(envPad)) { 54 console.error(`Geen .env voor instantie "${slug}" op ${envPad}.`); 55 console.error(`Bestaande instanties: ${bestaandeInstanties().join(', ') || '(geen gevonden)'}`); 56 console.error('Staat je data ergens anders? Zet KLONKT_DATA_ROOT.'); 57 process.exit(1); 58 } 59 // override: dotenv laat een bestaande variabele normaal met rust, maar wie 60 // --instance typt heeft die instantie AANGEWEZEN. Een DATABASE_PATH die nog 61 // in de shell hangt van een vorige instantie zou anders stilletjes winnen, 62 // en dan reset je het wachtwoord van de verkeerde site. 63 dotenv.config({ path: envPad, override: true }); 64 } else { 65 dotenv.config(); 66 } 67 68 function bestaandeInstanties() { 69 try { 70 return fs.readdirSync(DATA_ROOT).filter((d) => fs.existsSync(path.join(DATA_ROOT, d, '.env'))); 71 } catch { return []; } 72 } 73 74 // EERST KIJKEN, DAN PAS IMPORTEREN. src/config/database.js maakt zijn map en 75 // zijn bestand aan zodra hij geladen wordt -- prima bij de eerste start van een 76 // server, funest hier: sta je in de verkeerde map, dan legt hij een lege 77 // database neer en klapt daarna op "no such table: users". Dat is een 78 // stacktrace op de plek waar een aanwijzing hoort te staan. Dus dezelfde 79 // padkeuze als database.js, maar dan alleen berekend. 80 const dbPad = process.env.DATABASE_PATH || path.join(wortel, 'storage/database.sqlite'); 81 if (!fs.existsSync(dbPad)) { 82 console.error(`Geen database op ${dbPad}.`); 83 console.error(''); 84 console.error('Dit script maakt er met opzet geen aan: een ontbrekende database betekent'); 85 console.error('bijna altijd dat DATABASE_PATH niet geladen is, niet dat de site leeg is.'); 86 console.error(''); 87 if (!slug) { 88 const gevonden = bestaandeInstanties(); 89 if (gevonden.length) { 90 console.error(`Gedeelde opstelling? Noem de instantie: npm run reset-admin -- --instance <slug>`); 91 console.error(`Gevonden in ${DATA_ROOT}: ${gevonden.join(', ')}`); 92 } else { 93 console.error('Draai dit vanuit de projectmap, zodat de .env ernaast geladen wordt,'); 94 console.error('of geef het pad mee: DATABASE_PATH=/pad/naar/database.sqlite npm run reset-admin'); 95 } 96 } 97 process.exit(1); 98 } 99 100 const { default: db } = await import('../src/config/database.js'); 19 101 20 102 let user; 21 if (arg) { 22 user = db.prepare('SELECT * FROM users WHERE username = ? OR LOWER(email) = LOWER(?)').get(arg, arg); 23 } else { 24 // No arg: pick the admin (god or admin role), otherwise the very first user. 25 user = 26 db.prepare("SELECT * FROM users WHERE role IN ('god','admin') ORDER BY created_at LIMIT 1").get() || 27 db.prepare('SELECT * FROM users ORDER BY created_at LIMIT 1').get(); 103 try { 104 if (arg) { 105 user = db.prepare('SELECT * FROM users WHERE username = ? OR LOWER(email) = LOWER(?)').get(arg, arg); 106 } else { 107 // No arg: pick the admin (god or admin role), otherwise the very first user. 108 user = 109 db.prepare("SELECT * FROM users WHERE role IN ('god','admin') ORDER BY created_at LIMIT 1").get() || 110 db.prepare('SELECT * FROM users ORDER BY created_at LIMIT 1').get(); 111 } 112 } catch (e) { 113 // Een database zonder users-tabel is geen lege site maar het verkeerde 114 // bestand -- of een checkout die nog nooit gestart is. 115 if (/no such table/i.test(e.message)) { 116 console.error(`De database op ${dbPad} heeft geen users-tabel.`); 117 console.error('Dat is een ander bestand dan je site, of een site die nooit gestart is.'); 118 process.exit(1); 119 } 120 throw e; 28 121 } 29 122 30 123 if (!user) { 31 124 console.error(arg ? `Geen user gevonden voor "${arg}".` : 'Geen god-user gevonden.'); 125 console.error(`Gezocht in ${dbPad}.`); 32 126 process.exit(1); 33 127 } … … 45 139 46 140 console.log(`Wachtwoord gereset voor ${user.username} <${user.email}> (rol: ${user.role}).`); 141 console.log(`Database: ${dbPad}`); 47 142 if (!pwArg) console.log(`Nieuw wachtwoord: ${newPw}`); 48 143 console.log('Log nu in via /auth/login en wijzig het eventueel in je account.');
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)