|
#!/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 |
|
|
|
# ----------------------------------------------------------------------------- |
|
# @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 | sed 's/^up //')" |
|
|
|
primary_if="$(ip -o -4 route show default 2>/dev/null | awk '{print $5; exit}')" |
|
|
|
if [[ -n "$primary_if" ]]; then |
|
primary_ip="$(ip -o -4 addr show "$primary_if" 2>/dev/null | \ |
|
awk '{print $4; exit}' | cut -d/ -f1)" |
|
else |
|
primary_ip="" |
|
fi |
|
|
|
if [[ -z "$primary_ip" ]]; then |
|
primary_ip="$(hostname -I 2>/dev/null | awk '{print $1}')" |
|
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="/etc/update-motd.d/90-release" |
|
|
|
$SUDO tee "$script_path" >/dev/null <<'MOTD_EOF' |
|
#!/bin/bash |
|
# MOTD: host, FQDN, IP, OS release, architecture, bitness, uptime, and Pi board. |
|
|
|
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 | sed 's/^up //')" |
|
primary_if="$(ip -o -4 route show default 2>/dev/null | awk '{print $5; exit}')" |
|
|
|
if [ -n "$primary_if" ]; then |
|
primary_ip="$(ip -o -4 addr show "$primary_if" 2>/dev/null | awk '{print $4; exit}' | cut -d/ -f1)" |
|
else |
|
primary_ip="" |
|
fi |
|
|
|
if [ -z "$primary_ip" ]; then |
|
primary_ip="$(hostname -I 2>/dev/null | awk '{print $1}')" |
|
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 ]]; then |
|
$SUDO chmod -x /etc/update-motd.d/10-uname |
|
fi |
|
|
|
if [[ -e /etc/motd && ! -e /etc/motd.static ]]; then |
|
$SUDO mv /etc/motd /etc/motd.static |
|
fi |
|
} |
|
|
|
# ----------------------------------------------------------------------------- |
|
# @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="/etc/update-motd.d/90-release" |
|
|
|
if [[ -e "$script_path" ]]; then |
|
$SUDO rm -f "$script_path" |
|
fi |
|
|
|
if [[ -e /etc/update-motd.d/10-uname ]]; then |
|
$SUDO chmod +x /etc/update-motd.d/10-uname |
|
fi |
|
|
|
if [[ -e /etc/motd.static && ! -e /etc/motd ]]; then |
|
$SUDO mv /etc/motd.static /etc/motd |
|
fi |
|
} |
|
|
|
# ----------------------------------------------------------------------------- |
|
# @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 "$@" |