马春杰杰 Exit Reader Mode

Autodl私有云如何修改终端欢迎界面

这是默认的:

目前是有以下几点想要修改:

1、在顶部增加一个公告栏,方便给学生通知各种信息。

2、在目录处增加共享目录的介绍(新挂载了/datasets等目录,但是默认欢迎界面中不显示这些,所以就想显式地介绍下)

目前情况是,实例开机之后,会自动运行/etc/autodl-motd文件,然后Autodl私有云管理后台提供了两个钩子,一个是“容器创建完成后执行脚本”,一个是“容器每次开机后执行脚本”。目前的思路是,创建一个新的欢迎脚本,在容器第一次创建之后,替换原有的autodl-motd文件,并增加公告栏,通过远程文件进行更新,每次连接SSH的时候,都会自动获取远端公告信息,如果有更新就显示公告栏,如果没有公告就不显示公告栏。

下面是效果,首先是无公告,增加数据集目录介绍:

然后是远端更新公告之后:

操作步骤

由于代码较多,所以建议将脚本存放在远端存储,比如又拍云。我们新建一个文件vpms.sh,链接地址是:https://xxx.xxxx.com/xxxx/vpms.sh,填入:

#!/usr/bin/env bash
set -euo pipefail

# 备份原厂 motd
cp -a /etc/autodl-motd "/etc/autodl-motd.bak.$(date +%s)" 2>/dev/null || true

