💋 Sync 2026-11-02 01:02:56

This commit is contained in:
github-actions[bot] committed 2026-11-02 01:02:56 +08:00
1 parent d97dc35e3d
commit 2f941f0ffb
48 files changed
+4891 -1002

No files matched your search

+50
View File
@@ -0,0 +1,50 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-cf-optimizer
PKG_VERSION:=1.0.0
PKG_RELEASE:=1
PKG_LICENSE:=Apache-2.0
PKG_MAINTAINER:=Dante <dante@seckv.com>
LUCI_TITLE:=LuCI support for Cloudflare Proxy and Tunnel Host Optimizer
LUCI_DEPENDS:=+luci-base +curl +ucode +ucode-mod-fs +ucode-mod-uci +ucode-mod-ubus
LUCI_PKGARCH:=all
define Package/$(PKG_NAME)/conffiles
/etc/config/cf-optimizer
endef
define Package/$(PKG_NAME)/postinst
#!/bin/sh
[ -n "$${IPKG_INSTROOT}" ] || {
rm -f /tmp/luci-indexcache.* /tmp/luci-modulecache/* 2>/dev/null
chmod 755 /etc/init.d/cf-optimizer /usr/sbin/cf-optimizer 2>/dev/null
/etc/init.d/cf-optimizer enable 2>/dev/null || true
/etc/init.d/rpcd restart
}
exit 0
endef
define Package/$(PKG_NAME)/prerm
#!/bin/sh
if [ -z "$${IPKG_INSTROOT}" ]; then
/etc/init.d/cf-optimizer stop
/etc/init.d/cf-optimizer disable
/usr/sbin/cf-optimizer clean
fi
exit 0
endef
define Package/$(PKG_NAME)/postrm
#!/bin/sh
if [ -z "$${IPKG_INSTROOT}" ]; then
rm -rf /tmp/run/cf-optimizer /tmp/cf-optimizer
rm -f /tmp/hosts/cf-optimizer /var/log/cf-optimizer.log*
killall -HUP dnsmasq 2>/dev/null
fi
exit 0
endef
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature
@@ -0,0 +1,101 @@
'use strict';
'require view';
'require rpc';
'require poll';
'require ui';
'require dom';
var callGetLogs = rpc.declare({
object: 'luci.cf-optimizer',
method: 'get_logs'
});
var callClearLogs = rpc.declare({
object: 'luci.cf-optimizer',
method: 'clear_logs'
});
return view.extend({
handleSaveApply: null,
handleSave: null,
handleReset: null,
load: function() {
return callGetLogs();
},
handleClearLogs: function(ev) {
return callClearLogs().then(function() {
var textarea = document.getElementById('cf-log-textarea');
if (textarea) textarea.value = '';
ui.addNotification(null, E('p', {}, _('日志已清空')), 'info');
}).catch(function(e) {
ui.addNotification(null, E('p', {}, e.message));
});
},
render: function(data) {
data = data || {};
var lines = Array.isArray(data.logs) ? data.logs : [];
var logText = lines.join('\n');
var textarea = E('textarea', {
'id': 'cf-log-textarea',
'class': 'cbi-input-textarea',
'style': 'width: 100%; height: 480px; font-family: monospace; font-size: 12px; line-height: 1.5; resize: vertical; box-sizing: border-box; padding: 10px; border-radius: 4px;',
'readonly': 'readonly',
'wrap': 'off'
}, logText);
// 默认滚动到底部
setTimeout(function() {
textarea.scrollTop = textarea.scrollHeight;
}, 100);
var autoRefreshCheckbox = E('input', {
'type': 'checkbox',
'id': 'cf-auto-refresh',
'checked': 'checked',
'style': 'margin-right: 6px; vertical-align: middle;'
});
var controls = E('div', {
'style': 'display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;'
}, [
E('label', { 'style': 'cursor: pointer; user-select: none; font-size: 13px;' }, [
autoRefreshCheckbox,
_('自动刷新 (每 4 秒)')
]),
E('button', {
'class': 'btn cbi-button cbi-button-remove',
'click': ui.createHandlerFn(this, 'handleClearLogs')
}, _('清空日志'))
]);
poll.add(function() {
var chk = document.getElementById('cf-auto-refresh');
if (!chk || !chk.checked) return;
return callGetLogs().then(function(res) {
res = res || {};
var ta = document.getElementById('cf-log-textarea');
if (ta && Array.isArray(res.logs)) {
var wasAtBottom = (ta.scrollHeight - ta.clientHeight <= ta.scrollTop + 50);
ta.value = res.logs.join('\n');
if (wasAtBottom) {
ta.scrollTop = ta.scrollHeight;
}
}
});
}, 4);
return E('div', { 'class': 'cbi-map' }, [
E('h2', {}, _('Cloudflare 优选加速') + ' - ' + _('运行日志')),
E('div', { 'class': 'cbi-map-descr' }, _('查看后台优选测速与 hosts 重载的实时运行记录。')),
E('fieldset', { 'class': 'cbi-section' }, [
controls,
textarea
])
]);
}
});
@@ -0,0 +1,244 @@
'use strict';
'require view';
'require rpc';
'require poll';
'require ui';
'require dom';
var callGetStatus = rpc.declare({
object: 'luci.cf-optimizer',
method: 'get_status'
});
var callRunOnce = rpc.declare({
object: 'luci.cf-optimizer',
method: 'run_once'
});
var callServiceAction = rpc.declare({
object: 'luci.cf-optimizer',
method: 'service_action',
params: [ 'action' ]
});
return view.extend({
handleSaveApply: null,
handleSave: null,
handleReset: null,
load: function() {
return callGetStatus();
},
handleRunOnce: function(ev) {
ui.showModal(_('执行优选'), [
E('p', { 'class': 'spinning' }, _('正在后台启动优选探测测速任务,请稍候...'))
]);
return callRunOnce().then(function() {
setTimeout(function() {
ui.hideModal();
window.location.reload();
}, 3000);
}).catch(function(e) {
ui.addNotification(null, E('p', {}, e.message));
ui.hideModal();
});
},
handleServiceAction: function(action, ev) {
ui.showModal(_('服务操作'), [
E('p', { 'class': 'spinning' }, _('正在执行操作,请稍候...'))
]);
return callServiceAction(action).then(function() {
setTimeout(function() {
ui.hideModal();
window.location.reload();
}, 1500);
}).catch(function(e) {
ui.addNotification(null, E('p', {}, e.message));
ui.hideModal();
});
},
render: function(data) {
data = data || {};
var container = E('div', { 'class': 'cbi-map' }, [
E('h2', {}, _('Cloudflare 优选加速') + ' - ' + _('状态概览')),
E('div', { 'class': 'cbi-map-descr' }, _('监控当前生效的 Cloudflare 优选节点状态,展示各代理域名定向情况与最新测速排行榜。'))
]);
// 服务状态卡片
var isRunning = data.service_running === true;
var statusText = isRunning ? (_('运行中') + (data.service_pid ? ' [PID: ' + data.service_pid + ']' : '')) : _('已停止');
var statusColor = isRunning ? '#059669' : '#64748b';
var statusSection = E('fieldset', { 'class': 'cbi-section' }, [
E('legend', {}, _('服务状态')),
E('div', { 'class': 'cbi-section-node' }, [
E('table', { 'class': 'table cbi-section-table' }, [
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td left', 'width': '25%' }, _('服务状态')),
E('td', { 'class': 'td left' }, [
E('span', { 'id': 'cf-service-status', 'style': 'font-weight:bold; color:' + statusColor + '; margin-right:15px;' }, statusText),
E('button', {
'class': 'btn cbi-button cbi-button-action',
'style': 'margin-right:10px;',
'click': ui.createHandlerFn(this, 'handleRunOnce')
}, _('立即执行优选')),
E('button', {
'class': 'btn cbi-button cbi-button-reload',
'click': ui.createHandlerFn(this, 'handleServiceAction', 'restart')
}, _('重启服务'))
])
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td left' }, _('上次更新时间')),
E('td', { 'class': 'td left', 'id': 'cf-last-update' }, data.last_update || '-')
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td left' }, _('生效优选 IP')),
E('td', { 'class': 'td left', 'id': 'cf-best-ip', 'style': 'font-weight:bold;' }, data.best_ip || '-')
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td left' }, _('次优备选 IP')),
E('td', { 'class': 'td left', 'id': 'cf-standby-ip' }, data.standby_ip ? (data.standby_ip + ' (' + (data.standby_colo || '-') + ')') : _('暂无备选'))
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td left' }, _('健康看门狗')),
E('td', { 'class': 'td left', 'id': 'cf-watchdog-status', 'style': 'font-weight:bold; color:' + ((data.watchdog_status && data.watchdog_status.indexOf('倒换') !== -1) ? '#d97706' : '#059669') }, data.watchdog_status || _('未运行'))
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td left' }, _('数据中心')),
E('td', { 'class': 'td left', 'id': 'cf-best-colo' }, data.best_colo || '-')
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td left' }, _('测速延迟')),
E('td', { 'class': 'td left', 'id': 'cf-best-latency' }, [
E('span', { 'style': 'color:#2563eb; font-weight:bold;' }, data.best_tcp_lat ? 'TCP: ' + data.best_tcp_lat : '-'),
E('span', { 'style': 'margin:0 8px; color:#cbd5e1;' }, '|'),
E('span', { 'style': 'color:#059669; font-weight:bold;' }, data.best_tls_lat ? 'TLS: ' + data.best_tls_lat : '-')
])
])
])
])
]);
// 域名定向列表
var domainRows = [];
if (Array.isArray(data.domains) && data.domains.length > 0) {
for (var i = 0; i < data.domains.length; i++) {
var d = data.domains[i];
domainRows.push(E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td' }, E('strong', {}, d.domain)),
E('td', { 'class': 'td' }, d.ip || '-'),
E('td', { 'class': 'td' }, d.colo || '-'),
E('td', { 'class': 'td' }, d.tcp_lat || '-'),
E('td', { 'class': 'td' }, d.tls_lat || '-'),
E('td', { 'class': 'td' }, E('span', { 'style': 'color:#059669; font-weight:bold;' }, d.status || _('正常')))
]));
}
} else {
domainRows.push(E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td', 'colspan': '6', 'style': 'text-align:center; color:#94a3b8;' }, _('暂无生效的域名定向记录,请在参数配置中添加域名并启动服务。'))
]));
}
var domainSection = E('fieldset', { 'class': 'cbi-section' }, [
E('legend', {}, _('域名定向列表')),
E('div', { 'class': 'cbi-section-node' }, [
E('table', { 'class': 'table cbi-section-table', 'id': 'cf-domain-table' }, [
E('tr', { 'class': 'tr table-titles' }, [
E('th', { 'class': 'th' }, _('目标域名')),
E('th', { 'class': 'th' }, _('生效优选 IP')),
E('th', { 'class': 'th' }, _('数据中心')),
E('th', { 'class': 'th' }, _('TCP 延迟')),
E('th', { 'class': 'th' }, _('TLS 延迟')),
E('th', { 'class': 'th' }, _('状态'))
])
].concat(domainRows))
])
]);
// 优选候选排行榜
var rankingRows = [];
if (Array.isArray(data.rankings) && data.rankings.length > 0) {
for (var j = 0; j < data.rankings.length; j++) {
var r = data.rankings[j];
rankingRows.push(E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td' }, r.rank),
E('td', { 'class': 'td' }, E('strong', {}, r.ip)),
E('td', { 'class': 'td' }, r.colo || '-'),
E('td', { 'class': 'td', 'style': 'color:#059669; font-weight:bold;' }, r.avg_lat),
E('td', { 'class': 'td' }, r.lat_range || '-'),
E('td', { 'class': 'td' }, r.loss_rate || '0.0%')
]));
}
} else {
rankingRows.push(E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td', 'colspan': '6', 'style': 'text-align:center; color:#94a3b8;' }, _('暂无排行榜数据'))
]));
}
var rankingSection = E('fieldset', { 'class': 'cbi-section' }, [
E('legend', {}, _('优选候选节点排行榜')),
E('div', { 'class': 'cbi-section-node' }, [
E('table', { 'class': 'table cbi-section-table', 'id': 'cf-ranking-table' }, [
E('tr', { 'class': 'tr table-titles' }, [
E('th', { 'class': 'th' }, _('排名')),
E('th', { 'class': 'th' }, _('候选 IP')),
E('th', { 'class': 'th' }, _('数据中心')),
E('th', { 'class': 'th' }, _('平均延迟')),
E('th', { 'class': 'th' }, _('时延范围')),
E('th', { 'class': 'th' }, _('丢包率'))
])
].concat(rankingRows))
])
]);
container.appendChild(statusSection);
container.appendChild(domainSection);
container.appendChild(rankingSection);
// 定时轮询更新
poll.add(function() {
return callGetStatus().then(function(res) {
res = res || {};
var running = res.service_running === true;
var stEl = document.getElementById('cf-service-status');
if (stEl) {
stEl.textContent = running ? (_('运行中') + (res.service_pid ? ' [PID: ' + res.service_pid + ']' : '')) : _('已停止');
stEl.style.color = running ? '#059669' : '#64748b';
}
var upEl = document.getElementById('cf-last-update');
if (upEl) upEl.textContent = res.last_update || '-';
var ipEl = document.getElementById('cf-best-ip');
if (ipEl) ipEl.textContent = res.best_ip || '-';
var sbEl = document.getElementById('cf-standby-ip');
if (sbEl) sbEl.textContent = res.standby_ip ? (res.standby_ip + ' (' + (res.standby_colo || '-') + ')') : _('暂无备选');
var wdEl = document.getElementById('cf-watchdog-status');
if (wdEl) {
wdEl.textContent = res.watchdog_status || _('未运行');
wdEl.style.color = (res.watchdog_status && res.watchdog_status.indexOf('倒换') !== -1) ? '#d97706' : '#059669';
}
var coloEl = document.getElementById('cf-best-colo');
if (coloEl) coloEl.textContent = res.best_colo || '-';
var latEl = document.getElementById('cf-best-latency');
if (latEl) {
dom.content(latEl, [
E('span', { 'style': 'color:#2563eb; font-weight:bold;' }, res.best_tcp_lat ? 'TCP: ' + res.best_tcp_lat : '-'),
E('span', { 'style': 'margin:0 8px; color:#cbd5e1;' }, '|'),
E('span', { 'style': 'color:#059669; font-weight:bold;' }, res.best_tls_lat ? 'TLS: ' + res.best_tls_lat : '-')
]);
}
});
}, 3);
return container;
}
});
@@ -0,0 +1,153 @@
'use strict';
'require view';
'require form';
'require uci';
'require fs';
'require ui';
return view.extend({
handleSaveApply: function(ev, mode) {
return this.handleSave(ev).then(function() {
ui.showModal(_('应用配置'), [
E('p', { 'class': 'spinning' }, _('正在下发配置并同步 hosts 域名定向,请稍候...'))
]);
return fs.exec_direct('/etc/init.d/cf-optimizer', ['reload']).catch(function() {}).then(function() {
setTimeout(function() {
ui.hideModal();
window.location.reload();
}, 1500);
});
});
},
render: function() {
var m, s, o;
m = new form.Map('cf-optimizer', _('Cloudflare 优选加速') + ' - ' + _('参数配置'),
_('配置待优选的 Cloudflare 代理域名、测速周期及采样算法参数。'));
s = m.section(form.NamedSection, 'settings', 'cf-optimizer', _('基础设置'));
s.anonymous = true;
o = s.option(form.Flag, 'enabled', _('启用服务'),
_('开启后守护进程将按设定周期自动执行优选测速,并通过 dnsmasq 内存级重载生效。'));
o.rmempty = false;
o = s.option(form.DynamicList, 'domains', _('加速域名列表'),
_('填入需要优选加速的域名,支持 Cloudflare Tunnel 穿透域名和小黄云代理域名。'));
o.datatype = 'host';
o.rmempty = false;
o = s.option(form.ListValue, 'interval', _('自动优选周期'),
_('两次自动测速优选之间的休眠间隔时间。'));
o.value('30', _('30 分钟'));
o.value('60', _('1 小时'));
o.value('120', _('2 小时'));
o.value('240', _('4 小时'));
o.value('360', _('6 小时'));
o.value('720', _('12 小时'));
o.value('1440', _('24 小时'));
o.default = '120';
s = m.section(form.NamedSection, 'settings', 'cf-optimizer', _('高级探测参数'));
s.anonymous = true;
o = s.option(form.Value, 'sample_count', _('采样探测次数'),
_('初筛后对候选节点进行连续采样的次数,用于计算平均时延与丢包率,推荐范围 3-10 次。'));
o.datatype = 'range(2, 20)';
o.default = '4';
o = s.option(form.ListValue, 'hysteresis_ms', _('防震荡迟滞阈值'),
_('新节点平均延迟相比在用节点必须降低超过此绝对时延(毫秒)才触发切换,避免网络微小波动导致局域网长连接频繁重连。'));
o.value('0', _('关闭 (任意优化均切换)'));
o.value('10', '10 ms');
o.value('20', '20 ms');
o.value('30', '30 ms');
o.value('50', '50 ms');
o.value('80', '80 ms');
o.default = '30';
o = s.option(form.Value, 'concurrency', _('初筛并发数'),
_('初筛阶段并发探测的进程数量,推荐范围 5-20。'));
o.datatype = 'range(2, 30)';
o.default = '10';
o = s.option(form.ListValue, 'ip_source', _('候选 IP 来源'),
_('选择优选测速的候选节点 IP 来源。'));
o.value('builtin', _('内置优选 IP 池'));
o.value('custom', _('自定义 IP 或网段'));
o.value('mixed', _('混合模式'));
o.default = 'builtin';
o = s.option(form.DynamicList, 'custom_ips', _('自定义候选 IP 列表'),
_('填入自定义的 Cloudflare Anycast IP 地址或网段。'));
o.datatype = 'or(ip4addr, ip4prefix)';
o.depends('ip_source', 'custom');
o.depends('ip_source', 'mixed');
s = m.section(form.NamedSection, 'settings', 'cf-optimizer', _('故障快速倒换'));
s.anonymous = true;
o = s.option(form.Flag, 'failover_enabled', _('启用快速故障倒换'),
_('开启后短周期看门狗将持续监控在用节点健康度,发生严重丢包或阻断时自动秒级倒换至次优备选节点。'));
o.rmempty = false;
o.default = '1';
o = s.option(form.ListValue, 'health_interval', _('健康巡检间隔'),
_('看门狗对在用节点执行极轻量 HTTP 探活的间隔时间。'));
o.value('30', _('30 秒'));
o.value('60', _('60 秒'));
o.value('120', _('120 秒'));
o.default = '60';
o.depends('failover_enabled', '1');
o = s.option(form.Value, 'fail_threshold', _('连续故障判定阈值'),
_('探活连续失败达到指定次数后触发次优节点核验与自动倒换,推荐设置为 2 次以避免偶发网络抖动误切换。'));
o.datatype = 'range(1, 10)';
o.default = '2';
o.depends('failover_enabled', '1');
o = s.option(form.ListValue, 'latency_multiplier', _('延迟劣化容忍倍数'),
_('当在用节点实测延迟超过基线延迟指定倍数且绝对恶化超过 50ms 时判定为信道拥塞,连续触发将倒换至次优节点。'));
o.value('1.5', _('1.5 倍'));
o.value('2.0', _('2.0 倍'));
o.value('3.0', _('3.0 倍'));
o.value('0', _('禁用'));
o.default = '2.0';
o.depends('failover_enabled', '1');
s = m.section(form.NamedSection, 'settings', 'cf-optimizer', _('机房区域过滤'));
s.anonymous = true;
o = s.option(form.ListValue, 'colo_filter_mode', _('区域过滤模式'),
_('设置对 Cloudflare 数据中心机房的筛选策略。'));
o.value('none', _('不限制'));
o.value('exclude', _('排除指定机房'));
o.value('include', _('仅限指定机房'));
o.default = 'none';
o = s.option(form.MultiValue, 'colo_list', _('目标机房列表'),
_('选择需要排除或仅保留的 Cloudflare 常用数据中心机房。'));
o.value('SIN', _('新加坡 SIN'));
o.value('HKG', _('香港 HKG'));
o.value('TPE', _('台北 TPE'));
o.value('NRT', _('东京 NRT'));
o.value('KIX', _('大阪 KIX'));
o.value('ICN', _('首尔 ICN'));
o.value('KUL', _('吉隆坡 KUL'));
o.value('BKK', _('曼谷 BKK'));
o.value('SJC', _('圣何塞 SJC'));
o.value('LAX', _('洛杉矶 LAX'));
o.value('FRA', _('法兰克福 FRA'));
o.value('LHR', _('伦敦 LHR'));
o.depends('colo_filter_mode', 'exclude');
o.depends('colo_filter_mode', 'include');
o = s.option(form.DynamicList, 'custom_colos', _('自定义机房代码'),
_('填入其他三位大写机场代码,例如 SFO、SEA 等。'));
o.depends('colo_filter_mode', 'exclude');
o.depends('colo_filter_mode', 'include');
return m.render();
}
});
@@ -0,0 +1,290 @@
msgid ""
msgstr "Content-Type: text/plain; charset=UTF-8\n"
msgid "Cloudflare 优选加速"
msgstr "Cloudflare 优选加速"
msgid "状态概览"
msgstr "状态概览"
msgid "参数配置"
msgstr "参数配置"
msgid "运行日志"
msgstr "运行日志"
msgid "监控当前生效的 Cloudflare 优选节点状态,展示各代理域名定向情况与最新测速排行榜。"
msgstr "监控当前生效的 Cloudflare 优选节点状态,展示各代理域名定向情况与最新测速排行榜。"
msgid "配置待优选的 Cloudflare 代理域名、测速周期及采样算法参数。"
msgstr "配置待优选的 Cloudflare 代理域名、测速周期及采样算法参数。"
msgid "查看后台优选测速与 hosts 重载的实时运行记录。"
msgstr "查看后台优选测速与 hosts 重载的实时运行记录。"
msgid "服务状态"
msgstr "服务状态"
msgid "运行中"
msgstr "运行中"
msgid "已停止"
msgstr "已停止"
msgid "立即执行优选"
msgstr "立即执行优选"
msgid "重启服务"
msgstr "重启服务"
msgid "上次更新时间"
msgstr "上次更新时间"
msgid "生效优选 IP"
msgstr "生效优选 IP"
msgid "数据中心"
msgstr "数据中心"
msgid "测速延迟"
msgstr "测速延迟"
msgid "域名定向列表"
msgstr "域名定向列表"
msgid "目标域名"
msgstr "目标域名"
msgid "TCP 延迟"
msgstr "TCP 延迟"
msgid "TLS 延迟"
msgstr "TLS 延迟"
msgid "状态"
msgstr "状态"
msgid "正常"
msgstr "正常"
msgid "当前优选候选排行榜 (Top 5)"
msgstr "当前优选候选排行榜 (Top 5)"
msgid "排名"
msgstr "排名"
msgid "候选 IP"
msgstr "候选 IP"
msgid "平均延迟"
msgstr "平均延迟"
msgid "时延范围"
msgstr "时延范围"
msgid "丢包率"
msgstr "丢包率"
msgid "基础设置"
msgstr "基础设置"
msgid "启用服务"
msgstr "启用服务"
msgid "开启后守护进程将按设定周期自动执行优选测速,并通过 dnsmasq 内存级重载生效。"
msgstr "开启后守护进程将按设定周期自动执行优选测速,并通过 dnsmasq 内存级重载生效。"
msgid "加速域名列表"
msgstr "加速域名列表"
msgid "填入需要优选加速的域名,支持 Cloudflare Tunnel 穿透域名和小黄云代理域名。"
msgstr "填入需要优选加速的域名,支持 Cloudflare Tunnel 穿透域名和小黄云代理域名。"
msgid "自动优选周期"
msgstr "自动优选周期"
msgid "两次自动测速优选之间的休眠间隔时间。"
msgstr "两次自动测速优选之间的休眠间隔时间。"
msgid "高级探测参数"
msgstr "高级探测参数"
msgid "采样探测次数"
msgstr "采样探测次数"
msgid "初筛后对候选节点进行连续采样的次数,用于计算平均时延与丢包率,推荐范围 3-10 次。"
msgstr "初筛后对候选节点进行连续采样的次数,用于计算平均时延与丢包率,推荐范围 3-10 次。"
msgid "防震荡迟滞阈值"
msgstr "防震荡迟滞阈值"
msgid "新节点平均延迟相比在用节点必须降低超过此绝对时延(毫秒)才触发切换,避免网络微小波动导致局域网长连接频繁重连。"
msgstr ""
msgid "关闭 (任意优化均切换)"
msgstr ""
msgid "正在下发配置并同步 hosts 域名定向,请稍候..."
msgstr ""
msgid "初筛并发数"
msgstr "初筛并发数"
msgid "初筛阶段并发探测的进程数量,推荐范围 5-20。"
msgstr "初筛阶段并发探测的进程数量,推荐范围 5-20。"
msgid "候选 IP 来源"
msgstr "候选 IP 来源"
msgid "选择优选测速的候选节点 IP 来源。"
msgstr "选择优选测速的候选节点 IP 来源。"
msgid "内置优选 IP 池"
msgstr "内置优选 IP 池"
msgid "自定义 IP 或网段"
msgstr "自定义 IP 或网段"
msgid "混合模式"
msgstr "混合模式"
msgid "自定义候选 IP 列表"
msgstr "自定义候选 IP 列表"
msgid "填入自定义的 Cloudflare Anycast IP 地址或网段。"
msgstr "填入自定义的 Cloudflare Anycast IP 地址或网段。"
msgid "清空日志"
msgstr "清空日志"
msgid "日志已清空"
msgstr "日志已清空"
msgid "次优备选 IP"
msgstr ""
msgid "暂无备选"
msgstr ""
msgid "健康看门狗"
msgstr ""
msgid "未运行"
msgstr ""
msgid "故障快速倒换"
msgstr ""
msgid "启用快速故障倒换"
msgstr ""
msgid "开启后短周期看门狗将持续监控在用节点健康度,发生严重丢包或阻断时自动秒级倒换至次优备选节点。"
msgstr ""
msgid "健康巡检间隔"
msgstr ""
msgid "看门狗对在用节点执行极轻量 HTTP 探活的间隔时间。"
msgstr ""
msgid "30 秒"
msgstr ""
msgid "60 秒"
msgstr ""
msgid "120 秒"
msgstr ""
msgid "连续故障判定阈值"
msgstr ""
msgid "探活连续失败达到指定次数后触发次优节点核验与自动倒换,推荐设置为 2 次以避免偶发网络抖动误切换。"
msgstr ""
msgid "已倒换"
msgstr ""
msgid "延迟劣化容忍倍数"
msgstr ""
msgid "当在用节点实测延迟超过基线延迟指定倍数且绝对恶化超过 50ms 时判定为信道拥塞,连续触发将倒换至次优节点。"
msgstr ""
msgid "1.5 倍"
msgstr ""
msgid "2.0 倍"
msgstr ""
msgid "3.0 倍"
msgstr ""
msgid "禁用"
msgstr ""
msgid "机房区域过滤"
msgstr ""
msgid "区域过滤模式"
msgstr ""
msgid "设置对 Cloudflare 数据中心机房的筛选策略。"
msgstr ""
msgid "不限制"
msgstr ""
msgid "排除指定机房"
msgstr ""
msgid "仅限指定机房"
msgstr ""
msgid "目标机房列表"
msgstr ""
msgid "选择需要排除或仅保留的 Cloudflare 常用数据中心机房。"
msgstr ""
msgid "新加坡 SIN"
msgstr ""
msgid "香港 HKG"
msgstr ""
msgid "台北 TPE"
msgstr ""
msgid "东京 NRT"
msgstr ""
msgid "大阪 KIX"
msgstr ""
msgid "首尔 ICN"
msgstr ""
msgid "吉隆坡 KUL"
msgstr ""
msgid "曼谷 BKK"
msgstr ""
msgid "圣何塞 SJC"
msgstr ""
msgid "洛杉矶 LAX"
msgstr ""
msgid "法兰克福 FRA"
msgstr ""
msgid "伦敦 LHR"
msgstr ""
msgid "自定义机房代码"
msgstr ""
msgid "填入其他三位大写机场代码,例如 SFO、SEA 等。"
msgstr ""
+1
View File
@@ -0,0 +1 @@
zh_Hans
@@ -0,0 +1,290 @@
msgid ""
msgstr "Content-Type: text/plain; charset=UTF-8\n"
msgid "Cloudflare 优选加速"
msgstr "Cloudflare 优选加速"
msgid "状态概览"
msgstr "状态概览"
msgid "参数配置"
msgstr "参数配置"
msgid "运行日志"
msgstr "运行日志"
msgid "监控当前生效的 Cloudflare 优选节点状态,展示各代理域名定向情况与最新测速排行榜。"
msgstr "监控当前生效的 Cloudflare 优选节点状态,展示各代理域名定向情况与最新测速排行榜。"
msgid "配置待优选的 Cloudflare 代理域名、测速周期及采样算法参数。"
msgstr "配置待优选的 Cloudflare 代理域名、测速周期及采样算法参数。"
msgid "查看后台优选测速与 hosts 重载的实时运行记录。"
msgstr "查看后台优选测速与 hosts 重载的实时运行记录。"
msgid "服务状态"
msgstr "服务状态"
msgid "运行中"
msgstr "运行中"
msgid "已停止"
msgstr "已停止"
msgid "立即执行优选"
msgstr "立即执行优选"
msgid "重启服务"
msgstr "重启服务"
msgid "上次更新时间"
msgstr "上次更新时间"
msgid "生效优选 IP"
msgstr "生效优选 IP"
msgid "数据中心"
msgstr "数据中心"
msgid "测速延迟"
msgstr "测速延迟"
msgid "域名定向列表"
msgstr "域名定向列表"
msgid "目标域名"
msgstr "目标域名"
msgid "TCP 延迟"
msgstr "TCP 延迟"
msgid "TLS 延迟"
msgstr "TLS 延迟"
msgid "状态"
msgstr "状态"
msgid "正常"
msgstr "正常"
msgid "当前优选候选排行榜 (Top 5)"
msgstr "当前优选候选排行榜 (Top 5)"
msgid "排名"
msgstr "排名"
msgid "候选 IP"
msgstr "候选 IP"
msgid "平均延迟"
msgstr "平均延迟"
msgid "时延范围"
msgstr "时延范围"
msgid "丢包率"
msgstr "丢包率"
msgid "基础设置"
msgstr "基础设置"
msgid "启用服务"
msgstr "启用服务"
msgid "开启后守护进程将按设定周期自动执行优选测速,并通过 dnsmasq 内存级重载生效。"
msgstr "开启后守护进程将按设定周期自动执行优选测速,并通过 dnsmasq 内存级重载生效。"
msgid "加速域名列表"
msgstr "加速域名列表"
msgid "填入需要优选加速的域名,支持 Cloudflare Tunnel 穿透域名和小黄云代理域名。"
msgstr "填入需要优选加速的域名,支持 Cloudflare Tunnel 穿透域名和小黄云代理域名。"
msgid "自动优选周期"
msgstr "自动优选周期"
msgid "两次自动测速优选之间的休眠间隔时间。"
msgstr "两次自动测速优选之间的休眠间隔时间。"
msgid "高级探测参数"
msgstr "高级探测参数"
msgid "采样探测次数"
msgstr "采样探测次数"
msgid "初筛后对候选节点进行连续采样的次数,用于计算平均时延与丢包率,推荐范围 3-10 次。"
msgstr "初筛后对候选节点进行连续采样的次数,用于计算平均时延与丢包率,推荐范围 3-10 次。"
msgid "防震荡迟滞阈值"
msgstr "防震荡迟滞阈值"
msgid "新节点平均延迟相比在用节点必须降低超过此绝对时延(毫秒)才触发切换,避免网络微小波动导致局域网长连接频繁重连。"
msgstr "新节点平均延迟相比在用节点必须降低超过此绝对时延(毫秒)才触发切换,避免网络微小波动导致局域网长连接频繁重连。"
msgid "关闭 (任意优化均切换)"
msgstr "关闭 (任意优化均切换)"
msgid "正在下发配置并同步 hosts 域名定向,请稍候..."
msgstr "正在下发配置并同步 hosts 域名定向,请稍候..."
msgid "初筛并发数"
msgstr "初筛并发数"
msgid "初筛阶段并发探测的进程数量,推荐范围 5-20。"
msgstr "初筛阶段并发探测的进程数量,推荐范围 5-20。"
msgid "候选 IP 来源"
msgstr "候选 IP 来源"
msgid "选择优选测速的候选节点 IP 来源。"
msgstr "选择优选测速的候选节点 IP 来源。"
msgid "内置优选 IP 池"
msgstr "内置优选 IP 池"
msgid "自定义 IP 或网段"
msgstr "自定义 IP 或网段"
msgid "混合模式"
msgstr "混合模式"
msgid "自定义候选 IP 列表"
msgstr "自定义候选 IP 列表"
msgid "填入自定义的 Cloudflare Anycast IP 地址或网段。"
msgstr "填入自定义的 Cloudflare Anycast IP 地址或网段。"
msgid "清空日志"
msgstr "清空日志"
msgid "日志已清空"
msgstr "日志已清空"
msgid "次优备选 IP"
msgstr "次优备选 IP"
msgid "暂无备选"
msgstr "暂无备选"
msgid "健康看门狗"
msgstr "健康看门狗"
msgid "未运行"
msgstr "未运行"
msgid "故障快速倒换"
msgstr "故障快速倒换"
msgid "启用快速故障倒换"
msgstr "启用快速故障倒换"
msgid "开启后短周期看门狗将持续监控在用节点健康度,发生严重丢包或阻断时自动秒级倒换至次优备选节点。"
msgstr "开启后短周期看门狗将持续监控在用节点健康度,发生严重丢包或阻断时自动秒级倒换至次优备选节点。"
msgid "健康巡检间隔"
msgstr "健康巡检间隔"
msgid "看门狗对在用节点执行极轻量 HTTP 探活的间隔时间。"
msgstr "看门狗对在用节点执行极轻量 HTTP 探活的间隔时间。"
msgid "30 秒"
msgstr "30 秒"
msgid "60 秒"
msgstr "60 秒"
msgid "120 秒"
msgstr "120 秒"
msgid "连续故障判定阈值"
msgstr "连续故障判定阈值"
msgid "探活连续失败达到指定次数后触发次优节点核验与自动倒换,推荐设置为 2 次以避免偶发网络抖动误切换。"
msgstr "探活连续失败达到指定次数后触发次优节点核验与自动倒换,推荐设置为 2 次以避免偶发网络抖动误切换。"
msgid "已倒换"
msgstr "已倒换"
msgid "延迟劣化容忍倍数"
msgstr "延迟劣化容忍倍数"
msgid "当在用节点实测延迟超过基线延迟指定倍数且绝对恶化超过 50ms 时判定为信道拥塞,连续触发将倒换至次优节点。"
msgstr "当在用节点实测延迟超过基线延迟指定倍数且绝对恶化超过 50ms 时判定为信道拥塞,连续触发将倒换至次优节点。"
msgid "1.5 倍"
msgstr "1.5 倍"
msgid "2.0 倍"
msgstr "2.0 倍"
msgid "3.0 倍"
msgstr "3.0 倍"
msgid "禁用"
msgstr "禁用"
msgid "机房区域过滤"
msgstr "机房区域过滤"
msgid "区域过滤模式"
msgstr "区域过滤模式"
msgid "设置对 Cloudflare 数据中心机房的筛选策略。"
msgstr "设置对 Cloudflare 数据中心机房的筛选策略。"
msgid "不限制"
msgstr "不限制"
msgid "排除指定机房"
msgstr "排除指定机房"
msgid "仅限指定机房"
msgstr "仅限指定机房"
msgid "目标机房列表"
msgstr "目标机房列表"
msgid "选择需要排除或仅保留的 Cloudflare 常用数据中心机房。"
msgstr "选择需要排除或仅保留的 Cloudflare 常用数据中心机房。"
msgid "新加坡 SIN"
msgstr "新加坡 SIN"
msgid "香港 HKG"
msgstr "香港 HKG"
msgid "台北 TPE"
msgstr "台北 TPE"
msgid "东京 NRT"
msgstr "东京 NRT"
msgid "大阪 KIX"
msgstr "大阪 KIX"
msgid "首尔 ICN"
msgstr "首尔 ICN"
msgid "吉隆坡 KUL"
msgstr "吉隆坡 KUL"
msgid "曼谷 BKK"
msgstr "曼谷 BKK"
msgid "圣何塞 SJC"
msgstr "圣何塞 SJC"
msgid "洛杉矶 LAX"
msgstr "洛杉矶 LAX"
msgid "法兰克福 FRA"
msgstr "法兰克福 FRA"
msgid "伦敦 LHR"
msgstr "伦敦 LHR"
msgid "自定义机房代码"
msgstr "自定义机房代码"
msgid "填入其他三位大写机场代码,例如 SFO、SEA 等。"
msgstr "填入其他三位大写机场代码,例如 SFO、SEA 等。"
@@ -0,0 +1,12 @@
config cf-optimizer 'settings'
option enabled '0'
option interval '120'
option sample_count '4'
option hysteresis_ms '30'
option concurrency '10'
option ip_source 'builtin'
option failover_enabled '1'
option health_interval '60'
option fail_threshold '2'
option latency_multiplier '2.0'
option colo_filter_mode 'none'
+50
View File
@@ -0,0 +1,50 @@
#!/bin/sh /etc/rc.common
START=95
STOP=10
USE_PROCD=1
PROG="/usr/sbin/cf-optimizer"
CONF="cf-optimizer"
start_service() {
config_load "$CONF"
local enabled
config_get_bool enabled settings enabled 0
[ "$enabled" -eq 1 ] || return 0
mkdir -p /var/run/cf-optimizer
"$PROG" sync-hosts >/dev/null 2>&1
procd_open_instance
procd_set_param command "$PROG" daemon
procd_set_param respawn 3600 5 0
procd_set_param stdout 1
procd_set_param stderr 1
procd_set_param pidfile /var/run/cf-optimizer/cf-optimizer.pid
procd_close_instance
}
stop_service() {
rm -f /var/run/cf-optimizer/cf-optimizer.pid
}
service_stopped() {
config_load "$CONF"
local enabled
config_get_bool enabled settings enabled 0
if [ "$enabled" != "1" ]; then
"$PROG" clean
fi
}
reload_service() {
"$PROG" sync-hosts >/dev/null 2>&1
stop
start
"$PROG" run >/dev/null 2>&1 &
}
service_triggers() {
procd_add_reload_trigger "$CONF"
}
@@ -0,0 +1,22 @@
#!/bin/sh
[ ! -f "/usr/share/ucitrack/luci-app-ucitrack.json" ] && {
cat > /usr/share/ucitrack/luci-app-ucitrack.json << EEOF
{
"config": "ucitrack",
"init": "ucitrack"
}
EEOF
}
/etc/init.d/cf-optimizer enable >/dev/null 2>&1 || true
uci -q get ucitrack.@cf-optimizer[0] >/dev/null || {
uci add ucitrack cf-optimizer >/dev/null
uci set ucitrack.@cf-optimizer[-1].init='cf-optimizer'
uci commit ucitrack
}
rm -f /tmp/luci-indexcache* 2>/dev/null
rm -f /tmp/luci-modulecache/* 2>/dev/null
exit 0
+835
View File
@@ -0,0 +1,835 @@
#!/bin/sh
CONF="cf-optimizer"
RUN_DIR="/tmp/run/cf-optimizer"
TMP_DIR="/tmp/cf-optimizer"
HOSTS_FILE="/tmp/hosts/cf-optimizer"
LOG_FILE="/var/log/cf-optimizer.log"
DEFAULT_IPS_FILE="/usr/share/cf-optimizer/default_ips.txt"
COLO_MAP_FILE="/usr/share/cf-optimizer/colo_map.json"
PID_FILE="$RUN_DIR/cf-optimizer.pid"
COLO_CACHE_FILE="$RUN_DIR/colo_cache.txt"
STATUS_JSON="$RUN_DIR/status.json"
CURRENT_IP_FILE="$RUN_DIR/current_ip.txt"
PRIMARY_IP_FILE="$RUN_DIR/primary_ip.txt"
STANDBY_IP_FILE="$RUN_DIR/standby_ip.txt"
BASELINE_TCP_FILE="$RUN_DIR/baseline_tcp.txt"
FAIL_STREAK_FILE="$RUN_DIR/fail_streak.txt"
log() {
local msg="[$(date '+%Y-%m-%d %H:%M:%S')] $1"
echo "$msg"
mkdir -p "$(dirname "$LOG_FILE")"
echo "$msg" >> "$LOG_FILE"
# 限制日志大小不超过 1000 行
if [ -f "$LOG_FILE" ] && [ $(wc -l < "$LOG_FILE") -gt 1500 ]; then
tail -n 800 "$LOG_FILE" > "$LOG_FILE.tmp" && mv -f "$LOG_FILE.tmp" "$LOG_FILE"
fi
}
get_colo_name() {
local code="$1"
if [ -f "$COLO_MAP_FILE" ] && [ -n "$code" ]; then
local name
name=$(grep -E "\"$code\"[[:space:]]*:" "$COLO_MAP_FILE" 2>/dev/null | head -n 1 | sed -E 's/.*:[[:space:]]*"([^"]+)".*/\1/')
if [ -n "$name" ]; then
echo "$name"
return
fi
fi
echo "${code:-未知}"
}
update_colo_cache() {
local ip="$1"
local colo="$2"
[ -z "$ip" ] || [ -z "$colo" ] && return
mkdir -p "$RUN_DIR"
touch "$COLO_CACHE_FILE"
grep -v "^$ip[[:space:]]" "$COLO_CACHE_FILE" > "$COLO_CACHE_FILE.tmp" 2>/dev/null
echo "$ip $colo" >> "$COLO_CACHE_FILE.tmp"
mv -f "$COLO_CACHE_FILE.tmp" "$COLO_CACHE_FILE"
}
get_cached_colo() {
local ip="$1"
[ ! -f "$COLO_CACHE_FILE" ] && return
grep "^$ip[[:space:]]" "$COLO_CACHE_FILE" 2>/dev/null | tail -n 1 | awk '{print $2}'
}
clean_hosts() {
mkdir -p "$RUN_DIR"
if [ -f "$HOSTS_FILE" ]; then
rm -f "$HOSTS_FILE"
killall -HUP dnsmasq 2>/dev/null
log "已清空优选 hosts 映射并重载 dnsmasq"
fi
rm -f "$CURRENT_IP_FILE" "$PRIMARY_IP_FILE" "$STANDBY_IP_FILE" "$BASELINE_TCP_FILE" "$FAIL_STREAK_FILE"
rm -rf "$RUN_DIR/task.lock"
cat <<EOF > "$STATUS_JSON"
{
"running": false,
"last_update": "",
"best_ip": "",
"best_colo": "",
"best_tcp_lat": 0,
"best_tls_lat": 0,
"standby_ip": "",
"standby_colo": "",
"standby_tcp_lat": 0,
"watchdog_status": "未运行",
"domains": [],
"rankings": []
}
EOF
}
sync_hosts() {
. /lib/functions.sh
config_load "$CONF"
local enabled
config_get_bool enabled settings enabled 0
if [ "$enabled" != "1" ]; then
clean_hosts
return 0
fi
local domains=""
append_sync_domain() {
local d="$1"
[ -n "$d" ] && domains="$domains $d"
}
config_list_foreach settings domains append_sync_domain
if [ -z "$domains" ]; then
clean_hosts
return 0
fi
local current_ip=""
[ -f "$CURRENT_IP_FILE" ] && current_ip=$(cat "$CURRENT_IP_FILE" 2>/dev/null)
if [ -z "$current_ip" ]; then
local cand_tmp="$TMP_DIR/cand_init.txt"
mkdir -p "$TMP_DIR"
load_candidate_ips "$cand_tmp"
current_ip=$(head -n 1 "$cand_tmp" 2>/dev/null)
rm -f "$cand_tmp"
fi
[ -z "$current_ip" ] && return 0
local hosts_tmp="$HOSTS_FILE.tmp"
mkdir -p "$(dirname "$HOSTS_FILE")"
> "$hosts_tmp"
echo "# Auto-generated by cf-optimizer sync on $(date '+%Y-%m-%d %H:%M:%S')" >> "$hosts_tmp"
for d in $domains; do
echo "$current_ip $d" >> "$hosts_tmp"
done
mv -f "$hosts_tmp" "$HOSTS_FILE"
killall -HUP dnsmasq 2>/dev/null
log "已即时同步域名配置至 hosts 并重载 dnsmasq (定向至: $current_ip)"
}
load_candidate_ips() {
local out_file="$1"
> "$out_file"
local ip_source
config_get ip_source settings ip_source "builtin"
if [ "$ip_source" = "builtin" ] || [ "$ip_source" = "mixed" ]; then
if [ -f "$DEFAULT_IPS_FILE" ]; then
grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' "$DEFAULT_IPS_FILE" >> "$out_file"
fi
fi
if [ "$ip_source" = "custom" ] || [ "$ip_source" = "mixed" ]; then
append_custom_ip() {
local item="$1"
[ -z "$item" ] && return
# 若为 CIDR 简单网段 (如 /24),取前 3 个 IP
case "$item" in
*/*)
local base_prefix=$(echo "$item" | cut -d'/' -f1 | cut -d'.' -f1-3)
for host in 1 2 3; do
echo "$base_prefix.$host" >> "$out_file"
done
;;
*)
echo "$item" | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' >> "$out_file"
;;
esac
}
config_list_foreach settings custom_ips append_custom_ip
fi
# 兜底:若文件为空,填入稳定官方公共 IP
if [ ! -s "$out_file" ]; then
echo "104.16.0.1" >> "$out_file"
echo "104.17.0.1" >> "$out_file"
echo "104.18.0.1" >> "$out_file"
echo "172.64.0.1" >> "$out_file"
fi
# 排序去重
sort -u "$out_file" > "$out_file.tmp" && mv -f "$out_file.tmp" "$out_file"
}
# 粗筛单节点探测
probe_fast() {
local ip="$1"
local res_dir="$2"
# 仅测 TCP 握手
local connect_time
connect_time=$(curl -s -k -o /dev/null --connect-timeout 1 -m 2 -w "%{time_connect}\n" \
--resolve "speed.cloudflare.com:443:$ip" "https://speed.cloudflare.com/__down?bytes=0" 2>/dev/null)
if [ $? -eq 0 ] && [ -n "$connect_time" ] && [ "$connect_time" != "0.000000" ]; then
local ms=$(awk "BEGIN {printf \"%08.2f\", $connect_time * 1000}")
echo "$ms $ip" > "$res_dir/$ip"
fi
}
run_optimization() {
. /lib/functions.sh
config_load "$CONF"
local enabled
config_get_bool enabled settings enabled 0
if [ "$enabled" != "1" ]; then
log "服务未启用,退出任务"
clean_hosts
return 0
fi
local domains=""
append_domain() {
local d="$1"
[ -n "$d" ] && domains="$domains $d"
}
config_list_foreach settings domains append_domain
if [ -z "$domains" ]; then
log "未配置目标加速域名,跳过优选"
return 0
fi
local concurrency sample_count hysteresis_ms
config_get concurrency settings concurrency 10
config_get sample_count settings sample_count 4
config_get hysteresis_ms settings hysteresis_ms 30
local colo_filter_mode
config_get colo_filter_mode settings colo_filter_mode "none"
local target_colos=""
append_target_colo() {
local item="$1"
[ -z "$item" ] && return
for c in $item; do
local clean_c=$(echo "$c" | tr 'a-z' 'A-Z' | tr -d ' \t\r\n')
if [ -n "$clean_c" ]; then
case " $target_colos " in
*" $clean_c "*) ;;
*) target_colos="$target_colos $clean_c" ;;
esac
fi
done
}
if [ "$colo_filter_mode" != "none" ]; then
local multi_colos=""
config_get multi_colos settings colo_list ""
[ -n "$multi_colos" ] && append_target_colo "$multi_colos"
config_list_foreach settings colo_list append_target_colo
config_list_foreach settings custom_colos append_target_colo
fi
check_colo_allowed() {
local colo="$1"
[ -z "$colo" ] && return 0
[ "$colo_filter_mode" = "none" ] && return 0
local match=0
for c in $target_colos; do
if [ "$c" = "$colo" ]; then
match=1
break
fi
done
if [ "$colo_filter_mode" = "exclude" ]; then
[ "$match" -eq 1 ] && return 1 || return 0
elif [ "$colo_filter_mode" = "include" ]; then
[ "$match" -eq 1 ] && return 0 || return 1
fi
return 0
}
log "开始执行优选任务,加速域名数: $(echo $domains | wc -w),机房策略: $colo_filter_mode${target_colos:+ ($target_colos)}"
local lock_dir="$RUN_DIR/task.lock"
if ! mkdir "$lock_dir" 2>/dev/null; then
local lock_pid=""
[ -f "$lock_dir/pid" ] && lock_pid=$(cat "$lock_dir/pid" 2>/dev/null)
if [ -n "$lock_pid" ] && ! kill -0 "$lock_pid" 2>/dev/null; then
log "检测到残留任务锁 (PID: $lock_pid 已退出),自动释放死锁"
rm -rf "$lock_dir"
mkdir "$lock_dir" 2>/dev/null || return 0
else
log "已有优选任务正在运行中,跳过本次执行"
return 0
fi
fi
echo $$ > "$lock_dir/pid"
mkdir -p "$TMP_DIR/fast" "$TMP_DIR/deep" "$RUN_DIR"
rm -rf "$TMP_DIR/fast"/* "$TMP_DIR/deep"/*
local cand_file="$TMP_DIR/candidates.txt"
load_candidate_ips "$cand_file"
local total_cands=$(wc -l < "$cand_file")
log "阶段一:开始并发初筛,候选节点总数: $total_cands,并发度: $concurrency"
# 并发初筛(采用 POSIX 批次等待机制,确保在任何非交互式 Shell 环境下严格限流)
local running=0
while read -r ip; do
[ -z "$ip" ] && continue
probe_fast "$ip" "$TMP_DIR/fast" &
running=$((running + 1))
if [ "$running" -ge "$concurrency" ]; then
wait
running=0
fi
done < "$cand_file"
wait
# 汇总初筛候选,结合机房缓存预先剔除黑名单机房
local fast_sorted_file="$TMP_DIR/fast_sorted.txt"
cat "$TMP_DIR/fast"/* 2>/dev/null | sort -n | head -n 30 > "$fast_sorted_file"
local top_selected_file="$TMP_DIR/top_selected_ips.txt"
> "$top_selected_file"
local picked_count=0
while read -r _lat ip; do
[ -z "$ip" ] && continue
local cached_colo=$(get_cached_colo "$ip")
if [ -n "$cached_colo" ] && ! check_colo_allowed "$cached_colo"; then
log "初筛预过滤: 节点 $ip ($cached_colo) 命中机房排除策略,自动略过"
continue
fi
echo "$ip" >> "$top_selected_file"
picked_count=$((picked_count + 1))
[ "$picked_count" -ge 10 ] && break
done < "$fast_sorted_file"
local top_count=$(wc -l < "$top_selected_file")
if [ "$top_count" -eq 0 ]; then
log "阶段一初筛无可用候选节点(或全部被机房区域策略过滤),请检查配置"
rm -rf "$TMP_DIR"
rm -rf "$lock_dir"
return 1
fi
local probe_domain=$(echo "$domains" | awk '{print $1}')
[ -z "$probe_domain" ] && probe_domain="speed.cloudflare.com"
log "阶段一初筛完成,选出 $top_count 个候选节点进入精测采样,探活域名: $probe_domain(每节点轮询交叉采样 $sample_count 轮)"
# 阶段二:轮询交叉时序采样(跨越完整测试时段,精准捕获阶段性丢包与网络毛刺)
mkdir -p "$TMP_DIR/samples"
for seq in $(seq 1 "$sample_count"); do
while read -r ip; do
[ -z "$ip" ] && continue
[ -f "$TMP_DIR/blocked_${ip}" ] && continue
[ -f "$TMP_DIR/colo_excluded_${ip}" ] && continue
local raw_header="$TMP_DIR/header_${ip}_${seq}.txt"
local metrics
metrics=$(curl -s -k -I -o /dev/null --connect-timeout 2 -m 3 \
-A "Mozilla/5.0 (compatible; Cloudflare-Optimizer/1.0)" \
--resolve "$probe_domain:443:$ip" "https://$probe_domain/" \
-D "$raw_header" \
-w "%{time_connect} %{time_appconnect} %{http_code}\n" 2>/dev/null)
local tcp_sec=$(echo "$metrics" | awk '{print $1}')
local tls_sec=$(echo "$metrics" | awk '{print $2}')
local http_code=$(echo "$metrics" | awk '{print $3}')
if [ "$http_code" = "403" ]; then
touch "$TMP_DIR/blocked_${ip}"
rm -f "$raw_header"
continue
fi
local last_colo=""
if [ -f "$raw_header" ]; then
local cf_ray=$(grep -i '^cf-ray:' "$raw_header" 2>/dev/null | tr -d '\r\n')
if [ -n "$cf_ray" ]; then
last_colo=$(echo "$cf_ray" | sed -E 's/.*-([A-Za-z0-9]+).*/\1/' | tr 'a-z' 'A-Z')
fi
rm -f "$raw_header"
fi
if [ -n "$last_colo" ]; then
update_colo_cache "$ip" "$last_colo"
if ! check_colo_allowed "$last_colo"; then
log "节点 $ip 机房 ($last_colo) 不符合区域过滤策略 ($colo_filter_mode),已终止采样并排除"
touch "$TMP_DIR/colo_excluded_${ip}"
continue
fi
fi
if [ -n "$http_code" ] && [ "$http_code" != "000" ] && [ -n "$tcp_sec" ] && [ "$tcp_sec" != "0.000000" ]; then
local tcp_ms=$(awk "BEGIN {printf \"%.2f\", $tcp_sec * 1000}")
local tls_ms=$(awk "BEGIN {printf \"%.2f\", $tls_sec * 1000}")
echo "1|$tcp_ms|$tls_ms|$last_colo" >> "$TMP_DIR/samples/$ip"
else
echo "0|0|0|$last_colo" >> "$TMP_DIR/samples/$ip"
fi
done < "$top_selected_file"
done
# 汇总统计各节点采样指标
while read -r ip; do
[ -z "$ip" ] && continue
if [ -f "$TMP_DIR/blocked_${ip}" ]; then
log "节点 $ip 触发权限阻断 (HTTP 403/企业限制),已自动排除"
continue
fi
if [ -f "$TMP_DIR/colo_excluded_${ip}" ]; then
continue
fi
local sample_file="$TMP_DIR/samples/$ip"
[ ! -f "$sample_file" ] && continue
local success_count=0
local total_tcp=0
local total_tls=0
local min_tcp=99999
local max_tcp=0
local last_colo=""
while IFS='|' read -r ok tcp_ms tls_ms colo; do
[ -n "$colo" ] && last_colo="$colo"
if [ "$ok" = "1" ]; then
success_count=$((success_count + 1))
total_tcp=$(awk "BEGIN {printf \"%.2f\", $total_tcp + $tcp_ms}")
total_tls=$(awk "BEGIN {printf \"%.2f\", $total_tls + $tls_ms}")
min_tcp=$(awk "BEGIN {if ($tcp_ms < $min_tcp) print $tcp_ms; else print $min_tcp}")
max_tcp=$(awk "BEGIN {if ($tcp_ms > $max_tcp) print $tcp_ms; else print $max_tcp}")
fi
done < "$sample_file"
if [ -z "$last_colo" ]; then
last_colo=$(get_cached_colo "$ip")
fi
if ! check_colo_allowed "$last_colo"; then
log "节点 $ip 机房 ($last_colo) 最终区域校验未通过,舍弃"
continue
fi
local loss_rate=$(awk "BEGIN {printf \"%.1f\", (1 - $success_count / $sample_count) * 100}")
if [ "$success_count" -gt 0 ]; then
local avg_tcp=$(awk "BEGIN {printf \"%.2f\", $total_tcp / $success_count}")
local avg_tls=$(awk "BEGIN {printf \"%.2f\", $total_tls / $success_count}")
local jitter=$(awk "BEGIN {printf \"%.2f\", $max_tcp - $min_tcp}")
# 综合评分:平均时延 + 抖动*0.5 + 丢包率*10(采用固定宽度 0 填充)
local score=$(awk "BEGIN {printf \"%08.2f\", $avg_tcp + $jitter * 0.5 + $loss_rate * 10}")
local colo_disp=$(get_colo_name "$last_colo")
echo "$score|$ip|$avg_tcp|$min_tcp|$max_tcp|$avg_tls|$loss_rate%|$colo_disp|$last_colo" >> "$TMP_DIR/deep_results.txt"
fi
done < "$top_selected_file"
if [ ! -s "$TMP_DIR/deep_results.txt" ]; then
log "精测采样后无可用健康节点"
rm -rf "$TMP_DIR"
rm -rf "$lock_dir"
return 1
fi
# 排序:按 score 从低到高数值排序
sort -n "$TMP_DIR/deep_results.txt" > "$TMP_DIR/deep_sorted.txt"
local best_line=$(head -n 1 "$TMP_DIR/deep_sorted.txt")
local best_score=$(echo "$best_line" | cut -d'|' -f1)
local best_ip=$(echo "$best_line" | cut -d'|' -f2)
local best_avg_tcp=$(echo "$best_line" | cut -d'|' -f3)
local best_min_tcp=$(echo "$best_line" | cut -d'|' -f4)
local best_max_tcp=$(echo "$best_line" | cut -d'|' -f5)
local best_avg_tls=$(echo "$best_line" | cut -d'|' -f6)
local best_loss=$(echo "$best_line" | cut -d'|' -f7)
local best_colo=$(echo "$best_line" | cut -d'|' -f8)
log "测速完成,最优候选: $best_ip ($best_colo), 平均延迟: ${best_avg_tcp}ms, 丢包率: $best_loss"
# 迟滞逻辑判断
local current_ip=""
[ -f "$CURRENT_IP_FILE" ] && current_ip=$(cat "$CURRENT_IP_FILE")
local final_ip="$best_ip"
local final_colo="$best_colo"
local final_tcp="$best_avg_tcp"
local final_tls="$best_avg_tls"
if [ -n "$current_ip" ] && [ "$current_ip" != "$best_ip" ]; then
local cur_info=$(grep "|$current_ip|" "$TMP_DIR/deep_sorted.txt" 2>/dev/null)
local cur_colo_allowed=1
local cur_cached_colo=$(get_cached_colo "$current_ip")
if [ -n "$cur_cached_colo" ] && ! check_colo_allowed "$cur_cached_colo"; then
cur_colo_allowed=0
fi
if [ -n "$cur_info" ] && [ "$cur_colo_allowed" -eq 1 ]; then
local cur_tcp=$(echo "$cur_info" | cut -d'|' -f3)
local cur_loss=$(echo "$cur_info" | cut -d'|' -f7)
local cur_colo=$(echo "$cur_info" | cut -d'|' -f8)
if [ "$cur_loss" = "0.0%" ]; then
# 测算相比当前在用 IP 的绝对时延差 (ms)
local diff_ms=$(awk "BEGIN {if ($cur_tcp > $best_avg_tcp) printf \"%.2f\", $cur_tcp - $best_avg_tcp; else print 0}")
local is_better=$(awk "BEGIN {print ($diff_ms >= $hysteresis_ms) ? 1 : 0}")
if [ "$is_better" -eq 0 ]; then
log "新优选 IP 优化幅度 (${diff_ms}ms) 未达迟滞阈值 (${hysteresis_ms}ms),保持当前在用 IP: $current_ip"
final_ip="$current_ip"
final_colo="$cur_colo"
final_tcp="$cur_tcp"
final_tls=$(echo "$cur_info" | cut -d'|' -f6)
else
log "新优选 IP 性能显著提升 (时延改善 ${diff_ms}ms >= ${hysteresis_ms}ms),触发切换"
fi
else
log "当前在用 IP 发生丢包 ($cur_loss),强制切换至新最优 IP: $best_ip"
fi
else
if [ "$cur_colo_allowed" -eq 0 ]; then
log "当前在用 IP ($current_ip / $cur_cached_colo) 已被机房过滤策略排除,强制切换至新最优 IP: $best_ip"
fi
fi
fi
# 原子写入 /tmp/hosts/cf-optimizer
local hosts_tmp="$HOSTS_FILE.tmp"
mkdir -p "$(dirname "$HOSTS_FILE")"
> "$hosts_tmp"
echo "# Auto-generated by cf-optimizer on $(date '+%Y-%m-%d %H:%M:%S')" >> "$hosts_tmp"
for d in $domains; do
echo "$final_ip $d" >> "$hosts_tmp"
done
mv -f "$hosts_tmp" "$HOSTS_FILE"
echo "$final_ip" > "$CURRENT_IP_FILE"
echo "$final_ip" > "$PRIMARY_IP_FILE"
echo "0" > "$FAIL_STREAK_FILE"
# 选拔次优备选节点(排名紧随其后且 IP 不同的健康节点)
local standby_ip=""
local standby_colo="无备选"
local standby_tcp="-"
while IFS='|' read -r _sc _ip _atip _minip _maxip _attls _lrate _cdisp _craw; do
if [ -n "$_ip" ] && [ "$_ip" != "$final_ip" ]; then
standby_ip="$_ip"
standby_colo="$_cdisp"
standby_tcp="$_atip"
break
fi
done < "$TMP_DIR/deep_sorted.txt"
echo "$standby_ip|$standby_colo|$standby_tcp" > "$STANDBY_IP_FILE"
echo "$final_tcp" > "$BASELINE_TCP_FILE"
# 发送 HUP 信号通知 dnsmasq 毫秒级重载
killall -HUP dnsmasq 2>/dev/null
log "已更新 $HOSTS_FILE 并热重载 dnsmasq,在用主选: $final_ip ($final_colo),次优备选: ${standby_ip:-无} ($standby_colo)"
# 组装状态 JSON 给 LuCI 前端消费
local now_str=$(date '+%Y-%m-%d %H:%M:%S')
local domain_json="["
local first_d=1
for d in $domains; do
[ "$first_d" -eq 0 ] && domain_json="$domain_json,"
domain_json="$domain_json{\"domain\":\"$d\",\"ip\":\"$final_ip\",\"colo\":\"$final_colo\",\"tcp_lat\":\"$final_tcp ms\",\"tls_lat\":\"$final_tls ms\",\"status\":\"正常\"}"
first_d=0
done
domain_json="$domain_json]"
local rankings_json="["
local first_r=1
local rank_idx=1
while IFS='|' read -r sc ip atip minip maxip attls lrate cdisp craw; do
[ -z "$ip" ] && continue
[ "$first_r" -eq 0 ] && rankings_json="$rankings_json,"
rankings_json="$rankings_json{\"rank\":$rank_idx,\"ip\":\"$ip\",\"colo\":\"$cdisp\",\"avg_lat\":\"$atip ms\",\"lat_range\":\"$minip / $maxip ms\",\"loss_rate\":\"$lrate\"}"
first_r=0
rank_idx=$((rank_idx + 1))
[ "$rank_idx" -gt 5 ] && break
done < "$TMP_DIR/deep_sorted.txt"
rankings_json="$rankings_json]"
cat <<EOF > "$STATUS_JSON.tmp"
{
"running": true,
"last_update": "$now_str",
"best_ip": "$final_ip",
"best_colo": "$final_colo",
"best_tcp_lat": "$final_tcp ms",
"best_tls_lat": "$final_tls ms",
"standby_ip": "$standby_ip",
"standby_colo": "$standby_colo",
"standby_tcp_lat": "$standby_tcp ms",
"watchdog_status": "正常监控中",
"domains": $domain_json,
"rankings": $rankings_json
}
EOF
mv -f "$STATUS_JSON.tmp" "$STATUS_JSON"
rm -rf "$TMP_DIR"
rm -rf "$lock_dir"
return 0
}
apply_fast_failover() {
local target_ip="$1"
local target_colo="$2"
local target_tcp="${3:--}"
. /lib/functions.sh
config_load "$CONF"
local domains=""
append_failover_domain() {
local d="$1"
[ -n "$d" ] && domains="$domains $d"
}
config_list_foreach settings domains append_failover_domain
local hosts_tmp="$HOSTS_FILE.tmp"
mkdir -p "$(dirname "$HOSTS_FILE")"
> "$hosts_tmp"
echo "# Auto-generated by cf-optimizer failover on $(date '+%Y-%m-%d %H:%M:%S')" >> "$hosts_tmp"
for d in $domains; do
echo "$target_ip $d" >> "$hosts_tmp"
done
mv -f "$hosts_tmp" "$HOSTS_FILE"
echo "$target_ip" > "$CURRENT_IP_FILE"
echo "" > "$STANDBY_IP_FILE"
if [ -n "$target_tcp" ] && [ "$target_tcp" != "-" ]; then
echo "$target_tcp" > "$BASELINE_TCP_FILE"
else
rm -f "$BASELINE_TCP_FILE"
fi
killall -HUP dnsmasq 2>/dev/null
log "已将 hosts 紧急倒换至次优节点: $target_ip ($target_colo) 并重载 dnsmasq"
if [ -f "$STATUS_JSON" ]; then
local now_str=$(date '+%Y-%m-%d %H:%M:%S')
local domain_json="["
local first_d=1
for d in $domains; do
[ "$first_d" -eq 0 ] && domain_json="$domain_json,"
domain_json="$domain_json{\"domain\":\"$d\",\"ip\":\"$target_ip\",\"colo\":\"$target_colo\",\"tcp_lat\":\"$target_tcp ms\",\"tls_lat\":\"-\",\"status\":\"已倒换\"}"
first_d=0
done
domain_json="$domain_json]"
local tmp_status="$STATUS_JSON.tmp"
sed -E "s/\"best_ip\":[[:space:]]*\"[^\"]*\"/\"best_ip\": \"$target_ip\"/" "$STATUS_JSON" | \
sed -E "s/\"best_colo\":[[:space:]]*\"[^\"]*\"/\"best_colo\": \"$target_colo\"/" | \
sed -E "s/\"standby_ip\":[[:space:]]*\"[^\"]*\"/\"standby_ip\": \"\"/" | \
sed -E "s/\"standby_colo\":[[:space:]]*\"[^\"]*\"/\"standby_colo\": \"已倒换转正\"/" | \
sed -E "s/\"last_update\":[[:space:]]*\"[^\"]*\"/\"last_update\": \"$now_str (已触发倒换)\"/" | \
sed -E "s/\"watchdog_status\":[[:space:]]*\"[^\"]*\"/\"watchdog_status\": \"已触发故障倒换\"/" | \
sed -E "s/\"domains\":[[:space:]]*\[[^]]*\]/\"domains\": $domain_json/" > "$tmp_status" 2>/dev/null
[ -s "$tmp_status" ] && mv -f "$tmp_status" "$STATUS_JSON"
fi
}
check_health_and_failover() {
[ ! -f "$CURRENT_IP_FILE" ] && return 0
local current_ip=$(cat "$CURRENT_IP_FILE" 2>/dev/null)
[ -z "$current_ip" ] && return 0
. /lib/functions.sh
config_load "$CONF"
local failover_enabled fail_threshold latency_multiplier
config_get_bool failover_enabled settings failover_enabled 1
[ "$failover_enabled" != "1" ] && return 0
config_get fail_threshold settings fail_threshold 2
config_get latency_multiplier settings latency_multiplier "2.0"
local domains=""
append_check_domain() {
local d="$1"
[ -n "$d" ] && domains="$domains $d"
}
config_list_foreach settings domains append_check_domain
local probe_domain=$(echo "$domains" | awk '{print $1}')
[ -z "$probe_domain" ] && probe_domain="speed.cloudflare.com"
local metrics
metrics=$(curl -s -k -I -o /dev/null --connect-timeout 2 -m 3 \
-A "Mozilla/5.0 (compatible; Cloudflare-Optimizer/1.0)" \
--resolve "$probe_domain:443:$current_ip" "https://$probe_domain/" \
-w "%{time_connect} %{http_code}\n" 2>/dev/null)
local tcp_sec=$(echo "$metrics" | awk '{print $1}')
local http_code=$(echo "$metrics" | awk '{print $2}')
local cur_tcp_ms=0
if [ -n "$tcp_sec" ] && [ "$tcp_sec" != "0.000000" ]; then
cur_tcp_ms=$(awk "BEGIN {printf \"%.2f\", $tcp_sec * 1000}")
fi
local baseline_tcp=0
[ -f "$BASELINE_TCP_FILE" ] && baseline_tcp=$(cat "$BASELINE_TCP_FILE" 2>/dev/null)
baseline_tcp=${baseline_tcp:-0}
local streak=0
[ -f "$FAIL_STREAK_FILE" ] && streak=$(cat "$FAIL_STREAK_FILE" 2>/dev/null)
streak=${streak:-0}
local is_abnormal=0
local abnormal_reason=""
if [ -z "$http_code" ] || [ "$http_code" = "000" ] || [ "$http_code" = "403" ]; then
is_abnormal=1
abnormal_reason="HTTP ${http_code:-000} 阻断或超时"
elif [ "$latency_multiplier" != "0" ] && [ "$latency_multiplier" != "0.0" ]; then
local is_degraded
is_degraded=$(awk "BEGIN {
if ($baseline_tcp > 0 && $cur_tcp_ms >= ($baseline_tcp * $latency_multiplier) && ($cur_tcp_ms - $baseline_tcp) >= 50)
print 1;
else
print 0;
}")
if [ "$is_degraded" -eq 1 ]; then
is_abnormal=1
abnormal_reason="延迟严重劣化 (${cur_tcp_ms}ms >= 基线 ${baseline_tcp}ms x ${latency_multiplier})"
fi
fi
if [ "$is_abnormal" -eq 0 ]; then
if [ "$streak" -gt 0 ]; then
log "主用优选节点 $current_ip 探活恢复正常 (HTTP $http_code, TCP ${cur_tcp_ms}ms)"
echo "0" > "$FAIL_STREAK_FILE"
fi
return 0
fi
streak=$((streak + 1))
echo "$streak" > "$FAIL_STREAK_FILE"
log "在用优选节点 $current_ip 探活异常 [$abnormal_reason],连续异常: $streak/$fail_threshold"
if [ "$streak" -ge "$fail_threshold" ]; then
echo "0" > "$FAIL_STREAK_FILE"
local standby_info=""
[ -f "$STANDBY_IP_FILE" ] && standby_info=$(cat "$STANDBY_IP_FILE" 2>/dev/null)
local standby_ip=$(echo "$standby_info" | cut -d'|' -f1)
local standby_colo=$(echo "$standby_info" | cut -d'|' -f2)
if [ -n "$standby_ip" ] && [ "$standby_ip" != "$current_ip" ]; then
log "正在核验次优备选节点健康度: $standby_ip ($standby_colo)"
local s_metrics
s_metrics=$(curl -s -k -I -o /dev/null --connect-timeout 2 -m 3 \
-A "Mozilla/5.0 (compatible; Cloudflare-Optimizer/1.0)" \
--resolve "$probe_domain:443:$standby_ip" "https://$probe_domain/" \
-w "%{time_connect} %{http_code}\n" 2>/dev/null)
local s_tcp_sec=$(echo "$s_metrics" | awk '{print $1}')
local s_code=$(echo "$s_metrics" | awk '{print $2}')
local s_tcp_ms="-"
[ -n "$s_tcp_sec" ] && [ "$s_tcp_sec" != "0.000000" ] && s_tcp_ms=$(awk "BEGIN {printf \"%.2f\", $s_tcp_sec * 1000}")
if [ -n "$s_code" ] && [ "$s_code" != "000" ] && [ "$s_code" != "403" ]; then
log "主用节点异常,次优备选节点验证健康 (HTTP $s_code, TCP ${s_tcp_ms}ms),秒级倒换至次优节点"
apply_fast_failover "$standby_ip" "$standby_colo" "$s_tcp_ms"
return 0
else
log "次优备选节点同样不可用 (HTTP ${s_code:-000}),提前拉起全网节点紧急重测"
fi
else
log "未配置次优备选节点,提前拉起全网节点紧急重测"
fi
run_optimization
fi
}
daemon_loop() {
echo $$ > "$PID_FILE"
trap 'rm -f "$PID_FILE"; exit 0' INT TERM EXIT
log "守护进程启动 [PID: $$]"
local last_full_run=0
while true; do
. /lib/functions.sh
config_load "$CONF"
local enabled
config_get_bool enabled settings enabled 0
if [ "$enabled" != "1" ]; then
log "配置已禁用,清理并退出守护进程"
clean_hosts
break
fi
local interval failover_enabled health_interval
config_get interval settings interval 120
[ "$interval" -lt 10 ] && interval=10
local full_interval_sec=$((interval * 60))
config_get_bool failover_enabled settings failover_enabled 1
config_get health_interval settings health_interval 60
[ "$health_interval" -lt 15 ] && health_interval=15
local now=$(date +%s)
if [ "$last_full_run" -eq 0 ] || [ $((now - last_full_run)) -ge "$full_interval_sec" ]; then
run_optimization
last_full_run=$(date +%s)
elif [ "$failover_enabled" = "1" ]; then
check_health_and_failover
fi
local sleep_sec="$health_interval"
if [ "$failover_enabled" != "1" ]; then
sleep_sec=30
fi
local elapsed=0
while [ "$elapsed" -lt "$sleep_sec" ]; do
sleep 5 &
wait $!
elapsed=$((elapsed + 5))
done
done
}
case "$1" in
run)
run_optimization
;;
sync-hosts)
sync_hosts
;;
daemon)
daemon_loop
;;
clean)
clean_hosts
;;
status)
if [ -f "$STATUS_JSON" ]; then
cat "$STATUS_JSON"
else
echo '{"running":false,"domains":[],"rankings":[]}'
fi
;;
*)
echo "用法: $0 {run|sync-hosts|daemon|clean|status}"
exit 1
;;
esac
@@ -0,0 +1,58 @@
{
"HKG": "香港 HKG",
"TPE": "台北 TPE",
"KHH": "高雄 KHH",
"NRT": "东京 NRT",
"HND": "东京羽田 HND",
"KIX": "大阪 KIX",
"FUK": "福冈 FUK",
"ICN": "首尔 ICN",
"SIN": "新加坡 SIN",
"BKK": "曼谷 BKK",
"KUL": "吉隆坡 KUL",
"JHB": "新山 JHB",
"MNL": "马尼拉 MNL",
"SGN": "胡志明市 SGN",
"HAN": "河内 HAN",
"CGK": "雅加达 CGK",
"DPS": "巴厘岛 DPS",
"SJC": "圣何塞 SJC",
"SFO": "旧金山 SFO",
"LAX": "洛杉矶 LAX",
"SEA": "西雅图 SEA",
"PDX": "波特兰 PDX",
"DEN": "丹佛 DEN",
"PHX": "菲尼克斯 PHX",
"DFW": "达拉斯 DFW",
"IAH": "休斯顿 IAH",
"ORD": "芝加哥 ORD",
"ATL": "亚特兰大 ATL",
"MIA": "迈阿密 MIA",
"IAD": "华盛顿 IAD",
"EWR": "纽瓦克 EWR",
"JFK": "纽约 JFK",
"BOS": "波士顿 BOS",
"YVR": "温哥华 YVR",
"YYZ": "多伦多 YYZ",
"YUL": "蒙特利尔 YUL",
"FRA": "法兰克福 FRA",
"LHR": "伦敦 LHR",
"AMS": "阿姆斯特丹 AMS",
"CDG": "巴黎 CDG",
"MAD": "马德里 MAD",
"MXP": "米兰 MXP",
"ZRH": "苏黎世 ZRH",
"VIE": "维也纳 VIE",
"ARN": "斯德哥尔摩 ARN",
"HEL": "赫尔辛基 HEL",
"CPH": "哥本哈根 CPH",
"WAW": "华沙 WAW",
"PRG": "布拉格 PRG",
"SYD": "悉尼 SYD",
"MEL": "墨尔本 MEL",
"BNE": "布里斯班 BNE",
"PER": "珀斯 PER",
"AKL": "奥克兰 AKL",
"DXB": "迪拜 DXB",
"DOH": "多哈 DOH"
}
@@ -0,0 +1,102 @@
104.16.0.1
104.16.1.1
104.16.8.1
104.16.16.1
104.16.24.1
104.16.32.1
104.16.48.1
104.16.64.1
104.16.80.1
104.16.96.1
104.16.112.1
104.16.128.1
104.16.144.1
104.16.160.1
104.16.176.1
104.16.192.1
104.16.208.1
104.16.224.1
104.16.240.1
104.17.0.1
104.17.16.1
104.17.32.1
104.17.64.1
104.17.96.1
104.17.128.1
104.17.160.1
104.17.192.1
104.17.224.1
104.18.0.1
104.18.16.1
104.18.32.1
104.18.64.1
104.18.96.1
104.18.128.1
104.18.160.1
104.18.192.1
104.18.224.1
104.19.0.1
104.19.16.1
104.19.32.1
104.19.64.1
104.19.96.1
104.19.128.1
104.19.160.1
104.19.192.1
104.19.224.1
104.20.0.1
104.20.32.1
104.20.64.1
104.20.96.1
104.20.128.1
104.20.160.1
104.20.192.1
104.21.0.1
104.21.16.1
104.21.32.1
104.21.64.1
104.21.96.1
104.22.0.1
104.22.32.1
104.22.64.1
104.24.0.1
104.24.32.1
104.25.0.1
104.25.32.1
104.26.0.1
104.26.32.1
104.27.0.1
104.27.32.1
172.64.0.1
172.64.16.1
172.64.32.1
172.64.64.1
172.64.96.1
172.64.128.1
172.64.160.1
172.64.192.1
172.65.0.1
172.65.32.1
172.66.0.1
172.66.32.1
172.67.0.1
172.67.32.1
172.67.64.1
172.67.128.1
162.158.0.1
162.158.16.1
162.158.32.1
162.158.64.1
162.158.128.1
108.162.192.1
108.162.208.1
108.162.224.1
141.101.64.1
141.101.96.1
141.101.120.1
190.93.240.1
197.234.240.1
198.41.128.1
198.41.160.1
198.41.192.1
198.41.224.1
@@ -0,0 +1,37 @@
{
"admin/services/cf-optimizer": {
"title": "Cloudflare 优选加速",
"order": 65,
"action": {
"type": "firstchild"
},
"depends": {
"acl": [ "luci-app-cf-optimizer" ],
"uci": { "cf-optimizer": true }
}
},
"admin/services/cf-optimizer/overview": {
"title": "状态概览",
"order": 10,
"action": {
"type": "view",
"path": "cf-optimizer/overview"
}
},
"admin/services/cf-optimizer/settings": {
"title": "参数配置",
"order": 20,
"action": {
"type": "view",
"path": "cf-optimizer/settings"
}
},
"admin/services/cf-optimizer/log": {
"title": "运行日志",
"order": 30,
"action": {
"type": "view",
"path": "cf-optimizer/log"
}
}
}
@@ -0,0 +1,28 @@
{
"luci-app-cf-optimizer": {
"description": "Grant access to cf-optimizer LuCI procedures",
"read": {
"ubus": {
"luci.cf-optimizer": [
"get_status",
"get_logs"
]
},
"uci": [
"cf-optimizer"
]
},
"write": {
"ubus": {
"luci.cf-optimizer": [
"run_once",
"clear_logs",
"service_action"
]
},
"uci": [
"cf-optimizer"
]
}
}
}
@@ -0,0 +1,130 @@
'use strict';
import { connect } from 'ubus';
import { access, readfile, writefile, stat, popen } from 'fs';
function exec(cmd) {
let p = popen(cmd, 'r');
if (!p) return { code: -1, stdout: [] };
let out = [];
while (true) {
let line = p.read('line');
if (line == null) break;
push(out, rtrim(line, '\r\n'));
}
let code = p.close();
return { code: code, stdout: out };
}
const _trim = trim;
function trim_str(s) {
if (s == null) return '';
return _trim('' + s);
}
function get_procd_status() {
let u = connect();
if (!u) return { running: false };
let res = u.call('service', 'list', { name: 'cf-optimizer' });
if (res && res['cf-optimizer'] && res['cf-optimizer'].instances) {
let instances = res['cf-optimizer'].instances;
for (let name in instances) {
let inst = instances[name];
if (inst && inst.running)
return { running: true, pid: inst.pid };
}
}
let pid_res = exec('pidof cf-optimizer 2>/dev/null');
if (pid_res.code == 0 && length(pid_res.stdout) > 0) {
let pids = split(trim_str(join(' ', pid_res.stdout)), /\s+/);
if (length(pids) > 0 && pids[0] != '')
return { running: true, pid: +pids[0] };
}
return { running: false };
}
const methods = {};
methods.get_status = {
call: function() {
let proc_st = get_procd_status();
let status_data = null;
const status_file = '/tmp/run/cf-optimizer/status.json';
if (access(status_file)) {
let content = readfile(status_file);
if (content) {
try {
status_data = json(content);
} catch(e) {}
}
}
if (!status_data) {
status_data = {
running: proc_st.running,
last_update: '',
best_ip: '',
best_colo: '',
best_tcp_lat: '',
best_tls_lat: '',
domains: [],
rankings: []
};
}
status_data.service_running = proc_st.running;
status_data.service_pid = proc_st.pid || null;
return status_data;
}
};
methods.get_logs = {
call: function() {
const log_file = '/var/log/cf-optimizer.log';
let logs = [];
if (access(log_file)) {
let res = exec('tail -n 120 ' + log_file + ' 2>/dev/null');
if (res.code == 0) logs = res.stdout;
}
return { logs: logs };
}
};
methods.clear_logs = {
call: function() {
const log_file = '/var/log/cf-optimizer.log';
writefile(log_file, '');
return { success: true };
}
};
methods.run_once = {
call: function() {
exec('/usr/sbin/cf-optimizer run >/dev/null 2>&1 &');
return { success: true };
}
};
methods.service_action = {
args: { action: 'action' },
call: function(req) {
let action = req.args?.action;
if (!action) return { success: false, error: 'Missing action parameter' };
if (action == 'restart') {
exec('/etc/init.d/cf-optimizer restart >/dev/null 2>&1');
} else if (action == 'start') {
exec('/etc/init.d/cf-optimizer start >/dev/null 2>&1');
} else if (action == 'stop') {
exec('/etc/init.d/cf-optimizer stop >/dev/null 2>&1');
}
return { success: true };
}
};
return { 'luci.cf-optimizer': methods };