最前面优选IP网站

https://ip.v2too.top/
https://ip.v2too.top/api/nodes
https://ipdb.030101.xyz/bestcfv4/
https://ip.164746.xyz/
https://stock.hostmonit.com/CloudFlareYes
https://www.cnae.top/
youxuan.cf.090227.xyz
cloudflare.182682.xyz

备注:这些优选的都是CF的边缘ip,其实还是CF的,不一定是优化的线路

一、WIN普通版

一键测试优选,并且把最快的IP解析到域名

1.下载CloudflareSpeedTest

https://github.com/XIU2/CloudflareSpeedTest

2.创建脚本

解压刚刚GitHub下载的文件,在解压的文件目录创建两个文件run_all.bat跟update_cf_dns.ps1
971aca1614f7232d2237f0b91dce00ab.png

run_all.bat的代码如下

@echo off
:: 设置脚本输出的字符集为 UTF-8,以支持中文等字符
chcp 65001

:: 切换到当前批处理脚本所在的目录
cd /d "%~dp0"

:: 输出信息,提示正在删除 result.csv 文件
echo 正在删除 result.csv...
del /f /q "result.csv"

:: 启用延迟变量扩展(用于在同一行中使用变量)
setlocal enabledelayedexpansion

:: ================================
:: 获取当前小时数并判断是否为晚高峰时段
:: ================================
:: 1. 提取当前小时数(取第0到第2位)
set "current_hour=%time:~0,2%"

:: 2. 去除小时数前面的空格(防止 0-9 点时出现 " 8" 导致判断报错)
set "current_hour=%current_hour: =%"

:: 3. 设置晚高峰判断变量(默认设为 false)
set "IS_PEAK=false"

:: 4. 判断逻辑:如果是 20点 到 23点 (20, 21, 22, 23)
if %current_hour% geq 20 if %current_hour% lss 24 (
    set "IS_PEAK=true"
)

:: ================================
:: 根据是否是晚高峰时段,启动不同的 cfst.exe 测速命令
:: ================================
if "%IS_PEAK%"=="true" (
     :: 如果是晚高峰时段,执行更高频率、更高负载的测速任务
     start "" /b "%~dp0cfst.exe" -n 600 -t 8 -dn 10 -dt 15 -tp 443 -url https://speed.cloudflare.com/__down?bytes=200000000 -tl 200 -tll 40 -tlr 1 -p 10 -sl 1 -o "%~dp0result.csv"
) else (
     :: 如果不是晚高峰时段,执行较低频率的测速任务
     start "" /b "%~dp0cfst.exe" -n 600 -t 4 -dn 10 -dt 15 -tp 443 -url https://speed.cloudflare.com/__down?bytes=200000000 -tl 120 -tll 30 -tlr 0.5 -p 10 -sl 2 -o "%~dp0result.csv"
)

:: ================================
:: 启动 PowerShell 脚本(用于更新 Cloudflare DNS)
:: ================================
start powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0update_cf_dns.ps1"


REM 主窗口自动关闭

update_cf_dns.ps1代码如下,只需要修改配置里面的三个项

# 循环监听 result.csv 文件
$csvFile = "result.csv"
Write-Host "正在监听 result.csv 文件..."

# 等待 cfst.exe 生成 result.csv 文件
while (-not (Test-Path $csvFile)) {
    Write-Host "等待 result.csv 文件生成..."
    Start-Sleep -Seconds 5
}

Write-Host "检测到 result.csv,等待 10 秒后开始进行 DNS 更新..."

# 等待 10 秒钟后再执行 DNS 更新
Start-Sleep -Seconds 10

Write-Host "开始进行 DNS 更新..."

# 在新的 CMD 窗口中启动 DNS 更新逻辑(例如 Cloudflare API 或任何 DNS 更新方法)
Start-Process cmd.exe -ArgumentList "/K echo 正在开始 DNS 更新... && REM 在这里添加你的 DNS 更新逻辑 && pause"

Write-Host "DNS 更新已在新的 CMD 窗口中启动。"


# ---------- 配置 ----------
$CF_API_TOKEN     = ""      # Cloudflare API Token,必须有 Zone.DNS:Edit 权限
$ZONE_ID      = ""           # Cloudflare Zone ID
$DNS_NAME     = ""           # 要更新的域名
$FILE_PATH    = Join-Path $PSScriptRoot "result.csv"
# --------------------------

