2011-05-05 20:00:05 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
/*
|
2012-01-11 01:42:00 +01:00
|
|
|
* Copyright 2012 Facebook, Inc.
|
2011-05-05 20:00:05 +02:00
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
2012-03-14 00:21:04 +01:00
|
|
|
final class PhabricatorSetup {
|
2011-05-05 20:00:05 +02:00
|
|
|
|
|
|
|
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");
|
|
|
|
|
2011-07-24 20:59:16 +02:00
|
|
|
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 {
|
2012-05-31 01:38:53 +02:00
|
|
|
$open_libphutil = class_exists('Future');
|
2011-07-24 20:59:16 +02:00
|
|
|
} catch (Exception $ex) {
|
|
|
|
$message = $ex->getMessage();
|
|
|
|
self::write("Unable to load modules from libphutil: {$message}\n");
|
|
|
|
$open_libphutil = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
2012-05-31 01:38:53 +02:00
|
|
|
$open_arcanist = class_exists('ArcanistDiffParser');
|
2011-07-24 20:59:16 +02:00
|
|
|
} catch (Exception $ex) {
|
|
|
|
$message = $ex->getMessage();
|
|
|
|
self::write("Unable to load modules from Arcanist: {$message}\n");
|
|
|
|
$open_arcanist = false;
|
|
|
|
}
|
|
|
|
|
2012-04-06 18:54:53 +02:00
|
|
|
$open_urandom = false;
|
|
|
|
try {
|
|
|
|
Filesystem::readRandomBytes(1);
|
|
|
|
$open_urandom = true;
|
|
|
|
} catch (FilesystemException $ex) {
|
|
|
|
self::write($ex->getMessage()."\n");
|
2011-07-24 20:59:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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");
|
|
|
|
}
|
|
|
|
|
Provide a setting which forces all file views to be served from an alternate
domain
Summary:
See D758, D759.
- Provide a strongly recommended setting which permits configuration of an
alternate domain.
- Lock cookies down better: set them on the exact domain, and use SSL-only if
the configuration is HTTPS.
- Prevent Phabriator from setting cookies on other domains.
This assumes D759 will land, it is not effective without that change.
Test Plan:
- Attempted to login from a different domain and was rejected.
- Logged out, logged back in normally.
- Put install in setup mode and verified it revealed a warning.
- Configured an alterate domain.
- Tried to view an image with an old URI, got a 400.
- Went to /files/ and verified links rendered to the alternate domain.
- Viewed an alternate domain file.
- Tried to view an alternate domain file without the secret key, got a 404.
Reviewers: andrewjcg, erling, aran, tuomaspelkonen, jungejason, codeblock
CC: aran
Differential Revision: 760
2011-08-02 07:24:00 +02:00
|
|
|
if (!PhabricatorEnv::getEnvConfig('security.alternate-file-domain')) {
|
|
|
|
self::write(
|
|
|
|
"[WARN] You have not configured 'security.alternate-file-domain'. ".
|
Move ALL files to serve from the alternate file domain, not just files without
"Content-Disposition: attachment"
Summary:
We currently serve some files off the primary domain (with "Content-Disposition:
attachment" + a CSRF check) and some files off the alternate domain (without
either).
This is not sufficient, because some UAs (like the iPad) ignore
"Content-Disposition: attachment". So there's an attack that goes like this:
- Alice uploads xss.html
- Alice says to Bob "hey download this file on your iPad"
- Bob clicks "Download" on Phabricator on his iPad, gets XSS'd.
NOTE: This removes the CSRF check for downloading files. The check is nice to
have but only raises the barrier to entry slightly. Between iPad / sniffing /
flash bytecode attacks, single-domain installs are simply insecure. We could
restore the check at some point in conjunction with a derived authentication
cookie (i.e., a mini-session-token which is only useful for downloading files),
but that's a lot of complexity to drop all at once.
(Because files are now authenticated only by knowing the PHID and secret key,
this also fixes the "no profile pictures in public feed while logged out"
issue.)
Test Plan: Viewed, info'd, and downloaded files
Reviewers: btrahan, arice, alok
Reviewed By: arice
CC: aran, epriestley
Maniphest Tasks: T843
Differential Revision: https://secure.phabricator.com/D1608
2012-02-14 23:52:27 +01:00
|
|
|
"This makes your installation vulnerable to attack. Make sure you ".
|
|
|
|
"read the documentation for this parameter and understand the ".
|
Provide a setting which forces all file views to be served from an alternate
domain
Summary:
See D758, D759.
- Provide a strongly recommended setting which permits configuration of an
alternate domain.
- Lock cookies down better: set them on the exact domain, and use SSL-only if
the configuration is HTTPS.
- Prevent Phabriator from setting cookies on other domains.
This assumes D759 will land, it is not effective without that change.
Test Plan:
- Attempted to login from a different domain and was rejected.
- Logged out, logged back in normally.
- Put install in setup mode and verified it revealed a warning.
- Configured an alterate domain.
- Tried to view an image with an old URI, got a 400.
- Went to /files/ and verified links rendered to the alternate domain.
- Viewed an alternate domain file.
- Tried to view an alternate domain file without the secret key, got a 404.
Reviewers: andrewjcg, erling, aran, tuomaspelkonen, jungejason, codeblock
CC: aran
Differential Revision: 760
2011-08-02 07:24:00 +02:00
|
|
|
"consequences of leaving it unconfigured.\n");
|
|
|
|
}
|
|
|
|
|
Detect empty $PATH environmental var
Summary:
By default, PHP-FMP (an alternate PHP FCGI SAPI) cleans the entire environment
for child processes. This means we have no $PATH.
This causes some confusing failures for reasons I don't fully understand. If you
do these things:
exec_manual('env');
exec_manual('export');
...they show no $PATH, as expected. If you do this:
exec_manual('echo $PATH');
...it shows a path. And this works (i.e., it finds the executable):
exec_manual('ls');
...but this fails (it says "no ls in ((null))"):
exec_manual('which ls');
So, basically, the sh -c process itself gets a default PATH somehow, but its
children don't. I don't realllly get why this happens, but clearly an empty
$PATH is a misconfiguration, and can easily be remedied.
See discussion here: https://github.com/facebook/libphutil/issues/7
Test Plan: Applied patch to Centos6 + nginx + PHP-FPM machine, ran setup, the
configuration issue was detected and I was given information on resolving it.
Reviewers: btrahan, jungejason
Reviewed By: btrahan
CC: aran
Differential Revision: https://secure.phabricator.com/D1413
2012-01-16 15:27:16 +01:00
|
|
|
$path = getenv('PATH');
|
|
|
|
if (empty($path)) {
|
|
|
|
self::writeFailure();
|
|
|
|
self::write(
|
|
|
|
"Setup failure! The environmental \$PATH variable is empty. ".
|
|
|
|
"Phabricator needs to execute system commands like 'svn', 'git', ".
|
|
|
|
"'hg', and 'diff'. Set up your webserver so that it passes a valid ".
|
|
|
|
"\$PATH to the PHP process.\n\n");
|
|
|
|
if (php_sapi_name() == 'fpm-fcgi') {
|
|
|
|
self::write(
|
|
|
|
"You're running php-fpm, so the easiest way to do this is to add ".
|
|
|
|
"this line to your php-fpm.conf:\n\n".
|
|
|
|
" env[PATH] = /usr/local/bin:/usr/bin:/bin\n\n".
|
|
|
|
"Then restart php-fpm.\n");
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
self::write(" okay \$PATH is nonempty.\n");
|
|
|
|
}
|
|
|
|
|
2011-07-24 20:59:16 +02:00
|
|
|
self::write("[OKAY] Core configuration OKAY.\n");
|
|
|
|
|
2011-05-05 20:00:05 +02:00
|
|
|
self::writeHeader("REQUIRED PHP EXTENSIONS");
|
|
|
|
$extensions = array(
|
|
|
|
'mysql',
|
|
|
|
'hash',
|
|
|
|
'json',
|
2011-05-27 20:13:53 +02:00
|
|
|
'openssl',
|
2011-12-01 17:52:54 +01:00
|
|
|
'mbstring',
|
|
|
|
'iconv',
|
2011-06-13 05:16:34 +02:00
|
|
|
|
|
|
|
// There is a chance we might not need this, but some configurations (like
|
2012-06-19 00:11:47 +02:00
|
|
|
// OAuth or Amazon SES) will require it. Just mark it 'required' since
|
|
|
|
// it's widely available and relatively core.
|
2011-06-13 05:16:34 +02:00
|
|
|
'curl',
|
2011-05-05 20:00:05 +02:00
|
|
|
);
|
|
|
|
foreach ($extensions as $extension) {
|
|
|
|
$ok = self::requireExtension($extension);
|
|
|
|
if (!$ok) {
|
|
|
|
self::writeFailure();
|
|
|
|
self::write("Setup failure! Install PHP extension '{$extension}'.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Detect which PHP SAPI the CLI binary uses during setup
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
2012-01-13 20:29:52 +01:00
|
|
|
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();');
|
|
|
|
|
2011-08-31 19:50:40 +02:00
|
|
|
list($err, $stdout, $stderr) = exec_manual(
|
Detect which PHP SAPI the CLI binary uses during setup
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
2012-01-13 20:29:52 +01:00
|
|
|
'/usr/bin/env -i %s -f %s',
|
|
|
|
$php_bin,
|
|
|
|
$tmp_file);
|
2011-08-31 19:50:40 +02:00
|
|
|
if ($err) {
|
|
|
|
self::writeFailure();
|
|
|
|
self::write("Unable to execute 'php' on the command line from the web ".
|
Detect which PHP SAPI the CLI binary uses during setup
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
2012-01-13 20:29:52 +01:00
|
|
|
"server.\n".
|
2011-08-31 19:50:40 +02:00
|
|
|
" err: {$err}\n".
|
|
|
|
"stdout: {$stdout}\n".
|
|
|
|
"stderr: {$stderr}\n");
|
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
self::write(" okay PHP is available from the command line.\n");
|
Detect which PHP SAPI the CLI binary uses during setup
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
2012-01-13 20:29:52 +01:00
|
|
|
|
|
|
|
$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");
|
|
|
|
}
|
2011-08-31 19:50:40 +02:00
|
|
|
}
|
|
|
|
|
2011-05-05 20:00:05 +02:00
|
|
|
$root = dirname(phutil_get_library_root('phabricator'));
|
2011-05-31 03:54:40 +02:00
|
|
|
|
|
|
|
// 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(
|
2012-04-06 08:50:55 +02:00
|
|
|
'php %s',
|
|
|
|
"{$root}/scripts/setup/pcntl_available.php");
|
2011-05-31 03:54:40 +02:00
|
|
|
if ($err) {
|
|
|
|
self::writeFailure();
|
2011-08-29 18:57:10 +02:00
|
|
|
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");
|
2011-05-31 03:54:40 +02:00
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
if (trim($stdout) == 'YES') {
|
|
|
|
self::write(" okay pcntl is available from the command line.\n");
|
2011-06-12 00:11:50 +02:00
|
|
|
self::write("[OKAY] All extensions OKAY\n");
|
2011-05-31 03:54:40 +02:00
|
|
|
} 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");
|
2011-05-05 20:00:05 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2011-06-12 00:11:50 +02:00
|
|
|
self::write("[OKAY] All submodules OKAY.\n");
|
2011-05-05 20:00:05 +02:00
|
|
|
|
|
|
|
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 {
|
Fix regenerate arcanist cert, setup stuff and avoid accept non valid image files as profile picture.
Summary:
Well, since I couldn't regenerate my arcanist cert I figured out that this wass because "workflows" are unavailable there now. I really can not figure out why but it was.
I added in the setup script, the ability to check if is present the protocol of the host and if it has a trailing slash a the end of the line, since both are needed to generate the cert.
Users now only be able to upload valid image files with mimetype of jpg, jpeg,
png and gif.
Test Plan:
FIRST: DO NOT apply those changes! then
1- go to settings->arcanist certificate and the click on regenerate ... humm
2- On your config file, delete the trailing slash at the end and the protocol on "phabricator.base-uri", then go to setting->arcanist certificate. Here you
will see something like this "phabricator.example.comapi\/" instead of
"http:\/\/phabricator.example.com\/api\/".
SECOND: Now apply this changes:
1- Go to settings->arcanist certificate and the click on regenerate.
2- On your config file, delete the trailing slash at the end and the protocol
on "phabricator.base-uri", and setup "phabricator.setup" to true.
3- Then go to setting->arcanist certificate and you could see that this was successfully generated.
THIRD:
Go to settings->account and try to upload an invalid image file, and do the same on "youruserna"->edit profile.
Reviewed By: epriestley
Reviewers: epriestley jungejason
CC: epriestley jugesason cadamo aran
Differential Revision: 391
2011-06-03 04:27:10 +02:00
|
|
|
$host = PhabricatorEnv::getEnvConfig('phabricator.base-uri');
|
2012-01-11 01:42:00 +01:00
|
|
|
$host_uri = new PhutilURI($host);
|
|
|
|
$protocol = $host_uri->getProtocol();
|
2011-06-11 23:41:05 +02:00
|
|
|
$allowed_protocols = array(
|
|
|
|
'http' => true,
|
|
|
|
'https' => true,
|
|
|
|
);
|
|
|
|
if (empty($allowed_protocols[$protocol])) {
|
Fix regenerate arcanist cert, setup stuff and avoid accept non valid image files as profile picture.
Summary:
Well, since I couldn't regenerate my arcanist cert I figured out that this wass because "workflows" are unavailable there now. I really can not figure out why but it was.
I added in the setup script, the ability to check if is present the protocol of the host and if it has a trailing slash a the end of the line, since both are needed to generate the cert.
Users now only be able to upload valid image files with mimetype of jpg, jpeg,
png and gif.
Test Plan:
FIRST: DO NOT apply those changes! then
1- go to settings->arcanist certificate and the click on regenerate ... humm
2- On your config file, delete the trailing slash at the end and the protocol on "phabricator.base-uri", then go to setting->arcanist certificate. Here you
will see something like this "phabricator.example.comapi\/" instead of
"http:\/\/phabricator.example.com\/api\/".
SECOND: Now apply this changes:
1- Go to settings->arcanist certificate and the click on regenerate.
2- On your config file, delete the trailing slash at the end and the protocol
on "phabricator.base-uri", and setup "phabricator.setup" to true.
3- Then go to setting->arcanist certificate and you could see that this was successfully generated.
THIRD:
Go to settings->account and try to upload an invalid image file, and do the same on "youruserna"->edit profile.
Reviewed By: epriestley
Reviewers: epriestley jungejason
CC: epriestley jugesason cadamo aran
Differential Revision: 391
2011-06-03 04:27:10 +02:00
|
|
|
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)) {
|
2012-01-11 01:42:00 +01:00
|
|
|
self::write(" okay phabricator.base-uri protocol\n");
|
Fix regenerate arcanist cert, setup stuff and avoid accept non valid image files as profile picture.
Summary:
Well, since I couldn't regenerate my arcanist cert I figured out that this wass because "workflows" are unavailable there now. I really can not figure out why but it was.
I added in the setup script, the ability to check if is present the protocol of the host and if it has a trailing slash a the end of the line, since both are needed to generate the cert.
Users now only be able to upload valid image files with mimetype of jpg, jpeg,
png and gif.
Test Plan:
FIRST: DO NOT apply those changes! then
1- go to settings->arcanist certificate and the click on regenerate ... humm
2- On your config file, delete the trailing slash at the end and the protocol on "phabricator.base-uri", then go to setting->arcanist certificate. Here you
will see something like this "phabricator.example.comapi\/" instead of
"http:\/\/phabricator.example.com\/api\/".
SECOND: Now apply this changes:
1- Go to settings->arcanist certificate and the click on regenerate.
2- On your config file, delete the trailing slash at the end and the protocol
on "phabricator.base-uri", and setup "phabricator.setup" to true.
3- Then go to setting->arcanist certificate and you could see that this was successfully generated.
THIRD:
Go to settings->account and try to upload an invalid image file, and do the same on "youruserna"->edit profile.
Reviewed By: epriestley
Reviewers: epriestley jungejason
CC: epriestley jugesason cadamo aran
Differential Revision: 391
2011-06-03 04:27:10 +02:00
|
|
|
} 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;
|
|
|
|
}
|
2012-01-11 01:42:00 +01:00
|
|
|
|
|
|
|
$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, ".
|
2012-01-12 02:47:32 +01:00
|
|
|
"'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/'.");
|
2012-01-11 01:42:00 +01:00
|
|
|
return;
|
|
|
|
}
|
2011-05-05 20:00:05 +02:00
|
|
|
}
|
|
|
|
|
2011-07-24 20:59:16 +02:00
|
|
|
$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".
|
2012-06-21 19:48:34 +02:00
|
|
|
"http://www.php.net/manual/en/timezones.php\n");
|
2011-07-24 20:59:16 +02:00
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
self::write(" okay Timezone '{$timezone}' configured.\n");
|
|
|
|
}
|
|
|
|
|
2011-05-05 20:00:05 +02:00
|
|
|
self::write("[OKAY] Basic configuration OKAY\n");
|
|
|
|
|
Support thumbnailing non-image files and straighten out setup for 'gd'
Summary:
Make 'gd' an explicit optional dependency, test for it in setup, and make the
software behave correctly if it is not available.
When generating file thumnails, provide reasonable defaults and behavior for
non-image files.
Test Plan:
Uploaded text files, pdf files, etc., and got real thumbnails instead of a
broken image.
Simulated setup and gd failures and walked through setup process and image
fallback for thumbnails.
Reviewed By: aran
Reviewers: toulouse, jungejason, tuomaspelkonen, aran
CC: aran, epriestley
Differential Revision: 446
2011-06-13 17:43:42 +02:00
|
|
|
|
|
|
|
$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");
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2011-05-05 20:00:05 +02:00
|
|
|
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");
|
|
|
|
}
|
|
|
|
|
2011-07-20 07:48:38 +02:00
|
|
|
self::writeHeader("MySQL DATABASE & STORAGE CONFIGURATION");
|
2011-05-05 20:00:05 +02:00
|
|
|
|
2012-04-06 23:42:02 +02:00
|
|
|
$conf = PhabricatorEnv::newObjectFromConfig('mysql.configuration-provider');
|
2011-06-12 00:11:50 +02:00
|
|
|
$conn_user = $conf->getUser();
|
|
|
|
$conn_pass = $conf->getPassword();
|
|
|
|
$conn_host = $conf->getHost();
|
2011-05-05 20:00:05 +02:00
|
|
|
|
|
|
|
$timeout = ini_get('mysql.connect_timeout');
|
|
|
|
if ($timeout > 5) {
|
|
|
|
self::writeNote(
|
|
|
|
"Your MySQL connect timeout is very high ({$timeout} seconds). ".
|
2012-03-14 03:03:01 +01:00
|
|
|
"Consider reducing it to 5 or below by setting ".
|
|
|
|
"'mysql.connect_timeout' in your php.ini.");
|
2011-05-05 20:00:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
self::write(" okay Trying to connect to MySQL database ".
|
|
|
|
"{$conn_user}@{$conn_host}...\n");
|
|
|
|
|
|
|
|
ini_set('mysql.connect_timeout', 2);
|
|
|
|
|
2012-04-07 06:29:19 +02:00
|
|
|
$conn_raw = PhabricatorEnv::newObjectFromConfig(
|
|
|
|
'mysql.implementation',
|
2011-05-05 20:00:05 +02:00
|
|
|
array(
|
2012-04-07 06:29:19 +02:00
|
|
|
array(
|
|
|
|
'user' => $conn_user,
|
|
|
|
'pass' => $conn_pass,
|
|
|
|
'host' => $conn_host,
|
|
|
|
'database' => null,
|
|
|
|
),
|
2011-05-05 20:00:05 +02:00
|
|
|
));
|
|
|
|
|
|
|
|
try {
|
|
|
|
queryfx($conn_raw, 'SELECT 1');
|
|
|
|
self::write(" okay Connection successful!\n");
|
|
|
|
} catch (AphrontQueryConnectionException $ex) {
|
2011-11-22 01:29:44 +01:00
|
|
|
$message = $ex->getMessage();
|
2011-05-05 20:00:05 +02:00
|
|
|
self::writeFailure();
|
|
|
|
self::write(
|
2011-11-22 01:29:44 +01:00
|
|
|
"Setup failure! MySQL exception: {$message} \n".
|
|
|
|
"Edit Phabricator configuration keys 'mysql.user', ".
|
|
|
|
"'mysql.host' and 'mysql.pass' to enable Phabricator ".
|
|
|
|
"to connect.");
|
2011-05-05 20:00:05 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-04-09 00:03:38 +02:00
|
|
|
$engines = queryfx_all($conn_raw, 'SHOW ENGINES');
|
2012-04-12 22:44:19 +02:00
|
|
|
$engines = ipull($engines, 'Support', 'Engine');
|
|
|
|
|
|
|
|
$innodb = idx($engines, 'InnoDB');
|
|
|
|
if ($innodb != 'YES' && $innodb != 'DEFAULT') {
|
2012-04-09 00:03:38 +02:00
|
|
|
self::writeFailure();
|
|
|
|
self::write(
|
|
|
|
"Setup failure! The 'InnoDB' engine is not available. Enable ".
|
|
|
|
"InnoDB in your MySQL configuration. If you already created tables, ".
|
|
|
|
"MySQL incorrectly used some other engine. You need to convert ".
|
|
|
|
"them or drop and reinitialize them.");
|
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
self::write(" okay InnoDB is available.\n");
|
|
|
|
}
|
|
|
|
|
2011-05-05 20:00:05 +02:00
|
|
|
$databases = queryfx_all($conn_raw, 'SHOW DATABASES');
|
2012-04-09 00:03:38 +02:00
|
|
|
$databases = ipull($databases, 'Database', 'Database');
|
2011-05-05 20:00:05 +02:00
|
|
|
if (empty($databases['phabricator_meta_data'])) {
|
|
|
|
self::writeFailure();
|
|
|
|
self::write(
|
Make SQL patch management DAG-based and provide namespace support
Summary:
This addresses three issues with the current patch management system:
# Two people developing at the same time often pick the same SQL patch number, and then have to go rename it. The system catches this, but it's silly.
# Second/third-party developers can't use the same system to manage auxiliary storage they may want to add.
# There's no way to build mock databases for unit tests that need to do reads.
To resolve these things, you can now name your patches whatever you want and conflicts are just merge conflicts, which are less of a pain to fix than filename conflicts.
Dependencies are now a DAG, with implicit dependencies created on the prior patch if no dependencies are specified. Developers can add new concrete subclasses of `PhabricatorSQLPatchList` to add storage management, and define the dependency branchpoint of their patches so they apply in the correct order (although, generally, they should not depend on the mainline patches, presumably).
The commands `storage upgrade --namespace test1234` and `storage destroy --namespace test1234` will allow unit tests to build and destroy MySQL storage.
A "quickstart" mode allows an upgrade from scratch in ~1200ms. Destruction takes about 200ms. These seem like fairily reasonable costs to actually use in tests. Building from scratch patch-by-patch takes about 6000ms.
Test Plan:
- Created new databases from scratch with and without quickstart in a separate test namespace. Pointed the webapp at the test namespaces, browsed around, everything looked good.
- Compared quickstart and no-quickstart dump states, they're identical except for mysqldump timestamps and a few similar things.
- Upgraded a legacy database to the new storage format.
- Destroyed / dumped storage.
Reviewers: edward, vrana, btrahan, jungejason
Reviewed By: btrahan
CC: aran, nh
Maniphest Tasks: T140, T345
Differential Revision: https://secure.phabricator.com/D2323
2012-04-30 16:54:00 +02:00
|
|
|
"Setup failure! You haven't run 'bin/storage upgrade'. See this ".
|
|
|
|
"article for instructions:\n");
|
2011-05-05 20:00:05 +02:00
|
|
|
self::writeDoc('article/Configuration_Guide.html');
|
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
self::write(" okay Databases have been initialized.\n");
|
|
|
|
}
|
|
|
|
|
2011-06-15 17:39:02 +02:00
|
|
|
$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.");
|
|
|
|
}
|
|
|
|
|
2011-07-20 07:48:38 +02:00
|
|
|
$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) {
|
2011-07-24 20:59:16 +02:00
|
|
|
if (!Filesystem::pathExists($local_path) ||
|
|
|
|
!is_readable($local_path) ||
|
|
|
|
!is_writable($local_path)) {
|
2011-07-20 07:48:38 +02:00
|
|
|
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 ".
|
2011-07-24 20:59:16 +02:00
|
|
|
"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");
|
|
|
|
}
|
2011-07-20 07:48:38 +02:00
|
|
|
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");
|
2011-05-05 20:00:05 +02:00
|
|
|
|
|
|
|
|
2011-05-12 17:15:02 +02:00
|
|
|
self::writeHeader("OUTBOUND EMAIL CONFIGURATION");
|
|
|
|
|
|
|
|
$have_adapter = false;
|
|
|
|
$is_ses = false;
|
|
|
|
|
|
|
|
$adapter = PhabricatorEnv::getEnvConfig('metamta.mail-adapter');
|
|
|
|
switch ($adapter) {
|
|
|
|
case 'PhabricatorMailImplementationPHPMailerLiteAdapter':
|
|
|
|
|
|
|
|
$have_adapter = true;
|
|
|
|
|
2011-07-09 02:58:18 +02:00
|
|
|
if (!Filesystem::pathExists('/usr/bin/sendmail') &&
|
|
|
|
!Filesystem::pathExists('/usr/sbin/sendmail')) {
|
2011-05-12 17:15:02 +02:00
|
|
|
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");
|
|
|
|
}
|
|
|
|
|
2012-03-22 23:44:34 +01:00
|
|
|
self::writeHeader('CONFIG CLASSES');
|
2012-03-30 13:14:39 +02:00
|
|
|
foreach (PhabricatorEnv::getRequiredClasses() as $key => $instanceof) {
|
|
|
|
$config = PhabricatorEnv::getEnvConfig($key);
|
|
|
|
if (!$config) {
|
2012-03-22 23:44:34 +01:00
|
|
|
self::writeNote("'$key' is not set.");
|
|
|
|
} else {
|
|
|
|
try {
|
2012-03-30 13:14:39 +02:00
|
|
|
$r = new ReflectionClass($config);
|
|
|
|
if (!$r->isSubclassOf($instanceof)) {
|
|
|
|
throw new Exception(
|
|
|
|
"Config setting '$key' must be an instance of '$instanceof'.");
|
2012-04-05 00:06:45 +02:00
|
|
|
} else if (!$r->isInstantiable()) {
|
2012-03-30 13:14:39 +02:00
|
|
|
throw new Exception("Config setting '$key' must be instantiable.");
|
|
|
|
}
|
2012-03-22 23:44:34 +01:00
|
|
|
} catch (Exception $ex) {
|
|
|
|
self::writeFailure();
|
|
|
|
self::write("Setup failure! ".$ex->getMessage());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
self::write("[OKAY] Config classes OKAY\n");
|
|
|
|
|
2011-05-05 20:00:05 +02:00
|
|
|
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. -_-
|
2011-07-20 07:48:38 +02:00
|
|
|
usleep(20000);
|
2011-05-05 20:00:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private static function writeNote($note) {
|
2011-06-15 17:39:02 +02:00
|
|
|
$note = "*** NOTE: ".wordwrap($note, 75, "\n", true);
|
|
|
|
$note = "\n".str_replace("\n", "\n ", $note)."\n\n";
|
|
|
|
self::write($note);
|
2011-05-05 20:00:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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".
|
2012-04-09 23:23:10 +02:00
|
|
|
' http://www.phabricator.com/docs/phabricator/'.$doc.
|
2011-05-05 20:00:05 +02:00
|
|
|
"\n\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|