diff --git a/AdGuardHome/Makefile b/AdGuardHome/Makefile new file mode 100644 index 00000000..77e3acde --- /dev/null +++ b/AdGuardHome/Makefile @@ -0,0 +1,149 @@ +# +# Copyright (C) 2015-2016 OpenWrt.org +# +# This is free software, licensed under the GNU General Public License v2. +# See /LICENSE.txt for more information. +# + +include $(TOPDIR)/rules.mk + +PKG_NAME:=AdGuardHome +PKG_VERSION:=0.107.79 +PKG_RELEASE:=1 + +# 上游自 v0.107 起为“单二进制 + go:embed 前端”:前端由 client_v2 (npm/webpack) +# 构建到源码根的 build/ 目录,再由 main.go 的 //go:embed build 打包进二进制。 +# 这里使用官方 codeload 源码包(比 git clone 快,且可用 PKG_HASH 校验收包)。 +PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz +PKG_SOURCE_URL:=https://codeload.github.com/AdguardTeam/AdGuardHome/tar.gz/v$(PKG_VERSION)? +PKG_HASH:=5f4fa119f24ea741bb8cecfa74203165fe6b86b904cef05ff429fb227b07d425 +PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION) + +PKG_LICENSE:=GPL-3.0-only +PKG_LICENSE_FILES:=LICENSE.txt +PKG_MAINTAINER:=Yang Feng + +PKG_CONFIG_DEPENDS:=CONFIG_ADGUARDHOME_COMPRESS_UPX + +# --------------------------------------------------------------------------- +# 工具链说明 +# +# 本包不使用 OpenWrt 的 golang/host 与 node/host: +# * AdGuardHome 的 go.mod 要求 go >= 1.26.6,而 OpenWrt 24.10 的 golang feed +# 仍是 1.23.x; +# * node/host 需要从源码编译 Node,代价极高。 +# 因此直接调用外部工具链(默认优先取容器内预装的 ~/.openwrt/tools), +# 可用下列变量覆盖: +# ADGH_GO Go 可执行文件(需 >= 1.26) +# ADGH_NODE_DIR 含 node/npm 的目录 +# ADGH_NPM npm 可执行文件 +# ADGH_NPM_REGISTRY npm 源(默认 npmmirror,国内更快) +# ADGH_GOPROXY Go 模块代理(默认 goproxy.cn) +# 工具链准备方式见 .devcontainer/README.md。 +# --------------------------------------------------------------------------- +ADGH_TOOLS_DIR ?= $(HOME)/.openwrt/tools +ADGH_GO ?= $(if $(wildcard $(ADGH_TOOLS_DIR)/go/bin/go),$(ADGH_TOOLS_DIR)/go/bin/go,go) +ADGH_NODE_DIR ?= $(if $(wildcard $(ADGH_TOOLS_DIR)/node/bin/node),$(ADGH_TOOLS_DIR)/node/bin,$(if $(wildcard /usr/bin/node),/usr/bin,/usr/local/bin)) +ADGH_NPM ?= $(ADGH_NODE_DIR)/npm +ADGH_NPM_REGISTRY ?= https://registry.npmmirror.com +ADGH_GOPROXY ?= https://goproxy.cn,direct + +# OpenWrt ARCH -> GOARCH:纯 Go 交叉编译(CGO_ENABLED=0),无需 target 工具链 +ifeq ($(ARCH),x86_64) + ADGH_GOARCH:=amd64 +else ifeq ($(ARCH),i386) + ADGH_GOARCH:=386 +else ifneq ($(filter aarch64%,$(ARCH)),) + ADGH_GOARCH:=arm64 +else ifneq ($(filter arm%,$(ARCH)),) + ADGH_GOARCH:=arm + ADGH_GOARM:=$(if $(findstring _v5,$(ARCH)),5,$(if $(findstring _v6,$(ARCH)),6,7)) +else ifneq ($(filter mips64el%,$(ARCH)),) + ADGH_GOARCH:=mips64le + ADGH_GOMIPS:=softfloat +else ifneq ($(filter mipsel%,$(ARCH)),) + ADGH_GOARCH:=mipsle + ADGH_GOMIPS:=softfloat +else ifneq ($(filter mips64%,$(ARCH)),) + ADGH_GOARCH:=mips64 + ADGH_GOMIPS:=softfloat +else ifneq ($(filter mips%,$(ARCH)),) + ADGH_GOARCH:=mips + ADGH_GOMIPS:=softfloat +else ifneq ($(filter riscv64%,$(ARCH)),) + ADGH_GOARCH:=riscv64 +else ifneq ($(filter powerpc64%,$(ARCH)),) + ADGH_GOARCH:=ppc64 +else ifneq ($(filter powerpc%,$(ARCH)),) + ADGH_GOARCH:=ppc +else + ADGH_GOARCH:=unsupported +endif + +ADGH_VERSION_PKG:=github.com/AdguardTeam/AdGuardHome/internal/version +ADGH_LDFLAGS:=-s -w \ + -X $(ADGH_VERSION_PKG).version=v$(PKG_VERSION) \ + -X $(ADGH_VERSION_PKG).channel=release \ + -X $(ADGH_VERSION_PKG).committime=$(if $(SOURCE_DATE_EPOCH),$(SOURCE_DATE_EPOCH),0) \ + $(if $(ADGH_GOARM),-X $(ADGH_VERSION_PKG).goarm=$(ADGH_GOARM)) \ + $(if $(ADGH_GOMIPS),-X $(ADGH_VERSION_PKG).gomips=$(ADGH_GOMIPS)) + +include $(INCLUDE_DIR)/package.mk + +define Package/AdGuardHome + SECTION:=net + CATEGORY:=Network + TITLE:=Network-wide ads and trackers blocking DNS server + URL:=https://github.com/AdguardTeam/AdGuardHome + DEPENDS:=+ca-bundle +endef + +define Package/AdGuardHome/description + Free and open source, powerful network-wide ads and trackers blocking DNS server. +endef + +define Package/$(PKG_NAME)/config +config ADGUARDHOME_COMPRESS_UPX + bool "Compress executable files with UPX" + default n +endef + +define Build/Compile + @if ! $(ADGH_GO) version 2>/dev/null | grep -qE 'go1\.(2[6-9]|[3-9][0-9])'; then \ + echo "ERROR: AdGuardHome $(PKG_VERSION) 需要 Go >= 1.26(go.mod 要求 1.26.6)"; \ + echo " 当前工具链: $$($(ADGH_GO) version 2>&1)"; \ + echo " 可用 ADGH_GO=/path/to/go 指定,准备方法见 .devcontainer/README.md"; \ + exit 1; \ + fi + @echo "AdGuardHome: 构建前端 client_v2 (npm)" + cd $(PKG_BUILD_DIR)/client_v2 && \ + PATH="$(ADGH_NODE_DIR):$(PATH)" $(ADGH_NPM) ci \ + --registry=$(ADGH_NPM_REGISTRY) --no-audit --no-fund && \ + PATH="$(ADGH_NODE_DIR):$(PATH)" $(ADGH_NPM) run build-prod + @echo "AdGuardHome: 编译后端 (GOARCH=$(ADGH_GOARCH))" + cd $(PKG_BUILD_DIR) && \ + CGO_ENABLED=0 GOOS=linux GOARCH=$(ADGH_GOARCH) \ + GOARM=$(ADGH_GOARM) GOMIPS=$(ADGH_GOMIPS) \ + GOPROXY=$(ADGH_GOPROXY) GOTOOLCHAIN=local \ + $(ADGH_GO) build -trimpath -ldflags "$(ADGH_LDFLAGS)" \ + -o $(PKG_BUILD_DIR)/AdGuardHome . +ifeq ($(CONFIG_ADGUARDHOME_COMPRESS_UPX),y) + @if [ -x $(STAGING_DIR_HOST)/bin/upx ]; then \ + $(STAGING_DIR_HOST)/bin/upx --lzma --best $(PKG_BUILD_DIR)/AdGuardHome; \ + else \ + echo "WARNING: upx 不可用,跳过压缩"; \ + fi +endif +endef + +# luci 插件默认使用 /etc/config/adGuardConfig/AdGuardHome(uci: binpath), +# 因此二进制装到该路径;/usr/bin/AdGuardHome 以符号链接提供命令行入口, +# 避免在 ipk 里重复打包 40MB 的二进制。 +define Package/AdGuardHome/install + $(INSTALL_DIR) $(1)/etc/config/adGuardConfig + $(INSTALL_BIN) $(PKG_BUILD_DIR)/AdGuardHome $(1)/etc/config/adGuardConfig/AdGuardHome + $(INSTALL_DIR) $(1)/usr/bin + $(LN) /etc/config/adGuardConfig/AdGuardHome $(1)/usr/bin/AdGuardHome +endef + +$(eval $(call BuildPackage,AdGuardHome)) diff --git a/luci-app-adguardhome/.github/workflows/build-apk.yaml b/luci-app-adguardhome/.github/workflows/build-apk.yaml new file mode 100644 index 00000000..af5e77b6 --- /dev/null +++ b/luci-app-adguardhome/.github/workflows/build-apk.yaml @@ -0,0 +1,107 @@ +name: build-apk + +on: + push: + tags: + - 'v*' + - 'dev-*' + +permissions: + contents: write + +env: + TARGET: x86 + SUBTARGET: 64 + PKG_DIR_NAME: luci-app-adguardhome + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: 检出代码 + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: 安装依赖 + run: | + sudo apt update + sudo apt install -y build-essential clang flex bison g++ gawk \ + gcc-multilib g++-multilib gettext git libncurses5-dev libssl-dev \ + python3-setuptools rsync swig unzip zlib1g-dev file wget zstd jq + + - name: 下载 SDK + run: | + set -euo pipefail + + echo "🔍 获取 OpenWrt 25.12 最新版本..." + LATEST_TAG=$( + curl -fsSL https://api.github.com/repos/openwrt/openwrt/releases?per_page=50 \ + | jq -r '.[].tag_name' \ + | grep -E '^v25\.12\.[0-9]+(-rc[0-9]+)?$' \ + | sort -V \ + | tail -n 1 + ) + + if [[ -z "${LATEST_TAG}" || "${LATEST_TAG}" == "null" ]]; then + echo "❌ 获取 OpenWrt 25.12.x tag 失败" + exit 1 + fi + + RELEASE="${LATEST_TAG#v}" + echo "检测到 25.12 系列最新版本: ${RELEASE}" + + echo "📥 下载 SDK..." + BASE="https://downloads.openwrt.org/releases/${RELEASE}/targets/${TARGET}/${SUBTARGET}/" + SDK_TARBALL=$(curl -fsSL "$BASE" | grep -o 'openwrt-sdk-[^"]*\.tar\.zst' | head -n1) + mkdir -p _sdk + wget -q "${BASE}${SDK_TARBALL}" -O _sdk/sdk.tar.zst + tar --zstd -xf _sdk/sdk.tar.zst -C _sdk + SDK_DIR="$(find "$PWD/_sdk" -maxdepth 1 -type d -name 'openwrt-sdk-*' -print -quit)" + echo "SDK_DIR=${SDK_DIR}" >> "$GITHUB_ENV" + echo "✅ SDK 已解压到: ${SDK_DIR}" + + - name: 构建包 + run: | + cd "$SDK_DIR" + + mkdir -p package/${PKG_DIR_NAME} + rsync -a --delete "$GITHUB_WORKSPACE/" "package/${PKG_DIR_NAME}/" \ + --exclude '.git*' --exclude '_sdk' + + ./scripts/feeds update -a + ./scripts/feeds install luci-base tar + + touch .config + sed -i -E '/^(# )?CONFIG_(ALL|ALL_KMODS|ALL_NONSHARED|PACKAGE_TAR_XZ|PACKAGE_luci-app-adguardhome)(=| is not set)/d' .config + printf '%s\n' \ + 'CONFIG_ALL=n' \ + 'CONFIG_ALL_KMODS=n' \ + 'CONFIG_ALL_NONSHARED=n' \ + '# CONFIG_PACKAGE_TAR_XZ is not set' \ + 'CONFIG_PACKAGE_luci-app-adguardhome=y' >> .config + make defconfig + if ! grep -Eq '^CONFIG_PACKAGE_luci-app-adguardhome=(y|m)$' .config; then + echo '::error::luci-app-adguardhome was not enabled after make defconfig' + grep -n 'luci-app-adguardhome' .config || true + sed -n '/^[[:space:]]*config PACKAGE_luci-app-adguardhome$/,/^[[:space:]]*help/p' tmp/.config-package.in + exit 1 + fi + + make package/compile -j"$(nproc)" V=sc + + - name: 上传产物 + uses: actions/upload-artifact@v4 + with: + name: pkg-${{ github.ref_name }} + path: _sdk/**/bin/packages/**/${{ env.PKG_DIR_NAME }}* + if-no-files-found: error + + - name: 发布到 Releases + uses: softprops/action-gh-release@v3 + with: + tag_name: ${{ github.ref_name }} + prerelease: ${{ startsWith(github.ref_name, 'dev-') }} + make_latest: ${{ startsWith(github.ref_name, 'dev-') && 'false' || 'true' }} + files: _sdk/**/bin/packages/**/${{ env.PKG_DIR_NAME }}* \ No newline at end of file diff --git a/luci-app-adguardhome/.gitignore b/luci-app-adguardhome/.gitignore new file mode 100644 index 00000000..496ee2ca --- /dev/null +++ b/luci-app-adguardhome/.gitignore @@ -0,0 +1 @@ +.DS_Store \ No newline at end of file diff --git a/luci-app-adguardhome/Makefile b/luci-app-adguardhome/Makefile index 04ae3eb9..64d690fb 100644 --- a/luci-app-adguardhome/Makefile +++ b/luci-app-adguardhome/Makefile @@ -1,56 +1,86 @@ -# Copyright (C) 2018-2019 Lienol # -# This is free software, licensed under the Apache License, Version 2.0 . +# Copyright (C) 2025 w9315273 +# +# This is free software, licensed under the MIT License. # include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-adguardhome -PKG_VERSION:=1.8-20221120 +PKG_VERSION:=2.4.1 PKG_RELEASE:=1 -PKG_MAINTAINER:= -LUCI_TITLE:=LuCI app for adguardhome -LUCI_DEPENDS:=+!wget&&!curl&&!wget-ssl:curl +adguardhome -LUCI_PKGARCH:=all -LUCI_DESCRIPTION:=LuCI support for adguardhome +PKG_LICENSE:=MIT +PKG_LICENSE_FILES:=LICENSE +PKG_MAINTAINER:=w9315273 +PKG_BUILD_DEPENDS:=luci-base/host + +include $(INCLUDE_DIR)/package.mk + +define Package/luci-app-adguardhome + SECTION:=luci + CATEGORY:=LuCI + SUBMENU:=3. Applications + TITLE:=LuCI app for AdGuardHome + PKG_MAINTAINER:=https://github.com/w9315273/luci-app-adguardhome + PKGARCH:=all + DEPENDS:=+luci-base +rpcd-mod-ucode +uclient-fetch +tar +endef + +define Package/luci-app-adguardhome/description + LuCI support for AdGuardHome (nftables-based DNS redirect). + This package does NOT include the AdGuardHome core binary. +endef + +define Build/Prepare +endef + +define Build/Compile +endef define Package/luci-app-adguardhome/conffiles -/usr/share/AdGuardHome/links.txt /etc/config/AdGuardHome endef +define Package/luci-app-adguardhome/install + cp -pR ./root/* $(1)/ + + $(INSTALL_DIR) $(1)/usr/lib/lua/luci/i18n + po2lmo ./po/zh-cn/AdGuardHome.po \ + $(1)/usr/lib/lua/luci/i18n/AdGuardHome.zh-cn.lmo +endef + define Package/luci-app-adguardhome/postinst #!/bin/sh - /etc/init.d/AdGuardHome enable >/dev/null 2>&1 - enable=$(uci get AdGuardHome.AdGuardHome.enabled 2>/dev/null) - if [ "$enable" == "1" ]; then - /etc/init.d/AdGuardHome reload >/dev/null 2>&1 - fi - rm -f /tmp/luci-indexcache - rm -f /tmp/luci-modulecache/* +[ -n "$${IPKG_INSTROOT}" ] && exit 0 + +/etc/init.d/AdGuardHome enable >/dev/null 2>&1 + +enable=$$(uci get AdGuardHome.AdGuardHome.enabled 2>/dev/null) +if [ "$$enable" = "1" ]; then + /etc/init.d/AdGuardHome reload +fi + +rm -f /tmp/luci-indexcache +rm -f /tmp/luci-modulecache/* +/etc/init.d/rpcd reload + exit 0 endef define Package/luci-app-adguardhome/prerm #!/bin/sh if [ -z "$${IPKG_INSTROOT}" ]; then - /etc/init.d/AdGuardHome disable - /etc/init.d/AdGuardHome stop >/dev/null 2>&1 -uci -q batch <<-EOF >/dev/null 2>&1 - delete ucitrack.@AdGuardHome[-1] - commit ucitrack + /etc/init.d/AdGuardHome disable + /etc/init.d/AdGuardHome stop 2>/dev/null || true + + uci -q batch <<-EOF >/dev/null 2>&1 + delete ucitrack.@AdGuardHome[-1] + commit ucitrack EOF fi + exit 0 endef -define Package/luci-app-adguardhome/postrm -#!/bin/sh -rm -rf /etc/AdGuardHome/ -exit 0 -endef - -include $(TOPDIR)/feeds/luci/luci.mk - -# call BuildPackage - OpenWrt buildroot signature +$(eval $(call BuildPackage,luci-app-adguardhome)) diff --git a/luci-app-adguardhome/README.md b/luci-app-adguardhome/README.md index e685548f..fef7cd77 100644 --- a/luci-app-adguardhome/README.md +++ b/luci-app-adguardhome/README.md @@ -1,93 +1,26 @@ -# luci-app-adguardhome -复杂的AdGuardHome的openwrt的luci界面 +# luci-app-adguardhome(nft 版)简要说明 - - 可以管理网页端口 - - luci下载/更新核心版本(支持自定义链接下载) - - 如果为tar.gz文件需要与官方的文件结构一致 - - 或者直接为主程序二进制 - - upx 压缩核心(xz依赖,脚本自动下载,如果opkg源无法连接,请在编译时加入此包) - - dns重定向 - - 作为dnsmasq的上游服务器(在AGH中统计到的ip都为127.0.0.1,无法统计客户端及对应调整设置,ssr-plus正常) - - 重定向53端口到 AdGuardHome(ipv6需要开启ipv6 nat redirect 否则如果客户端使用ipv6过滤无效,不以dnsmasq为上游ssr-plus失效) - - 使用53端口替换 dnsmasq(需要设置AGH的dnsip为0.0.0.0, AGH和dnsmasq的端口将被交换,不以dnsmasq为上游ssr-plus失效) - - 自定义执行文件路径(支持tmp,每次重启后自动下载bin) - - 自定义配置文件路径 - - 自定义工作路径 - - 自定义运行日志路径 - - gfwlist 删除/添加/定义上游dns服务器 另外安利一下https://github.com/rufengsuixing/luci-app-autoipsetadder - - 修改网页登陆密码 - - 倒序/正序 查看/删除/备份 每3秒更新显示运行日志 + 本地浏览器时区转换 - - 手动修改配置文件 - - 支持yaml编辑器 - - 模板快速配置 - - 系统升级保留勾选文件 - - 开机启动后当网络准备好时重启adh(3分钟超时,主要用于防止过滤器更新失败) - - 关机时备份勾选的工作目录中的文件(须知:在ipk更新的时候也会触发备份) - - 计划任务(以下为默认值,时间和参数可以在计划任务中调整) - - 自动更新核心(最好谨慎使用)(3:30/天) - - 自动截短查询日志 (每小时 限制到2000行) - - 自动截短运行日志(3:30/天 限制到2000行) - - 自动更新ipv6主机并重启adh (每小时,无更新不重启) - - 自动更新gfw列表并重启adh (3:30/天,无更新不重启) -#### 已知问题: - - db数据库不支持放在不支持mmap的文件系统上比如 jffs2 data-stk-oo,请修改工作目录,本软件如果检测到jffs2会自动ln(软连接)到/tmp,将会导致重启丢失dns数据库 - - ~~AdGuardHome 不支持ipset 设置,在使用ipset的情况下,无法替代dnsmasq只能作为dnsmasq上游存在,如果你想要这个功能就去投票吧
- https://github.com/AdguardTeam/AdGuardHome/issues/1191~~ - - 反馈出现大量127.0.0.1查询了localhost的请求,问题出现原因是ddns插件,如果不用ddns插件,请删除或者注释掉\etc\hotplug.d\iface\95-ddns的内容,如果还有其他来自本机的异常查询情况,高级玩家可以使用kmod来查找原因https://github.com/rufengsuixing/kmod-plog-port - - 如果出现需要多次提交才有反应的现象请及时提交issue -#### 使用方法 - - 下载release,使用opkg安装即可 - - 或者编译op时clone本项目加入软件包并勾选 -#### 关于压缩 -本着较真的想法,我测试了在jffs2的压缩文件系统上进行upx压缩结果的内存占用与空间占用(单位kb,使用最好压缩)
-文件大小
-源文件 14112 使用upx 压缩后 5309
-实际占用 6260 使用upx 压缩后 5324 差值为 936
-VmRSS运存占用值
-不压缩 14380 使用upx 压缩后 18496 差值 -4116
-对于压缩文件系统来说开启收益有,但不大
-如果是非压缩文件系统,性价比还是比较高的
-所以压缩是用运存空间来换rom空间,觉得值得就可以开启 -#### 关于ssr配合 - - 方法一gfw代理:dns重定向-作为dnsmasq的上游服务器 - - 方法二gfw代理:手动设置adh上游dns为自己即127.0.0.1:[自己监听的端口],然后使用 dns重定向-使用53端口替换dnsmasq,(因为端口互换后就是dnsmasq为上游了) - - 方法三国外ip代理:任意重定向方式,adh加入gfw列表,开启计划任务定时更新gfw即可 - - 方法四gfw代理:dns重定向-重定向53端口到AdGuardHome,设置adh上游dns 为127.0.0.1:53 -#### 项目已经基本稳定,有bug欢迎主动反馈 +面向已熟悉原项目的用户: 仅将 **DNS** 重定向从 `iptables` 迁移到 `nftables`, 核心语义不变 -Complex openwrt AdGuardHome luci +## 变更概览 +- `iptables` → `nftables`:使用 nft 应用/清理规则 `/var/etc/adguardhome.nft` +- 模板路径:`/usr/share/AdGuardHome/adguardhome.nft.tpl` - - can manage browser port - - download/update core in luci - - compress core with upx - - redirect dns - - as the upstream of dnsmasq - - redirect port 53 to AdGuardHome(ipv6 need to install ipv6 nat redirect or if client use ipv6 redirect is invalid) - - replace dnsmasq with port 53 (need to set AGH,dnsip=0.0.0.0,the port of dnsmasq and AGH will be exchange) - - change bin path - - change config path - - change work dir(support tmp,auto redownload after reboot) - - change runtime log path - - gfwlist query to specific dns server - - modify browser login passord - - Positive/reverse order see/del/backup runtime log which update every 3 second - - modify config manually(support yaml editor) - - use template to fast config(when no config file) - - Keep bin file and config when system upgrade (database and querylog can be choose) - - when boot wait for network access (3min timeout) - - backup workdir when shutdown -#### known issues: - - db database not support filesystem which not support mmap such as jffs2 and data-stk-oo,please modify work dir,if jffs2 is found,will auto ln (soft link)the dbs to /tmp ,will lost dns database after reboot - - ~~AdGuardhome not support ipset,when we use ipset ,it can\`t be the repacement of dnsmasq but the upstream of dnsmasq ,if you want it,vote for it.
- https://github.com/AdguardTeam/AdGuardHome/issues/1191
~~ - - find so many localhost query from 127.0.0.1,the ddns plugin is the reason,if you don\`t use ddns, please remove or comment \etc\hotplug.d\iface\95-ddns -#### usage - - download release,install it with opkg - - or when make op,clone the code to the package path and set it as y or m +## 模板与默认行为 +![01](https://github.com/user-attachments/assets/904948fc-e83e-4246-8f61-b6c978a7e073) +![02](https://github.com/user-attachments/assets/14d51f1f-f7e7-43d4-a91c-cb95a8a9e4ef) -#### pic -example in zh-cn:
-![Screenshot_2019-12-23 newifi-d1 - 基础设置 - LuCI](https://user-images.githubusercontent.com/22387141/71361626-81d60900-25ce-11ea-91d5-ac4e35d5c41e.png) -![图片](https://user-images.githubusercontent.com/22387141/71361650-90242500-25ce-11ea-9727-9306a3da1357.png) -![Screenshot_2019-12-23 newifi-d1 - 日志 - LuCI(1)](https://user-images.githubusercontent.com/22387141/71361700-b944b580-25ce-11ea-8562-f68c28952b2b.png) -![Screenshot_2019-12-23 newifi-d1 - 手动设置 - LuCI](https://user-images.githubusercontent.com/22387141/71361704-bb0e7900-25ce-11ea-8042-6dd396607030.png) + +> [!TIP] +> 从 **v2.2.0** 开始,可在 LuCI 界面的 **WAN 接口** 选项中直接选择需要排除的网卡, +> 无需再手动编辑 `adguardhome.nft.tpl` 模板文件。 +> +> 该选项 **仅在「重定向」模式下生效**, +> 用于避免将来自公网接口的 DNS 请求重定向到本机, +> 从而防止 AdGuardHome 被暴露为公共 DNS 解析器。 +> +> 请仅选择 **直接面向公网的接口**(如 `eth0`、`pppoe-wan` 等) + +## 声明 +本项目基于 https://github.com/rufengsuixing/luci-app-adguardhome 修改。 +原项目未提供明确的开源协议,当前仅用于个人学习研究,不用于商业用途。如原作者有任何异议,请联系我处理。 diff --git a/luci-app-adguardhome/install.sh b/luci-app-adguardhome/install.sh new file mode 100644 index 00000000..325cbd09 --- /dev/null +++ b/luci-app-adguardhome/install.sh @@ -0,0 +1,34 @@ +#!/bin/sh +set -e + +REPO="w9315273/luci-app-adguardhome" + +echo "Getting latest version..." +TAG=$(uclient-fetch -qO- "https://api.github.com/repos/${REPO}/releases/latest" 2>/dev/null \ + | sed -n 's/.*"tag_name":[[:space:]]*"\([^"]*\)".*/\1/p' | head -n1) + +[ -n "$TAG" ] || { echo "ERROR: Failed to get latest version"; exit 1; } + +case "$TAG" in + *-*) ;; + *) echo "ERROR: Unexpected tag format: $TAG"; exit 1 ;; +esac + +VERSION=${TAG#v} +VERSION=${VERSION%-*}-r${VERSION##*-} + +URL="https://github.com/${REPO}/releases/download/${TAG}/luci-app-adguardhome-${VERSION}.apk" + +echo "Version: $VERSION" +echo "Downloading..." +trap 'rm -f /tmp/agh.apk' EXIT +uclient-fetch -qO /tmp/agh.apk "$URL" + +echo "Installing..." +apk add --allow-untrusted /tmp/agh.apk + +echo "Refreshing LuCI..." +rm -f /tmp/luci-indexcache /tmp/luci-modulecache/* 2>/dev/null +/etc/init.d/rpcd reload >/dev/null 2>&1 + +echo "Done." \ No newline at end of file diff --git a/luci-app-adguardhome/luasrc/controller/AdGuardHome.lua b/luci-app-adguardhome/luasrc/controller/AdGuardHome.lua deleted file mode 100644 index 56de406b..00000000 --- a/luci-app-adguardhome/luasrc/controller/AdGuardHome.lua +++ /dev/null @@ -1,147 +0,0 @@ -module("luci.controller.AdGuardHome",package.seeall) -local fs=require"nixio.fs" -local http=require"luci.http" -local uci=require"luci.model.uci".cursor() -function index() -entry({"admin", "services", "AdGuardHome"},alias("admin", "services", "AdGuardHome", "base"),_("AdGuard Home"), 10).dependent = true -entry({"admin","services","AdGuardHome","base"},cbi("AdGuardHome/base"),_("Base Setting"),1).leaf = true -entry({"admin","services","AdGuardHome","log"},form("AdGuardHome/log"),_("Log"),2).leaf = true -entry({"admin","services","AdGuardHome","manual"},cbi("AdGuardHome/manual"),_("Manual Config"),3).leaf = true -entry({"admin","services","AdGuardHome","status"},call("act_status")).leaf=true -entry({"admin", "services", "AdGuardHome", "check"}, call("check_update")) -entry({"admin", "services", "AdGuardHome", "doupdate"}, call("do_update")) -entry({"admin", "services", "AdGuardHome", "getlog"}, call("get_log")) -entry({"admin", "services", "AdGuardHome", "dodellog"}, call("do_dellog")) -entry({"admin", "services", "AdGuardHome", "reloadconfig"}, call("reload_config")) -entry({"admin", "services", "AdGuardHome", "gettemplateconfig"}, call("get_template_config")) -end -function get_template_config() - local b - local d="" - local rcauto=uci:get("dhcp","@dnsmasq[0]","resolvfile") - if (rcauto == nil) then - for fle in fs.dir("/var/etc") do - if fle ~="." and fle ~=".."then - tf="/var/etc/"..fle - if string.match(tf,"/var/etc/dnsmasq.conf.") then - if tf and fs.access(tf) then - for le in io.lines(tf) do - sf=string.match (le,"^resolv%-file=(%S+)") - if (sf ~=nil) then - rcauto=sf - end - end - end - end - end - end - end - if rcauto and fs.access(rcauto) then - for cnt in io.lines(rcauto) do - b=string.match (cnt,"^[^#]*nameserver%s+([^%s]+)$") - if (b~=nil) then - d=d.." - "..b.."\n" - end - end - end - local f=io.open("/usr/share/AdGuardHome/AdGuardHome_template.yaml", "r+") - local tbl = {} - local a="" - while (1) do - a=f:read("*l") - if (a=="#bootstrap_dns") then - a=d - elseif (a=="#upstream_dns") then - a=d - elseif (a==nil) then - break - end - table.insert(tbl, a) - end - f:close() - http.prepare_content("text/plain; charset=utf-8") - http.write(table.concat(tbl, "\n")) -end -function reload_config() - fs.remove("/tmp/AdGuardHometmpconfig.yaml") - http.prepare_content("application/json") - http.write('') -end -function act_status() - local e={} - local binpath=uci:get("AdGuardHome","AdGuardHome","binpath") - e.running=luci.sys.call("pgrep "..binpath.." >/dev/null")==0 - e.redirect=(fs.readfile("/var/run/AdGredir")=="1") - http.prepare_content("application/json") - http.write_json(e) -end -function do_update() - fs.writefile("/var/run/lucilogpos","0") - http.prepare_content("application/json") - http.write('') - local arg - if luci.http.formvalue("force") == "1" then - arg="force" - else - arg="" - end - if fs.access("/var/run/update_core") then - if arg=="force" then - luci.sys.exec("kill $(pgrep /usr/share/AdGuardHome/update_core.sh) ; sh /usr/share/AdGuardHome/update_core.sh "..arg.." >/tmp/AdGuardHome_update.log 2>&1 &") - end - else - luci.sys.exec("sh /usr/share/AdGuardHome/update_core.sh "..arg.." >/tmp/AdGuardHome_update.log 2>&1 &") - end -end -function get_log() - local logfile=uci:get("AdGuardHome","AdGuardHome","logfile") - if (logfile==nil) then - http.write("no log available\n") - return - elseif (logfile=="syslog") then - if not fs.access("/var/run/AdGuardHomesyslog") then - luci.sys.exec("(/usr/share/AdGuardHome/getsyslog.sh &); sleep 1;") - end - logfile="/tmp/AdGuardHometmp.log" - fs.writefile("/var/run/AdGuardHomesyslog","1") - elseif not fs.access(logfile) then - http.write("") - return - end - http.prepare_content("text/plain; charset=utf-8") - local fdp - if fs.access("/var/run/lucilogreload") then - fdp=0 - fs.remove("/var/run/lucilogreload") - else - fdp=tonumber(fs.readfile("/var/run/lucilogpos")) or 0 - end - local f=io.open(logfile, "r+") - f:seek("set",fdp) - local a=f:read(2048000) or "" - fdp=f:seek() - fs.writefile("/var/run/lucilogpos",tostring(fdp)) - f:close() - http.write(a) -end -function do_dellog() - local logfile=uci:get("AdGuardHome","AdGuardHome","logfile") - fs.writefile(logfile,"") - http.prepare_content("application/json") - http.write('') -end -function check_update() - http.prepare_content("text/plain; charset=utf-8") - local fdp=tonumber(fs.readfile("/var/run/lucilogpos")) or 0 - local f=io.open("/tmp/AdGuardHome_update.log", "r+") - f:seek("set",fdp) - local a=f:read(2048000) or "" - fdp=f:seek() - fs.writefile("/var/run/lucilogpos",tostring(fdp)) - f:close() -if fs.access("/var/run/update_core") then - http.write(a) -else - http.write(a.."\0") -end -end diff --git a/luci-app-adguardhome/luasrc/model/cbi/AdGuardHome/base.lua b/luci-app-adguardhome/luasrc/model/cbi/AdGuardHome/base.lua deleted file mode 100644 index 1999cbdc..00000000 --- a/luci-app-adguardhome/luasrc/model/cbi/AdGuardHome/base.lua +++ /dev/null @@ -1,360 +0,0 @@ -require("luci.sys") -require("luci.util") -require("io") -local m,s,o,o1 -local fs=require"nixio.fs" -local uci=require"luci.model.uci".cursor() -local configpath=uci:get("AdGuardHome","AdGuardHome","configpath") or "/etc/AdGuardHome.yaml" -local binpath=uci:get("AdGuardHome","AdGuardHome","binpath") or "/usr/bin/AdGuardHome" -httpport=uci:get("AdGuardHome","AdGuardHome","httpport") or "3000" -m = Map("AdGuardHome", "AdGuard Home") -m.description = translate("Free and open source, powerful network-wide ads & trackers blocking DNS server.") -m:section(SimpleSection).template = "AdGuardHome/AdGuardHome_status" - -s = m:section(TypedSection, "AdGuardHome") -s.anonymous=true -s.addremove=false ----- enable -o = s:option(Flag, "enabled", translate("Enable")) -o.default = 0 -o.optional = false ----- httpport -o =s:option(Value,"httpport",translate("Browser management port")) -o.placeholder=3000 -o.default=3000 -o.datatype="port" -o.optional = false -o.description = translate("") ----- update warning not safe -local binmtime=uci:get("AdGuardHome","AdGuardHome","binmtime") or "0" -local e="" -if not fs.access(configpath) then - e=e.." "..translate("no config") -end -if not fs.access(binpath) then - e=e.." "..translate("no core") -else - local version=uci:get("AdGuardHome","AdGuardHome","version") - local testtime=fs.stat(binpath,"mtime") - if testtime~=tonumber(binmtime) or version==nil then - local tmp=luci.sys.exec(binpath.." --version 2>/dev/null | grep -m 1 -E '[0-9]+[.][Bbeta0-9\.\-]+' -o") - version=string.sub(tmp, 1, -2) - if version=="" then version="core error" end - uci:set("AdGuardHome","AdGuardHome","version",version) - uci:set("AdGuardHome","AdGuardHome","binmtime",testtime) - uci:commit("AdGuardHome") - end - e=version..e -end -o=s:option(Button,"restart",translate("Update")) -o.inputtitle=translate("Update core version") -o.template = "AdGuardHome/AdGuardHome_check" -o.showfastconfig=(not fs.access(configpath)) -o.description=string.format(translate("core version:").."%s ",e) ----- port warning not safe -local port=luci.sys.exec("awk '/^dns:/ {found_dns=1} found_dns && /^ port:/ {print $2; exit}' "..configpath.." 2>nul") -if (port=="") then port="?" end ----- Redirect -o = s:option(ListValue, "redirect", port..translate("Redirect"), translate("AdGuardHome redirect mode")) -o.placeholder = "none" -o:value("none", translate("none")) -o:value("dnsmasq-upstream", translate("Run as dnsmasq upstream server")) -o:value("redirect", translate("Redirect 53 port to AdGuardHome")) -o:value("exchange", translate("Use port 53 replace dnsmasq")) -o.default = "none" -o.optional = true ----- bin path -o = s:option(Value, "binpath", translate("Bin Path"), translate("AdGuardHome Bin path if no bin will auto download")) -o.default = "/usr/bin/AdGuardHome" -o.datatype = "string" -o.optional = false -o.rmempty=false -o.validate=function(self, value) -if value=="" then return nil end -if fs.stat(value,"type")=="dir" then - fs.rmdir(value) -end -if fs.stat(value,"type")=="dir" then - if (m.message) then - m.message =m.message.."\nerror!bin path is a dir" - else - m.message ="error!bin path is a dir" - end - return nil -end -return value -end ---- arch -o = s:option(ListValue, "arch", translate("choose Arch for download")) -o:value("",translate("Auto")) -o:value("386",translate("i386")) -o:value("amd64",translate("x86_64")) -o:value("armv5",translate("armv5")) -o:value("armv6",translate("armv6")) -o:value("armv7",translate("armv7")) -o:value("arm64",translate("aarch64")) -o:value("mips_softfloat",translate("mips")) -o:value("mips64_softfloat",translate("mips64")) -o:value("mipsle_softfloat",translate("mipsel")) -o:value("mips64le_softfloat",translate("mips64el")) -o:value("ppc64le",translate("powerpc64")) -o.description=translate("Need to save to config first before downloading.") -o.default="" -o.rmempty=true - ---- upx -o = s:option(ListValue, "upxflag", translate("use upx to compress bin after download")) -o:value("", translate("none")) -o:value("-1", translate("compress faster")) -o:value("-9", translate("compress better")) -o:value("--best", translate("compress best(can be slow for big files)")) -o:value("--brute", translate("try all available compression methods & filters [slow]")) -o:value("--ultra-brute", translate("try even more compression variants [very slow]")) -o.default = "" -o.description=translate("bin use less space,but may have compatibility issues") -o.rmempty = true ----- config path -o = s:option(Value, "configpath", translate("Config Path"), translate("AdGuardHome config path")) -o.default = "/etc/AdGuardHome.yaml" -o.datatype = "string" -o.optional = false -o.rmempty=false -o.validate=function(self, value) -if value==nil then return nil end -if fs.stat(value,"type")=="dir" then - fs.rmdir(value) -end -if fs.stat(value,"type")=="dir" then - if m.message then - m.message =m.message.."\nerror!config path is a dir" - else - m.message ="error!config path is a dir" - end - return nil -end -return value -end ----- work dir -o = s:option(Value, "workdir", translate("Work dir"), translate("AdGuardHome work dir include rules,audit log and database")) -o.default = "/etc/AdGuardHome" -o.datatype = "string" -o.optional = false -o.rmempty=false -o.validate=function(self, value) -if value=="" then return nil end -if fs.stat(value,"type")=="reg" then - if m.message then - m.message =m.message.."\nerror!work dir is a file" - else - m.message ="error!work dir is a file" - end - return nil -end -if string.sub(value, -1)=="/" then - return string.sub(value, 1, -2) -else - return value -end -end ----- log file -o = s:option(Value, "logfile", translate("Runtime log file"), translate("AdGuardHome runtime Log file if 'syslog': write to system log;if empty no log")) -o.datatype = "string" -o.rmempty = true -o.validate=function(self, value) -if fs.stat(value,"type")=="dir" then - fs.rmdir(value) -end -if fs.stat(value,"type")=="dir" then - if m.message then - m.message =m.message.."\nerror!log file is a dir" - else - m.message ="error!log file is a dir" - end - return nil -end -return value -end ----- debug -o = s:option(Flag, "verbose", translate("Verbose log")) -o.default = 0 -o.optional = true ----- gfwlist -local a -if fs.access(configpath) then -a=luci.sys.call("grep -m 1 -q programadd "..configpath) -else -a=1 -end -if (a==0) then -a="Added" -else -a="Not added" -end -o=s:option(Button,"gfwdel",translate("Del gfwlist"),translate(a)) -o.optional = true -o.inputtitle=translate("Del") -o.write=function() - luci.sys.exec("sh /usr/share/AdGuardHome/gfw2adg.sh del 2>&1") - luci.http.redirect(luci.dispatcher.build_url("admin","services","AdGuardHome")) -end -o=s:option(Button,"gfwadd",translate("Add gfwlist"),translate(a)) -o.optional = true -o.inputtitle=translate("Add") -o.write=function() - luci.sys.exec("sh /usr/share/AdGuardHome/gfw2adg.sh 2>&1") - luci.http.redirect(luci.dispatcher.build_url("admin","services","AdGuardHome")) -end -if fs.access(configpath) then -a=luci.sys.call("grep -m 1 -q ipset.txt "..configpath) -else -a=1 -end -if (a==0) then -a="Added" -else -a="Not added" -end -o=s:option(Button,"gfwipsetdel",translate("Del gfwlist")..translate("(ipset only)"),translate(a)) -o.optional = true -o.inputtitle=translate("Del") -o.write=function() - luci.sys.exec("sh /usr/share/AdGuardHome/gfwipset2adg.sh del 2>&1") - luci.http.redirect(luci.dispatcher.build_url("admin","services","AdGuardHome")) -end -o=s:option(Button,"gfwipsetadd",translate("Add gfwlist")..translate("(ipset only)"),translate(a).." "..translate("will set to name gfwlist")) -o.optional = true -o.inputtitle=translate("Add") -o.write=function() - luci.sys.exec("sh /usr/share/AdGuardHome/gfwipset2adg.sh 2>&1") - luci.http.redirect(luci.dispatcher.build_url("admin","services","AdGuardHome")) -end -o = s:option(Value, "gfwupstream", translate("Gfwlist upstream dns server"), translate("Gfwlist domain upstream dns service")..translate(a)) -o.default = "tcp://208.67.220.220:5353" -o.datatype = "string" -o.optional = true ----- chpass -o = s:option(Value, "hashpass", translate("Change browser management password"), translate("Press load culculate model and culculate finally save/apply")) -o.default = "" -o.datatype = "string" -o.template = "AdGuardHome/AdGuardHome_chpass" -o.optional = true ----- upgrade protect -o = s:option(MultiValue, "upprotect", translate("Keep files when system upgrade")) -o:value("$binpath",translate("core bin")) -o:value("$configpath",translate("config file")) -o:value("$logfile",translate("log file")) -o:value("$workdir/data/sessions.db",translate("sessions.db")) -o:value("$workdir/data/stats.db",translate("stats.db")) -o:value("$workdir/data/querylog.json",translate("querylog.json")) -o:value("$workdir/data/filters",translate("filters")) -o.widget = "checkbox" -o.default = nil -o.optional=true ----- wait net on boot -o = s:option(Flag, "waitonboot", translate("On boot when network ok restart")) -o.default = 1 -o.optional = true ----- backup workdir on shutdown -local workdir=uci:get("AdGuardHome","AdGuardHome","workdir") or "/etc/AdGuardHome" -o = s:option(MultiValue, "backupfile", translate("Backup workdir files when shutdown")) -o1 = s:option(Value, "backupwdpath", translate("Backup workdir path")) -local name -o:value("filters","filters") -o:value("stats.db","stats.db") -o:value("querylog.json","querylog.json") -o:value("sessions.db","sessions.db") -o1:depends ("backupfile", "filters") -o1:depends ("backupfile", "stats.db") -o1:depends ("backupfile", "querylog.json") -o1:depends ("backupfile", "sessions.db") -for name in fs.glob(workdir.."/data/*") -do - name=fs.basename (name) - if name~="filters" and name~="stats.db" and name~="querylog.json" and name~="sessions.db" then - o:value(name,name) - o1:depends ("backupfile", name) - end -end -o.widget = "checkbox" -o.default = nil -o.optional=false -o.description=translate("Will be restore when workdir/data is empty") -----backup workdir path - -o1.default = "/etc/AdGuardHome" -o1.datatype = "string" -o1.optional = false -o1.validate=function(self, value) -if fs.stat(value,"type")=="reg" then - if m.message then - m.message =m.message.."\nerror!backup dir is a file" - else - m.message ="error!backup dir is a file" - end - return nil -end -if string.sub(value,-1)=="/" then - return string.sub(value, 1, -2) -else - return value -end -end - -----Crontab -o = s:option(MultiValue, "crontab", translate("Crontab task"),translate("Please change time and args in crontab")) -o:value("autoupdate",translate("Auto update core")) -o:value("cutquerylog",translate("Auto tail querylog")) -o:value("cutruntimelog",translate("Auto tail runtime log")) -o:value("autohost",translate("Auto update ipv6 hosts and restart adh")) -o:value("autogfw",translate("Auto update gfwlist and restart adh")) -o:value("autogfwipset",translate("Auto update ipset list and restart adh")) -o.widget = "checkbox" -o.default = nil -o.optional=true - -----downloadtarge -o = s:option(ListValue, "tagname", translate("Choose Release Version for download")) -o:value("release",translate("Release(Default)")) -o:value("beta",translate("Beta")) -o.description=translate("If this option is modified, please confirm the download links") -o.default="release" -o.rmempty=true - -----downloadpath -o = s:option(TextValue, "downloadlinks",translate("Download links for update")) -o.optional = false -o.rows = 4 -o.wrap = "soft" -o.cfgvalue = function(self, section) - return fs.readfile("/usr/share/AdGuardHome/links.txt") -end -o.write = function(self, section, value) - fs.writefile("/usr/share/AdGuardHome/links.txt", value:gsub("\r\n", "\n")) -end -fs.writefile("/var/run/lucilogpos","0") -function m.on_commit(map) - if (fs.access("/var/run/AdGserverdis")) then - io.popen("/etc/init.d/AdGuardHome reload &") - return - end - local ucitracktest=uci:get("AdGuardHome","AdGuardHome","ucitracktest") - if ucitracktest=="1" then - return - elseif ucitracktest=="0" then - io.popen("/etc/init.d/AdGuardHome reload &") - else - if (fs.access("/var/run/AdGlucitest")) then - uci:set("AdGuardHome","AdGuardHome","ucitracktest","0") - io.popen("/etc/init.d/AdGuardHome reload &") - else - fs.writefile("/var/run/AdGlucitest","") - if (ucitracktest=="2") then - uci:set("AdGuardHome","AdGuardHome","ucitracktest","1") - else - uci:set("AdGuardHome","AdGuardHome","ucitracktest","2") - end - end - uci:commit("AdGuardHome") - end -end -return m diff --git a/luci-app-adguardhome/luasrc/model/cbi/AdGuardHome/log.lua b/luci-app-adguardhome/luasrc/model/cbi/AdGuardHome/log.lua deleted file mode 100644 index 5d18a88d..00000000 --- a/luci-app-adguardhome/luasrc/model/cbi/AdGuardHome/log.lua +++ /dev/null @@ -1,16 +0,0 @@ -local fs=require"nixio.fs" -local uci=require"luci.model.uci".cursor() -local f,t -f=SimpleForm("logview") -f.reset = false -f.submit = false -t=f:field(TextValue,"conf") -t.rmempty=true -t.rows=20 -t.template="AdGuardHome/log" -t.readonly="readonly" -local logfile=uci:get("AdGuardHome","AdGuardHome","logfile") or "" -t.timereplace=(logfile~="syslog" and logfile~="" ) -t.pollcheck=logfile~="" -fs.writefile("/var/run/lucilogreload","") -return f diff --git a/luci-app-adguardhome/luasrc/model/cbi/AdGuardHome/manual.lua b/luci-app-adguardhome/luasrc/model/cbi/AdGuardHome/manual.lua deleted file mode 100644 index 2e5a5cb8..00000000 --- a/luci-app-adguardhome/luasrc/model/cbi/AdGuardHome/manual.lua +++ /dev/null @@ -1,117 +0,0 @@ -local m, s, o -local fs = require "nixio.fs" -local uci=require"luci.model.uci".cursor() -local sys=require"luci.sys" -require("string") -require("io") -require("table") -function gen_template_config() - local b - local d="" - local rcauto=uci:get("dhcp","@dnsmasq[0]","resolvfile") - if (rcauto == nil) then - for fle in fs.dir("/var/etc") do - if fle ~="." and fle ~=".."then - tf="/var/etc/"..fle - if string.match(tf,"/var/etc/dnsmasq.conf.") then - if tf and fs.access(tf) then - for le in io.lines(tf) do - sf=string.match (le,"^resolv%-file=(%S+)") - if (sf ~=nil) then - rcauto=sf - end - end - end - end - end - end - end - if rcauto and fs.access(rcauto) then - for cnt in io.lines(rcauto) do - b=string.match (cnt,"^[^#]*nameserver%s+([^%s]+)$") - if (b~=nil) then - d=d.." - "..b.."\n" - end - end - end - local f=io.open("/usr/share/AdGuardHome/AdGuardHome_template.yaml", "r+") - local tbl = {} - local a="" - while (1) do - a=f:read("*l") - if (a=="#bootstrap_dns") then - a=d - elseif (a=="#upstream_dns") then - a=d - elseif (a==nil) then - break - end - table.insert(tbl, a) - end - f:close() - return table.concat(tbl, "\n") -end -m = Map("AdGuardHome") -local configpath = uci:get("AdGuardHome","AdGuardHome","configpath") -local binpath = uci:get("AdGuardHome","AdGuardHome","binpath") -s = m:section(TypedSection, "AdGuardHome") -s.anonymous=true -s.addremove=false ---- config -o = s:option(TextValue, "escconf") -o.rows = 66 -o.wrap = "off" -o.rmempty = true -o.cfgvalue = function(self, section) - return fs.readfile("/tmp/AdGuardHometmpconfig.yaml") or fs.readfile(configpath) or gen_template_config() or "" -end -o.validate=function(self, value) - fs.writefile("/tmp/AdGuardHometmpconfig.yaml", value:gsub("\r\n", "\n")) - if fs.access(binpath) then - if (sys.call(binpath.." -c /tmp/AdGuardHometmpconfig.yaml --check-config 2> /tmp/AdGuardHometest.log")==0) then - return value - end - else - return value - end - luci.http.redirect(luci.dispatcher.build_url("admin","services","AdGuardHome","manual")) - return nil -end -o.write = function(self, section, value) - fs.move("/tmp/AdGuardHometmpconfig.yaml",configpath) -end -o.remove = function(self, section, value) - fs.writefile(configpath, "") -end ---- js and reload button -o = s:option(DummyValue, "") -o.anonymous=true -o.template = "AdGuardHome/yamleditor" -if not fs.access(binpath) then - o.description=translate("WARNING!!! no bin found apply config will not be test") -end ---- log -if (fs.access("/tmp/AdGuardHometmpconfig.yaml")) then -local c=fs.readfile("/tmp/AdGuardHometest.log") -if (c~="") then -o = s:option(TextValue, "") -o.readonly=true -o.rows = 5 -o.rmempty = true -o.name="" -o.cfgvalue = function(self, section) - return fs.readfile("/tmp/AdGuardHometest.log") -end -end -end -function m.on_commit(map) - local ucitracktest=uci:get("AdGuardHome","AdGuardHome","ucitracktest") - if ucitracktest=="1" then - return - elseif ucitracktest=="0" then - io.popen("/etc/init.d/AdGuardHome reload &") - else - fs.writefile("/var/run/AdGlucitest","") - end -end -return m \ No newline at end of file diff --git a/luci-app-adguardhome/luasrc/view/AdGuardHome/AdGuardHome_check.htm b/luci-app-adguardhome/luasrc/view/AdGuardHome/AdGuardHome_check.htm deleted file mode 100644 index 52315ef7..00000000 --- a/luci-app-adguardhome/luasrc/view/AdGuardHome/AdGuardHome_check.htm +++ /dev/null @@ -1,78 +0,0 @@ -<%+cbi/valueheader%> -<%local fs=require"nixio.fs"%> - - -<% if self.showfastconfig then %> - -<%end%> - - -<%+cbi/valuefooter%> \ No newline at end of file diff --git a/luci-app-adguardhome/luasrc/view/AdGuardHome/AdGuardHome_chpass.htm b/luci-app-adguardhome/luasrc/view/AdGuardHome/AdGuardHome_chpass.htm deleted file mode 100644 index 440eafd9..00000000 --- a/luci-app-adguardhome/luasrc/view/AdGuardHome/AdGuardHome_chpass.htm +++ /dev/null @@ -1,49 +0,0 @@ -<%+cbi/valueheader%> - - 0, "data-choices", { self.keylist, self.vallist }) - %> /> - <% if self.password then %><% end %> - -<%+cbi/valuefooter%> \ No newline at end of file diff --git a/luci-app-adguardhome/luasrc/view/AdGuardHome/AdGuardHome_status.htm b/luci-app-adguardhome/luasrc/view/AdGuardHome/AdGuardHome_status.htm deleted file mode 100644 index 7e924d11..00000000 --- a/luci-app-adguardhome/luasrc/view/AdGuardHome/AdGuardHome_status.htm +++ /dev/null @@ -1,27 +0,0 @@ - - -
-

- <%:Collecting data...%> -

-
\ No newline at end of file diff --git a/luci-app-adguardhome/luasrc/view/AdGuardHome/log.htm b/luci-app-adguardhome/luasrc/view/AdGuardHome/log.htm deleted file mode 100644 index 44b3da0b..00000000 --- a/luci-app-adguardhome/luasrc/view/AdGuardHome/log.htm +++ /dev/null @@ -1,110 +0,0 @@ -<%+cbi/valueheader%> -<%:reverse%> -<%if self.timereplace then%> -<%:localtime%>
-<%end%> - - - - -<%+cbi/valuefooter%> \ No newline at end of file diff --git a/luci-app-adguardhome/luasrc/view/AdGuardHome/yamleditor.htm b/luci-app-adguardhome/luasrc/view/AdGuardHome/yamleditor.htm deleted file mode 100644 index 639cb998..00000000 --- a/luci-app-adguardhome/luasrc/view/AdGuardHome/yamleditor.htm +++ /dev/null @@ -1,39 +0,0 @@ -<%+cbi/valueheader%> - - - - - - - - - -<%fs=require"nixio.fs"%> -<%if fs.access("/tmp/AdGuardHometmpconfig.yaml") then%> - -<%end%> - -<%+cbi/valuefooter%> \ No newline at end of file diff --git a/luci-app-adguardhome/po/zh-cn/AdGuardHome.po b/luci-app-adguardhome/po/zh-cn/AdGuardHome.po index 1c6e09fd..d6a9ea6c 100644 --- a/luci-app-adguardhome/po/zh-cn/AdGuardHome.po +++ b/luci-app-adguardhome/po/zh-cn/AdGuardHome.po @@ -1,7 +1,3 @@ - -msgid "" -msgstr "" - #/cgi-bin/luci/admin/services/AdGuardHome msgid "Base Setting" msgstr "基础设置" @@ -9,115 +5,47 @@ msgstr "基础设置" msgid "Log" msgstr "日志" +msgid "Basic Settings" +msgstr "基础设置" + msgid "Manual Config" msgstr "手动设置" -msgid "Free and open source, powerful network-wide ads & trackers blocking DNS server." -msgstr "免费开源,功能强大的全网络广告和跟踪程序拦截DNS服务器" - -msgid "RUNNING" -msgstr "运行中" - -msgid "NOT RUNNING" -msgstr "未运行" - -msgid "Redirected" -msgstr "已重定向" - -msgid "Not redirect" -msgstr "未重定向" - -msgid "Collecting data..." -msgstr "获取数据中..." - msgid "Enable" msgstr "启用" msgid "Browser management port" msgstr "网页管理端口" -msgid "Update" -msgstr "更新" +msgid "Redirect mode" +msgstr "重定向模式" -#button change -msgid "Update core version" -msgstr "更新核心版本" +msgid "AdGuardHome redirect mode" +msgstr "AdGuardHome 重定向模式" -msgid "Check..." -msgstr "检查中..." - -msgid "Updated" -msgstr "已更新" - -#button hide -msgid "Force update" -msgstr "强制更新" - -msgid "Fast config" -msgstr "快速配置" - -msgid "core version:" -msgstr "核心版本:" - -#description change -msgid "no config" -msgstr "没有配置文件" - -msgid "no core" -msgstr "没有核心" - -msgid "Redirect" -msgstr "重定向" - -#inlist -msgid "none" +msgid "None" msgstr "无" msgid "Run as dnsmasq upstream server" -msgstr "作为dnsmasq的上游服务器" +msgstr "作为 dnsmasq 的上游服务器" -msgid "Redirect 53 port to AdGuardHome" -msgstr "重定向53端口到AdGuardHome" +msgid "Redirect port 53 to AdGuardHome" +msgstr "将 53 端口重定向到 AdGuardHome" -msgid "Use port 53 replace dnsmasq" -msgstr "使用53端口替换dnsmasq" +msgid "Use port 53 instead of dnsmasq" +msgstr "使用 53 端口替代 dnsmasq" -msgid "AdGuardHome redirect mode" -msgstr "AdGuardHome重定向模式" +msgid "WAN Interface" +msgstr "WAN 接口" + +msgid "Only effective in redirect mode, bypass specified interfaces to avoid becoming a public DNS resolver" +msgstr "仅在重定向模式下生效,绕过指定接口以避免成为公共 DNS 解析器" msgid "Bin Path" msgstr "执行文件路径" -msgid "AdGuardHome Bin path if no bin will auto download" -msgstr "AdGuardHome 执行文件路径 如果没有执行文件将自动下载" - -msgid "choose Arch for download" -msgstr "要下载的程序架构" - -msgid "Need to save to config first before downloading." -msgstr "手动下载前如该选项有变更需先保存并应用后再点下载。" - -msgid "use upx to compress bin after download" -msgstr "下载后使用upx压缩执行文件" - -#inlist -msgid "compress faster" -msgstr "快速压缩" - -msgid "compress better" -msgstr "更好的压缩" - -msgid "compress best(can be slow for big files)" -msgstr "最好的压缩(大文件可能慢)" - -msgid "try all available compression methods & filters [slow]" -msgstr "尝试所有可能的压缩方法和过滤器[慢]" - -msgid "try even more compression variants [very slow]" -msgstr "尝试更多变体压缩手段[很慢]" - -msgid "bin use less space,but may have compatibility issues" -msgstr "减小执行文件空间占用,但是可能压缩后有兼容性问题" +msgid "AdGuardHome binary path" +msgstr "AdGuardHome 执行文件路径" msgid "Config Path" msgstr "配置文件路径" @@ -128,143 +56,102 @@ msgstr "AdGuardHome 配置文件路径" msgid "Work dir" msgstr "工作目录" -msgid "AdGuardHome work dir include rules,audit log and database" -msgstr "AdGuardHome 工作目录包含规则,审计日志和数据库" +msgid "AdGuardHome work dir including rules, audit log and database" +msgstr "AdGuardHome 工作目录,包含规则、审计日志和数据库" msgid "Runtime log file" -msgstr "运行日志" +msgstr "运行日志文件" -msgid "AdGuardHome runtime Log file if 'syslog': write to system log;if empty no log" -msgstr "AdGuardHome 运行日志 如果填syslog将写入系统日志;如果空则不记录日志" +msgid "If set to \"syslog\", write to system log; if empty, disable file logging" +msgstr "设为 \"syslog\" 时写入系统日志;留空则禁用文件日志" msgid "Verbose log" msgstr "详细日志" -#hide div -msgid "Add gfwlist" -msgstr "加入gfw列表" +msgid "Upgrade Core" +msgstr "更新核心" -msgid "Add" -msgstr "添加" +msgid "Update core version" +msgstr "更新核心版本" -msgid "Added" -msgstr "已添加" +msgid "Force update" +msgstr "强制更新" -msgid "Not added" -msgstr "未添加" +msgid "Check..." +msgstr "检查中..." -#hide div -msgid "Del gfwlist" -msgstr "删除gfw列表" +msgid "Updated" +msgstr "已更新" -msgid "Del" -msgstr "删除" +msgid "Update failed" +msgstr "更新失败" -msgid "will set to name gfwlist" -msgstr "将会配置给名为gfwlist的ipset集合" +msgid "Update in progress" +msgstr "更新正在进行中" -msgid "(ipset only)" -msgstr "(仅ipset)" +msgid "Unable to start update" +msgstr "无法启动更新" -#hide div -msgid "Gfwlist upstream dns server" -msgstr "gfw列表上游服务器" +msgid "Update status unavailable" +msgstr "更新状态不可用" -msgid "Gfwlist domain upstream dns service" -msgstr "gfw列表域名上游服务器" +msgid "AdGuardHome Web" +msgstr "AdGuardHome 仪表盘" -#hide div msgid "Change browser management password" -msgstr "改变网页登录密码" +msgstr "修改网页登录密码" -msgid "Culculate" -msgstr "计算" +msgid "New password" +msgstr "新密码" -##button change -msgid "Load culculate model" -msgstr "载入计算模块" - -msgid "loading..." -msgstr "载入中" - -msgid "Please save/apply" -msgstr "请提交" +msgid "Change password" +msgstr "修改密码" msgid "is empty" msgstr "为空" -msgid "Press load culculate model and culculate finally save/apply" -msgstr "按载入计算模块 然后计算 最后保存/提交" +msgid "Changing password..." +msgstr "正在修改密码..." -msgid "Keep files when system upgrade" -msgstr "系统升级时保留文件" +msgid "Password changed" +msgstr "密码已修改" -#checkbox -msgid "core bin" -msgstr "核心执行文件" +msgid "Password change failed" +msgstr "密码修改失败" -msgid "config file" -msgstr "配置文件" +msgid "Core" +msgstr "核心" -msgid "log file" -msgstr "日志文件" +msgid "core error" +msgstr "核心错误" -msgid "querylog.json" -msgstr "审计日志.json" +msgid "no core" +msgstr "没有核心" -msgid "On boot when network ok restart" -msgstr "开机后网络准备好时重启" +msgid "no config" +msgstr "没有配置文件" -msgid "Backup workdir files when shutdown" -msgstr "在关机时备份工作目录文件" +msgid "RUNNING" +msgstr "运行中" -msgid "Will be restore when workdir/data is empty" -msgstr "在工作目录/data为空的时候恢复" +msgid "NOT RUNNING" +msgstr "未运行" -msgid "Backup workdir path" -msgstr "工作目录备份路径" +msgid "Redirect" +msgstr "重定向" -msgid "Crontab task" -msgstr "计划任务" +msgid "Redirected" +msgstr "已重定向" -msgid "Auto update core" -msgstr "自动升级核心" +msgid "Not redirect" +msgstr "未重定向" -msgid "Auto tail querylog" -msgstr "自动截短查询日志" +msgid "Collecting data..." +msgstr "获取数据中..." -msgid "Auto tail runtime log" -msgstr "自动截短运行日志" +msgid "Status unavailable" +msgstr "状态不可用" -msgid "Auto update ipv6 hosts and restart adh" -msgstr "自动更新ipv6主机并重启adh" - -msgid "Auto update gfwlist and restart adh" -msgstr "自动更新gfw列表并重启adh" - -msgid "Auto update ipset list and restart adh" -msgstr "自动更新gfw ipset列表并重启adh" - -msgid "Please change time and args in crontab" -msgstr "请在计划任务中修改时间和参数" - -#download option -msgid "Choose Release Version for download" -msgstr "选择需要更新的版本" - -msgid "Release(Default)" -msgstr "正式版(默认)" - -msgid "Beta" -msgstr "测试版" - -msgid "If this option is modified, please confirm the download links" -msgstr "如果修改了版本选项,请自行确认下方升级用的下载链接" - -msgid "Download links for update" -msgstr "升级用的下载链接" - -#/cgi-bin/luci/admin/services/AdGuardHome/log/ msgid "reverse" msgstr "逆序" @@ -272,7 +159,7 @@ msgid "localtime" msgstr "本地时间" msgid "Please add log path in config to enable log" -msgstr "请在设置里填写日志路径以启用日志" +msgstr "请在设置中填写日志路径以启用日志" msgid "dellog" msgstr "删除日志" @@ -280,32 +167,44 @@ msgstr "删除日志" msgid "download log" msgstr "下载日志" -#/cgi-bin/luci/admin/services/AdGuardHome/manual/ msgid "Use template" msgstr "使用模板" -#hide button msgid "Reload Config" msgstr "重新载入配置" -msgid "WARNING!!! no bin found apply config will not be test" -msgstr "警告!!!未找到执行文件,提交配置将不会进行校验" +msgid "Save & Apply" +msgstr "保存并应用" -#unused -msgid "Change browser management username" -msgstr "改变网页登录用户名" +msgid "Unable to read config" +msgstr "无法读取配置" -msgid "Username" -msgstr "用户名" +msgid "Unable to load editor" +msgstr "无法加载编辑器" -msgid "Check Config" -msgstr "检查配置" +msgid "Saving..." +msgstr "正在保存..." -msgid "unknown" -msgstr "未知" +msgid "Saved" +msgstr "已保存" -msgid "Keep database when system upgrade" -msgstr "系统升级时保留数据" +msgid "Save failed" +msgstr "保存失败" -msgid "Boot delay until network ok" -msgstr "开机时直到网络准备好再启动" +msgid "Core is missing; configuration cannot be validated before saving." +msgstr "核心文件不存在,保存前无法校验配置。" + +msgid "Saved without validation (core missing)." +msgstr "已保存,但因核心文件不存在而未校验。" + +msgid "Configuration validation failed." +msgstr "配置校验失败。" + +msgid "Service reload failed; previous config restored." +msgstr "服务重载失败,已恢复原配置。" + +msgid "Restore failed; backup at" +msgstr "恢复失败,备份文件位于" + +msgid "Config changed on disk; reload before saving." +msgstr "磁盘上的配置已变化,请重新读取后再保存。" diff --git a/luci-app-adguardhome/root/etc/config/AdGuardHome b/luci-app-adguardhome/root/etc/config/AdGuardHome index 6ecfde8b..fb562bb1 100644 --- a/luci-app-adguardhome/root/etc/config/AdGuardHome +++ b/luci-app-adguardhome/root/etc/config/AdGuardHome @@ -1,11 +1,11 @@ + config AdGuardHome 'AdGuardHome' option enabled '0' option httpport '3000' option redirect 'none' option configpath '/etc/AdGuardHome.yaml' - option workdir '/etc/AdGuardHome' + option workdir '/tmp/AdGuardHome' option logfile '/tmp/AdGuardHome.log' option verbose '0' - option binpath '/usr/bin/AdGuardHome' - option upxflag '' - + option binpath '/usr/bin/AdGuardHome/AdGuardHome' + list wan_ifname 'eth0' diff --git a/luci-app-adguardhome/root/etc/init.d/AdGuardHome b/luci-app-adguardhome/root/etc/init.d/AdGuardHome old mode 100644 new mode 100755 index a4ef49d8..c5fc4579 --- a/luci-app-adguardhome/root/etc/init.d/AdGuardHome +++ b/luci-app-adguardhome/root/etc/init.d/AdGuardHome @@ -6,604 +6,435 @@ START=95 STOP=01 CONFIGURATION=AdGuardHome -CRON_FILE=/etc/crontabs/root -GFWSET="gfwlist" -EXTRA_COMMANDS="do_redirect testbackup test_crontab force_reload isrunning" +NFT_RULES_FILE="/var/etc/adguardhome.nft" +NFT_RULES_TPL="/usr/share/AdGuardHome/adguardhome.nft.tpl" +NFT_TABLE="adguardhome" + +EXTRA_COMMANDS="do_redirect force_reload isrunning" EXTRA_HELP=" do_redirect 0 or 1\ - testbackup backup or restore\ - test_crontab force_reload isrunning" -set_forward_dnsmasq() -{ - local PORT="$1" - addr="127.0.0.1#$PORT" - OLD_SERVER="`uci get dhcp.@dnsmasq[0].server 2>/dev/null`" - echo $OLD_SERVER | grep "^$addr" >/dev/null 2>&1 - if [ $? -eq 0 ]; then - return - fi - uci delete dhcp.@dnsmasq[0].server 2>/dev/null - uci add_list dhcp.@dnsmasq[0].server=$addr - for server in $OLD_SERVER; do - if [ "$server" = "$addr" ]; then - continue - fi - uci add_list dhcp.@dnsmasq[0].server=$server - done - uci delete dhcp.@dnsmasq[0].resolvfile 2>/dev/null - uci set dhcp.@dnsmasq[0].noresolv=1 - uci commit dhcp - /etc/init.d/dnsmasq restart + +get_wan_return_line() { + local wan + wan="$(uci -q get AdGuardHome.AdGuardHome.wan_ifname 2>/dev/null)" + + [ -z "$wan" ] && { echo ""; return 0; } + + local wan_set + wan_set="\"$(printf '%s' "$wan" | sed 's/ /\", \"/g')\"" + echo "iifname { $wan_set } return" } -stop_forward_dnsmasq() -{ - local OLD_PORT="$1" - addr="127.0.0.1#$OLD_PORT" - OLD_SERVER="`uci get dhcp.@dnsmasq[0].server 2>/dev/null`" - echo $OLD_SERVER | grep "^$addr" >/dev/null 2>&1 - if [ $? -ne 0 ]; then - return - fi +set_nft_redirect() { + local port="$1" + case "$port" in + *[!0-9]*|"") return 1 ;; + esac + [ ! -f "$NFT_RULES_TPL" ] && return 1 - uci del_list dhcp.@dnsmasq[0].server=$addr 2>/dev/null - rcauto="/tmp/resolv.conf.auto" - if [ -e "/tmp/resolv.conf.d/resolv.conf.auto" ]; then - rcauto="/tmp/resolv.conf.d/resolv.conf.auto" - fi - addrlist="`uci get dhcp.@dnsmasq[0].server 2>/dev/null`" - if [ -z "$addrlist" ] ; then - uci set dhcp.@dnsmasq[0].resolvfile="$rcauto" 2>/dev/null - uci delete dhcp.@dnsmasq[0].noresolv 2>/dev/null - fi - uci commit dhcp - /etc/init.d/dnsmasq restart + local return_line + return_line="$(get_wan_return_line)" + + awk -v port="$port" -v wan_line="$return_line" '{ + gsub("__AGH_PORT__", port) + gsub("__AGH_WAN_RETURN_LINE__", wan_line) + print + }' "$NFT_RULES_TPL" > "$NFT_RULES_FILE" || return 1 + + [ ! -s "$NFT_RULES_FILE" ] && return 1 + + nft delete table inet "$NFT_TABLE" 2>/dev/null + nft -f "$NFT_RULES_FILE" 2>/dev/null } -set_iptable() -{ - section=$(uci add firewall redirect) - if [ -z "$section" ]; then - echo "failed to add redirect rule." - exit 1 - fi - uci set firewall.$section.target="DNAT" - uci set firewall.$section.name="AdGuard Home" - uci set firewall.$section.src="lan" - uci set firewall.$section.src_dport="53" - uci set firewall.$section.dest_port="$AdGuardHome_PORT" - uci commit firewall - /etc/init.d/firewall reload - echo "firewall rules updated." +clear_nft_redirect() { + uci -q delete firewall.adguardhome_nft_include + uci -q commit firewall + [ -f "$NFT_RULES_FILE" ] && rm -f "$NFT_RULES_FILE" + nft delete table inet "$NFT_TABLE" 2>/dev/null } -clear_iptable() -{ - redirects=$(uci show firewall | grep "AdGuard Home" | awk -F".name" '{print $1}') - if [ -z "$redirects" ]; then - echo "no redirect rules found." - else - for redirect in $redirects; do - uci delete $redirect - echo "deleted redirect rule: $redirect" - done - uci commit firewall - /etc/init.d/firewall reload - echo "firewall rules updated." - fi +set_forward_dnsmasq() { + local PORT="$1" + addr="127.0.0.1#$PORT" + OLD_SERVER="$(uci get dhcp.@dnsmasq[0].server 2>/dev/null)" + echo "$OLD_SERVER" | grep -q "^$addr" && return 0 + uci delete dhcp.@dnsmasq[0].server 2>/dev/null + uci add_list dhcp.@dnsmasq[0].server="$addr" + for server in $OLD_SERVER; do + [ "$server" = "$addr" ] && continue + uci add_list dhcp.@dnsmasq[0].server="$server" + done + uci delete dhcp.@dnsmasq[0].resolvfile 2>/dev/null + uci set dhcp.@dnsmasq[0].noresolv=1 + uci commit dhcp + /etc/init.d/dnsmasq restart >/dev/null 2>&1 +} + +stop_forward_dnsmasq() { + local OLD_PORT="$1" + addr="127.0.0.1#$OLD_PORT" + OLD_SERVER="$(uci get dhcp.@dnsmasq[0].server 2>/dev/null)" + echo "$OLD_SERVER" | grep -q "^$addr" || return 0 + + uci del_list dhcp.@dnsmasq[0].server="$addr" 2>/dev/null + addrlist="$(uci get dhcp.@dnsmasq[0].server 2>/dev/null)" + if [ -z "$addrlist" ]; then + resolvfile="/tmp/resolv.conf.d/resolv.conf.auto" + [ ! -f "$resolvfile" ] && resolvfile="/tmp/resolv.conf.auto" + uci set dhcp.@dnsmasq[0].resolvfile="$resolvfile" 2>/dev/null + uci delete dhcp.@dnsmasq[0].noresolv 2>/dev/null + fi + uci commit dhcp + /etc/init.d/dnsmasq restart >/dev/null 2>&1 +} + +agh_reload() { + ubus call service event '{"type":"config.change","data":{"package":"AdGuardHome"}}' >/dev/null 2>&1 +} + +rm_port53() { + local configpath + config_get configpath $CONFIGURATION configpath "/etc/AdGuardHome.yaml" + + local AdGuardHome_PORT dnsmasq_port binpath + AdGuardHome_PORT=$(config_editor "dns.port" "" "$configpath" "1") + dnsmasq_port=$(uci get dhcp.@dnsmasq[0].port 2>/dev/null) + [ -z "$dnsmasq_port" ] && dnsmasq_port="53" + + if [ "$dnsmasq_port" = "$AdGuardHome_PORT" ]; then + [ "$dnsmasq_port" = "53" ] && dnsmasq_port="1745" + elif [ "$dnsmasq_port" = "53" ]; then + return + fi + + config_editor "dns.port" "$dnsmasq_port" "$configpath" + uci set dhcp.@dnsmasq[0].port="53" + uci commit dhcp + + config_get binpath $CONFIGURATION binpath "/usr/bin/AdGuardHome/AdGuardHome" + killall -9 "$binpath" 2>/dev/null + /etc/init.d/dnsmasq restart + agh_reload +} + +use_port53() { + local configpath + config_get configpath $CONFIGURATION configpath "/etc/AdGuardHome.yaml" + + local AdGuardHome_PORT dnsmasq_port + AdGuardHome_PORT=$(config_editor "dns.port" "" "$configpath" "1") + dnsmasq_port=$(uci get dhcp.@dnsmasq[0].port 2>/dev/null) + [ -z "$dnsmasq_port" ] && dnsmasq_port="53" + + if [ "$dnsmasq_port" = "$AdGuardHome_PORT" ]; then + [ "$dnsmasq_port" = "53" ] && AdGuardHome_PORT="1745" + elif [ "$AdGuardHome_PORT" = "53" ]; then + return + fi + + config_editor "dns.port" "53" "$configpath" + uci set dhcp.@dnsmasq[0].port="$AdGuardHome_PORT" + uci commit dhcp + /etc/init.d/dnsmasq restart + agh_reload +} + +mark_redirect_flag() { + local enabled="$1" redirect="$2" agh_port="$3" + + local configpath + config_get configpath $CONFIGURATION configpath "/etc/AdGuardHome.yaml" + + local flag=0 + + if [ "$enabled" = "1" ] && [ "$redirect" != "none" ]; then + flag=1 + + if [ "$redirect" = "redirect" ]; then + nft list table inet "$NFT_TABLE" >/dev/null 2>&1 || flag=0 + + elif [ "$redirect" = "dnsmasq-upstream" ]; then + local s + s="$(uci -q get dhcp.@dnsmasq[0].server | tr '\n' ' ')" + printf '%s\n' "$s" | grep -q -E "(^|[[:space:]])127\.0\.0\.1#${agh_port}([[:space:]]|$)" || flag=0 + + elif [ "$redirect" = "exchange" ]; then + local cfgp dport + cfgp="$(config_editor "dns.port" "" "$configpath" "1")" + dport="$(uci -q get dhcp.@dnsmasq[0].port)" + if [ "$cfgp" != "53" ] || [ "$dport" = "53" ] ; then + flag=0 + fi + fi + fi + + echo -n "$flag" > /var/run/AdGredir +} + +_do_redirect() { + local section="$CONFIGURATION" + args="" + ipv6_server=1 + tcp_server=0 + enabled="$1" + + config_get configpath $CONFIGURATION configpath "/etc/AdGuardHome.yaml" + AdGuardHome_PORT="$(config_editor "dns.port" "" "$configpath" "1")" + [ -z "$AdGuardHome_PORT" ] && AdGuardHome_PORT="0" + + config_get redirect "$section" redirect "none" + config_get old_redirect "$section" old_redirect "none" + config_get old_port "$section" old_port "0" + config_get old_enabled "$section" old_enabled "0" + + uci get dhcp.@dnsmasq[0].port >/dev/null 2>&1 || uci set dhcp.@dnsmasq[0].port="53" >/dev/null 2>&1 + uci commit dhcp + + if [ "$old_enabled" = "1" ] && [ "$old_redirect" = "exchange" ]; then + AdGuardHome_PORT="$(uci get dhcp.@dnsmasq[0].port 2>/dev/null)" + fi + + if [ "$old_redirect" != "$redirect" ] || [ "$old_port" != "$AdGuardHome_PORT" ] || { [ "$old_enabled" = "1" ] && [ "$enabled" = "0" ]; }; then + if [ "$old_redirect" != "none" ]; then + if [ "$old_redirect" = "redirect" ] && [ "$old_port" != "0" ]; then + clear_nft_redirect + elif [ "$old_redirect" = "dnsmasq-upstream" ]; then + stop_forward_dnsmasq "$old_port" + elif [ "$old_redirect" = "exchange" ]; then + rm_port53 + fi + fi + elif [ "$old_enabled" = "1" ] && [ "$enabled" = "1" ]; then + if [ "$old_redirect" = "redirect" ] && [ "$old_port" != "0" ]; then + clear_nft_redirect + fi + fi + + uci delete AdGuardHome.@AdGuardHome[0].old_redirect 2>/dev/null + uci delete AdGuardHome.@AdGuardHome[0].old_port 2>/dev/null + uci delete AdGuardHome.@AdGuardHome[0].old_enabled 2>/dev/null + uci add_list AdGuardHome.@AdGuardHome[0].old_redirect="$redirect" 2>/dev/null + uci add_list AdGuardHome.@AdGuardHome[0].old_port="$AdGuardHome_PORT" 2>/dev/null + uci add_list AdGuardHome.@AdGuardHome[0].old_enabled="$enabled" 2>/dev/null + uci commit AdGuardHome + + if [ "$enabled" = "0" ] || [ "$AdGuardHome_PORT" = "0" ]; then + echo -n "0" > /var/run/AdGredir + return 1 + fi + + if [ "$redirect" = "redirect" ]; then + set_nft_redirect "$AdGuardHome_PORT" + elif [ "$redirect" = "dnsmasq-upstream" ]; then + set_forward_dnsmasq "$AdGuardHome_PORT" + elif [ "$redirect" = "exchange" ] && [ "$(uci get dhcp.@dnsmasq[0].port 2>/dev/null)" = "53" ]; then + use_port53 + fi + + mark_redirect_flag "$enabled" "$redirect" "$AdGuardHome_PORT" +} + +do_redirect() { + config_load "${CONFIGURATION}" + _do_redirect "$1" } service_triggers() { - procd_add_reload_trigger "$CONFIGURATION" - [ "$(uci get AdGuardHome.AdGuardHome.redirect)" == "redirect" ] && procd_add_reload_trigger firewall -} -isrunning(){ -config_load "${CONFIGURATION}" -_isrunning -local r=$? -([ "$r" == "0" ] && echo "running") || ([ "$r" == "1" ] && echo "not run" ) || echo "no bin" -return $r -} -_isrunning(){ - config_get binpath $CONFIGURATION binpath "/usr/bin/AdGuardHome" - [ ! -f "$binpath" ] && return 2 - pgrep $binpath 2>&1 >/dev/null && return 0 - return 1 -} -force_reload(){ - config_load "${CONFIGURATION}" - _isrunning && procd_send_signal "$CONFIGURATION" || start -} -get_tz() -{ - SET_TZ="" - - if [ -e "/etc/localtime" ]; then - return - fi - - for tzfile in /etc/TZ /var/etc/TZ - do - if [ ! -e "$tzfile" ]; then - continue - fi - - tz="`cat $tzfile 2>/dev/null`" - done - - if [ -z "$tz" ]; then - return - fi - - SET_TZ=$tz + procd_add_reload_trigger "$CONFIGURATION" } -rm_port53() -{ - local AdGuardHome_PORT=$(config_editor "dns.port" "" "$configpath" "1") - dnsmasq_port=$(uci get dhcp.@dnsmasq[0].port 2>/dev/null) - if [ -z "$dnsmasq_port" ]; then - dnsmasq_port="53" - fi - if [ "$dnsmasq_port" == "$AdGuardHome_PORT" ]; then - if [ "$dnsmasq_port" == "53" ]; then - dnsmasq_port="1745" - fi - elif [ "$dnsmasq_port" == "53" ]; then - return - fi - config_editor "dns.port" "$dnsmasq_port" "$configpath" - uci set dhcp.@dnsmasq[0].port="53" - uci commit dhcp - /etc/init.d/dnsmasq restart +isrunning() { + config_load "${CONFIGURATION}" + _isrunning + local r=$? + [ "$r" = "0" ] && echo "running" || ([ "$r" = "1" ] && echo "not run" || echo "no bin") + return $r } -use_port53() -{ - local AdGuardHome_PORT=$(config_editor "dns.port" "" "$configpath" "1") - dnsmasq_port=$(uci get dhcp.@dnsmasq[0].port 2>/dev/null) - if [ -z "$dnsmasq_port" ]; then - dnsmasq_port="53" - fi - if [ "$dnsmasq_port" == "$AdGuardHome_PORT" ]; then - if [ "$dnsmasq_port" == "53" ]; then - AdGuardHome_PORT="1745" - fi - elif [ "$AdGuardHome_PORT" == "53" ]; then - return - fi - config_editor "dns.port" "53" "$configpath" - uci set dhcp.@dnsmasq[0].port="$AdGuardHome_PORT" - uci commit dhcp - /etc/init.d/dnsmasq restart +_isrunning() { + config_get binpath $CONFIGURATION binpath "/usr/bin/AdGuardHome/AdGuardHome" + [ ! -f "$binpath" ] && return 2 + pgrep -f "$(basename "$binpath")" >/dev/null 2>&1 && return 0 + return 1 } -do_redirect() -{ - config_load "${CONFIGURATION}" - _do_redirect $1 -} -_do_redirect() -{ - local section="$CONFIGURATION" - args="" - ipv6_server=1 - tcp_server=0 - enabled=$1 - if [ "$enabled" == "1" ]; then - echo -n "1">/var/run/AdGredir - else - echo -n "0">/var/run/AdGredir - fi - config_get configpath $CONFIGURATION configpath "/etc/AdGuardHome.yaml" - AdGuardHome_PORT=$(config_editor "dns.port" "" "$configpath" "1") - if [ -z "$AdGuardHome_PORT" ]; then - AdGuardHome_PORT="0" - fi - config_get "redirect" "$section" "redirect" "none" - config_get "old_redirect" "$section" "old_redirect" "none" - config_get "old_port" "$section" "old_port" "0" - config_get "old_enabled" "$section" "old_enabled" "0" - uci get dhcp.@dnsmasq[0].port >/dev/null 2>&1 || uci set dhcp.@dnsmasq[0].port="53" >/dev/null 2>&1 - if [ "$old_enabled" = "1" -a "$old_redirect" == "exchange" ]; then - AdGuardHome_PORT=$(uci get dhcp.@dnsmasq[0].port 2>/dev/null) - fi - if [ "$old_redirect" != "$redirect" ] || [ "$old_port" != "$AdGuardHome_PORT" ] || [ "$old_enabled" = "1" -a "$enabled" = "0" ]; then - if [ "$old_redirect" != "none" ]; then - if [ "$old_redirect" == "redirect" -a "$old_port" != "0" ]; then - clear_iptable - elif [ "$old_redirect" == "dnsmasq-upstream" ]; then - stop_forward_dnsmasq "$old_port" - elif [ "$old_redirect" == "exchange" ]; then - rm_port53 - fi - fi - elif [ "$old_enabled" = "1" -a "$enabled" = "1" ]; then - if [ "$old_redirect" == "redirect" -a "$old_port" != "0" ]; then - clear_iptable - fi - fi - uci delete AdGuardHome.@AdGuardHome[0].old_redirect 2>/dev/null - uci delete AdGuardHome.@AdGuardHome[0].old_port 2>/dev/null - uci delete AdGuardHome.@AdGuardHome[0].old_enabled 2>/dev/null - uci add_list AdGuardHome.@AdGuardHome[0].old_redirect="$redirect" 2>/dev/null - uci add_list AdGuardHome.@AdGuardHome[0].old_port="$AdGuardHome_PORT" 2>/dev/null - uci add_list AdGuardHome.@AdGuardHome[0].old_enabled="$enabled" 2>/dev/null - uci commit AdGuardHome - [ "$enabled" == "0" ] && return 1 - if [ "$AdGuardHome_PORT" == "0" ]; then - return 1 - fi - if [ "$redirect" = "redirect" ]; then - set_iptable - elif [ "$redirect" = "dnsmasq-upstream" ]; then - set_forward_dnsmasq "$AdGuardHome_PORT" - elif [ "$redirect" == "exchange" -a "$(uci get dhcp.@dnsmasq[0].port 2>/dev/null)" == "53" ]; then - use_port53 - fi +force_reload() { + config_load "${CONFIGURATION}" + _isrunning && procd_send_signal "$CONFIGURATION" || start } -get_filesystem() -{ -# print out path filesystem - echo $1 | awk ' + +get_tz() { + SET_TZ="" + [ -e "/etc/localtime" ] && return + for tzfile in /etc/TZ /var/etc/TZ; do + [ -e "$tzfile" ] && tz="$(cat "$tzfile" 2>/dev/null)" && break + done + [ -n "$tz" ] && SET_TZ=$tz +} + +get_filesystem() { + echo "$1" | awk ' BEGIN{ - while (("mount"| getline ret) > 0) - { - split(ret,d); - fs[d[3]]=d[5]; - m=index(d[1],":") - if (m==0) - { - pt[d[3]]=d[1] - }else{ - pt[d[3]]=substr(d[1],m+1) - }}}{ - split($0,d,"/"); - if ("/" in fs) - { - result1=fs["/"]; - } - if ("/" in pt) - { - result2=pt["/"]; - } - for (i=2;i<=length(d);i++) - { - p[i]=p[i-1]"/"d[i]; - if (p[i] in fs) - { - result1=fs[p[i]]; - result2=pt[p[i]]; + while (("mount"| getline ret) > 0) { + split(ret,d); fs[d[3]]=d[5]; + m=index(d[1],":"); + if (m==0) { pt[d[3]]=d[1] } else { pt[d[3]]=substr(d[1],m+1) } } } - if (result2 in fs){ - result=fs[result2]} - else{ - result=result1} - print(result);}' + { + split($0,d,"/"); + if ("/" in fs) { result1=fs["/"]; } + if ("/" in pt) { result2=pt["/"]; } + for (i=2;i<=length(d);i++) { + p[i]=p[i-1]"/"d[i]; + if (p[i] in fs) { result1=fs[p[i]]; result2=pt[p[i]]; } + } + if (result2 in fs) { result=fs[result2] } else { result=result1 } + print(result); + }' } -config_editor() -{ - [ -n "$3" -a -f $3 ] || return - awk -v yaml="$1" -v value="$2" -v file="$3" -v ro="$4" ' - BEGIN{split(yaml,part,"\.");s="";i=1;l=length(part);} +config_editor() { + awk -v yaml="$1" -v value="$2" -v file="$3" -v ro="$4" ' + BEGIN{split(yaml,part,"\.");s="";i=1;l=length(part);} { - if (match($0,s""part[i]":")) - { - if (i==l) - { + if (match($0,s""part[i]":")) { + if (i==l) { split($0,t,": "); - if (ro==""){ - system("sed -i '\''"FNR"c \\"t[1]": "value"'\'' "file); - }else{ - print(t[2]); - } - exit; + if (ro==""){ + system("sed -i '\''"FNR"c\\" t[1] ": " value "'\'' " file); + } else { + print(t[2]); + } + exit; } s=s"[- ]{2}"; i++; } - }' $3 + }' "$3" } boot_service() { - rm /var/run/AdGserverdis >/dev/null 2>&1 - config_load "${CONFIGURATION}" - config_get waitonboot $CONFIGURATION waitonboot "0" - config_get_bool enabled $CONFIGURATION enabled 0 - config_get binpath $CONFIGURATION binpath "/usr/bin/AdGuardHome" - [ -f "$binpath" ] && start_service - if [ "$enabled" == "1" ] && [ "$waitonboot" == "1" ]; then - procd_open_instance "waitnet" - procd_set_param command "/usr/share/AdGuardHome/waitnet.sh" - procd_close_instance - echo "no net start pinging" - fi + rm -f /var/run/AdGserverdis 2>/dev/null + config_load "${CONFIGURATION}" + config_get binpath $CONFIGURATION binpath "/usr/bin/AdGuardHome/AdGuardHome" + [ -f "$binpath" ] && start_service } -testbackup(){ - config_load "${CONFIGURATION}" - if [ "$1" == "backup" ]; then - backup - elif [ "$1" == "restore" ]; then - restore - fi -} -restore() -{ - config_get workdir $CONFIGURATION workdir "/etc/AdGuardHome" - config_get backupwdpath $CONFIGURATION backupwdpath "/etc/AdGuardHome" - cp -u -r -f $backupwdpath/data $workdir -} -backup() { - config_get backupwdpath $CONFIGURATION backupwdpath "/etc/AdGuardHome" - mkdir -p $backupwdpath/data - config_get workdir $CONFIGURATION workdir "/etc/AdGuardHome" - config_get backupfile $CONFIGURATION backupfile "" - for one in $backupfile; - do - while : - do - if [ -d "$backupwdpath/data/$one" ]; then - cpret=$(cp -u -r -f $workdir/data/$one $backupwdpath/data 2>&1) - else - cpret=$(cp -u -r -f $workdir/data/$one $backupwdpath/data/$one 2>&1) - fi - echo "$cpret" - echo "$cpret" | grep "no space left on device" - if [ "$?" == "0" ]; then - echo "磁盘已满,删除log重试中" - del_querylog && continue - rm -f -r $backupwdpath/data/filters - rm -f -r $workdir/data/filters && continue - echo "backup failed" - fi - break - done - done -} start_service() { - # Reading config - rm /var/run/AdGserverdis >/dev/null 2>&1 - config_load "${CONFIGURATION}" - # update password - config_get hashpass $CONFIGURATION hashpass "" - config_get configpath $CONFIGURATION configpath "/etc/AdGuardHome.yaml" - if [ -n "$hashpass" ]; then - config_editor "users.password" "$hashpass" "$configpath" - uci set $CONFIGURATION.$CONFIGURATION.hashpass="" - fi - local enabled - local ipst=0 - config_get_bool enabled $CONFIGURATION enabled 0 - # update crontab - do_crontab - if [ "$enabled" == "0" ]; then - _do_redirect 0 - return - fi - #what need to do before reload - config_get workdir $CONFIGURATION workdir "/etc/AdGuardHome" - grep -q "ipset.txt" $configpath 2>/dev/null && ipst=1 - if [ $ipst -eq 1 ];then - ipset list $GFWSET >/dev/null 2>&1 || ipset create $GFWSET hash:ip 2>/dev/null - fi - config_get backupfile $CONFIGURATION backupfile "" - mkdir -p $workdir/data - if [ -n "$backupfile" ] && [ ! -d "$workdir/data" ]; then - restore - fi + rm -f /var/run/AdGserverdis 2>/dev/null + config_load "${CONFIGURATION}" + config_get hashpass $CONFIGURATION hashpass "" + config_get configpath $CONFIGURATION configpath "/etc/AdGuardHome.yaml" + if [ -n "$hashpass" ]; then + config_editor "users.password" "$hashpass" "$configpath" + uci set $CONFIGURATION.$CONFIGURATION.hashpass="" + uci commit $CONFIGURATION + fi + local enabled + config_get_bool enabled $CONFIGURATION enabled 0 + if [ "$enabled" = "0" ]; then + _do_redirect 0 + return + fi + config_get workdir $CONFIGURATION workdir "/usr/bin/AdGuardHome" - # for overlay data-stk-oo not suppport - local cwdfs=$(get_filesystem $workdir) - echo "workdir is a $cwdfs filesystem" - if [ "$cwdfs" == "jffs2" ]; then - echo "fs error ln db to tmp $workdir $cwdfs" - logger "AdGuardHome" "warning db redirect to tmp" - touch $workdir/data/stats.db - if [ ! -L $workdir/data/stats.db ]; then - mv -f $workdir/data/stats.db /tmp/stats.db 2>/dev/null - ln -s /tmp/stats.db $workdir/data/stats.db 2>/dev/null - fi - touch $workdir/data/sessions.db - if [ ! -L $workdir/data/sessions.db ]; then - mv -f $workdir/data/sessions.db /tmp/sessions.db 2>/dev/null - ln -s /tmp/sessions.db $workdir/data/sessions.db 2>/dev/null - fi - fi - local ADDITIONAL_ARGS="" - config_get binpath $CONFIGURATION binpath "/usr/bin/AdGuardHome" + mkdir -p "$workdir/data" + local cwdfs + cwdfs=$(get_filesystem "$workdir") + echo "workdir is a $cwdfs filesystem" + if [ "$cwdfs" = "jffs2" ]; then + echo "fs error ln db to tmp $workdir $cwdfs" + logger "AdGuardHome" "warning db redirect to tmp" + touch "$workdir/data/stats.db" + if [ ! -L "$workdir/data/stats.db" ]; then + mv -f "$workdir/data/stats.db" /tmp/stats.db 2>/dev/null + ln -s /tmp/stats.db "$workdir/data/stats.db" 2>/dev/null + fi + touch "$workdir/data/sessions.db" + if [ ! -L "$workdir/data/sessions.db" ]; then + mv -f "$workdir/data/sessions.db" /tmp/sessions.db 2>/dev/null + ln -s /tmp/sessions.db "$workdir/data/sessions.db" 2>/dev/null + fi + fi + local ADDITIONAL_ARGS="" + config_get binpath $CONFIGURATION binpath "/usr/bin/AdGuardHome/AdGuardHome" - mkdir -p ${binpath%/*} - ADDITIONAL_ARGS="$ADDITIONAL_ARGS -c $configpath" - ADDITIONAL_ARGS="$ADDITIONAL_ARGS -w $workdir" - config_get httpport $CONFIGURATION httpport 3000 - ADDITIONAL_ARGS="$ADDITIONAL_ARGS -p $httpport" + mkdir -p "${binpath%/*}" + ADDITIONAL_ARGS="$ADDITIONAL_ARGS -c $configpath" + ADDITIONAL_ARGS="$ADDITIONAL_ARGS -w $workdir" + config_get httpport $CONFIGURATION httpport 3000 + ADDITIONAL_ARGS="$ADDITIONAL_ARGS -p $httpport" - # hack to save config file when upgrade system - config_get upprotect $CONFIGURATION upprotect "" - eval upprotect=${upprotect// /\\\\n} - echo -e "$upprotect">/lib/upgrade/keep.d/luci-app-adguardhome - - config_get logfile $CONFIGURATION logfile "" - if [ -n "$logfile" ]; then - ADDITIONAL_ARGS="$ADDITIONAL_ARGS -l $logfile" - fi + printf '%s\n' "$binpath" "$configpath" > /lib/upgrade/keep.d/luci-app-adguardhome - if [ ! -f "$binpath" ]; then - _do_redirect 0 - /usr/share/AdGuardHome/update_core.sh 2>&1 >/tmp/AdGuardHome_update.log & - exit 0 - fi + config_get logfile $CONFIGURATION logfile "" + if [ -n "$logfile" ]; then + ADDITIONAL_ARGS="$ADDITIONAL_ARGS -l $logfile" + fi - config_get_bool verbose $CONFIGURATION verbose 0 - if [ "$verbose" -eq 1 ]; then - ADDITIONAL_ARGS="$ADDITIONAL_ARGS -v" - fi + if [ ! -f "$binpath" ]; then + _do_redirect 0 + /usr/share/AdGuardHome/update_core.sh 2>&1 >/tmp/AdGuardHome_update.log & + exit 0 + fi - procd_open_instance - get_tz - if [ -n "$SET_TZ" ]; then - procd_set_param env TZ="$SET_TZ" - fi - procd_set_param respawn ${respawn_threshold:-3600} ${respawn_timeout:-5} ${respawn_retry:-5} - procd_set_param limits core="unlimited" nofile="65535 65535" - procd_set_param stderr 1 - procd_set_param command $binpath $ADDITIONAL_ARGS - procd_set_param file "$configpath" "/etc/hosts" "/etc/config/AdGuardHome" - procd_close_instance - if [ -f "$configpath" ]; then - _do_redirect 1 - else - _do_redirect 0 - config_get "redirect" $CONFIGURATION "redirect" "none" - if [ "$redirect" != "none" ]; then - procd_open_instance "waitconfig" - procd_set_param command "/usr/share/AdGuardHome/watchconfig.sh" - procd_close_instance - echo "no config start watching" - fi - fi - echo "AdGuardHome service enabled" - echo "luci enable switch=$enabled" - (sleep 10 && [ -z "$(pgrep $binpath)" ] && logger "AdGuardHome" "no process in 10s cancel redirect" && _do_redirect 0 )& - if [[ "`uci get bypass.@global[0].global_server 2>/dev/null`" && "`uci get bypass.@global[0].adguardhome 2>/dev/null`" == 1 && "$(uci get dhcp.@dnsmasq[0].port)" == "53" ]]; then - uci -q set AdGuardHome.AdGuardHome.redirect='exchange' - uci commit AdGuardHome - do_redirect 1 - fi -} -reload_service() -{ - rm /var/run/AdGlucitest >/dev/null 2>&1 - echo "AdGuardHome reloading" - start + config_get_bool verbose $CONFIGURATION verbose 0 + if [ "$verbose" -eq 1 ]; then + ADDITIONAL_ARGS="$ADDITIONAL_ARGS -v" + fi + + procd_open_instance + get_tz + if [ -n "$SET_TZ" ]; then + procd_set_param env TZ="$SET_TZ" + fi + procd_set_param respawn ${respawn_threshold:-3600} ${respawn_timeout:-5} ${respawn_retry:-5} + procd_set_param limits core="unlimited" nofile="65535 65535" + procd_set_param stderr 1 + procd_set_param command "$binpath" $ADDITIONAL_ARGS + procd_set_param file "$configpath" "/etc/hosts" "/etc/config/AdGuardHome" + procd_close_instance + if [ -f "$configpath" ]; then + (sleep 5 && _do_redirect 1) & + else + _do_redirect 0 + config_get "redirect" "AdGuardHome" "redirect" "none" + if [ "$redirect" != "none" ]; then + procd_open_instance "waitconfig" + procd_set_param command "/usr/share/AdGuardHome/watchconfig.sh" + procd_close_instance + echo "no config start watching" + fi + fi + echo "AdGuardHome service enabled" + echo "luci enable switch=$enabled" + (sleep 10 && [ -z "$(pgrep "$(basename "$binpath")")" ] && logger "AdGuardHome" "no process in 10s cancel redirect" && _do_redirect 0 )& + if [ "$(uci -q get bypass.@global[0].global_server)" ] && [ "$(uci -q get bypass.@global[0].adguardhome)" = "1" ] && [ "$(uci -q get dhcp.@dnsmasq[0].port)" = "53" ]; then + uci -q set AdGuardHome.AdGuardHome.redirect='exchange' + uci -q commit AdGuardHome + do_redirect + fi } -del_querylog(){ -local btarget=$(ls $backupwdpath/data | grep -F "querylog.json" | sort -r | head -n 1) -local wtarget=$(ls $workdir/data | grep -F "querylog.json" | sort -r | head -n 1) -if [ "$btarget"x == "$wtarget"x ]; then - [ -z "$btarget" ] && return 1 - rm -f $workdir/data/$wtarget - rm -f $backupwdpath/data/$btarget - return 0 -fi -if [ "$btarget" \> "$wtarget" ]; then - rm -f $backupwdpath/data/$btarget - return 0 -else - rm -f $workdir/data/$wtarget - return 0 -fi +reload_service() { + rm -f /var/run/AdGlucitest 2>/dev/null + echo "AdGuardHome reloading" + start } -stop_service() -{ - config_load "${CONFIGURATION}" - _do_redirect 0 - do_crontab - if [ "$1" != "nobackup" ]; then - config_get backupfile $CONFIGURATION backupfile "0" - if [ -n "$backupfile" ]; then - backup - fi - fi - echo "AdGuardHome service disabled" - touch /var/run/AdGserverdis + +stop_service() { + config_load "${CONFIGURATION}" + _do_redirect 0 + echo "AdGuardHome service disabled" + touch /var/run/AdGserverdis } + boot() { - rc_procd boot_service "$@" - if eval "type service_started" 2>/dev/null >/dev/null; then - service_started - fi -} -test_crontab(){ -config_load "${CONFIGURATION}" -do_crontab -} -do_crontab(){ - config_get_bool enabled $CONFIGURATION enabled 0 - config_get crontab $CONFIGURATION crontab "" - local findstr default cronenable replace commit - local cronreload=0 - local commit=0 - findstr="/usr/share/AdGuardHome/update_core.sh" - default="30 3 * * * /usr/share/AdGuardHome/update_core.sh 2>&1" - [ "$enabled" == "0" ] || [ "${crontab//autoupdate/}" == "$crontab" ] && cronenable=0 || cronenable=1 - crontab_editor - - config_get workdir $CONFIGURATION workdir "/etc/AdGuardHome" - config_get lastworkdir $CONFIGURATION lastworkdir "/etc/AdGuardHome" - findstr="/usr/share/AdGuardHome/tailto.sh [0-9]* \$(uci get AdGuardHome.AdGuardHome.workdir)/data/querylog.json" - #[ -n "$lastworkdir" ] && findstr="/usr/share/AdGuardHome/tailto.sh [0-9]* $lastworkdir/data/querylog.json" && [ "$lastworkdir" != "$workdir" ] && replace="${lastworkdir//\//\\/}/${workdir//\//\\/}" - default="0 * * * * /usr/share/AdGuardHome/tailto.sh 2000 \$(uci get AdGuardHome.AdGuardHome.workdir)/data/querylog.json" - [ "$enabled" == "0" ] || [ "${crontab//cutquerylog/}" == "$crontab" ] && cronenable=0 || cronenable=1 - crontab_editor - #[ "$lastworkdir" != "$workdir" ] && uci set AdGuardHome.AdGuardHome.lastworkdir="$workdir" && commit=1 - - config_get logfile $CONFIGURATION logfile "" - config_get lastlogfile $CONFIGURATION lastlogfile "" - findstr="/usr/share/AdGuardHome/tailto.sh [0-9]* \$(uci get AdGuardHome.AdGuardHome.logfile)" - default="30 3 * * * /usr/share/AdGuardHome/tailto.sh 2000 \$(uci get AdGuardHome.AdGuardHome.logfile)" - #[ -n "$lastlogfile" ] && findstr="/usr/share/AdGuardHome/tailto.sh [0-9]* $lastlogfile" && [ -n "$logfile" ] && [ "$lastlogfile" != "$logfile" ] && replace="${lastlogfile//\//\\/}/${logfile//\//\\/}" - [ "$logfile" == "syslog" ] || [ "$logfile" == "" ] || [ "$enabled" == "0" ] || [ "${crontab//cutruntimelog/}" == "$crontab" ] && cronenable=0 || cronenable=1 - crontab_editor - #[ -n "$logfile" ] && [ "$lastlogfile" != "$logfile" ] && uci set AdGuardHome.AdGuardHome.lastlogfile="$logfile" && commit=1 - - findstr="/usr/share/AdGuardHome/addhost.sh" - default="0 * * * * /usr/share/AdGuardHome/addhost.sh" - [ "$enabled" == "0" ] || [ "${crontab//autohost/}" == "$crontab" ] && cronenable=0 || cronenable=1 - crontab_editor - [ "$cronenable" == "0" ] && /usr/share/AdGuardHome/addhost.sh "del" "noreload" || /usr/share/AdGuardHome/addhost.sh "" "noreload" - - findstr="/usr/share/AdGuardHome/gfw2adg.sh" - default="30 3 * * * /usr/share/AdGuardHome/gfw2adg.sh" - [ "$enabled" == "0" ] || [ "${crontab//autogfw/}" == "$crontab" ] && cronenable=0 || cronenable=1 - crontab_editor - - findstr="/usr/share/AdGuardHome/gfwipset2adg.sh" - default="31 3 * * * /usr/share/AdGuardHome/gfwipset2adg.sh" - [ "$enabled" == "0" ] || [ "${crontab//autogfwipset/}" == "$crontab" ] && cronenable=0 || cronenable=1 - crontab_editor - [ "$cronreload" -gt 0 ] && /etc/init.d/cron restart - #[ "$commit" -gt 0 ] && uci commit AdGuardHome -} -crontab_editor(){ - #usage input: - #findstr= - #default= - #cronenable= - #replace="${last//\//\\/}/${now//\//\\/}" - #output:cronreload:if >1 please /etc/init.d/cron restart manual - local testline reload - local line="$(grep "$findstr" $CRON_FILE)" - [ -n "$replace" ] && [ -n "$line" ] && eval testline="\${line//$replace}" && [ "$testline" != "$line" ] && line="$testline" && reload="1" && replace="" - if [ "${line:0:1}" != "#" ]; then - if [ $cronenable -eq 1 ]; then - [ -z "$line" ] && line="$default" && reload="1" - if [ -n "$reload" ]; then - sed -i "\,$findstr,d" $CRON_FILE - echo "$line" >> $CRON_FILE - cronreload=$((cronreload+1)) - fi - elif [ -n "$line" ]; then - sed -i "\,$findstr,d" $CRON_FILE - echo "#$line" >> $CRON_FILE - cronreload=$((cronreload+1)) - fi - else - if [ $cronenable -eq 1 ]; then - sed -i "\,$findstr,d" $CRON_FILE - echo "${line:1}" >> $CRON_FILE - cronreload=$((cronreload+1)) - elif [ -z "$reload" ]; then - sed -i "\,$findstr,d" $CRON_FILE - echo "$line" >> $CRON_FILE - fi - fi - -} + rc_procd boot_service "$@" + eval "type service_started" 2>/dev/null >/dev/null && service_started +} \ No newline at end of file diff --git a/luci-app-adguardhome/root/etc/uci-defaults/40_luci-AdGuardHome b/luci-app-adguardhome/root/etc/uci-defaults/40_luci-AdGuardHome old mode 100644 new mode 100755 index 6af9df37..dc177342 --- a/luci-app-adguardhome/root/etc/uci-defaults/40_luci-AdGuardHome +++ b/luci-app-adguardhome/root/etc/uci-defaults/40_luci-AdGuardHome @@ -1,14 +1,11 @@ #!/bin/sh - +chmod +x /etc/init.d/AdGuardHome uci -q batch <<-EOF >/dev/null 2>&1 delete ucitrack.@AdGuardHome[-1] add ucitrack AdGuardHome set ucitrack.@AdGuardHome[-1].init=AdGuardHome commit ucitrack - delete AdGuardHome.AdGuardHome.ucitracktest EOF rm -f /tmp/luci-indexcache -chmod +x /etc/init.d/AdGuardHome /usr/share/AdGuardHome/* - -exit 0 +exit 0 \ No newline at end of file diff --git a/luci-app-adguardhome/root/lib/upgrade/keep.d/luci-app-adguardhome b/luci-app-adguardhome/root/lib/upgrade/keep.d/luci-app-adguardhome new file mode 100644 index 00000000..1a5bd6dc --- /dev/null +++ b/luci-app-adguardhome/root/lib/upgrade/keep.d/luci-app-adguardhome @@ -0,0 +1 @@ +/etc/AdGuardHome.yaml \ No newline at end of file diff --git a/luci-app-adguardhome/root/usr/share/AdGuardHome/AdGuardHome_template.yaml b/luci-app-adguardhome/root/usr/share/AdGuardHome/AdGuardHome_template.yaml old mode 100644 new mode 100755 index d020516c..b6f63852 --- a/luci-app-adguardhome/root/usr/share/AdGuardHome/AdGuardHome_template.yaml +++ b/luci-app-adguardhome/root/usr/share/AdGuardHome/AdGuardHome_template.yaml @@ -1,6 +1,7 @@ -#提交就可以直接用的配置模板文件。 user: root/password: root -#a template config can be use with a apply. user: root/password: root - +# 模板用户名为 admin 密码为 admin +# The username is admin and the password is admin. +# 保存并应用就可以直接用的配置模板文件 +# a template config can be use with a apply http: pprof: port: 6060 @@ -8,8 +9,8 @@ http: address: 0.0.0.0:3000 session_ttl: 720h users: - - name: root - password: $2y$10$dwn0hTYoECQMZETBErGlzOId2VANOVsPHsuH13TM/8KnysM5Dh/ve + - name: admin + password: $2y$10$iGkTW98QtGEZn4I/GC0J4eQhfuLQ53v7L4DDVauEJyWylJrXWwWeO auth_attempts: 5 block_auth_min: 15 http_proxy: "" @@ -18,20 +19,26 @@ theme: auto dns: bind_hosts: - 0.0.0.0 - port: 1745 + port: 3053 anonymize_client_ip: false - ratelimit: 0 + ratelimit: 20 + ratelimit_subnet_len_ipv4: 24 + ratelimit_subnet_len_ipv6: 56 ratelimit_whitelist: [] - refuse_any: false + refuse_any: true upstream_dns: - - 223.5.5.5 + - https://dns.cloudflare.com/dns-query + - '#' + - '# 对 ts.net 相关域名启用 DNS 分流, 交由 Tailscale 解析, 如不使用 Tailscale 可以删除.' + - '# Enable DNS conditional forwarding for ts.net via Tailscale; remove if Tailscale is not used.' + - '[/ts.net/]100.100.100.100' upstream_dns_file: "" bootstrap_dns: - - 119.29.29.29 - - 223.5.5.5 - fallback_dns: [] - all_servers: false - fastest_addr: false + - 1.1.1.1 + - 1.0.0.1 + fallback_dns: + - quic://dns.adguard-dns.com + upstream_mode: parallel fastest_timeout: 1s allowed_clients: [] disallowed_clients: [] @@ -42,10 +49,13 @@ dns: trusted_proxies: - 127.0.0.0/8 - ::1/128 + cache_enabled: true cache_size: 4194304 cache_ttl_min: 0 cache_ttl_max: 0 cache_optimistic: false + cache_optimistic_answer_ttl: 30s + cache_optimistic_max_age: 12h bogus_nxdomain: [] aaaa_disabled: false enable_dnssec: false @@ -67,6 +77,9 @@ dns: serve_http3: false use_http3_upstreams: false serve_plain_dns: true + hostsfile_enabled: true + pending_requests: + enabled: true tls: enabled: false server_name: "" @@ -83,24 +96,22 @@ tls: private_key_path: "" strict_sni_check: false querylog: + dir_path: "" ignored: [] - interval: 24h + interval: 2160h size_memory: 1000 enabled: true file_enabled: true statistics: + dir_path: "" ignored: [] - interval: 720h + interval: 24h enabled: true filters: - enabled: true - url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + url: https://adguardteam.github.io/HostlistsRegistry/assets/filter_1.txt name: AdGuard DNS filter id: 1 - - enabled: true - url: https://anti-ad.net/easylist.txt - name: 'CHN: anti-AD' - id: 2 whitelist_filters: [] user_rules: [] dhcp: @@ -125,13 +136,14 @@ filtering: blocking_ipv6: "" blocked_services: schedule: - time_zone: Local + time_zone: UTC ids: [] protection_disabled_until: null safe_search: enabled: false bing: true duckduckgo: true + ecosia: true google: true pixabay: true yandex: true @@ -140,6 +152,8 @@ filtering: parental_block_host: family-block.dns.adguard.com safebrowsing_block_host: standard-block.dns.adguard.com rewrites: [] + safe_fs_patterns: + - /tmp/AdGuardHome/userfilters/* safebrowsing_cache_size: 1048576 safesearch_cache_size: 1048576 parental_cache_size: 1048576 @@ -147,6 +161,7 @@ filtering: filters_update_interval: 24 blocked_response_ttl: 10 filtering_enabled: true + rewrites_enabled: true parental_enabled: false safebrowsing_enabled: false protection_enabled: true @@ -159,6 +174,7 @@ clients: hosts: true persistent: [] log: + enabled: true file: "" max_backups: 0 max_size: 100 @@ -170,4 +186,4 @@ os: group: "" user: "" rlimit_nofile: 0 -schema_version: 27 +schema_version: 32 \ No newline at end of file diff --git a/luci-app-adguardhome/root/usr/share/AdGuardHome/addhost.sh b/luci-app-adguardhome/root/usr/share/AdGuardHome/addhost.sh deleted file mode 100644 index 2461b74d..00000000 --- a/luci-app-adguardhome/root/usr/share/AdGuardHome/addhost.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/sh -checkmd5(){ -local nowmd5=$(md5sum /etc/hosts) -nowmd5=${nowmd5%% *} -local lastmd5=$(uci get AdGuardHome.AdGuardHome.hostsmd5 2>/dev/null) -if [ "$nowmd5" != "$lastmd5" ]; then - uci set AdGuardHome.AdGuardHome.hostsmd5="$nowmd5" - uci commit AdGuardHome - [ "$1" == "noreload" ] || /etc/init.d/AdGuardHome reload -fi -} -[ "$1" == "del" ] && sed -i '/programaddstart/,/programaddend/d' /etc/hosts && checkmd5 "$2" && exit 0 -/usr/bin/awk 'BEGIN{ -while ((getline < "/tmp/dhcp.leases") > 0) -{ - a[$2]=$4; -} -while (("ip -6 neighbor show | grep -v fe80" | getline) > 0) -{ - if (a[$5]) {print $1" "a[$5] >"/tmp/tmphost"; } -} -print "#programaddend" >"/tmp/tmphost"; -}' -grep programaddstart /etc/hosts >/dev/null 2>&1 -if [ "$?" == "0" ]; then - sed -i '/programaddstart/,/programaddend/c\#programaddstart' /etc/hosts - sed -i '/programaddstart/'r/tmp/tmphost /etc/hosts -else - echo "#programaddstart" >>/etc/hosts - cat /tmp/tmphost >> /etc/hosts -fi -rm /tmp/tmphost -checkmd5 "$2" \ No newline at end of file diff --git a/luci-app-adguardhome/root/usr/share/AdGuardHome/adguardhome.nft.tpl b/luci-app-adguardhome/root/usr/share/AdGuardHome/adguardhome.nft.tpl new file mode 100644 index 00000000..67eef35e --- /dev/null +++ b/luci-app-adguardhome/root/usr/share/AdGuardHome/adguardhome.nft.tpl @@ -0,0 +1,10 @@ +table inet adguardhome { + chain prerouting { + type nat hook prerouting priority dstnat; policy accept; + + __AGH_WAN_RETURN_LINE__ + + fib daddr type local udp dport 53 redirect to :__AGH_PORT__ + fib daddr type local tcp dport 53 redirect to :__AGH_PORT__ + } +} diff --git a/luci-app-adguardhome/root/usr/share/AdGuardHome/getsyslog.sh b/luci-app-adguardhome/root/usr/share/AdGuardHome/getsyslog.sh old mode 100644 new mode 100755 index c95cc077..e6387b26 --- a/luci-app-adguardhome/root/usr/share/AdGuardHome/getsyslog.sh +++ b/luci-app-adguardhome/root/usr/share/AdGuardHome/getsyslog.sh @@ -1,19 +1,20 @@ #!/bin/sh + PATH="/usr/sbin:/usr/bin:/sbin:/bin" logread -e AdGuardHome > /tmp/AdGuardHometmp.log logread -e AdGuardHome -f >> /tmp/AdGuardHometmp.log & pid=$! -echo "1">/var/run/AdGuardHomesyslog +echo "1" > /var/run/AdGuardHomesyslog while true do sleep 12 - watchdog=$(cat /var/run/AdGuardHomesyslog) - if [ "$watchdog"x == "0"x ]; then - kill $pid + watchdog=$(cat /var/run/AdGuardHomesyslog 2>/dev/null) + if [ "${watchdog}x" = "0x" ]; then + kill "$pid" rm /tmp/AdGuardHometmp.log rm /var/run/AdGuardHomesyslog exit 0 else - echo "0">/var/run/AdGuardHomesyslog + echo "0" > /var/run/AdGuardHomesyslog fi done \ No newline at end of file diff --git a/luci-app-adguardhome/root/usr/share/AdGuardHome/gfw2adg.sh b/luci-app-adguardhome/root/usr/share/AdGuardHome/gfw2adg.sh deleted file mode 100644 index c71bfb3a..00000000 --- a/luci-app-adguardhome/root/usr/share/AdGuardHome/gfw2adg.sh +++ /dev/null @@ -1,86 +0,0 @@ -#!/bin/sh -PATH="/usr/sbin:/usr/bin:/sbin:/bin" -checkmd5(){ -local nowmd5=$(md5sum /tmp/adguard.list 2>/dev/null) -nowmd5=${nowmd5%% *} -local lastmd5=$(uci get AdGuardHome.AdGuardHome.gfwlistmd5 2>/dev/null) -if [ "$nowmd5" != "$lastmd5" ]; then - uci set AdGuardHome.AdGuardHome.gfwlistmd5="$nowmd5" - uci commit AdGuardHome - [ "$1" == "noreload" ] || /etc/init.d/AdGuardHome reload -fi -} -configpath=$(uci get AdGuardHome.AdGuardHome.configpath 2>/dev/null) -[ "$1" == "del" ] && sed -i '/programaddstart/,/programaddend/d' $configpath && checkmd5 "$2" && exit 0 -gfwupstream=$(uci get AdGuardHome.AdGuardHome.gfwupstream 2>/dev/null) -if [ -z $gfwupstream ]; then -gfwupstream="tcp://208.67.220.220:5353" -fi -if [ ! -f "$configpath" ]; then - echo "please make a config first" - exit 1 -fi -wget-ssl --no-check-certificate https://cdn.jsdelivr.net/gh/gfwlist/gfwlist/gfwlist.txt -O- | base64 -d > /tmp/gfwlist.txt -cat /tmp/gfwlist.txt | awk -v upst="$gfwupstream" 'BEGIN{getline;}{ -s1=substr($0,1,1); -if (s1=="!") -{next;} -if (s1=="@"){ - $0=substr($0,3); - s1=substr($0,1,1); - white=1;} -else{ - white=0; -} - -if (s1=="|") - {s2=substr($0,2,1); - if (s2=="|") - { - $0=substr($0,3); - split($0,d,"/"); - $0=d[1]; - }else{ - split($0,d,"/"); - $0=d[3]; - }} -else{ - split($0,d,"/"); - $0=d[1]; -} -star=index($0,"*"); -if (star!=0) -{ - $0=substr($0,star+1); - dot=index($0,"."); - if (dot!=0) - $0=substr($0,dot+1); - else - next; - s1=substr($0,1,1); -} -if (s1==".") -{fin=substr($0,2);} -else{fin=$0;} -if (index(fin,".")==0) next; -if (index(fin,"%")!=0) next; -if (index(fin,":")!=0) next; -match(fin,"^[0-9\.]+") -if (RSTART==1 && RLENGTH==length(fin)) {print "ipset add gfwlist "fin>"/tmp/doipset.sh";next;} -if (fin=="" || finl==fin) next; -finl=fin; -if (white==0) - {print(" - '\''[/"fin"/]"upst"'\''");} -else{ - print(" - '\''[/"fin"/]#'\''");} -}END{print(" - '\''[/programaddend/]#'\''")}' > /tmp/adguard.list -grep programaddstart $configpath -if [ "$?" == "0" ]; then - sed -i '/programaddstart/,/programaddend/c\ - '\''\[\/programaddstart\/\]#'\''' $configpath - sed -i '/programaddstart/'r/tmp/adguard.list $configpath -else - sed -i '1i\ - '\''[/programaddstart/]#'\''' /tmp/adguard.list - sed -i '/upstream_dns:/'r/tmp/adguard.list $configpath -fi -checkmd5 "$2" -rm -f /tmp/gfwlist.txt /tmp/adguard.list \ No newline at end of file diff --git a/luci-app-adguardhome/root/usr/share/AdGuardHome/gfwipset2adg.sh b/luci-app-adguardhome/root/usr/share/AdGuardHome/gfwipset2adg.sh deleted file mode 100644 index 804c09e6..00000000 --- a/luci-app-adguardhome/root/usr/share/AdGuardHome/gfwipset2adg.sh +++ /dev/null @@ -1,93 +0,0 @@ -#!/bin/sh -PATH="/usr/sbin:/usr/bin:/sbin:/bin" -mkdir -p "/etc/AdGuardHome" -checkmd5(){ -local nowmd5=$(md5sum /tmp/adguard.list 2>/dev/null) -nowmd5=${nowmd5%% *} -local lastmd5=$(uci get AdGuardHome.AdGuardHome.ipsetlistmd5 2>/dev/null) -if [ "$nowmd5" != "$lastmd5" ]; then - uci set AdGuardHome.AdGuardHome.ipsetlistmd5="$nowmd5" - uci commit AdGuardHome - [ "$1" == "noreload" ] || /etc/init.d/AdGuardHome reload -fi -} -ipstok=1 -configpath=$(uci get AdGuardHome.AdGuardHome.configpath 2>/dev/null) -if [ "$1" == "del" ]; then - #sed -i -r 's/upstream_dns_file:\s*['\"]?.*['\"]?/upstream_dns_file: ""/' $configpath - sed -i -r 's/ipset_file:\s*['\"]?.*['\"]?/ipset_file: ""/' $configpath - checkmd5 "$2" - exit 0 -fi -gfwupstream=$(uci get AdGuardHome.AdGuardHome.gfwupstream 2>/dev/null) -if [ -z $gfwupstream ]; then -gfwupstream="tcp://208.67.220.220:5353" -fi -if [ ! -f "$configpath" ]; then - echo "please make a config first" - exit 1 -fi -wget-ssl --no-check-certificate https://cdn.jsdelivr.net/gh/gfwlist/gfwlist/gfwlist.txt -O- | base64 -d > /tmp/gfwlist.txt -cat /tmp/gfwlist.txt | awk -v upst="$gfwupstream" 'BEGIN{getline;}{ -s1=substr($0,1,1); -if (s1=="!") -{next;} -if (s1=="@"){ - $0=substr($0,3); - s1=substr($0,1,1); - white=1;} -else{ - white=0; -} - -if (s1=="|") - {s2=substr($0,2,1); - if (s2=="|") - { - $0=substr($0,3); - split($0,d,"/"); - $0=d[1]; - }else{ - split($0,d,"/"); - $0=d[3]; - }} -else{ - split($0,d,"/"); - $0=d[1]; -} -star=index($0,"*"); -if (star!=0) -{ - $0=substr($0,star+1); - dot=index($0,"."); - if (dot!=0) - $0=substr($0,dot+1); - else - next; - s1=substr($0,1,1); -} -if (s1==".") -{fin=substr($0,2);} -else{fin=$0;} -if (index(fin,".")==0) next; -if (index(fin,"%")!=0) next; -if (index(fin,":")!=0) next; -match(fin,"^[0-9\.]+") -if (RSTART==1 && RLENGTH==length(fin)) {print "ipset add gfwlist "fin>"/tmp/doipset.sh";next;} -if (fin=="" || finl==fin) next; -finl=fin; -if (white==0) - {print(" - '\''[/"fin"/]"upst"'\''");} -else{ - print(" - '\''[/"fin"/]#'\''");} -}END{print(" - '\''[/programaddend/]#'\''")}' > /tmp/adguard.list -cat /tmp/adguard.list 2>/dev/null |sed -r -e "s/\s+\-\s+'//" -e "s/'//" -e "/programaddend/d" > /etc/AdGuardHome/gfw.txt -cat /etc/AdGuardHome/gfw.txt 2>/dev/null |sed -e 's:\[\/::' -e 's:\/\].*:\/gfwlist:' -e "/#/d" |sort -u > /etc/AdGuardHome/ipset.txt -#sed -i -r 's/upstream_dns_file:\s*['\"].*['\"]/upstream_dns_file: \/etc\/AdGuardHome\/gfw.txt/' $configpath -which ipset >/dev/null || ipstok=0 -if [ -s /etc/AdGuardHome/ipset.txt -a $ipstok -eq 1 ];then - ipset list gfwlist >/dev/null 2>&1 || ipset create gfwlist hash:ip 2>/dev/null - sed -i -r 's/ipset_file:\s*['\"].*['\"]/ipset_file: \/etc\/AdGuardHome\/ipset.txt/' $configpath -fi -checkmd5 "$2" -rm -f /tmp/gfwlist.txt /tmp/adguard.list \ No newline at end of file diff --git a/luci-app-adguardhome/root/usr/share/AdGuardHome/links.txt b/luci-app-adguardhome/root/usr/share/AdGuardHome/links.txt deleted file mode 100644 index 8c83c2ff..00000000 --- a/luci-app-adguardhome/root/usr/share/AdGuardHome/links.txt +++ /dev/null @@ -1,3 +0,0 @@ -https://static.adguard.com/adguardhome/release/AdGuardHome_linux_${Arch}.tar.gz -#https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_${Arch}.tar.gz -https://github.com/AdguardTeam/AdGuardHome/releases/download/${latest_ver}/AdGuardHome_linux_${Arch}.tar.gz \ No newline at end of file diff --git a/luci-app-adguardhome/root/usr/share/AdGuardHome/links_backup.txt b/luci-app-adguardhome/root/usr/share/AdGuardHome/links_backup.txt deleted file mode 100644 index 8c83c2ff..00000000 --- a/luci-app-adguardhome/root/usr/share/AdGuardHome/links_backup.txt +++ /dev/null @@ -1,3 +0,0 @@ -https://static.adguard.com/adguardhome/release/AdGuardHome_linux_${Arch}.tar.gz -#https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_${Arch}.tar.gz -https://github.com/AdguardTeam/AdGuardHome/releases/download/${latest_ver}/AdGuardHome_linux_${Arch}.tar.gz \ No newline at end of file diff --git a/luci-app-adguardhome/root/usr/share/AdGuardHome/tailto.sh b/luci-app-adguardhome/root/usr/share/AdGuardHome/tailto.sh deleted file mode 100644 index ab70e3a6..00000000 --- a/luci-app-adguardhome/root/usr/share/AdGuardHome/tailto.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh -tail -n $1 "$2" > /var/run/tailtmp -cat /var/run/tailtmp > "$2" -rm /var/run/tailtmp \ No newline at end of file diff --git a/luci-app-adguardhome/root/usr/share/AdGuardHome/update_core.sh b/luci-app-adguardhome/root/usr/share/AdGuardHome/update_core.sh old mode 100644 new mode 100755 index 0b39e098..546ac0a4 --- a/luci-app-adguardhome/root/usr/share/AdGuardHome/update_core.sh +++ b/luci-app-adguardhome/root/usr/share/AdGuardHome/update_core.sh @@ -1,239 +1,176 @@ -#!/bin/bash +#!/bin/sh + PATH="/usr/sbin:/usr/bin:/sbin:/bin" -binpath=$(uci get AdGuardHome.AdGuardHome.binpath) +update_mode=$1 +binpath=$(uci get AdGuardHome.AdGuardHome.binpath 2>/dev/null) if [ -z "$binpath" ]; then -uci set AdGuardHome.AdGuardHome.binpath="/tmp/AdGuardHome/AdGuardHome" -binpath="/tmp/AdGuardHome/AdGuardHome" + uci set AdGuardHome.AdGuardHome.binpath="/tmp/AdGuardHome/AdGuardHome" + binpath="/tmp/AdGuardHome/AdGuardHome" fi -mkdir -p ${binpath%/*} -upxflag=$(uci get AdGuardHome.AdGuardHome.upxflag 2>/dev/null) -tagname=$(uci get AdGuardHome.AdGuardHome.tagname 2>/dev/null) +[ ! -d "${binpath%/*}" ] && mkdir -p "${binpath%/*}" +enabled=$(uci get AdGuardHome.AdGuardHome.enabled 2>/dev/null) +core_api_url="https://api.github.com/repos/AdguardTeam/AdGuardHome/releases/latest" -check_if_already_running(){ - running_tasks="$(ps |grep "AdGuardHome" |grep "update_core" |grep -v "grep" |awk '{print $1}' |wc -l)" - [ "${running_tasks}" -gt "2" ] && echo -e "\nA task is already running." && EXIT 2 -} - -check_wgetcurl(){ - which curl && downloader="curl -L -k --retry 2 --connect-timeout 20 -o" && return - which wget-ssl && downloader="wget-ssl --no-check-certificate -t 2 -T 20 -O" && return - [ -z "$1" ] && opkg update || (echo error opkg && EXIT 1) - [ -z "$1" ] && (opkg remove wget wget-nossl --force-depends ; opkg install wget ; check_wgetcurl 1 ;return) - [ "$1" == "1" ] && (opkg install curl ; check_wgetcurl 2 ; return) - echo error curl and wget && EXIT 1 -} -check_latest_version(){ - check_wgetcurl - echo -e "Check for update..." - if [ "$tagname" = "beta" ]; then - latest_ver="$(echo `$downloader - https://api.github.com/repos/AdguardTeam/AdGuardHome/releases 2>/dev/null|grep -E '(tag_name|prerelease)'`|sed 's#"tag#\n"tag#g'|grep "true"|head -n1|cut -d '"' -f4 2>/dev/null)" - else - latest_ver="$($downloader - https://api.github.com/repos/AdguardTeam/AdGuardHome/releases/latest 2>/dev/null|grep -E 'tag_name'|head -n1|cut -d '"' -f4 2>/dev/null)" - fi - if [ -z "${latest_ver}" ]; then - echo -e "\nFailed to check latest version, please try again later." && EXIT 1 - fi - now_ver="$($binpath --version 2>/dev/null | grep -m 1 -oE '[v]{0,1}[0-9]+[.][Bbeta0-9\.\-]+')" - if [ "${latest_ver}"x != "${now_ver}"x ] || [ "$1" == "force" ]; then - echo -e "Local version: ${now_ver}. Cloud version: ${latest_ver}." - doupdate_core - else - echo -e "\nLocal version: ${now_ver}, cloud version: ${latest_ver}." - echo -e "You're already using the latest version." - if [ ! -z "$upxflag" ]; then - filesize=$(ls -l $binpath | awk '{ print $5 }') - if [ $filesize -gt 8000000 ]; then - echo -e "start upx may take a long time" - doupx - mkdir -p "/tmp/AdGuardHomeupdate/AdGuardHome" >/dev/null 2>&1 - rm -fr /tmp/AdGuardHomeupdate/AdGuardHome/${binpath##*/} - /tmp/upx-${upx_latest_ver}-${Arch}_linux/upx $upxflag $binpath -o /tmp/AdGuardHomeupdate/AdGuardHome/${binpath##*/} - rm -rf /tmp/upx-${upx_latest_ver}-${Arch}_linux - /etc/init.d/AdGuardHome stop nobackup - rm $binpath - mv -f /tmp/AdGuardHomeupdate/AdGuardHome/${binpath##*/} $binpath - /etc/init.d/AdGuardHome start - echo -e "finished" - fi - fi - EXIT 0 - fi -} -doupx(){ - Archt="$(opkg info kernel | grep Architecture | awk -F "[ _]" '{print($2)}')" - case $Archt in - "i386") - Arch="i386" - ;; - "i686") - Arch="i386" - echo -e "i686 use $Arch may have bug" - ;; - "x86") - Arch="amd64" - ;; - "mipsel") - Arch="mipsel" - ;; - "mips64el") - Arch="mips64el" - Arch="mipsel" - echo -e "mips64el use $Arch may have bug" - ;; - "mips") - Arch="mips" - ;; - "mips64") - Arch="mips64" - Arch="mips" - echo -e "mips64 use $Arch may have bug" - ;; - "arm") - Arch="arm" - ;; - "armeb") - Arch="armeb" - ;; - "aarch64") - Arch="arm64" - ;; - "powerpc") - Arch="powerpc" - ;; - "powerpc64") - Arch="powerpc64" +Check_Task(){ + running_tasks=$(pgrep -f "update_core.sh" 2>/dev/null | grep -v "^$$\$" | wc -l) + running_tasks=${running_tasks:-0} + + case $1 in + force) + printf "%s\n" "执行: 强制更新核心" + if [ "$running_tasks" -gt 0 ]; then + printf "%s\n" "清除 ${running_tasks} 个进程 ..." + pkill -9 -f "update_core.sh" 2>/dev/null + sleep 1 + fi ;; *) - echo -e "error not support $Archt if you can use offical release please issue a bug" - EXIT 1 + if [ "$running_tasks" -gt 1 ]; then + printf "%s\n" "已经有更新任务正在运行, 请等待其执行结束或点击强制更新!" + EXIT 2 + fi ;; esac - upx_latest_ver="$($downloader - https://api.github.com/repos/upx/upx/releases/latest 2>/dev/null|grep -E 'tag_name' |grep -E '[0-9.]+' -o 2>/dev/null)" - $downloader /tmp/upx-${upx_latest_ver}-${Arch}_linux.tar.xz "https://github.com/upx/upx/releases/download/v${upx_latest_ver}/upx-${upx_latest_ver}-${Arch}_linux.tar.xz" 2>&1 - #tar xvJf - which xz || (opkg list | grep ^xz || opkg update && opkg install xz) || (echo "xz download fail" && EXIT 1) - mkdir -p /tmp/upx-${upx_latest_ver}-${Arch}_linux - xz -d -c /tmp/upx-${upx_latest_ver}-${Arch}_linux.tar.xz| tar -x -C "/tmp" >/dev/null 2>&1 - if [ ! -e "/tmp/upx-${upx_latest_ver}-${Arch}_linux/upx" ]; then - echo -e "Failed to download upx." - EXIT 1 - fi - rm /tmp/upx-${upx_latest_ver}-${Arch}_linux.tar.xz } -doupdate_core(){ - echo -e "Updating core..." - mkdir -p "/tmp/AdGuardHomeupdate" - rm -rf /tmp/AdGuardHomeupdate/* >/dev/null 2>&1 - Arch=$(uci -q get AdGuardHome.AdGuardHome.arch) - if [ -z "$Arch" ]; then - Archt="$(opkg info kernel | grep Architecture | awk -F "[ _]" '{print($2)}')" - case $Archt in - "i386"|"i486"|"i686"|"i786") - Arch="386" - ;; - "x86") - Arch="amd64" - ;; - "mipsel") - Arch="mipsle_softfloat" - ;; - "mips64el") - Arch="mips64le_softfloat" - ;; - "mips") - Arch="mips_softfloat" - ;; - "mips64") - Arch="mips64_softfloat" - ;; - "arm") - um=`uname -m` - if [ $um = "armv8l" ]; then - Arch="armv7" - elif [ $um = "armv6l" ]; then - Arch="armv6" - else - Arch="armv5" - fi - ;; - "aarch64") - Arch="arm64" - ;; - "powerpc") - Arch="ppc" - echo -e "error not support $Archt" - EXIT 1 - ;; - "powerpc64") - Arch="ppc64le" - ;; - *) - echo -e "error not support $Archt if you can use offical release please issue a bug" - EXIT 1 - ;; - esac - fi - echo -e "start download" - grep -v "^#" /usr/share/AdGuardHome/links.txt >/tmp/run/AdHlinks.txt - while read link - do - [ -n "$link" ] || continue - eval link="$link" - $downloader /tmp/AdGuardHomeupdate/${link##*/} "$link" 2>&1 - if [ "$?" != "0" ]; then - echo "download failed try another download" - rm -f /tmp/AdGuardHomeupdate/${link##*/} - else - local success="1" - break - fi - done < "/tmp/run/AdHlinks.txt" - rm /tmp/run/AdHlinks.txt - [ -z "$success" ] && echo "no download success" && EXIT 1 - if [ "${link##*.}" == "gz" ]; then - tar -zxf "/tmp/AdGuardHomeupdate/${link##*/}" -C "/tmp/AdGuardHomeupdate/" - if [ ! -e "/tmp/AdGuardHomeupdate/AdGuardHome" ]; then - echo -e "Failed to download core." - rm -rf "/tmp/AdGuardHomeupdate" >/dev/null 2>&1 - EXIT 1 - fi - downloadbin="/tmp/AdGuardHomeupdate/AdGuardHome/AdGuardHome" + +Check_Updates(){ + downloader_cmd="uclient-fetch" + downloader_opts="--no-check-certificate -T 5 -q -O" + downloader_quiet_opts="-q -O -" + printf "%s\n" "开始检查更新, 请耐心等待 ..." + Cloud_Version="$(${downloader_cmd} ${downloader_quiet_opts} "$core_api_url" 2>/dev/null | sed -n 's/.*"tag_name":[ ]*"\(v[^"]*\)".*/\1/p' | head -n 1)" + [ -z "$Cloud_Version" ] && printf "\n检查更新失败, 请检查网络或稍后重试!\n" && EXIT 1 + if [ -f "$binpath" ]; then + Current_Version="$($binpath --version 2>/dev/null | sed -n 's/.*\(v[0-9][0-9.]*\).*/\1/p' | head -n 1)" else - downloadbin="/tmp/AdGuardHomeupdate/${link##*/}" + Current_Version="未知" fi - chmod 755 $downloadbin - echo -e "download success start copy" - if [ -n "$upxflag" ]; then - echo -e "start upx may take a long time" - doupx - /tmp/upx-${upx_latest_ver}-${Arch}_linux/upx $upxflag $downloadbin - rm -rf /tmp/upx-${upx_latest_ver}-${Arch}_linux + [ -z "$Current_Version" ] && Current_Version="未知" + printf "\n执行文件路径: %s\n\n正在检查更新, 请耐心等待 ...\n" "${binpath%/*}" + printf "\n当前 AdGuardHome 版本: %s\n云端 AdGuardHome 版本: %s\n" "$Current_Version" "$Cloud_Version" + if [ "$Cloud_Version" != "$Current_Version" ] || [ "$1" = force ]; then + Update_Core + else + printf "\n已是最新版本, 无需更新!\n" + EXIT 0 fi - echo -e "start copy" - /etc/init.d/AdGuardHome stop nobackup - rm -f "$binpath" - mv -f "$downloadbin" "$binpath" - if [ "$?" == "1" ]; then - echo "mv failed maybe not enough space please use upx or change bin to /tmp/AdGuardHome" - EXIT 1 - fi - /etc/init.d/AdGuardHome start - rm -rf "/tmp/AdGuardHomeupdate" >/dev/null 2>&1 - echo -e "Succeeded in updating core." - echo -e "Local version: ${latest_ver}, cloud version: ${latest_ver}.\n" EXIT 0 } + +Update_Core(){ + rm -rf /tmp/AdGuardHome_Update > /dev/null 2>&1 + mkdir -p "/tmp/AdGuardHome_Update" + GET_Arch + link="https://github.com/AdguardTeam/AdGuardHome/releases/download/${Cloud_Version}/AdGuardHome_linux_${Arch}.tar.gz" + printf "%s\n" "下载链接:${link}" + printf "%s\n" "文件名称:${link##*/}" + printf "\n开始下载 AdGuardHome 核心文件 ...\n\n" + ${downloader_cmd} ${downloader_opts} "/tmp/AdGuardHome_Update/${link##*/}" "$link" > /dev/null 2>&1 + if [ $? -ne 0 ]; then + printf "\nAdGuardHome 核心下载失败 ...\n" + rm -rf /tmp/AdGuardHome_Update + EXIT 1 + fi + if [ "${link##*.}" = "gz" ]; then + printf "\n正在解压 AdGuardHome ...\n" + tar -zxf "/tmp/AdGuardHome_Update/${link##*/}" -C "/tmp/AdGuardHome_Update/" + if [ ! -e /tmp/AdGuardHome_Update/AdGuardHome ] + then + printf "%s\n" "AdGuardHome 核心解压失败!" + rm -rf "/tmp/AdGuardHome_Update" > /dev/null 2>&1 + EXIT 1 + fi + downloadbin="/tmp/AdGuardHome_Update/AdGuardHome/AdGuardHome" + else + downloadbin="/tmp/AdGuardHome_Update/${link##*/}" + fi + chmod +x "$downloadbin" + size_bytes=$(wc -c < "$downloadbin" 2>/dev/null || echo 0) + printf "\nAdGuardHome 核心体积: %s\n" "$(awk -v bytes="$size_bytes" 'BEGIN{printf "%.2fMB", bytes/1000000}')" + /etc/init.d/AdGuardHome stop > /dev/null 2>&1 + printf "\n移动 AdGuardHome 核心文件到 %s ...\n" "${binpath%/*}" + mv -f "$downloadbin" "$binpath" > /dev/null 2>&1 + if [ ! -s "$binpath" ] && [ $? -ne 0 ]; then + printf "AdGuardHome 核心移动失败!\n可能是设备空间不足导致, 请尝试更改 [执行文件路径] 为 /tmp/AdGuardHome\n" + EXIT 1 + fi + rm -rf /tmp/AdGuardHome_Update + chmod +x "$binpath" + if [ "$enabled" = 1 ]; then + printf "\n正在重启 AdGuardHome 服务...\n" + /etc/init.d/AdGuardHome restart > /dev/null 2>&1 + fi + printf "\nAdGuardHome 核心更新成功!\n" +} + +GET_Arch() { + if command -v apk > /dev/null 2>&1; then + PM="apk" + elif command -v opkg > /dev/null 2>&1; then + PM="opkg" + else + printf "%s\n" "未找到包管理器 (apk/opkg)!" && EXIT 1 + fi + case "${PM}" in + apk) + Archt="$(apk info -a kernel 2>/dev/null | grep Architecture | awk -F ":[ ]*" '{print $2}')" + [ -z "$Archt" ] && Archt="$(uname -m)" + ;; + opkg) + Archt="$(opkg info kernel | grep Architecture | awk -F "[ _]" '{print($2)}')" + ;; + esac + case "${Archt}" in + i386) + Arch=i386 + ;; + i686) + Arch=i386 + ;; + x86|x86_64) + Arch=amd64 + ;; + mipsel) + Arch=mipsle_softfloat + ;; + mips) + Arch=mips_softfloat + ;; + mips64el) + Arch=mips64le_softfloat + ;; + mips64) + Arch=mips64_softfloat + ;; + arm) + Arch=arm + ;; + armeb) + Arch=armeb + ;; + aarch64) + Arch=arm64 + ;; + *) + printf "\nAdGuardHome 暂不支持当前的设备架构: [%s]!\n" "$Archt" + EXIT 1 + esac + printf "\n当前设备架构: %s\n" "$Arch" +} + EXIT(){ - rm /var/run/update_core 2>/dev/null - [ "$1" != "0" ] && touch /var/run/update_core_error + rm -rf /var/run/update_core $LOCKU 2>/dev/null + [ "$1" -ne 0 ] && touch /var/run/update_core_error exit $1 } + main(){ - - check_if_already_running - check_latest_version $1 + Check_Task "$update_mode" + Check_Updates "$update_mode" } - trap "EXIT 1" SIGTERM SIGINT - touch /var/run/update_core - rm /var/run/update_core_error 2>/dev/null - main $1 + +trap "EXIT 1" SIGTERM SIGINT +touch /var/run/update_core +rm -rf /var/run/update_core_error 2>/dev/null + +main \ No newline at end of file diff --git a/luci-app-adguardhome/root/usr/share/AdGuardHome/waitnet.sh b/luci-app-adguardhome/root/usr/share/AdGuardHome/waitnet.sh deleted file mode 100644 index a79a8bac..00000000 --- a/luci-app-adguardhome/root/usr/share/AdGuardHome/waitnet.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/bin/sh -PATH="/usr/sbin:/usr/bin:/sbin:/bin" -count=0 -while : -do - ping -c 1 -W 1 -q www.baidu.com 1>/dev/null 2>&1 - if [ "$?" == "0" ]; then - /etc/init.d/AdGuardHome force_reload - break - fi - ping -c 1 -W 1 -q 202.108.22.5 1>/dev/null 2>&1 - if [ "$?" == "0" ]; then - /etc/init.d/AdGuardHome force_reload - break - fi - sleep 5 - ping -c 1 -W 1 -q www.google.com 1>/dev/null 2>&1 - if [ "$?" == "0" ]; then - /etc/init.d/AdGuardHome force_reload - break - fi - ping -c 1 -W 1 -q 8.8.8.8 1>/dev/null 2>&1 - if [ "$?" == "0" ]; then - /etc/init.d/AdGuardHome force_reload - break - fi - sleep 5 - count=$((count+1)) - if [ $count -gt 18 ]; then - /etc/init.d/AdGuardHome force_reload - break - fi -done -return 0 \ No newline at end of file diff --git a/luci-app-adguardhome/root/usr/share/AdGuardHome/watchconfig.sh b/luci-app-adguardhome/root/usr/share/AdGuardHome/watchconfig.sh old mode 100644 new mode 100755 index c5252250..f6850663 --- a/luci-app-adguardhome/root/usr/share/AdGuardHome/watchconfig.sh +++ b/luci-app-adguardhome/root/usr/share/AdGuardHome/watchconfig.sh @@ -1,4 +1,5 @@ #!/bin/sh + PATH="/usr/sbin:/usr/bin:/sbin:/bin" configpath=$(uci get AdGuardHome.AdGuardHome.configpath) while : @@ -9,4 +10,4 @@ do break fi done -return 0 \ No newline at end of file +exit 0 \ No newline at end of file diff --git a/luci-app-adguardhome/root/usr/share/luci/menu.d/luci-app-adguardhome-js.json b/luci-app-adguardhome/root/usr/share/luci/menu.d/luci-app-adguardhome-js.json new file mode 100644 index 00000000..c7bebea7 --- /dev/null +++ b/luci-app-adguardhome/root/usr/share/luci/menu.d/luci-app-adguardhome-js.json @@ -0,0 +1,45 @@ +{ + "admin/services/AdGuardHomeJS": { + "title": "AdGuard Home JS", + "order": 12, + "action": { + "type": "firstchild" + }, + "depends": { + "acl": [ "luci-app-adguardhome" ] + } + }, + "admin/services/AdGuardHomeJS/base": { + "title": "Base Setting", + "order": 1, + "action": { + "type": "view", + "path": "adguardhome/base" + }, + "depends": { + "acl": [ "luci-app-adguardhome" ] + } + }, + "admin/services/AdGuardHomeJS/log": { + "title": "Log", + "order": 3, + "action": { + "type": "view", + "path": "adguardhome/log" + }, + "depends": { + "acl": [ "luci-app-adguardhome" ] + } + }, + "admin/services/AdGuardHomeJS/manual": { + "title": "Manual Config", + "order": 2, + "action": { + "type": "view", + "path": "adguardhome/manual" + }, + "depends": { + "acl": [ "luci-app-adguardhome" ] + } + } +} \ No newline at end of file diff --git a/luci-app-adguardhome/root/usr/share/rpcd/acl.d/luci-app-adguardhome.json b/luci-app-adguardhome/root/usr/share/rpcd/acl.d/luci-app-adguardhome.json index 467b1766..aa6dfeca 100644 --- a/luci-app-adguardhome/root/usr/share/rpcd/acl.d/luci-app-adguardhome.json +++ b/luci-app-adguardhome/root/usr/share/rpcd/acl.d/luci-app-adguardhome.json @@ -2,29 +2,20 @@ "luci-app-adguardhome": { "description": "Grant UCI access for luci-app-adguardhome", "read": { - "file": { - "/usr/share/AdGuardHome/addhost.sh": [ "exec" ], - "/usr/share/AdGuardHome/AdGuardHome_template.yaml": [ "read" ], - "/usr/share/AdGuardHome/getsyslog.sh": [ "exec" ], - "/usr/share/AdGuardHome/gfw2adg.sh": [ "exec" ], - "/usr/share/AdGuardHome/gfwipset2adg.sh": [ "exec" ], - "/usr/share/AdGuardHome/links.txt": [ "read" ], - "/usr/share/AdGuardHome/tailto.sh": [ "exec" ], - "/usr/share/AdGuardHome/update_core.sh": [ "exec" ], - "/usr/share/AdGuardHome/waitnet.sh": [ "exec" ], - "/usr/share/AdGuardHome/watchconfig.sh": [ "exec" ], - "/etc/init.d/AdGuardHome": [ "exec" ], - "/etc/init.d/dnsmasq": [ "exec" ], - "/usr/bin/AdGuardHome": [ "exec" ] - }, - "uci": [ "AdGuardHome", "adguardhome" ] + "file": { + "/var/run/AdGredir": [ "read" ] + }, + "ubus": { + "service": [ "list" ], + "luci.adguardhome": [ "getCoreInfo", "getCoreUpdate", "getInterfaces", "getRuntimeLog", "getManualConfig" ] + }, + "uci": [ "AdGuardHome" ] }, "write": { - "file": { - "/usr/share/AdGuardHome/AdGuardHome_template.yaml": [ "write" ], - "/usr/share/AdGuardHome/links.txt": [ "write" ] - }, - "uci": [ "AdGuardHome", "adguardhome" ] + "ubus": { + "luci.adguardhome": [ "startCoreUpdate", "changeWebPassword", "clearRuntimeLog", "setManualConfig" ] + }, + "uci": [ "AdGuardHome" ] } } -} +} \ No newline at end of file diff --git a/luci-app-adguardhome/root/usr/share/rpcd/ucode/luci.adguardhome b/luci-app-adguardhome/root/usr/share/rpcd/ucode/luci.adguardhome new file mode 100644 index 00000000..d4987a4a --- /dev/null +++ b/luci-app-adguardhome/root/usr/share/rpcd/ucode/luci.adguardhome @@ -0,0 +1,198 @@ +#!/usr/bin/ucode +'use strict'; + +import { stat, popen, readfile, writefile, unlink, lsdir } from 'fs'; +import { cursor } from 'uci'; + +function shellQuote(value) { + return "'" + replace(value, /'/g, "'\\''") + "'"; +} + +return { + 'luci.adguardhome': { + getManualConfig: { + call: function() { + const conf = cursor(); + const configpath = conf.get('AdGuardHome', 'AdGuardHome', 'configpath') || + '/etc/AdGuardHome.yaml'; + conf.unload('AdGuardHome'); + + const template = readfile('/usr/share/AdGuardHome/AdGuardHome_template.yaml') || ''; + const current = readfile(configpath); + if (current != null) + return { content: current, template: template, source: 'current' }; + if (stat(configpath) != null) + return { content: '', template: template, source: 'error' }; + return { + content: template, + template: template, + source: template ? 'template' : 'missing' + }; + } + }, + setManualConfig: { + args: { content: '' }, + call: function(request) { + const content = request.args.content; + if (content == null) + return { saved: false, reason: 'invalid_content' }; + + const conf = cursor(); + const configpath = conf.get('AdGuardHome', 'AdGuardHome', 'configpath') || + '/etc/AdGuardHome.yaml'; + const binpath = conf.get('AdGuardHome', 'AdGuardHome', 'binpath') || + '/usr/bin/AdGuardHome/AdGuardHome'; + conf.unload('AdGuardHome'); + + const temporaryConfig = '/tmp/AdGuardHometmpconfig.yaml'; + const validationLog = '/tmp/AdGuardHometest.log'; + if (writefile(temporaryConfig, replace(content, /\r\n/g, '\n')) == null) + return { saved: false, reason: 'write_failed' }; + + if (stat(binpath)?.type == 'file') { + const valid = system(['/bin/sh', '-c', '"$1" -c "$2" --check-config > "$3" 2>&1', + 'sh', binpath, temporaryConfig, validationLog], 30000) == 0; + if (!valid) + return { saved: false, reason: 'validation_failed', log: readfile(validationLog) || '' }; + } + + if (writefile(configpath, readfile(temporaryConfig) || '') == null) + return { saved: false, reason: 'save_failed' }; + unlink(temporaryConfig); + if (system(['/etc/init.d/AdGuardHome', 'reload'], 30000) != 0) + return { saved: false, reason: 'reload_failed' }; + return { saved: true }; + } + }, + getInterfaces: { + call: function() { + return { interfaces: lsdir('/sys/class/net') || [] }; + } + }, + getCoreInfo: { + call: function() { + const conf = cursor(); + const binpath = conf.get('AdGuardHome', 'AdGuardHome', 'binpath') || + '/usr/bin/AdGuardHome/AdGuardHome'; + const configpath = conf.get('AdGuardHome', 'AdGuardHome', 'configpath') || + '/etc/AdGuardHome.yaml'; + const result = { + core_exists: stat(binpath)?.type == 'file', + config_exists: stat(configpath)?.type == 'file', + version: null + }; + + if (result.core_exists) { + // Pass the configured path as a positional argument; capture both output streams. + const proc = popen('/bin/sh -c \'"$1" --version 2>&1\' sh ' + shellQuote(binpath), 'r'); + if (proc) { + const output = proc.read('all') || ''; + proc.close(); + result.version = match(output, /version ([^, \t\r\n]+)/)?.[1] || null; + } + } + + conf.unload('AdGuardHome'); + return result; + } + }, + getCoreUpdate: { + call: function() { + const running = stat('/var/run/update_core') != null; + const failed = stat('/var/run/update_core_error') != null; + const log = readfile('/tmp/AdGuardHome_update.log', 131072) || ''; + + return { + state: running ? 'running' : failed ? 'failure' : log ? 'finished' : 'idle', + log: log + }; + } + }, + startCoreUpdate: { + args: { force: false }, + call: function(request) { + if (stat('/var/run/update_core') != null) + return { started: false, reason: 'busy' }; + + unlink('/var/run/update_core_error'); + unlink('/tmp/AdGuardHome_update.log'); + + const command = request.args.force + ? '/bin/sh /usr/share/AdGuardHome/update_core.sh force >/tmp/AdGuardHome_update.log 2>&1 /tmp/AdGuardHome_update.log 2>&1 /dev/null 2>&1 &'], 3000); + } + + const log = readfile(source) || ''; + const size = length(log); + let offset = +request.args.offset || 0; + if (offset < 0 || offset > size) + offset = 0; + const content = substr(log, offset, 2097152); + + return { + enabled: true, + localtime: logfile != 'syslog', + content: content, + offset: offset + length(content) + }; + } + }, + clearRuntimeLog: { + call: function() { + const conf = cursor(); + const logfile = conf.get('AdGuardHome', 'AdGuardHome', 'logfile') || ''; + conf.unload('AdGuardHome'); + if (!logfile) + return { cleared: false, reason: 'no_logfile' }; + + const source = logfile == 'syslog' ? '/tmp/AdGuardHometmp.log' : logfile; + return { cleared: writefile(source, '') != null }; + } + } + } +}; \ No newline at end of file diff --git a/luci-app-adguardhome/root/www/luci-static/resources/codemirror/addon/fold/foldcode.js b/luci-app-adguardhome/root/www/luci-static/resources/codemirror/addon/fold/foldcode.js deleted file mode 100644 index f93d42b7..00000000 --- a/luci-app-adguardhome/root/www/luci-static/resources/codemirror/addon/fold/foldcode.js +++ /dev/null @@ -1 +0,0 @@ -!function(n){"object"==typeof exports&&"object"==typeof module?n(require("../../lib/codemirror")):"function"==typeof define&&define.amd?define(["../../lib/codemirror"],n):n(CodeMirror)}(function(n){"use strict";function e(e,o,i,t){if(i&&i.call){var l=i;i=null}else l=r(e,i,"rangeFinder");"number"==typeof o&&(o=n.Pos(o,0));var f=r(e,i,"minFoldSize");function d(n){var r=l(e,o);if(!r||r.to.line-r.from.linee.firstLine();)o=n.Pos(o.line-1,0),u=d(!1);if(u&&!u.cleared&&"unfold"!==t){var a=function(n,e){var o=r(n,e,"widget");if("string"==typeof o){var i=document.createTextNode(o);(o=document.createElement("span")).appendChild(i),o.className="CodeMirror-foldmarker"}else o&&(o=o.cloneNode(!0));return o}(e,i);n.on(a,"mousedown",function(e){c.clear(),n.e_preventDefault(e)});var c=e.markText(u.from,u.to,{replacedWith:a,clearOnEnter:r(e,i,"clearOnEnter"),__isFold:!0});c.on("clear",function(o,r){n.signal(e,"unfold",e,o,r)}),n.signal(e,"fold",e,u.from,u.to)}}n.newFoldFunction=function(n,o){return function(r,i){e(r,i,{rangeFinder:n,widget:o})}},n.defineExtension("foldCode",function(n,o,r){e(this,n,o,r)}),n.defineExtension("isFolded",function(n){for(var e=this.findMarksAt(n),o=0;o=u){if(s&&f&&s.test(f.className))return;i=r(a.indicatorOpen)}}(i||f)&&t.setGutterMarker(n,a.gutter,i)})}function i(t){return new RegExp("(^|\\s)"+t+"(?:$|\\s)\\s*")}function f(t){var o=t.getViewport(),e=t.state.foldGutter;e&&(t.operation(function(){n(t,o.from,o.to)}),e.from=o.from,e.to=o.to)}function a(t,r,n){var i=t.state.foldGutter;if(i){var f=i.options;if(n==f.gutter){var a=e(t,r);a?a.clear():t.foldCode(o(r,0),f)}}}function d(t){var o=t.state.foldGutter;if(o){var e=o.options;o.from=o.to=0,clearTimeout(o.changeUpdate),o.changeUpdate=setTimeout(function(){f(t)},e.foldOnChangeTimeSpan||600)}}function u(t){var o=t.state.foldGutter;if(o){var e=o.options;clearTimeout(o.changeUpdate),o.changeUpdate=setTimeout(function(){var e=t.getViewport();o.from==o.to||e.from-o.to>20||o.from-e.to>20?f(t):t.operation(function(){e.fromo.to&&(n(t,o.to,e.to),o.to=e.to)})},e.updateViewportTimeSpan||400)}}function l(t,o){var e=t.state.foldGutter;if(e){var r=o.line;r>=e.from&&ro))break;r=l}}return r?{from:e.Pos(i.line,t.getLine(i.line).length),to:e.Pos(r,t.getLine(r).length)}:void 0}})}); \ No newline at end of file diff --git a/luci-app-adguardhome/root/www/luci-static/resources/view/adguardhome/base.js b/luci-app-adguardhome/root/www/luci-static/resources/view/adguardhome/base.js new file mode 100644 index 00000000..b17993a2 --- /dev/null +++ b/luci-app-adguardhome/root/www/luci-static/resources/view/adguardhome/base.js @@ -0,0 +1,265 @@ +'use strict'; +'require view'; +'require form'; +'require poll'; +'require rpc'; +'require view/adguardhome/status as status'; + +var callInterfaces = rpc.declare({ + object: 'luci.adguardhome', + method: 'getInterfaces', + expect: { '': {} }, + reject: true +}); + +var callCoreUpdate = rpc.declare({ + object: 'luci.adguardhome', + method: 'getCoreUpdate', + expect: { '': {} }, + reject: true +}); + +var callStartCoreUpdate = rpc.declare({ + object: 'luci.adguardhome', + method: 'startCoreUpdate', + params: [ 'force' ], + expect: { '': {} }, + reject: true +}); + +return view.extend({ + load: function() { + return callInterfaces().catch(function() { return { interfaces: [] }; }); + }, + + render: function(data) { + let m, s, o; + var interfaces = data.interfaces || []; + + m = new form.Map('AdGuardHome'); + + s = m.section( + form.TypedSection, + 'AdGuardHome', + _('Basic Settings') + ); + + s.anonymous = true; + s.addremove = false; + + o = s.option( + form.Flag, + 'enabled', + _('Enable') + ); + o.default = '0'; + o.rmempty = false; + + o = s.option( + form.Value, + 'httpport', + _('Browser management port') + ); + o.datatype = 'port'; + o.placeholder = '3000'; + o.default = '3000'; + o.rmempty = false; + + o = s.option( + form.ListValue, + 'redirect', + _('Redirect mode'), + _('AdGuardHome redirect mode') + ); + + o.value('none', _('None')); + o.value('dnsmasq-upstream', _('Run as dnsmasq upstream server')); + o.value('redirect', _('Redirect port 53 to AdGuardHome')); + o.value('exchange', _('Use port 53 instead of dnsmasq')); + + o.default = 'none'; + o.rmempty = false; + + o = s.option( + form.DynamicList, + 'wan_ifname', + _('WAN Interface'), + _('Only effective in redirect mode, bypass specified interfaces to avoid becoming a public DNS resolver') + ); + o.datatype = 'string'; + o.rmempty = true; + interfaces.filter(function(name) { return name !== 'lo'; }).forEach(function(name) { + o.value(name, name); + }); + + o = s.option( + form.Value, + 'binpath', + _('Bin Path'), + _('AdGuardHome binary path') + ); + o.default = '/usr/bin/AdGuardHome/AdGuardHome'; + o.rmempty = false; + + o = s.option( + form.Value, + 'configpath', + _('Config Path'), + _('AdGuardHome config path') + ); + o.default = '/etc/AdGuardHome.yaml'; + o.rmempty = false; + + o = s.option( + form.Value, + 'workdir', + _('Work dir'), + _('AdGuardHome work dir including rules, audit log and database') + ); + o.default = '/usr/bin/AdGuardHome'; + o.rmempty = false; + + o = s.option( + form.Value, + 'logfile', + _('Runtime log file'), + _('If set to "syslog", write to system log; if empty, disable file logging') + ); + o.rmempty = true; + + o = s.option( + form.Flag, + 'verbose', + _('Verbose log') + ); + o.default = '0'; + o.rmempty = true; + + return m.render().then(function(node) { + var sharedStatus = status.render(); + + var updateButton = E('button', { + 'class': 'btn cbi-button cbi-button-apply', 'type': 'button' + }, _('Update core version')); + var forceButton = E('button', { + 'class': 'btn cbi-button', 'type': 'button', 'style': 'margin-left:0.6em;' + }, _('Force update')); + var updateMessage = E('span', { 'style': 'margin-left:0.8em;' }, ''); + var reverseLog = E('input', { 'type': 'checkbox' }); + var updateLog = E('textarea', { + 'class': 'cbi-input-textarea', 'rows': 8, 'readonly': 'readonly', + 'style': 'display:none;width:100%;margin-top:0.6em;box-sizing:border-box;' + }); + var logOptions = E('label', { 'style': 'display:none;margin-top:0.6em;' }, [ + reverseLog, ' ' + _('reverse') + ]); + var updatePanel = E('div', { 'class': 'cbi-section' }, [ + E('h3', {}, _('Upgrade Core')), + E('div', { 'style': 'padding:0.6em;' }, [updateButton, forceButton, updateMessage]), + logOptions, updateLog + ]); + var lastLog = ''; + var startingAt = 0; + var activeUpdate = false; + var updateFinishedHere = false; + + function showLog() { + updateLog.value = reverseLog.checked ? lastLog.split('\n').reverse().join('\n') : lastLog; + } + reverseLog.addEventListener('change', showLog); + + function showUpdateStatus(result) { + var state = result.state || 'idle'; + if (startingAt && state === 'idle' && Date.now() - startingAt < 10000) + return; + startingAt = 0; + if (state === 'running') + activeUpdate = true; + else if (activeUpdate && (state === 'finished' || state === 'failure')) { + activeUpdate = false; + updateFinishedHere = true; + sharedStatus.refresh(); + } + + updateButton.disabled = forceButton.disabled = state === 'running'; + if (state === 'running' || updateFinishedHere) { + updateMessage.textContent = state === 'running' ? _('Check...') : + state === 'finished' ? _('Updated') : _('Update failed'); + lastLog = result.log || ''; + logOptions.style.display = updateLog.style.display = lastLog ? 'block' : 'none'; + showLog(); + } else { + updateMessage.textContent = ''; + lastLog = ''; + logOptions.style.display = updateLog.style.display = 'none'; + } + } + + function startUpdate(force) { + startingAt = Date.now(); + activeUpdate = true; + updateFinishedHere = false; + updateButton.disabled = forceButton.disabled = true; + updateMessage.textContent = _('Check...'); + lastLog = ''; + showLog(); + updateLog.style.display = logOptions.style.display = 'none'; + callStartCoreUpdate(force).then(function(result) { + if (!result.started) { + startingAt = 0; + activeUpdate = false; + updateMessage.textContent = _('Update in progress'); + return; + } + return callCoreUpdate().then(showUpdateStatus); + }).catch(function() { + startingAt = 0; + activeUpdate = false; + updateButton.disabled = forceButton.disabled = false; + updateMessage.textContent = _('Unable to start update'); + }); + } + + updateButton.addEventListener('click', function() { startUpdate(false); }); + forceButton.addEventListener('click', function() { startUpdate(true); }); + poll.add(function() { + return callCoreUpdate().then(showUpdateStatus).catch(function() { + updateMessage.textContent = _('Update status unavailable'); + }); + }, 3); + node.insertBefore(sharedStatus.node, node.firstChild); + sharedStatus.node.parentNode.insertBefore(updatePanel, sharedStatus.node.nextSibling); + + var portInput = node.querySelector('[data-name="httpport"] input'); + var portField = node.querySelector('[data-name="httpport"] .cbi-value-field'); + if (portInput && portField) { + var webLink = E('input', { + 'type': 'button', + 'style': 'width:210px;border-color:Teal;text-align:center;font-weight:bold;color:#337ab7;background:#ffc800;', + 'target': '_blank', + 'value': _('AdGuardHome Web') + }); + var linkWrap = E('div', { 'style': 'margin-top:0.6em;' }, webLink); + var host = window.location.hostname; + if (host.indexOf(':') >= 0 && host.charAt(0) !== '[') + host = '[' + host + ']'; + + function updateWebLink() { + var port = portInput.value.trim(); + var valid = /^[0-9]+$/.test(port) && +port >= 1 && +port <= 65535; + webLink.value = _('AdGuardHome Web') + ': ' + port; + webLink.onclick = valid ? function() { + window.open('http://' + host + ':' + port + '/'); + } : null; + webLink.style.display = valid ? 'inline-block' : 'none'; + } + + portField.appendChild(linkWrap); + portInput.addEventListener('input', updateWebLink); + updateWebLink(); + } + + return node; + }); + } +}); \ No newline at end of file diff --git a/luci-app-adguardhome/root/www/luci-static/resources/view/adguardhome/log.js b/luci-app-adguardhome/root/www/luci-static/resources/view/adguardhome/log.js new file mode 100644 index 00000000..d44dd65c --- /dev/null +++ b/luci-app-adguardhome/root/www/luci-static/resources/view/adguardhome/log.js @@ -0,0 +1,154 @@ +'use strict'; +'require view'; +'require poll'; +'require rpc'; +'require view/adguardhome/status as status'; + +var callRuntimeLog = rpc.declare({ + object: 'luci.adguardhome', + method: 'getRuntimeLog', + params: [ 'offset' ], + expect: { '': {} }, + reject: true +}); + +var callClearRuntimeLog = rpc.declare({ + object: 'luci.adguardhome', + method: 'clearRuntimeLog', + expect: { '': {} }, + reject: true +}); + +function pad(value) { + return value < 10 ? '0' + value : value; +} + +function localizeTimestamps(content) { + return content.split('\n').map(function(line) { + var date = new Date(line.substring(0, 19).replace(/\//g, '-') + ' UTC'); + if (isNaN(date.getTime())) + return line; + + return date.getFullYear() + '/' + pad(date.getMonth() + 1) + '/' + pad(date.getDate()) + ' ' + + pad(date.getHours()) + ':' + pad(date.getMinutes()) + ':' + pad(date.getSeconds()) + line.substring(19); + }).join('\n'); +} + +return view.extend({ + handleSave: null, + handleSaveApply: null, + handleReset: null, + + load: function() { + return callRuntimeLog(0).catch(function() { + return { enabled: false, localtime: false, content: '', offset: 0 }; + }); + }, + + render: function(initialLog) { + var sharedStatus = status.render(); + var content = initialLog.content || ''; + var offset = initialLog.offset || 0; + var enabled = initialLog.enabled; + var localtime = initialLog.localtime; + var reverse = true; + var convertTime = localtime; + var logArea = E('textarea', { + 'class': 'cbi-input-textarea', + 'rows': 32, + 'readonly': 'readonly', + 'style': 'width:100%;box-sizing:border-box;' + }); + var toggleStyle = 'margin:0 0.35em 0 0;height:auto;flex:none;'; + var reverseToggle = E('input', { 'type': 'checkbox', 'checked': true, 'style': toggleStyle }); + var timeToggle = E('input', { 'type': 'checkbox', 'checked': true, 'style': toggleStyle }); + var reverseLabel = E('label', { 'style': 'display:inline-flex;align-items:center;' }, [ + reverseToggle, _('reverse') + ]); + var timeLabel = E('label', { 'style': localtime ? 'display:inline-flex;align-items:center;margin-left:0.8em;' : 'display:none;' }, [ + timeToggle, _('localtime') + ]); + var message = E('span', { 'style': 'margin-left:0.8em;' }, ''); + + function renderLog() { + var displayed = convertTime ? localizeTimestamps(content) : content; + logArea.value = reverse ? displayed.split('\n').reverse().join('\n') : displayed; + } + + function downloadLog() { + var now = new Date(); + var link = document.createElement('a'); + link.download = 'AdGuardHome' + (now.getMonth() + 1) + '-' + now.getDate() + '-' + + pad(now.getHours()) + '_' + pad(now.getMinutes()) + '.log'; + link.href = URL.createObjectURL(new Blob([logArea.value], { type: 'text/plain' })); + link.click(); + URL.revokeObjectURL(link.href); + } + + var clearButton = E('button', { + 'class': 'btn cbi-button cbi-button-apply', 'type': 'button' + }, _('dellog')); + var downloadButton = E('button', { + 'class': 'btn cbi-button', 'type': 'button', 'style': 'margin-left:0.6em;' + }, _('download log')); + reverseToggle.addEventListener('change', function() { + reverse = reverseToggle.checked; + renderLog(); + }); + timeToggle.addEventListener('change', function() { + convertTime = timeToggle.checked; + renderLog(); + }); + clearButton.addEventListener('click', function() { + clearButton.disabled = true; + callClearRuntimeLog().then(function(result) { + if (!result.cleared) + throw new Error(result.reason || 'clear failed'); + content = ''; + offset = 0; + message.textContent = ''; + renderLog(); + }).catch(function() { + message.textContent = _('Status unavailable'); + }).then(function() { + clearButton.disabled = false; + }); + }); + downloadButton.addEventListener('click', downloadLog); + + var page = E('div', { 'class': 'cbi-map' }, [ + sharedStatus.node, + logArea, + E('div', { 'style': 'display:flex;align-items:center;padding:0.6em;' }, [ + reverseLabel, timeLabel + ]), + E('div', { 'style': 'padding:0 0.6em 0.6em;' }, [clearButton, downloadButton, message]) + ]); + + if (!enabled) + message.textContent = _('Please add log path in config to enable log'); + renderLog(); + + poll.add(function() { + return callRuntimeLog(offset).then(function(result) { + enabled = result.enabled; + if (!enabled) { + message.textContent = _('Please add log path in config to enable log'); + return; + } + message.textContent = ''; + if (result.offset < offset) + content = ''; + offset = result.offset; + if (result.content) { + content += result.content; + renderLog(); + } + }).catch(function() { + message.textContent = _('Status unavailable'); + }); + }, 3); + + return page; + } +}); \ No newline at end of file diff --git a/luci-app-adguardhome/root/www/luci-static/resources/view/adguardhome/manual.js b/luci-app-adguardhome/root/www/luci-static/resources/view/adguardhome/manual.js new file mode 100644 index 00000000..8e280f2d --- /dev/null +++ b/luci-app-adguardhome/root/www/luci-static/resources/view/adguardhome/manual.js @@ -0,0 +1,236 @@ +'use strict'; +'require view'; +'require rpc'; +'require view/adguardhome/status as status'; + +var callManualConfig = rpc.declare({ + object: 'luci.adguardhome', + method: 'getManualConfig', + expect: { '': {} }, + reject: true +}); + +var callSetManualConfig = rpc.declare({ + object: 'luci.adguardhome', + method: 'setManualConfig', + params: [ 'content' ], + expect: { '': {} }, + reject: true +}); + +var callChangeWebPassword = rpc.declare({ + object: 'luci.adguardhome', + method: 'changeWebPassword', + params: [ 'hash' ], + expect: { '': {} }, + reject: true +}); + +var codeMirrorLoader; +var bcryptLoader; + +function loadBcrypt() { + if (window.TwinBcrypt) + return Promise.resolve(window.TwinBcrypt); + + if (!bcryptLoader) { + bcryptLoader = new Promise(function(resolve, reject) { + var script = document.createElement('script'); + script.src = L.resource('twin-bcrypt.min.js'); + script.onload = function() { + if (window.TwinBcrypt) + resolve(window.TwinBcrypt); + else + reject(new Error('bcrypt unavailable')); + }; + script.onerror = reject; + document.head.appendChild(script); + }).catch(function(error) { + bcryptLoader = null; + throw error; + }); + } + + return bcryptLoader; +} + +function loadCodeMirror() { + if (window.CodeMirror) + return Promise.resolve(window.CodeMirror); + if (!codeMirrorLoader) { + codeMirrorLoader = new Promise(function(resolve, reject) { + var stylesheet = document.createElement('link'); + stylesheet.rel = 'stylesheet'; + stylesheet.href = L.resource('codemirror/lib/codemirror.css'); + document.head.appendChild(stylesheet); + var theme = document.createElement('link'); + theme.rel = 'stylesheet'; + theme.href = L.resource('codemirror/theme/dracula.css'); + document.head.appendChild(theme); + var script = document.createElement('script'); + script.src = L.resource('codemirror/lib/codemirror.js'); + script.onload = function() { + var yaml = document.createElement('script'); + yaml.src = L.resource('codemirror/mode/yaml/yaml.js'); + yaml.onload = function() { resolve(window.CodeMirror); }; + yaml.onerror = reject; + document.head.appendChild(yaml); + }; + script.onerror = reject; + document.head.appendChild(script); + }); + } + return codeMirrorLoader; +} + +return view.extend({ + handleSave: null, + handleSaveApply: null, + handleReset: null, + + load: function() { + return callManualConfig().catch(function() { + return { content: '', template: '', source: 'error' }; + }); + }, + + render: function(initial) { + var sharedStatus = status.render(); + var config = initial; + var showingTemplate = false; + var editor; + var message = E('span', { 'style': 'margin-left:0.8em;' }, ''); + var configArea = E('textarea', { + 'class': 'cbi-input-textarea', + 'rows': 40, + 'wrap': 'off', + 'style': 'width:100%;box-sizing:border-box;font-family:monospace;' + }); + var editorWrap = E('div', { 'style': 'padding:0.6em;' }, configArea); + var templateButton = E('button', { + 'class': 'btn cbi-button', 'type': 'button' + }, _('Use template')); + var reloadButton = E('button', { + 'class': 'btn cbi-button', 'type': 'button', 'style': 'margin-left:0.6em;' + }, _('Reload Config')); + var saveButton = E('button', { + 'class': 'btn cbi-button cbi-button-apply', 'type': 'button', 'style': 'margin-left:0.6em;' + }, _('Save & Apply')); + var passwordInput = E('input', { + 'class': 'cbi-input-password', 'type': 'password', + 'autocomplete': 'new-password', 'spellcheck': 'false' + }); + var passwordButton = E('button', { + 'class': 'btn cbi-button cbi-button-apply', 'type': 'button' + }, _('Change password')); + var passwordMessage = E('span', { 'style': 'margin-left:0.8em;' }, ''); + var passwordPanel = E('div', { 'class': 'cbi-section' }, [ + E('h3', {}, _('Change browser management password')), + E('div', { 'class': 'cbi-value', 'style': 'padding:0.6em 0;' }, [ + E('label', { 'class': 'cbi-value-title' }, _('New password')), + E('div', { 'class': 'cbi-value-field' }, [ + passwordInput, + E('span', { 'style': 'margin-left:0.6em;' }, passwordButton), + passwordMessage + ]) + ]) + ]); + + function showConfig() { + var content = showingTemplate ? config.template : config.content; + if (editor) + editor.setValue(content); + else + configArea.value = content; + templateButton.disabled = !config.template; + } + + templateButton.addEventListener('click', function() { + showingTemplate = true; + message.textContent = ''; + showConfig(); + }); + reloadButton.addEventListener('click', function() { + reloadButton.disabled = true; + message.textContent = ''; + callManualConfig().then(function(result) { + config = result; + showingTemplate = false; + showConfig(); + }).catch(function() { + message.textContent = _('Unable to read config'); + }).then(function() { + reloadButton.disabled = false; + }); + }); + saveButton.addEventListener('click', function() { + var content = editor ? editor.getValue() : configArea.value; + saveButton.disabled = true; + message.textContent = _('Saving...'); + callSetManualConfig(content).then(function(result) { + if (!result.saved) + throw new Error(result.log || result.reason || 'save failed'); + config.content = content; + config.source = 'current'; + showingTemplate = false; + message.textContent = _('Saved'); + }).catch(function(error) { + message.textContent = error.message || _('Save failed'); + }).then(function() { + saveButton.disabled = false; + }); + }); + passwordButton.addEventListener('click', function() { + var password = passwordInput.value; + if (!password) { + passwordMessage.textContent = _('New password') + ' ' + _('is empty'); + return; + } + + passwordButton.disabled = true; + passwordMessage.textContent = _('Changing password...'); + loadBcrypt().then(function(bcrypt) { + var hash = bcrypt.hashSync(password); + password = null; + passwordInput.value = ''; + return callChangeWebPassword(hash); + }).then(function(result) { + if (!result.changed) + throw new Error(result.reason || 'change failed'); + passwordMessage.textContent = _('Password changed'); + }).catch(function() { + passwordMessage.textContent = _('Password change failed'); + }).then(function() { + passwordButton.disabled = false; + }); + }); + showConfig(); + + var page = E('div', {}, [ + sharedStatus.node, + E('div', { 'class': 'cbi-section' }, [ + E('div', { 'style': 'padding:0.6em;' }, [ + templateButton, reloadButton, saveButton, message + ]), + editorWrap + ]), + passwordPanel + ]); + loadCodeMirror().then(function(CodeMirror) { + editor = CodeMirror.fromTextArea(configArea, { + mode: 'text/yaml', + lineNumbers: true, + theme: 'dracula', + lineWrapping: true, + matchBrackets: true + }); + editor.getWrapperElement().style.fontSize = '15px'; + editor.getWrapperElement().style.lineHeight = '1.5'; + editor.refresh(); + showConfig(); + }).catch(function() { + message.textContent = _('Unable to load editor'); + }); + return page; + } +}); \ No newline at end of file diff --git a/luci-app-adguardhome/root/www/luci-static/resources/view/adguardhome/status.js b/luci-app-adguardhome/root/www/luci-static/resources/view/adguardhome/status.js new file mode 100644 index 00000000..5e59b009 --- /dev/null +++ b/luci-app-adguardhome/root/www/luci-static/resources/view/adguardhome/status.js @@ -0,0 +1,101 @@ +'use strict'; +'require baseclass'; +'require poll'; +'require rpc'; +'require fs'; +'require uci'; + +var callCoreInfo = rpc.declare({ + object: 'luci.adguardhome', + method: 'getCoreInfo', + expect: { '': {} }, + reject: true +}); + +var callServiceList = rpc.declare({ + object: 'service', + method: 'list', + params: [ 'name', 'verbose' ], + expect: { '': {} }, + reject: true +}); + +var loadConfigPromise; + +function getConfiguredBinPath() { + if (!loadConfigPromise) + loadConfigPromise = uci.load('AdGuardHome').catch(function() { return null; }); + + return loadConfigPromise.then(function() { + return uci.get('AdGuardHome', 'AdGuardHome', 'binpath') || '/usr/bin/AdGuardHome/AdGuardHome'; + }); +} + +function coreIsRunning(services, binpath) { + var instances = services.AdGuardHome && services.AdGuardHome.instances; + if (!instances) + return false; + + return Object.keys(instances).some(function(name) { + var instance = instances[name]; + return instance.running === true && Array.isArray(instance.command) && instance.command[0] === binpath; + }); +} + +return baseclass.extend({ + render: function() { + var badgeStyle = 'display:inline-flex;align-items:center;padding:0.4em 0.8em;' + + 'border:1px solid;border-radius:999px;font-weight:600;line-height:1.4;'; + var coreBadge = E('span', { 'style': badgeStyle }, _('Collecting data...')); + var serviceBadge = E('span', { 'style': badgeStyle }, _('Collecting data...')); + var redirectBadge = E('span', { 'style': badgeStyle }, _('Collecting data...')); + var node = E('div', { 'class': 'cbi-section' }, [ + E('div', { 'style': 'display:flex;flex-wrap:wrap;gap:0.6em;padding:0.6em;' }, [ + coreBadge, serviceBadge, redirectBadge + ]) + ]); + + function setBadge(badge, label, state) { + badge.textContent = label; + badge.style.backgroundColor = state === true ? '#dcfce7' : state === false ? '#fee2e2' : '#e5e7eb'; + badge.style.color = state === true ? '#14532d' : state === false ? '#7f1d1d' : '#374151'; + badge.style.borderColor = state === true ? '#86efac' : state === false ? '#fca5a5' : '#d1d5db'; + } + + function refreshCore() { + return callCoreInfo().then(function(info) { + if (!info.core_exists) + setBadge(coreBadge, _('Core') + ': ' + _('no core'), false); + else if (!info.version) + setBadge(coreBadge, _('Core') + ': ' + _('core error'), false); + else if (!info.config_exists) + setBadge(coreBadge, _('Core') + ': ' + info.version + ' (' + _('no config') + ')', false); + else + setBadge(coreBadge, _('Core') + ': ' + info.version, true); + }).catch(function() { + setBadge(coreBadge, _('Core') + ': ' + _('Status unavailable'), null); + }); + } + + refreshCore(); + poll.add(function() { + var redirectFlag = fs.read('/var/run/AdGredir').catch(function(error) { + if (error.name === 'NotFoundError') + return ''; + throw error; + }); + + return Promise.all([getConfiguredBinPath(), callServiceList('AdGuardHome', true), redirectFlag]).then(function(results) { + var isRunning = coreIsRunning(results[1], results[0]); + setBadge(serviceBadge, 'AdGuardHome: ' + (isRunning ? _('RUNNING') : _('NOT RUNNING')), isRunning); + setBadge(redirectBadge, _('Redirect') + ': ' + (results[2].trim() === '1' ? _('Redirected') : _('Not redirect')), + results[2].trim() === '1'); + }).catch(function() { + setBadge(serviceBadge, _('Status unavailable'), null); + setBadge(redirectBadge, _('Status unavailable'), null); + }); + }, 3); + + return { node: node, refresh: refreshCore }; + } +}); \ No newline at end of file diff --git a/luci-app-chatgpt-web/.github/workflows/main.yml b/luci-app-chatgpt-web/.github/workflows/main.yml deleted file mode 100644 index 3de4bc6d..00000000 --- a/luci-app-chatgpt-web/.github/workflows/main.yml +++ /dev/null @@ -1,49 +0,0 @@ -name: Build - -on: - push: - tags: - - v* - -jobs: - build: - name: Build ${{ matrix.arch }} - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - arch: - - - aarch64_generic - - i386_pentium-mmx - - loongarch64_generic - - mips_mips32 - - mipsel_24kc - - mipsel_mips32 - - x86_64 - - steps: - - uses: actions/checkout@main - with: - fetch-depth: 0 - - name: Building packages - uses: sbwml/openwrt-gh-action-sdk@go1.24 - env: - ARCH: ${{ matrix.arch }}-openwrt-24.10 - FEEDNAME: packages_ci - PACKAGES: luci-app-chatgpt-web - NO_REFRESH_CHECK: true - - - name: Upload artifacts - uses: actions/upload-artifact@v4 - with: - name: ${{ matrix.arch }} - path: bin/packages/${{ matrix.arch }}/packages_ci/*.ipk - - - name: Upload packages - uses: ncipollo/release-action@v1 - with: - token: ${{ secrets.GITHUB_TOKEN }} - allowUpdates: true - replacesArtifacts: true - artifacts: "bin/packages/${{ matrix.arch }}/packages_ci/*.ipk" diff --git a/luci-app-chatgpt-web/Makefile b/luci-app-chatgpt-web/Makefile deleted file mode 100644 index b5cae97b..00000000 --- a/luci-app-chatgpt-web/Makefile +++ /dev/null @@ -1,22 +0,0 @@ -# SPDX-License-Identifier: GPL-3.0-only -# -# Copyright (C) 2023-2025 sirpdboy https://github.com/sirpdboy/luci-app-chatgpt-web -# -# This is free software, licensed under the Apache License, Version 2.0 . -# -include $(TOPDIR)/rules.mk - -NAME:=chatgpt-web -PKG_NAME:=luci-app-$(NAME) -PKG_VERSION:=1.2.1 -PKG_RELEASE:=16 - -LUCI_TITLE:=LuCI Support for chatgpt-web Client -LUCI_PKGARCH:=all - -define Package/$(PKG_NAME)/conffiles -/etc/config/chatgpt-web -endef -include $(TOPDIR)/feeds/luci/luci.mk - -# call BuildPackage - OpenWrt buildroot signature diff --git a/luci-app-chatgpt-web/README.md b/luci-app-chatgpt-web/README.md deleted file mode 100644 index ab5de559..00000000 --- a/luci-app-chatgpt-web/README.md +++ /dev/null @@ -1,179 +0,0 @@ -## 访问数:![hello](https://views.whatilearened.today/views/github/sirpdboy/deplives.svg)[![](https://img.shields.io/badge/TG群-点击加入-FFFFFF.svg)](https://t.me/joinchat/AAAAAEpRF88NfOK5vBXGBQ) - - - -

