feat: scaffold labd — master daemon with CockroachDB + Prisma
New @lab/labd workspace package:
- Fastify HTTP server + WebSocket for agent connections
- Prisma schema (CockroachDB): Server, Agent, User, Role, Permission,
UserRole, JoinToken, AuditLog, PulumiRun, Cluster models
- Health endpoint with DB connectivity check
- Server listing with cloud/env/status filters
- Auth routes: agent enrollment, join token management
- Placeholder mTLS auth middleware
- Dev stack: CockroachDB single-node in docker-compose
- 32 tests passing (2 new for labd health)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 00:13:16 +00:00
|
|
|
generator client {
|
|
|
|
|
provider = "prisma-client-js"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
datasource db {
|
|
|
|
|
provider = "cockroachdb"
|
|
|
|
|
url = env("DATABASE_URL")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
model Server {
|
|
|
|
|
id String @id @default(uuid())
|
|
|
|
|
hostname String @unique
|
|
|
|
|
mac String? @unique
|
|
|
|
|
cloud String @default("baremetal")
|
|
|
|
|
environment String @default("default")
|
|
|
|
|
role String @default("worker")
|
|
|
|
|
labels Json @default("{}")
|
|
|
|
|
ip String?
|
|
|
|
|
agentVersion String?
|
|
|
|
|
status String @default("unknown") // unknown, online, offline, provisioning
|
|
|
|
|
lastHeartbeat DateTime?
|
|
|
|
|
createdAt DateTime @default(now())
|
|
|
|
|
updatedAt DateTime @updatedAt
|
|
|
|
|
|
|
|
|
|
agent Agent?
|
|
|
|
|
auditLogs AuditLog[]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
model Agent {
|
|
|
|
|
id String @id @default(uuid())
|
|
|
|
|
serverId String @unique
|
|
|
|
|
server Server @relation(fields: [serverId], references: [id], onDelete: Cascade)
|
|
|
|
|
certificatePem String?
|
|
|
|
|
enrolledAt DateTime @default(now())
|
|
|
|
|
lastSeen DateTime?
|
|
|
|
|
|
|
|
|
|
@@index([serverId])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
model User {
|
|
|
|
|
id String @id @default(uuid())
|
|
|
|
|
username String @unique
|
|
|
|
|
displayName String?
|
|
|
|
|
certFingerprint String? @unique
|
|
|
|
|
createdAt DateTime @default(now())
|
|
|
|
|
updatedAt DateTime @updatedAt
|
|
|
|
|
|
|
|
|
|
roleBindings UserRole[]
|
|
|
|
|
auditLogs AuditLog[]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
model Role {
|
|
|
|
|
id String @id @default(uuid())
|
|
|
|
|
name String @unique
|
|
|
|
|
description String?
|
|
|
|
|
createdAt DateTime @default(now())
|
|
|
|
|
|
|
|
|
|
permissions Permission[]
|
|
|
|
|
userBindings UserRole[]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
model Permission {
|
|
|
|
|
id String @id @default(uuid())
|
|
|
|
|
roleId String
|
|
|
|
|
role Role @relation(fields: [roleId], references: [id], onDelete: Cascade)
|
|
|
|
|
type String @default("allow") // allow or deny
|
|
|
|
|
action String // read, exec, apply, destroy, manage, admin, kubectl, *
|
|
|
|
|
cloud String @default("*")
|
|
|
|
|
environment String @default("*")
|
|
|
|
|
server String @default("*")
|
|
|
|
|
|
|
|
|
|
@@index([roleId])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
model UserRole {
|
|
|
|
|
id String @id @default(uuid())
|
|
|
|
|
userId String
|
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
|
roleId String
|
|
|
|
|
role Role @relation(fields: [roleId], references: [id], onDelete: Cascade)
|
|
|
|
|
|
|
|
|
|
@@unique([userId, roleId])
|
|
|
|
|
@@index([userId])
|
|
|
|
|
@@index([roleId])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
model JoinToken {
|
|
|
|
|
id String @id @default(uuid())
|
|
|
|
|
token String @unique
|
|
|
|
|
type String @default("one-time") // one-time or reusable
|
|
|
|
|
label String?
|
|
|
|
|
usedBy String? // server hostname that used it
|
|
|
|
|
usedAt DateTime?
|
|
|
|
|
revokedAt DateTime?
|
|
|
|
|
createdAt DateTime @default(now())
|
|
|
|
|
expiresAt DateTime?
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
model AuditLog {
|
|
|
|
|
id String @id @default(uuid())
|
|
|
|
|
userId String?
|
|
|
|
|
user User? @relation(fields: [userId], references: [id])
|
|
|
|
|
serverId String?
|
|
|
|
|
server Server? @relation(fields: [serverId], references: [id])
|
|
|
|
|
sessionId String?
|
|
|
|
|
action String // exec, kubectl, apply, login, rbac-denied, etc.
|
|
|
|
|
resourceType String? // server, cluster, role, app, etc.
|
|
|
|
|
resourceName String?
|
|
|
|
|
args String? // sanitized command args
|
|
|
|
|
result String @default("success") // success, denied, error
|
|
|
|
|
durationMs Int?
|
|
|
|
|
sourceIp String?
|
|
|
|
|
timestamp DateTime @default(now())
|
|
|
|
|
|
|
|
|
|
@@index([userId])
|
|
|
|
|
@@index([serverId])
|
|
|
|
|
@@index([sessionId])
|
|
|
|
|
@@index([timestamp])
|
|
|
|
|
@@index([action])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
model PulumiRun {
|
|
|
|
|
id String @id @default(uuid())
|
|
|
|
|
userId String
|
|
|
|
|
stackName String
|
|
|
|
|
action String // up, preview, destroy
|
|
|
|
|
status String @default("pending") // pending, running, succeeded, failed
|
|
|
|
|
output String?
|
|
|
|
|
startedAt DateTime @default(now())
|
|
|
|
|
completedAt DateTime?
|
|
|
|
|
|
|
|
|
|
@@index([userId])
|
|
|
|
|
@@index([stackName])
|
|
|
|
|
}
|
|
|
|
|
|
feat: install logging, error trapping, PXE/ISO integration tests
Kickstart installs on real hardware failed silently — no error reporting,
only 3 progress callbacks, zero log streaming. This overhaul makes every
install fully observable.
Kickstart improvements:
- Error trapping in %pre and %post (trap ERR sends failure details to bastion)
- 12+ granular progress stages (was 3): SSH, hostname, k3s prep, EFI boot, metadata
- Background log streamer: tails %post output and batch-sends to /api/log
- bastion_log() function for explicit log lines from kickstart scripts
Bastion API:
- POST /api/log — receives raw log lines from kickstart (single or batch)
- InstallLogBuffer — per-MAC ring buffer (2000 lines) + file persistence
- GET /api/logs/:mac — now returns log_lines + log_total alongside stages
- SSE /api/logs/:mac/follow — uses named events (event: stage vs event: log)
- Progress events forwarded to labd via bastion-progress WebSocket message
- Post-provision k3s logs routed through progressBus (was console-only)
dnsmasq fixes found during VM testing:
- HTTP Boot filename: ipxe-real.efi → ipxe.efi (leftover from old 2-stage approach)
- pxe-service directives: only in proxy mode (breaks OVMF PXE in full mode)
- PXEClient vendor class echo for UEFI firmware compatibility
Integration tests:
- PXE boot test: blank UEFI VM → dnsmasq → HTTP Boot → iPXE → bastion → install
- ISO boot test: blank VM boots from bastion-generated ISO → same flow
- Shared helpers: pxe-network (no DHCP, nftables fix), pxe-vm (UEFI + ISO boot)
- test-provision.sh: runs both PXE + ISO tests with prerequisite checks
- 250GB sparse QCOW2 disk (LVM layout needs ~204GB)
201 unit tests passing (11 new).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 22:26:33 +00:00
|
|
|
model Bastion {
|
|
|
|
|
id String @id @default(uuid())
|
|
|
|
|
hostname String @unique
|
|
|
|
|
network String
|
|
|
|
|
serverIp String
|
|
|
|
|
status String @default("offline") // online, offline
|
|
|
|
|
lastHeartbeat DateTime?
|
|
|
|
|
createdAt DateTime @default(now())
|
|
|
|
|
updatedAt DateTime @updatedAt
|
|
|
|
|
}
|
|
|
|
|
|
feat: scaffold labd — master daemon with CockroachDB + Prisma
New @lab/labd workspace package:
- Fastify HTTP server + WebSocket for agent connections
- Prisma schema (CockroachDB): Server, Agent, User, Role, Permission,
UserRole, JoinToken, AuditLog, PulumiRun, Cluster models
- Health endpoint with DB connectivity check
- Server listing with cloud/env/status filters
- Auth routes: agent enrollment, join token management
- Placeholder mTLS auth middleware
- Dev stack: CockroachDB single-node in docker-compose
- 32 tests passing (2 new for labd health)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 00:13:16 +00:00
|
|
|
model Cluster {
|
|
|
|
|
id String @id @default(uuid())
|
|
|
|
|
name String @unique
|
|
|
|
|
cloud String @default("baremetal")
|
|
|
|
|
environment String @default("default")
|
|
|
|
|
kubeconfigEnc String? // encrypted kubeconfig
|
|
|
|
|
labels Json @default("{}")
|
|
|
|
|
createdAt DateTime @default(now())
|
|
|
|
|
updatedAt DateTime @updatedAt
|
|
|
|
|
}
|