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);
 }
Пример #4
0
    `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'];
Пример #5
0
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;
         }
     }
 }