侧边栏壁纸
博主头像
小新笔记坊

笔耕学思悟,细绘生活卷。

  • 累计撰写 85 篇文章
  • 累计创建 30 个标签
  • 累计收到 134 条评论

目 录CONTENT

文章目录

自建简易Linux运维助手

小新笔记坊
2026-01-13 / 0 评论 / 0 点赞 / 9 阅读 / 0 字 / 正在检测是否收录...
温馨提示:
本文最后更新于2026-01-13,若内容或图片失效,请留言反馈。

使用说明

指令:
自动识别模式: [待分析文件] xy auto "问题"
一键诊断模式: xy diag ["问题"]
通用询问模式: [待分析的文件] xy ask "问题"
日志分析模式: [待分析的文件] xy log "问题"
错误排查模式: [待分析的文件] xy err "问题"
配置审查模式: [待分析的文件] xy conf "问题"
系统诊断模式: [待分析的文件] xy sys "问题"
系统帮助模式: xy help

说明:
- 进入粘贴信息模式:指令后加-p进入粘贴信息模式,粘贴信息后ctrl+d等待解答
- 系统提示词:修改User_Data内容

示例:
  tail -n 200 /var/log/nginx/error.log | xy log "分析"
  cat /etc/nginx/nginx.conf | xy conf "审查"
  (free -h; df -h) | xy sys "诊断"
  xy ask -p "问题"

环境变量:
LAI_API_BASE   默认 http://127.0.0.1:1234/v1
LAI_MODEL      默认 qwen2.5-7b
LAI_API_KEY    线上Key
LAI_MAX_TOKENS 默认 2048,输出强制限制
LAI_KEEP_CHARS 默认 20000,会自动把输入截断到最后
LAI_DEBUG=0    打印调试信息,0/1

安装步骤

1.更新插件。

sudo apt update && sudo apt install -y curl jq

2.新建脚本目录及脚本文件,这里假设为/bin/xy

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

# ====== 配置区(可改 / 可用环境变量覆盖) ======
API_BASE="${LAI_API_BASE:-https://dashscope.aliyuncs.com/compatible-mode/v1}"
MODEL="${LAI_MODEL:-qwen3-max-preview}"
API_KEY="${LAI_API_KEY:-sk-abcdefghijk491db}"
MAX_TOKENS="${LAI_MAX_TOKENS:-2048}"
TEMPERATURE="${LAI_TEMPERATURE:-0.2}"
KEEP_CHARS="${LAI_KEEP_CHARS:-20000}"
DEBUG="${LAI_DEBUG:-0}"
DEFAULT_PASTE="${LAI_DEFAULT_PASTE:-0}"
# curl 超时/重试(避免网络卡死)
#多久连不上服务器才退出
CURL_CONNECT_TIMEOUT="${LAI_CONNECT_TIMEOUT:-30}"
#整次请求最多允许跑多久
CURL_MAX_TIME="${LAI_MAX_TIME:-300}"
#允许重复多少次
CURL_RETRY="${LAI_RETRY:-2}"
# =============================================

