Files
lab/bastion/src/labd/prisma/schema.prisma
Michal 46b017d77e
Some checks failed
CI/CD / lint (pull_request) Failing after 13s
CI/CD / test (pull_request) Failing after 10s
CI/CD / typecheck (pull_request) Failing after 36s
CI/CD / build (pull_request) Has been skipped
CI/CD / publish-rpm (pull_request) Has been skipped
CI/CD / publish-deb (pull_request) Has been skipped
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

157 lines
4.3 KiB
Plaintext

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])
}
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
}
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
}