panel — open-source game server manager (public release)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
-- 001_init.sql — initial schema
|
||||
--
|
||||
-- agents: one row per Target agent the controller has ever seen. Updated
|
||||
-- with each connection + heartbeat. Serves as audit trail — we
|
||||
-- never delete rows, so an agent disappearing from the network
|
||||
-- doesn't wipe its history.
|
||||
-- instances: one row per declared game server. The controller, not the
|
||||
-- agent, is the source of truth for "instance X exists". Status
|
||||
-- is updated from InstanceStateUpdate messages streamed up from
|
||||
-- the agent that owns the instance.
|
||||
|
||||
CREATE TABLE agents (
|
||||
agent_id TEXT PRIMARY KEY,
|
||||
version TEXT NOT NULL,
|
||||
host_os TEXT NOT NULL,
|
||||
host_arch TEXT NOT NULL,
|
||||
hostname TEXT NOT NULL,
|
||||
first_seen TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
last_seen TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE instances (
|
||||
instance_id TEXT PRIMARY KEY,
|
||||
agent_id TEXT NOT NULL,
|
||||
module_id TEXT NOT NULL,
|
||||
module_version TEXT,
|
||||
run_mode TEXT NOT NULL DEFAULT 'docker',
|
||||
data_path TEXT,
|
||||
config_values JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
status TEXT NOT NULL DEFAULT 'stopped',
|
||||
last_exit_code INTEGER,
|
||||
detail TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_instances_agent ON instances(agent_id);
|
||||
CREATE INDEX idx_instances_module ON instances(module_id);
|
||||
CREATE INDEX idx_instances_status ON instances(status);
|
||||
@@ -0,0 +1,29 @@
|
||||
-- 002_schedules.sql — timed / event-driven actions on instances.
|
||||
--
|
||||
-- For the MVP only cron triggers are supported; trigger_kind leaves room
|
||||
-- for future 'event' / 'composite' without a schema change.
|
||||
--
|
||||
-- action is JSONB so new action types (webhook, exec, etc.) can be added
|
||||
-- without migrations. The controller's scheduler engine validates shape
|
||||
-- at dispatch time.
|
||||
--
|
||||
-- last_fired_at / last_status let the UI show whether a schedule is
|
||||
-- actually doing what it says.
|
||||
|
||||
CREATE TABLE schedules (
|
||||
id TEXT PRIMARY KEY,
|
||||
instance_id TEXT NOT NULL,
|
||||
description TEXT NOT NULL DEFAULT '',
|
||||
trigger_kind TEXT NOT NULL DEFAULT 'cron',
|
||||
cron_spec TEXT NOT NULL,
|
||||
action JSONB NOT NULL,
|
||||
enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
last_fired_at TIMESTAMPTZ,
|
||||
last_status TEXT,
|
||||
last_detail TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_schedules_instance ON schedules(instance_id);
|
||||
CREATE INDEX idx_schedules_enabled ON schedules(enabled) WHERE enabled = TRUE;
|
||||
@@ -0,0 +1,29 @@
|
||||
-- 003_users.sql — local accounts + session cookies.
|
||||
--
|
||||
-- Password hashes are argon2id in PHC string format: all params live in the
|
||||
-- hash itself so we can bump time/memory costs without migrating.
|
||||
--
|
||||
-- Roles are coarse for now — admin (full control) / user (read-only). Fine-
|
||||
-- grained per-resource ACL is a later migration layered on top.
|
||||
|
||||
CREATE TABLE users (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
email TEXT NOT NULL UNIQUE,
|
||||
password_hash TEXT NOT NULL,
|
||||
role TEXT NOT NULL DEFAULT 'admin',
|
||||
disabled BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE sessions (
|
||||
token TEXT PRIMARY KEY, -- 32 bytes hex; random via crypto/rand
|
||||
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
expires_at TIMESTAMPTZ NOT NULL,
|
||||
last_seen_ip TEXT,
|
||||
last_seen_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_sessions_user ON sessions(user_id);
|
||||
CREATE INDEX idx_sessions_expires ON sessions(expires_at);
|
||||
@@ -0,0 +1,20 @@
|
||||
-- 004_event_triggers.sql — event-driven schedule triggers.
|
||||
--
|
||||
-- Until now schedules only supported cron. This migration adds:
|
||||
-- * trigger_kind = 'event' schedules, powered by a CEL expression over
|
||||
-- the event bus. Example: players_online == 0
|
||||
-- * optional sustained-state: fire only after the condition has been
|
||||
-- continuously true for N seconds. Enables "idle shutdown if 0 players
|
||||
-- for 30m" without firing every 60s when the polled player count is 0.
|
||||
--
|
||||
-- cron_spec becomes nullable so event triggers can exist with NULL there.
|
||||
-- Exactly one of cron_spec / event_spec is populated; the application
|
||||
-- enforces this.
|
||||
|
||||
ALTER TABLE schedules ALTER COLUMN cron_spec DROP NOT NULL;
|
||||
|
||||
ALTER TABLE schedules
|
||||
ADD COLUMN event_spec TEXT,
|
||||
ADD COLUMN event_sustained_seconds INTEGER NOT NULL DEFAULT 0;
|
||||
|
||||
CREATE INDEX idx_schedules_trigger_kind ON schedules(trigger_kind);
|
||||
@@ -0,0 +1,18 @@
|
||||
-- 005_backups.sql — instance backups (tar.gz of volumes).
|
||||
--
|
||||
-- The agent writes the archive to disk under its --backup-dir and
|
||||
-- reports back path + size. Controller persists the row so operators
|
||||
-- can list / restore / delete later.
|
||||
|
||||
CREATE TABLE backups (
|
||||
id TEXT PRIMARY KEY, -- bkp_<hex16>
|
||||
instance_id TEXT NOT NULL,
|
||||
agent_id TEXT NOT NULL,
|
||||
path TEXT NOT NULL, -- absolute path on the agent
|
||||
size_bytes BIGINT NOT NULL DEFAULT 0,
|
||||
description TEXT NOT NULL DEFAULT '',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_backups_instance ON backups(instance_id);
|
||||
CREATE INDEX idx_backups_created ON backups(created_at DESC);
|
||||
@@ -0,0 +1,21 @@
|
||||
-- 006_pair_tokens.sql — pairing tokens for agent mTLS onboarding.
|
||||
--
|
||||
-- An operator generates a token, hands it to a new agent, the agent uses
|
||||
-- it exactly once to fetch a signed client cert. Tokens expire fast
|
||||
-- (default 15 min) to limit damage from leaked tokens. token_hash is
|
||||
-- sha256 of the raw token — we never store the raw value.
|
||||
|
||||
CREATE TABLE pair_tokens (
|
||||
id TEXT PRIMARY KEY, -- pt_<hex16>
|
||||
token_hash TEXT NOT NULL UNIQUE, -- sha256 hex of the raw token
|
||||
description TEXT NOT NULL DEFAULT '',
|
||||
created_by BIGINT NOT NULL REFERENCES users(id) ON DELETE SET NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
expires_at TIMESTAMPTZ NOT NULL,
|
||||
used_at TIMESTAMPTZ,
|
||||
used_by_agent TEXT, -- agent_id that consumed it
|
||||
issued_cert_fingerprint TEXT
|
||||
);
|
||||
|
||||
CREATE INDEX idx_pair_tokens_hash ON pair_tokens(token_hash);
|
||||
CREATE INDEX idx_pair_tokens_expires ON pair_tokens(expires_at);
|
||||
@@ -0,0 +1,8 @@
|
||||
-- 007_agent_labels.sql — operator-set display metadata for agents.
|
||||
--
|
||||
-- agent_id is the cryptographic identity (fixed, used in cert CN). Label
|
||||
-- is a friendly name the operator assigns + can change. Notes is free-form.
|
||||
|
||||
ALTER TABLE agents
|
||||
ADD COLUMN label TEXT NOT NULL DEFAULT '',
|
||||
ADD COLUMN notes TEXT NOT NULL DEFAULT '';
|
||||
@@ -0,0 +1,25 @@
|
||||
-- 008_steam_credentials.sql — operator's cached Steam login for SteamCMD.
|
||||
--
|
||||
-- Some games (DayZ app 223350, Arma, a handful of others) require a Steam
|
||||
-- account that owns the game — anonymous SteamCMD login returns
|
||||
-- "No subscription". The panel stores one Steam credential per operator
|
||||
-- (for v1 we only support single-user mode, so a single row is enough).
|
||||
--
|
||||
-- password_ciphertext is AES-GCM encrypted using a key derived from the
|
||||
-- controller's CA private key via HKDF-SHA256; see auth.go's
|
||||
-- steamCredentialKey(). The plaintext password never touches disk or logs.
|
||||
--
|
||||
-- sentry_file is the SSFN-style device-auth blob SteamCMD writes after a
|
||||
-- successful Steam Guard challenge. Storing it lets subsequent logins skip
|
||||
-- the 2FA prompt. NULL until first successful login.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS steam_credentials (
|
||||
id SERIAL PRIMARY KEY,
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
password_ciphertext BYTEA NOT NULL,
|
||||
password_nonce BYTEA NOT NULL,
|
||||
sentry_file BYTEA, -- SSFN device-auth blob, filled after successful login
|
||||
last_used_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
@@ -0,0 +1,22 @@
|
||||
-- 009_steam_login.sql — Steam OpenID sign-in support.
|
||||
--
|
||||
-- Adds a nullable `steam_id` column to `users` so an admin or operator
|
||||
-- can log in with their Steam account instead of (or in addition to)
|
||||
-- email + password.
|
||||
--
|
||||
-- steam_id stores the 17-digit SteamID64 as TEXT (not BIGINT) because
|
||||
-- while the value fits in uint64, using TEXT avoids any sign-extension
|
||||
-- surprises across the app / frontend / logs. Unique constraint so one
|
||||
-- Steam account can only be linked to one panel user.
|
||||
--
|
||||
-- password_hash is made nullable: a user may exist who only signs in via
|
||||
-- Steam with no local password. The email column stays NOT NULL — even
|
||||
-- Steam-only users need a panel identity, we auto-fill with the
|
||||
-- Steam-derived email "steam+<steamid>@panel.local" when there's nothing
|
||||
-- better.
|
||||
|
||||
ALTER TABLE users ADD COLUMN steam_id TEXT;
|
||||
ALTER TABLE users ADD CONSTRAINT users_steam_id_unique UNIQUE (steam_id);
|
||||
ALTER TABLE users ALTER COLUMN password_hash DROP NOT NULL;
|
||||
|
||||
CREATE INDEX idx_users_steam_id ON users(steam_id) WHERE steam_id IS NOT NULL;
|
||||
@@ -0,0 +1,24 @@
|
||||
-- 010_port_allocation.sql — per-agent port range + per-instance assigned ports
|
||||
--
|
||||
-- Why: probing net.Listen for "is this port free?" only catches CURRENTLY
|
||||
-- bound ports. Two ARK servers, one running, one stopped — the running
|
||||
-- one's 7777 binding is detected, but the stopped one's intent isn't.
|
||||
-- Result: a fresh create assigns 7777 to a new server, then the stopped
|
||||
-- one fails to start because something else now owns its port.
|
||||
--
|
||||
-- Fix: persist each instance's assigned host ports in the DB, and let
|
||||
-- operators set a per-agent port range. Allocation now consults BOTH
|
||||
-- the persisted assignments AND the live probe — whichever rules out
|
||||
-- a port first wins.
|
||||
|
||||
ALTER TABLE agents
|
||||
ADD COLUMN port_range_start INTEGER,
|
||||
ADD COLUMN port_range_end INTEGER;
|
||||
|
||||
-- assigned_ports holds the actual host ports the panel allocated for
|
||||
-- this instance, keyed by manifest port name. Example:
|
||||
-- {"game": 7777, "raw": 7778, "rcon": 27020}
|
||||
-- Stays attached even when the instance is stopped — that's the whole
|
||||
-- point: stopped instances reserve their ports against the next create.
|
||||
ALTER TABLE instances
|
||||
ADD COLUMN assigned_ports JSONB NOT NULL DEFAULT '{}'::jsonb;
|
||||
@@ -0,0 +1,47 @@
|
||||
-- 011_opnfwd.sql — wire panel into opnfwd's port-forward HTTP API.
|
||||
--
|
||||
-- Three changes:
|
||||
-- 1. agents.lan_ip — operator-set LAN IP for this agent. opnfwd's `target`
|
||||
-- field needs the LAN-side IP of the host running the game container.
|
||||
-- Defaulted blank; the dashboard's agent edit modal lets the operator
|
||||
-- fill it in. Without it, the opnfwd integration silently no-ops for
|
||||
-- instances on that agent (loud log line, no destructive failure).
|
||||
--
|
||||
-- 2. panel_settings — singleton key/value table for instance-wide knobs.
|
||||
-- Today: opnfwd_enabled / opnfwd_url / opnfwd_token. Future: anything
|
||||
-- else that's "global, operator-edited, doesn't fit elsewhere."
|
||||
--
|
||||
-- 3. instance_forwards — what the panel believes it owns in opnfwd. We
|
||||
-- persist the {nat_uuid, filter_uuid} returned by `POST /api/forwards`
|
||||
-- so we can target opnfwd's bulk-delete on instance delete. Composite
|
||||
-- PK on (instance_id, port_name) — every public port gets one row.
|
||||
|
||||
ALTER TABLE agents ADD COLUMN IF NOT EXISTS lan_ip TEXT NOT NULL DEFAULT '';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS panel_settings (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL DEFAULT '',
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
INSERT INTO panel_settings (key, value) VALUES
|
||||
('opnfwd_enabled', 'false'),
|
||||
('opnfwd_url', ''),
|
||||
('opnfwd_token', '')
|
||||
ON CONFLICT (key) DO NOTHING;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS instance_forwards (
|
||||
instance_id TEXT NOT NULL,
|
||||
port_name TEXT NOT NULL,
|
||||
nat_uuid TEXT NOT NULL DEFAULT '',
|
||||
filter_uuid TEXT NOT NULL DEFAULT '',
|
||||
target_ip TEXT NOT NULL,
|
||||
host_port INTEGER NOT NULL,
|
||||
proto TEXT NOT NULL,
|
||||
descr TEXT NOT NULL DEFAULT '',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
PRIMARY KEY (instance_id, port_name)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS instance_forwards_instance_id_idx
|
||||
ON instance_forwards(instance_id);
|
||||
@@ -0,0 +1,66 @@
|
||||
-- Empyrion EAH-style features. Three tables:
|
||||
--
|
||||
-- 1. empyrion_events — append-only log of unsolicited Event_* packages
|
||||
-- streamed from the bridge. Powers the player-history view, chat log,
|
||||
-- and the "what happened while I was away" overview.
|
||||
--
|
||||
-- 2. empyrion_inv_templates — operator-defined inventory kits (name +
|
||||
-- JSON list of {itemId, count}). Click "Apply" on a player row to
|
||||
-- drop the whole kit via Request_Player_AddItem fan-out.
|
||||
--
|
||||
-- 3. empyrion_known_players — projected from empyrion_events. Cached
|
||||
-- last-seen / first-seen / login count for the players-list view so
|
||||
-- we don't aggregate the events table on every refresh.
|
||||
--
|
||||
-- All tables are scoped by instance_id so two running empyrion instances
|
||||
-- on the same agent (rare but allowed) keep separate state.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS empyrion_events (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
instance_id TEXT NOT NULL,
|
||||
ts TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
cmd TEXT NOT NULL,
|
||||
player_id INTEGER,
|
||||
steam_id TEXT,
|
||||
player_name TEXT,
|
||||
playfield TEXT,
|
||||
msg TEXT,
|
||||
raw JSONB
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS empyrion_events_instance_ts
|
||||
ON empyrion_events (instance_id, ts DESC);
|
||||
CREATE INDEX IF NOT EXISTS empyrion_events_player_ts
|
||||
ON empyrion_events (player_id, ts DESC) WHERE player_id IS NOT NULL;
|
||||
CREATE INDEX IF NOT EXISTS empyrion_events_steam_ts
|
||||
ON empyrion_events (steam_id, ts DESC) WHERE steam_id IS NOT NULL;
|
||||
CREATE INDEX IF NOT EXISTS empyrion_events_cmd
|
||||
ON empyrion_events (instance_id, cmd, ts DESC);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS empyrion_inv_templates (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT NOT NULL DEFAULT '',
|
||||
items JSONB NOT NULL DEFAULT '[]'::jsonb,
|
||||
-- items shape: [{ "itemId": 4307, "count": 1, "ammo": 0, "decay": 0 }, ...]
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS empyrion_inv_templates_name
|
||||
ON empyrion_inv_templates (name);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS empyrion_known_players (
|
||||
instance_id TEXT NOT NULL,
|
||||
player_id INTEGER NOT NULL,
|
||||
steam_id TEXT NOT NULL DEFAULT '',
|
||||
player_name TEXT NOT NULL DEFAULT '',
|
||||
first_seen TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
last_seen TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
login_count INTEGER NOT NULL DEFAULT 0,
|
||||
last_playfield TEXT NOT NULL DEFAULT '',
|
||||
notes TEXT NOT NULL DEFAULT '',
|
||||
PRIMARY KEY (instance_id, player_id)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS empyrion_known_players_steam
|
||||
ON empyrion_known_players (steam_id) WHERE steam_id != '';
|
||||
CREATE INDEX IF NOT EXISTS empyrion_known_players_lastseen
|
||||
ON empyrion_known_players (instance_id, last_seen DESC);
|
||||
@@ -0,0 +1,9 @@
|
||||
CREATE TABLE empyrion_blueprints (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
size_bytes BIGINT NOT NULL,
|
||||
uploaded_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
uploader TEXT,
|
||||
notes TEXT
|
||||
);
|
||||
CREATE INDEX idx_empyrion_blueprints_name ON empyrion_blueprints(name);
|
||||
@@ -0,0 +1,43 @@
|
||||
-- Saved-coordinate book (wdcoordinates_edit equivalent).
|
||||
CREATE TABLE empyrion_coords (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
instance_id TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
playfield TEXT NOT NULL,
|
||||
x DOUBLE PRECISION NOT NULL,
|
||||
y DOUBLE PRECISION NOT NULL,
|
||||
z DOUBLE PRECISION NOT NULL,
|
||||
notes TEXT,
|
||||
created_by TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
UNIQUE (instance_id, name)
|
||||
);
|
||||
CREATE INDEX idx_empyrion_coords_instance ON empyrion_coords(instance_id);
|
||||
|
||||
-- Player-warning log (wdwarnings + ctrlplayerwarnings equivalent).
|
||||
-- Append-only history of admin- or auto-issued warnings.
|
||||
CREATE TABLE empyrion_warnings (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
instance_id TEXT NOT NULL,
|
||||
player_id INT NOT NULL,
|
||||
player_name TEXT,
|
||||
reason TEXT NOT NULL,
|
||||
given_by TEXT,
|
||||
auto_rule TEXT,
|
||||
given_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
CREATE INDEX idx_empyrion_warnings_player ON empyrion_warnings(instance_id, player_id, given_at DESC);
|
||||
|
||||
-- Configurable warning rules (CEL-driven, matches scheduler_event.go's CEL env).
|
||||
CREATE TABLE empyrion_warning_rules (
|
||||
id TEXT PRIMARY KEY,
|
||||
instance_id TEXT, -- NULL = applies to all empyrion instances
|
||||
description TEXT NOT NULL DEFAULT '',
|
||||
cel TEXT NOT NULL,
|
||||
warn_message TEXT NOT NULL,
|
||||
threshold INT NOT NULL DEFAULT 3,
|
||||
threshold_action TEXT NOT NULL DEFAULT 'kick', -- kick | ban | none
|
||||
enabled BOOLEAN NOT NULL DEFAULT true,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
@@ -0,0 +1,19 @@
|
||||
-- Chat-bot configuration: per-instance trigger registry
|
||||
CREATE TABLE empyrion_chatbot_rules (
|
||||
id TEXT PRIMARY KEY,
|
||||
instance_id TEXT, -- NULL = applies to all empyrion instances
|
||||
command TEXT NOT NULL, -- player types "!<command>" in chat
|
||||
description TEXT NOT NULL DEFAULT '',
|
||||
response TEXT NOT NULL, -- supports {playerName} {playerId} {factionId} {playfield} placeholders
|
||||
channel TEXT NOT NULL DEFAULT 'whisper', -- whisper | global | faction
|
||||
enabled BOOLEAN NOT NULL DEFAULT true,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
UNIQUE (instance_id, command)
|
||||
);
|
||||
|
||||
-- Seed three default rules (global, applies to all instances)
|
||||
INSERT INTO empyrion_chatbot_rules (id, instance_id, command, description, response, channel, enabled) VALUES
|
||||
('cb_help', NULL, 'help', 'List available commands', 'Bot commands: !help · !coords · !time', 'whisper', true),
|
||||
('cb_coords', NULL, 'coords', 'Tell player their coords', 'Your position: {playfield} ({x},{y},{z})', 'whisper', true),
|
||||
('cb_time', NULL, 'time', 'Server clock', 'Server time: {now}', 'whisper', true);
|
||||
@@ -0,0 +1,17 @@
|
||||
-- 016_update_checks.sql — WI-14 "update available" detection.
|
||||
--
|
||||
-- One row per instance: the result of the last manual update check
|
||||
-- (installed buildid from the Steam ACF vs the latest buildid for the
|
||||
-- instance's branch from steamcmd app_info). CHECK-ONLY bookkeeping —
|
||||
-- nothing in the panel acts on this table automatically.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS update_checks (
|
||||
instance_id TEXT PRIMARY KEY,
|
||||
app_id TEXT NOT NULL DEFAULT '',
|
||||
branch TEXT NOT NULL DEFAULT '',
|
||||
installed_buildid TEXT NOT NULL DEFAULT '',
|
||||
latest_buildid TEXT NOT NULL DEFAULT '',
|
||||
update_available BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
error TEXT NOT NULL DEFAULT '',
|
||||
checked_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
Reference in New Issue
Block a user