feat(core): add naming, RCON, store, config, and image-build libraries
Foundational libraries: deterministic resource naming, the RCON client, the Postgres store with embedded SQL migrations, configuration loading, and container image-build helpers.
This commit is contained in:
18 files changed
+3475
No files matched your search
@@ -0,0 +1,75 @@
|
||||
-- Felis business-layer schema (spec §6). The CRD is the lifecycle
|
||||
-- source-of-truth; this database owns only what the CRD cannot express:
|
||||
-- ownership/claim, account links, quotas, image admission, builds, backups,
|
||||
-- audit. Authoritative CRD fields are never duplicated here.
|
||||
|
||||
CREATE TYPE user_role AS ENUM ('admin','user');
|
||||
CREATE TYPE build_status AS ENUM ('pending','building','succeeded','failed','cancelled');
|
||||
CREATE TYPE backup_status AS ENUM ('present','expired','deleted');
|
||||
|
||||
CREATE TABLE users (
|
||||
id text PRIMARY KEY, username text UNIQUE NOT NULL, email text,
|
||||
role user_role NOT NULL DEFAULT 'user', created_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- Identity bridge: web identity <-> MC UUID (claim/owner-only/allowlist rely on it).
|
||||
CREATE TABLE account_links (
|
||||
user_id text NOT NULL REFERENCES users(id), mc_uuid uuid NOT NULL,
|
||||
verified_at timestamptz NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (user_id, mc_uuid), UNIQUE (mc_uuid)
|
||||
);
|
||||
CREATE TABLE account_link_codes ( code text PRIMARY KEY, mc_uuid uuid NOT NULL, expires_at timestamptz NOT NULL );
|
||||
|
||||
CREATE TABLE quotas (
|
||||
user_id text PRIMARY KEY REFERENCES users(id),
|
||||
max_servers int, max_cpu_milli int, max_memory_mb int, max_storage_gb int
|
||||
);
|
||||
|
||||
-- Business projection: the CRD lives in K8s; this stores only the
|
||||
-- ownership/activity/warning that the CRD cannot express, plus a fast-query cache.
|
||||
CREATE TABLE servers (
|
||||
name text PRIMARY KEY, -- matches CRD metadata.name
|
||||
owner_id text REFERENCES users(id), -- NULL until claimed; reaper resets to NULL
|
||||
claimed_at timestamptz,
|
||||
last_active_at timestamptz NOT NULL DEFAULT now(), -- max(last human join, created_at)
|
||||
warned_3d_at timestamptz, warned_1d_at timestamptz, -- reaper warning dedup; cleared on renewal
|
||||
cached_phase text, -- CRD status projection, non-authoritative
|
||||
created_at timestamptz NOT NULL DEFAULT now(), deleted_at timestamptz
|
||||
);
|
||||
CREATE TABLE server_aliases ( subdomain text PRIMARY KEY, server_name text NOT NULL REFERENCES servers(name) );
|
||||
CREATE TABLE server_allowlist ( -- autostartPolicy=allowlist; first join auto-appends
|
||||
server_name text NOT NULL REFERENCES servers(name), mc_uuid uuid NOT NULL,
|
||||
added_at timestamptz NOT NULL DEFAULT now(), PRIMARY KEY (server_name, mc_uuid)
|
||||
);
|
||||
|
||||
-- Image admission (dynamic, auditable -> DB, not toml).
|
||||
CREATE TABLE image_whitelist (
|
||||
image_ref text PRIMARY KEY, -- registry/foo:1.0 or registry/foo:*
|
||||
source text NOT NULL DEFAULT 'built', -- built (cluster build) | external (pushed)
|
||||
build_id text, added_by text NOT NULL, enabled boolean NOT NULL DEFAULT true,
|
||||
added_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
CREATE TABLE image_builds (
|
||||
id text PRIMARY KEY, image_ref text NOT NULL, status build_status NOT NULL DEFAULT 'pending',
|
||||
dockerfile text NOT NULL, -- archived for audit
|
||||
context_ref text, base_image text, -- resolved FROM, audit
|
||||
requested_by text NOT NULL, job_name text, log_ref text, error text,
|
||||
created_at timestamptz NOT NULL DEFAULT now(), finished_at timestamptz
|
||||
);
|
||||
|
||||
-- World backups (reaper output; not FK'd to servers, which may be reset/deleted).
|
||||
CREATE TABLE world_backups (
|
||||
id text PRIMARY KEY, server_name text NOT NULL, former_owner text,
|
||||
backup_ref text NOT NULL, -- WorldArchiver location (ArchiveRef)
|
||||
size_bytes bigint, reason text NOT NULL, -- inactive_15d | manual
|
||||
status backup_status NOT NULL DEFAULT 'present',
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
expires_at timestamptz NOT NULL, -- created_at + 3mo
|
||||
deleted_at timestamptz
|
||||
);
|
||||
|
||||
CREATE TABLE audit_logs (
|
||||
id bigserial PRIMARY KEY, actor text NOT NULL, source text NOT NULL, action text NOT NULL,
|
||||
server_name text, request_id text, payload jsonb, created_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
CREATE TABLE tokens ( id text PRIMARY KEY, name text NOT NULL, token_hash text NOT NULL, scope jsonb NOT NULL, expires_at timestamptz );
|
||||
@@ -0,0 +1,42 @@
|
||||
-- User-submitted modpack approval lane (a user-directed extension over the §16
|
||||
-- build subsystem; see internal/submit for provenance). This is the UNTRUSTED-
|
||||
-- origin counterpart to the admin build path (POST /images/build): an ordinary
|
||||
-- user may upload a modpack but cannot start a build directly. Each upload lands
|
||||
-- here as pending_review; an admin must approve it before anything is built, and
|
||||
-- the approved submission then routes through the SAME Trivy-gated Kaniko build
|
||||
-- as an admin build (build subsystem §16). Approval is a human gate layered in
|
||||
-- FRONT of the automatic scan, never instead of it — a CRITICAL CVE still fails
|
||||
-- the build and nothing is admitted even after a human approved.
|
||||
--
|
||||
-- Source of truth (spec §1): this row is the Postgres BUSINESS authority for the
|
||||
-- approval (verdict + reviewer); the build EXECUTION lives in image_builds,
|
||||
-- linked by build_id once Builder.Submit succeeds. The approval never copies the
|
||||
-- build's authoritative fields.
|
||||
--
|
||||
-- Trust note: the platform derives BOTH the push target (image_ref) and the
|
||||
-- build context (context_ref) from the submission id — neither is free-form user
|
||||
-- input — so an untrusted submitter can never point the build at an arbitrary
|
||||
-- source or collide with the platform image namespace. There is deliberately no
|
||||
-- `origin` column: image_submissions is ONLY the user-upload lane (the platform
|
||||
-- uses the direct build path), and submitted_by already records the origin.
|
||||
|
||||
CREATE TYPE submission_status AS ENUM ('pending_review','approved','rejected');
|
||||
|
||||
CREATE TABLE image_submissions (
|
||||
id text PRIMARY KEY, -- lowercase, namespaces the derived image/context refs
|
||||
submitted_by text NOT NULL, -- uploading user's id (untrusted origin)
|
||||
display_name text NOT NULL, -- human-friendly label for the modpack
|
||||
context_ref text NOT NULL, -- DERIVED pinned build context (not user-supplied)
|
||||
status submission_status NOT NULL DEFAULT 'pending_review',
|
||||
image_ref text, -- DERIVED {registry}/user-uploads/{id}:latest, set at approve
|
||||
build_id text, -- image_builds.id, set only after Builder.Submit succeeds
|
||||
reviewed_by text, -- admin who approved/rejected
|
||||
reject_reason text, -- set on rejection
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
reviewed_at timestamptz
|
||||
);
|
||||
|
||||
-- The admin review queue scans by status (pending first); the per-user index
|
||||
-- serves the "my submissions" list.
|
||||
CREATE INDEX image_submissions_status_idx ON image_submissions (status, created_at);
|
||||
CREATE INDEX image_submissions_submitted_by_idx ON image_submissions (submitted_by, created_at DESC);
|
||||
Reference in new issue
Block a user