VPS

让你的 VPS 登录不再单调:打造炫酷实用的 MOTD

通过自定义 Shell 脚本制作 MOTD 欢迎界面,在每次 SSH 登录时展示系统状态、网络信息和维护提醒,兼顾美观与实用性。

··updated August 8, 2026·2 min read

前言:告别枯燥的 SSH 登录

刚入手 VPS(Virtual Private Server,虚拟专用服务器) 或者开始接触 Linux 系统?当你通过 SSH(Secure Shell,安全外壳协议) 远程连接到服务器时,通常会看到一个简单的命令行提示符,或者一些非常基础的系统信息。是不是感觉有点单调,而且每次登录都想快速了解服务器的当前状态?

别担心!我们可以通过一个简单的 Shell 脚本,自定义你的 MOTD(Message of the Day,每日问候信息),让它在每次登录时显示丰富多彩、信息全面的系统概览。这不仅看起来更酷,而且非常实用!

想象一下,一登录就能看到:

  • 服务器名字和欢迎语
  • 操作系统版本、内核信息
  • CPU 型号、核心数
  • 内存和 Swap 使用情况
  • 磁盘占用率
  • 本地 IP、公网 IP(IPv4/IPv6)
  • 系统运行时间、负载、当前用户数
  • 系统安装时间、启动时间、当前时间

是不是很方便?下面我们就来一步步实现它!

