From 97ca0976a75a1eba217570cdb62b8a3f3606b10b Mon Sep 17 00:00:00 2001 From: Samuel Georges Date: Tue, 7 Jun 2016 07:09:01 +1000 Subject: [PATCH] Create new october:install command Add quick start instructions to readme Fixes #1674 --- README.md | 9 + modules/system/ServiceProvider.php | 1 + modules/system/console/OctoberInstall.php | 362 ++++++++++++++++++++++ 3 files changed, 372 insertions(+) create mode 100644 modules/system/console/OctoberInstall.php diff --git a/README.md b/README.md index 45e1b6455..66cac42e3 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,15 @@ The best place to learn October is by [reading the documentation](http://october Instructions on how to install October can be found at the [installation guide](http://octobercms.com/docs/setup/installation). +### Quick start installation + +For advanced users, run this in your terminal to install October from command line: + +``` +wget https://octobercms.com/api/installer -O temp.zip && unzip temp.zip && rm temp.zip +php artisan october:install +``` + ### Development Team October was created by [Alexey Bobkov](http://ca.linkedin.com/pub/aleksey-bobkov/2b/ba0/232) and [Samuel Georges](http://au.linkedin.com/pub/sam-georges/31/641/a9), who both continue to develop the platform. diff --git a/modules/system/ServiceProvider.php b/modules/system/ServiceProvider.php index 797944b52..d1afcfdbe 100644 --- a/modules/system/ServiceProvider.php +++ b/modules/system/ServiceProvider.php @@ -219,6 +219,7 @@ class ServiceProvider extends ModuleServiceProvider $this->registerConsoleCommand('october.util', 'System\Console\OctoberUtil'); $this->registerConsoleCommand('october.mirror', 'System\Console\OctoberMirror'); $this->registerConsoleCommand('october.fresh', 'System\Console\OctoberFresh'); + $this->registerConsoleCommand('october.install', 'System\Console\OctoberInstall'); $this->registerConsoleCommand('plugin.install', 'System\Console\PluginInstall'); $this->registerConsoleCommand('plugin.remove', 'System\Console\PluginRemove'); diff --git a/modules/system/console/OctoberInstall.php b/modules/system/console/OctoberInstall.php new file mode 100644 index 000000000..408b6e1b6 --- /dev/null +++ b/modules/system/console/OctoberInstall.php @@ -0,0 +1,362 @@ +configWriter = new ConfigWriter; + } + + /** + * Execute the console command. + */ + public function fire() + { + $this->displayIntro(); + + if ( + App::hasDatabase() && + !$this->confirm('Application appears to be installed already. Continue anyway?', false) + ) { + return; + } + + $this->setupDatabaseConfig(); + $this->setupAdminUser(); + $this->setupCommonValues(); + + if ($this->confirm('Configure advanced options?', false)) { + $this->setupEncryptionKey(); + $this->setupAdvancedValues(); + } + else { + $this->setupEncryptionKey(true); + } + + $this->setupMigrateDatabase(); + $this->displayOutro(); + } + + /** + * Get the console command arguments. + */ + protected function getArguments() + { + return []; + } + + /** + * Get the console command options. + */ + protected function getOptions() + { + return [ + ['force', null, InputOption::VALUE_NONE, 'Force the operation to run.'], + ]; + } + + // + // Misc + // + + protected function setupCommonValues() + { + $url = $this->ask('Application URL', Config::get('app.url')); + $this->writeToConfig('app', ['url' => $url]); + } + + protected function setupAdvancedValues() + { + $backendUri = $this->ask('Backend URL', Config::get('cms.backendUri')); + $this->writeToConfig('cms', ['backendUri' => $backendUri]); + + $defaultMask = $this->ask('File Permission Mask', Config::get('cms.defaultMask.file') ?: '777'); + $this->writeToConfig('cms', ['defaultMask.file' => $defaultMask]); + + $defaultMask = $this->ask('Folder Permission Mask', Config::get('cms.defaultMask.folder') ?: '777'); + $this->writeToConfig('cms', ['defaultMask.folder' => $defaultMask]); + + $debug = (bool) $this->confirm('Enable Debug Mode?', true); + $this->writeToConfig('app', ['debug' => $debug]); + } + + // + // Encryption key + // + + protected function setupEncryptionKey($force = false) + { + $validKey = false; + $cipher = Config::get('app.cipher'); + $keyLength = $this->getKeyLength($cipher); + $randomKey = $this->getRandomKey($cipher); + + if ($force) { + $key = $randomKey; + } + else { + $this->line(sprintf('Enter a new value of %s characters, or press ENTER to use the generated key', $keyLength)); + + while (!$validKey) { + $key = $this->ask('Application key', $randomKey); + $validKey = Encrypter::supported($key, $cipher); + if (!$validKey) { + $this->error(sprintf('[ERROR] Invalid key length for "%s" cipher. Supplied key must be %s characters in length.', $cipher, $keyLength)); + } + } + } + + $this->writeToConfig('app', ['key' => $key]); + + $this->info(sprintf('Application key [%s] set successfully.', $key)); + } + + /** + * Generate a random key for the application. + * + * @param string $cipher + * @return string + */ + protected function getRandomKey($cipher) + { + return Str::random($this->getKeyLength($cipher)); + } + + /** + * Returns the supported length of a key for a cipher. + * + * @param string $cipher + * @return int + */ + protected function getKeyLength($cipher) + { + return $cipher === 'AES-128-CBC' ? 16 : 32; + } + + // + // Database config + // + + protected function setupDatabaseConfig() + { + $type = $this->choice('Database type', ['MySQL', 'Postgres', 'SQLite', 'SQL Server']); + + $typeMap = [ + 'SQLite' => 'sqlite', + 'MySQL' => 'mysql', + 'Postgres' => 'pgsql', + 'SQL Server' => 'sqlsrv', + ]; + + $driver = array_get($typeMap, $type, 'sqlite'); + + $method = 'setupDatabase'.Str::studly($driver); + + $newConfig = $this->$method(); + + $this->writeToConfig('database', ['default' => $driver]); + + foreach ($newConfig as $config => $value) { + $this->writeToConfig('database', ['connections.'.$driver.'.'.$config => $value]); + } + } + + protected function setupDatabaseMysql() + { + $result = []; + $result['host'] = $this->ask('MySQL Host', Config::get('database.connections.mysql.host')); + $result['port'] = $this->output->ask('MySQL Port', Config::get('database.connections.mysql.port') ?: false) ?: ''; + $result['database'] = $this->ask('Database Name', Config::get('database.connections.mysql.database')); + $result['username'] = $this->ask('MySQL Login', Config::get('database.connections.mysql.username')); + $result['password'] = $this->ask('MySQL Password', Config::get('database.connections.mysql.password') ?: false) ?: ''; + return $result; + } + + protected function setupDatabasePgsql() + { + $result = []; + $result['host'] = $this->ask('Postgres Host', Config::get('database.connections.pgsql.host')); + $result['port'] = $this->ask('Postgres Port', Config::get('database.connections.pgsql.port') ?: false) ?: ''; + $result['database'] = $this->ask('Database Name', Config::get('database.connections.pgsql.database')); + $result['username'] = $this->ask('Postgres Login', Config::get('database.connections.pgsql.username')); + $result['password'] = $this->ask('Postgres Password', Config::get('database.connections.pgsql.password') ?: false) ?: ''; + return $result; + } + + protected function setupDatabaseSqlite() + { + $filename = $this->ask('Database path', Config::get('database.connections.sqlite.database')); + + try { + if (!file_exists($filename)) { + $directory = dirname($filename); + if (!is_dir($directory)) { + mkdir($directory, 0777, true); + } + + new PDO('sqlite:'.$filename); + } + } + catch (Exception $ex) { + $this->error($ex->getMessage()); + $this->setupDatabaseSqlite(); + } + + return ['database' => $filename]; + } + + protected function setupDatabaseSqlsrv() + { + $result = []; + $result['host'] = $this->ask('SQL Host', Config::get('database.connections.sqlsrv.host')); + $result['port'] = $this->ask('SQL Port', Config::get('database.connections.sqlsrv.port') ?: false) ?: ''; + $result['database'] = $this->ask('Database Name', Config::get('database.connections.sqlsrv.database')); + $result['username'] = $this->ask('SQL Login', Config::get('database.connections.sqlsrv.username')); + $result['password'] = $this->ask('SQL Password', Config::get('database.connections.sqlsrv.password') ?: false) ?: ''; + return $result; + } + + // + // Migration + // + + protected function setupAdminUser() + { + $this->line('Enter a new value, or press ENTER for the default'); + + SeedSetupAdmin::$firstName = $this->ask('First Name', SeedSetupAdmin::$firstName); + SeedSetupAdmin::$lastName = $this->ask('Last Name', SeedSetupAdmin::$lastName); + SeedSetupAdmin::$email = $this->ask('Email Address', SeedSetupAdmin::$email); + SeedSetupAdmin::$login = $this->ask('Admin Login', SeedSetupAdmin::$login); + SeedSetupAdmin::$password = $this->ask('Admin Password', SeedSetupAdmin::$password); + + if (!$this->confirm('Is the information correct?', true)) { + $this->setupAdminUser(); + } + } + + protected function setupMigrateDatabase() + { + $this->line('Migrating application and plugins...'); + + try { + Db::purge(); + UpdateManager::instance()->resetNotes()->update(); + } + catch (Exception $ex) { + $this->error($ex->getMessage()); + $this->setupDatabaseConfig(); + $this->setupMigrateDatabase(); + } + } + + // + // Helpers + // + + protected function displayIntro() + { + $message = [ + ".====================================================================.", + " ", + " .d8888b. .o8888b. db .d8888b. d8888b. d88888b d8888b. .d888b. ", + ".8P Y8. d8P Y8 88 .8P Y8. 88 `8D 88' 88 `8D .8P , Y8.", + "88 88 8P oooo88 88 88 88oooY' 88oooo 88oobY' 88 | 88", + "88 88 8b ~~~~88 88 88 88~~~b. 88~~~~ 88`8b 88 |/ 88", + "`8b d8' Y8b d8 88 `8b d8' 88 8D 88. 88 `88. `8b | d8'", + " `Y8888P' `Y8888P' YP `Y8888P' Y8888P' Y88888P 88 YD `Y888P' ", + " ", + "`=========================== INSTALLATION ==========================='", + "", + ]; + + $this->line($message); + } + + protected function displayOutro() + { + $message = [ + ".=========================================.", + " ,@@@@@@@, ", + " ,,,. ,@@@@@@/@@, .oo8888o. ", + " ,&%%&%&&%,@@@@@/@@@@@@,8888\88/8o ", + " ,%&\%&&%&&%,@@@\@@@/@@@88\88888/88' ", + " %&&%&%&/%&&%@@\@@/ /@@@88888\88888' ", + " %&&%/ %&%%&&@@\ V /@@' `88\8 `/88' ", + " `&%\ ` /%&' |.| \ '|8' ", + " |o| | | | | ", + " |.| | | | | ", + "`========= INSTALLATION COMPLETE ========='", + "", + ]; + + $this->line($message); + } + + protected function writeToConfig($file, $values) + { + $configFile = $this->getConfigFile($file); + + foreach ($values as $key => $value) { + Config::set($file.'.'.$key, $value); + } + + $this->configWriter->toFile($configFile, $values); + } + + /** + * Get a config file and contents. + * + * @return array + */ + protected function getConfigFile($name = 'app') + { + $env = $this->option('env') ? $this->option('env').'/' : ''; + + $name .= '.php'; + + $contents = File::get($path = $this->laravel['path.config']."/{$env}{$name}"); + + return $path; + } +}