feat(adguard): add AdGuard Home component with DNS modes, failover and section upstream

Add AdGuard Home as an installable Tachyon component (component card with
check/update/remove/rollback/enable/disable) backed by providers/adguard
(validator + runtime) and an init.d service.

Backend:
- action.uc: install/remove/enable/backup/rollback/dispatch/releases for
  'adguard' (tar.gz from AdguardTeam/AdGuardHome, OpenWrt->AdGuard arch map)
- updater.uc: adguard asset selection; updates.uc: valid_component_name and
  automatic update checks
- runtime.uc: generates AdGuardHome.yaml from UCI (upstream=single-box DNS,
  fallback_dns for failover, web UI on LAN, filters), manages the daemon
- dns/apply.uc: dns_main_mode (dnsmasq->AdGuard->sing-box chain, or AdGuard on
  :53 with dnsmasq DHCP-only)
- settings: dns_main_mode (Main DNS) + dns_adguard_failover_enabled
- sections: routed_dns_type/dns_type 'adguard' resolve via the AdGuard provider
  upstreams; validator accepts the new type
- system-info: adguard_version/installed/enabled/backup fields

Frontend:
- component card 'AdGuard Home' with enable/disable + version actions
- LuCI DNS settings tab: Main DNS selector and failover flag
- section.js: AdGuard Home routed DNS protocol option

