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 + --fqdn → playbooks/<fqdn>.yml + inventory/host_vars/...
Current Enroll output is Ansible-only. Older examples that mention --target puppet or --target salt are from an experimental branch and no longer match the CLI.

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 host variables in --fqdn mode.
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-site mode
--fqdn moves host-specific state into Ansible inventory and implies --no-common-roles.
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.

$ 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 --fqdn 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, Docker/Podman images, writable sysctls, and fallback iptables/ipset state when persistent files are absent.

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.