2026-06-15
Every sysadmin has written the same line a hundred times:
sed -i 's/^#*PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
And every sysadmin has, at some point, watched it fail in interesting ways: the key was commented twice, it lived inside a Match block, it had a trailing comment, the file had no newline, the regex matched in AllowedPermitRootLogin, or the change duplicated because the script ran twice. Configuration files are structured data we keep pretending are text.
Augeas (and its CLI augtool) is the cure. It parses common config formats through "lenses" into a tree, lets you address nodes by path, and writes the file back preserving comments, ordering, and whitespace. puppet uses it under the hood; you can use it directly.
Inspect what Augeas sees:
$ augtool print /files/etc/ssh/sshd_config | head
/files/etc/ssh/sshd_config/Port = "22"
/files/etc/ssh/sshd_config/PermitRootLogin = "yes"
/files/etc/ssh/sshd_config/PasswordAuthentication = "no"
/files/etc/ssh/sshd_config/Match[1]/Condition/User = "git"
/files/etc/ssh/sshd_config/Match[1]/Settings/ForceCommand = "/usr/bin/git-shell"
Now flip a setting idempotently — works whether the key is present, commented, or missing:
$ augtool -s set /files/etc/ssh/sshd_config/PermitRootLogin no
Saved 1 file(s)
The -s means save. Run it twice — file is byte-identical the second time. Try that with sed -i.
Modify a value only inside a specific Match block — try writing this with awk:
$ augtool -s <<EOF
set /files/etc/ssh/sshd_config/Match[Condition/User='git']/Settings/X11Forwarding no
EOF
Add a host to /etc/hosts if it isn't there:
$ augtool -s <<EOF
set /files/etc/hosts/01/ipaddr 10.0.0.5
set /files/etc/hosts/01/canonical buildbox
EOF
Read every fstab mount that uses nfs:
$ augtool match "/files/etc/fstab/*[vfstype='nfs']/file"
/files/etc/fstab/3/file = /mnt/shared
/files/etc/fstab/7/file = /mnt/backups
Add a sudoers rule without losing your job to a syntax error:
$ augtool -s <<EOF
set /files/etc/sudoers/spec[last()+1]/user deploy
set /files/etc/sudoers/spec[last()]/host_group/host ALL
set /files/etc/sudoers/spec[last()]/host_group/command "/usr/bin/systemctl restart app"
set /files/etc/sudoers/spec[last()]/host_group/command/runas_user root
set /files/etc/sudoers/spec[last()]/host_group/command/tag NOPASSWD
EOF
Augeas validates as it writes. Break the syntax in your script and the file is left untouched, original kept as .augsave.
Built-in lenses cover the usual suspects: sshd, sudoers, fstab, hosts, resolv.conf, nsswitch, pam, postfix, dnsmasq, logrotate, grub, krb5, chrony, php.ini, nginx, samba, and a hundred more. List them:
$ augtool ls /augeas/load
For a format Augeas doesn't ship with, you can write your own lens — it's a small composable parser DSL based on Boomerang. But you rarely need to; the included set covers nearly every /etc file you'll touch in anger.
Why this beats sed-and-pray: idempotency, structural awareness, and atomic writes. Your provisioning script becomes describable as "this config tree should contain these nodes," not "run this regex and hope." It's the difference between editing JSON with jq versus sed — except augeas works on every weird inherited /etc/ format invented before JSON existed.
augtool gives you tree-addressed, idempotent, format-aware edits that survive comments, sections, and re-runs — without the sed regex roulette.
