New Phase 1 Platform database — PostgreSQL, separate from the Lab image's own SQLite (which keeps its current students/progress data untouched, decision #2 / correction C in docs/LAB_ARCHITECTURE.md). Schema (platform/provisioner/migrations/): - lab_hosts: the abstraction that keeps Docker connectivity out of code — endpoint (unix:// or tcp://+TLS), enabled flag. Phase 1 ships one row. - environments: environment_id (UUID), student_id (opaque external identifier, no FK — students table isn't in this database yet), lab_host_id (real FK), status, routing_token (CSPRNG via crypto.randomBytes, opaque), container/ network identity, resource limits, lifecycle timestamps. - environment_events: append-only audit trail, one row per lifecycle transition, never logs the routing token in plaintext (see lifecycle.js's redact()). File-based migration runner (db.js) matching the check-before-alter idempotency pattern already used in the Lab image's backend/server.js, applied on every Provisioner boot. Resource limits and the lab_hosts seed are config files (config/resource-policy.yaml, config/lab-hosts.seed.yaml), not database tables — there's no admin UI to manage them via a database yet, so a DB-backed policy table would just be a write path nothing uses. Co-Authored-By: Claude Sonnet 5 <[email protected]>
52 lines
2.6 KiB
SQL
52 lines
2.6 KiB
SQL
-- environments — the disposable per-student lab registry.
|
|
-- See docs/LAB_ARCHITECTURE.md §3 and docs/PHASE1_PLATFORM_FOUNDATION.md §2.
|
|
|
|
DO $$ BEGIN
|
|
CREATE TYPE environment_status AS ENUM (
|
|
'requested', 'provisioning', 'running', 'resetting',
|
|
'suspended', -- experimental path only, see decision #3 / correction A
|
|
'destroying', 'destroyed', 'error'
|
|
);
|
|
EXCEPTION
|
|
WHEN duplicate_object THEN NULL;
|
|
END $$;
|
|
|
|
CREATE TABLE IF NOT EXISTS environments (
|
|
environment_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
student_id TEXT NOT NULL, -- opaque external identifier (correction C) —
|
|
-- no FK: students table isn't in this database
|
|
-- until a later, separate Platform DB migration.
|
|
-- The Provisioner trusts its caller on this value.
|
|
lab_host_id TEXT NOT NULL REFERENCES lab_hosts(lab_host_id), -- real FK: both
|
|
-- tables are Provisioner-owned Postgres.
|
|
status environment_status NOT NULL DEFAULT 'requested',
|
|
routing_token TEXT NOT NULL, -- CSPRNG, opaque, unique
|
|
container_id TEXT,
|
|
container_name TEXT,
|
|
network_id TEXT,
|
|
network_name TEXT,
|
|
network_subnet CIDR, -- needed to install/remove the per-env firewall chain
|
|
image_ref TEXT NOT NULL,
|
|
cpu_limit NUMERIC NOT NULL,
|
|
mem_limit_mb INTEGER NOT NULL,
|
|
pids_limit INTEGER NOT NULL,
|
|
storage_limit_mb INTEGER NOT NULL,
|
|
internal_ip TEXT,
|
|
internal_port INTEGER NOT NULL DEFAULT 80,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
started_at TIMESTAMPTZ,
|
|
last_active_at TIMESTAMPTZ,
|
|
destroy_at TIMESTAMPTZ, -- idle-reaper's scheduled destroy time
|
|
error_reason TEXT
|
|
);
|
|
|
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_environments_routing_token ON environments(routing_token);
|
|
CREATE INDEX IF NOT EXISTS idx_environments_student ON environments(student_id);
|
|
CREATE INDEX IF NOT EXISTS idx_environments_status ON environments(status);
|
|
CREATE INDEX IF NOT EXISTS idx_environments_lab_host ON environments(lab_host_id);
|
|
|
|
-- Phase 1 invariant "at most one non-terminal environment per student" is enforced in
|
|
-- application logic (lifecycle.js), not a DB constraint yet — a partial unique index is the
|
|
-- natural upgrade once this needs to be bulletproof under concurrent requests, not needed for
|
|
-- a single-instance provisioner in this phase.
|