Categories
SysOps

How to display LWN news using message of the day framework

Display LWN news using the message of the day framework.

Create 90-lwn shell script inside /etc/update-motd.d/ directory.

#!/bin/bash
# Display LWN news using update-motd framework

# cache file
lwn_cache_file="/var/run/motd.lwn"
lwn_cache_time="7200"

# curl options
curl_max_time="5"

# LWN options
lwn_header="LWN news"
lwn_url="https://lwn.net/headlines/newrss"
lwn_color_link="\\\e[31m"
lwn_color_date="\\\e[2m"
lwn_color_clear="\\\e[0m"

# update cache file
function update_cache() {
  curl --max-time $curl_max_time --silent $lwn_url | \
    xmlstarlet select -N it="http://purl.org/rss/1.0/" \
                      -N dc="http://purl.org/dc/elements/1.1/" \
                      --text \
                      --noblanks \
                      --template --match "//rdf:RDF/it:item[position() <= 5]" \
                      -o "- $lwn_color_link" -v it:title -o "$lwn_color_clear" -o " at " -o "$lwn_color_date" -v dc:date -o "$lwn_color_clear" -o '\\n' | \
    xargs echo -e > $lwn_cache_file
}

# display contents of the cache file if it is not empty
function display_cache() {
  if [ -s "$lwn_cache_file" ]; then
    echo
    echo "$lwn_header"
    cat $lwn_cache_file
  fi
}

# check for specific command
check_for_command(){
  command -v "$1" 1>/dev/null 2>/dev/null
}

# check for curl and xmlstarlet
commands="curl xmlstarlet"

for command in $commands; do
  if ! check_for_command "$command"; then
    exit
  fi
done

# main
if [ -f "$lwn_cache_file" ]; then
  # compare time of last data modification, seconds since Epoch
  # to decide if it needs to be updated
  date_cache="$(stat --format %Y $lwn_cache_file)"
  date_now="$(date +%s)"
  date_diff="$(expr $date_now - $date_cache)"
  if [ "$date_diff" -ge "$lwn_cache_time" ]; then
    update_cache
  fi
else
    update_cache
fi

# display news
display_cache

Ensure that the executable bit is set.

$ chmod +x /etc/update-motd.d/90-lwn

Verify shell scripts’ execution order and output without initiating the login process.

$ ls /etc/update-motd.d/
10-uname  90-motd
$ run-parts --lsbsysinit /etc/update-motd.d
Linux buster 4.19.0-4-amd64 #1 SMP Debian 4.19.28-2 (2019-03-15) x86_64
LWN news
- [$] Examining exFAT at 2019-08-30T18:43:28+00:00
- A very deep dive into iOS Exploit chains found in the wild (Project Zero) at 2019-08-30T15:59:37+00:00
- Security updates for Friday at 2019-08-30T12:56:46+00:00
- [$] Change IDs for kernel patches at 2019-08-29T16:58:52+00:00
- Stable kernels 5.2.11, 4.19.69, and 4.14.141 at 2019-08-29T15:25:16+00:00

Empirically check the message of the day.

$ ssh debian.example.org -l milosz -i ~/.ssh/ext_milosz
Linux buster 4.19.0-4-amd64 #1 SMP Debian 4.19.28-2 (2019-03-15) x86_64
LWN news
- [$] Examining exFAT at 2019-08-30T18:43:28+00:00
- A very deep dive into iOS Exploit chains found in the wild (Project Zero) at 2019-08-30T15:59:37+00:00
- Security updates for Friday at 2019-08-30T12:56:46+00:00
- [$] Change IDs for kernel patches at 2019-08-29T16:58:52+00:00
- Stable kernels 5.2.11, 4.19.69, and 4.14.141 at 2019-08-29T15:25:16+00:00
The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Sun Sep  1 18:28:09 2019 from 10.0.2.2
milosz@buster:~$

References

This behavior is defined in pam_motd source code, look for update-motd patch.

/* Run the update-motd dynamic motd scripts, outputting to /run/motd.dynamic.
   This will be displayed only when calling pam_motd with
   motd=/run/motd.dynamic; current /etc/pam.d/login and /etc/pam.d/sshd
   display both this file and /etc/motd. */
if (do_update && (stat("/etc/update-motd.d", &st) == 0)
    && S_ISDIR(st.st_mode))
{
   mode_t old_mask = umask(0022);
   if (!system("/usr/bin/env -i PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin run-parts --lsbsysinit /etc/update-motd.d > /run/motd.dynamic.new"))
       rename("/run/motd.dynamic.new", "/run/motd.dynamic");
   umask(old_mask);
}

Inspect update-motd – dynamic MOTD generation manual page for more information.