parent
0ed180375e
commit
7f8e7f0c99
25 changed files with 261 additions and 530 deletions
355
src/content/_index.html
Normal file
355
src/content/_index.html
Normal file
|
|
@ -0,0 +1,355 @@
|
|||
---
|
||||
title: "Enroll"
|
||||
html_title: "Enroll - Reverse-engineering servers into Ansible"
|
||||
description: "Enroll inspects Debian-like and RedHat-like Linux hosts and generates Ansible roles/playbooks from what it finds. Harvest → Manifest → Manage."
|
||||
og_title: "Enroll - Reverse-engineering servers into Ansible"
|
||||
og_description: "Harvest a host's real configuration and turn it into Ansible roles/playbooks. Safe-by-default, with optional SOPS encryption."
|
||||
og_type: "website"
|
||||
---
|
||||
<header class="hero py-5">
|
||||
<div class="container py-4">
|
||||
<div class="row align-items-center g-4">
|
||||
<div class="col-lg-6">
|
||||
<div class="kicker mb-3"><i class="bi bi-magic"></i> Reverse-engineering servers into Ansible</div>
|
||||
<h1 class="display-5 fw-800 lh-1 mb-3" style="letter-spacing:-0.03em;">Get an existing Linux host into Ansible in seconds.</h1>
|
||||
<p class="lead mb-4">Enroll inspects a Debian-like or RedHat-like system, harvests the state that matters, and generates Ansible roles/playbooks so you can bring snowflakes under management fast.</p>
|
||||
<div class="d-flex flex-wrap gap-2 mb-4">
|
||||
<a class="btn btn-dark btn-lg" href="#quickstart"><i class="bi bi-rocket-takeoff"></i> Quickstart</a>
|
||||
<a class="btn btn-outline-dark btn-lg" href="#demos"><i class="bi bi-play-circle"></i> Watch demos</a>
|
||||
<a class="btn btn-outline-secondary btn-lg" href="https://pypi.org/project/enroll/" target="_blank" rel="noreferrer"><i class="bi bi-box-seam"></i> PyPI</a>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<span class="badge badge-soft rounded-pill px-3 py-2"><i class="bi bi-speedometer"></i> Super fast</span>
|
||||
<span class="badge badge-soft rounded-pill px-3 py-2"><i class="bi bi-lock"></i> Optional SOPS encryption</span>
|
||||
<span class="badge badge-soft rounded-pill px-3 py-2"><i class="bi bi-hdd-network"></i> Remote over SSH</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-6">
|
||||
<div class="hero-card p-4 p-lg-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<div class="fw-semibold"><i class="bi bi-terminal"></i> Config in the blink of an eye</div>
|
||||
<div class="small text-muted">single-shot → ansible-playbook</div>
|
||||
</div>
|
||||
<div class="terminal">
|
||||
<div class="mb-2"><span class="prompt">$</span> <code style="font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;">enroll single-shot --harvest ./harvest --out ./ansible</code></div>
|
||||
<div class="mb-2"><span class="prompt">$</span> <code style="font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;">cd ./ansible && tree -L 2</code></div>
|
||||
<pre class="mb-0" style="white-space:pre-wrap; font-family:'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace; font-size:.9rem;">.
|
||||
├── ansible.cfg
|
||||
├── playbook.yml
|
||||
├── roles/
|
||||
│ ├── cron/
|
||||
│ ├── etc_custom/
|
||||
│ ├── firewall/
|
||||
│ ├── nginx/
|
||||
│ ├── openssh-server/
|
||||
│ ├── users/
|
||||
└── README.md</pre>
|
||||
</div>
|
||||
<div class="smallprint mt-3">Tip: for multiple hosts, use <code>--fqdn</code> to generate inventory-driven, data-driven roles.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="py-5" id="how" aria-label="How it works">
|
||||
<div class="container">
|
||||
<div class="row g-4 align-items-start">
|
||||
<div class="col-lg-5">
|
||||
<h2 class="section-title display-6 fw-bold mb-3">A simple mental model</h2>
|
||||
<p class="mb-4">Enroll is built around two phases, plus optional drift and reporting tools:</p>
|
||||
<div class="d-flex flex-column gap-3">
|
||||
<div class="d-flex gap-3">
|
||||
<div class="icon-pill"><i class="bi bi-bucket"></i></div>
|
||||
<div>
|
||||
<div class="fw-semibold">Harvest</div>
|
||||
<div class="text-muted">Collect host facts + relevant files into a bundle.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex gap-3">
|
||||
<div class="icon-pill"><i class="bi bi-diagram-3"></i></div>
|
||||
<div>
|
||||
<div class="fw-semibold">Manifest</div>
|
||||
<div class="text-muted">Render Ansible roles & playbooks from the harvest.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex gap-3">
|
||||
<div class="icon-pill"><i class="bi bi-plus-slash-minus"></i></div>
|
||||
<div>
|
||||
<div class="fw-semibold">Diff</div>
|
||||
<div class="text-muted">Compare two harvests and notify via webhook/email.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex gap-3">
|
||||
<div class="icon-pill"><i class="bi bi-patch-question"></i></div>
|
||||
<div>
|
||||
<div class="fw-semibold">Explain</div>
|
||||
<div class="text-muted">Analyze what's included/excluded in the harvest and why.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-7">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<div class="card feature-card h-100">
|
||||
<div class="card-body p-4">
|
||||
<div class="fw-semibold mb-1">Safe-by-default harvesting</div>
|
||||
<div class="text-muted">Enroll avoids likely secrets with a path denylist, content sniffing, and size caps - then lets you opt in to more aggressive collection when you're ready.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="card feature-card h-100">
|
||||
<div class="card-body p-4">
|
||||
<div class="fw-semibold mb-1">Multi-site without "shared role broke host2"</div>
|
||||
<div class="text-muted">In <code>--fqdn</code> mode, roles are data-driven and host inventory decides what gets managed per host.</div>
|
||||
</div>
|
||||
</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">Remote over SSH</div>
|
||||
<div class="text-muted">Harvest a remote host from your workstation, then manifest Ansible output locally.</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">Encrypt bundles at rest</div>
|
||||
<div class="text-muted">Use <code>--sops</code> to store harvests/manifests as a single encrypted <code>.tar.gz.sops</code> file (GPG) for safer long-term storage as a DR strategy.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="callout p-4 mt-3">
|
||||
<div class="fw-semibold mb-2"><i class="bi bi-lightning-charge"></i> Why sysadmins like it</div>
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6 text-muted">• Rapid enrolling of existing infra into config management<br>• Tweak include/exclude paths as needed</div>
|
||||
<div class="col-md-6 text-muted">• Capture what changed from package defaults<br>• <code>diff</code> mode detects and alerts about drift</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="py-5 bg-light" id="quickstart" aria-label="Quickstart">
|
||||
<div class="container">
|
||||
<div class="row align-items-end g-3 mb-3">
|
||||
<div class="col-lg-7">
|
||||
<h2 class="section-title display-6 fw-bold mb-2">Quickstart</h2>
|
||||
</div>
|
||||
<div class="col-lg-5 text-lg-end">
|
||||
<a class="btn btn-outline-dark" href="https://git.mig5.net/mig5/enroll#readme" target="_blank" rel="noreferrer"><i class="bi bi-journal-text"></i> Full README</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul class="nav nav-pills gap-2" id="qsTabs" role="tablist">
|
||||
<li class="nav-item" role="presentation"><button class="nav-link active" id="qs1-tab" data-bs-toggle="tab" data-bs-target="#qs1" type="button" role="tab">Local</button></li>
|
||||
<li class="nav-item" role="presentation"><button class="nav-link" id="qs2-tab" data-bs-toggle="tab" data-bs-target="#qs2" type="button" role="tab">Remote</button></li>
|
||||
<li class="nav-item" role="presentation"><button class="nav-link" id="qs3-tab" data-bs-toggle="tab" data-bs-target="#qs3" type="button" role="tab">Multi-site</button></li>
|
||||
<li class="nav-item" role="presentation"><button class="nav-link" id="qs4-tab" data-bs-toggle="tab" data-bs-target="#qs4" type="button" role="tab">Diff</button></li>
|
||||
<li class="nav-item" role="presentation"><button class="nav-link" id="qs5-tab" data-bs-toggle="tab" data-bs-target="#qs5" type="button" role="tab">Explain</button></li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content mt-3">
|
||||
<div class="tab-pane fade show active" id="qs1" role="tabpanel" aria-labelledby="qs1-tab">
|
||||
<div class="row g-3">
|
||||
<div class="col-lg-6">
|
||||
<div class="codeblock">
|
||||
<button class="btn btn-sm btn-outline-secondary copy-btn" data-copy-target="#qs1code"><i class="bi bi-clipboard"></i> Copy</button>
|
||||
<pre class="terminal mb-0"><code id="qs1code"># Harvest → Manifest in one go
|
||||
enroll single-shot --harvest ./harvest --out ./ansible
|
||||
|
||||
# Then run Ansible locally
|
||||
ansible-playbook -i "localhost," -c local ./ansible/playbook.yml</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<div class="callout p-4 h-100">
|
||||
<div class="fw-semibold mb-2">Good for</div>
|
||||
<div class="text-muted">Disaster recovery snapshots, "make this one host reproducible", and carving a golden role set you'll refine over time.</div>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane fade" id="qs2" role="tabpanel" aria-labelledby="qs2-tab">
|
||||
<div class="codeblock">
|
||||
<button class="btn btn-sm btn-outline-secondary copy-btn" data-copy-target="#qs2code"><i class="bi bi-clipboard"></i> Copy</button>
|
||||
<pre class="terminal mb-0"><code id="qs2code"># Remote harvest over SSH, then manifest locally
|
||||
enroll single-shot \
|
||||
--remote-host myhost.example.com \
|
||||
--remote-user myuser \
|
||||
--harvest /tmp/enroll-harvest \
|
||||
--out ./ansible \
|
||||
--fqdn myhost.example.com</code></pre>
|
||||
</div>
|
||||
<div class="smallprint mt-3">If you don't want/need sudo on the remote host, add <code>--no-sudo</code> (expect a less complete harvest).</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane fade" id="qs3" role="tabpanel" aria-labelledby="qs3-tab">
|
||||
<div class="codeblock">
|
||||
<button class="btn btn-sm btn-outline-secondary copy-btn" data-copy-target="#qs3code"><i class="bi bi-clipboard"></i> Copy</button>
|
||||
<pre class="terminal mb-0"><code id="qs3code"># Multi-site mode: shared roles, host-specific state in inventory
|
||||
enroll harvest --out /tmp/enroll-harvest
|
||||
enroll manifest --harvest /tmp/enroll-harvest --out ./ansible --fqdn "$(hostname -f)"
|
||||
|
||||
# Run the per-host playbook
|
||||
ansible-playbook ./ansible/playbooks/"$(hostname -f)".yml</code></pre>
|
||||
</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>
|
||||
|
||||
<div class="tab-pane fade" id="qs4" role="tabpanel" aria-labelledby="qs4-tab">
|
||||
<div class="codeblock">
|
||||
<button class="btn btn-sm btn-outline-secondary copy-btn" data-copy-target="#qs4code"><i class="bi bi-clipboard"></i> Copy</button>
|
||||
<pre class="terminal mb-0"><code id="qs4code"># Compare two harvests and get a human-friendly report
|
||||
enroll diff --old /path/to/harvestA --new /path/to/harvestB --format markdown
|
||||
|
||||
# Send a webhook when differences are detected
|
||||
enroll diff \
|
||||
--old /path/to/harvestA \
|
||||
--new /path/to/harvestB \
|
||||
--webhook https://example.net/webhook \
|
||||
--webhook-format json \
|
||||
--webhook-header 'X-Enroll-Secret: ...' \
|
||||
--exit-code</code></pre>
|
||||
</div>
|
||||
<div class="smallprint mt-3">E-mail notifications are also supported. Run it on a systemd timer to alert to drift!</div>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="qs5" role="tabpanel" aria-labelledby="qs5-tab">
|
||||
<div class="codeblock">
|
||||
<button class="btn btn-sm btn-outline-secondary copy-btn" data-copy-target="#qs5code"><i class="bi bi-clipboard"></i> Copy</button>
|
||||
<pre class="terminal mb-0"><code id="qs5code"># Explain what's in a harvest
|
||||
enroll explain /path/to/harvest
|
||||
|
||||
# JSON format, and using a SOPS-encrypted harvest
|
||||
enroll explain /path/to/harvest.sops \
|
||||
--sops \
|
||||
--format json
|
||||
</code></pre>
|
||||
</div>
|
||||
<div class="smallprint mt-3">'explain' tells you why something was included, but also why something was <em>excluded</em>.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="py-5" id="demos" aria-label="Demos">
|
||||
<div class="container">
|
||||
<div class="row g-3 align-items-end mb-3">
|
||||
<div class="col-lg-8">
|
||||
<h2 class="section-title display-6 fw-bold mb-2">Demonstrations</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6 col-lg-6">
|
||||
<div class="card feature-card h-100">
|
||||
<div class="card-body p-4">
|
||||
<div class="fw-semibold mb-1">Harvest</div>
|
||||
<div class="text-muted mb-3">Collect state into a bundle.</div>
|
||||
<div class="asciicast" data-asciinema-id="765203"><script src="https://asciinema.org/a/765203.js" id="asciicast-765203" async="true"></script></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 col-lg-6">
|
||||
<div class="card feature-card h-100">
|
||||
<div class="card-body p-4">
|
||||
<div class="fw-semibold mb-1">Manifest</div>
|
||||
<div class="text-muted mb-3">Render Ansible roles/playbooks.</div>
|
||||
<div class="asciicast" data-asciinema-id="765204"><script src="https://asciinema.org/a/765204.js" id="asciicast-765204" async="true"></script></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6 col-lg-6">
|
||||
<div class="card feature-card h-100">
|
||||
<div class="card-body p-4">
|
||||
<div class="fw-semibold mb-1">Single-shot</div>
|
||||
<div class="text-muted mb-3">Harvest → Manifest in one command.</div>
|
||||
<div class="asciicast" data-asciinema-id="765127"><script src="https://asciinema.org/a/765127.js" id="asciicast-765127" async="true"></script></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 col-lg-6">
|
||||
<div class="card feature-card h-100">
|
||||
<div class="card-body p-4">
|
||||
<div class="fw-semibold mb-1">Diff</div>
|
||||
<div class="text-muted mb-3">Drift report + webhook/email notifications.</div>
|
||||
<div class="asciicast" data-asciinema-id="765128"><script src="https://asciinema.org/a/765128.js" id="asciicast-765128" async="true"></script></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="py-5 bg-light" id="install" aria-label="Install">
|
||||
<div class="container">
|
||||
<div class="row g-3 align-items-end mb-3">
|
||||
<div class="col-lg-8">
|
||||
<h2 class="section-title display-6 fw-bold mb-2">Install</h2>
|
||||
<p class="text-muted mb-0">Use your preferred packaging. An AppImage is also available.</p>
|
||||
</div>
|
||||
<div class="col-lg-4 text-lg-end">
|
||||
<a class="btn btn-outline-dark" href="https://git.mig5.net/mig5/enroll#install" target="_blank" rel="noreferrer"><i class="bi bi-link-45deg"></i>Other install steps</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul class="nav nav-pills gap-2" id="installTabs" role="tablist">
|
||||
<li class="nav-item" role="presentation"><button class="nav-link active" data-bs-toggle="tab" data-bs-target="#inst1" type="button" role="tab">Debian/Ubuntu</button></li>
|
||||
<li class="nav-item" role="presentation"><button class="nav-link" data-bs-toggle="tab" data-bs-target="#inst2" type="button" role="tab">Fedora</button></li>
|
||||
<li class="nav-item" role="presentation"><button class="nav-link" data-bs-toggle="tab" data-bs-target="#inst3" type="button" role="tab">Pip</button></li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content mt-3">
|
||||
<div class="tab-pane fade show active" id="inst1" role="tabpanel">
|
||||
<div class="codeblock">
|
||||
<button class="btn btn-sm btn-outline-secondary copy-btn" data-copy-target="#inst1code"><i class="bi bi-clipboard"></i> Copy</button>
|
||||
<pre class="terminal mb-0"><code id="inst1code">sudo mkdir -p /usr/share/keyrings
|
||||
curl -fsSL https://mig5.net/static/mig5.asc | sudo gpg --dearmor -o /usr/share/keyrings/mig5.gpg
|
||||
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/mig5.gpg] https://apt.mig5.net $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/mig5.list
|
||||
sudo apt update
|
||||
sudo apt install enroll</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="inst2" role="tabpanel">
|
||||
<div class="codeblock">
|
||||
<button class="btn btn-sm btn-outline-secondary copy-btn" data-copy-target="#inst2code"><i class="bi bi-clipboard"></i> Copy</button>
|
||||
<pre class="terminal mb-0"><code id="inst2code">sudo rpm --import https://mig5.net/static/mig5.asc
|
||||
|
||||
sudo tee /etc/yum.repos.d/mig5.repo > /dev/null << 'EOF'
|
||||
[mig5]
|
||||
name=mig5 Repository
|
||||
baseurl=https://rpm.mig5.net/$releasever/rpm/$basearch
|
||||
enabled=1
|
||||
gpgcheck=1
|
||||
repo_gpgcheck=1
|
||||
gpgkey=https://mig5.net/static/mig5.asc
|
||||
EOF
|
||||
|
||||
sudo dnf upgrade --refresh
|
||||
sudo dnf install enroll</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="inst3" role="tabpanel">
|
||||
<div class="codeblock">
|
||||
<button class="btn btn-sm btn-outline-secondary copy-btn" data-copy-target="#inst3code"><i class="bi bi-clipboard"></i> Copy</button>
|
||||
<pre class="terminal mb-0"><code id="inst3code">pip install enroll
|
||||
# or: pipx install enroll</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
Reference in a new issue