safeKey($domain->domain); $selKey = $this->safeKey($selector, 32); // Disk "local" zeigt bei dir auf storage/app/private (siehe Kommentar in deinem Code) $disk = Storage::disk('local'); $baseRel = "dkim/{$dirKey}"; // unsere Dateien (alle Varianten, damit sowohl App als auch OpenDKIM glücklich sind) $privPemRel = "{$baseRel}/{$selKey}.pem"; // PKCS#1/PKCS#8 (App-Backup) $pubPemRel = "{$baseRel}/{$selKey}.pub"; // Public PEM (App-Backup) $privOKRel = "{$baseRel}/{$selKey}.private"; // <-- OpenDKIM erwartet *genau das* $dnsTxtRel = "{$baseRel}/{$selKey}.txt"; // TXT-String "v=DKIM1; k=rsa; p=..." // Absolute Pfade $privPemAbs = method_exists($disk, 'path') ? $disk->path($privPemRel) : storage_path('app/private/'.$privPemRel); $pubPemAbs = method_exists($disk, 'path') ? $disk->path($pubPemRel) : storage_path('app/private/'.$pubPemRel); $privOKAbs = method_exists($disk, 'path') ? $disk->path($privOKRel) : storage_path('app/private/'.$privOKRel); $dnsTxtAbs = method_exists($disk, 'path') ? $disk->path($dnsTxtRel) : storage_path('app/private/'.$dnsTxtRel); // Wenn schon vollständig vorhanden → direkt zurück if ($disk->exists($privOKRel) && $disk->exists($dnsTxtRel)) { $privatePem = $disk->exists($privPemRel) ? $disk->get($privPemRel) : $disk->get($privOKRel); $publicPem = $disk->exists($pubPemRel) ? $disk->get($pubPemRel) : null; $dnsTxt = $disk->get($dnsTxtRel); return [ 'selector' => $selKey, 'priv_path' => $privOKAbs, 'pub_path' => $pubPemAbs, 'private_pem' => $privatePem, 'public_pem' => $publicPem, 'dns_name' => "{$selKey}._domainkey", 'dns_txt' => $dnsTxt, 'bits' => $bits, ]; } // Neu generieren $disk->makeDirectory($baseRel); $res = openssl_pkey_new([ 'private_key_type' => OPENSSL_KEYTYPE_RSA, 'private_key_bits' => $bits, ]); if ($res === false) { throw new RuntimeException('DKIM: openssl_pkey_new() fehlgeschlagen: ' . (openssl_error_string() ?: 'unbekannt')); } $privatePem = ''; if (!openssl_pkey_export($res, $privatePem)) { throw new RuntimeException('DKIM: openssl_pkey_export() fehlgeschlagen: ' . (openssl_error_string() ?: 'unbekannt')); } $details = openssl_pkey_get_details($res); if ($details === false || empty($details['key'])) { throw new RuntimeException('DKIM: Public Key konnte nicht gelesen werden.'); } $publicPem = $details['key']; $publicBase64 = trim(preg_replace('/-----(BEGIN|END)\s+PUBLIC KEY-----|\s+/', '', $publicPem)); if (strlen($publicBase64) < 300) { throw new RuntimeException('DKIM: Public Key zu kurz – vermutlich Parsing-Fehler.'); } $dnsTxt = "v=DKIM1; k=rsa; p={$publicBase64}"; // Dateien schreiben (App + OpenDKIM) if (!$disk->put($privPemRel, $privatePem)) throw new RuntimeException("DKIM: Private-Key schreiben fehlgeschlagen: {$privPemRel}"); if (!$disk->put($pubPemRel, $publicPem)) throw new RuntimeException("DKIM: Public-Key schreiben fehlgeschlagen: {$pubPemRel}"); if (!$disk->put($privOKRel, $privatePem)) throw new RuntimeException("DKIM: OpenDKIM-Key schreiben fehlgeschlagen: {$privOKRel}"); if (!$disk->put($dnsTxtRel, $dnsTxt)) throw new RuntimeException("DKIM: DNS-TXT schreiben fehlgeschlagen: {$dnsTxtRel}"); // Dateirechte (best effort) @chmod($privPemAbs, 0600); @chmod($privOKAbs, 0600); @chmod($pubPemAbs, 0640); @chmod($dnsTxtAbs, 0644); // DB pflegen DkimKey::updateOrCreate( ['domain_id' => $domain->id, 'selector' => $selKey], [ 'private_key_pem' => $privatePem, 'public_key_txt' => $publicBase64, 'is_active' => true, ] ); // OpenDKIM einhängen (wenn Helper existiert) $helper = '/usr/local/sbin/mailwolt-install-dkim'; if (is_executable($helper)) { $cmd = [ 'sudo','-n', $helper, $domain->domain, $selKey, $privOKAbs, // …/storage/app/private/dkim/