[
  {
    "path": ".gitattributes",
    "content": "* text eol=lf\n*.ps1 text eol=crlf\n*.bat text eol=crlf\n"
  },
  {
    "path": ".github/workflows/pack.yml",
    "content": "name: Pack AdGuardHomeForRoot\n\non:\n  workflow_dispatch:\n  push:\n    tags:\n      - \"[0-9]*\"\n\npermissions:\n  contents: write\n\njobs:\n  package:\n    name: Build ${{ matrix.arch }} package\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: false\n      matrix:\n        arch: [arm64, armv7]\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v4\n\n      - name: Resolve archive URL\n        id: vars\n        shell: bash\n        run: |\n          set -euo pipefail\n          case \"${{ matrix.arch }}\" in\n            arm64)\n              echo \"archive_url=https://github.com/AdguardTeam/AdGuardHome/releases/latest/download/AdGuardHome_linux_arm64.tar.gz\" >> \"$GITHUB_OUTPUT\"\n              ;;\n            armv7)\n              echo \"archive_url=https://github.com/AdguardTeam/AdGuardHome/releases/latest/download/AdGuardHome_linux_armv7.tar.gz\" >> \"$GITHUB_OUTPUT\"\n              ;;\n            *)\n              echo \"Unsupported arch: ${{ matrix.arch }}\" >&2\n              exit 1\n              ;;\n          esac\n\n      - name: Download release archive\n        shell: bash\n        run: |\n          set -euo pipefail\n          mkdir -p cache\n          curl -fL \"${{ steps.vars.outputs.archive_url }}\" -o \"cache/AdGuardHome_linux_${{ matrix.arch }}.tar.gz\"\n\n      - name: Extract archive\n        shell: bash\n        run: |\n          set -euo pipefail\n          mkdir -p \"cache/${{ matrix.arch }}\"\n          tar -xzf \"cache/AdGuardHome_linux_${{ matrix.arch }}.tar.gz\" -C \"cache/${{ matrix.arch }}\"\n\n      - name: Stage package files\n        shell: bash\n        run: |\n          set -euo pipefail\n          rm -rf staging\n          mkdir -p staging\n          cp -a src/. staging/\n          cp \"cache/${{ matrix.arch }}/AdGuardHome/AdGuardHome\" \"staging/bin/AdGuardHome\"\n\n      - name: Create zip package\n        shell: bash\n        run: |\n          set -euo pipefail\n          rm -f \"AdGuardHomeForRoot_${{ matrix.arch }}.zip\"\n          (\n            cd staging\n            zip -r \"../AdGuardHomeForRoot_${{ matrix.arch }}.zip\" .\n          )\n\n      - name: Upload artifact\n        uses: actions/upload-artifact@v4\n        with:\n          name: AdGuardHomeForRoot_${{ matrix.arch }}\n          path: AdGuardHomeForRoot_${{ matrix.arch }}.zip\n          if-no-files-found: error\n\n  release:\n    name: Create GitHub Release\n    runs-on: ubuntu-latest\n    needs: package\n    if: startsWith(github.ref, 'refs/tags/')\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v4\n\n      - name: Validate tag format\n        shell: bash\n        run: |\n          set -euo pipefail\n          if [[ ! \"${GITHUB_REF_NAME}\" =~ ^[0-9]{8}$ ]]; then\n            echo \"Tag must be an 8-digit date (YYYYMMDD), got: ${GITHUB_REF_NAME}\" >&2\n            exit 1\n          fi\n\n      - name: Download build artifacts\n        uses: actions/download-artifact@v4\n        with:\n          pattern: AdGuardHomeForRoot_*\n          merge-multiple: true\n\n      - name: Create release\n        uses: softprops/action-gh-release@v2\n        with:\n          tag_name: ${{ github.ref_name }}\n          name: ${{ github.ref_name }}\n          body_path: changelog.md\n          files: |\n            AdGuardHomeForRoot_arm64.zip\n            AdGuardHomeForRoot_armv7.zip\n"
  },
  {
    "path": ".gitignore",
    "content": "cache/\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2025 twoone3\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# AdGuardHome for Root\n\n[English](README_en.md) | 简体中文\n\n![arm-64 support](https://img.shields.io/badge/arm--64-support-ef476f?logo=linux&logoColor=white&color=ef476f)\n![arm-v7 support](https://img.shields.io/badge/arm--v7-support-ffa500?logo=linux&logoColor=white&color=ffa500)\n![GitHub downloads](https://img.shields.io/github/downloads/twoone-3/AdGuardHomeForRoot/total?logo=github&logoColor=white&color=ffd166)\n![License](https://img.shields.io/badge/License-MIT-9b5de5?logo=opensourceinitiative&logoColor=white)\n[![Docs](https://img.shields.io/badge/Docs-Guide-0066ff?logo=book&logoColor=white)](docs/index.md)\n[![Join Telegram Channel](https://img.shields.io/badge/Telegram-Join%20Channel-06d6a0?logo=telegram&logoColor=white)](https://t.me/+Q3Ur_HCYdM0xM2I1)\n[![Join Telegram Group](https://img.shields.io/badge/Telegram-Join%20Group-118ab2?logo=telegram&logoColor=white)](https://t.me/+Q3Ur_HCYdM0xM2I1)\n\n关注我们的频道获取最新消息，或加入我们的群组进行讨论！  \n\n## 简介\n\n- 本模块是一个在安卓设备上运行 [AdGuardHome](https://github.com/AdguardTeam/AdGuardHome) 的模块，提供了一个本地 DNS 服务器，能够屏蔽广告、恶意软件和跟踪器。\n- 它可以作为一个本地广告拦截模块使用，也可以通过调整配置文件，转变为一个独立运行的 AdGuardHome 工具。\n- 该模块支持 Magisk、KernelSU 和 APatch 等多种安装方式，适用于大多数 Android 设备。\n- 该模块的设计初衷是为了提供一个轻量级的广告拦截解决方案，避免了使用 VPN 的复杂性和性能损失。\n- 它可以与其他代理软件（如 [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) 等）共存，提供更好的隐私保护和网络安全。\n\n## 特性\n\n- 可选将本机 DNS 请求转发到本地 AdGuardHome 服务器\n- 使用 [秋风广告规则](https://github.com/TG-Twilight/AWAvenue-Ads-Rule) 过滤广告，轻量，省电，少误杀\n- 可从 <http://127.0.0.1:3000> 访问 AdGuardHome 控制面板，支持查询统计，修改 DNS 上游服务器以及自定义规则等功能\n\n## 教程\n\n1. 前往 [Release](https://github.com/twoone-3/AdGuardHomeForRoot/releases/latest) 页面下载模块\n2. 检查 Android 设置 -> 网络和互联网 -> 高级 -> 私人 DNS，确保 `私人 DNS` 关闭\n3. 在 root 管理器中安装模块，重启设备\n4. 若看到模块运行成功的提示，则可以访问 <http://127.0.0.1:3000> 进入 AdGuardHome 后台，默认用户密码 root/root\n5. 若需高级使用教程和常见问题解答，请访问 **[文档与教程](docs/index.md)**。\n\n## 鸣谢\n\n- [AWAwenue Ads Rule](https://github.com/TG-Twilight/AWAvenue-Ads-Rule)\n- [AdguardHome_magisk](https://github.com/410154425/AdGuardHome_magisk)\n- [akashaProxy](https://github.com/ModuleList/akashaProxy)\n- [box_for_magisk](https://github.com/taamarin/box_for_magisk)\n\n> 特别感谢赞助：\n>\n> - y******a - 200\n> - 偶****** - 10\n"
  },
  {
    "path": "README_en.md",
    "content": "# AdGuardHome for Root\n\nEnglish | [简体中文](README.md)\n\n![arm-64 support](https://img.shields.io/badge/arm--64-support-ef476f?logo=linux&logoColor=white&color=ef476f)\n![arm-v7 support](https://img.shields.io/badge/arm--v7-support-ffa500?logo=linux&logoColor=white&color=ffa500)\n![GitHub downloads](https://img.shields.io/github/downloads/twoone-3/AdGuardHomeForRoot/total?logo=github&logoColor=white&color=ffd166)\n![License](https://img.shields.io/badge/License-MIT-9b5de5?logo=opensourceinitiative&logoColor=white)\n[![Docs](https://img.shields.io/badge/Docs-Guide-0066ff?logo=book&logoColor=white)](docs/index.md)\n[![Join Telegram Channel](https://img.shields.io/badge/Telegram-Join%20Channel-06d6a0?logo=telegram&logoColor=white)](https://t.me/+Q3Ur_HCYdM0xM2I1)\n[![Join Telegram Group](https://img.shields.io/badge/Telegram-Join%20Group-118ab2?logo=telegram&logoColor=white)](https://t.me/+Q3Ur_HCYdM0xM2I1)\n\nFollow our channel for the latest news, or join our group for discussion!\n\n## Introduction\n\n- 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.\n- It can be used as a local ad-blocking module or transformed into a standalone AdGuardHome tool by adjusting the configuration file.\n- The module supports multiple installation methods, including Magisk, KernelSU, and APatch, making it compatible with most Android devices.\n- The design of this module aims to provide a lightweight ad-blocking solution, avoiding the complexity and performance loss associated with using VPNs.\n- 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.\n\n## Features\n\n- Optionally forward local DNS requests to the local AdGuardHome server\n- Filter ads using [AWAvenue-Ads-Rule](https://github.com/TG-Twilight/AWAvenue-Ads-Rule) for lightweight, power-saving, and fewer false positives\n- Access the AdGuardHome control panel from <http://127.0.0.1:3000>, supporting query statistics, modifying DNS upstream servers, and custom rules, etc.\n\n## Tutorial\n\n1. Go to the [Release](https://github.com/twoone-3/AdGuardHomeForRoot/releases/latest) page to download the module\n2. Check Android Settings -> Network & Internet -> Advanced -> Private DNS, ensure `Private DNS` is turned off\n3. Install the module in the root manager and reboot the device\n4. 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\n5. For advanced usage tutorials and FAQs, please visit **[Docs & Tutorials](docs/index.md)**.\n\n## Acknowledgments\n\n- [AWAwenue Ads Rule](https://github.com/TG-Twilight/AWAvenue-Ads-Rule)\n- [AdguardHome_magisk](https://github.com/410154425/AdGuardHome_magisk)\n- [akashaProxy](https://github.com/ModuleList/akashaProxy)\n- [box_for_magisk](https://github.com/taamarin/box_for_magisk)\n\n> Special thanks to sponsors:\n>\n> - y******a - 200\n> - 偶****** - 10\n"
  },
  {
    "path": "changelog.md",
    "content": "# Changelog\n\n- 同步 AdGuard Home v0.107.74\n- Sync with AdGuard Home v0.107.74\n- 新增 WebUI 配置界面 #61 (wTNTw)\n- New WebUI interface #61 (wTNTw)"
  },
  {
    "path": "docs/index.md",
    "content": "# 教程 (Tutorials)\n\n> **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.\n\n## 安装 (Installation)\n\n本模块仅适用于已经 root 的安卓设备，支持 [Magisk](https://github.com/topjohnwu/Magisk) / [KernelSU](https://github.com/tiann/KernelSU) / [APatch](https://github.com/bmax121/APatch) 等 root 工具\n\n在 Release 页面下载 zip 文件，提供了 arm64 和 armv7 两个版本。一般推荐使用 arm64 版，因为它在性能上更优，并且与大多数现代设备兼容。\n\n---\n\n## 配置 (Configuration)\n\n模块默认的 AdGuardHome 后台地址为 `http://127.0.0.1:3000`，可以通过浏览器直接访问，默认账号和密码均为 `root`。\n\n在 AdGuardHome 后台，你可以执行以下操作：\n\n- 查看 DNS 查询统计信息\n- 修改各种 DNS 配置\n- 查看日志\n- 添加自定义规则\n\n如果你更倾向于使用app管理AdGuardHome，可以尝试使用 [AdGuard Home Manager](https://github.com/JGeek00/adguard-home-manager) 应用。\n\n---\n\n## 模块控制 (Module Control)\n\n模块的状态会实时显示在`module.prop`文件中，在root管理器中可以看到模块的状态信息（如果没刷新请手动刷新）\n\n模块实时监测`/data/adb/modules/AdGuardHome`目录下的`disable`文件，如果存在则禁用模块，不存在则启用模块\n\n如果你想用其他方法来启停，你可以在文件管理器中手动创建和删除文件，也可以使用shell命令\n\n```shell\ntouch /data/adb/modules/AdGuardHome/disable\n```\n\n```shell\nrm /data/adb/modules/AdGuardHome/disable\n```\n\n本模块可以分为两部分，一部分是 AdGuardHome 本身，它在本地搭建了一个可自定义拦截功能的 DNS 服务器，另一部分是 iptables 转发规则，它负责将本机所有53端口出口流量重定向到 AdGuardHome\n\n---\n\n## 与代理软件共存 (Coexistence with Proxy Software)\n\n代理软件主要分为两类：\n\n**代理应用**：如 [NekoBox](https://github.com/MatsuriDayo/NekoBoxForAndroid)、[FlClash](https://github.com/chen08209/FlClash) 等。这些应用通常具有图形化界面，便于用户配置和管理代理规则。\n\n以下是我自用的 FlClash 配置文件示例：\n\n```yaml\nproxy-providers:\n  provider1:\n    type: http\n    url: \"\"\n    interval: 86400\n\n  provider2:\n    type: http\n    url: \"\"\n    interval: 86400\n\nproxy-groups:\n  - name: PROXY\n    type: select\n    include-all: true\n\nrules:\nproxy-groups:\n  - name: PROXY\n    type: select\n    include-all: true\n\nrules:\n  - GEOSITE,private,DIRECT\n  - GEOSITE,googlefcm,DIRECT\n  - GEOSITE,bilibili,DIRECT\n  - GEOSITE,onedrive,PROXY\n  - GEOSITE,twitter,PROXY\n  - GEOSITE,youtube,PROXY\n  - GEOSITE,telegram,PROXY\n  - GEOSITE,google,PROXY\n  \n  - GEOSITE,microsoft@cn,DIRECT\n  - GEOSITE,category-scholar-!cn,PROXY\n  - GEOSITE,steam@cn,DIRECT\n  - GEOSITE,category-games@cn,DIRECT\n  - GEOSITE,geolocation-!cn,PROXY\n  - GEOSITE,cn,DIRECT\n\n  - GEOIP,private,DIRECT,no-resolve\n  - GEOIP,google,DIRECT\n  - GEOIP,telegram,PROXY\n  - GEOIP,cn,DIRECT\n\n  - MATCH,DIRECT\n\n```\n\n没有写 DNS 部分是因为 FlClash 支持 DNS 覆写，在软件内就可配置 DNS 部分，将域名解析服务器改为 127.0.0.1:5591 即可使用本地的 adgh 作为DNS服务器\n\n**代理模块**：如 [box_for_magisk](https://github.com/taamarin/box_for_magisk)、[akashaProxy](https://github.com/akashaProxy/akashaProxy) 等。这些模块通常运行在系统层级，适合需要更高权限或更深度集成的场景。\n\n代理应用的 `分应用代理/访问控制` 功能非常实用。通过将国内应用设置为绕过模式，可以减少不必要的流量经过代理，同时这些绕过的应用仍然能够正常屏蔽广告。\n\n如果使用代理模块，强烈建议禁用模块的 iptables 转发规则。禁用后，模块仅运行 AdGuardHome 本身。随后，将代理模块的上游 DNS 服务器配置为 `127.0.0.1:5591`，即可确保代理软件的所有 DNS 查询通过 AdGuardHome 进行广告屏蔽。\n\n```yaml\ndns:\n  # ...\n  default-nameserver:\n    - 223.5.5.5\n  nameserver:\n    - 127.0.0.1:5591\n  # ...\n```\n\n---\n\n## 模块目录与配置文件 (Module Directory and Configuration Files)\n\n模块的文件结构主要分为以下两个目录：\n\n- **`/data/adb/agh`**：包含 AdGuardHome 的核心文件，包括二进制文件、工具脚本和配置文件。\n- **`/data/adb/modules/AdGuardHome`**：存储模块的启动脚本和运行时数据文件。\n\n模块的配置文件也分为两部分：\n\n- **`/data/adb/agh/bin/AdGuardHome.yaml`**：AdGuardHome 的主配置文件。\n- **`/data/adb/agh/settings.conf`**：模块的配置文件，具体说明请参考文件内的注释。\n\n在更新模块时，用户可以选择是否保留原有的配置文件。如果选择不保留，系统会自动将原配置文件备份到 **`/data/adb/agh/backup`** 目录，以确保数据安全。\n\n---\n\n## 模块打包 (Module Packaging)\n\n模块根目录下提供了一个名为 `pack.ps1` 的打包脚本，用户可以通过它快速生成模块的安装包。\n\n在 Windows 系统上，打开 PowerShell 并执行以下命令：\n\n```powershell\n.\\pack.ps1\n```\n\n运行脚本后，以下操作将自动完成：\n\n1. 创建 `cache` 目录（如果尚未存在）。\n2. 下载并缓存最新版本的 AdGuardHome（仅在 `cache` 目录中未找到缓存时执行下载）。\n3. 将 AdGuardHome 与模块的其他文件打包成一个 ZIP 文件。\n\n该脚本的设计确保了高效性：如果 `cache` 目录中已存在 AdGuardHome 的缓存版本，则无需重复下载，从而节省时间和带宽。\n\n## 常见问题 (Frequently Asked Questions)\n\n### **Q: 模块安装后无法正常运行怎么办？**  \n\n**A:**  \n\n- 检查 AdGuardHome 是否在运行：  \n  使用以下命令查看进程状态：  \n\n  ```shell\n  ps | grep AdGuardHome\n  ```\n\n- 确保设备的 **私人 DNS** 功能已关闭：  \n  前往 **设置 -> 网络和互联网 -> 高级 -> 私人 DNS**，并将其设置为关闭。\n\n### **Q: 如何更改 AdGuardHome 的默认端口？**  \n\n**A:**  \n\n- 打开 **`/data/adb/agh/bin/AdGuardHome.yaml`** 文件。  \n- 修改 `bind_host` 的端口号为所需值。  \n- 保存文件后，重启模块以应用更改。\n\n### **Q: 如何禁用模块的 iptables 转发规则？**  \n\n**A:**  \n\n- 编辑 **`/data/adb/agh/settings.conf`** 文件。  \n- 将 `ENABLE_IPTABLES` 参数设置为 `false`。  \n- 保存文件后，重启模块。\n\n### **Q: 使用代理模块时，广告屏蔽无效怎么办？**  \n\n**A:**  \n\n- 确保代理模块的上游 DNS 服务器配置为 **`127.0.0.1:5591`**。  \n- 检查代理模块的配置文件，确保所有 DNS 查询通过 AdGuardHome。\n\n### **Q: 模块是否会影响设备性能？**  \n\n**A:**  \n\n- 模块对性能的影响较小，但在低性能设备上可能会有轻微延迟。  \n- 推荐使用 **arm64** 版本以获得更好的性能。\n\n### **Q：使用模块后，无法访问 Google 怎么办？**\n\n**A:**\n\n- 如果你用的是 FlClash，可尝试在`settings.conf`填入以下配置：\n\n```ini\nignore_src_list=\"172.19.0.1\"\n```\n\n此问题与节点质量有关，有的机场不改也没问题\n"
  },
  {
    "path": "pack.ps1",
    "content": "# 定义下载 URL 和路径变量\r\n$CacheDir = \"$PSScriptRoot\\cache\"\r\n$UrlWitchCachePath = @{\r\n  \"https://github.com/AdguardTeam/AdGuardHome/releases/latest/download/AdGuardHome_linux_arm64.tar.gz\" = \"$CacheDir\\AdGuardHome_linux_arm64.tar.gz\"\r\n  \"https://github.com/AdguardTeam/AdGuardHome/releases/latest/download/AdGuardHome_linux_armv7.tar.gz\" = \"$CacheDir\\AdGuardHome_linux_armv7.tar.gz\"\r\n}\r\n\r\n# 创建缓存目录\r\nif (-Not (Test-Path -Path $CacheDir)) {\r\n  Write-Host \"Creating cache directory...\"\r\n  New-Item -Path $CacheDir -ItemType Directory\r\n}\r\n\r\n# 下载文件，有缓存时不再下载\r\nWrite-Host \"Downloading AdGuardHome...\"\r\nforeach ($url in $UrlWitchCachePath.Keys) {\r\n  $CachePath = $UrlWitchCachePath[$url]\r\n  if (-Not (Test-Path -Path $CachePath)) {\r\n    Write-Host \"Downloading $url...\"\r\n    Invoke-WebRequest -Uri $url -OutFile $CachePath\r\n    if ($?) {\r\n      Write-Host \"Download completed successfully.\"\r\n    }\r\n    else {\r\n      Write-Host \"Download failed. Exiting...\"\r\n      exit 1\r\n    }\r\n  }\r\n  else {\r\n    Write-Host \"File already exists in cache. Skipping download.\"\r\n  }\r\n}\r\n\r\n# 使用 tar 解压文件\r\nWrite-Host \"Extracting AdGuardHome...\"\r\nforeach ($url in $UrlWitchCachePath.Keys) {\r\n  $CachePath = $UrlWitchCachePath[$url]\r\n  if ($CachePath -match 'AdGuardHome_linux_(arm64|armv7)\\.tar\\.gz$') {\r\n    $ExtractDir = \"./cache/\" + $matches[1]\r\n  }\r\n  else {\r\n    throw \"Invalid file path: $CachePath\"\r\n  }\r\n  if (-Not (Test-Path -Path $ExtractDir)) {\r\n    New-Item -Path $ExtractDir -ItemType Directory\r\n    Write-Host \"Extracting $CachePath...\"\r\n    tar -xzf $CachePath -C $ExtractDir\r\n    if ($?) {\r\n      Write-Host \"Extraction completed successfully.\"\r\n    }\r\n    else {\r\n      Write-Host \"Extraction failed\"\r\n      exit 1\r\n    }\r\n  }\r\n}\r\n\r\n# 给项目打包，使用 7-Zip 压缩 zip\r\nWrite-Host \"Packing AdGuardHome...\"\r\n$OutputPathArm64 = \"$CacheDir\\AdGuardHomeForRoot_arm64.zip\"\r\n$OutputPathArmv7 = \"$CacheDir\\AdGuardHomeForRoot_armv7.zip\"\r\nif (Test-Path -Path $OutputPathArm64) {\r\n  Remove-Item -Path $OutputPathArm64\r\n}\r\nif (Test-Path -Path $OutputPathArmv7) {\r\n  Remove-Item -Path $OutputPathArmv7\r\n}\r\n\r\n# 设置项目根目录\r\n$ProjectRoot = \"$PSScriptRoot\\src\"\r\n$env:PATH += \";C:\\Program Files\\7-Zip\"\r\n\r\n# pack arm64\r\n7z a -tzip $OutputPathArm64 \"$ProjectRoot\\*.sh\"\r\n7z a -tzip $OutputPathArm64 \"$ProjectRoot\\settings.conf\"\r\n7z a -tzip $OutputPathArm64 \"$ProjectRoot\\module.prop\"\r\n7z a -tzip $OutputPathArm64 \"$ProjectRoot\\META-INF\"\r\n7z a -tzip $OutputPathArm64 \"$ProjectRoot\\scripts\"\r\n7z a -tzip $OutputPathArm64 \"$ProjectRoot\\webroot\"\r\n7z a -tzip $OutputPathArm64 \"$ProjectRoot\\bin\\\"\r\n7z a -tzip $OutputPathArm64 \"$CacheDir\\arm64\\AdGuardHome\\AdGuardHome\"\r\n7z rn $OutputPathArm64 \"AdGuardHome\" \"bin/AdGuardHome\"\r\n\r\n# pack armv7\r\n7z a -tzip $OutputPathArmv7 \"$ProjectRoot\\*.sh\"\r\n7z a -tzip $OutputPathArmv7 \"$ProjectRoot\\settings.conf\"\r\n7z a -tzip $OutputPathArmv7 \"$ProjectRoot\\module.prop\"\r\n7z a -tzip $OutputPathArmv7 \"$ProjectRoot\\META-INF\"\r\n7z a -tzip $OutputPathArmv7 \"$ProjectRoot\\scripts\"\r\n7z a -tzip $OutputPathArmv7 \"$ProjectRoot\\webroot\"\r\n7z a -tzip $OutputPathArmv7 \"$ProjectRoot\\bin\\\"\r\n7z a -tzip $OutputPathArmv7 \"$CacheDir\\armv7\\AdGuardHome\\AdGuardHome\"\r\n7z rn $OutputPathArmv7 \"AdGuardHome\" \"bin/AdGuardHome\"\r\n\r\nWrite-Host \"Packing completed successfully.\""
  },
  {
    "path": "src/META-INF/com/google/android/update-binary",
    "content": "#!/sbin/sh\n\n#################\n# Initialization\n#################\n\numask 022\n\n# echo before loading util_functions\nui_print() { echo \"$1\"; }\n\nrequire_new_magisk() {\n  ui_print \"*******************************\"\n  ui_print \" 请升级安装 Magisk v20.4或以上! \"\n  ui_print \"*******************************\"\n  exit 1\n}\n\n#########################\n# Load util_functions.sh\n#########################\n\nOUTFD=$2\nZIPFILE=$3\n\nmount /data 2>/dev/null\n\n[ -f /data/adb/magisk/util_functions.sh ] || require_new_magisk\n. /data/adb/magisk/util_functions.sh\n[ $MAGISK_VER_CODE -lt 20400 ] && require_new_magisk\n\ninstall_module\nexit 0"
  },
  {
    "path": "src/META-INF/com/google/android/updater-script",
    "content": "#MAGISK\n"
  },
  {
    "path": "src/action.sh",
    "content": ". /data/adb/agh/settings.conf\n\n$SCRIPT_DIR/tool.sh toggle\n\nsleep 1\n"
  },
  {
    "path": "src/bin/AdGuardHome.yaml",
    "content": "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    password: $2b$12$D3zeMIBFfzcQTqGqB.k7GOkMvqx1jgsrdiRCn2kwOHl.kNmmPfMom\nauth_attempts: 5\nblock_auth_min: 15\nhttp_proxy: \"\"\nlanguage: \"\"\ntheme: auto\ndns:\n  bind_hosts:\n    - 127.0.0.1\n  port: 5591\n  anonymize_client_ip: false\n  ratelimit: 0\n  ratelimit_subnet_len_ipv4: 24\n  ratelimit_subnet_len_ipv6: 56\n  ratelimit_whitelist: []\n  refuse_any: true\n  upstream_dns:\n    - https://doh.pub/dns-query\n    - https://dns.alidns.com/dns-query\n  upstream_dns_file: \"\"\n  bootstrap_dns:\n    - 119.29.29.29\n    - 223.5.5.5\n    - 223.6.6.6\n    - 1.1.1.1\n    - 8.8.8.8\n  fallback_dns:\n    - https://dns.google/dns-query\n    - https://cloudflare-dns.com/dns-query\n  upstream_mode: load_balance\n  fastest_timeout: 1s\n  allowed_clients: []\n  disallowed_clients: []\n  blocked_hosts:\n    - version.bind\n    - id.server\n    - hostname.bind\n  trusted_proxies:\n    - 127.0.0.0/8\n    - ::1/128\n  cache_enabled: true\n  cache_size: 67108864\n  cache_ttl_min: 0\n  cache_ttl_max: 0\n  cache_optimistic: true\n  cache_optimistic_answer_ttl: 30s\n  cache_optimistic_max_age: 12h\n  bogus_nxdomain: []\n  aaaa_disabled: false\n  enable_dnssec: false\n  edns_client_subnet:\n    custom_ip: \"\"\n    enabled: true\n    use_custom: false\n  max_goroutines: 300\n  handle_ddr: true\n  ipset: []\n  ipset_file: \"\"\n  bootstrap_prefer_ipv6: false\n  upstream_timeout: 10s\n  private_networks: []\n  use_private_ptr_resolvers: false\n  local_ptr_upstreams: []\n  use_dns64: false\n  dns64_prefixes: []\n  serve_http3: false\n  use_http3_upstreams: false\n  serve_plain_dns: true\n  hostsfile_enabled: true\n  pending_requests:\n    enabled: true\ntls:\n  enabled: false\n  server_name: \"\"\n  force_https: false\n  port_https: 443\n  port_dns_over_tls: 853\n  port_dns_over_quic: 853\n  port_dnscrypt: 0\n  dnscrypt_config_file: \"\"\n  allow_unencrypted_doh: false\n  certificate_chain: \"\"\n  private_key: \"\"\n  certificate_path: \"\"\n  private_key_path: \"\"\n  strict_sni_check: false\nquerylog:\n  dir_path: \"\"\n  ignored: []\n  interval: 24h\n  size_memory: 1000\n  enabled: true\n  file_enabled: true\nstatistics:\n  dir_path: \"\"\n  ignored: []\n  interval: 24h\n  enabled: true\nfilters:\n  - enabled: true\n    url: https://raw.githubusercontent.com/TG-Twilight/AWAvenue-Ads-Rule/main/AWAvenue-Ads-Rule.txt\n    name: AWAvenue\n    id: 1732747955\nwhitelist_filters: []\nuser_rules:\n  - \"\"\ndhcp:\n  enabled: false\n  interface_name: \"\"\n  local_domain_name: lan\n  dhcpv4:\n    gateway_ip: \"\"\n    subnet_mask: \"\"\n    range_start: \"\"\n    range_end: \"\"\n    lease_duration: 86400\n    icmp_timeout_msec: 1000\n    options: []\n  dhcpv6:\n    range_start: \"\"\n    lease_duration: 86400\n    ra_slaac_only: false\n    ra_allow_slaac: false\nfiltering:\n  blocking_ipv4: \"\"\n  blocking_ipv6: \"\"\n  blocked_services:\n    schedule:\n      time_zone: Asia/Shanghai\n    ids: []\n  protection_disabled_until: null\n  safe_search:\n    enabled: false\n    bing: true\n    duckduckgo: true\n    ecosia: true\n    google: true\n    pixabay: true\n    yandex: true\n    youtube: true\n  blocking_mode: default\n  parental_block_host: family-block.dns.adguard.com\n  safebrowsing_block_host: standard-block.dns.adguard.com\n  rewrites: []\n  safe_fs_patterns: []\n  safebrowsing_cache_size: 1048576\n  safesearch_cache_size: 1048576\n  parental_cache_size: 1048576\n  cache_time: 30\n  filters_update_interval: 72\n  blocked_response_ttl: 10\n  filtering_enabled: true\n  rewrites_enabled: true\n  parental_enabled: false\n  safebrowsing_enabled: false\n  protection_enabled: true\nclients:\n  runtime_sources:\n    whois: true\n    arp: true\n    rdns: true\n    dhcp: true\n    hosts: true\n  persistent: []\nlog:\n  enabled: true\n  file: \"\"\n  max_backups: 0\n  max_size: 100\n  max_age: 3\n  compress: false\n  local_time: false\n  verbose: false\nos:\n  group: \"\"\n  user: \"\"\n  rlimit_nofile: 0\nschema_version: 32\n"
  },
  {
    "path": "src/bin/data/filters/1732747955.txt",
    "content": "||1500020991.vodplayer.wxamedia.com^\n||1500022744.vodplayer.wxamedia.com^\n||1500026601.vodplayer.wxamedia.com^\n||8le8le.com^\n||8rn1y79f02-1.algolianet.com^\n||a0.app.xiaomi.com^\n||aaid.umeng.com^\n||abtest-ch.snssdk.com^\n||ad-cdn.qingting.fm^\n||ad-scope.com^\n||ad-scope.com.cn^\n||ad-sdk-config.youdao.com^\n||ad-splash-tracking.hktvmall.com^\n||ad-splash.hktvmall.com^\n||ad.12306.cn^\n||ad.51wnl.com^\n||ad.api.3g.youku.com^\n||ad.bwton.com^\n||ad.cctv.com^\n||ad.cyapi.cn^\n||ad.doubleclick.net^\n||ad.gameley.com^\n||ad.hpplay.cn^\n||ad.iot.360.cn^\n||ad.jia.360.cn^\n||ad.life.360.cn^\n||ad.partner.gifshow.com^\n||ad.qingting.fm^\n||ad.qq.com^\n||ad.richmob.cn^\n||ad.shenshiads.com^\n||ad.shunchangzhixing.com^\n||ad.tencentmusic.com^\n||ad.toutiao.com^\n||ad.v3mh.com^\n||ad.weibo.com^\n||ad.winrar.com.cn^\n||ad.ximalaya.com^\n||ad.xunkids.com^\n||ad.zijieapi.com^\n||adapi.izuiyou.com^\n||adapi.yynetwk.com^\n||adashbc.ut.taobao.com^\n||adashx.m.taobao.com^\n||adashxgc.ut.taobao.com^\n||adc.hpplay.cn^\n||adcdn.hpplay.cn^\n||adcdn.tencentmusic.com^\n||adclick.g.doubleclick.net^\n||adclick.tencentmusic.com^\n||adcolony.com^\n||addata.jd.com^\n||adeng.hpplay.cn^\n||adexp.chinaliftoff.io^\n||adexp.liftoff.io^\n||adexpo.tencentmusic.com^\n||adfilter.imtt.qq.com^\n||adguanggao.eee114.com^\n||adimg.uve.weibo.com^\n||adjust.cn^\n||adjust.com^\n||adks.hpplay.cn^\n||adlink-api.huan.tv^\n||adm.funshion.com^\n||ads-api-o.api.leiniao.com^\n||ads-api.tiktok.com^\n||ads-api.twitter.com^\n||ads-img-al.xhscdn.com^\n||ads-img-qc.xhscdn.com^\n||ads-jp.tiktok.com^\n||ads-marketing-vivofs.vivo.com.cn^\n||ads-sdk-api.ranfenghd.com^\n||ads-sg.tiktok.com^\n||ads-video-al.xhscdn.com^\n||ads-video-qc.xhscdn.com^\n||ads.95516.com^\n||ads.auctions.yahoo.com^\n||ads.cup.com.cn^\n||ads.google.cn^\n||ads.huan.tv^\n||ads.huantest.com^\n||ads.icloseli.cn^\n||ads.inmobi.com^\n||ads.linkedin.com^\n||ads.pinterest.com^\n||ads.pubmatic.com^\n||ads.raidrive.com^\n||ads.servebom.com^\n||ads.service.kugou.com^\n||ads.tiktok.com^\n||ads.v3mh.com^\n||ads.youtube.com^\n||ads.zhinengxiyifang.cn^\n||ads3-normal-hl.zijieapi.com^\n||ads3-normal-lf.zijieapi.com^\n||ads3-normal-lq.zijieapi.com^\n||ads3-normal.zijieapi.com^\n||ads5-normal-hl.zijieapi.com^\n||ads5-normal-lf.zijieapi.com^\n||ads5-normal-lq.zijieapi.com^\n||ads5-normal.zijieapi.com^\n||adsdk.vivo.com.cn^\n||adse.test.ximalaya.com^\n||adse.wsa.ximalaya.com^\n||adse.ximalaya.com^\n||adsebs.ximalaya.com^\n||adsense.google.cn^\n||adserver.unityads.unity3d.com^\n||adservice.google.com^\n||adservice.sigmob.cn^\n||adserviceretry.kugou.com^\n||adsfile.bssdlbig.kugou.com^\n||adsfile.qq.com^\n||adsfilebssdlbig.ali.kugou.com^\n||adsfileretry.service.kugou.com^\n||adsfs-sdkconfig.heytapimage.com^\n||adsfs.oppomobile.com^\n||adslvfile.qq.com^\n||adsmart.konka.com^\n||adsmind.gdtimg.com^\n||adsmind.ugdtimg.com^\n||adsp.xunlei.com^\n||adstat.izuiyou.com^\n||adstats.tencentmusic.com^\n||adstore-1252524079.file.myqcloud.com^\n||adstore-index-1252524079.file.myqcloud.com^\n||adstrategy.biz.weibo.com^\n||adstudio-assets.scdn.co^\n||adstudio.spotify.com^\n||adtago.s3.amazonaws.com^\n||adtech.yahooinc.com^\n||adtrack.quark.cn^\n||adtracker.medproad.com^\n||adui.tg.meitu.com^\n||adv-api.shenshiads.com^\n||adv.sec.intl.miui.com^\n||adv.sec.miui.com^\n||advertising-api-eu.amazon.com^\n||advertising-api-fe.amazon.com^\n||advertising-api.amazon.com^\n||advertising.apple.com^\n||advertising.yahoo.com^\n||advertising.yandex.ru^\n||advice-ads.s3.amazonaws.com^\n||adview.cn^\n||adx-ad.smart-tv.cn^\n||adx-api.jinmo.tech^\n||adx-bj-req.anythinktech.com^\n||adx-bj.anythinktech.com^\n||adx-cn.anythinktech.com^\n||adx-drcn.op.dbankcloud.cn^\n||adx-open-service.youku.com^\n||adx-os.anythinktech.com^\n||adx-saas.advlion.com^\n||adx-strategy-api-cn.statisticslinks.com^\n||adx.ads.heytapmobi.com^\n||adx.ads.oppomobile.com^\n||adx.appsdk.com.cn^\n||adx.sogaha.cn^\n||adx.tuia.cn^\n||adxlog-adnet.vivo.com.cn^\n||adxserver.ad.cmvideo.cn^\n||aegis.qq.com^\n||aem.dentsuchina.cn^\n||afs.googlesyndication.com^\n||agn.aty.sohu.com^\n||aiseet.aa.atianqi.com^\n||ali-ad.a.yximgs.com^\n||ali-p2p-v2.pull.yximgs.com^\n||alicoccdncnc-xn.inter.71edge.com^\n||alicoccdnct-xn.inter.71edge.com^\n||alog.umeng.com^\n||alogus.umeng.com^\n||als.baidu.com^\n||amdcopen.m.taobao.com^\n||an.facebook.com^\n||analysis.chatglm.cn^\n||analysis.yozocloud.cn^\n||analytics-api.samsunghealthcn.com^\n||analytics.adjust.cn^\n||analytics.oceanengine.com^\n||analytics.pinterest.com^\n||analytics.pointdrive.linkedin.com^\n||analytics.query.yahoo.com^\n||analytics.rayjump.com^\n||analytics.s3.amazonaws.com^\n||analytics.tiktok.com^\n||analytics.woozooo.com^\n||analyticsengine.s3.amazonaws.com^\n||analyze.lemurbrowser.com^\n||andrqd.play.aiseet.atianqi.com^\n||ap.dongdianqiu.com^\n||ap.dongqiudi.com^\n||apd-pcdnwxlogin.teg.tencent-cloud.net^\n||apd-pcdnwxnat.teg.tencent-cloud.net^\n||apd-pcdnwxstat.teg.tencent-cloud.net^\n||api-access.pangolin-sdk-toutiao.com^\n||api-access.pangolin-sdk-toutiao1.com^\n||api-access.pangolin-sdk-toutiao2.com^\n||api-access.pangolin-sdk-toutiao3.com^\n||api-access.pangolin-sdk-toutiao4.com^\n||api-access.pangolin-sdk-toutiao5.com^\n||api-ad-data.kajicam.com^\n||api-ad-product.huxiu.com^\n||api-ad.kajicam.com^\n||api-adservices.apple.com^\n||api-gd.hiaiabc.com^\n||api-htp.beizi.biz^\n||api.ad.xiaomi.com^\n||api.anythinktech.com^\n||api.aqyad.com^\n||api.e.kuaishou.com^\n||api.fu.xcultur.com^\n||api.htp.hubcloud.com.cn^\n||api.hzsanjiaomao.com^\n||api.installer.xiaomi.com^\n||api.jietuhb.com^\n||api.kingdata.ksyun.com^\n||api.shanghailingye.cn^\n||api.ssp.xcultur.com^\n||api.statsig.com^\n||api.touch-moblie.com^\n||api.yfanads.com^\n||api5-normal-quic-lf.ixigua.com^\n||apiyd.my91app.com^\n||app-measurement.com^\n||appcfg.v.qq.com^\n||applog-perf.uc.cn^\n||applog.lc.quark.cn^\n||applog.uc.cn^\n||applog.zijieapi.com^\n||applovin.com^\n||aqyad.com^\n||aspect-upush.umeng.com^\n||auction.unityads.unity3d.com^\n||audid-api.taobao.com^\n||audid.umeng.com^\n||b1-data.ads.heytapmobi.com^\n||badjs.weixinbridge.com^\n||baichuan-sdk.alicdn.com^\n||baichuan-sdk.taobao.com^\n||bdad.123pan.cn^\n||bdapi-ads.realmemobile.com^\n||bdapi-in-ads.realmemobile.com^\n||bdapi.ads.oppomobile.com^\n||bdcdncnc.inter.71edge.com^\n||beacon-api.aliyuncs.com^\n||beacon.qq.com^\n||beaconcdn.qq.com^\n||beacons.gvt2.com^\n||beizi.biz^\n||bes-mtj.baidu.com^\n||bgg.baidu.com^\n||bianxian.com^\n||bingads.microsoft.com^\n||biz.weibo.com^\n||bj-td-menta-01-callback.advlion.com^\n||bj.ad.track.66mobi.com^\n||books-analytics-events.apple.com^\n||bootpreload.uve.weibo.com^\n||browser.events.data.msn.cn^\n||browser.events.data.msn.com^\n||browsercfg-drcn.cloud.dbankcloud.cn^\n||bsrv.qq.com^\n||business-api.tiktok.com^\n||c.bidtoolads.com^\n||c.etoolads.cn^\n||c.evidon.com^\n||c.gj.qq.com^\n||c.kuaiduizuoye.com^\n||c.sayhi.360.cn^\n||c2.gdt.qq.com^\n||canvas-cdn.gdt.qq.com^\n||catalog.fjwhcbsh.com^\n||cbjs.baidu.com^\n||ccc-x.jd.com^\n||ccs.umeng.com^\n||cdn-ad.wtzw.com^\n||cdn-ads.oss-cn-shanghai.aliyuncs.com^\n||cdn-f.adsmoloco.com^\n||cdn-plugin-sync-upgrade-juui.hismarttv.com^\n||cdn.aiclk.com^\n||cdn.chinaliftoff-creatives.io^\n||cdn.hpplay.com.cn^\n||cdn.iads.unitychina.cn^\n||cdn.liftoff-creatives.io^\n||cdn.ynuf.aliapp.org^\n||cfg.imtt.qq.com^\n||chat1.jd.com^\n||chiq-cloud.com^\n||ck.ads.oppomobile.com^\n||click.chinaliftoff.io^\n||click.googleanalytics.com^\n||click.liftoff.io^\n||click.oneplus.cn^\n||click4.chinaliftoff.io^\n||clog.miguvideo.com^\n||cloooud.com^\n||cn-api.anythinktech.com^\n||cnlogs.umeng.com^\n||cnlogs.umengcloud.com^\n||cnzz.com^\n||collect.kugou.com^\n||commdata.v.qq.com^\n||conf.hpplay.cn^\n||config.chsmarttv.com^\n||config.inmobi.com^\n||config.unityads.unity3d.com^\n||cpc-service-square.aiclk.com^\n||cpro.baidustatic.com^\n||crash2.zhihu.com^\n||crashlyticsreports-pa.googleapis.com^\n||csjplatform.com^\n||d.applovin.com^\n||d.applvn.com^\n||da.anythinktech.com^\n||data-sdk-uuid-log.d.meituan.net^\n||data.ads.oppomobile.com^\n||data.chsmarttv.com^\n||data.mistat.india.xiaomi.com^\n||data.mistat.rus.xiaomi.com^\n||data.mistat.xiaomi.com^\n||datacollection.uve.weibo.com^\n||dc.sigmob.cn^\n||de.tynt.com^\n||diagnosis.ad.xiaomi.com^\n||dig.bdurl.net^\n||dlogs.bwton.com^\n||dm.toutiao.com^\n||domain.aishengji.com^\n||doubleclick-cn.net^\n||download.changhong.upgrade2.huan.tv^\n||downloadxml.changhong.upgrade2.huan.tv^\n||drcn-weather.cloud.huawei.com^\n||dsp-x.jd.com^\n||dsp.fcbox.com^\n||dualstack-logs.amap.com^\n||dxp.baidu.com^\n||dyp2p-ali.douyucdn.cn^\n||dyp2p-hw.douyucdn.cn^\n||e.ad.xiaomi.com^\n||eclick.baidu.com^\n||edge.ads.twitch.tv^\n||ef-dongfeng.tanx.com^\n||engine.dcad01.com^\n||entry.baidu.com^\n||et-eus.w.inmobi.com^\n||event.tradplusad.com^\n||events-drcn.op.dbankcloud.cn^\n||events.reddit.com^\n||events.redditmedia.com^\n||fancyapi.com^\n||fclog.baidu.com^\n||feed-image.baidu.com^\n||file.daihuo.qq.com^\n||firebaselogging-pa.googleapis.com^\n||flurry.com^\n||frontend-perf-service.e.kuaishou.com^\n||fxgate.baidu.com^\n||g-adnet.hiaiabc.com^\n||g-staic.ganjingworld.com^\n||g.dtv.cn.miaozhen.com^\n||g.fancyapi.com^\n||g2.ganjing.world^\n||game.loveota.com^\n||gdfp.gifshow.com^\n||gemini.yahoo.com^\n||geo.yahoo.com^\n||getui.cn^\n||ggx.cmvideo.cn^\n||ggx01.miguvideo.com^\n||ggx03.miguvideo.com^\n||globalapi.ad.xiaomi.com^\n||google-analytics.com^\n||googleads.g.doubleclick-cn.net^\n||googleads.g.doubleclick.net^\n||googleadservices-cn.com^\n||googleadservices.com^\n||googletagservices-cn.com^\n||gorgon.youdao.com^\n||gromore.pangolin-sdk-toutiao.com^\n||grs.dbankcloud.com^\n||grs.hicloud.com^\n||gslb.hpplay.cn^\n||gvideo.qpic.cn^\n||h-adashx.ut.taobao.com^\n||h.trace.qq.com^\n||h5.analytics.126.net^\n||h5.hpplay.com.cn^\n||hanlanad.com^\n||hc-ssp.sm.cn^\n||heads-ak.spotify.com.edgesuite.net^\n||heads-fa.spotify.com^\n||hexagon-analytics.com^\n||hlog.bigda.com^\n||hm.baidu.com^\n||hmma.baidu.com^\n||hotupgrade.hpplay.cn^\n||houyi.kkmh.com^\n||hpplay.cdn.cibn.cc^\n||httpdns.bcelive.com^\n||httpdns.ocloud.oppomobile.com^\n||hugelog.fcbox.com^\n||huichuan-mc.sm.cn^\n||huichuan.sm.cn^\n||hw-ot-ad.a.yximgs.com^\n||hw-p2p-pull.video-voip.com^\n||hw-p2p.pull.yximgs.com^\n||hwpub-s01-drcn.cloud.dbankcloud.cn^\n||hybrid.miniapp.taobao.com^\n||hye.comp.360os.com^\n||hyt.comp.360os.com^\n||i.l-new.inmobicdn.net^\n||i.l.inmobicdn.net^\n||iad.apple.com^\n||iad.g.163.com^\n||iadctest.qwapi.com^\n||iadmusicmat.music.126.net^\n||iadsdk.apple.com^\n||iadworkbench.apple.com^\n||ifacelog.iqiyi.com^\n||ifs.tanx.com^\n||ii.gdt.qq.com^\n||image-ad.sm.cn^\n||image.hpplay.cn^\n||images.outbrainimg.com^\n||images.pinduoduo.com^\n||imdns.hpplay.cn^\n||img-c.heytapimage.com^\n||img-x.jd.com^\n||img.adnyg.com.w.kunlungr.com^\n||img2.360buyimg.com^\n||impression-asia.chinaliftoff.io^\n||impression-asia.liftoff.io^\n||impression.appsflyer.com^\n||in.treasuredata.com^\n||ios.bugly.qq.com^\n||iot-eu-logser.realme.com^\n||iot-logser.realme.com^\n||ipv4.kkmh.com^\n||irc.qubiankeji.com^\n||ixav-cse.avlyun.com^\n||iyfbodn.com^\n||janapi.jd.com^\n||jiguang.cn^\n||jpush.cn^\n||jpush.html5.qq.com^\n||jpush.io^\n||jswebcollects.kugou.com^\n||kde.qq.com^\n||kepler.jd.com^\n||kl.67it.com^\n||klink.volceapplog.com^\n||knicks.jd.com^\n||ks-p2p-v2.pull.yximgs.com^\n||ks-p2p.pull.yximgs.com^\n||ks.ferlytc.com^\n||ks.pull.yximgs.com^\n||l6.fancyapi.com^\n||launcher.smart-tv.cn^\n||launcherimg.smart-tv.cn^\n||lf3-ad-union-sdk.pglstatp-toutiao.com^\n||lf6-ad-union-sdk.pglstatp-toutiao.com^\n||lftxali.fancyapi.com^\n||lh3.googleadsserving.cn^\n||litchiads.com^\n||live-api.hktvmall.com^\n||liveats-vod.video.ptqy.gitv.tv^\n||livemonitor.huan.tv^\n||livep.l.aiseet.atianqi.com^\n||lives.l.aiseet.atianqi.com^\n||lives.l.ott.video.qq.com^\n||livewebbs2pcdn.msstatic.com^\n||lm10111.jtrincc.cn^\n||log-api-mn.huxiu.com^\n||log-api.huxiu.com^\n||log-api.pangolin-sdk-toutiao-b.com^\n||log-api.pangolin-sdk-toutiao.com^\n||log-report.com^\n||log-sdk.gifshow.com^\n||log-sdk.ksapisrv.com^\n||log-upload-os.hoyoverse.com^\n||log-upload.mihoyo.com^\n||log-verify.dutils.com^\n||log-verify.hiaiabc.com^\n||log.ad.xiaomi.com^\n||log.aispeech.com^\n||log.amemv.com^\n||log.appstore3.huan.tv^\n||log.avlyun.com^\n||log.byteoversea.com^\n||log.fc.yahoo.com^\n||log.iflytek.com^\n||log.ireader.com^\n||log.kuwo.cn^\n||log.pinterest.com^\n||log.popin.cc^\n||log.stat.kugou.com^\n||log.tagtic.cn^\n||log.tbs.qq.com^\n||log.vcgame.cn^\n||log.web.kugou.com^\n||log.zijieapi.com^\n||log1.cmpassport.com^\n||logbak.hicloud.com^\n||logrcv.aiclk.com^\n||logrcv.yunxish.com^\n||logs.amap.com^\n||logservice.hicloud.com^\n||logservice1.hicloud.com^\n||logtj.kugou.com^\n||logupdate.avlyun.sec.miui.com^\n||logwebs.kugou.com^\n||luimg.baidu.com^\n||m-adnet.hiaiabc.com^\n||m.ad.zhangyue.com^\n||m.atm.youku.com^\n||m.shenshiads.com^\n||mapi.m.jd.com^\n||masdkv6.3g.qq.com^\n||mazu.m.qq.com^\n||mbdlog.iqiyi.com^\n||mcs.zijieapi.com^\n||medproad.com^\n||metrics.data.hicloud.com^\n||metrics.icloud.com^\n||metrics.mzstatic.com^\n||metrics2.data.hicloud.com^\n||metrika.yandex.ru^\n||mi.gdt.qq.com^\n||miav-cse.avlyun.com^\n||micro-xdb.com^\n||mini-prog-drm.vodplayvideo.net^\n||mission-pub.smart-tv.cn^\n||miui-fxcse.avlyun.com^\n||mlog.bigda.com^\n||mnqlog.ldmnq.com^\n||moatads.com^\n||mobads-logs.baidu.com^\n||mobads-pre-config.cdn.bcebos.com^\n||mobads.baidu.com^\n||mobaliyun.res.mgtv.com^\n||mobile.da.mgtv.com^\n||mobilead.kuwo.cn^\n||mobilelog.kugou.com^\n||mobilelog.upqzfile.com^\n||monitor-ads-test.huan.tv^\n||monitor-uu.play.aiseet.atianqi.com^\n||monitor.adxsenmeng.com^\n||monitor.music.qq.com^\n||monitor.uu.qq.com^\n||monsetting.toutiao.com^\n||mores.toponad.com^\n||ms.applovin.com^\n||ms.applvn.com^\n||ms4.applovin.com^\n||ms4.applvn.com^\n||msdk.voiceads.cn^\n||mssdk.volces.com^\n||mssdk.zijieapi.com^\n||mtj.baidu.com^\n||nadvideo2.baidu.com^\n||newvoice.chiq5.smart-tv.cn^\n||nex.163.com^\n||nmetrics.samsung.com^\n||notes-analytics-events.apple.com^\n||notify.sec.miui.com^\n||nsclick.baidu.com^\n||o-sdk.mediation.unity3d.com^\n||o2o.api.xiaomi.com^\n||offerwall.yandex.net^\n||ogads-pa.clients6.google.com^\n||omgmta.play.aiseet.atianqi.com^\n||open-set-api.shenshiads.com^\n||open.e.kuaishou.cn^\n||open.e.kuaishou.com^\n||open.kuaishouzt.com^\n||open.kwaizt.com^\n||open.snssdk.com^\n||openadapi.fancydsp.com^\n||optimus-ads.amap.com^\n||orbit.jd.com^\n||oss.cdn.aiclk.com^\n||oth.eve.mdt.qq.com^\n||oth.str.mdt.qq.com^\n||otheve.play.aiseet.atianqi.com^\n||outlookads.live.com^\n||p.l.qq.com^\n||p.s.360.cn^\n||p1-be-pack-sign.pglstatp-toutiao.com^\n||p1-lm.adkwai.com^\n||p2-be-pack-sign.pglstatp-toutiao.com^\n||p2-lm.adkwai.com^\n||p2pchunk-table.douyucdn.cn^\n||p2plive-ali.douyucdn.cn^\n||p2pupdate.gamedl.qq.com^\n||p2pupgrade.gamedl.qq.com^\n||p3-be-pack-sign.pglstatp-toutiao.com^\n||p3-lm.adkwai.com^\n||p3-tt.byteimg.com^\n||p4-be-pack-sign.pglstatp-toutiao.com^\n||p5-be-pack-sign.pglstatp-toutiao.com^\n||p6-be-pack-sign.pglstatp-toutiao.com^\n||p66-ad.adkwai.com^\n||pagead2.googleadservices.com^\n||pagead2.googlesyndication.com^\n||pangolin-sdk-toutiao-b.com^\n||pay.sboot.cn^\n||pcdn.xmcdn.com^\n||pcdn.yximgs.com^\n||pcm-img.zhls.qq.com^\n||pgdt.gtimg.cn^\n||pgdt.ugdtimg.com^\n||pglstatp-toutiao.com^\n||pig.pupuapi.com^\n||pin.hpplay.cn^\n||pixon.ads-pixiv.net^\n||pkoplink.com^\n||pl.cp31.ott.cibntv.net^\n||plbslog.umeng.com^\n||pms.mb.qq.com^\n||policy.video.ptqy.gitv.tv^\n||pos.baidu.com^\n||prod-mediate-events.applovin.com^\n||promotion-partner.kuaishou.com^\n||proxy.advp.apple.com^\n||public.gdtimg.com^\n||q.i.gdt.qq.com^\n||qcwx.medproad.com^\n||qmlog.baertt.com^\n||qn-cdnfile1pcdn.msstatic.com^\n||qqdata.ab.qq.com^\n||qzs.gdtimg.com^\n||recommend-drcn.hms.dbankcloud.cn^\n||report.tv.kohesport.qq.com^\n||res.hubcloud.com.cn^\n||res1.applovin.com^\n||res1.hubcloud.com.cn^\n||res2.hubcloud.com.cn^\n||res3.hubcloud.com.cn^\n||resolve.umeng.com^\n||review.gdtimg.com^\n||rlog.popin.cc^\n||rms-drcn.platform.dbankcloud.cn^\n||roi.soulapp.cn^\n||rp.hpplay.cn^\n||rps.hpplay.cn^\n||rpt.gdt.qq.com^\n||rt.applovin.com^\n||rt.applvn.com^\n||rtb.adxsenmeng.com^\n||rtb.voiceads.cn^\n||s.amazon-adsystem.com^\n||s1.cdn-sg.advlion.com^\n||s8t.teads.tv^\n||saad.ms.zhangyue.net^\n||saas.hpplay.cn^\n||safebrowsing.urlsec.gg.com^\n||samsung-com.112.2o7.net^\n||samsungads.com^\n||saxysec.com^\n||scs.openspeech.cn^\n||sdk-ab-config.qquanquan.com^\n||sdk-cache.video.ptqy.gitv.tv^\n||sdk.1rtb.net^\n||sdk.ad.smaato.net^\n||sdk.adx.adwangmai.com^\n||sdk.aqyad.com^\n||sdk.beizi.biz^\n||sdk.cferw.com^\n||sdk.e.qq.com^\n||sdk.hzsanjiaomao.com^\n||sdk.markmedia.com.cn^\n||sdk.mobads.adwangmai.com^\n||sdkapi.cloooud.com^\n||sdkapp.uve.weibo.com^\n||sdkauth.hpplay.cn^\n||sdkconf.avlyun.com^\n||sdkconfig.ad.intl.xiaomi.com^\n||sdkconfig.ad.xiaomi.com^\n||sdkconfig.play.aiseet.atianqi.com^\n||sdkconfig.video.qq.com^\n||sdkoptedge.chinanetcenter.com^\n||sdkreport.e.qq.com^\n||sdktmp.hubcloud.com.cn^\n||sdownload.stargame.com^\n||search.ixigua.com^\n||search3-search.ixigua.com^\n||search5-search-hl.ixigua.com^\n||search5-search.ixigua.com^\n||securemetrics.apple.com^\n||securepubads.g.doubleclick.net^\n||sensors-log.dongqiudi.com^\n||sentry.music.163.com^\n||service.changhong.upgrade2.huan.tv^\n||service.vmos.cn^\n||sf16-static.i18n-pglstatp.com^\n||sf3-fe-tos.pglstatp-toutiao.com^\n||shouji.sogou.com^\n||sigmob.com^\n||skdisplay.jd.com^\n||sl.hpplay.cn^\n||slb-p2p.vcloud.ks-live.com^\n||smad.ms.zhangyue.net^\n||smartad.10010.com^\n||smetrics.samsung.com^\n||sms.ads.oppomobile.com^\n||sngmta.qq.com^\n||snowflake.qq.com^\n||ssp.cloooud.com^\n||staging-notify.sec.miui.com^\n||stat.dongqiudi.com^\n||stat.y.qq.com^\n||static-s.iqiyi.com^\n||static.ads-twitter.com^\n||statichf.shihuocdn.cn^\n||statics.woozooo.com^\n||stats.wp.com^\n||statsigapi.net^\n||stg-data.ads.heytapmobi.com^\n||stun.hitv.com^\n||success.ctobsnssdk.com^\n||supply.inmobicdn.net^\n||sv-video.play.aiseet.atianqi.com^\n||syh-imp.cdnjtzy.com^\n||syh.zybang.com^\n||szbdyd.com^\n||t-adx.52qumao.com^\n||t-dsp.pinduoduo.com^\n||t.applovin.com^\n||t.l.qq.com^\n||t.teads.tv^\n||t.track.ad.xiaomi.com^\n||t002.ottcn.com^\n||t1.a.market.xiaomi.com^\n||t1.teads.tv^\n||t2.a.market.xiaomi.com^\n||t2.fancyapi.com^\n||t3.a.market.xiaomi.com^\n||t7z.cupid.ptqy.gitv.tv^\n||tagtic.cn^\n||tangram.e.qq.com^\n||target.ads.jihuoniao.com^\n||tdc.qq.com^\n||tdsdk.cpatrk.net^\n||tdsdk.xdrig.com^\n||telecome.cn^\n||telemetry.sdk.inmobi.com^\n||tencent-dtv.m.cn.miaozhen.com^\n||test.ad.xiaomi.com^\n||test.e.ad.xiaomi.com^\n||tj.b.qq.com^\n||tj.video.qq.com^\n||tk.anythinktech.com^\n||tmead.y.qq.com^\n||tmeadcomm.y.qq.com^\n||tmfmazu-wangka.m.qq.com^\n||tmfmazu.m.qq.com^\n||tmfsdk.m.qq.com^\n||tmfsdktcpv4.m.qq.com^\n||tnc0-aliec2.zijieapi.com^\n||tnc0-alisc1.zijieapi.com^\n||tnc0-bjlgy.zijieapi.com^\n||tnc3-aliec1.toutiaoapi.com^\n||tnc3-aliec2.bytedance.com^\n||tnc3-aliec2.snssdk.com^\n||tnc3-aliec2.toutiaoapi.com^\n||tnc3-aliec2.zijieapi.com^\n||tnc3-alisc1.bytedance.com^\n||tnc3-alisc1.snssdk.com^\n||tnc3-alisc1.toutiaoapi.com^\n||tnc3-alisc1.zijieapi.com^\n||tnc3-alisc2.zijieapi.com^\n||tnc3-bjlgy.bytedance.com^\n||tnc3-bjlgy.snssdk.com^\n||tnc3-bjlgy.toutiaoapi.com^\n||tnc3-bjlgy.zijieapi.com^\n||toblog.ctobsnssdk.com^\n||tpa-hcdn.iqiyi.com^\n||tpc.googlesyndication-cn.com^\n||tpc.googlesyndication.com^\n||tr-asia.adsmoloco.com^\n||trace.aqyad.com^\n||trace.qq.com^\n||tracelog-debug.aiclk.com^\n||tracelog-debug.qquanquan.com^\n||tracelog-debug.yunxish.com^\n||track.lc.quark.cn^\n||track.uc.cn^\n||tracker.ai.xiaomi.com^\n||tracker.gitee.com^\n||tracking.miui.com^\n||tracking.rus.miui.com^\n||tuiguang.meitu.com^\n||tvapp.hpplay.cn^\n||tvuser-ch.cedock.com^\n||tx-ad.a.yximgs.com^\n||tx-kmpaudio.pull.yximgs.com^\n||tx-p2p-pull.live-voip.com^\n||tx-p2p-v2.pull.yximgs.com^\n||tytx.m.cn.miaozhen.com^\n||tz.sec.xiaomi.com^\n||uapi.ads.heytapmobi.com^\n||udc.yahoo.com^\n||udcm.yahoo.com^\n||uedas.qidian.com^\n||uhabo.com^\n||ulog-sdk.gifshow.com^\n||ulogjs.gifshow.com^\n||ulogs.umeng.com^\n||ulogs.umengcloud.com^\n||umengacs.m.taobao.com^\n||umengjmacs.m.taobao.com^\n||umini.shujupie.com^\n||union-mlog.bigda.com^\n||union.baidu.cn^\n||union.baidu.com^\n||update.avlyun.sec.miui.com^\n||update.lejiao.tv^\n||update0.aiclk.com^\n||upgrade-update.hismarttv.com^\n||uranus.jd.com^\n||us.l.qq.com^\n||usr-api.aiclk.com^\n||utoken.umeng.com^\n||v.110route.cn^\n||v.adintl.cn^\n||v.adx.hubcloud.com.cn^\n||v1-ad.video.yximgs.com^\n||v2-ad.video.yximgs.com^\n||v2-api-channel-launcher.hismarttv.com^\n||v2.gdt.qq.com^\n||v2mi.gdt.qq.com^\n||v3-ad.video.yximgs.com^\n||v3.gdt.qq.com^\n||vd6.l.qq.com^\n||vi.l.qq.com^\n||video-ad.sm.cn^\n||video-dsp.pddpic.com^\n||video.dispatch.tc.qq.com^\n||video.market.xiaomi.com^\n||vipauth.hpplay.cn^\n||vipgslb.hpplay.cn^\n||virusinfo-cloudscan-cn.heytapmobi.com^\n||vlive.qqvideo.tc.qq.com^\n||volc.bj.ad.track.66mobi.com^\n||vungle.com^\n||w.l.qq.com^\n||w1.askwai.com^\n||w1.gskwai.com^\n||w1.kskwai.com^\n||watson.microsoft.com^\n||watson.telemetry.microsoft.com^\n||wcp.taobao.com^\n||weather-analytics-events.apple.com^\n||weather-community-drcn.weather.dbankcloud.cn^\n||webstat.qiumibao.com^\n||webview.unityads.unity3d.com^\n||widgets.outbrain.com^\n||widgets.pinterest.com^\n||wildcard.moatads.com.edgekey.net^\n||win.gdt.qq.com^\n||wn.x.jd.com^\n||ws-keyboard.shouji.sogou.com^\n||ws.sj.qq.com^\n||wv.inner-active.mobi^\n||wwads.cn^\n||wxa.wxs.qq.com^\n||wxaintpcos.wxqcloud.qq.com.cn^\n||wximg.wxs.qq.com^\n||wxsmw.wxs.qq.com^\n||wxsnsad.tc.qq.com^\n||wxsnsdy.tc.qq.com^\n||wxsnsdy.wxs.qq.com^\n||wxsnsdythumb.wxs.qq.com^\n||xc.gdt.qq.com^\n||xcx.dcad01.com^\n||xiaomi-dtv.m.cn.miaozhen.com^\n||xlog.jd.com^\n||xlviiirdr.com^\n||xlviirdr.com^\n||xycdn.com^\n||yk-ssp-ad.cp31.ott.cibntv.net^\n||yk-ssp.ad.youku.com^\n||ykad-data.cp31.ott.cibntv.net^\n||ykad-data.youku.com^\n||ykad-gateway.youku.com^\n||youku-acs.m.taobao.com^\n||ytx-file.110route.cn^\n||zeus.ad.xiaomi.com^\n||zhihu-web-analytics.zhihu.com^\n||zjres-ad.kajicam.com^\n||zxid-m.mobileservice.cn^\n||1rtb.com^\n||a.market.xiaomi.com^\n||ad.gameley.com^\n||adintl.cn^\n||adx.adwangmai.com^\n||data.hicloud.com^\n||log.aliyuncs.com^\n||ubixioe.com^\n||vlion.cn^\n||yfanads.com^\n||zhangyuyidong.cn^\n*-ad.sm.cn*\n*-ad.video.yximgs.com*\n*-ad.wtzw.com*\n*-be-pack-sign.pglstatp-toutiao.com*\n*-normal.zijieapi.com*\n"
  },
  {
    "path": "src/customize.sh",
    "content": "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 language\nlocale=$(getprop persist.sys.locale || getprop ro.product.locale || getprop persist.sys.language)\n\n# if the system language is English, set language to English\nif echo \"$locale\" | grep -qi \"en\"; then\n  language=\"en\"\nfi\n\nfunction info() {\n  [ \"$language\" = \"en\" ] && ui_print \"$1\" || ui_print \"$2\"\n}\n\nfunction error() {\n  [ \"$language\" = \"en\" ] && abort \"$1\" || abort \"$2\"\n}\n\ninfo \"- 🚀 Installing AdGuardHome for $ARCH\" \"- 🚀 开始安装 AdGuardHome for $ARCH\"\n\nAGH_DIR=\"/data/adb/agh\"\nBIN_DIR=\"$AGH_DIR/bin\"\nSCRIPT_DIR=\"$AGH_DIR/scripts\"\nPID_FILE=\"$AGH_DIR/bin/agh.pid\"\n\ninfo \"- 📦 Extracting module basic files...\" \"- 📦 解压模块基本文件...\"\nunzip -o \"$ZIPFILE\" \"action.sh\" -d \"$MODPATH\" >/dev/null 2>&1 \nunzip -o \"$ZIPFILE\" \"module.prop\" -d \"$MODPATH\" >/dev/null 2>&1\nunzip -o \"$ZIPFILE\" \"service.sh\" -d \"$MODPATH\" >/dev/null 2>&1\nunzip -o \"$ZIPFILE\" \"uninstall.sh\" -d \"$MODPATH\" >/dev/null 2>&1\nunzip -o \"$ZIPFILE\" \"webroot/*\" -d \"$MODPATH\" >/dev/null 2>&1\n\nextract_keep_config() {\n  info \"- 🌈 Keeping old configuration files...\" \"- 🌈 保留原来的配置文件...\"\n  info \"- 📜 Extracting script files...\" \"- 📜 正在解压脚本文件...\"\n  unzip -o \"$ZIPFILE\" \"scripts/*\" -d $AGH_DIR >/dev/null 2>&1 || {\n    error \"- ❌ Failed to extract scripts!\" \"- ❌ 解压脚本文件失败！\"\n  }\n  info \"- 🛠️ Extracting binary files except configuration...\" \"- 🛠️ 正在解压二进制文件（不包括配置文件）...\"\n  unzip -o \"$ZIPFILE\" \"bin/*\" -x \"bin/AdGuardHome.yaml\" -d $AGH_DIR >/dev/null 2>&1 || {\n    error \"- ❌ Failed to extract binary files!\" \"- ❌ 解压二进制文件失败！\"\n  }\n  info \"- 🚫 Skipping configuration file extraction...\" \"- 🚫 跳过解压配置文件...\"\n}\n\nextract_no_config() {\n  info \"- 💾 Backing up old configuration files with .bak extension...\" \"- 💾 使用 .bak 扩展名备份旧配置文件...\"\n  [ -f \"$AGH_DIR/settings.conf\" ] && mv \"$AGH_DIR/settings.conf\" \"$AGH_DIR/settings.conf.bak\"\n  [ -f \"$AGH_DIR/bin/AdGuardHome.yaml\" ] && mv \"$AGH_DIR/bin/AdGuardHome.yaml\" \"$AGH_DIR/bin/AdGuardHome.yaml.bak\"\n  extract_all\n}\n\nextract_all() {\n  info \"- 🌟 Extracting script files...\" \"- 🌟 正在解压脚本文件...\"\n  unzip -o \"$ZIPFILE\" \"scripts/*\" -d $AGH_DIR >/dev/null 2>&1 || {\n    error \"- ❌ Failed to extract scripts\" \"- ❌ 解压脚本文件失败\"\n  }\n  info \"- 🛠️ Extracting binary files...\" \"- 🛠️ 正在解压二进制文件...\"\n  unzip -o \"$ZIPFILE\" \"bin/*\" -d $AGH_DIR >/dev/null 2>&1 || {\n    error \"- ❌ Failed to extract binary files\" \"- ❌ 解压二进制文件失败\"\n  }\n  info \"- 📜 Extracting configuration files...\" \"- 📜 正在解压配置文件...\"\n  unzip -o \"$ZIPFILE\" \"settings.conf\" -d $AGH_DIR >/dev/null 2>&1 || {\n    error \"- ❌ Failed to extract configuration files\" \"- ❌ 解压配置文件失败\"\n  }\n}\n\nif [ -d \"$AGH_DIR\" ]; then\n  info \"- ⏹️ Found old version, stopping all AdGuardHome processes...\" \"- ⏹️ 发现旧版模块，正在停止所有 AdGuardHome 进程...\"\n  pkill -f \"AdGuardHome\" || pkill -9 -f \"AdGuardHome\" \n  info \"- 🔄 Do you want to keep the old configuration? (If not, it will be automatically backed up)\" \"- 🔄 是否保留原来的配置文件？（若不保留则自动备份）\"\n  info \"- 🔊 (Volume Up = Yes, Volume Down = No, 30s no input = Yes)\" \"- 🔊 （音量上键 = 是, 音量下键 = 否，30秒无操作 = 是）\"\n  START_TIME=$(date +%s)\n  while true; do\n    NOW_TIME=$(date +%s)\n    timeout 1 getevent -lc 1 2>&1 | grep KEY_VOLUME >\"$TMPDIR/events\"\n    if [ $((NOW_TIME - START_TIME)) -gt 29 ]; then\n      info \"- ⏰ No input detected after 30 seconds, defaulting to keep old configuration.\" \"- ⏰ 30秒无输入，默认保留原配置。\"\n      extract_keep_config\n      break\n    elif $(cat $TMPDIR/events | grep -q KEY_VOLUMEUP); then\n      extract_keep_config\n      break\n    elif $(cat $TMPDIR/events | grep -q KEY_VOLUMEDOWN); then\n      extract_no_config\n      break\n    fi\n  done\nelse\n  info \"- 📦 First time installation, extracting files...\" \"- 📦 第一次安装，正在解压文件...\"\n  mkdir -p \"$AGH_DIR\" \"$BIN_DIR\" \"$SCRIPT_DIR\"\n  extract_all\nfi\n\ninfo \"- 🔐 Setting permissions...\" \"- 🔐 设置权限...\"\n\nchmod +x \"$BIN_DIR/AdGuardHome\"\nchown root:net_raw \"$BIN_DIR/AdGuardHome\"\n\nchmod +x \"$SCRIPT_DIR\"/*.sh \"$MODPATH\"/*.sh\n\ninfo \"- 🎉 Installation completed, please reboot.\" \"- 🎉 安装完成，请重启设备。\"\n"
  },
  {
    "path": "src/module.prop",
    "content": "id=AdGuardHome\nname=AdGuardHome for Root\nversion=20260419\nversionCode=49\nauthor=twoone3\ndescription=none\nupdateJson=https://raw.githubusercontent.com/twoone-3/AdGuardHomeForRoot/main/version.json"
  },
  {
    "path": "src/scripts/base.sh",
    "content": "# add busybox to PATH\n[ -d \"/data/adb/magisk\" ] && export PATH=\"/data/adb/magisk:$PATH\"\n[ -d \"/data/adb/ksu/bin\" ] && export PATH=\"/data/adb/ksu/bin:$PATH\"\n[ -d \"/data/adb/ap/bin\" ] && export PATH=\"/data/adb/ap/bin:$PATH\"\n\n# most of the users are Chinese, so set default language to Chinese\nlanguage=\"zh\"\n\n# try to get the system language\nlocale=$(getprop persist.sys.locale || getprop ro.product.locale || getprop persist.sys.language)\n\n# if the system language is English, set language to English\nif echo \"$locale\" | grep -qi \"en\"; then\n  language=\"en\"\nfi\n\nfunction log() {\n  local timestamp=$(date \"+%Y-%m-%d %H:%M:%S\")\n  local str\n  [ \"$language\" = \"en\" ] && str=\"$timestamp $1\" || str=\"$timestamp $2\"\n  echo \"$str\" | tee -a \"$AGH_DIR/history.log\"\n}\n\nfunction update_description() {\n  local description\n  [ \"$language\" = \"en\" ] && description=\"$1\" || description=\"$2\"\n  sed -i \"/^description=/c\\description=$description\" \"$MOD_PATH/module.prop\"\n}"
  },
  {
    "path": "src/scripts/debug.sh",
    "content": "#!/system/bin/sh\n\nAGH_DIR=\"/data/adb/agh\"\nLOG=\"$AGH_DIR/debug.log\"\n\n{\n  echo \"==== AdGuardHome Debug Log ====\"\n  date\n  echo\n\n  echo \"== System Info ==\"\n  uname -a\n  echo \"Android Version: $(getprop ro.build.version.release)\"\n  echo \"Device: $(getprop ro.product.model)\"\n  echo \"Architecture: $(uname -m)\"\n  echo\n\n  echo \"== AdGuardHome Version ==\"\n  if [ -f \"$AGH_DIR/bin/AdGuardHome\" ]; then\n    \"$AGH_DIR/bin/AdGuardHome\" --version\n  else\n    echo \"AdGuardHome binary not found\"\n  fi\n  echo\n\n  echo \"== Root Method ==\"\n  if [ -d \"/data/adb/magisk\" ]; then\n    echo \"Magisk\"\n  elif [ -d \"/data/adb/ksu\" ]; then\n    echo \"KernelSU\"\n  elif [ -d \"/data/adb/ap\" ]; then\n    echo \"APatch\"\n  else\n    echo \"Unknown\"\n  fi\n  echo\n\n  echo \"== BusyBox Version ==\"\n  [ -d \"/data/adb/magisk\" ] && export PATH=\"/data/adb/magisk:$PATH\"\n  [ -d \"/data/adb/ksu/bin\" ] && export PATH=\"/data/adb/ksu/bin:$PATH\"\n  [ -d \"/data/adb/ap/bin\" ] && export PATH=\"/data/adb/ap/bin:$PATH\"\n  if command -v busybox >/dev/null 2>&1; then\n    busybox --version\n  else\n    echo \"BusyBox not found\"\n  fi\n\n  echo \"== AGH Directory Listing ==\"\n  ls -lR \"$AGH_DIR\"\n  echo\n\n  echo \"== AGH Bin Log (last 30 lines) ==\"\n  tail -n 30 \"$AGH_DIR/bin.log\" 2>/dev/null\n  echo\n\n  echo \"== AGH Settings ==\"\n  cat \"$AGH_DIR/settings.conf\" 2>/dev/null\n  echo\n\n  echo \"== AGH PID File ==\"\n  cat \"$AGH_DIR/bin/agh.pid\" 2>/dev/null\n  echo\n\n  echo \"== Running Processes (AdGuardHome) ==\"\n  ps -A | grep AdGuardHome\n  echo\n\n  echo \"== iptables -t nat -L -n -v ==\"\n  iptables -t nat -L -n -v\n  echo\n\n  echo \"== ip6tables -t filter -L -n -v ==\"\n  ip6tables -t filter -L -n -v\n  echo\n\n  echo \"== Network Interfaces ==\"\n  ip addr\n  echo\n\n} >\"$LOG\" 2>&1\n\necho \"Debug info collected in $LOG\"\n"
  },
  {
    "path": "src/scripts/inotify.sh",
    "content": ". /data/adb/agh/settings.conf\n\nreadonly EVENTS=$1\nreadonly MONITOR_DIR=$2\nreadonly MONITOR_FILE=$3\n\nif [ \"${MONITOR_FILE}\" = \"disable\" ]; then\n  if [ \"${EVENTS}\" = \"d\" ]; then\n    $SCRIPT_DIR/tool.sh start\n  elif [ \"${EVENTS}\" = \"n\" ]; then\n    $SCRIPT_DIR/tool.sh stop\n  fi\nfi\n"
  },
  {
    "path": "src/scripts/iptables.sh",
    "content": ". /data/adb/agh/settings.conf\n. /data/adb/agh/scripts/base.sh\n\niptables_w=\"iptables -w 64\"\nip6tables_w=\"ip6tables -w 64\"\n\ncheck_ipv6_nat_support() {\n  if $ip6tables_w -t nat -L >/dev/null 2>&1; then\n    return 0\n  else\n    return 1\n  fi\n}\n\nenable_iptables() {\n  if $iptables_w -t nat -L ADGUARD_REDIRECT_DNS >/dev/null 2>&1; then\n    log \"ADGUARD_REDIRECT_DNS chain already exists\" \"ADGUARD_REDIRECT_DNS 链已经存在\"\n    return 0\n  fi\n\n  log \"Creating ADGUARD_REDIRECT_DNS chain and adding rules\" \"创建 ADGUARD_REDIRECT_DNS 链并添加规则\"\n  $iptables_w -t nat -N ADGUARD_REDIRECT_DNS || return 1\n  $iptables_w -t nat -A ADGUARD_REDIRECT_DNS -m owner --uid-owner $adg_user --gid-owner $adg_group -j RETURN || return 1\n\n  for subnet in $ignore_dest_list; do\n    $iptables_w -t nat -A ADGUARD_REDIRECT_DNS -d $subnet -j RETURN || return 1\n  done\n\n  for subnet in $ignore_src_list; do\n    $iptables_w -t nat -A ADGUARD_REDIRECT_DNS -s $subnet -j RETURN || return 1\n  done\n\n  $iptables_w -t nat -A ADGUARD_REDIRECT_DNS -p udp --dport 53 -j REDIRECT --to-ports $redir_port || return 1\n  $iptables_w -t nat -A ADGUARD_REDIRECT_DNS -p tcp --dport 53 -j REDIRECT --to-ports $redir_port || return 1\n  $iptables_w -t nat -I OUTPUT -j ADGUARD_REDIRECT_DNS || return 1\n\n  log \"Applied iptables rules successfully\" \"成功应用 iptables 规则\"\n}\n\ndisable_iptables() {\n  if ! $iptables_w -t nat -L ADGUARD_REDIRECT_DNS >/dev/null 2>&1; then\n    log \"ADGUARD_REDIRECT_DNS chain does not exist\" \"ADGUARD_REDIRECT_DNS 链不存在\"\n    return 0\n  fi\n\n  log \"Deleting ADGUARD_REDIRECT_DNS chain and rules\" \"删除 ADGUARD_REDIRECT_DNS 链及规则\"\n  $iptables_w -t nat -D OUTPUT -j ADGUARD_REDIRECT_DNS || return 1\n  $iptables_w -t nat -F ADGUARD_REDIRECT_DNS || return 1\n  $iptables_w -t nat -X ADGUARD_REDIRECT_DNS || return 1\n}\n\nadd_block_ipv6_dns() {\n  if $ip6tables_w -t filter -L ADGUARD_BLOCK_DNS >/dev/null 2>&1; then\n    log \"ADGUARD_BLOCK_DNS chain already exists\" \"ADGUARD_BLOCK_DNS 链已经存在\"\n    return 0\n  fi\n\n  log \"Creating ADGUARD_BLOCK_DNS chain and adding rules\" \"创建 ADGUARD_BLOCK_DNS 链并添加规则\"\n  $ip6tables_w -t filter -N ADGUARD_BLOCK_DNS || return 1\n  $ip6tables_w -t filter -A ADGUARD_BLOCK_DNS -p udp --dport 53 -j DROP || return 1\n  $ip6tables_w -t filter -A ADGUARD_BLOCK_DNS -p tcp --dport 53 -j DROP || return 1\n  $ip6tables_w -t filter -I OUTPUT -j ADGUARD_BLOCK_DNS || return 1\n\n  log \"Applied ipv6 iptables rules successfully\" \"成功应用 ipv6 iptables 规则\"\n}\n\ndel_block_ipv6_dns() {\n  if ! $ip6tables_w -t filter -L ADGUARD_BLOCK_DNS >/dev/null 2>&1; then\n    log \"ADGUARD_BLOCK_DNS chain does not exist\" \"ADGUARD_BLOCK_DNS 链不存在\"\n    return 0\n  fi\n\n  log \"Deleting ADGUARD_BLOCK_DNS chain and rules\" \"删除 ADGUARD_BLOCK_DNS 链及规则\"\n  $ip6tables_w -t filter -F ADGUARD_BLOCK_DNS || return 1\n  $ip6tables_w -t filter -D OUTPUT -j ADGUARD_BLOCK_DNS || return 1\n  $ip6tables_w -t filter -X ADGUARD_BLOCK_DNS || return 1\n}\n\nenable_ipv6_iptables() {\n  if ! check_ipv6_nat_support; then\n    log \"IPv6 NAT is not supported, skipping IPv6 DNS hijack\" \"IPv6 NAT 不支持，跳过 IPv6 DNS 劫持\"\n    return 0\n  fi\n\n  if $ip6tables_w -t nat -L ADGUARD_REDIRECT_DNS6 >/dev/null 2>&1; then\n    log \"ADGUARD_REDIRECT_DNS6 chain already exists\" \"ADGUARD_REDIRECT_DNS6 链已经存在\"\n    return 0\n  fi\n\n  log \"Creating ADGUARD_REDIRECT_DNS6 chain and adding rules\" \"创建 ADGUARD_REDIRECT_DNS6 链并添加规则\"\n  $ip6tables_w -t nat -N ADGUARD_REDIRECT_DNS6 || return 1\n  $ip6tables_w -t nat -A ADGUARD_REDIRECT_DNS6 -m owner --uid-owner $adg_user --gid-owner $adg_group -j RETURN || return 1\n\n  for subnet in $ignore_dest_list; do\n    $ip6tables_w -t nat -A ADGUARD_REDIRECT_DNS6 -d $subnet -j RETURN || return 1\n  done\n\n  for subnet in $ignore_src_list; do\n    $ip6tables_w -t nat -A ADGUARD_REDIRECT_DNS6 -s $subnet -j RETURN || return 1\n  done\n\n  $ip6tables_w -t nat -A ADGUARD_REDIRECT_DNS6 -p udp --dport 53 -j REDIRECT --to-ports $redir_port || return 1\n  $ip6tables_w -t nat -A ADGUARD_REDIRECT_DNS6 -p tcp --dport 53 -j REDIRECT --to-ports $redir_port || return 1\n  $ip6tables_w -t nat -I OUTPUT -j ADGUARD_REDIRECT_DNS6 || return 1\n\n  log \"Applied ipv6 iptables rules successfully\" \"成功应用 ipv6 iptables 规则\"\n}\n\ndisable_ipv6_iptables() {\n  if ! check_ipv6_nat_support; then\n    log \"IPv6 NAT is not supported, skipping IPv6 DNS hijack cleanup\" \"IPv6 NAT 不支持，跳过 IPv6 DNS 劫持清理\"\n    return 0\n  fi\n\n  if ! $ip6tables_w -t nat -L ADGUARD_REDIRECT_DNS6 >/dev/null 2>&1; then\n    log \"ADGUARD_REDIRECT_DNS6 chain does not exist\" \"ADGUARD_REDIRECT_DNS6 链不存在\"\n    return 0\n  fi\n\n  log \"Deleting ADGUARD_REDIRECT_DNS6 chain and rules\" \"删除 ADGUARD_REDIRECT_DNS6 链及规则\"\n  $ip6tables_w -t nat -D OUTPUT -j ADGUARD_REDIRECT_DNS6 || return 1\n  $ip6tables_w -t nat -F ADGUARD_REDIRECT_DNS6 || return 1\n  $ip6tables_w -t nat -X ADGUARD_REDIRECT_DNS6 || return 1\n}\n\ncase \"$1\" in\nenable)\n  log \"Enabling iptables and ipv6 DNS blocking if configured\" \"启用 iptables\"\n  enable_iptables || exit 1\n  \n  if [ \"$block_ipv6_dns\" = true ]; then\n    log \"IPv6 DNS mode: block (DROP IPv6 DNS traffic)\" \"IPv6 DNS 模式: block (丢弃 IPv6 DNS 流量)\"\n    add_block_ipv6_dns || exit 1\n  else\n    log \"IPv6 DNS mode: hijack (NAT REDIRECT to AdGuard Home)\" \"IPv6 DNS 模式: hijack (劫持 IPv6 到 AdGuard Home)\"\n    enable_ipv6_iptables || exit 1\n  fi\n  ;;\ndisable)\n  log \"Disabling iptables and ipv6 DNS blocking\" \"禁用 iptables 和 ipv6 DNS 阻断\"\n  disable_iptables || exit 1\n  \n  del_block_ipv6_dns || exit 1\n  disable_ipv6_iptables || exit 1\n  ;;\n*)\n  echo \"Usage: $0 {enable|disable}\"\n  exit 1\n  ;;\nesac\n"
  },
  {
    "path": "src/scripts/tool.sh",
    "content": ". /data/adb/agh/settings.conf\n. /data/adb/agh/scripts/base.sh\n\nstart_adguardhome() {\n  # check if AdGuardHome is already running\n  if [ -f \"$PID_FILE\" ] && ps | grep -w \"$adg_pid\" | grep -q \"AdGuardHome\"; then\n    log \"AdGuardHome is already running\" \"AdGuardHome 已经在运行\"\n    exit 0\n  fi\n\n  # to fix https://github.com/AdguardTeam/AdGuardHome/issues/7002\n  export SSL_CERT_DIR=\"/system/etc/security/cacerts/\"\n  # set timezone to Shanghai\n  export TZ=\"Asia/Shanghai\"\n\n  # backup old log and overwrite new log\n  if [ -f \"$AGH_DIR/bin.log\" ]; then\n    mv \"$AGH_DIR/bin.log\" \"$AGH_DIR/bin.log.bak\"\n  fi\n\n  # run binary\n  busybox setuidgid \"$adg_user:$adg_group\" \"$BIN_DIR/AdGuardHome\" >\"$AGH_DIR/bin.log\" 2>&1 &\n  adg_pid=$!\n\n  # check if AdGuardHome started successfully\n  if ps | grep -w \"$adg_pid\" | grep -q \"AdGuardHome\"; then\n    echo \"$adg_pid\" >\"$PID_FILE\"\n    # check if iptables is enabled\n    if [ \"$enable_iptables\" = true ]; then\n      $SCRIPT_DIR/iptables.sh enable\n      log \"🥰 started PID: $adg_pid iptables: enabled\" \"🥰 启动成功 PID: $adg_pid iptables 已启用\"\n      update_description \"🥰 Started PID: $adg_pid iptables: enabled\" \"🥰 启动成功 PID: $adg_pid iptables 已启用\"\n    else\n      log \"🥰 started PID: $adg_pid iptables: disabled\" \"🥰 启动成功 PID: $adg_pid iptables 已禁用\"\n      update_description \"🥰 Started PID: $adg_pid iptables: disabled\" \"🥰 启动成功 PID: $adg_pid iptables 已禁用\"\n    fi\n  else\n    log \"😭 Error occurred, check logs for details\" \"😭 出现错误，请检查日志以获取详细信息\"\n    update_description \"😭 Error occurred, check logs for details\" \"😭 出现错误，请检查日志以获取详细信息\"\n    $SCRIPT_DIR/debug.sh\n    exit 1\n  fi\n}\n\nstop_adguardhome() {\n  if [ -f \"$PID_FILE\" ]; then\n    pid=$(cat \"$PID_FILE\")\n    kill $pid || kill -9 $pid\n    rm \"$PID_FILE\"\n    log \"AdGuardHome stopped (PID: $pid)\" \"AdGuardHome 已停止 (PID: $pid)\"\n  else\n    pkill -f \"AdGuardHome\" || pkill -9 -f \"AdGuardHome\"\n    log \"AdGuardHome force stopped\" \"AdGuardHome 强制停止\"\n  fi\n  update_description \"❌ Stopped\" \"❌ 已停止\"\n  $SCRIPT_DIR/iptables.sh disable\n}\n\ntoggle_adguardhome() {\n  if [ -f \"$PID_FILE\" ] && ps | grep -w \"$(cat $PID_FILE)\" | grep -q \"AdGuardHome\"; then\n    stop_adguardhome\n  else\n    start_adguardhome\n  fi\n}\n\ncase \"$1\" in\nstart)\n  start_adguardhome\n  ;;\nstop)\n  stop_adguardhome\n  ;;\ntoggle)\n  toggle_adguardhome\n  ;;\n*)\n  echo \"Usage: $0 {start|stop|toggle}\"\n  exit 1\n  ;;\nesac\n"
  },
  {
    "path": "src/service.sh",
    "content": "until [ $(getprop init.svc.bootanim) = \"stopped\" ]; do\n  sleep 12\ndone\n\n/data/adb/agh/scripts/tool.sh start\n\ninotifyd /data/adb/agh/scripts/inotify.sh /data/adb/modules/AdGuardHome:d,n &\n"
  },
  {
    "path": "src/settings.conf",
    "content": "# 是否启用内置的 iptables 规则\n# Whether to enable the built-in iptables rules\nenable_iptables=true\n\n# 阻断 ipv6 的 DNS 请求，仅在 enable_iptables=true 时生效\n# Block DNS requests for ipv6, only effective when enable_iptables=true\nblock_ipv6_dns=true\n\n# 重定向端口，请与 AdGuardHome 的设置保持一致\n# Redirect port, please keep it consistent with AdGuardHome settings\nredir_port=5591\n\n# 用户组和用户，用于绕过 AdGuardHome 本身\n# User group and user, used to bypass AdGuardHome itself\nadg_user=root\nadg_group=net_raw\n\n# 绕过目的地址列表，用空格分隔\n# list of destination addresses to bypass, separated by spaces\nignore_dest_list=\"\"\n\n# 绕过源地址列表，用空格分隔\n# list of source addresses to bypass, separated by spaces\nignore_src_list=\"\"\n\n# 文件路径，请勿修改\n# File paths, do not modify\nreadonly AGH_DIR=\"/data/adb/agh\"\nreadonly BIN_DIR=\"$AGH_DIR/bin\"\nreadonly SCRIPT_DIR=\"$AGH_DIR/scripts\"\nreadonly PID_FILE=\"$AGH_DIR/bin/agh.pid\"\nreadonly MOD_PATH=\"/data/adb/modules/AdGuardHome\"\n"
  },
  {
    "path": "src/uninstall.sh",
    "content": "[ -d \"/data/adb/agh\" ] && rm -rf \"/data/adb/agh\"\n"
  },
  {
    "path": "src/webroot/app.js",
    "content": "/**\n * AdGuardHome for Root - WebUI Application\n * \n * KernelSU API: ksu.exec(cmd) returns stdout string synchronously\n * KernelSU API: ksu.toast(msg) shows a toast\n */\n\n// ==================== Module API Layer ====================\nvar ModuleAPI = {\n  _api: null,\n\n  init: function() {\n    if (typeof ksu !== 'undefined' && typeof ksu.exec === 'function') {\n      this._api = ksu;\n    } else if (typeof ap !== 'undefined' && typeof ap.exec === 'function') {\n      this._api = ap;\n    }\n  },\n\n  isAvailable: function() {\n    return this._api !== null;\n  },\n\n  /**\n   * Execute shell command SYNCHRONOUSLY.\n   * ksu.exec(cmd) returns stdout string directly.\n   * Returns { errno, stdout }\n   */\n  exec: function(command) {\n    if (!this._api) {\n      return { errno: -1, stdout: '' };\n    }\n    try {\n      var result = this._api.exec(command);\n      return {\n        errno: 0,\n        stdout: (result || '').replace(/\\r/g, '')\n      };\n    } catch (e) {\n      return { errno: -1, stdout: '' };\n    }\n  },\n\n  toast: function(msg) {\n    if (this._api && typeof this._api.toast === 'function') {\n      try { this._api.toast(msg); } catch (e) {}\n    }\n  }\n};\n\nModuleAPI.init();\n\n// ==================== Utility Functions ====================\nfunction showToast(message, type, duration) {\n  type = type || 'info';\n  duration = duration || 3000;\n  var container = document.getElementById('toastContainer');\n  var toastEl = document.createElement('div');\n  toastEl.className = 'toast ' + type;\n  toastEl.textContent = message;\n  container.appendChild(toastEl);\n  setTimeout(function() {\n    toastEl.style.animation = 'toastOut 0.3s ease forwards';\n    setTimeout(function() { toastEl.remove(); }, 300);\n  }, duration);\n}\n\nfunction showLoading(show) {\n  document.getElementById('loadingOverlay').style.display = show ? 'flex' : 'none';\n}\n\n// ==================== Paths ====================\nvar AGH_DIR = '/data/adb/agh';\nvar CONF_FILE = AGH_DIR + '/settings.conf';\nvar PID_FILE = AGH_DIR + '/bin/agh.pid';\nvar LOG_FILE = AGH_DIR + '/history.log';\nvar SCRIPT_DIR = AGH_DIR + '/scripts';\nvar BIN_DIR = AGH_DIR + '/bin';\nvar MODULE_PROP = '/data/adb/modules/AdGuardHome/module.prop';\n\n// ==================== State ====================\nvar currentSettings = {};\nvar settingsChanged = false;\nvar isRunning = false;\nvar webPort = '3000';\nvar webUser = 'root';\nvar webPassword = 'root';\nvar statsFailCount = 0;\nvar statsAuthVisible = false;\n\n// ==================== Tab / Page Switching ====================\nvar currentPage = 0;\nvar pageCount = 2;\n\nfunction switchTab(index) {\n  if (index < 0 || index >= pageCount) return;\n  currentPage = index;\n\n  var container = document.getElementById('pagesContainer');\n  container.classList.remove('no-transition');\n  container.style.transform = 'translateX(' + (-index * 50) + '%)';\n\n  // Update floating tab active state\n  var tabs = document.querySelectorAll('.floating-tab-item');\n  for (var i = 0; i < tabs.length; i++) {\n    if (i === index) {\n      tabs[i].classList.add('active');\n    } else {\n      tabs[i].classList.remove('active');\n    }\n  }\n\n  // Always show floating tab when switching pages\n  var floatingTab = document.getElementById('floatingTab');\n  if (floatingTab) {\n    floatingTab.classList.remove('tab-hidden');\n  }\n}\n\n// ==================== Touch Swipe Support ====================\n(function() {\n  var startX = 0;\n  var startY = 0;\n  var deltaX = 0;\n  var swiping = false;\n  var container = null;\n\n  function getContainer() {\n    if (!container) {\n      container = document.getElementById('pagesContainer');\n    }\n    return container;\n  }\n\n  function onTouchStart(e) {\n    var tag = e.target.tagName.toLowerCase();\n    if (tag === 'input' || tag === 'textarea' || tag === 'select' || tag === 'button') return;\n\n    startX = e.touches[0].clientX;\n    startY = e.touches[0].clientY;\n    deltaX = 0;\n    swiping = false;\n  }\n\n  function onTouchMove(e) {\n    if (!startX && !startY) return;\n\n    var dx = e.touches[0].clientX - startX;\n    var dy = e.touches[0].clientY - startY;\n\n    if (!swiping) {\n      if (Math.abs(dx) > Math.abs(dy) && Math.abs(dx) > 10) {\n        swiping = true;\n        getContainer().classList.add('no-transition');\n      } else {\n        return;\n      }\n    }\n\n    if (Math.abs(dx) > Math.abs(dy)) {\n      e.preventDefault();\n    }\n\n    deltaX = dx;\n\n    var pageWidth = window.innerWidth;\n    var baseOffset = -currentPage * pageWidth;\n    var offset = baseOffset + deltaX;\n\n    var minOffset = -(pageCount - 1) * pageWidth;\n    if (offset > 0) offset = offset * 0.3;\n    if (offset < minOffset) offset = minOffset + (offset - minOffset) * 0.3;\n\n    getContainer().style.transform = 'translateX(' + offset + 'px)';\n  }\n\n  function onTouchEnd(e) {\n    if (!swiping) return;\n    swiping = false;\n\n    var threshold = window.innerWidth * 0.2;\n\n    if (deltaX < -threshold && currentPage < pageCount - 1) {\n      switchTab(currentPage + 1);\n    } else if (deltaX > threshold && currentPage > 0) {\n      switchTab(currentPage - 1);\n    } else {\n      switchTab(currentPage);\n    }\n\n    deltaX = 0;\n  }\n\n  document.addEventListener('DOMContentLoaded', function() {\n    var c = getContainer();\n    if (c) {\n      c.addEventListener('touchstart', onTouchStart, { passive: true });\n      c.addEventListener('touchmove', onTouchMove, { passive: false });\n      c.addEventListener('touchend', onTouchEnd, { passive: true });\n    }\n  });\n})();\n\n// ==================== Status Check ====================\nfunction checkStatus() {\n  var badge = document.getElementById('statusBadge');\n  var statusText = document.getElementById('statusText');\n  var pidBadge = document.getElementById('pidBadge');\n\n  var running = false;\n  var pid = '-';\n\n  var pidResult = ModuleAPI.exec('cat ' + PID_FILE + ' 2>/dev/null');\n  pid = pidResult.stdout.trim();\n\n  if (pid && /^\\d+$/.test(pid)) {\n    running = true;\n  } else {\n    pid = '-';\n  }\n\n  isRunning = running;\n  badge.className = 'status-badge ' + (running ? 'running' : 'stopped');\n  statusText.textContent = running ? 'Running' : 'Stopped';\n\n  pidBadge.textContent = 'PID: ' + pid;\n\n  document.getElementById('btnStart').disabled = running;\n  document.getElementById('btnStop').disabled = !running;\n  document.getElementById('btnRestart').disabled = !running;\n}\n\n// ==================== Load Settings ====================\nfunction getConf(key) {\n  var r = ModuleAPI.exec('grep \"^' + key + '=\" ' + CONF_FILE + ' 2>/dev/null | cut -d= -f2');\n  return (r.stdout || '').replace(/\\r/g, '').trim();\n}\n\nfunction getConfQuoted(key) {\n  return getConf(key).replace(/^\"|\"$/g, '');\n}\n\nfunction loadSettings() {\n  var settings = {};\n  settings.enable_iptables = getConf('enable_iptables');\n  settings.block_ipv6_dns = getConf('block_ipv6_dns');\n  settings.redir_port = getConf('redir_port') || '5591';\n  settings.adg_user = getConf('adg_user') || 'root';\n  settings.adg_group = getConf('adg_group') || 'net_raw';\n  settings.ignore_dest_list = getConfQuoted('ignore_dest_list');\n  settings.ignore_src_list = getConfQuoted('ignore_src_list');\n\n  currentSettings = settings;\n  applySettingsToUI(settings);\n}\n\nfunction applySettingsToUI(settings) {\n  document.getElementById('setEnableIptables').checked = (settings.enable_iptables === 'true');\n  document.getElementById('setBlockIpv6').checked = (settings.block_ipv6_dns === 'true');\n  document.getElementById('setRedirPort').value = settings.redir_port || '5591';\n  document.getElementById('setAdgUser').value = settings.adg_user || 'root';\n  document.getElementById('setAdgGroup').value = settings.adg_group || 'net_raw';\n  document.getElementById('setIgnoreDest').value = settings.ignore_dest_list || '';\n  document.getElementById('setIgnoreSrc').value = settings.ignore_src_list || '';\n\n  settingsChanged = false;\n  document.getElementById('btnSaveSettings').disabled = true;\n}\n\nfunction onSettingChange() {\n  settingsChanged = true;\n  document.getElementById('btnSaveSettings').disabled = false;\n}\n\n// ==================== Save Settings ====================\nfunction saveSettings() {\n  showLoading(true);\n\n  var enableIptables = document.getElementById('setEnableIptables').checked;\n  var blockIpv6 = document.getElementById('setBlockIpv6').checked;\n  var redirPort = document.getElementById('setRedirPort').value || '5591';\n  var adgUser = document.getElementById('setAdgUser').value || 'root';\n  var adgGroup = document.getElementById('setAdgGroup').value || 'net_raw';\n  var ignoreDest = document.getElementById('setIgnoreDest').value || '';\n  var ignoreSrc = document.getElementById('setIgnoreSrc').value || '';\n\n  var cmd =\n    \"sed -i 's#^enable_iptables=.*#enable_iptables=\" + enableIptables + \"#' \" + CONF_FILE +\n    \" && sed -i 's#^block_ipv6_dns=.*#block_ipv6_dns=\" + blockIpv6 + \"#' \" + CONF_FILE +\n    \" && sed -i 's#^redir_port=.*#redir_port=\" + redirPort + \"#' \" + CONF_FILE +\n    \" && sed -i 's#^adg_user=.*#adg_user=\" + adgUser + \"#' \" + CONF_FILE +\n    \" && sed -i 's#^adg_group=.*#adg_group=\" + adgGroup + \"#' \" + CONF_FILE +\n    \" && sed -i 's#^ignore_dest_list=.*#ignore_dest_list=\\\"\" + ignoreDest + \"\\\"#' \" + CONF_FILE +\n    \" && sed -i 's#^ignore_src_list=.*#ignore_src_list=\\\"\" + ignoreSrc + \"\\\"#' \" + CONF_FILE;\n\n  var result = ModuleAPI.exec(cmd);\n\n  if (result.errno === 0) {\n    settingsChanged = false;\n    document.getElementById('btnSaveSettings').disabled = true;\n    showToast('Settings saved successfully', 'success');\n  } else {\n    showToast('Failed to save settings', 'error');\n  }\n\n  showLoading(false);\n}\n\n// ==================== Log Viewer ====================\nfunction loadLog() {\n  if (!ModuleAPI.isAvailable()) return;\n  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\");\n  var logViewer = document.getElementById('logViewer');\n  if (logViewer) {\n    var raw = (result.stdout || '').trim();\n    var lines = raw.split('::NL::').filter(function(l) { return l !== ''; });\n    var content = lines.join('\\n');\n    logViewer.textContent = content || 'No log entries';\n    logViewer.scrollTop = logViewer.scrollHeight;\n  }\n}\n\n// ==================== Stats Help Modal ====================\nfunction toggleStatsHelp() {\n  var overlay = document.getElementById('statsHelpOverlay');\n  if (overlay) {\n    overlay.classList.add('visible');\n  }\n}\n\nfunction closeStatsHelp() {\n  var overlay = document.getElementById('statsHelpOverlay');\n  if (overlay) {\n    overlay.classList.remove('visible');\n  }\n}\n\n// ==================== Stats View Switching ====================\nfunction showStatsDataView() {\n  statsAuthVisible = false;\n  var dataView = document.getElementById('statsDataView');\n  var authView = document.getElementById('statsAuthView');\n  if (dataView) dataView.style.display = '';\n  if (authView) authView.style.display = 'none';\n}\n\nfunction showStatsAuthView() {\n  statsAuthVisible = true;\n  var dataView = document.getElementById('statsDataView');\n  var authView = document.getElementById('statsAuthView');\n  if (dataView) dataView.style.display = 'none';\n  if (authView) authView.style.display = '';\n}\n\nfunction submitCredentials() {\n  var userEl = document.getElementById('authUser');\n  var passEl = document.getElementById('authPassword');\n  if (userEl) webUser = userEl.value || 'root';\n  if (passEl) webPassword = passEl.value || 'root';\n  statsFailCount = 0;\n  showStatsDataView();\n  loadStats();\n}\n\n// ==================== Query Log Statistics ====================\nfunction loadStats() {\n  if (!ModuleAPI.isAvailable()) return;\n  if (!isRunning) return;\n  if (webPort === '-') return;\n  if (statsAuthVisible) return;\n\n  var url = 'http://127.0.0.1:' + webPort + '/control/stats';\n  var auth = webUser + ':' + webPassword;\n  var cmd = 'wget -qO- --user=' + webUser + ' --password=' + webPassword + ' ' + url + ' 2>/dev/null || curl -s -u ' + auth + ' ' + url + ' 2>/dev/null';\n\n  var result = ModuleAPI.exec(cmd);\n  var raw = (result.stdout || '').trim();\n\n  var queriesEl = document.getElementById('statQueries');\n  var blockedEl = document.getElementById('statBlocked');\n  var timeEl = document.getElementById('statTime');\n\n  if (!raw || raw.charAt(0) !== '{') {\n    statsFailCount++;\n    if (queriesEl) queriesEl.textContent = '-';\n    if (blockedEl) blockedEl.textContent = '-';\n    if (timeEl) timeEl.textContent = '-';\n    if (statsFailCount >= 2) {\n      showStatsAuthView();\n    }\n    return;\n  }\n\n  try {\n    var stats = JSON.parse(raw);\n\n    if (queriesEl) queriesEl.textContent = (typeof stats.num_dns_queries === 'number') ? stats.num_dns_queries : '-';\n    if (blockedEl) blockedEl.textContent = (typeof stats.num_blocked_filtering === 'number') ? stats.num_blocked_filtering : '-';\n    if (timeEl) timeEl.textContent = (typeof stats.avg_processing_time === 'number') ? stats.avg_processing_time.toFixed(2) + 's' : '-';\n    statsFailCount = 0;\n  } catch (e) {\n    statsFailCount++;\n    if (queriesEl) queriesEl.textContent = '-';\n    if (blockedEl) blockedEl.textContent = '-';\n    if (timeEl) timeEl.textContent = '-';\n    if (statsFailCount >= 2) {\n      showStatsAuthView();\n    }\n  }\n}\n\n// ==================== Control ====================\nfunction controlAdGuard(action) {\n  showLoading(true);\n\n  var cmd;\n  switch (action) {\n    case 'start':\n      cmd = SCRIPT_DIR + '/tool.sh start';\n      break;\n    case 'stop':\n      cmd = SCRIPT_DIR + '/tool.sh stop';\n      break;\n    case 'restart':\n      cmd = SCRIPT_DIR + '/tool.sh stop; sleep 1; ' + SCRIPT_DIR + '/tool.sh start';\n      break;\n    default:\n      showLoading(false);\n      return;\n  }\n\n  var result = ModuleAPI.exec(cmd);\n\n  if (result.errno === 0) {\n    var label = action.charAt(0).toUpperCase() + action.slice(1);\n    showToast(label + ' successful', 'success');\n  } else {\n    showToast(action + ' failed', 'error');\n  }\n\n  setTimeout(function() {\n    checkStatus();\n    loadLog();\n    showLoading(false);\n  }, 2000);\n}\n\n// ==================== Open AdGuardHome ====================\nfunction openAdGuardHome() {\n  var url = (webPort !== '-') ? ('http://127.0.0.1:' + webPort) : 'http://127.0.0.1:3000';\n  window.open(url, '_blank');\n}\n\n// ==================== Debug Info ====================\nfunction collectDebugInfo() {\n  showLoading(true);\n  ModuleAPI.exec(SCRIPT_DIR + '/debug.sh 2>/dev/null');\n  showToast('Debug info collected to ' + AGH_DIR + '/debug.log', 'success');\n  showLoading(false);\n}\n\n// ==================== Load Web Port ====================\nfunction loadWebPort() {\n  var result = ModuleAPI.exec(\"grep 'address:' \" + BIN_DIR + \"/AdGuardHome.yaml 2>/dev/null | head -1\");\n  var line = result.stdout.trim();\n  var match = line.match(/address:\\s*\\S+:(\\d+)/);\n  if (match && match[1]) {\n    webPort = match[1];\n  } else {\n    webPort = '3000';\n  }\n}\n\n// ==================== Load Version ====================\nfunction loadVersion() {\n  var result = ModuleAPI.exec('grep \"^version=\" ' + MODULE_PROP + ' 2>/dev/null | cut -d= -f2');\n  var version = result.stdout.trim();\n  if (version) {\n    document.getElementById('versionText').textContent = 'v' + version;\n  }\n}\n\n// ==================== Scroll Hide Floating Tab ====================\nfunction setupScrollHideTab() {\n  var pages = document.querySelectorAll('.page');\n  var floatingTab = document.getElementById('floatingTab');\n  if (!floatingTab) return;\n\n  for (var i = 0; i < pages.length; i++) {\n    (function(page) {\n      page.addEventListener('scroll', function() {\n        var atTop = page.scrollTop <= 2;\n        var atBottom = page.scrollTop + page.clientHeight >= page.scrollHeight - 2;\n\n        if (atTop || atBottom) {\n          floatingTab.classList.remove('tab-hidden');\n        } else {\n          floatingTab.classList.add('tab-hidden');\n        }\n      });\n    })(pages[i]);\n  }\n}\n\n// ==================== Initialize ====================\nfunction init() {\n  if (!ModuleAPI.isAvailable()) {\n    document.getElementById('noApiWarning').style.display = 'block';\n    document.getElementById('statusBadge').className = 'status-badge stopped';\n    document.getElementById('statusText').textContent = 'No API';\n    return;\n  }\n\n  loadSettings();\n  checkStatus();\n  loadWebPort();\n  loadVersion();\n  loadLog();\n  loadStats();\n  setupScrollHideTab();\n\n  switchTab(0);\n}\n\n// Auto-refresh every 5 seconds\nsetInterval(function() {\n  if (ModuleAPI.isAvailable()) {\n    checkStatus();\n    loadLog();\n    loadStats();\n  }\n}, 5000);\n\n// Start\ndocument.addEventListener('DOMContentLoaded', init);\n"
  },
  {
    "path": "src/webroot/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no\">\n<title>AdGuardHome for Root</title>\n<link rel=\"stylesheet\" href=\"style.css\">\n</head>\n<body>\n\n<div class=\"app-wrapper\">\n  <!-- Pages Container -->\n  <div class=\"pages-container\" id=\"pagesContainer\">\n\n    <!-- Page 1: Main -->\n    <div class=\"page page-main\" id=\"pageMain\">\n      <div class=\"container\">\n        <!-- Header -->\n        <div class=\"header\">\n          <div class=\"header-icon\">\n            <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>\n          </div>\n          <h1>AdGuardHome for Root</h1>\n          <div class=\"version\" id=\"versionText\">v20260315</div>\n          <div class=\"status-row\">\n            <span class=\"status-badge loading\" id=\"statusBadge\">\n              <span class=\"status-dot\"></span>\n              <span id=\"statusText\">Loading...</span>\n            </span>\n            <span class=\"pid-badge\" id=\"pidBadge\">PID: -</span>\n          </div>\n        </div>\n\n        <!-- No API Warning -->\n        <div class=\"no-api-warning\" id=\"noApiWarning\" style=\"display:none;\">\n          <h3>⚠️ Module API Not Available</h3>\n          <p>This WebUI requires KernelSU or APatch module manager. Please open this page from the module manager app.</p>\n        </div>\n\n        <!-- Statistics -->\n        <div class=\"card card-stats\">\n          <div class=\"card-title\">\n            <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>\n            Query Log Statistics\n            <button class=\"btn-help\" onclick=\"toggleStatsHelp()\">?</button>\n          </div>\n          <!-- Stats Data View -->\n          <div class=\"stats-grid\" id=\"statsDataView\">\n            <div class=\"stat-item\">\n              <div class=\"stat-value\" id=\"statQueries\">-</div>\n              <div class=\"stat-label\">DNS查询</div>\n            </div>\n            <div class=\"stat-item\">\n              <div class=\"stat-value\" id=\"statBlocked\">-</div>\n              <div class=\"stat-label\">规则拦截</div>\n            </div>\n            <div class=\"stat-item\">\n              <div class=\"stat-value\" id=\"statTime\">-</div>\n              <div class=\"stat-label\">处理耗时</div>\n            </div>\n          </div>\n          <!-- Credential Input View (hidden by default) -->\n          <div class=\"stats-auth-form\" id=\"statsAuthView\" style=\"display:none;\">\n            <div class=\"stats-auth-hint\">认证失败，请输入 AdGuardHome Web UI 账号密码</div>\n            <div class=\"input-group\">\n              <label class=\"input-label\">Username</label>\n              <input type=\"text\" class=\"input-field\" id=\"authUser\" placeholder=\"root\" value=\"root\">\n            </div>\n            <div class=\"input-group\">\n              <label class=\"input-label\">Password</label>\n              <input type=\"password\" class=\"input-field\" id=\"authPassword\" placeholder=\"root\" value=\"root\">\n            </div>\n            <button class=\"btn btn-primary btn-full\" onclick=\"submitCredentials()\" style=\"margin-top:8px;\">确认</button>\n          </div>\n        </div>\n\n        <!-- Open Web Panel -->\n        <div class=\"card card-action\">\n          <button class=\"btn btn-link btn-full\" onclick=\"openAdGuardHome()\">\n            AdGuardHome Web Panel\n          </button>\n        </div>\n\n        <!-- Control -->\n        <div class=\"card\">\n          <div class=\"control-buttons\">\n            <button class=\"btn btn-primary\" id=\"btnStart\" onclick=\"controlAdGuard('start')\" disabled>Start</button>\n            <button class=\"btn btn-danger\" id=\"btnStop\" onclick=\"controlAdGuard('stop')\" disabled>Stop</button>\n            <button class=\"btn btn-secondary\" id=\"btnRestart\" onclick=\"controlAdGuard('restart')\" disabled>Restart</button>\n            <button class=\"btn btn-secondary\" onclick=\"collectDebugInfo()\">Debug Info</button>\n          </div>\n          <div class=\"log-viewer\" id=\"logViewer\"></div>\n        </div>\n\n        <!-- Footer -->\n        <div class=\"footer\">\n          <p>AdGuardHome for Root by <a href=\"https://github.com/twoone-3\">twoone3</a></p>\n          <p style=\"margin-top:4px;\">MIT License · <a href=\"https://github.com/twoone-3/AdGuardHomeForRoot\">GitHub</a></p>\n        </div>\n      </div>\n    </div>\n\n    <!-- Page 2: Settings -->\n    <div class=\"page page-settings\" id=\"pageSettings\">\n      <div class=\"container\">\n        <!-- Settings Header -->\n        <div class=\"settings-header\">\n          <div class=\"card-title\">Settings</div>\n        </div>\n\n        <!-- Settings Content -->\n        <div class=\"card\">\n          <div class=\"setting-group\">\n            <div class=\"setting-row\">\n              <div class=\"setting-label\">\n                <div class=\"title\">Enable Iptables</div>\n                <div class=\"desc\">Redirect DNS requests via iptables</div>\n              </div>\n              <label class=\"toggle\">\n                <input type=\"checkbox\" id=\"setEnableIptables\" onchange=\"onSettingChange()\">\n                <span class=\"toggle-slider\"></span>\n              </label>\n            </div>\n            <div class=\"setting-row\">\n              <div class=\"setting-label\">\n                <div class=\"title\">Block IPv6 DNS</div>\n                <div class=\"desc\">Block DNS requests over IPv6</div>\n              </div>\n              <label class=\"toggle\">\n                <input type=\"checkbox\" id=\"setBlockIpv6\" onchange=\"onSettingChange()\">\n                <span class=\"toggle-slider\"></span>\n              </label>\n            </div>\n          </div>\n\n          <div class=\"setting-group\">\n            <div class=\"input-group\">\n              <label class=\"input-label\">Redirect Port (must match AdGuardHome DNS port)</label>\n              <input type=\"number\" class=\"input-field\" id=\"setRedirPort\" placeholder=\"5591\" onchange=\"onSettingChange()\">\n            </div>\n            <div class=\"input-group\">\n              <label class=\"input-label\">Run User</label>\n              <input type=\"text\" class=\"input-field\" id=\"setAdgUser\" placeholder=\"root\" onchange=\"onSettingChange()\">\n            </div>\n            <div class=\"input-group\">\n              <label class=\"input-label\">Run User Group</label>\n              <input type=\"text\" class=\"input-field\" id=\"setAdgGroup\" placeholder=\"net_raw\" onchange=\"onSettingChange()\">\n            </div>\n            <div class=\"input-group\">\n              <label class=\"input-label\">Bypass Destinations (space-separated)</label>\n              <input type=\"text\" class=\"input-field\" id=\"setIgnoreDest\" placeholder=\"\" onchange=\"onSettingChange()\">\n            </div>\n            <div class=\"input-group\">\n              <label class=\"input-label\">Bypass Sources (space-separated)</label>\n              <input type=\"text\" class=\"input-field\" id=\"setIgnoreSrc\" placeholder=\"\" onchange=\"onSettingChange()\">\n            </div>\n          </div>\n\n          <div class=\"setting-group\" style=\"margin-bottom:0;\">\n            <button class=\"btn btn-primary btn-full\" id=\"btnSaveSettings\" onclick=\"saveSettings()\" disabled>\n              Save Settings\n            </button>\n          </div>\n        </div>\n      </div>\n    </div>\n\n  </div>\n\n  <!-- Floating Tab Bar -->\n  <div class=\"floating-tab\" id=\"floatingTab\">\n    <div class=\"floating-tab-item active\" data-page=\"0\" onclick=\"switchTab(0)\">\n      <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>\n      <span class=\"tab-label\">Main</span>\n    </div>\n    <div class=\"floating-tab-item\" data-page=\"1\" onclick=\"switchTab(1)\">\n      <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>\n      <span class=\"tab-label\">Settings</span>\n    </div>\n  </div>\n</div>\n\n<!-- Stats Help Modal -->\n<div class=\"help-modal-overlay\" id=\"statsHelpOverlay\" onclick=\"closeStatsHelp()\">\n  <div class=\"help-modal\" onclick=\"event.stopPropagation()\">\n    <div class=\"help-modal-title\">指标说明</div>\n    <div class=\"help-modal-item\">\n      <strong>DNS查询</strong>\n      <p>AdGuard Home 核心处理的 DNS 查询总数。</p>\n    </div>\n    <div class=\"help-modal-item\">\n      <strong>规则拦截</strong>\n      <p>被过滤规则拦截的 DNS 请求数量。</p>\n    </div>\n    <div class=\"help-modal-item\">\n      <strong>处理耗时</strong>\n      <p>处理每个 DNS 请求的平均耗时（秒）。</p>\n    </div>\n    <div class=\"help-modal-note\">数据通过 AdGuard Home OpenAPI 实时获取，统计周期由核心配置决定。</div>\n    <button class=\"btn btn-secondary btn-full\" onclick=\"closeStatsHelp()\" style=\"margin-top:12px;\">关闭</button>\n  </div>\n</div>\n\n<!-- Toast Container -->\n<div class=\"toast-container\" id=\"toastContainer\"></div>\n\n<!-- Loading Overlay -->\n<div class=\"loading-overlay\" id=\"loadingOverlay\" style=\"display:none;\">\n  <div class=\"spinner\"></div>\n</div>\n\n<script src=\"app.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "src/webroot/style.css",
    "content": "/* AdGuardHome for Root - WebUI Styles */\n\n/* ===== Dark Theme (default) ===== */\n:root {\n  --primary: #68BC71;\n  --primary-dark: #4CAF50;\n  --primary-light: #A5D6A7;\n  --bg: #0c0e14;\n  --surface: #161923;\n  --surface-2: #1e2231;\n  --surface-3: #2a2f42;\n  --text: #E8EAED;\n  --text-secondary: #9AA0A6;\n  --text-hint: #6B7280;\n  --danger: #EF5350;\n  --danger-dark: #C62828;\n  --warning: #FFC107;\n  --info: #42A5F5;\n  --success: #66BB6A;\n  --border: #252a3a;\n  --radius: 14px;\n  --radius-sm: 10px;\n  --shadow: 0 2px 12px rgba(0,0,0,0.35);\n  --transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);\n  --log-bg: #0a0c12;\n  --toggle-off: #3a3f50;\n}\n\n/* ===== Light Theme ===== */\n@media (prefers-color-scheme: light) {\n  :root {\n    --bg: #f0f2f5;\n    --surface: #ffffff;\n    --surface-2: #f5f6f8;\n    --surface-3: #e8eaed;\n    --text: #1f1f1f;\n    --text-secondary: #5f6368;\n    --text-hint: #80868b;\n    --border: #dfe1e5;\n    --shadow: 0 2px 12px rgba(0,0,0,0.08);\n    --log-bg: #f8f9fa;\n    --toggle-off: #bdbdbd;\n  }\n}\n\n* { margin: 0; padding: 0; box-sizing: border-box; }\n\nbody {\n  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Noto Sans SC', 'PingFang SC', 'Microsoft YaHei', sans-serif;\n  background: var(--bg);\n  color: var(--text);\n  line-height: 1.6;\n  height: 100vh;\n  overflow: hidden;\n  -webkit-font-smoothing: antialiased;\n}\n\n/* ===== App Wrapper ===== */\n.app-wrapper {\n  height: 100vh;\n  display: flex;\n  flex-direction: column;\n  overflow: hidden;\n  position: relative;\n}\n\n/* ===== Pages Container (horizontal sliding) ===== */\n.pages-container {\n  flex: 1;\n  display: flex;\n  width: 200%;\n  overflow: hidden;\n  transition: transform 0.35s cubic-bezier(0.4, 0, 0.2, 1);\n  will-change: transform;\n}\n\n.pages-container.no-transition {\n  transition: none;\n}\n\n.page {\n  width: 50%;\n  height: 100%;\n  overflow-y: auto;\n  overflow-x: hidden;\n  -webkit-overflow-scrolling: touch;\n  touch-action: pan-y;\n  padding-bottom: 88px; /* space for floating tab */\n}\n\n.page::-webkit-scrollbar { width: 4px; }\n.page::-webkit-scrollbar-track { background: transparent; }\n.page::-webkit-scrollbar-thumb { background: var(--surface-3); border-radius: 4px; }\n\n.container {\n  max-width: 600px;\n  margin: 0 auto;\n  padding: 16px;\n}\n\n/* ===== Header ===== */\n.header {\n  text-align: center;\n  padding: 16px 0 12px;\n}\n\n.header-icon {\n  width: 48px;\n  height: 48px;\n  background: linear-gradient(145deg, var(--primary), var(--primary-dark));\n  border-radius: 14px;\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n  box-shadow: 0 4px 16px rgba(104, 188, 113, 0.25);\n}\n\n.header-icon svg {\n  width: 28px;\n  height: 28px;\n  fill: white;\n}\n\n.header h1 {\n  font-size: 20px;\n  font-weight: 700;\n  color: var(--text);\n  margin-bottom: 2px;\n  letter-spacing: -0.3px;\n}\n\n.header .version {\n  font-size: 12px;\n  color: var(--text-hint);\n}\n\n/* ===== Status Row (badge + PID inline) ===== */\n.status-row {\n  display: inline-flex;\n  align-items: center;\n  gap: 10px;\n  margin-top: 6px;\n}\n\n/* ===== Status Badge ===== */\n.status-badge {\n  display: inline-flex;\n  align-items: center;\n  gap: 6px;\n  padding: 4px 14px;\n  border-radius: 20px;\n  font-size: 13px;\n  font-weight: 600;\n}\n\n.status-badge.running {\n  background: rgba(102, 187, 106, 0.15);\n  color: var(--success);\n}\n\n.status-badge.stopped {\n  background: rgba(239, 83, 80, 0.15);\n  color: var(--danger);\n}\n\n.status-badge.loading {\n  background: rgba(66, 165, 245, 0.15);\n  color: var(--info);\n}\n\n.status-dot {\n  width: 8px;\n  height: 8px;\n  border-radius: 50%;\n  display: inline-block;\n}\n\n.status-badge.running .status-dot {\n  background: var(--success);\n  box-shadow: 0 0 8px var(--success);\n  animation: pulse 2s infinite;\n}\n\n.status-badge.stopped .status-dot {\n  background: var(--danger);\n}\n\n.status-badge.loading .status-dot {\n  background: var(--info);\n  animation: pulse 1s infinite;\n}\n\n@keyframes pulse {\n  0%, 100% { opacity: 1; }\n  50% { opacity: 0.4; }\n}\n\n/* ===== PID Badge ===== */\n.pid-badge {\n  display: inline-flex;\n  align-items: center;\n  padding: 4px 12px;\n  border-radius: 20px;\n  font-size: 12px;\n  font-weight: 600;\n  background: var(--surface-2);\n  color: var(--text-secondary);\n  border: 1px solid var(--border);\n}\n\n/* ===== Card ===== */\n.card {\n  background: var(--surface);\n  border-radius: var(--radius);\n  padding: 20px;\n  margin-bottom: 14px;\n  border: 1px solid var(--border);\n  box-shadow: var(--shadow);\n  transition: box-shadow 0.25s ease;\n}\n\n.card.card-action {\n  padding: 10px 20px;\n  border-color: rgba(104, 188, 113, 0.15);\n}\n\n.card-title {\n  font-size: 15px;\n  font-weight: 600;\n  color: var(--text);\n  margin-bottom: 16px;\n  display: flex;\n  align-items: center;\n  gap: 8px;\n}\n\n.card-title .icon {\n  font-size: 18px;\n}\n\n.card-title-icon {\n  width: 18px;\n  height: 18px;\n  fill: var(--primary);\n  flex-shrink: 0;\n}\n\n/* ===== Statistics Card ===== */\n.card-stats {\n  padding: 16px 20px;\n}\n\n.stats-grid {\n  display: grid;\n  grid-template-columns: repeat(3, 1fr);\n  gap: 8px;\n}\n\n.stat-item {\n  text-align: center;\n  padding: 12px 4px;\n  background: var(--surface-2);\n  border-radius: var(--radius-sm);\n  border: 1px solid var(--border);\n}\n\n.stat-value {\n  font-size: 24px;\n  font-weight: 700;\n  color: var(--primary);\n  line-height: 1.2;\n  margin-bottom: 4px;\n}\n\n.stat-label {\n  font-size: 11px;\n  color: var(--text-hint);\n  font-weight: 500;\n}\n\n/* ===== Stats Auth Form ===== */\n.stats-auth-form {\n  padding: 4px 0;\n}\n\n.stats-auth-hint {\n  font-size: 12px;\n  color: var(--warning);\n  margin-bottom: 10px;\n  text-align: center;\n  line-height: 1.5;\n}\n\n/* ===== Help Button ===== */\n.btn-help {\n  width: 16px;\n  height: 16px;\n  border-radius: 50%;\n  border: 1px solid var(--text-hint);\n  background: transparent;\n  color: var(--text-hint);\n  font-size: 10px;\n  font-weight: 700;\n  cursor: pointer;\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  margin-left: auto;\n  flex-shrink: 0;\n  padding: 0;\n  line-height: 1;\n  transition: var(--transition);\n  font-family: inherit;\n}\n\n.btn-help:hover {\n  border-color: var(--primary);\n  color: var(--primary);\n  background: rgba(104, 188, 113, 0.08);\n}\n\n/* ===== Help Modal ===== */\n.help-modal-overlay {\n  position: fixed;\n  top: 0; left: 0; right: 0; bottom: 0;\n  background: rgba(0, 0, 0, 0.5);\n  display: none;\n  align-items: center;\n  justify-content: center;\n  z-index: 500;\n  backdrop-filter: blur(4px);\n  -webkit-backdrop-filter: blur(4px);\n  padding: 24px;\n}\n\n.help-modal-overlay.visible {\n  display: flex;\n}\n\n.help-modal {\n  background: var(--surface);\n  border-radius: var(--radius);\n  padding: 20px;\n  max-width: 360px;\n  width: 100%;\n  border: 1px solid var(--border);\n  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);\n  animation: modalIn 0.2s ease;\n}\n\n@keyframes modalIn {\n  from { opacity: 0; transform: scale(0.95); }\n  to { opacity: 1; transform: scale(1); }\n}\n\n.help-modal-title {\n  font-size: 16px;\n  font-weight: 700;\n  color: var(--text);\n  margin-bottom: 14px;\n  text-align: center;\n}\n\n.help-modal-item {\n  padding: 8px 0;\n}\n\n.help-modal-item + .help-modal-item {\n  border-top: 1px solid var(--border);\n}\n\n.help-modal-item strong {\n  font-size: 13px;\n  color: var(--primary);\n  display: block;\n  margin-bottom: 2px;\n}\n\n.help-modal-item p {\n  font-size: 12px;\n  color: var(--text-secondary);\n  line-height: 1.5;\n  margin: 0;\n}\n\n.help-modal-note {\n  font-size: 11px;\n  color: var(--text-hint);\n  text-align: center;\n  margin-top: 12px;\n  padding-top: 10px;\n  border-top: 1px solid var(--border);\n  line-height: 1.5;\n}\n\n/* ===== Buttons ===== */\n.control-buttons {\n  display: grid;\n  grid-template-columns: 1fr 1fr;\n  gap: 10px;\n}\n\n.btn {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  gap: 6px;\n  padding: 11px 16px;\n  border: none;\n  border-radius: var(--radius-sm);\n  font-size: 13px;\n  font-weight: 600;\n  cursor: pointer;\n  transition: var(--transition);\n  outline: none;\n  position: relative;\n  overflow: hidden;\n  font-family: inherit;\n  letter-spacing: 0.2px;\n}\n\n.btn:active { transform: scale(0.97); }\n.btn:disabled { opacity: 0.45; cursor: not-allowed; transform: none; }\n\n.btn-primary {\n  background: linear-gradient(145deg, var(--primary), var(--primary-dark));\n  color: white;\n}\n.btn-primary:hover:not(:disabled) { box-shadow: 0 4px 16px rgba(104, 188, 113, 0.35); }\n\n.btn-danger {\n  background: linear-gradient(145deg, var(--danger), var(--danger-dark));\n  color: white;\n}\n.btn-danger:hover:not(:disabled) { box-shadow: 0 4px 16px rgba(239, 83, 80, 0.35); }\n\n.btn-secondary { background: var(--surface-2); color: var(--text); border: 1px solid var(--border); }\n.btn-secondary:hover:not(:disabled) { background: var(--surface-3); border-color: var(--text-hint); }\n\n.btn-full { width: 100%; padding: 13px; }\n\n.btn-link {\n  background: linear-gradient(135deg, rgba(104, 188, 113, 0.12), rgba(104, 188, 113, 0.04));\n  color: var(--primary);\n  border: 1px solid rgba(104, 188, 113, 0.2);\n  font-weight: 600;\n}\n.btn-link:hover:not(:disabled) {\n  background: linear-gradient(135deg, rgba(104, 188, 113, 0.22), rgba(104, 188, 113, 0.08));\n  border-color: rgba(104, 188, 113, 0.4);\n}\n\n/* ===== Settings ===== */\n.settings-header {\n  padding: 24px 0 8px;\n}\n\n.settings-header .card-title {\n  margin-bottom: 0;\n}\n\n.setting-group { margin-bottom: 12px; }\n.setting-group:last-child { margin-bottom: 0; }\n\n.setting-row {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  padding: 10px 0;\n  border-bottom: 1px solid var(--border);\n}\n\n.setting-row:last-child { border-bottom: none; padding-bottom: 0; }\n.setting-row:first-child { padding-top: 0; }\n\n.setting-label { flex: 1; }\n.setting-label .title { font-size: 14px; font-weight: 500; color: var(--text); }\n.setting-label .desc { font-size: 12px; color: var(--text-hint); margin-top: 2px; }\n\n/* ===== Toggle Switch ===== */\n.toggle {\n  position: relative;\n  width: 48px;\n  height: 26px;\n  flex-shrink: 0;\n  margin-left: 12px;\n}\n\n.toggle input { opacity: 0; width: 0; height: 0; }\n\n.toggle-slider {\n  position: absolute;\n  cursor: pointer;\n  top: 0; left: 0; right: 0; bottom: 0;\n  background: var(--toggle-off);\n  border-radius: 26px;\n  transition: var(--transition);\n}\n\n.toggle-slider:before {\n  content: '';\n  position: absolute;\n  height: 20px;\n  width: 20px;\n  left: 3px;\n  bottom: 3px;\n  background: white;\n  border-radius: 50%;\n  transition: var(--transition);\n}\n\n.toggle input:checked + .toggle-slider { background: var(--primary); }\n.toggle input:checked + .toggle-slider:before { transform: translateX(22px); }\n.toggle input:disabled + .toggle-slider { opacity: 0.5; cursor: not-allowed; }\n\n/* ===== Input ===== */\n.input-field {\n  width: 100%;\n  padding: 10px 14px;\n  background: var(--surface-2);\n  border: 1px solid var(--border);\n  border-radius: var(--radius-sm);\n  color: var(--text);\n  font-size: 14px;\n  font-family: inherit;\n  transition: var(--transition);\n  outline: none;\n}\n\n.input-field:focus {\n  border-color: var(--primary);\n  box-shadow: 0 0 0 3px rgba(104, 188, 113, 0.15);\n}\n\n.input-field:disabled { opacity: 0.5; }\n\n.input-group { margin-top: 8px; }\n\n.input-label {\n  font-size: 12px;\n  color: var(--text-hint);\n  margin-bottom: 6px;\n  display: block;\n}\n\n/* ===== Log Viewer ===== */\n.log-viewer {\n  background: var(--log-bg);\n  border-radius: var(--radius-sm);\n  padding: 10px 12px;\n  font-family: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', 'Consolas', monospace;\n  font-size: 10px;\n  line-height: 1.7;\n  max-height: 160px;\n  min-height: 80px;\n  overflow-y: auto;\n  color: var(--text-hint);\n  white-space: pre-wrap;\n  word-break: break-all;\n  border: 1px solid var(--border);\n  border-top: 2px solid var(--surface-3);\n  margin-top: 12px;\n}\n\n.log-viewer::-webkit-scrollbar { width: 3px; }\n.log-viewer::-webkit-scrollbar-track { background: transparent; }\n.log-viewer::-webkit-scrollbar-thumb { background: var(--surface-3); border-radius: 3px; }\n\n/* ===== Toast ===== */\n.toast-container {\n  position: fixed;\n  bottom: 80px;\n  left: 50%;\n  transform: translateX(-50%);\n  z-index: 1000;\n  display: flex;\n  flex-direction: column;\n  gap: 8px;\n  pointer-events: none;\n}\n\n.toast {\n  padding: 12px 20px;\n  border-radius: var(--radius-sm);\n  font-size: 14px;\n  font-weight: 500;\n  color: white;\n  box-shadow: 0 4px 16px rgba(0,0,0,0.4);\n  animation: toastIn 0.3s ease;\n  white-space: nowrap;\n  pointer-events: auto;\n}\n\n.toast.success { background: var(--success); }\n.toast.error { background: var(--danger); }\n.toast.info { background: var(--info); }\n.toast.warning { background: #E65100; }\n\n@keyframes toastIn {\n  from { opacity: 0; transform: translateY(20px); }\n  to { opacity: 1; transform: translateY(0); }\n}\n\n@keyframes toastOut {\n  from { opacity: 1; transform: translateY(0); }\n  to { opacity: 0; transform: translateY(-20px); }\n}\n\n/* ===== Loading Overlay ===== */\n.loading-overlay {\n  position: fixed;\n  top: 0; left: 0; right: 0; bottom: 0;\n  background: rgba(128, 128, 128, 0.5);\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  z-index: 999;\n  backdrop-filter: blur(4px);\n}\n\n.spinner {\n  width: 40px;\n  height: 40px;\n  border: 3px solid var(--surface-3);\n  border-top-color: var(--primary);\n  border-radius: 50%;\n  animation: spin 0.8s linear infinite;\n}\n\n@keyframes spin { to { transform: rotate(360deg); } }\n\n/* ===== No API Warning ===== */\n.no-api-warning {\n  background: rgba(239, 83, 80, 0.1);\n  border: 1px solid rgba(239, 83, 80, 0.3);\n  border-radius: var(--radius);\n  padding: 20px;\n  text-align: center;\n  margin-bottom: 12px;\n}\n\n.no-api-warning h3 {\n  color: var(--danger);\n  margin-bottom: 8px;\n  font-size: 16px;\n}\n\n.no-api-warning p {\n  color: var(--text-secondary);\n  font-size: 13px;\n  line-height: 1.6;\n}\n\n/* ===== Footer ===== */\n.footer {\n  text-align: center;\n  padding: 16px 0 24px;\n  color: var(--text-hint);\n  font-size: 12px;\n}\n\n.footer a {\n  color: var(--primary);\n  text-decoration: none;\n}\n\n.footer a:hover { text-decoration: underline; }\n\n/* ===== Floating Tab Bar ===== */\n.floating-tab {\n  position: fixed;\n  bottom: 24px;\n  left: 50%;\n  transform: translateX(-50%);\n  display: flex;\n  align-items: center;\n  gap: 4px;\n  padding: 5px 6px;\n  background: var(--surface);\n  border: 1px solid var(--border);\n  border-radius: 28px;\n  box-shadow: 0 4px 24px rgba(0,0,0,0.45);\n  z-index: 100;\n  -webkit-tap-highlight-color: transparent;\n  backdrop-filter: blur(12px);\n  -webkit-backdrop-filter: blur(12px);\n  transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s ease;\n}\n\n.floating-tab.tab-hidden {\n  transform: translateX(-50%) translateY(80px);\n  opacity: 0;\n  pointer-events: none;\n}\n\n.floating-tab-item {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  gap: 6px;\n  padding: 8px 22px;\n  border-radius: 22px;\n  cursor: pointer;\n  transition: all 0.25s ease;\n  color: var(--text-hint);\n  user-select: none;\n  font-size: 12px;\n  font-weight: 600;\n}\n\n.floating-tab-item.active {\n  background: linear-gradient(145deg, var(--primary), var(--primary-dark));\n  color: white;\n  box-shadow: 0 2px 10px rgba(104, 188, 113, 0.35);\n}\n\n.tab-icon {\n  width: 18px;\n  height: 18px;\n  fill: currentColor;\n}\n\n.tab-label {\n  font-size: 12px;\n  font-weight: 600;\n}\n\n/* ===== Responsive ===== */\n@media (max-width: 400px) {\n  .container { padding: 12px; }\n  .card { padding: 16px; }\n  .control-buttons { grid-template-columns: 1fr 1fr; }\n}\n"
  },
  {
    "path": "version.json",
    "content": "{\n  \"versionCode\": 49,\n  \"version\": \"20260419\",\n  \"zipUrl\": \"https://github.com/twoone-3/AdGuardHomeForRoot/releases/latest/download/AdGuardHomeForRoot_arm64.zip\",\n  \"changelog\": \"https://raw.githubusercontent.com/twoone-3/AdGuardHomeForRoot/main/changelog.md\"\n}\n"
  }
]