Add resilient dual-radio network failover

This commit is contained in:
2026-07-31 20:24:34 +02:00
parent 0a0dae144c
commit 6940cfb3a8
19 changed files with 857 additions and 11 deletions
+36
View File
@@ -19,6 +19,7 @@ import {
} from "./security.js";
import { collectSystemStatus } from "./status.js";
import { readUpdateStatus, triggerSystemUpdate } from "./update.js";
import { networkActionSchema, readNetworkStatus, requestNetworkAction } from "./network.js";
import { collectAdbStatus } from "./adb.js";
import "./types.js";
@@ -313,6 +314,41 @@ export async function buildApp(config: AppConfig): Promise<FastifyInstance> {
return reply.code(202).send(run);
});
app.get("/api/network", async (request, reply) => {
if (!requireUser(request, reply)) return;
return readNetworkStatus(config);
});
app.post("/api/network/actions", async (request, reply) => {
if (!requireUser(request, reply)) return;
const parsed = z.object({ action: networkActionSchema }).safeParse(request.body);
if (!parsed.success) {
return reply.code(400).send({ error: { code: "INVALID_NETWORK_ACTION", message: "That network action is not available" } });
}
try {
const command = await requestNetworkAction(config, parsed.data.action, request.authUser!.id);
audit(database, {
actorUserId: request.authUser!.id,
action: `network.${parsed.data.action}`,
result: "success",
ipAddress: clientAddress(request),
details: { commandId: command.id }
});
return reply.code(202).send({ accepted: true, commandId: command.id });
} catch {
audit(database, {
actorUserId: request.authUser!.id,
action: `network.${parsed.data.action}`,
result: "failure",
ipAddress: clientAddress(request),
details: { reason: "controller_unavailable" }
});
return reply.code(503).send({
error: { code: "NETWORK_CONTROL_UNAVAILABLE", message: "Automatic network management is not configured" }
});
}
});
app.get("/api/system/update", async (request, reply) => {
if (!requireUser(request, reply)) return;
return readUpdateStatus(config);