Write-Host "🚀 脚本开始执行..."

# 读取 CSV 第一行真实 IP 和 下载速度
$csvLine = (Get-Content $FILE_PATH -Encoding UTF8 | Select-Object -Skip 1 | Select-Object -First 1)
$fields = $csvLine.Split(",")
$IP = $fields[0].Trim()
$DownloadSpeed = [float]$fields[5].Trim()

# 检查下载速度是否为 0
if ($DownloadSpeed -eq 0) {
    Write-Host "❌ 下载速度为 0,跳过 DNS 更新"
    exit 0
}

Write-Host "📌 获取到 IP: $IP"
Write-Host "📌 下载速度: $DownloadSpeed MB/s"

# 设置请求头
$Headers = @{
    Authorization = "Bearer $CF_API_TOKEN"
    "Content-Type" = "application/json"
}

# 获取 DNS 记录 ID
$Resp = Invoke-RestMethod -Uri "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records?type=A&name=$DNS_NAME" -Method GET -Headers $Headers
if ($Resp.success -and $Resp.result.Count -gt 0) {
    $DNS_RECORD_ID = $Resp.result[0].id
    Write-Host "📌 DNS记录ID: $DNS_RECORD_ID"
} else {
    Write-Host "❌ 获取 DNS 记录 ID 失败"
    exit 1
}

# 构建 JSON Body(proxied = false,关闭小橙云)
$Body = @{
    type = "A"
    name = $DNS_NAME
    content = $IP
    ttl = 1
    proxied = $false
} | ConvertTo-Json -Compress

# 更新 DNS
$UpdateResp = Invoke-RestMethod -Uri "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records/$DNS_RECORD_ID" -Method PATCH -Headers $Headers -Body $Body

Write-Host "📌 Cloudflare 返回: $UpdateResp"

if ($UpdateResp.success) {
    Write-Host "✅ DNS记录更新成功: $DNS_NAME -> $IP"
} else {
    Write-Host "❌ DNS更新失败"
}

二、WIN优化版

优化功能:电脑上测试优选IP,解析最快的IP到cf域名,tg通知结果,上传结果到网页

win的脚本代码

# ========================== 全局参数配置 ==========================
# 程序相关
$cfstPath = ".\cfst.exe"  # cfst.exe 文件路径
$csvFile = ".\result.csv"  # CSV 文件路径

# api相关
$uploadUrl = "https://xxx/api/upload"  # 上传接口 URL
$authKey = ""  # 授权密钥

# 域名更新相关
$zoneId = ""  # Cloudflare Zone ID
$apiToken = ""  # Cloudflare API Token
$recordName = ""  # 要更新的 DNS 记录名称

# 测速相关
$url = ""  # 测速用的文件地址

# ========================== 删除旧的 CSV 文件 ==========================
Write-Host "[INFO] 删除旧的 result.csv 文件..."
if (Test-Path $csvFile) {
    Remove-Item $csvFile
}

# ========================== 开始测速 ==========================
Write-Host "[INFO] 开始测速..."

# 设置 cfst.exe 参数
$currentHour = (Get-Date).Hour

if ($currentHour -ge 19 -and $currentHour -lt 24) {
    # 晚高峰时段的参数
    $cfstArgs = "-n 800 -t 8 -dn 10 -dt 10 -tp 443 -tl 300 -tlr 0.5 -sl 0.01 -p 10 -url $url -o result.csv"
} else {
    # 平时的参数
    $cfstArgs = "-n 800 -t 4 -dn 10 -dt 10 -tp 443 -tl 300 -tlr 0.5 -sl 0.01 -p 10 -url $url -o result.csv"
}

# 在新的 PowerShell 窗口运行 cfst.exe
Start-Process powershell -ArgumentList "-NoExit", "-Command", "& '$cfstPath' $cfstArgs"

# 等待 result.csv 文件生成
$timeout = 900  # 设置最大等待时间 15 分钟
$startTime = Get-Date
while (-not (Test-Path $csvFile)) {
    $elapsedTime = (Get-Date) - $startTime
    if ($elapsedTime.TotalSeconds -gt $timeout) {
        Write-Host "[ERROR] 等待时间超过 15 分钟,未生成 result.csv 文件"
        exit
    }
    Write-Host "[INFO] 等待 result.csv 文件生成..."
    Start-Sleep -Seconds 10
}

