Skip to content

Commit dc922ba

Browse files
authored
Merge pull request #1704 from RaspAP/feat/adguard-provider
Add VPN provider support for AdGuard
2 parents 0e39896 + 66f5cd4 commit dc922ba

File tree

2 files changed

+125
-38
lines changed

2 files changed

+125
-38
lines changed

config/vpn-providers.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,26 @@
5050
"pattern": "(\\w+)\\s+",
5151
"replace": "$1,$1\\n"
5252
}
53+
},
54+
{
55+
"id": 4,
56+
"name": "AdGuard VPN",
57+
"bin_path": "/usr/local/bin/adguardvpn-cli",
58+
"install_page": "https://adguard-vpn.com/kb/adguard-vpn-for-linux/installation/",
59+
"account_page": "https://my.adguard-vpn.com/en/account/product/vpn",
60+
"cmd_overrides": {
61+
"countries": "list-locations",
62+
"connect": "connect -y -l",
63+
"log": "status",
64+
"account": "license",
65+
"version": "--version"
66+
},
67+
"regex": {
68+
"status": "\/vpn is disconnected\/",
69+
"pattern": "/^([A-Z]{2})\\s+.*?\\s([A-Za-z]+(?:\\s[A-Za-z]+)?)\\s+\\d+$/m",
70+
"replace": "$2",
71+
"slice": 3
72+
}
5373
}
5474
]
5575
}

includes/provider.php

Lines changed: 105 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -16,37 +16,11 @@ function DisplayProviderConfig()
1616
$providerName = getProviderValue($id, "name");
1717
$providerVersion = getProviderVersion($id, $binPath);
1818
$installPage = getProviderValue($id, "install_page");
19-
$publicIP = get_public_ip();
2019
$serviceStatus = 'down';
2120
$statusDisplay = 'down';
2221
$ctlState = '';
2322

