workspaces to dashboards, imap email sync, ffmpeg tool, tts fix, file browser refresh, automation sidebar reorder
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3,8 +3,8 @@ import { existsSync, mkdirSync, statSync, writeFileSync } from 'node:fs';
|
||||
|
||||
import { dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { getHomeDir, getGlobalSkillsDir, getGlobalToolsDir, getGlobalExtensionsDir, getUserSkillsDir, getUserToolsDir, DATA_PATH, SERVER_CONFIG_DIR, PI_CONFIG_DIR } from '@@/data-path';
|
||||
import { getUsers, getServerIntegration, getUserIntegration } from 'officerdb';
|
||||
import { getHomeDir, getGlobalSkillsDir, getGlobalToolsDir, getGlobalExtensionsDir, getUserSkillsDir, getUserToolsDir, DATA_PATH, PI_CONFIG_DIR, toShellUsername } from '@@/data-path';
|
||||
import { getUsers } from 'officerdb';
|
||||
|
||||
const ensureDir = (dir: string) => { if (!existsSync(dir)) mkdirSync(dir, { recursive: true }); return dir; };
|
||||
|
||||
@@ -140,32 +140,6 @@ const startDockerSidecar = async (port: number, homeDir: string, userId: number,
|
||||
|
||||
const containerHome = `/home/${username}`;
|
||||
|
||||
// Write google-oauth config from DB to files for Docker mount
|
||||
const googleConfigHost = join(SERVER_CONFIG_DIR, 'google-oauth.json');
|
||||
let googleMounts: string[] = [];
|
||||
try {
|
||||
const googleIntegration = await getServerIntegration('google');
|
||||
if (googleIntegration?.config) {
|
||||
mkdirSync(SERVER_CONFIG_DIR, { recursive: true });
|
||||
writeFileSync(googleConfigHost, JSON.stringify(googleIntegration.config, null, 2));
|
||||
googleMounts = ['-v', `${googleConfigHost}:/officer/google-oauth.json:ro`];
|
||||
}
|
||||
} catch {
|
||||
// No google config — skip mount
|
||||
}
|
||||
|
||||
// Write per-user google token from DB for Docker mount
|
||||
const userIntegrationsDir = join(DATA_PATH, email, 'integrations');
|
||||
try {
|
||||
const userGoogle = await getUserIntegration(userId, 'google');
|
||||
if (userGoogle?.config) {
|
||||
mkdirSync(userIntegrationsDir, { recursive: true });
|
||||
writeFileSync(join(userIntegrationsDir, 'google.json'), JSON.stringify(userGoogle.config, null, 2));
|
||||
}
|
||||
} catch {
|
||||
// No user google integration — skip
|
||||
}
|
||||
|
||||
const run = Bun.spawnSync({
|
||||
cmd: [
|
||||
dockerPath,
|
||||
@@ -196,8 +170,6 @@ const startDockerSidecar = async (port: number, homeDir: string, userId: number,
|
||||
'-v', `${PI_CONFIG_DIR}:${containerHome}/.pi/agent`,
|
||||
'-v', `${ensureDir(join(getHomeDir(email), '.pi', 'agent', 'sessions'))}:${containerHome}/.pi/agent/sessions`,
|
||||
'-v', `${join(DATA_PATH, '.generated')}:/officer/generated:ro`,
|
||||
...googleMounts,
|
||||
'-v', `${join(DATA_PATH, email, 'integrations')}:/officer/user/integrations:ro`,
|
||||
'-v', `${join(DATA_PATH, email, 'emails.db')}:/officer/emails.db`,
|
||||
'-w', containerHome,
|
||||
tag,
|
||||
@@ -207,7 +179,13 @@ const startDockerSidecar = async (port: number, homeDir: string, userId: number,
|
||||
});
|
||||
|
||||
if (run.exitCode !== 0) throw new Error('Failed to start terminal sandbox container');
|
||||
return { dockerId };
|
||||
|
||||
// Wait for entrypoint to finish (user creation, sidecar start)
|
||||
for (let i = 0; i < 20; i++) {
|
||||
await new Promise((r) => setTimeout(r, 500));
|
||||
if (await sidecarAlive(port)) return { dockerId };
|
||||
}
|
||||
throw new Error('Terminal sidecar did not start in time');
|
||||
};
|
||||
|
||||
const stopDockerSidecar = (dockerId: string) => {
|
||||
@@ -263,8 +241,19 @@ const dockerStart = (dockerId: string) => {
|
||||
};
|
||||
|
||||
export const ensureDockerContainer = async (email: string, userId: number, homeDir: string, username: string) => {
|
||||
// Check if mount sources are stale (e.g. data dir was deleted and Docker recreated them as root)
|
||||
// Must check BEFORE mkdirSync overwrites them
|
||||
const skillsDir = getUserSkillsDir(email);
|
||||
let stale = false;
|
||||
try {
|
||||
const s = statSync(skillsDir);
|
||||
if (s.uid === 0) stale = true;
|
||||
} catch {
|
||||
// doesn't exist yet — not stale, will be created below
|
||||
}
|
||||
|
||||
// Ensure user-specific resource dirs exist before mounting (Docker creates them as root if missing)
|
||||
mkdirSync(getUserSkillsDir(email), { recursive: true });
|
||||
mkdirSync(skillsDir, { recursive: true });
|
||||
mkdirSync(getUserToolsDir(email), { recursive: true });
|
||||
mkdirSync(join(DATA_PATH, email, 'integrations'), { recursive: true });
|
||||
|
||||
@@ -278,9 +267,9 @@ export const ensureDockerContainer = async (email: string, userId: number, homeD
|
||||
const existing = map[email];
|
||||
|
||||
if (existing && dockerContainerRunning(existing.dockerId)) {
|
||||
// Recreate if resource mounts are missing (e.g. first run after feature was added)
|
||||
if (!containerHasExpectedMounts(existing.dockerId)) {
|
||||
console.log(`[terminal] recreating container for ${email} — resource mounts missing`);
|
||||
// Recreate if resource mounts are missing or data dir was recreated (stale mounts)
|
||||
if (!containerHasExpectedMounts(existing.dockerId) || stale) {
|
||||
console.log(`[terminal] recreating container for ${email} — mounts stale or missing`);
|
||||
stopDockerSidecar(existing.dockerId);
|
||||
} else {
|
||||
console.log(`[terminal] reusing running container ${existing.dockerId} for ${email} on port ${existing.port}`);
|
||||
@@ -289,7 +278,7 @@ export const ensureDockerContainer = async (email: string, userId: number, homeD
|
||||
}
|
||||
|
||||
if (existing && dockerContainerExists(existing.dockerId)) {
|
||||
if (!containerHasExpectedMounts(existing.dockerId)) {
|
||||
if (!containerHasExpectedMounts(existing.dockerId) || stale) {
|
||||
stopDockerSidecar(existing.dockerId);
|
||||
} else if (dockerStart(existing.dockerId)) {
|
||||
return existing;
|
||||
@@ -366,7 +355,7 @@ export const initTerminalSidecars = async () => {
|
||||
mkdirSync(dirname(homeDir), { recursive: true });
|
||||
mkdirSync(homeDir, { recursive: true });
|
||||
try {
|
||||
await ensureDockerContainer(user.email, user.id, homeDir, user.username ?? user.email.split('@')[0]!);
|
||||
await ensureDockerContainer(user.email, user.id, homeDir, toShellUsername(user.username ?? '', user.email));
|
||||
console.log(`[terminal] sidecar ready for ${user.email}`);
|
||||
} catch (err) {
|
||||
console.error(`[terminal] failed to start sidecar for ${user.email}:`, err);
|
||||
|
||||
Reference in New Issue
Block a user