$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;
$conf = DatabaseConfigurationProvider::getConfiguration();
if ($options['u']) {
    $conn_user = $options['u'];
    $conn_pass = $options['p'];
} else {
    $conn_user = $conf->getUser();
    $conn_pass = $conf->getPassword();
}
$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 {
 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 == '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.");
 }