'pending', 'mail' => 'pending', 'webmail' => 'pending', ]; public bool $setupDone = false; private const STATE_DIR = '/var/lib/mailwolt/wizard'; public function mount() { $this->instance_name = config('app.name', 'Mailwolt'); try { $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', ''); } catch (\Throwable) { // DB noch nicht migriert — Standardwerte bleiben } } public function updatedUiDomain(): void { $this->fillEmptyDomains($this->ui_domain); } public function updatedMailDomain(): void { $this->fillEmptyDomains($this->mail_domain); } public function updatedWebmailDomain(): void { $this->fillEmptyDomains($this->webmail_domain); } private function fillEmptyDomains(string $value): void { if ($value === '') return; if ($this->ui_domain === '') $this->ui_domain = $value; if ($this->mail_domain === '') $this->mail_domain = $value; if ($this->webmail_domain === '') $this->webmail_domain = $value; } 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:6|same:admin_password_confirmation', 'admin_password_confirmation' => 'required', ], [ 'admin_password.min' => 'Mindestens 6 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(): void { $this->validate([ 'admin_name' => 'required|string|min:2|max:64', 'admin_email' => 'required|email|max:190', 'admin_password' => 'required|string|min:6|same:admin_password_confirmation', ]); // Settings + .env 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', ]); $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, ]); // Admin anlegen $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(); // Status-Verzeichnis leeren und Domain-Setup im Hintergrund starten @mkdir(self::STATE_DIR, 0755, true); @unlink(self::STATE_DIR . '/done'); foreach (['ui', 'mail', 'webmail'] as $k) { file_put_contents(self::STATE_DIR . "/{$k}", 'pending'); } $ssl = $this->skipSsl ? 0 : 1; $artisan = base_path('artisan'); $cmd = sprintf( 'nohup php %s mailwolt:wizard-domains --ui=%s --mail=%s --webmail=%s --ssl=%d > /dev/null 2>&1 &', escapeshellarg($artisan), escapeshellarg($this->ui_domain), escapeshellarg($this->mail_domain), escapeshellarg($this->webmail_domain), $ssl, ); @shell_exec($cmd); $this->step = 5; } public function pollSetup(): void { if ($this->setupDone) return; foreach (['ui', 'mail', 'webmail'] as $key) { $file = self::STATE_DIR . "/{$key}"; $this->domainStatus[$key] = is_readable($file) ? trim(@file_get_contents($file)) : 'pending'; } $done = @file_get_contents(self::STATE_DIR . '/done'); if ($done !== false) { $this->setupDone = true; // Bei erfolgreichem SSL sofort auf HTTPS weiterleiten, // damit Livewire nicht mehr über HTTP pollt if (trim($done) === '1' && $this->ui_domain) { $this->redirect('https://' . $this->ui_domain . '/setup', navigate: false); } } } public function retryDomains(): void { @unlink(self::STATE_DIR . '/done'); foreach (['ui', 'mail', 'webmail'] as $k) { file_put_contents(self::STATE_DIR . "/{$k}", 'pending'); } $this->domainStatus = ['ui' => 'pending', 'mail' => 'pending', 'webmail' => 'pending']; $this->setupDone = false; $ssl = $this->skipSsl ? 0 : 1; $artisan = base_path('artisan'); $cmd = sprintf( 'nohup php %s mailwolt:wizard-domains --ui=%s --mail=%s --webmail=%s --ssl=%d > /dev/null 2>&1 &', escapeshellarg($artisan), escapeshellarg($this->ui_domain), escapeshellarg($this->mail_domain), escapeshellarg($this->webmail_domain), $ssl, ); @shell_exec($cmd); } public function goToLogin(): mixed { $sslOk = Setting::get('ssl_configured', '0') === '1' && $this->ui_domain; $url = $sslOk ? 'https://' . $this->ui_domain . '/login' : '/login'; return redirect()->to($url)->with('setup_done', true); } private function writeEnv(array $values): void { $path = base_path('.env'); $content = @file_get_contents($path) ?: ''; foreach ($values as $key => $value) { $escaped = str_contains($value, ' ') ? '"' . $value . '"' : $value; $line = $key . '=' . $escaped; $pattern = '/^' . preg_quote($key, '/') . '=[^\r\n]*/m'; if (preg_match($pattern, $content)) { $content = preg_replace($pattern, $line, $content); } else { $content .= "\n{$line}"; } } file_put_contents($path, $content); } public function render() { $timezones = \DateTimeZone::listIdentifiers(\DateTimeZone::ALL); return view('livewire.setup.wizard', compact('timezones')); } }