'WGDashboard', 'DefaultNonSSLPort' => '10086', 'DefaultSSLPort' => '443', 'APIVersion' => '1.0', 'RequiresServer' => true, ); } function wgdashboard_GetHostname(array $params) { $hostname = $params['serverhostname'] ?? ''; if ($hostname === '') { throw new Exception('Could not find the WGDashboard\'s hostname - did you configure server group for the product?'); } // WHMCS sometimes stores placeholders like DOT/DASH foreach ([ 'DOT' => '.', 'DASH' => '-' ] as $from => $to) { $hostname = str_replace($from, $to, $hostname); } $hostname = preg_replace('#^https?://#i', '', trim($hostname)); $hostname = rtrim($hostname, '/'); // Strip port from hostname; always use $params['serverport'] if (strpos($hostname, ':') !== false) { $hostname = explode(':', $hostname)[0]; } $protocol = (!empty($params['serversecure'])) ? 'https://' : 'http://'; $port = isset($params['serverport']) && is_numeric($params['serverport']) && (int)$params['serverport'] > 0 ? (int)$params['serverport'] : null; return $protocol . $hostname . ($port ? (':' . $port) : ''); } function wgdashboard_API(array $params, $endpoint, array $data = [], $method = "GET", $dontLog = false) { $hostname = wgdashboard_GetHostname($params); $url = $hostname . '/api/' . $endpoint; $curl = curl_init(); curl_setopt($curl, CURLOPT_URL, $url); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method); curl_setopt($curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2); curl_setopt($curl, CURLOPT_USERAGENT, "WGDashboard-WHMCS"); curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); curl_setopt($curl, CURLOPT_POSTREDIR, CURL_REDIR_POST_301); curl_setopt($curl, CURLOPT_TIMEOUT, 30); $headers = [ "wg-dashboard-apikey: " . $params['serverpassword'], "Accept: application/json", "Content-Type: application/json", ]; if($method === 'POST' || $method === 'PATCH' || $method === 'PUT') { if(!empty($data)) { $jsonData = json_encode($data); curl_setopt($curl, CURLOPT_POSTFIELDS, $jsonData); $headers[] = "Content-Length: " . strlen($jsonData); } } curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); $response = curl_exec($curl); $responseData = json_decode($response, true); $httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE); $curlErr = curl_error($curl); if($response === false && !$dontLog) logModuleCall("WGDashboard-WHMCS", "CURL ERROR", $curlErr, ""); curl_close($curl); $result = [ 'status_code' => $httpCode, 'data' => $responseData ]; if(!$dontLog) logModuleCall("WGDashboard-WHMCS", $method . " - " . $url, isset($data) ? json_encode($data) : "", print_r($result, true)); return $result; } function wgdashboard_Error($func, $params, Exception $err) { logModuleCall("WGDashboard-WHMCS", $func, $params, $err->getMessage(), $err->getTraceAsString()); } function wgdashboard_ConfigOptions() { return [ 'Allowed IPs' => [ 'Type' => 'text', 'Size' => '40', 'Description' => 'Default allowed IPs for created peers (e.g. 10.10.0.0/24 or 0.0.0.0/0)', 'Default' => '0.0.0.0/0', ], 'Config Name' => [ 'Type' => 'text', 'Size' => '30', 'Description' => 'WireGuard config name on WGDashboard (if you manage multiple configs)', 'Default' => 'wg0', ], 'Peer Template' => [ 'Type' => 'textarea', 'Description' => "Optional JSON template to merge into peer creation payload. Use {{client_email}} or {{domain}} placeholders.", ], ]; } function wgdashboard_TestConnection(array $params) { $solutions = [ 0 => "Check module debug log for more detailed error.", 401 => "Authorization header either missing or not provided.", 403 => "Double check the API key (which should be in the server password field).", 404 => "Result not found.", 422 => "Validation error.", 500 => "WGDashboard errored, check panel logs.", ]; $err = ""; try { $response = wgdashboard_API($params, 'getWireguardConfigurations'); if($response['status_code'] !== 200) { $status_code = $response['status_code']; $err = "Invalid status_code received: " . $status_code . ". Possible solutions: " . (isset($solutions[$status_code]) ? $solutions[$status_code] : "None."); } } catch(Exception $e) { wgdashboard_Error(__FUNCTION__, $params, $e); $err = $e->getMessage(); } return [ "success" => $err === "", "error" => $err, ]; } function wgdashboard_GetPeerID(array $params, $raw = false) { // Try to get from database directly first if (!empty($params['serviceid'])) { try { $service = Capsule::table('tblhosting')->where('id', $params['serviceid'])->first(); if ($service && !empty($service->username)) { if ($raw) { // Get peer info by searching in the configuration $configName = $params['configoptions']['Config Name'] ?? $params['configoption2'] ?? 'wg0'; $configResult = wgdashboard_API($params, 'getWireguardConfigurationInfo?configurationName=' . urlencode($configName), [], 'GET', false); // Force logging // Debug: Force logging for this call even with dontLog=true if(function_exists('logModuleCall')) { logModuleCall("WGDashboard-WHMCS", "GetPeerID RAW Debug", "Config: " . $configName . ", Looking for: " . $service->username, "Status: " . $configResult['status_code'] . "\nResponse: " . print_r($configResult, true)); } if($configResult['status_code'] === 200 && isset($configResult['data']['data']['configurationPeers'])) { foreach($configResult['data']['data']['configurationPeers'] as $peer) { // Check both 'id' and 'public_key' fields if(($peer['id'] ?? '') === $service->username || ($peer['public_key'] ?? '') === $service->username) { return ['status_code' => 200, 'data' => $peer]; } } // Debug: If not found, log what peers exist if(function_exists('logModuleCall')) { $peerIds = array_map(function($p) { return $p['id'] ?? $p['public_key'] ?? 'no-id'; }, $configResult['data']['data']['configurationPeers']); logModuleCall("WGDashboard-WHMCS", "GetPeerID NOT FOUND", "Looking for: " . $service->username, "Available peers: " . implode(', ', $peerIds)); } } } else { return $service->username; // This is the public key/id } } } catch (Exception $e) { // Continue to other methods } } // Try to get peer public key from service username first if (!empty($params['serviceusername'])) { if ($raw) { // Get peer info by searching in the configuration $configName = $params['configoptions']['Config Name'] ?? $params['configoption2'] ?? 'wg0'; $configResult = wgdashboard_API($params, 'getWireguardConfigurationInfo?configurationName=' . urlencode($configName), [], 'GET', true); if($configResult['status_code'] === 200 && isset($configResult['data']['data']['configurationPeers'])) { foreach($configResult['data']['data']['configurationPeers'] as $peer) { // Check both 'id' and 'public_key' fields if(($peer['id'] ?? '') === $params['serviceusername'] || ($peer['public_key'] ?? '') === $params['serviceusername']) { return ['status_code' => 200, 'data' => $peer]; } } } } else { return $params['serviceusername']; // This is the public key/id } } // Try to get from custom fields if (!empty($params['customfields']['peer_public_key'])) { if ($raw) { $configName = $params['configoptions']['Config Name'] ?? $params['configoption2'] ?? 'wg0'; $configResult = wgdashboard_API($params, 'getWireguardConfigurationInfo?configurationName=' . urlencode($configName), [], 'GET', true); if($configResult['status_code'] === 200 && isset($configResult['data']['data']['configurationPeers'])) { foreach($configResult['data']['data']['configurationPeers'] as $peer) { // Check both 'id' and 'public_key' fields if(($peer['id'] ?? '') === $params['customfields']['peer_public_key'] || ($peer['public_key'] ?? '') === $params['customfields']['peer_public_key']) { return ['status_code' => 200, 'data' => $peer]; } } } } else { return $params['customfields']['peer_public_key']; } } return null; } function wgdashboard_GenerateKeyPair() { if (function_exists('exec') && !in_array('exec', explode(',', ini_get('disable_functions')))) { $privateKey = ''; $publicKey = ''; exec('wg genkey 2>/dev/null', $output, $return_code); if ($return_code === 0 && !empty($output[0])) { $privateKey = trim($output[0]); // Generate public key from private key $publicOutput = []; exec("echo '$privateKey' | wg pubkey 2>/dev/null", $publicOutput, $pub_return_code); if ($pub_return_code === 0 && !empty($publicOutput[0])) { $publicKey = trim($publicOutput[0]); return [ 'private_key' => $privateKey, 'public_key' => $publicKey ]; } } } return [ 'private_key' => '', 'public_key' => '' ]; } function wgdashboard_CreateAccount(array $params) { try { $peerId = wgdashboard_GetPeerID($params); if(isset($peerId)) throw new Exception('Failed to create peer because it is already created.'); $clientEmail = $params['clientsdetails']['email'] ?? ''; $domain = $params['domain'] ?? ''; $serviceId = $params['serviceid'] ?? null; $allowedIpsDefault = $params['configoptions']['Allowed IPs'] ?? $params['configoption1'] ?? '0.0.0.0/0'; $configName = $params['configoptions']['Config Name'] ?? $params['configoption2'] ?? 'wg0'; $peerName = preg_replace('/[^a-zA-Z0-9_\-\.]/', '_', ($params['clientsdetails']['email'] ?? 'peer') . '_' . ($serviceId ?: uniqid())); $keys = wgdashboard_GenerateKeyPair(); $payload = [ "name" => $peerName, "DNS" => "1.1.1.1", "endpoint_allowed_ip" => "0.0.0.0/0", "keepalive" => 21, "mtu" => 1420, "preshared_key" => "", "preshared_key_bulkAdd" => false, "advanced_security" => "off", ]; // Only add keys if they were successfully generated if (!empty($keys['private_key']) && !empty($keys['public_key'])) { $payload["private_key"] = $keys['private_key']; $payload["public_key"] = $keys['public_key']; } if (!empty($params['configoptions']['Peer Template'])) { $template = $params['configoptions']['Peer Template']; $template = str_replace(['{{client_email}}', '{{domain}}', '{{serviceid}}'], [$clientEmail, $domain, $serviceId], $template); $jsonTemplate = @json_decode($template, true); if (is_array($jsonTemplate)) { $payload = array_merge($payload, $jsonTemplate); } } $result = wgdashboard_API($params, 'addPeers/' . urlencode($configName), $payload, 'POST'); if($result['status_code'] !== 200) { throw new Exception('WGDashboard create failed: HTTP ' . $result['status_code'] . ' - ' . print_r($result['data'], true)); } $publicKeyToStore = ''; if (!empty($keys['public_key'])) { $publicKeyToStore = $keys['public_key']; } else { if (isset($result['data']['data']) && is_array($result['data']['data']) && !empty($result['data']['data'][0])) { $createdPeer = $result['data']['data'][0]; $publicKeyToStore = $createdPeer['id'] ?? ''; } if (empty($publicKeyToStore)) { sleep(1); $configResult = wgdashboard_API($params, 'getWireguardConfigurationInfo?configurationName=' . urlencode($configName), [], 'GET', true); if($configResult['status_code'] === 200 && isset($configResult['data']['data']['configurationPeers'])) { foreach($configResult['data']['data']['configurationPeers'] as $peer) { if($peer['name'] === $peerName) { $publicKeyToStore = $peer['id'] ?? $peer['public_key'] ?? ''; break; } } } } } if (empty($publicKeyToStore)) { throw new Exception('Failed to obtain public key for created peer'); } try { Capsule::table('tblhosting')->where('id', $params['serviceid'])->update([ 'username' => $publicKeyToStore, 'password' => '', ]); } catch (Exception $e) { return $e->getMessage() . "
" . $e->getTraceAsString(); } } catch(Exception $err) { return $err->getMessage(); } return 'success'; } function wgdashboard_TerminateAccount($params) { try { $publicKey = wgdashboard_GetPeerID($params); if(!isset($publicKey)) throw new Exception('Failed to terminate peer because it doesn\'t exist.'); $configName = $params['configoptions']['Config Name'] ?? $params['configoption2'] ?? 'wg0'; $publicKeyEncoded = urlencode($publicKey); $deleteResult = wgdashboard_API($params, 'deletePeers/' . urlencode($configName) . '?id=' . $publicKeyEncoded, [], 'DELETE'); if($deleteResult['status_code'] !== 200) throw new Exception('Failed to delete the peer, received error code: ' . $deleteResult['status_code'] . '. Enable module debug log for more info.'); } catch(Exception $err) { return $err->getMessage(); } return 'success'; } function wgdashboard_SuspendAccount($params) { try { $publicKey = wgdashboard_GetPeerID($params); if(!isset($publicKey)) throw new Exception('Failed to suspend peer because it doesn\'t exist.'); $configName = $params['configoptions']['Config Name'] ?? $params['configoption2'] ?? 'wg0'; $publicKeyEncoded = urlencode($publicKey); $suspendResult = wgdashboard_API($params, 'restrictPeers/' . urlencode($configName) . '?id=' . $publicKeyEncoded, [], 'POST'); if($suspendResult['status_code'] !== 200) throw new Exception('Failed to suspend the peer, received error code: ' . $suspendResult['status_code'] . '. Enable module debug log for more info.'); } catch(Exception $err) { return $err->getMessage(); } return 'success'; } function wgdashboard_UnsuspendAccount($params) { try { $publicKey = wgdashboard_GetPeerID($params); if(!isset($publicKey)) throw new Exception('Failed to unsuspend peer because it doesn\'t exist.'); $configName = $params['configoptions']['Config Name'] ?? $params['configoption2'] ?? 'wg0'; $publicKeyEncoded = urlencode($publicKey); $unsuspendResult = wgdashboard_API($params, 'allowAccessPeers/' . urlencode($configName) . '?id=' . $publicKeyEncoded, [], 'POST'); if($unsuspendResult['status_code'] !== 200) throw new Exception('Failed to unsuspend the peer, received error code: ' . $unsuspendResult['status_code'] . '. Enable module debug log for more info.'); } catch(Exception $err) { return $err->getMessage(); } return 'success'; } function wgdashboard_ChangePackage($params) { try { $peerId = wgdashboard_GetPeerID($params); if(!isset($peerId)) throw new Exception('Failed to change package because peer doesn\'t exist.'); $configName = $params['configoptions']['Config Name'] ?? $params['configoption2'] ?? 'wg0'; $allowedIps = $params['configoptions']['Allowed IPs'] ?? $params['configoption1'] ?? '0.0.0.0/0'; $updateData = [ 'allowed_ip' => $allowedIps, ]; $updateResult = wgdashboard_API($params, 'updatePeer/' . urlencode($configName) . '/' . urlencode($peerId), $updateData, 'POST'); if($updateResult['status_code'] !== 200) throw new Exception('Failed to update the peer, received error code: ' . $updateResult['status_code'] . '. Enable module debug log for more info.'); } catch(Exception $err) { return $err->getMessage(); } return 'success'; } function wgdashboard_AdminServicesTabFields($params) { $fields = []; try { $publicKey = wgdashboard_GetPeerID($params); $fields['Public Key'] = $publicKey ?: 'Not found'; if($publicKey) { $peerData = wgdashboard_GetPeerID($params, true); if($peerData && $peerData['status_code'] === 200 && isset($peerData['data'])) { $peer = $peerData['data']; $fields['Peer Name'] = $peer['name'] ?? 'N/A'; $fields['Allowed IPs'] = $peer['allowed_ip'] ?? 'N/A'; $fields['Status'] = ucfirst($peer['status'] ?? 'N/A'); $latestHandshake = $peer['latest_handshake'] ?? null; if ($latestHandshake && is_numeric($latestHandshake) && $latestHandshake > 0) { $fields['Latest Handshake'] = date('Y-m-d H:i:s', $latestHandshake); } elseif ($latestHandshake && preg_match('/^\d+:\d+:\d+$/', $latestHandshake)) { $fields['Latest Handshake'] = $latestHandshake . ' ago'; } else { $fields['Latest Handshake'] = 'Never'; } $cumuReceive = floatval($peer['cumu_receive'] ?? 0); $totalReceive = floatval($peer['total_receive'] ?? 0); $totalReceivedMB = $cumuReceive + $totalReceive; if ($totalReceivedMB >= 1) { $fields['Data Received P->S'] = number_format($totalReceivedMB, 2) . ' GB'; } else { $fields['Data Received P->S'] = number_format($totalReceivedMB*1024, 2) . ' MB'; } $cumuSent = floatval($peer['cumu_sent'] ?? 0); $totalSent = floatval($peer['total_sent'] ?? 0); $totalSentMB = $cumuSent + $totalSent; if ($totalSentMB >= 1) { $fields['Data Sent S->P'] = number_format($totalSentMB, 2) . ' GB'; } else { $fields['Data Sent S->P'] = number_format($totalSentMB*1024, 2) . ' MB'; } $totalDataMB = $totalReceivedMB + $totalSentMB; if ($totalDataMB >= 1) { $fields['Total Data'] = number_format($totalDataMB, 2) . ' GB'; } else { $fields['Total Data'] = number_format($totalDataMB*1024, 2) . ' MB'; } } } } catch (Exception $e) { $fields['Error'] = $e->getMessage(); } return $fields; } function wgdashboard_ClientArea(array $params) { if($params['moduletype'] !== 'wgdashboard') return; try { $hostname = wgdashboard_GetHostname($params); $publicKey = wgdashboard_GetPeerID($params); if(!$publicKey) { return [ 'templatefile' => 'clientarea', 'vars' => [ 'error' => 'Peer não encontrado ou ainda não criado.', ], ]; } $configName = $params['configoptions']['Config Name'] ?? $params['configoption2'] ?? 'wg0'; $peerData = wgdashboard_GetPeerID($params, true); if(!$peerData || $peerData['status_code'] !== 200) { return [ 'templatefile' => 'clientarea', 'vars' => [ 'error' => 'Falha ao recuperar dados do peer.', ], ]; } $peer = $peerData['data']; $formattedConfig = ''; if (!empty($peer['private_key'])) { $formattedConfig = "[Interface]\n"; $formattedConfig .= "PrivateKey = " . ($peer['private_key'] ?? '') . "\n"; $formattedConfig .= "Address = " . ($peer['allowed_ip'] ?? '10.0.0.1/32') . "\n"; $formattedConfig .= "MTU = " . ($peer['mtu'] ?? '1420') . "\n"; $formattedConfig .= "DNS = " . ($peer['DNS'] ?? '1.1.1.1') . "\n"; $formattedConfig .= "\n"; $formattedConfig .= "[Peer]\n"; $serverPublicKey = ''; if (isset($peer['configuration']['PublicKey'])) { $serverPublicKey = $peer['configuration']['PublicKey']; } else { $configInfoResult = wgdashboard_API($params, 'getWireguardConfigurationInfo?configurationName=' . urlencode($configName), [], 'GET', true); if($configInfoResult['status_code'] === 200 && isset($configInfoResult['data']['data']['configurationInfo']['PublicKey'])) { $serverPublicKey = $configInfoResult['data']['data']['configurationInfo']['PublicKey']; } } $formattedConfig .= "PublicKey = " . ($serverPublicKey ?: 'N/A') . "\n"; $formattedConfig .= "AllowedIPs = " . ($peer['endpoint_allowed_ip'] ?? '0.0.0.0/0') . "\n"; $endpoint = 'N/A'; if (!empty($peer['remote_endpoint'])) { $port = ''; if (isset($peer['configuration']['ListenPort'])) { $port = $peer['configuration']['ListenPort']; } else { if (isset($configInfoResult) && $configInfoResult['status_code'] === 200 && isset($configInfoResult['data']['data']['configurationInfo']['ListenPort'])) { $port = $configInfoResult['data']['data']['configurationInfo']['ListenPort']; } else { $port = '51820'; } } $endpoint = $peer['remote_endpoint'] . ':' . $port; } $formattedConfig .= "Endpoint = " . $endpoint . "\n"; $formattedConfig .= "PersistentKeepalive = " . ($peer['keepalive'] ?? '21') . "\n"; } return [ 'templatefile' => 'clientarea', 'vars' => [ 'formatted_config' => $formattedConfig, 'peer_name' => $peer['name'] ?? 'wireguard', ], ]; } catch (Exception $err) { return [ 'templatefile' => 'clientarea', 'vars' => [ 'error' => $err->getMessage(), ], ]; } }