diff --git a/src/applications/config/check/PhabricatorMailSetupCheck.php b/src/applications/config/check/PhabricatorMailSetupCheck.php index c89e0036a2..0b1374809c 100644 --- a/src/applications/config/check/PhabricatorMailSetupCheck.php +++ b/src/applications/config/check/PhabricatorMailSetupCheck.php @@ -1,24 +1,24 @@ newIssue('cluster.mailers') ->setName(pht('Mailers Not Configured')) ->setMessage($message) ->addPhabricatorConfig('cluster.mailers'); } } diff --git a/src/applications/config/check/PhabricatorMySQLSetupCheck.php b/src/applications/config/check/PhabricatorMySQLSetupCheck.php index de9a9e8b54..c5b71b1cda 100644 --- a/src/applications/config/check/PhabricatorMySQLSetupCheck.php +++ b/src/applications/config/check/PhabricatorMySQLSetupCheck.php @@ -1,399 +1,399 @@ executeRefChecks($ref); } catch (AphrontConnectionQueryException $ex) { // If we're unable to connect to a host, just skip the checks for it. // This can happen if we're restarting during a cluster incident. See // T12966 for discussion. } } } private function executeRefChecks(PhabricatorDatabaseRef $ref) { $max_allowed_packet = $ref->loadRawMySQLConfigValue('max_allowed_packet'); $host_name = $ref->getRefKey(); // This primarily supports setting the filesize limit for MySQL to 8MB, // which may produce a >16MB packet after escaping. $recommended_minimum = (32 * 1024 * 1024); if ($max_allowed_packet < $recommended_minimum) { $message = pht( 'On host "%s", MySQL is configured with a small "%s" (%d), which '. 'may cause some large writes to fail. The recommended minimum value '. 'for this setting is "%d".', $host_name, 'max_allowed_packet', $max_allowed_packet, $recommended_minimum); $this->newIssue('mysql.max_allowed_packet') ->setName(pht('Small MySQL "%s"', 'max_allowed_packet')) ->setMessage($message) ->setDatabaseRef($ref) ->addMySQLConfig('max_allowed_packet'); } $modes = $ref->loadRawMySQLConfigValue('sql_mode'); $modes = explode(',', $modes); if (!in_array('STRICT_ALL_TABLES', $modes)) { $summary = pht( 'MySQL is not in strict mode (on host "%s"), but using strict mode '. 'is recommended.', $host_name); $message = pht( 'On database host "%s", the global "sql_mode" setting does not '. 'include the "STRICT_ALL_TABLES" mode. Enabling this mode is '. 'recommended to generally improve how MySQL handles certain errors.'. "\n\n". 'Without this mode enabled, MySQL will silently ignore some error '. 'conditions, including inserts which attempt to store more data in '. 'a column than actually fits. This behavior is usually undesirable '. 'and can lead to data corruption (by truncating multibyte characters '. 'in the middle), data loss (by discarding the data which does not '. 'fit into the column), or security concerns (for example, by '. 'truncating keys or credentials).'. "\n\n". - 'Phabricator is developed and tested in "STRICT_ALL_TABLES" mode so '. + 'This software is developed and tested in "STRICT_ALL_TABLES" mode so '. 'you should normally never encounter these situations, but may run '. 'into them if you interact with the database directly, run '. 'third-party code, develop extensions, or just encounter a bug in '. 'the software.'. "\n\n". 'Enabling "STRICT_ALL_TABLES" makes MySQL raise an explicit error '. 'if one of these unusual situations does occur. This is a safer '. 'behavior and prevents these situations from causing secret, subtle, '. 'and potentially serious issues later on.'. "\n\n". 'You can find more information about this mode (and how to configure '. 'it) in the MySQL manual. Usually, it is sufficient to add this to '. 'your "my.cnf" file (in the "[mysqld]" section) and then '. 'restart "mysqld":'. "\n\n". '%s'. "\n". 'Note that if you run other applications against the same database, '. 'they may not work in strict mode.'. "\n\n". 'If you can not or do not want to enable "STRICT_ALL_TABLES", you '. - 'can safely ignore this warning. Phabricator will work correctly '. + 'can safely ignore this warning. This software will work correctly '. 'with this mode enabled or disabled.', $host_name, phutil_tag('pre', array(), 'sql_mode=STRICT_ALL_TABLES')); $this->newIssue('sql_mode.strict') ->setName(pht('MySQL %s Mode Not Set', 'STRICT_ALL_TABLES')) ->setSummary($summary) ->setMessage($message) ->setDatabaseRef($ref) ->addMySQLConfig('sql_mode'); } $is_innodb_fulltext = false; $is_myisam_fulltext = false; if ($this->shouldUseMySQLSearchEngine()) { if (PhabricatorSearchDocument::isInnoDBFulltextEngineAvailable()) { $is_innodb_fulltext = true; } else { $is_myisam_fulltext = true; } } if ($is_myisam_fulltext) { $stopword_file = $ref->loadRawMySQLConfigValue('ft_stopword_file'); if ($stopword_file === null) { $summary = pht( 'Your version of MySQL (on database host "%s") does not support '. 'configuration of a stopword file. You will not be able to find '. 'search results for common words.', $host_name); $message = pht( "Database host \"%s\" does not support the %s option. You will not ". "be able to find search results for common words. You can gain ". "access to this option by upgrading MySQL to a more recent ". "version.\n\n". "You can ignore this warning if you plan to configure Elasticsearch ". "later, or aren't concerned about searching for common words.", $host_name, phutil_tag('tt', array(), 'ft_stopword_file')); $this->newIssue('mysql.ft_stopword_file') ->setName(pht('MySQL %s Not Supported', 'ft_stopword_file')) ->setSummary($summary) ->setMessage($message) ->setDatabaseRef($ref) ->addMySQLConfig('ft_stopword_file'); } else if ($stopword_file == '(built-in)') { $root = dirname(phutil_get_library_root('phabricator')); $stopword_path = $root.'/resources/sql/stopwords.txt'; $stopword_path = Filesystem::resolvePath($stopword_path); $namespace = PhabricatorEnv::getEnvConfig('storage.default-namespace'); $summary = pht( 'MySQL (on host "%s") is using a default stopword file, which '. 'will prevent searching for many common words.', $host_name); $message = pht( "Database host \"%s\" is using the builtin stopword file for ". - "building search indexes. This can make Phabricator's search ". + "building search indexes. This can make the search ". "feature less useful.\n\n". "Stopwords are common words which are not indexed and thus can not ". "be searched for. The default stopword file has about 500 words, ". "including various words which you are likely to wish to search ". "for, such as 'various', 'likely', 'wish', and 'zero'.\n\n". "To make search more useful, you can use an alternate stopword ". "file with fewer words. Alternatively, if you aren't concerned ". "about searching for common words, you can ignore this warning. ". "If you later plan to configure Elasticsearch, you can also ignore ". "this warning: this stopword file only affects MySQL fulltext ". "indexes.\n\n". "To choose a different stopword file, add this to your %s file ". "(in the %s section) and then restart %s:\n\n". "%s\n". "(You can also use a different file if you prefer. The file ". "suggested above has about 50 of the most common English words.)\n\n". "Finally, run this command to rebuild indexes using the new ". "rules:\n\n". "%s", $host_name, phutil_tag('tt', array(), 'my.cnf'), phutil_tag('tt', array(), '[mysqld]'), phutil_tag('tt', array(), 'mysqld'), phutil_tag('pre', array(), 'ft_stopword_file='.$stopword_path), phutil_tag( 'pre', array(), "mysql> REPAIR TABLE {$namespace}_search.search_documentfield;")); $this->newIssue('mysql.ft_stopword_file') ->setName(pht('MySQL is Using Default Stopword File')) ->setSummary($summary) ->setMessage($message) ->setDatabaseRef($ref) ->addMySQLConfig('ft_stopword_file'); } } if ($is_myisam_fulltext) { $min_len = $ref->loadRawMySQLConfigValue('ft_min_word_len'); if ($min_len >= 4) { $namespace = PhabricatorEnv::getEnvConfig('storage.default-namespace'); $summary = pht( 'MySQL is configured (on host "%s") to only index words with at '. 'least %d characters.', $host_name, $min_len); $message = pht( "Database host \"%s\" is configured to use the default minimum word ". "length when building search indexes, which is 4. This means words ". "which are only 3 characters long will not be indexed and can not ". "be searched for.\n\n". "For example, you will not be able to find search results for words ". "like 'SMS', 'web', or 'DOS'.\n\n". "You can change this setting to 3 to allow these words to be ". "indexed. Alternatively, you can ignore this warning if you are ". "not concerned about searching for 3-letter words. If you later ". "plan to configure Elasticsearch, you can also ignore this warning: ". "only MySQL fulltext search is affected.\n\n". "To reduce the minimum word length to 3, add this to your %s file ". "(in the %s section) and then restart %s:\n\n". "%s\n". "Finally, run this command to rebuild indexes using the new ". "rules:\n\n". "%s", $host_name, phutil_tag('tt', array(), 'my.cnf'), phutil_tag('tt', array(), '[mysqld]'), phutil_tag('tt', array(), 'mysqld'), phutil_tag('pre', array(), 'ft_min_word_len=3'), phutil_tag( 'pre', array(), "mysql> REPAIR TABLE {$namespace}_search.search_documentfield;")); $this->newIssue('mysql.ft_min_word_len') ->setName(pht('MySQL is Using Default Minimum Word Length')) ->setSummary($summary) ->setMessage($message) ->setDatabaseRef($ref) ->addMySQLConfig('ft_min_word_len'); } } // NOTE: The default value of "innodb_ft_min_token_size" is 3, which is // a reasonable value, so we do not warn about it: if it is set to // something else, the user adjusted it on their own. // NOTE: We populate a stopwords table at "phabricator_search.stopwords", // but the default InnoDB stopword list is pretty reasonable (36 words, // versus 500+ in MyISAM). Just use the builtin list until we run into // concrete issues with it. Users can switch to our stopword table with: // // [mysqld] // innodb_ft_server_stopword_table = phabricator_search/stopwords $innodb_pool = $ref->loadRawMySQLConfigValue('innodb_buffer_pool_size'); $innodb_bytes = phutil_parse_bytes($innodb_pool); $innodb_readable = phutil_format_bytes($innodb_bytes); // This is arbitrary and just trying to detect values that the user // probably didn't set themselves. The Mac OS X default is 128MB and // 40% of an AWS EC2 Micro instance is 245MB, so keeping it somewhere // between those two values seems like a reasonable approximation. $minimum_readable = '225MB'; $minimum_bytes = phutil_parse_bytes($minimum_readable); if ($innodb_bytes < $minimum_bytes) { $summary = pht( 'MySQL (on host "%s") is configured with a very small '. 'innodb_buffer_pool_size, which may impact performance.', $host_name); $message = pht( "Database host \"%s\" is configured with a very small %s (%s). ". "This may cause poor database performance and lock exhaustion.\n\n". "There are no hard-and-fast rules to setting an appropriate value, ". "but a reasonable starting point for a standard install is something ". "like 40%% of the total memory on the machine. For example, if you ". - "have 4GB of RAM on the machine you have installed Phabricator on, ". + "have 4GB of RAM on the machine you have installed this software on, ". "you might set this value to %s.\n\n". "You can read more about this option in the MySQL documentation to ". "help you make a decision about how to configure it for your use ". - "case. There are no concerns specific to Phabricator which make it ". + "case. There are no concerns specific to this software which make it ". "different from normal workloads with respect to this setting.\n\n". "To adjust the setting, add something like this to your %s file (in ". "the %s section), replacing %s with an appropriate value for your ". "host and use case. Then restart %s:\n\n". "%s\n". "If you're satisfied with the current setting, you can safely ". "ignore this setup warning.", $host_name, phutil_tag('tt', array(), 'innodb_buffer_pool_size'), phutil_tag('tt', array(), $innodb_readable), phutil_tag('tt', array(), '1600M'), phutil_tag('tt', array(), 'my.cnf'), phutil_tag('tt', array(), '[mysqld]'), phutil_tag('tt', array(), '1600M'), phutil_tag('tt', array(), 'mysqld'), phutil_tag('pre', array(), 'innodb_buffer_pool_size=1600M')); $this->newIssue('mysql.innodb_buffer_pool_size') ->setName(pht('MySQL May Run Slowly')) ->setSummary($summary) ->setMessage($message) ->setDatabaseRef($ref) ->addMySQLConfig('innodb_buffer_pool_size'); } $conn = $ref->newManagementConnection(); $ok = PhabricatorStorageManagementAPI::isCharacterSetAvailableOnConnection( 'utf8mb4', $conn); if (!$ok) { $summary = pht( 'You are using an old version of MySQL (on host "%s"), and should '. 'upgrade.', $host_name); $message = pht( 'You are using an old version of MySQL (on host "%s") which has poor '. 'unicode support (it does not support the "utf8mb4" collation set). '. 'You will encounter limitations when working with some unicode data.'. "\n\n". 'We strongly recommend you upgrade to MySQL 5.5 or newer.', $host_name); $this->newIssue('mysql.utf8mb4') ->setName(pht('Old MySQL Version')) ->setSummary($summary) ->setDatabaseRef($ref) ->setMessage($message); } $info = queryfx_one( $conn, 'SELECT UNIX_TIMESTAMP() epoch'); $epoch = (int)$info['epoch']; $local = PhabricatorTime::getNow(); $delta = (int)abs($local - $epoch); if ($delta > 60) { $this->newIssue('mysql.clock') ->setName(pht('Major Web/Database Clock Skew')) ->setSummary( pht( 'This web host ("%s") is set to a very different time than a '. 'database host "%s".', php_uname('n'), $host_name)) ->setMessage( pht( 'A database host ("%s") and this web host ("%s") disagree on the '. 'current time by more than 60 seconds (absolute skew is %s '. 'seconds). Check that the current time is set correctly '. 'everywhere.', $host_name, php_uname('n'), new PhutilNumber($delta))); } $local_infile = $ref->loadRawMySQLConfigValue('local_infile'); if ($local_infile) { $summary = pht( 'The MySQL "local_infile" option is enabled. This option is '. 'unsafe.'); $message = pht( 'Your MySQL server is configured with the "local_infile" option '. 'enabled. This option allows an attacker who finds an SQL injection '. 'hole to escalate their attack by copying files from the webserver '. 'into the database with "LOAD DATA LOCAL INFILE" queries, then '. 'reading the file content with "SELECT" queries.'. "\n\n". 'You should disable this option in your %s file, in the %s section:'. "\n\n". '%s', phutil_tag('tt', array(), 'my.cnf'), phutil_tag('tt', array(), '[mysqld]'), phutil_tag('pre', array(), 'local_infile=0')); $this->newIssue('mysql.local_infile') ->setName(pht('Unsafe MySQL "local_infile" Setting Enabled')) ->setSummary($summary) ->setMessage($message) ->setDatabaseRef($ref) ->addMySQLConfig('local_infile'); } } protected function shouldUseMySQLSearchEngine() { $services = PhabricatorSearchService::getAllServices(); foreach ($services as $service) { if ($service instanceof PhabricatorMySQLSearchHost) { return true; } } return false; } } diff --git a/src/applications/config/check/PhabricatorPHPConfigSetupCheck.php b/src/applications/config/check/PhabricatorPHPConfigSetupCheck.php index bee2bc91b8..09b96d05cf 100644 --- a/src/applications/config/check/PhabricatorPHPConfigSetupCheck.php +++ b/src/applications/config/check/PhabricatorPHPConfigSetupCheck.php @@ -1,153 +1,153 @@ $doc_href, 'target' => '_blank', ), pht('Configuring a Preamble Script'))); $this->newIssue('php.remote_addr') ->setName(pht('No REMOTE_ADDR available')) ->setSummary($summary) ->setMessage($message); } if (version_compare(phpversion(), '7', '>=')) { // This option was removed in PHP7. $raw_post_data = -1; } else { $raw_post_data = (int)ini_get('always_populate_raw_post_data'); } if ($raw_post_data != -1) { $summary = pht( 'PHP setting "%s" should be set to "-1" to avoid deprecation '. 'warnings.', 'always_populate_raw_post_data'); $message = pht( 'The "%s" key is set to some value other than "-1" in your PHP '. 'configuration. This can cause PHP to raise deprecation warnings '. 'during process startup. Set this option to "-1" to prevent these '. 'warnings from appearing.', 'always_populate_raw_post_data'); $this->newIssue('php.always_populate_raw_post_data') ->setName(pht('Disable PHP %s', 'always_populate_raw_post_data')) ->setSummary($summary) ->setMessage($message) ->addPHPConfig('always_populate_raw_post_data'); } if (!extension_loaded('mysqli')) { $summary = pht( 'Install the MySQLi extension to improve database behavior.'); $message = pht( 'PHP is currently using the very old "mysql" extension to interact '. 'with the database. You should install the newer "mysqli" extension '. 'to improve behaviors (like error handling and query timeouts).'. "\n\n". - 'Phabricator will work with the older extension, but upgrading to the '. - 'newer extension is recommended.'. + 'This software will work with the older extension, but upgrading to '. + 'the newer extension is recommended.'. "\n\n". 'You may be able to install the extension with a command like: %s', // NOTE: We're intentionally telling you to install "mysqlnd" here; on // Ubuntu, there's no separate "mysqli" package. phutil_tag('tt', array(), 'sudo apt-get install php5-mysqlnd')); $this->newIssue('php.mysqli') ->setName(pht('MySQLi Extension Not Available')) ->setSummary($summary) ->setMessage($message); } else if (!defined('MYSQLI_ASYNC')) { $summary = pht( 'Configure the MySQL Native Driver to improve database behavior.'); $message = pht( 'PHP is currently using the older MySQL external driver instead of '. 'the newer MySQL native driver. The older driver lacks options and '. - 'features (like support for query timeouts) which allow Phabricator '. + 'features (like support for query timeouts) which allow this server '. 'to interact better with the database.'. "\n\n". - 'Phabricator will work with the older driver, but upgrading to the '. + 'This software will work with the older driver, but upgrading to the '. 'native driver is recommended.'. "\n\n". 'You may be able to install the native driver with a command like: %s', phutil_tag('tt', array(), 'sudo apt-get install php5-mysqlnd')); $this->newIssue('php.myqlnd') ->setName(pht('MySQL Native Driver Not Available')) ->setSummary($summary) ->setMessage($message); } if (extension_loaded('mysqli')) { $infile_key = 'mysqli.allow_local_infile'; } else { $infile_key = 'mysql.allow_local_infile'; } if (ini_get($infile_key)) { $summary = pht( 'Disable unsafe option "%s" in PHP configuration.', $infile_key); $message = pht( 'PHP is currently configured to honor requests from any MySQL server '. 'it connects to for the content of any local file.'. "\n\n". 'This capability supports MySQL "LOAD DATA LOCAL INFILE" queries, but '. 'allows a malicious MySQL server read access to the local disk: the '. 'server can ask the client to send the content of any local file, '. 'and the client will comply.'. "\n\n". 'Although it is normally difficult for an attacker to convince '. - 'Phabricator to connect to a malicious MySQL server, you should '. + 'this software to connect to a malicious MySQL server, you should '. 'disable this option: this capability is unnecessary and inherently '. 'dangerous.'. "\n\n". 'To disable this option, set: %s', phutil_tag('tt', array(), pht('%s = 0', $infile_key))); $this->newIssue('php.'.$infile_key) ->setName(pht('Unsafe PHP "Local Infile" Configuration')) ->setSummary($summary) ->setMessage($message) ->addPHPConfig($infile_key); } } } diff --git a/src/applications/config/check/PhabricatorPathSetupCheck.php b/src/applications/config/check/PhabricatorPathSetupCheck.php index 75c89d332a..8a98c5ba9f 100644 --- a/src/applications/config/check/PhabricatorPathSetupCheck.php +++ b/src/applications/config/check/PhabricatorPathSetupCheck.php @@ -1,136 +1,136 @@ newIssue('config.environment.append-paths') ->setName(pht('%s Not Set', '$PATH')) ->setSummary($summary) ->setMessage($message) ->addPhabricatorConfig('environment.append-paths'); // Bail on checks below. return; } // Users are remarkably industrious at misconfiguring software. Try to // catch mistaken configuration of PATH. $path_parts = explode(PATH_SEPARATOR, $path); $bad_paths = array(); foreach ($path_parts as $path_part) { if (!strlen($path_part)) { continue; } $message = null; $not_exists = false; foreach (Filesystem::walkToRoot($path_part) as $part) { if (!Filesystem::pathExists($part)) { $not_exists = $part; // Walk up so we can tell if this is a readability issue or not. continue; } else if (!is_dir(Filesystem::resolvePath($part))) { $message = pht( "The PATH component '%s' (which resolves as the absolute path ". "'%s') is not usable because '%s' is not a directory.", $path_part, Filesystem::resolvePath($path_part), $part); } else if (!is_readable($part)) { $message = pht( "The PATH component '%s' (which resolves as the absolute path ". "'%s') is not usable because '%s' is not readable.", $path_part, Filesystem::resolvePath($path_part), $part); } else if ($not_exists) { $message = pht( "The PATH component '%s' (which resolves as the absolute path ". "'%s') is not usable because '%s' does not exist.", $path_part, Filesystem::resolvePath($path_part), $not_exists); } else { // Everything seems good. break; } if ($message !== null) { break; } } if ($message === null) { if (!phutil_is_windows() && !@file_exists($path_part.'/.')) { $message = pht( "The PATH component '%s' (which resolves as the absolute path ". "'%s') is not usable because it is not traversable (its '%s' ". "permission bit is not set).", $path_part, Filesystem::resolvePath($path_part), '+x'); } } if ($message !== null) { $bad_paths[$path_part] = $message; } } if ($bad_paths) { foreach ($bad_paths as $path_part => $message) { $digest = substr(PhabricatorHash::weakDigest($path_part), 0, 8); $this ->newIssue('config.PATH.'.$digest) ->setName(pht('%s Component Unusable', '$PATH')) ->setSummary( pht( 'A component of the configured PATH can not be used by '. 'the webserver: %s', $path_part)) ->setMessage( pht( "The configured PATH includes a component which is not usable. ". - "Phabricator will be unable to find or execute binaries located ". + "This server will be unable to find or execute binaries located ". "here:". "\n\n". "%s". "\n\n". "The user that the webserver runs as must be able to read all ". "the directories in PATH in order to make use of them.", $message)) ->addPhabricatorConfig('environment.append-paths'); } } } } diff --git a/src/applications/config/check/PhabricatorPygmentSetupCheck.php b/src/applications/config/check/PhabricatorPygmentSetupCheck.php index 0ffb4ff7ad..74dfe5a324 100644 --- a/src/applications/config/check/PhabricatorPygmentSetupCheck.php +++ b/src/applications/config/check/PhabricatorPygmentSetupCheck.php @@ -1,83 +1,83 @@ newIssue('pygments.enabled') ->setName(pht('%s Not Found', 'pygmentize')) ->setSummary($summary) ->setMessage($message) ->addRelatedPhabricatorConfig('pygments.enabled') ->addPhabricatorConfig('environment.append-paths'); } else { list($err) = exec_manual('pygmentize -h'); if ($err) { $summary = pht( 'You have enabled pygments and the %s script is '. 'available, but does not seem to work.', 'pygmentize'); $message = pht( - 'Phabricator has %s available in %s, but the binary '. + 'This server has %s available in %s, but the binary '. 'exited with an error code when run as %s. Check that it is '. 'installed correctly.', phutil_tag('tt', array(), 'pygmentize'), phutil_tag('tt', array(), '$PATH'), phutil_tag('tt', array(), 'pygmentize -h')); $this ->newIssue('pygments.failed') ->setName(pht('%s Not Working', 'pygmentize')) ->setSummary($summary) ->setMessage($message) ->addRelatedPhabricatorConfig('pygments.enabled') ->addPhabricatorConfig('environment.append-paths'); } } } else { $summary = pht( 'Pygments should be installed and enabled '. 'to provide advanced syntax highlighting.'); $message = pht( - 'Phabricator can highlight a few languages by default, '. + 'This software can highlight a few languages by default, '. 'but installing and enabling Pygments (a third-party highlighting '. "tool) will add syntax highlighting for many more languages. \n\n". 'For instructions on installing and enabling Pygments, see the '. '%s configuration option.'."\n\n". 'If you do not want to install Pygments, you can ignore this issue.', phutil_tag('tt', array(), 'pygments.enabled')); $this ->newIssue('pygments.noenabled') ->setName(pht('Install Pygments to Improve Syntax Highlighting')) ->setSummary($summary) ->setMessage($message) ->addRelatedPhabricatorConfig('pygments.enabled'); } } } diff --git a/src/applications/config/check/PhabricatorRepositoriesSetupCheck.php b/src/applications/config/check/PhabricatorRepositoriesSetupCheck.php index 64073ec1ed..7d010fd954 100644 --- a/src/applications/config/check/PhabricatorRepositoriesSetupCheck.php +++ b/src/applications/config/check/PhabricatorRepositoriesSetupCheck.php @@ -1,66 +1,66 @@ setViewer(PhabricatorUser::getOmnipotentUser()) ->withServiceTypes( array( AlmanacClusterRepositoryServiceType::SERVICETYPE, )) ->setLimit(1) ->execute(); if ($cluster_services) { // If cluster repository services are defined, these checks aren't useful // because some nodes (like web nodes) will usually not have any local // repository information. // Errors with this configuration will still be detected by checks on // individual repositories. return; } $repo_path = PhabricatorEnv::getEnvConfig('repository.default-local-path'); if (!$repo_path) { $summary = pht( "The configuration option '%s' is not set.", 'repository.default-local-path'); $this->newIssue('repository.default-local-path.empty') ->setName(pht('Missing Repository Local Path')) ->setSummary($summary) ->addPhabricatorConfig('repository.default-local-path'); return; } if (!Filesystem::pathExists($repo_path)) { $summary = pht( 'The path for local repositories does not exist, or is not '. 'readable by the webserver.'); $message = pht( "The directory for local repositories (%s) does not exist, or is not ". - "readable by the webserver. Phabricator uses this directory to store ". - "information about repositories. If this directory does not exist, ". - "create it:\n\n". + "readable by the webserver. This software uses this directory to ". + "store information about repositories. If this directory does not ". + "exist, create it:\n\n". "%s\n". "If this directory exists, make it readable to the webserver. You ". "can also edit the configuration below to use some other directory.", phutil_tag('tt', array(), $repo_path), phutil_tag('pre', array(), csprintf('$ mkdir -p %s', $repo_path))); $this->newIssue('repository.default-local-path.empty') ->setName(pht('Missing Repository Local Path')) ->setSummary($summary) ->setMessage($message) ->addPhabricatorConfig('repository.default-local-path'); } } } diff --git a/src/applications/config/check/PhabricatorSecuritySetupCheck.php b/src/applications/config/check/PhabricatorSecuritySetupCheck.php index 0cd1b41504..e84e26a208 100644 --- a/src/applications/config/check/PhabricatorSecuritySetupCheck.php +++ b/src/applications/config/check/PhabricatorSecuritySetupCheck.php @@ -1,77 +1,77 @@ '() { :;} ; echo VULNERABLE', ); list($err, $stdout) = id(new ExecFuture('echo shellshock-test')) ->setEnv($payload, $wipe_process_env = true) ->resolve(); if (!$err && preg_match('/VULNERABLE/', $stdout)) { $summary = pht( 'This system has an unpatched version of Bash with a severe, widely '. 'disclosed vulnerability.'); $message = pht( 'The version of %s on this system is out of date and contains a '. 'major, widely disclosed vulnerability (the "Shellshock" '. 'vulnerability).'. "\n\n". 'Upgrade %s to a patched version.'. "\n\n". - 'To learn more about how this issue affects Phabricator, see %s.', + 'To learn more about how this issue affects this software, see %s.', phutil_tag('tt', array(), 'bash'), phutil_tag('tt', array(), 'bash'), phutil_tag( 'a', array( 'href' => 'https://secure.phabricator.com/T6185', 'target' => '_blank', ), pht('T6185 "Shellshock" Bash Vulnerability'))); $this ->newIssue('security.shellshock') ->setName(pht('Severe Security Vulnerability: Unpatched Bash')) ->setSummary($summary) ->setMessage($message); } $file_key = 'security.alternate-file-domain'; $file_domain = PhabricatorEnv::getEnvConfig($file_key); if (!$file_domain) { $doc_href = PhabricatorEnv::getDoclink('Configuring a File Domain'); $this->newIssue('security.'.$file_key) ->setName(pht('Alternate File Domain Not Configured')) ->setSummary( pht( 'Improve security by configuring an alternate file domain.')) ->setMessage( pht( - 'Phabricator is currently configured to serve user uploads '. + 'This software is currently configured to serve user uploads '. 'directly from the same domain as other content. This is a '. 'security risk.'. "\n\n". 'Configure a CDN (or alternate file domain) to eliminate this '. 'risk. Using a CDN will also improve performance. See the '. 'guide below for instructions.')) ->addPhabricatorConfig($file_key) ->addLink( $doc_href, pht('Configuration Guide: Configuring a File Domain')); } } } diff --git a/src/applications/config/check/PhabricatorStorageSetupCheck.php b/src/applications/config/check/PhabricatorStorageSetupCheck.php index cc74cce2ea..86aa1f2ebd 100644 --- a/src/applications/config/check/PhabricatorStorageSetupCheck.php +++ b/src/applications/config/check/PhabricatorStorageSetupCheck.php @@ -1,196 +1,196 @@ checkS3(); if (!$chunk_engine_active) { $doc_href = PhabricatorEnv::getDoclink('Configuring File Storage'); $message = pht( 'Large file storage has not been configured, which will limit '. 'the maximum size of file uploads. See %s for '. 'instructions on configuring uploads and storage.', phutil_tag( 'a', array( 'href' => $doc_href, 'target' => '_blank', ), pht('Configuring File Storage'))); $this ->newIssue('large-files') ->setShortName(pht('Large Files')) ->setName(pht('Large File Storage Not Configured')) ->setMessage($message); } $post_max_size = ini_get('post_max_size'); if ($post_max_size && ((int)$post_max_size > 0)) { $post_max_bytes = phutil_parse_bytes($post_max_size); $post_max_need = (32 * 1024 * 1024); if ($post_max_need > $post_max_bytes) { $summary = pht( 'Set %s in your PHP configuration to at least 32MB '. 'to support large file uploads.', phutil_tag('tt', array(), 'post_max_size')); $message = pht( 'Adjust %s in your PHP configuration to at least 32MB. When '. 'set to smaller value, large file uploads may not work properly.', phutil_tag('tt', array(), 'post_max_size')); $this ->newIssue('php.post_max_size') ->setName(pht('PHP post_max_size Not Configured')) ->setSummary($summary) ->setMessage($message) ->setGroup(self::GROUP_PHP) ->addPHPConfig('post_max_size'); } } // This is somewhat arbitrary, but make sure we have enough headroom to // upload a default file at the chunk threshold (8MB), which may be // base64 encoded, then JSON encoded in the request, and may need to be // held in memory in the raw and as a query string. $need_bytes = (64 * 1024 * 1024); $memory_limit = PhabricatorStartup::getOldMemoryLimit(); if ($memory_limit && ((int)$memory_limit > 0)) { $memory_limit_bytes = phutil_parse_bytes($memory_limit); $memory_usage_bytes = memory_get_usage(); $available_bytes = ($memory_limit_bytes - $memory_usage_bytes); if ($need_bytes > $available_bytes) { $summary = pht( 'Your PHP memory limit is configured in a way that may prevent '. 'you from uploading large files or handling large requests.'); $message = pht( 'When you upload a file via drag-and-drop or the API, chunks must '. 'be buffered into memory before being written to permanent '. - 'storage. Phabricator needs memory available to store these '. + 'storage. This server needs memory available to store these '. 'chunks while they are uploaded, but PHP is currently configured '. 'to severely limit the available memory.'. "\n\n". 'PHP processes currently have very little free memory available '. '(%s). To work well, processes should have at least %s.'. "\n\n". '(Note that the application itself must also fit in available '. 'memory, so not all of the memory under the memory limit is '. 'available for running workloads.)'. "\n\n". "The easiest way to resolve this issue is to set %s to %s in your ". "PHP configuration, to disable the memory limit. There is ". "usually little or no value to using this option to limit ". - "Phabricator process memory.". + "process memory.". "\n\n". "You can also increase the limit or ignore this issue and accept ". "that you may encounter problems uploading large files and ". "processing large requests.", phutil_format_bytes($available_bytes), phutil_format_bytes($need_bytes), phutil_tag('tt', array(), 'memory_limit'), phutil_tag('tt', array(), '-1')); $this ->newIssue('php.memory_limit.upload') ->setName(pht('Memory Limit Restricts File Uploads')) ->setSummary($summary) ->setMessage($message) ->setGroup(self::GROUP_PHP) ->addPHPConfig('memory_limit') ->addPHPConfigOriginalValue('memory_limit', $memory_limit); } } $local_path = PhabricatorEnv::getEnvConfig('storage.local-disk.path'); if (!$local_path) { return; } if (!Filesystem::pathExists($local_path) || !is_readable($local_path) || !is_writable($local_path)) { $message = pht( 'Configured location for storing uploaded files on disk ("%s") does '. 'not exist, or is not readable or writable. Verify the directory '. 'exists and is readable and writable by the webserver.', $local_path); $this ->newIssue('config.storage.local-disk.path') ->setShortName(pht('Local Disk Storage')) ->setName(pht('Local Disk Storage Not Readable/Writable')) ->setMessage($message) ->addPhabricatorConfig('storage.local-disk.path'); } } private function checkS3() { $access_key = PhabricatorEnv::getEnvConfig('amazon-s3.access-key'); $secret_key = PhabricatorEnv::getEnvConfig('amazon-s3.secret-key'); $region = PhabricatorEnv::getEnvConfig('amazon-s3.region'); $endpoint = PhabricatorEnv::getEnvConfig('amazon-s3.endpoint'); $how_many = 0; if (strlen($access_key)) { $how_many++; } if (strlen($secret_key)) { $how_many++; } if (strlen($region)) { $how_many++; } if (strlen($endpoint)) { $how_many++; } // Nothing configured, no issues here. if ($how_many === 0) { return; } // Everything configured, no issues here. if ($how_many === 4) { return; } $message = pht( 'File storage in Amazon S3 has been partially configured, but you are '. 'missing some required settings. S3 will not be available to store '. 'files until you complete the configuration. Either configure S3 fully '. 'or remove the partial configuration.'); $this->newIssue('storage.s3.partial-config') ->setShortName(pht('S3 Partially Configured')) ->setName(pht('Amazon S3 is Only Partially Configured')) ->setMessage($message) ->addPhabricatorConfig('amazon-s3.access-key') ->addPhabricatorConfig('amazon-s3.secret-key') ->addPhabricatorConfig('amazon-s3.region') ->addPhabricatorConfig('amazon-s3.endpoint'); } } diff --git a/src/applications/config/check/PhabricatorTimezoneSetupCheck.php b/src/applications/config/check/PhabricatorTimezoneSetupCheck.php index 08f6a28d10..71036724f7 100644 --- a/src/applications/config/check/PhabricatorTimezoneSetupCheck.php +++ b/src/applications/config/check/PhabricatorTimezoneSetupCheck.php @@ -1,57 +1,58 @@ newIssue('php.date.timezone') ->setShortName(pht('PHP Timezone')) ->setName(pht('PHP Timezone Invalid')) ->setMessage($message) ->addPHPConfig('date.timezone'); } } $timezone = nonempty( PhabricatorEnv::getEnvConfig('phabricator.timezone'), ini_get('date.timezone')); if ($timezone) { return; } $summary = pht( 'Without a configured timezone, PHP will emit warnings when working '. 'with dates, and dates and times may not display correctly.'); $message = pht( "Your configuration fails to specify a server timezone. You can either ". - "set the PHP configuration value '%s' or the Phabricator ". - "configuration value '%s' to specify one.", + "set the PHP configuration value '%s' or the %s configuration ". + "value '%s' to specify one.", 'date.timezone', + PlatformSymbols::getPlatformServerName(), 'phabricator.timezone'); $this ->newIssue('config.timezone') ->setShortName(pht('Timezone')) ->setName(pht('Server Timezone Not Configured')) ->setSummary($summary) ->setMessage($message) ->addPHPConfig('date.timezone') ->addPhabricatorConfig('phabricator.timezone'); } } diff --git a/src/applications/config/controller/settings/PhabricatorConfigEditController.php b/src/applications/config/controller/settings/PhabricatorConfigEditController.php index 2cb217db69..38c592efca 100644 --- a/src/applications/config/controller/settings/PhabricatorConfigEditController.php +++ b/src/applications/config/controller/settings/PhabricatorConfigEditController.php @@ -1,472 +1,472 @@ getViewer(); $key = $request->getURIData('key'); $options = PhabricatorApplicationConfigOptions::loadAllOptions(); if (empty($options[$key])) { $ancient = PhabricatorExtraConfigSetupCheck::getAncientConfig(); if (isset($ancient[$key])) { $desc = pht( "This configuration has been removed. You can safely delete ". "it.\n\n%s", $ancient[$key]); } else { $desc = pht( 'This configuration option is unknown. It may be misspelled, '. - 'or have existed in a previous version of Phabricator.'); + 'or have existed in a previous version of the software.'); } // This may be a dead config entry, which existed in the past but no // longer exists. Allow it to be edited so it can be reviewed and // deleted. $option = id(new PhabricatorConfigOption()) ->setKey($key) ->setType('wild') ->setDefault(null) ->setDescription($desc); $group = null; } else { $option = $options[$key]; $group = $option->getGroup(); } $issue = $request->getStr('issue'); if ($issue) { // If the user came here from an open setup issue, send them back. $done_uri = $this->getApplicationURI('issue/'.$issue.'/'); } else { $done_uri = $this->getApplicationURI('settings/'); } // Check if the config key is already stored in the database. // Grab the value if it is. $config_entry = id(new PhabricatorConfigEntry()) ->loadOneWhere( 'configKey = %s AND namespace = %s', $key, 'default'); if (!$config_entry) { $config_entry = id(new PhabricatorConfigEntry()) ->setConfigKey($key) ->setNamespace('default') ->setIsDeleted(true); $config_entry->setPHID($config_entry->generatePHID()); } $e_value = null; $errors = array(); if ($request->isFormPost() && !$option->getLocked()) { $result = $this->readRequest( $option, $request); list($e_value, $value_errors, $display_value, $xaction) = $result; $errors = array_merge($errors, $value_errors); if (!$errors) { $editor = id(new PhabricatorConfigEditor()) ->setActor($viewer) ->setContinueOnNoEffect(true) ->setContentSourceFromRequest($request); try { $editor->applyTransactions($config_entry, array($xaction)); return id(new AphrontRedirectResponse())->setURI($done_uri); } catch (PhabricatorConfigValidationException $ex) { $e_value = pht('Invalid'); $errors[] = $ex->getMessage(); } } } else { if ($config_entry->getIsDeleted()) { $display_value = null; } else { $display_value = $this->getDisplayValue( $option, $config_entry, $config_entry->getValue()); } } $form = id(new AphrontFormView()) ->setEncType('multipart/form-data'); $error_view = null; if ($errors) { $error_view = id(new PHUIInfoView()) ->setSeverity(PHUIInfoView::SEVERITY_ERROR) ->setErrors($errors); } $doc_href = PhabricatorEnv::getDoclink( 'Configuration Guide: Locked and Hidden Configuration'); $doc_link = phutil_tag( 'a', array( 'href' => $doc_href, 'target' => '_blank', ), pht('Learn more about locked and hidden options.')); $status_items = array(); $tag = null; if ($option->getHidden()) { $tag = id(new PHUITagView()) ->setName(pht('Hidden')) ->setColor(PHUITagView::COLOR_GREY) ->setBorder(PHUITagView::BORDER_NONE) ->setType(PHUITagView::TYPE_SHADE); $message = pht( 'This configuration is hidden and can not be edited or viewed from '. 'the web interface.'); $status_items[] = id(new PHUIInfoView()) ->appendChild(array($message, ' ', $doc_link)); } else if ($option->getLocked()) { $tag = id(new PHUITagView()) ->setName(pht('Locked')) ->setColor(PHUITagView::COLOR_RED) ->setBorder(PHUITagView::BORDER_NONE) ->setType(PHUITagView::TYPE_SHADE); $message = $option->getLockedMessage(); $status_items[] = id(new PHUIInfoView()) ->appendChild(array($message, ' ', $doc_link)); } if ($option->getHidden() || $option->getLocked()) { $controls = array(); } else { $controls = $this->renderControls( $option, $display_value, $e_value); } $form ->setUser($viewer) ->addHiddenInput('issue', $request->getStr('issue')); $description = $option->newDescriptionRemarkupView($viewer); if ($description) { $form->appendChild( id(new AphrontFormMarkupControl()) ->setLabel(pht('Description')) ->setValue($description)); } if ($group) { $extra = $group->renderContextualDescription( $option, $request); if ($extra !== null) { $form->appendChild( id(new AphrontFormMarkupControl()) ->setValue($extra)); } } foreach ($controls as $control) { $form->appendControl($control); } if (!$option->getLocked()) { $form->appendChild( id(new AphrontFormSubmitControl()) ->addCancelButton($done_uri) ->setValue(pht('Save Config Entry'))); } $current_config = null; if (!$option->getHidden()) { $current_config = $this->renderDefaults($option, $config_entry); $current_config = $this->buildConfigBoxView( pht('Current Configuration'), $current_config); } $examples = $this->renderExamples($option); if ($examples) { $examples = $this->buildConfigBoxView( pht('Examples'), $examples); } $title = $key; $box_header = array(); $box_header[] = $key; $crumbs = $this->newCrumbs() ->addTextCrumb($key, '/config/edit/'.$key); $form_box = $this->buildConfigBoxView($box_header, $form, $tag); $timeline = $this->buildTransactionTimeline( $config_entry, new PhabricatorConfigTransactionQuery()); $timeline->setShouldTerminate(true); $header = $this->buildHeaderView($title); $view = id(new PHUITwoColumnView()) ->setHeader($header) ->setFooter( array( $error_view, $form_box, $status_items, $examples, $current_config, )); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) ->appendChild($view); } private function readRequest( PhabricatorConfigOption $option, AphrontRequest $request) { $type = $option->newOptionType(); if ($type) { $is_set = $type->isValuePresentInRequest($option, $request); if ($is_set) { $value = $type->readValueFromRequest($option, $request); $errors = array(); try { $canonical_value = $type->newValueFromRequestValue( $option, $value); $type->validateStoredValue($option, $canonical_value); $xaction = $type->newTransaction($option, $canonical_value); } catch (PhabricatorConfigValidationException $ex) { $errors[] = $ex->getMessage(); $xaction = null; } catch (Exception $ex) { // NOTE: Some older validators throw bare exceptions. Purely in good // taste, it would be nice to convert these at some point. $errors[] = $ex->getMessage(); $xaction = null; } return array( $errors ? pht('Invalid') : null, $errors, $value, $xaction, ); } else { $delete_xaction = id(new PhabricatorConfigTransaction()) ->setTransactionType(PhabricatorConfigTransaction::TYPE_EDIT) ->setNewValue( array( 'deleted' => true, 'value' => null, )); return array( null, array(), null, $delete_xaction, ); } } // TODO: If we missed on the new `PhabricatorConfigType` map, fall back // to the old semi-modular, semi-hacky way of doing things. $xaction = new PhabricatorConfigTransaction(); $xaction->setTransactionType(PhabricatorConfigTransaction::TYPE_EDIT); $e_value = null; $errors = array(); if ($option->isCustomType()) { $info = $option->getCustomObject()->readRequest($option, $request); list($e_value, $errors, $set_value, $value) = $info; } else { throw new Exception( pht( 'Unknown configuration option type "%s".', $option->getType())); } if (!$errors) { $xaction->setNewValue( array( 'deleted' => false, 'value' => $set_value, )); } else { $xaction = null; } return array($e_value, $errors, $value, $xaction); } private function getDisplayValue( PhabricatorConfigOption $option, PhabricatorConfigEntry $entry, $value) { $type = $option->newOptionType(); if ($type) { return $type->newDisplayValue($option, $value); } if ($option->isCustomType()) { return $option->getCustomObject()->getDisplayValue( $option, $entry, $value); } throw new Exception( pht( 'Unknown configuration option type "%s".', $option->getType())); } private function renderControls( PhabricatorConfigOption $option, $display_value, $e_value) { $type = $option->newOptionType(); if ($type) { return $type->newControls( $option, $display_value, $e_value); } if ($option->isCustomType()) { $controls = $option->getCustomObject()->renderControls( $option, $display_value, $e_value); } else { throw new Exception( pht( 'Unknown configuration option type "%s".', $option->getType())); } return $controls; } private function renderExamples(PhabricatorConfigOption $option) { $examples = $option->getExamples(); if (!$examples) { return null; } $table = array(); $table[] = phutil_tag('tr', array('class' => 'column-labels'), array( phutil_tag('th', array(), pht('Example')), phutil_tag('th', array(), pht('Value')), )); foreach ($examples as $example) { list($value, $description) = $example; if ($value === null) { $value = phutil_tag('em', array(), pht('(empty)')); } else { if (is_array($value)) { $value = implode("\n", $value); } } $table[] = phutil_tag('tr', array('class' => 'column-labels'), array( phutil_tag('th', array(), $description), phutil_tag('td', array(), $value), )); } require_celerity_resource('config-options-css'); return phutil_tag( 'table', array( 'class' => 'config-option-table', 'cellspacing' => '0', 'cellpadding' => '0', ), $table); } private function renderDefaults( PhabricatorConfigOption $option, PhabricatorConfigEntry $entry) { $stack = PhabricatorEnv::getConfigSourceStack(); $stack = $stack->getStack(); $table = array(); $table[] = phutil_tag('tr', array('class' => 'column-labels'), array( phutil_tag('th', array(), pht('Source')), phutil_tag('th', array(), pht('Value')), )); $is_effective_value = true; foreach ($stack as $key => $source) { $row_classes = array( 'column-labels', ); $value = $source->getKeys( array( $option->getKey(), )); if (!array_key_exists($option->getKey(), $value)) { $value = phutil_tag('em', array(), pht('(No Value Configured)')); } else { $value = $this->getDisplayValue( $option, $entry, $value[$option->getKey()]); if ($is_effective_value) { $is_effective_value = false; $row_classes[] = 'config-options-effective-value'; } } $table[] = phutil_tag( 'tr', array( 'class' => implode(' ', $row_classes), ), array( phutil_tag('th', array(), $source->getName()), phutil_tag('td', array(), $value), )); } require_celerity_resource('config-options-css'); return phutil_tag( 'table', array( 'class' => 'config-option-table', 'cellspacing' => '0', 'cellpadding' => '0', ), $table); } }