#!/bin/bash # ============================================================ # remote-host-agent 机器B端 agent.sh(轻量,仅需 bash+curl+base64) # 功能:定时上报 CPU/内存/磁盘 等指标 + 长轮询执行机器A下发的命令 # 用法: # 前台运行 ./agent.sh # 后台常驻 nohup ./agent.sh > /var/log/host-agent.log 2>&1 & # 系统服务 cp host-agent.service /etc/systemd/system/ && systemctl daemon-reload && systemctl enable --now host-agent # 配置:同目录 config.sh(SERVER / TOKEN / HOST_NAME / INTERVAL) # ============================================================ set -u SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # ---------- 加载配置 ---------- if [ -f "$SCRIPT_DIR/config.sh" ]; then . "$SCRIPT_DIR/config.sh" fi SERVER="${SERVER:-http://121.40.164.32:16018}" TOKEN="${TOKEN:-}" HOST_NAME="${HOST_NAME:-$(hostname)}" INTERVAL="${INTERVAL:-10}" # 上报间隔(秒) POLL_TIMEOUT="${POLL_TIMEOUT:-65}" # 命令长轮询超时(秒),略大于服务端阻塞时间 CMD_TIMEOUT="${CMD_TIMEOUT:-30}" # 单条命令执行超时(秒) if [ -z "$TOKEN" ]; then echo "[错误] 未配置 TOKEN,请在 config.sh 中填写机器A生成的 token" >&2 exit 1 fi UA="host-agent/1.0" # ---------- 通用请求封装(保留备用;上报/结果用 curl -G 保证参数编码) ---------- req() { # req [--data ...] local method="$1" path="$2"; shift 2 curl -s -m 15 -A "$UA" -X "$method" \ -H "Authorization: Bearer $TOKEN" \ "$@" \ "http://${SERVER#http://}${path}" 2>/dev/null || true } # ---------- 采集指标(全部系统自带命令) ---------- collect_cpu() { # 取两次采样差值,避免 top 首次采样不准(awk 用 %d 强制整数,防科学计数法) local c1 c2 id1 id2 c1=$(grep '^cpu ' /proc/stat | awk '{printf "%d", $2+$3+$4+$5+$6+$7+$8}') id1=$(grep '^cpu ' /proc/stat | awk '{printf "%d", $5}') sleep 1 c2=$(grep '^cpu ' /proc/stat | awk '{printf "%d", $2+$3+$4+$5+$6+$7+$8}') id2=$(grep '^cpu ' /proc/stat | awk '{printf "%d", $5}') local total=$((c2 - c1 + id2 - id1)) local idle=$((id2 - id1)) if [ "$total" -le 0 ]; then echo 0; else awk -v t="$total" -v i="$idle" 'BEGIN{printf "%.1f", 100*(t-i)/t}' fi } collect_metrics() { # CPU 使用率 % CPU="$(collect_cpu)" # 内存使用率 %(用行号取数据行,兼容中英文 locale) local mt mu read mt mu < <(free -m 2>/dev/null | awk 'NR==2{print $2, $3}') if [ -n "$mt" ] && [ "$mt" -gt 0 ]; then MEM=$(( mu * 100 / mt )) else MEM=0 fi # 根分区磁盘使用率 % DISK="$(df -h / 2>/dev/null | awk 'NR==2{gsub("%","",$5); print $5}')" [ -z "$DISK" ] && DISK=0 # 负载 LOAD="$(cat /proc/loadavg 2>/dev/null | awk '{print $1" "$2" "$3}')" # 开机秒数 UPTIME="$(awk '{print int($1)}' /proc/uptime 2>/dev/null)" } # ---------- 上报 ---------- report() { collect_metrics curl -s -m 15 -A "$UA" -X POST -G \ -H "Authorization: Bearer $TOKEN" \ --data-urlencode "host=${HOST_NAME}" \ --data-urlencode "cpu=${CPU}" \ --data-urlencode "mem=${MEM}" \ --data-urlencode "disk=${DISK}" \ --data-urlencode "load=${LOAD}" \ --data-urlencode "uptime=${UPTIME}" \ "http://${SERVER#http://}/api/report" >/dev/null 2>&1 } # ---------- 长轮询取命令并执行 ---------- poll_and_run() { local resp resp=$(curl -s -m "$POLL_TIMEOUT" -A "$UA" \ -H "Authorization: Bearer $TOKEN" \ "http://${SERVER#http://}/api/poll?host=${HOST_NAME}" 2>/dev/null) || return 0 # 解析 cmd_id / cmd(用 grep 轻量解析,避免依赖 jq) local cmd_id cmd cmd_id=$(echo "$resp" | grep -o '"cmd_id":[0-9]*' | head -1 | cut -d: -f2) cmd=$(echo "$resp" | sed -n 's/.*"cmd":"\([^"]*\)".*/\1/p') if [ -n "$cmd_id" ] && [ -n "$cmd" ]; then # 还原 JSON 转义 cmd=$(printf '%b' "$cmd" | sed 's/\\n/\n/g; s/\\"/"/g; s/\\\\/\\/g') local out exitcode out=$(timeout "$CMD_TIMEOUT" bash -c "$cmd" 2>&1) exitcode=$? local b64 b64=$(printf '%s' "$out" | base64 -w0 2>/dev/null || printf '%s' "$out" | base64) # 用 curl -G + --data-urlencode,避免 base64 中的 + / = 被 URL 解码破坏 curl -s -m 15 -A "$UA" -X POST -G \ -H "Authorization: Bearer $TOKEN" \ --data-urlencode "host=${HOST_NAME}" \ --data-urlencode "cmd_id=${cmd_id}" \ --data-urlencode "output=${b64}" \ --data-urlencode "exit_code=${exitcode}" \ "http://${SERVER#http://}/api/result" >/dev/null 2>&1 fi } # ---------- 主循环(常驻,永不退出) ---------- echo "[$(date '+%F %T')] host-agent 启动: host=${HOST_NAME} server=${SERVER} interval=${INTERVAL}s" while true; do report poll_and_run sleep "$INTERVAL" done