Compare commits
3 commits
7e4f85b0fd
...
0d8927ef6d
| Author | SHA1 | Date | |
|---|---|---|---|
| 0d8927ef6d | |||
| 83c30de44a | |||
| dd9a9113ba |
14 changed files with 728 additions and 142 deletions
|
|
@ -1,22 +1,23 @@
|
||||||
---
|
---
|
||||||
title: "Enroll"
|
title: "Enroll"
|
||||||
html_title: "Enroll - Reverse-engineering servers into Ansible"
|
html_title: "Enroll - Reverse-engineering servers into configuration management"
|
||||||
description: "Enroll inspects Debian-like and RedHat-like Linux hosts and generates Ansible roles/playbooks from what it finds. Harvest → Manifest → Manage."
|
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 Ansible"
|
og_title: "Enroll - Reverse-engineering servers into configuration management"
|
||||||
og_description: "Harvest a host's real configuration and turn it into Ansible roles/playbooks. Safe-by-default, with optional SOPS encryption."
|
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"
|
og_type: "website"
|
||||||
---
|
---
|
||||||
<header class="hero py-5">
|
<header class="hero py-5">
|
||||||
<div class="container py-4">
|
<div class="container py-4">
|
||||||
<div class="row align-items-center g-4">
|
<div class="row align-items-center g-4">
|
||||||
<div class="col-lg-6">
|
<div class="col-lg-6">
|
||||||
<div class="kicker mb-3"><i class="bi bi-magic"></i> Reverse-engineering servers into Ansible</div>
|
<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 Ansible in seconds.</h1>
|
<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/playbooks so you can bring snowflakes under management fast.</p>
|
<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">
|
<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-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-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="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>
|
||||||
<div class="d-flex flex-wrap gap-2">
|
<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>
|
<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="hero-card p-4 p-lg-4">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
<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="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>
|
||||||
<div class="terminal">
|
<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;">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;">cd ./ansible && tree -L 2</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>
|
||||||
<pre class="mb-0" style="white-space:pre-wrap; font-family:'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace; font-size:.9rem;">.
|
<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>
|
||||||
├── ansible.cfg
|
<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>
|
||||||
├── playbook.yml
|
<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/...
|
||||||
├── roles/
|
./puppet → manifests/site.pp, modules/, data/...
|
||||||
│ ├── cron/
|
./salt → states/top.sls, states/roles/, pillar/...</pre>
|
||||||
│ ├── etc_custom/
|
|
||||||
│ ├── firewall/
|
|
||||||
│ ├── nginx/
|
|
||||||
│ ├── openssh-server/
|
|
||||||
│ ├── users/
|
|
||||||
└── README.md</pre>
|
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -71,7 +66,7 @@ og_type: "website"
|
||||||
<div class="icon-pill"><i class="bi bi-diagram-3"></i></div>
|
<div class="icon-pill"><i class="bi bi-diagram-3"></i></div>
|
||||||
<div>
|
<div>
|
||||||
<div class="fw-semibold">Manifest</div>
|
<div class="fw-semibold">Manifest</div>
|
||||||
<div class="text-muted">Render Ansible roles & 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>
|
</div>
|
||||||
<div class="d-flex gap-3">
|
<div class="d-flex gap-3">
|
||||||
|
|
@ -107,11 +102,27 @@ og_type: "website"
|
||||||
</div>
|
</div>
|
||||||
</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">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="col-md-6">
|
||||||
<div class="card feature-card h-100">
|
<div class="card feature-card h-100">
|
||||||
<div class="card-body p-4">
|
<div class="card-body p-4">
|
||||||
<div class="fw-semibold mb-1">Multi-site without "shared role broke host2"</div>
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -119,7 +130,7 @@ og_type: "website"
|
||||||
<div class="card feature-card h-100">
|
<div class="card feature-card h-100">
|
||||||
<div class="card-body p-4">
|
<div class="card-body p-4">
|
||||||
<div class="fw-semibold mb-1">Remote over SSH</div>
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -171,19 +182,28 @@ og_type: "website"
|
||||||
<div class="col-lg-6">
|
<div class="col-lg-6">
|
||||||
<div class="codeblock">
|
<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>
|
<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
|
<pre class="terminal mb-0"><code id="qs1code"># Harvest once
|
||||||
enroll single-shot --harvest ./harvest --out ./ansible
|
enroll harvest --out ./harvest
|
||||||
|
|
||||||
# Then run Ansible locally
|
# Render and noop-test Ansible
|
||||||
ansible-playbook -i "localhost," -c local ./ansible/playbook.yml</code></pre>
|
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>
|
</div>
|
||||||
<div class="col-lg-6">
|
<div class="col-lg-6">
|
||||||
<div class="callout p-4 h-100">
|
<div class="callout p-4 h-100">
|
||||||
<div class="fw-semibold mb-2">Good for</div>
|
<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>
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -197,6 +217,7 @@ enroll single-shot \
|
||||||
--remote-host myhost.example.com \
|
--remote-host myhost.example.com \
|
||||||
--remote-user myuser \
|
--remote-user myuser \
|
||||||
--harvest /tmp/enroll-harvest \
|
--harvest /tmp/enroll-harvest \
|
||||||
|
--target ansible \
|
||||||
--out ./ansible \
|
--out ./ansible \
|
||||||
--fqdn myhost.example.com</code></pre>
|
--fqdn myhost.example.com</code></pre>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -206,14 +227,23 @@ enroll single-shot \
|
||||||
<div class="tab-pane fade" id="qs3" role="tabpanel" aria-labelledby="qs3-tab">
|
<div class="tab-pane fade" id="qs3" role="tabpanel" aria-labelledby="qs3-tab">
|
||||||
<div class="codeblock">
|
<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>
|
<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 harvest --out /tmp/enroll-harvest
|
||||||
enroll manifest --harvest /tmp/enroll-harvest --out ./ansible --fqdn "$(hostname -f)"
|
|
||||||
|
|
||||||
# Run the per-host playbook
|
# Ansible: inventory/host_vars and per-host playbook
|
||||||
ansible-playbook ./ansible/playbooks/"$(hostname -f)".yml</code></pre>
|
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>
|
||||||
<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>
|
||||||
|
|
||||||
<div class="tab-pane fade" id="qs4" role="tabpanel" aria-labelledby="qs4-tab">
|
<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="tab-pane fade" id="qs6" role="tabpanel" aria-labelledby="qs6-tab">
|
||||||
<div class="codeblock">
|
<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>
|
<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
|
enroll validate /path/to/harvest
|
||||||
|
|
||||||
# Check against the latest published version of the state schema specification
|
# Check against the latest published version of the state schema specification
|
||||||
enroll validate /path/to/harvest --schema https://enroll.sh/schema/state.schema.json
|
enroll validate /path/to/harvest --schema https://enroll.sh/schema/state.schema.json
|
||||||
</code></pre>
|
</code></pre>
|
||||||
</div>
|
</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>
|
</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 feature-card h-100">
|
||||||
<div class="card-body p-4">
|
<div class="card-body p-4">
|
||||||
<div class="fw-semibold mb-1">Manifest</div>
|
<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 class="asciicast" data-asciinema-id="765204"><script src="https://asciinema.org/a/765204.js" id="asciicast-765204" async="true"></script></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -325,6 +355,23 @@ enroll validate /path/to/harvest --schema https://enroll.sh/schema/state.schema.
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</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">
|
<section class="py-5 bg-light" id="install" aria-label="Install">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row g-3 align-items-end mb-3">
|
<div class="row g-3 align-items-end mb-3">
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ description: "How Enroll works: harvest, manifest, modes, and configuration."
|
||||||
<div class="position-sticky" style="top: 96px;">
|
<div class="position-sticky" style="top: 96px;">
|
||||||
<div class="list-group">
|
<div class="list-group">
|
||||||
<a class="list-group-item list-group-item-action" href="#model">Mental model</a>
|
<a class="list-group-item list-group-item-action" href="#model">Mental model</a>
|
||||||
|
<a class="list-group-item list-group-item-action" href="#targets">Targets and grouping</a>
|
||||||
<a class="list-group-item list-group-item-action" href="#harvest">How harvesting works</a>
|
<a class="list-group-item list-group-item-action" href="#harvest">How harvesting works</a>
|
||||||
<a class="list-group-item list-group-item-action" href="#schema">State schema</a>
|
<a class="list-group-item list-group-item-action" href="#schema">State schema</a>
|
||||||
<a class="list-group-item list-group-item-action" href="#modes">Single-site vs multi-site</a>
|
<a class="list-group-item list-group-item-action" href="#modes">Single-site vs multi-site</a>
|
||||||
|
|
@ -38,7 +39,7 @@ description: "How Enroll works: harvest, manifest, modes, and configuration."
|
||||||
<div class="col-lg-9">
|
<div class="col-lg-9">
|
||||||
<section id="model" class="scroll-mt-nav mb-5">
|
<section id="model" class="scroll-mt-nav mb-5">
|
||||||
<h2 class="section-title fw-bold">Mental model</h2>
|
<h2 class="section-title fw-bold">Mental model</h2>
|
||||||
<p class="text-secondary">Enroll is intentionally simple: it collects facts first, then renders Ansible from those facts.</p>
|
<p class="text-secondary">Enroll is intentionally simple: it collects facts first, then renders target-native configuration-management output from those facts.</p>
|
||||||
|
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
|
|
@ -54,7 +55,7 @@ description: "How Enroll works: harvest, manifest, modes, and configuration."
|
||||||
<li>Detect installed packages and services</li>
|
<li>Detect installed packages and services</li>
|
||||||
<li>Collect config that deviates from packaged defaults (where possible)</li>
|
<li>Collect config that deviates from packaged defaults (where possible)</li>
|
||||||
<li>Grab relevant custom/unowned files in service dirs</li>
|
<li>Grab relevant custom/unowned files in service dirs</li>
|
||||||
<li>Capture non-system users & SSH public keys, .bashrc files etc</li>
|
<li>Capture non-system users, group memberships, SSH public keys, and dangerous-mode shell dotfiles when requested</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -64,13 +65,13 @@ description: "How Enroll works: harvest, manifest, modes, and configuration."
|
||||||
<div class="icon-pill"><i class="bi bi-boxes"></i></div>
|
<div class="icon-pill"><i class="bi bi-boxes"></i></div>
|
||||||
<div>
|
<div>
|
||||||
<div class="fw-semibold">2) Manifest</div>
|
<div class="fw-semibold">2) Manifest</div>
|
||||||
<div class="small text-secondary">Generate an Ansible repo structure</div>
|
<div class="small text-secondary">Generate Ansible, Puppet, or Salt output</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ul class="mb-0 small">
|
<ul class="mb-0 small">
|
||||||
<li>Roles with files/templates and defaults</li>
|
<li>Ansible roles/playbooks, Puppet modules/Hiera data, or Salt states/pillar</li>
|
||||||
<li>Playbooks to apply the captured state</li>
|
<li>Noop-friendly output you can review before applying</li>
|
||||||
<li>Optional inventory structure for multi-host runs: each host gets its own playbook</li>
|
<li>Optional target-native host data for multi-host runs</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -79,8 +80,52 @@ description: "How Enroll works: harvest, manifest, modes, and configuration."
|
||||||
<div class="terminal mt-4">
|
<div class="terminal mt-4">
|
||||||
<div class="small text-secondary mb-2">Typical flow</div>
|
<div class="small text-secondary mb-2">Typical flow</div>
|
||||||
<pre class="mb-0"><code><span class="prompt">$</span> enroll harvest --out /tmp/enroll-harvest
|
<pre class="mb-0"><code><span class="prompt">$</span> enroll harvest --out /tmp/enroll-harvest
|
||||||
<span class="prompt">$</span> enroll manifest --harvest /tmp/enroll-harvest --out /tmp/enroll-ansible
|
<span class="prompt">$</span> enroll manifest --harvest /tmp/enroll-harvest --target ansible --out /tmp/enroll-ansible
|
||||||
<span class="prompt">$</span> ansible-playbook -i "localhost," -c local /tmp/enroll-ansible/playbook.yml</code></pre>
|
<span class="prompt">$</span> ansible-playbook -i "localhost," -c local /tmp/enroll-ansible/playbook.yml --check --diff
|
||||||
|
|
||||||
|
<span class="prompt">$</span> enroll manifest --harvest /tmp/enroll-harvest --target puppet --out /tmp/enroll-puppet
|
||||||
|
<span class="prompt">$</span> puppet apply --modulepath /tmp/enroll-puppet/modules /tmp/enroll-puppet/manifests/site.pp --noop
|
||||||
|
|
||||||
|
<span class="prompt">$</span> enroll manifest --harvest /tmp/enroll-harvest --target salt --out /tmp/enroll-salt
|
||||||
|
<span class="prompt">$</span> salt-call --local --file-root /tmp/enroll-salt/states state.apply test=True</code></pre>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="targets" class="scroll-mt-nav mb-5">
|
||||||
|
<h2 class="section-title fw-bold">Targets and grouping</h2>
|
||||||
|
<p class="text-secondary">The harvest bundle is the contract. Manifesting is a separate step, so the same harvest can be rendered into Ansible, Puppet, or Salt without re-probing the host.</p>
|
||||||
|
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="feature-card p-4 h-100">
|
||||||
|
<div class="fw-semibold mb-2">Ansible</div>
|
||||||
|
<p class="small text-secondary mb-2">Generates playbooks, roles, defaults, files/templates, inventory, and host vars.</p>
|
||||||
|
<div class="terminal"><pre class="mb-0"><code><span class="prompt">$</span> enroll manifest --harvest ./harvest --target ansible --out ./ansible
|
||||||
|
<span class="prompt">$</span> ansible-playbook -i "localhost," -c local ./ansible/playbook.yml --check --diff</code></pre></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="feature-card p-4 h-100">
|
||||||
|
<div class="fw-semibold mb-2">Puppet</div>
|
||||||
|
<p class="small text-secondary mb-2">Generates a control-repo-style tree with <code>manifests/site.pp</code>, modules, files, and Hiera data in <code>--fqdn</code> mode.</p>
|
||||||
|
<div class="terminal"><pre class="mb-0"><code><span class="prompt">$</span> enroll manifest --harvest ./harvest --target puppet --out ./puppet
|
||||||
|
<span class="prompt">$</span> puppet apply --modulepath ./puppet/modules ./puppet/manifests/site.pp --noop</code></pre></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="feature-card p-4 h-100">
|
||||||
|
<div class="fw-semibold mb-2">Salt</div>
|
||||||
|
<p class="small text-secondary mb-2">Generates Salt state trees under <code>states/</code>, with pillar data under <code>pillar/</code> in <code>--fqdn</code> mode.</p>
|
||||||
|
<div class="terminal"><pre class="mb-0"><code><span class="prompt">$</span> enroll manifest --harvest ./harvest --target salt --out ./salt
|
||||||
|
<span class="prompt">$</span> salt-call --local --file-root ./salt/states state.apply test=True</code></pre></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="callout p-4 mt-3">
|
||||||
|
<div class="fw-semibold mb-2"><i class="bi bi-collection"></i> Reducing role/module/state sprawl</div>
|
||||||
|
<p class="small text-secondary mb-2">When Enroll can see package metadata, it tries to combine related package/service snapshots by the package <code>Section</code> category, or equivalent backend metadata. On Debian-like systems this can turn many tiny package roles into broader groups such as admin, web, net, libs, or utils. On RPM-like systems it uses comparable package group metadata where available.</p>
|
||||||
|
<p class="small text-secondary mb-0">This grouping is enabled by default in single-site output. It is disabled by <code>--no-common-roles</code> when you want one role/module/state per discovered package/service, and it is also disabled by <code>--fqdn</code> because host-specific output should stay isolated in inventory/Hiera/pillar.</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
@ -92,19 +137,19 @@ description: "How Enroll works: harvest, manifest, modes, and configuration."
|
||||||
<li>Detects the OS and its package backend (e.g dpkg vs rpm)</li>
|
<li>Detects the OS and its package backend (e.g dpkg vs rpm)</li>
|
||||||
<li>Detects what packages are installed</li>
|
<li>Detects what packages are installed</li>
|
||||||
<li>For each package, it tries to detect files in <code>/etc</code> that have been modified from the default that get shipped with the package.</li>
|
<li>For each package, it tries to detect files in <code>/etc</code> that have been modified from the default that get shipped with the package.</li>
|
||||||
<li>It detects running/enabled services and timers via systemd. For each of these, it looks for the unit files, any 'drop-in' files, environment variable files, etc, as well as what executable it executes, and tries to map those systemd services to the packages it's already learned about earlier (that way, those 'packages' or future Ansible roles, can also be associated with 'handlers' in Ansible, to handle restart of the services if/when the configs change)</li>
|
<li>It detects running/enabled services and timers via systemd. For each of these, it looks for the unit files, any 'drop-in' files, environment variable files, etc, as well as what executable it executes, and tries to map those systemd services to the packages it's already learned about earlier (that way, those 'packages' or future manifest output can also be associated with service handling in the selected target, to handle restart of the services if/when the configs change)</li>
|
||||||
<li>Aside from known packages already learned, it optimistically tries to capture extra system configuration in <code>/etc</code> that is common for config management. This is stuff like the apt or dnf configuration, crons, logrotate configs, networking settings, hosts files, etc.</li>
|
<li>Aside from known packages already learned, it optimistically tries to capture extra system configuration in <code>/etc</code> that is common for config management. This is stuff like the apt or dnf configuration, crons, logrotate configs, networking settings, hosts files, etc.</li>
|
||||||
<li>For applications that commonly make use of symlinks (think Apache2 or Nginx's <code>sites-enabled</code> or <code>mods-enabled</code>), it notes what symlinks exist so that it can capture those in Ansible</li>
|
<li>For applications that commonly make use of symlinks (think Apache2 or Nginx's <code>sites-enabled</code> or <code>mods-enabled</code>), it notes what symlinks exist so that it can capture those in the generated manifest</li>
|
||||||
<li>It also looks for other snowflake stuff in <code>/etc</code> not associated with packages/services or other typical system config, and will put these into an <code>etc_custom</code> role.</li>
|
<li>It also looks for other snowflake stuff in <code>/etc</code> not associated with packages/services or other typical system config, and will put these into an <code>etc_custom</code> role.</li>
|
||||||
<li>Likewise, it looks in <code>/usr/local</code> for stuff, on the assumption that this is an area that custom apps/configs might've been placed in. These go into a <code>usr_local_custom</code> role.</li>
|
<li>Likewise, it looks in <code>/usr/local</code> for stuff, on the assumption that this is an area that custom apps/configs might've been placed in. These go into a <code>usr_local_custom</code> role.</li>
|
||||||
<li>It captures non-system user accounts, their group memberships and files such as their <code>.ssh/authorized_keys</code>, and <code>.bashrc</code>, <code>.profile</code>, <code>.bash_aliases</code>, <code>.bash_logout</code> if these files differ from the <code>skel</code> defaults</li>
|
<li>It captures non-system user accounts, group memberships, and files such as <code>.ssh/authorized_keys</code>. In <code>--dangerous</code> mode it can also capture shell dotfiles such as <code>.bashrc</code>, <code>.profile</code>, <code>.bash_aliases</code>, and <code>.bash_logout</code> when they differ from the <code>skel</code> defaults.</li>
|
||||||
<li>It takes into account anything the user set with <code>--exclude-path</code> or <code>--include-path</code>. For anything extra that is included, it will put these into an '<code>extra_paths</code>' role. The location could be anywhere e.g something in <code>/opt</code>, <code>/srv</code>, whatever you want.</li>
|
<li>It takes into account anything the user set with <code>--exclude-path</code> or <code>--include-path</code>. For anything extra that is included, it will put these into an '<code>extra_paths</code>' role. The location could be anywhere e.g something in <code>/opt</code>, <code>/srv</code>, whatever you want.</li>
|
||||||
<li>It writes the state.json and captures the artifacts.</li>
|
<li>It writes the state.json and captures the artifacts.</li>
|
||||||
</ul>
|
</ul>
|
||||||
<br />
|
<br />
|
||||||
<p class="text-secondary">Other things to be aware of:</p>
|
<p class="text-secondary">Other things to be aware of:</p>
|
||||||
<ul class="mb-0 small">
|
<ul class="mb-0 small">
|
||||||
<li>You can use multiple invocations of <code>--exclude-path</code> to skip the bits you don't want. You also can always comment out from the playbook.yml or delete certain roles it generates once you've run the <code>enroll manifest</code>.</li>
|
<li>You can use multiple invocations of <code>--exclude-path</code> to skip the bits you don't want. You can also comment out or delete generated playbooks, classes, states, roles, or modules after <code>enroll manifest</code> if you want to trim the first pass.</li>
|
||||||
<li>In terms of safety measures: it doesn't traverse into symlinks, and it has an 'IgnorePolicy' that makes it ignore most binary files (except GPG binary keys used with apt) - though if you specify certain paths with <code>--include-path</code> and use <code>--dangerous</code>, it will skip some policy statements such as what types of content to ignore.</li>
|
<li>In terms of safety measures: it doesn't traverse into symlinks, and it has an 'IgnorePolicy' that makes it ignore most binary files (except GPG binary keys used with apt) - though if you specify certain paths with <code>--include-path</code> and use <code>--dangerous</code>, it will skip some policy statements such as what types of content to ignore.</li>
|
||||||
<li>It will skip files that are too large, and it also currently has a hardcoded cap of the number of files that it will harvest (4000 for <code>/etc</code>, <code>/usr/local/etc</code> and <code>/usr/local/bin</code>, and 500 files per 'role'), to avoid unintentional 'runaway' situations.</li>
|
<li>It will skip files that are too large, and it also currently has a hardcoded cap of the number of files that it will harvest (4000 for <code>/etc</code>, <code>/usr/local/etc</code> and <code>/usr/local/bin</code>, and 500 files per 'role'), to avoid unintentional 'runaway' situations.</li>
|
||||||
<li>If you are using the 'remote' mode to harvest, and your remote user requires a password for sudo, you can pass in <code>--ask-become-pass</code> (or <code>-K</code>) and it will prompt for the password. If you forget, and remote requires password for sudo, it'll still fall back to prompting for a password, but will be a bit slower to do so.</li>
|
<li>If you are using the 'remote' mode to harvest, and your remote user requires a password for sudo, you can pass in <code>--ask-become-pass</code> (or <code>-K</code>) and it will prompt for the password. If you forget, and remote requires password for sudo, it'll still fall back to prompting for a password, but will be a bit slower to do so.</li>
|
||||||
|
|
@ -112,12 +157,12 @@ description: "How Enroll works: harvest, manifest, modes, and configuration."
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div class="alert alert-secondary mt-3 mb-0">
|
<div class="alert alert-secondary mt-3 mb-0">
|
||||||
<div class="fw-semibold">Does Enroll use Ansible community/galaxy roles?</div>
|
<div class="fw-semibold">Does Enroll use community roles/modules?</div>
|
||||||
<div class="small mb-0">No, Enroll doesn't have any knowledge of Ansible Galaxy roles or community plugins. It generates all the roles itself. If you really want to use roles from the community, Enroll may not be the tool for you, other than perhaps to help get you started.</div>
|
<div class="small mb-0">No, Enroll does not pull in Ansible Galaxy roles, Puppet Forge modules, or Salt formulas. It generates its own output from the harvest state. If you want to adopt community content later, Enroll can still help you discover and bootstrap the host-specific state you need to port.</div>
|
||||||
<br />
|
<br />
|
||||||
<div class="small mb-0">Keep in mind that a lot of software config files are also good candidates for being Jinja templates with abstracted vars for separate hosts.</div>
|
<div class="small mb-0">Keep in mind that a lot of software config files are also good candidates for being Jinja templates with abstracted vars for separate hosts.</div>
|
||||||
<br />
|
<br />
|
||||||
<div class="small mb-0">Enroll does use my companion tool <a href="https://git.mig5.net/mig5/jinjaturtle" target="_blank" rel="noopener noreferrer">JinjaTurtle</a> if it's installed, but JinjaTurtle only recognises certain types of files (.ini style, .json, .xml, .yaml, .toml, but not special ones like Nginx or Apache conf files which have their own special syntax). When Enroll can't turn a config file into a template, it copies the raw file instead and uses it with <code>ansible.builtin.copy</code> in role tasks.</div>
|
<div class="small mb-0">Enroll does use my companion tool <a href="https://git.mig5.net/mig5/jinjaturtle" target="_blank" rel="noopener noreferrer">JinjaTurtle</a> if it's installed, but JinjaTurtle only recognises certain types of files (.ini style, .json, .xml, .yaml, .toml, but not special ones like Nginx or Apache conf files which have their own special syntax). When Enroll cannot turn a config file into a template, it copies the raw file instead. JinjaTurtle templating is currently most useful for Ansible output; Puppet and Salt output stay closer to raw managed files and target-native data.</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
@ -125,7 +170,7 @@ description: "How Enroll works: harvest, manifest, modes, and configuration."
|
||||||
<section id="schema" class="scroll-mt-nav mb-5">
|
<section id="schema" class="scroll-mt-nav mb-5">
|
||||||
<h2 class="section-title fw-bold">State schema</h2>
|
<h2 class="section-title fw-bold">State schema</h2>
|
||||||
<p class="text-secondary">Enroll writes a <code>state.json</code> file describing what was harvested. The canonical definition of that file format is the JSON Schema below.</p>
|
<p class="text-secondary">Enroll writes a <code>state.json</code> file describing what was harvested. The canonical definition of that file format is the JSON Schema below.</p>
|
||||||
<p class="text secondary">You can also validate a harvest state file against the schema by using <code>enroll validate /path/to/harvest</code>.</p>
|
<p class="text-secondary">You can also validate a harvest state file against the schema by using <code>enroll validate /path/to/harvest</code>.</p>
|
||||||
|
|
||||||
<div class="callout p-4">
|
<div class="callout p-4">
|
||||||
<div class="d-flex flex-wrap gap-2 align-items-center">
|
<div class="d-flex flex-wrap gap-2 align-items-center">
|
||||||
|
|
@ -137,17 +182,18 @@ description: "How Enroll works: harvest, manifest, modes, and configuration."
|
||||||
|
|
||||||
<section id="modes" class="scroll-mt-nav mb-5">
|
<section id="modes" class="scroll-mt-nav mb-5">
|
||||||
<h2 class="section-title fw-bold">Single-site vs multi-site</h2>
|
<h2 class="section-title fw-bold">Single-site vs multi-site</h2>
|
||||||
<p class="text-secondary">Manifest output has two styles. Choose based on how you'll use the result.</p>
|
<p class="text-secondary">Manifest output has two styles. The exact files differ by target, but the intent is the same.</p>
|
||||||
|
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="callout p-4 h-100">
|
<div class="callout p-4 h-100">
|
||||||
<div class="fw-semibold mb-2"><i class="bi bi-house"></i> Single-site (default)</div>
|
<div class="fw-semibold mb-2"><i class="bi bi-house"></i> Single-site (default)</div>
|
||||||
<p class="small mb-2">Best when you are enrolling one host, or you're producing a reusable "golden" role set that could be applied anywhere.</p>
|
<p class="small mb-2">Best when you are enrolling one host, or producing a reusable baseline that you will review and refine.</p>
|
||||||
<ul class="small mb-0">
|
<ul class="small mb-0">
|
||||||
<li>Roles are self-contained</li>
|
<li>Ansible: self-contained roles and <code>playbook.yml</code></li>
|
||||||
<li>Raw files live in each role's <code>files/</code></li>
|
<li>Puppet: <code>manifests/site.pp</code> plus modules under <code>modules/</code></li>
|
||||||
<li>Template variables live in <code>defaults/main.yml</code></li>
|
<li>Salt: <code>states/top.sls</code> plus states under <code>states/roles/</code></li>
|
||||||
|
<li>Common role grouping is enabled unless <code>--no-common-roles</code> is passed</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -156,23 +202,31 @@ description: "How Enroll works: harvest, manifest, modes, and configuration."
|
||||||
<div class="fw-semibold mb-2"><i class="bi bi-diagram-3"></i> Multi-site (<code>--fqdn</code>)</div>
|
<div class="fw-semibold mb-2"><i class="bi bi-diagram-3"></i> Multi-site (<code>--fqdn</code>)</div>
|
||||||
<p class="small mb-2">Best when you want to enroll several existing servers quickly, especially if they differ.</p>
|
<p class="small mb-2">Best when you want to enroll several existing servers quickly, especially if they differ.</p>
|
||||||
<ul class="small mb-0">
|
<ul class="small mb-0">
|
||||||
<li>Roles are shared; raw files live in host-specific inventory</li>
|
<li>Ansible: host-specific state in <code>inventory/host_vars/<fqdn>/</code></li>
|
||||||
<li>Inventory decides what gets managed on each host (files/packages/services)</li>
|
<li>Puppet: host-specific data in Hiera under <code>data/nodes/<fqdn>.yaml</code></li>
|
||||||
<li>Non-templated files go under <code>inventory/host_vars/<fqdn>/<role>/.files</code></li>
|
<li>Salt: host-specific data in pillar under <code>pillar/nodes/</code></li>
|
||||||
|
<li>Common grouping is disabled so each host's harvested state remains isolated</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="terminal mt-4">
|
<div class="terminal mt-4">
|
||||||
<div class="small text-secondary mb-2">Multi-site example</div>
|
<div class="small text-secondary mb-2">Multi-site noop examples</div>
|
||||||
<pre class="mb-0"><code><span class="prompt">$</span> enroll manifest --harvest /tmp/enroll-harvest --out /tmp/enroll-ansible --fqdn "$(hostname -f)"
|
<pre class="mb-0"><code><span class="prompt">$</span> fqdn="$(hostname -f)"
|
||||||
<span class="prompt">$</span> ansible-playbook /tmp/enroll-ansible/playbooks/"$(hostname -f)".yml</code></pre>
|
<span class="prompt">$</span> enroll manifest --harvest /tmp/enroll-harvest --target ansible --out /tmp/enroll-ansible --fqdn "$fqdn"
|
||||||
|
<span class="prompt">$</span> ansible-playbook /tmp/enroll-ansible/playbooks/${fqdn}.yml -i /tmp/enroll-ansible/inventory/hosts.ini -c local --check --diff
|
||||||
|
|
||||||
|
<span class="prompt">$</span> enroll manifest --harvest /tmp/enroll-harvest --target puppet --out /tmp/enroll-puppet --fqdn "$fqdn"
|
||||||
|
<span class="prompt">$</span> puppet apply --modulepath /tmp/enroll-puppet/modules --hiera_config /tmp/enroll-puppet/hiera.yaml --certname "$fqdn" /tmp/enroll-puppet/manifests/site.pp --noop
|
||||||
|
|
||||||
|
<span class="prompt">$</span> enroll manifest --harvest /tmp/enroll-harvest --target salt --out /tmp/enroll-salt --fqdn "$fqdn"
|
||||||
|
<span class="prompt">$</span> salt-call --local --id "$fqdn" --file-root /tmp/enroll-salt/states --pillar-root /tmp/enroll-salt/pillar state.apply test=True</code></pre>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="alert alert-secondary mt-3 mb-0">
|
<div class="alert alert-secondary mt-3 mb-0">
|
||||||
<div class="fw-semibold">Tip: role tags</div>
|
<div class="fw-semibold">Tip: targeting subsets</div>
|
||||||
<div class="small mb-0">Generated playbooks tag each role as <code>role_<name></code> (e.g. <code>role_users</code>, <code>role_services</code>, <code>role_other</code>). You can target a subset with <code>ansible-playbook ... --tags role_users</code>.</div>
|
<div class="small mb-0">Ansible playbooks tag each role as <code>role_<name></code>. Puppet and Salt output can be trimmed by editing generated class/state inclusion data. In every target, treat the first generated manifest as a reviewable bootstrap rather than magic.</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
@ -181,12 +235,12 @@ description: "How Enroll works: harvest, manifest, modes, and configuration."
|
||||||
<p class="text-secondary">Run Enroll on your workstation, harvest a remote host over SSH. The harvest is pulled locally.</p>
|
<p class="text-secondary">Run Enroll on your workstation, harvest a remote host over SSH. The harvest is pulled locally.</p>
|
||||||
<div class="terminal">
|
<div class="terminal">
|
||||||
<pre class="mb-0"><code><span class="prompt">$</span> enroll harvest --remote-host myhost.example.com --remote-user myuser --out /tmp/enroll-harvest
|
<pre class="mb-0"><code><span class="prompt">$</span> enroll harvest --remote-host myhost.example.com --remote-user myuser --out /tmp/enroll-harvest
|
||||||
<span class="prompt">$</span> enroll manifest --harvest /tmp/enroll-harvest --out /tmp/enroll-manifest
|
<span class="prompt">$</span> enroll manifest --harvest /tmp/enroll-harvest --target ansible --out /tmp/enroll-ansible
|
||||||
|
|
||||||
# Alternatively, run both commands combined together with the 'single-shot' mode:
|
# Alternatively, run both commands combined together with the 'single-shot' mode:
|
||||||
|
|
||||||
<span class="prompt">$</span> enroll single-shot --remote-host myhost.example.com --remote-user myuser \
|
<span class="prompt">$</span> enroll single-shot --remote-host myhost.example.com --remote-user myuser \
|
||||||
--harvest /tmp/enroll-harvest --out /tmp/enroll-ansible \
|
--harvest /tmp/enroll-harvest --target ansible --out /tmp/enroll-ansible \
|
||||||
--fqdn myhost.example.com
|
--fqdn myhost.example.com
|
||||||
|
|
||||||
# Encrypted SSH key examples:
|
# Encrypted SSH key examples:
|
||||||
|
|
@ -211,7 +265,7 @@ description: "How Enroll works: harvest, manifest, modes, and configuration."
|
||||||
|
|
||||||
<section id="templates" class="scroll-mt-nav mb-5">
|
<section id="templates" class="scroll-mt-nav mb-5">
|
||||||
<h2 class="section-title fw-bold">JinjaTurtle integration</h2>
|
<h2 class="section-title fw-bold">JinjaTurtle integration</h2>
|
||||||
<p class="text-secondary">If <a href="https://git.mig5.net/mig5/jinjaturtle" target="_blank" rel="noopener noreferrer">JinjaTurtle</a> (one of my other projects) is installed, Enroll can also produce Jinja2 templates for ini/json/xml/toml-style config and extract variables cleanly into Ansible, instead of just storing the 'raw' files.</p>
|
<p class="text-secondary">If <a href="https://git.mig5.net/mig5/jinjaturtle" target="_blank" rel="noopener noreferrer">JinjaTurtle</a> (one of my other projects) is installed, Enroll can also produce Jinja2 templates for ini/json/xml/toml-style config and extract variables cleanly into Ansible, instead of just storing the 'raw' files. Puppet and Salt output currently focus on raw managed files plus target-native data.</p>
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="feature-card p-4 h-100">
|
<div class="feature-card p-4 h-100">
|
||||||
|
|
@ -227,8 +281,8 @@ description: "How Enroll works: harvest, manifest, modes, and configuration."
|
||||||
<div class="feature-card p-4 h-100">
|
<div class="feature-card p-4 h-100">
|
||||||
<div class="fw-semibold mb-2">Where variables land</div>
|
<div class="fw-semibold mb-2">Where variables land</div>
|
||||||
<ul class="small mb-0">
|
<ul class="small mb-0">
|
||||||
<li>Single-site: <code>roles/<role>/defaults/main.yml</code></li>
|
<li>Ansible single-site: <code>roles/<role>/defaults/main.yml</code></li>
|
||||||
<li>Multi-site: <code>inventory/host_vars/<fqdn>/<role>.yml</code></li>
|
<li>Ansible multi-site: <code>inventory/host_vars/<fqdn>/<role>.yml</code></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -279,7 +333,7 @@ ignore_package_versions = true
|
||||||
|
|
||||||
<section id="diff" class="scroll-mt-nav mb-5">
|
<section id="diff" class="scroll-mt-nav mb-5">
|
||||||
<h2 class="section-title fw-bold">Drift detection with <code>enroll diff</code></h2>
|
<h2 class="section-title fw-bold">Drift detection with <code>enroll diff</code></h2>
|
||||||
<p class="text-secondary">One of the things I miss from my Puppet days, was the way the Puppet 'agent' would check in with the server and realign itself to the declared desired state. With Ansible, it's easy for systems to fall 'out of date', especially if someone is doing the wrong thing and changing things on-the-fly instead of via config management!</p>
|
<p class="text-secondary">One of the things I miss from my Puppet days, was the way the Puppet 'agent' would check in with the server and realign itself to the declared desired state. With any configuration-management system, it is easy for systems to fall out of date if someone changes things on-the-fly instead of via the desired-state workflow.</p>
|
||||||
<p class="text-secondary">The purpose of <code>enroll diff</code> is to compare two 'harvests' and detect what has changed - be it adding/removing of programs, change to systemd unit state, modifications, addition or removal of files, and so on.</p>
|
<p class="text-secondary">The purpose of <code>enroll diff</code> is to compare two 'harvests' and detect what has changed - be it adding/removing of programs, change to systemd unit state, modifications, addition or removal of files, and so on.</p>
|
||||||
|
|
||||||
<div class="callout p-4 mb-3">
|
<div class="callout p-4 mb-3">
|
||||||
|
|
@ -302,7 +356,7 @@ ignore_package_versions = true
|
||||||
|
|
||||||
<div class="callout p-4 mb-3">
|
<div class="callout p-4 mb-3">
|
||||||
<div class="fw-semibold mb-1">Optional: enforce the old harvest state (<code>--enforce</code>)</div>
|
<div class="fw-semibold mb-1">Optional: enforce the old harvest state (<code>--enforce</code>)</div>
|
||||||
<div class="small text-secondary mb-0">If drift exists and <code>ansible-playbook</code> is on <code>PATH</code>, Enroll can generate a manifest from the <em>old</em> harvest and apply it locally to restore expected state. It avoids package downgrades, and will often run Ansible with <code>--tags role_...</code> so only the roles implicated by the drift are applied. This is very much like a return to Puppet's agent mode!</div>
|
<div class="small text-secondary mb-0">If drift exists and <code>ansible-playbook</code> is on <code>PATH</code>, Enroll can generate a manifest from the <em>old</em> harvest and apply it locally to restore expected state. It avoids package downgrades, and will often run Ansible with <code>--tags role_...</code> so only the roles implicated by the drift are applied. This is intentionally Ansible-based today, even when you also use Puppet or Salt rendering for normal manifests.</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="terminal mb-4">
|
<div class="terminal mb-4">
|
||||||
|
|
@ -561,7 +615,7 @@ sudo journalctl -u enroll-harvest-diff.service -n 200 --no-pager
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="feature-card p-4 h-100">
|
<div class="feature-card p-4 h-100">
|
||||||
<div class="fw-semibold mb-2"><i class="bi bi-diagram-2"></i> Multi-host safety</div>
|
<div class="fw-semibold mb-2"><i class="bi bi-diagram-2"></i> Multi-host safety</div>
|
||||||
<p class="small text-secondary mb-0">For fleets, prefer multi-site output so roles stay generic and host inventory controls what is applied per host - reducing "shared role breaks other host" surprises.</p>
|
<p class="small text-secondary mb-0">For fleets, prefer multi-site output so target-native host data controls what is applied per host - reducing "shared role/module/state breaks another host" surprises.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
---
|
---
|
||||||
title: "Examples"
|
title: "Examples"
|
||||||
html_title: "Enroll Examples"
|
html_title: "Enroll Examples"
|
||||||
description: "Copy/paste recipes for Enroll: one host, fleets, drift detection, and safe storage."
|
description: "Copy/paste recipes for Enroll: Ansible, Puppet, Salt, fleets, drift detection, and safe storage."
|
||||||
---
|
---
|
||||||
<header class="py-5 hero">
|
<header class="py-5 hero">
|
||||||
<div class="container py-3">
|
<div class="container py-3">
|
||||||
<div class="kicker mb-3"><i class="bi bi-terminal"></i> Examples</div>
|
<div class="kicker mb-3"><i class="bi bi-terminal"></i> Examples</div>
|
||||||
<h1 class="display-6 fw-bold mb-2">Copy/paste recipes</h1>
|
<h1 class="display-6 fw-bold mb-2">Copy/paste recipes</h1>
|
||||||
<p class="lead mb-0">Practical flows you can adapt to your environment.</p>
|
<p class="lead mb-0">Practical flows you can adapt to Ansible, Puppet, Salt, or all three.</p>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
|
@ -17,49 +17,85 @@ description: "Copy/paste recipes for Enroll: one host, fleets, drift detection,
|
||||||
<div class="row g-4">
|
<div class="row g-4">
|
||||||
<div class="col-lg-6">
|
<div class="col-lg-6">
|
||||||
<div class="feature-card p-4 h-100">
|
<div class="feature-card p-4 h-100">
|
||||||
<div class="fw-semibold mb-2">Enroll a single host (local)</div>
|
<div class="fw-semibold mb-2">Harvest once, try each renderer locally</div>
|
||||||
<div class="codeblock terminal">
|
<div class="codeblock terminal">
|
||||||
<button class="btn btn-sm btn-outline-secondary copy-btn" data-copy-target="#ex-single-local"><i class="bi bi-clipboard"></i> Copy</button>
|
<button class="btn btn-sm btn-outline-secondary copy-btn" data-copy-target="#ex-single-local"><i class="bi bi-clipboard"></i> Copy</button>
|
||||||
<pre class="mb-0"><code id="ex-single-local"><span class="prompt">$</span> enroll harvest --out /tmp/enroll-harvest
|
<pre class="mb-0"><code id="ex-single-local"><span class="prompt">$</span> enroll harvest --out /tmp/enroll-harvest
|
||||||
<span class="prompt">$</span> enroll manifest --harvest /tmp/enroll-harvest \
|
|
||||||
--out /tmp/enroll-ansible
|
# Ansible noop
|
||||||
<span class="prompt">$</span> ansible-playbook -i "localhost," -c local \
|
<span class="prompt">$</span> enroll manifest --harvest /tmp/enroll-harvest --target ansible --out /tmp/enroll-ansible
|
||||||
/tmp/enroll-ansible/playbook.yml --diff --check</code></pre>
|
<span class="prompt">$</span> ansible-playbook -i "localhost," -c local /tmp/enroll-ansible/playbook.yml --diff --check
|
||||||
|
|
||||||
|
# Puppet noop
|
||||||
|
<span class="prompt">$</span> enroll manifest --harvest /tmp/enroll-harvest --target puppet --out /tmp/enroll-puppet
|
||||||
|
<span class="prompt">$</span> puppet apply --modulepath /tmp/enroll-puppet/modules /tmp/enroll-puppet/manifests/site.pp --noop
|
||||||
|
|
||||||
|
# Salt noop
|
||||||
|
<span class="prompt">$</span> enroll manifest --harvest /tmp/enroll-harvest --target salt --out /tmp/enroll-salt
|
||||||
|
<span class="prompt">$</span> salt-call --local --file-root /tmp/enroll-salt/states state.apply test=True</code></pre>
|
||||||
</div>
|
</div>
|
||||||
<p class="small text-secondary mt-2 mb-0">Great for "make this box reproducible" or building a golden role set.</p>
|
<p class="small text-secondary mt-2 mb-0">Great for “make this box reproducible”, testing renderer output, or building a golden baseline you will refine.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-lg-6">
|
<div class="col-lg-6">
|
||||||
<div class="feature-card p-4 h-100">
|
<div class="feature-card p-4 h-100">
|
||||||
<div class="fw-semibold mb-2">Enroll a remote host (over SSH)</div>
|
<div class="fw-semibold mb-2">Enroll a remote host over SSH</div>
|
||||||
<div class="codeblock terminal">
|
<div class="codeblock terminal">
|
||||||
<button class="btn btn-sm btn-outline-secondary copy-btn" data-copy-target="#ex-remote"><i class="bi bi-clipboard"></i> Copy</button>
|
<button class="btn btn-sm btn-outline-secondary copy-btn" data-copy-target="#ex-remote"><i class="bi bi-clipboard"></i> Copy</button>
|
||||||
<pre class="mb-0"><code id="ex-remote"><span class="prompt">$</span> enroll harvest \
|
<pre class="mb-0"><code id="ex-remote"><span class="prompt">$</span> enroll harvest \
|
||||||
--remote-host myhost.example.com \
|
--remote-host myhost.example.com \
|
||||||
--remote-user myuser \
|
--remote-user myuser \
|
||||||
--out /tmp/enroll-harvest
|
--out /tmp/enroll-harvest
|
||||||
|
|
||||||
<span class="prompt">$</span> enroll manifest \
|
<span class="prompt">$</span> enroll manifest \
|
||||||
--harvest /tmp/enroll-harvest \
|
--harvest /tmp/enroll-harvest \
|
||||||
--out /tmp/enroll-ansible</code></pre>
|
--target puppet \
|
||||||
|
--out /tmp/enroll-puppet</code></pre>
|
||||||
</div>
|
</div>
|
||||||
<p class="small text-secondary mt-2 mb-0">No need to manually run commands on the server - your bundle lands locally. If your remote user needs a password for sudo, pass in <code>--ask-become-pass</code> or <code>-K</code>, just like in Ansible. If you don't want to use sudo, pass <code>--no-sudo</code>, but your harvest may contain less data.</p>
|
<p class="small text-secondary mt-2 mb-0">The bundle lands locally. Change <code>--target puppet</code> to <code>ansible</code> or <code>salt</code> without re-harvesting. For remote sudo prompts use <code>--ask-become-pass</code>/<code>-K</code>; use <code>--no-sudo</code> for a less complete non-root harvest.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-lg-6">
|
<div class="col-lg-6">
|
||||||
<div class="feature-card p-4 h-100">
|
<div class="feature-card p-4 h-100">
|
||||||
<div class="fw-semibold mb-2">Fleets: multi-site output</div>
|
<div class="fw-semibold mb-2">Fleets: <code>--fqdn</code> output</div>
|
||||||
<div class="codeblock terminal">
|
<div class="codeblock terminal">
|
||||||
<button class="btn btn-sm btn-outline-secondary copy-btn" data-copy-target="#ex-multisite"><i class="bi bi-clipboard"></i> Copy</button>
|
<button class="btn btn-sm btn-outline-secondary copy-btn" data-copy-target="#ex-multisite"><i class="bi bi-clipboard"></i> Copy</button>
|
||||||
<pre class="mb-0"><code id="ex-multisite"><span class="prompt">$</span> fqdn="$(hostname -f)"
|
<pre class="mb-0"><code id="ex-multisite"><span class="prompt">$</span> fqdn="$(hostname -f)"
|
||||||
<span class="prompt">$</span> enroll single-shot --remote-host "$fqdn" \
|
<span class="prompt">$</span> enroll harvest --out /tmp/enroll-harvest
|
||||||
--remote-user myuser \
|
|
||||||
--out /tmp/enroll-ansible \
|
# Ansible: inventory/host_vars and a per-host playbook
|
||||||
--fqdn "$fqdn"
|
<span class="prompt">$</span> enroll manifest --harvest /tmp/enroll-harvest --target ansible --out /tmp/enroll-ansible --fqdn "$fqdn"
|
||||||
<span class="prompt">$</span> ansible-playbook "/tmp/enroll-ansible/playbooks/${fqdn}.yml"</code></pre>
|
<span class="prompt">$</span> ansible-playbook /tmp/enroll-ansible/playbooks/${fqdn}.yml -i /tmp/enroll-ansible/inventory/hosts.ini -c local --check --diff
|
||||||
|
|
||||||
|
# Puppet: Hiera data keyed by certname
|
||||||
|
<span class="prompt">$</span> enroll manifest --harvest /tmp/enroll-harvest --target puppet --out /tmp/enroll-puppet --fqdn "$fqdn"
|
||||||
|
<span class="prompt">$</span> puppet apply --modulepath /tmp/enroll-puppet/modules --hiera_config /tmp/enroll-puppet/hiera.yaml --certname "$fqdn" /tmp/enroll-puppet/manifests/site.pp --noop
|
||||||
|
|
||||||
|
# Salt: pillar data keyed by minion id
|
||||||
|
<span class="prompt">$</span> enroll manifest --harvest /tmp/enroll-harvest --target salt --out /tmp/enroll-salt --fqdn "$fqdn"
|
||||||
|
<span class="prompt">$</span> salt-call --local --id "$fqdn" --file-root /tmp/enroll-salt/states --pillar-root /tmp/enroll-salt/pillar state.apply test=True</code></pre>
|
||||||
</div>
|
</div>
|
||||||
<p class="small text-secondary mt-2 mb-0">Shared roles + host inventory keeps one host's differences from breaking another.</p>
|
<p class="small text-secondary mt-2 mb-0"><code>--fqdn</code> disables common grouping and moves host-specific state into the target’s native host-data layer: inventory, Hiera, or pillar.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-lg-6">
|
||||||
|
<div class="feature-card p-4 h-100">
|
||||||
|
<div class="fw-semibold mb-2">Control role/module/state grouping</div>
|
||||||
|
<div class="codeblock terminal">
|
||||||
|
<button class="btn btn-sm btn-outline-secondary copy-btn" data-copy-target="#ex-grouping"><i class="bi bi-clipboard"></i> Copy</button>
|
||||||
|
<pre class="mb-0"><code id="ex-grouping"># Default single-site mode: combine packages/services by package Section where possible
|
||||||
|
<span class="prompt">$</span> enroll manifest --harvest /tmp/enroll-harvest --target ansible --out /tmp/enroll-ansible
|
||||||
|
|
||||||
|
# Keep output split more literally by discovered package/service role
|
||||||
|
<span class="prompt">$</span> enroll manifest --harvest /tmp/enroll-harvest --target ansible --out /tmp/enroll-ansible-raw --no-common-roles
|
||||||
|
|
||||||
|
# --fqdn also disables common grouping because host data must remain isolated
|
||||||
|
<span class="prompt">$</span> enroll manifest --harvest /tmp/enroll-harvest --target salt --out /tmp/enroll-salt --fqdn "$(hostname -f)"</code></pre>
|
||||||
|
</div>
|
||||||
|
<p class="small text-secondary mt-2 mb-0">Grouping keeps first-pass output manageable. Use <code>--no-common-roles</code> when you prefer more direct one-role-per-package/service output.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -68,15 +104,19 @@ description: "Copy/paste recipes for Enroll: one host, fleets, drift detection,
|
||||||
<div class="fw-semibold mb-2">Drift detection with <code>enroll diff</code></div>
|
<div class="fw-semibold mb-2">Drift detection with <code>enroll diff</code></div>
|
||||||
<div class="codeblock terminal">
|
<div class="codeblock terminal">
|
||||||
<button class="btn btn-sm btn-outline-secondary copy-btn" data-copy-target="#ex-diff"><i class="bi bi-clipboard"></i> Copy</button>
|
<button class="btn btn-sm btn-outline-secondary copy-btn" data-copy-target="#ex-diff"><i class="bi bi-clipboard"></i> Copy</button>
|
||||||
<pre class="mb-0"><code id="ex-diff"><span class="prompt">$</span> enroll diff --old /path/to/harvestA --new /path/to/harvestB --format markdown --exclude-path /var/anacron --ignore-package-versions
|
<pre class="mb-0"><code id="ex-diff"><span class="prompt">$</span> enroll diff --old /path/to/harvestA --new /path/to/harvestB \
|
||||||
|
--format markdown \
|
||||||
|
--exclude-path /var/anacron \
|
||||||
|
--ignore-package-versions
|
||||||
|
|
||||||
<span class="prompt">$</span> enroll diff --old /path/to/golden --new /path/to/current \
|
<span class="prompt">$</span> enroll diff --old /path/to/golden --new /path/to/current \
|
||||||
--webhook https://example.net/webhook \
|
--webhook https://example.net/webhook \
|
||||||
--webhook-format json \
|
--webhook-format json \
|
||||||
--webhook-header 'X-Enroll-Secret: ...' \
|
--webhook-header 'X-Enroll-Secret: ...' \
|
||||||
--ignore-package-versions --exit-code
|
--ignore-package-versions \
|
||||||
</code></pre>
|
--exit-code</code></pre>
|
||||||
</div>
|
</div>
|
||||||
<p class="small text-secondary mt-2 mb-0">Use it in cron or CI to alert on change.</p>
|
<p class="small text-secondary mt-2 mb-0">Use it in cron, systemd timers, or CI to alert on change.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -87,13 +127,13 @@ description: "Copy/paste recipes for Enroll: one host, fleets, drift detection,
|
||||||
<button class="btn btn-sm btn-outline-secondary copy-btn" data-copy-target="#ex-explain"><i class="bi bi-clipboard"></i> Copy</button>
|
<button class="btn btn-sm btn-outline-secondary copy-btn" data-copy-target="#ex-explain"><i class="bi bi-clipboard"></i> Copy</button>
|
||||||
<pre class="mb-0"><code id="ex-explain"><span class="prompt">$</span> enroll explain /tmp/enroll-harvest
|
<pre class="mb-0"><code id="ex-explain"><span class="prompt">$</span> enroll explain /tmp/enroll-harvest
|
||||||
|
|
||||||
# machine-readable (reasons, examples, inventory breakdown)
|
# machine-readable reasons, examples, and inventory breakdown
|
||||||
<span class="prompt">$</span> enroll explain /tmp/enroll-harvest --format json | jq .
|
<span class="prompt">$</span> enroll explain /tmp/enroll-harvest --format json | jq .
|
||||||
|
|
||||||
# encrypted bundle
|
# encrypted bundle
|
||||||
<span class="prompt">$</span> enroll explain /var/lib/enroll/harvest.tar.gz.sops --sops</code></pre>
|
<span class="prompt">$</span> enroll explain /var/lib/enroll/harvest.tar.gz.sops --sops</code></pre>
|
||||||
</div>
|
</div>
|
||||||
<p class="small text-secondary mt-2 mb-0">Great for answering "why did it include/exclude that file?" before you generate a manifest.</p>
|
<p class="small text-secondary mt-2 mb-0">Great for answering “why did it include/exclude that file?” before you generate a manifest.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -101,39 +141,31 @@ description: "Copy/paste recipes for Enroll: one host, fleets, drift detection,
|
||||||
<div class="feature-card p-4 h-100">
|
<div class="feature-card p-4 h-100">
|
||||||
<div class="fw-semibold mb-2">Enforce the previous state with <code>enroll diff --enforce</code></div>
|
<div class="fw-semibold mb-2">Enforce the previous state with <code>enroll diff --enforce</code></div>
|
||||||
<div class="codeblock terminal">
|
<div class="codeblock terminal">
|
||||||
<button class="btn btn-sm btn-outline-secondary copy-btn" data-copy-target="#ex-diff"><i class="bi bi-clipboard"></i> Copy</button>
|
<button class="btn btn-sm btn-outline-secondary copy-btn" data-copy-target="#ex-enforce"><i class="bi bi-clipboard"></i> Copy</button>
|
||||||
<pre class="mb-0"><code id="ex-diff"><span class="prompt">$</span> enroll diff \
|
<pre class="mb-0"><code id="ex-enforce"><span class="prompt">$</span> enroll diff \
|
||||||
--old /path/to/harvestA \
|
--old /path/to/harvestA \
|
||||||
--new /path/to/harvestB \
|
--new /path/to/harvestB \
|
||||||
--enforce \
|
--enforce \
|
||||||
--format json
|
--format json</code></pre>
|
||||||
</code></pre>
|
|
||||||
</div>
|
|
||||||
<p class="small text-secondary mt-2 mb-0">Enforcing the old harvest will restore its files/perms, missing packages, changed services or users, if <code>ansible-playbook</code> is on the PATH.</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
<p class="small text-secondary mt-2 mb-0">Enforcement currently uses generated Ansible and <code>ansible-playbook</code> on <code>PATH</code>, even if you also use Puppet or Salt for normal manifest output.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr class="my-5">
|
|
||||||
|
|
||||||
<div class="row g-4">
|
|
||||||
<div class="col-lg-6">
|
<div class="col-lg-6">
|
||||||
<div class="callout p-4 h-100">
|
<div class="feature-card p-4 h-100">
|
||||||
<div class="fw-semibold mb-2"><i class="bi bi-shield-check"></i> Safe harvesting (default)</div>
|
<div class="fw-semibold mb-2">Encrypt bundles at rest with SOPS</div>
|
||||||
<p class="small text-secondary mb-3">Enroll tries to avoid harvesting files that might contain secrets. If you need to capture "everything", pass <code>--dangerous</code> and treat the output as sensitive.</p>
|
<div class="codeblock terminal">
|
||||||
<p class="small text-secondary mb-3">You can still control what gets collected and what doesn't by using <code>--include</code> and <code>--exclude</code> flags.</p>
|
<button class="btn btn-sm btn-outline-secondary copy-btn" data-copy-target="#ex-sops"><i class="bi bi-clipboard"></i> Copy</button>
|
||||||
<div class="terminal"><pre class="mb-0"><code><span class="prompt">$</span> enroll harvest --dangerous --out /tmp/enroll-harvest</code></pre></div>
|
<pre class="mb-0"><code id="ex-sops"><span class="prompt">$</span> enroll harvest --dangerous --out /tmp/harvest \
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-lg-6">
|
|
||||||
<div class="callout p-4 h-100">
|
|
||||||
<div class="fw-semibold mb-2"><i class="bi bi-lock"></i> Encrypt bundles at rest (SOPS)</div>
|
|
||||||
<p class="small text-secondary mb-3">Produce a single encrypted file for harvest and/or manifest output (requires SOPS to be installed).</p>
|
|
||||||
<p class="small text-secondary mb-3">This is especially a good idea if you are using <code>--dangerous</code>, which might sweep up secrets (see above).</p>
|
|
||||||
<div class="terminal"><pre class="mb-0"><code><span class="prompt">$</span> enroll harvest --dangerous --out /tmp/harvest \
|
|
||||||
--sops <FINGERPRINT>
|
--sops <FINGERPRINT>
|
||||||
|
|
||||||
<span class="prompt">$</span> enroll manifest --harvest /tmp/harvest/harvest.tar.gz.sops \
|
<span class="prompt">$</span> enroll manifest --harvest /tmp/harvest/harvest.tar.gz.sops \
|
||||||
--out /tmp/enroll-ansible --sops <FINGERPRINT></code></pre></div>
|
--target salt \
|
||||||
|
--out /tmp/enroll-salt \
|
||||||
|
--sops <FINGERPRINT></code></pre>
|
||||||
|
</div>
|
||||||
|
<p class="small text-secondary mt-2 mb-0">Use this if you intend to store harvests/manifests long term or if you harvested with <code>--dangerous</code>.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
115
src/content/news/0-7-0.html
Normal file
115
src/content/news/0-7-0.html
Normal file
|
|
@ -0,0 +1,115 @@
|
||||||
|
---
|
||||||
|
title: "Enroll 0.7.0: Puppet and Salt renderer support, and more"
|
||||||
|
html_title: "Enroll 0.7.0 News"
|
||||||
|
description: "Enroll 0.7.0 adds first-class Puppet and Salt manifest rendering alongside Ansible, along with other improvements."
|
||||||
|
date: 2026-06-17
|
||||||
|
summary: "Enroll can now render Puppet and Salt manifests alongside Ansible, detect Flatpak and Snap packages and other obscure runtime state, and produce better organised roles/modules."
|
||||||
|
---
|
||||||
|
<header class="py-5 hero">
|
||||||
|
<div class="container py-3">
|
||||||
|
<div class="kicker mb-3"><i class="bi bi-stars"></i> Release notes</div>
|
||||||
|
<h1 class="display-6 fw-bold mb-2">Enroll 0.7.0: Puppet and Salt join Ansible</h1>
|
||||||
|
<p class="lead mb-0">A single harvest can now be rendered into Ansible, Puppet, or Salt output.</p>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="py-5">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row g-4">
|
||||||
|
<div class="col-lg-8">
|
||||||
|
<article class="feature-card p-4">
|
||||||
|
<p class="text-secondary">Welcome to the first of Enroll's new, erm, news section! To celebrate, Enroll 0.7.0 has been released, and makes manifest rendering target-selectable. Ansible remains the default, but Puppet and Salt are now first-class manifest targets rather than ad-hoc exports.</p>
|
||||||
|
|
||||||
|
<h2 class="h4 fw-bold mt-4">Highlights</h2>
|
||||||
|
<ul>
|
||||||
|
<li><code>--target puppet</code> renders Puppet module/control-repo style output., and in <code>--fqdn</code> mode, renders per-host Hiera data.</li>
|
||||||
|
<li><code>--target salt</code> renders Salt state trees and, in <code>--fqdn</code> mode, Salt pillar data.</li>
|
||||||
|
<li>Ansible works basically as it always did, and is the default, but you can specify <code>--target ansible</code> too. As usual, in <code>--fqdn</code> mode, specific artifacts end up in <code>host_vars</code> inventory folders rather than polluting the 'golden' roles.</li>
|
||||||
|
<li>All three config management manifest renderers derive from the same harvest state. You can rendered repeatedly into different config management tools without re-harvesting the host!</li>
|
||||||
|
<li>Single-site output tries to combine package/service data by their package manager's <code>Section</code> (or equivalent metadata), to reduce role/module/state sprawl.</li>
|
||||||
|
<li>Flatpak and Snap detection!</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2 class="h4 fw-bold mt-4">Dry-run examples</h2>
|
||||||
|
<div class="codeblock terminal">
|
||||||
|
<button class="btn btn-sm btn-outline-secondary copy-btn" data-copy-target="#news-070-noop"><i class="bi bi-clipboard"></i> Copy</button>
|
||||||
|
<pre class="mb-0"><code id="news-070-noop"><span class="prompt">$</span> enroll harvest --out ./harvest
|
||||||
|
|
||||||
|
<span class="prompt">$</span> enroll manifest --harvest ./harvest --target ansible --out ./ansible
|
||||||
|
<span class="prompt">$</span> ansible-playbook -i "localhost," -c local ./ansible/playbook.yml --check --diff
|
||||||
|
|
||||||
|
<span class="prompt">$</span> enroll manifest --harvest ./harvest --target puppet --out ./puppet
|
||||||
|
<span class="prompt">$</span> puppet apply --modulepath ./puppet/modules ./puppet/manifests/site.pp --noop
|
||||||
|
|
||||||
|
<span class="prompt">$</span> enroll manifest --harvest ./harvest --target salt --out ./salt
|
||||||
|
<span class="prompt">$</span> salt-call --local --file-root ./salt/states state.apply test=True</code></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 class="h4 fw-bold mt-4">New grouping behaviour in roles/modules</h2>
|
||||||
|
<p class="text-secondary">Did you find the number of manifested roles overwhelming?</p>
|
||||||
|
<p>Previously, Enroll created an Ansible role (or, now, a Puppet module or Salt role) for pretty much every 'package' it found. In some cases (especially on desktops) this could result in hundreds of roles. Technically fine, but overwhelming to look at!</p>
|
||||||
|
|
||||||
|
<p>As of 0.7.0, where Enroll can read that package metadata, it groups related package and service snapshots by the package manager's <code>Section</code> category (or comparable backend metadata), to make it less noisy. For example, network-related packages and config files might end up in a role called <code>net</code>. Meanwhile, vim, nano might both appear in <code>editors</code>, and mutt and Thunderbird may be in <code>mail</code>.</p>
|
||||||
|
|
||||||
|
<p>If you're not a fan of this new layout, you can pass <code>--no-common-roles</code> to enforce the previous behaviour. Also, if you use <code>--fqdn</code> for host-specific data-driven output, the 'common' roles are disabled automatically, because it's then safer to avoid 'bleed in' of unnecessary package installation on other hosts from a role that otherwise 'assumes too much' for all hosts.</p>
|
||||||
|
|
||||||
|
<h2 class="h4 fw-bold mt-4">Flatpak and Snap detection</h2>
|
||||||
|
<p class="text-secondary">Beyond deb and rpm</p>
|
||||||
|
<p>When using Ansible, Enroll now attempts to detect Flatpak and Snaps present on the system. For Flatpaks, this includes user-specific Flatpaks as well as system-wide ones. Manifesting to Ansible will attempt to use the <code>community.general</code> collection to create <a href="https://galaxy.ansible.com/ui/repo/published/community/general/content/module/flatpak/" target="_blank" rel="noopener noreferrer">Flatpak</a> and <a href="https://galaxy.ansible.com/ui/repo/published/community/general/content/module/snap/" target="_blank" rel="noopener noreferrer">Snap</a> tasks to enforce the presence of those packages.</p>
|
||||||
|
<div class="codeblock terminal">
|
||||||
|
<pre class="mb-0"><code id="news-070-snap"><span class="prompt">$</span> sudo ansible-playbook playbook.yml -i localhost, -c local --tags role_snap --diff
|
||||||
|
|
||||||
|
PLAY [Apply all roles on all hosts] *******************************************************************************************************************************************************************************
|
||||||
|
|
||||||
|
TASK [Gathering Facts] ********************************************************************************************************************************************************************************************
|
||||||
|
ok: [localhost]
|
||||||
|
|
||||||
|
TASK [snap : Install system-wide snaps with full detected attributes] *********************************************************************************************************************************************
|
||||||
|
ok: [localhost] => (item={'channel': 'latest/stable', 'classic': False, 'dangerous': False, 'devmode': False, 'install_revision': False, 'name': 'bare', 'notes': ['base'], 'revision': 5, 'source': 'snap-list'})
|
||||||
|
ok: [localhost] => (item={'channel': 'latest/stable', 'classic': False, 'dangerous': False, 'devmode': False, 'install_revision': False, 'name': 'core24', 'notes': ['base'], 'revision': 1643, 'source': 'snap-list'})
|
||||||
|
ok: [localhost] => (item={'channel': 'latest/stable', 'classic': False, 'dangerous': False, 'devmode': False, 'install_revision': False, 'name': 'gnome-46-2404', 'notes': [], 'revision': 153, 'source': 'snap-list'})
|
||||||
|
ok: [localhost] => (item={'channel': 'latest/stable', 'classic': False, 'dangerous': False, 'devmode': False, 'install_revision': False, 'name': 'gtk-common-themes', 'notes': [], 'revision': 1535, 'source': 'snap-list'})
|
||||||
|
ok: [localhost] => (item={'channel': 'latest/stable', 'classic': False, 'dangerous': False, 'devmode': False, 'install_revision': False, 'name': 'mesa-2404', 'notes': [], 'revision': 1165, 'source': 'snap-list'})
|
||||||
|
ok: [localhost] => (item={'channel': 'latest/stable', 'classic': False, 'dangerous': False, 'devmode': False, 'install_revision': False, 'name': 'onionshare', 'notes': [], 'revision': 212, 'source': 'snap-list'})
|
||||||
|
ok: [localhost] => (item={'channel': 'latest/stable', 'classic': False, 'dangerous': False, 'devmode': False, 'install_revision': False, 'name': 'snapd', 'notes': ['snapd'], 'revision': 26865, 'source': 'snap-list'})
|
||||||
|
</code></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>Please be aware that the Puppet and Salt renderers do not support the Flatpak/Snap package enforcement - only Ansible for now. You'll also need version 13+ of the <code>community.general</code> collection for this to work properly. A <code>requirements.yml</code> gets created with your manifest to help you install it if necessary.</p>
|
||||||
|
|
||||||
|
<h2 class="h4 fw-bold mt-4">Other smaller changes</h2>
|
||||||
|
<ul>
|
||||||
|
<li><code>sysctl</code> runtime parameters are now detected and would be written to <code>/etc/sysctl.d/99-enroll.conf</code>. Not all runtime parameters are supported.</li>
|
||||||
|
<li><code>.bashrc</code> and similar files are now only harvested from user directories when <code>--dangerous</code> is used, since this is a common place for sensitive environment variables to be set. As always, remember that <code>--dangerous</code> gives better harvest coverage, but you should use <code>--sops</code> or some other means of your own to encrypt the harvested data at rest safely!</li>
|
||||||
|
<li>Some output during an Ansible play is hidden with <code>no_log</code> to avoid potentially sensitive output, particularly of systemd unit state.</li>
|
||||||
|
<li>In case you missed it in version 0.6.0: Enroll now harvests runtime <code>iptables</code> and <code>ipset</code> rules!</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2 class="h4 fw-bold mt-4">See you soon..</h2>
|
||||||
|
<p>I'm off to try and write more tests - we're at 84% coverage in pytest, and we also run a stack of 'noop' executions for Ansible, Puppet and Salt too now, <a href="https://git.mig5.net/mig5/enroll/actions/runs/592" target="_blank" rel="noopener noreferrer">in CI</a>.</p>
|
||||||
|
<p>Thanks to everyone who has reached out with suggestions, constructive criticism, and bug reports! You're helping make Enroll better for everyone.</p>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-lg-4">
|
||||||
|
<div class="callout p-4">
|
||||||
|
<div class="fw-semibold mb-2">Targets</div>
|
||||||
|
<ul class="small mb-0">
|
||||||
|
<li><code>--target ansible</code> (default)</li>
|
||||||
|
<li><code>--target puppet</code></li>
|
||||||
|
<li><code>--target salt</code></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="callout p-4 mt-3">
|
||||||
|
<div class="fw-semibold mb-2">Useful links</div>
|
||||||
|
<div class="d-grid gap-2">
|
||||||
|
<a class="btn btn-sm btn-outline-dark" href="../docs.html#targets"><i class="bi bi-book"></i> Target docs</a>
|
||||||
|
<a class="btn btn-sm btn-outline-dark" href="../examples.html"><i class="bi bi-terminal"></i> Examples</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
12
src/content/news/_index.html
Normal file
12
src/content/news/_index.html
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
---
|
||||||
|
title: "News"
|
||||||
|
html_title: "Enroll News"
|
||||||
|
description: "Release notes and project updates for Enroll."
|
||||||
|
---
|
||||||
|
<header class="py-5 hero">
|
||||||
|
<div class="container py-3">
|
||||||
|
<div class="kicker mb-3"><i class="bi bi-megaphone"></i> News</div>
|
||||||
|
<h1 class="display-6 fw-bold mb-2">What's new in Enroll</h1>
|
||||||
|
<p class="lead mb-0">Release notes, tips and tricks, and whatever else we've been cooking up.</p>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
@ -40,12 +40,12 @@ description: "Security posture and safe workflows for Enroll outputs."
|
||||||
<button class="btn btn-sm btn-outline-secondary copy-btn" data-copy-target="#sec-sops"><i class="bi bi-clipboard"></i> Copy</button>
|
<button class="btn btn-sm btn-outline-secondary copy-btn" data-copy-target="#sec-sops"><i class="bi bi-clipboard"></i> Copy</button>
|
||||||
<pre class="mb-0"><code id="sec-sops"><span class="prompt">$</span> enroll harvest --out /tmp/enroll-harvest --dangerous --sops <FINGERPRINT>
|
<pre class="mb-0"><code id="sec-sops"><span class="prompt">$</span> enroll harvest --out /tmp/enroll-harvest --dangerous --sops <FINGERPRINT>
|
||||||
<span class="prompt">$</span> enroll manifest --harvest /tmp/enroll-harvest/harvest.tar.gz.sops \
|
<span class="prompt">$</span> enroll manifest --harvest /tmp/enroll-harvest/harvest.tar.gz.sops \
|
||||||
--out /tmp/enroll-ansible --sops <FINGERPRINT></code></pre>
|
--target ansible --out /tmp/enroll-ansible --sops <FINGERPRINT></code></pre>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="alert alert-secondary mt-3 mb-0">
|
<div class="alert alert-secondary mt-3 mb-0">
|
||||||
<div class="fw-semibold">Important</div>
|
<div class="fw-semibold">Important</div>
|
||||||
<div class="small mb-0">In manifest <code>--sops</code> mode, you'll need to decrypt and extract the bundle before running <code>ansible-playbook</code>.</div>
|
<div class="small mb-0">In manifest <code>--sops</code> mode, you'll need to decrypt and extract the bundle before running <code>ansible-playbook</code>, <code>puppet apply</code>, or <code>salt-call</code>.</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -92,7 +92,7 @@ description: "Security posture and safe workflows for Enroll outputs."
|
||||||
<ul class="small mb-0">
|
<ul class="small mb-0">
|
||||||
<li>Accidentally copying obvious secrets in default mode</li>
|
<li>Accidentally copying obvious secrets in default mode</li>
|
||||||
<li>Harvesting huge/unbounded file sets by mistake</li>
|
<li>Harvesting huge/unbounded file sets by mistake</li>
|
||||||
<li>One host's difference causing problems for other hosts in terms of Ansible task steps (multi-site mode)</li>
|
<li>One host's difference causing problems for other hosts by keeping multi-site data in inventory, Hiera, or pillar</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
|
|
|
||||||
|
|
@ -6,4 +6,4 @@ uglyURLs = true
|
||||||
disableKinds = ["taxonomy", "term", "RSS", "sitemap"]
|
disableKinds = ["taxonomy", "term", "RSS", "sitemap"]
|
||||||
|
|
||||||
[params]
|
[params]
|
||||||
description = "Enroll inspects Debian-like and RedHat-like Linux hosts and generates Ansible roles/playbooks from what it finds. Harvest → Manifest → Manage."
|
description = "Enroll inspects Debian-like and RedHat-like Linux hosts and generates Ansible, Puppet, or Salt manifests from what it finds. Harvest → Manifest → Manage."
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,14 @@
|
||||||
"minLength": 1,
|
"minLength": 1,
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"group": {
|
||||||
|
"minLength": 1,
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"section": {
|
||||||
|
"minLength": 1,
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"version": {
|
"version": {
|
||||||
"minLength": 1,
|
"minLength": 1,
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
|
@ -364,6 +372,12 @@
|
||||||
},
|
},
|
||||||
"type": "array"
|
"type": "array"
|
||||||
},
|
},
|
||||||
|
"section": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
"version": {
|
"version": {
|
||||||
"type": [
|
"type": [
|
||||||
"string",
|
"string",
|
||||||
|
|
@ -390,6 +404,16 @@
|
||||||
"package": {
|
"package": {
|
||||||
"minLength": 1,
|
"minLength": 1,
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"has_config": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"section": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
|
|
@ -571,6 +595,21 @@
|
||||||
"$ref": "#/$defs/UserEntry"
|
"$ref": "#/$defs/UserEntry"
|
||||||
},
|
},
|
||||||
"type": "array"
|
"type": "array"
|
||||||
|
},
|
||||||
|
"user_flatpaks": {
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/$defs/FlatpakInstall"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"user_flatpak_remotes": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/$defs/FlatpakRemote"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
|
|
@ -652,6 +691,256 @@
|
||||||
"notes"
|
"notes"
|
||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
|
},
|
||||||
|
"SysctlSnapshot": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"role_name": {
|
||||||
|
"const": "sysctl"
|
||||||
|
},
|
||||||
|
"managed_files": {
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/$defs/ManagedFile"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"parameters": {
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"notes": {
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"role_name",
|
||||||
|
"managed_files",
|
||||||
|
"parameters",
|
||||||
|
"notes"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"FlatpakInstall": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1
|
||||||
|
},
|
||||||
|
"method": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"system",
|
||||||
|
"user"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"remote": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"branch": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"arch": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"kind": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
|
"enum": [
|
||||||
|
"app",
|
||||||
|
"runtime",
|
||||||
|
null
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ref": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"home": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"source": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"from_url": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"name",
|
||||||
|
"method"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"FlatpakRemote": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1
|
||||||
|
},
|
||||||
|
"method": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"system",
|
||||||
|
"user"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"home": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"source": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"name",
|
||||||
|
"method",
|
||||||
|
"url"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"SnapInstall": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1
|
||||||
|
},
|
||||||
|
"channel": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"revision": {
|
||||||
|
"type": [
|
||||||
|
"integer",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
|
"minimum": 0
|
||||||
|
},
|
||||||
|
"classic": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"devmode": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"dangerous": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"notes": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"source": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"install_revision": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"FlatpakSnapshot": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"role_name": {
|
||||||
|
"const": "flatpak"
|
||||||
|
},
|
||||||
|
"system_flatpaks": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/$defs/FlatpakInstall"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"remotes": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/$defs/FlatpakRemote"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notes": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"role_name"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"SnapSnapshot": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"role_name": {
|
||||||
|
"const": "snap"
|
||||||
|
},
|
||||||
|
"system_snaps": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/$defs/SnapInstall"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notes": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"role_name"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"$id": "https://enroll.sh/schema/state.schema.json",
|
"$id": "https://enroll.sh/schema/state.schema.json",
|
||||||
|
|
@ -762,6 +1051,15 @@
|
||||||
},
|
},
|
||||||
"firewall_runtime": {
|
"firewall_runtime": {
|
||||||
"$ref": "#/$defs/FirewallRuntimeSnapshot"
|
"$ref": "#/$defs/FirewallRuntimeSnapshot"
|
||||||
|
},
|
||||||
|
"sysctl": {
|
||||||
|
"$ref": "#/$defs/SysctlSnapshot"
|
||||||
|
},
|
||||||
|
"flatpak": {
|
||||||
|
"$ref": "#/$defs/FlatpakSnapshot"
|
||||||
|
},
|
||||||
|
"snap": {
|
||||||
|
"$ref": "#/$defs/SnapSnapshot"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
|
|
|
||||||
19
src/themes/enroll-theme/layouts/news/list.html
Normal file
19
src/themes/enroll-theme/layouts/news/list.html
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
{{ define "main" }}
|
||||||
|
{{ .Content | safeHTML }}
|
||||||
|
<main class="py-5">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row g-4">
|
||||||
|
{{ range .Pages.ByDate.Reverse }}
|
||||||
|
<div class="col-lg-6">
|
||||||
|
<article class="feature-card p-4 h-100">
|
||||||
|
<div class="small text-secondary mb-2">{{ .Date.Format "2 January 2006" }}</div>
|
||||||
|
<h2 class="h4 fw-bold mb-2"><a class="link-dark text-decoration-none" href="{{ .RelPermalink }}">{{ .Title }}</a></h2>
|
||||||
|
{{ with .Params.summary }}<p class="text-secondary">{{ . }}</p>{{ end }}
|
||||||
|
<a class="btn btn-sm btn-outline-dark" href="{{ .RelPermalink }}">Read more</a>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
{{ end }}
|
||||||
3
src/themes/enroll-theme/layouts/news/single.html
Normal file
3
src/themes/enroll-theme/layouts/news/single.html
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
{{ define "main" }}
|
||||||
|
{{ .Content | safeHTML }}
|
||||||
|
{{ end }}
|
||||||
|
|
@ -3,12 +3,14 @@
|
||||||
<div class="row g-4 align-items-start">
|
<div class="row g-4 align-items-start">
|
||||||
<div class="col-lg-6">
|
<div class="col-lg-6">
|
||||||
<div class="d-flex align-items-center gap-2 mb-2">
|
<div class="d-flex align-items-center gap-2 mb-2">
|
||||||
<img class="brand-mark" src="assets/img/enroll.svg" alt="Enroll">
|
<img class="brand-mark" src="{{ "assets/img/enroll.svg" | relURL }}" alt="Enroll">
|
||||||
<div class="fw-bold">Enroll (a mig5 project)</div>
|
<div class="fw-bold">Enroll (a mig5 project)</div>
|
||||||
<span class="badge badge-soft rounded-pill">CLI</span>
|
<span class="badge badge-soft rounded-pill">CLI</span>
|
||||||
<span class="badge badge-soft rounded-pill">Ansible</span>
|
<span class="badge badge-soft rounded-pill">Ansible</span>
|
||||||
|
<span class="badge badge-soft rounded-pill">Puppet</span>
|
||||||
|
<span class="badge badge-soft rounded-pill">Salt</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-secondary mb-3">Reverse-engineering servers into Ansible.</div>
|
<div class="text-secondary mb-3">Reverse-engineering servers into Ansible, Puppet, or Salt.</div>
|
||||||
|
|
||||||
<div class="d-flex flex-wrap gap-2">
|
<div class="d-flex flex-wrap gap-2">
|
||||||
<a class="btn btn-sm btn-outline-dark" href="https://git.mig5.net/mig5/enroll" target="_blank" rel="noreferrer">
|
<a class="btn btn-sm btn-outline-dark" href="https://git.mig5.net/mig5/enroll" target="_blank" rel="noreferrer">
|
||||||
|
|
@ -26,10 +28,11 @@
|
||||||
<div class="col-lg-3">
|
<div class="col-lg-3">
|
||||||
<div class="fw-semibold mb-2">Site</div>
|
<div class="fw-semibold mb-2">Site</div>
|
||||||
<ul class="list-unstyled small mb-0">
|
<ul class="list-unstyled small mb-0">
|
||||||
<li><a class="link-secondary text-decoration-none" href="docs.html">Docs</a></li>
|
<li><a class="link-secondary text-decoration-none" href="{{ "docs.html" | relURL }}">Docs</a></li>
|
||||||
<li><a class="link-secondary text-decoration-none" href="examples.html">Examples</a></li>
|
<li><a class="link-secondary text-decoration-none" href="{{ "examples.html" | relURL }}">Examples</a></li>
|
||||||
<li><a class="link-secondary text-decoration-none" href="schema.html">Schema</a></li>
|
<li><a class="link-secondary text-decoration-none" href="{{ "news.html" | relURL }}">News</a></li>
|
||||||
<li><a class="link-secondary text-decoration-none" href="security.html">Security Design</a></li>
|
<li><a class="link-secondary text-decoration-none" href="{{ "schema.html" | relURL }}">Schema</a></li>
|
||||||
|
<li><a class="link-secondary text-decoration-none" href="{{ "security.html" | relURL }}">Security Design</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,4 +21,4 @@
|
||||||
<!-- Bootstrap -->
|
<!-- Bootstrap -->
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" rel="stylesheet">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" rel="stylesheet">
|
||||||
<link href="assets/css/site.css" rel="stylesheet">
|
<link href="{{ "assets/css/site.css" | relURL }}" rel="stylesheet">
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<nav class="navbar navbar-expand-lg bg-white bg-opacity-75 sticky-top border-bottom" data-bs-theme="light">
|
<nav class="navbar navbar-expand-lg bg-white bg-opacity-75 sticky-top border-bottom" data-bs-theme="light">
|
||||||
<div class="container py-1">
|
<div class="container py-1">
|
||||||
<a class="navbar-brand fw-bold d-flex align-items-center gap-2" href="index.html">
|
<a class="navbar-brand fw-bold d-flex align-items-center gap-2" href="{{ "index.html" | relURL }}">
|
||||||
<img class="brand-mark" src="assets/img/enroll.svg" alt="Enroll">
|
<img class="brand-mark" src="{{ "assets/img/enroll.svg" | relURL }}" alt="Enroll">
|
||||||
<span>Enroll</span>
|
<span>Enroll</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
|
@ -13,13 +13,16 @@
|
||||||
<div class="collapse navbar-collapse" id="nav">
|
<div class="collapse navbar-collapse" id="nav">
|
||||||
<ul class="navbar-nav ms-auto align-items-lg-center gap-lg-2">
|
<ul class="navbar-nav ms-auto align-items-lg-center gap-lg-2">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="docs.html">Docs</a>
|
<a class="nav-link" href="{{ "docs.html" | relURL }}">Docs</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="examples.html">Examples</a>
|
<a class="nav-link" href="{{ "examples.html" | relURL }}">Examples</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="security.html">Security Design</a>
|
<a class="nav-link" href="{{ "news.html" | relURL }}">News</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="{{ "security.html" | relURL }}">Security Design</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="nav-item ms-lg-2">
|
<li class="nav-item ms-lg-2">
|
||||||
|
|
|
||||||
|
|
@ -5,4 +5,4 @@
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
<script src="assets/js/site.js"></script>
|
<script src="{{ "assets/js/site.js" | relURL }}"></script>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue