From 08e0a3499ca17295d14f600a61f8865f44f8ad90 Mon Sep 17 00:00:00 2001 From: infosave2007 Date: Mon, 20 Apr 2026 18:25:35 +0300 Subject: [PATCH] feat: update AWG2 configuration handling in InstallProtocolManager and VpnClient --- inc/InstallProtocolManager.php | 61 ++++++++++++++++++++++++---------- inc/VpnClient.php | 40 ++++++++++++++++------ 2 files changed, 73 insertions(+), 28 deletions(-) diff --git a/inc/InstallProtocolManager.php b/inc/InstallProtocolManager.php index e34c1a3..aef481e 100644 --- a/inc/InstallProtocolManager.php +++ b/inc/InstallProtocolManager.php @@ -431,6 +431,12 @@ class InstallProtocolManager $containerFilter = escapeshellarg('^' . $containerName . '$'); $containerArg = escapeshellarg($containerName); + // Для AWG2 конфигурация внутри контейнера находится в /opt/amnezia/awg/awg0.conf + // На хосте может быть /opt/amnezia/awg2/wg0.conf (монтируется как /opt/amnezia/awg внутри контейнера) + $isAwg2 = (stripos($containerName, 'awg2') !== false || ($protocol['slug'] ?? '') === 'awg2'); + $configDir = $isAwg2 ? '/opt/amnezia/awg' : '/opt/amnezia/awg'; + $configFile = $isAwg2 ? 'awg0.conf' : 'wg0.conf'; + $containerListRaw = trim($server->executeCommand("docker ps -a --filter name={$containerFilter} --format '{{.Names}}'", true)); if ($containerListRaw === '') { return [ @@ -460,11 +466,13 @@ class InstallProtocolManager $containerState = trim($server->executeCommand("docker inspect --format '{{.State.Status}}' {$containerArg}", true)); - $wgConfig = $server->executeCommand("docker exec -i {$containerArg} cat /opt/amnezia/awg/wg0.conf 2>/dev/null", true); + // Для AWG2 проверяем оба возможных имени файла конфигурации + $configFile = ($protocol['slug'] ?? '') === 'awg2' ? 'awg0.conf' : 'wg0.conf'; + $wgConfig = $server->executeCommand("docker exec -i {$containerArg} cat {$configDir}/{$configFile} 2>/dev/null", true); if (trim($wgConfig) === '') { return [ 'status' => 'partial', - 'message' => 'Контейнер найден, но конфигурация wg0.conf отсутствует', + 'message' => "Контейнер найден, но конфигурация {$configFile} отсутствует", 'details' => [ 'container_name' => $containerName, 'container_status' => $containerState, @@ -484,8 +492,8 @@ class InstallProtocolManager ]; } - $publicKey = trim($server->executeCommand("docker exec -i {$containerArg} cat /opt/amnezia/awg/wireguard_server_public_key.key 2>/dev/null", true)); - $presharedKey = trim($server->executeCommand("docker exec -i {$containerArg} cat /opt/amnezia/awg/wireguard_psk.key 2>/dev/null", true)); + $publicKey = trim($server->executeCommand("docker exec -i {$containerArg} cat {$configDir}/wireguard_server_public_key.key 2>/dev/null", true)); + $presharedKey = trim($server->executeCommand("docker exec -i {$containerArg} cat {$configDir}/wireguard_psk.key 2>/dev/null", true)); if ($publicKey === '' || $presharedKey === '') { return [ @@ -498,7 +506,7 @@ class InstallProtocolManager ]; } - $clientsRaw = $server->executeCommand("docker exec -i {$containerArg} cat /opt/amnezia/awg/clientsTable 2>/dev/null", true); + $clientsRaw = $server->executeCommand("docker exec -i {$containerArg} cat {$configDir}/clientsTable 2>/dev/null", true); $clients = json_decode(trim($clientsRaw), true); $clientsCount = is_array($clients) ? count($clients) : 0; @@ -524,10 +532,16 @@ 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 внутри контейнера) + $isAwg2 = (stripos($containerName, 'awg2') !== false || ($protocol['slug'] ?? '') === 'awg2'); + $configDir = '/opt/amnezia/awg'; // Внутри контейнера всегда /opt/amnezia/awg + $configFile = $isAwg2 ? 'awg0.conf' : 'wg0.conf'; + // Try to ensure container is running and wg is up $server->executeCommand("docker start {$containerArg} 2>/dev/null || true", true); - $server->executeCommand("docker exec -i {$containerArg} wg-quick down /opt/amnezia/awg/wg0.conf 2>/dev/null || true", true); - $server->executeCommand("docker exec -i {$containerArg} wg-quick up /opt/amnezia/awg/wg0.conf 2>/dev/null || true", true); + $server->executeCommand("docker exec -i {$containerArg} wg-quick down {$configDir}/{$configFile} 2>/dev/null || true", true); + $server->executeCommand("docker exec -i {$containerArg} wg-quick up {$configDir}/{$configFile} 2>/dev/null || true", true); $pdo = DB::conn(); $stmt = $pdo->prepare(' @@ -553,9 +567,12 @@ class InstallProtocolManager $server->refresh(); $serverData = $server->getData(); - // Import existing peers from wg0.conf into database as disabled clients - $wgConfig = $server->executeCommand("docker exec -i {$containerArg} cat /opt/amnezia/awg/wg0.conf 2>/dev/null", true); - $tableRaw = $server->executeCommand("docker exec -i {$containerArg} cat /opt/amnezia/awg/clientsTable 2>/dev/null", true); + // Import existing peers from config into database as disabled clients + $serverId = $server->getId(); + Logger::appendInstall($serverId, "Restore: configDir={$configDir}, configFile={$configFile}, containerArg={$containerArg}"); + $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); + Logger::appendInstall($serverId, "Restore: wgConfig length=" . strlen($wgConfig) . ", tableRaw length=" . strlen($tableRaw)); $clientsTable = json_decode(trim($tableRaw), true); $nameByPub = []; if (is_array($clientsTable)) { @@ -569,6 +586,7 @@ class InstallProtocolManager } $restored = 0; $pid = self::resolveProtocolId($protocol); + Logger::appendInstall($serverId, "Restore: protocol_id={$pid}, wgConfig empty=" . (trim($wgConfig) === '' ? 'yes' : 'no')); if (trim($wgConfig) !== '') { $pattern = '/\[Peer\][^\[]*?PublicKey\s*=\s*(.+?)\s*[\r\n]+[\s\S]*?AllowedIPs\s*=\s*(.+?)(?:\r?\n|$)/'; if (preg_match_all($pattern, $wgConfig, $matches, PREG_SET_ORDER)) { @@ -611,6 +629,7 @@ class InstallProtocolManager } } + Logger::appendInstall($serverId, "Restore: finished, restored={$restored}"); return [ 'success' => true, 'mode' => 'restore', @@ -1836,10 +1855,13 @@ class InstallProtocolManager return; } - $containerName = $server->getData()['container_name'] ?? 'amnezia-awg'; - - // Read existing config - $conf = $server->executeCommand("docker exec -i $containerName cat /opt/amnezia/awg/wg0.conf", true); + $serverData = $server->getData(); + $containerName = $serverData['container_name'] ?? 'amnezia-awg'; + // Для AWG2 конфигурация внутри контейнера находится в /opt/amnezia/awg/awg0.conf + $isAwg2 = (stripos($containerName, 'awg2') !== false || ($protocol['slug'] ?? '') === 'awg2'); + $configDir = '/opt/amnezia/awg'; // Внутри контейнера всегда /opt/amnezia/awg + $configFile = $isAwg2 ? 'awg0.conf' : 'wg0.conf'; + $conf = $server->executeCommand("docker exec -i $containerName cat {$configDir}/{$configFile}", true); if (!$conf) return; @@ -1869,7 +1891,7 @@ class InstallProtocolManager Logger::appendInstall($serverId, "Syncing $count existing clients to server config"); $conf .= $newPeersBlock; $escaped = addslashes($conf); - $server->executeCommand("docker exec -i $containerName sh -c 'echo \"$escaped\" > /opt/amnezia/awg/wg0.conf'", true); + $server->executeCommand("docker exec -i $containerName sh -c 'echo \"$escaped\" > {$configDir}/{$configFile}'", true); // Reload interface $server->executeCommand("docker exec -i $containerName wg-quick down wg0 || true", true); @@ -2185,9 +2207,12 @@ class InstallProtocolManager $serverData = $server->getData(); $pid = self::resolveProtocolId($protocol); - // Read wg0.conf and clientsTable - $wgConfig = $server->executeCommand("docker exec -i {$containerArg} cat /opt/amnezia/awg/wg0.conf 2>/dev/null", true); - $tableRaw = $server->executeCommand("docker exec -i {$containerArg} cat /opt/amnezia/awg/clientsTable 2>/dev/null", true); + // Для AWG2 конфигурация внутри контейнера находится в /opt/amnezia/awg/awg0.conf + $isAwg2 = (stripos($containerName, 'awg2') !== false || ($protocol['slug'] ?? '') === 'awg2'); + $configDir = '/opt/amnezia/awg'; // Внутри контейнера всегда /opt/amnezia/awg + $configFile = $isAwg2 ? 'awg0.conf' : '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); // Build name lookup diff --git a/inc/VpnClient.php b/inc/VpnClient.php index 705a659..7d8f416 100644 --- a/inc/VpnClient.php +++ b/inc/VpnClient.php @@ -1077,6 +1077,11 @@ class VpnClient public static function addClientToServer(array $serverData, string $publicKey, string $clientIP): void { $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 + $configFile = $isAwg2 ? 'awg0.conf' : 'wg0.conf'; $presharedKey = $serverData['preshared_key']; $publicKey = trim($publicKey); @@ -1111,7 +1116,8 @@ class VpnClient $peerBlock .= "AllowedIPs = {$clientIP}/32\n"; $escapedBlock = addslashes($peerBlock); - $cmd4 = sprintf("docker exec -i %s sh -c 'echo \"%s\" >> /opt/amnezia/awg/wg0.conf'", $containerName, $escapedBlock); + $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); // 5. Update clientsTable @@ -1119,7 +1125,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 /opt/amnezia/awg/wg0.conf 2>&1'", $containerName); + $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); self::executeServerCommand($serverData, $cmd5, true); } @@ -1129,9 +1135,12 @@ class VpnClient private static function updateClientsTable(array $serverData, string $publicKey, string $name): void { $containerName = $serverData['container_name']; + $protocolSlug = (string) ($serverData['install_protocol'] ?? ''); + // Для AWG2 конфигурация внутри контейнера находится в /opt/amnezia/awg/ + $configDir = '/opt/amnezia/awg'; // Внутри контейнера всегда /opt/amnezia/awg // Read current table - $cmd = sprintf("docker exec -i %s cat /opt/amnezia/awg/clientsTable 2>/dev/null", $containerName); + $cmd = sprintf("docker exec -i %s cat %s/clientsTable 2>/dev/null", $containerName, $configDir); $tableJson = self::executeServerCommand($serverData, $cmd, true); $table = json_decode(trim($tableJson), true); @@ -1151,7 +1160,7 @@ class VpnClient // Save back $newTableJson = json_encode($table, JSON_PRETTY_PRINT); $escaped = addslashes($newTableJson); - $updateCmd = sprintf("docker exec -i %s sh -c 'echo \"%s\" > /opt/amnezia/awg/clientsTable'", $containerName, $escaped); + $updateCmd = sprintf("docker exec -i %s sh -c 'echo \"%s\" > %s/clientsTable'", $containerName, $escaped, $configDir); self::executeServerCommand($serverData, $updateCmd, true); } @@ -1378,6 +1387,12 @@ class VpnClient private static function removeClientFromServer(array $serverData, string $publicKey): void { $containerName = $serverData['container_name']; + $protocolSlug = (string) ($serverData['install_protocol'] ?? ''); + // Для AWG2 конфигурация внутри контейнера находится в /opt/amnezia/awg/ + $configDir = '/opt/amnezia/awg'; // Внутри контейнера всегда /opt/amnezia/awg + + // Determine config filename + $configFile = (stripos($containerName, 'awg2') !== false || $protocolSlug === 'awg2') ? 'awg0.conf' : 'wg0.conf'; // First, remove using wg command (live removal) $removeCmd = sprintf( @@ -1388,9 +1403,9 @@ class VpnClient self::executeServerCommand($serverData, $removeCmd, true); - // Then remove from wg0.conf file to make it persistent + // Then remove from config file to make it persistent // Use a more reliable method: read, filter, write - $readCmd = sprintf("docker exec -i %s cat /opt/amnezia/awg/wg0.conf", $containerName); + $readCmd = sprintf("docker exec -i %s cat %s/%s", $containerName, $configDir, $configFile); $config = self::executeServerCommand($serverData, $readCmd, true); // Parse and remove the peer section @@ -1399,9 +1414,11 @@ class VpnClient // Write back to file $escapedConfig = str_replace("'", "'\\''", $newConfig); $writeCmd = sprintf( - "docker exec -i %s sh -c 'echo '\''%s'\'' > /opt/amnezia/awg/wg0.conf'", + "docker exec -i %s sh -c 'echo '\''%s'\'' > %s/%s'", $containerName, - $escapedConfig + $escapedConfig, + $configDir, + $configFile ); self::executeServerCommand($serverData, $writeCmd, true); @@ -1466,9 +1483,12 @@ class VpnClient private static function removeFromClientsTable(array $serverData, string $publicKey): void { $containerName = $serverData['container_name']; + $protocolSlug = (string) ($serverData['install_protocol'] ?? ''); + // Для AWG2 конфигурация внутри контейнера находится в /opt/amnezia/awg/ + $configDir = '/opt/amnezia/awg'; // Внутри контейнера всегда /opt/amnezia/awg // Read current table - $cmd = sprintf("docker exec -i %s cat /opt/amnezia/awg/clientsTable 2>/dev/null", $containerName); + $cmd = sprintf("docker exec -i %s cat %s/clientsTable 2>/dev/null", $containerName, $configDir); $tableJson = self::executeServerCommand($serverData, $cmd, true); $table = json_decode(trim($tableJson), true); @@ -1487,7 +1507,7 @@ class VpnClient // Save back $newTableJson = json_encode($table, JSON_PRETTY_PRINT); $escaped = addslashes($newTableJson); - $updateCmd = sprintf("docker exec -i %s sh -c 'echo \"%s\" > /opt/amnezia/awg/clientsTable'", $containerName, $escaped); + $updateCmd = sprintf("docker exec -i %s sh -c 'echo \"%s\" > %s/clientsTable'", $containerName, $escaped, $configDir); self::executeServerCommand($serverData, $updateCmd, true); }