parent
0ed180375e
commit
7f8e7f0c99
25 changed files with 261 additions and 530 deletions
448
src/index.html
448
src/index.html
|
|
@ -1,448 +0,0 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Enroll - Reverse-engineering servers into Ansible</title>
|
||||
<meta name="description" content="Enroll inspects Debian-like and RedHat-like Linux hosts and generates Ansible roles/playbooks from what it finds. Harvest → Manifest → Manage.">
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
|
||||
<!-- 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-icons@1.11.3/font/bootstrap-icons.min.css" rel="stylesheet">
|
||||
|
||||
<link href="assets/css/site.css" rel="stylesheet">
|
||||
|
||||
<!-- OpenGraph -->
|
||||
<meta property="og:title" content="Enroll - Reverse-engineering servers into Ansible">
|
||||
<meta property="og:description" content="Harvest a host's real configuration and turn it into Ansible roles/playbooks. Safe-by-default, with optional SOPS encryption.">
|
||||
<meta property="og:type" content="website">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav class="navbar navbar-expand-lg bg-white bg-opacity-75 sticky-top border-bottom" data-bs-theme="light">
|
||||
<div class="container py-1">
|
||||
<a class="navbar-brand fw-bold d-flex align-items-center gap-2" href="index.html">
|
||||
<img class="brand-mark" src="assets/img/enroll.svg" alt="Enroll">
|
||||
<span>Enroll</span>
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#nav" aria-controls="nav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="nav">
|
||||
<ul class="navbar-nav ms-auto align-items-lg-center gap-lg-2">
|
||||
<li class="nav-item"><a class="nav-link" href="docs.html">Docs</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="examples.html">Examples</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="security.html">Security Design</a></li>
|
||||
<li class="nav-item ms-lg-2">
|
||||
<a class="btn btn-sm btn-outline-dark" href="https://git.mig5.net/mig5/enroll" target="_blank" rel="noreferrer">
|
||||
<i class="bi bi-git"></i> Repo
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<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>
|
||||
|
||||
<footer class="py-5">
|
||||
<div class="container">
|
||||
<div class="row g-4">
|
||||
<div class="col-lg-5">
|
||||
<div class="d-flex align-items-center gap-2 mb-2">
|
||||
<img class="brand-mark" src="assets/img/enroll.svg" alt="Enroll">
|
||||
<div class="fw-bold">Enroll (a mig5 project)</div>
|
||||
</div>
|
||||
<div class="smallprint">Reverse-engineering servers into Ansible.</div>
|
||||
</div>
|
||||
<div class="col-lg-7">
|
||||
<div class="row g-3">
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="fw-semibold mb-2">Site</div>
|
||||
<div class="d-flex flex-column gap-1">
|
||||
<a href="docs.html" class="link-secondary text-decoration-none">Docs</a>
|
||||
<a href="examples.html" class="link-secondary text-decoration-none">Examples</a>
|
||||
<a href="security.html" class="link-secondary text-decoration-none">Security Design</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="fw-semibold mb-2">Project</div>
|
||||
<div class="d-flex flex-column gap-1">
|
||||
<a href="https://git.mig5.net/mig5/enroll" target="_blank" rel="noreferrer" class="link-secondary text-decoration-none">Repo</a>
|
||||
<a href="https://pypi.org/project/enroll/" target="_blank" rel="noreferrer" class="link-secondary text-decoration-none">PyPI</a>
|
||||
<a href="https://git.mig5.net/mig5/enroll/releases" target="_blank" rel="noreferrer" class="link-secondary text-decoration-none">Releases</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="fw-semibold mb-2">Contact</div>
|
||||
<li><a class="link-secondary text-decoration-none" href="https://nr.mig5.net/forms/mig5/contact" target="_blank" rel="noreferrer">Form</a></li>
|
||||
<li><span class="text-secondary">Fediverse:</span> <a class="link-secondary text-decoration-none" href="https://goto.mig5.net/@mig5" target="_blank" rel="noreferrer">@mig5</a></li>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="my-4">
|
||||
<div class="d-flex flex-column flex-md-row justify-content-between gap-2 smallprint">
|
||||
<div class="text-secondary">© <span id="year"></span> <a href="https://mig5.net" target="_blank" rel="noopener noreferrer">mig5 system administration</a></div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
document.getElementById('year').textContent = new Date().getFullYear();
|
||||
</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>
|
||||
</body>
|
||||
</html>
|
||||
Reference in a new issue