interface SuperuserAuthResponse { token: string; } interface UserRecord { id: string; email: string; is_admin?: boolean; subscription?: string; } function requireEnv(name: string): string { const value = process.env[name]; if (!value) throw new Error(`${name} is required`); return value; } async function pbJson( url: string, init: RequestInit, label: string ): Promise { const res = await fetch(url, init); if (!res.ok) { throw new Error(`${label} ${res.status}: ${await res.text()}`); } return (await res.json()) as T; } async function superuserToken(pbUrl: string, email: string, password: string): Promise { const data = await pbJson( `${pbUrl}/api/collections/_superusers/auth-with-password`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ identity: email, password }), }, 'PocketBase superuser auth' ); return data.token; } async function findUser(pbUrl: string, token: string, email: string): Promise { const filter = `email="${email.replaceAll('"', '\\"')}"`; const data = await pbJson<{ items: UserRecord[] }>( `${pbUrl}/api/collections/users/records?filter=${encodeURIComponent(filter)}&perPage=1`, { headers: { Authorization: `Bearer ${token}` } }, 'PocketBase user lookup' ); return data.items[0] ?? null; } function recorderUserBody(email: string, password: string): Record { return { email, emailVisibility: true, verified: true, password, passwordConfirm: password, is_admin: true, subscription: 'licensed', }; } export async function ensureRecorderAdminUser(): Promise { const pbUrl = requireEnv('PB_URL').replace(/\/$/, ''); const email = requireEnv('PB_EMAIL'); const password = requireEnv('PB_PASSWORD'); const adminEmail = process.env.PB_ADMIN_EMAIL ?? process.env.POCKETBASE_ADMIN_EMAIL; const adminPassword = process.env.PB_ADMIN_PASSWORD ?? process.env.POCKETBASE_ADMIN_PASSWORD; if (!adminEmail || !adminPassword) { throw new Error('PB_ADMIN_EMAIL/PB_ADMIN_PASSWORD are required to bootstrap the recorder user'); } const token = await superuserToken(pbUrl, adminEmail, adminPassword); const existing = await findUser(pbUrl, token, email); const body = recorderUserBody(email, password); if (existing) { await pbJson( `${pbUrl}/api/collections/users/records/${existing.id}`, { method: 'PATCH', headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', }, body: JSON.stringify(body), }, 'PocketBase recorder user update' ); console.log(`Updated recorder admin user ${email}.`); return; } await pbJson( `${pbUrl}/api/collections/users/records`, { method: 'POST', headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', }, body: JSON.stringify(body), }, 'PocketBase recorder user create' ); console.log(`Created recorder admin user ${email}.`); } async function main() { await ensureRecorderAdminUser(); } if (process.argv[1]?.endsWith('pb-admin.js')) { main().catch((err) => { console.error(err); process.exit(1); }); }