From 0bc23e11db6c8af494ad24b3b76e08e2374781fd Mon Sep 17 00:00:00 2001 From: infosave2007 Date: Sat, 4 Apr 2026 13:59:37 +0300 Subject: [PATCH] feat: add AWG2 protocol support and enhance API documentation for protocol management --- .gitignore | 1 + API_EXAMPLES.md | 28 ++++++++++++++++++++++++ README.md | 9 ++++++-- inc/InstallProtocolManager.php | 32 ++++++++++++++++++++++++---- migrations/058_add_awg2_protocol.sql | 18 ++++++++-------- public/index.php | 8 +++++++ 6 files changed, 81 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index cbf409e..4c5b09e 100644 --- a/.gitignore +++ b/.gitignore @@ -73,3 +73,4 @@ scripts/bootstrap_awg_container.sh scripts/fix_server_visibility.sh scripts/remote_fix_client_create.sh scripts/retest_client_api.sh +scripts/awg2_retest_final.sh diff --git a/API_EXAMPLES.md b/API_EXAMPLES.md index d813884..cfda2e1 100644 --- a/API_EXAMPLES.md +++ b/API_EXAMPLES.md @@ -18,6 +18,34 @@ Response: } ``` +## Protocols + +### List Active Protocols (for JWT API clients) +```bash +curl -X GET http://localhost:8082/api/protocols/active \ + -H "Authorization: Bearer $TOKEN" +``` + +Example response: +```json +{ + "success": true, + "protocols": [ + {"id": 11, "slug": "awg2", "name": "AmneziaWG 2.0"}, + {"id": 13, "slug": "aivpn", "name": "AIVPN"}, + {"id": 12, "slug": "mtproxy", "name": "MTProxy (Telegram)"} + ] +} +``` + +### Install Protocol on Server +```bash +curl -X POST http://localhost:8082/api/servers/1/protocols/install \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"protocol_id":11}' +``` + ## Clients ### Create Client with QR Code diff --git a/README.md b/README.md index 8a9c206..a63bb07 100644 --- a/README.md +++ b/README.md @@ -36,9 +36,13 @@ cp .env.example .env docker compose up -d docker compose exec web composer install +# Ensure all SQL migrations are applied (safe to run repeatedly) +docker compose exec -T db sh -lc 'for f in /docker-entrypoint-initdb.d/*.sql; do mysql -uroot -p"$MYSQL_ROOT_PASSWORD" "$MYSQL_DATABASE" < "$f" || true; done' + # Or for older Docker Compose V1 docker-compose up -d docker-compose exec web composer install +docker-compose exec -T db sh -lc 'for f in /docker-entrypoint-initdb.d/*.sql; do mysql -uroot -p"$MYSQL_ROOT_PASSWORD" "$MYSQL_DATABASE" < "$f" || true; done' ``` Access: http://localhost:8082 @@ -54,7 +58,7 @@ DB_HOST=db DB_PORT=3306 DB_DATABASE=amnezia_panel DB_USERNAME=amnezia -DB_PASSWORD=amnezia123 +DB_PASSWORD=amnezia ADMIN_EMAIL=admin@amnez.ia ADMIN_PASSWORD=admin123 @@ -255,7 +259,8 @@ GET /api/servers/{id}/clients - List clients on server ### Protocols ``` -GET /api/protocols/active - List all available protocols (with IDs) +GET /api/protocols/active - List all available protocols (JWT-friendly, includes protocol IDs) +GET /api/protocols - Protocol management endpoint (requires session admin auth, not JWT) GET /api/servers/{id}/protocols - List installed protocols on server POST /api/servers/{id}/protocols/install - Install protocol ``` diff --git a/inc/InstallProtocolManager.php b/inc/InstallProtocolManager.php index 24c8975..a393b45 100644 --- a/inc/InstallProtocolManager.php +++ b/inc/InstallProtocolManager.php @@ -278,25 +278,46 @@ class InstallProtocolManager try { Logger::appendInstall($serverId, 'Running scripted install...'); + $metadata = $protocol['definition']['metadata'] ?? []; // Choose/ensure VPN UDP port for script-driven installs if (($protocol['slug'] ?? '') === 'xray-vless' && (!isset($options['server_port']) || !is_int($options['server_port']) || $options['server_port'] <= 0)) { $options['server_port'] = 443; } if (!isset($options['server_port']) || !is_int($options['server_port'])) { - $options['server_port'] = self::chooseServerPort($server, $protocol['definition']['metadata'] ?? []); + $options['server_port'] = self::chooseServerPort($server, $metadata); } $result = self::runScript($server, $protocol, 'install', $options); if (!isset($result['success'])) { $result['success'] = true; } Logger::appendInstall($serverId, 'Scripted install finished: ' . json_encode($result)); + + $rawPort = $result['vpn_port'] ?? null; + $resolvedPort = (is_numeric($rawPort) && (int) $rawPort > 0) + ? (int) $rawPort + : ($options['server_port'] ?? null); + + $awgParams = $result['awg_params'] ?? null; + if (!is_array($awgParams)) { + $flat = []; + foreach (['Jc', 'Jmin', 'Jmax', 'S1', 'S2', 'S3', 'S4', 'H1', 'H2', 'H3', 'H4'] as $k) { + if (array_key_exists($k, $result) && $result[$k] !== '' && $result[$k] !== null) { + $flat[$k] = $result[$k]; + } + } + if (!empty($flat)) { + $awgParams = $flat; + } + } + $extras = [ - 'vpn_port' => $result['vpn_port'] ?? ($options['server_port'] ?? null), + 'vpn_port' => $resolvedPort, 'server_public_key' => $result['server_public_key'] ?? null, 'preshared_key' => $result['preshared_key'] ?? null, - 'awg_params' => $result['awg_params'] ?? null, + 'awg_params' => $awgParams, 'secret' => $result['secret'] ?? null, 'server_host' => $result['server_host'] ?? null, + 'container_name' => $result['container_name'] ?? ($metadata['container_name'] ?? null), ]; if (($protocol['slug'] ?? '') === 'xray-vless') { foreach (['client_id', 'container_name', 'server_port', 'xray_port', 'reality_public_key', 'reality_private_key', 'reality_short_id', 'reality_server_name'] as $k) { @@ -563,7 +584,6 @@ class InstallProtocolManager $context = self::buildContext($server, $protocol, $options); $script = self::renderTemplate($scripts, $context); - $script = preg_replace('/<<\s*EOF\b/', "<<'EOF'", $script); $script = preg_replace('/\n\+\s*/', "\n", $script); $exportLines = self::buildExports($context); $wrapper = "bash <<'EOS'\nset -euo pipefail\n" . $exportLines . $script . "\nEOS"; @@ -685,6 +705,10 @@ class InstallProtocolManager $setParts[] = 'preshared_key = ?'; $params[] = (string) $extras['preshared_key']; } + if (isset($extras['container_name']) && $extras['container_name'] !== null && $extras['container_name'] !== '') { + $setParts[] = 'container_name = ?'; + $params[] = (string) $extras['container_name']; + } if (array_key_exists('awg_params', $extras)) { $awgParams = $extras['awg_params']; if (is_array($awgParams)) { diff --git a/migrations/058_add_awg2_protocol.sql b/migrations/058_add_awg2_protocol.sql index ab01fae..1d2e856 100644 --- a/migrations/058_add_awg2_protocol.sql +++ b/migrations/058_add_awg2_protocol.sql @@ -93,11 +93,11 @@ S1_VAL=50 S2_VAL=100 S3_VAL=20 S4_VAL=10 -# H1-H4: header ranges (string format "x-y" per AWG2 spec) -H1_VAL="1-4294967295" -H2_VAL="1-4294967295" -H3_VAL="1-4294967295" -H4_VAL="1-4294967295" +# H1-H4: keep numeric values for broad awg-tools compatibility. +H1_VAL=123456789 +H2_VAL=223456789 +H3_VAL=323456789 +H4_VAL=423456789 # Write config cat > /opt/amnezia/awg2/wg0.conf << EOF @@ -286,10 +286,10 @@ S1_VAL=50 S2_VAL=100 S3_VAL=20 S4_VAL=10 -H1_VAL="1-4294967295" -H2_VAL="1-4294967295" -H3_VAL="1-4294967295" -H4_VAL="1-4294967295" +H1_VAL=123456789 +H2_VAL=223456789 +H3_VAL=323456789 +H4_VAL=423456789 cat > /opt/amnezia/awg2/wg0.conf << EOF [Interface] diff --git a/public/index.php b/public/index.php index 22071dc..1faeb88 100644 --- a/public/index.php +++ b/public/index.php @@ -2549,6 +2549,14 @@ Router::post('/api/servers/{id}/protocols/install', function ($params) { } $result = InstallProtocolManager::activate($server, $protocol, []); + + // Keep API behavior consistent with UI flow: once protocol activation succeeds, + // clear transient error state and mark server as active for client creation. + if (is_array($result) && !empty($result['success'])) { + $pdo = DB::conn(); + $stmtUpdate = $pdo->prepare('UPDATE vpn_servers SET status = ?, error_message = NULL WHERE id = ?'); + $stmtUpdate->execute(['active', $serverId]); + } echo json_encode($result, JSON_UNESCAPED_SLASHES | JSON_INVALID_UTF8_SUBSTITUTE); } catch (Exception $e) { http_response_code(500);