第一步:创建依赖安装脚本

sudo nano /opt/install-deps.sh

回车后进入编辑器,然后:

  1. 复制下方全部内容
  2. 在 SSH 终端中右键粘贴(或 Shift+Insert
  3. 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_CONTAINERFRPC2_CONTAINER


第四步:创建主监控脚本

sudo nano /opt/frpc-monitor.sh

回车后进入编辑器,然后:

  1. 复制下方全部内容
  2. 在 SSH 终端中右键粘贴(或 Shift+Insert
  3. 修改以下几项(用键盘方向键移动光标到对应位置修改):
    • CF_API_TOKEN → 填入你的 CF API Token
    • CF_ZONE_ID → 填入你的 Zone ID
    • FRPC1_CONTAINER → 填入第三步查到的容器名
    • FRPC2_CONTAINER → 填入第三步查到的容器名
  4. 修改完成后按 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+XYEnter 保存退出。

# 第二步:验证配置
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

出处:http://xblog.itxgo.com/article.php?id=44
版权:本文采用 CC BY-NC-SA 4.0 协议,完整转载请注明来源。