diff --git a/inc/VpnClient.php b/inc/VpnClient.php index 21bdad7..d94d093 100644 --- a/inc/VpnClient.php +++ b/inc/VpnClient.php @@ -799,26 +799,33 @@ class VpnClient $containerName = $serverData['container_name']; $protocolSlug = (string) ($serverData['install_protocol'] ?? ''); $isAwg2 = (stripos($containerName, 'awg2') !== false || $protocolSlug === 'awg2'); + // The amneziawg-go image ships `awg` and a `wg -> awg` symlink, so either + // tool works there. Use `awg` for AWG2 and `wg` otherwise. $wgTool = $isAwg2 ? 'awg' : 'wg'; - $cmd = sprintf( - "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), + // Inner script that runs inside the container shell. Generates a private + // key, derives the public key and prints them separated by a "---" marker. + $script = sprintf( + '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"', $wgTool, $wgTool ); - $escaped = escapeshellarg($cmd); - $sshCmd = 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", - escapeshellarg($serverData['password']), - $serverData['port'], - $serverData['username'], - $serverData['host'], - $escaped + $cmd = sprintf( + 'docker exec -i %s sh -lc %s', + escapeshellarg($containerName), + escapeshellarg($script) ); - $out = (string) shell_exec($sshCmd); + // Route the command through VpnServer::executeCommand so that SSH key + // authentication and automatic docker sudo detection are handled the same + // way as every other remote operation. The previous implementation built + // its own password-only SSH command (PubkeyAuthentication=no, no sudo), + // which failed on key-based servers and on hosts where docker needs sudo, + // producing the "Failed to generate client keys" error (issue #50). + $server = new VpnServer((int) $serverData['id']); + $out = (string) $server->executeCommand($cmd); // null sudo => auto-detect for docker + $parts = explode("---", trim($out)); if (count($parts) < 2) { @@ -860,8 +867,11 @@ class VpnClient try { $containerName = $serverData['container_name'] ?? 'amnezia-awg'; $server = new VpnServer($serverData['id']); + // AWG2 stores its config as awg0.conf (inside the container the path is + // always /opt/amnezia/awg/). Read awg0.conf first, then fall back to the + // legacy wg0.conf so externally created peers are still detected. $cmd = sprintf( - "docker exec %s cat /opt/amnezia/awg/wg0.conf 2>/dev/null", + "docker exec %s sh -c 'cat /opt/amnezia/awg/awg0.conf 2>/dev/null; cat /opt/amnezia/awg/wg0.conf 2>/dev/null'", escapeshellarg($containerName) ); $serverConfig = $server->executeCommand($cmd, true); diff --git a/migrations/070_fix_awg2_build_no_cache_timeout.sql b/migrations/070_fix_awg2_build_no_cache_timeout.sql new file mode 100644 index 0000000..bb671f1 --- /dev/null +++ b/migrations/070_fix_awg2_build_no_cache_timeout.sql @@ -0,0 +1,20 @@ +-- ===================================================================== +-- Migration 070: Speed up / stabilize AmneziaWG 2.0 (awg2) installation +-- +-- Issue #50: the first install of awg2 frequently failed with +-- "Invalid server response". Root cause: the install script ran +-- `docker build --no-cache` every time, forcing a full recompile of the +-- amneziawg-go Go sources on each attempt. That build can take several +-- minutes, exceeding the web request timeout, so the browser received a +-- truncated (non-JSON) response. On retry the work from the first attempt +-- had already produced the image/config, so it "magically" succeeded. +-- +-- Dropping `--no-cache` lets Docker reuse cached layers, making installs +-- (and especially retries) fast and idempotent. The sources are pinned via +-- `git clone --depth=1`, so a cached build is the desired behaviour. +-- ===================================================================== + +UPDATE protocols +SET install_script = REPLACE(install_script, 'docker build --no-cache -t amnezia-awg2', 'docker build -t amnezia-awg2') +WHERE slug = 'awg2' + AND install_script LIKE '%docker build --no-cache -t amnezia-awg2%'; diff --git a/public/index.php b/public/index.php index fbf9893..fa04c4e 100644 --- a/public/index.php +++ b/public/index.php @@ -568,6 +568,13 @@ Router::post('/servers/{id}/deploy', function ($params) { requireAuth(); header('Content-Type: application/json'); + // Some protocols (e.g. AmneziaWG 2.0 / awg2) build a Docker image from source + // on the remote host, which can take several minutes. Without lifting the PHP + // time limit the request is killed mid-build and the browser receives a + // truncated, non-JSON body shown as "Invalid server response" (issue #50). + @set_time_limit(0); + @ignore_user_abort(true); + $serverId = (int) $params['id']; $rawBody = file_get_contents('php://input'); $options = [];