Initial import of verae-fleet from zapier monorepo
This commit is contained in:
commit
5de9f586fe
45 changed files with 3208 additions and 0 deletions
86
src/agent.js
Normal file
86
src/agent.js
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* Remote host agent: spawn/kill fleet workers so the control plane can spread replicas.
|
||||
*
|
||||
* FLEET_AGENT_PORT=3851 node src/agent.js
|
||||
*
|
||||
* Bind loopback or a private interface. Do not publish NATS.
|
||||
*/
|
||||
import http from 'node:http';
|
||||
import { spawn } from 'node:child_process';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const WORKER = path.join(path.dirname(fileURLToPath(import.meta.url)), 'worker.js');
|
||||
const PORT = Number(process.env.FLEET_AGENT_PORT || 3851);
|
||||
const BIND = process.env.FLEET_AGENT_BIND || '127.0.0.1';
|
||||
const children = new Map();
|
||||
|
||||
function json(res, code, obj) {
|
||||
res.writeHead(code, { 'content-type': 'application/json' });
|
||||
res.end(JSON.stringify(obj));
|
||||
}
|
||||
|
||||
async function readBody(req) {
|
||||
const chunks = [];
|
||||
for await (const c of req) chunks.push(c);
|
||||
const raw = Buffer.concat(chunks).toString('utf8');
|
||||
return raw ? JSON.parse(raw) : {};
|
||||
}
|
||||
|
||||
export function startAgent({ port = PORT, bind = BIND } = {}) {
|
||||
const server = http.createServer(async (req, res) => {
|
||||
const url = new URL(req.url, `http://${bind}:${port}`);
|
||||
try {
|
||||
if (req.method === 'GET' && url.pathname === '/health') {
|
||||
return json(res, 200, {
|
||||
ok: true,
|
||||
role: 'fleet-agent',
|
||||
workers: [...children.keys()],
|
||||
});
|
||||
}
|
||||
if (req.method === 'POST' && url.pathname === '/spawn') {
|
||||
const body = await readBody(req);
|
||||
const instance = body.instance;
|
||||
if (!instance) return json(res, 400, { error: 'instance required' });
|
||||
if (children.has(instance)) {
|
||||
return json(res, 200, { pid: children.get(instance).pid, instance, existing: true });
|
||||
}
|
||||
const child = spawn(process.execPath, [WORKER], {
|
||||
env: { ...process.env, ...(body.env || {}) },
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
});
|
||||
children.set(instance, child);
|
||||
child.on('exit', () => children.delete(instance));
|
||||
return json(res, 200, { pid: child.pid, instance });
|
||||
}
|
||||
if (req.method === 'POST' && url.pathname === '/kill') {
|
||||
const body = await readBody(req);
|
||||
const child = children.get(body.instance);
|
||||
if (child) {
|
||||
child.kill('SIGTERM');
|
||||
children.delete(body.instance);
|
||||
}
|
||||
return json(res, 200, { killed: Boolean(child), instance: body.instance });
|
||||
}
|
||||
json(res, 404, { error: 'not found' });
|
||||
} catch (err) {
|
||||
json(res, 400, { error: err.message });
|
||||
}
|
||||
});
|
||||
server.listen(port, bind, () => {
|
||||
process.stdout.write(`fleet-agent http://${bind}:${port}/\n`);
|
||||
});
|
||||
return {
|
||||
server,
|
||||
async close() {
|
||||
for (const c of children.values()) c.kill('SIGTERM');
|
||||
children.clear();
|
||||
await new Promise((r) => server.close(r));
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (process.argv[1] && path.basename(process.argv[1]) === 'agent.js') {
|
||||
startAgent();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue