Files
vibedns/internal/database/migrations/0001_initial_schema.sql
T
2026-08-16 21:18:45 -05:00

183 lines
7.5 KiB
SQL

-- Initial schema.
--
-- Design notes:
-- * Every searchable entity gets a real relational table. Only genuinely
-- list-shaped configuration (upstream resolvers, ACL networks) lives as a
-- JSON value inside the settings key/value table.
-- * Timestamps are stored as unix epoch seconds (INTEGER) so that they sort
-- and range-scan cheaply and carry no timezone ambiguity.
-- * Booleans are INTEGER 0/1.
CREATE TABLE settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
updated_at INTEGER NOT NULL DEFAULT (unixepoch())
);
CREATE TABLE admin_user (
id INTEGER PRIMARY KEY CHECK (id = 1),
username TEXT NOT NULL,
password_hash TEXT NOT NULL,
must_change_password INTEGER NOT NULL DEFAULT 0,
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
updated_at INTEGER NOT NULL DEFAULT (unixepoch()),
last_login_at INTEGER
);
CREATE TABLE api_tokens (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
description TEXT NOT NULL DEFAULT '',
token_prefix TEXT NOT NULL,
token_hash TEXT NOT NULL,
enabled INTEGER NOT NULL DEFAULT 1,
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
last_used_at INTEGER
);
CREATE INDEX idx_api_tokens_prefix ON api_tokens (token_prefix);
CREATE TABLE zones (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE, -- normalised FQDN with trailing dot
kind TEXT NOT NULL DEFAULT 'forward'
CHECK (kind IN ('forward', 'reverse4', 'reverse6')),
description TEXT NOT NULL DEFAULT '',
enabled INTEGER NOT NULL DEFAULT 1,
default_ttl INTEGER NOT NULL DEFAULT 3600,
primary_ns TEXT NOT NULL,
admin_email TEXT NOT NULL,
serial INTEGER NOT NULL DEFAULT 1,
refresh INTEGER NOT NULL DEFAULT 7200,
retry INTEGER NOT NULL DEFAULT 3600,
expire INTEGER NOT NULL DEFAULT 1209600,
minimum INTEGER NOT NULL DEFAULT 3600,
auto_serial INTEGER NOT NULL DEFAULT 1,
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
updated_at INTEGER NOT NULL DEFAULT (unixepoch())
);
CREATE INDEX idx_zones_enabled ON zones (enabled);
CREATE INDEX idx_zones_kind ON zones (kind);
CREATE TABLE records (
id INTEGER PRIMARY KEY AUTOINCREMENT,
zone_id INTEGER NOT NULL REFERENCES zones (id) ON DELETE CASCADE,
name TEXT NOT NULL, -- relative to the apex; '@' is the apex itself
type TEXT NOT NULL,
data TEXT NOT NULL, -- rdata in zone-file presentation format
ttl INTEGER, -- NULL inherits zones.default_ttl
enabled INTEGER NOT NULL DEFAULT 1,
comment TEXT NOT NULL DEFAULT '',
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
updated_at INTEGER NOT NULL DEFAULT (unixepoch())
);
CREATE INDEX idx_records_zone ON records (zone_id);
CREATE INDEX idx_records_zone_name ON records (zone_id, name);
CREATE INDEX idx_records_zone_name_type ON records (zone_id, name, type);
CREATE INDEX idx_records_type ON records (type);
CREATE TABLE networks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
cidr TEXT NOT NULL,
description TEXT NOT NULL DEFAULT '',
enabled INTEGER NOT NULL DEFAULT 1,
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
updated_at INTEGER NOT NULL DEFAULT (unixepoch())
);
CREATE INDEX idx_networks_enabled ON networks (enabled);
CREATE TABLE policies (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
description TEXT NOT NULL DEFAULT '',
enabled INTEGER NOT NULL DEFAULT 1,
block_action TEXT NOT NULL DEFAULT 'nxdomain'
CHECK (block_action IN ('nxdomain', 'refused', 'sinkhole')),
sinkhole_ipv4 TEXT NOT NULL DEFAULT '0.0.0.0',
sinkhole_ipv6 TEXT NOT NULL DEFAULT '::',
block_ttl INTEGER NOT NULL DEFAULT 60,
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
updated_at INTEGER NOT NULL DEFAULT (unixepoch())
);
CREATE TABLE domain_lists (
id INTEGER PRIMARY KEY AUTOINCREMENT,
kind TEXT NOT NULL CHECK (kind IN ('blacklist', 'allowlist')),
name TEXT NOT NULL,
description TEXT NOT NULL DEFAULT '',
enabled INTEGER NOT NULL DEFAULT 1,
source_url TEXT NOT NULL DEFAULT '',
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
updated_at INTEGER NOT NULL DEFAULT (unixepoch()),
UNIQUE (kind, name)
);
CREATE TABLE domain_entries (
id INTEGER PRIMARY KEY AUTOINCREMENT,
list_id INTEGER NOT NULL REFERENCES domain_lists (id) ON DELETE CASCADE,
domain TEXT NOT NULL, -- normalised: lowercase, no trailing dot
match_subdomains INTEGER NOT NULL DEFAULT 1,
enabled INTEGER NOT NULL DEFAULT 1,
comment TEXT NOT NULL DEFAULT '',
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
UNIQUE (list_id, domain)
);
CREATE INDEX idx_domain_entries_list ON domain_entries (list_id);
CREATE INDEX idx_domain_entries_domain ON domain_entries (domain);
-- Policy assignments.
CREATE TABLE network_policies (
network_id INTEGER NOT NULL REFERENCES networks (id) ON DELETE CASCADE,
policy_id INTEGER NOT NULL REFERENCES policies (id) ON DELETE CASCADE,
PRIMARY KEY (network_id, policy_id)
);
CREATE INDEX idx_network_policies_policy ON network_policies (policy_id);
CREATE TABLE policy_lists (
policy_id INTEGER NOT NULL REFERENCES policies (id) ON DELETE CASCADE,
list_id INTEGER NOT NULL REFERENCES domain_lists (id) ON DELETE CASCADE,
PRIMARY KEY (policy_id, list_id)
);
CREATE INDEX idx_policy_lists_list ON policy_lists (list_id);
CREATE TABLE query_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ts INTEGER NOT NULL, -- unix milliseconds
client_ip TEXT NOT NULL,
network_id INTEGER,
network_name TEXT NOT NULL DEFAULT '',
qname TEXT NOT NULL,
qtype TEXT NOT NULL,
rcode TEXT NOT NULL,
source TEXT NOT NULL,
cache_hit INTEGER NOT NULL DEFAULT 0,
blocked INTEGER NOT NULL DEFAULT 0,
policy_id INTEGER,
policy_name TEXT NOT NULL DEFAULT '',
blacklist_id INTEGER,
blacklist_name TEXT NOT NULL DEFAULT '',
matched_rule TEXT NOT NULL DEFAULT '',
protocol TEXT NOT NULL DEFAULT 'udp',
duration_us INTEGER NOT NULL DEFAULT 0,
answer_count INTEGER NOT NULL DEFAULT 0
);
CREATE INDEX idx_query_logs_ts ON query_logs (ts);
CREATE INDEX idx_query_logs_client ON query_logs (client_ip, ts);
CREATE INDEX idx_query_logs_qname ON query_logs (qname, ts);
CREATE INDEX idx_query_logs_blocked ON query_logs (blocked, ts);
CREATE TABLE audit_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ts INTEGER NOT NULL, -- unix milliseconds
actor TEXT NOT NULL DEFAULT '',
source TEXT NOT NULL DEFAULT 'web',
client_ip TEXT NOT NULL DEFAULT '',
action TEXT NOT NULL,
object_type TEXT NOT NULL DEFAULT '',
object_id TEXT NOT NULL DEFAULT '',
object_name TEXT NOT NULL DEFAULT '',
details TEXT NOT NULL DEFAULT ''
);
CREATE INDEX idx_audit_logs_ts ON audit_logs (ts);
CREATE INDEX idx_audit_logs_object ON audit_logs (object_type, ts);