# ========================== 读取 CSV 文件 ==========================
Write-Host "[INFO] 读取 result.csv 文件..."
$csvContent = Import-Csv -Path $csvFile

# 选择最快的 IP
$bestIp = $csvContent | Sort-Object { [double]$_.'下载速度(MB/s)' } -Descending | Select-Object -First 1

if ($bestIp) {
    Write-Host "[INFO] 选择的 IP: $($bestIp.'IP 地址') SPEED: $($bestIp.'下载速度(MB/s)')"
} else {
    Write-Host "[ERROR] 找不到有效的 IP 地址"
    exit
}

# ========================== 更新 DNS 记录 ==========================
Write-Host "[INFO] 准备更新 DNS,IP 地址: $($bestIp.'IP 地址')"
$recordUrl = "https://api.cloudflare.com/client/v4/zones/$zoneId/dns_records?name=$recordName"
$headers = @{
    "Authorization" = "Bearer $apiToken"
    "Content-Type"  = "application/json"
}

$response = Invoke-RestMethod -Uri $recordUrl -Headers $headers -Method Get

if ($response.success -and $response.result.Count -gt 0) {
    $recordId = $response.result[0].id

    # 更新 DNS 记录
    $updateUrl = "https://api.cloudflare.com/client/v4/zones/$zoneId/dns_records/$recordId"
    $body = @{
        "type"    = "A"
        "name"    = $recordName
        "content" = $bestIp.'IP 地址'
        "ttl"     = 60
        "proxied" = $false
    } | ConvertTo-Json

    $updateResponse = Invoke-RestMethod -Uri $updateUrl -Headers $headers -Method Put -Body $body

    if ($updateResponse.success) {
        Write-Host "[INFO] DNS 更新成功"
    } else {
        Write-Host "[ERROR] 更新 DNS 失败: $($updateResponse.errors)"
    }
} else {
    Write-Host "[ERROR] 获取 DNS 记录失败"
    exit
}

# ========================== 上传结果到 Worker ==========================
Write-Host "[INFO] 上传数据到 Worker..."

$jsonArray = @()
foreach ($row in $csvContent) {
    $jsonArray += @{
        "ip"          = $row.'IP 地址'
        "speed"       = $row.'下载速度(MB/s)'
        "latency"     = $row.'平均延迟'
        "packetLoss"  = $row.'丢包率'
        "region"      = $row.'地区码'
        "sent"        = $row.'已发送'
        "received"    = $row.'已接收'
        "time"        = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
    }
}

# 转换为 JSON 格式
$jsonBody = $jsonArray | ConvertTo-Json -Depth 4

try {
    $uploadResponse = Invoke-RestMethod -Uri $uploadUrl -Method Post -Headers @{
        "Authorization" = $authKey
        "Content-Type"  = "application/json"
    } -Body $jsonBody

    if ($uploadResponse.success) {
        Write-Host "[INFO] 数据上传成功"
    } else {
        Write-Host "[ERROR] 数据上传失败: $($uploadResponse.errors)"
    }
} catch {
    Write-Host "[ERROR] 上传失败: $_"
}

# 防止脚本结束,保持当前窗口打开
Write-Host "[INFO] 脚本执行完成"
Read-Host "按任意键退出"

后端是部署在cloudfalre的worker,需要绑定一个kv,kv命名是IP_KV

tg通知的话,也还需要在变量里面添加变量名,TG_BOT_TOKEN tg机器人api,TG_CHAT_ID tg用户id

AUTH_KEY 可选,用户上传接口验证的密钥

CF的woker代码

// ================= 配置 =================
const CONFIG = {
  // 节点地区映射
  REGION_MAP: {
    HKG: "香港", KHH: "高雄", NRT: "东京", LAX: "洛杉矶",
    SEA: "西雅图", SJC: "圣何塞", FRA: "法兰克福",
    MAD: "马德里", SIN: "新加坡", CAN: "广州", SHA: "上海"
  },
  MAX_NODES_DISPLAY: 10,             // Telegram 最多显示节点数量
  DATA_RETENTION_MS: 24 * 60 * 60 * 1000, // 节点数据保留 24 小时
  BAR_MAX_LEN: 5,                    // Telegram 条形图长度
  NODE_ACCESS_INTERVAL: 30 * 1000    // 节点接口访问最小间隔 30 秒
};

// ================= 获取北京时间 =================
const getBJTime = () => new Date(Date.now() + 8 * 3600 * 1000);

