// SysDeck Web Edition v0.2.0 — state schema // Mirrors the cockpit edition's /var/lib/sysdeck state dirs + /etc/sysdeck config. generator client { provider = "prisma-client-js" } datasource db { provider = "sqlite" url = env("DATABASE_URL") } // ── Global audit log (mirrors /etc/cockpit/MODULE_LICENSES.log pattern) ── model AuditLog { id String @id @default(cuid()) ts DateTime @default(now()) module String action String detail String? actor String @default("operator") } // ── Image Builder ────────────────────────────────────────────────── model SdProfile { id String @id @default(cuid()) name String @unique backend String // mkosi | vmdb2 | archiso | live-build base String? // shipped profile it was copied from packages String @default("") // newline-separated package list createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model SdBuild { id String @id @default(cuid()) buildId String @unique // - profile String backend String state String @default("queued") // queued | running | succeeded | failed rc Int? durationMs Int? log String @default("") artifacts SdArtifact[] startedAt DateTime @default(now()) finishedAt DateTime? } model SdArtifact { id String @id @default(cuid()) buildId String path String sizeBytes Int createdAt DateTime @default(now()) build SdBuild @relation(fields: [buildId], references: [buildId]) } // ── Firewall ─────────────────────────────────────────────────────── model FwRuleset { id String @id @default(cuid()) name String @unique backend String // nftables | iptables | firewalld | sysdeck-fw | cilium template String? // public-webserver | vps-webserver | ai-llm | remote-admin | no-services | cilium active Boolean @default(false) appliedAt DateTime? } model FwRule { id String @id @default(cuid()) ruleset String chain String @default("INPUT") action String // accept | drop | reject | log proto String @default("tcp") port String? source String? comment String? position Int @default(0) } // ── Service / Ports editor (SERVICES_REGISTRY from firewall.py) ──── model ServicePort { id String @id @default(cuid()) service String @unique // ssh | cockpit | caddy | varnish | mariadb | ollama | openwebui | hermes | odysseus label String port Int configPath String configKey String running Boolean @default(false) updatedPort Int? } // ── Policy & permissions ─────────────────────────────────────────── model PolicyRule { id String @id @default(cuid()) scope String // e.g. "org.sysdeck.builder.modify" subject String effect String @default("allow") // allow | deny priority Int @default(50) enabled Boolean @default(true) note String? } // ── Encryption Vault ─────────────────────────────────────────────── model VaultEntry { id String @id @default(cuid()) label String kind String // luks-volume | keyfile | tpm-sealed | secret device String? cipher String? sizeBytes Int? status String @default("locked") // locked | unlocked | sealed note String? createdAt DateTime @default(now()) } // ── Hardware alerts (orphaned bridge finally gets its panel) ──────── model HwDevice { id String @id @default(cuid()) deviceId String @unique kind String // usb-storage | thunderbolt | bluetooth | pci | rfid | firewire name String vendor String? authorized Boolean @default(true) whitelisted Boolean @default(false) firstSeen DateTime @default(now()) lastSeen DateTime @default(now()) } model HwAlert { id String @id @default(cuid()) deviceId String kind String // unauthorized-usb | dma-thunderbolt | rogue-bluetooth | firmware-tamper | new-pci severity String @default("warn") // info | warn | danger message String state String @default("active") // active | acknowledged | dismissed ts DateTime @default(now()) } // ── Mining ────────────────────────────────────────────────────────── model MiningRig { id String @id @default(cuid()) name String host String coins String @default("BTC") status String @default("online") // online | offline | idle | error hashrate Float @default(0) // TH/s powerW Int @default(0) tempC Float @default(0) pool String? gpus MiningGpu[] updatedAt DateTime @updatedAt } model MiningGpu { id String @id @default(cuid()) rigId String model String hashrate Float @default(0) tempC Float @default(0) fanPct Int @default(0) rig MiningRig @relation(fields: [rigId], references: [id]) } // ── Databases ────────────────────────────────────────────────────── model DbInstance { id String @id @default(cuid()) name String engine String // mariadb | postgres | sqlite | redis host String @default("127.0.0.1") port Int status String @default("running") sizeMb Float @default(0) conns Int @default(0) version String? note String? } // ── Containers & VMs / Kata / Remote FS ──────────────────────────── model Container { id String @id @default(cuid()) name String driver String // incus | lxc | podman | libvirt | firecracker kind String @default("container") // container | vm image String? state String @default("stopped") // running | stopped | frozen | error cpuPct Float @default(0) memMb Float @default(0) uptimeS Int @default(0) ports String? createdAt DateTime @default(now()) } model KataPod { id String @id @default(cuid()) name String runtime String @default("kata-qemu") sandbox String state String @default("running") vmm String? memMb Float @default(0) cpuPct Float @default(0) pods Int @default(1) } model RemoteFs { id String @id @default(cuid()) name String backend String // ceph | glusterfs | moosefs | beegfs | orangefs state String @default("healthy") // healthy | degraded | offline sizeGb Float @default(0) usedGb Float @default(0) mounts Int @default(1) bricks Int @default(0) note String? } // ── Media: Jellyfin + Photos ─────────────────────────────────────── model MediaItem { id String @id @default(cuid()) kind String // movie | series | episode | photo title String library String sizeMb Float @default(0) year Int? addedAt DateTime @default(now()) playCount Int @default(0) } model MediaSession { id String @id @default(cuid()) user String device String item String state String @default("playing") // playing | paused | idle startedAt DateTime @default(now()) } model PhotoLibrary { id String @id @default(cuid()) backend String // photoprism | piwigo | lychee | nextcloud-memories | librephotos name String photos Int @default(0) videos Int @default(0) sizeGb Float @default(0) state String @default("idle") // indexing | idle | scanning | error } // ── Benchmark history ────────────────────────────────────────────── model BenchmarkResult { id String @id @default(cuid()) suite String // cpu | memory | disk | network score Float metric String detail String? ts DateTime @default(now()) } // ── Integrity (tripwire-style) ───────────────────────────────────── model IntegrityBaseline { id String @id @default(cuid()) path String @unique hash String kind String @default("file") ts DateTime @default(now()) } model IntegrityDrift { id String @id @default(cuid()) path String oldHash String? newHash String? change String // modified | added | removed detected DateTime @default(now()) resolved Boolean @default(false) } // ── Netsec ───────────────────────────────────────────────────────── model NetsecBan { id String @id @default(cuid()) ip String service String @default("sshd") jail String @default("sysdeck") reason String? strikes Int @default(1) bannedAt DateTime @default(now()) expiresAt DateTime? } model NetsecScan { id String @id @default(cuid()) target String kind String @default("port-sweep") ports String result String @default("done") ts DateTime @default(now()) } // ── 3rd-party module installer (catalog from modules3p.py) ───────── model Module3pInstall { id String @id @default(cuid()) moduleId String @unique action String @default("install") // install | uninstall license String ts DateTime @default(now()) } // ── Fleet nodes ──────────────────────────────────────────────────── model FleetNode { id String @id @default(cuid()) name String @unique host String role String @default("compute") // compute | storage | edge | control arch String @default("x86_64") state String @default("online") // online | offline | degraded cpuModel String? cores Int @default(4) memGb Int @default(8) local Boolean @default(false) } // ── Themes ───────────────────────────────────────────────────────── model ThemeSetting { id String @id @default(cuid()) key String @unique value String } // ── Klanker / Frosty Deno LLM gateway (klanker-gate) ─────────────── model KlankerProvider { id String @id @default(cuid()) name String @unique // openai | anthropic | gemini | openrouter | groq | ollama-local kind String // openai | anthropic | gemini | openrouter | groq | ollama models Int @default(0) // enabled model count on the account status String @default("ok") // ok | error | unknown | disabled latencyMs Int? // last list-models probe latency note String? createdAt DateTime @default(now()) } model KlankerVKey { id String @id @default(cuid()) label String @unique team String? // governance hierarchy: key -> team -> customer scope String @default("unrestricted") // unrestricted | scoped providers/models requests24h Int @default(0) tokens24h Int @default(0) costMicroUsd Int @default(0) // repo-wide cost unit: integer micro-USD rateLimitPerMin Int? // maxRequests of the 60s window, when set budgetUsd Float? // budget.maxCostUsd, when set state String @default("active") // active | disabled createdAt DateTime @default(now()) } model KlankerLogEntry { id String @id @default(cuid()) ts DateTime @default(now()) model String provider String vkey String? // virtual key label, when the request was keyed tokensIn Int @default(0) tokensOut Int @default(0) costMicroUsd Int @default(0) latencyMs Int @default(0) status Int @default(200) cacheHit Boolean @default(false) surface String @default("openai") // ingress dialect: openai | anthropic | gemini | openrouter } // ── KV store for misc module state ───────────────────────────────── model SdKv { key String @id value String ts DateTime @default(now()) } // ── Local console accounts (v0.4.0 unix-login fallback) ──────────── // When the host PAM path is unavailable (non-root service on a box // where unix_chkpwd only serves the invoking uid, or a python3-less // install), SYSDECK_AUTH_MODE=local/pam+local authenticates against // these instead: scrypt-hashed passwords, per-account lockout flag. // This is the escape hatch, NOT the primary path — PAM (the host's // own account system) is, exactly like Cockpit. model SdUser { username String @unique realname String? hash String // scrypt: salt$N$r$p$hex — never the password disabled Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt lastLoginAt DateTime? lastLoginIp String? }