Skip to content

Instantly share code, notes, and snippets.

@lbussy
Last active August 22, 2026 13:35
Show Gist options
  • Select an option

  • Save lbussy/3be88f4d10de41f110adf9e2cf54c9f2 to your computer and use it in GitHub Desktop.

Select an option

Save lbussy/3be88f4d10de41f110adf9e2cf54c9f2 to your computer and use it in GitHub Desktop.
MOTD Manager Documentation

MOTD Manager Documentation

Overview

motd-manager is installed from the motd-manager.sh Bash script and is a utility for managing a clean, colored dynamic MOTD (Message of the Day) on Raspberry Pi or Debian-based systems. It replaces the default kernel banner and legal boilerplate with a concise, informative, and visually distinct MOTD that includes host information, OS version, uptime, network details, and board type (if running on a Raspberry Pi).


Quick Install

The URL below is intentionally commit-free and always retrieves the current gist. The recommended process provides an inspection checkpoint before running it as root:

curl -fL --proto '=https' --tlsv1.2 -o motd-manager.sh \
  https://gist.githubusercontent.com/lbussy/3be88f4d10de41f110adf9e2cf54c9f2/raw/motd-manager.sh
less motd-manager.sh
sudo install -m 0755 motd-manager.sh /usr/local/sbin/motd-manager
sudo /usr/local/sbin/motd-manager install

The one-line form skips that inspection checkpoint:

curl -fsSL --proto '=https' --tlsv1.2 https://gist.githubusercontent.com/lbussy/3be88f4d10de41f110adf9e2cf54c9f2/raw/motd-manager.sh | sudo bash -s -- install

Features

  • Displays system summary with colorized output:

    • Hostname
    • Fully qualified domain name (or {hostname}.local)
    • IP address
    • OS release name and version
    • CPU architecture
    • Uptime (pretty format)
    • Primary network interface and IP address
    • Detected Raspberry Pi board model (if applicable)
  • Creates a persistent /etc/update-motd.d/90-motd-manager script.

  • Disables default MOTD snippets (kernel banner and Debian legal text).

  • Supports restoring the default MOTD configuration.

  • Compatible with both SSH and local console logins.


Installation

1. Copy the script

Save the file to /usr/local/sbin/motd-manager:

sudo tee /usr/local/sbin/motd-manager >/dev/null <<'EOF'
# (paste the full script contents here)
EOF

Make it executable:

sudo chmod +x /usr/local/sbin/motd-manager

2. Install the dynamic MOTD

Run the installer to create the colorized MOTD and disable the defaults:

sudo /usr/local/sbin/motd-manager install

This will:

  • Create /etc/update-motd.d/90-motd-manager.
  • Disable /etc/update-motd.d/10-uname (kernel banner).
  • Rename /etc/motd to /var/lib/motd-manager/motd (hiding the legal text).

Usage

Display system info immediately

/usr/local/sbin/motd-manager show

Example output:

