>|null $domainNames */ public function syncDomainsForUser(User $user, ?array $domainNames = null): void { $query = Domain::query()->where('user_id', $user->id); if ($domainNames !== null) { $normalized = $this->normalizeDomainNames($domainNames); if ($normalized === []) { return; } $query->whereIn('domain', $normalized); } /** @var EloquentCollection $domains */ $domains = $query->get(); foreach ($domains as $domain) { $this->syncDomain($domain); } } public function syncDomain(Domain $domain): void { try { $records = $this->ensureDnsRecords($domain); if ($records->isEmpty()) { return; } $settings = DnsSetting::getAll(); $hostname = $this->getServerHostname(); $serverIp = $this->getServerIp(); $serverIpv6 = $settings['default_ipv6'] ?? null; $this->agent->send('dns.sync_zone', [ 'domain' => $domain->domain, 'records' => $this->formatRecords($records), 'ns1' => $settings['ns1'] ?? "ns1.{$hostname}", 'ns2' => $settings['ns2'] ?? "ns2.{$hostname}", 'admin_email' => $settings['admin_email'] ?? "admin.{$hostname}", 'default_ip' => $settings['default_ip'] ?? $serverIp, 'default_ipv6' => $serverIpv6, 'default_ttl' => (int) ($settings['default_ttl'] ?? 3600), ]); } catch (Exception $e) { Log::warning("Failed to sync DNS zone for {$domain->domain}: {$e->getMessage()}"); } } /** * @return Collection */ protected function ensureDnsRecords(Domain $domain): Collection { $records = $domain->dnsRecords()->get(); $defaultRecords = $this->getDefaultRecords($domain); foreach ($defaultRecords as $record) { if (! $this->shouldCreateDefaultRecord($records, $record)) { continue; } $domain->dnsRecords()->create($record); } return $domain->dnsRecords()->get(); } /** * @return array> */ protected function getDefaultRecords(Domain $domain): array { $settings = DnsSetting::getAll(); $defaultIp = $domain->ip_address ?: ($settings['default_ip'] ?? $this->getServerIp()); $defaultIpv6 = $domain->ipv6_address ?: ($settings['default_ipv6'] ?? null); $defaultTtl = (int) ($settings['default_ttl'] ?? 3600); $hostname = $this->getServerHostname(); $ns1 = $settings['ns1'] ?? "ns1.{$hostname}"; $ns2 = $settings['ns2'] ?? "ns2.{$hostname}"; $records = [ ['name' => '@', 'type' => 'NS', 'content' => $ns1, 'ttl' => $defaultTtl], ['name' => '@', 'type' => 'NS', 'content' => $ns2, 'ttl' => $defaultTtl], ['name' => '@', 'type' => 'A', 'content' => $defaultIp, 'ttl' => $defaultTtl], ['name' => 'www', 'type' => 'A', 'content' => $defaultIp, 'ttl' => $defaultTtl], ['name' => 'mail', 'type' => 'A', 'content' => $defaultIp, 'ttl' => $defaultTtl], ['name' => '@', 'type' => 'MX', 'content' => "mail.{$domain->domain}", 'ttl' => $defaultTtl, 'priority' => 10], ['name' => '@', 'type' => 'TXT', 'content' => 'v=spf1 mx a ~all', 'ttl' => $defaultTtl], ['name' => '_dmarc', 'type' => 'TXT', 'content' => "v=DMARC1; p=none; rua=mailto:postmaster@{$domain->domain}", 'ttl' => $defaultTtl], ]; if (! empty($defaultIpv6)) { $records[] = ['name' => '@', 'type' => 'AAAA', 'content' => $defaultIpv6, 'ttl' => $defaultTtl]; $records[] = ['name' => 'www', 'type' => 'AAAA', 'content' => $defaultIpv6, 'ttl' => $defaultTtl]; $records[] = ['name' => 'mail', 'type' => 'AAAA', 'content' => $defaultIpv6, 'ttl' => $defaultTtl]; } $records = $this->appendNameserverRecords( $records, $domain->domain, $ns1, $ns2, $defaultIp, $defaultIpv6, $defaultTtl ); return $records; } /** * @param Collection $records * @param array $record */ protected function shouldCreateDefaultRecord(Collection $records, array $record): bool { $name = $record['name'] ?? ''; $type = $record['type'] ?? ''; if ($type === 'TXT' && $name === '@') { return ! $records->contains(function (DnsRecord $existing): bool { return $existing->type === 'TXT' && $existing->name === '@' && str_contains(strtolower($existing->content), 'v=spf1'); }); } if ($type === 'TXT' && $name === '_dmarc') { return ! $records->contains(function (DnsRecord $existing): bool { return $existing->type === 'TXT' && $existing->name === '_dmarc'; }); } return ! $records->contains(function (DnsRecord $existing) use ($name, $type): bool { return $existing->type === $type && $existing->name === $name; }); } /** * @param array> $records * @return array> */ protected function appendNameserverRecords( array $records, string $domain, string $ns1, string $ns2, string $ipv4, ?string $ipv6, int $ttl ): array { $labels = $this->getNameserverLabels($domain, [$ns1, $ns2]); if ($labels === []) { return $records; } $existingA = array_map( fn (array $record): string => $record['name'] ?? '', array_filter($records, fn (array $record): bool => ($record['type'] ?? '') === 'A') ); $existingAAAA = array_map( fn (array $record): string => $record['name'] ?? '', array_filter($records, fn (array $record): bool => ($record['type'] ?? '') === 'AAAA') ); foreach ($labels as $label) { if (! in_array($label, $existingA, true)) { $records[] = ['name' => $label, 'type' => 'A', 'content' => $ipv4, 'ttl' => $ttl]; } if ($ipv6 && ! in_array($label, $existingAAAA, true)) { $records[] = ['name' => $label, 'type' => 'AAAA', 'content' => $ipv6, 'ttl' => $ttl]; } } return $records; } /** * @param array $nameservers * @return array */ protected function getNameserverLabels(string $domain, array $nameservers): array { $domain = rtrim($domain, '.'); $labels = []; foreach ($nameservers as $nameserver) { $nameserver = rtrim($nameserver ?? '', '.'); if ($nameserver === '') { continue; } if ($nameserver === $domain) { $label = '@'; } elseif (str_ends_with($nameserver, '.'.$domain)) { $label = substr($nameserver, 0, -strlen('.'.$domain)); } else { continue; } if ($label !== '@') { $labels[] = $label; } } return array_values(array_unique($labels)); } /** * @param Collection $records * @return array> */ protected function formatRecords(Collection $records): array { return $records->map(static function (DnsRecord $record): array { return [ 'name' => $record->name, 'type' => $record->type, 'content' => $record->content, 'ttl' => $record->ttl, 'priority' => $record->priority, ]; })->all(); } /** * @param array> $domains * @return array */ protected function normalizeDomainNames(array $domains): array { $names = []; foreach ($domains as $domain) { if (is_array($domain)) { $name = $domain['name'] ?? $domain['domain'] ?? null; } else { $name = $domain; } if (! is_string($name) || $name === '') { continue; } $names[] = strtolower(trim($name)); } return array_values(array_unique($names)); } protected function getServerHostname(): string { return gethostname() ?: 'localhost'; } protected function getServerIp(): string { $ip = trim(shell_exec("hostname -I | awk '{print $1}'") ?? ''); return $ip !== '' ? $ip : '127.0.0.1'; } }