Files
remote-host-agent/agent.sh
T
hz4th_coder c877b25ff6 v1.0.0 remote-host-agent: 机器B监控/控制轻量方案(HTTP上报+命令长轮询)
- collector.py: 机器A端 FastAPI 服务(16018),token认证 + strict/open 命令白名单 + SQLite存储
- agent.sh: 机器B端轻量agent(仅bash+curl+base64,零安装),采集CPU/内存/磁盘/负载/开机时间 + 长轮询执行命令回传结果
- hostctl.py: 机器A端 CLI(status/hosts/run/history/commands)
- host-agent.service: 机器B端 systemd 服务
- start.sh: collector 启停脚本
2026-08-19 19:08:23 +08:00

129 lines
4.9 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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.shSERVER / 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 <method> <path> [--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