210 lines
10 KiB
Markdown
210 lines
10 KiB
Markdown
# Nadir Web UI
|
|
|
|
SvelteKit dashboard for [nadir-agent](https://tea.urania.dev/urania/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](https://bun.com) and a reachable nadir-agent instance with
|
|
a machine token (see the agent README's _Connecting a dashboard_ section).
|
|
|
|
```sh
|
|
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
|
|
|
|
```sh
|
|
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 `query`s 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/`:
|
|
|
|
```sh
|
|
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.
|