diff --git a/src/content/_index.html b/src/content/_index.html index dcf6cd7..253e406 100644 --- a/src/content/_index.html +++ b/src/content/_index.html @@ -1,23 +1,22 @@ --- title: "Enroll" -html_title: "Enroll - Reverse-engineering servers into configuration management" -description: "Enroll inspects Debian-like and RedHat-like Linux hosts and generates Ansible, Puppet, or Salt manifests from what it finds. Harvest → Manifest → Manage." -og_title: "Enroll - Reverse-engineering servers into configuration management" -og_description: "Harvest a host's real configuration and turn it into Ansible, Puppet, or Salt code. Safe-by-default, with optional SOPS encryption." +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" ---
-
Reverse-engineering servers into Ansible, Puppet, or Salt
-

Get an existing Linux host into configuration management in seconds.

-

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.

+
Reverse-engineering servers into Ansible
+

Get an existing Linux host into Ansible in seconds.

+

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.

Super fast @@ -30,18 +29,24 @@ og_type: "website"
Config in the blink of an eye
-
harvest once → render to your CM tool
+
single-shot → ansible-playbook
-
$ enroll harvest --out ./harvest
-
$ enroll manifest --harvest ./harvest --target ansible --out ./ansible
-
$ enroll manifest --harvest ./harvest --target puppet --out ./puppet
-
$ enroll manifest --harvest ./harvest --target salt --out ./salt
-
./ansible  → playbook.yml, roles/, inventory/...
-./puppet   → manifests/site.pp, modules/, data/...
-./salt     → states/top.sls, states/roles/, pillar/...
+
$ enroll single-shot --harvest ./harvest --out ./ansible
+
$ cd ./ansible && tree -L 2
+
.
+├── ansible.cfg
+├── playbook.yml
+├── roles/
+│   ├── cron/
+│   ├── etc_custom/
+│   ├── firewall/
+│   ├── nginx/
+│   ├── openssh-server/
+│   ├── users/
+└── README.md
-
Tip: for multiple hosts, use --fqdn to generate target-native host data: Ansible inventory, Puppet Hiera, or Salt pillar.
+
Tip: for multiple hosts, use --fqdn to generate inventory-driven, data-driven roles.
@@ -66,7 +71,7 @@ og_type: "website"
Manifest
-
Render Ansible roles/playbooks, Puppet modules/Hiera data, or Salt states/pillar from the harvest.
+
Render Ansible roles & playbooks from the harvest.
@@ -102,27 +107,11 @@ og_type: "website"
-
-
-
-
Ansible, Puppet, or Salt
-
The harvest bundle is renderer-neutral. Choose --target ansible, --target puppet, or --target salt at manifest time.
-
-
-
-
-
-
-
Fewer roles/modules where possible
-
By default, package/service snapshots are grouped by package Section or equivalent metadata to reduce role/module sprawl. Use --no-common-roles, or --fqdn, to keep output more host-specific.
-
-
-
Multi-site without "shared role broke host2"
-
In --fqdn mode, host-specific data moves into the target's native host-data layer: Ansible inventory, Puppet Hiera, or Salt pillar.
+
In --fqdn mode, roles are data-driven and host inventory decides what gets managed per host.
@@ -130,7 +119,7 @@ og_type: "website"
Remote over SSH
-
Harvest a remote host from your workstation, then render Ansible, Puppet, or Salt output locally.
+
Harvest a remote host from your workstation, then manifest Ansible output locally.
@@ -182,28 +171,19 @@ og_type: "website"
-
# Harvest once
-enroll harvest --out ./harvest
+              
# Harvest → Manifest in one go
+enroll single-shot --harvest ./harvest --out ./ansible
 
-# Render and noop-test Ansible
-enroll manifest --harvest ./harvest --target ansible --out ./ansible
-ansible-playbook -i "localhost," -c local ./ansible/playbook.yml --check --diff
-
-# Render and noop-test Puppet
-enroll manifest --harvest ./harvest --target puppet --out ./puppet
-puppet apply --modulepath ./puppet/modules ./puppet/manifests/site.pp --noop
-
-# Render and noop-test Salt
-enroll manifest --harvest ./harvest --target salt --out ./salt
-salt-call --local --file-root ./salt/states state.apply test=True
+# Then run Ansible locally +ansible-playbook -i "localhost," -c local ./ansible/playbook.yml
Good for
-
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.
+
Disaster recovery snapshots, "make this one host reproducible", and carving a golden role set you'll refine over time.

-
Using Ansible and want templates for structured configs? Install JinjaTurtle and use --jinjaturtle (or let it auto-detect).
+
Want templates for structured configs? Install JinjaTurtle and use --jinjaturtle (or let it auto-detect).
@@ -217,7 +197,6 @@ enroll single-shot \ --remote-host myhost.example.com \ --remote-user myuser \ --harvest /tmp/enroll-harvest \ - --target ansible \ --out ./ansible \ --fqdn myhost.example.com @@ -227,23 +206,14 @@ enroll single-shot \
-
# Multi-site mode: one harvest, target-native host data
-fqdn="$(hostname -f)"
+          
# 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)"
 
