it was the only sidecar living outside src/servers/sidecar/ — it sat in api/terminal/ next to the bridge that talks to it, which is the one place a reader looking for "the sidecars" would not check. now src/servers/sidecar/pty/index.mjs, matching every peer, with a note on the pm2 entry about why this one is node and .mjs (node-pty is a native addon) rather than bun and typescript like the rest. the templates/ directory went to api/users/, next to provision.ts:seedShellConfigs, which is now its only consumer — the sidecar's duplicate seeder went with the sandbox branch in the previous commit. api/terminal/ is left holding exactly one thing: the websocket bridge. no behaviour change. the pm2 entry's script path changed, so `pm2 restart officer-pty` is not enough — pm2 remembers the old path until the entry is deleted and started again. commands are in SIDECAR_WORK_LOG.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
86 lines
3.1 KiB
TypeScript
86 lines
3.1 KiB
TypeScript
import { existsSync, mkdirSync } from 'node:fs';
|
|
import { join } from 'node:path';
|
|
import { DATA_PATH, getHomeDir, toShellUsername } from '@@/data-path';
|
|
import { generateContainerContext, generateClaudeSettings } from '@@/generate-container-context';
|
|
|
|
const TEMPLATE_DIR = join(import.meta.dir, 'templates');
|
|
|
|
const copyTemplate = async (src: string, dest: string) => {
|
|
if (existsSync(dest)) return;
|
|
const content = await Bun.file(src).text();
|
|
await Bun.write(dest, content);
|
|
};
|
|
|
|
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 environment for ${email}`);
|
|
|
|
// Ensure data directories exist
|
|
mkdirSync(userRoot, { recursive: true });
|
|
mkdirSync(homeDir, { recursive: true });
|
|
|
|
// Seed shell config files
|
|
await seedShellConfigs(homeDir);
|
|
|
|
// Generate and write CLAUDE.md + settings.json
|
|
const contextFile = generateContainerContext(email);
|
|
const claudeDir = join(homeDir, '.claude');
|
|
mkdirSync(claudeDir, { recursive: true });
|
|
|
|
const contextContent = await Bun.file(contextFile).text();
|
|
await Bun.write(join(claudeDir, 'CLAUDE.md'), contextContent);
|
|
|
|
const settingsFile = generateClaudeSettings(email, shellUsername);
|
|
const settingsContent = await Bun.file(settingsFile).text();
|
|
await Bun.write(join(claudeDir, 'settings.json'), settingsContent);
|
|
|
|
console.log(`[provision] provisioning complete for ${email}`);
|
|
return true;
|
|
}
|
|
|
|
async function seedShellConfigs(homeDir: string): Promise<void> {
|
|
// .zshenv (must be first — prevents system compinit before oh-my-zsh)
|
|
await copyTemplate(join(TEMPLATE_DIR, '.zshenv'), join(homeDir, '.zshenv'));
|
|
|
|
// .zshrc
|
|
await copyTemplate(join(TEMPLATE_DIR, '.zshrc'), join(homeDir, '.zshrc'));
|
|
|
|
// .tmux.conf
|
|
await copyTemplate(join(TEMPLATE_DIR, '.tmux.conf'), join(homeDir, '.tmux.conf'));
|
|
|
|
// starship config
|
|
const configDir = join(homeDir, '.config');
|
|
mkdirSync(configDir, { recursive: true });
|
|
await copyTemplate(join(TEMPLATE_DIR, 'starship-officer.toml'), join(configDir, 'starship-officer.toml'));
|
|
|
|
// Oh My Zsh — copy from host install
|
|
const ohMyZshDest = join(homeDir, '.oh-my-zsh');
|
|
if (!existsSync(ohMyZshDest)) {
|
|
const hostOhMyZsh = join(process.env.HOME ?? '', '.oh-my-zsh');
|
|
if (existsSync(hostOhMyZsh)) {
|
|
Bun.spawnSync({ cmd: ['cp', '-r', hostOhMyZsh, ohMyZshDest], stdout: 'ignore', stderr: 'ignore' });
|
|
} else {
|
|
Bun.spawnSync({
|
|
cmd: ['git', 'clone', '--depth=1', 'https://github.com/ohmyzsh/ohmyzsh.git', ohMyZshDest],
|
|
stdout: 'ignore',
|
|
stderr: 'ignore',
|
|
});
|
|
}
|
|
}
|
|
|
|
// LazyVim config
|
|
const nvimDir = join(homeDir, '.config', 'nvim');
|
|
if (!existsSync(nvimDir)) {
|
|
const hostNvim = join(process.env.HOME ?? '', '.config', 'nvim');
|
|
if (existsSync(hostNvim)) {
|
|
Bun.spawnSync({ cmd: ['cp', '-r', hostNvim, nvimDir], stdout: 'ignore', stderr: 'ignore' });
|
|
}
|
|
}
|
|
|
|
// Ensure .local/bin exists
|
|
mkdirSync(join(homeDir, '.local', 'bin'), { recursive: true });
|
|
}
|