usage() {
  cat <<'EOF'
指令:
自动识别模式: [待分析文件] xy auto "问题"
一键诊断模式: xy diag ["问题"]
通用询问模式: [待分析的文件] xy ask "问题"
日志分析模式: [待分析的文件] xy log "问题"
错误排查模式: [待分析的文件] xy err "问题"
配置审查模式: [待分析的文件] xy conf "问题"
系统诊断模式: [待分析的文件] xy sys "问题"
系统帮助模式: xy help

说明:
- 进入粘贴信息模式:指令后加-p进入粘贴信息模式,粘贴信息后ctrl+d等待解答
- 系统提示词:修改User_Data内容

示例:
  tail -n 200 /var/log/nginx/error.log | xy log "分析"
  cat /etc/nginx/nginx.conf | xy conf "审查"
  (free -h; df -h) | xy sys "诊断"
  xy ask -p "问题"

环境变量:
LAI_API_BASE   默认 http://127.0.0.1:1234/v1
LAI_MODEL      默认 qwen2.5-7b
LAI_API_KEY    线上Key
LAI_MAX_TOKENS 默认 2048,输出强制限制
LAI_KEEP_CHARS 默认 20000,会自动把输入截断到最后
LAI_DEBUG=0    打印调试信息,0/1

EOF
}
#检查依赖命令是否存在,否则退出。
need() { command -v "$1" >/dev/null 2>&1 || { echo "缺少依赖:$1" >&2; exit 1; }; }
#按字符数从尾部截断字符串。
truncate_tail() {
  local s="${1-}"        # 若 $1 不存在,用空字符串
  local n="${2-0}"       # 若 $2 不存在,用 0
  local len="${#s}"

  if (( len <= n )); then
    printf "%s" "$s"
  else
    printf "%s" "${s:len-n:n}"
  fi
}
#用 jq 将字符串安全转为 JSON 字符串(处理引号/换行)
json_escape() { jq -Rs '.' <<<"$1"; }
#DEBUG=1 时打印当前配置到 stderr
debug_log() {
  [[ "$DEBUG" == "1" ]] || return 0
  echo "[xy debug] API_BASE=$API_BASE" >&2
  echo "[xy debug] MODEL=$MODEL" >&2
  echo "[xy debug] MAX_TOKENS=$MAX_TOKENS KEEP_CHARS=$KEEP_CHARS TEMP=$TEMPERATURE" >&2
}
#读取输入材料(文件/管道/粘贴),并做截断
read_input_any() {
  # 参数:file paste_flag
  local file="${1:-}" paste_flag="${2:-0}"
  local raw=""

  if [[ -n "$file" ]]; then
    raw="$(cat "$file")"
  else
    # 有管道/重定向:读 stdin
    if [[ -t 0 ]]; then
      # stdin 是终端:只有显式 paste 或 DEFAULT_PASTE 才读取
      if [[ "$paste_flag" == "1" || "$DEFAULT_PASTE" == "1" ]]; then
        raw="$(cat)"
      else
        raw=""
      fi
    else
      raw="$(cat)"
    fi
  fi

  truncate_tail "$raw" "$KEEP_CHARS"
}
#组装请求 JSON,调用接口,处理错误并输出结果
call_llm() {
  local system="$1" user="$2"

  local system_json user_json payload
  system_json=$(json_escape "$system")
  user_json=$(json_escape "$user")

  payload=$(cat <<EOF
{
  "model": "$(printf "%s" "$MODEL")",
  "temperature": $TEMPERATURE,
  "max_tokens": $MAX_TOKENS,
  "messages": [
    {"role":"system","content": $system_json},
    {"role":"user","content": $user_json}
  ]
}
EOF
)

  local auth_header=()
  if [[ -n "$API_KEY" ]]; then auth_header=(-H "Authorization: Bearer $API_KEY"); fi

  # 请求
  local resp
  resp="$(curl -sS \
    --connect-timeout "$CURL_CONNECT_TIMEOUT" \
    --max-time "$CURL_MAX_TIME" \
    --retry "$CURL_RETRY" \
    --retry-delay 1 \
    --retry-all-errors \
    "${API_BASE}/chat/completions" \
    -H "Content-Type: application/json" \
    "${auth_header[@]}" \
    -d "$payload" || true)"

  # 解析错误
  local err
  err="$(jq -r '.error.message // empty' <<<"$resp" 2>/dev/null || true)"
  if [[ -n "$err" ]]; then
    echo "接口返回错误:$err" >&2
    # 尝试打印状态信息
    echo "$resp" | jq -r '.' >&2 || true
    exit 2
  fi

  # 正常输出
  jq -r '.choices[0].message.content // .choices[0].text // empty' <<<"$resp"
}
#自动判断输入更像 err/conf/log/sys/ask 哪一种。
detect_mode_from_text() {
  # 输入:raw(可能为空)
  # 输出:ask/log/err/conf/sys
  local raw="$1"

  # 强错误特征
  if grep -Eiq '(traceback|exception|panic|segfault|fatal|stack trace|permission denied|no such file|cannot|failed|error:|ERR_|E\d{3,})' <<<"$raw"; then
    echo "err"; return 0
  fi

  # 配置特征
  if grep -Eiq '(^\s*\[Unit\]|\bserver\s*\{|\bhttp\s*\{|\bevents\s*\{|apiVersion:|kind:|yaml:|^---$|^\s*listen\s+|^\s*location\s+|^\s*upstream\s+|^\s*proxy_pass\s+)' <<<"$raw"; then
    echo "conf"; return 0
  fi

  # 日志特征:时间戳/level
  if grep -Eiq '(\b(20[0-9]{2}-[01][0-9]-[0-3][0-9])[ T][0-2][0-9]:[0-5][0-9]:[0-5][0-9]\b|\b[0-2]?[0-9]:[0-5][0-9]:[0-5][0-9]\b|\b(INFO|WARN|WARNING|ERROR|DEBUG|TRACE)\b|\[[A-Z]+\])' <<<"$raw"; then
    echo "log"; return 0
  fi

  # 系统信息特征
  if grep -Eiq '(\bload average\b|\bMem:\b|\bSwap:\b|\bKiB Mem\b|\bFilesystem\b|\bTasks:\b|\btop -\b|\buptime\b|\bdf -h\b|\bfree -h\b|\bsystemctl\b|\bdmesg\b)' <<<"$raw"; then
    echo "sys"; return 0
  fi

  echo "ask"
}
#采集系统关键状态信息(用于一键诊断)
collect_diag() {
  # 采集一套常用系统信息(尽量短、可用)
  {
    echo "### uptime"
    uptime 2>&1 || true
    echo
    echo "### uname -a"
    uname -a 2>&1 || true
    echo
    echo "### date"
    date 2>&1 || true
    echo
    echo "### free -h"
    free -h 2>&1 || true
    echo
    echo "### df -h"
    df -h 2>&1 || true
    echo
    echo "### top (head)"
    top -b -n1 2>&1 | head -n 25 || true
    echo
    echo "### ss -tunlp (head)"
    ss -tunlp 2>&1 | head -n 40 || true
    echo
    echo "### systemctl --failed"
    systemctl --failed 2>&1 || true
    echo
    echo "### dmesg (tail)"
    dmesg 2>&1 | tail -n 120 || true
  }
}

