Migration 022 adds agent_hosts / game_instances / instance_clusters / instance_stats (named agent_hosts to avoid the existing B2B hosts table). HostAgentConsumerService now parses the full v2 heartbeat and upserts an agent_hosts row (host metrics: cpu/mem/disk/agent version, keyed by license_id+hostname until enrollment) plus one game_instances row per heartbeat instance entry (state + uptime, the billing unit). Legacy server_connections write retained so the current panel keeps working — additive migration, nothing breaks. Staleness sweep + offline beacon now flip agent_hosts too. cluster_id FK reserved for Soulmask/ Dune. Migration applied to live DB; tsc green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
103 lines
4.8 KiB
SQL
103 lines
4.8 KiB
SQL
-- 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);
|