update 2026-09-24 16:56:26

This commit is contained in:
action committed 2026-09-24 16:56:26 +08:00
1 parent b31a5c3878
commit e4cee331e9
6885 files changed
+3240523

No files matched your search

+107
View File
@@ -0,0 +1,107 @@
name: build-apk
on:
push:
tags:
- 'v*'
- 'dev-*'
permissions:
contents: write
env:
TARGET: x86
SUBTARGET: 64
PKG_DIR_NAME: luci-app-adguardhome
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: 检出代码
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: 安装依赖
run: |
sudo apt update
sudo apt install -y build-essential clang flex bison g++ gawk \
gcc-multilib g++-multilib gettext git libncurses5-dev libssl-dev \
python3-setuptools rsync swig unzip zlib1g-dev file wget zstd jq
- name: 下载 SDK
run: |
set -euo pipefail
echo "🔍 获取 OpenWrt 25.12 最新版本..."
LATEST_TAG=$(
curl -fsSL https://api.github.com/repos/openwrt/openwrt/releases?per_page=50 \
| jq -r '.[].tag_name' \
| grep -E '^v25\.12\.[0-9]+(-rc[0-9]+)?$' \
| sort -V \
| tail -n 1
)
if [[ -z "${LATEST_TAG}" || "${LATEST_TAG}" == "null" ]]; then
echo "❌ 获取 OpenWrt 25.12.x tag 失败"
exit 1
fi
RELEASE="${LATEST_TAG#v}"
echo "检测到 25.12 系列最新版本: ${RELEASE}"
echo "📥 下载 SDK..."
BASE="https://downloads.openwrt.org/releases/${RELEASE}/targets/${TARGET}/${SUBTARGET}/"
SDK_TARBALL=$(curl -fsSL "$BASE" | grep -o 'openwrt-sdk-[^"]*\.tar\.zst' | head -n1)
mkdir -p _sdk
wget -q "${BASE}${SDK_TARBALL}" -O _sdk/sdk.tar.zst
tar --zstd -xf _sdk/sdk.tar.zst -C _sdk
SDK_DIR="$(find "$PWD/_sdk" -maxdepth 1 -type d -name 'openwrt-sdk-*' -print -quit)"
echo "SDK_DIR=${SDK_DIR}" >> "$GITHUB_ENV"
echo "✅ SDK 已解压到: ${SDK_DIR}"
- name: 构建包
run: |
cd "$SDK_DIR"
mkdir -p package/${PKG_DIR_NAME}
rsync -a --delete "$GITHUB_WORKSPACE/" "package/${PKG_DIR_NAME}/" \
--exclude '.git*' --exclude '_sdk'
./scripts/feeds update -a
./scripts/feeds install luci-base tar
touch .config
sed -i -E '/^(# )?CONFIG_(ALL|ALL_KMODS|ALL_NONSHARED|PACKAGE_TAR_XZ|PACKAGE_luci-app-adguardhome)(=| is not set)/d' .config
printf '%s\n' \
'CONFIG_ALL=n' \
'CONFIG_ALL_KMODS=n' \
'CONFIG_ALL_NONSHARED=n' \
'# CONFIG_PACKAGE_TAR_XZ is not set' \
'CONFIG_PACKAGE_luci-app-adguardhome=y' >> .config
make defconfig
if ! grep -Eq '^CONFIG_PACKAGE_luci-app-adguardhome=(y|m)$' .config; then
echo '::error::luci-app-adguardhome was not enabled after make defconfig'
grep -n 'luci-app-adguardhome' .config || true
sed -n '/^[[:space:]]*config PACKAGE_luci-app-adguardhome$/,/^[[:space:]]*help/p' tmp/.config-package.in
exit 1
fi
make package/compile -j"$(nproc)" V=sc
- name: 上传产物
uses: actions/upload-artifact@v4
with:
name: pkg-${{ github.ref_name }}
path: _sdk/**/bin/packages/**/${{ env.PKG_DIR_NAME }}*
if-no-files-found: error
- name: 发布到 Releases
uses: softprops/action-gh-release@v3
with:
tag_name: ${{ github.ref_name }}
prerelease: ${{ startsWith(github.ref_name, 'dev-') }}
make_latest: ${{ startsWith(github.ref_name, 'dev-') && 'false' || 'true' }}
files: _sdk/**/bin/packages/**/${{ env.PKG_DIR_NAME }}*
+1
View File
@@ -0,0 +1 @@
.DS_Store
+86
View File
@@ -0,0 +1,86 @@
#
# Copyright (C) 2025 w9315273
#
# This is free software, licensed under the MIT License.
#
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-adguardhome
PKG_VERSION:=2.4.1
PKG_RELEASE:=1
PKG_LICENSE:=MIT
PKG_LICENSE_FILES:=LICENSE
PKG_MAINTAINER:=w9315273
PKG_BUILD_DEPENDS:=luci-base/host
include $(INCLUDE_DIR)/package.mk
define Package/luci-app-adguardhome
SECTION:=luci
CATEGORY:=LuCI
SUBMENU:=3. Applications
TITLE:=LuCI app for AdGuardHome
PKG_MAINTAINER:=https://github.com/w9315273/luci-app-adguardhome
PKGARCH:=all
DEPENDS:=+luci-base +rpcd-mod-ucode +uclient-fetch +tar
endef
define Package/luci-app-adguardhome/description
LuCI support for AdGuardHome (nftables-based DNS redirect).
This package does NOT include the AdGuardHome core binary.
endef
define Build/Prepare
endef
define Build/Compile
endef
define Package/luci-app-adguardhome/conffiles
/etc/config/AdGuardHome
endef
define Package/luci-app-adguardhome/install
cp -pR ./root/* $(1)/
$(INSTALL_DIR) $(1)/usr/lib/lua/luci/i18n
po2lmo ./po/zh-cn/AdGuardHome.po \
$(1)/usr/lib/lua/luci/i18n/AdGuardHome.zh-cn.lmo
endef
define Package/luci-app-adguardhome/postinst
#!/bin/sh
[ -n "$${IPKG_INSTROOT}" ] && exit 0
/etc/init.d/AdGuardHome enable >/dev/null 2>&1
enable=$$(uci get AdGuardHome.AdGuardHome.enabled 2>/dev/null)
if [ "$$enable" = "1" ]; then
/etc/init.d/AdGuardHome reload
fi
rm -f /tmp/luci-indexcache
rm -f /tmp/luci-modulecache/*
/etc/init.d/rpcd reload
exit 0
endef
define Package/luci-app-adguardhome/prerm
#!/bin/sh
if [ -z "$${IPKG_INSTROOT}" ]; then
/etc/init.d/AdGuardHome disable
/etc/init.d/AdGuardHome stop 2>/dev/null || true
uci -q batch <<-EOF >/dev/null 2>&1
delete ucitrack.@AdGuardHome[-1]
commit ucitrack
EOF
fi
exit 0
endef
$(eval $(call BuildPackage,luci-app-adguardhome))
+26
View File
@@ -0,0 +1,26 @@
# luci-app-adguardhome(nft 版)简要说明
面向已熟悉原项目的用户: 仅将 **DNS** 重定向从 `iptables` 迁移到 `nftables`, 核心语义不变
## 变更概览
- `iptables` → `nftables`:使用 nft 应用/清理规则 `/var/etc/adguardhome.nft`
- 模板路径:`/usr/share/AdGuardHome/adguardhome.nft.tpl`
## 模板与默认行为
![01](https://github.com/user-attachments/assets/904948fc-e83e-4246-8f61-b6c978a7e073)
![02](https://github.com/user-attachments/assets/14d51f1f-f7e7-43d4-a91c-cb95a8a9e4ef)
> [!TIP]
> 从 **v2.2.0** 开始,可在 LuCI 界面的 **WAN 接口** 选项中直接选择需要排除的网卡,
> 无需再手动编辑 `adguardhome.nft.tpl` 模板文件。
>
> 该选项 **仅在「重定向」模式下生效**,
> 用于避免将来自公网接口的 DNS 请求重定向到本机,
> 从而防止 AdGuardHome 被暴露为公共 DNS 解析器。
>
> 请仅选择 **直接面向公网的接口**(如 `eth0`、`pppoe-wan` 等)
## 声明
本项目基于 https://github.com/rufengsuixing/luci-app-adguardhome 修改。
原项目未提供明确的开源协议,当前仅用于个人学习研究,不用于商业用途。如原作者有任何异议,请联系我处理。
+34
View File
@@ -0,0 +1,34 @@
#!/bin/sh
set -e
REPO="w9315273/luci-app-adguardhome"
echo "Getting latest version..."
TAG=$(uclient-fetch -qO- "https://api.github.com/repos/${REPO}/releases/latest" 2>/dev/null \
| sed -n 's/.*"tag_name":[[:space:]]*"\([^"]*\)".*/\1/p' | head -n1)
[ -n "$TAG" ] || { echo "ERROR: Failed to get latest version"; exit 1; }
case "$TAG" in
*-*) ;;
*) echo "ERROR: Unexpected tag format: $TAG"; exit 1 ;;
esac
VERSION=${TAG#v}
VERSION=${VERSION%-*}-r${VERSION##*-}
URL="https://github.com/${REPO}/releases/download/${TAG}/luci-app-adguardhome-${VERSION}.apk"
echo "Version: $VERSION"
echo "Downloading..."
trap 'rm -f /tmp/agh.apk' EXIT
uclient-fetch -qO /tmp/agh.apk "$URL"
echo "Installing..."
apk add --allow-untrusted /tmp/agh.apk
echo "Refreshing LuCI..."
rm -f /tmp/luci-indexcache /tmp/luci-modulecache/* 2>/dev/null
/etc/init.d/rpcd reload >/dev/null 2>&1
echo "Done."
@@ -0,0 +1,210 @@
#/cgi-bin/luci/admin/services/AdGuardHome
msgid "Base Setting"
msgstr "基础设置"
msgid "Log"
msgstr "日志"
msgid "Basic Settings"
msgstr "基础设置"
msgid "Manual Config"
msgstr "手动设置"
msgid "Enable"
msgstr "启用"
msgid "Browser management port"
msgstr "网页管理端口"
msgid "Redirect mode"
msgstr "重定向模式"
msgid "AdGuardHome redirect mode"
msgstr "AdGuardHome 重定向模式"
msgid "None"
msgstr "无"
msgid "Run as dnsmasq upstream server"
msgstr "作为 dnsmasq 的上游服务器"
msgid "Redirect port 53 to AdGuardHome"
msgstr "将 53 端口重定向到 AdGuardHome"
msgid "Use port 53 instead of dnsmasq"
msgstr "使用 53 端口替代 dnsmasq"
msgid "WAN Interface"
msgstr "WAN 接口"
msgid "Only effective in redirect mode, bypass specified interfaces to avoid becoming a public DNS resolver"
msgstr "仅在重定向模式下生效,绕过指定接口以避免成为公共 DNS 解析器"
msgid "Bin Path"
msgstr "执行文件路径"
msgid "AdGuardHome binary path"
msgstr "AdGuardHome 执行文件路径"
msgid "Config Path"
msgstr "配置文件路径"
msgid "AdGuardHome config path"
msgstr "AdGuardHome 配置文件路径"
msgid "Work dir"
msgstr "工作目录"
msgid "AdGuardHome work dir including rules, audit log and database"
msgstr "AdGuardHome 工作目录,包含规则、审计日志和数据库"
msgid "Runtime log file"
msgstr "运行日志文件"
msgid "If set to \"syslog\", write to system log; if empty, disable file logging"
msgstr "设为 \"syslog\" 时写入系统日志;留空则禁用文件日志"
msgid "Verbose log"
msgstr "详细日志"
msgid "Upgrade Core"
msgstr "更新核心"
msgid "Update core version"
msgstr "更新核心版本"
msgid "Force update"
msgstr "强制更新"
msgid "Check..."
msgstr "检查中..."
msgid "Updated"
msgstr "已更新"
msgid "Update failed"
msgstr "更新失败"
msgid "Update in progress"
msgstr "更新正在进行中"
msgid "Unable to start update"
msgstr "无法启动更新"
msgid "Update status unavailable"
msgstr "更新状态不可用"
msgid "AdGuardHome Web"
msgstr "AdGuardHome 仪表盘"
msgid "Change browser management password"
msgstr "修改网页登录密码"
msgid "New password"
msgstr "新密码"
msgid "Change password"
msgstr "修改密码"
msgid "is empty"
msgstr "为空"
msgid "Changing password..."
msgstr "正在修改密码..."
msgid "Password changed"
msgstr "密码已修改"
msgid "Password change failed"
msgstr "密码修改失败"
msgid "Core"
msgstr "核心"
msgid "core error"
msgstr "核心错误"
msgid "no core"
msgstr "没有核心"
msgid "no config"
msgstr "没有配置文件"
msgid "RUNNING"
msgstr "运行中"
msgid "NOT RUNNING"
msgstr "未运行"
msgid "Redirect"
msgstr "重定向"
msgid "Redirected"
msgstr "已重定向"
msgid "Not redirect"
msgstr "未重定向"
msgid "Collecting data..."
msgstr "获取数据中..."
msgid "Status unavailable"
msgstr "状态不可用"
msgid "reverse"
msgstr "逆序"
msgid "localtime"
msgstr "本地时间"
msgid "Please add log path in config to enable log"
msgstr "请在设置中填写日志路径以启用日志"
msgid "dellog"
msgstr "删除日志"
msgid "download log"
msgstr "下载日志"
msgid "Use template"
msgstr "使用模板"
msgid "Reload Config"
msgstr "重新载入配置"
msgid "Save & Apply"
msgstr "保存并应用"
msgid "Unable to read config"
msgstr "无法读取配置"
msgid "Unable to load editor"
msgstr "无法加载编辑器"
msgid "Saving..."
msgstr "正在保存..."
msgid "Saved"
msgstr "已保存"
msgid "Save failed"
msgstr "保存失败"
msgid "Core is missing; configuration cannot be validated before saving."
msgstr "核心文件不存在,保存前无法校验配置。"
msgid "Saved without validation (core missing)."
msgstr "已保存,但因核心文件不存在而未校验。"
msgid "Configuration validation failed."
msgstr "配置校验失败。"
msgid "Service reload failed; previous config restored."
msgstr "服务重载失败,已恢复原配置。"
msgid "Restore failed; backup at"
msgstr "恢复失败,备份文件位于"
msgid "Config changed on disk; reload before saving."
msgstr "磁盘上的配置已变化,请重新读取后再保存。"
@@ -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'
+440
View File
@@ -0,0 +1,440 @@
#!/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
}
set_forward_dnsmasq() {
local PORT="$1"
addr="127.0.0.1#$PORT"
OLD_SERVER="$(uci get dhcp.@dnsmasq[0].server 2>/dev/null)"
echo "$OLD_SERVER" | grep -q "^$addr" && return 0
uci delete dhcp.@dnsmasq[0].server 2>/dev/null
uci add_list dhcp.@dnsmasq[0].server="$addr"
for server in $OLD_SERVER; do
[ "$server" = "$addr" ] && continue
uci add_list dhcp.@dnsmasq[0].server="$server"
done
uci delete dhcp.@dnsmasq[0].resolvfile 2>/dev/null
uci set dhcp.@dnsmasq[0].noresolv=1
uci commit dhcp
/etc/init.d/dnsmasq restart >/dev/null 2>&1
}
stop_forward_dnsmasq() {
local OLD_PORT="$1"
addr="127.0.0.1#$OLD_PORT"
OLD_SERVER="$(uci get dhcp.@dnsmasq[0].server 2>/dev/null)"
echo "$OLD_SERVER" | grep -q "^$addr" || return 0
uci del_list dhcp.@dnsmasq[0].server="$addr" 2>/dev/null
addrlist="$(uci get dhcp.@dnsmasq[0].server 2>/dev/null)"
if [ -z "$addrlist" ]; then
resolvfile="/tmp/resolv.conf.d/resolv.conf.auto"
[ ! -f "$resolvfile" ] && resolvfile="/tmp/resolv.conf.auto"
uci set dhcp.@dnsmasq[0].resolvfile="$resolvfile" 2>/dev/null
uci delete dhcp.@dnsmasq[0].noresolv 2>/dev/null
fi
uci commit dhcp
/etc/init.d/dnsmasq restart >/dev/null 2>&1
}
agh_reload() {
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 binpath
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
config_editor "dns.port" "$dnsmasq_port" "$configpath"
uci set dhcp.@dnsmasq[0].port="53"
uci commit dhcp
config_get binpath $CONFIGURATION binpath "/usr/bin/AdGuardHome/AdGuardHome"
killall -9 "$binpath" 2>/dev/null
/etc/init.d/dnsmasq restart
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
config_editor "dns.port" "53" "$configpath"
uci set dhcp.@dnsmasq[0].port="$AdGuardHome_PORT"
uci commit dhcp
/etc/init.d/dnsmasq restart
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
}
_do_redirect() {
local section="$CONFIGURATION"
args=""
ipv6_server=1
tcp_server=0
enabled="$1"
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
stop_forward_dnsmasq "$old_port"
elif [ "$old_redirect" = "exchange" ]; then
rm_port53
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
echo -n "0" > /var/run/AdGredir
return 1
fi
if [ "$redirect" = "redirect" ]; then
set_nft_redirect "$AdGuardHome_PORT"
elif [ "$redirect" = "dnsmasq-upstream" ]; then
set_forward_dnsmasq "$AdGuardHome_PORT"
elif [ "$redirect" = "exchange" ] && [ "$(uci get dhcp.@dnsmasq[0].port 2>/dev/null)" = "53" ]; then
use_port53
fi
mark_redirect_flag "$enabled" "$redirect" "$AdGuardHome_PORT"
}
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() {
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 2>&1 >/tmp/AdGuardHome_update.log &
exit 0
fi
config_get_bool verbose $CONFIGURATION verbose 0
if [ "$verbose" -eq 1 ]; then
ADDITIONAL_ARGS="$ADDITIONAL_ARGS -v"
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
(sleep 5 && _do_redirect 1) &
else
_do_redirect 0
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"
(sleep 10 && [ -z "$(pgrep "$(basename "$binpath")")" ] && logger "AdGuardHome" "no process in 10s cancel redirect" && _do_redirect 0 )&
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
do_redirect
fi
}
reload_service() {
rm -f /var/run/AdGlucitest 2>/dev/null
echo "AdGuardHome reloading"
start
}
stop_service() {
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,11 @@
#!/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
rm -f /tmp/luci-indexcache
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,176 @@
#!/bin/sh
PATH="/usr/sbin:/usr/bin:/sbin:/bin"
update_mode=$1
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_Task(){
running_tasks=$(pgrep -f "update_core.sh" 2>/dev/null | grep -v "^$$\$" | wc -l)
running_tasks=${running_tasks:-0}
case $1 in
force)
printf "%s\n" "执行: 强制更新核心"
if [ "$running_tasks" -gt 0 ]; then
printf "%s\n" "清除 ${running_tasks} 个进程 ..."
pkill -9 -f "update_core.sh" 2>/dev/null
sleep 1
fi
;;
*)
if [ "$running_tasks" -gt 1 ]; then
printf "%s\n" "已经有更新任务正在运行, 请等待其执行结束或点击强制更新!"
EXIT 2
fi
;;
esac
}
Check_Updates(){
downloader_cmd="uclient-fetch"
downloader_opts="--no-check-certificate -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 核心下载失败 ...\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 > /dev/null 2>&1
printf "\n移动 AdGuardHome 核心文件到 %s ...\n" "${binpath%/*}"
mv -f "$downloadbin" "$binpath" > /dev/null 2>&1
if [ ! -s "$binpath" ] && [ $? -ne 0 ]; 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 > /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(){
rm -rf /var/run/update_core $LOCKU 2>/dev/null
[ "$1" -ne 0 ] && touch /var/run/update_core_error
exit $1
}
main(){
Check_Task "$update_mode"
Check_Updates "$update_mode"
}
trap "EXIT 1" SIGTERM SIGINT
touch /var/run/update_core
rm -rf /var/run/update_core_error 2>/dev/null
main
@@ -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/AdGuardHomeJS": {
"title": "AdGuard Home JS",
"order": 12,
"action": {
"type": "firstchild"
},
"depends": {
"acl": [ "luci-app-adguardhome" ]
}
},
"admin/services/AdGuardHomeJS/base": {
"title": "Base Setting",
"order": 1,
"action": {
"type": "view",
"path": "adguardhome/base"
},
"depends": {
"acl": [ "luci-app-adguardhome" ]
}
},
"admin/services/AdGuardHomeJS/log": {
"title": "Log",
"order": 3,
"action": {
"type": "view",
"path": "adguardhome/log"
},
"depends": {
"acl": [ "luci-app-adguardhome" ]
}
},
"admin/services/AdGuardHomeJS/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", "getCoreUpdate", "getInterfaces", "getRuntimeLog", "getManualConfig" ]
},
"uci": [ "AdGuardHome" ]
},
"write": {
"ubus": {
"luci.adguardhome": [ "startCoreUpdate", "changeWebPassword", "clearRuntimeLog", "setManualConfig" ]
},
"uci": [ "AdGuardHome" ]
}
}
}
@@ -0,0 +1,198 @@
#!/usr/bin/ucode
'use strict';
import { stat, popen, readfile, writefile, unlink, lsdir } from 'fs';
import { cursor } from 'uci';
function shellQuote(value) {
return "'" + replace(value, /'/g, "'\\''") + "'";
}
return {
'luci.adguardhome': {
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 = stat('/var/run/update_core') != null;
const failed = stat('/var/run/update_core_error') != 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 (stat('/var/run/update_core') != null)
return { started: false, reason: 'busy' };
unlink('/var/run/update_core_error');
unlink('/tmp/AdGuardHome_update.log');
const command = request.args.force
? '/bin/sh /usr/share/AdGuardHome/update_core.sh force >/tmp/AdGuardHome_update.log 2>&1 </dev/null &'
: '/bin/sh /usr/share/AdGuardHome/update_core.sh >/tmp/AdGuardHome_update.log 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 };
}
},
getRuntimeLog: {
args: { offset: 0 },
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);
}
const log = readfile(source) || '';
const size = length(log);
let offset = +request.args.offset || 0;
if (offset < 0 || offset > size)
offset = 0;
const content = substr(log, offset, 2097152);
return {
enabled: true,
localtime: logfile != 'syslog',
content: content,
offset: offset + length(content)
};
}
},
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,265 @@
'use strict';
'require view';
'require form';
'require poll';
'require rpc';
'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({
load: function() {
return callInterfaces().catch(function() { return { interfaces: [] }; });
},
render: function(data) {
let m, s, o;
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')
);
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',
_('Redirect mode'),
_('AdGuardHome redirect mode')
);
o.value('none', _('None'));
o.value('dnsmasq-upstream', _('Run as dnsmasq upstream server'));
o.value('redirect', _('Redirect port 53 to AdGuardHome'));
o.value('exchange', _('Use port 53 instead of dnsmasq'));
o.default = 'none';
o.rmempty = false;
o = s.option(
form.DynamicList,
'wan_ifname',
_('WAN Interface'),
_('Only effective in redirect mode, bypass specified interfaces to avoid becoming a public DNS resolver')
);
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'),
_('AdGuardHome binary path')
);
o.default = '/usr/bin/AdGuardHome/AdGuardHome';
o.rmempty = false;
o = s.option(
form.Value,
'configpath',
_('Config Path'),
_('AdGuardHome config path')
);
o.default = '/etc/AdGuardHome.yaml';
o.rmempty = false;
o = s.option(
form.Value,
'workdir',
_('Work dir'),
_('AdGuardHome work dir including rules, audit log and database')
);
o.default = '/usr/bin/AdGuardHome';
o.rmempty = false;
o = s.option(
form.Value,
'logfile',
_('Runtime log file'),
_('If set to "syslog", write to system log; if empty, disable file logging')
);
o.rmempty = true;
o = s.option(
form.Flag,
'verbose',
_('Verbose log')
);
o.default = '0';
o.rmempty = true;
return m.render().then(function(node) {
var sharedStatus = status.render();
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', 'style': 'margin-left:0.6em;'
}, _('Force update'));
var updateMessage = E('span', { 'style': 'margin-left:0.8em;' }, '');
var reverseLog = E('input', { 'type': 'checkbox' });
var updateLog = E('textarea', {
'class': 'cbi-input-textarea', 'rows': 8, 'readonly': 'readonly',
'style': 'display:none;width:100%;margin-top:0.6em;box-sizing:border-box;'
});
var logOptions = E('label', { 'style': 'display:none;margin-top:0.6em;' }, [
reverseLog, ' ' + _('reverse')
]);
var updatePanel = E('div', { 'class': 'cbi-section' }, [
E('h3', {}, _('Upgrade Core')),
E('div', { 'style': 'padding:0.6em;' }, [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 = 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);
node.insertBefore(sharedStatus.node, node.firstChild);
sharedStatus.node.parentNode.insertBefore(updatePanel, sharedStatus.node.nextSibling);
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.6em;' }, 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,154 @@
'use strict';
'require view';
'require poll';
'require rpc';
'require view/adguardhome/status as status';
var callRuntimeLog = rpc.declare({
object: 'luci.adguardhome',
method: 'getRuntimeLog',
params: [ 'offset' ],
expect: { '': {} },
reject: true
});
var callClearRuntimeLog = rpc.declare({
object: 'luci.adguardhome',
method: 'clearRuntimeLog',
expect: { '': {} },
reject: true
});
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({
handleSave: null,
handleSaveApply: null,
handleReset: null,
load: function() {
return callRuntimeLog(0).catch(function() {
return { enabled: false, localtime: false, content: '', offset: 0 };
});
},
render: function(initialLog) {
var sharedStatus = status.render();
var content = initialLog.content || '';
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': 32,
'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;margin-left:0.8em;' : 'display:none;' }, [
timeToggle, _('localtime')
]);
var message = E('span', { 'style': 'margin-left:0.8em;' }, '');
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', 'style': 'margin-left:0.6em;'
}, _('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;
callClearRuntimeLog().then(function(result) {
if (!result.cleared)
throw new Error(result.reason || 'clear failed');
content = '';
offset = 0;
message.textContent = '';
renderLog();
}).catch(function() {
message.textContent = _('Status unavailable');
}).then(function() {
clearButton.disabled = false;
});
});
downloadButton.addEventListener('click', downloadLog);
var page = E('div', { 'class': 'cbi-map' }, [
sharedStatus.node,
logArea,
E('div', { 'style': 'display:flex;align-items:center;padding:0.6em;' }, [
reverseLabel, timeLabel
]),
E('div', { 'style': 'padding:0 0.6em 0.6em;' }, [clearButton, downloadButton, message])
]);
if (!enabled)
message.textContent = _('Please add log path in config to enable log');
renderLog();
poll.add(function() {
return callRuntimeLog(offset).then(function(result) {
enabled = result.enabled;
if (!enabled) {
message.textContent = _('Please add log path in config to enable log');
return;
}
message.textContent = '';
if (result.offset < offset)
content = '';
offset = result.offset;
if (result.content) {
content += result.content;
renderLog();
}
}).catch(function() {
message.textContent = _('Status unavailable');
});
}, 3);
return page;
}
});
@@ -0,0 +1,236 @@
'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': 40,
'wrap': 'off',
'style': 'width:100%;box-sizing:border-box;font-family:monospace;'
});
var editorWrap = E('div', { 'style': 'padding:0.6em;' }, 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',
'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': 'cbi-section' }, [
E('h3', {}, _('Change browser management password')),
E('div', { 'class': 'cbi-value', 'style': 'padding:0.6em 0;' }, [
E('label', { 'class': 'cbi-value-title' }, _('New password')),
E('div', { 'class': 'cbi-value-field' }, [
passwordInput,
E('span', { 'style': 'margin-left:0.6em;' }, 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', {}, [
sharedStatus.node,
E('div', { 'class': 'cbi-section' }, [
E('div', { 'style': 'padding:0.6em;' }, [
templateButton, reloadButton, saveButton, message
]),
editorWrap
]),
passwordPanel
]);
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';
editor.refresh();
showConfig();
}).catch(function() {
message.textContent = _('Unable to load editor');
});
return page;
}
});
@@ -0,0 +1,101 @@
'use strict';
'require baseclass';
'require poll';
'require rpc';
'require fs';
'require uci';
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 loadConfigPromise;
function getConfiguredBinPath() {
if (!loadConfigPromise)
loadConfigPromise = uci.load('AdGuardHome').catch(function() { return null; });
return loadConfigPromise.then(function() {
return uci.get('AdGuardHome', 'AdGuardHome', 'binpath') || '/usr/bin/AdGuardHome/AdGuardHome';
});
}
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 === true ? '#dcfce7' : state === false ? '#fee2e2' : '#e5e7eb';
badge.style.color = state === true ? '#14532d' : state === false ? '#7f1d1d' : '#374151';
badge.style.borderColor = 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);
});
}
refreshCore();
poll.add(function() {
var redirectFlag = fs.read('/var/run/AdGredir').catch(function(error) {
if (error.name === 'NotFoundError')
return '';
throw error;
});
return Promise.all([getConfiguredBinPath(), callServiceList('AdGuardHome', true), redirectFlag]).then(function(results) {
var isRunning = coreIsRunning(results[1], results[0]);
setBadge(serviceBadge, 'AdGuardHome: ' + (isRunning ? _('RUNNING') : _('NOT RUNNING')), isRunning);
setBadge(redirectBadge, _('Redirect') + ': ' + (results[2].trim() === '1' ? _('Redirected') : _('Not redirect')),
results[2].trim() === '1');
}).catch(function() {
setBadge(serviceBadge, _('Status unavailable'), null);
setBadge(redirectBadge, _('Status unavailable'), null);
});
}, 3);
return { node: node, refresh: refreshCore };
}
});