This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Documentation

Welcome to the Nebula Commander documentation.

This section contains guides for installing, configuring, and using Nebula Commander:

When the backend is running, interactive API docs are available at /api/docs.

1 - Getting Started

Nebula Commander is a self-hosted control plane for Nebula overlay networks. You run the server yourself and manage networks, nodes, certificates, and access from a web UI and optional device client.

Features

  • Networks and nodes – Create networks, manage nodes, IP allocation, and certificates
  • Web UI – React dashboard with OIDC (for example, Keycloak) or a dev token for local development
  • Device client (ncclient) – Enroll devices with a one-time code, then run as a daemon to pull config and certificates and optionally run or restart Nebula when config changes. Install with pip install nebula-commander; see ncclient for details.

Status

Early development. Core APIs and UI are implemented.

Prerequisites

  • Nebula – You need Nebula installed on devices that will join your networks. Nebula Commander issues certificates and config; the Nebula binary runs on each node.
  • Server – A machine or container to run the backend (and optionally the frontend). Python 3.10+ for development; Docker or NixOS for deployment.

Next steps

  1. Choose an installation method (Docker or NixOS), or development setup for local hacking.
  2. Configure the backend (database, JWT, and optionally OIDC and SMTP).
  3. Use the Web UI and ncclient as needed.

License

Backend and frontend: MIT. Client (ncclient): GPLv3 or later. See the repository for details.

2 - Server Installation

You can run Nebula Commander in several ways:

MethodBest for
Docker (recommended)Quick start and production: pre-built images, docker compose
NixOSNixOS hosts: systemd module with options for port, database, certs, JWT
Reverse ProxyNginx, Traefik, or Caddy in front with TLS and HSTS

Choose one and follow the linked page. For local development (venv, uvicorn, frontend dev server), see Development: Setup. After installation, see Server Configuration to set the database, JWT, and optional OIDC.

2.1 - Docker

Run Nebula Commander with Docker using pre-built images or a local build. You can download all required files without cloning the repository. For HTTPS and HSTS at the edge (Nginx, Traefik, or Caddy in front of the frontend container), see Reverse Proxy.

Downloading files

You need: docker-compose.yml, docker-compose-keycloak.yml, .env.example, and the env.d.example/ directory (backend and Keycloak config). Either use the commands below or the provided script.

Using curl

Run these in an empty directory (or where you want the Docker setup):

BASE_URL="https://raw.githubusercontent.com/NixRTR/nebula-commander/main/docker"

curl -sSL -o docker-compose.yml "${BASE_URL}/docker-compose.yml"
curl -sSL -o docker-compose-keycloak.yml "${BASE_URL}/docker-compose-keycloak.yml"
curl -sSL -o .env.example "${BASE_URL}/.env.example"

mkdir -p env.d.example/keycloak
curl -sSL -o env.d.example/backend "${BASE_URL}/env.d.example/backend"
curl -sSL -o env.d.example/keycloak/keycloak "${BASE_URL}/env.d.example/keycloak/keycloak"
curl -sSL -o env.d.example/keycloak/postgresql "${BASE_URL}/env.d.example/keycloak/postgresql"

Then create the Docker network (required by the compose files):

docker network create nebula-commander

Copy the example env into place and edit as needed:

cp .env.example .env
cp -r env.d.example env.d
# Edit env.d/backend (JWT secret, OIDC, etc.)

Using the download script

The repository provides a script that downloads the same files and checks for prerequisites. It does not install anything; if Docker or Docker Compose is missing, it prints what you need and exits.

curl -sSL https://raw.githubusercontent.com/NixRTR/nebula-commander/main/docker/download.sh | bash

Then run the steps the script prints: copy .env.example to .env, copy env.d.example to env.d, edit env.d/backend, create the network, and start with docker compose up -d.


Example file contents

Below are the default file contents for reference. After downloading, copy to .env and env.d/ and customize.

docker-compose.yml

name: nebulacdr

include:
  - path: ./docker-compose-keycloak.yml

services:

  backend:
    build:
      context: ..
      dockerfile: docker/backend/Dockerfile
    image: ghcr.io/nixrtr/nebula-commander-backend:latest
    container_name: nebula-commander-backend
    restart: unless-stopped
    ports:
      - "${BACKEND_PORT:-8081}:8081"
    volumes:
      # Persistent data storage
      - nebula-commander-data:/var/lib/nebula-commander
      # Optional: mount JWT secret file
      - ${JWT_SECRET_FILE:-/dev/null}:/run/secrets/jwt-secret:ro
    env_file:
      # Backend configuration (database, JWT, OIDC, CORS, debug)
      - env.d/backend
    environment:
      - NEBULA_COMMANDER_SERVER_HOST=0.0.0.0
      - NEBULA_COMMANDER_SERVER_PORT=8081
    healthcheck:
      test: ["CMD", "python3", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8081/api/health')"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 10s
    networks:
      - nebula-commander

  frontend:
    build:
      context: ..
      dockerfile: docker/frontend/Dockerfile
    image: ghcr.io/nixrtr/nebula-commander-frontend:latest
    container_name: nebula-commander-frontend
    restart: unless-stopped
    ports:
      - "${FRONTEND_PORT:-80}:80"
    depends_on:
      backend:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 5s
    networks:
      - nebula-commander

networks:
  nebula-commander:
    external: true

volumes:
  nebula-commander-data:
    driver: local

docker-compose-keycloak.yml

# Keycloak OIDC Authentication Stack
# To use: docker compose -f docker-compose.yml -f docker-compose-keycloak.yml up -d

services:
  keycloak_db:
    image: postgres:16-alpine
    container_name: keycloak-db
    restart: unless-stopped
    env_file:
      - env.d/keycloak/postgresql
    volumes:
      - keycloak-db-data:/var/lib/postgresql/data
    networks:
      - nebula-commander
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-keycloak}"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 10s

  keycloak:
    build:
      context: ..
      dockerfile: docker/keycloak/Dockerfile
    image: ghcr.io/nixrtr/nebula-commander-keycloak:latest
    container_name: keycloak
    restart: unless-stopped
    env_file:
      - env.d/keycloak/keycloak
      - env.d/backend
    ports:
      - "${KEYCLOAK_PORT:-8080}:8080"
      - "${KEYCLOAK_ADMIN_PORT:-9000}:9000"
    depends_on:
      keycloak_db:
        condition: service_healthy
    networks:
      - nebula-commander
    healthcheck:
      test: ["CMD-SHELL", "exec 3<>/dev/tcp/127.0.0.1/8080 && echo -e 'GET /health/ready HTTP/1.1\\r\\nhost: 127.0.0.1\\r\\nConnection: close\\r\\n\\r\\n' >&3 && cat <&3 | grep -q '200 OK'"]
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 60s

volumes:
  keycloak-db-data:
    driver: local

networks:
  nebula-commander:
    external: true

.env.example

# Nebula Commander Docker Infrastructure Configuration
# Copy to .env and customize. Backend settings go in env.d/backend.

# Port for the frontend (Nginx)
FRONTEND_PORT=80

# Port for the backend (FastAPI)
# BACKEND_PORT=8081

# Port for Keycloak (when using docker-compose-keycloak.yml)
# KEYCLOAK_PORT=8080

# Path to JWT secret file on host (optional)
# JWT_SECRET_FILE=/path/to/jwt-secret.txt

env.d.example/backend

Full example. See Configuration: Environment for all options.

# =============================================================================
# Nebula Commander Backend Configuration
# =============================================================================
# All variables use the NEBULA_COMMANDER_ prefix.

# Database
NEBULA_COMMANDER_DATABASE_URL=sqlite+aiosqlite:////var/lib/nebula-commander/db.sqlite
NEBULA_COMMANDER_CERT_STORE_PATH=/var/lib/nebula-commander/certs

# JWT (generate with: openssl rand -base64 32)
NEBULA_COMMANDER_JWT_SECRET_KEY=CHANGE_ME_GENERATE_RANDOM_32_CHARS_MIN
NEBULA_COMMANDER_JWT_ALGORITHM=HS256
NEBULA_COMMANDER_JWT_EXPIRATION_MINUTES=1440

# Public URL (FQDN or host:port)
NEBULA_COMMANDER_PUBLIC_URL=https://nebula.example.com

# OIDC (optional)
NEBULA_COMMANDER_OIDC_ISSUER_URL=http://keycloak:8080/realms/nebula-commander
NEBULA_COMMANDER_OIDC_PUBLIC_ISSUER_URL=https://auth.example.com/realms/nebula-commander
NEBULA_COMMANDER_OIDC_CLIENT_ID=nebula-commander
NEBULA_COMMANDER_OIDC_CLIENT_SECRET=YOUR_KEYCLOAK_CLIENT_SECRET_HERE
NEBULA_COMMANDER_OIDC_SCOPES=openid profile email

# CORS (include your public URL)
NEBULA_COMMANDER_CORS_ORIGINS=https://nebula.example.com
NEBULA_COMMANDER_SESSION_HTTPS_ONLY=false
NEBULA_COMMANDER_ALLOWED_REDIRECT_HOSTS=

# SMTP (optional)
NEBULA_COMMANDER_SMTP_ENABLED=false
NEBULA_COMMANDER_SMTP_HOST=smtp.gmail.com
NEBULA_COMMANDER_SMTP_PORT=587
NEBULA_COMMANDER_SMTP_USE_TLS=true
NEBULA_COMMANDER_SMTP_USERNAME=your-email@gmail.com
NEBULA_COMMANDER_SMTP_PASSWORD=your-app-password
NEBULA_COMMANDER_SMTP_FROM_EMAIL=noreply@example.com
NEBULA_COMMANDER_SMTP_FROM_NAME=Nebula Commander

