diff --git a/src/api/provisionAgent.js b/src/api/provisionAgent.js index f0b9186..92ba5f4 100644 --- a/src/api/provisionAgent.js +++ b/src/api/provisionAgent.js @@ -43,6 +43,7 @@ const AGENT_TEMPLATE_VMID = Number( const AGENT_PORT = Number(process.env.ZLH_AGENT_PORT || 18888); const AGENT_TOKEN = process.env.ZLH_AGENT_TOKEN || null; +const ZONE = process.env.TECHNITIUM_ZONE || "zerolaghub.quest"; /* ------------------------------------------------------------- VERSION PARSER (Minecraft only) @@ -107,7 +108,7 @@ function generateAdminPassword() { } /* ------------------------------------------------------------- - GAME PAYLOAD (UNCHANGED) + GAME PAYLOAD ------------------------------------------------------------- */ function buildGameAgentPayload({ vmid, @@ -186,9 +187,9 @@ function buildGameAgentPayload({ } /* ------------------------------------------------------------- - DEV PAYLOAD (NEW, MINIMAL, CANONICAL) + DEV PAYLOAD ------------------------------------------------------------- */ -function buildDevAgentPayload({ vmid, runtime, version, memoryMiB }) { +function buildDevAgentPayload({ vmid, runtime, version, memoryMiB, ports }) { if (!runtime) throw new Error("runtime required for dev container"); if (!version) throw new Error("version required for dev container"); @@ -198,6 +199,7 @@ function buildDevAgentPayload({ vmid, runtime, version, memoryMiB }) { runtime, version, memory_mb: Number(memoryMiB) || 2048, + ports: Array.isArray(ports) ? ports : [ports].filter(Boolean), }; } @@ -283,10 +285,35 @@ export async function provisionAgentInstance(body = {}) { let vmid; let ctIp; + let allocatedPorts = []; + let txnId = null; try { vmid = await allocateVmid(ctype); + // Allocate ports if needed + if (req.portsNeeded && req.portsNeeded > 0) { + console.log(`[agentProvision] Allocating ${req.portsNeeded} port(s)`); + txnId = crypto.randomUUID(); + + const portObjs = await PortAllocationService.reserve({ + game: req.game, + variant: req.variant, + customerId: req.customerId, + vmid, + purpose: ctype === 'game' ? 'game_main' : 'dev', + txnId, + count: req.portsNeeded, + }); + + // Extract port numbers from objects + allocatedPorts = Array.isArray(portObjs) + ? portObjs.map(p => typeof p === 'object' ? p.port : p) + : [portObjs]; + + console.log(`[agentProvision] Allocated ports: ${allocatedPorts.join(', ')}`); + } + const hostname = generateSystemHostname({ ctype, game: req.game, @@ -294,6 +321,9 @@ export async function provisionAgentInstance(body = {}) { vmid, }); + // Generate FQDN for DNS/EdgePublisher + const slotHostname = `${hostname}.${ZONE}`; + await cloneContainer({ templateVmid: AGENT_TEMPLATE_VMID, vmid, @@ -312,6 +342,7 @@ export async function provisionAgentInstance(body = {}) { ctIp = await getCtIpWithRetry(vmid); + // Build payload WITH ports const payload = ctype === "dev" ? buildDevAgentPayload({ @@ -319,10 +350,12 @@ export async function provisionAgentInstance(body = {}) { runtime: body.runtime, version: body.version, memoryMiB: req.memoryMiB, + ports: allocatedPorts, }) : buildGameAgentPayload({ vmid, ...req, + ports: allocatedPorts, }); await sendAgentConfig({ ip: ctIp, payload }); @@ -335,23 +368,47 @@ export async function provisionAgentInstance(body = {}) { ctype, hostname, ip: ctIp, + ports: allocatedPorts, payload, agentState: "running", agentLastSeen: new Date(), }, }); - await enqueuePublishEdge({ - vmid, - instanceHostname: hostname, - ctIp, - game: req.game, - }); + // Enqueue EdgePublisher with ALL required fields + if (allocatedPorts.length > 0) { + console.log(`[agentProvision] Enqueuing EdgePublisher for vmid=${vmid}`); + await enqueuePublishEdge({ + vmid, + slotHostname, // ← FQDN for DNS records + instanceHostname: hostname, // ← Short hostname + ports: allocatedPorts, // ← CRITICAL + ctIp, + game: req.game, + txnId, + }); + + // Commit ports to mark them as in-use + await PortAllocationService.commit({ vmid, ports: allocatedPorts }); + console.log(`[agentProvision] Ports committed for vmid=${vmid}`); + } await confirmVmidAllocated(vmid); - return { vmid, hostname, ip: ctIp }; + return { vmid, hostname, ip: ctIp, ports: allocatedPorts }; } catch (err) { + console.error(`[agentProvision] ERROR for vmid=${vmid}:`, err.message); + + // Rollback ports on failure + if (vmid && allocatedPorts.length > 0) { + try { + await PortAllocationService.releaseByVmid(vmid); + console.log(`[agentProvision] Rolled back ports for vmid=${vmid}`); + } catch (rollbackErr) { + console.error(`[agentProvision] Port rollback failed:`, rollbackErr.message); + } + } + if (vmid) { try { await deleteContainer(vmid); } catch {} try { await releaseVmid(vmid); } catch {}