NetStacksNetStacks

System Settings

Enterprise

Configure NetStacks Controller: authentication, security, SSH/terminal, collection, integrations, plugins, HA, and the license — via the Settings UI and key-value API.

Overview

System Settings in the NetStacks Controller provide centralized, key-value configuration for platform-wide behavior. The Controller is the multi-user, commercial side of NetStacks; the single-user Terminal does not have these settings. Settings are edited in the Admin UI under Settings, and exposed through a generic key-value API.

The Settings UI is organized into these categories:

  • General — application name, base URL, timezone, date format, items-per-page, and the session-timeout warning.
  • Authentication — local login, LDAP/Active Directory, OIDC, and session/JWT lifetimes (see Authentication).
  • Security — rate limiting for requests and failed logins.
  • Collection — config-backup collection defaults (concurrency, retries, timeouts, default credential).
  • Integrations — external systems such as NetBox.
  • Jumpboxes, TLS / SSL, External SSH Proxy, and Terminal Sessions — connectivity and proxy behavior.
  • Plugins — plugin system configuration and per-plugin settings (see Plugin System).
  • HA / Clustering — advertise-address field and session lifecycle for high-availability deployments (the operative advertise address is the ADVERTISE_ADDR environment variable).
  • AI and AI Data Security — provider configuration and credential redaction (see LLM Configuration).
  • Token Budget — monthly LLM token limits.
  • License — activate or view the Controller license key.
Permission required

Viewing and changing system settings requires the system.manage permission (label System Settings). This permission also gates license activation, backup/restore, and plugin management. Grant it through a role — see Roles & Permissions.

How It Works

Settings Storage

Settings live in PostgreSQL as rows in a settings table. Each row has a key (dot-separated, e.g. auth.ldap.enabled), a value (JSONB — boolean, number, string, or object), a category (the first segment of the key), an optional description, and an is_secret flag. Secret values (such as auth.jwt_secret or auth.ldap.bind_password) are stored with is_secret = true.

Two API Surfaces

  • Admin API (/api/admin/settings) — lists settings grouped by category, fetches a single key, and updates a key. Used by the Admin UI. Requires authentication.
  • Terminal-compatible API (/api/settings/{key}) — a simpler interface that returns the raw JSON value (not wrapped) and accepts a raw JSON body on PUT. Used by client applications for preferences.

Upsert Behavior

A PUT to a key that does not yet exist creates it: the category is derived from the key prefix and is_secret defaults to false. A GET for a key that was never set returns 404 Not Found — settings are created on first write, not on first read.

ssh.host_key_checking is a no-op

The Controller accepts writes to ssh.host_key_checking for backward compatibility but ignores the value — strict SSH host-key checking is always on and cannot be disabled through settings.

Setting Categories

Settings are grouped by the first segment of the key. The table below lists the real categories and representative seeded keys shipped with the Controller. This is not exhaustive — use GET /api/admin/settings to enumerate everything in your instance.

CategoryExample KeysPurpose
generalgeneral.app_name, general.base_url, general.timezone, general.date_formatOrganization and display defaults
authauth.local.enabled, auth.ldap.enabled, auth.oidc.enabled, auth.jwt_secretLogin methods and JWT signing
sessionsession.jwt_expiry_hours, session.refresh_token_days, session.access_token_hoursToken lifetimes
securitysecurity.rate_limit.requests_per_minute, security.rate_limit.failed_login_limitRate limiting and lockout
collectioncollection.max_concurrent, collection.retry_attempts, collection.timeout_secondsConfig-backup collection defaults
integrationsintegrations.netbox.verify_ssl, integrations.netbox.default_credential_idExternal system connections
sshssh.proxy_enabled, ssh.listen_address, ssh.idle_timeout_minutes, ssh.session_recordingExternal SSH proxy (port 2222)
terminalterminal.browser_enabled, terminal.idle_timeout_minutes, terminal.reconnect_window_secondsBrowser terminal sessions
pluginsplugins.enabledPlugin system master switch
jumpbox / tunnels / tlsjumpbox.enabled, tunnels.enabled, tls.sansConnectivity and certificates
aiai.provider_config, ai.terminal_mode_enabled, ai.config_changes_enabledAI provider and feature gates
licenselicense.enterprise_key, license.validation_urlLicense key and validation endpoint
Booleans gate features

NetStacks does not use a single “feature toggles” screen. Instead, individual boolean settings act as switches — for example plugins.enabled, jumpbox.enabled, tunnels.enabled, terminal.browser_enabled, and ai.terminal_mode_enabled. Flip them in the relevant Settings category or via the API.

License

The single-user NetStacks Terminal is free and open source — it has no license key and no license screen. Only the multi-user Controller uses a license, configured under Settings → License.

To activate, paste your enterprise license key into the field and click Activate License. The Controller verifies the key with the NetStacks license server, stores the resulting record, and shows the current tier, seat usage, and status. Two endpoints back this screen:

  • POST /api/admin/license/activate — submit a license key.
  • GET /api/admin/license/status — read the current tier, seat counts, expiration, and validation status.

