Data Model
Overview
The data model defines how Nexus persists and manages state using SQLite. This includes VM configurations, workspace metadata, networking, and operational state.
An abstraction layer over SQLite allows swapping to Postgres or etcd for clustering.
Schema
-- Nexus Database Schema (Pre-Alpha)
--
-- During pre-alpha, schema changes are applied by:
-- 1. Updating this file
-- 2. Deleting the database file
-- 3. Restarting the daemon (schema recreates automatically)
-- Application settings (key-value store)
CREATE TABLE settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
type TEXT NOT NULL CHECK(type IN ('string', 'int', 'bool', 'json'))
);
-- Tags for organizational categorization
CREATE TABLE tags (
name TEXT PRIMARY KEY,
description TEXT,
color TEXT, -- hex color for UI display (e.g., "#FF5733")
text_color TEXT -- hex color for contrast (e.g., "#FFFFFF")
);
-- VMs: Firecracker microVM instances
CREATE TABLE vms (
id TEXT PRIMARY KEY,
name TEXT UNIQUE,
role TEXT NOT NULL CHECK(role IN ('portal', 'work', 'service')),
state TEXT NOT NULL CHECK(state IN ('created', 'running', 'stopped', 'crashed', 'failed')),
cid INTEGER NOT NULL UNIQUE, -- vsock context ID
vcpu_count INTEGER NOT NULL DEFAULT 1,
mem_size_mib INTEGER NOT NULL DEFAULT 128,
config_json TEXT, -- full Firecracker config snapshot
pid INTEGER, -- Firecracker process ID (NULL when not running)
socket_path TEXT, -- Firecracker API socket (NULL when not running)
uds_path TEXT, -- vsock UDS base path
console_log_path TEXT,
created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
started_at INTEGER,
stopped_at INTEGER
);
-- Master images: read-only btrfs subvolumes
CREATE TABLE master_images (
id TEXT PRIMARY KEY,
name TEXT NOT NULL UNIQUE,
subvolume_path TEXT NOT NULL UNIQUE,
size_bytes INTEGER,
created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
);
-- Workspaces: btrfs subvolume snapshots assigned to VMs
CREATE TABLE workspaces (
id TEXT PRIMARY KEY,
name TEXT UNIQUE,
vm_id TEXT, -- NULL if unattached
subvolume_path TEXT NOT NULL UNIQUE,
master_image_id TEXT, -- master image this was snapshotted from
parent_workspace_id TEXT, -- NULL if snapshotted from base
size_bytes INTEGER,
is_root_device INTEGER NOT NULL DEFAULT 0 CHECK(is_root_device IN (0, 1)),
is_read_only INTEGER NOT NULL DEFAULT 0 CHECK(is_read_only IN (0, 1)),
attached_at INTEGER,
detached_at INTEGER,
created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
FOREIGN KEY (vm_id) REFERENCES vms(id) ON DELETE SET NULL,
FOREIGN KEY (master_image_id) REFERENCES master_images(id) ON DELETE RESTRICT,
FOREIGN KEY (parent_workspace_id) REFERENCES workspaces(id) ON DELETE SET NULL
);
-- VM boot history: tracks each boot/shutdown cycle
CREATE TABLE vm_boot_history (
id TEXT PRIMARY KEY,
vm_id TEXT NOT NULL,
boot_started_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
boot_stopped_at INTEGER,
exit_code INTEGER,
error_message TEXT,
console_log_path TEXT,
FOREIGN KEY (vm_id) REFERENCES vms(id) ON DELETE CASCADE
);
-- vsock routes: inter-VM communication mediated by Nexus
CREATE TABLE routes (
id TEXT PRIMARY KEY,
source_vm_id TEXT NOT NULL,
target_vm_id TEXT NOT NULL,
source_port INTEGER NOT NULL,
target_port INTEGER NOT NULL,
created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
FOREIGN KEY (source_vm_id) REFERENCES vms(id) ON DELETE CASCADE,
FOREIGN KEY (target_vm_id) REFERENCES vms(id) ON DELETE CASCADE,
UNIQUE (source_vm_id, source_port)
);
-- vsock services registered by guest agents
CREATE TABLE vsock_services (
id TEXT PRIMARY KEY,
vm_id TEXT NOT NULL,
port INTEGER NOT NULL,
service_name TEXT NOT NULL,
state TEXT NOT NULL DEFAULT 'stopped' CHECK(state IN ('listening', 'stopped')),
created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
FOREIGN KEY (vm_id) REFERENCES vms(id) ON DELETE CASCADE,
UNIQUE (vm_id, port)
);
-- Network bridges
CREATE TABLE bridges (
name TEXT PRIMARY KEY,
subnet TEXT NOT NULL, -- CIDR notation (e.g., "172.16.0.0/24")
gateway TEXT NOT NULL, -- gateway IP (e.g., "172.16.0.1")
interface TEXT NOT NULL, -- host interface name
created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
);
-- VM network configuration
CREATE TABLE vm_network (
vm_id TEXT PRIMARY KEY,
ip_address TEXT NOT NULL,
bridge_name TEXT NOT NULL,
FOREIGN KEY (vm_id) REFERENCES vms(id) ON DELETE CASCADE,
FOREIGN KEY (bridge_name) REFERENCES bridges(name) ON DELETE RESTRICT
);
-- Firewall rules for VMs (nftables-based)
CREATE TABLE firewall_rules (
id TEXT PRIMARY KEY,
vm_id TEXT NOT NULL,
rule_order INTEGER NOT NULL,
action TEXT NOT NULL CHECK(action IN ('accept', 'drop', 'reject')),
protocol TEXT CHECK(protocol IN ('tcp', 'udp', 'icmp', 'all')),
source_ip TEXT,
source_port TEXT,
dest_ip TEXT,
dest_port TEXT,
description TEXT,
created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
FOREIGN KEY (vm_id) REFERENCES vms(id) ON DELETE CASCADE,
UNIQUE (vm_id, rule_order)
);
-- Tags (organizational)
CREATE TABLE vm_tags (
vm_id TEXT NOT NULL,
tag_name TEXT NOT NULL,
PRIMARY KEY (vm_id, tag_name),
FOREIGN KEY (vm_id) REFERENCES vms(id) ON DELETE CASCADE,
FOREIGN KEY (tag_name) REFERENCES tags(name) ON DELETE CASCADE
);
CREATE TABLE workspace_tags (
workspace_id TEXT NOT NULL,
tag_name TEXT NOT NULL,
PRIMARY KEY (workspace_id, tag_name),
FOREIGN KEY (workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE,
FOREIGN KEY (tag_name) REFERENCES tags(name) ON DELETE CASCADE
);
-- Indexes
CREATE INDEX idx_vms_role ON vms(role);
CREATE INDEX idx_vms_state ON vms(state);
CREATE INDEX idx_workspaces_vm_id ON workspaces(vm_id);
CREATE INDEX idx_workspaces_base ON workspaces(master_image_id);
CREATE INDEX idx_vm_boot_history_vm_id ON vm_boot_history(vm_id);
CREATE INDEX idx_vsock_services_vm_id ON vsock_services(vm_id);
CREATE INDEX idx_routes_source ON routes(source_vm_id);
CREATE INDEX idx_routes_target ON routes(target_vm_id);
CREATE INDEX idx_firewall_rules_vm_id ON firewall_rules(vm_id);
CREATE INDEX idx_vm_tags_tag ON vm_tags(tag_name);
CREATE INDEX idx_workspace_tags_tag ON workspace_tags(tag_name);
-- Partial index: workspace can only be attached to one VM at a time
CREATE UNIQUE INDEX idx_workspace_current_attachment
ON workspaces(vm_id) WHERE vm_id IS NOT NULL AND detached_at IS NULL;
-- Partial index: each VM has only one root device
CREATE UNIQUE INDEX idx_vm_root_device
ON workspaces(vm_id) WHERE vm_id IS NOT NULL AND detached_at IS NULL AND is_root_device = 1;
Diagrams
Core Entity Relationships
erDiagram
vms ||--o{ workspaces : "has attached"
vms ||--o{ vm_boot_history : "boot history"
vms ||--o{ vsock_services : "runs services"
vms ||--o| vm_network : "has network"
master_images ||--o{ workspaces : "snapshot of"
workspaces ||--o{ workspaces : "derived from"
bridges ||--o{ vm_network : "provides connectivity"
vms ||--o{ firewall_rules : "has rules"
vms ||--o{ routes : "source"
vms ||--o{ routes : "target"
vms {
text id PK
text name
text role
text state
int cid
int vcpu_count
int mem_size_mib
int pid
}
master_images {
text id PK
text name
text subvolume_path
int size_bytes
}
workspaces {
text id PK
text name
text vm_id FK
text subvolume_path
text master_image_id FK
text parent_workspace_id FK
int is_root_device
int is_read_only
}
routes {
text id PK
text source_vm_id FK
text target_vm_id FK
int source_port
int target_port
}
vsock_services {
text id PK
text vm_id FK
int port
text service_name
text state
}
bridges {
text name PK
text subnet
text gateway
}
vm_network {
text vm_id PK
text ip_address
text bridge_name FK
}
firewall_rules {
text id PK
text vm_id FK
int rule_order
text action
text protocol
}
VM State Machine
stateDiagram-v2
[*] --> created: POST /v1/vms
created --> running: POST /v1/vms/:id/start
created --> failed: Boot failure (automatic)
created --> [*]: DELETE /v1/vms/:id
running --> stopped: POST /v1/vms/:id/stop
running --> crashed: (automatic)
stopped --> running: POST /v1/vms/:id/start
stopped --> [*]: DELETE /v1/vms/:id
crashed --> running: POST /v1/vms/:id/start
crashed --> [*]: DELETE /v1/vms/:id
failed --> [*]: DELETE /v1/vms/:id
States
| State | Description |
|---|---|
created | VM record exists, Firecracker process not started |
running | Firecracker process active, VM booted |
stopped | VM gracefully stopped via API, can be restarted |
crashed | VM terminated unexpectedly, can be restarted |
failed | VM failed to boot (e.g., bad workspace image) |
Valid Transitions
| From | To | Trigger |
|---|---|---|
created | running | POST /v1/vms/:id/start |
created | failed | Automatic (boot failure) |
created | (deleted) | DELETE /v1/vms/:id |
running | stopped | POST /v1/vms/:id/stop |
running | crashed | Automatic (unexpected termination) |
stopped | running | POST /v1/vms/:id/start |
stopped | (deleted) | DELETE /v1/vms/:id |
crashed | running | POST /v1/vms/:id/start |
crashed | (deleted) | DELETE /v1/vms/:id |
failed | (deleted) | DELETE /v1/vms/:id |
Constraints
- Cannot delete running VM: Must stop first (returns
409 Conflict) - Cannot start running VM: Already running (returns
409 Conflict) - Cannot manually transition to crashed or failed: Set automatically by Nexus
- Failed VMs can only be deleted: Boot failure requires recreating with a working workspace