diff --git a/.gitignore b/.gitignore index 60a7113..cbf409e 100644 --- a/.gitignore +++ b/.gitignore @@ -69,3 +69,7 @@ scripts/test_xray_install.sh scripts/test_online.php API_AWG_DOCS.md log.txt +scripts/bootstrap_awg_container.sh +scripts/fix_server_visibility.sh +scripts/remote_fix_client_create.sh +scripts/retest_client_api.sh diff --git a/inc/VpnClient.php b/inc/VpnClient.php index bf3ff85..6dae8a2 100644 --- a/inc/VpnClient.php +++ b/inc/VpnClient.php @@ -741,6 +741,8 @@ class VpnClient private static function syncServerKeysFromContainer(VpnServer $server, array $serverData): void { $containerName = $serverData['container_name'] ?? 'amnezia-awg'; + $protocolSlug = (string) ($serverData['install_protocol'] ?? ''); + $primaryConfigDir = $protocolSlug === 'awg2' ? '/opt/amnezia/awg2' : '/opt/amnezia/awg'; try { // Try to get public key from wg show @@ -755,23 +757,33 @@ class VpnClient // Prefer that file (stable) and fall back to parsing the first peer PSK from wg0.conf. $psk = ''; - $pskKeyFileCmd = "docker exec $containerName sh -c \"cat /opt/amnezia/awg/wireguard_psk.key 2>/dev/null || true\""; + $pskKeyFileCmd = "docker exec $containerName sh -c \"cat $primaryConfigDir/wireguard_psk.key 2>/dev/null || cat /opt/amnezia/awg/wireguard_psk.key 2>/dev/null || true\""; $psk = trim($server->executeCommand($pskKeyFileCmd, true)); if ($psk === '') { - $pskFromConfCmd = "docker exec $containerName sh -c \"grep -E '^[[:space:]]*PresharedKey[[:space:]]*=' /opt/amnezia/awg/wg0.conf 2>/dev/null | head -1 | sed -E 's/^[[:space:]]*PresharedKey[[:space:]]*=[[:space:]]*//' | tr -d '\\r'\" 2>/dev/null || true"; + $pskFromConfCmd = "docker exec $containerName sh -c \"grep -E '^[[:space:]]*PresharedKey[[:space:]]*=' $primaryConfigDir/wg0.conf 2>/dev/null | head -1 | sed -E 's/^[[:space:]]*PresharedKey[[:space:]]*=[[:space:]]*//' | tr -d '\\r'\" 2>/dev/null || true"; $psk = trim($server->executeCommand($pskFromConfCmd, true)); } + if ($psk === '' && $primaryConfigDir !== '/opt/amnezia/awg') { + $pskFromAwgConfCmd = "docker exec $containerName sh -c \"grep -E '^[[:space:]]*PresharedKey[[:space:]]*=' /opt/amnezia/awg/wg0.conf 2>/dev/null | head -1 | sed -E 's/^[[:space:]]*PresharedKey[[:space:]]*=[[:space:]]*//' | tr -d '\\r'\" 2>/dev/null || true"; + $psk = trim($server->executeCommand($pskFromAwgConfCmd, true)); + } + if ($psk === '') { $pskFromAltConfCmd = "docker exec $containerName sh -c \"grep -E '^[[:space:]]*PresharedKey[[:space:]]*=' /etc/wireguard/wg0.conf 2>/dev/null | head -1 | sed -E 's/^[[:space:]]*PresharedKey[[:space:]]*=[[:space:]]*//' | tr -d '\\r'\" 2>/dev/null || true"; $psk = trim($server->executeCommand($pskFromAltConfCmd, true)); } // Extract DNS from config - $dnsCmd = "docker exec $containerName sh -c \"grep -E '^DNS' /opt/amnezia/awg/wg0.conf 2>/dev/null | head -1 | cut -d= -f2 | tr -d '[:space:]'\" 2>/dev/null || echo ''"; + $dnsCmd = "docker exec $containerName sh -c \"grep -E '^DNS' $primaryConfigDir/wg0.conf 2>/dev/null | head -1 | cut -d= -f2 | tr -d '[:space:]'\" 2>/dev/null || echo ''"; $dns = trim($server->executeCommand($dnsCmd, true)); + if (empty($dns) && $primaryConfigDir !== '/opt/amnezia/awg') { + $dnsAwgCmd = "docker exec $containerName sh -c \"grep -E '^DNS' /opt/amnezia/awg/wg0.conf 2>/dev/null | head -1 | cut -d= -f2 | tr -d '[:space:]'\" 2>/dev/null || echo ''"; + $dns = trim($server->executeCommand($dnsAwgCmd, true)); + } + if (empty($dns)) { // Try alternative config location $dnsCmd2 = "docker exec $containerName sh -c \"grep -E '^DNS' /etc/wireguard/wg0.conf 2>/dev/null | head -1 | cut -d= -f2 | tr -d '[:space:]'\" 2>/dev/null || echo ''"; @@ -800,7 +812,10 @@ class VpnClient // Primary source: wg0.conf if (empty($awgParams)) { - $awgParams = self::extractAwgParamsFromWg0Conf($server, $containerName, '/opt/amnezia/awg/wg0.conf'); + $awgParams = self::extractAwgParamsFromWg0Conf($server, $containerName, $primaryConfigDir . '/wg0.conf'); + if (empty($awgParams) && $primaryConfigDir !== '/opt/amnezia/awg') { + $awgParams = self::extractAwgParamsFromWg0Conf($server, $containerName, '/opt/amnezia/awg/wg0.conf'); + } if (empty($awgParams)) { $awgParams = self::extractAwgParamsFromWg0Conf($server, $containerName, '/etc/wireguard/wg0.conf'); } @@ -954,21 +969,40 @@ class VpnClient */ private static function executeServerCommand(array $serverData, string $command, bool $sudo = false): string { - if ($sudo && strtolower($serverData['username']) !== 'root') { - $command = "echo '{$serverData['password']}' | sudo -S " . $command; + $needsSudo = $sudo && strtolower((string) ($serverData['username'] ?? '')) !== 'root'; + $baseCommand = $command; + + if ($needsSudo) { + // Suppress sudo prompt noise in stdout to keep parser output stable. + $command = "echo '{$serverData['password']}' | sudo -S -p '' " . $command; } - $escapedCommand = escapeshellarg($command); - $sshCommand = sprintf( - "sshpass -p '%s' ssh -p %d -q -o LogLevel=ERROR -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o PreferredAuthentications=password -o PubkeyAuthentication=no %s@%s %s 2>&1", - $serverData['password'], - $serverData['port'], - $serverData['username'], - $serverData['host'], - $escapedCommand - ); + $run = static function (string $cmd) use ($serverData): string { + $escapedCommand = escapeshellarg($cmd); + $sshCommand = sprintf( + "sshpass -p '%s' ssh -p %d -q -o LogLevel=ERROR -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o PreferredAuthentications=password -o PubkeyAuthentication=no %s@%s %s 2>&1", + $serverData['password'], + $serverData['port'], + $serverData['username'], + $serverData['host'], + $escapedCommand + ); - return shell_exec($sshCommand) ?? ''; + return shell_exec($sshCommand) ?? ''; + }; + + $output = $run($command); + + // If sudo auth fails but docker is available without sudo (docker group), retry without sudo. + if ( + $needsSudo + && preg_match('/(^|\\n)docker(\\s|$)/', ltrim($baseCommand)) + && preg_match('/incorrect password attempts|sorry, try again|a password is required/i', $output) + ) { + $output = $run($baseCommand); + } + + return $output; } /** diff --git a/inc/VpnServer.php b/inc/VpnServer.php index 54d3fa5..ababa46 100644 --- a/inc/VpnServer.php +++ b/inc/VpnServer.php @@ -427,6 +427,7 @@ class VpnServer */ public function executeCommand(string $command, bool $sudo = false): string { + $baseCommand = $command; $escapedCommand = escapeshellarg($command); // Determine auth method @@ -448,8 +449,10 @@ class VpnServer $escapedCommand ); } else { - if ($sudo && strtolower($this->data['username']) !== 'root') { - $command = "echo '{$this->data['password']}' | sudo -S " . $command; + $needsSudo = $sudo && strtolower((string) ($this->data['username'] ?? '')) !== 'root'; + if ($needsSudo) { + // Suppress sudo prompt text to keep command output machine-parseable. + $command = "echo '{$this->data['password']}' | sudo -S -p '' " . $command; $escapedCommand = escapeshellarg($command); } @@ -467,6 +470,26 @@ class VpnServer $output = shell_exec($sshCommand) ?? ''; + // If sudo auth fails but user can run docker without sudo, retry docker commands directly. + if ( + empty($this->data['ssh_key']) + && !empty($needsSudo) + && preg_match('/(^|\\n)docker(\\s|$)/', ltrim($baseCommand)) + && preg_match('/incorrect password attempts|sorry, try again|a password is required/i', $output) + ) { + $escapedBaseCommand = escapeshellarg($baseCommand); + $sshCommandNoSudo = sprintf( + "sshpass -p '%s' ssh -p %d %s %s@%s %s 2>&1", + $this->data['password'], + $this->data['port'], + $sshOptions, + $this->data['username'], + $this->data['host'], + $escapedBaseCommand + ); + $output = shell_exec($sshCommandNoSudo) ?? ''; + } + if ($keyFile && file_exists($keyFile)) { unlink($keyFile); }