update 2026-11-01 17:54:16

This commit is contained in:
action committed 2026-11-01 17:54:17 +08:00
1 parent 34a307153c
commit db2388ca1f
76 files changed
+4876 -1096

No files matched your search

File renamed without changes.
File renamed without changes.
File renamed without changes.
-70
View File
@@ -1,70 +0,0 @@
name: build-apk
on:
push:
tags:
- 'v*'
- 'dev-*'
workflow_dispatch:
permissions:
contents: write
env:
SDK_DIR: /builder
PKG_DIR_NAME: luci-app-adguardhome-tk
jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 90
strategy:
matrix:
sdk: [x86-64-25.12.6]
container:
image: ghcr.io/openwrt/sdk:${{ matrix.sdk }}
options: --user root
defaults:
run:
shell: bash
steps:
- name: 检出代码
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: 缓存依赖源码下载
uses: actions/cache@v5
with:
path: ${{ env.SDK_DIR }}/dl
key: adguardhome-dl-v2-${{ runner.os }}-${{ matrix.sdk }}-${{ hashFiles('Makefile', '.github/workflows/build-apk.yaml', 'scripts/*.sh') }}
restore-keys: |
adguardhome-dl-v2-${{ runner.os }}-${{ matrix.sdk }}-
- name: 准备 SDK
run: bash scripts/prepare-sdk.sh
- name: 构建包
run: runuser -u buildbot -- bash scripts/build-apk.sh
- name: 整理 APK 产物
run: bash scripts/collect-apks.sh
- name: 上传 APK 压缩包
uses: actions/upload-artifact@v7
with:
name: adguardhome-apks
path: dist/*.apk
archive: true
if-no-files-found: error
- name: 发布到 Releases
if: startsWith(github.ref, 'refs/tags/')
uses: softprops/action-gh-release@v3
with:
tag_name: ${{ github.ref_name }}
prerelease: ${{ startsWith(github.ref_name, 'dev-') }}
make_latest: 'false'
fail_on_unmatched_files: true
files: dist/*.apk
+8 -1
View File
@@ -1,2 +1,9 @@
.DS_Store .DS_Store
.local
# Local debug artifacts from playwright-cli UI verification
adg-*.png
m-*.png
bs-*.png
*.png
!screenshots/*.png
.playwright-cli/
+55 -85
View File
@@ -1,109 +1,79 @@
# Copyright (C) 2018-2024 OpenWrt luci-app-adguardhome contributors
# #
# Copyright (C) 2025 w9315273 # This is free software, licensed under the Apache License, Version 2.0 .
# #
include $(TOPDIR)/rules.mk include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-adguardhome-tk PKG_NAME:=luci-app-adguardhome
PKG_VERSION:=2.6.0 PKG_MAINTAINER:=kenzok8
PKG_RELEASE:=3 PKG_VERSION:=1.0
PKG_RELEASE:=9
PKG_MAINTAINER:=w9315273 LUCI_TITLE:=LuCI app for AdGuardHome
PKG_BUILD_DEPENDS:=luci-base/host LUCI_PKGARCH:=all
LUCI_DEPENDS:=+ca-certs +curl +wget-ssl +PACKAGE_$(PKG_NAME)_INCLUDE_binary:adguardhome
# luci-compat only exists in ucode-based LuCI (24.10+)
ifneq ($(wildcard $(TOPDIR)/feeds/luci/luci-compat/Makefile),)
LUCI_DEPENDS+= +luci-compat
endif
LUCI_DESCRIPTION:=LuCI support for AdGuardHome
include $(INCLUDE_DIR)/package.mk define Package/$(PKG_NAME)/config
config PACKAGE_$(PKG_NAME)_INCLUDE_binary
define Package/luci-app-adguardhome-tk bool "Include Binary File"
SECTION:=luci default n
CATEGORY:=LuCI help
SUBMENU:=3. Applications When enabled, also pulls in the adguardhome binary package.
TITLE:=LuCI app for AdGuardHome Disabled by default to avoid init.d/config name conflict with
PKG_MAINTAINER:=https://github.com/w9315273/luci-app-adguardhome the adguardhome package (uses lowercase init.d/adguardhome,
PKGARCH:=all while this LuCI app uses /etc/init.d/AdGuardHome). The LuCI
PROVIDES:=luci-app-adguardhome app downloads the binary at runtime, so the package dependency
CONFLICTS:=luci-app-adguardhome is only needed for offline / sysupgrade scenarios.
DEPENDS:=+luci-base +rpcd-mod-ucode +uclient-fetch +jsonfilter +flock +ca-bundle +!(PACKAGE_libustream-openssl||PACKAGE_libustream-wolfssl):libustream-mbedtls +tar
endef endef
define Package/luci-app-adguardhome-tk/description PKG_CONFIG_DEPENDS:= CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_binary
LuCI support for AdGuardHome (nftables-based DNS redirect).
This package does NOT include the AdGuardHome core binary.
endef
define Package/luci-i18n-adguardhome-tk-zh-cn define Package/luci-app-adguardhome/conffiles
SECTION:=luci /usr/share/AdGuardHome/links.txt
CATEGORY:=LuCI
HIDDEN:=1
TITLE:=AdGuardHome - Simplified Chinese translation
PKGARCH:=all
PROVIDES:=luci-i18n-adguardhome-zh-cn
CONFLICTS:=luci-i18n-adguardhome-zh-cn
DEPENDS:=luci-app-adguardhome-tk
DEFAULT:=LUCI_LANG_zh_Hans
endef
define Package/luci-i18n-adguardhome-tk-zh-cn/description
Simplified Chinese translation for luci-app-adguardhome-tk.
endef
define Build/Prepare
endef
define Build/Compile
endef
define Package/luci-app-adguardhome-tk/conffiles
/etc/config/AdGuardHome /etc/config/AdGuardHome
endef endef
define Package/luci-app-adguardhome-tk/install define Package/luci-app-adguardhome/postinst
cp -pR ./root/* $(1)/
endef
define Package/luci-i18n-adguardhome-tk-zh-cn/install
$(INSTALL_DIR) $(1)/etc/uci-defaults
echo "uci set luci.languages.zh_cn='简体中文 (Simplified Chinese)'; uci commit luci" \
> $(1)/etc/uci-defaults/luci-i18n-adguardhome-tk-zh-cn
$(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-tk/postinst
#!/bin/sh #!/bin/sh
[ -n "$${IPKG_INSTROOT}" ] && exit 0 # Stop and disable the lowercase /etc/init.d/adguardhome that ships
# with the adguardhome binary package, so it can not race the LuCI
/etc/init.d/AdGuardHome enable >/dev/null 2>&1 # app's /etc/init.d/AdGuardHome (issue #254).
if [ -x /etc/init.d/adguardhome ] && [ -x /etc/init.d/AdGuardHome ]; then
enable=$$(uci get AdGuardHome.AdGuardHome.enabled 2>/dev/null) /etc/init.d/adguardhome stop >/dev/null 2>&1
if [ "$$enable" = "1" ]; then /etc/init.d/adguardhome disable >/dev/null 2>&1
/etc/init.d/AdGuardHome reload fi
fi /etc/init.d/AdGuardHome enable >/dev/null 2>&1
enable="$(uci get AdGuardHome.AdGuardHome.enabled 2>/dev/null)"
# Remove the former menu definition when upgrading. [ "$enable" = "1" ] && /etc/init.d/AdGuardHome reload >/dev/null 2>&1
rm -f /usr/share/luci/menu.d/luci-app-adguardhome-js.json rm -f /tmp/luci-indexcache
rm -f /tmp/luci-indexcache /tmp/luci-indexcache.* rm -f /tmp/luci-modulecache/* 2>/dev/null
rm -f /tmp/luci-modulecache/*
/etc/init.d/rpcd reload
exit 0 exit 0
endef endef
define Package/luci-app-adguardhome-tk/prerm define Package/luci-app-adguardhome/prerm
#!/bin/sh #!/bin/sh
if [ -z "$${IPKG_INSTROOT}" ]; then if [ -z "$${IPKG_INSTROOT}" ]; then
/etc/init.d/AdGuardHome disable /etc/init.d/AdGuardHome disable >/dev/null 2>&1
/etc/init.d/AdGuardHome stop 2>/dev/null || true /etc/init.d/AdGuardHome stop >/dev/null 2>&1
ucitrack_del() {
uci -q batch <<-EOF >/dev/null 2>&1 local name="$1"
delete ucitrack.@AdGuardHome[-1] local track="$(uci show ucitrack 2>/dev/null | grep -F "$name\." | head -1)"
commit ucitrack [ -n "$track" ] && uci -q del "$track" 2>/dev/null
EOF }
ucitrack_del "AdGuardHome"
[ -n "$(uci show ucitrack 2>/dev/null | grep -c 'AdGuardHome')" ] || true
uci commit ucitrack 2>/dev/null
fi fi
exit 0 exit 0
endef endef
$(eval $(call BuildPackage,luci-app-adguardhome-tk)) include $(TOPDIR)/feeds/luci/luci.mk
$(eval $(call BuildPackage,luci-i18n-adguardhome-tk-zh-cn)) # call BuildPackage - OpenWrt buildroot signature
$(eval $(call BuildPackage,luci-app-adguardhome))
+81
View File
@@ -0,0 +1,81 @@
# luci-app-adguardhome
适用于 OpenWrt / ImmortalWrt 的 AdGuard Home LuCI 控制面板。
> 基于 [rufengsuixing/luci-app-adguardhome](https://github.com/rufengsuixing/luci-app-adguardhome) 维护,
> 针对 **ImmortalWrt 24.10+** 进行了大量 UI 重构和 Bug 修复。
## 兼容性
| 系统 | 状态 |
|------|------|
| ImmortalWrt 24.10 / 25.12 | 主要测试目标,完全兼容 |
| OpenWrt 23.05 | 兼容 |
| OpenWrt 21.02 / 22.03 | 兼容 |
| OpenWrt 19.07 | 兼容(jail 字段回退为 TextValue) |
| OpenWrt 18.06 | 基本兼容(DynamicList 自动回退) |
## 截图
### 概览
![概览](https://raw.githubusercontent.com/kenzok8/kenzok8/main/screenshot/adguardhome/01-overview.png)
### 基础设置
![基础设置](https://raw.githubusercontent.com/kenzok8/kenzok8/main/screenshot/adguardhome/02-base-setting.png)
### 运维
![运维](https://raw.githubusercontent.com/kenzok8/kenzok8/main/screenshot/adguardhome/03-tools.png)
### 日志
![日志](https://raw.githubusercontent.com/kenzok8/kenzok8/main/screenshot/adguardhome/04-log.png)
### 手动配置
![手动配置](https://raw.githubusercontent.com/kenzok8/kenzok8/main/screenshot/adguardhome/05-manual.png)
## 功能
- **概览面板**:服务状态、重定向模式、核心版本、一键启停 + Web 控制台快捷入口
- **基础设置**:启用开关、管理端口、核心更新、5553 重定向(4 种模式)、路径配置、进程参数
- **工具与任务**:改密、sysupgrade 保留文件、关机备份、Crontab 计划任务
- **日志查看**:实时轮询、反向/本地时区切换、下载、清空
- **手动配置**:YAML 编辑器(CodeMirror 可选),保存前二次确认
- **核心自动更新**:GitHub/AdGuard 多源下载 → UPX 压缩 → 重启服务
- **UPX 压缩**:>8MB 二进制自动压缩(支持 x86_64 / arm64 / mips 等架构)
- **计划任务**:自动更新核心、切割日志、IPv6 hosts 同步
- **升级保护**:sysupgrade 保留文件 + 关机备份
## 安装
### Feed 方式
```bash
echo 'src-git adguardhome https://github.com/kenzok78/luci-app-adguardhome' >> feeds.conf.default
./scripts/feeds update -a && ./scripts/feeds install -a -p adguardhome
make package/luci-app-adguardhome/compile V=s
```
### 直接编译
```bash
git clone https://github.com/kenzok78/luci-app-adguardhome.git package/luci-app-adguardhome
make menuconfig # LuCI → Applications → luci-app-adguardhome
make -j$(nproc) V=s
```
## 重定向模式
| 模式 | 说明 |
|------|------|
| 不启用 | 仅运行,不拦截 DNS |
| 作为 dnsmasq 上游 | dnsmasq 转发到 AGH |
| 53 端口劫持 | iptables 重定向到 AGH |
| 替代 dnsmasq | AGH 直接监听 53 |
## 许可证
Apache-2.0
@@ -0,0 +1,360 @@
/* luci-app-adguardhome card-style theme (inspired by luci-app-clashoo) */
.adg-wrap {
--adg-primary-rgb: 0, 122, 255;
--adg-primary: rgba(var(--adg-primary-rgb), 0.9);
--adg-card-bg: rgba(128, 128, 128, 0.04);
--adg-card-border: rgba(128, 128, 128, 0.16);
--adg-card-shadow: 0 4px 20px rgba(0, 0, 0, 0.05);
--adg-card-shadow-hover: 0 6px 24px rgba(0, 0, 0, 0.07);
--adg-radius: 12px;
--adg-label-muted: rgba(60, 66, 76, 0.85);
--adg-input-bg: rgba(128, 128, 128, 0.05);
--adg-input-border: rgba(128, 128, 128, 0.2);
--adg-ok: #2db84a;
--adg-warn: #e0a02f;
--adg-bad: #d05050;
}
/* Dark mode: triggered either by OS preference, or by an explicit class
set from head.htm when the active LuCI theme renders a dark background
regardless of OS preference (Argon mode=dark, bootstrap-dark, etc.) */
@media (prefers-color-scheme: dark) {
.adg-wrap {
--adg-card-bg: rgba(255, 255, 255, 0.05);
--adg-card-border: rgba(255, 255, 255, 0.16);
--adg-input-bg: rgba(255, 255, 255, 0.06);
--adg-input-border: rgba(255, 255, 255, 0.22);
--adg-label-muted: rgba(220, 226, 232, 0.88);
}
}
.adg-wrap.adg-dark {
--adg-card-bg: rgba(255, 255, 255, 0.05);
--adg-card-border: rgba(255, 255, 255, 0.16);
--adg-input-bg: rgba(255, 255, 255, 0.06);
--adg-input-border: rgba(255, 255, 255, 0.22);
--adg-label-muted: rgba(220, 226, 232, 0.88);
}
/* LuCI's selected theme is authoritative. If the OS/browser prefers dark but
Argon/bootstrap is in light mode, keep contrast tuned for a light surface. */
.adg-wrap:not(.adg-dark) {
--adg-card-bg: rgba(255, 255, 255, 0.45);
--adg-card-border: rgba(50, 50, 93, 0.12);
--adg-input-bg: rgba(255, 255, 255, 0.55);
--adg-input-border: rgba(50, 50, 93, 0.18);
--adg-label-muted: rgba(50, 50, 93, 0.78);
color: #32325d;
}
.adg-wrap:not(.adg-dark) .cbi-value-description,
.adg-wrap:not(.adg-dark) .cbi-section-descr,
.adg-wrap:not(.adg-dark) .cbi-map-descr,
.adg-wrap:not(.adg-dark) .adg-ov-card .title,
.adg-wrap:not(.adg-dark) .adg-ov-card .sub {
color: rgba(50, 50, 93, 0.70) !important;
opacity: 1 !important;
}
.adg-wrap:not(.adg-dark) .cbi-value > .cbi-value-title,
.adg-wrap:not(.adg-dark) .cbi-checkbox {
color: rgba(50, 50, 93, 0.82) !important;
opacity: 1 !important;
}
.adg-wrap:not(.adg-dark) .cbi-value-field input[type="text"],
.adg-wrap:not(.adg-dark) .cbi-value-field input[type="password"],
.adg-wrap:not(.adg-dark) .cbi-value-field input[type="number"],
.adg-wrap:not(.adg-dark) .cbi-value-field select,
.adg-wrap:not(.adg-dark) .cbi-value-field textarea {
color: #32325d !important;
-webkit-text-fill-color: #32325d;
opacity: 1 !important;
}
.adg-wrap:not(.adg-dark) .cbi-value-field input:disabled,
.adg-wrap:not(.adg-dark) .cbi-value-field select:disabled,
.adg-wrap:not(.adg-dark) .cbi-value-field textarea:disabled {
color: #5f6f86 !important;
-webkit-text-fill-color: #5f6f86;
opacity: 1 !important;
}
/* Some Argon dark rules set transparent input bg with !important — override
so our inputs stay visible. */
.adg-wrap.adg-dark .cbi-value-field input[type="text"],
.adg-wrap.adg-dark .cbi-value-field input[type="password"],
.adg-wrap.adg-dark .cbi-value-field input[type="number"],
.adg-wrap.adg-dark .cbi-value-field select,
.adg-wrap.adg-dark .cbi-value-field textarea {
background: var(--adg-input-bg) !important;
border-color: var(--adg-input-border) !important;
color: #e6eaf0 !important;
}
/* Help text + section descriptions — Argon paints them #ccc, bootstrap-dark
leaves them as default gray-on-dark which is also low-contrast. */
.adg-wrap.adg-dark .cbi-value-description,
.adg-wrap.adg-dark .cbi-section-descr,
.adg-wrap.adg-dark .cbi-map-descr {
color: rgba(220, 226, 232, 0.78) !important;
}
.adg-wrap.adg-dark .cbi-value > .cbi-value-title {
color: var(--adg-label-muted) !important;
}
.adg-wrap.adg-dark .cbi-section > h3,
.adg-wrap.adg-dark .cbi-section > legend {
color: #e6eaf0 !important;
}
/* Cards are constrained for comfortable reading; form fills its natural width.
Bootstrap theme has sidebar; Argon is full-width. Let the theme decide the
outer container width — we only cap the card content. */
.adg-wrap .cbi-section {
max-width: 1000px;
}
/* every CBI section becomes a card */
.adg-wrap .cbi-section {
background: var(--adg-card-bg);
border: 1px solid var(--adg-card-border);
border-radius: var(--adg-radius);
box-shadow: var(--adg-card-shadow);
padding: 18px 22px;
margin: 16px 0;
transition: box-shadow .2s ease;
}
.adg-wrap .cbi-section:hover {
box-shadow: var(--adg-card-shadow-hover);
}
/* section heading inside card */
.adg-wrap .cbi-section > h3,
.adg-wrap .cbi-section > legend {
background: transparent !important;
border: 0 !important;
box-shadow: none !important;
padding: 0 0 12px 0 !important;
margin: 0 0 12px 0 !important;
font-size: 15px;
font-weight: 600;
letter-spacing: .2px;
border-bottom: 1px solid var(--adg-card-border) !important;
}
/* label column slimmer (default LuCI is ~280px) */
.adg-wrap .cbi-value > .cbi-value-title {
width: 170px;
min-width: 170px;
padding-right: 12px;
color: var(--adg-label-muted);
font-weight: 500;
}
/* inputs */
.adg-wrap .cbi-value-field input[type="text"],
.adg-wrap .cbi-value-field input[type="password"],
.adg-wrap .cbi-value-field input[type="number"],
.adg-wrap .cbi-value-field select,
.adg-wrap .cbi-value-field textarea {
background: var(--adg-input-bg);
border: 1px solid var(--adg-input-border);
border-radius: 8px;
padding: 6px 10px;
max-width: 420px;
width: 100%;
box-shadow: none;
}
.adg-wrap .cbi-value-field textarea {
max-width: 420px;
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
/* long-form textareas need full width (URL lists / YAML editor) */
.adg-wrap .cbi-value-field textarea[id$="downloadlinks"],
.adg-wrap .cbi-value-field textarea[id$="escconf"],
.adg-wrap .cbi-value-field textarea#cbid\.logview\.1\.conf {
max-width: 100%;
}
.adg-wrap .cbi-value-field input:focus,
.adg-wrap .cbi-value-field select:focus,
.adg-wrap .cbi-value-field textarea:focus {
border-color: var(--adg-primary);
outline: none;
box-shadow: 0 0 0 3px rgba(var(--adg-primary-rgb), 0.15);
}
/* help text */
.adg-wrap .cbi-value-description {
color: var(--adg-label-muted);
font-size: 12.5px;
margin-top: 4px;
}
/* between-row separator gone (the card itself is the boundary) */
.adg-wrap .cbi-value {
border-bottom: 0 !important;
padding: 8px 0;
}
/* MultiValue checkbox groups — let items flow inline and wrap naturally */
.adg-wrap .cbi-value-field .cbi-checkbox {
display: inline-flex;
align-items: center;
gap: 4px;
margin-right: 16px;
margin-bottom: 4px;
font-size: 13px;
white-space: nowrap;
}
.adg-wrap .cbi-value-field .cbi-checkbox input[type="checkbox"] {
width: auto;
height: auto;
margin: 0;
flex-shrink: 0;
}
/* DynamicList — compact row layout */
.adg-wrap .cbi-dynlist .cbi-value-title { display: none; }
.adg-wrap .cbi-dynlist .cbi-value { padding: 4px 0; }
.adg-wrap .cbi-dynlist .cbi-value-field { flex: 1; max-width: 420px; }
.adg-wrap .cbi-dynlist .cbi-value-field input[type="text"] { max-width: 100%; }
/* AdGuardHome custom status row */
.adg-status-row {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin: 6px 0 18px 0;
}
.adg-status-pill {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 6px 12px;
border-radius: 999px;
background: var(--adg-card-bg);
border: 1px solid var(--adg-card-border);
font-size: 13px;
color: var(--adg-label-muted);
}
.adg-status-pill .dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--adg-label-muted);
}
.adg-status-pill.ok .dot { background: var(--adg-ok); }
.adg-status-pill.warn .dot { background: var(--adg-warn); }
.adg-status-pill.bad .dot { background: var(--adg-bad); }
.adg-status-pill.ok { color: var(--adg-ok); border-color: rgba(45,184,74,.3); }
.adg-status-pill.bad { color: var(--adg-bad); border-color: rgba(208,80,80,.3); }
/* CBI button color fallback — ensure visibility on themes that use
transparent backgrounds (e.g. Argon light) */
.adg-wrap input.cbi-button-apply,
.adg-wrap button.cbi-button-apply {
background: #007aff !important;
border-color: #007aff !important;
color: #fff !important;
}
.adg-wrap input.cbi-button-apply:hover,
.adg-wrap button.cbi-button-apply:hover {
background: #0064d2 !important;
border-color: #0064d2 !important;
}
.adg-wrap input.cbi-button-remove,
.adg-wrap button.cbi-button-remove {
background: #d05050 !important;
border-color: #d05050 !important;
color: #fff !important;
}
.adg-wrap input.cbi-button-remove:hover,
.adg-wrap button.cbi-button-remove:hover {
background: #b03d3d !important;
border-color: #b03d3d !important;
}
.adg-wrap input.cbi-button-save,
.adg-wrap button.cbi-button-save {
background: #2d7d46 !important;
border-color: #2d7d46 !important;
color: #fff !important;
}
.adg-wrap input.cbi-button-save:hover,
.adg-wrap button.cbi-button-save:hover {
background: #226434 !important;
border-color: #226434 !important;
}
/* collapsible sections */
.adg-wrap .cbi-section.adg-collapsible > h3,
.adg-wrap .cbi-section.adg-collapsible > legend {
cursor: pointer;
position: relative;
padding-right: 28px !important;
user-select: none;
}
.adg-wrap .cbi-section.adg-collapsible > h3::after,
.adg-wrap .cbi-section.adg-collapsible > legend::after {
content: "";
position: absolute;
right: 4px;
top: 50%;
width: 8px;
height: 8px;
border-right: 2px solid var(--adg-label-muted);
border-bottom: 2px solid var(--adg-label-muted);
transform: translateY(-65%) rotate(45deg);
transition: transform .2s ease;
}
.adg-wrap .cbi-section.adg-collapsible.adg-collapsed > h3::after,
.adg-wrap .cbi-section.adg-collapsible.adg-collapsed > legend::after {
transform: translateY(-30%) rotate(-45deg);
}
.adg-wrap .cbi-section.adg-collapsible.adg-collapsed > .cbi-value,
.adg-wrap .cbi-section.adg-collapsible.adg-collapsed > div.cbi-section-node,
.adg-wrap .cbi-section.adg-collapsible.adg-collapsed > fieldset {
display: none;
}
/* mobile tweaks — reset table/flex layout entirely */
@media (max-width: 720px) {
.adg-wrap .cbi-section {
padding: 12px 10px;
margin: 8px 0;
}
/* force flex column, kill any theme table layout */
.adg-wrap .cbi-value {
display: flex !important;
flex-direction: column;
align-items: stretch;
border-bottom: 0 !important;
padding: 6px 0;
}
.adg-wrap .cbi-value > .cbi-value-title {
width: 100% !important;
min-width: 0 !important;
padding: 0 0 3px 0 !important;
flex: none;
text-align: left;
}
.adg-wrap .cbi-value > .cbi-value-field {
width: 100% !important;
flex: none;
}
.adg-wrap .cbi-value-field input[type="text"],
.adg-wrap .cbi-value-field input[type="password"],
.adg-wrap .cbi-value-field input[type="number"],
.adg-wrap .cbi-value-field select {
max-width: 100%;
}
.adg-wrap .cbi-value-field > div {
width: 100%;
}
/* checkboxes — single column */
.adg-wrap .cbi-value-field .cbi-checkbox {
display: flex;
white-space: normal;
margin-right: 0;
margin-bottom: 6px;
width: 100%;
}
/* DynamicList — expand */
.adg-wrap .cbi-dynlist .cbi-value-field { max-width: 100%; }
/* hide tooltips on touch */
.adg-wrap .cbi-tooltip-container,
.adg-wrap .cbi-tooltip {
display: none !important;
}
}
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: Apache-2.0
// NOTE: luci-app-adguardhome 使用多页面 CBI 模板架构,luci 23.05+ 通过 menu.d alias 跳转至 base 子页。
// 此 JS 文件不直接调用,保留供参考。
'use strict';
'require view';
'require rpc';
'require form';
return view.extend({
render: function() {
// 实际界面由 luasrc/model/cbi/AdGuardHome/ 下多个 CBI 模型提供
return E('p', {}, _('Loading…'));
}
});
@@ -0,0 +1,274 @@
module("luci.controller.AdGuardHome", package.seeall)
local fs = require "nixio.fs"
local http = require "luci.http"
local uci = require "luci.model.uci".cursor()
local function service_running()
local binpath = uci:get("AdGuardHome", "AdGuardHome", "binpath") or "/usr/bin/AdGuardHome"
if not fs.access(binpath) then
return false
end
return luci.sys.call("/etc/init.d/AdGuardHome status >/dev/null 2>&1") == 0
end
function index()
-- luci 23.05+ 已通过 menu.d JSON 注册菜单,无需重复注册
if not nixio.fs.access("/usr/share/luci/menu.d/luci-app-adguardhome.json") then
local page = entry({"admin", "services", "AdGuardHome"},
alias("admin", "services", "AdGuardHome", "overview"),
_("AdGuard Home"), 10)
page.dependent = true
page.acl_depends = { "luci-app-adguardhome" }
entry({"admin", "services", "AdGuardHome", "overview"},
cbi("AdGuardHome/overview"), _("概览"), 1).leaf = true
entry({"admin", "services", "AdGuardHome", "base"},
cbi("AdGuardHome/base"), _("基础设置"), 2).leaf = true
entry({"admin", "services", "AdGuardHome", "tools"},
cbi("AdGuardHome/tools"), _("运维"), 3).leaf = true
entry({"admin", "services", "AdGuardHome", "log"},
form("AdGuardHome/log"), _("日志"), 4).leaf = true
entry({"admin", "services", "AdGuardHome", "manual"},
cbi("AdGuardHome/manual"), _("手动配置"), 5).leaf = true
end
-- API 路由在新旧版本均需注册
entry({"admin", "services", "AdGuardHome", "status"},
call("act_status"), nil).leaf = true
entry({"admin", "services", "AdGuardHome", "toggle"},
call("toggle_service"), nil).leaf = true
entry({"admin", "services", "AdGuardHome", "check"},
call("check_update"), nil)
entry({"admin", "services", "AdGuardHome", "doupdate"},
call("do_update"), nil)
entry({"admin", "services", "AdGuardHome", "getlog"},
call("get_log"), nil)
entry({"admin", "services", "AdGuardHome", "dodellog"},
call("do_dellog"), nil)
entry({"admin", "services", "AdGuardHome", "reloadconfig"},
call("reload_config"), nil)
entry({"admin", "services", "AdGuardHome", "gettemplateconfig"},
call("get_template_config"), nil)
end
local function gen_template_config()
local dns_servers = ""
local resolv_auto = "/tmp/resolv.conf.d/resolv.conf.auto"
if fs.access(resolv_auto) then
for line in io.lines(resolv_auto) do
local ns = line:match("^[^#]*nameserver%s+([^%s]+)")
if ns then
dns_servers = dns_servers .. " - " .. ns .. "\n"
end
end
end
local tmpl = "/usr/share/AdGuardHome/AdGuardHome_template.yaml"
local f = io.open(tmpl, "r")
if not f then return "" end
local lines = {}
for line in f:lines() do
if line == "#bootstrap_dns" then
table.insert(lines, dns_servers)
elseif line == "#upstream_dns" then
table.insert(lines, dns_servers)
else
table.insert(lines, line)
end
end
f:close()
return table.concat(lines, "\n")
end
function get_template_config()
http.prepare_content("text/plain; charset=utf-8")
http.write(gen_template_config())
end
function reload_config()
fs.remove("/tmp/AdGuardHometmpconfig.yaml")
http.prepare_content("application/json")
http.write('{}')
end
function act_status()
local result = {}
result.running = service_running()
local redir = fs.readfile("/var/run/AdGredir")
result.redirect = (redir == "1")
http.prepare_content("application/json")
http.write_json(result)
end
function toggle_service()
local enabled = http.formvalue("enabled") == "1" and "1" or "0"
local old_enabled = uci:get("AdGuardHome", "AdGuardHome", "enabled") == "1" and "1" or "0"
local result = {
enabled = (enabled == "1")
}
-- Preflight: when enabling, make sure the binary is actually present.
-- Without this, init.d reload silently fails and the UI only sees an
-- opaque "Operation failed" toast (issue #254).
if enabled == "1" then
local binpath = uci:get("AdGuardHome", "AdGuardHome", "binpath") or "/usr/bin/AdGuardHome"
if not fs.access(binpath) then
result.success = false
result.enabled = (old_enabled == "1")
result.running = false
result.message = "找不到 AdGuardHome 二进制文件(" .. binpath ..
"),请先在「运维」页面下载。"
http.prepare_content("application/json")
http.write_json(result)
return
end
end
uci:set("AdGuardHome", "AdGuardHome", "enabled", enabled)
uci:commit("AdGuardHome")
local rc
if enabled == "1" then
-- Detach the reload from the CGI request so the HTTP call returns
-- immediately while the daemon comes up. Fully redirecting fds + `&`
-- is enough — avoid start-stop-daemon, whose busybox applet is not
-- enabled on some images (e.g. immortalwrt 25.10), where it would
-- exit 127 and make the toggle wrongly report a start failure (#254).
rc = luci.sys.call("/etc/init.d/AdGuardHome reload >/dev/null 2>&1 &")
else
rc = luci.sys.call("/etc/init.d/AdGuardHome stop >/dev/null 2>&1")
end
if enabled == "1" then
result.running = false
else
result.running = service_running()
for _ = 1, 2 do
if not result.running then
break
end
luci.sys.call("sleep 1")
result.running = service_running()
end
end
result.pending = (enabled == "1" and not result.running)
result.success = (rc == 0) and (enabled == "1" or not result.running)
if not result.success then
uci:set("AdGuardHome", "AdGuardHome", "enabled", old_enabled)
uci:commit("AdGuardHome")
result.enabled = (old_enabled == "1")
result.message = enabled == "1" and "AdGuardHome start failed" or "AdGuardHome stop failed"
end
http.prepare_content("application/json")
http.write_json(result)
end
function do_update()
fs.writefile("/var/run/lucilogpos", "0")
http.prepare_content("application/json")
http.write('{}')
local arg = ""
if luci.http.formvalue("force") == "1" then
arg = "force"
end
local script = "/usr/share/AdGuardHome/update_core.sh"
if fs.access("/var/run/update_core") then
if arg == "force" then
-- Security fix: script 路径是固定常量,arg 只能是 "force" 或 "",无注入风险
luci.sys.exec("pkill -f '" .. script .. "' 2>/dev/null; " .. script .. " " .. arg .. " >/tmp/AdGuardHome_update.log 2>&1 &")
end
else
luci.sys.exec(script .. " " .. arg .. " >/tmp/AdGuardHome_update.log 2>&1 &")
end
end
function get_log()
local logfile = uci:get("AdGuardHome", "AdGuardHome", "logfile")
if not logfile or logfile == "" then
http.write("no log available\n")
return
end
if logfile == "syslog" then
if not fs.access("/var/run/AdGuardHomesyslog") then
luci.sys.exec("(/usr/share/AdGuardHome/getsyslog.sh &); sleep 1;")
end
logfile = "/tmp/AdGuardHometmp.log"
fs.writefile("/var/run/AdGuardHomesyslog", "1")
elseif not fs.access(logfile) then
http.write("")
return
end
http.prepare_content("text/plain; charset=utf-8")
local fdp = 0
if fs.access("/var/run/lucilogreload") then
fdp = 0
fs.remove("/var/run/lucilogreload")
else
local pos = fs.readfile("/var/run/lucilogpos")
fdp = tonumber(pos) or 0
end
local f = io.open(logfile, "r")
if not f then
http.write("")
return
end
f:seek("set", fdp)
local content = f:read(2048000) or ""
fdp = f:seek()
f:close()
fs.writefile("/var/run/lucilogpos", tostring(fdp))
http.write(content)
end
function do_dellog()
local logfile = uci:get("AdGuardHome", "AdGuardHome", "logfile")
if logfile and logfile ~= "" and logfile ~= "syslog" then
fs.writefile(logfile, "")
end
http.prepare_content("application/json")
http.write('{}')
end
function check_update()
http.prepare_content("text/plain; charset=utf-8")
local pos = fs.readfile("/var/run/lucilogpos")
local fdp = tonumber(pos) or 0
local f = io.open("/tmp/AdGuardHome_update.log", "r")
if not f then
http.write("")
return
end
f:seek("set", fdp)
local content = f:read(2048000) or ""
fdp = f:seek()
f:close()
fs.writefile("/var/run/lucilogpos", tostring(fdp))
if fs.access("/var/run/update_core") then
http.write(content)
else
http.write(content .. "\0")
end
end
@@ -0,0 +1,244 @@
require("luci.sys")
require("luci.util")
require("io")
local m, s, o, o1
local fs = require "nixio.fs"
local uci = require "luci.model.uci".cursor()
local configpath = uci:get("AdGuardHome", "AdGuardHome", "configpath") or "/etc/AdGuardHome.yaml"
local binpath = uci:get("AdGuardHome", "AdGuardHome", "binpath") or "/usr/bin/AdGuardHome"
local httpport = uci:get("AdGuardHome", "AdGuardHome", "httpport") or "3000"
local function service_running()
if not fs.access(binpath) then
return false
end
return luci.sys.call("/etc/init.d/AdGuardHome status >/dev/null 2>&1") == 0
end
m = Map("AdGuardHome", "AdGuard Home")
m.description = translate("Free and open source, powerful network-wide ads & trackers blocking DNS server.")
-- Inject card-style CSS for AGH pages
-- ============================================================
-- Section 1: Basic Settings
-- ============================================================
s = m:section(NamedSection, "AdGuardHome", "AdGuardHome", "基本设置")
o = s:option(Flag, "enabled", translate("Enable"))
o.default = "0"
o.optional = false
o.template = "AdGuardHome/enable_switch"
o.cfgvalue = function()
return service_running() and "1" or "0"
end
o = s:option(Value, "httpport", translate("Browser management port"))
o.placeholder = "3000"
o.default = "3000"
o.datatype = "port"
o.optional = false
o = s:option(Button, "restart", translate("Update"))
o.inputtitle = translate("Update core version")
o.template = "AdGuardHome/AdGuardHome_check"
o.showfastconfig = not fs.access(configpath)
o = s:option(ListValue, "release_channel", translate("更新通道"),
translate("正式版稳定;预发布版抢先体验 AdGuard 团队 beta 构建。"))
o:value("stable", translate("正式版 (stable)"))
o:value("beta", translate("预发布版 (beta)"))
o.default = "stable"
o.rmempty = false
local port = luci.sys.exec("awk '/^ port:/{print $2;exit}' " .. configpath .. " 2>/dev/null")
if not port or port == "" then
port = "?"
end
o = s:option(ListValue, "redirect", port .. translate("Redirect"), translate("AdGuardHome redirect mode"))
o:value("none", translate("不启用"))
o:value("dnsmasq-upstream", translate("作为 dnsmasq 上游服务器"))
o:value("redirect", translate("将 53 端口劫持到 AdGuardHome"))
o:value("exchange", translate("使用 53 端口替代 dnsmasq"))
o.default = "none"
o.optional = true
-- ============================================================
-- Section 2: Paths & Logs
-- ============================================================
s = m:section(NamedSection, "AdGuardHome", "AdGuardHome", "路径与日志")
o = s:option(Value, "binpath", translate("Bin Path"), translate("AdGuardHome Bin path if no bin will auto download"))
o.default = "/usr/bin/AdGuardHome"
o.datatype = "string"
o.optional = false
o.rmempty = false
o.validate = function(self, value)
if value == "" then return nil end
if fs.stat(value, "type") == "dir" then
fs.rmdir(value)
end
if fs.stat(value, "type") == "dir" then
m.message = (m.message or "") .. "\n" .. translate("Error: bin path is a directory")
return nil
end
return value
end
o = s:option(Value, "configpath", translate("Config Path"), translate("AdGuardHome config path"))
o.default = "/etc/AdGuardHome.yaml"
o.datatype = "string"
o.optional = false
o.rmempty = false
o.validate = function(self, value)
if not value then return nil end
if fs.stat(value, "type") == "dir" then
fs.rmdir(value)
end
if fs.stat(value, "type") == "dir" then
m.message = (m.message or "") .. "\n" .. translate("Error: config path is a directory")
return nil
end
return value
end
o = s:option(Value, "workdir", translate("Work dir"), translate("AdGuardHome work dir include rules,audit log and database"))
o.default = "/etc/AdGuardHome"
o.datatype = "string"
o.optional = false
o.rmempty = false
o.validate = function(self, value)
if value == "" then return nil end
if fs.stat(value, "type") == "reg" then
m.message = (m.message or "") .. "\n" .. translate("Error: work dir is a file")
return nil
end
if value:sub(-1) == "/" then
return value:sub(1, -2)
end
return value
end
o = s:option(Value, "logfile", translate("Runtime log file"), translate("AdGuardHome runtime Log file if 'syslog': write to system log;if empty no log"))
o.datatype = "string"
o.rmempty = true
o.validate = function(self, value)
if value and value ~= "" and fs.stat(value, "type") == "dir" then
fs.rmdir(value)
end
if value and value ~= "" and fs.stat(value, "type") == "dir" then
m.message = (m.message or "") .. "\n" .. translate("Error: log file is a directory")
return nil
end
return value
end
o = s:option(Flag, "verbose", translate("Verbose log"))
o.default = "0"
o.optional = true
-- ============================================================
-- Section 3: Advanced Settings
-- ============================================================
s = m:section(NamedSection, "AdGuardHome", "AdGuardHome", "高级设置")
o = s:option(ListValue, "upxflag", translate("use upx to compress bin after download"))
o:value("", translate("none"))
o:value("-1", translate("compress faster"))
o:value("-9", translate("compress better"))
o:value("--best", translate("compress best(can be slow for big files)"))
o:value("--brute", translate("try all available compression methods & filters [slow]"))
o:value("--ultra-brute", translate("try even more compression variants [very slow]"))
o.default = ""
o.description = translate("bin use less space,but may have compatibility issues")
o.rmempty = true
o = s:option(Value, "user", translate("Service user"), translate("User the service runs under. If empty, defaults to 'adguardhome'."))
o.placeholder = "adguardhome"
o.datatype = "string"
o.rmempty = true
o = s:option(Value, "group", translate("Service group"), translate("Group the service runs under. If empty, defaults to 'adguardhome'."))
o.placeholder = "adguardhome"
o.datatype = "string"
o.rmempty = true
-- Helper:兼容把旧的 newline-separated 字符串读成 table(仅一次性迁移期需要)
local function split_legacy_list(value)
if type(value) ~= "string" or value == "" then return value end
if not value:find("[\r\n]") then return value end
local t = {}
for line in value:gmatch("[^\r\n]+") do
line = line:match("^%s*(.-)%s*$")
if line ~= "" then t[#t+1] = line end
end
return t
end
o = s:option(DynamicList, "jail_mount", translate("只读文件访问"),
translate("AdGuardHome 可以读取的目录或文件路径,每行一项。"))
o.placeholder = "/etc/hosts"
o.rmempty = true
o.cfgvalue = function(self, section)
return split_legacy_list(uci:get("AdGuardHome", section, "jail_mount"))
end
o = s:option(DynamicList, "jail_mount_rw", translate("读写文件访问"),
translate("AdGuardHome 可以读写的目录或文件路径,每行一项。"))
o.placeholder = "/var/log/AdGuardHome"
o.rmempty = true
o.cfgvalue = function(self, section)
return split_legacy_list(uci:get("AdGuardHome", section, "jail_mount_rw"))
end
-- Probe system memory to suggest a sane MiB cap (≈50% of total RAM)
local total_mem_kb = tonumber(luci.sys.exec("awk '/MemTotal/{print $2}' /proc/meminfo 2>/dev/null")) or 0
local mem_suggest_mib = (total_mem_kb > 0) and math.floor(total_mem_kb / 1024 / 2) or 128
o = s:option(Value, "memlimit", translate("内存上限 (MiB)"),
translate("达到阈值时主动触发 GC,避免被系统 OOM 杀进程。留空 = 不限制。建议设为系统内存的 50%。"))
o.datatype = "uinteger"
o.placeholder = translate("推荐 ") .. tostring(mem_suggest_mib)
o.rmempty = true
o = s:option(Flag, "waitonboot", translate("On boot when network ok restart"))
o.default = "1"
o.rmempty = true
-- ============================================================
-- Commit handler
-- ============================================================
fs.writefile("/var/run/lucilogpos", "0")
function m.on_commit(map)
if fs.access("/var/run/AdGserverdis") then
io.popen("/etc/init.d/AdGuardHome reload &")
return
end
local ucitracktest = uci:get("AdGuardHome", "AdGuardHome", "ucitracktest")
if ucitracktest == "1" then
return
elseif ucitracktest == "0" then
io.popen("/etc/init.d/AdGuardHome reload &")
else
if fs.access("/var/run/AdGlucitest") then
uci:set("AdGuardHome", "AdGuardHome", "ucitracktest", "0")
io.popen("/etc/init.d/AdGuardHome reload &")
else
fs.writefile("/var/run/AdGlucitest", "")
if ucitracktest == "2" then
uci:set("AdGuardHome", "AdGuardHome", "ucitracktest", "1")
else
uci:set("AdGuardHome", "AdGuardHome", "ucitracktest", "2")
end
end
uci:save("AdGuardHome")
end
end
return m
@@ -0,0 +1,16 @@
local fs=require"nixio.fs"
local uci=require"luci.model.uci".cursor()
local f,t
f=SimpleForm("logview")
f.reset = false
f.submit = false
t=f:field(TextValue,"conf")
t.rmempty=true
t.rows=20
t.template="AdGuardHome/log"
t.readonly="readonly"
local logfile=uci:get("AdGuardHome","AdGuardHome","logfile") or ""
t.timereplace=(logfile~="syslog" and logfile~="" )
t.pollcheck=logfile~=""
fs.writefile("/var/run/lucilogreload","")
return f
@@ -0,0 +1,121 @@
local m, s, o
local fs = require "nixio.fs"
local uci = require "luci.model.uci".cursor()
local sys = require "luci.sys"
require("string")
require("io")
require("table")
m = Map("AdGuardHome")
local configpath = uci:get("AdGuardHome", "AdGuardHome", "configpath") or "/etc/AdGuardHome.yaml"
local binpath = uci:get("AdGuardHome", "AdGuardHome", "binpath") or "/usr/bin/AdGuardHome"
s = m:section(TypedSection, "AdGuardHome")
s.anonymous = true
s.addremove = false
o = s:option(TextValue, "escconf")
o.rows = 66
o.wrap = "off"
o.rmempty = true
o.cfgvalue = function(self, section)
local tmp = fs.readfile("/tmp/AdGuardHometmpconfig.yaml")
if tmp then return tmp end
local cfg = fs.readfile(configpath)
if cfg then return cfg end
local dns_servers = ""
local resolv_auto = "/tmp/resolv.conf.d/resolv.conf.auto"
if fs.access(resolv_auto) then
for line in io.lines(resolv_auto) do
local ns = line:match("^[^#]*nameserver%s+([^%s]+)")
if ns then
dns_servers = dns_servers .. " - " .. ns .. "\n"
end
end
end
local tmpl = "/usr/share/AdGuardHome/AdGuardHome_template.yaml"
local f = io.open(tmpl, "r")
if not f then return "" end
local lines = {}
for line in f:lines() do
if line == "#bootstrap_dns" then
table.insert(lines, dns_servers)
elseif line == "#upstream_dns" then
table.insert(lines, dns_servers)
else
table.insert(lines, line)
end
end
f:close()
return table.concat(lines, "\n")
end
o.validate = function(self, value)
if not value then return nil end
fs.writefile("/tmp/AdGuardHometmpconfig.yaml", value:gsub("\r\n", "\n"))
if fs.access(binpath) then
local ret = sys.call(binpath .. " -c /tmp/AdGuardHometmpconfig.yaml --check-config 2>/tmp/AdGuardHometest.log")
if ret == 0 then
return value
end
else
return value
end
luci.http.redirect(luci.dispatcher.build_url("admin", "services", "AdGuardHome", "manual"))
return nil
end
o.write = function(self, section, value)
if fs.access("/tmp/AdGuardHometmpconfig.yaml") then
fs.move("/tmp/AdGuardHometmpconfig.yaml", configpath)
end
end
o.remove = function(self, section, value)
fs.writefile(configpath, "")
end
o = s:option(DummyValue, "")
o.anonymous = true
o.template = "AdGuardHome/yamleditor"
if not fs.access(binpath) then
o.description = translate("WARNING: no binary found, config validation will be skipped")
end
if fs.access("/tmp/AdGuardHometest.log") then
local errlog = fs.readfile("/tmp/AdGuardHometest.log")
if errlog and errlog ~= "" then
o = s:option(TextValue, "")
o.readonly = true
o.rows = 5
o.rmempty = true
o.name = ""
o.cfgvalue = function(self, section)
return fs.readfile("/tmp/AdGuardHometest.log")
end
end
end
function m.on_commit(map)
local ucitracktest = uci:get("AdGuardHome", "AdGuardHome", "ucitracktest")
if ucitracktest == "1" then
return
elseif ucitracktest == "0" then
io.popen("/etc/init.d/AdGuardHome reload &")
else
fs.writefile("/var/run/AdGlucitest", "")
end
end
return m
@@ -0,0 +1,16 @@
require("luci.sys")
local m, s
local fs = require "nixio.fs"
local uci = require "luci.model.uci".cursor()
m = Map("AdGuardHome")
m:section(SimpleSection).template = "AdGuardHome/overview"
-- Read-only dashboard — hide form submit / reset buttons
m.submit = false
m.reset = false
m.apply_on_parse = false
return m
@@ -0,0 +1,113 @@
require("luci.sys")
require("luci.util")
local m, s, o, o1
local fs = require "nixio.fs"
local uci = require "luci.model.uci".cursor()
local workdir = uci:get("AdGuardHome", "AdGuardHome", "workdir") or "/etc/AdGuardHome"
m = Map("AdGuardHome", translate("运维"))
m.description = translate("改密、备份、计划任务、下载源")
s = m:section(NamedSection, "AdGuardHome", "AdGuardHome", "")
s.anonymous = true
s.addremove = false
-- Change password ------------------------------------------------
o = s:option(Value, "hashpass", translate("Change browser management password"),
translate("Press load culculate model and culculate finally save/apply"))
o.default = ""
o.datatype = "string"
o.template = "AdGuardHome/AdGuardHome_chpass"
o.rmempty = true
-- Upgrade keep files ---------------------------------------------
o = s:option(MultiValue, "upprotect", translate("Keep files when system upgrade"),
translate("刷固件 (sysupgrade) 时保留这些文件。普通用户按推荐勾选即可:核心 + 配置 + filters + sessions.db + stats.db。日志和 querylog 通常不需要保留(querylog 可能占数十 MB)。"))
o:value("$binpath", translate("核心执行文件 (推荐)"))
o:value("$configpath", translate("配置文件 (必选)"))
o:value("$logfile", translate("日志文件"))
o:value("$workdir/data/sessions.db", translate("登录会话 sessions.db (推荐)"))
o:value("$workdir/data/stats.db", translate("统计 stats.db (推荐)"))
o:value("$workdir/data/querylog.json", translate("查询日志 querylog.json (占空间)"))
o:value("$workdir/data/filters", translate("过滤规则 filters (推荐)"))
o.widget = "checkbox"
o.default = "$binpath $configpath $workdir/data/sessions.db $workdir/data/stats.db $workdir/data/filters"
o.optional = false
-- Backup workdir -------------------------------------------------
o = s:option(MultiValue, "backupfile", translate("Backup workdir files when shutdown"),
translate("关机时把这些文件复制到下方「备份工作目录路径」,下次开机如果 workdir/data 为空会自动恢复。仅当工作目录位于 tmpfs(如 /tmp)时才需要勾选,持久化路径无需备份。"))
o1 = s:option(Value, "backupwdpath", translate("Backup workdir path"))
o:value("filters", "filters")
o:value("stats.db", "stats.db")
o:value("querylog.json", "querylog.json")
o:value("sessions.db", "sessions.db")
o1:depends("backupfile", "filters")
o1:depends("backupfile", "stats.db")
o1:depends("backupfile", "querylog.json")
o1:depends("backupfile", "sessions.db")
if fs.access(workdir .. "/data") then
for name in fs.glob(workdir .. "/data/*") do
local basename = fs.basename(name)
if basename ~= "filters" and basename ~= "stats.db"
and basename ~= "querylog.json" and basename ~= "sessions.db" then
o:value(basename, basename)
o1:depends("backupfile", basename)
end
end
end
o.widget = "checkbox"
o.default = nil
o.optional = false
o.description = translate("Will be restore when workdir/data is empty")
o1.default = "/etc/AdGuardHome"
o1.datatype = "string"
o1.optional = false
o1.validate = function(self, value)
if value and value ~= "" and fs.stat(value, "type") == "reg" then
m.message = (m.message or "") .. "\n" .. translate("Error: backup dir is a file")
return nil
end
if value and value:sub(-1) == "/" then
return value:sub(1, -2)
end
return value
end
-- Crontab tasks --------------------------------------------------
o = s:option(MultiValue, "crontab", translate("Crontab task"),
translate("Please change time and args in crontab"))
o:value("autoupdate", translate("Auto update core"))
o:value("cutquerylog", translate("Auto tail querylog"))
o:value("cutruntimelog", translate("Auto tail runtime log"))
o:value("autohost", translate("Auto update ipv6 hosts and restart adh"))
o.widget = "checkbox"
o.default = nil
o.optional = false
-- Download links -------------------------------------------------
o = s:option(TextValue, "downloadlinks", translate("Download links for update"))
o.optional = false
o.rows = 4
o.wrap = "soft"
o.cfgvalue = function(self, section)
if fs.access("/usr/share/AdGuardHome/links.txt") then
return fs.readfile("/usr/share/AdGuardHome/links.txt")
end
return ""
end
o.write = function(self, section, value)
if value then
fs.writefile("/usr/share/AdGuardHome/links.txt", value:gsub("\r\n", "\n"))
end
end
return m
@@ -0,0 +1,51 @@
<%+AdGuardHome/head%>
<%+cbi/valueheader%>
<script src="<%=resource%>/twin-bcrypt.min.js"></script>
<div style="display:flex;flex-wrap:wrap;align-items:center;gap:6px;">
<input data-update="change"<%=
attr("id", cbid) ..
attr("name", cbid) ..
attr("type", self.password and "password" or "text") ..
attr("class", self.password and "cbi-input-password" or "cbi-input-text") ..
attr("value", self:cfgvalue(section) or self.default) ..
ifattr(self.size, "size") ..
ifattr(self.placeholder, "placeholder") ..
ifattr(self.readonly, "readonly") ..
ifattr(self.maxlength, "maxlength") ..
ifattr(self.datatype, "data-type", self.datatype) ..
ifattr(self.datatype, "data-optional", self.optional or self.rmempty) ..
ifattr(self.combobox_manual, "data-manual", self.combobox_manual) ..
ifattr(#self.keylist > 0, "data-choices", { self.keylist, self.vallist })
%> />
<% if self.password then %><img src="<%=resource%>/cbi/reload.gif" style="vertical-align:middle" title="<%:Reveal/hide password%>" onclick="var e = document.getElementById('<%=cbid%>'); e.type = (e.type=='password') ? 'text' : 'password';" /><% end %>
<input type="button" class="btn cbi-button cbi-button-apply" id="<%=cbid%>.applychpass" value="<%:Load culculate model%>" onclick="return chpass(this)"/>
<input type="button" class="btn cbi-button cbi-button-save" id="<%=cbid%>.copypass" value="<%:Copy%>" style="display:none" onclick="return copypass()"/>
</div>
<script type="text/javascript">//<![CDATA[
var hashpassFieldId = '<%=cbid%>';
function chpass(btn) {
var lv = document.getElementById(hashpassFieldId);
if (!lv || lv.value === '') { btn.value = '<%:is empty%>'; return; }
if (typeof TwinBcrypt === 'undefined') { btn.value = '<%:loading...%>'; return; }
btn.disabled = true;
var hash = TwinBcrypt.hashSync(lv.value);
lv.value = hash;
lv.readOnly = true;
btn.style.display = 'none';
var copyBtn = document.getElementById(hashpassFieldId + '.copypass');
copyBtn.style.display = '';
copyBtn.value = '<%:Copy%>';
}
function copypass() {
var lv = document.getElementById(hashpassFieldId);
if (!lv) return;
lv.select();
try { document.execCommand('copy'); }
catch(e) { navigator.clipboard.writeText(lv.value).catch(function(){}); }
var copyBtn = document.getElementById(hashpassFieldId + '.copypass');
copyBtn.value = '<%:Copied%>';
setTimeout(function(){ copyBtn.value = '<%:Copy%>'; }, 2000);
}
//]]>
</script>
<%+cbi/valuefooter%>
@@ -0,0 +1,145 @@
<style>
/* === AdGuardHome Status Bar === */
.adg-status-bar {
display: flex;
flex-wrap: wrap;
gap: 10px;
align-items: center;
padding: 4px 0;
}
.adg-status-badge {
display: inline-flex;
align-items: center;
gap: 5px;
padding: 3px 10px;
border-radius: 12px;
font-size: 13px;
font-weight: 600;
line-height: 1.5;
}
.adg-status-badge::before {
content: "";
width: 8px;
height: 8px;
border-radius: 50%;
flex-shrink: 0;
}
.adg-status-running {
background: rgba(74,160,101,.15);
color: #2d7d46;
}
.adg-status-running::before {
background: #2d7d46;
box-shadow: 0 0 0 2px rgba(74,160,101,.3);
}
.adg-status-stopped {
background: rgba(220,80,80,.12);
color: #b03030;
}
.adg-status-stopped::before {
background: #b03030;
box-shadow: 0 0 0 2px rgba(220,80,80,.25);
}
.adg-status-redirect-yes {
background: rgba(64,128,196,.12);
color: #2864a0;
}
.adg-status-redirect-yes::before {
background: #2864a0;
box-shadow: 0 0 0 2px rgba(64,128,196,.25);
}
.adg-status-redirect-no {
background: rgba(160,160,160,.10);
color: #888;
}
.adg-status-redirect-no::before {
background: #888;
}
/* === NamedSection card styling === */
.adg-section-card {
background: var(--cbi-background, #fff);
border: 1px solid var(--cbi-border, #e0e0e0);
border-radius: 8px;
margin-bottom: 16px;
overflow: hidden;
}
.adg-section-card .cbi-section-title {
background: var(--cbi-section-heading-bg, #f5f7fa);
padding: 10px 14px;
font-size: 14px;
font-weight: 600;
border-bottom: 1px solid var(--cbi-border, #e0e0e0);
margin: 0;
}
.adg-section-card .cbi-section-title h3 {
margin: 0;
font-size: inherit;
}
/* === TextValue inputs — match text input width === */
.cbi-value-field textarea.cbi-input-textarea {
max-width: 280px;
width: auto;
box-sizing: border-box;
}
/* === Mobile responsive === */
@media screen and (max-width: 600px) {
.adg-status-bar {
gap: 6px;
}
.adg-status-badge {
font-size: 12px;
padding: 2px 8px;
}
.adg-status-badge::before {
width: 6px;
height: 6px;
}
.adg-section-card {
border-radius: 6px;
margin-bottom: 10px;
}
.cbi-value-field .cbi-input-textarea {
max-width: 100%;
box-sizing: border-box;
}
/* fix download links textarea on mobile */
#cbid\\.AdGuardHome\\.AdGuardHome\\.downloadlinks {
max-width: 100% !important;
width: 100% !important;
box-sizing: border-box;
}
}
</style>
<script type="text/javascript">//<![CDATA[
XHR.poll(3, '<%=url([[admin]], [[services]], [[AdGuardHome]], [[status]])%>', null,
function(x, data) {
var tb = document.getElementById('AdGuardHome_status');
if (data && tb) {
var html = '<div class="adg-status-bar">';
if (data.running) {
html += '<span class="adg-status-badge adg-status-running">AdGuardHome <%:RUNNING%></span>';
} else {
html += '<span class="adg-status-badge adg-status-stopped">AdGuardHome <%:NOT RUNNING%></span>';
}
if (data.redirect) {
html += '<span class="adg-status-badge adg-status-redirect-yes"><%:Redirected%></span>';
} else {
html += '<span class="adg-status-badge adg-status-redirect-no"><%:Not redirect%></span>';
}
html += '</div>';
tb.innerHTML = html;
}
}
);
//]]>
</script>
<fieldset class="cbi-section">
<div id="AdGuardHome_status">
<div class="adg-status-bar">
<span class="adg-status-badge adg-status-stopped"><%:Collecting data...%></span>
</div>
</div>
</fieldset>
@@ -0,0 +1,169 @@
<%+AdGuardHome/head%>
<%+cbi/valueheader%>
<%
local value = self:cfgvalue(section)
local checked = value == "1"
local field_id = cbid
%>
<style>
.adg-enable-wrap {
display: inline-flex;
align-items: center;
gap: 10px;
min-height: 30px;
}
.adg-service-switch {
position: relative;
width: 48px;
height: 26px;
border: 0;
border-radius: 999px;
background: rgba(128,128,128,.24);
padding: 2px;
box-shadow: none;
cursor: pointer;
vertical-align: middle;
transition: background .18s ease, opacity .18s ease;
}
.adg-service-switch .adg-service-knob {
position: absolute;
top: 3px;
left: 3px;
width: 20px;
height: 20px;
border-radius: 50%;
background: rgba(255,255,255,.94);
box-shadow: 0 1px 4px rgba(0,0,0,.18);
transition: transform .18s ease, background .18s ease;
}
.adg-service-switch.is-on {
background: rgba(74,160,101,.52);
}
.adg-service-switch.is-on .adg-service-knob {
transform: translateX(22px);
}
.adg-service-switch:disabled {
opacity: .72;
cursor: not-allowed;
}
.adg-enable-status {
font-size: 12px;
color: rgba(128,128,128,.86);
}
</style>
<div class="adg-enable-wrap">
<input type="hidden" data-update="change"<%=attr("id", field_id) .. attr("name", field_id) .. attr("value", checked and "1" or "0")%> />
<button type="button" class="adg-service-switch <%=checked and 'is-on' or 'is-off'%>"<%=attr("id", field_id .. ".switch") .. attr("aria-pressed", checked and "true" or "false")%> aria-label="<%:Enable%>">
<span class="adg-service-knob"></span>
</button>
<span class="adg-enable-status"<%=attr("id", field_id .. ".status")%>></span>
</div>
<script type="text/javascript">//<![CDATA[
(function() {
var hidden = document.getElementById('<%=field_id%>');
var btn = document.getElementById('<%=field_id%>.switch');
var status = document.getElementById('<%=field_id%>.status');
var current = hidden && hidden.value === '1';
var pollSerial = 0;
function setState(on) {
current = !!on;
if (hidden)
hidden.value = current ? '1' : '0';
if (btn) {
btn.className = 'adg-service-switch ' + (current ? 'is-on' : 'is-off');
btn.setAttribute('aria-pressed', current ? 'true' : 'false');
}
}
function notify(message) {
if (window.L && L.ui && L.ui.addNotification) {
var p = document.createElement('p');
p.appendChild(document.createTextNode(message));
L.ui.addNotification(null, p);
} else {
alert(message);
}
}
function updateStatus(data) {
var tb = document.getElementById('AdGuardHome_status');
if (!data || !tb)
return;
if (data.running)
tb.innerHTML = '<em><b><font color=green>AdGuardHome <%:RUNNING%></font></b></em>';
else
tb.innerHTML = '<em><b><font color=red>AdGuardHome <%:NOT RUNNING%></font></b></em>';
if (data.redirect)
tb.innerHTML += '<em><b><font color=green><%:Redirected%></font></b></em>';
else
tb.innerHTML += '<em><b><font color=red><%:Not redirect%></font></b></em>';
}
function pollTarget(target, serial, left) {
XHR.get('<%=url([[admin]], [[services]], [[AdGuardHome]], [[status]])%>', null, function(_, statusData) {
if (serial !== pollSerial)
return;
updateStatus(statusData);
if (statusData && statusData.running === target) {
if (status)
status.innerHTML = '';
return;
}
if (left <= 0) {
setState(!target);
if (status)
status.innerHTML = '';
notify(target ? '<%:AdGuardHome start failed%>' : '<%:AdGuardHome stop failed%>');
return;
}
window.setTimeout(function() {
pollTarget(target, serial, left - 1);
}, 1000);
});
}
if (!btn || !hidden)
return;
btn.onclick = function(ev) {
if (ev)
ev.preventDefault();
var next = !current;
var previous = current;
var serial = ++pollSerial;
setState(next);
btn.disabled = true;
if (status)
status.innerHTML = next ? '<%:Starting...%>' : '<%:Stopping...%>';
XHR.get('<%=url([[admin]], [[services]], [[AdGuardHome]], [[toggle]])%>', { enabled: next ? 1 : 0 }, function(x, data) {
btn.disabled = false;
if (data && data.success) {
setState(data.enabled);
pollTarget(next, serial, 12);
return;
}
if (status)
status.innerHTML = '';
setState(previous);
var fallback = '<%:Operation failed%>';
if (x && x.status && x.status !== 200)
fallback += ' (HTTP ' + x.status + ')';
notify(data && data.message ? data.message : fallback);
});
return false;
};
})();
//]]>
</script>
<%+cbi/valuefooter%>
@@ -0,0 +1,83 @@
<script>
(function () {
// Load our CSS (CBI strips <link> tags; inject via JS)
var link = document.createElement('link');
link.rel = 'stylesheet';
link.href = '<%=resource%>/adguardhome/adg.css';
document.head.appendChild(link);
// Add .adg-wrap to the Argon main container (exists at page load)
var wrapEl;
(function addWrap() {
wrapEl = document.querySelector('.main') || document.querySelector('#maincontent') || document.body;
wrapEl.classList.add('adg-wrap');
})();
// Dark-mode detection: the LuCI theme (Argon dark / bootstrap-dark) may
// render a dark page regardless of OS prefers-color-scheme. Sample the
// body background and flip .adg-dark when luminance is low.
function detectDark() {
try {
var bg = getComputedStyle(document.body).backgroundColor || '';
var m = bg.match(/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/);
if (!m) return;
var lum = 0.2126 * +m[1] + 0.7152 * +m[2] + 0.0722 * +m[3];
wrapEl.classList.toggle('adg-dark', lum < 128);
} catch (e) {}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', detectDark);
} else {
detectDark();
}
// Re-check shortly after — Argon injects dark CSS late on some pages.
setTimeout(detectDark, 300);
setTimeout(detectDark, 1200);
// Collapsible sections — wait for CBI to render (Argon loads it async)
var COLLAPSE_KEYS = ['路径与日志', '高级设置', 'Paths & Logs', 'Advanced Settings'];
var STORE_KEY = 'adg-collapsed';
function getStore() {
try { return JSON.parse(localStorage.getItem(STORE_KEY) || '{}'); }
catch (e) { return {}; }
}
function setStore(s) {
try { localStorage.setItem(STORE_KEY, JSON.stringify(s)); } catch (e) {}
}
function applyCollapse() {
var store = getStore();
var sections = document.querySelectorAll('.adg-wrap .cbi-section');
if (sections.length === 0) return false;
sections.forEach(function (sec) {
var head = sec.querySelector(':scope > h3, :scope > legend');
if (!head) return;
var title = (head.textContent || '').trim();
if (COLLAPSE_KEYS.indexOf(title) === -1) return;
if (sec.classList.contains('adg-collapsible')) return;
sec.classList.add('adg-collapsible');
var collapsed = (title in store) ? store[title] : true;
if (collapsed) sec.classList.add('adg-collapsed');
head.addEventListener('click', function () {
sec.classList.toggle('adg-collapsed');
store[title] = sec.classList.contains('adg-collapsed');
setStore(store);
});
});
return true;
}
// Try immediately, then retry up to 20 times (10 seconds total)
var tries = 0;
function retry() {
if (applyCollapse()) return;
if (++tries < 40) setTimeout(retry, 250);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function () { setTimeout(retry, 100); });
} else {
setTimeout(retry, 100);
}
})();
</script>
@@ -0,0 +1,186 @@
<%+cbi/valueheader%>
<%+AdGuardHome/head%>
<%+AdGuardHome/log_empty%>
<style>
.adg-log-controls {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
margin-bottom: 6px;
}
.adg-log-controls label {
display: inline-flex;
align-items: center;
gap: 3px;
font-size: 13px;
cursor: pointer;
}
.adg-log-controls input[type="checkbox"] {
vertical-align: middle;
height: auto;
margin: 0;
}
.adg-log-pane {
height: 60vh;
min-height: 360px;
overflow: auto;
padding: 8px 10px;
border: 1px solid rgba(0,0,0,.08);
border-radius: 7px;
background: #1a1d21;
color: #cfd6df;
font-family: ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;
font-size: 11.5px;
line-height: 1.35 !important;
white-space: pre-wrap;
word-break: break-all;
width: 100%;
}
.adg-log-pane .adg-line { padding: 0 4px; border-radius: 3px; display: block; }
.adg-log-pane .adg-info { color: #cfd6df; }
.adg-log-pane .adg-warn { color: #e8b95a; }
.adg-log-pane .adg-error { color: #ea7878; background: rgba(234,120,120,.06); }
.adg-log-pane .adg-debug { color: #7a8290; opacity: .7; }
body.dark .adg-log-pane { border-color: rgba(255,255,255,.1); }
</style>
<div class="adg-log-controls">
<label><input type="checkbox" value="reverse" onclick="return reverselog()" checked><%:reverse%></input></label>
<%if self.timereplace then%>
<label><input type="checkbox" value="localtime" onclick="return chlogtime()" checked><%:localtime%></input></label>
<%end%>
</div>
<pre id="cbid.logview.1.conf" class="adg-log-pane" style="line-height:1.35 !important"></pre>
<div style="display:flex;gap:6px;margin-top:6px;flex-wrap:wrap;">
<input type="button" class="btn cbi-button cbi-button-apply" id="apply_update_button" value="<%:dellog%>" onclick="return apply_del_log()"/>
<input type="button" class="btn cbi-button cbi-button-apply" value="<%:download log%>" onclick="return download_log()"/>
</div>
<script type="text/javascript">//<![CDATA[
var islogreverse = true;
var isutc2local = <%=tostring(self.timereplace)%>;
function hlLine(ln) {
var cls = '';
var l = ln.toLowerCase();
if (l.indexOf('error') >= 0 || l.indexOf('fatal') >= 0 || l.indexOf('panic') >= 0) cls = 'adg-error';
else if (l.indexOf('warn') >= 0) cls = 'adg-warn';
else if (l.indexOf('debug') >= 0 || l.indexOf('trace') >= 0) cls = 'adg-debug';
else cls = 'adg-info';
return '<span class="adg-line ' + cls + '">' + ln.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;') + '</span>';
}
function hlLines(text) {
return text.split('\n').map(hlLine).join('');
}
function createAndDownloadFile(fileName, content) {
var aTag = document.createElement('a');
var blob = new Blob([content]);
aTag.download = fileName;
aTag.href = URL.createObjectURL(blob);
aTag.click();
URL.revokeObjectURL(blob);
}
function download_log(){
var lv = document.getElementById('cbid.logview.1.conf');
var dt = new Date();
var timestamp = (dt.getMonth()+1)+"-"+dt.getDate()+"-"+dt.getHours()+"_"+dt.getMinutes();
createAndDownloadFile("AdGuardHome"+timestamp+".log",lv.textContent)
return
}
function apply_del_log(){
XHR.get('<%=url([[admin]], [[services]], [[AdGuardHome]], [[dodellog]])%>',null,function(x, data){
var lv = document.getElementById('cbid.logview.1.conf');
lv.innerHTML="";
}
);
return
}
function chlogtime(){
var lv = document.getElementById('cbid.logview.1.conf');
var text = lv.textContent;
if (isutc2local){
lv.innerHTML = hlLines(line_toUTC(text).join('\n'));
isutc2local=false;
}else{
lv.innerHTML = hlLines(line_tolocal(text).join('\n'));
isutc2local=true;
}
return
}
function reverselog(){
var lv = document.getElementById('cbid.logview.1.conf');
lv.innerHTML = lv.textContent.split('\n').reverse().map(hlLine).join('');
if (islogreverse){
islogreverse=false;
}else{
islogreverse=true;
}
return
}
function p(s) {
return s < 10 ? '0' + s: s;
}
function line_tolocal(str){
var strt=new Array();
str.trim().split('\n').forEach(function(v, i) {
var dt = new Date(v.substring(0,19)+" UTC");
if (dt != "Invalid Date"){
strt[i]=dt.getFullYear()+"/"+p(dt.getMonth()+1)+"/"+p(dt.getDate())+" "+p(dt.getHours())+":"+p(dt.getMinutes())+":"+p(dt.getSeconds())+v.substring(19);
}else{
strt[i]=v;}})
return strt
}
function line_toUTC(str){
var strt=new Array();
str.trim().split('\n').forEach(function(v, i) {
var dt = new Date(v.substring(0,19))
if (dt != "Invalid Date"){
strt[i]=dt.getUTCFullYear()+"/"+p(dt.getUTCMonth()+1)+"/"+p(dt.getUTCDate())+" "+p(dt.getUTCHours())+":"+p(dt.getUTCMinutes())+":"+p(dt.getUTCSeconds())+v.substring(19);
}else{
strt[i]=v;}})
return strt
}
function displayLog(text) {
var lv = document.getElementById('cbid.logview.1.conf');
if (!lv) return;
if (isutc2local) {
var lines = line_toUTC(text);
if (islogreverse) lines = lines.reverse();
text = lines.join('\n');
text = line_tolocal(text).join('\n');
} else if (islogreverse) {
text = text.split('\n').reverse().join('\n');
}
lv.innerHTML = hlLines(text);
}
function poll_check(){
XHR.poll(3, '<%=url([[admin]], [[services]], [[AdGuardHome]], [[getlog]])%>', null,
function(x, data) {
var lv = document.getElementById('cbid.logview.1.conf');
if (x.responseText && lv) {
var text = x.responseText;
if (isutc2local) {
var lines = line_toUTC(text);
if (islogreverse) lines = lines.reverse();
text = lines.join('\n');
text = line_tolocal(text).join('\n');
} else if (islogreverse) {
text = text.split('\n').reverse().join('\n');
}
if (islogreverse) {
lv.innerHTML = hlLines(text) + lv.innerHTML;
} else {
lv.innerHTML += hlLines(text);
}
}
}
);}
<%if self.pollcheck then%>
poll_check();
<%else%>
var lv = document.getElementById('cbid.logview.1.conf');
lv.innerHTML="<%:Please add log path in config to enable log%>"
<%end%>
//]]>
</script>
<%+cbi/valuefooter%>
@@ -0,0 +1,27 @@
<%
local fs = require "nixio.fs"
local uci = require "luci.model.uci".cursor()
local logfile = uci:get("AdGuardHome", "AdGuardHome", "logfile") or ""
local no_logfile = (logfile == "")
%>
<% if no_logfile then %>
<div class="adg-empty-state warn">
<div class="adg-empty-icon">!</div>
<div class="adg-empty-body">
<div class="adg-empty-title"><%:运行日志未配置%></div>
<div class="adg-empty-desc"><%:请在「基础设置 → 路径与日志 → 运行日志」填入日志路径(如 /tmp/AdGuardHome.log),或填 syslog 写入系统日志。%></div>
<a class="btn cbi-button cbi-button-apply" href="<%=luci.dispatcher.build_url('admin','services','AdGuardHome','base')%>"><%:去设置%></a>
</div>
</div>
<style>
.adg-empty-state { display:flex; align-items:flex-start; gap:14px; padding:14px 18px; margin:6px 0 14px 0; border-radius:10px; background:rgba(128,128,128,0.05); border:1px solid rgba(128,128,128,0.18); }
.adg-empty-state.warn { border-color:rgba(224,160,47,0.4); background:rgba(224,160,47,0.06); }
.adg-empty-icon { font-size:22px; line-height:1; flex:0 0 auto; padding-top:2px; color:#e0a02f; }
.adg-empty-body { flex:1; }
.adg-empty-title { font-weight:600; font-size:14px; margin-bottom:4px; }
.adg-empty-desc { font-size:13px; opacity:.8; margin-bottom:10px; line-height:1.55; }
.adg-empty-state a.btn { display:inline-block; }
</style>
<% end %>
@@ -0,0 +1,286 @@
<%+AdGuardHome/head%>
<%
local fs = require "nixio.fs"
local uci = require "luci.model.uci".cursor()
local sys = require "luci.sys"
local dsp = require "luci.dispatcher"
local binpath = uci:get("AdGuardHome", "AdGuardHome", "binpath") or "/usr/bin/AdGuardHome"
local configpath = uci:get("AdGuardHome", "AdGuardHome", "configpath") or "/etc/AdGuardHome.yaml"
local httpport = uci:get("AdGuardHome", "AdGuardHome", "httpport") or "3000"
local redirect = uci:get("AdGuardHome", "AdGuardHome", "redirect") or "none"
local version = uci:get("AdGuardHome", "AdGuardHome", "version") or "-"
local has_bin = fs.access(binpath)
local has_config = fs.access(configpath)
local toggle_url = dsp.build_url("admin", "services", "AdGuardHome", "toggle")
local base_url = dsp.build_url("admin", "services", "AdGuardHome", "base")
local tools_url = dsp.build_url("admin", "services", "AdGuardHome", "tools")
local log_url = dsp.build_url("admin", "services", "AdGuardHome", "log")
local redirect_label = {
none = translate("未启用"),
["dnsmasq-upstream"] = translate("作为 dnsmasq 上游"),
redirect = translate("53 端口劫持"),
exchange = translate("替代 dnsmasq")
}
local redirect_text = redirect_label[redirect] or redirect
%>
<style>
/* hide LuCI form footer (Save/Apply/Reset) on this read-only dashboard */
body:has(.adg-ov-root) .cbi-page-actions,
body:has(.adg-ov-root) form .cbi-section + .cbi-page-actions { display: none !important; }
.adg-ov-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 14px;
margin-bottom: 16px;
}
.adg-ov-card {
background: var(--adg-card-bg, rgba(128,128,128,0.04));
border: 1px solid var(--adg-card-border, rgba(128,128,128,0.16));
border-radius: 12px;
padding: 16px 18px;
display: flex;
flex-direction: column;
gap: 10px;
}
.adg-ov-card .head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
}
.adg-ov-card .title {
font-size: 13px;
color: var(--adg-label-muted, #666);
font-weight: 500;
}
.adg-ov-card .value {
font-size: 18px;
font-weight: 600;
letter-spacing: .2px;
}
.adg-ov-card .sub {
font-size: 12.5px;
color: var(--adg-label-muted, #888);
}
/* status dot */
.adg-ov-dot {
display: inline-block;
width: 10px; height: 10px;
border-radius: 50%;
background: #b03030;
margin-right: 6px;
vertical-align: middle;
box-shadow: 0 0 0 3px rgba(176,48,48,.18);
}
.adg-ov-dot.ok { background: #2d7d46; box-shadow: 0 0 0 3px rgba(45,125,70,.18); }
.adg-ov-dot.warn { background: #e0a02f; box-shadow: 0 0 0 3px rgba(224,160,47,.18); }
/* big enable switch (iOS-ish) */
.adg-ov-switch {
position: relative;
display: inline-block;
width: 52px; height: 30px;
flex: 0 0 auto;
}
.adg-ov-switch input { display: none; }
.adg-ov-switch .track {
position: absolute;
inset: 0;
border-radius: 999px;
background: rgba(128,128,128,0.25);
transition: background .2s;
cursor: pointer;
}
.adg-ov-switch .knob {
position: absolute;
top: 3px; left: 3px;
width: 24px; height: 24px;
border-radius: 50%;
background: #fff;
box-shadow: 0 2px 4px rgba(0,0,0,.2);
transition: transform .2s;
}
.adg-ov-switch input:checked + .track { background: #2d7d46; }
.adg-ov-switch input:checked + .track .knob { transform: translateX(22px); }
.adg-ov-switch.busy .track { opacity: .6; cursor: wait; }
/* quick actions */
.adg-ov-actions {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin: 10px 0 18px 0;
}
.adg-ov-actions a, .adg-ov-actions button {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 8px 14px;
border-radius: 8px;
font-size: 13px;
border: 1px solid var(--adg-card-border, rgba(128,128,128,0.18));
background: var(--adg-card-bg, rgba(128,128,128,0.04));
color: inherit;
text-decoration: none;
cursor: pointer;
transition: background .15s;
}
.adg-ov-actions a:hover, .adg-ov-actions button:hover {
background: rgba(0,122,255,0.08);
border-color: rgba(0,122,255,0.4);
}
.adg-ov-actions a.primary {
background: #007aff;
border-color: #007aff;
color: #fff;
}
.adg-ov-actions a.primary:hover { background: #0064d2; }
@media (max-width: 720px) {
.adg-ov-card .value { font-size: 16px; }
.adg-ov-actions a, .adg-ov-actions button { flex: 1 1 calc(50% - 5px); justify-content: center; }
}
</style>
<div class="cbi-section adg-ov-root">
<!-- Card grid -->
<div class="adg-ov-grid">
<!-- Status + Enable -->
<div class="adg-ov-card">
<div class="head">
<div>
<div class="title"><%:服务状态%></div>
<div class="value" id="adg-ov-status">
<span class="adg-ov-dot"></span><%:正在检测…%>
</div>
</div>
<label class="adg-ov-switch" id="adg-ov-switch">
<input type="checkbox" id="adg-ov-toggle">
<span class="track"><span class="knob"></span></span>
</label>
</div>
<div class="sub" id="adg-ov-status-sub">&nbsp;</div>
</div>
<!-- Redirect mode -->
<div class="adg-ov-card">
<div class="title"><%:重定向模式%></div>
<div class="value" id="adg-ov-redir">
<%= redirect_text %>
</div>
<div class="sub" id="adg-ov-redir-sub">
<%:配置「基础设置 → 5553 重定向」%>
</div>
</div>
<!-- Core version -->
<div class="adg-ov-card">
<div class="title"><%:核心版本%></div>
<div class="value">
<% if has_bin then %>
<%= version %>
<% else %>
<span style="color:#b03030;"><%:未安装%></span>
<% end %>
</div>
<div class="sub">
<% if not has_config then %>
<span style="color:#e0a02f;"><%:配置文件缺失%></span>
<% elseif not has_bin then %>
<%:点击「基础设置 → 更新核心版本」下载%>
<% else %>
<%= binpath %>
<% end %>
</div>
</div>
</div>
<!-- Quick actions -->
<div class="adg-ov-actions">
<a class="primary" id="adg-ov-webui" target="_blank" rel="noopener"
href="http://<%=luci.http.getenv('SERVER_NAME') or ''%>:<%=httpport%>/">
<%:打开 Web 控制台%> :<%=httpport%>
</a>
<a href="<%=base_url%>"><%:基础设置%></a>
<a href="<%=tools_url%>"><%:运维%></a>
<a href="<%=log_url%>"><%:查看日志%></a>
</div>
</div>
<script>
(function () {
var STATUS_URL = '<%=dsp.build_url("admin","services","AdGuardHome","status")%>';
var TOGGLE_URL = '<%=toggle_url%>';
var elStatus = document.getElementById('adg-ov-status');
var elStatusSub = document.getElementById('adg-ov-status-sub');
var elToggle = document.getElementById('adg-ov-toggle');
var elSwitch = document.getElementById('adg-ov-switch');
var elRedir = document.getElementById('adg-ov-redir');
var elRedirSub = document.getElementById('adg-ov-redir-sub');
var elWebUI = document.getElementById('adg-ov-webui');
// patch the webui link to use current hostname
if (elWebUI) {
var host = window.location.hostname;
elWebUI.href = 'http://' + host + ':<%=httpport%>/';
}
function paint(data) {
if (!data) return;
// status
if (data.running) {
elStatus.innerHTML = '<span class="adg-ov-dot ok"></span><%:运行中%>';
elStatusSub.textContent = '<%:AdGuardHome 正在处理 DNS 请求%>';
elToggle.checked = true;
} else {
elStatus.innerHTML = '<span class="adg-ov-dot"></span><%:未运行%>';
elStatusSub.textContent = data.message || '<%:服务未启动%>';
elToggle.checked = false;
}
// redirect
if (data.redirect) {
elRedir.innerHTML = '<span class="adg-ov-dot ok"></span><%= redirect_text %>';
} else {
elRedir.innerHTML = '<span class="adg-ov-dot warn"></span><%:未重定向%>';
}
}
function poll() {
XHR.get(STATUS_URL, null, function (x, data) { paint(data); });
}
elToggle.addEventListener('change', function (ev) {
var want = elToggle.checked ? '1' : '0';
elSwitch.classList.add('busy');
elToggle.disabled = true;
XHR.post(TOGGLE_URL, { enabled: want, token: L && L.env ? L.env.token : '' }, function (x, data) {
elSwitch.classList.remove('busy');
elToggle.disabled = false;
if (data) paint(data);
poll();
});
});
// hide bottom form action bar (Save/Apply/Reset) — fallback for browsers without :has
(function hideFooter() {
var bar = document.querySelector('.cbi-page-actions');
if (bar) bar.style.display = 'none';
})();
// initial fetch + 3s poll
poll();
if (typeof XHR.poll === 'function') {
XHR.poll(3, STATUS_URL, null, function (x, data) { paint(data); });
} else {
setInterval(poll, 3000);
}
})();
</script>
@@ -0,0 +1,84 @@
<%+AdGuardHome/head%>
<%+cbi/valueheader%>
<script src="/luci-static/resources/codemirror/lib/codemirror.js"></script>
<link rel="stylesheet" href="/luci-static/resources/codemirror/lib/codemirror.css"/>
<style>
.CodeMirror { line-height: 1.3; font-size: 11.5px; }
.CodeMirror pre.CodeMirror-line,
.CodeMirror pre.CodeMirror-line-like { line-height: 1.3; }
</style>
<script src="/luci-static/resources/codemirror/mode/yaml/yaml.js"></script>
<link rel="stylesheet" href="/luci-static/resources/codemirror/theme/dracula.css"/>
<link rel="stylesheet" href="/luci-static/resources/codemirror/addon/fold/foldgutter.css"/>
<script src="/luci-static/resources/codemirror/addon/fold/foldcode.js"></script>
<script src="/luci-static/resources/codemirror/addon/fold/foldgutter.js"></script>
<script src="/luci-static/resources/codemirror/addon/fold/indent-fold.js"></script>
<script type="text/javascript">//<![CDATA[
var editor = null;
try {
if (typeof CodeMirror !== 'undefined') {
editor = CodeMirror.fromTextArea(document.getElementById("cbid.AdGuardHome.AdGuardHome.escconf"), {
mode: "text/yaml",
styleActiveLine: true,
lineNumbers: true,
theme: "dracula",
lineWrapping: true,
foldGutter: true,
gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"],
matchBrackets: true
});
editor.setSize("100%", null);
}
} catch (e) {
console.warn('CodeMirror init skipped:', e);
}
function reload_config(){
XHR.get('<%=url([[admin]], [[services]], [[AdGuardHome]], [[reloadconfig]])%>', null,
function(x, data) {
location.reload();
});}
function use_template(){
if (!confirm("<%:即将用模板覆盖当前 YAML,未保存的修改将丢失,是否继续?%>")) return;
XHR.get('<%=url([[admin]], [[services]], [[AdGuardHome]], [[gettemplateconfig]])%>', null,
function(x, data) {
if (editor) editor.setValue(x.responseText);
else document.getElementById("cbid.AdGuardHome.AdGuardHome.escconf").value = x.responseText;
});}
// Confirm before "保存并应用" / "保存" on the manual page.
// Buttons are rendered after this script by LuCI form footer, so we
// poll for them and wrap their inline onclick handlers.
(function () {
var msg = "<%:即将覆盖 AdGuardHome.yaml,请确认已备份配置。继续保存?%>";
function wrap() {
var targets = document.querySelectorAll('.cbi-button-save, .cbi-button-apply');
var wrapped = 0;
targets.forEach(function (btn) {
if (btn.__adgWrapped) return;
btn.__adgWrapped = true;
wrapped++;
var original = btn.onclick;
btn.onclick = function (ev) {
if (!confirm(msg)) {
if (ev) ev.preventDefault();
return false;
}
if (typeof original === 'function') return original.call(btn, ev);
};
});
return wrapped;
}
var attempts = 0;
var iv = setInterval(function () {
attempts++;
if (wrap() > 0 || attempts > 40) clearInterval(iv);
}, 100);
})();
//]]>
</script>
<%fs=require"nixio.fs"%>
<%if fs.access("/tmp/AdGuardHometmpconfig.yaml") then%>
<input type="button" id="apply_update_button" value="<%:Reload Config%>" onclick=" return reload_config() "/>
<%end%>
<input type="button" id="template_button" value="<%:Use template%>" onclick=" return use_template() "/>
<%+cbi/valuefooter%>
@@ -0,0 +1,226 @@
bind_host: 0.0.0.0
bind_port: 3000
beta_bind_port: 0
users:
- name: admin
password: $2y$10$vHRcARdPCieYG3RXWomV5evDYN.Nj/edtwEkQgQJZcK6z7qTLaIc6
auth_attempts: 5
block_auth_min: 15
http_proxy: ""
language: zh-cn
rlimit_nofile: 0
debug_pprof: false
web_session_ttl: 720
dns:
bind_hosts:
- 0.0.0.0
port: 5553
statistics_interval: 1
querylog_enabled: true
querylog_file_enabled: true
querylog_interval: 1
querylog_size_memory: 1000
anonymize_client_ip: false
protection_enabled: true
blocking_mode: default
blocking_ipv4: ""
blocking_ipv6: ""
blocked_response_ttl: 10
parental_block_host: family-block.dns.adguard.com
safebrowsing_block_host: standard-block.dns.adguard.com
ratelimit: 0
ratelimit_whitelist: []
refuse_any: false
upstream_dns:
- 223.5.5.5
upstream_dns_file: ""
bootstrap_dns:
- 119.29.29.29
- 223.5.5.5
all_servers: false
fastest_addr: false
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_size: 4194304
cache_ttl_min: 0
cache_ttl_max: 0
cache_optimistic: true
bogus_nxdomain: []
aaaa_disabled: false
enable_dnssec: false
edns_client_subnet: false
max_goroutines: 300
ipset: []
filtering_enabled: true
filters_update_interval: 24
parental_enabled: false
safesearch_enabled: false
safebrowsing_enabled: false
safebrowsing_cache_size: 1048576
safesearch_cache_size: 1048576
parental_cache_size: 1048576
cache_time: 30
rewrites: []
blocked_services: []
upstream_timeout: 10s
local_domain_name: lan
resolve_clients: true
use_private_ptr_resolvers: true
local_ptr_upstreams: []
tls:
enabled: false
server_name: ""
force_https: false
port_https: 443
port_dns_over_tls: 853
port_dns_over_quic: 784
port_dnscrypt: 0
dnscrypt_config_file: ""
allow_unencrypted_doh: false
strict_sni_check: false
certificate_chain: ""
private_key: ""
certificate_path: ""
private_key_path: ""
filters:
- enabled: true
url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt
name: AdGuard DNS filter
id: 1228750870
- enabled: true
url: http://sub.adtchrome.com/adt-chinalist-easylist.txt
name: 广告终结者使用的拦截规则,基于ChinaList+EasyList修正维护
id: 139789181
- enabled: true
url: https://easylist-downloads.adblockplus.org/easylist.txt
name: EasyList-去除国际网页中大多数广告,包括不需要的框架、图像和对象
id: 139789112
- enabled: true
url: https://easylist-downloads.adblockplus.org/easylistchina.txt
name: EasyList China-EasyList针对国内的补充规则
id: 139789121
- enabled: true
url: https://anti-ad.net/adguard.txt
name: anti-AD命中率最高列表
id: 139789122
- enabled: true
url: https://raw.iqiq.io/banbendalao/ADgk/master/ADgk.txt
name: ADgk去广告easylistchina补充规则
id: 139789131
- enabled: true
url: https://raw.iqiq.io/banbendalao/ADgk/master/kill-baidu-ad.txt
name: ADgk百度广告过滤
id: 139789133
- enabled: false
url: https://pgl.yoyo.org/adservers/serverlist.php?hostformat=hosts&showintro=1&mimetype=plaintext
name: Peter Lowe’s Ad and tracking server list​
id: 139789152
- enabled: false
url: https://easylist.to/easylist/fanboy-social.txt
name: Fanboy's Social Blocking List
id: 139789153
- enabled: false
url: https://easylist.to/easylist/fanboy-annoyance.txt
name: Fanboy's Annoyance List
id: 139789154
- enabled: false
url: https://secure.fanboy.co.nz/fanboy-cookiemonster.txt
name: EasyList Cookie List
id: 139789155
- enabled: false
url: https://fanboy.co.nz/fanboy-antifacebook.txt
name: Anti-Facebook List
id: 139789161
- enabled: false
url: https://filters.adtidy.org/extension/ublock/filters/14.txt
name: AdGuard Annoyances filter
id: 139789162
- enabled: false
url: https://www.fanboy.co.nz/enhancedstats.txt
name: Fanboy's Enhanced Tracking List
id: 139789164
- enabled: false
url: https://filters.adtidy.org/extension/ublock/filters/3.txt
name: AdGuard Tracking Protection filter
id: 139789165
- enabled: false
url: https://easylist.to/easylist/easylist.txt
name: EasyList
id: 139789171
- enabled: false
url: https://filters.adtidy.org/extension/ublock/filters/11.txt
name: AdGuard Mobile Ads filter
id: 139789172
- enabled: false
url: https://filters.adtidy.org/extension/ublock/filters/2_without_easylist.txt
name: AdGuard Base filter
id: 139789173
- enabled: false
url: https://easylist-downloads.adblockplus.org/antiadblockfilters.txt
name: Adblock Warning Removal List
id: 139789174
- enabled: false
url: https://fanboy.co.nz/fanboy-problematic-sites.txt
name: Fanboy's problematic-sites
id: 139789175
- enabled: false
url: https://adaway.org/hosts.txt
name: Adaway HOST
id: 139789187
whitelist_filters: []
user_rules:
- '@@||taobao.com^$important'
- '@@||tmall.com^$important'
- '@@||jd.com^important'
- '@@||flyme.cn^$important'
- '@@||meizu.com^$important'
- '@@||wl.jd.com^$important'
- '@@||flydigi.com^'
- '@@||pv.sohu.com^$important'
- /googleads.$~script,domain=~googleads.github.io
- /pagead/lvz?
- '||google.com/pagead/'
- '||static.doubleclick.net^$domain=youtube.com'
- '||youtube.com/get_midroll_'
- '||5hz.org^'
- '@@||000714.xyz^'
- '@@||blueskyxn.com^'
- '||topnewsfeeds.net^'
- '||nbryb.com^'
- '||superbdolly.com^'
- '||salutationcheerlessdemote.com^'
- '||buildingdoodlesquare.com^'
- '||colonistnobilityheroic.com^'
dhcp:
enabled: false
interface_name: ""
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
clients: []
log_compress: false
log_localtime: false
log_max_backups: 0
log_max_size: 100
log_max_age: 3
log_file: ""
verbose: false
schema_version: 10
@@ -1,11 +1,17 @@
config AdGuardHome 'AdGuardHome' config AdGuardHome 'AdGuardHome'
option enabled '0' option enabled '0'
option httpport '3000' option httpport '3000'
option redirect 'none' option redirect 'none'
option configpath '/etc/AdGuardHome.yaml' option configpath '/etc/AdGuardHome.yaml'
option workdir '/tmp/AdGuardHome' option workdir '/etc/AdGuardHome'
option logfile '/tmp/AdGuardHome.log' option logfile '/tmp/AdGuardHome.log'
option verbose '0' option verbose '0'
option binpath '/usr/bin/AdGuardHome/AdGuardHome' option binpath '/usr/bin/AdGuardHome'
list wan_ifname 'eth0' option upxflag ''
option user ''
option group ''
option advanced_settings '0'
option gc '0'
option maxprocs '0'
option memlimit '0'
File diff suppressed because it is too large. Load diff
+6 -5
View File
@@ -1,14 +1,15 @@
#!/bin/sh #!/bin/sh
chmod +x /etc/init.d/AdGuardHome
uci -q batch <<-EOF >/dev/null 2>&1 uci -q batch <<-EOF >/dev/null 2>&1
delete ucitrack.@AdGuardHome[-1] delete ucitrack.@AdGuardHome[-1]
add ucitrack AdGuardHome add ucitrack AdGuardHome
set ucitrack.@AdGuardHome[-1].init=AdGuardHome set ucitrack.@AdGuardHome[-1].init=AdGuardHome
commit ucitrack commit ucitrack
delete AdGuardHome.AdGuardHome.ucitracktest
/etc/init.d/AdGuardHome restart
EOF EOF
# Remove the former menu and both Lua and ucode menu caches. rm -f /tmp/luci-indexcache
rm -f /usr/share/luci/menu.d/luci-app-adguardhome-js.json
rm -f /tmp/luci-indexcache /tmp/luci-indexcache.* chmod +x /etc/init.d/AdGuardHome /usr/share/AdGuardHome/*
rm -f /tmp/luci-modulecache/*
exit 0 exit 0
+68 -126
View File
@@ -1,123 +1,108 @@
# 模板用户名为 admin 密码为 admin bind_host: 0.0.0.0
# The username is admin and the password is admin. bind_port: 3000
# 保存并应用就可以直接用的配置模板文件 beta_bind_port: 0
# 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: users:
- name: admin - name: root
password: $2y$10$iGkTW98QtGEZn4I/GC0J4eQhfuLQ53v7L4DDVauEJyWylJrXWwWeO password: $2y$10$dwn0hTYoECQMZETBErGlzOId2VANOVsPHsuH13TM/8KnysM5Dh/ve
auth_attempts: 5 auth_attempts: 5
block_auth_min: 15 block_auth_min: 15
http_proxy: "" http_proxy: ""
language: "" language: zh-cn
theme: auto debug_pprof: false
web_session_ttl: 720
dns: dns:
bind_hosts: bind_hosts:
- 0.0.0.0 - 0.0.0.0
port: 3053 port: 1745
statistics_interval: 30
querylog_enabled: true
querylog_file_enabled: true
querylog_interval: 6h
querylog_size_memory: 1000
anonymize_client_ip: false anonymize_client_ip: false
ratelimit: 20 protection_enabled: true
ratelimit_subnet_len_ipv4: 24 blocking_mode: default
ratelimit_subnet_len_ipv6: 56 blocking_ipv4: ""
blocking_ipv6: ""
blocked_response_ttl: 60
parental_block_host: family-block.dns.adguard.com
safebrowsing_block_host: standard-block.dns.adguard.com
ratelimit: 0
ratelimit_whitelist: [] ratelimit_whitelist: []
refuse_any: true refuse_any: false
upstream_dns: upstream_dns:
- https://dns.cloudflare.com/dns-query - 223.5.5.5
- '#'
- '# 对 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: "" upstream_dns_file: ""
bootstrap_dns: bootstrap_dns:
- 1.1.1.1 - 119.29.29.29
- 1.0.0.1 - 223.5.5.5
fallback_dns: all_servers: false
- quic://dns.adguard-dns.com fastest_addr: false
upstream_mode: parallel
fastest_timeout: 1s fastest_timeout: 1s
allowed_clients: [] allowed_clients: []
disallowed_clients: [] disallowed_clients: []
blocked_hosts: blocked_hosts:
- version.bind - version.bind
- id.server - id.server
- hostname.bind - hostname.bind
trusted_proxies: trusted_proxies:
- 127.0.0.0/8 - 127.0.0.0/8
- ::1/128 - ::1/128
cache_enabled: true
cache_size: 4194304 cache_size: 4194304
cache_ttl_min: 0 cache_ttl_min: 0
cache_ttl_max: 0 cache_ttl_max: 0
cache_optimistic: false cache_optimistic: true
cache_optimistic_answer_ttl: 30s
cache_optimistic_max_age: 12h
bogus_nxdomain: [] bogus_nxdomain: []
aaaa_disabled: false aaaa_disabled: false
enable_dnssec: false enable_dnssec: false
edns_client_subnet: edns_client_subnet: false
custom_ip: ""
enabled: false
use_custom: false
max_goroutines: 300 max_goroutines: 300
handle_ddr: true
ipset: [] ipset: []
ipset_file: "" filtering_enabled: true
bootstrap_prefer_ipv6: false filters_update_interval: 24
parental_enabled: false
safesearch_enabled: false
safebrowsing_enabled: false
safebrowsing_cache_size: 1048576
safesearch_cache_size: 1048576
parental_cache_size: 1048576
cache_time: 30
rewrites: []
blocked_services: []
upstream_timeout: 10s upstream_timeout: 10s
private_networks: [] local_domain_name: lan
resolve_clients: true
use_private_ptr_resolvers: true use_private_ptr_resolvers: true
local_ptr_upstreams: [] 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: tls:
enabled: false enabled: false
server_name: "" server_name: ""
force_https: false force_https: false
port_https: 443 port_https: 443
port_dns_over_tls: 853 port_dns_over_tls: 853
port_dns_over_quic: 853 port_dns_over_quic: 784
port_dnscrypt: 0 port_dnscrypt: 0
dnscrypt_config_file: "" dnscrypt_config_file: ""
allow_unencrypted_doh: false allow_unencrypted_doh: false
strict_sni_check: false
certificate_chain: "" certificate_chain: ""
private_key: "" private_key: ""
certificate_path: "" certificate_path: ""
private_key_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: filters:
- enabled: true - enabled: true
url: https://adguardteam.github.io/HostlistsRegistry/assets/filter_1.txt url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt
name: AdGuard DNS filter name: AdGuard DNS filter
id: 1 id: 1628750870
- enabled: true
url: https://anti-ad.net/easylist.txt
name: 'CHN: anti-AD'
id: 1628750871
whitelist_filters: [] whitelist_filters: []
user_rules: [] user_rules: []
dhcp: dhcp:
enabled: false enabled: false
interface_name: "" interface_name: ""
local_domain_name: lan
dhcpv4: dhcpv4:
gateway_ip: "" gateway_ip: ""
subnet_mask: "" subnet_mask: ""
@@ -131,59 +116,16 @@ dhcp:
lease_duration: 86400 lease_duration: 86400
ra_slaac_only: false ra_slaac_only: false
ra_allow_slaac: false ra_allow_slaac: false
filtering: clients: []
blocking_ipv4: "" log_compress: false
blocking_ipv6: "" log_localtime: false
blocked_services: log_max_backups: 0
schedule: log_max_size: 100
time_zone: UTC log_max_age: 3
ids: [] log_file: ""
protection_disabled_until: null verbose: false
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: os:
group: "" group: ""
user: "" user: ""
rlimit_nofile: 0 rlimit_nofile: 0
schema_version: 32 schema_version: 12
+54
View File
@@ -0,0 +1,54 @@
#!/bin/sh
checkmd5(){
local nowmd5
nowmd5="$(md5sum /etc/hosts 2>/dev/null)"
nowmd5="${nowmd5%% *}"
local lastmd5
lastmd5="$(uci get AdGuardHome.AdGuardHome.hostsmd5 2>/dev/null)"
if [ "$nowmd5" != "$lastmd5" ]; then
uci set AdGuardHome.AdGuardHome.hostsmd5="$nowmd5"
uci commit AdGuardHome
[ "$1" != "noreload" ] && /etc/init.d/AdGuardHome reload >/dev/null 2>&1
fi
}
if [ "$1" = "del" ]; then
sed -i '/programaddstart/,/programaddend/d' /etc/hosts
checkmd5 "$2"
exit 0
fi
awk 'BEGIN {
dhcp[""] = ""
}
{
if (FILENAME == "/tmp/dhcp.leases" && (NF >= 4)) {
dhcp[$2] = $4
}
}
END {
cmd = "ip -6 neighbor show 2>/dev/null | grep -v fe80"
while ((cmd | getline line) > 0) {
split(line, f)
if (f[5] in dhcp && f[5] != "") {
print f[1] " " dhcp[f[5]]
}
}
close(cmd)
print "#programaddend"
}' /tmp/dhcp.leases > /tmp/tmphost 2>/dev/null
grep -q "programaddstart" /etc/hosts 2>/dev/null
if [ $? -eq 0 ]; then
sed -i '/programaddstart/,/programaddend/c\#programaddstart' /etc/hosts
sed -i '/programaddstart/r /tmp/tmphost' /etc/hosts
else
{
echo "#programaddstart"
cat /tmp/tmphost
} >> /etc/hosts
fi
rm -f /tmp/tmphost
checkmd5 "$2"
@@ -0,0 +1,8 @@
#!/bin/sh
AdGuardHome_enable=$(uci get AdGuardHome.AdGuardHome.enabled)
redirect=$(uci get AdGuardHome.AdGuardHome.redirect)
if [ $AdGuardHome_enable -eq 1 -a "$redirect" == "redirect" ]; then
/etc/init.d/AdGuardHome do_redirect 1
fi
@@ -4,17 +4,17 @@ PATH="/usr/sbin:/usr/bin:/sbin:/bin"
logread -e AdGuardHome > /tmp/AdGuardHometmp.log logread -e AdGuardHome > /tmp/AdGuardHometmp.log
logread -e AdGuardHome -f >> /tmp/AdGuardHometmp.log & logread -e AdGuardHome -f >> /tmp/AdGuardHometmp.log &
pid=$! pid=$!
echo "1" > /var/run/AdGuardHomesyslog echo "1">/var/run/AdGuardHomesyslog
while true while true
do do
sleep 12 sleep 12
watchdog=$(cat /var/run/AdGuardHomesyslog 2>/dev/null) watchdog=$(cat /var/run/AdGuardHomesyslog)
if [ "${watchdog}x" = "0x" ]; then if [ "$watchdog"x == "0"x ]; then
kill "$pid" kill $pid
rm /tmp/AdGuardHometmp.log rm /tmp/AdGuardHometmp.log
rm /var/run/AdGuardHomesyslog rm /var/run/AdGuardHomesyslog
exit 0 exit 0
else else
echo "0" > /var/run/AdGuardHomesyslog echo "0">/var/run/AdGuardHomesyslog
fi fi
done done
@@ -0,0 +1,3 @@
https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_${Arch}.tar.gz
https://github.com/AdguardTeam/AdGuardHome/releases/download/${latest_ver}/AdGuardHome_linux_${Arch}.tar.gz
https://static.adguard.com/adguardhome/release/AdGuardHome_linux_${Arch}.tar.gz
@@ -0,0 +1,5 @@
#!/bin/sh
tail -n $1 "$2" > /var/run/tailtmp
cat /var/run/tailtmp > "$2"
rm /var/run/tailtmp
@@ -1,164 +1,321 @@
#!/bin/sh #!/bin/sh
PATH="/usr/sbin:/usr/bin:/sbin:/bin" 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. binpath="$(uci get AdGuardHome.AdGuardHome.binpath 2>/dev/null)"
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 if [ -z "$binpath" ]; then
uci set AdGuardHome.AdGuardHome.binpath="/tmp/AdGuardHome/AdGuardHome" uci set AdGuardHome.AdGuardHome.binpath="/tmp/AdGuardHome/AdGuardHome"
binpath="/tmp/AdGuardHome/AdGuardHome" binpath="/tmp/AdGuardHome/AdGuardHome"
fi 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(){ mkdir -p "${binpath%/*}"
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(){ upxflag="$(uci get AdGuardHome.AdGuardHome.upxflag 2>/dev/null)"
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() { cleanup() {
if command -v apk > /dev/null 2>&1; then rm -f /var/run/update_core 2>/dev/null
PM="apk" [ "$1" != "0" ] && touch /var/run/update_core_error
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" exit "$1"
} }
Check_Updates "$update_mode" check_already_running(){
# 先检查锁,再 touch(顺序关键:touch 在函数调用之后执行)
if [ -f /var/run/update_core ]; then
echo "已有任务在运行,退出"
exit 2
fi
}
check_wgetcurl(){
if which curl >/dev/null 2>&1; then
downloader="curl -L -k --retry 2 --connect-timeout 20 -o"
return 0
fi
if which wget-ssl >/dev/null 2>&1; then
downloader="wget-ssl --no-check-certificate -t 2 -T 20 -O"
return 0
fi
if [ -z "$1" ]; then
opkg update >/dev/null 2>&1 || { echo "错误:opkg 更新失败"; cleanup 1; }
fi
if [ -z "$1" ]; then
opkg remove wget wget-nossl --force-depends >/dev/null 2>&1
opkg install wget >/dev/null 2>&1 && check_wgetcurl 1 && return 0
fi
if [ "$1" = "1" ]; then
opkg install curl >/dev/null 2>&1 && check_wgetcurl 2 && return 0
fi
if [ "$1" = "2" ]; then
check_wgetcurl && return 0
fi
echo "错误:缺少 curl 和 wget"
cleanup 1
}
get_arch(){
# 尝试多种方式获取架构:opkg → apk → uname -m
local a
a="$(opkg info kernel 2>/dev/null | grep Architecture | awk '{print $2}')"
[ -z "$a" ] && a="$(apk info --architecture 2>/dev/null)"
[ -z "$a" ] && a="$(uname -m)"
echo "$a"
}
detect_arch(){
local Archt
Archt="$(get_arch)"
case "$Archt" in
i386|i686|i386|x86)
Arch="386"
;;
x86_64|x86-64|amd64)
Arch="amd64"
;;
mipsel|mipsle)
Arch="mipsle"
;;
mips64el|mips64le)
Arch="mips64le"
;;
mips)
Arch="mips"
;;
mips64)
Arch="mips64"
;;
arm|armv7*|armv8*)
Arch="arm"
;;
armeb)
Arch="armeb"
;;
aarch64|arm64)
Arch="arm64"
;;
*)
echo "错误:不支持的架构:$Archt"
cleanup 1
;;
esac
}
check_latest_version(){
check_wgetcurl
release_channel="$(uci get AdGuardHome.AdGuardHome.release_channel 2>/dev/null)"
[ -z "$release_channel" ] && release_channel="stable"
if [ "$release_channel" = "beta" ]; then
echo "正在检查最新版本(预发布通道)..."
# /releases 返回时间倒序的所有 release,含 prerelease。取第一条 = 最新 beta。
local api_url="https://api.github.com/repos/AdguardTeam/AdGuardHome/releases"
else
echo "正在检查最新版本(正式版通道)..."
local api_url="https://api.github.com/repos/AdguardTeam/AdGuardHome/releases/latest"
fi
local api_result
api_result="$($downloader - "$api_url" 2>/dev/null)"
latest_ver="$(echo "$api_result" | grep -oE '"tag_name": *"v[^"]+"' | head -1 | sed 's/.*"v\(.*\)".*/v\1/')"
if [ -z "${latest_ver}" ]; then
echo "检查最新版本失败,请稍后重试"
cleanup 1
fi
if [ -x "$binpath" ]; then
now_ver="$($binpath --version 2>/dev/null | grep -oE 'v[0-9.]+(-[A-Za-z0-9.]+)?' | head -1)"
[ -z "$now_ver" ] && now_ver="$($binpath -c /dev/null --check-config 2>&1 | grep -oE 'v[0-9.]+(-[A-Za-z0-9.]+)?' | head -1)"
else
now_ver=""
fi
echo "本地版本:${now_ver:-无},云端版本:${latest_ver}"
if [ "$1" = "force" ]; then
update_core
elif [ -z "$now_ver" ]; then
update_core
elif [ "$(echo "$latest_ver" | sed 's/[^0-9.]//g')" != "$(echo "$now_ver" | sed 's/[^0-9.]//g')" ]; then
# 只有云端版本号严格大于本地时才更新
local newer
newer="$(printf '%s\n%s' "$latest_ver" "$now_ver" | sed 's/^v//' | sort -Vr | head -1 | sed 's/^/v/')"
if [ "$newer" = "$latest_ver" ] && [ "$latest_ver" != "$now_ver" ]; then
update_core
else
echo "已是最新版本(本地版本更高或相同)"
cleanup 0
fi
else
echo "已是最新版本"
cleanup 0
fi
}
apply_upx(){
[ -z "$upxflag" ] && return
local target="${1:-$binpath}"
[ ! -f "$target" ] && return
local filesize
filesize="$(ls -l "$target" 2>/dev/null | awk '{print $5}')"
[ -z "$filesize" ] && return
[ "$filesize" -le 8000000 ] && return
echo "二进制文件大于 8MB,正在使用 upx 压缩..."
fetch_upx
local UPX_DIR="/tmp/upx-${upx_ver_nov}-${Arch}_linux"
local UPX_BIN="${UPX_DIR}/upx"
[ ! -x "$UPX_BIN" ] && { echo "upx 可执行文件未找到"; return; }
local upx_out="/tmp/AdGuardHomeupdate/upx-packed-$$"
$UPX_BIN $upxflag "$target" -o "$upx_out"
local upxret=$?
rm -rf "$UPX_DIR"
if [ $upxret -eq 0 ]; then
rm -f "$target"
mv -f "$upx_out" "$target"
chmod 755 "$target"
echo "upx 压缩完成"
else
rm -f "$upx_out"
fi
}
fetch_upx(){
local Archt_upx
Archt_upx="$(get_arch)"
case "$Archt_upx" in
i386|i686|x86|i386) Arch="i386";;
x86_64|x86-64|amd64) Arch="amd64";;
mipsel|mipsle) Arch="mipsel";;
mips64el|mips64le) Arch="mipsel";;
mips) Arch="mips";;
mips64) Arch="mips";;
arm|armv7*|armv8*) Arch="arm";;
aarch64|arm64) Arch="arm64";;
*)
echo "upx:不支持的架构 $Archt_upx"
return 1
;;
esac
upx_latest_ver="$($downloader - "https://api.github.com/repos/upx/upx/releases/latest" 2>/dev/null | grep -oE '"tag_name": *"[^"]+"' | head -1 | sed 's/.*"\(.*\)"/\1/')"
if [ -z "$upx_latest_ver" ]; then
echo "获取 upx 版本失败"
return 1
fi
upx_ver_nov="$(echo "$upx_latest_ver" | sed 's/^v//')"
local UPX_TGZ="/tmp/upx-${upx_ver_nov}-${Arch}_linux.tar.xz"
local UPX_URL="https://github.com/upx/upx/releases/download/${upx_latest_ver}/upx-${upx_ver_nov}-${Arch}_linux.tar.xz"
$downloader "$UPX_TGZ" "$UPX_URL" 2>&1
[ $? -ne 0 ] && { echo "upx 下载失败"; return 1; }
which xz >/dev/null 2>&1 || { opkg install xz >/dev/null 2>&1 || apk add xz >/dev/null 2>&1; } || { echo "xz 不可用"; return 1; }
xz -d -c "$UPX_TGZ" | tar -x -C "/tmp" >/dev/null 2>&1
[ ! -x "/tmp/upx-${upx_ver_nov}-${Arch}_linux/upx" ] && { echo "upx 解压失败"; return 1; }
rm -f "$UPX_TGZ"
}
update_core(){
echo "正在更新 AdGuardHome 核心..."
mkdir -p "/tmp/AdGuardHomeupdate"
rm -rf "/tmp/AdGuardHomeupdate/*" 2>/dev/null
detect_arch
echo "架构:$Arch"
echo "正在获取下载链接..."
mkdir -p /tmp/run
# 按通道生成下载源:beta 优先 static beta;stable 优先 static release
if [ "$release_channel" = "beta" ]; then
{
echo "https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_${Arch}.tar.gz"
echo "https://github.com/AdguardTeam/AdGuardHome/releases/download/${latest_ver}/AdGuardHome_linux_${Arch}.tar.gz"
} > /tmp/run/AdHlinks.txt
else
{
echo "https://static.adguard.com/adguardhome/release/AdGuardHome_linux_${Arch}.tar.gz"
echo "https://github.com/AdguardTeam/AdGuardHome/releases/download/${latest_ver}/AdGuardHome_linux_${Arch}.tar.gz"
} > /tmp/run/AdHlinks.txt
fi
local downloadbin=""
local success=""
while read link; do
[ -z "$link" ] && continue
eval link="$link"
echo "尝试下载:$link"
$downloader "/tmp/AdGuardHomeupdate/${link##*/}" "$link" 2>&1
if [ $? -eq 0 ] && [ -s "/tmp/AdGuardHomeupdate/${link##*/}" ]; then
downloadbin="/tmp/AdGuardHomeupdate/${link##*/}"
success="1"
echo "下载成功"
break
else
echo "下载失败,尝试下一个..."
rm -f "/tmp/AdGuardHomeupdate/${link##*/}"
fi
done < /tmp/run/AdHlinks.txt
rm -f /tmp/run/AdHlinks.txt
if [ -z "$success" ]; then
echo "错误:所有下载源均失败"
cleanup 1
fi
if [ "${downloadbin##*.}" = "gz" ]; then
tar -zxf "$downloadbin" -C "/tmp/AdGuardHomeupdate/" 2>/dev/null
if [ -d "/tmp/AdGuardHomeupdate/AdGuardHome" ]; then
downloadbin="/tmp/AdGuardHomeupdate/AdGuardHome/AdGuardHome"
else
echo "错误:解压失败"
cleanup 1
fi
fi
chmod 755 "$downloadbin"
echo "下载完成,如已配置 upx 则进行压缩..."
apply_upx "$downloadbin"
echo "正在停止服务..."
/etc/init.d/AdGuardHome stop nobackup 2>/dev/null
echo "正在安装新二进制文件..."
rm -f "$binpath" 2>/dev/null
mv -f "$downloadbin" "$binpath" 2>/dev/null
[ $? -ne 0 ] && { echo "mv 失败 - 磁盘空间不足?"; cleanup 1; }
chmod 755 "$binpath"
rm -rf "/tmp/AdGuardHomeupdate" 2>/dev/null
echo "正在启动服务..."
/etc/init.d/AdGuardHome start 2>/dev/null
echo "AdGuardHome 已成功更新至 ${latest_ver}"
cleanup 0
}
trap "cleanup 1" SIGTERM SIGINT
check_already_running
touch /var/run/update_core
rm -f /var/run/update_core_error 2>/dev/null
check_latest_version "$1"
+21
View File
@@ -0,0 +1,21 @@
#!/bin/sh
PATH="/usr/sbin:/usr/bin:/sbin:/bin"
count=0
while :; do
for host in www.baidu.com 223.5.5.5 www.google.com 8.8.8.8; do
ping -c 1 -W 2 -q "$host" >/dev/null 2>&1
if [ $? -eq 0 ]; then
/etc/init.d/AdGuardHome force_reload
exit 0
fi
done
count=$((count + 1))
if [ $count -gt 18 ]; then
/etc/init.d/AdGuardHome force_reload
exit 0
fi
sleep 5
done
@@ -10,4 +10,4 @@ do
break break
fi fi
done done
exit 0 return 0
@@ -1,45 +1,53 @@
{ {
"admin/services/AdGuardHome": { "admin/services/AdGuardHome": {
"title": "AdGuard Home", "title": "AdGuard Home",
"order": 12, "order": 10,
"action": { "action": {
"type": "firstchild" "type": "alias",
}, "path": "admin/services/AdGuardHome/overview"
"depends": { },
"acl": [ "luci-app-adguardhome" ] "depends": {
} "acl": [ "luci-app-adguardhome" ]
}, }
"admin/services/AdGuardHome/base": { },
"title": "Base Setting", "admin/services/AdGuardHome/overview": {
"order": 1, "title": "概览",
"action": { "order": 1,
"type": "view", "action": {
"path": "adguardhome/base" "type": "cbi",
}, "path": "AdGuardHome/overview"
"depends": { }
"acl": [ "luci-app-adguardhome" ] },
} "admin/services/AdGuardHome/base": {
}, "title": "基础设置",
"admin/services/AdGuardHome/log": { "order": 2,
"title": "Log", "action": {
"order": 3, "type": "cbi",
"action": { "path": "AdGuardHome/base"
"type": "view", }
"path": "adguardhome/log" },
}, "admin/services/AdGuardHome/tools": {
"depends": { "title": "运维",
"acl": [ "luci-app-adguardhome" ] "order": 3,
} "action": {
}, "type": "cbi",
"admin/services/AdGuardHome/manual": { "path": "AdGuardHome/tools"
"title": "Manual Config", }
"order": 2, },
"action": { "admin/services/AdGuardHome/log": {
"type": "view", "title": "日志",
"path": "adguardhome/manual" "order": 4,
}, "action": {
"depends": { "type": "form",
"acl": [ "luci-app-adguardhome" ] "path": "AdGuardHome/log"
} }
} },
"admin/services/AdGuardHome/manual": {
"title": "手动配置",
"order": 5,
"action": {
"type": "cbi",
"path": "AdGuardHome/manual"
}
}
} }
@@ -2,20 +2,10 @@
"luci-app-adguardhome": { "luci-app-adguardhome": {
"description": "Grant UCI access for luci-app-adguardhome", "description": "Grant UCI access for luci-app-adguardhome",
"read": { "read": {
"file": {
"/var/run/AdGredir": [ "read" ]
},
"ubus": {
"service": [ "list" ],
"luci.adguardhome": [ "getCoreInfo", "getRedirectStatus", "getCoreUpdate", "getInterfaces", "getRuntimeLog", "getManualConfig" ]
},
"uci": [ "AdGuardHome" ] "uci": [ "AdGuardHome" ]
}, },
"write": { "write": {
"ubus": {
"luci.adguardhome": [ "setVerboseLog", "startCoreUpdate", "changeWebPassword", "clearRuntimeLog", "setManualConfig" ]
},
"uci": [ "AdGuardHome" ] "uci": [ "AdGuardHome" ]
} }
} }
} }
@@ -0,0 +1 @@
!function(n){"object"==typeof exports&&"object"==typeof module?n(require("../../lib/codemirror")):"function"==typeof define&&define.amd?define(["../../lib/codemirror"],n):n(CodeMirror)}(function(n){"use strict";function e(e,o,i,t){if(i&&i.call){var l=i;i=null}else l=r(e,i,"rangeFinder");"number"==typeof o&&(o=n.Pos(o,0));var f=r(e,i,"minFoldSize");function d(n){var r=l(e,o);if(!r||r.to.line-r.from.line<f)return null;for(var i=e.findMarksAt(r.from),d=0;d<i.length;++d)if(i[d].__isFold&&"fold"!==t){if(!n)return null;r.cleared=!0,i[d].clear()}return r}var u=d(!0);if(r(e,i,"scanUp"))for(;!u&&o.line>e.firstLine();)o=n.Pos(o.line-1,0),u=d(!1);if(u&&!u.cleared&&"unfold"!==t){var a=function(n,e){var o=r(n,e,"widget");if("string"==typeof o){var i=document.createTextNode(o);(o=document.createElement("span")).appendChild(i),o.className="CodeMirror-foldmarker"}else o&&(o=o.cloneNode(!0));return o}(e,i);n.on(a,"mousedown",function(e){c.clear(),n.e_preventDefault(e)});var c=e.markText(u.from,u.to,{replacedWith:a,clearOnEnter:r(e,i,"clearOnEnter"),__isFold:!0});c.on("clear",function(o,r){n.signal(e,"unfold",e,o,r)}),n.signal(e,"fold",e,u.from,u.to)}}n.newFoldFunction=function(n,o){return function(r,i){e(r,i,{rangeFinder:n,widget:o})}},n.defineExtension("foldCode",function(n,o,r){e(this,n,o,r)}),n.defineExtension("isFolded",function(n){for(var e=this.findMarksAt(n),o=0;o<e.length;++o)if(e[o].__isFold)return!0}),n.commands.toggleFold=function(n){n.foldCode(n.getCursor())},n.commands.fold=function(n){n.foldCode(n.getCursor(),null,"fold")},n.commands.unfold=function(n){n.foldCode(n.getCursor(),null,"unfold")},n.commands.foldAll=function(e){e.operation(function(){for(var o=e.firstLine(),r=e.lastLine();o<=r;o++)e.foldCode(n.Pos(o,0),null,"fold")})},n.commands.unfoldAll=function(e){e.operation(function(){for(var o=e.firstLine(),r=e.lastLine();o<=r;o++)e.foldCode(n.Pos(o,0),null,"unfold")})},n.registerHelper("fold","combine",function(){var n=Array.prototype.slice.call(arguments,0);return function(e,o){for(var r=0;r<n.length;++r){var i=n[r](e,o);if(i)return i}}}),n.registerHelper("fold","auto",function(n,e){for(var o=n.getHelpers(e,"fold"),r=0;r<o.length;r++){var i=o[r](n,e);if(i)return i}});var o={rangeFinder:n.fold.auto,widget:"↔",minFoldSize:0,scanUp:!1,clearOnEnter:!0};function r(n,e,r){if(e&&void 0!==e[r])return e[r];var i=n.options.foldOptions;return i&&void 0!==i[r]?i[r]:o[r]}n.defineOption("foldOptions",null),n.defineExtension("foldOption",function(n,e){return r(this,n,e)})});
@@ -0,0 +1 @@
.CodeMirror-foldmarker{color:blue;text-shadow:#b9f 1px 1px 2px,#b9f -1px -1px 2px,#b9f 1px -1px 2px,#b9f -1px 1px 2px;font-family:arial;line-height:.3;cursor:pointer}.CodeMirror-foldgutter{width:.7em}.CodeMirror-foldgutter-open,.CodeMirror-foldgutter-folded{cursor:pointer}.CodeMirror-foldgutter-open:after{content:"\25BE"}.CodeMirror-foldgutter-folded:after{content:"\25B8"}
@@ -0,0 +1 @@
!function(t){"object"==typeof exports&&"object"==typeof module?t(require("../../lib/codemirror"),require("./foldcode")):"function"==typeof define&&define.amd?define(["../../lib/codemirror","./foldcode"],t):t(CodeMirror)}(function(t){"use strict";t.defineOption("foldGutter",!1,function(o,e,r){r&&r!=t.Init&&(o.clearGutter(o.state.foldGutter.options.gutter),o.state.foldGutter=null,o.off("gutterClick",a),o.off("changes",d),o.off("viewportChange",u),o.off("fold",l),o.off("unfold",l),o.off("swapDoc",d)),e&&(o.state.foldGutter=new function(t){this.options=t,this.from=this.to=0}(function(t){!0===t&&(t={});null==t.gutter&&(t.gutter="CodeMirror-foldgutter");null==t.indicatorOpen&&(t.indicatorOpen="CodeMirror-foldgutter-open");null==t.indicatorFolded&&(t.indicatorFolded="CodeMirror-foldgutter-folded");return t}(e)),f(o),o.on("gutterClick",a),o.on("changes",d),o.on("viewportChange",u),o.on("fold",l),o.on("unfold",l),o.on("swapDoc",d))});var o=t.Pos;function e(t,e){for(var r=t.findMarks(o(e,0),o(e+1,0)),n=0;n<r.length;++n)if(r[n].__isFold){var i=r[n].find(-1);if(i&&i.line===e)return r[n]}}function r(t){if("string"==typeof t){var o=document.createElement("div");return o.className=t+" CodeMirror-guttermarker-subtle",o}return t.cloneNode(!0)}function n(t,n,f){var a=t.state.foldGutter.options,d=n-1,u=t.foldOption(a,"minFoldSize"),l=t.foldOption(a,"rangeFinder"),c="string"==typeof a.indicatorFolded&&i(a.indicatorFolded),s="string"==typeof a.indicatorOpen&&i(a.indicatorOpen);t.eachLine(n,f,function(n){++d;var i=null,f=n.gutterMarkers;if(f&&(f=f[a.gutter]),e(t,d)){if(c&&f&&c.test(f.className))return;i=r(a.indicatorFolded)}else{var p=o(d,0),m=l&&l(t,p);if(m&&m.to.line-m.from.line>=u){if(s&&f&&s.test(f.className))return;i=r(a.indicatorOpen)}}(i||f)&&t.setGutterMarker(n,a.gutter,i)})}function i(t){return new RegExp("(^|\\s)"+t+"(?:$|\\s)\\s*")}function f(t){var o=t.getViewport(),e=t.state.foldGutter;e&&(t.operation(function(){n(t,o.from,o.to)}),e.from=o.from,e.to=o.to)}function a(t,r,n){var i=t.state.foldGutter;if(i){var f=i.options;if(n==f.gutter){var a=e(t,r);a?a.clear():t.foldCode(o(r,0),f)}}}function d(t){var o=t.state.foldGutter;if(o){var e=o.options;o.from=o.to=0,clearTimeout(o.changeUpdate),o.changeUpdate=setTimeout(function(){f(t)},e.foldOnChangeTimeSpan||600)}}function u(t){var o=t.state.foldGutter;if(o){var e=o.options;clearTimeout(o.changeUpdate),o.changeUpdate=setTimeout(function(){var e=t.getViewport();o.from==o.to||e.from-o.to>20||o.from-e.to>20?f(t):t.operation(function(){e.from<o.from&&(n(t,e.from,o.from),o.from=e.from),e.to>o.to&&(n(t,o.to,e.to),o.to=e.to)})},e.updateViewportTimeSpan||400)}}function l(t,o){var e=t.state.foldGutter;if(e){var r=o.line;r>=e.from&&r<e.to&&n(t,r,r+1)}}});
@@ -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";function n(n,t){var i=n.getLine(t),o=i.search(/\S/);return-1==o||/\bcomment\b/.test(n.getTokenTypeAt(e.Pos(t,o+1)))?-1:e.countColumn(i,null,n.getOption("tabSize"))}e.registerHelper("fold","indent",function(t,i){var o=n(t,i.line);if(!(o<0)){for(var r=null,l=i.line+1,f=t.lastLine();l<=f;++l){var u=n(t,l);if(-1==u);else{if(!(u>o))break;r=l}}return r?{from:e.Pos(i.line,t.getLine(i.line).length),to:e.Pos(r,t.getLine(r).length)}:void 0}})});
File renamed without changes.
+11
View File
@@ -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
}
+14
View File
@@ -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
+189
View File
@@ -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
+20
View File
@@ -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
+164
View File
@@ -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"
+13
View File
@@ -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 @@
.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}
+7
View File
@@ -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
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.