如何让你的Linux服务器有一个炫酷的motd

2023-04-21

motd,全称Message Of The Day,在类Unix系统中用于向用户发送欢迎消息。其消息内容通常存储在/etc/motd中,会在用户首次连接时显示出来。但/etc/motd只能存放静态的文本。

本文将为读者介绍如何实现动态motd,并自定义一个炫酷的motd。

不同Linux发行版对motd的实现方式不同,Debian系除了显示/etc/motd的内容外还会显示/var/run/motd.dynamic的内容。这是根据/etc/update-motd.d/里的shell脚本动态生成的。所以我们只需要修改/etc/update-motd.d/里的脚本,就可以自定义motd了。

如果你用的是非Debian系Linux(笔者的服务器用的是archlinux),可以用仿照Debian的目录布局,存入自己的脚本,以定时任务的形式实现动态motd。
执行sudo crontab -e填入以下内容:

*/10 * * * * run-parts /etc/update-motd.d/ > /etc/motd

这样,每十分钟就会按数字顺序(使用run-parts实现)执行/etc/update-motd.d/中的脚本文件,并将脚本结果输出/etc/motd中。

在笔者看来,一个完美的motd应至少能显示:

  1. 服务器基本信息
  2. 系统运行信息
  3. 服务器到期时间

为此,先将update-motd.d里的脚本移走备份一下,然后分别用三个脚本来实现我们想要的功能。

  1. 显示服务器名称 /etc/update-motd.d/10-uname
#!/bin/bash
export TERM=xterm-256color
. /etc/os-release
echo "$(tput setaf 6)Host: TencentCloud Lighthouse Shanghai
$(tput setaf 4)OS: $PRETTY_NAME"

调用笔者的API来生成ASCII Art风格的logo

curl -s "https://api.zjm.im/ascii/?text=tencent&font=future&type=1&export=sh" >> /etc/update-motd.d/10-uname

这里的字体和样式都是可以更改的,你可以选择你喜欢的。 其中,text参数为文本内容,font参数为字体,type参数指定样式,取值为0, 1, 2,分别对应null, metal, rainbow
详细用法见 https://api.zjm.im/ascii/
更多字体见 xero/figlet-fonts: my collection of figlet / toilet ascii art fonts (github.com)

当然,你也可以安装toilet或者figlet来自己生成ASCII Art。安装完toilet后,可以用以下脚本来预览系统中所有的figlet字体。

for f in /usr/share/figlet/*.{tlf,flf}; do
    font=$(basename -- "$f")
    font="${font%.*}"
    echo "$font"
    toilet -f "$font" -F metal "$font"
done
  1. 系统运行信息 /etc/update-motd.d/20-sysinfo
#!/bin/bash
STORAGE=/dev/sda1

function display() {
        # $1=name $2=value $3=red_limit $4=minimal_show_limit $5=unit $6=after $7=acs/desc{
        if [[ -n "$2" && "$2" > "0" && (( "${2%.*}" -ge "$4" )) ]]; then
        printf "%-14s%s" "$1:"
                if awk "BEGIN{exit ! ($2 > $3)}"; then echo -ne "\e[0;91m $2"; else echo -ne "\e[0;92m $2"; fi
                printf "%-1s%s\x1B[0m" "$5"
                printf "%-11s%s\t" "$6"
                return 1
        fi
} # display

function storage_info() {
        # storage info
        RootInfo=$(df -h /)
        root_usage=$(awk '/\// {print $(NF-1)}' <<<${RootInfo} | sed 's/%//g')
        root_total=$(awk '/\// {print $(NF-4)}' <<<${RootInfo})
        StorageInfo=$(df -h $STORAGE 2>/dev/null | grep $STORAGE)
        if [[ -n "${StorageInfo}" && ${RootInfo} != *$STORAGE* ]]; then
                storage_usage=$(awk '/\// {print $(NF-1)}' <<<${StorageInfo} | sed 's/%//g')
                storage_total=$(awk '/\// {print $(NF-4)}' <<<${StorageInfo})
        fi
} # storage_info

# query various systems and send some stuff to the background for overall faster execution.
storage_info
critical_load=80

UPTIME=$(LC_ALL=C uptime)
load=${UPTIME#*'load average: '}
load=${load//','}
load=$(echo $load | cut -d" " -f1)
[[ $load == 0.0* ]] && load=0.10
cpucount=$(grep -c processor /proc/cpuinfo)

load=$(awk '{printf("%.0f",($1/$2) * 100)}' <<< "$load $cpucount")

# memory and swap
mem_info=$(LC_ALL=C free -w 2>/dev/null | grep "^Mem" || LC_ALL=C free | grep "^Mem")
memory_usage=$(awk '{printf("%.0f",(($2-($4+$6+$7))/$2) * 100)}' <<<${mem_info})
mem_info=$(echo $mem_info | awk '{print $2}')
memory_total=$(( mem_info / 1024 ))
swap_info=$(LC_ALL=C free -m | grep "^Swap")
swap_usage=$( (awk '/Swap/ { printf("%3.0f", $3/$2*100) }' <<<${swap_info} 2>/dev/null || echo 0) | tr -c -d '[:digit:]')
swap_total=$(awk '{print $(2)}' <<<${swap_info})

# display info
display "System load" "${load%% *}" "${critical_load}" "0" "%" ""

if [[ ${memory_total} -gt 1000 ]]; then
         memory_total=$(awk '{printf("%.2f",$1/1024)}' <<<${memory_total})"G"
else
        memory_total+="M"
fi

if [[ ${swap_total} -gt 500 ]]; then
        swap_total=$(awk '{printf("%.2f",$1/1024)}' <<<${swap_total})"G"
else
        swap_total+="M"
fi

display "Memory usage" "$memory_usage" "70" "0" "%" " of ${memory_total}"
echo "" # fixed newline
display "Zram usage" "$swap_usage" "75" "0" "%" " of ${swap_total}"
display "Usage of /" "$root_usage" "90" "1" "%" " of $root_total"
display "storage/" "$storage_usage" "90" "1" "%" " of $storage_tota"
if [[ -n "${swap_usage}" && "${swap_usage}" > "0" ]]; then echo ""; fi
printf "Up time:       \x1B[92m%s\x1B[0m days\t\t" "$(( $(awk -F . '{print $1}' /proc/uptime) / 86400))"
echo ""
  1. 服务器到期时间 /etc/update-motd.d/30-countdown
#!/bin/bash
target_date="Sep 1 2024" # the expire time of the server
today=`echo $(($(date --utc --date "$1" +%s)/86400))`
target=`echo $(($(date --utc --date "$target_date" +%s)/86400))`
days=`expr $target - $today`
case $days in
  0) echo -e "\033[31mWARNING! THE SERVER WILL EXPIRE TODAY.\033[0m";;
  [0-9]) echo -e "\033[33mNOTICE\033[0m: The server will expire on $target_date after \033[31m$days\033[0m days. ";;
  [0-9]*) echo -e "The server will expire on $target_date after \033[33m$days\033[0m days.";;
esac

最后,给脚本添加运行权限

sudo chmod +x -R /etc/update-motd.d/

在Cloudflare Workers上实现SOCKS5客户端

几种排序算法的实现与测速