Tests: tests/adguard_validator.sh, tests/adguard_runtime.sh
This commit is contained in:
2026-09-17 14:22:13 +03:00
parent 5bfe7103dc
commit 617107ef15
26 changed files with 1438 additions and 31 deletions
@@ -53,6 +53,13 @@ const componentActionKeyMap: Record<string, UpdatesActionKey> = {
'tailscale:install_version': 'tailscaleInstall',
'tailscale:remove': 'tailscaleRemove',
'tailscale:rollback': 'tailscaleRollback',
'adguard:check_update': 'adguardCheck',
'adguard:install': 'adguardInstall',
'adguard:install_version': 'adguardInstall',
'adguard:remove': 'adguardRemove',
'adguard:rollback': 'adguardRollback',
'adguard:enable': 'adguardEnable',
'adguard:disable': 'adguardDisable',
'direct_bypass:enable': 'directBypassEnable',
'direct_bypass:disable': 'directBypassDisable',
'torrserver_direct:enable': 'torrserverDirectEnable',
@@ -249,6 +249,11 @@ export interface StoreType {
fptn_installed: number;
fptn_backup_version?: string;
fptn_backup_time?: number;
adguard_version?: string;
adguard_installed?: number;
adguard_enabled?: number;
adguard_backup_version?: string;
adguard_backup_time?: number;
server_inbounds_enabled_count: number;
direct_bypass_enabled?: number;
direct_bypass_address?: string;
@@ -301,6 +306,12 @@ export interface StoreType {
tailscaleInstall: { loading: boolean };
tailscaleRemove: { loading: boolean };
tailscaleRollback: { loading: boolean };
adguardCheck: { loading: boolean };
adguardInstall: { loading: boolean };
adguardRemove: { loading: boolean };
adguardRollback: { loading: boolean };
adguardEnable: { loading: boolean };
adguardDisable: { loading: boolean };
directBypassEnable: { loading: boolean };
directBypassDisable: { loading: boolean };
torrserverDirectEnable: { loading: boolean };
@@ -32,6 +32,9 @@ const UNKNOWN_SYSTEM_INFO: StoreType['diagnosticsSystemInfo'] = {
tailscale_installed: 0,
fptn_version: _('unknown'),
fptn_installed: 0,
adguard_version: _('unknown'),
adguard_installed: 0,
adguard_enabled: 0,
server_inbounds_enabled_count: -1,
openwrt_version: _('unknown'),
device_model: _('unknown'),
@@ -53,6 +53,12 @@ function getEmptyUpdatesActions(): StoreType['updatesActions'] {
tailscaleInstall: { loading: false },
tailscaleRemove: { loading: false },
tailscaleRollback: { loading: false },
adguardCheck: { loading: false },
adguardInstall: { loading: false },
adguardRemove: { loading: false },
adguardRollback: { loading: false },
adguardEnable: { loading: false },
adguardDisable: { loading: false },
directBypassEnable: { loading: false },
directBypassDisable: { loading: false },
torrserverDirectEnable: { loading: false },
@@ -207,6 +207,12 @@ export const initialDiagnosticStore: Pick<
tailscaleInstall: { loading: false },
tailscaleRemove: { loading: false },
tailscaleRollback: { loading: false },
adguardCheck: { loading: false },
adguardInstall: { loading: false },
adguardRemove: { loading: false },
adguardRollback: { loading: false },
adguardEnable: { loading: false },
adguardDisable: { loading: false },
directBypassEnable: { loading: false },
directBypassDisable: { loading: false },
torrserverDirectEnable: { loading: false },
@@ -222,6 +228,7 @@ export const initialDiagnosticStore: Pick<
olcrtc: { status: null, latest_version: '', release_url: '' },
fptn: { status: null, latest_version: '', release_url: '' },
tailscale: { status: null, latest_version: '', release_url: '' },
adguard: { status: null, latest_version: '', release_url: '' },
direct_bypass: { status: null, latest_version: '', release_url: '' },
torrserver_direct: { status: null, latest_version: '', release_url: '' },
},
@@ -78,6 +78,8 @@ function getComponentCardTitle(component: Tachyon.ComponentName): string {
return 'FPTN';
case 'tailscale':
return 'Tailscale';
case 'adguard':
return 'AdGuard Home';
default:
return String(component);
}
@@ -106,6 +108,8 @@ function getComponentCurrentVersion(
return sys.fptn_version;
case 'tailscale':
return sys.tailscale_version;
case 'adguard':
return sys.adguard_version;
default:
return undefined;
}
@@ -577,6 +581,23 @@ function patchSystemInfoAfterMutation(result: Tachyon.ComponentActionResult) {
}
}
if (result.component === 'adguard') {
nextSystemInfo.providerInfoLoaded = true;
if (result.action === 'remove') {
nextSystemInfo.adguard_installed = 0;
nextSystemInfo.adguard_version = 'not installed';
nextSystemInfo.adguard_enabled = 0;
} else if (result.action === 'enable') {
nextSystemInfo.adguard_enabled = 1;
} else if (result.action === 'disable') {
nextSystemInfo.adguard_enabled = 0;
} else {
nextSystemInfo.adguard_installed = 1;
nextSystemInfo.adguard_version = version;
}
}
if (result.component === 'direct_bypass') {
nextSystemInfo.direct_bypass_enabled = result.action === 'enable' ? 1 : 0;
}
@@ -1102,6 +1123,8 @@ function getComponentInstallKey(
return 'olcrtcInstall';
case 'tailscale':
return 'tailscaleInstall';
case 'adguard':
return 'adguardInstall';
default:
return 'tachyonInstall';
}
@@ -1151,6 +1174,8 @@ function getComponentBackupVersion(component: Tachyon.ComponentName): string {
return sys.fptn_backup_version || '';
case 'tailscale':
return sys.tailscale_backup_version || '';
case 'adguard':
return sys.adguard_backup_version || '';
default:
return '';
}
@@ -1185,7 +1210,8 @@ function getOptionalComponentActions({
| 'wdtt'
| 'olcrtc'
| 'fptn'
| 'tailscale';
| 'tailscale'
| 'adguard';
installed: boolean;
checkKey: UpdatesActionKey;
installKey: UpdatesActionKey;
@@ -1225,6 +1251,7 @@ const COMPONENT_REPO_URLS: Record<Tachyon.ComponentName, string> = {
olcrtc: 'https://github.com/Dushnilin/openwrt-olcrtc',
fptn: 'https://github.com/Dushnilin/fptn',
tailscale: 'https://openwrt.org/packages/pkgdata/tailscale',
adguard: 'https://github.com/AdguardTeam/AdGuardHome',
direct_bypass: '',
torrserver_direct: '',
};
@@ -1383,6 +1410,28 @@ function getComponentCards(): ComponentCard[] {
removeKey: 'tailscaleRemove',
rollbackKey: 'tailscaleRollback',
});
const adguardInstalled = Boolean(systemInfo.adguard_installed);
const adguardActions = [
...getOptionalComponentActions({
component: 'adguard',
installed: adguardInstalled,
checkKey: 'adguardCheck',
installKey: 'adguardInstall',
removeKey: 'adguardRemove',
rollbackKey: 'adguardRollback',
}),
{
key: systemInfo.adguard_enabled
? ('adguardDisable' as UpdatesActionKey)
: ('adguardEnable' as UpdatesActionKey),
text: systemInfo.adguard_enabled ? _('Disable') : _('Enable'),
icon: renderRotateCcwIcon24,
component: 'adguard' as const,
action: systemInfo.adguard_enabled
? ('disable' as Tachyon.ComponentAction)
: ('enable' as Tachyon.ComponentAction),
},
];
const directBypassEnabled = Boolean(systemInfo.direct_bypass_enabled);
const directBypassEndpoint = systemInfo.direct_bypass_address
@@ -1602,6 +1651,21 @@ function getComponentCards(): ComponentCard[] {
repoUrl: COMPONENT_REPO_URLS.tailscale,
actions: tailscaleActions,
},
{
component: 'adguard',
column: 2,
title: 'AdGuard Home',
version: systemInfoLoading
? _('Loading...')
: adguardInstalled
? `${systemInfo.adguard_enabled ? _('Enabled') : _('Disabled')} · ${systemInfo.adguard_version || _('unknown')}`
: _('Not installed'),
latestVersion: getLatestVersion('adguard'),
releaseUrl: getGitHubReleaseUrl('adguard'),
repoUrl: COMPONENT_REPO_URLS.adguard,
actions: adguardActions,
supportsVersions: true,
},
];
}
+8
View File
@@ -666,6 +666,11 @@ export namespace Tachyon {
fptn_supported?: 0 | 1;
fptn_backup_version?: string;
fptn_backup_time?: number;
adguard_version?: string;
adguard_installed?: 0 | 1;
adguard_enabled?: 0 | 1;
adguard_backup_version?: string;
adguard_backup_time?: number;
direct_bypass_enabled?: 0 | 1;
direct_bypass_address?: string;
direct_bypass_port?: string;
@@ -680,6 +685,7 @@ export namespace Tachyon {
show_component_olcrtc?: 0 | 1;
show_component_fptn?: 0 | 1;
show_component_tailscale?: 0 | 1;
show_component_adguard?: 0 | 1;
show_component_direct_bypass?: 0 | 1;
show_component_torrserver_direct?: 0 | 1;
openwrt_version: string;
@@ -702,6 +708,7 @@ export namespace Tachyon {
zapret_installed: 0 | 1;
zapret2_installed: 0 | 1;
byedpi_installed: 0 | 1;
adguard_installed?: 0 | 1;
wdtt_installed: 0 | 1;
olcrtc_installed: 0 | 1;
server_inbounds_enabled_count: number;
@@ -769,6 +776,7 @@ export namespace Tachyon {
| 'olcrtc'
| 'fptn'
| 'tailscale'
| 'adguard'
| 'direct_bypass'
| 'torrserver_direct';