main 6a961919a0cc cached
28 files
99.5 KB
34.9k tokens
28 symbols
1 requests
Download .txt
Repository: twoone-3/AdGuardHomeForMagisk
Branch: main
Commit: 6a961919a0cc
Files: 28
Total size: 99.5 KB

Directory structure:
gitextract_2fth1gt4/

├── .gitattributes
├── .github/
│   └── workflows/
│       └── pack.yml
├── .gitignore
├── LICENSE
├── README.md
├── README_en.md
├── changelog.md
├── docs/
│   └── index.md
├── pack.ps1
├── src/
│   ├── META-INF/
│   │   └── com/
│   │       └── google/
│   │           └── android/
│   │               ├── update-binary
│   │               └── updater-script
│   ├── action.sh
│   ├── bin/
│   │   ├── AdGuardHome.yaml
│   │   └── data/
│   │       └── filters/
│   │           └── 1732747955.txt
│   ├── customize.sh
│   ├── module.prop
│   ├── scripts/
│   │   ├── base.sh
│   │   ├── debug.sh
│   │   ├── inotify.sh
│   │   ├── iptables.sh
│   │   └── tool.sh
│   ├── service.sh
│   ├── settings.conf
│   ├── uninstall.sh
│   └── webroot/
│       ├── app.js
│       ├── index.html
│       └── style.css
└── version.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitattributes
================================================
* text eol=lf
*.ps1 text eol=crlf
*.bat text eol=crlf


================================================
FILE: .github/workflows/pack.yml
================================================
name: Pack AdGuardHomeForRoot

on:
  workflow_dispatch:
  push:
    tags:
      - "[0-9]*"

permissions:
  contents: write

jobs:
  package:
    name: Build ${{ matrix.arch }} package
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        arch: [arm64, armv7]

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Resolve archive URL
        id: vars
        shell: bash
        run: |
          set -euo pipefail
          case "${{ matrix.arch }}" in
            arm64)
              echo "archive_url=https://github.com/AdguardTeam/AdGuardHome/releases/latest/download/AdGuardHome_linux_arm64.tar.gz" >> "$GITHUB_OUTPUT"
              ;;
            armv7)
              echo "archive_url=https://github.com/AdguardTeam/AdGuardHome/releases/latest/download/AdGuardHome_linux_armv7.tar.gz" >> "$GITHUB_OUTPUT"
              ;;
            *)
              echo "Unsupported arch: ${{ matrix.arch }}" >&2
              exit 1
              ;;
          esac

      - name: Download release archive
        shell: bash
        run: |
          set -euo pipefail
          mkdir -p cache
          curl -fL "${{ steps.vars.outputs.archive_url }}" -o "cache/AdGuardHome_linux_${{ matrix.arch }}.tar.gz"

      - name: Extract archive
        shell: bash
        run: |
          set -euo pipefail
          mkdir -p "cache/${{ matrix.arch }}"
          tar -xzf "cache/AdGuardHome_linux_${{ matrix.arch }}.tar.gz" -C "cache/${{ matrix.arch }}"

      - name: Stage package files
        shell: bash
        run: |
          set -euo pipefail
          rm -rf staging
          mkdir -p staging
          cp -a src/. staging/
          cp "cache/${{ matrix.arch }}/AdGuardHome/AdGuardHome" "staging/bin/AdGuardHome"

      - name: Create zip package
        shell: bash
        run: |
          set -euo pipefail
          rm -f "AdGuardHomeForRoot_${{ matrix.arch }}.zip"
          (
            cd staging
            zip -r "../AdGuardHomeForRoot_${{ matrix.arch }}.zip" .
          )

      - name: Upload artifact
        uses: actions/upload-artifact@v4
        with:
          name: AdGuardHomeForRoot_${{ matrix.arch }}
          path: AdGuardHomeForRoot_${{ matrix.arch }}.zip
          if-no-files-found: error

  release:
    name: Create GitHub Release
    runs-on: ubuntu-latest
    needs: package
    if: startsWith(github.ref, 'refs/tags/')

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Validate tag format
        shell: bash
        run: |
          set -euo pipefail
          if [[ ! "${GITHUB_REF_NAME}" =~ ^[0-9]{8}$ ]]; then
            echo "Tag must be an 8-digit date (YYYYMMDD), got: ${GITHUB_REF_NAME}" >&2
            exit 1
          fi

      - name: Download build artifacts
        uses: actions/download-artifact@v4
        with:
          pattern: AdGuardHomeForRoot_*
          merge-multiple: true

      - name: Create release
        uses: softprops/action-gh-release@v2
        with:
          tag_name: ${{ github.ref_name }}
          name: ${{ github.ref_name }}
          body_path: changelog.md
          files: |
            AdGuardHomeForRoot_arm64.zip
            AdGuardHomeForRoot_armv7.zip


================================================
FILE: .gitignore
================================================
cache/


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2025 twoone3

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# AdGuardHome for Root

[English](README_en.md) | 简体中文

