Skip to content

Nerthus.Config

Configuration management for the estate: what should exist, what does exist, what differs. One package, nerthus-config, with a Python shell and a Rust core, downstream of Nerthus.Platform.

Why it exists

So that what the estate should be is declared in git, what it is gets observed and recorded, and every difference reaches a person. It is a plan engine with a state file, providers that can list what exists, and hosts that report facts to a site that can tell a person.

What it owns

  • Declarations against state against live. A plan on every merge request, a drift run every hour, a hardening grade every night. Drift is a finding and never a fix.
  • Resources above the VM, by API. Cloudflare, GitLab and R2, each behind one provider trait with a list(), so what nobody declared is a plan output rather than an absence, and a 403 on a listing is unknown rather than "no such resource". It never creates a VM: that is a runbook step.
  • Hosts, by pull. A static Rust agent, nerthus-config-agent, fetches the declaration repository, applies its own role's cookbook, decrypts its own secrets and reports facts. A role is a cookbook; a cookbook is an ordered list of typed steps, each with a check that reads and an apply that writes, with one arm per OS family where the families differ.
  • Four OS families from the first cookbook: Debian, Ubuntu, Alpine, AlmaLinux.
  • Hardening against CIS, with two instruments — its own checks as steps, and a scanner — where a disagreement between them is a finding rather than a tie broken silently.

What it does not own

Credentials, instances, events, jobs and orchestration stay Nerthus.Platform's: a domain rule exists once. Config registers minter plugins with Platform's secrets store rather than holding its own. It has no HTTP surface and no dashboard of its own; drift and hardening reach a person through Platform's alert path.

Where its declarations live

Not here. Nerthus.Infra holds the inventory, the per-site values, the package pins per family, the hardening catalogue, the vault ciphertext, and state/. It holds zero code, and state/ is written only by apply, import and drift, on branch namespaces of their own; a hand branch that touches it is refused by CI. A hand edit to state is a claim about the estate that no read supports.

Where its CI runs

On the estate's runners, through $NERTHUS_RUNNER_TAG, on Nerthus.Platform's CI image by digest — building a second toolchain would be a second answer to which compiler measured a commit. Its jobs: lint in both languages, pytest and cargo test with coverage floors, the contract diff, the regime digest, the programme's Task: trailer and merge-request-title gates, the pin scanner, the protected-ref lint, and two cross-compiled wheels. One job publishes, on a protected tag alone.

What it pins, and what pins it

It pins Nerthus.Platform's distribution to a minor line, and nerthus-config doctor refuses a nerthus outside it: a client that does not match its site's minor is refused entirely rather than run against. It pins the interpreter to a patch release, the Rust channel to a version, and every image it names by digest.

Nothing pins Nerthus.Config yet. When hosts run it, what a host pins is the agent digest and the Config version, and the agent refuses to move backwards on either.

What breaks if it stops

Nothing converges and nothing is graded. A host keeps running what it was last converged to — git is the backup of host configuration, so everything except identities and WireGuard keys is rebuildable from Nerthus.Infra — and it stops being told when it has drifted, which is the failure the previous tooling had for its whole life.

Its reference

docs/resources.md in the repository lists every resource kind, generated from its contracts.

See also