No description
  • TypeScript 77%
  • Svelte 20.7%
  • Shell 1.6%
  • CSS 0.4%
  • Dockerfile 0.3%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Julian Imhof e2ae65fd6c
All checks were successful
Build and Deploy / verify (push) Successful in 1m32s
Build and Deploy / build (push) Successful in 2m7s
chore: drop a stray test screenshot
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-02 19:27:47 +02:00
.clinerules fix(docker): a fetch that starts after the caller, and ALREADY checks the repo 2026-08-10 15:24:31 +02:00
.cursor/rules docs: CLAUDE.md + per-agent caveman rule files 2026-07-11 22:02:53 +02:00
.forgejo ci(deno.sh): a sweep that only follows a build always arrives too early 2026-08-21 22:07:12 +02:00
.github fix(docker): a fetch that starts after the caller, and ALREADY checks the repo 2026-08-10 15:24:31 +02:00
.opencode fix(docker): a fetch that starts after the caller, and ALREADY checks the repo 2026-08-10 15:24:31 +02:00
.windsurf/rules fix(docker): a fetch that starts after the caller, and ALREADY checks the repo 2026-08-10 15:24:31 +02:00
docker fix(dep-check): an image built here is not a dependency a registry answers for 2026-08-16 13:46:17 +02:00
docs feat(ui-schema): pathTree — a lazy, collapsible path tree that can take input 2026-09-02 19:27:46 +02:00
packages fix(pathTree): discard superseded listings, drop backup wording from the label 2026-09-02 19:27:47 +02:00
tools Merge pull request 'ci(deno.sh): losing git is an error, one digest, and no image left behind' (#42) from fix/issue-29-deno-sh-followups into main 2026-08-21 23:20:26 +02:00
.dockerignore fix(dev-rig): stop the rig misreporting the build, the tester and itself 2026-08-09 19:00:55 +02:00
.env.example build: typecheck the browser, verify it in CI, and version the singletons 2026-08-10 15:37:05 +02:00
.gitignore fix(docker): close the job-slot hole and the rest of the review 2026-08-10 11:42:10 +02:00
AGENTS.md fix(docker): a fetch that starts after the caller, and ALREADY checks the repo 2026-08-10 15:24:31 +02:00
CLAUDE.md fix(modules): recover a wedged work tree by re-cloning on failure, not by probing harder 2026-08-21 23:44:14 +02:00
deno.json Merge pull request 'feat(ui): the components an external module turned out to need' (#31) from feat/ui-access-denied-and-rate into main 2026-08-15 22:15:49 +02:00
deno.lock build: typecheck the browser, verify it in CI, and version the singletons 2026-08-10 15:37:05 +02:00
README.md fix(docker): the answering container answers for every label, not just one 2026-08-21 22:27:36 +02:00
tsconfig.svelte.json build: typecheck the browser, verify it in CI, and version the singletons 2026-08-10 15:37:05 +02:00

OpsDeck

Modular, self-hosted server dashboard. One Docker container; frontend and backend are separate components talking over a versioned API; functionality ships as modules that are toggled with environment variables.

Stack: Deno 2 + Hono (backend) · Svelte 5 (frontend) · DuckDB (metrics history) · OIDC (auth) · uPlot (charts).

Built-in modules

Module Shows
hardware specs, CPU/RAM/network/disk-IO/temperature history, filesystem usage, GPU utilization/VRAM/temp/power (NVIDIA via nvidia-smi, Intel/AMD via sysfs + DRM fdinfo)
zfs pools, capacity/fragmentation, vdev tree with errors, scrub status, SMART disk health
docker compose stacks, container state/config (env values redacted), image update checks against registries

Enable with OPSDECK_MODULES=hardware,zfs,docker.

External modules come from git: OPSDECK_EXTERNAL_MODULES=https://git.example.com/foo.git#<commit-sha> — cloned, validated and built at container startup, cached in the data volume. Pin by commit SHA; a module runs with the server's privileges (see docs/security.md).

Deploy

cp .env.example .env   # fill in OIDC settings
docker compose -f docker/docker-compose.yml up -d --build

The compose file documents the required mounts (writable host root, docker.sock, data volume), pid: host, and the granular capability set — plus when privileged: true is the saner choice (docs/security.md has the threat model: the socket and host mount are root-equivalent either way).

Development (any OS)

deno task build        # shell + shared singleton bundles + module frontends
deno task start        # serve on :8080
deno task test packages/server/tests/

Set OPSDECK_AUTH=disabled and OPSDECK_FAKE_DATA=1 to develop without an IdP or a Linux host — every module has a synthetic data source. For the docker module on Windows, set DOCKER_HOST=tcp://localhost:2375 (Docker Desktop → "Expose daemon") and OPSDECK_HOST_ROOT=/ — the host-root prefix defaults to /host/root, a container mount that does not exist on a checkout, and drive-letter compose paths are refused under it rather than joined into a path that cannot exist. That fixes the paths, and reads genuinely work after it; whether anything can run a COMMAND on the host is a second question. A host root of / says the process is already there, so host exec tries only its direct candidate — no chroot, no nsenter — and proves it by running an sh -c test through it. With a sh.exe on PATH (Git for Windows with the Unix tools, MSYS2) that test passes and host commands run against the Windows docker CLI; with no sh at all it fails and the strategy stays unset. What that costs differs by feature: a stack update never fails over it — the updater probes first and degrades to the engine-API recreate, logging compose unavailable … — using the engine API — while a pin's compose validation and the git commit it makes run through host exec directly and fail with host command execution unavailable. Browsing stacks, files and logs never shells out and works either way; a pin without a shell fails at its validate step and the compensator rolls the edit back. Frontend hot-reload: deno task dev (proxies /api to :8080, so run deno task dev:server too). Note: module frontends are loaded from built files even in dev — rerun deno task build:modules after editing them.

Architecture

packages/
  sdk/             @opsdeck/sdk — the module contract (backend ctx + frontend types)
  server/          Hono app: module host, OIDC sessions, DuckDB metrics, SSE bus
  ui/              @opsdeck/ui — Svelte 5 component library + design tokens
  shell/           SPA shell: router, nav, dynamic module loader
  module-builder/  compiles module frontends against the shared singletons
  modules/         built-in modules (hardware, zfs, docker, demo)

Module frontends never bundle Svelte or the UI library. One pinned Svelte version inside the container compiles everything; builds externalize svelte, @opsdeck/ui and @opsdeck/sdk/client, and a browser import map resolves them to shared singleton bundles under /shared/. That keeps styling consistent and reactivity working across independently-built modules.

Metrics flow: module collectors → buffered writer → DuckDB samples (raw, 48 h) with 5-minute (30 d) and hourly (2 y) rollups → /api/core/metrics/query for charts, plus live values over SSE (/api/core/events).

Writing your own module: docs/modules.md. Mobile alert pushes (privacy-preserving FCM): docs/push-notifications.md. How CI actions and base images are kept current: docs/dependencies.md.