diff --git a/app/Console/Commands/InstallCommand.php b/app/Console/Commands/InstallCommand.php new file mode 100644 index 000000000000..c9df46e0f496 --- /dev/null +++ b/app/Console/Commands/InstallCommand.php @@ -0,0 +1,369 @@ + + */ +class InstallCommand extends Command +{ + /** + * The console command name. + * + * @var string + */ + protected $name = 'cachet:install'; + + /** + * The console command description. + * + * @var string + */ + protected $description = 'Install Cachet'; + + /** + * Execute the console command. + * + * @return void + */ + public function fire() + { + if (!$this->confirm('Do you want to install Cachet?')) { + $this->line('Installation aborted. Goodbye!'); + + return; + } + + $this->configureEnvironmentFile(); + $this->configureKey(); + $this->configureDatabase(); + $this->configureDrivers(); + $this->configureMail(); + $this->configureCachet(); + + $this->info('Cachet is installed ⚡'); + } + + /** + * Copy the environment file. + * + * @return void + */ + protected function configureEnvironmentFile() + { + $dir = app()->environmentPath(); + $file = app()->environmentFile(); + $path = "{$dir}/{$file}"; + + if (file_exists($path)) { + $this->line('Environment file already exists. Moving on.'); + + return; + } + + copy("$path.example", $path); + } + + /** + * Generate the app key. + * + * @return void + */ + protected function configureKey() + { + $this->call('key:generate'); + } + + /** + * Configure the database. + * + * @param array $default + * + * @return void + */ + protected function configureDatabase(array $default = []) + { + $config = array_merge([ + 'DB_DRIVER' => null, + 'DB_HOST' => null, + 'DB_DATABASE' => null, + 'DB_USERNAME' => null, + 'DB_PASSWORD' => null, + 'DB_PORT' => null, + 'DB_PREFIX' => null, + ], $default); + + $config['DB_DRIVER'] = $this->choice('Which database driver do you want to use?', [ + 'mysql' => 'MySQL', + 'postgresql' => 'PostgreSQL', + 'sqlite' => 'SQLite', + ], $config['DB_DRIVER']); + + if ($config['DB_DRIVER'] === 'sqlite') { + $config['DB_DATABASE'] = $this->ask('Please provide the full path to your SQLite file.', $config['DB_DATABASE']); + } else { + $config['DB_HOST'] = $this->ask("What is the host of your {$config['DB_DRIVER']} database?", $config['DB_HOST']); + + $config['DB_DATABASE'] = $this->ask('What is the name of the database that Cachet should use?', $config['DB_DATABASE']); + + $config['DB_USERNAME'] = $this->ask('What username should we connect with?', $config['DB_USERNAME']); + + $config['DB_PASSWORD'] = $this->secret('What password should we connect with?', $config['DB_PASSWORD']); + + if ($this->confirm('Is your database listening on a non-standard port number?')) { + $config['DB_PORT'] = $this->anticipate('What port number is your database using?', [3306, 5432], $config['DB_PORT']); + } + } + + if ($this->confirm('Do you want to use a prefix on the table names?')) { + $config['DB_PREFIX'] = $this->ask('Please enter the prefix now...', $config['DB_PREFIX']); + } + + // Format the settings ready to display them in the table. + $this->formatConfigsTable($config); + + if (!$this->confirm('Are these settings correct?')) { + return $this->configureDatabase($config); + } + + foreach ($config as $setting => $value) { + $this->writeEnv($setting, $value); + } + } + + /** + * Configure other drivers. + * + * @param array $default + * + * @return void + */ + protected function configureDrivers(array $default = []) + { + $config = array_merge([ + 'CACHE_DRIVER' => null, + 'SESSION_DRIVER' => null, + 'QUEUE_DRIVER' => null, + ], $default); + + // Format the settings ready to display them in the table. + $this->formatConfigsTable($config); + + $config['CACHE_DRIVER'] = $this->choice('Which cache driver do you want to use?', [ + 'apc' => 'APC(u)', + 'array' => 'Array', + 'database' => 'Database', + 'file' => 'File', + 'memcached' => 'Memcached', + 'redis' => 'Redis', + ], $config['CACHE_DRIVER']); + + // We need to configure Redis. + if ($config['CACHE_DRIVER'] === 'redis') { + $this->configureRedis(); + } + + $config['SESSION_DRIVER'] = $this->choice('Which session driver do you want to use?', [ + 'apc' => 'APC(u)', + 'array' => 'Array', + 'database' => 'Database', + 'file' => 'File', + 'memcached' => 'Memcached', + 'redis' => 'Redis', + ], $config['SESSION_DRIVER']); + + // We need to configure Redis. + if ($config['SESSION_DRIVER'] === 'redis') { + $this->configureRedis(); + } + + $config['QUEUE_DRIVER'] = $this->choice('Which queue driver do you want to use?', [ + 'null' => 'None', + 'sync' => 'Synchronous', + 'database' => 'Database', + 'beanstalkd' => 'Beanstalk', + 'sqs' => 'Amazon SQS', + 'redis' => 'Redis', + ], $config['QUEUE_DRIVER']); + + // We need to configure Redis, but only if the cache driver wasn't redis. + if ($config['QUEUE_DRIVER'] === 'redis' && ($config['SESSION_DRIVER'] !== 'redis' || $config['CACHE_DRIVER'] !== 'redis')) { + $this->configureRedis(); + } + + // Format the settings ready to display them in the table. + $this->formatConfigsTable($config); + + if (!$this->confirm('Are these settings correct?')) { + return $this->configureDrivers($config); + } + + foreach ($config as $setting => $value) { + $this->writeEnv($setting, $value); + } + } + + /** + * Configure mail. + * + * @param array $config + * + * @return void + */ + protected function configureMail(array $config = []) + { + $config = array_merge([ + 'MAIL_DRIVER' => null, + 'MAIL_HOST' => null, + 'MAIL_PORT' => null, + 'MAIL_USERNAME' => null, + 'MAIL_PASSWORD' => null, + 'MAIL_ADDRESS' => null, + 'MAIL_NAME' => null, + 'MAIL_ENCRYPTION' => null, + ], $config); + + // Don't continue with these settings if we're not interested in notifications. + if (!$this->confirm('Do you want Cachet to send mail notifications?')) { + return; + } + + $config['MAIL_DRIVER'] = $this->choice('What driver do you want to use to send notifications?', [ + 'smtp' => 'SMTP', + 'mail' => 'Mail', + 'sendmail' => 'Sendmail', + 'mailgun' => 'Mailgun', + 'mandrill' => 'Mandrill', + 'ses' => 'Amazon SES', + 'sparkpost' => 'SparkPost', + 'log' => 'Log (Testing)', + ]); + + if (!$config['MAIL_DRIVER'] === 'log') { + if ($config['MAIL_DRIVER'] === 'smtp') { + $config['MAIL_HOST'] = $this->ask('Please supply your mail server host'); + } + + $config['MAIL_ADDRESS'] = $this->ask('What email address should we send notifications from?'); + $config['MAIL_USERNAME'] = $this->ask('What username should we connect as?'); + $config['MAIL_PASSWORD'] = $this->secret('What password should we connect with?'); + } + + // Format the settings ready to display them in the table. + $this->formatConfigsTable($config); + + if (!$this->confirm('Are these settings correct?')) { + return $this->configureMail($config); + } + + foreach ($config as $setting => $value) { + $this->writeEnv($setting, $value); + } + } + + /** + * Configure Cachet. + * + * @return void + */ + protected function configureCachet() + { + if ($this->confirm('Do you wish to use Cachet Beacon?')) { + $config['CACHET_BEACON'] = 'true'; + } + + if ($this->confirm('Do you wish to use Emoji? This requires a GitHub oAuth Token!')) { + $config['GITHUB_TOKEN'] = $this->ask('Please enter your GitHub oAuth Token'); + $config['CACHET_EMOJI'] = 'true'; + } + + foreach ($config as $setting => $value) { + $this->writeEnv($setting, $value); + } + } + + /** + * Configure the redis connection. + * + * @return void + */ + protected function configureRedis() + { + $config = [ + 'REDIS_HOST' => null, + 'REDIS_DATABASE' => null, + 'REDIS_PORT' => null, + ]; + + $config['REDIS_HOST'] = $this->ask('What is the host of your redis server?'); + $config['REDIS_DATABASE'] = $this->ask('What is the name of the database that Cachet should use?'); + $config['REDIS_PORT'] = $this->ask('What port should Cachet use?', 6379); + + foreach ($config as $setting => $value) { + $this->writeEnv($setting, $value); + } + } + + /** + * Format the configs into a pretty table that we can easily read. + * + * @param array $config + * + * @return void + */ + protected function formatConfigsTable(array $config) + { + $configRows = []; + + foreach ($config as $setting => $value) { + $configRows[] = compact('setting', 'value'); + } + + $this->table(['Setting', 'Value'], $configRows); + } + + /** + * Writes to the .env file with given parameters. + * + * @param string $key + * @param mixed $value + * + * @return void + */ + protected function writeEnv($key, $value) + { + $dir = app()->environmentPath(); + $file = app()->environmentFile(); + $path = "{$dir}/{$file}"; + + try { + (new Dotenv($dir, $file))->load(); + + $envKey = strtoupper($key); + $envValue = env($envKey) ?: 'null'; + + file_put_contents($path, str_replace( + "{$envKey}={$envValue}", + "{$envKey}={$value}", + file_get_contents($path) + )); + } catch (InvalidPathException $e) { + throw $e; + } + } +} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 059aa0cc1c04..78bd5b441865 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -14,6 +14,7 @@ use CachetHQ\Cachet\Console\Commands\BeaconCommand; use CachetHQ\Cachet\Console\Commands\DemoMetricPointSeederCommand; use CachetHQ\Cachet\Console\Commands\DemoSeederCommand; +use CachetHQ\Cachet\Console\Commands\InstallCommand; use Illuminate\Console\Scheduling\Schedule; use Illuminate\Foundation\Console\Kernel as ConsoleKernel; @@ -35,6 +36,7 @@ class Kernel extends ConsoleKernel BeaconCommand::class, DemoMetricPointSeederCommand::class, DemoSeederCommand::class, + InstallCommand::class, ]; /**