# -------------------- 解析命令 --------------------
need curl
need jq

paste_flag=0

mode="${1:-help}"
shift || true

# xy paste <mode> ...
if [[ "$mode" == "paste" ]]; then
  paste_flag=1
  mode="${1:-help}"
  shift || true
fi

# help
if [[ "$mode" == "help" || "$mode" == "-h" || "$mode" == "--help" ]]; then
  usage
  exit 0
fi

# diag
if [[ "$mode" == "diag" ]]; then
  question="${1:-这台机器现在可能有什么问题?请给最短排查步骤和命令。}"
  # diag 不从文件读取;若用户用管道附加内容,也会读进来并拼接
  extra="$(read_input_any "" "0")"
  raw="$(collect_diag)"
  if [[ -n "$extra" ]]; then
    raw="${raw}\n\n### extra_input\n${extra}"
  fi
  raw="$(truncate_tail "$raw" "$KEEP_CHARS")"
  mode="sys"
  file=""
else
  # 支持模式:ask/log/err/conf/sys/auto
  case "$mode" in
    ask|log|err|conf|sys|auto) ;;
    *) echo "未知模式:$mode(用 xy help 查看用法)" >&2; exit 1 ;;
  esac

  # 解析 -p/--paste 选项(放在问题前/后都可)
  # 用法例:xy log -p "问题"  或  xy log "问题" -p
  args=()
  for a in "$@"; do
    if [[ "$a" == "-p" || "$a" == "--paste" ]]; then
      paste_flag=1
    else
      args+=("$a")
    fi
  done

  # 重组参数:期望 args[0]=question args[1]=file(optional)
  question="${args[0]:-}"
  file="${args[1]:-}"

  if [[ -z "$question" ]]; then
    echo "需要提供问题字符串,例如:xy log \"这些日志什么意思\" [file]" >&2
    exit 1
  fi

  raw="$(read_input_any "$file" "$paste_flag")"

  if [[ "$mode" == "auto" ]]; then
    detected="$(detect_mode_from_text "$raw")"
    mode="$detected"
  fi
fi

debug_log

# -------------------- 组装提示词 --------------------
# 你说“系统提示词:修改 User_Data 内容”,这里保留一个集中变量
User_Data="操作系统:Ubuntu 24.04.3 LTS。
请直接给结论和可执行步骤,不要长篇推理,不要复述输入内容,不要输出无关背景。输出尽量短(要点列表即可)。"

case "$mode" in
  ask)
    system="你是一个Linux服务器运维助手。${User_Data}"
    user="${question}\n\n【上下文/材料】\n${raw}"
    ;;
  log)
    system="你是资深SRE,擅长快速读日志定位问题。${User_Data} 优先给:最可能原因TOP3 + 下一步排查命令。"
    user="${question}\n\n【日志片段(可能已截断,仅最后部分)】\n${raw}"
    ;;
  err)
    system="你是排障助手,擅长从报错输出定位根因并给修复步骤。${User_Data} 给:根因 + 最短修复路径 + 验证命令。"
    user="${question}\n\n【错误输出(可能已截断,仅最后部分)】\n${raw}"
    ;;
  conf)
    system="你是安全与运维配置审查助手。${User_Data} 关注:风险点、错误配置、最小修改建议。"
    user="${question}\n\n【配置内容(可能已截断,仅最后部分)】\n${raw}"
    ;;
  sys)
    system="你是Linux系统诊断助手。${User_Data} 关注:资源/服务状态/内核/磁盘/网络。"
    user="${question}\n\n【系统信息(可能已截断,仅最后部分)】\n${raw}"
    ;;
  *)
    # 理论上不会到这里
    system="你是Linux服务器运维助手。${User_Data}"
    user="${question}\n\n【材料】\n${raw}"
    ;;
esac

call_llm "$system" "$user"

3.进入如脚本所在目录,授予其执行权限。

chmod +x xy

4.回到./bin目录。

5.将脚本所在的./bin目录添加至环境变量,使系统全局均能访问脚本。

echo 'export PATH="/home/user/桌面/xy/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
which xy

6.打开终端,即可随时进行问答。

xy help

0
  1. 支付宝打赏

    qrcode alipay
  2. 微信打赏

    qrcode weixin
    1. 支付宝打赏

      qrcode alipay
    2. 微信打赏

      qrcode weixin

评论区