\033[1;33mHost:\033[0m    wspr4
\033[1;33mSystem:\033[0m  Debian GNU/Linux 12 (bookworm)  (\033[1;36maarch64\033[0m)
\033[1;33mBoard:\033[0m   Raspberry Pi 4 Model B Rev 1.5
\033[1;32mUptime:\033[0m  5 days, 17 hours, 52 minutes
\033[1;36mAddress:\033[0m 192.168.1.64 on wlan1

Install dynamic MOTD

sudo /usr/local/sbin/motd-manager install

This sets up the clean MOTD and removes redundant information.

The command reports the installed snippet path on success.

Restore defaults

sudo /usr/local/sbin/motd-manager restore

This restores the original Debian MOTD behavior:

  • Re-enables /etc/update-motd.d/10-uname only if this tool disabled it.
  • Restores /etc/motd from /var/lib/motd-manager/motd if available.

The command reports successful restoration on success.


Implementation Details

Raspberry Pi detection

If the system exposes /proc/device-tree/model, its contents are printed as the board model line. This includes variants like:

  • Raspberry Pi 4 Model B Rev 1.5
  • Raspberry Pi Zero 2 W Rev 1.0

Color handling

Color is always enabled for consistent SSH and console display. ANSI escape sequences are used:

  • Yellow (\033[1;33m) – host and system name
  • Cyan (\033[1;36m) – architecture and IP address
  • Green (\033[1;32m) – uptime

Safe operation

  • All file changes require root privileges (via sudo).
  • Managed-file markers prevent overwriting or deleting an unrelated snippet. Saved state makes restore change only settings changed by this tool. set -euo pipefail catches many errors, but does not make multi-file changes atomic.

Recommended filenames

  • Install as /usr/local/sbin/motd-manager; it does not need a .sh suffix.
  • Use 90-motd-manager, not the ambiguous 90-release.
  • Keep state in /var/lib/motd-manager, not collision-prone /etc/motd.static.
  • This document is MOTD Manager Documentation.md, matching its H1 so the gist page uses the README title.

If an older revision is installed, restore it before installing this revision. The new script deliberately does not guess whether existing legacy files belong to it.


Files Affected

File Purpose
/etc/update-motd.d/90-motd-manager Custom MOTD snippet with system summary
/etc/update-motd.d/10-uname Disabled only if initially executable
/etc/motd/var/lib/motd-manager/motd Legal text backup

License

This script is released under the MIT License. You are free to use, modify, and distribute it with attribution.


Version History

Version Date Notes
1.1 Aug 2026 Conservative restore state and descriptive filenames
1.0 Nov 2025 Initial release with Pi board detection and colored MOTD support
#!/usr/bin/env bash
# -----------------------------------------------------------------------------
# @file motd-manager.sh
# @brief Manage a clean, colored dynamic MOTD for Raspberry Pi or Debian hosts.
# @details This script can preview the MOTD content, install a dynamic MOTD
# block into /etc/update-motd.d, or restore the default MOTD behavior.
# -----------------------------------------------------------------------------
set -euo pipefail
readonly STATE_DIR="/var/lib/motd-manager"
readonly RELEASE_BLOCK="/etc/update-motd.d/90-motd-manager"
readonly RELEASE_MARKER="# Managed by motd-manager.sh; do not edit."
readonly UNAME_STATE="$STATE_DIR/10-uname.disabled"
readonly MOTD_BACKUP="$STATE_DIR/motd"
# -----------------------------------------------------------------------------
# @brief Print a message to stderr.
# @param $1 Message text.
# -----------------------------------------------------------------------------
log_error() {
local message="${1:-Unknown error}"
printf "%s\n" "$message" >&2
}
# -----------------------------------------------------------------------------
# @brief Resolve the host FQDN for display.
# @details This prefers the system FQDN. If only the short hostname is
# available, it attempts to append the configured domain. If no domain
# is configured, it falls back to "hostname.local".
# @return Prints the resolved FQDN to stdout.
# -----------------------------------------------------------------------------
get_fqdn() {
local host fqdn domain
host="$(hostname)"
fqdn="$(hostname -f 2>/dev/null || true)"
if [[ -z "$fqdn" || "$fqdn" == "$host" ]]; then
domain="$(hostname -d 2>/dev/null || true)"
if [[ -n "$domain" ]]; then
fqdn="${host}.${domain}"
else
fqdn="${host}.local"
fi
fi
printf "%s\n" "$fqdn"
}
# -----------------------------------------------------------------------------
# @brief Print host, FQDN, IP, OS release, architecture, bitness, uptime,
# and Pi board.
# @param $1 Optional debug flag.
# -----------------------------------------------------------------------------
show_system_info() {
local debug="${1:-}"
# Colors.
local Y="\033[1;33m"
local C="\033[1;36m"
local G="\033[1;32m"
local R="\033[0m"
if [[ -n "$debug" ]] && [[ $(type -t debug_print 2>/dev/null || true) == "function" ]]; then
debug_print "show_system_info called" "$debug"
fi
local host fqdn rel arch bitness uptime_str
local primary_if primary_ip pi_model
host="$(hostname)"
fqdn="$(get_fqdn)"
if [[ -r /etc/os-release ]]; then
# shellcheck disable=SC1091
. /etc/os-release
rel="${PRETTY_NAME:-${NAME}}"
else
rel="$(lsb_release -ds 2>/dev/null || printf "%s" "Unknown")"
fi
arch="$(uname -m)"
bitness="$(getconf LONG_BIT 2>/dev/null || printf "%s" "?")"
uptime_str="$(uptime -p 2>/dev/null | sed 's/^up //' || printf 'Unavailable')"
primary_if="$(ip -o -4 route show default 2>/dev/null | awk '{print $5; exit}' || true)"
if [[ -n "$primary_if" ]]; then
primary_ip="$(ip -o -4 addr show "$primary_if" 2>/dev/null | \
awk '{print $4; exit}' | cut -d/ -f1 || true)"
else
primary_ip=""
fi
if [[ -z "$primary_ip" ]]; then
primary_ip="$(hostname -I 2>/dev/null | awk '{print $1}' || true)"
fi
if [[ -r /proc/device-tree/model ]]; then
pi_model="$(tr -d '\0' </proc/device-tree/model 2>/dev/null)"
else
pi_model=""
fi
printf "\nHost: ${Y}%s${R}\n" "$host"
printf "FQDN: ${C}%s${R}\n" "$fqdn"
if [[ -n "$primary_if" && -n "$primary_ip" ]]; then
printf "Address: ${C}%s${R} on ${Y}%s${R}\n" "$primary_ip" "$primary_if"
elif [[ -n "$primary_ip" ]]; then
printf "Address: ${C}%s${R}\n" "$primary_ip"
else
printf "Address: ${C}%s${R}\n" "Unavailable"
fi
printf "System: ${Y}%s${R} (${C}%s, %s-bit${R})\n" "$rel" "$arch" "$bitness"
if [[ -n "$pi_model" ]]; then
printf "Board: ${Y}%s${R}\n" "$pi_model"
fi
printf "Uptime: ${G}%s${R}\n" "$uptime_str"
}
# -----------------------------------------------------------------------------
# @brief Install colored dynamic MOTD and hide the default kernel banner.
# @param $1 Optional debug flag.
# -----------------------------------------------------------------------------
motd_install_release_block() {
local debug="${1:-}"
local SUDO script_path
SUDO=""
if [[ $EUID -ne 0 ]]; then
SUDO="sudo"
fi
if [[ -n "$debug" ]] && [[ $(type -t debug_print 2>/dev/null || true) == "function" ]]; then
debug_print "Installing dynamic MOTD" "$debug"
fi
script_path="$RELEASE_BLOCK"
if [[ -e "$script_path" ]] && ! grep -Fqx "$RELEASE_MARKER" "$script_path"; then
log_error "Refusing to overwrite unmanaged file: $script_path"
return 1
fi
if [[ -e /etc/motd && -e "$MOTD_BACKUP" ]]; then
log_error "Both /etc/motd and $MOTD_BACKUP exist; resolve them first."
return 1
fi
$SUDO install -d -m 0755 "$STATE_DIR"
$SUDO tee "$script_path" >/dev/null <<'MOTD_EOF'
#!/bin/bash
# Managed by motd-manager.sh; do not edit.
set -u
get_fqdn() {
local host fqdn domain
host="$(hostname)"
fqdn="$(hostname -f 2>/dev/null || true)"
if [ -z "$fqdn" ] || [ "$fqdn" = "$host" ]; then
domain="$(hostname -d 2>/dev/null || true)"
if [ -n "$domain" ]; then
fqdn="${host}.${domain}"
else
fqdn="${host}.local"
fi
fi
printf "%s\n" "$fqdn"
}
Y="\033[1;33m"
C="\033[1;36m"
G="\033[1;32m"
R="\033[0m"
host="$(hostname)"
fqdn="$(get_fqdn)"
if [ -r /etc/os-release ]; then
. /etc/os-release
rel="${PRETTY_NAME:-${NAME}}"
else
rel="$(lsb_release -ds 2>/dev/null || echo "Unknown")"
fi
arch="$(uname -m)"
bitness="$(getconf LONG_BIT 2>/dev/null || echo "?")"
uptime_str="$(uptime -p 2>/dev/null | sed 's/^up //' || printf 'Unavailable')"
primary_if="$(ip -o -4 route show default 2>/dev/null | awk '{print $5; exit}' || true)"
if [ -n "$primary_if" ]; then
primary_ip="$(ip -o -4 addr show "$primary_if" 2>/dev/null | awk '{print $4; exit}' | cut -d/ -f1 || true)"
else
primary_ip=""
fi
if [ -z "$primary_ip" ]; then
primary_ip="$(hostname -I 2>/dev/null | awk '{print $1}' || true)"
fi
if [ -r /proc/device-tree/model ]; then
pi_model="$(tr -d '\0' </proc/device-tree/model 2>/dev/null)"
else
pi_model=""
fi
printf "\nHost: ${Y}%s${R}\n" "$host"
printf "FQDN: ${C}%s${R}\n" "$fqdn"
if [ -n "$primary_if" ] && [ -n "$primary_ip" ]; then
printf "Address: ${C}%s${R} on ${Y}%s${R}\n" "$primary_ip" "$primary_if"
elif [ -n "$primary_ip" ]; then
printf "Address: ${C}%s${R}\n" "$primary_ip"
else
printf "Address: ${C}%s${R}\n" "Unavailable"
fi
printf "System: ${Y}%s${R} (${C}%s, %s-bit${R})\n" "$rel" "$arch" "$bitness"
if [ -n "$pi_model" ]; then
printf "Board: ${Y}%s${R}\n" "$pi_model"
fi
printf "Uptime: ${G}%s${R}\n" "$uptime_str"
MOTD_EOF
$SUDO chmod 0755 "$script_path"
if [[ -x /etc/update-motd.d/10-uname && ! -e "$UNAME_STATE" ]]; then
$SUDO touch "$UNAME_STATE"
$SUDO chmod -x /etc/update-motd.d/10-uname
fi
if [[ -e /etc/motd && ! -e "$MOTD_BACKUP" ]]; then
$SUDO mv /etc/motd "$MOTD_BACKUP"
fi
printf 'Installed %s\n' "$script_path"
}
# -----------------------------------------------------------------------------
# @brief Restore Debian defaults for MOTD display.
# @param $1 Optional debug flag.
# -----------------------------------------------------------------------------
motd_restore_defaults() {
local debug="${1:-}"
local SUDO script_path
SUDO=""
if [[ $EUID -ne 0 ]]; then
SUDO="sudo"
fi
if [[ -n "$debug" ]] && [[ $(type -t debug_print 2>/dev/null || true) == "function" ]]; then
debug_print "Restoring default MOTD behavior" "$debug"
fi
script_path="$RELEASE_BLOCK"
if [[ -e "$script_path" ]] && ! grep -Fqx "$RELEASE_MARKER" "$script_path"; then
log_error "Refusing to remove unmanaged file: $script_path"
return 1
fi
if [[ -e "$MOTD_BACKUP" && -e /etc/motd ]]; then
log_error "Refusing to overwrite existing /etc/motd."
return 1
fi
if [[ -e "$script_path" ]]; then
$SUDO rm -f "$script_path"
fi
if [[ -e "$UNAME_STATE" && -e /etc/update-motd.d/10-uname ]]; then
$SUDO chmod +x /etc/update-motd.d/10-uname
fi
[[ ! -e "$UNAME_STATE" ]] || $SUDO rm -f "$UNAME_STATE"
if [[ -e "$MOTD_BACKUP" ]]; then
$SUDO mv "$MOTD_BACKUP" /etc/motd
fi
$SUDO rmdir "$STATE_DIR" 2>/dev/null || true
printf 'Restored the MOTD state recorded by motd-manager.\n'
}
# -----------------------------------------------------------------------------
# @brief Print usage information.
# @param $1 Script name.
# -----------------------------------------------------------------------------
show_usage() {
local script_name="${1:-${0##*/}}"
printf "Usage: %s {show|install|restore}\n" "$script_name" >&2
}
# -----------------------------------------------------------------------------
# @brief Main dispatcher.
# @param $1 Command.
# -----------------------------------------------------------------------------
main() {
local command="${1:-show}"
case "$command" in
show)
show_system_info
;;
install)
motd_install_release_block
;;
restore)
motd_restore_defaults
;;
*)
show_usage "${0##*/}"
exit 1
;;
esac
}
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment