mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-10 20:47:52 +08:00
💋 Sync 2026-10-04 04:16:09
This commit is contained in:
1 parent
f6b2df73ec
commit
f62992fd80
745 files changed
+212416
-1173
No files matched your search
@@ -0,0 +1,12 @@
|
||||
config main 'config'
|
||||
option enabled '0'
|
||||
option listen_port '5335'
|
||||
option schedule '0 5 * * *'
|
||||
option threads '4'
|
||||
option probes '4'
|
||||
option download_count '5'
|
||||
option timeout '5'
|
||||
option max_latency '300'
|
||||
option min_speed '1'
|
||||
option test_url 'https://speed.cloudflare.com/__down?bytes=20000000'
|
||||
list domains 'www.cloudflare.com'
|
||||
@@ -0,0 +1,28 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
|
||||
START=91
|
||||
USE_PROCD=1
|
||||
|
||||
start_service() {
|
||||
local enabled port
|
||||
config_load cf_dns_speedtest
|
||||
config_get_bool enabled config enabled 0
|
||||
[ "$enabled" = 1 ] || return 0
|
||||
[ -s /var/lib/cf-dns-speedtest/mosdns.yaml ] || /usr/bin/cf-dns-speedtest apply
|
||||
procd_open_instance
|
||||
procd_set_param command /usr/bin/mosdns start -c /var/lib/cf-dns-speedtest/mosdns.yaml
|
||||
procd_set_param respawn 3600 5 5
|
||||
procd_set_param stdout 1
|
||||
procd_set_param stderr 1
|
||||
procd_set_param limits nofile="65535 65535"
|
||||
procd_close_instance
|
||||
}
|
||||
|
||||
service_triggers() {
|
||||
procd_add_reload_trigger cf_dns_speedtest
|
||||
}
|
||||
|
||||
reload_service() {
|
||||
stop
|
||||
start
|
||||
}
|
||||
+191
@@ -0,0 +1,191 @@
|
||||
#!/bin/sh
|
||||
|
||||
NAME=cf-dns-speedtest
|
||||
STATE=/var/lib/$NAME
|
||||
RUN=/var/run/$NAME
|
||||
CFG=cf_dns_speedtest.config
|
||||
LOG_MAX=131072
|
||||
|
||||
rotate_log() {
|
||||
[ -f "$STATE/run.log" ] || return 0
|
||||
[ "$(wc -c < "$STATE/run.log")" -le "$LOG_MAX" ] && return 0
|
||||
tail -c 65536 "$STATE/run.log" > "$STATE/run.log.new" && mv "$STATE/run.log.new" "$STATE/run.log"
|
||||
}
|
||||
|
||||
log() {
|
||||
mkdir -p "$STATE"
|
||||
logger -t "$NAME" "$*"
|
||||
printf '%s %s\n' "$(date '+%F %T')" "$*" >> "$STATE/run.log"
|
||||
rotate_log
|
||||
}
|
||||
|
||||
cfg() { uci -q get "$CFG.$1"; }
|
||||
|
||||
uint() {
|
||||
case "$1" in ''|*[!0-9]*) printf '%s' "$2";; *) printf '%s' "$1";; esac
|
||||
}
|
||||
|
||||
domains() {
|
||||
uci -q get "$CFG.domains" | tr ' ' '\n' | sed 's/^\.//;s/\.$//' |
|
||||
grep -E '^[A-Za-z0-9]([A-Za-z0-9_-]*[A-Za-z0-9])?([.][A-Za-z0-9]([A-Za-z0-9_-]*[A-Za-z0-9])?)+$' | sort -u
|
||||
}
|
||||
|
||||
valid_ipv4() {
|
||||
printf '%s\n' "$1" | awk -F. 'NF==4 { for (i=1;i<=4;i++) if ($i !~ /^[0-9]+$/ || $i>255) exit 1; exit 0 } { exit 1 }'
|
||||
}
|
||||
|
||||
remove_old_routes() {
|
||||
local route old port
|
||||
if [ -s "$STATE/routes.active" ]; then
|
||||
while IFS= read -r route; do
|
||||
[ -n "$route" ] && uci -q del_list dhcp.@dnsmasq[0].server="$route"
|
||||
done < "$STATE/routes.active"
|
||||
elif [ -s "$STATE/domains.active" ]; then
|
||||
# Migration from versions before 1.1.0. Only remove domain-scoped
|
||||
# routes owned by this package; never touch the generic DNS upstream.
|
||||
port="$(uint "$(cfg listen_port)" 5335)"
|
||||
while IFS= read -r old; do
|
||||
[ -n "$old" ] && uci -q del_list dhcp.@dnsmasq[0].server="/$old/127.0.0.1#$port"
|
||||
done < "$STATE/domains.active"
|
||||
fi
|
||||
}
|
||||
|
||||
make_config() {
|
||||
local ip="$1" port d route
|
||||
port="$(uint "$(cfg listen_port)" 5335)"
|
||||
[ "$port" -ge 1024 ] 2>/dev/null && [ "$port" -le 65535 ] 2>/dev/null || port=5335
|
||||
mkdir -p "$STATE"
|
||||
domains > "$STATE/domains.new"
|
||||
[ -s "$STATE/domains.new" ] || { rm -f "$STATE/domains.new"; log "no valid domains configured"; return 1; }
|
||||
: > "$STATE/hosts.txt.new"
|
||||
: > "$STATE/routes.new"
|
||||
while IFS= read -r d; do
|
||||
printf '%s %s\n' "$d" "$ip" >> "$STATE/hosts.txt.new"
|
||||
printf '/%s/127.0.0.1#%s\n' "$d" "$port" >> "$STATE/routes.new"
|
||||
done < "$STATE/domains.new"
|
||||
cat > "$STATE/mosdns.yaml.new" <<EOF
|
||||
log:
|
||||
level: warn
|
||||
plugins:
|
||||
- tag: cf_hosts
|
||||
type: hosts
|
||||
args:
|
||||
files:
|
||||
- $STATE/hosts.txt
|
||||
- tag: main
|
||||
type: sequence
|
||||
args:
|
||||
- exec: \$cf_hosts
|
||||
- matches:
|
||||
- has_resp
|
||||
exec: accept
|
||||
- exec: reject 3
|
||||
- type: udp_server
|
||||
args:
|
||||
entry: main
|
||||
listen: 127.0.0.1:$port
|
||||
- type: tcp_server
|
||||
args:
|
||||
entry: main
|
||||
listen: 127.0.0.1:$port
|
||||
EOF
|
||||
remove_old_routes
|
||||
if [ "$(cfg enabled)" = 1 ]; then
|
||||
while IFS= read -r route; do
|
||||
[ -n "$route" ] && uci -q add_list dhcp.@dnsmasq[0].server="$route"
|
||||
done < "$STATE/routes.new"
|
||||
fi
|
||||
uci -q commit dhcp || return 1
|
||||
mv "$STATE/hosts.txt.new" "$STATE/hosts.txt"
|
||||
mv "$STATE/mosdns.yaml.new" "$STATE/mosdns.yaml"
|
||||
mv "$STATE/domains.new" "$STATE/domains.active"
|
||||
mv "$STATE/routes.new" "$STATE/routes.active"
|
||||
}
|
||||
|
||||
update_cron() {
|
||||
local schedule tmp=/tmp/$NAME.cron.$$
|
||||
schedule="$(cfg schedule)"
|
||||
grep -v '# cf-dns-speedtest$' /etc/crontabs/root 2>/dev/null > "$tmp" || :
|
||||
if [ -n "$schedule" ] && [ "$(cfg enabled)" = 1 ]; then
|
||||
printf '%s /usr/bin/cf-dns-speedtest start # cf-dns-speedtest\n' "$schedule" >> "$tmp"
|
||||
fi
|
||||
mv "$tmp" /etc/crontabs/root
|
||||
/etc/init.d/cron restart >/dev/null 2>&1
|
||||
}
|
||||
|
||||
restart_dns() {
|
||||
/etc/init.d/$NAME restart || return 1
|
||||
/etc/init.d/dnsmasq restart || return 1
|
||||
}
|
||||
|
||||
apply() {
|
||||
local ip
|
||||
mkdir -p "$STATE"
|
||||
ip="$(cat "$STATE/best_ip" 2>/dev/null)"
|
||||
valid_ipv4 "$ip" || ip=1.1.1.1
|
||||
make_config "$ip" || return 1
|
||||
update_cron
|
||||
restart_dns || { log "service restart failed"; return 1; }
|
||||
log "configuration applied; active IP: $ip"
|
||||
}
|
||||
|
||||
run_test() {
|
||||
local threads probes count timeout latency speed url ip old_ip
|
||||
mkdir -p "$STATE" "$RUN"
|
||||
if [ -f "$RUN/pid" ] && kill -0 "$(cat "$RUN/pid")" 2>/dev/null; then log "a test is already running"; return 2; fi
|
||||
rm -f "$RUN/pid"
|
||||
printf '%s\n' "$$" > "$RUN/pid"
|
||||
trap 'rm -f "$RUN/pid" "$STATE/result.csv.new"' EXIT INT TERM
|
||||
rotate_log
|
||||
echo running > "$STATE/status"
|
||||
threads="$(uint "$(cfg threads)" 4)"; [ "$threads" -ge 1 ] && [ "$threads" -le 64 ] || threads=4
|
||||
probes="$(uint "$(cfg probes)" 4)"; [ "$probes" -ge 1 ] && [ "$probes" -le 20 ] || probes=4
|
||||
count="$(uint "$(cfg download_count)" 5)"; [ "$count" -ge 1 ] && [ "$count" -le 20 ] || count=5
|
||||
timeout="$(uint "$(cfg timeout)" 5)"; [ "$timeout" -ge 1 ] && [ "$timeout" -le 30 ] || timeout=5
|
||||
latency="$(uint "$(cfg max_latency)" 300)"; [ "$latency" -ge 1 ] && [ "$latency" -le 9999 ] || latency=300
|
||||
speed="$(cfg min_speed)"; case "$speed" in ''|*[!0-9.]*) speed=1;; esac
|
||||
url="$(cfg test_url)"; case "$url" in https://*|http://*) :;; *) echo failed > "$STATE/status"; log "invalid test URL"; return 1;; esac
|
||||
cd /usr/share/$NAME || return 1
|
||||
log "speed test started"
|
||||
if ./cfst -f ip.txt -o "$STATE/result.csv.new" -n "$threads" -t "$probes" -dn "$count" -dt "$timeout" -tl "$latency" -sl "$speed" -p 10 -url "$url" > "$STATE/cfst.out" 2>&1; then
|
||||
ip="$(awk -F, 'NR==2 {gsub(/\r/,""); print $1; exit}' "$STATE/result.csv.new")"
|
||||
if ! valid_ipv4 "$ip"; then echo failed > "$STATE/status"; log "no valid IPv4 result"; return 1; fi
|
||||
old_ip="$(cat "$STATE/best_ip" 2>/dev/null)"
|
||||
if ! make_config "$ip" || ! restart_dns; then
|
||||
[ -n "$old_ip" ] && make_config "$old_ip" && restart_dns
|
||||
echo failed > "$STATE/status"; log "new DNS configuration failed; previous IP restored"; return 1
|
||||
fi
|
||||
mv "$STATE/result.csv.new" "$STATE/result.csv"
|
||||
printf '%s\n' "$ip" > "$STATE/best_ip"
|
||||
date '+%F %T' > "$STATE/last_run"
|
||||
echo success > "$STATE/status"
|
||||
log "speed test completed; best IP: $ip"
|
||||
else
|
||||
echo failed > "$STATE/status"
|
||||
log "speed test failed; previous DNS configuration retained"
|
||||
return 1
|
||||
fi
|
||||
# Keep diagnostic output bounded without polluting the user-facing log.
|
||||
if [ "$(wc -c < "$STATE/cfst.out")" -gt 65536 ]; then
|
||||
tail -c 65536 "$STATE/cfst.out" > "$STATE/cfst.out.new" && mv "$STATE/cfst.out.new" "$STATE/cfst.out"
|
||||
fi
|
||||
}
|
||||
|
||||
stop_test() {
|
||||
if [ -f "$RUN/pid" ] && kill -0 "$(cat "$RUN/pid")" 2>/dev/null; then
|
||||
kill "$(cat "$RUN/pid")" 2>/dev/null
|
||||
log "speed test stopped"
|
||||
fi
|
||||
rm -f "$RUN/pid"
|
||||
echo stopped > "$STATE/status"
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start) mkdir -p "$STATE" "$RUN"; (run_test </dev/null >/dev/null 2>&1 &) ;;
|
||||
run) run_test ;;
|
||||
stop) stop_test ;;
|
||||
apply) apply ;;
|
||||
status) printf 'status=%s\nbest_ip=%s\nlast_run=%s\n' "$(cat "$STATE/status" 2>/dev/null || echo idle)" "$(cat "$STATE/best_ip" 2>/dev/null || echo '-')" "$(cat "$STATE/last_run" 2>/dev/null || echo '-')"; tail -n 20 "$STATE/run.log" 2>/dev/null ;;
|
||||
ui) printf 'status=%s\nbest_ip=%s\nenabled=%s\nlast_run=%s\nscheduled=%s\n' "$(cat "$STATE/status" 2>/dev/null || echo idle)" "$(cat "$STATE/best_ip" 2>/dev/null || echo '-')" "$(cfg enabled)" "$(cat "$STATE/last_run" 2>/dev/null || echo '-')" "$(grep -c '# cf-dns-speedtest$' /etc/crontabs/root 2>/dev/null)"; [ -s "$STATE/result.csv" ] && cat "$STATE/result.csv" ;;
|
||||
*) echo "Usage: $0 {start|run|stop|apply|status|ui}"; exit 1 ;;
|
||||
esac
|
||||
Binary file not shown.
@@ -0,0 +1,15 @@
|
||||
173.245.48.1
|
||||
103.21.244.1
|
||||
103.22.200.1
|
||||
103.31.4.1
|
||||
141.101.64.1
|
||||
108.162.192.1
|
||||
190.93.240.1
|
||||
188.114.96.1
|
||||
197.234.240.1
|
||||
198.41.128.1
|
||||
162.158.0.1
|
||||
104.16.0.1
|
||||
104.24.0.1
|
||||
172.64.0.1
|
||||
131.0.72.1
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"admin/services/cf-dns-speedtest": {
|
||||
"title": "Cloudflare DNS 优选",
|
||||
"order": 65,
|
||||
"action": { "type": "view", "path": "cf-dns-speedtest" },
|
||||
"depends": { "acl": [ "luci-app-cf-dns-speedtest" ] }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"luci-app-cf-dns-speedtest": {
|
||||
"description": "Cloudflare DNS speedtest configuration",
|
||||
"read": {
|
||||
"uci": [ "cf_dns_speedtest" ],
|
||||
"file": {
|
||||
"/var/lib/cf-dns-speedtest/status": [ "read" ],
|
||||
"/var/lib/cf-dns-speedtest/best_ip": [ "read" ],
|
||||
"/var/lib/cf-dns-speedtest/run.log": [ "read" ]
|
||||
}
|
||||
},
|
||||
"write": {
|
||||
"uci": [ "cf_dns_speedtest" ],
|
||||
"file": {
|
||||
"/usr/bin/cf-dns-speedtest": [ "exec" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
'use strict';
|
||||
'require view';
|
||||
'require form';
|
||||
'require fs';
|
||||
'require ui';
|
||||
|
||||
return view.extend({
|
||||
load: function() {
|
||||
return L.resolveDefault(fs.exec('/usr/bin/cf-dns-speedtest', [ 'ui' ]), {
|
||||
stdout: 'status=idle\nbest_ip=-\nenabled=0\nlast_run=-\nscheduled=0\n'
|
||||
});
|
||||
},
|
||||
|
||||
render: function(data) {
|
||||
var m, s, o;
|
||||
m = new form.Map('cf_dns_speedtest', _('Cloudflare DNS 优选'),
|
||||
_('测速后仅将指定域名交给独立 mosdns 实例返回优选 IP;其他 DNS 查询继续使用原有链路。'));
|
||||
s = m.section(form.TypedSection, 'main', _('设置'));
|
||||
s.anonymous = true;
|
||||
o = s.option(form.Flag, 'enabled', _('启用'));
|
||||
o.rmempty = false;
|
||||
o = s.option(form.DynamicList, 'domains', _('Cloudflare 域名'));
|
||||
o.datatype = 'hostname';
|
||||
o.placeholder = 'www.cloudflare.com';
|
||||
o = s.option(form.Value, 'listen_port', _('mosdns 旁路端口'));
|
||||
o.datatype = 'port'; o.default = '5335';
|
||||
o = s.option(form.Value, 'schedule', _('定时任务'));
|
||||
o.placeholder = '0 5 * * *';
|
||||
o.description = _('标准 cron 表达式,留空则不定时测速。保存后请点击“应用 DNS 配置”。');
|
||||
o = s.option(form.Value, 'threads', _('测速线程'));
|
||||
o.datatype = 'range(1,64)'; o.default = '4';
|
||||
o = s.option(form.Value, 'probes', _('延迟测速次数'));
|
||||
o.datatype = 'range(1,20)'; o.default = '4';
|
||||
o = s.option(form.Value, 'download_count', _('下载测速数量'));
|
||||
o.datatype = 'range(1,20)'; o.default = '5';
|
||||
o = s.option(form.Value, 'timeout', _('单项超时(秒)'));
|
||||
o.datatype = 'range(1,30)'; o.default = '5';
|
||||
o = s.option(form.Value, 'max_latency', _('最大延迟(毫秒)'));
|
||||
o.datatype = 'range(1,9999)'; o.default = '300';
|
||||
o = s.option(form.Value, 'min_speed', _('最低速度(MB/s)'));
|
||||
o.datatype = 'ufloat'; o.default = '1';
|
||||
o = s.option(form.Value, 'test_url', _('测速 URL'));
|
||||
o.datatype = 'string';
|
||||
|
||||
s = m.section(form.NamedSection, 'runtime', 'runtime', _('运行状态'));
|
||||
s.render = function() {
|
||||
var lines = String(data.stdout || '').trim().split('\n');
|
||||
var status = (lines.shift() || 'status=idle').replace(/^status=/, '');
|
||||
var ip = (lines.shift() || 'best_ip=-').replace(/^best_ip=/, '');
|
||||
var enabled = (lines.shift() || 'enabled=0').replace(/^enabled=/, '');
|
||||
var lastRun = (lines.shift() || 'last_run=-').replace(/^last_run=/, '');
|
||||
var scheduled = (lines.shift() || 'scheduled=0').replace(/^scheduled=/, '');
|
||||
var rows = lines.filter(function(line) { return line.length; }).map(function(line) { return line.split(','); });
|
||||
var widths = [ '20%', '10%', '10%', '10%', '15%', '20%', '15%' ];
|
||||
var resultTable = rows.length ? E('div', { style: 'width:100%;overflow-x:auto;margin-top:1rem' }, [
|
||||
E('table', { style: 'display:table;width:100%;min-width:620px;table-layout:fixed;border-collapse:collapse' }, [
|
||||
E('thead', {}, E('tr', {}, rows[0].map(function(cell, index) {
|
||||
return E('th', { style: 'width:' + widths[index] + ';padding:.65rem .6rem;text-align:left;white-space:nowrap;border-bottom:1px solid var(--border-color-medium,#444)' }, cell);
|
||||
}))),
|
||||
E('tbody', {}, rows.slice(1).map(function(row) {
|
||||
return E('tr', {}, row.map(function(cell, index) {
|
||||
return E('td', { style: 'width:' + widths[index] + ';padding:.65rem .6rem;text-align:left;white-space:nowrap;border-bottom:1px solid var(--border-color-low,#222)' }, cell);
|
||||
}));
|
||||
}))
|
||||
])
|
||||
]) : E('p', { style: 'margin-top:1rem' }, _('尚无测速结果'));
|
||||
function card(label, value) {
|
||||
return E('div', { style: 'padding:.8rem;border:1px solid var(--border-color-medium,#333);border-radius:6px' }, [
|
||||
E('small', {}, label), E('div', { style: 'margin-top:.3rem;font-weight:600' }, value)
|
||||
]);
|
||||
}
|
||||
return E('div', { class: 'cbi-section' }, [
|
||||
E('div', { style: 'display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:.75rem;margin-bottom:1rem' }, [
|
||||
card(_('状态'), status), card(_('当前优选 IP'), ip),
|
||||
card(_('DNS 应用'), enabled === '1' ? _('已启用') : _('未启用')),
|
||||
card(_('上次测速'), lastRun), card(_('定时任务'), scheduled !== '0' ? _('已生效') : _('未生效'))
|
||||
]),
|
||||
E('div', { style: 'display:flex;flex-wrap:wrap;gap:.5rem;align-items:center' }, [
|
||||
E('button', { class: 'btn cbi-button-action', click: ui.createHandlerFn(this, function() {
|
||||
return fs.exec('/usr/bin/cf-dns-speedtest', [ 'start' ]).then(function() {
|
||||
ui.addNotification(null, E('p', _('测速已在后台启动。')));
|
||||
window.setTimeout(function() { location.reload(); }, 1500);
|
||||
});
|
||||
}) }, _('立即测速')),
|
||||
E('button', { class: 'btn cbi-button-negative', click: ui.createHandlerFn(this, function() {
|
||||
return fs.exec('/usr/bin/cf-dns-speedtest', [ 'stop' ]).then(function() { location.reload(); });
|
||||
}) }, _('停止测速')),
|
||||
E('button', { class: 'btn cbi-button-apply', click: ui.createHandlerFn(this, function() {
|
||||
return fs.exec('/usr/bin/cf-dns-speedtest', [ 'apply' ]).then(function() { location.reload(); });
|
||||
}) }, _('应用 DNS 配置')),
|
||||
E('button', { class: 'btn', click: function() { location.reload(); } }, _('刷新状态'))
|
||||
]),
|
||||
resultTable
|
||||
]);
|
||||
};
|
||||
return m.render();
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user