mirror of
https://github.com/caiwx86/small-packages.git
synced 2026-11-05 18:44:06 +08:00
update 2026-11-01 17:54:16
This commit is contained in:
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.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -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
|
||||
@@ -1,2 +1,9 @@
|
||||
.DS_Store
|
||||
.local
|
||||
|
||||
# Local debug artifacts from playwright-cli UI verification
|
||||
adg-*.png
|
||||
m-*.png
|
||||
bs-*.png
|
||||
*.png
|
||||
!screenshots/*.png
|
||||
.playwright-cli/
|
||||
@@ -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
|
||||
|
||||
PKG_NAME:=luci-app-adguardhome-tk
|
||||
PKG_VERSION:=2.6.0
|
||||
PKG_RELEASE:=3
|
||||
PKG_NAME:=luci-app-adguardhome
|
||||
PKG_MAINTAINER:=kenzok8
|
||||
PKG_VERSION:=1.0
|
||||
PKG_RELEASE:=9
|
||||
|
||||
PKG_MAINTAINER:=w9315273
|
||||
PKG_BUILD_DEPENDS:=luci-base/host
|
||||
LUCI_TITLE:=LuCI app for AdGuardHome
|
||||
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/luci-app-adguardhome-tk
|
||||
SECTION:=luci
|
||||
CATEGORY:=LuCI
|
||||
SUBMENU:=3. Applications
|
||||
TITLE:=LuCI app for AdGuardHome
|
||||
PKG_MAINTAINER:=https://github.com/w9315273/luci-app-adguardhome
|
||||
PKGARCH:=all
|
||||
PROVIDES:=luci-app-adguardhome
|
||||
CONFLICTS:=luci-app-adguardhome
|
||||
DEPENDS:=+luci-base +rpcd-mod-ucode +uclient-fetch +jsonfilter +flock +ca-bundle +!(PACKAGE_libustream-openssl||PACKAGE_libustream-wolfssl):libustream-mbedtls +tar
|
||||
define Package/$(PKG_NAME)/config
|
||||
config PACKAGE_$(PKG_NAME)_INCLUDE_binary
|
||||
bool "Include Binary File"
|
||||
default n
|
||||
help
|
||||
When enabled, also pulls in the adguardhome binary package.
|
||||
Disabled by default to avoid init.d/config name conflict with
|
||||
the adguardhome package (uses lowercase init.d/adguardhome,
|
||||
while this LuCI app uses /etc/init.d/AdGuardHome). The LuCI
|
||||
app downloads the binary at runtime, so the package dependency
|
||||
is only needed for offline / sysupgrade scenarios.
|
||||
endef
|
||||
|
||||
define Package/luci-app-adguardhome-tk/description
|
||||
LuCI support for AdGuardHome (nftables-based DNS redirect).
|
||||
This package does NOT include the AdGuardHome core binary.
|
||||
endef
|
||||
PKG_CONFIG_DEPENDS:= CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_binary
|
||||
|
||||
define Package/luci-i18n-adguardhome-tk-zh-cn
|
||||
SECTION:=luci
|
||||
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
|
||||
define Package/luci-app-adguardhome/conffiles
|
||||
/usr/share/AdGuardHome/links.txt
|
||||
/etc/config/AdGuardHome
|
||||
endef
|
||||
|
||||
define Package/luci-app-adguardhome-tk/install
|
||||
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
|
||||
define Package/luci-app-adguardhome/postinst
|
||||
#!/bin/sh
|
||||
[ -n "$${IPKG_INSTROOT}" ] && exit 0
|
||||
|
||||
/etc/init.d/AdGuardHome enable >/dev/null 2>&1
|
||||
|
||||
enable=$$(uci get AdGuardHome.AdGuardHome.enabled 2>/dev/null)
|
||||
if [ "$$enable" = "1" ]; then
|
||||
/etc/init.d/AdGuardHome reload
|
||||
fi
|
||||
|
||||
# Remove the former menu definition when upgrading.
|
||||
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/*
|
||||
/etc/init.d/rpcd reload
|
||||
|
||||
# Stop and disable the lowercase /etc/init.d/adguardhome that ships
|
||||
# with the adguardhome binary package, so it can not race the LuCI
|
||||
# app's /etc/init.d/AdGuardHome (issue #254).
|
||||
if [ -x /etc/init.d/adguardhome ] && [ -x /etc/init.d/AdGuardHome ]; then
|
||||
/etc/init.d/adguardhome stop >/dev/null 2>&1
|
||||
/etc/init.d/adguardhome disable >/dev/null 2>&1
|
||||
fi
|
||||
/etc/init.d/AdGuardHome enable >/dev/null 2>&1
|
||||
enable="$(uci get AdGuardHome.AdGuardHome.enabled 2>/dev/null)"
|
||||
[ "$enable" = "1" ] && /etc/init.d/AdGuardHome reload >/dev/null 2>&1
|
||||
rm -f /tmp/luci-indexcache
|
||||
rm -f /tmp/luci-modulecache/* 2>/dev/null
|
||||
exit 0
|
||||
endef
|
||||
|
||||
define Package/luci-app-adguardhome-tk/prerm
|
||||
define Package/luci-app-adguardhome/prerm
|
||||
#!/bin/sh
|
||||
if [ -z "$${IPKG_INSTROOT}" ]; then
|
||||
/etc/init.d/AdGuardHome disable
|
||||
/etc/init.d/AdGuardHome stop 2>/dev/null || true
|
||||
|
||||
uci -q batch <<-EOF >/dev/null 2>&1
|
||||
delete ucitrack.@AdGuardHome[-1]
|
||||
commit ucitrack
|
||||
EOF
|
||||
/etc/init.d/AdGuardHome disable >/dev/null 2>&1
|
||||
/etc/init.d/AdGuardHome stop >/dev/null 2>&1
|
||||
ucitrack_del() {
|
||||
local name="$1"
|
||||
local track="$(uci show ucitrack 2>/dev/null | grep -F "$name\." | head -1)"
|
||||
[ -n "$track" ] && uci -q del "$track" 2>/dev/null
|
||||
}
|
||||
ucitrack_del "AdGuardHome"
|
||||
[ -n "$(uci show ucitrack 2>/dev/null | grep -c 'AdGuardHome')" ] || true
|
||||
uci commit ucitrack 2>/dev/null
|
||||
fi
|
||||
|
||||
exit 0
|
||||
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))
|
||||
@@ -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 自动回退) |
|
||||
|
||||
## 截图
|
||||
|
||||
### 概览
|
||||
|
||||

|
||||
|
||||
### 基础设置
|
||||
|
||||

|
||||
|
||||
### 运维
|
||||
|
||||

|
||||
|
||||
### 日志
|
||||
|
||||

|
||||
|
||||
### 手动配置
|
||||
|
||||

|
||||
|
||||
## 功能
|
||||
|
||||
- **概览面板**:服务状态、重定向模式、核心版本、一键启停 + 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
|
||||
Binary file not shown.
@@ -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,'&').replace(/</g,'<').replace(/>/g,'>') + '</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"> </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'
|
||||
option enabled '0'
|
||||
option httpport '3000'
|
||||
option redirect 'none'
|
||||
option configpath '/etc/AdGuardHome.yaml'
|
||||
option workdir '/tmp/AdGuardHome'
|
||||
option workdir '/etc/AdGuardHome'
|
||||
option logfile '/tmp/AdGuardHome.log'
|
||||
option verbose '0'
|
||||
option binpath '/usr/bin/AdGuardHome/AdGuardHome'
|
||||
list wan_ifname 'eth0'
|
||||
option binpath '/usr/bin/AdGuardHome'
|
||||
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
Executable → Regular
+6
-5
@@ -1,14 +1,15 @@
|
||||
#!/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
|
||||
delete AdGuardHome.AdGuardHome.ucitracktest
|
||||
/etc/init.d/AdGuardHome restart
|
||||
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/*
|
||||
rm -f /tmp/luci-indexcache
|
||||
|
||||
chmod +x /etc/init.d/AdGuardHome /usr/share/AdGuardHome/*
|
||||
exit 0
|
||||
Binary file not shown.
Executable → Regular
+68
-126
@@ -1,123 +1,108 @@
|
||||
# 模板用户名为 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
|
||||
bind_host: 0.0.0.0
|
||||
bind_port: 3000
|
||||
beta_bind_port: 0
|
||||
users:
|
||||
- name: admin
|
||||
password: $2y$10$iGkTW98QtGEZn4I/GC0J4eQhfuLQ53v7L4DDVauEJyWylJrXWwWeO
|
||||
- name: root
|
||||
password: $2y$10$dwn0hTYoECQMZETBErGlzOId2VANOVsPHsuH13TM/8KnysM5Dh/ve
|
||||
auth_attempts: 5
|
||||
block_auth_min: 15
|
||||
http_proxy: ""
|
||||
language: ""
|
||||
theme: auto
|
||||
language: zh-cn
|
||||
debug_pprof: false
|
||||
web_session_ttl: 720
|
||||
dns:
|
||||
bind_hosts:
|
||||
- 0.0.0.0
|
||||
port: 3053
|
||||
- 0.0.0.0
|
||||
port: 1745
|
||||
statistics_interval: 30
|
||||
querylog_enabled: true
|
||||
querylog_file_enabled: true
|
||||
querylog_interval: 6h
|
||||
querylog_size_memory: 1000
|
||||
anonymize_client_ip: false
|
||||
ratelimit: 20
|
||||
ratelimit_subnet_len_ipv4: 24
|
||||
ratelimit_subnet_len_ipv6: 56
|
||||
protection_enabled: true
|
||||
blocking_mode: default
|
||||
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: []
|
||||
refuse_any: true
|
||||
refuse_any: false
|
||||
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'
|
||||
- 223.5.5.5
|
||||
upstream_dns_file: ""
|
||||
bootstrap_dns:
|
||||
- 1.1.1.1
|
||||
- 1.0.0.1
|
||||
fallback_dns:
|
||||
- quic://dns.adguard-dns.com
|
||||
upstream_mode: parallel
|
||||
- 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
|
||||
- version.bind
|
||||
- id.server
|
||||
- hostname.bind
|
||||
trusted_proxies:
|
||||
- 127.0.0.0/8
|
||||
- ::1/128
|
||||
cache_enabled: true
|
||||
- 127.0.0.0/8
|
||||
- ::1/128
|
||||
cache_size: 4194304
|
||||
cache_ttl_min: 0
|
||||
cache_ttl_max: 0
|
||||
cache_optimistic: false
|
||||
cache_optimistic_answer_ttl: 30s
|
||||
cache_optimistic_max_age: 12h
|
||||
cache_optimistic: true
|
||||
bogus_nxdomain: []
|
||||
aaaa_disabled: false
|
||||
enable_dnssec: false
|
||||
edns_client_subnet:
|
||||
custom_ip: ""
|
||||
enabled: false
|
||||
use_custom: false
|
||||
edns_client_subnet: false
|
||||
max_goroutines: 300
|
||||
handle_ddr: true
|
||||
ipset: []
|
||||
ipset_file: ""
|
||||
bootstrap_prefer_ipv6: false
|
||||
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
|
||||
private_networks: []
|
||||
local_domain_name: lan
|
||||
resolve_clients: true
|
||||
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_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: ""
|
||||
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
|
||||
- enabled: true
|
||||
url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt
|
||||
name: AdGuard DNS filter
|
||||
id: 1628750870
|
||||
- enabled: true
|
||||
url: https://anti-ad.net/easylist.txt
|
||||
name: 'CHN: anti-AD'
|
||||
id: 1628750871
|
||||
whitelist_filters: []
|
||||
user_rules: []
|
||||
dhcp:
|
||||
enabled: false
|
||||
interface_name: ""
|
||||
local_domain_name: lan
|
||||
dhcpv4:
|
||||
gateway_ip: ""
|
||||
subnet_mask: ""
|
||||
@@ -131,59 +116,16 @@ dhcp:
|
||||
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
|
||||
clients: []
|
||||
log_compress: false
|
||||
log_localtime: false
|
||||
log_max_backups: 0
|
||||
log_max_size: 100
|
||||
log_max_age: 3
|
||||
log_file: ""
|
||||
verbose: false
|
||||
os:
|
||||
group: ""
|
||||
user: ""
|
||||
rlimit_nofile: 0
|
||||
schema_version: 32
|
||||
schema_version: 12
|
||||
@@ -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 -f >> /tmp/AdGuardHometmp.log &
|
||||
pid=$!
|
||||
echo "1" > /var/run/AdGuardHomesyslog
|
||||
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"
|
||||
watchdog=$(cat /var/run/AdGuardHomesyslog)
|
||||
if [ "$watchdog"x == "0"x ]; then
|
||||
kill $pid
|
||||
rm /tmp/AdGuardHometmp.log
|
||||
rm /var/run/AdGuardHomesyslog
|
||||
exit 0
|
||||
else
|
||||
echo "0" > /var/run/AdGuardHomesyslog
|
||||
echo "0">/var/run/AdGuardHomesyslog
|
||||
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
|
||||
|
||||
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)
|
||||
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
|
||||
}
|
||||
mkdir -p "${binpath%/*}"
|
||||
|
||||
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"
|
||||
}
|
||||
upxflag="$(uci get AdGuardHome.AdGuardHome.upxflag 2>/dev/null)"
|
||||
|
||||
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(){
|
||||
cleanup() {
|
||||
rm -f /var/run/update_core 2>/dev/null
|
||||
[ "$1" != "0" ] && touch /var/run/update_core_error
|
||||
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"
|
||||
@@ -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
|
||||
fi
|
||||
done
|
||||
exit 0
|
||||
return 0
|
||||
@@ -1,45 +1,53 @@
|
||||
{
|
||||
"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" ]
|
||||
}
|
||||
}
|
||||
"admin/services/AdGuardHome": {
|
||||
"title": "AdGuard Home",
|
||||
"order": 10,
|
||||
"action": {
|
||||
"type": "alias",
|
||||
"path": "admin/services/AdGuardHome/overview"
|
||||
},
|
||||
"depends": {
|
||||
"acl": [ "luci-app-adguardhome" ]
|
||||
}
|
||||
},
|
||||
"admin/services/AdGuardHome/overview": {
|
||||
"title": "概览",
|
||||
"order": 1,
|
||||
"action": {
|
||||
"type": "cbi",
|
||||
"path": "AdGuardHome/overview"
|
||||
}
|
||||
},
|
||||
"admin/services/AdGuardHome/base": {
|
||||
"title": "基础设置",
|
||||
"order": 2,
|
||||
"action": {
|
||||
"type": "cbi",
|
||||
"path": "AdGuardHome/base"
|
||||
}
|
||||
},
|
||||
"admin/services/AdGuardHome/tools": {
|
||||
"title": "运维",
|
||||
"order": 3,
|
||||
"action": {
|
||||
"type": "cbi",
|
||||
"path": "AdGuardHome/tools"
|
||||
}
|
||||
},
|
||||
"admin/services/AdGuardHome/log": {
|
||||
"title": "日志",
|
||||
"order": 4,
|
||||
"action": {
|
||||
"type": "form",
|
||||
"path": "AdGuardHome/log"
|
||||
}
|
||||
},
|
||||
"admin/services/AdGuardHome/manual": {
|
||||
"title": "手动配置",
|
||||
"order": 5,
|
||||
"action": {
|
||||
"type": "cbi",
|
||||
"path": "AdGuardHome/manual"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,20 +2,10 @@
|
||||
"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" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
@@ -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)})});
|
||||
+1
@@ -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"}
|
||||
+1
@@ -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)}}});
|
||||
+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.
@@ -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'
|
||||
Executable
+653
@@ -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
|
||||
}
|
||||
Executable
+14
@@ -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
|
||||
File renamed without changes.
+189
@@ -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
|
||||
File renamed without changes.
Executable
+20
@@ -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
|
||||
Executable
+164
@@ -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"
|
||||
Executable
+13
@@ -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" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
File renamed without changes.
@@ -0,0 +1 @@
|
||||
.CodeMirror{font-family:monospace;height:500px;color:black;direction:ltr}.CodeMirror-lines{padding:4px 0}.CodeMirror pre.CodeMirror-line,.CodeMirror pre.CodeMirror-line-like{padding:0 4px}.CodeMirror-scrollbar-filler,.CodeMirror-gutter-filler{background-color:white}.CodeMirror-gutters{border-right:1px solid #ddd;background-color:#f7f7f7;white-space:nowrap}.CodeMirror-linenumber{padding:0 3px 0 5px;min-width:20px;text-align:right;color:#999;white-space:nowrap}.CodeMirror-guttermarker{color:black}.CodeMirror-guttermarker-subtle{color:#999}.CodeMirror-cursor{border-left:1px solid black;border-right:0;width:0}.CodeMirror div.CodeMirror-secondarycursor{border-left:1px solid silver}.cm-fat-cursor .CodeMirror-cursor{width:auto;border:0 !important;background:#7e7}.cm-fat-cursor div.CodeMirror-cursors{z-index:1}.cm-fat-cursor-mark{background-color:rgba(20,255,20,0.5);-webkit-animation:blink 1.06s steps(1) infinite;-moz-animation:blink 1.06s steps(1) infinite;animation:blink 1.06s steps(1) infinite}.cm-animate-fat-cursor{width:auto;border:0;-webkit-animation:blink 1.06s steps(1) infinite;-moz-animation:blink 1.06s steps(1) infinite;animation:blink 1.06s steps(1) infinite;background-color:#7e7}@-moz-keyframes blink{50%{background-color:transparent}}@-webkit-keyframes blink{50%{background-color:transparent}}@keyframes blink{50%{background-color:transparent}}.cm-tab{display:inline-block;text-decoration:inherit}.CodeMirror-rulers{position:absolute;left:0;right:0;top:-50px;bottom:0;overflow:hidden}.CodeMirror-ruler{border-left:1px solid #ccc;top:0;bottom:0;position:absolute}.cm-s-default .cm-header{color:blue}.cm-s-default .cm-quote{color:#090}.cm-negative{color:#d44}.cm-positive{color:#292}.cm-header,.cm-strong{font-weight:bold}.cm-em{font-style:italic}.cm-link{text-decoration:underline}.cm-strikethrough{text-decoration:line-through}.cm-s-default .cm-keyword{color:#708}.cm-s-default .cm-atom{color:#219}.cm-s-default .cm-number{color:#164}.cm-s-default .cm-def{color:#00f}.cm-s-default .cm-variable-2{color:#05a}.cm-s-default .cm-variable-3,.cm-s-default .cm-type{color:#085}.cm-s-default .cm-comment{color:#a50}.cm-s-default .cm-string{color:#a11}.cm-s-default .cm-string-2{color:#f50}.cm-s-default .cm-meta{color:#555}.cm-s-default .cm-qualifier{color:#555}.cm-s-default .cm-builtin{color:#30a}.cm-s-default .cm-bracket{color:#997}.cm-s-default .cm-tag{color:#170}.cm-s-default .cm-attribute{color:#00c}.cm-s-default .cm-hr{color:#999}.cm-s-default .cm-link{color:#00c}.cm-s-default .cm-error{color:red}.cm-invalidchar{color:red}.CodeMirror-composing{border-bottom:2px solid}div.CodeMirror span.CodeMirror-matchingbracket{color:#0b0}div.CodeMirror span.CodeMirror-nonmatchingbracket{color:#a22}.CodeMirror-matchingtag{background:rgba(255,150,0,.3)}.CodeMirror-activeline-background{background:#e8f2ff}.CodeMirror{position:relative;overflow:hidden;background:white}.CodeMirror-scroll{overflow:scroll !important;margin-bottom:-30px;margin-right:-30px;padding-bottom:30px;height:100%;outline:0;position:relative}.CodeMirror-sizer{position:relative;border-right:30px solid transparent}.CodeMirror-vscrollbar,.CodeMirror-hscrollbar,.CodeMirror-scrollbar-filler,.CodeMirror-gutter-filler{position:absolute;z-index:6;display:none}.CodeMirror-vscrollbar{right:0;top:0;overflow-x:hidden;overflow-y:scroll}.CodeMirror-hscrollbar{bottom:0;left:0;overflow-y:hidden;overflow-x:scroll}.CodeMirror-scrollbar-filler{right:0;bottom:0}.CodeMirror-gutter-filler{left:0;bottom:0}.CodeMirror-gutters{position:absolute;left:0;top:0;min-height:100%;z-index:3}.CodeMirror-gutter{white-space:normal;height:100%;display:inline-block;vertical-align:top;margin-bottom:-30px}.CodeMirror-gutter-wrapper{position:absolute;z-index:4;background:none !important;border:none !important}.CodeMirror-gutter-background{position:absolute;top:0;bottom:0;z-index:4}.CodeMirror-gutter-elt{position:absolute;cursor:default;z-index:4}.CodeMirror-gutter-wrapper ::selection{background-color:transparent}.CodeMirror-gutter-wrapper ::-moz-selection{background-color:transparent}.CodeMirror-lines{cursor:text;min-height:1px}.CodeMirror pre.CodeMirror-line,.CodeMirror pre.CodeMirror-line-like{-moz-border-radius:0;-webkit-border-radius:0;border-radius:0;border-width:0;background:transparent;font-family:inherit;font-size:inherit;margin:0;white-space:pre;word-wrap:normal;line-height:inherit;color:inherit;z-index:2;position:relative;overflow:visible;-webkit-tap-highlight-color:transparent;-webkit-font-variant-ligatures:contextual;font-variant-ligatures:contextual}.CodeMirror-wrap pre.CodeMirror-line,.CodeMirror-wrap pre.CodeMirror-line-like{word-wrap:break-word;white-space:pre-wrap;word-break:normal}.CodeMirror-linebackground{position:absolute;left:0;right:0;top:0;bottom:0;z-index:0}.CodeMirror-linewidget{position:relative;z-index:2;padding:.1px}.CodeMirror-rtl pre{direction:rtl}.CodeMirror-code{outline:0}.CodeMirror-scroll,.CodeMirror-sizer,.CodeMirror-gutter,.CodeMirror-gutters,.CodeMirror-linenumber{-moz-box-siLine truncated
|
||||
@@ -0,0 +1 @@
|
||||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.CodeMirror=t()}(this,function(){"use strict";var e=navigator.userAgent,t=navigator.platform,r=/gecko\/\d/i.test(e),n=/MSIE \d/.test(e),i=/Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(e),o=/Edge\/(\d+)/.exec(e),l=n||i||o,s=l&&(n?document.documentMode||6:+(o||i)[1]),a=!o&&/WebKit\//.test(e),u=a&&/Qt\/\d+\.\d+/.test(e),c=!o&&/Chrome\//.test(e),h=/Opera\//.test(e),f=/Apple Computer/.test(navigator.vendor),d=/Mac OS X 1\d\D([8-9]|\d\d)\D/.test(e),p=/PhantomJS/.test(e),g=!o&&/AppleWebKit/.test(e)&&/Mobile\/\w+/.test(e),v=/Android/.test(e),m=g||v||/webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(e),y=g||/Mac/.test(t),b=/\bCrOS\b/.test(e),w=/win/i.test(t),x=h&&e.match(/Version\/(\d*\.\d*)/);x&&(x=Number(x[1])),x&&x>=15&&(h=!1,a=!0);var C=y&&(u||h&&(null==x||x<12.11)),S=r||l&&s>=9;function L(e){return new RegExp("(^|\\s)"+e+"(?:$|\\s)\\s*")}var k,T=function(e,t){var r=e.className,n=L(t).exec(r);if(n){var i=r.slice(n.index+n[0].length);e.className=r.slice(0,n.index)+(i?n[1]+i:"")}};function M(e){for(var t=e.childNodes.length;t>0;--t)e.removeChild(e.firstChild);return e}function N(e,t){return M(e).appendChild(t)}function O(e,t,r,n){var i=document.createElement(e);if(r&&(i.className=r),n&&(i.style.cssText=n),"string"==typeof t)i.appendChild(document.createTextNode(t));else if(t)for(var o=0;o<t.length;++o)i.appendChild(t[o]);return i}function A(e,t,r,n){var i=O(e,t,r,n);return i.setAttribute("role","presentation"),i}function D(e,t){if(3==t.nodeType&&(t=t.parentNode),e.contains)return e.contains(t);do{if(11==t.nodeType&&(t=t.host),t==e)return!0}while(t=t.parentNode)}function W(){var e;try{e=document.activeElement}catch(t){e=document.body||null}for(;e&&e.shadowRoot&&e.shadowRoot.activeElement;)e=e.shadowRoot.activeElement;return e}function H(e,t){var r=e.className;L(t).test(r)||(e.className+=(r?" ":"")+t)}function F(e,t){for(var r=e.split(" "),n=0;n<r.length;n++)r[n]&&!L(r[n]).test(t)&&(t+=" "+r[n]);return t}k=document.createRange?function(e,t,r,n){var i=document.createRange();return i.setEnd(n||e,r),i.setStart(e,t),i}:function(e,t,r){var n=document.body.createTextRange();try{n.moveToElementText(e.parentNode)}catch(e){return n}return n.collapse(!0),n.moveEnd("character",r),n.moveStart("character",t),n};var P=function(e){e.select()};function E(e){var t=Array.prototype.slice.call(arguments,1);return function(){return e.apply(null,t)}}function I(e,t,r){for(var n in t||(t={}),e)!e.hasOwnProperty(n)||!1===r&&t.hasOwnProperty(n)||(t[n]=e[n]);return t}function z(e,t,r,n,i){null==t&&-1==(t=e.search(/[^\s\u00a0]/))&&(t=e.length);for(var o=n||0,l=i||0;;){var s=e.indexOf("\t",o);if(s<0||s>=t)return l+(t-o);l+=s-o,l+=r-l%r,o=s+1}}g?P=function(e){e.selectionStart=0,e.selectionEnd=e.value.length}:l&&(P=function(e){try{e.select()}catch(e){}});var R=function(){this.id=null,this.f=null,this.time=0,this.handler=E(this.onTimeout,this)};function B(e,t){for(var r=0;r<e.length;++r)if(e[r]==t)return r;return-1}R.prototype.onTimeout=function(e){e.id=0,e.time<=+new Date?e.f():setTimeout(e.handler,e.time-+new Date)},R.prototype.set=function(e,t){this.f=t;var r=+new Date+e;(!this.id||r<this.time)&&(clearTimeout(this.id),this.id=setTimeout(this.handler,e),this.time=r)};var G=30,U={toString:function(){return"CodeMirror.Pass"}},V={scroll:!1},K={origin:"*mouse"},j={origin:"+move"};function X(e,t,r){for(var n=0,i=0;;){var o=e.indexOf("\t",n);-1==o&&(o=e.length);var l=o-n;if(o==e.length||i+l>=t)return n+Math.min(l,t-i);if(i+=o-n,n=o+1,(i+=r-i%r)>=t)return n}}var Y=[""];function _(e){for(;Y.length<=e;)Y.push($(Y)+" ");return Y[e]}function $(e){return e[e.length-1]}function q(e,t){for(var r=[],n=0;n<e.length;n++)r[n]=t(e[n],n);return r}function Z(){}function Q(e,t){var r;return Object.create?r=Object.create(e):(Z.prototype=e,r=new Z),t&&I(t,r),r}var J=/[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/;function ee(e){return/\w/.test(e)||e>""&&(e.toUpperCase()!=e.toLowerCase()||J.test(e))}function te(e,t){return t?!!(t.source.indexOf("\\w")>-1&&ee(e))||t.test(e):ee(e)}function re(e){for(var t in e)if(e.hasOwnProperty(t)&&e[t])return!1;return!0}var ne=/[\u0300-\u036f\u0483-\u0489\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u065e\u0670\u06d6-\u06dc\u06de-\u06e4\u06e7\u06e8\u06ea-\u06ed\u0711\u0730-\u074a\u07a6-\u07b0\u07eb-\u07f3\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0900-\u0902\u093c\u0941-\u0948\u094d\u0951-\u0955\u0962\u0963\u0981\u09bc\u09be\u09c1-\u09c4\u09cd\u09d7\u09e2\u09e3\u0a01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a70\u0a71\u0a75\u0a81\u0a82\u0abc\u0ac1-\u0ac5\u0ac7\u0ac8\u0acd\u0ae2\u0ae3\u0b01\u0b3c\u0b3e\u0b3f\u0b41-\u0b44\u0b4d\u0b56\u0b57\u0b62\u0b63\u0b82\u0bbe\u0bc0\u0bcd\u0bd7\u0c3e-\u0c40\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0cbc\u0cbf\u0cc2\u0cc6\u0ccc\u0ccd\u0cd5\u0cdLine truncated
|
||||
@@ -0,0 +1 @@
|
||||
!function(e){"object"==typeof exports&&"object"==typeof module?e(require("../../lib/codemirror")):"function"==typeof define&&define.amd?define(["../../lib/codemirror"],e):e(CodeMirror)}(function(e){"use strict";e.defineMode("yaml",function(){var e=new RegExp("\\b(("+["true","false","on","off","yes","no"].join(")|(")+"))$","i");return{token:function(i,t){var r=i.peek(),n=t.escaped;if(t.escaped=!1,"#"==r&&(0==i.pos||/\s/.test(i.string.charAt(i.pos-1))))return i.skipToEnd(),"comment";if(i.match(/^('([^']|\\.)*'?|"([^"]|\\.)*"?)/))return"string";if(t.literal&&i.indentation()>t.keyCol)return i.skipToEnd(),"string";if(t.literal&&(t.literal=!1),i.sol()){if(t.keyCol=0,t.pair=!1,t.pairStart=!1,i.match(/---/))return"def";if(i.match(/\.\.\./))return"def";if(i.match(/\s*-\s+/))return"meta"}if(i.match(/^(\{|\}|\[|\])/))return"{"==r?t.inlinePairs++:"}"==r?t.inlinePairs--:"["==r?t.inlineList++:t.inlineList--,"meta";if(t.inlineList>0&&!n&&","==r)return i.next(),"meta";if(t.inlinePairs>0&&!n&&","==r)return t.keyCol=0,t.pair=!1,t.pairStart=!1,i.next(),"meta";if(t.pairStart){if(i.match(/^\s*(\||\>)\s*/))return t.literal=!0,"meta";if(i.match(/^\s*(\&|\*)[a-z0-9\._-]+\b/i))return"variable-2";if(0==t.inlinePairs&&i.match(/^\s*-?[0-9\.\,]+\s?$/))return"number";if(t.inlinePairs>0&&i.match(/^\s*-?[0-9\.\,]+\s?(?=(,|}))/))return"number";if(i.match(e))return"keyword"}return!t.pair&&i.match(/^\s*(?:[,\[\]{}&*!|>'"%@`][^\s'":]|[^,\[\]{}#&*!|>'"%@`])[^#]*?(?=\s*:($|\s))/)?(t.pair=!0,t.keyCol=i.indentation(),"atom"):t.pair&&i.match(/^:\s*/)?(t.pairStart=!0,"meta"):(t.pairStart=!1,t.escaped="\\"==r,i.next(),null)},startState:function(){return{pair:!1,pairStart:!1,keyCol:0,inlinePairs:0,inlineList:0,literal:!1,escaped:!1}},lineComment:"#",fold:"indent"}}),e.defineMIME("text/x-yaml","yaml"),e.defineMIME("text/yaml","yaml")});
|
||||
@@ -0,0 +1 @@
|
||||
.cm-s-dracula.CodeMirror,.cm-s-dracula .CodeMirror-gutters{background-color:#282a36 !important;color:#f8f8f2 !important;border:0}.cm-s-dracula .CodeMirror-gutters{color:#282a36}.cm-s-dracula .CodeMirror-cursor{border-left:solid thin #f8f8f0}.cm-s-dracula .CodeMirror-linenumber{color:#6d8a88}.cm-s-dracula .CodeMirror-selected{background:rgba(255,255,255,0.10)}.cm-s-dracula .CodeMirror-line::selection,.cm-s-dracula .CodeMirror-line>span::selection,.cm-s-dracula .CodeMirror-line>span>span::selection{background:rgba(255,255,255,0.10)}.cm-s-dracula .CodeMirror-line::-moz-selection,.cm-s-dracula .CodeMirror-line>span::-moz-selection,.cm-s-dracula .CodeMirror-line>span>span::-moz-selection{background:rgba(255,255,255,0.10)}.cm-s-dracula span.cm-comment{color:#6272a4}.cm-s-dracula span.cm-string,.cm-s-dracula span.cm-string-2{color:#f1fa8c}.cm-s-dracula span.cm-number{color:#bd93f9}.cm-s-dracula span.cm-variable{color:#50fa7b}.cm-s-dracula span.cm-variable-2{color:white}.cm-s-dracula span.cm-def{color:#50fa7b}.cm-s-dracula span.cm-operator{color:#ff79c6}.cm-s-dracula span.cm-keyword{color:#ff79c6}.cm-s-dracula span.cm-atom{color:#bd93f9}.cm-s-dracula span.cm-meta{color:#f8f8f2}.cm-s-dracula span.cm-tag{color:#ff79c6}.cm-s-dracula span.cm-attribute{color:#50fa7b}.cm-s-dracula span.cm-qualifier{color:#50fa7b}.cm-s-dracula span.cm-property{color:#66d9ef}.cm-s-dracula span.cm-builtin{color:#50fa7b}.cm-s-dracula span.cm-variable-3,.cm-s-dracula span.cm-type{color:#ffb86c}.cm-s-dracula .CodeMirror-activeline-background{background:rgba(255,255,255,0.1)}.cm-s-dracula .CodeMirror-matchingbracket{text-decoration:underline;color:white !important}
|
||||
@@ -0,0 +1,7 @@
|
||||
/* @license
|
||||
* Twin-Bcrypt 2.2.0
|
||||
* https://github.com/fpirsch/twin-bcrypt
|
||||
* Licence: BSD-3-Clause
|
||||
*/
|
||||
!function(r,n){"use strict";function e(r){return y[g]=t.apply(n,r),g++}function t(r){var e=[].slice.call(arguments,1);return function(){"function"==typeof r?r.apply(n,e):new Function(""+r)()}}function o(r){if(m)setTimeout(t(o,r),0);else{var n=y[r];if(n){m=!0;try{n()}finally{a(r),m=!1}}}}function a(r){delete y[r]}function i(){p=function(){var r=e(arguments);return process.nextTick(t(o,r)),r}}function u(){if(r.postMessage&&!r.importScripts){var n=!0,e=r.onmessage;return r.onmessage=function(){n=!1},r.postMessage("","*"),r.onmessage=e,n}}function f(){var n="setImmediate$"+Math.random()+"$",t=function(e){e.source===r&&"string"==typeof e.data&&0===e.data.indexOf(n)&&o(+e.data.slice(n.length))};r.addEventListener?r.addEventListener("message",t,!1):r.attachEvent("onmessage",t),p=function(){var t=e(arguments);return r.postMessage(n+t,"*"),t}}function c(){var r=new MessageChannel;r.port1.onmessage=function(r){var n=r.data;o(n)},p=function(){var n=e(arguments);return r.port2.postMessage(n),n}}function s(){var r=v.documentElement;p=function(){var n=e(arguments),t=v.createElement("script");return t.onreadystatechange=function(){o(n),t.onreadystatechange=null,r.removeChild(t),t=null},r.appendChild(t),n}}function l(){p=function(){var r=e(arguments);return setTimeout(t(o,r),0),r}}if(!r.setImmediate){var p,g=1,y={},m=!1,v=r.document,d=Object.getPrototypeOf&&Object.getPrototypeOf(r);d=d&&d.setTimeout?d:r,"[object process]"==={}.toString.call(r.process)?i():u()?f():r.MessageChannel?c():v&&"onreadystatechange"in v.createElement("script")?s():l(),d.setImmediate=p,d.clearImmediate=a}}(new Function("return this")()),function(r){"object"==typeof exports?r(exports,require("crypto")):r(self.TwinBcrypt={},self.crypto||self.msCrypto)}(function(r,n){"use strict";function e(r){for(var n=unescape(encodeURIComponent(r)),e=n.length,t=new Array(e),o=0;e>o;o++)t[o]=n.charCodeAt(o);return t}function t(r){for(var n=r.length,e=new Array(n),t=0;n>t;t++)e[t]=r.charCodeAt(t);return e}function o(r,n){for(var e,t,o=0,a="";n>o;){if(e=255&r[o++],a+=B[e>>2],e=(3&e)<<4,o>=n){a+=B[e];break}if(t=255&r[o++],e|=t>>4,a+=B[e],e=(15&t)<<2,o>=n){a+=B[e];break}t=255&r[o++],e|=t>>6,a+=B[e],a+=B[63&t]}return a}function a(r){for(var n,e,t=new Array(16),o=0,a=0;;){if(n=D[r.charCodeAt(o++)-46],e=D[r.charCodeAt(o++)-46],t[a++]=255&(n<<2|e>>4),22===o)break;n=e<<4,e=D[r.charCodeAt(o++)-46],t[a++]=255&(n|e>>2),n=e<<6,e=D[r.charCodeAt(o++)-46],t[a++]=255&(n|e)}return t}function i(r){for(var n=r.length,e=new Array(72),t=0,o=0;72>o;)e[o++]=r[t++],t===n&&(t=0);return e}function u(r,n,e){for(var t=0,o=e>>2;t<n.length;t++,o++)r[o]=n[t]}function f(r,n,e){for(var t=0;72>t;)r[n++]=e[t++]<<24|e[t++]<<16|e[t++]<<8|e[t++]}function c(r){function n(n){for(var e=r,t=G>>2,o=t|O,f=n>>2,c=e[f]^e[t],s=e[1|f];o>t;)s^=(e[c>>>24]+e[a|c>>>16&255]^e[i|c>>>8&255])+e[u|255&c]^e[++t],c^=(e[s>>>24]+e[a|s>>>16&255]^e[i|s>>>8&255])+e[u|255&s]^e[++t];e[f]=s^e[S>>2],e[1|f]=c}function e(n){var e;for(r[L>>2]=0,r[L+4>>2]=0,e=0;M>e;e++)r[G>>2|e]^=r[(n>>2)+e];var t,o,f,c,s,l=r;for(e=0;M>e;e+=2){for(t=G>>2,o=t|O,f=L>>2,c=l[f]^l[t],s=l[1|f];o>t;)s^=(l[c>>>24]+l[a|c>>>16&255]^l[i|c>>>8&255])+l[u|255&c]^l[++t],c^=(l[s>>>24]+l[a|s>>>16&255]^l[i|s>>>8&255])+l[u|255&s]^l[++t];l[f]=s^l[S>>2],l[1|f]=c,r[G>>2|e]=l[f],r[G>>2|e+1]=c}for(e=0;T>e;e+=2){for(t=G>>2,o=t|O,f=L>>2,c=l[f]^l[t],s=l[1|f];o>t;)s^=(l[c>>>24]+l[a|c>>>16&255]^l[i|c>>>8&255])+l[u|255&c]^l[++t],c^=(l[s>>>24]+l[a|s>>>16&255]^l[i|s>>>8&255])+l[u|255&s]^l[++t];l[f]=s^l[S>>2],l[1|f]=c,r[e]=l[f],r[1|e]=c}}function t(r,n,t){for(var o=0;t>=o&&!(r>n);o++)e(R),e(j),r++;return r}var o=k>>2,a=o+256|0,i=a+256|0,u=i+256|0;return{encrypt:n,expandLoop:t}}function s(stdlib, foreign, heap) {"use asm";var HEAP32=new stdlib.Uint32Array(heap);var BLOWFISH_NUM_ROUNDS=16;var S_offset=0x0000;var S1_offset=0x0400;var S2_offset=0x0800;var S3_offset=0x0C00;var P_offset=0x1000;var P_last_offset=0x1044;var crypt_ciphertext_offset=0x1048;var LR_offset=0x01060;var password_offset=0x1068;var salt_offset=0x10b0;var P_LEN=18;var S_LEN=1024;function encrypt(offset) {offset=offset|0;var i=0;var n=0;var L=0;var R=0;var imax=0;imax=P_offset|BLOWFISH_NUM_ROUNDS<<2;L=HEAP32[offset>>2]|0;R=HEAP32[offset+4>>2]|0;L=L^HEAP32[P_offset>>2];for (i=P_offset; (i|0)<(imax|0);) {i=(i+4)>>>0;R=R^(((HEAP32[(L>>>22)>>2]>>>0) +(HEAP32[(S1_offset|(L>>>14&0x3ff))>>2]>>>0) ^(HEAP32[(S2_offset|(L>>>6&0x3ff))>>2])) +(HEAP32[(S3_offset|(L<<2&0x3ff))>>2]>>>0))^HEAP32[i>>2];i=(i+4)>>>0;L=L^(((HEAP32[(R>>>22)>>2]>>>0) +(HEAP32[(S1_offset|(R>>>14&0x3ff))>>2]>>>0) ^(HEAP32[(S2_offset|(R>>>6&0x3ff))>>2])) +(HEAP32[(S3_offset|(R<<2&0x3ff))>>2]>>>0))^HEAP32[i>>2];}HEAP32[offset>>2]=R^HEAP32[P_last_offset>>2];HEAP32[(offset+4)>>2]=L;}function expandKey(offset) {offset=offset|0;var i=0;var off=0;off=P_offset|0;for (i=0; (i|0)<(P_LEN|0); i=(i+1)|0) {HEAP32[off>>2]=HEAP32[off>>2]^HEAP32[offset>>2];offset=(offset+4)|0;off=(off+4)|0;}HEAP32[LR_offset>>2]=0;HEAP32[LR_offset+4>>2]=0;off=P_offset;for (i=0; (i|0)<(P_LEN|0); iLine truncated
|
||||
function l(r,n,e,t){var o,a,i,u=L>>2,f=u+1;for(t[u]=0,t[f]=0,a=0,o=0;M>o;o++)i=n[a++]<<24|n[a++]<<16|n[a++]<<8|n[a++],t[G>>2|o]^=i;for(a=0,o=0;M>o;o+=2)i=r[a++]<<24|r[a++]<<16|r[a++]<<8|r[a++],a&=65295,t[u]^=i,i=r[a++]<<24|r[a++]<<16|r[a++]<<8|r[a++],a&=65295,t[f]^=i,e.encrypt(L),t[G>>2|o]=t[u],t[G>>2|o+1]=t[f];var c=k>>2;for(o=0;T>o;o+=2)i=r[a++]<<24|r[a++]<<16|r[a++]<<8|r[a++],a&=65295,t[u]^=i,i=r[a++]<<24|r[a++]<<16|r[a++]<<8|r[a++],a&=65295,t[f]^=i,e.encrypt(L),t[c|o]=t[u],t[c|o+1]=t[f]}function p(r,n,e,t,o,a,i){for(var u=e;t>=u;){if(u=r.expandLoop(u,t,o),a){var f=a(u/(t+1));if(f===!1)return}if(u>t){if(i)return void setImmediate(g.bind(null,r,n,i));return}if(i)return void setImmediate(p.bind(null,r,n,u,t,o,a,i))}}function g(r,n,e){u(n,x,F);var t;for(t=0;64>t;t++)r.encrypt(F+0),r.encrypt(F+8),r.encrypt(F+16);var o,a=0,i=x.length,f=new Array(4*i);for(t=0;i>t;t++)o=n[(F>>2)+t],f[a++]=o>>24,f[a++]=o>>16&255,f[a++]=o>>8&255,f[a++]=255&o;return e&&e(f),f}function y(r,n){return r+o(n,23)}function m(n,o,m,v){var d,h=o.substr(0,29),w=+o.substr(4,2),A=o.substr(7,22);if("string"==typeof n)d=r.encodingMode===r.ENCODING_UTF8?e(n):t(n);else if(Array.isArray(n))d=n.map(function(r){return 255&r});else{if(!(n instanceof Uint8Array))throw new Error("Incorrect arguments");d=Array.prototype.slice.call(n)}d.push(0);var b,E,N=a(A,C),O=31>w?1<<w:2147483648,M=O-1,T=m?127:M;if(I){var x=new ArrayBuffer(8192);b=new Uint32Array(x),E=s({Uint32Array:Uint32Array},null,x)}else E=c(b=[]);return u(b,U,k),u(b,$,G),d=i(d),N=i(N),f(b,j>>2,N),f(b,R>>2,d),l(N,d,E,b),v?void p(E,b,0,M,T,m,function(r){v(y(h,r))}):(p(E,b,0,M,T,m),y(h,g(E,b)))}function v(r){if(!b)throw new Error("No cryptographically secure pseudorandom number generator available.");if(null==r&&(r=N),r=0|+r,isNaN(r)||4>r||r>31)throw new Error("Invalid cost parameter.");var n="$2y$";return 10>r&&(n+="0"),n+=r+"$",n+=o(b(C),C)}function d(r,n,e){if(n&&"number"!=typeof n){if("string"!=typeof n||!z.test(n))throw new Error("Invalid salt")}else n=v(n);return m(r,n,e)}function h(r,n,e,t){if(arguments.length<2)throw new Error("Incorrect arguments");if(2===arguments.length?(t=n,n=e=null):3===arguments.length&&(t=e,e=null,"function"==typeof n&&(e=n,n=null)),n&&"number"!=typeof n){if("string"!=typeof n||!z.test(n))throw new Error("Invalid salt")}else n=v(n);if(!t||"function"!=typeof t)throw new Error("No callback function was given.");m(r,n,e,t)}function w(r,n){if("string"!=typeof n||!Z.test(n))throw new Error("Incorrect arguments");var e=n.substr(0,n.length-31),t=d(r,e);return t===n}function A(r,n,e,t){if("string"!=typeof n||!Z.test(n))throw new Error("Incorrect arguments");if(t||(t=e,e=null),!t||"function"!=typeof t)throw new Error("No callback function was given.");var o=n.substr(0,n.length-31);h(r,o,e,function(r){t(r===n)})}var b,E="undefined"!=typeof InstallTrigger,I=E;n&&(b=n.randomBytes,n.getRandomValues&&(b=function(r){var e=new Uint8Array(r);return n.getRandomValues(e)}));var C=16,N=10,O=16,$=[608135816,2242054355,320440878,57701188,2752067618,698298832,137296536,3964562569,1160258022,953160567,3193202383,887688300,3232508343,3380367581,1065670069,3041331479,2450970073,2306472731],U=[3509652390,2564797868,805139163,3491422135,3101798381,1780907670,3128725573,4046225305,614570311,3012652279,134345442,2240740374,1667834072,1901547113,2757295779,4103290238,227898511,1921955416,1904987480,2182433518,2069144605,3260701109,2620446009,720527379,3318853667,677414384,3393288472,3101374703,2390351024,1614419982,1822297739,2954791486,3608508353,3174124327,2024746970,1432378464,3864339955,2857741204,1464375394,1676153920,1439316330,715854006,3033291828,289532110,2706671279,2087905683,3018724369,1668267050,732546397,1947742710,3462151702,2609353502,2950085171,1814351708,2050118529,680887927,999245976,1800124847,3300911131,1713906067,1641548236,4213287313,1216130144,1575780402,4018429277,3917837745,3693486850,3949271944,596196993,3549867205,258830323,2213823033,772490370,2760122372,1774776394,2652871518,566650946,4142492826,1728879713,2882767088,1783734482,3629395816,2517608232,2874225571,1861159788,326777828,3124490320,2130389656,2716951837,967770486,1724537150,2185432712,2364442137,1164943284,2105845187,998989502,3765401048,2244026483,1075463327,1455516326,1322494562,910128902,469688178,1117454909,936433444,3490320968,3675253459,1240580251,122909385,2157517691,634681816,4142456567,3825094682,3061402683,2540495037,79693498,3249098678,1084186820,1583128258,426386531,1761308591,1047286709,322548459,995290223,1845252383,2603652396,3431023940,2942221577,3202600964,3727903485,1712269319,422464435,3234572375,1170764815,3523960633,3117677531,1434042557,442511882,3600875718,1076654713,1738483198,4213154764,2393238008,3677496056,1014306527,4251020053,793779912,2902807211,842905082,4246964064,1395751752,1040244610,2656851899,3396308128,445077038,3742853595,3577915638,679411651,2892444358,2354009459,1767581616,3150600392,3791627101,3102740896,284835224,4246832056,1258075500,768725851,2589189241,3069724005Line truncated
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Reference in new issue
Block a user