跳到主要内容
安全

Linux采集VNC相关日志脚本

通过 2026年9月23日 没有评论
脚本已写到 D:\202609\collect-linux-logs.sh,内容如下(可直接复制到 Linux 上使用):

  #!/usr/bin/env bash
  #===============================================================================
  # collect-linux-logs.sh — Linux 故障诊断日志一键收集
  #
  # 收集范围:
  #   1. 基础系统信息   uname / 发行版 / CPU / 内存 / 磁盘 / 显卡 / 失败服务
  #   2. OS 系统日志    /var/log/messages /var/log/syslog /var/log/secure ...
  #                     + journalctl (当前启动全量 + 最近 N 天 warning 以上)
  #   3. 内核日志       dmesg -TT / journalctl -k / /var/log/dmesg / kern.log
  #   4. VNC 日志       所有用户的 ~/.vnc/*.log|*.pid|config|xstartup ...
  #                     /tmp/.X11-unix / 监听端口 / vnc 相关 systemd 单元
  #   5. 显示管理器     /var/log/gdm|gdm3|lightdm|sddm|lxdm|...
  #                     + journalctl -u gdm -u lightdm -u sddm ...
  #   6. Xorg 日志      /var/log/Xorg.*.log* / ~/.local/share/xorg/ ~/.xsession-errors
  #
  # 用法:
  #   sudo ./collect-linux-logs.sh [-d 天数] [-o 输出目录] [-n] [-h]
  #     -d 天数     journalctl 回溯天数,默认 3
  #     -o 目录     输出目录,默认当前目录
  #     -n          只生成目录,不打包成 tar.gz
  #     -h          显示帮助
  #
  # 建议以 root 运行,否则部分日志(secure/audit/dmesg)无法读取。
  #===============================================================================
  set -uo pipefail

  #------------------------------ 参数解析 --------------------------------------
  DAYS=3
  OUTBASE="$PWD"
  PACK=1

  usage() { sed -n '3,24p' "$0" 2>/dev/null || echo "用法: sudo $0 [-d 天数] [-o 输出目录] [-n]"; }

  while getopts ":d:o:nh" opt; do
      case "$opt" in
          d) DAYS="$OPTARG" ;;
          o) OUTBASE="$OPTARG" ;;
          n) PACK=0 ;;
          h) usage; exit 0 ;;
          :) echo "参数 -$OPTARG 需要值" >&2; exit 1 ;;
          *) echo "未知参数: -$OPTARG" >&2; usage; exit 1 ;;
      esac
  done

  #------------------------------ 提权 ------------------------------------------
  if [ "$(id -u)" -ne 0 ]; then
      echo "[!] 当前非 root,部分日志无法读取。"
      if command -v sudo >/dev/null 2>&1; then
          echo "[*] 尝试通过 sudo 重新执行..."
          exec sudo -E bash "$0" "$@"
      fi
      echo "[!] 未找到 sudo,将以普通用户身份继续(结果可能不完整)。"
  fi

  #------------------------------ 全局变量 --------------------------------------
  TS="$(date +%Y%m%d-%H%M%S)"
  HOST="$(hostname -s 2>/dev/null || echo unknown)"
  OUTDIR="$OUTBASE/logs-${HOST}-${TS}"
  SUMMARY="$OUTDIR/00-收集摘要.txt"
  MAXBYTES=$((200 * 1024 * 1024))   # 单文件超过 200MB 只保留末尾

  OK_CNT=0
  MISS_CNT=0

  mkdir -p "$OUTDIR" && chmod 700 "$OUTDIR"
  : > "$SUMMARY"

  #------------------------------ 辅助函数 --------------------------------------
  note() { printf '%s\n' "$*" >> "$SUMMARY"; }

  ok()  { OK_CNT=$((OK_CNT+1));   printf '  [OK]   %s\n' "$*"; note "[OK]   $*"; }
  miss(){ MISS_CNT=$((MISS_CNT+1)); printf '  [MISS] %s\n' "$*"; note "[MISS] $*"; }
  warn(){ printf '  [WARN] %s\n' "$*"; note "[WARN] $*"; }

  # 拷贝文件或目录到目标目录(自动截断超大文件)
  copy_path() {
      local src="$1" dest="$2"
      [ -e "$src" ] || { miss "$src"; return 1; }

      if [ -f "$src" ]; then
          local sz
          sz="$(stat -c %s "$src" 2>/dev/null || echo 0)"
          if [ "$sz" -gt "$MAXBYTES" ]; then
              local base; base="$(basename "$src")"
              mkdir -p "$dest"
              echo "### 源文件 ${sz} 字节,超限,仅保留末尾 ${MAXBYTES} 字节 ###" > "$dest/$base"
              tail -c "$MAXBYTES" "$src" >> "$dest/$base" 2>/dev/null
              warn "TRUNC $src (${sz} bytes)"
              return 0
          fi
      fi

      mkdir -p "$dest"
      if cp -a "$src" "$dest/" 2>/dev/null; then
          ok "$src"
      else
          warn "COPY-FAIL $src"
      fi
  }

  # 按通配符拷贝一批文件
  copy_glob() {
      # shellcheck disable=SC2086
      local pat="$1" dest="$2" f found=0
      mkdir -p "$dest"
      for f in $pat; do
          [ -e "$f" ] || continue
          found=1
          copy_path "$f" "$dest"
      done
      [ "$found" -eq 0 ] && miss "$pat"
      return 0
  }

  # 执行命令并落盘
  run_cmd() {
      local out="$1"; shift
      mkdir -p "$(dirname "$out")"
      if "$@" > "$out" 2>&1; then
          ok "CMD: $*"
      else
          warn "CMD-FAIL($?): $*"
      fi
  }

  has() { command -v "$1" >/dev/null 2>&1; }

  have_journal=0
  if has journalctl && [ -d /run/systemd/system ]; then have_journal=1; fi

  echo "==============================================================="
  echo " Linux 日志收集  ->  $OUTDIR"
  echo " 时间: $(date '+%F %T')   主机: $HOST   回溯: ${DAYS} 天"
  echo "==============================================================="

  #===============================================================================
  # 01 基础系统信息
  #===============================================================================
  echo "[1/6] 基础系统信息 ..."
  S1="$OUTDIR/01-system"

  { echo "===== uname -a ====="; uname -a
    echo; echo "===== /etc/os-release ====="; cat /etc/os-release 2>/dev/null
    echo; echo "===== uptime / 运行时长 ====="; uptime
    echo; echo "===== date / timezone ====="; date; timedatectl 2>/dev/null
    echo; echo "===== CPU ====="; lscpu 2>/dev/null || cat /proc/cpuinfo
    echo; echo "===== 内存 ====="; free -h 2>/dev/null; echo; cat /proc/meminfo 2>/dev/null
    echo; echo "===== 磁盘 ====="; df -hT 2>/dev/null; echo; lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINT 2>/dev/null
    echo; echo "===== 已登录用户 ====="; who 2>/dev/null; echo; w 2>/dev/null
    echo; echo "===== last -n 50 ====="; last -n 50 2>/dev/null
    echo; echo "===== 环境变量(去敏) ====="
    env | grep -Ei '^(DISPLAY|WAYLAND_DISPLAY|XDG_SESSION|SSH_|LANG|LC_)' 2>/dev/null
  } > "$S1/系统信息.txt" 2>&1 && ok "$S1/系统信息.txt" || warn "写系统信息失败"

  has lspci && run_cmd "$S1/显卡设备.txt"   lspci -nnk
  has lsmod && { lsmod 2>/dev/null | grep -Ei 'nvidia|nouveau|amdgpu|radeon|i915|vmwgfx|vboxvideo|qxl|virtio_gpu' >
  "$S1/显卡驱动模块.txt" 2>&1; ok "$S1/显卡驱动模块.txt"; }
  [ -r /proc/cmdline ] && copy_path /proc/cmdline "$S1"

  [ "$have_journal" -eq 1 ] && {
      run_cmd "$S1/失败的服务.txt" systemctl --failed --no-pager
      run_cmd "$S1/显示管理器状态.txt" systemctl status display-manager --no-pager -l
      run_cmd "$S1/全部单元文件列表.txt" systemctl list-unit-files --type=service --no-legend
  }
  has dmesg  && run_cmd "$S1/内核版本.txt" uname -r
  has systemd-detect-virt && run_cmd "$S1/虚拟化类型.txt" systemd-detect-virt

  #===============================================================================
  # 02 OS 系统日志
  #===============================================================================
  echo "[2/6] OS 系统日志 ..."
  S2="$OUTDIR/02-os"

  copy_glob "/var/log/messages"      "$S2"
  copy_glob "/var/log/messages.*"    "$S2"
  copy_glob "/var/log/syslog"        "$S2"
  copy_glob "/var/log/syslog.*"      "$S2"
  copy_glob "/var/log/secure"        "$S2"
  copy_glob "/var/log/secure.*"      "$S2"
  copy_glob "/var/log/auth.log"      "$S2"
  copy_glob "/var/log/auth.log.*"    "$S2"
  copy_glob "/var/log/boot.log"      "$S2"
  copy_glob "/var/log/boot.log.*"    "$S2"
  copy_glob "/var/log/cron"          "$S2"
  copy_glob "/var/log/cron.*"        "$S2"
  copy_glob "/var/log/audit/audit.log" "$S2/audit"
  copy_glob "/var/log/wtmp"          "$S2"
  copy_glob "/var/log/btmp"          "$S2"
  copy_glob "/var/log/lastlog"       "$S2"
  copy_glob "/var/log/dpkg.log"      "$S2"

  if [ "$have_journal" -eq 1 ]; then
      run_cmd "$S2/journalctl-本次启动全量.log" journalctl --no-pager -b -o short-iso
      run_cmd "$S2/journalctl-最近${DAYS}天-警告以上.log" journalctl --no-pager -o short-iso \
              --since "-${DAYS} days" -p warning
      run_cmd "$S2/journalctl-磁盘占用.log" journalctl --disk-usage
  else
      warn "未检测到 systemd-journald,跳过 journalctl 采集"
  fi

  #===============================================================================
  # 03 内核日志
  #===============================================================================
  echo "[3/6] 内核日志 ..."
  S3="$OUTDIR/03-kernel"

  if has dmesg; then
      if dmesg -TT > "$S3/dmesg-TT.log" 2>&1; then
          ok "dmesg -TT"
      elif dmesg -T > "$S3/dmesg-T.log" 2>&1; then
          ok "dmesg -T (不支持 -TT)"
      else
          warn "dmesg 读取失败(可能受 kernel.dmesg_restrict 限制)"
      fi
      run_cmd "$S3/dmesg-仅错误级别.log" dmesg -T -l err,crit,alert,emerg
  else
      miss "dmesg 命令"
  fi

  copy_glob "/var/log/dmesg"     "$S3"
  copy_glob "/var/log/dmesg.*"   "$S3"
  copy_glob "/var/log/kern.log"  "$S3"
  copy_glob "/var/log/kern.log.*" "$S3"
  copy_glob "/var/log/kernel.log" "$S3"
  copy_glob "/var/log/kernel.log.*" "$S3"

  [ "$have_journal" -eq 1 ] && {
      run_cmd "$S3/journalctl-kernel-本次启动.log" journalctl --no-pager -k -b -o short-iso
      run_cmd "$S3/journalctl-kernel-最近${DAYS}天.log" journalctl --no-pager -k -o short-iso --since "-${DAYS} days"
  }

  [ -r /proc/cmdline ] && copy_path /proc/cmdline "$S3"

  #===============================================================================
  # 04 用户 VNC 日志
  #===============================================================================
  echo "[4/6] 用户 VNC 日志 ..."
  S4="$OUTDIR/04-vnc"

  # 枚举所有用户及其家目录
  list_users() {
      if has getent; then
          getent passwd | awk -F: '{print $1" "$6}'
      else
          awk -F: '{print $1" "$6}' /etc/passwd
      fi
  }

  USER_FOUND=0
  while read -r u uhome; do
      [ -n "${uhome:-}" ] || continue
      [ -d "$uhome" ] || continue

      # 只处理确实存在 VNC / X 会话痕迹的用户
      [ -d "$uhome/.vnc" ] || [ -d "$uhome/.x11vnc" ] || \
      [ -e "$uhome/.xsession-errors" ] || [ -d "$uhome/.local/share/xorg" ] || continue

      dest="$S4/home/$u"
      USER_FOUND=1
      echo "    - 用户 $u ($uhome)"

      # TigerVNC / TightVNC / RealVNC: ~/.vnc/{*.log,*.pid,config,xstartup}
      copy_glob "$uhome/.vnc/*.log"     "$dest/.vnc"
      copy_glob "$uhome/.vnc/*.log.old" "$dest/.vnc"
      copy_glob "$uhome/.vnc/*.pid"     "$dest/.vnc"
      copy_glob "$uhome/.vnc/config"    "$dest/.vnc"
      copy_glob "$uhome/.vnc/xstartup"  "$dest/.vnc"
      copy_glob "$uhome/.vnc/*.conf"    "$dest/.vnc"

      # x11vnc
      copy_glob "$uhome/.x11vnc/*"      "$dest/.x11vnc"
      copy_glob "$uhome/.config/x11vnc*" "$dest/.config"

      # X 会话错误输出(VNC 桌面起不来时最常看这个)
      copy_glob "$uhome/.xsession-errors"      "$dest"
      copy_glob "$uhome/.xsession-errors.old"  "$dest"
      copy_glob "$uhome/.vnc/xsession-errors"  "$dest/.vnc"
  done < <(list_users)

  [ "$USER_FOUND" -eq 0 ] && miss "未在任何用户家目录发现 .vnc / x11vnc / X 会话文件"

  # 系统级 VNC 日志
  copy_glob "/var/log/vnc*"        "$S4/system"
  copy_glob "/var/log/tigervnc*"   "$S4/system"
  copy_glob "/var/log/vncserver*"  "$S4/system"
  copy_glob "/var/log/x11vnc*"     "$S4/system"
  copy_glob "/etc/vnc/*"           "$S4/system/etc-vnc"
  copy_glob "/etc/tigervnc/*"      "$S4/system/etc-vnc"

  # 运行态信息
  has ps && { ps -eo pid,ppid,user,lstart,cmd 2>/dev/null | grep -Ei 'vnc|x11vnc|Xvnc|tightvnc|realvnc' | grep -v grep >
  "$S4/VNC进程.txt" 2>&1; ok "$S4/VNC进程.txt"; }
  has ss      && run_cmd "$S4/监听端口-ss.txt" ss -tulnp
  has netstat && run_cmd "$S4/监听端口-netstat.txt" netstat -tulnp
  run_cmd "$S4/X11-socket列表.txt" ls -la /tmp/.X11-unix
  copy_glob "/tmp/.X*-lock"        "$S4/tmp"
  copy_glob "/tmp/.vnc-*"          "$S4/tmp"

  # VNC 相关 systemd 单元
  if [ "$have_journal" -eq 1 ]; then
      run_cmd "$S4/VNC相关单元.txt" systemctl list-unit-files --type=service --no-legend
      vnc_units="$(systemctl list-unit-files --type=service --no-legend 2>/dev/null | awk '{print $1}' | grep -Ei 'vnc'
  || true)"
      if [ -n "$vnc_units" ]; then
          # shellcheck disable=SC2086
          run_cmd "$S4/journalctl-VNC单元.log" journalctl --no-pager -b -o short-iso \
                  $(printf -- '-u %s ' $vnc_units)
      fi
      # 按关键字过滤全局日志,兜底抓取
      run_cmd "$S4/journalctl-含vnc关键字.log" journalctl --no-pager -b -o short-iso --grep=vnc
  fi

  #===============================================================================
  # 05 显示管理器日志 (gdm / gdm3 / lightdm / sddm / lxdm / xdm)
  #===============================================================================
  echo "[5/6] 显示管理器日志 ..."
  S5="$OUTDIR/05-display-manager"

  copy_glob "/var/log/gdm/*"      "$S5/gdm"
  copy_glob "/var/log/gdm3/*"     "$S5/gdm3"
  copy_glob "/var/log/lightdm/*"  "$S5/lightdm"
  copy_glob "/var/log/lightdm*"   "$S5/lightdm"
  copy_glob "/var/log/sddm.log"   "$S5/sddm"
  copy_glob "/var/log/sddm/*"     "$S5/sddm"
  copy_glob "/var/log/lxdm/*"     "$S5/lxdm"
  copy_glob "/var/log/xdm.log"    "$S5/xdm"
  copy_glob "/var/log/slim.log"   "$S5/slim"

  for dm in gdm gdm3 lightdm sddm lxdm xdm lxdm-greeter; do
      if systemctl list-unit-files "${dm}.service" >/dev/null 2>&1 && \
         systemctl status "${dm}.service" >/dev/null 2>&1; then
          run_cmd "$S5/状态-${dm}.txt" systemctl status "${dm}.service" --no-pager -l
      fi
  done

  [ "$have_journal" -eq 1 ] && {
      run_cmd "$S5/journalctl-gdm.log"     journalctl --no-pager -b -o short-iso -u gdm -u gdm3
      run_cmd "$S5/journalctl-lightdm.log" journalctl --no-pager -b -o short-iso -u lightdm
      run_cmd "$S5/journalctl-sddm.log"    journalctl --no-pager -b -o short-iso -u sddm
      run_cmd "$S5/journalctl-lxdm.log"    journalctl --no-pager -b -o short-iso -u lxdm -u xdm
      run_cmd "$S5/journalctl-显示管理器相关.log" journalctl --no-pager -b -o short-iso
  --grep='gdm|lightdm|sddm|lxdm|Xorg|X server'
  }

  copy_glob "/etc/gdm3/*"        "$S5/etc-config/gdm3"
  copy_glob "/etc/gdm/*"         "$S5/etc-config/gdm"
  copy_glob "/etc/lightdm/*"     "$S5/etc-config/lightdm"
  copy_glob "/etc/sddm.conf"     "$S5/etc-config"
  copy_glob "/etc/sddm.conf.d/*" "$S5/etc-config/sddm.conf.d"
  copy_glob "/etc/lxdm/*"        "$S5/etc-config/lxdm"

  #===============================================================================
  # 06 Xorg 日志
  #===============================================================================
  echo "[6/6] Xorg 日志 ..."
  S6="$OUTDIR/06-xorg"

  copy_glob "/var/log/Xorg.*.log"      "$S6"
  copy_glob "/var/log/Xorg.*.log.old"  "$S6"
  copy_glob "/var/log/Xorg.failsafe.log" "$S6"
  copy_glob "/var/log/xorg.*.log"      "$S6"
  copy_glob "/var/log/X.log"           "$S6"

  copy_glob "/etc/X11/xorg.conf"       "$S6/etc-X11"
  copy_glob "/etc/X11/xorg.conf.d/*"   "$S6/etc-X11/xorg.conf.d"
  copy_glob "/etc/X11/Xwrapper.config" "$S6/etc-X11"
  copy_glob "/usr/share/X11/xorg.conf.d/*" "$S6/usr-share-X11/xorg.conf.d"

  # 当前会话相关
  run_cmd "$S6/X11-socket.txt" ls -la /tmp/.X11-unix
  copy_glob "/tmp/.X*-lock"    "$S6"
  [ -r /var/lib/xdm/authdir ] && copy_path /var/lib/xdm/authdir "$S6"

  [ "$have_journal" -eq 1 ] && \
      run_cmd "$S6/journalctl-Xorg关键字.log" journalctl --no-pager -b -o short-iso --grep='Xorg|X
  server|glx|drm|modeset|EE'

  #===============================================================================
  # 打包 + 摘要
  #===============================================================================
  echo
  echo "---------------------------------------------------------------"
  {
      echo "收集完成时间 : $(date '+%F %T')"
      echo "主机名       : $HOST"
      echo "输出目录     : $OUTDIR"
      echo "成功条目     : $OK_CNT"
      echo "缺失条目     : $MISS_CNT"
      echo
      echo "目录结构:"
      find "$OUTDIR" -maxdepth 2 -type d | sed "s|$OUTDIR|.|" | sort
      echo
      echo "各目录大小:"
      du -sh "$OUTDIR"/* 2>/dev/null
      echo
      echo "各文件大小:"
      find "$OUTDIR" -type f -printf '%10s  %p\n' 2>/dev/null | sed "s|$OUTDIR|.|g" | sort -k2
      echo
      echo "===== 明细日志 ====="
  } >> "$SUMMARY"

  if [ "$PACK" -eq 1 ] && has tar; then
      TARBALL="${OUTDIR}.tar.gz"
      if tar -czf "$TARBALL" -C "$(dirname "$OUTDIR")" "$(basename "$OUTDIR")" 2>/dev/null; then
          chmod 600 "$TARBALL"
          echo "[✓] 已打包: $TARBALL  ($(du -h "$TARBALL" | cut -f1))"
      else
          warn "打包失败,请直接使用目录: $OUTDIR"
      fi
  fi

  echo "[✓] 明细目录: $OUTDIR"
  echo "[✓] 收集摘要: $SUMMARY"
  echo "---------------------------------------------------------------"
  tail -n 20 "$SUMMARY" | grep -E '成功条目|缺失条目' || true
  echo
  echo "提示: 摘要中含敏感信息(如 secure/auth.log),外发前请自行脱敏。"

  使用方式

  # 上传后(Windows 编辑器传过去记得转 LF)
  sed -i 's/\r$//' collect-linux-logs.sh
  chmod +x collect-linux-logs.sh

  sudo ./collect-linux-logs.sh              # 默认回溯 3 天
  sudo ./collect-linux-logs.sh -d 7 -o /tmp # 回溯 7 天,输出到 /tmp
  sudo ./collect-linux-logs.sh -n           # 只留目录不打包

  输出结构:

  logs-<主机名>-<时间戳>/
  ├── 00-收集摘要.txt          # 成功/缺失清单 + 文件大小列表
  ├── 01-system/               # 系统信息、显卡、失败服务
  ├── 02-os/                   # messages/syslog/secure/auth.log + journalctl
  ├── 03-kernel/               # dmesg -TT / journalctl -k / kern.log
  ├── 04-vnc/                  # home/<用户>/.vnc/*.log、进程、端口、systemd 单元
  ├── 05-display-manager/      # gdm|gdm3|lightdm|sddm|lxdm + journalctl
  └── 06-xorg/                 # Xorg.*.log、xorg.conf、X11 socket

  几个设计点

  - 兼容新旧发行版:/var/log/messages 是 RHEL/CentOS 系,Debian/Ubuntu 系是
  syslog/auth.log,脚本用通配符全试一遍,缺了记 [MISS] 不报错;同时判断 /run/systemd/system 决定是否走 journalctl。
  - dmesg -TT 降级:老内核不支持 -TT(可读时间戳)时自动退回 dmesg -T。
  - VNC 按用户枚举:从 getent passwd 取所有家目录,只处理真有 .vnc / .x11vnc / .xsession-errors
  的用户,目录里按用户名分开放,多用户机器不会混。额外抓了 .xsession-errors —— VNC
  连上了但桌面是黑屏/灰屏时,问题基本都在这个文件里。
  - 超大文件保护:单文件超 200MB 只保留末尾 200MB 并打 [TRUNC] 标记,避免 /var/log/messages 几十 G 把机器撑爆。
  - 非致命:set -u -o pipefail 但不开 -e,任何一个日志缺失都不会中断整体收集。
  - 权限:输出目录 700、tar 包 600,因为里面含 secure/auth.log 等敏感内容,脚本末尾也给了脱敏提醒。

  一个提醒:脚本会把 /etc/gdm3/*、/etc/lightdm/* 等配置一并收进去,LightDM 的 lightdm.conf
  里如果手工配了自动登录密码,会在包里 —— 外发前建议扫一眼。

留下回复