forked from wrenn/wrenn
v0.2.0 (#50)
Co-authored-by: Tasnim Kabir Sadik <tksadik@omukk.dev> Reviewed-on: wrenn/wrenn#50
This commit is contained in:
21
db/migrations/20260518200117_add_sessions.sql
Normal file
21
db/migrations/20260518200117_add_sessions.sql
Normal file
@ -0,0 +1,21 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
CREATE TABLE sessions (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
team_id UUID NOT NULL REFERENCES teams(id) ON DELETE CASCADE,
|
||||
csrf_token TEXT NOT NULL,
|
||||
user_agent TEXT NOT NULL DEFAULT '',
|
||||
ip_address TEXT NOT NULL DEFAULT '',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
last_seen_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
expires_at TIMESTAMPTZ NOT NULL
|
||||
);
|
||||
CREATE INDEX sessions_user_id_idx ON sessions(user_id);
|
||||
CREATE INDEX sessions_expires_at_idx ON sessions(expires_at);
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP TABLE IF EXISTS sessions;
|
||||
-- +goose StatementEnd
|
||||
15
db/migrations/20260519231056_hash_session_ids.sql
Normal file
15
db/migrations/20260519231056_hash_session_ids.sql
Normal file
@ -0,0 +1,15 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
-- Session IDs are now stored as sha256(raw_sid) hex so a DB/Redis dump
|
||||
-- cannot be replayed as session cookies. Existing sessions hold raw SIDs
|
||||
-- in id; they are unrecoverable under the new scheme and must be wiped.
|
||||
-- Users will need to log in again after this migration.
|
||||
TRUNCATE TABLE sessions;
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
-- Down: nothing to do schematically. Hashed rows remain but will never
|
||||
-- match a raw cookie under the old code path; safest is to wipe again.
|
||||
TRUNCATE TABLE sessions;
|
||||
-- +goose StatementEnd
|
||||
49
db/migrations/20260522154716_seed_system_base_templates.sql
Normal file
49
db/migrations/20260522154716_seed_system_base_templates.sql
Normal file
@ -0,0 +1,49 @@
|
||||
-- +goose Up
|
||||
|
||||
-- Replace the old all-zeros "minimal" base template with the four system base
|
||||
-- templates (ubuntu/alpine/arch/fedora). All are platform-owned (team_id
|
||||
-- all-zeros) with reserved template IDs 0..3, default user wrenn-user.
|
||||
--
|
||||
-- Template IDs are well-known: the all-zeros UUID + low byte = {0,1,2,3}.
|
||||
-- On disk each lives at images/teams/{base36(0)}/{base36(id)}/rootfs.ext4.
|
||||
|
||||
-- 0 → minimal-ubuntu (was "minimal").
|
||||
UPDATE templates
|
||||
SET name = 'minimal-ubuntu',
|
||||
default_user = 'wrenn-user'
|
||||
WHERE id = '00000000-0000-0000-0000-000000000000';
|
||||
|
||||
-- Seed the row if it did not already exist (fresh DBs).
|
||||
INSERT INTO templates (id, name, type, vcpus, memory_mb, size_bytes, team_id, default_user)
|
||||
VALUES ('00000000-0000-0000-0000-000000000000', 'minimal-ubuntu', 'base', 1, 512, 0,
|
||||
'00000000-0000-0000-0000-000000000000', 'wrenn-user')
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
-- 1 → minimal-alpine, 2 → minimal-arch, 3 → minimal-fedora.
|
||||
INSERT INTO templates (id, name, type, vcpus, memory_mb, size_bytes, team_id, default_user)
|
||||
VALUES
|
||||
('00000000-0000-0000-0000-000000000001', 'minimal-alpine', 'base', 1, 512, 0,
|
||||
'00000000-0000-0000-0000-000000000000', 'wrenn-user'),
|
||||
('00000000-0000-0000-0000-000000000002', 'minimal-arch', 'base', 1, 512, 0,
|
||||
'00000000-0000-0000-0000-000000000000', 'wrenn-user'),
|
||||
('00000000-0000-0000-0000-000000000003', 'minimal-fedora', 'base', 1, 512, 0,
|
||||
'00000000-0000-0000-0000-000000000000', 'wrenn-user')
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
-- Point the sandboxes.template column default at the new default base template.
|
||||
ALTER TABLE sandboxes ALTER COLUMN template SET DEFAULT 'minimal-ubuntu';
|
||||
|
||||
-- +goose Down
|
||||
|
||||
ALTER TABLE sandboxes ALTER COLUMN template SET DEFAULT 'minimal';
|
||||
|
||||
DELETE FROM templates WHERE id IN (
|
||||
'00000000-0000-0000-0000-000000000001',
|
||||
'00000000-0000-0000-0000-000000000002',
|
||||
'00000000-0000-0000-0000-000000000003'
|
||||
);
|
||||
|
||||
UPDATE templates
|
||||
SET name = 'minimal',
|
||||
default_user = 'root'
|
||||
WHERE id = '00000000-0000-0000-0000-000000000000';
|
||||
@ -13,7 +13,36 @@ SELECT * FROM hosts ORDER BY created_at DESC;
|
||||
SELECT * FROM hosts WHERE type = $1 ORDER BY created_at DESC;
|
||||
|
||||
-- name: ListHostsByTeam :many
|
||||
SELECT * FROM hosts WHERE team_id = $1 AND type = 'byoc' ORDER BY created_at DESC;
|
||||
-- Returns hosts by team with per-host sandbox resource consumption aggregated.
|
||||
-- Follows the same aggregation pattern as ListHostsAdmin and GetHostsWithLoad.
|
||||
SELECT
|
||||
h.id,
|
||||
h.type,
|
||||
h.team_id,
|
||||
h.provider,
|
||||
h.availability_zone,
|
||||
h.arch,
|
||||
h.cpu_cores,
|
||||
h.memory_mb,
|
||||
h.disk_gb,
|
||||
h.address,
|
||||
h.status,
|
||||
h.last_heartbeat_at,
|
||||
h.metadata,
|
||||
h.created_by,
|
||||
h.created_at,
|
||||
h.updated_at,
|
||||
COALESCE(SUM(s.vcpus) FILTER (WHERE s.status IN ('running', 'starting', 'pending')), 0)::int AS running_vcpus,
|
||||
COALESCE(SUM(s.memory_mb) FILTER (WHERE s.status IN ('running', 'starting', 'pending')), 0)::int AS running_memory_mb,
|
||||
COALESCE(SUM(s.disk_size_mb) FILTER (WHERE s.status IN ('running', 'starting', 'pending')), 0)::int AS running_disk_mb,
|
||||
COALESCE(SUM(s.memory_mb) FILTER (WHERE s.status = 'paused'), 0)::int AS paused_memory_mb,
|
||||
COALESCE(SUM(s.disk_size_mb) FILTER (WHERE s.status = 'paused'), 0)::int AS paused_disk_mb
|
||||
FROM hosts h
|
||||
LEFT JOIN sandboxes s ON s.host_id = h.id
|
||||
AND s.status IN ('running', 'paused', 'starting', 'pending')
|
||||
WHERE h.team_id = $1 AND h.type = 'byoc'
|
||||
GROUP BY h.id
|
||||
ORDER BY h.created_at DESC;
|
||||
|
||||
-- name: ListHostsByStatus :many
|
||||
SELECT * FROM hosts WHERE status = $1 ORDER BY created_at DESC;
|
||||
@ -101,8 +130,6 @@ SELECT
|
||||
h.created_by,
|
||||
h.created_at,
|
||||
h.updated_at,
|
||||
h.cert_fingerprint,
|
||||
h.cert_expires_at,
|
||||
COALESCE(SUM(s.vcpus) FILTER (WHERE s.status IN ('running', 'starting', 'pending')), 0)::int AS running_vcpus,
|
||||
COALESCE(SUM(s.memory_mb) FILTER (WHERE s.status IN ('running', 'starting', 'pending')), 0)::int AS running_memory_mb,
|
||||
COALESCE(SUM(s.disk_size_mb) FILTER (WHERE s.status IN ('running', 'starting', 'pending')), 0)::int AS running_disk_mb,
|
||||
@ -125,5 +152,37 @@ SET last_heartbeat_at = NOW(),
|
||||
updated_at = NOW()
|
||||
WHERE id = $1;
|
||||
|
||||
-- name: ListHostsAdmin :many
|
||||
-- Returns all hosts with per-host sandbox resource consumption aggregated.
|
||||
-- Unlike GetHostsWithLoad, this returns ALL hosts (not just online) so admins
|
||||
-- can see resource usage across the entire fleet including offline/pending hosts.
|
||||
SELECT
|
||||
h.id,
|
||||
h.type,
|
||||
h.team_id,
|
||||
h.provider,
|
||||
h.availability_zone,
|
||||
h.arch,
|
||||
h.cpu_cores,
|
||||
h.memory_mb,
|
||||
h.disk_gb,
|
||||
h.address,
|
||||
h.status,
|
||||
h.last_heartbeat_at,
|
||||
h.metadata,
|
||||
h.created_by,
|
||||
h.created_at,
|
||||
h.updated_at,
|
||||
COALESCE(SUM(s.vcpus) FILTER (WHERE s.status IN ('running', 'starting', 'pending')), 0)::int AS running_vcpus,
|
||||
COALESCE(SUM(s.memory_mb) FILTER (WHERE s.status IN ('running', 'starting', 'pending')), 0)::int AS running_memory_mb,
|
||||
COALESCE(SUM(s.disk_size_mb) FILTER (WHERE s.status IN ('running', 'starting', 'pending')), 0)::int AS running_disk_mb,
|
||||
COALESCE(SUM(s.memory_mb) FILTER (WHERE s.status = 'paused'), 0)::int AS paused_memory_mb,
|
||||
COALESCE(SUM(s.disk_size_mb) FILTER (WHERE s.status = 'paused'), 0)::int AS paused_disk_mb
|
||||
FROM hosts h
|
||||
LEFT JOIN sandboxes s ON s.host_id = h.id
|
||||
AND s.status IN ('running', 'paused', 'starting', 'pending')
|
||||
GROUP BY h.id
|
||||
ORDER BY h.created_at DESC;
|
||||
|
||||
-- name: MarkHostUnreachable :exec
|
||||
UPDATE hosts SET status = 'unreachable', updated_at = NOW() WHERE id = $1;
|
||||
|
||||
@ -42,6 +42,13 @@ ORDER BY ts ASC;
|
||||
DELETE FROM sandbox_metric_points
|
||||
WHERE sandbox_id = $1;
|
||||
|
||||
-- name: GetLatestSandboxMetricPoint :one
|
||||
SELECT ts, cpu_pct, mem_bytes, disk_bytes
|
||||
FROM sandbox_metric_points
|
||||
WHERE sandbox_id = $1
|
||||
ORDER BY ts DESC
|
||||
LIMIT 1;
|
||||
|
||||
-- name: DeleteSandboxMetricPointsByTier :exec
|
||||
DELETE FROM sandbox_metric_points
|
||||
WHERE sandbox_id = $1 AND tier = $2;
|
||||
|
||||
@ -72,7 +72,7 @@ ORDER BY created_at DESC;
|
||||
UPDATE sandboxes
|
||||
SET status = 'missing',
|
||||
last_updated = NOW()
|
||||
WHERE host_id = $1 AND status IN ('running', 'starting', 'pending');
|
||||
WHERE host_id = $1 AND status IN ('running', 'starting', 'pending', 'pausing', 'resuming', 'stopping');
|
||||
|
||||
-- name: UpdateSandboxMetadata :exec
|
||||
UPDATE sandboxes
|
||||
@ -80,10 +80,42 @@ SET metadata = $2,
|
||||
last_updated = NOW()
|
||||
WHERE id = $1;
|
||||
|
||||
-- name: BulkRestoreRunning :exec
|
||||
-- Called by the reconciler when a host comes back online and its sandboxes are
|
||||
-- confirmed alive. Restores only sandboxes that are in 'missing' state.
|
||||
-- name: UpdateSandboxRunningIf :one
|
||||
-- Conditionally transition a sandbox to running only if the current status
|
||||
-- matches the expected value. Prevents races where a user destroys a sandbox
|
||||
-- while the create/resume goroutine is still in-flight.
|
||||
UPDATE sandboxes
|
||||
SET status = 'running',
|
||||
SET status = 'running',
|
||||
host_ip = $3,
|
||||
guest_ip = $4,
|
||||
started_at = $5,
|
||||
last_active_at = $5,
|
||||
last_updated = NOW()
|
||||
WHERE id = $1 AND status = $2
|
||||
RETURNING *;
|
||||
|
||||
-- name: UpdateSandboxStatusIf :one
|
||||
-- Atomically update status only when the current status matches the expected value.
|
||||
-- Prevents background goroutines from overwriting a status that has since changed
|
||||
-- (e.g. user destroyed a sandbox while Create was in-flight).
|
||||
UPDATE sandboxes
|
||||
SET status = $3,
|
||||
last_updated = NOW()
|
||||
WHERE id = $1 AND status = $2
|
||||
RETURNING *;
|
||||
|
||||
-- name: BulkRestoreMissingToStatus :exec
|
||||
-- Called by the reconciler when a host comes back online and its sandboxes are
|
||||
-- confirmed alive. Restores only sandboxes currently in 'missing' state to the
|
||||
-- given target status (typically 'running' or 'paused' based on the live state
|
||||
-- reported by the host agent's ListSandboxes RPC).
|
||||
UPDATE sandboxes
|
||||
SET status = $2,
|
||||
last_updated = NOW()
|
||||
WHERE id = ANY($1::uuid[]) AND status = 'missing';
|
||||
|
||||
-- name: UpdateSandboxDiskSize :exec
|
||||
UPDATE sandboxes
|
||||
SET disk_size_mb = $2,
|
||||
last_updated = NOW()
|
||||
WHERE id = $1;
|
||||
|
||||
28
db/queries/sessions.sql
Normal file
28
db/queries/sessions.sql
Normal file
@ -0,0 +1,28 @@
|
||||
-- name: InsertSession :one
|
||||
INSERT INTO sessions (id, user_id, team_id, csrf_token, user_agent, ip_address, expires_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||
RETURNING *;
|
||||
|
||||
-- name: GetSession :one
|
||||
SELECT * FROM sessions WHERE id = $1;
|
||||
|
||||
-- name: TouchSession :exec
|
||||
UPDATE sessions SET last_seen_at = NOW() WHERE id = $1;
|
||||
|
||||
-- name: UpdateSessionTeam :exec
|
||||
UPDATE sessions SET team_id = $2 WHERE id = $1;
|
||||
|
||||
-- name: DeleteSession :exec
|
||||
DELETE FROM sessions WHERE id = $1;
|
||||
|
||||
-- name: DeleteSessionForUser :exec
|
||||
DELETE FROM sessions WHERE id = $1 AND user_id = $2;
|
||||
|
||||
-- name: ListSessionsByUserID :many
|
||||
SELECT * FROM sessions WHERE user_id = $1 ORDER BY last_seen_at DESC;
|
||||
|
||||
-- name: DeleteSessionsByUserID :many
|
||||
DELETE FROM sessions WHERE user_id = $1 RETURNING id;
|
||||
|
||||
-- name: DeleteExpiredSessions :exec
|
||||
DELETE FROM sessions WHERE expires_at < NOW();
|
||||
@ -66,7 +66,9 @@ SELECT
|
||||
COALESCE(owner_u.name, '') AS owner_name,
|
||||
COALESCE(owner_u.email, '') AS owner_email,
|
||||
(SELECT COUNT(*) FROM sandboxes s WHERE s.team_id = t.id AND s.status IN ('running', 'paused', 'starting'))::int AS active_sandbox_count,
|
||||
(SELECT COUNT(*) FROM channels c WHERE c.team_id = t.id)::int AS channel_count
|
||||
(SELECT COUNT(*) FROM channels c WHERE c.team_id = t.id)::int AS channel_count,
|
||||
COALESCE((SELECT SUM(s.vcpus) FROM sandboxes s WHERE s.team_id = t.id AND s.status IN ('running', 'paused', 'starting')), 0)::int AS running_vcpus,
|
||||
COALESCE((SELECT SUM(s.memory_mb) FROM sandboxes s WHERE s.team_id = t.id AND s.status IN ('running', 'paused', 'starting')), 0)::int AS running_memory_mb
|
||||
FROM teams t
|
||||
LEFT JOIN users_teams owner_ut ON owner_ut.team_id = t.id AND owner_ut.role = 'owner'
|
||||
LEFT JOIN users owner_u ON owner_u.id = owner_ut.user_id
|
||||
|
||||
@ -42,6 +42,9 @@ DELETE FROM templates WHERE name = $1 AND team_id = $2;
|
||||
-- Bulk delete all templates owned by a team (for team soft-delete cleanup).
|
||||
DELETE FROM templates WHERE team_id = $1;
|
||||
|
||||
-- name: UpdateTemplateSize :exec
|
||||
UPDATE templates SET size_bytes = $2 WHERE id = $1;
|
||||
|
||||
-- name: ListTemplatesByTeamOnly :many
|
||||
-- List templates owned by a specific team (NOT including platform templates).
|
||||
SELECT * FROM templates WHERE team_id = $1 ORDER BY created_at DESC;
|
||||
|
||||
Reference in New Issue
Block a user