protected function executeChecks() { $master = PhabricatorDatabaseRef::getMasterDatabaseRef(); if (!$master) { // If we're implicitly in read-only mode during disaster recovery, // don't bother with these setup checks. return; } $conn_raw = $master->newManagementConnection(); try { queryfx($conn_raw, 'SELECT 1'); $database_exception = null; } catch (AphrontInvalidCredentialsQueryException $ex) { $database_exception = $ex; } catch (AphrontConnectionQueryException $ex) { $database_exception = $ex; } if ($database_exception) { $issue = PhabricatorSetupIssue::newDatabaseConnectionIssue($database_exception); $this->addIssue($issue); return; } $engines = queryfx_all($conn_raw, 'SHOW ENGINES'); $engines = ipull($engines, 'Support', 'Engine'); $innodb = idx($engines, 'InnoDB'); if ($innodb != 'YES' && $innodb != 'DEFAULT') { $message = pht("The 'InnoDB' engine is not available in MySQL. Enable InnoDB in " . "your MySQL configuration." . "\n\n" . "(If you aleady created tables, MySQL incorrectly used some other " . "engine to create them. You need to convert them or drop and " . "reinitialize them.)"); $this->newIssue('mysql.innodb')->setName(pht('MySQL InnoDB Engine Not Available'))->setMessage($message)->setIsFatal(true); return; } $namespace = PhabricatorEnv::getEnvConfig('storage.default-namespace'); $databases = queryfx_all($conn_raw, 'SHOW DATABASES'); $databases = ipull($databases, 'Database', 'Database'); if (empty($databases[$namespace . '_meta_data'])) { $message = pht("Run the storage upgrade script to setup Phabricator's database " . "schema."); $this->newIssue('storage.upgrade')->setName(pht('Setup MySQL Schema'))->setMessage($message)->setIsFatal(true)->addCommand(hsprintf('<tt>phabricator/ $</tt> ./bin/storage upgrade')); } else { $conn_meta = $master->newApplicationConnection($namespace . '_meta_data'); $applied = queryfx_all($conn_meta, 'SELECT patch FROM patch_status'); $applied = ipull($applied, 'patch', 'patch'); $all = PhabricatorSQLPatchList::buildAllPatches(); $diff = array_diff_key($all, $applied); if ($diff) { $this->newIssue('storage.patch')->setName(pht('Upgrade MySQL Schema'))->setMessage(pht("Run the storage upgrade script to upgrade Phabricator's " . "database schema. Missing patches:<br />%s<br />", phutil_implode_html(phutil_tag('br'), array_keys($diff))))->addCommand(hsprintf('<tt>phabricator/ $</tt> ./bin/storage upgrade')); } } $host = PhabricatorEnv::getEnvConfig('mysql.host'); $matches = null; if (preg_match('/^([^:]+):(\\d+)$/', $host, $matches)) { $host = $matches[1]; $port = $matches[2]; $this->newIssue('storage.mysql.hostport')->setName(pht('Deprecated mysql.host Format'))->setSummary(pht('Move port information from `%s` to `%s` in your config.', 'mysql.host', 'mysql.port'))->setMessage(pht('Your `%s` configuration contains a port number, but this usage ' . 'is deprecated. Instead, put the port number in `%s`.', 'mysql.host', 'mysql.port'))->addPhabricatorConfig('mysql.host')->addPhabricatorConfig('mysql.port')->addCommand(hsprintf('<tt>phabricator/ $</tt> ./bin/config set mysql.host %s', $host))->addCommand(hsprintf('<tt>phabricator/ $</tt> ./bin/config set mysql.port %s', $port)); } }
protected function executeChecks() { $conf = PhabricatorEnv::newObjectFromConfig('mysql.configuration-provider'); $conn_user = $conf->getUser(); $conn_pass = $conf->getPassword(); $conn_host = $conf->getHost(); $conn_port = $conf->getPort(); ini_set('mysql.connect_timeout', 2); $config = array('user' => $conn_user, 'pass' => $conn_pass, 'host' => $conn_host, 'port' => $conn_port, 'database' => null); $conn_raw = PhabricatorEnv::newObjectFromConfig('mysql.implementation', array($config)); try { queryfx($conn_raw, 'SELECT 1'); } catch (AphrontConnectionQueryException $ex) { $message = pht("Unable to connect to MySQL!\n\n" . "%s\n\n" . "Make sure Phabricator and MySQL are correctly configured.", $ex->getMessage()); $this->newIssue('mysql.connect')->setName(pht('Can Not Connect to MySQL'))->setMessage($message)->setIsFatal(true)->addRelatedPhabricatorConfig('mysql.host')->addRelatedPhabricatorConfig('mysql.port')->addRelatedPhabricatorConfig('mysql.user')->addRelatedPhabricatorConfig('mysql.pass'); return; } $engines = queryfx_all($conn_raw, 'SHOW ENGINES'); $engines = ipull($engines, 'Support', 'Engine'); $innodb = idx($engines, 'InnoDB'); if ($innodb != 'YES' && $innodb != 'DEFAULT') { $message = pht("The 'InnoDB' engine is not available in MySQL. Enable InnoDB in " . "your MySQL configuration." . "\n\n" . "(If you aleady created tables, MySQL incorrectly used some other " . "engine to create them. You need to convert them or drop and " . "reinitialize them.)"); $this->newIssue('mysql.innodb')->setName(pht('MySQL InnoDB Engine Not Available'))->setMessage($message)->setIsFatal(true); return; } $namespace = PhabricatorEnv::getEnvConfig('storage.default-namespace'); $databases = queryfx_all($conn_raw, 'SHOW DATABASES'); $databases = ipull($databases, 'Database', 'Database'); if (empty($databases[$namespace . '_meta_data'])) { $message = pht("Run the storage upgrade script to setup Phabricator's database " . "schema."); $this->newIssue('storage.upgrade')->setName(pht('Setup MySQL Schema'))->setMessage($message)->setIsFatal(true)->addCommand(hsprintf('<tt>phabricator/ $</tt> ./bin/storage upgrade')); } else { $config['database'] = $namespace . '_meta_data'; $conn_meta = PhabricatorEnv::newObjectFromConfig('mysql.implementation', array($config)); $applied = queryfx_all($conn_meta, 'SELECT patch FROM patch_status'); $applied = ipull($applied, 'patch', 'patch'); $all = PhabricatorSQLPatchList::buildAllPatches(); $diff = array_diff_key($all, $applied); if ($diff) { $this->newIssue('storage.patch')->setName(pht('Upgrade MySQL Schema'))->setMessage(pht("Run the storage upgrade script to upgrade Phabricator's " . "database schema. Missing patches:<br />%s<br />", phutil_implode_html(phutil_tag('br'), array_keys($diff))))->addCommand(hsprintf('<tt>phabricator/ $</tt> ./bin/storage upgrade')); } } $host = PhabricatorEnv::getEnvConfig('mysql.host'); $matches = null; if (preg_match('/^([^:]+):(\\d+)$/', $host, $matches)) { $host = $matches[1]; $port = $matches[2]; $this->newIssue('storage.mysql.hostport')->setName(pht('Deprecated mysql.host Format'))->setSummary(pht('Move port information from `%s` to `%s` in your config.', 'mysql.host', 'mysql.port'))->setMessage(pht('Your `%s` configuration contains a port number, but this usage ' . 'is deprecated. Instead, put the port number in `%s`.', 'mysql.host', 'mysql.port'))->addPhabricatorConfig('mysql.host')->addPhabricatorConfig('mysql.port')->addCommand(hsprintf('<tt>phabricator/ $</tt> ./bin/config set mysql.host %s', $host))->addCommand(hsprintf('<tt>phabricator/ $</tt> ./bin/config set mysql.port %s', $port)); } }
private function getDatabaseNames() { $api = $this->getAPI(); $patches = PhabricatorSQLPatchList::buildAllPatches(); return $api->getDatabaseList($patches, $only_living = true); }
`version` INTEGER not null ); END; queryfx($conn, $create_sql); // Get the version only if commandline argument wasn't given if ($next_version === null) { $version = queryfx_one($conn, 'SELECT * FROM phabricator_meta_data.%T', SCHEMA_VERSION_TABLE_NAME); if (!$version) { print "*** No version information in the database ***\n"; print "*** Give the first patch version which to ***\n"; print "*** apply as the command line argument ***\n"; exit(-1); } $next_version = $version['version'] + 1; } $patches = PhabricatorSQLPatchList::getPatchList(); $patch_applied = false; foreach ($patches as $patch) { if ($patch['version'] < $next_version) { continue; } $short_name = basename($patch['path']); print "Applying patch {$short_name}...\n"; if ($conn_port) { $port = '--port=' . (int) $conn_port; } else { $port = null; } if (preg_match('/\\.php$/', $patch['path'])) { $schema_conn = $conn; require_once $patch['path'];
if ($args->getArg('password') === null) { // This is already a PhutilOpaqueEnvelope. $password = $conf->getPassword(); } else { // Put this in a PhutilOpaqueEnvelope. $password = new PhutilOpaqueEnvelope($args->getArg('password')); PhabricatorEnv::overrideConfig('mysql.pass', $args->getArg('password')); } $api = new PhabricatorStorageManagementAPI(); $api->setUser($args->getArg('user')); PhabricatorEnv::overrideConfig('mysql.user', $args->getArg('user')); $api->setHost($default_host); $api->setPort($default_port); $api->setPassword($password); $api->setNamespace($args->getArg('namespace')); $api->setDisableUTF8MB4($args->getArg('disable-utf8mb4')); try { queryfx($api->getConn(null), 'SELECT 1'); } catch (AphrontQueryException $ex) { $message = phutil_console_format("**%s**\n\n%s\n\n**%s**: %s\n", pht('Bad Administrative Credentials'), pht('Unable to connect to MySQL using the administrative credentials ' . 'provided with the __%s__ and __%s__ flags. Check that ' . 'you have entered them correctly.', '--user', '--password'), pht('Raw MySQL Error'), $ex->getMessage()); echo phutil_console_wrap($message); exit(1); } $workflows = id(new PhutilSymbolLoader())->setAncestorClass('PhabricatorStorageManagementWorkflow')->loadObjects(); $patches = PhabricatorSQLPatchList::buildAllPatches(); foreach ($workflows as $workflow) { $workflow->setAPI($api); $workflow->setPatches($patches); } $workflows[] = new PhutilHelpArgumentWorkflow(); $args->parseWorkflows($workflows);
public static function runSetup() { header("Content-Type: text/plain"); self::write("PHABRICATOR SETUP\n\n"); // Force browser to stop buffering. self::write(str_repeat(' ', 2048)); usleep(250000); self::write("This setup mode will guide you through setting up your " . "Phabricator configuration.\n"); self::writeHeader("CORE CONFIGURATION"); // NOTE: Test this first since other tests depend on the ability to // execute system commands and will fail if safe_mode is enabled. $safe_mode = ini_get('safe_mode'); if ($safe_mode) { self::writeFailure(); self::write("Setup failure! You have 'safe_mode' enabled. Phabricator will not " . "run in safe mode, and it has been deprecated in PHP 5.3 and removed " . "in PHP 5.4.\n"); return; } else { self::write(" okay PHP's deprecated 'safe_mode' is disabled.\n"); } // NOTE: Also test this early since we can't include files from other // libraries if this is set strictly. $open_basedir = ini_get('open_basedir'); if ($open_basedir) { // 'open_basedir' restricts which files we're allowed to access with // file operations. This might be okay -- we don't need to write to // arbitrary places in the filesystem -- but we need to access certain // resources. This setting is unlikely to be providing any real measure // of security so warn even if things look OK. try { phutil_require_module('phutil', 'utils'); $open_libphutil = true; } catch (Exception $ex) { $message = $ex->getMessage(); self::write("Unable to load modules from libphutil: {$message}\n"); $open_libphutil = false; } try { phutil_require_module('arcanist', 'workflow/base'); $open_arcanist = true; } catch (Exception $ex) { $message = $ex->getMessage(); self::write("Unable to load modules from Arcanist: {$message}\n"); $open_arcanist = false; } $open_urandom = @fopen('/dev/urandom', 'r'); if (!$open_urandom) { self::write("Unable to open /dev/urandom!\n"); } try { $tmp = new TempFile(); file_put_contents($tmp, '.'); $open_tmp = @fopen((string) $tmp, 'r'); } catch (Exception $ex) { $message = $ex->getMessage(); $dir = sys_get_temp_dir(); self::write("Unable to open temp files from '{$dir}': {$message}\n"); $open_tmp = false; } if (!$open_urandom || !$open_tmp || !$open_libphutil || !$open_arcanist) { self::writeFailure(); self::write("Setup failure! Your server is configured with 'open_basedir' in " . "php.ini which prevents Phabricator from opening files it needs to " . "access. Either make the setting more permissive or remove it. It " . "is unlikely you derive significant security benefits from having " . "this configured; files outside this directory can still be " . "accessed through system command execution."); return; } else { self::write("[WARN] You have an 'open_basedir' configured in your php.ini. " . "Although the setting seems permissive enough that Phabricator " . "will run properly, you may run into problems because of it. It is " . "unlikely you gain much real security benefit from having it " . "configured, because the application can still access files outside " . "the 'open_basedir' by running system commands.\n"); } } else { self::write(" okay 'open_basedir' is not set.\n"); } self::write("[OKAY] Core configuration OKAY.\n"); self::writeHeader("REQUIRED PHP EXTENSIONS"); $extensions = array('mysql', 'hash', 'json', 'openssl', 'curl'); foreach ($extensions as $extension) { $ok = self::requireExtension($extension); if (!$ok) { self::writeFailure(); self::write("Setup failure! Install PHP extension '{$extension}'."); return; } } $root = dirname(phutil_get_library_root('phabricator')); // On RHEL6, doing a distro install of pcntl makes it available from the // CLI binary but not from the Apache module. This isn't entirely // unreasonable and we don't need it from Apache, so do an explicit test // for CLI availability. list($err, $stdout, $stderr) = exec_manual('%s/scripts/setup/pcntl_available.php', $root); if ($err) { self::writeFailure(); self::write("Unable to execute scripts/setup/pcntl_available.php."); return; } else { if (trim($stdout) == 'YES') { self::write(" okay pcntl is available from the command line.\n"); self::write("[OKAY] All extensions OKAY\n"); } else { self::write(" warn pcntl is not available!\n"); self::write("[WARN] *** WARNING *** pcntl extension not available. " . "You will not be able to run daemons.\n"); } } self::writeHeader("GIT SUBMODULES"); if (!Filesystem::pathExists($root . '/.git')) { self::write(" skip Not a git clone.\n\n"); } else { list($info) = execx('(cd %s && git submodule status)', $root); foreach (explode("\n", rtrim($info)) as $line) { $matches = null; if (!preg_match('/^(.)([0-9a-f]{40}) (\\S+)(?: |$)/', $line, $matches)) { self::writeFailure(); self::write("Setup failure! 'git submodule' produced unexpected output:\n" . $line); return; } $status = $matches[1]; $module = $matches[3]; switch ($status) { case '-': case '+': case 'U': self::writeFailure(); self::write("Setup failure! Git submodule '{$module}' is not up to date. " . "Run:\n\n" . " cd {$root} && git submodule update --init\n\n" . "...to update submodules."); return; case ' ': self::write(" okay Git submodule '{$module}' up to date.\n"); break; default: self::writeFailure(); self::write("Setup failure! 'git submodule' reported unknown status " . "'{$status}' for submodule '{$module}'. This is a bug; report " . "it to the Phabricator maintainers."); return; } } } self::write("[OKAY] All submodules OKAY.\n"); self::writeHeader("BASIC CONFIGURATION"); $env = PhabricatorEnv::getEnvConfig('phabricator.env'); if ($env == 'production' || $env == 'default' || $env == 'development') { self::writeFailure(); self::write("Setup failure! Your PHABRICATOR_ENV is set to '{$env}', which is " . "a Phabricator environmental default. You should create a custom " . "environmental configuration instead of editing the defaults " . "directly. See this document for instructions:\n"); self::writeDoc('article/Configuration_Guide.html'); return; } else { $host = PhabricatorEnv::getEnvConfig('phabricator.base-uri'); $protocol = id(new PhutilURI($host))->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; } } $timezone = nonempty(PhabricatorEnv::getEnvConfig('phabricator.timezone'), ini_get('date.timezone')); if (!$timezone) { self::writeFailure(); self::write("Setup failure! Your configuration fails to specify a server " . "timezone. Either set 'date.timezone' in your php.ini or " . "'phabricator.timezone' in your Phabricator configuration. See the " . "PHP documentation for a list of supported timezones:\n\n" . "http://us.php.net/manual/en/timezones.php\n"); return; } else { self::write(" okay Timezone '{$timezone}' configured.\n"); } 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 & STORAGE CONFIGURATION"); $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"); } $index_min_length = queryfx_one($conn_raw, 'SHOW VARIABLES LIKE %s', 'ft_min_word_len'); $index_min_length = idx($index_min_length, 'Value', 4); if ($index_min_length >= 4) { self::writeNote("MySQL is configured with a 'ft_min_word_len' of 4 or greater, which " . "means you will not be able to search for 3-letter terms. Consider " . "setting this in your configuration:\n" . "\n" . " [mysqld]\n" . " ft_min_word_len=3\n" . "\n" . "Then optionally run:\n" . "\n" . " REPAIR TABLE phabricator_search.search_documentfield QUICK;\n" . "\n" . "...to reindex existing documents."); } $max_allowed_packet = queryfx_one($conn_raw, 'SHOW VARIABLES LIKE %s', 'max_allowed_packet'); $max_allowed_packet = idx($max_allowed_packet, 'Value', PHP_INT_MAX); $recommended_minimum = 1024 * 1024; if ($max_allowed_packet < $recommended_minimum) { self::writeNote("MySQL is configured with a small 'max_allowed_packet' " . "('{$max_allowed_packet}'), which may cause some large writes to " . "fail. Consider raising this to at least {$recommended_minimum}."); } else { self::write(" okay max_allowed_packet = {$max_allowed_packet}.\n"); } $mysql_key = 'storage.mysql-engine.max-size'; $mysql_limit = PhabricatorEnv::getEnvConfig($mysql_key); if ($mysql_limit && $mysql_limit + 8192 > $max_allowed_packet) { self::writeFailure(); self::write("Setup failure! Your Phabricator 'storage.mysql-engine.max-size' " . "configuration ('{$mysql_limit}') must be at least 8KB smaller " . "than your MySQL 'max_allowed_packet' configuration " . "('{$max_allowed_packet}'). Raise the 'max_allowed_packet' in your " . "MySQL configuration, or reduce the maximum file size allowed by " . "the Phabricator configuration.\n"); return; } else { if (!$mysql_limit) { self::write(" skip MySQL file storage engine not configured.\n"); } else { self::write(" okay MySQL file storage engine configuration okay.\n"); } } $local_key = 'storage.local-disk.path'; $local_path = PhabricatorEnv::getEnvConfig($local_key); if ($local_path) { if (!Filesystem::pathExists($local_path) || !is_readable($local_path) || !is_writable($local_path)) { self::writeFailure(); self::write("Setup failure! You have configured local disk storage but the " . "path you specified ('{$local_path}') does not exist or is not " . "readable or writable.\n"); if ($open_basedir) { self::write("You have an 'open_basedir' setting -- make sure Phabricator is " . "allowed to open files in the local storage directory.\n"); } return; } else { self::write(" okay Local disk storage exists and is writable.\n"); } } else { self::write(" skip Not configured for local disk storage.\n"); } $selector = PhabricatorEnv::getEnvConfig('storage.engine-selector'); try { $storage_selector_exists = class_exists($selector); } catch (Exception $ex) { $storage_selector_exists = false; } if ($storage_selector_exists) { self::write(" okay Using '{$selector}' as a storage engine selector.\n"); } else { self::writeFailure(); self::write("Setup failure! You have configured '{$selector}' as a storage engine " . "selector but it does not exist or could not be loaded.\n"); return; } self::write("[OKAY] Database and storage 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') && !Filesystem::pathExists('/usr/sbin/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 == '*****@*****.**') { 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."); }
private function executeRefChecks(PhabricatorDatabaseRef $ref) { $conn_raw = $ref->newManagementConnection(); $ref_key = $ref->getRefKey(); $engines = queryfx_all($conn_raw, 'SHOW ENGINES'); $engines = ipull($engines, 'Support', 'Engine'); $innodb = idx($engines, 'InnoDB'); if ($innodb != 'YES' && $innodb != 'DEFAULT') { $message = pht('The "InnoDB" engine is not available in MySQL (on host "%s"). ' . 'Enable InnoDB in your MySQL configuration.' . "\n\n" . '(If you aleady created tables, MySQL incorrectly used some other ' . 'engine to create them. You need to convert them or drop and ' . 'reinitialize them.)', $ref_key); $this->newIssue('mysql.innodb')->setName(pht('MySQL InnoDB Engine Not Available'))->setMessage($message)->setIsFatal(true); return true; } $namespace = PhabricatorEnv::getEnvConfig('storage.default-namespace'); $databases = queryfx_all($conn_raw, 'SHOW DATABASES'); $databases = ipull($databases, 'Database', 'Database'); if (empty($databases[$namespace . '_meta_data'])) { $message = pht('Run the storage upgrade script to setup databases (host "%s" has ' . 'not been initialized).', $ref_key); $this->newIssue('storage.upgrade')->setName(pht('Setup MySQL Schema'))->setMessage($message)->setIsFatal(true)->addCommand(hsprintf('<tt>phabricator/ $</tt> ./bin/storage upgrade')); return true; } $conn_meta = $ref->newApplicationConnection($namespace . '_meta_data'); $applied = queryfx_all($conn_meta, 'SELECT patch FROM patch_status'); $applied = ipull($applied, 'patch', 'patch'); $all = PhabricatorSQLPatchList::buildAllPatches(); $diff = array_diff_key($all, $applied); if ($diff) { $message = pht('Run the storage upgrade script to upgrade databases (host "%s" is ' . 'out of date). Missing patches: %s.', $ref_key, implode(', ', array_keys($diff))); $this->newIssue('storage.patch')->setName(pht('Upgrade MySQL Schema'))->setIsFatal(true)->setMessage($message)->addCommand(hsprintf('<tt>phabricator/ $</tt> ./bin/storage upgrade')); return true; } // NOTE: It's possible that replication is broken but we have not been // granted permission to "SHOW SLAVE STATUS" so we can't figure it out. // We allow this kind of configuration and survive these checks, trusting // that operations knows what they're doing. This issue is shown on the // "Database Servers" console. switch ($ref->getReplicaStatus()) { case PhabricatorDatabaseRef::REPLICATION_MASTER_REPLICA: $message = pht('Database host "%s" is configured as a master, but is replicating ' . 'another host. This is dangerous and can mangle or destroy data. ' . 'Only replicas should be replicating. Stop replication on the ' . 'host or reconfigure Phabricator.', $ref->getRefKey()); $this->newIssue('db.master.replicating')->setName(pht('Replicating Master'))->setIsFatal(true)->setMessage($message); return true; case PhabricatorDatabaseRef::REPLICATION_REPLICA_NONE: case PhabricatorDatabaseRef::REPLICATION_NOT_REPLICATING: if (!$ref->getIsMaster()) { $message = pht('Database replica "%s" is listed as a replica, but is not ' . 'currently replicating. You are vulnerable to data loss if ' . 'the master fails.', $ref->getRefKey()); // This isn't a fatal because it can normally only put data at risk, // not actually do anything destructive or unrecoverable. $this->newIssue('db.replica.not-replicating')->setName(pht('Nonreplicating Replica'))->setMessage($message); } break; } // If we have more than one master, we require that the cluster database // configuration written to each database node is exactly the same as the // one we are running with. $masters = PhabricatorDatabaseRef::getAllMasterDatabaseRefs(); if (count($masters) > 1) { $state_actual = queryfx_one($conn_meta, 'SELECT stateValue FROM %T WHERE stateKey = %s', PhabricatorStorageManagementAPI::TABLE_HOSTSTATE, 'cluster.databases'); if ($state_actual) { $state_actual = $state_actual['stateValue']; } $state_expect = $ref->getPartitionStateForCommit(); if ($state_expect !== $state_actual) { $message = pht('Database host "%s" has a configured cluster state which disagrees ' . 'with the state on this host ("%s"). Run `bin/storage partition` ' . 'to commit local state to the cluster. This host may have started ' . 'with an out-of-date configuration.', $ref->getRefKey(), php_uname('n')); $this->newIssue('db.state.desync')->setName(pht('Cluster Configuration Out of Sync'))->setMessage($message)->setIsFatal(true); return true; } } }