24-
if (!file_exists($binPath)) {
25-
$status->addMessage(sprintf(_('Expected %s binary not found at: %s'), $providerName, $binPath), 'warning');
26-
$status->addMessage(sprintf(_('Visit the <a href="%s" target="_blank">installation instructions</a> for %s\'s Linux CLI.'), $installPage, $providerName), 'warning');
27-
$ctlState = 'disabled';
28-
$providerVersion = 'not found';
29-
} elseif (empty($providerVersion)) {
30-
$status->addMessage(sprintf(_('Unable to execute %s binary found at: %s'), $providerName, $binPath), 'warning');
31-
$status->addMessage(_('Check that binary is executable and permissions exist in raspap.sudoers'), 'warning');
32-
$ctlState = 'disabled';
33-
$providerVersion = 'not found';
34-
} else {
35-
// fetch provider status
36-
$serviceStatus = getProviderStatus($id, $binPath);
37-
$statusDisplay = $serviceStatus == "down" ? "inactive" : "active";
38-
39-
// fetch provider log
40-
$providerLog = getProviderLog($id, $binPath, $country);
41-
42-
// fetch account info
43-
$accountInfo = getAccountInfo($id, $binPath, $providerName);
44-
$accountLink = getProviderValue($id, "account_page");
45-
46-
// fetch available countries
47-
$countries = getCountries($id, $binPath);
48-
}
49-
23+
// handle page actions
5024
if (!RASPI_MONITOR_ENABLED) {
5125
if (isset($_POST['SaveProviderSettings'])) {
5226
if (isset($_POST['country'])) {
@@ -60,7 +34,11 @@ function DisplayProviderConfig()
6034
} elseif (isset($_POST['StartProviderVPN'])) {
6135
$status->addMessage('Attempting to connect VPN provider', 'info');
6236
$cmd = getCliOverride($id, 'cmd_overrides', 'connect');
63-
exec("sudo $binPath $cmd", $return);
37+
$country = escapeshellarg(trim($_POST['country']));
38+
if ($id = 4) { // AdGuard requires country argument on connect
39+
$arg = escapeshellarg(trim($_POST['country']));
40+
}
41+
exec("sudo $binPath $cmd $arg", $return);
6442
$return = stripArtifacts($return);
6543
foreach ($return as $line) {
6644
if (strlen(trim($line)) > 0) {
@@ -83,6 +61,33 @@ function DisplayProviderConfig()
8361
}
8462
}
8563

64+
if (!file_exists($binPath)) {
65+
$status->addMessage(sprintf(_('Expected %s binary not found at: %s'), $providerName, $binPath), 'warning');
66+
$status->addMessage(sprintf(_('Visit the <a href="%s" target="_blank">installation instructions</a> for %s\'s Linux CLI.'), $installPage, $providerName), 'warning');
67+
$ctlState = 'disabled';
68+
$providerVersion = 'not found';
69+
} elseif (empty($providerVersion)) {
70+
$status->addMessage(sprintf(_('Unable to execute %s binary found at: %s'), $providerName, $binPath), 'warning');
71+
$status->addMessage(_('Check that binary is executable and permissions exist in raspap.sudoers'), 'warning');
72+
$ctlState = 'disabled';
73+
$providerVersion = 'not found';
74+
} else {
75+
// fetch provider status
76+
$serviceStatus = getProviderStatus($id, $binPath);
77+
$statusDisplay = $serviceStatus == "down" ? "inactive" : "active";
78+
79+
// fetch account info
80+
$accountInfo = getAccountInfo($id, $binPath, $providerName);
81+
$accountLink = getProviderValue($id, "account_page");
82+
83+
// fetch available countries
84+
$countries = getCountries($id, $binPath);
85+
86+
// fetch provider log
87+
$providerLog = getProviderLog($id, $binPath, $country);
88+
}
89+
$publicIP = get_public_ip();
90+
8691
echo renderTemplate(
8792
"provider", compact(
8893
"status",
@@ -129,16 +134,45 @@ function saveProviderConfig($status, $binPath, $country, $id = null)
129134
}
130135

131136
/**
132-
* Removes artifacts from shell_exec string values
137+
* Removes artifacts from shell_exec string values and lines with ANSI escape sequences
133138
*
134-
* @param string $output
135-
* @param string $pattern
136-
* @return string $result
139+
* @param string|array $output
140+
* @param string|null $pattern
141+
* @return string|array $result
137142
*/
138143
function stripArtifacts($output, $pattern = null)
139144
{
140-
$result = preg_replace('/[-\/\n\t\\\\'.$pattern.'|]/', '', $output);
141-
return $result;
145+
if (is_array($output)) {
146+
return array_map(function ($line) use ($pattern) {
147+
return stripArtifacts($line, $pattern);
148+
}, $output);
149+
}
150+
if (!is_string($output)) {
151+
return $output;
152+
}
153+
154+
$lines = explode("\n", $output);
155+
$lines = array_filter($lines, function ($line) use ($pattern) {
156+
// remove ANSI escape sequences
157+
if (preg_match('/\x1b\[[0-9;]*[a-zA-Z]/', $line)) {
158+
return false;
159+
}
160+
$line = preg_replace('/[-\/\t\\\\' . preg_quote($pattern, '/') . '|]/', '', $line);
161+
return trim($line) !== '';
162+
});
163+
return implode("\n", $lines);
164+
}
165+
166+
/**
167+
* Removes ANSI escape sequences and preserves CLI return values
168+
*
169+
* @param array $output
170+
*/
171+
function stripAnsiSequence($output)
172+
{
173+
return array_map(function ($line) {
174+
return preg_replace('/\x1b\[[0-9;]*[a-zA-Z]/', '', $line);
175+
}, $output);
142176
}
143177

144178
/**
@@ -176,8 +210,7 @@ function getProviderStatus($id, $binPath)
176210
$cmd = getCliOverride($id, 'cmd_overrides', 'status');
177211
$pattern = getCliOverride($id, 'regex', 'status');
178212
exec("sudo $binPath $cmd", $cmd_raw);
179-
$cmd_raw = strtolower(stripArtifacts($cmd_raw[0]));
180-
213+
$cmd_raw = strtolower(($cmd_raw[0]));
181214
if (!empty($cmd_raw[0])) {
182215
if (preg_match($pattern, $cmd_raw, $match)) {
183216
$status = "down";
@@ -245,6 +278,40 @@ function getCountries($id, $binPath)
245278
$countries[$value] = str_replace("_", " ", $value);
246279
}
247280
break;
281+
case 4: // adguard
282+
$raw_countries = [];
283+
$totalLines = count($output);
284+
foreach ($output as $index => $item) {
285+
if ($index === 0 || $index === $totalLines - 1) {
286+
// exclude first and last lines
287+
continue;
288+
}
289+
preg_match($pattern, $item, $matches);
290+
$item_country = trim($matches[1]);
291+
$item_city = trim($matches[2]);
292+
$item_key = str_replace(" ", "_", $item_city);
293+
if ( strlen($item_key) > 0 ){
294+
$countries[$item_key] = "{$item_country} {$item_city}";
295+
if (!isset($raw_countries[$item_country])) {
296+
$raw_countries[$item_country] = [];
297+
}
298+
$raw_countries[$item_country][] = $item_city;
299+
}
300+
}
301+
// sort countries alphabetically
302+
ksort($raw_countries);
303+
// sort cities within each country
304+
foreach ($raw_countries as $country => $cities) {
305+
sort($raw_countries[$country]); // Trier les villes par ordre alphabétique
306+
}
307+
// sort results by country, then by city
308+
foreach ($raw_countries as $country => $cities) {
309+
foreach ($cities as $city) {
310+
$item_key = str_replace(" ", "_", $city);
311+
$countries[$item_key] = "{$country} {$city}";
312+
}
313+
}
314+
break;
248315
default:
249316
break;
250317
}
@@ -266,7 +333,7 @@ function getProviderLog($id, $binPath, &$country)
266333
$providerLog = '';
267334
$cmd = getCliOverride($id, 'cmd_overrides', 'log');
268335
exec("sudo $binPath $cmd", $cmd_raw);
269-
$output = stripArtifacts($cmd_raw);
336+
$output = stripAnsiSequence($cmd_raw);
270337
foreach ($output as $item) {
271338
if (preg_match('/Country: (\w+)/', $item, $match)) {
272339
$country = $match[1];
@@ -303,7 +370,7 @@ function getAccountInfo($id, $binPath, $providerName)
303370
{
304371
$cmd = getCliOverride($id, 'cmd_overrides', 'account');
305372
exec("sudo $binPath $cmd", $acct);
306-
373+
$acct = stripAnsiSequence($acct);
307374
foreach ($acct as &$item) {
308375
$item = preg_replace('/^[^\w]+\s*/', '', $item);
309376
}

0 commit comments

Comments
 (0)