mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-11 09:22:40 +01:00
b71e1c15ef
Summary: - PHP uses a SAPI ("server API") to determine how it interacts with the caller (e.g., how to read the environment, how to read flags, what code to execute). - There are several different SAPIs: cli, cgi, cgi-fcgi, apache, etc. - Each SAPI has different behavior -- for instance, the "cgi" SAPI emits some CGI headers unless told not to, so a script like 'echo "x"' actually echoes some headers and then 'x' as an HTTP body. - In some setups, "php" may be php-cgi. - If you run php-cgi as "php scriptname.php" and your ENV has an existing CGI request in it, it runs that CGI request instead of the script. This causes an infinite loop. - Add checks to verify that "php" is the "cli" SAPI binary, not some other SAPI. - In particular, cPanel uses suphp and is affected by this configuration issue. See this thread: https://lists.marsching.com/pipermail/suphp/2008-September/002036.html Test Plan: - On a cPanel + suphp machine, ran setup and was stopped for having the "cgi-fcgi" SAPI instead of throw into an infinite loop. - Applied the suggested remedy, setup now runs fine. Reviewers: btrahan, jungejason Reviewed By: btrahan CC: aran, btrahan, epriestley Differential Revision: https://secure.phabricator.com/D1390
773 lines
27 KiB
PHP
773 lines
27 KiB
PHP
<?php
|
|
|
|
/*
|
|
* Copyright 2012 Facebook, Inc.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
class PhabricatorSetup {
|
|
|
|
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");
|
|
}
|
|
|
|
if (!PhabricatorEnv::getEnvConfig('security.alternate-file-domain')) {
|
|
self::write(
|
|
"[WARN] You have not configured 'security.alternate-file-domain'. ".
|
|
"This may make your installation vulnerable to attack. Make sure ".
|
|
"you read the documentation for this parameter and understand the ".
|
|
"consequences of leaving it unconfigured.\n");
|
|
}
|
|
|
|
self::write("[OKAY] Core configuration OKAY.\n");
|
|
|
|
self::writeHeader("REQUIRED PHP EXTENSIONS");
|
|
$extensions = array(
|
|
'mysql',
|
|
'hash',
|
|
'json',
|
|
'openssl',
|
|
'mbstring',
|
|
'iconv',
|
|
|
|
// There is a chance we might not need this, but some configurations (like
|
|
// Amazon SES) will require it. Just mark it 'required' since it's widely
|
|
// available and relatively core.
|
|
'curl',
|
|
);
|
|
foreach ($extensions as $extension) {
|
|
$ok = self::requireExtension($extension);
|
|
if (!$ok) {
|
|
self::writeFailure();
|
|
self::write("Setup failure! Install PHP extension '{$extension}'.");
|
|
return;
|
|
}
|
|
}
|
|
|
|
list($err, $stdout, $stderr) = exec_manual('which php');
|
|
if ($err) {
|
|
self::writeFailure();
|
|
self::write("Unable to locate 'php' on the command line from the web ".
|
|
"server. Verify that 'php' is in the webserver's PATH.\n".
|
|
" err: {$err}\n".
|
|
"stdout: {$stdout}\n".
|
|
"stderr: {$stderr}\n");
|
|
return;
|
|
} else {
|
|
self::write(" okay PHP binary found on the command line.\n");
|
|
$php_bin = trim($stdout);
|
|
}
|
|
|
|
// NOTE: In cPanel + suphp installs, 'php' may be the PHP CGI SAPI, not the
|
|
// PHP CLI SAPI. proc_open() will pass the environment to the child process,
|
|
// which will re-execute the webpage (causing an infinite number of
|
|
// processes to spawn). To test that the 'php' binary is safe to execute,
|
|
// we call php_sapi_name() using "env -i" to wipe the environment so it
|
|
// doesn't execute another reuqest if it's the wrong binary. We can't use
|
|
// "-r" because php-cgi doesn't support that flag.
|
|
|
|
$tmp_file = new TempFile('sapi.php');
|
|
Filesystem::writeFile($tmp_file, '<?php echo php_sapi_name();');
|
|
|
|
list($err, $stdout, $stderr) = exec_manual(
|
|
'/usr/bin/env -i %s -f %s',
|
|
$php_bin,
|
|
$tmp_file);
|
|
if ($err) {
|
|
self::writeFailure();
|
|
self::write("Unable to execute 'php' on the command line from the web ".
|
|
"server.\n".
|
|
" err: {$err}\n".
|
|
"stdout: {$stdout}\n".
|
|
"stderr: {$stderr}\n");
|
|
return;
|
|
} else {
|
|
self::write(" okay PHP is available from the command line.\n");
|
|
|
|
$sapi = trim($stdout);
|
|
if ($sapi != 'cli') {
|
|
self::writeFailure();
|
|
self::write(
|
|
"The 'php' binary on this system uses the '{$sapi}' SAPI, but the ".
|
|
"'cli' SAPI is expected. Replace 'php' with the php-cli SAPI ".
|
|
"binary, or edit your webserver configuration so the first 'php' ".
|
|
"in PATH is the 'cli' SAPI.\n\n".
|
|
"If you're running cPanel with suphp, the easiest way to fix this ".
|
|
"is to add '/usr/local/bin' before '/usr/bin' for 'env_path' in ".
|
|
"suconf.php:\n\n".
|
|
' env_path="/bin:/usr/local/bin:/usr/bin"'.
|
|
"\n\n");
|
|
return;
|
|
} else {
|
|
self::write(" okay 'php' is CLI SAPI.\n");
|
|
}
|
|
}
|
|
|
|
$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 to ".
|
|
"test for the availability of pcntl from the CLI.\n".
|
|
" err: {$err}\n".
|
|
"stdout: {$stdout}\n".
|
|
"stderr: {$stderr}\n");
|
|
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');
|
|
$host_uri = new PhutilURI($host);
|
|
$protocol = $host_uri->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 protocol\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;
|
|
}
|
|
|
|
$host_domain = $host_uri->getDomain();
|
|
if (strpos($host_domain, '.') !== false) {
|
|
self::write(" okay phabricator.base-uri domain\n");
|
|
} else {
|
|
self::writeFailure();
|
|
self::write(
|
|
"You must host Phabricator on a domain that contains a dot ('.'). ".
|
|
"The current domain, '{$host_domain}', does not have a dot, so some ".
|
|
"browsers will not set cookies on it. For instance, ".
|
|
"'http://example.com/ is OK, but 'http://example/' won't work. ".
|
|
"If you are using localhost, create an entry in the hosts file like ".
|
|
"'127.0.0.1 example.com', and access the localhost with ".
|
|
"'http://example.com/'.");
|
|
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) {
|
|
$message = $ex->getMessage();
|
|
self::writeFailure();
|
|
self::write(
|
|
"Setup failure! MySQL exception: {$message} \n".
|
|
"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.");
|
|
|
|
}
|
|
|
|
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(20000);
|
|
}
|
|
|
|
private static function writeNote($note) {
|
|
$note = "*** NOTE: ".wordwrap($note, 75, "\n", true);
|
|
$note = "\n".str_replace("\n", "\n ", $note)."\n\n";
|
|
self::write($note);
|
|
}
|
|
|
|
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");
|
|
}
|
|
|
|
}
|