-- Fleet data model — License → Host → Instance (with optional Cluster) -- -- ADDITIVE: existing server_connections / server_config / server_stats are -- left untouched so the current single-server panel keeps working. The -- host-agent consumer writes BOTH the legacy connection row and these fleet -- tables during the transition; the panel migrates to the fleet tables in a -- later phase. -- -- Shape mirrors the host agent's wire protocol v2 heartbeat: -- host{} block → agent_hosts -- instances[] entries → game_instances -- Host metrics (CPU/RAM/disk) live on the HOST, not duplicated per instance. -- -- Named `agent_hosts` (not `hosts`) to avoid collision with the existing B2B -- `hosts` table (hosting-partner companies) — different concept entirely. ----------------------------------------------------------- -- AGENT_HOSTS — one Corrosion host agent / one machine ----------------------------------------------------------- CREATE TABLE IF NOT EXISTS agent_hosts ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), license_id UUID NOT NULL REFERENCES licenses(id) ON DELETE CASCADE, -- Natural key until enrollment issues a stable host identity. hostname VARCHAR(255) NOT NULL DEFAULT '', agent_version VARCHAR(64), agent_commit VARCHAR(64), os VARCHAR(32), arch VARCHAR(32), status VARCHAR(20) NOT NULL DEFAULT 'offline' CHECK (status IN ('connected', 'degraded', 'offline')), last_heartbeat_at TIMESTAMPTZ, cpu_percent DOUBLE PRECISION, cpu_cores INTEGER, mem_total_mb BIGINT, mem_used_mb BIGINT, uptime_seconds BIGINT, disks JSONB, -- [{ "mount": "/", "total_mb": n, "free_mb": n }] created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), UNIQUE (license_id, hostname) ); CREATE INDEX IF NOT EXISTS idx_agent_hosts_license ON agent_hosts(license_id); ----------------------------------------------------------- -- INSTANCE CLUSTERS — optional grouping (Soulmask main/child, Dune battlegroup) -- Reserved now; cluster logic ships with those game adapters. ----------------------------------------------------------- CREATE TABLE IF NOT EXISTS instance_clusters ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), license_id UUID NOT NULL REFERENCES licenses(id) ON DELETE CASCADE, game VARCHAR(32) NOT NULL, name VARCHAR(255) NOT NULL, topology VARCHAR(32), -- main_client | battlegroup config JSONB, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_clusters_license ON instance_clusters(license_id); ----------------------------------------------------------- -- GAME INSTANCES — one game server process / orchestrated unit. -- The billing unit (plans count instances). ----------------------------------------------------------- CREATE TABLE IF NOT EXISTS game_instances ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), license_id UUID NOT NULL REFERENCES licenses(id) ON DELETE CASCADE, host_id UUID REFERENCES agent_hosts(id) ON DELETE SET NULL, cluster_id UUID REFERENCES instance_clusters(id) ON DELETE SET NULL, -- The agent's instance slug; the NATS subject segment. agent_instance_id VARCHAR(64) NOT NULL, game VARCHAR(32) NOT NULL, label VARCHAR(255), -- running | stopped | starting | stopping | crashed -- | configured | missing_root | unmanaged | unknown state VARCHAR(32) NOT NULL DEFAULT 'unknown', root_path TEXT, uptime_seconds BIGINT NOT NULL DEFAULT 0, last_seen_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), UNIQUE (license_id, agent_instance_id) ); CREATE INDEX IF NOT EXISTS idx_instances_license ON game_instances(license_id); CREATE INDEX IF NOT EXISTS idx_instances_host ON game_instances(host_id); ----------------------------------------------------------- -- INSTANCE STATS — per-instance time series (game metrics). -- Populated once game-level telemetry (player count/FPS via RCON/plugin) is -- collected; the host heartbeat carries host metrics, not game metrics. ----------------------------------------------------------- CREATE TABLE IF NOT EXISTS instance_stats ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), instance_id UUID NOT NULL REFERENCES game_instances(id) ON DELETE CASCADE, license_id UUID NOT NULL REFERENCES licenses(id) ON DELETE CASCADE, player_count INTEGER NOT NULL DEFAULT 0, max_players INTEGER NOT NULL DEFAULT 0, fps DOUBLE PRECISION NOT NULL DEFAULT 0, memory_usage_mb INTEGER NOT NULL DEFAULT 0, recorded_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_instance_stats_instance ON instance_stats(instance_id, recorded_at DESC);