The three non-owner accounts had no directory of their own — only the owner did,
grown organically as features created what they needed. Adds an idempotent script
that creates the skeleton, and runs it for those three.
DATA_PATH/<email>/{home,attachments,cache,dashboards,email_accounts,
general_chat_sessions,logs,sidecar}
Most of those are also created on demand by whichever feature owns them, so
pre-creating buys legibility rather than function: the tree now shows the shape a
user has without having to use it first. `home` is the exception and the reason
this exists — nothing creates it today, because getOwnerHomeDir returns
process.env.HOME_DIR whenever it is set, which it always is on a real install. It
is where a non-owner's sessions will run.
Takes emails as arguments rather than reading the user table. The layout does not
depend on the database, keeping the DB out means it runs with nothing else up,
and at invite time the caller already knows the email. It refuses arguments that
are not email addresses, since a stray one would create a junk directory sitting
next to real user roots and looking like one.
Deliberately NOT a revival of the provision.ts deleted two commits ago. That one
was ~90% per-container scaffolding — shell rc files, a generated CLAUDE.md and
settings.json describing the container — wrapped around the two useful lines this
keeps.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
74 lines
2.8 KiB
TypeScript
74 lines
2.8 KiB
TypeScript
// Create the per-user root under DATA_PATH for the given accounts.
|
|
//
|
|
// bun scripts/provision-user-dirs.ts a@b.com c@d.com
|
|
// DRY_RUN=1 bun scripts/provision-user-dirs.ts a@b.com
|
|
//
|
|
// Takes emails as arguments rather than reading the user table: the directory layout does not depend
|
|
// on the database, and keeping the DB out means this runs with nothing else up. At invite time the
|
|
// caller already knows the email.
|
|
//
|
|
// This is the directory skeleton ONLY. It is deliberately not a revival of the old provision.ts, which
|
|
// also seeded shell rc files and wrote a generated CLAUDE.md + settings.json describing the user's
|
|
// Docker container. That architecture is gone. What survives from it is the useful part: a user has a
|
|
// root, and a home inside it.
|
|
//
|
|
// Idempotent — an existing directory is left exactly as it is, so re-running is safe.
|
|
|
|
import { mkdirSync, existsSync } from 'node:fs';
|
|
import { join } from 'node:path';
|
|
|
|
const DATA_PATH = process.env.DATA_PATH ?? join(process.cwd(), 'data');
|
|
|
|
// Mirrors the shape the owner's root grew organically. Most of these are also created on demand by
|
|
// whichever feature owns them (attachments, dashboards, email_accounts…), so pre-creating them buys
|
|
// legibility more than function — the tree shows what a user has without having to use it first.
|
|
//
|
|
// `home` is the exception, and the reason this exists at all: nothing creates it today. getOwnerHomeDir
|
|
// returns process.env.HOME_DIR whenever it is set, which it always is on a real install, so the
|
|
// per-user home has never actually been reached. It is where a non-owner's sessions will run.
|
|
const USER_DIRS = [
|
|
'home',
|
|
'attachments',
|
|
'cache',
|
|
'dashboards',
|
|
'email_accounts',
|
|
'general_chat_sessions',
|
|
'logs',
|
|
'sidecar',
|
|
] as const;
|
|
|
|
const DRY_RUN = process.env.DRY_RUN === '1';
|
|
const emails = process.argv.slice(2).filter(Boolean);
|
|
|
|
if (!emails.length) {
|
|
console.error('Usage: bun scripts/provision-user-dirs.ts <email> [email...]');
|
|
process.exit(2);
|
|
}
|
|
|
|
// A stray argument would create a junk directory next to real user roots, and it would look like a
|
|
// user. Cheap to refuse.
|
|
const invalid = emails.filter((e) => !/^[^\s@/]+@[^\s@/]+\.[^\s@/]+$/.test(e));
|
|
if (invalid.length) {
|
|
console.error(`Not valid email addresses: ${invalid.join(', ')}`);
|
|
process.exit(2);
|
|
}
|
|
|
|
console.log(`DATA_PATH = ${DATA_PATH}`);
|
|
console.log(`${DRY_RUN ? 'Would provision' : 'Provisioning'} ${emails.length} user root(s)\n`);
|
|
|
|
for (const email of emails) {
|
|
const root = join(DATA_PATH, email);
|
|
console.log(`${email}${existsSync(root) ? '' : ' [new root]'}`);
|
|
|
|
for (const dir of USER_DIRS) {
|
|
const path = join(root, dir);
|
|
if (existsSync(path)) {
|
|
console.log(` · ${dir} (exists)`);
|
|
continue;
|
|
}
|
|
if (!DRY_RUN) mkdirSync(path, { recursive: true });
|
|
console.log(` ${DRY_RUN ? '+' : '✓'} ${dir}`);
|
|
}
|
|
console.log('');
|
|
}
|