command->warn("BASE_DOMAIN ist 'example.com' – Seeder überspringt produktive Einträge."); return; } $mtaSub = env('MTA_SUB', 'mx') ?: 'mx'; // z.B. mx $serverFqdn = "{$mtaSub}.{$platformBase}"; // z.B. mx.nexlab.at $systemSub = config('mailpool.platform_system_zone') ?: 'sysmail'; $systemFqdn = "{$systemSub}.{$platformBase}"; // z.B. sysmail.nexlab.at // ========================================================================= // 1) MAILSERVER-DOMAIN zuerst (mx.), is_server = true // ========================================================================= $serverDomain = Domain::firstOrCreate( ['domain' => $serverFqdn], ['is_active' => true, 'is_system' => false, 'is_server' => true] ); if (!$serverDomain->is_server) { $serverDomain->is_server = true; $serverDomain->save(); $this->command->info("Server-Host markiert: {$serverDomain->domain}"); } // --- TLSA (3 1 1) prüfen/erzeugen (nur wenn LE-Zert schon da ist) ----- $hasTlsa = TlsaRecord::where('domain_id', $serverDomain->id) ->where('host', $serverFqdn) ->where('service', '_25._tcp') ->exists(); if (!$hasTlsa) { $tlsa = app(TlsaService::class)->refreshForServerDomain($serverDomain); if ($tlsa) { $this->command->info("TLSA erstellt: _25._tcp.{$tlsa->host} 3 1 1 {$tlsa->hash}"); } else { $this->command->warn("TLSA übersprungen: LE-Zertifikat (noch) nicht vorhanden – später erneut seeden."); } } else { $this->command->line("TLSA bereits vorhanden, übersprungen."); } // ========================================================================= // 2) SYSTEM-DOMAIN danach (sysmail.), is_system = true // ========================================================================= $systemDomain = Domain::firstOrCreate( ['domain' => $systemFqdn], ['is_active' => true, 'is_system' => true] ); // System-Absender (no-reply) – ohne Passwort (kein Login) MailUser::firstOrCreate( ['email' => "no-reply@{$systemFqdn}"], [ 'domain_id' => $systemDomain->id, 'localpart' => 'no-reply', 'password_hash' => null, 'is_active' => true, 'is_system' => true, 'must_change_pw' => false, 'quota_mb' => 0, ] ); // DKIM – Key erzeugen, falls keiner aktiv existiert if (!$systemDomain->dkimKeys()->where('is_active', true)->exists()) { [$privPem, $pubTxt] = $this->generateDkimKeyPair(); $selector = 'mwl1'; // frei wählbar, später rotieren DkimKey::create([ 'domain_id' => $systemDomain->id, 'selector' => $selector, 'private_key_pem' => $privPem, 'public_key_txt' => $pubTxt, 'is_active' => true, ]); $this->command->info("DKIM angelegt: Host = {$selector}._domainkey.{$systemFqdn}"); } $dk = $systemDomain->dkimKeys()->where('is_active', true)->latest()->first(); $dkimTxt = $dk ? "v=DKIM1; k=rsa; p={$dk->public_key_txt}" : null; app(DnsRecordService::class)->provision( $systemDomain, dkimSelector: $dk?->selector, dkimTxt: $dkimTxt, opts: [ 'dmarc_policy' => 'none', 'spf_tail' => '-all', // optional: 'ipv4' => $serverIp, 'ipv6' => ... ] ); $this->command->info("System-Domain '{$systemFqdn}' fertig. SPF/DMARC/DKIM & DNS-Empfehlungen eingetragen."); $this->printDnsHints($systemDomain); } /** @return array{0:string privatePem,1:string publicTxt} */ private function generateDkimKeyPair(): array { $res = openssl_pkey_new([ 'private_key_bits' => 2048, 'private_key_type' => OPENSSL_KEYTYPE_RSA, ]); openssl_pkey_export($res, $privateKeyPem); $details = openssl_pkey_get_details($res); $pubDer = $details['key']; $publicTxt = trim(preg_replace('/-----(BEGIN|END) PUBLIC KEY-----|\s+/', '', $pubDer)); return [$privateKeyPem, $publicTxt]; } private function printDnsHints(Domain $domain): void { $base = $domain->domain; $dkim = $domain->dkimKeys()->where('is_active', true)->latest()->first(); if ($dkim) { $this->command->line(" • DKIM TXT @ {$dkim->selector}._domainkey.{$base}"); $this->command->line(" v=DKIM1; k=rsa; p={$dkim->public_key_txt}"); } $spf = $domain->spf()->where('is_active', true)->latest()->first(); if ($spf) { $this->command->line(" • SPF TXT @ {$base}"); $this->command->line(" {$spf->record_txt}"); } $dmarc = $domain->dmarc()->where('is_active', true)->latest()->first(); if ($dmarc) { $this->command->line(" • DMARC TXT @ _dmarc.{$base}"); $this->command->line(" " . ($dmarc->record_txt ?? $dmarc->renderTxt())); } } } //namespace Database\Seeders; // //use App\Models\DkimKey; //use App\Models\Domain; //use App\Models\MailUser; //use App\Services\DnsRecordService; //use App\Services\TlsaService; //use Illuminate\Database\Seeder; // //class SystemDomainSeeder extends Seeder //{ // public function run(): void // { // // --- Basiswerte aus Config/ENV --- // $platformBase = config('mailpool.platform_zone', env('BASE_DOMAIN', 'example.com')); // z.B. nexlab.at // if (!$platformBase || $platformBase === 'example.com') { // $this->command->warn("BASE_DOMAIN ist 'example.com' – Seeder überspringt produktive Einträge."); // return; // } // // $mtaSub = env('MTA_SUB', 'mx') ?: 'mx'; // z.B. mx // $serverFqdn = "{$mtaSub}.{$platformBase}"; // z.B. mx.nexlab.at // // $systemSub = config('mailpool.platform_system_zone') ?: 'sysmail'; // z.B. sysmail // $systemFqdn = "{$systemSub}.{$platformBase}"; // z.B. sysmail.nexlab.at // // // ========================================================================= // // 1) MAILSERVER-DOMAIN zuerst (mx.), is_server = true // // ========================================================================= // $serverDomain = Domain::firstOrCreate( // ['domain' => $serverFqdn], // ['is_active' => true, 'is_system' => false, 'is_server' => true] // ); // // if (!$serverDomain->is_server) { // $serverDomain->is_server = true; // $serverDomain->save(); // $this->command->info("Server-Host markiert: {$serverDomain->domain}"); // } // // // TLSA (3 1 1) nur in Laravel erzeugen/aktualisieren (falls LE-Zert bereits da ist) // if (! $serverDomain->tlsa()->exists()) { // $tlsa = app(\App\Services\TlsaService::class)->refreshForServerDomain($serverDomain); // if ($tlsa) { // $this->command->info("TLSA erstellt: _25._tcp.{$tlsa->host} 3 1 1 {$tlsa->hash}"); // } else { // $this->command->warn("TLSA übersprungen: LE-Zertifikat (noch) nicht vorhanden – später erneut seeden."); // } // } else { // $this->command->line("TLSA bereits vorhanden, übersprungen."); // } // // // ========================================================================= // // 2) SYSTEM-DOMAIN danach (sysmail.), is_system = true // // ========================================================================= // $systemDomain = Domain::firstOrCreate( // ['domain' => $systemFqdn], // ['is_active' => true, 'is_system' => true] // ); // // // System-Absender (no-reply) – ohne Passwort (kein Login) // MailUser::firstOrCreate( // ['email' => "no-reply@{$systemFqdn}"], // [ // 'domain_id' => $systemDomain->id, // 'localpart' => 'no-reply', // 'password_hash' => null, // 'is_active' => true, // 'is_system' => true, // 'must_change_pw' => false, // 'quota_mb' => 0, // ] // ); // // // DKIM – Key erzeugen, falls keiner aktiv existiert // if (!$systemDomain->dkimKeys()->where('is_active', true)->exists()) { // [$privPem, $pubTxt] = $this->generateDkimKeyPair(); // $selector = 'mwl1'; // frei wählbar, später rotieren // // DkimKey::create([ // 'domain_id' => $systemDomain->id, // 'selector' => $selector, // 'private_key_pem' => $privPem, // 'public_key_txt' => $pubTxt, // 'is_active' => true, // ]); // // $this->command->info("DKIM angelegt: Host = {$selector}._domainkey.{$systemFqdn}"); // } // // $dk = $systemDomain->dkimKeys()->where('is_active', true)->latest()->first(); // $dkimTxt = $dk ? "v=DKIM1; k=rsa; p={$dk->public_key_txt}" : null; // // app(DnsRecordService::class)->provision( // $systemDomain, // dkimSelector: $dk?->selector, // dkimTxt: $dkimTxt, // opts: [ // 'dmarc_policy' => 'none', // 'spf_tail' => '-all', // // optional: 'ipv4' => $serverIp, 'ipv6' => ... // ] // ); // // $this->command->info("System-Domain '{$systemFqdn}' fertig. SPF/DMARC/DKIM & DNS-Empfehlungen eingetragen."); // $this->printDnsHints($systemDomain); // } // // /** @return array{0:string privatePem,1:string publicTxt} */ // private function generateDkimKeyPair(): array // { // $res = openssl_pkey_new([ // 'private_key_bits' => 2048, // 'private_key_type' => OPENSSL_KEYTYPE_RSA, // ]); // openssl_pkey_export($res, $privateKeyPem); // $details = openssl_pkey_get_details($res); // $pubDer = $details['key']; // // Public PEM zu "p=" (reines Base64) normalisieren // $publicTxt = trim(preg_replace('/-----(BEGIN|END) PUBLIC KEY-----|\s+/', '', $pubDer)); // return [$privateKeyPem, $publicTxt]; // } // // private function printDnsHints(Domain $domain): void // { // $base = $domain->domain; // // $dkim = $domain->dkimKeys()->where('is_active', true)->latest()->first(); // if ($dkim) { // $this->command->line(" • DKIM TXT @ {$dkim->selector}._domainkey.{$base}"); // $this->command->line(" v=DKIM1; k=rsa; p={$dkim->public_key_txt}"); // } // // $spf = $domain->spf()->where('is_active', true)->latest()->first(); // if ($spf) { // $this->command->line(" • SPF TXT @ {$base}"); // $this->command->line(" {$spf->record_txt}"); // } // // $dmarc = $domain->dmarc()->where('is_active', true)->latest()->first(); // if ($dmarc) { // $this->command->line(" • DMARC TXT @ _dmarc.{$base}"); // $this->command->line(" " . ($dmarc->record_txt ?? $dmarc->renderTxt())); // } // } //} // //---------------------------------------------------------------- // //namespace Database\Seeders; // //use App\Models\DkimKey; //use App\Models\Domain; //use App\Models\MailUser; //use App\Services\DnsRecordService; //use Illuminate\Database\Seeder; // //class SystemDomainSeeder extends Seeder //{ // public function run(): void // { // $base = config('mailpool.platform_zone', 'example.com'); // if (!$base || $base === 'example.com') { // $this->command->warn("BASE_DOMAIN ist 'example.com' – Seeder überspringt produktive Einträge."); // return; // } // // $systemSub = config('mailpool.platform_system_zone'); // $base = "{$systemSub}.{$base}"; // // // Domain anlegen/holen // $domain = Domain::firstOrCreate( // ['domain' => $base], // ['is_active' => true, 'is_system' => true] // ); // // // System Absender (no-reply) – ohne Passwort (kein Login) // MailUser::firstOrCreate( // ['email' => "no-reply@{$base}"], // [ // 'domain_id' => $domain->id, // 'localpart' => 'no-reply', // 'password_hash' => null, // 'is_active' => true, // 'is_system' => true, // 'must_change_pw' => false, // 'quota_mb' => 0, // ] // ); // // // DKIM – Key erzeugen, falls keiner aktiv existiert // if (! $domain->dkimKeys()->where('is_active', true)->exists()) { // [$privPem, $pubTxt] = $this->generateDkimKeyPair(); // $selector = 'mwl1'; // frei wählbar, z. B. rotierend später // // DkimKey::create([ // 'domain_id' => $domain->id, // 'selector' => $selector, // 'private_key_pem'=> $privPem, // 'public_key_txt' => $pubTxt, // 'is_active' => true, // ]); // // $this->command->info("DKIM angelegt: Host = {$selector}._domainkey.{$base}"); // } // // $dk = $domain->dkimKeys()->where('is_active', true)->latest()->first(); // $dkimTxt = $dk ? "v=DKIM1; k=rsa; p={$dk->public_key_txt}" : null; // // app(DnsRecordService::class)->provision( // $domain, // dkimSelector: $dk?->selector, // dkimTxt: $dkimTxt, // opts: [ // 'dmarc_policy' => 'none', // 'spf_tail' => '-all', // // optional: 'ipv4' => $serverIp, 'ipv6' => ... // ] // ); // // $base = config('mailpool.platform_zone', env('BASE_DOMAIN', 'example.com')); // z.B. nexlab.at // $mta = env('MTA_SUB', 'mx') ?: 'mx'; // $serverHostFqdn = "{$mta}.{$base}"; // // $serverHostDomain = \App\Models\Domain::firstOrCreate( // ['domain' => $serverHostFqdn], // ['is_active' => true, 'is_system' => false, 'is_server' => true] // ); // // if (!$serverHostDomain->is_server) { // $serverHostDomain->is_server = true; // $serverHostDomain->save(); // $this->command->info("Server-Host markiert: {$serverHostDomain->domain}"); // } // // $tlsa = app(\App\Services\TlsaService::class)->refreshForServerDomain($serverHostDomain); // if ($tlsa) { // $this->command->info("TLSA gespeichert: _25._tcp.{$tlsa->host} 3 1 1 {$tlsa->hash}"); // } else { // $this->command->warn("TLSA übersprungen: LE-Zertifikat (noch) nicht vorhanden – später erneut seeden."); // } // // $this->command->info("System-Domain '{$base}' fertig. SPF/DMARC/DKIM & DNS-Empfehlungen eingetragen."); // $this->printDnsHints($domain); // } // // /** @return array{0:string privatePem,1:string publicTxt} */ // private function generateDkimKeyPair(): array // { // $res = openssl_pkey_new([ // 'private_key_bits' => 2048, // 'private_key_type' => OPENSSL_KEYTYPE_RSA, // ]); // openssl_pkey_export($res, $privateKeyPem); // $details = openssl_pkey_get_details($res); // // $details['key'] ist PEM, wir brauchen Base64 ohne Header/Footers // $pubDer = $details['key']; // // Public PEM zu "p=" Wert (reines Base64) normalisieren // $pubTxt = trim(preg_replace('/-----(BEGIN|END) PUBLIC KEY-----|\s+/', '', $pubDer)); // // return [$privateKeyPem, $pubTxt]; // } // // private function printDnsHints(Domain $domain): void // { // $base = $domain->domain; // $dkim = $domain->dkimKeys()->where('is_active', true)->latest()->first(); // if ($dkim) { // $this->command->line(" • DKIM TXT @ {$dkim->selector}._domainkey.{$base}"); // $this->command->line(" v=DKIM1; k=rsa; p={$dkim->public_key_txt}"); // } // // $spf = $domain->spf()->where('is_active', true)->latest()->first(); // if ($spf) { // $this->command->line(" • SPF TXT @ {$base}"); // $this->command->line(" {$spf->record_txt}"); // } // // $dmarc = $domain->dmarc()->where('is_active', true)->latest()->first(); // if ($dmarc) { // $this->command->line(" • DMARC TXT @ _dmarc.{$base}"); // $this->command->line(" " . ($dmarc->record_txt ?? $dmarc->renderTxt())); // } // } //}