- Shell 63.1%
- Python 12.6%
- PowerShell 12.5%
- Jinja 7%
- Makefile 1.9%
- Other 2.9%
deb822_repository needs the Python debian library on the target, and nothing installed it: install mode failed at the repository task on a host without python3-debian, under --check and in a real run. Install it with apt first; under --check, where it cannot be installed yet, skip the repository with a notice so the rest of the run previews. |
||
|---|---|---|
| .agents | ||
| .claude | ||
| .codex | ||
| .config/molecule | ||
| .opencode | ||
| docs | ||
| group_vars | ||
| plugins/cache | ||
| roles | ||
| scripts | ||
| tests | ||
| .ansible-lint | ||
| .gitignore | ||
| .prettierrc | ||
| .yamllint | ||
| AGENTS.md | ||
| ansible.cfg | ||
| CONTRIBUTING.md | ||
| COPYING | ||
| fortify.yml | ||
| Makefile | ||
| os-update.yml | ||
| reachable.yml | ||
| README.md | ||
| requirements-dev.txt | ||
| requirements.yml | ||
| ssh-copy-id.yml | ||
castrum
Hardened baseline for mixed client fleets: one tested role library, per-tenant inventories, applied in rolling batches.
A Roman castrum was built to the same plan wherever the legion stopped — same layout, same gates, same defences, on any ground. This repository is that plan for a fleet of machines: the same hardening, firewall and service configuration applied identically across every site it manages, varied only where the ground demands it.
Each site's inventory lives outside this repository. What is here is what a group means, the roles that implement it, and the tests that prove they do.
Goal
castrum gives every host in a fleet the same hardening, firewall, services,
sysadmin tooling and updates, varied only by the groups a host belongs to
and its own host variables, never by editing a playbook. fortify.yml and
os-update.yml apply it in widening batches, one host first, so a bad
change breaks one machine while the rest are still untouched.
Scope is GNU/Linux, Debian family, plus Windows 11 workgroup PCs over
OpenSSH: their hostname, a hardening baseline, the Windows firewall,
Windows Update and, in scan_shares, a scanner drop share. A Windows
Server gets the Windows firewall, key-only SSH and Windows Update. castrum is not a provisioning tool — it configures machines
that already exist — and not a multi-OS abstraction.
Quick start
-
Clone the repository and build the toolchain (needs Python 3.12+):
git clone https://git.unitoo.it/claudiomaradonna/castrum.git && cd castrum make setup && . .venv/bin/activate -
Create
inventory/<tenant>/hosts.ymlwith one host inmanaged, the group every playbook targets, and give the host its address ininventory/<tenant>/host_vars/web1.yml:managed: hosts: web1: # webservers: # a host joins as many groups as apply # hosts: # web1:ansible_host: 192.0.2.10 -
Install your key once, with password authentication this one time (needs
sshpasson your machine and a host that accepts a root password login once):ansible-playbook ssh-copy-id.yml -i inventory/<tenant>/ -k \ -e ssh_user=root -e "ssh_copy_id='$(cat ~/.ssh/id_ed25519.pub)'"castrum connects as
root(ansible_useringroup_vars/all.yml); a tenant that connects as someone else setsansible_userin its hosts'host_vars(Per-host settings).A Windows PC also joins
windows—managedalone sends it the Linux play — and runsscripts/bootstrap-windows-ssh.ps1once, at the PC, instead of this step; setansible_userin itshost_varsto the account that script creates (Windows PCs). -
Dry run:
ansible-playbook fortify.yml -i inventory/<tenant>/ --check --diff -
Read the diff, then run the same command without
--check.
Pass the tenant directory, never inventory/ itself, which would merge
every tenant into one run
(Tenants and inventories).
Common usage
fortify.ymlapplies the baseline: hardening, firewall, the service roles a host's groups switch on, and sysadmin tooling.os-update.ymlupdates packages, or runs Windows Update;-e os_update_auto=rebootorshutdownacts when an update wants a reboot.ssh-copy-id.ymlinstalls a public key for a user. Linux only.
ansible-playbook fortify.yml -i inventory/<tenant>/ # the whole tenant
ansible-playbook fortify.yml -i inventory/<tenant>/ -e target=web1 # one host
ansible-playbook fortify.yml -i inventory/<tenant>/ --tags firewall # one concern
ansible-playbook fortify.yml -i inventory/<tenant>/ --tags zram # one role
ansible-playbook os-update.yml -i inventory/<tenant>/ -e os_update_auto=reboot
Two modes are passed with -e, for one run only
(Modes):
update_only=trueskips the install-and-configure work of the roles that implement it.uninstall=truedeletes the castrum firewall table until the next ordinary run.
Documentation
- docs/ADVANCED_USAGE.md — running it in full: groups, tags, modes, Windows, first runs, updating.
- docs/ARCHITECTURE.md — how it is built, and the list of roles.
- CONTRIBUTING.md — changing it, and the gates.
- Each role's
README.md, underroles/— what that role does. - AGENTS.md — instructions for coding agents.
License
GNU General Public License, version 3 or later (GPL-3.0-or-later); see COPYING.
Agent policy seeded from Agent Foundry by Claudio Maradonna.