zpack-api/src/services/velocityClient.js

139 lines
4.2 KiB
JavaScript

// src/services/velocityClient.js
// Handles dynamic backend registration with the Velocity ZpackVelocityBridge plugin.
import fetch from "node-fetch";
import crypto from "crypto";
import dotenv from "dotenv";
dotenv.config();
const VELOCITY_URL = process.env.VELOCITY_URL || "http://10.70.0.241:8081";
const SHARED_SECRET = process.env.ZPACK_SECRET;
function getSecretHash() {
return crypto.createHash("sha256").update(SHARED_SECRET).digest("hex");
}
/* -------------------------------------------------------------------------- */
/* EXISTENCE CHECK */
/* -------------------------------------------------------------------------- */
export async function serverExists(name) {
try {
const res = await fetch(`${VELOCITY_URL}/zpack/list`);
if (!res.ok) return false;
const data = await res.json();
return Array.isArray(data.servers) && data.servers.some(s => s.name === name);
} catch (err) {
console.error(`[velocityClient] ⚠️ Server existence check failed: ${err.message}`);
return false;
}
}
// --- Internal deduplication cache ---
const registrationCache = new Map();
/**
* Register a backend server with Velocity dynamically.
* Adds deduplication to prevent duplicate registration attempts within 10 seconds.
*
* @param {Object} params
* @param {string} params.name - FQDN or short server name
* @param {string} params.address - IP address of the backend container
* @param {number} params.port - Listening port (e.g. 25565)
*/
export async function registerServer({ name, address, port }) {
const key = `${name}:${address}:${port}`;
const now = Date.now();
// Debounce repeated registrations for the same backend
if (registrationCache.has(key) && now - registrationCache.get(key) < 10000) {
console.log(`[velocityClient] Skipping duplicate registration for ${key}`);
return "duplicate-skip";
}
registrationCache.set(key, now);
const payload = { server_name: name, address, port };
const secretHash = getSecretHash();
console.log(`[velocityClient] Registering backend: ${name} -> ${address}:${port}`);
console.log(`[velocityClient] Using Velocity URL: ${VELOCITY_URL}`);
const res = await fetch(`${VELOCITY_URL}/zpack/register`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Zpack-Secret": secretHash,
},
body: JSON.stringify(payload),
});
if (!res.ok) {
const text = await res.text();
throw new Error(`Velocity register failed (${res.status}): ${text}`);
}
const text = await res.text();
console.log(`[velocityClient] ✓ Velocity registered ${name}${address}:${port}`);
return text;
}
/**
* Unregister a backend server from Velocity dynamically.
* @param {string} name - Short hostname or FQDN
*/
export async function unregisterServer(name) {
// Convert short name → full FQDN if needed
let serverName = name;
const ZONE = process.env.CF_ZONE_NAME || "zerolaghub.quest";
if (!serverName.includes(".")) {
serverName = `${serverName}.${ZONE}`;
}
const payload = { server_name: serverName };
const secretHash = getSecretHash();
console.log(`[velocityClient] Unregistering backend: ${serverName}`);
let res;
try {
res = await fetch(`${VELOCITY_URL}/zpack/unregister`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Zpack-Secret": secretHash,
},
body: JSON.stringify(payload),
});
} catch (err) {
console.error(`[velocityClient] ⚠️ Velocity unreachable: ${err.message}`);
return false;
}
const text = await res.text();
// ---------------------------
// Idempotent delete:
// Velocity returns 404 when the backend is already removed → this is SUCCESS
// ---------------------------
if (res.status === 404) {
console.log(
`[velocityClient] ✓ Backend already removed (idempotent): ${serverName}`
);
return true;
}
if (!res.ok) {
console.error(
`[velocityClient] ❌ Velocity unregister failed (${res.status}): ${text}`
);
return false;
}
console.log(`[velocityClient] ✓ Velocity unregistered ${serverName}`);
return true;
}
export default { registerServer, unregisterServer };