From 65e6a2dd0b0def060c989505507ca73000cb5bac Mon Sep 17 00:00:00 2001 From: infosave2007 Date: Sat, 4 Apr 2026 09:56:49 +0300 Subject: [PATCH] feat: add support for awg2, mtproxy, and aivpn protocols, and implement user role-based access control. --- .gitignore | 1 + controllers/ProtocolManagementController.php | 31 +- inc/InstallProtocolManager.php | 7 +- inc/VpnClient.php | 13 +- migrations/012_add_user_roles.sql | 32 ++ migrations/058_add_awg2_protocol.sql | 381 +++++++++++++++++++ migrations/059_add_mtproxy_protocol.sql | 113 ++++++ migrations/060_add_aivpn_protocol.sql | 153 ++++++++ public/index.php | 4 +- 9 files changed, 726 insertions(+), 9 deletions(-) create mode 100644 migrations/012_add_user_roles.sql create mode 100644 migrations/058_add_awg2_protocol.sql create mode 100644 migrations/059_add_mtproxy_protocol.sql create mode 100644 migrations/060_add_aivpn_protocol.sql diff --git a/.gitignore b/.gitignore index 93f08a3..60a7113 100644 --- a/.gitignore +++ b/.gitignore @@ -68,3 +68,4 @@ scripts/regen_qr.php scripts/test_xray_install.sh scripts/test_online.php API_AWG_DOCS.md +log.txt diff --git a/controllers/ProtocolManagementController.php b/controllers/ProtocolManagementController.php index 6df0e6c..9abb5c6 100644 --- a/controllers/ProtocolManagementController.php +++ b/controllers/ProtocolManagementController.php @@ -800,8 +800,18 @@ SH; $res['client_id'] = $m[1]; } + // Extract secret (for MTProxy and similar protocols) + if (preg_match('/Secret:\s*([a-fA-F0-9]+)/i', $output, $m)) { + $res['secret'] = $m[1]; + } + + // Extract server host/IP + if (preg_match('/Server\s*Host:\s*(\S+)/i', $output, $m)) { + $res['server_host'] = trim($m[1], "'\""); + } + // Generic variable extraction (Variable: KEY=VALUE) - if (preg_match_all('/Variable:\\s*([a-zA-Z0-9_]+)=(.*)/', $output, $matches, PREG_SET_ORDER)) { + if (preg_match_all('/Variable:\s*([a-zA-Z0-9_]+)=(.*)/', $output, $matches, PREG_SET_ORDER)) { foreach ($matches as $m) { $key = trim($m[1]); $val = trim($m[2]); @@ -811,6 +821,25 @@ SH; } } + // Fallback: parse any remaining "Key: Value" lines not yet captured + // This catches protocol-specific variables like custom fields + $lines = preg_split('/\r?\n/', $output); + foreach ($lines as $line) { + $line = trim($line); + if ($line === '') continue; + // Skip set -x trace lines (start with +) + if (preg_match('/^\+/', $line)) continue; + if (preg_match('/^([A-Za-z][A-Za-z0-9 _-]*?)\s*:\s*(.+)$/', $line, $m)) { + $rawKey = trim($m[1]); + $rawVal = trim($m[2], " \t'\""); + $normalized = strtolower(preg_replace('/\s+/', '_', $rawKey)); + // Don't overwrite already extracted keys + if (!array_key_exists($normalized, $res)) { + $res[$normalized] = $rawVal; + } + } + } + return $res; } diff --git a/inc/InstallProtocolManager.php b/inc/InstallProtocolManager.php index e2edc8c..24c8975 100644 --- a/inc/InstallProtocolManager.php +++ b/inc/InstallProtocolManager.php @@ -295,6 +295,8 @@ class InstallProtocolManager 'server_public_key' => $result['server_public_key'] ?? null, 'preshared_key' => $result['preshared_key'] ?? null, 'awg_params' => $result['awg_params'] ?? null, + 'secret' => $result['secret'] ?? null, + 'server_host' => $result['server_host'] ?? 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) { @@ -653,6 +655,9 @@ class InstallProtocolManager 'privatekey' => 'reality_private_key', 'shortid' => 'reality_short_id', 'servername' => 'reality_server_name', + 'secret' => 'secret', + 'serverhost' => 'server_host', + 'server_host' => 'server_host', ]; $finalKey = $keyMap[$normalizedKey] ?? $normalizedKey; @@ -899,7 +904,7 @@ class InstallProtocolManager */ private static function isAwgProtocol(string $slug, array $protocol): bool { - if (in_array($slug, ['amnezia-wg', 'amnezia-wg-advanced'], true)) { + if (in_array($slug, ['amnezia-wg', 'amnezia-wg-advanced', 'awg2'], true)) { return true; } $installScript = (string) ($protocol['install_script'] ?? ''); diff --git a/inc/VpnClient.php b/inc/VpnClient.php index 6573991..bf3ff85 100644 --- a/inc/VpnClient.php +++ b/inc/VpnClient.php @@ -67,7 +67,7 @@ class VpnClient $protoRow = $stmtProto2->fetch(); } $slug = $protoRow['slug'] ?? ($serverData['install_protocol'] ?? 'amnezia-wg'); - $isWireguard = in_array($slug, ['amnezia-wg-advanced', 'wireguard-standard', 'amnezia-wg'], true); + $isWireguard = in_array($slug, ['amnezia-wg-advanced', 'wireguard-standard', 'amnezia-wg', 'awg2'], true); // Auto-sync server keys from container EVERY TIME for WireGuard protocols // This ensures we always use current container configuration even if it was recreated @@ -1121,7 +1121,7 @@ class VpnClient $stmt = $pdo->prepare('SELECT slug FROM protocols WHERE id = ?'); $stmt->execute([$protocolId]); $slug = (string) $stmt->fetchColumn(); - return in_array($slug, ['amnezia-wg-advanced', 'wireguard-standard', 'amnezia-wg'], true); + return in_array($slug, ['amnezia-wg-advanced', 'wireguard-standard', 'amnezia-wg', 'awg2'], true); } catch (Exception $e) { return true; } @@ -1309,7 +1309,7 @@ class VpnClient $protoRow = $stmt->fetch(); } $slug = $protoRow['slug'] ?? ''; - $isWireguard = in_array($slug, ['amnezia-wg-advanced', 'wireguard-standard', 'amnezia-wg'], true); + $isWireguard = in_array($slug, ['amnezia-wg-advanced', 'wireguard-standard', 'amnezia-wg', 'awg2'], true); if (!$isWireguard) { return ['success' => false, 'error' => 'not_wireguard_protocol', 'protocol_slug' => $slug]; @@ -1335,7 +1335,7 @@ class VpnClient // If AWG params are missing (common after reinstall), fetch them directly from wg0.conf // to avoid falling back to template defaults that will not match the server. - if ($slug === 'amnezia-wg-advanced') { + if (in_array($slug, ['amnezia-wg-advanced', 'awg2'], true)) { $needKeys = ['JC', 'JMIN', 'JMAX', 'S1', 'S2', 'H1', 'H2', 'H3', 'H4']; $missing = false; foreach ($needKeys as $k) { @@ -1346,8 +1346,9 @@ class VpnClient } if ($missing) { - $containerName = $serverData['container_name'] ?? 'amnezia-awg'; - $direct = self::extractAwgParamsFromWg0Conf($server, $containerName, '/opt/amnezia/awg/wg0.conf'); + $containerName = $serverData['container_name'] ?? ($slug === 'awg2' ? 'amnezia-awg2' : 'amnezia-awg'); + $configDir = $slug === 'awg2' ? '/opt/amnezia/awg2' : '/opt/amnezia/awg'; + $direct = self::extractAwgParamsFromWg0Conf($server, $containerName, $configDir . '/wg0.conf'); if (empty($direct)) { $direct = self::extractAwgParamsFromWg0Conf($server, $containerName, '/etc/wireguard/wg0.conf'); } diff --git a/migrations/012_add_user_roles.sql b/migrations/012_add_user_roles.sql new file mode 100644 index 0000000..dbb04a5 --- /dev/null +++ b/migrations/012_add_user_roles.sql @@ -0,0 +1,32 @@ +-- Migration: Add user roles and permissions +-- Date: 2025-11-10 + +-- User roles table +CREATE TABLE IF NOT EXISTS user_roles ( + id INT PRIMARY KEY AUTO_INCREMENT, + name VARCHAR(50) NOT NULL UNIQUE, + display_name VARCHAR(100) NOT NULL, + description TEXT, + permissions JSON NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- Add role to users table +ALTER TABLE users +ADD COLUMN role VARCHAR(50) DEFAULT 'viewer' AFTER ldap_dn, +ADD INDEX idx_role (role); + +-- Insert default roles +INSERT IGNORE INTO user_roles (name, display_name, description, permissions) VALUES +('admin', 'Administrator', 'Full access to all features', JSON_ARRAY('*')), +('manager', 'Manager', 'Can manage servers and clients', JSON_ARRAY('servers.view', 'servers.create', 'servers.edit', 'clients.view', 'clients.create', 'clients.edit', 'clients.delete')), +('viewer', 'Viewer', 'Can only view own clients', JSON_ARRAY('clients.view_own', 'clients.download_own')); + +-- Insert default LDAP group mappings (examples) +INSERT IGNORE INTO ldap_group_mappings (ldap_group, role_name, description) VALUES +('vpn-admins', 'admin', 'VPN administrators with full access'), +('vpn-managers', 'manager', 'VPN managers who can create and manage clients'), +('vpn-users', 'viewer', 'Regular VPN users with view-only access'); + +-- Update existing users to admin role (backward compatibility) +UPDATE users SET role = 'admin' WHERE role IS NULL OR role = ''; diff --git a/migrations/058_add_awg2_protocol.sql b/migrations/058_add_awg2_protocol.sql new file mode 100644 index 0000000..ab01fae --- /dev/null +++ b/migrations/058_add_awg2_protocol.sql @@ -0,0 +1,381 @@ +-- ===================================================================== +-- Migration 058: Add AmneziaWG 2.0 protocol (amneziawg-go userspace) +-- Uses amneziawg-go (Go userspace) instead of kernel module +-- https://github.com/amnezia-vpn/amneziawg-go +-- ===================================================================== + +-- 1. Insert the protocol entry (clone output_template from amnezia-wg-advanced) +INSERT INTO protocols (name, slug, description, install_script, uninstall_script, output_template, ubuntu_compatible, is_active, definition, created_at, updated_at) +SELECT + 'AmneziaWG 2.0', + 'awg2', + 'AmneziaWG 2.0 — userspace Go implementation (amneziawg-go). No kernel module required.', + '#!/bin/bash +set -euo pipefail + +# Use exported variables from panel (SERVER_PORT, SERVER_CONTAINER) or defaults +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:-1420} + +# Install git if not available +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 + +# Clone amneziawg-go source for Docker build +if [ ! -d /opt/amnezia/awg2/src ]; then + git clone --depth=1 https://github.com/amnezia-vpn/amneziawg-go.git /opt/amnezia/awg2/src +fi + +# Build Docker image using the repo Dockerfile (multi-stage: Go compile + tools) +docker build --no-cache -t amnezia-awg2 /opt/amnezia/awg2/src + +# Run container (userspace: no SYS_MODULE, no /lib/modules) +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 + +# Check for existing config +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" + + # Output AWG params from existing config + for P in Jc Jmin Jmax S1 S2 S3 S4 H1 H2 H3 H4; do + VAL=$(grep -E "^$P " /opt/amnezia/awg2/wg0.conf | cut -d= -f2 | tr -d "[:space:]") + if [ -n "$VAL" ]; then echo "Variable: $P=$VAL"; fi + done + echo "Variable: dns_servers=1.1.1.1, 1.0.0.1" + exit 0 +fi + +# Generate keys +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) + +# AWG obfuscation parameters +JC=5 +JMIN=50 +JMAX=1000 +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" + +# Write config +cat > /opt/amnezia/awg2/wg0.conf << EOF +[Interface] +PrivateKey = $PRIVATE_KEY +Address = 10.8.1.1/24 +ListenPort = $VPN_PORT +MTU = $MTU +Jc = $JC +Jmin = $JMIN +Jmax = $JMAX +S1 = $S1_VAL +S2 = $S2_VAL +S3 = $S3_VAL +S4 = $S4_VAL +H1 = $H1_VAL +H2 = $H2_VAL +H3 = $H3_VAL +H4 = $H4_VAL +PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE +PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE + +[Peer] +PublicKey = +PresharedKey = $PRESHARED_KEY +AllowedIPs = 10.8.1.2/32 +EOF + +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 + +# Get external IP +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: dns_servers=1.1.1.1, 1.0.0.1"', + '#!/bin/bash +set -euo pipefail + +CONTAINER_NAME="${CONTAINER_NAME:-amnezia-awg2}" + +docker stop "$CONTAINER_NAME" 2>/dev/null || true +docker rm -fv "$CONTAINER_NAME" 2>/dev/null || true +docker image rm amnezia-awg2 2>/dev/null || true +rm -rf /opt/amnezia/awg2 2>/dev/null || true + +echo "{\"success\":true,\"message\":\"AmneziaWG 2.0 uninstalled\"}"', + p.output_template, + 1, + 1, + JSON_OBJECT( + 'engine', 'shell', + 'metadata', JSON_OBJECT( + 'container_name', 'amnezia-awg2', + 'vpn_subnet', '10.8.1.0/24', + 'port_range', JSON_ARRAY(30000, 65000), + 'config_dir', '/opt/amnezia/awg2' + ) + ), + NOW(), + NOW() +FROM protocols p +WHERE p.slug = 'amnezia-wg-advanced' + AND NOT EXISTS (SELECT 1 FROM protocols WHERE slug = 'awg2'); + +-- 2. Clone protocol variables from amnezia-wg-advanced to awg2 +INSERT INTO protocol_variables (protocol_id, variable_name, variable_type, default_value, description, required) +SELECT + (SELECT id FROM protocols WHERE slug = 'awg2' LIMIT 1), + src.variable_name, + src.variable_type, + src.default_value, + src.description, + src.required +FROM protocol_variables src +WHERE src.protocol_id = (SELECT id FROM protocols WHERE slug = 'amnezia-wg-advanced' LIMIT 1) + AND NOT EXISTS ( + SELECT 1 FROM protocol_variables ev + WHERE ev.protocol_id = (SELECT id FROM protocols WHERE slug = 'awg2' LIMIT 1) + AND ev.variable_name = src.variable_name + ); + +-- 3. Clone protocol templates from amnezia-wg-advanced to awg2 +INSERT INTO protocol_templates (protocol_id, template_name, template_content, is_default) +SELECT + (SELECT id FROM protocols WHERE slug = 'awg2' LIMIT 1), + src.template_name, + src.template_content, + src.is_default +FROM protocol_templates src +WHERE src.protocol_id = (SELECT id FROM protocols WHERE slug = 'amnezia-wg-advanced' LIMIT 1) + AND NOT EXISTS ( + SELECT 1 FROM protocol_templates et + WHERE et.protocol_id = (SELECT id FROM protocols WHERE slug = 'awg2' LIMIT 1) + AND et.template_name = src.template_name + ); + +-- 4. Update install_script for existing awg2 protocol (in case migration was already run) +UPDATE protocols SET 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:-1420} + +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; do + VAL=$(grep -E "^$P " /opt/amnezia/awg2/wg0.conf | cut -d= -f2 | tr -d "[:space:]") + if [ -n "$VAL" ]; 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=50 +JMAX=1000 +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" + +cat > /opt/amnezia/awg2/wg0.conf << EOF +[Interface] +PrivateKey = $PRIVATE_KEY +Address = 10.8.1.1/24 +ListenPort = $VPN_PORT +MTU = $MTU +Jc = $JC +Jmin = $JMIN +Jmax = $JMAX +S1 = $S1_VAL +S2 = $S2_VAL +S3 = $S3_VAL +S4 = $S4_VAL +H1 = $H1_VAL +H2 = $H2_VAL +H3 = $H3_VAL +H4 = $H4_VAL +PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE +PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE + +[Peer] +PublicKey = +PresharedKey = $PRESHARED_KEY +AllowedIPs = 10.8.1.2/32 +EOF + +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: dns_servers=1.1.1.1, 1.0.0.1"' +WHERE slug = 'awg2'; + +-- 5. Update output_template for AWG2 (add S3/S4 padding params) +UPDATE protocols SET output_template = '[Interface] +PrivateKey = {{private_key}} +Address = {{client_ip}}/32 +DNS = {{dns_servers}} +MTU = 1280 +Jc = {{Jc}} +Jmin = {{Jmin}} +Jmax = {{Jmax}} +S1 = {{S1}} +S2 = {{S2}} +S3 = {{S3}} +S4 = {{S4}} +H1 = {{H1}} +H2 = {{H2}} +H3 = {{H3}} +H4 = {{H4}} + +[Peer] +PublicKey = {{server_public_key}} +PresharedKey = {{preshared_key}} +AllowedIPs = 0.0.0.0/0, ::/0 +Endpoint = {{server_host}}:{{server_port}} +PersistentKeepalive = 25' +WHERE slug = 'awg2'; + +-- 6. Add S3/S4 protocol variables for awg2 +INSERT INTO protocol_variables (protocol_id, variable_name, variable_type, default_value, description, required) +SELECT p.id, 'S3', 'number', '20', 'Padding of handshake cookie message', false +FROM protocols p WHERE p.slug = 'awg2' + AND NOT EXISTS (SELECT 1 FROM protocol_variables WHERE protocol_id = p.id AND variable_name = 'S3'); + +INSERT INTO protocol_variables (protocol_id, variable_name, variable_type, default_value, description, required) +SELECT p.id, 'S4', 'number', '10', 'Padding of transport messages', false +FROM protocols p WHERE p.slug = 'awg2' + AND NOT EXISTS (SELECT 1 FROM protocol_variables WHERE protocol_id = p.id AND variable_name = 'S4'); diff --git a/migrations/059_add_mtproxy_protocol.sql b/migrations/059_add_mtproxy_protocol.sql new file mode 100644 index 0000000..e5de8db --- /dev/null +++ b/migrations/059_add_mtproxy_protocol.sql @@ -0,0 +1,113 @@ +-- ===================================================================== +-- Migration 059: Add MTProxy (Telegram) protocol +-- https://hub.docker.com/r/telegrammessenger/proxy/ +-- Zero-configuration Telegram MTProto proxy server +-- ===================================================================== + +-- 1. Insert the MTProxy protocol +INSERT INTO protocols (name, slug, description, install_script, uninstall_script, output_template, show_text_content, ubuntu_compatible, is_active, definition, created_at, updated_at) +SELECT + 'MTProxy (Telegram)', + 'mtproxy', + 'Telegram MTProto proxy — zero-configuration proxy server for Telegram messenger.', + '#!/bin/bash +set -euo pipefail + +# Use exported variables from panel (SERVER_PORT, SERVER_CONTAINER) or defaults +CONTAINER_NAME="${SERVER_CONTAINER:-amnezia-mtproxy}" +PORT_RANGE_START=${PORT_RANGE_START:-30000} +PORT_RANGE_END=${PORT_RANGE_END:-65000} +MTPROXY_PORT="${SERVER_PORT:-$((RANDOM % (PORT_RANGE_END - PORT_RANGE_START + 1) + PORT_RANGE_START))}" + +mkdir -p /opt/amnezia/mtproxy + +# Generate secret if not exists +if [ -f /opt/amnezia/mtproxy/secret ]; then + SECRET=$(cat /opt/amnezia/mtproxy/secret) + echo "Using existing MTProxy secret" +else + SECRET=$(cat /dev/urandom | tr -dc a-f0-9 | head -c 32 || true) + echo "$SECRET" > /opt/amnezia/mtproxy/secret +fi + +# Store port +echo "$MTPROXY_PORT" > /opt/amnezia/mtproxy/port + +# Remove existing container +docker rm -f "$CONTAINER_NAME" >/dev/null 2>&1 || true + +# Run MTProxy container (single line for heredoc compatibility) +docker run -d --name "$CONTAINER_NAME" --restart always -p "${MTPROXY_PORT}:443" -v /opt/amnezia/mtproxy:/data -e SECRET="$SECRET" telegrammessenger/proxy:latest + +sleep 3 + +# Get external IP +EXTERNAL_IP=$(curl -s -4 ifconfig.me 2>/dev/null || curl -s -4 icanhazip.com 2>/dev/null || echo "YOUR_SERVER_IP") + +echo "MTProxy installed successfully" +echo "Port: $MTPROXY_PORT" +echo "Secret: $SECRET" +echo "Server Host: $EXTERNAL_IP"', + '#!/bin/bash +set -euo pipefail + +CONTAINER_NAME="${CONTAINER_NAME:-amnezia-mtproxy}" + +docker stop "$CONTAINER_NAME" 2>/dev/null || true +docker rm -fv "$CONTAINER_NAME" 2>/dev/null || true +docker image rm telegrammessenger/proxy:latest 2>/dev/null || true +rm -rf /opt/amnezia/mtproxy 2>/dev/null || true + +echo "{\"success\":true,\"message\":\"MTProxy uninstalled\"}"', + 'tg://proxy?server={{server_host}}&port={{server_port}}&secret={{secret}}', + 1, + 1, + 1, + JSON_OBJECT( + 'engine', 'shell', + 'metadata', JSON_OBJECT( + 'container_name', 'amnezia-mtproxy', + 'port_range', JSON_ARRAY(30000, 65000), + 'config_dir', '/opt/amnezia/mtproxy' + ) + ), + NOW(), + NOW() +WHERE NOT EXISTS (SELECT 1 FROM protocols WHERE slug = 'mtproxy'); + +-- 2. Add protocol variables for MTProxy +INSERT INTO protocol_variables (protocol_id, variable_name, variable_type, default_value, description, required) +SELECT p.id, 'secret', 'string', '', 'MTProxy secret (32 hex chars)', true +FROM protocols p WHERE p.slug = 'mtproxy' + AND NOT EXISTS (SELECT 1 FROM protocol_variables WHERE protocol_id = p.id AND variable_name = 'secret'); + +INSERT INTO protocol_variables (protocol_id, variable_name, variable_type, default_value, description, required) +SELECT p.id, 'server_host', 'string', '', 'Server hostname or IP', true +FROM protocols p WHERE p.slug = 'mtproxy' + AND NOT EXISTS (SELECT 1 FROM protocol_variables WHERE protocol_id = p.id AND variable_name = 'server_host'); + +INSERT INTO protocol_variables (protocol_id, variable_name, variable_type, default_value, description, required) +SELECT p.id, 'server_port', 'number', '443', 'MTProxy external port', true +FROM protocols p WHERE p.slug = 'mtproxy' + AND NOT EXISTS (SELECT 1 FROM protocol_variables WHERE protocol_id = p.id AND variable_name = 'server_port'); + +-- 3. Add default template for MTProxy +INSERT INTO protocol_templates (protocol_id, template_name, template_content, is_default) +SELECT p.id, 'Default MTProxy', 'tg://proxy?server={{server_host}}&port={{server_port}}&secret={{secret}}', true +FROM protocols p WHERE p.slug = 'mtproxy' + AND NOT EXISTS (SELECT 1 FROM protocol_templates WHERE protocol_id = p.id AND template_name = 'Default MTProxy'); + +-- 4. Add QR code template (same as output) +UPDATE protocols SET + qr_code_template = 'tg://proxy?server={{server_host}}&port={{server_port}}&secret={{secret}}', + qr_code_format = 'raw' +WHERE slug = 'mtproxy'; + +-- 5. Add translations for MTProxy +INSERT INTO translations (locale, category, key_name, translation) VALUES +('en', 'protocols', 'protocol_mtproxy', 'MTProxy (Telegram)') +ON DUPLICATE KEY UPDATE translation = VALUES(translation); + +INSERT INTO translations (locale, category, key_name, translation) VALUES +('ru', 'protocols', 'protocol_mtproxy', 'MTProxy (Telegram)') +ON DUPLICATE KEY UPDATE translation = VALUES(translation); diff --git a/migrations/060_add_aivpn_protocol.sql b/migrations/060_add_aivpn_protocol.sql new file mode 100644 index 0000000..aa7b218 --- /dev/null +++ b/migrations/060_add_aivpn_protocol.sql @@ -0,0 +1,153 @@ +-- ===================================================================== +-- Migration 060: Add AIVPN protocol (AI-powered VPN with traffic disguise) +-- https://github.com/infosave2007/aivpn +-- Neural Resonance AI for DPI bypass, Zero-RTT, PFS +-- ===================================================================== + +-- 1. Insert the AIVPN protocol +INSERT INTO protocols (name, slug, description, install_script, uninstall_script, output_template, show_text_content, ubuntu_compatible, is_active, definition, created_at, updated_at) +SELECT + 'AIVPN', + 'aivpn', + 'AIVPN — AI-powered VPN с маскировкой трафика под реальные приложения (Zoom, TikTok, DNS). Neural Resonance для обхода DPI.', + '#!/bin/bash +set -euo pipefail + +# Use exported variables from panel (SERVER_PORT, SERVER_CONTAINER) or defaults +CONTAINER_NAME="${SERVER_CONTAINER:-aivpn-server}" +VPN_PORT="${SERVER_PORT:-443}" +CONFIG_DIR="/etc/aivpn" + +# Install git and iptables if not available +if ! command -v git &> /dev/null || ! command -v iptables &> /dev/null; then + apt-get update -qq + if ! command -v git &> /dev/null; then + apt-get install -y -qq git >/dev/null 2>&1 + fi + if ! command -v iptables &> /dev/null; then + apt-get install -y -qq iptables >/dev/null 2>&1 + fi +fi + +# Install Docker if not available +if ! command -v docker &> /dev/null; then + apt-get update -qq + apt-get install -y -qq apt-transport-https ca-certificates curl gnupg lsb-release >/dev/null 2>&1 + curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg + echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" > /etc/apt/sources.list.d/docker.list + apt-get update -qq && apt-get install -y -qq docker-ce docker-ce-cli containerd.io >/dev/null 2>&1 +fi + +mkdir -p "$CONFIG_DIR" + +# Enable IP forwarding +sysctl -w net.ipv4.ip_forward=1 2>/dev/null || true + +# Generate server key if not exists +if [ ! -f "$CONFIG_DIR/server.key" ]; then + openssl rand 32 > "$CONFIG_DIR/server.key" + chmod 600 "$CONFIG_DIR/server.key" + echo "Generated new AIVPN server key" +else + echo "Using existing AIVPN server key" +fi + +# Setup NAT +iptables -t nat -C POSTROUTING -s 10.0.0.0/24 -o eth0 -j MASQUERADE 2>/dev/null || \ + iptables -t nat -A POSTROUTING -s 10.0.0.0/24 -o eth0 -j MASQUERADE + +# Get external IP +EXTERNAL_IP=$(curl -s -4 ifconfig.me 2>/dev/null || curl -s -4 icanhazip.com 2>/dev/null || echo "YOUR_SERVER_IP") + +# Clone AIVPN source for Docker build +if [ ! -d /opt/amnezia/aivpn ]; then + git clone --depth=1 https://github.com/infosave2007/aivpn.git /opt/amnezia/aivpn +fi + +# Build Docker image +cd /opt/amnezia/aivpn +docker build --no-cache -t aivpn-server -f Dockerfile . + +# Remove existing container +docker rm -f "$CONTAINER_NAME" >/dev/null 2>&1 || true + +# Run AIVPN container +docker run -d --name "$CONTAINER_NAME" --restart always --cap-add=NET_ADMIN --device /dev/net/tun --network host -v "$CONFIG_DIR:/etc/aivpn" aivpn-server --listen "0.0.0.0:${VPN_PORT}" --key-file /etc/aivpn/server.key + +sleep 3 + +# Check container status +STATUS=$(docker inspect --format="{{.State.Status}}" "$CONTAINER_NAME" 2>/dev/null || echo "unknown") +if [ "$STATUS" != "running" ]; then + echo "ERROR: AIVPN container is not running" + docker logs "$CONTAINER_NAME" 2>&1 + exit 1 +fi + +echo "AIVPN installed successfully" +echo "Port: $VPN_PORT" +echo "ExternalIP: $EXTERNAL_IP" +echo "ConfigDir: $CONFIG_DIR"', + '#!/bin/bash +set -euo pipefail + +CONTAINER_NAME="${CONTAINER_NAME:-aivpn-server}" + +docker stop "$CONTAINER_NAME" 2>/dev/null || true +docker rm -fv "$CONTAINER_NAME" 2>/dev/null || true +docker image rm aivpn-server 2>/dev/null || true +rm -rf /opt/amnezia/aivpn 2>/dev/null || true + +# Remove NAT rules +iptables -t nat -D POSTROUTING -s 10.0.0.0/24 -o eth0 -j MASQUERADE 2>/dev/null || true + +echo "{\"success\":true,\"message\":\"AIVPN uninstalled\"}"', + 'aivpn://{{connection_key}}', + 1, + 1, + 1, + JSON_OBJECT( + 'engine', 'shell', + 'metadata', JSON_OBJECT( + 'container_name', 'aivpn-server', + 'port_range', JSON_ARRAY(443, 443), + 'config_dir', '/etc/aivpn', + 'vpn_subnet', '10.0.0.0/24', + 'requires_docker_build', true, + 'git_repo', 'https://github.com/infosave2007/aivpn.git' + ) + ), + NOW(), + NOW() +WHERE NOT EXISTS (SELECT 1 FROM protocols WHERE slug = 'aivpn'); + +-- 2. Add protocol variables for AIVPN +INSERT INTO protocol_variables (protocol_id, variable_name, variable_type, default_value, description, required) +SELECT p.id, 'connection_key', 'string', '', 'AIVPN connection key (generated by server)', true +FROM protocols p WHERE p.slug = 'aivpn' + AND NOT EXISTS (SELECT 1 FROM protocol_variables WHERE protocol_id = p.id AND variable_name = 'connection_key'); + +INSERT INTO protocol_variables (protocol_id, variable_name, variable_type, default_value, description, required) +SELECT p.id, 'server_host', 'string', '', 'Server hostname or IP', true +FROM protocols p WHERE p.slug = 'aivpn' + AND NOT EXISTS (SELECT 1 FROM protocol_variables WHERE protocol_id = p.id AND variable_name = 'server_host'); + +INSERT INTO protocol_variables (protocol_id, variable_name, variable_type, default_value, description, required) +SELECT p.id, 'server_port', 'number', '443', 'AIVPN server port', true +FROM protocols p WHERE p.slug = 'aivpn' + AND NOT EXISTS (SELECT 1 FROM protocol_variables WHERE protocol_id = p.id AND variable_name = 'server_port'); + +-- 3. Add default template for AIVPN +INSERT INTO protocol_templates (protocol_id, template_name, template_content, is_default) +SELECT p.id, 'Default AIVPN', 'aivpn://{{connection_key}}', true +FROM protocols p WHERE p.slug = 'aivpn' + AND NOT EXISTS (SELECT 1 FROM protocol_templates WHERE protocol_id = p.id AND template_name = 'Default AIVPN'); + +-- 4. Add translations for AIVPN +INSERT INTO translations (locale, category, key_name, translation) VALUES +('en', 'protocols', 'protocol_aivpn', 'AIVPN (AI-Powered)') +ON DUPLICATE KEY UPDATE translation = VALUES(translation); + +INSERT INTO translations (locale, category, key_name, translation) VALUES +('ru', 'protocols', 'protocol_aivpn', 'AIVPN (ИИ-протокол)') +ON DUPLICATE KEY UPDATE translation = VALUES(translation); diff --git a/public/index.php b/public/index.php index 4926f5a..22071dc 100644 --- a/public/index.php +++ b/public/index.php @@ -1152,7 +1152,7 @@ Router::get('/clients/{id}', function ($params) { } if ($protocol && ($protocol['output_template'] ?? '') !== '') { $slug = $protocol['slug'] ?? ''; - $isWireguard = in_array($slug, ['amnezia-wg-advanced', 'wireguard-standard', 'amnezia-wg'], true); + $isWireguard = in_array($slug, ['amnezia-wg-advanced', 'wireguard-standard', 'amnezia-wg', 'awg2'], true); if ($isWireguard) { // For WG, we don’t render protocol_output; config is downloadable $protocolOutput = ''; @@ -1771,6 +1771,8 @@ Router::post('/api/servers/create', function () { 'port' => $port, 'username' => $username, 'password' => $password, + 'install_protocol' => trim($input['install_protocol'] ?? ''), + 'install_options' => $input['install_options'] ?? null, ]); http_response_code(201);