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.

Super fast Optional SOPS encryption Remote over SSH
Config in the blink of an eye
single-shot → ansible-playbook
$ enroll single-shot --harvest ./harvest --out ./ansible
$ cd ./ansible && tree -L 2
.
├── ansible.cfg
├── playbook.yml
├── roles/
│   ├── cron/
│   ├── etc_custom/
│   ├── firewall/
│   ├── nginx/
│   ├── openssh-server/
│   ├── users/
└── README.md
Tip: for multiple hosts, use --fqdn to generate inventory-driven, data-driven roles.

A simple mental model

Enroll is built around two phases, plus optional drift and reporting tools:

Harvest
Collect host facts + relevant files into a bundle.
Manifest
Render Ansible roles & playbooks from the harvest.
Diff
Compare two harvests and notify via webhook/email.
Explain
Analyze what's included/excluded in the harvest and why.
Validate
Confirm that a harvest isn't corrupt or lacking artifacts.
Safe-by-default harvesting
Enroll avoids likely secrets with a path denylist, content sniffing, and size caps - then lets you opt in to more aggressive collection when you're ready.
Multi-site without "shared role broke host2"
In --fqdn mode, roles are data-driven and host inventory decides what gets managed per host.
Remote over SSH
Harvest a remote host from your workstation, then manifest Ansible output locally.
Encrypt bundles at rest
Use --sops to store harvests/manifests as a single encrypted .tar.gz.sops file (GPG) for safer long-term storage as a DR strategy.
Why sysadmins like it
• Rapid enrolling of existing infra into config management
• Tweak include/exclude paths as needed
• Capture what changed from package defaults
diff mode detects and alerts about drift

Quickstart

# Harvest → Manifest in one go
enroll single-shot --harvest ./harvest --out ./ansible

# Then run Ansible locally
ansible-playbook -i "localhost," -c local ./ansible/playbook.yml
Good for
Disaster recovery snapshots, "make this one host reproducible", and carving a golden role set you'll refine over time.

Want templates for structured configs? Install JinjaTurtle and use --jinjaturtle (or let it auto-detect).
# Remote harvest over SSH, then manifest locally
enroll single-shot \
  --remote-host myhost.example.com \
  --remote-user myuser \
  --harvest /tmp/enroll-harvest \
  --out ./ansible \
  --fqdn myhost.example.com
If you don't want/need sudo on the remote host, add --no-sudo (expect a less complete harvest).
# Multi-site mode: shared roles, host-specific state in inventory
enroll harvest --out /tmp/enroll-harvest
enroll manifest --harvest /tmp/enroll-harvest --out ./ansible --fqdn "$(hostname -f)"

# Run the per-host playbook
ansible-playbook ./ansible/playbooks/"$(hostname -f)".yml
Rule of thumb: single-site for "one server, easy-to-read roles"; --fqdn for "many servers, high abstraction, fast adoption".
# Compare two harvests and get a human-friendly report (ignoring noise)
enroll diff --old /path/to/harvestA --new /path/to/harvestB --format markdown \
  --exclude-path /var/anacron \
  --ignore-package-versions

# Send a webhook when differences are detected
enroll diff \
  --old /path/to/harvestA \
  --new /path/to/harvestB \
  --webhook https://example.net/webhook \
  --webhook-format json \
  --webhook-header 'X-Enroll-Secret: ...' \
  --ignore-package-versions \
  --exit-code

# Ignore a path and changes to package versions, and optionally
# enforce the old state locally (requires ansible-playbook)
enroll diff --old /path/to/harvestA --new /path/to/harvestB \
  --exclude-path /var/anacron \
  --ignore-package-versions \
  --enforce
E-mail notifications are also supported. Run it on a systemd timer to alert to drift!
# Explain what's in a harvest
enroll explain /path/to/harvest

# JSON format, and using a SOPS-encrypted harvest
enroll explain /path/to/harvest.sops \
  --sops \
  --format json
'explain' tells you why something was included, but also why something was excluded.
# 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.

Demonstrations

Harvest
Collect state into a bundle.
Manifest
Render Ansible roles/playbooks.
Single-shot
Harvest → Manifest in one command.
Diff
Drift report + webhook/email notifications, or optionally enforce the previous state!

Install

Use your preferred packaging. An AppImage is also available.

sudo mkdir -p /usr/share/keyrings
curl -fsSL https://mig5.net/static/mig5.asc | sudo gpg --dearmor -o /usr/share/keyrings/mig5.gpg
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/mig5.gpg] https://apt.mig5.net $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/mig5.list
sudo apt update
sudo apt install enroll
sudo rpm --import https://mig5.net/static/mig5.asc

sudo tee /etc/yum.repos.d/mig5.repo > /dev/null << 'EOF'
[mig5]
name=mig5 Repository
baseurl=https://rpm.mig5.net/$releasever/rpm/$basearch
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://mig5.net/static/mig5.asc
EOF

sudo dnf upgrade --refresh
sudo dnf install enroll
pip install enroll
# or: pipx install enroll