Phase2
Some checks failed
CI / Node — lint, typecheck, test, build (push) Failing after 10s
CI / Go — node-agent tests (push) Failing after 8s
CI / Go — edge-gateway build (push) Successful in 13s

This commit is contained in:
smueller
2026-06-26 12:01:25 +02:00
parent 58961000eb
commit c4077d4673
93 changed files with 4099 additions and 165 deletions

View File

@@ -0,0 +1,75 @@
-- Phase 2: Game server lifecycle and node enrollment
-- New enums
DO $$ BEGIN
CREATE TYPE "ServerEdition" AS ENUM ('JAVA', 'BEDROCK');
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
DO $$ BEGIN
CREATE TYPE "SoftwareFamily" AS ENUM ('VANILLA', 'PAPER', 'PURPUR', 'FABRIC', 'FORGE', 'NEOFORGE', 'QUILT', 'BEDROCK_DEDICATED');
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
DO $$ BEGIN
CREATE TYPE "AllocationStatus" AS ENUM ('ACTIVE', 'RELEASED');
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
-- Extend GameNodeStatus
ALTER TYPE "GameNodeStatus" ADD VALUE IF NOT EXISTS 'UNREACHABLE';
-- Game servers: add columns
ALTER TABLE "game_servers" ADD COLUMN IF NOT EXISTS "edition" "ServerEdition" NOT NULL DEFAULT 'JAVA';
ALTER TABLE "game_servers" ADD COLUMN IF NOT EXISTS "softwareFamily" "SoftwareFamily" NOT NULL DEFAULT 'VANILLA';
ALTER TABLE "game_servers" ADD COLUMN IF NOT EXISTS "minecraftVersion" TEXT NOT NULL DEFAULT '1.21.1';
ALTER TABLE "game_servers" ADD COLUMN IF NOT EXISTS "eulaAccepted" BOOLEAN NOT NULL DEFAULT false;
ALTER TABLE "game_servers" ADD COLUMN IF NOT EXISTS "hostPort" INTEGER;
ALTER TABLE "game_servers" ADD COLUMN IF NOT EXISTS "containerId" TEXT;
ALTER TABLE "game_servers" ADD COLUMN IF NOT EXISTS "dataPath" TEXT;
ALTER TABLE "game_servers" ADD COLUMN IF NOT EXISTS "ramMb" INTEGER NOT NULL DEFAULT 1024;
-- Game nodes: enrollment + heartbeat
ALTER TABLE "game_nodes" ADD COLUMN IF NOT EXISTS "lastHeartbeatAt" TIMESTAMPTZ(3);
ALTER TABLE "game_nodes" ADD COLUMN IF NOT EXISTS "agentVersion" TEXT;
ALTER TABLE "game_nodes" ADD COLUMN IF NOT EXISTS "enrollmentTokenHash" TEXT;
ALTER TABLE "game_nodes" ADD COLUMN IF NOT EXISTS "enrolledAt" TIMESTAMPTZ(3);
-- State transitions: audit fields
ALTER TABLE "game_server_state_transitions" ADD COLUMN IF NOT EXISTS "correlationId" TEXT;
ALTER TABLE "game_server_state_transitions" ADD COLUMN IF NOT EXISTS "actorId" TEXT;
ALTER TABLE "game_server_state_transitions" ADD COLUMN IF NOT EXISTS "errorCode" TEXT;
ALTER TABLE "game_server_state_transitions" ADD COLUMN IF NOT EXISTS "errorMessage" TEXT;
-- Allocations
CREATE TABLE IF NOT EXISTS "game_server_allocations" (
"id" TEXT NOT NULL,
"serverId" TEXT NOT NULL,
"nodeId" TEXT NOT NULL,
"hostPort" INTEGER NOT NULL,
"ramMbReserved" INTEGER NOT NULL,
"status" "AllocationStatus" NOT NULL DEFAULT 'ACTIVE',
"createdAt" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMPTZ(3) NOT NULL,
CONSTRAINT "game_server_allocations_pkey" PRIMARY KEY ("id")
);
CREATE UNIQUE INDEX IF NOT EXISTS "game_server_allocations_serverId_key" ON "game_server_allocations"("serverId");
CREATE UNIQUE INDEX IF NOT EXISTS "game_server_allocations_nodeId_hostPort_key" ON "game_server_allocations"("nodeId", "hostPort");
CREATE INDEX IF NOT EXISTS "game_server_allocations_nodeId_idx" ON "game_server_allocations"("nodeId");
DO $$ BEGIN
ALTER TABLE "game_server_allocations" ADD CONSTRAINT "game_server_allocations_serverId_fkey" FOREIGN KEY ("serverId") REFERENCES "game_servers"("id") ON DELETE CASCADE ON UPDATE CASCADE;
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
DO $$ BEGIN
ALTER TABLE "game_server_allocations" ADD CONSTRAINT "game_server_allocations_nodeId_fkey" FOREIGN KEY ("nodeId") REFERENCES "game_nodes"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
-- Free plan seed
INSERT INTO "plans" ("id", "name", "slug", "description", "isFree", "isActive", "maxRamMb", "maxCpuCores", "maxStorageMb", "createdAt", "updatedAt")
SELECT gen_random_uuid()::text, 'Free', 'free', 'Free tier with limited resources', true, true, 2048, 1, 5120, NOW(), NOW()
WHERE NOT EXISTS (SELECT 1 FROM "plans" WHERE "slug" = 'free');

