187 lines
7.8 KiB
SQL
187 lines
7.8 KiB
SQL
CREATE TABLE IF NOT EXISTS clusters (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
slug TEXT NOT NULL UNIQUE,
|
|
name TEXT NOT NULL,
|
|
status TEXT NOT NULL DEFAULT 'active',
|
|
region TEXT,
|
|
metadata JSONB NOT NULL DEFAULT '{}'::JSONB,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
CONSTRAINT clusters_status_check
|
|
CHECK (status IN ('active', 'disabled', 'archived', 'degraded'))
|
|
);
|
|
|
|
INSERT INTO clusters (slug, name, status, region, metadata)
|
|
VALUES ('default', 'Default Cluster', 'active', NULL, '{"bootstrap":true}'::jsonb)
|
|
ON CONFLICT (slug) DO NOTHING;
|
|
|
|
CREATE TABLE IF NOT EXISTS cluster_memberships (
|
|
cluster_id UUID NOT NULL REFERENCES clusters(id) ON DELETE CASCADE,
|
|
node_id UUID NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
|
|
membership_status TEXT NOT NULL DEFAULT 'active',
|
|
joined_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
last_seen_at TIMESTAMPTZ,
|
|
metadata JSONB NOT NULL DEFAULT '{}'::JSONB,
|
|
PRIMARY KEY (cluster_id, node_id),
|
|
CONSTRAINT cluster_memberships_status_check
|
|
CHECK (membership_status IN ('active', 'draining', 'disabled', 'revoked'))
|
|
);
|
|
|
|
INSERT INTO cluster_memberships (cluster_id, node_id, membership_status, joined_at, last_seen_at, metadata)
|
|
SELECT c.id, n.id, 'active', COALESCE(n.created_at, NOW()), n.last_seen_at, '{"backfilled":true}'::jsonb
|
|
FROM clusters c
|
|
CROSS JOIN nodes n
|
|
WHERE c.slug = 'default'
|
|
ON CONFLICT (cluster_id, node_id) DO NOTHING;
|
|
|
|
CREATE TABLE IF NOT EXISTS node_identities (
|
|
node_id UUID PRIMARY KEY REFERENCES nodes(id) ON DELETE CASCADE,
|
|
public_key TEXT NOT NULL,
|
|
certificate_serial TEXT,
|
|
certificate_not_before TIMESTAMPTZ,
|
|
certificate_not_after TIMESTAMPTZ,
|
|
identity_status TEXT NOT NULL DEFAULT 'pending',
|
|
rotated_at TIMESTAMPTZ,
|
|
revoked_at TIMESTAMPTZ,
|
|
metadata JSONB NOT NULL DEFAULT '{}'::JSONB,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
CONSTRAINT node_identities_status_check
|
|
CHECK (identity_status IN ('pending', 'active', 'rotating', 'revoked'))
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS node_join_tokens (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
cluster_id UUID NOT NULL REFERENCES clusters(id) ON DELETE CASCADE,
|
|
token_hash TEXT NOT NULL UNIQUE,
|
|
scope JSONB NOT NULL DEFAULT '{}'::JSONB,
|
|
expires_at TIMESTAMPTZ NOT NULL,
|
|
max_uses INTEGER NOT NULL DEFAULT 1,
|
|
used_count INTEGER NOT NULL DEFAULT 0,
|
|
status TEXT NOT NULL DEFAULT 'active',
|
|
created_by_user_id UUID REFERENCES users(id) ON DELETE SET NULL,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
revoked_at TIMESTAMPTZ,
|
|
CONSTRAINT node_join_tokens_status_check
|
|
CHECK (status IN ('active', 'revoked', 'expired')),
|
|
CONSTRAINT node_join_tokens_max_uses_check
|
|
CHECK (max_uses > 0),
|
|
CONSTRAINT node_join_tokens_used_count_check
|
|
CHECK (used_count >= 0)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_node_join_tokens_cluster_status
|
|
ON node_join_tokens(cluster_id, status, expires_at);
|
|
|
|
CREATE TABLE IF NOT EXISTS node_join_requests (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
cluster_id UUID NOT NULL REFERENCES clusters(id) ON DELETE CASCADE,
|
|
join_token_id UUID REFERENCES node_join_tokens(id) ON DELETE SET NULL,
|
|
node_name TEXT NOT NULL,
|
|
node_fingerprint TEXT NOT NULL,
|
|
public_key TEXT NOT NULL,
|
|
reported_capabilities JSONB NOT NULL DEFAULT '{}'::JSONB,
|
|
reported_facts JSONB NOT NULL DEFAULT '{}'::JSONB,
|
|
requested_roles JSONB NOT NULL DEFAULT '[]'::JSONB,
|
|
status TEXT NOT NULL DEFAULT 'pending',
|
|
reviewed_by_user_id UUID REFERENCES users(id) ON DELETE SET NULL,
|
|
reviewed_at TIMESTAMPTZ,
|
|
approved_node_id UUID REFERENCES nodes(id) ON DELETE SET NULL,
|
|
rejection_reason TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
CONSTRAINT node_join_requests_status_check
|
|
CHECK (status IN ('pending', 'approved', 'rejected', 'cancelled'))
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_node_join_requests_cluster_status
|
|
ON node_join_requests(cluster_id, status, created_at DESC);
|
|
|
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_node_join_requests_pending_fingerprint
|
|
ON node_join_requests(cluster_id, node_fingerprint)
|
|
WHERE status = 'pending';
|
|
|
|
CREATE TABLE IF NOT EXISTS node_role_assignments (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
cluster_id UUID NOT NULL REFERENCES clusters(id) ON DELETE CASCADE,
|
|
node_id UUID NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
|
|
organization_id UUID REFERENCES organizations(id) ON DELETE CASCADE,
|
|
role TEXT NOT NULL,
|
|
status TEXT NOT NULL DEFAULT 'active',
|
|
policy JSONB NOT NULL DEFAULT '{}'::JSONB,
|
|
assigned_by_user_id UUID REFERENCES users(id) ON DELETE SET NULL,
|
|
assigned_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
revoked_at TIMESTAMPTZ,
|
|
CONSTRAINT node_role_assignments_status_check
|
|
CHECK (status IN ('active', 'disabled', 'revoked')),
|
|
CONSTRAINT node_role_assignments_role_check
|
|
CHECK (role IN (
|
|
'entry-node',
|
|
'relay-node',
|
|
'core-mesh',
|
|
'rdp-worker',
|
|
'vnc-worker',
|
|
'vpn-exit',
|
|
'vpn-connector',
|
|
'file-storage-cache',
|
|
'update-cache',
|
|
'video-relay'
|
|
))
|
|
);
|
|
|
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_node_role_assignments_unique_active
|
|
ON node_role_assignments(cluster_id, node_id, role, COALESCE(organization_id, '00000000-0000-0000-0000-000000000000'::uuid))
|
|
WHERE status = 'active';
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_node_role_assignments_cluster
|
|
ON node_role_assignments(cluster_id, role, status);
|
|
|
|
CREATE TABLE IF NOT EXISTS node_heartbeats (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
cluster_id UUID NOT NULL REFERENCES clusters(id) ON DELETE CASCADE,
|
|
node_id UUID NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
|
|
health_status TEXT NOT NULL DEFAULT 'unknown',
|
|
reported_version TEXT,
|
|
capabilities JSONB NOT NULL DEFAULT '{}'::JSONB,
|
|
service_states JSONB NOT NULL DEFAULT '{}'::JSONB,
|
|
metadata JSONB NOT NULL DEFAULT '{}'::JSONB,
|
|
observed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
CONSTRAINT node_heartbeats_health_status_check
|
|
CHECK (health_status IN ('unknown', 'healthy', 'warning', 'critical'))
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_node_heartbeats_cluster_node_observed
|
|
ON node_heartbeats(cluster_id, node_id, observed_at DESC);
|
|
|
|
CREATE TABLE IF NOT EXISTS node_latest_heartbeats (
|
|
cluster_id UUID NOT NULL REFERENCES clusters(id) ON DELETE CASCADE,
|
|
node_id UUID NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
|
|
heartbeat_id UUID REFERENCES node_heartbeats(id) ON DELETE SET NULL,
|
|
health_status TEXT NOT NULL DEFAULT 'unknown',
|
|
reported_version TEXT,
|
|
capabilities JSONB NOT NULL DEFAULT '{}'::JSONB,
|
|
service_states JSONB NOT NULL DEFAULT '{}'::JSONB,
|
|
metadata JSONB NOT NULL DEFAULT '{}'::JSONB,
|
|
observed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
PRIMARY KEY (cluster_id, node_id),
|
|
CONSTRAINT node_latest_heartbeats_health_status_check
|
|
CHECK (health_status IN ('unknown', 'healthy', 'warning', 'critical'))
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS cluster_audit_events (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
cluster_id UUID REFERENCES clusters(id) ON DELETE SET NULL,
|
|
actor_user_id UUID REFERENCES users(id) ON DELETE SET NULL,
|
|
event_type TEXT NOT NULL,
|
|
target_type TEXT NOT NULL,
|
|
target_id TEXT,
|
|
payload JSONB NOT NULL DEFAULT '{}'::JSONB,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_cluster_audit_events_cluster_created
|
|
ON cluster_audit_events(cluster_id, created_at DESC);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_cluster_audit_events_type_created
|
|
ON cluster_audit_events(event_type, created_at DESC);
|