Officer is single-user: the server owner is the only account, created once by /auth/bootstrap. Everything that existed to serve additional users was unreachable, so it is gone rather than left looking like it does something. Accounts: drop the invite / resend-invite / delete / list-users routes and the Users settings screen, the inert /auth/signup handler, and the account verification chain it fed (verify, resend-verification, VerifyScreen, the UserInvite + VerifyAdmin + VerifyRegistration templates). /auth/verify-token survives for password resets only, and now requires a reset-password token rather than accepting any signed JWT. Roles: drop the users.role column and the four-value USER_ROLES enum. The permissions table granted every role identical methods, and every role === 'Super Admin' check was permanently true. The JWT no longer carries a role claim. Sandbox: remove sidecar/sandbox.ts and its five call sites. bwrap was selected only for non-Super-Admin users, so it never ran. It was also not a usable agent jail as written — --share-net, the project root (with .env) bound read-only, and runuser dropping to the server's own uid. Rebuilding it for agent containment would be a different construction, and git history keeps this one. getHomeDir keeps its DATA_PATH meaning; the new getOwnerHomeDir resolves the owner's real login home, which is what terminals, chats and task runs use. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
66 lines
3.1 KiB
TypeScript
66 lines
3.1 KiB
TypeScript
import { join, resolve } from 'node:path';
|
|
import { mkdirSync } from 'node:fs';
|
|
import { homedir } from 'node:os';
|
|
|
|
export const DATA_PATH = process.env.DATA_PATH ?? join(process.cwd(), 'data');
|
|
|
|
// Unified, file-based store for all agent items, living outside the repo. Every skill/tool/task/
|
|
// process/extension is a directory under one of these type subfolders — no scope tiers, no DB.
|
|
export const OFFICER_ITEMS_DIR = process.env.OFFICER_ITEMS_DIR ?? join(process.cwd(), 'officer-items');
|
|
|
|
export type ItemType = 'skills' | 'tools' | 'tasks' | 'processes' | 'extensions';
|
|
|
|
export const ITEM_TYPES: ItemType[] = ['skills', 'tools', 'tasks', 'processes', 'extensions'];
|
|
|
|
export const itemsDir = (type: ItemType) => join(OFFICER_ITEMS_DIR, type);
|
|
|
|
export const ensureItemDirs = () => {
|
|
for (const type of ITEM_TYPES) mkdirSync(itemsDir(type), { recursive: true });
|
|
};
|
|
|
|
export const SERVER_CONFIG_DIR = join(DATA_PATH, 'server-settings');
|
|
|
|
// The `pi`/opencode agent's own config dir (auth.json + models.json). External-tool path.
|
|
export const AGENT_CONFIG_DIR = join(homedir(), '.pi', 'agent');
|
|
|
|
export const SEED_PATH = resolve(import.meta.dir, '../../seed');
|
|
|
|
// The managed home under DATA_PATH — what provisioning seeds and what the generated Claude config
|
|
// points at. Distinct from the owner's real login home below.
|
|
export const getHomeDir = (email: string) => join(DATA_PATH, email, 'home');
|
|
|
|
// Where the owner's sessions actually run: their real login home when HOME_DIR is set, so platform
|
|
// terminals/chats/tasks share config and credentials with the shell they use outside Officer.
|
|
export const getOwnerHomeDir = (email: string): string => process.env.HOME_DIR ?? getHomeDir(email);
|
|
|
|
export const getUserPiConfigDir = (email: string) => join(DATA_PATH, email, 'home', '.pi', 'agent');
|
|
|
|
export const getUserProjectsDir = (email: string) => join(DATA_PATH, email, 'home', 'Projects');
|
|
|
|
export const getTmpAttachmentsDir = (email: string) => join(DATA_PATH, email, 'attachments', 'tmp');
|
|
|
|
export const getAttachmentsDir = (email: string, sessionId: string) => join(DATA_PATH, email, 'attachments', sessionId);
|
|
|
|
// Per-account email storage: DATA_PATH/<owner>/email_accounts/<accountEmail>/emails.db, with a
|
|
// single attachment_cache/ shared across the owner's accounts.
|
|
export const getEmailAccountsDir = (ownerEmail: string) => join(DATA_PATH, ownerEmail, 'email_accounts');
|
|
export const getEmailDbPath = (ownerEmail: string, accountEmail: string) =>
|
|
join(getEmailAccountsDir(ownerEmail), accountEmail, 'emails.db');
|
|
export const getEmailAttachmentCacheDir = (ownerEmail: string) =>
|
|
join(getEmailAccountsDir(ownerEmail), 'attachment_cache');
|
|
|
|
/** Derive a valid Linux username from a display username or email. */
|
|
export const toShellUsername = (username: string, email: string): string => {
|
|
const raw = username || email.split('@')[0]!;
|
|
// Replace invalid chars, lowercase, truncate to 32 chars
|
|
return (
|
|
raw
|
|
.replace(/@.*$/, '')
|
|
.replace(/[^a-zA-Z0-9._-]/g, '_')
|
|
.toLowerCase()
|
|
.slice(0, 32) || 'officer'
|
|
);
|
|
};
|
|
|
|
export const getUserAppsDir = (email: string) => join(DATA_PATH, email, 'apps');
|