diff --git a/luci-app-adguardhome/.official_channel b/luci-app-adguardhome/.official_channel new file mode 100644 index 000000000..d7025695e --- /dev/null +++ b/luci-app-adguardhome/.official_channel @@ -0,0 +1 @@ +release diff --git a/luci-app-adguardhome/.official_json_version b/luci-app-adguardhome/.official_json_version new file mode 100644 index 000000000..0abf5391a --- /dev/null +++ b/luci-app-adguardhome/.official_json_version @@ -0,0 +1 @@ +26.268.33051~dd3d1cb diff --git a/luci-app-adguardhome/.prepare.sh b/luci-app-adguardhome/.prepare.sh new file mode 100755 index 000000000..92a72fb1f --- /dev/null +++ b/luci-app-adguardhome/.prepare.sh @@ -0,0 +1,19 @@ +PKG_NAME="$1" +CURDIR="$2" +PKG_BUILD_DIR="$3" + +if [ -d "$CURDIR/.git" ]; then + config="$CURDIR/.git/config" +else + config="$(sed "s|^gitdir:\s*|$CURDIR/|;s|$|/config|" "$CURDIR/.git")" +fi +if grep -q -E "url.*gitcode\.(com|net)" "$config"; then + for d in htdocs patches po root; do + rm -rf "$PKG_BUILD_DIR"/$d + done + mkdir -p "$PKG_BUILD_DIR"/htdocs/luci-static/resources/view + touch "$PKG_BUILD_DIR"/htdocs/luci-static/resources/view/$PKG_NAME.js + mkdir -p "$PKG_BUILD_DIR"/root/usr/share/luci/menu.d + touch "$PKG_BUILD_DIR"/root/usr/share/luci/menu.d/$PKG_NAME.json +fi +exit 0 diff --git a/luci-app-adguardhome/.upstream_meta b/luci-app-adguardhome/.upstream_meta new file mode 100644 index 000000000..cfffae49d --- /dev/null +++ b/luci-app-adguardhome/.upstream_meta @@ -0,0 +1,2 @@ +c3d5b719f5337ceefa8b5b1f6c1d7b96965a3812 +e192c5983e50169ed984d48b094798869a0a780b diff --git a/luci-app-adguardhome/LICENSE b/luci-app-adguardhome/LICENSE index b09cd7856..261eeb9e9 100644 --- a/luci-app-adguardhome/LICENSE +++ b/luci-app-adguardhome/LICENSE @@ -1,4 +1,4 @@ -Apache License + Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ diff --git a/luci-app-adguardhome/Makefile b/luci-app-adguardhome/Makefile old mode 100755 new mode 100644 index 0ff9b510c..ec35f246d --- a/luci-app-adguardhome/Makefile +++ b/luci-app-adguardhome/Makefile @@ -1,14 +1,33 @@ -# Copyright 2024- Douglas Orend -# This is free software, licensed under the Apache License, Version 2.0 +# SPDX-License-Identifier: GPL-2.0-only include $(TOPDIR)/rules.mk -PKG_LICENSE:=Apache-2.0 +LUCI_NAME:=luci-app-adguardhome +LUCI_MAINTAINER:=Jackie264 +PKG_LICENSE:=GPL-2.0-only +PKG_CPE_ID:=cpe:/a:Jackie264:luci-app-adguardhome -LUCI_TITLE:=LuCI support for AdguardHome -LUCI_DEPENDS:=+adguardhome +luci-base +curl +LUCI_TITLE:=LuCI support for AdGuard Home +LUCI_DEPENDS:=+luci-base +LUCI_PKGARCH:=all +USERID:=adguardhome=853:adguardhome=853 -PKG_MAINTAINER:=Douglas Orend +PKG_UNPACK:=$(CURDIR)/.prepare.sh $(PKG_NAME) $(CURDIR) $(PKG_BUILD_DIR) + +define Package/luci-app-adguardhome/conffiles +/etc/adguardhome/adguardhome.yaml +/etc/config/adguardhome +endef + +define Package/luci-app-adguardhome/prerm +#!/bin/sh +[ -n "$${IPKG_INSTROOT}" ] || { + rm -f /tmp/luci-indexcache.* + rm -rf /tmp/luci-modulecache/ + /etc/init.d/rpcd reload 2>/dev/null +} +exit 0 +endef include $(TOPDIR)/feeds/luci/luci.mk diff --git a/luci-app-adguardhome/README.md b/luci-app-adguardhome/README.md index d7bf4af4e..47e20c610 100644 --- a/luci-app-adguardhome/README.md +++ b/luci-app-adguardhome/README.md @@ -1,13 +1,48 @@ -# LuCI App AdGuardHome - -**NOTE:** Original Source found for LUCI app [here](https://github.com/openwrt/luci/tree/3253c2644215a1ea8136199044e06ad5e4fd9de9/applications/luci-app-adguardhome) by Duncan Hill (). - -## Screenshots - -### Configuration Tab -![MAC Filter Tab](https://github.com/kiddin9/luci-app-adguardhome/blob/main/.github/images/config.png?raw=true) - -### Status Tab -![Status Tab](https://github.com/kiddin9/luci-app-adguardhome/blob/main/.github/images/status.png?raw=true) +## luci-app-adguardhome +#### (Modified version based on the official source code) +### Overview of changes +- Migrate from lua to luci standard js +- Add a global switch +- Add the UI interface to create/modify AGH password function +- Add UI interface to modify AGH management binding ip:port function +- You can choose AGH as dnsmasq upstream, redirect 53 port, switch dnsmasq 53 port or do nothing +- Update the kernel to choose the Latest/Beta version, download from AGH mirror station/AGH github station +- When updating the kernel, you can choose the Latest/Beta version, and to download from AGH mirror station/AGH github station. +- Dynamically obtain ***WAN*** interface passed to nft rules + +### Default behaviors +> [!TIP] +> Use the following rules to exclude inbound traffic from **WAN** to avoid exposing the router to !!️ **Public Parser**!!️ +> +> This function is only effective in "Redirect" mode. +> ``` +> iifname { __WAN_EXCLUDES__ } return +> ``` +> +> The rest of the `53` port traffic that matches the `target is local` will be redirected to the listening port of **AdGuard Home** +> ``` +> fib daddr type local udp dport 53 redirect to :__AGH_PORT__ +> fib daddr type local tcp dport 53 redirect to :__AGH_PORT__ +> ``` + +> [!CAUTION] +> *Only suitable for the OpenWRT official version, and questions generated by third parties will not be answered* +> +> *Only `nftables` is supported, the one is still using `iptables` may have problems* +> +> *The device name can be viewed through `ip a` / `ifconfig`* + +### Attribution +#### Reference +- [OpenWRT official luci](https://github.com/openwrt/luci/tree/master/applications/luci-app-adguardhome) +- [OpenWRT official packages](https://github.com/openwrt/packages/tree/master/net/adguardhome) + +#### Acknowledgment +Inspired by +- [@w9315273 nft version](https://github.com/w9315273/luci-app-adguardhome) +- [@rufengsuixing](https://github.com/rufengsuixing/luci-app-adguardhome) + +#### Notice +If the author of the original code has any objections, please contact me to deal with it. diff --git a/luci-app-adguardhome/SECURITY.md b/luci-app-adguardhome/SECURITY.md new file mode 100644 index 000000000..034e84803 --- /dev/null +++ b/luci-app-adguardhome/SECURITY.md @@ -0,0 +1,21 @@ +# Security Policy + +## Supported Versions + +Use this section to tell people about which versions of your project are +currently being supported with security updates. + +| Version | Supported | +| ------- | ------------------ | +| 5.1.x | :white_check_mark: | +| 5.0.x | :x: | +| 4.0.x | :white_check_mark: | +| < 4.0 | :x: | + +## Reporting a Vulnerability + +Use this section to tell people how to report a vulnerability. + +Tell them where to go, how often they can expect to get an update on a +reported vulnerability, what to expect if the vulnerability is accepted or +declined, etc. diff --git a/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js b/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js index 5e5d3695d..12fc3ab4f 100644 --- a/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js +++ b/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js @@ -1,16 +1,82 @@ 'use strict'; -'require rpc'; + +'require dom'; 'require form'; -'require view'; +'require fs'; 'require poll'; +'require rpc'; +'require uci'; +'require view'; const DEFAULT_CONFIG_FILE = '/etc/adguardhome/adguardhome.yaml'; const DEFAULT_WORK_DIR = '/var/lib/adguardhome'; const DEFAULT_USER = 'adguardhome'; const DEFAULT_GROUP = DEFAULT_USER; +const DEFAULT_GOGC = '0'; +const DEFAULT_GOMAXPROCS = '0'; +const DEFAULT_GOMEMLIMIT = '0'; + const PATH_REGEX = new RegExp('^/etc(/[^/]+)?/?$'); +const POLL_INTERVAL = 5; + +const RUNNING_SPAN = `${_('Running')}`; +const NOT_RUNNING_SPAN = `${_('Not running')}`; + +const STORAGE_KEY = 'luci-app-adguardhome'; +const STORAGE_KEY_CORE = 'luci-app-adguardhome_core_update'; + +function getServiceInfo(name) { + const fn = rpc.declare({ + object: 'service', + method: 'list', + params: ['name'], + expect: { [name]: { instances: { [name]: {} }}}, + }); + return () => fn(name); +} + +const getAGHServiceInfo = getServiceInfo('adguardhome'); + +async function getStatus() { + try { + const res = await getAGHServiceInfo(); + const isRunning = res?.instances?.adguardhome?.running; + return isRunning ?? false; + } catch (e) { + console.error(e); + return false; + } +} + +function getStatusValue(isRunning) { + return isRunning ? RUNNING_SPAN : NOT_RUNNING_SPAN; +} + +async function getVersion() { + try { + const res = await fs.exec('/usr/bin/AdGuardHome', ['--version']); + const version = res.stdout + ? (res.stdout.match(/version\s+(.*)/) || [null, res.stdout.trim()])[1] + : ''; + return version; + } catch (e) { + console.error(e); + return ''; + } +} + +function updateStatus(node) { + const output = node?.querySelector('output'); + return output + ? async () => { + const isRunning = await getStatus(); + dom.content(output, getStatusValue(isRunning)); + } + : () => {}; +} + function validateConfigFile(_unused, value) { if (value == null || value === '') { return true; @@ -37,122 +103,518 @@ function validateWorkDir(_unused, value) { return true; } -function renderStatus(status, agh_config) { - var spanTemp = '%s %s'; - var renderHTML; - if (status.running) { - var button = String.format(' %s', - agh_config.web_url, _('Open Web Interface')); - renderHTML = spanTemp.format('green', _('adguardhome'), _('RUNNING')) + button; - } else { - renderHTML = spanTemp.format('red', _('adguardhome'), _('NOT RUNNING')); - } - - return renderHTML; -} - return view.extend({ - load_adguardhome_config: rpc.declare({ - object: 'luci.adguardhome', - method: 'get_config' - }), - load_adguardhome_status: rpc.declare({ - object: 'luci.adguardhome', - method: 'get_status' - }), - load: function () { + load() { return Promise.all([ - this.load_adguardhome_status(), - this.load_adguardhome_config() + getStatus(), + getVersion(), + uci.load('adguardhome').then(() => { + const sections = uci.sections('adguardhome', 'adguardhome'); + const sec = sections.length > 0 ? sections[0] : {}; + const configFile = sec.config_file || DEFAULT_CONFIG_FILE; + return fs.read(configFile).catch(() => null); + }) ]); }, - render: function (data) { - var status = data[0] || {}; - var agh_config = data[1] || {}; - // A basic configuration form; the luci.adguardhome script that - // powers the other UI pages needs a username and password to - // communicate with the AdguardHome REST API. - var s, o; - var m = new form.Map('adguardhome', _('AdGuard Home Configuration'), - _('默认用户名admin 密码admin') + + async render([isRunning, version, yamlContent]) { + // If there is a version number, it means that the kernel exists. If there is no version number, it does not exist. + const coreExists = Boolean(version); + let dnsPort = '53'; + if (yamlContent) { + const portMatches = yamlContent.match(/port:\s*(\d+)/g); + if (portMatches && portMatches.length >= 2) { + const actualPort = portMatches[1].match(/\d+/); + if (actualPort) { + dnsPort = actualPort[0]; + } + } + } + + // 💡 Dynamic security value: UI rendering requires httpport to generate jump links + const sections = uci.sections('adguardhome', 'adguardhome'); + const savedHttpPort = (sections.length > 0 && sections[0].httpport) ? sections[0].httpport : '3008'; + + const map = new form.Map('adguardhome', _('AdGuard Home')); + + const statusSect = map.section(form.TypedSection, 'status'); + statusSect.anonymous = true; + statusSect.cfgsections = () => ['status_section']; + + const versionOpt = statusSect.option(form.DummyValue, '_version', _('Version')); + versionOpt.cfgvalue = () => version || `${_('Not installed')}`; + versionOpt.rawhtml = true; + + const statusOpt = statusSect.option(form.DummyValue, '_status', _('Service Status')); + statusOpt.rawhtml = true; + statusOpt.cfgvalue = () => getStatusValue(isRunning); + + const mainSect = map.section(form.TypedSection, 'adguardhome'); + mainSect.anonymous = true; + + mainSect.tab('general', _('General Settings')); + mainSect.tab( + 'jail', + _('File System Access'), + _('Files and directories that AdGuard Home should have read-only or read-write access to.'), + ); + mainSect.tab('dns_redirect', _('Services Settings')); + mainSect.tab( + 'core_update', + _('Core Update'), + _('Settings and operations for updating the AdGuardHome core binary.') + ); + mainSect.tab( + 'advanced', + _('Advanced Settings'), + _('Go environment variables that tune garbage collector and memory management.') + + ' ' + _('Modify at your own risk.'), ); - s = m.section(form.TypedSection); - s.anonymous = true; - - s.render = function () { - setTimeout(function() { - poll.add(function () { - var view = document.getElementById('service_status'); - view.innerHTML = renderStatus(status, agh_config); - }); - }, 0); - - return E('div', { class: 'cbi-section', id: 'status_bar' }, [ - E('p', { id: 'service_status' }, _('Collecting data...')) - ]); + // ==== Move here: global switch ==== + const enabledOpt = mainSect.taboption( + 'general', + form.Flag, + 'enabled', + _('Enable') + ); + enabledOpt.default = '0'; + enabledOpt.rmempty = false; + // If the kernel does not exist, additional warning prompts will guide users to turn on the service to download automatically. + if (!coreExists) { + enabledOpt.description = `${_('Core binary not found. Enable the service to trigger an automatic download.')}`; } - - s = m.section(form.NamedSection, 'config', 'adguardhome', _('General settings')); - o = s.option(form.Flag, 'enabled', _('Enable')); - o.default = o.disabled; - o.rmempty = false; - o = s.option(form.Value, 'config_file', _('Configuration file')); - o.datatype = 'string'; - o.placeholder = DEFAULT_CONFIG_FILE; - o.validate = validateConfigFile; + const configFileOpt = mainSect.taboption( + 'general', + form.Value, + 'config_file', + _('Configuration file'), + _('Configuration file must be stored in its own directory, and not in \'/etc\'.') + + '
' + _('Parent directory will be owned by the service user.') + + '
' + _('If empty, defaults to') + ` '${DEFAULT_CONFIG_FILE}'.`, + ); + configFileOpt.placeholder = DEFAULT_CONFIG_FILE; + configFileOpt.validate = validateConfigFile; - o = s.option(form.Value, 'work_dir', _('Work Dir')); - o.datatype = 'string'; - o.placeholder = DEFAULT_WORK_DIR; - o.validate = validateWorkDir; + const workDirOpt = mainSect.taboption( + 'general', + form.Value, + 'work_dir', + _('Working directory'), + _('Directory where filters, logs, and statistics are stored.') + + '
' + _('Will be owned by the service user.') + + '
' + _('If empty, defaults to') + ` '${DEFAULT_WORK_DIR}'.`, + ); + workDirOpt.placeholder = DEFAULT_WORK_DIR; + workDirOpt.validate = validateWorkDir; - o = s.option(form.Value, 'user', _('Service user'), _('User the service runs under.')); - o.datatype = 'string'; - o.placeholder = DEFAULT_USER; + const userOpt = mainSect.taboption( + 'general', + form.Value, + 'user', + _('Service user'), + _('User the service runs under.') + ' ' + _('If empty, defaults to') + + ` '${DEFAULT_USER}'.`, + ); + userOpt.placeholder = DEFAULT_USER; - o = s.option(form.Value, 'group', _('Service group'), _('Group the service runs under.')); - o.datatype = 'string'; - o.placeholder = DEFAULT_GROUP; + const groupOpt = mainSect.taboption( + 'general', + form.Value, + 'group', + _('Service group'), + _('Group the service runs under.') + ' ' + _('If empty, defaults to') + + ` '${DEFAULT_GROUP}'.`, - // 添加重定向模式选择 - o = s.option(form.ListValue, 'redirect', _('Redirect'), _('AdGuardHome redirect mode')); - o.value('none', _('none')); - o.value('dnsmasq-upstream', _('Run as dnsmasq upstream server')); - o.value('redirect', _('Redirect 53 port to AdGuardHome')); - o.value('exchange', _('Use port 53 replace dnsmasq')); - o.default = 'none'; + ); + groupOpt.placeholder = DEFAULT_GROUP; - o = s.option(form.Value, 'web_username', _('Username for AdGuard Home'), _('The username you configured when you set up AdGuard Home')); - o.default = 'admin'; + const verboseOpt = mainSect.taboption( + 'general', + form.Flag, + 'verbose', + _('Verbose logging'), + ); + verboseOpt.default = '0'; - o = s.option(form.Value, 'web_password', _('Password for AdGuard Home'), _('The password you configured when you set up AdGuard Home')); - o.password = true; - o.default = 'admin'; + const advSettingsOpt = mainSect.taboption( + 'general', + form.Flag, + 'advanced_settings', + _('Advanced Settings'), + ); + advSettingsOpt.default = '0'; + advSettingsOpt.rmempty = false; + advSettingsOpt.load = () => sessionStorage.getItem(STORAGE_KEY) || '0'; + advSettingsOpt.remove = () => {}; + advSettingsOpt.write = (_, value) => sessionStorage.setItem(STORAGE_KEY, value); - var ChangePassword = form.Button.extend({ - set_passwd: rpc.declare({ - object: 'luci.adguardhome', - method: 'set_passwd', - params: [ 'username', 'hash' ], - }), - onclick: function() { - var username = document.getElementById("widget.cbid.adguardhome.config.web_username").value; - var password = document.getElementById("widget.cbid.adguardhome.config.web_password").value; - var hash = TwinBcrypt.hashSync(password); - return Promise.all([ this.set_passwd( username, hash ) ]); - }, + // ==== New: The switch of Core Update on General tab ==== + const coreUpdateToggleOpt = mainSect.taboption( + 'general', + form.Flag, + 'enable_core_update', + _('Core Update'), + _('Show the tab and settings for updating the AdGuardHome core binary.') + ); + coreUpdateToggleOpt.default = '0'; + coreUpdateToggleOpt.rmempty = false; + coreUpdateToggleOpt.load = () => sessionStorage.getItem(STORAGE_KEY_CORE) || '0'; + coreUpdateToggleOpt.remove = () => {}; + coreUpdateToggleOpt.write = (_, value) => sessionStorage.setItem(STORAGE_KEY_CORE, value); + // ========================================== + + mainSect.taboption('jail', form.DynamicList, 'jail_mount', _('Read-only access')); + mainSect.taboption('jail', form.DynamicList, 'jail_mount_rw', _('Read-write access')); + + const gcOpt = mainSect.taboption( + 'advanced', + form.Value, + 'gc', + 'GOGC', + _('Tunes the garbage collector\'s aggressiveness by setting the percentage of heap ' + + 'growth allowed before the next collection cycle triggers.') + '
' + + _('If empty, defaults to') + ' ' + _('unset and 100') + '.', + 'https://go.dev/doc/gc-guide#GOGC' + ); + gcOpt.datatype = 'uinteger'; + gcOpt.depends('advanced_settings', '1'); + gcOpt.placeholder = DEFAULT_GOGC; + gcOpt.retain = true; + + const maxProcsOpt = mainSect.taboption( + 'advanced', + form.Value, + 'maxprocs', + 'GOMAXPROCS', + _('The maximum number of operating system threads that can execute user-level Go code' + + ' simultaneously.') + '
' + + _('If empty, defaults to') + ' ' + _('unset and matching the number of CPUs') + '.', + ); + maxProcsOpt.datatype = 'uinteger'; + maxProcsOpt.depends('advanced_settings', '1'); + maxProcsOpt.placeholder = DEFAULT_GOMAXPROCS; + maxProcsOpt.retain = true; + + const memLimitOpt = mainSect.taboption( + 'advanced', + form.Value, + 'memlimit', + 'GOMEMLIMIT', + _('A soft memory cap for the Go runtime, allowing the garbage collector to run more ' + + 'frequently as usage approaches the limit to prevent Out-of-Memory (OOM) kills.') + + '
' + + _('If empty, defaults to') + ' ' + _('unset') + '.', + ); + memLimitOpt.datatype = 'uinteger'; + memLimitOpt.depends('advanced_settings', '1'); + memLimitOpt.placeholder = DEFAULT_GOMEMLIMIT; + memLimitOpt.retain = true; + + // 💡 1. Extract the real listening address and port directly from YAML + let realHttpAddress = '0.0.0.0:3008'; + if (yamlContent) { + // Match the address: field below http: in YAML + const addrMatch = yamlContent.match(/address:\s*([^\s]+)/); + if (addrMatch && addrMatch[1]) { + realHttpAddress = addrMatch[1]; + } + } + + // 💡 2. Analyze the real IP and port used by the jump button + let linkIp = window.location.hostname; + let linkPort = '3008'; + const addrParts = realHttpAddress.split(':'); + if (addrParts.length >= 2) { + linkPort = addrParts.pop(); + let ipPart = addrParts.join(':').replace(/\[|\]/g, ''); + // If the binding is a full zero address, the IP accessed by the current router will be returned. + if (ipPart !== '0.0.0.0' && ipPart !== '') { + linkIp = ipPart; + } + } + + // 💡 3. Upgrade the original httpport to http_address + const isServiceEnabled = sections.length > 0 && sections[0].enabled === '1'; + const disabledHint = _('Service is disabled. Please go to "General Settings" to enable it.'); + + // 💡 4. WebUI button HTML to construct intelligent state + const webuiBtnHtml = isServiceEnabled + ? `${_('Open AdGuardHome WebUI')}` + : ` + ${_('Open AdGuardHome WebUI')} + `; + + // 💡 5. Rendering components + const httpAddressOpt = mainSect.taboption( + 'dns_redirect', + form.Value, + 'http_address', + _('WebUI listener (address:port)'), + _('Bind to specific interface:port (e.g., 0.0.0.0:3008). Leave as 0.0.0.0 to listen on all interfaces.') + + `
${webuiBtnHtml}` + ); + httpAddressOpt.placeholder = '0.0.0.0:3008'; + httpAddressOpt.default = '0.0.0.0:3008'; + httpAddressOpt.datatype = 'hostport'; + httpAddressOpt.rmempty = false; + + // 🎯 Overwrite the default reading UCI behavior and forcibly echo the real value in YAML + httpAddressOpt.cfgvalue = function(section_id) { + return realHttpAddress; + }; + + // ==== Add the password modification function below the WebUI port ==== + const isPasswordEmpty = yamlContent ? /password:[ \t]*(\r?\n|$)/.test(yamlContent) : false; + const hashPassOpt = mainSect.taboption( + 'dns_redirect', + form.Value, + 'hashpass', + _('Change password'), + _('Enter the password here. Click the Load calculate Module button below and go on.') + + '
' + ); + hashPassOpt.default = ''; + hashPassOpt.datatype = 'string'; + hashPassOpt.password = true; + hashPassOpt.rmempty = true; + hashPassOpt.placeholder = isPasswordEmpty ? _('Please create a new password.') : ''; + hashPassOpt.cfgvalue = function(section_id) { + return ''; + }; + + const redirectOpt = mainSect.taboption( + 'dns_redirect', + form.ListValue, + 'redirect', + `${dnsPort} ` + _('Redirect'), + _('AdGuardHome redirect mode') + ); + redirectOpt.value('none', _('No redirect')); + redirectOpt.value('dnsmasq-upstream', _('As the upstream server of dnsmasq')); + redirectOpt.value('redirect', _('Redirect port 53 to AdGuardHome')); + redirectOpt.value('exchange', _('Use port 53 to replace dnsmasq')); + redirectOpt.default = 'none'; + redirectOpt.rmempty = false; + // ========================================== + + // ======== Core Update Control content ======== + const coreVersionOpt = mainSect.taboption( + 'core_update', + form.ListValue, + 'core_version', + _('Core Branch'), + _('Select the branch for the core binary update.') + ); + coreVersionOpt.value('latest', _('Latest Version')); + coreVersionOpt.value('beta', _('Beta Version')); + coreVersionOpt.default = 'latest'; + coreVersionOpt.depends('enable_core_update', '1'); + coreVersionOpt.retain = true; + + const coreUrlOpt = mainSect.taboption( + 'core_update', + form.ListValue, + 'update_url', + _('Update URL'), + _('Select the download link for the core update.') + ); + coreUrlOpt.value('https://static.adtidy.org/adguardhome/release/AdGuardHome_linux_${Arch}.tar.gz', _('Official Mirror (AdTidy - Recommended)')); + coreUrlOpt.value('https://github.com/AdguardTeam/AdGuardHome/releases/download/${Cloud_Version}/AdGuardHome_linux_${Arch}.tar.gz', _('GitHub Releases (Original)')); + coreUrlOpt.default = 'https://static.adtidy.org/adguardhome/release/AdGuardHome_linux_${Arch}.tar.gz'; + coreUrlOpt.rmempty = false; + coreUrlOpt.depends('enable_core_update', '1'); + coreUrlOpt.retain = true; + + const updateActionOpt = mainSect.taboption( + 'core_update', + form.DummyValue, + '_update_action', + _('Action') + ); + updateActionOpt.rawhtml = true; + updateActionOpt.cfgvalue = () => ` +
+ + +
+ + `; + updateActionOpt.depends('enable_core_update', '1'); + // ========================================== + + const rendered = await map.render(); + + const statusNode = map.findElement('data-field', statusOpt.cbid('status_section')); + poll.add(updateStatus(statusNode), POLL_INTERVAL); + + // ========== Update the status and polling logic ========= + let updatePollId = null; + + function startLogPolling() { + if (updatePollId) clearInterval(updatePollId); + const pollAction = () => { + const btnU = document.getElementById('btn-agh-update'); + const btnF = document.getElementById('btn-agh-force'); + const logC = document.getElementById('agh-update-log-container'); + const logT = document.getElementById('agh-update-log'); + + if (btnU) btnU.disabled = true; + if (btnF) btnF.style.display = 'inline-block'; + if (logC) logC.style.display = 'block'; + + fs.read('/tmp/AdGuardHome_update.log').then((txt) => { + if (txt && logT) { + logT.value = txt; + logT.scrollTop = logT.scrollHeight; + } + }).catch(() => {}); + + Promise.all([ + fs.stat('/var/run/update_core').catch(() => null), + fs.stat('/var/run/update_core_done').catch(() => null), + fs.stat('/var/run/update_core_error').catch(() => null) + ]).then(([isCore, isDone, isError]) => { + if (isDone) { + clearInterval(updatePollId); + fs.remove('/var/run/update_core_done').catch(() => {}); + if (btnU) { + btnU.disabled = false; + btnU.textContent = _('Updated'); + } + } else if (isError) { + clearInterval(updatePollId); + if (btnU) { + btnU.disabled = false; + btnU.textContent = _('Failed'); + } + } else if (!isCore && !isDone && !isError) { + clearInterval(updatePollId); + if (btnU) { + btnU.disabled = false; + btnU.textContent = _('Already up-to-date'); + } + } + }); + }; + + pollAction(); + updatePollId = setInterval(pollAction, 1500); + } + + function applyUpdate(isForce) { + const btnU = document.getElementById('btn-agh-update'); + const logC = document.getElementById('agh-update-log-container'); + const logT = document.getElementById('agh-update-log'); + if (btnU) { + btnU.textContent = _('Checking...'); + btnU.disabled = true; + } + + if (logC) logC.style.display = 'block'; + if (logT) logT.value = _('Checking and preparing...\n'); + + map.save().then(() => { + const arg = isForce ? 'force' : ''; + fs.exec('/usr/share/AdGuardHome/update_core.sh', [arg]).catch((err) => { + console.error('Failed to trigger update script:', err); + }); + startLogPolling(); + }).catch((err) => { + console.error('Config save failed:', err); + if (btnU) { + btnU.textContent = _('Save Failed'); + btnU.disabled = false; + } + }); + } + + rendered.addEventListener('click', (e) => { + if (e.target && e.target.id === 'btn-agh-update') { + e.preventDefault(); + applyUpdate(false); + } else if (e.target && e.target.id === 'btn-agh-force') { + e.preventDefault(); + applyUpdate(true); + } + // ==== Handle front-end hash encryption logic ==== + else if (e.target && e.target.id === 'btn-agh-calc-hash') { + e.preventDefault(); + const btn = e.target; + + // Dynamically find the password input box on the page (matching elements with the suffix .hashpass) + const inputs = rendered.querySelectorAll('input[type="text"], input[type="password"]'); + let passInput = null; + for (const el of inputs) { + if (el.id && el.id.endsWith('.hashpass')) { + passInput = el; + break; + } + } + + if (!passInput) return; + + // 1. If JS has not been loaded, it will be loaded dynamically. + if (typeof window.TwinBcrypt === 'undefined') { + btn.disabled = true; + btn.textContent = _('Loading...'); + + const script = document.createElement('script'); + script.src = L.resource('view/adguardhome/twin-bcrypt.min.js'); + script.type = 'text/javascript'; + + script.onload = () => { + btn.textContent = _('Click here to Calculate'); + btn.disabled = false; + }; + script.onerror = () => { + btn.textContent = _(' ... Load Error ... '); + btn.disabled = false; + }; + document.head.appendChild(script); + } + // 2. If JS has been loaded, the calculation will be executed. + else { + if (passInput.value) { + // Prevent repeated hashing of hashed strings (starting with $2a$ or $2y$) + if (passInput.value.startsWith('$2a$') || passInput.value.startsWith('$2y$')) { + btn.textContent = _('Calculation already DONE !'); + return; + } + + const hash = window.TwinBcrypt.hashSync(passInput.value); + passInput.value = hash; + + // Manually trigger native input and change events + passInput.dispatchEvent(new Event('input', { bubbles: true })); + passInput.dispatchEvent(new Event('change', { bubbles: true })); + + btn.textContent = _(' ... Click Save/Apply ↘️ ... '); + } else { + btn.textContent = _(' ... Nothing inputted yet ... '); + } + } + } + // ========================================== }); - o = s.option(ChangePassword, 'change_password', _('Change Password')); - var BCryptInclude = form.DummyValue.extend({ - renderWidget: function(section_id, option_index, cfgvalue) { - return E('script', { 'type':'text/javascript', 'src':'/luci-static/resources/view/twin-bcrypt.min.js' }); + Promise.all([ + fs.stat('/var/run/update_core').catch(() => null), + fs.stat('/var/run/update_core_error').catch(() => null) + ]).then(([isCore, isError]) => { + if (isCore || isError) { + const btnU = document.getElementById('btn-agh-update'); + if (btnU) btnU.textContent = _('Checking...'); + startLogPolling(); } }); - o = s.option(BCryptInclude, 'misc'); + // ========================================== - return m.render(); + return rendered; }, -}) +}); diff --git a/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/logs.js b/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/logs.js deleted file mode 100644 index 21d043771..000000000 --- a/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/logs.js +++ /dev/null @@ -1,44 +0,0 @@ -'use strict'; -'require dom'; -'require fs'; -'require poll'; -'require ui'; -'require view'; - -return view.extend({ - load: function() { - return Promise.all([ - L.resolveDefault(fs.stat('/sbin/logread'), null), - L.resolveDefault(fs.stat('/usr/sbin/logread'), null) - ]).then(function(stat) { - var logger = stat[0] ? stat[0].path : stat[1] ? stat[1].path : null; - - return fs.exec_direct(logger, [ '-e', 'AdGuardHome' ]).catch(function(err) { - ui.addNotification(null, E('p', {}, _('Unable to load log data: ' + err.message))); - return ''; - }); - }); - }, - - render: function(logdata) { - var loglines = logdata.trim().split(/\n/).reverse().slice(0, 50); - - return E([], [ - E('h2', {}, [_('System Log (AdGuard Home)')]), - E('div', {}, [_('Showing last 50 lines')]), - E('div', { 'id': 'content_syslog' }, [ - E('textarea', { - 'id': 'syslog', - 'style': 'font-size:12px', - 'readonly': 'readonly', - 'wrap': 'off', - 'rows': loglines.length + 1 - }, [ loglines.join('\n') ]) - ]) - ]); - }, - - handleSave: null, - handleSaveApply: null, - handleReset: null -}); diff --git a/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/status.js b/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/status.js deleted file mode 100644 index ef0450163..000000000 --- a/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/status.js +++ /dev/null @@ -1,171 +0,0 @@ -'use strict'; -'require rpc'; -'require view'; - -return view.extend({ - load_adguardhome_config: rpc.declare({ - object: 'luci.adguardhome', - method: 'get_config' - }), - load_adguardhome_status: rpc.declare({ - object: 'luci.adguardhome', - method: 'get_status' - }), - load_adguardhome_statistics: rpc.declare({ - object: 'luci.adguardhome', - method: 'get_statistics' - }), - - generic_failure: function(message) { - return E('div', {'class': 'error'}, [_('RPC call failure: '), message]) - }, - render_list: function ( agh_list ) { - var table = E('ul', {}); - for (var i = 0; i < agh_list.length; i++) { - table.appendChild(E('li', {}, agh_list[i])); - } - return table; - }, - render_status_table: function (status, agh_config) { - if (status.error) { - return this.generic_failure(status.error) - } - // Take a hint from the base LuCI module for the Overview page, - // declare the fields and use a loop to build the tabular status view. - // Written out as key/value pairs, but it's just an iterable of elements. - const weburl = agh_config.web_url; - const listen_addresses = L.isObject(status.dns_addresses) ? this.render_list(status.dns_addresses) : _('Not found'); - const bootstrap_dns = L.isObject(agh_config.dns_bootstrap_dns) ? this.render_list(agh_config.dns_bootstrap_dns) : _('Not found'); - const upstream_dns = L.isObject(agh_config.dns_upstream_dns) ? this.render_list(agh_config.dns_upstream_dns) : _('Not found'); - const fields = [ - _('Running'), status.running ? _('Yes') : _('No'), - _('Protection enabled'), status.protection_enabled ? _('Yes') : _('No'), - _('Statistics period (days)'), agh_config.statistics_interval, - _('Web interface'), E('button', { 'class': "cbi-button cbi-button-button" }, E('a', { 'href': weburl, 'target': '_blank' }, weburl)), - _('DNS listen port'), status.dns_port, - _('DNS listen addresses'), listen_addresses, - _('Bootstrap DNS addresses'), bootstrap_dns, - _('Upstream DNS addresses'), upstream_dns, - _('Version'), status.version, - ]; - - var table = E('table', { 'class': 'table', 'id': 'status' }); - for (var i = 0; i < fields.length; i += 2) { - table.appendChild(E('tr', { 'class': 'tr' }, [ - E('td', { 'class': 'td left', 'width': '33%', 'vertical-align': 'text-top' }, [fields[i]]), - E('td', { 'class': 'td left' }, [(fields[i + 1] != null) ? fields[i + 1] : _('Not found')]) - ])); - } - return table; - }, - render_statistics_table: function (statistics) { - // High level statistics - if (statistics.error) { - return this.generic_failure(statistics.error) - } - const fields = [ - _('DNS queries'), statistics.num_dns_queries, - _('DNS blocks'), statistics.num_blocked_filtering, - _('DNS replacements (safesearch)'), statistics.num_replaced_safesearch, - _('DNS replacements (malware/phishing)'), statistics.num_replaced_safebrowsing, - _('Average processing time (seconds)'), statistics.avg_processing_time, - ]; - - var table = E('table', { 'class': 'table', 'id': 'statistics' }); - for (var i = 0; i < fields.length; i += 2) { - table.appendChild( - E('tr', { 'class': 'tr' }, [ - E('td', { 'class': 'td left', 'width': '33%' }, [fields[i]]), - E('td', { 'class': 'td left' }, [(fields[i + 1] != null) ? fields[i + 1] : _('Not found')]) - ])); - } - return table; - }, - render_top_table: function(table_id, objects) { - var table = E('table', { 'class': 'table', 'id': table_id }); - for (var i = 0; i < objects.length; i++) { - table.appendChild( - E('tr', { 'class': 'tr' }, [ - E('td', { 'class': 'td left', 'width': '33%' }, Object.keys(objects[i])[0]), - E('td', { 'class': 'td left' }, Object.values(objects[i])[0]) - ]) - ); - } - return table; - }, - render_top_queries_table: function (statistics) { - // Top 5 queried domains table view - if (statistics.error) { - return this.generic_failure(statistics.error) - } - if (typeof statistics.top_queried_domains === 'undefined') { return ""; } - const top_queries = statistics.top_queried_domains.slice(0, 5); - return this.render_top_table('top_queries', top_queries) - }, - render_top_blocked_table: function (statistics) { - // Top 5 blocked domains table view - if (statistics.error) { - return this.generic_failure(statistics.error) - } - if (typeof statistics.top_blocked_domains === 'undefined') { return ""; } - const top_blocked = statistics.top_blocked_domains.slice(0, 5); - return this.render_top_table('top_blocked', top_blocked) - }, - // Core LuCI functions from here on. - load: function () { - return Promise.all([ - this.load_adguardhome_status(), - this.load_adguardhome_statistics(), - this.load_adguardhome_config() - ]); - }, - render: function (data) { - // data[0] should be load_adguardhome_status() result - var status = data[0] || {}; - // data[1] should be load_adguardhome_statistics() result - var statistics = data[1] || {}; - // data[2] should be load_adguardhome_config() result - var agh_config = data[2] || {}; - - // status.auth_error is only filled in when the config fetch failed - // to get the credentials. That stops all activity, since the user must - // first configure the username and password. Don't even bother trying - // to make REST API calls without credentials. - if (status.auth_error) { - return E('div', { 'class': 'cbi-map', 'id': 'map' }, [ - E('div', { 'class': 'cbi-section' }, [ - E('div', { 'class': 'left' }, [ - E('h2', _('AdGuard Home Status - Error')), - E('div', { 'class': 'error' }, status.auth_error), - E('div', { 'class': 'info' }, _('Please open the Configuration section, and provide the credentials.')) - ]) - ]), - ]); - } - - // Render all the status tables as one big block. - return E('div', { 'class': 'cbi-map', 'id': 'map' }, [ - E('div', { 'class': 'cbi-section' }, [ - E('div', { 'class': 'left' }, [ - E('h2', _('AdGuard Home Status')), - this.render_status_table(status, agh_config) - ]), - E('div', { 'class': 'left' }, [ - E('h3', _('AdGuard Home Statistics')), - this.render_statistics_table(statistics) - ]), - E('div', { 'class': 'left' }, [ - E('h3', _('Top Queried Domains')), - this.render_top_queries_table(statistics) - ]), - E('div', { 'class': 'left' }, [ - E('h3', _('Top Blocked Domains')), - this.render_top_blocked_table(statistics) - ]) - ]), - ]); - }, - handleSave: null, - handleSaveApply: null, - handleReset: null -}) diff --git a/luci-app-adguardhome/htdocs/luci-static/resources/view/twin-bcrypt.min.js b/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/twin-bcrypt.min.js similarity index 99% rename from luci-app-adguardhome/htdocs/luci-static/resources/view/twin-bcrypt.min.js rename to luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/twin-bcrypt.min.js index 6284357c6..3a0326804 100644 --- a/luci-app-adguardhome/htdocs/luci-static/resources/view/twin-bcrypt.min.js +++ b/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/twin-bcrypt.min.js @@ -4,4 +4,4 @@ * Licence: BSD-3-Clause */ !function(r,n){"use strict";function e(r){return y[g]=t.apply(n,r),g++}function t(r){var e=[].slice.call(arguments,1);return function(){"function"==typeof r?r.apply(n,e):new Function(""+r)()}}function o(r){if(m)setTimeout(t(o,r),0);else{var n=y[r];if(n){m=!0;try{n()}finally{a(r),m=!1}}}}function a(r){delete y[r]}function i(){p=function(){var r=e(arguments);return process.nextTick(t(o,r)),r}}function u(){if(r.postMessage&&!r.importScripts){var n=!0,e=r.onmessage;return r.onmessage=function(){n=!1},r.postMessage("","*"),r.onmessage=e,n}}function f(){var n="setImmediate$"+Math.random()+"$",t=function(e){e.source===r&&"string"==typeof e.data&&0===e.data.indexOf(n)&&o(+e.data.slice(n.length))};r.addEventListener?r.addEventListener("message",t,!1):r.attachEvent("onmessage",t),p=function(){var t=e(arguments);return r.postMessage(n+t,"*"),t}}function c(){var r=new MessageChannel;r.port1.onmessage=function(r){var n=r.data;o(n)},p=function(){var n=e(arguments);return r.port2.postMessage(n),n}}function s(){var r=v.documentElement;p=function(){var n=e(arguments),t=v.createElement("script");return t.onreadystatechange=function(){o(n),t.onreadystatechange=null,r.removeChild(t),t=null},r.appendChild(t),n}}function l(){p=function(){var r=e(arguments);return setTimeout(t(o,r),0),r}}if(!r.setImmediate){var p,g=1,y={},m=!1,v=r.document,d=Object.getPrototypeOf&&Object.getPrototypeOf(r);d=d&&d.setTimeout?d:r,"[object process]"==={}.toString.call(r.process)?i():u()?f():r.MessageChannel?c():v&&"onreadystatechange"in v.createElement("script")?s():l(),d.setImmediate=p,d.clearImmediate=a}}(new Function("return this")()),function(r){"object"==typeof exports?r(exports,require("crypto")):r(self.TwinBcrypt={},self.crypto||self.msCrypto)}(function(r,n){"use strict";function e(r){for(var n=unescape(encodeURIComponent(r)),e=n.length,t=new Array(e),o=0;e>o;o++)t[o]=n.charCodeAt(o);return t}function t(r){for(var n=r.length,e=new Array(n),t=0;n>t;t++)e[t]=r.charCodeAt(t);return e}function o(r,n){for(var e,t,o=0,a="";n>o;){if(e=255&r[o++],a+=B[e>>2],e=(3&e)<<4,o>=n){a+=B[e];break}if(t=255&r[o++],e|=t>>4,a+=B[e],e=(15&t)<<2,o>=n){a+=B[e];break}t=255&r[o++],e|=t>>6,a+=B[e],a+=B[63&t]}return a}function a(r){for(var n,e,t=new Array(16),o=0,a=0;;){if(n=D[r.charCodeAt(o++)-46],e=D[r.charCodeAt(o++)-46],t[a++]=255&(n<<2|e>>4),22===o)break;n=e<<4,e=D[r.charCodeAt(o++)-46],t[a++]=255&(n|e>>2),n=e<<6,e=D[r.charCodeAt(o++)-46],t[a++]=255&(n|e)}return t}function i(r){for(var n=r.length,e=new Array(72),t=0,o=0;72>o;)e[o++]=r[t++],t===n&&(t=0);return e}function u(r,n,e){for(var t=0,o=e>>2;tt;)r[n++]=e[t++]<<24|e[t++]<<16|e[t++]<<8|e[t++]}function c(r){function n(n){for(var e=r,t=G>>2,o=t|O,f=n>>2,c=e[f]^e[t],s=e[1|f];o>t;)s^=(e[c>>>24]+e[a|c>>>16&255]^e[i|c>>>8&255])+e[u|255&c]^e[++t],c^=(e[s>>>24]+e[a|s>>>16&255]^e[i|s>>>8&255])+e[u|255&s]^e[++t];e[f]=s^e[S>>2],e[1|f]=c}function e(n){var e;for(r[L>>2]=0,r[L+4>>2]=0,e=0;M>e;e++)r[G>>2|e]^=r[(n>>2)+e];var t,o,f,c,s,l=r;for(e=0;M>e;e+=2){for(t=G>>2,o=t|O,f=L>>2,c=l[f]^l[t],s=l[1|f];o>t;)s^=(l[c>>>24]+l[a|c>>>16&255]^l[i|c>>>8&255])+l[u|255&c]^l[++t],c^=(l[s>>>24]+l[a|s>>>16&255]^l[i|s>>>8&255])+l[u|255&s]^l[++t];l[f]=s^l[S>>2],l[1|f]=c,r[G>>2|e]=l[f],r[G>>2|e+1]=c}for(e=0;T>e;e+=2){for(t=G>>2,o=t|O,f=L>>2,c=l[f]^l[t],s=l[1|f];o>t;)s^=(l[c>>>24]+l[a|c>>>16&255]^l[i|c>>>8&255])+l[u|255&c]^l[++t],c^=(l[s>>>24]+l[a|s>>>16&255]^l[i|s>>>8&255])+l[u|255&s]^l[++t];l[f]=s^l[S>>2],l[1|f]=c,r[e]=l[f],r[1|e]=c}}function t(r,n,t){for(var o=0;t>=o&&!(r>n);o++)e(R),e(j),r++;return r}var o=k>>2,a=o+256|0,i=a+256|0,u=i+256|0;return{encrypt:n,expandLoop:t}}function s(stdlib, foreign, heap) {"use asm";var HEAP32=new stdlib.Uint32Array(heap);var BLOWFISH_NUM_ROUNDS=16;var S_offset=0x0000;var S1_offset=0x0400;var S2_offset=0x0800;var S3_offset=0x0C00;var P_offset=0x1000;var P_last_offset=0x1044;var crypt_ciphertext_offset=0x1048;var LR_offset=0x01060;var password_offset=0x1068;var salt_offset=0x10b0;var P_LEN=18;var S_LEN=1024;function encrypt(offset) {offset=offset|0;var i=0;var n=0;var L=0;var R=0;var imax=0;imax=P_offset|BLOWFISH_NUM_ROUNDS<<2;L=HEAP32[offset>>2]|0;R=HEAP32[offset+4>>2]|0;L=L^HEAP32[P_offset>>2];for (i=P_offset; (i|0)<(imax|0);) {i=(i+4)>>>0;R=R^(((HEAP32[(L>>>22)>>2]>>>0) +(HEAP32[(S1_offset|(L>>>14&0x3ff))>>2]>>>0) ^(HEAP32[(S2_offset|(L>>>6&0x3ff))>>2])) +(HEAP32[(S3_offset|(L<<2&0x3ff))>>2]>>>0))^HEAP32[i>>2];i=(i+4)>>>0;L=L^(((HEAP32[(R>>>22)>>2]>>>0) +(HEAP32[(S1_offset|(R>>>14&0x3ff))>>2]>>>0) ^(HEAP32[(S2_offset|(R>>>6&0x3ff))>>2])) +(HEAP32[(S3_offset|(R<<2&0x3ff))>>2]>>>0))^HEAP32[i>>2];}HEAP32[offset>>2]=R^HEAP32[P_last_offset>>2];HEAP32[(offset+4)>>2]=L;}function expandKey(offset) {offset=offset|0;var i=0;var off=0;off=P_offset|0;for (i=0; (i|0)<(P_LEN|0); i=(i+1)|0) {HEAP32[off>>2]=HEAP32[off>>2]^HEAP32[offset>>2];offset=(offset+4)|0;off=(off+4)|0;}HEAP32[LR_offset>>2]=0;HEAP32[LR_offset+4>>2]=0;off=P_offset;for (i=0; (i|0)<(P_LEN|0); i=(i+2)|0) {encrypt(LR_offset);HEAP32[off>>2]=HEAP32[LR_offset>>2];HEAP32[off+4>>2]=HEAP32[LR_offset+4>>2];off=(off+8)|0;}off=S_offset;for (i=0; (i|0)<(S_LEN|0); i=(i+2)|0) {encrypt(LR_offset);HEAP32[off>>2]=HEAP32[LR_offset>>2];HEAP32[off+4>>2]=HEAP32[LR_offset+4>>2];off=(off+8)|0;}}function expandLoop(i, counterEnd, maxIterations) {i=i|0;counterEnd=counterEnd|0;maxIterations=maxIterations|0;var j=0;for (j=0; (j|0) <= (maxIterations|0); j=(j+1)|0) {if ((i>>>0)>(counterEnd>>>0)) break;expandKey(password_offset);expandKey(salt_offset);i=(i+1)>>>0;}return i|0;}return {encrypt: encrypt,expandLoop: expandLoop};} -function l(r,n,e,t){var o,a,i,u=L>>2,f=u+1;for(t[u]=0,t[f]=0,a=0,o=0;M>o;o++)i=n[a++]<<24|n[a++]<<16|n[a++]<<8|n[a++],t[G>>2|o]^=i;for(a=0,o=0;M>o;o+=2)i=r[a++]<<24|r[a++]<<16|r[a++]<<8|r[a++],a&=65295,t[u]^=i,i=r[a++]<<24|r[a++]<<16|r[a++]<<8|r[a++],a&=65295,t[f]^=i,e.encrypt(L),t[G>>2|o]=t[u],t[G>>2|o+1]=t[f];var c=k>>2;for(o=0;T>o;o+=2)i=r[a++]<<24|r[a++]<<16|r[a++]<<8|r[a++],a&=65295,t[u]^=i,i=r[a++]<<24|r[a++]<<16|r[a++]<<8|r[a++],a&=65295,t[f]^=i,e.encrypt(L),t[c|o]=t[u],t[c|o+1]=t[f]}function p(r,n,e,t,o,a,i){for(var u=e;t>=u;){if(u=r.expandLoop(u,t,o),a){var f=a(u/(t+1));if(f===!1)return}if(u>t){if(i)return void setImmediate(g.bind(null,r,n,i));return}if(i)return void setImmediate(p.bind(null,r,n,u,t,o,a,i))}}function g(r,n,e){u(n,x,F);var t;for(t=0;64>t;t++)r.encrypt(F+0),r.encrypt(F+8),r.encrypt(F+16);var o,a=0,i=x.length,f=new Array(4*i);for(t=0;i>t;t++)o=n[(F>>2)+t],f[a++]=o>>24,f[a++]=o>>16&255,f[a++]=o>>8&255,f[a++]=255&o;return e&&e(f),f}function y(r,n){return r+o(n,23)}function m(n,o,m,v){var d,h=o.substr(0,29),w=+o.substr(4,2),A=o.substr(7,22);if("string"==typeof n)d=r.encodingMode===r.ENCODING_UTF8?e(n):t(n);else if(Array.isArray(n))d=n.map(function(r){return 255&r});else{if(!(n instanceof Uint8Array))throw new Error("Incorrect arguments");d=Array.prototype.slice.call(n)}d.push(0);var b,E,N=a(A,C),O=31>w?1<>2,N),f(b,R>>2,d),l(N,d,E,b),v?void p(E,b,0,M,T,m,function(r){v(y(h,r))}):(p(E,b,0,M,T,m),y(h,g(E,b)))}function v(r){if(!b)throw new Error("No cryptographically secure pseudorandom number generator available.");if(null==r&&(r=N),r=0|+r,isNaN(r)||4>r||r>31)throw new Error("Invalid cost parameter.");var n="$2y$";return 10>r&&(n+="0"),n+=r+"$",n+=o(b(C),C)}function d(r,n,e){if(n&&"number"!=typeof n){if("string"!=typeof n||!z.test(n))throw new Error("Invalid salt")}else n=v(n);return m(r,n,e)}function h(r,n,e,t){if(arguments.length<2)throw new Error("Incorrect arguments");if(2===arguments.length?(t=n,n=e=null):3===arguments.length&&(t=e,e=null,"function"==typeof n&&(e=n,n=null)),n&&"number"!=typeof n){if("string"!=typeof n||!z.test(n))throw new Error("Invalid salt")}else n=v(n);if(!t||"function"!=typeof t)throw new Error("No callback function was given.");m(r,n,e,t)}function w(r,n){if("string"!=typeof n||!Z.test(n))throw new Error("Incorrect arguments");var e=n.substr(0,n.length-31),t=d(r,e);return t===n}function A(r,n,e,t){if("string"!=typeof n||!Z.test(n))throw new Error("Incorrect arguments");if(t||(t=e,e=null),!t||"function"!=typeof t)throw new Error("No callback function was given.");var o=n.substr(0,n.length-31);h(r,o,e,function(r){t(r===n)})}var b,E="undefined"!=typeof InstallTrigger,I=E;n&&(b=n.randomBytes,n.getRandomValues&&(b=function(r){var e=new Uint8Array(r);return n.getRandomValues(e)}));var C=16,N=10,O=16,$=[608135816,2242054355,320440878,57701188,2752067618,698298832,137296536,3964562569,1160258022,953160567,3193202383,887688300,3232508343,3380367581,1065670069,3041331479,2450970073,2306472731],U=[3509652390,2564797868,805139163,3491422135,3101798381,1780907670,3128725573,4046225305,614570311,3012652279,134345442,2240740374,1667834072,1901547113,2757295779,4103290238,227898511,1921955416,1904987480,2182433518,2069144605,3260701109,2620446009,720527379,3318853667,677414384,3393288472,3101374703,2390351024,1614419982,1822297739,2954791486,3608508353,3174124327,2024746970,1432378464,3864339955,2857741204,1464375394,1676153920,1439316330,715854006,3033291828,289532110,2706671279,2087905683,3018724369,1668267050,732546397,1947742710,3462151702,2609353502,2950085171,1814351708,2050118529,680887927,999245976,1800124847,3300911131,1713906067,1641548236,4213287313,1216130144,1575780402,4018429277,3917837745,3693486850,3949271944,596196993,3549867205,258830323,2213823033,772490370,2760122372,1774776394,2652871518,566650946,4142492826,1728879713,2882767088,1783734482,3629395816,2517608232,2874225571,1861159788,326777828,3124490320,2130389656,2716951837,967770486,1724537150,2185432712,2364442137,1164943284,2105845187,998989502,3765401048,2244026483,1075463327,1455516326,1322494562,910128902,469688178,1117454909,936433444,3490320968,3675253459,1240580251,122909385,2157517691,634681816,4142456567,3825094682,3061402683,2540495037,79693498,3249098678,1084186820,1583128258,426386531,1761308591,1047286709,322548459,995290223,1845252383,2603652396,3431023940,2942221577,3202600964,3727903485,1712269319,422464435,3234572375,1170764815,3523960633,3117677531,1434042557,442511882,3600875718,1076654713,1738483198,4213154764,2393238008,3677496056,1014306527,4251020053,793779912,2902807211,842905082,4246964064,1395751752,1040244610,2656851899,3396308128,445077038,3742853595,3577915638,679411651,2892444358,2354009459,1767581616,3150600392,3791627101,3102740896,284835224,4246832056,1258075500,768725851,2589189241,3069724005,3532540348,1274779536,3789419226,2764799539,1660621633,3471099624,4011903706,913787905,3497959166,737222580,2514213453,2928710040,3937242737,1804850592,3499020752,2949064160,2386320175,2390070455,2415321851,4061277028,2290661394,2416832540,1336762016,1754252060,3520065937,3014181293,791618072,3188594551,3933548030,2332172193,3852520463,3043980520,413987798,3465142937,3030929376,4245938359,2093235073,3534596313,375366246,2157278981,2479649556,555357303,3870105701,2008414854,3344188149,4221384143,3956125452,2067696032,3594591187,2921233993,2428461,544322398,577241275,1471733935,610547355,4027169054,1432588573,1507829418,2025931657,3646575487,545086370,48609733,2200306550,1653985193,298326376,1316178497,3007786442,2064951626,458293330,2589141269,3591329599,3164325604,727753846,2179363840,146436021,1461446943,4069977195,705550613,3059967265,3887724982,4281599278,3313849956,1404054877,2845806497,146425753,1854211946,1266315497,3048417604,3681880366,3289982499,290971e4,1235738493,2632868024,2414719590,3970600049,1771706367,1449415276,3266420449,422970021,1963543593,2690192192,3826793022,1062508698,1531092325,1804592342,2583117782,2714934279,4024971509,1294809318,4028980673,1289560198,2221992742,1669523910,35572830,157838143,1052438473,1016535060,1802137761,1753167236,1386275462,3080475397,2857371447,1040679964,2145300060,2390574316,1461121720,2956646967,4031777805,4028374788,33600511,2920084762,1018524850,629373528,3691585981,3515945977,2091462646,2486323059,586499841,988145025,935516892,3367335476,2599673255,2839830854,265290510,3972581182,2759138881,3795373465,1005194799,847297441,406762289,1314163512,1332590856,1866599683,4127851711,750260880,613907577,1450815602,3165620655,3734664991,3650291728,3012275730,3704569646,1427272223,778793252,1343938022,2676280711,2052605720,1946737175,3164576444,3914038668,3967478842,3682934266,1661551462,3294938066,4011595847,840292616,3712170807,616741398,312560963,711312465,1351876610,322626781,1910503582,271666773,2175563734,1594956187,70604529,3617834859,1007753275,1495573769,4069517037,2549218298,2663038764,504708206,2263041392,3941167025,2249088522,1514023603,1998579484,1312622330,694541497,2582060303,2151582166,1382467621,776784248,2618340202,3323268794,2497899128,2784771155,503983604,4076293799,907881277,423175695,432175456,1378068232,4145222326,3954048622,3938656102,3820766613,2793130115,2977904593,26017576,3274890735,3194772133,1700274565,1756076034,4006520079,3677328699,720338349,1533947780,354530856,688349552,3973924725,1637815568,332179504,3949051286,53804574,2852348879,3044236432,1282449977,3583942155,3416972820,4006381244,1617046695,2628476075,3002303598,1686838959,431878346,2686675385,1700445008,1080580658,1009431731,832498133,3223435511,2605976345,2271191193,2516031870,1648197032,4164389018,2548247927,300782431,375919233,238389289,3353747414,2531188641,2019080857,1475708069,455242339,2609103871,448939670,3451063019,1395535956,2413381860,1841049896,1491858159,885456874,4264095073,4001119347,1565136089,3898914787,1108368660,540939232,1173283510,2745871338,3681308437,4207628240,3343053890,4016749493,1699691293,1103962373,3625875870,2256883143,3830138730,1031889488,3479347698,1535977030,4236805024,3251091107,2132092099,1774941330,1199868427,1452454533,157007616,2904115357,342012276,595725824,1480756522,206960106,497939518,591360097,863170706,2375253569,3596610801,1814182875,2094937945,3421402208,1082520231,3463918190,2785509508,435703966,3908032597,1641649973,2842273706,3305899714,1510255612,2148256476,2655287854,3276092548,4258621189,236887753,3681803219,274041037,1734335097,3815195456,3317970021,1899903192,1026095262,4050517792,356393447,2410691914,3873677099,3682840055,3913112168,2491498743,4132185628,2489919796,1091903735,1979897079,3170134830,3567386728,3557303409,857797738,1136121015,1342202287,507115054,2535736646,337727348,3213592640,1301675037,2528481711,1895095763,1721773893,3216771564,62756741,2142006736,835421444,2531993523,1442658625,3659876326,2882144922,676362277,1392781812,170690266,3921047035,1759253602,3611846912,1745797284,664899054,1329594018,3901205900,3045908486,2062866102,2865634940,3543621612,3464012697,1080764994,553557557,3656615353,3996768171,991055499,499776247,1265440854,648242737,3940784050,980351604,3713745714,1749149687,3396870395,4211799374,3640570775,1161844396,3125318951,1431517754,545492359,4268468663,3499529547,1437099964,2702547544,3433638243,2581715763,2787789398,1060185593,1593081372,2418618748,4260947970,69676912,2159744348,86519011,2512459080,3838209314,1220612927,3339683548,133810670,1090789135,1078426020,1569222167,845107691,3583754449,4072456591,1091646820,628848692,1613405280,3757631651,526609435,236106946,48312990,2942717905,3402727701,1797494240,859738849,992217954,4005476642,2243076622,3870952857,3732016268,765654824,3490871365,2511836413,1685915746,3888969200,1414112111,2273134842,3281911079,4080962846,172450625,2569994100,980381355,4109958455,2819808352,2716589560,2568741196,3681446669,3329971472,1835478071,660984891,3704678404,4045999559,3422617507,3040415634,1762651403,1719377915,3470491036,2693910283,3642056355,3138596744,1364962596,2073328063,1983633131,926494387,3423689081,2150032023,4096667949,1749200295,3328846651,309677260,2016342300,1779581495,3079819751,111262694,1274766160,443224088,298511866,1025883608,3806446537,1145181785,168956806,3641502830,3584813610,1689216846,3666258015,3200248200,1692713982,2646376535,4042768518,1618508792,1610833997,3523052358,4130873264,2001055236,3610705100,2202168115,4028541809,2961195399,1006657119,2006996926,3186142756,1430667929,3210227297,1314452623,4074634658,4101304120,2273951170,1399257539,3367210612,3027628629,1190975929,2062231137,2333990788,2221543033,2438960610,1181637006,548689776,2362791313,3372408396,3104550113,3145860560,296247880,1970579870,3078560182,3769228297,1714227617,3291629107,3898220290,166772364,1251581989,493813264,448347421,195405023,2709975567,677966185,3703036547,1463355134,2715995803,1338867538,1343315457,2802222074,2684532164,233230375,2599980071,2000651841,3277868038,1638401717,4028070440,3237316320,6314154,819756386,300326615,590932579,1405279636,3267499572,3150704214,2428286686,3959192993,3461946742,1862657033,1266418056,963775037,2089974820,2263052895,1917689273,448879540,3550394620,3981727096,150775221,3627908307,1303187396,508620638,2975983352,2726630617,1817252668,1876281319,1457606340,908771278,3720792119,3617206836,2455994898,1729034894,1080033504,976866871,3556439503,2881648439,1522871579,1555064734,1336096578,3548522304,2579274686,3574697629,3205460757,3593280638,3338716283,3079412587,564236357,2993598910,1781952180,1464380207,3163844217,3332601554,1699332808,1393555694,1183702653,3581086237,1288719814,691649499,2847557200,2895455976,3193889540,2717570544,1781354906,1676643554,2592534050,3230253752,1126444790,2770207658,2633158820,2210423226,2615765581,2414155088,3127139286,673620729,2805611233,1269405062,4015350505,3341807571,4149409754,1057255273,2012875353,2162469141,2276492801,2601117357,993977747,3918593370,2654263191,753973209,36408145,2530585658,25011837,3520020182,2088578344,530523599,2918365339,1524020338,1518925132,3760827505,3759777254,1202760957,3985898139,3906192525,674977740,4174734889,2031300136,2019492241,3983892565,4153806404,3822280332,352677332,2297720250,60907813,90501309,3286998549,1016092578,2535922412,2839152426,457141659,509813237,4120667899,652014361,1966332200,2975202805,55981186,2327461051,676427537,3255491064,2882294119,3433927263,1307055953,942726286,933058658,2468411793,3933900994,4215176142,1361170020,2001714738,2830558078,3274259782,1222529897,1679025792,2729314320,3714953764,1770335741,151462246,3013232138,1682292957,1483529935,471910574,1539241949,458788160,3436315007,1807016891,3718408830,978976581,1043663428,3165965781,1927990952,4200891579,2372276910,3208408903,3533431907,1412390302,2931980059,4132332400,1947078029,3881505623,4168226417,2941484381,1077988104,1320477388,886195818,18198404,3786409e3,2509781533,112762804,3463356488,1866414978,891333506,18488651,661792760,1628790961,3885187036,3141171499,876946877,2693282273,1372485963,791857591,2686433993,3759982718,3167212022,3472953795,2716379847,445679433,3561995674,3504004811,3574258232,54117162,3331405415,2381918588,3769707343,4154350007,1140177722,4074052095,668550556,3214352940,367459370,261225585,2610173221,4209349473,3468074219,3265815641,314222801,3066103646,3808782860,282218597,3406013506,3773591054,379116347,1285071038,846784868,2669647154,3771962079,3550491691,2305946142,453669953,1268987020,3317592352,3279303384,3744833421,2610507566,3859509063,266596637,3847019092,517658769,3462560207,3443424879,370717030,4247526661,2224018117,4143653529,4112773975,2788324899,2477274417,1456262402,2901442914,1517677493,1846949527,2295493580,3734397586,2176403920,1280348187,1908823572,3871786941,846861322,1172426758,3287448474,3383383037,1655181056,3139813346,901632758,1897031941,2986607138,3066810236,3447102507,1393639104,373351379,950779232,625454576,3124240540,4148612726,2007998917,544563296,2244738638,2330496472,2058025392,1291430526,424198748,50039436,29584100,3605783033,2429876329,2791104160,1057563949,3255363231,3075367218,3463963227,1469046755,985887462],M=$.length,T=U.length,x=[1332899944,1700884034,1701343084,1684370003,1668446532,1869963892],k=0,G=4096,S=4164,F=4168,L=4192,R=4200,j=4272,B="./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",D=[0,1,54,55,56,57,58,59,60,61,62,63,-1,-1,-1,-1,-1,-1,-1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,-1,-1,-1,-1,-1,-1,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,-1,-1,-1,-1,-1],z=/^\$2[ay]\$(0[4-9]|[12][0-9]|3[01])\$[.\/A-Za-z0-9]{21}[.Oeu]/,Z=/^\$2[ay]\$(0[4-9]|[12][0-9]|3[01])\$[.\/A-Za-z0-9]{21}[.Oeu][.\/A-Za-z0-9]{30}[.CGKOSWaeimquy26]$/;r.genSalt=v,r.hashSync=d,r.hash=h,r.compareSync=w,r.compare=A,r.ENCODING_UTF8=0,r.ENCODING_RAW=1,r.encodingMode=r.ENCODING_UTF8,r.cryptoRNG=!!b,r.randomBytes=b,r.defaultCost=N,r.version="2.2.0"}); \ No newline at end of file +function l(r,n,e,t){var o,a,i,u=L>>2,f=u+1;for(t[u]=0,t[f]=0,a=0,o=0;M>o;o++)i=n[a++]<<24|n[a++]<<16|n[a++]<<8|n[a++],t[G>>2|o]^=i;for(a=0,o=0;M>o;o+=2)i=r[a++]<<24|r[a++]<<16|r[a++]<<8|r[a++],a&=65295,t[u]^=i,i=r[a++]<<24|r[a++]<<16|r[a++]<<8|r[a++],a&=65295,t[f]^=i,e.encrypt(L),t[G>>2|o]=t[u],t[G>>2|o+1]=t[f];var c=k>>2;for(o=0;T>o;o+=2)i=r[a++]<<24|r[a++]<<16|r[a++]<<8|r[a++],a&=65295,t[u]^=i,i=r[a++]<<24|r[a++]<<16|r[a++]<<8|r[a++],a&=65295,t[f]^=i,e.encrypt(L),t[c|o]=t[u],t[c|o+1]=t[f]}function p(r,n,e,t,o,a,i){for(var u=e;t>=u;){if(u=r.expandLoop(u,t,o),a){var f=a(u/(t+1));if(f===!1)return}if(u>t){if(i)return void setImmediate(g.bind(null,r,n,i));return}if(i)return void setImmediate(p.bind(null,r,n,u,t,o,a,i))}}function g(r,n,e){u(n,x,F);var t;for(t=0;64>t;t++)r.encrypt(F+0),r.encrypt(F+8),r.encrypt(F+16);var o,a=0,i=x.length,f=new Array(4*i);for(t=0;i>t;t++)o=n[(F>>2)+t],f[a++]=o>>24,f[a++]=o>>16&255,f[a++]=o>>8&255,f[a++]=255&o;return e&&e(f),f}function y(r,n){return r+o(n,23)}function m(n,o,m,v){var d,h=o.substr(0,29),w=+o.substr(4,2),A=o.substr(7,22);if("string"==typeof n)d=r.encodingMode===r.ENCODING_UTF8?e(n):t(n);else if(Array.isArray(n))d=n.map(function(r){return 255&r});else{if(!(n instanceof Uint8Array))throw new Error("Incorrect arguments");d=Array.prototype.slice.call(n)}d.push(0);var b,E,N=a(A,C),O=31>w?1<>2,N),f(b,R>>2,d),l(N,d,E,b),v?void p(E,b,0,M,T,m,function(r){v(y(h,r))}):(p(E,b,0,M,T,m),y(h,g(E,b)))}function v(r){if(!b)throw new Error("No cryptographically secure pseudorandom number generator available.");if(null==r&&(r=N),r=0|+r,isNaN(r)||4>r||r>31)throw new Error("Invalid cost parameter.");var n="$2y$";return 10>r&&(n+="0"),n+=r+"$",n+=o(b(C),C)}function d(r,n,e){if(n&&"number"!=typeof n){if("string"!=typeof n||!z.test(n))throw new Error("Invalid salt")}else n=v(n);return m(r,n,e)}function h(r,n,e,t){if(arguments.length<2)throw new Error("Incorrect arguments");if(2===arguments.length?(t=n,n=e=null):3===arguments.length&&(t=e,e=null,"function"==typeof n&&(e=n,n=null)),n&&"number"!=typeof n){if("string"!=typeof n||!z.test(n))throw new Error("Invalid salt")}else n=v(n);if(!t||"function"!=typeof t)throw new Error("No callback function was given.");m(r,n,e,t)}function w(r,n){if("string"!=typeof n||!Z.test(n))throw new Error("Incorrect arguments");var e=n.substr(0,n.length-31),t=d(r,e);return t===n}function A(r,n,e,t){if("string"!=typeof n||!Z.test(n))throw new Error("Incorrect arguments");if(t||(t=e,e=null),!t||"function"!=typeof t)throw new Error("No callback function was given.");var o=n.substr(0,n.length-31);h(r,o,e,function(r){t(r===n)})}var b,E="undefined"!=typeof InstallTrigger,I=E;n&&(b=n.randomBytes,n.getRandomValues&&(b=function(r){var e=new Uint8Array(r);return n.getRandomValues(e)}));var C=16,N=10,O=16,$=[608135816,2242054355,320440878,57701188,2752067618,698298832,137296536,3964562569,1160258022,953160567,3193202383,887688300,3232508343,3380367581,1065670069,3041331479,2450970073,2306472731],U=[3509652390,2564797868,805139163,3491422135,3101798381,1780907670,3128725573,4046225305,614570311,3012652279,134345442,2240740374,1667834072,1901547113,2757295779,4103290238,227898511,1921955416,1904987480,2182433518,2069144605,3260701109,2620446009,720527379,3318853667,677414384,3393288472,3101374703,2390351024,1614419982,1822297739,2954791486,3608508353,3174124327,2024746970,1432378464,3864339955,2857741204,1464375394,1676153920,1439316330,715854006,3033291828,289532110,2706671279,2087905683,3018724369,1668267050,732546397,1947742710,3462151702,2609353502,2950085171,1814351708,2050118529,680887927,999245976,1800124847,3300911131,1713906067,1641548236,4213287313,1216130144,1575780402,4018429277,3917837745,3693486850,3949271944,596196993,3549867205,258830323,2213823033,772490370,2760122372,1774776394,2652871518,566650946,4142492826,1728879713,2882767088,1783734482,3629395816,2517608232,2874225571,1861159788,326777828,3124490320,2130389656,2716951837,967770486,1724537150,2185432712,2364442137,1164943284,2105845187,998989502,3765401048,2244026483,1075463327,1455516326,1322494562,910128902,469688178,1117454909,936433444,3490320968,3675253459,1240580251,122909385,2157517691,634681816,4142456567,3825094682,3061402683,2540495037,79693498,3249098678,1084186820,1583128258,426386531,1761308591,1047286709,322548459,995290223,1845252383,2603652396,3431023940,2942221577,3202600964,3727903485,1712269319,422464435,3234572375,1170764815,3523960633,3117677531,1434042557,442511882,3600875718,1076654713,1738483198,4213154764,2393238008,3677496056,1014306527,4251020053,793779912,2902807211,842905082,4246964064,1395751752,1040244610,2656851899,3396308128,445077038,3742853595,3577915638,679411651,2892444358,2354009459,1767581616,3150600392,3791627101,3102740896,284835224,4246832056,1258075500,768725851,2589189241,3069724005,3532540348,1274779536,3789419226,2764799539,1660621633,3471099624,4011903706,913787905,3497959166,737222580,2514213453,2928710040,3937242737,1804850592,3499020752,2949064160,2386320175,2390070455,2415321851,4061277028,2290661394,2416832540,1336762016,1754252060,3520065937,3014181293,791618072,3188594551,3933548030,2332172193,3852520463,3043980520,413987798,3465142937,3030929376,4245938359,2093235073,3534596313,375366246,2157278981,2479649556,555357303,3870105701,2008414854,3344188149,4221384143,3956125452,2067696032,3594591187,2921233993,2428461,544322398,577241275,1471733935,610547355,4027169054,1432588573,1507829418,2025931657,3646575487,545086370,48609733,2200306550,1653985193,298326376,1316178497,3007786442,2064951626,458293330,2589141269,3591329599,3164325604,727753846,2179363840,146436021,1461446943,4069977195,705550613,3059967265,3887724982,4281599278,3313849956,1404054877,2845806497,146425753,1854211946,1266315497,3048417604,3681880366,3289982499,290971e4,1235738493,2632868024,2414719590,3970600049,1771706367,1449415276,3266420449,422970021,1963543593,2690192192,3826793022,1062508698,1531092325,1804592342,2583117782,2714934279,4024971509,1294809318,4028980673,1289560198,2221992742,1669523910,35572830,157838143,1052438473,1016535060,1802137761,1753167236,1386275462,3080475397,2857371447,1040679964,2145300060,2390574316,1461121720,2956646967,4031777805,4028374788,33600511,2920084762,1018524850,629373528,3691585981,3515945977,2091462646,2486323059,586499841,988145025,935516892,3367335476,2599673255,2839830854,265290510,3972581182,2759138881,3795373465,1005194799,847297441,406762289,1314163512,1332590856,1866599683,4127851711,750260880,613907577,1450815602,3165620655,3734664991,3650291728,3012275730,3704569646,1427272223,778793252,1343938022,2676280711,2052605720,1946737175,3164576444,3914038668,3967478842,3682934266,1661551462,3294938066,4011595847,840292616,3712170807,616741398,312560963,711312465,1351876610,322626781,1910503582,271666773,2175563734,1594956187,70604529,3617834859,1007753275,1495573769,4069517037,2549218298,2663038764,504708206,2263041392,3941167025,2249088522,1514023603,1998579484,1312622330,694541497,2582060303,2151582166,1382467621,776784248,2618340202,3323268794,2497899128,2784771155,503983604,4076293799,907881277,423175695,432175456,1378068232,4145222326,3954048622,3938656102,3820766613,2793130115,2977904593,26017576,3274890735,3194772133,1700274565,1756076034,4006520079,3677328699,720338349,1533947780,354530856,688349552,3973924725,1637815568,332179504,3949051286,53804574,2852348879,3044236432,1282449977,3583942155,3416972820,4006381244,1617046695,2628476075,3002303598,1686838959,431878346,2686675385,1700445008,1080580658,1009431731,832498133,3223435511,2605976345,2271191193,2516031870,1648197032,4164389018,2548247927,300782431,375919233,238389289,3353747414,2531188641,2019080857,1475708069,455242339,2609103871,448939670,3451063019,1395535956,2413381860,1841049896,1491858159,885456874,4264095073,4001119347,1565136089,3898914787,1108368660,540939232,1173283510,2745871338,3681308437,4207628240,3343053890,4016749493,1699691293,1103962373,3625875870,2256883143,3830138730,1031889488,3479347698,1535977030,4236805024,3251091107,2132092099,1774941330,1199868427,1452454533,157007616,2904115357,342012276,595725824,1480756522,206960106,497939518,591360097,863170706,2375253569,3596610801,1814182875,2094937945,3421402208,1082520231,3463918190,2785509508,435703966,3908032597,1641649973,2842273706,3305899714,1510255612,2148256476,2655287854,3276092548,4258621189,236887753,3681803219,274041037,1734335097,3815195456,3317970021,1899903192,1026095262,4050517792,356393447,2410691914,3873677099,3682840055,3913112168,2491498743,4132185628,2489919796,1091903735,1979897079,3170134830,3567386728,3557303409,857797738,1136121015,1342202287,507115054,2535736646,337727348,3213592640,1301675037,2528481711,1895095763,1721773893,3216771564,62756741,2142006736,835421444,2531993523,1442658625,3659876326,2882144922,676362277,1392781812,170690266,3921047035,1759253602,3611846912,1745797284,664899054,1329594018,3901205900,3045908486,2062866102,2865634940,3543621612,3464012697,1080764994,553557557,3656615353,3996768171,991055499,499776247,1265440854,648242737,3940784050,980351604,3713745714,1749149687,3396870395,4211799374,3640570775,1161844396,3125318951,1431517754,545492359,4268468663,3499529547,1437099964,2702547544,3433638243,2581715763,2787789398,1060185593,1593081372,2418618748,4260947970,69676912,2159744348,86519011,2512459080,3838209314,1220612927,3339683548,133810670,1090789135,1078426020,1569222167,845107691,3583754449,4072456591,1091646820,628848692,1613405280,3757631651,526609435,236106946,48312990,2942717905,3402727701,1797494240,859738849,992217954,4005476642,2243076622,3870952857,3732016268,765654824,3490871365,2511836413,1685915746,3888969200,1414112111,2273134842,3281911079,4080962846,172450625,2569994100,980381355,4109958455,2819808352,2716589560,2568741196,3681446669,3329971472,1835478071,660984891,3704678404,4045999559,3422617507,3040415634,1762651403,1719377915,3470491036,2693910283,3642056355,3138596744,1364962596,2073328063,1983633131,926494387,3423689081,2150032023,4096667949,1749200295,3328846651,309677260,2016342300,1779581495,3079819751,111262694,1274766160,443224088,298511866,1025883608,3806446537,1145181785,168956806,3641502830,3584813610,1689216846,3666258015,3200248200,1692713982,2646376535,4042768518,1618508792,1610833997,3523052358,4130873264,2001055236,3610705100,2202168115,4028541809,2961195399,1006657119,2006996926,3186142756,1430667929,3210227297,1314452623,4074634658,4101304120,2273951170,1399257539,3367210612,3027628629,1190975929,2062231137,2333990788,2221543033,2438960610,1181637006,548689776,2362791313,3372408396,3104550113,3145860560,296247880,1970579870,3078560182,3769228297,1714227617,3291629107,3898220290,166772364,1251581989,493813264,448347421,195405023,2709975567,677966185,3703036547,1463355134,2715995803,1338867538,1343315457,2802222074,2684532164,233230375,2599980071,2000651841,3277868038,1638401717,4028070440,3237316320,6314154,819756386,300326615,590932579,1405279636,3267499572,3150704214,2428286686,3959192993,3461946742,1862657033,1266418056,963775037,2089974820,2263052895,1917689273,448879540,3550394620,3981727096,150775221,3627908307,1303187396,508620638,2975983352,2726630617,1817252668,1876281319,1457606340,908771278,3720792119,3617206836,2455994898,1729034894,1080033504,976866871,3556439503,2881648439,1522871579,1555064734,1336096578,3548522304,2579274686,3574697629,3205460757,3593280638,3338716283,3079412587,564236357,2993598910,1781952180,1464380207,3163844217,3332601554,1699332808,1393555694,1183702653,3581086237,1288719814,691649499,2847557200,2895455976,3193889540,2717570544,1781354906,1676643554,2592534050,3230253752,1126444790,2770207658,2633158820,2210423226,2615765581,2414155088,3127139286,673620729,2805611233,1269405062,4015350505,3341807571,4149409754,1057255273,2012875353,2162469141,2276492801,2601117357,993977747,3918593370,2654263191,753973209,36408145,2530585658,25011837,3520020182,2088578344,530523599,2918365339,1524020338,1518925132,3760827505,3759777254,1202760957,3985898139,3906192525,674977740,4174734889,2031300136,2019492241,3983892565,4153806404,3822280332,352677332,2297720250,60907813,90501309,3286998549,1016092578,2535922412,2839152426,457141659,509813237,4120667899,652014361,1966332200,2975202805,55981186,2327461051,676427537,3255491064,2882294119,3433927263,1307055953,942726286,933058658,2468411793,3933900994,4215176142,1361170020,2001714738,2830558078,3274259782,1222529897,1679025792,2729314320,3714953764,1770335741,151462246,3013232138,1682292957,1483529935,471910574,1539241949,458788160,3436315007,1807016891,3718408830,978976581,1043663428,3165965781,1927990952,4200891579,2372276910,3208408903,3533431907,1412390302,2931980059,4132332400,1947078029,3881505623,4168226417,2941484381,1077988104,1320477388,886195818,18198404,3786409e3,2509781533,112762804,3463356488,1866414978,891333506,18488651,661792760,1628790961,3885187036,3141171499,876946877,2693282273,1372485963,791857591,2686433993,3759982718,3167212022,3472953795,2716379847,445679433,3561995674,3504004811,3574258232,54117162,3331405415,2381918588,3769707343,4154350007,1140177722,4074052095,668550556,3214352940,367459370,261225585,2610173221,4209349473,3468074219,3265815641,314222801,3066103646,3808782860,282218597,3406013506,3773591054,379116347,1285071038,846784868,2669647154,3771962079,3550491691,2305946142,453669953,1268987020,3317592352,3279303384,3744833421,2610507566,3859509063,266596637,3847019092,517658769,3462560207,3443424879,370717030,4247526661,2224018117,4143653529,4112773975,2788324899,2477274417,1456262402,2901442914,1517677493,1846949527,2295493580,3734397586,2176403920,1280348187,1908823572,3871786941,846861322,1172426758,3287448474,3383383037,1655181056,3139813346,901632758,1897031941,2986607138,3066810236,3447102507,1393639104,373351379,950779232,625454576,3124240540,4148612726,2007998917,544563296,2244738638,2330496472,2058025392,1291430526,424198748,50039436,29584100,3605783033,2429876329,2791104160,1057563949,3255363231,3075367218,3463963227,1469046755,985887462],M=$.length,T=U.length,x=[1332899944,1700884034,1701343084,1684370003,1668446532,1869963892],k=0,G=4096,S=4164,F=4168,L=4192,R=4200,j=4272,B="./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",D=[0,1,54,55,56,57,58,59,60,61,62,63,-1,-1,-1,-1,-1,-1,-1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,-1,-1,-1,-1,-1,-1,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,-1,-1,-1,-1,-1],z=/^\$2[ay]\$(0[4-9]|[12][0-9]|3[01])\$[.\/A-Za-z0-9]{21}[.Oeu]/,Z=/^\$2[ay]\$(0[4-9]|[12][0-9]|3[01])\$[.\/A-Za-z0-9]{21}[.Oeu][.\/A-Za-z0-9]{30}[.CGKOSWaeimquy26]$/;r.genSalt=v,r.hashSync=d,r.hash=h,r.compareSync=w,r.compare=A,r.ENCODING_UTF8=0,r.ENCODING_RAW=1,r.encodingMode=r.ENCODING_UTF8,r.cryptoRNG=!!b,r.randomBytes=b,r.defaultCost=N,r.version="2.2.0"}); diff --git a/luci-app-adguardhome/patches/00_makefile.patch b/luci-app-adguardhome/patches/00_makefile.patch new file mode 100644 index 000000000..4f8e8afec --- /dev/null +++ b/luci-app-adguardhome/patches/00_makefile.patch @@ -0,0 +1,51 @@ +From b65bfa5784b2cbec97740c96fa69d799f3203df0 Mon Sep 17 00:00:00 2001 +From: Jackie264 <125248570+Jackie264@users.noreply.github.com> +Date: Fri, 26 Jun 2026 06:50:26 +0800 +Subject: [PATCH] Modify Makefile for luci-app-adguardhome + +Updated maintainer information and added package configuration. +--- + Makefile | 26 ++++++++++++++++++++++---- + 1 file changed, 22 insertions(+), 4 deletions(-) + +diff --git a/Makefile b/Makefile +index a48c04d..ec35f24 100644 +--- a/Makefile ++++ b/Makefile +@@ -3,14 +3,32 @@ + include $(TOPDIR)/rules.mk + + LUCI_NAME:=luci-app-adguardhome +-LUCI_MAINTAINER:=George Sapkin ++LUCI_MAINTAINER:=Jackie264 + PKG_LICENSE:=GPL-2.0-only ++PKG_CPE_ID:=cpe:/a:Jackie264:luci-app-adguardhome + + LUCI_TITLE:=LuCI support for AdGuard Home +-LUCI_DEPENDS:=+adguardhome +luci-base +-LUCI_EXTRA_DEPENDS:=adguardhome (>=0.107.73-r3) ++LUCI_DEPENDS:=+luci-base + LUCI_PKGARCH:=all ++USERID:=adguardhome=853:adguardhome=853 + +-include ../../luci.mk ++PKG_UNPACK:=$(CURDIR)/.prepare.sh $(PKG_NAME) $(CURDIR) $(PKG_BUILD_DIR) ++ ++define Package/luci-app-adguardhome/conffiles ++/etc/adguardhome/adguardhome.yaml ++/etc/config/adguardhome ++endef ++ ++define Package/luci-app-adguardhome/prerm ++#!/bin/sh ++[ -n "$${IPKG_INSTROOT}" ] || { ++ rm -f /tmp/luci-indexcache.* ++ rm -rf /tmp/luci-modulecache/ ++ /etc/init.d/rpcd reload 2>/dev/null ++} ++exit 0 ++endef ++ ++include $(TOPDIR)/feeds/luci/luci.mk + + # call BuildPackage - OpenWrt buildroot signature diff --git a/luci-app-adguardhome/patches/01_init.patch b/luci-app-adguardhome/patches/01_init.patch new file mode 100644 index 000000000..3830cd0fe --- /dev/null +++ b/luci-app-adguardhome/patches/01_init.patch @@ -0,0 +1,254 @@ +From 2cfa7fa8e6b2c20824bdb81a3d7449c9757ae793 Mon Sep 17 00:00:00 2001 +From: Jackie264 <125248570+Jackie264@users.noreply.github.com> +Date: Sat, 6 Jun 2026 23:21:32 +0800 +Subject: [PATCH] Enhance AdGuardHome init script with new features + +Add extra commands and improve cron handling in AdGuardHome init script. +--- + root/etc/init.d/adguardhome | 164 +++++++++++++++++++++++++++++++++++- + 1 file changed, 162 insertions(+), 2 deletions(-) + +diff --git a/root/etc/init.d/adguardhome b/root/etc/init.d/adguardhome +index 4712913..1383834 100755 +--- a/root/etc/init.d/adguardhome ++++ b/root/etc/init.d/adguardhome +@@ -3,6 +3,7 @@ + # SPDX-License-Identifier: GPL-2.0-only + # + # shellcheck disable=SC3043 # ash supports local ++. /usr/share/AdGuardHome/helper.sh + + # matches dnsmasq + START=19 +@@ -12,22 +13,87 @@ STOP=89 + PROG=/usr/bin/AdGuardHome + USE_PROCD=1 + ++EXTRA_COMMANDS="do_redirect force_reload isrunning" ++EXTRA_HELP="\ ++\tdo_redirect \t0 or 1\n\ ++\tforce_reload\n\ ++\tisrunning" ++ ++CONTROL_SCRIPT="/usr/share/AdGuardHome/control.sh" ++CRON_FILE=/etc/crontabs/root ++ ++get_tz() { ++ SET_TZ="" ++ local zonename ++ zonename="$(uci get system.@system[0].zonename 2>/dev/null)" ++ if [ -n "$zonename" ]; then ++ SET_TZ="$zonename" ++ return ++ fi ++ for tzfile in /etc/TZ /var/etc/TZ; do ++ [ -e "$tzfile" ] && tz="$(cat "$tzfile" 2>/dev/null)" && break ++ done ++ [ -n "$tz" ] && SET_TZ=$tz && return ++} ++ ++do_crontab() { ++ local enabled="$1" ++ local crontab="$2" ++ local findstr default cronenable cronreload=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 ++ ++ [ "$cronreload" -gt 0 ] && /etc/init.d/cron restart ++} ++ ++crontab_editor() { ++ local line ++ line="$(grep "$findstr" $CRON_FILE 2>/dev/null)" ++ if [ "${line:0:1}" != "#" ]; then ++ if [ "$cronenable" -eq 1 ]; then ++ if [ -z "$line" ]; then ++ echo "$default" >> $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)) ++ fi ++ fi ++} ++ + boot() { + ADGUARDHOME_BOOT=1 + start "$@" + } + + start_service() { ++ mkdir -p /var/etc ++ [ ! -f /var/etc/adguardhome.nft ] && touch /var/etc/adguardhome.nft ++ chmod 644 /var/etc/adguardhome.nft ++ + if [ -n "$ADGUARDHOME_BOOT" ]; then + # Do not start yet, wait for triggers + return 0 + fi + ++ rm -f /var/run/AdGserverdis 2>/dev/null + config_load 'adguardhome' + + local config_name='config' + local config_file config group user verbose work_dir workdir + local gc maxprocs memlimit ++ local enabled hashpass crontab redirect + + uci_validate_section 'adguardhome' 'adguardhome' "$config_name" \ + 'gc:uinteger:0' \ +@@ -41,12 +107,46 @@ start_service() { + 'user:string:adguardhome' \ + 'verbose:bool:0' \ + 'workdir:string' \ +- 'work_dir:string:/var/lib/adguardhome' ++ 'work_dir:string:/var/lib/adguardhome' \ ++ 'enabled:bool:0' \ ++ 'hashpass:string' \ ++ 'crontab:string' \ ++ 'redirect:string:none' \ ++ 'http_address:string' + + # Compatibility with older configs + [ -n "$config" ] && config_file="$config" + [ -n "$workdir" ] && work_dir="$workdir" + ++ # Handle hashpass clear text issuance and back brush removal ++ if [ -n "$hashpass" ]; then ++ config_editor "users.password" "$hashpass" "$config_file" ++ uci set adguardhome."$config_name".hashpass="" ++ uci commit adguardhome ++ fi ++ ++ # Synchronize the front-end incing WebUI listening address to YAML ++ if [ -n "$http_address" ]; then ++ config_editor "http.address" "$http_address" "$config_file" ++ uci set adguardhome."$config_name".http_address="" ++ uci commit adguardhome ++ fi ++ ++ # Isse the plan task change ++ do_crontab "$enabled" "$crontab" ++ ++ # If it is not enabled, the linkage cleans up the redirection firewall rules and exits safely. ++ if [ "$enabled" = 0 ]; then ++ $CONTROL_SCRIPT 0 ++ return 0 ++ fi ++ ++ if [ "$user" = "adguardhome" ] && ! grep -q "^adguardhome:" /etc/passwd; then ++ echo "luci-app: User 'adguardhome' is missing, injecting UID 853 into system files..." >&2 ++ grep -q "^adguardhome:" /etc/group || echo "adguardhome:x:853:" >> /etc/group ++ echo "adguardhome:x:853:853:adguardhome:/var/run/adguardhome:/bin/false" >> /etc/passwd ++ fi ++ + local config_dir + config_dir=$(dirname "$config_file") + if [ "$config_dir" = '/etc' ]; then +@@ -59,6 +159,13 @@ start_service() { + mkdir -m 0700 -p "$work_dir" + chown -R "$user":"$group" "$work_dir" + ++ # Silently downgrade and call the kernel download script when the kernel is missing. ++ if [ ! -f "$PROG" ]; then ++ $CONTROL_SCRIPT 0 ++ /usr/share/AdGuardHome/update_core.sh >/tmp/AdGuardHome_update.log 2>&1 & ++ return 0 ++ fi ++ + procd_open_instance adguardhome + + procd_set_param command "$PROG" +@@ -67,12 +174,17 @@ start_service() { + [ "$maxprocs" -le 0 ] || procd_append_param env GOMAXPROCS="$maxprocs" + [ "$memlimit" -le 0 ] || procd_append_param env GOMEMLIMIT="$memlimit" + ++ local SET_TZ ++ get_tz ++ [ -n "$SET_TZ" ] && procd_append_param env TZ="$SET_TZ" ++ + procd_append_param command --config "$config_file" + procd_append_param command --logfile syslog +- procd_append_param command --no-check-update ++ # procd_append_param command --no-check-update + [ "$verbose" = 1 ] && procd_append_param command --verbose + procd_append_param command --work-dir "$work_dir" + ++ procd_set_param file /etc/config/adguardhome "$config_file" + procd_set_param stdout 1 + procd_set_param stderr 1 + procd_set_param user "$user" +@@ -91,10 +203,58 @@ start_service() { + + procd_add_jail_mount '/etc/hosts' + procd_add_jail_mount '/etc/ssl/certs' ++ [ -f /etc/TZ ] && procd_add_jail_mount '/etc/TZ' ++ + config_list_foreach "$config_name" jail_mount procd_add_jail_mount + config_list_foreach "$config_name" jail_mount_rw procd_add_jail_mount_rw + + procd_close_instance ++ ++ # After the main process is successfully submitted for start-up, it begins to intervene asynchronously and take over the control. ++ if [ -f "$config_file" ]; then ++ ($CONTROL_SCRIPT 1) & ++ else ++ $CONTROL_SCRIPT 0 ++ if [ "$redirect" != "none" ]; then ++ procd_open_instance "waitconfig" ++ procd_set_param command "/usr/share/AdGuardHome/watchconfig.sh" ++ procd_close_instance ++ fi ++ fi ++ ++ (sleep 10 && [ -z "$(pgrep -f "$(basename "$PROG")")" ] && logger -t adguardhome "no process in 10s cancel redirect" && $CONTROL_SCRIPT 0 ) & ++} ++ ++stop_service() { ++ config_load 'adguardhome' ++ local config_name='config' ++ local crontab ++ config_get crontab "$config_name" crontab "" ++ ++ $CONTROL_SCRIPT 0 ++ do_crontab 0 "$crontab" ++ touch /var/run/AdGserverdis ++} ++ ++do_redirect() { ++ $CONTROL_SCRIPT "$1" ++} ++ ++_isrunning() { ++ [ ! -f "$PROG" ] && return 2 ++ pgrep -f "$(basename "$PROG")" >/dev/null 2>&1 && return 0 ++ return 1 ++} ++ ++isrunning() { ++ _isrunning ++ local r=$? ++ [ "$r" = "0" ] && echo "running" || ([ "$r" = "1" ] && echo "not run" || echo "no bin") ++ return $r ++} ++ ++force_reload() { ++ _isrunning && procd_send_signal adguardhome || start + } + + service_triggers() { + +reload_service() { + restart +} diff --git a/luci-app-adguardhome/patches/02_acl.patch b/luci-app-adguardhome/patches/02_acl.patch new file mode 100644 index 000000000..5786392cb --- /dev/null +++ b/luci-app-adguardhome/patches/02_acl.patch @@ -0,0 +1,54 @@ +From 883995b0d027fcd32fb9ba4ff24d607e9c176ee2 Mon Sep 17 00:00:00 2001 +From: Jackie264 <125248570+Jackie264@users.noreply.github.com> +Date: Thu, 28 May 2026 10:14:54 +0800 +Subject: [PATCH] Update luci-app-adguardhome.json + +--- + .../rpcd/acl.d/luci-app-adguardhome.json | 36 +++++++++++-------- + 1 file changed, 21 insertions(+), 15 deletions(-) + +diff --git a/root/usr/share/rpcd/acl.d/luci-app-adguardhome.json b/root/usr/share/rpcd/acl.d/luci-app-adguardhome.json +index 748778a..33ba1fd 100644 +--- a/root/usr/share/rpcd/acl.d/luci-app-adguardhome.json ++++ b/root/usr/share/rpcd/acl.d/luci-app-adguardhome.json +@@ -1,17 +1,25 @@ + { +- "luci-app-adguardhome": { +- "description": "Grant permissions for the AdGuard Home LuCI app", +- "read": { +- "file": { +- "/usr/bin/AdGuardHome --version": [ "exec" ] +- }, +- "ubus": { +- "service": [ "list" ] +- }, +- "uci": [ "adguardhome" ] +- }, +- "write": { +- "uci": [ "adguardhome" ] +- } +- } ++ "luci-app-adguardhome": { ++ "description": "Grant permissions for the AdGuard Home LuCI app", ++ "read": { ++ "file": { ++ "/tmp/AdGuardHome_update.log": [ "read" ], ++ "/var/run/update_core": [ "stat" ], ++ "/var/run/update_core_error": [ "stat" ], ++ "/var/run/AdGredir": [ "read" ], ++ "/var/run/AdGserverdis": [ "read" ], ++ "/usr/bin/AdGuardHome --version": [ "exec" ] ++ }, ++ "ubus": { ++ "service": [ "list" ] ++ }, ++ "uci": [ "adguardhome" ] ++ }, ++ "write": { ++ "file": { ++ "/usr/share/AdGuardHome/update_core.sh": [ "exec" ] ++ }, ++ "uci": [ "adguardhome" ] ++ } ++ } + } diff --git a/luci-app-adguardhome/patches/03_config_js.patch b/luci-app-adguardhome/patches/03_config_js.patch new file mode 100644 index 000000000..465075fb8 --- /dev/null +++ b/luci-app-adguardhome/patches/03_config_js.patch @@ -0,0 +1,456 @@ +From fc3e12a5860b5da4230293d7e6dc4f3b24b32b23 Mon Sep 17 00:00:00 2001 +From: Jackie264 <125248570+Jackie264@users.noreply.github.com> +Date: Sat, 4 Jul 2026 14:19:51 +0800 +Subject: [PATCH] Enhance AdGuard Home config with core update options + +Updated service status indicators and added core update functionality. +--- + .../resources/view/adguardhome/config.js | 369 +++++++++++++++++- + 1 file changed, 364 insertions(+), 5 deletions(-) + +diff --git a/htdocs/luci-static/resources/view/adguardhome/config.js b/htdocs/luci-static/resources/view/adguardhome/config.js +index 6dc2d45..12fc3ab 100644 +--- a/htdocs/luci-static/resources/view/adguardhome/config.js ++++ b/htdocs/luci-static/resources/view/adguardhome/config.js +@@ -5,6 +5,7 @@ + 'require fs'; + 'require poll'; + 'require rpc'; ++'require uci'; + 'require view'; + + const DEFAULT_CONFIG_FILE = '/etc/adguardhome/adguardhome.yaml'; +@@ -20,10 +21,11 @@ const PATH_REGEX = new RegExp('^/etc(/[^/]+)?/?$'); + + const POLL_INTERVAL = 5; + +-const RUNNING_SPAN = `${_('Running')}`; +-const NOT_RUNNING_SPAN = `${_('Not running')}`; ++const RUNNING_SPAN = `${_('Running')}`; ++const NOT_RUNNING_SPAN = `${_('Not running')}`; + + const STORAGE_KEY = 'luci-app-adguardhome'; ++const STORAGE_KEY_CORE = 'luci-app-adguardhome_core_update'; + + function getServiceInfo(name) { + const fn = rpc.declare({ +@@ -61,7 +63,7 @@ async function getVersion() { + return version; + } catch (e) { + console.error(e); +- return 'unknown version'; ++ return ''; + } + } + +@@ -106,10 +108,33 @@ return view.extend({ + return Promise.all([ + getStatus(), + getVersion(), ++ uci.load('adguardhome').then(() => { ++ const sections = uci.sections('adguardhome', 'adguardhome'); ++ const sec = sections.length > 0 ? sections[0] : {}; ++ const configFile = sec.config_file || DEFAULT_CONFIG_FILE; ++ return fs.read(configFile).catch(() => null); ++ }) + ]); + }, + +- async render([isRunning, version]) { ++ async render([isRunning, version, yamlContent]) { ++ // If there is a version number, it means that the kernel exists. If there is no version number, it does not exist. ++ const coreExists = Boolean(version); ++ let dnsPort = '53'; ++ if (yamlContent) { ++ const portMatches = yamlContent.match(/port:\s*(\d+)/g); ++ if (portMatches && portMatches.length >= 2) { ++ const actualPort = portMatches[1].match(/\d+/); ++ if (actualPort) { ++ dnsPort = actualPort[0]; ++ } ++ } ++ } ++ ++ // 💡 Dynamic security value: UI rendering requires httpport to generate jump links ++ const sections = uci.sections('adguardhome', 'adguardhome'); ++ const savedHttpPort = (sections.length > 0 && sections[0].httpport) ? sections[0].httpport : '3008'; ++ + const map = new form.Map('adguardhome', _('AdGuard Home')); + + const statusSect = map.section(form.TypedSection, 'status'); +@@ -117,7 +142,8 @@ return view.extend({ + statusSect.cfgsections = () => ['status_section']; + + const versionOpt = statusSect.option(form.DummyValue, '_version', _('Version')); +- versionOpt.cfgvalue = () => version; ++ versionOpt.cfgvalue = () => version || `${_('Not installed')}`; ++ versionOpt.rawhtml = true; + + const statusOpt = statusSect.option(form.DummyValue, '_status', _('Service Status')); + statusOpt.rawhtml = true; +@@ -132,6 +158,12 @@ return view.extend({ + _('File System Access'), + _('Files and directories that AdGuard Home should have read-only or read-write access to.'), + ); ++ mainSect.tab('dns_redirect', _('Services Settings')); ++ mainSect.tab( ++ 'core_update', ++ _('Core Update'), ++ _('Settings and operations for updating the AdGuardHome core binary.') ++ ); + mainSect.tab( + 'advanced', + _('Advanced Settings'), +@@ -139,6 +171,20 @@ return view.extend({ + ' ' + _('Modify at your own risk.'), + ); + ++ // ==== Move here: global switch ==== ++ const enabledOpt = mainSect.taboption( ++ 'general', ++ form.Flag, ++ 'enabled', ++ _('Enable') ++ ); ++ enabledOpt.default = '0'; ++ enabledOpt.rmempty = false; ++ // If the kernel does not exist, additional warning prompts will guide users to turn on the service to download automatically. ++ if (!coreExists) { ++ enabledOpt.description = `${_('Core binary not found. Enable the service to trigger an automatic download.')}`; ++ } ++ + const configFileOpt = mainSect.taboption( + 'general', + form.Value, +@@ -204,6 +250,21 @@ return view.extend({ + advSettingsOpt.remove = () => {}; + advSettingsOpt.write = (_, value) => sessionStorage.setItem(STORAGE_KEY, value); + ++ // ==== New: The switch of Core Update on General tab ==== ++ const coreUpdateToggleOpt = mainSect.taboption( ++ 'general', ++ form.Flag, ++ 'enable_core_update', ++ _('Core Update'), ++ _('Show the tab and settings for updating the AdGuardHome core binary.') ++ ); ++ coreUpdateToggleOpt.default = '0'; ++ coreUpdateToggleOpt.rmempty = false; ++ coreUpdateToggleOpt.load = () => sessionStorage.getItem(STORAGE_KEY_CORE) || '0'; ++ coreUpdateToggleOpt.remove = () => {}; ++ coreUpdateToggleOpt.write = (_, value) => sessionStorage.setItem(STORAGE_KEY_CORE, value); ++ // ========================================== ++ + mainSect.taboption('jail', form.DynamicList, 'jail_mount', _('Read-only access')); + mainSect.taboption('jail', form.DynamicList, 'jail_mount_rw', _('Read-write access')); + +@@ -251,11 +312,309 @@ return view.extend({ + memLimitOpt.placeholder = DEFAULT_GOMEMLIMIT; + memLimitOpt.retain = true; + ++ // 💡 1. Extract the real listening address and port directly from YAML ++ let realHttpAddress = '0.0.0.0:3008'; ++ if (yamlContent) { ++ // Match the address: field below http: in YAML ++ const addrMatch = yamlContent.match(/address:\s*([^\s]+)/); ++ if (addrMatch && addrMatch[1]) { ++ realHttpAddress = addrMatch[1]; ++ } ++ } ++ ++ // 💡 2. Analyze the real IP and port used by the jump button ++ let linkIp = window.location.hostname; ++ let linkPort = '3008'; ++ const addrParts = realHttpAddress.split(':'); ++ if (addrParts.length >= 2) { ++ linkPort = addrParts.pop(); ++ let ipPart = addrParts.join(':').replace(/\[|\]/g, ''); ++ // If the binding is a full zero address, the IP accessed by the current router will be returned. ++ if (ipPart !== '0.0.0.0' && ipPart !== '') { ++ linkIp = ipPart; ++ } ++ } ++ ++ // 💡 3. Upgrade the original httpport to http_address ++ const isServiceEnabled = sections.length > 0 && sections[0].enabled === '1'; ++ const disabledHint = _('Service is disabled. Please go to "General Settings" to enable it.'); ++ ++ // 💡 4. WebUI button HTML to construct intelligent state ++ const webuiBtnHtml = isServiceEnabled ++ ? `${_('Open AdGuardHome WebUI')}` ++ : ` ++ ${_('Open AdGuardHome WebUI')} ++ `; ++ ++ // 💡 5. Rendering components ++ const httpAddressOpt = mainSect.taboption( ++ 'dns_redirect', ++ form.Value, ++ 'http_address', ++ _('WebUI listener (address:port)'), ++ _('Bind to specific interface:port (e.g., 0.0.0.0:3008). Leave as 0.0.0.0 to listen on all interfaces.') + ++ `
${webuiBtnHtml}` ++ ); ++ httpAddressOpt.placeholder = '0.0.0.0:3008'; ++ httpAddressOpt.default = '0.0.0.0:3008'; ++ httpAddressOpt.datatype = 'hostport'; ++ httpAddressOpt.rmempty = false; ++ ++ // 🎯 Overwrite the default reading UCI behavior and forcibly echo the real value in YAML ++ httpAddressOpt.cfgvalue = function(section_id) { ++ return realHttpAddress; ++ }; ++ ++ // ==== Add the password modification function below the WebUI port ==== ++ const isPasswordEmpty = yamlContent ? /password:[ \t]*(\r?\n|$)/.test(yamlContent) : false; ++ const hashPassOpt = mainSect.taboption( ++ 'dns_redirect', ++ form.Value, ++ 'hashpass', ++ _('Change password'), ++ _('Enter the password here. Click the Load calculate Module button below and go on.') + ++ '
' ++ ); ++ hashPassOpt.default = ''; ++ hashPassOpt.datatype = 'string'; ++ hashPassOpt.password = true; ++ hashPassOpt.rmempty = true; ++ hashPassOpt.placeholder = isPasswordEmpty ? _('Please create a new password.') : ''; ++ hashPassOpt.cfgvalue = function(section_id) { ++ return ''; ++ }; ++ ++ const redirectOpt = mainSect.taboption( ++ 'dns_redirect', ++ form.ListValue, ++ 'redirect', ++ `${dnsPort} ` + _('Redirect'), ++ _('AdGuardHome redirect mode') ++ ); ++ redirectOpt.value('none', _('No redirect')); ++ redirectOpt.value('dnsmasq-upstream', _('As the upstream server of dnsmasq')); ++ redirectOpt.value('redirect', _('Redirect port 53 to AdGuardHome')); ++ redirectOpt.value('exchange', _('Use port 53 to replace dnsmasq')); ++ redirectOpt.default = 'none'; ++ redirectOpt.rmempty = false; ++ // ========================================== ++ ++ // ======== Core Update Control content ======== ++ const coreVersionOpt = mainSect.taboption( ++ 'core_update', ++ form.ListValue, ++ 'core_version', ++ _('Core Branch'), ++ _('Select the branch for the core binary update.') ++ ); ++ coreVersionOpt.value('latest', _('Latest Version')); ++ coreVersionOpt.value('beta', _('Beta Version')); ++ coreVersionOpt.default = 'latest'; ++ coreVersionOpt.depends('enable_core_update', '1'); ++ coreVersionOpt.retain = true; ++ ++ const coreUrlOpt = mainSect.taboption( ++ 'core_update', ++ form.ListValue, ++ 'update_url', ++ _('Update URL'), ++ _('Select the download link for the core update.') ++ ); ++ coreUrlOpt.value('https://static.adtidy.org/adguardhome/release/AdGuardHome_linux_${Arch}.tar.gz', _('Official Mirror (AdTidy - Recommended)')); ++ coreUrlOpt.value('https://github.com/AdguardTeam/AdGuardHome/releases/download/${Cloud_Version}/AdGuardHome_linux_${Arch}.tar.gz', _('GitHub Releases (Original)')); ++ coreUrlOpt.default = 'https://static.adtidy.org/adguardhome/release/AdGuardHome_linux_${Arch}.tar.gz'; ++ coreUrlOpt.rmempty = false; ++ coreUrlOpt.depends('enable_core_update', '1'); ++ coreUrlOpt.retain = true; ++ ++ const updateActionOpt = mainSect.taboption( ++ 'core_update', ++ form.DummyValue, ++ '_update_action', ++ _('Action') ++ ); ++ updateActionOpt.rawhtml = true; ++ updateActionOpt.cfgvalue = () => ` ++
++ ++ ++
++ ++ `; ++ updateActionOpt.depends('enable_core_update', '1'); ++ // ========================================== ++ + const rendered = await map.render(); + + const statusNode = map.findElement('data-field', statusOpt.cbid('status_section')); + poll.add(updateStatus(statusNode), POLL_INTERVAL); + ++ // ========== Update the status and polling logic ========= ++ let updatePollId = null; ++ ++ function startLogPolling() { ++ if (updatePollId) clearInterval(updatePollId); ++ const pollAction = () => { ++ const btnU = document.getElementById('btn-agh-update'); ++ const btnF = document.getElementById('btn-agh-force'); ++ const logC = document.getElementById('agh-update-log-container'); ++ const logT = document.getElementById('agh-update-log'); ++ ++ if (btnU) btnU.disabled = true; ++ if (btnF) btnF.style.display = 'inline-block'; ++ if (logC) logC.style.display = 'block'; ++ ++ fs.read('/tmp/AdGuardHome_update.log').then((txt) => { ++ if (txt && logT) { ++ logT.value = txt; ++ logT.scrollTop = logT.scrollHeight; ++ } ++ }).catch(() => {}); ++ ++ Promise.all([ ++ fs.stat('/var/run/update_core').catch(() => null), ++ fs.stat('/var/run/update_core_done').catch(() => null), ++ fs.stat('/var/run/update_core_error').catch(() => null) ++ ]).then(([isCore, isDone, isError]) => { ++ if (isDone) { ++ clearInterval(updatePollId); ++ fs.remove('/var/run/update_core_done').catch(() => {}); ++ if (btnU) { ++ btnU.disabled = false; ++ btnU.textContent = _('Updated'); ++ } ++ } else if (isError) { ++ clearInterval(updatePollId); ++ if (btnU) { ++ btnU.disabled = false; ++ btnU.textContent = _('Failed'); ++ } ++ } else if (!isCore && !isDone && !isError) { ++ clearInterval(updatePollId); ++ if (btnU) { ++ btnU.disabled = false; ++ btnU.textContent = _('Already up-to-date'); ++ } ++ } ++ }); ++ }; ++ ++ pollAction(); ++ updatePollId = setInterval(pollAction, 1500); ++ } ++ ++ function applyUpdate(isForce) { ++ const btnU = document.getElementById('btn-agh-update'); ++ const logC = document.getElementById('agh-update-log-container'); ++ const logT = document.getElementById('agh-update-log'); ++ if (btnU) { ++ btnU.textContent = _('Checking...'); ++ btnU.disabled = true; ++ } ++ ++ if (logC) logC.style.display = 'block'; ++ if (logT) logT.value = _('Checking and preparing...\n'); ++ ++ map.save().then(() => { ++ const arg = isForce ? 'force' : ''; ++ fs.exec('/usr/share/AdGuardHome/update_core.sh', [arg]).catch((err) => { ++ console.error('Failed to trigger update script:', err); ++ }); ++ startLogPolling(); ++ }).catch((err) => { ++ console.error('Config save failed:', err); ++ if (btnU) { ++ btnU.textContent = _('Save Failed'); ++ btnU.disabled = false; ++ } ++ }); ++ } ++ ++ rendered.addEventListener('click', (e) => { ++ if (e.target && e.target.id === 'btn-agh-update') { ++ e.preventDefault(); ++ applyUpdate(false); ++ } else if (e.target && e.target.id === 'btn-agh-force') { ++ e.preventDefault(); ++ applyUpdate(true); ++ } ++ // ==== Handle front-end hash encryption logic ==== ++ else if (e.target && e.target.id === 'btn-agh-calc-hash') { ++ e.preventDefault(); ++ const btn = e.target; ++ ++ // Dynamically find the password input box on the page (matching elements with the suffix .hashpass) ++ const inputs = rendered.querySelectorAll('input[type="text"], input[type="password"]'); ++ let passInput = null; ++ for (const el of inputs) { ++ if (el.id && el.id.endsWith('.hashpass')) { ++ passInput = el; ++ break; ++ } ++ } ++ ++ if (!passInput) return; ++ ++ // 1. If JS has not been loaded, it will be loaded dynamically. ++ if (typeof window.TwinBcrypt === 'undefined') { ++ btn.disabled = true; ++ btn.textContent = _('Loading...'); ++ ++ const script = document.createElement('script'); ++ script.src = L.resource('view/adguardhome/twin-bcrypt.min.js'); ++ script.type = 'text/javascript'; ++ ++ script.onload = () => { ++ btn.textContent = _('Click here to Calculate'); ++ btn.disabled = false; ++ }; ++ script.onerror = () => { ++ btn.textContent = _(' ... Load Error ... '); ++ btn.disabled = false; ++ }; ++ document.head.appendChild(script); ++ } ++ // 2. If JS has been loaded, the calculation will be executed. ++ else { ++ if (passInput.value) { ++ // Prevent repeated hashing of hashed strings (starting with $2a$ or $2y$) ++ if (passInput.value.startsWith('$2a$') || passInput.value.startsWith('$2y$')) { ++ btn.textContent = _('Calculation already DONE !'); ++ return; ++ } ++ ++ const hash = window.TwinBcrypt.hashSync(passInput.value); ++ passInput.value = hash; ++ ++ // Manually trigger native input and change events ++ passInput.dispatchEvent(new Event('input', { bubbles: true })); ++ passInput.dispatchEvent(new Event('change', { bubbles: true })); ++ ++ btn.textContent = _(' ... Click Save/Apply ↘️ ... '); ++ } else { ++ btn.textContent = _(' ... Nothing inputted yet ... '); ++ } ++ } ++ } ++ // ========================================== ++ }); ++ ++ Promise.all([ ++ fs.stat('/var/run/update_core').catch(() => null), ++ fs.stat('/var/run/update_core_error').catch(() => null) ++ ]).then(([isCore, isError]) => { ++ if (isCore || isError) { ++ const btnU = document.getElementById('btn-agh-update'); ++ if (btnU) btnU.textContent = _('Checking...'); ++ startLogPolling(); ++ } ++ }); ++ // ========================================== ++ + return rendered; + }, + }); diff --git a/luci-app-adguardhome/patches/04_etc_config.patch b/luci-app-adguardhome/patches/04_etc_config.patch new file mode 100644 index 000000000..51eaa8138 --- /dev/null +++ b/luci-app-adguardhome/patches/04_etc_config.patch @@ -0,0 +1,24 @@ +From 04b0fc3af8ce6b1277143093f1038493bfb3a3cd Mon Sep 17 00:00:00 2001 +From: Jackie264 <125248570+Jackie264@users.noreply.github.com> +Date: Mon, 1 Jun 2026 16:02:48 +0800 +Subject: [PATCH] Add configuration options for AdGuard Home + +--- + root/etc/config/adguardhome | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/root/etc/config/adguardhome b/root/etc/config/adguardhome +index 92d86e1..598f316 100644 +--- a/root/etc/config/adguardhome ++++ b/root/etc/config/adguardhome +@@ -7,6 +7,10 @@ config adguardhome 'config' + option user 'adguardhome' + option group 'adguardhome' + option verbose '0' ++ option enabled '0' ++ option redirect 'none' ++ option core_version 'latest' ++ option update_url 'https://static.adtidy.org/adguardhome/release/AdGuardHome_linux_${Arch}.tar.gz' + + # Files and directories that AdGuard Home has read-only access to + # list jail_mount '/etc/ssl/adguardhome.crt' diff --git a/luci-app-adguardhome/patches/05_nft_tpl.patch b/luci-app-adguardhome/patches/05_nft_tpl.patch new file mode 100644 index 000000000..ba42fbfb9 --- /dev/null +++ b/luci-app-adguardhome/patches/05_nft_tpl.patch @@ -0,0 +1,26 @@ +From c8fd7d003099e858306ad6801ad10cc158bab461 Mon Sep 17 00:00:00 2001 +From: Jackie264 <125248570+Jackie264@users.noreply.github.com> +Date: Fri, 29 May 2026 07:46:16 +0800 +Subject: [PATCH] Create adguardhome.nft.tpl + +--- + root/usr/share/AdGuardHome/adguardhome.nft.tpl | 10 ++++++++++ + 1 file changed, 10 insertions(+) + create mode 100644 root/usr/share/AdGuardHome/adguardhome.nft.tpl + +diff --git a/root/usr/share/AdGuardHome/adguardhome.nft.tpl b/root/usr/share/AdGuardHome/adguardhome.nft.tpl +new file mode 100644 +index 0000000..d11a9dd +--- /dev/null ++++ b/root/usr/share/AdGuardHome/adguardhome.nft.tpl +@@ -0,0 +1,10 @@ ++table inet adguardhome { ++ chain prerouting { ++ type nat hook prerouting priority dstnat; policy accept; ++ ++ iifname { __WAN_EXCLUDES__ } return ++ ++ 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/patches/06_nft.patch b/luci-app-adguardhome/patches/06_nft.patch new file mode 100644 index 000000000..3c196aa1d --- /dev/null +++ b/luci-app-adguardhome/patches/06_nft.patch @@ -0,0 +1,17 @@ +From 9510be3c2646f7c1a7701072a1eb3e9fb77f6431 Mon Sep 17 00:00:00 2001 +From: Jackie264 <125248570+Jackie264@users.noreply.github.com> +Date: Fri, 29 May 2026 07:33:01 +0800 +Subject: [PATCH] Create 99-adguardhome.nft + +--- + root/etc/nftables.d/99-adguardhome.nft | 1 + + 1 file changed, 1 insertion(+) + create mode 100644 root/etc/nftables.d/99-adguardhome.nft + +diff --git a/root/etc/nftables.d/99-adguardhome.nft b/root/etc/nftables.d/99-adguardhome.nft +new file mode 100644 +index 0000000..78819ad +--- /dev/null ++++ b/root/etc/nftables.d/99-adguardhome.nft +@@ -0,0 +1 @@ ++include "/var/etc/adguardhome.nft" diff --git a/luci-app-adguardhome/patches/07_update_core.patch b/luci-app-adguardhome/patches/07_update_core.patch new file mode 100644 index 000000000..681fb7cc7 --- /dev/null +++ b/luci-app-adguardhome/patches/07_update_core.patch @@ -0,0 +1,231 @@ +From 7a73fb81100d86127e9c27ad8f14af28e6bc66e3 Mon Sep 17 00:00:00 2001 +From: Jackie264 <125248570+Jackie264@users.noreply.github.com> +Date: Thu, 28 May 2026 14:23:18 +0800 +Subject: [PATCH] Create update_core.sh + +--- + root/usr/share/AdGuardHome/update_core.sh | 215 ++++++++++++++++++++++ + 1 file changed, 215 insertions(+) + create mode 100644 root/usr/share/AdGuardHome/update_core.sh + +diff --git a/root/usr/share/AdGuardHome/update_core.sh b/root/usr/share/AdGuardHome/update_core.sh +new file mode 100644 +index 0000000..bd80165 +--- /dev/null ++++ b/root/usr/share/AdGuardHome/update_core.sh +@@ -0,0 +1,215 @@ ++#!/bin/sh ++ ++# === Core security: self-background operation mechanism === ++if [ "$1" != "bg_run" ]; then ++ rm -f /var/run/update_core* /tmp/AdGuardHome_update.log ++ touch /var/run/update_core ++ /usr/share/AdGuardHome/update_core.sh bg_run "$1" /tmp/AdGuardHome_update.log 2>&1 & ++ exit 0 ++fi ++ ++shift ++# ========================================================== ++ ++PATH="/usr/sbin:/usr/bin:/sbin:/bin" ++binpath="/usr/bin/AdGuardHome" ++update_mode=$1 ++ ++core_version=$(uci get adguardhome.config.core_version 2>/dev/null || true) ++update_url=$(uci get adguardhome.config.update_url 2>/dev/null || true) ++ ++case "${core_version}" in ++beta) ++ core_api_url=https://api.github.com/repos/AdguardTeam/AdGuardHome/releases ++ ;; ++*) ++ core_api_url=https://api.github.com/repos/AdguardTeam/AdGuardHome/releases/latest ++ ;; ++esac ++ ++EXIT(){ ++ rm -rf /var/run/update_core /tmp/AdGuardHome_Update 2>/dev/null ++ if [ "$1" != "0" ]; then ++ touch /var/run/update_core_error ++ fi ++ exit $1 ++} ++ ++trap "EXIT 1" SIGTERM SIGINT ++ ++rm -rf /var/run/update_core_error /var/run/update_core_done 2>/dev/null ++touch /var/run/update_core ++ ++Check_Task(){ ++ running_tasks="$(ps w | grep -v grep | grep 'AdGuardHome' | grep 'update_core' | wc -l)" ++ case $1 in ++ force) ++ echo "Force update requested" ++ echo "Killing ${running_tasks} running tasks ..." ++ ps w | grep -v grep | grep -v $$ | grep 'AdGuardHome' | grep 'update_core' | awk '{print $1}' | xargs kill -9 2>/dev/null ++ ;; ++ *) ++ [ "${running_tasks}" -gt 2 ] && echo -e "There are ${running_tasks} update tasks already running. Please wait or stop them manually." && EXIT 2 ++ ;; ++ esac ++} ++ ++Check_Downloader() { ++ if command -v curl >/dev/null 2>&1; then ++ PKG="curl" ++ return ++ fi ++ ++ if command -v wget >/dev/null 2>&1; then ++ PKG="wget" ++ return ++ fi ++ ++ echo "Neither curl nor wget is installed, cannot check updates!" >&2 ++ EXIT 1 ++} ++ ++Check_Updates(){ ++ Check_Downloader ++ case "${PKG}" in ++ curl) ++ Downloader="curl -L -k -o" ++ _Downloader="curl -s" ++ ;; ++ wget) ++ Downloader="wget --no-check-certificate -T 5 -O" ++ _Downloader="wget -q -O -" ++ ;; ++ esac ++ echo "[${PKG}] Checking for updates ..." ++ Cloud_Version="$(${_Downloader} ${core_api_url} 2>/dev/null | grep 'tag_name' | egrep -o "v[0-9].+[0-9.]" | awk 'NR==1')" ++ if [ -z "${Cloud_Version}" ]; then ++ echo "Failed to check updates, please check network." >&2 ++ EXIT 1 ++ fi ++ ++ if [ -f "${binpath}" ]; then ++ Current_Version="$(${binpath} --version 2>/dev/null | egrep -o "v[0-9].+[0-9]" | sed -r 's/(.*), c(.*)/\1/')" ++ else ++ Current_Version="unknown" ++ fi ++ [ -z "${Current_Version}" ] && Current_Version="unknown" ++ ++ echo "Binary path: ${binpath%/*}" ++ echo "Current version: ${Current_Version}" ++ echo "Latest version: ${Cloud_Version}" ++ ++ if [ ! "${Cloud_Version}" = "${Current_Version}" ] || [ "$1" = force ]; then ++ Update_Core || EXIT 1 ++ else ++ echo "Already up to date." ++ EXIT 0 ++ fi ++ EXIT 0 ++} ++ ++Update_Core(){ ++ rm -rf "/tmp/AdGuardHome_Update" > /dev/null 2>&1 ++ mkdir -p "/tmp/AdGuardHome_Update" || { echo "Unable to create a temporary directory"; EXIT 1; } ++ ++ GET_Arch ++ eval link="${update_url}" ++ echo "Download link: ${link}" ++ echo "File name: ${link##*/}" ++ echo "Downloading AdGuardHome core ..." ++ ++ if ! $Downloader "/tmp/AdGuardHome_Update/${link##*/}" "${link}"; then ++ echo "Download failed." ++ rm -rf "/tmp/AdGuardHome_Update" ++ EXIT 1 ++ fi ++ ++ if [ "${link##*.}" = "gz" ]; then ++ echo "Extracting AdGuardHome ..." ++ if ! tar -zxf "/tmp/AdGuardHome_Update/${link##*/}" -C "/tmp/AdGuardHome_Update/"; then ++ echo "Extraction failed!" ++ rm -rf "/tmp/AdGuardHome_Update" ++ EXIT 1 ++ fi ++ if [ ! -e "/tmp/AdGuardHome_Update/AdGuardHome/AdGuardHome" ]; then ++ echo "Extraction failed: binary not found!" ++ rm -rf "/tmp/AdGuardHome_Update" ++ EXIT 1 ++ fi ++ downloadbin="/tmp/AdGuardHome_Update/AdGuardHome/AdGuardHome" ++ else ++ downloadbin="/tmp/AdGuardHome_Update/${link##*/}" ++ fi ++ ++ chmod +x "${downloadbin}" 2>/dev/null || true ++ echo "Core size: $(awk 'BEGIN{printf "%.2fMB\n",'$((`ls -l $downloadbin | awk '{print $5}'`))'/1000000}')" ++ ++ /etc/init.d/AdGuardHome stop > /dev/null 2>&1 ++ echo "Moving AdGuardHome binary to ${binpath%/*} ..." ++ ++ if ! mv -f "${downloadbin}" "${binpath}"; then ++ echo -e "The core movement failed! \nIt may be caused by insufficient space." ++ rm -rf "/tmp/AdGuardHome_Update" ++ EXIT 1 ++ fi ++ ++ rm -rf /tmp/AdGuardHome_Update ++ chmod +x ${binpath} ++ echo "Restarting AdGuardHome service ..." ++ /etc/init.d/adguardhome restart > /dev/null 2>&1 ++ ++ echo "AdGuardHome core updated successfully!" ++ touch /var/run/update_core_done ++ EXIT 0 ++} ++ ++GET_Arch() { ++ Archt="$(uname -m)" ++ case "${Archt}" in ++ i386|i686) ++ Arch="i386" ++ ;; ++ x86_64|amd64) ++ Arch="amd64" ++ ;; ++ mipsel|mipsel*) ++ Arch="mipsle_softfloat" ++ ;; ++ mips|mips*) ++ Arch="mips_softfloat" ++ ;; ++ mips64el) ++ Arch="mips64le_softfloat" ++ ;; ++ mips64) ++ Arch="mips64_softfloat" ++ ;; ++ armv5*|armv5l|armv5tel) ++ Arch="armv5" ++ ;; ++ armv6*|armv6l) ++ Arch="armv6" ++ ;; ++ armv7*|armv7l) ++ Arch="armv7" ++ ;; ++ arm|armhf) ++ Arch="armv7" ++ ;; ++ aarch64) ++ Arch="arm64" ++ ;; ++ *) ++ echo "Unsupported architecture: [${Archt}]" ++ EXIT 1 ++ esac ++ echo "Detected architecture: ${Arch}" ++} ++ ++ ++main(){ ++ Check_Task ${update_mode} ++ Check_Updates ${update_mode} ++} ++ ++main diff --git a/luci-app-adguardhome/patches/08_control.patch b/luci-app-adguardhome/patches/08_control.patch new file mode 100644 index 000000000..f774b0930 --- /dev/null +++ b/luci-app-adguardhome/patches/08_control.patch @@ -0,0 +1,239 @@ +From 0e2440feea5f51d187189aae41d45d15102773bc Mon Sep 17 00:00:00 2001 +From: Jackie264 <125248570+Jackie264@users.noreply.github.com> +Date: Sun, 31 May 2026 09:38:02 +0800 +Subject: [PATCH] Create control.sh + +--- + root/usr/share/AdGuardHome/control.sh | 223 ++++++++++++++++++++++++++ + 1 file changed, 223 insertions(+) + create mode 100644 root/usr/share/AdGuardHome/control.sh + +diff --git a/root/usr/share/AdGuardHome/control.sh b/root/usr/share/AdGuardHome/control.sh +new file mode 100644 +index 0000000..1686183 +--- /dev/null ++++ b/root/usr/share/AdGuardHome/control.sh +@@ -0,0 +1,223 @@ ++#!/bin/sh ++# /usr/share/AdGuardHome/control.sh ++. /usr/share/AdGuardHome/helper.sh ++ ++ENABLED="$1" ++CONFIGURATION="adguardhome" ++NFT_RULES_TPL="/usr/share/AdGuardHome/adguardhome.nft.tpl" ++NFT_RULES_FILE="/var/etc/adguardhome.nft" ++NFT_TABLE="adguardhome" ++ ++set_nft_redirect() { ++ local port="$1" ++ [ -z "$port" ] && return 1 ++ [ ! -f "$NFT_RULES_TPL" ] && return 1 ++ ++ local wan_section_name ++ wan_section_name=$(uci show firewall | awk -F'.' '/\.name='\''wan'\''$/ {print $2}') ++ local WAN_IFS="" ++ if [ -n "$wan_section_name" ]; then ++ WAN_IFS=$(uci get firewall.$wan_section_name.network 2>/dev/null | xargs) ++ fi ++ ++ local WAN_NFT_SET="" ++ for IF in $WAN_IFS; do ++ WAN_NFT_SET="${WAN_NFT_SET} \"$IF\"," ++ done ++ WAN_NFT_SET=$(echo "$WAN_NFT_SET" | sed 's/,$//') ++ ++ sed -e "s/__WAN_EXCLUDES__/${WAN_NFT_SET}/g" -e "s/__AGH_PORT__/${port}/g" "$NFT_RULES_TPL" > "$NFT_RULES_FILE" ++ ++ nft delete table inet "$NFT_TABLE" 2>/dev/null ++ fw4 reload ++ logger -t adguardhome "nft table $NFT_TABLE applied on port $port, WAN excludes: $WAN_NFT_SET" ++} ++ ++clear_nft_redirect() { ++ # 优化:只有当表存在时才清理和重载防火墙,避免无意义的性能开销 ++ if nft list table inet "$NFT_TABLE" >/dev/null 2>&1; then ++ [ -f "$NFT_RULES_FILE" ] && > "$NFT_RULES_FILE" ++ nft delete table inet "$NFT_TABLE" 2>/dev/null ++ fw4 reload ++ logger -t adguardhome "nft table $NFT_TABLE cleared" ++ fi ++} ++ ++set_forward_dnsmasq() { ++ local PORT="$1" ++ local addr="127.0.0.1#$PORT" ++ local OLD_SERVER ++ 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" ++ 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" ++ local addr="127.0.0.1#$OLD_PORT" ++ local OLD_SERVER ++ 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 ++ local addrlist ++ addrlist="$(uci get dhcp.@dnsmasq[0].server 2>/dev/null)" ++ if [ -z "$addrlist" ]; then ++ local 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 ++ configpath=$(uci -q get adguardhome.config.config_file) ++ [ -z "$configpath" ] && configpath="/etc/adguardhome/adguardhome.yaml" ++ ++ local adguardhome_PORT dnsmasq_port ++ adguardhome_PORT=$(config_editor "dns.port" "" "$configpath" "1") ++ dnsmasq_port=$(uci get dhcp.@dnsmasq[0].port 2>/dev/null) ++ [ -z "$dnsmasq_port" ] && dnsmasq_port="53" ++ ++ if [ "$dnsmasq_port" = "$adguardhome_PORT" ]; then ++ [ "$dnsmasq_port" = "53" ] && dnsmasq_port="1745" ++ elif [ "$dnsmasq_port" = "53" ]; then ++ return ++ fi ++ ++ config_editor "dns.port" "$dnsmasq_port" "$configpath" ++ uci set dhcp.@dnsmasq[0].port="53" ++ uci commit dhcp ++ ++ /etc/init.d/dnsmasq restart ++ agh_reload ++} ++ ++use_port53() { ++ local configpath ++ configpath=$(uci -q get adguardhome.config.config_file) ++ [ -z "$configpath" ] && configpath="/etc/adguardhome/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 flag=0 ++ configpath=$(uci -q get adguardhome.config.config_file) ++ [ -z "$configpath" ] && configpath="/etc/adguardhome/adguardhome.yaml" ++ ++ 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 enabled="$1" ++ local configpath ++ configpath=$(uci -q get adguardhome.config.config_file) ++ [ -z "$configpath" ] && configpath="/etc/adguardhome/adguardhome.yaml" ++ ++ local adguardhome_PORT redirect old_redirect old_port old_enabled ++ adguardhome_PORT="$(config_editor "dns.port" "" "$configpath" "1")" ++ [ -z "$adguardhome_PORT" ] && adguardhome_PORT="0" ++ ++ redirect=$(uci -q get adguardhome.config.redirect || echo "none") ++ ++ # 【核心修复】:从内存文件中读取旧状态,彻底放弃污染 UCI ++ if [ -f /var/run/adguardhome.state ]; then ++ . /var/run/adguardhome.state ++ else ++ old_redirect="none" ++ old_port="0" ++ old_enabled="0" ++ fi ++ ++ uci get dhcp.@dnsmasq[0].port >/dev/null 2>&1 || uci set dhcp.@dnsmasq[0].port="53" ++ 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 ++ ++ # 【核心修复】:将当前状态覆盖写入内存文件,轻量、疾速、安全! ++ cat < /var/run/adguardhome.state ++old_redirect="$redirect" ++old_port="$adguardhome_PORT" ++old_enabled="$enabled" ++EOF ++ ++ 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 "$ENABLED" diff --git a/luci-app-adguardhome/patches/09_watchconfig.patch b/luci-app-adguardhome/patches/09_watchconfig.patch new file mode 100644 index 000000000..bed80b07b --- /dev/null +++ b/luci-app-adguardhome/patches/09_watchconfig.patch @@ -0,0 +1,34 @@ +From b1391765b484c6f50feba7f09bdf50d3aa443356 Mon Sep 17 00:00:00 2001 +From: Jackie264 <125248570+Jackie264@users.noreply.github.com> +Date: Fri, 29 May 2026 14:27:56 +0800 +Subject: [PATCH] Add watchconfig.sh to monitor AdGuardHome config + +--- + root/usr/share/AdGuardHome/watchconfig.sh | 18 ++++++++++++++++++ + 1 file changed, 18 insertions(+) + create mode 100644 root/usr/share/AdGuardHome/watchconfig.sh + +diff --git a/root/usr/share/AdGuardHome/watchconfig.sh b/root/usr/share/AdGuardHome/watchconfig.sh +new file mode 100644 +index 0000000..c7267bf +--- /dev/null ++++ b/root/usr/share/AdGuardHome/watchconfig.sh +@@ -0,0 +1,18 @@ ++#!/bin/sh ++# /usr/share/AdGuardHome/watchconfig.sh ++ ++PATH="/usr/sbin:/usr/bin:/sbin:/bin" ++ ++configpath=$(uci -q get adguardhome.config.config_file) ++[ -z "$configpath" ] && configpath="/etc/adguardhome/adguardhome.yaml" ++ ++while : ++do ++ sleep 10 ++ if [ -f "$configpath" ]; then ++ /etc/init.d/adguardhome do_redirect 1 ++ break ++ fi ++done ++ ++exit 0 diff --git a/luci-app-adguardhome/patches/10_yaml_helper.patch b/luci-app-adguardhome/patches/10_yaml_helper.patch new file mode 100644 index 000000000..e1390a628 --- /dev/null +++ b/luci-app-adguardhome/patches/10_yaml_helper.patch @@ -0,0 +1,38 @@ +From 864c042e5cdbe61922f917d1785bf7342b3fc90d Mon Sep 17 00:00:00 2001 +From: Jackie264 <125248570+Jackie264@users.noreply.github.com> +Date: Sun, 31 May 2026 08:25:55 +0800 +Subject: [PATCH] Create helper.sh for YAML reading and writing + +Added a helper script for YAML configuration management. +--- + root/usr/share/AdGuardHome/helper.sh | 21 +++++++++++++++++++++ + 1 file changed, 21 insertions(+) + create mode 100644 root/usr/share/AdGuardHome/helper.sh + +diff --git a/root/usr/share/AdGuardHome/helper.sh b/root/usr/share/AdGuardHome/helper.sh +new file mode 100644 +index 0000000..35f0198 +--- /dev/null ++++ b/root/usr/share/AdGuardHome/helper.sh +@@ -0,0 +1,21 @@ ++#!/bin/sh ++# Auxiliary functions specially used for reading and writing YAML ++config_editor() { ++ awk -v yaml="$1" -v value="$2" -v file="$3" -v ro="$4" ' ++ BEGIN{split(yaml,part,"\.");s="";i=1;l=length(part);} ++ { ++ if (match($0,s""part[i]":")) { ++ if (i==l) { ++ split($0,t,": "); ++ if (ro==""){ ++ system("sed -i '\''"FNR"c\\" t[1] ": " value "'\'' " file " >/dev/null 2>&1"); ++ } else { ++ print(t[2]); ++ } ++ exit; ++ } ++ s=s"[- ]{2}"; ++ i++; ++ } ++ }' "$3" ++} diff --git a/luci-app-adguardhome/patches/11_AGH_yaml.patch b/luci-app-adguardhome/patches/11_AGH_yaml.patch new file mode 100644 index 000000000..68812a0bf --- /dev/null +++ b/luci-app-adguardhome/patches/11_AGH_yaml.patch @@ -0,0 +1,324 @@ +From 2cc00f18ce38e8c39af30ce5381f229e3c742adf Mon Sep 17 00:00:00 2001 +From: Jackie264 <125248570+Jackie264@users.noreply.github.com> +Date: Sat, 30 May 2026 17:40:57 +0800 +Subject: [PATCH] Add initial configuration for AdGuard Home + +--- + root/etc/adguardhome/adguardhome.yaml | 308 ++++++++++++++++++++++++++ + 1 file changed, 308 insertions(+) + create mode 100644 root/etc/adguardhome/adguardhome.yaml + +diff --git a/root/etc/adguardhome/adguardhome.yaml b/root/etc/adguardhome/adguardhome.yaml +new file mode 100644 +index 0000000..f7a1a94 +--- /dev/null ++++ b/root/etc/adguardhome/adguardhome.yaml +@@ -0,0 +1,308 @@ ++http: ++ pprof: ++ port: 6060 ++ enabled: false ++ doh: ++ routes: ++ - GET /dns-query ++ - POST /dns-query ++ - GET /dns-query/{ClientID} ++ - POST /dns-query/{ClientID} ++ insecure_enabled: false ++ address: 0.0.0.0:3008 ++ session_ttl: 30d ++users: ++ - name: root ++ password: ++auth_attempts: 5 ++block_auth_min: 15 ++http_proxy: "" ++language: "" ++theme: auto ++dns: ++ bind_hosts: ++ - 0.0.0.0 ++ port: 1745 ++ anonymize_client_ip: false ++ ratelimit: 0 ++ ratelimit_subnet_len_ipv4: 24 ++ ratelimit_subnet_len_ipv6: 56 ++ ratelimit_whitelist: [] ++ refuse_any: true ++ upstream_dns: ++ - '#https://dns10.quad9.net/dns-query' ++ - 223.5.5.5 ++ - 2400:3200::1 ++ - https://dns.alidns.com/dns-query ++ upstream_dns_file: "" ++ bootstrap_dns: ++ - 119.29.29.29 ++ - 114.114.114.114 ++ fallback_dns: ++ - 119.29.29.29 ++ - 113.31.119.88 ++ - 2400:7fc0:849e:200:62fd:1de3:1c90:1 ++ upstream_mode: parallel ++ fastest_timeout: 1s ++ allowed_clients: [] ++ disallowed_clients: [] ++ blocked_hosts: ++ - version.bind ++ - id.server ++ - hostname.bind ++ trusted_proxies: ++ - 127.0.0.0/8 ++ - ::1/128 ++ cache_enabled: true ++ cache_size: 4194304 ++ cache_ttl_min: 0 ++ cache_ttl_max: 0 ++ cache_optimistic: true ++ cache_optimistic_answer_ttl: 30s ++ cache_optimistic_max_age: 12h ++ bogus_nxdomain: [] ++ aaaa_disabled: false ++ enable_dnssec: true ++ edns_client_subnet: ++ custom_ip: "" ++ enabled: true ++ use_custom: false ++ max_goroutines: 300 ++ handle_ddr: true ++ ipset: [] ++ ipset_file: "" ++ bootstrap_prefer_ipv6: false ++ upstream_timeout: 10s ++ private_networks: [] ++ use_private_ptr_resolvers: true ++ local_ptr_upstreams: ++ - 127.0.0.1:1745 ++ use_dns64: false ++ dns64_prefixes: [] ++ serve_http3: false ++ use_http3_upstreams: false ++ serve_plain_dns: true ++ hostsfile_enabled: true ++ pending_requests: ++ enabled: true ++tls: ++ enabled: false ++ server_name: "" ++ force_https: false ++ port_https: 443 ++ port_dns_over_tls: 853 ++ port_dns_over_quic: 853 ++ port_dnscrypt: 0 ++ dnscrypt_config_file: "" ++ certificate_chain: "" ++ private_key: "" ++ certificate_path: "" ++ private_key_path: "" ++ strict_sni_check: false ++querylog: ++ dir_path: "" ++ ignored: ++ - orirainbow.cc^ ++ - orirainwe.edu.kg^ ++ - coriseay.edu.kg^ ++ - google.com^ ++ interval: 7d ++ size_memory: 1000 ++ enabled: true ++ ignored_enabled: true ++ file_enabled: true ++statistics: ++ dir_path: "" ++ ignored: ++ - orirainbow.cc^ ++ - orirainwe.edu.kg^ ++ - coriseay.edu.kg^ ++ - google.com^ ++ interval: 7d ++ enabled: true ++ ignored_enabled: true ++filters: ++ - enabled: true ++ url: https://adguardteam.github.io/HostlistsRegistry/assets/filter_1.txt ++ name: AdGuard DNS filter ++ id: 1 ++ - enabled: false ++ url: https://adguardteam.github.io/HostlistsRegistry/assets/filter_2.txt ++ name: AdAway Default Blocklist ++ id: 2 ++whitelist_filters: [] ++user_rules: ++ - '@@||ntfy.sh^' ++ - '@@||pubnub.com^' ++ - '@@||clarity.ms^$important' ++ - '@@||sh-gsg.com^$important' ++ - '@@||sh-mtgc.com^$important' ++ - '@@||taobao.com^$important' ++ - '@@||tmall.com^$important' ++ - '@@||jd.com^important' ++ - '@@||flyme.cn^$important' ++ - '@@||meizu.com^$important' ++ - '@@||flydigi.com^' ++ - '@@||pv.sohu.com^$important' ++ - '@@||trafficmanager.net^' ++ - '||yify.foo^' ++ - '||www.668cbb.com/static/adScript.js' ++ - '||topnewsfeeds.net^' ++ - '||nbryb.com^' ++ - '||superbdolly.com^' ++ - '||salutationcheerlessdemote.com^' ++ - '||buildingdoodlesquare.com^' ++ - '||colonistnobilityheroic.com^' ++ - '#解除 小米 及其所有子域名的封锁;' ++ - '@@||mi.com^' ++ - '@@||mi.cn^' ++ - '@@||xiaomi.com^' ++ - '@@||miui.com^' ++ - '@@||xiaomi.net^' ++ - '@@||xiaomi-mipdx.cn.miaozhen.com^' ++ - '@@||xiaomi-dtv.m.cn.miaozhen.com^' ++ - '@@||miwifi.com^' ++ - '@@||tracker.ai.xiaomi.com^$important' ++ - '@@||tracking.miui.com^$important' ++ - '#解除 Apple 及其所有子域名的封锁;' ++ - '@@||apple.com^' ++ - '@@||apple.com.cn^' ++ - '@@||icloud.com^' ++ - '@@||icloud.com.cn^' ++ - '#解除 cloudflare 及其所有子域名的封锁;' ++ - '@@||static.cloudflareinsights.com^$important' ++ - '@@||cloudflare.com^' ++ - '@@||cloudflare-cn.com^' ++ - '@@||argotunnel.com^' ++ - '#解除 microsoft 及其所有子域名的封锁;' ++ - '@@||microsoft.com^$important' ++ - '@@||windows.com^$important' ++ - '@@||microsoftonline.com^$important' ++ - '@@||windowsupdate.com^$important' ++ - '@@||office.com^$important' ++ - '@@||live.com^$important' ++ - '@@||skype.com^$important' ++ - '@@||msn.com^$important' ++ - '@@||bing.com^$important' ++ - '@@||msftconnecttest.com^$important' ++ - '@@||res.public.onecdn.static.microsoft^$important' ++ - '@@||inference.location.live.net^$important' ++ - '@@||static.edge.microsoftapp.net^$important' ++ - '@@||ipv6.msftconnecttest.com^$important' ++ - '@@||sentry.io^$important' ++ - '@@||metrics1.data.hicloud.com^$important' ++ - '@@||otheve.beacon.qq.com^$important' ++ - '@@||app-measurement.com^$important' ++ - '@@||tmdb.org^' ++ - '@@||themoviedb.org^' ++ - /googleads.$~script,domain=~googleads.github.io ++ - /pagead/lvz? ++ - '||google.com/pagead/' ++ - '||static.doubleclick.net^$domain=youtube.com' ++ - '||youtube.com/get_midroll_' ++ - '||5hz.org^' ++ - '||evewan.com^' ++ - '||stgowan.com^' ++ - '||51gowan.com^' ++ - '||www.66s6.net/d/js/acmsd/thea5.js' ++ - '||www.66s6.net/d/js/acmsd/thea6.js' ++ - '||sogowan.com^' ++ - '||52xkdy.com^' ++ - '||ttzj666.com^' ++ - '||888dyy.cc^' ++ - '||qisaoba.com^' ++ - '||euqq.cn^' ++ - '||wkyaoye.com^' ++ - '||qisaoba.com^' ++ - '@||adobe.io^' ++ - '@||adobestats.io' ++ - '@||adobe.com^' ++ - '@||gocart-web-prod-ue1-alb-1461435473.us-east-1.elb.amazonaws.com' ++ - '@@||dig.bdurl.net^$important' ++ - '@@||android.bugly.qq.com^$important' ++ - '@@||yun-hl.3g.qq.com^$important' ++ - '@@||collector.github.com^$important' ++ - '@@||geolocation.onetrust.com^$important' ++ - '@@||google-analytics.com^$important' ++ - '@@||client-upload.cn-shanghai.log.aliyuncs.com^$important' ++ - '@@||twingate.com^' ++ - '@@||syncthing.net^' ++ - '@@||loggw-ex.alipay.com^$important' ++ - '@@||hdfans.org^' ++ - '@@||1ptba.com^' ++ - '@@||ptcafe.club^' ++ - '||nbpicture.xyz^' ++ - "" ++dhcp: ++ enabled: false ++ interface_name: "" ++ local_domain_name: lan ++ dhcpv4: ++ gateway_ip: "" ++ subnet_mask: "" ++ range_start: "" ++ range_end: "" ++ lease_duration: 86400 ++ icmp_timeout_msec: 1000 ++ options: [] ++ dhcpv6: ++ range_start: "" ++ lease_duration: 86400 ++ ra_slaac_only: false ++ ra_allow_slaac: false ++filtering: ++ blocking_ipv4: "" ++ blocking_ipv6: "" ++ blocked_services: ++ schedule: ++ time_zone: UTC ++ ids: [] ++ protection_disabled_until: null ++ safe_search: ++ enabled: false ++ bing: true ++ duckduckgo: true ++ ecosia: true ++ google: true ++ pixabay: true ++ yandex: true ++ youtube: true ++ blocking_mode: default ++ parental_block_host: family-block.dns.adguard.com ++ safebrowsing_block_host: standard-block.dns.adguard.com ++ rewrites: [] ++ safe_fs_patterns: ++ - /var/lib/adguardhome/userfilters/* ++ safebrowsing_cache_size: 1048576 ++ safesearch_cache_size: 1048576 ++ parental_cache_size: 1048576 ++ cache_time: 30 ++ filters_update_interval: 24 ++ blocked_response_ttl: 10 ++ filtering_enabled: true ++ rewrites_enabled: true ++ parental_enabled: false ++ safebrowsing_enabled: false ++ protection_enabled: true ++clients: ++ runtime_sources: ++ whois: true ++ arp: true ++ rdns: true ++ dhcp: true ++ hosts: true ++ persistent: [] ++log: ++ enabled: true ++ file: "" ++ max_backups: 0 ++ max_size: 100 ++ max_age: 3 ++ compress: false ++ local_time: false ++ verbose: false ++os: ++ group: "" ++ user: "" ++ rlimit_nofile: 0 ++schema_version: 34 diff --git a/luci-app-adguardhome/patches/12_twin-bcrypt.patch b/luci-app-adguardhome/patches/12_twin-bcrypt.patch new file mode 100644 index 000000000..43a0574fe --- /dev/null +++ b/luci-app-adguardhome/patches/12_twin-bcrypt.patch @@ -0,0 +1,23 @@ +From bc9e4fac2e0be80730df3aa345acf3dc2b7784d0 Mon Sep 17 00:00:00 2001 +From: Jackie264 <125248570+Jackie264@users.noreply.github.com> +Date: Sun, 31 May 2026 20:27:21 +0800 +Subject: [PATCH] Create twin-bcrypt.min.js + +--- + .../resources/view/adguardhome/twin-bcrypt.min.js | 7 +++++++ + 1 file changed, 7 insertions(+) + create mode 100644 htdocs/luci-static/resources/view/adguardhome/twin-bcrypt.min.js + +diff --git a/htdocs/luci-static/resources/view/adguardhome/twin-bcrypt.min.js b/htdocs/luci-static/resources/view/adguardhome/twin-bcrypt.min.js +new file mode 100644 +index 0000000..3a03268 +--- /dev/null ++++ b/htdocs/luci-static/resources/view/adguardhome/twin-bcrypt.min.js +@@ -0,0 +1,7 @@ ++/* @license ++ * Twin-Bcrypt 2.2.0 ++ * https://github.com/fpirsch/twin-bcrypt ++ * Licence: BSD-3-Clause ++ */ ++!function(r,n){"use strict";function e(r){return y[g]=t.apply(n,r),g++}function t(r){var e=[].slice.call(arguments,1);return function(){"function"==typeof r?r.apply(n,e):new Function(""+r)()}}function o(r){if(m)setTimeout(t(o,r),0);else{var n=y[r];if(n){m=!0;try{n()}finally{a(r),m=!1}}}}function a(r){delete y[r]}function i(){p=function(){var r=e(arguments);return process.nextTick(t(o,r)),r}}function u(){if(r.postMessage&&!r.importScripts){var n=!0,e=r.onmessage;return r.onmessage=function(){n=!1},r.postMessage("","*"),r.onmessage=e,n}}function f(){var n="setImmediate$"+Math.random()+"$",t=function(e){e.source===r&&"string"==typeof e.data&&0===e.data.indexOf(n)&&o(+e.data.slice(n.length))};r.addEventListener?r.addEventListener("message",t,!1):r.attachEvent("onmessage",t),p=function(){var t=e(arguments);return r.postMessage(n+t,"*"),t}}function c(){var r=new MessageChannel;r.port1.onmessage=function(r){var n=r.data;o(n)},p=function(){var n=e(arguments);return r.port2.postMessage(n),n}}function s(){var r=v.documentElement;p=function(){var n=e(arguments),t=v.createElement("script");return t.onreadystatechange=function(){o(n),t.onreadystatechange=null,r.removeChild(t),t=null},r.appendChild(t),n}}function l(){p=function(){var r=e(arguments);return setTimeout(t(o,r),0),r}}if(!r.setImmediate){var p,g=1,y={},m=!1,v=r.document,d=Object.getPrototypeOf&&Object.getPrototypeOf(r);d=d&&d.setTimeout?d:r,"[object process]"==={}.toString.call(r.process)?i():u()?f():r.MessageChannel?c():v&&"onreadystatechange"in v.createElement("script")?s():l(),d.setImmediate=p,d.clearImmediate=a}}(new Function("return this")()),function(r){"object"==typeof exports?r(exports,require("crypto")):r(self.TwinBcrypt={},self.crypto||self.msCrypto)}(function(r,n){"use strict";function e(r){for(var n=unescape(encodeURIComponent(r)),e=n.length,t=new Array(e),o=0;e>o;o++)t[o]=n.charCodeAt(o);return t}function t(r){for(var n=r.length,e=new Array(n),t=0;n>t;t++)e[t]=r.charCodeAt(t);return e}function o(r,n){for(var e,t,o=0,a="";n>o;){if(e=255&r[o++],a+=B[e>>2],e=(3&e)<<4,o>=n){a+=B[e];break}if(t=255&r[o++],e|=t>>4,a+=B[e],e=(15&t)<<2,o>=n){a+=B[e];break}t=255&r[o++],e|=t>>6,a+=B[e],a+=B[63&t]}return a}function a(r){for(var n,e,t=new Array(16),o=0,a=0;;){if(n=D[r.charCodeAt(o++)-46],e=D[r.charCodeAt(o++)-46],t[a++]=255&(n<<2|e>>4),22===o)break;n=e<<4,e=D[r.charCodeAt(o++)-46],t[a++]=255&(n|e>>2),n=e<<6,e=D[r.charCodeAt(o++)-46],t[a++]=255&(n|e)}return t}function i(r){for(var n=r.length,e=new Array(72),t=0,o=0;72>o;)e[o++]=r[t++],t===n&&(t=0);return e}function u(r,n,e){for(var t=0,o=e>>2;tt;)r[n++]=e[t++]<<24|e[t++]<<16|e[t++]<<8|e[t++]}function c(r){function n(n){for(var e=r,t=G>>2,o=t|O,f=n>>2,c=e[f]^e[t],s=e[1|f];o>t;)s^=(e[c>>>24]+e[a|c>>>16&255]^e[i|c>>>8&255])+e[u|255&c]^e[++t],c^=(e[s>>>24]+e[a|s>>>16&255]^e[i|s>>>8&255])+e[u|255&s]^e[++t];e[f]=s^e[S>>2],e[1|f]=c}function e(n){var e;for(r[L>>2]=0,r[L+4>>2]=0,e=0;M>e;e++)r[G>>2|e]^=r[(n>>2)+e];var t,o,f,c,s,l=r;for(e=0;M>e;e+=2){for(t=G>>2,o=t|O,f=L>>2,c=l[f]^l[t],s=l[1|f];o>t;)s^=(l[c>>>24]+l[a|c>>>16&255]^l[i|c>>>8&255])+l[u|255&c]^l[++t],c^=(l[s>>>24]+l[a|s>>>16&255]^l[i|s>>>8&255])+l[u|255&s]^l[++t];l[f]=s^l[S>>2],l[1|f]=c,r[G>>2|e]=l[f],r[G>>2|e+1]=c}for(e=0;T>e;e+=2){for(t=G>>2,o=t|O,f=L>>2,c=l[f]^l[t],s=l[1|f];o>t;)s^=(l[c>>>24]+l[a|c>>>16&255]^l[i|c>>>8&255])+l[u|255&c]^l[++t],c^=(l[s>>>24]+l[a|s>>>16&255]^l[i|s>>>8&255])+l[u|255&s]^l[++t];l[f]=s^l[S>>2],l[1|f]=c,r[e]=l[f],r[1|e]=c}}function t(r,n,t){for(var o=0;t>=o&&!(r>n);o++)e(R),e(j),r++;return r}var o=k>>2,a=o+256|0,i=a+256|0,u=i+256|0;return{encrypt:n,expandLoop:t}}function s(stdlib, foreign, heap) {"use asm";var HEAP32=new stdlib.Uint32Array(heap);var BLOWFISH_NUM_ROUNDS=16;var S_offset=0x0000;var S1_offset=0x0400;var S2_offset=0x0800;var S3_offset=0x0C00;var P_offset=0x1000;var P_last_offset=0x1044;var crypt_ciphertext_offset=0x1048;var LR_offset=0x01060;var password_offset=0x1068;var salt_offset=0x10b0;var P_LEN=18;var S_LEN=1024;function encrypt(offset) {offset=offset|0;var i=0;var n=0;var L=0;var R=0;var imax=0;imax=P_offset|BLOWFISH_NUM_ROUNDS<<2;L=HEAP32[offset>>2]|0;R=HEAP32[offset+4>>2]|0;L=L^HEAP32[P_offset>>2];for (i=P_offset; (i|0)<(imax|0);) {i=(i+4)>>>0;R=R^(((HEAP32[(L>>>22)>>2]>>>0) +(HEAP32[(S1_offset|(L>>>14&0x3ff))>>2]>>>0) ^(HEAP32[(S2_offset|(L>>>6&0x3ff))>>2])) +(HEAP32[(S3_offset|(L<<2&0x3ff))>>2]>>>0))^HEAP32[i>>2];i=(i+4)>>>0;L=L^(((HEAP32[(R>>>22)>>2]>>>0) +(HEAP32[(S1_offset|(R>>>14&0x3ff))>>2]>>>0) ^(HEAP32[(S2_offset|(R>>>6&0x3ff))>>2])) +(HEAP32[(S3_offset|(R<<2&0x3ff))>>2]>>>0))^HEAP32[i>>2];}HEAP32[offset>>2]=R^HEAP32[P_last_offset>>2];HEAP32[(offset+4)>>2]=L;}function expandKey(offset) {offset=offset|0;var i=0;var off=0;off=P_offset|0;for (i=0; (i|0)<(P_LEN|0); i=(i+1)|0) {HEAP32[off>>2]=HEAP32[off>>2]^HEAP32[offset>>2];offset=(offset+4)|0;off=(off+4)|0;}HEAP32[LR_offset>>2]=0;HEAP32[LR_offset+4>>2]=0;off=P_offset;for (i=0; (i|0)<(P_LEN|0); i=(i+2)|0) {encrypt(LR_offset);HEAP32[off>>2]=HEAP32[LR_offset>>2];HEAP32[off+4>>2]=HEAP32[LR_offset+4>>2];off=(off+8)|0;}off=S_offset;for (i=0; (i|0)<(S_LEN|0); i=(i+2)|0) {encrypt(LR_offset);HEAP32[off>>2]=HEAP32[LR_offset>>2];HEAP32[off+4>>2]=HEAP32[LR_offset+4>>2];off=(off+8)|0;}}function expandLoop(i, counterEnd, maxIterations) {i=i|0;counterEnd=counterEnd|0;maxIterations=maxIterations|0;var j=0;for (j=0; (j|0) <= (maxIterations|0); j=(j+1)|0) {if ((i>>>0)>(counterEnd>>>0)) break;expandKey(password_offset);expandKey(salt_offset);i=(i+1)>>>0;}return i|0;}return {encrypt: encrypt,expandLoop: expandLoop};} ++function l(r,n,e,t){var o,a,i,u=L>>2,f=u+1;for(t[u]=0,t[f]=0,a=0,o=0;M>o;o++)i=n[a++]<<24|n[a++]<<16|n[a++]<<8|n[a++],t[G>>2|o]^=i;for(a=0,o=0;M>o;o+=2)i=r[a++]<<24|r[a++]<<16|r[a++]<<8|r[a++],a&=65295,t[u]^=i,i=r[a++]<<24|r[a++]<<16|r[a++]<<8|r[a++],a&=65295,t[f]^=i,e.encrypt(L),t[G>>2|o]=t[u],t[G>>2|o+1]=t[f];var c=k>>2;for(o=0;T>o;o+=2)i=r[a++]<<24|r[a++]<<16|r[a++]<<8|r[a++],a&=65295,t[u]^=i,i=r[a++]<<24|r[a++]<<16|r[a++]<<8|r[a++],a&=65295,t[f]^=i,e.encrypt(L),t[c|o]=t[u],t[c|o+1]=t[f]}function p(r,n,e,t,o,a,i){for(var u=e;t>=u;){if(u=r.expandLoop(u,t,o),a){var f=a(u/(t+1));if(f===!1)return}if(u>t){if(i)return void setImmediate(g.bind(null,r,n,i));return}if(i)return void setImmediate(p.bind(null,r,n,u,t,o,a,i))}}function g(r,n,e){u(n,x,F);var t;for(t=0;64>t;t++)r.encrypt(F+0),r.encrypt(F+8),r.encrypt(F+16);var o,a=0,i=x.length,f=new Array(4*i);for(t=0;i>t;t++)o=n[(F>>2)+t],f[a++]=o>>24,f[a++]=o>>16&255,f[a++]=o>>8&255,f[a++]=255&o;return e&&e(f),f}function y(r,n){return r+o(n,23)}function m(n,o,m,v){var d,h=o.substr(0,29),w=+o.substr(4,2),A=o.substr(7,22);if("string"==typeof n)d=r.encodingMode===r.ENCODING_UTF8?e(n):t(n);else if(Array.isArray(n))d=n.map(function(r){return 255&r});else{if(!(n instanceof Uint8Array))throw new Error("Incorrect arguments");d=Array.prototype.slice.call(n)}d.push(0);var b,E,N=a(A,C),O=31>w?1<>2,N),f(b,R>>2,d),l(N,d,E,b),v?void p(E,b,0,M,T,m,function(r){v(y(h,r))}):(p(E,b,0,M,T,m),y(h,g(E,b)))}function v(r){if(!b)throw new Error("No cryptographically secure pseudorandom number generator available.");if(null==r&&(r=N),r=0|+r,isNaN(r)||4>r||r>31)throw new Error("Invalid cost parameter.");var n="$2y$";return 10>r&&(n+="0"),n+=r+"$",n+=o(b(C),C)}function d(r,n,e){if(n&&"number"!=typeof n){if("string"!=typeof n||!z.test(n))throw new Error("Invalid salt")}else n=v(n);return m(r,n,e)}function h(r,n,e,t){if(arguments.length<2)throw new Error("Incorrect arguments");if(2===arguments.length?(t=n,n=e=null):3===arguments.length&&(t=e,e=null,"function"==typeof n&&(e=n,n=null)),n&&"number"!=typeof n){if("string"!=typeof n||!z.test(n))throw new Error("Invalid salt")}else n=v(n);if(!t||"function"!=typeof t)throw new Error("No callback function was given.");m(r,n,e,t)}function w(r,n){if("string"!=typeof n||!Z.test(n))throw new Error("Incorrect arguments");var e=n.substr(0,n.length-31),t=d(r,e);return t===n}function A(r,n,e,t){if("string"!=typeof n||!Z.test(n))throw new Error("Incorrect arguments");if(t||(t=e,e=null),!t||"function"!=typeof t)throw new Error("No callback function was given.");var o=n.substr(0,n.length-31);h(r,o,e,function(r){t(r===n)})}var b,E="undefined"!=typeof InstallTrigger,I=E;n&&(b=n.randomBytes,n.getRandomValues&&(b=function(r){var e=new Uint8Array(r);return n.getRandomValues(e)}));var C=16,N=10,O=16,$=[608135816,2242054355,320440878,57701188,2752067618,698298832,137296536,3964562569,1160258022,953160567,3193202383,887688300,3232508343,3380367581,1065670069,3041331479,2450970073,2306472731],U=[3509652390,2564797868,805139163,3491422135,3101798381,1780907670,3128725573,4046225305,614570311,3012652279,134345442,2240740374,1667834072,1901547113,2757295779,4103290238,227898511,1921955416,1904987480,2182433518,2069144605,3260701109,2620446009,720527379,3318853667,677414384,3393288472,3101374703,2390351024,1614419982,1822297739,2954791486,3608508353,3174124327,2024746970,1432378464,3864339955,2857741204,1464375394,1676153920,1439316330,715854006,3033291828,289532110,2706671279,2087905683,3018724369,1668267050,732546397,1947742710,3462151702,2609353502,2950085171,1814351708,2050118529,680887927,999245976,1800124847,3300911131,1713906067,1641548236,4213287313,1216130144,1575780402,4018429277,3917837745,3693486850,3949271944,596196993,3549867205,258830323,2213823033,772490370,2760122372,1774776394,2652871518,566650946,4142492826,1728879713,2882767088,1783734482,3629395816,2517608232,2874225571,1861159788,326777828,3124490320,2130389656,2716951837,967770486,1724537150,2185432712,2364442137,1164943284,2105845187,998989502,3765401048,2244026483,1075463327,1455516326,1322494562,910128902,469688178,1117454909,936433444,3490320968,3675253459,1240580251,122909385,2157517691,634681816,4142456567,3825094682,3061402683,2540495037,79693498,3249098678,1084186820,1583128258,426386531,1761308591,1047286709,322548459,995290223,1845252383,2603652396,3431023940,2942221577,3202600964,3727903485,1712269319,422464435,3234572375,1170764815,3523960633,3117677531,1434042557,442511882,3600875718,1076654713,1738483198,4213154764,2393238008,3677496056,1014306527,4251020053,793779912,2902807211,842905082,4246964064,1395751752,1040244610,2656851899,3396308128,445077038,3742853595,3577915638,679411651,2892444358,2354009459,1767581616,3150600392,3791627101,3102740896,284835224,4246832056,1258075500,768725851,2589189241,3069724005,3532540348,1274779536,3789419226,2764799539,1660621633,3471099624,4011903706,913787905,3497959166,737222580,2514213453,2928710040,3937242737,1804850592,3499020752,2949064160,2386320175,2390070455,2415321851,4061277028,2290661394,2416832540,1336762016,1754252060,3520065937,3014181293,791618072,3188594551,3933548030,2332172193,3852520463,3043980520,413987798,3465142937,3030929376,4245938359,2093235073,3534596313,375366246,2157278981,2479649556,555357303,3870105701,2008414854,3344188149,4221384143,3956125452,2067696032,3594591187,2921233993,2428461,544322398,577241275,1471733935,610547355,4027169054,1432588573,1507829418,2025931657,3646575487,545086370,48609733,2200306550,1653985193,298326376,1316178497,3007786442,2064951626,458293330,2589141269,3591329599,3164325604,727753846,2179363840,146436021,1461446943,4069977195,705550613,3059967265,3887724982,4281599278,3313849956,1404054877,2845806497,146425753,1854211946,1266315497,3048417604,3681880366,3289982499,290971e4,1235738493,2632868024,2414719590,3970600049,1771706367,1449415276,3266420449,422970021,1963543593,2690192192,3826793022,1062508698,1531092325,1804592342,2583117782,2714934279,4024971509,1294809318,4028980673,1289560198,2221992742,1669523910,35572830,157838143,1052438473,1016535060,1802137761,1753167236,1386275462,3080475397,2857371447,1040679964,2145300060,2390574316,1461121720,2956646967,4031777805,4028374788,33600511,2920084762,1018524850,629373528,3691585981,3515945977,2091462646,2486323059,586499841,988145025,935516892,3367335476,2599673255,2839830854,265290510,3972581182,2759138881,3795373465,1005194799,847297441,406762289,1314163512,1332590856,1866599683,4127851711,750260880,613907577,1450815602,3165620655,3734664991,3650291728,3012275730,3704569646,1427272223,778793252,1343938022,2676280711,2052605720,1946737175,3164576444,3914038668,3967478842,3682934266,1661551462,3294938066,4011595847,840292616,3712170807,616741398,312560963,711312465,1351876610,322626781,1910503582,271666773,2175563734,1594956187,70604529,3617834859,1007753275,1495573769,4069517037,2549218298,2663038764,504708206,2263041392,3941167025,2249088522,1514023603,1998579484,1312622330,694541497,2582060303,2151582166,1382467621,776784248,2618340202,3323268794,2497899128,2784771155,503983604,4076293799,907881277,423175695,432175456,1378068232,4145222326,3954048622,3938656102,3820766613,2793130115,2977904593,26017576,3274890735,3194772133,1700274565,1756076034,4006520079,3677328699,720338349,1533947780,354530856,688349552,3973924725,1637815568,332179504,3949051286,53804574,2852348879,3044236432,1282449977,3583942155,3416972820,4006381244,1617046695,2628476075,3002303598,1686838959,431878346,2686675385,1700445008,1080580658,1009431731,832498133,3223435511,2605976345,2271191193,2516031870,1648197032,4164389018,2548247927,300782431,375919233,238389289,3353747414,2531188641,2019080857,1475708069,455242339,2609103871,448939670,3451063019,1395535956,2413381860,1841049896,1491858159,885456874,4264095073,4001119347,1565136089,3898914787,1108368660,540939232,1173283510,2745871338,3681308437,4207628240,3343053890,4016749493,1699691293,1103962373,3625875870,2256883143,3830138730,1031889488,3479347698,1535977030,4236805024,3251091107,2132092099,1774941330,1199868427,1452454533,157007616,2904115357,342012276,595725824,1480756522,206960106,497939518,591360097,863170706,2375253569,3596610801,1814182875,2094937945,3421402208,1082520231,3463918190,2785509508,435703966,3908032597,1641649973,2842273706,3305899714,1510255612,2148256476,2655287854,3276092548,4258621189,236887753,3681803219,274041037,1734335097,3815195456,3317970021,1899903192,1026095262,4050517792,356393447,2410691914,3873677099,3682840055,3913112168,2491498743,4132185628,2489919796,1091903735,1979897079,3170134830,3567386728,3557303409,857797738,1136121015,1342202287,507115054,2535736646,337727348,3213592640,1301675037,2528481711,1895095763,1721773893,3216771564,62756741,2142006736,835421444,2531993523,1442658625,3659876326,2882144922,676362277,1392781812,170690266,3921047035,1759253602,3611846912,1745797284,664899054,1329594018,3901205900,3045908486,2062866102,2865634940,3543621612,3464012697,1080764994,553557557,3656615353,3996768171,991055499,499776247,1265440854,648242737,3940784050,980351604,3713745714,1749149687,3396870395,4211799374,3640570775,1161844396,3125318951,1431517754,545492359,4268468663,3499529547,1437099964,2702547544,3433638243,2581715763,2787789398,1060185593,1593081372,2418618748,4260947970,69676912,2159744348,86519011,2512459080,3838209314,1220612927,3339683548,133810670,1090789135,1078426020,1569222167,845107691,3583754449,4072456591,1091646820,628848692,1613405280,3757631651,526609435,236106946,48312990,2942717905,3402727701,1797494240,859738849,992217954,4005476642,2243076622,3870952857,3732016268,765654824,3490871365,2511836413,1685915746,3888969200,1414112111,2273134842,3281911079,4080962846,172450625,2569994100,980381355,4109958455,2819808352,2716589560,2568741196,3681446669,3329971472,1835478071,660984891,3704678404,4045999559,3422617507,3040415634,1762651403,1719377915,3470491036,2693910283,3642056355,3138596744,1364962596,2073328063,1983633131,926494387,3423689081,2150032023,4096667949,1749200295,3328846651,309677260,2016342300,1779581495,3079819751,111262694,1274766160,443224088,298511866,1025883608,3806446537,1145181785,168956806,3641502830,3584813610,1689216846,3666258015,3200248200,1692713982,2646376535,4042768518,1618508792,1610833997,3523052358,4130873264,2001055236,3610705100,2202168115,4028541809,2961195399,1006657119,2006996926,3186142756,1430667929,3210227297,1314452623,4074634658,4101304120,2273951170,1399257539,3367210612,3027628629,1190975929,2062231137,2333990788,2221543033,2438960610,1181637006,548689776,2362791313,3372408396,3104550113,3145860560,296247880,1970579870,3078560182,3769228297,1714227617,3291629107,3898220290,166772364,1251581989,493813264,448347421,195405023,2709975567,677966185,3703036547,1463355134,2715995803,1338867538,1343315457,2802222074,2684532164,233230375,2599980071,2000651841,3277868038,1638401717,4028070440,3237316320,6314154,819756386,300326615,590932579,1405279636,3267499572,3150704214,2428286686,3959192993,3461946742,1862657033,1266418056,963775037,2089974820,2263052895,1917689273,448879540,3550394620,3981727096,150775221,3627908307,1303187396,508620638,2975983352,2726630617,1817252668,1876281319,1457606340,908771278,3720792119,3617206836,2455994898,1729034894,1080033504,976866871,3556439503,2881648439,1522871579,1555064734,1336096578,3548522304,2579274686,3574697629,3205460757,3593280638,3338716283,3079412587,564236357,2993598910,1781952180,1464380207,3163844217,3332601554,1699332808,1393555694,1183702653,3581086237,1288719814,691649499,2847557200,2895455976,3193889540,2717570544,1781354906,1676643554,2592534050,3230253752,1126444790,2770207658,2633158820,2210423226,2615765581,2414155088,3127139286,673620729,2805611233,1269405062,4015350505,3341807571,4149409754,1057255273,2012875353,2162469141,2276492801,2601117357,993977747,3918593370,2654263191,753973209,36408145,2530585658,25011837,3520020182,2088578344,530523599,2918365339,1524020338,1518925132,3760827505,3759777254,1202760957,3985898139,3906192525,674977740,4174734889,2031300136,2019492241,3983892565,4153806404,3822280332,352677332,2297720250,60907813,90501309,3286998549,1016092578,2535922412,2839152426,457141659,509813237,4120667899,652014361,1966332200,2975202805,55981186,2327461051,676427537,3255491064,2882294119,3433927263,1307055953,942726286,933058658,2468411793,3933900994,4215176142,1361170020,2001714738,2830558078,3274259782,1222529897,1679025792,2729314320,3714953764,1770335741,151462246,3013232138,1682292957,1483529935,471910574,1539241949,458788160,3436315007,1807016891,3718408830,978976581,1043663428,3165965781,1927990952,4200891579,2372276910,3208408903,3533431907,1412390302,2931980059,4132332400,1947078029,3881505623,4168226417,2941484381,1077988104,1320477388,886195818,18198404,3786409e3,2509781533,112762804,3463356488,1866414978,891333506,18488651,661792760,1628790961,3885187036,3141171499,876946877,2693282273,1372485963,791857591,2686433993,3759982718,3167212022,3472953795,2716379847,445679433,3561995674,3504004811,3574258232,54117162,3331405415,2381918588,3769707343,4154350007,1140177722,4074052095,668550556,3214352940,367459370,261225585,2610173221,4209349473,3468074219,3265815641,314222801,3066103646,3808782860,282218597,3406013506,3773591054,379116347,1285071038,846784868,2669647154,3771962079,3550491691,2305946142,453669953,1268987020,3317592352,3279303384,3744833421,2610507566,3859509063,266596637,3847019092,517658769,3462560207,3443424879,370717030,4247526661,2224018117,4143653529,4112773975,2788324899,2477274417,1456262402,2901442914,1517677493,1846949527,2295493580,3734397586,2176403920,1280348187,1908823572,3871786941,846861322,1172426758,3287448474,3383383037,1655181056,3139813346,901632758,1897031941,2986607138,3066810236,3447102507,1393639104,373351379,950779232,625454576,3124240540,4148612726,2007998917,544563296,2244738638,2330496472,2058025392,1291430526,424198748,50039436,29584100,3605783033,2429876329,2791104160,1057563949,3255363231,3075367218,3463963227,1469046755,985887462],M=$.length,T=U.length,x=[1332899944,1700884034,1701343084,1684370003,1668446532,1869963892],k=0,G=4096,S=4164,F=4168,L=4192,R=4200,j=4272,B="./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",D=[0,1,54,55,56,57,58,59,60,61,62,63,-1,-1,-1,-1,-1,-1,-1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,-1,-1,-1,-1,-1,-1,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,-1,-1,-1,-1,-1],z=/^\$2[ay]\$(0[4-9]|[12][0-9]|3[01])\$[.\/A-Za-z0-9]{21}[.Oeu]/,Z=/^\$2[ay]\$(0[4-9]|[12][0-9]|3[01])\$[.\/A-Za-z0-9]{21}[.Oeu][.\/A-Za-z0-9]{30}[.CGKOSWaeimquy26]$/;r.genSalt=v,r.hashSync=d,r.hash=h,r.compareSync=w,r.compare=A,r.ENCODING_UTF8=0,r.ENCODING_RAW=1,r.encodingMode=r.ENCODING_UTF8,r.cryptoRNG=!!b,r.randomBytes=b,r.defaultCost=N,r.version="2.2.0"}); diff --git a/luci-app-adguardhome/patches/prepare.patch b/luci-app-adguardhome/patches/prepare.patch new file mode 100644 index 000000000..bdfeef816 --- /dev/null +++ b/luci-app-adguardhome/patches/prepare.patch @@ -0,0 +1,36 @@ +From 0b024c2ebe6be105c3535f82e55a51e57dd947bb Mon Sep 17 00:00:00 2001 +From: Jackie264 <125248570+Jackie264@users.noreply.github.com> +Date: Sun, 7 Jun 2026 15:32:44 +0800 +Subject: [PATCH] Add .prepare.sh script for package preparation + +The script prepares the package build directory by checking for a Git repository and creating necessary directories and files based on the package name. +--- + .prepare.sh | 19 +++++++++++++++++++ + 1 file changed, 19 insertions(+) + create mode 100644 .prepare.sh + +diff --git a/.prepare.sh b/.prepare.sh +new file mode 100644 +index 0000000..92a72fb +--- /dev/null ++++ b/.prepare.sh +@@ -0,0 +1,19 @@ ++PKG_NAME="$1" ++CURDIR="$2" ++PKG_BUILD_DIR="$3" ++ ++if [ -d "$CURDIR/.git" ]; then ++ config="$CURDIR/.git/config" ++else ++ config="$(sed "s|^gitdir:\s*|$CURDIR/|;s|$|/config|" "$CURDIR/.git")" ++fi ++if grep -q -E "url.*gitcode\.(com|net)" "$config"; then ++ for d in htdocs patches po root; do ++ rm -rf "$PKG_BUILD_DIR"/$d ++ done ++ mkdir -p "$PKG_BUILD_DIR"/htdocs/luci-static/resources/view ++ touch "$PKG_BUILD_DIR"/htdocs/luci-static/resources/view/$PKG_NAME.js ++ mkdir -p "$PKG_BUILD_DIR"/root/usr/share/luci/menu.d ++ touch "$PKG_BUILD_DIR"/root/usr/share/luci/menu.d/$PKG_NAME.json ++fi ++exit 0 diff --git a/luci-app-adguardhome/po/cs/adguardhome.po b/luci-app-adguardhome/po/cs/adguardhome.po new file mode 100644 index 000000000..f03deb820 --- /dev/null +++ b/luci-app-adguardhome/po/cs/adguardhome.po @@ -0,0 +1,181 @@ +msgid "" +msgstr "" +"Content-Type: text/plain; charset=UTF-8\n" +"Language: cs\n" +"Content-Transfer-Encoding: 8bit\n" +"Last-Translator: Martin Olmer \n" +"Language-Team: Czech \n" +"Plural-Forms: nplurals=3; plural=((n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2);\n" +"PO-Revision-Date: 2026-08-27 18:20+0000\n" +"X-Generator: Weblate 2026.9.dev0\n" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:244 +msgid "" +"A soft memory cap for the Go runtime, allowing the garbage collector to run " +"more frequently as usage approaches the limit to prevent Out-of-Memory (OOM) " +"kills." +msgstr "" +"Překročitelný strop paměti pro běhové prostředí Go, umožňující častější " +"spouštění úklidu použité paměti, tak jak se využití blíží limitu, aby se " +"zabránilo vynucenému ukončování v docházející paměti (OOM)." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:113 +#: applications/luci-app-adguardhome/root/usr/share/luci/menu.d/luci-app-adguardhome.json:3 +msgid "AdGuard Home" +msgstr "AdGuard Home" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:137 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:199 +msgid "Advanced Settings" +msgstr "Pokročilá nastavení" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:146 +msgid "Configuration file" +msgstr "Soubor s nastaveními" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:89 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:147 +msgid "" +"Configuration file must be stored in its own directory, and not in '/etc'." +msgstr "" +"Je třeba, aby soubor s nastavením byl uložen ve své vlastní složce a ne " +"přímo v „/etc“." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:159 +msgid "Directory where filters, logs, and statistics are stored." +msgstr "Složka ve které jsou ukládány filtry, záznamy událostí a statistiky." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:132 +msgid "File System Access" +msgstr "Přístup k souborovému systému" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:133 +msgid "" +"Files and directories that AdGuard Home should have read-only or read-write " +"access to." +msgstr "" +"Soubory a složky, ke kterým by AdGuard Home měl mít přístup pouze pro čtení " +"nebo pro čtení i zápis." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:129 +msgid "General Settings" +msgstr "Obecná nastavení" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:138 +msgid "" +"Go environment variables that tune garbage collector and memory management." +msgstr "" +"Proměnné prostředí pro jazyk Go, které upravují chování automatického " +"uvolňování použité paměti a její správu." + +#: applications/luci-app-adguardhome/root/usr/share/rpcd/acl.d/luci-app-adguardhome.json:3 +msgid "Grant permissions for the AdGuard Home LuCI app" +msgstr "Udělit oprávnění pro LuCI aplikace AdGuard Home" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:181 +msgid "Group the service runs under." +msgstr "Skupina, pod kterou služba běží." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:149 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:161 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:171 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:181 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:217 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:232 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:247 +msgid "If empty, defaults to" +msgstr "Pokud nevyplněno, bude použito výchozí" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:139 +msgid "Modify at your own risk." +msgstr "Měňte na své vlastní riziko." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:24 +msgid "Not running" +msgstr "Nespuštěné" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:148 +msgid "Parent directory will be owned by the service user." +msgstr "Nadřazená složka bude vlastněna uživatelem, pod kterým služba běží." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:83 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:99 +msgid "Path must be absolute." +msgstr "Je třeba, aby popis umístění byl absolutní." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:86 +msgid "Path must not end with a slash." +msgstr "Je třeba, aby popis umístění nekončil lomítkem." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:207 +msgid "Read-only access" +msgstr "Přístup pouze pro čtení" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:208 +msgid "Read-write access" +msgstr "Přístup pro čtení a zápis" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:23 +msgid "Running" +msgstr "Spuštěné" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:122 +msgid "Service Status" +msgstr "Stav služby" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:180 +msgid "Service group" +msgstr "Skupina služby" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:170 +msgid "Service user" +msgstr "Uživatel služby" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:230 +msgid "" +"The maximum number of operating system threads that can execute user-level " +"Go code simultaneously." +msgstr "" +"Nejvyšší umožněný počet vláken operačního systému které mohou souběžně " +"vykonávat kód v Go na uživatelské úrovni." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:215 +msgid "" +"Tunes the garbage collector's aggressiveness by setting the percentage of " +"heap growth allowed before the next collection cycle triggers." +msgstr "" +"Upravuje agresivnost úklidu použité paměti nastavením procenta růstu " +"„haldy“, dovoleného než bude spuštěn další cyklus úklidu." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:171 +msgid "User the service runs under." +msgstr "Uživatel, pod kterým služba běží." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:191 +msgid "Verbose logging" +msgstr "Podrobné zaznamenávání událostí" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:119 +msgid "Version" +msgstr "Verze" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:160 +msgid "Will be owned by the service user." +msgstr "Bude vlastněno uživatelem, pod kterým služba běží." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:158 +msgid "Working directory" +msgstr "Pracovní složka" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:247 +msgid "unset" +msgstr "zrušit nastavení" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:217 +msgid "unset and 100" +msgstr "zrušit nastavení a 100" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:232 +msgid "unset and matching the number of CPUs" +msgstr "zrušit nastavení a odpovídající počtu jader procesoru" diff --git a/luci-app-adguardhome/po/de/adguardhome.po b/luci-app-adguardhome/po/de/adguardhome.po new file mode 100644 index 000000000..9529ba323 --- /dev/null +++ b/luci-app-adguardhome/po/de/adguardhome.po @@ -0,0 +1,168 @@ +msgid "" +msgstr "" +"Content-Type: text/plain; charset=UTF-8\n" +"Language: de\n" +"Content-Transfer-Encoding: 8bit\n" +"Last-Translator: Pavel Borecki \n" +"Language-Team: German \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"PO-Revision-Date: 2026-09-23 19:35+0000\n" +"X-Generator: Weblate 2026.10.dev0\n" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:244 +msgid "" +"A soft memory cap for the Go runtime, allowing the garbage collector to run " +"more frequently as usage approaches the limit to prevent Out-of-Memory (OOM) " +"kills." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:113 +#: applications/luci-app-adguardhome/root/usr/share/luci/menu.d/luci-app-adguardhome.json:3 +msgid "AdGuard Home" +msgstr "AdGuard Home" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:137 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:199 +msgid "Advanced Settings" +msgstr "Erweiterte Einstellungen" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:146 +msgid "Configuration file" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:89 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:147 +msgid "" +"Configuration file must be stored in its own directory, and not in '/etc'." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:159 +msgid "Directory where filters, logs, and statistics are stored." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:132 +msgid "File System Access" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:133 +msgid "" +"Files and directories that AdGuard Home should have read-only or read-write " +"access to." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:129 +msgid "General Settings" +msgstr "Allgemeine Einstellungen" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:138 +msgid "" +"Go environment variables that tune garbage collector and memory management." +msgstr "" + +#: applications/luci-app-adguardhome/root/usr/share/rpcd/acl.d/luci-app-adguardhome.json:3 +msgid "Grant permissions for the AdGuard Home LuCI app" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:181 +msgid "Group the service runs under." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:149 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:161 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:171 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:181 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:217 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:232 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:247 +msgid "If empty, defaults to" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:139 +msgid "Modify at your own risk." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:24 +msgid "Not running" +msgstr "Angehalten" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:148 +msgid "Parent directory will be owned by the service user." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:83 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:99 +msgid "Path must be absolute." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:86 +msgid "Path must not end with a slash." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:207 +msgid "Read-only access" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:208 +msgid "Read-write access" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:23 +msgid "Running" +msgstr "Läuft" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:122 +msgid "Service Status" +msgstr "Dienststatus" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:180 +msgid "Service group" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:170 +msgid "Service user" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:230 +msgid "" +"The maximum number of operating system threads that can execute user-level " +"Go code simultaneously." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:215 +msgid "" +"Tunes the garbage collector's aggressiveness by setting the percentage of " +"heap growth allowed before the next collection cycle triggers." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:171 +msgid "User the service runs under." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:191 +msgid "Verbose logging" +msgstr "Ausführliche Protokollierung" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:119 +msgid "Version" +msgstr "Version" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:160 +msgid "Will be owned by the service user." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:158 +msgid "Working directory" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:247 +msgid "unset" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:217 +msgid "unset and 100" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:232 +msgid "unset and matching the number of CPUs" +msgstr "" diff --git a/luci-app-adguardhome/po/es/adguardhome.po b/luci-app-adguardhome/po/es/adguardhome.po new file mode 100644 index 000000000..bfbb38ffd --- /dev/null +++ b/luci-app-adguardhome/po/es/adguardhome.po @@ -0,0 +1,188 @@ +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-08-23 22:27+0200\n" +"PO-Revision-Date: 2026-09-23 19:06+0000\n" +"Last-Translator: Franco Castillo \n" +"Language-Team: Spanish \n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 2026.10.dev0\n" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:244 +msgid "" +"A soft memory cap for the Go runtime, allowing the garbage collector to run " +"more frequently as usage approaches the limit to prevent Out-of-Memory (OOM) " +"kills." +msgstr "" +"Un límite de memoria flexible para el entorno de ejecución de Go, que " +"permite que el recolector de basura se ejecute con mayor frecuencia a medida " +"que el uso se acerca al límite para evitar bloqueos por falta de memoria " +"(OOM, por sus siglas en inglés)." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:113 +#: applications/luci-app-adguardhome/root/usr/share/luci/menu.d/luci-app-adguardhome.json:3 +msgid "AdGuard Home" +msgstr "AdGuard Home" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:137 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:199 +msgid "Advanced Settings" +msgstr "Configuración avanzada" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:146 +msgid "Configuration file" +msgstr "Archivo de configuración" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:89 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:147 +msgid "" +"Configuration file must be stored in its own directory, and not in '/etc'." +msgstr "" +"El archivo de configuración debe almacenarse en su propio directorio, y no " +"en «/etc»." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:159 +msgid "Directory where filters, logs, and statistics are stored." +msgstr "" +"Directorio donde se almacenan los filtros, los registros y las estadísticas." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:132 +msgid "File System Access" +msgstr "Acceso al sistema de archivos" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:133 +msgid "" +"Files and directories that AdGuard Home should have read-only or read-write " +"access to." +msgstr "" +"Archivos y directorios a los que AdGuard Home debería tener acceso de solo " +"lectura o de lectura y escritura." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:129 +msgid "General Settings" +msgstr "Configuración general" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:138 +msgid "" +"Go environment variables that tune garbage collector and memory management." +msgstr "" +"Variables de entorno de Go que ajustan el recolector de basura y la gestión " +"de memoria." + +#: applications/luci-app-adguardhome/root/usr/share/rpcd/acl.d/luci-app-adguardhome.json:3 +msgid "Grant permissions for the AdGuard Home LuCI app" +msgstr "Otorgar permisos para la aplicación LuCI AdGuard Home" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:181 +msgid "Group the service runs under." +msgstr "Grupo al que pertenece el servicio." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:149 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:161 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:171 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:181 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:217 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:232 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:247 +msgid "If empty, defaults to" +msgstr "Si está vacío, por defecto es" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:139 +msgid "Modify at your own risk." +msgstr "Modifique bajo su propia responsabilidad." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:24 +msgid "Not running" +msgstr "No se está ejecutando" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:148 +msgid "Parent directory will be owned by the service user." +msgstr "El directorio principal pertenecerá al usuario del servicio." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:83 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:99 +msgid "Path must be absolute." +msgstr "La ruta debe ser absoluta." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:86 +msgid "Path must not end with a slash." +msgstr "La ruta no debe terminar con una barra diagonal." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:207 +msgid "Read-only access" +msgstr "Acceso de solo lectura" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:208 +msgid "Read-write access" +msgstr "Acceso de lectura y escritura" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:23 +msgid "Running" +msgstr "Ejecutándose" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:122 +msgid "Service Status" +msgstr "Estado del servicio" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:180 +msgid "Service group" +msgstr "Grupo del servicio" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:170 +msgid "Service user" +msgstr "Usuario del servicio" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:230 +msgid "" +"The maximum number of operating system threads that can execute user-level " +"Go code simultaneously." +msgstr "" +"El número máximo de subprocesos del sistema operativo que pueden ejecutar " +"código Go a nivel de usuario simultáneamente." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:215 +msgid "" +"Tunes the garbage collector's aggressiveness by setting the percentage of " +"heap growth allowed before the next collection cycle triggers." +msgstr "" +"Permite ajustar la agresividad del recolector de basura estableciendo el " +"porcentaje de crecimiento de la pila permitido antes de que se active el " +"siguiente ciclo de recolección." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:171 +msgid "User the service runs under." +msgstr "Usuario bajo el cual se ejecuta el servicio." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:191 +msgid "Verbose logging" +msgstr "Registro detallado" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:119 +msgid "Version" +msgstr "Versión" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:160 +msgid "Will be owned by the service user." +msgstr "Será propiedad del usuario del servicio." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:158 +msgid "Working directory" +msgstr "Directorio de trabajo" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:247 +msgid "unset" +msgstr "no establecido" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:217 +msgid "unset and 100" +msgstr "no establecido y 100" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:232 +msgid "unset and matching the number of CPUs" +msgstr "no configurado y que coincide con el número de CPUs" diff --git a/luci-app-adguardhome/po/ga/adguardhome.po b/luci-app-adguardhome/po/ga/adguardhome.po new file mode 100644 index 000000000..77c379926 --- /dev/null +++ b/luci-app-adguardhome/po/ga/adguardhome.po @@ -0,0 +1,183 @@ +msgid "" +msgstr "" +"Content-Type: text/plain; charset=UTF-8\n" +"Language: ga\n" +"Content-Transfer-Encoding: 8bit\n" +"Last-Translator: Aindriú Mac Giolla Eoin \n" +"Language-Team: Irish \n" +"Plural-Forms: nplurals=5; plural=n==1 ? 0 : n==2 ? 1 : (n>2 && n<7) ? 2 :(n>" +"6 && n<11) ? 3 : 4;\n" +"PO-Revision-Date: 2026-09-02 17:28+0000\n" +"X-Generator: Weblate 2026.9.dev0\n" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:244 +msgid "" +"A soft memory cap for the Go runtime, allowing the garbage collector to run " +"more frequently as usage approaches the limit to prevent Out-of-Memory (OOM) " +"kills." +msgstr "" +"Teorainn bhog chuimhne don am rite Go, rud a ligeann don bhailitheoir " +"truflais rith níos minice de réir mar a thagann an úsáid i dtreo na teorann " +"chun maruithe As Cuimhne (OOM) a chosc." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:113 +#: applications/luci-app-adguardhome/root/usr/share/luci/menu.d/luci-app-adguardhome.json:3 +msgid "AdGuard Home" +msgstr "AdGuard Home" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:137 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:199 +msgid "Advanced Settings" +msgstr "Socruithe Ardleibhéil" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:146 +msgid "Configuration file" +msgstr "Comhad cumraíochta" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:89 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:147 +msgid "" +"Configuration file must be stored in its own directory, and not in '/etc'." +msgstr "" +"Ní mór an comhad cumraíochta a stóráil ina eolaire féin, agus ní i '/etc'." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:159 +msgid "Directory where filters, logs, and statistics are stored." +msgstr "Eolaire ina stóráiltear scagairí, logaí agus staitisticí." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:132 +msgid "File System Access" +msgstr "Rochtain ar an gCóras Comhad" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:133 +msgid "" +"Files and directories that AdGuard Home should have read-only or read-write " +"access to." +msgstr "" +"Comhaid agus eolairí ar cheart go mbeadh rochtain léite amháin nó rochtana " +"léite-scríofa ag AdGuard Home orthu." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:129 +msgid "General Settings" +msgstr "Socruithe Ginearálta" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:138 +msgid "" +"Go environment variables that tune garbage collector and memory management." +msgstr "" +"Athróga timpeallachta Téigh a choigeartaíonn bailitheoir truflais agus " +"bainistíocht cuimhne." + +#: applications/luci-app-adguardhome/root/usr/share/rpcd/acl.d/luci-app-adguardhome.json:3 +msgid "Grant permissions for the AdGuard Home LuCI app" +msgstr "Deonaigh ceadanna don aip AdGuard Home LuCI" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:181 +msgid "Group the service runs under." +msgstr "Grúpa faoina ritheann an tseirbhís." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:149 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:161 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:171 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:181 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:217 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:232 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:247 +msgid "If empty, defaults to" +msgstr "Más folamh, réamhshocraítear é go" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:139 +msgid "Modify at your own risk." +msgstr "Déan modhnú ar do phriacal féin." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:24 +msgid "Not running" +msgstr "Gan rith" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:148 +msgid "Parent directory will be owned by the service user." +msgstr "" +"Beidh eolaire na dtuismitheoirí faoi úinéireacht úsáideoir na seirbhíse." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:83 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:99 +msgid "Path must be absolute." +msgstr "Caithfidh an cosán a bheith absalóideach." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:86 +msgid "Path must not end with a slash." +msgstr "Ní mór don chosán a bheith críochnaithe le slais." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:207 +msgid "Read-only access" +msgstr "Rochtain léite amháin" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:208 +msgid "Read-write access" +msgstr "Rochtain léite-scríofa" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:23 +msgid "Running" +msgstr "Ag rith" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:122 +msgid "Service Status" +msgstr "Stádas Seirbhíse" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:180 +msgid "Service group" +msgstr "Grúpa seirbhíse" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:170 +msgid "Service user" +msgstr "Úsáideoir seirbhíse" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:230 +msgid "" +"The maximum number of operating system threads that can execute user-level " +"Go code simultaneously." +msgstr "" +"An líon uasta snáitheanna córais oibriúcháin ar féidir leo cód Go ar " +"leibhéal an úsáideora a fhorghníomhú ag an am céanna." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:215 +msgid "" +"Tunes the garbage collector's aggressiveness by setting the percentage of " +"heap growth allowed before the next collection cycle triggers." +msgstr "" +"Coigeartaíonn sé ionsaitheacht an bhailitheora bruscair tríd an gcéatadán " +"fáis carn a cheadaítear sula dtosaíonn an chéad timthriall bailiúcháin eile " +"a shocrú." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:171 +msgid "User the service runs under." +msgstr "An t-úsáideoir a ritheann an tseirbhís faoi." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:191 +msgid "Verbose logging" +msgstr "Logáil fhaclach" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:119 +msgid "Version" +msgstr "Leagan" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:160 +msgid "Will be owned by the service user." +msgstr "Beidh sé faoi úinéireacht an úsáideora seirbhíse." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:158 +msgid "Working directory" +msgstr "Eolaire oibre" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:247 +msgid "unset" +msgstr "díchumasaigh" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:217 +msgid "unset and 100" +msgstr "díchumasaigh agus 100" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:232 +msgid "unset and matching the number of CPUs" +msgstr "díchumasaigh agus ag teacht le líon na LAPanna" diff --git a/luci-app-adguardhome/po/ko/adguardhome.po b/luci-app-adguardhome/po/ko/adguardhome.po new file mode 100644 index 000000000..74d1b9340 --- /dev/null +++ b/luci-app-adguardhome/po/ko/adguardhome.po @@ -0,0 +1,178 @@ +msgid "" +msgstr "" +"Content-Type: text/plain; charset=UTF-8\n" +"Language: ko\n" +"Content-Transfer-Encoding: 8bit\n" +"Last-Translator: Hyeonjeong Lee \n" +"Language-Team: Korean \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"PO-Revision-Date: 2026-09-15 21:51+0000\n" +"X-Generator: Weblate 2026.10.dev0\n" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:244 +msgid "" +"A soft memory cap for the Go runtime, allowing the garbage collector to run " +"more frequently as usage approaches the limit to prevent Out-of-Memory (OOM) " +"kills." +msgstr "" +"Go 런타임의 소프트 메모리 상한선입니다. 사용량이 한계에 가까워질수록 쓰레기 " +"수집기가 더 자주 작동하여 메모리 부족 (OOM)으로 인한 강제 종료를 방지합니다." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:113 +#: applications/luci-app-adguardhome/root/usr/share/luci/menu.d/luci-app-adguardhome.json:3 +msgid "AdGuard Home" +msgstr "AdGuard Home" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:137 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:199 +msgid "Advanced Settings" +msgstr "고급 설정" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:146 +msgid "Configuration file" +msgstr "설정 파일" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:89 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:147 +msgid "" +"Configuration file must be stored in its own directory, and not in '/etc'." +msgstr "설정 파일은 '/etc'가 아닌 전용 디렉터리에 저장해야 합니다." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:159 +msgid "Directory where filters, logs, and statistics are stored." +msgstr "필터, 로그, 통계가 저장되는 디렉터리입니다." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:132 +msgid "File System Access" +msgstr "파일 시스템 접근" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:133 +msgid "" +"Files and directories that AdGuard Home should have read-only or read-write " +"access to." +msgstr "" +"AdGuard Home에서 읽기 전용 또는 읽기/쓰기 권한을 가져야 하는 파일 및 디렉터리" +"입니다." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:129 +msgid "General Settings" +msgstr "일반 설정" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:138 +msgid "" +"Go environment variables that tune garbage collector and memory management." +msgstr "" +"쓰레기 수집기(Garbage Collector) 및 메모리 관리를 조정하는 Go 환경 변수입니" +"다." + +#: applications/luci-app-adguardhome/root/usr/share/rpcd/acl.d/luci-app-adguardhome.json:3 +msgid "Grant permissions for the AdGuard Home LuCI app" +msgstr "AdGuard Home LuCI 앱에 권한 부여" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:181 +msgid "Group the service runs under." +msgstr "서비스를 실행할 그룹입니다." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:149 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:161 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:171 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:181 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:217 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:232 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:247 +msgid "If empty, defaults to" +msgstr "미입력 시 기본값:" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:139 +msgid "Modify at your own risk." +msgstr "수정 시 책임은 사용자에게 있습니다." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:24 +msgid "Not running" +msgstr "중지됨" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:148 +msgid "Parent directory will be owned by the service user." +msgstr "상위 디렉터리는 서비스 사용자의 소유로 설정됩니다." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:83 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:99 +msgid "Path must be absolute." +msgstr "절대 경로여야 합니다." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:86 +msgid "Path must not end with a slash." +msgstr "경로가 슬래시(/)로 끝나서는 안 됩니다." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:207 +msgid "Read-only access" +msgstr "읽기 전용 접근" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:208 +msgid "Read-write access" +msgstr "읽기/쓰기 접근" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:23 +msgid "Running" +msgstr "실행 중" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:122 +msgid "Service Status" +msgstr "서비스 상태" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:180 +msgid "Service group" +msgstr "서비스 그룹" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:170 +msgid "Service user" +msgstr "서비스 사용자" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:230 +msgid "" +"The maximum number of operating system threads that can execute user-level " +"Go code simultaneously." +msgstr "" +"사용자 수준의 Go 코드를 동시에 실행할 수 있는 운영 체제 스레드의 최대 개수입" +"니다." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:215 +msgid "" +"Tunes the garbage collector's aggressiveness by setting the percentage of " +"heap growth allowed before the next collection cycle triggers." +msgstr "" +"다음 수집 주기가 실행되기 전에 허용되는 힙 메모리 증가율(%)을 설정하여 쓰레" +"기 수집기의 작동 빈도(Aggressiveness)를 조정합니다." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:171 +msgid "User the service runs under." +msgstr "서비스를 실행할 사용자입니다." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:191 +msgid "Verbose logging" +msgstr "상세 로그 기록" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:119 +msgid "Version" +msgstr "버전" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:160 +msgid "Will be owned by the service user." +msgstr "서비스 사용자의 소유로 설정됩니다." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:158 +msgid "Working directory" +msgstr "작업 디렉터리" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:247 +msgid "unset" +msgstr "설정 안 함" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:217 +msgid "unset and 100" +msgstr "미설정 (100)" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:232 +msgid "unset and matching the number of CPUs" +msgstr "미설정 (CPU 코어 수와 일치)" diff --git a/luci-app-adguardhome/po/lo/adguardhome.po b/luci-app-adguardhome/po/lo/adguardhome.po new file mode 100644 index 000000000..bd1248f03 --- /dev/null +++ b/luci-app-adguardhome/po/lo/adguardhome.po @@ -0,0 +1,168 @@ +msgid "" +msgstr "" +"Content-Type: text/plain; charset=UTF-8\n" +"Language: lo\n" +"Content-Transfer-Encoding: 8bit\n" +"Last-Translator: Martin Olmer \n" +"Language-Team: Lao \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"PO-Revision-Date: 2026-08-28 18:08+0000\n" +"X-Generator: Weblate 2026.9.dev0\n" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:244 +msgid "" +"A soft memory cap for the Go runtime, allowing the garbage collector to run " +"more frequently as usage approaches the limit to prevent Out-of-Memory (OOM) " +"kills." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:113 +#: applications/luci-app-adguardhome/root/usr/share/luci/menu.d/luci-app-adguardhome.json:3 +msgid "AdGuard Home" +msgstr "AdGuard Home" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:137 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:199 +msgid "Advanced Settings" +msgstr "ການຕັ້ງຄ່າຂັ້ນສູງ" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:146 +msgid "Configuration file" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:89 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:147 +msgid "" +"Configuration file must be stored in its own directory, and not in '/etc'." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:159 +msgid "Directory where filters, logs, and statistics are stored." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:132 +msgid "File System Access" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:133 +msgid "" +"Files and directories that AdGuard Home should have read-only or read-write " +"access to." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:129 +msgid "General Settings" +msgstr "ການຕັ້ງຄ່າທົ່ວໄປ" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:138 +msgid "" +"Go environment variables that tune garbage collector and memory management." +msgstr "" + +#: applications/luci-app-adguardhome/root/usr/share/rpcd/acl.d/luci-app-adguardhome.json:3 +msgid "Grant permissions for the AdGuard Home LuCI app" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:181 +msgid "Group the service runs under." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:149 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:161 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:171 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:181 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:217 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:232 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:247 +msgid "If empty, defaults to" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:139 +msgid "Modify at your own risk." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:24 +msgid "Not running" +msgstr "ບໍ່ເຮັດວຽກ" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:148 +msgid "Parent directory will be owned by the service user." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:83 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:99 +msgid "Path must be absolute." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:86 +msgid "Path must not end with a slash." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:207 +msgid "Read-only access" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:208 +msgid "Read-write access" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:23 +msgid "Running" +msgstr "ກຳລັງເຮັດວຽກ" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:122 +msgid "Service Status" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:180 +msgid "Service group" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:170 +msgid "Service user" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:230 +msgid "" +"The maximum number of operating system threads that can execute user-level " +"Go code simultaneously." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:215 +msgid "" +"Tunes the garbage collector's aggressiveness by setting the percentage of " +"heap growth allowed before the next collection cycle triggers." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:171 +msgid "User the service runs under." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:191 +msgid "Verbose logging" +msgstr "ການບັນທຶກລັອກແບບລະອຽດ" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:119 +msgid "Version" +msgstr "ເວີຊັນ" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:160 +msgid "Will be owned by the service user." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:158 +msgid "Working directory" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:247 +msgid "unset" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:217 +msgid "unset and 100" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:232 +msgid "unset and matching the number of CPUs" +msgstr "" diff --git a/luci-app-adguardhome/po/lt/adguardhome.po b/luci-app-adguardhome/po/lt/adguardhome.po new file mode 100644 index 000000000..737eabaf1 --- /dev/null +++ b/luci-app-adguardhome/po/lt/adguardhome.po @@ -0,0 +1,169 @@ +msgid "" +msgstr "" +"Content-Type: text/plain; charset=UTF-8\n" +"Language: lt\n" +"Content-Transfer-Encoding: 8bit\n" +"Last-Translator: Martin Olmer \n" +"Language-Team: Lithuanian \n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " +"(n%100<10 || n%100>=20) ? 1 : 2);\n" +"PO-Revision-Date: 2026-08-28 12:25+0000\n" +"X-Generator: Weblate 2026.9.dev0\n" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:244 +msgid "" +"A soft memory cap for the Go runtime, allowing the garbage collector to run " +"more frequently as usage approaches the limit to prevent Out-of-Memory (OOM) " +"kills." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:113 +#: applications/luci-app-adguardhome/root/usr/share/luci/menu.d/luci-app-adguardhome.json:3 +msgid "AdGuard Home" +msgstr "AdGuard Home" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:137 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:199 +msgid "Advanced Settings" +msgstr "Pažangūs nustatymai" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:146 +msgid "Configuration file" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:89 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:147 +msgid "" +"Configuration file must be stored in its own directory, and not in '/etc'." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:159 +msgid "Directory where filters, logs, and statistics are stored." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:132 +msgid "File System Access" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:133 +msgid "" +"Files and directories that AdGuard Home should have read-only or read-write " +"access to." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:129 +msgid "General Settings" +msgstr "Bendri nustatymai" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:138 +msgid "" +"Go environment variables that tune garbage collector and memory management." +msgstr "" + +#: applications/luci-app-adguardhome/root/usr/share/rpcd/acl.d/luci-app-adguardhome.json:3 +msgid "Grant permissions for the AdGuard Home LuCI app" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:181 +msgid "Group the service runs under." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:149 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:161 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:171 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:181 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:217 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:232 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:247 +msgid "If empty, defaults to" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:139 +msgid "Modify at your own risk." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:24 +msgid "Not running" +msgstr "Nevykdomas/-a" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:148 +msgid "Parent directory will be owned by the service user." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:83 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:99 +msgid "Path must be absolute." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:86 +msgid "Path must not end with a slash." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:207 +msgid "Read-only access" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:208 +msgid "Read-write access" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:23 +msgid "Running" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:122 +msgid "Service Status" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:180 +msgid "Service group" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:170 +msgid "Service user" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:230 +msgid "" +"The maximum number of operating system threads that can execute user-level " +"Go code simultaneously." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:215 +msgid "" +"Tunes the garbage collector's aggressiveness by setting the percentage of " +"heap growth allowed before the next collection cycle triggers." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:171 +msgid "User the service runs under." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:191 +msgid "Verbose logging" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:119 +msgid "Version" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:160 +msgid "Will be owned by the service user." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:158 +msgid "Working directory" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:247 +msgid "unset" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:217 +msgid "unset and 100" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:232 +msgid "unset and matching the number of CPUs" +msgstr "" diff --git a/luci-app-adguardhome/po/pl/adguardhome.po b/luci-app-adguardhome/po/pl/adguardhome.po new file mode 100644 index 000000000..95313f31e --- /dev/null +++ b/luci-app-adguardhome/po/pl/adguardhome.po @@ -0,0 +1,183 @@ +msgid "" +msgstr "" +"Content-Type: text/plain; charset=UTF-8\n" +"Language: pl\n" +"Content-Transfer-Encoding: 8bit\n" +"Last-Translator: Matthaiks \n" +"Language-Team: Polish \n" +"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " +"|| n%100>=20) ? 1 : 2);\n" +"PO-Revision-Date: 2026-08-24 16:48+0000\n" +"X-Generator: Weblate 2026.9.dev0\n" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:244 +msgid "" +"A soft memory cap for the Go runtime, allowing the garbage collector to run " +"more frequently as usage approaches the limit to prevent Out-of-Memory (OOM) " +"kills." +msgstr "" +"Miękki limit pamięci dla środowiska wykonawczego Go, umożliwiający modułowi " +"zbierającemu nieużytki częstsze uruchamianie się, gdy wykorzystanie pamięci " +"zbliża się do limitu, zapobiegając w ten sposób wyłączeniom z powodu braku " +"pamięci (OOM)." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:113 +#: applications/luci-app-adguardhome/root/usr/share/luci/menu.d/luci-app-adguardhome.json:3 +msgid "AdGuard Home" +msgstr "AdGuard Home" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:137 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:199 +msgid "Advanced Settings" +msgstr "Ustawienia zaawansowane" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:146 +msgid "Configuration file" +msgstr "Plik konfiguracyjny" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:89 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:147 +msgid "" +"Configuration file must be stored in its own directory, and not in '/etc'." +msgstr "" +"Plik konfiguracyjny musi być zapisany w osobnym katalogu, a nie w '/etc'." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:159 +msgid "Directory where filters, logs, and statistics are stored." +msgstr "Katalog, w którym przechowywane są filtry, dzienniki i statystyki." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:132 +msgid "File System Access" +msgstr "Dostęp do systemu plików" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:133 +msgid "" +"Files and directories that AdGuard Home should have read-only or read-write " +"access to." +msgstr "" +"Pliki i katalogi, do których AdGuard Home powinien mieć dostęp tylko do " +"odczytu lub do odczytu i zapisu." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:129 +msgid "General Settings" +msgstr "Ustawienia główne" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:138 +msgid "" +"Go environment variables that tune garbage collector and memory management." +msgstr "" +"Zmienne środowiskowe Go, które dostrajają moduł zbierający nieużytki i " +"zarządzanie pamięcią." + +#: applications/luci-app-adguardhome/root/usr/share/rpcd/acl.d/luci-app-adguardhome.json:3 +msgid "Grant permissions for the AdGuard Home LuCI app" +msgstr "Udziel uprawnień aplikacji LuCI AdGuard Home" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:181 +msgid "Group the service runs under." +msgstr "Grupa, w ramach której działa usługa." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:149 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:161 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:171 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:181 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:217 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:232 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:247 +msgid "If empty, defaults to" +msgstr "Jeśli puste, domyślnie" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:139 +msgid "Modify at your own risk." +msgstr "Dokonujesz zmian na własne ryzyko." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:24 +msgid "Not running" +msgstr "Nieuruchomione" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:148 +msgid "Parent directory will be owned by the service user." +msgstr "Właścicielem katalogu nadrzędnego będzie użytkownik usługi." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:83 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:99 +msgid "Path must be absolute." +msgstr "Ścieżka musi być bezwzględna." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:86 +msgid "Path must not end with a slash." +msgstr "Ścieżka nie może kończyć się ukośnikiem." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:207 +msgid "Read-only access" +msgstr "Dostęp tylko do odczytu" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:208 +msgid "Read-write access" +msgstr "Dostęp do odczytu i zapisu" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:23 +msgid "Running" +msgstr "Uruchomione" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:122 +msgid "Service Status" +msgstr "Status usługi" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:180 +msgid "Service group" +msgstr "Grupa usługi" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:170 +msgid "Service user" +msgstr "Użytkownik usługi" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:230 +msgid "" +"The maximum number of operating system threads that can execute user-level " +"Go code simultaneously." +msgstr "" +"Maksymalna liczba wątków systemu operacyjnego, które mogą jednocześnie " +"wykonywać kod Go na poziomie użytkownika." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:215 +msgid "" +"Tunes the garbage collector's aggressiveness by setting the percentage of " +"heap growth allowed before the next collection cycle triggers." +msgstr "" +"Dostosowuje agresywność modułu zbierającego nieużytki poprzez ustawienie " +"procentowego wzrostu stosu dozwolonego przed wyzwoleniem następnego cyklu " +"zbierania nieużytków." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:171 +msgid "User the service runs under." +msgstr "Użytkownik, w ramach którego działa usługa." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:191 +msgid "Verbose logging" +msgstr "Szczegółowe rejestrowanie" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:119 +msgid "Version" +msgstr "Wersja" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:160 +msgid "Will be owned by the service user." +msgstr "Będzie własnością użytkownika usługi." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:158 +msgid "Working directory" +msgstr "Katalog roboczy" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:247 +msgid "unset" +msgstr "nieustawione" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:217 +msgid "unset and 100" +msgstr "nieustawione i 100" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:232 +msgid "unset and matching the number of CPUs" +msgstr "nieustawione i dopasowanie liczby procesorów" diff --git a/luci-app-adguardhome/po/ru/adguardhome.po b/luci-app-adguardhome/po/ru/adguardhome.po new file mode 100644 index 000000000..6ac73e7a2 --- /dev/null +++ b/luci-app-adguardhome/po/ru/adguardhome.po @@ -0,0 +1,182 @@ +msgid "" +msgstr "" +"Content-Type: text/plain; charset=UTF-8\n" +"Language: ru\n" +"Content-Transfer-Encoding: 8bit\n" +"Last-Translator: SnIPeRSnIPeR " +"\n" +"Language-Team: Russian \n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " +"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" +"PO-Revision-Date: 2026-08-28 22:28+0000\n" +"X-Generator: Weblate 2026.9.dev0\n" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:244 +msgid "" +"A soft memory cap for the Go runtime, allowing the garbage collector to run " +"more frequently as usage approaches the limit to prevent Out-of-Memory (OOM) " +"kills." +msgstr "" +"Мягкое ограничение памяти для среды выполнения Go, позволяющее сборщику " +"мусора запускаться чаще по мере приближения к лимиту, чтобы предотвратить " +"принудительное завершение процесса из-за нехватки памяти (OOM)." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:113 +#: applications/luci-app-adguardhome/root/usr/share/luci/menu.d/luci-app-adguardhome.json:3 +msgid "AdGuard Home" +msgstr "AdGuard Home" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:137 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:199 +msgid "Advanced Settings" +msgstr "Расширенные настройки" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:146 +msgid "Configuration file" +msgstr "Файл конфигурации" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:89 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:147 +msgid "" +"Configuration file must be stored in its own directory, and not in '/etc'." +msgstr "" +"Файл конфигурации должен храниться в собственном каталоге, а не в '/etc'." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:159 +msgid "Directory where filters, logs, and statistics are stored." +msgstr "Каталог, в котором хранятся фильтры, журналы и статистика." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:132 +msgid "File System Access" +msgstr "Доступ к файловой системе" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:133 +msgid "" +"Files and directories that AdGuard Home should have read-only or read-write " +"access to." +msgstr "" +"Файлы и каталоги, к которым AdGuard Home должен иметь доступ только для " +"чтения или для чтения и записи." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:129 +msgid "General Settings" +msgstr "Основные настройки" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:138 +msgid "" +"Go environment variables that tune garbage collector and memory management." +msgstr "" +"Переменные окружения Go, которые настраивают сборщик мусора и управление " +"памятью." + +#: applications/luci-app-adguardhome/root/usr/share/rpcd/acl.d/luci-app-adguardhome.json:3 +msgid "Grant permissions for the AdGuard Home LuCI app" +msgstr "Предоставить разрешения для приложения AdGuard Home в LuCI" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:181 +msgid "Group the service runs under." +msgstr "Группа, под которой работает служба." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:149 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:161 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:171 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:181 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:217 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:232 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:247 +msgid "If empty, defaults to" +msgstr "Если пусто, по умолчанию используется" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:139 +msgid "Modify at your own risk." +msgstr "Изменяйте на свой страх и риск." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:24 +msgid "Not running" +msgstr "Не запущено" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:148 +msgid "Parent directory will be owned by the service user." +msgstr "Родительский каталог будет принадлежать пользователю службы." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:83 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:99 +msgid "Path must be absolute." +msgstr "Путь должен быть абсолютным." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:86 +msgid "Path must not end with a slash." +msgstr "Путь не должен заканчиваться косой чертой." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:207 +msgid "Read-only access" +msgstr "Доступ только для чтения" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:208 +msgid "Read-write access" +msgstr "Доступ для чтения и записи" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:23 +msgid "Running" +msgstr "Запущено" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:122 +msgid "Service Status" +msgstr "Состояние службы" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:180 +msgid "Service group" +msgstr "Группа службы" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:170 +msgid "Service user" +msgstr "Пользователь службы" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:230 +msgid "" +"The maximum number of operating system threads that can execute user-level " +"Go code simultaneously." +msgstr "" +"Максимальное количество потоков операционной системы, которые могут " +"одновременно выполнять код Go на уровне пользователя." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:215 +msgid "" +"Tunes the garbage collector's aggressiveness by setting the percentage of " +"heap growth allowed before the next collection cycle triggers." +msgstr "" +"Настраивает агрессивность сборщика мусора, задавая процент роста кучи, " +"допустимый до запуска следующего цикла сборки." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:171 +msgid "User the service runs under." +msgstr "Пользователь, под которым работает служба." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:191 +msgid "Verbose logging" +msgstr "Подробное ведение журнала" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:119 +msgid "Version" +msgstr "Версия" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:160 +msgid "Will be owned by the service user." +msgstr "Будет принадлежать пользователю службы." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:158 +msgid "Working directory" +msgstr "Рабочий каталог" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:247 +msgid "unset" +msgstr "не задано" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:217 +msgid "unset and 100" +msgstr "не задано и 100" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:232 +msgid "unset and matching the number of CPUs" +msgstr "не задано и соответствует количеству процессоров" diff --git a/luci-app-adguardhome/po/sv/adguardhome.po b/luci-app-adguardhome/po/sv/adguardhome.po new file mode 100644 index 000000000..6d15ef89d --- /dev/null +++ b/luci-app-adguardhome/po/sv/adguardhome.po @@ -0,0 +1,177 @@ +msgid "" +msgstr "" +"Content-Type: text/plain; charset=UTF-8\n" +"Language: sv\n" +"Content-Transfer-Encoding: 8bit\n" +"Last-Translator: Daniel Nylander \n" +"Language-Team: Swedish \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"PO-Revision-Date: 2026-09-16 17:45+0000\n" +"X-Generator: Weblate 2026.10.dev0\n" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:244 +msgid "" +"A soft memory cap for the Go runtime, allowing the garbage collector to run " +"more frequently as usage approaches the limit to prevent Out-of-Memory (OOM) " +"kills." +msgstr "" +"En mjuk minnesgräns för Go-körmiljön som låter skräpsamlaren köras oftare " +"när användningen närmar sig gränsen, för att förhindra att processer " +"avslutas på grund av minnesbrist (OOM)." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:113 +#: applications/luci-app-adguardhome/root/usr/share/luci/menu.d/luci-app-adguardhome.json:3 +msgid "AdGuard Home" +msgstr "AdGuard Home" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:137 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:199 +msgid "Advanced Settings" +msgstr "Avancerade inställningar" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:146 +msgid "Configuration file" +msgstr "Konfigurationsfil" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:89 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:147 +msgid "" +"Configuration file must be stored in its own directory, and not in '/etc'." +msgstr "Konfigurationsfilen måste lagras i en egen katalog, inte i ”/etc”." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:159 +msgid "Directory where filters, logs, and statistics are stored." +msgstr "Katalog där filter, loggar och statistik lagras." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:132 +msgid "File System Access" +msgstr "Åtkomst till filsystemet" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:133 +msgid "" +"Files and directories that AdGuard Home should have read-only or read-write " +"access to." +msgstr "" +"Filer och kataloger som AdGuard Home ska ha skrivskyddad eller läs- och " +"skrivåtkomst till." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:129 +msgid "General Settings" +msgstr "Allmänna inställningar" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:138 +msgid "" +"Go environment variables that tune garbage collector and memory management." +msgstr "Go-miljövariabler som justerar skräpsamlaren och minneshanteringen." + +#: applications/luci-app-adguardhome/root/usr/share/rpcd/acl.d/luci-app-adguardhome.json:3 +msgid "Grant permissions for the AdGuard Home LuCI app" +msgstr "Bevilja behörigheter för AdGuard Home-appen i LuCI" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:181 +msgid "Group the service runs under." +msgstr "Gruppen som tjänsten körs under." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:149 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:161 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:171 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:181 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:217 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:232 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:247 +msgid "If empty, defaults to" +msgstr "Om den är tom används standardvärdet" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:139 +msgid "Modify at your own risk." +msgstr "Ändra på egen risk." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:24 +msgid "Not running" +msgstr "Körs inte" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:148 +msgid "Parent directory will be owned by the service user." +msgstr "Den överordnade katalogen ägs av tjänstanvändaren." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:83 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:99 +msgid "Path must be absolute." +msgstr "Sökvägen måste vara absolut." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:86 +msgid "Path must not end with a slash." +msgstr "Sökvägen får inte sluta med ett snedstreck." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:207 +msgid "Read-only access" +msgstr "Skrivskyddad åtkomst" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:208 +msgid "Read-write access" +msgstr "Läs- och skrivåtkomst" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:23 +msgid "Running" +msgstr "Körs" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:122 +msgid "Service Status" +msgstr "Status för tjänsten" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:180 +msgid "Service group" +msgstr "Tjänstegrupp" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:170 +msgid "Service user" +msgstr "Tjänstanvändare" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:230 +msgid "" +"The maximum number of operating system threads that can execute user-level " +"Go code simultaneously." +msgstr "" +"Det högsta antalet operativsystemtrådar som kan köra Go-kod på användarnivå " +"samtidigt." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:215 +msgid "" +"Tunes the garbage collector's aggressiveness by setting the percentage of " +"heap growth allowed before the next collection cycle triggers." +msgstr "" +"Justerar skräpsamlarens aggressivitet genom att ange hur stor procentuell " +"heapökning som tillåts innan nästa insamlingscykel startar." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:171 +msgid "User the service runs under." +msgstr "Användaren som tjänsten körs under." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:191 +msgid "Verbose logging" +msgstr "Detaljerad loggning" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:119 +msgid "Version" +msgstr "Version" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:160 +msgid "Will be owned by the service user." +msgstr "Ägs av tjänstanvändaren." + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:158 +msgid "Working directory" +msgstr "Arbetskatalog" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:247 +msgid "unset" +msgstr "inte inställt" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:217 +msgid "unset and 100" +msgstr "inte inställt och 100" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:232 +msgid "unset and matching the number of CPUs" +msgstr "inte inställt och samma antal som antalet processorer" diff --git a/luci-app-adguardhome/po/templates/adguardhome.pot b/luci-app-adguardhome/po/templates/adguardhome.pot new file mode 100644 index 000000000..2120d2646 --- /dev/null +++ b/luci-app-adguardhome/po/templates/adguardhome.pot @@ -0,0 +1,159 @@ +msgid "" +msgstr "Content-Type: text/plain; charset=UTF-8" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:244 +msgid "" +"A soft memory cap for the Go runtime, allowing the garbage collector to run " +"more frequently as usage approaches the limit to prevent Out-of-Memory (OOM) " +"kills." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:113 +#: applications/luci-app-adguardhome/root/usr/share/luci/menu.d/luci-app-adguardhome.json:3 +msgid "AdGuard Home" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:137 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:199 +msgid "Advanced Settings" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:146 +msgid "Configuration file" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:89 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:147 +msgid "" +"Configuration file must be stored in its own directory, and not in '/etc'." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:159 +msgid "Directory where filters, logs, and statistics are stored." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:132 +msgid "File System Access" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:133 +msgid "" +"Files and directories that AdGuard Home should have read-only or read-write " +"access to." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:129 +msgid "General Settings" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:138 +msgid "" +"Go environment variables that tune garbage collector and memory management." +msgstr "" + +#: applications/luci-app-adguardhome/root/usr/share/rpcd/acl.d/luci-app-adguardhome.json:3 +msgid "Grant permissions for the AdGuard Home LuCI app" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:181 +msgid "Group the service runs under." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:149 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:161 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:171 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:181 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:217 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:232 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:247 +msgid "If empty, defaults to" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:139 +msgid "Modify at your own risk." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:24 +msgid "Not running" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:148 +msgid "Parent directory will be owned by the service user." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:83 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:99 +msgid "Path must be absolute." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:86 +msgid "Path must not end with a slash." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:207 +msgid "Read-only access" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:208 +msgid "Read-write access" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:23 +msgid "Running" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:122 +msgid "Service Status" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:180 +msgid "Service group" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:170 +msgid "Service user" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:230 +msgid "" +"The maximum number of operating system threads that can execute user-level " +"Go code simultaneously." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:215 +msgid "" +"Tunes the garbage collector's aggressiveness by setting the percentage of " +"heap growth allowed before the next collection cycle triggers." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:171 +msgid "User the service runs under." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:191 +msgid "Verbose logging" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:119 +msgid "Version" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:160 +msgid "Will be owned by the service user." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:158 +msgid "Working directory" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:247 +msgid "unset" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:217 +msgid "unset and 100" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:232 +msgid "unset and matching the number of CPUs" +msgstr "" diff --git a/luci-app-adguardhome/po/tr/adguardhome.po b/luci-app-adguardhome/po/tr/adguardhome.po new file mode 100644 index 000000000..e7b219d09 --- /dev/null +++ b/luci-app-adguardhome/po/tr/adguardhome.po @@ -0,0 +1,168 @@ +msgid "" +msgstr "" +"Content-Type: text/plain; charset=UTF-8\n" +"Language: tr\n" +"Content-Transfer-Encoding: 8bit\n" +"Last-Translator: Martin Olmer \n" +"Language-Team: Turkish \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"PO-Revision-Date: 2026-08-31 19:11+0000\n" +"X-Generator: Weblate 2026.9.dev0\n" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:244 +msgid "" +"A soft memory cap for the Go runtime, allowing the garbage collector to run " +"more frequently as usage approaches the limit to prevent Out-of-Memory (OOM) " +"kills." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:113 +#: applications/luci-app-adguardhome/root/usr/share/luci/menu.d/luci-app-adguardhome.json:3 +msgid "AdGuard Home" +msgstr "AdGuard Home" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:137 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:199 +msgid "Advanced Settings" +msgstr "Gelişmiş Ayarlar" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:146 +msgid "Configuration file" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:89 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:147 +msgid "" +"Configuration file must be stored in its own directory, and not in '/etc'." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:159 +msgid "Directory where filters, logs, and statistics are stored." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:132 +msgid "File System Access" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:133 +msgid "" +"Files and directories that AdGuard Home should have read-only or read-write " +"access to." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:129 +msgid "General Settings" +msgstr "Genel Ayarlar" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:138 +msgid "" +"Go environment variables that tune garbage collector and memory management." +msgstr "" + +#: applications/luci-app-adguardhome/root/usr/share/rpcd/acl.d/luci-app-adguardhome.json:3 +msgid "Grant permissions for the AdGuard Home LuCI app" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:181 +msgid "Group the service runs under." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:149 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:161 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:171 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:181 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:217 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:232 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:247 +msgid "If empty, defaults to" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:139 +msgid "Modify at your own risk." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:24 +msgid "Not running" +msgstr "Çalışmıyor" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:148 +msgid "Parent directory will be owned by the service user." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:83 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:99 +msgid "Path must be absolute." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:86 +msgid "Path must not end with a slash." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:207 +msgid "Read-only access" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:208 +msgid "Read-write access" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:23 +msgid "Running" +msgstr "Çalışıyor" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:122 +msgid "Service Status" +msgstr "Hizmet Durumu" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:180 +msgid "Service group" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:170 +msgid "Service user" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:230 +msgid "" +"The maximum number of operating system threads that can execute user-level " +"Go code simultaneously." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:215 +msgid "" +"Tunes the garbage collector's aggressiveness by setting the percentage of " +"heap growth allowed before the next collection cycle triggers." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:171 +msgid "User the service runs under." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:191 +msgid "Verbose logging" +msgstr "Ayrıntılı günlük kaydı" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:119 +msgid "Version" +msgstr "Sürüm" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:160 +msgid "Will be owned by the service user." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:158 +msgid "Working directory" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:247 +msgid "unset" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:217 +msgid "unset and 100" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:232 +msgid "unset and matching the number of CPUs" +msgstr "" diff --git a/luci-app-adguardhome/po/uk/adguardhome.po b/luci-app-adguardhome/po/uk/adguardhome.po new file mode 100644 index 000000000..7e308efd5 --- /dev/null +++ b/luci-app-adguardhome/po/uk/adguardhome.po @@ -0,0 +1,169 @@ +msgid "" +msgstr "" +"Content-Type: text/plain; charset=UTF-8\n" +"Language: uk\n" +"Content-Transfer-Encoding: 8bit\n" +"Last-Translator: Martin Olmer \n" +"Language-Team: Ukrainian \n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " +"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" +"PO-Revision-Date: 2026-08-31 06:59+0000\n" +"X-Generator: Weblate 2026.9.dev0\n" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:244 +msgid "" +"A soft memory cap for the Go runtime, allowing the garbage collector to run " +"more frequently as usage approaches the limit to prevent Out-of-Memory (OOM) " +"kills." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:113 +#: applications/luci-app-adguardhome/root/usr/share/luci/menu.d/luci-app-adguardhome.json:3 +msgid "AdGuard Home" +msgstr "AdGuard Home" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:137 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:199 +msgid "Advanced Settings" +msgstr "Розширені налаштування" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:146 +msgid "Configuration file" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:89 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:147 +msgid "" +"Configuration file must be stored in its own directory, and not in '/etc'." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:159 +msgid "Directory where filters, logs, and statistics are stored." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:132 +msgid "File System Access" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:133 +msgid "" +"Files and directories that AdGuard Home should have read-only or read-write " +"access to." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:129 +msgid "General Settings" +msgstr "Загальні налаштування" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:138 +msgid "" +"Go environment variables that tune garbage collector and memory management." +msgstr "" + +#: applications/luci-app-adguardhome/root/usr/share/rpcd/acl.d/luci-app-adguardhome.json:3 +msgid "Grant permissions for the AdGuard Home LuCI app" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:181 +msgid "Group the service runs under." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:149 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:161 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:171 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:181 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:217 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:232 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:247 +msgid "If empty, defaults to" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:139 +msgid "Modify at your own risk." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:24 +msgid "Not running" +msgstr "Не працює" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:148 +msgid "Parent directory will be owned by the service user." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:83 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:99 +msgid "Path must be absolute." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:86 +msgid "Path must not end with a slash." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:207 +msgid "Read-only access" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:208 +msgid "Read-write access" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:23 +msgid "Running" +msgstr "Запущено" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:122 +msgid "Service Status" +msgstr "Стан служби" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:180 +msgid "Service group" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:170 +msgid "Service user" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:230 +msgid "" +"The maximum number of operating system threads that can execute user-level " +"Go code simultaneously." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:215 +msgid "" +"Tunes the garbage collector's aggressiveness by setting the percentage of " +"heap growth allowed before the next collection cycle triggers." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:171 +msgid "User the service runs under." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:191 +msgid "Verbose logging" +msgstr "Докладне журналювання" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:119 +msgid "Version" +msgstr "Версія" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:160 +msgid "Will be owned by the service user." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:158 +msgid "Working directory" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:247 +msgid "unset" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:217 +msgid "unset and 100" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:232 +msgid "unset and matching the number of CPUs" +msgstr "" diff --git a/luci-app-adguardhome/po/zh_Hans/adguardhome.po b/luci-app-adguardhome/po/zh_Hans/adguardhome.po index deaa0907b..a00b550da 100644 --- a/luci-app-adguardhome/po/zh_Hans/adguardhome.po +++ b/luci-app-adguardhome/po/zh_Hans/adguardhome.po @@ -1,179 +1,173 @@ msgid "" msgstr "" "Content-Type: text/plain; charset=UTF-8\n" -"Project-Id-Version: PACKAGE VERSION\n" -"Last-Translator: Automatically generated\n" -"Language-Team: none\n" -"Language: zh\n" -"MIME-Version: 1.0\n" +"Language: zh_Hans\n" "Content-Transfer-Encoding: 8bit\n" +"Last-Translator: Hosted Weblate user 54392 " +"\n" +"Language-Team: Chinese (Simplified Han script) \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"PO-Revision-Date: 2026-08-31 06:44+0000\n" +"X-Generator: Weblate 2026.9.dev0\n" -msgid "AdGuard Home" +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:244 +msgid "" +"A soft memory cap for the Go runtime, allowing the garbage collector to run " +"more frequently as usage approaches the limit to prevent Out-of-Memory (OOM) " +"kills." msgstr "" +"Go 运行时的软性内存限制,允许垃圾收集器在内存占用接近限制时更频繁地运行防止内" +"存耗尽终止程序。" -msgid "AdGuard Home Configuration" -msgstr "配置" +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:113 +#: applications/luci-app-adguardhome/root/usr/share/luci/menu.d/luci-app-adguardhome.json:3 +msgid "AdGuard Home" +msgstr "AdGuard Home" -msgid "AdGuard Home Statistics" -msgstr "统计" +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:137 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:199 +msgid "Advanced Settings" +msgstr "高级设置" -msgid "AdGuard Home Status" -msgstr "状态" +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:146 +msgid "Configuration file" +msgstr "配置文件" -msgid "AdGuard Home Status - Error" -msgstr "状态 - 错误" +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:89 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:147 +msgid "" +"Configuration file must be stored in its own directory, and not in '/etc'." +msgstr "配置文件必须存储在自己的目录中,不能在 '/etc' 中。" -msgid "Average processing time (seconds)" -msgstr "平均处理时间(秒)" +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:159 +msgid "Directory where filters, logs, and statistics are stored." +msgstr "存储过滤器、日志和统计数据的目录。" -msgid "Bootstrap DNS addresses" -msgstr "Bootstrap DNS 地址" +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:132 +msgid "File System Access" +msgstr "文件系统访问" -msgid "Configuration" -msgstr "配置" +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:133 +msgid "" +"Files and directories that AdGuard Home should have read-only or read-write " +"access to." +msgstr "AdGuard Home 应该有只读或读写权限的文件和目录。" -msgid "DNS blocks" -msgstr "DNS 拦截" +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:129 +msgid "General Settings" +msgstr "通用设置" -msgid "DNS listen addresses" -msgstr "DNS 监听地址" - -msgid "DNS listen port" -msgstr "DNS 监听端口" - -msgid "DNS queries" -msgstr "DNS 查询" - -msgid "DNS replacements (malware/phishing)" -msgstr "DNS 替换(恶意软件/钓鱼)" - -msgid "DNS replacements (safesearch)" -msgstr "DNS 替换(安全搜索)" - -msgid "General settings" -msgstr "常规设置" +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:138 +msgid "" +"Go environment variables that tune garbage collector and memory management." +msgstr "调节垃圾收集器和内存管理的 Go 环境变量。" +#: applications/luci-app-adguardhome/root/usr/share/rpcd/acl.d/luci-app-adguardhome.json:3 msgid "Grant permissions for the AdGuard Home LuCI app" -msgstr "授予 AdGuard Home LuCI 应用权限" +msgstr "授予 AdGuard Home LuCI 应用的权限" -msgid "Logs" -msgstr "日志" +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:181 +msgid "Group the service runs under." +msgstr "服务运行在什么组下面。" -msgid "No" -msgstr "否" +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:149 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:161 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:171 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:181 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:217 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:232 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:247 +msgid "If empty, defaults to" +msgstr "如为空,默认为" -msgid "Not found" -msgstr "未找到" +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:139 +msgid "Modify at your own risk." +msgstr "修改风险自负。" -msgid "Password for AdGuard Home" -msgstr "密码" +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:24 +msgid "Not running" +msgstr "未在运行" -msgid "Please open the Configuration section, and provide the credentials." -msgstr "请打开配置部分,并提供凭证。" +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:148 +msgid "Parent directory will be owned by the service user." +msgstr "主目录将被服务用户所拥有。" -msgid "Protection enabled" -msgstr "保护已启用" +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:83 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:99 +msgid "Path must be absolute." +msgstr "必须是绝对路径。" -msgid "RPC call failure:" -msgstr "RPC 调用失败:" +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:86 +msgid "Path must not end with a slash." +msgstr "路径不能用 / 结束。" +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:207 +msgid "Read-only access" +msgstr "只读权限" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:208 +msgid "Read-write access" +msgstr "读写权限" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:23 msgid "Running" msgstr "运行中" -msgid "Showing last 50 lines" -msgstr "显示最近的50行" +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:122 +msgid "Service Status" +msgstr "服务状态" -msgid "Statistics period (days)" -msgstr "统计周期(天)" +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:180 +msgid "Service group" +msgstr "服务组" -msgid "Status" -msgstr "状态" - -msgid "System Log (AdGuard Home)" -msgstr "系统日志" - -msgid "The password you configured when you set up AdGuard Home" -msgstr "您在设置 AdGuard Home 时配置的密码" - -msgid "The username you configured when you set up AdGuard Home" -msgstr "您在设置 AdGuard Home 时配置的用户名" +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:170 +msgid "Service user" +msgstr "服务用户" +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:230 msgid "" -"This application requires the username and password that were configured " -"when you set up AdGuard Home, as the REST API for AdGuard Home is password " -"protected. The password cannot be read from the YAML configuration file for " -"AdGuard Home, as it is encrypted in that store. The credentials supplied " -"here will be stored unencrypted in /etc/config/adguardhome on your device." +"The maximum number of operating system threads that can execute user-level " +"Go code simultaneously." +msgstr "可同时执行用户级 Go 代码的操作系统线程的最大数量。" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:215 +msgid "" +"Tunes the garbage collector's aggressiveness by setting the percentage of " +"heap growth allowed before the next collection cycle triggers." msgstr "" -"此应用程序需要您在设置 AdGuard Home 时配置的用户名和密码,因为 AdGuard Home 的 REST API 受密码保护。" -"密码无法从 AdGuard Home 的 YAML 配置文件中读取,因为它在存储中是加密的。" -"此处提供的凭证将以未加密的形式存储在设备上的 /etc/config/adguardhome 中。" +"通过设置下个垃圾收集周期触发前允许的堆的百分比增速来调节垃圾收集器的激进程" +"度。" -msgid "Top Blocked Domains" -msgstr "最常被拦截的域名" +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:171 +msgid "User the service runs under." +msgstr "服务运行在什么用户下面。" -msgid "Top Queried Domains" -msgstr "最常查询的域名" - -msgid "Unable to load log data:" -msgstr "无法加载日志数据:" - -msgid "Upstream DNS addresses" -msgstr "上游 DNS 地址" - -msgid "Username for AdGuard Home" -msgstr "用户名" +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:191 +msgid "Verbose logging" +msgstr "详细日志记录" +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:119 msgid "Version" msgstr "版本" -msgid "Web interface" -msgstr "AdGuardHome 管理界面" +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:160 +msgid "Will be owned by the service user." +msgstr "将被服务用户所拥有。" -msgid "Yes" -msgstr "是" - -msgid "RUNNING" -msgstr "运行中" - -msgid "NOT RUNNING" -msgstr "未运行" - -msgid "Collecting data..." -msgstr "获取数据中..." - -msgid "Enable" -msgstr "启用" - -msgid "Redirect" -msgstr "重定向" - -msgid "none" -msgstr "无" - -msgid "Run as dnsmasq upstream server" -msgstr "作为dnsmasq的上游服务器" - -msgid "Redirect 53 port to AdGuardHome" -msgstr "重定向53端口到AdGuardHome" - -msgid "Use port 53 replace dnsmasq" -msgstr "使用53端口替换dnsmasq" - -msgid "AdGuardHome redirect mode" -msgstr "重定向模式" - -msgid "Change Password" -msgstr "修改密码" - -msgid "Work Dir" +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:158 +msgid "Working directory" msgstr "工作目录" -msgid "Service user" -msgstr "以此用户权限运行" +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:247 +msgid "unset" +msgstr "取消设置" -msgid "Service group" -msgstr "以此用户组权限运行" +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:217 +msgid "unset and 100" +msgstr "取消设置和 100" -msgid "Configuration file" -msgstr "配置文件" +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:232 +msgid "unset and matching the number of CPUs" +msgstr "取消设置并匹配 CPU 数目" diff --git a/luci-app-adguardhome/po/zh_Hant/adguardhome.po b/luci-app-adguardhome/po/zh_Hant/adguardhome.po new file mode 100644 index 000000000..902dc225e --- /dev/null +++ b/luci-app-adguardhome/po/zh_Hant/adguardhome.po @@ -0,0 +1,168 @@ +msgid "" +msgstr "" +"Content-Type: text/plain; charset=UTF-8\n" +"Language: zh_Hant\n" +"Content-Transfer-Encoding: 8bit\n" +"Last-Translator: 為什麼不加空格 \n" +"Language-Team: Chinese (Traditional Han script) \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"PO-Revision-Date: 2026-09-02 20:59+0000\n" +"X-Generator: Weblate 2026.9.dev0\n" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:244 +msgid "" +"A soft memory cap for the Go runtime, allowing the garbage collector to run " +"more frequently as usage approaches the limit to prevent Out-of-Memory (OOM) " +"kills." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:113 +#: applications/luci-app-adguardhome/root/usr/share/luci/menu.d/luci-app-adguardhome.json:3 +msgid "AdGuard Home" +msgstr "AdGuard Home" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:137 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:199 +msgid "Advanced Settings" +msgstr "進階設定" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:146 +msgid "Configuration file" +msgstr "配置檔案" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:89 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:147 +msgid "" +"Configuration file must be stored in its own directory, and not in '/etc'." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:159 +msgid "Directory where filters, logs, and statistics are stored." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:132 +msgid "File System Access" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:133 +msgid "" +"Files and directories that AdGuard Home should have read-only or read-write " +"access to." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:129 +msgid "General Settings" +msgstr "一般設定" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:138 +msgid "" +"Go environment variables that tune garbage collector and memory management." +msgstr "" + +#: applications/luci-app-adguardhome/root/usr/share/rpcd/acl.d/luci-app-adguardhome.json:3 +msgid "Grant permissions for the AdGuard Home LuCI app" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:181 +msgid "Group the service runs under." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:149 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:161 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:171 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:181 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:217 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:232 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:247 +msgid "If empty, defaults to" +msgstr "如果為空,預設為" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:139 +msgid "Modify at your own risk." +msgstr "修改風險自負。" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:24 +msgid "Not running" +msgstr "未執行" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:148 +msgid "Parent directory will be owned by the service user." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:83 +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:99 +msgid "Path must be absolute." +msgstr "必須為絕對路徑。" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:86 +msgid "Path must not end with a slash." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:207 +msgid "Read-only access" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:208 +msgid "Read-write access" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:23 +msgid "Running" +msgstr "執行中" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:122 +msgid "Service Status" +msgstr "服務狀態" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:180 +msgid "Service group" +msgstr "服務群組" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:170 +msgid "Service user" +msgstr "服務使用者" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:230 +msgid "" +"The maximum number of operating system threads that can execute user-level " +"Go code simultaneously." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:215 +msgid "" +"Tunes the garbage collector's aggressiveness by setting the percentage of " +"heap growth allowed before the next collection cycle triggers." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:171 +msgid "User the service runs under." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:191 +msgid "Verbose logging" +msgstr "詳細記錄" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:119 +msgid "Version" +msgstr "版本" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:160 +msgid "Will be owned by the service user." +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:158 +msgid "Working directory" +msgstr "工作目錄" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:247 +msgid "unset" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:217 +msgid "unset and 100" +msgstr "" + +#: applications/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js:232 +msgid "unset and matching the number of CPUs" +msgstr "" diff --git a/luci-app-adguardhome/root/etc/adguardhome/adguardhome.yaml b/luci-app-adguardhome/root/etc/adguardhome/adguardhome.yaml index e5c0a5097..a32a4d07a 100644 --- a/luci-app-adguardhome/root/etc/adguardhome/adguardhome.yaml +++ b/luci-app-adguardhome/root/etc/adguardhome/adguardhome.yaml @@ -2,11 +2,18 @@ http: pprof: port: 6060 enabled: false - address: 0.0.0.0:3000 - session_ttl: 720h + doh: + routes: + - GET /dns-query + - POST /dns-query + - GET /dns-query/{ClientID} + - POST /dns-query/{ClientID} + insecure_enabled: false + address: 0.0.0.0:3008 + session_ttl: 30d users: - - name: admin - password: $2y$10$.ujnc.Bphsaz4Z.SbOoMiux2z8DxpJUaIrD5gcITed.vKx.qrZSfC + - name: root + password: auth_attempts: 5 block_auth_min: 15 http_proxy: "" @@ -15,22 +22,26 @@ theme: auto dns: bind_hosts: - 0.0.0.0 - port: 5333 + port: 1745 anonymize_client_ip: false - ratelimit: 20 + ratelimit: 0 ratelimit_subnet_len_ipv4: 24 ratelimit_subnet_len_ipv6: 56 ratelimit_whitelist: [] refuse_any: true upstream_dns: - - https://dns10.quad9.net/dns-query + - '#https://dns10.quad9.net/dns-query' + - 223.5.5.5 + - 2400:3200::1 + - https://dns.alidns.com/dns-query upstream_dns_file: "" bootstrap_dns: - - 9.9.9.10 - - 149.112.112.10 - - 2620:fe::10 - - 2620:fe::fe:10 - fallback_dns: [] + - 119.29.29.29 + - 114.114.114.114 + fallback_dns: + - 119.29.29.29 + - 113.31.119.88 + - 2400:7fc0:849e:200:62fd:1de3:1c90:1 upstream_mode: parallel fastest_timeout: 1s allowed_clients: [] @@ -42,16 +53,19 @@ 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: true + cache_optimistic_answer_ttl: 30s + cache_optimistic_max_age: 12h bogus_nxdomain: [] aaaa_disabled: false - enable_dnssec: false + enable_dnssec: true edns_client_subnet: custom_ip: "" - enabled: false + enabled: true use_custom: false max_goroutines: 300 handle_ddr: true @@ -61,13 +75,16 @@ dns: upstream_timeout: 10s private_networks: [] use_private_ptr_resolvers: true - local_ptr_upstreams: [] + local_ptr_upstreams: + - 127.0.0.1:1745 use_dns64: false dns64_prefixes: [] serve_http3: false use_http3_upstreams: false serve_plain_dns: true hostsfile_enabled: true + pending_requests: + enabled: true tls: enabled: false server_name: "" @@ -77,7 +94,6 @@ tls: port_dns_over_quic: 853 port_dnscrypt: 0 dnscrypt_config_file: "" - allow_unencrypted_doh: false certificate_chain: "" private_key: "" certificate_path: "" @@ -85,16 +101,26 @@ tls: strict_sni_check: false querylog: dir_path: "" - ignored: [] - interval: 2160h + ignored: + - orirainbow.cc^ + - orirainwe.edu.kg^ + - coriseay.edu.kg^ + - google.com^ + interval: 7d size_memory: 1000 enabled: true + ignored_enabled: true file_enabled: true statistics: dir_path: "" - ignored: [] - interval: 24h + ignored: + - orirainbow.cc^ + - orirainwe.edu.kg^ + - coriseay.edu.kg^ + - google.com^ + interval: 7d enabled: true + ignored_enabled: true filters: - enabled: true url: https://adguardteam.github.io/HostlistsRegistry/assets/filter_1.txt @@ -105,7 +131,108 @@ filters: name: AdAway Default Blocklist id: 2 whitelist_filters: [] -user_rules: [] +user_rules: + - '@@||ntfy.sh^' + - '@@||pubnub.com^' + - '@@||clarity.ms^$important' + - '@@||sh-gsg.com^$important' + - '@@||sh-mtgc.com^$important' + - '@@||taobao.com^$important' + - '@@||tmall.com^$important' + - '@@||jd.com^important' + - '@@||flyme.cn^$important' + - '@@||meizu.com^$important' + - '@@||flydigi.com^' + - '@@||pv.sohu.com^$important' + - '@@||trafficmanager.net^' + - '||yify.foo^' + - '||www.668cbb.com/static/adScript.js' + - '||topnewsfeeds.net^' + - '||nbryb.com^' + - '||superbdolly.com^' + - '||salutationcheerlessdemote.com^' + - '||buildingdoodlesquare.com^' + - '||colonistnobilityheroic.com^' + - '#解除 小米 及其所有子域名的封锁;' + - '@@||mi.com^' + - '@@||mi.cn^' + - '@@||xiaomi.com^' + - '@@||miui.com^' + - '@@||xiaomi.net^' + - '@@||xiaomi-mipdx.cn.miaozhen.com^' + - '@@||xiaomi-dtv.m.cn.miaozhen.com^' + - '@@||miwifi.com^' + - '@@||tracker.ai.xiaomi.com^$important' + - '@@||tracking.miui.com^$important' + - '#解除 Apple 及其所有子域名的封锁;' + - '@@||apple.com^' + - '@@||apple.com.cn^' + - '@@||icloud.com^' + - '@@||icloud.com.cn^' + - '#解除 cloudflare 及其所有子域名的封锁;' + - '@@||static.cloudflareinsights.com^$important' + - '@@||cloudflare.com^' + - '@@||cloudflare-cn.com^' + - '@@||argotunnel.com^' + - '#解除 microsoft 及其所有子域名的封锁;' + - '@@||microsoft.com^$important' + - '@@||windows.com^$important' + - '@@||microsoftonline.com^$important' + - '@@||windowsupdate.com^$important' + - '@@||office.com^$important' + - '@@||live.com^$important' + - '@@||skype.com^$important' + - '@@||msn.com^$important' + - '@@||bing.com^$important' + - '@@||msftconnecttest.com^$important' + - '@@||res.public.onecdn.static.microsoft^$important' + - '@@||inference.location.live.net^$important' + - '@@||static.edge.microsoftapp.net^$important' + - '@@||ipv6.msftconnecttest.com^$important' + - '@@||sentry.io^$important' + - '@@||metrics1.data.hicloud.com^$important' + - '@@||otheve.beacon.qq.com^$important' + - '@@||app-measurement.com^$important' + - '@@||tmdb.org^' + - '@@||themoviedb.org^' + - /googleads.$~script,domain=~googleads.github.io + - /pagead/lvz? + - '||google.com/pagead/' + - '||static.doubleclick.net^$domain=youtube.com' + - '||youtube.com/get_midroll_' + - '||5hz.org^' + - '||evewan.com^' + - '||stgowan.com^' + - '||51gowan.com^' + - '||www.66s6.net/d/js/acmsd/thea5.js' + - '||www.66s6.net/d/js/acmsd/thea6.js' + - '||sogowan.com^' + - '||52xkdy.com^' + - '||ttzj666.com^' + - '||888dyy.cc^' + - '||qisaoba.com^' + - '||euqq.cn^' + - '||wkyaoye.com^' + - '||qisaoba.com^' + - '@||adobe.io^' + - '@||adobestats.io' + - '@||adobe.com^' + - '@||gocart-web-prod-ue1-alb-1461435473.us-east-1.elb.amazonaws.com' + - '@@||dig.bdurl.net^$important' + - '@@||android.bugly.qq.com^$important' + - '@@||yun-hl.3g.qq.com^$important' + - '@@||collector.github.com^$important' + - '@@||geolocation.onetrust.com^$important' + - '@@||google-analytics.com^$important' + - '@@||client-upload.cn-shanghai.log.aliyuncs.com^$important' + - '@@||twingate.com^' + - '@@||syncthing.net^' + - '@@||loggw-ex.alipay.com^$important' + - '@@||hdfans.org^' + - '@@||1ptba.com^' + - '@@||ptcafe.club^' + - '||nbpicture.xyz^' + - "" dhcp: enabled: false interface_name: "" @@ -145,7 +272,7 @@ filtering: safebrowsing_block_host: standard-block.dns.adguard.com rewrites: [] safe_fs_patterns: - - /tmp/lib/adguardhome/userfilters/* + - /var/lib/adguardhome/userfilters/* safebrowsing_cache_size: 1048576 safesearch_cache_size: 1048576 parental_cache_size: 1048576 @@ -153,6 +280,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 @@ -177,4 +305,4 @@ os: group: "" user: "" rlimit_nofile: 0 -schema_version: 29 +schema_version: 34 diff --git a/luci-app-adguardhome/root/etc/capabilities/adguardhome.json b/luci-app-adguardhome/root/etc/capabilities/adguardhome.json new file mode 100644 index 000000000..3586a4ae9 --- /dev/null +++ b/luci-app-adguardhome/root/etc/capabilities/adguardhome.json @@ -0,0 +1,22 @@ +{ + "bounding": [ + "CAP_NET_BIND_SERVICE", + "CAP_NET_RAW" + ], + "effective": [ + "CAP_NET_BIND_SERVICE", + "CAP_NET_RAW" + ], + "ambient": [ + "CAP_NET_BIND_SERVICE", + "CAP_NET_RAW" + ], + "permitted": [ + "CAP_NET_BIND_SERVICE", + "CAP_NET_RAW" + ], + "inheritable": [ + "CAP_NET_BIND_SERVICE", + "CAP_NET_RAW" + ] +} diff --git a/luci-app-adguardhome/root/etc/config/adguardhome b/luci-app-adguardhome/root/etc/config/adguardhome new file mode 100644 index 000000000..598f3166f --- /dev/null +++ b/luci-app-adguardhome/root/etc/config/adguardhome @@ -0,0 +1,33 @@ + +config adguardhome 'config' + # All paths must be readable by the configured user + option config_file '/etc/adguardhome/adguardhome.yaml' + # Where to store persistent data by AdGuard Home + option work_dir '/var/lib/adguardhome' + option user 'adguardhome' + option group 'adguardhome' + option verbose '0' + option enabled '0' + option redirect 'none' + option core_version 'latest' + option update_url 'https://static.adtidy.org/adguardhome/release/AdGuardHome_linux_${Arch}.tar.gz' + + # Files and directories that AdGuard Home has read-only access to + # list jail_mount '/etc/ssl/adguardhome.crt' + # list jail_mount '/etc/ssl/adguardhome.key' + + # Files and directories that AdGuard Home has read-write access to + # list jail_mount_rw '/path/to/dir' + + # Advanced options. Modify at your own risk. + + # More info: https://go.dev/doc/gc-guide#GOGC + option gc '0' + + # Max number of OS threads to use + # 0 to match the number of CPUs (default) + # >0 to explicitly specify concurrency + option maxprocs '0' + + # Soft memory limit in MB, 0 to disable + option memlimit '0' diff --git a/luci-app-adguardhome/root/etc/init.d/adguardhome b/luci-app-adguardhome/root/etc/init.d/adguardhome index 3ac88c37a..642258581 100755 --- a/luci-app-adguardhome/root/etc/init.d/adguardhome +++ b/luci-app-adguardhome/root/etc/init.d/adguardhome @@ -1,136 +1,101 @@ #!/bin/sh /etc/rc.common +# +# SPDX-License-Identifier: GPL-2.0-only +# +# shellcheck disable=SC3043 # ash supports local +. /usr/share/AdGuardHome/helper.sh -# Start after network (19 matches dnsmasq) +# matches dnsmasq START=19 -# Stop before network stops +# stops before networking stops STOP=89 PROG=/usr/bin/AdGuardHome USE_PROCD=1 +EXTRA_COMMANDS="do_redirect force_reload isrunning" +EXTRA_HELP="\ +\tdo_redirect \t0 or 1\n\ +\tforce_reload\n\ +\tisrunning" + +CONTROL_SCRIPT="/usr/share/AdGuardHome/control.sh" +CRON_FILE=/etc/crontabs/root + +get_tz() { + SET_TZ="" + local zonename + zonename="$(uci get system.@system[0].zonename 2>/dev/null)" + if [ -n "$zonename" ]; then + SET_TZ="$zonename" + return + fi + for tzfile in /etc/TZ /var/etc/TZ; do + [ -e "$tzfile" ] && tz="$(cat "$tzfile" 2>/dev/null)" && break + done + [ -n "$tz" ] && SET_TZ=$tz && return +} + +do_crontab() { + local enabled="$1" + local crontab="$2" + local findstr default cronenable cronreload=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 + + [ "$cronreload" -gt 0 ] && /etc/init.d/cron restart +} + +crontab_editor() { + local line + line="$(grep "$findstr" $CRON_FILE 2>/dev/null)" + if [ "${line:0:1}" != "#" ]; then + if [ "$cronenable" -eq 1 ]; then + if [ -z "$line" ]; then + echo "$default" >> $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)) + fi + fi +} + boot() { ADGUARDHOME_BOOT=1 start "$@" } -# Configure DNS to use port 53 -use_port53() { - local dns_port="$1" - local adguard_port="$2" - local config_file="$3" - - [ "$dns_port" != "53" ] && return - - # Update AdGuardHome config - sed -i '/dns:/,/^[^ ]/{ /[[:space:]]*port:/ s/port:[[:space:]]*[0-9]\+/port: 53/ }' "$config_file" - - # Update dnsmasq config - uci set "dhcp.@dnsmasq[0].port=$adguard_port" - uci commit dhcp - /etc/init.d/dnsmasq restart -} - -# Restore original port configuration -rm_port53() { - local current_port="$1" - local new_port="$2" - local config_file="$3" - - [ "$current_port" != "53" ] && return - - # Update AdGuardHome config - sed -i '/dns:/,/^[^ ]/{ /[[:space:]]*port:/ s/port:[[:space:]]*[0-9]\+/port: '"$new_port"'/ }' "$config_file" - - # Restore dnsmasq config - uci -q set "dhcp.@dnsmasq[0].port=53" - uci commit dhcp - /etc/init.d/dnsmasq restart -} - -# Set up firewall redirect rules -set_redirect() { - local dest_port="$1" - local section - - # Add new redirect section - section=$(uci add firewall redirect) - [ -z "$section" ] && return - [ "$(uci -q get "firewall.$section.dest_port")" = "$dest_port" ] && return - - # Configure redirect rules - uci -q set "firewall.$section.target=DNAT" - uci -q set "firewall.$section.name=AdGuard Home" - uci -q set "firewall.$section.src=lan" - uci -q set "firewall.$section.src_dport=53" - uci -q set "firewall.$section.dest_port=$dest_port" - - # Apply changes - uci commit firewall - /etc/init.d/firewall reload - echo "Firewall rules updated." -} - -# Clear all AdGuard Home redirect rules -clear_redirect() { - local redirects - redirects=$(uci show firewall | grep "AdGuard Home" | cut -d'.' -f1-2) - [ -z "$redirects" ] && return - - for redirect in $redirects; do - uci -q delete "$redirect" - echo "Deleted redirect rule: $redirect" - done - - uci commit firewall - /etc/init.d/firewall reload - echo "Firewall rules updated." -} - -# Configure dnsmasq to forward requests to AdGuard Home -set_forward_dnsmasq() { - local port="$1" - local current_server="$2" - local addr="127.0.0.1#$port" - - echo "$current_server" | grep -q "^$addr" && return - - uci -q add_list dhcp.@dnsmasq[0].server="$addr" - uci -q delete "dhcp.@dnsmasq[0].resolvfile" - uci set "dhcp.@dnsmasq[0].noresolv=1" - uci commit dhcp - /etc/init.d/dnsmasq restart -} - -# Stop forwarding requests to AdGuard Home -stop_forward_dnsmasq() { - local port="$1" - local current_server="$2" - local addr="127.0.0.1#$port" - - echo "$current_server" | grep -q "^$addr" || return - - uci -q delete "dhcp.@dnsmasq[0].server" - uci -q delete "dhcp.@dnsmasq[0].noresolv" - uci commit dhcp - /etc/init.d/dnsmasq restart -} - -# Start AdGuard Home service start_service() { + mkdir -p /var/etc + [ ! -f /var/etc/adguardhome.nft ] && touch /var/etc/adguardhome.nft + chmod 644 /var/etc/adguardhome.nft + if [ -n "$ADGUARDHOME_BOOT" ]; then # Do not start yet, wait for triggers return 0 fi + rm -f /var/run/AdGserverdis 2>/dev/null config_load 'adguardhome' local config_name='config' local config_file config group user verbose work_dir workdir - local gc maxprocs memlimit redirect enabled + local gc maxprocs memlimit + local enabled hashpass crontab redirect uci_validate_section 'adguardhome' 'adguardhome' "$config_name" \ - 'enabled:bool:0' \ - 'redirect:string:none' \ 'gc:uinteger:0' \ 'group:string:adguardhome' \ 'config:string' \ @@ -142,13 +107,45 @@ start_service() { 'user:string:adguardhome' \ 'verbose:bool:0' \ 'workdir:string' \ - 'work_dir:string:/var/lib/adguardhome' + 'work_dir:string:/var/lib/adguardhome' \ + 'enabled:bool:0' \ + 'hashpass:string' \ + 'crontab:string' \ + 'redirect:string:none' \ + 'http_address:string' # Compatibility with older configs [ -n "$config" ] && config_file="$config" [ -n "$workdir" ] && work_dir="$workdir" - [ "$enabled" -eq 0 ] && return 1 + # Handle hashpass clear text issuance and back brush removal + if [ -n "$hashpass" ]; then + config_editor "users.password" "$hashpass" "$config_file" + uci set adguardhome."$config_name".hashpass="" + uci commit adguardhome + fi + + # Synchronize the front-end incing WebUI listening address to YAML + if [ -n "$http_address" ]; then + config_editor "http.address" "$http_address" "$config_file" + uci set adguardhome."$config_name".http_address="" + uci commit adguardhome + fi + + # Isse the plan task change + do_crontab "$enabled" "$crontab" + + # If it is not enabled, the linkage cleans up the redirection firewall rules and exits safely. + if [ "$enabled" = 0 ]; then + $CONTROL_SCRIPT 0 + return 0 + fi + + if [ "$user" = "adguardhome" ] && ! grep -q "^adguardhome:" /etc/passwd; then + echo "luci-app: User 'adguardhome' is missing, injecting UID 853 into system files..." >&2 + grep -q "^adguardhome:" /etc/group || echo "adguardhome:x:853:" >> /etc/group + echo "adguardhome:x:853:853:adguardhome:/var/run/adguardhome:/bin/false" >> /etc/passwd + fi local config_dir config_dir=$(dirname "$config_file") @@ -161,26 +158,15 @@ start_service() { mkdir -m 0700 -p "$work_dir" chown -R "$user":"$group" "$work_dir" - - # Get ports configuration - local AdGuardHome_PORT=$(awk '/dns:/{f=1} f&&/port:/{split($0,a,": *");print a[2];exit}' "$config_file") - local dnsmasq_port=$(uci -q get dhcp.@dnsmasq[0].port) - local DNSMASQ_SERVER=$(uci -q get dhcp.@dnsmasq[0].server) - # Configure redirect mode - case "$redirect" in - "redirect") - set_redirect "$AdGuardHome_PORT" - ;; - "dnsmasq-upstream") - set_forward_dnsmasq "$AdGuardHome_PORT" "$DNSMASQ_SERVER" - ;; - "exchange") - use_port53 "$dnsmasq_port" "$AdGuardHome_PORT" "$config_file" - ;; - esac + # Silently downgrade and call the kernel download script when the kernel is missing. + if [ ! -f "$PROG" ]; then + $CONTROL_SCRIPT 0 + /usr/share/AdGuardHome/update_core.sh >/tmp/AdGuardHome_update.log 2>&1 & + return 0 + fi - procd_open_instance adguardhome + procd_open_instance adguardhome procd_set_param command "$PROG" @@ -188,12 +174,17 @@ start_service() { [ "$maxprocs" -le 0 ] || procd_append_param env GOMAXPROCS="$maxprocs" [ "$memlimit" -le 0 ] || procd_append_param env GOMEMLIMIT="$memlimit" + local SET_TZ + get_tz + [ -n "$SET_TZ" ] && procd_append_param env TZ="$SET_TZ" + procd_append_param command --config "$config_file" procd_append_param command --logfile syslog - procd_append_param command --no-check-update + # procd_append_param command --no-check-update [ "$verbose" = 1 ] && procd_append_param command --verbose procd_append_param command --work-dir "$work_dir" + procd_set_param file /etc/config/adguardhome "$config_file" procd_set_param stdout 1 procd_set_param stderr 1 procd_set_param user "$user" @@ -212,47 +203,70 @@ start_service() { procd_add_jail_mount '/etc/hosts' procd_add_jail_mount '/etc/ssl/certs' + [ -f /etc/TZ ] && procd_add_jail_mount '/etc/TZ' + config_list_foreach "$config_name" jail_mount procd_add_jail_mount config_list_foreach "$config_name" jail_mount_rw procd_add_jail_mount_rw procd_close_instance -} -# Stop AdGuard Home service -stop_service() { - if [ -n "$ADGUARDHOME_BOOT" ]; then - # Do not start yet, wait for triggers - return 0 + # After the main process is successfully submitted for start-up, it begins to intervene asynchronously and take over the control. + if [ -f "$config_file" ]; then + ($CONTROL_SCRIPT 1) & + else + $CONTROL_SCRIPT 0 + if [ "$redirect" != "none" ]; then + procd_open_instance "waitconfig" + procd_set_param command "/usr/share/AdGuardHome/watchconfig.sh" + procd_close_instance + fi fi - local config_file='/etc/adguardhome/adguardhome.yaml' + (sleep 10 && [ -z "$(pgrep -f "$(basename "$PROG")")" ] && logger -t adguardhome "no process in 10s cancel redirect" && $CONTROL_SCRIPT 0 ) & +} +stop_service() { config_load 'adguardhome' - - # Get ports configuration - local AdGuardHome_PORT=$(awk '/dns:/{f=1} f&&/port:/{split($0,a,": *");print a[2];exit}' "$config_file") - local dnsmasq_port=$(uci -q get dhcp.@dnsmasq[0].port) - local DNSMASQ_SERVER=$(uci -q get dhcp.@dnsmasq[0].server) - - # Clean up configurations - rm_port53 "$AdGuardHome_PORT" "$dnsmasq_port" "$config_file" - clear_redirect - stop_forward_dnsmasq "$AdGuardHome_PORT" "$DNSMASQ_SERVER" + local config_name='config' + local crontab + config_get crontab "$config_name" crontab "" + + $CONTROL_SCRIPT 0 + do_crontab 0 "$crontab" + touch /var/run/AdGserverdis } -# Reload service -reload_service() { - restart +do_redirect() { + $CONTROL_SCRIPT "$1" +} + +_isrunning() { + [ ! -f "$PROG" ] && return 2 + pgrep -f "$(basename "$PROG")" >/dev/null 2>&1 && return 0 + return 1 +} + +isrunning() { + _isrunning + local r=$? + [ "$r" = "0" ] && echo "running" || ([ "$r" = "1" ] && echo "not run" || echo "no bin") + return $r +} + +force_reload() { + _isrunning && procd_send_signal adguardhome || start } -# Service triggers service_triggers() { - procd_add_reload_trigger adguardhome - - # Add interface trigger during boot - if [ -n "$ADGUARDHOME_BOOT" ]; then - # Wait for interfaces to be up before starting AdGuard Home - # Prevents issues like https://github.com/openwrt/packages/issues/21868 - procd_add_raw_trigger 'interface.*.up' 5000 /etc/init.d/adguardhome restart - fi + procd_add_reload_trigger adguardhome + + if [ -n "$ADGUARDHOME_BOOT" ]; then + # Wait for interfaces to be up before starting AdGuard Home for real. + # Prevents issues like https://github.com/openwrt/packages/issues/21868. + procd_add_raw_trigger 'interface.*.up' 5000 /etc/init.d/adguardhome restart + fi +} + +reload_service() { + restart } diff --git a/luci-app-adguardhome/root/etc/nftables.d/99-adguardhome.nft b/luci-app-adguardhome/root/etc/nftables.d/99-adguardhome.nft new file mode 100644 index 000000000..78819ad81 --- /dev/null +++ b/luci-app-adguardhome/root/etc/nftables.d/99-adguardhome.nft @@ -0,0 +1 @@ +include "/var/etc/adguardhome.nft" diff --git a/luci-app-adguardhome/root/etc/sysctl.d/50-adguardhome.conf b/luci-app-adguardhome/root/etc/sysctl.d/50-adguardhome.conf new file mode 100644 index 000000000..dfc1698af --- /dev/null +++ b/luci-app-adguardhome/root/etc/sysctl.d/50-adguardhome.conf @@ -0,0 +1,3 @@ +# quic-go expects larger UDP send/receive buffers +net.core.rmem_max = 7500000 +net.core.wmem_max = 7500000 diff --git a/luci-app-adguardhome/root/etc/uci-defaults/adguardhome b/luci-app-adguardhome/root/etc/uci-defaults/adguardhome new file mode 100755 index 000000000..e33c9f089 --- /dev/null +++ b/luci-app-adguardhome/root/etc/uci-defaults/adguardhome @@ -0,0 +1,113 @@ +#!/bin/sh +# +# SPDX-License-Identifier: GPL-2.0-only + +# Migrate old config format only +OLD_CONFIG_FILE=$(uci -q get adguardhome.config.config) +OLD_CONFIG_FILE=${OLD_CONFIG_FILE:-/etc/adguardhome.yaml} +OLD_CONFIG_NEW_OPT_FILE=$(uci -q get adguardhome.config.config_file) +NEW_CONFIG_DIR=/etc/adguardhome +NEW_CONFIG_FILE="$NEW_CONFIG_DIR/adguardhome.yaml" + +start_service() { + if ! /etc/init.d/adguardhome running; then + /etc/init.d/adguardhome start + fi +} + +stop_service() { + if /etc/init.d/adguardhome running; then + /etc/init.d/adguardhome stop + fi +} + +if [ -f "$OLD_CONFIG_NEW_OPT_FILE" ] && [ "$OLD_CONFIG_NEW_OPT_FILE" != "$NEW_CONFIG_FILE" ]; then + echo "Old AdGuard Home config found in '$OLD_CONFIG_NEW_OPT_FILE'" + + USER=$(uci -q get adguardhome.config.user) + USER=${USER:-adguardhome} + + echo "AdGuard Home config is stored in a non-default path." + echo "Ensure configured service user '$USER' can access it." + +elif [ -f "$OLD_CONFIG_FILE" ] && [ "$OLD_CONFIG_FILE" != "$NEW_CONFIG_FILE" ]; then + echo "Old AdGuard Home config found in '$OLD_CONFIG_FILE'" + OLD_CONFIG_DIR=$(dirname "$OLD_CONFIG_FILE") + + USER=$(uci -q get adguardhome.config.user) + USER=${USER:-adguardhome} + GROUP=$(uci -q get adguardhome.config.group) + GROUP=${GROUP:-adguardhome} + + CUR_CONFIG_FILE="$OLD_CONFIG_FILE" + if [ "$OLD_CONFIG_DIR" = "/etc" ]; then + echo "AdGuard Home config must be stored in its own directory. Migrating..." + stop_service + + echo "Using $USER:$GROUP for file ownership." + + [ -d "$NEW_CONFIG_DIR" ] || mkdir -m 0700 -p "$NEW_CONFIG_DIR" + mv "$OLD_CONFIG_FILE" "$NEW_CONFIG_FILE" + chown -R "$USER":"$GROUP" "$NEW_CONFIG_DIR" + CUR_CONFIG_FILE="$NEW_CONFIG_FILE" + + echo "Config migrated to '$NEW_CONFIG_FILE'" + + else + echo "AdGuard Home config is stored in a non-default path." + echo "Ensure configured service user '$USER' can access it." + fi + + uci set adguardhome.config.config_file="$CUR_CONFIG_FILE" + uci -q delete adguardhome.config.config + + # Use awk to split match on :, remove double quotes and trim leading and + # trailing spaces + cert_path=$(grep certificate_path: "$CUR_CONFIG_FILE" \ + | awk -F':' '{gsub(/"/, "", $2); gsub(/^ +| +$/, "", $2); print $2}') + if [ -n "$cert_path" ]; then + echo "Found custom 'certificate_path' pointing to '$cert_path'." \ + + "Ensure configured service user '$USER' can access it." + + stop_service + + if ! uci -q show adguardhome.config.jail_mount | grep -q "$cert_path"; then + uci add_list adguardhome.config.jail_mount="$cert_path" + fi + fi + + private_key_path=$(grep private_key_path: "$CUR_CONFIG_FILE" \ + | awk -F':' '{gsub(/"/, "", $2); gsub(/^ +| +$/, "", $2); print $2}') + if [ -n "$private_key_path" ]; then + echo "Found custom 'private_key_path' pointing to '$private_key_path'." \ + + "Ensure configured service user '$USER' can access it." + + stop_service + + if ! uci -q show adguardhome.config.jail_mount | grep -q "$private_key_path"; then + uci add_list adguardhome.config.jail_mount="$private_key_path" + fi + fi + + uci commit adguardhome + start_service + +elif [ -z "$OLD_CONFIG_NEW_OPT_FILE" ] && [ "$OLD_CONFIG_FILE" != "$NEW_CONFIG_FILE" ]; then + echo "Old AdGuard Home config not found in '$OLD_CONFIG_FILE'" + stop_service + + # Service script will create the new config directory + uci set adguardhome.config.config_file="$NEW_CONFIG_FILE" + uci -q delete adguardhome.config.config + echo "Config path changed to '$NEW_CONFIG_FILE'" + + uci commit adguardhome + start_service + +else + echo "AdGuard Home config is in its default path '$NEW_CONFIG_FILE'" +fi + +reload_service() { + restart +} diff --git a/luci-app-adguardhome/root/usr/libexec/rpcd/luci.adguardhome b/luci-app-adguardhome/root/usr/libexec/rpcd/luci.adguardhome deleted file mode 100755 index 71f6004c0..000000000 --- a/luci-app-adguardhome/root/usr/libexec/rpcd/luci.adguardhome +++ /dev/null @@ -1,153 +0,0 @@ -#!/bin/sh -. /usr/share/libubox/jshn.sh - -############################################################################################ -# Functions to parse AdGuardHome YAML file: -############################################################################################ -parse_yaml() { - local prefix=$2 - local s='[[:space:]]*' w='[a-zA-Z0-9_]*' fs=$(echo @|tr @ '\034') - sed -ne "s|,$s\]$s\$|]|" \ - -e ":1;s|^\($s\)\($w\)$s:$s\[$s\(.*\)$s,$s\(.*\)$s\]|\1\2: [\3]\n\1 - \4|;t1" \ - -e "s|^\($s\)\($w\)$s:$s\[$s\(.*\)$s\]|\1\2:\n\1 - \3|;p" $1 | \ - sed -ne "s|,$s}$s\$|}|" \ - -e ":1;s|^\($s\)-$s{$s\(.*\)$s,$s\($w\)$s:$s\(.*\)$s}|\1- {\2}\n\1 \3: \4|;t1" \ - -e "s|^\($s\)-$s{$s\(.*\)$s}|\1-\n\1 \2|;p" | \ - sed -ne "s|^\($s\):|\1|" \ - -e "s|^\($s\)-$s[\"']\(.*\)[\"']$s\$|\1$fs$fs\2|p" \ - -e "s|^\($s\)-$s\(.*\)$s\$|\1$fs$fs\2|p" \ - -e "s|^\($s\)\($w\)$s:$s[\"']\(.*\)[\"']$s\$|\1$fs\2$fs\3|p" \ - -e "s|^\($s\)\($w\)$s:$s\(.*\)$s\$|\1$fs\2$fs\3|p" | \ - awk -F$fs '{ - indent = length($1)/2; - vname[indent] = $2; - for (i in vname) {if (i > indent) {delete vname[i]; idx[i]=0}} - if(length($2)== 0){ vname[indent]= ++idx[indent] }; - if (length($3) > 0) { - vn=""; for (i=0; i /dev/null) - check_if_empty "Username not found in uci adguardhome" ${AGH_USER} || exit 1 - AGH_PASS=$(uci get adguardhome.config.web_password 2> /dev/null) - check_if_empty "Password not found in uci adguardhome" ${AGH_PASS} || exit 1 - CODE=$(curl --silent -L -k -u ${AGH_USER}:${AGH_PASS} ${PROTO}://${HOST}:${PORT}/control/$1 -o $TFILE -w "%{http_code"}) - if [[ "${CODE}" == "200" ]]; then - cat $TFILE | tr -d '\n' - else - MSG="REST call failed with '$(cat ${TFILE})'. Are the credentials correct?" - log_error "$MSG" - echo "{\"error\":\"$MSG\"}" > ${TFILE} - fi -} - -############################################################################################ -# Function that groups variables starting with specified string into a single JSON object: -############################################################################################ -group_vars() { - local LOOK_FOR=${1/_1/} - echo -n "\"$LOOK_FOR\":[" - cat ${TFILE} | grep "${LOOK_FOR}" | while read FOUND; do - sed -i "/${FOUND%=*}/d" ${TFILE} - echo "${FOUND##*=}" - done | sed '$!s/$/,/' | tr -d '\n' - echo -n "]," -} - -############################################################################################ -# Parse AGH yaml file and determine necessary variables in order to function: -############################################################################################ -TFILE=/tmp/rpcd-adguardhome-$(date -Ins).tmp -trap 'rm ${TFILE}; exit' EXIT -read_config > ${TFILE} -source ${TFILE} -HOST=${http_address%:*} -[[ "${HOST}" == "0.0.0.0" ]] && HOST=$(uci get system.@system[0].hostname).$(uci get dhcp.@dnsmasq[0].domain) -HOST=$(echo ${HOST} | awk '{print tolower($0)}') -PORT=${http_address##*:} -PROTO="http" -if [[ "${tls_enabled}" == "true" ]]; then - PROTO="https" - HOST=${tls_server_name} - PORT=${tls_port_https} -fi -[[ -z "${http_address}" ]] && http_address=${bind_host}:${bind_port} - -############################################################################################ -# Decision-making cade: -############################################################################################ -case "$1" in - list) - echo '{ "get_config": {}, "get_statistics": {}, "get_status": {}, "set_passwd": {"username":"string", "hash":"string"} }' - ;; - call) - case "$2" in - get_config) - # Start the JSON array: - echo -n "{" - # Group any variables starting with "dns_" together: - grep "_1=\"" ${TFILE} | grep dns_ | while read LINE; do group_vars ${LINE%=*}; done - # Return the AGH configuration as a JSON table: - cat ${TFILE} | sed "s|^|\"|g" | sed "s|=\"|\":\"|g" | sed "s|\"$|\",|g" | tr -d '\n' - # Include web url, then close the JSON array: - echo "\"web_url\":\"${PROTO}://${HOST}:${PORT}\"}" - ;; - - get_statistics) - # Return the AGH statistics as a JSON table: - call_rest_api stats - ;; - - get_status) - # Return the AGH status as a JSON table: - call_rest_api status - ;; - - set_passwd) - # Check if bcrypt-tool is available for use: - # Read and load the argument into JSON array: - read input - json_load "$input" - # Get username and password from JSON array and verify they aren't empty: - json_get_var AGH_USER "username" - check_if_empty "username parameter missing." "${AGH_USER}" || exit 1 - json_get_var AGH_HASH "hash" - check_if_empty "hash parameter missing." "${AGH_HASH}" || exit 1 - # Change the username in the AGH configuration file: - sed -i "s|\- name: .*|\- name: ${AGH_USER} |" /etc/adguardhome/adguardhome.yaml - # Calculate and replace password hash in the AGH configuration file: - sed -i "s|\password: .*|\password: ${AGH_HASH}|" /etc/adguardhome/adguardhome.yaml - # Restart AdGaurdHome: - service adguardhome restart - # Return success to caller: - echo "{\"success\":true}" - ;; - esac - ;; -esac 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 000000000..d11a9dd08 --- /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; + + iifname { __WAN_EXCLUDES__ } return + + 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/control.sh b/luci-app-adguardhome/root/usr/share/AdGuardHome/control.sh new file mode 100755 index 000000000..360e8776d --- /dev/null +++ b/luci-app-adguardhome/root/usr/share/AdGuardHome/control.sh @@ -0,0 +1,223 @@ +#!/bin/sh +# /usr/share/AdGuardHome/control.sh +. /usr/share/AdGuardHome/helper.sh + +ENABLED="$1" +CONFIGURATION="adguardhome" +NFT_RULES_TPL="/usr/share/AdGuardHome/adguardhome.nft.tpl" +NFT_RULES_FILE="/var/etc/adguardhome.nft" +NFT_TABLE="adguardhome" + +set_nft_redirect() { + local port="$1" + [ -z "$port" ] && return 1 + [ ! -f "$NFT_RULES_TPL" ] && return 1 + + local wan_section_name + wan_section_name=$(uci show firewall | awk -F'.' '/\.name='\''wan'\''$/ {print $2}') + local WAN_IFS="" + if [ -n "$wan_section_name" ]; then + WAN_IFS=$(uci get firewall.$wan_section_name.network 2>/dev/null | xargs) + fi + + local WAN_NFT_SET="" + for IF in $WAN_IFS; do + WAN_NFT_SET="${WAN_NFT_SET} \"$IF\"," + done + WAN_NFT_SET=$(echo "$WAN_NFT_SET" | sed 's/,$//') + + sed -e "s/__WAN_EXCLUDES__/${WAN_NFT_SET}/g" -e "s/__AGH_PORT__/${port}/g" "$NFT_RULES_TPL" > "$NFT_RULES_FILE" + + nft delete table inet "$NFT_TABLE" 2>/dev/null + fw4 reload + logger -t adguardhome "nft table $NFT_TABLE applied on port $port, WAN excludes: $WAN_NFT_SET" +} + +clear_nft_redirect() { + # 优化:只有当表存在时才清理和重载防火墙,避免无意义的性能开销 + if nft list table inet "$NFT_TABLE" >/dev/null 2>&1; then + [ -f "$NFT_RULES_FILE" ] && > "$NFT_RULES_FILE" + nft delete table inet "$NFT_TABLE" 2>/dev/null + fw4 reload + logger -t adguardhome "nft table $NFT_TABLE cleared" + fi +} + +set_forward_dnsmasq() { + local PORT="$1" + local addr="127.0.0.1#$PORT" + local OLD_SERVER + 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" + 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" + local addr="127.0.0.1#$OLD_PORT" + local OLD_SERVER + 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 + local addrlist + addrlist="$(uci get dhcp.@dnsmasq[0].server 2>/dev/null)" + if [ -z "$addrlist" ]; then + local 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 + configpath=$(uci -q get adguardhome.config.config_file) + [ -z "$configpath" ] && configpath="/etc/adguardhome/adguardhome.yaml" + + local adguardhome_PORT dnsmasq_port + adguardhome_PORT=$(config_editor "dns.port" "" "$configpath" "1") + dnsmasq_port=$(uci get dhcp.@dnsmasq[0].port 2>/dev/null) + [ -z "$dnsmasq_port" ] && dnsmasq_port="53" + + if [ "$dnsmasq_port" = "$adguardhome_PORT" ]; then + [ "$dnsmasq_port" = "53" ] && dnsmasq_port="1745" + elif [ "$dnsmasq_port" = "53" ]; then + return + fi + + config_editor "dns.port" "$dnsmasq_port" "$configpath" + uci set dhcp.@dnsmasq[0].port="53" + uci commit dhcp + + /etc/init.d/dnsmasq restart + agh_reload +} + +use_port53() { + local configpath + configpath=$(uci -q get adguardhome.config.config_file) + [ -z "$configpath" ] && configpath="/etc/adguardhome/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 flag=0 + configpath=$(uci -q get adguardhome.config.config_file) + [ -z "$configpath" ] && configpath="/etc/adguardhome/adguardhome.yaml" + + 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 enabled="$1" + local configpath + configpath=$(uci -q get adguardhome.config.config_file) + [ -z "$configpath" ] && configpath="/etc/adguardhome/adguardhome.yaml" + + local adguardhome_PORT redirect old_redirect old_port old_enabled + adguardhome_PORT="$(config_editor "dns.port" "" "$configpath" "1")" + [ -z "$adguardhome_PORT" ] && adguardhome_PORT="0" + + redirect=$(uci -q get adguardhome.config.redirect || echo "none") + + # 【核心修复】:从内存文件中读取旧状态,彻底放弃污染 UCI + if [ -f /var/run/adguardhome.state ]; then + . /var/run/adguardhome.state + else + old_redirect="none" + old_port="0" + old_enabled="0" + fi + + uci get dhcp.@dnsmasq[0].port >/dev/null 2>&1 || uci set dhcp.@dnsmasq[0].port="53" + 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 + + # 【核心修复】:将当前状态覆盖写入内存文件,轻量、疾速、安全! + cat < /var/run/adguardhome.state +old_redirect="$redirect" +old_port="$adguardhome_PORT" +old_enabled="$enabled" +EOF + + 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 "$ENABLED" diff --git a/luci-app-adguardhome/root/usr/share/AdGuardHome/helper.sh b/luci-app-adguardhome/root/usr/share/AdGuardHome/helper.sh new file mode 100755 index 000000000..35f01985f --- /dev/null +++ b/luci-app-adguardhome/root/usr/share/AdGuardHome/helper.sh @@ -0,0 +1,21 @@ +#!/bin/sh +# Auxiliary functions specially used for reading and writing YAML +config_editor() { + awk -v yaml="$1" -v value="$2" -v file="$3" -v ro="$4" ' + BEGIN{split(yaml,part,"\.");s="";i=1;l=length(part);} + { + if (match($0,s""part[i]":")) { + if (i==l) { + split($0,t,": "); + if (ro==""){ + system("sed -i '\''"FNR"c\\" t[1] ": " value "'\'' " file " >/dev/null 2>&1"); + } else { + print(t[2]); + } + exit; + } + s=s"[- ]{2}"; + i++; + } + }' "$3" +} diff --git a/luci-app-adguardhome/root/usr/share/AdGuardHome/update_core.sh b/luci-app-adguardhome/root/usr/share/AdGuardHome/update_core.sh new file mode 100755 index 000000000..bd8016510 --- /dev/null +++ b/luci-app-adguardhome/root/usr/share/AdGuardHome/update_core.sh @@ -0,0 +1,215 @@ +#!/bin/sh + +# === Core security: self-background operation mechanism === +if [ "$1" != "bg_run" ]; then + rm -f /var/run/update_core* /tmp/AdGuardHome_update.log + touch /var/run/update_core + /usr/share/AdGuardHome/update_core.sh bg_run "$1" /tmp/AdGuardHome_update.log 2>&1 & + exit 0 +fi + +shift +# ========================================================== + +PATH="/usr/sbin:/usr/bin:/sbin:/bin" +binpath="/usr/bin/AdGuardHome" +update_mode=$1 + +core_version=$(uci get adguardhome.config.core_version 2>/dev/null || true) +update_url=$(uci get adguardhome.config.update_url 2>/dev/null || true) + +case "${core_version}" in +beta) + core_api_url=https://api.github.com/repos/AdguardTeam/AdGuardHome/releases + ;; +*) + core_api_url=https://api.github.com/repos/AdguardTeam/AdGuardHome/releases/latest + ;; +esac + +EXIT(){ + rm -rf /var/run/update_core /tmp/AdGuardHome_Update 2>/dev/null + if [ "$1" != "0" ]; then + touch /var/run/update_core_error + fi + exit $1 +} + +trap "EXIT 1" SIGTERM SIGINT + +rm -rf /var/run/update_core_error /var/run/update_core_done 2>/dev/null +touch /var/run/update_core + +Check_Task(){ + running_tasks="$(ps w | grep -v grep | grep 'AdGuardHome' | grep 'update_core' | wc -l)" + case $1 in + force) + echo "Force update requested" + echo "Killing ${running_tasks} running tasks ..." + ps w | grep -v grep | grep -v $$ | grep 'AdGuardHome' | grep 'update_core' | awk '{print $1}' | xargs kill -9 2>/dev/null + ;; + *) + [ "${running_tasks}" -gt 2 ] && echo -e "There are ${running_tasks} update tasks already running. Please wait or stop them manually." && EXIT 2 + ;; + esac +} + +Check_Downloader() { + if command -v curl >/dev/null 2>&1; then + PKG="curl" + return + fi + + if command -v wget >/dev/null 2>&1; then + PKG="wget" + return + fi + + echo "Neither curl nor wget is installed, cannot check updates!" >&2 + EXIT 1 +} + +Check_Updates(){ + Check_Downloader + case "${PKG}" in + curl) + Downloader="curl -L -k -o" + _Downloader="curl -s" + ;; + wget) + Downloader="wget --no-check-certificate -T 5 -O" + _Downloader="wget -q -O -" + ;; + esac + echo "[${PKG}] Checking for updates ..." + Cloud_Version="$(${_Downloader} ${core_api_url} 2>/dev/null | grep 'tag_name' | egrep -o "v[0-9].+[0-9.]" | awk 'NR==1')" + if [ -z "${Cloud_Version}" ]; then + echo "Failed to check updates, please check network." >&2 + EXIT 1 + fi + + if [ -f "${binpath}" ]; then + Current_Version="$(${binpath} --version 2>/dev/null | egrep -o "v[0-9].+[0-9]" | sed -r 's/(.*), c(.*)/\1/')" + else + Current_Version="unknown" + fi + [ -z "${Current_Version}" ] && Current_Version="unknown" + + echo "Binary path: ${binpath%/*}" + echo "Current version: ${Current_Version}" + echo "Latest version: ${Cloud_Version}" + + if [ ! "${Cloud_Version}" = "${Current_Version}" ] || [ "$1" = force ]; then + Update_Core || EXIT 1 + else + echo "Already up to date." + EXIT 0 + fi + EXIT 0 +} + +Update_Core(){ + rm -rf "/tmp/AdGuardHome_Update" > /dev/null 2>&1 + mkdir -p "/tmp/AdGuardHome_Update" || { echo "Unable to create a temporary directory"; EXIT 1; } + + GET_Arch + eval link="${update_url}" + echo "Download link: ${link}" + echo "File name: ${link##*/}" + echo "Downloading AdGuardHome core ..." + + if ! $Downloader "/tmp/AdGuardHome_Update/${link##*/}" "${link}"; then + echo "Download failed." + rm -rf "/tmp/AdGuardHome_Update" + EXIT 1 + fi + + if [ "${link##*.}" = "gz" ]; then + echo "Extracting AdGuardHome ..." + if ! tar -zxf "/tmp/AdGuardHome_Update/${link##*/}" -C "/tmp/AdGuardHome_Update/"; then + echo "Extraction failed!" + rm -rf "/tmp/AdGuardHome_Update" + EXIT 1 + fi + if [ ! -e "/tmp/AdGuardHome_Update/AdGuardHome/AdGuardHome" ]; then + echo "Extraction failed: binary not found!" + rm -rf "/tmp/AdGuardHome_Update" + EXIT 1 + fi + downloadbin="/tmp/AdGuardHome_Update/AdGuardHome/AdGuardHome" + else + downloadbin="/tmp/AdGuardHome_Update/${link##*/}" + fi + + chmod +x "${downloadbin}" 2>/dev/null || true + echo "Core size: $(awk 'BEGIN{printf "%.2fMB\n",'$((`ls -l $downloadbin | awk '{print $5}'`))'/1000000}')" + + /etc/init.d/AdGuardHome stop > /dev/null 2>&1 + echo "Moving AdGuardHome binary to ${binpath%/*} ..." + + if ! mv -f "${downloadbin}" "${binpath}"; then + echo -e "The core movement failed! \nIt may be caused by insufficient space." + rm -rf "/tmp/AdGuardHome_Update" + EXIT 1 + fi + + rm -rf /tmp/AdGuardHome_Update + chmod +x ${binpath} + echo "Restarting AdGuardHome service ..." + /etc/init.d/adguardhome restart > /dev/null 2>&1 + + echo "AdGuardHome core updated successfully!" + touch /var/run/update_core_done + EXIT 0 +} + +GET_Arch() { + Archt="$(uname -m)" + case "${Archt}" in + i386|i686) + Arch="i386" + ;; + x86_64|amd64) + Arch="amd64" + ;; + mipsel|mipsel*) + Arch="mipsle_softfloat" + ;; + mips|mips*) + Arch="mips_softfloat" + ;; + mips64el) + Arch="mips64le_softfloat" + ;; + mips64) + Arch="mips64_softfloat" + ;; + armv5*|armv5l|armv5tel) + Arch="armv5" + ;; + armv6*|armv6l) + Arch="armv6" + ;; + armv7*|armv7l) + Arch="armv7" + ;; + arm|armhf) + Arch="armv7" + ;; + aarch64) + Arch="arm64" + ;; + *) + echo "Unsupported architecture: [${Archt}]" + EXIT 1 + esac + echo "Detected architecture: ${Arch}" +} + + +main(){ + Check_Task ${update_mode} + Check_Updates ${update_mode} +} + +main diff --git a/luci-app-adguardhome/root/usr/share/AdGuardHome/watchconfig.sh b/luci-app-adguardhome/root/usr/share/AdGuardHome/watchconfig.sh new file mode 100755 index 000000000..c7267bfed --- /dev/null +++ b/luci-app-adguardhome/root/usr/share/AdGuardHome/watchconfig.sh @@ -0,0 +1,18 @@ +#!/bin/sh +# /usr/share/AdGuardHome/watchconfig.sh + +PATH="/usr/sbin:/usr/bin:/sbin:/bin" + +configpath=$(uci -q get adguardhome.config.config_file) +[ -z "$configpath" ] && configpath="/etc/adguardhome/adguardhome.yaml" + +while : +do + sleep 10 + if [ -f "$configpath" ]; then + /etc/init.d/adguardhome do_redirect 1 + break + fi +done + +exit 0 diff --git a/luci-app-adguardhome/root/usr/share/luci/menu.d/luci-app-adguardhome.json b/luci-app-adguardhome/root/usr/share/luci/menu.d/luci-app-adguardhome.json index 547b638cc..f4dddb53a 100644 --- a/luci-app-adguardhome/root/usr/share/luci/menu.d/luci-app-adguardhome.json +++ b/luci-app-adguardhome/root/usr/share/luci/menu.d/luci-app-adguardhome.json @@ -1,40 +1,15 @@ { - "admin/services/adguardhome": { - "title": "AdGuard Home", - "action": { - "type": "firstchild" - }, - "depends": { - "acl": [ - "luci-app-adguardhome" - ], - "uci": { - "adguardhome": true - } - } - }, - "admin/services/adguardhome/config": { - "title": "Configuration", - "order": 1, - "action": { - "type": "view", - "path": "adguardhome/config" - } - }, - "admin/services/adguardhome/status": { - "title": "Status", - "order": 11, - "action": { - "type": "view", - "path": "adguardhome/status" - } - }, - "admin/services/adguardhome/logs": { - "title": "Logs", - "order": 21, - "action": { - "type": "view", - "path": "adguardhome/logs" - } - } -} \ No newline at end of file + "admin/services/adguardhome": { + "title": "AdGuard Home", + "action": { + "type": "view", + "path": "adguardhome/config" + }, + "depends": { + "acl": [ "luci-app-adguardhome" ], + "uci": { + "adguardhome": true + } + } + } +} 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 689923c03..7d7cc4997 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,28 +2,23 @@ "luci-app-adguardhome": { "description": "Grant permissions for the AdGuard Home LuCI app", "read": { - "ubus": { - "luci.adguardhome": [ - "get_config", - "get_status", - "get_statistics", - "set_passwd" - ] - }, - "uci": [ "adguardhome" ], "file": { - "/sbin/logread": [ - "stat", - "exec" - ], - "/sbin/logread -e AdGuardHome": [ - "exec" - ], - "/etc/init.d/dnsmasq": [ "exec" ], - "/etc/init.d/firewall": [ "exec" ] - } + "/tmp/AdGuardHome_update.log": [ "read" ], + "/var/run/update_core": [ "stat" ], + "/var/run/update_core_error": [ "stat" ], + "/var/run/AdGredir": [ "read" ], + "/var/run/AdGserverdis": [ "read" ], + "/usr/bin/AdGuardHome --version": [ "exec" ] + }, + "ubus": { + "service": [ "list" ] + }, + "uci": [ "adguardhome" ] }, "write": { + "file": { + "/usr/share/AdGuardHome/update_core.sh": [ "exec" ] + }, "uci": [ "adguardhome" ] } } diff --git a/luci-app-adguardhome/root/usr/share/ucitrack/luci-app-adguardhome.json b/luci-app-adguardhome/root/usr/share/ucitrack/luci-app-adguardhome.json new file mode 100644 index 000000000..003c0887f --- /dev/null +++ b/luci-app-adguardhome/root/usr/share/ucitrack/luci-app-adguardhome.json @@ -0,0 +1,4 @@ +{ + "config": "adguardhome", + "init": "adguardhome" +} diff --git a/luci-app-caddy/.gitattributes b/luci-app-caddy/.gitattributes new file mode 100644 index 000000000..1b12051b3 --- /dev/null +++ b/luci-app-caddy/.gitattributes @@ -0,0 +1,10 @@ +# 保证脚本 / lua / shell 配置以 LF 入库, 避免 Windows 编辑后 CRLF 上传杀死 busybox ash +* text=eol=lf + +# 二进制例外 +*.png binary +*.jpg binary +*.ico binary +*.bin binary +*.zip binary +*.ipk binary diff --git a/luci-app-caddy/.gitignore b/luci-app-caddy/.gitignore new file mode 100644 index 000000000..7ac8a497b --- /dev/null +++ b/luci-app-caddy/.gitignore @@ -0,0 +1,2 @@ +/docs/ +*.md diff --git a/luci-app-caddy/Makefile b/luci-app-caddy/Makefile index 368f882e6..13d331332 100644 --- a/luci-app-caddy/Makefile +++ b/luci-app-caddy/Makefile @@ -1,4 +1,5 @@ -# Copyright (C) 2016 Openwrt.org +# luci-app-caddy - LuCI 管理 Caddy (反向代理 / ACME DNS-01 / Aliyun DDNS) +# Copyright (C) 2026 andangel - https://github.com/andangel/luci-app-caddy # # This is free software, licensed under the Apache License, Version 2.0 . # @@ -9,17 +10,18 @@ PKG_NAME:=luci-app-caddy LUCI_TITLE:=LuCI support for caddy LUCI_DEPENDS:=+caddy -PKG_VERSION:=1.0 -PKG_RELEASE:=2 +PKG_VERSION:=2.0 +PKG_RELEASE:=3 define Package/$(PKG_NAME)/conffiles -/etc/caddy +/etc/config/caddy +/etc/caddy/Caddyfile endef define Package/$(PKG_NAME)/postrm #!/bin/sh [ -f /etc/caddy/Caddyfile ] && rm -rf /etc/caddy/Caddyfile >/dev/null 2>&1 - return 0 +return 0 endef define Package/$(PKG_NAME)/postinst diff --git a/luci-app-caddy/luasrc/controller/caddy.lua b/luci-app-caddy/luasrc/controller/caddy.lua index bff02e337..467ab7f91 100644 --- a/luci-app-caddy/luasrc/controller/caddy.lua +++ b/luci-app-caddy/luasrc/controller/caddy.lua @@ -1,30 +1,38 @@ module("luci.controller.caddy", package.seeall) +local DL_DEFAULT = "https://github.com/andangel/luci-app-caddy/releases/latest/download/caddy_linux_arm64" +local DL_API = "https://api.github.com/repos/andangel/luci-app-caddy/releases/latest" + function index() - entry({"admin", "nas"}, firstchild(), _("NAS") , 45).dependent = false - if not nixio.fs.access("/etc/config/caddy") then - return - end - - local page = entry({"admin", "nas", "caddy"}, alias("admin", "nas", "caddy", "basic"), _("Caddy"), 20) - page.dependent = true - page.acl_depends = { "luci-app-caddy" } - - entry({"admin", "nas"}, firstchild(), "NAS", 44).dependent = false - entry({"admin", "nas", "caddy", "basic"}, cbi("caddy/caddy"), _("基本设置"), 1).leaf = true - entry({"admin", "nas", "caddy", "log"}, cbi("caddy/caddy_log"), _("日志"), 2).leaf = true - entry({"admin", "nas", "caddy", "caddy_status"}, call("caddy_status")).leaf = true - entry({"admin", "nas", "caddy", "get_log"}, call("get_log")).leaf = true - entry({"admin", "nas", "caddy", "clear_log"}, call("clear_log")).leaf = true - entry({"admin", "nas", "caddy", "admin_info"}, call("admin_info")).leaf = true + entry({"admin", "network", "caddy"}, alias("admin", "network", "caddy", "basic"), _("Caddy"), 30) + entry({"admin", "network", "caddy", "basic"}, cbi("caddy/caddy"), _("状态"), 1).leaf = true + entry({"admin", "network", "caddy", "domain"}, cbi("caddy/domain"), _("域名"), 2).leaf = true + entry({"admin", "network", "caddy", "host"}, cbi("caddy/host"), _("主机"), 3).leaf = true + entry({"admin", "network", "caddy", "caddyfile"}, cbi("caddy/caddyfile"), _("配置"), 4).leaf = true + entry({"admin", "network", "caddy", "log"}, cbi("caddy/caddy_log"), _("日志"), 5).leaf = true + entry({"admin", "network", "caddy", "caddy_status"}, call("caddy_status")).leaf = true + entry({"admin", "network", "caddy", "get_log"}, call("get_log")).leaf = true + entry({"admin", "network", "caddy", "clear_log"}, call("clear_log")).leaf = true + entry({"admin", "network", "caddy", "admin_info"}, call("admin_info")).leaf = true + entry({"admin", "network", "caddy", "download"}, call("download")).leaf = true + entry({"admin", "network", "caddy", "download_status"}, call("download_status")).leaf = true + entry({"admin", "network", "caddy", "download_ctl"}, call("download_ctl")).leaf = true end function caddy_status() local e={} - local sys = require "luci.sys" local uci = require "luci.model.uci".cursor() - local port = tonumber(uci:get_first("caddy", "caddy", "port")) - e.port = (port or 12311) + -- Web 界面按钮地址: 取第一个 proxy host 的 域名:端口, 没有则留空 + local weburl = "" + local pname = uci:get_first("caddy", "proxy") + if pname then + local pdom = uci:get("caddy", pname, "domain") + local pprt = uci:get("caddy", pname, "port") + if pdom then + weburl = "http://" .. pdom .. (pprt and (":" .. pprt) or "") + end + end + e.weburl = weburl e.running=luci.sys.call("pidof caddy >/dev/null")==0 local tagfile = io.open("/tmp/caddy_time", "r") if tagfile then @@ -57,7 +65,8 @@ function caddy_status() e.caddyram = "unknown" end - local command4 = io.popen("([ -s /tmp/caddy.tag ] && cat /tmp/caddy.tag ) || (echo `$(uci -q get caddy.@caddy[0].bin_dir) -v |awk '{print $1}' | sed 's/[^0-9.]*//g'` > /tmp/caddy.tag && cat /tmp/caddy.tag)") +-- 官方 CLI: caddy version (输出形如 v2.11.4 h1:xxx, 取版本号段) + local command4 = io.popen("$(uci -q get caddy.@caddy[0].bin_dir) version 2>/dev/null | awk '{print $1}'") e.caddytag = command4:read("*all") command4:close() if e.caddytag == "" then @@ -83,9 +92,165 @@ function clear_log() end function admin_info() - local validate = luci.sys.exec("$(uci -q get caddy.@caddy[0].bin_dir) validate --config /etc/caddy/Caddyfile --adapter caddyfile 2>&1") - + -- HOME=/root 兜底: LuCI CGI (uhttpd) 环境没 $HOME/$XDG_CONFIG_HOME, + -- caddy validate 会 warn "unable to determine directory for user configuration; falling back to current directory". + -- 显式给个家目录就没这条 warn; --config 已显式传, 结果不受 HOME 影响. + local validate = luci.sys.exec("env HOME=/root $(uci -q get caddy.@caddy[0].bin_dir) validate --config /etc/caddy/Caddyfile --adapter caddyfile 2>&1") luci.http.prepare_content("application/json") luci.http.write_json({ validate = validate }) end +function download_status() + local st = { state = "idle", size = 0, total = 0, speed = 0, msg = "" } + local f = io.open("/tmp/caddy_dl.status", "r") + if f then + for line in f:lines() do + local k, v = line:match("^([%a_]+)=(.*)$") + if k then st[k] = v end + end + f:close() + end + st.size = tonumber(st.size) or 0 + st.total = tonumber(st.total) or 0 + st.speed = tonumber(st.speed) or 0 + -- 看门狗: 活动状态下 status 由下载进程每秒刷新一次并带 ts 时间戳 (该 lua 桥无 os.stat, 不靠文件 mtime), + -- 若 >60s 没刷新说明进程已丢失 (被 OOM/清理 SIGKILL 不执行 trap), 状态文件残留会导致 UI 永远下载中 + local actives = { downloading = 1, paused = 1, verifying = 1 } + local ts = tonumber(st.ts) or 0 + if actives[st.state] and ts > 0 and os.time() - ts > 60 then + st.state = "lost" + st.speed = 0 + st.msg = (st.size > 0) and "下载进程已丢失 (保留进度, 点[下载并重启]续传)" or "状态异常 (点[下载并重启]重新开始)" + end + luci.http.prepare_content("application/json") + luci.http.write_json(st) +end + +-- 暂停/继续/停止: 写一个控制命令到 /tmp/caddy_dl.ctl, 下载进程每秒轮询处理. +-- 暂停=SIGSTOP 冻结 curl (连接仍挂着, 不额外撞 GitHub); 继续=SIGCONT; 停止=杀 curl 留已下部分. +function download_ctl() + -- 本 runtime (Luci openwrt-25.12 / ucode 桥) 的 luci.http 不提供 readrequest, 用 formvalue 读 POST 字段 + local act = tostring(luci.http.formvalue("act") or "") + local ok, msg = false, "" + if act == "pause" or act == "resume" or act == "stop" then + local f = io.open("/tmp/caddy_dl.ctl", "w") + if f then f:write(act .. "\n"); f:close(); ok = true; msg = "ok" end + else + msg = "unknown act: " .. act + end + luci.http.prepare_content("application/json") + luci.http.write_json({ ok = ok, msg = msg }) +end + +function download() + local uci = require "luci.model.uci".cursor() + local bin = uci:get("caddy", "caddy", "bin_dir") or "/usr/sbin/caddy" + local u = uci:get("caddy", "caddy", "download_url") + local url = (u and u ~= "") and u or DL_DEFAULT + local use_sha = (url == DL_DEFAULT) and "1" or "0" + + local sq = function(s) return s:gsub("'", "'\\''") end + local L = { + "#!/bin/sh", + "URL='" .. sq(url) .. "'", + "TMP='/tmp/caddy.dl'", + "STATUS='/tmp/caddy_dl.status'", + "CTL='/tmp/caddy_dl.ctl'", + "BIN='" .. sq(bin) .. "'", + "USE_SHA=" .. use_sha, + "API='" .. DL_API .. "'", + "RECEIPT='/tmp/caddy_dl.done'", + "CURLPID=0; PAUSED=0; STOPTED=0", + "rm -f \"$CTL\"", + "", + "# 写临时文件再原子 mv, 防状态端点读到半截文件而把 UI 误渲染成 idle", + "ws(){ printf 'state=%s\\nsize=%s\\ntotal=%s\\nspeed=%s\\nmsg=%s\\nts=%s\\n' \"$1\" \"$2\" \"$3\" \"$4\" \"$5\" $(date +%s) > \"$STATUS.tmp\"; mv -f \"$STATUS.tmp\" \"$STATUS\"; }", + "", + "PIDFILE='/tmp/caddy_dl.pid'", + "# 去重锁: 已有存活实例则直接退出 (防清理漏杀导致多实例竞争写同一文件 / 互相覆盖 status)", + "if [ -f \"$PIDFILE\" ]; then OLD=$(cat \"$PIDFILE\" 2>/dev/null); if [ -n \"$OLD\" ] && kill -0 \"$OLD\" 2>/dev/null; then exit 0; fi; fi", + "echo $$ > \"$PIDFILE\"", + "trap 'rm -f \"$PIDFILE\"' EXIT", + "", + "# 取总大小: 发 Range bytes=0-0 只下 1 字节, 从 Content-Range: bytes 0-0/TOTAL 抽分母 (比完整 HEAD 稳); GitHub 抖则重试 3 次", + "TOT=0", + "i=0", + 'while [ "$i" -lt 3 ]; do', + "TOT=$(curl -s -L -r 0-0 --connect-timeout 10 -D - -o /dev/null \"$URL\" 2>/dev/null | tr -d '\\r' | grep -i 'Content-Range:' | grep -oE '[0-9]+/[0-9]+' | awk -F/ '{print $2}' | head -1)", + "if [ -n \"$TOT\" ] && [ \"$TOT\" -gt 0 ] 2>/dev/null; then break; fi", + "i=$((i+1)); sleep 2", + "done", + "ws downloading $(wc -c < \"$TMP\" 2>/dev/null || echo 0) \"$TOT\" 0 \"\"", + "", + "# 已下载收据: 上次下载且 SHA 通过时, 记下当时的期望 sha (写入在成功替换后). 收据在 /tmp, 重启即清空 → 重启后再点会重新下载(拉到新版).", + "# 本次点击若 API 最新 sha == 收据 == 当前二进制 sha, 说明盘上的就是刚下载的版本 → 不重复下载, 直接重启", + "SKIP=0", + "BINSIZE=$(wc -c < \"$BIN\" 2>/dev/null || echo 0)", + "if [ \"$USE_SHA\" -eq 1 ] && [ -f \"$RECEIPT\" ]; then", + "ws verifying \"$BINSIZE\" \"$BINSIZE\" 0 '核对已下载版本...'", + "API_NOW=$(curl -L -sS --connect-timeout 10 --max-time 20 \"$API\" | grep -oE 'sha256:[0-9a-f]{64}' | head -1 | sed 's/sha256://')", + "BIN_SHA=$(sha256sum \"$BIN\" 2>/dev/null | awk '{print $1}')", + "if [ \"${#API_NOW}\" -ge 64 ] && [ \"${#BIN_SHA}\" -ge 64 ] && [ \"$(cat \"$RECEIPT\" 2>/dev/null)\" = \"$API_NOW\" ] && [ \"$BIN_SHA\" = \"$API_NOW\" ]; then SKIP=1; fi", + "fi", + "if [ $SKIP -eq 1 ]; then /etc/init.d/caddy restart >/dev/null 2>&1; ws done \"$BINSIZE\" \"$BINSIZE\" 0 '已是上次下载的最新版, 无需重复下载, 已直接重启 Caddy'; exit 0; fi", + "", + "# 直接后台运行 curl (不经函数包一层), 使 $! 即真实 curl pid, 暂停/停止(信号)才能直接作用于 curl", + "# GitHub 直连不稳: 除网络类错误外, HTTP/2 reset / -C 续传冲突等瞬时错误也按 --retry 自动再来", + "# speed-limit/speed-time: 低于 ~100B/s 持续 45s 视为假死, 触发重试 (比干等 5 分钟超时快)", + "curl -L -C - -sS --retry 4 --retry-delay 8 --retry-all-errors --connect-timeout 20 --speed-limit 100 --speed-time 45 -o \"$TMP\" \"$URL\" >> /tmp/caddy_dl.log 2>&1 &", + "CURLPID=$!; RC=", + "LAST=$(wc -c < \"$TMP\" 2>/dev/null || echo 0); LTIME=$(date +%s)", + "# 主循环: 每秒刷新进度 + 处理暂停/继续/停止控制命令 (与下载解耦, 控制端点只写 $CTL 文件)", + 'while kill -0 "$CURLPID" 2>/dev/null; do', + 'if [ -f "$CTL" ]; then', + ' CMD=$(cat "$CTL" 2>/dev/null); rm -f "$CTL"', + ' if [ "$CMD" = "pause" ] && [ $PAUSED -eq 0 ]; then kill -STOP $CURLPID 2>/dev/null; PAUSED=1', + ' elif [ "$CMD" = "resume" ] && [ $PAUSED -eq 1 ]; then kill -CONT $CURLPID 2>/dev/null; PAUSED=0', + ' elif [ "$CMD" = "stop" ]; then kill $CURLPID 2>/dev/null; STOPTED=1', + ' fi', + 'fi', + 'if [ $PAUSED -eq 1 ]; then ws paused $(wc -c < "$TMP" 2>/dev/null || echo 0) "$TOT" 0 ""; sleep 1; continue; fi', + 'SZ=$(wc -c < "$TMP" 2>/dev/null || echo 0)', + 'NOW=$(date +%s); DT=$((NOW - LTIME)); SP=0', + 'if [ "$DT" -gt 0 ]; then SP=$(( (SZ - LAST) / DT )); fi', + 'LAST=$SZ; LTIME=$NOW', + 'ws downloading "$SZ" "$TOT" "$SP" ""', + "# 僵尸检测: curl 死掉但未 wait 收尸时 kill -0 仍成功会卡死主循环, /proc 状态 Z 即已死; 已下满按成功, 否则按失败", + 'CS=$(awk "{print $3}" /proc/$CURLPID/stat 2>/dev/null)', + 'if [ "$CS" = "Z" ]; then if [ "$TOT" -gt 0 ] 2>/dev/null && [ "$SZ" -ge "$TOT" ] 2>/dev/null; then RC=0; else RC=128; fi; break; fi', + "sleep 1", + "done", + "# 主循环已判定 (z 检测设了 RC) 则不覆盖; 否则 wait 收尸取真实退出码", + 'if [ -z "$RC" ]; then wait $CURLPID 2>/dev/null; RC=$?; fi', + 'SZ=$(wc -c < "$TMP" 2>/dev/null || echo 0)', + "if [ $STOPTED -eq 1 ]; then ws stopped \"$SZ\" \"$TOT\" 0 '已停止 (保留已下载部分, 点[下载并重启]可续传)'; exit 0; fi", + "if [ \"$RC\" -ne 0 ]; then ws error \"$SZ\" \"$TOT\" 0 '下载失败 (GitHub 波动/网络), 点[下载并重启]可自动续传重试'; exit 0; fi", + "if [ \"$USE_SHA\" -eq 1 ]; then", + "API_SHA=$(curl -L -sS --connect-timeout 10 --max-time 20 \"$API\" | grep -oE 'sha256:[0-9a-f]{64}' | head -1 | sed 's/sha256://')", + "LOC_SHA=$(sha256sum \"$TMP\" 2>/dev/null | awk '{print $1}')", + 'if [ "${#API_SHA}" -ge 64 ] && [ "${#LOC_SHA}" -ge 64 ] && [ "$API_SHA" != "$LOC_SHA" ]; then', + 'rm -f "$TMP"; ws error "$SZ" "$TOT" 0 "SHA256 不匹配, 文件可能损坏, 已丢弃, 请重试"; exit 0', + "fi", + "fi", + "# curl -o 落盘不带可执行位, -h 前先 chmod, 否则任何架构 (包括正确 arm64) 都会 Permission denied", + "chmod +x \"$TMP\" 2>/dev/null", + "if ! \"$TMP\" -h >/dev/null 2>&1; then rm -f \"$TMP\"; ws error \"$SZ\" \"$TOT\" 0 '文件无法执行, 可能架构不匹配 (应为 linux/arm64 静态二进制)'; exit 0; fi", + "if ! mv -f \"$TMP\" \"$BIN\"; then ws error \"$SZ\" \"$TOT\" 0 '替换 $BIN 失败'; exit 0; fi", + "# 写收据: 本版本已在盘上, 下次点击命中 SKIP 不再重复下载 (仅默认 URL 有 API_SHA)", + "[ \"$USE_SHA\" -eq 1 ] && echo \"$API_SHA\" > \"$RECEIPT\" 2>/dev/null", + "if /etc/init.d/caddy restart >/dev/null 2>&1; then ws done \"$SZ\" \"$TOT\" 0 '下载完成, 已替换并重启 Caddy'; else ws done \"$SZ\" \"$TOT\" 0 '下载完成并已替换, 但重启 Caddy 失败, 请手动 /etc/init.d/caddy restart'; fi", + } + local f = io.open("/tmp/caddy_dl.sh", "w") + f:write(table.concat(L, "\n") .. "\n") + f:close() + os.execute("chmod +x /tmp/caddy_dl.sh") + -- 先清掉旧的下载进程 (脚本本体 + 它遗留的写 caddy.dl 的 curl 子进程, 否则多实例互相覆盖 status) + -- ps 会截断命令行导致漏杀 curl, 故直接扫 /proc/*/cmdline; 用 [.] 自转义 glob 避免匹配到本命令自身 + os.execute("for p in /proc/[0-9]*; do c=$(tr '\\0' ' ' < $p/cmdline 2>/dev/null); case \"$c\" in *caddy_dl[.]sh*|*caddy[.]dl*) kill ${p##*/} 2>/dev/null;; esac; done; sleep 1; rm -f /tmp/caddy_dl.status") + -- nohup 脱离 CGI, 页面刷新也不中断 + os.execute("nohup sh /tmp/caddy_dl.sh >/dev/null 2>&1 &") + luci.http.prepare_content("application/json") + -- 注意: call 端点内 _ 未注入 (只在 index 阶段有), 这里用字面量; 该消息仅作启动占位, 随即被进度轮询覆盖 + luci.http.write_json({ ok = true, msg = "已开始后台下载 (支持断点续传), 自动刷新进度..." }) +end + diff --git a/luci-app-caddy/luasrc/model/cbi/caddy/caddy.lua b/luci-app-caddy/luasrc/model/cbi/caddy/caddy.lua index cad4aaafd..8e08f42ac 100644 --- a/luci-app-caddy/luasrc/model/cbi/caddy/caddy.lua +++ b/luci-app-caddy/luasrc/model/cbi/caddy/caddy.lua @@ -1,127 +1,56 @@ -local m, s -m = Map("caddy", translate("Caddy"), translate("Caddy 是一个可扩展的服务器平台,具有自动 HTTPS 功能的快速且可扩展的多平台 HTTP/1-2-3 Web 服务器") .. "
" .. "项目地址:" .. [[]] .. translate("github.com/caddyserver/caddy") .. "    " .. " caddy文档:" .. [[]] .. translate("caddyserver.com/docs/") .. [[]]) +local m, s, o -m:section(SimpleSection).template = "caddy/caddy_status" +m = Map("caddy", translate("Caddy 状态"), + translate("全局配置: 启用 Caddy / 公共开关 / 程序与日志路径. 反向代理 Host 在 [Host] 标签页增删改查") .. "
" .. + [[]] .. + translate("github.com/andangel/luci-app-caddy") .. "  " .. + [[]] .. + translate("caddyserver.com/docs/ (Caddy 官方文档)") .. [[]]) +m:section(SimpleSection).template = "caddy/caddy_status" + +---------------------------------------------------------------------- +-- 全局设置 (单例) - JSON 里的 global 对象, 只有一条, 只有"改", 没有增删 +---------------------------------------------------------------------- s = m:section(TypedSection, "caddy") s.addremove = false s.anonymous = true -o = s:option(Flag, "enabled", translate("Enabled")) +o = s:option(Flag, "enabled", translate("启用 Caddy")) o.rmempty = false -o.default = 0 +o.default = 1 -o = s:option(Button, "btnrm", translate("重启")) -o.inputtitle = translate("重启") -o.description = translate("在没有修改参数的情况下快速重新启动一次") +o = s:option(Button, "restart", translate("重启 Caddy")) +o.inputtitle = translate("重启 Caddy") +o.description = translate("按全局 + Host 配置重新生成 Caddyfile 并重启 Caddy") o.inputstyle = "apply" o:depends("enabled", "1") o.write = function() - os.execute("/etc/init.d/caddy restart") + os.execute("/etc/init.d/caddy restart") end -e=s:option(ListValue,"cmd",translate("启动方式"), - translate("自定义配置文件启动,若不懂参数请勿选择自定义")) -e:value("默认") -e:value("自定义") - -o = s:option(TextValue, "caddyfile", translate("Caddyfile配置文件"), - translate("这是caddy的启动配置文件Caddyfile,路径在/etc/caddy/Caddyfile
使用的命令是:caddy run --config /etc/caddy/Caddyfile --adapter caddyfile
如需设置密码,这里就不能使用明文密码,需要转换
转换命令:$(uci -q get caddy.@caddy[0].bin_dir) hash-password --plaintext 新密码")) -o.rows = 3 -o.wrap = "off" -o:depends("cmd", "自定义") - -o = s:option(Value, "port", translate("端口")) -o.datatype = "and(port,min(1))" -o.default = "12311" -o:depends("cmd", "默认") - -o = s:option(Flag,"file_pass", translate("启用 认证")) -o.datatype = "string" -o.default = "0" -o:depends("cmd", "默认") - -o = s:option(Value,"file_username", translate("用户名")) -o.datatype = "string" -o.default = "admin" -o:depends("file_pass", "1") - -o = s:option(Value,"file_password", translate("密码")) -o.datatype = "string" -o.password = true -o.default = "123456" -o:depends("file_pass", "1") - -o = s:option(Flag, "filezip", translate("启用 压缩")) -o.default = 0 -o:depends("cmd", "默认") - -o = s:option(Flag, "log", translate("启用日志")) -o.default = 1 -o:depends("cmd", "默认") - -o = s:option(Value, "log_dir", translate("日志路径"), - translate("日志的存放路径,填写完整的路径及日志文件名
建议/tmp里,例如:/tmp/caddy/requests.log")) -o.datatype = "string" -o.default = "/tmp/caddy/requests.log" -o:depends("log", "1") - -o = s:option(Value, "bin_dir", translate("程序路径"), - translate("caddy二进制的存放路径,填写完整的路径及caddy名称
例如:/usr/bin/caddy     例如:/tmp/caddy
可自行编译对应架构的二进制程序:编译教程")) -o.datatype = "string" -o.default = "/usr/bin/caddy" - -o = s:option(Value, "data_dir", translate("指向路径"), - translate("指向一个路径,可在web界面访问你的文件,默认为 /mnt")) -o.datatype = "string" -o.default = "/mnt" -o:depends("cmd", "默认") - -o = s:option(Flag, "webdav", translate("启用 webdav")) -o.default = "0" -o:depends("cmd", "默认") - -o = s:option(Flag,"webdav_pass", translate("启用 认证")) -o.datatype = "string" -o:depends("webdav", "1") -o.default = "0" - -o = s:option(Value,"webdav_username", translate("用户名")) -o.datatype = "string" -o.default = "admin" -o:depends("webdav_pass", "1") - -o = s:option(Value,"webdav_password", translate("密码")) -o.datatype = "string" -o.password = true -o.default = "123456" -o:depends("webdav_pass", "1") - -o = s:option(Value,"webdav_port", translate("webdav端口")) -o.datatype = "and(port,min(1))" -o.default = "12322" -o:depends("webdav", "1") - -o = s:option(Flag, "webzip", translate("启用 压缩")) -o.default = 0 -o:depends("webdav", "1") - -o = s:option(Value, "webdav_data_dir", translate("webdav指向路径"), - translate("指向一个路径,使用webdav访问你的文件,默认为 /mnt
连接地址须加/dav后缀  如: 192.168.1.1:12311/dav")) -o.datatype = "string" -o:depends("webdav", "1") -o.default = "/mnt" - -o = s:option(Flag, "allow_wan", translate("允许从外网访问")) -o.rmempty = false - -o = s:option(Flag, "api", translate("启用 API接口")) -o:depends("cmd", "默认") - -o = s:option(Button, "admin_info", translate("检测配置文件"), - translate("验证Caddyfile配置文件是否正确,它会模拟启动caddy
但是并不会真的启动,会列出详细信息,以便修正配置文件")) - +o = s:option(Button, "validate", translate("检测配置文件")) o.rawhtml = true o.template = "caddy/admin_info" +--- 主程序 --- +o = s:option(Value, "bin_dir", translate("主程序"), + translate("caddy 二进制完整路径")) +o.default = "/usr/sbin/caddy" +o.placeholder = "/usr/sbin/caddy" + +o = s:option(Value, "download_url", translate("下载地址(可选)"), + translate("留空用默认 GitHub latest Release. 填了则用该 URL (可换镜像/固定版本). 下载带重试+SHA256 校验")) +o.placeholder = "https://github.com/andangel/luci-app-caddy/releases/latest/download/caddy_linux_arm64" + +o = s:option(Button, "download", translate("下载并重启")) +o.rawhtml = true +o.template = "caddy/download" + +--- 日志 --- +o = s:option(Value, "log_dir", translate("日志"), + translate("各 Host 的访问日志写入此文件, 建议放 /tmp 以免占用闪存")) +o.default = "/tmp/caddy/requests.log" +o.placeholder = "/tmp/caddy/requests.log" + return m diff --git a/luci-app-caddy/luasrc/model/cbi/caddy/caddyfile.lua b/luci-app-caddy/luasrc/model/cbi/caddy/caddyfile.lua new file mode 100644 index 000000000..dee58094f --- /dev/null +++ b/luci-app-caddy/luasrc/model/cbi/caddy/caddyfile.lua @@ -0,0 +1,45 @@ +local m, s, o + +-- 手动 Caddyfile 编辑: 路径从 UCI caddy_file 取 (默认 /etc/caddy/Caddyfile), +-- 与 init.d 里 G_FILE 一致, 避免用户在 [状态] 页改 bin_dir/log_dir 后此处仍写死路径. +local uci = require("luci.model.uci").cursor() +-- get_first 返回的是 section name 而非字段值, 必须用 get(cfg, section, key) 取字段 +local cf_file = uci:get("caddy", "caddy", "caddy_file") or "/etc/caddy/Caddyfile" + +local cur_caddyfile +local cfh = io.open(cf_file, "r") +if cfh then + cur_caddyfile = cfh:read("*a") + cfh:close() +end + +m = Map("caddy", translate("Caddy 配置 (手动)"), + translate("直接编辑 当前 Caddyfile (" .. cf_file .. "). 默认载入当前文件. 注意: 在 [主机] 页重启 Caddy 时会按表单重新生成并覆盖此页内容")) + +s = m:section(TypedSection, "caddy") +s.addremove = false +s.anonymous = true + +o = s:option(TextValue, "caddyfile", translate("Caddyfile"), + translate("完整 Caddyfile. 要 Caddy 的任何功能 (forward_proxy / encode / basicauth / reverse_proxy ...) 都写在这里
" + .. "如需设置密码, 用命令生成 hash: caddy hash-password --plaintext 新密码")) +o.default = cur_caddyfile +o.rows = 25 +o.wrap = "off" + +o = s:option(Button, "apply_caddyfile", translate("写入并重启 Caddy")) +o.inputtitle = translate("写入并重启 Caddy") +o.description = translate("把上方内容写入当前 Caddyfile 并重启 Caddy (下次 [主机] 页重启 Caddy 会覆盖)") +o.inputstyle = "apply" +o.write = function(_, value) + -- 经 /tmp 中转, 避免写一半坏掉 Caddyfile; 写完 mv 原子替换 + local tmp = "/tmp/caddyfile_manual.tmp" + local f = io.open(tmp, "w") + if f then + f:write(value or "") + f:close() + os.execute(string.format("cp '%s' '%s' && rm -f '%s' && /etc/init.d/caddy restart", tmp, cf_file, tmp)) + end +end + +return m diff --git a/luci-app-caddy/luasrc/model/cbi/caddy/domain.lua b/luci-app-caddy/luasrc/model/cbi/caddy/domain.lua new file mode 100644 index 000000000..a1649b131 --- /dev/null +++ b/luci-app-caddy/luasrc/model/cbi/caddy/domain.lua @@ -0,0 +1,65 @@ +local m, s, o +local uci = require("luci.model.uci").cursor() + +m = Map("caddy", translate("Caddy Domain"), + translate("域名列表 (增删改查). 每条域名对应一个阿里云账号 (AK), 该域名的 TLS 证书和 DDNS 都用这份 AK")) + +--- Domain (多条) - 增删改查, 每条 = 一个域名 + 对应的阿里云 AK + DDNS 参数 --- +--- anonymous: 用户点"添加"即可, 无需填 UCI 标识符 (域名含 . 不合 UCI 命名规范) --- +s = m:section(TypedSection, "domain") +s.addremove = true +s.anonymous = true +s.nodeps = true +s.cfgtitle = "domain_name" + +o = s:option(Value, "domain_name", translate("域名"), + translate("例如 example.com. Host 页的监听域名下拉从这里取")) +o.rmempty = false + +o = s:option(Value, "key_id", translate("阿里云 Access Key ID"), + translate("该域名的 AliDNS 写权限 AK. 需要 AliyunDNS 的 Read/Write 权限")) +o.rmempty = false + +o = s:option(Value, "key_secret", translate("阿里云 Access Key Secret")) +o.password = true +o.rmempty = false + +--- DDNS 子块: 勾选 ddns_on 才显示以下字段 --- +o = s:option(Flag, "ddns_on", translate("启用 DDNS (更新 A 记录)"), + translate("勾选 = 生成的 Caddyfile 包含该域名的 aliyun_ddns 块, 定期把公网 IP 写入 A 记录")) +o.default = 0 + +o = s:option(TextValue, "ddns_records", translate("额外记录名 (每行一条)"), + translate("每行一个子域名前缀, 用换行分隔, 不要带主域名. 例如填 mail 会更新 mail.example.com, 填 www 会更新 www.example.com. 留空 = 只更新 @ 主域 A 记录")) +o.rows = 3 +o.wrap = "off" +o:depends("ddns_on", "1") + +o = s:option(ListValue, "ddns_wan_iface", translate("IP 获取方式"), + translate("public = 公网查询 (api.ipify.org 等取出口 IP); 选接口 = 直读该网卡 IPv4. 一般填 pppoe-wan, 下拉自动列出路由器实际接口")) +o:value("public") +local netf = io.popen("ls /sys/class/net 2>/dev/null") +if netf then + for line in netf:lines() do + local name = line:match("%S+") + -- 只保留 WAN 口 (名字含 wan, 如 pppoe-wan / eth0-wan), 过滤掉 LAN/WiFi/桥等无关设备 + if name and name:lower():find("wan", 1, true) then + o:value(name) + end + end + netf:close() +end +o.default = "pppoe-wan" +o:depends("ddns_on", "1") + +o = s:option(Value, "ddns_check_interval", translate("检查间隔"), + translate("多久检查一次 A 记录是否需要更新")) +o.default = "5m" +o:depends("ddns_on", "1") + +o = s:option(Value, "ddns_ttl", translate("TTL"), + translate("A 记录 TTL, 影响公网 DNS 生效速度")) +o.default = "60s" +o:depends("ddns_on", "1") + +return m diff --git a/luci-app-caddy/luasrc/model/cbi/caddy/host.lua b/luci-app-caddy/luasrc/model/cbi/caddy/host.lua new file mode 100644 index 000000000..dbf252a9c --- /dev/null +++ b/luci-app-caddy/luasrc/model/cbi/caddy/host.lua @@ -0,0 +1,80 @@ +local m, s, gs, o + +m = Map("caddy", translate("Caddy Host"), + translate("反向代理 Host 列表 (增删改查). 每条 Host 对应 Caddyfile 里一段 域名:端口 { ... }")) + +---------------------------------------------------------------------- +-- 通用开关 (全局, 单例 @caddy[0]). 放在 Host 列表上方: 跨所有 Host 生效, +-- 不是某一条 Host 的属性. 后续全局项 (如日志开关) 也加在这个 section 里 +---------------------------------------------------------------------- +gs = m:section(TypedSection, "caddy") +gs.addremove = false +gs.anonymous = true +gs.nodeps = true + +o = gs:option(Flag, "disable_redirect", translate("保留 HTTP (禁用自动跳转 HTTPS)"), + translate("勾选 = Caddyfile 写 auto_https disable_redirects; 不勾 = 不写, Caddy 默认 HTTP 自动 301 到 HTTPS")) +o.default = 1 + +o = gs:option(Flag, "log_on", translate("请求日志"), + translate("勾选 = 各 Host 写访问日志到 [状态] 页的 日志 路径; 不勾 = Caddyfile 不生成 log 块, 不写日志, 省资源")) +o.default = 1 + +---------------------------------------------------------------------- +-- Host (多条) - 增删改查, NPM 的 Proxy Hosts +---------------------------------------------------------------------- +s = m:section(TypedSection, "proxy") +s.addremove = true +s.anonymous = true +s.nodeps = true +s.cfgtitle = "name" + +o = s:option(Value, "name", translate("名称"), + translate("给这条 Host 起个名, 便于区分")) +o.rmempty = false + +o = s:option(Flag, "enabled", translate("启用")) +o.default = 1 + +o = s:option(ListValue, "domain", translate("监听域名"), + translate("从 [Domain] 页的域名列表中选择. 每个域名列两个选项: 裸域 (签 example.com 一张证书, 覆盖裸域) 和 *. 通配符 (签 裸域+通配符 各一张, 覆盖裸域+全部子域, 等价 acme.sh 两条 -d). 只要 Domain 页填了 AK 就一律走 DNS-01, 不依赖 80/443 入站. 需先在 Domain 页创建域名")) +o.rmempty = false +-- 从 UCI 文件读 domain_name: 直接读 /etc/config/caddy, 匹配有引号和没引号两种风格. +-- 比 grep + sed 更稳 (不靠 shell pipeline 转义), 比 uci cursor 更简单 (不依赖 cursor API 版本差异). +local f = io.open("/etc/config/caddy", "r") +if f then + for line in f:lines() do + local dn = line:match("option%s+domain_name%s+'([^']+)'") + or line:match("option%s+domain_name%s+([%w%.%-]+)") + if dn and #dn > 0 then + o:value(dn) + o:value("*." .. dn) + end + end + f:close() +end + +o = s:option(Value, "port", translate("监听端口"), + translate("非标端口可防指纹扫描, 例如 443 / 8443")) +o.datatype = "and(port,min(1))" +o.default = "443" + +o = s:option(Value, "target", translate("转发目标"), + translate("内网后端地址, 形如 192.168.1.x:8080")) +o.rmempty = false + +o = s:option(ListValue, "tls_cert", translate("TLS 证书"), + translate("auto = Caddy 自动签发+自动续签 (Domain 页填了 AK 走 DNS-01, 不依赖 80/443 入站); off = 纯 HTTP 不加密 (内网调试用)")) +o.default = "auto" +o:value("auto") +o:value("off") + +o = s:option(Value, "api_key", translate("API Key (可选)"), + translate("留空 = Caddy 不做 key 校验 (交给后端); 填值 = Caddy 层校验 Authorization: Bearer ")) + +o = s:option(TextValue, "extra", translate("自定义指令 (可选)"), + translate("Caddyfile 语法, 原样插入此 Host 块末尾. 用于 path 路由 / rate_limit / encode / basicauth / handle 等表单没覆盖的指令")) +o.rows = 6 +o.wrap = "off" + +return m diff --git a/luci-app-caddy/luasrc/view/caddy/admin_info.htm b/luci-app-caddy/luasrc/view/caddy/admin_info.htm index 0d9ef5f0a..d6b6931ef 100644 --- a/luci-app-caddy/luasrc/view/caddy/admin_info.htm +++ b/luci-app-caddy/luasrc/view/caddy/admin_info.htm @@ -4,7 +4,7 @@ { btn.disabled = true; btn.value = '<%:Collecting data...%>'; - XHR.get('<%=luci.dispatcher.build_url("admin", "nas", "caddy", "admin_info")%>', + XHR.get('<%=luci.dispatcher.build_url("admin", "network", "caddy", "admin_info")%>', null, function(x,rv) { diff --git a/luci-app-caddy/luasrc/view/caddy/caddy_log.htm b/luci-app-caddy/luasrc/view/caddy/caddy_log.htm index cc481b942..0a49a80af 100644 --- a/luci-app-caddy/luasrc/view/caddy/caddy_log.htm +++ b/luci-app-caddy/luasrc/view/caddy/caddy_log.htm @@ -1,124 +1,167 @@ - + -
-
+
+
- - + +
-
- - 共 {{ filteredLogs.length }} 条日志 +
+   + 0 <%:条日志%>
+

<%:加载中...%>

+
-
-
-
时间:{{ time(log.ts) }}
-
访问:{{ fullUrl(log) }}
-
方法:{{ log.request.method }}
-
状态:{{ log.status }}
-
IP:{{ realIp(log) }}
-
标识:{{ userAgent(log) }}
-
耗时:{{ (log.duration*1000).toFixed(2) }} ms
- -
- -
-
-
- -