# 覆盖为定制 motd
cat >/etc/autodl-motd <<'MOTD'
#!/bin/bash
if [[ $- == *i* ]]; then
  printf "+-----------------------------------------------AutoDL-----------------------------------------------------+\n"

  # ===== CJK-safe ASCII 公告:边框黄,正文红,meta 黄,支持 \n 换行 =====
  draw_notice_box() {
    local body_raw="$1" mtime="$2" exp="$3"
    # 展开 \n
    local body="$(printf '%b' "$body_raw")"

    local cols boxw
    cols=${COLUMNS:-$(tput cols 2>/dev/null || echo 100)}
    [ "$cols" -lt 60 ] && cols=60
    boxw=$(( cols - 4 ))
    [ "$boxw" -gt 100 ] && boxw=100

    # 颜色
    local Y="\033[1;33m" R="\033[1;31m" N="\033[0m"

    # 顶部横线 + 居中标题
    local title=" 公告 "
    local line; line=$(printf '%*s' "$boxw" '' | tr ' ' '-')
    local padL=$(( (boxw - ${#title}) / 2 )); [ $padL -lt 1 ] && padL=1
    local padR=$(( boxw - padL - ${#title} ))

    printf "${Y}+%s+${N}\n" "$line"
    tput sc
    printf "${Y}|%*s%s%*s${N}" "$padL" "" "$title" "$padR" ""
    tput rc; tput cuf $((boxw+1)); printf "${Y}|${N}\n"

    # 正文(红色)
    local L
    while IFS= read -r L; do
      tput sc
      printf "${Y}| ${R}%s${N}" "$L"
      tput rc; tput cuf $((boxw+1)); printf "${Y}|${N}\n"
    done <<< "$body"

    # 空一行(正文和时间之间)
    if [ -n "$mtime" ] || [ -n "$exp" ]; then
      tput sc; printf "${Y}|${N}"; tput rc; tput cuf $((boxw+1)); printf "${Y}|${N}\n"
    fi

    # meta 信息(黄色)
    if [ -n "$mtime" ] || [ -n "$exp" ]; then
      local meta=""
      [ -n "$mtime" ] && meta="最后更新:$mtime"
      [ -n "$exp" ] && meta="${meta:+$meta | }有效期:$exp"
      tput sc
      printf "${Y}| ${Y}%s${N}" "$meta"
      tput rc; tput cuf $((boxw+1)); printf "${Y}|${N}\n"
    fi

    # 底部横线
    printf "${Y}+%s+${N}\n\n" "$line"
  }


  # ===== 公告读取(/etc/autodl-notice;首行可为 EXPIRES=...;正文空则不显示)=====
  NOTICE="/etc/autodl-notice"
  if [ -s "$NOTICE" ]; then
    # 从文件中读取 meta 行
    mt=""
    first=$(head -n1 "$NOTICE" 2>/dev/null || true)
    exp=$(echo "$first" | sed -n 's/^EXPIRES=\(.*\)$/\1/p')
    mt_epoch=$(grep -m1 '^MTIME_EPOCH=' "$NOTICE" | cut -d= -f2 || true)

    body="$(grep -vE '^(EXPIRES=|MTIME_EPOCH=)' "$NOTICE")"
    body_trimmed="$(echo "$body" | sed '/^[[:space:]]*$/d')"
    show=1
    if [ -n "$exp" ]; then
      now=$(date +%s); ts=$(date -d "$exp" +%s 2>/dev/null || echo 0)
      [ "$ts" -gt 0 ] && [ "$now" -ge "$ts" ] && show=0
    fi
    [ -z "$body_trimmed" ] && show=0
    if [ "$show" -eq 1 ]; then
      if [ -n "$mt_epoch" ]; then
        mt="$(date -d "@$mt_epoch" '+%Y-%m-%d %H:%M:%S %Z' 2>/dev/null || true)"
      fi
      draw_notice_box "$body" "$mt" "$exp"
    fi

  fi

  # ===== 目录说明(含 nas、fs、datasets、datasets2)=====
  printf "\033[1;32m目录说明:\033[0m\n"
  printf "╔═════════════════╦════════╦════╦═════════════════════════════════════════════════════════════════════════╗\n"
  printf "║目录             ║名称    ║速度║说明                                                                     ║\n"
  printf "╠═════════════════╬════════╬════╬═════════════════════════════════════════════════════════════════════════╣\n"
  printf "║/                ║系 统 盘║一般║实例关机数据不会丢失,可存放代码等。会随保存镜像一起保存。               ║\n"
  printf "║/root/autodl-tmp ║数 据 盘║ 快 ║实例关机数据不会丢失,可存放读写IO要求高的数据。但不会随保存镜像一起保存 ║\n"
  if [ -d /root/autodl-nas ]; then
    printf "║/root/autodl-nas ║网    盘║ 慢 ║多实例同步共享,不受开关机与保存镜像影响。                               ║\n"
  fi
  if [ -d /root/autodl-fs ]; then
    printf "║/root/autodl-fs  ║文件存储║一般║多实例同步共享,不受开关机与保存镜像影响。                               ║\n"
  fi
  if [ -d /datasets ]; then
    printf "║/datasets        ║数 据 集║只读║\033[1;34m共享只读数据集\033[0m,可直接查找使用;如需新增请联系贾浩或其他老师。           ║\n"
  fi
  if [ -d /datasets2 ]; then
    printf "║/datasets2       ║数 据 集║只读║\033[1;34m共享只读数据集\033[0m,可直接查找使用;如需新增请联系贾浩或其他老师。           ║\n"
  fi
  printf "╚═════════════════╩════════╩════╩═════════════════════════════════════════════════════════════════════════╝\n"

  # ===== CPU / 内存(cgroup v1 & v2 兼容)=====
  if [ -f /sys/fs/cgroup/cpu/cpu.cfs_quota_us ]; then
    q=$(cat /sys/fs/cgroup/cpu/cpu.cfs_quota_us); p=$(cat /sys/fs/cgroup/cpu/cpu.cfs_period_us)
    if [ "$q" -ge "$p" ]; then cores=$((q/p)); else cores=0.$((q*10/p)); fi
    printf "\033[1;32mCPU :\033[0m%s 核心\n" "$cores"
    m=$(cat /sys/fs/cgroup/memory/memory.limit_in_bytes); printf "\033[1;32m内存:\033[0m%s GB\n" $((m/1024/1024/1024))
  else
    cores=$(awk '{print $1/$2}' /sys/fs/cgroup/cpu.max 2>/dev/null); printf "\033[1;32mCPU :\033[0m%s 核心\n" "$cores"
    m=$(cat /sys/fs/cgroup/memory.max 2>/dev/null); printf "\033[1;32m内存:\033[0m%s GB\n" $((m/1024/1024/1024))
  fi

  # ===== GPU/NPU 检测 =====
  if type nvidia-smi >/dev/null 2>&1; then
    gpu=$(nvidia-smi -i 0 --query-gpu=name,count --format=csv,noheader)
    printf "\033[1;32mGPU :\033[0m%s\n" "${gpu}"
  elif type npu-smi >/dev/null 2>&1; then
    npu_num=$(ls /dev/davinci* 2>/dev/null | wc -l)
    printf "\033[1;32mGPU :\033[0mAscend, $((npu_num-1))\n"
  elif type mthreads-gmi >/dev/null 2>&1; then
    npu_num=$(ls /dev/mtgpu* 2>/dev/null | wc -l)
    gpu=$(mthreads-gmi -q | grep 'Product Name' | awk -F ':' '{print $2}')
    printf "\033[1;32mGPU :\033[0m%s, %s\n" "${gpu// /}, ${npu_num}"
  fi

  # ===== 存储(含 nas/fs/datasets*;datasets* 显示只读标记)=====
  df_stats="$(df -ah)"
  printf "\033[1;32m存储:\033[0m\n"
  disk=$(echo "$df_stats" | awk '$NF=="/"{print $5" "$3"/"$2}'); printf "  系 统 盘/               :%s\n" "${disk}"
  if [ -d /root/autodl-tmp ]; then
    disk=$(echo "$df_stats" | awk '$NF=="/root/autodl-tmp"{print $5" "$3"/"$2}')
    [ -z "$disk" ] && disk="未挂载"
    printf "  数 据 盘/root/autodl-tmp:%s\n" "${disk}"
  fi
  if [ -d /root/autodl-nas ]; then
    disk=$(echo "$df_stats" | awk '$NF=="/root/autodl-nas"{print $5" "$3"/"$2}')
    [ -z "$disk" ] && disk="未挂载"
    printf "  网    盘/root/autodl-nas:%s\n" "${disk}"
  fi
  if [ -d /root/autodl-fs ]; then
    disk=$(echo "$df_stats" | awk '$NF=="/root/autodl-fs"{print $5" "$3"/"$2}')
    [ -z "$disk" ] && disk="未挂载"
    printf "  文件存储/root/autodl-fs :%s\n" "${disk}"
  fi
  if [ -d /datasets ]; then
    d1=$(echo "$df_stats" | awk '$NF=="/datasets"{print $5" "$3"/"$2}'); [ -z "$d1" ] && d1="未挂载"
    m1=$(awk '$2=="/datasets"{print $4}' /proc/mounts 2>/dev/null); ro1=""; echo "$m1" | tr ',' '\n' | grep -qx ro && ro1=" (只读)"
    printf "  \033[1;34m数 据 集/datasets       :%s%s\033[0m\n" "$d1" "$ro1"
  fi
  if [ -d /datasets2 ]; then
    d2=$(echo "$df_stats" | awk '$NF=="/datasets2"{print $5" "$3"/"$2}'); [ -z "$d2" ] && d2="未挂载"
    m2=$(awk '$2=="/datasets2"{print $4}' /proc/mounts 2>/dev/null); ro2=""; echo "$m2" | tr ',' '\n' | grep -qx ro && ro2=" (只读)"
    printf "  \033[1;34m数 据 集/datasets2      :%s%s\033[0m\n" "$d2" "$ro2"
  fi

  printf "+----------------------------------------------------------------------------------------------------------+\n"
  printf "\033[1;31m*注意:\033[0m\n"
  printf "\033[1;31m1.系统盘较小请将大的数据存放于数据盘或文件存储中,重置系统时数据盘和文件存储中的数据不受影响\033[0m\n"
  printf "\033[1;31m2.清理系统盘请参考:https://www.autodl.com/docs/qa1/\033[0m\n"
  printf "\033[1;31m3.终端中长期执行命令请使用screen等工具开后台运行,确保程序不受SSH连接中断影响:https://www.autodl.com/docs/daemon/\033[0m\n"
  printf "\033[1;31m4./datasets 与 /datasets2 为共享只读数据集目录,可直接查找使用;如需新增请联系贾浩或其他老师。\033[0m\n"
  printf "\033[1;31m5.除了本地磁盘,大家还可以用阿里云、夸克云直接进行上传、下载。可以点击“AutoPanel”工具进行使用。\033[0m\n"

  # ===== A4000 / A5000: 设置 NCCL_P2P_LEVEL=NVL(若未设置)=====
  if type nvidia-smi >/dev/null 2>&1; then
    if echo "$gpu" | grep -Eq 'A4000|A5000'; then
      if ! grep -q 'NCCL_P2P_LEVEL' /etc/profile 2>/dev/null; then
        echo 'export NCCL_P2P_LEVEL=NVL' >> /etc/profile
        # shellcheck disable=SC1091
        source /etc/profile
      fi
    fi
  fi

  alias sudo=""
fi
MOTD
chmod +x /etc/autodl-motd

# 安装“每次 SSH 登录自动刷新公告”的钩子(从你的 vpms2.sh 拉取并执行)
install -d /etc/profile.d
cat >/etc/profile.d/20-adl-notice-refresh.sh <<'SH'
# 仅交互式终端执行;拉取远端脚本刷新/清空公告(2秒超时,失败静默)
if ! [ -t 1 ] || { [ -z "$PS1" ] && [ -z "$ZSH_VERSION" ]; }; then
  return 0
fi
( curl -fsSL --max-time 2 https://ypyssl.machunjie.com/vpms/vpms2.sh \
  || wget -qO- --timeout=2 https://ypyssl.machunjie.com/vpms/vpms2.sh ) \
| bash >/dev/null 2>&1 || :
SH
chmod +x /etc/profile.d/20-adl-notice-refresh.sh

echo "[init] motd installed & login hook ready."

接下来是公告脚本,再新建个脚本vpms2.sh,填入:

#!/usr/bin/env bash
set -euo pipefail

SELF_URL="https://xxx.xxxx.com/xxxx/vpms2.sh"

NOTICE="目前是测试阶段。\n如有问题及时反馈~"
UNTIL="2025-12-31 23:59:59"

trimmed_body="$(printf '%b' "$NOTICE" | sed '/^[[:space:]]*$/d')"

T="$(mktemp)"; trap 'rm -f "$T"' EXIT

# 抓取 HTTP 响应头(跟随重定向,静默,失败不退出)
hdr="$(curl -fsSLI --max-time 10 --connect-timeout 5 --retry 2 --retry-delay 1 "$SELF_URL" || true)"

# 取 HTTP 头,大小写无关匹配(兼容 mawk)
lm="$(
  printf '%s\n' "$hdr" | awk '
    { s=$0; gsub(/\r$/, "", s); t=tolower(s) }
    t ~ /^last-modified:/ { sub(/^[^:]*:[[:space:]]*/, "", s); l=s }
    t ~ /^date:/          { sub(/^[^:]*:[[:space:]]*/, "", s); d=s }
    END { if (l!="") print l; else print d; }
  '
)"

# 将 Last-Modified / Date 解析为 epoch 秒
mtime_epoch=""
[ -n "$lm" ] && mtime_epoch="$(date -d "$lm" +%s 2>/dev/null || true)"

# 若解析失败,给个保底(可改成留空)
[ -z "$mtime_epoch" ] && mtime_epoch="$(date +%s)"

# 组装元数据 + 正文
[ -n "$UNTIL" ]        && printf 'EXPIRES=%s\n'     "$UNTIL"        >"$T" || :
[ -n "$mtime_epoch" ]  && printf 'MTIME_EPOCH=%s\n' "$mtime_epoch" >>"$T" || :
[ -n "$trimmed_body" ] && printf '%b\n'             "$NOTICE"      >>"$T" || :

# 空公告 => 删除
if [ ! -s "$T" ]; then
  [ -e /etc/autodl-notice ] && rm -f /etc/autodl-notice && echo "[notice] cleared (file removed)" || echo "[notice] already empty"
  exit 0
fi

# 内容未变 => 不覆盖
if [ -e /etc/autodl-notice ] && cmp -s "$T" /etc/autodl-notice; then
  echo "[notice] unchanged (mtime preserved)"
  exit 0
fi

# 覆盖
install -m 0644 -o root -g root "$T" /etc/autodl-notice
echo "[notice] updated -> /etc/autodl-notice (MTIME_EPOCH=$mtime_epoch)"

其中NOTICE字段就是公告内容,通过\n换行。另外,此处如果为空,就不显示公告栏~

最后打开Autodl私有云后台–系统配置容器实例配置,在“容器创建完成后执行脚本”处填写:

curl -sSf https://xxx.xxxx.com/xxxx/vpms.sh | bash

这样就可以了~

另外,如果想要修改目录说明下面的表格,可以用下面这个小脚本,免除了手动调整表格格式的痛苦~

#!/usr/bin/env bash
set -euo pipefail

# ===== Colors (use ANSI C quoting so ESC is real, not '\033' literal) =====
C_GREEN=$'\033[1;32m'
C_BLUE=$'\033[1;34m'
C_RED=$'\033[1;31m'
C_RESET=$'\033[0m'

# ===== Column widths (visible cells) =====
W1=17   # 目录
W2=8    # 名称
W3=4    # 速度
W4=73   # 说明

# Pad a string (may contain ANSI colors) to visible width
pad_ansi() {
  local s="$1" width="$2"
  python3 - "$s" "$width" <<'PY'
import sys, re, unicodedata
s     = sys.argv[1]
width = int(sys.argv[2])
# strip ANSI CSI sequences
plain = re.sub(r'\x1b\[[0-9;]*[ -/]*[@-~]', '', s)
# visible width: fullwidth counted as 2
w = 0
for ch in plain:
    w += 2 if unicodedata.east_asian_width(ch) in ('F','W') else 1
pad = max(0, width - w)
sys.stdout.write(s + ' ' * pad)
PY
}

print_row() {
  printf "║"; pad_ansi "$1" "$W1"
  printf "║"; pad_ansi "$2" "$W2"
  printf "║"; pad_ansi "$3" "$W3"
  printf "║"; pad_ansi "$4" "$W4"
  printf "║\n"
}

# ===== Table =====
printf "${C_GREEN}目录说明:${C_RESET}\n"
printf "╔═════════════════╦════════╦════╦═════════════════════════════════════════════════════════════════════════╗\n"
print_row "目录" "名称" "速度" "说明"
printf "╠═════════════════╬════════╬════╬═════════════════════════════════════════════════════════════════════════╣\n"

print_row "/"               "系统盘"   "正常" "实例关机数据不会丢失,可存放代码等。会随保存镜像一起保存。"
print_row "/root/autodl-tmp" "数据盘"   "正常" "实例关机数据不会丢失,可存放读写IO要求高的数据。但不会随保存镜像一起保存"

if [ -d /root/autodl-nas ]; then
  print_row "/root/autodl-nas" "网盘"     "慢"   "多实例同步共享,不受开关机与保存镜像影响。"
fi
if [ -d /root/autodl-fs ]; then
  print_row "/root/autodl-fs"  "文件存储" "一般" "多实例同步共享,不受开关机与保存镜像影响。"
fi
if [ -d /datasets ]; then
  print_row "/datasets"        "数据集"   "只读" "${C_BLUE}共享只读数据集${C_RESET},包括${C_RED}Xray、xxxx、xxxx等${C_RESET}。"
fi
if [ -d /datasets2 ]; then
  print_row "/datasets2"       "数据集"   "只读" "${C_BLUE}共享只读数据集${C_RESET},包括${C_RED}篡改检测、xxx、xxxx、xxxx等${C_RESET}。"
fi

printf "╚═════════════════╩════════╩════╩═════════════════════════════════════════════════════════════════════════╝\n"