// ================= Telegram 消息生成函数(等宽条形图) =================
function generateTGMessageText(data) {
  if (!data || data.length === 0)
    return "❌ Cloudflare 优选 IP\n\n暂无有效数据";

  // 1. 排序并取前 MAX_NODES_DISPLAY 条
  const sortedData = [...data]
    .map(i => ({ ip: String(i.ip), speed: parseFloat(i.speed) || 0 }))
    .sort((a, b) => b.speed - a.speed)
    .slice(0, CONFIG.MAX_NODES_DISPLAY);

  const maxSpeed = sortedData[0].speed;
  const barLen = CONFIG.BAR_MAX_LEN;

  // 2. 构建条形图行
  const nodeLines = sortedData.map(i => {
    const ratio = maxSpeed > 0 ? i.speed / maxSpeed : 0;
    let fill = Math.round(ratio * barLen);
    if (fill < 1) fill = 1;

    let block;
    if (ratio > 0.7) block = "█";       // 高速
    else if (ratio > 0.3) block = "▓";  // 中速
    else block = "▒";                    // 低速

    const bar = block.repeat(fill).padEnd(barLen, " ");
    const ip = i.ip.padEnd(16, " ");
    const speed = i.speed.toFixed(2).padStart(6, " ");
    return `${ip}${bar} ${speed} MB/s`;
  }).join("\n");

  const fastest = sortedData[0];
  const slowest = sortedData[sortedData.length - 1];
  const timeStr = getBJTime().toISOString().replace("T", " ").substring(0, 16);

  // 3. 返回 Telegram 消息
  return `✅ Cloudflare 优选 IP 更新通知

⚡️ 最快: ${fastest.ip} - ${fastest.speed.toFixed(2)} MB/s
☁️ 最慢: ${slowest.ip} - ${slowest.speed.toFixed(2)} MB/s

🕙 更新时间: ${timeStr}
<code>
${nodeLines}
</code>`;
}

// ================= HTML 页面渲染 =================
function renderHTML(list) {
  const updateTime = list.length > 0 ? list[0].time : "-";

  // 计算最大速度,用于条形图比例
  const maxSpeed = list.length > 0 ? Math.max(...list.map(i => i.speed)) : 0;

  // 生成每个节点卡片 HTML
  const cards = list.sort((a, b) => b.speed - a.speed)
    .map((i, idx) => {
      const widthPct = maxSpeed > 0 ? Math.round((i.speed / maxSpeed) * 100) : 0;
      return `
      <div class="card" onclick="copyToClipboard('${i.ip}')">
        <div class="card-header">
          <span class="rank">#${idx + 1}</span>
          <span class="region">${CONFIG.REGION_MAP[i.region] || i.region || "Any"}</span>
        </div>
        <div class="ip">${i.ip}</div>
        <div class="speed">${i.speed} MB/s</div>
        <div class="latency">${i.latency} ms</div>
        <div class="card-footer">点击复制 IP</div>
      </div>`;
    }).join("");

  return `<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Cloudflare 优选 IP</title>
<style>
  :root {
    --primary-color: #4caf50;
    --secondary-color: #f3f6f9;
    --card-bg: #ffffff;
    --text-color: #333;
    --muted-color: #888;
    --highlight-color: #f3f8fb;
    --shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
    --hover-shadow: 0 8px 16px rgba(0, 0, 0, 0.15);
  }

  * { box-sizing: border-box; }
  body {
    margin: 0;
    padding: 20px;
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    background: linear-gradient(145deg, #e0f7fa, #ffffff);
    color: var(--text-color);
  }
  .container { max-width: 1200px; margin: 0 auto; }
  .header { text-align: center; margin-bottom: 40px; }
  .header h1 { font-size: 2rem; color: var(--primary-color); }
  .header p { font-size: 0.9rem; color: var(--muted-color); }

  .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 20px; }

  /* 卡片样式 */
  .card {
    background-color: var(--card-bg);
    border-radius: 12px;
    padding: 20px;
    box-shadow: var(--shadow);
    cursor: pointer;
    transition: all 0.3s ease;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
  }
  .card:hover { transform: translateY(-5px); box-shadow: var(--hover-shadow); }

  .card-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 12px;
  }
  .rank {
    font-size: 0.8rem;
    font-weight: bold;
    color: var(--primary-color);
    background: var(--highlight-color);
    padding: 6px 12px;
    border-radius: 20px;
  }
  .region {
    font-size: 0.8rem;
    color: #1e40af;
    background: #e0e7ff;
    padding: 6px 12px;
    border-radius: 12px;
  }

  .ip {
    font-family: 'Courier New', Courier, monospace;
    font-size: 1.2rem;
    font-weight: 600;
    text-align: center;
    margin-bottom: 14px;
  }

  .speed {
    font-size: 2rem;
    font-weight: bold;
    color: var(--primary-color);
    text-align: center;
    margin: 12px 0;
  }

  .latency {
    font-size: 1.2rem;
    font-weight: 600;
    color: var(--muted-color);
    text-align: center;
  }

  .card-footer {
    text-align: center;
    font-size: 0.75rem;
    color: var(--muted-color);
    margin-top: 14px;
  }

  #toast {
    position: fixed;
    bottom: 20px;
    left: 50%;
    transform: translateX(-50%);
    background: var(--primary-color);
    color: white;
    padding: 12px 20px;
    border-radius: 20px;
    font-size: 0.85rem;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.3s ease, transform 0.3s ease;
  }
  #toast.show {
    opacity: 1;
    transform: translateX(-50%) translateY(-6px);
  }
</style>
<script>
  // 复制 IP 并显示 Toast 提示
  function copyToClipboard(text) {
    navigator.clipboard.writeText(text).then(() => {
      const toast = document.getElementById("toast");
      toast.classList.add("show");
      setTimeout(() => toast.classList.remove("show"), 1800);
    });
  }
</script>
</head>
<body>
<div class="container">
  <div class="header">
    <h1>Cloudflare 优选 IP</h1>
    <p>最后更新 · ${updateTime}</p>
  </div>
  <div class="grid">
    ${cards || '<div style="grid-column: 1/-1; text-align:center; color:#9ca3af; padding:60px;">暂无数据</div>'}
  </div>
</div>
<div id="toast">IP 已复制</div>
</body>
</html>`;
}

