Enroll 0.9.0: safer captures and better Ansible projects
Better protection around captured data, fixes for service and package handling, and shared Ansible roles that preserve each server's configuration.
Enroll captures the state of an existing Linux server and turns it into Ansible you can review and maintain. I wrote about its origins earlier: it grew out of wanting to bring existing, occasionally rather individual servers under configuration management.
Version 0.9.0 brings a fairly substantial collection of improvements: more careful handling of captured data, better package and service attribution, fixes to generated Ansible, and a reworked multi-host workflow. Personally, I'm finding the breadth of capture, consistency and ordering better in terms of Ansible using the new version, and I hope you will too.
Two servers, one project
Let's talk about one of the major refactors: that being the 'merging' of multiple host harvests into the one 'manifested' Ansible set of roles.
For a hypothetical example, imagine web01.example.net and web02.example.net. Both run web services, with some shared configuration and some differences. We want those differences represented clearly when we bring them into the same project.
This release makes that workflow much more useful. You can start a project with one harvest, then extend it with another:
enroll manifest --harvest web01.harvest \
--host web01.example.net --out ansible
enroll manifest --harvest web02.harvest \
--host web02.example.net --out ansible --extend
This logic with --host and --extend replaces the old --fqdn argument, and does it more safely, without sacrifincg on reusability.
The project retains the information needed to add further hosts, so you can extend it later without keeping every earlier harvest to hand. Each host keeps its own settings, capture notes and play order.
Sharing the things that match
The interesting part is what happens when two servers have the same configuration file.
If their captured copies are identical, Enroll keeps one shared copy in the role. If a later host has different contents for that same file, Enroll moves the affected hosts' copies into inventory/host_files/, preserving each server's version. Host settings live in inventory/host_vars/, and they tell the shared tasks what to deploy.
A third server follows the same rule. Its own captured file becomes its host-specific copy once that path has been split. Adding a host should always preserve what was harvested from that host.
This keeps common configuration convenient to maintain while giving differences an obvious home. Roles with matching task and handler implementations can be shared. Where the actual implementations differ (that is, at the 'task' level, not at the file content level), Enroll retains separate roles with readable hostnames in their names.
One role can manage different services
Suppose web01 runs both Apache and Nginx, while web02 runs Nginx alone. Both can have an httpd role. The group name comes from package classification, so it can cover several related services.
Generated grouped roles now use a common restart handler which loops over that host's list of active services. The same role can therefore handle those two arrangements. This applies across the grouped roles, including mail services and other package groups.
There is an operational detail worth knowing: when that grouped handler is notified, it restarts all the active services listed for that role on that host. Review those lists alongside the configuration before applying a manifest.
Getting the order right
A generated playbook needs packages and accounts in place before it can deploy files owned by those accounts. Services need their configuration ready before activation.
Version 0.9.0 makes those stages explicit: repository configuration first, package and account prerequisites next, then configuration, followed by service activation. APT and DNF configuration run once in their early stage. The configuration pass also avoids repeating the package and account work already completed.
Generated roles still have a complete default entry point for use independently in your own playbooks. Inside Enroll's generated plays, they participate in the ordered stages.
There are correctness fixes around this too. Handler notifications are scoped to the relevant role or unit, and loop results are filtered so notifications reflect changed items. Changes to managed symlinks and systemd units or drop-ins now trigger the appropriate activation work. Prerequisite and activation imports also follow the role selected for each host in a multi-host project. Group IDs are preserved, conflicting identity mappings are refused, and supplementary group membership remains additive.
Finding the package behind a service
A service's configuration can belong to a related package whose name differs from the unit. Enroll now uses installed direct dependencies and shared source-package identity to help associate those pieces on Debian/Ubuntu and RPM/DNF/Yum systems.
That improves how related configuration ends up alongside its service. Attribution evidence is recorded, and ambiguous cases receive deterministic handling. Independently captured packages and services also have separate artifact namespaces, avoiding collisions when similarly named resources cannot be confidently associated.
Locally supplied systemd unit fragments are captured too, including those outside package ownership, and Enroll reports executable prerequisites. That helps make the requirements of a custom service visible when reviewing the generated project.
Smaller fixes that matter during replay
Drift comparison has expanded: DNF and firewall artifacts, directories, symlinks, container images and application inventories receive broader coverage. Package observation includes automatically installed dependencies.
There are several other practical fixes:
- Destination exclusions now apply to generated runtime configuration as well, so an excluded path stays excluded when Enroll produces files from captured runtime state.
- Snap replay fails explicitly when requested attributes cannot be honoured. That makes an incomplete restoration visible to the operator.
- Incompatible retained templates are refused, preventing an old template from being quietly carried into output it no longer fits.
- Standalone output always includes its Ansible configuration and collection requirements. Modern and legacy package metadata are also kept in sync.
These are easy details to overlook until a generated project encounters them. Getting them right makes the output easier to review and the failures easier to understand.
More careful capture and project updates
Security work has continued per the last couple releases, and follows Enroll's documented threat model : protect administrators against accidental exposure and unsafe filesystem handling while keeping the trust decisions visible.
Secret screening now covers Flatpak remote metadata and opted-in runtime output, extending the checks around information that can end up in a harvest. Safe-mode content checks remain conservative; unusual configuration may still need an explicit decision about sensitive capture.
There is also a fix for FIFO handling. A FIFO is a named pipe, and opening one as though it were an ordinary file can leave a process waiting for another process to write to it. Enroll now prevents that blocking read before validation, so an unsuitable filesystem entry can be rejected promptly.
Generated role names receive collision checks, and enroll_runtime is reserved for Enroll's own role. Those checks protect the association between a role and the tasks it is intended to run. New manifest publication is staged as well.
Extending a project is staged and locked. Enroll checks for concurrent changes and publishes the completed extension using an atomic Linux directory exchange. Unsafe filesystem entries and unsupported atomic publication cause the operation to stop without changing the existing project. It also refuses duplicate hosts, conflicting collection requirements and edits to roles needed for sharing, while preserving edited host variables.
Harvests still deserve the same care as other sensitive administrative material. Structural validation checks the bundle and its artifacts; applying its desired state requires trusting its provenance. Safe-mode secret detection has limits, and --dangerous deliberately broadens what can be captured. SOPS encryption remains available for sensitive harvests.
Changes to know before upgrading
Two changes deserve attention if you already are used to Enroll:
manifest --fqdnis replaced by--hostand--extend. Start a fresh project with this version's metadata before extending it with additional hosts. Standalone generation continues to require a new output directory. The host options also work withsingle-shot; encrypted projects need to be unpacked before extension.- Live firewall and sysctl capture are now opt-in, using
--harvest-firewalland--harvest-sysctlon local or remoteharvestandsingle-shot. Persistent configuration is still harvested normally, and existing bundles retain their recorded runtime snapshots. The reason I did this is because maybe you already have sysctl parameters or iptables rules captured in other scripts that Enroll can't deduce easily: it's not great to add new config files/scripts if you've already got them, so opt-in is a better model.
For captured firewall state, optional boot restoration restores ipsets before iptables rules and includes reconciliation during a run. Persistence defaults to off through firewall_runtime_persist: false, giving you an explicit choice about how it fits with any existing firewall persistence service.
My recommended starting point for a generated project is still a review followed by a check-mode run:
cd ansible
ansible-playbook -i inventory/hosts.yml playbook.yml --check --diff
That preview helps inspect the proposed changes. Service lifecycle and firewall behaviour also deserve testing on a suitable machine before relying on them in production.
As usual, the Enroll website has the usage documentation. The Forgejo repository has the source, changelog and (now, since I opened up Github auth for others) an issue tracker.
Phew! That's a lot of detail, and probably some of it is too low-level. Suffice it to say, the aim throughout this release has been to make captured state more dependable to work with, from the initial harvest through to the Ansible you keep maintaining.
Let me know if you try it out!
- Release: Enroll 0.9.0
- Multi-host projects:
--hostand--extend - Shared files stay in roles; differing copies move into host inventory
- Common grouped handlers use each host's active service list
- Repository, package, account, configuration and activation stages
- Secret screening for Flatpak metadata and opted-in runtime output
- FIFO handling fixes, collision checks and staged project publication
- Broader drift comparison and service/package attribution
- Live firewall and sysctl capture now require explicit flags