Every server thou ownest says something when thou enterest. On most of them it says nothing at all, and on the rest it says something a colleague typed in 2019 and then left the company. This writes the same true greeting on all of them, keeps it current, and remembers what was there before.
And lo, the banner said WELCOME TO WEB01, and it was web04. Greetings 1:1
============================================================================== PRODUCTION | web01.example.com ============================================================================== IP : 10.20.4.11 Platform : kvm OS : Red Hat Enterprise Linux 9.4 (Plow) (5.14.0-427.el9.x86_64) Owner : Platform Support : ops@example.com Uptime : 41d 6h 12m Load : 0.42 / 0.51 / 0.48 (8 cores, 0.05 per core) Memory : 11.2G / 15.5G (72%) Disk / : 18.1G / 40.0G (46%) Disk /var : 7.9G / 8.0G (98 !!%) Sessions : 2 Updates : 14 pending, 3 !! security !! REBOOT REQUIRED !! 2 failed unit(s): chronyd.service,rsyslog.service INTERNAL. Authorised use only. Activity is logged. Raise changes at https://jira.example.com/servicedesk ==============================================================================
One template. Every host. No agents.
It reads a list of hosts, connects with the keys thou already hast (or a password, if
thy fleet is honest about what it is), escalates with sudo, and writes a
small shell renderer that draws the banner at each login. Not a static file that rots.
A renderer, so the uptime is the real uptime.
Everything it touches is copied, hashed, and written into a manifest before a single byte changes. Including the files that were not there.
Every write is re-read and compared. A write that claims success and did not land is a failure here, not a pass.
One command puts the host back exactly as it was, including deleting the files it created. Most tools forget that part.
There is no single MOTD mechanism, which is the first thing everyone gets wrong. It detects and adapts, per host.
| Family | Mechanism | The catch |
|---|---|---|
| Debian, Ubuntu | /etc/update-motd.d/ |
pam executes these. They all run, so adding one gives thee two banners, not one. |
| RHEL / Rocky / Alma 8, 9 | /etc/motd.d/ |
pam reads these. It does not execute them. A script dropped here will be printed to the user as source code. |
| RHEL / CentOS 7 | /etc/profile.d/ |
That pam reads /etc/motd and nothing else. No drop-ins exist. |
They dropped the script into motd.d, and pam printed it, and every user read the source. Lamentations 4:4
Weighed at every login, or weighed on a timer
Anything drawn at login runs on every SSH session, including thy monitoring checks and thy backup jobs. So the fields are split by what they cost, and the expensive ones never touch the login path.
| Cost | Fields | When |
|---|---|---|
| Fast | hostname, IP, OS, kernel, platform, uptime, load, memory, swap, disk, sessions | Every login. Reads /proc and nothing else. Budget: 50ms. |
| Slow | pending updates, security updates, reboot required, failed units, last audit | A systemd timer, every 30 min with a random delay. Cached to a file. |
| Policy | environment, owner team, support contact, ticket queue, change freeze, classification | From thy inventory. Costs the host nothing. |
A renderer that shells out to dnf adds seconds to every
login. Multiply that by a check that SSHes into two hundred hosts every minute and thou
hast built thyself an outage, with a banner on it.
Values past their threshold are coloured and marked. Both, not either: the static delivery mode cannot carry colour, so a breach that relied on red would be invisible in exactly the mode that reaches every login.
| Result | Field | Detail |
|---|---|---|
| OK | Disk / | 46% — below 85, drawn plain |
| !! | Disk /var | 98% — coloured, and marked, and still marked when piped |
| !! | Security updates | 3 — the number that ends up in the incident review |
| OK | Load per core | 0.05 — the machine is asleep and content |
Breaking the fourth wall, because this one is not a joke
A script in /etc/profile.d that prints to stdout without checking will
corrupt scp, sftp and rsync on every host thou
hast touched. Those protocols read the SSH stream as data. Thy banner becomes the first
few bytes of the file.
The symptom is truncated transfers and mysterious checksum failures across the fleet, and nothing about it points back at a login banner. People have lost days to this.
# the first four lines of the generated hook, and the reason for them case $- in *i*) ;; *) return 0 ;; esac # interactive shells only [ -t 1 ] || return 0 # stdout must be a terminal [ -n "${LINUX_MOTD_QUIET:-}" ] && return 0 [ -n "${LINUX_MOTD_SHOWN:-}" ] && return 0
The test suite runs a real scp and a real sftp get against
an installed host on every supported image. Reading the code is not testing it. That
part is also not a joke.
Look before thou writest
# receive the canon
pip install linux-motd
# see what one host would say. writes nothing.
linux-motd preview -i inventory.yaml -H web01.example.com
# the whole fleet, as a plan with diffs. still writes nothing.
linux-motd plan -i inventory.yaml
# now write it, one approval at a time
linux-motd push -i inventory.yaml --ask-ssh-pass --sudo-pass-same-as-ssh
The gate offers [y]es / [n]o / [a]ll / [q]uit / [d]etail.
--dry-run writes nothing at all. --yes skips the asking, for
machines, and still takes every backup.
Undoing, properly
Most rollback restores what it overwrote and calls it done. But nearly every file this tool installs is new, and restoring only what existed leaves all of them in place. The manifest records the absence explicitly, so the undo deletes.
linux-motd rollback -H web01.example.com linux-motd remove -i inventory.yaml
| It remembers | So that |
|---|---|
| Content hash, mode, owner | The file comes back exactly, not approximately. |
| SELinux context | A file with the right bytes and the wrong label is ignored by pam. It checks. |
existed: false | Files it created are deleted, not left behind wearing a fresh timestamp. |
| The mode of thy distro's own scripts | Ubuntu's banners are disabled by clearing a bit, and the bit comes back. |
The rollback reported success, and on the next reboot the change returned. Numbers 7:9
Plainly, for a moment
A real tool, written by a sysadmin who got tired of logging into a box and not knowing which one it was. Agentless, parallel, MIT licensed, and it fails per host: one unreachable machine is logged with the reason and the run carries on.
The banner is shown to anyone who authenticates, including a compromised account, so
every field that is useful reconnaissance can be switched off per group. The pre-auth
banner in /etc/issue is stricter still: it uses a separate template whose
loader rejects every hostname, address, version and patch-state field by name.
Not by convention, at load time, with an error.
It also never writes a spreadsheet formula. Everything a host reports is written to the report as text, so a compromised server cannot hide a payload in its hostname and have it fire when thou openest the workbook. That part is not a joke either.
Built by vK, who has been paged at 03:00 and did not enjoy it. Companion to Linux Fleet Audit, which reads thy fleet, and linux-harden, which mends it. This one merely announces the result.
Kneel also at the parish and read at the scriptorium. The Cluster abides. The Loop reconciles.