remove seed directory, clean up provisioning and sync modules
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
import type { SidecarCommand, SidecarEvent } from '../protocol';
|
||||
import { loadState, flushAndSave, acquireLock, releaseLock, getState } from './state';
|
||||
import { startAnthropicProxy, getProxySecret, ensureProxySecret } from './proxy';
|
||||
import * as claudeManager from './claude-manager';
|
||||
import { createSidecarConnector } from '../connect';
|
||||
|
||||
const API_URL = process.env.API_URL ?? `ws://127.0.0.1:${process.env.PORT ?? '5000'}`;
|
||||
@@ -9,7 +8,7 @@ const API_URL = process.env.API_URL ?? `ws://127.0.0.1:${process.env.PORT ?? '50
|
||||
// ── Startup ──
|
||||
|
||||
if (!acquireLock()) {
|
||||
console.error('[claude] another instance is already running (lock file exists with live PID)');
|
||||
console.error('[proxy] another instance is already running (lock file exists with live PID)');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -20,7 +19,7 @@ ensureProxySecret();
|
||||
try {
|
||||
startAnthropicProxy();
|
||||
} catch (err) {
|
||||
console.error('[claude] failed to start Anthropic proxy:', err instanceof Error ? err.message : err);
|
||||
console.error('[proxy] failed to start Anthropic proxy:', err instanceof Error ? err.message : err);
|
||||
}
|
||||
|
||||
// ── Command handlers ──
|
||||
@@ -48,43 +47,6 @@ async function handleCommand(cmd: SidecarCommand, reply: ReplyFn) {
|
||||
reply({ type: 'proxy:secret', id: cmd.id, secret: getProxySecret() });
|
||||
break;
|
||||
|
||||
case 'claude:spawn': {
|
||||
try {
|
||||
const result = await claudeManager.spawnClaude(cmd.params);
|
||||
reply({ type: 'claude:result', id: cmd.id, result });
|
||||
} catch (err) {
|
||||
reply({ type: 'claude:error', id: cmd.id, error: err instanceof Error ? err.message : String(err) });
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'claude:spawn-streaming': {
|
||||
reply({ type: 'claude:spawned', id: cmd.id, sessionKey: cmd.params.sessionKey });
|
||||
|
||||
const onEvent = (event: import('../../api/pi/types').PiEvent) => {
|
||||
connection.send({ type: 'claude:event', sessionKey: cmd.params.sessionKey, event });
|
||||
};
|
||||
|
||||
claudeManager.spawnClaudeStreaming(cmd.params, onEvent).catch((err) => {
|
||||
connection.send({
|
||||
type: 'claude:event',
|
||||
sessionKey: cmd.params.sessionKey,
|
||||
event: { type: 'error', message: err instanceof Error ? err.message : String(err) },
|
||||
});
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case 'claude:kill':
|
||||
claudeManager.killClaudeSession(cmd.sessionKey);
|
||||
reply({ type: 'claude:killed', id: cmd.id });
|
||||
break;
|
||||
|
||||
case 'claude:clear-session':
|
||||
claudeManager.clearSession(cmd.sessionKey);
|
||||
reply({ type: 'claude:session-cleared', id: cmd.id });
|
||||
break;
|
||||
|
||||
default:
|
||||
reply({
|
||||
type: 'error',
|
||||
@@ -98,8 +60,8 @@ async function handleCommand(cmd: SidecarCommand, reply: ReplyFn) {
|
||||
|
||||
const connection = createSidecarConnector({
|
||||
apiUrl: `${API_URL}/api/sidecar/register`,
|
||||
name: 'claude',
|
||||
capabilities: ['claude', 'proxy'],
|
||||
name: 'proxy',
|
||||
capabilities: ['proxy'],
|
||||
onCommand(cmd, reply) {
|
||||
handleCommand(cmd as SidecarCommand, reply as ReplyFn);
|
||||
},
|
||||
@@ -108,7 +70,7 @@ const connection = createSidecarConnector({
|
||||
// ── Graceful shutdown ──
|
||||
|
||||
async function shutdown(signal: string) {
|
||||
console.log(`[claude] ${signal} received, saving state...`);
|
||||
console.log(`[proxy] ${signal} received, saving state...`);
|
||||
connection.destroy();
|
||||
await flushAndSave();
|
||||
releaseLock();
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import { join } from 'node:path';
|
||||
import { mkdirSync, existsSync } from 'node:fs';
|
||||
import { mkdirSync, existsSync, readFileSync, writeFileSync, unlinkSync } from 'node:fs';
|
||||
|
||||
const DATA_PATH = process.env.DATA_PATH ?? join(process.cwd(), 'data');
|
||||
const STATE_DIR = join(DATA_PATH, 'sidecar');
|
||||
const STATE_FILE = join(STATE_DIR, 'claude-state.json');
|
||||
const LOCK_FILE = join(STATE_DIR, 'claude.lock');
|
||||
|
||||
export type PersistedState = {
|
||||
proxySecret: string;
|
||||
@@ -16,23 +13,42 @@ const DEFAULT_STATE: PersistedState = {
|
||||
claudeSessions: {},
|
||||
};
|
||||
|
||||
let stateDir: string;
|
||||
let stateFile: string;
|
||||
let lockFile: string;
|
||||
let currentState: PersistedState = { ...DEFAULT_STATE };
|
||||
let saveTimer: Timer | null = null;
|
||||
|
||||
/** Call once at startup to configure paths. For proxy: no email. For per-user: pass email. */
|
||||
export function initPaths(email?: string): void {
|
||||
if (email) {
|
||||
stateDir = join(DATA_PATH, email, 'sidecar');
|
||||
stateFile = join(stateDir, 'claude-state.json');
|
||||
lockFile = join(stateDir, 'claude.lock');
|
||||
} else {
|
||||
stateDir = join(DATA_PATH, 'sidecar');
|
||||
stateFile = join(stateDir, 'claude-state.json');
|
||||
lockFile = join(stateDir, 'claude.lock');
|
||||
}
|
||||
}
|
||||
|
||||
// Default to proxy paths
|
||||
initPaths();
|
||||
|
||||
function ensureDir() {
|
||||
if (!existsSync(STATE_DIR)) {
|
||||
mkdirSync(STATE_DIR, { recursive: true });
|
||||
if (!existsSync(stateDir)) {
|
||||
mkdirSync(stateDir, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
export function loadState(): PersistedState {
|
||||
ensureDir();
|
||||
try {
|
||||
if (!existsSync(STATE_FILE)) {
|
||||
if (!existsSync(stateFile)) {
|
||||
currentState = { ...DEFAULT_STATE };
|
||||
return currentState;
|
||||
}
|
||||
const text = require('node:fs').readFileSync(STATE_FILE, 'utf-8');
|
||||
const text = readFileSync(stateFile, 'utf-8');
|
||||
currentState = { ...DEFAULT_STATE, ...JSON.parse(text) };
|
||||
return currentState;
|
||||
} catch {
|
||||
@@ -43,7 +59,7 @@ export function loadState(): PersistedState {
|
||||
|
||||
export async function saveState(): Promise<void> {
|
||||
ensureDir();
|
||||
await Bun.write(STATE_FILE, JSON.stringify(currentState, null, 2));
|
||||
await Bun.write(stateFile, JSON.stringify(currentState, null, 2));
|
||||
}
|
||||
|
||||
export function getState(): PersistedState {
|
||||
@@ -90,14 +106,14 @@ export async function flushAndSave(): Promise<void> {
|
||||
export function acquireLock(): boolean {
|
||||
ensureDir();
|
||||
try {
|
||||
if (existsSync(LOCK_FILE)) {
|
||||
const pidStr = require('node:fs').readFileSync(LOCK_FILE, 'utf-8').trim();
|
||||
if (existsSync(lockFile)) {
|
||||
const pidStr = readFileSync(lockFile, 'utf-8').trim();
|
||||
const pid = Number(pidStr);
|
||||
if (pid && isProcessAlive(pid)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
require('node:fs').writeFileSync(LOCK_FILE, String(process.pid));
|
||||
writeFileSync(lockFile, String(process.pid));
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
@@ -106,8 +122,8 @@ export function acquireLock(): boolean {
|
||||
|
||||
export function releaseLock(): void {
|
||||
try {
|
||||
if (existsSync(LOCK_FILE)) {
|
||||
require('node:fs').unlinkSync(LOCK_FILE);
|
||||
if (existsSync(lockFile)) {
|
||||
unlinkSync(lockFile);
|
||||
}
|
||||
} catch {
|
||||
// best effort
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { existsSync, mkdirSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import type { VncStartParams, VncSessionInfo } from '../protocol';
|
||||
import { getHomeDirForRole, toShellUsername } from '@@/data-path';
|
||||
import { getHomeDirForRole } from '@@/data-path';
|
||||
|
||||
type VncSession = {
|
||||
email: string;
|
||||
@@ -87,7 +87,6 @@ export async function startSession(params: VncStartParams): Promise<{ port: numb
|
||||
sessions.delete(params.email);
|
||||
}
|
||||
|
||||
const shellUsername = toShellUsername(params.username, params.email);
|
||||
const homeDir = getHomeDirForRole(params.email, params.role);
|
||||
const resolution = params.resolution ?? '1920x1080';
|
||||
const display = findFreeDisplay();
|
||||
@@ -96,21 +95,9 @@ export async function startSession(params: VncStartParams): Promise<{ port: numb
|
||||
// Lazy-provision VNC environment if missing
|
||||
await ensureVncEnv(homeDir);
|
||||
|
||||
// Spawn VNC server as the target user
|
||||
// Spawn VNC server
|
||||
const proc = Bun.spawn({
|
||||
cmd: [
|
||||
'sudo',
|
||||
'-u',
|
||||
shellUsername,
|
||||
'vncserver',
|
||||
`:${display}`,
|
||||
'-geometry',
|
||||
resolution,
|
||||
'-depth',
|
||||
'24',
|
||||
'-localhost',
|
||||
'yes',
|
||||
],
|
||||
cmd: ['vncserver', `:${display}`, '-geometry', resolution, '-depth', '24', '-localhost', 'yes'],
|
||||
env: { ...process.env, HOME: homeDir },
|
||||
stdout: 'pipe',
|
||||
stderr: 'pipe',
|
||||
@@ -130,7 +117,7 @@ export async function startSession(params: VncStartParams): Promise<{ port: numb
|
||||
pid = parseInt(content.trim(), 10) || 0;
|
||||
}
|
||||
|
||||
sessions.set(params.email, { email: params.email, username: params.username, display, port, pid });
|
||||
sessions.set(params.email, { email: params.email, username: params.username ?? '', display, port, pid });
|
||||
console.log(`[vnc] started session for ${params.email} on :${display} (port ${port}, pid ${pid})`);
|
||||
|
||||
return { port, display };
|
||||
@@ -140,11 +127,10 @@ export function stopSession(email: string): void {
|
||||
const session = sessions.get(email);
|
||||
if (!session) return;
|
||||
|
||||
const shellUsername = toShellUsername(session.username, email);
|
||||
const homeDir = getHomeDirForRole(email, null);
|
||||
|
||||
Bun.spawnSync({
|
||||
cmd: ['sudo', '-u', shellUsername, 'vncserver', '-kill', `:${session.display}`],
|
||||
cmd: ['vncserver', '-kill', `:${session.display}`],
|
||||
env: { ...process.env, HOME: homeDir },
|
||||
stdout: 'ignore',
|
||||
stderr: 'ignore',
|
||||
|
||||
Reference in New Issue
Block a user