288 lines
14 KiB
SQL
288 lines
14 KiB
SQL
CREATE TABLE system_state (
|
|
singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
|
|
bootstrap_completed_at TEXT
|
|
);
|
|
CREATE TABLE users (
|
|
id TEXT PRIMARY KEY,
|
|
username TEXT NOT NULL UNIQUE COLLATE NOCASE,
|
|
email TEXT UNIQUE COLLATE NOCASE,
|
|
password_hash TEXT NOT NULL,
|
|
global_role TEXT NOT NULL CHECK (global_role IN ('admin', 'user')),
|
|
disabled_at TEXT,
|
|
created_at TEXT NOT NULL
|
|
, language TEXT NOT NULL DEFAULT 'en' CHECK (language IN ('en', 'fr')));
|
|
CREATE TABLE sessions (
|
|
id_hash BLOB PRIMARY KEY,
|
|
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
csrf_hash BLOB NOT NULL,
|
|
created_at TEXT NOT NULL,
|
|
expires_at TEXT NOT NULL,
|
|
last_seen_at TEXT NOT NULL
|
|
);
|
|
CREATE INDEX sessions_user_id_idx ON sessions(user_id);
|
|
CREATE INDEX sessions_expires_at_idx ON sessions(expires_at);
|
|
CREATE TABLE authentication_attempts (
|
|
attempt_key TEXT PRIMARY KEY,
|
|
failures INTEGER NOT NULL,
|
|
blocked_until TEXT,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
CREATE TABLE templates (
|
|
id TEXT PRIMARY KEY,
|
|
origin TEXT NOT NULL,
|
|
trust_status TEXT NOT NULL,
|
|
active_version TEXT NOT NULL,
|
|
available INTEGER NOT NULL DEFAULT 1,
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
CREATE TABLE template_repositories (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL CHECK (length(name) BETWEEN 1 AND 120),
|
|
url TEXT NOT NULL UNIQUE COLLATE NOCASE CHECK (length(url) BETWEEN 1 AND 2048),
|
|
created_at TEXT NOT NULL
|
|
);
|
|
CREATE INDEX template_repositories_name_idx ON template_repositories(name COLLATE NOCASE, id);
|
|
CREATE TABLE template_versions (
|
|
template_id TEXT NOT NULL REFERENCES templates(id) ON DELETE RESTRICT,
|
|
version TEXT NOT NULL,
|
|
schema_version INTEGER NOT NULL,
|
|
canonical_yaml TEXT NOT NULL,
|
|
digest TEXT NOT NULL,
|
|
game_id TEXT NOT NULL,
|
|
game_name TEXT NOT NULL,
|
|
description TEXT NOT NULL,
|
|
image TEXT NOT NULL,
|
|
module_bundle BLOB,
|
|
created_at TEXT NOT NULL,
|
|
PRIMARY KEY (template_id, version),
|
|
UNIQUE (digest)
|
|
);
|
|
CREATE TABLE instances (
|
|
id TEXT PRIMARY KEY,
|
|
slug TEXT NOT NULL UNIQUE,
|
|
display_name TEXT NOT NULL,
|
|
template_id TEXT NOT NULL,
|
|
template_version TEXT NOT NULL,
|
|
template_digest TEXT NOT NULL,
|
|
revision INTEGER NOT NULL CHECK (revision >= 1),
|
|
lifecycle_state TEXT NOT NULL CHECK (lifecycle_state IN ('draft', 'installing', 'stopped', 'starting', 'online', 'stopping', 'backup', 'restore', 'update', 'degraded', 'error', 'unknown', 'intervention_required', 'deleting', 'deleted')),
|
|
observed_state TEXT NOT NULL DEFAULT 'unknown' CHECK (observed_state IN ('unknown', 'missing', 'stopped', 'running', 'ready', 'degraded')),
|
|
preview_json TEXT NOT NULL,
|
|
plan_digest TEXT NOT NULL,
|
|
container_id TEXT,
|
|
desired_running INTEGER NOT NULL DEFAULT 0 CHECK (desired_running IN (0, 1)),
|
|
last_error_code TEXT,
|
|
deleted_at TEXT,
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL, custom_labels_json TEXT NOT NULL DEFAULT '{}', docker_user_mode TEXT NOT NULL DEFAULT 'dogama' CHECK (docker_user_mode IN ('dogama', 'custom', 'image')), docker_uid INTEGER CHECK (docker_uid BETWEEN 0 AND 4294967295), docker_gid INTEGER CHECK (docker_gid BETWEEN 0 AND 4294967295), image_tag_mode TEXT NOT NULL DEFAULT 'tracked' CHECK (image_tag_mode IN ('tracked', 'pinned')), image_tag TEXT NOT NULL DEFAULT 'en', container_config_pending INTEGER NOT NULL DEFAULT 0 CHECK (container_config_pending IN (0, 1)),
|
|
FOREIGN KEY (template_id, template_version) REFERENCES template_versions(template_id, version) ON DELETE RESTRICT
|
|
);
|
|
CREATE INDEX instances_template_idx ON instances(template_id, template_version);
|
|
CREATE INDEX instances_lifecycle_idx ON instances(lifecycle_state);
|
|
CREATE TABLE instance_secrets (
|
|
instance_id TEXT PRIMARY KEY REFERENCES instances(id) ON DELETE CASCADE,
|
|
encrypted_values BLOB NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
CREATE TABLE instance_memberships (
|
|
instance_id TEXT NOT NULL REFERENCES instances(id) ON DELETE CASCADE,
|
|
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
role TEXT NOT NULL CHECK (role IN ('user', 'manager')),
|
|
created_by TEXT NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL,
|
|
PRIMARY KEY (instance_id, user_id)
|
|
);
|
|
CREATE INDEX instance_memberships_user_idx ON instance_memberships(user_id, instance_id);
|
|
CREATE TABLE permission_overrides (
|
|
instance_id TEXT NOT NULL,
|
|
user_id TEXT NOT NULL,
|
|
permission TEXT NOT NULL CHECK (permission IN ('instance.view', 'instance.start', 'instance.stop', 'instance.restart', 'instance.update', 'instance.configure', 'instance.delete', 'instance.welcome.edit', 'metrics.view', 'players.view', 'players.kick', 'players.ban', 'players.unban', 'announcements.send', 'logs.view', 'mods.manage', 'backup.create', 'backup.list', 'backup.export', 'backup.restore', 'backup.delete', 'request.create')),
|
|
effect TEXT NOT NULL CHECK (effect IN ('allow', 'deny')),
|
|
created_by TEXT NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL,
|
|
PRIMARY KEY (instance_id, user_id, permission),
|
|
FOREIGN KEY (instance_id, user_id) REFERENCES instance_memberships(instance_id, user_id) ON DELETE CASCADE
|
|
);
|
|
CREATE TABLE installation_requests (
|
|
id TEXT PRIMARY KEY,
|
|
requested_by TEXT NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
|
|
template_id TEXT NOT NULL,
|
|
template_version TEXT NOT NULL,
|
|
suggested_name TEXT,
|
|
player_estimate INTEGER CHECK (player_estimate IS NULL OR (player_estimate >= 1 AND player_estimate <= 10000)),
|
|
desired_schedule TEXT,
|
|
mods_requested INTEGER NOT NULL DEFAULT 0 CHECK (mods_requested IN (0, 1)),
|
|
message TEXT,
|
|
status TEXT NOT NULL CHECK (status IN ('pending', 'approved', 'refused', 'cancelled')),
|
|
reviewed_by TEXT REFERENCES users(id) ON DELETE RESTRICT,
|
|
review_reason TEXT,
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL,
|
|
reviewed_at TEXT,
|
|
FOREIGN KEY (template_id, template_version) REFERENCES template_versions(template_id, version) ON DELETE RESTRICT
|
|
);
|
|
CREATE UNIQUE INDEX installation_requests_pending_idx ON installation_requests(requested_by, template_id, template_version) WHERE status = 'pending';
|
|
CREATE INDEX installation_requests_status_idx ON installation_requests(status, created_at);
|
|
CREATE INDEX installation_requests_requester_idx ON installation_requests(requested_by, created_at DESC);
|
|
CREATE TABLE instance_operations (
|
|
id TEXT PRIMARY KEY,
|
|
instance_id TEXT NOT NULL REFERENCES instances(id) ON DELETE RESTRICT,
|
|
kind TEXT NOT NULL CHECK (kind IN ('install', 'start', 'stop', 'restart', 'delete_container', 'reconcile', 'backup', 'restore', 'import')),
|
|
state TEXT NOT NULL CHECK (state IN ('running', 'succeeded', 'failed', 'intervention_required')),
|
|
phase TEXT NOT NULL,
|
|
error_code TEXT,
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL,
|
|
completed_at TEXT
|
|
);
|
|
CREATE UNIQUE INDEX instance_operation_active_idx ON instance_operations(instance_id) WHERE state = 'running';
|
|
CREATE INDEX instance_operation_history_idx ON instance_operations(instance_id, created_at DESC);
|
|
CREATE TABLE backup_policies (
|
|
instance_id TEXT PRIMARY KEY REFERENCES instances(id) ON DELETE CASCADE,
|
|
enabled INTEGER NOT NULL DEFAULT 0 CHECK (enabled IN (0, 1)),
|
|
cron_expression TEXT,
|
|
timezone TEXT NOT NULL DEFAULT 'UTC',
|
|
retention_count INTEGER NOT NULL DEFAULT 7 CHECK (retention_count BETWEEN 1 AND 1000),
|
|
next_run_at TEXT,
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
CREATE TABLE backups (
|
|
id TEXT PRIMARY KEY,
|
|
instance_id TEXT NOT NULL REFERENCES instances(id) ON DELETE RESTRICT,
|
|
operation_id TEXT REFERENCES instance_operations(id) ON DELETE SET NULL,
|
|
origin TEXT NOT NULL CHECK (origin IN ('manual', 'scheduled', 'pre_update', 'pre_restore', 'idle_shutdown', 'imported', 'system')),
|
|
status TEXT NOT NULL CHECK (status IN ('creating', 'available', 'failed', 'deleted')),
|
|
relative_path TEXT,
|
|
size_bytes INTEGER CHECK (size_bytes IS NULL OR size_bytes >= 0),
|
|
sha256 TEXT CHECK (sha256 IS NULL OR length(sha256) = 64),
|
|
manifest_json TEXT,
|
|
error_code TEXT,
|
|
created_by TEXT REFERENCES users(id) ON DELETE SET NULL,
|
|
created_at TEXT NOT NULL,
|
|
completed_at TEXT,
|
|
deleted_at TEXT
|
|
);
|
|
CREATE INDEX backups_instance_created_idx ON backups(instance_id, created_at DESC);
|
|
CREATE INDEX backups_retention_idx ON backups(instance_id, origin, status, created_at);
|
|
CREATE TABLE imports (
|
|
id TEXT PRIMARY KEY,
|
|
requested_by TEXT NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
|
|
instance_id TEXT REFERENCES instances(id) ON DELETE CASCADE,
|
|
template_id TEXT NOT NULL,
|
|
template_version TEXT NOT NULL,
|
|
status TEXT NOT NULL CHECK (status IN ('staging', 'validated', 'attached', 'failed', 'expired')),
|
|
format TEXT NOT NULL,
|
|
relative_stage_path TEXT NOT NULL,
|
|
data_root TEXT,
|
|
detected_type TEXT,
|
|
confidence TEXT CHECK (confidence IS NULL OR confidence IN ('confirmed', 'probable', 'recognized_unknown_version', 'unrecognized')),
|
|
file_count INTEGER NOT NULL DEFAULT 0,
|
|
expanded_size_bytes INTEGER NOT NULL DEFAULT 0,
|
|
error_code TEXT,
|
|
created_at TEXT NOT NULL,
|
|
expires_at TEXT NOT NULL,
|
|
completed_at TEXT,
|
|
FOREIGN KEY (template_id, template_version) REFERENCES template_versions(template_id, version) ON DELETE RESTRICT
|
|
);
|
|
CREATE INDEX imports_expiry_idx ON imports(status, expires_at);
|
|
CREATE TABLE module_versions (
|
|
module_id TEXT NOT NULL,
|
|
version TEXT NOT NULL,
|
|
game_id TEXT NOT NULL,
|
|
abi TEXT NOT NULL,
|
|
manifest_json TEXT NOT NULL,
|
|
wasm_sha256 TEXT NOT NULL CHECK (length(wasm_sha256) = 64),
|
|
wasm_size_bytes INTEGER NOT NULL CHECK (wasm_size_bytes > 0),
|
|
source TEXT NOT NULL CHECK (source IN ('bundled', 'uploaded')),
|
|
installed_at TEXT NOT NULL,
|
|
PRIMARY KEY (module_id, version)
|
|
);
|
|
CREATE TABLE instance_module_bindings (
|
|
instance_id TEXT PRIMARY KEY REFERENCES instances(id) ON DELETE CASCADE,
|
|
module_id TEXT NOT NULL,
|
|
module_version TEXT NOT NULL,
|
|
port_id TEXT NOT NULL,
|
|
enabled INTEGER NOT NULL DEFAULT 0 CHECK (enabled IN (0, 1)),
|
|
configuration_json TEXT NOT NULL DEFAULT '{}',
|
|
secret_references_json TEXT NOT NULL DEFAULT '{}',
|
|
last_health TEXT NOT NULL DEFAULT 'unknown' CHECK (last_health IN ('ready', 'degraded', 'offline', 'unknown')),
|
|
last_error_code TEXT,
|
|
activated_at TEXT,
|
|
updated_at TEXT NOT NULL,
|
|
FOREIGN KEY (module_id, module_version) REFERENCES module_versions(module_id, version) ON DELETE RESTRICT
|
|
);
|
|
CREATE INDEX instance_module_health_idx ON instance_module_bindings(enabled, last_health);
|
|
CREATE TABLE system_settings (
|
|
key TEXT PRIMARY KEY,
|
|
value_json TEXT NOT NULL,
|
|
revision INTEGER NOT NULL DEFAULT 1 CHECK (revision >= 1),
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
CREATE TABLE configuration_revisions (
|
|
instance_id TEXT NOT NULL REFERENCES instances(id) ON DELETE CASCADE,
|
|
revision INTEGER NOT NULL CHECK (revision >= 1),
|
|
redacted_snapshot TEXT NOT NULL,
|
|
reason TEXT NOT NULL CHECK (length(reason) BETWEEN 1 AND 100),
|
|
created_by TEXT,
|
|
created_at TEXT NOT NULL,
|
|
PRIMARY KEY (instance_id, revision)
|
|
);
|
|
CREATE INDEX configuration_revision_history_idx ON configuration_revisions(instance_id, revision DESC);
|
|
CREATE TABLE notification_channels (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
type TEXT NOT NULL CHECK (type IN ('email', 'webhook', 'discord', 'gotify')),
|
|
enabled INTEGER NOT NULL DEFAULT 1 CHECK (enabled IN (0, 1)),
|
|
encrypted_config BLOB NOT NULL,
|
|
event_filter_json TEXT NOT NULL DEFAULT '[]',
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
-- Personal delivery choices are deliberately separate from encrypted global
|
|
-- channel configuration. Important failures default to enabled; routine
|
|
-- lifecycle events default to disabled.
|
|
CREATE TABLE user_notification_preferences (
|
|
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
category TEXT NOT NULL CHECK (category IN ('server_start', 'server_stop', 'server_error', 'backup', 'restore', 'update', 'administration')),
|
|
enabled INTEGER NOT NULL CHECK (enabled IN (0, 1)),
|
|
updated_at TEXT NOT NULL,
|
|
PRIMARY KEY (user_id, category)
|
|
);
|
|
CREATE INDEX user_notification_preferences_user_idx ON user_notification_preferences(user_id);
|
|
CREATE TABLE notification_deliveries (
|
|
id TEXT PRIMARY KEY,
|
|
channel_id TEXT NOT NULL REFERENCES notification_channels(id) ON DELETE CASCADE,
|
|
event_type TEXT NOT NULL,
|
|
payload_redacted TEXT NOT NULL,
|
|
status TEXT NOT NULL DEFAULT 'queued' CHECK (status IN ('queued', 'retrying', 'succeeded', 'failed')),
|
|
attempt INTEGER NOT NULL DEFAULT 0 CHECK (attempt >= 0),
|
|
next_attempt_at TEXT NOT NULL,
|
|
last_error_code TEXT NOT NULL DEFAULT 'en',
|
|
created_at TEXT NOT NULL,
|
|
completed_at TEXT
|
|
);
|
|
CREATE INDEX notification_deliveries_due_idx ON notification_deliveries(status, next_attempt_at);
|
|
CREATE TABLE audit_events (
|
|
id TEXT PRIMARY KEY,
|
|
occurred_at TEXT NOT NULL,
|
|
actor_id TEXT REFERENCES users(id) ON DELETE SET NULL,
|
|
actor_label TEXT NOT NULL,
|
|
instance_id TEXT REFERENCES instances(id) ON DELETE SET NULL,
|
|
action TEXT NOT NULL,
|
|
outcome TEXT NOT NULL CHECK (outcome IN ('allowed', 'denied', 'failed')),
|
|
summary_json TEXT NOT NULL DEFAULT '{}'
|
|
);
|
|
CREATE INDEX audit_events_time_idx ON audit_events(occurred_at DESC, id DESC);
|
|
CREATE INDEX audit_events_filters_idx ON audit_events(actor_id, instance_id, action, outcome);
|
|
CREATE INDEX audit_events_actor_time_idx ON audit_events(actor_id, occurred_at DESC, id DESC);
|
|
CREATE INDEX audit_events_instance_time_idx ON audit_events(instance_id, occurred_at DESC, id DESC);
|
|
CREATE INDEX audit_events_action_time_idx ON audit_events(action, occurred_at DESC, id DESC);
|
|
|
|
INSERT INTO system_state (singleton, bootstrap_completed_at) VALUES (1, NULL);
|
|
INSERT INTO system_settings(key, value_json, revision, updated_at)
|
|
VALUES ('audit_policy', '{"retention_days":30,"maximum_count":10000}', 1, strftime('%Y-%m-%dT%H:%M:%fZ','now'));
|