CNAME-bindings отдавали пустой ips[]; теперь IP берутся из локальной цепочки bindings, DNS resolve4 или IP сервиса — чтобы vps-tracker мог матчить домен к VPS. Co-authored-by: Cursor <cursoragent@cursor.com>
73 lines
2.5 KiB
TypeScript
73 lines
2.5 KiB
TypeScript
import { timingSafeEqual } from "node:crypto";
|
|
import type { FastifyInstance } from "fastify";
|
|
import { vpsTrackerEventSchema } from "@cfdm/shared";
|
|
import { getAppSettingsSecrets } from "@cfdm/db";
|
|
import * as vpsTrackerEvents from "../services/vps-tracker-events.js";
|
|
import { buildAllSyncBindings } from "../services/vps-tracker-sync.js";
|
|
|
|
function verifyBearer(authHeader: string | undefined, expected: string): boolean {
|
|
if (!authHeader?.startsWith("Bearer ")) return false;
|
|
const token = authHeader.slice(7);
|
|
if (!token || !expected) return false;
|
|
const a = Buffer.from(token);
|
|
const b = Buffer.from(expected);
|
|
if (a.length !== b.length) return false;
|
|
return timingSafeEqual(a, b);
|
|
}
|
|
|
|
export async function integrationsVpsTrackerRoutes(app: FastifyInstance) {
|
|
app.post("/integrations/vps-tracker/events", async (req, reply) => {
|
|
const secrets = getAppSettingsSecrets(app.db);
|
|
const token = secrets.vpsTrackerIntegrationToken;
|
|
if (!token) {
|
|
return reply.status(503).send({ error: "Integration not configured" });
|
|
}
|
|
if (!verifyBearer(req.headers.authorization, token)) {
|
|
return reply.status(401).send({ error: "Unauthorized" });
|
|
}
|
|
|
|
const parsed = vpsTrackerEventSchema.safeParse(req.body);
|
|
if (!parsed.success) {
|
|
return reply.status(400).send({ error: parsed.error.flatten() });
|
|
}
|
|
|
|
if (parsed.data.event !== "vps_down") {
|
|
return { ok: true, reconciled: 0 };
|
|
}
|
|
|
|
const reconciled = await vpsTrackerEvents.reconcileForVpsDown(
|
|
app.db,
|
|
app.cf,
|
|
parsed.data,
|
|
);
|
|
return { ok: true, reconciled };
|
|
});
|
|
|
|
/**
|
|
* Полная синхронизация по запросу VPS Tracker (pull).
|
|
* Bindings возвращаются в ответе — клиент применяет их локально.
|
|
* Не требует доступности VPS Tracker URL из CFDM.
|
|
*/
|
|
app.post("/integrations/vps-tracker/sync", async (req, reply) => {
|
|
const secrets = getAppSettingsSecrets(app.db);
|
|
const token = secrets.vpsTrackerIntegrationToken;
|
|
if (!token) {
|
|
return reply.status(503).send({
|
|
ok: false,
|
|
error: "Integration not configured",
|
|
});
|
|
}
|
|
if (!verifyBearer(req.headers.authorization, token)) {
|
|
return reply.status(401).send({ ok: false, error: "Unauthorized" });
|
|
}
|
|
|
|
const bindings = await buildAllSyncBindings(app.db);
|
|
return {
|
|
ok: true,
|
|
count: bindings.length,
|
|
bindings,
|
|
fullSync: true,
|
|
};
|
|
});
|
|
}
|