Docker images / prepare-release (push) Successful in 12s
Docker images / backend-test (push) Successful in 4m9s
Docker images / frontend-image (push) Successful in 4m28s
Docker images / updater-image (push) Successful in 58s
Docker images / backend-image (push) Successful in 2m49s
Docker images / notify-webhook (push) Skipped
Docker images / publish-release (push) Successful in 11s
При старте backend накатывает схему PostgreSQL 18 и, если база пустая, один раз импортирует mikrotik.db с тома. Повторный старт не копирует данные. Бэкап в UI идёт через pg_dump. Co-authored-by: Cursor <cursoragent@cursor.com>
627 lines
25 KiB
SQL
627 lines
25 KiB
SQL
-- MikrotikManager PostgreSQL 18 schema (not 1:1 SQLite).
|
|
-- Applied once at backend boot via src/db/migrate.ts
|
|
|
|
CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
id TEXT PRIMARY KEY,
|
|
applied_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS servers (
|
|
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
|
name TEXT NOT NULL DEFAULT '',
|
|
host TEXT NOT NULL,
|
|
port INTEGER NOT NULL DEFAULT 443,
|
|
username TEXT NOT NULL DEFAULT 'admin',
|
|
password TEXT NOT NULL DEFAULT '',
|
|
use_ssl BOOLEAN NOT NULL DEFAULT TRUE,
|
|
verify_ssl BOOLEAN NOT NULL DEFAULT FALSE,
|
|
type TEXT NOT NULL DEFAULT 'home-router'
|
|
CHECK (type IN ('jump-host', 'exit-node', 'home-router')),
|
|
site TEXT NOT NULL DEFAULT '',
|
|
country TEXT NOT NULL DEFAULT '',
|
|
asn TEXT NOT NULL DEFAULT '',
|
|
comment TEXT NOT NULL DEFAULT '',
|
|
enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
|
lan_subnet TEXT NOT NULL DEFAULT '',
|
|
wan_uplinks JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
mgmt_tunnel_ip TEXT NOT NULL DEFAULT '',
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS traffic_settings (
|
|
id BIGINT PRIMARY KEY CHECK (id = 1),
|
|
enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
|
interval_sec INTEGER NOT NULL DEFAULT 30,
|
|
retention_days INTEGER NOT NULL DEFAULT 14,
|
|
last_collected_at TIMESTAMPTZ,
|
|
last_duration_ms INTEGER,
|
|
last_error TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS servers_api_ping_settings (
|
|
id BIGINT PRIMARY KEY CHECK (id = 1),
|
|
enabled BOOLEAN NOT NULL DEFAULT FALSE,
|
|
interval_sec INTEGER NOT NULL DEFAULT 120,
|
|
last_collected_at TIMESTAMPTZ,
|
|
last_duration_ms INTEGER,
|
|
last_error TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS traffic_flow_settings (
|
|
id BIGINT PRIMARY KEY CHECK (id = 1),
|
|
enabled BOOLEAN NOT NULL DEFAULT FALSE,
|
|
collector_ip TEXT NOT NULL DEFAULT '10.255.254.1',
|
|
flow_listen_port INTEGER NOT NULL DEFAULT 4739,
|
|
wg_listen_port INTEGER NOT NULL DEFAULT 51821,
|
|
prefix TEXT NOT NULL DEFAULT '10.255.254.0/24',
|
|
public_endpoint TEXT NOT NULL DEFAULT '',
|
|
host_public_key TEXT NOT NULL DEFAULT '',
|
|
host_private_key TEXT NOT NULL DEFAULT '',
|
|
hub_server_id BIGINT,
|
|
retention_hours INTEGER NOT NULL DEFAULT 24,
|
|
top_n INTEGER NOT NULL DEFAULT 200,
|
|
map_service_min_share_pct DOUBLE PRECISION NOT NULL DEFAULT 5,
|
|
last_datagram_at TIMESTAMPTZ,
|
|
last_exporter_ip TEXT,
|
|
last_error TEXT,
|
|
packets_received BIGINT NOT NULL DEFAULT 0,
|
|
peers_json JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS uptime_settings (
|
|
id BIGINT PRIMARY KEY CHECK (id = 1),
|
|
enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
|
resources_enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
|
ping_enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
|
speed_enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
|
interval_sec INTEGER NOT NULL DEFAULT 15,
|
|
probe_interval_sec INTEGER NOT NULL DEFAULT 15,
|
|
speed_interval_sec INTEGER NOT NULL DEFAULT 60,
|
|
retention_days INTEGER NOT NULL DEFAULT 14,
|
|
last_collected_at TIMESTAMPTZ,
|
|
last_duration_ms INTEGER,
|
|
last_error TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS evobgp_settings (
|
|
id BIGINT PRIMARY KEY CHECK (id = 1),
|
|
base_url TEXT NOT NULL DEFAULT '',
|
|
api_key TEXT NOT NULL DEFAULT '',
|
|
enabled BOOLEAN NOT NULL DEFAULT FALSE,
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS alert_telegram_settings (
|
|
id BIGINT PRIMARY KEY CHECK (id = 1),
|
|
bot_token TEXT NOT NULL DEFAULT '',
|
|
chat_id TEXT NOT NULL DEFAULT '',
|
|
message_thread_id INTEGER,
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS acme_settings (
|
|
id BIGINT PRIMARY KEY CHECK (id = 1),
|
|
directory_url TEXT NOT NULL DEFAULT 'https://acme-v02.api.letsencrypt.org/directory',
|
|
cloudflare_api_token TEXT NOT NULL DEFAULT '',
|
|
default_zone_id TEXT NOT NULL DEFAULT '',
|
|
account_private_key TEXT NOT NULL DEFAULT '',
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS certificate_renew_settings (
|
|
id BIGINT PRIMARY KEY CHECK (id = 1),
|
|
enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
|
interval_sec INTEGER NOT NULL DEFAULT 21600,
|
|
renew_before_days INTEGER NOT NULL DEFAULT 30,
|
|
last_collected_at TIMESTAMPTZ,
|
|
last_duration_ms INTEGER,
|
|
last_error TEXT,
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS backup_schedule_settings (
|
|
id BIGINT PRIMARY KEY CHECK (id = 1),
|
|
enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
|
frequency TEXT NOT NULL DEFAULT 'daily' CHECK (frequency IN ('daily', 'weekly', 'monthly')),
|
|
hour INTEGER NOT NULL DEFAULT 3,
|
|
minute INTEGER NOT NULL DEFAULT 0,
|
|
week_day INTEGER NOT NULL DEFAULT 0,
|
|
month_day INTEGER NOT NULL DEFAULT 1,
|
|
keep_count INTEGER NOT NULL DEFAULT 7,
|
|
format TEXT NOT NULL DEFAULT 'rsc' CHECK (format IN ('rsc', 'backup')),
|
|
server_ids_json JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
last_run_at TIMESTAMPTZ,
|
|
last_duration_ms INTEGER,
|
|
last_error TEXT,
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS internet_path_settings (
|
|
id BIGINT PRIMARY KEY CHECK (id = 1),
|
|
enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
|
interval_sec INTEGER NOT NULL DEFAULT 300,
|
|
retention_days INTEGER NOT NULL DEFAULT 14,
|
|
last_collected_at TIMESTAMPTZ,
|
|
last_duration_ms INTEGER,
|
|
last_error TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS alert_engine_cursor (
|
|
id BIGINT PRIMARY KEY CHECK (id = 1),
|
|
last_source_finished_at TIMESTAMPTZ,
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS data_migration (
|
|
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
sqlite_imported_at TIMESTAMPTZ,
|
|
sqlite_path TEXT,
|
|
sqlite_sha256 TEXT,
|
|
report_json JSONB
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS filter_rules (
|
|
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
|
server_id BIGINT NOT NULL REFERENCES servers(id) ON DELETE CASCADE,
|
|
sort_order INTEGER NOT NULL DEFAULT 0,
|
|
community TEXT NOT NULL,
|
|
community_name TEXT,
|
|
action TEXT NOT NULL DEFAULT 'route' CHECK (action IN ('route', 'blackhole')),
|
|
gateway TEXT NOT NULL DEFAULT '',
|
|
gateway_tunnel_id TEXT NOT NULL DEFAULT '',
|
|
description TEXT NOT NULL DEFAULT '',
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_filter_rules_server_sort ON filter_rules(server_id, sort_order);
|
|
|
|
CREATE TABLE IF NOT EXISTS recursive_routes (
|
|
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
|
server_id BIGINT NOT NULL REFERENCES servers(id) ON DELETE CASCADE,
|
|
sort_order INTEGER NOT NULL DEFAULT 0,
|
|
dst_address TEXT NOT NULL,
|
|
gateway TEXT NOT NULL,
|
|
distance INTEGER NOT NULL DEFAULT 1,
|
|
scope INTEGER,
|
|
target_scope INTEGER,
|
|
routing_table TEXT NOT NULL DEFAULT 'main',
|
|
check_gateway TEXT NOT NULL DEFAULT '',
|
|
country TEXT NOT NULL DEFAULT '',
|
|
comment TEXT NOT NULL DEFAULT '',
|
|
disabled BOOLEAN NOT NULL DEFAULT FALSE,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_recursive_routes_server_sort ON recursive_routes(server_id, sort_order);
|
|
|
|
CREATE TABLE IF NOT EXISTS server_snapshots (
|
|
id BIGINT GENERATED BY DEFAULT AS IDENTITY,
|
|
server_id BIGINT NOT NULL REFERENCES servers(id) ON DELETE CASCADE,
|
|
polled_at TIMESTAMPTZ NOT NULL,
|
|
status TEXT NOT NULL CHECK (status IN ('online', 'offline')),
|
|
latency_ms DOUBLE PRECISION,
|
|
ros_version TEXT,
|
|
board_name TEXT,
|
|
uptime TEXT,
|
|
cpu_load INTEGER,
|
|
free_memory BIGINT,
|
|
total_memory BIGINT,
|
|
identity_name TEXT,
|
|
raw_interfaces JSONB,
|
|
raw_ip_addresses JSONB,
|
|
PRIMARY KEY (id, polled_at)
|
|
) PARTITION BY RANGE (polled_at);
|
|
CREATE INDEX IF NOT EXISTS idx_server_snapshots_server_time ON server_snapshots(server_id, polled_at);
|
|
CREATE INDEX IF NOT EXISTS idx_server_snapshots_polled_brin ON server_snapshots USING BRIN (polled_at);
|
|
|
|
CREATE TABLE IF NOT EXISTS traffic_samples (
|
|
id BIGINT GENERATED BY DEFAULT AS IDENTITY,
|
|
server_id BIGINT NOT NULL REFERENCES servers(id) ON DELETE CASCADE,
|
|
interface_name TEXT NOT NULL,
|
|
peer_public_key TEXT NOT NULL DEFAULT '',
|
|
sampled_at TIMESTAMPTZ NOT NULL,
|
|
rx_bytes BIGINT NOT NULL DEFAULT 0,
|
|
tx_bytes BIGINT NOT NULL DEFAULT 0,
|
|
rx_bps BIGINT NOT NULL DEFAULT 0,
|
|
tx_bps BIGINT NOT NULL DEFAULT 0,
|
|
running BOOLEAN NOT NULL DEFAULT FALSE,
|
|
disabled BOOLEAN NOT NULL DEFAULT FALSE,
|
|
PRIMARY KEY (id, sampled_at)
|
|
) PARTITION BY RANGE (sampled_at);
|
|
CREATE INDEX IF NOT EXISTS idx_traffic_samples_server_time ON traffic_samples(server_id, sampled_at);
|
|
CREATE INDEX IF NOT EXISTS idx_traffic_samples_server_iface_time ON traffic_samples(server_id, interface_name, sampled_at);
|
|
CREATE INDEX IF NOT EXISTS idx_traffic_samples_sampled_brin ON traffic_samples USING BRIN (sampled_at);
|
|
|
|
CREATE TABLE IF NOT EXISTS servers_rest_ping_samples (
|
|
id BIGINT GENERATED BY DEFAULT AS IDENTITY,
|
|
server_id BIGINT NOT NULL REFERENCES servers(id) ON DELETE CASCADE,
|
|
sampled_at TIMESTAMPTZ NOT NULL,
|
|
ok BOOLEAN NOT NULL,
|
|
latency_ms INTEGER,
|
|
error TEXT,
|
|
PRIMARY KEY (id, sampled_at)
|
|
) PARTITION BY RANGE (sampled_at);
|
|
CREATE INDEX IF NOT EXISTS idx_servers_rest_ping_samples_server_id ON servers_rest_ping_samples(server_id, sampled_at);
|
|
CREATE INDEX IF NOT EXISTS idx_servers_rest_ping_samples_brin ON servers_rest_ping_samples USING BRIN (sampled_at);
|
|
|
|
CREATE TABLE IF NOT EXISTS flow_buckets (
|
|
server_id BIGINT NOT NULL REFERENCES servers(id) ON DELETE CASCADE,
|
|
bucket_at TIMESTAMPTZ NOT NULL,
|
|
src TEXT NOT NULL,
|
|
dst TEXT NOT NULL,
|
|
proto INTEGER NOT NULL DEFAULT 0,
|
|
src_port INTEGER NOT NULL DEFAULT 0,
|
|
dst_port INTEGER NOT NULL DEFAULT 0,
|
|
bytes BIGINT NOT NULL DEFAULT 0,
|
|
packets BIGINT NOT NULL DEFAULT 0,
|
|
in_iface TEXT NOT NULL DEFAULT '',
|
|
out_iface TEXT NOT NULL DEFAULT '',
|
|
next_hop TEXT NOT NULL DEFAULT '',
|
|
flow_start_ms BIGINT NOT NULL DEFAULT 0,
|
|
flow_end_ms BIGINT NOT NULL DEFAULT 0,
|
|
PRIMARY KEY (server_id, bucket_at, src, dst, proto, src_port, dst_port, in_iface)
|
|
) PARTITION BY RANGE (bucket_at);
|
|
CREATE INDEX IF NOT EXISTS idx_flow_buckets_server_time ON flow_buckets(server_id, bucket_at);
|
|
CREATE INDEX IF NOT EXISTS idx_flow_buckets_bucket_brin ON flow_buckets USING BRIN (bucket_at);
|
|
|
|
CREATE TABLE IF NOT EXISTS flow_minute_stats (
|
|
server_id BIGINT NOT NULL REFERENCES servers(id) ON DELETE CASCADE,
|
|
bucket_at TIMESTAMPTZ NOT NULL,
|
|
bytes BIGINT NOT NULL DEFAULT 0,
|
|
packets BIGINT NOT NULL DEFAULT 0,
|
|
unique_src INTEGER NOT NULL DEFAULT 0,
|
|
unique_dst INTEGER NOT NULL DEFAULT 0,
|
|
conversations INTEGER NOT NULL DEFAULT 0,
|
|
PRIMARY KEY (server_id, bucket_at)
|
|
) PARTITION BY RANGE (bucket_at);
|
|
CREATE INDEX IF NOT EXISTS idx_flow_minute_stats_brin ON flow_minute_stats USING BRIN (bucket_at);
|
|
|
|
CREATE TABLE IF NOT EXISTS flow_minute_dims (
|
|
server_id BIGINT NOT NULL REFERENCES servers(id) ON DELETE CASCADE,
|
|
bucket_at TIMESTAMPTZ NOT NULL,
|
|
dim TEXT NOT NULL,
|
|
key TEXT NOT NULL,
|
|
bytes BIGINT NOT NULL DEFAULT 0,
|
|
packets BIGINT NOT NULL DEFAULT 0,
|
|
PRIMARY KEY (server_id, bucket_at, dim, key)
|
|
) PARTITION BY RANGE (bucket_at);
|
|
CREATE INDEX IF NOT EXISTS idx_flow_minute_dims_time ON flow_minute_dims(bucket_at, dim);
|
|
CREATE INDEX IF NOT EXISTS idx_flow_minute_dims_brin ON flow_minute_dims USING BRIN (bucket_at);
|
|
|
|
CREATE TABLE IF NOT EXISTS flow_daily_dims (
|
|
server_id BIGINT NOT NULL REFERENCES servers(id) ON DELETE CASCADE,
|
|
day DATE NOT NULL,
|
|
dim TEXT NOT NULL,
|
|
key TEXT NOT NULL,
|
|
bytes BIGINT NOT NULL DEFAULT 0,
|
|
packets BIGINT NOT NULL DEFAULT 0,
|
|
PRIMARY KEY (server_id, day, dim, key)
|
|
) PARTITION BY RANGE (day);
|
|
CREATE INDEX IF NOT EXISTS idx_flow_daily_dims_day ON flow_daily_dims(day, dim);
|
|
|
|
CREATE TABLE IF NOT EXISTS flow_ip_meta (
|
|
prefix TEXT PRIMARY KEY,
|
|
asn INTEGER NOT NULL DEFAULT 0,
|
|
country TEXT NOT NULL DEFAULT '',
|
|
lat DOUBLE PRECISION,
|
|
lng DOUBLE PRECISION,
|
|
holder TEXT NOT NULL DEFAULT '',
|
|
ok INTEGER NOT NULL DEFAULT 1,
|
|
fetched_at TIMESTAMPTZ NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS flow_asn_meta (
|
|
asn INTEGER PRIMARY KEY,
|
|
holder TEXT NOT NULL DEFAULT '',
|
|
fetched_at TIMESTAMPTZ NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS uptime_probes (
|
|
id TEXT PRIMARY KEY,
|
|
src_server_id BIGINT NOT NULL REFERENCES servers(id) ON DELETE CASCADE,
|
|
src_interface TEXT NOT NULL DEFAULT '',
|
|
name TEXT NOT NULL,
|
|
target TEXT NOT NULL,
|
|
probe_filter TEXT NOT NULL DEFAULT '—',
|
|
enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
|
interval_sec INTEGER NOT NULL DEFAULT 0,
|
|
show_on_dashboard BOOLEAN NOT NULL DEFAULT FALSE,
|
|
sort_order INTEGER NOT NULL DEFAULT 0,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_uptime_probes_server_sort ON uptime_probes(src_server_id, sort_order);
|
|
|
|
CREATE TABLE IF NOT EXISTS uptime_probe_samples (
|
|
id BIGINT GENERATED BY DEFAULT AS IDENTITY,
|
|
probe_id TEXT NOT NULL REFERENCES uptime_probes(id) ON DELETE CASCADE,
|
|
sampled_at TIMESTAMPTZ NOT NULL,
|
|
rtt_ms INTEGER,
|
|
loss_pct INTEGER NOT NULL DEFAULT 0,
|
|
status TEXT NOT NULL DEFAULT 'down' CHECK (status IN ('up', 'warn', 'down')),
|
|
PRIMARY KEY (id, sampled_at)
|
|
) PARTITION BY RANGE (sampled_at);
|
|
CREATE INDEX IF NOT EXISTS idx_uptime_probe_samples_probe_time ON uptime_probe_samples(probe_id, sampled_at);
|
|
CREATE INDEX IF NOT EXISTS idx_uptime_probe_samples_brin ON uptime_probe_samples USING BRIN (sampled_at);
|
|
|
|
CREATE TABLE IF NOT EXISTS uptime_resource_samples (
|
|
id BIGINT GENERATED BY DEFAULT AS IDENTITY,
|
|
server_id BIGINT NOT NULL REFERENCES servers(id) ON DELETE CASCADE,
|
|
sampled_at TIMESTAMPTZ NOT NULL,
|
|
status TEXT NOT NULL DEFAULT 'offline' CHECK (status IN ('online', 'offline')),
|
|
cpu_load INTEGER NOT NULL DEFAULT 0,
|
|
free_memory BIGINT NOT NULL DEFAULT 0,
|
|
total_memory BIGINT NOT NULL DEFAULT 0,
|
|
free_hdd_space BIGINT NOT NULL DEFAULT 0,
|
|
total_hdd_space BIGINT NOT NULL DEFAULT 0,
|
|
uptime_seconds BIGINT NOT NULL DEFAULT 0,
|
|
board_name TEXT NOT NULL DEFAULT '',
|
|
ros_version TEXT NOT NULL DEFAULT '',
|
|
PRIMARY KEY (id, sampled_at)
|
|
) PARTITION BY RANGE (sampled_at);
|
|
CREATE INDEX IF NOT EXISTS idx_uptime_resource_samples_server_time ON uptime_resource_samples(server_id, sampled_at);
|
|
CREATE INDEX IF NOT EXISTS idx_uptime_resource_samples_brin ON uptime_resource_samples USING BRIN (sampled_at);
|
|
|
|
CREATE TABLE IF NOT EXISTS uptime_speed_probes (
|
|
id TEXT PRIMARY KEY,
|
|
src_server_id BIGINT NOT NULL REFERENCES servers(id) ON DELETE CASCADE,
|
|
dst_server_id BIGINT NOT NULL REFERENCES servers(id) ON DELETE CASCADE,
|
|
src_interface TEXT NOT NULL DEFAULT '',
|
|
dst_interface TEXT NOT NULL DEFAULT '',
|
|
protocol TEXT NOT NULL DEFAULT 'tcp' CHECK (protocol IN ('tcp', 'udp')),
|
|
direction TEXT NOT NULL DEFAULT 'both' CHECK (direction IN ('transmit', 'receive', 'both')),
|
|
duration_sec INTEGER NOT NULL DEFAULT 10,
|
|
enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
|
last_run_at TIMESTAMPTZ,
|
|
last_tx_avg_mbps DOUBLE PRECISION,
|
|
last_rx_avg_mbps DOUBLE PRECISION,
|
|
last_status TEXT CHECK (last_status IN ('done', 'error')),
|
|
last_error TEXT,
|
|
last_ping_rtt_ms INTEGER,
|
|
last_ping_loss_pct INTEGER,
|
|
last_ping_at TIMESTAMPTZ,
|
|
last_ping_error TEXT,
|
|
sort_order INTEGER NOT NULL DEFAULT 0,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_uptime_speed_probes_src_sort ON uptime_speed_probes(src_server_id, sort_order);
|
|
|
|
CREATE TABLE IF NOT EXISTS uptime_speed_test_runs (
|
|
id TEXT PRIMARY KEY,
|
|
probe_id TEXT,
|
|
src_server_id BIGINT NOT NULL REFERENCES servers(id) ON DELETE CASCADE,
|
|
dst_server_id BIGINT NOT NULL REFERENCES servers(id) ON DELETE CASCADE,
|
|
src_interface TEXT NOT NULL DEFAULT '',
|
|
dst_interface TEXT NOT NULL DEFAULT '',
|
|
src_address TEXT,
|
|
dst_address TEXT,
|
|
src_interface_address TEXT,
|
|
dst_interface_address TEXT,
|
|
protocol TEXT NOT NULL DEFAULT 'tcp' CHECK (protocol IN ('tcp', 'udp')),
|
|
direction TEXT NOT NULL DEFAULT 'both' CHECK (direction IN ('transmit', 'receive', 'both')),
|
|
duration_sec INTEGER NOT NULL DEFAULT 10,
|
|
tx_avg_mbps DOUBLE PRECISION,
|
|
rx_avg_mbps DOUBLE PRECISION,
|
|
ping_rtt_ms INTEGER,
|
|
ping_loss_pct INTEGER,
|
|
ping_error TEXT,
|
|
status TEXT NOT NULL DEFAULT 'done' CHECK (status IN ('done', 'error')),
|
|
error TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_uptime_speed_test_runs_created_at ON uptime_speed_test_runs(created_at);
|
|
|
|
CREATE TABLE IF NOT EXISTS certificate_issue_jobs (
|
|
id TEXT PRIMARY KEY,
|
|
status TEXT NOT NULL DEFAULT 'queued' CHECK (status IN ('queued', 'running', 'done', 'failed')),
|
|
step TEXT NOT NULL DEFAULT 'queued',
|
|
source TEXT NOT NULL DEFAULT 'manual' CHECK (source IN ('manual', 'scheduler')),
|
|
server_id BIGINT NOT NULL REFERENCES servers(id) ON DELETE CASCADE,
|
|
cert_name TEXT NOT NULL,
|
|
domain_names JSONB NOT NULL,
|
|
key_type TEXT NOT NULL DEFAULT 'rsa2048',
|
|
trust_store TEXT NOT NULL DEFAULT 'www,api',
|
|
requested_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
started_at TIMESTAMPTZ,
|
|
finished_at TIMESTAMPTZ,
|
|
error TEXT
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS backup_entries (
|
|
id TEXT PRIMARY KEY,
|
|
server_id BIGINT REFERENCES servers(id) ON DELETE SET NULL,
|
|
server_name TEXT NOT NULL,
|
|
filename TEXT NOT NULL,
|
|
size_bytes BIGINT NOT NULL,
|
|
kind TEXT NOT NULL DEFAULT 'manual' CHECK (kind IN ('manual', 'auto')),
|
|
notes TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL
|
|
);
|
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_backup_entries_filename ON backup_entries(filename);
|
|
CREATE INDEX IF NOT EXISTS idx_backup_entries_server_created ON backup_entries(server_id, created_at);
|
|
|
|
CREATE TABLE IF NOT EXISTS alert_groups (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
combine_mode TEXT NOT NULL DEFAULT 'any' CHECK (combine_mode IN ('any', 'all')),
|
|
enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
|
cooldown_override TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS alert_rules (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
type TEXT NOT NULL,
|
|
target TEXT NOT NULL,
|
|
condition TEXT NOT NULL,
|
|
severity TEXT NOT NULL CHECK (severity IN ('critical', 'warning', 'info')),
|
|
enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
|
cooldown TEXT NOT NULL DEFAULT '5м',
|
|
rule_chat_id TEXT NOT NULL DEFAULT '',
|
|
confirm_stability_sec INTEGER,
|
|
recovery_mode TEXT NOT NULL DEFAULT 'always' CHECK (recovery_mode IN ('always', 'never', 'conditional')),
|
|
recovery_stability_sec INTEGER,
|
|
group_id TEXT REFERENCES alert_groups(id) ON DELETE SET NULL,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS alert_rule_targets (
|
|
id TEXT PRIMARY KEY,
|
|
rule_id TEXT NOT NULL REFERENCES alert_rules(id) ON DELETE CASCADE,
|
|
target TEXT NOT NULL,
|
|
sort_index INTEGER NOT NULL DEFAULT 0
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_alert_rule_targets_rule ON alert_rule_targets(rule_id);
|
|
|
|
CREATE TABLE IF NOT EXISTS alert_rule_conditions (
|
|
id TEXT PRIMARY KEY,
|
|
rule_id TEXT NOT NULL REFERENCES alert_rules(id) ON DELETE CASCADE,
|
|
condition_line TEXT NOT NULL,
|
|
sort_index INTEGER NOT NULL DEFAULT 0
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_alert_rule_conditions_rule ON alert_rule_conditions(rule_id);
|
|
|
|
CREATE TABLE IF NOT EXISTS alert_engine_state (
|
|
scope_key TEXT PRIMARY KEY,
|
|
last_fired_at TEXT NOT NULL DEFAULT '',
|
|
last_payload_hash TEXT
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS alert_engine_prev_live (
|
|
kind TEXT PRIMARY KEY CHECK (kind IN ('gre', 'bgp')),
|
|
payload_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS alert_engine_confirm_pending (
|
|
rule_id TEXT PRIMARY KEY,
|
|
payload_hash TEXT NOT NULL,
|
|
since_at TIMESTAMPTZ NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS alert_history (
|
|
id TEXT PRIMARY KEY,
|
|
rule_id TEXT,
|
|
group_id TEXT,
|
|
rule_name TEXT NOT NULL,
|
|
severity TEXT NOT NULL CHECK (severity IN ('critical', 'warning', 'info')),
|
|
message TEXT NOT NULL,
|
|
sent_ok BOOLEAN NOT NULL DEFAULT TRUE,
|
|
fired_at TIMESTAMPTZ NOT NULL
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_alert_history_fired_at ON alert_history(fired_at);
|
|
CREATE INDEX IF NOT EXISTS idx_alert_history_rule_id ON alert_history(rule_id);
|
|
|
|
CREATE TABLE IF NOT EXISTS alert_outbox (
|
|
id TEXT PRIMARY KEY,
|
|
dedupe_key TEXT NOT NULL,
|
|
channel TEXT NOT NULL DEFAULT 'telegram' CHECK (channel IN ('telegram')),
|
|
status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'sent', 'failed')),
|
|
retry_count INTEGER NOT NULL DEFAULT 0,
|
|
max_retries INTEGER NOT NULL DEFAULT 3,
|
|
next_attempt_at TIMESTAMPTZ NOT NULL,
|
|
payload_json JSONB NOT NULL,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
sent_at TIMESTAMPTZ,
|
|
last_error TEXT
|
|
);
|
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_alert_outbox_dedupe ON alert_outbox(dedupe_key);
|
|
CREATE INDEX IF NOT EXISTS idx_alert_outbox_status_next_attempt ON alert_outbox(status, next_attempt_at);
|
|
CREATE INDEX IF NOT EXISTS idx_alert_outbox_pending_next ON alert_outbox(next_attempt_at) WHERE status = 'pending';
|
|
|
|
CREATE TABLE IF NOT EXISTS scheduler_runs (
|
|
id TEXT PRIMARY KEY,
|
|
job_key TEXT NOT NULL,
|
|
started_at TIMESTAMPTZ NOT NULL,
|
|
finished_at TIMESTAMPTZ NOT NULL,
|
|
status TEXT NOT NULL CHECK (status IN ('ok', 'error')),
|
|
error TEXT,
|
|
duration_ms INTEGER NOT NULL DEFAULT 0,
|
|
result_json JSONB
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_scheduler_runs_job_time ON scheduler_runs(job_key, started_at);
|
|
|
|
CREATE TABLE IF NOT EXISTS events (
|
|
id TEXT PRIMARY KEY,
|
|
created_at TIMESTAMPTZ NOT NULL,
|
|
level TEXT NOT NULL CHECK (level IN ('critical', 'warning', 'info')),
|
|
event_type TEXT NOT NULL,
|
|
source_module TEXT NOT NULL,
|
|
title TEXT NOT NULL,
|
|
message TEXT NOT NULL,
|
|
entity_type TEXT,
|
|
entity_id TEXT,
|
|
payload_json JSONB
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_events_created_at ON events(created_at DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_events_level_created_at ON events(level, created_at DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_events_source_created_at ON events(source_module, created_at DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_events_event_type_created_at ON events(event_type, created_at DESC);
|
|
|
|
CREATE TABLE IF NOT EXISTS app_users (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL DEFAULT '',
|
|
login TEXT NOT NULL UNIQUE,
|
|
email TEXT NOT NULL DEFAULT '',
|
|
role TEXT NOT NULL DEFAULT 'viewer' CHECK (role IN ('admin', 'operator', 'viewer')),
|
|
active BOOLEAN NOT NULL DEFAULT TRUE,
|
|
avatar TEXT NOT NULL DEFAULT '',
|
|
last_seen TIMESTAMPTZ,
|
|
sections_json JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
servers_json JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS user_interface_bindings (
|
|
id TEXT PRIMARY KEY,
|
|
user_id TEXT NOT NULL REFERENCES app_users(id) ON DELETE CASCADE,
|
|
server_id BIGINT NOT NULL REFERENCES servers(id) ON DELETE CASCADE,
|
|
interface_name TEXT NOT NULL,
|
|
interface_type TEXT NOT NULL DEFAULT 'other' CHECK (interface_type IN ('ether', 'gre', 'wg', 'other')),
|
|
peer_public_key TEXT NOT NULL DEFAULT '',
|
|
peer_name TEXT NOT NULL DEFAULT '',
|
|
comment TEXT NOT NULL DEFAULT '',
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
UNIQUE (server_id, interface_name, peer_public_key)
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_user_iface_bind_user ON user_interface_bindings(user_id);
|
|
|
|
CREATE TABLE IF NOT EXISTS internet_path_snapshots (
|
|
id BIGINT GENERATED BY DEFAULT AS IDENTITY,
|
|
sampled_at TIMESTAMPTZ NOT NULL,
|
|
payload_json JSONB NOT NULL,
|
|
PRIMARY KEY (id, sampled_at)
|
|
) PARTITION BY RANGE (sampled_at);
|
|
CREATE INDEX IF NOT EXISTS idx_internet_path_snapshots_sampled ON internet_path_snapshots(sampled_at);
|
|
CREATE INDEX IF NOT EXISTS idx_internet_path_snapshots_brin ON internet_path_snapshots USING BRIN (sampled_at);
|
|
|
|
INSERT INTO traffic_settings (id) VALUES (1) ON CONFLICT (id) DO NOTHING;
|
|
INSERT INTO traffic_flow_settings (id) VALUES (1) ON CONFLICT (id) DO NOTHING;
|
|
INSERT INTO uptime_settings (id) VALUES (1) ON CONFLICT (id) DO NOTHING;
|
|
INSERT INTO evobgp_settings (id) VALUES (1) ON CONFLICT (id) DO NOTHING;
|
|
INSERT INTO servers_api_ping_settings (id) VALUES (1) ON CONFLICT (id) DO NOTHING;
|
|
INSERT INTO internet_path_settings (id) VALUES (1) ON CONFLICT (id) DO NOTHING;
|
|
INSERT INTO alert_telegram_settings (id) VALUES (1) ON CONFLICT (id) DO NOTHING;
|
|
INSERT INTO acme_settings (id) VALUES (1) ON CONFLICT (id) DO NOTHING;
|
|
INSERT INTO certificate_renew_settings (id) VALUES (1) ON CONFLICT (id) DO NOTHING;
|
|
INSERT INTO backup_schedule_settings (id) VALUES (1) ON CONFLICT (id) DO NOTHING;
|
|
INSERT INTO alert_engine_cursor (id) VALUES (1) ON CONFLICT (id) DO NOTHING;
|
|
INSERT INTO data_migration (id) VALUES (1) ON CONFLICT (id) DO NOTHING;
|