diff --git a/scripts/sql/upgrade_schema.php b/scripts/sql/upgrade_schema.php index f7a718f575..10d0159885 100755 --- a/scripts/sql/upgrade_schema.php +++ b/scripts/sql/upgrade_schema.php @@ -1,195 +1,196 @@ #!/usr/bin/env php null, // Upgrade from specific version 'u' => null, // Override MySQL User 'p' => null, // Override MySQL Pass ); foreach (array('h', 'f') as $key) { // By default, these keys are set to 'false' to indicate that the flag was // passed. if (array_key_exists($key, $options)) { $options[$key] = true; } } if (!empty($options['h']) || ($options['v'] && !is_numeric($options['v']))) { usage(); } if (empty($options['f'])) { echo phutil_console_wrap( "Before running this script, you should take down the Phabricator web ". "interface and stop any running Phabricator daemons."); if (!phutil_console_confirm('Are you ready to continue?')) { echo "Cancelled.\n"; exit(1); } } // Use always the version from the commandline if it is defined $next_version = isset($options['v']) ? (int)$options['v'] : null; -// TODO: Get this stuff from DatabaseConfigurationProvider? +$conf = DatabaseConfigurationProvider::getConfiguration(); + if ($options['u']) { $conn_user = $options['u']; $conn_pass = $options['p']; } else { - $conn_user = PhabricatorEnv::getEnvConfig('mysql.user'); - $conn_pass = PhabricatorEnv::getEnvConfig('mysql.pass'); + $conn_user = $conf->getUser(); + $conn_pass = $conf->getPassword(); } -$conn_host = PhabricatorEnv::getEnvConfig('mysql.host'); +$conn_host = $conf->getHost(); // Split out port information, since the command-line client requires a // separate flag for the port. $uri = new PhutilURI('mysql://'.$conn_host); if ($uri->getPort()) { $conn_port = $uri->getPort(); $conn_bare_hostname = $uri->getDomain(); } else { $conn_port = null; $conn_bare_hostname = $conn_host; } $conn = new AphrontMySQLDatabaseConnection( array( 'user' => $conn_user, 'pass' => $conn_pass, 'host' => $conn_host, 'database' => null, )); try { $create_sql = <<getMessage(). "\n\n"; exit(1); } function usage() { echo "usage: upgrade_schema.php [-v version] [-u user -p pass] [-f] [-h]". "\n\n". "Run 'upgrade_schema.php -u root -p hunter2' to override the configured ". "default user.\n". "Run 'upgrade_schema.php -v 12' to apply all patches starting from ". "version 12. It is very unlikely you need to do this.\n". "Use the -f flag to upgrade noninteractively, without prompting.\n". "Use the -h flag to show this help.\n"; exit(1); } diff --git a/src/applications/base/storage/configuration/DatabaseConfigurationProvider.php b/src/applications/base/storage/configuration/DatabaseConfigurationProvider.php index 6ec9771dd9..cdc9652890 100644 --- a/src/applications/base/storage/configuration/DatabaseConfigurationProvider.php +++ b/src/applications/base/storage/configuration/DatabaseConfigurationProvider.php @@ -1,51 +1,62 @@ dao = $dao; $this->mode = $mode; } public function getUser() { return PhabricatorEnv::getEnvConfig('mysql.user'); } public function getPassword() { return PhabricatorEnv::getEnvConfig('mysql.pass'); } public function getHost() { return PhabricatorEnv::getEnvConfig('mysql.host'); } public function getDatabase() { return 'phabricator_'.$this->getDao()->getApplicationName(); } final protected function getDao() { return $this->dao; } final protected function getMode() { return $this->mode; } + + public static function getConfiguration() { + // Get DB info. Note that we are using a dummy PhabricatorUser object in + // creating the DatabaseConfigurationProvider, which is not used at all. + $conf_provider = PhabricatorEnv::getEnvConfig( + 'mysql.configuration_provider', 'DatabaseConfigurationProvider'); + PhutilSymbolLoader::loadClass($conf_provider); + $conf = newv($conf_provider, array(new PhabricatorUser(), 'r')); + return $conf; + } + } diff --git a/src/applications/base/storage/configuration/__init__.php b/src/applications/base/storage/configuration/__init__.php index bf6cd309d0..5aa96d158c 100644 --- a/src/applications/base/storage/configuration/__init__.php +++ b/src/applications/base/storage/configuration/__init__.php @@ -1,12 +1,16 @@ getProtocol(); $allowed_protocols = array( 'http' => true, 'https' => true, ); if (empty($allowed_protocols[$protocol])) { self::writeFailure(); self::write( "You must specify the protocol over which your host works (e.g.: ". "\"http:// or https://\")\nin your custom config file.\nRefer to ". "'default.conf.php' for documentation on configuration options.\n"); return; } if (preg_match('/.*\/$/', $host)) { self::write(" okay phabricator.base-uri\n"); } else { self::writeFailure(); self::write( "You must add a trailing slash at the end of the host\n(e.g.: ". "\"http://phabricator.example.com/ instead of ". "http://phabricator.example.com\")\nin your custom config file.". "\nRefer to 'default.conf.php' for documentation on configuration ". "options.\n"); return; } } self::write("[OKAY] Basic configuration OKAY\n"); $issue_gd_warning = false; self::writeHeader('GD LIBRARY'); if (extension_loaded('gd')) { self::write(" okay Extension 'gd' is loaded.\n"); $image_type_map = array( 'imagepng' => 'PNG', 'imagegif' => 'GIF', 'imagejpeg' => 'JPEG', ); foreach ($image_type_map as $function => $image_type) { if (function_exists($function)) { self::write(" okay Support for '{$image_type}' is available.\n"); } else { self::write(" warn Support for '{$image_type}' is not available!\n"); $issue_gd_warning = true; } } } else { self::write(" warn Extension 'gd' is not loaded.\n"); $issue_gd_warning = true; } if ($issue_gd_warning) { self::write( "[WARN] The 'gd' library is missing or lacks full support. ". "Phabricator will not be able to generate image thumbnails without ". "gd.\n"); } else { self::write("[OKAY] 'gd' loaded and has full image type support.\n"); } self::writeHeader('FACEBOOK INTEGRATION'); $fb_auth = PhabricatorEnv::getEnvConfig('facebook.auth-enabled'); if (!$fb_auth) { self::write(" skip 'facebook.auth-enabled' not enabled.\n"); } else { self::write(" okay 'facebook.auth-enabled' is enabled.\n"); $app_id = PhabricatorEnv::getEnvConfig('facebook.application-id'); $app_secret = PhabricatorEnv::getEnvConfig('facebook.application-secret'); if (!$app_id) { self::writeFailure(); self::write( "Setup failure! 'facebook.auth-enabled' is true but there is no ". "setting for 'facebook.application-id'.\n"); return; } else { self::write(" okay 'facebook.application-id' is set.\n"); } if (!is_string($app_id)) { self::writeFailure(); self::write( "Setup failure! 'facebook.application-id' should be a string."); return; } else { self::write(" okay 'facebook.application-id' is string.\n"); } if (!$app_secret) { self::writeFailure(); self::write( "Setup failure! 'facebook.auth-enabled' is true but there is no ". "setting for 'facebook.application-secret'."); return; } else { self::write(" okay 'facebook.application-secret is set.\n"); } self::write("[OKAY] Facebook integration OKAY\n"); } self::writeHeader("MySQL DATABASE CONFIGURATION"); - $conn_user = PhabricatorEnv::getEnvConfig('mysql.user'); - $conn_pass = PhabricatorEnv::getEnvConfig('mysql.pass'); - $conn_host = PhabricatorEnv::getEnvConfig('mysql.host'); + $conf = DatabaseConfigurationProvider::getConfiguration(); + $conn_user = $conf->getUser(); + $conn_pass = $conf->getPassword(); + $conn_host = $conf->getHost(); $timeout = ini_get('mysql.connect_timeout'); if ($timeout > 5) { self::writeNote( "Your MySQL connect timeout is very high ({$timeout} seconds). ". "Consider reducing it by setting 'mysql.connect_timeout' in your ". "php.ini."); } self::write(" okay Trying to connect to MySQL database ". "{$conn_user}@{$conn_host}...\n"); ini_set('mysql.connect_timeout', 2); $conn_raw = new AphrontMySQLDatabaseConnection( array( 'user' => $conn_user, 'pass' => $conn_pass, 'host' => $conn_host, 'database' => null, )); try { queryfx($conn_raw, 'SELECT 1'); self::write(" okay Connection successful!\n"); } catch (AphrontQueryConnectionException $ex) { self::writeFailure(); self::write( "Setup failure! Unable to connect to MySQL database ". "'{$conn_host}' with user '{$conn_user}'. Edit Phabricator ". "configuration keys 'mysql.user', 'mysql.host' and 'mysql.pass' to ". "enable Phabricator to connect."); return; } $databases = queryfx_all($conn_raw, 'SHOW DATABASES'); $databases = ipull($databases, 'Database'); $databases = array_fill_keys($databases, true); if (empty($databases['phabricator_meta_data'])) { self::writeFailure(); self::write( "Setup failure! You haven't loaded the 'initialize.sql' file into ". "MySQL. This file initializes necessary databases. See this guide for ". "instructions:\n"); self::writeDoc('article/Configuration_Guide.html'); return; } else { self::write(" okay Databases have been initialized.\n"); } $schema_version = queryfx_one( $conn_raw, 'SELECT version FROM phabricator_meta_data.schema_version'); $schema_version = idx($schema_version, 'version', 'null'); $expect = PhabricatorSQLPatchList::getExpectedSchemaVersion(); if ($schema_version != $expect) { self::writeFailure(); self::write( "Setup failure! You haven't upgraded your database schema to the ". "latest version. Expected version is '{$expect}', but your local ". "version is '{$schema_version}'. See this guide for instructions:\n"); self::writeDoc('article/Upgrading_Schema.html'); return; } else { self::write(" okay Database schema are up to date (v{$expect}).\n"); } self::write("[OKAY] Database configuration OKAY\n"); self::writeHeader("OUTBOUND EMAIL CONFIGURATION"); $have_adapter = false; $is_ses = false; $adapter = PhabricatorEnv::getEnvConfig('metamta.mail-adapter'); switch ($adapter) { case 'PhabricatorMailImplementationPHPMailerLiteAdapter': $have_adapter = true; if (!Filesystem::pathExists('/usr/bin/sendmail')) { self::writeFailure(); self::write( "Setup failure! You don't have a 'sendmail' binary on this system ". "but outbound email is configured to use sendmail. Install an MTA ". "(like sendmail, qmail or postfix) or use a different outbound ". "mail configuration. See this guide for configuring outbound ". "email:\n"); self::writeDoc('article/Configuring_Outbound_Email.html'); return; } else { self::write(" okay Sendmail is configured.\n"); } break; case 'PhabricatorMailImplementationAmazonSESAdapter': $is_ses = true; $have_adapter = true; if (PhabricatorEnv::getEnvConfig('metamta.can-send-as-user')) { self::writeFailure(); self::write( "Setup failure! 'metamta.can-send-as-user' must be false when ". "configured with Amazon SES."); return; } else { self::write(" okay Sender config looks okay.\n"); } if (!PhabricatorEnv::getEnvConfig('amazon-ses.access-key')) { self::writeFailure(); self::write( "Setup failure! 'amazon-ses.access-key' is not set, but ". "outbound mail is configured to deliver via Amazon SES."); return; } else { self::write(" okay Amazon SES access key is set.\n"); } if (!PhabricatorEnv::getEnvConfig('amazon-ses.secret-key')) { self::writeFailure(); self::write( "Setup failure! 'amazon-ses.secret-key' is not set, but ". "outbound mail is configured to deliver via Amazon SES."); return; } else { self::write(" okay Amazon SES secret key is set.\n"); } if (PhabricatorEnv::getEnvConfig('metamta.send-immediately')) { self::writeNote( "Your configuration uses Amazon SES to deliver email but tries ". "to send it immediately. This will work, but it's slow. ". "Consider configuring the MetaMTA daemon."); } break; case 'PhabricatorMailImplementationTestAdapter': self::write(" skip You have disabled outbound email.\n"); break; default: self::write(" skip Configured with a custom adapter.\n"); break; } if ($have_adapter) { $default = PhabricatorEnv::getEnvConfig('metamta.default-address'); if (!$default || $default == 'noreply@example.com') { self::writeFailure(); self::write( "Setup failure! You have not set 'metamta.default-address'."); return; } else { self::write(" okay metamta.default-address is set.\n"); } if ($is_ses) { self::writeNote( "Make sure you've verified your 'from' address ('{$default}') with ". "Amazon SES. Until you verify it, you will be unable to send mail ". "using Amazon SES."); } $domain = PhabricatorEnv::getEnvConfig('metamta.domain'); if (!$domain || $domain == 'example.com') { self::writeFailure(); self::write( "Setup failure! You have not set 'metamta.domain'."); return; } else { self::write(" okay metamta.domain is set.\n"); } self::write("[OKAY] Mail configuration OKAY\n"); } self::writeHeader('SUCCESS!'); self::write( "Congratulations! Your setup seems mostly correct, or at least fairly ". "reasonable.\n\n". "*** NEXT STEP ***\n". "Edit your configuration file (conf/{$env}.conf.php) and remove the ". "'phabricator.setup' line to finish installation."); } public static function requireExtension($extension) { if (extension_loaded($extension)) { self::write(" okay Extension '{$extension}' installed.\n"); return true; } else { self::write("[FAIL] Extension '{$extension}' is NOT INSTALLED!\n"); return false; } } private static function writeFailure() { self::write("\n\n<<< *** FAILURE! *** >>>\n"); } private static function write($str) { echo $str; ob_flush(); flush(); // This, uh, makes it look cool. -_- usleep(40000); } private static function writeNote($note) { self::write( 'Note: '.wordwrap($note, 75, "\n ", true)."\n\n"); } public static function writeHeader($header) { $template = '>>>'.str_repeat('-', 77); $template = substr_replace( $template, ' '.$header.' ', 3, strlen($header) + 4); self::write("\n\n{$template}\n\n"); } public static function writeDoc($doc) { self::write( "\n". ' http://phabricator.com/docs/phabricator/'.$doc. "\n\n"); } } diff --git a/src/infrastructure/setup/__init__.php b/src/infrastructure/setup/__init__.php index b2b41afd60..61104d7777 100644 --- a/src/infrastructure/setup/__init__.php +++ b/src/infrastructure/setup/__init__.php @@ -1,18 +1,22 @@