第一步:创建依赖安装脚本
sudo nano /opt/install-deps.sh
回车后进入编辑器,然后:
- 复制下方全部内容
- 在 SSH 终端中右键粘贴(或
Shift+Insert) - 按
Ctrl+X→ 按Y→ 按Enter保存退出
#!/bin/bash
# install-deps.sh
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"; }
if [[ $EUID -ne 0 ]]; then
echo "请用 root 或 sudo 运行此脚本"
exit 1
fi
if command -v apt-get &>/dev/null; then
PKG_MANAGER="apt-get"
UPDATE_CMD="apt-get update -y"
INSTALL_CMD="apt-get install -y"
elif command -v yum &>/dev/null; then
PKG_MANAGER="yum"
UPDATE_CMD="yum makecache -y"
INSTALL_CMD="yum install -y"
elif command -v dnf &>/dev/null; then
PKG_MANAGER="dnf"
UPDATE_CMD="dnf makecache -y"
INSTALL_CMD="dnf install -y"
else
echo "❌ 未识别的包管理器,请手动安装:curl、netcat、jq"
exit 1
fi
log "📦 包管理器: ${PKG_MANAGER}"
log "🔄 更新软件源..."
$UPDATE_CMD
install_pkg() {
local name=$1
local cmd=$2
if command -v "$cmd" &>/dev/null; then
log "✅ ${name} 已安装,跳过"
else
log "📥 安装 ${name}..."
if $INSTALL_CMD "$name"; then
log "✅ ${name} 安装成功"
else
log "❌ ${name} 安装失败,请手动安装"
exit 1
fi
fi
}
install_pkg "curl" "curl"
install_pkg "jq" "jq"
install_pkg "docker" "docker"
if command -v nc &>/dev/null; then
log "✅ netcat 已安装,跳过"
else
log "📥 安装 netcat..."
if [[ "$PKG_MANAGER" == "apt-get" ]]; then
$INSTALL_CMD netcat-openbsd || $INSTALL_CMD netcat
else
$INSTALL_CMD nmap-ncat || $INSTALL_CMD netcat
fi
if command -v nc &>/dev/null; then
log "✅ netcat 安装成功"
else
log "❌ netcat 安装失败,请手动安装"
exit 1
fi
fi
log ""
log "========================================"
log "✅ 所有依赖安装完成,版本信息:"
log " curl: $(curl --version | head -1)"
log " jq: $(jq --version)"
log " nc: $(nc -h 2>&1 | head -1)"
log " docker: $(docker --version)"
log "========================================"
log "👉 现在可以运行 frpc-monitor.sh"
第二步:运行依赖安装脚本
sudo chmod +x /opt/install-deps.sh && sudo bash /opt/install-deps.sh
回车后等待安装完成,看到如下输出说明成功:
✅ 所有依赖安装完成,版本信息:
curl: curl 7.x.x ...
jq: jq-1.x
nc: ...
docker: Docker version ...
第三步:查询 frpc 容器名
docker ps | grep frpc
回车后记录输出中的容器名,例如:
a1b2c3d4e5f6 frpc ... frpc01
b2c3d4e5f6a1 frpc ... frpc02
最后一列就是容器名,填入下一步脚本的 FRPC1_CONTAINER 和 FRPC2_CONTAINER
第四步:创建主监控脚本
sudo nano /opt/frpc-monitor.sh
回车后进入编辑器,然后:
- 复制下方全部内容
- 在 SSH 终端中右键粘贴(或
Shift+Insert) - 修改以下几项(用键盘方向键移动光标到对应位置修改):
CF_API_TOKEN→ 填入你的 CF API TokenCF_ZONE_ID→ 填入你的 Zone IDFRPC1_CONTAINER→ 填入第三步查到的容器名FRPC2_CONTAINER→ 填入第三步查到的容器名
- 修改完成后按
Ctrl+X→ 按Y→ 按Enter保存退出
#!/bin/bash
# =============================================================
# frpc 双线监控 + Cloudflare DNS 自动切换脚本
# 单A记录模式 | 双重验证 | frpc自动重启
# 依赖:curl / jq / nc / docker
# =============================================================
# ==================== 请修改以下配置 ====================
CF_API_TOKEN="your_cloudflare_api_token"
CF_ZONE_ID="your_zone_id"
VPS1_IP="主ip"
VPS2_IP="辅ip"
FRPS_PORT=7000
DOMAINS=(
"comhh.com"
"1.comhh.com"
"xblog.comhh.com"
"memos.comhh.com"
"subapi.comhh.com"
"tool.comhh.com"
"tv.comhh.com"
"url.comhh.com"
"webssh.comhh.com"
)
FRPC1_URL="http://127.0.0.1:7401/api/status"
FRPC2_URL="http://127.0.0.1:7402/api/status"
FRPC_USER="客户端用户名"
FRPC_PASS="客户端密码"
FRPC1_CONTAINER="frpc01"
FRPC2_CONTAINER="frpc02"
DNS_TTL=60
CHECK_INTERVAL=30
RESTART_WAIT=15
CF_PROXIED=false
LOG_FILE="/var/log/frpc-monitor.log"
STATE_FILE="/tmp/frpc_monitor_state"
# =============================================================
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"; }
load_state() { [[ -f "$STATE_FILE" ]] && source "$STATE_FILE" || LAST_STATE="UNKNOWN"; }
save_state() { echo "LAST_STATE=\"$1\"" > "$STATE_FILE"; }
check_dashboard() {
local code
code=$(curl -s -o /dev/null -w "%{http_code}" \
--connect-timeout 5 --max-time 8 \
-u "${FRPC_USER}:${FRPC_PASS}" "$1" 2>/dev/null)
[[ "$code" == "200" ]]
}
check_port() { nc -z -w 3 "$1" "$2" &>/dev/null; }
restart_frpc() {
local container=$1
log " 🔄 重启容器: ${container}"
if docker restart "$container" &>/dev/null; then
log " ✅ ${container} 重启成功,等待 ${RESTART_WAIT}s"
sleep "$RESTART_WAIT"
return 0
else
log " ❌ ${container} 重启失败"
return 1
fi
}
get_vps_status() {
local dashboard_url=$1
local vps_ip=$2
local container=$3
if check_dashboard "$dashboard_url"; then
echo "OK"
return
fi
if ! check_port "$vps_ip" "$FRPS_PORT"; then
echo "VPS_DOWN"
return
fi
log "⚡ ${container}: VPS正常但frpc崩溃,尝试重启"
if restart_frpc "$container" && check_dashboard "$dashboard_url"; then
log " ✅ ${container} 重启后恢复正常"
echo "OK"
else
log " ❌ ${container} 重启后仍异常,按宕机处理"
echo "VPS_DOWN"
fi
}
get_record_info() {
local response
response=$(curl -s -X GET \
"https://api.cloudflare.com/client/v4/zones/${CF_ZONE_ID}/dns_records?type=A&name=$1" \
-H "Authorization: Bearer ${CF_API_TOKEN}" \
-H "Content-Type: application/json")
local record_id current_ip
record_id=$(echo "$response" | jq -r '.result[0].id // ""')
current_ip=$(echo "$response" | jq -r '.result[0].content // ""')
echo "${record_id}|${current_ip}"
}
update_record() {
local domain=$1 target_ip=$2
local info record_id current_ip
info=$(get_record_info "$domain")
record_id=$(echo "$info" | cut -d'|' -f1)
current_ip=$(echo "$info" | cut -d'|' -f2)
if [[ -z "$record_id" ]]; then
log " ❌ ${domain} → 获取记录失败,跳过"
return 1
fi
if [[ "$current_ip" == "$target_ip" ]]; then
log " ✅ ${domain} → 已是 ${target_ip},跳过"
return 0
fi
local response success
response=$(curl -s -X PUT \
"https://api.cloudflare.com/client/v4/zones/${CF_ZONE_ID}/dns_records/${record_id}" \
-H "Authorization: Bearer ${CF_API_TOKEN}" \
-H "Content-Type: application/json" \
--data "{\"type\":\"A\",\"name\":\"${domain}\",\"content\":\"${target_ip}\",\"ttl\":${DNS_TTL},\"proxied\":${CF_PROXIED}}")
success=$(echo "$response" | jq -r '.success')
if [[ "$success" == "true" ]]; then
log " ✅ ${domain} → ${current_ip} 改为 ${target_ip}"
else
log " ❌ ${domain} → 更新失败: $response"
return 1
fi
}
switch_all() {
local target_ip=$1 label=$2
local ok=0 fail=0
log "🔄 批量切换 → ${target_ip} (${label})"
for domain in "${DOMAINS[@]}"; do
update_record "$domain" "$target_ip" && ((ok++)) || ((fail++))
sleep 0.3
done
log "📋 完成:成功 ${ok} 个,失败 ${fail} 个"
}
log "========================================"
log "🚀 frpc监控启动 | 域名数:${#DOMAINS[@]} | VPS1:${VPS1_IP} | VPS2:${VPS2_IP} | 间隔:${CHECK_INTERVAL}s"
log "========================================"
load_state
while true; do
VPS1_STATUS=$(get_vps_status "$FRPC1_URL" "$VPS1_IP" "$FRPC1_CONTAINER")
VPS2_STATUS=$(get_vps_status "$FRPC2_URL" "$VPS2_IP" "$FRPC2_CONTAINER")
log "📊 VPS1:${VPS1_STATUS} VPS2:${VPS2_STATUS} 上次:${LAST_STATE}"
case "${VPS1_STATUS}|${VPS2_STATUS}" in
"OK|OK")
CURRENT_STATE="BOTH_OK"
if [[ "$LAST_STATE" == "VPS1_FAIL" || "$LAST_STATE" == "BOTH_FAIL" ]]; then
log "🎉 主线VPS1恢复,切回主线"
switch_all "$VPS1_IP" "恢复主线"
else
log "✅ 双线正常,DNS保持主线VPS1"
fi
;;
"VPS_DOWN|OK")
CURRENT_STATE="VPS1_FAIL"
if [[ "$LAST_STATE" != "VPS1_FAIL" ]]; then
log "⚠️ 主线VPS1宕机,切换备线VPS2"
switch_all "$VPS2_IP" "切换备线"
else
log "⚠️ 主线VPS1仍宕机,DNS保持备线VPS2"
fi
;;
"OK|VPS_DOWN")
CURRENT_STATE="VPS2_FAIL"
if [[ "$LAST_STATE" != "VPS2_FAIL" ]]; then
log "ℹ️ 备线VPS2宕机,确保DNS指向主线VPS1"
switch_all "$VPS1_IP" "确保主线"
else
log "ℹ️ 备线VPS2仍宕机,DNS正常指向主线VPS1"
fi
;;
"VPS_DOWN|VPS_DOWN")
CURRENT_STATE="BOTH_FAIL"
log "🚨 双线均宕机!DNS保持不变,等待恢复"
;;
esac
save_state "$CURRENT_STATE"
LAST_STATE="$CURRENT_STATE"
sleep "$CHECK_INTERVAL"
done
第五步:赋权并启动服务
逐行复制执行:
# 赋权
sudo chmod +x /opt/frpc-monitor.sh
# 创建 systemd 服务
sudo tee /etc/systemd/system/frpc-monitor.service > /dev/null <<EOF
[Unit]
Description=frpc Monitor with CF DNS Failover
After=network.target docker.service
Requires=docker.service
[Service]
Type=simple
ExecStart=/bin/bash /opt/frpc-monitor.sh
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
# 重载并启动
sudo systemctl daemon-reload
sudo systemctl enable frpc-monitor
sudo systemctl start frpc-monitor
# 确认运行状态
sudo systemctl status frpc-monitor
看到 Active: active (running) 说明服务正常运行。
第六步:查看实时日志
tail -f /var/log/frpc-monitor.log
按 Ctrl+C 退出日志查看,服务仍在后台运行。
配置 logrotate 自动轮转完整操作(限制日志为3天)
# 第一步:创建配置文件
sudo nano /etc/logrotate.d/frpc-monitor
粘贴以下内容:
/var/log/frpc-monitor.log {
daily
rotate 3
compress
delaycompress
missingok
notifempty
copytruncate
}
Ctrl+X → Y → Enter 保存退出。
# 第二步:验证配置
sudo logrotate --debug /etc/logrotate.d/frpc-monitor
看到 considering log /var/log/frpc-monitor.log 且无报错即为正常,完成。
如果以后不需要这个日志轮转了,删除该配置文件即可:
# 删除 logrotate 配置
sudo rm /etc/logrotate.d/frpc-monitor
# 同时清理已轮转的旧日志
sudo rm -f /var/log/frpc-monitor.log*
禁用服务、删除脚本 步骤
# 第一步:停止并禁用服务
sudo systemctl stop frpc-monitor
sudo systemctl disable frpc-monitor
# 第二步:删除 systemd 服务文件
sudo rm /etc/systemd/system/frpc-monitor.service
# 第三步:重载 systemd(让其忘记该服务)
sudo systemctl daemon-reload
sudo systemctl reset-failed
# 第四步:删除脚本文件
sudo rm /opt/frpc-monitor.sh
sudo rm /opt/install-deps.sh
# 第五步:删除日志和状态文件
sudo rm /var/log/frpc-monitor.log
sudo rm /tmp/frpc_monitor_state
# 第六步:确认清理完毕
echo "--- 服务状态 ---"
sudo systemctl status frpc-monitor 2>&1 | head -3
echo "--- 文件残留检查 ---"
ls /opt/frpc-monitor.sh 2>&1
ls /opt/install-deps.sh 2>&1
ls /etc/systemd/system/frpc-monitor.service 2>&1
ls /var/log/frpc-monitor.log 2>&1
ls /tmp/frpc_monitor_state 2>&1
第六步每行都输出 No such file or directory 、服务状态显示 could not be found 则说明已清理干净。
常用管理命令备忘
# 停止服务
sudo systemctl stop frpc-monitor
# 重启服务
sudo systemctl restart frpc-monitor
# 查看日志最后50行
tail -n 50 /var/log/frpc-monitor.log
# 实时跟踪日志
tail -f /var/log/frpc-monitor.log
# 查看 systemd 日志
sudo journalctl -u frpc-monitor -f