- TypeScript 77%
- Svelte 20.7%
- Shell 1.6%
- CSS 0.4%
- Dockerfile 0.3%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
|
||
| .clinerules | ||
| .cursor/rules | ||
| .forgejo | ||
| .github | ||
| .opencode | ||
| .windsurf/rules | ||
| docker | ||
| docs | ||
| packages | ||
| tools | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| AGENTS.md | ||
| CLAUDE.md | ||
| deno.json | ||
| deno.lock | ||
| README.md | ||
| tsconfig.svelte.json | ||
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.