Phase8
This commit is contained in:
158
packages/dns/src/sync.ts
Normal file
158
packages/dns/src/sync.ts
Normal file
@@ -0,0 +1,158 @@
|
||||
import { prisma } from '@hexahost/database';
|
||||
import type { GameNode, GameServer } from '@hexahost/database';
|
||||
|
||||
import { buildJoinSlug } from './slug';
|
||||
|
||||
export function getPlayDomain(): string {
|
||||
return process.env['GAME_BASE_DOMAIN'] ?? 'play.example.net';
|
||||
}
|
||||
|
||||
export function getEdgePublicHost(): string {
|
||||
return process.env['EDGE_PUBLIC_HOST'] ?? '127.0.0.1';
|
||||
}
|
||||
|
||||
export function getEdgeListenPort(): number {
|
||||
const raw = process.env['EDGE_LISTEN_PORT'] ?? '25565';
|
||||
const port = Number.parseInt(raw, 10);
|
||||
return Number.isFinite(port) ? port : 25565;
|
||||
}
|
||||
|
||||
function joinHostname(slug: string, playDomain = getPlayDomain()): string {
|
||||
return `${slug}.${playDomain}`;
|
||||
}
|
||||
|
||||
async function upsertDnsRecord(input: {
|
||||
serverId: string;
|
||||
fqdn: string;
|
||||
recordType: string;
|
||||
target: string;
|
||||
port?: number;
|
||||
srvPriority?: number;
|
||||
srvWeight?: number;
|
||||
}): Promise<void> {
|
||||
await prisma.serverDnsRecord.upsert({
|
||||
where: {
|
||||
serverId_recordType_fqdn: {
|
||||
serverId: input.serverId,
|
||||
recordType: input.recordType,
|
||||
fqdn: input.fqdn,
|
||||
},
|
||||
},
|
||||
create: {
|
||||
serverId: input.serverId,
|
||||
fqdn: input.fqdn,
|
||||
recordType: input.recordType,
|
||||
target: input.target,
|
||||
port: input.port,
|
||||
srvPriority: input.srvPriority,
|
||||
srvWeight: input.srvWeight,
|
||||
status: 'ACTIVE',
|
||||
lastSyncedAt: new Date(),
|
||||
},
|
||||
update: {
|
||||
target: input.target,
|
||||
port: input.port,
|
||||
srvPriority: input.srvPriority,
|
||||
srvWeight: input.srvWeight,
|
||||
status: 'ACTIVE',
|
||||
lastSyncedAt: new Date(),
|
||||
lastError: null,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function syncServerDnsRecords(
|
||||
server: GameServer & { node: GameNode | null },
|
||||
playDomain = getPlayDomain(),
|
||||
): Promise<void> {
|
||||
if (!server.joinSlug) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fqdn = joinHostname(server.joinSlug, playDomain);
|
||||
const edgeTarget = getEdgePublicHost();
|
||||
const edgePort = getEdgeListenPort();
|
||||
|
||||
await upsertDnsRecord({
|
||||
serverId: server.id,
|
||||
fqdn,
|
||||
recordType: 'A',
|
||||
target: edgeTarget,
|
||||
});
|
||||
|
||||
if (server.edition === 'JAVA') {
|
||||
await upsertDnsRecord({
|
||||
serverId: server.id,
|
||||
fqdn: `_minecraft._tcp.${fqdn}`,
|
||||
recordType: 'SRV',
|
||||
target: fqdn,
|
||||
port: edgePort,
|
||||
srvPriority: 0,
|
||||
srvWeight: 5,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureUniqueSlug(serverId: string, serverName: string): Promise<string> {
|
||||
let slug = buildJoinSlug(serverName, serverId);
|
||||
let attempt = 0;
|
||||
|
||||
while (
|
||||
await prisma.gameServer.findFirst({
|
||||
where: { joinSlug: slug, NOT: { id: serverId } },
|
||||
select: { id: true },
|
||||
})
|
||||
) {
|
||||
attempt += 1;
|
||||
slug = `${buildJoinSlug(serverName, serverId)}-${attempt}`;
|
||||
}
|
||||
|
||||
return slug;
|
||||
}
|
||||
|
||||
export async function assignJoinAddress(
|
||||
serverId: string,
|
||||
options?: { playDomain?: string },
|
||||
): Promise<{ joinSlug: string; joinHostname: string } | null> {
|
||||
const playDomain = options?.playDomain ?? getPlayDomain();
|
||||
|
||||
const server = await prisma.gameServer.findUnique({
|
||||
where: { id: serverId },
|
||||
include: { node: true },
|
||||
});
|
||||
|
||||
if (!server || !server.nodeId || server.hostPort === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const joinSlug = server.joinSlug ?? (await ensureUniqueSlug(serverId, server.name));
|
||||
|
||||
const updated =
|
||||
server.joinSlug === joinSlug
|
||||
? server
|
||||
: await prisma.gameServer.update({
|
||||
where: { id: serverId },
|
||||
data: { joinSlug },
|
||||
include: { node: true },
|
||||
});
|
||||
|
||||
await syncServerDnsRecords(updated, playDomain);
|
||||
|
||||
return {
|
||||
joinSlug,
|
||||
joinHostname: joinHostname(joinSlug, playDomain),
|
||||
};
|
||||
}
|
||||
|
||||
export function formatJoinAddress(
|
||||
joinSlug: string | null,
|
||||
edition: GameServer['edition'],
|
||||
playDomain = getPlayDomain(),
|
||||
): string | null {
|
||||
if (!joinSlug) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const hostname = joinHostname(joinSlug, playDomain);
|
||||
return edition === 'JAVA' ? hostname : `${hostname}:${getEdgeListenPort()}`;
|
||||
}
|
||||
Reference in New Issue
Block a user