diff --git a/bash-completion/Makefile b/bash-completion/Makefile new file mode 100644 index 00000000..d7a58492 --- /dev/null +++ b/bash-completion/Makefile @@ -0,0 +1,43 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=bash-completion +PKG_VERSION:=2.14.0 +PKG_RELEASE:=1 + +PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.xz +PKG_SOURCE_URL:=https://github.com/scop/bash-completion/releases/download/$(PKG_VERSION) +PKG_HASH:=5c7494f968280832d6adb5aa19f745a56f1a79df311e59338c5efa6f7285e168 + +PKG_MAINTAINER:=sbwml +PKG_LICENSE:=GPL-2.0 +PKG_LICENSE_FILES:=COPYING + +PKG_INSTALL:=1 +PKG_BUILD_PARALLEL:=1 + +include $(INCLUDE_DIR)/package.mk + +define Package/bash-completion + SECTION:=utils + CATEGORY:=Utilities + SUBMENU:=Shells + TITLE:=Programmable completion functions for bash + URL:=https://github.com/scop/bash-completion + DEPENDS:=+bash +endef + +define Package/bash-completion/description + bash-completion is a collection of command line command completions for the Bash shell, + collection of helper functions to assist in creating new completions, + and set of facilities for loading completions automatically on demand, as well as installing them. +endef + +define Package/bash-completion/install + $(INSTALL_DIR) $(1)/etc/profile.d + $(CP) $(PKG_INSTALL_DIR)/etc/profile.d/* $(1)/etc/profile.d/ + $(INSTALL_DIR) $(1)/usr/share/bash-completion + $(CP) $(PKG_INSTALL_DIR)/usr/share/bash-completion/* $(1)/usr/share/bash-completion/ + $(INSTALL_DIR) $(1)/etc/bash_completion.d +endef + +$(eval $(call BuildPackage,bash-completion)) diff --git a/ddns-scripts-aliyun/Makefile b/ddns-scripts-aliyun/Makefile new file mode 100644 index 00000000..b3926b6b --- /dev/null +++ b/ddns-scripts-aliyun/Makefile @@ -0,0 +1,56 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=ddns-scripts-aliyun +PKG_VERSION:=1.0.3 +PKG_RELEASE:=6 + +PKG_LICENSE:=GPLv2 +PKG_MAINTAINER:=Sense + +PKG_BUILD_PARALLEL:=1 + +include $(INCLUDE_DIR)/package.mk + +define Package/$(PKG_NAME) + SECTION:=net + CATEGORY:=Network + SUBMENU:=IP Addresses and Names + TITLE:=DDNS extension for AliYun.com + PKGARCH:=all + DEPENDS:=+ddns-scripts +curl +jsonfilter +openssl-util +endef + +define Package/$(PKG_NAME)/description + Dynamic DNS Client scripts extension for AliYun.com +endef + +define Build/Configure +endef + +define Build/Compile + $(CP) ./*.sh $(PKG_BUILD_DIR) + # remove comments, white spaces and empty lines + for FILE in `find $(PKG_BUILD_DIR) -type f`; do \ + $(SED) 's/^[[:space:]]*//' \ + -e '/^#[[:space:]]\|^#$$$$/d' \ + -e 's/[[:space:]]#[[:space:]].*$$$$//' \ + -e 's/[[:space:]]*$$$$//' \ + -e '/^\/\/[[:space:]]/d' \ + -e '/^[[:space:]]*$$$$/d' $$$$FILE; \ + done +endef + +define Package/$(PKG_NAME)/install + $(INSTALL_DIR) $(1)/usr/lib/ddns + $(INSTALL_BIN) $(PKG_BUILD_DIR)/update_aliyun_com.sh $(1)/usr/lib/ddns + $(INSTALL_DIR) $(1)/usr/share/ddns/default + $(INSTALL_DATA) ./aliyun.com.json $(1)/usr/share/ddns/default +endef + +define Package/$(PKG_NAME)/prerm + #!/bin/sh + [ -z "$${IPKG_INSTROOT}" ] && /etc/init.d/ddns stop >/dev/null 2>&1 + exit 0 # suppress errors +endef + +$(eval $(call BuildPackage,$(PKG_NAME))) diff --git a/ddns-scripts-aliyun/README.md b/ddns-scripts-aliyun/README.md new file mode 100644 index 00000000..3bf9d587 --- /dev/null +++ b/ddns-scripts-aliyun/README.md @@ -0,0 +1,2 @@ +# ddns-scripts-aliyun +基于https://github.com/sensec/ddns-scripts_aliyun 改进,将wget替换为curl,并且完善了逻辑判断,日志可以显示更新失败时的原因 diff --git a/ddns-scripts-aliyun/aliyun.com.json b/ddns-scripts-aliyun/aliyun.com.json new file mode 100644 index 00000000..c1ad8a55 --- /dev/null +++ b/ddns-scripts-aliyun/aliyun.com.json @@ -0,0 +1,9 @@ +{ + "name": "aliyun.com", + "ipv4": { + "url": "update_aliyun_com.sh" + }, + "ipv6": { + "url": "update_aliyun_com.sh" + } +} diff --git a/ddns-scripts-aliyun/update_aliyun_com.sh b/ddns-scripts-aliyun/update_aliyun_com.sh new file mode 100644 index 00000000..6f613bc7 --- /dev/null +++ b/ddns-scripts-aliyun/update_aliyun_com.sh @@ -0,0 +1,205 @@ +#!/bin/sh +# +# 用于阿里云解析的DNS更新脚本 +# 阿里云解析API文档 https://help.aliyun.com/document_detail/29739.html +# +# 本脚本由 dynamic_dns_functions.sh 内的函数 send_update() 调用 +# +# 需要在 /etc/config/ddns 中设置的选项 +# option username - 阿里云API访问账号 Access Key ID。可通过 aliyun.com 帐号管理的 accesskeys 获取, 或者访问 https://ak-console.aliyun.com +# option password - 阿里云API访问密钥 Access Key Secret +# option domain - 完整的域名。建议主机与域名之间使用 @符号 分隔,否则将以第一个 .符号 之前的内容作为主机名 +# + +# 检查传入参数 +[ -z "$username" ] && write_log 14 "Configuration error! The 'username' that holds the Alibaba Cloud API access account cannot be empty" +[ -z "$password" ] && write_log 14 "Configuration error! The 'password' that holds the Alibaba Cloud API access account cannot be empty" + +# 检查外部调用工具 +[ -n "$CURL_SSL" ] || write_log 13 "Alibaba Cloud API communication require cURL with SSL support. Please install" +[ -n "$CURL_PROXY" ] || write_log 13 "cURL: libcurl compiled without Proxy support" +command -v sed >/dev/null 2>&1 || write_log 13 "Sed support is required to use Alibaba Cloud API, please install first" +command -v openssl >/dev/null 2>&1 || write_log 13 "Openssl-util support is required to use Alibaba Cloud API, please install first" + +# 变量声明 +local __HOST __DOMAIN __TYPE __CMDBASE __RECID __TTL + +# 从 $domain 分离主机和域名 +[ "${domain:0:2}" = "@." ] && domain="${domain/./}" # 主域名处理 +[ "$domain" = "${domain/@/}" ] && domain="${domain/./@}" # 未找到分隔符,兼容常用域名格式 +__HOST="${domain%%@*}" +__DOMAIN="${domain#*@}" +[ -z "$__HOST" -o "$__HOST" = "$__DOMAIN" ] && __HOST=@ + +# 设置记录类型 +[ $use_ipv6 = 0 ] && __TYPE=A || __TYPE=AAAA + +# 构造基本通信命令 +build_command(){ + __CMDBASE="$CURL -Ss" + # 绑定用于通信的主机/IP + if [ -n "$bind_network" ];then + local __DEVICE + network_get_physdev __DEVICE $bind_network || write_log 13 "Can not detect local device using 'network_get_physdev $bind_network' - Error: '$?'" + write_log 7 "Force communication via device '$__DEVICE'" + __CMDBASE="$__CMDBASE --interface $__DEVICE" + fi + # 强制设定IP版本 + if [ $force_ipversion = 1 ];then + [ $use_ipv6 = 0 ] && __CMDBASE="$__CMDBASE -4" || __CMDBASE="$__CMDBASE -6" + fi + # 设置CA证书参数 + if [ $use_https = 1 ];then + if [ "$cacert" = IGNORE ];then + __CMDBASE="$__CMDBASE --insecure" + elif [ -f "$cacert" ];then + __CMDBASE="$__CMDBASE --cacert $cacert" + elif [ -d "$cacert" ];then + __CMDBASE="$__CMDBASE --capath $cacert" + elif [ -n "$cacert" ];then + write_log 14 "No valid certificate(s) found at '$cacert' for HTTPS communication" + fi + fi + # 如果没有设置,禁用代理 (这可能是 .wgetrc 或环境设置错误) + [ -z "$proxy" ] && __CMDBASE="$__CMDBASE --noproxy '*'" +} + +# 百分号编码 +percentEncode(){ + if [ -z "${1//[A-Za-z0-9_.~-]/}" ];then + echo -n "$1" + else + local string=$1;local i=0;local ret chr + while [ $i -lt ${#string} ];do + chr=${string:$i:1} + [ -z "${chr#[^A-Za-z0-9_.~-]}" ] && chr=$(printf '%%%02X' "'$chr") + ret="$ret$chr" + i=$(( $i + 1 )) + done + echo -n "$ret" + fi +} + +# 用于阿里云API的通信函数 +aliyun_transfer(){ + __CNT=0;__URLARGS= + [ $# = 0 ] && write_log 12 "'aliyun_transfer()' Error - wrong number of parameters" + # 添加请求参数 + for string in $*;do + case "${string%%=*}" in + Format|Version|AccessKeyId|SignatureMethod|Timestamp|SignatureVersion|SignatureNonce|Signature);; # 过滤公共参数 + *)__URLARGS="$__URLARGS&"$(percentEncode "${string%%=*}")"="$(percentEncode "${string#*=}");; + esac + done + __URLARGS="${__URLARGS:1}" + # 附加公共参数 + string="Format=JSON";__URLARGS="$__URLARGS&"$(percentEncode "${string%%=*}")"="$(percentEncode "${string#*=}") + string="Version=2015-01-09";__URLARGS="$__URLARGS&"$(percentEncode "${string%%=*}")"="$(percentEncode "${string#*=}") + string="AccessKeyId=$username";__URLARGS="$__URLARGS&"$(percentEncode "${string%%=*}")"="$(percentEncode "${string#*=}") + string="SignatureMethod=HMAC-SHA1";__URLARGS="$__URLARGS&"$(percentEncode "${string%%=*}")"="$(percentEncode "${string#*=}") + string="Timestamp="$(date -u '+%Y-%m-%dT%H:%M:%SZ');__URLARGS="$__URLARGS&"$(percentEncode "${string%%=*}")"="$(percentEncode "${string#*=}") + string="SignatureVersion=1.0";__URLARGS="$__URLARGS&"$(percentEncode "${string%%=*}")"="$(percentEncode "${string#*=}") + string="SignatureNonce="$(cat '/proc/sys/kernel/random/uuid');__URLARGS="$__URLARGS&"$(percentEncode "${string%%=*}")"="$(percentEncode "${string#*=}") + string="Line=default";__URLARGS="$__URLARGS&"$(percentEncode "${string%%=*}")"="$(percentEncode "${string#*=}") + # 对请求参数进行排序,用于生成签名 + string=$(echo -n "$__URLARGS" | sed 's/\'"&"'/\n/g' | sort | sed ':label; N; s/\n/\'"&"'/g; b label') + # 构造用于计算签名的字符串 + string="GET&"$(percentEncode "/")"&"$(percentEncode "$string") + # 字符串计算签名值 + local signature=$(echo -n "$string" | openssl dgst -sha1 -hmac "$password&" -binary | openssl base64) + # 附加签名参数 + string="Signature=$signature";__URLARGS="$__URLARGS&"$(percentEncode "${string%%=*}")"="$(percentEncode "${string#*=}") + __A="$__CMDBASE 'https://alidns.aliyuncs.com/?$__URLARGS'" + write_log 7 "#> $__A" + while ! __TMP=`eval $__A 2>&1`;do + write_log 3 "[$__TMP]" + if [ $VERBOSE -gt 1 ];then + write_log 4 "Transfer failed - detailed mode: $VERBOSE - Do not try again after an error" + return 1 + fi + __CNT=$(( $__CNT + 1 )) + [ $retry_count -gt 0 -a $__CNT -gt $retry_count ] && write_log 14 "Transfer failed after $retry_count retries" + write_log 4 "Transfer failed - $__CNT Try again in $RETRY_SECONDS seconds" + sleep $RETRY_SECONDS & + PID_SLEEP=$! + wait $PID_SLEEP + PID_SLEEP=0 + done + __ERR=`jsonfilter -s "$__TMP" -e "@.Code"` + [ -z "$__ERR" ] && return 0 + case $__ERR in + LastOperationNotFinished)printf "%s\n" " $(date +%H%M%S) : 最后一次操作未完成,2秒后重试" >> $LOGFILE;return 1;; + InvalidTimeStamp.Expired)printf "%s\n" " $(date +%H%M%S) : 时间戳错误,2秒后重试" >> $LOGFILE;return 1;; + InvalidAccessKeyId.NotFound)__ERR="无效AccessKey ID";; + SignatureDoesNotMatch)__ERR="无效AccessKey Secret";; + InvalidDomainName.NoExist)__ERR="无效域名";; + esac + local A="$(date +%H%M%S) ERROR : [$__ERR] - 终止进程" + logger -p user.err -t ddns-scripts[$$] $SECTION_ID: ${A:15} + printf "%s\n" " $A" >> $LOGFILE + exit 1 +} + +# 添加解析记录 +add_domain(){ + while ! aliyun_transfer "Action=AddDomainRecord" "DomainName=$__DOMAIN" "RR=$__HOST" "Type=$__TYPE" "Value=$__IP";do + sleep 2 + done + printf "%s\n" " $(date +%H%M%S) : 添加解析记录成功: [$([ "$__HOST" = @ ] || echo $__HOST.)$__DOMAIN],[IP:$__IP]" >> $LOGFILE +} + +# 启用解析记录 +enable_domain(){ + while ! aliyun_transfer "Action=SetDomainRecordStatus" "RecordId=$__RECID" "Status=Enable";do + sleep 2 + done + printf "%s\n" " $(date +%H%M%S) : 启用解析记录成功" >> $LOGFILE +} + +# 修改解析记录 +update_domain(){ + while ! aliyun_transfer "Action=UpdateDomainRecord" "RecordId=$__RECID" "RR=$__HOST" "Type=$__TYPE" "Value=$__IP" "TTL=$__TTL";do + sleep 2 + done + printf "%s\n" " $(date +%H%M%S) : 修改解析记录成功: [$([ "$__HOST" = @ ] || echo $__HOST.)$__DOMAIN],[IP:$__IP],[TTL:$__TTL]" >> $LOGFILE +} + +# 获取子域名解析记录列表 +describe_domain(){ + ret=0 + while ! aliyun_transfer "Action=DescribeSubDomainRecords" "SubDomain=$__HOST.$__DOMAIN" "Type=$__TYPE";do + sleep 2 + done + __TMP=`jsonfilter -s "$__TMP" -e "@.DomainRecords.Record[@]"` + if [ -z "$__TMP" ];then + printf "%s\n" " $(date +%H%M%S) : 解析记录不存在: [$([ "$__HOST" = @ ] || echo $__HOST.)$__DOMAIN]" >> $LOGFILE + ret=1 + else + __STATUS=`jsonfilter -s "$__TMP" -e "@.Status"` + __RECIP=`jsonfilter -s "$__TMP" -e "@.Value"` + if [ "$__STATUS" != ENABLE ];then + printf "%s\n" " $(date +%H%M%S) : 解析记录被禁用" >> $LOGFILE + ret=$(( $ret | 2 )) + fi + if [ "$__RECIP" != "$__IP" ];then + __TTL=`jsonfilter -s "$__TMP" -e "@.TTL"` + printf "%s\n" " $(date +%H%M%S) : 解析记录需要更新: [解析记录IP:$__RECIP] [本地IP:$__IP]" >> $LOGFILE + ret=$(( $ret | 4 )) + fi + fi +} + +build_command +describe_domain +if [ $ret = 0 ];then + printf "%s\n" " $(date +%H%M%S) : 解析记录不需要更新: [解析记录IP:$__RECIP] [本地IP:$__IP]" >> $LOGFILE +elif [ $ret = 1 ];then + sleep 3 + add_domain +else + __RECID=`jsonfilter -s "$__TMP" -e "@.RecordId"` + [ $(( $ret & 2 )) -ne 0 ] && sleep 3 && enable_domain + [ $(( $ret & 4 )) -ne 0 ] && sleep 3 && update_domain +fi + +return 0 diff --git a/luci-app-netspeedtest/Makefile b/luci-app-netspeedtest/Makefile new file mode 100644 index 00000000..00366263 --- /dev/null +++ b/luci-app-netspeedtest/Makefile @@ -0,0 +1,16 @@ +include $(TOPDIR)/rules.mk + +LUCI_NAME:=luci-app-netspeedtest +PKG_VERSION:=1.0 + +LUCI_TITLE:=LuCI Net Speedtest +LUCI_DEPENDS:=+iperf3 +speedtest-cli +curl +jsonfilter +taskset +bash + +define Package/$(LUCI_NAME)/conffiles +/etc/config/netspeedtest +/etc/speedtest +endef + +include $(TOPDIR)/feeds/luci/luci.mk + +# call BuildPackage - OpenWrt buildroot signature diff --git a/luci-app-netspeedtest/htdocs/luci-static/resources/view/netspeedtest/iperf3.js b/luci-app-netspeedtest/htdocs/luci-static/resources/view/netspeedtest/iperf3.js new file mode 100644 index 00000000..92a3411a --- /dev/null +++ b/luci-app-netspeedtest/htdocs/luci-static/resources/view/netspeedtest/iperf3.js @@ -0,0 +1,105 @@ +'use strict'; +'require view'; +'require poll'; +'require fs'; +'require rpc'; +'require uci'; +'require ui'; +'require form'; + +var conf = 'netspeedtest'; +var instance = 'iperf3'; + +var callServiceList = rpc.declare({ + object: 'service', + method: 'list', + params: ['name'], + expect: { '': {} } +}); + +function getServiceStatus() { + return L.resolveDefault(callServiceList(conf), {}) + .then(function (res) { + var isrunning = false; + try { + isrunning = res[conf]['instances'][instance]['running']; + } catch (e) { } + return isrunning; + }); +} + +return view.extend({ +// handleSaveApply: null, +// handleSave: null, +// handleReset: null, + + load: function() { + return Promise.all([ + getServiceStatus(), + uci.load('netspeedtest') + ]); + }, + + poll_status: function(nodes, stat) { + var isRunning = stat[0], + view = nodes.querySelector('#service_status'); + + if (isRunning) { + view.innerHTML = "" + instance + " - " + _("RUNNING") + ""; + } else { + view.innerHTML = "" + instance + " - " + _("NOT RUNNING") + ""; + } + return; + }, + + render: function(res) { + var isRunning = res[0]; + + var m, s, o; + + m = new form.Map('netspeedtest', _('iPerf3 Bandwidth Performance Test'), + _('iPerf3 is a tool for active measurements of the maximum achievable bandwidth on IP networks.')); + + s = m.section(form.NamedSection, '_status'); + s.anonymous = true; + s.render = function (section_id) { + return E('div', { class: 'cbi-section' }, [ + E('div', { id: 'service_status' }, _('Collecting data ...')) + ]); + }; + + s = m.section(form.NamedSection, 'config', 'netspeedtest'); + s.anonymous = true; + + o = s.option(form.Flag, 'iperf3_enabled', _('Enable')); + o.default = o.disabled; + o.rmempty = false; + + s = m.section(form.TypedSection, '_cmd_ref'); + s.anonymous = true; + s.render = function (section_id) { + return E('div', { 'class': 'cbi-section' }, [ + E('h3', {}, _('iPerf3 Common commands reference')), + E('pre', {}, [ +" -c, --client \n\ + -u, --udp UDP mode\n\ + -b, --bandwidth [KMG] target bandwidth in bits/sec (0 for unlimited)\n\ + -t, --time time in seconds to transmit for (default 10 secs)\n\ + -i, --interval seconds between periodic bandwidth reports\n\ + -P, --parallel number of parallel client streams to run\n\ + -R, --reverse run in reverse mode (server sends, client receives)\n" + ]) + ]); + }; + + return m.render() + .then(L.bind(function(m, nodes) { + poll.add(L.bind(function() { + return Promise.all([ + getServiceStatus() + ]).then(L.bind(this.poll_status, this, nodes)); + }, this), 3); + return nodes; + }, this, m)); + } +}); diff --git a/luci-app-netspeedtest/htdocs/luci-static/resources/view/netspeedtest/speedtest.js b/luci-app-netspeedtest/htdocs/luci-static/resources/view/netspeedtest/speedtest.js new file mode 100644 index 00000000..e7504dd4 --- /dev/null +++ b/luci-app-netspeedtest/htdocs/luci-static/resources/view/netspeedtest/speedtest.js @@ -0,0 +1,128 @@ +'use strict'; +'require view'; +'require poll'; +'require dom'; +'require fs'; +'require rpc'; +'require uci'; +'require ui'; +'require form'; + +var TestTimeout = 240 * 1000; // 4 Minutes + +return view.extend({ +// handleSaveApply: null, +// handleSave: null, +// handleReset: null, + + load: function() { + return Promise.all([ + L.resolveDefault(fs.stat('/usr/bin/speedtest'), {}), + L.resolveDefault(fs.read('/etc/speedtest/speedtest_result'), null), + L.resolveDefault(fs.stat('/etc/speedtest/speedtest_result'), {}), + uci.load('netspeedtest') + ]); + }, + + poll_status: function(nodes, res) { + var has_ookla = res[0].path, + result_content = res[1] ? res[1].trim().split("\n") : []; + var ookla_stat = nodes.querySelector('#ookla_status'), + result_stat = nodes.querySelector('#speedtest_result'); + if (has_ookla) { + ookla_stat.style.color = 'green'; + dom.content(ookla_stat, [ _('Installed') ]); + } else { + ookla_stat.style.color = 'red'; + dom.content(ookla_stat, [ _('Not Installed') ]); + }; + if (result_content.length) { + if (result_content[0] == 'Testing') { + result_stat.innerHTML = "" + + '  ' + + "" + + ' ' + + _('Testing in progress...') + + ""; + }; + if (result_content[0].match(/https?:\S+/)) { + result_stat.innerHTML = "
"; + }; + if (result_content[0] == 'Test failed') { + result_stat.innerHTML = "" + _('Test failed.') + ""; + } + } else { + result_stat.innerHTML = "" + _('No result.') + ""; + }; + return; + }, + + render: function(res) { + var has_ookla = res[0].path, + result_content = res[1] ? res[1].trim().split("\n") : [], + result_mtime = res[2] ? res[2].mtime * 1000 : 0, + date = new Date(); + + var m, s, o; + + m = new form.Map('netspeedtest', _('Speedtest by Ookla')); + + s = m.section(form.TypedSection, '_result'); + s.anonymous = true; + s.render = function (section_id) { + if (result_content.length) { + if (result_content[0] == 'Testing') { + return E('div', { 'id': 'speedtest_result' }, [ E('span', { 'style': 'color:yellow;font-weight:bold' }, [ + E('img', { 'src': L.resource(['icons/loading.gif']), 'height': '20', 'style': 'vertical-align:middle' }, []), + _('Testing in progress...') + ]) ]) + }; + if (result_content[0].match(/https?:\S+/)) { + return E('div', { 'id': 'speedtest_result' }, [ E('div', { 'style': 'max-width:500px' }, [ + E('a', { 'href': result_content[0], 'target': '_blank' }, [ + E('img', { 'src': result_content[0] + '.png', 'style': 'max-width:100%;max-height:100%;vertical-align:middle' }, []) + ]) ]) ]) + }; + if (result_content[0] == 'Test failed') { + return E('div', { 'id': 'speedtest_result' }, [ E('span', { 'style': 'color:red;font-weight:bold' }, [ _('Test failed.') ]) ]) + } + } else { + return E('div', { 'id': 'speedtest_result' }, [ E('span', { 'style': 'color:red;font-weight:bold;display:none' }, [ _('No result.') ]) ]) + } + }; + + s = m.section(form.NamedSection, 'config', 'speedtest', _('Internet Speedtest')); + + o = s.option(form.Button, '_start', _('Start Test')); + o.inputtitle = _('Start Test'); + o.inputstyle = 'apply'; + if (result_content.length && result_content[0] == 'Testing' && (date.getTime() - result_mtime) < TestTimeout) + o.readonly = true; + o.onclick = function() { + return fs.exec_direct('/usr/lib/netspeedtest/speedtest') + .then(function(res) { return window.location = window.location.href.split('#')[0] }) + .catch(function(e) { ui.addNotification(null, E('p', e.message), 'error') }); + }; + + o = s.option(form.DummyValue, '_ookla_status', _('Speedtest® CLI')); + o.rawhtml = true; + o.cfgvalue = function(s) { + if (has_ookla) { + return E('span', { 'id': 'ookla_status', 'style': 'color:green;font-weight:bold' }, [ _('Installed') ]); + } else { + return E('span', { 'id': 'ookla_status', 'style': 'color:red;font-weight:bold' }, [ _('Not Installed') ]); + } + }; + + return m.render() + .then(L.bind(function(m, nodes) { + poll.add(L.bind(function() { + return Promise.all([ + L.resolveDefault(fs.stat('/usr/bin/speedtest'), {}), + L.resolveDefault(fs.read('/etc/speedtest/speedtest_result'), null) + ]).then(L.bind(this.poll_status, this, nodes)); + }, this), 5); + return nodes; + }, this, m)); + } +}); diff --git a/luci-app-netspeedtest/po/zh-cn b/luci-app-netspeedtest/po/zh-cn new file mode 120000 index 00000000..8d69574d --- /dev/null +++ b/luci-app-netspeedtest/po/zh-cn @@ -0,0 +1 @@ +zh_Hans \ No newline at end of file diff --git a/luci-app-netspeedtest/po/zh_Hans/netspeedtest.po b/luci-app-netspeedtest/po/zh_Hans/netspeedtest.po new file mode 100644 index 00000000..7584795c --- /dev/null +++ b/luci-app-netspeedtest/po/zh_Hans/netspeedtest.po @@ -0,0 +1,59 @@ +msgid "iPerf3 Server" +msgstr "iPerf3 服务器" + +msgid "iPerf3 Bandwidth Performance Test" +msgstr "iPerf3 带宽性能测试" + +msgid "iPerf3 Common commands reference" +msgstr "iPerf3 常用命令参考" + +msgid "iPerf3 is a tool for active measurements of the maximum achievable bandwidth on IP networks." +msgstr "iPerf3 是一种主动测量 IP 网络最大可达带宽的工具。" + +msgid "Collecting data ..." +msgstr "正在收集数据..." + +msgid "NOT RUNNING" +msgstr "未运行" + +msgid "RUNNING" +msgstr "运行中" + +msgid "Enable" +msgstr "启用" + +msgid "No result." +msgstr "无结果." + +msgid "Ookla SpeedTest" +msgstr "" + +msgid "Speedtest by Ookla" +msgstr "" + +msgid "Speedtest CLI" +msgstr "" + +msgid "Speedtest® CLI" +msgstr "" + +msgid "Installed" +msgstr "已安装" + +msgid "Not Installed" +msgstr "未安装" + +msgid "SpeedTest" +msgstr "网速测试" + +msgid "Internet Speedtest" +msgstr "互联网测速" + +msgid "Start Test" +msgstr "启动测试" + +msgid "Test failed." +msgstr "测试失败." + +msgid "Testing in progress..." +msgstr "测试进行中..." diff --git a/luci-app-netspeedtest/root/etc/init.d/netspeedtest b/luci-app-netspeedtest/root/etc/init.d/netspeedtest new file mode 100755 index 00000000..a3e96f8e --- /dev/null +++ b/luci-app-netspeedtest/root/etc/init.d/netspeedtest @@ -0,0 +1,31 @@ +#!/bin/sh /etc/rc.common + +START=99 +USE_PROCD=1 + +get_config() { + config_load netspeedtest + config_get "iperf3_enabled" "config" "iperf3_enabled" "0" +} + +start_service() { + get_config + [ "$iperf3_enabled" -ne "1" ] && return 1 + procd_open_instance "iperf3" + procd_set_param command /usr/bin/taskset -c 0-1 /usr/bin/iperf3 + procd_append_param command -s + procd_set_param respawn + procd_set_param stdout 0 + procd_set_param stderr 0 + procd_close_instance "iperf3" +} + +service_triggers() { + procd_add_reload_trigger "netspeedtest" +} + +reload_service() { + stop + sleep 1 + start +} diff --git a/luci-app-netspeedtest/root/etc/uci-defaults/99_netspeedtest b/luci-app-netspeedtest/root/etc/uci-defaults/99_netspeedtest new file mode 100755 index 00000000..680dc796 --- /dev/null +++ b/luci-app-netspeedtest/root/etc/uci-defaults/99_netspeedtest @@ -0,0 +1,6 @@ +#!/bin/sh +touch /etc/config/netspeedtest +uci -q batch <<-EOF >/dev/null + set netspeedtest.config=netspeedtest + commit netspeedtest +EOF diff --git a/luci-app-netspeedtest/root/usr/lib/netspeedtest/speedtest b/luci-app-netspeedtest/root/usr/lib/netspeedtest/speedtest new file mode 100755 index 00000000..da2f7346 --- /dev/null +++ b/luci-app-netspeedtest/root/usr/lib/netspeedtest/speedtest @@ -0,0 +1,40 @@ +#!/bin/bash + +mkdir -p /etc/speedtest + +export HOME='/etc/speedtest' +SPEEDTEST_CLI='/usr/bin/speedtest' +SPEEDTEST_RESULT='/etc/speedtest/speedtest_result' + +[ -n "$(pgrep -f "$SPEEDTEST_CLI")" ] && exit 1 + +LOCAL_IP=$(curl -s -4 --connect-timeout 3 http://ip.3322.net) + +BAIDU_SK="LHHGlmhcb4ENvIXpR9QQ2tBYa6ooUowX hYCENCEx1nXO0Nt46ldexfG9oI49xBGh 0kKZnWWhXEPfzIkklmzAa3dZ" +if [ -n "$LOCAL_IP" ]; then + for SK in $BAIDU_SK + do + INFO=$(curl -sk --connect-timeout 3 "https://api.map.baidu.com/location/ip?ip="$LOCAL_IP"&coor=bd09ll&ak=$SK") + if [ "$(echo $INFO | jsonfilter -e "@['status']")" = 0 ]; then + status=0 + break + fi + done + if [ "$status" = 0 ]; then + lon=$(echo $INFO | jsonfilter -e "@['content']['point']['x']") + lat=$(echo $INFO | jsonfilter -e "@['content']['point']['y']") + server_id=$(curl -sk --connect-timeout 3 "https://www.speedtest.net/api/ios-config.php?lon=$lon&lat=$lat" | grep "server url" | head -n1 | sed 's/.*id="//;s/".*//') + [ -n "$server_id" ] && ARG="-s $server_id" + fi +fi + +echo "Testing" > "$SPEEDTEST_RESULT" + +RUNTEST=$($SPEEDTEST_CLI --accept-gdpr --accept-license --progress=no $ARG 2>&1) +if [ $(echo $RUNTEST | grep -c "No servers defined") -ge 1 ] || [ $(echo $RUNTEST | grep -c "error") -ge 1 ]; then + RUNTEST=$($SPEEDTEST_CLI --accept-gdpr --accept-license --progress=no 2>&1) +fi + +RESULT=$(echo "$RUNTEST" | grep "Result URL" | awk '{print $NF}') + +[ -n "$RESULT" ] && echo "$RESULT" > "$SPEEDTEST_RESULT" || echo "Test failed" > "$SPEEDTEST_RESULT" diff --git a/luci-app-netspeedtest/root/usr/share/luci/menu.d/luci-app-netspeedtest.json b/luci-app-netspeedtest/root/usr/share/luci/menu.d/luci-app-netspeedtest.json new file mode 100644 index 00000000..c78be296 --- /dev/null +++ b/luci-app-netspeedtest/root/usr/share/luci/menu.d/luci-app-netspeedtest.json @@ -0,0 +1,29 @@ +{ + "admin/network/netspeedtest": { + "title": "SpeedTest", + "order": 55, + "action": { + "type": "firstchild" + }, + "depends": { + "acl": [ "luci-app-netspeedtest" ], + "uci": { "netspeedtest": true } + } + }, + "admin/network/netspeedtest/iperf3": { + "title": "iPerf3 Server", + "order": 1, + "action": { + "type": "view", + "path": "netspeedtest/iperf3" + } + }, + "admin/network/netspeedtest/speedtest": { + "title": "Internet Speedtest", + "order": 2, + "action": { + "type": "view", + "path": "netspeedtest/speedtest" + } + } +} diff --git a/luci-app-netspeedtest/root/usr/share/rpcd/acl.d/luci-app-netspeedtest.json b/luci-app-netspeedtest/root/usr/share/rpcd/acl.d/luci-app-netspeedtest.json new file mode 100644 index 00000000..b53a0433 --- /dev/null +++ b/luci-app-netspeedtest/root/usr/share/rpcd/acl.d/luci-app-netspeedtest.json @@ -0,0 +1,19 @@ +{ + "luci-app-netspeedtest": { + "description": "Grant access to netspeedtest procedures", + "read": { + "file": { + "/etc/init.d/netspeedtest": [ "exec" ], + "/usr/lib/netspeedtest/speedtest": [ "exec" ], + "/etc/speedtest/speedtest_result": [ "read" ] + }, + "ubus": { + "service": [ "list" ] + }, + "uci": [ "netspeedtest" ] + }, + "write": { + "uci": [ "netspeedtest" ] + } + } +} diff --git a/speedtest-cli/Makefile b/speedtest-cli/Makefile new file mode 100644 index 00000000..7e0faae7 --- /dev/null +++ b/speedtest-cli/Makefile @@ -0,0 +1,61 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=speedtest-cli +PKG_VERSION:=1.2.0 +PKG_RELEASE:=1 + +ifeq ($(ARCH),aarch64) + PKG_HASH:=3953d231da3783e2bf8904b6dd72767c5c6e533e163d3742fd0437affa431bd3 +else ifeq ($(ARCH),arm) + ARM_CPU_FEATURES:=$(word 2,$(subst +,$(space),$(call qstrip,$(CONFIG_CPU_TYPE)))) + ifeq ($(ARM_CPU_FEATURES),) + ARCH:=armel + PKG_HASH:=629a455a2879224bd0dbd4b36d8c721dda540717937e4660b4d2c966029466bf + else + ARCH:=armhf + PKG_HASH:=e45fcdebbd8a185553535533dd032d6b10bc8c64eee4139b1147b9c09835d08d + endif +else ifeq ($(ARCH),i386) + PKG_HASH:=9ff7e18dbae7ee0e03c66108445a2fb6ceea6c86f66482e1392f55881b772fe8 +else ifeq ($(ARCH),x86_64) + PKG_HASH:=5690596c54ff9bed63fa3732f818a05dbc2db19ad36ed68f21ca5f64d5cfeeb7 +endif + +PKG_SOURCE:=ookla-speedtest-$(PKG_VERSION)-linux-$(ARCH).tgz +PKG_SOURCE_URL:=https://install.speedtest.net/app/cli + +PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION) + +PKG_MAINTAINER:=sbwml + +include $(INCLUDE_DIR)/package.mk + +define Package/$(PKG_NAME) + SECTION:=net + CATEGORY:=Network + TITLE:=Speedtest CLI by Ookla + DEPENDS:=@(aarch64||arm||i386||x86_64) +ca-certificates + URL:=https://www.speedtest.net/ +endef + +define Package/$(PKG_NAME)/description + The Global Broadband Speed Test +endef + +define Build/Prepare + ( \ + pushd $(PKG_BUILD_DIR) ; \ + $(TAR) -zxf $(DL_DIR)/ookla-speedtest-$(PKG_VERSION)-linux-$(ARCH).tgz -C . ; \ + popd ; \ + ) +endef + +define Build/Compile +endef + +define Package/$(PKG_NAME)/install + $(INSTALL_DIR) $(1)/usr/bin + $(INSTALL_BIN) $(PKG_BUILD_DIR)/speedtest $(1)/usr/bin +endef + +$(eval $(call BuildPackage,$(PKG_NAME)))