Initial import of verae-fleet from zapier monorepo
This commit is contained in:
commit
f13e2fbf68
42 changed files with 3086 additions and 0 deletions
107
src/machines.js
Normal file
107
src/machines.js
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
/**
|
||||
* Host catalog: where replicas may run. Least-loaded placement.
|
||||
* @module machines
|
||||
*/
|
||||
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
export function defaultMachines() {
|
||||
return [
|
||||
{
|
||||
id: 'local',
|
||||
title: 'Control plane (this host)',
|
||||
kind: 'local',
|
||||
host: '127.0.0.1',
|
||||
enabled: true,
|
||||
capacity: 32,
|
||||
roles: ['*'],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function loadMachines(root, overlayPath) {
|
||||
const file = path.join(root, 'machines.json');
|
||||
let list = defaultMachines();
|
||||
if (fs.existsSync(file)) {
|
||||
const raw = JSON.parse(fs.readFileSync(file, 'utf8'));
|
||||
if (Array.isArray(raw.machines) && raw.machines.length) list = raw.machines;
|
||||
}
|
||||
if (overlayPath && fs.existsSync(overlayPath)) {
|
||||
const extra = JSON.parse(fs.readFileSync(overlayPath, 'utf8'));
|
||||
const incoming = Array.isArray(extra) ? extra : extra.machines || [];
|
||||
for (const m of incoming) upsertMachine(list, m);
|
||||
}
|
||||
const secretsPath = path.join(root, 'machines.secrets.json');
|
||||
if (fs.existsSync(secretsPath)) {
|
||||
const secrets = JSON.parse(fs.readFileSync(secretsPath, 'utf8'));
|
||||
const byId = secrets.machines || secrets;
|
||||
for (const m of list) {
|
||||
const extra = byId[m.id];
|
||||
if (extra && typeof extra === 'object') Object.assign(m, extra);
|
||||
}
|
||||
}
|
||||
return list.map(normalizeMachine);
|
||||
}
|
||||
|
||||
export function normalizeMachine(m) {
|
||||
const kind = m.kind === 'agent' || m.kind === 'ssh' ? m.kind : 'local';
|
||||
return {
|
||||
id: String(m.id || '').trim(),
|
||||
title: m.title || m.id,
|
||||
kind,
|
||||
host: m.host || '127.0.0.1',
|
||||
user: m.user || (kind === 'ssh' ? 'marchon' : ''),
|
||||
sshPort: Number(m.sshPort || 22),
|
||||
identityFile: m.identityFile || (kind === 'ssh' ? '~/.ssh/id_ed25519' : ''),
|
||||
remoteDir: m.remoteDir || '~/verae-fleet-runtime',
|
||||
agentPort: Number(m.agentPort || 3851),
|
||||
enabled: m.enabled !== false,
|
||||
capacity: Number(m.capacity || 8),
|
||||
roles: Array.isArray(m.roles) && m.roles.length ? m.roles : ['*'],
|
||||
notes: m.notes || '',
|
||||
};
|
||||
}
|
||||
|
||||
export function upsertMachine(list, spec) {
|
||||
const m = normalizeMachine(spec);
|
||||
if (!m.id) throw new Error('machine id required');
|
||||
const i = list.findIndex((x) => x.id === m.id);
|
||||
if (i >= 0) list[i] = { ...list[i], ...m };
|
||||
else list.push(m);
|
||||
return m;
|
||||
}
|
||||
|
||||
export function canHost(machine, serviceId, role) {
|
||||
if (!machine?.enabled) return false;
|
||||
const roles = machine.roles || ['*'];
|
||||
if (roles.includes('*')) return true;
|
||||
return roles.includes(serviceId) || (role && roles.includes(role));
|
||||
}
|
||||
|
||||
/**
|
||||
* Pick the enabled machine with the most free capacity (fewest running / capacity).
|
||||
* @param {object[]} machines
|
||||
* @param {Array<{ machine?: string }>} instances
|
||||
* @param {string} serviceId
|
||||
* @param {string} [role]
|
||||
*/
|
||||
export function pickMachine(machines, instances, serviceId, role, exclude = []) {
|
||||
const skip = new Set(exclude);
|
||||
const eligible = machines.filter((m) => canHost(m, serviceId, role) && !skip.has(m.id));
|
||||
if (!eligible.length) return null;
|
||||
const scored = eligible.map((m) => {
|
||||
const running = instances.filter((i) => i.machine === m.id && i.pid).length;
|
||||
const free = m.capacity - running;
|
||||
const load = m.capacity <= 0 ? 1 : running / m.capacity;
|
||||
return { m, running, free, load };
|
||||
});
|
||||
scored.sort((a, b) => a.load - b.load || a.running - b.running || a.m.id.localeCompare(b.m.id));
|
||||
const best = scored.find((s) => s.free > 0);
|
||||
return best ? best.m : null;
|
||||
}
|
||||
|
||||
export function saveOverlay(overlayPath, machines) {
|
||||
fs.mkdirSync(path.dirname(overlayPath), { recursive: true });
|
||||
fs.writeFileSync(overlayPath, JSON.stringify({ machines }, null, 2));
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue