Files
Job-Info/ModeSwitch/backend/orchestrator.ts
T
aewing 7b997dc354 Rebuild Mode1Svelte with SvelteKit + Node.js adapter
- Migrated from Bun/Hono to modern SvelteKit tech stack
- Implemented complete authentication module (Azure MSAL + PocketBase)
- Created 5 API endpoints for auth operations (config, graph token, validate, refresh, compare)
- Built interactive Svelte dashboard with mobile optimization (iPhone 16)
- Added Node.js adapter (@sveltejs/adapter-node) for production deployment
- Integrated Tailwind CSS with responsive design
- Fixed orchestrator to spawn Mode1Svelte with Node.js on port 3005
- Updated systemd service (job-info-test.service) to start orchestrator on port 3006
- Port configuration locked: 3005 = Mode1Svelte, 3006 = Orchestrator
- Full TypeScript type safety throughout application
- Services auto-managed by orchestrator with systemd integration
2026-01-23 02:18:34 +00:00

234 lines
5.9 KiB
TypeScript

import { Hono } from 'hono';
import { cors } from 'hono/cors';
import * as fs from 'node:fs';
// Configuration
const MODES_DIR = '/home/admin/Job-Info-Test';
const MODE_CONFIG_FILE = `${MODES_DIR}/ModeSwitch/activeMode.txt`;
const SHARED_PORT = 3005; // Mode services run here - NEVER use 3000 or 3001
const ORCHESTRATOR_PORT = 3006; // Orchestrator API runs here
// Mode folder mappings - only Mode6Test exists currently
const MODE_FOLDERS: Record<string, string> = {
'Mode1': 'Mode1',
'Mode1Svelte': 'Mode1Svelte',
'Mode6Test': 'Mode6Test',
};
let currentMode: string = 'Mode1Svelte';
let currentProcess: any = null;
// Helper: Read active mode from config file
function readActiveMode(): string {
try {
if (fs.existsSync(MODE_CONFIG_FILE)) {
const content = fs.readFileSync(MODE_CONFIG_FILE, 'utf-8').trim();
if (content && MODE_FOLDERS[content]) {
return content;
}
}
} catch (err) {
console.error('Failed to read mode config:', err);
}
return 'Mode6Test';
}
// Helper: Write active mode to config file
function writeActiveMode(mode: string): void {
try {
fs.writeFileSync(MODE_CONFIG_FILE, mode, 'utf-8');
} catch (err) {
console.error('Failed to write mode config:', err);
}
}
// Helper: Kill process on shared port
async function killProcessOnPort(port: number): Promise<void> {
try {
await Bun.spawn({
cmd: ['pkill', '-f', `bun.*:${port}`],
stdout: 'ignore',
stderr: 'ignore',
});
// Fallback: Try to kill any Bun process we spawned
if (currentProcess) {
currentProcess.kill();
currentProcess = null;
}
// Give OS time to release the port
await new Promise(resolve => setTimeout(resolve, 500));
} catch (err) {
console.error(`Failed to kill process on port ${port}:`, err);
}
}
// Helper: Start a mode
async function startMode(mode: string): Promise<boolean> {
try {
// Kill any existing process
await killProcessOnPort(SHARED_PORT);
const folderName = MODE_FOLDERS[mode];
if (!folderName) {
console.error(`Unknown mode: ${mode}`);
return false;
}
const modePath = `${MODES_DIR}/${folderName}`;
if (!fs.existsSync(modePath)) {
console.error(`Mode folder not found: ${modePath}`);
return false;
}
console.log(`[ModeSwitch] Starting ${mode} (${folderName}/) on port ${SHARED_PORT}...`);
// Mode1Svelte uses Node.js adapter with build/index.js
if (mode === 'Mode1Svelte') {
const buildPath = `${modePath}/build`;
if (!fs.existsSync(buildPath)) {
console.error(`Build directory not found for Mode1Svelte. Run: cd ${modePath} && npm run build`);
return false;
}
// Start with Node.js
currentProcess = Bun.spawn({
cmd: ['node', 'build/index.js'],
cwd: modePath,
env: {
...process.env,
PORT: String(SHARED_PORT),
},
stdout: 'pipe',
stderr: 'pipe',
});
} else {
// Other modes (Mode1, Mode6Test) use Bun with backend/server.ts
const serverPath = 'backend/server.ts';
currentProcess = Bun.spawn({
cmd: [process.execPath, 'run', serverPath],
cwd: modePath,
env: {
...process.env,
PORT: String(SHARED_PORT),
},
stdout: 'pipe',
stderr: 'pipe',
});
}
currentMode = mode;
writeActiveMode(mode);
// Read and log output from the spawned process
if (currentProcess.stdout) {
(async () => {
const reader = currentProcess.stdout.getReader();
while (true) {
const { done, value } = await reader.read();
if (done) break;
console.log(`[${mode}]`, new TextDecoder().decode(value));
}
})();
}
console.log(`${mode} started successfully on port ${SHARED_PORT}`);
return true;
} catch (err) {
console.error(`Failed to start ${mode}:`, err);
return false;
}
}
// Initialize app
const app = new Hono();
// Enable CORS
app.use('*', cors());
// API: Get current mode status
app.get('/status', (c) => {
return c.json({
currentMode,
availableModes: Object.keys(MODE_FOLDERS),
sharedPort: SHARED_PORT,
orchestratorPort: ORCHESTRATOR_PORT,
});
});
// API: Switch to a different mode
app.post('/switch/:mode', async (c) => {
const mode = c.req.param('mode');
if (!MODE_FOLDERS[mode]) {
return c.json({
success: false,
error: `Unknown mode: ${mode}. Available: ${Object.keys(MODE_FOLDERS).join(', ')}`
}, 400);
}
if (mode === currentMode) {
return c.json({
success: true,
message: `Already running ${mode}`,
currentMode
});
}
const success = await startMode(mode);
if (success) {
return c.json({
success: true,
message: `Switched to ${mode}`,
currentMode: mode
});
} else {
return c.json({
success: false,
error: `Failed to start ${mode}`
}, 500);
}
});
// API: Restart current mode
app.post('/restart', async (c) => {
console.log(`[ModeSwitch] Restarting ${currentMode}...`);
const success = await startMode(currentMode);
return c.json({
success,
message: success ? `Restarted ${currentMode}` : `Failed to restart ${currentMode}`,
currentMode
});
});
// API: List available modes
app.get('/modes', (c) => {
return c.json({
modes: Object.keys(MODE_FOLDERS),
currentMode
});
});
// Startup message
console.log(`
================================================================================
[ModeSwitch Orchestrator]
================================================================================
Listening on port: ${ORCHESTRATOR_PORT}
Shared mode port: ${SHARED_PORT}
Config file: ${MODE_CONFIG_FILE}
================================================================================
`);
// Start initial mode
const initialMode = readActiveMode();
await startMode(initialMode);
// Export for Bun
export default {
port: ORCHESTRATOR_PORT,
fetch: app.fetch,
};