What the target needs¶
Ansible's strongest card is that a managed host needs nothing but sshd. Punix's position is a gradient, and the shallow end of it matches Ansible exactly: rollback Ansible does not have.
The pessimistic version ("you need Punix on every box") is what most people assume, and it is wrong for the case most people start with.
The gradient¶
| what you deploy | what the target needs | what you get |
|---|---|---|
config only: config files, dataFiles, service units, secrets |
sshd + rsync. Nothing else. No Punix, no Python, no agent, no daemon. |
generations, one-second byte-exact rollback, provenance, drift detection |
| + prebuilt artifacts, same platform as your control node | the above; the closure arrives by rsync |
reproducible, content-addressed binaries |
| + a from-source build, target platform ≠ control node | Punix installed on the target | the full build pillar on a foreign platform |
Only the third row needs anything installed, and it needs it for a reason that is a theorem rather than an oversight: realise happens on the control node, and a macOS binary does not run on Linux. There is no cross-realise, so a target of a different platform has to build for itself.
The row worth knowing about¶
A config-only stack deploys to a stock Debian box with nothing on it.
module MailConfig {
backend = "systemd"
configFiles = [
{ path = "/etc/postfix/main.cf" source = "./postfix/main.cf" },
{ path = "/etc/dovecot/dovecot.conf" source = "./dovecot/dovecot.conf" }
]
services = ["PostfixSvc", "DovecotSvc"]
}
That host has never heard of Punix. It gets:
- a numbered generation recording every file's
sha256, the source hash, and which recipe produced what; punix service rollback MailConfig: one syscall, byte-exact, and it works even if you delete the PCL;punix verify MailConfig: "is this host still what I declared?", answered without an agent;punix service why MailConfig /etc/postfix/main.cf: which recipe wrote this file, and a hash of that recipe's code.
The daemons themselves come from the distro's packages. Punix is managing their configuration, not replacing them. This is the shape the four-daemon mail stack ran in.
Versus Ansible, precisely¶
| Ansible | Punix, config-only | |
|---|---|---|
| target prerequisites | sshd (+ Python for most modules) |
sshd + rsync |
| declaration recorded on the host | ❌ none | ✅ gen-NNN.json |
| undo | re-run an older playbook and hope | ✅ one syscall, byte-exact |
| drift detection | ❌ (no record to diff against) | ✅ punix verify |
| provenance per file | ❌ | ✅ producing recipe + its code hash |
| orchestrating change on existing hosts | ✅ its actual job | ➖ not what this is |
The last row marks the boundary. Ansible orchestrates change; Punix replaces a declared state atomically. If you need rolling restarts across a mixed inventory with per-host conditionals, use Ansible. If you need "this host is exactly this, and I can put it back", this is the trade.
Installing on the target, when you do need to¶
Since 0.3.0 the wheel depends on cyclopts and tabulate and nothing else: no compiler, no git, no evaluator you never invoke:
Earlier versions pulled MIXINv2 from a pinned git source, which made git a real prerequisite. That is gone; see architecture.
Related¶
- Deploy over SSH: the operator recipe.
- Why Punix: the full comparison, including Nix, Guix and SlapOS.
- Generations and rollback: what the target records.
- ADR-032 D7 (the gradient) and D8 (drift detection).