Use Laravel prompts 💬

This commit is contained in:
Dennis
2025-08-13 20:46:33 +02:00
parent 592bc2b0f6
commit e776ae299a

View File

@@ -14,6 +14,17 @@ use App\Services\VersionChecker;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Artisan;
use function Laravel\Prompts\text;
use function Laravel\Prompts\password;
use function Laravel\Prompts\confirm;
use function Laravel\Prompts\select;
use function Laravel\Prompts\info;
use function Laravel\Prompts\error;
use function Laravel\Prompts\warning;
use function Laravel\Prompts\note;
use function Laravel\Prompts\spin;
use function Laravel\Prompts\intro;
use function Laravel\Prompts\outro;
class Install extends Command
{
@@ -25,29 +36,34 @@ class Install extends Command
public function handle()
{
$this->init();
$this->intro();
$this->isInstalled();
$this->checkApplicationKey();
$this->checkDatabaseConnection();
$this->runDatabaseMigrations();
$this->checkCredentials();
$this->askAboutAdministrationAccount();
$this->askAboutDefaultPackages();
$this->checkApplicationUrl();
$this->createInstallationFile();
$this->linkStorage();
try {
$this->init();
$this->intro();
$this->isInstalled();
$this->checkApplicationKey();
$this->checkDatabaseConnection();
$this->runDatabaseMigrations();
$this->checkCredentials();
$this->askAboutAdministrationAccount();
$this->askAboutDefaultPackages();
$this->checkApplicationUrl();
$this->createInstallationFile();
$this->linkStorage();
$this->info('Success! Installation has finished.');
$this->line(' ');
$this->writeSeparationLine();
$this->info('Make sure to also setup emailing, the cronjob and the queue worker.');
$this->line(' ');
$this->info('Setting up emailing: https://docs.ploi-core.io/261-getting-started/918-setting-up-email');
$this->info('Setting up cronjob & queue worker: https://docs.ploi-core.io/261-getting-started/638-installation');
$this->writeSeparationLine();
$this->line(' ');
$this->info('Visit your platform at ' . env('APP_URL'));
outro('🎉 Installation completed successfully!');
note(
"Next steps:\n\n" .
"📧 Setup email: https://docs.ploi-core.io/261-getting-started/918-setting-up-email\n" .
"⚙️ Setup cron & queue: https://docs.ploi-core.io/261-getting-started/638-installation\n\n" .
"Visit your platform at: " . env('APP_URL')
);
return Command::SUCCESS;
} catch (\Exception $e) {
error('Installation failed: ' . $e->getMessage());
return Command::FAILURE;
}
}
protected function init()
@@ -58,88 +74,115 @@ class Install extends Command
protected function askAboutAdministrationAccount()
{
if (!User::query()->where('role', User::ADMIN)->count()) {
$this->info('Let\'s start by setting up your administration account.');
note('Let\'s set up your administration account');
$name = $this->ask('What is your name', $this->company['user_name']);
$email = $this->ask('What is your e-mail address', $this->company['email']);
$password = $this->secret('What password do you desire');
$name = text(
label: 'What is your name?',
default: $this->company['user_name'],
required: true
);
$check = User::where('email', $email)->count();
$email = text(
label: 'What is your email address?',
default: $this->company['email'],
required: true,
validate: fn (string $value) => match (true) {
!filter_var($value, FILTER_VALIDATE_EMAIL) => 'Please enter a valid email address.',
User::where('email', $value)->exists() => 'This email is already registered in the system.',
default => null
}
);
if ($check) {
$this->line('');
$this->comment('This user is already present in your system, please refresh your database or use different credentials.');
$this->comment('Aborting installation..');
$password = password(
label: 'Choose a secure password',
required: true,
validate: fn (string $value) => match (true) {
strlen($value) < 8 => 'Password must be at least 8 characters.',
default => null
}
);
exit();
}
spin(
function () use ($name, $email, $password) {
User::forceCreate([
'name' => $name,
'email' => $email,
'password' => $password,
'role' => User::ADMIN
]);
},
'Creating administrator account...'
);
User::forceCreate([
'name' => $name,
'email' => $email,
'password' => $password,
'role' => User::ADMIN
]);
info('✓ Administrator account created successfully');
} else {
$this->line('Already found a administrator user in your system. Use that user to login.');
note('Administrator account already exists. Use existing credentials to login.');
}
}
protected function askAboutDefaultPackages()
{
$basicPackages = $this->confirm(
'Do you want to create the basic packages which you can edit later?',
true
$createPackages = confirm(
label: 'Would you like to create default packages?',
default: true,
hint: 'Basic (5 sites), Professional (30 sites), and Unlimited packages'
);
if (!$basicPackages) {
if (!$createPackages) {
return false;
}
Package::create([
'name' => 'Basic',
'maximum_sites' => 5,
'site_permissions' => [
'create' => true,
'update' => true,
'delete' => true
],
'server_permissions' => [
'create' => false,
'update' => false,
'delete' => false
]
]);
spin(
function () {
Package::create([
'name' => 'Basic',
'maximum_sites' => 5,
'site_permissions' => [
'create' => true,
'update' => true,
'delete' => true
],
'server_permissions' => [
'create' => false,
'update' => false,
'delete' => false
]
]);
Package::create([
'name' => 'Professional',
'maximum_sites' => 30,
'site_permissions' => [
'create' => true,
'update' => true,
'delete' => true
],
'server_permissions' => [
'create' => false,
'update' => false,
'delete' => false
]
]);
Package::create([
'name' => 'Professional',
'maximum_sites' => 30,
'site_permissions' => [
'create' => true,
'update' => true,
'delete' => true
],
'server_permissions' => [
'create' => false,
'update' => false,
'delete' => false
]
]);
Package::create([
'name' => 'Unlimited',
'maximum_sites' => 0,
'site_permissions' => [
'create' => true,
'update' => true,
'delete' => true
],
'server_permissions' => [
'create' => false,
'update' => false,
'delete' => false
]
]);
Package::create([
'name' => 'Unlimited',
'maximum_sites' => 0,
'site_permissions' => [
'create' => true,
'update' => true,
'delete' => true
],
'server_permissions' => [
'create' => false,
'update' => false,
'delete' => false
]
]);
},
'Creating default packages...'
);
info('✓ Created 3 default packages');
}
protected function getCompany($token)
@@ -169,28 +212,26 @@ class Install extends Command
protected function intro()
{
$this->writeSeparationLine();
$this->line('Ploi Core Installation');
$this->line('Ploi Core version: ' . $this->versionChecker->currentVersion);
$this->line('Ploi Core remote: ' . $this->versionChecker->remoteVersion);
$this->line('Laravel version: ' . app()->version());
$this->line('PHP version: ' . trim(phpversion()));
$this->line(' ');
$this->line('Website: https://ploi-core.io');
$this->line('E-mail: core@ploi.io');
$this->line('Terms of service: https://ploi-core.io/terms');
$this->writeSeparationLine();
$this->line('');
intro('🚀 Ploi Core Installation');
note(
"Ploi Core v{$this->versionChecker->currentVersion} (Remote: v{$this->versionChecker->remoteVersion})\n" .
"Laravel v" . app()->version() . " | PHP v" . trim(phpversion()) . "\n\n" .
"Website: https://ploi-core.io\n" .
"E-mail: core@ploi.io\n" .
"Terms: https://ploi-core.io/terms"
);
}
protected function isInstalled()
{
if (file_exists(storage_path($this->installationFile)) && !$this->option('force')) {
$this->line('');
$this->comment('Ploi Core has already been installed before.');
$this->comment('If you still want to start installation, remove this file to continue: ./storage/' . $this->installationFile);
$this->comment('Aborting installation..');
warning('Ploi Core has already been installed before.');
error(
"To reinstall, either:\n" .
"• Remove the file: ./storage/{$this->installationFile}\n" .
"• Or run with --force flag"
);
exit();
}
@@ -200,133 +241,215 @@ class Install extends Command
protected function checkApplicationKey(): void
{
if (!config('app.key')) {
$this->call('key:generate');
$this->info('Application key has been set');
spin(
fn () => $this->call('key:generate', [], $this->getOutput()),
'Generating application key...'
);
info('✓ Application key has been set');
}
}
protected function checkApplicationUrl()
{
// Ask about URL
$url = $this->ask('What URL is this platform using?', env('APP_URL'));
$url = text(
label: 'What URL will this platform use?',
default: env('APP_URL', 'https://example.com'),
required: true,
validate: fn (string $value) => match (true) {
!filter_var($value, FILTER_VALIDATE_URL) => 'Please enter a valid URL.',
!str_starts_with($value, 'http://') && !str_starts_with($value, 'https://') => 'URL must start with http:// or https://',
default => null
},
hint: 'Include the protocol (http:// or https://)'
);
$this->writeToEnvironmentFile('APP_URL', $url);
try {
$this->writeToEnvironmentFile('APP_URL', $url);
info('✓ Application URL configured');
} catch (\Exception $e) {
error('Failed to save application URL: ' . $e->getMessage());
exit(1);
}
}
protected function createInstallationFile()
{
file_put_contents(storage_path($this->installationFile), json_encode($this->getInstallationPayload(), JSON_PRETTY_PRINT));
try {
$path = storage_path($this->installationFile);
$content = json_encode($this->getInstallationPayload(), JSON_PRETTY_PRINT);
if (file_put_contents($path, $content) === false) {
error('Failed to create installation file');
exit(1);
}
info('✓ Installation marker created');
} catch (\Exception $e) {
error('Error creating installation file: ' . $e->getMessage());
exit(1);
}
}
protected function linkStorage()
{
Artisan::call('storage:link');
// Create storage symlink
$publicPath = public_path('storage');
$storagePath = storage_path('app/public');
// Remove existing symlink if it exists
if (is_link($publicPath)) {
unlink($publicPath);
}
// Create new symlink
if (!file_exists($publicPath)) {
try {
symlink($storagePath, $publicPath);
info('✓ Storage symlink created');
} catch (\Exception $e) {
warning('Could not create storage symlink (may need manual creation)');
}
} else {
info('✓ Storage path already exists');
}
}
protected function createDatabaseCredentials(): bool
{
$storeCredentials = $this->confirm(
'Unable to connect to your database. Would you like to enter your credentials now?',
true
$storeCredentials = confirm(
label: 'Would you like to configure database credentials now?',
default: true
);
if (!$storeCredentials) {
return false;
}
$connection = $this->choice('Type', ['mysql', 'pgsql'], 0);
$connection = select(
label: 'Select database type',
options: [
'mysql' => 'MySQL / MariaDB',
'pgsql' => 'PostgreSQL'
],
default: 'mysql'
);
$defaultPort = $connection === 'mysql' ? '3306' : '5432';
$variables = [
'DB_CONNECTION' => $connection,
'DB_HOST' => $this->anticipate(
'Host',
['127.0.0.1', 'localhost'],
config("database.connections.{$connection}.host", '127.0.0.1')
'DB_HOST' => text(
label: 'Database host',
default: config("database.connections.{$connection}.host", '127.0.0.1'),
required: true,
hint: 'Usually 127.0.0.1 or localhost'
),
'DB_PORT' => $this->ask(
'Port',
config("database.connections.{$connection}.port", '3306')
'DB_PORT' => text(
label: 'Database port',
default: config("database.connections.{$connection}.port", $defaultPort),
required: true
),
'DB_DATABASE' => $this->ask(
'Database',
config("database.connections.{$connection}.database")
'DB_DATABASE' => text(
label: 'Database name',
default: config("database.connections.{$connection}.database", 'ploi_core'),
required: true
),
'DB_USERNAME' => $this->ask(
'Username',
config("database.connections.{$connection}.username")
'DB_USERNAME' => text(
label: 'Database username',
default: config("database.connections.{$connection}.username", 'root'),
required: true
),
'DB_PASSWORD' => $this->secret(
'Password',
config("database.connections.{$connection}.password")
),
'DB_PASSWORD' => password(
label: 'Database password',
hint: 'Leave empty if no password is set'
) ?: '',
];
$this->persistVariables($variables);
spin(
fn () => $this->persistVariables($variables),
'Saving database configuration...'
);
return true;
}
protected function checkCredentials()
{
do {
$ploiApiToken = $this->ask('Enter the Ploi API token', env('PLOI_TOKEN'));
} while (empty($ploiApiToken));
$ploiApiToken = text(
label: 'Enter your Ploi API token',
default: env('PLOI_TOKEN'),
required: true,
hint: 'You can find this in your Ploi account settings'
);
$this->company = $this->getCompany($ploiApiToken);
$this->company = spin(
fn () => $this->getCompany($ploiApiToken),
'Authenticating with Ploi API...'
);
if (!$this->company) {
$this->error('Could not authenticate with ploi.io, please retry by running this command again.');
error('Could not authenticate with ploi.io');
exit();
}
if (isset($this->company['error'])) {
$this->error($this->company['error']);
error($this->company['error']);
exit();
}
if ($this->company['user']['subscription'] !== 'unlimited') {
$this->error('Your subscription does not cover the usage of Ploi Core. Please upgrade your subscription to Unlimited.');
error('Your Ploi subscription does not support Ploi Core.');
warning('Please upgrade to the Unlimited plan at https://ploi.io');
exit();
}
info('✓ Successfully authenticated with Ploi');
$this->writeToEnvironmentFile('PLOI_TOKEN', $ploiApiToken);
$name = $this->ask('What is the name of your company? (Press enter to keep the name here)', $this->company['name']);
$name = text(
label: 'What is the name of your company?',
default: $this->company['name'],
required: true
);
$this->writeToEnvironmentFile('APP_NAME', $name);
setting(['name' => $name]);
}
protected function runDatabaseMigrations()
{
$this->info('Running database migrations..');
$this->call('migrate', ['--force' => true]);
$this->info('Database migrations successful');
spin(
fn () => $this->call('migrate', ['--force' => true], $this->getOutput()),
'Running database migrations...'
);
info('✓ Database migrations completed');
}
protected function checkDatabaseConnection(): void
{
try {
DB::connection()->getPdo();
$this->info('Database connection successful.');
spin(
fn () => DB::connection()->getPdo(),
'Testing database connection...'
);
info('✓ Database connection successful');
} catch (Exception $e) {
warning('Unable to connect to database');
try {
if (!$this->createDatabaseCredentials()) {
$this->error('A database connection could not be established. Please update your configuration and try again.');
error('Database connection could not be established.');
$this->printDatabaseConfig();
exit();
}
} catch (RuntimeException $e) {
$this->error('Failed to persist environment configuration.');
error('Failed to persist environment configuration.');
exit();
}
@@ -338,14 +461,15 @@ class Install extends Command
{
$connection = config('database.default');
$this->line('');
$this->info('Database Configuration:');
$this->line("- Connection: {$connection}");
$this->line('- Host: ' . config("database.connections.{$connection}.host"));
$this->line('- Port: ' . config("database.connections.{$connection}.port"));
$this->line('- Database: ' . config("database.connections.{$connection}.database"));
$this->line('- Username: ' . config("database.connections.{$connection}.username"));
$this->line('- Password: ' . config("database.connections.{$connection}.password"));
note(
"Current Database Configuration:\n" .
" Connection: {$connection}\n" .
" Host: " . config("database.connections.{$connection}.host") . "\n" .
" Port: " . config("database.connections.{$connection}.port") . "\n" .
" Database: " . config("database.connections.{$connection}.database") . "\n" .
" Username: " . config("database.connections.{$connection}.username") . "\n" .
" Password: " . (config("database.connections.{$connection}.password") ? '***' : '(not set)')
);
}
protected function persistVariables(array $connectionData): void
@@ -401,8 +525,4 @@ class Install extends Command
$this->laravel['config'][$key] = $value;
}
protected function writeSeparationLine()
{
$this->info('*---------------------------------------------------------------------------*');
}
}