View File

@@ -13,14 +13,32 @@ enum UserStatus {
DELETED
}
enum ServerEdition {
JAVA
BEDROCK
}
enum SoftwareFamily {
VANILLA
PAPER
PURPUR
FABRIC
FORGE
NEOFORGE
QUILT
BEDROCK_DEDICATED
}
enum GameServerStatus {
PENDING
DRAFT
PROVISIONING
INSTALLING
STOPPED
STARTING
RUNNING
STOPPING
ERROR
DELETING
DELETED
}
@@ -29,6 +47,12 @@ enum GameNodeStatus {
OFFLINE
MAINTENANCE
DRAINING
UNREACHABLE
}
enum AllocationStatus {
ACTIVE
RELEASED
}
enum JobStatus {
@@ -51,14 +75,14 @@ model User {
createdAt DateTime @default(now()) @db.Timestamptz(3)
updatedAt DateTime @updatedAt @db.Timestamptz(3)
credentials UserCredential[]
sessions UserSession[]
platformRoles UserPlatformRole[]
gameServers GameServer[]
auditEvents AuditEvent[]
emailVerificationTokens EmailVerificationToken[]
passwordResetTokens PasswordResetToken[]
totpCredential TotpCredential?
credentials UserCredential[]
sessions UserSession[]
platformRoles UserPlatformRole[]
gameServers GameServer[]
auditEvents AuditEvent[]
emailVerificationTokens EmailVerificationToken[]
passwordResetTokens PasswordResetToken[]
totpCredential TotpCredential?
@@map("users")
}
@@ -77,15 +101,15 @@ model UserCredential {
}
model UserSession {
id String @id @default(uuid())
userId String
tokenHash String @unique
expiresAt DateTime @db.Timestamptz(3)
revokedAt DateTime? @db.Timestamptz(3)
ipAddress String?
userAgent String?
createdAt DateTime @default(now()) @db.Timestamptz(3)
updatedAt DateTime @updatedAt @db.Timestamptz(3)
id String @id @default(uuid())
userId String
tokenHash String @unique
expiresAt DateTime @db.Timestamptz(3)
revokedAt DateTime? @db.Timestamptz(3)
ipAddress String?
userAgent String?
createdAt DateTime @default(now()) @db.Timestamptz(3)
updatedAt DateTime @updatedAt @db.Timestamptz(3)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@ -180,20 +204,29 @@ model UserPlatformRole {
}
model GameServer {
id String @id @default(uuid())
userId String
planId String?
nodeId String?
name String
status GameServerStatus @default(PENDING)
version Int @default(1)
createdAt DateTime @default(now()) @db.Timestamptz(3)
updatedAt DateTime @updatedAt @db.Timestamptz(3)
id String @id @default(uuid())
userId String
planId String?
nodeId String?
name String
status GameServerStatus @default(DRAFT)
version Int @default(1)
edition ServerEdition @default(JAVA)
softwareFamily SoftwareFamily @default(VANILLA)
minecraftVersion String @default("1.21.1")
eulaAccepted Boolean @default(false)
hostPort Int?
containerId String?
dataPath String?
ramMb Int @default(1024)
createdAt DateTime @default(now()) @db.Timestamptz(3)
updatedAt DateTime @updatedAt @db.Timestamptz(3)
user User @relation(fields: [userId], references: [id])
plan Plan? @relation(fields: [planId], references: [id])
node GameNode? @relation(fields: [nodeId], references: [id])
stateTransitions GameServerStateTransition[]
allocation GameServerAllocation?
@@index([userId])
@@index([nodeId])
@@ -201,14 +234,36 @@ model GameServer {
@@map("game_servers")
}
model GameServerAllocation {
id String @id @default(uuid())
serverId String @unique
nodeId String
hostPort Int
ramMbReserved Int
status AllocationStatus @default(ACTIVE)
createdAt DateTime @default(now()) @db.Timestamptz(3)
updatedAt DateTime @updatedAt @db.Timestamptz(3)
server GameServer @relation(fields: [serverId], references: [id], onDelete: Cascade)
node GameNode @relation(fields: [nodeId], references: [id])
@@unique([nodeId, hostPort])
@@index([nodeId])
@@map("game_server_allocations")
}
model GameServerStateTransition {
id String @id @default(uuid())
gameServerId String
fromStatus GameServerStatus?
toStatus GameServerStatus
reason String?
metadata Json?
createdAt DateTime @default(now()) @db.Timestamptz(3)
id String @id @default(uuid())
gameServerId String
fromStatus GameServerStatus?
toStatus GameServerStatus
reason String?
correlationId String?
actorId String?
errorCode String?
errorMessage String?
metadata Json?
createdAt DateTime @default(now()) @db.Timestamptz(3)
gameServer GameServer @relation(fields: [gameServerId], references: [id], onDelete: Cascade)
@@ -218,18 +273,23 @@ model GameServerStateTransition {
}
model GameNode {
id String @id @default(uuid())
name String @unique
hostname String
status GameNodeStatus @default(OFFLINE)
maxServers Int @default(0)
activeServers Int @default(0)
metadata Json?
createdAt DateTime @default(now()) @db.Timestamptz(3)
updatedAt DateTime @updatedAt @db.Timestamptz(3)
id String @id @default(uuid())
name String @unique
hostname String
status GameNodeStatus @default(OFFLINE)
maxServers Int @default(10)
activeServers Int @default(0)
lastHeartbeatAt DateTime? @db.Timestamptz(3)
agentVersion String?
enrollmentTokenHash String?
enrolledAt DateTime? @db.Timestamptz(3)
metadata Json?
createdAt DateTime @default(now()) @db.Timestamptz(3)
updatedAt DateTime @updatedAt @db.Timestamptz(3)
gameServers GameServer[]
heartbeats GameNodeHeartbeat[]
gameServers GameServer[]
heartbeats GameNodeHeartbeat[]
allocations GameServerAllocation[]
@@map("game_nodes")
}
@@ -248,18 +308,18 @@ model GameNodeHeartbeat {
}
model Plan {
id String @id @default(uuid())
name String @unique
slug String @unique
description String?
isFree Boolean @default(false)
isActive Boolean @default(true)
maxRamMb Int
maxCpuCores Float
id String @id @default(uuid())
name String @unique
slug String @unique
description String?
isFree Boolean @default(false)
isActive Boolean @default(true)
maxRamMb Int
maxCpuCores Float
maxStorageMb Int
metadata Json?
createdAt DateTime @default(now()) @db.Timestamptz(3)
updatedAt DateTime @updatedAt @db.Timestamptz(3)
metadata Json?
createdAt DateTime @default(now()) @db.Timestamptz(3)
updatedAt DateTime @updatedAt @db.Timestamptz(3)
gameServers GameServer[]