Add all files
This commit is contained in:
417
extensions/Servers/Proxmox/TechnitiumDns.php
Normal file
417
extensions/Servers/Proxmox/TechnitiumDns.php
Normal file
@@ -0,0 +1,417 @@
|
||||
<?php
|
||||
|
||||
namespace Paymenter\Extensions\Servers\Proxmox;
|
||||
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Http\Client\Response;
|
||||
use Paymenter\Extensions\Servers\Proxmox\Models\IPAddress;
|
||||
|
||||
class TechnitiumDns
|
||||
{
|
||||
private string $baseUrl;
|
||||
private string $token;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->baseUrl = 'https://dns.virtnet.bond';
|
||||
$this->token = 'fab3cb86382687472062c22b37aa5789bebba1dbe556017a7234a295aa5b586d';
|
||||
}
|
||||
|
||||
/**
|
||||
* Make an API request to Technitium DNS server
|
||||
*/
|
||||
private function request(string $endpoint, string $method = 'get', array $data = []): Response
|
||||
{
|
||||
$url = rtrim($this->baseUrl, '/') . '/api' . $endpoint;
|
||||
|
||||
$params = array_merge(['token' => $this->token], $data);
|
||||
|
||||
if ($method === 'get') {
|
||||
return Http::get($url, $params);
|
||||
} else {
|
||||
return Http::asForm()->post($url, $params);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all zones
|
||||
*/
|
||||
public function getZones(): array
|
||||
{
|
||||
try {
|
||||
$response = $this->request('/zones/list');
|
||||
$json = $response->json();
|
||||
|
||||
if ($json['status'] === 'ok') {
|
||||
return $json['response'] ?? [];
|
||||
}
|
||||
|
||||
return [];
|
||||
} catch (\Exception $e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get zone details
|
||||
*/
|
||||
public function getZone(string $zoneName): ?array
|
||||
{
|
||||
try {
|
||||
$response = $this->request('/zones/get', 'get', ['zone' => $zoneName]);
|
||||
$json = $response->json();
|
||||
|
||||
if ($json['status'] === 'ok') {
|
||||
return $json['response'] ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (\Exception $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a subdomain exists
|
||||
*/
|
||||
public function subdomainExists(string $subdomain): bool
|
||||
{
|
||||
$fullDomain = $subdomain . '.dns.virtnet.host';
|
||||
$zones = $this->getZones();
|
||||
|
||||
foreach ($zones as $zone) {
|
||||
if (isset($zone['name']) && $zone['name'] === $fullDomain) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new zone (subdomain)
|
||||
*/
|
||||
/**
|
||||
* Create a DNS zone and primary records
|
||||
*
|
||||
* @return array{success: bool, recordType?: string, wildcardCreated?: bool, message?: string}
|
||||
*/
|
||||
public function createZone(string $subdomain, string $ipAddress, int $serverId, int $ipAddressId): array
|
||||
{
|
||||
try {
|
||||
$fullDomain = $subdomain . '.dns.virtnet.host';
|
||||
$comment = "Server ID: {$serverId}";
|
||||
|
||||
// Determine record type based on IP address version
|
||||
$isIpv6 = filter_var($ipAddress, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6);
|
||||
$recordType = $isIpv6 ? 'AAAA' : 'A';
|
||||
|
||||
// Create the zone
|
||||
$response = $this->request('/zones/set', 'post', [
|
||||
'zone' => $fullDomain,
|
||||
'type' => 'Primary',
|
||||
]);
|
||||
|
||||
$json = $response->json();
|
||||
if ($json['status'] !== 'ok') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add A or AAAA record for the subdomain based on IP version
|
||||
$wildcardDomain = '*.' . $fullDomain;
|
||||
$mainRecordComment = $comment . " | Wildcard: {$wildcardDomain} ({$recordType})";
|
||||
|
||||
$recordResponse = $this->request('/zones/records/add', 'post', [
|
||||
'zone' => $fullDomain,
|
||||
'domain' => $fullDomain,
|
||||
'type' => $recordType,
|
||||
'ipAddress' => $ipAddress,
|
||||
'ttl' => 3600,
|
||||
'comments' => $mainRecordComment,
|
||||
]);
|
||||
|
||||
$recordJson = $recordResponse->json();
|
||||
if ($recordJson['status'] !== 'ok') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add wildcard record (*.subdomain.dns.virtnet.host)
|
||||
$wildcardComment = $comment . " | Parent: {$fullDomain} ({$recordType})";
|
||||
$wildcardResponse = $this->request('/zones/records/add', 'post', [
|
||||
'zone' => $fullDomain,
|
||||
'domain' => $wildcardDomain,
|
||||
'type' => $recordType,
|
||||
'ipAddress' => $ipAddress,
|
||||
'ttl' => 3600,
|
||||
'comments' => $wildcardComment,
|
||||
]);
|
||||
|
||||
// Continue even if wildcard creation fails (non-critical)
|
||||
$wildcardCreated = false;
|
||||
if (isset($wildcardResponse)) {
|
||||
$wildcardJson = $wildcardResponse->json();
|
||||
$wildcardCreated = ($wildcardJson['status'] ?? '') === 'ok';
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'recordType' => $recordType,
|
||||
'wildcardCreated' => $wildcardCreated,
|
||||
];
|
||||
} catch (\Exception $e) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => $e->getMessage(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a zone (subdomain)
|
||||
*/
|
||||
public function deleteZone(string $subdomain, int $ipAddressId): bool
|
||||
{
|
||||
try {
|
||||
$fullDomain = $subdomain . '.dns.virtnet.host';
|
||||
|
||||
// Get the IP address to retrieve stored records
|
||||
$ip = IPAddress::find($ipAddressId);
|
||||
if (!$ip) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get stored records from database
|
||||
$storedRecords = $ip->subdomain['records'] ?? [];
|
||||
|
||||
// First, try to delete all records manually
|
||||
// This handles the case where the zone doesn't exist but records do
|
||||
foreach ($storedRecords as $record) {
|
||||
if (isset($record['name']) && isset($record['type'])) {
|
||||
try {
|
||||
$ipAddress = $record['rdata'] ?? $record['ipAddress'] ?? null;
|
||||
$this->deleteRecord($subdomain, $record['name'], $record['type'], $ipAddress);
|
||||
} catch (\Exception $e) {
|
||||
// Continue even if individual record deletion fails
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Also try to get records from the API and delete them
|
||||
// This handles cases where records exist in DNS but not in our database
|
||||
try {
|
||||
$zoneDetails = $this->getZone($fullDomain);
|
||||
if ($zoneDetails && isset($zoneDetails['records'])) {
|
||||
foreach ($zoneDetails['records'] as $record) {
|
||||
if (in_array($record['type'] ?? '', ['A', 'AAAA'])) {
|
||||
$domain = $record['domain'] ?? '';
|
||||
if ($domain === $fullDomain) {
|
||||
$name = '@';
|
||||
} else {
|
||||
$name = str_replace('.' . $fullDomain, '', $domain);
|
||||
}
|
||||
try {
|
||||
$ipAddress = $record['rdata'] ?? $record['ipAddress'] ?? null;
|
||||
$this->deleteRecord($subdomain, $name, $record['type'], $ipAddress);
|
||||
} catch (\Exception $e) {
|
||||
// Continue even if individual record deletion fails
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// Zone might not exist, which is fine
|
||||
}
|
||||
|
||||
// Now try to delete the zone (if it exists)
|
||||
try {
|
||||
$response = $this->request('/zones/delete', 'post', [
|
||||
'zone' => $fullDomain,
|
||||
]);
|
||||
|
||||
$json = $response->json();
|
||||
// If zone doesn't exist, that's fine - we've already deleted the records
|
||||
if ($json['status'] !== 'ok') {
|
||||
$errorMessage = strtolower($json['message'] ?? '');
|
||||
if (str_contains($errorMessage, 'no such zone') || str_contains($errorMessage, 'zone not found')) {
|
||||
// Zone doesn't exist, but that's okay - records are deleted
|
||||
} else {
|
||||
// Some other error occurred, but we'll still clean up the database
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// Zone deletion failed, but we've deleted records, so continue
|
||||
}
|
||||
|
||||
// Remove subdomain info from database
|
||||
$ip->subdomain = null;
|
||||
$ip->save();
|
||||
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
// Even if there's an error, try to clean up the database
|
||||
try {
|
||||
$ip = IPAddress::find($ipAddressId);
|
||||
if ($ip) {
|
||||
$ip->subdomain = null;
|
||||
$ip->save();
|
||||
}
|
||||
} catch (\Exception $dbException) {
|
||||
// Ignore database cleanup errors
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get zones associated with a server (from database)
|
||||
*/
|
||||
public function getServerZones(int $serverId): array
|
||||
{
|
||||
$serverZones = [];
|
||||
|
||||
// Get all IP addresses for this server that have subdomains
|
||||
$ipAddresses = IPAddress::where('server_id', $serverId)
|
||||
->whereNotNull('subdomain')
|
||||
->get();
|
||||
|
||||
foreach ($ipAddresses as $ip) {
|
||||
$subdomainData = $ip->subdomain;
|
||||
if (isset($subdomainData['subdomain'])) {
|
||||
$subdomain = $subdomainData['subdomain'];
|
||||
$fullDomain = $subdomain . '.dns.virtnet.host';
|
||||
|
||||
// Get records from DNS API
|
||||
$zoneRecords = $this->getZoneRecords($subdomain);
|
||||
|
||||
$serverZones[] = [
|
||||
'subdomain' => $subdomain,
|
||||
'fullDomain' => $fullDomain,
|
||||
'ipAddress' => $ip->ip,
|
||||
'ipAddressId' => $ip->id,
|
||||
'records' => $subdomainData['records'] ?? [],
|
||||
'zoneRecords' => $zoneRecords,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $serverZones;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the IP address from zone records
|
||||
*/
|
||||
private function getZoneIpAddress(array $zoneDetails): ?string
|
||||
{
|
||||
$records = $zoneDetails['records'] ?? [];
|
||||
foreach ($records as $record) {
|
||||
if (isset($record['type']) && in_array($record['type'], ['A', 'AAAA'])) {
|
||||
// Check both rdata and ipAddress fields
|
||||
$ipAddress = $record['rdata'] ?? $record['ipAddress'] ?? null;
|
||||
if ($ipAddress) {
|
||||
return $ipAddress;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a DNS record to a zone
|
||||
*/
|
||||
public function addRecord(string $subdomain, string $name, string $type, string $rdata, int $ttl = 3600): bool
|
||||
{
|
||||
try {
|
||||
$fullDomain = $subdomain . '.dns.virtnet.host';
|
||||
$recordName = $name === '@' ? $fullDomain : $name . '.' . $fullDomain;
|
||||
|
||||
$response = $this->request('/zones/records/add', 'post', [
|
||||
'zone' => $fullDomain,
|
||||
'domain' => $recordName,
|
||||
'type' => $type,
|
||||
'ipAddress' => $rdata,
|
||||
'ttl' => $ttl,
|
||||
]);
|
||||
|
||||
$json = $response->json();
|
||||
return $json['status'] === 'ok';
|
||||
} catch (\Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a DNS record from a zone
|
||||
*/
|
||||
public function deleteRecord(string $subdomain, string $name, string $type, ?string $ipAddress = null): bool
|
||||
{
|
||||
try {
|
||||
$fullDomain = $subdomain . '.dns.virtnet.host';
|
||||
$recordName = $name === '@' ? $fullDomain : $name . '.' . $fullDomain;
|
||||
|
||||
$params = [
|
||||
'zone' => $fullDomain,
|
||||
'domain' => $recordName,
|
||||
'type' => $type,
|
||||
];
|
||||
|
||||
// Add ipAddress parameter if provided (required for A/AAAA records)
|
||||
if ($ipAddress !== null) {
|
||||
$params['ipAddress'] = $ipAddress;
|
||||
}
|
||||
|
||||
$response = $this->request('/zones/records/delete', 'post', $params);
|
||||
|
||||
$json = $response->json();
|
||||
return $json['status'] === 'ok';
|
||||
} catch (\Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all records for a zone
|
||||
*/
|
||||
public function getZoneRecords(string $subdomain): array
|
||||
{
|
||||
try {
|
||||
$fullDomain = $subdomain . '.dns.virtnet.host';
|
||||
$zoneDetails = $this->getZone($fullDomain);
|
||||
|
||||
if (!$zoneDetails) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$records = $zoneDetails['records'] ?? [];
|
||||
$formattedRecords = [];
|
||||
|
||||
foreach ($records as $record) {
|
||||
if (!in_array($record['type'] ?? '', ['A', 'AAAA'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$domain = $record['domain'] ?? '';
|
||||
// Extract sub-subdomain name
|
||||
if ($domain === $fullDomain) {
|
||||
$name = '@';
|
||||
} else {
|
||||
$name = str_replace('.' . $fullDomain, '', $domain);
|
||||
}
|
||||
|
||||
// Get IP address from either 'rdata' or 'ipAddress' field
|
||||
$ipAddress = $record['rdata'] ?? $record['ipAddress'] ?? '';
|
||||
|
||||
$formattedRecords[] = [
|
||||
'name' => $name,
|
||||
'type' => $record['type'] ?? '',
|
||||
'rdata' => $ipAddress,
|
||||
'ttl' => $record['ttl'] ?? 3600,
|
||||
];
|
||||
}
|
||||
|
||||
return $formattedRecords;
|
||||
} catch (\Exception $e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user