One hand for the plans that do not need a whole liturgy.
A shared applier for the toolkit. Give it a plan from linux-patch, linux-users or linux-firewall and it normalises the plan into actions, re-validates each one against the live host, and runs only what is still needed.
“A plan is a snapshot. A server is not.” The discipline every tool in the family shares, in one line
Each tool already has its own apply, with its own careful,
tool-specific behaviour — linux-patch's serialized reboots, linux-users' protection
rules, linux-firewall's SSH guard. This is not a replacement for those. It is the lighter,
uniform path for straightforward plans, and the one place where actions from different
tools can be composed into a single run.
action_plan.json · adapted 4 days ago · click to re-validate against live state
Deliberately simple, so that nothing clever can hide in it
An action plan is a list of hosts, and per host a list of actions. Each action carries
an id, a description, an optional validate snippet, and a
run snippet. That is the whole schema.
The validate line is the whole point. Exit 0 means the change is
still needed. Anything else means the world has moved on since the plan was written, and the
action is skipped rather than forced.
Action plans are produced by adapters, never written by hand. An adapter
takes a tool's own plan and emits the normalised form, and it owns the safe construction of
the shell snippets — every value taken from the source plan is shell-quoted on the way
in. Hand-writing a run snippet would put that responsibility on thee,
which is exactly the arrangement this avoids.
Each with a guard chosen for the way that particular thing goes wrong
| Adapter | Reads | Guard, and what it drops |
|---|---|---|
patch--from patch |
linux-patch.update-planOne dnf-update action per host |
Guarded by a live dnf check-update of the same packages, so a host somebody patched by hand since the plan was made is skipped. Honours --security. |
users--from users |
linux-users.account-planOne lock action per stale candidate |
Guarded by “the account still exists and is not currently logged in”, so anyone who came back since discover is skipped. root and UID 0 are dropped defensively. Expiry only with --expire. |
firewall--from firewall |
linux-firewall.rule-planPer-rule actions, then one reload per host |
Each rule guarded by a live firewall-cmd --query — add only if absent, remove only if present. Removal of the ssh service or the control SSH port is dropped defensively. nftables and unmanaged hosts are skipped. |
Three, not ten. linux-audit and linux-drift are read-only and have no plan to apply; linux-harden and linux-diskspace have their own executors with behaviour too specific to normalise — watchdogs, backups, protected-path deny lists. Those stay where they are.
run BehavesOne confirmation, one script, one guard per action
For each host it builds a single script that, per action, runs the
validate guard and then the run command only
if the guard passes. The host is confirmed once — with its actions
listed — before anything executes. Nothing runs without the guard agreeing the change
is still needed.
Afterwards it writes apply_results.json, recording each action's
outcome as one of three words:
| Outcome | What it means |
|---|---|
| applied | The guard agreed, the command ran, and it exited cleanly. |
| skipped | The guard said the change was no longer needed. Not a failure — usually the most reassuring line in the file. |
| failed | The guard agreed and the command did not succeed. Named, with the host, so thou canst go and look. |
This is the one tool in the family that writes no xlsx. It has no third-party
dependencies at all — standard library only, not even
openpyxl. The reporting belongs to the discover tools; this one
only needs to say what it did, to a file another program can read.
Adapt, or adapt and run in one breath
Python 3.9+ and nothing else, plus sshpass only if
thou usest password SSH login. Auth flags match the rest of the family. Passwords travel by
stdin or the SSHPASS environment variable, never argv.
Breaking the fourth wall, briefly
A real tool, written by a real sysadmin who noticed he was writing the same validate-then-act loop for the fourth time and stopped. Agentless, MIT licensed, standard library only.
It is not the toolkit's only executor and was never meant to be. Each tool keeps its own
apply where the behaviour is genuinely specific — a reboot that
must be serialized, a watchdog that must survive a lost session, a deny list that must not be
configurable. This one exists for the plans where none of that is needed, and for the runs
where thou wantest patch, users and firewall actions to go out together.
The adapters shell-quote every value they take from a source plan before it reaches a command, because those plans were built from what hosts reported, and a host can be lying. That part is not a joke.
Built by vK, who has been paged at 03:00 and did not enjoy it. It serves the wider toolkit — three of its books, so far.
Kneel also at the parish and read at the scriptorium. The Cluster abides. The Loop reconciles. This one checks first.
Nothing here works alone, and neither shouldst thou
The fleet is one testament in several books. Each does one job, writes a report thou canst hand to anyone, and refuses to be clever with thy servers.