# Debug (disable in production)
NEBULA_COMMANDER_DEBUG=true

env.d.example/keycloak/keycloak

# Keycloak Configuration
KC_DB=postgres
KC_DB_URL_HOST=keycloak_db
KC_DB_URL_PORT=5432
KC_DB_URL_DATABASE=keycloak
KC_DB_USERNAME=keycloak
KC_DB_PASSWORD=keycloak_db_password

KC_BOOTSTRAP_ADMIN_USERNAME=admin
KC_BOOTSTRAP_ADMIN_PASSWORD=admin

KC_HOSTNAME=localhost
KC_HOSTNAME_STRICT=false
KC_HTTP_ENABLED=true
KC_HEALTH_ENABLED=true
KC_METRICS_ENABLED=true
KC_LOG_LEVEL=info

env.d.example/keycloak/postgresql

# PostgreSQL Configuration for Keycloak
POSTGRES_DB=keycloak
POSTGRES_USER=keycloak
POSTGRES_PASSWORD=keycloak_db_password

Quick start (pre-built images)

After you have the files and env.d/backend configured:

docker network create nebula-commander   # if not already created
docker compose pull
docker compose up -d
docker compose logs -f

The application is available at http://localhost (or the port set in .env).

Building locally

If you prefer to build images instead of pulling:

docker compose build
docker compose up -d

See Development: Manual builds for build-args and multi-arch builds.

Images

ImageRegistryBasePortPlatforms
Backendghcr.io/nixrtr/nebula-commander-backend:latestPython 3.13-slim8081linux/amd64, linux/arm64
Frontendghcr.io/nixrtr/nebula-commander-frontend:latestnginx:alpine80linux/amd64, linux/arm64
Keycloakghcr.io/nixrtr/nebula-commander-keycloak:latestKeycloak8080linux/amd64, linux/arm64

