command->warn("BASE_DOMAIN ist 'example.com' – Seeder überspringt produktive Einträge."); return; } $systemSub = env('SYSTEM_SUB', 'system'); $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}"); } // SPF – einfachen Default bauen $serverIp = env('SERVER_IP'); // optional vom Installer rein schreiben $parts = ['v=spf1','mx','a']; if ($serverIp) $parts[] = "ip4:{$serverIp}"; $parts[] = '-all'; $spf = implode(' ', $parts); SpfRecord::firstOrCreate( ['domain_id' => $domain->id, 'record_txt' => $spf], ['is_active' => true] ); // DMARC – vorsichtig starten (p=none) $rua = "mailto:dmarc@{$base}"; $dmarc = DmarcRecord::firstOrCreate( ['domain_id' => $domain->id, 'policy' => 'none'], ['rua' => $rua, 'pct' => 100, 'record_txt' => "v=DMARC1; p=none; rua={$rua}; pct=100", 'is_active' => true] ); $this->command->info("System-Domain '{$base}' fertig. SPF/DMARC/DKIM eingetragen."); $this->command->line("DNS-Hinweise:"); $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())); } } }