![arm-64 support](https://img.shields.io/badge/arm--64-support-ef476f?logo=linux&logoColor=white&color=ef476f)
![arm-v7 support](https://img.shields.io/badge/arm--v7-support-ffa500?logo=linux&logoColor=white&color=ffa500)
![GitHub downloads](https://img.shields.io/github/downloads/twoone-3/AdGuardHomeForRoot/total?logo=github&logoColor=white&color=ffd166)
![License](https://img.shields.io/badge/License-MIT-9b5de5?logo=opensourceinitiative&logoColor=white)
[![Docs](https://img.shields.io/badge/Docs-Guide-0066ff?logo=book&logoColor=white)](docs/index.md)
[![Join Telegram Channel](https://img.shields.io/badge/Telegram-Join%20Channel-06d6a0?logo=telegram&logoColor=white)](https://t.me/+Q3Ur_HCYdM0xM2I1)
[![Join Telegram Group](https://img.shields.io/badge/Telegram-Join%20Group-118ab2?logo=telegram&logoColor=white)](https://t.me/+Q3Ur_HCYdM0xM2I1)

关注我们的频道获取最新消息,或加入我们的群组进行讨论!  

## 简介

- 本模块是一个在安卓设备上运行 [AdGuardHome](https://github.com/AdguardTeam/AdGuardHome) 的模块,提供了一个本地 DNS 服务器,能够屏蔽广告、恶意软件和跟踪器。
- 它可以作为一个本地广告拦截模块使用,也可以通过调整配置文件,转变为一个独立运行的 AdGuardHome 工具。
- 该模块支持 Magisk、KernelSU 和 APatch 等多种安装方式,适用于大多数 Android 设备。
- 该模块的设计初衷是为了提供一个轻量级的广告拦截解决方案,避免了使用 VPN 的复杂性和性能损失。
- 它可以与其他代理软件(如 [NekoBox](https://github.com/MatsuriDayo/NekoBoxForAndroid)、[FlClash](https://github.com/chen08209/FlClash)、[box for magisk](https://github.com/taamarin/box_for_magisk)、[akashaProxy](https://github.com/akashaProxy/akashaProxy) 等)共存,提供更好的隐私保护和网络安全。

## 特性

- 可选将本机 DNS 请求转发到本地 AdGuardHome 服务器
- 使用 [秋风广告规则](https://github.com/TG-Twilight/AWAvenue-Ads-Rule) 过滤广告,轻量,省电,少误杀
- 可从 <http://127.0.0.1:3000> 访问 AdGuardHome 控制面板,支持查询统计,修改 DNS 上游服务器以及自定义规则等功能

## 教程

1. 前往 [Release](https://github.com/twoone-3/AdGuardHomeForRoot/releases/latest) 页面下载模块
2. 检查 Android 设置 -> 网络和互联网 -> 高级 -> 私人 DNS,确保 `私人 DNS` 关闭
3. 在 root 管理器中安装模块,重启设备
4. 若看到模块运行成功的提示,则可以访问 <http://127.0.0.1:3000> 进入 AdGuardHome 后台,默认用户密码 root/root
5. 若需高级使用教程和常见问题解答,请访问 **[文档与教程](docs/index.md)**。

## 鸣谢

- [AWAwenue Ads Rule](https://github.com/TG-Twilight/AWAvenue-Ads-Rule)
- [AdguardHome_magisk](https://github.com/410154425/AdGuardHome_magisk)
- [akashaProxy](https://github.com/ModuleList/akashaProxy)
- [box_for_magisk](https://github.com/taamarin/box_for_magisk)

> 特别感谢赞助:
>
> - y******a - 200
> - 偶****** - 10


================================================
FILE: README_en.md
================================================
# AdGuardHome for Root

English | [简体中文](README.md)

![arm-64 support](https://img.shields.io/badge/arm--64-support-ef476f?logo=linux&logoColor=white&color=ef476f)
![arm-v7 support](https://img.shields.io/badge/arm--v7-support-ffa500?logo=linux&logoColor=white&color=ffa500)
![GitHub downloads](https://img.shields.io/github/downloads/twoone-3/AdGuardHomeForRoot/total?logo=github&logoColor=white&color=ffd166)
![License](https://img.shields.io/badge/License-MIT-9b5de5?logo=opensourceinitiative&logoColor=white)
[![Docs](https://img.shields.io/badge/Docs-Guide-0066ff?logo=book&logoColor=white)](docs/index.md)
[![Join Telegram Channel](https://img.shields.io/badge/Telegram-Join%20Channel-06d6a0?logo=telegram&logoColor=white)](https://t.me/+Q3Ur_HCYdM0xM2I1)
[![Join Telegram Group](https://img.shields.io/badge/Telegram-Join%20Group-118ab2?logo=telegram&logoColor=white)](https://t.me/+Q3Ur_HCYdM0xM2I1)

Follow our channel for the latest news, or join our group for discussion!

## Introduction

- This module is a module that runs [AdGuardHome](https://github.com/AdguardTeam/AdGuardHome) on Android devices, providing a local DNS server that can block ads, malware, and trackers.
- It can be used as a local ad-blocking module or transformed into a standalone AdGuardHome tool by adjusting the configuration file.
- The module supports multiple installation methods, including Magisk, KernelSU, and APatch, making it compatible with most Android devices.
- The design of this module aims to provide a lightweight ad-blocking solution, avoiding the complexity and performance loss associated with using VPNs.
- It can coexist with other proxy software (such as [NekoBox](https://github.com/MatsuriDayo/NekoBoxForAndroid), [FlClash](https://github.com/chen08209/FlClash), [box for magisk](https://github.com/taamarin/box_for_magisk), [akashaProxy](https://github.com/akashaProxy/akashaProxy)), providing better privacy protection and network security.

## Features

- Optionally forward local DNS requests to the local AdGuardHome server
- Filter ads using [AWAvenue-Ads-Rule](https://github.com/TG-Twilight/AWAvenue-Ads-Rule) for lightweight, power-saving, and fewer false positives
- Access the AdGuardHome control panel from <http://127.0.0.1:3000>, supporting query statistics, modifying DNS upstream servers, and custom rules, etc.

## Tutorial

1. Go to the [Release](https://github.com/twoone-3/AdGuardHomeForRoot/releases/latest) page to download the module
2. Check Android Settings -> Network & Internet -> Advanced -> Private DNS, ensure `Private DNS` is turned off
3. Install the module in the root manager and reboot the device
4. If you see a successful module running prompt, you can access <http://127.0.0.1:3000> to enter the AdGuardHome backend, default username and password are root/root
5. For advanced usage tutorials and FAQs, please visit **[Docs & Tutorials](docs/index.md)**.

## Acknowledgments

- [AWAwenue Ads Rule](https://github.com/TG-Twilight/AWAvenue-Ads-Rule)
- [AdguardHome_magisk](https://github.com/410154425/AdGuardHome_magisk)
- [akashaProxy](https://github.com/ModuleList/akashaProxy)
- [box_for_magisk](https://github.com/taamarin/box_for_magisk)

> Special thanks to sponsors:
>
> - y******a - 200
> - 偶****** - 10


================================================
FILE: changelog.md
================================================
# Changelog

- 同步 AdGuard Home v0.107.74
- Sync with AdGuard Home v0.107.74
- 新增 WebUI 配置界面 #61 (wTNTw)
- New WebUI interface #61 (wTNTw)

================================================
FILE: docs/index.md
================================================
# 教程 (Tutorials)

> **For English Users:** This module is primarily designed for Chinese users. If you require the documentation in English, we kindly recommend using a reliable translation tool.

## 安装 (Installation)

本模块仅适用于已经 root 的安卓设备,支持 [Magisk](https://github.com/topjohnwu/Magisk) / [KernelSU](https://github.com/tiann/KernelSU) / [APatch](https://github.com/bmax121/APatch) 等 root 工具

在 Release 页面下载 zip 文件,提供了 arm64 和 armv7 两个版本。一般推荐使用 arm64 版,因为它在性能上更优,并且与大多数现代设备兼容。

---

## 配置 (Configuration)

模块默认的 AdGuardHome 后台地址为 `http://127.0.0.1:3000`,可以通过浏览器直接访问,默认账号和密码均为 `root`。

在 AdGuardHome 后台,你可以执行以下操作:

- 查看 DNS 查询统计信息
- 修改各种 DNS 配置
- 查看日志
- 添加自定义规则

如果你更倾向于使用app管理AdGuardHome,可以尝试使用 [AdGuard Home Manager](https://github.com/JGeek00/adguard-home-manager) 应用。

---

## 模块控制 (Module Control)

模块的状态会实时显示在`module.prop`文件中,在root管理器中可以看到模块的状态信息(如果没刷新请手动刷新)

模块实时监测`/data/adb/modules/AdGuardHome`目录下的`disable`文件,如果存在则禁用模块,不存在则启用模块

如果你想用其他方法来启停,你可以在文件管理器中手动创建和删除文件,也可以使用shell命令

```shell
touch /data/adb/modules/AdGuardHome/disable
```

```shell
rm /data/adb/modules/AdGuardHome/disable
```

本模块可以分为两部分,一部分是 AdGuardHome 本身,它在本地搭建了一个可自定义拦截功能的 DNS 服务器,另一部分是 iptables 转发规则,它负责将本机所有53端口出口流量重定向到 AdGuardHome

---

## 与代理软件共存 (Coexistence with Proxy Software)

代理软件主要分为两类:

**代理应用**:如 [NekoBox](https://github.com/MatsuriDayo/NekoBoxForAndroid)、[FlClash](https://github.com/chen08209/FlClash) 等。这些应用通常具有图形化界面,便于用户配置和管理代理规则。

以下是我自用的 FlClash 配置文件示例:

```yaml
proxy-providers:
  provider1:
    type: http
    url: ""
    interval: 86400

  provider2:
    type: http
    url: ""
    interval: 86400

proxy-groups:
  - name: PROXY
    type: select
    include-all: true

rules:
proxy-groups:
  - name: PROXY
    type: select
    include-all: true

rules:
  - GEOSITE,private,DIRECT
  - GEOSITE,googlefcm,DIRECT
  - GEOSITE,bilibili,DIRECT
  - GEOSITE,onedrive,PROXY
  - GEOSITE,twitter,PROXY
  - GEOSITE,youtube,PROXY
  - GEOSITE,telegram,PROXY
  - GEOSITE,google,PROXY
  
  - GEOSITE,microsoft@cn,DIRECT
  - GEOSITE,category-scholar-!cn,PROXY
  - GEOSITE,steam@cn,DIRECT
  - GEOSITE,category-games@cn,DIRECT
  - GEOSITE,geolocation-!cn,PROXY
  - GEOSITE,cn,DIRECT

  - GEOIP,private,DIRECT,no-resolve
  - GEOIP,google,DIRECT
  - GEOIP,telegram,PROXY
  - GEOIP,cn,DIRECT

  - MATCH,DIRECT

```

没有写 DNS 部分是因为 FlClash 支持 DNS 覆写,在软件内就可配置 DNS 部分,将域名解析服务器改为 127.0.0.1:5591 即可使用本地的 adgh 作为DNS服务器

**代理模块**:如 [box_for_magisk](https://github.com/taamarin/box_for_magisk)、[akashaProxy](https://github.com/akashaProxy/akashaProxy) 等。这些模块通常运行在系统层级,适合需要更高权限或更深度集成的场景。

代理应用的 `分应用代理/访问控制` 功能非常实用。通过将国内应用设置为绕过模式,可以减少不必要的流量经过代理,同时这些绕过的应用仍然能够正常屏蔽广告。

如果使用代理模块,强烈建议禁用模块的 iptables 转发规则。禁用后,模块仅运行 AdGuardHome 本身。随后,将代理模块的上游 DNS 服务器配置为 `127.0.0.1:5591`,即可确保代理软件的所有 DNS 查询通过 AdGuardHome 进行广告屏蔽。

```yaml
dns:
  # ...
  default-nameserver:
    - 223.5.5.5
  nameserver:
    - 127.0.0.1:5591
  # ...
```

---

## 模块目录与配置文件 (Module Directory and Configuration Files)

模块的文件结构主要分为以下两个目录:

- **`/data/adb/agh`**:包含 AdGuardHome 的核心文件,包括二进制文件、工具脚本和配置文件。
- **`/data/adb/modules/AdGuardHome`**:存储模块的启动脚本和运行时数据文件。

模块的配置文件也分为两部分:

- **`/data/adb/agh/bin/AdGuardHome.yaml`**:AdGuardHome 的主配置文件。
- **`/data/adb/agh/settings.conf`**:模块的配置文件,具体说明请参考文件内的注释。

在更新模块时,用户可以选择是否保留原有的配置文件。如果选择不保留,系统会自动将原配置文件备份到 **`/data/adb/agh/backup`** 目录,以确保数据安全。

---

## 模块打包 (Module Packaging)

模块根目录下提供了一个名为 `pack.ps1` 的打包脚本,用户可以通过它快速生成模块的安装包。

在 Windows 系统上,打开 PowerShell 并执行以下命令:

```powershell
.\pack.ps1
```

运行脚本后,以下操作将自动完成:

1. 创建 `cache` 目录(如果尚未存在)。
2. 下载并缓存最新版本的 AdGuardHome(仅在 `cache` 目录中未找到缓存时执行下载)。
3. 将 AdGuardHome 与模块的其他文件打包成一个 ZIP 文件。

该脚本的设计确保了高效性:如果 `cache` 目录中已存在 AdGuardHome 的缓存版本,则无需重复下载,从而节省时间和带宽。

## 常见问题 (Frequently Asked Questions)

### **Q: 模块安装后无法正常运行怎么办?**  

**A:**  

- 检查 AdGuardHome 是否在运行:  
  使用以下命令查看进程状态:  

  ```shell
  ps | grep AdGuardHome
  ```

- 确保设备的 **私人 DNS** 功能已关闭:  
  前往 **设置 -> 网络和互联网 -> 高级 -> 私人 DNS**,并将其设置为关闭。

### **Q: 如何更改 AdGuardHome 的默认端口?**  

**A:**  

- 打开 **`/data/adb/agh/bin/AdGuardHome.yaml`** 文件。  
- 修改 `bind_host` 的端口号为所需值。  
- 保存文件后,重启模块以应用更改。

### **Q: 如何禁用模块的 iptables 转发规则?**  

**A:**  

- 编辑 **`/data/adb/agh/settings.conf`** 文件。  
- 将 `ENABLE_IPTABLES` 参数设置为 `false`。  
- 保存文件后,重启模块。

### **Q: 使用代理模块时,广告屏蔽无效怎么办?**  

**A:**  

- 确保代理模块的上游 DNS 服务器配置为 **`127.0.0.1:5591`**。  
- 检查代理模块的配置文件,确保所有 DNS 查询通过 AdGuardHome。

### **Q: 模块是否会影响设备性能?**  

**A:**  

- 模块对性能的影响较小,但在低性能设备上可能会有轻微延迟。  
- 推荐使用 **arm64** 版本以获得更好的性能。

### **Q:使用模块后,无法访问 Google 怎么办?**

**A:**

- 如果你用的是 FlClash,可尝试在`settings.conf`填入以下配置:

```ini
ignore_src_list="172.19.0.1"
```

此问题与节点质量有关,有的机场不改也没问题


================================================
FILE: pack.ps1
================================================
# 定义下载 URL 和路径变量
$CacheDir = "$PSScriptRoot\cache"
$UrlWitchCachePath = @{
  "https://github.com/AdguardTeam/AdGuardHome/releases/latest/download/AdGuardHome_linux_arm64.tar.gz" = "$CacheDir\AdGuardHome_linux_arm64.tar.gz"
  "https://github.com/AdguardTeam/AdGuardHome/releases/latest/download/AdGuardHome_linux_armv7.tar.gz" = "$CacheDir\AdGuardHome_linux_armv7.tar.gz"
}

# 创建缓存目录
if (-Not (Test-Path -Path $CacheDir)) {
  Write-Host "Creating cache directory..."
  New-Item -Path $CacheDir -ItemType Directory
}

# 下载文件,有缓存时不再下载
Write-Host "Downloading AdGuardHome..."
foreach ($url in $UrlWitchCachePath.Keys) {
  $CachePath = $UrlWitchCachePath[$url]
  if (-Not (Test-Path -Path $CachePath)) {
    Write-Host "Downloading $url..."
    Invoke-WebRequest -Uri $url -OutFile $CachePath
    if ($?) {
      Write-Host "Download completed successfully."
    }
    else {
      Write-Host "Download failed. Exiting..."
      exit 1
    }
  }
  else {
    Write-Host "File already exists in cache. Skipping download."
  }
}

# 使用 tar 解压文件
Write-Host "Extracting AdGuardHome..."
foreach ($url in $UrlWitchCachePath.Keys) {
  $CachePath = $UrlWitchCachePath[$url]
  if ($CachePath -match 'AdGuardHome_linux_(arm64|armv7)\.tar\.gz$') {
    $ExtractDir = "./cache/" + $matches[1]
  }
  else {
    throw "Invalid file path: $CachePath"
  }
  if (-Not (Test-Path -Path $ExtractDir)) {
    New-Item -Path $ExtractDir -ItemType Directory
    Write-Host "Extracting $CachePath..."
    tar -xzf $CachePath -C $ExtractDir
    if ($?) {
      Write-Host "Extraction completed successfully."
    }
    else {
      Write-Host "Extraction failed"
      exit 1
    }
  }
}

# 给项目打包,使用 7-Zip 压缩 zip
Write-Host "Packing AdGuardHome..."
$OutputPathArm64 = "$CacheDir\AdGuardHomeForRoot_arm64.zip"
$OutputPathArmv7 = "$CacheDir\AdGuardHomeForRoot_armv7.zip"
if (Test-Path -Path $OutputPathArm64) {
  Remove-Item -Path $OutputPathArm64
}
if (Test-Path -Path $OutputPathArmv7) {
  Remove-Item -Path $OutputPathArmv7
}

# 设置项目根目录
$ProjectRoot = "$PSScriptRoot\src"
$env:PATH += ";C:\Program Files\7-Zip"

# pack arm64
7z a -tzip $OutputPathArm64 "$ProjectRoot\*.sh"
7z a -tzip $OutputPathArm64 "$ProjectRoot\settings.conf"
7z a -tzip $OutputPathArm64 "$ProjectRoot\module.prop"
7z a -tzip $OutputPathArm64 "$ProjectRoot\META-INF"
7z a -tzip $OutputPathArm64 "$ProjectRoot\scripts"
7z a -tzip $OutputPathArm64 "$ProjectRoot\webroot"
7z a -tzip $OutputPathArm64 "$ProjectRoot\bin\"
7z a -tzip $OutputPathArm64 "$CacheDir\arm64\AdGuardHome\AdGuardHome"
7z rn $OutputPathArm64 "AdGuardHome" "bin/AdGuardHome"

# pack armv7
7z a -tzip $OutputPathArmv7 "$ProjectRoot\*.sh"
7z a -tzip $OutputPathArmv7 "$ProjectRoot\settings.conf"
7z a -tzip $OutputPathArmv7 "$ProjectRoot\module.prop"
7z a -tzip $OutputPathArmv7 "$ProjectRoot\META-INF"
7z a -tzip $OutputPathArmv7 "$ProjectRoot\scripts"
7z a -tzip $OutputPathArmv7 "$ProjectRoot\webroot"
7z a -tzip $OutputPathArmv7 "$ProjectRoot\bin\"
7z a -tzip $OutputPathArmv7 "$CacheDir\armv7\AdGuardHome\AdGuardHome"
7z rn $OutputPathArmv7 "AdGuardHome" "bin/AdGuardHome"

Write-Host "Packing completed successfully."

================================================
FILE: src/META-INF/com/google/android/update-binary
================================================
#!/sbin/sh

#################
# Initialization
#################

umask 022

# echo before loading util_functions
ui_print() { echo "$1"; }

require_new_magisk() {
  ui_print "*******************************"
  ui_print " 请升级安装 Magisk v20.4或以上! "
  ui_print "*******************************"
  exit 1
}

#########################
# Load util_functions.sh
#########################

OUTFD=$2
ZIPFILE=$3

mount /data 2>/dev/null

[ -f /data/adb/magisk/util_functions.sh ] || require_new_magisk
. /data/adb/magisk/util_functions.sh
[ $MAGISK_VER_CODE -lt 20400 ] && require_new_magisk

install_module
exit 0

================================================
FILE: src/META-INF/com/google/android/updater-script
================================================
#MAGISK


================================================
FILE: src/action.sh
================================================
. /data/adb/agh/settings.conf

$SCRIPT_DIR/tool.sh toggle

sleep 1


================================================
FILE: src/bin/AdGuardHome.yaml
================================================
http:
  pprof:
    port: 6060
    enabled: false
  address: 127.0.0.1:3000
  session_ttl: 720h
users:
  - name: root
    password: $2b$12$D3zeMIBFfzcQTqGqB.k7GOkMvqx1jgsrdiRCn2kwOHl.kNmmPfMom
auth_attempts: 5
block_auth_min: 15
http_proxy: ""
language: ""
theme: auto
dns:
  bind_hosts:
    - 127.0.0.1
  port: 5591
  anonymize_client_ip: false
  ratelimit: 0
  ratelimit_subnet_len_ipv4: 24
  ratelimit_subnet_len_ipv6: 56
  ratelimit_whitelist: []
  refuse_any: true
  upstream_dns:
    - https://doh.pub/dns-query
    - https://dns.alidns.com/dns-query
  upstream_dns_file: ""
  bootstrap_dns:
    - 119.29.29.29
    - 223.5.5.5
    - 223.6.6.6
    - 1.1.1.1
    - 8.8.8.8
  fallback_dns:
    - https://dns.google/dns-query
    - https://cloudflare-dns.com/dns-query
  upstream_mode: load_balance
  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: 67108864
  cache_ttl_min: 0
  cache_ttl_max: 0
  cache_optimistic: true
  cache_optimistic_answer_ttl: 30s
  cache_optimistic_max_age: 12h
  bogus_nxdomain: []
  aaaa_disabled: false
  enable_dnssec: false
  edns_client_subnet:
    custom_ip: ""
    enabled: true
    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: false
  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: 24h
  size_memory: 1000
  enabled: true
  file_enabled: true
statistics:
  dir_path: ""
  ignored: []
  interval: 24h
  enabled: true
filters:
  - enabled: true
    url: https://raw.githubusercontent.com/TG-Twilight/AWAvenue-Ads-Rule/main/AWAvenue-Ads-Rule.txt
    name: AWAvenue
    id: 1732747955
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: Asia/Shanghai
    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: []
  safebrowsing_cache_size: 1048576
  safesearch_cache_size: 1048576
  parental_cache_size: 1048576
  cache_time: 30
  filters_update_interval: 72
  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: src/bin/data/filters/1732747955.txt
================================================
||1500020991.vodplayer.wxamedia.com^
||1500022744.vodplayer.wxamedia.com^
||1500026601.vodplayer.wxamedia.com^
||8le8le.com^
||8rn1y79f02-1.algolianet.com^
||a0.app.xiaomi.com^
||aaid.umeng.com^
||abtest-ch.snssdk.com^
||ad-cdn.qingting.fm^
||ad-scope.com^
||ad-scope.com.cn^
||ad-sdk-config.youdao.com^
||ad-splash-tracking.hktvmall.com^
||ad-splash.hktvmall.com^
||ad.12306.cn^
||ad.51wnl.com^
||ad.api.3g.youku.com^
||ad.bwton.com^
||ad.cctv.com^
||ad.cyapi.cn^
||ad.doubleclick.net^
||ad.gameley.com^
||ad.hpplay.cn^
||ad.iot.360.cn^
||ad.jia.360.cn^
||ad.life.360.cn^
||ad.partner.gifshow.com^
||ad.qingting.fm^
||ad.qq.com^
||ad.richmob.cn^
||ad.shenshiads.com^
||ad.shunchangzhixing.com^
||ad.tencentmusic.com^
||ad.toutiao.com^
||ad.v3mh.com^
||ad.weibo.com^
||ad.winrar.com.cn^
||ad.ximalaya.com^
||ad.xunkids.com^
||ad.zijieapi.com^
||adapi.izuiyou.com^
||adapi.yynetwk.com^
||adashbc.ut.taobao.com^
||adashx.m.taobao.com^
||adashxgc.ut.taobao.com^
||adc.hpplay.cn^
||adcdn.hpplay.cn^
||adcdn.tencentmusic.com^
||adclick.g.doubleclick.net^
||adclick.tencentmusic.com^
||adcolony.com^
||addata.jd.com^
||adeng.hpplay.cn^
||adexp.chinaliftoff.io^
||adexp.liftoff.io^
||adexpo.tencentmusic.com^
||adfilter.imtt.qq.com^
||adguanggao.eee114.com^
||adimg.uve.weibo.com^
||adjust.cn^
||adjust.com^
||adks.hpplay.cn^
||adlink-api.huan.tv^
||adm.funshion.com^
||ads-api-o.api.leiniao.com^
||ads-api.tiktok.com^
||ads-api.twitter.com^
||ads-img-al.xhscdn.com^
||ads-img-qc.xhscdn.com^
||ads-jp.tiktok.com^
||ads-marketing-vivofs.vivo.com.cn^
||ads-sdk-api.ranfenghd.com^
||ads-sg.tiktok.com^
||ads-video-al.xhscdn.com^
||ads-video-qc.xhscdn.com^
||ads.95516.com^
||ads.auctions.yahoo.com^
||ads.cup.com.cn^
||ads.google.cn^
||ads.huan.tv^
||ads.huantest.com^
||ads.icloseli.cn^
||ads.inmobi.com^
||ads.linkedin.com^
||ads.pinterest.com^
||ads.pubmatic.com^
||ads.raidrive.com^
||ads.servebom.com^
||ads.service.kugou.com^
||ads.tiktok.com^
||ads.v3mh.com^
||ads.youtube.com^
||ads.zhinengxiyifang.cn^
||ads3-normal-hl.zijieapi.com^
||ads3-normal-lf.zijieapi.com^
||ads3-normal-lq.zijieapi.com^
||ads3-normal.zijieapi.com^
||ads5-normal-hl.zijieapi.com^
||ads5-normal-lf.zijieapi.com^
||ads5-normal-lq.zijieapi.com^
||ads5-normal.zijieapi.com^
||adsdk.vivo.com.cn^
||adse.test.ximalaya.com^
||adse.wsa.ximalaya.com^
||adse.ximalaya.com^
||adsebs.ximalaya.com^
||adsense.google.cn^
||adserver.unityads.unity3d.com^
||adservice.google.com^
||adservice.sigmob.cn^
||adserviceretry.kugou.com^
||adsfile.bssdlbig.kugou.com^
||adsfile.qq.com^
||adsfilebssdlbig.ali.kugou.com^
||adsfileretry.service.kugou.com^
||adsfs-sdkconfig.heytapimage.com^
||adsfs.oppomobile.com^
||adslvfile.qq.com^
||adsmart.konka.com^
||adsmind.gdtimg.com^
||adsmind.ugdtimg.com^
||adsp.xunlei.com^
||adstat.izuiyou.com^
||adstats.tencentmusic.com^
||adstore-1252524079.file.myqcloud.com^
||adstore-index-1252524079.file.myqcloud.com^
||adstrategy.biz.weibo.com^
||adstudio-assets.scdn.co^
||adstudio.spotify.com^
||adtago.s3.amazonaws.com^
||adtech.yahooinc.com^
||adtrack.quark.cn^
||adtracker.medproad.com^
||adui.tg.meitu.com^
||adv-api.shenshiads.com^
||adv.sec.intl.miui.com^
||adv.sec.miui.com^
||advertising-api-eu.amazon.com^
||advertising-api-fe.amazon.com^
||advertising-api.amazon.com^
||advertising.apple.com^
||advertising.yahoo.com^
||advertising.yandex.ru^
||advice-ads.s3.amazonaws.com^
||adview.cn^
||adx-ad.smart-tv.cn^
||adx-api.jinmo.tech^
||adx-bj-req.anythinktech.com^
||adx-bj.anythinktech.com^
||adx-cn.anythinktech.com^
||adx-drcn.op.dbankcloud.cn^
||adx-open-service.youku.com^
||adx-os.anythinktech.com^
||adx-saas.advlion.com^
||adx-strategy-api-cn.statisticslinks.com^
||adx.ads.heytapmobi.com^
||adx.ads.oppomobile.com^
||adx.appsdk.com.cn^
||adx.sogaha.cn^
||adx.tuia.cn^
||adxlog-adnet.vivo.com.cn^
||adxserver.ad.cmvideo.cn^
||aegis.qq.com^
||aem.dentsuchina.cn^
||afs.googlesyndication.com^
||agn.aty.sohu.com^
||aiseet.aa.atianqi.com^
||ali-ad.a.yximgs.com^
||ali-p2p-v2.pull.yximgs.com^
||alicoccdncnc-xn.inter.71edge.com^
||alicoccdnct-xn.inter.71edge.com^
||alog.umeng.com^
||alogus.umeng.com^
||als.baidu.com^
||amdcopen.m.taobao.com^
||an.facebook.com^
||analysis.chatglm.cn^
||analysis.yozocloud.cn^
||analytics-api.samsunghealthcn.com^
||analytics.adjust.cn^
||analytics.oceanengine.com^
||analytics.pinterest.com^
||analytics.pointdrive.linkedin.com^
||analytics.query.yahoo.com^
||analytics.rayjump.com^
||analytics.s3.amazonaws.com^
||analytics.tiktok.com^
||analytics.woozooo.com^
||analyticsengine.s3.amazonaws.com^
||analyze.lemurbrowser.com^
||andrqd.play.aiseet.atianqi.com^
||ap.dongdianqiu.com^
||ap.dongqiudi.com^
||apd-pcdnwxlogin.teg.tencent-cloud.net^
||apd-pcdnwxnat.teg.tencent-cloud.net^
||apd-pcdnwxstat.teg.tencent-cloud.net^
||api-access.pangolin-sdk-toutiao.com^
||api-access.pangolin-sdk-toutiao1.com^
||api-access.pangolin-sdk-toutiao2.com^
||api-access.pangolin-sdk-toutiao3.com^
||api-access.pangolin-sdk-toutiao4.com^
||api-access.pangolin-sdk-toutiao5.com^
||api-ad-data.kajicam.com^
||api-ad-product.huxiu.com^
||api-ad.kajicam.com^
||api-adservices.apple.com^
||api-gd.hiaiabc.com^
||api-htp.beizi.biz^
||api.ad.xiaomi.com^
||api.anythinktech.com^
||api.aqyad.com^
||api.e.kuaishou.com^
||api.fu.xcultur.com^
||api.htp.hubcloud.com.cn^
||api.hzsanjiaomao.com^
||api.installer.xiaomi.com^
||api.jietuhb.com^
||api.kingdata.ksyun.com^
||api.shanghailingye.cn^
||api.ssp.xcultur.com^
||api.statsig.com^
||api.touch-moblie.com^
||api.yfanads.com^
||api5-normal-quic-lf.ixigua.com^
||apiyd.my91app.com^
||app-measurement.com^
||appcfg.v.qq.com^
||applog-perf.uc.cn^
||applog.lc.quark.cn^
||applog.uc.cn^
||applog.zijieapi.com^
||applovin.com^
||aqyad.com^
||aspect-upush.umeng.com^
||auction.unityads.unity3d.com^
||audid-api.taobao.com^
||audid.umeng.com^
||b1-data.ads.heytapmobi.com^
||badjs.weixinbridge.com^
||baichuan-sdk.alicdn.com^
||baichuan-sdk.taobao.com^
||bdad.123pan.cn^
||bdapi-ads.realmemobile.com^
||bdapi-in-ads.realmemobile.com^
||bdapi.ads.oppomobile.com^
||bdcdncnc.inter.71edge.com^
||beacon-api.aliyuncs.com^
||beacon.qq.com^
||beaconcdn.qq.com^
||beacons.gvt2.com^
||beizi.biz^
||bes-mtj.baidu.com^
||bgg.baidu.com^
||bianxian.com^
||bingads.microsoft.com^
||biz.weibo.com^
||bj-td-menta-01-callback.advlion.com^
||bj.ad.track.66mobi.com^
||books-analytics-events.apple.com^
||bootpreload.uve.weibo.com^
||browser.events.data.msn.cn^
||browser.events.data.msn.com^
||browsercfg-drcn.cloud.dbankcloud.cn^
||bsrv.qq.com^
||business-api.tiktok.com^
||c.bidtoolads.com^
||c.etoolads.cn^
||c.evidon.com^
||c.gj.qq.com^
||c.kuaiduizuoye.com^
||c.sayhi.360.cn^
||c2.gdt.qq.com^
||canvas-cdn.gdt.qq.com^
||catalog.fjwhcbsh.com^
||cbjs.baidu.com^
||ccc-x.jd.com^
||ccs.umeng.com^
||cdn-ad.wtzw.com^
||cdn-ads.oss-cn-shanghai.aliyuncs.com^
||cdn-f.adsmoloco.com^
||cdn-plugin-sync-upgrade-juui.hismarttv.com^
||cdn.aiclk.com^
||cdn.chinaliftoff-creatives.io^
||cdn.hpplay.com.cn^
||cdn.iads.unitychina.cn^
||cdn.liftoff-creatives.io^
||cdn.ynuf.aliapp.org^
||cfg.imtt.qq.com^
||chat1.jd.com^
||chiq-cloud.com^
||ck.ads.oppomobile.com^
||click.chinaliftoff.io^
||click.googleanalytics.com^
||click.liftoff.io^
||click.oneplus.cn^
||click4.chinaliftoff.io^
||clog.miguvideo.com^
||cloooud.com^
||cn-api.anythinktech.com^
||cnlogs.umeng.com^
||cnlogs.umengcloud.com^
||cnzz.com^
||collect.kugou.com^
||commdata.v.qq.com^
||conf.hpplay.cn^
||config.chsmarttv.com^
||config.inmobi.com^
||config.unityads.unity3d.com^
||cpc-service-square.aiclk.com^
||cpro.baidustatic.com^
||crash2.zhihu.com^
||crashlyticsreports-pa.googleapis.com^
||csjplatform.com^
||d.applovin.com^
||d.applvn.com^
||da.anythinktech.com^
||data-sdk-uuid-log.d.meituan.net^
||data.ads.oppomobile.com^
||data.chsmarttv.com^
||data.mistat.india.xiaomi.com^
||data.mistat.rus.xiaomi.com^
||data.mistat.xiaomi.com^
||datacollection.uve.weibo.com^
||dc.sigmob.cn^
||de.tynt.com^
||diagnosis.ad.xiaomi.com^
||dig.bdurl.net^
||dlogs.bwton.com^
||dm.toutiao.com^
||domain.aishengji.com^
||doubleclick-cn.net^
||download.changhong.upgrade2.huan.tv^
||downloadxml.changhong.upgrade2.huan.tv^
||drcn-weather.cloud.huawei.com^
||dsp-x.jd.com^
||dsp.fcbox.com^
||dualstack-logs.amap.com^
||dxp.baidu.com^
||dyp2p-ali.douyucdn.cn^
||dyp2p-hw.douyucdn.cn^
||e.ad.xiaomi.com^
||eclick.baidu.com^
||edge.ads.twitch.tv^
||ef-dongfeng.tanx.com^
||engine.dcad01.com^
||entry.baidu.com^
||et-eus.w.inmobi.com^
||event.tradplusad.com^
||events-drcn.op.dbankcloud.cn^
||events.reddit.com^
||events.redditmedia.com^
||fancyapi.com^
||fclog.baidu.com^
||feed-image.baidu.com^
||file.daihuo.qq.com^
||firebaselogging-pa.googleapis.com^
||flurry.com^
||frontend-perf-service.e.kuaishou.com^
||fxgate.baidu.com^
||g-adnet.hiaiabc.com^
||g-staic.ganjingworld.com^
||g.dtv.cn.miaozhen.com^
||g.fancyapi.com^
||g2.ganjing.world^
||game.loveota.com^
||gdfp.gifshow.com^
||gemini.yahoo.com^
||geo.yahoo.com^
||getui.cn^
||ggx.cmvideo.cn^
||ggx01.miguvideo.com^
||ggx03.miguvideo.com^
||globalapi.ad.xiaomi.com^
||google-analytics.com^
||googleads.g.doubleclick-cn.net^
||googleads.g.doubleclick.net^
||googleadservices-cn.com^
||googleadservices.com^
||googletagservices-cn.com^
||gorgon.youdao.com^
||gromore.pangolin-sdk-toutiao.com^
||grs.dbankcloud.com^
||grs.hicloud.com^
||gslb.hpplay.cn^
||gvideo.qpic.cn^
||h-adashx.ut.taobao.com^
||h.trace.qq.com^
||h5.analytics.126.net^
||h5.hpplay.com.cn^
||hanlanad.com^
||hc-ssp.sm.cn^
||heads-ak.spotify.com.edgesuite.net^
||heads-fa.spotify.com^
||hexagon-analytics.com^
||hlog.bigda.com^
||hm.baidu.com^
||hmma.baidu.com^
||hotupgrade.hpplay.cn^
||houyi.kkmh.com^
||hpplay.cdn.cibn.cc^
||httpdns.bcelive.com^
||httpdns.ocloud.oppomobile.com^
||hugelog.fcbox.com^
||huichuan-mc.sm.cn^
||huichuan.sm.cn^
||hw-ot-ad.a.yximgs.com^
||hw-p2p-pull.video-voip.com^
||hw-p2p.pull.yximgs.com^
||hwpub-s01-drcn.cloud.dbankcloud.cn^
||hybrid.miniapp.taobao.com^
||hye.comp.360os.com^
||hyt.comp.360os.com^
||i.l-new.inmobicdn.net^
||i.l.inmobicdn.net^
||iad.apple.com^
||iad.g.163.com^
||iadctest.qwapi.com^
||iadmusicmat.music.126.net^
||iadsdk.apple.com^
||iadworkbench.apple.com^
||ifacelog.iqiyi.com^
||ifs.tanx.com^
||ii.gdt.qq.com^
||image-ad.sm.cn^
||image.hpplay.cn^
||images.outbrainimg.com^
||images.pinduoduo.com^
||imdns.hpplay.cn^
||img-c.heytapimage.com^
||img-x.jd.com^
||img.adnyg.com.w.kunlungr.com^
||img2.360buyimg.com^
||impression-asia.chinaliftoff.io^
||impression-asia.liftoff.io^
||impression.appsflyer.com^
||in.treasuredata.com^
||ios.bugly.qq.com^
||iot-eu-logser.realme.com^
||iot-logser.realme.com^
||ipv4.kkmh.com^
||irc.qubiankeji.com^
||ixav-cse.avlyun.com^
||iyfbodn.com^
||janapi.jd.com^
||jiguang.cn^
||jpush.cn^
||jpush.html5.qq.com^
||jpush.io^
||jswebcollects.kugou.com^
||kde.qq.com^
||kepler.jd.com^
||kl.67it.com^
||klink.volceapplog.com^
||knicks.jd.com^
||ks-p2p-v2.pull.yximgs.com^
||ks-p2p.pull.yximgs.com^
||ks.ferlytc.com^
||ks.pull.yximgs.com^
||l6.fancyapi.com^
||launcher.smart-tv.cn^
||launcherimg.smart-tv.cn^
||lf3-ad-union-sdk.pglstatp-toutiao.com^
||lf6-ad-union-sdk.pglstatp-toutiao.com^
||lftxali.fancyapi.com^
||lh3.googleadsserving.cn^
||litchiads.com^
||live-api.hktvmall.com^
||liveats-vod.video.ptqy.gitv.tv^
||livemonitor.huan.tv^
||livep.l.aiseet.atianqi.com^
||lives.l.aiseet.atianqi.com^
||lives.l.ott.video.qq.com^
||livewebbs2pcdn.msstatic.com^
||lm10111.jtrincc.cn^
||log-api-mn.huxiu.com^
||log-api.huxiu.com^
||log-api.pangolin-sdk-toutiao-b.com^
||log-api.pangolin-sdk-toutiao.com^
||log-report.com^
||log-sdk.gifshow.com^
||log-sdk.ksapisrv.com^
||log-upload-os.hoyoverse.com^
||log-upload.mihoyo.com^
||log-verify.dutils.com^
||log-verify.hiaiabc.com^
||log.ad.xiaomi.com^
||log.aispeech.com^
||log.amemv.com^
||log.appstore3.huan.tv^
||log.avlyun.com^
||log.byteoversea.com^
||log.fc.yahoo.com^
||log.iflytek.com^
||log.ireader.com^
||log.kuwo.cn^
||log.pinterest.com^
||log.popin.cc^
||log.stat.kugou.com^
||log.tagtic.cn^
||log.tbs.qq.com^
||log.vcgame.cn^
||log.web.kugou.com^
||log.zijieapi.com^
||log1.cmpassport.com^
||logbak.hicloud.com^
||logrcv.aiclk.com^
||logrcv.yunxish.com^
||logs.amap.com^
||logservice.hicloud.com^
||logservice1.hicloud.com^
||logtj.kugou.com^
||logupdate.avlyun.sec.miui.com^
||logwebs.kugou.com^
||luimg.baidu.com^
||m-adnet.hiaiabc.com^
||m.ad.zhangyue.com^
||m.atm.youku.com^
||m.shenshiads.com^
||mapi.m.jd.com^
||masdkv6.3g.qq.com^
||mazu.m.qq.com^
||mbdlog.iqiyi.com^
||mcs.zijieapi.com^
||medproad.com^
||metrics.data.hicloud.com^
||metrics.icloud.com^
||metrics.mzstatic.com^
||metrics2.data.hicloud.com^
||metrika.yandex.ru^
||mi.gdt.qq.com^
||miav-cse.avlyun.com^
||micro-xdb.com^
||mini-prog-drm.vodplayvideo.net^
||mission-pub.smart-tv.cn^
||miui-fxcse.avlyun.com^
||mlog.bigda.com^
||mnqlog.ldmnq.com^
||moatads.com^
||mobads-logs.baidu.com^
||mobads-pre-config.cdn.bcebos.com^
||mobads.baidu.com^
||mobaliyun.res.mgtv.com^
||mobile.da.mgtv.com^
||mobilead.kuwo.cn^
||mobilelog.kugou.com^
||mobilelog.upqzfile.com^
||monitor-ads-test.huan.tv^
||monitor-uu.play.aiseet.atianqi.com^
||monitor.adxsenmeng.com^
||monitor.music.qq.com^
||monitor.uu.qq.com^
||monsetting.toutiao.com^
||mores.toponad.com^
||ms.applovin.com^
||ms.applvn.com^
||ms4.applovin.com^
||ms4.applvn.com^
||msdk.voiceads.cn^
||mssdk.volces.com^
||mssdk.zijieapi.com^
||mtj.baidu.com^
||nadvideo2.baidu.com^
||newvoice.chiq5.smart-tv.cn^
||nex.163.com^
||nmetrics.samsung.com^
||notes-analytics-events.apple.com^
||notify.sec.miui.com^
||nsclick.baidu.com^
||o-sdk.mediation.unity3d.com^
||o2o.api.xiaomi.com^
||offerwall.yandex.net^
||ogads-pa.clients6.google.com^
||omgmta.play.aiseet.atianqi.com^
||open-set-api.shenshiads.com^
||open.e.kuaishou.cn^
||open.e.kuaishou.com^
||open.kuaishouzt.com^
||open.kwaizt.com^
||open.snssdk.com^
||openadapi.fancydsp.com^
||optimus-ads.amap.com^
||orbit.jd.com^
||oss.cdn.aiclk.com^
||oth.eve.mdt.qq.com^
||oth.str.mdt.qq.com^
||otheve.play.aiseet.atianqi.com^
||outlookads.live.com^
||p.l.qq.com^
||p.s.360.cn^
||p1-be-pack-sign.pglstatp-toutiao.com^
||p1-lm.adkwai.com^
||p2-be-pack-sign.pglstatp-toutiao.com^
||p2-lm.adkwai.com^
||p2pchunk-table.douyucdn.cn^
||p2plive-ali.douyucdn.cn^
||p2pupdate.gamedl.qq.com^
||p2pupgrade.gamedl.qq.com^
||p3-be-pack-sign.pglstatp-toutiao.com^
||p3-lm.adkwai.com^
||p3-tt.byteimg.com^
||p4-be-pack-sign.pglstatp-toutiao.com^
||p5-be-pack-sign.pglstatp-toutiao.com^
||p6-be-pack-sign.pglstatp-toutiao.com^
||p66-ad.adkwai.com^
||pagead2.googleadservices.com^
||pagead2.googlesyndication.com^
||pangolin-sdk-toutiao-b.com^
||pay.sboot.cn^
||pcdn.xmcdn.com^
||pcdn.yximgs.com^
||pcm-img.zhls.qq.com^
||pgdt.gtimg.cn^
||pgdt.ugdtimg.com^
||pglstatp-toutiao.com^
||pig.pupuapi.com^
||pin.hpplay.cn^
||pixon.ads-pixiv.net^
||pkoplink.com^
||pl.cp31.ott.cibntv.net^
||plbslog.umeng.com^
||pms.mb.qq.com^
||policy.video.ptqy.gitv.tv^
||pos.baidu.com^
||prod-mediate-events.applovin.com^
||promotion-partner.kuaishou.com^
||proxy.advp.apple.com^
||public.gdtimg.com^
||q.i.gdt.qq.com^
||qcwx.medproad.com^
||qmlog.baertt.com^
||qn-cdnfile1pcdn.msstatic.com^
||qqdata.ab.qq.com^
||qzs.gdtimg.com^
||recommend-drcn.hms.dbankcloud.cn^
||report.tv.kohesport.qq.com^
||res.hubcloud.com.cn^
||res1.applovin.com^
||res1.hubcloud.com.cn^
||res2.hubcloud.com.cn^
||res3.hubcloud.com.cn^
||resolve.umeng.com^
||review.gdtimg.com^
||rlog.popin.cc^
||rms-drcn.platform.dbankcloud.cn^
||roi.soulapp.cn^
||rp.hpplay.cn^
||rps.hpplay.cn^
||rpt.gdt.qq.com^
||rt.applovin.com^
||rt.applvn.com^
||rtb.adxsenmeng.com^
||rtb.voiceads.cn^
||s.amazon-adsystem.com^
||s1.cdn-sg.advlion.com^
||s8t.teads.tv^
||saad.ms.zhangyue.net^
||saas.hpplay.cn^
||safebrowsing.urlsec.gg.com^
||samsung-com.112.2o7.net^
||samsungads.com^
||saxysec.com^
||scs.openspeech.cn^
||sdk-ab-config.qquanquan.com^
||sdk-cache.video.ptqy.gitv.tv^
||sdk.1rtb.net^
||sdk.ad.smaato.net^
||sdk.adx.adwangmai.com^
||sdk.aqyad.com^
||sdk.beizi.biz^
||sdk.cferw.com^
||sdk.e.qq.com^
||sdk.hzsanjiaomao.com^
||sdk.markmedia.com.cn^
||sdk.mobads.adwangmai.com^
||sdkapi.cloooud.com^
||sdkapp.uve.weibo.com^
||sdkauth.hpplay.cn^
||sdkconf.avlyun.com^
||sdkconfig.ad.intl.xiaomi.com^
||sdkconfig.ad.xiaomi.com^
||sdkconfig.play.aiseet.atianqi.com^
||sdkconfig.video.qq.com^
||sdkoptedge.chinanetcenter.com^
||sdkreport.e.qq.com^
||sdktmp.hubcloud.com.cn^
||sdownload.stargame.com^
||search.ixigua.com^
||search3-search.ixigua.com^
||search5-search-hl.ixigua.com^
||search5-search.ixigua.com^
||securemetrics.apple.com^
||securepubads.g.doubleclick.net^
||sensors-log.dongqiudi.com^
||sentry.music.163.com^
||service.changhong.upgrade2.huan.tv^
||service.vmos.cn^
||sf16-static.i18n-pglstatp.com^
||sf3-fe-tos.pglstatp-toutiao.com^
||shouji.sogou.com^
||sigmob.com^
||skdisplay.jd.com^
||sl.hpplay.cn^
||slb-p2p.vcloud.ks-live.com^
||smad.ms.zhangyue.net^
||smartad.10010.com^
||smetrics.samsung.com^
||sms.ads.oppomobile.com^
||sngmta.qq.com^
||snowflake.qq.com^
||ssp.cloooud.com^
||staging-notify.sec.miui.com^
||stat.dongqiudi.com^
||stat.y.qq.com^
||static-s.iqiyi.com^
||static.ads-twitter.com^
||statichf.shihuocdn.cn^
||statics.woozooo.com^
||stats.wp.com^
||statsigapi.net^
||stg-data.ads.heytapmobi.com^
||stun.hitv.com^
||success.ctobsnssdk.com^
||supply.inmobicdn.net^
||sv-video.play.aiseet.atianqi.com^
||syh-imp.cdnjtzy.com^
||syh.zybang.com^
||szbdyd.com^
||t-adx.52qumao.com^
||t-dsp.pinduoduo.com^
||t.applovin.com^
||t.l.qq.com^
||t.teads.tv^
||t.track.ad.xiaomi.com^
||t002.ottcn.com^
||t1.a.market.xiaomi.com^
||t1.teads.tv^
||t2.a.market.xiaomi.com^
||t2.fancyapi.com^
||t3.a.market.xiaomi.com^
||t7z.cupid.ptqy.gitv.tv^
||tagtic.cn^
||tangram.e.qq.com^
||target.ads.jihuoniao.com^
||tdc.qq.com^
||tdsdk.cpatrk.net^
||tdsdk.xdrig.com^
||telecome.cn^
||telemetry.sdk.inmobi.com^
||tencent-dtv.m.cn.miaozhen.com^
||test.ad.xiaomi.com^
||test.e.ad.xiaomi.com^
||tj.b.qq.com^
||tj.video.qq.com^
||tk.anythinktech.com^
||tmead.y.qq.com^
||tmeadcomm.y.qq.com^
||tmfmazu-wangka.m.qq.com^
||tmfmazu.m.qq.com^
||tmfsdk.m.qq.com^
||tmfsdktcpv4.m.qq.com^
||tnc0-aliec2.zijieapi.com^
||tnc0-alisc1.zijieapi.com^
||tnc0-bjlgy.zijieapi.com^
||tnc3-aliec1.toutiaoapi.com^
||tnc3-aliec2.bytedance.com^
||tnc3-aliec2.snssdk.com^
||tnc3-aliec2.toutiaoapi.com^
||tnc3-aliec2.zijieapi.com^
||tnc3-alisc1.bytedance.com^
||tnc3-alisc1.snssdk.com^
||tnc3-alisc1.toutiaoapi.com^
||tnc3-alisc1.zijieapi.com^
||tnc3-alisc2.zijieapi.com^
||tnc3-bjlgy.bytedance.com^
||tnc3-bjlgy.snssdk.com^
||tnc3-bjlgy.toutiaoapi.com^
||tnc3-bjlgy.zijieapi.com^
||toblog.ctobsnssdk.com^
||tpa-hcdn.iqiyi.com^
||tpc.googlesyndication-cn.com^
||tpc.googlesyndication.com^
||tr-asia.adsmoloco.com^
||trace.aqyad.com^
||trace.qq.com^
||tracelog-debug.aiclk.com^
||tracelog-debug.qquanquan.com^
||tracelog-debug.yunxish.com^
||track.lc.quark.cn^
||track.uc.cn^
||tracker.ai.xiaomi.com^
||tracker.gitee.com^
||tracking.miui.com^
||tracking.rus.miui.com^
||tuiguang.meitu.com^
||tvapp.hpplay.cn^
||tvuser-ch.cedock.com^
||tx-ad.a.yximgs.com^
||tx-kmpaudio.pull.yximgs.com^
||tx-p2p-pull.live-voip.com^
||tx-p2p-v2.pull.yximgs.com^
||tytx.m.cn.miaozhen.com^
||tz.sec.xiaomi.com^
||uapi.ads.heytapmobi.com^
||udc.yahoo.com^
||udcm.yahoo.com^
||uedas.qidian.com^
||uhabo.com^
||ulog-sdk.gifshow.com^
||ulogjs.gifshow.com^
||ulogs.umeng.com^
||ulogs.umengcloud.com^
||umengacs.m.taobao.com^
||umengjmacs.m.taobao.com^
||umini.shujupie.com^
||union-mlog.bigda.com^
||union.baidu.cn^
||union.baidu.com^
||update.avlyun.sec.miui.com^
||update.lejiao.tv^
||update0.aiclk.com^
||upgrade-update.hismarttv.com^
||uranus.jd.com^
||us.l.qq.com^
||usr-api.aiclk.com^
||utoken.umeng.com^
||v.110route.cn^
||v.adintl.cn^
||v.adx.hubcloud.com.cn^
||v1-ad.video.yximgs.com^
||v2-ad.video.yximgs.com^
||v2-api-channel-launcher.hismarttv.com^
||v2.gdt.qq.com^
||v2mi.gdt.qq.com^
||v3-ad.video.yximgs.com^
||v3.gdt.qq.com^
||vd6.l.qq.com^
||vi.l.qq.com^
||video-ad.sm.cn^
||video-dsp.pddpic.com^
||video.dispatch.tc.qq.com^
||video.market.xiaomi.com^
||vipauth.hpplay.cn^
||vipgslb.hpplay.cn^
||virusinfo-cloudscan-cn.heytapmobi.com^
||vlive.qqvideo.tc.qq.com^
||volc.bj.ad.track.66mobi.com^
||vungle.com^
||w.l.qq.com^
||w1.askwai.com^
||w1.gskwai.com^
||w1.kskwai.com^
||watson.microsoft.com^
||watson.telemetry.microsoft.com^
||wcp.taobao.com^
||weather-analytics-events.apple.com^
||weather-community-drcn.weather.dbankcloud.cn^
||webstat.qiumibao.com^
||webview.unityads.unity3d.com^
||widgets.outbrain.com^
||widgets.pinterest.com^
||wildcard.moatads.com.edgekey.net^
||win.gdt.qq.com^
||wn.x.jd.com^
||ws-keyboard.shouji.sogou.com^
||ws.sj.qq.com^
||wv.inner-active.mobi^
||wwads.cn^
||wxa.wxs.qq.com^
||wxaintpcos.wxqcloud.qq.com.cn^
||wximg.wxs.qq.com^
||wxsmw.wxs.qq.com^
||wxsnsad.tc.qq.com^
||wxsnsdy.tc.qq.com^
||wxsnsdy.wxs.qq.com^
||wxsnsdythumb.wxs.qq.com^
||xc.gdt.qq.com^
||xcx.dcad01.com^
||xiaomi-dtv.m.cn.miaozhen.com^
||xlog.jd.com^
||xlviiirdr.com^
||xlviirdr.com^
||xycdn.com^
||yk-ssp-ad.cp31.ott.cibntv.net^
||yk-ssp.ad.youku.com^
||ykad-data.cp31.ott.cibntv.net^
||ykad-data.youku.com^
||ykad-gateway.youku.com^
||youku-acs.m.taobao.com^
||ytx-file.110route.cn^
||zeus.ad.xiaomi.com^
||zhihu-web-analytics.zhihu.com^
||zjres-ad.kajicam.com^
||zxid-m.mobileservice.cn^
||1rtb.com^
||a.market.xiaomi.com^
||ad.gameley.com^
||adintl.cn^
||adx.adwangmai.com^
||data.hicloud.com^
||log.aliyuncs.com^
||ubixioe.com^
||vlion.cn^
||yfanads.com^
||zhangyuyidong.cn^
*-ad.sm.cn*
*-ad.video.yximgs.com*
*-ad.wtzw.com*
*-be-pack-sign.pglstatp-toutiao.com*
*-normal.zijieapi.com*


================================================
FILE: src/customize.sh
================================================
SKIPUNZIP=1

# most of the users are Chinese, so set default language to Chinese
language="zh"

# try to get the system language
locale=$(getprop persist.sys.locale || getprop ro.product.locale || getprop persist.sys.language)

# if the system language is English, set language to English
if echo "$locale" | grep -qi "en"; then
  language="en"
fi

function info() {
  [ "$language" = "en" ] && ui_print "$1" || ui_print "$2"
}

function error() {
  [ "$language" = "en" ] && abort "$1" || abort "$2"
}

info "- 🚀 Installing AdGuardHome for $ARCH" "- 🚀 开始安装 AdGuardHome for $ARCH"

AGH_DIR="/data/adb/agh"
BIN_DIR="$AGH_DIR/bin"
SCRIPT_DIR="$AGH_DIR/scripts"
PID_FILE="$AGH_DIR/bin/agh.pid"

info "- 📦 Extracting module basic files..." "- 📦 解压模块基本文件..."
unzip -o "$ZIPFILE" "action.sh" -d "$MODPATH" >/dev/null 2>&1 
unzip -o "$ZIPFILE" "module.prop" -d "$MODPATH" >/dev/null 2>&1
unzip -o "$ZIPFILE" "service.sh" -d "$MODPATH" >/dev/null 2>&1
unzip -o "$ZIPFILE" "uninstall.sh" -d "$MODPATH" >/dev/null 2>&1
unzip -o "$ZIPFILE" "webroot/*" -d "$MODPATH" >/dev/null 2>&1

extract_keep_config() {
  info "- 🌈 Keeping old configuration files..." "- 🌈 保留原来的配置文件..."
  info "- 📜 Extracting script files..." "- 📜 正在解压脚本文件..."
  unzip -o "$ZIPFILE" "scripts/*" -d $AGH_DIR >/dev/null 2>&1 || {
    error "- ❌ Failed to extract scripts!" "- ❌ 解压脚本文件失败!"
  }
  info "- 🛠️ Extracting binary files except configuration..." "- 🛠️ 正在解压二进制文件(不包括配置文件)..."
  unzip -o "$ZIPFILE" "bin/*" -x "bin/AdGuardHome.yaml" -d $AGH_DIR >/dev/null 2>&1 || {
    error "- ❌ Failed to extract binary files!" "- ❌ 解压二进制文件失败!"
  }
  info "- 🚫 Skipping configuration file extraction..." "- 🚫 跳过解压配置文件..."
}

extract_no_config() {
  info "- 💾 Backing up old configuration files with .bak extension..." "- 💾 使用 .bak 扩展名备份旧配置文件..."
  [ -f "$AGH_DIR/settings.conf" ] && mv "$AGH_DIR/settings.conf" "$AGH_DIR/settings.conf.bak"
  [ -f "$AGH_DIR/bin/AdGuardHome.yaml" ] && mv "$AGH_DIR/bin/AdGuardHome.yaml" "$AGH_DIR/bin/AdGuardHome.yaml.bak"
  extract_all
}

extract_all() {
  info "- 🌟 Extracting script files..." "- 🌟 正在解压脚本文件..."
  unzip -o "$ZIPFILE" "scripts/*" -d $AGH_DIR >/dev/null 2>&1 || {
    error "- ❌ Failed to extract scripts" "- ❌ 解压脚本文件失败"
  }
  info "- 🛠️ Extracting binary files..." "- 🛠️ 正在解压二进制文件..."
  unzip -o "$ZIPFILE" "bin/*" -d $AGH_DIR >/dev/null 2>&1 || {
    error "- ❌ Failed to extract binary files" "- ❌ 解压二进制文件失败"
  }
  info "- 📜 Extracting configuration files..." "- 📜 正在解压配置文件..."
  unzip -o "$ZIPFILE" "settings.conf" -d $AGH_DIR >/dev/null 2>&1 || {
    error "- ❌ Failed to extract configuration files" "- ❌ 解压配置文件失败"
  }
}

if [ -d "$AGH_DIR" ]; then
  info "- ⏹️ Found old version, stopping all AdGuardHome processes..." "- ⏹️ 发现旧版模块,正在停止所有 AdGuardHome 进程..."
  pkill -f "AdGuardHome" || pkill -9 -f "AdGuardHome" 
  info "- 🔄 Do you want to keep the old configuration? (If not, it will be automatically backed up)" "- 🔄 是否保留原来的配置文件?(若不保留则自动备份)"
  info "- 🔊 (Volume Up = Yes, Volume Down = No, 30s no input = Yes)" "- 🔊 (音量上键 = 是, 音量下键 = 否,30秒无操作 = 是)"
  START_TIME=$(date +%s)
  while true; do
    NOW_TIME=$(date +%s)
    timeout 1 getevent -lc 1 2>&1 | grep KEY_VOLUME >"$TMPDIR/events"
    if [ $((NOW_TIME - START_TIME)) -gt 29 ]; then
      info "- ⏰ No input detected after 30 seconds, defaulting to keep old configuration." "- ⏰ 30秒无输入,默认保留原配置。"
      extract_keep_config
      break
    elif $(cat $TMPDIR/events | grep -q KEY_VOLUMEUP); then
      extract_keep_config
      break
    elif $(cat $TMPDIR/events | grep -q KEY_VOLUMEDOWN); then
      extract_no_config
      break
    fi
  done
else
  info "- 📦 First time installation, extracting files..." "- 📦 第一次安装,正在解压文件..."
  mkdir -p "$AGH_DIR" "$BIN_DIR" "$SCRIPT_DIR"
  extract_all
fi

info "- 🔐 Setting permissions..." "- 🔐 设置权限..."

chmod +x "$BIN_DIR/AdGuardHome"
chown root:net_raw "$BIN_DIR/AdGuardHome"

chmod +x "$SCRIPT_DIR"/*.sh "$MODPATH"/*.sh

info "- 🎉 Installation completed, please reboot." "- 🎉 安装完成,请重启设备。"


================================================
FILE: src/module.prop
================================================
id=AdGuardHome
name=AdGuardHome for Root
version=20260419
versionCode=49
author=twoone3
description=none
updateJson=https://raw.githubusercontent.com/twoone-3/AdGuardHomeForRoot/main/version.json

================================================
FILE: src/scripts/base.sh
================================================
# add busybox to PATH
[ -d "/data/adb/magisk" ] && export PATH="/data/adb/magisk:$PATH"
[ -d "/data/adb/ksu/bin" ] && export PATH="/data/adb/ksu/bin:$PATH"
[ -d "/data/adb/ap/bin" ] && export PATH="/data/adb/ap/bin:$PATH"

# most of the users are Chinese, so set default language to Chinese
language="zh"

# try to get the system language
locale=$(getprop persist.sys.locale || getprop ro.product.locale || getprop persist.sys.language)

# if the system language is English, set language to English
if echo "$locale" | grep -qi "en"; then
  language="en"
fi

function log() {
  local timestamp=$(date "+%Y-%m-%d %H:%M:%S")
  local str
  [ "$language" = "en" ] && str="$timestamp $1" || str="$timestamp $2"
  echo "$str" | tee -a "$AGH_DIR/history.log"
}

function update_description() {
  local description
  [ "$language" = "en" ] && description="$1" || description="$2"
  sed -i "/^description=/c\description=$description" "$MOD_PATH/module.prop"
}

================================================
FILE: src/scripts/debug.sh
================================================
#!/system/bin/sh

AGH_DIR="/data/adb/agh"
LOG="$AGH_DIR/debug.log"

{
  echo "==== AdGuardHome Debug Log ===="
  date
  echo

  echo "== System Info =="
  uname -a
  echo "Android Version: $(getprop ro.build.version.release)"
  echo "Device: $(getprop ro.product.model)"
  echo "Architecture: $(uname -m)"
  echo

  echo "== AdGuardHome Version =="
  if [ -f "$AGH_DIR/bin/AdGuardHome" ]; then
    "$AGH_DIR/bin/AdGuardHome" --version
  else
    echo "AdGuardHome binary not found"
  fi
  echo

  echo "== Root Method =="
  if [ -d "/data/adb/magisk" ]; then
    echo "Magisk"
  elif [ -d "/data/adb/ksu" ]; then
    echo "KernelSU"
  elif [ -d "/data/adb/ap" ]; then
    echo "APatch"
  else
    echo "Unknown"
  fi
  echo

  echo "== BusyBox Version =="
  [ -d "/data/adb/magisk" ] && export PATH="/data/adb/magisk:$PATH"
  [ -d "/data/adb/ksu/bin" ] && export PATH="/data/adb/ksu/bin:$PATH"
  [ -d "/data/adb/ap/bin" ] && export PATH="/data/adb/ap/bin:$PATH"
  if command -v busybox >/dev/null 2>&1; then
    busybox --version
  else
    echo "BusyBox not found"
  fi

  echo "== AGH Directory Listing =="
  ls -lR "$AGH_DIR"
  echo

  echo "== AGH Bin Log (last 30 lines) =="
  tail -n 30 "$AGH_DIR/bin.log" 2>/dev/null
  echo

  echo "== AGH Settings =="
  cat "$AGH_DIR/settings.conf" 2>/dev/null
  echo

  echo "== AGH PID File =="
  cat "$AGH_DIR/bin/agh.pid" 2>/dev/null
  echo

  echo "== Running Processes (AdGuardHome) =="
  ps -A | grep AdGuardHome
  echo

  echo "== iptables -t nat -L -n -v =="
  iptables -t nat -L -n -v
  echo

  echo "== ip6tables -t filter -L -n -v =="
  ip6tables -t filter -L -n -v
  echo

  echo "== Network Interfaces =="
  ip addr
  echo

} >"$LOG" 2>&1

echo "Debug info collected in $LOG"


================================================
FILE: src/scripts/inotify.sh
================================================
. /data/adb/agh/settings.conf

readonly EVENTS=$1
readonly MONITOR_DIR=$2
readonly MONITOR_FILE=$3

if [ "${MONITOR_FILE}" = "disable" ]; then
  if [ "${EVENTS}" = "d" ]; then
    $SCRIPT_DIR/tool.sh start
  elif [ "${EVENTS}" = "n" ]; then
    $SCRIPT_DIR/tool.sh stop
  fi
fi


================================================
FILE: src/scripts/iptables.sh
================================================
. /data/adb/agh/settings.conf
. /data/adb/agh/scripts/base.sh

iptables_w="iptables -w 64"
ip6tables_w="ip6tables -w 64"

check_ipv6_nat_support() {
  if $ip6tables_w -t nat -L >/dev/null 2>&1; then
    return 0
  else
    return 1
  fi
}

enable_iptables() {
  if $iptables_w -t nat -L ADGUARD_REDIRECT_DNS >/dev/null 2>&1; then
    log "ADGUARD_REDIRECT_DNS chain already exists" "ADGUARD_REDIRECT_DNS 链已经存在"
    return 0
  fi

  log "Creating ADGUARD_REDIRECT_DNS chain and adding rules" "创建 ADGUARD_REDIRECT_DNS 链并添加规则"
  $iptables_w -t nat -N ADGUARD_REDIRECT_DNS || return 1
  $iptables_w -t nat -A ADGUARD_REDIRECT_DNS -m owner --uid-owner $adg_user --gid-owner $adg_group -j RETURN || return 1

  for subnet in $ignore_dest_list; do
    $iptables_w -t nat -A ADGUARD_REDIRECT_DNS -d $subnet -j RETURN || return 1
  done

  for subnet in $ignore_src_list; do
    $iptables_w -t nat -A ADGUARD_REDIRECT_DNS -s $subnet -j RETURN || return 1
  done

  $iptables_w -t nat -A ADGUARD_REDIRECT_DNS -p udp --dport 53 -j REDIRECT --to-ports $redir_port || return 1
  $iptables_w -t nat -A ADGUARD_REDIRECT_DNS -p tcp --dport 53 -j REDIRECT --to-ports $redir_port || return 1
  $iptables_w -t nat -I OUTPUT -j ADGUARD_REDIRECT_DNS || return 1

  log "Applied iptables rules successfully" "成功应用 iptables 规则"
}

disable_iptables() {
  if ! $iptables_w -t nat -L ADGUARD_REDIRECT_DNS >/dev/null 2>&1; then
    log "ADGUARD_REDIRECT_DNS chain does not exist" "ADGUARD_REDIRECT_DNS 链不存在"
    return 0
  fi

  log "Deleting ADGUARD_REDIRECT_DNS chain and rules" "删除 ADGUARD_REDIRECT_DNS 链及规则"
  $iptables_w -t nat -D OUTPUT -j ADGUARD_REDIRECT_DNS || return 1
  $iptables_w -t nat -F ADGUARD_REDIRECT_DNS || return 1
  $iptables_w -t nat -X ADGUARD_REDIRECT_DNS || return 1
}

add_block_ipv6_dns() {
  if $ip6tables_w -t filter -L ADGUARD_BLOCK_DNS >/dev/null 2>&1; then
    log "ADGUARD_BLOCK_DNS chain already exists" "ADGUARD_BLOCK_DNS 链已经存在"
    return 0
  fi

  log "Creating ADGUARD_BLOCK_DNS chain and adding rules" "创建 ADGUARD_BLOCK_DNS 链并添加规则"
  $ip6tables_w -t filter -N ADGUARD_BLOCK_DNS || return 1
  $ip6tables_w -t filter -A ADGUARD_BLOCK_DNS -p udp --dport 53 -j DROP || return 1
  $ip6tables_w -t filter -A ADGUARD_BLOCK_DNS -p tcp --dport 53 -j DROP || return 1
  $ip6tables_w -t filter -I OUTPUT -j ADGUARD_BLOCK_DNS || return 1

  log "Applied ipv6 iptables rules successfully" "成功应用 ipv6 iptables 规则"
}

del_block_ipv6_dns() {
  if ! $ip6tables_w -t filter -L ADGUARD_BLOCK_DNS >/dev/null 2>&1; then
    log "ADGUARD_BLOCK_DNS chain does not exist" "ADGUARD_BLOCK_DNS 链不存在"
    return 0
  fi

  log "Deleting ADGUARD_BLOCK_DNS chain and rules" "删除 ADGUARD_BLOCK_DNS 链及规则"
  $ip6tables_w -t filter -F ADGUARD_BLOCK_DNS || return 1
  $ip6tables_w -t filter -D OUTPUT -j ADGUARD_BLOCK_DNS || return 1
  $ip6tables_w -t filter -X ADGUARD_BLOCK_DNS || return 1
}

enable_ipv6_iptables() {
  if ! check_ipv6_nat_support; then
    log "IPv6 NAT is not supported, skipping IPv6 DNS hijack" "IPv6 NAT 不支持,跳过 IPv6 DNS 劫持"
    return 0
  fi

  if $ip6tables_w -t nat -L ADGUARD_REDIRECT_DNS6 >/dev/null 2>&1; then
    log "ADGUARD_REDIRECT_DNS6 chain already exists" "ADGUARD_REDIRECT_DNS6 链已经存在"
    return 0
  fi

  log "Creating ADGUARD_REDIRECT_DNS6 chain and adding rules" "创建 ADGUARD_REDIRECT_DNS6 链并添加规则"
  $ip6tables_w -t nat -N ADGUARD_REDIRECT_DNS6 || return 1
  $ip6tables_w -t nat -A ADGUARD_REDIRECT_DNS6 -m owner --uid-owner $adg_user --gid-owner $adg_group -j RETURN || return 1

  for subnet in $ignore_dest_list; do
    $ip6tables_w -t nat -A ADGUARD_REDIRECT_DNS6 -d $subnet -j RETURN || return 1
  done

  for subnet in $ignore_src_list; do
    $ip6tables_w -t nat -A ADGUARD_REDIRECT_DNS6 -s $subnet -j RETURN || return 1
  done

  $ip6tables_w -t nat -A ADGUARD_REDIRECT_DNS6 -p udp --dport 53 -j REDIRECT --to-ports $redir_port || return 1
  $ip6tables_w -t nat -A ADGUARD_REDIRECT_DNS6 -p tcp --dport 53 -j REDIRECT --to-ports $redir_port || return 1
  $ip6tables_w -t nat -I OUTPUT -j ADGUARD_REDIRECT_DNS6 || return 1

  log "Applied ipv6 iptables rules successfully" "成功应用 ipv6 iptables 规则"
}

disable_ipv6_iptables() {
  if ! check_ipv6_nat_support; then
    log "IPv6 NAT is not supported, skipping IPv6 DNS hijack cleanup" "IPv6 NAT 不支持,跳过 IPv6 DNS 劫持清理"
    return 0
  fi

  if ! $ip6tables_w -t nat -L ADGUARD_REDIRECT_DNS6 >/dev/null 2>&1; then
    log "ADGUARD_REDIRECT_DNS6 chain does not exist" "ADGUARD_REDIRECT_DNS6 链不存在"
    return 0
  fi

  log "Deleting ADGUARD_REDIRECT_DNS6 chain and rules" "删除 ADGUARD_REDIRECT_DNS6 链及规则"
  $ip6tables_w -t nat -D OUTPUT -j ADGUARD_REDIRECT_DNS6 || return 1
  $ip6tables_w -t nat -F ADGUARD_REDIRECT_DNS6 || return 1
  $ip6tables_w -t nat -X ADGUARD_REDIRECT_DNS6 || return 1
}

case "$1" in
enable)
  log "Enabling iptables and ipv6 DNS blocking if configured" "启用 iptables"
  enable_iptables || exit 1
  
  if [ "$block_ipv6_dns" = true ]; then
    log "IPv6 DNS mode: block (DROP IPv6 DNS traffic)" "IPv6 DNS 模式: block (丢弃 IPv6 DNS 流量)"
    add_block_ipv6_dns || exit 1
  else
    log "IPv6 DNS mode: hijack (NAT REDIRECT to AdGuard Home)" "IPv6 DNS 模式: hijack (劫持 IPv6 到 AdGuard Home)"
    enable_ipv6_iptables || exit 1
  fi
  ;;
disable)
  log "Disabling iptables and ipv6 DNS blocking" "禁用 iptables 和 ipv6 DNS 阻断"
  disable_iptables || exit 1
  
  del_block_ipv6_dns || exit 1
  disable_ipv6_iptables || exit 1
  ;;
*)
  echo "Usage: $0 {enable|disable}"
  exit 1
  ;;
esac


================================================
FILE: src/scripts/tool.sh
================================================
. /data/adb/agh/settings.conf
. /data/adb/agh/scripts/base.sh

start_adguardhome() {
  # check if AdGuardHome is already running
  if [ -f "$PID_FILE" ] && ps | grep -w "$adg_pid" | grep -q "AdGuardHome"; then
    log "AdGuardHome is already running" "AdGuardHome 已经在运行"
    exit 0
  fi

  # to fix https://github.com/AdguardTeam/AdGuardHome/issues/7002
  export SSL_CERT_DIR="/system/etc/security/cacerts/"
  # set timezone to Shanghai
  export TZ="Asia/Shanghai"

  # backup old log and overwrite new log
  if [ -f "$AGH_DIR/bin.log" ]; then
    mv "$AGH_DIR/bin.log" "$AGH_DIR/bin.log.bak"
  fi

  # run binary
  busybox setuidgid "$adg_user:$adg_group" "$BIN_DIR/AdGuardHome" >"$AGH_DIR/bin.log" 2>&1 &
  adg_pid=$!

  # check if AdGuardHome started successfully
  if ps | grep -w "$adg_pid" | grep -q "AdGuardHome"; then
    echo "$adg_pid" >"$PID_FILE"
    # check if iptables is enabled
    if [ "$enable_iptables" = true ]; then
      $SCRIPT_DIR/iptables.sh enable
      log "🥰 started PID: $adg_pid iptables: enabled" "🥰 启动成功 PID: $adg_pid iptables 已启用"
      update_description "🥰 Started PID: $adg_pid iptables: enabled" "🥰 启动成功 PID: $adg_pid iptables 已启用"
    else
      log "🥰 started PID: $adg_pid iptables: disabled" "🥰 启动成功 PID: $adg_pid iptables 已禁用"
      update_description "🥰 Started PID: $adg_pid iptables: disabled" "🥰 启动成功 PID: $adg_pid iptables 已禁用"
    fi
  else
    log "😭 Error occurred, check logs for details" "😭 出现错误,请检查日志以获取详细信息"
    update_description "😭 Error occurred, check logs for details" "😭 出现错误,请检查日志以获取详细信息"
    $SCRIPT_DIR/debug.sh
    exit 1
  fi
}

stop_adguardhome() {
  if [ -f "$PID_FILE" ]; then
    pid=$(cat "$PID_FILE")
    kill $pid || kill -9 $pid
    rm "$PID_FILE"
    log "AdGuardHome stopped (PID: $pid)" "AdGuardHome 已停止 (PID: $pid)"
  else
    pkill -f "AdGuardHome" || pkill -9 -f "AdGuardHome"
    log "AdGuardHome force stopped" "AdGuardHome 强制停止"
  fi
  update_description "❌ Stopped" "❌ 已停止"
  $SCRIPT_DIR/iptables.sh disable
}

toggle_adguardhome() {
  if [ -f "$PID_FILE" ] && ps | grep -w "$(cat $PID_FILE)" | grep -q "AdGuardHome"; then
    stop_adguardhome
  else
    start_adguardhome
  fi
}

case "$1" in
start)
  start_adguardhome
  ;;
stop)
  stop_adguardhome
  ;;
toggle)
  toggle_adguardhome
  ;;
*)
  echo "Usage: $0 {start|stop|toggle}"
  exit 1
  ;;
esac


================================================
FILE: src/service.sh
================================================
until [ $(getprop init.svc.bootanim) = "stopped" ]; do
  sleep 12
done

/data/adb/agh/scripts/tool.sh start

inotifyd /data/adb/agh/scripts/inotify.sh /data/adb/modules/AdGuardHome:d,n &


================================================
FILE: src/settings.conf
================================================
# 是否启用内置的 iptables 规则
# Whether to enable the built-in iptables rules
enable_iptables=true

# 阻断 ipv6 的 DNS 请求,仅在 enable_iptables=true 时生效
# Block DNS requests for ipv6, only effective when enable_iptables=true
block_ipv6_dns=true

# 重定向端口,请与 AdGuardHome 的设置保持一致
# Redirect port, please keep it consistent with AdGuardHome settings
redir_port=5591

# 用户组和用户,用于绕过 AdGuardHome 本身
# User group and user, used to bypass AdGuardHome itself
adg_user=root
adg_group=net_raw

# 绕过目的地址列表,用空格分隔
# list of destination addresses to bypass, separated by spaces
ignore_dest_list=""

# 绕过源地址列表,用空格分隔
# list of source addresses to bypass, separated by spaces
ignore_src_list=""

# 文件路径,请勿修改
# File paths, do not modify
readonly AGH_DIR="/data/adb/agh"
readonly BIN_DIR="$AGH_DIR/bin"
readonly SCRIPT_DIR="$AGH_DIR/scripts"
readonly PID_FILE="$AGH_DIR/bin/agh.pid"
readonly MOD_PATH="/data/adb/modules/AdGuardHome"


================================================
FILE: src/uninstall.sh
================================================
[ -d "/data/adb/agh" ] && rm -rf "/data/adb/agh"


================================================
FILE: src/webroot/app.js
================================================
/**
 * AdGuardHome for Root - WebUI Application
 * 
 * KernelSU API: ksu.exec(cmd) returns stdout string synchronously
 * KernelSU API: ksu.toast(msg) shows a toast
 */

// ==================== Module API Layer ====================
var ModuleAPI = {
  _api: null,

  init: function() {
    if (typeof ksu !== 'undefined' && typeof ksu.exec === 'function') {
      this._api = ksu;
    } else if (typeof ap !== 'undefined' && typeof ap.exec === 'function') {
      this._api = ap;
    }
  },

  isAvailable: function() {
    return this._api !== null;
  },

  /**
   * Execute shell command SYNCHRONOUSLY.
   * ksu.exec(cmd) returns stdout string directly.
   * Returns { errno, stdout }
   */
  exec: function(command) {
    if (!this._api) {
      return { errno: -1, stdout: '' };
    }
    try {
      var result = this._api.exec(command);
      return {
        errno: 0,
        stdout: (result || '').replace(/\r/g, '')
      };
    } catch (e) {
      return { errno: -1, stdout: '' };
    }
  },

  toast: function(msg) {
    if (this._api && typeof this._api.toast === 'function') {
      try { this._api.toast(msg); } catch (e) {}
    }
  }
};

ModuleAPI.init();

// ==================== Utility Functions ====================
function showToast(message, type, duration) {
  type = type || 'info';
  duration = duration || 3000;
  var container = document.getElementById('toastContainer');
  var toastEl = document.createElement('div');
  toastEl.className = 'toast ' + type;
  toastEl.textContent = message;
  container.appendChild(toastEl);
  setTimeout(function() {
    toastEl.style.animation = 'toastOut 0.3s ease forwards';
    setTimeout(function() { toastEl.remove(); }, 300);
  }, duration);
}

function showLoading(show) {
  document.getElementById('loadingOverlay').style.display = show ? 'flex' : 'none';
}

// ==================== Paths ====================
var AGH_DIR = '/data/adb/agh';
var CONF_FILE = AGH_DIR + '/settings.conf';
var PID_FILE = AGH_DIR + '/bin/agh.pid';
var LOG_FILE = AGH_DIR + '/history.log';
var SCRIPT_DIR = AGH_DIR + '/scripts';
var BIN_DIR = AGH_DIR + '/bin';
var MODULE_PROP = '/data/adb/modules/AdGuardHome/module.prop';

// ==================== State ====================
var currentSettings = {};
var settingsChanged = false;
var isRunning = false;
var webPort = '3000';
var webUser = 'root';
var webPassword = 'root';
var statsFailCount = 0;
var statsAuthVisible = false;

// ==================== Tab / Page Switching ====================
var currentPage = 0;
var pageCount = 2;

function switchTab(index) {
  if (index < 0 || index >= pageCount) return;
  currentPage = index;

  var container = document.getElementById('pagesContainer');
  container.classList.remove('no-transition');
  container.style.transform = 'translateX(' + (-index * 50) + '%)';

  // Update floating tab active state
  var tabs = document.querySelectorAll('.floating-tab-item');
  for (var i = 0; i < tabs.length; i++) {
    if (i === index) {
      tabs[i].classList.add('active');
    } else {
      tabs[i].classList.remove('active');
    }
  }

  // Always show floating tab when switching pages
  var floatingTab = document.getElementById('floatingTab');
  if (floatingTab) {
    floatingTab.classList.remove('tab-hidden');
  }
}

// ==================== Touch Swipe Support ====================
(function() {
  var startX = 0;
  var startY = 0;
  var deltaX = 0;
  var swiping = false;
  var container = null;

  function getContainer() {
    if (!container) {
      container = document.getElementById('pagesContainer');
    }
    return container;
  }

  function onTouchStart(e) {
    var tag = e.target.tagName.toLowerCase();
    if (tag === 'input' || tag === 'textarea' || tag === 'select' || tag === 'button') return;

    startX = e.touches[0].clientX;
    startY = e.touches[0].clientY;
    deltaX = 0;
    swiping = false;
  }

  function onTouchMove(e) {
    if (!startX && !startY) return;

    var dx = e.touches[0].clientX - startX;
    var dy = e.touches[0].clientY - startY;

    if (!swiping) {
      if (Math.abs(dx) > Math.abs(dy) && Math.abs(dx) > 10) {
        swiping = true;
        getContainer().classList.add('no-transition');
      } else {
        return;
      }
    }

    if (Math.abs(dx) > Math.abs(dy)) {
      e.preventDefault();
    }

    deltaX = dx;

    var pageWidth = window.innerWidth;
    var baseOffset = -currentPage * pageWidth;
    var offset = baseOffset + deltaX;

    var minOffset = -(pageCount - 1) * pageWidth;
    if (offset > 0) offset = offset * 0.3;
    if (offset < minOffset) offset = minOffset + (offset - minOffset) * 0.3;

    getContainer().style.transform = 'translateX(' + offset + 'px)';
  }

  function onTouchEnd(e) {
    if (!swiping) return;
    swiping = false;

    var threshold = window.innerWidth * 0.2;

    if (deltaX < -threshold && currentPage < pageCount - 1) {
      switchTab(currentPage + 1);
    } else if (deltaX > threshold && currentPage > 0) {
      switchTab(currentPage - 1);
    } else {
      switchTab(currentPage);
    }

    deltaX = 0;
  }

  document.addEventListener('DOMContentLoaded', function() {
    var c = getContainer();
    if (c) {
      c.addEventListener('touchstart', onTouchStart, { passive: true });
      c.addEventListener('touchmove', onTouchMove, { passive: false });
      c.addEventListener('touchend', onTouchEnd, { passive: true });
    }
  });
})();

// ==================== Status Check ====================
function checkStatus() {
  var badge = document.getElementById('statusBadge');
  var statusText = document.getElementById('statusText');
  var pidBadge = document.getElementById('pidBadge');

  var running = false;
  var pid = '-';

  var pidResult = ModuleAPI.exec('cat ' + PID_FILE + ' 2>/dev/null');
  pid = pidResult.stdout.trim();

  if (pid && /^\d+$/.test(pid)) {
    running = true;
  } else {
    pid = '-';
  }

  isRunning = running;
  badge.className = 'status-badge ' + (running ? 'running' : 'stopped');
  statusText.textContent = running ? 'Running' : 'Stopped';

  pidBadge.textContent = 'PID: ' + pid;

  document.getElementById('btnStart').disabled = running;
  document.getElementById('btnStop').disabled = !running;
  document.getElementById('btnRestart').disabled = !running;
}

// ==================== Load Settings ====================
function getConf(key) {
  var r = ModuleAPI.exec('grep "^' + key + '=" ' + CONF_FILE + ' 2>/dev/null | cut -d= -f2');
  return (r.stdout || '').replace(/\r/g, '').trim();
}

function getConfQuoted(key) {
  return getConf(key).replace(/^"|"$/g, '');
}

function loadSettings() {
  var settings = {};
  settings.enable_iptables = getConf('enable_iptables');
  settings.block_ipv6_dns = getConf('block_ipv6_dns');
  settings.redir_port = getConf('redir_port') || '5591';
  settings.adg_user = getConf('adg_user') || 'root';
  settings.adg_group = getConf('adg_group') || 'net_raw';
  settings.ignore_dest_list = getConfQuoted('ignore_dest_list');
  settings.ignore_src_list = getConfQuoted('ignore_src_list');

  currentSettings = settings;
  applySettingsToUI(settings);
}

function applySettingsToUI(settings) {
  document.getElementById('setEnableIptables').checked = (settings.enable_iptables === 'true');
  document.getElementById('setBlockIpv6').checked = (settings.block_ipv6_dns === 'true');
  document.getElementById('setRedirPort').value = settings.redir_port || '5591';
  document.getElementById('setAdgUser').value = settings.adg_user || 'root';
  document.getElementById('setAdgGroup').value = settings.adg_group || 'net_raw';
  document.getElementById('setIgnoreDest').value = settings.ignore_dest_list || '';
  document.getElementById('setIgnoreSrc').value = settings.ignore_src_list || '';

  settingsChanged = false;
  document.getElementById('btnSaveSettings').disabled = true;
}

function onSettingChange() {
  settingsChanged = true;
  document.getElementById('btnSaveSettings').disabled = false;
}

// ==================== Save Settings ====================
function saveSettings() {
  showLoading(true);

  var enableIptables = document.getElementById('setEnableIptables').checked;
  var blockIpv6 = document.getElementById('setBlockIpv6').checked;
  var redirPort = document.getElementById('setRedirPort').value || '5591';
  var adgUser = document.getElementById('setAdgUser').value || 'root';
  var adgGroup = document.getElementById('setAdgGroup').value || 'net_raw';
  var ignoreDest = document.getElementById('setIgnoreDest').value || '';
  var ignoreSrc = document.getElementById('setIgnoreSrc').value || '';

  var cmd =
    "sed -i 's#^enable_iptables=.*#enable_iptables=" + enableIptables + "#' " + CONF_FILE +
    " && sed -i 's#^block_ipv6_dns=.*#block_ipv6_dns=" + blockIpv6 + "#' " + CONF_FILE +
    " && sed -i 's#^redir_port=.*#redir_port=" + redirPort + "#' " + CONF_FILE +
    " && sed -i 's#^adg_user=.*#adg_user=" + adgUser + "#' " + CONF_FILE +
    " && sed -i 's#^adg_group=.*#adg_group=" + adgGroup + "#' " + CONF_FILE +
    " && sed -i 's#^ignore_dest_list=.*#ignore_dest_list=\"" + ignoreDest + "\"#' " + CONF_FILE +
    " && sed -i 's#^ignore_src_list=.*#ignore_src_list=\"" + ignoreSrc + "\"#' " + CONF_FILE;

  var result = ModuleAPI.exec(cmd);

  if (result.errno === 0) {
    settingsChanged = false;
    document.getElementById('btnSaveSettings').disabled = true;
    showToast('Settings saved successfully', 'success');
  } else {
    showToast('Failed to save settings', 'error');
  }

  showLoading(false);
}

// ==================== Log Viewer ====================
function loadLog() {
  if (!ModuleAPI.isAvailable()) return;
  var result = ModuleAPI.exec("awk '{a[NR]=$0}END{for(i=(NR>5?NR-4:1);i<=NR;i++)printf \"%s::NL::\",a[i]}' " + LOG_FILE + " 2>/dev/null");
  var logViewer = document.getElementById('logViewer');
  if (logViewer) {
    var raw = (result.stdout || '').trim();
    var lines = raw.split('::NL::').filter(function(l) { return l !== ''; });
    var content = lines.join('\n');
    logViewer.textContent = content || 'No log entries';
    logViewer.scrollTop = logViewer.scrollHeight;
  }
}

// ==================== Stats Help Modal ====================
function toggleStatsHelp() {
  var overlay = document.getElementById('statsHelpOverlay');
  if (overlay) {
    overlay.classList.add('visible');
  }
}

function closeStatsHelp() {
  var overlay = document.getElementById('statsHelpOverlay');
  if (overlay) {
    overlay.classList.remove('visible');
  }
}

// ==================== Stats View Switching ====================
function showStatsDataView() {
  statsAuthVisible = false;
  var dataView = document.getElementById('statsDataView');
  var authView = document.getElementById('statsAuthView');
  if (dataView) dataView.style.display = '';
  if (authView) authView.style.display = 'none';
}

function showStatsAuthView() {
  statsAuthVisible = true;
  var dataView = document.getElementById('statsDataView');
  var authView = document.getElementById('statsAuthView');
  if (dataView) dataView.style.display = 'none';
  if (authView) authView.style.display = '';
}

function submitCredentials() {
  var userEl = document.getElementById('authUser');
  var passEl = document.getElementById('authPassword');
  if (userEl) webUser = userEl.value || 'root';
  if (passEl) webPassword = passEl.value || 'root';
  statsFailCount = 0;
  showStatsDataView();
  loadStats();
}

// ==================== Query Log Statistics ====================
function loadStats() {
  if (!ModuleAPI.isAvailable()) return;
  if (!isRunning) return;
  if (webPort === '-') return;
  if (statsAuthVisible) return;

  var url = 'http://127.0.0.1:' + webPort + '/control/stats';
  var auth = webUser + ':' + webPassword;
  var cmd = 'wget -qO- --user=' + webUser + ' --password=' + webPassword + ' ' + url + ' 2>/dev/null || curl -s -u ' + auth + ' ' + url + ' 2>/dev/null';

  var result = ModuleAPI.exec(cmd);
  var raw = (result.stdout || '').trim();

  var queriesEl = document.getElementById('statQueries');
  var blockedEl = document.getElementById('statBlocked');
  var timeEl = document.getElementById('statTime');

  if (!raw || raw.charAt(0) !== '{') {
    statsFailCount++;
    if (queriesEl) queriesEl.textContent = '-';
    if (blockedEl) blockedEl.textContent = '-';
    if (timeEl) timeEl.textContent = '-';
    if (statsFailCount >= 2) {
      showStatsAuthView();
    }
    return;
  }

  try {
    var stats = JSON.parse(raw);

    if (queriesEl) queriesEl.textContent = (typeof stats.num_dns_queries === 'number') ? stats.num_dns_queries : '-';
    if (blockedEl) blockedEl.textContent = (typeof stats.num_blocked_filtering === 'number') ? stats.num_blocked_filtering : '-';
    if (timeEl) timeEl.textContent = (typeof stats.avg_processing_time === 'number') ? stats.avg_processing_time.toFixed(2) + 's' : '-';
    statsFailCount = 0;
  } catch (e) {
    statsFailCount++;
    if (queriesEl) queriesEl.textContent = '-';
    if (blockedEl) blockedEl.textContent = '-';
    if (timeEl) timeEl.textContent = '-';
    if (statsFailCount >= 2) {
      showStatsAuthView();
    }
  }
}

// ==================== Control ====================
function controlAdGuard(action) {
  showLoading(true);

  var cmd;
  switch (action) {
    case 'start':
      cmd = SCRIPT_DIR + '/tool.sh start';
      break;
    case 'stop':
      cmd = SCRIPT_DIR + '/tool.sh stop';
      break;
    case 'restart':
      cmd = SCRIPT_DIR + '/tool.sh stop; sleep 1; ' + SCRIPT_DIR + '/tool.sh start';
      break;
    default:
      showLoading(false);
      return;
  }

  var result = ModuleAPI.exec(cmd);

  if (result.errno === 0) {
    var label = action.charAt(0).toUpperCase() + action.slice(1);
    showToast(label + ' successful', 'success');
  } else {
    showToast(action + ' failed', 'error');
  }

  setTimeout(function() {
    checkStatus();
    loadLog();
    showLoading(false);
  }, 2000);
}

// ==================== Open AdGuardHome ====================
function openAdGuardHome() {
  var url = (webPort !== '-') ? ('http://127.0.0.1:' + webPort) : 'http://127.0.0.1:3000';
  window.open(url, '_blank');
}

// ==================== Debug Info ====================
function collectDebugInfo() {
  showLoading(true);
  ModuleAPI.exec(SCRIPT_DIR + '/debug.sh 2>/dev/null');
  showToast('Debug info collected to ' + AGH_DIR + '/debug.log', 'success');
  showLoading(false);
}

// ==================== Load Web Port ====================
function loadWebPort() {
  var result = ModuleAPI.exec("grep 'address:' " + BIN_DIR + "/AdGuardHome.yaml 2>/dev/null | head -1");
  var line = result.stdout.trim();
  var match = line.match(/address:\s*\S+:(\d+)/);
  if (match && match[1]) {
    webPort = match[1];
  } else {
    webPort = '3000';
  }
}

// ==================== Load Version ====================
function loadVersion() {
  var result = ModuleAPI.exec('grep "^version=" ' + MODULE_PROP + ' 2>/dev/null | cut -d= -f2');
  var version = result.stdout.trim();
  if (version) {
    document.getElementById('versionText').textContent = 'v' + version;
  }
}

// ==================== Scroll Hide Floating Tab ====================
function setupScrollHideTab() {
  var pages = document.querySelectorAll('.page');
  var floatingTab = document.getElementById('floatingTab');
  if (!floatingTab) return;

  for (var i = 0; i < pages.length; i++) {
    (function(page) {
      page.addEventListener('scroll', function() {
        var atTop = page.scrollTop <= 2;
        var atBottom = page.scrollTop + page.clientHeight >= page.scrollHeight - 2;

        if (atTop || atBottom) {
          floatingTab.classList.remove('tab-hidden');
        } else {
          floatingTab.classList.add('tab-hidden');
        }
      });
    })(pages[i]);
  }
}

// ==================== Initialize ====================
function init() {
  if (!ModuleAPI.isAvailable()) {
    document.getElementById('noApiWarning').style.display = 'block';
    document.getElementById('statusBadge').className = 'status-badge stopped';
    document.getElementById('statusText').textContent = 'No API';
    return;
  }

  loadSettings();
  checkStatus();
  loadWebPort();
  loadVersion();
  loadLog();
  loadStats();
  setupScrollHideTab();

  switchTab(0);
}

// Auto-refresh every 5 seconds
setInterval(function() {
  if (ModuleAPI.isAvailable()) {
    checkStatus();
    loadLog();
    loadStats();
  }
}, 5000);

// Start
document.addEventListener('DOMContentLoaded', init);


================================================
FILE: src/webroot/index.html
================================================
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>AdGuardHome for Root</title>
<link rel="stylesheet" href="style.css">
</head>
<body>

<div class="app-wrapper">
  <!-- Pages Container -->
  <div class="pages-container" id="pagesContainer">

    <!-- Page 1: Main -->
    <div class="page page-main" id="pageMain">
      <div class="container">
        <!-- Header -->
        <div class="header">
          <div class="header-icon">
            <svg viewBox="0 0 24 24"><path d="M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4zm0 10.99h7c-.53 4.12-3.28 7.79-7 8.94V12H5V6.3l7-3.11v8.8z"/></svg>
          </div>
          <h1>AdGuardHome for Root</h1>
          <div class="version" id="versionText">v20260315</div>
          <div class="status-row">
            <span class="status-badge loading" id="statusBadge">
              <span class="status-dot"></span>
              <span id="statusText">Loading...</span>
            </span>
            <span class="pid-badge" id="pidBadge">PID: -</span>
          </div>
        </div>

        <!-- No API Warning -->
        <div class="no-api-warning" id="noApiWarning" style="display:none;">
          <h3>⚠️ Module API Not Available</h3>
          <p>This WebUI requires KernelSU or APatch module manager. Please open this page from the module manager app.</p>
        </div>

        <!-- Statistics -->
        <div class="card card-stats">
          <div class="card-title">
            <svg class="card-title-icon" viewBox="0 0 24 24"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM9 17H7v-7h2v7zm4 0h-2V7h2v10zm4 0h-2v-4h2v4z"/></svg>
            Query Log Statistics
            <button class="btn-help" onclick="toggleStatsHelp()">?</button>
          </div>
          <!-- Stats Data View -->
          <div class="stats-grid" id="statsDataView">
            <div class="stat-item">
              <div class="stat-value" id="statQueries">-</div>
              <div class="stat-label">DNS查询</div>
            </div>
            <div class="stat-item">
              <div class="stat-value" id="statBlocked">-</div>
              <div class="stat-label">规则拦截</div>
            </div>
            <div class="stat-item">
              <div class="stat-value" id="statTime">-</div>
              <div class="stat-label">处理耗时</div>
            </div>
          </div>
          <!-- Credential Input View (hidden by default) -->
          <div class="stats-auth-form" id="statsAuthView" style="display:none;">
            <div class="stats-auth-hint">认证失败,请输入 AdGuardHome Web UI 账号密码</div>
            <div class="input-group">
              <label class="input-label">Username</label>
              <input type="text" class="input-field" id="authUser" placeholder="root" value="root">
            </div>
            <div class="input-group">
              <label class="input-label">Password</label>
              <input type="password" class="input-field" id="authPassword" placeholder="root" value="root">
            </div>
            <button class="btn btn-primary btn-full" onclick="submitCredentials()" style="margin-top:8px;">确认</button>
          </div>
        </div>

        <!-- Open Web Panel -->
        <div class="card card-action">
          <button class="btn btn-link btn-full" onclick="openAdGuardHome()">
            AdGuardHome Web Panel
          </button>
        </div>

        <!-- Control -->
        <div class="card">
          <div class="control-buttons">
            <button class="btn btn-primary" id="btnStart" onclick="controlAdGuard('start')" disabled>Start</button>
            <button class="btn btn-danger" id="btnStop" onclick="controlAdGuard('stop')" disabled>Stop</button>
            <button class="btn btn-secondary" id="btnRestart" onclick="controlAdGuard('restart')" disabled>Restart</button>
            <button class="btn btn-secondary" onclick="collectDebugInfo()">Debug Info</button>
          </div>
          <div class="log-viewer" id="logViewer"></div>
        </div>

        <!-- Footer -->
        <div class="footer">
          <p>AdGuardHome for Root by <a href="https://github.com/twoone-3">twoone3</a></p>
          <p style="margin-top:4px;">MIT License · <a href="https://github.com/twoone-3/AdGuardHomeForRoot">GitHub</a></p>
        </div>
      </div>
    </div>

    <!-- Page 2: Settings -->
    <div class="page page-settings" id="pageSettings">
      <div class="container">
        <!-- Settings Header -->
        <div class="settings-header">
          <div class="card-title">Settings</div>
        </div>

        <!-- Settings Content -->
        <div class="card">
          <div class="setting-group">
            <div class="setting-row">
              <div class="setting-label">
                <div class="title">Enable Iptables</div>
                <div class="desc">Redirect DNS requests via iptables</div>
              </div>
              <label class="toggle">
                <input type="checkbox" id="setEnableIptables" onchange="onSettingChange()">
                <span class="toggle-slider"></span>
              </label>
            </div>
            <div class="setting-row">
              <div class="setting-label">
                <div class="title">Block IPv6 DNS</div>
                <div class="desc">Block DNS requests over IPv6</div>
              </div>
              <label class="toggle">
                <input type="checkbox" id="setBlockIpv6" onchange="onSettingChange()">
                <span class="toggle-slider"></span>
              </label>
            </div>
          </div>

          <div class="setting-group">
            <div class="input-group">
              <label class="input-label">Redirect Port (must match AdGuardHome DNS port)</label>
              <input type="number" class="input-field" id="setRedirPort" placeholder="5591" onchange="onSettingChange()">
            </div>
            <div class="input-group">
              <label class="input-label">Run User</label>
              <input type="text" class="input-field" id="setAdgUser" placeholder="root" onchange="onSettingChange()">
            </div>
            <div class="input-group">
              <label class="input-label">Run User Group</label>
              <input type="text" class="input-field" id="setAdgGroup" placeholder="net_raw" onchange="onSettingChange()">
            </div>
            <div class="input-group">
              <label class="input-label">Bypass Destinations (space-separated)</label>
              <input type="text" class="input-field" id="setIgnoreDest" placeholder="" onchange="onSettingChange()">
            </div>
            <div class="input-group">
              <label class="input-label">Bypass Sources (space-separated)</label>
              <input type="text" class="input-field" id="setIgnoreSrc" placeholder="" onchange="onSettingChange()">
            </div>
          </div>

          <div class="setting-group" style="margin-bottom:0;">
            <button class="btn btn-primary btn-full" id="btnSaveSettings" onclick="saveSettings()" disabled>
              Save Settings
            </button>
          </div>
        </div>
      </div>
    </div>

  </div>

  <!-- Floating Tab Bar -->
  <div class="floating-tab" id="floatingTab">
    <div class="floating-tab-item active" data-page="0" onclick="switchTab(0)">
      <svg class="tab-icon" viewBox="0 0 24 24"><path d="M12 2L2 12h3v8h6v-6h2v6h6v-8h3L12 2zm0 2.84L19.16 12H18v8h-4v-6H10v6H6v-8H4.84L12 4.84z"/></svg>
      <span class="tab-label">Main</span>
    </div>
    <div class="floating-tab-item" data-page="1" onclick="switchTab(1)">
      <svg class="tab-icon" viewBox="0 0 24 24"><path d="M19.14,12.94c0.04-0.3,0.06-0.61,0.06-0.94c0-0.32-0.02-0.64-0.07-0.94l2.03-1.58c0.18-0.14,0.23-0.41,0.12-0.61 l-1.92-3.32c-0.12-0.22-0.37-0.29-0.59-0.22l-2.39,0.96c-0.5-0.38-1.03-0.7-1.62-0.94L14.4,2.81c-0.04-0.24-0.24-0.41-0.48-0.41 h-3.84c-0.24,0-0.43,0.17-0.47,0.41L9.25,5.35C8.66,5.59,8.12,5.92,7.63,6.29L5.24,5.33c-0.22-0.08-0.47,0-0.59,0.22L2.74,8.87 C2.62,9.08,2.66,9.34,2.86,9.48l2.03,1.58C4.84,11.36,4.8,11.69,4.8,12s0.02,0.64,0.07,0.94l-2.03,1.58 c-0.18,0.14-0.23,0.41-0.12,0.61l1.92,3.32c0.12,0.22,0.37,0.29,0.59,0.22l2.39-0.96c0.5,0.38,1.03,0.7,1.62,0.94l0.36,2.54 c0.05,0.24,0.24,0.41,0.48,0.41h3.84c0.24,0,0.44-0.17,0.47-0.41l0.36-2.54c0.59-0.24,1.13-0.56,1.62-0.94l2.39,0.96 c0.22,0.08,0.47,0,0.59-0.22l1.92-3.32c0.12-0.22,0.07-0.47-0.12-0.61L19.14,12.94z M12,15.6c-1.98,0-3.6-1.62-3.6-3.6 s1.62-3.6,3.6-3.6s3.6,1.62,3.6,3.6S13.98,15.6,12,15.6z"/></svg>
      <span class="tab-label">Settings</span>
    </div>
  </div>
</div>

<!-- Stats Help Modal -->
<div class="help-modal-overlay" id="statsHelpOverlay" onclick="closeStatsHelp()">
  <div class="help-modal" onclick="event.stopPropagation()">
    <div class="help-modal-title">指标说明</div>
    <div class="help-modal-item">
      <strong>DNS查询</strong>
      <p>AdGuard Home 核心处理的 DNS 查询总数。</p>
    </div>
    <div class="help-modal-item">
      <strong>规则拦截</strong>
      <p>被过滤规则拦截的 DNS 请求数量。</p>
    </div>
    <div class="help-modal-item">
      <strong>处理耗时</strong>
      <p>处理每个 DNS 请求的平均耗时(秒)。</p>
    </div>
    <div class="help-modal-note">数据通过 AdGuard Home OpenAPI 实时获取,统计周期由核心配置决定。</div>
    <button class="btn btn-secondary btn-full" onclick="closeStatsHelp()" style="margin-top:12px;">关闭</button>
  </div>
</div>

<!-- Toast Container -->
<div class="toast-container" id="toastContainer"></div>

<!-- Loading Overlay -->
<div class="loading-overlay" id="loadingOverlay" style="display:none;">
  <div class="spinner"></div>
</div>

<script src="app.js"></script>
</body>
</html>


================================================
FILE: src/webroot/style.css
================================================
/* AdGuardHome for Root - WebUI Styles */

/* ===== Dark Theme (default) ===== */
:root {
  --primary: #68BC71;
  --primary-dark: #4CAF50;
  --primary-light: #A5D6A7;
  --bg: #0c0e14;
  --surface: #161923;
  --surface-2: #1e2231;
  --surface-3: #2a2f42;
  --text: #E8EAED;
  --text-secondary: #9AA0A6;
  --text-hint: #6B7280;
  --danger: #EF5350;
  --danger-dark: #C62828;
  --warning: #FFC107;
  --info: #42A5F5;
  --success: #66BB6A;
  --border: #252a3a;
  --radius: 14px;
  --radius-sm: 10px;
  --shadow: 0 2px 12px rgba(0,0,0,0.35);
  --transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
  --log-bg: #0a0c12;
  --toggle-off: #3a3f50;
}

/* ===== Light Theme ===== */
@media (prefers-color-scheme: light) {
  :root {
    --bg: #f0f2f5;
    --surface: #ffffff;
    --surface-2: #f5f6f8;
    --surface-3: #e8eaed;
    --text: #1f1f1f;
    --text-secondary: #5f6368;
    --text-hint: #80868b;
    --border: #dfe1e5;
    --shadow: 0 2px 12px rgba(0,0,0,0.08);
    --log-bg: #f8f9fa;
    --toggle-off: #bdbdbd;
  }
}

* { margin: 0; padding: 0; box-sizing: border-box; }

body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Noto Sans SC', 'PingFang SC', 'Microsoft YaHei', sans-serif;
  background: var(--bg);
  color: var(--text);
  line-height: 1.6;
  height: 100vh;
  overflow: hidden;
  -webkit-font-smoothing: antialiased;
}

/* ===== App Wrapper ===== */
.app-wrapper {
  height: 100vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  position: relative;
}

/* ===== Pages Container (horizontal sliding) ===== */
.pages-container {
  flex: 1;
  display: flex;
  width: 200%;
  overflow: hidden;
  transition: transform 0.35s cubic-bezier(0.4, 0, 0.2, 1);
  will-change: transform;
}

.pages-container.no-transition {
  transition: none;
}

.page {
  width: 50%;
  height: 100%;
  overflow-y: auto;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  touch-action: pan-y;
  padding-bottom: 88px; /* space for floating tab */
}

.page::-webkit-scrollbar { width: 4px; }
.page::-webkit-scrollbar-track { background: transparent; }
.page::-webkit-scrollbar-thumb { background: var(--surface-3); border-radius: 4px; }

.container {
  max-width: 600px;
  margin: 0 auto;
  padding: 16px;
}

/* ===== Header ===== */
.header {
  text-align: center;
  padding: 16px 0 12px;
}

.header-icon {
  width: 48px;
  height: 48px;
  background: linear-gradient(145deg, var(--primary), var(--primary-dark));
  border-radius: 14px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 8px;
  box-shadow: 0 4px 16px rgba(104, 188, 113, 0.25);
}

.header-icon svg {
  width: 28px;
  height: 28px;
  fill: white;
}

.header h1 {
  font-size: 20px;
  font-weight: 700;
  color: var(--text);
  margin-bottom: 2px;
  letter-spacing: -0.3px;
}

.header .version {
  font-size: 12px;
  color: var(--text-hint);
}

/* ===== Status Row (badge + PID inline) ===== */
.status-row {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  margin-top: 6px;
}

/* ===== Status Badge ===== */
.status-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 14px;
  border-radius: 20px;
  font-size: 13px;
  font-weight: 600;
}

.status-badge.running {
  background: rgba(102, 187, 106, 0.15);
  color: var(--success);
}

.status-badge.stopped {
  background: rgba(239, 83, 80, 0.15);
  color: var(--danger);
}

.status-badge.loading {
  background: rgba(66, 165, 245, 0.15);
  color: var(--info);
}

.status-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  display: inline-block;
}

.status-badge.running .status-dot {
  background: var(--success);
  box-shadow: 0 0 8px var(--success);
  animation: pulse 2s infinite;
}

.status-badge.stopped .status-dot {
  background: var(--danger);
}

.status-badge.loading .status-dot {
  background: var(--info);
  animation: pulse 1s infinite;
}

@keyframes pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.4; }
}

/* ===== PID Badge ===== */
.pid-badge {
  display: inline-flex;
  align-items: center;
  padding: 4px 12px;
  border-radius: 20px;
  font-size: 12px;
  font-weight: 600;
  background: var(--surface-2);
  color: var(--text-secondary);
  border: 1px solid var(--border);
}

/* ===== Card ===== */
.card {
  background: var(--surface);
  border-radius: var(--radius);
  padding: 20px;
  margin-bottom: 14px;
  border: 1px solid var(--border);
  box-shadow: var(--shadow);
  transition: box-shadow 0.25s ease;
}

.card.card-action {
  padding: 10px 20px;
  border-color: rgba(104, 188, 113, 0.15);
}

.card-title {
  font-size: 15px;
  font-weight: 600;
  color: var(--text);
  margin-bottom: 16px;
  display: flex;
  align-items: center;
  gap: 8px;
}

.card-title .icon {
  font-size: 18px;
}

.card-title-icon {
  width: 18px;
  height: 18px;
  fill: var(--primary);
  flex-shrink: 0;
}

/* ===== Statistics Card ===== */
.card-stats {
  padding: 16px 20px;
}

.stats-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
}

.stat-item {
  text-align: center;
  padding: 12px 4px;
  background: var(--surface-2);
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
}

.stat-value {
  font-size: 24px;
  font-weight: 700;
  color: var(--primary);
  line-height: 1.2;
  margin-bottom: 4px;
}

.stat-label {
  font-size: 11px;
  color: var(--text-hint);
  font-weight: 500;
}

/* ===== Stats Auth Form ===== */
.stats-auth-form {
  padding: 4px 0;
}

.stats-auth-hint {
  font-size: 12px;
  color: var(--warning);
  margin-bottom: 10px;
  text-align: center;
  line-height: 1.5;
}

/* ===== Help Button ===== */
.btn-help {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  border: 1px solid var(--text-hint);
  background: transparent;
  color: var(--text-hint);
  font-size: 10px;
  font-weight: 700;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-left: auto;
  flex-shrink: 0;
  padding: 0;
  line-height: 1;
  transition: var(--transition);
  font-family: inherit;
}

.btn-help:hover {
  border-color: var(--primary);
  color: var(--primary);
  background: rgba(104, 188, 113, 0.08);
}

/* ===== Help Modal ===== */
.help-modal-overlay {
  position: fixed;
  top: 0; left: 0; right: 0; bottom: 0;
  background: rgba(0, 0, 0, 0.5);
  display: none;
  align-items: center;
  justify-content: center;
  z-index: 500;
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  padding: 24px;
}

.help-modal-overlay.visible {
  display: flex;
}

.help-modal {
  background: var(--surface);
  border-radius: var(--radius);
  padding: 20px;
  max-width: 360px;
  width: 100%;
  border: 1px solid var(--border);
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
  animation: modalIn 0.2s ease;
}

@keyframes modalIn {
  from { opacity: 0; transform: scale(0.95); }
  to { opacity: 1; transform: scale(1); }
}

.help-modal-title {
  font-size: 16px;
  font-weight: 700;
  color: var(--text);
  margin-bottom: 14px;
  text-align: center;
}

.help-modal-item {
  padding: 8px 0;
}

.help-modal-item + .help-modal-item {
  border-top: 1px solid var(--border);
}

.help-modal-item strong {
  font-size: 13px;
  color: var(--primary);
  display: block;
  margin-bottom: 2px;
}

.help-modal-item p {
  font-size: 12px;
  color: var(--text-secondary);
  line-height: 1.5;
  margin: 0;
}

.help-modal-note {
  font-size: 11px;
  color: var(--text-hint);
  text-align: center;
  margin-top: 12px;
  padding-top: 10px;
  border-top: 1px solid var(--border);
  line-height: 1.5;
}

/* ===== Buttons ===== */
.control-buttons {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 11px 16px;
  border: none;
  border-radius: var(--radius-sm);
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  transition: var(--transition);
  outline: none;
  position: relative;
  overflow: hidden;
  font-family: inherit;
  letter-spacing: 0.2px;
}

.btn:active { transform: scale(0.97); }
.btn:disabled { opacity: 0.45; cursor: not-allowed; transform: none; }

.btn-primary {
  background: linear-gradient(145deg, var(--primary), var(--primary-dark));
  color: white;
}
.btn-primary:hover:not(:disabled) { box-shadow: 0 4px 16px rgba(104, 188, 113, 0.35); }

.btn-danger {
  background: linear-gradient(145deg, var(--danger), var(--danger-dark));
  color: white;
}
.btn-danger:hover:not(:disabled) { box-shadow: 0 4px 16px rgba(239, 83, 80, 0.35); }

.btn-secondary { background: var(--surface-2); color: var(--text); border: 1px solid var(--border); }
.btn-secondary:hover:not(:disabled) { background: var(--surface-3); border-color: var(--text-hint); }

.btn-full { width: 100%; padding: 13px; }

.btn-link {
  background: linear-gradient(135deg, rgba(104, 188, 113, 0.12), rgba(104, 188, 113, 0.04));
  color: var(--primary);
  border: 1px solid rgba(104, 188, 113, 0.2);
  font-weight: 600;
}
.btn-link:hover:not(:disabled) {
  background: linear-gradient(135deg, rgba(104, 188, 113, 0.22), rgba(104, 188, 113, 0.08));
  border-color: rgba(104, 188, 113, 0.4);
}

/* ===== Settings ===== */
.settings-header {
  padding: 24px 0 8px;
}

.settings-header .card-title {
  margin-bottom: 0;
}

.setting-group { margin-bottom: 12px; }
.setting-group:last-child { margin-bottom: 0; }

.setting-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 0;
  border-bottom: 1px solid var(--border);
}

.setting-row:last-child { border-bottom: none; padding-bottom: 0; }
.setting-row:first-child { padding-top: 0; }

.setting-label { flex: 1; }
.setting-label .title { font-size: 14px; font-weight: 500; color: var(--text); }
.setting-label .desc { font-size: 12px; color: var(--text-hint); margin-top: 2px; }

/* ===== Toggle Switch ===== */
.toggle {
  position: relative;
  width: 48px;
  height: 26px;
  flex-shrink: 0;
  margin-left: 12px;
}

.toggle input { opacity: 0; width: 0; height: 0; }

.toggle-slider {
  position: absolute;
  cursor: pointer;
  top: 0; left: 0; right: 0; bottom: 0;
  background: var(--toggle-off);
  border-radius: 26px;
  transition: var(--transition);
}

.toggle-slider:before {
  content: '';
  position: absolute;
  height: 20px;
  width: 20px;
  left: 3px;
  bottom: 3px;
  background: white;
  border-radius: 50%;
  transition: var(--transition);
}

.toggle input:checked + .toggle-slider { background: var(--primary); }
.toggle input:checked + .toggle-slider:before { transform: translateX(22px); }
.toggle input:disabled + .toggle-slider { opacity: 0.5; cursor: not-allowed; }

/* ===== Input ===== */
.input-field {
  width: 100%;
  padding: 10px 14px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--text);
  font-size: 14px;
  font-family: inherit;
  transition: var(--transition);
  outline: none;
}

.input-field:focus {
  border-color: var(--primary);
  box-shadow: 0 0 0 3px rgba(104, 188, 113, 0.15);
}

.input-field:disabled { opacity: 0.5; }

.input-group { margin-top: 8px; }

.input-label {
  font-size: 12px;
  color: var(--text-hint);
  margin-bottom: 6px;
  display: block;
}

/* ===== Log Viewer ===== */
.log-viewer {
  background: var(--log-bg);
  border-radius: var(--radius-sm);
  padding: 10px 12px;
  font-family: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', 'Consolas', monospace;
  font-size: 10px;
  line-height: 1.7;
  max-height: 160px;
  min-height: 80px;
  overflow-y: auto;
  color: var(--text-hint);
  white-space: pre-wrap;
  word-break: break-all;
  border: 1px solid var(--border);
  border-top: 2px solid var(--surface-3);
  margin-top: 12px;
}

.log-viewer::-webkit-scrollbar { width: 3px; }
.log-viewer::-webkit-scrollbar-track { background: transparent; }
.log-viewer::-webkit-scrollbar-thumb { background: var(--surface-3); border-radius: 3px; }

/* ===== Toast ===== */
.toast-container {
  position: fixed;
  bottom: 80px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 1000;
  display: flex;
  flex-direction: column;
  gap: 8px;
  pointer-events: none;
}

.toast {
  padding: 12px 20px;
  border-radius: var(--radius-sm);
  font-size: 14px;
  font-weight: 500;
  color: white;
  box-shadow: 0 4px 16px rgba(0,0,0,0.4);
  animation: toastIn 0.3s ease;
  white-space: nowrap;
  pointer-events: auto;
}

.toast.success { background: var(--success); }
.toast.error { background: var(--danger); }
.toast.info { background: var(--info); }
.toast.warning { background: #E65100; }

@keyframes toastIn {
  from { opacity: 0; transform: translateY(20px); }
  to { opacity: 1; transform: translateY(0); }
}

@keyframes toastOut {
  from { opacity: 1; transform: translateY(0); }
  to { opacity: 0; transform: translateY(-20px); }
}

/* ===== Loading Overlay ===== */
.loading-overlay {
  position: fixed;
  top: 0; left: 0; right: 0; bottom: 0;
  background: rgba(128, 128, 128, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 999;
  backdrop-filter: blur(4px);
}

.spinner {
  width: 40px;
  height: 40px;
  border: 3px solid var(--surface-3);
  border-top-color: var(--primary);
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}

@keyframes spin { to { transform: rotate(360deg); } }

/* ===== No API Warning ===== */
.no-api-warning {
  background: rgba(239, 83, 80, 0.1);
  border: 1px solid rgba(239, 83, 80, 0.3);
  border-radius: var(--radius);
  padding: 20px;
  text-align: center;
  margin-bottom: 12px;
}

.no-api-warning h3 {
  color: var(--danger);
  margin-bottom: 8px;
  font-size: 16px;
}

.no-api-warning p {
  color: var(--text-secondary);
  font-size: 13px;
  line-height: 1.6;
}

/* ===== Footer ===== */
.footer {
  text-align: center;
  padding: 16px 0 24px;
  color: var(--text-hint);
  font-size: 12px;
}

.footer a {
  color: var(--primary);
  text-decoration: none;
}

.footer a:hover { text-decoration: underline; }

/* ===== Floating Tab Bar ===== */
.floating-tab {
  position: fixed;
  bottom: 24px;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 5px 6px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 28px;
  box-shadow: 0 4px 24px rgba(0,0,0,0.45);
  z-index: 100;
  -webkit-tap-highlight-color: transparent;
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s ease;
}

.floating-tab.tab-hidden {
  transform: translateX(-50%) translateY(80px);
  opacity: 0;
  pointer-events: none;
}

.floating-tab-item {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 8px 22px;
  border-radius: 22px;
  cursor: pointer;
  transition: all 0.25s ease;
  color: var(--text-hint);
  user-select: none;
  font-size: 12px;
  font-weight: 600;
}

.floating-tab-item.active {
  background: linear-gradient(145deg, var(--primary), var(--primary-dark));
  color: white;
  box-shadow: 0 2px 10px rgba(104, 188, 113, 0.35);
}

.tab-icon {
  width: 18px;
  height: 18px;
  fill: currentColor;
}

.tab-label {
  font-size: 12px;
  font-weight: 600;
}

/* ===== Responsive ===== */
@media (max-width: 400px) {
  .container { padding: 12px; }
  .card { padding: 16px; }
  .control-buttons { grid-template-columns: 1fr 1fr; }
}


================================================
FILE: version.json
================================================
{
  "versionCode": 49,
  "version": "20260419",
  "zipUrl": "https://github.com/twoone-3/AdGuardHomeForRoot/releases/latest/download/AdGuardHomeForRoot_arm64.zip",
  "changelog": "https://raw.githubusercontent.com/twoone-3/AdGuardHomeForRoot/main/changelog.md"
}
Download .txt
gitextract_2fth1gt4/

├── .gitattributes
├── .github/
│   └── workflows/
│       └── pack.yml
├── .gitignore
├── LICENSE
├── README.md
├── README_en.md
├── changelog.md
├── docs/
│   └── index.md
├── pack.ps1
├── src/
│   ├── META-INF/
│   │   └── com/
│   │       └── google/
│   │           └── android/
│   │               ├── update-binary
│   │               └── updater-script
│   ├── action.sh
│   ├── bin/
│   │   ├── AdGuardHome.yaml
│   │   └── data/
│   │       └── filters/
│   │           └── 1732747955.txt
│   ├── customize.sh
│   ├── module.prop
│   ├── scripts/
│   │   ├── base.sh
│   │   ├── debug.sh
│   │   ├── inotify.sh
│   │   ├── iptables.sh
│   │   └── tool.sh
│   ├── service.sh
│   ├── settings.conf
│   ├── uninstall.sh
│   └── webroot/
│       ├── app.js
│       ├── index.html
│       └── style.css
└── version.json
Download .txt
SYMBOL INDEX (28 symbols across 1 files)

FILE: src/webroot/app.js
  function showToast (line 54) | function showToast(message, type, duration) {
  function showLoading (line 68) | function showLoading(show) {
  function switchTab (line 95) | function switchTab(index) {
  function getContainer (line 128) | function getContainer() {
  function onTouchStart (line 135) | function onTouchStart(e) {
  function onTouchMove (line 145) | function onTouchMove(e) {
  function onTouchEnd (line 177) | function onTouchEnd(e) {
  function checkStatus (line 205) | function checkStatus() {
  function getConf (line 234) | function getConf(key) {
  function getConfQuoted (line 239) | function getConfQuoted(key) {
  function loadSettings (line 243) | function loadSettings() {
  function applySettingsToUI (line 257) | function applySettingsToUI(settings) {
  function onSettingChange (line 270) | function onSettingChange() {
  function saveSettings (line 276) | function saveSettings() {
  function loadLog (line 310) | function loadLog() {
  function toggleStatsHelp (line 324) | function toggleStatsHelp() {
  function closeStatsHelp (line 331) | function closeStatsHelp() {
  function showStatsDataView (line 339) | function showStatsDataView() {
  function showStatsAuthView (line 347) | function showStatsAuthView() {
  function submitCredentials (line 355) | function submitCredentials() {
  function loadStats (line 366) | function loadStats() {
  function controlAdGuard (line 413) | function controlAdGuard(action) {
  function openAdGuardHome (line 449) | function openAdGuardHome() {
  function collectDebugInfo (line 455) | function collectDebugInfo() {
  function loadWebPort (line 463) | function loadWebPort() {
  function loadVersion (line 475) | function loadVersion() {
  function setupScrollHideTab (line 484) | function setupScrollHideTab() {
  function init (line 506) | function init() {
Condensed preview — 28 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (113K chars).
[
  {
    "path": ".gitattributes",
    "chars": 54,
    "preview": "* text eol=lf\n*.ps1 text eol=crlf\n*.bat text eol=crlf\n"
  },
  {
    "path": ".github/workflows/pack.yml",
    "chars": 3258,
    "preview": "name: Pack AdGuardHomeForRoot\n\non:\n  workflow_dispatch:\n  push:\n    tags:\n      - \"[0-9]*\"\n\npermissions:\n  contents: wri"
  },
  {
    "path": ".gitignore",
    "chars": 7,
    "preview": "cache/\n"
  },
  {
    "path": "LICENSE",
    "chars": 1064,
    "preview": "MIT License\n\nCopyright (c) 2025 twoone3\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof"
  },
  {
    "path": "README.md",
    "chars": 2329,
    "preview": "# AdGuardHome for Root\n\n[English](README_en.md) | 简体中文\n\n![arm-64 support](https://img.shields.io/badge/arm--64-support-e"
  },
  {
    "path": "README_en.md",
    "chars": 3263,
    "preview": "# AdGuardHome for Root\n\nEnglish | [简体中文](README.md)\n\n![arm-64 support](https://img.shields.io/badge/arm--64-support-ef47"
  },
  {
    "path": "changelog.md",
    "chars": 137,
    "preview": "# Changelog\n\n- 同步 AdGuard Home v0.107.74\n- Sync with AdGuard Home v0.107.74\n- 新增 WebUI 配置界面 #61 (wTNTw)\n- New WebUI inte"
  },
  {
    "path": "docs/index.md",
    "chars": 4586,
    "preview": "# 教程 (Tutorials)\n\n> **For English Users:** This module is primarily designed for Chinese users. If you require the docum"
  },
  {
    "path": "pack.ps1",
    "chars": 3221,
    "preview": "# 定义下载 URL 和路径变量\r\n$CacheDir = \"$PSScriptRoot\\cache\"\r\n$UrlWitchCachePath = @{\r\n  \"https://github.com/AdguardTeam/AdGuardH"
  },
  {
    "path": "src/META-INF/com/google/android/update-binary",
    "chars": 604,
    "preview": "#!/sbin/sh\n\n#################\n# Initialization\n#################\n\numask 022\n\n# echo before loading util_functions\nui_pri"
  },
  {
    "path": "src/META-INF/com/google/android/updater-script",
    "chars": 8,
    "preview": "#MAGISK\n"
  },
  {
    "path": "src/action.sh",
    "chars": 67,
    "preview": ". /data/adb/agh/settings.conf\n\n$SCRIPT_DIR/tool.sh toggle\n\nsleep 1\n"
  },
  {
    "path": "src/bin/AdGuardHome.yaml",
    "chars": 3864,
    "preview": "http:\n  pprof:\n    port: 6060\n    enabled: false\n  address: 127.0.0.1:3000\n  session_ttl: 720h\nusers:\n  - name: root\n   "
  },
  {
    "path": "src/bin/data/filters/1732747955.txt",
    "chars": 21466,
    "preview": "||1500020991.vodplayer.wxamedia.com^\n||1500022744.vodplayer.wxamedia.com^\n||1500026601.vodplayer.wxamedia.com^\n||8le8le."
  },
  {
    "path": "src/customize.sh",
    "chars": 3994,
    "preview": "SKIPUNZIP=1\n\n# most of the users are Chinese, so set default language to Chinese\nlanguage=\"zh\"\n\n# try to get the system "
  },
  {
    "path": "src/module.prop",
    "chars": 195,
    "preview": "id=AdGuardHome\nname=AdGuardHome for Root\nversion=20260419\nversionCode=49\nauthor=twoone3\ndescription=none\nupdateJson=http"
  },
  {
    "path": "src/scripts/base.sh",
    "chars": 950,
    "preview": "# add busybox to PATH\n[ -d \"/data/adb/magisk\" ] && export PATH=\"/data/adb/magisk:$PATH\"\n[ -d \"/data/adb/ksu/bin\" ] && ex"
  },
  {
    "path": "src/scripts/debug.sh",
    "chars": 1732,
    "preview": "#!/system/bin/sh\n\nAGH_DIR=\"/data/adb/agh\"\nLOG=\"$AGH_DIR/debug.log\"\n\n{\n  echo \"==== AdGuardHome Debug Log ====\"\n  date\n  "
  },
  {
    "path": "src/scripts/inotify.sh",
    "chars": 278,
    "preview": ". /data/adb/agh/settings.conf\n\nreadonly EVENTS=$1\nreadonly MONITOR_DIR=$2\nreadonly MONITOR_FILE=$3\n\nif [ \"${MONITOR_FILE"
  },
  {
    "path": "src/scripts/iptables.sh",
    "chars": 5458,
    "preview": ". /data/adb/agh/settings.conf\n. /data/adb/agh/scripts/base.sh\n\niptables_w=\"iptables -w 64\"\nip6tables_w=\"ip6tables -w 64\""
  },
  {
    "path": "src/scripts/tool.sh",
    "chars": 2334,
    "preview": ". /data/adb/agh/settings.conf\n. /data/adb/agh/scripts/base.sh\n\nstart_adguardhome() {\n  # check if AdGuardHome is already"
  },
  {
    "path": "src/service.sh",
    "chars": 187,
    "preview": "until [ $(getprop init.svc.bootanim) = \"stopped\" ]; do\n  sleep 12\ndone\n\n/data/adb/agh/scripts/tool.sh start\n\ninotifyd /d"
  },
  {
    "path": "src/settings.conf",
    "chars": 898,
    "preview": "# 是否启用内置的 iptables 规则\n# Whether to enable the built-in iptables rules\nenable_iptables=true\n\n# 阻断 ipv6 的 DNS 请求,仅在 enable"
  },
  {
    "path": "src/uninstall.sh",
    "chars": 49,
    "preview": "[ -d \"/data/adb/agh\" ] && rm -rf \"/data/adb/agh\"\n"
  },
  {
    "path": "src/webroot/app.js",
    "chars": 16419,
    "preview": "/**\n * AdGuardHome for Root - WebUI Application\n * \n * KernelSU API: ksu.exec(cmd) returns stdout string synchronously\n "
  },
  {
    "path": "src/webroot/index.html",
    "chars": 9806,
    "preview": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, ini"
  },
  {
    "path": "src/webroot/style.css",
    "chars": 15359,
    "preview": "/* AdGuardHome for Root - WebUI Styles */\n\n/* ===== Dark Theme (default) ===== */\n:root {\n  --primary: #68BC71;\n  --prim"
  },
  {
    "path": "version.json",
    "chars": 263,
    "preview": "{\n  \"versionCode\": 49,\n  \"version\": \"20260419\",\n  \"zipUrl\": \"https://github.com/twoone-3/AdGuardHomeForRoot/releases/lat"
  }
]

About this extraction

This page contains the full source code of the twoone-3/AdGuardHomeForMagisk GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 28 files (99.5 KB), approximately 34.9k tokens, and a symbol index with 28 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!