Reverse-engineering servers into Ansible

Turn a running Linux host into an Ansible repo.

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.

Safe by default Optional SOPS encryption Remote over SSH
Config management in seconds
single-shot → ansible-playbook
$ enroll single-shot --out ./ansible
$ cd ./ansible && tree -L 2
.
├── ansible.cfg
├── playbook.yml
├── roles/
│   ├── cron/
│   ├── firewall/
│   ├── nginx/
│   ├── openssh-server/
│   ├── users/
│   ├── etc_custom/
└── 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 an optional drift report:

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.
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
• Capture what might've been missed in initial config management
• Tweak include/exclude paths as needed
• Capture what changed from package defaults
• Include or exclude paths
diff mode detects and alerts about drift over time

Quickstart

Copy, paste, iterate.

# Harvest → Manifest in one go
enroll single-shot --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
enroll diff --old /path/to/harvestA --new /path/to/harvestB --format markdown

# 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: ...' \
  --exit-code

Demonstrations

Harvest
Collect state into a bundle.
Manifest
Render Ansible roles/playbooks.
Single-shot
One command → workable output.
Diff
Drift report + notifications.

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/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
If you're experimenting with --dangerous, strongly consider pairing it with --sops for encrypted-at-rest bundles.