-# Ansible: inventory/host_vars and per-host playbook
-enroll manifest --harvest /tmp/enroll-harvest --target ansible --out ./ansible --fqdn "$fqdn"
-ansible-playbook ./ansible/playbooks/${fqdn}.yml -i ./ansible/inventory/hosts.ini -c local --check --diff
-
-# Puppet: Hiera node data, certname selects the host
-enroll manifest --harvest /tmp/enroll-harvest --target puppet --out ./puppet --fqdn "$fqdn"
-puppet apply --modulepath ./puppet/modules --hiera_config ./puppet/hiera.yaml --certname "$fqdn" ./puppet/manifests/site.pp --noop
-
-# Salt: pillar node data, minion id selects the host
-enroll manifest --harvest /tmp/enroll-harvest --target salt --out ./salt --fqdn "$fqdn"
-salt-call --local --id "$fqdn" --file-root ./salt/states --pillar-root ./salt/pillar state.apply test=True
+# Run the per-host playbook +ansible-playbook ./ansible/playbooks/"$(hostname -f)".yml
-
Rule of thumb: single-site for "one server, easy-to-read roles/modules/states"; --fqdn for "many servers, host-specific data, fast adoption".
+
Rule of thumb: single-site for "one server, easy-to-read roles"; --fqdn for "many servers, high abstraction, fast adoption".
@@ -290,14 +260,14 @@ enroll explain /path/to/harvest.sops \
-
# Validate a harvest is correct.
+          
# Validate a harvest is correct.
 enroll validate /path/to/harvest
 
 # Check against the latest published version of the state schema specification
 enroll validate /path/to/harvest --schema https://enroll.sh/schema/state.schema.json
 
-
'validate' makes sure the harvest's state conforms to Enroll's state schema, 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.
+
'validate' makes sure the harvest's state confirms to Enroll's state schema, 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.
@@ -325,7 +295,7 @@ enroll validate /path/to/harvest --schema https://enroll.sh/schema/state.schema.
Manifest
-
Render Ansible roles/playbooks, Puppet modules, or Salt states.
+
Render Ansible roles/playbooks.
@@ -355,23 +325,6 @@ enroll validate /path/to/harvest --schema https://enroll.sh/schema/state.schema. -
-
-
-
-
-
News
-

Enroll 0.7.0 adds Puppet and Salt rendering

-

The same harvest bundle can now render Ansible, Puppet, or Salt output, with target-native multi-host layouts and package-section grouping where possible.

-
- -
-
-
-
-
diff --git a/src/content/docs.html b/src/content/docs.html index 5e6812d..d6dbb48 100644 --- a/src/content/docs.html +++ b/src/content/docs.html @@ -18,7 +18,6 @@ description: "How Enroll works: harvest, manifest, modes, and configuration."
Mental model - Targets and grouping How harvesting works State schema Single-site vs multi-site @@ -39,7 +38,7 @@ description: "How Enroll works: harvest, manifest, modes, and configuration."

Mental model

-

Enroll is intentionally simple: it collects facts first, then renders target-native configuration-management output from those facts.

+

Enroll is intentionally simple: it collects facts first, then renders Ansible from those facts.

