diff --git a/.gitignore b/.gitignore index 4c5b09e..e1e4c28 100644 --- a/.gitignore +++ b/.gitignore @@ -74,3 +74,5 @@ scripts/fix_server_visibility.sh scripts/remote_fix_client_create.sh scripts/retest_client_api.sh scripts/awg2_retest_final.sh +migrations/065_aivpn_prebuilt.sql +migrations/069_fix_aivpn_prebuilt_binary.sql diff --git a/inc/InstallProtocolManager.php b/inc/InstallProtocolManager.php index c5c9576..e34c1a3 100644 --- a/inc/InstallProtocolManager.php +++ b/inc/InstallProtocolManager.php @@ -356,7 +356,7 @@ class InstallProtocolManager $awgParams = $result['awg_params'] ?? null; if (!is_array($awgParams)) { $flat = []; - foreach (['Jc', 'Jmin', 'Jmax', 'S1', 'S2', 'S3', 'S4', 'H1', 'H2', 'H3', 'H4'] as $k) { + foreach (['Jc', 'Jmin', 'Jmax', 'S1', 'S2', 'S3', 'S4', 'H1', 'H2', 'H3', 'H4', 'I1', 'I2', 'I3', 'I4', 'I5'] as $k) { if (array_key_exists($k, $result) && $result[$k] !== '' && $result[$k] !== null) { $flat[$k] = $result[$k]; } @@ -1037,7 +1037,7 @@ class InstallProtocolManager private static function parseWireGuardConfig(string $config): array { $lines = preg_split('/\r?\n/', $config); - $awgKeys = ['Jc', 'Jmin', 'Jmax', 'S1', 'S2', 'S3', 'S4', 'H1', 'H2', 'H3', 'H4']; + $awgKeys = ['Jc', 'Jmin', 'Jmax', 'S1', 'S2', 'S3', 'S4', 'H1', 'H2', 'H3', 'H4', 'I1', 'I2', 'I3', 'I4', 'I5']; $awgParams = []; $listenPort = null; @@ -1333,7 +1333,7 @@ class InstallProtocolManager $resolvedAwgParams = $res['awg_params'] ?? null; if (!is_array($resolvedAwgParams)) { $candidate = []; - foreach (['Jc', 'Jmin', 'Jmax', 'S1', 'S2', 'H1', 'H2', 'H3', 'H4'] as $k) { + foreach (['Jc', 'Jmin', 'Jmax', 'S1', 'S2', 'S3', 'S4', 'H1', 'H2', 'H3', 'H4', 'I1', 'I2', 'I3', 'I4', 'I5'] as $k) { if (array_key_exists($k, $res)) { $candidate[$k] = $res[$k]; } diff --git a/inc/QrUtil.php b/inc/QrUtil.php index 2eef62e..c720ec3 100644 --- a/inc/QrUtil.php +++ b/inc/QrUtil.php @@ -74,12 +74,36 @@ class QrUtil return self::urlsafe_b64_encode($header . $compressed); } - public static function encodeOldPayloadFromConf(string $confText): string + public static function encodeOldPayloadFromConf(string $confText, string $protocolSlug = ''): string { - $payload = self::buildOldEnvelopeFromConf($confText); + // For AWG2, use simple format: header + plain config text (like real Amnezia app) + // For other protocols, use the old JSON+compression format for backward compatibility + if ($protocolSlug === 'awg2') { + return self::encodeSimpleConf($confText); + } + + // Old format for backward compatibility with regular AWG + $payload = self::buildOldEnvelopeFromConf($confText, $protocolSlug); return self::encodeOldPayloadFromJson(json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)); } + /** + * Encode config in simple format used by real Amnezia app for AWG2: + * Header (8 bytes): version (4) + length (4) + config text + * No compression, no JSON wrapper + * + * Note: In real app, bytes 8-11 contain first 4 bytes of config text, + * not a separate field. So header is only 8 bytes. + */ + private static function encodeSimpleConf(string $confText): string + { + $version = 0x07C00100; // Amnezia magic version number + $length = strlen($confText); + + $header = pack('N2', $version, $length); + return self::urlsafe_b64_encode($header . $confText); + } + private static function resolveServerDescription(?string $endpointHost): string { $desc = (string) ($endpointHost ?? ''); @@ -181,11 +205,18 @@ class QrUtil 'H2' => null, 'H3' => null, 'H4' => null, + 'I1' => null, + 'I2' => null, + 'I3' => null, + 'I4' => null, + 'I5' => null, 'Jc' => null, 'Jmin' => null, 'Jmax' => null, 'S1' => null, 'S2' => null, + 'S3' => null, + 'S4' => null, ]; foreach (explode("\n", $conf) as $line) { $line = trim($line); @@ -203,11 +234,18 @@ class QrUtil 'H2' => (string) ($params['H2'] ?? ''), 'H3' => (string) ($params['H3'] ?? ''), 'H4' => (string) ($params['H4'] ?? ''), + 'I1' => (string) ($params['I1'] ?? ''), + 'I2' => (string) ($params['I2'] ?? ''), + 'I3' => (string) ($params['I3'] ?? ''), + 'I4' => (string) ($params['I4'] ?? ''), + 'I5' => (string) ($params['I5'] ?? ''), 'Jc' => (string) ($params['Jc'] ?? ''), 'Jmax' => (string) ($params['Jmax'] ?? ''), 'Jmin' => (string) ($params['Jmin'] ?? ''), 'S1' => (string) ($params['S1'] ?? ''), 'S2' => (string) ($params['S2'] ?? ''), + 'S3' => (string) ($params['S3'] ?? ''), + 'S4' => (string) ($params['S4'] ?? ''), 'allowed_ips' => $allowedIps ?: ['0.0.0.0/0', '::/0'], 'clientId' => $clientPubKey ?: '', 'client_ip' => preg_replace('/\/(\d{1,2})$/', '', (string) ($address ?? '')), @@ -249,7 +287,7 @@ class QrUtil return $vars; } - private static function buildOldEnvelopeFromConf(string $conf): array + private static function buildOldEnvelopeFromConf(string $conf, string $protocolSlug = ''): array { $endpointHost = null; $endpointPort = null; @@ -327,11 +365,18 @@ class QrUtil 'H2' => null, 'H3' => null, 'H4' => null, + 'I1' => null, + 'I2' => null, + 'I3' => null, + 'I4' => null, + 'I5' => null, 'Jc' => null, 'Jmin' => null, 'Jmax' => null, 'S1' => null, 'S2' => null, + 'S3' => null, + 'S4' => null, ]; foreach (explode("\n", $conf) as $line) { $line = trim($line); @@ -349,11 +394,18 @@ class QrUtil 'H2' => (string) ($params['H2'] ?? ''), 'H3' => (string) ($params['H3'] ?? ''), 'H4' => (string) ($params['H4'] ?? ''), + 'I1' => (string) ($params['I1'] ?? ''), + 'I2' => (string) ($params['I2'] ?? ''), + 'I3' => (string) ($params['I3'] ?? ''), + 'I4' => (string) ($params['I4'] ?? ''), + 'I5' => (string) ($params['I5'] ?? ''), 'Jc' => (string) ($params['Jc'] ?? ''), 'Jmax' => (string) ($params['Jmax'] ?? ''), 'Jmin' => (string) ($params['Jmin'] ?? ''), 'S1' => (string) ($params['S1'] ?? ''), 'S2' => (string) ($params['S2'] ?? ''), + 'S3' => (string) ($params['S3'] ?? ''), + 'S4' => (string) ($params['S4'] ?? ''), 'allowed_ips' => $allowedIps ?: ['0.0.0.0/0', '::/0'], 'clientId' => $clientPubKey ?: '', 'client_ip' => preg_replace('/\/(\d{1,2})$/', '', (string) ($address ?? '')), @@ -388,11 +440,19 @@ class QrUtil 'last_config' => json_encode($lastConfigObj, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT), 'port' => (string) $endpointPort, 'transport_proto' => 'udp', - ], - 'container' => 'amnezia-awg', + ] + ($protocolSlug === 'awg2' ? [ + 'I1' => (string) ($params['I1'] ?? ''), + 'I2' => (string) ($params['I2'] ?? ''), + 'I3' => (string) ($params['I3'] ?? ''), + 'I4' => (string) ($params['I4'] ?? ''), + 'I5' => (string) ($params['I5'] ?? ''), + 'S3' => (string) ($params['S3'] ?? ''), + 'S4' => (string) ($params['S4'] ?? ''), + ] : []), + 'container' => $protocolSlug === 'awg2' ? 'amnezia-awg2' : 'amnezia-awg', ], ], - 'defaultContainer' => 'amnezia-awg', + 'defaultContainer' => $protocolSlug === 'awg2' ? 'amnezia-awg2' : 'amnezia-awg', 'description' => $serverDesc, 'dns1' => $dns1, 'dns2' => $dns2, diff --git a/inc/VpnClient.php b/inc/VpnClient.php index abb7b51..705a659 100644 --- a/inc/VpnClient.php +++ b/inc/VpnClient.php @@ -142,26 +142,14 @@ class VpnClient } } + $defaultAwgParams = self::getAwgParamDefaults($slug); + // Add AWG parameters (use UPPERCASE keys internal logic) - foreach (['JC', 'JMIN', 'JMAX', 'S1', 'S2', 'S3', 'S4', 'H1', 'H2', 'H3', 'H4'] as $key) { + foreach (array_keys($defaultAwgParams) as $key) { if (isset($cleanAwgParams[$key])) { $vars[$key] = $cleanAwgParams[$key]; } else { - // Default values for AWG params (Fallback only) - $defaults = [ - 'JC' => 5, - 'JMIN' => 100, - 'JMAX' => 200, - 'S1' => 50, - 'S2' => 100, - 'S3' => 20, - 'S4' => 10, - 'H1' => 1, - 'H2' => 2, - 'H3' => 3, - 'H4' => 4, - ]; - $vars[$key] = $defaults[$key] ?? 0; + $vars[$key] = $defaultAwgParams[$key]; } } @@ -176,6 +164,11 @@ class VpnClient if (!isset($vars['Jmax']) && isset($vars['JMAX'])) { $vars['Jmax'] = (string) $vars['JMAX']; } + foreach (['S1', 'S2', 'S3', 'S4', 'H1', 'H2', 'H3', 'H4', 'I1', 'I2', 'I3', 'I4', 'I5'] as $key) { + if (!isset($vars[$key]) && isset($vars[strtoupper($key)])) { + $vars[$key] = (string) $vars[strtoupper($key)]; + } + } // Generate config from template if ($protoRow && !empty($protoRow['output_template'])) { @@ -190,12 +183,13 @@ class VpnClient $serverData['preshared_key'], $serverData['host'], $serverData['vpn_port'], - is_array($awgParams) ? $awgParams : [] + is_array($awgParams) ? $awgParams : [], + $slug ); } self::addClientToServer($serverData, $keys['public'], $clientIP); - $qrCode = self::generateQRCode($config); + $qrCode = self::generateQRCode($config, $slug); $priv = $keys['private']; $pub = $keys['public']; $psk = $serverData['preshared_key']; @@ -501,7 +495,7 @@ class VpnClient $vars['last_config_json'] = json_encode($decoded, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT); } - $qrCode = self::generateQRCode($config); + $qrCode = self::generateQRCode($config, $slug); $priv = ''; $pub = ''; @@ -636,7 +630,8 @@ class VpnClient $presharedKey, $serverData['host'], (int) $serverData['vpn_port'], - $awgParams + $awgParams, + (string) ($serverData['install_protocol'] ?? '') ); } @@ -654,7 +649,7 @@ class VpnClient $vars['last_config_json'] = json_encode($decoded, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT); } - $qrCode = $config !== '' ? self::generateQRCode($config) : ''; + $qrCode = $config !== '' ? self::generateQRCode($config, $serverData['install_protocol'] ?? '') : ''; $status = strtolower($clientData['status'] ?? 'active') === 'disabled' ? 'disabled' : 'active'; $expiresAt = $clientData['expires_at'] ?? null; @@ -784,12 +779,50 @@ class VpnClient /** * Auto-sync server keys from running container (for externally installed protocols) */ + private static function getAwgParamDefaults(string $protocolSlug = ''): array + { + if ($protocolSlug === 'awg2') { + return [ + 'JC' => 5, + 'JMIN' => 10, + 'JMAX' => 50, + 'S1' => 51, + 'S2' => 125, + 'S3' => 13, + 'S4' => 9, + 'H1' => '1443912531-1981073285', + 'H2' => '1984025557-2135018048', + 'H3' => '2145217268-2146643749', + 'H4' => '2146790761-2146860793', + 'I1' => '', + 'I2' => '', + 'I3' => '', + 'I4' => '', + 'I5' => '', + ]; + } + + return [ + 'JC' => 5, + 'JMIN' => 100, + 'JMAX' => 200, + 'S1' => 50, + 'S2' => 100, + 'S3' => 20, + 'S4' => 10, + 'H1' => 1, + 'H2' => 2, + 'H3' => 3, + 'H4' => 4, + ]; + } + private static function extractAwgParamsFromWg0Conf(VpnServer $server, string $containerName, string $confPath): array { $awgParams = []; $awgLinesCmd = sprintf( - "docker exec %s sh -c \"grep -E '^[[:space:]]*(Jc|Jmin|Jmax|S1|S2|S3|S4|H1|H2|H3|H4)[[:space:]]*=' %s 2>/dev/null || true\"", + "docker exec %s sh -c \"grep -E '^[[:space:]]*(Jc|Jmin|Jmax|S1|S2|S3|S4|H1|H2|H3|H4|I1|I2|I3|I4|I5)[[:space:]]*=' %s 2>/dev/null || true\"", escapeshellarg($containerName), escapeshellarg($confPath) ); @@ -800,9 +833,10 @@ class VpnClient if ($line === '') { continue; } - if (preg_match('/^(Jc|Jmin|Jmax|S1|S2|S3|S4|H1|H2|H3|H4)\s*=\s*(\d+)\s*$/i', $line, $m)) { + if (preg_match('/^(Jc|Jmin|Jmax|S1|S2|S3|S4|H1|H2|H3|H4|I1|I2|I3|I4|I5)\s*=\s*(.*)$/i', $line, $m)) { $k = strtoupper($m[1]); - $awgParams[$k] = (int) $m[2]; + $value = trim($m[2]); + $awgParams[$k] = ctype_digit($value) ? (int) $value : $value; } } @@ -911,10 +945,18 @@ class VpnClient // Legacy attempt: some builds print jc/jmin/... in `wg show` output. $wgShowCmd = "docker exec $containerName wg show wg0 2>/dev/null"; $wgOutput = (string) $server->executeCommand($wgShowCmd, true); - $paramNames = ['jc', 'jmin', 'jmax', 's1', 's2', 's3', 's4', 'h1', 'h2', 'h3', 'h4']; + $paramNames = ['jc', 'jmin', 'jmax', 's1', 's2', 's3', 's4', 'h1', 'h2', 'h3', 'h4', 'i1', 'i2', 'i3', 'i4', 'i5']; foreach ($paramNames as $param) { - if (preg_match('/^\s*' . preg_quote($param, '/') . ':\s*(\d+)/mi', $wgOutput, $matches)) { - $awgParams[strtoupper($param)] = (int) $matches[1]; + // For H1-H4 parameters, expect format like "1443912531-1981073285" (two values with dash) + // For other parameters, expect single integer value + if (in_array($param, ['h1', 'h2', 'h3', 'h4'], true)) { + if (preg_match('/^\s*' . preg_quote($param, '/') . ':\s*(\d+-\d+)/mi', $wgOutput, $matches)) { + $awgParams[strtoupper($param)] = $matches[1]; + } + } else { + if (preg_match('/^\s*' . preg_quote($param, '/') . ':\s*(\d+)/mi', $wgOutput, $matches)) { + $awgParams[strtoupper($param)] = (int) $matches[1]; + } } } @@ -962,24 +1004,60 @@ class VpnClient string $presharedKey, string $serverHost, int $serverPort, - array $awgParams + array $awgParams, + string $protocolSlug = '' ): string { + // Get default parameters for the protocol + $defaultParams = self::getAwgParamDefaults($protocolSlug); + + // Normalize $awgParams keys to uppercase for consistency + $normalizedAwgParams = []; + foreach ($awgParams as $k => $v) { + $normalizedAwgParams[strtoupper($k)] = $v; + } + + // Merge: use server params only if they have correct format, otherwise use defaults + // This is critical for H1-H4 which must have "value1-value2" format + $finalParams = $defaultParams; + foreach ($normalizedAwgParams as $key => $value) { + $upperKey = strtoupper($key); + + // For H1-H4 parameters, only use server value if it has the correct "value1-value2" format + if (in_array($upperKey, ['H1', 'H2', 'H3', 'H4'], true)) { + if (is_string($value) && preg_match('/^\d+-\d+$/', $value)) { + $finalParams[$upperKey] = $value; + } + // Otherwise keep the default value + } else { + // For other parameters, use server value if present + $finalParams[$upperKey] = $value; + } + } + $config = "[Interface]\n"; - $config .= "PrivateKey = {$privateKey}\n"; $config .= "Address = {$clientIP}/32\n"; $config .= "DNS = 1.1.1.1, 1.0.0.1\n"; + $config .= "PrivateKey = {$privateKey}\n"; - // Add AWG parameters - foreach (['Jc', 'Jmin', 'Jmax', 'S1', 'S2', 'S3', 'S4', 'H1', 'H2', 'H3', 'H4'] as $key) { - if (isset($awgParams[$key])) { - $config .= "{$key} = {$awgParams[$key]}\n"; - continue; + // Add AWG parameters (in the order used by Amnezia app) + // For awg2 include I1-I5, S3, S4; for regular awg only H1-H4, Jc, Jmin, Jmax, S1, S2 + // Order: Jc, Jmin, Jmax, S1, S2, S3, S4, H1, H2, H3, H4, I1, I2, I3, I4, I5 + $paramKeys = ['Jc', 'Jmin', 'Jmax', 'S1', 'S2', 'S3', 'S4', 'H1', 'H2', 'H3', 'H4']; + if ($protocolSlug === 'awg2') { + $paramKeys = array_merge($paramKeys, ['I1', 'I2', 'I3', 'I4', 'I5']); + } + + foreach ($paramKeys as $key) { + $value = null; + if (isset($finalParams[$key])) { + $value = $finalParams[$key]; + } elseif (isset($finalParams[strtoupper($key)])) { + $value = $finalParams[strtoupper($key)]; } - - // Accept uppercase keys too (JC/JMIN/JMAX/...) - $alt = strtoupper($key); - if (isset($awgParams[$alt])) { - $config .= "{$key} = {$awgParams[$alt]}\n"; + + // Always add parameter if it's defined (even if empty for I2-I5) + if ($value !== null) { + $config .= "{$key} = {$value}\n"; } } @@ -1122,7 +1200,7 @@ class VpnClient * Generate QR code for configuration using Amnezia format * Uses working QrUtil from /Users/oleg/Documents/amnezia */ - private static function generateQRCode(string $config): string + private static function generateQRCode(string $config, string $protocolSlug = ''): string { require_once __DIR__ . '/QrUtil.php'; @@ -1158,8 +1236,8 @@ class VpnClient } // Fallback for WireGuard / default - // Use old Amnezia format with Qt/QDataStream encoding - $payloadOld = QrUtil::encodeOldPayloadFromConf($config); + // Use old Amnezia format with Qt/QDataStream encoding, but pass protocol slug + $payloadOld = QrUtil::encodeOldPayloadFromConf($config, $protocolSlug); $dataUri = QrUtil::pngBase64($payloadOld); return $dataUri; } catch (Throwable $e) { @@ -1484,7 +1562,7 @@ class VpnClient // by duplicating them into canonical uppercase AWG keys. foreach ($awgParams as $k => $v) { $uk = strtoupper((string) $k); - if (in_array($uk, ['JC', 'JMIN', 'JMAX', 'S1', 'S2', 'S3', 'S4', 'H1', 'H2', 'H3', 'H4'], true) && !isset($awgParams[$uk])) { + if (in_array($uk, ['JC', 'JMIN', 'JMAX', 'S1', 'S2', 'S3', 'S4', 'H1', 'H2', 'H3', 'H4', 'I1', 'I2', 'I3', 'I4', 'I5'], true) && !isset($awgParams[$uk])) { $awgParams[$uk] = $v; } } @@ -1524,12 +1602,7 @@ class VpnClient } } - if (!isset($awgParams['S3'])) { - $awgParams['S3'] = 0; - } - if (!isset($awgParams['S4'])) { - $awgParams['S4'] = 0; - } + $awgParams = array_merge(self::getAwgParamDefaults($slug), $awgParams); // Still missing? Refuse to overwrite config with template defaults. foreach ($needKeys as $k) { @@ -1567,7 +1640,7 @@ class VpnClient 'dns_servers' => (string) ($serverData['dns_servers'] ?? '1.1.1.1, 1.0.0.1'), ]; - foreach (['JC', 'JMIN', 'JMAX', 'S1', 'S2', 'S3', 'S4', 'H1', 'H2', 'H3', 'H4'] as $key) { + foreach (array_keys(self::getAwgParamDefaults($slug)) as $key) { if (isset($awgParams[$key])) { $vars[$key] = $awgParams[$key]; } @@ -1582,6 +1655,11 @@ class VpnClient if (!isset($vars['Jmax']) && isset($vars['JMAX'])) { $vars['Jmax'] = (string) $vars['JMAX']; } + foreach (['S1', 'S2', 'S3', 'S4', 'H1', 'H2', 'H3', 'H4', 'I1', 'I2', 'I3', 'I4', 'I5'] as $key) { + if (!isset($vars[$key]) && isset($vars[strtoupper($key)])) { + $vars[$key] = (string) $vars[strtoupper($key)]; + } + } if ($protoRow && !empty($protoRow['output_template'])) { require_once __DIR__ . '/ProtocolService.php'; @@ -1594,11 +1672,12 @@ class VpnClient $presharedKeyForConfig, (string) ($serverData['host'] ?? ''), (int) ($serverData['vpn_port'] ?? 0), - $awgParams + $awgParams, + $slug ); } - $qrCode = self::generateQRCode($config); + $qrCode = self::generateQRCode($config, $slug); $pdo = DB::conn(); $stmt = $pdo->prepare('UPDATE vpn_clients SET config = ?, qr_code = ?, preshared_key = ? WHERE id = ?'); diff --git a/migrations/064_complete_awg2_original_params.sql b/migrations/064_complete_awg2_original_params.sql new file mode 100644 index 0000000..2d38b92 --- /dev/null +++ b/migrations/064_complete_awg2_original_params.sql @@ -0,0 +1,211 @@ +-- Complete AWG2 support with original Amnezia parameters, including I1-I5. + +UPDATE protocols +SET output_template = '[Interface] +Address = {{client_ip}}/32 +DNS = {{dns_servers}} +PrivateKey = {{private_key}} +Jc = {{Jc}} +Jmin = {{Jmin}} +Jmax = {{Jmax}} +S1 = {{S1}} +S2 = {{S2}} +S3 = {{S3}} +S4 = {{S4}} +H1 = {{H1}} +H2 = {{H2}} +H3 = {{H3}} +H4 = {{H4}} +I1 = {{I1}} +I2 = {{I2}} +I3 = {{I3}} +I4 = {{I4}} +I5 = {{I5}} + +[Peer] +PublicKey = {{server_public_key}} +PresharedKey = {{preshared_key}} +AllowedIPs = 0.0.0.0/0, ::/0 +Endpoint = {{server_host}}:{{server_port}} +PersistentKeepalive = 25', + install_script = '#!/bin/bash +set -euo pipefail + +CONTAINER_NAME="${SERVER_CONTAINER:-amnezia-awg2}" +PORT_RANGE_START=${PORT_RANGE_START:-30000} +PORT_RANGE_END=${PORT_RANGE_END:-65000} +VPN_PORT="${SERVER_PORT:-$((RANDOM % (PORT_RANGE_END - PORT_RANGE_START + 1) + PORT_RANGE_START))}" +MTU=${MTU:-1280} + +if ! command -v git &> /dev/null; then + apt-get update -qq && apt-get install -y -qq git >/dev/null 2>&1 +fi + +mkdir -p /opt/amnezia/awg2 + +if [ ! -d /opt/amnezia/awg2/src ]; then + git clone --depth=1 https://github.com/amnezia-vpn/amneziawg-go.git /opt/amnezia/awg2/src +fi + +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" + sleep 2 +else + STATUS=$(docker inspect --format="{{.State.Status}}" "$CONTAINER_NAME" 2>/dev/null || echo "") + if [ "$STATUS" != "running" ]; then + docker start "$CONTAINER_NAME" >/dev/null 2>&1 || true + 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:]") + 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:]") + fi + PUBKEY=$(cat /opt/amnezia/awg2/wireguard_server_public_key.key 2>/dev/null || true) + if [ -z "$PUBKEY" ]; then + PRIVKEY=$(cat /opt/amnezia/awg2/wireguard_server_private_key.key 2>/dev/null || true) + if [ -n "$PRIVKEY" ]; then + PUBKEY=$(echo "$PRIVKEY" | docker exec -i "$CONTAINER_NAME" wg pubkey) + fi + fi + + echo "Using existing AmneziaWG 2.0 configuration" + echo "Port: ${PORT:-$VPN_PORT}" + if [ -n "${PUBKEY:-}" ]; then echo "Server Public Key: $PUBKEY"; fi + if [ -n "${PSK:-}" ]; then echo "PresharedKey = $PSK"; fi + + EXTERNAL_IP=$(curl -s -4 ifconfig.me 2>/dev/null || curl -s -4 icanhazip.com 2>/dev/null || echo "YOUR_SERVER_IP") + 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") + 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" + exit 0 +fi + +PRIVATE_KEY=$(docker exec "$CONTAINER_NAME" wg genkey) +PUBLIC_KEY=$(echo "$PRIVATE_KEY" | docker exec -i "$CONTAINER_NAME" wg pubkey) +PRESHARED_KEY=$(docker exec "$CONTAINER_NAME" wg genpsk) + +JC=5 +JMIN=10 +JMAX=50 +S1_VAL=51 +S2_VAL=125 +S3_VAL=13 +S4_VAL=9 +H1_VAL="1443912531-1981073285" +H2_VAL="1984025557-2135018048" +H3_VAL="2145217268-2146643749" +H4_VAL="2146790761-2146860793" +I1_VAL="" +I2_VAL="" +I3_VAL="" +I4_VAL="" +I5_VAL="" + +{ +echo "[Interface]" +echo "PrivateKey = $PRIVATE_KEY" +echo "Address = 10.8.1.1/24" +echo "ListenPort = $VPN_PORT" +echo "Jc = $JC" +echo "Jmin = $JMIN" +echo "Jmax = $JMAX" +echo "S1 = $S1_VAL" +echo "S2 = $S2_VAL" +echo "S3 = $S3_VAL" +echo "S4 = $S4_VAL" +echo "H1 = $H1_VAL" +echo "H2 = $H2_VAL" +echo "H3 = $H3_VAL" +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 + +echo "$PRIVATE_KEY" > /opt/amnezia/awg2/wireguard_server_private_key.key +echo "$PUBLIC_KEY" > /opt/amnezia/awg2/wireguard_server_public_key.key +echo "$PRESHARED_KEY" > /opt/amnezia/awg2/wireguard_psk.key +echo "[]" > /opt/amnezia/awg2/clientsTable + +EXTERNAL_IP=$(curl -s -4 ifconfig.me 2>/dev/null || curl -s -4 icanhazip.com 2>/dev/null || echo "YOUR_SERVER_IP") + +echo "AmneziaWG 2.0 installed successfully" +echo "Port: $VPN_PORT" +echo "Server Public Key: $PUBLIC_KEY" +echo "PresharedKey = $PRESHARED_KEY" +echo "Server Host: $EXTERNAL_IP" +echo "Variable: Jc=$JC" +echo "Variable: Jmin=$JMIN" +echo "Variable: Jmax=$JMAX" +echo "Variable: S1=$S1_VAL" +echo "Variable: S2=$S2_VAL" +echo "Variable: S3=$S3_VAL" +echo "Variable: S4=$S4_VAL" +echo "Variable: H1=$H1_VAL" +echo "Variable: H2=$H2_VAL" +echo "Variable: H3=$H3_VAL" +echo "Variable: H4=$H4_VAL" +echo "Variable: I1=$I1_VAL" +echo "Variable: dns_servers=1.1.1.1, 1.0.0.1"' +WHERE slug = 'awg2'; + +INSERT INTO protocol_variables (protocol_id, variable_name, variable_type, default_value, description, required) +SELECT p.id, 'I1', 'text', '', 'Original AmneziaWG packet template I1', false +FROM protocols p WHERE p.slug = 'awg2' + AND NOT EXISTS (SELECT 1 FROM protocol_variables WHERE protocol_id = p.id AND variable_name = 'I1'); + +INSERT INTO protocol_variables (protocol_id, variable_name, variable_type, default_value, description, required) +SELECT p.id, 'I2', 'text', '', 'Original AmneziaWG packet template I2', false +FROM protocols p WHERE p.slug = 'awg2' + AND NOT EXISTS (SELECT 1 FROM protocol_variables WHERE protocol_id = p.id AND variable_name = 'I2'); + +INSERT INTO protocol_variables (protocol_id, variable_name, variable_type, default_value, description, required) +SELECT p.id, 'I3', 'text', '', 'Original AmneziaWG packet template I3', false +FROM protocols p WHERE p.slug = 'awg2' + AND NOT EXISTS (SELECT 1 FROM protocol_variables WHERE protocol_id = p.id AND variable_name = 'I3'); + +INSERT INTO protocol_variables (protocol_id, variable_name, variable_type, default_value, description, required) +SELECT p.id, 'I4', 'text', '', 'Original AmneziaWG packet template I4', false +FROM protocols p WHERE p.slug = 'awg2' + AND NOT EXISTS (SELECT 1 FROM protocol_variables WHERE protocol_id = p.id AND variable_name = 'I4'); + +INSERT INTO protocol_variables (protocol_id, variable_name, variable_type, default_value, description, required) +SELECT p.id, 'I5', 'text', '', 'Original AmneziaWG packet template I5', false +FROM protocols p WHERE p.slug = 'awg2' + AND NOT EXISTS (SELECT 1 FROM protocol_variables WHERE protocol_id = p.id AND variable_name = 'I5'); + +UPDATE protocol_variables pv +JOIN protocols p ON p.id = pv.protocol_id +SET pv.default_value = CASE pv.variable_name + WHEN 'Jc' THEN '5' + WHEN 'Jmin' THEN '10' + WHEN 'Jmax' THEN '50' + WHEN 'S1' THEN '51' + WHEN 'S2' THEN '125' + WHEN 'S3' THEN '13' + WHEN 'S4' THEN '9' + WHEN 'H1' THEN '1443912531-1981073285' + WHEN 'H2' THEN '1984025557-2135018048' + WHEN 'H3' THEN '2145217268-2146643749' + WHEN 'H4' THEN '2146790761-2146860793' + ELSE pv.default_value +END +WHERE p.slug = 'awg2' + AND pv.variable_name IN ('Jc', 'Jmin', 'Jmax', 'S1', 'S2', 'S3', 'S4', 'H1', 'H2', 'H3', 'H4'); + +-- Fix awg_params for all existing servers using awg2 protocol +-- Problem: H1-H4 parameters were stored with single values instead of "value1-value2" format +-- This was causing QR codes to be detected as "legacy" instead of proper AmneziaWG 2.0 format +UPDATE vpn_servers +SET awg_params = '{"JC":5,"JMIN":10,"JMAX":50,"S1":51,"S2":125,"S3":13,"S4":9,"H1":"1443912531-1981073285","H2":"1984025557-2135018048","H3":"2145217268-2146643749","H4":"2146790761-2146860793","I1":"","I2":"","I3":"","I4":"","I5":""}' +WHERE install_protocol = 'awg2'; \ No newline at end of file