Development
Prerequisites
Section titled “Prerequisites”- Node.js 20.19+ or 22.12+ and pnpm
- Go ≥ 1.25 (for the root-worker)
- Docker, to run the local NATS server (or a
nats-serverbinary with JetStream) curl,openssl
Install dependencies
Section titled “Install dependencies”pnpm installThe Prisma client is generated automatically before builds and typechecks. If
you change the schema (packages/database/prisma/schema/), you can regenerate
it explicitly:
pnpm --filter @app/database db:generateOther database scripts (from packages/database): db:push, db:migrate,
db:deploy, db:seed.
Run the dev servers
Section titled “Run the dev servers”A local stack has four processes: NATS, the root worker, the backend and the dashboard.
1. Database
Section titled “1. Database”pnpm --filter @app/database db:pushpnpm --filter @app/database db:seed # no account: the setup assistant creates it2. Backend environment
Section titled “2. Backend environment”cat > apps/backend/.env << 'END'JWT_SECRET=dev-secretNATS_URL=nats://127.0.0.1:4222NATS_USER=backendNATS_PASS=backend-devEND# Encrypts notification connector secrets (webhook URLs, SMTP passwords)echo "HSI_SECRETS_KEY=$(openssl rand -hex 32)" >> apps/backend/.envapps/backend/.env is git-ignored. See Configuration for
every environment variable.
3. NATS (terminal 1)
Section titled “3. NATS (terminal 1)”docker compose updocker-compose.yaml starts NATS with JetStream using the repository’s
nats.conf, which defines the development accounts backend / backend-dev
and worker / worker-dev. It listens on 127.0.0.1:4222 only.
4. Root worker (terminal 2, needs root)
Section titled “4. Root worker (terminal 2, needs root)”cd apps/root-workergo build -o root-worker .sudo NATS_URL=nats://127.0.0.1:4222 NATS_USER=worker NATS_PASS=worker-dev ./root-workerThe worker performs privileged operations (disks, users, Samba) on the machine it runs on. Prefer a disposable VM over your workstation.
5. Backend (terminal 3)
Section titled “5. Backend (terminal 3)”pnpm --filter @app/backend dev # vite-node, serves the API on :90016. Dashboard (terminal 4)
Section titled “6. Dashboard (terminal 4)”pnpm --filter @app/dashboard dev # Vite dev server with hot reload on :5173Open http://localhost:5173: a fresh database opens the setup assistant. Its link comes from the worker (as root, like the worker itself):
cd apps/root-worker && sudo ./root-worker setup-token --host localhost --port 5173pnpm dev runs the
backend and dashboard dev scripts in parallel if you prefer one terminal for
both.
The backend reads DASHBOARD_PATH to serve built assets; in dev the dashboard is
served by Vite instead.
Build & verify
Section titled “Build & verify”pnpm verify # lint + typecheck + backend/Go tests + production buildsRun individual gates with pnpm lint, pnpm typecheck, pnpm test, or
pnpm build (pnpm lint:fix applies safe ESLint fixes).
CI enforces ESLint, TypeScript/Vue typechecks, backend security tests, Go tests,
gofmt, go vet, i18n checks, and production builds. The ESLint config
(eslint.config.js) uses the correctness-focused rules only (Vue flat/essential
- typescript-eslint recommended) and deliberately leaves template formatting to the editor.
The same canonical backend build is used by local development, CI, source installation, and release packaging.
Runtime smoke test
Section titled “Runtime smoke test”To exercise the built app end-to-end, point the backend at the built dashboard and boot it:
pnpm --filter @app/dashboard buildcd apps/backend && DASHBOARD_PATH=../dashboard/dist pnpm dev# then: curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:9001/Monorepo layout
Section titled “Monorepo layout”apps/ backend/ Fastify + tRPC API, serves the dashboard (@app/backend) dashboard/ Vue 3 + Tailwind v4 SPA (@app/dashboard) root-worker/ Go privileged worker (NATS request/reply)packages/ database/ Prisma schema + generated client (@app/database) shared-types/ Types shared backend ↔ dashboard (@app/shared-types)scripts/ install.sh Install/update on a Linux host release.sh Cut a release (bump, changelog, tag, push)docs/ This documentationDashboard source of note:
src/components/ui/Modal.vue: the shared modal (animation, window-scoping, Escape stack)src/components/storage/dialogs/: shared device/destroy dialogs reused by all storage sectionssrc/lib/: composables and helpers (confirm.ts,escLayer.ts,desktop.ts,trpc.ts, …)src/style.css: design tokens and shared utility classes
Release process
Section titled “Release process”Releases are manual and batched: group related changes, then cut one release. Never release automatically.
-
Add your changes under
## [Unreleased]inCHANGELOG.md. -
Ensure the working tree is clean and you are on
main. -
Run the release script with the new SemVer version:
Terminal window ./scripts/release.sh 1.28.1It bumps
package.json, moves the[Unreleased]notes under the new version, commits, tagsv<version>, and pushesmain+ the tag, which triggers the GitHub Actions release workflow. -
On the pushed tag, the workflow first runs the VM tests (
scripts/vm/suite.sh, every scenario), then builds, signs and publishes. When a scenario fails nothing is published: fix it onmainand cut the next version. Publishing an existing tag again with the workflow’s Run workflow button skips the VM tests; keep it for a release that failed after them (signing, upload).
The script refuses to run on a dirty tree, off main, with an existing tag, or
with an empty [Unreleased] section.