diff --git a/app/Models/MailAlias.php b/app/Models/MailAlias.php index 09e01dc..a48a33a 100644 --- a/app/Models/MailAlias.php +++ b/app/Models/MailAlias.php @@ -8,8 +8,8 @@ use Illuminate\Database\Eloquent\Relations\HasMany; class MailAlias extends Model { - protected $fillable = ['domain_id','local','type','group_name','is_active','notes']; - protected $casts = ['is_active' => 'bool']; + protected $fillable = ['domain_id','local','type','group_name','is_active','is_system','notes']; + protected $casts = ['is_active' => 'bool', 'is_system' => 'bool']; public function domain(): BelongsTo { @@ -18,7 +18,7 @@ class MailAlias extends Model public function recipients(): HasMany { - return $this->hasMany(MailAliasRecipient::class, 'alias_id'); + return $this->hasMany(MailAliasRecipient::class, 'alias_id')->orderBy('position'); } public function getAddressAttribute(): string diff --git a/database/migrations/2025_09_27_153311_create_mail_users_table.php b/database/migrations/2025_09_27_153311_create_mail_users_table.php index d9d0e02..ddc5a21 100644 --- a/database/migrations/2025_09_27_153311_create_mail_users_table.php +++ b/database/migrations/2025_09_27_153311_create_mail_users_table.php @@ -24,6 +24,7 @@ return new class extends Migration $table->boolean('is_system')->default(false)->index(); // oft nach Systemkonten filtern $table->boolean('is_active')->default(true)->index(); + $table->boolean('can_login')->default(true)->index(); $table->unsignedInteger('quota_mb')->default(0); // 0 = unlimited $table->unsignedInteger('rate_limit_per_hour')->nullable(); diff --git a/database/migrations/2025_09_27_153328_create_mail_aliases_table.php b/database/migrations/2025_09_27_153328_create_mail_aliases_table.php index ba66fd7..3fe8b7a 100644 --- a/database/migrations/2025_09_27_153328_create_mail_aliases_table.php +++ b/database/migrations/2025_09_27_153328_create_mail_aliases_table.php @@ -19,6 +19,7 @@ return new class extends Migration $table->enum('type', ['single','group'])->default('single'); $table->string('group_name', 80)->nullable(); $table->boolean('is_active')->default(true)->index(); + $table->boolean('is_system')->default(false)->after('is_active')->index(); $table->text('notes')->nullable(); $table->timestamps(); diff --git a/database/migrations/2025_10_13_103122_create_mail_alias_recipients_table.php b/database/migrations/2025_09_27_153330_create_mail_alias_recipients_table.php similarity index 65% rename from database/migrations/2025_10_13_103122_create_mail_alias_recipients_table.php rename to database/migrations/2025_09_27_153330_create_mail_alias_recipients_table.php index ff96ca1..5febdb5 100644 --- a/database/migrations/2025_10_13_103122_create_mail_alias_recipients_table.php +++ b/database/migrations/2025_09_27_153330_create_mail_alias_recipients_table.php @@ -13,22 +13,12 @@ return new class extends Migration { Schema::create('mail_alias_recipients', function (Blueprint $table) { $table->id(); - - $table->foreignId('alias_id') - ->constrained('mail_aliases') - ->cascadeOnDelete(); - - // interner Empfänger (MailUser) ODER externer Empfänger (E-Mail) - $table->foreignId('mail_user_id') - ->nullable() - ->constrained('mail_users') // <-- richtige Tabelle! - ->nullOnDelete(); - + $table->foreignId('alias_id')->constrained('mail_aliases')->cascadeOnDelete(); + $table->foreignId('mail_user_id')->nullable()->constrained('mail_users')->nullOnDelete(); $table->string('email', 320)->nullable(); // externer Empfänger $table->unsignedSmallInteger('position')->default(0); $table->timestamps(); - // Duplikate vermeiden: $table->unique(['alias_id','mail_user_id']); $table->unique(['alias_id','email']); }); diff --git a/database/seeders/SystemDomainSeeder.php b/database/seeders/SystemDomainSeeder.php index 156dde3..4108264 100644 --- a/database/seeders/SystemDomainSeeder.php +++ b/database/seeders/SystemDomainSeeder.php @@ -4,11 +4,15 @@ namespace Database\Seeders; use App\Models\DkimKey; use App\Models\Domain; +use App\Models\MailAlias; +use App\Models\MailAliasRecipient; use App\Models\MailUser; use App\Models\TlsaRecord; use App\Services\DnsRecordService; use App\Services\TlsaService; use Illuminate\Database\Seeder; +use Illuminate\Foundation\Auth\User; +use Illuminate\Support\Str; class SystemDomainSeeder extends Seeder { @@ -137,7 +141,7 @@ class SystemDomainSeeder extends Seeder } // System-Absender (no-reply) – ohne Passwort (kein Login) - MailUser::firstOrCreate( + $noReply = MailUser::firstOrCreate( ['email' => "no-reply@{$systemFqdn}"], [ 'domain_id' => $systemDomain->id, @@ -149,35 +153,27 @@ class SystemDomainSeeder extends Seeder ] ); - // 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 + $seedGroup = function(string $local, array $emails) use ($systemDomain, $noReply) { + $alias = MailAlias::updateOrCreate( + ['domain_id' => $systemDomain->id, 'local' => $local], + ['type' => 'group', 'is_active' => true, 'is_system' => true] + ); + $alias->recipients()->delete(); + $pos=0; + foreach ($emails as $addr) { + MailAliasRecipient::create([ + 'alias_id' => $alias->id, + 'email' => $addr, + 'position' => $pos++, + ]); + } + }; - 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' => ... - ] - ); +// alle vier erst einmal nur ans no-reply Postfach + $seedGroup('system', [$noReply->email]); + $seedGroup('bounces', [$noReply->email]); + $seedGroup('postmaster', [$noReply->email]); + $seedGroup('abuse', [$noReply->email]); $this->command->info("System-Domain '{$systemFqdn}' fertig. SPF/DMARC/DKIM & DNS-Empfehlungen eingetragen."); $this->printDnsHints($systemDomain);