Introduction
NXD is a declarative infrastructure reconciliation engine for Nix-authored desired state. It evaluates consumer Nix modules into canonical JSON, plans structural and host changes as a dependency-ordered graph, applies persisted plans under digest approval, and verifies the result through typed Rust providers.
This manual documents behavior that exists in the tree. NXD is pre-release: where a capability is planned rather than shipped, the page says so.
Where to start
| If you want to | Read |
|---|---|
| Run NXD against disposable fixtures first | Quick start |
| Check a configuration without touching infrastructure | Validate configuration |
| Understand why the engine is shaped this way | Architecture overview |
| Know what NXD refuses to do, and why | Safety model |
| Look up a command, flag, or resource field | CLI reference, Resource schema |
| Diagnose a failure | Common failures |
The pipeline
Every NXD invocation moves through the same stages. The boundaries between them are the load-bearing part of the design: each stage may only consume what the previous one produced.
Nix flake modules
│
▼ evalConfiguration — offline, no network, no decryption
Canonical JSON specification
│
▼ nxd plan — dependency-ordered action graph + risk classification
Persisted plan (sha256:…)
│
▼ approval of that exact digest
Provider mutation
│
▼ nxd verify — live state re-read
Post-apply verification and idempotency check
Key concepts
Nix-authored canonical configuration
Site topology, host inventory, hypervisor settings, and service attachments are
authored in Nix modules under nxd.site, nxd.providers, nxd.secrets, and
nxd.operations. nxd.lib.evalConfiguration exports that desired state as
canonical JSON.
Evaluation is strictly offline. It contacts no endpoint and decrypts no secret, so planning cannot be influenced by the state of the infrastructure being planned against.
Canonical JSON specification
One reproducible document describing the full desired state:
deploymentTarget— host declarations, build settings, SSH identities, and plane attachments.providerInstances— provider configuration (Nix, PVE, PBS, VMware, Headscale, Identity, DigitalOcean, WSL).secretBindings— references to SOPS/age documents. References only; never values.resources— provider-owned managed resources such aspveVm,pbsDatastore, andheadscaleNode.
Dependency-ordered action graph
The scheduler computes an acyclic graph ordering actions across every provider, rather than running per-provider steps in sequence.
Prerequisites are edges, not conventions. Minting a Headscale preauth key or resolving an SSH host key is ordered before the target activation that consumes it, because the dependency is recorded in the graph.
Every action carries a risk classification, ordered ReadOnly <
Reversible < ServiceImpacting < Destructive < IdentityCritical. The
classification is visible at approval time, so the class of change is known
before it is authorized, and evidence records the highest risk in the plan. See
the safety model for what each level means.
Digest-bound approval
A plan is persisted to disk and hashed. Applying requires approval of that exact digest. If configuration changes between planning and applying, the digest no longer matches and the apply refuses rather than executing a plan nobody reviewed.
See the safety model for the full refusal set.
Secret mediation
Secrets stay out of Nix store paths and out of canonical JSON. Configuration
carries binding references such as
secret/bar/hosts/medo/ssh-host-ed25519-private-key, and nothing else.
At apply time, SOPS/age resolves those references directly into process memory,
or into staging files with 0600 permissions when a subprocess requires a
path. The Nix store is world-readable, which is precisely why nothing sensitive
is placed in it.
Idempotent verification
Success is a re-read of live state, not a zero exit code. Every lifecycle phase
verifies the target against the desired closure, and a converged system
replans to zero actions with desiredSystemPath == activeSystemPath and
systemPathMatches: true.