diff --git a/src/content/_index.html b/src/content/_index.html
index 253e406..dcf6cd7 100644
--- a/src/content/_index.html
+++ b/src/content/_index.html
@@ -1,22 +1,23 @@
---
title: "Enroll"
-html_title: "Enroll - Reverse-engineering servers into Ansible"
-description: "Enroll inspects Debian-like and RedHat-like Linux hosts and generates Ansible roles/playbooks from what it finds. Harvest → Manifest → Manage."
-og_title: "Enroll - Reverse-engineering servers into Ansible"
-og_description: "Harvest a host's real configuration and turn it into Ansible roles/playbooks. Safe-by-default, with optional SOPS encryption."
+html_title: "Enroll - Reverse-engineering servers into configuration management"
+description: "Enroll inspects Debian-like and RedHat-like Linux hosts and generates Ansible, Puppet, or Salt manifests from what it finds. Harvest → Manifest → Manage."
+og_title: "Enroll - Reverse-engineering servers into configuration management"
+og_description: "Harvest a host's real configuration and turn it into Ansible, Puppet, or Salt code. Safe-by-default, with optional SOPS encryption."
og_type: "website"
---
-
Reverse-engineering servers into Ansible
-
Get an existing Linux host into Ansible in seconds.
-
Enroll inspects a Debian-like or RedHat-like system, harvests the state that matters, and generates Ansible roles/playbooks so you can bring snowflakes under management fast.
+
Reverse-engineering servers into Ansible, Puppet, or Salt
+
Get an existing Linux host into configuration management in seconds.
+
Enroll inspects a Debian-like or RedHat-like system, harvests the state that matters, and generates Ansible roles, Puppet modules, or Salt states so you can bring snowflakes under management fast.
Tip: for multiple hosts, use --fqdn to generate inventory-driven, data-driven roles.
+
Tip: for multiple hosts, use --fqdn to generate target-native host data: Ansible inventory, Puppet Hiera, or Salt pillar.
@@ -71,7 +66,7 @@ og_type: "website"
Manifest
-
Render Ansible roles & playbooks from the harvest.
+
Render Ansible roles/playbooks, Puppet modules/Hiera data, or Salt states/pillar from the harvest.
@@ -107,11 +102,27 @@ og_type: "website"
+
+
+
+
Ansible, Puppet, or Salt
+
The harvest bundle is renderer-neutral. Choose --target ansible, --target puppet, or --target salt at manifest time.
+
+
+
+
+
+
+
Fewer roles/modules where possible
+
By default, package/service snapshots are grouped by package Section or equivalent metadata to reduce role/module sprawl. Use --no-common-roles, or --fqdn, to keep output more host-specific.
+
+
+
Multi-site without "shared role broke host2"
-
In --fqdn mode, roles are data-driven and host inventory decides what gets managed per host.
+
In --fqdn mode, host-specific data moves into the target's native host-data layer: Ansible inventory, Puppet Hiera, or Salt pillar.
@@ -119,7 +130,7 @@ og_type: "website"
Remote over SSH
-
Harvest a remote host from your workstation, then manifest Ansible output locally.
+
Harvest a remote host from your workstation, then render Ansible, Puppet, or Salt output locally.
@@ -171,19 +182,28 @@ og_type: "website"
-
# Harvest → Manifest in one go
-enroll single-shot --harvest ./harvest --out ./ansible
+
# Harvest once
+enroll harvest --out ./harvest
-# Then run Ansible locally
-ansible-playbook -i "localhost," -c local ./ansible/playbook.yml
Disaster recovery snapshots, "make this one host reproducible", and carving a golden role set you'll refine over time.
+
Disaster recovery snapshots, "make this one host reproducible", and comparing how the same harvest looks in Ansible, Puppet, or Salt before you commit to a workflow.
-
Want templates for structured configs? Install JinjaTurtle and use --jinjaturtle (or let it auto-detect).
+
Using Ansible and want templates for structured configs? Install JinjaTurtle and use --jinjaturtle (or let it auto-detect).
# Validate a harvest is correct.
enroll validate /path/to/harvest
# Check against the latest published version of the state schema specification
enroll validate /path/to/harvest --schema https://enroll.sh/schema/state.schema.json
-
'validate' makes sure the harvest's state confirms to Enroll's state schema, doesn't contain orphaned artifacts and isn't missing any artifacts needed by the state. By default, it checks against the schema packaged with Enroll, but you can also check against the latest version on this site.
+
'validate' makes sure the harvest's state conforms to Enroll's state schema, doesn't contain orphaned artifacts and isn't missing any artifacts needed by the state. By default, it checks against the schema packaged with Enroll, but you can also check against the latest version on this site.
The same harvest bundle can now render Ansible, Puppet, or Salt output, with target-native multi-host layouts and package-section grouping where possible.
The harvest bundle is the contract. Manifesting is a separate step, so the same harvest can be rendered into Ansible, Puppet, or Salt without re-probing the host.
+
+
+
+
+
Ansible
+
Generates playbooks, roles, defaults, files/templates, inventory, and host vars.
When Enroll can see package metadata, it tries to combine related package/service snapshots by the package Section category, or equivalent backend metadata. On Debian-like systems this can turn many tiny package roles into broader groups such as admin, web, net, libs, or utils. On RPM-like systems it uses comparable package group metadata where available.
+
This grouping is enabled by default in single-site output. It is disabled by --no-common-roles when you want one role/module/state per discovered package/service, and it is also disabled by --fqdn because host-specific output should stay isolated in inventory/Hiera/pillar.
Detects the OS and its package backend (e.g dpkg vs rpm)
Detects what packages are installed
For each package, it tries to detect files in /etc that have been modified from the default that get shipped with the package.
-
It detects running/enabled services and timers via systemd. For each of these, it looks for the unit files, any 'drop-in' files, environment variable files, etc, as well as what executable it executes, and tries to map those systemd services to the packages it's already learned about earlier (that way, those 'packages' or future Ansible roles, can also be associated with 'handlers' in Ansible, to handle restart of the services if/when the configs change)
+
It detects running/enabled services and timers via systemd. For each of these, it looks for the unit files, any 'drop-in' files, environment variable files, etc, as well as what executable it executes, and tries to map those systemd services to the packages it's already learned about earlier (that way, those 'packages' or future manifest output can also be associated with service handling in the selected target, to handle restart of the services if/when the configs change)
Aside from known packages already learned, it optimistically tries to capture extra system configuration in /etc that is common for config management. This is stuff like the apt or dnf configuration, crons, logrotate configs, networking settings, hosts files, etc.
-
For applications that commonly make use of symlinks (think Apache2 or Nginx's sites-enabled or mods-enabled), it notes what symlinks exist so that it can capture those in Ansible
+
For applications that commonly make use of symlinks (think Apache2 or Nginx's sites-enabled or mods-enabled), it notes what symlinks exist so that it can capture those in the generated manifest
It also looks for other snowflake stuff in /etc not associated with packages/services or other typical system config, and will put these into an etc_custom role.
Likewise, it looks in /usr/local for stuff, on the assumption that this is an area that custom apps/configs might've been placed in. These go into a usr_local_custom role.
-
It captures non-system user accounts, their group memberships and files such as their .ssh/authorized_keys, and .bashrc, .profile, .bash_aliases, .bash_logout if these files differ from the skel defaults
+
It captures non-system user accounts, group memberships, and files such as .ssh/authorized_keys. In --dangerous mode it can also capture shell dotfiles such as .bashrc, .profile, .bash_aliases, and .bash_logout when they differ from the skel defaults.
It takes into account anything the user set with --exclude-path or --include-path. For anything extra that is included, it will put these into an 'extra_paths' role. The location could be anywhere e.g something in /opt, /srv, whatever you want.
It writes the state.json and captures the artifacts.
Other things to be aware of:
-
You can use multiple invocations of --exclude-path to skip the bits you don't want. You also can always comment out from the playbook.yml or delete certain roles it generates once you've run the enroll manifest.
+
You can use multiple invocations of --exclude-path to skip the bits you don't want. You can also comment out or delete generated playbooks, classes, states, roles, or modules after enroll manifest if you want to trim the first pass.
In terms of safety measures: it doesn't traverse into symlinks, and it has an 'IgnorePolicy' that makes it ignore most binary files (except GPG binary keys used with apt) - though if you specify certain paths with --include-path and use --dangerous, it will skip some policy statements such as what types of content to ignore.
It will skip files that are too large, and it also currently has a hardcoded cap of the number of files that it will harvest (4000 for /etc, /usr/local/etc and /usr/local/bin, and 500 files per 'role'), to avoid unintentional 'runaway' situations.
If you are using the 'remote' mode to harvest, and your remote user requires a password for sudo, you can pass in --ask-become-pass (or -K) and it will prompt for the password. If you forget, and remote requires password for sudo, it'll still fall back to prompting for a password, but will be a bit slower to do so.
No, Enroll doesn't have any knowledge of Ansible Galaxy roles or community plugins. It generates all the roles itself. If you really want to use roles from the community, Enroll may not be the tool for you, other than perhaps to help get you started.
+
Does Enroll use community roles/modules?
+
No, Enroll does not pull in Ansible Galaxy roles, Puppet Forge modules, or Salt formulas. It generates its own output from the harvest state. If you want to adopt community content later, Enroll can still help you discover and bootstrap the host-specific state you need to port.
Keep in mind that a lot of software config files are also good candidates for being Jinja templates with abstracted vars for separate hosts.
-
Enroll does use my companion tool JinjaTurtle if it's installed, but JinjaTurtle only recognises certain types of files (.ini style, .json, .xml, .yaml, .toml, but not special ones like Nginx or Apache conf files which have their own special syntax). When Enroll can't turn a config file into a template, it copies the raw file instead and uses it with ansible.builtin.copy in role tasks.
+
Enroll does use my companion tool JinjaTurtle if it's installed, but JinjaTurtle only recognises certain types of files (.ini style, .json, .xml, .yaml, .toml, but not special ones like Nginx or Apache conf files which have their own special syntax). When Enroll cannot turn a config file into a template, it copies the raw file instead. JinjaTurtle templating is currently most useful for Ansible output; Puppet and Salt output stay closer to raw managed files and target-native data.
Generated playbooks tag each role as role_<name> (e.g. role_users, role_services, role_other). You can target a subset with ansible-playbook ... --tags role_users.
+
Tip: targeting subsets
+
Ansible playbooks tag each role as role_<name>. Puppet and Salt output can be trimmed by editing generated class/state inclusion data. In every target, treat the first generated manifest as a reviewable bootstrap rather than magic.
If JinjaTurtle (one of my other projects) is installed, Enroll can also produce Jinja2 templates for ini/json/xml/toml-style config and extract variables cleanly into Ansible, instead of just storing the 'raw' files.
+
If JinjaTurtle (one of my other projects) is installed, Enroll can also produce Jinja2 templates for ini/json/xml/toml-style config and extract variables cleanly into Ansible, instead of just storing the 'raw' files. Puppet and Salt output currently focus on raw managed files plus target-native data.
One of the things I miss from my Puppet days, was the way the Puppet 'agent' would check in with the server and realign itself to the declared desired state. With Ansible, it's easy for systems to fall 'out of date', especially if someone is doing the wrong thing and changing things on-the-fly instead of via config management!
+
One of the things I miss from my Puppet days, was the way the Puppet 'agent' would check in with the server and realign itself to the declared desired state. With any configuration-management system, it is easy for systems to fall out of date if someone changes things on-the-fly instead of via the desired-state workflow.
The purpose of enroll diff is to compare two 'harvests' and detect what has changed - be it adding/removing of programs, change to systemd unit state, modifications, addition or removal of files, and so on.
Optional: enforce the old harvest state (--enforce)
-
If drift exists and ansible-playbook is on PATH, Enroll can generate a manifest from the old harvest and apply it locally to restore expected state. It avoids package downgrades, and will often run Ansible with --tags role_... so only the roles implicated by the drift are applied. This is very much like a return to Puppet's agent mode!
+
If drift exists and ansible-playbook is on PATH, Enroll can generate a manifest from the old harvest and apply it locally to restore expected state. It avoids package downgrades, and will often run Ansible with --tags role_... so only the roles implicated by the drift are applied. This is intentionally Ansible-based today, even when you also use Puppet or Salt rendering for normal manifests.
For fleets, prefer multi-site output so roles stay generic and host inventory controls what is applied per host - reducing "shared role breaks other host" surprises.
+
For fleets, prefer multi-site output so target-native host data controls what is applied per host - reducing "shared role/module/state breaks another host" surprises.
diff --git a/src/content/examples.html b/src/content/examples.html
index 464eb1e..58e5ca5 100644
--- a/src/content/examples.html
+++ b/src/content/examples.html
@@ -1,13 +1,13 @@
---
title: "Examples"
html_title: "Enroll Examples"
-description: "Copy/paste recipes for Enroll: one host, fleets, drift detection, and safe storage."
+description: "Copy/paste recipes for Enroll: Ansible, Puppet, Salt, fleets, drift detection, and safe storage."
---
Examples
Copy/paste recipes
-
Practical flows you can adapt to your environment.
+
Practical flows you can adapt to Ansible, Puppet, Salt, or all three.
@@ -17,49 +17,85 @@ description: "Copy/paste recipes for Enroll: one host, fleets, drift detection,
No need to manually run commands on the server - your bundle lands locally. If your remote user needs a password for sudo, pass in --ask-become-pass or -K, just like in Ansible. If you don't want to use sudo, pass --no-sudo, but your harvest may contain less data.
+
The bundle lands locally. Change --target puppet to ansible or salt without re-harvesting. For remote sudo prompts use --ask-become-pass/-K; use --no-sudo for a less complete non-root harvest.
Enforcing the old harvest will restore its files/perms, missing packages, changed services or users, if ansible-playbook is on the PATH.
+
Enforcement currently uses generated Ansible and ansible-playbook on PATH, even if you also use Puppet or Salt for normal manifest output.
-
-
-
-
-
-
Safe harvesting (default)
-
Enroll tries to avoid harvesting files that might contain secrets. If you need to capture "everything", pass --dangerous and treat the output as sensitive.
-
You can still control what gets collected and what doesn't by using --include and --exclude flags.
Use this if you intend to store harvests/manifests long term or if you harvested with --dangerous.
diff --git a/src/content/news/0-7-0.html b/src/content/news/0-7-0.html
new file mode 100644
index 0000000..f4c5f97
--- /dev/null
+++ b/src/content/news/0-7-0.html
@@ -0,0 +1,115 @@
+---
+title: "Enroll 0.7.0: Puppet and Salt renderer support, and more"
+html_title: "Enroll 0.7.0 News"
+description: "Enroll 0.7.0 adds first-class Puppet and Salt manifest rendering alongside Ansible, along with other improvements."
+date: 2026-06-17
+summary: "Enroll can now render Puppet and Salt manifests alongside Ansible, detect Flatpak and Snap packages and other obscure runtime state, and produce better organised roles/modules."
+---
+
+
+
Release notes
+
Enroll 0.7.0: Puppet and Salt join Ansible
+
A single harvest can now be rendered into Ansible, Puppet, or Salt output.
+
+
+
+
+
+
+
+
+
Welcome to the first of Enroll's new, erm, news section! To celebrate, Enroll 0.7.0 has been released, and makes manifest rendering target-selectable. Ansible remains the default, but Puppet and Salt are now first-class manifest targets rather than ad-hoc exports.
+
+
Highlights
+
+
--target puppet renders Puppet module/control-repo style output., and in --fqdn mode, renders per-host Hiera data.
+
--target salt renders Salt state trees and, in --fqdn mode, Salt pillar data.
+
Ansible works basically as it always did, and is the default, but you can specify --target ansible too. As usual, in --fqdn mode, specific artifacts end up in host_vars inventory folders rather than polluting the 'golden' roles.
+
All three config management manifest renderers derive from the same harvest state. You can rendered repeatedly into different config management tools without re-harvesting the host!
+
Single-site output tries to combine package/service data by their package manager's Section (or equivalent metadata), to reduce role/module/state sprawl.
Did you find the number of manifested roles overwhelming?
+
Previously, Enroll created an Ansible role (or, now, a Puppet module or Salt role) for pretty much every 'package' it found. In some cases (especially on desktops) this could result in hundreds of roles. Technically fine, but overwhelming to look at!
+
+
As of 0.7.0, where Enroll can read that package metadata, it groups related package and service snapshots by the package manager's Section category (or comparable backend metadata), to make it less noisy. For example, network-related packages and config files might end up in a role called net. Meanwhile, vim, nano might both appear in editors, and mutt and Thunderbird may be in mail.
+
+
If you're not a fan of this new layout, you can pass --no-common-roles to enforce the previous behaviour. Also, if you use --fqdn for host-specific data-driven output, the 'common' roles are disabled automatically, because it's then safer to avoid 'bleed in' of unnecessary package installation on other hosts from a role that otherwise 'assumes too much' for all hosts.
+
+
Flatpak and Snap detection
+
Beyond deb and rpm
+
When using Ansible, Enroll now attempts to detect Flatpak and Snaps present on the system. For Flatpaks, this includes user-specific Flatpaks as well as system-wide ones. Manifesting to Ansible will attempt to use the community.general collection to create Flatpak and Snap tasks to enforce the presence of those packages.
Please be aware that you need version 13+ of the community.general collection for this to work properly. A requirements.yml gets created with your manifest to help you install it if necessary.
+
+
Other smaller changes
+
+
sysctl runtime parameters are now detected and would be written to /etc/sysctl.d/99-enroll.conf. Not all runtime parameters are supported.
+
.bashrc and similar files are now only harvested from user directories when --dangerous is used, since this is a common place for sensitive environment variables to be set. As always, remember that --dangerous gives better harvest coverage, but you should use --sops or some other means of your own to encrypt the harvested data at rest safely!
+
Some output during an Ansible play is hidden with no_log to avoid potentially sensitive output, particularly of systemd unit state.
+
In case you missed it in version 0.6.0: Enroll now harvests runtime iptables and ipset rules!
+
+
+
See you soon..
+
I'm off to try and write more tests - we're at 84% coverage in pytest, and we also run a stack of 'noop' executions for Ansible, Puppet and Salt too now, in CI.
+
Thanks to everyone who has reached out with suggestions, constructive criticism, and bug reports! You're helping make Enroll better for everyone.