If no license has been activated, the status endpoint returns a pending state so the UI can show the activation form. Only team and enterprise tiers are accepted by the Controller. The Controller revalidates the license with the license server in the background roughly once a day; if it cannot reach the server, the license moves to a grace_period status rather than failing immediately.

No phone-home from the product you run

License validation is a Controller-to-license-server check. The NetStacks Terminal and Local Agent contain no telemetry and no license check — see Source Code & Cryptography.

HA & Session Settings

The HA / Clustering category exposes a small set of high-availability options. The heavy lifting — the advertise address, Valkey connection, Sentinel topology, and node coordination — is configured with environment variables in the Controller's Docker configuration. The panel surfaces an ha.advertise_addr settings field, but the running Controller reads the ADVERTISE_ADDRenvironment variable for share links and inter-instance communication.

KnobWhere it livesNotes
Advertise addressADVERTISE_ADDR (env var)Public URL of this instance; used for share links and inter-instance communication. Unset = auto-detect (https://{host}:{port}). The HA / Clustering panel also shows an ha.advertise_addr field, but the running Controller reads the env var.
Reconnect windowterminal.reconnect_window_seconds (DB setting)How long a disconnected session can be resumed across instances.
Idle timeoutterminal.idle_timeout_minutes (DB setting)Close sessions after inactivity.
Valkey (session store)VALKEY_URL (env var)Required for HA, session sharing, and cross-instance state.
Valkey SentinelVALKEY_SENTINEL_URLS, VALKEY_SENTINEL_MASTER (env vars)Automated failover for the Valkey tier.

For the full multi-instance topology — Nginx load balancing, external PostgreSQL, Valkey Sentinel, and TLS — see HA Deployment.

Connected Status Popover

The Connected indicator in the top-right of the Admin UI is interactive. Clicking it opens a popover summarizing Controller health, drawn largely from GET /api/admin/system/info and the health endpoints.

What it shows

  • Version — the running Controller version.
  • Uptime — seconds since the process started.
  • Database size — current PostgreSQL database size.
  • Users and Devices — total counts in the organization.
  • Enabled plugins — plugins currently in the enabled state.
  • HA status — standalone vs. high-availability and Valkey connectivity, surfaced from the HA status endpoint.
System info endpoint

The same data is available programmatically at GET /api/admin/system/info, and HA/health details at GET /api/admin/health and GET /api/admin/ha-status.

Step-by-Step Guide

Changing a General Setting

  1. Open the Admin UI and go to Settings → General.
  2. Edit a field — for example the application name, base URL, timezone, or date format.
  3. Click the inline Save control. The change is written immediately; there is no separate apply step for individual keys.

Activating a Controller License

  1. Go to Settings → License.
  2. Paste your enterprise or team license key into the key field.
  3. Click Activate License. The Controller verifies the key with the license server and stores the result.
  4. On success, the status dashboard shows your tier, seat usage, and expiration.

Enabling LDAP or OIDC

  1. Go to Settings → Authentication.
  2. Toggle auth.ldap.enabled or auth.oidc.enabled and fill in the provider fields (server URL, base DN, client ID, etc.).
  3. Save. For the full provider walkthrough, see Authentication.

Managing Plugins

  1. Ensure plugins.enabled is on under Settings → Plugins.
  2. Use the Plugins panel to enable, disable, and configure individual plugins (e.g. Alerts, Incidents).
  3. See Plugin System for details.
Every change is audited

Settings updates, license activations, and plugin state changes are recorded in the audit log with user attribution and a timestamp. Use it to answer “who changed what, and when.”

Code Examples

Admin settings endpoints live under /api/admin/settings and require an authenticated request (JWT bearer token) from a user whose role includes system.manage.

List All Settings (Grouped by Category)

list-settings.shbash
curl https://controller.example.com/api/admin/settings \
  -H "Authorization: Bearer $TOKEN"

Response (truncated):

settings-response.jsonjson
{
  "settings": {
    "auth": [
      {
        "key": "auth.ldap.enabled",
        "value": false,
        "category": "auth",
        "description": "Enable LDAP/Active Directory authentication",
        "is_secret": false
      },
      {
        "key": "auth.oidc.provider_url",
        "value": "",
        "category": "auth",
        "description": "OIDC provider issuer URL (e.g., https://company.okta.com)",
        "is_secret": false
      }
    ],
    "session": [
      {
        "key": "session.jwt_expiry_hours",
        "value": 24,
        "category": "session",
        "description": "JWT token expiry in hours",
        "is_secret": false
      }
    ]
  }
}

Get a Single Setting (Admin API)

get-setting.shbash
curl https://controller.example.com/api/admin/settings/session.jwt_expiry_hours \
  -H "Authorization: Bearer $TOKEN"

Response (wrapped):

get-setting-response.jsonjson
{
  "setting": {
    "key": "session.jwt_expiry_hours",
    "value": 24,
    "category": "session",
    "description": "JWT token expiry in hours",
    "is_secret": false
  }
}

Update Settings (Admin API)

The admin PUT takes a JSON object with a value field. It upserts — creating the key if it does not exist.

update-settings.shbash
# Enable LDAP
curl -X PUT https://controller.example.com/api/admin/settings/auth.ldap.enabled \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"value": true}'

# Set JWT expiry to 12 hours
curl -X PUT https://controller.example.com/api/admin/settings/session.jwt_expiry_hours \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"value": 12}'

# Set the items-per-page display default
curl -X PUT https://controller.example.com/api/admin/settings/general.items_per_page \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"value": 50}'

Terminal-Compatible API (Raw Values)

The /api/settings/{key} endpoints return and accept raw JSON values — no wrapper object. PUT upserts and returns 204 No Content.

terminal-settings.shbash
# GET returns the raw value
curl https://controller.example.com/api/settings/general.timezone \
  -H "Authorization: Bearer $TOKEN"
# => "UTC"

# PUT a raw JSON body (not { "value": ... })
curl -X PUT https://controller.example.com/api/settings/general.timezone \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '"America/New_York"'

Check License Status

license-status.shbash
curl https://controller.example.com/api/admin/license/status \
  -H "Authorization: Bearer $TOKEN"

Response:

license-status-response.jsonjson
{
  "tier": "enterprise",
  "seats_total": 50,
  "seats_used": 12,
  "expires_at": "2026-12-31T23:59:59Z",
  "status": "valid",
  "warning_level": "none",
  "last_validated_at": "2026-06-15T08:00:00Z",
  "active_sessions": []
}

Activate a License

license-activate.shbash
curl -X POST https://controller.example.com/api/admin/license/activate \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"license_key": "XXXX-XXXX-XXXX-XXXX-XXXX"}'

System Info (Connected Status Data)

system-info.shbash
curl https://controller.example.com/api/admin/system/info \
  -H "Authorization: Bearer $TOKEN"

Response:

system-info-response.jsonjson
{
  "version": "0.2.40",
  "database_size": "184 MB",
  "user_count": 18,
  "device_count": 642,
  "uptime_seconds": 432000,
  "plugins_enabled": ["alerts", "incidents"]
}

Questions & Answers

What permission do I need to edit system settings?
The system.manage permission. It also covers license activation, database backup/restore, and plugin management. Add it to a role under Roles & Permissions and assign that role to the user.
Does NetStacks send email / have SMTP settings?
No. The Controller does not ship an SMTP/email configuration. There is no smtp.* category and no “send test email” flow. Alerts and notifications are delivered through the alerting plugins — see the Alert Pipeline.
Where do I configure high availability?
A few session knobs live in Settings → HA / Clustering (terminal.reconnect_window_seconds, terminal.idle_timeout_minutes). The advertise address, Valkey connection, and Sentinel topology are set with environment variables (ADVERTISE_ADDR, VALKEY_URL, VALKEY_SENTINEL_URLS, VALKEY_SENTINEL_MASTER). Full details are on the HA Deployment page.
How do I turn a feature on or off?
Flip the corresponding boolean setting in the relevant category — for example plugins.enabled, jumpbox.enabled, tunnels.enabled, terminal.browser_enabled, or ai.terminal_mode_enabled. There is no single feature-toggle dashboard.
Why does a GET for my setting return 404?
Settings are created on first write. If a key has never been PUT, a GET returns 404 Not Found. Write the value once (admin or terminal API) and subsequent reads will succeed.
What is the difference between the admin and terminal settings APIs?
The admin API (/api/admin/settings) returns objects with key/value/category metadata and takes {"value": ...} on PUT. The terminal-compatible API (/api/settings/{key}) returns and accepts the raw JSON value with no wrapper.
Can I disable strict SSH host-key checking via settings?
No. The ssh.host_key_checking key is accepted for backward compatibility but ignored — strict host-key checking is always enforced.

Troubleshooting

Setting Change Did Not Apply

  • Confirm the write succeeded by reading the key back: curl .../api/admin/settings/auth.ldap.enabled
  • Refresh the Admin UI — the page may have cached the previous value.
  • Verify your token's role includes system.manage; without it, writes are rejected.
  • For changes that affect long-lived processes, restart the Controller container: docker compose restart controller

License Will Not Activate

  • Check the key is pasted without surrounding whitespace or line breaks.
  • Only team and enterprise tiers are accepted by the Controller; other tiers are rejected.
  • Expired keys are refused at activation time. Confirm the expiry on your key.
  • A 502 from the activate endpoint means the Controller could not reach the license server — check outbound HTTPS from the Controller host.

License Shows “grace_period”

  • This means the daily background revalidation could not confirm the license (server unreachable or validation failed). Restore outbound connectivity and the next cycle will return it to valid.
  • Dev licenses without a server ID are skipped during revalidation by design.

Setting Not Found (404)

  • The key has never been written. PUT it once to create it (both APIs upsert on write).
  • Keys are case-sensitive and dot-separated — double-check spelling, e.g. session.jwt_expiry_hours not session.jwt_expiry.

Plugin Will Not Enable

  • Confirm plugins.enabled is true under Settings → Plugins.
  • Check Controller logs for the plugin's startup output: docker compose logs controller
  • See Plugin System for plugin-specific requirements.