// ================= Worker 主逻辑 =================
export default {
  async fetch(request, env) {
    const { pathname } = new URL(request.url);

    // ---------- 上传测速数据接口 ---------- 
    if (pathname === "/api/upload" && request.method === "POST") {
      try {
        const auth = request.headers.get("Authorization");
        if (env.AUTH_KEY && auth !== env.AUTH_KEY)
          return new Response("Unauthorized", { status: 401 });

        const rawData = await request.json();
        if (!Array.isArray(rawData)) throw new Error("Invalid format");

        const now = getBJTime().toISOString();
        const cleanData = rawData.map(i => ({
          ip: i.ip, speed: i.speed, latency: i.latency, region: i.region, time: now.replace("T", " ").substring(0, 16)
        }));

        // 每次上传的数据都完全覆盖之前的数据
        await env.SPEED_TEST_KV.put("speed_test_data", JSON.stringify(cleanData));

        // 发送 Telegram 消息
        await Promise.allSettled([
          fetch(`https://api.telegram.org/bot${env.TG_BOT_TOKEN}/sendMessage`, {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({
              chat_id: env.TG_CHAT_ID,
              text: generateTGMessageText(cleanData),
              parse_mode: "HTML",
              disable_notification: true
            })
          }),
        ]);

        return new Response(JSON.stringify({ success: true }), {
          headers: { "Content-Type": "application/json; charset=utf-8" }
        });

      } catch (e) {
        return new Response(JSON.stringify({ error: e.message }), {
          status: 400,
          headers: { "Content-Type": "application/json; charset=utf-8" }
        });
      }
    }

    // ---------- 获取节点数据接口 ---------- 
    if (pathname === "/api/nodes") {
      const nowTs = Date.now();
      const clientIP = request.headers.get("CF-Connecting-IP") || "unknown";

      const rawAccess = await env.SPEED_TEST_KV.get("nodes_access_map");
      let accessMap = rawAccess ? JSON.parse(rawAccess) : {};

      // 删除过期访问记录
      for (let ip in accessMap)
        if (nowTs - accessMap[ip] > CONFIG.NODE_ACCESS_INTERVAL) delete accessMap[ip];

      // 检查访问频率
      if (accessMap[clientIP])
        return new Response(JSON.stringify({ success: false, message: "请30秒后再请求" }), {
          headers: { "Content-Type": "application/json; charset=utf-8" }
        });

      // 更新访问时间
      accessMap[clientIP] = nowTs;
      await env.SPEED_TEST_KV.put("nodes_access_map", JSON.stringify(accessMap));

      // 获取 24 小时内有效节点数据
      const raw = await env.SPEED_TEST_KV.get("speed_test_data");
      let list = raw ? JSON.parse(raw) : [];
      list = list.filter(i => nowTs - new Date(i.time).getTime() < CONFIG.DATA_RETENTION_MS);

      return new Response(JSON.stringify({ success: true, data: list }), {
        headers: { "Content-Type": "application/json; charset=utf-8" }
      });
    }

    // ---------- 网页端展示 ---------- 
    if (pathname === "/") {
      const raw = await env.SPEED_TEST_KV.get("speed_test_data") || "[]";
      return new Response(renderHTML(JSON.parse(raw)), {
        headers: { "Content-Type": "text/html; charset=UTF-8" }
      });
    }

    // ---------- 404 ---------- 
    return new Response("Not Found", { status: 404 });
  }
};

