diff --git a/app/Livewire/Setup/Wizard.php b/app/Livewire/Setup/Wizard.php index 29fb65a..893ac09 100644 --- a/app/Livewire/Setup/Wizard.php +++ b/app/Livewire/Setup/Wizard.php @@ -2,212 +2,148 @@ namespace App\Livewire\Setup; -use App\Jobs\ProvisionCertJob; -use App\Support\Setting; -use App\Models\SystemTask; +use App\Models\Setting; use App\Models\User; -use App\Support\EnvWriter; -use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Hash; -use Illuminate\Support\Facades\Redis; -use Livewire\Attributes\Validate; +use Livewire\Attributes\Layout; +use Livewire\Attributes\Title; use Livewire\Component; +#[Layout('layouts.setup')] +#[Title('Einrichtung · Mailwolt')] class Wizard extends Component { public int $step = 1; + public int $totalSteps = 4; - // Step 1 - #[Validate('required|string|min:3')] - public string $form_domain = ''; + // Schritt 1 — System + public string $instance_name = 'Mailwolt'; + public string $locale = 'de'; + public string $timezone = 'Europe/Berlin'; - #[Validate('required|timezone')] - public string $form_timezone = 'UTC'; + // Schritt 2 — Domains + public string $ui_domain = ''; + public string $mail_domain = ''; + public string $webmail_domain = ''; - public bool $form_cert_force_https = true; + // Schritt 3 — Admin-Account + public string $admin_name = ''; + public string $admin_email = ''; + public string $admin_password = ''; + public string $admin_password_confirmation = ''; - // Step 2 - #[Validate('required|string|min:3')] - public string $form_admin_name = ''; - - #[Validate('required|email')] - public string $form_admin_email = ''; - - /** optional: wenn du Login auch über username erlauben willst */ - public ?string $form_admin_username = null; - - #[Validate('required|string|min:8|same:form_admin_password_confirmation')] - public string $form_admin_password = ''; - - public string $form_admin_password_confirmation = ''; - - // Step 3 (Zertifikat) - public bool $form_cert_create_now = false; - - #[Validate('required_if:form_cert_create_now,true|email')] - public string $form_cert_email = ''; - - public function nextStep() + public function mount(): void { - if ($this->step === 1) { - $this->validateOnly('form_domain'); - $this->validateOnly('form_timezone'); - } elseif ($this->step === 2) { - $this->validate([ - 'form_admin_name' => 'required|string|min:3', - 'form_admin_email' => 'required|email', - 'form_admin_password' => 'required|string|min:8|same:form_admin_password_confirmation', - ]); - } - - $this->step = min($this->step + 1, 3); + $this->instance_name = config('app.name', 'Mailwolt'); + $this->timezone = Setting::get('timezone', 'Europe/Berlin'); + $this->locale = Setting::get('locale', 'de'); + $this->ui_domain = Setting::get('ui_domain', ''); + $this->mail_domain = Setting::get('mail_domain', ''); + $this->webmail_domain = Setting::get('webmail_domain', ''); } - public function prevStep() + public function next(): void + { + match ($this->step) { + 1 => $this->validate([ + 'instance_name' => 'required|string|min:2|max:64', + 'locale' => 'required|in:de,en,fr', + 'timezone' => 'required|timezone', + ]), + 2 => $this->validate([ + 'ui_domain' => ['required', 'regex:/^(?!https?:\/\/)(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/'], + 'mail_domain' => ['required', 'regex:/^(?!https?:\/\/)(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/'], + 'webmail_domain' => ['required', 'regex:/^(?!https?:\/\/)(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/'], + ], [ + 'ui_domain.required' => 'Pflichtfeld.', + 'mail_domain.required' => 'Pflichtfeld.', + 'webmail_domain.required' => 'Pflichtfeld.', + 'ui_domain.regex' => 'Ungültige Domain — kein Schema (http://) erlaubt.', + 'mail_domain.regex' => 'Ungültige Domain — kein Schema (http://) erlaubt.', + 'webmail_domain.regex' => 'Ungültige Domain — kein Schema (http://) erlaubt.', + ]), + 3 => $this->validate([ + 'admin_name' => 'required|string|min:2|max:64', + 'admin_email' => 'required|email|max:190', + 'admin_password' => 'required|string|min:10|same:admin_password_confirmation', + 'admin_password_confirmation' => 'required', + ], [ + 'admin_password.min' => 'Mindestens 10 Zeichen.', + 'admin_password.same' => 'Passwörter stimmen nicht überein.', + ]), + default => null, + }; + + $this->step = min($this->step + 1, $this->totalSteps); + } + + public function back(): void { $this->step = max($this->step - 1, 1); } - public function finish() + public function finish(): mixed { - // Step 3 Validierung (nur wenn sofort erstellen) - if ($this->form_cert_create_now) { - $this->validateOnly('form_cert_email'); - } - - // 1) Settings persistieren - Setting::set('app.domain', $this->form_domain); - Setting::set('app.timezone', $this->form_timezone); - Setting::set('app.force_https', (bool)$this->form_cert_force_https); - - // Optional: .env spiegeln, damit URLs/HMR etc. sofort passen - $scheme = $this->form_cert_force_https ? 'https' : 'http'; - EnvWriter::set([ - 'APP_HOST' => $this->form_domain, - 'APP_URL' => "{$scheme}://{$this->form_domain}", - 'APP_TIMEZONE' => $this->form_timezone, + // Schritt-3-Validierung nochmals sicherstellen + $this->validate([ + 'admin_name' => 'required|string|min:2|max:64', + 'admin_email' => 'required|email|max:190', + 'admin_password' => 'required|string|min:10|same:admin_password_confirmation', ]); - // 2) Admin anlegen/aktualisieren - $user = User::query() - ->where('email', $this->form_admin_email) - ->when($this->form_admin_username, fn($q) => $q->orWhere('username', $this->form_admin_username) - ) - ->first(); + // Settings speichern + Setting::setMany([ + 'locale' => $this->locale, + 'timezone' => $this->timezone, + 'ui_domain' => $this->ui_domain, + 'mail_domain' => $this->mail_domain, + 'webmail_domain' => $this->webmail_domain, + 'setup_completed' => '1', + ]); - if (!$user) { - $user = new User(); - $user->email = $this->form_admin_email; - if ($this->form_admin_username) { - $user->username = $this->form_admin_username; - } - } else { - // vorhandene Email/Username harmonisieren - $user->email = $this->form_admin_email; - if ($this->form_admin_username) { - $user->username = $this->form_admin_username; - } - } + // .env aktualisieren + $this->writeEnv([ + 'APP_NAME' => $this->instance_name, + 'APP_HOST' => $this->ui_domain, + 'APP_URL' => 'https://' . $this->ui_domain, + 'MTA_SUB' => explode('.', $this->mail_domain)[0] ?? '', + 'WEBMAIL_DOMAIN' => $this->webmail_domain, + ]); - $user->name = $this->form_admin_name; - $user->is_admin = true; - $user->password = Hash::make($this->form_admin_password); - $user->required_change_password = true; + // Admin anlegen oder aktualisieren + $user = User::where('email', $this->admin_email)->first() ?? new User(); + $user->name = $this->admin_name; + $user->email = $this->admin_email; + $user->password = Hash::make($this->admin_password); + $user->role = 'admin'; $user->save(); - // 3) Zertifikat jetzt ausstellen (optional) - $taskKey = 'issue-cert:' . $this->form_domain; + return redirect()->route('login')->with('setup_done', true); + } - if ($this->form_cert_create_now) { - SystemTask::updateOrCreate( - ['key' => $taskKey], - [ - 'type' => 'issue-cert', - 'status' => 'queued', - 'message' => 'Warte auf Ausführung…', - 'payload' => [ - 'domain' => $this->form_domain, - 'email' => $this->form_cert_email, - 'mode' => 'letsencrypt' - ], - ] - ); + private function writeEnv(array $values): void + { + $path = base_path('.env'); + $content = @file_get_contents($path) ?: ''; - Cache::store('redis')->put($taskKey, [ - 'type' => 'issue-cert', - 'status' => 'queued', - 'message' => 'Warte auf Ausführung…', - 'payload' => [ - 'domain' => $this->form_domain, - 'email' => $this->form_cert_create_now ? $this->form_cert_email : null, - 'mode' => $this->form_cert_create_now ? 'letsencrypt' : 'self-signed', - ], - ], now()->addMinutes(30)); + foreach ($values as $key => $value) { + $escaped = str_contains($value, ' ') ? '"' . $value . '"' : $value; + $line = $key . '=' . $escaped; + $pattern = '/^' . preg_quote($key, '/') . '=[^\r\n]*/m'; - Redis::sadd('ui:toasts', $taskKey); - - ProvisionCertJob::dispatch( - domain: $this->form_domain, - email: $this->form_cert_email, - taskKey: $taskKey, - useLetsEncrypt: true - ); - session()->flash('task_key', $taskKey); - session()->flash('banner_ok', 'Let’s Encrypt wird gestartet…'); - } else { - // automatisch self-signed - SystemTask::updateOrCreate( - ['key' => $taskKey], - [ - 'type' => 'issue-cert', - 'status' => 'queued', - 'message' => 'Warte auf Ausführung…', - 'payload' => [ - 'domain' => $this->form_domain, - 'mode' => 'self-signed' - ], - ] - ); - - Cache::store('redis')->put($taskKey, [ - 'type' => 'issue-cert', - 'status' => 'queued', - 'message' => 'Warte auf Ausführung…', - 'payload' => [ - 'domain' => $this->form_domain, - 'email' => $this->form_cert_create_now ? $this->form_cert_email : null, - 'mode' => $this->form_cert_create_now ? 'letsencrypt' : 'self-signed', - ], - ], now()->addMinutes(30)); - - Cache::store('redis')->put($taskKey, [ - 'type' => 'issue-cert', - 'status' => 'queued', - 'message' => 'Warte auf Ausführung…', - 'payload' => [ - 'domain' => $this->form_domain, - 'email' => $this->form_cert_create_now ? $this->form_cert_email : null, - 'mode' => $this->form_cert_create_now ? 'letsencrypt' : 'self-signed', - ], - ], now()->addMinutes(30)); - - Redis::sadd('ui:toasts', $taskKey); - - ProvisionCertJob::dispatch( - domain: $this->form_domain, - email: null, - taskKey: $taskKey, - useLetsEncrypt: false - ); - session()->flash('task_key', $taskKey); - session()->flash('banner_ok', 'Self-Signed Zertifikat wird erstellt…'); + if (preg_match($pattern, $content)) { + $content = preg_replace($pattern, $line, $content); + } else { + $content .= "\n{$line}"; + } } - return redirect()->route('dashboard'); + file_put_contents($path, $content); } public function render() { - return view('livewire.setup.wizard'); + $timezones = \DateTimeZone::listIdentifiers(\DateTimeZone::ALL); + return view('livewire.setup.wizard', compact('timezones')); } } diff --git a/resources/views/layouts/setup.blade.php b/resources/views/layouts/setup.blade.php new file mode 100644 index 0000000..54689fe --- /dev/null +++ b/resources/views/layouts/setup.blade.php @@ -0,0 +1,18 @@ + + +
+ + +http:// am Anfang.
+