// @ts-check
/**
* Connector-facing Runner API (/api/runner/v1/*) — Topologie-Endpunkte.
* Auth: X-Runner-Credential-Id + timestamp + signature (except register/exchange).
*
* Jobs/TLS/Preview-Records sind nach AP 1 (050-Migrationsplan) nicht mehr hier —
* die Mitglieder-Runtime liegt beim Zusteller (ide-server).
*
* @import {ExpressResponse} from '../../types.js'
*/
import { sendOk, sendErrorFrom } from '../../utils/apiEnvelope.js';
import * as registration from '../registration/service.js';
import * as runnerService from '../runner/service.js';
/**
* POST /api/runner/v1/register/exchange
* @param {import('express').Request} req
* @param {ExpressResponse} res
*/
export const exchange = async (req, res) => {
try {
const result = await registration.exchangeRegistration(req.body || {});
sendOk(res, result.result);
} catch (e) {
sendErrorFrom(res, e);
}
};
/**
* Middleware: authenticate runner credential.
*/
export const requireRunnerAuth = async (req, res, next) => {
try {
req.runnerAuth = await registration.authenticateRunner(req);
next();
} catch (e) {
sendErrorFrom(res, e);
}
};
/**
* POST /api/runner/v1/heartbeat — Topologie-Heartbeat in die Companion-Zeile.
*/
export const heartbeat = async (req, res) => {
try {
const { runnerUid, tenantUid } = req.runnerAuth;
await runnerService.upsertRuntime(runnerUid, tenantUid, {
capacity: req.body?.capacity,
heartbeat: {
version: req.body?.version,
status: req.body?.status,
at: new Date().toISOString(),
},
});
// Statische Meldungen (tailnet/capabilities) nur in ObjectBase.Data,
// wenn sie sich ändern; draining/revoked darf der Heartbeat nicht
// überschreiben (Topologie-Zustand gewinnt).
const meta = await runnerService.readRunnerMeta(runnerUid);
if (meta && meta.status !== 'draining' && meta.status !== 'revoked') {
await runnerService.patchRunnerData(runnerUid, runnerUid, {
...(req.body.tailnetIp ? { tailnetIp: req.body.tailnetIp } : {}),
...(req.body.tailnetNodeId ? { tailnetNodeId: req.body.tailnetNodeId } : {}),
...(req.body.capabilities ? { capabilities: req.body.capabilities } : {}),
...(req.body.architecture ? { architecture: req.body.architecture } : {}),
...(req.body.operatingSystem ? { operatingSystem: req.body.operatingSystem } : {}),
status: 'online',
});
}
sendOk(res, { ok: true });
} catch (e) {
sendErrorFrom(res, e);
}
};