Reverse-engineering Linux hosts into Ansible

Get an existing server into config management quickly.

Enroll inspects a Debian-like or RedHat-like Linux machine, harvests the state that matters, and generates Ansible roles and playbooks from the captured bundle.

Safe by default Optional SOPS encryption Remote over SSH
Harvest once, render Ansible
state.json + artifacts → roles/playbooks
$ enroll harvest --out ./harvest
$ enroll manifest --harvest ./harvest --out ./ansible
$ ansible-galaxy collection install -r ./ansible/requirements.yml
./ansible → playbook.yml, roles/, requirements.yml
./ansible + --host → inventory/host_vars/..., playbooks/<host>.yml

The mental model

Enroll is built around a small pipeline: capture first, render later, validate before you trust the output.

Harvest
Collect host facts and selected files into a bundle.
Manifest
Turn that bundle into an Ansible repository-style output tree.
Diff
Compare two harvests and optionally notify by webhook or email.
Explain
Summarise what was included or excluded and why.
Validate
Check schema and artifact consistency before using a bundle.
Default secret avoidance
Likely private keys, credential assignments, tokens, secret paths, oversized files, and binary-looking files are avoided unless you opt into --dangerous.
Ansible output
Generated output includes roles, playbooks, requirements, defaults, templates, and per-host variables in --host projects.
Grouped roles by default
Package/service snapshots are grouped by Debian Section or RPM Group where possible. Use --no-common-roles to keep one generated role per package or unit.
Multi-host projects
Start with --host and add hosts with --extend. Shared role logic uses inventory values; differing files are stored per host.
Remote over SSH
Run the harvest on another host through Paramiko/OpenSSH config support, then render the manifest locally.
SOPS bundles
--sops stores harvests and generated manifests as encrypted tarballs for safer at-rest storage.

Quickstart

Start with a local safe-mode harvest, inspect the generated output, then run Ansible when you are ready.

$ pipx install enroll
$ enroll harvest --out /tmp/enroll-harvest
$ enroll validate /tmp/enroll-harvest
$ enroll manifest --harvest /tmp/enroll-harvest --out /tmp/enroll-ansible
$ cd /tmp/enroll-ansible
$ ansible-galaxy collection install -r requirements.yml
$ ansible-playbook -i "localhost," -c local playbook.yml --check
$ enroll harvest \
  --remote-host host.example.net \
  --remote-user admin \
  --remote-ssh-config ~/.ssh/config \
  --out /tmp/enroll-harvest
$ enroll manifest --harvest /tmp/enroll-harvest --out /tmp/enroll-ansible --host host.example.net
$ enroll harvest --out /srv/enroll/baseline
$ enroll harvest --out /srv/enroll/current
$ enroll diff --old /srv/enroll/baseline --new /srv/enroll/current --format markdown --ignore-package-versions

What Enroll tries to capture

Packages and services

Manual packages, service-linked packages, systemd enable/running state, and service-relevant changed or custom config.

Configuration files

Changed package conffiles, unowned service files, miscellaneous /etc, APT/DNF/YUM config, selected symlinks, and explicit include paths.

Runtime and user state

Non-system users, SSH public keys, Flatpak/Snap state and Docker/Podman images. Capture live sysctl and firewall state explicitly with --harvest-sysctl and --harvest-firewall.

Commands

harvest

Collect host state into state.json and artifacts/.
Harvest docs

manifest

Validate a harvest and render Ansible output.
Manifest docs

single-shot

Run harvest and manifest in one command.
Single-shot docs

diff

Compare harvests and send reports.
Diff docs

explain

Explain what a harvest contains.
Explain docs

validate

Check schema and bundle consistency.
Validate docs

Safe by default, not magic

Enroll avoids obvious secrets by default, validates harvest structure, freezes directory bundles into private temp trees before manifesting, rejects unknown SSH host keys, and warns when a root PATH looks unsafe.

It cannot prove that a structurally valid harvest is semantically safe. Only apply manifests generated from harvests whose provenance you trust.

Dangerous mode means dangerous
Use --dangerous only when you intentionally want to bypass likely-secret checks. Pair it with --sops or another appropriate at-rest encryption workflow whenever there is any doubt.