@orkify/cli 1.0.0-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +191 -0
- package/README.md +1701 -0
- package/bin/orkify +3 -0
- package/boot/systemd/orkify@.service +30 -0
- package/dist/agent-name.d.ts +4 -0
- package/dist/agent-name.js +42 -0
- package/dist/alerts/AlertEvaluator.d.ts +14 -0
- package/dist/alerts/AlertEvaluator.js +135 -0
- package/dist/cli/commands/autostart.d.ts +3 -0
- package/dist/cli/commands/autostart.js +11 -0
- package/dist/cli/commands/crash-test.d.ts +3 -0
- package/dist/cli/commands/crash-test.js +17 -0
- package/dist/cli/commands/daemon-reload.d.ts +3 -0
- package/dist/cli/commands/daemon-reload.js +72 -0
- package/dist/cli/commands/delete.d.ts +3 -0
- package/dist/cli/commands/delete.js +37 -0
- package/dist/cli/commands/deploy.d.ts +6 -0
- package/dist/cli/commands/deploy.js +266 -0
- package/dist/cli/commands/down.d.ts +3 -0
- package/dist/cli/commands/down.js +36 -0
- package/dist/cli/commands/flush.d.ts +3 -0
- package/dist/cli/commands/flush.js +28 -0
- package/dist/cli/commands/kill.d.ts +3 -0
- package/dist/cli/commands/kill.js +35 -0
- package/dist/cli/commands/list.d.ts +14 -0
- package/dist/cli/commands/list.js +361 -0
- package/dist/cli/commands/logs.d.ts +3 -0
- package/dist/cli/commands/logs.js +107 -0
- package/dist/cli/commands/mcp.d.ts +3 -0
- package/dist/cli/commands/mcp.js +151 -0
- package/dist/cli/commands/reload.d.ts +3 -0
- package/dist/cli/commands/reload.js +54 -0
- package/dist/cli/commands/restart.d.ts +3 -0
- package/dist/cli/commands/restart.js +43 -0
- package/dist/cli/commands/restore.d.ts +3 -0
- package/dist/cli/commands/restore.js +88 -0
- package/dist/cli/commands/run.d.ts +8 -0
- package/dist/cli/commands/run.js +212 -0
- package/dist/cli/commands/snap.d.ts +3 -0
- package/dist/cli/commands/snap.js +30 -0
- package/dist/cli/commands/up.d.ts +3 -0
- package/dist/cli/commands/up.js +125 -0
- package/dist/cli/crash-recovery.d.ts +2 -0
- package/dist/cli/crash-recovery.js +67 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.js +46 -0
- package/dist/cli/parse.d.ts +28 -0
- package/dist/cli/parse.js +97 -0
- package/dist/cluster/ClusterWrapper.d.ts +18 -0
- package/dist/cluster/ClusterWrapper.js +602 -0
- package/dist/config/ConfigStore.d.ts +11 -0
- package/dist/config/ConfigStore.js +21 -0
- package/dist/config/schema.d.ts +103 -0
- package/dist/config/schema.js +49 -0
- package/dist/constants.d.ts +83 -0
- package/dist/constants.js +289 -0
- package/dist/cron/CronScheduler.d.ts +25 -0
- package/dist/cron/CronScheduler.js +149 -0
- package/dist/daemon/GracefulManager.d.ts +8 -0
- package/dist/daemon/GracefulManager.js +29 -0
- package/dist/daemon/ManagedProcess.d.ts +71 -0
- package/dist/daemon/ManagedProcess.js +1020 -0
- package/dist/daemon/Orchestrator.d.ts +51 -0
- package/dist/daemon/Orchestrator.js +416 -0
- package/dist/daemon/RotatingWriter.d.ts +27 -0
- package/dist/daemon/RotatingWriter.js +264 -0
- package/dist/daemon/index.d.ts +2 -0
- package/dist/daemon/index.js +106 -0
- package/dist/daemon/startDaemon.d.ts +30 -0
- package/dist/daemon/startDaemon.js +693 -0
- package/dist/deploy/CommandPoller.d.ts +13 -0
- package/dist/deploy/CommandPoller.js +53 -0
- package/dist/deploy/DeployExecutor.d.ts +33 -0
- package/dist/deploy/DeployExecutor.js +340 -0
- package/dist/deploy/config.d.ts +20 -0
- package/dist/deploy/config.js +161 -0
- package/dist/deploy/env.d.ts +2 -0
- package/dist/deploy/env.js +17 -0
- package/dist/deploy/tarball.d.ts +32 -0
- package/dist/deploy/tarball.js +243 -0
- package/dist/detect/framework.d.ts +2 -0
- package/dist/detect/framework.js +24 -0
- package/dist/ipc/DaemonClient.d.ts +31 -0
- package/dist/ipc/DaemonClient.js +248 -0
- package/dist/ipc/DaemonServer.d.ts +28 -0
- package/dist/ipc/DaemonServer.js +166 -0
- package/dist/ipc/MultiUserClient.d.ts +27 -0
- package/dist/ipc/MultiUserClient.js +203 -0
- package/dist/ipc/protocol.d.ts +7 -0
- package/dist/ipc/protocol.js +53 -0
- package/dist/ipc/restoreDaemon.d.ts +8 -0
- package/dist/ipc/restoreDaemon.js +19 -0
- package/dist/machine-id.d.ts +11 -0
- package/dist/machine-id.js +51 -0
- package/dist/mcp/auth.d.ts +118 -0
- package/dist/mcp/auth.js +245 -0
- package/dist/mcp/http.d.ts +20 -0
- package/dist/mcp/http.js +229 -0
- package/dist/mcp/index.d.ts +3 -0
- package/dist/mcp/index.js +8 -0
- package/dist/mcp/server.d.ts +37 -0
- package/dist/mcp/server.js +413 -0
- package/dist/probe/compute-fingerprint.d.ts +27 -0
- package/dist/probe/compute-fingerprint.js +65 -0
- package/dist/probe/parse-frames.d.ts +21 -0
- package/dist/probe/parse-frames.js +57 -0
- package/dist/probe/resolve-sourcemaps.d.ts +25 -0
- package/dist/probe/resolve-sourcemaps.js +281 -0
- package/dist/state/StateStore.d.ts +11 -0
- package/dist/state/StateStore.js +78 -0
- package/dist/telemetry/TelemetryReporter.d.ts +49 -0
- package/dist/telemetry/TelemetryReporter.js +451 -0
- package/dist/types/index.d.ts +373 -0
- package/dist/types/index.js +2 -0
- package/package.json +148 -0
- package/packages/cache/README.md +114 -0
- package/packages/cache/dist/CacheClient.d.ts +26 -0
- package/packages/cache/dist/CacheClient.d.ts.map +1 -0
- package/packages/cache/dist/CacheClient.js +174 -0
- package/packages/cache/dist/CacheClient.js.map +1 -0
- package/packages/cache/dist/CacheFileStore.d.ts +45 -0
- package/packages/cache/dist/CacheFileStore.d.ts.map +1 -0
- package/packages/cache/dist/CacheFileStore.js +446 -0
- package/packages/cache/dist/CacheFileStore.js.map +1 -0
- package/packages/cache/dist/CachePersistence.d.ts +9 -0
- package/packages/cache/dist/CachePersistence.d.ts.map +1 -0
- package/packages/cache/dist/CachePersistence.js +67 -0
- package/packages/cache/dist/CachePersistence.js.map +1 -0
- package/packages/cache/dist/CachePrimary.d.ts +25 -0
- package/packages/cache/dist/CachePrimary.d.ts.map +1 -0
- package/packages/cache/dist/CachePrimary.js +155 -0
- package/packages/cache/dist/CachePrimary.js.map +1 -0
- package/packages/cache/dist/CacheStore.d.ts +50 -0
- package/packages/cache/dist/CacheStore.d.ts.map +1 -0
- package/packages/cache/dist/CacheStore.js +271 -0
- package/packages/cache/dist/CacheStore.js.map +1 -0
- package/packages/cache/dist/constants.d.ts +6 -0
- package/packages/cache/dist/constants.d.ts.map +1 -0
- package/packages/cache/dist/constants.js +9 -0
- package/packages/cache/dist/constants.js.map +1 -0
- package/packages/cache/dist/index.d.ts +16 -0
- package/packages/cache/dist/index.d.ts.map +1 -0
- package/packages/cache/dist/index.js +86 -0
- package/packages/cache/dist/index.js.map +1 -0
- package/packages/cache/dist/serialize.d.ts +9 -0
- package/packages/cache/dist/serialize.d.ts.map +1 -0
- package/packages/cache/dist/serialize.js +40 -0
- package/packages/cache/dist/serialize.js.map +1 -0
- package/packages/cache/dist/types.d.ts +123 -0
- package/packages/cache/dist/types.d.ts.map +1 -0
- package/packages/cache/dist/types.js +2 -0
- package/packages/cache/dist/types.js.map +1 -0
- package/packages/cache/package.json +27 -0
- package/packages/cache/src/CacheClient.ts +227 -0
- package/packages/cache/src/CacheFileStore.ts +528 -0
- package/packages/cache/src/CachePersistence.ts +89 -0
- package/packages/cache/src/CachePrimary.ts +172 -0
- package/packages/cache/src/CacheStore.ts +308 -0
- package/packages/cache/src/constants.ts +10 -0
- package/packages/cache/src/index.ts +100 -0
- package/packages/cache/src/serialize.ts +49 -0
- package/packages/cache/src/types.ts +156 -0
- package/packages/cache/tsconfig.json +18 -0
- package/packages/cache/tsconfig.tsbuildinfo +1 -0
- package/packages/next/README.md +166 -0
- package/packages/next/dist/error-capture.d.ts +34 -0
- package/packages/next/dist/error-capture.d.ts.map +1 -0
- package/packages/next/dist/error-capture.js +130 -0
- package/packages/next/dist/error-capture.js.map +1 -0
- package/packages/next/dist/error-handler.d.ts +10 -0
- package/packages/next/dist/error-handler.d.ts.map +1 -0
- package/packages/next/dist/error-handler.js +186 -0
- package/packages/next/dist/error-handler.js.map +1 -0
- package/packages/next/dist/isr-cache.d.ts +9 -0
- package/packages/next/dist/isr-cache.d.ts.map +1 -0
- package/packages/next/dist/isr-cache.js +86 -0
- package/packages/next/dist/isr-cache.js.map +1 -0
- package/packages/next/dist/stream.d.ts +5 -0
- package/packages/next/dist/stream.d.ts.map +1 -0
- package/packages/next/dist/stream.js +22 -0
- package/packages/next/dist/stream.js.map +1 -0
- package/packages/next/dist/types.d.ts +33 -0
- package/packages/next/dist/types.d.ts.map +1 -0
- package/packages/next/dist/types.js +6 -0
- package/packages/next/dist/types.js.map +1 -0
- package/packages/next/dist/use-cache.d.ts +4 -0
- package/packages/next/dist/use-cache.d.ts.map +1 -0
- package/packages/next/dist/use-cache.js +86 -0
- package/packages/next/dist/use-cache.js.map +1 -0
- package/packages/next/dist/utils.d.ts +32 -0
- package/packages/next/dist/utils.d.ts.map +1 -0
- package/packages/next/dist/utils.js +88 -0
- package/packages/next/dist/utils.js.map +1 -0
- package/packages/next/package.json +52 -0
- package/packages/next/src/error-capture.ts +177 -0
- package/packages/next/src/error-handler.ts +221 -0
- package/packages/next/src/isr-cache.ts +100 -0
- package/packages/next/src/stream.ts +23 -0
- package/packages/next/src/types.ts +33 -0
- package/packages/next/src/use-cache.ts +99 -0
- package/packages/next/src/utils.ts +102 -0
- package/packages/next/tsconfig.json +19 -0
- package/packages/next/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,693 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
import { closeSync, createReadStream, existsSync, mkdirSync, openSync, readdirSync, readFileSync, unlinkSync, writeSync, } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { createInterface } from 'node:readline';
|
|
5
|
+
import { AlertEvaluator } from '../alerts/AlertEvaluator.js';
|
|
6
|
+
import { ConfigStore } from '../config/ConfigStore.js';
|
|
7
|
+
import { DAEMON_PID_FILE, IPCMessageType, LOGS_DIR, ORKIFY_DEPLOYS_DIR, ORKIFY_HOME, SOCKET_PATH, TELEMETRY_DEFAULT_API_HOST, } from '../constants.js';
|
|
8
|
+
import { CronScheduler } from '../cron/CronScheduler.js';
|
|
9
|
+
import { CommandPoller } from '../deploy/CommandPoller.js';
|
|
10
|
+
import { getOrkifyConfig } from '../deploy/config.js';
|
|
11
|
+
import { DeployExecutor } from '../deploy/DeployExecutor.js';
|
|
12
|
+
import { DaemonServer } from '../ipc/DaemonServer.js';
|
|
13
|
+
import { createResponse } from '../ipc/protocol.js';
|
|
14
|
+
import { RemoteConfigVerifier } from '../mcp/auth.js';
|
|
15
|
+
import { startMcpHttpServer } from '../mcp/http.js';
|
|
16
|
+
import { TelemetryReporter } from '../telemetry/TelemetryReporter.js';
|
|
17
|
+
import { Orchestrator } from './Orchestrator.js';
|
|
18
|
+
/**
|
|
19
|
+
* Acquire an exclusive lock on the PID file using O_EXCL (atomic create).
|
|
20
|
+
* Keeps the fd open for the process lifetime so the PID file's existence
|
|
21
|
+
* reliably indicates a running daemon. Returns the open fd.
|
|
22
|
+
*
|
|
23
|
+
* If the PID file already exists, checks whether the holder is still alive.
|
|
24
|
+
* Stale PID files (dead holder) are removed and re-acquired.
|
|
25
|
+
*
|
|
26
|
+
* Throws if another daemon is genuinely running.
|
|
27
|
+
*/
|
|
28
|
+
function acquirePidLock() {
|
|
29
|
+
try {
|
|
30
|
+
const fd = openSync(DAEMON_PID_FILE, 'wx'); // O_CREAT | O_EXCL | O_WRONLY
|
|
31
|
+
writeSync(fd, String(process.pid));
|
|
32
|
+
return fd;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// PID file exists — check if the holder is still alive
|
|
36
|
+
let holderPid;
|
|
37
|
+
try {
|
|
38
|
+
holderPid = parseInt(readFileSync(DAEMON_PID_FILE, 'utf-8').trim(), 10);
|
|
39
|
+
process.kill(holderPid, 0); // throws if process is dead
|
|
40
|
+
}
|
|
41
|
+
catch (e) {
|
|
42
|
+
// Holder is dead or PID file is unreadable — stale lock, take over
|
|
43
|
+
if (e instanceof Error && 'code' in e && e.code === 'EPERM') {
|
|
44
|
+
// EPERM means the process exists but we can't signal it (different user)
|
|
45
|
+
throw new Error('Another daemon is already running (cannot signal PID, permission denied)', { cause: e });
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
unlinkSync(DAEMON_PID_FILE);
|
|
49
|
+
const fd = openSync(DAEMON_PID_FILE, 'wx');
|
|
50
|
+
writeSync(fd, String(process.pid));
|
|
51
|
+
return fd;
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
throw new Error('Failed to acquire daemon lock (race with another process)');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
throw new Error(`Another daemon is already running (PID ${holderPid})`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Read the last N lines from a log file on disk.
|
|
62
|
+
*/
|
|
63
|
+
function tailFile(filePath, lines) {
|
|
64
|
+
return new Promise((resolve, reject) => {
|
|
65
|
+
const buffer = [];
|
|
66
|
+
const stream = createReadStream(filePath, { encoding: 'utf8' });
|
|
67
|
+
const rl = createInterface({ input: stream });
|
|
68
|
+
rl.on('line', (line) => {
|
|
69
|
+
buffer.push(line);
|
|
70
|
+
if (buffer.length > lines) {
|
|
71
|
+
buffer.shift();
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
rl.on('close', () => resolve(buffer));
|
|
75
|
+
rl.on('error', reject);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Read recent log lines from disk for a process (or all processes).
|
|
80
|
+
*/
|
|
81
|
+
async function readLogFiles(target, lines) {
|
|
82
|
+
if (!existsSync(LOGS_DIR))
|
|
83
|
+
return [];
|
|
84
|
+
const files = [];
|
|
85
|
+
if (target !== 'all') {
|
|
86
|
+
const outFile = join(LOGS_DIR, `${target}.stdout.log`);
|
|
87
|
+
const errFile = join(LOGS_DIR, `${target}.stderr.log`);
|
|
88
|
+
if (existsSync(outFile))
|
|
89
|
+
files.push(outFile);
|
|
90
|
+
if (existsSync(errFile))
|
|
91
|
+
files.push(errFile);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
for (const file of readdirSync(LOGS_DIR)) {
|
|
95
|
+
if (file.endsWith('.stdout.log') || file.endsWith('.stderr.log')) {
|
|
96
|
+
files.push(join(LOGS_DIR, file));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const results = [];
|
|
101
|
+
for (const file of files) {
|
|
102
|
+
const tail = await tailFile(file, lines);
|
|
103
|
+
if (tail.length > 0) {
|
|
104
|
+
results.push({ file, lines: tail });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return results;
|
|
108
|
+
}
|
|
109
|
+
export async function startDaemon(options = {}) {
|
|
110
|
+
const { foreground = false, skipTimestampPrefix = false } = options;
|
|
111
|
+
// Prepend ISO timestamps to all daemon log output (background mode only)
|
|
112
|
+
if (!skipTimestampPrefix) {
|
|
113
|
+
const originalLog = console.log.bind(console);
|
|
114
|
+
const originalError = console.error.bind(console);
|
|
115
|
+
console.log = (...args) => {
|
|
116
|
+
originalLog(`[${new Date().toISOString()}]`, ...args);
|
|
117
|
+
};
|
|
118
|
+
console.error = (...args) => {
|
|
119
|
+
originalError(`[${new Date().toISOString()}] ERROR`, ...args);
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
// Ensure home directory exists
|
|
123
|
+
if (!existsSync(ORKIFY_HOME)) {
|
|
124
|
+
mkdirSync(ORKIFY_HOME, { recursive: true });
|
|
125
|
+
}
|
|
126
|
+
// Acquire exclusive PID lock — throws if another daemon is running
|
|
127
|
+
const pidLockFd = acquirePidLock();
|
|
128
|
+
const orchestrator = new Orchestrator();
|
|
129
|
+
const cronScheduler = new CronScheduler(orchestrator);
|
|
130
|
+
const server = new DaemonServer();
|
|
131
|
+
const configStore = new ConfigStore();
|
|
132
|
+
const alertEvaluator = new AlertEvaluator(configStore);
|
|
133
|
+
let telemetry = null;
|
|
134
|
+
const apiKey = process.env.ORKIFY_API_KEY;
|
|
135
|
+
if (apiKey) {
|
|
136
|
+
const apiHost = process.env.ORKIFY_API_HOST || TELEMETRY_DEFAULT_API_HOST;
|
|
137
|
+
const config = { apiKey, apiHost };
|
|
138
|
+
telemetry = new TelemetryReporter(config, orchestrator, configStore, alertEvaluator);
|
|
139
|
+
telemetry.start();
|
|
140
|
+
const poller = new CommandPoller(config, orchestrator, telemetry);
|
|
141
|
+
poller.start();
|
|
142
|
+
console.log(`Telemetry enabled → ${apiHost}`);
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
console.log('Telemetry disabled (no ORKIFY_API_KEY)');
|
|
146
|
+
}
|
|
147
|
+
// Evaluate alert rules every second
|
|
148
|
+
const alertInterval = setInterval(() => {
|
|
149
|
+
const processList = orchestrator.list();
|
|
150
|
+
const now = Date.now();
|
|
151
|
+
alertEvaluator.evaluate(processList.map((p) => ({
|
|
152
|
+
processName: p.name,
|
|
153
|
+
processId: p.id,
|
|
154
|
+
execMode: p.execMode,
|
|
155
|
+
status: p.status,
|
|
156
|
+
workers: p.workers.map((w) => ({
|
|
157
|
+
id: w.id,
|
|
158
|
+
pid: w.pid,
|
|
159
|
+
cpu: w.cpu,
|
|
160
|
+
memory: w.memory,
|
|
161
|
+
uptime: w.uptime,
|
|
162
|
+
restarts: w.restarts,
|
|
163
|
+
crashes: w.crashes,
|
|
164
|
+
status: w.status,
|
|
165
|
+
stale: w.stale,
|
|
166
|
+
heapUsed: w.heapUsed,
|
|
167
|
+
heapTotal: w.heapTotal,
|
|
168
|
+
external: w.external,
|
|
169
|
+
arrayBuffers: w.arrayBuffers,
|
|
170
|
+
eventLoopLag: w.eventLoopLag,
|
|
171
|
+
eventLoopLagP95: w.eventLoopLagP95,
|
|
172
|
+
activeHandles: w.activeHandles,
|
|
173
|
+
})),
|
|
174
|
+
timestamp: now,
|
|
175
|
+
})));
|
|
176
|
+
}, 1000);
|
|
177
|
+
alertInterval.unref();
|
|
178
|
+
// MCP HTTP server state
|
|
179
|
+
let mcpServer = null;
|
|
180
|
+
let mcpOptions = null;
|
|
181
|
+
// Forward logs to connected clients
|
|
182
|
+
orchestrator.on('log', (data) => {
|
|
183
|
+
server.broadcastLog(data.processName, data);
|
|
184
|
+
});
|
|
185
|
+
// Unregister cron jobs when a process is stopped or deleted
|
|
186
|
+
orchestrator.on('process:stop', ({ processName }) => {
|
|
187
|
+
cronScheduler.unregister(processName);
|
|
188
|
+
});
|
|
189
|
+
// Register handlers
|
|
190
|
+
server.registerHandler(IPCMessageType.UP, async (request) => {
|
|
191
|
+
if (foreground) {
|
|
192
|
+
// In foreground mode, block additional processes
|
|
193
|
+
const existingProcesses = orchestrator.list();
|
|
194
|
+
if (existingProcesses.length > 0) {
|
|
195
|
+
return createResponse(request.id, false, undefined, 'Daemon is in foreground mode — use `orkify run` for additional processes or switch to daemon mode with `orkify up`.');
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
const payload = request.payload;
|
|
199
|
+
const info = await orchestrator.up(payload);
|
|
200
|
+
if (payload.cron?.length) {
|
|
201
|
+
cronScheduler.register(payload.name ?? info.name, payload.cron);
|
|
202
|
+
}
|
|
203
|
+
return createResponse(request.id, true, info);
|
|
204
|
+
});
|
|
205
|
+
server.registerHandler(IPCMessageType.DOWN, async (request) => {
|
|
206
|
+
const payload = request.payload;
|
|
207
|
+
const results = await orchestrator.down(payload.target);
|
|
208
|
+
return createResponse(request.id, true, results);
|
|
209
|
+
});
|
|
210
|
+
server.registerHandler(IPCMessageType.RESTART, async (request) => {
|
|
211
|
+
const payload = request.payload;
|
|
212
|
+
const results = await orchestrator.restart(payload.target);
|
|
213
|
+
return createResponse(request.id, true, results);
|
|
214
|
+
});
|
|
215
|
+
server.registerHandler(IPCMessageType.RELOAD, async (request) => {
|
|
216
|
+
const payload = request.payload;
|
|
217
|
+
const results = await orchestrator.reload(payload.target);
|
|
218
|
+
return createResponse(request.id, true, results);
|
|
219
|
+
});
|
|
220
|
+
server.registerHandler(IPCMessageType.DELETE, async (request) => {
|
|
221
|
+
const payload = request.payload;
|
|
222
|
+
const results = await orchestrator.delete(payload.target);
|
|
223
|
+
for (const info of results) {
|
|
224
|
+
cronScheduler.unregister(info.name);
|
|
225
|
+
}
|
|
226
|
+
return createResponse(request.id, true, results);
|
|
227
|
+
});
|
|
228
|
+
server.registerHandler(IPCMessageType.LIST, async (request) => {
|
|
229
|
+
const list = orchestrator.list();
|
|
230
|
+
return createResponse(request.id, true, list);
|
|
231
|
+
});
|
|
232
|
+
server.registerHandler(IPCMessageType.LOGS, async (request, client) => {
|
|
233
|
+
const payload = request.payload;
|
|
234
|
+
const target = payload.target !== undefined ? String(payload.target) : 'all';
|
|
235
|
+
if (payload.follow) {
|
|
236
|
+
server.subscribeToLogs(target, client, request.id);
|
|
237
|
+
return createResponse(request.id, true, { subscribed: true });
|
|
238
|
+
}
|
|
239
|
+
// Non-follow mode: read recent lines from log files on disk
|
|
240
|
+
const lines = payload.lines ?? 100;
|
|
241
|
+
const logs = await readLogFiles(target, lines);
|
|
242
|
+
return createResponse(request.id, true, { subscribed: false, logs });
|
|
243
|
+
});
|
|
244
|
+
server.registerHandler(IPCMessageType.SNAP, async (request) => {
|
|
245
|
+
const payload = request.payload;
|
|
246
|
+
await orchestrator.snap({
|
|
247
|
+
noEnv: payload?.noEnv,
|
|
248
|
+
file: payload?.file,
|
|
249
|
+
mcpOptions: mcpOptions ?? undefined,
|
|
250
|
+
});
|
|
251
|
+
return createResponse(request.id, true, { saved: true });
|
|
252
|
+
});
|
|
253
|
+
server.registerHandler(IPCMessageType.RESTORE, async (request) => {
|
|
254
|
+
const payload = request.payload;
|
|
255
|
+
const { processes, configs, mcpState } = await orchestrator.restoreFromSnapshot(payload?.file);
|
|
256
|
+
// Re-register cron jobs from restored configs
|
|
257
|
+
for (const config of configs) {
|
|
258
|
+
if (config.cron?.length) {
|
|
259
|
+
cronScheduler.register(config.name, config.cron);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
// Restore MCP HTTP server if it was running when snapshot was taken
|
|
263
|
+
if (mcpState && !mcpServer) {
|
|
264
|
+
try {
|
|
265
|
+
mcpServer = await startMcpHttpServer({
|
|
266
|
+
port: mcpState.port,
|
|
267
|
+
bind: mcpState.bind,
|
|
268
|
+
cors: mcpState.cors,
|
|
269
|
+
skipSignalHandlers: true,
|
|
270
|
+
});
|
|
271
|
+
mcpOptions = mcpState;
|
|
272
|
+
}
|
|
273
|
+
catch (err) {
|
|
274
|
+
console.error('Failed to restore MCP server:', err.message);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return createResponse(request.id, true, processes);
|
|
278
|
+
});
|
|
279
|
+
server.registerHandler(IPCMessageType.RESTORE_CONFIGS, async (request) => {
|
|
280
|
+
const configs = request.payload;
|
|
281
|
+
const results = await orchestrator.restoreFromMemory(configs);
|
|
282
|
+
// Re-register cron jobs from restored configs
|
|
283
|
+
for (const config of configs) {
|
|
284
|
+
if (config.cron?.length) {
|
|
285
|
+
cronScheduler.register(config.name, config.cron);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return createResponse(request.id, true, results);
|
|
289
|
+
});
|
|
290
|
+
server.registerHandler(IPCMessageType.DEPLOY_LOCAL, async (request, client) => {
|
|
291
|
+
const p = request.payload;
|
|
292
|
+
const version = Math.floor(Date.now() / 1000);
|
|
293
|
+
const cmd = {
|
|
294
|
+
type: 'deploy',
|
|
295
|
+
deployId: `local-${version}`,
|
|
296
|
+
targetId: 'local',
|
|
297
|
+
artifactId: `local-${version}`,
|
|
298
|
+
version,
|
|
299
|
+
sha256: '',
|
|
300
|
+
sizeBytes: 0,
|
|
301
|
+
downloadToken: '',
|
|
302
|
+
downloadUrl: '',
|
|
303
|
+
deployConfig: p.deployConfig,
|
|
304
|
+
};
|
|
305
|
+
const deployOptions = {
|
|
306
|
+
localTarball: p.tarballPath,
|
|
307
|
+
secrets: p.env ?? {},
|
|
308
|
+
skipTelemetry: true,
|
|
309
|
+
};
|
|
310
|
+
const config = {
|
|
311
|
+
apiKey: apiKey ?? '',
|
|
312
|
+
apiHost: process.env.ORKIFY_API_HOST || TELEMETRY_DEFAULT_API_HOST,
|
|
313
|
+
};
|
|
314
|
+
const executor = new DeployExecutor(config, orchestrator, telemetry, cmd, deployOptions);
|
|
315
|
+
executor.setProgressCallback((phase) => {
|
|
316
|
+
client.send({
|
|
317
|
+
id: request.id,
|
|
318
|
+
type: IPCMessageType.DEPLOY_PROGRESS,
|
|
319
|
+
success: true,
|
|
320
|
+
data: { phase },
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
executor.setOutputCallback((line) => {
|
|
324
|
+
client.send({
|
|
325
|
+
id: request.id,
|
|
326
|
+
type: IPCMessageType.DEPLOY_PROGRESS,
|
|
327
|
+
success: true,
|
|
328
|
+
data: { output: line },
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
const result = await executor.execute();
|
|
332
|
+
if (!result.success) {
|
|
333
|
+
return createResponse(request.id, false, undefined, result.error);
|
|
334
|
+
}
|
|
335
|
+
return createResponse(request.id, true, { deployed: true });
|
|
336
|
+
});
|
|
337
|
+
server.registerHandler(IPCMessageType.DEPLOY_RESTORE, async (request, client) => {
|
|
338
|
+
const p = request.payload;
|
|
339
|
+
if (p.downloadUrl) {
|
|
340
|
+
// New version available — run full deploy
|
|
341
|
+
const cmd = {
|
|
342
|
+
type: 'deploy',
|
|
343
|
+
deployId: `restore-${p.version}`,
|
|
344
|
+
targetId: 'restore',
|
|
345
|
+
artifactId: p.artifactId,
|
|
346
|
+
version: p.version,
|
|
347
|
+
sha256: p.sha256,
|
|
348
|
+
sizeBytes: p.sizeBytes,
|
|
349
|
+
downloadToken: '',
|
|
350
|
+
downloadUrl: p.downloadUrl,
|
|
351
|
+
deployConfig: p.deployConfig,
|
|
352
|
+
};
|
|
353
|
+
const deployOptions = {
|
|
354
|
+
secrets: p.secrets,
|
|
355
|
+
skipTelemetry: !telemetry,
|
|
356
|
+
};
|
|
357
|
+
const config = {
|
|
358
|
+
apiKey: apiKey ?? '',
|
|
359
|
+
apiHost: process.env.ORKIFY_API_HOST || TELEMETRY_DEFAULT_API_HOST,
|
|
360
|
+
};
|
|
361
|
+
const executor = new DeployExecutor(config, orchestrator, telemetry, cmd, deployOptions);
|
|
362
|
+
executor.setProgressCallback((phase) => {
|
|
363
|
+
client.send({
|
|
364
|
+
id: request.id,
|
|
365
|
+
type: IPCMessageType.DEPLOY_PROGRESS,
|
|
366
|
+
success: true,
|
|
367
|
+
data: { phase },
|
|
368
|
+
});
|
|
369
|
+
});
|
|
370
|
+
executor.setOutputCallback((line) => {
|
|
371
|
+
client.send({
|
|
372
|
+
id: request.id,
|
|
373
|
+
type: IPCMessageType.DEPLOY_PROGRESS,
|
|
374
|
+
success: true,
|
|
375
|
+
data: { output: line },
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
const result = await executor.execute();
|
|
379
|
+
if (!result.success) {
|
|
380
|
+
return createResponse(request.id, false, undefined, result.error);
|
|
381
|
+
}
|
|
382
|
+
return createResponse(request.id, true, { deployed: true, version: p.version });
|
|
383
|
+
}
|
|
384
|
+
else {
|
|
385
|
+
// Current is latest — reconcile from local with secrets
|
|
386
|
+
const currentLink = join(ORKIFY_DEPLOYS_DIR, 'current');
|
|
387
|
+
const fileConfig = getOrkifyConfig(currentLink);
|
|
388
|
+
if (!fileConfig?.processes?.length) {
|
|
389
|
+
return createResponse(request.id, false, undefined, 'No processes defined in orkify.yml');
|
|
390
|
+
}
|
|
391
|
+
const configs = fileConfig.processes.map((c) => ({
|
|
392
|
+
...c,
|
|
393
|
+
script: join(currentLink, c.script),
|
|
394
|
+
cwd: currentLink,
|
|
395
|
+
}));
|
|
396
|
+
const result = await orchestrator.reconcile(configs, p.secrets);
|
|
397
|
+
// Register cron jobs for started/restarted processes
|
|
398
|
+
for (const config of configs) {
|
|
399
|
+
if (config.cron?.length) {
|
|
400
|
+
cronScheduler.register(config.name, config.cron);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
return createResponse(request.id, true, result);
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
server.registerHandler(IPCMessageType.CONFIGURE_TELEMETRY, async (request) => {
|
|
407
|
+
const payload = request.payload;
|
|
408
|
+
if (telemetry) {
|
|
409
|
+
// Already configured — nothing to do
|
|
410
|
+
return createResponse(request.id, true, { configured: false, reason: 'already_active' });
|
|
411
|
+
}
|
|
412
|
+
if (!payload.apiKey) {
|
|
413
|
+
return createResponse(request.id, true, { configured: false, reason: 'no_key' });
|
|
414
|
+
}
|
|
415
|
+
const config = { apiKey: payload.apiKey, apiHost: payload.apiHost };
|
|
416
|
+
telemetry = new TelemetryReporter(config, orchestrator, configStore, alertEvaluator);
|
|
417
|
+
telemetry.start();
|
|
418
|
+
// If MCP advanced-http is pending, mark capability
|
|
419
|
+
if (mcpOptions?.transport === 'advanced-http') {
|
|
420
|
+
telemetry.setMcpCapable(true);
|
|
421
|
+
}
|
|
422
|
+
const poller = new CommandPoller(config, orchestrator, telemetry);
|
|
423
|
+
poller.start();
|
|
424
|
+
// Store in process.env so KILL_DAEMON can forward them to the next daemon
|
|
425
|
+
process.env.ORKIFY_API_KEY = payload.apiKey;
|
|
426
|
+
process.env.ORKIFY_API_HOST = payload.apiHost;
|
|
427
|
+
console.log(`Telemetry configured at runtime → ${payload.apiHost}`);
|
|
428
|
+
return createResponse(request.id, true, { configured: true });
|
|
429
|
+
});
|
|
430
|
+
server.registerHandler(IPCMessageType.KILL_DAEMON, async (request) => {
|
|
431
|
+
const payload = request.payload;
|
|
432
|
+
const force = payload?.force === true;
|
|
433
|
+
// Schedule shutdown after sending response
|
|
434
|
+
setTimeout(async () => {
|
|
435
|
+
if (force) {
|
|
436
|
+
// Remove PID file + socket first, then kill everything.
|
|
437
|
+
cleanup();
|
|
438
|
+
if (!foreground) {
|
|
439
|
+
if (process.platform === 'win32') {
|
|
440
|
+
// Kill the entire process tree on Windows
|
|
441
|
+
execSync(`taskkill /T /F /PID ${process.pid}`, { stdio: 'ignore' });
|
|
442
|
+
}
|
|
443
|
+
else {
|
|
444
|
+
// Kill the entire process group (daemon + all children + grandchildren)
|
|
445
|
+
// in one syscall. The daemon is the group leader (spawned detached).
|
|
446
|
+
process.kill(-process.pid, 'SIGKILL');
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
else {
|
|
451
|
+
await gracefulShutdown({ persistCache: true });
|
|
452
|
+
if (!foreground) {
|
|
453
|
+
process.exit(0);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}, 100);
|
|
457
|
+
// Return telemetry env vars + process configs + MCP state so daemon-reload can restore
|
|
458
|
+
const env = {};
|
|
459
|
+
if (process.env.ORKIFY_API_KEY)
|
|
460
|
+
env.ORKIFY_API_KEY = process.env.ORKIFY_API_KEY;
|
|
461
|
+
if (process.env.ORKIFY_API_HOST)
|
|
462
|
+
env.ORKIFY_API_HOST = process.env.ORKIFY_API_HOST;
|
|
463
|
+
const processes = orchestrator.getRunningConfigs();
|
|
464
|
+
return createResponse(request.id, true, {
|
|
465
|
+
killing: true,
|
|
466
|
+
env,
|
|
467
|
+
processes,
|
|
468
|
+
mcpOptions: mcpOptions ?? undefined,
|
|
469
|
+
});
|
|
470
|
+
});
|
|
471
|
+
server.registerHandler(IPCMessageType.PING, async (request) => {
|
|
472
|
+
return createResponse(request.id, true, { pong: true, status: orchestrator.getDaemonStatus() });
|
|
473
|
+
});
|
|
474
|
+
server.registerHandler(IPCMessageType.FLUSH, async (request) => {
|
|
475
|
+
const payload = request.payload;
|
|
476
|
+
await orchestrator.flushLogs(payload.target);
|
|
477
|
+
return createResponse(request.id, true, { flushed: true });
|
|
478
|
+
});
|
|
479
|
+
server.registerHandler(IPCMessageType.MCP_START, async (request) => {
|
|
480
|
+
const payload = request.payload;
|
|
481
|
+
// Validate transport type
|
|
482
|
+
if (payload.transport !== 'simple-http' && payload.transport !== 'advanced-http') {
|
|
483
|
+
return createResponse(request.id, false, undefined, `Unknown MCP transport: "${payload.transport}"`);
|
|
484
|
+
}
|
|
485
|
+
// advanced-http: don't start server immediately — wait for config sync
|
|
486
|
+
if (payload.transport === 'advanced-http') {
|
|
487
|
+
mcpOptions = payload;
|
|
488
|
+
telemetry?.setMcpCapable(true);
|
|
489
|
+
console.log('MCP advanced-http registered — server will start after first config sync');
|
|
490
|
+
return createResponse(request.id, true, {
|
|
491
|
+
started: false,
|
|
492
|
+
waitingForConfig: true,
|
|
493
|
+
port: payload.port,
|
|
494
|
+
bind: payload.bind,
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
// Already running with same options → idempotent success
|
|
498
|
+
if (mcpServer && mcpOptions) {
|
|
499
|
+
if (mcpOptions.transport === payload.transport &&
|
|
500
|
+
mcpOptions.port === payload.port &&
|
|
501
|
+
mcpOptions.bind === payload.bind &&
|
|
502
|
+
mcpOptions.cors === payload.cors) {
|
|
503
|
+
return createResponse(request.id, true, {
|
|
504
|
+
started: false,
|
|
505
|
+
reason: 'already_running',
|
|
506
|
+
port: mcpOptions.port,
|
|
507
|
+
bind: mcpOptions.bind,
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
// Different options → stop old, start new.
|
|
511
|
+
// Stop first, then start. If the new one fails, the old one is gone.
|
|
512
|
+
const oldServer = mcpServer;
|
|
513
|
+
const oldOptions = mcpOptions;
|
|
514
|
+
try {
|
|
515
|
+
await oldServer.shutdown();
|
|
516
|
+
mcpServer = null;
|
|
517
|
+
mcpOptions = null;
|
|
518
|
+
mcpServer = await startMcpHttpServer({
|
|
519
|
+
port: payload.port,
|
|
520
|
+
bind: payload.bind,
|
|
521
|
+
cors: payload.cors,
|
|
522
|
+
skipSignalHandlers: true,
|
|
523
|
+
});
|
|
524
|
+
mcpOptions = payload;
|
|
525
|
+
return createResponse(request.id, true, {
|
|
526
|
+
started: true,
|
|
527
|
+
port: payload.port,
|
|
528
|
+
bind: payload.bind,
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
catch (err) {
|
|
532
|
+
// New server failed to start — try to restore the old one
|
|
533
|
+
try {
|
|
534
|
+
mcpServer = await startMcpHttpServer({
|
|
535
|
+
port: oldOptions.port,
|
|
536
|
+
bind: oldOptions.bind,
|
|
537
|
+
cors: oldOptions.cors,
|
|
538
|
+
skipSignalHandlers: true,
|
|
539
|
+
});
|
|
540
|
+
mcpOptions = oldOptions;
|
|
541
|
+
}
|
|
542
|
+
catch (restoreErr) {
|
|
543
|
+
console.error('MCP server is down — failed to restore previous config:', restoreErr.message);
|
|
544
|
+
}
|
|
545
|
+
throw err;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
mcpServer = await startMcpHttpServer({
|
|
549
|
+
port: payload.port,
|
|
550
|
+
bind: payload.bind,
|
|
551
|
+
cors: payload.cors,
|
|
552
|
+
skipSignalHandlers: true,
|
|
553
|
+
});
|
|
554
|
+
mcpOptions = payload;
|
|
555
|
+
return createResponse(request.id, true, {
|
|
556
|
+
started: true,
|
|
557
|
+
port: payload.port,
|
|
558
|
+
bind: payload.bind,
|
|
559
|
+
});
|
|
560
|
+
});
|
|
561
|
+
server.registerHandler(IPCMessageType.MCP_STOP, async (request) => {
|
|
562
|
+
if (!mcpServer) {
|
|
563
|
+
return createResponse(request.id, true, { stopped: false, reason: 'not_running' });
|
|
564
|
+
}
|
|
565
|
+
await mcpServer.shutdown();
|
|
566
|
+
mcpServer = null;
|
|
567
|
+
mcpOptions = null;
|
|
568
|
+
return createResponse(request.id, true, { stopped: true });
|
|
569
|
+
});
|
|
570
|
+
server.registerHandler(IPCMessageType.MCP_STATUS, async (request) => {
|
|
571
|
+
if (!mcpServer || !mcpOptions) {
|
|
572
|
+
return createResponse(request.id, true, { running: false });
|
|
573
|
+
}
|
|
574
|
+
return createResponse(request.id, true, {
|
|
575
|
+
running: true,
|
|
576
|
+
transport: mcpOptions.transport,
|
|
577
|
+
port: mcpOptions.port,
|
|
578
|
+
bind: mcpOptions.bind,
|
|
579
|
+
cors: mcpOptions.cors,
|
|
580
|
+
});
|
|
581
|
+
});
|
|
582
|
+
server.registerHandler(IPCMessageType.CRASH_TEST, async (request) => {
|
|
583
|
+
// Throw after responding so the uncaughtException handler triggers crash recovery.
|
|
584
|
+
setTimeout(() => {
|
|
585
|
+
throw new Error('CRASH_TEST trigger');
|
|
586
|
+
}, 100);
|
|
587
|
+
return createResponse(request.id, true, { crashing: true });
|
|
588
|
+
});
|
|
589
|
+
// Config-driven MCP lifecycle: start/stop MCP server based on remote config
|
|
590
|
+
configStore.on('config:updated', async (config, _prevMcp) => {
|
|
591
|
+
const mcpConfig = config.mcp;
|
|
592
|
+
if (mcpConfig.enabled && !mcpServer && mcpOptions?.transport === 'advanced-http') {
|
|
593
|
+
try {
|
|
594
|
+
mcpServer = await startMcpHttpServer({
|
|
595
|
+
port: mcpOptions.port,
|
|
596
|
+
bind: mcpOptions.bind,
|
|
597
|
+
cors: mcpOptions.cors,
|
|
598
|
+
skipSignalHandlers: true,
|
|
599
|
+
tokenVerifier: new RemoteConfigVerifier(configStore),
|
|
600
|
+
});
|
|
601
|
+
console.log(`MCP advanced-http server started on http://${mcpOptions.bind}:${mcpOptions.port}/mcp`);
|
|
602
|
+
}
|
|
603
|
+
catch (err) {
|
|
604
|
+
console.error('Failed to start MCP advanced-http server:', err.message);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
else if (!mcpConfig.enabled && mcpServer && mcpOptions?.transport === 'advanced-http') {
|
|
608
|
+
await mcpServer.shutdown();
|
|
609
|
+
mcpServer = null;
|
|
610
|
+
console.log('MCP advanced-http server stopped (disabled via config)');
|
|
611
|
+
}
|
|
612
|
+
// If keys changed while running: no restart needed, verifier reads from ConfigStore live
|
|
613
|
+
});
|
|
614
|
+
// Release PID lock and clean up socket
|
|
615
|
+
function cleanup() {
|
|
616
|
+
try {
|
|
617
|
+
closeSync(pidLockFd);
|
|
618
|
+
}
|
|
619
|
+
catch {
|
|
620
|
+
// Ignore — may already be closed
|
|
621
|
+
}
|
|
622
|
+
try {
|
|
623
|
+
if (existsSync(DAEMON_PID_FILE)) {
|
|
624
|
+
unlinkSync(DAEMON_PID_FILE);
|
|
625
|
+
}
|
|
626
|
+
if (existsSync(SOCKET_PATH)) {
|
|
627
|
+
unlinkSync(SOCKET_PATH);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
catch {
|
|
631
|
+
// Ignore cleanup errors
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
// Guard against concurrent shutdown sequences
|
|
635
|
+
let isShuttingDown = false;
|
|
636
|
+
let skipServerStop = false;
|
|
637
|
+
async function gracefulShutdown(opts) {
|
|
638
|
+
if (isShuttingDown)
|
|
639
|
+
return;
|
|
640
|
+
isShuttingDown = true;
|
|
641
|
+
try {
|
|
642
|
+
clearInterval(alertInterval);
|
|
643
|
+
cronScheduler.shutdown();
|
|
644
|
+
// Shut down MCP HTTP server first (before closing the IPC server)
|
|
645
|
+
if (mcpServer) {
|
|
646
|
+
await mcpServer.shutdown().catch((err) => {
|
|
647
|
+
console.error('MCP server shutdown error:', err.message);
|
|
648
|
+
});
|
|
649
|
+
mcpServer = null;
|
|
650
|
+
mcpOptions = null;
|
|
651
|
+
}
|
|
652
|
+
await telemetry?.shutdown();
|
|
653
|
+
await orchestrator.shutdown({ persistCache: opts?.persistCache });
|
|
654
|
+
if (!skipServerStop) {
|
|
655
|
+
// Remove PID file before closing the IPC server. The socket
|
|
656
|
+
// disappearing is the signal tests (and CLI) use to detect
|
|
657
|
+
// the daemon is gone, so the PID file must be gone first.
|
|
658
|
+
cleanup();
|
|
659
|
+
await server.stop();
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
catch (err) {
|
|
663
|
+
console.error('Error during graceful shutdown:', err.message);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
async function startMcpHttpFromPayload(opts) {
|
|
667
|
+
mcpServer = await startMcpHttpServer({
|
|
668
|
+
port: opts.port,
|
|
669
|
+
bind: opts.bind,
|
|
670
|
+
cors: opts.cors,
|
|
671
|
+
skipSignalHandlers: true,
|
|
672
|
+
});
|
|
673
|
+
mcpOptions = opts;
|
|
674
|
+
}
|
|
675
|
+
/** Allow the caller to mark that server.stop()/cleanup() should be skipped
|
|
676
|
+
* (e.g. because crash recovery already cleaned up the socket). */
|
|
677
|
+
function markSkipServerStop() {
|
|
678
|
+
skipServerStop = true;
|
|
679
|
+
}
|
|
680
|
+
return {
|
|
681
|
+
orchestrator,
|
|
682
|
+
cronScheduler,
|
|
683
|
+
server,
|
|
684
|
+
telemetry,
|
|
685
|
+
startMcpHttp: startMcpHttpFromPayload,
|
|
686
|
+
getMcpOptions: () => mcpOptions,
|
|
687
|
+
gracefulShutdown,
|
|
688
|
+
startServer: () => server.start(),
|
|
689
|
+
cleanup,
|
|
690
|
+
markSkipServerStop,
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
//# sourceMappingURL=startDaemon.js.map
|