mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-14 20:48:17 +08:00
💋 Sync 2026-11-02 01:02:56
This commit is contained in:
1 parent
d97dc35e3d
commit
2f941f0ffb
48 files changed
+4891
-1002
No files matched your search
@@ -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 ""
|
||||
Symlink
+1
@@ -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
@@ -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
@@ -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 };
|
||||
Reference in new issue
Block a user