三、Linux版

1.官方安装说明

# 如果是第一次使用,则建议创建新文件夹(后续更新时,跳过该步骤)
mkdir cfst

# 进入文件夹(后续更新,只需要从这里重复下面的下载、解压命令即可)
cd cfst

# 下载 CFST 压缩包(自行根据需求替换 URL 中 [版本号] 和 [文件名])
wget -N https://github.com/XIU2/CloudflareSpeedTest/releases/download/v2.3.4/cfst_linux_amd64.tar.gz
# 如果你是在国内网络环境中下载,那么请使用下面这几个镜像加速之一:
# wget -N https://ghfast.top/https://github.com/XIU2/CloudflareSpeedTest/releases/download/v2.3.4/cfst_linux_arm64.tar.gz
# wget -N https://wget.la/https://github.com/XIU2/CloudflareSpeedTest/releases/download/v2.3.4/cfst_linux_arm64.tar.gz
# wget -N https://ghproxy.net/https://github.com/XIU2/CloudflareSpeedTest/releases/download/v2.3.4/cfst_linux_arm64.tar.gz
# wget -N https://gh-proxy.com/https://github.com/XIU2/CloudflareSpeedTest/releases/download/v2.3.4/cfst_linux_arm64.tar.gz
# wget -N https://hk.gh-proxy.com/https://github.com/XIU2/CloudflareSpeedTest/releases/download/v2.3.4/cfst_linux_arm64.tar.gz
# 如果下载失败的话,尝试删除 -N 参数(如果是为了更新,则记得提前删除旧压缩包 rm cfst_linux_amd64.tar.gz )

# 解压(不需要删除旧文件,会直接覆盖,自行根据需求替换 文件名)
tar -zxf cfst_linux_amd64.tar.gz

# 赋予执行权限
chmod +x cfst

# 运行(不带参数)
./cfst

# 运行(带参数示例)
./cfst -tl 200 -dn 20

在解压的文件里面,新建一个sh文件run_cf_ddns.sh

需要修改的内容都在配置信息里面

API_TOKEN # Cloudflare API Token,必须有 Zone.DNS:Edit 权限
ZONE_ID # Cloudflare Zone ID
RECORD_NAME # 要更新的域名

记得给文件添加可执行权限,chmod 777 run_cf_ddns.sh,然后运行看看效果./run_cf_ddns.sh

#!/bin/bash

# ================= 配置信息 =================
# Cloudflare API Token,必须有 Zone.DNS:Edit 权限
API_TOKEN=""
# Cloudflare Zone ID
ZONE_ID=""
# 要更新的域名
RECORD_NAME=""
# 测速url
SPEED_URL=""

# ================= cfst 参数 =================
# 定义平时参数
CFST_PARAMS_DAY="-n 600 -t 4 -dn 10 -dt 15 -tp 443 -url $SPEED_URL -tl 120 -tll 40 -tlr 0.5 -p 10 -sl 1 -o result.csv"
# 定义晚高峰参数
CFST_PARAMS_PEAK="-n 600 -t 6 -dn 10 -dt 15 -tp 443 -url $SPEED_URL -tl 200 -tll 40 -tlr 1 -p 10 -sl 1 -o result.csv"

# 根据当前时间判断使用哪一套参数
current_hour=$(date +%H)
if [ "$current_hour" -ge 20 ] && [ "$current_hour" -lt 24 ]; then
    CFST_PARAMS="$CFST_PARAMS_PEAK"  # 晚高峰使用晚高峰参数
