script-install-motd 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. #!/usr/bin/env bash
  2. # Install and generate motd
  3. # Copyright 2019-2025 Bryan C. Roessler
  4. # Licensed under the Apache License, Version 2.0
  5. set -euo pipefail
  6. PROG_NAME="$(basename "$0")"
  7. INSTALL_PATH="/usr/local/bin/$PROG_NAME"
  8. SERVICE_UNIT="/usr/lib/systemd/system/motd.service"
  9. TIMER_UNIT="/usr/lib/systemd/system/motd.timer"
  10. usage() {
  11. cat <<-EOF
  12. Usage: $PROG_NAME [--motd | --install] [--help]
  13. Options:
  14. --motd Output MOTD to stdout (used by systemd)
  15. --install Instal motd systemd service and timer
  16. -h, --help Show this help message
  17. EOF
  18. }
  19. print_motd() {
  20. # ANSI color codes
  21. local default='\033[0m' green='\033[0;32m' red='\033[0;31m'
  22. local dim='\033[2m' undim='\033[22m'
  23. cat <<-'EOF'
  24. _ _ _ _ _
  25. | | | | | | | | | |
  26. | |__| | __ _ _ __| |_ _ __ ___ __ _ _ __ | | __ _| |__
  27. | __ |/ _` | `__| __| `_ ` _ \ / _` | `_ \ | | / _` | `_ \
  28. | | | | (_| | | | |_| | | | | | (_| | | | | | |___| (_| | |_) |
  29. |_| |_|\__,_|_| \__|_| |_| |_|\__,_|_| |_| |______\__,_|_.__/
  30. EOF
  31. # System info
  32. read -r LOAD1 LOAD5 LOAD15 < <(awk '{print $1, $2, $3}' /proc/loadavg)
  33. read -r USED AVAIL TOTAL < <(free -htm | awk '/^Mem:/ {print $3, $7, $2}')
  34. local ROOT_PROCS NONROOT_PROCS TOTAL_PROCS
  35. ROOT_PROCS=$(pgrep -u root | wc -l)
  36. NONROOT_PROCS=$(( $(ps -eo user= | wc -l) - ROOT_PROCS ))
  37. TOTAL_PROCS=$(( ROOT_PROCS + NONROOT_PROCS ))
  38. local CPU_MODEL CPU_COUNT
  39. CPU_MODEL=$(awk -F': ' '/model name/ {print $2; exit}' /proc/cpuinfo)
  40. CPU_COUNT=$(grep -c '^processor' /proc/cpuinfo)
  41. . /etc/os-release
  42. local DISTRO="$PRETTY_NAME"
  43. local KERNEL; KERNEL="$(uname -sr)"
  44. local UPTIME; UPTIME="$(uptime -p)"
  45. # Load
  46. printf "\n"
  47. printf " Distro......: %s\n" "$DISTRO"
  48. printf " Kernel......: %s\n" "$KERNEL"
  49. printf " Uptime......: %s\n" "$UPTIME"
  50. printf " Load........: %b%s%b (1m), %b%s%b (5m), %b%s%b (15m)\n" \
  51. "$green" "$LOAD1" "$default" \
  52. "$green" "$LOAD5" "$default" \
  53. "$green" "$LOAD15" "$default"
  54. printf " Processes...: %b%d%b (root), %b%d%b (user), %b%d%b (total)\n" \
  55. "$green" "$ROOT_PROCS" "$default" \
  56. "$green" "$NONROOT_PROCS" "$default" \
  57. "$green" "$TOTAL_PROCS" "$default"
  58. printf " CPU.........: %s (%b%d%b vCPU)\n" \
  59. "$CPU_MODEL" "$green" "$CPU_COUNT" "$default"
  60. printf " Memory......: %b%s%b used, %b%s%b avail, %b%s%b total\n" \
  61. "$green" "$USED" "$default" \
  62. "$green" "$AVAIL" "$default" \
  63. "$green" "$TOTAL" "$default"
  64. # Disks
  65. echo -e "\n Disk usage"
  66. local max_usage=90 bar_width=50
  67. while read -r target pcent size; do
  68. local use=${pcent%\%}
  69. local filled=$(( use * bar_width / 100 ))
  70. local bar="["
  71. local col=$green
  72. (( use >= max_usage )) && col=$red
  73. bar+="$col"
  74. for ((i=0;i<filled;i++)); do bar+="="; done
  75. bar+="$default$dim"
  76. for ((i=filled;i<bar_width;i++)); do bar+="="; done
  77. bar+="$undim]"
  78. printf " %-25s %3s of %-6s\n" "$target" "$pcent" "$size"
  79. printf " %b\n" "$bar"
  80. done < <(
  81. df -H -x zfs -x squashfs -x tmpfs -x devtmpfs -x overlay \
  82. --output=target,pcent,size | tail -n +2
  83. )
  84. # Services
  85. echo -e "\n Services"
  86. local services=(
  87. btrfs-balance.timer btrfs-scrub.timer backup.timer btrbk.timer fstrim.timer
  88. fail2ban firewalld smb nmb motion smartd cockpit.socket
  89. dnf-automatic.timer motd.timer
  90. )
  91. for ((i=0; i<${#services[@]}; i+=2)); do
  92. local s1=${services[i]} s2=${services[i+1]:-}
  93. local st1 st2 c1 c2
  94. st1=$(systemctl is-active "$s1" 2>/dev/null || echo inactive)
  95. st2=$(systemctl is-active "$s2" 2>/dev/null || echo inactive)
  96. [[ $st1 == active ]] && c1="${green}$st1${default}" || c1="${red}$st1${default}"
  97. [[ $st2 == active ]] && c2="${green}$st2${default}" || c2="${red}$st2${default}"
  98. printf " %-15s : %b %-15s : %b\n" \
  99. "${s1%.*}" "$c1" "${s2%.*}" "$c2"
  100. done
  101. # Fail2Ban
  102. read -r -a jails <<< "$(fail2ban-client status | grep "Jail list:" | sed "s/ //g" | awk '{split($2,a,",");for(i in a) print a[i]}')"
  103. out="jail,failed,total,banned,total\n"
  104. for jail in "${jails[@]}"; do
  105. # Slow because fail2ban-client has to be called for every jail (~70ms per jail)
  106. status=$(fail2ban-client status "$jail")
  107. failed=$(echo "$status" | grep -ioP '(?<=Currently failed:\t)[[:digit:]]+')
  108. totalfailed=$(echo "$status" | grep -ioP '(?<=Total failed:\t)[[:digit:]]+')
  109. banned=$(echo "$status" | grep -ioP '(?<=Currently banned:\t)[[:digit:]]+')
  110. totalbanned=$(echo "$status" | grep -ioP '(?<=Total banned:\t)[[:digit:]]+')
  111. out+="$jail,$failed,$totalfailed,$banned,$totalbanned\n"
  112. done
  113. printf "\nFail2ban\n"
  114. printf "%b\n" "$out" | column -ts $',' | sed -e 's/^/ /'
  115. # Help links
  116. cat <<-EOF
  117. Links (ctrl+click to follow)
  118. Server Manual.........: https://tinyurl.com/jjz9h6fr
  119. Cockpit (for admins)..: http://localhost:9090
  120. Robot Camera..........: http://localhost:9999
  121. JupyterLab............: http://localhost:8888
  122. RStudio Server........: http://localhost:8787
  123. Robot Computer........: vnc://192.168.16.101:5900
  124. Windows 10 VM.........: vnc://localhost:5900 (pw: hartman)
  125. EOF
  126. # Scheduled reboot
  127. if systemctl is-active scheduled-reboot.timer &>/dev/null; then
  128. echo -n "Next scheduled reboot: "
  129. time=$(systemctl cat scheduled-reboot.timer | grep OnCalendar=)
  130. time=${time#*=}
  131. echo "$time"
  132. fi
  133. if systemctl is-active scheduled-reboot.timer &>/dev/null; then
  134. echo -n " Next reboot : "
  135. systemctl show -p OnCalendar scheduled-reboot.timer | cut -d= -f2
  136. fi
  137. printf "\n"
  138. }
  139. install_services() {
  140. # Write the service unit
  141. cat >"$SERVICE_UNIT" <<-EOF
  142. [Unit]
  143. Description=Generate MoTD
  144. [Service]
  145. Type=oneshot
  146. ExecStart=$INSTALL_PATH --motd > /etc/motd
  147. [Install]
  148. WantedBy=multi-user.target
  149. EOF
  150. # Write the timer unit
  151. cat >"$TIMER_UNIT" <<-EOF
  152. [Unit]
  153. Description=Generate MoTD every minute
  154. [Timer]
  155. OnCalendar=*:0/1
  156. OnBootSec=10s
  157. [Install]
  158. WantedBy=timers.target
  159. EOF
  160. chmod +x "$INSTALL_PATH"
  161. systemctl daemon-reload
  162. systemctl enable --now motd.timer
  163. echo "Installed and started motd.timer → $SERVICE_UNIT, $TIMER_UNIT"
  164. }
  165. main() {
  166. if [[ ${1:-} == --motd ]]; then
  167. print_motd
  168. exit 0
  169. fi
  170. if [[ ${1:-} == --install ]]; then
  171. if [[ $EUID -ne 0 ]]; then
  172. echo "Error: Must run as root to install." >&2
  173. exit 1
  174. fi
  175. cp -f "$0" "$INSTALL_PATH"
  176. install_services
  177. fi
  178. if [[ ${1:-} =~ ^(-h|--help)$ ]]; then
  179. usage
  180. exit 0
  181. fi
  182. }
  183. main "$@"