Initial import of verae-fleet from zapier monorepo

This commit is contained in:
George Lambert 2026-09-11 15:00:08 -04:00
commit f13e2fbf68
42 changed files with 3086 additions and 0 deletions

107
src/machines.js Normal file
View 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));
}