@@ -55,7 +54,7 @@ description: "How Enroll works: harvest, manifest, modes, and configuration."
  • Detect installed packages and services
  • Collect config that deviates from packaged defaults (where possible)
  • Grab relevant custom/unowned files in service dirs
  • -
  • Capture non-system users, group memberships, SSH public keys, and dangerous-mode shell dotfiles when requested
  • +
  • Capture non-system users & SSH public keys, .bashrc files etc
  • @@ -65,13 +64,13 @@ description: "How Enroll works: harvest, manifest, modes, and configuration."
    2) Manifest
    -
    Generate Ansible, Puppet, or Salt output
    +
    Generate an Ansible repo structure
      -
    • Ansible roles/playbooks, Puppet modules/Hiera data, or Salt states/pillar
    • -
    • Noop-friendly output you can review before applying
    • -
    • Optional target-native host data for multi-host runs
    • +
    • Roles with files/templates and defaults
    • +
    • Playbooks to apply the captured state
    • +
    • Optional inventory structure for multi-host runs: each host gets its own playbook
    @@ -80,52 +79,8 @@ description: "How Enroll works: harvest, manifest, modes, and configuration."
    Typical flow
    $ enroll harvest --out /tmp/enroll-harvest
    -$ enroll manifest --harvest /tmp/enroll-harvest --target ansible --out /tmp/enroll-ansible
    -$ ansible-playbook -i "localhost," -c local /tmp/enroll-ansible/playbook.yml --check --diff
    -
    -$ enroll manifest --harvest /tmp/enroll-harvest --target puppet --out /tmp/enroll-puppet
    -$ puppet apply --modulepath /tmp/enroll-puppet/modules /tmp/enroll-puppet/manifests/site.pp --noop
    -
    -$ enroll manifest --harvest /tmp/enroll-harvest --target salt --out /tmp/enroll-salt
    -$ salt-call --local --file-root /tmp/enroll-salt/states state.apply test=True
    -
    -
    - -
    -

    Targets and grouping

    -

    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.

    - -
    -
    -
    -
    Ansible
    -

    Generates playbooks, roles, defaults, files/templates, inventory, and host vars.

    -
    $ enroll manifest --harvest ./harvest --target ansible --out ./ansible
    -$ ansible-playbook -i "localhost," -c local ./ansible/playbook.yml --check --diff
    -
    -
    -
    -
    -
    Puppet
    -

    Generates a control-repo-style tree with manifests/site.pp, modules, files, and Hiera data in --fqdn mode.

    -
    $ enroll manifest --harvest ./harvest --target puppet --out ./puppet
    -$ puppet apply --modulepath ./puppet/modules ./puppet/manifests/site.pp --noop
    -
    -
    -
    -
    -
    Salt
    -

    Generates Salt state trees under states/, with pillar data under pillar/ in --fqdn mode.

    -
    $ enroll manifest --harvest ./harvest --target salt --out ./salt
    -$ salt-call --local --file-root ./salt/states state.apply test=True
    -
    -
    -
    - -
    -
    Reducing role/module/state sprawl
    -

    When Enroll can see package metadata, it tries to combine related package/service snapshots by the package Section 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.

    -

    This grouping is enabled by default in single-site output. It is disabled by --no-common-roles when you want one role/module/state per discovered package/service, and it is also disabled by --fqdn because host-specific output should stay isolated in inventory/Hiera/pillar.

    +$ enroll manifest --harvest /tmp/enroll-harvest --out /tmp/enroll-ansible +$ ansible-playbook -i "localhost," -c local /tmp/enroll-ansible/playbook.yml
    @@ -137,19 +92,19 @@ description: "How Enroll works: harvest, manifest, modes, and configuration."
  • Detects the OS and its package backend (e.g dpkg vs rpm)
  • Detects what packages are installed
  • For each package, it tries to detect files in /etc that have been modified from the default that get shipped with the package.
  • -
  • 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)
  • +
  • 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)
  • Aside from known packages already learned, it optimistically tries to capture extra system configuration in /etc that is common for config management. This is stuff like the apt or dnf configuration, crons, logrotate configs, networking settings, hosts files, etc.
  • -
  • For applications that commonly make use of symlinks (think Apache2 or Nginx's sites-enabled or mods-enabled), it notes what symlinks exist so that it can capture those in the generated manifest
  • +
  • For applications that commonly make use of symlinks (think Apache2 or Nginx's sites-enabled or mods-enabled), it notes what symlinks exist so that it can capture those in Ansible
  • It also looks for other snowflake stuff in /etc not associated with packages/services or other typical system config, and will put these into an etc_custom role.
  • Likewise, it looks in /usr/local for stuff, on the assumption that this is an area that custom apps/configs might've been placed in. These go into a usr_local_custom role.
  • -
  • It captures non-system user accounts, group memberships, and files such as .ssh/authorized_keys. In --dangerous mode it can also capture shell dotfiles such as .bashrc, .profile, .bash_aliases, and .bash_logout when they differ from the skel defaults.
  • +
  • It captures non-system user accounts, their group memberships and files such as their .ssh/authorized_keys, and .bashrc, .profile, .bash_aliases, .bash_logout if these files differ from the skel defaults
  • It takes into account anything the user set with --exclude-path or --include-path. For anything extra that is included, it will put these into an 'extra_paths' role. The location could be anywhere e.g something in /opt, /srv, whatever you want.
  • It writes the state.json and captures the artifacts.

  • Other things to be aware of:

    -
    Does Enroll use community roles/modules?
    -
    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.
    +
    Does Enroll use Ansible community/galaxy roles?
    +
    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.

    Keep in mind that a lot of software config files are also good candidates for being Jinja templates with abstracted vars for separate hosts.

    -
    Enroll does use my companion tool JinjaTurtle 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.
    +
    Enroll does use my companion tool JinjaTurtle 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 ansible.builtin.copy in role tasks.
    @@ -170,7 +125,7 @@ description: "How Enroll works: harvest, manifest, modes, and configuration."

    State schema

    Enroll writes a state.json file describing what was harvested. The canonical definition of that file format is the JSON Schema below.

    -

    You can also validate a harvest state file against the schema by using enroll validate /path/to/harvest.

    +

    You can also validate a harvest state file against the schema by using enroll validate /path/to/harvest.

    @@ -182,18 +137,17 @@ description: "How Enroll works: harvest, manifest, modes, and configuration."

    Single-site vs multi-site

    -

    Manifest output has two styles. The exact files differ by target, but the intent is the same.

    +

    Manifest output has two styles. Choose based on how you'll use the result.

    Single-site (default)
    -

    Best when you are enrolling one host, or producing a reusable baseline that you will review and refine.

    +

    Best when you are enrolling one host, or you're producing a reusable "golden" role set that could be applied anywhere.

      -
    • Ansible: self-contained roles and playbook.yml
    • -
    • Puppet: manifests/site.pp plus modules under modules/
    • -
    • Salt: states/top.sls plus states under states/roles/
    • -
    • Common role grouping is enabled unless --no-common-roles is passed
    • +
    • Roles are self-contained
    • +
    • Raw files live in each role's files/
    • +
    • Template variables live in defaults/main.yml
    @@ -202,31 +156,23 @@ description: "How Enroll works: harvest, manifest, modes, and configuration."
    Multi-site (--fqdn)

    Best when you want to enroll several existing servers quickly, especially if they differ.

      -
    • Ansible: host-specific state in inventory/host_vars/<fqdn>/
    • -
    • Puppet: host-specific data in Hiera under data/nodes/<fqdn>.yaml
    • -
    • Salt: host-specific data in pillar under pillar/nodes/
    • -
    • Common grouping is disabled so each host's harvested state remains isolated
    • +
    • Roles are shared; raw files live in host-specific inventory
    • +
    • Inventory decides what gets managed on each host (files/packages/services)
    • +
    • Non-templated files go under inventory/host_vars/<fqdn>/<role>/.files
    -
    Multi-site noop examples
    -
    $ fqdn="$(hostname -f)"
    -$ enroll manifest --harvest /tmp/enroll-harvest --target ansible --out /tmp/enroll-ansible --fqdn "$fqdn"
    -$ ansible-playbook /tmp/enroll-ansible/playbooks/${fqdn}.yml -i /tmp/enroll-ansible/inventory/hosts.ini -c local --check --diff
    -
    -$ enroll manifest --harvest /tmp/enroll-harvest --target puppet --out /tmp/enroll-puppet --fqdn "$fqdn"
    -$ puppet apply --modulepath /tmp/enroll-puppet/modules --hiera_config /tmp/enroll-puppet/hiera.yaml --certname "$fqdn" /tmp/enroll-puppet/manifests/site.pp --noop
    -
    -$ enroll manifest --harvest /tmp/enroll-harvest --target salt --out /tmp/enroll-salt --fqdn "$fqdn"
    -$ salt-call --local --id "$fqdn" --file-root /tmp/enroll-salt/states --pillar-root /tmp/enroll-salt/pillar state.apply test=True
    +
    Multi-site example
    +
    $ enroll manifest --harvest /tmp/enroll-harvest --out /tmp/enroll-ansible --fqdn "$(hostname -f)"
    +$ ansible-playbook /tmp/enroll-ansible/playbooks/"$(hostname -f)".yml
    -
    Tip: targeting subsets
    -
    Ansible playbooks tag each role as role_<name>. 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.
    +
    Tip: role tags
    +
    Generated playbooks tag each role as role_<name> (e.g. role_users, role_services, role_other). You can target a subset with ansible-playbook ... --tags role_users.
    @@ -235,12 +181,12 @@ description: "How Enroll works: harvest, manifest, modes, and configuration."

    Run Enroll on your workstation, harvest a remote host over SSH. The harvest is pulled locally.

    $ enroll harvest --remote-host myhost.example.com --remote-user myuser --out /tmp/enroll-harvest
    -$ enroll manifest --harvest /tmp/enroll-harvest --target ansible --out /tmp/enroll-ansible
    +$ enroll manifest --harvest /tmp/enroll-harvest --out /tmp/enroll-manifest
     
     # Alternatively, run both commands combined together with the 'single-shot' mode:
     
     $ enroll single-shot --remote-host myhost.example.com --remote-user myuser \
    -  --harvest /tmp/enroll-harvest --target ansible --out /tmp/enroll-ansible \
    +  --harvest /tmp/enroll-harvest --out /tmp/enroll-ansible \
       --fqdn myhost.example.com
     
     # Encrypted SSH key examples:
    @@ -265,7 +211,7 @@ description: "How Enroll works: harvest, manifest, modes, and configuration."
     
             

    JinjaTurtle integration

    -

    If JinjaTurtle (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.

    +

    If JinjaTurtle (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.

    @@ -281,8 +227,8 @@ description: "How Enroll works: harvest, manifest, modes, and configuration."
    Where variables land
      -
    • Ansible single-site: roles/<role>/defaults/main.yml
    • -
    • Ansible multi-site: inventory/host_vars/<fqdn>/<role>.yml
    • +
    • Single-site: roles/<role>/defaults/main.yml
    • +
    • Multi-site: inventory/host_vars/<fqdn>/<role>.yml
    @@ -333,7 +279,7 @@ ignore_package_versions = true

    Drift detection with enroll diff

    -

    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.

    +

    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!

    The purpose of enroll diff 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.

    @@ -356,7 +302,7 @@ ignore_package_versions = true
    Optional: enforce the old harvest state (--enforce)
    -
    If drift exists and ansible-playbook is on PATH, Enroll can generate a manifest from the old harvest and apply it locally to restore expected state. It avoids package downgrades, and will often run Ansible with --tags role_... 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.
    +
    If drift exists and ansible-playbook is on PATH, Enroll can generate a manifest from the old harvest and apply it locally to restore expected state. It avoids package downgrades, and will often run Ansible with --tags role_... so only the roles implicated by the drift are applied. This is very much like a return to Puppet's agent mode!
    @@ -615,7 +561,7 @@ sudo journalctl -u enroll-harvest-diff.service -n 200 --no-pager
    Multi-host safety
    -

    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.

    +

    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.

    diff --git a/src/content/examples.html b/src/content/examples.html index 58e5ca5..464eb1e 100644 --- a/src/content/examples.html +++ b/src/content/examples.html @@ -1,13 +1,13 @@ --- title: "Examples" html_title: "Enroll Examples" -description: "Copy/paste recipes for Enroll: Ansible, Puppet, Salt, fleets, drift detection, and safe storage." +description: "Copy/paste recipes for Enroll: one host, fleets, drift detection, and safe storage." ---
    Examples

    Copy/paste recipes

    -

    Practical flows you can adapt to Ansible, Puppet, Salt, or all three.

    +

    Practical flows you can adapt to your environment.

    @@ -17,85 +17,49 @@ description: "Copy/paste recipes for Enroll: Ansible, Puppet, Salt, fleets, drif
    -
    Harvest once, try each renderer locally
    +
    Enroll a single host (local)
    $ enroll harvest --out /tmp/enroll-harvest
    -
    -# Ansible noop
    -$ enroll manifest --harvest /tmp/enroll-harvest --target ansible --out /tmp/enroll-ansible
    -$ ansible-playbook -i "localhost," -c local /tmp/enroll-ansible/playbook.yml --diff --check
    -
    -# Puppet noop
    -$ enroll manifest --harvest /tmp/enroll-harvest --target puppet --out /tmp/enroll-puppet
    -$ puppet apply --modulepath /tmp/enroll-puppet/modules /tmp/enroll-puppet/manifests/site.pp --noop
    -
    -# Salt noop
    -$ enroll manifest --harvest /tmp/enroll-harvest --target salt --out /tmp/enroll-salt
    -$ salt-call --local --file-root /tmp/enroll-salt/states state.apply test=True
    +$ enroll manifest --harvest /tmp/enroll-harvest \ + --out /tmp/enroll-ansible +$ ansible-playbook -i "localhost," -c local \ + /tmp/enroll-ansible/playbook.yml --diff --check
    -

    Great for “make this box reproducible”, testing renderer output, or building a golden baseline you will refine.

    +

    Great for "make this box reproducible" or building a golden role set.

    -
    Enroll a remote host over SSH
    +
    Enroll a remote host (over SSH)
    $ enroll harvest \
       --remote-host myhost.example.com \
       --remote-user myuser \
       --out /tmp/enroll-harvest
    -
     $ enroll manifest \
       --harvest /tmp/enroll-harvest \
    -  --target puppet \
    -  --out /tmp/enroll-puppet
    + --out /tmp/enroll-ansible
    -

    The bundle lands locally. Change --target puppet to ansible or salt without re-harvesting. For remote sudo prompts use --ask-become-pass/-K; use --no-sudo for a less complete non-root harvest.

    +

    No need to manually run commands on the server - your bundle lands locally. If your remote user needs a password for sudo, pass in --ask-become-pass or -K, just like in Ansible. If you don't want to use sudo, pass --no-sudo, but your harvest may contain less data.

    -
    Fleets: --fqdn output
    +
    Fleets: multi-site output
    $ fqdn="$(hostname -f)"
    -$ enroll harvest --out /tmp/enroll-harvest
    -
    -# Ansible: inventory/host_vars and a per-host playbook
    -$ enroll manifest --harvest /tmp/enroll-harvest --target ansible --out /tmp/enroll-ansible --fqdn "$fqdn"
    -$ 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
    -$ enroll manifest --harvest /tmp/enroll-harvest --target puppet --out /tmp/enroll-puppet --fqdn "$fqdn"
    -$ 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
    -$ enroll manifest --harvest /tmp/enroll-harvest --target salt --out /tmp/enroll-salt --fqdn "$fqdn"
    -$ salt-call --local --id "$fqdn" --file-root /tmp/enroll-salt/states --pillar-root /tmp/enroll-salt/pillar state.apply test=True
    +$ enroll single-shot --remote-host "$fqdn" \ + --remote-user myuser \ + --out /tmp/enroll-ansible \ + --fqdn "$fqdn" +$ ansible-playbook "/tmp/enroll-ansible/playbooks/${fqdn}.yml"
    -

    --fqdn disables common grouping and moves host-specific state into the target’s native host-data layer: inventory, Hiera, or pillar.

    -
    -
    - -
    -
    -
    Control role/module/state grouping
    -
    - -
    # Default single-site mode: combine packages/services by package Section where possible
    -$ enroll manifest --harvest /tmp/enroll-harvest --target ansible --out /tmp/enroll-ansible
    -
    -# Keep output split more literally by discovered package/service role
    -$ 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
    -$ enroll manifest --harvest /tmp/enroll-harvest --target salt --out /tmp/enroll-salt --fqdn "$(hostname -f)"
    -
    -

    Grouping keeps first-pass output manageable. Use --no-common-roles when you prefer more direct one-role-per-package/service output.

    +

    Shared roles + host inventory keeps one host's differences from breaking another.

    @@ -104,19 +68,15 @@ description: "Copy/paste recipes for Enroll: Ansible, Puppet, Salt, fleets, drif
    Drift detection with enroll diff
    -
    $ enroll diff --old /path/to/harvestA --new /path/to/harvestB \
    -  --format markdown \
    -  --exclude-path /var/anacron \
    -  --ignore-package-versions
    -
    +            
    $ enroll diff --old /path/to/harvestA --new /path/to/harvestB --format markdown --exclude-path /var/anacron --ignore-package-versions
     $ enroll diff --old /path/to/golden --new /path/to/current \
    -  --webhook https://example.net/webhook \
    +  --webhook https://example.net/webhook  \
       --webhook-format json \
    -  --webhook-header 'X-Enroll-Secret: ...' \
    -  --ignore-package-versions \
    -  --exit-code
    + --webhook-header 'X-Enroll-Secret: ...' \ + --ignore-package-versions --exit-code +
    -

    Use it in cron, systemd timers, or CI to alert on change.

    +

    Use it in cron or CI to alert on change.

    @@ -127,13 +87,13 @@ description: "Copy/paste recipes for Enroll: Ansible, Puppet, Salt, fleets, drif
    $ enroll explain /tmp/enroll-harvest
     
    -# machine-readable reasons, examples, and inventory breakdown
    +# machine-readable (reasons, examples, inventory breakdown)
     $ enroll explain /tmp/enroll-harvest --format json | jq .
     
     # encrypted bundle
     $ enroll explain /var/lib/enroll/harvest.tar.gz.sops --sops
    -

    Great for answering “why did it include/exclude that file?” before you generate a manifest.

    +

    Great for answering "why did it include/exclude that file?" before you generate a manifest.

    @@ -141,31 +101,39 @@ description: "Copy/paste recipes for Enroll: Ansible, Puppet, Salt, fleets, drif
    Enforce the previous state with enroll diff --enforce
    - -
    $ enroll diff \
    +            
    +            
    $ enroll diff \
       --old /path/to/harvestA \
       --new /path/to/harvestB \
       --enforce \
    -  --format json
    + --format json +
    -

    Enforcement currently uses generated Ansible and ansible-playbook on PATH, even if you also use Puppet or Salt for normal manifest output.

    +

    Enforcing the old harvest will restore its files/perms, missing packages, changed services or users, if ansible-playbook is on the PATH.

    + +
    + +
    -
    -
    Encrypt bundles at rest with SOPS
    -
    - -
    $ enroll harvest --dangerous --out /tmp/harvest \
    +        
    +
    Safe harvesting (default)
    +

    Enroll tries to avoid harvesting files that might contain secrets. If you need to capture "everything", pass --dangerous and treat the output as sensitive.

    +

    You can still control what gets collected and what doesn't by using --include and --exclude flags.

    +
    $ enroll harvest --dangerous --out /tmp/enroll-harvest
    +
    +
    +
    +
    +
    Encrypt bundles at rest (SOPS)
    +

    Produce a single encrypted file for harvest and/or manifest output (requires SOPS to be installed).

    +

    This is especially a good idea if you are using --dangerous, which might sweep up secrets (see above).

    +
    $ enroll harvest --dangerous --out /tmp/harvest \
       --sops <FINGERPRINT>
    -
     $ enroll manifest --harvest /tmp/harvest/harvest.tar.gz.sops \
    -  --target salt \
    -  --out /tmp/enroll-salt \
    -  --sops <FINGERPRINT>
    -
    -

    Use this if you intend to store harvests/manifests long term or if you harvested with --dangerous.

    + --out /tmp/enroll-ansible --sops <FINGERPRINT>
    diff --git a/src/content/news/0-7-0.html b/src/content/news/0-7-0.html deleted file mode 100644 index 8bf2c74..0000000 --- a/src/content/news/0-7-0.html +++ /dev/null @@ -1,115 +0,0 @@ ---- -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." ---- -
    -
    -
    Release notes
    -

    Enroll 0.7.0: Puppet and Salt join Ansible

    -

    A single harvest can now be rendered into Ansible, Puppet, or Salt output.

    -
    -
    - -
    -
    -
    -
    -
    -

    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.

    - -

    Highlights

    -
      -
    • --target puppet renders Puppet module/control-repo style output., and in --fqdn mode, renders per-host Hiera data.
    • -
    • --target salt renders Salt state trees and, in --fqdn mode, Salt pillar data.
    • -
    • Ansible works basically as it always did, and is the default, but you can specify --target ansible too. As usual, in --fqdn mode, specific artifacts end up in host_vars inventory folders rather than polluting the 'golden' roles.
    • -
    • 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!
    • -
    • Single-site output tries to combine package/service data by their package manager's Section (or equivalent metadata), to reduce role/module/state sprawl.
    • -
    • Flatpak and Snap detection!
    • -
    - -

    Dry-run examples

    -
    - -
    $ enroll harvest --out ./harvest
    -
    -$ enroll manifest --harvest ./harvest --target ansible --out ./ansible
    -$ ansible-playbook -i "localhost," -c local ./ansible/playbook.yml --check --diff
    -
    -$ enroll manifest --harvest ./harvest --target puppet --out ./puppet
    -$ puppet apply --modulepath ./puppet/modules ./puppet/manifests/site.pp --noop
    -
    -$ enroll manifest --harvest ./harvest --target salt --out ./salt
    -$ salt-call --local --file-root ./salt/states state.apply test=True
    -
    - -

    New grouping behaviour in roles/modules

    -

    Did you find the number of manifested roles overwhelming?

    -

    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!

    - -

    As of 0.7.0, where Enroll can read that package metadata, it groups related package and service snapshots by the package manager's Section 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 net. Meanwhile, vim, nano might both appear in editors, and mutt and Thunderbird may be in mail.

    - -

    If you're not a fan of this new layout, you can pass --no-common-roles to enforce the previous behaviour. Also, if you use --fqdn 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.

    - -

    Flatpak and Snap detection

    -

    Beyond deb and rpm

    -

    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 community.general collection to create Flatpak and Snap tasks to enforce the presence of those packages.

    -
    -
    $ 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'})
    -            
    -
    - -

    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 community.general collection for this to work properly. A requirements.yml gets created with your manifest to help you install it if necessary.

    - -

    Other smaller changes

    -
      -
    • sysctl runtime parameters are now detected and would be written to /etc/sysctl.d/99-enroll.conf. Not all runtime parameters are supported.
    • -
    • .bashrc and similar files are now only harvested from user directories when --dangerous is used, since this is a common place for sensitive environment variables to be set. As always, remember that --dangerous gives better harvest coverage, but you should use --sops or some other means of your own to encrypt the harvested data at rest safely!
    • -
    • Some output during an Ansible play is hidden with no_log to avoid potentially sensitive output, particularly of systemd unit state.
    • -
    • In case you missed it in version 0.6.0: Enroll now harvests runtime iptables and ipset rules!
    • -
    - -

    See you soon..

    -

    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, in CI.

    -

    Thanks to everyone who has reached out with suggestions, constructive criticism, and bug reports! You're helping make Enroll better for everyone.

    - - -
    -
    - -
    -
    -
    Targets
    -
      -
    • --target ansible (default)
    • -
    • --target puppet
    • -
    • --target salt
    • -
    -
    -
    -
    Useful links
    - -
    -
    -
    -
    -
    diff --git a/src/content/news/_index.html b/src/content/news/_index.html deleted file mode 100644 index 6958573..0000000 --- a/src/content/news/_index.html +++ /dev/null @@ -1,12 +0,0 @@ ---- -title: "News" -html_title: "Enroll News" -description: "Release notes and project updates for Enroll." ---- -
    -
    -
    News
    -

    What's new in Enroll

    -

    Release notes, tips and tricks, and whatever else we've been cooking up.

    -
    -
    diff --git a/src/content/security.html b/src/content/security.html index 2218f29..c2a812b 100644 --- a/src/content/security.html +++ b/src/content/security.html @@ -40,12 +40,12 @@ description: "Security posture and safe workflows for Enroll outputs."
    $ enroll harvest --out /tmp/enroll-harvest --dangerous --sops <FINGERPRINT>
     $ enroll manifest --harvest /tmp/enroll-harvest/harvest.tar.gz.sops \
    -  --target ansible --out /tmp/enroll-ansible --sops <FINGERPRINT>
    + --out /tmp/enroll-ansible --sops <FINGERPRINT>
    Important
    -
    In manifest --sops mode, you'll need to decrypt and extract the bundle before running ansible-playbook, puppet apply, or salt-call.
    +
    In manifest --sops mode, you'll need to decrypt and extract the bundle before running ansible-playbook.
    @@ -92,7 +92,7 @@ description: "Security posture and safe workflows for Enroll outputs."
    diff --git a/src/hugo.toml b/src/hugo.toml index 4172e70..53735bb 100644 --- a/src/hugo.toml +++ b/src/hugo.toml @@ -6,4 +6,4 @@ uglyURLs = true disableKinds = ["taxonomy", "term", "RSS", "sitemap"] [params] - description = "Enroll inspects Debian-like and RedHat-like Linux hosts and generates Ansible, Puppet, or Salt manifests from what it finds. Harvest → Manifest → Manage." + description = "Enroll inspects Debian-like and RedHat-like Linux hosts and generates Ansible roles/playbooks from what it finds. Harvest → Manifest → Manage." diff --git a/src/static/schema/state.schema.json b/src/static/schema/state.schema.json index a67cf64..d0bde52 100644 --- a/src/static/schema/state.schema.json +++ b/src/static/schema/state.schema.json @@ -117,14 +117,6 @@ "minLength": 1, "type": "string" }, - "group": { - "minLength": 1, - "type": "string" - }, - "section": { - "minLength": 1, - "type": "string" - }, "version": { "minLength": 1, "type": "string" @@ -372,12 +364,6 @@ }, "type": "array" }, - "section": { - "type": [ - "string", - "null" - ] - }, "version": { "type": [ "string", @@ -404,16 +390,6 @@ "package": { "minLength": 1, "type": "string" - }, - "has_config": { - "type": "boolean", - "default": true - }, - "section": { - "type": [ - "string", - "null" - ] } }, "required": [ @@ -595,21 +571,6 @@ "$ref": "#/$defs/UserEntry" }, "type": "array" - }, - "user_flatpaks": { - "additionalProperties": { - "type": "array", - "items": { - "$ref": "#/$defs/FlatpakInstall" - } - }, - "type": "object" - }, - "user_flatpak_remotes": { - "type": "array", - "items": { - "$ref": "#/$defs/FlatpakRemote" - } } }, "required": [ @@ -691,256 +652,6 @@ "notes" ], "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", @@ -1051,15 +762,6 @@ }, "firewall_runtime": { "$ref": "#/$defs/FirewallRuntimeSnapshot" - }, - "sysctl": { - "$ref": "#/$defs/SysctlSnapshot" - }, - "flatpak": { - "$ref": "#/$defs/FlatpakSnapshot" - }, - "snap": { - "$ref": "#/$defs/SnapSnapshot" } }, "required": [ diff --git a/src/themes/enroll-theme/layouts/news/list.html b/src/themes/enroll-theme/layouts/news/list.html deleted file mode 100644 index 6c0783c..0000000 --- a/src/themes/enroll-theme/layouts/news/list.html +++ /dev/null @@ -1,19 +0,0 @@ -{{ define "main" }} -{{ .Content | safeHTML }} -
    -
    -
    - {{ range .Pages.ByDate.Reverse }} -
    -
    -
    {{ .Date.Format "2 January 2006" }}
    -

    {{ .Title }}

    - {{ with .Params.summary }}

    {{ . }}

    {{ end }} - Read more -
    -
    - {{ end }} -
    -
    -
    -{{ end }} diff --git a/src/themes/enroll-theme/layouts/news/single.html b/src/themes/enroll-theme/layouts/news/single.html deleted file mode 100644 index 3a342e5..0000000 --- a/src/themes/enroll-theme/layouts/news/single.html +++ /dev/null @@ -1,3 +0,0 @@ -{{ define "main" }} -{{ .Content | safeHTML }} -{{ end }} diff --git a/src/themes/enroll-theme/layouts/partials/footer.html b/src/themes/enroll-theme/layouts/partials/footer.html index a8ab385..3ab6587 100644 --- a/src/themes/enroll-theme/layouts/partials/footer.html +++ b/src/themes/enroll-theme/layouts/partials/footer.html @@ -3,14 +3,12 @@
    - Enroll + Enroll
    Enroll (a mig5 project)
    CLI Ansible - Puppet - Salt
    -
    Reverse-engineering servers into Ansible, Puppet, or Salt.
    +
    Reverse-engineering servers into Ansible.
    @@ -28,11 +26,10 @@ diff --git a/src/themes/enroll-theme/layouts/partials/head.html b/src/themes/enroll-theme/layouts/partials/head.html index 8b2bd48..f185324 100644 --- a/src/themes/enroll-theme/layouts/partials/head.html +++ b/src/themes/enroll-theme/layouts/partials/head.html @@ -21,4 +21,4 @@ - + diff --git a/src/themes/enroll-theme/layouts/partials/nav.html b/src/themes/enroll-theme/layouts/partials/nav.html index 3812e8f..3c03651 100644 --- a/src/themes/enroll-theme/layouts/partials/nav.html +++ b/src/themes/enroll-theme/layouts/partials/nav.html @@ -1,7 +1,7 @@