239 lines
8.7 KiB
PHP
239 lines
8.7 KiB
PHP
<?php
|
|
|
|
namespace App\Livewire\Setup;
|
|
|
|
use App\Models\Setting;
|
|
use App\Models\User;
|
|
use Illuminate\Support\Facades\Hash;
|
|
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 = 5;
|
|
|
|
// Schritt 1 — System
|
|
public string $instance_name = 'Mailwolt';
|
|
public string $locale = 'de';
|
|
public string $timezone = 'Europe/Berlin';
|
|
|
|
// Schritt 2 — Domains
|
|
public string $ui_domain = '';
|
|
public string $mail_domain = '';
|
|
public string $webmail_domain = '';
|
|
|
|
// Schritt 4 — Option
|
|
public bool $skipSsl = false;
|
|
|
|
// Schritt 3 — Admin-Account
|
|
public string $admin_name = '';
|
|
public string $admin_email = '';
|
|
public string $admin_password = '';
|
|
public string $admin_password_confirmation = '';
|
|
|
|
// Schritt 5 — Domain-Setup Status
|
|
public array $domainStatus = [
|
|
'ui' => 'pending',
|
|
'mail' => 'pending',
|
|
'webmail' => 'pending',
|
|
];
|
|
public bool $setupDone = false;
|
|
|
|
private const STATE_DIR = '/var/lib/mailwolt/wizard';
|
|
|
|
public function mount(): void
|
|
{
|
|
$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: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(): void
|
|
{
|
|
$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',
|
|
]);
|
|
|
|
// 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 && app()->isProduction()) ? 1 : 0;
|
|
$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
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
|
|
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 && app()->isProduction()) ? 1 : 0;
|
|
$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
|
|
{
|
|
return redirect()->route('login')->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'));
|
|
}
|
|
}
|