update 2026-10-29 18:46:39

This commit is contained in:
action committed 2026-10-29 18:46:39 +08:00
1 parent e2f86e09f4
commit 95f76f26d3
5034 files changed
+2043234

No files matched your search

@@ -0,0 +1,11 @@
config AdGuardHome 'AdGuardHome'
option enabled '0'
option httpport '3000'
option redirect 'none'
option configpath '/etc/AdGuardHome.yaml'
option workdir '/tmp/AdGuardHome'
option logfile '/tmp/AdGuardHome.log'
option verbose '0'
option binpath '/usr/bin/AdGuardHome/AdGuardHome'
list wan_ifname 'eth0'
+653
View File
@@ -0,0 +1,653 @@
#!/bin/sh /etc/rc.common
USE_PROCD=1
START=95
STOP=01
CONFIGURATION=AdGuardHome
NFT_RULES_FILE="/var/etc/adguardhome.nft"
NFT_RULES_TPL="/usr/share/AdGuardHome/adguardhome.nft.tpl"
NFT_TABLE="adguardhome"
EXTRA_COMMANDS="do_redirect force_reload isrunning"
EXTRA_HELP=" do_redirect 0 or 1\
force_reload
isrunning"
get_wan_return_line() {
local wan
wan="$(uci -q get AdGuardHome.AdGuardHome.wan_ifname 2>/dev/null)"
[ -z "$wan" ] && { echo ""; return 0; }
local wan_set
wan_set="\"$(printf '%s' "$wan" | sed 's/ /\", \"/g')\""
echo "iifname { $wan_set } return"
}
set_nft_redirect() {
local port="$1"
case "$port" in
*[!0-9]*|"") return 1 ;;
esac
[ ! -f "$NFT_RULES_TPL" ] && return 1
local return_line
return_line="$(get_wan_return_line)"
awk -v port="$port" -v wan_line="$return_line" '{
gsub("__AGH_PORT__", port)
gsub("__AGH_WAN_RETURN_LINE__", wan_line)
print
}' "$NFT_RULES_TPL" > "$NFT_RULES_FILE" || return 1
[ ! -s "$NFT_RULES_FILE" ] && return 1
nft delete table inet "$NFT_TABLE" 2>/dev/null
nft -f "$NFT_RULES_FILE" 2>/dev/null
}
clear_nft_redirect() {
uci -q delete firewall.adguardhome_nft_include
uci -q commit firewall
[ -f "$NFT_RULES_FILE" ] && rm -f "$NFT_RULES_FILE"
nft delete table inet "$NFT_TABLE" 2>/dev/null
}
wait_for_pid_exit() {
local pid="$1" attempts=0
case "$pid" in ''|*[!0-9]*) return 0 ;; esac
while kill -0 "$pid" 2>/dev/null; do
[ "$attempts" -lt 15 ] || return 1
sleep 1
attempts=$((attempts + 1))
done
}
wait_for_dns_port_release() {
local port="$1" attempts=0 tables="" table busy
for table in /proc/net/tcp /proc/net/tcp6 /proc/net/udp /proc/net/udp6; do
[ -r "$table" ] && tables="$tables $table"
done
[ -n "$tables" ] || return 1
port="$(printf '%04X' "$port")" || return 1
while :; do
busy="$(awk -v port="$port" '
FNR > 1 {
split($2, address, ":")
if (toupper(address[2]) == port &&
((FILENAME ~ /tcp/ && $4 == "0A") ||
(FILENAME ~ /udp/ && $4 == "07"))) found = 1
}
END { print found ? 1 : 0 }
' $tables)" || return 1
[ "$busy" = "0" ] && return 0
[ "$attempts" -lt 15 ] || return 1
sleep 1
attempts=$((attempts + 1))
done
}
restart_dnsmasq() {
local released_port="$1" service pids pid
service="$(ubus call service list '{"name":"dnsmasq"}' 2>/dev/null)" || return 1
pids="$(printf '%s' "$service" | jsonfilter -e '@.dnsmasq.instances.*.pid')"
/etc/init.d/dnsmasq stop 8>&- 1000>&- || return 1
for pid in $pids; do
if ! wait_for_pid_exit "$pid"; then
logger -t AdGuardHome 'dnsmasq did not stop; refusing to reuse its old jail.'
return 1
fi
done
if [ -n "$released_port" ] && ! wait_for_dns_port_release "$released_port"; then
logger -t AdGuardHome "DNS port $released_port is still occupied after stopping dnsmasq."
/etc/init.d/dnsmasq start 8>&- 1000>&-
return 1
fi
/etc/init.d/dnsmasq start 8>&- 1000>&- || return 1
if [ -n "$released_port" ] && ! wait_for_dns_port_release "$released_port"; then
logger -t AdGuardHome "DNS port $released_port was occupied again after starting dnsmasq."
return 1
fi
return 0
}
save_dnsmasq_upstream_config() {
local backup="AdGuardHome.dnsmasq_backup" option value server
[ "$(uci -q get "$backup.ready")" = "1" ] && return 0
uci -q delete "$backup"
uci set "$backup=dnsmasq_backup" || return 1
value="$(uci -q get dhcp.@dnsmasq[0].server)"
for server in $value; do
uci add_list "$backup.server=$server" || return 1
done
for option in resolvfile noresolv; do
if value="$(uci -q get "dhcp.@dnsmasq[0].$option")"; then
uci set "$backup.$option=$value" || return 1
fi
done
uci set "$backup.ready=1" || return 1
if ! uci commit AdGuardHome; then
uci -q revert "$backup"
return 1
fi
}
apply_dnsmasq_config() {
[ "${AGH_DEFER_DNSMASQ_RESTART:-0}" = "1" ] && return 0
restart_dnsmasq "$@"
}
is_scoped_dnsmasq_server() {
local domains="${1%/*}"
case "$1" in
/*) case "$domains/" in */\#/*) return 1 ;; esac; return 0 ;;
esac
return 1
}
set_forward_dnsmasq() (
set -f
local addr="127.0.0.1#$1" servers desired server
if [ "$(uci -q get AdGuardHome.dnsmasq_backup.ready)" != "1" ] &&
[ "$2" = "dnsmasq-upstream" ] && [ "$3" = "1" ]; then
stop_forward_dnsmasq "$4" || return 1
fi
save_dnsmasq_upstream_config || return 1
servers="$(uci -q get dhcp.@dnsmasq[0].server)"
desired="$addr"
for server in $servers; do
is_scoped_dnsmasq_server "$server" && desired="$desired $server"
done
if [ "$servers" = "$desired" ] && [ "$(uci -q get dhcp.@dnsmasq[0].noresolv)" = "1" ]; then
return 0
fi
uci -q delete dhcp.@dnsmasq[0].server
for server in $desired; do
uci add_list "dhcp.@dnsmasq[0].server=$server" || return 1
done
uci set dhcp.@dnsmasq[0].noresolv=1 || return 1
uci commit dhcp || return 1
apply_dnsmasq_config >/dev/null 2>&1
)
stop_forward_dnsmasq() (
set -f
local backup="AdGuardHome.dnsmasq_backup" option value server
local addr="127.0.0.1#$1" found=0 has_general=0
if [ "$(uci -q get "$backup.ready")" = "1" ]; then
uci -q delete dhcp.@dnsmasq[0].server
value="$(uci -q get "$backup.server")"
for server in $value; do
uci add_list "dhcp.@dnsmasq[0].server=$server" || return 1
done
for option in resolvfile noresolv; do
if value="$(uci -q get "$backup.$option")"; then
uci set "dhcp.@dnsmasq[0].$option=$value" || return 1
else
uci -q delete "dhcp.@dnsmasq[0].$option"
fi
done
uci commit dhcp || return 1
apply_dnsmasq_config >/dev/null 2>&1 || return 1
[ "${AGH_DEFER_DNSMASQ_RESTART:-0}" = "1" ] && return 0
uci -q delete "$backup"
uci commit AdGuardHome
return $?
fi
value="$(uci -q get dhcp.@dnsmasq[0].server)"
for server in $value; do
if [ "$server" = "$addr" ]; then
found=1
elif ! is_scoped_dnsmasq_server "$server"; then
has_general=1
fi
done
[ "$found" = "1" ] || return 0
uci -q del_list "dhcp.@dnsmasq[0].server=$addr" || return 1
if [ "$has_general" = "0" ]; then
if ! uci -q get dhcp.@dnsmasq[0].resolvfile >/dev/null; then
value="/tmp/resolv.conf.d/resolv.conf.auto"
[ -f "$value" ] || value="/tmp/resolv.conf.auto"
uci set "dhcp.@dnsmasq[0].resolvfile=$value" || return 1
fi
uci -q delete dhcp.@dnsmasq[0].noresolv
fi
logger -t AdGuardHome 'No DNS snapshot from previous version; removed old upstream, original resolver settings are unavailable.'
uci commit dhcp || return 1
apply_dnsmasq_config >/dev/null 2>&1
)
stop_core() {
local service instance pid
service="$(ubus call service list '{"name":"AdGuardHome"}' 2>/dev/null)" || return 1
instance="$(printf '%s' "$service" | jsonfilter -e '@.AdGuardHome.instances.instance1')"
[ -n "$instance" ] || return 0
pid="$(printf '%s' "$instance" | jsonfilter -e '@.pid')"
ubus call service delete '{"name":"AdGuardHome","instance":"instance1"}' >/dev/null || return 1
if ! wait_for_pid_exit "$pid"; then
logger -t AdGuardHome 'Core did not stop; DNS port settings were not changed.'
return 1
fi
}
agh_reload() {
[ "${AGH_REDIRECT_RELOAD:-1}" = "1" ] || return 0
if [ "${AGH_DEFER_DNSMASQ_RESTART:-0}" = "1" ]; then
AGH_RELOAD_PENDING=1
return 0
fi
ubus call service event '{"type":"config.change","data":{"package":"AdGuardHome"}}' >/dev/null 2>&1
}
rm_port53() {
local configpath
config_get configpath $CONFIGURATION configpath "/etc/AdGuardHome.yaml"
local AdGuardHome_PORT dnsmasq_port
AdGuardHome_PORT=$(config_editor "dns.port" "" "$configpath" "1")
dnsmasq_port=$(uci get dhcp.@dnsmasq[0].port 2>/dev/null)
[ -z "$dnsmasq_port" ] && dnsmasq_port="53"
if [ "$dnsmasq_port" = "$AdGuardHome_PORT" ]; then
[ "$dnsmasq_port" = "53" ] && dnsmasq_port="1745"
elif [ "$dnsmasq_port" = "53" ]; then
return
fi
stop_core || return 1
config_editor "dns.port" "$dnsmasq_port" "$configpath"
[ "$(config_editor "dns.port" "" "$configpath" "1")" = "$dnsmasq_port" ] || return 1
uci set dhcp.@dnsmasq[0].port="53" || return 1
uci commit dhcp || return 1
apply_dnsmasq_config "$dnsmasq_port" || return 1
agh_reload
}
use_port53() {
local configpath
config_get configpath $CONFIGURATION configpath "/etc/AdGuardHome.yaml"
local AdGuardHome_PORT dnsmasq_port
AdGuardHome_PORT=$(config_editor "dns.port" "" "$configpath" "1")
dnsmasq_port=$(uci get dhcp.@dnsmasq[0].port 2>/dev/null)
[ -z "$dnsmasq_port" ] && dnsmasq_port="53"
if [ "$dnsmasq_port" = "$AdGuardHome_PORT" ]; then
[ "$dnsmasq_port" = "53" ] && AdGuardHome_PORT="1745"
elif [ "$AdGuardHome_PORT" = "53" ]; then
return
fi
stop_core || return 1
config_editor "dns.port" "53" "$configpath"
[ "$(config_editor "dns.port" "" "$configpath" "1")" = "53" ] || return 1
uci set dhcp.@dnsmasq[0].port="$AdGuardHome_PORT" || return 1
uci commit dhcp || return 1
apply_dnsmasq_config "$dnsmasq_port" || return 1
agh_reload
}
mark_redirect_flag() {
local enabled="$1" redirect="$2" agh_port="$3"
local configpath
config_get configpath $CONFIGURATION configpath "/etc/AdGuardHome.yaml"
local flag=0
if [ "$enabled" = "1" ] && [ "$redirect" != "none" ]; then
flag=1
if [ "$redirect" = "redirect" ]; then
nft list table inet "$NFT_TABLE" >/dev/null 2>&1 || flag=0
elif [ "$redirect" = "dnsmasq-upstream" ]; then
local s
s="$(uci -q get dhcp.@dnsmasq[0].server | tr '\n' ' ')"
printf '%s\n' "$s" | grep -q -E "(^|[[:space:]])127\.0\.0\.1#${agh_port}([[:space:]]|$)" || flag=0
elif [ "$redirect" = "exchange" ]; then
local cfgp dport
cfgp="$(config_editor "dns.port" "" "$configpath" "1")"
dport="$(uci -q get dhcp.@dnsmasq[0].port)"
if [ "$cfgp" != "53" ] || [ "$dport" = "53" ] ; then
flag=0
fi
fi
fi
echo -n "$flag" > /var/run/AdGredir
}
finish_dnsmasq_config() {
case "$old_redirect:$redirect" in
*dnsmasq-upstream*|*exchange*)
if [ "$dnsmasq_before" != "$(uci -q export dhcp)" ] || [ "$previous_flag" != "1" ] || [ "$enabled" = "0" ]; then
local released_port=""
if [ "$enabled" = "1" ] && [ "$redirect" = "exchange" ]; then
released_port=53
elif [ "$dnsmasq_previous_port" != "$(uci -q get dhcp.@dnsmasq[0].port)" ]; then
released_port="$dnsmasq_previous_port"
fi
restart_dnsmasq "$released_port" || return 1
fi
;;
esac
if { [ "$enabled" = "0" ] || [ "$redirect" != "dnsmasq-upstream" ]; } &&
[ "$(uci -q get AdGuardHome.dnsmasq_backup.ready)" = "1" ]; then
uci -q delete AdGuardHome.dnsmasq_backup
uci commit AdGuardHome || return 1
fi
return 0
}
_do_redirect() (
exec 8>/var/lock/AdGuardHome-redirect.lock || return 1
flock -w 10 8 || return 1
local AGH_DEFER_DNSMASQ_RESTART=1 AGH_RELOAD_PENDING=0
local dnsmasq_before dnsmasq_previous_port previous_flag
dnsmasq_before="$(uci -q export dhcp)" || return 1
dnsmasq_previous_port="$(uci -q get dhcp.@dnsmasq[0].port)"
dnsmasq_previous_port="${dnsmasq_previous_port:-53}"
previous_flag="$(cat /var/run/AdGredir 2>/dev/null)"
echo -n "0" > /var/run/AdGredir
local section="$CONFIGURATION"
args=""
ipv6_server=1
tcp_server=0
enabled="$1"
AGH_REDIRECT_RELOAD="${2:-$enabled}"
config_get configpath $CONFIGURATION configpath "/etc/AdGuardHome.yaml"
AdGuardHome_PORT="$(config_editor "dns.port" "" "$configpath" "1")"
[ -z "$AdGuardHome_PORT" ] && AdGuardHome_PORT="0"
config_get redirect "$section" redirect "none"
config_get old_redirect "$section" old_redirect "none"
config_get old_port "$section" old_port "0"
config_get old_enabled "$section" old_enabled "0"
uci get dhcp.@dnsmasq[0].port >/dev/null 2>&1 || uci set dhcp.@dnsmasq[0].port="53" >/dev/null 2>&1
uci commit dhcp
if [ "$old_enabled" = "1" ] && [ "$old_redirect" = "exchange" ]; then
AdGuardHome_PORT="$(uci get dhcp.@dnsmasq[0].port 2>/dev/null)"
fi
if [ "$old_redirect" != "$redirect" ] || [ "$old_port" != "$AdGuardHome_PORT" ] || { [ "$old_enabled" = "1" ] && [ "$enabled" = "0" ]; }; then
if [ "$old_redirect" != "none" ]; then
if [ "$old_redirect" = "redirect" ] && [ "$old_port" != "0" ]; then
clear_nft_redirect
elif [ "$old_redirect" = "dnsmasq-upstream" ]; then
if [ "$old_enabled" = "1" ] || [ "$(uci -q get AdGuardHome.dnsmasq_backup.ready)" = "1" ]; then
if ! stop_forward_dnsmasq "$old_port"; then
echo -n "0" > /var/run/AdGredir
return 1
fi
fi
old_enabled=0
elif [ "$old_redirect" = "exchange" ]; then
if ! rm_port53; then
echo -n "0" > /var/run/AdGredir
return 1
fi
fi
fi
elif [ "$old_enabled" = "1" ] && [ "$enabled" = "1" ]; then
if [ "$old_redirect" = "redirect" ] && [ "$old_port" != "0" ]; then
clear_nft_redirect
fi
fi
uci delete AdGuardHome.@AdGuardHome[0].old_redirect 2>/dev/null
uci delete AdGuardHome.@AdGuardHome[0].old_port 2>/dev/null
uci delete AdGuardHome.@AdGuardHome[0].old_enabled 2>/dev/null
uci add_list AdGuardHome.@AdGuardHome[0].old_redirect="$redirect" 2>/dev/null
uci add_list AdGuardHome.@AdGuardHome[0].old_port="$AdGuardHome_PORT" 2>/dev/null
uci add_list AdGuardHome.@AdGuardHome[0].old_enabled="$enabled" 2>/dev/null
uci commit AdGuardHome
if [ "$enabled" = "0" ] || [ "$AdGuardHome_PORT" = "0" ]; then
finish_dnsmasq_config || return 1
echo -n "0" > /var/run/AdGredir
return 1
fi
if [ "$redirect" = "redirect" ]; then
set_nft_redirect "$AdGuardHome_PORT"
elif [ "$redirect" = "dnsmasq-upstream" ]; then
if ! set_forward_dnsmasq "$AdGuardHome_PORT" "$old_redirect" "$old_enabled" "$old_port"; then
echo -n "0" > /var/run/AdGredir
return 1
fi
elif [ "$redirect" = "exchange" ] && [ "$(uci get dhcp.@dnsmasq[0].port 2>/dev/null)" = "53" ]; then
if ! use_port53; then
echo -n "0" > /var/run/AdGredir
return 1
fi
fi
finish_dnsmasq_config || return 1
mark_redirect_flag "$enabled" "$redirect" "$AdGuardHome_PORT" || return 1
AGH_DEFER_DNSMASQ_RESTART=0
[ "$AGH_RELOAD_PENDING" = "0" ] || agh_reload
return 0
)
do_redirect() {
config_load "${CONFIGURATION}"
_do_redirect "$1"
}
service_triggers() {
procd_add_reload_trigger "$CONFIGURATION"
}
isrunning() {
config_load "${CONFIGURATION}"
_isrunning
local r=$?
[ "$r" = "0" ] && echo "running" || ([ "$r" = "1" ] && echo "not run" || echo "no bin")
return $r
}
_isrunning() {
config_get binpath $CONFIGURATION binpath "/usr/bin/AdGuardHome/AdGuardHome"
[ ! -f "$binpath" ] && return 2
pgrep -f "$(basename "$binpath")" >/dev/null 2>&1 && return 0
return 1
}
force_reload() {
config_load "${CONFIGURATION}"
_isrunning && procd_send_signal "$CONFIGURATION" || start
}
get_tz() {
SET_TZ=""
[ -e "/etc/localtime" ] && return
for tzfile in /etc/TZ /var/etc/TZ; do
[ -e "$tzfile" ] && tz="$(cat "$tzfile" 2>/dev/null)" && break
done
[ -n "$tz" ] && SET_TZ=$tz
}
get_filesystem() {
echo "$1" | awk '
BEGIN{
while (("mount"| getline ret) > 0) {
split(ret,d); fs[d[3]]=d[5];
m=index(d[1],":");
if (m==0) { pt[d[3]]=d[1] } else { pt[d[3]]=substr(d[1],m+1) }
}
}
{
split($0,d,"/");
if ("/" in fs) { result1=fs["/"]; }
if ("/" in pt) { result2=pt["/"]; }
for (i=2;i<=length(d);i++) {
p[i]=p[i-1]"/"d[i];
if (p[i] in fs) { result1=fs[p[i]]; result2=pt[p[i]]; }
}
if (result2 in fs) { result=fs[result2] } else { result=result1 }
print(result);
}'
}
config_editor() {
awk -v yaml="$1" -v value="$2" -v file="$3" -v ro="$4" '
BEGIN{split(yaml,part,"\.");s="";i=1;l=length(part);}
{
if (match($0,s""part[i]":")) {
if (i==l) {
split($0,t,": ");
if (ro==""){
system("sed -i '\''"FNR"c\\" t[1] ": " value "'\'' " file);
} else {
print(t[2]);
}
exit;
}
s=s"[- ]{2}";
i++;
}
}' "$3"
}
boot_service() {
rm -f /var/run/AdGserverdis 2>/dev/null
config_load "${CONFIGURATION}"
config_get binpath $CONFIGURATION binpath "/usr/bin/AdGuardHome/AdGuardHome"
[ -f "$binpath" ] && start_service
}
start_service() {
stop_core || return 1
rm -f /var/run/AdGserverdis 2>/dev/null
config_load "${CONFIGURATION}"
config_get hashpass $CONFIGURATION hashpass ""
config_get configpath $CONFIGURATION configpath "/etc/AdGuardHome.yaml"
if [ -n "$hashpass" ]; then
config_editor "users.password" "$hashpass" "$configpath"
uci set $CONFIGURATION.$CONFIGURATION.hashpass=""
uci commit $CONFIGURATION
fi
local enabled
config_get_bool enabled $CONFIGURATION enabled 0
if [ "$enabled" = "0" ]; then
_do_redirect 0
return
fi
config_get workdir $CONFIGURATION workdir "/usr/bin/AdGuardHome"
mkdir -p "$workdir/data"
local cwdfs
cwdfs=$(get_filesystem "$workdir")
echo "workdir is a $cwdfs filesystem"
if [ "$cwdfs" = "jffs2" ]; then
echo "fs error ln db to tmp $workdir $cwdfs"
logger "AdGuardHome" "warning db redirect to tmp"
touch "$workdir/data/stats.db"
if [ ! -L "$workdir/data/stats.db" ]; then
mv -f "$workdir/data/stats.db" /tmp/stats.db 2>/dev/null
ln -s /tmp/stats.db "$workdir/data/stats.db" 2>/dev/null
fi
touch "$workdir/data/sessions.db"
if [ ! -L "$workdir/data/sessions.db" ]; then
mv -f "$workdir/data/sessions.db" /tmp/sessions.db 2>/dev/null
ln -s /tmp/sessions.db "$workdir/data/sessions.db" 2>/dev/null
fi
fi
local ADDITIONAL_ARGS=""
config_get binpath $CONFIGURATION binpath "/usr/bin/AdGuardHome/AdGuardHome"
mkdir -p "${binpath%/*}"
ADDITIONAL_ARGS="$ADDITIONAL_ARGS -c $configpath"
ADDITIONAL_ARGS="$ADDITIONAL_ARGS -w $workdir"
config_get httpport $CONFIGURATION httpport 3000
ADDITIONAL_ARGS="$ADDITIONAL_ARGS -p $httpport"
printf '%s\n' "$binpath" "$configpath" > /lib/upgrade/keep.d/luci-app-adguardhome
config_get logfile $CONFIGURATION logfile ""
if [ -n "$logfile" ]; then
ADDITIONAL_ARGS="$ADDITIONAL_ARGS -l $logfile"
fi
if [ ! -f "$binpath" ]; then
_do_redirect 0
/usr/share/AdGuardHome/update_core.sh >/dev/null 2>&1 &
exit 0
fi
config_get_bool verbose $CONFIGURATION verbose 0
if [ "$verbose" -eq 1 ]; then
ADDITIONAL_ARGS="$ADDITIONAL_ARGS -v"
fi
if [ "$(uci -q get bypass.@global[0].global_server)" ] && [ "$(uci -q get bypass.@global[0].adguardhome)" = "1" ] && [ "$(uci -q get dhcp.@dnsmasq[0].port)" = "53" ]; then
uci -q set AdGuardHome.AdGuardHome.redirect='exchange'
uci -q commit AdGuardHome
config_load "${CONFIGURATION}"
fi
if [ -f "$configpath" ]; then
if ! _do_redirect 1 0; then
[ "$(config_editor "dns.port" "" "$configpath" "1")" = "0" ] || return 1
fi
else
_do_redirect 0 0
fi
procd_open_instance
get_tz
if [ -n "$SET_TZ" ]; then
procd_set_param env TZ="$SET_TZ"
fi
procd_set_param respawn ${respawn_threshold:-3600} ${respawn_timeout:-5} ${respawn_retry:-5}
procd_set_param limits core="unlimited" nofile="65535 65535"
procd_set_param stderr 1
procd_set_param command "$binpath" $ADDITIONAL_ARGS
procd_set_param file "$configpath" "/etc/hosts" "/etc/config/AdGuardHome"
procd_close_instance
if [ ! -f "$configpath" ]; then
config_get "redirect" "AdGuardHome" "redirect" "none"
if [ "$redirect" != "none" ]; then
procd_open_instance "waitconfig"
procd_set_param command "/usr/share/AdGuardHome/watchconfig.sh"
procd_close_instance
echo "no config start watching"
fi
fi
echo "AdGuardHome service enabled"
echo "luci enable switch=$enabled"
}
reload_service() {
rm -f /var/run/AdGlucitest 2>/dev/null
echo "AdGuardHome reloading"
start
}
stop_service() {
stop_core || return 1
config_load "${CONFIGURATION}"
_do_redirect 0
echo "AdGuardHome service disabled"
touch /var/run/AdGserverdis
}
boot() {
rc_procd boot_service "$@"
eval "type service_started" 2>/dev/null >/dev/null && service_started
}
@@ -0,0 +1,14 @@
#!/bin/sh
chmod +x /etc/init.d/AdGuardHome
uci -q batch <<-EOF >/dev/null 2>&1
delete ucitrack.@AdGuardHome[-1]
add ucitrack AdGuardHome
set ucitrack.@AdGuardHome[-1].init=AdGuardHome
commit ucitrack
EOF
# Remove the former menu and both Lua and ucode menu caches.
rm -f /usr/share/luci/menu.d/luci-app-adguardhome-js.json
rm -f /tmp/luci-indexcache /tmp/luci-indexcache.*
rm -f /tmp/luci-modulecache/*
exit 0
@@ -0,0 +1 @@
/etc/AdGuardHome.yaml
@@ -0,0 +1,189 @@
# 模板用户名为 admin 密码为 admin
# The username is admin and the password is admin.
# 保存并应用就可以直接用的配置模板文件
# a template config can be use with a apply
http:
pprof:
port: 6060
enabled: false
address: 0.0.0.0:3000
session_ttl: 720h
users:
- name: admin
password: $2y$10$iGkTW98QtGEZn4I/GC0J4eQhfuLQ53v7L4DDVauEJyWylJrXWwWeO
auth_attempts: 5
block_auth_min: 15
http_proxy: ""
language: ""
theme: auto
dns:
bind_hosts:
- 0.0.0.0
port: 3053
anonymize_client_ip: false
ratelimit: 20
ratelimit_subnet_len_ipv4: 24
ratelimit_subnet_len_ipv6: 56
ratelimit_whitelist: []
refuse_any: true
upstream_dns:
- https://dns.cloudflare.com/dns-query
- '#'
- '# 对 ts.net 相关域名启用 DNS 分流, 交由 Tailscale 解析, 如不使用 Tailscale 可以删除.'
- '# Enable DNS conditional forwarding for ts.net via Tailscale; remove if Tailscale is not used.'
- '[/ts.net/]100.100.100.100'
upstream_dns_file: ""
bootstrap_dns:
- 1.1.1.1
- 1.0.0.1
fallback_dns:
- quic://dns.adguard-dns.com
upstream_mode: parallel
fastest_timeout: 1s
allowed_clients: []
disallowed_clients: []
blocked_hosts:
- version.bind
- id.server
- hostname.bind
trusted_proxies:
- 127.0.0.0/8
- ::1/128
cache_enabled: true
cache_size: 4194304
cache_ttl_min: 0
cache_ttl_max: 0
cache_optimistic: false
cache_optimistic_answer_ttl: 30s
cache_optimistic_max_age: 12h
bogus_nxdomain: []
aaaa_disabled: false
enable_dnssec: false
edns_client_subnet:
custom_ip: ""
enabled: false
use_custom: false
max_goroutines: 300
handle_ddr: true
ipset: []
ipset_file: ""
bootstrap_prefer_ipv6: false
upstream_timeout: 10s
private_networks: []
use_private_ptr_resolvers: true
local_ptr_upstreams: []
use_dns64: false
dns64_prefixes: []
serve_http3: false
use_http3_upstreams: false
serve_plain_dns: true
hostsfile_enabled: true
pending_requests:
enabled: true
tls:
enabled: false
server_name: ""
force_https: false
port_https: 443
port_dns_over_tls: 853
port_dns_over_quic: 853
port_dnscrypt: 0
dnscrypt_config_file: ""
allow_unencrypted_doh: false
certificate_chain: ""
private_key: ""
certificate_path: ""
private_key_path: ""
strict_sni_check: false
querylog:
dir_path: ""
ignored: []
interval: 2160h
size_memory: 1000
enabled: true
file_enabled: true
statistics:
dir_path: ""
ignored: []
interval: 24h
enabled: true
filters:
- enabled: true
url: https://adguardteam.github.io/HostlistsRegistry/assets/filter_1.txt
name: AdGuard DNS filter
id: 1
whitelist_filters: []
user_rules: []
dhcp:
enabled: false
interface_name: ""
local_domain_name: lan
dhcpv4:
gateway_ip: ""
subnet_mask: ""
range_start: ""
range_end: ""
lease_duration: 86400
icmp_timeout_msec: 1000
options: []
dhcpv6:
range_start: ""
lease_duration: 86400
ra_slaac_only: false
ra_allow_slaac: false
filtering:
blocking_ipv4: ""
blocking_ipv6: ""
blocked_services:
schedule:
time_zone: UTC
ids: []
protection_disabled_until: null
safe_search:
enabled: false
bing: true
duckduckgo: true
ecosia: true
google: true
pixabay: true
yandex: true
youtube: true
blocking_mode: default
parental_block_host: family-block.dns.adguard.com
safebrowsing_block_host: standard-block.dns.adguard.com
rewrites: []
safe_fs_patterns:
- /tmp/AdGuardHome/userfilters/*
safebrowsing_cache_size: 1048576
safesearch_cache_size: 1048576
parental_cache_size: 1048576
cache_time: 30
filters_update_interval: 24
blocked_response_ttl: 10
filtering_enabled: true
rewrites_enabled: true
parental_enabled: false
safebrowsing_enabled: false
protection_enabled: true
clients:
runtime_sources:
whois: true
arp: true
rdns: true
dhcp: true
hosts: true
persistent: []
log:
enabled: true
file: ""
max_backups: 0
max_size: 100
max_age: 3
compress: false
local_time: false
verbose: false
os:
group: ""
user: ""
rlimit_nofile: 0
schema_version: 32
@@ -0,0 +1,10 @@
table inet adguardhome {
chain prerouting {
type nat hook prerouting priority dstnat; policy accept;
__AGH_WAN_RETURN_LINE__
fib daddr type local udp dport 53 redirect to :__AGH_PORT__
fib daddr type local tcp dport 53 redirect to :__AGH_PORT__
}
}
@@ -0,0 +1,20 @@
#!/bin/sh
PATH="/usr/sbin:/usr/bin:/sbin:/bin"
logread -e AdGuardHome > /tmp/AdGuardHometmp.log
logread -e AdGuardHome -f >> /tmp/AdGuardHometmp.log &
pid=$!
echo "1" > /var/run/AdGuardHomesyslog
while true
do
sleep 12
watchdog=$(cat /var/run/AdGuardHomesyslog 2>/dev/null)
if [ "${watchdog}x" = "0x" ]; then
kill "$pid"
rm /tmp/AdGuardHometmp.log
rm /var/run/AdGuardHomesyslog
exit 0
else
echo "0" > /var/run/AdGuardHomesyslog
fi
done
@@ -0,0 +1,164 @@
#!/bin/sh
PATH="/usr/sbin:/usr/bin:/sbin:/bin"
update_mode=$1
# Keep the lock file: unlinking it would allow a second lock on a new inode.
exec 9>/var/lock/AdGuardHome-update.lock || exit 1
# Allow a brief status probe to finish before treating the lock as busy.
if ! flock -w 1 9; then
printf '%s\n' '已经有更新任务正在运行,请等待其结束。'
exit 2
fi
# Only the lock owner may reset the log and update status.
exec >/tmp/AdGuardHome_update.log 2>&1
cleanup() {
local result=$?
trap - EXIT
[ "$result" -ne 0 ] && touch /var/run/update_core_error
rm -f /var/run/update_core
exit "$result"
}
trap cleanup EXIT
trap 'exit 1' TERM INT HUP
rm -f /var/run/update_core_error
touch /var/run/update_core
binpath=$(uci get AdGuardHome.AdGuardHome.binpath 2>/dev/null)
if [ -z "$binpath" ]; then
uci set AdGuardHome.AdGuardHome.binpath="/tmp/AdGuardHome/AdGuardHome"
binpath="/tmp/AdGuardHome/AdGuardHome"
fi
[ ! -d "${binpath%/*}" ] && mkdir -p "${binpath%/*}"
enabled=$(uci get AdGuardHome.AdGuardHome.enabled 2>/dev/null)
core_api_url="https://api.github.com/repos/AdguardTeam/AdGuardHome/releases/latest"
Check_Updates(){
downloader_cmd="uclient-fetch"
downloader_opts="-T 5 -q -O"
downloader_quiet_opts="-q -O -"
printf "%s\n" "开始检查更新, 请耐心等待 ..."
Cloud_Version="$(${downloader_cmd} ${downloader_quiet_opts} "$core_api_url" 2>/dev/null | sed -n 's/.*"tag_name":[ ]*"\(v[^"]*\)".*/\1/p' | head -n 1)"
[ -z "$Cloud_Version" ] && printf "\n检查更新失败, 请检查网络或稍后重试!\n" && EXIT 1
if [ -f "$binpath" ]; then
Current_Version="$($binpath --version 2>/dev/null | sed -n 's/.*\(v[0-9][0-9.]*\).*/\1/p' | head -n 1)"
else
Current_Version="未知"
fi
[ -z "$Current_Version" ] && Current_Version="未知"
printf "\n执行文件路径: %s\n\n正在检查更新, 请耐心等待 ...\n" "${binpath%/*}"
printf "\n当前 AdGuardHome 版本: %s\n云端 AdGuardHome 版本: %s\n" "$Current_Version" "$Cloud_Version"
if [ "$Cloud_Version" != "$Current_Version" ] || [ "$1" = force ]; then
Update_Core
else
printf "\n已是最新版本, 无需更新!\n"
EXIT 0
fi
EXIT 0
}
Update_Core(){
rm -rf /tmp/AdGuardHome_Update > /dev/null 2>&1
mkdir -p "/tmp/AdGuardHome_Update"
GET_Arch
link="https://github.com/AdguardTeam/AdGuardHome/releases/download/${Cloud_Version}/AdGuardHome_linux_${Arch}.tar.gz"
printf "%s\n" "下载链接:${link}"
printf "%s\n" "文件名称:${link##*/}"
printf "\n开始下载 AdGuardHome 核心文件 ...\n\n"
${downloader_cmd} ${downloader_opts} "/tmp/AdGuardHome_Update/${link##*/}" "$link" > /dev/null 2>&1
if [ $? -ne 0 ]; then
printf "\nAdGuardHome 核心下载失败,请检查网络、系统时间及 CA 证书 ...\n"
rm -rf /tmp/AdGuardHome_Update
EXIT 1
fi
if [ "${link##*.}" = "gz" ]; then
printf "\n正在解压 AdGuardHome ...\n"
tar -zxf "/tmp/AdGuardHome_Update/${link##*/}" -C "/tmp/AdGuardHome_Update/"
if [ ! -e /tmp/AdGuardHome_Update/AdGuardHome ]
then
printf "%s\n" "AdGuardHome 核心解压失败!"
rm -rf "/tmp/AdGuardHome_Update" > /dev/null 2>&1
EXIT 1
fi
downloadbin="/tmp/AdGuardHome_Update/AdGuardHome/AdGuardHome"
else
downloadbin="/tmp/AdGuardHome_Update/${link##*/}"
fi
chmod +x "$downloadbin"
size_bytes=$(wc -c < "$downloadbin" 2>/dev/null || echo 0)
printf "\nAdGuardHome 核心体积: %s\n" "$(awk -v bytes="$size_bytes" 'BEGIN{printf "%.2fMB", bytes/1000000}')"
/etc/init.d/AdGuardHome stop 9>&- > /dev/null 2>&1
printf "\n移动 AdGuardHome 核心文件到 %s ...\n" "${binpath%/*}"
if ! mv -f "$downloadbin" "$binpath" > /dev/null 2>&1 || [ ! -s "$binpath" ]; then
printf "AdGuardHome 核心移动失败!\n可能是设备空间不足导致, 请尝试更改 [执行文件路径] 为 /tmp/AdGuardHome\n"
EXIT 1
fi
rm -rf /tmp/AdGuardHome_Update
chmod +x "$binpath"
if [ "$enabled" = 1 ]; then
printf "\n正在重启 AdGuardHome 服务...\n"
/etc/init.d/AdGuardHome restart 9>&- > /dev/null 2>&1
fi
printf "\nAdGuardHome 核心更新成功!\n"
}
GET_Arch() {
if command -v apk > /dev/null 2>&1; then
PM="apk"
elif command -v opkg > /dev/null 2>&1; then
PM="opkg"
else
printf "%s\n" "未找到包管理器 (apk/opkg)!" && EXIT 1
fi
case "${PM}" in
apk)
Archt="$(apk info -a kernel 2>/dev/null | grep Architecture | awk -F ":[ ]*" '{print $2}')"
[ -z "$Archt" ] && Archt="$(uname -m)"
;;
opkg)
Archt="$(opkg info kernel | grep Architecture | awk -F "[ _]" '{print($2)}')"
;;
esac
case "${Archt}" in
i386)
Arch=i386
;;
i686)
Arch=i386
;;
x86|x86_64)
Arch=amd64
;;
mipsel)
Arch=mipsle_softfloat
;;
mips)
Arch=mips_softfloat
;;
mips64el)
Arch=mips64le_softfloat
;;
mips64)
Arch=mips64_softfloat
;;
arm)
Arch=arm
;;
armeb)
Arch=armeb
;;
aarch64)
Arch=arm64
;;
*)
printf "\nAdGuardHome 暂不支持当前的设备架构: [%s]!\n" "$Archt"
EXIT 1
esac
printf "\n当前设备架构: %s\n" "$Arch"
}
EXIT(){
exit "$1"
}
Check_Updates "$update_mode"
@@ -0,0 +1,13 @@
#!/bin/sh
PATH="/usr/sbin:/usr/bin:/sbin:/bin"
configpath=$(uci get AdGuardHome.AdGuardHome.configpath)
while :
do
sleep 10
if [ -f "$configpath" ]; then
/etc/init.d/AdGuardHome do_redirect 1
break
fi
done
exit 0
@@ -0,0 +1,45 @@
{
"admin/services/AdGuardHome": {
"title": "AdGuard Home",
"order": 12,
"action": {
"type": "firstchild"
},
"depends": {
"acl": [ "luci-app-adguardhome" ]
}
},
"admin/services/AdGuardHome/base": {
"title": "Base Setting",
"order": 1,
"action": {
"type": "view",
"path": "adguardhome/base"
},
"depends": {
"acl": [ "luci-app-adguardhome" ]
}
},
"admin/services/AdGuardHome/log": {
"title": "Log",
"order": 3,
"action": {
"type": "view",
"path": "adguardhome/log"
},
"depends": {
"acl": [ "luci-app-adguardhome" ]
}
},
"admin/services/AdGuardHome/manual": {
"title": "Manual Config",
"order": 2,
"action": {
"type": "view",
"path": "adguardhome/manual"
},
"depends": {
"acl": [ "luci-app-adguardhome" ]
}
}
}
@@ -0,0 +1,21 @@
{
"luci-app-adguardhome": {
"description": "Grant UCI access for luci-app-adguardhome",
"read": {
"file": {
"/var/run/AdGredir": [ "read" ]
},
"ubus": {
"service": [ "list" ],
"luci.adguardhome": [ "getCoreInfo", "getRedirectStatus", "getCoreUpdate", "getInterfaces", "getRuntimeLog", "getManualConfig" ]
},
"uci": [ "AdGuardHome" ]
},
"write": {
"ubus": {
"luci.adguardhome": [ "setVerboseLog", "startCoreUpdate", "changeWebPassword", "clearRuntimeLog", "setManualConfig" ]
},
"uci": [ "AdGuardHome" ]
}
}
}
@@ -0,0 +1,265 @@
#!/usr/bin/ucode
'use strict';
import { stat, open, popen, readfile, writefile, unlink, lsdir } from 'fs';
import { cursor } from 'uci';
function shellQuote(value) {
return "'" + replace(value, /'/g, "'\\''") + "'";
}
function coreUpdateRunning() {
// The kernel releases the lock even if the updater is killed.
const result = system(['/usr/bin/flock', '-n', '-E', '2',
'/var/lock/AdGuardHome-update.lock', '/bin/true'], 3000);
if (result != 0 && result != 2)
die('Unable to check core update lock');
return result == 2;
}
return {
'luci.adguardhome': {
getRedirectStatus: {
call: function() {
const conf = cursor();
function option(name, fallback) {
const value = conf.get('AdGuardHome', 'AdGuardHome', name);
return (type(value) == 'array' ? value[0] : value) || fallback;
}
const result = {
mode: option('redirect', 'none'),
enabled: option('enabled', '0') == '1',
applied_mode: option('old_redirect', 'none'),
applied_enabled: option('old_enabled', '0') == '1',
binpath: option('binpath', '/usr/bin/AdGuardHome/AdGuardHome'),
redirected: trim(readfile('/var/run/AdGredir') || '') == '1'
};
conf.unload('AdGuardHome');
const lock = system(['/usr/bin/flock', '-n', '-E', '2',
'/var/lock/AdGuardHome-redirect.lock', '/bin/true'], 3000);
if (lock != 0 && lock != 2)
die('Unable to check redirect status');
result.busy = lock == 2;
return result;
}
},
getManualConfig: {
call: function() {
const conf = cursor();
const configpath = conf.get('AdGuardHome', 'AdGuardHome', 'configpath') ||
'/etc/AdGuardHome.yaml';
conf.unload('AdGuardHome');
const template = readfile('/usr/share/AdGuardHome/AdGuardHome_template.yaml') || '';
const current = readfile(configpath);
if (current != null)
return { content: current, template: template, source: 'current' };
if (stat(configpath) != null)
return { content: '', template: template, source: 'error' };
return {
content: template,
template: template,
source: template ? 'template' : 'missing'
};
}
},
setManualConfig: {
args: { content: '' },
call: function(request) {
const content = request.args.content;
if (content == null)
return { saved: false, reason: 'invalid_content' };
const conf = cursor();
const configpath = conf.get('AdGuardHome', 'AdGuardHome', 'configpath') ||
'/etc/AdGuardHome.yaml';
const binpath = conf.get('AdGuardHome', 'AdGuardHome', 'binpath') ||
'/usr/bin/AdGuardHome/AdGuardHome';
conf.unload('AdGuardHome');
const temporaryConfig = '/tmp/AdGuardHometmpconfig.yaml';
const validationLog = '/tmp/AdGuardHometest.log';
if (writefile(temporaryConfig, replace(content, /\r\n/g, '\n')) == null)
return { saved: false, reason: 'write_failed' };
if (stat(binpath)?.type == 'file') {
const valid = system(['/bin/sh', '-c', '"$1" -c "$2" --check-config > "$3" 2>&1',
'sh', binpath, temporaryConfig, validationLog], 30000) == 0;
if (!valid)
return { saved: false, reason: 'validation_failed', log: readfile(validationLog) || '' };
}
if (writefile(configpath, readfile(temporaryConfig) || '') == null)
return { saved: false, reason: 'save_failed' };
unlink(temporaryConfig);
if (system(['/etc/init.d/AdGuardHome', 'reload'], 30000) != 0)
return { saved: false, reason: 'reload_failed' };
return { saved: true };
}
},
getInterfaces: {
call: function() {
return { interfaces: lsdir('/sys/class/net') || [] };
}
},
getCoreInfo: {
call: function() {
const conf = cursor();
const binpath = conf.get('AdGuardHome', 'AdGuardHome', 'binpath') ||
'/usr/bin/AdGuardHome/AdGuardHome';
const configpath = conf.get('AdGuardHome', 'AdGuardHome', 'configpath') ||
'/etc/AdGuardHome.yaml';
const result = {
core_exists: stat(binpath)?.type == 'file',
config_exists: stat(configpath)?.type == 'file',
version: null
};
if (result.core_exists) {
// Pass the configured path as a positional argument; capture both output streams.
const proc = popen('/bin/sh -c \'"$1" --version 2>&1\' sh ' + shellQuote(binpath), 'r');
if (proc) {
const output = proc.read('all') || '';
proc.close();
result.version = match(output, /version ([^, \t\r\n]+)/)?.[1] || null;
}
}
conf.unload('AdGuardHome');
return result;
}
},
getCoreUpdate: {
call: function() {
const running = coreUpdateRunning();
const failed = stat('/var/run/update_core_error') != null ||
(!running && stat('/var/run/update_core') != null);
const log = readfile('/tmp/AdGuardHome_update.log', 131072) || '';
return {
state: running ? 'running' : failed ? 'failure' : log ? 'finished' : 'idle',
log: log
};
}
},
startCoreUpdate: {
args: { force: false },
call: function(request) {
if (coreUpdateRunning())
return { started: false, reason: 'busy' };
const command = request.args.force
? '/bin/sh /usr/share/AdGuardHome/update_core.sh force >/dev/null 2>&1 </dev/null &'
: '/bin/sh /usr/share/AdGuardHome/update_core.sh >/dev/null 2>&1 </dev/null &';
return { started: system(['/bin/sh', '-c', command], 3000) == 0 };
}
},
changeWebPassword: {
args: { hash: '' },
call: function(request) {
const hash = request.args.hash;
if (!match(hash, /^\$2[aby]\$(0[4-9]|[12][0-9]|3[01])\$[.\/A-Za-z0-9]{53}$/))
return { changed: false, reason: 'invalid_hash' };
const conf = cursor();
const configpath = conf.get('AdGuardHome', 'AdGuardHome', 'configpath') ||
'/etc/AdGuardHome.yaml';
if (stat(configpath)?.type != 'file')
return { changed: false, reason: 'no_config' };
if (!conf.set('AdGuardHome', 'AdGuardHome', 'hashpass', hash) ||
!conf.commit('AdGuardHome'))
return { changed: false, reason: 'save_failed' };
conf.unload('AdGuardHome');
if (system(['/etc/init.d/AdGuardHome', 'reload'], 30000) != 0)
return { changed: false, reason: 'reload_failed' };
if (index(readfile(configpath) || '', hash) < 0)
return { changed: false, reason: 'verify_failed' };
return { changed: true };
}
},
setVerboseLog: {
args: { enabled: false },
call: function(request) {
const conf = cursor();
if (!conf.set('AdGuardHome', 'AdGuardHome', 'verbose', request.args.enabled ? '1' : '0') ||
!conf.commit('AdGuardHome'))
return { saved: false, applied: false };
conf.unload('AdGuardHome');
return {
saved: true,
applied: system(['/etc/init.d/AdGuardHome', 'reload'], 30000) == 0
};
}
},
getRuntimeLog: {
args: { offset: 0, identity: '' },
call: function(request) {
const conf = cursor();
const logfile = conf.get('AdGuardHome', 'AdGuardHome', 'logfile') || '';
conf.unload('AdGuardHome');
if (!logfile)
return { enabled: false, localtime: false, content: '', offset: 0 };
let source = logfile;
if (logfile == 'syslog') {
source = '/tmp/AdGuardHometmp.log';
writefile('/var/run/AdGuardHomesyslog', '1');
if (stat(source)?.type != 'file')
system(['/bin/sh', '-c', '/bin/sh /usr/share/AdGuardHome/getsyslog.sh >/dev/null 2>&1 &'], 3000);
}
// Bound memory and start with the most recent 256 KiB on a new view.
const limit = 262144;
const info = stat(source);
const identity = info ? source + ':' + info.dev.major + ':' + info.dev.minor + ':' + info.inode : '';
let offset = int(request.args.offset) || 0;
let reset = request.args.identity != identity || offset < 0 || offset > (info?.size || 0);
if (reset)
offset = 0;
if (info && info.size - offset > limit) {
offset = info.size - limit;
reset = true;
}
let content = '';
if (info?.type == 'file') {
const file = open(source, 'r');
if (!file)
die('Unable to open runtime log');
const positioned = file.seek(offset);
const chunk = positioned ? file.read(limit) : null;
file.close();
if (chunk == null)
die('Unable to read runtime log');
content = chunk;
}
return {
enabled: true,
localtime: logfile != 'syslog',
// ubus strings stop at NUL; cleared files can contain sparse zero-filled gaps.
content_b64: b64enc(content),
offset: offset + length(content),
identity: identity,
reset: reset
};
}
},
clearRuntimeLog: {
call: function() {
const conf = cursor();
const logfile = conf.get('AdGuardHome', 'AdGuardHome', 'logfile') || '';
conf.unload('AdGuardHome');
if (!logfile)
return { cleared: false, reason: 'no_logfile' };
const source = logfile == 'syslog' ? '/tmp/AdGuardHometmp.log' : logfile;
return { cleared: writefile(source, '') != null };
}
}
}
};
@@ -0,0 +1 @@
.CodeMirror{font-family:monospace;height:500px;color:black;direction:ltr}.CodeMirror-lines{padding:4px 0}.CodeMirror pre.CodeMirror-line,.CodeMirror pre.CodeMirror-line-like{padding:0 4px}.CodeMirror-scrollbar-filler,.CodeMirror-gutter-filler{background-color:white}.CodeMirror-gutters{border-right:1px solid #ddd;background-color:#f7f7f7;white-space:nowrap}.CodeMirror-linenumber{padding:0 3px 0 5px;min-width:20px;text-align:right;color:#999;white-space:nowrap}.CodeMirror-guttermarker{color:black}.CodeMirror-guttermarker-subtle{color:#999}.CodeMirror-cursor{border-left:1px solid black;border-right:0;width:0}.CodeMirror div.CodeMirror-secondarycursor{border-left:1px solid silver}.cm-fat-cursor .CodeMirror-cursor{width:auto;border:0 !important;background:#7e7}.cm-fat-cursor div.CodeMirror-cursors{z-index:1}.cm-fat-cursor-mark{background-color:rgba(20,255,20,0.5);-webkit-animation:blink 1.06s steps(1) infinite;-moz-animation:blink 1.06s steps(1) infinite;animation:blink 1.06s steps(1) infinite}.cm-animate-fat-cursor{width:auto;border:0;-webkit-animation:blink 1.06s steps(1) infinite;-moz-animation:blink 1.06s steps(1) infinite;animation:blink 1.06s steps(1) infinite;background-color:#7e7}@-moz-keyframes blink{50%{background-color:transparent}}@-webkit-keyframes blink{50%{background-color:transparent}}@keyframes blink{50%{background-color:transparent}}.cm-tab{display:inline-block;text-decoration:inherit}.CodeMirror-rulers{position:absolute;left:0;right:0;top:-50px;bottom:0;overflow:hidden}.CodeMirror-ruler{border-left:1px solid #ccc;top:0;bottom:0;position:absolute}.cm-s-default .cm-header{color:blue}.cm-s-default .cm-quote{color:#090}.cm-negative{color:#d44}.cm-positive{color:#292}.cm-header,.cm-strong{font-weight:bold}.cm-em{font-style:italic}.cm-link{text-decoration:underline}.cm-strikethrough{text-decoration:line-through}.cm-s-default .cm-keyword{color:#708}.cm-s-default .cm-atom{color:#219}.cm-s-default .cm-number{color:#164}.cm-s-default .cm-def{color:#00f}.cm-s-default .cm-variable-2{color:#05a}.cm-s-default .cm-variable-3,.cm-s-default .cm-type{color:#085}.cm-s-default .cm-comment{color:#a50}.cm-s-default .cm-string{color:#a11}.cm-s-default .cm-string-2{color:#f50}.cm-s-default .cm-meta{color:#555}.cm-s-default .cm-qualifier{color:#555}.cm-s-default .cm-builtin{color:#30a}.cm-s-default .cm-bracket{color:#997}.cm-s-default .cm-tag{color:#170}.cm-s-default .cm-attribute{color:#00c}.cm-s-default .cm-hr{color:#999}.cm-s-default .cm-link{color:#00c}.cm-s-default .cm-error{color:red}.cm-invalidchar{color:red}.CodeMirror-composing{border-bottom:2px solid}div.CodeMirror span.CodeMirror-matchingbracket{color:#0b0}div.CodeMirror span.CodeMirror-nonmatchingbracket{color:#a22}.CodeMirror-matchingtag{background:rgba(255,150,0,.3)}.CodeMirror-activeline-background{background:#e8f2ff}.CodeMirror{position:relative;overflow:hidden;background:white}.CodeMirror-scroll{overflow:scroll !important;margin-bottom:-30px;margin-right:-30px;padding-bottom:30px;height:100%;outline:0;position:relative}.CodeMirror-sizer{position:relative;border-right:30px solid transparent}.CodeMirror-vscrollbar,.CodeMirror-hscrollbar,.CodeMirror-scrollbar-filler,.CodeMirror-gutter-filler{position:absolute;z-index:6;display:none}.CodeMirror-vscrollbar{right:0;top:0;overflow-x:hidden;overflow-y:scroll}.CodeMirror-hscrollbar{bottom:0;left:0;overflow-y:hidden;overflow-x:scroll}.CodeMirror-scrollbar-filler{right:0;bottom:0}.CodeMirror-gutter-filler{left:0;bottom:0}.CodeMirror-gutters{position:absolute;left:0;top:0;min-height:100%;z-index:3}.CodeMirror-gutter{white-space:normal;height:100%;display:inline-block;vertical-align:top;margin-bottom:-30px}.CodeMirror-gutter-wrapper{position:absolute;z-index:4;background:none !important;border:none !important}.CodeMirror-gutter-background{position:absolute;top:0;bottom:0;z-index:4}.CodeMirror-gutter-elt{position:absolute;cursor:default;z-index:4}.CodeMirror-gutter-wrapper ::selection{background-color:transparent}.CodeMirror-gutter-wrapper ::-moz-selection{background-color:transparent}.CodeMirror-lines{cursor:text;min-height:1px}.CodeMirror pre.CodeMirror-line,.CodeMirror pre.CodeMirror-line-like{-moz-border-radius:0;-webkit-border-radius:0;border-radius:0;border-width:0;background:transparent;font-family:inherit;font-size:inherit;margin:0;white-space:pre;word-wrap:normal;line-height:inherit;color:inherit;z-index:2;position:relative;overflow:visible;-webkit-tap-highlight-color:transparent;-webkit-font-variant-ligatures:contextual;font-variant-ligatures:contextual}.CodeMirror-wrap pre.CodeMirror-line,.CodeMirror-wrap pre.CodeMirror-line-like{word-wrap:break-word;white-space:pre-wrap;word-break:normal}.CodeMirror-linebackground{position:absolute;left:0;right:0;top:0;bottom:0;z-index:0}.CodeMirror-linewidget{position:relative;z-index:2;padding:.1px}.CodeMirror-rtl pre{direction:rtl}.CodeMirror-code{outline:0}.CodeMirror-scroll,.CodeMirror-sizer,.CodeMirror-gutter,.CodeMirror-gutters,.CodeMirror-linenumber{-moz-box-siLine truncated
@@ -0,0 +1 @@
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.CodeMirror=t()}(this,function(){"use strict";var e=navigator.userAgent,t=navigator.platform,r=/gecko\/\d/i.test(e),n=/MSIE \d/.test(e),i=/Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(e),o=/Edge\/(\d+)/.exec(e),l=n||i||o,s=l&&(n?document.documentMode||6:+(o||i)[1]),a=!o&&/WebKit\//.test(e),u=a&&/Qt\/\d+\.\d+/.test(e),c=!o&&/Chrome\//.test(e),h=/Opera\//.test(e),f=/Apple Computer/.test(navigator.vendor),d=/Mac OS X 1\d\D([8-9]|\d\d)\D/.test(e),p=/PhantomJS/.test(e),g=!o&&/AppleWebKit/.test(e)&&/Mobile\/\w+/.test(e),v=/Android/.test(e),m=g||v||/webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(e),y=g||/Mac/.test(t),b=/\bCrOS\b/.test(e),w=/win/i.test(t),x=h&&e.match(/Version\/(\d*\.\d*)/);x&&(x=Number(x[1])),x&&x>=15&&(h=!1,a=!0);var C=y&&(u||h&&(null==x||x<12.11)),S=r||l&&s>=9;function L(e){return new RegExp("(^|\\s)"+e+"(?:$|\\s)\\s*")}var k,T=function(e,t){var r=e.className,n=L(t).exec(r);if(n){var i=r.slice(n.index+n[0].length);e.className=r.slice(0,n.index)+(i?n[1]+i:"")}};function M(e){for(var t=e.childNodes.length;t>0;--t)e.removeChild(e.firstChild);return e}function N(e,t){return M(e).appendChild(t)}function O(e,t,r,n){var i=document.createElement(e);if(r&&(i.className=r),n&&(i.style.cssText=n),"string"==typeof t)i.appendChild(document.createTextNode(t));else if(t)for(var o=0;o<t.length;++o)i.appendChild(t[o]);return i}function A(e,t,r,n){var i=O(e,t,r,n);return i.setAttribute("role","presentation"),i}function D(e,t){if(3==t.nodeType&&(t=t.parentNode),e.contains)return e.contains(t);do{if(11==t.nodeType&&(t=t.host),t==e)return!0}while(t=t.parentNode)}function W(){var e;try{e=document.activeElement}catch(t){e=document.body||null}for(;e&&e.shadowRoot&&e.shadowRoot.activeElement;)e=e.shadowRoot.activeElement;return e}function H(e,t){var r=e.className;L(t).test(r)||(e.className+=(r?" ":"")+t)}function F(e,t){for(var r=e.split(" "),n=0;n<r.length;n++)r[n]&&!L(r[n]).test(t)&&(t+=" "+r[n]);return t}k=document.createRange?function(e,t,r,n){var i=document.createRange();return i.setEnd(n||e,r),i.setStart(e,t),i}:function(e,t,r){var n=document.body.createTextRange();try{n.moveToElementText(e.parentNode)}catch(e){return n}return n.collapse(!0),n.moveEnd("character",r),n.moveStart("character",t),n};var P=function(e){e.select()};function E(e){var t=Array.prototype.slice.call(arguments,1);return function(){return e.apply(null,t)}}function I(e,t,r){for(var n in t||(t={}),e)!e.hasOwnProperty(n)||!1===r&&t.hasOwnProperty(n)||(t[n]=e[n]);return t}function z(e,t,r,n,i){null==t&&-1==(t=e.search(/[^\s\u00a0]/))&&(t=e.length);for(var o=n||0,l=i||0;;){var s=e.indexOf("\t",o);if(s<0||s>=t)return l+(t-o);l+=s-o,l+=r-l%r,o=s+1}}g?P=function(e){e.selectionStart=0,e.selectionEnd=e.value.length}:l&&(P=function(e){try{e.select()}catch(e){}});var R=function(){this.id=null,this.f=null,this.time=0,this.handler=E(this.onTimeout,this)};function B(e,t){for(var r=0;r<e.length;++r)if(e[r]==t)return r;return-1}R.prototype.onTimeout=function(e){e.id=0,e.time<=+new Date?e.f():setTimeout(e.handler,e.time-+new Date)},R.prototype.set=function(e,t){this.f=t;var r=+new Date+e;(!this.id||r<this.time)&&(clearTimeout(this.id),this.id=setTimeout(this.handler,e),this.time=r)};var G=30,U={toString:function(){return"CodeMirror.Pass"}},V={scroll:!1},K={origin:"*mouse"},j={origin:"+move"};function X(e,t,r){for(var n=0,i=0;;){var o=e.indexOf("\t",n);-1==o&&(o=e.length);var l=o-n;if(o==e.length||i+l>=t)return n+Math.min(l,t-i);if(i+=o-n,n=o+1,(i+=r-i%r)>=t)return n}}var Y=[""];function _(e){for(;Y.length<=e;)Y.push($(Y)+" ");return Y[e]}function $(e){return e[e.length-1]}function q(e,t){for(var r=[],n=0;n<e.length;n++)r[n]=t(e[n],n);return r}function Z(){}function Q(e,t){var r;return Object.create?r=Object.create(e):(Z.prototype=e,r=new Z),t&&I(t,r),r}var J=/[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/;function ee(e){return/\w/.test(e)||e>"€"&&(e.toUpperCase()!=e.toLowerCase()||J.test(e))}function te(e,t){return t?!!(t.source.indexOf("\\w")>-1&&ee(e))||t.test(e):ee(e)}function re(e){for(var t in e)if(e.hasOwnProperty(t)&&e[t])return!1;return!0}var ne=/[\u0300-\u036f\u0483-\u0489\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u065e\u0670\u06d6-\u06dc\u06de-\u06e4\u06e7\u06e8\u06ea-\u06ed\u0711\u0730-\u074a\u07a6-\u07b0\u07eb-\u07f3\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0900-\u0902\u093c\u0941-\u0948\u094d\u0951-\u0955\u0962\u0963\u0981\u09bc\u09be\u09c1-\u09c4\u09cd\u09d7\u09e2\u09e3\u0a01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a70\u0a71\u0a75\u0a81\u0a82\u0abc\u0ac1-\u0ac5\u0ac7\u0ac8\u0acd\u0ae2\u0ae3\u0b01\u0b3c\u0b3e\u0b3f\u0b41-\u0b44\u0b4d\u0b56\u0b57\u0b62\u0b63\u0b82\u0bbe\u0bc0\u0bcd\u0bd7\u0c3e-\u0c40\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0cbc\u0cbf\u0cc2\u0cc6\u0ccc\u0ccd\u0cd5\u0cdLine truncated
@@ -0,0 +1 @@
!function(e){"object"==typeof exports&&"object"==typeof module?e(require("../../lib/codemirror")):"function"==typeof define&&define.amd?define(["../../lib/codemirror"],e):e(CodeMirror)}(function(e){"use strict";e.defineMode("yaml",function(){var e=new RegExp("\\b(("+["true","false","on","off","yes","no"].join(")|(")+"))$","i");return{token:function(i,t){var r=i.peek(),n=t.escaped;if(t.escaped=!1,"#"==r&&(0==i.pos||/\s/.test(i.string.charAt(i.pos-1))))return i.skipToEnd(),"comment";if(i.match(/^('([^']|\\.)*'?|"([^"]|\\.)*"?)/))return"string";if(t.literal&&i.indentation()>t.keyCol)return i.skipToEnd(),"string";if(t.literal&&(t.literal=!1),i.sol()){if(t.keyCol=0,t.pair=!1,t.pairStart=!1,i.match(/---/))return"def";if(i.match(/\.\.\./))return"def";if(i.match(/\s*-\s+/))return"meta"}if(i.match(/^(\{|\}|\[|\])/))return"{"==r?t.inlinePairs++:"}"==r?t.inlinePairs--:"["==r?t.inlineList++:t.inlineList--,"meta";if(t.inlineList>0&&!n&&","==r)return i.next(),"meta";if(t.inlinePairs>0&&!n&&","==r)return t.keyCol=0,t.pair=!1,t.pairStart=!1,i.next(),"meta";if(t.pairStart){if(i.match(/^\s*(\||\>)\s*/))return t.literal=!0,"meta";if(i.match(/^\s*(\&|\*)[a-z0-9\._-]+\b/i))return"variable-2";if(0==t.inlinePairs&&i.match(/^\s*-?[0-9\.\,]+\s?$/))return"number";if(t.inlinePairs>0&&i.match(/^\s*-?[0-9\.\,]+\s?(?=(,|}))/))return"number";if(i.match(e))return"keyword"}return!t.pair&&i.match(/^\s*(?:[,\[\]{}&*!|>'"%@`][^\s'":]|[^,\[\]{}#&*!|>'"%@`])[^#]*?(?=\s*:($|\s))/)?(t.pair=!0,t.keyCol=i.indentation(),"atom"):t.pair&&i.match(/^:\s*/)?(t.pairStart=!0,"meta"):(t.pairStart=!1,t.escaped="\\"==r,i.next(),null)},startState:function(){return{pair:!1,pairStart:!1,keyCol:0,inlinePairs:0,inlineList:0,literal:!1,escaped:!1}},lineComment:"#",fold:"indent"}}),e.defineMIME("text/x-yaml","yaml"),e.defineMIME("text/yaml","yaml")});
@@ -0,0 +1 @@
.cm-s-dracula.CodeMirror,.cm-s-dracula .CodeMirror-gutters{background-color:#282a36 !important;color:#f8f8f2 !important;border:0}.cm-s-dracula .CodeMirror-gutters{color:#282a36}.cm-s-dracula .CodeMirror-cursor{border-left:solid thin #f8f8f0}.cm-s-dracula .CodeMirror-linenumber{color:#6d8a88}.cm-s-dracula .CodeMirror-selected{background:rgba(255,255,255,0.10)}.cm-s-dracula .CodeMirror-line::selection,.cm-s-dracula .CodeMirror-line>span::selection,.cm-s-dracula .CodeMirror-line>span>span::selection{background:rgba(255,255,255,0.10)}.cm-s-dracula .CodeMirror-line::-moz-selection,.cm-s-dracula .CodeMirror-line>span::-moz-selection,.cm-s-dracula .CodeMirror-line>span>span::-moz-selection{background:rgba(255,255,255,0.10)}.cm-s-dracula span.cm-comment{color:#6272a4}.cm-s-dracula span.cm-string,.cm-s-dracula span.cm-string-2{color:#f1fa8c}.cm-s-dracula span.cm-number{color:#bd93f9}.cm-s-dracula span.cm-variable{color:#50fa7b}.cm-s-dracula span.cm-variable-2{color:white}.cm-s-dracula span.cm-def{color:#50fa7b}.cm-s-dracula span.cm-operator{color:#ff79c6}.cm-s-dracula span.cm-keyword{color:#ff79c6}.cm-s-dracula span.cm-atom{color:#bd93f9}.cm-s-dracula span.cm-meta{color:#f8f8f2}.cm-s-dracula span.cm-tag{color:#ff79c6}.cm-s-dracula span.cm-attribute{color:#50fa7b}.cm-s-dracula span.cm-qualifier{color:#50fa7b}.cm-s-dracula span.cm-property{color:#66d9ef}.cm-s-dracula span.cm-builtin{color:#50fa7b}.cm-s-dracula span.cm-variable-3,.cm-s-dracula span.cm-type{color:#ffb86c}.cm-s-dracula .CodeMirror-activeline-background{background:rgba(255,255,255,0.1)}.cm-s-dracula .CodeMirror-matchingbracket{text-decoration:underline;color:white !important}
@@ -0,0 +1,7 @@
/* @license
* Twin-Bcrypt 2.2.0
* https://github.com/fpirsch/twin-bcrypt
* Licence: BSD-3-Clause
*/
!function(r,n){"use strict";function e(r){return y[g]=t.apply(n,r),g++}function t(r){var e=[].slice.call(arguments,1);return function(){"function"==typeof r?r.apply(n,e):new Function(""+r)()}}function o(r){if(m)setTimeout(t(o,r),0);else{var n=y[r];if(n){m=!0;try{n()}finally{a(r),m=!1}}}}function a(r){delete y[r]}function i(){p=function(){var r=e(arguments);return process.nextTick(t(o,r)),r}}function u(){if(r.postMessage&&!r.importScripts){var n=!0,e=r.onmessage;return r.onmessage=function(){n=!1},r.postMessage("","*"),r.onmessage=e,n}}function f(){var n="setImmediate$"+Math.random()+"$",t=function(e){e.source===r&&"string"==typeof e.data&&0===e.data.indexOf(n)&&o(+e.data.slice(n.length))};r.addEventListener?r.addEventListener("message",t,!1):r.attachEvent("onmessage",t),p=function(){var t=e(arguments);return r.postMessage(n+t,"*"),t}}function c(){var r=new MessageChannel;r.port1.onmessage=function(r){var n=r.data;o(n)},p=function(){var n=e(arguments);return r.port2.postMessage(n),n}}function s(){var r=v.documentElement;p=function(){var n=e(arguments),t=v.createElement("script");return t.onreadystatechange=function(){o(n),t.onreadystatechange=null,r.removeChild(t),t=null},r.appendChild(t),n}}function l(){p=function(){var r=e(arguments);return setTimeout(t(o,r),0),r}}if(!r.setImmediate){var p,g=1,y={},m=!1,v=r.document,d=Object.getPrototypeOf&&Object.getPrototypeOf(r);d=d&&d.setTimeout?d:r,"[object process]"==={}.toString.call(r.process)?i():u()?f():r.MessageChannel?c():v&&"onreadystatechange"in v.createElement("script")?s():l(),d.setImmediate=p,d.clearImmediate=a}}(new Function("return this")()),function(r){"object"==typeof exports?r(exports,require("crypto")):r(self.TwinBcrypt={},self.crypto||self.msCrypto)}(function(r,n){"use strict";function e(r){for(var n=unescape(encodeURIComponent(r)),e=n.length,t=new Array(e),o=0;e>o;o++)t[o]=n.charCodeAt(o);return t}function t(r){for(var n=r.length,e=new Array(n),t=0;n>t;t++)e[t]=r.charCodeAt(t);return e}function o(r,n){for(var e,t,o=0,a="";n>o;){if(e=255&r[o++],a+=B[e>>2],e=(3&e)<<4,o>=n){a+=B[e];break}if(t=255&r[o++],e|=t>>4,a+=B[e],e=(15&t)<<2,o>=n){a+=B[e];break}t=255&r[o++],e|=t>>6,a+=B[e],a+=B[63&t]}return a}function a(r){for(var n,e,t=new Array(16),o=0,a=0;;){if(n=D[r.charCodeAt(o++)-46],e=D[r.charCodeAt(o++)-46],t[a++]=255&(n<<2|e>>4),22===o)break;n=e<<4,e=D[r.charCodeAt(o++)-46],t[a++]=255&(n|e>>2),n=e<<6,e=D[r.charCodeAt(o++)-46],t[a++]=255&(n|e)}return t}function i(r){for(var n=r.length,e=new Array(72),t=0,o=0;72>o;)e[o++]=r[t++],t===n&&(t=0);return e}function u(r,n,e){for(var t=0,o=e>>2;t<n.length;t++,o++)r[o]=n[t]}function f(r,n,e){for(var t=0;72>t;)r[n++]=e[t++]<<24|e[t++]<<16|e[t++]<<8|e[t++]}function c(r){function n(n){for(var e=r,t=G>>2,o=t|O,f=n>>2,c=e[f]^e[t],s=e[1|f];o>t;)s^=(e[c>>>24]+e[a|c>>>16&255]^e[i|c>>>8&255])+e[u|255&c]^e[++t],c^=(e[s>>>24]+e[a|s>>>16&255]^e[i|s>>>8&255])+e[u|255&s]^e[++t];e[f]=s^e[S>>2],e[1|f]=c}function e(n){var e;for(r[L>>2]=0,r[L+4>>2]=0,e=0;M>e;e++)r[G>>2|e]^=r[(n>>2)+e];var t,o,f,c,s,l=r;for(e=0;M>e;e+=2){for(t=G>>2,o=t|O,f=L>>2,c=l[f]^l[t],s=l[1|f];o>t;)s^=(l[c>>>24]+l[a|c>>>16&255]^l[i|c>>>8&255])+l[u|255&c]^l[++t],c^=(l[s>>>24]+l[a|s>>>16&255]^l[i|s>>>8&255])+l[u|255&s]^l[++t];l[f]=s^l[S>>2],l[1|f]=c,r[G>>2|e]=l[f],r[G>>2|e+1]=c}for(e=0;T>e;e+=2){for(t=G>>2,o=t|O,f=L>>2,c=l[f]^l[t],s=l[1|f];o>t;)s^=(l[c>>>24]+l[a|c>>>16&255]^l[i|c>>>8&255])+l[u|255&c]^l[++t],c^=(l[s>>>24]+l[a|s>>>16&255]^l[i|s>>>8&255])+l[u|255&s]^l[++t];l[f]=s^l[S>>2],l[1|f]=c,r[e]=l[f],r[1|e]=c}}function t(r,n,t){for(var o=0;t>=o&&!(r>n);o++)e(R),e(j),r++;return r}var o=k>>2,a=o+256|0,i=a+256|0,u=i+256|0;return{encrypt:n,expandLoop:t}}function s(stdlib, foreign, heap) {"use asm";var HEAP32=new stdlib.Uint32Array(heap);var BLOWFISH_NUM_ROUNDS=16;var S_offset=0x0000;var S1_offset=0x0400;var S2_offset=0x0800;var S3_offset=0x0C00;var P_offset=0x1000;var P_last_offset=0x1044;var crypt_ciphertext_offset=0x1048;var LR_offset=0x01060;var password_offset=0x1068;var salt_offset=0x10b0;var P_LEN=18;var S_LEN=1024;function encrypt(offset) {offset=offset|0;var i=0;var n=0;var L=0;var R=0;var imax=0;imax=P_offset|BLOWFISH_NUM_ROUNDS<<2;L=HEAP32[offset>>2]|0;R=HEAP32[offset+4>>2]|0;L=L^HEAP32[P_offset>>2];for (i=P_offset; (i|0)<(imax|0);) {i=(i+4)>>>0;R=R^(((HEAP32[(L>>>22)>>2]>>>0) +(HEAP32[(S1_offset|(L>>>14&0x3ff))>>2]>>>0) ^(HEAP32[(S2_offset|(L>>>6&0x3ff))>>2])) +(HEAP32[(S3_offset|(L<<2&0x3ff))>>2]>>>0))^HEAP32[i>>2];i=(i+4)>>>0;L=L^(((HEAP32[(R>>>22)>>2]>>>0) +(HEAP32[(S1_offset|(R>>>14&0x3ff))>>2]>>>0) ^(HEAP32[(S2_offset|(R>>>6&0x3ff))>>2])) +(HEAP32[(S3_offset|(R<<2&0x3ff))>>2]>>>0))^HEAP32[i>>2];}HEAP32[offset>>2]=R^HEAP32[P_last_offset>>2];HEAP32[(offset+4)>>2]=L;}function expandKey(offset) {offset=offset|0;var i=0;var off=0;off=P_offset|0;for (i=0; (i|0)<(P_LEN|0); i=(i+1)|0) {HEAP32[off>>2]=HEAP32[off>>2]^HEAP32[offset>>2];offset=(offset+4)|0;off=(off+4)|0;}HEAP32[LR_offset>>2]=0;HEAP32[LR_offset+4>>2]=0;off=P_offset;for (i=0; (i|0)<(P_LEN|0); iLine truncated
function l(r,n,e,t){var o,a,i,u=L>>2,f=u+1;for(t[u]=0,t[f]=0,a=0,o=0;M>o;o++)i=n[a++]<<24|n[a++]<<16|n[a++]<<8|n[a++],t[G>>2|o]^=i;for(a=0,o=0;M>o;o+=2)i=r[a++]<<24|r[a++]<<16|r[a++]<<8|r[a++],a&=65295,t[u]^=i,i=r[a++]<<24|r[a++]<<16|r[a++]<<8|r[a++],a&=65295,t[f]^=i,e.encrypt(L),t[G>>2|o]=t[u],t[G>>2|o+1]=t[f];var c=k>>2;for(o=0;T>o;o+=2)i=r[a++]<<24|r[a++]<<16|r[a++]<<8|r[a++],a&=65295,t[u]^=i,i=r[a++]<<24|r[a++]<<16|r[a++]<<8|r[a++],a&=65295,t[f]^=i,e.encrypt(L),t[c|o]=t[u],t[c|o+1]=t[f]}function p(r,n,e,t,o,a,i){for(var u=e;t>=u;){if(u=r.expandLoop(u,t,o),a){var f=a(u/(t+1));if(f===!1)return}if(u>t){if(i)return void setImmediate(g.bind(null,r,n,i));return}if(i)return void setImmediate(p.bind(null,r,n,u,t,o,a,i))}}function g(r,n,e){u(n,x,F);var t;for(t=0;64>t;t++)r.encrypt(F+0),r.encrypt(F+8),r.encrypt(F+16);var o,a=0,i=x.length,f=new Array(4*i);for(t=0;i>t;t++)o=n[(F>>2)+t],f[a++]=o>>24,f[a++]=o>>16&255,f[a++]=o>>8&255,f[a++]=255&o;return e&&e(f),f}function y(r,n){return r+o(n,23)}function m(n,o,m,v){var d,h=o.substr(0,29),w=+o.substr(4,2),A=o.substr(7,22);if("string"==typeof n)d=r.encodingMode===r.ENCODING_UTF8?e(n):t(n);else if(Array.isArray(n))d=n.map(function(r){return 255&r});else{if(!(n instanceof Uint8Array))throw new Error("Incorrect arguments");d=Array.prototype.slice.call(n)}d.push(0);var b,E,N=a(A,C),O=31>w?1<<w:2147483648,M=O-1,T=m?127:M;if(I){var x=new ArrayBuffer(8192);b=new Uint32Array(x),E=s({Uint32Array:Uint32Array},null,x)}else E=c(b=[]);return u(b,U,k),u(b,$,G),d=i(d),N=i(N),f(b,j>>2,N),f(b,R>>2,d),l(N,d,E,b),v?void p(E,b,0,M,T,m,function(r){v(y(h,r))}):(p(E,b,0,M,T,m),y(h,g(E,b)))}function v(r){if(!b)throw new Error("No cryptographically secure pseudorandom number generator available.");if(null==r&&(r=N),r=0|+r,isNaN(r)||4>r||r>31)throw new Error("Invalid cost parameter.");var n="$2y$";return 10>r&&(n+="0"),n+=r+"$",n+=o(b(C),C)}function d(r,n,e){if(n&&"number"!=typeof n){if("string"!=typeof n||!z.test(n))throw new Error("Invalid salt")}else n=v(n);return m(r,n,e)}function h(r,n,e,t){if(arguments.length<2)throw new Error("Incorrect arguments");if(2===arguments.length?(t=n,n=e=null):3===arguments.length&&(t=e,e=null,"function"==typeof n&&(e=n,n=null)),n&&"number"!=typeof n){if("string"!=typeof n||!z.test(n))throw new Error("Invalid salt")}else n=v(n);if(!t||"function"!=typeof t)throw new Error("No callback function was given.");m(r,n,e,t)}function w(r,n){if("string"!=typeof n||!Z.test(n))throw new Error("Incorrect arguments");var e=n.substr(0,n.length-31),t=d(r,e);return t===n}function A(r,n,e,t){if("string"!=typeof n||!Z.test(n))throw new Error("Incorrect arguments");if(t||(t=e,e=null),!t||"function"!=typeof t)throw new Error("No callback function was given.");var o=n.substr(0,n.length-31);h(r,o,e,function(r){t(r===n)})}var b,E="undefined"!=typeof InstallTrigger,I=E;n&&(b=n.randomBytes,n.getRandomValues&&(b=function(r){var e=new Uint8Array(r);return n.getRandomValues(e)}));var C=16,N=10,O=16,$=[608135816,2242054355,320440878,57701188,2752067618,698298832,137296536,3964562569,1160258022,953160567,3193202383,887688300,3232508343,3380367581,1065670069,3041331479,2450970073,2306472731],U=[3509652390,2564797868,805139163,3491422135,3101798381,1780907670,3128725573,4046225305,614570311,3012652279,134345442,2240740374,1667834072,1901547113,2757295779,4103290238,227898511,1921955416,1904987480,2182433518,2069144605,3260701109,2620446009,720527379,3318853667,677414384,3393288472,3101374703,2390351024,1614419982,1822297739,2954791486,3608508353,3174124327,2024746970,1432378464,3864339955,2857741204,1464375394,1676153920,1439316330,715854006,3033291828,289532110,2706671279,2087905683,3018724369,1668267050,732546397,1947742710,3462151702,2609353502,2950085171,1814351708,2050118529,680887927,999245976,1800124847,3300911131,1713906067,1641548236,4213287313,1216130144,1575780402,4018429277,3917837745,3693486850,3949271944,596196993,3549867205,258830323,2213823033,772490370,2760122372,1774776394,2652871518,566650946,4142492826,1728879713,2882767088,1783734482,3629395816,2517608232,2874225571,1861159788,326777828,3124490320,2130389656,2716951837,967770486,1724537150,2185432712,2364442137,1164943284,2105845187,998989502,3765401048,2244026483,1075463327,1455516326,1322494562,910128902,469688178,1117454909,936433444,3490320968,3675253459,1240580251,122909385,2157517691,634681816,4142456567,3825094682,3061402683,2540495037,79693498,3249098678,1084186820,1583128258,426386531,1761308591,1047286709,322548459,995290223,1845252383,2603652396,3431023940,2942221577,3202600964,3727903485,1712269319,422464435,3234572375,1170764815,3523960633,3117677531,1434042557,442511882,3600875718,1076654713,1738483198,4213154764,2393238008,3677496056,1014306527,4251020053,793779912,2902807211,842905082,4246964064,1395751752,1040244610,2656851899,3396308128,445077038,3742853595,3577915638,679411651,2892444358,2354009459,1767581616,3150600392,3791627101,3102740896,284835224,4246832056,1258075500,768725851,2589189241,3069724005Line truncated
@@ -0,0 +1,276 @@
'use strict';
'require view';
'require form';
'require poll';
'require rpc';
'require ui';
'require view/adguardhome/status as status';
var callInterfaces = rpc.declare({
object: 'luci.adguardhome',
method: 'getInterfaces',
expect: { '': {} },
reject: true
});
var callCoreUpdate = rpc.declare({
object: 'luci.adguardhome',
method: 'getCoreUpdate',
expect: { '': {} },
reject: true
});
var callStartCoreUpdate = rpc.declare({
object: 'luci.adguardhome',
method: 'startCoreUpdate',
params: [ 'force' ],
expect: { '': {} },
reject: true
});
return view.extend({
handleSaveApply: function(ev, mode) {
var target = {
mode: this.redirectOption.formvalue('AdGuardHome') || 'none',
enabled: this.enabledOption.formvalue('AdGuardHome') === '1'
};
this.sharedStatus.beginSwitch(target);
return this.handleSave(ev).then(function() {
ui.changes.apply(mode == '0');
}).catch(function(error) {
this.sharedStatus.cancelSwitch();
throw error;
}.bind(this));
},
load: function() {
return callInterfaces().catch(function() { return { interfaces: [] }; });
},
render: function(data) {
let m, s, o;
var self = this;
var interfaces = data.interfaces || [];
m = new form.Map('AdGuardHome');
s = m.section(
form.TypedSection,
'AdGuardHome',
_('Basic Settings')
);
s.anonymous = true;
s.addremove = false;
o = s.option(
form.Flag,
'enabled',
_('Enable')
);
this.enabledOption = o;
o.default = '0';
o.rmempty = false;
o = s.option(
form.Value,
'httpport',
_('Browser management port')
);
o.datatype = 'port';
o.placeholder = '3000';
o.default = '3000';
o.rmempty = false;
o = s.option(
form.ListValue,
'redirect',
_('DNS handling mode')
);
this.redirectOption = o;
o.value('none', _('Do not handle DNS'));
o.value('redirect', _('Forward port 53 requests through the firewall (recommended)'));
o.value('dnsmasq-upstream', _('Run as dnsmasq upstream server'));
o.value('exchange', _('Use port 53 instead of dnsmasq'));
o.default = 'none';
o.rmempty = false;
o = s.option(
form.DynamicList,
'wan_ifname',
_('Network devices excluded from forwarding'),
_('DNS requests arriving on these network devices are not forwarded to AdGuardHome')
);
o.depends('redirect', 'redirect');
o.retain = true;
o.datatype = 'string';
o.rmempty = true;
interfaces.filter(function(name) { return name !== 'lo'; }).forEach(function(name) {
o.value(name, name);
});
o = s.option(
form.Value,
'binpath',
_('Bin Path')
);
o.default = '/usr/bin/AdGuardHome/AdGuardHome';
o.rmempty = false;
o = s.option(
form.Value,
'configpath',
_('Config Path')
);
o.default = '/etc/AdGuardHome.yaml';
o.rmempty = false;
o = s.option(
form.Value,
'workdir',
_('Work dir')
);
o.default = '/usr/bin/AdGuardHome';
o.rmempty = false;
return m.render().then(function(node) {
var sharedStatus = status.render();
self.sharedStatus = sharedStatus;
var updateButton = E('button', {
'class': 'btn cbi-button cbi-button-apply', 'type': 'button'
}, _('Update core version'));
var forceButton = E('button', {
'class': 'btn cbi-button', 'type': 'button'
}, _('Force update'));
var updateMessage = E('span', {}, '');
var reverseLog = E('input', { 'type': 'checkbox' });
var updateLog = E('textarea', {
'class': 'cbi-input-textarea agh-update-log', 'rows': 8, 'readonly': 'readonly',
'style': 'display:none;'
});
var logOptions = E('label', { 'class': 'agh-update-options', 'style': 'display:none;' }, [
reverseLog, E('span', {}, _('reverse'))
]);
var updatePanel = E('div', { 'class': 'cbi-value' }, [
E('div', { 'class': 'cbi-value-title' }, _('Upgrade Core')),
E('div', { 'class': 'cbi-value-field' }, E('div', { 'class': 'agh-update-content' }, [
E('div', { 'class': 'agh-toolbar' }, [updateButton, forceButton, updateMessage]),
logOptions, updateLog
]))
]);
var lastLog = '';
var startingAt = 0;
var activeUpdate = false;
var updateFinishedHere = false;
function showLog() {
updateLog.value = reverseLog.checked ? lastLog.split('\n').reverse().join('\n') : lastLog;
}
reverseLog.addEventListener('change', showLog);
function showUpdateStatus(result) {
var state = result.state || 'idle';
if (startingAt && state === 'idle' && Date.now() - startingAt < 10000)
return;
startingAt = 0;
if (state === 'running')
activeUpdate = true;
else if (activeUpdate && (state === 'finished' || state === 'failure')) {
activeUpdate = false;
updateFinishedHere = true;
sharedStatus.refresh();
}
updateButton.disabled = forceButton.disabled = state === 'running';
if (state === 'running' || updateFinishedHere) {
updateMessage.textContent = state === 'running' ? _('Check...') :
state === 'finished' ? _('Updated') : _('Update failed');
lastLog = result.log || '';
logOptions.style.display = lastLog ? 'inline-flex' : 'none';
updateLog.style.display = lastLog ? 'block' : 'none';
showLog();
} else {
updateMessage.textContent = '';
lastLog = '';
logOptions.style.display = updateLog.style.display = 'none';
}
}
function startUpdate(force) {
startingAt = Date.now();
activeUpdate = true;
updateFinishedHere = false;
updateButton.disabled = forceButton.disabled = true;
updateMessage.textContent = _('Check...');
lastLog = '';
showLog();
updateLog.style.display = logOptions.style.display = 'none';
callStartCoreUpdate(force).then(function(result) {
if (!result.started) {
startingAt = 0;
activeUpdate = false;
updateMessage.textContent = _('Update in progress');
return;
}
return callCoreUpdate().then(showUpdateStatus);
}).catch(function() {
startingAt = 0;
activeUpdate = false;
updateButton.disabled = forceButton.disabled = false;
updateMessage.textContent = _('Unable to start update');
});
}
updateButton.addEventListener('click', function() { startUpdate(false); });
forceButton.addEventListener('click', function() { startUpdate(true); });
poll.add(function() {
return callCoreUpdate().then(showUpdateStatus).catch(function() {
updateMessage.textContent = _('Update status unavailable');
});
}, 3);
var settingsCard = node.querySelector('.cbi-section');
settingsCard.classList.add('agh-compact-card', 'agh-base-card');
var settingsFields = settingsCard.querySelector('.cbi-section-node') || settingsCard;
settingsFields.appendChild(updatePanel);
node.insertBefore(sharedStatus.node, node.firstChild);
node.insertBefore(E('link', {
'rel': 'stylesheet', 'href': L.resource('view/adguardhome/compact.css')
}), node.firstChild);
var portInput = node.querySelector('[data-name="httpport"] input');
var portField = node.querySelector('[data-name="httpport"] .cbi-value-field');
if (portInput && portField) {
var webLink = E('input', {
'type': 'button',
'style': 'width:210px;border-color:Teal;text-align:center;font-weight:bold;color:#337ab7;background:#ffc800;',
'target': '_blank',
'value': _('AdGuardHome Web')
});
var linkWrap = E('div', { 'style': 'margin-top:0.35rem;' }, webLink);
var host = window.location.hostname;
if (host.indexOf(':') >= 0 && host.charAt(0) !== '[')
host = '[' + host + ']';
function updateWebLink() {
var port = portInput.value.trim();
var valid = /^[0-9]+$/.test(port) && +port >= 1 && +port <= 65535;
webLink.value = _('AdGuardHome Web') + ': ' + port;
webLink.onclick = valid ? function() {
window.open('http://' + host + ':' + port + '/');
} : null;
webLink.style.display = valid ? 'inline-block' : 'none';
}
portField.appendChild(linkWrap);
portInput.addEventListener('input', updateWebLink);
updateWebLink();
}
return node;
});
}
});
@@ -0,0 +1,153 @@
/* Scoped to the settings and log cards; shared status badges keep their layout. */
.agh-compact-card {
margin-bottom: 0.75rem;
}
.agh-compact-card .cbi-value {
margin-bottom: 0;
padding-top: 0.35rem;
padding-bottom: 0.35rem;
}
.agh-compact-card .cbi-value-description {
margin-top: 0.25rem;
}
.agh-compact-card .agh-toolbar {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 0.5rem 0.75rem;
}
.agh-compact-card .agh-toolbar > * {
margin: 0;
}
.agh-compact-card .agh-update-content {
width: 100%;
max-width: 40rem;
min-width: 0;
}
.agh-compact-card .agh-update-options {
align-items: center;
gap: 0.35rem;
width: auto;
margin: 0.5rem 0 0;
padding: 0;
float: none;
}
.agh-compact-card .agh-update-options input[type="checkbox"] {
position: static;
flex: 0 0 auto;
margin: 0;
}
.agh-compact-card textarea.agh-update-log {
width: 100%;
height: 10rem;
min-height: 6rem;
box-sizing: border-box;
resize: vertical;
margin: 0.35rem 0 0;
}
.agh-log-view {
padding: 0.6em;
}
.agh-log-view textarea {
display: block;
width: 100%;
height: clamp(12rem, 32vh, 20rem);
min-height: 10rem;
box-sizing: border-box;
resize: vertical;
margin: 0 0 0.6em;
}
.agh-manual-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 0.5rem 1rem;
padding: 0;
}
.agh-compact-card .agh-password-controls label {
float: none;
width: auto;
padding: 0;
}
.agh-compact-card .agh-password-controls input {
width: 14rem;
max-width: 100%;
min-width: 0;
}
.agh-manual-card {
padding: 0.6em;
}
.agh-editor-wrap {
margin: 0 0 0.6em;
}
.agh-config-actions {
margin-left: auto;
}
/* Keep the base card heading in the theme's original position. */
.agh-base-card .cbi-section-node {
margin: 0;
padding: 0.6em;
}
.agh-log-view .agh-log-controls {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 0.6em 1.2em;
width: 100%;
margin: 0;
padding: 0;
text-align: left;
}
.agh-log-view .agh-log-toggles {
justify-content: flex-start;
flex: 0 1 auto;
}
.agh-log-view .agh-log-actions {
display: flex;
align-items: center;
justify-content: flex-end;
flex-wrap: wrap;
gap: 0.6em 1.2em;
flex: 0 1 auto;
min-width: 0;
margin-left: auto;
}
.agh-log-view .agh-log-actions > .cbi-value {
display: flex;
align-items: center;
flex-wrap: wrap;
flex: 0 1 auto;
gap: 0.5em;
width: auto;
min-width: 0;
margin: 0;
padding: 0;
border: 0;
}
.agh-log-view .agh-log-actions .cbi-value-title {
float: none;
flex: 0 0 auto;
width: auto;
margin: 0;
padding: 0;
}
.agh-log-view .agh-log-actions .cbi-value-field {
flex: 0 1 auto;
width: auto;
min-width: 0;
margin: 0;
padding: 0;
}
.agh-log-view .agh-log-actions .cbi-value-field input {
width: 22em;
max-width: 100%;
box-sizing: border-box;
}
.agh-log-controls .agh-toolbar > span:empty {
display: none;
}
@@ -0,0 +1,242 @@
'use strict';
'require view';
'require form';
'require poll';
'require rpc';
'require view/adguardhome/status as status';
var callRuntimeLog = rpc.declare({
object: 'luci.adguardhome',
method: 'getRuntimeLog',
params: [ 'offset', 'identity' ],
expect: { '': {} },
reject: true
});
var callClearRuntimeLog = rpc.declare({
object: 'luci.adguardhome',
method: 'clearRuntimeLog',
expect: { '': {} },
reject: true
});
var callSetVerboseLog = rpc.declare({
object: 'luci.adguardhome',
method: 'setVerboseLog',
params: [ 'enabled' ],
expect: { '': {} },
reject: true,
timeout: 45000
});
function decodeLog(result) {
var content = result.content || '';
if (typeof result.content_b64 === 'string') {
var binary = atob(result.content_b64);
var bytes = new Uint8Array(binary.length);
for (var i = 0; i < binary.length; i++)
bytes[i] = binary.charCodeAt(i);
content = new TextDecoder('utf-8').decode(bytes);
}
// Truncating an open log does not reset a non-append writer's file position.
return content.replace(/\0/g, '');
}
function pad(value) {
return value < 10 ? '0' + value : value;
}
function localizeTimestamps(content) {
return content.split('\n').map(function(line) {
var date = new Date(line.substring(0, 19).replace(/\//g, '-') + ' UTC');
if (isNaN(date.getTime()))
return line;
return date.getFullYear() + '/' + pad(date.getMonth() + 1) + '/' + pad(date.getDate()) + ' ' +
pad(date.getHours()) + ':' + pad(date.getMinutes()) + ':' + pad(date.getSeconds()) + line.substring(19);
}).join('\n');
}
return view.extend({
load: function() {
return callRuntimeLog(0, '').catch(function() {
return { error: true, localtime: false, content: '', offset: 0 };
});
},
render: function(initialLog) {
var m = new form.Map('AdGuardHome');
var s = m.section(form.TypedSection, 'AdGuardHome');
s.anonymous = true;
s.addremove = false;
var o;
o = s.option(
form.Value,
'logfile',
_('Runtime log file')
);
o.rmempty = true;
return m.render().then(function(settingsNode) {
var sharedStatus = status.render();
var logLimit = 262144;
var content = decodeLog(initialLog).slice(-logLimit);
var identity = initialLog.identity || '';
var generation = 0;
var offset = initialLog.offset || 0;
var enabled = initialLog.enabled;
var localtime = initialLog.localtime;
var reverse = true;
var convertTime = localtime;
var logArea = E('textarea', {
'class': 'cbi-input-textarea',
'rows': 16,
'readonly': 'readonly',
'style': 'width:100%;box-sizing:border-box;'
});
var toggleStyle = 'margin:0 0.35em 0 0;height:auto;flex:none;';
var reverseToggle = E('input', { 'type': 'checkbox', 'checked': true, 'style': toggleStyle });
var timeToggle = E('input', { 'type': 'checkbox', 'checked': true, 'style': toggleStyle });
var reverseLabel = E('label', { 'style': 'display:inline-flex;align-items:center;' }, [
reverseToggle, _('reverse')
]);
var timeLabel = E('label', { 'style': localtime ? 'display:inline-flex;align-items:center;' : 'display:none;' }, [
timeToggle, _('localtime')
]);
var verboseToggle = E('input', {
'type': 'checkbox', 'style': toggleStyle
});
// Boolean HTML attributes are true whenever present, even with a false value.
verboseToggle.checked = m.data.get('AdGuardHome', 'AdGuardHome', 'verbose') === '1';
verboseToggle.disabled = !!m.readonly;
var verboseMessage = E('span', { 'role': 'status' }, '');
var verboseLabel = E('label', { 'style': 'display:inline-flex;align-items:center;' }, [
verboseToggle, _('Verbose log')
]);
verboseToggle.addEventListener('change', function() {
var requested = verboseToggle.checked;
verboseToggle.disabled = true;
verboseMessage.textContent = _('Applying verbose logging…');
callSetVerboseLog(requested).then(function(result) {
if (!result.saved)
throw new Error('save failed');
verboseMessage.textContent = result.applied ? '' : _('Saved, but service reload failed; check the log');
}).catch(function() {
verboseToggle.checked = !requested;
verboseMessage.textContent = _('Unable to apply verbose logging');
}).then(function() {
verboseToggle.disabled = false;
});
});
var message = E('span', {}, '');
function renderLog() {
var displayed = convertTime ? localizeTimestamps(content) : content;
logArea.value = reverse ? displayed.split('\n').reverse().join('\n') : displayed;
}
function downloadLog() {
var now = new Date();
var link = document.createElement('a');
link.download = 'AdGuardHome' + (now.getMonth() + 1) + '-' + now.getDate() + '-' +
pad(now.getHours()) + '_' + pad(now.getMinutes()) + '.log';
link.href = URL.createObjectURL(new Blob([logArea.value], { type: 'text/plain' }));
link.click();
URL.revokeObjectURL(link.href);
}
var clearButton = E('button', {
'class': 'btn cbi-button cbi-button-apply', 'type': 'button'
}, _('dellog'));
var downloadButton = E('button', {
'class': 'btn cbi-button', 'type': 'button'
}, _('download log'));
reverseToggle.addEventListener('change', function() {
reverse = reverseToggle.checked;
renderLog();
});
timeToggle.addEventListener('change', function() {
convertTime = timeToggle.checked;
renderLog();
});
clearButton.addEventListener('click', function() {
clearButton.disabled = true;
generation++;
callClearRuntimeLog().then(function(result) {
if (!result.cleared)
throw new Error(result.reason || 'clear failed');
content = '';
offset = 0;
identity = '';
generation++;
message.textContent = '';
renderLog();
}).catch(function() {
message.textContent = _('Status unavailable');
}).then(function() {
clearButton.disabled = false;
});
});
downloadButton.addEventListener('click', downloadLog);
var settingsCard = settingsNode.querySelector('.cbi-section');
settingsCard.classList.add('agh-compact-card', 'agh-log-view');
var settingsFields = settingsCard.querySelector('.cbi-section-node');
var logFileField = settingsFields.querySelector('.cbi-value');
var logPath = logFileField.querySelector('input[type="text"]');
if (logPath)
logPath.title = _('If set to "syslog", write to system log; if empty, disable file logging');
var logControls = E('div', { 'class': 'agh-log-controls' }, [
E('div', { 'class': 'agh-toolbar agh-log-toggles' }, [
reverseLabel, timeLabel, verboseLabel, verboseMessage, message
]),
E('div', { 'class': 'agh-log-actions' }, [
E('div', { 'class': 'agh-toolbar' }, [clearButton, downloadButton]),
logFileField
])
]);
// Keep the form field inside its map, outside the theme's section layout.
settingsFields.hidden = true;
settingsCard.insertBefore(logControls, settingsFields);
settingsCard.insertBefore(logArea, logControls);
var page = E('div', {}, [
E('link', { 'rel': 'stylesheet', 'href': L.resource('view/adguardhome/compact.css') }),
sharedStatus.node,
settingsNode
]);
if (initialLog.error)
message.textContent = _('Unable to read runtime log');
else if (!enabled)
message.textContent = _('Please add log path in config to enable log');
renderLog();
poll.add(function() {
if (clearButton.disabled)
return Promise.resolve();
var requestGeneration = generation;
return callRuntimeLog(offset, identity).then(function(result) {
if (requestGeneration !== generation)
return;
enabled = result.enabled;
if (!enabled) {
message.textContent = _('Please add log path in config to enable log');
return;
}
message.textContent = '';
if (result.reset || result.offset < offset)
content = '';
offset = result.offset;
identity = result.identity || '';
content = (content + decodeLog(result)).slice(-logLimit);
renderLog();
}).catch(function() {
message.textContent = _('Status unavailable');
});
}, 3);
return page;
});
}
});
@@ -0,0 +1,232 @@
'use strict';
'require view';
'require rpc';
'require view/adguardhome/status as status';
var callManualConfig = rpc.declare({
object: 'luci.adguardhome',
method: 'getManualConfig',
expect: { '': {} },
reject: true
});
var callSetManualConfig = rpc.declare({
object: 'luci.adguardhome',
method: 'setManualConfig',
params: [ 'content' ],
expect: { '': {} },
reject: true
});
var callChangeWebPassword = rpc.declare({
object: 'luci.adguardhome',
method: 'changeWebPassword',
params: [ 'hash' ],
expect: { '': {} },
reject: true
});
var codeMirrorLoader;
var bcryptLoader;
function loadBcrypt() {
if (window.TwinBcrypt)
return Promise.resolve(window.TwinBcrypt);
if (!bcryptLoader) {
bcryptLoader = new Promise(function(resolve, reject) {
var script = document.createElement('script');
script.src = L.resource('twin-bcrypt.min.js');
script.onload = function() {
if (window.TwinBcrypt)
resolve(window.TwinBcrypt);
else
reject(new Error('bcrypt unavailable'));
};
script.onerror = reject;
document.head.appendChild(script);
}).catch(function(error) {
bcryptLoader = null;
throw error;
});
}
return bcryptLoader;
}
function loadCodeMirror() {
if (window.CodeMirror)
return Promise.resolve(window.CodeMirror);
if (!codeMirrorLoader) {
codeMirrorLoader = new Promise(function(resolve, reject) {
var stylesheet = document.createElement('link');
stylesheet.rel = 'stylesheet';
stylesheet.href = L.resource('codemirror/lib/codemirror.css');
document.head.appendChild(stylesheet);
var theme = document.createElement('link');
theme.rel = 'stylesheet';
theme.href = L.resource('codemirror/theme/dracula.css');
document.head.appendChild(theme);
var script = document.createElement('script');
script.src = L.resource('codemirror/lib/codemirror.js');
script.onload = function() {
var yaml = document.createElement('script');
yaml.src = L.resource('codemirror/mode/yaml/yaml.js');
yaml.onload = function() { resolve(window.CodeMirror); };
yaml.onerror = reject;
document.head.appendChild(yaml);
};
script.onerror = reject;
document.head.appendChild(script);
});
}
return codeMirrorLoader;
}
return view.extend({
handleSave: null,
handleSaveApply: null,
handleReset: null,
load: function() {
return callManualConfig().catch(function() {
return { content: '', template: '', source: 'error' };
});
},
render: function(initial) {
var sharedStatus = status.render();
var config = initial;
var showingTemplate = false;
var editor;
var message = E('span', { 'style': 'margin-left:0.8em;' }, '');
var configArea = E('textarea', {
'class': 'cbi-input-textarea',
'rows': 20,
'wrap': 'off',
'style': 'width:100%;box-sizing:border-box;font-family:monospace;'
});
var editorWrap = E('div', { 'class': 'agh-editor-wrap' }, configArea);
var templateButton = E('button', {
'class': 'btn cbi-button', 'type': 'button'
}, _('Use template'));
var reloadButton = E('button', {
'class': 'btn cbi-button', 'type': 'button', 'style': 'margin-left:0.6em;'
}, _('Reload Config'));
var saveButton = E('button', {
'class': 'btn cbi-button cbi-button-apply', 'type': 'button', 'style': 'margin-left:0.6em;'
}, _('Save & Apply'));
var passwordInput = E('input', {
'class': 'cbi-input-password', 'type': 'password',
'id': 'agh-web-password', 'autocomplete': 'new-password', 'spellcheck': 'false'
});
var passwordButton = E('button', {
'class': 'btn cbi-button cbi-button-apply', 'type': 'button'
}, _('Change password'));
var passwordMessage = E('span', { 'style': 'margin-left:0.8em;' }, '');
var passwordPanel = E('div', { 'class': 'agh-toolbar agh-password-controls' }, [
E('label', { 'for': 'agh-web-password' }, _('Change browser management password')),
passwordInput, passwordButton, passwordMessage
]);
function showConfig() {
var content = showingTemplate ? config.template : config.content;
if (editor)
editor.setValue(content);
else
configArea.value = content;
templateButton.disabled = !config.template;
}
templateButton.addEventListener('click', function() {
showingTemplate = true;
message.textContent = '';
showConfig();
});
reloadButton.addEventListener('click', function() {
reloadButton.disabled = true;
message.textContent = '';
callManualConfig().then(function(result) {
config = result;
showingTemplate = false;
showConfig();
}).catch(function() {
message.textContent = _('Unable to read config');
}).then(function() {
reloadButton.disabled = false;
});
});
saveButton.addEventListener('click', function() {
var content = editor ? editor.getValue() : configArea.value;
saveButton.disabled = true;
message.textContent = _('Saving...');
callSetManualConfig(content).then(function(result) {
if (!result.saved)
throw new Error(result.log || result.reason || 'save failed');
config.content = content;
config.source = 'current';
showingTemplate = false;
message.textContent = _('Saved');
}).catch(function(error) {
message.textContent = error.message || _('Save failed');
}).then(function() {
saveButton.disabled = false;
});
});
passwordButton.addEventListener('click', function() {
var password = passwordInput.value;
if (!password) {
passwordMessage.textContent = _('New password') + ' ' + _('is empty');
return;
}
passwordButton.disabled = true;
passwordMessage.textContent = _('Changing password...');
loadBcrypt().then(function(bcrypt) {
var hash = bcrypt.hashSync(password);
password = null;
passwordInput.value = '';
return callChangeWebPassword(hash);
}).then(function(result) {
if (!result.changed)
throw new Error(result.reason || 'change failed');
passwordMessage.textContent = _('Password changed');
}).catch(function() {
passwordMessage.textContent = _('Password change failed');
}).then(function() {
passwordButton.disabled = false;
});
});
showConfig();
var page = E('div', {}, [
E('link', { 'rel': 'stylesheet', 'href': L.resource('view/adguardhome/compact.css') }),
sharedStatus.node,
E('div', { 'class': 'cbi-section agh-compact-card agh-manual-card' }, [
editorWrap,
E('div', { 'class': 'agh-manual-toolbar' }, [
passwordPanel,
E('div', { 'class': 'agh-toolbar agh-config-actions' }, [message, templateButton, reloadButton, saveButton])
])
])
]);
loadCodeMirror().then(function(CodeMirror) {
editor = CodeMirror.fromTextArea(configArea, {
mode: 'text/yaml',
lineNumbers: true,
theme: 'dracula',
lineWrapping: true,
matchBrackets: true
});
editor.getWrapperElement().style.fontSize = '15px';
editor.getWrapperElement().style.lineHeight = '1.5';
// Twenty lines at 1.5em, plus CodeMirror's vertical content padding.
editor.setSize(null, 'calc(30em + 8px)');
editor.refresh();
showConfig();
}).catch(function() {
message.textContent = _('Unable to load editor');
});
return page;
}
});
@@ -0,0 +1,204 @@
'use strict';
'require baseclass';
'require poll';
'require rpc';
var callCoreInfo = rpc.declare({
object: 'luci.adguardhome',
method: 'getCoreInfo',
expect: { '': {} },
reject: true
});
var callServiceList = rpc.declare({
object: 'service',
method: 'list',
params: [ 'name', 'verbose' ],
expect: { '': {} },
reject: true
});
var callRedirectStatus = rpc.declare({
object: 'luci.adguardhome',
method: 'getRedirectStatus',
expect: { '': {} },
reject: true
});
function modeLabel(mode) {
return {
none: _('Do not handle DNS'),
'dnsmasq-upstream': _('Run as dnsmasq upstream server'),
redirect: _('Forward port 53 requests through the firewall'),
exchange: _('Use port 53 instead of dnsmasq')
}[mode] || _('Status unavailable');
}
function coreIsRunning(services, binpath) {
var instances = services.AdGuardHome && services.AdGuardHome.instances;
if (!instances)
return false;
return Object.keys(instances).some(function(name) {
var instance = instances[name];
return instance.running === true && Array.isArray(instance.command) && instance.command[0] === binpath;
});
}
return baseclass.extend({
render: function() {
var badgeStyle = 'display:inline-flex;align-items:center;padding:0.4em 0.8em;' +
'border:1px solid;border-radius:999px;font-weight:600;line-height:1.4;';
var coreBadge = E('span', { 'style': badgeStyle }, _('Collecting data...'));
var serviceBadge = E('span', { 'style': badgeStyle }, _('Collecting data...'));
var redirectBadge = E('span', { 'style': badgeStyle }, _('Collecting data...'));
var node = E('div', { 'class': 'cbi-section' }, [
E('div', { 'style': 'display:flex;flex-wrap:wrap;gap:0.6em;padding:0.6em;' }, [
coreBadge, serviceBadge, redirectBadge
])
]);
function setBadge(badge, label, state) {
badge.textContent = label;
badge.style.backgroundColor = state === 'pending' ? '#fef3c7' : state === true ? '#dcfce7' : state === false ? '#fee2e2' : '#e5e7eb';
badge.style.color = state === 'pending' ? '#92400e' : state === true ? '#14532d' : state === false ? '#7f1d1d' : '#374151';
badge.style.borderColor = state === 'pending' ? '#fcd34d' : state === true ? '#86efac' : state === false ? '#fca5a5' : '#d1d5db';
}
function refreshCore() {
return callCoreInfo().then(function(info) {
if (!info.core_exists)
setBadge(coreBadge, _('Core') + ': ' + _('no core'), false);
else if (!info.version)
setBadge(coreBadge, _('Core') + ': ' + _('core error'), false);
else if (!info.config_exists)
setBadge(coreBadge, _('Core') + ': ' + info.version + ' (' + _('no config') + ')', false);
else
setBadge(coreBadge, _('Core') + ': ' + info.version, true);
}).catch(function() {
setBadge(coreBadge, _('Core') + ': ' + _('Status unavailable'), null);
});
}
var storageKey = 'adguardhome.pendingSwitch';
var pending = null;
var generation = 0;
var stable = 0;
var nextRefresh = 0;
var inFlight = null;
try {
var saved = JSON.parse(sessionStorage.getItem(storageKey));
if (saved && typeof saved.mode === 'string' && typeof saved.enabled === 'boolean' &&
typeof saved.started === 'number')
pending = saved;
} catch (e) {}
function remember() {
try {
if (pending)
sessionStorage.setItem(storageKey, JSON.stringify(pending));
else
sessionStorage.removeItem(storageKey);
} catch (e) {}
}
function showPending(running) {
var label = Date.now() - pending.started >= 30000
? _('Switch not completed; check the log') : _('Switching');
if (typeof running === 'boolean')
setBadge(serviceBadge, running ? _('RUNNING') : _('NOT RUNNING'), running);
else
setBadge(serviceBadge, _('Checking service…'), 'pending');
setBadge(redirectBadge, label, 'pending');
}
function beginSwitch(target) {
generation++;
stable = 0;
nextRefresh = 0;
pending = { mode: target.mode, enabled: target.enabled, started: Date.now() };
remember();
showPending();
}
function cancelSwitch() {
generation++;
pending = null;
stable = 0;
nextRefresh = 0;
remember();
return refreshStatus();
}
function matches(info, running, target) {
return !info.busy && info.mode === target.mode && info.enabled === target.enabled &&
info.applied_enabled === target.enabled && running === target.enabled &&
(!target.enabled || info.applied_mode === target.mode) &&
info.redirected === (target.enabled && target.mode !== 'none');
}
function refreshStatus() {
if (inFlight)
return inFlight;
var currentGeneration = generation;
var requestedAt = Date.now();
inFlight = Promise.all([callRedirectStatus(), callServiceList('AdGuardHome', true)]).then(function(results) {
if (currentGeneration !== generation)
return;
var info = results[0];
var running = coreIsRunning(results[1], info.binpath);
// Recover switching feedback after navigation or an apply from another page.
if (!pending && (info.busy || info.mode !== info.applied_mode || info.enabled !== info.applied_enabled))
beginSwitch(info);
if (pending) {
stable = matches(info, running, pending) ? stable + 1 : 0;
// Two consecutive samples avoid declaring a short-lived process healthy.
if (stable < 2) {
showPending(running);
return;
}
pending = null;
remember();
}
setBadge(serviceBadge, running ? _('RUNNING') : _('NOT RUNNING'), running);
var active = info.redirected && running && info.mode === info.applied_mode;
var label = modeLabel(info.mode);
if (!info.enabled)
label += ' (' + _('Disabled') + ')';
else if (info.mode !== 'none' && !active)
label += ' (' + _('Not redirect') + ')';
setBadge(redirectBadge, label,
!info.enabled || info.mode === 'none' ? null : active);
}).catch(function() {
if (currentGeneration !== generation)
return;
stable = 0;
if (pending) {
showPending();
setBadge(serviceBadge, _('Status unavailable'), null);
setBadge(redirectBadge, _('Unable to read switch status') + ' (' + modeLabel(pending.mode) + ')', null);
} else {
setBadge(serviceBadge, _('Status unavailable'), null);
setBadge(redirectBadge, _('Status unavailable'), null);
}
}).then(function() {
nextRefresh = requestedAt + (pending ? 1000 : 3000);
inFlight = null;
});
return inFlight;
}
if (pending)
showPending();
refreshCore();
refreshStatus();
poll.add(function() {
if (Date.now() >= nextRefresh)
return refreshStatus();
}, 1);
return { node: node, refresh: function() {
return Promise.all([refreshCore(), refreshStatus()]);
}, beginSwitch: beginSwitch, cancelSwitch: cancelSwitch };
}
});