Noderax API is the NestJS control plane for the platform. It serves the web dashboard, the first-run installer, workspace-aware control-plane routes, and the remote Linux-based Go agent runtime.
Current stable release: 1.0.0
- NestJS 11
- TypeScript
- PostgreSQL with TypeORM
- Redis for pub/sub and bridge work
- JWT authentication
- Socket.IO gateways for web and agent realtime
src/
modules/
audit-logs/
agent-updates/
agent-realtime/
agents/
auth/
diagnostics/
enrollments/
events/
metrics/
nodes/
notifications/
packages/
platform-settings/
realtime/
setup/
terminal-sessions/
tasks/
users/
workspaces/
common/
config/
database/
install/
redis/
- Installer-managed first-run setup with
setup,installed,legacy, andrestart_requiredmodes - Setup-time PostgreSQL, Redis, and optional SMTP validation
- Global user directory with platform-admin-only CRUD
- Invite-only operator onboarding with transactional email delivery
- Forgot/reset password lifecycle and authenticated password change
- TOTP MFA with recovery codes and short-lived MFA challenge tokens
- OIDC provider management with Google, Microsoft, and generic discovery-based SSO
- Workspace-aware control plane with:
- workspace listing and detail
- members and teams
- owner/admin/member/viewer roles
- default-workspace selection
- granular notification levels (INFO, WARNING, CRITICAL) for Email and Telegram
- automated slug generation
- archive / restore with read-only enforcement
- protected workspace deletion rules
- Append-only platform and workspace audit logs
- Platform-level admin role:
platform_admin - User-centric membership rules:
- users are created globally first
- invited users activate through one-time links before they can sign in
- workspace memberships point to existing accepted active users
- teams are composed from workspace members only
- inactive users cannot log in or receive new assignments
- Workspace-scoped unified search for nodes, tasks, schedules, events, members, and teams
- Linux node inventory with online/offline detection, maintenance mode, team ownership, and version telemetry
- Workspace-scoped one-click node bootstrap with short-lived install commands, live install progress tracking, installer consumption, and legacy enrollment compatibility
- Official agent release catalog resolution through CDN-first metadata with GitHub Releases fallback
- Platform-admin agent update rollouts with sequential dispatch, retry, skip, resume, cancel, rollback, and heartbeat-confirmed completion
- Metrics ingestion and node telemetry persistence
- Task creation, team-targeted dispatch, batch dispatch, long-poll task claiming, lifecycle updates, and logs
- Workspace-scoped task templates
- Scheduled task creation with workspace timezone support and team targeting
- Package operations through the shared task pipeline
- Platform settings persistence through installer state, including SMTP settings validation
- Realtime updates for node state, metrics, tasks, events, and node install progress
- Interactive terminal sessions over a dedicated JWT-authenticated Socket.IO namespace
- Terminal transcript persistence with ordered base64 I/O chunks, 7-day retention, and retention cleanup
- Agent realtime terminal bridge for start, input, resize, stop, opened, output, exited, and error events
- Install dependencies:
pnpm install- Create the local environment file:
cp .env.example .env- Configure the important values:
DATABASE_HOSTDATABASE_PORTDATABASE_USERNAMEDATABASE_PASSWORDDATABASE_NAMEDATABASE_SSLREDIS_ENABLEDREDIS_HOSTREDIS_PORTREDIS_PASSWORDCORS_ORIGINNODERAX_STATE_DIRJWT_SECRETSECRETS_ENCRYPTION_KEYAGENT_ENROLLMENT_TOKENAGENT_PUBLIC_API_URLAGENT_INSTALL_SCRIPT_URLSMTP_HOSTSMTP_PORTSMTP_USERNAMESMTP_PASSWORDSMTP_FROM_EMAILSMTP_FROM_NAMEWEB_APP_URL
DATABASE_* is the preferred naming scheme. Legacy DB_* aliases are still supported for backward compatibility.
For installer-managed deployments, NODERAX_STATE_DIR should point to a writable application-data path. For Docker, use a mounted path such as /data/noderax.
If SMTP_HOST is left blank, mail delivery remains disabled. Invite, reset-password, and operational email flows only send when SMTP is configured. In tests, the API uses JSON transport and exposes captured deliveries through the in-memory mailer service.
AGENT_PUBLIC_API_URL should point to the externally reachable API origin used by target servers. In installer-managed setups, the setup flow populates this from the system API URL. AGENT_INSTALL_SCRIPT_URL controls the installer script URL embedded into the generated node install command.
CORS_ORIGIN should be a comma-separated list of explicit web origins in production. The same policy is applied to HTTP, /realtime, /terminal, and /agent-realtime.
Outside setup mode, production boot rejects unsafe placeholder values such as CORS_ORIGIN=*, default demo JWT and encryption secrets, and the example admin credentials.
Platform runtime updates are restart-aware:
PATCH /platform-settingswrites the next boot configuration into installer statePOST /platform-settings/restartasks the current API process to exit after the response flushes- the process is expected to come back through Docker restart policy, systemd, or another supervisor
- the health payload now includes
startedAtandbootIdso operators and the web app can confirm that a new process instance is live
If you want the API to create the first platform admin automatically, set:
SEED_DEFAULT_ADMIN=trueADMIN_NAMEADMIN_EMAILADMIN_PASSWORD
- Start the API:
pnpm start:devDefault URLs:
- Health:
http://localhost:3000/health - API base:
http://localhost:3000/api/v1 - Swagger UI:
http://localhost:3000/api/v1/docs - OpenAPI JSON:
http://localhost:3000/api/v1/docs-json
Health response shape:
{
"service": "noderax-api",
"status": "ok",
"timestamp": "2026-04-01T08:45:00.000Z",
"startedAt": "2026-04-01T08:42:13.000Z",
"bootId": "2e0b7a58-7d0a-4d4a-a909-e246e74f1c6a"
}- Copy the environment template:
cp .env.example .env- Review these values in
.env:
NODE_ENV=development
DATABASE_HOST=postgres
DATABASE_PORT=5432
DATABASE_SSL=false
DATABASE_SYNCHRONIZE=true
DATABASE_LOGGING=false
REDIS_HOST=redis
REDIS_PORT=6379
REDIS_PASSWORD=change-this-redis-password
NODERAX_STATE_DIR=/data/noderax- Start the development stack:
docker compose -f docker-compose.yml -f docker-compose.dev.yml up --buildAvailable endpoints:
- Health:
http://localhost:3000/health - API base:
http://localhost:3000/api/v1 - Swagger UI:
http://localhost:3000/api/v1/docs
The development override mounts the source tree, keeps node_modules in a named volume, and runs pnpm run start:dev for hot reload.
- Copy the environment template:
cp .env.example .env- Set strong production values before booting:
NODE_ENV=production
DATABASE_HOST=postgres
DATABASE_PORT=5432
DATABASE_SSL=false
DATABASE_SYNCHRONIZE=false
DATABASE_LOGGING=false
REDIS_HOST=redis
REDIS_PORT=6379
REDIS_PASSWORD=change-this-redis-password
JWT_SECRET=change-this-jwt-secret
SECRETS_ENCRYPTION_KEY=change-this-secrets-key
AGENT_ENROLLMENT_TOKEN=change-this-agent-enrollment-token
NODERAX_STATE_DIR=/data/noderax- Build and run the production stack:
docker compose up -d --buildProduction behavior:
- Only the API is published on
http://localhost:3000 - PostgreSQL and Redis stay internal to the Docker network
- PostgreSQL data, Redis data, and installer state are persisted in named volumes
- Redis runs with AOF enabled and password protection
- Runtime CORS must be configured with explicit origins before switching to production traffic
The API now owns the official agent update control plane.
GET /agent-updates/summaryreturns the latest tagged release, outdated node counts, and the active rollout summary.GET /agent-updates/releasesandGET /agent-updates/rolloutspower the platform-adminUpdatescenter in the web app.POST /agent-updates/rollouts,resume,cancel,retry, andskipmanage sequential fleet rollout state.POST /agent-updates/targets/:targetId/progressis agent-authenticated and receives detached updater progress from the target node.- A rollout target is not considered successful until the agent heartbeat reports
agentVersion === targetVersion; updater progress alone is not enough. - On the first target failure or timeout the rollout is paused and requires explicit operator action before the fleet continues.
- Only tagged official releases are catalogued. The API reads the official CDN manifest catalog first and falls back to official GitHub Release assets when the CDN is unavailable.
On a fresh install, the API starts in setup mode. Complete the initial installation through the setup flow exposed under the API base path:
GET /api/v1/setup/statusPOST /api/v1/setup/validate/postgresPOST /api/v1/setup/validate/redisPOST /api/v1/setup/validate/smtpPOST /api/v1/setup/install
After installation completes, the normal application surface becomes active.
- Workspace owners, admins, and platform admins can create one-click install commands through
POST /workspaces/:workspaceId/node-installs - The response includes the full
curl | sudo bashinstaller command, the public API URL, the installer script URL, the install record ID, and the initial live status payload - When
AGENT_PUBLIC_API_URLis configured, it is always used as the installer command origin so target hosts receive a deterministic public API endpoint - Install tokens are single-use and short-lived
- Install status can be read through
GET /workspaces/:workspaceId/node-installs/:installId - The installer reports progress stages through
POST /node-installs/progress - Common progress stages include
installer_started, dependency preparation, binary download, bootstrap, andservice_started - Workspace realtime consumers receive
node-install.updatedframes while bootstrap is running - Target hosts consume the token through
POST /node-installs/consume - Legacy
POST /enrollments/initiateandGET /enrollments/:tokenremain available for backward compatibility
The installer persists runtime setup into install-state.json under NODERAX_STATE_DIR.
Important behavior:
setupmode: Fresh install. The API exposes setup endpoints under/api/v1/setup/*and waits for first-time provisioning.installedmode: The installer has completed and the normal app surface is active.restart_requiredmode: Installer-managed settings were updated and a restart is needed before they fully apply.legacymode: Existing schema and env-driven installs continue to boot without installer ownership.
If the state directory is not writable, setup and platform-settings flows will warn or fail. In containers, use a writable mounted directory instead of writing under a read-only application path.
Platform Settings behavior:
GET /platform-settingsreturns the effective installer-managed snapshot plus:editablesourcerestartRequiredmessage
restartRequired=truemeans installer state differs from the current process environment and a restart is still pendingPOST /platform-settings/restartisplatform_adminonly and is intentionally supervisor-driven; it does not invoke Docker or systemd directly- duplicate restart requests are treated idempotently while a shutdown is already scheduled
- Platform role:
platform_adminuser
- Workspace membership roles:
owneradminmemberviewer
Key rules:
platform_admincan create workspaces, manage platform settings, and manage global users.- Workspace
ownerandadmincan update workspace settings, assign existing users as members, manage teams, and delete the workspace. - Archived workspaces remain readable, but mutations, enrollment finalization, and schedule execution are blocked until restore.
- The current default workspace cannot be deleted until another workspace is selected as default.
- The current default workspace cannot be archived until another workspace becomes default.
- Workspace member creation is reference-based:
POST /workspaces/:workspaceId/membersacceptsuserIdplus role. - Team membership creation is constrained to active users who already belong to the same workspace.
- Removing a workspace membership also removes that user from teams inside the same workspace.
- Deleting a user is blocked while that user still owns workspace memberships, team memberships, or scheduled tasks.
Usersis the single source of truth for operator identities.POST /userscreates a pending invited user and dispatches a one-time activation email.POST /users/:userId/resend-inviterotates the prior invite token and sends a fresh activation email.GET /auth/invitations/:tokenandPOST /auth/invitations/:token/acceptpower account activation.POST /auth/password/forgot,GET /auth/password/reset/:token, andPOST /auth/password/reset/:tokenpower the reset flow.POST /users/me/passwordchanges the authenticated password.GET /usersremains platform-admin only.GET /workspaces/:workspaceId/assignable-usersreturns active users not yet attached to the workspace, for workspaceownerandadminplus platform admins.PATCH /users/:userIdsupports profile, role, and active-state updates with last-active-admin protections.DELETE /users/:userIdperforms hard delete only when the user has no blocking assignments.- Session invalidation is version-based. Invite accept, password reset, password change, deactivate, and role-sensitive mutations rotate
sessionVersion. - Bootstrap now repairs orphaned team memberships that no longer have a matching workspace membership.
- Teams are no longer organizational only. Nodes can be assigned to teams, and tasks can be broadcast to every eligible node currently owned by a team.
GET /audit-logsexposes platform-wide append-only audit entries for platform admins.GET /workspaces/:workspaceId/audit-logsexposes workspace-scoped audit entries for workspace owners/admins.- Nodes support maintenance mode with:
POST /nodes/:id/maintenance/enablePOST /nodes/:id/maintenance/disable- workspace-scoped equivalents under
/workspaces/:workspaceId/nodes/:id/*
Interactive terminal access is separate from the HTTP task pipeline.
- Live operator traffic uses the dedicated Socket.IO namespace
/terminal - Browser events:
terminal.attachterminal.inputterminal.resizeterminal.terminate
- Browser receives:
terminal.session.stateterminal.outputterminal.closedterminal.error
- Agent-side realtime events:
- API to agent:
terminal.start,terminal.input,terminal.resize,terminal.stop - agent to API:
terminal.opened,terminal.output,terminal.exited,terminal.error
- API to agent:
REST surface:
POST /workspaces/:workspaceId/nodes/:nodeId/terminal-sessionsGET /workspaces/:workspaceId/nodes/:nodeId/terminal-sessionsGET /workspaces/:workspaceId/terminal-sessions/:sessionIdGET /workspaces/:workspaceId/terminal-sessions/:sessionId/chunksPOST /workspaces/:workspaceId/terminal-sessions/:sessionId/terminate
Current behavior:
- Only
platform_adminusers who are workspaceowneroradmincan start sessions - Archived workspaces cannot start new sessions
- Nodes must be online and reachable through the agent realtime route
- Maintenance mode does not block terminal access
- A live session has a single controller: the creator can interact, others can inspect closed transcripts
- Transcript chunks are stored in order with a unique
(sessionId, seq)constraint - Transcript retention is 7 days
- Controller disconnect has a 5-minute reattach grace window before the session is closed
- Termination requests have a backend timeout fallback so sessions do not remain stuck in
terminatingif the remote shell disconnects without a final exit event - Audit events are written for create, open, terminate request, exit, failure, and transcript retention cleanup
POST /auth/loginmay return either a normal session token orrequiresMfa=truewith a short-lived challenge token.- MFA enrollment is QR-compatible through
POST /auth/mfa/setup/initiate, then confirmed withPOST /auth/mfa/setup/confirm. - Recovery code flows are supported through
POST /auth/mfa/recovery/verifyandPOST /auth/mfa/recovery/regenerate. - MFA disable requires authenticated confirmation through
DELETE /auth/mfa. - OIDC providers are managed through:
GET /auth/providersfor public login buttonsGET /auth/providers/adminPOST /auth/providersPATCH /auth/providers/:providerIdDELETE /auth/providers/:providerIdPOST /auth/providers/test
- Public OIDC handoff routes are:
GET /auth/oidc/:provider/startGET /auth/oidc/:provider/callback
The primary task execution path is HTTP polling.
- Agents long-poll
POST /agent/tasks/claim - Agents report lifecycle with:
POST /agent/tasks/:taskId/acceptedPOST /agent/tasks/:taskId/startedPOST /agent/tasks/:taskId/logsPOST /agent/tasks/:taskId/completed
- Cancellation is observed through agent control polling
Interactive terminals are the exception to this rule: they are bridged over the agent realtime socket instead of the HTTP claim loop.
- Realtime agent sockets remain active for telemetry and lifecycle support
- Realtime task push exists only as an explicit compatibility mode and is disabled by default
All routes below are relative to http://localhost:3000/api/v1.
GET /healthPOST /auth/loginGET /auth/providersGET /auth/oidc/:provider/startGET /auth/oidc/:provider/callbackPOST /auth/mfa/challenge/verifyPOST /auth/mfa/recovery/verifyGET /auth/invitations/:tokenPOST /auth/invitations/:token/acceptPOST /auth/password/forgotGET /auth/password/reset/:tokenPOST /auth/password/reset/:tokenGET /setup/statusPOST /setup/validate/postgresPOST /setup/validate/redisPOST /setup/validate/smtpPOST /setup/installPOST /node-installs/consumePOST /node-installs/progressPOST /enrollments/initiateGET /enrollments/:token
POST /agent/register(legacy)POST /agent/heartbeatPOST /agent/metricsPOST /agent/tasks/claimPOST /agent/tasks/:taskId/acceptedGET /agent/tasks/:taskId/controlPOST /agent/tasks/:taskId/startedPOST /agent/tasks/:taskId/logsPOST /agent/tasks/:taskId/completed
GET /audit-logsGET /workspaces/:workspaceId/audit-logsGET /platform-settingsPATCH /platform-settingsPOST /platform-settings/validate/smtpPOST /auth/mfa/setup/initiatePOST /auth/mfa/setup/confirmPOST /auth/mfa/recovery/regenerateDELETE /auth/mfaGET /auth/providers/adminPOST /auth/providersPATCH /auth/providers/:providerIdDELETE /auth/providers/:providerIdPOST /auth/providers/testPOST /workspaces/:workspaceId/tasks/teams/:teamIdPOST /workspaces/:workspaceId/node-installsGET /workspaces/:workspaceId/node-installs/:installIdGET /workspaces/:workspaceId/task-templatesPOST /workspaces/:workspaceId/task-templatesPATCH /workspaces/:workspaceId/task-templates/:idDELETE /workspaces/:workspaceId/task-templates/:idPOST /workspaces/:workspaceId/nodes/:id/teamPOST /workspaces/:workspaceId/nodes/:id/maintenance/enablePOST /workspaces/:workspaceId/nodes/:id/maintenance/disable
GET /usersGET /users/mePOST /usersPOST /users/:userId/resend-invitePATCH /users/:userIdDELETE /users/:userIdPATCH /users/me/preferencesPOST /users/me/passwordGET /workspacesGET /workspaces/:workspaceIdGET /workspaces/:workspaceId/membersGET /workspaces/:workspaceId/assignable-usersGET /workspaces/:workspaceId/searchGET /workspaces/:workspaceId/teamsGET /workspaces/:workspaceId/teams/:teamId/membersGET /workspaces/:workspaceId/nodesGET /workspaces/:workspaceId/tasksGET /workspaces/:workspaceId/scheduled-tasksGET /workspaces/:workspaceId/eventsGET /workspaces/:workspaceId/metricsGET /workspaces/:workspaceId/nodes/:id/packages
POST /workspacesPATCH /workspaces/:workspaceIdDELETE /workspaces/:workspaceIdPOST /workspaces/:workspaceId/membersPATCH /workspaces/:workspaceId/members/:membershipIdDELETE /workspaces/:workspaceId/members/:membershipIdPOST /workspaces/:workspaceId/teamsPATCH /workspaces/:workspaceId/teams/:teamIdDELETE /workspaces/:workspaceId/teams/:teamIdGET /platform-settingsPATCH /platform-settings
GET /diagnostics/task-flow
Returns claim, realtime, and queue-health counters for the web diagnostics panel.
The API publishes web-facing realtime events for:
node.status.updatedmetrics.ingestedtask.createdtask.updatedevent.creatednode-install.updated
It also hosts the agent realtime namespace at /agent-realtime.
Recommended checks:
pnpm build
pnpm lint
pnpm test
pnpm test:e2e
pnpm audit --audit-level highDependency hardening notes:
- The lockfile pins patched
path-to-regexp,picomatch,minimatch, andhandlebarstransitive chains throughpnpm.overrides. ts-jestis kept on the latest29.4.xpatch line to avoid the known vulnerable Handlebars dependency range.
- The bundled installer and platform-settings flows are designed for writable persistent storage.
- Workspace-scoped routes are the primary surface for the current web app.
- Legacy env-driven installs are still supported, but new installs should prefer the setup flow.
