Metadata-Version: 2.4
Name: nebula-commander
Version: 0.9.0
Summary: Nebula Commander device client (ncclient): enroll with a one-time code, then run as a daemon to pull config and certs and optionally orchestrate Nebula.
Author: NixRTR
License: GPL-3.0-or-later
Project-URL: Homepage, https://github.com/NixRTR/nebula-commander
Project-URL: Documentation, https://github.com/NixRTR/nebula-commander/blob/main/client/README.md
Project-URL: Repository, https://github.com/NixRTR/nebula-commander
Keywords: nebula,vpn,commander,ncclient,dnclient
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: System Administrators
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: System :: Networking
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: requests>=2.28
Requires-Dist: keyring
Requires-Dist: PyYAML>=6.0
Requires-Dist: cryptography>=42
Requires-Dist: jeepney>=0.8; sys_platform == "linux"
Provides-Extra: dev
Requires-Dist: build; extra == "dev"
Requires-Dist: twine; extra == "dev"
Dynamic: license-file

# ncclient – Nebula Commander device client

**License:** GNU GPLv3 or later. See [LICENSE](LICENSE) in this directory.

A small client that works like [Defined.net's dnclient](https://docs.defined.net/glossary/dnclient) and [dnclientd](https://docs.defined.net/glossary/dnclientd): enroll once with a code from Nebula Commander, then run as a daemon to pull config and certificates and optionally **orchestrate the Nebula process** (start/restart it when config changes).

## Install

Most users should install the deb/rpm packages (Linux), the MSI (Windows), or the Docker image instead. For Linux nodes other than lighthouses, the recommended method is the package repository at https://pkgs.nebulacommander.com, the only Linux install that can update itself automatically (`sudo ncclient auto-update enable`); see the [installation docs](https://nebulacommander.com/docs/usage/ncclient/installation/). **Pip is for advanced users only:** it's the most delicate method, and easy to set up so that `sudo` can't find ncclient or the service can't find its token.

From PyPI:

```bash
pip install nebula-commander
```

This installs the `ncclient` command. Requires Python 3.10+.

From source (e.g. repo clone):

```bash
pip install -r client/requirements.txt
# then run as: python -m client --server URL enroll --code XXX
```

Or install the package in development mode from the `client/` directory: `pip install -e .` to get the `ncclient` command.

**Linux:** ncclient runs as root there, so install it where root owns it and `sudo` can find it (not a virtualenv in your home directory, whose code your user could change):

```bash
sudo python3 -m venv /opt/ncclient
sudo /opt/ncclient/bin/pip install nebula-commander
sudo ln -s /opt/ncclient/bin/ncclient /usr/local/bin/ncclient
```

On Debian, Ubuntu, Fedora and openSUSE the deb/rpm packages (pkgs.nebulacommander.com) are easier still; they set up the service for you.

## Linux setup

Nebula needs root to create its TUN interface, so on Linux the client runs as root, and every step is done as root, in this order:

```bash
sudo ncclient enroll --server https://YOUR_NEBULA_COMMANDER_URL --code XXXXXXXX   # 1. enroll as root
sudo ncclient install                                                              # 2. install + start the systemd service
systemctl status ncclient                                                          # 3. check it's running
```

From then on the service *is* the client; don't also run `ncclient run` by hand. Keep using `sudo` for `ncclient routes …` and re-enrolling. Run as a regular user, `ncclient` reads that user's own (empty) token and settings, not the service's. See [Running at startup](#running-at-startup) for what `install` prompts for.

## Enroll (one-time)

1. In Nebula Commander, open **Nodes**, find your node, and click **Enroll**.
2. Copy the enrollment code and run on the device:

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

Run it as the same user that will run the client: root on Linux (see [Linux setup](#linux-setup)).

This saves the device token to your OS credential store (e.g. Windows Credential Manager, macOS Keychain, Linux Secret Service), or to `~/.nebula/device-token` (mode 0600) when there's no usable one, which is the case for root, servers and SSH sessions. It also saves the server URL so you can run `ncclient run` without `--server` next time.

## Several networks

One device can be in several networks, each on its own Nebula Commander server if
needed (for example your own network plus a customer's). Enroll once per network with
that network's code; the running service picks a new network up within a few seconds:

```bash
sudo ncclient enroll --server https://nc.example.com --code XXXXXXXX       # first network
sudo ncclient enroll --server https://nc.customer.example --code YYYYYYYY  # another one
sudo ncclient networks list                                                # all of them, with state
sudo ncclient networks remove customer                                     # leave one (name or id)
```

Each network runs its own Nebula on its own tun device (`nc-…`, set per network in
Nebula Commander) and keeps its own token, config and DNS; the desktop apps show a
**Networks** page with one entry per network. Things to know:

- **One exit node per device.** Accepting an exit node in one network replaces one
  in use in another network.
- **Listen ports.** Two networks can't share a UDP port. If they ask for the same one,
  the second network uses a port the OS picks, unless it's a lighthouse or relay
  (which need a fixed port) - give one of them another listen port in Nebula Commander.
- **Interface names.** Each network needs its own tun device. Servers older than 0.8.0
  call every network's device `nebula1`; when a second such network joins, ncclient
  names its device `nc-<network id>` instead (the first network keeps `nebula1`). The
  name sticks, so it doesn't change again when the other network is removed.
- **Commands for one network** take `--network NAME` when there's more than one, e.g.
  `ncclient routes --network customer list`.
- Each network's files are in `<output dir>/networks/<id>/`. A device enrolled with an
  older version is converted to this layout on the first start of 0.8.0; going back to
  an older version afterwards needs a new enrollment.

**Upgrading from an older version:** If you previously used a token file, you must re-enroll once after upgrading. You can then delete the old token file if it exists (`~/.config/nebula-commander/token` or `/etc/nebula-commander/token`).

## Run (daemon)

Poll for config and certs every 60 seconds, write them to `/etc/nebula` (or another directory), and **run Nebula** (from your PATH) when config changes. If you have already enrolled, you can omit `--server` (the URL is saved at enrollment):

```bash
ncclient run
# or: ncclient run --server https://YOUR_NEBULA_COMMANDER_URL
```

ncclient assumes `nebula` is on your PATH and will start/restart it by default. Options:

- `--output-dir DIR` – where to write `config.yaml`, `ca.crt`, `host.crt` (default: `/etc/nebula` on Linux/macOS, `~/.nebula` on Windows)
- `--interval N` – poll interval in seconds (default: 60)
- **`--nebula PATH`** – path to the `nebula` binary **only if it's not in PATH** (e.g. `--nebula /opt/homebrew/bin/nebula`). Omit this when `nebula` is already on your PATH.
- **`--restart-service NAME`** – instead of running nebula directly, restart this systemd service after config updates (e.g. `nebula`). Use **only one** of `--nebula` or `--restart-service`.
- **`--accept-dns`** – enable split-horizon DNS: fetch DNS config from the server, write `dns-client.json`, and apply it so the Nebula domain is resolved via the network’s DNS (e.g. lighthouse). On Linux the client tries, in order: **systemd-resolved**, **dnsmasq**, **NetworkManager**, **systemd-networkd** (with resolved), then **/etc/resolv.conf**. The resolv.conf fallback is best-effort only (no guaranteed split-horizon). Install systemd-resolved or dnsmasq for proper split-horizon. On Windows it uses NRPT. Run as root (Linux) or Administrator (Windows) to apply.

Example – nebula in a non-standard location:

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

Example – use systemd to run Nebula; ncclient only restarts the service:

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

When the certificate was **created** via the server (Create certificate in the Nebula Commander UI), the bundle includes `host.key` and no manual copy is needed. For certificates created via **Sign** (betterkeys, client-generated key), the server does not have the key; place your own `host.key` in the same directory as the generated certs.

**Linux:** Creating the Nebula TUN device requires root. Run ncclient as root so the Nebula process can create the interface, e.g. `sudo ncclient run --server https://...` (or use `--output-dir ~/.nebula` and run as root so nebula reads from a dir that has host.key). Running it by hand is fine for testing; for normal use install the service (see [Linux setup](#linux-setup)) and don't run both.

## Split-horizon DNS (Linux)

With `--accept-dns`, ncclient applies split-horizon DNS so the Nebula domain (e.g. `*.nebula.example.com`) is resolved by the network’s DNS server while everything else keeps using the host's normal DNS. It detects how the host manages DNS and uses the matching mechanism:

1. **NetworkManager delegating to systemd-resolved**, or **systemd-resolved** on its own (e.g. systemd-networkd servers) – per-link DNS on the Nebula interface via `resolvectl` (`~domain` routing, not a default route).
2. **NetworkManager with its dnsmasq plugin** – a `server=/domain/ip` snippet in `/etc/NetworkManager/dnsmasq.d/`. If NetworkManager writes `/etc/resolv.conf` directly (`dns=default`, e.g. Debian) and `dnsmasq` (Debian: `dnsmasq-base`) is installed, ncclient switches NetworkManager to its dnsmasq plugin with a drop-in in `/etc/NetworkManager/conf.d/`. Without dnsmasq or systemd-resolved, split-horizon can't work there and ncclient says so.
3. **A standalone dnsmasq service** – snippet under `/etc/dnsmasq.d/`.
4. **/etc/resolv.conf** – append nameserver and search domain (last resort; not true split-horizon; the first nameserver often gets all queries).

When NetworkManager is running, ncclient also marks every `nebula*` and `nc-*` interface unmanaged (`/etc/NetworkManager/conf.d/91-nebula-commander-unmanaged.conf`) so NetworkManager never takes over a tun device, and it deletes stale `nebula*` tun profiles older versions left in `/etc/NetworkManager/system-connections/`. Every poll it checks the DNS is still in effect and re-applies it if a resolver restart or a recreated interface undid it. ncclient removes its DNS settings again when it stops or DNS is turned off. The unmanaged drop-in stays until the package is uninstalled, because removing it while another ncclient runs would let NetworkManager take that instance's tun device.

**Several networks on one host:** run one ncclient per network. Each network has its own tun device, and the dnsmasq snippets are per device (`nebula-commander-<tun device>.conf`), so instances don't overwrite each other. NetworkManager's dnsmasq-mode drop-in (`90-nebula-commander-dns.conf`) is removed when the last instance's snippet goes. Two limits apply:

- Every ncclient on the host must be 0.8.0 or newer. Older versions remove the shared NetworkManager drop-ins on start and take the other instances' tunnels down.
- The `/etc/resolv.conf` fallback has a single block, so only one instance can use it.

If nothing works, ncclient reports why and what to install. For proper split-horizon, use a system with systemd-resolved or dnsmasq. Manual apply/remove scripts (for when ncclient itself isn't run with enough privilege to self-apply): `client/contrib/dns-apply-linux.sh` and `client/contrib/dns-apply-windows.ps1`. **Note:** the Linux fallback script only covers the systemd-resolved, dnsmasq, and resolv.conf backends (not NetworkManager) - on those setups, run `ncclient --accept-dns` directly instead.

## Subnet router / exit node

Being picked as a consumer of a subnet route or exit node (the gateway's "Used by"
list, or this node's own "Use Subnet Router"/"Use Exit Node" dropdown in the admin
UI) makes the route *available* to this device - it isn't used automatically. The
same way `--accept-dns` is a separate local opt-in on top of DNS being enabled for
the network, this device has to locally **accept** a route before Nebula actually
uses it. This is cross-platform (Linux and Windows, since both run through the same
`ncclient` core):

```bash
ncclient routes list                              # what's offered vs. accepted
ncclient routes accept 192.168.1.0/24              # add --via IP if offered by multiple gateways
ncclient routes reject 192.168.1.0/24
ncclient routes accept-exit-node --via 10.100.0.30   # --block-lan: LAN through it too
ncclient routes exit-lan allow|block                 # local network direct (default) or via the exit node
ncclient routes reject-exit-node
```

Multiple subnet routes can be accepted at once as long as their CIDRs don't
overlap - `accept` rejects a conflicting one with an explanation of which
already-accepted route it clashes with. At most one exit node is accepted at a
time, across all networks on the device. A change takes effect within one poll
cycle, no restart needed. With several networks, add `--network NAME` (before the
subcommand: `ncclient routes --network office list`).

An accepted exit node carries all of this device's traffic. `ncclient` keeps
Nebula's own UDP out of the tunnel (policy routing on Linux, binding Nebula to the
uplink's address on Windows), blocks IPv6 the gateway can't route instead of letting it leak, and on
Linux drops internet traffic while Nebula is down rather than sending it around the
tunnel. See "Using an exit node" in [../docs/unsafe-routes.md](../docs/unsafe-routes.md).

**On Linux**, when a node is set up as a *gateway* (advertising a subnet or acting
as an exit node itself, not consuming one), `ncclient` also automatically enables
IP forwarding and installs the nftables rules that make forwarding actually work -
no manual host setup needed there either. See
[../docs/unsafe-routes.md](../docs/unsafe-routes.md) for the full picture on both
sides (gateway and consumer), and what to do on Windows/macOS/Docker or a bare
`nebula` install where the gateway-side automation doesn't run.

## Server moves

When the Nebula Commander server a network belongs to moves to another address
(Settings > Move server on the old server - for example leaving a hosted server
for your own domain or IP), `ncclient` 0.9.0+ follows it without re-enrolling:

- At enrollment (or the first check-in after upgrading) each network pins its
  server's key. Only a move statement signed with that key is ever followed.
- When a move is announced, `ncclient networks list` and the desktop apps show
  "Moving to ..." and the device waits: at least until the time the server set,
  and never less than **1 hour** within the same domain or **24 hours** to another
  domain or an IP, whatever the server says - time to notice a move nobody here
  expected.
- Before switching it checks that the new server holds the key whose fingerprint
  the admin pasted, and that it knows this device (same node and network). If
  anything doesn't check out, the device stays where it is and tries again later.
- The previous server is kept in `settings.json` as `previous_server`.

To limit this on a device, set `move_policy` in `settings.json` (it can only be
changed by editing the file; servers and apps can't set it): `"any"` (default),
`"same-domain"` or `"none"`.

A NixOS module or Docker setup that declares `server` keeps the old address in its
configuration after a move; that is harmless (the stored per-network server wins),
but update it when convenient.

## Troubleshooting

- **No network device (tun) created / can't ping Nebula IP**  
  On Linux, Nebula needs root to create the TUN interface. Run **`sudo ncclient run --server ...`**.  
  If the certificate was **created** via the server (Create certificate in the UI), the bundle includes `host.key` and no manual copy is needed. If it was created via **Sign** (betterkeys), put your `host.key` in the output dir (e.g. `/etc/nebula`). Nebula will exit or fail without `host.key`.  
  Nebula's errors are printed to the same terminal; look for messages like "failed to get tun device" (permission) or "no such file" (missing host.key).

- **Nebula starts then exits**  
  Check the Nebula error lines ncclient prints. Common causes: missing `host.key` (for Sign flow; Create flow includes it in the bundle), wrong config path, or (Linux) need to run as root.

## Running at startup

### Quick install (Linux)

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

```bash
sudo ncclient install
```

This is step 2 of [Linux setup](#linux-setup). It checks that root has already enrolled. If not, it prints the exact `ncclient enroll --server URL --code XXXXXXXX` command to run first (get the code from the Nebula Commander UI: Nodes → Enroll). Then it prompts for the server URL and optional settings (output directory, poll interval, nebula path, restart-service), writes `/etc/default/ncclient` and `/etc/systemd/system/ncclient.service`, enables the service, and optionally starts it. Use `--no-start` to enable without starting; use `--non-interactive` with `NEBULA_COMMANDER_SERVER` (and optional env vars) set for scripting.

### Manual setup (all platforms)

Run `ncclient run` under systemd (or your init system) so config and certs stay up to date. ncclient runs `nebula` from your PATH by default; use `--restart-service` if you prefer to have systemd run Nebula and ncclient only restart the service. Example configs are in **`examples/`**; see [examples/README-startup.md](examples/README-startup.md) for step-by-step install on macOS and Windows.

## macOS

ncclient works on macOS (Intel and Apple Silicon). Use Python 3.10+ and install with `pip install nebula-commander`.

- **Token** is stored in the OS credential store (Keychain).
- **Default output dir** is `/etc/nebula` (same as Linux). If you run as a normal user, use `--output-dir ~/.nebula` so you don't need sudo to write config/certs.
- **Nebula**: ncclient runs `nebula` from your PATH by default. After `brew install nebula`, you usually don't need `--nebula`. Use `--nebula /opt/homebrew/bin/nebula` (Apple Silicon) or `--nebula /usr/local/bin/nebula` (Intel) only if it's not on PATH. Do not use `--restart-service`; macOS uses launchd, not systemd.
- To run ncclient in the background, use **launchd** (e.g. a LaunchAgent in `~/Library/LaunchAgents` or a LaunchDaemon in `/Library/LaunchDaemons`).

## Windows 11

ncclient works on Windows 11. Use Python 3.10+ and install with `pip install nebula-commander`.

- **Token** is stored under `%USERPROFILE%\.config\nebula-commander\token`.
- **Default output dir** for config and certs is `%USERPROFILE%\.nebula`. Override with `--output-dir` (e.g. `C:\ProgramData\Nebula` if you run as Administrator).
- **Nebula**: ncclient runs `nebula` from your PATH by default. If `nebula.exe` is not on PATH, use `--nebula "C:\Path\To\nebula.exe"`. Do not use `--restart-service`; there is no systemd on Windows.
- Run ncclient in a terminal or install it as a Windows service (e.g. with NSSM or Task Scheduler) so it keeps running.

### Windows GUI app

**Nebula Commander** (`client/windows-app/`, WinUI 3) ships alongside the CLI,
talking to the `NebulaCommanderService` Windows Service over the shared
`%ProgramData%\nebula-commander\` state. A proper windowed app with side tabs
(Status, Enrollment, Settings), minimizes to the tray on close instead of
exiting. The Status page shows live server/service/interface/DNS state, lets
you start/stop/restart the service, view `config.yaml`, and accept/reject
subnet routes and exit nodes interactively. See
**[client/windows-app/README.md](windows-app/README.md)**.

(The older Python/Tkinter system-tray-only app that used to ship alongside it
has been removed - see **[client/windows/README.md](windows/README.md)** for
the `NebulaCommanderService` it still talks to.)
