remove seed directory, clean up provisioning and sync modules

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-08 00:07:50 +00:00
co-authored by Claude Opus 4.6
parent 9578110e8b
commit 56f8da8907
86 changed files with 182 additions and 12061 deletions
+4 -4
View File
@@ -6,7 +6,7 @@ import argon2 from 'argon2';
import * as errors from '@@/custom-errors';
import { validatePassword } from './validate-password';
import { validateUsername } from './validate-username';
import { provisionLinuxUser } from '../users/provision';
import { provisionUserEnvironment } from '../users/provision';
export const verifyHandler: Handler = async function (ctx) {
const { verificationCode, name, username, password, confirmPassword } = ctx.get('body');
@@ -43,9 +43,9 @@ export const verifyHandler: Handler = async function (ctx) {
const finalUser = await getUserById(userInfo.id);
if (!finalUser) throw errors.NOT_FOUND('User not found');
// Provision Linux user for terminal/Pi/Claude Code isolation
provisionLinuxUser(finalUser.email, finalUser.username ?? '').catch((err) => {
console.error('[verify] failed to provision Linux user:', err);
// Provision user environment (directories, configs, VNC)
provisionUserEnvironment(finalUser.email, finalUser.username ?? '').catch((err) => {
console.error('[verify] failed to provision user environment:', err);
});
// Issue a token so the user is logged in immediately
+7 -63
View File
@@ -4,7 +4,6 @@ import { DATA_PATH, getHomeDir, toShellUsername } from '@@/data-path';
import { generateContainerContext, generateClaudeSettings } from '@@/generate-container-context';
const TEMPLATE_DIR = join(import.meta.dir, '../terminal/templates');
const SHARED_GROUP = 'officerdev';
const run = (cmd: string[], opts?: { cwd?: string }): boolean => {
const result = Bun.spawnSync({ cmd, stdout: 'ignore', stderr: 'pipe', ...opts });
@@ -14,60 +13,23 @@ const run = (cmd: string[], opts?: { cwd?: string }): boolean => {
return result.exitCode === 0;
};
const linuxUserExists = (username: string): boolean => {
const result = Bun.spawnSync({ cmd: ['id', username], stdout: 'ignore', stderr: 'ignore' });
return result.exitCode === 0;
};
const groupExists = (group: string): boolean => {
const result = Bun.spawnSync({ cmd: ['getent', 'group', group], stdout: 'ignore', stderr: 'ignore' });
return result.exitCode === 0;
};
const copyTemplate = async (src: string, dest: string) => {
if (existsSync(dest)) return;
const content = await Bun.file(src).text();
await Bun.write(dest, content);
};
export function ensureSharedGroup(): void {
if (groupExists(SHARED_GROUP)) return;
run(['sudo', 'groupadd', SHARED_GROUP]);
const serviceUser = process.env.USER ?? '';
if (serviceUser) {
run(['sudo', 'usermod', '-aG', SHARED_GROUP, serviceUser]);
}
console.log(`[provision] created shared group ${SHARED_GROUP} and added ${serviceUser}`);
}
export async function provisionLinuxUser(email: string, username: string): Promise<boolean> {
export async function provisionUserEnvironment(email: string, username: string): Promise<boolean> {
const shellUsername = toShellUsername(username, email);
const homeDir = getHomeDir(email);
const userRoot = join(DATA_PATH, email);
console.log(`[provision] provisioning Linux user ${shellUsername} for ${email}`);
// Ensure shared group exists
ensureSharedGroup();
console.log(`[provision] provisioning environment for ${email}`);
// Ensure data directories exist
mkdirSync(userRoot, { recursive: true });
mkdirSync(homeDir, { recursive: true });
// Create Linux user if not exists
if (!linuxUserExists(shellUsername)) {
const ok = run(['sudo', 'useradd', '-d', homeDir, '-s', '/bin/zsh', '-G', SHARED_GROUP, '-M', shellUsername]);
if (!ok) {
console.error(`[provision] failed to create Linux user ${shellUsername}`);
return false;
}
console.log(`[provision] created Linux user ${shellUsername}`);
} else {
// Ensure existing user is in the shared group
run(['sudo', 'usermod', '-aG', SHARED_GROUP, shellUsername]);
console.log(`[provision] Linux user ${shellUsername} already exists`);
}
// Seed shell config files
await seedShellConfigs(homeDir);
@@ -86,13 +48,7 @@ export async function provisionLinuxUser(email: string, username: string): Promi
// Provision VNC environment
await provisionVncEnv(homeDir);
// Service user (pastilhas) owns everything — server can always read/write.
// User's personal group gives only that user terminal access. Others get nothing.
const serviceUser = process.env.USER ?? 'pastilhas';
run(['sudo', 'chown', '-R', `${serviceUser}:${shellUsername}`, userRoot]);
run(['sudo', 'chmod', '-R', '2770', userRoot]);
console.log(`[provision] provisioning complete for ${shellUsername}`);
console.log(`[provision] provisioning complete for ${email}`);
return true;
}
@@ -186,21 +142,9 @@ exec startxfce4
console.log(`[provision] VNC environment provisioned at ${vncDir}`);
}
export function deprovisionLinuxUser(email: string, username: string): boolean {
const shellUsername = toShellUsername(username, email);
console.log(`[provision] deprovisioning Linux user ${shellUsername}`);
if (!linuxUserExists(shellUsername)) {
console.log(`[provision] Linux user ${shellUsername} does not exist, skipping`);
return true;
}
const ok = run(['sudo', 'userdel', shellUsername]);
if (!ok) {
console.error(`[provision] failed to delete Linux user ${shellUsername}`);
return false;
}
console.log(`[provision] deprovisioned Linux user ${shellUsername}`);
export function deprovisionUserEnvironment(email: string, _username: string): boolean {
// User data directories are intentionally kept on disk.
// This function exists for API compatibility.
console.log(`[provision] deprovision called for ${email} (no-op, data kept on disk)`);
return true;
}
+3 -3
View File
@@ -6,7 +6,7 @@ import { sendMail } from 'emailer';
import * as errors from '@@/custom-errors';
import { originMiddleware } from '@@/_middlewares';
import { updateUserHandler } from './update-user';
import { deprovisionLinuxUser } from './provision';
import { deprovisionUserEnvironment } from './provision';
export const usersRouter = createRouter();
usersRouter.use(originMiddleware);
@@ -103,8 +103,8 @@ usersRouter.delete('/:id', async (ctx) => {
const target = await getUserById(id);
if (!target) throw errors.NOT_FOUND('User not found');
// Deprovision Linux user before deleting from database
deprovisionLinuxUser(target.email, target.username ?? '');
// Deprovision user environment before deleting from database
deprovisionUserEnvironment(target.email, target.username ?? '');
await deleteUser(id);
return ctx.json({ ok: true });