Skip to main content

nordility

nordility is a standalone extraction of the nordility.py tooling from citegres, repackaged as a small Python project that is explicitly focused on automating NordVPN actions.

It keeps the original workflow:

  • connect to NordVPN
  • disconnect from NordVPN
  • rotate to a new country/server group

It also adds a usable CLI, packaging metadata, and a small test suite.

Contributor Docs

  • Architecture blueprint: docs/contributor-architecture-blueprint.md
  • Diagram source: docs/diagrams/repo-architecture.puml
  • Draw.io source: docs/diagrams/repo-architecture.drawio
  • Rendered diagram targets:
    • docs/diagrams/repo-architecture.puml.png
    • docs/diagrams/repo-architecture.puml.svg

Features

  • Platform-agnostic VPN automation through two interchangeable backends: the original NordVPN.exe flow on Windows, and the nordvpn terminal CLI on Linux and macOS
  • Backend auto-detection, so the same commands work unchanged on each platform
  • Fast and full country pools for randomized server rotation
  • A NordVPN/WireGuard watch service that keeps private WireGuard access working after external NordVPN reconnects or rotates
  • A local web control surface for private Caddy/mTLS access from Safari
  • No third-party runtime dependencies
  • Compatibility helpers that preserve the original function names

Platform support

nordility began as a Windows-first tool wrapping NordVPN.exe. It is now platform-agnostic: the package installs and the CLI runs on Linux, macOS and Windows, verified on each by CI on every push.

What limits a given command is rarely nordility itself. It drives whichever NordVPN executable is present, so support follows two separate questions: does this package run here, and is there a NordVPN binary for it to drive?

Feature Linux macOS Windows
Package installs, CLI runs, --help works yes yes yes
NordVPN.exe backend — — yes
nordvpn CLI backend yes if present —
web over TCP (status only) yes yes yes
web --unix-socket (privileged actions) yes yes no
login --token via KeePass yes no no
watch-wireguard and its systemd units yes no no

Backend selection is by executable path, not by OS: a path ending in .exe selects the Windows backend, anything else selects the CLI backend. So macOS resolves to the CLI backend and works wherever a nordvpn binary is on PATH — NordVPN ships that CLI for Linux, so on macOS this depends on what you have installed rather than on anything here.

The rest is the host OS having a facility or not. web --unix-socket needs AF_UNIX, which Windows does not provide; run web without it for the status-only TCP interface. watch-wireguard repairs Linux policy-routing rules and installs systemd units, so it is inherently Linux-only. login --token guards on sys.platform for the PTY and core-dump hardening it relies on.

Where a feature is unavailable the CLI reports it and exits, rather than failing obscurely.

Install

For the CLI and Python API:

python -m pip install nordility

On Linux, the optional KeePass-backed login also needs auto-pass, currently installed from its source repository:

python -m pip install 'git+https://github.com/casonk/auto-pass.git'

The NordVPN application or nordvpn command must be installed separately. The package wheel contains the Python CLI and API. The systemd installers and tracked config examples below are repository files; clone the repository to use them.

For development from a checkout:

python -m venv .venv
source .venv/bin/activate    # Windows: .venv\Scripts\activate
pip install -e .

Configure

By default, nordility uses:

  • Windows backend: C:/Program Files/NordVPN/NordVPN.exe
  • CLI backend: nordvpn

You can override the executable with either environment variable. On Linux or macOS:

export NORDILITY_EXECUTABLE="/usr/bin/nordvpn"
export NORDILITY_BACKEND="cli"

On Windows:

$env:NORDILITY_EXECUTABLE = "C:/Program Files/NordVPN/NordVPN.exe"
$env:NORDILITY_BACKEND = "windows"

Neither is usually needed: auto infers the backend from the executable path.

The accepted backends are:

  • auto
  • windows
  • cli

auto infers windows for .exe executables and cli otherwise.

For auto-pass-backed login defaults in a repository checkout, copy config/auto-pass.example.ini to config/auto-pass.ini. The CLI will use that file as the default --keepass-profile and --keepass-entry source for login, connect --auto-login, and change --auto-login. With a wheel installation, pass those options explicitly and configure auto-pass through its documented environment variables. The config file and sibling-checkout discovery are conveniences for the repository deployment, not prerequisites for the core package.

Literal --token input is intentionally unsupported because it exposes the credential in the caller's process arguments. On Linux, KeePass-resolved tokens pass over a private stdin pipe to nordility.token_login; that helper validates the root-owned official CLI, disables core dumps/process dumpability, and launches nordvpn login --token in a PTY with no positional credential. It verifies consent is disabled, waits for NordVPN 5.2's exact prompt and disabled ECHO/ECHONL, and only then reads, forwards, and wipes the token while discarding all child output. Prompt drift fails closed; there is no credential-bearing fallback. The token is also redacted from caller errors and results.

Usage

nordility connect
nordility login
nordility disconnect
nordility change --speed fast
nordility change --group United_States
nordility watch-wireguard --once
nordility web --host 127.0.0.1 --port 5300  # status only; actions disabled
nordility list-groups --speed full

If you do not install the package, you can still run it from the repo root:

