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:
@@ -53,6 +53,13 @@ const componentActionKeyMap: Record<string, UpdatesActionKey> = {
|
|||||||
'tailscale:install_version': 'tailscaleInstall',
|
'tailscale:install_version': 'tailscaleInstall',
|
||||||
'tailscale:remove': 'tailscaleRemove',
|
'tailscale:remove': 'tailscaleRemove',
|
||||||
'tailscale:rollback': 'tailscaleRollback',
|
'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:enable': 'directBypassEnable',
|
||||||
'direct_bypass:disable': 'directBypassDisable',
|
'direct_bypass:disable': 'directBypassDisable',
|
||||||
'torrserver_direct:enable': 'torrserverDirectEnable',
|
'torrserver_direct:enable': 'torrserverDirectEnable',
|
||||||
|
|||||||
@@ -249,6 +249,11 @@ export interface StoreType {
|
|||||||
fptn_installed: number;
|
fptn_installed: number;
|
||||||
fptn_backup_version?: string;
|
fptn_backup_version?: string;
|
||||||
fptn_backup_time?: number;
|
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;
|
server_inbounds_enabled_count: number;
|
||||||
direct_bypass_enabled?: number;
|
direct_bypass_enabled?: number;
|
||||||
direct_bypass_address?: string;
|
direct_bypass_address?: string;
|
||||||
@@ -301,6 +306,12 @@ export interface StoreType {
|
|||||||
tailscaleInstall: { loading: boolean };
|
tailscaleInstall: { loading: boolean };
|
||||||
tailscaleRemove: { loading: boolean };
|
tailscaleRemove: { loading: boolean };
|
||||||
tailscaleRollback: { 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 };
|
directBypassEnable: { loading: boolean };
|
||||||
directBypassDisable: { loading: boolean };
|
directBypassDisable: { loading: boolean };
|
||||||
torrserverDirectEnable: { loading: boolean };
|
torrserverDirectEnable: { loading: boolean };
|
||||||
|
|||||||
@@ -32,6 +32,9 @@ const UNKNOWN_SYSTEM_INFO: StoreType['diagnosticsSystemInfo'] = {
|
|||||||
tailscale_installed: 0,
|
tailscale_installed: 0,
|
||||||
fptn_version: _('unknown'),
|
fptn_version: _('unknown'),
|
||||||
fptn_installed: 0,
|
fptn_installed: 0,
|
||||||
|
adguard_version: _('unknown'),
|
||||||
|
adguard_installed: 0,
|
||||||
|
adguard_enabled: 0,
|
||||||
server_inbounds_enabled_count: -1,
|
server_inbounds_enabled_count: -1,
|
||||||
openwrt_version: _('unknown'),
|
openwrt_version: _('unknown'),
|
||||||
device_model: _('unknown'),
|
device_model: _('unknown'),
|
||||||
|
|||||||
@@ -53,6 +53,12 @@ function getEmptyUpdatesActions(): StoreType['updatesActions'] {
|
|||||||
tailscaleInstall: { loading: false },
|
tailscaleInstall: { loading: false },
|
||||||
tailscaleRemove: { loading: false },
|
tailscaleRemove: { loading: false },
|
||||||
tailscaleRollback: { 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 },
|
directBypassEnable: { loading: false },
|
||||||
directBypassDisable: { loading: false },
|
directBypassDisable: { loading: false },
|
||||||
torrserverDirectEnable: { loading: false },
|
torrserverDirectEnable: { loading: false },
|
||||||
|
|||||||
@@ -207,6 +207,12 @@ export const initialDiagnosticStore: Pick<
|
|||||||
tailscaleInstall: { loading: false },
|
tailscaleInstall: { loading: false },
|
||||||
tailscaleRemove: { loading: false },
|
tailscaleRemove: { loading: false },
|
||||||
tailscaleRollback: { 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 },
|
directBypassEnable: { loading: false },
|
||||||
directBypassDisable: { loading: false },
|
directBypassDisable: { loading: false },
|
||||||
torrserverDirectEnable: { loading: false },
|
torrserverDirectEnable: { loading: false },
|
||||||
@@ -222,6 +228,7 @@ export const initialDiagnosticStore: Pick<
|
|||||||
olcrtc: { status: null, latest_version: '', release_url: '' },
|
olcrtc: { status: null, latest_version: '', release_url: '' },
|
||||||
fptn: { status: null, latest_version: '', release_url: '' },
|
fptn: { status: null, latest_version: '', release_url: '' },
|
||||||
tailscale: { 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: '' },
|
direct_bypass: { status: null, latest_version: '', release_url: '' },
|
||||||
torrserver_direct: { 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';
|
return 'FPTN';
|
||||||
case 'tailscale':
|
case 'tailscale':
|
||||||
return 'Tailscale';
|
return 'Tailscale';
|
||||||
|
case 'adguard':
|
||||||
|
return 'AdGuard Home';
|
||||||
default:
|
default:
|
||||||
return String(component);
|
return String(component);
|
||||||
}
|
}
|
||||||
@@ -106,6 +108,8 @@ function getComponentCurrentVersion(
|
|||||||
return sys.fptn_version;
|
return sys.fptn_version;
|
||||||
case 'tailscale':
|
case 'tailscale':
|
||||||
return sys.tailscale_version;
|
return sys.tailscale_version;
|
||||||
|
case 'adguard':
|
||||||
|
return sys.adguard_version;
|
||||||
default:
|
default:
|
||||||
return undefined;
|
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') {
|
if (result.component === 'direct_bypass') {
|
||||||
nextSystemInfo.direct_bypass_enabled = result.action === 'enable' ? 1 : 0;
|
nextSystemInfo.direct_bypass_enabled = result.action === 'enable' ? 1 : 0;
|
||||||
}
|
}
|
||||||
@@ -1102,6 +1123,8 @@ function getComponentInstallKey(
|
|||||||
return 'olcrtcInstall';
|
return 'olcrtcInstall';
|
||||||
case 'tailscale':
|
case 'tailscale':
|
||||||
return 'tailscaleInstall';
|
return 'tailscaleInstall';
|
||||||
|
case 'adguard':
|
||||||
|
return 'adguardInstall';
|
||||||
default:
|
default:
|
||||||
return 'tachyonInstall';
|
return 'tachyonInstall';
|
||||||
}
|
}
|
||||||
@@ -1151,6 +1174,8 @@ function getComponentBackupVersion(component: Tachyon.ComponentName): string {
|
|||||||
return sys.fptn_backup_version || '';
|
return sys.fptn_backup_version || '';
|
||||||
case 'tailscale':
|
case 'tailscale':
|
||||||
return sys.tailscale_backup_version || '';
|
return sys.tailscale_backup_version || '';
|
||||||
|
case 'adguard':
|
||||||
|
return sys.adguard_backup_version || '';
|
||||||
default:
|
default:
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
@@ -1185,7 +1210,8 @@ function getOptionalComponentActions({
|
|||||||
| 'wdtt'
|
| 'wdtt'
|
||||||
| 'olcrtc'
|
| 'olcrtc'
|
||||||
| 'fptn'
|
| 'fptn'
|
||||||
| 'tailscale';
|
| 'tailscale'
|
||||||
|
| 'adguard';
|
||||||
installed: boolean;
|
installed: boolean;
|
||||||
checkKey: UpdatesActionKey;
|
checkKey: UpdatesActionKey;
|
||||||
installKey: UpdatesActionKey;
|
installKey: UpdatesActionKey;
|
||||||
@@ -1225,6 +1251,7 @@ const COMPONENT_REPO_URLS: Record<Tachyon.ComponentName, string> = {
|
|||||||
olcrtc: 'https://github.com/Dushnilin/openwrt-olcrtc',
|
olcrtc: 'https://github.com/Dushnilin/openwrt-olcrtc',
|
||||||
fptn: 'https://github.com/Dushnilin/fptn',
|
fptn: 'https://github.com/Dushnilin/fptn',
|
||||||
tailscale: 'https://openwrt.org/packages/pkgdata/tailscale',
|
tailscale: 'https://openwrt.org/packages/pkgdata/tailscale',
|
||||||
|
adguard: 'https://github.com/AdguardTeam/AdGuardHome',
|
||||||
direct_bypass: '',
|
direct_bypass: '',
|
||||||
torrserver_direct: '',
|
torrserver_direct: '',
|
||||||
};
|
};
|
||||||
@@ -1383,6 +1410,28 @@ function getComponentCards(): ComponentCard[] {
|
|||||||
removeKey: 'tailscaleRemove',
|
removeKey: 'tailscaleRemove',
|
||||||
rollbackKey: 'tailscaleRollback',
|
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 directBypassEnabled = Boolean(systemInfo.direct_bypass_enabled);
|
||||||
const directBypassEndpoint = systemInfo.direct_bypass_address
|
const directBypassEndpoint = systemInfo.direct_bypass_address
|
||||||
@@ -1602,6 +1651,21 @@ function getComponentCards(): ComponentCard[] {
|
|||||||
repoUrl: COMPONENT_REPO_URLS.tailscale,
|
repoUrl: COMPONENT_REPO_URLS.tailscale,
|
||||||
actions: tailscaleActions,
|
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,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -666,6 +666,11 @@ export namespace Tachyon {
|
|||||||
fptn_supported?: 0 | 1;
|
fptn_supported?: 0 | 1;
|
||||||
fptn_backup_version?: string;
|
fptn_backup_version?: string;
|
||||||
fptn_backup_time?: number;
|
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_enabled?: 0 | 1;
|
||||||
direct_bypass_address?: string;
|
direct_bypass_address?: string;
|
||||||
direct_bypass_port?: string;
|
direct_bypass_port?: string;
|
||||||
@@ -680,6 +685,7 @@ export namespace Tachyon {
|
|||||||
show_component_olcrtc?: 0 | 1;
|
show_component_olcrtc?: 0 | 1;
|
||||||
show_component_fptn?: 0 | 1;
|
show_component_fptn?: 0 | 1;
|
||||||
show_component_tailscale?: 0 | 1;
|
show_component_tailscale?: 0 | 1;
|
||||||
|
show_component_adguard?: 0 | 1;
|
||||||
show_component_direct_bypass?: 0 | 1;
|
show_component_direct_bypass?: 0 | 1;
|
||||||
show_component_torrserver_direct?: 0 | 1;
|
show_component_torrserver_direct?: 0 | 1;
|
||||||
openwrt_version: string;
|
openwrt_version: string;
|
||||||
@@ -702,6 +708,7 @@ export namespace Tachyon {
|
|||||||
zapret_installed: 0 | 1;
|
zapret_installed: 0 | 1;
|
||||||
zapret2_installed: 0 | 1;
|
zapret2_installed: 0 | 1;
|
||||||
byedpi_installed: 0 | 1;
|
byedpi_installed: 0 | 1;
|
||||||
|
adguard_installed?: 0 | 1;
|
||||||
wdtt_installed: 0 | 1;
|
wdtt_installed: 0 | 1;
|
||||||
olcrtc_installed: 0 | 1;
|
olcrtc_installed: 0 | 1;
|
||||||
server_inbounds_enabled_count: number;
|
server_inbounds_enabled_count: number;
|
||||||
@@ -769,6 +776,7 @@ export namespace Tachyon {
|
|||||||
| 'olcrtc'
|
| 'olcrtc'
|
||||||
| 'fptn'
|
| 'fptn'
|
||||||
| 'tailscale'
|
| 'tailscale'
|
||||||
|
| 'adguard'
|
||||||
| 'direct_bypass'
|
| 'direct_bypass'
|
||||||
| 'torrserver_direct';
|
| 'torrserver_direct';
|
||||||
|
|
||||||
|
|||||||
@@ -6124,6 +6124,12 @@ var initialDiagnosticStore = {
|
|||||||
tailscaleInstall: { loading: false },
|
tailscaleInstall: { loading: false },
|
||||||
tailscaleRemove: { loading: false },
|
tailscaleRemove: { loading: false },
|
||||||
tailscaleRollback: { 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 },
|
directBypassEnable: { loading: false },
|
||||||
directBypassDisable: { loading: false },
|
directBypassDisable: { loading: false },
|
||||||
torrserverDirectEnable: { loading: false },
|
torrserverDirectEnable: { loading: false },
|
||||||
@@ -6139,6 +6145,7 @@ var initialDiagnosticStore = {
|
|||||||
olcrtc: { status: null, latest_version: "", release_url: "" },
|
olcrtc: { status: null, latest_version: "", release_url: "" },
|
||||||
fptn: { status: null, latest_version: "", release_url: "" },
|
fptn: { status: null, latest_version: "", release_url: "" },
|
||||||
tailscale: { 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: "" },
|
direct_bypass: { status: null, latest_version: "", release_url: "" },
|
||||||
torrserver_direct: { status: null, latest_version: "", release_url: "" }
|
torrserver_direct: { status: null, latest_version: "", release_url: "" }
|
||||||
}
|
}
|
||||||
@@ -6533,6 +6540,13 @@ var componentActionKeyMap = {
|
|||||||
"tailscale:install_version": "tailscaleInstall",
|
"tailscale:install_version": "tailscaleInstall",
|
||||||
"tailscale:remove": "tailscaleRemove",
|
"tailscale:remove": "tailscaleRemove",
|
||||||
"tailscale:rollback": "tailscaleRollback",
|
"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:enable": "directBypassEnable",
|
||||||
"direct_bypass:disable": "directBypassDisable",
|
"direct_bypass:disable": "directBypassDisable",
|
||||||
"torrserver_direct:enable": "torrserverDirectEnable",
|
"torrserver_direct:enable": "torrserverDirectEnable",
|
||||||
@@ -6719,6 +6733,12 @@ function getEmptyUpdatesActions() {
|
|||||||
tailscaleInstall: { loading: false },
|
tailscaleInstall: { loading: false },
|
||||||
tailscaleRemove: { loading: false },
|
tailscaleRemove: { loading: false },
|
||||||
tailscaleRollback: { 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 },
|
directBypassEnable: { loading: false },
|
||||||
directBypassDisable: { loading: false },
|
directBypassDisable: { loading: false },
|
||||||
torrserverDirectEnable: { loading: false },
|
torrserverDirectEnable: { loading: false },
|
||||||
@@ -11236,6 +11256,9 @@ var UNKNOWN_SYSTEM_INFO = {
|
|||||||
tailscale_installed: 0,
|
tailscale_installed: 0,
|
||||||
fptn_version: _("unknown"),
|
fptn_version: _("unknown"),
|
||||||
fptn_installed: 0,
|
fptn_installed: 0,
|
||||||
|
adguard_version: _("unknown"),
|
||||||
|
adguard_installed: 0,
|
||||||
|
adguard_enabled: 0,
|
||||||
server_inbounds_enabled_count: -1,
|
server_inbounds_enabled_count: -1,
|
||||||
openwrt_version: _("unknown"),
|
openwrt_version: _("unknown"),
|
||||||
device_model: _("unknown")
|
device_model: _("unknown")
|
||||||
@@ -21159,6 +21182,8 @@ function getComponentCardTitle(component) {
|
|||||||
return "FPTN";
|
return "FPTN";
|
||||||
case "tailscale":
|
case "tailscale":
|
||||||
return "Tailscale";
|
return "Tailscale";
|
||||||
|
case "adguard":
|
||||||
|
return "AdGuard Home";
|
||||||
default:
|
default:
|
||||||
return String(component);
|
return String(component);
|
||||||
}
|
}
|
||||||
@@ -21184,6 +21209,8 @@ function getComponentCurrentVersion(component) {
|
|||||||
return sys.fptn_version;
|
return sys.fptn_version;
|
||||||
case "tailscale":
|
case "tailscale":
|
||||||
return sys.tailscale_version;
|
return sys.tailscale_version;
|
||||||
|
case "adguard":
|
||||||
|
return sys.adguard_version;
|
||||||
default:
|
default:
|
||||||
return void 0;
|
return void 0;
|
||||||
}
|
}
|
||||||
@@ -21512,6 +21539,21 @@ function patchSystemInfoAfterMutation(result) {
|
|||||||
nextSystemInfo.fptn_version = version;
|
nextSystemInfo.fptn_version = version;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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") {
|
if (result.component === "direct_bypass") {
|
||||||
nextSystemInfo.direct_bypass_enabled = result.action === "enable" ? 1 : 0;
|
nextSystemInfo.direct_bypass_enabled = result.action === "enable" ? 1 : 0;
|
||||||
}
|
}
|
||||||
@@ -21907,6 +21949,8 @@ function getComponentInstallKey(component) {
|
|||||||
return "olcrtcInstall";
|
return "olcrtcInstall";
|
||||||
case "tailscale":
|
case "tailscale":
|
||||||
return "tailscaleInstall";
|
return "tailscaleInstall";
|
||||||
|
case "adguard":
|
||||||
|
return "adguardInstall";
|
||||||
default:
|
default:
|
||||||
return "tachyonInstall";
|
return "tachyonInstall";
|
||||||
}
|
}
|
||||||
@@ -21945,6 +21989,8 @@ function getComponentBackupVersion(component) {
|
|||||||
return sys.fptn_backup_version || "";
|
return sys.fptn_backup_version || "";
|
||||||
case "tailscale":
|
case "tailscale":
|
||||||
return sys.tailscale_backup_version || "";
|
return sys.tailscale_backup_version || "";
|
||||||
|
case "adguard":
|
||||||
|
return sys.adguard_backup_version || "";
|
||||||
default:
|
default:
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
@@ -21995,6 +22041,7 @@ var COMPONENT_REPO_URLS = {
|
|||||||
olcrtc: "https://github.com/Dushnilin/openwrt-olcrtc",
|
olcrtc: "https://github.com/Dushnilin/openwrt-olcrtc",
|
||||||
fptn: "https://github.com/Dushnilin/fptn",
|
fptn: "https://github.com/Dushnilin/fptn",
|
||||||
tailscale: "https://openwrt.org/packages/pkgdata/tailscale",
|
tailscale: "https://openwrt.org/packages/pkgdata/tailscale",
|
||||||
|
adguard: "https://github.com/AdguardTeam/AdGuardHome",
|
||||||
direct_bypass: "",
|
direct_bypass: "",
|
||||||
torrserver_direct: ""
|
torrserver_direct: ""
|
||||||
};
|
};
|
||||||
@@ -22139,6 +22186,24 @@ function getComponentCards() {
|
|||||||
removeKey: "tailscaleRemove",
|
removeKey: "tailscaleRemove",
|
||||||
rollbackKey: "tailscaleRollback"
|
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" : "adguardEnable",
|
||||||
|
text: systemInfo.adguard_enabled ? _("Disable") : _("Enable"),
|
||||||
|
icon: renderRotateCcwIcon24,
|
||||||
|
component: "adguard",
|
||||||
|
action: systemInfo.adguard_enabled ? "disable" : "enable"
|
||||||
|
}
|
||||||
|
];
|
||||||
const directBypassEnabled = Boolean(systemInfo.direct_bypass_enabled);
|
const directBypassEnabled = Boolean(systemInfo.direct_bypass_enabled);
|
||||||
const directBypassEndpoint = systemInfo.direct_bypass_address ? `${systemInfo.direct_bypass_address}:${systemInfo.direct_bypass_port || "2080"}` : "";
|
const directBypassEndpoint = systemInfo.direct_bypass_address ? `${systemInfo.direct_bypass_address}:${systemInfo.direct_bypass_port || "2080"}` : "";
|
||||||
const torrserverRunning = Boolean(systemInfo.torrserver_running);
|
const torrserverRunning = Boolean(systemInfo.torrserver_running);
|
||||||
@@ -22294,6 +22359,17 @@ function getComponentCards() {
|
|||||||
releaseUrl: getGitHubReleaseUrl("tailscale"),
|
releaseUrl: getGitHubReleaseUrl("tailscale"),
|
||||||
repoUrl: COMPONENT_REPO_URLS.tailscale,
|
repoUrl: COMPONENT_REPO_URLS.tailscale,
|
||||||
actions: tailscaleActions
|
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
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2223,6 +2223,7 @@ function dnsTypeChoices() {
|
|||||||
{ value: "dot", label: _("DNS over TLS (DoT)") },
|
{ value: "dot", label: _("DNS over TLS (DoT)") },
|
||||||
{ value: "doq", label: _("DNS over QUIC (DoQ)") },
|
{ value: "doq", label: _("DNS over QUIC (DoQ)") },
|
||||||
{ value: "udp", label: "UDP" },
|
{ value: "udp", label: "UDP" },
|
||||||
|
{ value: "adguard", label: _("AdGuard Home") },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -11287,15 +11288,21 @@ function createSectionContent(section) {
|
|||||||
routedDnsTypeOption.value("dot", "DNS over TLS");
|
routedDnsTypeOption.value("dot", "DNS over TLS");
|
||||||
routedDnsTypeOption.value("doh", "DNS over HTTPS");
|
routedDnsTypeOption.value("doh", "DNS over HTTPS");
|
||||||
routedDnsTypeOption.value("doq", "DNS over QUIC");
|
routedDnsTypeOption.value("doq", "DNS over QUIC");
|
||||||
|
routedDnsTypeOption.value("adguard", _("AdGuard Home"));
|
||||||
|
|
||||||
const routedDnsServerOption = addDynamicConditionField(section, {
|
const routedDnsServerOption = addDynamicConditionField(section, {
|
||||||
key: "routed_dns_server",
|
key: "routed_dns_server",
|
||||||
label: _("Routed DNS Servers"),
|
label: _("Routed DNS Servers"),
|
||||||
description: _(
|
description: _(
|
||||||
"DNS server addresses for domain resolution in this section (e.g. 1.1.1.1, https://cloudflare-dns.com/dns-query).",
|
"DNS server addresses for domain resolution in this section (e.g. 1.1.1.1, https://cloudflare-dns.com/dns-query). When 'AdGuard Home' is selected as the protocol, the DNS servers configured in the AdGuard Home provider are used instead.",
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
routedDnsServerOption.depends({ routed_dns_enabled: "1" });
|
routedDnsServerOption.depends({ routed_dns_enabled: "1" });
|
||||||
|
routedDnsServerOption.retain = true;
|
||||||
|
routedDnsServerOption.checkDepends = function (section_id) {
|
||||||
|
const dnsType = uci.get(UCI_PACKAGE, section_id, "routed_dns_type") || "udp";
|
||||||
|
return dnsType !== "adguard";
|
||||||
|
};
|
||||||
|
|
||||||
const portsOption = addDynamicConditionField(section, {
|
const portsOption = addDynamicConditionField(section, {
|
||||||
key: "ports",
|
key: "ports",
|
||||||
|
|||||||
@@ -1360,6 +1360,13 @@ function createComponentsVisibilityWidget(option, section_id) {
|
|||||||
defaultVisible: true,
|
defaultVisible: true,
|
||||||
fixed: false,
|
fixed: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "adguard",
|
||||||
|
label: _("AdGuard Home"),
|
||||||
|
desc: _("Caching DNS with ad blocking and failover"),
|
||||||
|
defaultVisible: true,
|
||||||
|
fixed: false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: "torrserver_direct",
|
id: "torrserver_direct",
|
||||||
label: _("TorrServer Direct"),
|
label: _("TorrServer Direct"),
|
||||||
@@ -1877,6 +1884,34 @@ function createSettingsContent(section, capabilities) {
|
|||||||
o.datatype = "uinteger";
|
o.datatype = "uinteger";
|
||||||
o.rmempty = true;
|
o.rmempty = true;
|
||||||
|
|
||||||
|
// ─── AdGuard Home main DNS mode ──────────────────────────────────────────
|
||||||
|
|
||||||
|
o = section.taboption(
|
||||||
|
"dns",
|
||||||
|
form.ListValue,
|
||||||
|
"dns_main_mode",
|
||||||
|
_("Main DNS"),
|
||||||
|
_(
|
||||||
|
"Choose which resolver serves DNS to the LAN. When AdGuard Home is installed and enabled, 'dnsmasq' keeps the dnsmasq→AdGuard→sing-box chain, and 'AdGuard Home' makes AdGuard the main DNS listener on port 53 (dnsmasq serves DHCP only). Without AdGuard Home this setting has no effect.",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
o.value("dnsmasq", _("dnsmasq (via AdGuard → sing-box)"));
|
||||||
|
o.value("adguard", _("AdGuard Home (port 53, dnsmasq = DHCP only)"));
|
||||||
|
o.default = "dnsmasq";
|
||||||
|
o.rmempty = false;
|
||||||
|
|
||||||
|
o = section.taboption(
|
||||||
|
"dns",
|
||||||
|
form.Flag,
|
||||||
|
"dns_adguard_failover_enabled",
|
||||||
|
_("DNS Failover to AdGuard"),
|
||||||
|
_(
|
||||||
|
"When Tachyon (sing-box DNS) is unreachable, AdGuard Home automatically answers from the fallback DNS servers you configure in its own settings (upstream list). DNS keeps resolving through AdGuard until Tachyon recovers.",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
o.default = "0";
|
||||||
|
o.rmempty = false;
|
||||||
|
|
||||||
// ─── DNS Strategy ────────────────────────────────────────────────────────
|
// ─── DNS Strategy ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
o = section.taboption(
|
o = section.taboption(
|
||||||
|
|||||||
@@ -50,6 +50,9 @@ config settings 'settings'
|
|||||||
option cache_path '/tmp/sing-box/cache.db'
|
option cache_path '/tmp/sing-box/cache.db'
|
||||||
option dns_local_cache '0'
|
option dns_local_cache '0'
|
||||||
option dns_cache_size '10000'
|
option dns_cache_size '10000'
|
||||||
|
option dns_main_mode 'dnsmasq'
|
||||||
|
option dns_adguard_failover_enabled '0'
|
||||||
|
option show_component_adguard '1'
|
||||||
option log_level 'warn'
|
option log_level 'warn'
|
||||||
option exclude_ntp '0'
|
option exclude_ntp '0'
|
||||||
option shutdown_correctly '0'
|
option shutdown_correctly '0'
|
||||||
@@ -311,5 +314,20 @@ config guest_mode 'guest_mode'
|
|||||||
#list days 'monday'
|
#list days 'monday'
|
||||||
option notify '1'
|
option notify '1'
|
||||||
|
|
||||||
|
#config provider 'adguard'
|
||||||
|
# option enabled '0'
|
||||||
|
# option webui_enabled '1'
|
||||||
|
# option webui_port '3000'
|
||||||
|
# option webui_username ''
|
||||||
|
# option webui_password ''
|
||||||
|
# # DNS servers used by AdGuard Home for fallback (when Tachyon DNS is down)
|
||||||
|
# # and as the resolver for sections that choose 'AdGuard Home':
|
||||||
|
# #list upstream_dns 'https://dns.google/dns-query'
|
||||||
|
# #list upstream_dns 'tls://1.1.1.1'
|
||||||
|
# #list bootstrap_dns '1.1.1.1'
|
||||||
|
# #list bootstrap_dns '8.8.8.8'
|
||||||
|
# # Ad-block filter lists (empty = the built-in AdGuard DNS filter):
|
||||||
|
# #list filter_lists 'https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
#!/bin/sh /etc/rc.common
|
||||||
|
|
||||||
|
# Tachyon-managed AdGuard Home service. The configuration file is generated
|
||||||
|
# from /etc/config/tachyon by providers/adguard/runtime.uc; do not edit the
|
||||||
|
# YAML by hand, it is overwritten on every reconfigure.
|
||||||
|
|
||||||
|
USE_PROCD=1
|
||||||
|
START=98
|
||||||
|
STOP=20
|
||||||
|
|
||||||
|
PROG=/usr/bin/AdGuardHome
|
||||||
|
WORK_DIR=/etc/adguard
|
||||||
|
CONFIG=$WORK_DIR/AdGuardHome.yaml
|
||||||
|
PID_FILE=/var/run/tachyon/adguard/adguard.pid
|
||||||
|
|
||||||
|
start_service() {
|
||||||
|
[ -x "$PROG" ] || return 0
|
||||||
|
[ -f "$CONFIG" ] || return 0
|
||||||
|
|
||||||
|
mkdir -p "$WORK_DIR" /var/run/tachyon/adguard
|
||||||
|
|
||||||
|
procd_open_instance
|
||||||
|
procd_set_param command "$PROG" -c "$CONFIG" -w "$WORK_DIR" --pidfile "$PID_FILE" --no-check-update
|
||||||
|
procd_set_param file "$CONFIG"
|
||||||
|
procd_set_param respawn
|
||||||
|
procd_set_param stdout 1
|
||||||
|
procd_set_param stderr 1
|
||||||
|
procd_close_instance
|
||||||
|
}
|
||||||
|
|
||||||
|
stop_service() {
|
||||||
|
[ -f "$PID_FILE" ] || return 0
|
||||||
|
local pid
|
||||||
|
pid="$(cat "$PID_FILE" 2>/dev/null)"
|
||||||
|
[ -n "$pid" ] && kill "$pid" 2>/dev/null
|
||||||
|
rm -f "$PID_FILE"
|
||||||
|
}
|
||||||
@@ -1504,6 +1504,170 @@ function resolve_fptn_release(arch, tag) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function strip_leading_v(value) {
|
||||||
|
value = as_string(value);
|
||||||
|
return substr(value, 0, 1) == "v" ? substr(value, 1) : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolve_adguard_release(arch, tag) {
|
||||||
|
let owner = "AdguardTeam";
|
||||||
|
let repo = "AdGuardHome";
|
||||||
|
let releases_json = (tag != null && tag != "") ?
|
||||||
|
fetch_github_release_by_tag_json(owner, repo, tag) :
|
||||||
|
fetch_github_releases_json(owner, repo, "30");
|
||||||
|
if (releases_json == "" || releases_json == "[]")
|
||||||
|
return null;
|
||||||
|
|
||||||
|
let resolved = trim(helper_output_input(releases_json, "adguard-select-asset", [ arch.candidates ]));
|
||||||
|
let fields = split(resolved, "\t");
|
||||||
|
if (length(fields) >= 5) {
|
||||||
|
return {
|
||||||
|
arch: fields[0],
|
||||||
|
package_name: fields[1],
|
||||||
|
package_url: fields[2],
|
||||||
|
release_url: fields[3],
|
||||||
|
version: strip_leading_v(fields[4])
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function adguard_runtime_module() {
|
||||||
|
return LIB_DIR + "/providers/adguard/runtime.uc";
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensure_adguard_provider_section() {
|
||||||
|
let cursor = uci_core.cursor();
|
||||||
|
if (cursor == null)
|
||||||
|
return;
|
||||||
|
cursor.load("tachyon");
|
||||||
|
if (cursor.get("tachyon", "adguard", "enabled") != null)
|
||||||
|
return;
|
||||||
|
cursor.set("tachyon", "adguard", "provider");
|
||||||
|
cursor.set("tachyon", "adguard", "enabled", "0");
|
||||||
|
cursor.set("tachyon", "adguard", "webui_enabled", "1");
|
||||||
|
cursor.set("tachyon", "adguard", "webui_port", "3000");
|
||||||
|
cursor.commit("tachyon");
|
||||||
|
}
|
||||||
|
|
||||||
|
function install_adguard(action, target_tag) {
|
||||||
|
let component = "adguard";
|
||||||
|
let label = "AdGuard Home";
|
||||||
|
init_tmp_dir() || action_fail(component, action, "Failed to create temporary directory");
|
||||||
|
let arch = resolve_arch_candidates();
|
||||||
|
if (arch == null)
|
||||||
|
action_fail(component, action, "Failed to detect package architecture");
|
||||||
|
|
||||||
|
let release = null;
|
||||||
|
retry_resolve("Resolving " + label + " package", function() {
|
||||||
|
release = resolve_adguard_release(arch, target_tag);
|
||||||
|
return release != null;
|
||||||
|
});
|
||||||
|
if (release == null)
|
||||||
|
action_fail(component, action, "Failed to resolve " + label + " package for this router architecture");
|
||||||
|
|
||||||
|
let runtime_module = adguard_runtime_module();
|
||||||
|
let installed = provider_installed(runtime_module);
|
||||||
|
let current_version = provider_package_version(runtime_module);
|
||||||
|
if (action == "check_update") {
|
||||||
|
if (!installed)
|
||||||
|
action_success(component, action, label + " is not installed", current_version, release.version, 0, "", release.release_url || "");
|
||||||
|
check_success(component, current_version, release.version, release.release_url || "");
|
||||||
|
}
|
||||||
|
|
||||||
|
let pkg = download_direct_package(release);
|
||||||
|
if (pkg == null)
|
||||||
|
action_fail(component, action, "Failed to download " + label + " package");
|
||||||
|
|
||||||
|
let extract_dir = tmp_dir + "/adguard-extract";
|
||||||
|
command_success_from_args([ "mkdir", "-p", extract_dir ]);
|
||||||
|
if (!run_logged("Extracting " + label + " archive",
|
||||||
|
"tar -xzf " + shell_quote(pkg.file) + " -C " + shell_quote(extract_dir)))
|
||||||
|
action_fail(component, action, "Failed to extract " + label + " archive");
|
||||||
|
|
||||||
|
let found_bin = "";
|
||||||
|
let found = trim(command_output_from_args([ "find", extract_dir, "-maxdepth", "2", "-type", "f", "-name", "AdGuardHome" ]));
|
||||||
|
for (let line in split(found, "\n")) {
|
||||||
|
line = trim(as_string(line));
|
||||||
|
if (line != "") {
|
||||||
|
found_bin = line;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (found_bin == "")
|
||||||
|
action_fail(component, action, "AdGuardHome binary was not found in the downloaded archive");
|
||||||
|
|
||||||
|
module_success([ runtime_module, "stop-runtime" ]);
|
||||||
|
|
||||||
|
if (!install_staged_file(found_bin, "/usr/bin/AdGuardHome", "0755"))
|
||||||
|
action_fail(component, action, "Failed to install " + label + " binary");
|
||||||
|
|
||||||
|
command_success_from_args([ "mkdir", "-p", "/etc/adguard" ]);
|
||||||
|
ensure_adguard_provider_section();
|
||||||
|
|
||||||
|
restart_tachyon_after_successful_change();
|
||||||
|
clear_version_caches();
|
||||||
|
current_version = provider_package_version(runtime_module);
|
||||||
|
if (current_version == "")
|
||||||
|
current_version = pkg.version || "unknown";
|
||||||
|
action_success(component, action, label + " has been installed", current_version, pkg.version, 1, "latest", release.release_url || "");
|
||||||
|
}
|
||||||
|
|
||||||
|
function remove_adguard() {
|
||||||
|
let component = "adguard";
|
||||||
|
let label = "AdGuard Home";
|
||||||
|
let runtime_module = adguard_runtime_module();
|
||||||
|
let current_version = provider_package_version(runtime_module);
|
||||||
|
let cursor = uci_core.cursor();
|
||||||
|
|
||||||
|
module_success([ runtime_module, "stop-runtime" ]);
|
||||||
|
|
||||||
|
if (cursor != null) {
|
||||||
|
cursor.load("tachyon");
|
||||||
|
if (cursor.get("tachyon", "adguard", "enabled") != null)
|
||||||
|
cursor.set("tachyon", "adguard", "enabled", "0");
|
||||||
|
cursor.commit("tachyon");
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_file("/usr/bin/AdGuardHome");
|
||||||
|
remove_file("/etc/init.d/adguard");
|
||||||
|
remove_file("/var/run/tachyon/adguard/adguard.pid");
|
||||||
|
|
||||||
|
clear_version_caches();
|
||||||
|
restart_tachyon_after_successful_change();
|
||||||
|
action_success(component, "remove", label + " has been removed", current_version, "", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_adguard_enabled(action) {
|
||||||
|
let enable = action == "enable";
|
||||||
|
let runtime_module = adguard_runtime_module();
|
||||||
|
let cursor = uci_core.cursor();
|
||||||
|
if (cursor == null) {
|
||||||
|
action_fail("adguard", action, "UCI is unavailable");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cursor.load("tachyon");
|
||||||
|
if (cursor.get("tachyon", "adguard", "enabled") == null) {
|
||||||
|
cursor.set("tachyon", "adguard", "provider");
|
||||||
|
cursor.set("tachyon", "adguard", "webui_enabled", "1");
|
||||||
|
cursor.set("tachyon", "adguard", "webui_port", "3000");
|
||||||
|
}
|
||||||
|
cursor.set("tachyon", "adguard", "enabled", enable ? "1" : "0");
|
||||||
|
cursor.commit("tachyon");
|
||||||
|
|
||||||
|
remove_file(SYSTEM_INFO_CACHE_FILE);
|
||||||
|
restart_tachyon_after_successful_change();
|
||||||
|
|
||||||
|
action_success(
|
||||||
|
"adguard",
|
||||||
|
action,
|
||||||
|
enable ? "AdGuard Home has been enabled" : "AdGuard Home has been disabled",
|
||||||
|
enable ? "enabled" : "disabled",
|
||||||
|
enable ? "enabled" : "disabled",
|
||||||
|
1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function download_direct_package(release) {
|
function download_direct_package(release) {
|
||||||
let package_file = tmp_dir + "/" + release.package_name;
|
let package_file = tmp_dir + "/" + release.package_name;
|
||||||
if (!download_with_retry(release.package_url, package_file, release.package_name) || !file_nonempty(package_file))
|
if (!download_with_retry(release.package_url, package_file, release.package_name) || !file_nonempty(package_file))
|
||||||
@@ -3205,6 +3369,8 @@ function normalize_component_name(component) {
|
|||||||
return "fptn";
|
return "fptn";
|
||||||
if (component == "tachyon")
|
if (component == "tachyon")
|
||||||
return "tachyon";
|
return "tachyon";
|
||||||
|
if (component == "adguardhome" || component == "adguard-home" || component == "ad_guard")
|
||||||
|
return "adguard";
|
||||||
if (component == "direct-bypass" || component == "directbypass" || component == "direct_proxy")
|
if (component == "direct-bypass" || component == "directbypass" || component == "direct_proxy")
|
||||||
return "direct_bypass";
|
return "direct_bypass";
|
||||||
if (component == "torrserver_direct" || component == "torrserver-direct")
|
if (component == "torrserver_direct" || component == "torrserver-direct")
|
||||||
@@ -3386,6 +3552,22 @@ function create_component_backup(component) {
|
|||||||
write_file(meta_file, sprintf("%J\n", meta));
|
write_file(meta_file, sprintf("%J\n", meta));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
else if (component == "adguard") {
|
||||||
|
let bin = "/usr/bin/AdGuardHome";
|
||||||
|
if (!file_exists(bin)) return true;
|
||||||
|
let st = fs.stat(bin);
|
||||||
|
let size = (st && st.size) ? st.size : 0;
|
||||||
|
if (!check_free_disk_space("/etc", size)) return false;
|
||||||
|
ensure_dir(bdir);
|
||||||
|
command_success_from_args([ "cp", "-p", bin, bdir + "/AdGuardHome" ]);
|
||||||
|
let meta = {
|
||||||
|
component: "adguard",
|
||||||
|
version: provider_package_version(adguard_runtime_module()),
|
||||||
|
timestamp: now_seconds()
|
||||||
|
};
|
||||||
|
write_file(meta_file, sprintf("%J\n", meta));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -3509,6 +3691,17 @@ function rollback_component(component) {
|
|||||||
restart_tachyon_after_successful_change();
|
restart_tachyon_after_successful_change();
|
||||||
action_success("tailscale", "rollback", "Tailscale rolled back to " + backup_version, backup_version, "", 1);
|
action_success("tailscale", "rollback", "Tailscale rolled back to " + backup_version, backup_version, "", 1);
|
||||||
}
|
}
|
||||||
|
else if (component == "adguard") {
|
||||||
|
let backup_bin = bdir + "/AdGuardHome";
|
||||||
|
if (!file_exists(backup_bin)) action_fail("adguard", "rollback", "AdGuard Home backup binary is missing");
|
||||||
|
module_success([ adguard_runtime_module(), "stop-runtime" ]);
|
||||||
|
remove_file("/usr/bin/AdGuardHome");
|
||||||
|
command_success_from_args([ "cp", "-p", backup_bin, "/usr/bin/AdGuardHome" ]);
|
||||||
|
command_success_from_args([ "chmod", "0755", "/usr/bin/AdGuardHome" ]);
|
||||||
|
clear_version_caches();
|
||||||
|
restart_tachyon_after_successful_change();
|
||||||
|
action_success("adguard", "rollback", "AdGuard Home rolled back to " + backup_version, backup_version, "", 1);
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
action_fail(component, "rollback", "Rollback not supported for component " + component);
|
action_fail(component, "rollback", "Rollback not supported for component " + component);
|
||||||
}
|
}
|
||||||
@@ -3555,6 +3748,8 @@ function list_component_releases(component, count) {
|
|||||||
owner = "Dushnilin"; repo = "openwrt-olcrtc";
|
owner = "Dushnilin"; repo = "openwrt-olcrtc";
|
||||||
} else if (component == "fptn") {
|
} else if (component == "fptn") {
|
||||||
owner = "Dushnilin"; repo = "fptn";
|
owner = "Dushnilin"; repo = "fptn";
|
||||||
|
} else if (component == "adguard") {
|
||||||
|
owner = "AdguardTeam"; repo = "AdGuardHome";
|
||||||
} else {
|
} else {
|
||||||
print("[]\n"); return;
|
print("[]\n"); return;
|
||||||
}
|
}
|
||||||
@@ -3619,6 +3814,8 @@ function install_component_version(component, tag) {
|
|||||||
install_olcrtc("install", tag);
|
install_olcrtc("install", tag);
|
||||||
} else if (component == "fptn") {
|
} else if (component == "fptn") {
|
||||||
install_fptn("install", tag);
|
install_fptn("install", tag);
|
||||||
|
} else if (component == "adguard") {
|
||||||
|
install_adguard("install", tag);
|
||||||
} else {
|
} else {
|
||||||
action_fail(component, "install_version", "Component " + component + " does not support version installation");
|
action_fail(component, "install_version", "Component " + component + " does not support version installation");
|
||||||
}
|
}
|
||||||
@@ -3730,7 +3927,8 @@ function component_action(component, action, extra) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action != "check_update" && action != "remove" && component != "direct_bypass" && component != "torrserver_direct") {
|
if (action != "check_update" && action != "remove" && component != "direct_bypass" && component != "torrserver_direct" &&
|
||||||
|
!(component == "adguard" && (action == "enable" || action == "disable"))) {
|
||||||
create_component_backup(component);
|
create_component_backup(component);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3773,6 +3971,12 @@ function component_action(component, action, extra) {
|
|||||||
install_tailscale(action);
|
install_tailscale(action);
|
||||||
else if (component == "tailscale" && action == "remove")
|
else if (component == "tailscale" && action == "remove")
|
||||||
remove_optional_component("tailscale", "tailscale", "Tailscale", LIB_DIR + "/providers/tailscale/runtime.uc");
|
remove_optional_component("tailscale", "tailscale", "Tailscale", LIB_DIR + "/providers/tailscale/runtime.uc");
|
||||||
|
else if (component == "adguard" && (action == "check_update" || action == "install"))
|
||||||
|
install_adguard(action);
|
||||||
|
else if (component == "adguard" && action == "remove")
|
||||||
|
remove_adguard();
|
||||||
|
else if (component == "adguard" && (action == "enable" || action == "disable"))
|
||||||
|
set_adguard_enabled(action);
|
||||||
else if (component == "direct_bypass" && (action == "enable" || action == "disable"))
|
else if (component == "direct_bypass" && (action == "enable" || action == "disable"))
|
||||||
set_direct_bypass(action);
|
set_direct_bypass(action);
|
||||||
else if (component == "torrserver_direct" && (action == "enable" || action == "disable"))
|
else if (component == "torrserver_direct" && (action == "enable" || action == "disable"))
|
||||||
|
|||||||
@@ -1011,6 +1011,82 @@ function byedpi_select_asset(series, asset_ext, arch_candidates) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AdGuardHome ships static tar.gz archives named
|
||||||
|
// "AdGuardHome_linux_<suffix>.tar.gz" where <suffix> is AdGuard's own arch
|
||||||
|
// naming, not OpenWrt's. Map OpenWrt arch candidates onto it.
|
||||||
|
function adguard_arch_suffix(arch) {
|
||||||
|
arch = as_string(arch);
|
||||||
|
if (arch == "x86_64" || arch == "amd64")
|
||||||
|
return "amd64";
|
||||||
|
if (arch == "x86" || arch == "i386" || arch == "i486" || arch == "i586" || arch == "i686" || arch == "386")
|
||||||
|
return "386";
|
||||||
|
if (arch == "aarch64" || arch == "arm64" || arch == "aarch64_generic" || str_startswith(arch, "aarch64_"))
|
||||||
|
return "arm64";
|
||||||
|
if (arch == "riscv64" || arch == "riscv64_generic" || str_startswith(arch, "riscv64_"))
|
||||||
|
return "riscv64";
|
||||||
|
if (arch == "s390x")
|
||||||
|
return "s390x";
|
||||||
|
if (str_startswith(arch, "arm_")) {
|
||||||
|
if (str_contains(arch, "cortex-a5") || str_contains(arch, "cortex-a7") || str_contains(arch, "cortex-a8") ||
|
||||||
|
str_contains(arch, "cortex-a9") || str_contains(arch, "cortex-a15") || str_contains(arch, "mpcore"))
|
||||||
|
return "armv7";
|
||||||
|
if (str_contains(arch, "arm1176jzf-s"))
|
||||||
|
return "armv6";
|
||||||
|
if (str_contains(arch, "arm926ej-s") || str_contains(arch, "xscale"))
|
||||||
|
return "armv5";
|
||||||
|
return "armv7";
|
||||||
|
}
|
||||||
|
if (str_startswith(arch, "mips64el") || str_startswith(arch, "mips64le"))
|
||||||
|
return "mips64le_softfloat";
|
||||||
|
if (str_startswith(arch, "mipsel") || str_startswith(arch, "mipsle"))
|
||||||
|
return "mipsle_softfloat";
|
||||||
|
if (str_startswith(arch, "mips"))
|
||||||
|
return "mips_softfloat";
|
||||||
|
if (str_startswith(arch, "powerpc") || str_startswith(arch, "ppc")) {
|
||||||
|
if (str_contains(arch, "64"))
|
||||||
|
return "ppc64le";
|
||||||
|
return "ppc";
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function adguard_select_asset(arch_candidates) {
|
||||||
|
let data = read_stdin_json();
|
||||||
|
let releases = [];
|
||||||
|
if (type(data) == "array")
|
||||||
|
releases = data;
|
||||||
|
else if (type(data) == "object")
|
||||||
|
releases = [ data ];
|
||||||
|
|
||||||
|
for (let release in releases) {
|
||||||
|
if (type(release) != "object")
|
||||||
|
continue;
|
||||||
|
if (release.draft === true)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for (let arch in split(as_string(arch_candidates), " ")) {
|
||||||
|
if (arch == "")
|
||||||
|
continue;
|
||||||
|
let suffix = adguard_arch_suffix(arch);
|
||||||
|
if (suffix == "")
|
||||||
|
continue;
|
||||||
|
|
||||||
|
let expected = "AdGuardHome_linux_" + suffix + ".tar.gz";
|
||||||
|
for (let asset in array_or_empty(release.assets)) {
|
||||||
|
if (type(asset) != "object")
|
||||||
|
continue;
|
||||||
|
let name = as_string(asset.name || "");
|
||||||
|
let url = as_string(asset.browser_download_url || "");
|
||||||
|
if (url != "" && name == expected) {
|
||||||
|
print(arch, "\t", name, "\t", url, "\t",
|
||||||
|
as_string(release.html_url || ""), "\t", as_string(release.tag_name || ""), "\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function fptn_asset_matches(name, series, arch, ext) {
|
function fptn_asset_matches(name, series, arch, ext) {
|
||||||
if (!str_startswith(name, "fptn-client-") && !str_startswith(name, "fptn-"))
|
if (!str_startswith(name, "fptn-client-") && !str_startswith(name, "fptn-"))
|
||||||
return false;
|
return false;
|
||||||
@@ -1540,6 +1616,8 @@ else if (mode == "olcrtc-select-asset")
|
|||||||
olcrtc_select_asset(ARGV[1], ARGV[2], ARGV[3]);
|
olcrtc_select_asset(ARGV[1], ARGV[2], ARGV[3]);
|
||||||
else if (mode == "fptn-select-asset")
|
else if (mode == "fptn-select-asset")
|
||||||
fptn_select_asset(ARGV[1], ARGV[2], ARGV[3]);
|
fptn_select_asset(ARGV[1], ARGV[2], ARGV[3]);
|
||||||
|
else if (mode == "adguard-select-asset")
|
||||||
|
adguard_select_asset(ARGV[1]);
|
||||||
else if (mode == "sing-box-extended-release-tag")
|
else if (mode == "sing-box-extended-release-tag")
|
||||||
sing_box_extended_release_tag();
|
sing_box_extended_release_tag();
|
||||||
else if (mode == "sing-box-lx-release-tag")
|
else if (mode == "sing-box-lx-release-tag")
|
||||||
|
|||||||
@@ -616,13 +616,16 @@ function normalize_component_name(component) {
|
|||||||
return "sing_box";
|
return "sing_box";
|
||||||
if (component == "tachyon")
|
if (component == "tachyon")
|
||||||
return "tachyon";
|
return "tachyon";
|
||||||
|
if (component == "adguardhome" || component == "adguard-home" || component == "ad_guard")
|
||||||
|
return "adguard";
|
||||||
return component;
|
return component;
|
||||||
}
|
}
|
||||||
|
|
||||||
function valid_component_name(component) {
|
function valid_component_name(component) {
|
||||||
component = normalize_component_name(component);
|
component = normalize_component_name(component);
|
||||||
return component == "tachyon" || component == "sing_box" || component == "zapret" ||
|
return component == "tachyon" || component == "sing_box" || component == "zapret" ||
|
||||||
component == "zapret2" || component == "byedpi" || component == "wdtt" || component == "olcrtc" || component == "fptn" || component == "tailscale";
|
component == "zapret2" || component == "byedpi" || component == "wdtt" || component == "olcrtc" || component == "fptn" || component == "tailscale" ||
|
||||||
|
component == "adguard";
|
||||||
}
|
}
|
||||||
|
|
||||||
function settings_component_auto_update_enabled(settings) {
|
function settings_component_auto_update_enabled(settings) {
|
||||||
@@ -2045,6 +2048,8 @@ function automatic_component_check_names() {
|
|||||||
push(result, "olcrtc");
|
push(result, "olcrtc");
|
||||||
if (module_success([ LIB_DIR + "/providers/fptn/runtime.uc", "installed" ]))
|
if (module_success([ LIB_DIR + "/providers/fptn/runtime.uc", "installed" ]))
|
||||||
push(result, "fptn");
|
push(result, "fptn");
|
||||||
|
if (module_success([ LIB_DIR + "/providers/adguard/runtime.uc", "installed" ]))
|
||||||
|
push(result, "adguard");
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -701,11 +701,15 @@ function routed_dns_enabled(section) {
|
|||||||
|
|
||||||
function routed_dns_type(section) {
|
function routed_dns_type(section) {
|
||||||
let t = option(section, "routed_dns_type", "udp");
|
let t = option(section, "routed_dns_type", "udp");
|
||||||
if (!contains(["udp", "dot", "doh", "doq"], t))
|
if (!contains(["udp", "dot", "doh", "doq", "adguard"], t))
|
||||||
t = "udp";
|
t = "udp";
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function routed_dns_adguard(section) {
|
||||||
|
return routed_dns_type(section) == "adguard";
|
||||||
|
}
|
||||||
|
|
||||||
function routed_dns_servers(section) {
|
function routed_dns_servers(section) {
|
||||||
let servers = list_option(section, "routed_dns_server");
|
let servers = list_option(section, "routed_dns_server");
|
||||||
if (length(servers) == 0) {
|
if (length(servers) == 0) {
|
||||||
|
|||||||
@@ -1427,14 +1427,16 @@ function dns_action_has_domain_matchers(section) {
|
|||||||
function validate_dns_action(section, sections, context) {
|
function validate_dns_action(section, sections, context) {
|
||||||
let name = section_name(section);
|
let name = section_name(section);
|
||||||
let dns_type = option(section, "dns_type", "udp");
|
let dns_type = option(section, "dns_type", "udp");
|
||||||
if (!contains([ "udp", "dot", "doh", "doq" ], dns_type))
|
if (!contains([ "udp", "dot", "doh", "doq", "adguard" ], dns_type))
|
||||||
fail_validation("DNS rule '" + name + "' uses unsupported protocol '" + dns_type + "'. Use udp, dot, doh, or doq. Aborted.");
|
fail_validation("DNS rule '" + name + "' uses unsupported protocol '" + dns_type + "'. Use udp, dot, doh, doq, or adguard. Aborted.");
|
||||||
let dns_servers = list_option(section, "dns_server");
|
if (dns_type != "adguard") {
|
||||||
if (length(dns_servers) == 0)
|
let dns_servers = list_option(section, "dns_server");
|
||||||
fail_validation("DNS rule '" + name + "' has no DNS server specified. Aborted.");
|
if (length(dns_servers) == 0)
|
||||||
for (let dns_server in dns_servers) {
|
fail_validation("DNS rule '" + name + "' has no DNS server specified. Aborted.");
|
||||||
if (!dns_server_value_valid(dns_server))
|
for (let dns_server in dns_servers) {
|
||||||
fail_validation("DNS rule '" + name + "' has an invalid DNS server '" + dns_server + "'. Aborted.");
|
if (!dns_server_value_valid(dns_server))
|
||||||
|
fail_validation("DNS rule '" + name + "' has an invalid DNS server '" + dns_server + "'. Aborted.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (length(connections.rule_sets_with_subnets(section)) > 0)
|
if (length(connections.rule_sets_with_subnets(section)) > 0)
|
||||||
fail_validation("DNS rule '" + name + "' can use domain-only rule sets, but subnet extraction is enabled. Disable 'Include IP addresses and subnets'. Aborted.");
|
fail_validation("DNS rule '" + name + "' can use domain-only rule sets, but subnet extraction is enabled. Disable 'Include IP addresses and subnets'. Aborted.");
|
||||||
@@ -1624,14 +1626,16 @@ function validate_rule(section, sections, context) {
|
|||||||
if (action == "dns" || action == "hosts" || action == "block")
|
if (action == "dns" || action == "hosts" || action == "block")
|
||||||
fail_validation("Rule '" + name + "' has routed DNS enabled but action '" + action + "' does not support routed DNS. Aborted.");
|
fail_validation("Rule '" + name + "' has routed DNS enabled but action '" + action + "' does not support routed DNS. Aborted.");
|
||||||
let routed_type = connections.routed_dns_type(section);
|
let routed_type = connections.routed_dns_type(section);
|
||||||
if (!contains(["udp", "dot", "doh", "doq"], routed_type))
|
if (!contains(["udp", "dot", "doh", "doq", "adguard"], routed_type))
|
||||||
fail_validation("Rule '" + name + "' has invalid routed DNS type '" + routed_type + "'. Use udp, dot, doh, or doq. Aborted.");
|
fail_validation("Rule '" + name + "' has invalid routed DNS type '" + routed_type + "'. Use udp, dot, doh, doq, or adguard. Aborted.");
|
||||||
let routed_servers = connections.routed_dns_servers(section);
|
if (routed_type != "adguard") {
|
||||||
if (length(routed_servers) == 0)
|
let routed_servers = connections.routed_dns_servers(section);
|
||||||
fail_validation("Rule '" + name + "' has routed DNS enabled but no DNS server specified. Aborted.");
|
if (length(routed_servers) == 0)
|
||||||
for (let srv in routed_servers)
|
fail_validation("Rule '" + name + "' has routed DNS enabled but no DNS server specified. Aborted.");
|
||||||
if (!dns_server_value_valid(srv))
|
for (let srv in routed_servers)
|
||||||
fail_validation("Rule '" + name + "' has an invalid routed DNS server '" + srv + "'. Aborted.");
|
if (!dns_server_value_valid(srv))
|
||||||
|
fail_validation("Rule '" + name + "' has an invalid routed DNS server '" + srv + "'. Aborted.");
|
||||||
|
}
|
||||||
if (!connections.has_dns_matchers(section) && length(list_option(section, "fully_routed_ips")) == 0)
|
if (!connections.has_dns_matchers(section) && length(list_option(section, "fully_routed_ips")) == 0)
|
||||||
fail_validation("Rule '" + name + "' has routed DNS enabled but no domain conditions or forced devices. Routed DNS requires at least one domain matcher. Aborted.");
|
fail_validation("Rule '" + name + "' has routed DNS enabled but no domain conditions or forced devices. Routed DNS requires at least one domain matcher. Aborted.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ const WDTT_RUNTIME_UC = LIB_DIR + "/providers/wdtt/runtime.uc";
|
|||||||
const OLCRTC_RUNTIME_UC = LIB_DIR + "/providers/olcrtc/runtime.uc";
|
const OLCRTC_RUNTIME_UC = LIB_DIR + "/providers/olcrtc/runtime.uc";
|
||||||
const FPTN_RUNTIME_UC = LIB_DIR + "/providers/fptn/runtime.uc";
|
const FPTN_RUNTIME_UC = LIB_DIR + "/providers/fptn/runtime.uc";
|
||||||
const TAILSCALE_RUNTIME_UC = LIB_DIR + "/providers/tailscale/runtime.uc";
|
const TAILSCALE_RUNTIME_UC = LIB_DIR + "/providers/tailscale/runtime.uc";
|
||||||
|
const ADGUARD_RUNTIME_UC = LIB_DIR + "/providers/adguard/runtime.uc";
|
||||||
const ZAPRET_VALIDATOR_UC = LIB_DIR + "/providers/zapret/validator.uc";
|
const ZAPRET_VALIDATOR_UC = LIB_DIR + "/providers/zapret/validator.uc";
|
||||||
const ZAPRET2_VALIDATOR_UC = LIB_DIR + "/providers/zapret2/validator.uc";
|
const ZAPRET2_VALIDATOR_UC = LIB_DIR + "/providers/zapret2/validator.uc";
|
||||||
const BYEDPI_VALIDATOR_UC = LIB_DIR + "/providers/byedpi/validator.uc";
|
const BYEDPI_VALIDATOR_UC = LIB_DIR + "/providers/byedpi/validator.uc";
|
||||||
@@ -1227,6 +1228,9 @@ function build_system_info() {
|
|||||||
let fptn_installed = provider_installed(FPTN_RUNTIME_UC) ? 1 : 0;
|
let fptn_installed = provider_installed(FPTN_RUNTIME_UC) ? 1 : 0;
|
||||||
let fptn_supported = (fptn_installed == 1 || is_fptn_supported()) ? 1 : 0;
|
let fptn_supported = (fptn_installed == 1 || is_fptn_supported()) ? 1 : 0;
|
||||||
let fptn_version = fptn_installed ? provider_version(FPTN_RUNTIME_UC) : "not installed";
|
let fptn_version = fptn_installed ? provider_version(FPTN_RUNTIME_UC) : "not installed";
|
||||||
|
let adguard_installed = provider_installed(ADGUARD_RUNTIME_UC) ? 1 : 0;
|
||||||
|
let adguard_version = adguard_installed ? provider_version(ADGUARD_RUNTIME_UC) : "not installed";
|
||||||
|
let adguard_enabled = module_success(ADGUARD_RUNTIME_UC, [ "provider-enabled" ]) ? 1 : 0;
|
||||||
let device_model = first_line_value("/tmp/sysinfo/model", "unknown");
|
let device_model = first_line_value("/tmp/sysinfo/model", "unknown");
|
||||||
|
|
||||||
let direct_bypass_enabled = bool_option(settings(), "direct_bypass_enabled", false) ? 1 : 0;
|
let direct_bypass_enabled = bool_option(settings(), "direct_bypass_enabled", false) ? 1 : 0;
|
||||||
@@ -1245,6 +1249,7 @@ function build_system_info() {
|
|||||||
let show_component_olcrtc = bool_option(settings(), "show_component_olcrtc", true) ? 1 : 0;
|
let show_component_olcrtc = bool_option(settings(), "show_component_olcrtc", true) ? 1 : 0;
|
||||||
let show_component_fptn = bool_option(settings(), "show_component_fptn", true) ? 1 : 0;
|
let show_component_fptn = bool_option(settings(), "show_component_fptn", true) ? 1 : 0;
|
||||||
let show_component_tailscale = bool_option(settings(), "show_component_tailscale", true) ? 1 : 0;
|
let show_component_tailscale = bool_option(settings(), "show_component_tailscale", true) ? 1 : 0;
|
||||||
|
let show_component_adguard = bool_option(settings(), "show_component_adguard", true) ? 1 : 0;
|
||||||
let show_component_direct_bypass = bool_option(settings(), "show_component_direct_bypass", true) ? 1 : 0;
|
let show_component_direct_bypass = bool_option(settings(), "show_component_direct_bypass", true) ? 1 : 0;
|
||||||
let show_component_torrserver_direct = bool_option(settings(), "show_component_torrserver_direct", true) ? 1 : 0;
|
let show_component_torrserver_direct = bool_option(settings(), "show_component_torrserver_direct", true) ? 1 : 0;
|
||||||
|
|
||||||
@@ -1269,6 +1274,7 @@ function build_system_info() {
|
|||||||
let olcrtc_meta = read_backup_meta("olcrtc");
|
let olcrtc_meta = read_backup_meta("olcrtc");
|
||||||
let tailscale_meta = read_backup_meta("tailscale");
|
let tailscale_meta = read_backup_meta("tailscale");
|
||||||
let fptn_meta = read_backup_meta("fptn");
|
let fptn_meta = read_backup_meta("fptn");
|
||||||
|
let adguard_meta = read_backup_meta("adguard");
|
||||||
|
|
||||||
return {
|
return {
|
||||||
tachyon_version: TACHYON_VERSION,
|
tachyon_version: TACHYON_VERSION,
|
||||||
@@ -1313,6 +1319,11 @@ function build_system_info() {
|
|||||||
fptn_supported,
|
fptn_supported,
|
||||||
fptn_backup_version: fptn_meta ? as_string(fptn_meta.version) : "",
|
fptn_backup_version: fptn_meta ? as_string(fptn_meta.version) : "",
|
||||||
fptn_backup_time: fptn_meta ? int(fptn_meta.timestamp || 0) : 0,
|
fptn_backup_time: fptn_meta ? int(fptn_meta.timestamp || 0) : 0,
|
||||||
|
adguard_version,
|
||||||
|
adguard_installed,
|
||||||
|
adguard_enabled,
|
||||||
|
adguard_backup_version: adguard_meta ? as_string(adguard_meta.version) : "",
|
||||||
|
adguard_backup_time: adguard_meta ? int(adguard_meta.timestamp || 0) : 0,
|
||||||
direct_bypass_enabled,
|
direct_bypass_enabled,
|
||||||
direct_bypass_address,
|
direct_bypass_address,
|
||||||
direct_bypass_port,
|
direct_bypass_port,
|
||||||
@@ -1327,6 +1338,7 @@ function build_system_info() {
|
|||||||
show_component_olcrtc,
|
show_component_olcrtc,
|
||||||
show_component_fptn,
|
show_component_fptn,
|
||||||
show_component_tailscale,
|
show_component_tailscale,
|
||||||
|
show_component_adguard,
|
||||||
show_component_direct_bypass,
|
show_component_direct_bypass,
|
||||||
show_component_torrserver_direct,
|
show_component_torrserver_direct,
|
||||||
openwrt_version: openwrt_release(),
|
openwrt_version: openwrt_release(),
|
||||||
@@ -5423,6 +5435,8 @@ else if (mode == "check-zapret2-runtime")
|
|||||||
exit(module_passthrough(ZAPRET2_RUNTIME_UC, [ "check" ]));
|
exit(module_passthrough(ZAPRET2_RUNTIME_UC, [ "check" ]));
|
||||||
else if (mode == "check-byedpi-runtime")
|
else if (mode == "check-byedpi-runtime")
|
||||||
exit(module_passthrough(BYEDPI_RUNTIME_UC, [ "check" ]));
|
exit(module_passthrough(BYEDPI_RUNTIME_UC, [ "check" ]));
|
||||||
|
else if (mode == "check-adguard-runtime")
|
||||||
|
exit(module_passthrough(ADGUARD_RUNTIME_UC, [ "check" ]));
|
||||||
else if (mode == "check-tor-runtime") {
|
else if (mode == "check-tor-runtime") {
|
||||||
print(sprintf("%J", { success: true, data: { tor_installed: 0 } }));
|
print(sprintf("%J", { success: true, data: { tor_installed: 0 } }));
|
||||||
exit(0);
|
exit(0);
|
||||||
@@ -5461,6 +5475,8 @@ else if (mode == "get-fptn-status")
|
|||||||
exit(module_passthrough(FPTN_RUNTIME_UC, [ "status" ]));
|
exit(module_passthrough(FPTN_RUNTIME_UC, [ "status" ]));
|
||||||
else if (mode == "get-tailscale-status")
|
else if (mode == "get-tailscale-status")
|
||||||
exit(module_passthrough(TAILSCALE_RUNTIME_UC, [ "status" ]));
|
exit(module_passthrough(TAILSCALE_RUNTIME_UC, [ "status" ]));
|
||||||
|
else if (mode == "get-adguard-status")
|
||||||
|
exit(module_passthrough(ADGUARD_RUNTIME_UC, [ "status" ]));
|
||||||
else if (mode == "get-tailscale-peers")
|
else if (mode == "get-tailscale-peers")
|
||||||
exit(module_passthrough(TAILSCALE_RUNTIME_UC, [ "peers" ]));
|
exit(module_passthrough(TAILSCALE_RUNTIME_UC, [ "peers" ]));
|
||||||
else if (mode == "get-system-info")
|
else if (mode == "get-system-info")
|
||||||
|
|||||||
@@ -1114,7 +1114,7 @@ function write_server_capabilities_json(sing_box_extended, sing_box_tiny, sing_b
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function write_ui_capabilities_json(sing_box_extended, sing_box_tiny, sing_box_compressed, sing_box_lx, sing_box_tailscale, zapret_installed, zapret2_installed, byedpi_installed, server_inbounds_enabled_count) {
|
function write_ui_capabilities_json(sing_box_extended, sing_box_tiny, sing_box_compressed, sing_box_lx, sing_box_tailscale, zapret_installed, zapret2_installed, byedpi_installed, server_inbounds_enabled_count, adguard_installed) {
|
||||||
write_json({
|
write_json({
|
||||||
sing_box_extended: arg_number(sing_box_extended),
|
sing_box_extended: arg_number(sing_box_extended),
|
||||||
sing_box_tiny: arg_number(sing_box_tiny),
|
sing_box_tiny: arg_number(sing_box_tiny),
|
||||||
@@ -1124,6 +1124,7 @@ function write_ui_capabilities_json(sing_box_extended, sing_box_tiny, sing_box_c
|
|||||||
zapret_installed: arg_number(zapret_installed),
|
zapret_installed: arg_number(zapret_installed),
|
||||||
zapret2_installed: arg_number(zapret2_installed),
|
zapret2_installed: arg_number(zapret2_installed),
|
||||||
byedpi_installed: arg_number(byedpi_installed),
|
byedpi_installed: arg_number(byedpi_installed),
|
||||||
|
adguard_installed: arg_number(adguard_installed),
|
||||||
server_inbounds_enabled_count: arg_number(server_inbounds_enabled_count)
|
server_inbounds_enabled_count: arg_number(server_inbounds_enabled_count)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1377,6 +1378,8 @@ function render_global_system_info() {
|
|||||||
print_line("\ud83e\uddf5 Zapret2: " + zapret2_version);
|
print_line("\ud83e\uddf5 Zapret2: " + zapret2_version);
|
||||||
if (flag_is_one(value.byedpi_installed))
|
if (flag_is_one(value.byedpi_installed))
|
||||||
print_line("\ud83e\uddf5 ByeDPI: " + byedpi_version);
|
print_line("\ud83e\uddf5 ByeDPI: " + byedpi_version);
|
||||||
|
if (flag_is_one(value.adguard_installed))
|
||||||
|
print_line("\ud83e\uddf5 AdGuard Home: " + as_string(object_value(value, "adguard_version") || "unknown"));
|
||||||
print_line("\ud83d\udedc OpenWrt: " + openwrt_version);
|
print_line("\ud83d\udedc OpenWrt: " + openwrt_version);
|
||||||
print_line("\ud83d\udedc Device: " + device_model);
|
print_line("\ud83d\udedc Device: " + device_model);
|
||||||
}
|
}
|
||||||
@@ -1931,7 +1934,7 @@ else if (mode == "inbounds-check-json")
|
|||||||
else if (mode == "server-capabilities-json")
|
else if (mode == "server-capabilities-json")
|
||||||
write_server_capabilities_json(ARGV[1], ARGV[2], ARGV[3]);
|
write_server_capabilities_json(ARGV[1], ARGV[2], ARGV[3]);
|
||||||
else if (mode == "ui-capabilities-json")
|
else if (mode == "ui-capabilities-json")
|
||||||
write_ui_capabilities_json(ARGV[1], ARGV[2], ARGV[3], ARGV[4], ARGV[5], ARGV[6], ARGV[7], ARGV[8], ARGV[9]);
|
write_ui_capabilities_json(ARGV[1], ARGV[2], ARGV[3], ARGV[4], ARGV[5], ARGV[6], ARGV[7], ARGV[8], ARGV[9], ARGV[10]);
|
||||||
else if (mode == "service-status-json")
|
else if (mode == "service-status-json")
|
||||||
write_service_status_json(ARGV[1], ARGV[2], ARGV[3], ARGV[4]);
|
write_service_status_json(ARGV[1], ARGV[2], ARGV[3], ARGV[4]);
|
||||||
else if (mode == "service-status-running")
|
else if (mode == "service-status-running")
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ let uci = require("core.uci");
|
|||||||
|
|
||||||
const CONFIG_NAME = getenv("TACHYON_CONFIG_NAME") || "tachyon";
|
const CONFIG_NAME = getenv("TACHYON_CONFIG_NAME") || "tachyon";
|
||||||
const SB_DNS_INBOUND_ADDRESS = getenv("SB_DNS_INBOUND_ADDRESS") || "127.0.0.42";
|
const SB_DNS_INBOUND_ADDRESS = getenv("SB_DNS_INBOUND_ADDRESS") || "127.0.0.42";
|
||||||
|
const ADGUARD_LOCAL_SERVER = getenv("ADGUARD_LOCAL_SERVER") || "127.0.0.1#5354";
|
||||||
let common = require("core.common");
|
let common = require("core.common");
|
||||||
let as_string = common.as_string;
|
let as_string = common.as_string;
|
||||||
let shell_quote = common.shell_quote;
|
let shell_quote = common.shell_quote;
|
||||||
@@ -101,7 +102,31 @@ function dnsmasq_default_servers() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function dnsmasq_default_has_tachyon_dns() {
|
function dnsmasq_default_has_tachyon_dns() {
|
||||||
return list_has(dnsmasq_default_servers(), SB_DNS_INBOUND_ADDRESS);
|
return list_has(dnsmasq_default_servers(), SB_DNS_INBOUND_ADDRESS) ||
|
||||||
|
list_has(dnsmasq_default_servers(), ADGUARD_LOCAL_SERVER);
|
||||||
|
}
|
||||||
|
|
||||||
|
function adguard_provider_enabled() {
|
||||||
|
return truthy(uci_get(CONFIG_NAME + ".adguard.enabled"));
|
||||||
|
}
|
||||||
|
|
||||||
|
function adguard_main_dns_mode() {
|
||||||
|
if (!adguard_provider_enabled())
|
||||||
|
return "dnsmasq";
|
||||||
|
return uci_get(CONFIG_NAME + ".settings.dns_main_mode") == "adguard" ? "adguard" : "dnsmasq";
|
||||||
|
}
|
||||||
|
|
||||||
|
function tachyon_dns_server_target() {
|
||||||
|
if (adguard_provider_enabled() && adguard_main_dns_mode() == "dnsmasq")
|
||||||
|
return ADGUARD_LOCAL_SERVER;
|
||||||
|
return SB_DNS_INBOUND_ADDRESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
function is_tachyon_dns_server(server) {
|
||||||
|
server = as_string(server);
|
||||||
|
return server == SB_DNS_INBOUND_ADDRESS ||
|
||||||
|
starts_with(server, SB_DNS_INBOUND_ADDRESS + "#") ||
|
||||||
|
server == ADGUARD_LOCAL_SERVER;
|
||||||
}
|
}
|
||||||
|
|
||||||
function dnsmasq_has_tachyon_dns() {
|
function dnsmasq_has_tachyon_dns() {
|
||||||
@@ -129,9 +154,14 @@ function dnsmasq_target_cachesize() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function dnsmasq_default_config_is_complete() {
|
function dnsmasq_default_config_is_complete() {
|
||||||
|
let adguard_mode = adguard_provider_enabled() && adguard_main_dns_mode() == "adguard";
|
||||||
|
let port_ok = adguard_mode
|
||||||
|
? uci_get("dhcp.@dnsmasq[0].port") == "0"
|
||||||
|
: uci_get("dhcp.@dnsmasq[0].port") != "0";
|
||||||
return dnsmasq_default_has_tachyon_dns() &&
|
return dnsmasq_default_has_tachyon_dns() &&
|
||||||
uci_get("dhcp.@dnsmasq[0].noresolv") == "1" &&
|
uci_get("dhcp.@dnsmasq[0].noresolv") == "1" &&
|
||||||
uci_get("dhcp.@dnsmasq[0].cachesize") == dnsmasq_target_cachesize() &&
|
uci_get("dhcp.@dnsmasq[0].cachesize") == dnsmasq_target_cachesize() &&
|
||||||
|
port_ok &&
|
||||||
!dnsmasq_legacy_instance_exists();
|
!dnsmasq_legacy_instance_exists();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,7 +190,7 @@ function backup_dnsmasq_server_list() {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
for (let server in words(dnsmasq_default_servers())) {
|
for (let server in words(dnsmasq_default_servers())) {
|
||||||
if (server != SB_DNS_INBOUND_ADDRESS && !starts_with(server, SB_DNS_INBOUND_ADDRESS + "#"))
|
if (!is_tachyon_dns_server(server))
|
||||||
uci_add_list("dhcp.@dnsmasq[0].tachyon_server", server);
|
uci_add_list("dhcp.@dnsmasq[0].tachyon_server", server);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -210,20 +240,32 @@ function dnsmasq_configure_default_instance() {
|
|||||||
backup_dnsmasq_config_option("noresolv", "tachyon_noresolv");
|
backup_dnsmasq_config_option("noresolv", "tachyon_noresolv");
|
||||||
backup_dnsmasq_config_option("cachesize", "tachyon_cachesize");
|
backup_dnsmasq_config_option("cachesize", "tachyon_cachesize");
|
||||||
}
|
}
|
||||||
|
backup_dnsmasq_config_option("port", "tachyon_port");
|
||||||
|
|
||||||
backup_dnsmasq_config_option("addn_hosts", "tachyon_addn_hosts");
|
backup_dnsmasq_config_option("addn_hosts", "tachyon_addn_hosts");
|
||||||
uci_delete("dhcp.@dnsmasq[0].addn_hosts");
|
uci_delete("dhcp.@dnsmasq[0].addn_hosts");
|
||||||
|
|
||||||
|
if (adguard_provider_enabled() && adguard_main_dns_mode() == "adguard") {
|
||||||
|
// AdGuard Home serves DNS on :53; dnsmasq keeps DHCP only.
|
||||||
|
uci_delete("dhcp.@dnsmasq[0].server");
|
||||||
|
uci_set("dhcp.@dnsmasq[0].port", "0");
|
||||||
|
uci_set("dhcp.@dnsmasq[0].noresolv", "1");
|
||||||
|
uci_set("dhcp.@dnsmasq[0].cachesize", "0");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
uci_delete("dhcp.@dnsmasq[0].server");
|
uci_delete("dhcp.@dnsmasq[0].server");
|
||||||
uci_add_list("dhcp.@dnsmasq[0].server", SB_DNS_INBOUND_ADDRESS);
|
uci_add_list("dhcp.@dnsmasq[0].server", tachyon_dns_server_target());
|
||||||
uci_set("dhcp.@dnsmasq[0].noresolv", "1");
|
uci_set("dhcp.@dnsmasq[0].noresolv", "1");
|
||||||
uci_set("dhcp.@dnsmasq[0].cachesize", dnsmasq_target_cachesize());
|
uci_set("dhcp.@dnsmasq[0].cachesize", dnsmasq_target_cachesize());
|
||||||
|
if (uci_get("dhcp.@dnsmasq[0].port") == "0")
|
||||||
|
uci_delete("dhcp.@dnsmasq[0].port");
|
||||||
}
|
}
|
||||||
|
|
||||||
function dnsmasq_restore_default_instance() {
|
function dnsmasq_restore_default_instance() {
|
||||||
let server_list = dnsmasq_default_servers();
|
let server_list = dnsmasq_default_servers();
|
||||||
let backup_servers = uci_get("dhcp.@dnsmasq[0].tachyon_server");
|
let backup_servers = uci_get("dhcp.@dnsmasq[0].tachyon_server");
|
||||||
let managed_global_dns = list_has(server_list, SB_DNS_INBOUND_ADDRESS);
|
let managed_global_dns = list_has(server_list, SB_DNS_INBOUND_ADDRESS) || list_has(server_list, ADGUARD_LOCAL_SERVER);
|
||||||
|
|
||||||
uci_delete("dhcp.@dnsmasq[0].server");
|
uci_delete("dhcp.@dnsmasq[0].server");
|
||||||
if (backup_servers != "") {
|
if (backup_servers != "") {
|
||||||
@@ -233,12 +275,18 @@ function dnsmasq_restore_default_instance() {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
for (let value in words(server_list)) {
|
for (let value in words(server_list)) {
|
||||||
if (value != SB_DNS_INBOUND_ADDRESS && !starts_with(value, SB_DNS_INBOUND_ADDRESS + "#"))
|
if (!is_tachyon_dns_server(value))
|
||||||
uci_add_list("dhcp.@dnsmasq[0].server", value);
|
uci_add_list("dhcp.@dnsmasq[0].server", value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
uci_delete("dhcp.@dnsmasq[0].tachyon_server");
|
uci_delete("dhcp.@dnsmasq[0].tachyon_server");
|
||||||
|
|
||||||
|
let port = uci_get("dhcp.@dnsmasq[0].tachyon_port");
|
||||||
|
if (port != "")
|
||||||
|
restore_dnsmasq_config_option("port", "tachyon_port", "53");
|
||||||
|
else if (uci_get("dhcp.@dnsmasq[0].port") == "0")
|
||||||
|
uci_delete("dhcp.@dnsmasq[0].port");
|
||||||
|
|
||||||
let noresolv = uci_get("dhcp.@dnsmasq[0].tachyon_noresolv");
|
let noresolv = uci_get("dhcp.@dnsmasq[0].tachyon_noresolv");
|
||||||
if (noresolv != "")
|
if (noresolv != "")
|
||||||
restore_dnsmasq_config_option("noresolv", "tachyon_noresolv", "");
|
restore_dnsmasq_config_option("noresolv", "tachyon_noresolv", "");
|
||||||
@@ -302,7 +350,7 @@ function dnsmasq_configure(force) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uci_get("dhcp.@dnsmasq[0].port") == "0" && (run("pidof AdGuardHome >/dev/null 2>&1") || run("pidof adguardhome >/dev/null 2>&1"))) {
|
if (!adguard_provider_enabled() && uci_get("dhcp.@dnsmasq[0].port") == "0" && (run("pidof AdGuardHome >/dev/null 2>&1") || run("pidof adguardhome >/dev/null 2>&1"))) {
|
||||||
log("dnsmasq configuration skipped: AdGuardHome is active and dnsmasq DNS is disabled (port 0)", "info");
|
log("dnsmasq configuration skipped: AdGuardHome is active and dnsmasq DNS is disabled (port 0)", "info");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,454 @@
|
|||||||
|
#!/usr/bin/env ucode
|
||||||
|
|
||||||
|
let fs = require("fs");
|
||||||
|
let common = require("core.common");
|
||||||
|
let uci_core = require("core.uci");
|
||||||
|
|
||||||
|
let as_string = common.as_string;
|
||||||
|
let bool_value = common.bool_value;
|
||||||
|
let write_json = common.write_json;
|
||||||
|
let shell_quote = common.shell_quote;
|
||||||
|
let command_from_args = common.command_from_args;
|
||||||
|
let command_output = common.command_output;
|
||||||
|
let command_output_from_args = common.command_output_from_args;
|
||||||
|
let command_status = common.command_status;
|
||||||
|
let command_success = common.command_success;
|
||||||
|
let command_success_from_args = common.command_success_from_args;
|
||||||
|
let object_or_empty = common.object_or_empty;
|
||||||
|
let array_or_empty = common.array_or_empty;
|
||||||
|
let option = common.option;
|
||||||
|
let bool_option = common.bool_option;
|
||||||
|
let list_option = common.list_option;
|
||||||
|
|
||||||
|
const CONFIG_NAME = getenv("TACHYON_CONFIG_NAME") || "tachyon";
|
||||||
|
const LIB_DIR = getenv("TACHYON_LIB") || "/usr/lib/tachyon";
|
||||||
|
const ADGUARD_BIN = getenv("ADGUARD_BIN") || "/usr/bin/AdGuardHome";
|
||||||
|
const ADGUARD_WORK_DIR = getenv("ADGUARD_WORK_DIR") || "/etc/adguard";
|
||||||
|
const ADGUARD_CONFIG = getenv("ADGUARD_CONFIG") || ADGUARD_WORK_DIR + "/AdGuardHome.yaml";
|
||||||
|
const ADGUARD_SERVICE_INIT = getenv("ADGUARD_SERVICE_INIT") || "/etc/init.d/adguard";
|
||||||
|
const ADGUARD_STATE_DIR = getenv("ADGUARD_STATE_DIR") || "/var/run/tachyon/adguard";
|
||||||
|
const ADGUARD_PID_FILE = getenv("ADGUARD_PID_FILE") || ADGUARD_STATE_DIR + "/adguard.pid";
|
||||||
|
const ADGUARD_DEFAULT_WEBUI_PORT = getenv("ADGUARD_DEFAULT_WEBUI_PORT") || "3000";
|
||||||
|
const ADGUARD_LOCAL_PORT = getenv("ADGUARD_LOCAL_PORT") || "5354";
|
||||||
|
const SB_DNS_INBOUND_ADDRESS = getenv("SB_DNS_INBOUND_ADDRESS") || "127.0.0.42";
|
||||||
|
const SB_DNS_INBOUND_PORT = getenv("SB_DNS_INBOUND_PORT") || "53";
|
||||||
|
const ADGUARD_DEFAULT_FILTER = getenv("ADGUARD_DEFAULT_FILTER") || "https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt";
|
||||||
|
|
||||||
|
function log_message(message, level) {
|
||||||
|
common.log_message(message, level);
|
||||||
|
}
|
||||||
|
|
||||||
|
function command_exists(name) {
|
||||||
|
return command_success_from_args([ "command", "-v", name ]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function uci_sections(type_name) {
|
||||||
|
return uci_core.section_objects(CONFIG_NAME, as_string(type_name));
|
||||||
|
}
|
||||||
|
|
||||||
|
function uci_settings() {
|
||||||
|
return object_or_empty(uci_core.get_all(CONFIG_NAME, "settings"));
|
||||||
|
}
|
||||||
|
|
||||||
|
function provider_section() {
|
||||||
|
for (let section in uci_sections("provider")) {
|
||||||
|
if (as_string(section[".name"]) == "adguard")
|
||||||
|
return section;
|
||||||
|
}
|
||||||
|
// Legacy/alternate: a dedicated top-level section named adguard.
|
||||||
|
let direct = uci_core.get_all(CONFIG_NAME, "adguard");
|
||||||
|
if (type(direct) == "object" && length(keys(direct)) > 0)
|
||||||
|
return direct;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
function provider_enabled() {
|
||||||
|
return bool_option(provider_section(), "enabled", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function dns_main_mode() {
|
||||||
|
let mode = option(uci_settings(), "dns_main_mode", "dnsmasq");
|
||||||
|
return mode == "adguard" ? "adguard" : "dnsmasq";
|
||||||
|
}
|
||||||
|
|
||||||
|
function failover_enabled() {
|
||||||
|
return bool_option(uci_settings(), "dns_adguard_failover_enabled", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function webui_enabled() {
|
||||||
|
return bool_option(provider_section(), "webui_enabled", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function webui_port() {
|
||||||
|
let value = option(provider_section(), "webui_port", ADGUARD_DEFAULT_WEBUI_PORT);
|
||||||
|
return match(value, /^[0-9]+$/) != null && int(value) >= 1 && int(value) <= 65535
|
||||||
|
? int(value)
|
||||||
|
: int(ADGUARD_DEFAULT_WEBUI_PORT);
|
||||||
|
}
|
||||||
|
|
||||||
|
function webui_username() {
|
||||||
|
return trim(as_string(option(provider_section(), "webui_username", "")));
|
||||||
|
}
|
||||||
|
|
||||||
|
function webui_password() {
|
||||||
|
return as_string(option(provider_section(), "webui_password", ""));
|
||||||
|
}
|
||||||
|
|
||||||
|
function upstream_dns_list() {
|
||||||
|
let result = [];
|
||||||
|
for (let value in list_option(provider_section(), "upstream_dns")) {
|
||||||
|
value = trim(as_string(value));
|
||||||
|
if (value != "")
|
||||||
|
push(result, value);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function bootstrap_dns_list() {
|
||||||
|
let result = [];
|
||||||
|
for (let value in list_option(provider_section(), "bootstrap_dns")) {
|
||||||
|
value = trim(as_string(value));
|
||||||
|
if (value != "")
|
||||||
|
push(result, value);
|
||||||
|
}
|
||||||
|
if (length(result) == 0) {
|
||||||
|
push(result, "1.1.1.1");
|
||||||
|
push(result, "8.8.8.8");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function filter_list_urls() {
|
||||||
|
let result = [];
|
||||||
|
for (let value in list_option(provider_section(), "filter_lists")) {
|
||||||
|
value = trim(as_string(value));
|
||||||
|
if (value != "")
|
||||||
|
push(result, value);
|
||||||
|
}
|
||||||
|
if (length(result) == 0)
|
||||||
|
push(result, ADGUARD_DEFAULT_FILTER);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function lan_ipv4() {
|
||||||
|
let addr = uci_core.get("network.lan.ipaddr");
|
||||||
|
if (addr != "" && match(addr, /^[0-9.]+$/) != null)
|
||||||
|
return as_string(addr);
|
||||||
|
// Fallback to the auto-detected Tachyon service listen address.
|
||||||
|
if (fs.stat(LIB_DIR + "/singbox/runtime.uc") != null) {
|
||||||
|
let detected = trim(command_output_from_args([
|
||||||
|
"ucode", "-L", LIB_DIR, LIB_DIR + "/singbox/runtime.uc", "service-listen-address"
|
||||||
|
]));
|
||||||
|
if (detected != "" && match(detected, /^[0-9.]+$/) != null)
|
||||||
|
return detected;
|
||||||
|
}
|
||||||
|
return "0.0.0.0";
|
||||||
|
}
|
||||||
|
|
||||||
|
function dns_listen_host() {
|
||||||
|
return dns_main_mode() == "adguard" ? lan_ipv4() : "127.0.0.1";
|
||||||
|
}
|
||||||
|
|
||||||
|
function dns_listen_port() {
|
||||||
|
return dns_main_mode() == "adguard" ? 53 : int(ADGUARD_LOCAL_PORT);
|
||||||
|
}
|
||||||
|
|
||||||
|
function provider_available() {
|
||||||
|
let stat = fs.stat(ADGUARD_BIN);
|
||||||
|
return stat != null && stat.mode != null && (int(stat.mode) & 73) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function first_nonempty_field(value) {
|
||||||
|
for (let line in split(as_string(value), "\n")) {
|
||||||
|
line = trim(as_string(line));
|
||||||
|
if (line == "")
|
||||||
|
continue;
|
||||||
|
let fields = split(line, /[ \t\r\n]+/);
|
||||||
|
if (length(fields) > 0)
|
||||||
|
return as_string(fields[length(fields) - 1]);
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function package_version() {
|
||||||
|
if (!provider_available())
|
||||||
|
return "";
|
||||||
|
let output = command_output_from_args([ ADGUARD_BIN, "--version" ]);
|
||||||
|
let version = first_nonempty_field(output);
|
||||||
|
return replace(as_string(version), /^v/, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
function runtime_pid_running(pid) {
|
||||||
|
pid = as_string(pid);
|
||||||
|
return match(pid, /^[0-9]+$/) != null && command_success_from_args([ "kill", "-0", pid ]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function running_pid() {
|
||||||
|
let pid = trim(as_string(fs.readfile(ADGUARD_PID_FILE) || ""));
|
||||||
|
if (runtime_pid_running(pid))
|
||||||
|
return pid;
|
||||||
|
let output = trim(command_output_from_args([ "pgrep", "-f", "AdGuardHome" ]));
|
||||||
|
for (let line in split(output, "\n")) {
|
||||||
|
line = trim(as_string(line));
|
||||||
|
if (line != "")
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function running() {
|
||||||
|
return running_pid() != "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function service_enabled() {
|
||||||
|
return fs.stat(ADGUARD_SERVICE_INIT) != null && command_success_from_args([ ADGUARD_SERVICE_INIT, "enabled" ]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function service_running() {
|
||||||
|
return fs.stat(ADGUARD_SERVICE_INIT) != null && command_success_from_args([ ADGUARD_SERVICE_INIT, "status" ]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function yaml_escape(value) {
|
||||||
|
value = replace(as_string(value), /\\/g, "\\\\");
|
||||||
|
value = replace(value, /"/g, "\\\"");
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function yaml_list(lines, values) {
|
||||||
|
for (let value in values)
|
||||||
|
push(lines, " - \"" + yaml_escape(value) + "\"");
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
function build_config_yaml() {
|
||||||
|
let lines = [];
|
||||||
|
let upstreams = [];
|
||||||
|
// Primary upstream: sing-box DNS inbound, so FakeIP and per-domain routing keep working.
|
||||||
|
push(upstreams, SB_DNS_INBOUND_ADDRESS + ":" + SB_DNS_INBOUND_PORT);
|
||||||
|
|
||||||
|
let fallback = [];
|
||||||
|
if (failover_enabled())
|
||||||
|
fallback = upstream_dns_list();
|
||||||
|
|
||||||
|
push(lines, "bind_host: 0.0.0.0");
|
||||||
|
push(lines, "bind_port: " + as_string(webui_port()));
|
||||||
|
push(lines, "http_proxy: \"\"");
|
||||||
|
push(lines, "language: \"\"");
|
||||||
|
push(lines, "dns:");
|
||||||
|
push(lines, " bind_hosts:");
|
||||||
|
push(lines, " - " + dns_listen_host());
|
||||||
|
push(lines, " port: " + as_string(dns_listen_port()));
|
||||||
|
push(lines, " upstream_dns:");
|
||||||
|
yaml_list(lines, upstreams);
|
||||||
|
push(lines, " fallback_dns:");
|
||||||
|
yaml_list(lines, fallback);
|
||||||
|
push(lines, " bootstrap_dns:");
|
||||||
|
yaml_list(lines, bootstrap_dns_list());
|
||||||
|
push(lines, " upstream_dns_file: \"\"");
|
||||||
|
push(lines, " cache_size: 4194304");
|
||||||
|
push(lines, " cache_ttl_min: 0");
|
||||||
|
push(lines, " cache_ttl_max: 0");
|
||||||
|
push(lines, " cache_optimistic: false");
|
||||||
|
push(lines, " aaaa_disabled: false");
|
||||||
|
push(lines, " enable_dnssec: false");
|
||||||
|
push(lines, " max_goroutines: 300");
|
||||||
|
push(lines, " upstream_timeout: 10s");
|
||||||
|
push(lines, " use_http3_upstreams: false");
|
||||||
|
push(lines, "filtering:");
|
||||||
|
push(lines, " protection_enabled: true");
|
||||||
|
push(lines, " filtering_enabled: true");
|
||||||
|
push(lines, " filters_update_interval: 24");
|
||||||
|
push(lines, " filters:");
|
||||||
|
let filter_id = 1;
|
||||||
|
for (let url in filter_list_urls()) {
|
||||||
|
push(lines, " - enabled: true");
|
||||||
|
push(lines, " url: \"" + yaml_escape(url) + "\"");
|
||||||
|
push(lines, " name: \"" + yaml_escape(url) + "\"");
|
||||||
|
push(lines, " id: " + as_string(filter_id));
|
||||||
|
filter_id++;
|
||||||
|
}
|
||||||
|
push(lines, " user_rules: []");
|
||||||
|
push(lines, " rules: []");
|
||||||
|
push(lines, "querylog:");
|
||||||
|
push(lines, " enabled: false");
|
||||||
|
push(lines, " file_enabled: false");
|
||||||
|
push(lines, " interval: 0");
|
||||||
|
push(lines, " size_memory: 1000");
|
||||||
|
push(lines, "statistics:");
|
||||||
|
push(lines, " enabled: false");
|
||||||
|
push(lines, " interval: 0");
|
||||||
|
push(lines, "clients:");
|
||||||
|
push(lines, " runtime_sources:");
|
||||||
|
push(lines, " whois: true");
|
||||||
|
push(lines, " arp: true");
|
||||||
|
push(lines, " rdns: true");
|
||||||
|
push(lines, " dhcp: true");
|
||||||
|
push(lines, " hosts: true");
|
||||||
|
push(lines, " persistent: []");
|
||||||
|
push(lines, "log:");
|
||||||
|
push(lines, " enabled: true");
|
||||||
|
push(lines, " file: \"\"");
|
||||||
|
push(lines, " max_backups: 0");
|
||||||
|
push(lines, " max_size: 100");
|
||||||
|
push(lines, " max_age: 3");
|
||||||
|
push(lines, " compress: false");
|
||||||
|
push(lines, " local_time: false");
|
||||||
|
push(lines, " verbose: false");
|
||||||
|
push(lines, "os:");
|
||||||
|
push(lines, " group: \"\"");
|
||||||
|
push(lines, " user: \"\"");
|
||||||
|
push(lines, " rlimit_nofile: 0");
|
||||||
|
push(lines, "schema_version: 28");
|
||||||
|
return join("\n", lines) + "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensure_dirs() {
|
||||||
|
return command_success_from_args([ "mkdir", "-p", ADGUARD_WORK_DIR, ADGUARD_STATE_DIR ]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function write_config() {
|
||||||
|
if (!ensure_dirs())
|
||||||
|
return false;
|
||||||
|
let tmp = ADGUARD_CONFIG + ".tmp." + as_string(int(clock()[1]));
|
||||||
|
if (fs.writefile(tmp, build_config_yaml()) == null) {
|
||||||
|
common.remove_file(tmp);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!fs.rename(tmp, ADGUARD_CONFIG)) {
|
||||||
|
common.remove_file(tmp);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function apply_webui_auth() {
|
||||||
|
// AdGuardHome stores a bcrypt hash for the admin user. When no users are
|
||||||
|
// configured yet the control API accepts /control/install to create the
|
||||||
|
// first admin from a plaintext password. Best effort: if it fails the web
|
||||||
|
// UI simply stays open on the LAN.
|
||||||
|
let username = webui_username();
|
||||||
|
let password = webui_password();
|
||||||
|
if (username == "" || password == "")
|
||||||
|
return true;
|
||||||
|
if (!command_exists("curl"))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
let payload = sprintf("%J", { username, password });
|
||||||
|
let command = command_from_args([
|
||||||
|
"curl", "-fsS", "-m", "10",
|
||||||
|
"-H", "Content-Type: application/json",
|
||||||
|
"-d", payload,
|
||||||
|
"http://127.0.0.1:" + as_string(webui_port()) + "/control/install"
|
||||||
|
]) + " >/dev/null 2>&1";
|
||||||
|
return command_success(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
function restart_service() {
|
||||||
|
if (fs.stat(ADGUARD_SERVICE_INIT) == null)
|
||||||
|
return false;
|
||||||
|
command_success_from_args([ ADGUARD_SERVICE_INIT, "restart" ]);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function stop_service() {
|
||||||
|
if (fs.stat(ADGUARD_SERVICE_INIT) == null)
|
||||||
|
return true;
|
||||||
|
command_success_from_args([ ADGUARD_SERVICE_INIT, "stop" ]);
|
||||||
|
command_success_from_args([ "killall", "AdGuardHome" ]);
|
||||||
|
common.remove_file(ADGUARD_PID_FILE);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function configure() {
|
||||||
|
if (!provider_available())
|
||||||
|
return false;
|
||||||
|
if (!write_config())
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function start_runtime() {
|
||||||
|
if (!provider_enabled() || !provider_available()) {
|
||||||
|
stop_service();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (!configure())
|
||||||
|
return 1;
|
||||||
|
command_success_from_args([ ADGUARD_SERVICE_INIT, "enable" ]);
|
||||||
|
restart_service();
|
||||||
|
// Give the daemon a moment, then try to set the web UI credentials once.
|
||||||
|
command_success_from_args([ "sleep", "2" ]);
|
||||||
|
apply_webui_auth();
|
||||||
|
if (!running())
|
||||||
|
log_message("AdGuard Home is configured but the daemon is not running yet; check the service log", "warn");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function stop_runtime() {
|
||||||
|
stop_service();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function status_json() {
|
||||||
|
let installed = provider_available();
|
||||||
|
let version = installed ? package_version() : "not installed";
|
||||||
|
if (installed && version == "")
|
||||||
|
version = "unknown";
|
||||||
|
let enabled = provider_enabled();
|
||||||
|
let config_ready = fs.stat(ADGUARD_CONFIG) != null;
|
||||||
|
|
||||||
|
write_json({
|
||||||
|
installed,
|
||||||
|
provider_available: installed,
|
||||||
|
provider_path: ADGUARD_BIN,
|
||||||
|
version,
|
||||||
|
enabled,
|
||||||
|
running: running(),
|
||||||
|
service_enabled: service_enabled(),
|
||||||
|
service_running: service_running(),
|
||||||
|
config_ready,
|
||||||
|
dns_main_mode: dns_main_mode(),
|
||||||
|
dns_listen_host: dns_listen_host(),
|
||||||
|
dns_listen_port: dns_listen_port(),
|
||||||
|
webui_enabled: webui_enabled(),
|
||||||
|
webui_port: webui_port(),
|
||||||
|
upstream_count: length(upstream_dns_list()),
|
||||||
|
failover_enabled: failover_enabled()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function check_json() {
|
||||||
|
write_json({
|
||||||
|
adguard_installed: provider_available(),
|
||||||
|
adguard_provider_path: ADGUARD_BIN
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let mode = ARGV[0] || "";
|
||||||
|
|
||||||
|
if (mode == "start-runtime")
|
||||||
|
exit(start_runtime());
|
||||||
|
else if (mode == "stop-runtime")
|
||||||
|
exit(stop_runtime());
|
||||||
|
else if (mode == "configure")
|
||||||
|
exit(configure() ? 0 : 1);
|
||||||
|
else if (mode == "status")
|
||||||
|
status_json();
|
||||||
|
else if (mode == "check")
|
||||||
|
check_json();
|
||||||
|
else if (mode == "installed" || mode == "provider-available")
|
||||||
|
exit(provider_available() ? 0 : 1);
|
||||||
|
else if (mode == "package-version")
|
||||||
|
print(package_version(), "\n");
|
||||||
|
else if (mode == "dns-main-mode")
|
||||||
|
print(dns_main_mode(), "\n");
|
||||||
|
else if (mode == "dns-listen-host")
|
||||||
|
print(dns_listen_host(), "\n");
|
||||||
|
else if (mode == "dns-listen-port")
|
||||||
|
print(as_string(dns_listen_port()), "\n");
|
||||||
|
else if (mode == "provider-enabled")
|
||||||
|
exit(provider_enabled() ? 0 : 1);
|
||||||
|
else {
|
||||||
|
warn("Usage: providers/adguard/runtime.uc <start-runtime|stop-runtime|configure|status|check|installed|package-version|dns-main-mode|dns-listen-host|dns-listen-port|provider-enabled> ...\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
#!/usr/bin/env ucode
|
||||||
|
|
||||||
|
let common = require("core.common");
|
||||||
|
let as_string = common.as_string;
|
||||||
|
|
||||||
|
function write_json(value) {
|
||||||
|
print(sprintf("%J", value), "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
function words(value) {
|
||||||
|
value = replace(as_string(value), /[\t\r\n]/g, " ");
|
||||||
|
value = replace(value, / +/g, " ");
|
||||||
|
value = replace(value, /^ /, "");
|
||||||
|
value = replace(value, / $/, "");
|
||||||
|
return value == "" ? [] : split(value, " ");
|
||||||
|
}
|
||||||
|
|
||||||
|
function contains(values, needle) {
|
||||||
|
for (let value in values)
|
||||||
|
if (as_string(value) == as_string(needle))
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function valid_port(value) {
|
||||||
|
value = as_string(value);
|
||||||
|
return value != "" && match(value, /^[0-9]+$/) != null &&
|
||||||
|
int(value) >= 1 && int(value) <= 65535;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upstream DNS accepted forms:
|
||||||
|
// - plain IP[:port] e.g. 1.1.1.1, 8.8.8.8:53
|
||||||
|
// - https://host[/dns-query] DoH
|
||||||
|
// - tls://host[:port] DoT
|
||||||
|
// - quic://host[:port] DoQ
|
||||||
|
// - udp://host[:port] explicit plain
|
||||||
|
function validate_upstream(value) {
|
||||||
|
value = trim(as_string(value));
|
||||||
|
if (value == "")
|
||||||
|
return { valid: false, message: "Empty DNS server" };
|
||||||
|
|
||||||
|
if (match(value, /^https:\/\//) != null)
|
||||||
|
return match(value, /^https:\/\/[^\s\/]+\//) != null || match(value, /^https:\/\/[^\s\/]+$/) != null
|
||||||
|
? { valid: true, message: "" }
|
||||||
|
: { valid: false, message: "Invalid DoH URL: " + value };
|
||||||
|
|
||||||
|
if (match(value, /^tls:\/\//) != null || match(value, /^quic:\/\//) != null)
|
||||||
|
return { valid: true, message: "" };
|
||||||
|
|
||||||
|
if (match(value, /^udp:\/\//) != null)
|
||||||
|
value = substr(value, 6);
|
||||||
|
|
||||||
|
let hostport = value;
|
||||||
|
let colon = rindex(hostport, ":");
|
||||||
|
let host = "";
|
||||||
|
let port = "";
|
||||||
|
if (colon >= 0 && index(hostport, "]") < 0) {
|
||||||
|
host = substr(hostport, 0, colon);
|
||||||
|
port = substr(hostport, colon + 1);
|
||||||
|
} else {
|
||||||
|
host = hostport;
|
||||||
|
}
|
||||||
|
if (host == "")
|
||||||
|
return { valid: false, message: "Empty DNS host" };
|
||||||
|
if (port != "" && !valid_port(port))
|
||||||
|
return { valid: false, message: "Invalid DNS port in: " + value };
|
||||||
|
|
||||||
|
return { valid: true, message: "" };
|
||||||
|
}
|
||||||
|
|
||||||
|
function validate_upstreams(values) {
|
||||||
|
for (let value in (type(values) == "array" ? values : [])) {
|
||||||
|
let result = validate_upstream(value);
|
||||||
|
if (!result.valid)
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return { valid: true, message: "" };
|
||||||
|
}
|
||||||
|
|
||||||
|
function dns_main_mode_valid(mode) {
|
||||||
|
mode = as_string(mode);
|
||||||
|
return mode == "dnsmasq" || mode == "adguard";
|
||||||
|
}
|
||||||
|
|
||||||
|
function print_dns_main_mode_valid(mode) {
|
||||||
|
exit(dns_main_mode_valid(mode) ? 0 : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function validate_filter_url(value) {
|
||||||
|
value = trim(as_string(value));
|
||||||
|
if (value == "")
|
||||||
|
return { valid: false, message: "Empty filter list URL" };
|
||||||
|
return match(value, /^https?:\/\//) != null
|
||||||
|
? { valid: true, message: "" }
|
||||||
|
: { valid: false, message: "Filter list must be an http(s) URL: " + value };
|
||||||
|
}
|
||||||
|
|
||||||
|
let mode = ARGV[0] || "";
|
||||||
|
|
||||||
|
if (mode == "validate-upstream")
|
||||||
|
write_json(validate_upstream(ARGV[1]));
|
||||||
|
else if (mode == "validate-upstreams")
|
||||||
|
write_json(validate_upstreams(ARGV[1] != null ? [ ARGV[1] ] : []));
|
||||||
|
else if (mode == "validate-filter-url")
|
||||||
|
write_json(validate_filter_url(ARGV[1]));
|
||||||
|
else if (mode == "dns-main-mode-valid")
|
||||||
|
print_dns_main_mode_valid(ARGV[1]);
|
||||||
|
else if (mode == "valid-port")
|
||||||
|
exit(valid_port(ARGV[1]) ? 0 : 1);
|
||||||
|
else {
|
||||||
|
warn("Usage: providers/adguard/validator.uc <validate-upstream|validate-filter-url|dns-main-mode-valid|valid-port> ...\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
@@ -123,6 +123,7 @@ const TAILSCALE_UC = LIB_DIR + "/providers/tailscale/runtime.uc";
|
|||||||
const WDTT_UC = LIB_DIR + "/providers/wdtt/runtime.uc";
|
const WDTT_UC = LIB_DIR + "/providers/wdtt/runtime.uc";
|
||||||
const OLCRTC_UC = LIB_DIR + "/providers/olcrtc/runtime.uc";
|
const OLCRTC_UC = LIB_DIR + "/providers/olcrtc/runtime.uc";
|
||||||
const FPTN_UC = LIB_DIR + "/providers/fptn/runtime.uc";
|
const FPTN_UC = LIB_DIR + "/providers/fptn/runtime.uc";
|
||||||
|
const ADGUARD_UC = LIB_DIR + "/providers/adguard/runtime.uc";
|
||||||
const PARENTAL_QUOTA_UC = LIB_DIR + "/service/parental_quota.uc";
|
const PARENTAL_QUOTA_UC = LIB_DIR + "/service/parental_quota.uc";
|
||||||
const PACKAGES_UC = LIB_DIR + "/core/packages.uc";
|
const PACKAGES_UC = LIB_DIR + "/core/packages.uc";
|
||||||
const WATCHDOG_UC = LIB_DIR + "/service/watchdog.uc";
|
const WATCHDOG_UC = LIB_DIR + "/service/watchdog.uc";
|
||||||
@@ -994,6 +995,10 @@ function start_impl() {
|
|||||||
if (status != 0)
|
if (status != 0)
|
||||||
return status;
|
return status;
|
||||||
|
|
||||||
|
// AdGuard Home must be up before dnsmasq is pointed at it (dnsmasq mode)
|
||||||
|
// or before dnsmasq DNS is disabled in favour of AdGuard (adguard mode).
|
||||||
|
module_success(ADGUARD_UC, [ "start-runtime" ]);
|
||||||
|
|
||||||
if (!setting_bool("dont_touch_dhcp", false)) {
|
if (!setting_bool("dont_touch_dhcp", false)) {
|
||||||
status = dnsmasq_configure(false);
|
status = dnsmasq_configure(false);
|
||||||
if (status != 0)
|
if (status != 0)
|
||||||
@@ -1060,6 +1065,8 @@ function stop_main() {
|
|||||||
log_message("FPTN stop failed (non-fatal)", "warn");
|
log_message("FPTN stop failed (non-fatal)", "warn");
|
||||||
if (!module_success(TAILSCALE_UC, [ "stop-runtime" ]))
|
if (!module_success(TAILSCALE_UC, [ "stop-runtime" ]))
|
||||||
log_message("Tailscale stop failed (non-fatal)", "warn");
|
log_message("Tailscale stop failed (non-fatal)", "warn");
|
||||||
|
if (!module_success(ADGUARD_UC, [ "stop-runtime" ]))
|
||||||
|
log_message("AdGuard Home stop failed (non-fatal)", "warn");
|
||||||
module_success(PARENTAL_QUOTA_UC, [ "remove-cron" ]);
|
module_success(PARENTAL_QUOTA_UC, [ "remove-cron" ]);
|
||||||
module_success(TELEGRAM_UC, [ "stop-runtime" ]);
|
module_success(TELEGRAM_UC, [ "stop-runtime" ]);
|
||||||
|
|
||||||
|
|||||||
@@ -1401,14 +1401,27 @@ function add_dns_server_for_section(config, section) {
|
|||||||
let detour = dns_action_detour_tag(section);
|
let detour = dns_action_detour_tag(section);
|
||||||
let server_tags = [];
|
let server_tags = [];
|
||||||
|
|
||||||
|
if (dns_type == "adguard") {
|
||||||
|
let upstreams = adguard_provider_upstreams();
|
||||||
|
if (length(upstreams) == 0)
|
||||||
|
ctx.runtime_generate_unsupported("dns_type 'adguard' requires upstream DNS servers configured in the AdGuard Home provider (config provider 'adguard')");
|
||||||
|
servers = upstreams;
|
||||||
|
dns_type = "udp";
|
||||||
|
}
|
||||||
|
|
||||||
for (let i = 0; i < length(servers); i++) {
|
for (let i = 0; i < length(servers); i++) {
|
||||||
let s_val = servers[i];
|
let s_val = servers[i];
|
||||||
|
let entry_dns_type = dns_type;
|
||||||
|
if (type(s_val) == "object") {
|
||||||
|
entry_dns_type = s_val.dns_type;
|
||||||
|
s_val = s_val.value;
|
||||||
|
}
|
||||||
let tag_name = length(servers) <= 1
|
let tag_name = length(servers) <= 1
|
||||||
? dns_action_server_tag(section_name)
|
? dns_action_server_tag(section_name)
|
||||||
: dns_action_server_tag(section_name, i + 1);
|
: dns_action_server_tag(section_name, i + 1);
|
||||||
let server = runtime_dns.server_from_options(
|
let server = runtime_dns.server_from_options(
|
||||||
tag_name,
|
tag_name,
|
||||||
dns_type,
|
entry_dns_type,
|
||||||
s_val,
|
s_val,
|
||||||
detour
|
detour
|
||||||
);
|
);
|
||||||
@@ -1420,6 +1433,29 @@ function add_dns_server_for_section(config, section) {
|
|||||||
return server_tags;
|
return server_tags;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function adguard_provider_upstreams() {
|
||||||
|
let result = [];
|
||||||
|
let settings = object_or_empty(ctx.uci_cursor().get_all(CONFIG_NAME, "adguard"));
|
||||||
|
for (let value in list_option(settings, "upstream_dns")) {
|
||||||
|
value = trim(as_string(value));
|
||||||
|
if (value == "")
|
||||||
|
continue;
|
||||||
|
let dns_type = "udp";
|
||||||
|
if (match(value, /^https:\/\//) != null)
|
||||||
|
dns_type = "doh";
|
||||||
|
else if (match(value, /^tls:\/\//) != null)
|
||||||
|
dns_type = "dot";
|
||||||
|
else if (match(value, /^quic:\/\//) != null)
|
||||||
|
dns_type = "doq";
|
||||||
|
else if (match(value, /^udp:\/\//) != null) {
|
||||||
|
dns_type = "udp";
|
||||||
|
value = substr(value, 6);
|
||||||
|
}
|
||||||
|
push(result, { value, dns_type });
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
function add_routed_dns_server_for_section(config, section) {
|
function add_routed_dns_server_for_section(config, section) {
|
||||||
if (!connections.routed_dns_enabled(section))
|
if (!connections.routed_dns_enabled(section))
|
||||||
return;
|
return;
|
||||||
@@ -1430,14 +1466,30 @@ function add_routed_dns_server_for_section(config, section) {
|
|||||||
let tag_name = routed_dns_server_tag(section_name);
|
let tag_name = routed_dns_server_tag(section_name);
|
||||||
let detour = action == "bypass" ? null : outbound_tag(section_name);
|
let detour = action == "bypass" ? null : outbound_tag(section_name);
|
||||||
|
|
||||||
|
if (connections.routed_dns_adguard(section)) {
|
||||||
|
// "adguard" resolves the section's domains through the DNS servers
|
||||||
|
// configured in the AdGuard Home provider, keeping caching and the
|
||||||
|
// user's central DoH/DoT/plain choice without an upstream loop.
|
||||||
|
let upstreams = adguard_provider_upstreams();
|
||||||
|
if (length(upstreams) == 0)
|
||||||
|
ctx.runtime_generate_unsupported("routed DNS type 'adguard' requires upstream DNS servers configured in the AdGuard Home provider (config provider 'adguard')");
|
||||||
|
servers = upstreams;
|
||||||
|
dns_type = "udp";
|
||||||
|
}
|
||||||
|
|
||||||
for (let i = 0; i < length(servers); i++) {
|
for (let i = 0; i < length(servers); i++) {
|
||||||
let s_val = servers[i];
|
let s_val = servers[i];
|
||||||
|
let entry_dns_type = dns_type;
|
||||||
|
if (type(s_val) == "object") {
|
||||||
|
entry_dns_type = s_val.dns_type;
|
||||||
|
s_val = s_val.value;
|
||||||
|
}
|
||||||
let server_tag = length(servers) <= 1
|
let server_tag = length(servers) <= 1
|
||||||
? tag_name
|
? tag_name
|
||||||
: runtime_constants.tag(section_name, "routed-dns-server-" + (i + 1));
|
: runtime_constants.tag(section_name, "routed-dns-server-" + (i + 1));
|
||||||
let server = runtime_dns.server_from_options(
|
let server = runtime_dns.server_from_options(
|
||||||
server_tag,
|
server_tag,
|
||||||
dns_type,
|
entry_dns_type,
|
||||||
s_val,
|
s_val,
|
||||||
detour
|
detour
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -eo pipefail
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
TACHYON_LIB="$ROOT_DIR/tachyon/files/usr/lib"
|
||||||
|
RUNTIME="$ROOT_DIR/tachyon/files/usr/lib/providers/adguard/runtime.uc"
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
printf 'FAIL: %s\n' "$1" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# The binary is absent in the test environment, so "installed" must be false.
|
||||||
|
if ucode -L "$TACHYON_LIB" -- "$RUNTIME" installed >/dev/null 2>&1; then
|
||||||
|
fail "installed should be false without an AdGuardHome binary"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ucode -L "$TACHYON_LIB" -- "$RUNTIME" provider-available >/dev/null 2>&1; then
|
||||||
|
fail "provider-available should be false without an AdGuardHome binary"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# package-version of a missing binary must be empty.
|
||||||
|
ver="$(ucode -L "$TACHYON_LIB" -- "$RUNTIME" package-version)"
|
||||||
|
[ -z "$ver" ] || fail "package-version should be empty when not installed, got '$ver'"
|
||||||
|
|
||||||
|
# Without UCI the provider section is absent -> disabled.
|
||||||
|
if ucode -L "$TACHYON_LIB" -- "$RUNTIME" provider-enabled >/dev/null 2>&1; then
|
||||||
|
fail "provider-enabled should be false when the adguard provider section is absent"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Default DNS main mode is dnsmasq.
|
||||||
|
mode="$(ucode -L "$TACHYON_LIB" -- "$RUNTIME" dns-main-mode)"
|
||||||
|
[ "$mode" = "dnsmasq" ] || fail "default dns_main_mode should be dnsmasq, got '$mode'"
|
||||||
|
|
||||||
|
# Default listen port in dnsmasq mode is the local 5354 port.
|
||||||
|
port="$(ucode -L "$TACHYON_LIB" -- "$RUNTIME" dns-listen-port)"
|
||||||
|
[ "$port" = "5354" ] || fail "dnsmasq mode listen port should be 5354, got '$port'"
|
||||||
|
|
||||||
|
host="$(ucode -L "$TACHYON_LIB" -- "$RUNTIME" dns-listen-host)"
|
||||||
|
[ "$host" = "127.0.0.1" ] || fail "dnsmasq mode listen host should be 127.0.0.1, got '$host'"
|
||||||
|
|
||||||
|
status="$(ucode -L "$TACHYON_LIB" -- "$RUNTIME" status)"
|
||||||
|
JSON_VALUE="$status" node - <<'NODE'
|
||||||
|
const value = JSON.parse(process.env.JSON_VALUE);
|
||||||
|
if (String(value.installed) !== 'false') process.exit(1);
|
||||||
|
if (String(value.enabled) !== 'false') process.exit(1);
|
||||||
|
if (String(value.dns_main_mode) !== 'dnsmasq') process.exit(1);
|
||||||
|
NODE
|
||||||
|
|
||||||
|
check="$(ucode -L "$TACHYON_LIB" -- "$RUNTIME" check)"
|
||||||
|
JSON_VALUE="$check" node - <<'NODE'
|
||||||
|
const value = JSON.parse(process.env.JSON_VALUE);
|
||||||
|
if (String(value.adguard_installed) !== 'false') process.exit(1);
|
||||||
|
if (value.adguard_provider_path !== '/usr/bin/AdGuardHome') process.exit(1);
|
||||||
|
NODE
|
||||||
|
|
||||||
|
printf 'OK: adguard runtime tests passed\n'
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -eo pipefail
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
TACHYON_LIB="$ROOT_DIR/tachyon/files/usr/lib"
|
||||||
|
VALIDATOR="$ROOT_DIR/tachyon/files/usr/lib/providers/adguard/validator.uc"
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
printf 'FAIL: %s\n' "$1" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_json_field() {
|
||||||
|
local json="$1"
|
||||||
|
local field="$2"
|
||||||
|
local expected="$3"
|
||||||
|
|
||||||
|
JSON_VALUE="$json" node - "$field" "$expected" <<'NODE'
|
||||||
|
const field = process.argv[2];
|
||||||
|
const expected = process.argv[3];
|
||||||
|
const value = JSON.parse(process.env.JSON_VALUE);
|
||||||
|
const actual = String(value[field]);
|
||||||
|
if (actual !== expected) {
|
||||||
|
console.error(`expected ${field}=${expected}, got ${actual}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
NODE
|
||||||
|
}
|
||||||
|
|
||||||
|
doh_json="$(ucode -L "$TACHYON_LIB" -- "$VALIDATOR" validate-upstream 'https://dns.google/dns-query')"
|
||||||
|
assert_json_field "$doh_json" valid true
|
||||||
|
|
||||||
|
dot_json="$(ucode -L "$TACHYON_LIB" -- "$VALIDATOR" validate-upstream 'tls://1.1.1.1')"
|
||||||
|
assert_json_field "$dot_json" valid true
|
||||||
|
|
||||||
|
plain_json="$(ucode -L "$TACHYON_LIB" -- "$VALIDATOR" validate-upstream '8.8.8.8')"
|
||||||
|
assert_json_field "$plain_json" valid true
|
||||||
|
|
||||||
|
udp_json="$(ucode -L "$TACHYON_LIB" -- "$VALIDATOR" validate-upstream 'udp://1.1.1.1:53')"
|
||||||
|
assert_json_field "$udp_json" valid true
|
||||||
|
|
||||||
|
bad_doh_json="$(ucode -L "$TACHYON_LIB" -- "$VALIDATOR" validate-upstream 'http://not-tls.example')"
|
||||||
|
assert_json_field "$bad_doh_json" valid false
|
||||||
|
|
||||||
|
empty_json="$(ucode -L "$TACHYON_LIB" -- "$VALIDATOR" validate-upstream '')"
|
||||||
|
assert_json_field "$empty_json" valid false
|
||||||
|
|
||||||
|
bad_port_json="$(ucode -L "$TACHYON_LIB" -- "$VALIDATOR" validate-upstream '1.1.1.1:99999')"
|
||||||
|
assert_json_field "$bad_port_json" valid false
|
||||||
|
|
||||||
|
if ucode -L "$TACHYON_LIB" -- "$VALIDATOR" dns-main-mode-valid 'dnsmasq' >/dev/null 2>&1; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
fail "dnsmasq should be a valid dns_main_mode"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ucode -L "$TACHYON_LIB" -- "$VALIDATOR" dns-main-mode-valid 'adguard' >/dev/null 2>&1; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
fail "adguard should be a valid dns_main_mode"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ucode -L "$TACHYON_LIB" -- "$VALIDATOR" dns-main-mode-valid 'bogus' >/dev/null 2>&1; then
|
||||||
|
fail "bogus dns_main_mode should be rejected"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ucode -L "$TACHYON_LIB" -- "$VALIDATOR" valid-port '3000' >/dev/null 2>&1; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
fail "3000 should be a valid port"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ucode -L "$TACHYON_LIB" -- "$VALIDATOR" valid-port 'notaport' >/dev/null 2>&1; then
|
||||||
|
fail "non-numeric port should be rejected"
|
||||||
|
fi
|
||||||
|
|
||||||
|
filter_json="$(ucode -L "$TACHYON_LIB" -- "$VALIDATOR" validate-filter-url 'https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt')"
|
||||||
|
assert_json_field "$filter_json" valid true
|
||||||
|
|
||||||
|
bad_filter_json="$(ucode -L "$TACHYON_LIB" -- "$VALIDATOR" validate-filter-url 'ftp://filter')"
|
||||||
|
assert_json_field "$bad_filter_json" valid false
|
||||||
|
|
||||||
|
printf 'OK: adguard validator tests passed\n'
|
||||||
Reference in New Issue
Block a user