-
luci-app-chatgpt-web
-

- -

-OpenWrt -Chrome -Safari -Firefox - GitHub release -Downloads -

- - -![screenshots](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/说明1.jpg) - -请 **认真阅读完毕** 本页面,本页面包含注意事项和如何使用。 - -## 功能说明 - -luci-app-chatgpt Pure ChatGPT by OPENWRT based on OpenAI API - -luci-app-chatgpt 在OPENWRT上实现ChatGPT项目,基于OpenAI API - - - -- [luci-app-chatgpt](#功能说明) - - [特性](#特性) - - [编译方法](#编译方法) - - [使用方法](#使用方法) - - [自定义选项](#自定义选项)。 - - [说明](#说明) - - [界面](#界面) - - [捐助](#捐助) - - - -## 特性 - -- 无须第三依赖,直接使用官方API接口。 -- 支持复制/更新/刷新会话,语音输入,朗读等功能,以及众多[自定义选项](#自定义选项)。 -- 支持搜索会话,导入导出会话和设置,自定义头像,自定义系统角色等。 - -参考项目: -[markdown-it](https://github.com/markdown-it/markdown-it), -[highlight.js](https://github.com/highlightjs/highlight.js), -[github-markdown-css](https://github.com/sindresorhus/github-markdown-css), -[chatgpt-html](https://github.com/slippersheepig/chatgpt-html), -[markdown-it-copy](https://github.com/ReAlign/markdown-it-copy), -[markdown-it-texmath](https://github.com/goessner/markdown-it-texmath), -[awesome-chatgpt-prompts-zh](https://github.com/PlexPt/awesome-chatgpt-prompts-zh) - - -## 编译方法 - -- 将luci-app-chatgpt添加至 LEDE/OpenWRT 源码的方法。 - -### 下载源码方法: - - ```Brach - - # 下载源码 - - git clone https://github.com/sirpdboy/chatgpt-web.git package/luci-app-chatgpt - make menuconfig - - ``` -### 配置菜单 - - ```Brach - make menuconfig - # 找到 LuCI -> Applications, 选择 luci-app-chatgpt, 保存后退出。 - ``` - -### 编译 - - ```Brach - # 编译固件 - make package/luci-app-chatgpt/compile V=s - ``` - -## 使用方法 - - - 打开luci-app-chatgpt基本设置,填写API密钥、GPT模型、填写OpenAI接口、选择用户图像等。 - - - 可选GPT模型,默认gpt-3.5,当前使用gpt-4模型需通过openai的表单申请。 - - - OpenAI接口当本地可正常访问`api.openai.com`,填写`https://api.openai.com/` - - - 不可正常访问`api.openai.com`,填写其反代地址,注意:反代接口响应需添加跨域Header `Access-Control-Allow-Origin` - - - chatgpt-web页面就可以正常使用了,如果需要设置更多请看 自定义设置设置语音和系统角色等。 - -## 自定义选项 - -- 左边栏支持,清除对话,搜索会话,新建/重命名/删除(会话/文件夹)导出/导入/重置会话和设置数据,显示本地存储。 - -- 可选系统角色,默认不开启,有四个预设角色,后期会加入更多角色。 - -- 可选角色性格,默认灵活创新,对应接口文档的top_p参数。 - -- 可选回答质量,默认平衡,对应接口文档的temperature参数。 - -- 修改打字机速度,默认较快,值越大速度越快。 - -- 允许连续对话,默认开启,对话中包含上下文信息,会导致api费用增加。 - -- 允许长回复,默认关闭,**开启后可能导致api费用增加,并丢失大部分上下文,对于一些要发送`继续`才完整的回复,不用发`继续`了。** - -- 选择语音,默认Bing语音,支持Azure语音和系统语音,可分开设置提问语音和回答语音。 - -- 音量,默认最大。 - -- 语速,默认正常。 - -- 音调,默认正常。 - -- 允许连续朗读,默认开启,连续郎读到所有对话结束。 - -- 允许自动朗读,默认关闭,自动朗读新的回答。**(iOS需打开设置-自动播放视频预览,Mac上Safari需打开此网站的设置-允许全部自动播放)** - -- 支持语音输入,默认识别为普通话,可长按语音按钮修改识别选项。**语音识别必需条件:使用chrome内核系浏览器 + https网页或本地网页。** 如点击语音按钮没反应,可能是未授予麦克风权限或者没安装麦克风设备。 - - -## 说明 - --源码来源:https://github.com/sirpdboy/chatgpt-web --你可以随意使用其中的源码,但请注明出处。 - -![screenshots](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/说明2.jpg) - -## 界面 - -![screenshots](./doc/chatgpt1.png) - -![screenshots](./doc/chatgpt2.png) - -![screenshots](./doc/chatgpt3.png) - - -## 使用与授权相关说明 - -- 本人开源的所有源码,任何引用需注明本处出处,如需修改二次发布必告之本人,未经许可不得做于任何商用用途。 - -# My other project - -- 路由安全看门狗 :https://github.com/sirpdboy/luci-app-watchdog -- 网络速度测试 :https://github.com/sirpdboy/luci-app-netspeedtest -- 计划任务插件(原定时设置) : https://github.com/sirpdboy/luci-app-taskplan -- 关机功能插件 : https://github.com/sirpdboy/luci-app-poweroffdevice -- opentopd主题 : https://github.com/sirpdboy/luci-theme-opentopd -- kucat酷猫主题: https://github.com/sirpdboy/luci-theme-kucat -- kucat酷猫主题设置工具: https://github.com/sirpdboy/luci-app-kucat-config -- NFT版上网时间控制插件: https://github.com/sirpdboy/luci-app-timecontrol -- 家长控制: https://github.com/sirpdboy/luci-theme-parentcontrol -- 定时限速: https://github.com/sirpdboy/luci-app-eqosplus -- 系统高级设置 : https://github.com/sirpdboy/luci-app-advanced -- ddns-go动态域名: https://github.com/sirpdboy/luci-app-ddns-go -- 进阶设置(系统高级设置+主题设置kucat/agron/opentopd): https://github.com/sirpdboy/luci-app-advancedplus -- 网络设置向导: https://github.com/sirpdboy/luci-app-netwizard -- 一键分区扩容: https://github.com/sirpdboy/luci-app-partexp -- lukcy大吉: https://github.com/sirpdboy/luci-app-lukcy - -## 捐助 - -![screenshots](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/说明3.jpg) - -| 图飞了😂 | 图飞了😂 | -| :-----------------: | :-------------: | -|![xm1](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/支付宝.png) | ![xm1](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/微信.png) | - - - 图飞了😂 - - diff --git a/luci-app-chatgpt-web/doc/chatgpt1.png b/luci-app-chatgpt-web/doc/chatgpt1.png deleted file mode 100644 index f715b006..00000000 Binary files a/luci-app-chatgpt-web/doc/chatgpt1.png and /dev/null differ diff --git a/luci-app-chatgpt-web/doc/chatgpt2.png b/luci-app-chatgpt-web/doc/chatgpt2.png deleted file mode 100644 index 7de824a2..00000000 Binary files a/luci-app-chatgpt-web/doc/chatgpt2.png and /dev/null differ diff --git a/luci-app-chatgpt-web/doc/chatgpt3.png b/luci-app-chatgpt-web/doc/chatgpt3.png deleted file mode 100644 index 9d280941..00000000 Binary files a/luci-app-chatgpt-web/doc/chatgpt3.png and /dev/null differ diff --git a/luci-app-chatgpt-web/luasrc/controller/chatgpt-web.lua b/luci-app-chatgpt-web/luasrc/controller/chatgpt-web.lua deleted file mode 100644 index c100132c..00000000 --- a/luci-app-chatgpt-web/luasrc/controller/chatgpt-web.lua +++ /dev/null @@ -1,17 +0,0 @@ --- Copyright (C) 2023-2025 sirpdboy https://github.com/sirpdboy/luci-app-chatgpt-web --- Licensed to the public under the Apache License 2.0. - -module("luci.controller.chatgpt-web", package.seeall) - -function index() - if not nixio.fs.access("/etc/config/chatgpt-web") then - return - end - - local e = entry({"admin", "services", "chatgpt-web"}, alias("admin", "services", "chatgpt-web", "setting"),_("Chatgpt Web"), 20) - e.dependent=false - e.acl_depends = { "luci-app-chatgpt-web" } - entry({"admin", "services", "chatgpt-web", "setting"}, cbi("chatgpt-web"), _("Base Setting"), 10).leaf=true - entry({"admin", "services", "chatgpt-web", "chatgpt-web"}, cbi("chatgpt",{hideapplybtn=true, hidesavebtn=true, hideresetbtn=true}), _("Chatgpt Web"), 30).leaf=true -end - diff --git a/luci-app-chatgpt-web/luasrc/model/cbi/chatgpt-web.lua b/luci-app-chatgpt-web/luasrc/model/cbi/chatgpt-web.lua deleted file mode 100644 index 38f5e7da..00000000 --- a/luci-app-chatgpt-web/luasrc/model/cbi/chatgpt-web.lua +++ /dev/null @@ -1,36 +0,0 @@ --- Copyright (C) 2023-2025 sirpdboy https://github.com/sirpdboy/https://github.com/sirpdboy/luci-app-chatgpt-web - -local m, s ,o - -m = Map("chatgpt-web") -m.title = translate("Chatgpt Web") -m.description = translate("ChatGPT-Web is the version used on OPENWRT of OPENAI")..translate("
For specific usage, see:")..translate("GitHub @sirpdboy/chatgpt-web\ ") - -s = m:section(TypedSection, "basic", translate("Base Setting")) -s.addremove = false -s.anonymous = true - - -o = s:option(Value,"userpic",translate("Character images")) -o:value("girl.jpg",translate("girl")) -o:value("girl2.jpg",translate("girl2")) -o:value("girl3.jpg",translate("girl3")) -o:value("boy.jpg",translate("boy")) -o:value("boy2.jpg",translate("boy2")) -o:value("boy3.jpg",translate("boy3")) -o:value("dog.jpg",translate("dog")) -o:value("cartoon.jpg",translate("cartoon")) -o:value("cartoonboy.jpg",translate("cartoonboy")) -o:value("cartoonboy2.jpg",translate("cartoonboy2")) -o:value("cartoongirl.jpg",translate("cartoongirl")) -o:value("cartoongirl2.jpg",translate("cartoongirl2")) -o.rmempty = true -o.default="girl.jpg" - - -o=s:option(TextValue, "systemrole" ,translate("Default Role Settings")) -o.rows = 5 -o.rmempty=false - - -return m diff --git a/luci-app-chatgpt-web/luasrc/model/cbi/chatgpt.lua b/luci-app-chatgpt-web/luasrc/model/cbi/chatgpt.lua deleted file mode 100644 index 162de6d0..00000000 --- a/luci-app-chatgpt-web/luasrc/model/cbi/chatgpt.lua +++ /dev/null @@ -1,9 +0,0 @@ --- Copyright (C) 2023-2025 sirpdboy https://github.com/sirpdboy/luci-app-chatgpt-web -require("luci.util") -local m, s - -m = Map("chatgpt-web",translate("")) - -m:section(SimpleSection).template = "chatgpt-web" - -return m diff --git a/luci-app-chatgpt-web/luasrc/view/chatgpt-web.htm b/luci-app-chatgpt-web/luasrc/view/chatgpt-web.htm deleted file mode 100644 index 078e5ac7..00000000 --- a/luci-app-chatgpt-web/luasrc/view/chatgpt-web.htm +++ /dev/null @@ -1,6845 +0,0 @@ -<%# - Copyright 2023-2025 sirpdboy Wich https://github.com/sirpdboy/luci-app-chatgpt-web - Licensed to the public under the Apache License 2.0. --%> -<% -local fs = require "nixio.fs" -local dsp = require "luci.dispatcher" -local uci = luci.model.uci.cursor() -local apikeys = uci:get_first('chatgpt-web', 'basic', 'apikey') -local userpic = "/luci-static/chatgpt-web/user/" .. uci:get_first('chatgpt-web', 'basic', 'userpic') -local sysrole = uci:get_first('chatgpt-web', 'basic', 'systemrole') - --%> - -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
-
ChatGPT
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
- - 4o - - -
-
-
- - -
- - -
-
-
-
- -
-
-
-
- - -
- - -
-
-
-
-
-
- - - -
-
-
-
-
- - - -
-
- - - -
-
-
-
-
-
ChatGPT
-
DeepSeek
-
Azure
-
Gemini
-
Claude
-
-
- -
-
-
-
ChatGPT
-
- -
-
-
-
-
- -
-
-
-
-
- -
-
-
- - - - -
- - - -
-
-
-
- - - - - - - - - - - - - - - - -
\ No newline at end of file diff --git a/luci-app-chatgpt-web/po/zh-cn/chatgpt-web.po b/luci-app-chatgpt-web/po/zh-cn/chatgpt-web.po deleted file mode 100644 index 91654dbd..00000000 --- a/luci-app-chatgpt-web/po/zh-cn/chatgpt-web.po +++ /dev/null @@ -1,71 +0,0 @@ -msgid "" -msgstr "Content-Type: text/plain; charset=UTF-8" - -msgid "chatgpt-web" -msgstr "ChatGPT-Web" - -msgid "ChatGPT-Web is the version used on OPENWRT of OPENAI" -msgstr "ChatGPT-Web是OPENAI的OPENWRT上使用的WEB版本,不需要第三方依赖,绿色安全直接使用OPENAI的API接口" - -msgid "Base Setting" -msgstr "基本设置" - -msgid "GPT Model" -msgstr "GPT模型" - -msgid "Character images" -msgstr "人物图像" - -msgid "boy" -msgstr "男生" - -msgid "boy2" -msgstr "男生2" - -msgid "boy3" -msgstr "男生3" - -msgid "girl" -msgstr "女生" - -msgid "girl2" -msgstr "女生2" - -msgid "girl3" -msgstr "女生3" - -msgid "dog" -msgstr "狗" - -msgid "cartoon" -msgstr "卡通图像" - -msgid "cartoonboy" -msgstr "卡通男孩" - -msgid "cartoonboy2" -msgstr "卡通小男孩" - -msgid "cartoongirl" -msgstr "卡通女生" - -msgid "cartoongirl2" -msgstr "卡通女生2" - -msgid "OpenAI interface" -msgstr "OpenAI接口" - -msgid "API Key" -msgstr "API密钥" - -msgid "Enter your own API key, and if you do not have one, enter the official website API to generate it:" -msgstr "输入自己的API密钥(sk-开头),没有的进官网API生成:" - -msgid "Click to open" -msgstr "点击前往" - -msgid "Default Role Settings" -msgstr "默认角色设置" - -msgid "
For specific usage, see:" -msgstr "
具体使用方法参见:" diff --git a/luci-app-chatgpt-web/po/zh_Hans/chatgpt-web.po b/luci-app-chatgpt-web/po/zh_Hans/chatgpt-web.po deleted file mode 100644 index 91654dbd..00000000 --- a/luci-app-chatgpt-web/po/zh_Hans/chatgpt-web.po +++ /dev/null @@ -1,71 +0,0 @@ -msgid "" -msgstr "Content-Type: text/plain; charset=UTF-8" - -msgid "chatgpt-web" -msgstr "ChatGPT-Web" - -msgid "ChatGPT-Web is the version used on OPENWRT of OPENAI" -msgstr "ChatGPT-Web是OPENAI的OPENWRT上使用的WEB版本,不需要第三方依赖,绿色安全直接使用OPENAI的API接口" - -msgid "Base Setting" -msgstr "基本设置" - -msgid "GPT Model" -msgstr "GPT模型" - -msgid "Character images" -msgstr "人物图像" - -msgid "boy" -msgstr "男生" - -msgid "boy2" -msgstr "男生2" - -msgid "boy3" -msgstr "男生3" - -msgid "girl" -msgstr "女生" - -msgid "girl2" -msgstr "女生2" - -msgid "girl3" -msgstr "女生3" - -msgid "dog" -msgstr "狗" - -msgid "cartoon" -msgstr "卡通图像" - -msgid "cartoonboy" -msgstr "卡通男孩" - -msgid "cartoonboy2" -msgstr "卡通小男孩" - -msgid "cartoongirl" -msgstr "卡通女生" - -msgid "cartoongirl2" -msgstr "卡通女生2" - -msgid "OpenAI interface" -msgstr "OpenAI接口" - -msgid "API Key" -msgstr "API密钥" - -msgid "Enter your own API key, and if you do not have one, enter the official website API to generate it:" -msgstr "输入自己的API密钥(sk-开头),没有的进官网API生成:" - -msgid "Click to open" -msgstr "点击前往" - -msgid "Default Role Settings" -msgstr "默认角色设置" - -msgid "
For specific usage, see:" -msgstr "
具体使用方法参见:" diff --git a/luci-app-chatgpt-web/root/etc/config/chatgpt-web b/luci-app-chatgpt-web/root/etc/config/chatgpt-web deleted file mode 100644 index a7a1636a..00000000 --- a/luci-app-chatgpt-web/root/etc/config/chatgpt-web +++ /dev/null @@ -1,8 +0,0 @@ - -config basic - option apiHost 'https://api.openai.com/' - option modelver 'gpt-3.5-turbo' - option userpic 'girl.jpg' - option apikey '' - option systemrole '你是一个乐于助人的专业助手,尽量简明扼要地回答' - diff --git a/luci-app-chatgpt-web/root/usr/share/rpcd/acl.d/luci-app-chatgpt-web.json b/luci-app-chatgpt-web/root/usr/share/rpcd/acl.d/luci-app-chatgpt-web.json deleted file mode 100644 index c8c06f50..00000000 --- a/luci-app-chatgpt-web/root/usr/share/rpcd/acl.d/luci-app-chatgpt-web.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "luci-app-chatgpt-web": { - "description": "Grant UCI access for luci-app-chatgpt-web", - "read": { - "uci": [ "chatgpt-web" ] - }, - "write": { - "uci": [ "chatgpt-web" ] - } - } -} diff --git a/luci-app-chatgpt-web/root/www/luci-static/chatgpt-web/11.11.1github.min.css b/luci-app-chatgpt-web/root/www/luci-static/chatgpt-web/11.11.1github.min.css deleted file mode 100644 index 275239a7..00000000 --- a/luci-app-chatgpt-web/root/www/luci-static/chatgpt-web/11.11.1github.min.css +++ /dev/null @@ -1,10 +0,0 @@ -pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*! - Theme: GitHub - Description: Light theme as seen on github.com - Author: github.com - Maintainer: @Hirse - Updated: 2021-05-15 - - Outdated base version: https://github.com/primer/github-syntax-light - Current colors taken from GitHub's CSS -*/.hljs{color:#24292e;background:#fff}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#d73a49}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#6f42c1}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable{color:#005cc5}.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#032f62}.hljs-built_in,.hljs-symbol{color:#e36209}.hljs-code,.hljs-comment,.hljs-formula{color:#6a737d}.hljs-name,.hljs-quote,.hljs-selector-pseudo,.hljs-selector-tag{color:#22863a}.hljs-subst{color:#24292e}.hljs-section{color:#005cc5;font-weight:700}.hljs-bullet{color:#735c0f}.hljs-emphasis{color:#24292e;font-style:italic}.hljs-strong{color:#24292e;font-weight:700}.hljs-addition{color:#22863a;background-color:#f0fff4}.hljs-deletion{color:#b31d28;background-color:#ffeef0} \ No newline at end of file diff --git a/luci-app-chatgpt-web/root/www/luci-static/chatgpt-web/chatgpt-web.css b/luci-app-chatgpt-web/root/www/luci-static/chatgpt-web/chatgpt-web.css deleted file mode 100644 index 7a24e376..00000000 --- a/luci-app-chatgpt-web/root/www/luci-static/chatgpt-web/chatgpt-web.css +++ /dev/null @@ -1,2166 +0,0 @@ - - .chat_window { - display: flex; - width: 100%; - max-width: 1920px; - height: 888px; - border-radius: 8px; - background-color: rgba(255,255,255,0.5); - overflow: auto; - } - - .chat_window>.overlay { - width: 100%; - height: 100%; - position: absolute; - left: 0; - top: 0; - background-color: rgba(0, 0, 0, .3); - z-index: 90; - opacity: 0; - visibility: hidden; - cursor: pointer; - transition: all 120ms ease-in-out; - } - - .chat_window .mainContent .cbi-input-invalid { - color: var(--title-color); - border-bottom-color: var(--title-color); - } - - - @media screen and (min-width: 800px) { - - .mainContent { - width: calc(100% - 250px); - } - - .chat_window>.nav { - position: relative; - margin-left: -250px; - transition: margin-left 250ms; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - } - - .show-nav .nav { - margin-left: 0; - } - - #sysDialog { - max-width: 600px; - } - - .sysContent { - display: flex; - } - - .sysSwitch { - flex-shrink: 0; - width: 160px; - } - - .sysSwitch>div { - padding-left: 6px; - } - - .sysDetail { - margin-left: 12px; - margin-top: 5px; - } - } - - @media screen and (max-width: 800px) { - - .mainContent { - width: 100%; - } - - .chat_window>.nav { - position: absolute !important; - left: -250px; - transition: left 250ms; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - } - - .show-nav .nav { - left: 0; - box-shadow: rgba(0, 0, 0, 0.2) 0px 8px 10px -5px, rgba(0, 0, 0, 0.14) 0px 16px 24px 2px, rgba(0, 0, 0, 0.12) 0px 6px 30px 5px; - } - - .show-nav .overlay { - visibility: visible; - opacity: 1; - } - - #sysDialog { - max-width: 400px; - } - - .sysSwitch { - display: flex; - } - - .sysSwitch>div { - width: 50%; - justify-content: center; - } - - .sysDetail { - margin-top: 8px; - } - } - .full_window { - max-width: none; - max-height: none; - } - - .chat_window>.nav { - width: 250px; - background-color: rgba(var(--primary-rgbbody),1); - color: var(--body-color); - top: 0; - z-index: 99; - flex-shrink: 0; - display: flex; - flex-direction: column; - margin-left: -250px; - transition: left 120ms ease-in-out; - } - - .mainContent { - height: 100%; - position: relative; - width: 100%; - display: flex; - flex-direction: column; - flex: 1; - } - - .show-nav .nav { - margin-left: 0; - } - .show-nav .chatnav { - margin-left: 0; - box-shadow: rgba(0, 0, 0, 0.2) 0px 8px 10px -5px, rgba(0, 0, 0, 0.14) 0px 16px 24px 2px, rgba(0, 0, 0, 0.12) 0px 6px 30px 5px; - } - - .show-nav .overlay { - opacity: 1; - visibility: visible; - } - .top_menu { - width: 100%; - background-color: rgba(255,255,255,0.5); - height: 50px; - padding: 5px 0; - } - - .top_menu .toggler { - margin-left: 10px; - width: 40px; - height: 40px; - float: left; - padding: 5px 7px; - border-radius: 4px; - cursor: pointer; - -webkit-tap-highlight-color: transparent; - } - - .top_menu .toggler:hover { - background: #e0e0e0; - } - - .top_menu .toggler .button { - width: 26px; - height: 4px; - border-radius: 4px; - position: absolute; - pointer-events: none; - } - - .top_menu .toggler .button.close { - margin-top: 3px; - background-color: #000; - } - - .top_menu .toggler .button.minimize { - margin-top: 13px; - background-color: #000; - } - - .top_menu .toggler .button.maximize { - margin-top: 23px; - background-color: #000; - } - - .top_menu .title { - font-size: 20px; - height: 40px; - line-height: 40px; - position: relative; - pointer-events: none; - } - - #selector { - position: absolute; - top: 50%; - left: 50%; - padding: 0 4px 0 10px; - cursor: pointer; - font-size: 20px; - transform: translate(-50%, -50%); - pointer-events: auto; - border-radius: 4px; - -webkit-user-select: none; - user-select: none; - display: flex; - align-items: center; - } - - #selector:hover { - color: var(--title-color); - } - - #modelVer { - margin-left: 6px; - margin-right: 4px; - height: 40px; - line-height: 38px; - } - - .messages { - position: relative; - flex: 1; - overflow-x: hidden; - overflow-y: auto; - font-size: 16px; - width: 100%; - text-align: center; - } - - .messages::-webkit-scrollbar, - - #chatlog .markdown-body>pre::-webkit-scrollbar, - #chatlog .markdown-body>pre>code::-webkit-scrollbar, - #chatlog .responseBody pre>code::-webkit-scrollbar, - #setDialog::-webkit-scrollbar, - .allList::-webkit-scrollbar, - .sysDetail::-webkit-scrollbar, - #apiSelect::-webkit-scrollbar { - width: 10px; - height: 10px; - } - - .messages::-webkit-scrollbar-track, - - #chatlog .markdown-body>pre::-webkit-scrollbar-track, - #chatlog .markdown-body>pre>code::-webkit-scrollbar-track, - #chatlog .responseBody pre>code::-webkit-scrollbar-track, - #setDialog::-webkit-scrollbar-track, - .allList::-webkit-scrollbar-track, - .sysDetail::-webkit-scrollbar-track, - #apiSelect::-webkit-scrollbar-track { - background-clip: padding-box; - background: transparent; - border: none; - } - - .messages::-webkit-scrollbar-corner, - - #chatlog .markdown-body>pre::-webkit-scrollbar-corner, - #chatlog .markdown-body>pre>code::-webkit-scrollbar-corner, - #chatlog .responseBody pre>code::-webkit-scrollbar-corner, - #setDialog::-webkit-scrollbar-corner, - .allList::-webkit-scrollbar-corner, - .sysDetail::-webkit-scrollbar-corner, - #apiSelect::-webkit-scrollbar-corner { - background-color: transparent; - } - - .messages::-webkit-scrollbar-thumb, - - #chatlog .markdown-body>pre::-webkit-scrollbar-thumb, - #chatlog .markdown-body>pre>code::-webkit-scrollbar-thumb, - #chatlog .responseBody pre>code::-webkit-scrollbar-thumb, - #setDialog::-webkit-scrollbar-thumb, - .allList::-webkit-scrollbar-thumb, - .sysDetail::-webkit-scrollbar-thumb, - #apiSelect::-webkit-scrollbar-thumb { - background-color: rgba(0, 0, 0, 0.1); - background-clip: padding-box; - border: solid transparent; - border-radius: 10px; - } - - .messages::-webkit-scrollbar-thumb:hover, - - #chatlog .markdown-body>pre::-webkit-scrollbar-thumb:hover, - #chatlog .markdown-body>pre>code::-webkit-scrollbar-thumb:hover, - #chatlog .responseBody pre>code::-webkit-scrollbar-thumb:hover, - #setDialog::-webkit-scrollbar-thumb:hover, - .allList::-webkit-scrollbar-thumb:hover, - .sysDetail::-webkit-scrollbar-thumb:hover, - #apiSelect::-webkit-scrollbar-thumb:hover { - background-color: rgba(0, 0, 0, 0.4); - } - - #chatlog { - word-wrap: break-word; - text-align: start; - } - - #chatlog>div { - padding: 18px 40px 18px 54px; - } - .chatAvatar { - position: absolute; - width: 30px; - height: 30px; - border-radius: 2px; - } - - .request>.chatAvatar { - top: 18px; - left: 13px; - margin-top: 15px; - margin-bottom: 15px; - } - .response>.chatAvatar { - background: #10a37f; - top: 18px; - left: 13px; - margin-top: 15px; - margin-bottom: 15px; - } - - .gptAvatar>svg { - margin-top: 3px; - margin-left: 3px; - } - - .chatAvatar>img { - display: block; - width: 100%; - height: 100%; - border-radius: 2px; - } - - .modelAvatar>svg, - .chatAvatar>svg { - display: block; - margin-top: 4px; - margin-left: 4px; - } - - #chatlog .request { - position: relative; - background-color: var(--inputborder-color); - display: flex; - justify-content: center; - } - - - .response .responseBody { - display: flex; - justify-content: flex-end; - margin: 18px 45px 18px 20px; - font-size: 15px; - - } - .requestBody { - white-space: pre-wrap; - margin: 18px; - flex: 1; - } - - #chatlog .response { - background-color: var(--inputborder-color); - position: relative; - } - - .response .markdown-body { - margin: 18px 65px 18px 0px; - flex: 1; - font-size: 15px; - background-color: rgba(255,255,255,0)!important; - - } - - #chatlog .markdown-body a { - padding: 0 1px 0 2px; - } - - #chatlog .markdown-body pre { - overflow-x: auto; - padding: 10px; - position: relative; - white-space: pre-wrap; - background-color: var(--inputborder-color)!important; - - } - - #chatlog .markdown-body .mermaid { - margin-bottom: 8px; - padding: 0 10px 0 10px; - position: relative; - border-radius: 6px; - } - - .mermaid>svg { - cursor: pointer; - max-height: 600px; - display: block; - } - - #chatlog .markdown-body pre>code { - overflow-x: auto; - white-space: pre-wrap; - display: block; - } - - #chatlog .markdown-body ul { - list-style-type: disc; - } - - .m-mdic-copy-wrapper { - position: absolute; - top: 5px; - right: 16px; - -webkit-user-select: none; - user-select: none; - } - - .m-mdic-copy-wrapper span.u-mdic-copy-code_lang { - position: absolute; - top: 3px; - right: calc(100% + 4px); - font-family: system-ui; - font-size: 12px; - line-height: 18px; - color: #bbb; - } - - .m-mdic-copy-wrapper div.u-mdic-copy-notify { - position: absolute; - top: 0; - right: 0; - padding: 3px 6px; - border: 0; - border-radius: 3px; - background: none; - font-family: system-ui; - font-size: 12px; - line-height: 18px; - color: #555; - outline: none; - right: 100%; - padding-right: 4px; - } - - .m-mdic-copy-wrapper button.u-mdic-copy-btn { - position: relative; - top: 0; - right: 0; - padding: 3px 6px; - border: 0; - border-radius: 3px; - background: none; - font-family: system-ui; - font-size: 12px; - line-height: 18px; - color: #bbb; - outline: none; - cursor: pointer; - transition: color 0.2s; - } - - .m-mdic-copy-wrapper span.u-mdic-copy-code_lang::before, - .m-mdic-copy-wrapper div.u-mdic-copy-notify::before, - .m-mdic-copy-wrapper button.u-mdic-copy-btn::before { - content: attr(text); - } - - .m-mdic-copy-wrapper button.u-mdic-copy-btn:hover { - color: #555; - } - - #stopChat { - display: none; - margin: 0 auto; - margin-top: 3px; - width: 80px; - height: 32px; - text-align: center; - line-height: 32px; - color: white; - background: #f8b26a; - cursor: pointer; - border-radius: 3px; - position: sticky; - bottom: 2px; - justify-content: center; - align-items: center; - } - - #stopChat>svg { - margin-right: 8px; - } - - #stopChat:hover { - background: #f0aa60; - } - - .bottom_wrapper { - position: relative; - width: 100%; - padding: 10px 10px; - max-width: 91%; - margin: 0 auto; - } - - .bottom_wrapper .message_input_wrapper { - width: calc(100% - 134px); - position: relative; - text-align: left; - } - - .bottom_wrapper .message_input_wrapper .message_input_text { - border-radius: 4px; - border: none; - outline: none; - resize: none; - background-color: var(--inputborder-color); - height: 47px; - font-size: 16px; - max-height: 200px; - padding: 13px 0 13px 13px; - width: 100%!important; - display: block; - transition: background-color 250ms; - } - - .bottom_wrapper .message_input_wrapper .message_input_text:focus { - background-color: var(--inputborder-color); - } - - .bottom_wrapper .message_input_wrapper .message_input_text::-webkit-scrollbar { - display: none; - width: 0; - height: 0; - } - - #sendbutton { - width: 70px; - height: 47px; - font-size: 18px; - font-weight: bold; - color: #fff; - background-color: #7ac516; - padding: 0.5rem 0.5rem; - transition: all 0.2s linear; - position: absolute; - right: 65px; - bottom: 10px; - cursor: not-allowed; - } - - .activeSendBtn { - background-color: #99c959 !important; - cursor: pointer !important; - } - - .activeSendBtn:hover { - background-color: #90c050 !important; - } - - .clearConv { - position: absolute; - right: 10px; - bottom: 10px; - width: 47px; - padding: 0.5rem 0.5rem; - height: 47px; - } - - .clearConv>svg { - margin: 0 auto; - top: 50%; - left: 50%; - } - - .clearConv:hover { - background-color: var(--body-hover-bgcolor); - } - - .clearConv svg:first-child { - display: none; - } - - .clearConv svg:nth-child(2) { - display: block; - } - - .closeConv { - background: #e0e0e0;; - } - - .closeConv:hover { - background: #e8e8e8; - } - - .closeConv svg:first-child { - display: block; - } - - .closeConv svg:nth-child(2) { - display: none; - } - - .loaded>span { - display: inline-block; - } - - .loaded>svg { - display: none; - } - - .loading { - background: rgba(0,0,0,0) !important; - } - - .loading>span { - display: none; - } - - .loading>svg { - display: block; - } - - .switch-slide { - display: inline-block; - vertical-align: middle; - } - - .switch-slide-label { - display: block; - width: 38px; - height: 18px; - background: #909090; - border-radius: 30px; - cursor: pointer; - position: relative; - -webkit-transition: 0.3s ease; - transition: 0.3s ease; - } - - .switch-slide-label:after { - content: ""; - display: block; - width: 16px; - height: 16px; - border-radius: 100%; - background: #fff; - box-shadow: 0 1px 1px rgba(0, 0, 0, .1); - position: absolute; - left: 1px; - top: 1px; - -webkit-transform: translateZ(0); - transform: translateZ(0); - -webkit-transition: all 250ms; - transition: all 250ms; - } - - .switch-slide input:checked+label { - background: #99c959; - -webkit-transition: all 250ms; - transition: all 250ms; - } - - .switch-slide input:checked+label:after { - left: 21px; - } - - .settings { - padding: 0px 7px; - margin-top: -40px; - float: right; - border-radius: 4px; - cursor: pointer; - top: 5px; - } - - - .setBtn { - margin-left: 2px; - cursor: pointer; - padding: 5px; - border: none; - border-radius: 4px; - } - - .setBtn>svg { - display: block; - color: #909090; - } - - .setBtn:hover { - background: #e0e0e0;; - } - - #setting { - right: 15px; - } - - #toggleFull { - right: 56px; - } - - #toggleLight *, - #toggleFull * { - pointer-events: none; - } - - .showSetting { - background-color: var(--inputborder-color); - } - - .showModels { - background-color: var(--inputborder-color); - } - - .showModels>svg:last-child { - transform: scaleY(-1); - } - - #modelDialog { - position: absolute; - z-index: 2; - background: rgba(var(--primary-rgbbody), 1); - width: 240px; - left: 50%; - transform: translateX(-50%); - top: 46px; - overflow-y: auto; - max-height: calc(100% - 55px); - -webkit-user-select: none; - user-select: none; - border-radius: 4px; - padding: 8px 5px 8px 5px; - box-shadow: 0 0 6px rgba(0, 0, 0, 0.15); - } - - .modelSingle { - height: 40px; - border-radius: 3px; - cursor: pointer; - line-height: 40px; - display: flex; - align-items: center; - position: relative; - } - - .modelSingle * { - pointer-events: none; - } - - .modelSingle:hover { - background-color: var(--body-hover-bgcolor); - } - - .activeModel::before { - content: ""; - position: absolute; - right: 15px; - top: 7px; - width: 8px; - height: 16px; - border-style: solid; - border-width: 0 4px 4px 0; - transform: rotate(45deg); - } - - .modelAvatar { - margin-left: 6px; - margin-right: 10px; - width: 30px; - flex-shrink: 0; - border-radius: 2px; - height: 30px; - } - - #setDialog { - position: absolute; - z-index: 2; - background-color: rgba(var(--primary-rgbbody),1); - width: 320px; - right: 6px; - top: 46px; - overflow-y: auto; - max-height: calc(100% - 55px); - -webkit-user-select: none; - user-select: none; - border-radius: 5px; - padding: 8px 12px 8px 12px; - box-shadow: 0 0 6px rgba(0, 0, 0, 0.15); - } - - #setDialog input { - width: 100%; - } - - #setDialog .inlineTitle { - display: inline-block; - width: 80px; - line-height: 16px; - vertical-align: middle; - } - - #convOption, - #speechOption, - #speechDetail, - #recOption { - margin-bottom: 6px; - } - - #convOption>div, - #speechOption>div, - #speechDetail>div, - #recOption div { - margin-top: 10px; - } - - #voiceRecSetting select, #speechDetail select { - color: var(--title-color); - - } - .inputTextClass { - outline: none; - border-radius: 2px; - margin-top: 3px; - height: 32px; - font-size: 15px; - padding-left: 6px; - border: none; - } - - .areaTextClass { - width: 100%; - height: 150px; - white-space: normal; - resize: none; - padding: 6px; - } - - input[type="range"] { - -webkit-appearance: none; - appearance: none; - display: block; - margin: 4px 0 3px 0; - height: 8px; - background: #909090; - border-radius: 5px; - background-image: linear-gradient(#99c959, #99c959); - background-size: 100% 100%; - background-repeat: no-repeat; - } - - input[type="range"]::-webkit-slider-thumb { - -webkit-appearance: none; - height: 15px; - width: 15px; - border-radius: 50%; - background: #99c959; - cursor: ew-resize; - } - - input[type=range]::-webkit-slider-runnable-track { - -webkit-appearance: none; - box-shadow: none; - border: none; - background: transparent; - } - - .justSetLine { - display: flex; - justify-content: space-between; - align-items: center; - } - - .justSetBtn { - height: 32px; - border-radius: 3px; - line-height: 32px; - background-color: var(--inputborder-color); - text-align: center; - padding: 0px 8px; - display: flex; - justify-content: center; - align-items: center; - cursor: pointer; - } - - .justSetBtn:hover { - background-color: #d0d0d0; - } - - .justSetBtn>svg { - margin-right: 3px; - } - - .readyTestVoice>div:not(:first-child) { - display: none; - } - - .pauseTestVoice>div:nth-child(1), - .pauseTestVoice>div:nth-child(3) { - display: none; - } - - .resumeTestVoice>div:nth-child(1), - .resumeTestVoice>div:nth-child(2) { - display: none; - } - - .presetSelect>div { - display: inline-block; - } - - .presetSelect select { - outline: none; - border-radius: 3px; - width: 120px; - border-color: rgba(0, 0, 0, .3); - } - - .selectDef { - display: flex; - justify-content: space-between; - font-size: 13px; - color: #909090; - } - - #preSetSpeech { - width: 100%; - outline: none; - height: 30px; - line-height: 30px; - font-size: 14px; - margin-top: 5px; - border-radius: 3px; - border-color: rgba(0, 0, 0, .3); - } - - .mdOption { - position: absolute; - right: 0px; - top: 0; - width: 50px; - height: 100%; - pointer-events: none; - } - - .mdOption>div { - pointer-events: auto; - cursor: pointer; - } - - .mdOption svg * { - pointer-events: none; - } - - .refreshReq svg:not(:first-child) { - display: none; - } - - .halfRefReq svg:not(:nth-child(2)) { - display: none; - } - - .optionItems { - position: absolute; - bottom: -5px; - display: flex; - justify-content: space-between; - visibility: visible; - z-index: 1; - color: #808080; - } - - .optionItems:hover { - visibility: visible; - } - - .request:hover .optionItems, - .request:hover .voiceCls, - .response:hover .optionItems, - .response:hover .voiceCls { - visibility: visible; - } - - .optionItem { - border-radius: 10px; - height: 24px; - width: 32px; - border: 1px solid #e0e0e0;; - background-color: #f7f7f8; ; - display: flex !important; - justify-content: center; - align-items: center; - } - - .optionItem * { - pointer-events: none; - } - - .optionItem:hover { - background: #e0e0e0;; - } - - .voiceCls { - position: relative; - height: 100%; - visibility: visible; - display: flex; - align-items: center; - } - - .voiceCls>svg { - color: #c0c0c0; - display: block; - margin-left: 5px; - position: relative; - pointer-events: none; - } - - .voiceCls:hover>svg { - color: #808080; - } - - .showVoiceCls, - .showVoiceCls .markdown-body { - background: #e0e0e0 !important; - } - - .showVoiceCls .voiceCls { - visibility: visible !important; - } - - .showEditReq { - position: sticky !important; - top: 0; - bottom: 0; - z-index: 1; - } - - .showEditReq, - .showEditReq .markdown-body { - background: #e0e0e0 !important; - } - - .readyVoice svg:not(:first-child) { - display: none; - } - - .pauseVoice svg:not(:nth-child(2)) { - display: none; - } - - .resumeVoice svg:not(:nth-child(3)) { - display: none; - } - - #voiceTypes>span { - border-radius: 3px; - margin-left: 4px; - cursor: pointer; - color: var(--title-color); - padding: 1px 5px; - } - - #voiceTypes>span:hover { - background-color: var(--body-hover-bgcolor); - } - - .selVoiceType { - background-color: var(--inputborder-color); - } - - .navHeader { - width: 100%; - padding: 5px 10px; - display: flex; - justify-content: space-between; - } - - #newChat { - text-align: center; - width: 80%; - height: 40px; - border-radius: 3px; - background: var(--inputborder-color); - display: flex; - align-items: center; - justify-content: center; - -webkit-user-select: none; - user-select: none; - cursor: pointer; - flex: 1; - } - - .navHeader>div:hover { - background: var(--body-hover-bgcolor)!important; - } - - #newChat>svg { - margin-right: 2px; - } - - #newFolder { - height: 40px; - width: 40px; - margin-left: 10px; - border-radius: 3px; - background: var(--inputborder-color); - cursor: pointer; - position: relative; - -webkit-user-select: none; - user-select: none; - } - - #newFolder>svg { - display: block; - margin: 8px auto; - } - - .extraChat { - padding: 2px 10px 6px 10px; - position: relative; - } - - #searchChat { - width: 100%; - height: 36px; - padding-left: 10px; - padding-right: 16px; - font-size: 16px; - outline: none; - border: none; - background: var(--inputborder-color); - border-radius: 3px; - } - - #searchChat:focus { - background: var(--inputborder-color); - } - - #searchChat:placeholder-shown+#clearSearch { - display: none; - } - - #clearSearch { - position: absolute; - right: 12px; - top: 8px; - cursor: pointer; - } - - #clearSearch:hover { - color: var(--title-color); - } - - #clearSearch>svg { - display: block; - } - - .seledSearch { - background: var(--inputborder-color)!important; - } - - #matchCaseSearch { - position: absolute; - right: 12px; - top: 8px; - cursor: pointer; - border-radius: 3px; - color: var(--inputborder-color); - } - - #matchCaseSearch:hover { - background: var(--body-hover-bgcolor); - } - - #matchCaseSearch>svg { - display: block; - } - - .navFooter { - padding-bottom: 8px; - background: var(--inputborder-color); - } - - .navFooter .divider { - width: 100%; - border-top: 1px solid var(--inputborder-color); - margin: 4px 0; - } - - .navFunc { - padding-top: 5px; - display: flex; - justify-content: space-evenly; - } - - .navFunc svg { - display: block; - } - - .navFunc>div, - .navFunc>label { - border-radius: 20px; - text-align: center; - padding: 8px 8px; - font-size: 14px; - cursor: pointer; - } - - .navFunc>div:hover, - .navFunc>label:hover { - background: var(--body-hover-bgcolor); - } - - .navFooter .links { - text-align: center; - -webkit-user-select: none; - user-select: none; - } - - .navFooter .links a { - text-decoration: none; - } - - - .allList { - width: 100%; - flex: 1; - overflow-y: auto; - } - - #chatList { - min-height: 50px; - } - - .dragingLi { - filter: brightness(90%); - } - - .dragingChat { - background: #e8e8e8; - } - - .expandFolder>.headLi>svg { - transform: rotate(90deg); - } - - .expandFolder>.chatsInFolder { - display: block; - } - - .chatsInFolder { - display: none; - margin-left: 22px; - padding-left: 2px; - border-left: 1px solid #909090; ; - } - - .headLi, - .chatLi { - cursor: pointer; - width: 100%; - height: 50px; - display: flex; - justify-content: space-between; - align-items: center; - position: relative; - } - - .headLi *, - .chatLi * { - pointer-events: none; - } - - .headLi>svg, - .chatLi>svg { - margin-left: 10px; - pointer-events: none; - } - - .folderOption svg, - .chatOption svg { - pointer-events: auto; - } - - .headLi svg *, - .chatLi svg * { - pointer-events: none; - } - - .headLi .folderInfo { - position: absolute; - left: 40px; - height: 40px; - max-width: calc(100% - 115px); - } - - .chatLi .chatInfo { - position: absolute; - left: 45px; - height: 40px; - max-width: calc(100% - 100px); - } - - .folderInfo *, - .chatInfo * { - -webkit-user-select: none; - user-select: none; - } - - .chatInfo span { - background: var(--inputborder-color)!important; - } - - .headLi .folderName, - .chatLi .chatName { - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - line-height: 20px; - height: 20px; - } - - .headLi .folderNum, - .chatLi .chatPre { - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - font-size: 12px; - line-height: 20px; - height: 20px; - } - - .headLi .folderOption, - .chatLi .chatOption { - visibility: hidden; - display: flex; - margin-right: 5px; - } - - .folderLi .chatLi>svg { - margin-left: 5px; - } - - .folderLi .chatLi .chatInfo { - left: 35px; - max-width: calc(100% - 85px); - } - - .folderLi .chatLi #activeChatEdit { - left: 32px; - width: calc(100% - 60px) - } - - .folderLi:hover { - background: var(--body-hover-bgcolor)!important; - } - - .chatLi:hover { - background: var(--body-hover-bgcolor)!important; - } - - .headLi:hover .folderOption, - .chatLi:hover .chatOption { - visibility: visible !important; - } - - .activeFolder, - .activeChatLi { - background: var(--inputborder-color)!important; - } - - .activeChatLi .chatOption { - visibility: visible !important; - } - - .folderOption>svg:hover, - .chatOption>svg:hover { - color: var(--title-color); - } - - #activeChatEdit { - position: absolute; - left: 42px; - font-size: 16px; - border-radius: 2px; - color: var(--title-color); - background-color: rgba(var(--primary-rgbbody),1); - outline: none; - border: none; - pointer-events: auto; - height: 24px; - line-height: 24px; - width: calc(100% - 75px); - padding: 20px 3px; - z-index: 1; - } - - #loadMask { - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - z-index: 100; - background-color: #edeff2; - } - - #loadMask>div { - position: absolute; - left: 50%; - top: 50%; - transform: translate(-50%, -50%); - text-align: center; - } - - @keyframes loading { - - 0%, - 100% { - transform: scale(0); - } - - 50% { - transform: scale(1); - } - } - - .loadingCSSIcon { - position: relative; - display: flex; - align-items: center; - justify-content: space-evenly; - } - - .loadingCSSIcon div { - width: 15%; - height: 0; - padding-bottom: 15%; - border-radius: 50%; - animation: loading 1s cubic-bezier(0.3, 0, 0.7, 1) infinite; - } - - .loadingCSSIcon div:nth-child(1) { - background: #e15b64; - animation-delay: -0.4s - } - - .loadingCSSIcon div:nth-child(2) { - background: #f8b26a; - animation-delay: -0.2s - } - - .loadingCSSIcon div:nth-child(3) { - background: #99c959; - animation-delay: 0s; - } - - #loadMask>div>:first-child { - font-size: 40px; - color: #909090; ; - } - - #loadMask>div>:last-child { - width: 140px; - height: 70px; - margin: 0 auto; - } - - #voiceRec { - position: absolute; - right: 0; - top: 0; - width: 47px; - height: 100%; - } - - .message_if_voice { - padding-right: 47px !important; - } - - #voiceRecIcon { - width: 100%; - height: 100%; - text-align: center; - cursor: pointer; - position: relative; - } - - #voiceRecIcon:hover>svg { - color: #808080; - } - - #voiceRecIcon>svg { - width: 38px; - height: 38px; - color: #b0b0b0; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - } - - #voiceRecIcon>svg .animVoice { - display: none; - } - - .voiceRecing>svg { - color: #99c959 !important; - } - - .voiceRecing .animVoice { - display: inline !important; - transform-origin: 0 64%; - animation-duration: 1.5s; - animation-name: scaleVoice; - animation-timing-function: ease; - animation-iteration-count: infinite; - } - - .voiceLong .animVoice { - display: inline !important; - transform-origin: 0 64%; - animation-duration: 0.3s; - animation-name: longVoice; - animation-timing-function: ease-in-out; - animation-iteration-count: 1; - } - - @keyframes longVoice { - 0% { - transform: scaleY(0); - } - - 100% { - transform: scaleY(1); - } - } - - @keyframes scaleVoice { - 0% { - transform: scaleY(0.28); - } - - 20% { - transform: scaleY(0.60); - } - - 28% { - transform: scaleY(0.28); - } - - 36% { - transform: scaleY(0.45); - } - - 44% { - transform: scaleY(0.28); - } - - 52% { - transform: scaleY(0.45); - } - - 62% { - transform: scaleY(0.80); - } - - 72% { - transform: scaleY(0.80); - } - - 90% { - transform: scaleY(0.28); - } - - 100% { - transform: scaleY(0.28); - } - } - - #voiceRecSetting { - display: none; - position: absolute; - top: -70px; - left: -26px; - z-index: 1; - padding: 4px 4px; - -webkit-user-select: none; - user-select: none; - border-radius: 3px; - box-shadow: 0 0 6px rgba(0, 0, 0, 0.15); - } - - #voiceRecSetting select { - width: 102px; - outline: none; - height: 28px; - border-radius: 3px; - border-color: rgba(0, 0, 0, .3); - } - - .presetModelCls label { - margin-right: 8px; - } - - .presetModelCls select { - height: 30px; - line-height: 30px; - margin-top: 2px; - font-size: 15px; - } - - .modelSwitch, - .setSwitch { - display: flex; - } - - .modelSwitch { - margin-bottom: 10px; - -webkit-user-select: none; - user-select: none; - } - - .modelSwitch>div, - .setSwitch>div { - border-radius: 3px; - width: calc(100% / 3); - padding: 8px; - line-height: 1; - text-align: center; - cursor: pointer; - } - - .modelSwitch>div:hover, - .setSwitch>div:hover { - background-color: var(--body-hover-bgcolor); - } - - .activeSwitch { - background-color: var(--inputborder-color); - } - - .checkLoad { - height: 32px; - border-radius: 3px; - line-height: 32px; - background: #d0d0d0; - text-align: center; - display: flex; - justify-content: center; - cursor: pointer; - } - - .checkLoad:hover { - background: #c0c0c0; - } - - .voiceChecking { - background-color: #c0c0c0 !important; - } - - .voiceChecking>svg { - display: inline !important; - } - - .checkLoad>svg { - display: none; - margin-right: 8px; - height: 32px; - width: 64px; - } - - #preSetSystem { - height: 30px; - line-height: 30px; - } - - #sysMask { - display: none; - position: fixed; - z-index: 200; - top: 0; - left: 0; - bottom: 0; - right: 0; - cursor: pointer; - justify-content: center; - align-items: center; - background: rgba(0, 0, 0, .4); - -webkit-tap-highlight-color: transparent; - } - - #sysDialog { - position: relative; - background: rgba(var(--primary-rgbbody),1); - cursor: auto; - max-height: 100%; - width: 100%; - display: flex; - flex-direction: column; - border-radius: 4px; - padding: 20px; - } - - .sysTitle { - font-size: 20px; - font-weight: bold; - margin-bottom: 8px; - -webkit-user-select: none; - user-select: none; - } - - .sysSwitch, - .sysSwitch>div * { - pointer-events: none; - } - - .sysSwitch>div { - border-radius: 3px; - height: 32px; - line-height: 32px; - text-align: center; - cursor: pointer; - pointer-events: auto; - font-weight: bold; - display: flex; - align-items: center; - -webkit-user-select: none; - user-select: none; - } - - .sysSwitch>div>svg { - margin-right: 4px; - } - - .sysSwitch>div:hover { - background-color: var(--body-hover-bgcolor); - } - - .sysDetail { - overflow-y: auto; - flex: 1; - margin-top: 4px; - } - - #closeSet { - position: absolute; - right: 0px; - top: 0px; - cursor: pointer; - padding: 10px 14px; - } - - #closeSet:hover { - color: #000; - } - - .setContent { - margin-top: 8px; - } - - .setNotNormalFlow { - position: relative; - } - - .setTitle { - margin-bottom: 6px; - font-weight: bold; - -webkit-user-select: none; - user-select: none; - } - - .setDetail { - margin: 0 10px; - -webkit-user-select: none; - user-select: none; - } - - .autoSelect>label, - .autoSelect>input { - cursor: pointer; - } - - .dataDetail { - display: flex; - } - - .dataDetail svg { - margin-right: 4px; - } - - .dataDetail>div, - .dataDetail>label { - border-radius: 3px; - text-align: center; - padding: 6px 8px; - margin-right: 12px; - font-size: 15px; - cursor: pointer; - display: flex; - align-items: center; - justify-content: space-between; - } - - .dataDetail>div:hover, - .dataDetail>label:hover { - background-color: var(--body-hover-bgcolor); - } - - .keyOptionDetail { - display: flex; - margin-top: 10px; - } - - #resetHotKey { - display: flex; - align-items: center; - justify-content: space-between; - cursor: pointer; - font-size: 15px; - border-radius: 3px; - padding: 6px 8px; - margin-left: 90px; - } - - #resetHotKey>svg { - margin-right: 4px; - } - - #resetHotKey:hover { - background-color:var(--body-hover-bgcolor); - } - - .hotKeyDetail>div { - position: relative; - height: 30px; - margin-bottom: 2px; - } - - .hotKeyDetail label { - line-height: 30px; - } - - .hotKeyDetail select { - position: absolute; - left: 110px; - outline: none; - border-radius: 3px; - width: 120px; - border-color: rgba(0, 0, 0, .3); - height: 30px; - font-size: 15px; - } - - .avatarDetail { - display: flex; - margin-top: 2px; - } - - .avatarDetail img { - border-radius: 2px; - width: 32px; - height: 32px; - flex-shrink: 0; - margin-right: 10px; - margin-top: 2px; - } - - .inputDetail input { - outline: none; - border-radius: 3px; - padding-left: 8px; - font-size: 15px; - width: 100%; - height: 34px; - border: 1px solid rgba(0, 0, 0, .3); - } - - .themeDetail { - display: flex; - width: 180px; - justify-content: space-between; - pointer-events: none; - } - - .themeDetail svg { - display: block; - } - - .themeDetail>div { - pointer-events: auto; - border-radius: 20px; - text-align: center; - padding: 8px 8px; - font-size: 14px; - cursor: pointer; - } - - .themeDetail>div * { - pointer-events: none; - } - - .themeDetail>div:hover { - background-color: var(--body-hover-bgcolor); - } - - .darkTheme>div:first-child { - background-color: var(--inputborder-color); - } - - .lightTheme>div:nth-child(2) { - background-color: var(--inputborder-color); - } - - .autoTheme>div:nth-child(3) { - background-color: var(--inputborder-color); - } - - .langDetail { - width: 110px; - } - - .enLang>div:first-child { - background-color: var(--inputborder-color); - } - - .zhLang>div:nth-child(2) { - background-color: var(--inputborder-color); - } - - #customAutoSet input { - width: 100px; - height: 30px; - line-height: 30px; - font-size: 15px; - outline: none; - border: 1px solid rgba(0, 0, 0, .3); - text-align: center; - border-radius: 3px; - background: #fff; - color: #24292f; - } - - #customAutoSet label { - margin-right: 8px; - } - - .progressBar { - position: relative; - width: 100%; - height: 12px; - border-radius: 6px; - background: #e0e0e0; - overflow: hidden; - } - - .nowProgress { - position: absolute; - left: 0; - top: 0; - height: 12px; - min-width: 1px; - border-radius: 6px; - background: #99c959; - } - - .progressDetail { - display: flex; - justify-content: space-between; - font-size: 15px; - } - - .cursorCls { - background-color: var(--inputborder-color); - width: 4px; - animation: 1s cursor-blinker infinite step-start; - } - - @keyframes cursor-blinker { - 0% { - opacity: 0; - } - - 50% { - opacity: 1; - } - - 100% { - opacity: 0; - } - } - - .apiSelsContainer { - position: relative; - z-index: 1; - } - - #apiSelect { - position: absolute; - top: 64px; - padding: 4px 0; - width: calc(100% - 20px); - left: 10px; - box-shadow: 0 0 6px rgba(0, 0, 0, 0.15); - max-height: 180px; - overflow-y: auto; - -webkit-user-select: none; - user-select: none; - } - - #apiSelect>div { - pointer-events: auto; - cursor: pointer; - font-size: 15px; - padding: 6px 0 6px 8px; - height: 36px; - display: flex; - justify-content: space-between; - align-items: center; - } - - #apiSelect>div:hover { - background-color:var(--body-hover-bgcolor); - } - - #apiSelect>div>span { - height: 100%; - line-height: 23px; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - } - - #apiSelect>div * { - pointer-events: none; - } - - .delApiOption:hover { - background-color: var(--body-hover-bgcolor); - } - - .delApiOption { - width: 36px; - height: 36px; - flex-shrink: 0; - pointer-events: auto !important; - } - - .delApiOption>svg { - margin: 6px; - display: block; - } - - @keyframes textInOut { - from { - opacity: 0; - } - - to { - opacity: 1; - } - } - - .response[data-loading="true"]>.responseBody>*:not(.cursorCls) { - animation: textInOut 0.48s ease-in-out forwards; - } - - .cotBtn { - width: 120px; - height: 32px; - font-size: 14px; - user-select: none; - background-color: var(--inputborder-color); - position: relative; - padding-left: 14px; - line-height: 32px; - border-radius: 8px; - margin-bottom: 12px; - cursor: pointer; - } - - .cotBtn:hover { - background-color: var(--body-hover-bgcolor); - } - - .cotBtn.endCot { - width: 142px; - } - - .cotBtn::after { - content: ''; - display: inline-block; - position: absolute; - right: 15px; - top: 14px; - width: 6px; - height: 6px; - transform: rotate(-45deg); - } - - .cotBtn.down::after { - top: 11px; - transform: rotate(135deg); - } - - .cotContent { - background-color: var(--inputborder-color); - padding-left: 12px; - font-size: 14px; - } - - .cotContent>:last-child { - margin-bottom: 12px !important; - } - - .hide { - display: none; - } - -