feat: add multi-protocol support for AWG2 with dynamic tool selection and configuration path resolution
This commit is contained in:
@@ -427,14 +427,16 @@ class InstallProtocolManager
|
||||
{
|
||||
$metadata = $protocol['definition']['metadata'] ?? [];
|
||||
$serverData = $server->getData();
|
||||
$containerName = $serverData['container_name'] ?? ($metadata['container_name'] ?? 'amnezia-awg');
|
||||
// For multi-protocol servers, use container_name from protocol metadata first
|
||||
// (vpn_servers.container_name stores the primary protocol's container, e.g. 'aivpn-server')
|
||||
$containerName = $metadata['container_name'] ?? ($serverData['container_name'] ?? 'amnezia-awg');
|
||||
$containerFilter = escapeshellarg('^' . $containerName . '$');
|
||||
$containerArg = escapeshellarg($containerName);
|
||||
|
||||
// Для AWG2 конфигурация внутри контейнера находится в /opt/amnezia/awg/awg0.conf
|
||||
// На хосте может быть /opt/amnezia/awg2/wg0.conf (монтируется как /opt/amnezia/awg внутри контейнера)
|
||||
// AWG2 uses awg0.conf (standard, same as native Amnezia app)
|
||||
// Old AWG uses wg0.conf
|
||||
$isAwg2 = (stripos($containerName, 'awg2') !== false || ($protocol['slug'] ?? '') === 'awg2');
|
||||
$configDir = $isAwg2 ? '/opt/amnezia/awg' : '/opt/amnezia/awg';
|
||||
$configDir = '/opt/amnezia/awg';
|
||||
$configFile = $isAwg2 ? 'awg0.conf' : 'wg0.conf';
|
||||
|
||||
$containerListRaw = trim($server->executeCommand("docker ps -a --filter name={$containerFilter} --format '{{.Names}}'", true));
|
||||
@@ -466,13 +468,18 @@ class InstallProtocolManager
|
||||
|
||||
$containerState = trim($server->executeCommand("docker inspect --format '{{.State.Status}}' {$containerArg}", true));
|
||||
|
||||
// Для AWG2 проверяем оба возможных имени файла конфигурации
|
||||
$configFile = ($protocol['slug'] ?? '') === 'awg2' ? 'awg0.conf' : 'wg0.conf';
|
||||
// AWG2: try awg0.conf first (standard), fall back to wg0.conf (legacy panel installs)
|
||||
$configFile = $isAwg2 ? 'awg0.conf' : 'wg0.conf';
|
||||
$wgConfig = $server->executeCommand("docker exec -i {$containerArg} cat {$configDir}/{$configFile} 2>/dev/null", true);
|
||||
if (trim($wgConfig) === '') {
|
||||
if ($isAwg2 && (trim($wgConfig) === '' || strpos($wgConfig, '[Interface]') === false)) {
|
||||
// Fallback to wg0.conf for legacy panel installs
|
||||
$configFile = 'wg0.conf';
|
||||
$wgConfig = $server->executeCommand("docker exec -i {$containerArg} cat {$configDir}/{$configFile} 2>/dev/null", true);
|
||||
}
|
||||
if (trim($wgConfig) === '' || strpos($wgConfig, '[Interface]') === false) {
|
||||
return [
|
||||
'status' => 'partial',
|
||||
'message' => "Контейнер найден, но конфигурация {$configFile} отсутствует",
|
||||
'message' => "Контейнер найден, но конфигурация wg0.conf/awg0.conf отсутствует",
|
||||
'details' => [
|
||||
'container_name' => $containerName,
|
||||
'container_status' => $containerState,
|
||||
@@ -532,11 +539,18 @@ class InstallProtocolManager
|
||||
$containerName = $details['container_name'] ?? ($protocol['definition']['metadata']['container_name'] ?? 'amnezia-awg');
|
||||
$containerArg = escapeshellarg($containerName);
|
||||
|
||||
// Для AWG2 конфигурация внутри контейнера находится в /opt/amnezia/awg/awg0.conf
|
||||
// На хосте может быть /opt/amnezia/awg2/wg0.conf (монтируется как /opt/amnezia/awg внутри контейнера)
|
||||
// Config is always wg0.conf — container CMD runs: awg-quick up /opt/amnezia/awg/wg0.conf
|
||||
$isAwg2 = (stripos($containerName, 'awg2') !== false || ($protocol['slug'] ?? '') === 'awg2');
|
||||
$configDir = '/opt/amnezia/awg'; // Внутри контейнера всегда /opt/amnezia/awg
|
||||
$configDir = '/opt/amnezia/awg';
|
||||
// AWG2: try awg0.conf first (standard), fall back to wg0.conf (legacy)
|
||||
$configFile = $isAwg2 ? 'awg0.conf' : 'wg0.conf';
|
||||
$testConf = trim($server->executeCommand("docker exec -i {$containerArg} cat {$configDir}/{$configFile} 2>/dev/null", true));
|
||||
if ($isAwg2 && ($testConf === '' || strpos($testConf, '[Interface]') === false)) {
|
||||
$configFile = 'wg0.conf';
|
||||
}
|
||||
|
||||
// Determine interface name from config filename (wg0.conf -> wg0, awg0.conf -> awg0)
|
||||
$ifaceName = str_replace('.conf', '', $configFile);
|
||||
|
||||
// Try to ensure container is running and wg is up
|
||||
$server->executeCommand("docker start {$containerArg} 2>/dev/null || true", true);
|
||||
@@ -544,6 +558,17 @@ class InstallProtocolManager
|
||||
$server->executeCommand("docker exec -i {$containerArg} wg-quick up {$configDir}/{$configFile} 2>/dev/null || true", true);
|
||||
|
||||
$pdo = DB::conn();
|
||||
$serverData = $server->getData();
|
||||
$serverId = $server->getId();
|
||||
$protocolId = self::resolveProtocolId($protocol);
|
||||
$protocolSlug = $protocol['slug'] ?? ($isAwg2 ? 'awg2' : 'amnezia-wg');
|
||||
|
||||
// Check if server already has another primary protocol installed
|
||||
$existingProtocol = $serverData['install_protocol'] ?? '';
|
||||
$isSecondaryProtocol = ($existingProtocol !== '' && $existingProtocol !== $protocolSlug);
|
||||
|
||||
if (!$isSecondaryProtocol) {
|
||||
// Primary protocol — update vpn_servers
|
||||
$stmt = $pdo->prepare('
|
||||
UPDATE vpn_servers
|
||||
SET vpn_port = ?,
|
||||
@@ -562,22 +587,35 @@ class InstallProtocolManager
|
||||
$details['preshared_key'] ?? null,
|
||||
isset($details['awg_params']) ? json_encode($details['awg_params']) : null,
|
||||
'active',
|
||||
$protocol['slug'] ?? ($isAwg2 ? 'awg2' : 'amnezia-wg'),
|
||||
$server->getId()
|
||||
$protocolSlug,
|
||||
$serverId
|
||||
]);
|
||||
} else {
|
||||
// Secondary protocol — only ensure server is active, don't overwrite primary protocol data
|
||||
$stmt = $pdo->prepare('UPDATE vpn_servers SET status = ?, error_message = NULL WHERE id = ?');
|
||||
$stmt->execute(['active', $serverId]);
|
||||
}
|
||||
|
||||
// Add entry to server_protocols table so protocol shows in installed list
|
||||
$protocolId = self::resolveProtocolId($protocol);
|
||||
// Store protocol-specific config in server_protocols (works for both primary and secondary)
|
||||
if ($protocolId) {
|
||||
$stmt = $pdo->prepare('
|
||||
INSERT INTO server_protocols (server_id, protocol_id, applied_at)
|
||||
VALUES (?, ?, NOW())
|
||||
ON DUPLICATE KEY UPDATE applied_at = NOW()
|
||||
');
|
||||
$stmt->execute([
|
||||
$server->getId(),
|
||||
$protocolId
|
||||
$configData = json_encode([
|
||||
'server_host' => $serverData['ip_address'] ?? $serverData['hostname'] ?? null,
|
||||
'server_port' => $details['vpn_port'] ?? null,
|
||||
'extras' => [
|
||||
'vpn_port' => $details['vpn_port'] ?? null,
|
||||
'vpn_subnet' => $details['vpn_subnet'] ?? '10.8.1.0/24',
|
||||
'server_public_key' => $details['server_public_key'] ?? null,
|
||||
'preshared_key' => $details['preshared_key'] ?? null,
|
||||
'awg_params' => $details['awg_params'] ?? null,
|
||||
'container_name' => $containerName,
|
||||
],
|
||||
]);
|
||||
$stmt = $pdo->prepare('
|
||||
INSERT INTO server_protocols (server_id, protocol_id, config_data, applied_at, created_at)
|
||||
VALUES (?, ?, ?, NOW(), NOW())
|
||||
ON DUPLICATE KEY UPDATE config_data = VALUES(config_data), applied_at = NOW()
|
||||
');
|
||||
$stmt->execute([$serverId, $protocolId, $configData]);
|
||||
}
|
||||
|
||||
$server->refresh();
|
||||
@@ -646,18 +684,28 @@ class InstallProtocolManager
|
||||
// Use existing keys and config
|
||||
} else {
|
||||
// Generate new key pair for this client
|
||||
$newPrivateKey = trim($server->executeCommand("docker exec -i {$containerArg} /usr/bin/awg genkey 2>/dev/null", true));
|
||||
$newPublicKey = trim($server->executeCommand("docker exec -i {$containerArg} sh -c 'echo \"'\"$newPrivateKey\"'\" | /usr/bin/awg pubkey' 2>/dev/null", true));
|
||||
// Use awg for AWG2, wg for standard
|
||||
$keyTool = $isAwg2 ? 'awg' : 'wg';
|
||||
$newPrivateKey = trim($server->executeCommand("docker exec {$containerArg} {$keyTool} genkey", true));
|
||||
if (!empty($newPrivateKey)) {
|
||||
$escapedKey = escapeshellarg($newPrivateKey);
|
||||
$newPublicKey = trim($server->executeCommand("docker exec {$containerArg} sh -c 'echo {$escapedKey} | {$keyTool} pubkey'", true));
|
||||
} else {
|
||||
$newPublicKey = '';
|
||||
}
|
||||
|
||||
if (!empty($newPrivateKey) && !empty($newPublicKey)) {
|
||||
Logger::appendInstall($serverId, "Restore: keygen for {$clientIp}: privkey_len=" . strlen($newPrivateKey) . " pubkey_len=" . strlen($newPublicKey));
|
||||
|
||||
if (!empty($newPrivateKey) && !empty($newPublicKey) && strlen($newPublicKey) >= 40) {
|
||||
$privateKey = $newPrivateKey;
|
||||
$protocolSlug = $protocol['slug'] ?? '';
|
||||
$serverHost = $serverData['host'] ?? $serverData['ip_address'] ?? $serverData['hostname'] ?? '';
|
||||
$config = VpnClient::buildClientConfig(
|
||||
$privateKey,
|
||||
$clientIp,
|
||||
$details['server_public_key'] ?? '',
|
||||
$details['preshared_key'] ?? '',
|
||||
$serverData['ip_address'] ?? $serverData['hostname'] ?? '',
|
||||
$serverHost,
|
||||
$details['vpn_port'] ?? 51820,
|
||||
$details['awg_params'] ?? [],
|
||||
$protocolSlug
|
||||
@@ -667,6 +715,8 @@ class InstallProtocolManager
|
||||
// Mark that we need to update server config with new public key
|
||||
$needsServerConfigUpdate = true;
|
||||
$keyUpdates[] = ['old' => $pub, 'new' => $newPublicKey];
|
||||
} else {
|
||||
Logger::appendInstall($serverId, "Restore: WARNING keygen failed for {$clientIp}, keeping original public key");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -693,7 +743,7 @@ class InstallProtocolManager
|
||||
if ($needsServerConfigUpdate && !empty($keyUpdates)) {
|
||||
Logger::appendInstall($serverId, "Restore: updating server config with " . count($keyUpdates) . " new public keys");
|
||||
|
||||
// Update awg0.conf - replace old public keys with new ones
|
||||
// Update wg0.conf - replace old public keys with new ones
|
||||
$updatedConfig = $wgConfig;
|
||||
foreach ($keyUpdates as $update) {
|
||||
// Escape special characters for regex
|
||||
@@ -1403,6 +1453,7 @@ class InstallProtocolManager
|
||||
|
||||
if ($isAwg) {
|
||||
$detection = self::detectBuiltinAwg($server, $protocol);
|
||||
Logger::appendInstall($serverId, 'AWG detect result: status=' . ($detection['status'] ?? 'null') . ' message=' . ($detection['message'] ?? 'none'));
|
||||
if (($detection['status'] ?? '') === 'existing') {
|
||||
Logger::appendInstall($serverId, 'Existing AWG installation detected, restoring instead of reinstalling');
|
||||
$restoreResult = self::restoreBuiltinAwg($server, $protocol, $detection, $options);
|
||||
@@ -2104,11 +2155,15 @@ class InstallProtocolManager
|
||||
|
||||
$serverData = $server->getData();
|
||||
$containerName = $serverData['container_name'] ?? 'amnezia-awg';
|
||||
// Для AWG2 конфигурация внутри контейнера находится в /opt/amnezia/awg/awg0.conf
|
||||
// AWG2: try awg0.conf first (standard), fall back to wg0.conf (legacy)
|
||||
$isAwg2 = (stripos($containerName, 'awg2') !== false || ($protocol['slug'] ?? '') === 'awg2');
|
||||
$configDir = '/opt/amnezia/awg'; // Внутри контейнера всегда /opt/amnezia/awg
|
||||
$configDir = '/opt/amnezia/awg';
|
||||
$configFile = $isAwg2 ? 'awg0.conf' : 'wg0.conf';
|
||||
$conf = $server->executeCommand("docker exec -i $containerName cat {$configDir}/{$configFile}", true);
|
||||
if ($isAwg2 && (!$conf || strpos($conf, '[Interface]') === false)) {
|
||||
$configFile = 'wg0.conf';
|
||||
$conf = $server->executeCommand("docker exec -i $containerName cat {$configDir}/{$configFile}", true);
|
||||
}
|
||||
if (!$conf)
|
||||
return;
|
||||
|
||||
@@ -2454,11 +2509,15 @@ class InstallProtocolManager
|
||||
$serverData = $server->getData();
|
||||
$pid = self::resolveProtocolId($protocol);
|
||||
|
||||
// Для AWG2 конфигурация внутри контейнера находится в /opt/amnezia/awg/awg0.conf
|
||||
// AWG2: try awg0.conf first (standard), fall back to wg0.conf (legacy)
|
||||
$isAwg2 = (stripos($containerName, 'awg2') !== false || ($protocol['slug'] ?? '') === 'awg2');
|
||||
$configDir = '/opt/amnezia/awg'; // Внутри контейнера всегда /opt/amnezia/awg
|
||||
$configDir = '/opt/amnezia/awg';
|
||||
$configFile = $isAwg2 ? 'awg0.conf' : 'wg0.conf';
|
||||
$wgConfig = $server->executeCommand("docker exec -i {$containerArg} cat {$configDir}/{$configFile} 2>/dev/null", true);
|
||||
if ($isAwg2 && (trim($wgConfig) === '' || strpos($wgConfig, '[Interface]') === false)) {
|
||||
$configFile = 'wg0.conf';
|
||||
$wgConfig = $server->executeCommand("docker exec -i {$containerArg} cat {$configDir}/{$configFile} 2>/dev/null", true);
|
||||
}
|
||||
$tableRaw = $server->executeCommand("docker exec -i {$containerArg} cat {$configDir}/clientsTable 2>/dev/null", true);
|
||||
$clientsTable = json_decode(trim($tableRaw), true);
|
||||
|
||||
|
||||
+100
-17
@@ -100,6 +100,61 @@ class VpnClient
|
||||
}
|
||||
}
|
||||
|
||||
// For multi-protocol setups, override server data with protocol-specific settings
|
||||
// (subnet, keys, port, AWG params) from server_protocols.config_data or protocol metadata
|
||||
if ($protocolId) {
|
||||
try {
|
||||
$stmtSp = $pdo->prepare('SELECT config_data FROM server_protocols WHERE server_id = ? AND protocol_id = ? LIMIT 1');
|
||||
$stmtSp->execute([$serverId, $protocolId]);
|
||||
$spConfigRaw = $stmtSp->fetchColumn();
|
||||
if ($spConfigRaw) {
|
||||
$spConfig = is_string($spConfigRaw) ? json_decode($spConfigRaw, true) : $spConfigRaw;
|
||||
if (is_array($spConfig)) {
|
||||
$spExtras = $spConfig['extras'] ?? [];
|
||||
// If extras has 'result' subarray, merge it
|
||||
if (isset($spExtras['result']) && is_array($spExtras['result'])) {
|
||||
$spExtras = array_merge($spExtras, $spExtras['result']);
|
||||
}
|
||||
// Override server data with protocol-specific values
|
||||
if (!empty($spExtras['server_public_key'])) {
|
||||
$serverData['server_public_key'] = $spExtras['server_public_key'];
|
||||
}
|
||||
if (!empty($spExtras['preshared_key'])) {
|
||||
$serverData['preshared_key'] = $spExtras['preshared_key'];
|
||||
}
|
||||
if (!empty($spExtras['vpn_port'])) {
|
||||
$serverData['vpn_port'] = $spExtras['vpn_port'];
|
||||
}
|
||||
if (!empty($spConfig['server_port'])) {
|
||||
$serverData['vpn_port'] = $spConfig['server_port'];
|
||||
}
|
||||
// Override AWG params from protocol config
|
||||
// AWG params can be at extras level (Jc, S1, etc.) or nested in extras.awg_params
|
||||
$awgOverride = [];
|
||||
$awgSource = $spExtras;
|
||||
if (isset($spExtras['awg_params']) && is_array($spExtras['awg_params'])) {
|
||||
$awgSource = array_merge($awgSource, $spExtras['awg_params']);
|
||||
}
|
||||
foreach (['Jc', 'Jmin', 'Jmax', 'S1', 'S2', 'S3', 'S4', 'H1', 'H2', 'H3', 'H4', 'I1', 'I2', 'I3', 'I4', 'I5'] as $ak) {
|
||||
if (isset($awgSource[$ak]) && $awgSource[$ak] !== '' && $awgSource[$ak] !== null) {
|
||||
$awgOverride[$ak] = $awgSource[$ak];
|
||||
}
|
||||
}
|
||||
if (!empty($awgOverride)) {
|
||||
$serverData['awg_params'] = json_encode($awgOverride);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
error_log('Failed to load protocol config_data: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
// Override vpn_subnet from protocol definition metadata (e.g. AWG2 uses 10.8.1.0/24)
|
||||
if (!empty($protoMetadata['vpn_subnet'])) {
|
||||
$serverData['vpn_subnet'] = $protoMetadata['vpn_subnet'];
|
||||
}
|
||||
}
|
||||
|
||||
$clientIP = self::getNextClientIP($serverData);
|
||||
$loginBase = $login !== null && $login !== '' ? $login : $name;
|
||||
$loginBase = str_replace(' ', '_', trim($loginBase));
|
||||
@@ -742,9 +797,15 @@ class VpnClient
|
||||
private static function generateClientKeys(array $serverData, string $clientName): array
|
||||
{
|
||||
$containerName = $serverData['container_name'];
|
||||
$protocolSlug = (string) ($serverData['install_protocol'] ?? '');
|
||||
$isAwg2 = (stripos($containerName, 'awg2') !== false || $protocolSlug === 'awg2');
|
||||
$wgTool = $isAwg2 ? 'awg' : 'wg';
|
||||
|
||||
$cmd = sprintf(
|
||||
"docker exec -i %s sh -lc 'set -e; umask 077; priv=\$(wg genkey | tr -d " . '"' . "\\r\\n" . '"' . "); [ -n \"\$priv\" ] || { echo empty_private_key; exit 1; }; pub=\$(printf " . '"' . "%%s\\n" . '"' . " \"\$priv\" | wg pubkey | tr -d " . '"' . "\\r\\n" . '"' . "); [ -n \"\$pub\" ] || { echo empty_public_key; exit 1; }; printf " . '"' . "%%s\\n---\\n%%s\\n" . '"' . " \"\$priv\" \"\$pub\"'",
|
||||
escapeshellarg($containerName)
|
||||
"docker exec -i %s sh -lc 'set -e; umask 077; priv=\$(%s genkey | tr -d " . '"' . "\\r\\n" . '"' . "); [ -n \"\$priv\" ] || { echo empty_private_key; exit 1; }; pub=\$(printf " . '"' . "%%s\\n" . '"' . " \"\$priv\" | %s pubkey | tr -d " . '"' . "\\r\\n" . '"' . "); [ -n \"\$pub\" ] || { echo empty_public_key; exit 1; }; printf " . '"' . "%%s\\n---\\n%%s\\n" . '"' . " \"\$priv\" \"\$pub\"'",
|
||||
escapeshellarg($containerName),
|
||||
$wgTool,
|
||||
$wgTool
|
||||
);
|
||||
|
||||
$escaped = escapeshellarg($cmd);
|
||||
@@ -1133,10 +1194,18 @@ class VpnClient
|
||||
{
|
||||
$containerName = $serverData['container_name'];
|
||||
$protocolSlug = (string) ($serverData['install_protocol'] ?? '');
|
||||
// Для AWG2 конфигурация внутри контейнера находится в /opt/amnezia/awg/awg0.conf
|
||||
$isAwg2 = (stripos($containerName, 'awg2') !== false || $protocolSlug === 'awg2');
|
||||
$configDir = '/opt/amnezia/awg'; // Внутри контейнера всегда /opt/amnezia/awg
|
||||
$configDir = '/opt/amnezia/awg';
|
||||
|
||||
// AWG2: try awg0.conf first (standard), fall back to wg0.conf (legacy panel installs)
|
||||
$configFile = $isAwg2 ? 'awg0.conf' : 'wg0.conf';
|
||||
$testConf = trim(self::executeServerCommand($serverData, "docker exec -i {$containerName} cat {$configDir}/{$configFile} 2>/dev/null", true));
|
||||
if ($isAwg2 && ($testConf === '' || strpos($testConf, '[Interface]') === false)) {
|
||||
$configFile = 'wg0.conf';
|
||||
}
|
||||
// Interface name matches config filename (wg0.conf -> wg0, awg0.conf -> awg0)
|
||||
$ifaceName = str_replace('.conf', '', $configFile);
|
||||
|
||||
$presharedKey = $serverData['preshared_key'];
|
||||
$publicKey = trim($publicKey);
|
||||
|
||||
@@ -1144,16 +1213,21 @@ class VpnClient
|
||||
throw new Exception('Refusing to add client with empty public key');
|
||||
}
|
||||
|
||||
// Determine correct tool names (awg for AWG2, wg for standard)
|
||||
$wgTool = $isAwg2 ? 'awg' : 'wg';
|
||||
$wgQuickTool = $isAwg2 ? 'awg-quick' : 'wg-quick';
|
||||
|
||||
// 1. Create temp file for PSK (to avoid shell escaping issues)
|
||||
$pskFile = '/tmp/' . bin2hex(random_bytes(8)) . '.psk';
|
||||
$cmd1 = sprintf("docker exec -i %s sh -c 'echo \"%s\" > %s'", $containerName, $presharedKey, $pskFile);
|
||||
self::executeServerCommand($serverData, $cmd1, true);
|
||||
|
||||
// 2. Add peer using wg set
|
||||
// wg set wg0 peer <PUBKEY> preshared-key <FILE> allowed-ips <IPS>
|
||||
// 2. Add peer using wg/awg set
|
||||
$cmd2 = sprintf(
|
||||
"docker exec -i %s wg set wg0 peer %s preshared-key %s allowed-ips %s/32",
|
||||
"docker exec -i %s %s set %s peer %s preshared-key %s allowed-ips %s/32",
|
||||
$containerName,
|
||||
$wgTool,
|
||||
$ifaceName,
|
||||
escapeshellarg($publicKey),
|
||||
$pskFile,
|
||||
$clientIP
|
||||
@@ -1164,14 +1238,13 @@ class VpnClient
|
||||
$cmd3 = sprintf("docker exec -i %s rm -f %s", $containerName, $pskFile);
|
||||
self::executeServerCommand($serverData, $cmd3, true);
|
||||
|
||||
// 4. Persist to wg0.conf (append)
|
||||
// 4. Persist to config file (append)
|
||||
$peerBlock = "\n[Peer]\n";
|
||||
$peerBlock .= "PublicKey = {$publicKey}\n";
|
||||
$peerBlock .= "PresharedKey = {$presharedKey}\n";
|
||||
$peerBlock .= "AllowedIPs = {$clientIP}/32\n";
|
||||
|
||||
$escapedBlock = addslashes($peerBlock);
|
||||
$configFile = (stripos($containerName, 'awg2') !== false || $protocolSlug === 'awg2') ? 'awg0.conf' : 'wg0.conf';
|
||||
$cmd4 = sprintf("docker exec -i %s sh -c 'echo \"%s\" >> %s/%s'", $containerName, $escapedBlock, $configDir, $configFile);
|
||||
self::executeServerCommand($serverData, $cmd4, true);
|
||||
|
||||
@@ -1180,7 +1253,7 @@ class VpnClient
|
||||
|
||||
// 6. CRITICAL: Reload WG interface to apply AWG obfuscation params
|
||||
// Without this, the interface uses standard WireGuard without Jc/S1/S2/H1-H4
|
||||
$cmd5 = sprintf("docker exec -i %s sh -c 'ip link del wg0 2>/dev/null || true; wg-quick up %s/%s 2>&1'", $containerName, $configDir, $configFile);
|
||||
$cmd5 = sprintf("docker exec -i %s sh -c 'ip link del %s 2>/dev/null || true; %s up %s/%s 2>&1'", $containerName, $ifaceName, $wgQuickTool, $configDir, $configFile);
|
||||
self::executeServerCommand($serverData, $cmd5, true);
|
||||
}
|
||||
|
||||
@@ -1467,16 +1540,25 @@ class VpnClient
|
||||
{
|
||||
$containerName = $serverData['container_name'];
|
||||
$protocolSlug = (string) ($serverData['install_protocol'] ?? '');
|
||||
// Для AWG2 конфигурация внутри контейнера находится в /opt/amnezia/awg/
|
||||
$configDir = '/opt/amnezia/awg'; // Внутри контейнера всегда /opt/amnezia/awg
|
||||
// Config dir inside container is always /opt/amnezia/awg
|
||||
$configDir = '/opt/amnezia/awg';
|
||||
|
||||
// Determine config filename
|
||||
$configFile = (stripos($containerName, 'awg2') !== false || $protocolSlug === 'awg2') ? 'awg0.conf' : 'wg0.conf';
|
||||
// AWG2: try awg0.conf first (standard), fall back to wg0.conf (legacy panel installs)
|
||||
$isAwg2 = (stripos($containerName, 'awg2') !== false || $protocolSlug === 'awg2');
|
||||
$configFile = $isAwg2 ? 'awg0.conf' : 'wg0.conf';
|
||||
$testConf = trim(self::executeServerCommand($serverData, "docker exec -i {$containerName} cat {$configDir}/{$configFile} 2>/dev/null", true));
|
||||
if ($isAwg2 && ($testConf === '' || strpos($testConf, '[Interface]') === false)) {
|
||||
$configFile = 'wg0.conf';
|
||||
}
|
||||
$ifaceName = str_replace('.conf', '', $configFile);
|
||||
$wgTool = $isAwg2 ? 'awg' : 'wg';
|
||||
|
||||
// First, remove using wg command (live removal)
|
||||
// First, remove using wg/awg command (live removal)
|
||||
$removeCmd = sprintf(
|
||||
"docker exec -i %s wg set wg0 peer %s remove",
|
||||
"docker exec -i %s %s set %s peer %s remove",
|
||||
$containerName,
|
||||
$wgTool,
|
||||
$ifaceName,
|
||||
escapeshellarg($publicKey)
|
||||
);
|
||||
|
||||
@@ -1503,7 +1585,8 @@ class VpnClient
|
||||
self::executeServerCommand($serverData, $writeCmd, true);
|
||||
|
||||
// Save config
|
||||
$saveCmd = sprintf("docker exec -i %s wg-quick save wg0", $containerName);
|
||||
$wgQuickTool = $isAwg2 ? 'awg-quick' : 'wg-quick';
|
||||
$saveCmd = sprintf("docker exec -i %s %s save %s", $containerName, $wgQuickTool, $ifaceName);
|
||||
self::executeServerCommand($serverData, $saveCmd, true);
|
||||
|
||||
// Remove from clientsTable
|
||||
|
||||
@@ -51,7 +51,7 @@ docker build --no-cache -t amnezia-awg2 /opt/amnezia/awg2/src
|
||||
|
||||
EXISTING=$(docker ps -aq -f "name=$CONTAINER_NAME" 2>/dev/null | head -1)
|
||||
if [ -z "$EXISTING" ]; then
|
||||
docker run -d --name "$CONTAINER_NAME" --restart always --cap-add=NET_ADMIN --device /dev/net/tun -p "${VPN_PORT}:${VPN_PORT}/udp" -v /opt/amnezia/awg2:/opt/amnezia/awg amnezia-awg2 sh -c "while [ ! -f /opt/amnezia/awg/wg0.conf ]; do sleep 1; done; WG_QUICK_USERSPACE_IMPLEMENTATION=amneziawg-go awg-quick up /opt/amnezia/awg/wg0.conf && sleep infinity"
|
||||
docker run -d --name "$CONTAINER_NAME" --restart always --cap-add=NET_ADMIN --device /dev/net/tun -p "${VPN_PORT}:${VPN_PORT}/udp" -v /opt/amnezia/awg2:/opt/amnezia/awg amnezia-awg2 sh -c "while [ ! -f /opt/amnezia/awg/awg0.conf ]; do sleep 1; done; WG_QUICK_USERSPACE_IMPLEMENTATION=amneziawg-go awg-quick up /opt/amnezia/awg/awg0.conf && sleep infinity"
|
||||
sleep 2
|
||||
else
|
||||
STATUS=$(docker inspect --format="{{.State.Status}}" "$CONTAINER_NAME" 2>/dev/null || echo "")
|
||||
@@ -59,12 +59,28 @@ else
|
||||
docker start "$CONTAINER_NAME" >/dev/null 2>&1 || true
|
||||
fi
|
||||
fi
|
||||
# Check for existing config: first on host, then inside container (native Amnezia app installs)
|
||||
CONF_FILE="/opt/amnezia/awg2/awg0.conf"
|
||||
if [ ! -f "$CONF_FILE" ]; then
|
||||
# Try to extract config from inside the container (native Amnezia app stores config without volume mount)
|
||||
CONTAINER_CONF=$(docker exec "$CONTAINER_NAME" cat /opt/amnezia/awg/awg0.conf 2>/dev/null || true)
|
||||
if [ -n "$CONTAINER_CONF" ] && echo "$CONTAINER_CONF" | grep -q "\\[Interface\\]"; then
|
||||
mkdir -p /opt/amnezia/awg2
|
||||
echo "$CONTAINER_CONF" > /opt/amnezia/awg2/awg0.conf
|
||||
# Also extract keys if available
|
||||
docker exec "$CONTAINER_NAME" cat /opt/amnezia/awg/wireguard_server_private_key.key > /opt/amnezia/awg2/wireguard_server_private_key.key 2>/dev/null || true
|
||||
docker exec "$CONTAINER_NAME" cat /opt/amnezia/awg/wireguard_server_public_key.key > /opt/amnezia/awg2/wireguard_server_public_key.key 2>/dev/null || true
|
||||
docker exec "$CONTAINER_NAME" cat /opt/amnezia/awg/wireguard_psk.key > /opt/amnezia/awg2/wireguard_psk.key 2>/dev/null || true
|
||||
docker exec "$CONTAINER_NAME" cat /opt/amnezia/awg/clientsTable > /opt/amnezia/awg2/clientsTable 2>/dev/null || true
|
||||
echo "Extracted existing config from container"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -f /opt/amnezia/awg2/wg0.conf ]; then
|
||||
PORT=$(grep -E "^ListenPort" /opt/amnezia/awg2/wg0.conf | cut -d= -f2 | tr -d "[:space:]")
|
||||
if [ -f /opt/amnezia/awg2/awg0.conf ]; then
|
||||
PORT=$(grep -E "^ListenPort" /opt/amnezia/awg2/awg0.conf | cut -d= -f2 | tr -d "[:space:]")
|
||||
PSK=$(cat /opt/amnezia/awg2/wireguard_psk.key 2>/dev/null || true)
|
||||
if [ -z "$PSK" ]; then
|
||||
PSK=$(grep -E "^PresharedKey" /opt/amnezia/awg2/wg0.conf | cut -d= -f2 | tr -d "[:space:]")
|
||||
PSK=$(grep -E "^PresharedKey" /opt/amnezia/awg2/awg0.conf | head -1 | cut -d= -f2 | tr -d "[:space:]")
|
||||
fi
|
||||
PUBKEY=$(cat /opt/amnezia/awg2/wireguard_server_public_key.key 2>/dev/null || true)
|
||||
if [ -z "$PUBKEY" ]; then
|
||||
@@ -83,7 +99,7 @@ if [ -f /opt/amnezia/awg2/wg0.conf ]; then
|
||||
echo "Server Host: $EXTERNAL_IP"
|
||||
|
||||
for P in Jc Jmin Jmax S1 S2 S3 S4 H1 H2 H3 H4 I1 I2 I3 I4 I5; do
|
||||
VAL=$(sed -n -E "s/^[[:space:]]*$P[[:space:]]*=[[:space:]]*//p" /opt/amnezia/awg2/wg0.conf | head -1 | tr -d "\r")
|
||||
VAL=$(sed -n -E "s/^[[:space:]]*$P[[:space:]]*=[[:space:]]*//p" /opt/amnezia/awg2/awg0.conf | head -1 | tr -d "\\r")
|
||||
if [ -n "$VAL" ] || [[ "$P" =~ ^I[2-5]$ ]]; then echo "Variable: $P=$VAL"; fi
|
||||
done
|
||||
echo "Variable: dns_servers=1.1.1.1, 1.0.0.1"
|
||||
@@ -130,7 +146,7 @@ echo "H4 = $H4_VAL"
|
||||
echo "I1 = $I1_VAL"
|
||||
echo "PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE"
|
||||
echo "PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE"
|
||||
} > /opt/amnezia/awg2/wg0.conf
|
||||
} > /opt/amnezia/awg2/awg0.conf
|
||||
|
||||
echo "$PRIVATE_KEY" > /opt/amnezia/awg2/wireguard_server_private_key.key
|
||||
echo "$PUBLIC_KEY" > /opt/amnezia/awg2/wireguard_server_public_key.key
|
||||
|
||||
@@ -58,8 +58,10 @@
|
||||
<h3 class="font-bold mb-4">{{ t('servers.server_info') }}</h3>
|
||||
<dl class="space-y-2">
|
||||
<div><dt class="text-sm text-gray-600">{{ t('common.status') }}</dt><dd><span class="px-2 py-1 bg-green-100 text-green-800 rounded text-sm">{{ server.status }}</span></dd></div>
|
||||
{% if server_protocols|length <= 1 %}
|
||||
<div><dt class="text-sm text-gray-600">VPN Port</dt><dd>{{ server.vpn_port }}</dd></div>
|
||||
<div><dt class="text-sm text-gray-600">Subnet</dt><dd>{{ server.vpn_subnet }}</dd></div>
|
||||
{% endif %}
|
||||
|
||||
</dl>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user