让你的 VPS 登录不再单调:打造炫酷实用的 MOTD
通过自定义 Shell 脚本制作 MOTD 欢迎界面,在每次 SSH 登录时展示系统状态、网络信息和维护提醒,兼顾美观与实用性。

前言:告别枯燥的 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
简单解读脚本
对于新手来说,不需要完全理解每一行代码。我们挑几个重点讲讲:
**#!/bin/bash**:这是脚本的”魔法开头”,告诉系统要用 Bash 这个程序来运行这个脚本。**# --- Configuration ---**:这里可以调整一些设置,比如LABEL_WIDTH控制左边标签的宽度,CURL_TIMEOUT控制获取公网 IP 的超时时间。**print_info()函数**:这是一个自定义的小工具,用来按固定格式打印每一行信息(标签 + 冒号 + 值),并自动对齐。- 信息收集部分:这是脚本的核心,它运行各种 Linux 命令来获取信息:
hostname:获取服务器名字。grep ... /etc/os-release:读取文件获取操作系统名称。uname:获取内核版本和架构。uptime、who:获取运行时间和登录用户。lscpu、/proc/cpuinfo:获取 CPU 信息。free:获取内存和 Swap 信息。df:获取磁盘使用情况。ip:获取本地 IP 与默认网关。curl https://ifconfig.me:通过外部服务查询公网 IP 地址。这需要你的 VPS 能访问互联网,并且已安装curl工具。**|| echo "N/A"**:很多命令后面跟着这个,表示如果前面的命令执行失败或没有找到信息,就显示N/A(Not Available),防止脚本报错或输出空白。
如何在你的 VPS 上使用
只需要几步:
- 通过 SSH 连接到你的 VPS。
- 创建脚本文件。把它放在用户主目录下,命名为
.my_motd.sh(前面的点.表示这是一个隐藏文件)。使用vim创建并编辑:
vim ~/.my_motd.sh
- 进入编辑模式:按
i键进入插入模式,然后粘贴上面的完整脚本代码。 - 保存并退出
vim:按Esc键退出插入模式,输入:wq并回车。 - 添加执行权限:
chmod +x ~/.my_motd.sh
- 让脚本在每次登录时自动运行。这需要编辑你的 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_IPV4和EXTERNAL_IPV6的几行代码,在行首加上#注释掉即可。 -
不需要某项信息? 找到脚本末尾
# --- Output Section ---中对应的print_info行,在行首加上#注释掉即可。
遇到问题?
- 没显示任何东西?
- 确认你编辑的是正确的启动文件(
~/.bash_profile、~/.profile或~/.zprofile)。 - 确认脚本路径(
~/.my_motd.sh)与启动文件里填写的路径完全一致。 - 确认脚本具有执行权限:运行
ls -l ~/.my_motd.sh,权限位中应包含x。
- 确认你编辑的是正确的启动文件(
- 公网 IP 显示
curl not found或Query Failed?- 系统可能没有安装
curl。尝试安装:- Debian/Ubuntu:
sudo apt update && sudo apt install curl -y - CentOS/RHEL:
sudo yum update && sudo yum install curl -y
- Debian/Ubuntu:
- 如果显示
Query Failed,可能是 VPS 网络暂时无法访问ifconfig.me,或者超时时间设置过短。
- 系统可能没有安装
- 系统安装时间显示
N/A或不准确? 这是正常的。Linux 本身没有记录系统”出生日期”的标准方法,脚本只是通过根文件系统的创建时间来估算。
结语
现在,你拥有了一个既美观又实用的 VPS 登录界面!这不仅能让你快速掌握服务器状态,也是一个学习 Linux 和 Shell 脚本的有趣实践。希望这篇新手友好的指南能帮助你轻松上手。享受你的 Linux 之旅吧!
Hook this up to your favourite commenting platform — Giscus, Disqus, or your own.
Continue reading

VPS 自建加密 DNS:AdGuard Home
用 VPS 部署 AdGuard Home,并通过 DNS-over-TLS 接入 Android 私人 DNS,打造属于自己的加密 DNS 服务

Serv00 新手到进阶:常用命令、保活与部署应用全攻略
汇总 Serv00 的常用命令、PHP 保活方案,以及搭建兰空图床、哪吒探针、New API 和 HY2 节点的完整步骤,适合需要在 Serv00 上部署项目的用户

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