feat: provision register command and k3s kubeconfig merge
Some checks failed
CI/CD / lint (pull_request) Failing after 11s
CI/CD / test (pull_request) Failing after 11s
CI/CD / typecheck (pull_request) Failing after 22s
CI/CD / build (pull_request) Has been skipped
CI/CD / publish-rpm (pull_request) Has been skipped
CI/CD / publish-deb (pull_request) Has been skipped

Add `labctl provision register` to re-add machines to installed state
without reprovisioning (e.g. after bastion state loss). Full stack:
protocol type, bastion API + WS handler, labd route, CLI command.

Add `labctl app k3s kubeconfig <target>` to fetch kubeconfig from a
k3s node via SSH, rewrite server URL, and merge into ~/.kube/config.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Michal
2026-03-31 01:15:31 +01:00
parent 8635da08a6
commit 49d747db98
11 changed files with 250 additions and 5 deletions

View File

@@ -172,6 +172,43 @@ export function registerBastionRoutes(app: FastifyInstance, db: DbClient): void
}
});
// Register an already-installed machine — route to correct bastion (or single bastion)
app.post<{
Body: { mac?: string; hostname?: string; role?: string; ip?: string };
}>("/api/machines/register", async (request, reply) => {
const { mac, hostname, role, ip } = request.body ?? {};
if (!mac || !hostname) {
return reply.code(400).send({ error: "mac and hostname are required" });
}
const normalized = mac.toLowerCase().replace(/-/g, ":");
// Find bastion that knows this MAC, or use single connected bastion
const bastion = bastionRegistry.findBastionByMac(normalized);
const target = bastion ?? (bastionRegistry.getAll().length === 1 ? bastionRegistry.getAll()[0] : null);
if (!target) {
const all = bastionRegistry.getAll();
if (all.length === 0) {
return reply.code(503).send({ error: "No bastions connected" });
}
return reply.code(404).send({ error: `MAC ${normalized} not found on any bastion and multiple bastions connected` });
}
try {
const result = await sendCommand(target.bastionId, {
type: "command-register",
mac: normalized,
hostname,
role: role ?? "worker",
ip: ip ?? "",
});
return reply.code(result.status === "ok" ? 200 : 500).send(result);
} catch (err) {
return reply.code(500).send({ error: err instanceof Error ? err.message : String(err) });
}
});
// Queue debug/rescue mode — route to correct bastion by MAC
app.post<{
Body: { mac?: string; pxeBoot?: boolean };