command->warn("BASE_DOMAIN ist 'example.com' – Seeder überspringt produktive Einträge."); return; } $mtaSub = strtolower(env('MTA_SUB', 'mx') ?: 'mx'); // z.B. mx $systemSub = strtolower(config('mailpool.platform_system_zone') ?: 'sysmail'); // z.B. sysmail $serverFqdn = strtolower("{$mtaSub}.{$platformBase}"); // mx.nexlab.at $systemFqdn = strtolower("{$systemSub}.{$platformBase}"); // 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, // nix anlegen unter mx. 'max_mailboxes' => 0, 'max_aliases' => 0, // Quotas praktisch sperren 'default_quota_mb' => 0, 'max_quota_per_mailbox_mb' => 0, 'total_quota_mb' => 0, // kein Versand über diese Domain 'rate_limit_per_hour' => 0, 'rate_limit_override' => false, ] ); $serverDomain->fill([ 'is_active' => true, 'is_system' => false, 'is_server' => true, 'max_mailboxes' => 0, 'max_aliases' => 0, 'default_quota_mb' => 0, 'max_quota_per_mailbox_mb' => 0, 'total_quota_mb' => 0, 'rate_limit_per_hour' => 0, 'rate_limit_override' => false, ])->save(); Domain::where('is_server', true) ->where('id', '!=', $serverDomain->id) ->update(['is_server' => false]); // Falls bereits vorhanden, sicherstellen, dass is_server=true bleibt 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 noch nicht vorhanden) ------- $hasTlsa = TlsaRecord::where('domain_id', $serverDomain->id) ->where('host', $serverFqdn) ->where('service', '_25._tcp') ->exists(); if (!$hasTlsa) { // TlsaService nutzt denselben Host aus ENV (mx.) – passt zu serverFqdn. // $tlsa = app(TlsaService::class)->refreshForServerDomain($serverDomain); $tlsa = app(TlsaService::class)->refreshForMx('_25._tcp'); if ($tlsa) { $this->command->info("TLSA erstellt: _25._tcp.{$tlsa->host} 3 1 1 {$tlsa->hash}"); } else { $path = "/etc/letsencrypt/live/{$serverFqdn}/fullchain.pem"; $this->command->warn("TLSA übersprungen: LE-Zertifikat (noch) nicht vorhanden unter {$path}"); } } 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, // Limits 'max_aliases' => 20, 'max_mailboxes'=> 1, // Quota 'default_quota_mb' => 512, 'max_quota_per_mailbox_mb' => 2048, 'total_quota_mb' => 2048, // Rate limiting 'rate_limit_per_hour' => 600, 'rate_limit_override' => false, ] ); $systemDomain->fill([ 'is_active' => true, 'is_system' => true, 'max_aliases' => 20, 'max_mailboxes' => 1, 'default_quota_mb' => 512, 'max_quota_per_mailbox_mb' => 2048, 'total_quota_mb' => 2048, 'rate_limit_per_hour' => 600, 'rate_limit_override' => false, ])->save(); if ($systemDomain->wasRecentlyCreated) { $this->command->line("System-Domain angelegt: {$systemDomain->domain}"); } $noReply = MailUser::firstOrCreate( ['domain_id' => $systemDomain->id, 'localpart' => 'no-reply'], [ 'password_hash' => null, 'is_active' => true, 'is_system' => true, 'quota_mb' => 0, ] ); $addRecipient = function (MailAlias $alias, MailUser $user) { // sichere, vollständige Adresse bauen $user->loadMissing('domain'); $addr = $user->localpart.'@'.$user->domain->domain; MailAliasRecipient::create([ 'alias_id' => $alias->id, 'mail_user_id' => $user->id, // Referenz 'email' => $addr, // denormalisierte, lesbare Adresse 'position' => 0, ]); }; $seedGroup = function (string $local, MailUser $user) use ($systemDomain, $addRecipient) { $alias = MailAlias::updateOrCreate( ['domain_id' => $systemDomain->id, 'local' => $local], ['type' => 'group', 'is_active' => true, 'is_system' => true] ); $alias->recipients()->delete(); $addRecipient($alias, $user); }; $seedGroup('system', $noReply); $seedGroup('bounces', $noReply); $seedGroup('postmaster', $noReply); $seedGroup('abuse', $noReply); $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 reinem Base64 für DKIM p= 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\Models\TlsaRecord; // //// ← NEU: fürs exists() //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'; // $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 { // $path = "/etc/letsencrypt/live/{$serverDomain->domain}/fullchain.pem"; // $this->command->warn("TLSA übersprungen: LE-Zertifikat (noch) nicht vorhanden unter {$path}"); // } // } // // // ========================================================================= // // 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())); // } // } //}