最终效果预览(概念图)

                       |\__/,|   (`\
                     _.|o o  |_   ) )
       -------------(((---(((-------------------
 Hostname: phoenix1
─────────────────────────────────────────────────────────────
 💻 System Information
 OS                 : Ubuntu 24.04.2 LTS
 Kernel             : Linux 6.8.0-1021-oracle
 Architecture       : aarch64
 Shell              : bash
 Terminal           : xterm-256color
 🛠️ Hardware Information
 CPU Model          : Neoverse-N1
 CPU Cores          : 2
 Memory (RAM)       : 2.2Gi / 11Gi
 Swap               : 268Ki / 4.0Gi
 Disk (/)           : 15G / 96G (16% used)
 🌐 Network Information
 Local IP           : 10.0.0.239
 Gateway            : 10.0.0.1 10.0.0.1
 Public IPv4        : 129.146.50.251
 Public IPv6        : Query Failed/No IPv6
 DNS Servers        : 127.0.0.53
 ⏱️ Status & Time
 System Install     : 2025-02-06 00:08:58 (approx, /etc/os-release)
 System Boot        : 2025-04-06 13:35
 System Current     : 2025-04-12 10:35:28 CST
 Uptime             : 5 days, 20 hours, 59 minutes
 Load Average       : 0.00 0.01 0.00
 Current Users      : 1
 System Age         : 0y 2m 5d
─────────────────────────────────────────────────────────────

注意:上面的 IP 地址、时间和具体硬件信息仅为示例。

获取脚本代码

以下就是我们要使用的 Shell 脚本。它使用了很多 Linux 内建或常见的命令来收集信息,并通过 printf 命令对齐输出。将完整代码保存为 ~/.my_motd.sh

#!/bin/bash

# --- Configuration ---
LABEL_WIDTH=25     # Width for labels
CURL_TIMEOUT=5     # Timeout for external IP check

# --- NO Colors / Styles ---
# Ensuring plain text output
C_RESET='' C_BORDER='' C_HEADER='' C_LABEL='' C_VALUE='' C_VALUE_EMPHASIS=''
C_STATUS_OK='' C_STATUS_WARN='' C_STATUS_CRIT='' C_INFO='' C_MUTED=''
BOLD='' RESET=''

# --- Helper Function for Printing Lines ---
print_info() {
    local label_text="$1"
    local value_text="$2"
    # Handle potential multi-line values by replacing newline with spaces
    local cleaned_value_text=$(printf "%s" "${value_text}" | tr '\n' ' ')
    printf "%-${LABEL_WIDTH}s : %s\n" "${label_text}" "${cleaned_value_text}"
}

# --- Helper Function for Section Headers with Icons and Separator ---
print_header() {
    local title="$1"
    local icon=""
    # Define the separator line (using Unicode box drawing character)
    local separator="──────────────────────────────────────────────────" # Adjust length if needed

    # Select icon based on title
    case "$title" in
        "System")           icon="⚙️ " ;;
        "Hardware")         icon="🛠️ " ;;
        "Network")          icon="🌐 " ;;
        "Session & Time")   icon="🕰️ " ;;
        "Maintenance")      icon="🔧 " ;;
        *)                  icon="" ;; # Default: no icon
    esac

    echo "" # Add space before separator/header
    echo "${separator}" # Print the separator line
    echo "${icon}${title}:"
    echo "" # Add space after header title
}

# --- Linux Information Gathering ---

# System Info
HOSTNAME=$(hostname)
# Get OS Info from /etc/os-release (more reliable than lsb_release)
OS_INFO="N/A"
if [ -f /etc/os-release ]; then
    OS_INFO=$(grep PRETTY_NAME= /etc/os-release | sed 's/PRETTY_NAME=//g' | tr -d '"')
elif [ -f /etc/lsb-release ]; then
    OS_INFO=$(lsb_release -ds 2>/dev/null || echo "N/A")
fi
KERNEL=$(uname -sr)
ARCH=$(uname -m)
SHELL_INFO=$(basename "$SHELL" || echo "N/A")
TERM_INFO="${TERM:-N/A}"

# Uptime (using pretty format) and User Count
UPTIME_STR=$(uptime -p | sed 's/^up //' || echo "N/A") # Get pretty uptime string like "1 day, 2 hours, 34 minutes"
USERS=$(who | wc -l | awk '{print $1}') # Get user count separately

# Hardware & Resource Info
# CPU Info - try lscpu first, fallback to /proc/cpuinfo
CPU_INFO=$(lscpu | grep "Model name:" | sed 's/Model name:[[:space:]]*//' 2>/dev/null)
if [ -z "$CPU_INFO" ]; then
    CPU_INFO=$(grep "model name" /proc/cpuinfo | head -n 1 | sed 's/model name[[:space:]]*: //')
fi
[ -z "$CPU_INFO" ] && CPU_INFO="N/A"

CPU_CORES_LOGICAL=$(grep -c ^processor /proc/cpuinfo 2>/dev/null || echo "N/A")
# Physical cores might need more complex parsing depending on hyperthreading, get logical for simplicity
CPU_CORES_INFO="${CPU_CORES_LOGICAL} Logical"

# Memory and Swap (Get both human-readable and byte values)
MEM_SWAP_INFO_H=$(free -h 2>/dev/null)
MEM_SWAP_INFO_B=$(free -b 2>/dev/null) # Use -b for bytes explicitly

# Memory Info
MEM_INFO_H=$(echo "$MEM_SWAP_INFO_H" | grep "^Mem:" | awk '{printf "%s used / %s total", $3, $2}' || echo "N/A")
MEM_PERCENTAGE=""
MEM_VALUES_B=$(echo "$MEM_SWAP_INFO_B" | grep "^Mem:")
if [ -n "$MEM_VALUES_B" ]; then
    MEM_PERCENTAGE=$(echo "$MEM_VALUES_B" | awk '{if ($2 > 0) {printf "(%.0f%%)", $3/$2 * 100} else {printf "(0%%)"}}')
fi
MEM_INFO="${MEM_INFO_H} ${MEM_PERCENTAGE}"

# Swap Info
SWAP_INFO_H=$(echo "$MEM_SWAP_INFO_H" | grep "^Swap:" | awk '{printf "%s used / %s total", $3, $2}' || echo "N/A")
SWAP_PERCENTAGE=""
# Check if Swap line exists and total > 0
SWAP_VALUES_B=$(echo "$MEM_SWAP_INFO_B" | grep "^Swap:")
if [ -n "$SWAP_VALUES_B" ]; then
     # Check if total swap ($2) is greater than 0 before calculating percentage
    SWAP_PERCENTAGE=$(echo "$SWAP_VALUES_B" | awk '{if ($2 > 0) {printf "(%.0f%%)", $3/$2 * 100} else {printf "(0%%)"}}')
    SWAP_INFO="${SWAP_INFO_H} ${SWAP_PERCENTAGE}"
elif [[ "$SWAP_INFO_H" == *" 0B total"* || "$SWAP_INFO_H" == "N/A" ]]; then
    SWAP_INFO="N/A (Not configured or 0 size)"
else
    # Should not happen if free -h worked but free -b didn't, but handle anyway
    SWAP_INFO="${SWAP_INFO_H} (Percentage N/A)"
fi

# Disk Usage (Root volume, human readable) - Remains the same
DISK_ROOT_INFO_RAW=$(df -h / 2>/dev/null | awk 'NR==2 {printf "%s used / %s total", $3, $2}' || echo "N/A")
DISK_ROOT_PERCENT=$(df -h / 2>/dev/null | awk 'NR==2 {print $5}' || echo "N/A")
DISK_ROOT_INFO="${DISK_ROOT_INFO_RAW} (${DISK_ROOT_PERCENT})"

# Network Info (using 'ip' command, preferred over netstat/ifconfig)
PRIMARY_INTERFACE=$(ip route show default | awk '/default/ {print $5}' | head -n 1 || echo "N/A")
LOCAL_IP="N/A"
if [ "$PRIMARY_INTERFACE" != "N/A" ]; then
    LOCAL_IP=$(ip -4 addr show dev "$PRIMARY_INTERFACE" | grep "inet " | awk '{print $2}' | sed 's|/.*||' || echo "N/A")
fi
# Fallback if primary interface detection failed or has no IP
if [ "$LOCAL_IP" = "N/A" ]; then
    # Try common interfaces
    for iface in eth0 ens eth1 wlan0 wlp; do # Check common prefixes/names
        FOUND_IP=$(ip -4 addr show | grep "inet " | grep "$iface" | awk '{print $2}' | sed 's|/.*||' | head -n 1)
         if [ -n "$FOUND_IP" ]; then
            LOCAL_IP=$FOUND_IP
            PRIMARY_INTERFACE=$(ip -4 addr show | grep "inet $FOUND_IP" | awk '{print $NF}') # Get the interface name
            break
        fi
    done
    [ -z "$LOCAL_IP" ] && LOCAL_IP="N/A" # If still not found
fi

GATEWAY=$(ip route show default | awk '/default/ {print $3}' | head -n 1 || echo "N/A")

EXTERNAL_IPV4="Querying..."
EXTERNAL_IPV6="Querying..."
if command -v curl &> /dev/null; then
    EXTERNAL_IPV4=$(curl -4fs --connect-timeout ${CURL_TIMEOUT} https://ifconfig.me || echo "Query Failed")
    EXTERNAL_IPV6=$(curl -6fs --connect-timeout ${CURL_TIMEOUT} https://ifconfig.me || echo "No IPv6 / Failed")
else
    EXTERNAL_IPV4="curl not found"
    EXTERNAL_IPV6="curl not found"
fi

# DNS Servers from /etc/resolv.conf
DNS_SERVERS=$(grep "^nameserver" /etc/resolv.conf | awk '{print $2}' | paste -sd ' ' || echo "N/A")
[ -z "$DNS_SERVERS" ] && DNS_SERVERS="N/A"

# Time & Session Info
CURRENT_TIME=$(date +"%Y-%m-%d %H:%M:%S %Z" || echo "N/A")
# Boot time using who -b (simpler than parsing /proc/stat)
BOOT_TIME=$(who -b | sed 's/.*system boot//' | awk '{$1=$1};1' || echo "N/A") # {$1=$1};1 trims leading/trailing space

# --- START: Added System Creation and Total Runtime ---
SYSTEM_CREATION_TIME_STR="N/A"
TOTAL_RUNTIME_STR="N/A"
CREATION_EPOCH=0

# Find the device for the root filesystem '/'
ROOT_DEVICE=$(findmnt -n -o SOURCE / 2>/dev/null)

if [ -n "$ROOT_DEVICE" ] && [ -b "$ROOT_DEVICE" ]; then
    # Find the filesystem type
    FS_TYPE=$(findmnt -n -o FSTYPE / 2>/dev/null)

    if [[ "$FS_TYPE" == "ext2" || "$FS_TYPE" == "ext3" || "$FS_TYPE" == "ext4" ]]; then
        if command -v tune2fs &> /dev/null; then
            # Important: tune2fs typically requires root privileges
            # This script often runs as root when generating MOTD, so it should work
            FS_CREATED_STR=$(sudo tune2fs -l "$ROOT_DEVICE" 2>/dev/null | grep "Filesystem created:")
            if [ -n "$FS_CREATED_STR" ]; then
                # Extract the date string (e.g., "Sat Mar 16 10:30:00 2024")
                FS_CREATED_STR=$(echo "$FS_CREATED_STR" | sed 's/Filesystem created:\s*//')
                # Try to format it consistently and get epoch
                CREATION_EPOCH=$(date -d "$FS_CREATED_STR" +%s 2>/dev/null)
                if [ $? -eq 0 ] && [ "$CREATION_EPOCH" -gt 0 ]; then
                     # Format for display (YYYY-MM-DD HH:MM:SS)
                    SYSTEM_CREATION_TIME_STR=$(date -d "@$CREATION_EPOCH" +"%Y-%m-%d %H:%M:%S")
                else
                    SYSTEM_CREATION_TIME_STR="Parse Failed ($FS_CREATED_STR)"
                    CREATION_EPOCH=0 # Reset epoch if parsing failed
                fi
            else
                 SYSTEM_CREATION_TIME_STR="Failed to get creation time (tune2fs)"
            fi
        else
            SYSTEM_CREATION_TIME_STR="tune2fs not found"
        fi
    # Add checks for other filesystems like xfs if needed
    # elif [ "$FS_TYPE" == "xfs" ]; then ...
    else
        SYSTEM_CREATION_TIME_STR="Unsupported FS type ($FS_TYPE)"
    fi
else
    SYSTEM_CREATION_TIME_STR="Root device not found"
fi

# Calculate Total Runtime if creation epoch is known
if [ "$CREATION_EPOCH" -gt 0 ]; then
    CURRENT_EPOCH=$(date +%s)
    TOTAL_SECONDS=$((CURRENT_EPOCH - CREATION_EPOCH))

    if [ "$TOTAL_SECONDS" -lt 0 ]; then
        TOTAL_RUNTIME_STR="Clock skew detected?"
    else
        # Simple calculation for days, hours, minutes
        days=$((TOTAL_SECONDS / 86400))
        hours=$(((TOTAL_SECONDS % 86400) / 3600))
        minutes=$(((TOTAL_SECONDS % 3600) / 60))
        # Build the string
        RUNTIME_PARTS=()
        [ "$days" -gt 0 ] && RUNTIME_PARTS+=("${days}d")
        [ "$hours" -gt 0 ] && RUNTIME_PARTS+=("${hours}h")
        # Always show minutes if total seconds > 0, or if days/hours are 0
        if [ "$TOTAL_SECONDS" -gt 0 ] || { [ "$days" -eq 0 ] && [ "$hours" -eq 0 ]; }; then
             RUNTIME_PARTS+=("${minutes}m")
        fi

        if [ ${#RUNTIME_PARTS[@]} -gt 0 ]; then
            TOTAL_RUNTIME_STR=$(IFS=, ; echo "${RUNTIME_PARTS[*]}") # Join with commas
        else
            TOTAL_RUNTIME_STR="< 1 minute" # Handle very short times
        fi
         # Add seconds for more precision if needed:
         # seconds=$((TOTAL_SECONDS % 60))
         # TOTAL_RUNTIME_STR="${days}d ${hours}h ${minutes}m ${seconds}s"
    fi
fi
# --- END: Added System Creation and Total Runtime ---

# Last Login Info
LAST_LOGIN="N/A"
if command -v last &> /dev/null; then
    # Get the first relevant line for the current user, avoiding "still logged in" and header
    LAST_LOGIN_RAW=$(last -n 10 "${USER}" 2>/dev/null | grep -v "still logged in" | grep -v "^wtmp" | grep "${USER}" | head -n 1)
    if [[ -n "$LAST_LOGIN_RAW" ]]; then
        # Extract basic info: user, tty, source (IP/hostname), date/time range
        LAST_LOGIN=$(echo "$LAST_LOGIN_RAW" | awk '{ for(i=4; i<=NF; i++) printf "%s ", $i; print "" }' | sed 's/ $//') # Get time onwards
        LAST_LOGIN_SOURCE=$(echo "$LAST_LOGIN_RAW" | awk '{print $3}') # Get source
        LAST_LOGIN="From ${LAST_LOGIN_SOURCE} on ${LAST_LOGIN}"
    else
        LAST_LOGIN="No previous login recorded for ${USER}"
    fi
fi

# Maintenance/Alerts Info (Linux specific)
PENDING_UPDATES_INFO="N/A"
REBOOT_REQUIRED_INFO="N/A"

# Check for APT updates (Debian/Ubuntu)
if command -v apt-get &> /dev/null && [ -x /usr/lib/update-notifier/apt-check ]; then
    UPDATE_CHECK_OUTPUT=$(/usr/lib/update-notifier/apt-check 2>&1)
    UPDATES=$(echo "$UPDATE_CHECK_OUTPUT" | cut -d';' -f1)
    SECURITY_UPDATES=$(echo "$UPDATE_CHECK_OUTPUT" | cut -d';' -f2)
    if [[ "$UPDATES" =~ ^[0-9]+$ ]] && [[ "$SECURITY_UPDATES" =~ ^[0-9]+$ ]]; then
        if [ "$UPDATES" -eq 0 ] && [ "$SECURITY_UPDATES" -eq 0 ]; then
            PENDING_UPDATES_INFO="System is up to date"
        else
            PENDING_UPDATES_INFO="${UPDATES} updates pending (${SECURITY_UPDATES} security)"
        fi
    else
        PENDING_UPDATES_INFO="Check failed (apt-check error)"
    fi
# Add checks for other package managers like yum/dnf if needed
# elif command -v yum &> /dev/null; then ...
fi

# Check for reboot required flag
if [ -f /var/run/reboot-required ]; then
    REBOOT_REQUIRED_INFO="Yes - $(cat /var/run/reboot-required 2>/dev/null)"
else
    REBOOT_REQUIRED_INFO="No"
fi

# --- Output Section ---

echo "System Information for ${HOSTNAME}"
# No line here

print_header "System"
print_info "Operating System" "${OS_INFO}"
print_info "Kernel Version" "${KERNEL}"
print_info "Architecture" "${ARCH}"
print_info "Uptime (Since Boot)" "${UPTIME_STR}" # Renamed for clarity
# print_info "Load Average" "${LOAD_AVG}" # Still commented out

print_header "Hardware"
print_info "CPU Model" "${CPU_INFO}"
print_info "CPU Cores" "${CPU_CORES_INFO}"
print_info "Memory" "${MEM_INFO}"
print_info "Swap Usage" "${SWAP_INFO}"
print_info "Disk Usage (/)" "${DISK_ROOT_INFO}"

print_header "Network"
# Display interface name with IP if found
if [ "$LOCAL_IP" != "N/A" ]; then
    print_info "Local IP (${PRIMARY_INTERFACE})" "${LOCAL_IP}"
else
    print_info "Local IP" "N/A"
fi
print_info "Default Gateway" "${GATEWAY}"
print_info "Public IPv4" "${EXTERNAL_IPV4}"
print_info "Public IPv6" "${EXTERNAL_IPV6}"
print_info "DNS Servers" "${DNS_SERVERS}"

print_header "Session & Time"
print_info "Current Time" "${CURRENT_TIME}"
print_info "System Boot Time" "${BOOT_TIME}"
print_info "System Creation Time" "${SYSTEM_CREATION_TIME_STR}" # Added
print_info "Total Runtime" "${TOTAL_RUNTIME_STR}"            # Added
print_info "Shell" "${SHELL_INFO}"
print_info "Terminal" "${TERM_INFO}"
print_info "Current Users" "${USERS}"       # User count is still displayed here
print_info "Last Login (${USER})" "${LAST_LOGIN}"

print_header "Maintenance"
print_info "Software Updates" "${PENDING_UPDATES_INFO}"
print_info "Reboot Required" "${REBOOT_REQUIRED_INFO}"

# No line here before the cat

# --- ASCII Cat Footer ---
# Use single quotes to preserve characters literally
echo ''
echo '                       |\__/,|   (`\'
echo '                     _.|o o  |_   ) )'
echo '       -------------(((---(((-------------------'
echo ''

exit 0

简单解读脚本

对于新手来说,不需要完全理解每一行代码。我们挑几个重点讲讲:

  1. **#!/bin/bash**:这是脚本的”魔法开头”,告诉系统要用 Bash 这个程序来运行这个脚本。
  2. **# --- Configuration ---**:这里可以调整一些设置,比如 LABEL_WIDTH 控制左边标签的宽度,CURL_TIMEOUT 控制获取公网 IP 的超时时间。
  3. **print_info() 函数**:这是一个自定义的小工具,用来按固定格式打印每一行信息(标签 + 冒号 + 值),并自动对齐。
  4. 信息收集部分:这是脚本的核心,它运行各种 Linux 命令来获取信息:
  • hostname:获取服务器名字。
  • grep ... /etc/os-release:读取文件获取操作系统名称。
  • uname:获取内核版本和架构。
  • uptimewho:获取运行时间和登录用户。
  • lscpu/proc/cpuinfo:获取 CPU 信息。
  • free:获取内存和 Swap 信息。
  • df:获取磁盘使用情况。
  • ip:获取本地 IP 与默认网关。
  • curl https://ifconfig.me通过外部服务查询公网 IP 地址。这需要你的 VPS 能访问互联网,并且已安装 curl 工具。
  • **|| echo "N/A"**:很多命令后面跟着这个,表示如果前面的命令执行失败或没有找到信息,就显示 N/A(Not Available),防止脚本报错或输出空白。

如何在你的 VPS 上使用

只需要几步:

  1. 通过 SSH 连接到你的 VPS。
  2. 创建脚本文件。把它放在用户主目录下,命名为 .my_motd.sh(前面的点 . 表示这是一个隐藏文件)。使用 vim 创建并编辑:
 vim ~/.my_motd.sh
  1. 进入编辑模式:按 i 键进入插入模式,然后粘贴上面的完整脚本代码。
  2. 保存并退出 vim:按 Esc 键退出插入模式,输入 :wq 并回车。
  3. 添加执行权限
 chmod +x ~/.my_motd.sh
  1. 让脚本在每次登录时自动运行。这需要编辑你的 Shell 启动配置文件。根据你使用的 Shell 和 Linux 发行版,文件可能是 ~/.bash_profile~/.profile~/.zprofile
  • 使用 Bash 时,可以先尝试 ~/.bash_profile;如果不存在,再使用 ~/.profile
  • 使用 Zsh 时,编辑 ~/.zprofile

~/.bash_profile 为例(如果文件不存在,vim 会自动创建):

在文件末尾添加以下内容:

这段代码的含义是:检查 ~/.my_motd.sh 是否存在,如果存在就执行它。保存并退出 vim 即可。 7. 大功告成! 退出当前 SSH 会话(输入 exit),然后重新登录你的 VPS。现在你应该就能看到精心制作的、信息丰富的欢迎界面了!

简单定制(可选)

  • 启用颜色输出? 脚本默认以纯文本输出。如果想恢复颜色,可以在脚本的 # --- NO Colors / Styles --- 部分手动设置 ANSI 转义序列,例如:

    C_VALUE='\033[0;32m'      # 绿色
    C_LABEL='\033[0;36m'      # 青色
    C_RESET='\033[0m'         # 重置颜色
    BOLD='\033[1m'

    你可以在网上搜索 “bash color codes” 找到更多颜色选项。

  • 标签太宽或太窄? 修改配置区的 LABEL_WIDTH=25 这个数字即可。

  • 获取公网 IP 太慢? 如果你的网络状况不佳,可以调大 CURL_TIMEOUT=5 这个值;如果完全不需要公网 IP 信息,找到获取 EXTERNAL_IPV4EXTERNAL_IPV6 的几行代码,在行首加上 # 注释掉即可。

  • 不需要某项信息? 找到脚本末尾 # --- Output Section --- 中对应的 print_info 行,在行首加上 # 注释掉即可。

遇到问题?

  • 没显示任何东西?
    • 确认你编辑的是正确的启动文件(~/.bash_profile~/.profile~/.zprofile)。
    • 确认脚本路径(~/.my_motd.sh)与启动文件里填写的路径完全一致。
    • 确认脚本具有执行权限:运行 ls -l ~/.my_motd.sh,权限位中应包含 x
  • 公网 IP 显示 curl not foundQuery Failed
    • 系统可能没有安装 curl。尝试安装:
      • Debian/Ubuntu:sudo apt update && sudo apt install curl -y
      • CentOS/RHEL:sudo yum update && sudo yum install curl -y
    • 如果显示 Query Failed,可能是 VPS 网络暂时无法访问 ifconfig.me,或者超时时间设置过短。
  • 系统安装时间显示 N/A 或不准确? 这是正常的。Linux 本身没有记录系统”出生日期”的标准方法,脚本只是通过根文件系统的创建时间来估算。

结语

现在,你拥有了一个既美观又实用的 VPS 登录界面!这不仅能让你快速掌握服务器状态,也是一个学习 Linux 和 Shell 脚本的有趣实践。希望这篇新手友好的指南能帮助你轻松上手。享受你的 Linux 之旅吧!

Share
Comments

Hook this up to your favourite commenting platform — Giscus, Disqus, or your own.

Continue reading

VPS·

新建 Oracle VPS 的推荐设置

针对 Oracle Cloud Ubuntu VPS 的新机初始化指南:清理默认 iptables、配置 UFW 防火墙、使用 Fail2ban 自动封禁攻击 IP,并完成 SSH 端口加固。

· 2 min