🌴 Sync 2026-03-05 23:51:42

This commit is contained in:
github-actions[bot] committed 2026-03-05 23:51:42 +08:00
1 parent 97c049ed0a
commit 0acff03804
14253 files changed
+3893239

No files matched your search

@@ -0,0 +1,11 @@
config dnscrypt-proxy 'ns1'
option enable '0'
list resolvers 'public-resolvers'
option netprobe_address '114.114.114.114:53'
option bootstrap_resolvers '114.114.114.114:53'
option listen_addresses '127.0.0.1:5335'
option force 'cache dnscrypt_servers lb_estimator ignore_system_dns cert_ignore_timestamp block_unqualified block_undelegated ipv4_servers require_nofilter require_nolog doh_servers block_ipv6'
list forwarding_rules '/etc/dnsmasq.oversea/oversea_list.conf'
list blocked_names '/usr/share/adbyby/dnsmasq.adblock'
@@ -0,0 +1,464 @@
#!/bin/sh /etc/rc.common
#
# Copyright (C) 2019 p.t. <peter-tank@github.com>
#
# This is free software, licensed under the GNU General Public License v3.
# See /LICENSE for more information.
#
START=30
USE_PROCD=1
INIT_TRACE=
EXTRA_COMMANDS=
PROG=/usr/sbin/dnscrypt-proxy
CONFIG_DIR=/var/etc
USER=nobody
NAME=dnscrypt-proxy
SERVICE_DAEMONIZE=
SERVICE_USE_PID=1
SERVICE_MATCH_EXEC=1
SERVICE_STOP_TIME=15
SERVICE_PID_FILE=/var/run/$NAME.pid
LUCI_STATUS=/var/run/luci-reload-status
LOG_FILE="/var/etc/dnscrypt"
CACHE_DIR="/usr/share/dnscrypt-proxy"
DIRECT_IPSETS="vpsiplist localnetwork ss_spec_wan_ac"
# has side effects when there is a option names the same 'addrs' in shadowsocksr config file
# keep this uci function excuted at the bottom of scripts
uci_filter_resolver_addrs() {
local __PKGNAME="${NAME}"
local __SECTIONTYPE="server_addr"
local ret
filter_resolver_addrs()
{
local __TYPE="$1"
local __OPTION="$2"
set | sed -ne "s/^CONFIG_.*_addrs='\([^']*\)'$/\1/p" | sed -e "s/[, ]/\n/g" | sort | uniq
}
# config_load "${__PKGNAME}"
# tricky way first for all server_addr addrs in DNSCrypt ACL
ret=$(filter_resolver_addrs "${__SECTIONTYPE}" "addrs")
# or get all addrs from all resolvers configured.
[ -z "${ret}" ] && {
CONFIG_APPEND=1
config_list_foreach "ns1" "resolvers" config_load
ret=$(filter_resolver_addrs ".*" "addrs")
}
echo "${ret}"
}
uci_get_by_name() {
local ret=$(uci get dnscrypt-proxy.$1.$2 2>/dev/null)
echo "${ret:=$3}"
}
boot() {
dnscrypt_boot=1
rc_procd start_service
}
dnscrypt_instance() {
local config_path="$CONFIG_DIR/dnscrypt-proxy-$1.conf"
[ $(uci_get_by_name "$1" enable 0) -eq 1 ] || return 0
create_config_file $1 "$config_path"
procd_open_instance
procd_set_param command $PROG -config "$config_path" -pidfile "$SERVICE_PID_FILE"
procd_set_param file "$config_path"
procd_close_instance
}
include_for_firewall_reload() {
local FWI=$(uci get firewall.dnscrypt_proxy.path 2>/dev/null)
[ -n "$FWI" ] || return 0
echo '#!/bin/sh' >$FWI
cat <<-EOF >>"$FWI"
addr_filter=\$(uci get ${NAME}.ns1.addr_filter 2>/dev/null)
if [ "\${addr_filter:-auto}" == "auto" ]; then
for list in ${DIRECT_IPSETS}; do
ipset list -n "\${list}" >/dev/null 2>&1
[ \$? -eq 0 ] && addr_filter="\${list}" && break
done
fi
ipset list -n "\${addr_filter}" >/dev/null 2>&1 && {
ipset_shell=\$(find ${CONFIG_DIR} -name "dnscrypt_filter2_*.sh" | head -n1)
[ -z "\${ipset_shell}" ] || {
sed -i 's/^ipset add [^ ]* \(.*\)$/ipset add '"\${addr_filter}"' \1/g' "\${ipset_shell}"
echo "### \$(date) on firewall reload" >> "${LOG_FILE}.log"
echo "### appending resolver addresses to ipset list \${addr_filter}..." >> "${LOG_FILE}.log"
wc -l "\${ipset_shell}" >> "${LOG_FILE}.log" 2>&1
date >"\${ipset_shell}.log"
\${ipset_shell} >>"\${ipset_shell}.log" 2>&1 &
}
}
EOF
}
create_config_file() {
local server_names listen_address query_meta max_clients bootstrap_resolvers netprobe_address
local disabled_server_names log_file log_level timeout keepalive cert_refresh_delay netprobe_timeout
local log_files_max_size log_files_max_age log_files_max_backups reject_ttl
local reject_ttl cache_size cache_min_ttl cache_max_ttl cache_neg_min_ttl cache_neg_max_ttl
local lb_strategy blocked_query_response force_defaults force_gets
local proxy http_proxy cloaking_rules cloak_ttl
local config_path="$2"
local dstf two_line_header forward_resolvers
[ ! -d "$CONFIG_DIR" ] && mkdir -p "$CONFIG_DIR"
[ -d "${CACHE_DIR}" ] || mkdir -p "${CACHE_DIR}"
[ -f "$config_path" ] && rm "$config_path"
config_get server_names $1 'server_names' ''
config_get listen_addresses $1 'listen_addresses' '127.0.0.1:5335'
config_get query_meta $1 'query_meta' ''
config_get max_clients $1 'max_clients' '250'
config_get bootstrap_resolvers $1 'bootstrap_resolvers' '114.114.114.114:53'
config_get netprobe_address $1 'netprobe_address' '114.114.114.114:53'
config_get disabled_server_names $1 'disabled_server_names' ''
config_get log_file $1 'log_file' "${LOG_FILE}.log"
config_get log_level $1 'log_level' '2'
config_get timeout $1 'timeout' '5000'
config_get keepalive $1 'keepalive' '30'
config_get cert_refresh_delay $1 'cert_refresh_delay' '240'
config_get netprobe_timeout $1 'netprobe_timeout' '60'
config_get log_files_max_size $1 'log_files_max_size' '1'
config_get log_files_max_age $1 'log_files_max_age' '7'
config_get log_files_max_backups $1 'log_files_max_backups' '1'
config_get reject_ttl $1 'reject_ttl' '600'
config_get cache_size $1 'cache_size' '512'
config_get cache_min_ttl $1 'cache_min_ttl' '600'
config_get cache_max_ttl $1 'cache_max_ttl' '86400'
config_get cache_neg_min_ttl $1 'cache_neg_min_ttl' '60'
config_get cache_neg_max_ttl $1 'cache_neg_max_ttl' '600'
config_get lb_strategy $1 'lb_strategy' 'p2'
config_get blocked_query_response $1 'blocked_query_response' 'hinfo'
config_get proxy $1 'proxy' ''
config_get http_proxy $1 'http_proxy' ''
config_get cloak_ttl $1 'cloak_ttl' '600'
append_str_param "user_name" "$USER" $config_path
append_multivalue_param "server_names" "$server_names" $config_path
append_multivalue_param "listen_addresses" "$listen_addresses" $config_path
append_multivalue_param "query_meta" "$query_meta" $config_path
append_param "max_clients" "$max_clients" $config_path
append_multivalue_param "bootstrap_resolvers" "$bootstrap_resolvers" $config_path
append_str_param "netprobe_address" "$netprobe_address" $config_path
append_multivalue_param "disabled_server_names" "$disabled_server_names" $config_path
append_param "log_level" "$log_level" $config_path
append_str_param "log_file" "$log_file" $config_path
append_param "timeout" "$timeout" $config_path
append_param "cert_refresh_delay" "$cert_refresh_delay" $config_path
append_param "netprobe_timeout" "$netprobe_timeout" $config_path
append_param "log_files_max_size" "$log_files_max_size" $config_path
append_param "log_files_max_age" "$log_files_max_age" $config_path
append_param "log_files_max_backups" "$log_files_max_backups" $config_path
append_param "reject_ttl" "$reject_ttl" $config_path
append_param "cache_size" "$cache_size" $config_path
append_param "cache_min_ttl" "$cache_min_ttl" $config_path
append_param "cache_max_ttl" "$cache_max_ttl" $config_path
append_param "cache_neg_min_ttl" "$cache_neg_min_ttl" $config_path
append_param "cache_neg_max_ttl" "$cache_neg_max_ttl" $config_path
append_str_param "lb_strategy" "$lb_strategy" $config_path
append_str_param "blocked_query_response" "$blocked_query_response" $config_path
append_str_param "proxy" "$proxy" $config_path
append_str_param "http_proxy" "$http_proxy" $config_path
append_param "cloak_ttl" "$cloak_ttl" $config_path
force_defaults='
lb_estimator,true
ignore_system_dns,true
block_unqualified,true
block_undelegated,true
ipv4_servers,true
ipv6_servers,false
block_ipv6,true
dnscrypt_servers,true
doh_servers,true
require_dnssec,false
force_tcp,false
require_nolog,true
require_nofilter,true
cache,true
offline_mode,false
dnscrypt_ephemeral_keys,false
tls_disable_session_tickets,false
cert_ignore_timestamp,true
'
append_yes_no() {
local sets=$1
local defs=$2
local config_path=$3
local line param_name param_value val
for line in $defs; do
param_name="${line%%,*}"; param_value="${line##*,}";
for val in $sets; do
[[ x$val == x$param_name ]] && param_value=true
done
echo "$param_name = $param_value" >> $config_path
done
}
config_get force_gets $1 'force' ''
append_yes_no "$force_gets" "$force_defaults" "$config_path"
handle_list_file() {
local file
local fwd="$2"
if [ x"${1:0:4}" == x"http" ]; then file=$(cache_file "${1}" 0);
elif [ x"${1:0:1}" == x"/" ]; then file="${1}";
else file="$CACHE_DIR/${1}";
fi
[ x"$file" == x -o ! -f "${file}" ] && return
local tmpf="/tmp/dnscrypt_${file##*/}"
if [ x"${file##*.}" == xconf -o x"${file##*.}" == xadblock ]; then
sed -ne "s/^\(address\|server\)=\/\([^/]*\)\/.*$/\2 $fwd/p" -e "s/ *$//g" "$file" > "$tmpf"
else
[ -z "$fwd" ] || sed -i -e "s/$/ $fwd/g" "$tmpf"
cat "$file" > "$tmpf"
fi
echo "$tmpf"
}
append_list_file() {
local sec=$1
local list=$2
local param_name=$3
local txtf=$4
local log=$5
local config_path=$6
local line2=$7
local fwd=$8
echo -e $line2 > "$txtf"
[ -z "$log" ] || {
append_str_param "log_file" "${LOG_FILE}_$log" $config_path
append_str_param "log_format" "tsv" $config_path
}
merg_list_file() {
local src=${1%% *}
local list_file
list_file=$(handle_list_file $src "$fwd")
[[ x"$list_file" == x ]] && return 0
cat "$list_file" >> "$txtf"
}
config_list_foreach "$sec" "$list" merg_list_file $config_path
line=$(sort "$txtf" | uniq); echo "$line" > "$txtf"
line=$(cat "$txtf" | wc -l); [ $line -le 2 ] && rm -f "$txtf" && return
echo "#total $((line-1)) uniq items." >> "$txtf"
append_str_param "$param_name" "$txtf" $config_path
}
dstf="$CONFIG_DIR/dnscrypt_forwarding_rules.txt"
two_line_header='#dnscrypt forwarding rule file.\n'
forward_resolvers="$bootstrap_resolvers"
append_list_file $1 "forwarding_rules" "forwarding_rules" "$dstf" "" $config_path "$two_line_header" $forward_resolvers
dstf="$CONFIG_DIR/dnscrypt_cloaking_rules.txt"
two_line_header='#dnscrypt cloaking rules file.\n'
append_list_file $1 "cloaking_rules" "cloaking_rules" "$dstf" "" $config_path "$two_line_header"
echo "[query_log]" >> $config_path
append_str_param "file" "${LOG_FILE}_query.log" $config_path
append_str_param "format" "tsv" $config_path
echo "[nx_log]" >> $config_path
append_str_param "file" "${LOG_FILE}_nx.log" $config_path
append_str_param "format" "tsv" $config_path
echo "[blocked_names]" >> $config_path
dstf="$CONFIG_DIR/dnscrypt_blocked_names.txt"
two_line_header='#dnscrypt domain black list file.\n*.test\n*.onion\n*.localhost\n*.local\n*.invalid\n*.bind\n*.lan\n*.internal\n*.intranet\n*.private\n*.workgroup\n*.10.in-addr.arpa\n*.16.172.in-addr.arpa\n*.168.192.in-addr.arpa\n*.254.169.in-addr.arpa\n*.d.f.ip6.arpa'
append_list_file $1 "blocked_names" "blocked_names_file" "$dstf" "bd.log" $config_path "$two_line_header"
echo "[blocked_ips]" >> $config_path
dstf="$CONFIG_DIR/dnscrypt_blocked_ips.txt"
two_line_header='#dnscrypt ip black list file.\n127.*\n192.168.*'
append_list_file $1 "blocked_ips" "blocked_ips_file" "$dstf" "bi.log" $config_path "$two_line_header"
echo "[allowed_names]" >> $config_path
dstf="$CONFIG_DIR/dnscrypt_allowed_names.txt"
two_line_header='#dnscrypt white list file.\n'
append_list_file $1 "allowed_names" "allowed_names_file" "$dstf" "wd.log" $config_path "$two_line_header"
echo "[static]" >> $config_path
append_static_resolver() {
local sdns=$1
local config_path=$2
local sdns
[[ "x$sdns" == x ]] && return 0
echo "[static.'$sdns']" >> $config_path
append_str_param "sdns" "$sdns" $config_path
}
config_list_foreach "$1" "sdns" append_static_resolver $config_path
echo "[sources]" >> $config_path
append_resolvers_source() {
local resolver=$1
local config_path=$2
local section="global"
local urls cache_file minisign_key prefix selfsign details_json
config_unset $section "urls"
[ -f "/etc/config/${resolver}" ] || return 0
config_load ${resolver}
[ $? -eq 0 ] || return 0
config_get urls $section 'urls' ''
[[ "x$urls" == x ]] && return 0
echo "[sources.'${resolver}']" >> $config_path
config_get urls $section 'urls' ''
config_get format $section 'format' 'v2'
config_get cache_file $section 'cache_file' "${urls##*/}"
config_get minisign_key $section 'minisign_key' 'RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3'
config_get prefix $section 'prefix' ''
config_get details_json $section 'details_json' "${cache_file%%.md}.json"
config_get_bool cache_mode $section 'cache_mode' '1'
config_get_bool selfsign $section 'selfsign' '0'
if [ $cache_mode -eq 1 ] ; then
local redirect_https site_addr schema port
redirect_https="$(uci_get uhttpd main redirect_https '0')"
site_addr="$(uci_get uhttpd main listen_https '[::]:80')"
schema=http
port=${site_addr##*:}
[ x"${port}" != "x80" -a $redirect_https -eq 1 ] && opkg list-installed | grep libustream-openssl && schema=https
[ x"${port}" == "x80" -o x"${port}" == "x443" ] && port=
urls="${schema}://localhost${port:+:${port}}/${urls##*/}"
fi
append_multivalue_param "urls" "$urls" $config_path
append_str_param "format" "$format" $config_path
append_str_param "cache_file" "$cache_file" $config_path
append_str_param "minisign_key" "$minisign_key" $config_path
append_str_param "prefix" "$prefix" $config_path
config_get_bool selfsign $section 'selfsign' '0'
config_get details_json $section 'details_json' ''
}
config_list_foreach "$1" "resolvers" append_resolvers_source $config_path
}
multivalue_parse() {
local param_values=$(echo $1 | sed -e 's/[, ]/\n/g' -e 's/\n\n/\n/g')
[[ x == "x${param_values}" ]] && echo -n "[]" && return 1
local p
p=$(for p in ${param_values}; do echo -n "'${p}'"; done)
echo -n "["
echo -n $p | sed -e "s/''/', '/g"
echo -n "]"
}
log_ignored_param() {
local param_name=$1
logger -t dnscrypt-proxy -p user.warn "dnscrypt-proxy plugins support not present, ignoring '$param_name' parameter."
}
append_multivalue_param() {
local param_name=$1
local param_value=$2
local config_path=$3
echo "$param_name = $(multivalue_parse $param_value)" >> $config_path
}
append_str_param() {
local param_name=$1
local param_value=$2
local config_path=$3
echo "$param_name = '$param_value'" >> $config_path
}
append_param() {
local param_name=$1
local param_value=$2
local config_path=$3
echo "$param_name = $param_value" >> $config_path
}
start_service() {
config_load dnscrypt-proxy
local addr_filter list
config_get addr_filter ns1 'addr_filter' 'auto'
if [ "${addr_filter}" == "auto" ]; then
for list in ${DIRECT_IPSETS}; do
ipset list -n "${list}" >/dev/null 2>&1
[ $? -eq 0 ] && addr_filter="${list}" && break
done
fi
ipset list -n "${addr_filter}" >/dev/null 2>&1 && {
local ipset_shell="${CONFIG_DIR}/dnscrypt_filter2_${addr_filter}.sh"
uci_filter_resolver_addrs > "${ipset_shell}"
sed -i 's/^\(.*\)$/ipset add '"${addr_filter}"' "\1" nomatch/g' "${ipset_shell}"
chmod +x "${ipset_shell}"
echo "### appending resolver addresses to ipset list ${addr_filter}..." >> "${LOG_FILE}.log"
wc -l "${ipset_shell}" >> "${LOG_FILE}.log" 2>&1
date >"${ipset_shell}.log"
${ipset_shell} >>"${ipset_shell}.log" 2>&1 &
}
include_for_firewall_reload
config_foreach dnscrypt_instance dnscrypt-proxy
}
service_triggers() {
procd_add_reload_trigger 'dnscrypt-proxy'
}
reload_service() {
stop
start
}
cache_file() {
local tdl=$1
local key=$2
local root=$3
local mode=${4:-0}
[ -n "$root" ] && tdl=$tdl$'\n'$tdl.minisig
for cdl in $tdl; do
echo "INFO: details[$cdl]" > $LUCI_STATUS && sleep 3
local md5=$(echo $cdl | md5sum | cut -d' ' -f1)
local fn="$CACHE_DIR/${md5}_${cdl##*/}"
local tmpf="/tmp/dnscrypt_${cdl##*/}.dl"
local line
[ ! -f "$fn" ] && {
wget -q --no-check-certificate -t 3 -T 30 -O "$tmpf" "$cdl"
[ $? -eq 0 ] || return
line=$(cat "$tmpf" | wc -l)
[ $line -gt 1 ] || return
cat /dev/null > "$fn"
[ -z "$key" -o $mode -eq 11 ] && echo "##source[#$line]: $cdl" >> "$fn"
cat "$tmpf" >> "$fn"
}
[ $mode -ge 10 ] && cat "$fn" > $root/${cdl##*/}
done
echo "${fn%%.minisig}"
}
@@ -0,0 +1,165 @@
#!/bin/sh
#
# Copyright (C) 2019 p.t. <peter-tank@github.com>
#
# This is free software, licensed under the GNU General Public License v3.
# See /LICENSE for more information.
#
EXTRA_COMMANDS="update cache_file"
PROG=/usr/sbin/dnscrypt-proxy
CONFIG_DIR=/var/etc
LUCI_STATUS=/var/run/luci-reload-status
LOG_FILE="/var/etc/dnscrypt"
CACHE_DIR="/usr/share/dnscrypt-proxy"
[ -d "${CACHE_DIR}" ] || mkdir -p "${CACHE_DIR}"
#set -x
. $IPKG_INSTROOT/lib/functions.sh
multivalue_parse() {
local param_values=$(echo $1 | sed -e 's/[, ]/\n/g' -e 's/\n\n/\n/g')
[[ x == x${param_values} ]] && echo -n "[]" && return 1
local p
p=$(for p in ${param_values}; do echo -n "'${p}'"; done)
echo -n "["
echo -n $p | sed -e "s/''/', '/g"
echo -n "]"
}
cache_file() {
local tdl=$1
local key=$2
local root=$3
local mode=${4:-0}
[ -n "$root" ] && tdl=$tdl$'\n'$tdl.minisig
for cdl in $tdl; do
echo "INFO: details[$cdl]" > $LUCI_STATUS && sleep 3
local md5=$(echo $cdl | md5sum | cut -d' ' -f1)
local fn="$CACHE_DIR/${md5}_${cdl##*/}"
local tmpf="/tmp/dnscrypt_${cdl##*/}.dl"
local line
[ ! -f "$fn" ] && {
wget -q --no-check-certificate -t 3 -O "$tmpf" "$cdl"
[ $? -eq 0 ] || return
line=$(cat "$tmpf" | wc -l)
[ $line -gt 1 ] || return
cat /dev/null > "$fn"
[ -z "$key" -o $mode -eq 11 ] && echo "##source[#$line]: $cdl" >> "$fn"
cat "$tmpf" >> "$fn"
}
[ $mode -ge 10 ] && cat "$fn" > $root/${cdl##*/}
done
echo "${fn%%.minisig}"
}
#update <resolvers_filename_in_configure>
update() {
local cfg="${1}"
local siteroot="$(uci_get uhttpd main home ""/www"")"
[ -z "${cfg}" -o ! -f "/etc/config/${cfg}" ] && exit 0
NO_CALLBACK=1 config_load "${cfg}"
local section="global" urls minisign_key cache_file details_json cache_mode selfsign
config_get urls $section 'urls' ''
[ $? -ne 0 ] && echo "ERROR: Wrong configure..." > $LUCI_STATUS && sleep 3 && exit 1
[ -z "${urls}" ] && echo "SKIPED: None resolvers URLs..." > $LUCI_STATUS && sleep 3 && exit 2
. "$IPKG_INSTROOT/usr/share/libubox/jshn.sh"
config_get minisign_key $section 'minisign_key' ''
config_get cache_file $section 'cache_file' "${urls##*/}"
config_get details_json $section 'details_json' "${cache_file%%.md}.json"
config_get_bool cache_mode $section 'cache_mode' '1'
config_get_bool selfsign $section 'selfsign' '0'
json_set_namespace "urls" "pre"
json_init
json_load "{'urls':$(multivalue_parse $urls)}"
json_for_each_item cache_file 'urls' "$siteroot" "$cache_mode$selfsign"
json_cleanup
json_set_namespace "pre"
local url="$details_json" stype="dnscrypt" file
[ -z "${url}" ] && return 2
if [ x"${url:0:4}" == x"http" ]; then file=$(cache_file "${url}" $cache_mode$selfsign);
elif [ x"${url:0:1}" == x"/" ]; then file="${url}";
else file="$CACHE_DIR/${url}";
fi
[ x"$file" == x -o ! -f "${file}" ] && { echo "ERROR: JSON file not exist!" > $LUCI_STATUS; sleep 3; return 3; }
json_set_namespace "resolvers" "pre"
json_init
echo "INFO: JSON parsing [$file]..." > $LUCI_STATUS
echo "{'resolvers':$(cat $file)}" > "/tmp/${file##*/}.ld"
json_load_file "/tmp/${file##*/}.ld"
local type resolvers resolver keys key val
local sname name addrs ports proto stamp country description dnssec ipv6 location nofilter nolog
json_get_type type "resolvers"
[ x"$type" == "xarray" ] || { echo "ERROR: JSON parse error!" > $LUCI_STATUS; sleep 3; return 4; }
json_get_multivalue() {
local _v_dst="$1"
local _v_keys _v_val _select=
local _json_no_warning=1 _sep=","
unset "$_v_dst"
[ -n "$2" ] && {
json_select "$2" || return 1
_select=1
}
json_get_keys _v_keys
set -- $_v_keys
while [ "$#" -gt 0 ]; do
json_get_var _v_val "$1"
__jshn_raw_append "$_v_dst" "$_v_val" "$_sep"
shift
done
[ -n "$_select" ] && json_select ..
return 0
}
json_select "resolvers"
json_get_keys resolvers
local icount=${resolvers##* }
for resolver in $resolvers; do
json_select "$resolver"
json_get_vars name proto stamp country description dnssec ipv6 nofilter nolog
json_get_multivalue addrs "addrs"
json_get_multivalue ports "ports"
json_get_multivalue location "location"
sname=$(echo "$name" | sed 's/[^a-zA-Z0-9_]/_/g')
uci batch <<-EOB
set "$cfg.$sname"="$stype"
set "$cfg.$sname.name"="$name"
set "$cfg.$sname.proto"="$proto"
set "$cfg.$sname.addrs"="$addrs"
set "$cfg.$sname.ports"="$ports"
set "$cfg.$sname.stamp"="$stamp"
set "$cfg.$sname.location"="$location"
set "$cfg.$sname.country"="${country:-Unkown}"
set "$cfg.$sname.description"="$description"
set "$cfg.$sname.dnssec"="$dnssec"
set "$cfg.$sname.ipv6"="$ipv6"
set "$cfg.$sname.nofilter"="$nofilter"
set "$cfg.$sname.nolog"="$nolog"
EOB
[ "${resolver:${#resolver}-1:1}" -eq 9 ] && uci_commit "$cfg" && echo "INFO: resolver[$resolver/${icount}]: $name {${proto}://$addrs|$ports}" > $LUCI_STATUS
json_select ..
done
json_select ..
uci_commit "$cfg" && echo "DONE: resolver[$resolver/${icount}]: $name {${proto}://$addrs|$ports}" > $LUCI_STATUS && sleep 3
json_cleanup
json_set_namespace "pre"
return 0
}
[ x"$1" == x"enabled" ] && return 0
echo "INFO: update resolvers[$1]" > $LUCI_STATUS && sleep 3
update $1
@@ -0,0 +1,11 @@
{
"luci-app-dnscrypt-proxy2": {
"description": "Grant UCI access for luci-app-dnscrypt-proxy2",
"read": {
"uci": [ "*" ]
},
"write": {
"uci": [ "*" ]
}
}
}
@@ -0,0 +1,211 @@
-- Copyright (C) 2019 github.com/peter-tank
-- Licensed to the public under the GNU General Public License v3.
module("luci.controller.dnscrypt-proxy", package.seeall)
function index()
if not nixio.fs.access("/etc/config/dnscrypt-proxy") then
return
end
entry({"admin", "services", "dnscrypt-proxy"},alias("admin", "services", "dnscrypt-proxy", "global"),_("DNSCrypt Proxy"), 10).acl_depends = { "luci-app-dnscrypt-proxy2" }
entry({"admin", "services", "dnscrypt-proxy", "global"},cbi("dnscrypt-proxy/global"),_("Overview"), 10).leaf = true
entry({"admin", "services", "dnscrypt-proxy", "dnscrypt-resolvers"},cbi("dnscrypt-proxy/dnscrypt-resolvers"),_("Resolvers"), 20).leaf = true
entry({"admin", "services", "dnscrypt-proxy", "dnscrypt-resolvers"},arcombine(cbi("dnscrypt-proxy/dnscrypt-resolvers"), cbi("dnscrypt-proxy/dnscrypt-resolvers-config")),_("Resolvers"), 25).dependent = true
entry({"admin", "services", "dnscrypt-proxy", "dnscrypt-proxy-acl"},cbi("dnscrypt-proxy/dnscrypt-proxy-acl"),_("Resolver ACL"), 30).leaf = true
entry({"admin", "services", "dnscrypt-proxy", "dnscrypt-proxy"},cbi("dnscrypt-proxy/dnscrypt-proxy"),_("Proxy Setting"), 40).dependent = true
entry({"admin", "services", "dnscrypt-proxy", "refresh_c"}, call("refresh_cmd"))
entry({"admin", "services", "dnscrypt-proxy", "resolve_c"}, call("resolve_cmd"))
entry({"admin", "services", "dnscrypt-proxy", "update_c"}, call("update_cmd"))
entry({"admin", "services", "dnscrypt-proxy", "fileread"}, call("act_read"), nil).leaf=true
entry({"admin", "services", "dnscrypt-proxy", "logview"}, cbi("dnscrypt-proxy/logview"), _("Log") ,80).leaf=true
end
function update_cmd()
local dc = require "luci.tools.dnscrypt".init()
local set = luci.http.formvalue("set")
local surl = luci.http.formvalue("url")
local ecount, retstring = 0, "-1"
if set == "dnscrypt_bin" then
local bin_file = "/usr/sbin/dnscrypt-proxy"
local ret = action_update(surl)
retstring = type(ret) == "table" and table.concat(ret,", ") or ret
ecount = nixio.fs.chmod(bin_file, 755)
else
ecount = -1
retstring = "Unkown CMD: " .. set
end
luci.http.prepare_content("application/json")
luci.http.write_json({ err=ecount, status=retstring})
end
function refresh_cmd()
local set = luci.http.formvalue("set")
local icount, retstring = 0, "-1"
if set == "remove_caches" then
local dir = "/usr/share/dnscrypt-proxy"
local output = { }
exec("/bin/busybox", { "find", dir, "-type", "f" },
function(chunk) output[#output+1] = chunk:match("%S+") end)
exec("/bin/busybox", { "find", dir, "-type", "f", "-exec", "rm", "{}", ";" })
icount = #output
retstring = "[%d]<br />%s" % { icount, table.concat(output, "<br />") }
elseif set == "dump_resolver" then
local output = "/usr/share/dnscrypt-proxy/running.json"
luci.sys.exec("/usr/sbin/dnscrypt-proxy -json --list-all -config /var/etc/dnscrypt-proxy-ns1.conf > %s" % output)
local lua_data = luci.jsonc.parse(nixio.fs.readfile(output))
icount = #lua_data
retstring = "[%d] - %s" % { icount, output }
else
icount = -1
retstring = "Unkown CMD: " .. set
end
luci.http.prepare_content("application/json")
luci.http.write_json({ ret=retstring ,retcount=icount})
end
function resolve_cmd()
local set = luci.http.formvalue("set")
local retstring="<br /><br />"
retstring = luci.sys.exec("/usr/sbin/dnscrypt-proxy -resolve www.google.com,127.0.0.1:7915 -config /var/etc/dnscrypt-proxy-ns1.conf")
luci.http.prepare_content("application/json")
luci.http.write_json({ ret=retstring })
end
-- called by XHR.get from logview.htm
function act_read(lfile)
local fs = require "nixio.fs"
local http = require "luci.http"
local lfile = http.formvalue("lfile")
local ldata={}
ldata[#ldata+1] = fs.readfile(lfile) or "_nofile_"
if ldata[1] == "" then
ldata[1] = "_nodata_"
end
http.prepare_content("application/json")
http.write_json(ldata)
end
function action_data()
local http = require "luci.http"
local types = {
csv = "text/csv",
json = "application/json"
}
local args = { }
local mtype = http.formvalue("type") or "json"
http.prepare_content(types[mtype])
exec("/usr/sbin/dnscrypt-proxy", args, http.write)
end
function action_list()
local http = require "luci.http"
local fd = io.popen("/usr/sbin/dnscrypt-proxy -c list")
local periods = { }
if fd then
while true do
local period = fd:read("*l")
if not period then
break
end
periods[#periods+1] = period
end
fd:close()
end
http.prepare_content("application/json")
http.write_json(periods)
end
function action_update(surl)
local tmp = "/tmp/dnscrypt-proxy_bin.tar.gz"
local dir = "/tmp"
local stype = luci.util.exec("uname"):lower()
local sarch = luci.util.exec("uname -m")
exec("/usr/bin/wget-ssl", {"--no-check-certificate", "-O", tmp, surl})
exec("/usr/sbin/dnscrypt-proxy", {"-service", "stop" })
local files = { }
local tar = io.popen("/bin/tar -tzf %s" % tmp, "r")
if tar then
while true do
local file = tar:read("*l")
if not file then
break
elseif file:match("^(.*\/dnscrypt.proxy)$") then
files[#files+1] = file
end
end
tar:close()
end
if #files == 0 then
return {500, "Internal Server Error", stype, sarch}
end
local output = { }
exec("/usr/sbin/dnscrypt-proxy", {"-service", "stop" })
exec("/bin/mkdir", { "-p", dir })
exec("/bin/tar", { "-C", dir, "-vxzf", tmp, unpack(files) },
function(chunk) output[#output+1] = chunk:match("%S+") end)
exec("/bin/cp", { "-f", dir .. "/" .. files[1], "/usr/sbin/dnscrypt-proxy"})
exec("/bin/rm", { "-f", "%s/%s-%s/dnscrypt-proxy" % {dir, stype, sarch}})
exec("/bin/rm", { "-f", tmp })
exec("/usr/sbin/dnscrypt-proxy", {"-service", "start" })
return out
end
function exec(cmd, args, writer)
local os = require "os"
local nixio = require "nixio"
local fdi, fdo = nixio.pipe()
local pid = nixio.fork()
if pid > 0 then
fdo:close()
while true do
local buffer = fdi:read(2048)
if not buffer or #buffer == 0 then
break
end
if writer then
writer(buffer)
end
end
nixio.waitpid(pid)
elseif pid == 0 then
nixio.dup(fdo, nixio.stdout)
fdi:close()
fdo:close()
nixio.exece(cmd, args, nil)
nixio.stdout:close()
os.exit(1)
end
end
@@ -0,0 +1,454 @@
msgid ""
msgstr "Content-Type: text/plain; charset=UTF-8\n"
msgid "Enable"
msgstr "启用"
msgid "Disable"
msgstr "停用"
msgid "Log"
msgstr "日志"
msgid "Servers Setting"
msgstr "服务器配置"
msgid "Alias(optional)"
msgstr "别名(可选)"
msgid "Server Address"
msgstr "服务器地址"
msgid "Server Port"
msgstr "服务器端口"
msgid "Local Port"
msgstr "本地端口"
msgid "Protocol"
msgstr "传输协议"
msgid "Access Control"
msgstr "访问控制"
msgid "Enable Server"
msgstr "启动服务端"
msgid "Server Setting"
msgstr "服务端配置"
msgid "Edit DNSCrypt Server"
msgstr "编辑服务器配置"
msgid "Alias"
msgstr "别名"
msgid "Server"
msgstr "服务器"
msgid "Status"
msgstr "状态"
msgid "Unknown"
msgstr "未知"
msgid "Running Status"
msgstr "运行状态"
msgid "Not exist"
msgstr "未安装可执行文件"
msgid "Not Running"
msgstr "未运行"
msgid "Running"
msgstr "运行中"
msgid "No Check"
msgstr "未检查"
msgid "Check"
msgstr "检查"
msgid "Connect OK"
msgstr "连接正常"
msgid "Connect Error"
msgstr "连接错误"
msgid "Check..."
msgstr "正在检查.."
msgid "Proxy Check"
msgstr "代理检查"
msgid "Records"
msgstr "条记录"
msgid "Refresh Data"
msgstr "更新数据库"
msgid "Refresh..."
msgstr "正在更新,请稍候.."
msgid "Refresh OK!"
msgstr "更新成功!"
msgid "Refresh Error!"
msgstr "更新失败!"
msgid "No new data!"
msgstr "你已经是最新数据,无需更新!"
msgid "Total Records:"
msgstr "新的总纪录数:"
msgid "Check Server"
msgstr "检查服务器"
msgid "Servers Manage"
msgstr "服务器管理"
msgid "Update"
msgstr "更新"
msgid "Server Count"
msgstr "服务器节点数量"
msgid "Severs Nodes"
msgstr "服务器节点"
msgid "Server Node Type"
msgstr "服务器节点类型"
msgid "DNSCrypt Resolvers"
msgstr "订阅列表"
msgid "DNSCrypt Proxy Settings"
msgstr "DNSCrypt代理设置"
msgid "Proxy Setting"
msgstr "代理设置"
msgid "DNSCrypt Proxy"
msgstr "DNSCrypt代理"
msgid "DNSCrypt Proxy binary not found."
msgstr "未发现DNSCrypt代理应用。"
msgid "Binary Management"
msgstr "应用管理"
msgid "Binary update"
msgstr "应用更新 "
msgid "Current version:"
msgstr "当前版本:"
msgid "Version2 atleast:"
msgstr "至少应为第2版:"
msgid "Update to final release from:"
msgstr "更新到最新正式版本:"
msgid "Force Reload"
msgstr "强制重启"
msgid "Proxy force reload,"
msgstr "强制重启代理:"
msgid "DNS resolve test"
msgstr "DNS解析测试"
msgid "Cache Management"
msgstr "缓存文件"
msgid "Running resolver details and offline caches manipulating."
msgstr "节点详情和订阅的离线缓存文件控制。"
msgid "Dump Details"
msgstr "调取详情"
msgid "Dump DNSCrypt resolver details to cache file:"
msgstr "从代理实例调取节点详情到文件缓存:"
msgid "JSON dumped"
msgstr "详情JSON已保存 "
msgid "Remove cache files"
msgstr "删除缓存文件"
msgid "files"
msgstr "文件"
msgid "Remove cached resolvers info from:"
msgstr "删除所有订阅的缓存:"
msgid "Resolver cache"
msgstr "订阅缓存 "
msgid "Listening address"
msgstr "监听地址"
msgid "Enabled Resolvers"
msgstr "订阅列表"
msgid "Fallback resolvers"
msgstr "备用DNS"
msgid "DNS resolver on query fails or for forced forwarding domain list."
msgstr "用于解析DNSCrypt解析失败时,及强制转发名单里的域名。"
msgid "Resolver White List"
msgstr "节点白名单"
msgid "Resolver white list by name, Allow *ALL* in default."
msgstr "默认允许所有订阅内任意节点名的节点。"
msgid "Resolver Black List"
msgstr "节点黑名单"
msgid "Resolver black list by name, disable specified resolver."
msgstr "不连接节点名在此名单内的节点。"
msgid "Forwarding2Fallback"
msgstr "强制转发名单"
msgid "Domains forced to fallback resolver, [.conf] file treat like dnsmasq configure."
msgstr "强制由备用DNS解析的名单,可处理dnsmasq的配置文件[.conf]。"
msgid "Domain Black List"
msgstr "域名黑名单"
msgid "Domains to blacklist, [.conf|.adblock] file treat like dnsmasq configure:"
msgstr "拒绝解析的域名表,可处理dnsmasq及adbyby的配置文件[.conf|.adblock]:"
msgid "IP Address List"
msgstr "IP黑名单"
msgid "IP Address to blacklist, [.conf] file treat like dnsmasq configure:"
msgstr "拒绝解析的IP名单,可处理dnsmasq的配置文件[.conf]:"
msgid "Net probe address"
msgstr "找网DNS"
msgid "Resolver on downloading resolver lists file."
msgstr "用于解析订阅地址的DNS。"
msgid "Force Options"
msgstr "强制选项"
msgid "ignore_system_dns"
msgstr "忽略系统DNS"
msgid "ipv4_servers"
msgstr "连IPv4类型"
msgid "ipv6_servers"
msgstr "连IPv6类型"
msgid "block_ipv6"
msgstr "立即空答IPv6"
msgid "dnscrypt_servers"
msgstr "连DNSCrypt类型"
msgid "doh_servers"
msgstr "连DoH类型"
msgid "require_dnssec"
msgstr "DNSSEC验证"
msgid "force_tcp"
msgstr "只用TCP查询"
msgid "require_nolog"
msgstr "无记录验证"
msgid "require_nofilter"
msgstr "无过滤验证"
msgid "cache"
msgstr "缓存结果"
msgid "offline_mode"
msgstr "离线模式"
msgid "use_syslog"
msgstr "系统LOG"
msgid "dnscrypt_ephemeral_keys"
msgstr "不停刷新钥匙"
msgid "cert_ignore_timestamp"
msgstr "忽略证书时间戳"
msgid "tls_disable_session_tickets"
msgstr "TLS握手不缓存"
msgid "lb_estimator"
msgstr "节点监测"
msgid "Response for blocked queries."
msgstr "拒绝时回应"
msgid "Load-balancing strategy"
msgstr "负载均衡模式"
msgid "Items forced for checking, will show your the defaults when unchecked all."
msgstr "DNSCrypt节点过滤和其它设置项,清空后会显示为默认。"
msgid "Static Stamp"
msgstr "静态戳记"
msgid "Mostly useful for testing your own servers."
msgstr "主要用来测试自建DNSCrypt服务器。"
msgid "One or more requred configure is missing!"
msgstr "至少需要1个正确配置并更新的订阅!"
msgid "Address filter list(ipset)"
msgstr "节点地址过滤(ipset)"
msgid "The ipset list name that DNSCrypt server addresses try appending to if any."
msgstr "向使用中的 ipset 列表追加过滤出来的DNSCrypt 节点地址。"
msgid "Follwing available one (auto)"
msgstr "检测已知 (auto)"
msgid "PassWall direct (vpsiplist)"
msgstr "PassWall 直走(vpsiplist)"
msgid "Clash direct (localnetwork)"
msgstr "Clash 直走(localnetwork)"
msgid "SSR-Plus direct (ss_spec_wan_ac)"
msgstr "SSR+ 直走(ss_spec_wan_ac)"
msgid "Total %d records."
msgstr "总计 %d 条记录。"
msgid "Resolvers"
msgstr "订阅"
msgid "Available Resolvers:"
msgstr "已知节点:"
msgid "DNSCrypt Resolver Info"
msgstr "DNSCrypt节点信息"
msgid "Trash Resolver Info"
msgstr "清空节点信息"
msgid "DNS Forward"
msgstr " DNS转发"
msgid "DNS Forward resolve test"
msgstr "DNS转发检查"
msgid "Check DNSCrypt resolver:"
msgstr "检测DNS服务器:"
msgid "Resolver ACL"
msgstr "节点流控"
msgid "ACL Setting"
msgstr "流控设置"
msgid "DNSCrypt Resolver ACL"
msgstr "DNSCrypt节点流控"
msgid "DNSCrypt will automatically pick the fastest, working servers from the list<br />All or filtered out addresses are prepared for 'Address filter list(ipset)'.<br />Resolver with detailed addresses is a must."
msgstr "DNSCrypt 会自动选择最快的有效服务器。<br />所有已知或过滤出来的地址将用于 '节点地址过滤(ipset)' <br />只有存在详细地址的节点才能添加进来。"
msgid "DNS Resolver Filter"
msgstr "节点篩选器"
msgid "Resolver"
msgstr "解析服务器"
msgid "Stamp"
msgstr "戳记"
msgid "more than 100, try filter out to show details..."
msgstr "多于100条,请进一步篩选以显示节点详情。"
msgid "Choose a resolver to configure"
msgstr "请选择一个订阅来配置"
msgid "Input the name to re-configure, if your resolvers not updated correctly and not showns up."
msgstr "如果你的订阅没有出现在列表里(或未能正确更新),请手动输入订阅名字新增(或重新)订阅。"
msgid "Update the resolvers here by a new apply. Permently cover your modifications on specific resolve, if the source resolved successfully."
msgstr "在此应用借以更新订阅。如订阅成功,将重建此订阅中所有节点信息。"
msgid "Not a typical dnscrypt-proxy resolvers configure!"
msgstr "非典型的DNSCrypt代理订阅配置!"
msgid "Additional changes on apply:"
msgstr "点应用后,将会产生如下附加配置的修改:"
msgid "Change this only on duplicating this resolver to another source when retrieving:"
msgstr "修改此值并更新,借以新增自定订阅:"
msgid "Cache File"
msgstr "缓存"
msgid "Details File URL"
msgstr "节点详情"
msgid "Detailed resolvers JSON file `dnscrypt-proxy -json --list-all` or:"
msgstr "有关节点情细情况的JSON文件,可在启动后手动执行`dnscrypt-proxy -json --list-all`来抓取,或:"
msgid "DNSCrypt Resolvers Manage"
msgstr "DNSCrypt订阅管理"
msgid "Update the resolvers here by a new apply."
msgstr "应用时会自动检查并更新订阅。"
msgid "DNSCrypt Resolver Editing -"
msgstr "DNSCrypt节点编辑 - "
msgid "Source URL"
msgstr "订阅地址"
msgid "Split MultiValues by a comma"
msgstr "多个值以英文逗号分隔"
msgid "MiniSign Key"
msgstr "Mini验证"
msgid "Resolvers singed by:"
msgstr "订阅源签名公钥:"
msgid "No resolvers update."
msgstr "离线模式"
msgid "Force cached resolvers only:"
msgstr "使用缓存文件,主要用来解决在订阅源无法正常获取时,DNSCrypt代理启动失败的状况:"
msgid "Selfsign Resolvers"
msgstr "自签订阅"
msgid "Mostly useful for build your own resolvers list on net probe fails. minisign binary needed:"
msgstr "主要用于自定义的服务器订阅,需要安装minisign应用:"
msgid "Choose a file from the list to read."
msgstr "请从列表选择文件来查看。"
msgid "File Viewer"
msgstr "文件查看器"
msgid "Read / Reread log file"
msgstr "读取/重新读取"
msgid "Please press [Read] button"
msgstr "请点读取按钮"
msgid "File not found or empty"
msgstr "文件未找到或为空"
msgid "No files found"
msgstr "文件未找到"
@@ -0,0 +1,60 @@
-- Copyright (C) 2019 github.com/peter-tank
-- Licensed to the public under the GNU General Public License v3.
local disp = require "luci.dispatcher"
local m, _, s, o
local cfg="dnscrypt-proxy"
m = Map(cfg, "%s - %s" %{translate("DNSCrypt Proxy"),
translate("ACL Setting")})
-- [[ ACL Setting ]]--
s = m:section(TypedSection, "server_addr", translate("DNSCrypt Resolver ACL"), translate("DNSCrypt will automatically pick the fastest, working servers from the list<br />All or filtered out addresses are prepared for 'Address filter list(ipset)'.<br />Resolver with detailed addresses is a must.") .. " https://dnscrypt.info/stamps/")
s.sectionhead = translate("Alias")
s.template = "cbi/tblsection"
s.addremove = true
s.anonymous = true
s.sortable = false
s.template_addremove = "dnscrypt-proxy/cbi_addserver"
function s.create(self, section)
local a = m:formvalue("_newsrv.alias")
local c = m:formvalue("_newsrv.country")
local n = m:formvalue("_newsrv.name")
local ad = m:formvalue("_newsrv.addrs")
local po = m:formvalue("_newsrv.ports")
local pr = m:formvalue("_newsrv.proto")
local st = m:formvalue("_newsrv.stamp")
created = nil
if a ~= "" and n ~= "" and ad ~= "" and st ~= "" then
created = TypedSection.create(self, section)
self.map:set(created, "alias", a)
self.map:set(created, "country", c or "Unknown")
self.map:set(created, "name", n or "Custom")
self.map:set(created, "addrs", ad)
self.map:set(created, "ports", po or "ALL")
self.map:set(created, "proto", pr or "Unknown")
self.map:set(created, "stamp", st or "Error")
end
end
function s.parse(self, ...)
TypedSection.parse(self, ...)
if created then
m.uci:save("dnscrypt-proxy")
luci.http.redirect(disp.build_url("admin", "services", "dnscrypt-proxy", "dnscrypt-proxy-acl"))
end
end
s:option(DummyValue,"alias",translate("Alias"))
s:option(DummyValue,"country",translate("Country"))
s:option(DummyValue,"proto",translate("Protocol"))
s:option(DummyValue,"name",translate("Resolver"))
s:option(DummyValue,"addrs",translate("Address"))
s:option(DummyValue,"ports",translate("Server Port"))
s:option(DummyValue,"stamp",translate("Stamp"))
return m
@@ -0,0 +1,229 @@
-- Copyright (C) 2019 github.com/peter-tank
-- Licensed to the public under the GNU General Public License v3.
local m, _, s, o
local dc = require "luci.tools.dnscrypt".init()
local resolvers = dc:resolvers_list(true)
local cfg = "dnscrypt-proxy"
m = Map(cfg, "%s - %s" %{translate("DNSCrypt Proxy"), translate("Proxy Setting")})
-- [[ Proxy Setting ]]--
local type = "dnscrypt-proxy"
s = m:section(TypedSection, type)
s.anonymous = false
-- section might not exist
function s.cfgvalue(self, section)
if not self.map:get(section) then
self.map:set(section, nil, self.sectiontype)
self.map:set(section, "resolvers", {"public-resolvers", "opennic"})
end
return self.map:get(section)
end
o = s:option(Flag, "enable", translate("Enable"))
o.default = false
o.optional = false
o = s:option(Value, "listen_addresses", translate("Listening address"), translate("Split MultiValues by a comma"))
o.default = "127.0.0.1:5335"
o.placeholder = o.default
o.optional = false
o.rmempty = false
o = s:option(Value, "netprobe_address", translate("Net probe address"), translate("Resolver on downloading resolver lists file."))
o.default = "114.114.114.114:53"
o.placeholder = o.default
o.optional = false
o.rmempty = false
o = s:option(Value, "bootstrap_resolvers", translate("Fallback resolvers"), translate("DNS resolver on query fails or for forced forwarding domain list.") .. translate("Split MultiValues by a comma"))
o.default = "114.114.114.114:53"
o.placeholder = o.default
o.optional = false
o.rmempty = false
o = s:option(DynamicList, "resolvers", translate("Enabled Resolvers"), translate("Available Resolvers: ") .. "https://download.dnscrypt.info/dnscrypt-resolvers/v2/{*}.md")
local opt, val
for _, val in ipairs(resolvers) do
opt = luci.util.split(val, "|")[1]
o:value(opt, translate(opt))
end
o.optional = false
o.rmempty = false
o.placeholder = "onion-services"
o = s:option(MultiValue, "force", translate("Force Options"), translate("Items forced for checking, will show your the defaults when unchecked all."))
o.optional = false
o.widget = "select"
o.force_defaults = {
["lb_estimator"] = "true",
["ignore_system_dns"] = "true",
["block_unqualified"] = "true",
["block_undelegated"] = "true",
["ipv4_servers"] = "true",
["ipv6_servers"] = "false",
["block_ipv6"] = "true",
["dnscrypt_servers"] = "true",
["doh_servers"] = "true",
["require_dnssec"] = "false",
["force_tcp"] = "false",
["require_nolog"] = "true",
["require_nofilter"] = "true",
["cache"] = "true",
["offline_mode"] = "false",
["dnscrypt_ephemeral_keys"] = "false",
["tls_disable_session_tickets"] = "false",
["cert_ignore_timestamp"] = "true",
["use_syslog"] = "false",
}
for k, v in pairs(o.force_defaults) do o:value(k, translate(k)) end
o.cfgvalue = function (self, section)
local ret, k, d
ret = Value.cfgvalue(self, section)
if ret then return ret end
ret = ""
for k, d in pairs(self.force_defaults) do
if d == "true" then
ret = ret .. self.delimiter .. k
end
end
return ret
end
o = s:option(ListValue, "log_level", translate("Log output level"))
o:value(0, translate("Debug"))
o:value(1, translate("Info"))
o:value(2, translate("Notice"))
o:value(3, translate("Warning"))
o:value(4, translate("Error"))
o:value(5, translate("Critical"))
o:value(6, translate("Fatal"))
o.default = 2
o.optional = true
o.rmempty = true
o = s:option(ListValue, "blocked_query_response", translate("Response for blocked queries."))
o:value("refused", translate("refused"))
o:value("hinfo", translate("hinfo"))
o.default = "hinfo"
o.placeholder = "eg: a:<IPv4>,aaaa:<IPv6>"
o.optional = true
o.rmempty = true
o = s:option(ListValue, "lb_strategy", translate("Load-balancing strategy"))
o:value("p2", translate("p2"))
o:value("ph", translate("ph"))
o:value("first", translate("first"))
o:value("random", translate("random"))
o.default = "p2"
o.optional = true
o.rmempty = true
o = s:option(DynamicList, "forwarding_rules", translate("Forwarding2Fallback"), translate("Domains forced to fallback resolver, [.conf] file treat like dnsmasq configure."))
o.default = "/etc/dnsmasq.oversea/oversea_list.conf"
o.placeholder = "/etc/dnsmasq.oversea/oversea_list.conf"
o.optional = true
o.rmempty = true
o = s:option(DynamicList, "blocked_named", translate("Domain Black List"), translate("Domains to blacklist, [.conf|.adblock] file treat like dnsmasq configure: ") .. "https://download.dnscrypt.info/blacklists/domains/mybase.txt")
o.default = "/etc/dnsmasq.ssr/ad.conf"
o.placeholder = "/usr/share/adbyby/dnsmasq.adblock"
o.optional = true
o.rmempty = true
o = s:option(DynamicList, "blocked_ips", translate("IP Address List"), translate("IP Address to blacklist, [.conf] file treat like dnsmasq configure: ") .. "https://download.dnscrypt.info/blacklists/ips/mybase.txt")
o.default = "https://download.dnscrypt.info/blacklists/domains/mybase.txt"
o.placeholder = "/etc/dnsmasq.ssr/ad.conf"
o.optional = true
o.rmempty = true
o = s:option(DynamicList, "static", translate("Static Stamp"), translate("Mostly useful for testing your own servers."))
o.optional = true
o.rmempty = true
o.placeholder = "eg: sdns:AQcAAAAAAAAAAAAQMi5kbnNjcnlwdC1jZXJ0Lg"
o = s:option(Value, "server_names", translate("Resolver White List"), "%s %s" % {translate("Resolver white list by name, Allow *ALL* in default."), translate("Split MultiValues by a comma")})
o.optional = true
o.rmempty = true
o.placeholder = "eg: goodguy1,goodguy2"
o = s:option(Value, "disabled_server_names", translate("Resolver Black List"), "%s %s" % {translate("Resolver black list by name, disable specified resolver."), translate("Split MultiValues by a comma")})
o.optional = true
o.rmempty = true
o.placeholder = "eg: badguy1,badguy2"
o = s:option(Value, "max_clients", translate("Simulaneous"), translate("Maximum number of simultaneous client connections to accept."))
o.optional = true
o.rmempty = true
o.datatype = "uinteger"
o.default = 500
o.placeholder = o.default
o = s:option(Value, "keepalive", translate("Keep Alive"), translate("Keepalive for HTTP (HTTPS, HTTP/2) queries, in seconds."))
o.optional = true
o.rmempty = true
o.datatype = "uinteger"
o.default = 30
o.placeholder = o.default
o = s:option(Value, "cert_refresh_delay", translate("Cert refresh delay"), translate("Delay, in minutes, after which certificates are reloaded."))
o.optional = true
o.rmempty = true
o.datatype = "uinteger"
o.default = 240
o.placeholder = o.default
o = s:option(Value, "netprobe_timeout", translate("Net probe timer"), translate("Maximum time (in seconds) to wait for network connectivity before initializing the proxy."))
o.optional = true
o.rmempty = true
o.datatype = "uinteger"
o.default = 60
o.placeholder = o.default
o = s:option(Value, "reject_ttl", translate("Reject TTL"), translate("TTL for synthetic responses sent when a request has been blocked (due to IPv6 or blacklists)."))
o.optional = true
o.rmempty = true
o.datatype = "uinteger"
o.default = 60
o.placeholder = o.default
o = s:option(Value, "cache_size", translate("Cache size"), translate("Cache size for queries."))
o.optional = true
o.rmempty = true
o.datatype = "uinteger"
o.default = 512
o.placeholder = o.default
o = s:option(Value, "query_meta", translate("Additional TXT records"), translate("Additional data to attach to outgoing queries.") .. [[<br />]] .. translate("Split MultiValues by a comma"))
o.optional = true
o.rmempty = true
o.placeholder = "eg: key1:value1,key2:value2"
o = s:option(Value, "proxy", translate("SOCKS proxy"), translate("Tor doesn't support UDP, so set `force_tcp` to `true` as well."))
o.optional = true
o.rmempty = true
o.placeholder = "eg: socks5://127.0.0.1:9050"
o = s:option(Value, "http_proxy", translate("HTTP/HTTPS proxy"), translate("Only for DoH servers."))
o.optional = true
o.rmempty = true
o.placeholder = "eg: http://127.0.0.1:8888"
o = s:option(DynamicList, "cloaking_rules", translate("Cloaking rules"), translate("Cloaking returns a predefined address for a specific name."))
o.optional = true
o.rmempty = true
o.placeholder = "eg: /usr/share/dnscrypt-proxy/cloaking-rules.txt"
o = s:option(Value, "addr_filter", translate("Address filter list(ipset)"), translate("The ipset list name that DNSCrypt server addresses try appending to if any."))
o:value("auto", translate("Follwing available one (auto)"))
o:value("vpsiplist", translate("PassWall direct (vpsiplist)"))
o:value("localnetwork", translate("Clash direct (localnetwork)"))
o:value("ss_spec_wan_ac", translate("SSR-Plus direct (ss_spec_wan_ac)"))
o.optional = false
o.rmempty = true
o.default = "auto"
o.placeholder = "e.g.: gfwlist"
return m
@@ -0,0 +1,210 @@
-- Copyright (C) 2019 github.com/peter-tank
-- Licensed to the public under the GNU General Public License v3.
local m, s, o, _, v, created
local fs = require "nixio.fs"
local sys = require "luci.sys"
local ds = require "luci.tools.dnscrypt".init()
local cfg, sid = arg[1], arg[2]
local protocol = {
"DNSCrypt",
"DoH",
}
function typicalmsg(self)
if not ds:typicalcfg(cfg) then
local c, init, exec = luci.model.uci.cursor()
c:load("ucitrack")
init = c:get_first("ucitrack", self.config, "init", "null")
exec = c:get_first("ucitrack", self.config, "exec", "null")
c:unload("ucitrack")
local error = {}
table.insert(error, translate("Not a typical dnscrypt-proxy resolvers configure!") .. " Original for: [%s]" % init)
table.insert(error, translate("Additional changes on apply:"))
table.insert(error, "init script: %s --> %s" % {init, "null"})
table.insert(error, "exec script: %s --> %s %s" % {exec, "/etc/init.d/dnscrypt-proxy_resolvers", self.config})
self.error = self.error and self.error or {}
self.error["global"] = self.error["global"] and table(self.error["global"], error) or error
end
end
function list_dns(m, cfg)
-- [[ Source Setting ]]--
s = m:section(NamedSection, "global", "source", translate("Configuration -: ") .. cfg)
s.description = translate("Update the resolvers here by a new apply. Permently cover your modifications on specific resolve, if the source resolved successfully.")
s.anonymous = false
s.addremove = true
typicalmsg(s)
-- section might not exist
function s.cfgvalue(self, section)
if not self.map:get(section) then
self.map:set(section, nil, self.sectiontype)
end
return self.map:get(section)
end
function s.remove(self, section)
self.map:del(section)
self.map.proceed = true
return self.map.uci:delete_all(self.config, "dnscrypt", function(section) return true end)
end
o = s:option(Value, "urls", translate("Source URL"), translate("Split MultiValues by a comma"))
o.rmempty = true
o.default = "https://download.dnscrypt.info/dnscrypt-resolvers/v3/public-resolvers.md"
o.placeholder = "https://xxx.xxx.xxx/public-resolvers.md"
--o = s:option(Value, "cache_file", translate("Cache File"), translate("Change this only on duplicating this resolver to another source when retrieving: ") .. "/usr/share/dnscrypt-proxy/{*}.md")
--o.default = "public-resolvers.md"
--o.placeholder = "eg: custom.md"
--o.rmempty = false
o = s:option(Value, "details_json", translate("Details File URL"), translate("Detailed resolvers JSON file `dnscrypt-proxy -json --list-all` or: ") .. "https://download.dnscrypt.info/dnscrypt-resolvers/json/{*}.json")
o.default = "https://download.dnscrypt.info/dnscrypt-resolvers/json/public-resolvers.json"
o.placeholder = "eg: custom.json"
o.rmempty = true
o = s:option(Flag, "cache_mode", translate("No resolvers update."), translate("Force cached resolvers only: ").. "/usr/share/dnscrypt-proxy/{.*}[.md|.minisig|.json]")
o.default = true
o.optional = false
o.rmempty = false
o = s:option(Flag, "selfsign", translate("Selfsign Resolvers"), translate("Mostly useful for build your own resolvers list on net probe fails. minisign binary needed: ") .. "https://github.com/peter-tank/openwrt-minisign")
o.default = false
o.optional = false
o.rmempty = true
o:depends("cache_mode", true)
o = s:option(Value, "minisign_key", translate("MiniSign Key"), translate("Resolvers singed by: ") .. "https://download.dnscrypt.info/dnscrypt-resolvers/v3/minisign.pub")
o.default = "RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3"
o.optional = false
o.placeholder = o.default
o.rmempty = false
o:depends("selfsign", false)
o = s:option(Value, "prefix", translate("Prefix"), translate("Change this only on filtering the resolvers: "))
o.optional = true
o.placeholder = "eg: quad9-"
o.rmempty = true
o = s:option(Value, "format", translate("Version"), translate("Configure file version."))
o.optional = true
o.default = "v3"
o.rmempty = true
s = m:section(TypedSection, 'dnscrypt', translate("DNSCrypt Resolver Info"))
s.anonymous = false
s.addremove = true
s.sortable = false
s.template = "cbi/tblsection"
s.extedit = luci.dispatcher.build_url("admin/services/dnscrypt-proxy/dnscrypt-resolvers", cfg, "%s")
function s.create(self, name)
AbstractSection.create(self, name)
luci.http.redirect( self.extedit:format(name) )
end
s:option(DummyValue, "country", translate("Country"))
s:option(DummyValue, "proto", translate("Protocol"))
s:option(DummyValue, "name", translate("Resolver"))
s:option(DummyValue, "description", translate("Description"))
s:option(Flag, "ipv6", translate("IPv6"))
s:option(Flag, "dnssec", translate("DNSSec Validation"))
s:option(Flag, "nolog", translate("NoLog"))
s:option(Flag, "nofilter", translate("NoFilter"))
s:option(DummyValue, "addrs", translate("Address"))
s:option(DummyValue, "ports", translate("Port"))
-- s:option(DummyValue, "stamp", translate("Stamp"))
end
-- [[ Servers Setting ]]--
function edit_list(m)
s = m:section(NamedSection, sid, "dnscrypt", translate("DNSCrypt Resolver Editing - ") .. sid)
s.anonymous = true
s.addremove = true
o = s:option(Value, "name", translate("Resolver"))
o.default = sid
o.rmempty = false
o = s:option(Value, "country", translate("Country"))
o.default = "China"
o = s:option(Value, "proto", translate("Protocol"))
for _, v in ipairs(protocol) do o:value(v) end
o.default = "DNSCrypt"
o.rmempty = false
o = s:option(Value, "description", translate("Description"))
o.default = "Dns-over-HTTPS protocol . Non-logging, supports DNSSEC. By https://www.google.com"
o = s:option(Flag, "dnssec", translate("DNSSec Validation"))
o.rmempty = false
o = s:option(Flag, "ipv6", translate("IPv6"))
o.rmempty = false
o = s:option(Flag, "nolog", translate("NoLog"))
o.rmempty = false
o = s:option(Flag, "nofilter", translate("NoFilter"))
o.rmempty = false
o = s:option(Value, "addrs", translate("Resolver Address"), translate("Split MultiValues by a comma"))
o.placeholder = "eg: 8.8.8.8,8.8.4.4"
o.rmempty = false
o = s:option(Value, "ports", translate("Port"), translate("Split MultiValues by a comma"))
o.placeholder = "eg: 443,5353"
o.rmempty = false
o = s:option(Value, "location", translate("Geography Coordinates"))
o.placeholder = "eg: 33.032501, 83.895699"
o = s:option(Value, "stamp", translate("Stamp"))
o.placeholder = "eg: sdns://AgcAAAAAAAAADjExNi4yMDMuNzAuMTU2ID4aGg9sU_PpekktVwhLW5gHBZ7gV6sVBYdv2D_aPbg4EWRvaC5kbnN3YXJkZW4uY29tCy91bmNlbnNvcmVk"
o.rmempty = false
end
m = Map(cfg, translate("DNSCrypt Resolvers Manage"))
m.on_before_commit = function (self)
local exec = "/etc/init.d/dnscrypt-proxy_resolvers"
local section, stype, on_remove = "global", "source", false
local sec = "DNSCrypt_resolvers_" .. self.config:gsub("[^%w_]", "_")
local c = luci.model.uci.cursor()
local changes = c:changes()
self.redirect = luci.dispatcher.build_url("admin/services/dnscrypt-proxy/dnscrypt-resolvers", self.config)
for r, tbl in pairs(changes or next(changes)) do
for s, os in pairs(tbl) do
if os['.type'] and os['.type'] == "" and r == self.config and s == section then on_remove = true end
end
end
c:load("ucitrack")
if self:get(section, "urls") and not on_remove then
if not c:get_first("ucitrack", self.config, "exec", nil) then
c:delete_all("ucitrack", self.config, function(section) return true end)
sec = c:section("ucitrack", self.config, sec)
c:set("ucitrack", sec, "init", "null")
c:set("ucitrack", sec, "test", "0")
c:set("ucitrack", sec, "exec", "%s %s" % {exec, self.config})
c:save("ucitrack"); c:commit("ucitrack");
end
else
if c:get_first("ucitrack", self.config, "exec", nil) == ("%s %s" %{exec, self.config}) then
c:delete_all("ucitrack", self.config, function(section) return true end)
c:save("ucitrack"); c:commit("ucitrack");
end
self.redirect = luci.dispatcher.build_url("admin/services/dnscrypt-proxy/dnscrypt-resolvers")
end
c:unload("ucitrack")
luci.util.exec("/etc/init.d/ucitrack reload")
end
if cfg then
if sid == nil then list_dns(m, cfg) else edit_list(m, cfg) end
else
luci.http.redirect(luci.dispatcher.build_url("admin/services/dnscrypt-proxy/dnscrypt-resolvers"))
return
end
return m
@@ -0,0 +1,55 @@
-- Copyright (C) 2019 github.com/peter-tank
-- Licensed to the public under the GNU General Public License v3.
local m, _, s, o, id, cfg, src, val, k, v
local uci = luci.model.uci.cursor()
local dc = require "luci.tools.dnscrypt".init()
local resolvers = dc:resolvers_list(true)
local disp = require "luci.dispatcher"
cfg = "dnscrypt-proxy"
local dnslist_table = {}
for _, s in pairs(dc:dns_list()) do
if s.addrs ~= nil then
dnslist_table[#dnslist_table+1] = "%s.%s:%s@%s://%s:%s" %{s.resolver, s.country, s.name, s.proto, s.addrs, s.ports}
end
end
-- [[ Servers Setting ]]--
m = Map(cfg, translate("DNSCrypt Resolvers"))
m.anonymous = true
m.addremove = false
m.pageaction = false
local type = "dnscrypt-proxy"
s = m:section(NamedSection, 'ns1', type, translate("Choose a resolver to configure"), translate("Input the name to re-configure, if your resolvers not updated correctly and not showns up."))
s.cfgvalue = function(self, section) return resolvers end
s.template = "dnscrypt-proxy/cfgselection"
s.extedit = luci.dispatcher.build_url("admin/services/dnscrypt-proxy/dnscrypt-resolvers", "%s")
--s = m:section(NamedSection, 'ns1', type, translate("DNSCrypt Resolvers update"))
o = s:option(TextValue, "_Dummy", translate("DNSCrypt Resolver Info"), translate("Total %d records.") % #dnslist_table)
o.rows = 7
o.readonly = true
o.wrap = "soft"
o.cfgvalue = function (self, sec)
local ret = translate("Available Resolvers:")
for k, v in pairs(dnslist_table) do ret = "%s\n%03d) %s" % {ret, k, v} end
return ret
end
o.write = function (...) end
o = s:option(Button,"trash", translate("Trash Resolver Info"))
o.inputstyle = "reset"
o.write = function()
local resolver
for _, val in pairs(resolvers) do
resolver = luci.util.split(val, "|")[1]
uci:delete_all(resolver, "dnscrypt", function(s) return true end)
uci:save(resolver)
end
end
return m
@@ -0,0 +1,86 @@
-- Copyright (C) 2019 github.com/peter-tank
-- Licensed to the public under the GNU General Public License v3.
local m, _, s, o, id, cfg, src, val, k, v
local cfg="dnscrypt-proxy"
local sys = require "luci.sys"
local http = require "luci.http"
local disp = require "luci.dispatcher"
local fs = require "nixio.fs"
local dc = require "luci.tools.dnscrypt".init()
local resolvers = dc:resolvers_list(true)
local caches_list = {}
local caches="/usr/share/dnscrypt-proxy/*"
for name in (fs.glob(caches) or function() end) do
caches_list[#caches_list+1] = name
end
local function has_bin(name)
return sys.call("command -v %s >/dev/null" %{name}) == 0
end
local has_dnscrypt = has_bin("dnscrypt-proxy")
local bin_version
local bin_file="/usr/sbin/dnscrypt-proxy"
if not fs.access(bin_file) then
bin_version=translate("Not exist")
else
if not fs.access(bin_file, "rwx", "rx", "rx") then
fs.chmod(bin_file, 755)
end
bin_version=luci.util.trim(luci.sys.exec(bin_file .. " -version"))
if not bin_version or bin_version == "" then
bin_version = translate("Unknown")
end
end
m = Map(cfg, "%s - %s" %{translate("DNSCrypt Proxy"),
translate("Overview")})
m.pageaction = false
-- [[ Binary ]]--
s = m:section(TypedSection, cfg, translate("Binary Management"), has_dnscrypt and "" or ('<b style="color:red">%s</b>' % translate("DNSCrypt Proxy binary not found.")))
s.anonymous = true
o = s:option(DummyValue,"dnscrypt_bin", translate("Binary update"))
o.rawhtml = true
o.template = "dnscrypt-proxy/refresh"
o.name = translate("Update")
o.ret = bin_version:match('^1%..*') and translate("Version2 atleast: ") or translate("Current version: ")
o.value = o.ret .. bin_version
o.description = translate("Update to final release from: ") .. "https://github.com/dnscrypt/dnscrypt-proxy/releases"
o.write = function (self, ...) end
o = s:option(Button, "Force Reload", translate("Force Reload"), translate("Proxy force reload, ") .. "/etc/init.d/dnscrypt-proxy reload")
o.inputstyle = "reset"
o.write = function (self, ...)
sys.call("/etc/init.d/dnscrypt-proxy reload >/dev/null 2>&1 &")
http.redirect(disp.build_url("admin", "services", "dnscrypt-proxy"))
end
o = s:option(Button, "dnscrypt_ck", translate("DNS resolve test"))
o.rawhtml = true
o.template = "dnscrypt-proxy/resolve"
o.description = translate("Check DNSCrypt resolver: ") .. "/usr/sbin/dnscrypt-proxy -resolve www.google.com,127.0.0.1:7915 -config /var/etc/dnscrypt-proxy-ns1.conf"
-- [[ Caches ]]--
s = m:section(TypedSection, cfg, translate("Cache Management"), translate("Running resolver details and offline caches manipulating."))
s.anonymous = true
o = s:option(Button,"dump_resolver", translate("Dump Details"))
o.rawhtml = true
o.template = "dnscrypt-proxy/refresh"
o.ret = translate("JSON dumped ")
o.value = " %d %s" % {#resolvers, translate("Records")}
o.description = translate("Dump DNSCrypt resolver details to cache file: ") .. "dnscrypt-proxy -json --list-all > /usr/share/dnscrypt-proxy/running.json"
o = s:option(Button, "remove_caches", translate("Remove cache files"))
o.rawhtml = true
o.template = "dnscrypt-proxy/refresh"
o.name = translate("Remove")
o.ret = translate("Resolver cache ")
o.value = " %d %s" % {#caches_list, translate("files")}
o.description = translate("Remove cached resolvers info from: ") .. "/usr/share/dnscrypt-proxy/*"
return m
@@ -0,0 +1,55 @@
-- Copyright 2008 Steven Barth <steven@midlink.org>
-- Licensed to the public under the Apache License 2.0.
local NXFS = require "nixio.fs"
local DISP = require "luci.dispatcher"
local HTTP = require "luci.http"
local UCI = luci.model.uci.cursor()
local m = Map("dnscrypt-proxy")
m.pageaction=false
-- log directory
log_dir = UCI:get_first(m.config, "global", "log_dir") or "/tmp"
run_dir = UCI:get_first(m.config, "global", "run_dir") or "/var/etc"
local logfile_list = {}
local logfs = {
"%s/dnscrypt*" % run_dir,
"%s/dnsmasq.*/*.*" % log_dir,
"%s/*" % "/usr/share/dnscrypt-proxy",
"%s/dnscrypt_*" % log_dir,
"%s/*.ld" % log_dir,
}
local _, dir, ns, lv, lfile
for _, dir in pairs(logfs) do
for path in (NXFS.glob(dir) or function() end) do
logfile_list[#logfile_list+1] = path
end
end
ns = m:section(TypedSection, "_dummy", translate("File Viewer"))
ns.addremove = false
ns.anonymous = true
function ns.cfgsections()
return{"_exrules"}
end
lv = ns:option(DynamicList, "logfiles")
lv.template = "dnscrypt-proxy/logsview"
lv.inputtitle = translate("Read / Reread log file")
lv.rows = 25
lv.default = ""
for _, lfile in ipairs(logfile_list) do lv:value(lfile, lfile) end
function lv.cfgvalue(self, section)
if logfile_list[1] then
local lfile=logfile_list[1]
if NXFS.access(lfile) then
return lfile .. "\n" .. translate("Please press [Read] button")
end
return lfile .. "\n" .. translate("File not found or empty")
else
return log_dir .. "\/\n" .. translate("No files found")
end
end
return m
@@ -0,0 +1,126 @@
local type, pairs, ipairs, table, luci, math
= type, pairs, ipairs, table, luci, math
local tpl = require "luci.template.parser"
local utl = require "luci.util"
local uci = require "luci.model.uci"
local fs = require "nixio.fs"
module "luci.tools.dnscrypt"
local uci_r, uci_s
local dsec, dlst = "dnscrypt", "resolvers"
local dglb, dsrc = "source", "urls"
function _valid_id(x)
return (x and #x > 0 and x:match("^[a-zA-Z0-9_]+$"))
end
function _get(c, s, o)
return uci_r:get(c, s, o)
end
function _set(c, s, o, v)
if v ~= nil then
if type(v) == "boolean" then v = v and "1" or "0" end
return uci_r:set(c, s, o, v)
else
return uci_r:delete(c, s, o)
end
end
function typicalcfg(self, cfg)
local dnscrypt_types, all_val, k, t = {"source", "dnscrypt"}, uci_r:get_all(cfg)
if not all_val then return true end
for k, t in pairs(all_val) do
if not luci.util.contains(dnscrypt_types, t[".type"]) then return false end
end
return true
end
function resolvers_list(self, with_src)
local _, cfg, r = nil, nil, { }
for cfg in (fs.glob(uci_r:get_confdir() .. "/*")) do
cfg=fs.basename(cfg)
uci_r:foreach(cfg, dsec,
function(s)
if s.name and s.stamp and s.proto and s.country then
local src = uci_r:get_first(cfg, dglb, dsrc)
r[#r+1] = with_src and "%s|%s" %{cfg, src or "Unknown"} or cfg
end
return false
end)
end
return r
end
function country_list(self)
local _, cfg, r = nil, nil, { }
for _, cfg in pairs(self:resolvers_list(false)) do
uci_r:foreach(cfg, dsec,
function(s)
if s.country then
r[s.country] = s.country
end
end)
end
return r
end
function proto_list(self)
local _, cfg, r = nil, nil, { }
for _, cfg in pairs(self:resolvers_list(false)) do
uci_r:foreach(cfg, dsec,
function(s)
if s.proto then
r[s.proto] = s.proto
end
end)
end
return r
end
function dns_list(self, rf, cf, prf)
local _, cfg, r = nil, nil, { }
for _, cfg in pairs(self:resolvers_list(false)) do
uci_r:foreach(cfg, dsec,
function(s)
if rf and cfg ~= rf then return false end
if s.name and s.stamp and s.proto then
if cf and cf ~= s.country then return true end
if prf and prf ~= s.proto then return true end
r[#r+1] = {
['resolver'] = cfg,
['proto'] = s.proto,
['country'] = s.country,
['name'] = s.name,
['stamp'] = s.stamp,
['addrs'] = s.addrs,
['ports'] = s.ports,
}
end
end)
end
return r
end
function init(cursor)
uci_r = cursor or uci_r or uci.cursor()
uci_s = uci_r:substate()
return _M
end
function save(self, ...)
uci_r:save(...)
uci_r:load(...)
end
function commit(self, ...)
uci_r:commit(...)
uci_r:load(...)
end
function get_defaults()
return defaults()
end
@@ -0,0 +1,180 @@
<%-
local dc = require "luci.tools.dnscrypt".init()
local rsl = dc:resolvers_list(false)
local ctl = dc:country_list()
local prl = dc:proto_list()
local dnss = dc:dns_list()
local _, z
-%>
<script type="text/javascript">//<![CDATA[
var dnslist = <%= luci.http.write_json(dnss) %>;
function find_in_object(my_object, my_criteria){
return my_object.filter(function(obj) {
return Object.keys(my_criteria).every(function(c) {
return obj[c] == my_criteria[c];
});
});
}
function unique_list(data, id) {
if (data.length > 0) {
var result = ["ALL"];
data.forEach(function (elem) {
if (result.indexOf(elem[id]) === -1) {
result.push(elem[id]);
}
});
return result;
}
}
function cbi_set_values(id, vals) {
while (id.options[0])
id.remove(0);
for (var i = 0; vals && i < vals.length; i += 1)
{
// if (!vals[i+2])
// continue;
var opt = document.createElement('option');
opt.value = vals[i];
opt.text = vals[i];
id.add(opt);
}
id.vals = vals;
}
function cbi_init_dnslist(self, reset = false) {
id = "_newsrv";
var oproto = document.getElementById(id + '.proto');
var oresolvers = document.getElementById(id + '.resolvers');
var ocountry = document.getElementById(id + '.country');
var oname = document.getElementById(id + '.name');
var oaddrs = document.getElementById(id + '.addrs');
var oports = document.getElementById(id + '.ports');
var ohints = document.getElementById(id + '.hints');
var oaddbt = document.getElementById("cbi.cts.<%=self.config%>.<%=self.sectiontype%>.<%=section%>");
filter = {};
vproto = reset ? "ALL" : oproto.value;
vresolvers = reset ? "ALL" : oresolvers.value;
vcountry = reset ? "ALL" : ocountry.value;
vname = reset ? "ALL" : oname.value;
vaddrs = reset ? "ALL" : oaddrs.value;
vports = reset ? "ALL" : oports.value;
if (vproto != "ALL" && vproto != "") filter["proto"] = vproto;
if (vresolvers != "ALL" && vresolvers != "") filter["resolver"] = vresolvers;
if (vcountry != "ALL" && vcountry != "") filter["country"] = vcountry;
if (vname != "ALL" && vname != "") filter["name"] = vname;
if (vaddrs != "ALL" && vaddrs != "") filter["addrs"] = vaddrs;
if (vports != "ALL" && vports != "") filter["ports"] = vports;
var dst = find_in_object(dnslist, filter);
cbi_set_values(oproto, unique_list(dst, "proto"));
cbi_set_values(oresolvers, unique_list(dst, "resolver"));
cbi_set_values(ocountry, unique_list(dst, "country"));
cbi_set_values(oname, unique_list(dst, "name"));
cbi_set_values(oaddrs, unique_list(dst, "addrs"));
cbi_set_values(oports, unique_list(dst, "ports"));
if (dst.length === 1) {
vproto = dst[0]["proto"];
vresolvers = dst[0]["resolver"];
vcountry = dst[0]["country"];
vname = dst[0]["name"];
vaddrs = dst[0]["addrs"];
document.getElementById(id + '.alias').value = vname + "." + vcountry + "@" + vproto;
document.getElementById(id + '.stamp').value = dst[0]['stamp'];
}
oproto.value = vproto;
oresolvers.value = vresolvers;
ocountry.value = vcountry;
oname.value = vname;
oaddrs.value = vaddrs;
oports.value = vports;
oaddbt.disabled = ( dst.length !== 1);
ohints.innerHTML = "Filter: (" + String(dst.length) + ")" + JSON.stringify(filter) + '\n' + ((dst.length < 80) ? JSON.stringify(dst) : "<%:more than 100, try filter out to show details...%>");
}
//]]></script>
<div class="cbi-section cbi-tblsection">
<h3><%:DNS Resolver Filter%></h3>
<div class="table cbi-section-table">
<div class="tr cbi-section-table-titles">
<div class="th cbi-section-table-cell"><%:Resolvers%></div>
<div class="th cbi-section-table-cell"><%:Country%></div>
<div class="th cbi-section-table-cell"><%:Protocol%></div>
<div class="th cbi-section-table-cell"><%:Resolver%></div>
<div class="th cbi-section-table-cell"><%:Address%></div>
<div class="th cbi-section-table-cell"><%:Port%></div>
<div class="th cbi-section-table-cell cbi-section-actions"></div>
</div>
<div class="tr cbi-section-table-row">
<div class="td cbi-section-table-cell">
<select class="cbi-input-select" id="_newsrv.resolvers" name="_newsrv.resolvers">
<option value="ALL"><%:ALL%></option>
<% for _, z in ipairs(rsl) do -%><option value="<%=z%>"><%=z%></option><%- end %>
</select>
</div>
<div class="td cbi-section-table-cell">
<select class="cbi-input-select" id="_newsrv.country" name="_newsrv.country">
<option value="ALL"><%:ALL%></option>
<% for _, z in pairs(ctl) do -%><option value="<%=z%>"><%=z%></option><%- end %>
</select>
</div>
<div class="td cbi-section-table-cell">
<select class="cbi-input-select" id="_newsrv.proto" name="_newsrv.proto">
<option value="ALL"><%:ALL%></option>
<% for _, z in pairs(prl) do -%><option value="<%=z%>"><%=z%></option><%- end %>
</select>
</div>
<div class="td cbi-section-table-cell">
<select class="cbi-input-select" id="_newsrv.name" name="_newsrv.name">
<option value="ALL"><%:ALL%></option>
<% for _, z in pairs(dnss) do -%><option value="<%=z['name']%>"><%=z['name']%></option><%- end %>
</select>
</div>
<div class="td cbi-section-table-cell">
<select class="cbi-input-select" id="_newsrv.addrs" name="_newsrv.addrs">
<option value="ALL"><%:ALL%></option>
<% for _, z in pairs(dnss) do -%><option value="<%=z['addrs']%>"><%=z['addrs']%></option><%- end %>
</select>
</div>
<div class="td cbi-section-table-cell">
<select class="cbi-input-select" id="_newsrv.ports" name="_newsrv.ports">
<option value="ALL"><%:ALL%></option>
</select>
</div>
<div class="td cbi-section-table-cell nowrap cbi-section-actions"><div>
<input type="reset" class="cbi-button cbi-button-reset" name="_newsrv.reset" id="_newsrv.reset" value="<%:Reset%>" />
</div></div>
</div>
</div>
<div class="table cbi-section-table">
<div class="tr cbi-section-table-row">
<div class="td cbi-section-table-cell">
<input type="text" class="cbi-input-text" id="_newsrv.alias" name="_newsrv.alias" placeholder="<%:Alias%>" />
</div>
<div class="td cbi-section-table-cell">
<input type="text" class="cbi-input-text" id="_newsrv.stamp" name="_newsrv.stamp" data-optional="false" placeholder="<%:Stamp%>" />
</div>
<div class="td cbi-section-table-cell nowrap cbi-section-actions"><div>
<input type="submit" class="cbi-button cbi-button-add" name="cbi.cts.<%=self.config%>.<%=self.sectiontype%>.<%=section%>" id="cbi.cts.<%=self.config%>.<%=self.sectiontype%>.<%=section%>" value="<%:Add%>" />
</div></div>
</div>
</div>
</div>
<textarea class="cbi-input-textarea" style="width: 100%" id="_newsrv.hints" name="_newsrv.hints" data-update="change" rows="10" wrap="soft" readonly="true">sample</textarea>
<script type="text/javascript">//<![CDATA[
cbi_init_dnslist();
document.getElementById('_newsrv.country').addEventListener('change', function() {try{cbi_init_dnslist();}catch(e){}});
document.getElementById('_newsrv.resolvers').addEventListener('change', function() {try{cbi_init_dnslist();}catch(e){}});
document.getElementById('_newsrv.proto').addEventListener('change', function() {try{cbi_init_dnslist();}catch(e){}});
document.getElementById('_newsrv.name').addEventListener('change', function() {try{cbi_init_dnslist();}catch(e){}});
document.getElementById('_newsrv.addrs').addEventListener('change', function() {try{cbi_init_dnslist();}catch(e){}});
document.getElementById('_newsrv.ports').addEventListener('change', function() {try{cbi_init_dnslist();}catch(e){}});
document.getElementById('_newsrv.reset').addEventListener('click', function() {try{cbi_init_dnslist(true);}catch(e){};});
//]]></script>
@@ -0,0 +1,61 @@
<% if self:cfgvalue(self.section) then section = self.section; cfgs = self:cfgvalue(self.section)%>
<fieldset class="cbi-section">
<% if self.title and #self.title > 0 then -%>
<legend><%=self.title%></legend>
<%- end %>
<div class="cbi-section-node" id="cbi-<%=self.config%>-<%=section%>">
<% local isempty = true for i, val in ipairs(cfgs) do -%>
<% local cfg = luci.util.split(val, "|")[1]; local src = luci.util.split(val, "|")[2] -%>
<div class="cbi-value<% if i > #cfgs then -%> cbi-value-last<%- end%>" id="cbi-<%=cfg.."-source-urls"%>" data-index="<%=i%>" data-depends="[]">
<label class="cbi-value-title"<%= attr("for", "source") %>>
<a title="<%=translate('Go to relevant configuration page')%>" class="cbi-title-ref" href="<%=self.extedit % cfg%>">
<%-='%d) %s' % {i, cfg}-%>
</a>
</label>
<div class="cbi-value-field">
<%
write('Source: <a href="%s">%s</a>' % {src, src})
%>
</div>
</div>
<% isempty = false -%>
<%- end %>
<br />
<% if isempty then i = 1 -%>
<em><%:One or more requred configure is missing!%><br /><br /></em>
<%- end %>
<% if self.description and #self.description > 0 then -%>
<div class="cbi-section-descr"><%=self.description%></div>
<%- end %>
<div class="cbi-section-create">
<input type="text" class="cbi-section-create-name" id="cbi.cts.<%=self.config%>.new.source" name="cbi.cts.<%=self.config%>.new.source" data-type="uciname" data-optional="true" />
<input type="submit" class="cbi-button cbi-button-add" onclick="window.location.href='<%=self.extedit%>'.format(document.getElementById('cbi.cts.<%=self.config%>.new.source').value); return false" value="<%:Add%>/<%:Edit%><%:Resolvers%>" />
<% if self.invalid_cts then -%>
<br /><%:Invalid%></div>
<%- end %>
</div>
<br />
<% self:render_children(section, scope or {}) %>
</div>
<br />
<% if self.error and self.error[1] then -%>
<div class="cbi-section-error">
<ul><% for _, e in ipairs(self.error[1]) do -%>
<li>
<%- if e == "invalid" then -%>
<%:One or more configure contain invalid values!%>
<%- elseif e == "missing" then -%>
<%:One or more required configure is missing!%>
<%- else -%>
<%=pcdata(e)%>
<%- end -%>
</li>
<%- end %></ul>
</div>
<%- end %>
<% if message then %>
<div class="alert-message"><%=message%></div>
<% end %>
<br />
</fieldset>
<% end %>
@@ -0,0 +1,107 @@
<!-- ++ BEGIN ++ DNSCrypt Proxy ++ logsview.htm ++ -->
<%-
local values = self:formvalue(section)
if not values then
values = self:cfgvalue(section) or {self.default}
end
local function serialize_json(x, cb)
local rv, push = nil, cb
if not push then
rv = { }
push = function(tok) rv[#rv+1] = tok end
end
if x == nil then
push("null")
elseif type(x) == "table" then
push("[")
for k = 1, #x do
if k > 1 then
push(",")
end
serialize_json(x[k], push)
end
push("]")
else
push('"%s"' % tostring(x):gsub('["%z\1-\31\\]',
function(c) return '\\u%04x' % c:byte(1) end))
end
if not cb then
return table.concat(rv, "")
end
end
-%>
<%+cbi/valueheader%>
<select class="cbi-input-select" onchange="onclick_logview(this, false)"
<%=
attr("name", cbid) .. attr("id", cbid) .. attr("value", self.default) .. ifattr(self.size, "size")
%>
></select>
<script type="text/javascript">//<![CDATA[
(function() {
//var values = <%=serialize_json(values)%>;
var keylist = <%=serialize_json(self.keylist)%>;
var vallist = <%=serialize_json(self.vallist)%>;
var parent = document.getElementById("<%=cbid%>");
for (var j = 0; j < keylist.length; j++) {
var opt = document.createElement("option");
opt.value = keylist[j];
if (j == 0) {
opt.selected = "selected";
}
opt.appendChild(document.createTextNode(vallist[j]));
parent.appendChild(opt);
}
}());
//]]></script>
<script type="text/javascript">//<![CDATA[
function onclick_logview(id, bottom) {
// get elements
var txt = document.getElementById("<%=cbid%>.txt"); // TextArea
if ( !txt ) { return; } // security check
txt.value= "Reading: " + document.getElementById("<%=cbid%>").value
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "dnscrypt-proxy", "fileread")%>', {lfile: document.getElementById("<%=cbid%>").value} ,
function(x, rv) {
if(x.readyState === 4 ) {
if(x.status === 200) {try{res = JSON.parse(x.responseText);}catch(e){res=null;}
if(res !==null && typeof(res) === "object") txt.value = res[0]; if (bottom) txt.scrollTop = txt.scrollHeight; else txt.scrollTop = 0; txt.scrollLeft = 0;
} else {
txt.value = "XHR.get(<%=luci.dispatcher.build_url("admin", "services", "dnscrypt-proxy", "fileread")%>) Failed!";
}}});
return false;
}
//]]></script>
<%
-- one button on top, one at the buttom
%>
<br /><br />
<input class="cbi-button cbi-input-button" style="align: center; width: 100%" type="button" onclick='onclick_logview(this, false)'
<%=
attr("name", section) .. attr("id", cbid .. ".btn1") .. attr("value", self.inputtitle)
%> />
<br /><br />
<%
-- set a readable style taken from openwrt theme for textarea#syslog
-- in openwrt theme there are problems with a width of 100 so we check for theme and set to lower value
%>
<textarea style="width: <%if media == "/luci-static/openwrt.org" then%>98.7%<%else%>100%<%end%> ; min-height: 200px; border: 3px solid #cccccc; padding: 5px; font-family: monospace; resize: none;" wrap="off" readonly="readonly"
<%=
attr("name", cbid .. ".txt") .. attr("id", cbid .. ".txt") .. ifattr(self.rows, "rows")
%> >
<%-=pcdata(self:cfgvalue(section))-%>
</textarea>
<br /><br />
<%
-- one button on top, one at the buttom
%>
<input class="cbi-button cbi-input-button" style="align: center; width: 100%" type="button" onclick='onclick_logview(this, true)'
<%= attr("name", section) .. attr("id", cbid .. ".btn2") .. attr("value", self.inputtitle) %> />
<br /><br />
<%+cbi/valuefooter%>
<!-- ++ END ++ DNSCrypt Proxy ++ logsview.htm ++ -->
@@ -0,0 +1,103 @@
<%+cbi/valueheader%>
<%-
local util = require "luci.util"
local sys = require "luci.sys"
-%>
<script type="text/javascript">//<![CDATA[
<% if self.option == "dnscrypt_bin" then -%>
function update_bin(obtn, dataname, surl)
{
obtn.disabled = true;
ret = false;
alert(dataname+":\n" + surl);
murl = dataname;
var osta = document.getElementById(dataname+'-status'); XHR.get('<%=luci.dispatcher.build_url("admin", "services", "dnscrypt-proxy","update_c")%>',
{ set:"dnscrypt_bin", url:surl },
function(x, rv) {
if(x.readyState === 4 ) {
if(x.status === 200) {try{res = JSON.parse(x.responseText);}catch(e){res=null;}
if(res !==null && typeof(res) === "object") {osta.innerText = res.status; obtn.disabled = false; obtn.value = '<%= self.name or translate("Refresh Data")%>';obtn.onclick = function (){alert("<%:No new data!%>");}; return (rv.err === 0);}
}}});
return false;
}
function parse_gittag(gtag, obtn, osta, sprefix, scver)
{
ret = true;
tagv = gtag.name;
dlfile = 'dnscrypt-proxy-%s-%s.tar.gz'.format(sprefix, tagv);
obtn.title = "";
osta.innerHTML = '<font color="red">%s</font><br /><div class="cbi-section-descr"><pre>%s</pre></div>'.format(dlfile, gtag.body);
if(scver === tagv) {obtn.value = '<%:No new data!%>';}
else if(! gtag.assets) {osta.innerText = '<%:Refresh Error!%>'; ret = false;}
else {
_assets = gtag.assets;
osta.innerText = dlfile;
for (var idx = 0; idx < _assets.length; idx++) {
var dls=_assets[idx];
if(dls.name === dlfile) {
obtn.value = '<%:Update%> ' + tagv;
obtn.title = dls.browser_download_url;
obtn.onclick = function() { update_bin(this,'<%=self.option%>',this.title); };
obtn.disabled = false;
osta.innerHTML = '<a href="%s">%s</a>(%00.1fMB)<br /><div class="cbi-section-descr"><pre>%s</pre></div>'.format(dls.browser_download_url, dlfile, dls.size/1024/1024, gtag.body);
break;
}
}
}
return ret;
}
<%- end %>
function refresh_data_<%=self.option%>(btn,dataname)
{
btn.disabled = true;
btn.value = '<%:Refresh...%> ';
murl=dataname;
var s = document.getElementById(dataname+'-status');
if (dataname === "dnscrypt_bin") {
os = '<%=util.trim(sys.exec("uname")):lower()%>';
arch = '<%=util.trim(sys.exec("uname -m"))%>';
prefix = '%s_%s'.format(os, arch);
cver = '<%=util.trim(sys.exec("/usr/sbin/dnscrypt-proxy -version"))%>';
tag_url = 'https://api.github.com/repos/dnscrypt/dnscrypt-proxy/releases/latest';
s.innerHTML = ' current: dnscrypt-proxy %s-%s.tar.gz <a href="%s">release checking...</a>'.format(prefix, cver, tag_url);
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "dnscrypt-proxy","refresh_c")%>',null, function(x,rv) {
s.innerText = 'Test, %s readyState: %d, status: %d'.format(x.statusText, x.readyState, x.status);
x.open('GET', tag_url, true);
x.onreadystatechange = function() {
if(x.readyState === 4 && x.status === 200) {
try{res = JSON.parse(x.responseText);}catch(e){res=null;}
if(res !==null && typeof(res) === "object") {parse_gittag(res, btn, s, prefix, cver);}
} else {
s.innerHTML = '<font color="red">ReleaseTag, %s readyState: %d, status: %d</font>'.format(x.statusText, x.readyState, x.status);
btn.disabled = false;
btn.value = '<%= self.name or translate("Refresh Data")%>';
}};
x.send(null);
});
return false;
} else {
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "dnscrypt-proxy","refresh_c")%>',
{ set:murl },
function(x, rv) {
if(x.readyState === 4 ) {
if(x.status === 200) {try{res = JSON.parse(x.responseText);}catch(e){res=null;}
if(res !==null && typeof(res) === "object") {
var s = document.getElementById(dataname+'-status');
if (s) {
if (res.ret=="0") s.innerHTML ="<font color='green'>"+"<%:No new data!%> "+"</font>";
else if(res.ret=="-1") s.innerHTML ="<font color='red'>"+"<%:Refresh Error!%> "+"</font>";
else s.innerHTML ="<font color='green'>"+"<%:Refresh OK!%> "+"<%= self.ret or translate('Total Records:')%>"+res.ret+"</font>";
btn.disabled = false;
btn.value = '<%= self.name or translate('Refresh Data')%>';}}
}}});
return false;
}
}
//]]></script>
<input type="button" class="cbi-button cbi-input-reload" value="<%= self.name or translate('Refresh Data')%> " onclick="return refresh_data_<%=self.option%>(this,'<%=self.option%>')" />
<span id="<%=self.option%>-status"><em><%=self.value%></em></span>
<%+cbi/valuefooter%>
@@ -0,0 +1,22 @@
<%+cbi/valueheader%>
<script type="text/javascript">//<![CDATA[
function check_port(btn, func)
{
btn.disabled = true;
btn.value = '<%:Check...%>';
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "dnscrypt-proxy","resolve_c")%>',
{ set:func },
function(x, rv) {
if(x.readyState === 4 ) {
if(x.status === 200) {try{res = JSON.parse(x.responseText);}catch(e){res=null;}
if(res !==null && typeof(res) === "object") {var s = document.getElementById(func + '-status'); if (s) s.innerHTML =res.ret; btn.disabled = false; btn.value = '<%:Check Server%>';}
}}});
return false;
}
//]]></script>
<input type="button" class="cbi-button cbi-button-apply" value="<%:Check Server%>" onclick="return check_port(this,'<%=self.option%>')" />
<span id="<%=self.option%>-status"><em><%=self.value%></em></span>
<%+cbi/valuefooter%>
@@ -0,0 +1,22 @@
config source 'global'
option urls 'https://github.com/DNSCrypt/dnscrypt-resolvers/raw/master/v3/public-resolvers.md'
option details_json 'https://download.dnscrypt.info/dnscrypt-resolvers/json/public-resolvers.json'
option minisign_key 'RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3'
option cache_mode '1'
option selfsign '0'
config dnscrypt 'quad9_dnscrypt_ip6_filter_pri'
option name 'quad9-dnscrypt-ip6-filter-pri'
option proto 'DNSCrypt'
option addrs '[2620:fe::fe:9]'
option ports '8443'
option stamp 'sdns://AQMAAAAAAAAAFFsyNjIwOmZlOjpmZTo5XTo4NDQzIGfIR7jIdYzRICRVQ751Z0bfNN8dhMALjEcDaN-CHYY-GTIuZG5zY3J5cHQtY2VydC5xdWFkOS5uZXQ'
option description 'Quad9 (anycast) dnssec/no-log/filter 2620:fe::fe:9'
option location '37.751000,-97.822000'
option country 'United States'
option dnssec '1'
option ipv6 '1'
option nolog '1'
option nofilter '0'
@@ -0,0 +1,25 @@
#!/bin/sh
[ ! -f "/usr/share/ucitrack/luci-app-dnscrypt_proxy.json" ] && {
cat > /usr/share/ucitrack/luci-app-dnscrypt_proxy.json << EEOF
{
"config": "dnscrypt_proxy",
"init": "dnscrypt_proxy"
}
EEOF
}
uci -q batch <<-EOF >/dev/null
delete ucitrack.@dnscrypt_proxy[-1]
add ucitrack dnscrypt_proxy
set ucitrack.@dnscrypt_proxy[-1].init=dnscrypt-proxy
commit ucitrack
delete firewall.dnscrypt_proxy
set firewall.dnscrypt_proxy=include
set firewall.dnscrypt_proxy.type=script
set firewall.dnscrypt_proxy.path=/var/etc/dnscrypt-proxy.include
set firewall.dnscrypt_proxy.reload=1
commit firewall
EOF
rm -f /tmp/luci-indexcache
exit 0