This commit is contained in:
Miguel Jacq 2026-06-17 15:35:16 +10:00
parent 7e4f85b0fd
commit dd9a9113ba
Signed by: mig5
GPG key ID: 03906B4110AAD3B8
6 changed files with 388 additions and 128 deletions

View file

@ -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"
---
<header class="hero py-5">
<div class="container py-4">
<div class="row align-items-center g-4">
<div class="col-lg-6">
<div class="kicker mb-3"><i class="bi bi-magic"></i> Reverse-engineering servers into Ansible</div>
<h1 class="display-5 fw-800 lh-1 mb-3" style="letter-spacing:-0.03em;">Get an existing Linux host into Ansible in seconds.</h1>
<p class="lead mb-4">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.</p>
<div class="kicker mb-3"><i class="bi bi-magic"></i> Reverse-engineering servers into Ansible, Puppet, or Salt</div>
<h1 class="display-5 fw-800 lh-1 mb-3" style="letter-spacing:-0.03em;">Get an existing Linux host into configuration management in seconds.</h1>
<p class="lead mb-4">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.</p>
<div class="d-flex flex-wrap gap-2 mb-4">
<a class="btn btn-dark btn-lg" href="#quickstart"><i class="bi bi-rocket-takeoff"></i> Quickstart</a>
<a class="btn btn-outline-dark btn-lg" href="#demos"><i class="bi bi-play-circle"></i> Watch demos</a>
<a class="btn btn-outline-secondary btn-lg" href="https://pypi.org/project/enroll/" target="_blank" rel="noreferrer"><i class="bi bi-box-seam"></i> PyPI</a>
<a class="btn btn-outline-secondary btn-lg" href="news.html"><i class="bi bi-megaphone"></i> News</a>
</div>
<div class="d-flex flex-wrap gap-2">
<span class="badge badge-soft rounded-pill px-3 py-2"><i class="bi bi-speedometer"></i> Super fast</span>
@ -29,24 +30,18 @@ og_type: "website"
<div class="hero-card p-4 p-lg-4">
<div class="d-flex justify-content-between align-items-center mb-3">
<div class="fw-semibold"><i class="bi bi-terminal"></i> Config in the blink of an eye</div>
<div class="small text-muted">single-shot → ansible-playbook</div>
<div class="small text-muted">harvest once → render to your CM tool</div>
</div>
<div class="terminal">
<div class="mb-2"><span class="prompt">$</span> <code style="font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;">enroll single-shot --harvest ./harvest --out ./ansible</code></div>
<div class="mb-2"><span class="prompt">$</span> <code style="font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;">cd ./ansible && tree -L 2</code></div>
<pre class="mb-0" style="white-space:pre-wrap; font-family:'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace; font-size:.9rem;">.
├── ansible.cfg
├── playbook.yml
├── roles/
│ ├── cron/
│ ├── etc_custom/
│ ├── firewall/
│ ├── nginx/
│ ├── openssh-server/
│ ├── users/
└── README.md</pre>
<div class="mb-2"><span class="prompt">$</span> <code style="font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;">enroll harvest --out ./harvest</code></div>
<div class="mb-2"><span class="prompt">$</span> <code style="font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;">enroll manifest --harvest ./harvest --target ansible --out ./ansible</code></div>
<div class="mb-2"><span class="prompt">$</span> <code style="font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;">enroll manifest --harvest ./harvest --target puppet --out ./puppet</code></div>
<div class="mb-2"><span class="prompt">$</span> <code style="font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;">enroll manifest --harvest ./harvest --target salt --out ./salt</code></div>
<pre class="mb-0" style="white-space:pre-wrap; font-family:'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace; font-size:.9rem;">./ansible → playbook.yml, roles/, inventory/...
./puppet → manifests/site.pp, modules/, data/...
./salt → states/top.sls, states/roles/, pillar/...</pre>
</div>
<div class="smallprint mt-3">Tip: for multiple hosts, use <code>--fqdn</code> to generate inventory-driven, data-driven roles.</div>
<div class="smallprint mt-3">Tip: for multiple hosts, use <code>--fqdn</code> to generate target-native host data: Ansible inventory, Puppet Hiera, or Salt pillar.</div>
</div>
</div>
</div>
@ -71,7 +66,7 @@ og_type: "website"
<div class="icon-pill"><i class="bi bi-diagram-3"></i></div>
<div>
<div class="fw-semibold">Manifest</div>
<div class="text-muted">Render Ansible roles &amp; playbooks from the harvest.</div>
<div class="text-muted">Render Ansible roles/playbooks, Puppet modules/Hiera data, or Salt states/pillar from the harvest.</div>
</div>
</div>
<div class="d-flex gap-3">
@ -107,11 +102,27 @@ og_type: "website"
</div>
</div>
</div>
<div class="col-md-6">
<div class="card feature-card h-100">
<div class="card-body p-4">
<div class="fw-semibold mb-1">Ansible, Puppet, or Salt</div>
<div class="text-muted">The harvest bundle is renderer-neutral. Choose <code>--target ansible</code>, <code>--target puppet</code>, or <code>--target salt</code> at manifest time.</div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card feature-card h-100">
<div class="card-body p-4">
<div class="fw-semibold mb-1">Fewer roles/modules where possible</div>
<div class="text-muted">By default, package/service snapshots are grouped by package <code>Section</code> or equivalent metadata to reduce role/module sprawl. Use <code>--no-common-roles</code>, or <code>--fqdn</code>, to keep output more host-specific.</div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card feature-card h-100">
<div class="card-body p-4">
<div class="fw-semibold mb-1">Multi-site without "shared role broke host2"</div>
<div class="text-muted">In <code>--fqdn</code> mode, roles are data-driven and host inventory decides what gets managed per host.</div>
<div class="text-muted">In <code>--fqdn</code> mode, host-specific data moves into the target's native host-data layer: Ansible inventory, Puppet Hiera, or Salt pillar.</div>
</div>
</div>
</div>
@ -119,7 +130,7 @@ og_type: "website"
<div class="card feature-card h-100">
<div class="card-body p-4">
<div class="fw-semibold mb-1">Remote over SSH</div>
<div class="text-muted">Harvest a remote host from your workstation, then manifest Ansible output locally.</div>
<div class="text-muted">Harvest a remote host from your workstation, then render Ansible, Puppet, or Salt output locally.</div>
</div>
</div>
</div>
@ -171,19 +182,28 @@ og_type: "website"
<div class="col-lg-6">
<div class="codeblock">
<button class="btn btn-sm btn-outline-secondary copy-btn" data-copy-target="#qs1code"><i class="bi bi-clipboard"></i> Copy</button>
<pre class="terminal mb-0"><code id="qs1code"># Harvest → Manifest in one go
enroll single-shot --harvest ./harvest --out ./ansible
<pre class="terminal mb-0"><code id="qs1code"># Harvest once
enroll harvest --out ./harvest
# Then run Ansible locally
ansible-playbook -i "localhost," -c local ./ansible/playbook.yml</code></pre>
# Render and noop-test Ansible
enroll manifest --harvest ./harvest --target ansible --out ./ansible
ansible-playbook -i "localhost," -c local ./ansible/playbook.yml --check --diff
# Render and noop-test Puppet
enroll manifest --harvest ./harvest --target puppet --out ./puppet
puppet apply --modulepath ./puppet/modules ./puppet/manifests/site.pp --noop
# Render and noop-test Salt
enroll manifest --harvest ./harvest --target salt --out ./salt
salt-call --local --file-root ./salt/states state.apply test=True</code></pre>
</div>
</div>
<div class="col-lg-6">
<div class="callout p-4 h-100">
<div class="fw-semibold mb-2">Good for</div>
<div class="text-muted">Disaster recovery snapshots, "make this one host reproducible", and carving a golden role set you'll refine over time.</div>
<div class="text-muted">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.</div>
<hr>
<div class="smallprint">Want templates for structured configs? Install <a href="https://git.mig5.net/mig5/jinjaturtle" target="_blank" rel="noopener noreferrer">JinjaTurtle</a> and use <code>--jinjaturtle</code> (or let it auto-detect).</div>
<div class="smallprint">Using Ansible and want templates for structured configs? Install <a href="https://git.mig5.net/mig5/jinjaturtle" target="_blank" rel="noopener noreferrer">JinjaTurtle</a> and use <code>--jinjaturtle</code> (or let it auto-detect).</div>
</div>
</div>
</div>
@ -197,6 +217,7 @@ enroll single-shot \
--remote-host myhost.example.com \
--remote-user myuser \
--harvest /tmp/enroll-harvest \
--target ansible \
--out ./ansible \
--fqdn myhost.example.com</code></pre>
</div>
@ -206,14 +227,23 @@ enroll single-shot \
<div class="tab-pane fade" id="qs3" role="tabpanel" aria-labelledby="qs3-tab">
<div class="codeblock">
<button class="btn btn-sm btn-outline-secondary copy-btn" data-copy-target="#qs3code"><i class="bi bi-clipboard"></i> Copy</button>
<pre class="terminal mb-0"><code id="qs3code"># Multi-site mode: shared roles, host-specific state in inventory
<pre class="terminal mb-0"><code id="qs3code"># Multi-site mode: one harvest, target-native host data
fqdn="$(hostname -f)"
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</code></pre>
# Ansible: inventory/host_vars and per-host playbook
enroll manifest --harvest /tmp/enroll-harvest --target ansible --out ./ansible --fqdn "$fqdn"
ansible-playbook ./ansible/playbooks/${fqdn}.yml -i ./ansible/inventory/hosts.ini -c local --check --diff
# Puppet: Hiera node data, certname selects the host
enroll manifest --harvest /tmp/enroll-harvest --target puppet --out ./puppet --fqdn "$fqdn"
puppet apply --modulepath ./puppet/modules --hiera_config ./puppet/hiera.yaml --certname "$fqdn" ./puppet/manifests/site.pp --noop
# Salt: pillar node data, minion id selects the host
enroll manifest --harvest /tmp/enroll-harvest --target salt --out ./salt --fqdn "$fqdn"
salt-call --local --id "$fqdn" --file-root ./salt/states --pillar-root ./salt/pillar state.apply test=True</code></pre>
</div>
<div class="smallprint mt-3">Rule of thumb: single-site for "one server, easy-to-read roles"; <code>--fqdn</code> for "many servers, high abstraction, fast adoption".</div>
<div class="smallprint mt-3">Rule of thumb: single-site for "one server, easy-to-read roles/modules/states"; <code>--fqdn</code> for "many servers, host-specific data, fast adoption".</div>
</div>
<div class="tab-pane fade" id="qs4" role="tabpanel" aria-labelledby="qs4-tab">
@ -260,14 +290,14 @@ enroll explain /path/to/harvest.sops \
<div class="tab-pane fade" id="qs6" role="tabpanel" aria-labelledby="qs6-tab">
<div class="codeblock">
<button class="btn btn-sm btn-outline-secondary copy-btn" data-copy-target="#qs6code"><i class="bi bi-clipboard"></i> Copy</button>
<pre class="terminal mb-0"><code id="qs5code"># Validate a harvest is correct.
<pre class="terminal mb-0"><code id="qs6code"># 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
</code></pre>
</div>
<div class="smallprint mt-3">'validate' makes sure the harvest's state confirms to Enroll's <a href="/schema.html">state schema</a>, 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.</div>
<div class="smallprint mt-3">'validate' makes sure the harvest's state conforms to Enroll's <a href="/schema.html">state schema</a>, 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.</div>
</div>
</div>
</div>
@ -295,7 +325,7 @@ enroll validate /path/to/harvest --schema https://enroll.sh/schema/state.schema.
<div class="card feature-card h-100">
<div class="card-body p-4">
<div class="fw-semibold mb-1">Manifest</div>
<div class="text-muted mb-3">Render Ansible roles/playbooks.</div>
<div class="text-muted mb-3">Render Ansible roles/playbooks, Puppet modules, or Salt states.</div>
<div class="asciicast" data-asciinema-id="765204"><script src="https://asciinema.org/a/765204.js" id="asciicast-765204" async="true"></script></div>
</div>
</div>
@ -325,6 +355,23 @@ enroll validate /path/to/harvest --schema https://enroll.sh/schema/state.schema.
</div>
</section>
<section class="py-5" id="news" aria-label="News">
<div class="container">
<div class="callout p-4">
<div class="row align-items-center g-3">
<div class="col-lg-8">
<div class="kicker mb-2"><i class="bi bi-megaphone"></i> News</div>
<h2 class="h4 fw-bold mb-2">Enroll 0.7.0 adds Puppet and Salt rendering</h2>
<p class="text-secondary mb-0">The same harvest bundle can now render Ansible, Puppet, or Salt output, with target-native multi-host layouts and package-section grouping where possible.</p>
</div>
<div class="col-lg-4 text-lg-end">
<a class="btn btn-outline-dark" href="news.html"><i class="bi bi-newspaper"></i> Read release notes</a>
</div>
</div>
</div>
</div>
</section>
<section class="py-5 bg-light" id="install" aria-label="Install">
<div class="container">
<div class="row g-3 align-items-end mb-3">