Nadir Web UI
SvelteKit dashboard for nadir-agent — a central web console that talks to one or many Nadir backend nodes over their typed REST API.
The agent does the system-administration work (systemd services, users, packages, networking, storage, audit, terminal, ...). This UI is the operator's view of it: sign in, register machines with their bearer token, see live host metrics on the dashboard, and drive everyday tasks from the browser.
Stack
- SvelteKit 2 (Svelte 5, adapter-node) + TailwindCSS 4 + shadcn-svelte
- Bun as the runtime / package manager / dev server
- Drizzle ORM on SQLite (libSQL driver) for the UI's own state (users, machines, encrypted tokens, settings)
- Better Auth with email/password, OAuth, optional 2FA, admin & username plugins
- Paraglide for i18n (messages in
messages/) - openapi-fetch + typed client generated from the nadir-agent OpenAPI spec
(
src/lib/server/nadir-agent/schema.d.ts)
Getting started
Prerequisites: Bun and a reachable nadir-agent instance with a machine token (see the agent README's Connecting a dashboard section).
bun install
cp .env.example .env # then edit (see below)
bun run db:push # creates db.sqlite from the Drizzle schema
bun run dev # starts on http://localhost:5173
Environment
Set in .env (validated at startup via src/lib/const/schema.ts):
| Var | Default | Purpose |
|---|---|---|
CRYPTO_SECRET |
(required) | Encrypts machine bearer tokens at rest in the local DB |
DATABASE_URL |
file:db.sqlite |
libSQL connection string |
ORIGIN |
http://localhost:5173 |
Public origin (used by Better Auth) |
REPOSITORY_URL |
(required) | Gitea API URL for the nadir-agent releases; enables GET /install.sh |
DISABLE_SIGNUP |
false |
Lock down registration |
ENABLE_2FA |
false |
Enable the TOTP 2FA flow |
ENABLE_EMAIL_AND_PASSWORD |
true |
Toggle email/password auth |
SMTP_HOST |
- | Outbound mail server hostname |
SMTP_PORT |
- | SMTP port |
SMTP_USER |
- | SMTP username |
SMTP_PASS |
- | SMTP password |
SMTP_FROM |
- | From address for verification / reset / 2FA emails |
SMTP_SSL |
- | Use SSL/TLS for SMTP |
OAuth providers (optional) live in config/oauth.json and are passed straight
to Better Auth's genericOAuth plugin.
Scripts
bun run dev # vite dev server (binds --host for LAN access)
bun run dev:types # regenerate typed client, then start dev server
bun run type:generate # generate typed client from live nadir-agent OpenAPI spec
bun run build # production build (adapter-node -> build/)
bun run preview # preview the production build
bun run check # svelte-check (must pass 0 errors before done)
bun run lint # prettier + eslint
bun run format # prettier --write
bun run db:push # apply schema to the DB (dev)
bun run db:generate # generate migration from schema changes
bun run db:migrate # run pending migrations
bun run db:studio # drizzle-kit studio
type:generate fetches the OpenAPI spec from the live agent configured in the script
(http://100.64.0.189:9999/openapi.json by default — adjust for your environment).
Project layout
src/
routes/
auth/ sign-in, sign-up, forgot/reset password, 2fa, 2fa setup
dashboard/ machine list; per-machine live dashboard
[machineId]/
networking/ interfaces (list/detail/configure), routes, DNS, /etc/hosts
packages/ installed packages, available updates
services/ service list + per-service control and logs
storage/ active mounts, fstab entries
system/ overview, hostname, date-time, localization, power, nadir self-update
users/ local accounts, groups (list + detail)
admin/ dashboard users (Better Auth admin), config
docs/ built-in operator documentation (architecture, installation,
security, limitations)
api/emailer/send/ internal SvelteKit server route for outbound email
install.sh/ serves the nadir-agent installer shell script (enabled
when REPOSITORY_URL is set; returns 404 otherwise)
lib/
auth/ Better Auth server instance + browser client
components/ shadcn-svelte UI + reusable dashboard panels
remotes/ SvelteKit remote functions, one file per agent domain:
auth, config, machines, networking, packages,
pam-users, server, services, storage, system,
terminal, users, utils
schemas/ valibot schemas (shared validation types)
server/
db/ Drizzle schema + auth-schema + encrypted column type
emails/ nodemailer + better-svelte-email templates
nadir-agent/ generated OpenAPI types + typed fetch client
terminal/ SSH session management (open/stream/write/resize/close)
paraglide/ generated i18n runtime
messages/ translation source (en.json, ...)
config/oauth.json optional OAuth providers passed to Better Auth
Remote functions
src/lib/remotes/*.remote.ts are the SvelteKit server-side functions the browser
triggers via query / command. They call the agent through
nadirForMachine(machineId), which handles token decryption and 401/404/500 routing.
After a mutating command, the relevant querys are .refresh()ed inside the command.
| File | Agent domain covered |
|---|---|
auth.remote.ts |
Sign-in, sign-out, session |
config.remote.ts |
UI application config |
machines.remote.ts |
Machine CRUD, reordering, health batch |
networking.remote.ts |
Interfaces, routing, DNS, /etc/hosts |
packages.remote.ts |
Installed, updates, install/remove (SSE) |
pam-users.remote.ts |
System user listing (PAM side) |
server.remote.ts |
Agent self-update (POST /api/update) |
services.remote.ts |
systemd unit list/control/logs (SSE) |
storage.remote.ts |
Mounts, fstab entries |
system.remote.ts |
Host info, hostname, time/tz/NTP, locale, power |
terminal.remote.ts |
SSH terminal sessions (open/stream/write/resize/close) |
users.remote.ts |
Local accounts + groups |
utils.ts |
Shared helpers (not a remote file) |
Database
The UI keeps its own SQLite database (Drizzle ORM, libSQL driver):
| Table | Contents |
|---|---|
user |
Dashboard user (email, username, name, UI role, ban state) |
session |
Better Auth sessions |
account |
Better Auth OAuth accounts |
verification |
Better Auth verification tokens |
twoFactor |
TOTP secrets / backup codes |
machines |
{ id, name, address, order, token(encrypted) } — registered agent nodes |
settings |
Key/value store for UI-side application settings |
token in machines is stored with a custom encryptedText column type and is
decrypted on demand (autoDecrypt: false) only when making a server-side call to
the agent.
SSH Terminal
The UI includes a browser-based SSH terminal (terminal.remote.ts). Operators
can open an SSH session to any registered machine's host directly from the
dashboard. Sessions are managed server-side (SSH client runs in the SvelteKit
server process); the browser streams output via SSE and sends keystrokes via
command. Sessions are closed on disconnect or explicit closeSshTerminal.
GET /install.sh
When REPOSITORY_URL is set, the UI serves a shell installer at /install.sh
that bootstraps nadir-agent on a new host. The script is generated from
src/routes/install.sh/install.sh.tmpl with the release repo URL injected. If
REPOSITORY_URL is not set the route returns 404 with an explanation.
Deploying
adapter-node produces a plain Node/Bun server under build/:
bun run build
PORT=3000 ORIGIN=https://nadir.example.com bun run build/index.js
Put it behind the same reverse proxy you use for nadir-agent, or co-host them. The agent's CSRF rules apply when the UI calls it cross-origin — see the agent README's Connecting a dashboard section. The recommended pattern is to call the agent server-to-server from the SvelteKit server layer; the browser never holds or sends the agent token.
License
MIT.