#!/usr/bin/env bun /** * `bun run doctor` — check the invariants that provisioning cannot check for itself. * * ── Why ── * * Every step in `provisionOsAccount` is deliberately non-fatal, so "nothing reported an error" is much * weaker than it sounds. The 2026-08-19 case: a fresh server, an Admin granted files, chat and terminal, * every named ACL entry correct, provisioning clean — and the account reached nothing, because a later * `chmod 700` had recomputed the ACL mask to `---`. It was found by a person, three days and one * confusing UI message later. * * This is the same posture as `assertPermissionTotality`: state the invariant, verify it, name the fix. * The difference is that this reports rather than refusing to boot — a broken member must not stop the * platform serving everyone else. * * Read-only. It changes nothing, so it is safe on a production box. * * Exit 1 if anything is wrong, so it can gate the end of a setup run. */ import { getUsers, getAllRoleGrants, OWNER_USER_ID } from 'officerdb'; import { checkOsAccount } from '../src/servers/os-user-verify'; import { PERMISSIONS } from '../src/servers/permissions/registry'; const red = (s: string) => `\x1b[31m${s}\x1b[0m`; const green = (s: string) => `\x1b[32m${s}\x1b[0m`; const dim = (s: string) => `\x1b[2m${s}\x1b[0m`; let problems = 0; const fail = (what: string, fix: string) => { problems++; console.log(`${red('✗')} ${what}`); console.log(` ${dim(fix)}`); }; const pass = (what: string) => console.log(`${green('✓')} ${what}`); console.log('\n── Accounts ──\n'); const users = await getUsers(); for (const user of users) { if (user.id === OWNER_USER_ID) { // The owner runs AS the service user, in the service user's own home. There is no Linux account to // check and no ACL to get wrong — `osUser: null` is correct here, not a finding. pass(`${user.email} ${dim('(owner — runs as the service account)')}`); continue; } const check = await checkOsAccount(user.email, user.osUser ?? null); if (check.ok) { pass(`${user.email} ${dim(`(${user.role}, os_user=${check.osUser}, home readable)`)}`); } else { fail(`${user.email} (${user.role}) — ${check.problem}`, `home: ${check.home}`); } } console.log('\n── Grants that cannot take effect ──\n'); // A `confined` grant is silently dropped for an account with no Linux user. That is correct behaviour and // completely invisible: the grant is in the table, the UI shows it ticked, and it resolves to nothing. const confinedKeys = new Set(PERMISSIONS.filter((p) => p.kind === 'confined').map((p) => p.key)); const grants = await getAllRoleGrants(); const unconfined = users.filter((u) => u.id !== OWNER_USER_ID && !u.osUser); if (!unconfined.length) { pass('every non-owner account has a Linux user'); } else { for (const user of unconfined) { const dropped = grants .filter((g) => g.role === user.role && confinedKeys.has(g.permission)) .map((g) => g.permission); if (!dropped.length) continue; fail( `${user.email} (${user.role}) holds ${dropped.join(', ')} — all dropped, because the account has no Linux user`, 'Fix: Settings → User management → provision, or POST /api/users//provision-linux', ); } } console.log(''); if (problems === 0) { console.log(green('No problems found.\n')); process.exit(0); } console.log(red(`${problems} problem${problems === 1 ? '' : 's'} found.\n`)); process.exit(1);