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
- API-authored per-node root access profiles with desired/applied/sync metadata and realtime propagation
- 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
- Root-aware shell task and scheduled shell task creation with
runAsRootandrootScope - Package operations through the shared task pipeline
- Platform settings persistence through installer state, including SMTP settings validation
- Realtime updates for node state, root access 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, retention cleanup, and optional root-session launch
- Agent realtime terminal bridge for start, input, resize, stop, opened, output, exited, and error events
The API is the source of truth for per-node root access. Each node stores:
rootAccessProfile: desired profile chosen by an operatorrootAccessAppliedProfile: profile most recently reported as applied by the agentrootAccessSyncStatus:pending,applied, orfailedrootAccessUpdatedAt,rootAccessUpdatedByUserId,rootAccessLastAppliedAt,rootAccessLastError
Supported profiles:
off: no privileged panel surfacesoperational: package install/remove/purge plusapt-get update,reboot, andrestart-agenttask: rootshell.execand scheduled shell tasksterminal: root interactive terminal sessionsall: union of all surfaces
The desired profile is returned on every agent claim poll and realtime auth acknowledgement. Agents report their applied profile and last sync error back through the same control channel.
- 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.