Architecture

  • Frontend (Nginx) – Serves the React SPA and proxies /api/* to the backend. Port 80.
  • Backend (FastAPI) – REST API, certificate management, SQLite. Port 8081.
  • Persistent volume – SQLite database and Nebula certificates under /var/lib/nebula-commander.
Frontend (Nginx) :80  -->  Backend (FastAPI) :8081  -->  Volume (db + certs)

Configuration

Use two places:

  1. Infrastructure.env: ports, optional JWT secret file path.
  2. Backendenv.d/backend: all NEBULA_COMMANDER_* variables (database, JWT, OIDC, CORS, SMTP, debug).

See Configuration for full option lists.

With Keycloak (OIDC)

To use Keycloak for login:

docker compose -f docker-compose.yml -f docker-compose-keycloak.yml up -d

Configure OIDC in env.d/backend and optionally use the zero-touch Keycloak setup. Details: Configuration: OIDC.

Without Keycloak

Run only the backend and frontend. The backend exposes /api/auth/dev-token when OIDC is not configured (suitable for development only).

2.2 - NixOS

Run Nebula Commander as a NixOS service by adding the module and enabling the service. You can add the module from a local path (clone) or, when available, from a flake.

Adding the module (path-based, no flake)

Use this when you have the nebula-commander repository on disk (for example under /etc/nixos or a path you manage).

1. Get the repository

Clone or copy the nebula-commander repo so that the path contains both nix/ and backend/:

git clone https://github.com/NixRTR/nebula-commander.git /etc/nixos/nebula-commander
# Or use a path of your choice; the module expects ../../backend relative to nix/module.nix

2. Import the module in your NixOS configuration

In configuration.nix (or a NixOS module you include), add the import and enable the service:

{
  imports = [
    /etc/nixos/nebula-commander/nix/module.nix
  ];

  services.nebula-commander.enable = true;
}

If you use a different path, use that path in imports, for example ./nebula-commander/nix/module.nix if the repo is in the same directory as your configuration.nix.

3. Optional: set options

Override any of the options (see the table below). The default package builds the backend from the same repo: it copies backend/ from the path relative to nix/module.nix (../../backend), so your clone must have that layout.

services.nebula-commander = {
  enable = true;
  backendPort = 8081;
  databasePath = "/var/lib/nebula-commander/db.sqlite";
  certStorePath = "/var/lib/nebula-commander/certs";
  jwtSecretFile = null;   # or e.g. /run/secrets/nebula-commander-jwt
  debug = false;
};

Then rebuild: nixos-rebuild switch (or your usual method).


Adding via a flake

If the nebula-commander repository provides a flake.nix that exposes a NixOS module, you can add it as a flake input and use it in your NixOS configuration.

1. Add nebula-commander as an input

In your system flake (e.g. flake.nix in /etc/nixos or your config directory):

{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    nebula-commander.url = "github:NixRTR/nebula-commander";
    # If the repo has a flake, you might use a specific ref:
    # nebula-commander.url = "github:NixRTR/nebula-commander/main";
  };

  outputs = { self, nixpkgs, nebula-commander, ... }: {
    nixosConfigurations.yourHost = nixpkgs.lib.nixosSystem {
      system = "x86_64-linux";
      modules = [
        ./configuration.nix
        nebula-commander.nixosModules.default   # exact name depends on what the flake exports
      ];
    };
  };
}

The exact attribute (e.g. nebula-commander.nixosModules.default) depends on what the nebula-commander flake exports. If the repository does not yet have a flake.nix, use the path-based import above. When a flake is added, it may expose the module under nixosModules.default or a named module; check the flake output.

2. Enable the service and optional package

In your configuration.nix (or in the flake’s module list):

services.nebula-commander.enable = true;
# If the flake provides a package, point the service at it:
# services.nebula-commander.package = nebula-commander.packages.${pkgs.system}.default;

Then rebuild: nixos-rebuild switch --flake .#yourHost (or your usual flake command).


Options

All options live under services.nebula-commander:

OptionTypeDefaultDescription
enableboolEnable the Nebula Commander service
packagepackagebackend source from repoNebula Commander package (backend source). With path-based import, built from ../../backend relative to the module file.
portport8080Port for the HTTP API when using nginx
backendPortport8081Port for the FastAPI backend (internal)
databasePathstring/var/lib/nebula-commander/db.sqliteSQLite database file path
certStorePathstring/var/lib/nebula-commander/certsDirectory for CA and host certificates
jwtSecretFilenull or pathnullPath to JWT secret file (e.g. managed by sops-nix). If null, a oneshot service generates /var/lib/nebula-commander/jwt-secret on first boot.
debugboolfalseEnable debug mode

The module creates a nebula-commander system user and group, tmpfiles for data directories, and (when jwtSecretFile is null) a oneshot service that generates a JWT secret. The main service runs uvicorn with the backend and passes environment variables (database URL, cert store path, port, JWT secret file, debug).

For OIDC and other backend settings not exposed as NixOS options, extend the service environment in your config or use a config file; the backend reads NEBULA_COMMANDER_* from the environment.

2.3 - Reverse Proxy

Nebula Commander is typically run behind a reverse proxy such as Nginx, Traefik, or Caddy. The frontend and backend containers listen on plain HTTP inside Docker; your reverse proxy:

  • Terminates TLS on port 443 with a certificate
  • Proxies requests to the frontend container (and through it to the backend)
  • Adds security headers like Strict-Transport-Security (HSTS)

This page shows recommended HSTS settings and example reverse proxy configurations.

HSTS and HTTPS

For best security, configure HTTPS and HSTS on your reverse proxy, not inside the containers. That lets you choose the right TLS policy for your environment (LAN-only, VPN-only, or Internet-facing).

Recommended baseline HSTS header:

Strict-Transport-Security: max-age=31536000

For public Internet deployments on a DNS-backed domain, you can include subdomains:

Strict-Transport-Security: max-age=31536000; includeSubDomains

Avoid preload and avoid HSTS on bare IPs or non-public hostnames:

  • HSTS preload lists require a public, DNS-backed domain
  • HSTS on an IP or "internal-only" hostname can cause problems if it is repurposed later

The examples below assume you already have TLS certificates (for example from Let’s Encrypt) and that the Nebula Commander frontend container is reachable as frontend:80 on a Docker network.

Nginx example

Minimal Nginx configuration to put nebula.example.com behind HTTPS with HSTS:

server {
    listen 443 ssl http2;
    server_name nebula.example.com;

    ssl_certificate     /path/to/fullchain.pem;
    ssl_certificate_key /path/to/privkey.pem;

    # HSTS: adjust policy for your environment
    add_header Strict-Transport-Security "max-age=31536000" always;

    location / {
        proxy_pass http://frontend:80;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Change:

  • nebula.example.com to your actual hostname
  • Certificate paths to where your certs are stored
  • frontend:80 if you renamed the frontend service or are not using Docker networking

Traefik (file or dynamic configuration)

Traefik can apply HSTS via a headers middleware and route HTTPS traffic to the frontend service. Example dynamic configuration (YAML) using the websecure entrypoint:

http:
  middlewares:
    hsts-headers:
      headers:
        stsSeconds: 31536000
        stsIncludeSubdomains: true

  routers:
    nebula:
      rule: "Host(`nebula.example.com`)"
      entryPoints: ["websecure"]
      service: nebula-frontend
      middlewares:
        - hsts-headers
      tls:
        certResolver: letsencrypt

  services:
    nebula-frontend:
      loadBalancer:
        servers:
          - url: "http://frontend:80"

Adapt:

  • nebula.example.com to your hostname
  • websecure and letsencrypt to your Traefik entrypoint and certresolver names
  • frontend:80 to match your frontend container name and port

Traefik with Docker labels

If you use the Nebula Commander Docker compose stack and Traefik’s Docker provider, you can attach labels directly to the frontend service. Example:

  frontend:
    image: ghcr.io/nixrtr/nebula-commander-frontend:latest
    container_name: nebula-commander-frontend
    restart: unless-stopped
    networks:
      - nebula-commander
      - traefik
    labels:
      - "traefik.enable=true"

      # Router: HTTPS entrypoint and host rule
      - "traefik.http.routers.nebula.rule=Host(`nebula.example.com`)"
      - "traefik.http.routers.nebula.entrypoints=websecure"
      - "traefik.http.routers.nebula.tls.certresolver=letsencrypt"

      # Service: forward to container port 80
      - "traefik.http.services.nebula-frontend.loadbalancer.server.port=80"

      # HSTS middleware
      - "traefik.http.middlewares.nebula-hsts.headers.stsSeconds=31536000"
      - "traefik.http.middlewares.nebula-hsts.headers.stsIncludeSubdomains=true"

      # Attach middleware to router
      - "traefik.http.routers.nebula.middlewares=nebula-hsts"

You must also ensure Traefik is on the same Docker network as the frontend service (for example a shared traefik network).

Rename router (nebula), service (nebula-frontend), middleware (nebula-hsts), entrypoint (websecure), and certresolver (letsencrypt) to fit your existing Traefik setup.

Caddy example

Caddy can automatically obtain certificates and proxy to the frontend container:

nebula.example.com {
    reverse_proxy frontend:80

    header {
        Strict-Transport-Security "max-age=31536000"
    }
}

Change nebula.example.com and frontend:80 as needed. Caddy will handle TLS certificates for you when properly configured with DNS and ports 80/443 exposed.

3 - Server Configuration

Nebula Commander is configured with environment variables. All backend settings use the prefix NEBULA_COMMANDER_. You can configure everything from the docs below.

  • Environment variables – Full list: database, JWT, OIDC, CORS, session, SMTP, debug. Use this to configure the backend completely.
  • OIDC – Log in with Keycloak or another OIDC provider: issuer URLs (internal vs public), client id/secret, redirect URI, and zero-touch vs manual setup.

Two-tier layout (Docker)

When using Docker:

  • Infrastructuredocker/.env: ports (e.g. frontend, backend, Keycloak), optional JWT_SECRET_FILE path.
  • Backenddocker/env.d/backend: every NEBULA_COMMANDER_* variable. Copy from env.d.example/backend and edit.

See Installation: Docker for the copy steps.

NixOS

NixOS options (port, backendPort, databasePath, certStorePath, jwtSecretFile, debug) are documented in Installation: NixOS. For OIDC and other backend-only settings, extend the service environment or point the backend at a config file if your setup supports it.

3.1 - Environment Variables

All backend settings use the NEBULA_COMMANDER_ prefix. Set them in the environment or in a file (e.g. docker/env.d/backend). Optional values can be omitted; defaults apply.

Application

VariableDescriptionDefault
NEBULA_COMMANDER_DEBUGEnable debug mode (enables dev-token endpoint; do not use in production)false

Database

VariableDescriptionDefault
NEBULA_COMMANDER_DATABASE_URLDatabase URL (SQLite: use four slashes for absolute path, e.g. sqlite+aiosqlite:////var/lib/nebula-commander/db.sqlite)sqlite+aiosqlite:////var/lib/nebula-commander/db.sqlite
NEBULA_COMMANDER_DATABASE_PATHOverride for SQLite path
NEBULA_COMMANDER_CERT_STORE_PATHDirectory for CA and host certificates/var/lib/nebula-commander/certs

JWT

VariableDescriptionDefault
NEBULA_COMMANDER_JWT_SECRET_KEYSecret for signing tokens (generate with e.g. openssl rand -base64 32)change-this-in-production
NEBULA_COMMANDER_JWT_SECRET_FILEPath to file containing JWT secret (overrides secret key when present)
NEBULA_COMMANDER_JWT_ALGORITHMJWT algorithmHS256
NEBULA_COMMANDER_JWT_EXPIRATION_MINUTESToken expiration in minutes1440 (24 hours)

Public URL and OIDC

VariableDescriptionDefault
NEBULA_COMMANDER_PUBLIC_URLBase URL where users reach the app (FQDN or host:port). Used to derive redirect URI and for redirect validation.
NEBULA_COMMANDER_OIDC_ISSUER_URLOIDC issuer URL used by the backend to reach the provider (internal; e.g. http://keycloak:8080/realms/nebula-commander)
NEBULA_COMMANDER_OIDC_PUBLIC_ISSUER_URLOIDC issuer URL as seen by the browser (FQDN or host:port)
NEBULA_COMMANDER_OIDC_CLIENT_IDOIDC client ID
NEBULA_COMMANDER_OIDC_CLIENT_SECRETOIDC client secret
NEBULA_COMMANDER_OIDC_CLIENT_SECRET_FILEPath to file containing OIDC client secret
NEBULA_COMMANDER_OIDC_REDIRECT_URICallback URL (optional; derived as PUBLIC_URL + /api/auth/callback when PUBLIC_URL is set)
NEBULA_COMMANDER_OIDC_SCOPESOIDC scopes (space-separated)openid profile email

CORS and session

VariableDescriptionDefault
NEBULA_COMMANDER_CORS_ORIGINSAllowed CORS origins: * or comma-separated list. Include your public app URL. Using * with credentials is insecure.http://localhost:3000, http://localhost:5173
NEBULA_COMMANDER_SESSION_HTTPS_ONLYSet session cookie to HTTPS-only (use true in production with HTTPS)false
NEBULA_COMMANDER_ALLOWED_REDIRECT_HOSTSAllowed hosts for OAuth/OIDC redirects (comma-separated). When empty and PUBLIC_URL is set, derived from PUBLIC_URL.

Certificates and device tokens

VariableDescriptionDefault
NEBULA_COMMANDER_DEFAULT_CERT_EXPIRY_DAYSDefault certificate expiry in days365
NEBULA_COMMANDER_DEVICE_TOKEN_EXPIRATION_DAYSDevice token (enrollment) expiry in days3650

SMTP (optional)

VariableDescriptionDefault
NEBULA_COMMANDER_SMTP_ENABLEDEnable sending email (e.g. for invitations)false
NEBULA_COMMANDER_SMTP_HOSTSMTP hostlocalhost
NEBULA_COMMANDER_SMTP_PORTSMTP port587
NEBULA_COMMANDER_SMTP_USERNAMESMTP username
NEBULA_COMMANDER_SMTP_PASSWORDSMTP password
NEBULA_COMMANDER_SMTP_PASSWORD_FILEPath to file containing SMTP password
NEBULA_COMMANDER_SMTP_USE_TLSUse TLStrue
NEBULA_COMMANDER_SMTP_FROM_EMAILFrom addressnoreply@example.com
NEBULA_COMMANDER_SMTP_FROM_NAMEFrom nameNebula Commander

Server (advanced)

VariableDescriptionDefault
NEBULA_COMMANDER_HOSTBind host0.0.0.0
NEBULA_COMMANDER_PORTBind port8081

Security notes

  • Generate a strong JWT secret for production (e.g. openssl rand -base64 32). Do not use the default.
  • Prefer *_FILE options (JWT, OIDC secret, SMTP password) over plain env vars when possible.
  • In production: set DEBUG=false, use HTTPS for PUBLIC_URL and OIDC, and set CORS_ORIGINS to your actual frontend origin(s). For examples of putting Nebula Commander behind Nginx, Traefik, or Caddy with TLS and HSTS, see Reverse Proxy.

3.2 - OIDC Authentication

Nebula Commander can use OpenID Connect (OIDC) for login. Keycloak is supported with a pre-configured setup; other OIDC providers (Authentik, Auth0, etc.) work with the same environment variables.

Required variables

Set these in env.d/backend (Docker) or in the backend environment:

  • NEBULA_COMMANDER_PUBLIC_URL – URL where users reach the app (FQDN or host:port). The redirect URI is derived as PUBLIC_URL + /api/auth/callback if you do not set OIDC_REDIRECT_URI.
  • NEBULA_COMMANDER_OIDC_ISSUER_URL – Issuer URL used by the backend to talk to the provider. Inside Docker use the container name and container port (e.g. http://keycloak:8080/realms/nebula-commander). Do not use the host-mapped port here.
  • NEBULA_COMMANDER_OIDC_PUBLIC_ISSUER_URL – Issuer URL as seen by the browser (FQDN or host:port). Use the same URL users would use to open the provider’s login page.
  • NEBULA_COMMANDER_OIDC_CLIENT_ID – OIDC client ID (e.g. nebula-commander).
  • NEBULA_COMMANDER_OIDC_CLIENT_SECRET – Client secret from the provider. Prefer NEBULA_COMMANDER_OIDC_CLIENT_SECRET_FILE in production.

Optional: NEBULA_COMMANDER_OIDC_REDIRECT_URI – Override only if you need a callback URL different from PUBLIC_URL + /api/auth/callback.

Keycloak with Docker

  1. Copy example env files and edit env.d/backend (set PUBLIC_URL, OIDC issuer URLs, client id/secret).
  2. Start with Keycloak:
    docker compose -f docker-compose.yml -f docker-compose-keycloak.yml up -d
  3. Create a user in Keycloak Admin and assign client roles under the nebula-commander client: system-admin, network-owner, or user.

Zero-touch Keycloak

The project provides a custom Keycloak image that imports the realm and theme at startup. Build the image (see Installation: Docker and docker/keycloak), set the same OIDC variables in env.d/backend, and start the stack. No manual realm creation is required; create users and assign roles in the Admin console.

Manual Keycloak setup

If you do not use the zero-touch image, create the realm and client in Keycloak:

  • Client type: OpenID Connect, confidential, standard flow.
  • Valid redirect URI: your PUBLIC_URL + /api/auth/callback.
  • Valid post-logout redirect URIs: PUBLIC_URL + /*.
  • Web origins: your PUBLIC_URL.
  • Create client roles: system-admin, network-owner, user. Assign them to users via Role mapping.

Then set the client secret in env.d/backend.

Backend cannot reach Keycloak

The backend talks to Keycloak on the Docker network. Use the container port in NEBULA_COMMANDER_OIDC_ISSUER_URL (e.g. http://keycloak:8080/realms/nebula-commander), not the host-mapped port. Use the host port or FQDN only in NEBULA_COMMANDER_OIDC_PUBLIC_ISSUER_URL and in NEBULA_COMMANDER_PUBLIC_URL.

External OIDC provider

Use the same variables. In the provider, set redirect URI to PUBLIC_URL + /api/auth/callback, client type Confidential, grant type Authorization Code. Set OIDC_ISSUER_URL and OIDC_PUBLIC_ISSUER_URL to the provider’s issuer URL (often the same). Run without the Keycloak compose file.

Roles and permissions

  • system-admin – Manage users; view all networks/nodes with limited data; delete networks/nodes (with reauth); request access grants from owners.
  • network-owner – Create and manage networks, nodes, invitations, node requests, and access; set default node parameters and auto-approval.
  • user – Access only what network owners grant; request nodes and higher access.

See the repository docker/README for detailed RBAC and permission features.

4 - Web UI Usage

The Nebula Commander web UI is a React dashboard that talks to the backend API. You use it to manage networks, nodes, certificates, and users.

Web UI home

Logging in

  • With OIDC – When OIDC is configured (OIDC), open the app URL and you are redirected to the provider (e.g. Keycloak). After login, you are sent back to the UI with a session.
  • Without OIDC (development) – When the backend has DEBUG=true and OIDC is not configured, the backend exposes a dev-token endpoint. The UI can log you in without a real IdP. Do not enable this in production.

Main flows

  • Networks – Create and manage Nebula networks. As a network owner you control nodes, IP allocation, and firewall groups.
  • Groups – Define security groups and inbound firewall rules per group.
  • Nodes – Add nodes to networks, assign IPs, create or sign certificates, and generate enrollment codes for ncclient.
  • Client Download – Download ncclient binaries (CLI and Windows tray) served from this server.
  • Invitations – Invite users to networks with roles and permissions (when OIDC is enabled).

Certificates are created or signed from the Nodes page (Create or Sign flow). For Sign flow, the server does not have the private key; place host.key on the device (e.g. in the ncclient output directory).

Access control

Access is role-based: system-admin, network-owner, and user. Permissions include network-level and node-level access, node request workflow, auto-approval, and access grants. Critical actions (e.g. delete network or node) require reauthentication. Details are in the repository docker/README.

4.1 - Networks

The Networks page lists all Nebula networks you can access and lets you create or delete networks. Each network has a name and a subnet (CIDR) used for IP allocation to nodes.

Networks page

Adding a new network

  1. Open Networks in the sidebar.
  2. Click Add Network.
  3. Fill in the form:
    • Network Name – A label for the network (e.g. production, home). Must be unique and non-empty.
    • Subnet CIDR – The IPv4 range for this network. Example: 10.100.0.0/24. Node IPs are assigned from this range. Choose a range that does not overlap with your existing networks or LAN.
  4. Click Create (or submit). The new network appears in the table.

You can create multiple networks to separate environments (e.g. dev, staging, prod) or teams.

Listing and managing networks

The table shows for each network:

  • Name – Network name.
  • Subnet – CIDR (e.g. 10.100.0.0/24).
  • Actions – Links to manage nodes in that network, and optionally delete.

Clicking a network row or a “Nodes” link takes you to the Nodes page filtered to that network. From there you add nodes, assign IPs, and manage certificates.

Deleting a network

Deleting a network is a critical action. The UI requires you to reauthenticate before the delete is performed.

  1. On the Networks page, use the delete action for the network you want to remove.
  2. A modal opens asking you to type the network name to confirm.
  3. Type the exact network name and confirm. You are redirected to the OIDC provider (or dev login) to reauthenticate.
  4. After reauthentication, you are returned to the UI and the network is deleted.

Ensure no nodes or critical services depend on the network before deleting. Deletion removes the network and its node/certificate data from Nebula Commander.

4.2 - Groups

The Groups page lets you define Nebula security groups and their inbound firewall rules per network. Groups are used by nodes (each node has one or more groups). Firewall rules control which other groups can send traffic to this group and on what protocol and ports.

Groups page

Selecting a network

At the top of the Groups page, choose a network from the dropdown. Groups and rules are per network. Only networks you have access to appear in the list.

What groups are

In Nebula, a group is a tag (e.g. laptops, servers, admin) that you assign to nodes. The firewall then allows or denies traffic based on group membership. On this page you define the inbound rules for each group: which other groups can connect to this group, and on which protocol and port range.

Adding a group

  1. Select the network.
  2. In the “Add group” area, enter a new group name (e.g. servers). Group names are case-sensitive and must be unique within the network.
  3. Click add. The group appears in the list with an empty set of inbound rules. You can then add rules (see below).

Groups are created implicitly when you add the first rule or when you create a node and assign it a group. If the group does not exist yet, adding an inbound rule for it or assigning it to a node will create it.

Firewall rules per group

For each group you configure inbound rules: who can send traffic to this group.

FieldDescription
Allowed groupThe name of the other group that is allowed to send traffic to this group.
Protocolany, tcp, udp, or icmp.
Port rangePort or range (e.g. 80, 443, 8000-8010) or any for all ports.
DescriptionOptional note for the rule.

Example: To allow the group laptops to reach the group servers on TCP ports 80 and 443, add an inbound rule on the servers group with allowed group laptops, protocol tcp, and port range 80,443 (or two rules if your UI uses one port per rule).

Firewall rules per group

Rules are stored per group. After editing rules, click Save for that group. Changes apply when Nebula config is regenerated (e.g. when ncclient polls or you re-download config).

How to leverage groups

  • Segment by role – Put nodes in groups like laptops, servers, iot. Then allow only laptops and servers to reach iot on specific ports, and only servers to talk to each other on admin ports.
  • Least privilege – Only add inbound rules that are needed. Avoid any/any unless intentional.
  • Node assignment – On the Nodes page, when you create or edit a node you set its group. That node is then subject to the inbound rules defined for that group on this page.

If a group has no inbound rules, no other groups can send traffic to it (default deny). Add rules to allow the desired traffic.

4.3 - Nodes

The Nodes page lists all nodes across networks (or filtered by one network). You create nodes, assign IPs and groups, create or sign certificates, generate enrollment codes for ncclient, and download config.

Nodes list

Creating a node

  1. Open Nodes and optionally filter by network.
  2. Use the Add node (or similar) action. You may need to pick a network first if not filtered.
  3. Enter a hostname (e.g. laptop-alice, server-1). The hostname identifies the node and is used in certificates and config.
  4. The server assigns an IP address from the network’s subnet, or you may be able to choose one if the UI allows.
  5. Set the node’s group (e.g. laptops, servers). The group is used for firewall rules and must match a group defined for that network.
  6. Submit. The node is created. Next you create or sign a certificate (see below).

Node options (when creating or editing)

When creating or editing a node, you can set:

OptionDescription
GroupNebula security group(s) for this node. Used for firewall (see Groups).
LighthouseIf enabled, this node acts as a Nebula lighthouse (others can punch through to it).
RelayIf enabled, this node can relay traffic for other nodes.
Public endpointOptional public address (e.g. 1.2.3.4:4242) so other nodes can reach this node. Used for lighthouse/relay.
Serve DNSIf the node is a lighthouse, enable to serve DNS for the Nebula network.
DNS host / portBind address and port for the built-in DNS server (e.g. 0.0.0.0, 53).
Interval (seconds)Nebula’s lighthouse check interval.
Log levelpanic, fatal, error, warning, info, debug.
Log formatjson or text.
PunchyOptions for NAT punch-through (respond, delay, respond_delay).

Not all options may be visible in the UI; the table covers the main ones from the API. Defaults are applied for any you leave unset.

Editing a node

Click the node row or an Edit action to open the node details.

Node detail Change hostname, group, lighthouse/relay, public endpoint, DNS, logging, or punchy options as needed. Save. If you change the certificate (create/sign) or group, config for this node will change; devices using ncclient will pick up the change on the next poll.

Certificates: Create vs Sign

For each node you need a host certificate. Two flows:

  • Create certificate – The server generates the private key and certificate. You can download a bundle that includes host.key, host.crt, and ca.crt. Use this when the device does not already have a key (e.g. ncclient or you will copy the bundle to the device).
  • Sign certificate – You generate the private key on the device; the server only signs the cert. The server never has host.key. After signing, download host.crt and ca.crt and place them on the device next to your existing host.key.

Choose Create for simplicity when the server can hold the key (or when you will deploy the bundle once). Choose Sign when you want the key to never leave the device.

Enrollment code (for ncclient)

After the node exists and has a certificate, you can generate an enrollment code for ncclient. On the Nodes page, open the node and click Enroll. Copy the one-time code. On the device run:

ncclient enroll --server https://YOUR_NEBULA_COMMANDER_URL --code XXXXXXXX

The device stores a token and can then use ncclient run to pull config and certs. See Client Download for binaries.

Download config

You can download the node’s Nebula config and certs (e.g. config.yaml, ca.crt, host.crt, and host.key if Create was used). Use this for manual (nebula) setup or backup.

Re-enroll and delete

  • Re-enroll – If a device was enrolled but the token is lost or expired, you can generate a new enrollment code and run ncclient enroll again on the device.
  • Delete node – Removes the node and its certificate from Nebula Commander. Critical actions may require reauthentication. Devices using that node will need a new node or re-enrollment if you recreate it.

4.4 - Client Download

The Client Download page (also reachable from the sidebar or at /client-download) lets users download ncclient binaries served directly from your Nebula Commander instance. No internet access to GitHub is required after deployment.

Client Download page

Purpose

Install and run ncclient on a device to enroll with Nebula Commander and pull config and certificates. The enrollment code is obtained from the Nodes page (Enroll button for the node). This page provides the CLI and, on Windows, the tray app.

CLI binaries (command-line ncclient)

Pre-built executables for:

PlatformFile name
Linux x86_64ncclient-linux-amd64
Linux ARM64ncclient-linux-arm64
Windows x86_64ncclient-windows-amd64.exe
macOS Intelncclient-macos-amd64
macOS Apple Siliconncclient-macos-arm64

Downloads are served from /downloads/ (e.g. /downloads/ncclient-linux-amd64). These binaries are only available if the frontend was built with client binaries included (e.g. Docker image built with DOWNLOAD_BINARIES=1). If the page shows no downloads or 404, use ncclient installation from releases or pip instead.

After download on Linux or macOS, make the file executable and place it on your PATH:

chmod +x ncclient-linux-amd64   # or the file you downloaded
# Move to /usr/local/bin or add the directory to PATH

On Windows, add the directory containing ncclient-windows-amd64.exe to your PATH or run it by full path.

Windows Tray App

For Windows, the page may offer:

  • ncclient-tray-windows-amd64.exe – Standalone tray application. Features: system tray icon, Start/Stop polling, Enroll dialog (paste code from Nodes page), Settings (server URL, output dir, poll interval), auto-start at login (Registry), and optionally bundled Nebula binary.

Download and run; no installer required. Alternatively, use the MSI installer (e.g. /downloads/NebulaCommander-windows-amd64.msi) to install both the CLI and the tray app and add them to PATH.

Getting the enrollment code

  1. In the Web UI, go to Nodes.
  2. Open the node for this device (or create one and create/sign a certificate).
  3. Click Enroll and copy the one-time code.
  4. On the device, run: ncclient enroll --server https://YOUR_SERVER_URL --code XXXXXXXX.

Then run ncclient run --server https://YOUR_SERVER_URL to start polling for config and certs. See ncclient usage for full steps.

4.5 - Invitations

The Invitations page lets network owners (and system admins) invite users to a network with a chosen role and permissions. Invitations work when OIDC is configured; invited users sign in via the IdP and then gain access to the specified network.

Invitations – send invite

What invitations are

An invitation associates an email (or link) with a network, a role (e.g. member, admin), and optional permissions (can manage nodes, can invite users, can manage firewall). The invitee accepts by clicking a link or using a token; after acceptance they appear as a user with access to that network according to the role and permissions.

Creating an invitation

  1. Open Invitations in the sidebar.
  2. Use the “Invite user” or similar form.
  3. Fill in:
    • Email – Invitee’s email (used for the invite link and, if SMTP is configured, to send the email).
    • Network – The network to grant access to.
    • Role – e.g. member or admin. Affects default permissions.
    • Can manage nodes – Allow the user to add, edit, delete nodes in this network.
    • Can invite users – Allow the user to send further invitations for this network.
    • Can manage firewall – Allow the user to edit groups and firewall rules for this network.
    • Expires in (days) – How long the invitation link is valid (e.g. 7 days).
  4. Submit. You can either send email (if SMTP is configured) or copy link to send manually.

The invitation appears in the list with status (e.g. pending, accepted, expired).

Accepting an invitation

The invitee opens the invitation link (from email or shared link). The link contains a token and typically looks like /invitations/accept/TOKEN. They are prompted to log in with OIDC if not already logged in. After accepting, they have access to the network according to the role and permissions set in the invitation.

Listing and revoking

The Invitations page lists all invitations you created (or all invitations in the system if you are a system admin).

Invitations – pending list You can see status (pending, accepted, expired), email, network, role, and expiry. To revoke a pending invitation, use the revoke or delete action; the link will no longer work.

Email delivery

If the backend has SMTP configured, invitation emails are sent automatically when you create an invitation. If SMTP is not configured, use “Copy link” and share the URL manually. Email status (sent, failed) may be shown on the invitation row; check the Environment docs for SMTP options.

5 - Client Usage

After server installation and server configuration, you can use the Web UI or run Nebula on devices:

  • ncclient – Device client (experimental): installation and usage (CLI, Windows Tray). ncclient will change often; see nebula for the stable approach.
  • nebula – Preferred method until ncclient stabilizes: create networks and nodes in the UI, then deploy config and certs yourself and run Nebula manually.

All of these assume the backend is running and reachable at the URL you configure.

5.1 - ncclient

Experimental: ncclient is experimental and will change often. Using Nebula manually is the stable method for now.

ncclient is the Nebula Commander device client. It enrolls once with a code from the UI, then polls for config and certificates and can run or restart Nebula when config changes.

  • Installation – Pip (PyPI), binaries (Web UI or GitHub Releases), or Windows Tray (MSI).
  • Usage – Command line (enrollment, daemon, service) and Windows Tray.

Both the CLI and the Windows tray assume the Nebula Commander backend is running and reachable at the URL you configure.

5.1.1 - ncclient Installation

You can install ncclient in several ways: from PyPI with pip, as a standalone binary from the Web UI or GitHub Releases, or on Windows via the MSI installer (CLI and tray app).

Pip (PyPI)

From PyPI (recommended):

pip install nebula-commander

Requires Python 3.10+. This installs the ncclient command.

From source (repo clone):

cd nebula-commander
pip install -r client/requirements.txt

Then run as python -m client --server URL enroll --code XXX, or install the client in development mode to get the ncclient command:

cd client
pip install -e .

Binaries

From Web UI

When your Nebula Commander instance is deployed with client binaries included (for example the frontend image built with DOWNLOAD_BINARIES=1), the Web UI can serve them.

  1. Open your Nebula Commander URL in a browser and log in.
  2. Go to the downloads or client section (or open https://YOUR_SERVER/downloads/ if your instance serves that path).
  3. Download the binary for your platform:
    • Linux x86_64: ncclient-linux-amd64
    • Linux ARM64: ncclient-linux-arm64
    • Windows x86_64: ncclient-windows-amd64.exe
    • macOS Intel: ncclient-macos-amd64
    • macOS Apple Silicon: ncclient-macos-arm64
  4. Place the file in a directory on your PATH (or add that directory to PATH). On Linux and macOS, make it executable: chmod +x ncclient-linux-amd64 (or the file you downloaded).

If your instance does not serve binaries, use From releases or Pip (PyPI) instead.

From releases

Pre-built binaries are attached to GitHub Releases for each version.

  1. Open the releases page and choose a version (e.g. the latest).
  2. Download the file for your platform (same names as in From Web UI).
  3. Optionally verify with SHA256SUMS.txt in the same release.
  4. Place the binary in a directory on your PATH (or add that directory to PATH). On Linux and macOS, make it executable: chmod +x ncclient-linux-amd64 (or the file you downloaded).

Windows Tray

On Windows you can install both the ncclient CLI and the optional tray app using the MSI installer.

What the installer includes:

  • ncclient – CLI for enrollment and daemon (poll for config/certs, run or restart Nebula).
  • ncclient-tray – System tray app: enroll, settings, start/stop polling, optional bundled Nebula, start at login.

Both are installed to %ProgramFiles%\Nebula Commander\. The installer can add that directory to PATH and create Start Menu shortcuts.

Getting the installer:

  • Download NebulaCommander-windows-amd64.msi from the GitHub Releases page for the version you want.
  • Use SHA256SUMS.txt in the same release to verify the file.

After install:

  1. Enroll: in Nebula Commander go to Nodes, open the node, click Enroll, and copy the code. Then run:
    ncclient enroll --server https://YOUR_SERVER_URL --code XXXXXXXX
    
  2. Run the client from the command line or use the tray app from the Start Menu (see Windows Tray in Usage).

For building the MSI yourself, see Development: Manual builds.

5.1.2 - ncclient Usage

After installing ncclient, enroll the device once, then run the daemon (or install the service on Linux).

Command Line

Enrollment

Enrollment is one-time per device. It stores a device token that ncclient uses to fetch config and certificates.

  1. In Nebula Commander, open Nodes, select the node for this device, and click Enroll.
  2. Copy the enrollment code.
  3. On the device, run:
ncclient enroll --server https://YOUR_NEBULA_COMMANDER_URL --code XXXXXXXX

The device token is saved to ~/.config/nebula-commander/token (or /etc/nebula-commander/token when run as root). On Windows, the token is stored under %USERPROFILE%\.config\nebula-commander\token.

Run (daemon)

After enrollment, run ncclient so it periodically pulls config and certificates and optionally runs or restarts Nebula:

ncclient run --server https://YOUR_NEBULA_COMMANDER_URL

Defaults: poll every 60 seconds, write files to /etc/nebula (or ~/.nebula on Windows), and start or restart Nebula from PATH when config changes.

Options

OptionDescription
--output-dir DIRWhere to write config.yaml, ca.crt, host.crt (default: /etc/nebula on Linux/macOS, ~/.nebula on Windows)
--interval NPoll interval in seconds (default: 60)
--token-file PATHPath to device token file
--nebula PATHPath to the nebula binary if it is not on PATH
--restart-service NAMEInstead of running nebula directly, restart this systemd service (e.g. nebula). Use only one of --nebula or --restart-service.

Example with nebula in a non-standard location:

ncclient run --server https://nc.example.com --nebula /usr/local/bin/nebula

Example using systemd to run Nebula (ncclient only restarts the service):

ncclient run --server https://nc.example.com --restart-service nebula

Linux: Creating the Nebula TUN device requires root. Run ncclient as root, e.g. sudo ncclient run --server https://....

Certificates: If the cert was created via the server (Create certificate in the UI), the bundle includes host.key. If it was signed (Sign flow), the server does not have the key; put your host.key in the same directory as the generated certs (the output dir).

Install service

Linux (quick install)

On Linux you can install a systemd service with one command:

sudo ncclient install

This checks for an existing token at /etc/nebula-commander/token. If missing, it prints the exact ncclient enroll ... command to run first. It then prompts for server URL and options (output dir, interval, nebula path, restart-service), writes /etc/default/ncclient and /etc/systemd/system/ncclient.service, and enables (and optionally starts) the service.

  • Use --no-start to enable without starting.
  • Use --non-interactive with NEBULA_COMMANDER_SERVER (and optional env vars) set for scripting.

Other platforms

Run ncclient run under your init system (launchd on macOS, Task Scheduler or NSSM on Windows). Example configs are in the repo under examples/; see examples/README-startup.md for step-by-step setup on macOS and Windows.

Troubleshooting

  • No TUN device / cannot ping Nebula IP – On Linux, run with sudo. For Sign flow, ensure host.key is in the output directory. Check Nebula’s error output (e.g. “failed to get tun device”, “no such file”).
  • Nebula starts then exits – Often missing host.key (Sign flow), wrong config path, or on Linux needing root. Check the Nebula lines ncclient prints.

macOS notes

Token: ~/.config/nebula-commander/token (or /etc/nebula-commander/token as root). Default output dir: /etc/nebula; for non-root use --output-dir ~/.nebula. Nebula: use PATH or --nebula /opt/homebrew/bin/nebula (Apple Silicon) or /usr/local/bin/nebula (Intel). Do not use --restart-service; use launchd for background runs.

Windows notes (CLI)

Token: %USERPROFILE%\.config\nebula-commander\token. Default output dir: %USERPROFILE%\.nebula. Use --nebula if nebula.exe is not on PATH. Do not use --restart-service. For a GUI and start at login, use the Windows Tray section below.

Windows Tray

The Windows tray app provides the same enroll-and-poll flow as the ncclient CLI but with a GUI: tray icon, Enroll and Settings dialogs, Start/Stop polling, optional bundled Nebula, and Start at login (Registry Run).

Usage

  • Enroll – Open the tray menu and use Enroll. Enter the server URL and the one-time code from Nebula Commander (Nodes → Enroll for the node). The token is stored in the same location as the CLI (%USERPROFILE%\.config\nebula-commander\token).
  • Settings – Configure server URL, output directory for config and certs, poll interval, and optional path to the Nebula binary. When the app is built with bundled Nebula, the default Nebula path points to the bundled nebula.exe.
  • Start / Stop polling – Start polling to fetch config and certs periodically and optionally run Nebula. Stop to pause.
  • Start at login – When enabled, the app is registered in the Windows Registry (HKCU\...\Run) so it starts when you sign in. No Windows Service is installed; the tray runs as a normal app.

Settings are stored in %APPDATA%\nebula-commander\settings.json.

Run from source

From the nebula-commander repo root:

pip install -r client/windows/requirements.txt
pip install -e client/
python -m client.windows.tray

Or with pythonw to avoid a console window:

pythonw -m client.windows.tray

Build (PyInstaller)

To build a standalone ncclient-tray.exe (and optionally bundle the Nebula Windows binary):

cd client/windows
pip install -r requirements.txt pyinstaller
python build.py

Output is in client/windows/dist/ (e.g. ncclient-tray.exe). See client/windows/README.md and build.py for details.

The Windows Tray installer (MSI) includes both the CLI and the tray app.

5.2 - Running Nebula manually

This is the preferred method until ncclient stabilizes. You use Nebula Commander to create networks, nodes, and certificates, then run Nebula on devices yourself without ncclient. Config and certs are copied or downloaded from the UI (or API) and you start Nebula manually.

When to use this

  • You prefer to deploy config and certs yourself (copy to the device, run nebula -config ...).
  • You do not want to enroll devices or run the ncclient daemon.
  • You are fine updating config and certs manually when the network or node changes (re-download from the UI or API and replace files, then restart Nebula).

With ncclient, the device enrolls once and ncclient polls for config and certs and can run or restart Nebula automatically. With manual setup, you handle file deployment and restarts yourself.

Steps

1. Create network and node in Nebula Commander

In the Web UI: create a network, add a node for this device, and create or sign a certificate for the node.

  • Create certificate – The server generates the key and cert; you can download a bundle that includes host.key, host.crt, ca.crt, and config.
  • Sign certificate – You generate the key on the device; the server signs the cert. You will need to place your own host.key next to the downloaded certs.

2. Get config and certs onto the device

Download or copy from the UI (or use the API) the node’s config and certificate files. You typically need:

  • config.yaml (Nebula config for this node)
  • ca.crt (CA certificate)
  • host.crt (host certificate for this node)
  • host.key (only if you used Create certificate; with Sign, you already have this on the device)

Where to get them depends on your Nebula Commander version: use the node’s detail or download actions in the UI, or the device/config API. Place the files in a directory on the device (e.g. /etc/nebula or ~/.nebula).

3. Install and run Nebula on the device

Install Nebula from slackhq/nebula (packages, binary release, or build from source). Then run:

nebula -config /path/to/config.yaml

Use the path to the config.yaml you deployed. Nebula will read ca.crt, host.crt, and host.key from the paths specified in the config (often the same directory as the config).

4. Run Nebula at startup (optional)

Use your platform’s init system so Nebula keeps running: systemd on Linux, launchd on macOS, or a Windows service/task. When you change config or certs (after re-downloading from Nebula Commander), replace the files and restart Nebula.

Summary

ncclientManual (nebula)
EnrollmentOne-time; device gets a tokenNone
Config/certsFetched automatically by ncclientYou copy or download and place them
Nebula processncclient can run or restart itYou run and restart Nebula yourself
Updatesncclient polls and updates filesYou re-download and replace files, then restart Nebula

6 - Development

This section covers building Nebula Commander from source, using CI, and the backend API.

  • Setup – Run the backend and frontend locally (venv, uvicorn, npm run dev).
  • GitHub Actions – Workflows for ncclient binaries, releases, and Docker images.
  • Manual Builds – Build ncclient, Windows tray, MSI, and Docker images locally.
  • API – REST API base path, OpenAPI docs, and router summary.

6.1 - Development Setup

Run the backend and frontend locally for development.

Backend

From the nebula-commander repository root:

python -m venv .venv
source .venv/bin/activate   # or .venv\Scripts\activate on Windows
pip install -r backend/requirements.txt
export NEBULA_COMMANDER_DATABASE_URL="sqlite+aiosqlite:///./backend/db.sqlite"
export NEBULA_COMMANDER_CERT_STORE_PATH="./backend/certs"
export DEBUG=true
python -m uvicorn backend.main:app --reload --port 8081

Use a real JWT secret in production; for local dev, DEBUG=true enables the dev-token endpoint.

Frontend

In another terminal:

cd frontend && npm install && npm run dev

Open http://localhost:5173. When the backend is in debug mode, you can log in via the dev token (no OIDC required).

Configuration

Set at least:

  • NEBULA_COMMANDER_DATABASE_URL – SQLite path (e.g. sqlite+aiosqlite:///./backend/db.sqlite)
  • NEBULA_COMMANDER_CERT_STORE_PATH – Directory for CA and host certs
  • DEBUG=true – Enables dev token and hot reload

See Configuration: Environment for all options.

6.2 - GitHub Actions

Nebula Commander uses two GitHub Actions workflows: one to build ncclient binaries (and create releases), and one to build and push Docker images.

Build ncclient Binaries

Workflow file: .github/workflows/build-ncclient-binaries.yml

Triggers

  • Version tags – Push a tag matching v* (e.g. v0.1.5) to build all binaries and create a GitHub Release. This is the only trigger that produces a release; there is no trigger on push to main.
  • Pull requests – Runs when changes touch client/binaries/**, client/windows/**, client/ncclient.py, installer/windows/**, or the workflow file itself.
  • Manual – Use “Run workflow” in the Actions tab (workflow_dispatch).

Platforms built

PlatformArtifact name
Linux x86_64ncclient-linux-amd64
Linux ARM64ncclient-linux-arm64
Windows x86_64ncclient-windows-amd64.exe
macOS Intelncclient-macos-amd64
macOS ARM64 (Apple Silicon)ncclient-macos-arm64

Windows ARM64 is not built (GitHub has no Windows ARM64 runners). Linux ARM64 is built in Docker with QEMU on the host runner.

Jobs and outputs

  1. build – Matrix job: builds the ncclient CLI for each platform. Uploads one artifact per platform.
  2. build-windows-tray – Builds the Windows tray app (ncclient-tray.exe) with optional bundled Nebula. Depends on the CLI build; uploads ncclient-tray-windows-amd64.exe.
  3. build-msi – Runs only on tag pushes. Downloads the Windows CLI and tray artifacts, copies them into installer/windows/redist/, builds the MSI with WiX 5, and uploads NebulaCommander-windows-amd64.msi.
  4. upload-release – Runs only on tag pushes. Downloads all artifacts (CLI, tray, MSI), flattens them, generates SHA256SUMS.txt, and uploads everything to the GitHub Release for that tag. Release is not draft; files can be overwritten.
  5. checksums – Generates checksums for the Actions UI; release gets checksums from the upload-release job.
  6. summary – Prints build status and notes that release binaries were uploaded when the run was tag-triggered.

Creating a release

  1. Bump version (e.g. in client/pyproject.toml or as appropriate).
  2. Tag and push:
    git tag v0.1.5
    git push origin v0.1.5
    
  3. The workflow builds all platforms, the tray app, and the MSI, then creates the release and attaches all binaries and SHA256SUMS.

Manual run

In GitHub: Actions → “Build ncclient Binaries” → “Run workflow”. Choose branch and run. Artifacts appear in the run; no release is created unless you ran from a tag.


Build Docker Images

Workflow file: .github/workflows/build-docker-images.yml

Triggers

  • After ncclient workflow – Runs when the “Build ncclient Binaries” workflow completes. It runs only if that workflow succeeded. Version is taken from the commit that triggered the binaries workflow: if that commit has a tag v*, that tag (without the v) is used; otherwise version is latest.
  • Manual – “Run workflow” in the Actions tab. Version is latest unless the run is triggered by the binaries workflow.

Images built and pushed

All images are pushed to GitHub Container Registry (ghcr.io):

ImagePlatforms
ghcr.io/nixrtr/nebula-commander-backendlinux/amd64, linux/arm64
ghcr.io/nixrtr/nebula-commander-frontendlinux/amd64, linux/arm64
ghcr.io/nixrtr/nebula-commander-keycloaklinux/amd64, linux/arm64

Each image is tagged with the version (e.g. 1.2.3) and latest. Build uses Docker Buildx, layer caching (GitHub Actions cache), and the Dockerfiles under docker/. The frontend image is built with DOWNLOAD_BINARIES=1 so it can serve ncclient binaries from the release.

Summary job

A final job prints the version and the full image names with that tag.


Testing workflows locally (act)

You can run the workflows locally with act. Only Linux jobs run in Docker; Windows and macOS jobs do not use real Windows/macOS runners in act. See the repository .github/README.md for setup, usage, and how to simulate a tag push. Release upload and secrets still require GitHub when running with act.

6.3 - Manual Builds

You can build all ncclient binaries, the Windows tray app, the Windows MSI, and the Docker images locally without using GitHub Actions.

ncclient CLI (standalone binary)

The CLI is built with PyInstaller from client/binaries/. Python 3.11 is used in CI.

Same platform (Linux x86_64, Windows x86_64, macOS)

From the repository root:

pip install -r client/binaries/requirements.txt
pip install -r client/requirements.txt
cd client/binaries
python build.py

Output: client/binaries/dist/ncclient (or ncclient.exe on Windows). Use python build.py --clean to remove build artifacts; python build.py --test to build and run basic tests.

Linux ARM64

CI builds Linux ARM64 in a Docker container because the host runner is x86_64. Locally you can do the same:

docker run --rm --platform linux/arm64 \
  -v "$(pwd):/work" -w /work/client/binaries \
  python:3.11-slim \
  bash -c "
    apt-get update && apt-get install -y binutils &&
    pip install --upgrade pip &&
    pip install -r requirements.txt &&
    pip install -r ../requirements.txt &&
    python build.py
  "

The executable will be in client/binaries/dist/ncclient (arm64). Run this from the repo root so $(pwd) mounts the full tree.

Windows ARM64

On a Windows ARM64 machine (or with an ARM64 Python), install dependencies and run python build.py in client/binaries. To force PyInstaller to target ARM64 from an x64 host, set PYINSTALLER_TARGET_ARCH=arm64 in the environment when running build.py (CI does this for the Windows ARM64 matrix; GitHub does not provide Windows ARM64 runners, so this is for local use only).


Windows tray app

From the repository root:

pip install -r client/requirements.txt
pip install -r client/windows/requirements.txt
pip install pyinstaller
cd client/windows
python build.py

Output: client/windows/dist/ncclient-tray.exe. The build can optionally bundle the Nebula Windows binary; see client/windows/README.md and build.py for details.


Windows MSI

The MSI installs the ncclient CLI and the tray app. You need both executables and WiX 5.

  1. Get the two executables – Build as above or download from a release. Copy them into installer/windows/redist/:

    • redist/ncclient.exe (from client/binaries/dist/ncclient.exe)
    • redist/ncclient-tray.exe (from client/windows/dist/ncclient-tray.exe)
  2. Install WiX 5 – e.g. dotnet tool install --global wix --version 5.0.2. Add the Util extension once:

    wix extension add -g WixToolset.Util.wixext/5.0.0
    
  3. Build the MSI – From installer/windows/:

    wix build Product.wxs -ext WixToolset.Util.wixext -o NebulaCommander-windows-amd64.msi -d Version=0.1.12 -arch x64
    

    Replace 0.1.12 with the version you are building.

Output: NebulaCommander-windows-amd64.msi.


Docker

Backend and frontend (compose)

From the repository root:

cd docker
docker compose build

This builds the backend and frontend images with default build-args. No Keycloak image is built by default; use the Keycloak Dockerfile separately if needed.

Backend image (docker build)

From the repository root:

docker build -f docker/backend/Dockerfile -t nebula-commander-backend:local .

Optional build-args: VERSION (default latest), NEBULA_VERSION (default 1.8.2).

Frontend image (docker build)

From the repository root:

docker build -f docker/frontend/Dockerfile -t nebula-commander-frontend:local .

Build-args:

  • VERSION – Version tag used when downloading ncclient binaries (default latest).
  • DOWNLOAD_BINARIES – Set to 1 to download ncclient binaries from GitHub releases (by version) into the image; set to 0 (default) for local builds that do not need bundled binaries.

Example with version and binaries:

docker build -f docker/frontend/Dockerfile \
  --build-arg VERSION=0.1.12 \
  --build-arg DOWNLOAD_BINARIES=1 \
  -t nebula-commander-frontend:0.1.12 .

Keycloak image

From the repository root:

docker build -f docker/keycloak/Dockerfile -t nebula-commander-keycloak:local .

No required build-args. For the nebula login background, ensure nebula-bg.webp exists under docker/keycloak-theme/nebula/login/resources/img/ (or copy from frontend/public/nebula-bg.webp) before building.

Multi-architecture (Buildx)

To build for linux/amd64 and linux/arm64 and push (e.g. to GHCR):

docker buildx build --platform linux/amd64,linux/arm64 \
  -f docker/backend/Dockerfile \
  -t ghcr.io/nixrtr/nebula-commander-backend:latest \
  --build-arg VERSION=latest \
  --push .

Use the same pattern for the frontend (with VERSION and DOWNLOAD_BINARIES as needed) and keycloak Dockerfiles.

6.4 - API

The Nebula Commander backend exposes a REST API under the base path /api. All routes are prefixed with /api. Most endpoints require a valid JWT in the Authorization: Bearer <token> header unless noted otherwise.

OpenAPI docs

When the backend is running in debug mode, interactive documentation is available at:

  • Swagger UI – /api/docs
  • ReDoc – /api/redoc

For full request/response schemas and parameters, use the interactive docs.


Health and root

MethodPathAuthDescription
GET/apiNoRoot response: name, version, status.
GET/api/healthNoHealth check. Returns {"status": "healthy"}.

Auth (/api/auth)

Authentication and session management. OIDC (e.g. Keycloak) is used when configured; otherwise a dev token is available in debug mode.

MethodPathAuthDescription
GET/api/auth/dev-tokenNoDevelopment only. When DEBUG=true or OIDC is not configured, returns a JWT (token, expires_in). Grants full admin access. Returns 404 in production when OIDC is configured.
GET/api/auth/meOptionalCurrent user info. Returns {"authenticated": false} or {"authenticated": true, "sub", "email", "role", "system_role"}.
GET/api/auth/loginNoRedirects to the OIDC provider for login. Returns 501 if OIDC is not configured.
GET/api/auth/oidc-statusNoOIDC provider readiness. Returns {"status": "ok"}, {"status": "disabled"}, or 503 if provider is unavailable.
GET/api/auth/callbackNoOAuth callback. Exchanges authorization code for tokens and redirects to frontend with JWT in query (/auth/callback?token=...).
GET/api/auth/logoutNoLogs out and redirects to OIDC logout (or frontend if OIDC not configured).
POST/api/auth/reauth/challengeYesCreates a reauthentication challenge for critical operations. Body: none. Response: challenge, reauth_url. Used before destructive actions (e.g. delete network).
GET/api/auth/reauth/callbackNoReauth OAuth callback. Validates state (challenge) and redirects to frontend with reauth token.

Heartbeat (/api/nodes)

Used by ncclient (or other clients) to report node liveness.

MethodPathAuthDescription
POST/api/nodes/{node_id}/heartbeatYes (JWT)Updates last_seen and sets node status to active. Call periodically from enrolled nodes. Response: {"ok": true, "last_seen": "<iso>"}.

Networks (/api/networks)

Create and manage Nebula networks. Permissions are enforced per network (owner, member, and capability flags).

MethodPathAuthDescription
GET/api/networksYesList networks the user can access. Includes role, can_manage_nodes, can_invite_users, can_manage_firewall per network. System admins see all networks (with limited data).
POST/api/networksYesCreate a network. Body: name, subnet_cidr. Creator becomes owner. Returns full network object. 409 if name exists.
GET/api/networks/{network_id}YesGet a single network. System admins need an access grant to see CA path.
PATCH/api/networks/{network_id}YesUpdate network (owner only). Body: optional fields (currently no network-level firewall; use group firewall).
DELETE/api/networks/{network_id}YesDelete network (owner only; system admins can delete any). Body: reauth_token, confirmation (must match network name). 204 on success.
GET/api/networks/{network_id}/group-firewallYesList per-group firewall configs. Requires can_manage_firewall. Response: list of {group_name, inbound_rules}.
PUT/api/networks/{network_id}/group-firewall/{group_name}YesCreate or update inbound firewall rules for a group. Body: inbound_rules (each: allowed_group, protocol (any/tcp/udp/icmp), port_range, optional description).
DELETE/api/networks/{network_id}/group-firewall/{group_name}YesRemove group firewall config for that group. 204 on success.
GET/api/networks/{network_id}/check-ipYesCheck if an IP is available in the network. Query: ip. Response: {"available": true or false}. 400 if IP not in subnet.

Nodes (/api/nodes)

Manage Nebula nodes (hosts) within networks. Used by the Web UI and for manual cert/config workflows.

MethodPathAuthDescription
GET/api/nodesYesList nodes. Query: optional network_id. Returns list of node objects (id, network_id, hostname, ip_address, groups, is_lighthouse, is_relay, status, etc.).
GET/api/nodes/{node_id}YesGet a single node by ID.
PATCH/api/nodes/{node_id}YesUpdate node. Body (all optional): group, is_lighthouse, is_relay, public_endpoint, lighthouse_options, logging_options, punchy_options. 409 if removing the only lighthouse. Response: {"ok": true}.
DELETE/api/nodes/{node_id}YesDelete node: release IP, remove host cert/key files, delete related records. 204. 409 if node is the only lighthouse.
GET/api/nodes/{node_id}/configYesGenerate and return Nebula YAML config for the node (with inline PKI when key is stored). Response: application/yaml attachment.
GET/api/nodes/{node_id}/certsYesReturn a ZIP with ca.crt, host.crt, optional host.key, and README.txt.
POST/api/nodes/{node_id}/revoke-certificateYesRevoke the node’s certificate; node record is kept. Releases IP and removes cert/key files. Node can re-enroll later. Response: {"ok": true}.
POST/api/nodes/{node_id}/re-enrollYesRevoke existing cert (if any) and issue a new one for this node. Frontend typically creates an enrollment code afterward. Response: {"ok": true, "node_id": id}.

Certificates (/api/certificates)

Create or sign host certificates. Used when creating nodes from the Web UI or when using client-generated keys (e.g. betterkeys).

MethodPathAuthDescription
POST/api/certificates/signYesSign a host certificate (client sends public key). Body: network_id, name, public_key, optional group, suggested_ip, duration_days. Response: ip_address, certificate (PEM), optional ca_certificate. Creates or updates node record.
POST/api/certificates/createYesCreate a host certificate (server generates keypair). Body: network_id, name, optional group, suggested_ip, duration_days, is_lighthouse, is_relay, public_endpoint, lighthouse_options, punchy_options. Response: node_id, hostname, ip_address, certificate, private_key, optional ca_certificate. First node in network must be lighthouse. 409 if node name exists or suggested IP is taken.
GET/api/certificatesYesList issued certificates. Query: optional network_id. Response: list of {id, node_id, node_name, network_id, network_name, ip_address, issued_at, expires_at, revoked_at}.

Device (/api/device)

Used by ncclient for enrollment and for fetching config/certs with a device token. Flow: create enrollment code (admin) → device redeems code at POST /enroll → device uses returned token for GET /config and GET /certs.

MethodPathAuthDescription
POST/api/device/enrollment-codesYes (JWT)Create a one-time enrollment code for a node. Body: node_id, expires_in_hours (default 24). Response: code, expires_at, node_id, hostname. Node must already have a certificate.
POST/api/device/enrollNoPublic. Redeem a one-time code. Body: code. Response: device_token, node_id, hostname. Rate limited (e.g. 5 attempts per 15 min per IP). 404 if code invalid/expired, 400 if already used or expired.
GET/api/device/configDevice tokenReturn Nebula YAML config for the device (inline PKI). Header: Authorization: Bearer <device_token>. Optional If-None-Match: <etag> for 304 when unchanged.
GET/api/device/certsDevice tokenReturn ZIP with ca.crt, host.crt, optional host.key, README.txt for the device.

Users (/api/users)

System admin user management. All endpoints require system-admin role.

MethodPathAuthDescription
GET/api/usersSystem adminList all users. Response: list of {id, oidc_sub, email, system_role, created_at, network_count}.
GET/api/users/{user_id}System adminGet user details including networks (list of network id, name, role, permission flags).
PATCH/api/users/{user_id}System adminUpdate user. Body: optional system_role (system-admin or user).
DELETE/api/users/{user_id}System adminDelete user; removes all their network permissions. 204.

Node requests (/api/node-requests)

Request/approve workflow for node creation (e.g. when auto-approve is off).

MethodPathAuthDescription
POST/api/node-requestsYesCreate a node request. Body: network_id, hostname, optional groups, is_lighthouse, is_relay. If user has manage_nodes or network has auto_approve_nodes, request is approved immediately and node is created. Response includes status, created_node_id if approved.
GET/api/node-requestsYesList node requests. Query: optional network_id, status. Users see own requests; network owners/admins see requests for their networks; system admins see all.
POST/api/node-requests/{request_id}/approveYesApprove a pending request; creates the node and allocates IP. Body: empty object. Requires manage_nodes on the network.
POST/api/node-requests/{request_id}/rejectYesReject a pending request. Body: reason. Requires manage_nodes on the network.

Access grants (/api/access-grants)

Temporary access for system admins to a specific network or node (e.g. for support). Only network owners can create grants.

MethodPathAuthDescription
POST/api/access-grantsYesCreate an access grant. Body: admin_user_id, resource_type (network or node), resource_id, duration_hours, reason. Target user must be system-admin.
GET/api/access-grantsYesList grants. Query: active_only (default true). Network owners see grants they created; system admins see grants for them.
DELETE/api/access-grants/{grant_id}YesRevoke a grant. Only the user who created the grant can revoke. 204.

Invitations (/api/invitations)

Invite users to join a network by email. Requires can_invite_users (or owner) on the network.

MethodPathAuthDescription
POST/api/invitationsYesCreate invitation. Body: email, network_id, role (owner/member), can_manage_nodes, can_invite_users, can_manage_firewall, expires_in_days (default 7). Sends email if SMTP configured. 400 if user already member or pending invitation exists.
GET/api/invitationsYesList invitations. Query: optional network_id, status_filter. Owners see invitations for their networks; system admins see all.
GET/api/invitations/public/{token}NoPublic. Get invitation details by token (no auth). Returns network name, inviter, role, permissions, status, expires_at. 410 if expired.
POST/api/invitations/{token}/acceptYesAccept invitation; creates NetworkPermission for current user. Must be logged in. Email should match invitation (optional check).
POST/api/invitations/{invitation_id}/resendYesResend invitation email. Pending only.
DELETE/api/invitations/{invitation_id}YesRevoke invitation (inviter or network owner). Sets status to revoked. 204.

Network permissions (/api/networks)

Manage which users have access to a network and their roles/permissions. Owner-only for list/update/remove; can_invite_users for add.

MethodPathAuthDescription
GET/api/networks/{network_id}/usersYesList users with access to the network. Owner or system admin. Response: list of user_id, email, role, can_manage_, invited_by_, created_at.
POST/api/networks/{network_id}/usersYesAdd user to network. Body: user_id, role (owner/member), can_manage_nodes, can_invite_users, can_manage_firewall. Requires can_invite_users. 400 if already member.
PATCH/api/networks/{network_id}/users/{target_user_id}YesUpdate user’s permissions. Body: optional role, can_manage_nodes, can_invite_users, can_manage_firewall. Owner only. Cannot demote the last owner.
DELETE/api/networks/{network_id}/users/{target_user_id}YesRemove user from network. Owner only. Cannot remove the last owner. 204.

Audit (/api/audit)

Read-only audit log. System admins only.

MethodPathAuthDescription
GET/api/auditSystem adminList audit log entries. Query: limit (default 50, max 200), offset, optional action, resource_type, from_date, to_date. Ordered by occurred_at descending. Response: list of id, occurred_at, action, actor_*, resource_type, resource_id, result, details, client_ip.

Summary

  • /api — Root, health
  • /api/auth — Login, callback, dev-token, logout, reauth
  • /api/nodes — Heartbeat, and full node CRUD + config/certs/revoke/re-enroll
  • /api/networks — Networks CRUD, group firewall, check-ip, and network users (permissions)
  • /api/certificates — Sign host cert (client key), create host cert (server key), list certs
  • /api/device — Enrollment codes, enroll (public), config and certs (device token)
  • /api/users — User list/detail/update/delete (system admin)
  • /api/node-requests — Create/list/approve/reject node requests
  • /api/access-grants — Create/list/revoke temporary admin access
  • /api/invitations — Create/list/accept/resend/revoke invitations
  • /api/audit — List audit log (system admin)