Skip to content

Slave Enrollment

Ankur Nair edited this page Apr 19, 2026 · 1 revision

Slave Enrollment

Enrolling a TitanX install as a slave connects it to a master's control plane. The handshake establishes mutual trust (via Ed25519 key exchange) and provisions a long-lived JWT the slave uses for subsequent master interactions.


What enrollment establishes

After a successful enrollment:

  • Slave has a device ID (UUID) known to master
  • Slave has master's public signing key (to verify config bundles + commands)
  • Slave has a JWT (typically 1-year lifetime) to authenticate subsequent polls
  • Master has slave's public key (to verify slave's signed telemetry + command acks)
  • Both sides have audit-log entries for the enrollment event

Prerequisites

If master uses a self-signed TLS cert (default for local testing), you need to either trust the cert system-wide or accept the fingerprint during enrollment.


Enrolling via the UI

Step 1 — Switch to slave mode

  1. Click the fleet icon in the titlebar
  2. Select Slave in the modal
  3. Enter:
    • Master URL — exactly as provided, including https:// and port
    • Enrollment token — the one-time token from master's admin
  4. Save & restart

Step 2 — TitanX relaunches + enrolls automatically

Behind the scenes during relaunch:

  1. TitanX generates a fresh Ed25519 keypair (device identity) if it doesn't already have one
  2. POSTs /api/fleet/enroll with the enrollment token + device public key + device metadata
  3. Master verifies the token is valid, single-use, unexpired
  4. Master binds the token to the slave's device ID, stores the slave's public key
  5. Master returns a JWT + its own public signing key + config version
  6. TitanX stores all three encrypted in the secrets vault

Step 3 — Verify enrollment

Post-restart, you're in slave mode. Check:

  • Status bar at bottom — should read Slave · enrolled · JWT cached
  • Settings → Fleet — your device ID + master URL
  • Activity logfleet.enrollment.success entry

If enrollment failed, the error appears as a banner. Common causes are Troubleshooting#slave-enrollment-fails-with-token-rejected.


Changing between Workforce ↔ Farm roles

After initial enrollment, you can flip between workforce (managed endpoint) and farm (compute node) roles without re-enrolling:

  1. Titlebar → second fleet icon → Slave role
  2. Pick new role
  3. Re-enroll happens automatically using the cached JWT

The master-side role record updates; destructive-command eligibility + farm provisioning eligibility update accordingly.

Note: changing role is an auditable event, logged on both sides.


What the enrollment request contains

For transparency — the exact payload sent by the slave to master:

{
  "enrollmentToken": "<one-time token>",
  "device": {
    "deviceId": "<UUID, pinned forever>",
    "publicKey": "<Ed25519 public key, base64>",
    "hostname": "<os.hostname()>",
    "platform": "darwin|linux|win32",
    "arch": "arm64|x64",
    "titanxVersion": "2.5.1",
    "role": "workforce|farm"
  },
  "metadata": {
    "enrolledAt": "<ISO timestamp>",
    "userAgent": "TitanX/2.5.1"
  }
}

No API keys, no secrets, no user data. Enrollment is a pure identity exchange.


What master returns

{
  "ok": true,
  "deviceId": "<echoed back>",
  "jwt": "<JWT token, 1-year TTL default>",
  "masterPublicKey": "<Ed25519 public key, base64>",
  "configVersion": 42,
  "serverUrl": "<master URL>",
  "minSlaveVersion": "2.5.0"
}

The minSlaveVersion field lets master refuse enrollment from slaves running protocol-incompatible older versions.


TLS + certificate handling

For production, master should run behind a real TLS cert (Let's Encrypt, corporate CA, etc.) with a domain name.

For local-network / testing, master often runs with a self-signed cert. Slave needs to either:

  1. Trust the cert system-wide — add to OS keychain or /etc/ssl/certs/
  2. Accept via fingerprint during enrollment — master's enrollment QR code (v2.6) or displayed fingerprint includes the cert thumbprint; slave pins it

In v2.5.1: slaves accept self-signed certs transparently (warning logged). This will change in v2.6 to require explicit trust.


JWT lifecycle

JWT is stored encrypted in the slave's secrets vault. Contents:

{
  "deviceId": "...",
  "role": "workforce",
  "iss": "titanx-master",
  "sub": "<device-id>",
  "iat": 1713456789,
  "exp": 1744992789
}

Slave includes the JWT as Authorization: Bearer <jwt> on every request. Master validates on every request.

When JWT is near expiry (< 7 days), TitanX automatically initiates a refresh:

  1. Slave POSTs /api/fleet/refresh-jwt with current JWT
  2. Master validates, issues new JWT if device is still trusted
  3. Slave replaces cached JWT

If refresh fails (device was revoked), slave falls back to "requires re-enrollment" state — operator sees a banner asking for a new enrollment token.


Revocation from slave's perspective

If master revokes the slave:

  • Next request returns 401 Unauthorized with reason device_revoked
  • Slave clears cached JWT
  • Slave UI shows revocation banner
  • Slave continues operating as Regular mode (single-machine) until re-enrolled

Master's revocation is final until re-enrollment with a new token.


Enrollment via config file (batch deploy)

For IT admins deploying to many machines via MDM or config management:

  1. Generate a template config JSON (master's admin UI produces one)
  2. Deploy to each slave at ~/.aionui-config/fleet-bootstrap.json:
{
  "masterUrl": "https://fleet.mycompany.com:8888",
  "enrollmentToken": "<token>",
  "role": "workforce"
}
  1. On next TitanX launch, if slave is not already enrolled, it reads this file and auto-enrolls
  2. File is deleted on success (one-time use)

Common enrollment errors

See Troubleshooting#slave-enrollment-fails-with-token-rejected for the full list. Most frequent:

Error Cause Fix
token_expired Token TTL elapsed Generate a new token on master
token_already_used One-time token re-used Generate a new token
master_unreachable Network / URL wrong Verify master URL; test with curl
tls_mismatch Self-signed cert not trusted Trust cert or enable fingerprint pinning
version_mismatch Slave older than minSlaveVersion Upgrade slave to meet master's minimum

Related pages

Clone this wiki locally