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:
flyemoji committed 2026-06-26 23:31:58 +09:00
1 parent 7fbebfe843
commit 708cdfc5b8
18 files changed
+3475

No files matched your search

+75
View File
@@ -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);