else
    CFST_PARAMS="$CFST_PARAMS_DAY"   # 非高峰期使用平时参数
fi

echo "使用 cfst 参数: $CFST_PARAMS"
# ============================================

# ================= 防止重复运行逻辑 =================
LOCK_FILE="/tmp/cfst_ddns_update.lock"
LOCK_TIMEOUT=900  # 15分钟超时

# 使用文件描述符 200 绑定锁文件
exec 200>"$LOCK_FILE"

# 尝试获取排他锁 (-n 表示非阻塞,如果失败立即返回)
if ! flock -n 200; then
    echo "--- [$(date '+%Y-%m-%d %H:%M:%S')] 错误: 另一个脚本实例正在运行,本次任务跳过 ---"
    exit 0
fi

# 设置超时控制:超过15分钟自动解除锁并停止脚本
{
    sleep "$LOCK_TIMEOUT" && echo "--- [$(date '+%Y-%m-%d %H:%M:%S')] 错误: 超过 15 分钟未完成任务,自动解锁并退出 ---" && flock -u 200 && exit 1
} &

# 脚本的主要任务处理
echo "--- [$(date '+%Y-%m-%d %H:%M:%S')] 正在执行任务 ---"
# 任务代码...

# 脚本任务完成后解除锁
flock -u 200
echo "--- [$(date '+%Y-%m-%d %H:%M:%S')] 任务完成,已释放锁 ---"
# ===================================================

# 获取脚本所在绝对路径
BASE_DIR=$(cd "$(dirname "$0")"; pwd)
cd "$BASE_DIR" || exit 1
RESULT_FILE="$BASE_DIR/result.csv"

echo "--- 任务开始: $(date '+%Y-%m-%d %H:%M:%S') ---"

# 1. 环境清理
[ -f "$RESULT_FILE" ] && rm -f "$RESULT_FILE"

# 2. 执行测速
if [ -f "./cfst" ]; then
    echo "[步骤1/4] 正在运行 cfst 测速..."
    chmod +x ./cfst
    ./cfst $CFST_PARAMS
else
    echo "错误: 目录下未找到 cfst 执行文件"
    exit 1
fi

# 3. 结果校验与提取
if [ ! -s "$RESULT_FILE" ]; then
    echo "错误: 测速失败,未生成有效的 result.csv"
    exit 1
fi

READ_DATA=$(awk -F, 'NR==2 {print $1,$6}' "$RESULT_FILE")
read -r IP SPEED <<< "$READ_DATA"

CHECK_RESULT=$(echo "$SPEED" | awk '{if($1 <= 0.01) print "stop"; else print "go"}')

if [ "$CHECK_RESULT" == "stop" ] || [ -z "$IP" ]; then
    echo "停止更新: 速度过低 ($SPEED MB/s) 或未获取到 IP。"
    exit 0
fi

echo "[步骤2/4] 测速通过!最优 IP: $IP, 速度: $SPEED MB/s"

# 4. 更新 Cloudflare
echo "[步骤3/4] 正在获取 Record ID..."
RECORD_INFO=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records?name=$RECORD_NAME" \
     -H "Authorization: Bearer $API_TOKEN" \
     -H "Content-Type: application/json")

RECORD_ID=$(echo "$RECORD_INFO" | sed -n 's/.*"id":"\([^"]*\)".*/\1/p' | head -n 1)

if [ -z "$RECORD_ID" ] || [ ${#RECORD_ID} -lt 10 ]; then
    echo "错误: 无法获取 Record ID。"
    exit 1
fi

echo "[步骤4/4] 正在同步 DNS 记录..."
UPDATE_RES=$(curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records/$RECORD_ID" \
     -H "Authorization: Bearer $API_TOKEN" \
     -H "Content-Type: application/json" \
     --data "{\"type\":\"A\",\"name\":\"$RECORD_NAME\",\"content\":\"$IP\",\"ttl\":60,\"proxied\":false}")

if [[ "$UPDATE_RES" == *"\"success\":true"* ]]; then
    echo "=========================================="
    echo "  更新成功! IP: $IP"
    echo "=========================================="
else
    echo "更新失败!"
    echo "$UPDATE_RES"
    exit 1
fi

echo "--- 任务结束: $(date '+%Y-%m-%d %H:%M:%S') ---"