PYTHONPATH=src python -m nordility change --speed fast

Python API

from nordility import change_vpn_server, connect_vpn_server, disconnect_vpn_server

print(connect_vpn_server())
print(change_vpn_server(speed="fast"))
print(disconnect_vpn_server())

For more control, use NordVPNClient directly.

NordVPN/WireGuard Watch Service

NordVPN reconnects and server rotations can flush Linux policy-routing rules. If this host is also serving a private WireGuard tunnel, that can route phone handshake replies through nordlynx instead of the real gateway.

Run one repair pass:

PYTHONPATH=src python -m nordility --backend cli watch-wireguard --once

Install the resident systemd watcher:

sudo ./scripts/install_wireguard_watch_service.sh
sudo systemctl status nordility-wireguard-watch.service --no-pager

The watcher detects NordVPN status/NordLynx endpoint changes and routing drift, starts wg0 via wg-quick@wg0.service if /etc/wireguard/wg0.conf exists but the interface is down, then derives a config-owned interface allowlist before it reads or resets peer endpoints. It reapplies the user-managed WireGuard socket fwmark plus:

ip rule add fwmark 51820 lookup main priority 100 protocol 196

It only changes WireGuard interfaces backed by /etc/wireguard/<iface>.conf, and it rejects the daemon-managed nordlynx name even if a confusingly named config file exists. A repair is reported as successful only when the interface fwmark mutation succeeds and the exact priority-100, fwmark-51820, lookup-main, protocol-196 rule is already present or is added successfully. The numeric protocol tag is Nordility's ownership marker, so rollback deletes only the rule Nordility created. Rules at another priority, for another mark, table, or protocol, or with a partial mark mask do not satisfy that check. Before installing the global rule, Nordility inventories every active WireGuard fwmark and refuses a collision on any interface outside the explicit mutation allowlist. A newly added rule is rolled back if no target interface can be marked.

Interface startup is systemd-only. Nordility does not fall back to raw wg-quick up, because doing so would bypass security drop-ins attached to the wg-quick@<interface>.service activation boundary.

Private Web Control

The privileged web control surface is served only over a root-created Unix socket intended for wiring-harness Caddy/mTLS:

sudo ./scripts/install_web_service.sh
sudo systemctl status nordility-web.service --no-pager

Default backend:

  • Local upstream: unix//run/nordility/web.sock (root:caddy, mode 0660)
  • Private Caddy URL: https://nordility.clockwork.internal

TCP mode is deliberately status-only: POST /api/action returns 403 even on loopback. Unix-socket actions additionally require the exact configured HTTPS Origin, matching Host, and application/json; this prevents loopback bypass and browser CSRF from replacing Caddy/mTLS as the authorization boundary.

The page exposes power on/off, fast/full rotation, and built-in country selection. Installed root services deliberately do not accept --auto-login or KeePass options: authenticate the official NordVPN client separately before starting them. After each VPN action the web service runs the same WireGuard repair path used by the watcher so wg0 remains available for phone access.

All three systemd installers stage an explicit Python-module allowlist at /opt/nordility with root ownership and non-writable modes. Root services never execute Python from the user-writable Git checkout.

After adding nordility.clockwork.internal to the local wiring-harness service registry, refresh the shared certificate SANs and Caddy config:

[[services]]
name        = "nordility"
description = "NordVPN outbound control surface"
owner_repo  = "./util-repos/nordility"
hostname    = "nordility.clockwork.internal"
access_mode = "shared-mtls"
ingress     = "wiring-harness-caddy"
unix_socket = "/run/nordility/web.sock"
cd ../wiring-harness
WH_WG_IP=10.99.0.1 bash scripts/setup-mtls.sh --refresh-server
sudo python3 scripts/setup_caddy.py --provision

Tests

PYTHONDONTWRITEBYTECODE=1 PYTHONPATH=src python3 -m unittest discover -s tests -v
bash tests/test_token_login_podman.sh

The container test runs the Linux-only helper against a deliberately delayed no-echo prompt and checks that accepted and rejected credentials do not escape through process arguments, the environment, or helper output.

Metadata

Release files for nordility 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for nordility 0.1.0
File Size Uploaded
nordility-0.1.0.tar.gz 51.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for nordility 0.1.0
File Interpreter ABI Platform
nordility-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 85.2 kB

Release files / nordility-0.1.0.tar.gz

Download URL nordility-0.1.0.tar.gz
Size 51.4 kB
Tags Source
SHA-256 checksum
How to use checksums
6f9f843a15ebb26ba1642ef46a092b57884bdccfc6ac3749246ef4ff107b3756
BLAKE2b-256 checksum
How to use checksums
664449079b62cd4ede510be8dbf718b6edbbbcfcd309448ab5a31fb2620f90a7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.

Transparency log

Release files / nordility-0.1.0-py3-none-any.whl

Download URL nordility-0.1.0-py3-none-any.whl
Size 33.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ba0d48b0b7f3ce2fc5bd3db398a280afdde9eb6618df539d985521f81be754cc
BLAKE2b-256 checksum
How to use checksums
5367acd4863c5a0729eec3f0faf1412bb3685909a1c41d29372e9a5cb9cdfc25
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page