clusev/app/Console/Commands/Install.php

72 lines
2.9 KiB
PHP

<?php
namespace App\Console\Commands;
use App\Models\User;
use Illuminate\Console\Command;
use Illuminate\Support\Str;
/**
* Idempotent first-run bootstrap (called by install.sh, phase 7).
*
* Creates the initial admin with a RANDOM initial password (printed once by install.sh from the
* CLUSEV_ADMIN_PASSWORD line below) rather than a guessable literal — rotation is optional now, so a
* well-known default like "clusev" would be a standing takeover credential. `must_change_password` is
* still set (the banner nudges a rotation), but even if kept, the generated secret is not guessable.
* Re-running once an admin exists is a hard no-op.
*/
class Install extends Command
{
/**
* Standard default admin e-mail (login + Let's-Encrypt contact) when none is given. A fixed,
* predictable value so a fresh, unattended install always logs in at admin@clusev.local —
* not a host-derived address the operator would have to guess. Override with --email /
* CLUSEV_ADMIN_EMAIL, or change it later under Settings → Profile.
*/
private const DEFAULT_EMAIL = 'admin@clusev.local';
protected $signature = "clusev:install
{--email= : Admin-Login + Let's-Encrypt-E-Mail}
{--name=Administrator : Anzeigename des Admins}";
protected $description = 'Idempotenter Erstlauf: legt den ersten Admin mit zufälligem Initialpasswort an (Wechsel empfohlen, nicht erzwungen)';
public function handle(): int
{
if (User::query()->exists()) {
$this->info('Clusev ist bereits installiert — kein Admin angelegt.');
return self::SUCCESS;
}
$email = (string) ($this->option('email') ?: self::DEFAULT_EMAIL);
if (! filter_var($email, FILTER_VALIDATE_EMAIL)) {
$this->error("Ungueltige Admin-E-Mail: {$email}");
return self::FAILURE;
}
// Random, non-guessable initial password (printed once below). Satisfies any policy.
$password = Str::password(16);
$user = User::create([
'name' => (string) ($this->option('name') ?: 'Administrator'),
'email' => $email,
'password' => $password, // 'hashed' cast hashes on save
'must_change_password' => true, // nudges a rotation on first login (optional)
'role' => 'admin', // the first/root account is a full administrator
]);
// install.sh greps these two lines for the closing banner — this is the ONLY time the
// generated password is shown, so the operator must note it now.
$this->newLine();
$this->line('CLUSEV_ADMIN_EMAIL='.$user->email);
$this->line('CLUSEV_ADMIN_PASSWORD='.$password);
$this->newLine();
$this->info('Admin angelegt. Generiertes Passwort jetzt notieren — es wird nur einmal angezeigt.');
return self::SUCCESS;
}
}