@@ -8,15 +8,280 @@

namespace Aegir\Provision\Service\Http;

use Aegir\Provision\Context\ServerContext;
use Aegir\Provision\Provision;
use Aegir\Provision\Service\Http\Nginx\Configuration\PlatformConfiguration;
use Aegir\Provision\Service\Http\Nginx\Configuration\ServerConfiguration;
use Aegir\Provision\Service\Http\Nginx\Configuration\SiteCommonConfiguration;
use Aegir\Provision\Service\Http\Nginx\Configuration\SiteConfiguration;
use Aegir\Provision\Service\HttpService;

/**
* Class HttpNginxService
*
* @package Aegir\Provision\Service\Http
*/
class HttpNginxService extends HttpService
{
const SERVICE_TYPE = 'nginx';
const SERVICE_TYPE_NAME = 'NGINX';
class HttpNginxService extends HttpService {

const SERVICE_TYPE = 'nginx';

const SERVICE_TYPE_NAME = 'NGINX';

/**
* Path to PHP FPM Socket file for php5.
*/
const SOCKET_PATH_PHP5 = '/var/run/php5-fpm.sock';

/**
* Path to PHP FPM Socket file for php7.
*/
const SOCKET_PATH_PHP7 = '/var/run/php/php7.0-fpm.sock';

/**
* Path to NGINX Control mode file.
*/
const NGINX_CONTROL_MODE_FILE = '/etc/nginx/basic_nginx.conf';

/**
*
*/
const BOA_CONFIG_PATH = '/data/conf/global.inc';

/**
* HttpNginxService constructor.
*
* Detects NGINX configuration and sets as service properties.
*
* @param $service_config
* @param \Aegir\Provision\Context\ServerContext $provider_context
*/
function __construct($service_config, ServerContext $provider_context) {
parent::__construct($service_config, $provider_context);

}

/**
* Run when a `verify` command is invoked.
*
* @TODO: Should we move this to a different function? I don't want to store
* these values in config files since they are read from the system.
*
* Are these values needed in other methods or commands? Is it ok for those
* other methods to invoke verify() themselves if they need these properties?
*
* @return array
*/
public function verify() {

$nginx_config = $this->provider->shell_exec(self::getNginxExecutable() . ' -V');
$this->setProperty('nginx_is_modern', preg_match("/nginx\/1\.((1\.(8|9|(1[0-9]+)))|((2|3|4|5|6|7|8|9|[1-9][0-9]+)\.))/", $nginx_config, $match));
$this->setProperty('nginx_has_etag', preg_match("/nginx\/1\.([12][0-9]|[3]\.([12][0-9]|[3-9]))/", $nginx_config, $match));
$this->setProperty('nginx_has_http2', preg_match("/http_v2_module/", $nginx_config, $match));
$this->setProperty('nginx_has_upload_progress', preg_match("/upload/", $nginx_config, $match));
$this->setProperty('nginx_has_gzip', preg_match("/http_gzip_static_module/", $nginx_config, $match));

// Use basic nginx configuration if this control file exists.
if (Provision::fs()->exists(self::NGINX_CONTROL_MODE_FILE)) {
$this->setProperty('nginx_config_mode', 'basic');
$this->getProvision()->getLogger()->info('Basic Nginx Config Active -SAVE- YES control file found {path}.', [
'path' => self::NGINX_CONTROL_MODE_FILE,
]);
}
else {
$this->setProperty('nginx_config_mode', 'extended');
$this->getProvision()->getLogger()->info('Extended Nginx Config Active -SAVE- NO control file found {path}.', [
'path' => self::NGINX_CONTROL_MODE_FILE,
]);
}

// Check if there is php-fpm listening on unix socket, otherwise use port 9000 to connect
$this->setProperty('phpfpm_mode', self::getPhpFpmMode());
$this->setProperty('phpfpm_socket_path', self::getPhpFpmSocketPath());

// @TODO: Work out a way for something like BOA to do this via a plugin.
// Check if there is BOA specific global.inc file to enable extra Nginx locations
if ($this->provider->fs->exists(self::BOA_CONFIG_PATH)) {
$this->setProperty('satellite_mode', 'boa');
$this->getProvision()->getLogger()->debug('BOA mode detected -SAVE- YES file found {path}.', ['path' => self::BOA_CONFIG_PATH]);
}
else {
$this->setProperty('satellite_mode', 'vanilla');
$this->getProvision()->getLogger()->debug('Vanilla mode detected -SAVE- NO file found {path}.', ['path' => self::BOA_CONFIG_PATH]);
}

// // Set correct subdirs_support value on server save
// if (provision_hosting_feature_enabled('subdirs')) {
// $this->server->subdirs_support = TRUE;
// }

$this->setProperty('server_config_path', $this->provider->server_config_path);

return parent::verify();
}

/**
* No tasks to run
* @return array
*/
function verifyPlatform() {
$tasks = [];
return $tasks;
}

/**
/**
* Override restart_command task to use our default_restart_cmd()
*
* @TODO: Not sure why this is needed? Why does "self" not return this class's default_restart_cmd()?
*
* @return array
*/
static function server_options() {
$options = parent::server_options();
$options['restart_command'] = Provision::newProperty()
->description('The command to reload the web server configuration.')
->defaultValue(function () {
return self::default_restart_cmd();
})
->required()
;
$options['php_fpm_sock_location'] = Provision::newProperty()
->description('Path to PHP FPM socket, or address and port that PHP-FPM is listening on (127.0.0.1:5000). NOTE: If installed using yum or apt, you may already have a configured "upstream" named "php-fpm", if so use "php-fpm" here.')
->defaultValue(self::getPhpFpmLocation())
->hidden()
;
return $options;
}

/**
* Returns array of Configuration classes for this service.
*
* @see Provision_Service_http_apache::init_server();
*
* @return array
*/
public function getConfigurations()
{
$configs['server'][] = ServerConfiguration::class;
$configs['server'][] = SiteCommonConfiguration::class;
$configs['site'][] = SiteConfiguration::class;
return $configs;
}

/**
* Determine full nginx restart command.
*
* @return string
*/
public static function default_restart_cmd() {
$command = self::getNginxExecutable();
if ($command != '/etc/init.d/nginx') {
$command .= ' -s';
}

return "sudo $command reload";
}

/**
* Find the nginx executable and return the path to it.
*
* @return array
*/
public static function getNginxExecutable() {
$command = '/etc/init.d/nginx'; // A proper default for most of the world
$options[] = $command;
// Try to detect the nginx restart command.
foreach (explode(':', $_SERVER['PATH']) as $path) {
$options[] = "$path/nginx";
}
$options[] = '/usr/sbin/nginx';
$options[] = '/usr/local/sbin/nginx';
$options[] = '/usr/local/bin/nginx';

foreach ($options as $test) {
if (is_executable($test)) {
return $test;
}
}
}

/**
* Determines the PHP FPM mode.
*
* @return string
* The mode, either 'socket' or 'port'.
*/
public static function getPhpFpmMode() {

// Search for socket files or fall back to port mode.
switch (TRUE) {
case Provision::fs()->exists(self::SOCKET_PATH_PHP5):
$mode = 'socket';
$socket_path = self::SOCKET_PATH_PHP5;
break;
case Provision::fs()->exists(self::SOCKET_PATH_PHP7):
$mode = 'socket';
$socket_path = self::SOCKET_PATH_PHP7;
break;
default:
$mode = 'port';
$socket_path = '';
break;
}

// Return the discovered mode.
return $mode;
}

/**
* Gets the PHP FPM unix socket path.
*
* If we're running in port mode, there is no socket path. FALSE would be
* returned in this case.
*
* @return string
* The path, or FALSE if there isn't one.
*/
public static function getPhpFpmSocketPath() {
// Simply return FALSE if we're in port mode.
if (self::getPhpFpmMode() == 'port') {
return FALSE;
}

// Return the socket path based on the PHP version.
if (strtok(phpversion(), '.') == 7) {
return self::SOCKET_PATH_PHP7;
}
else {
return self::SOCKET_PATH_PHP5;
}
}

/**
* Find the path to PHP FPM socket from common options.
*
* @return mixed
*/
public static function getPhpFpmLocation() {
$options[] = '/run/php-fpm/www.sock';
$options[] = '/run/php/php7.2-fpm.sock';
$options[] = '/run/php/php7.1-fpm.sock';
$options[] = '/run/php/php7.0-fpm.sock';
$options[] = '/var/run/php7-fpm.sock';
$options[] = '/var/run/php5-fpm.sock';
$options[] = '/var/run/php/php7.2-fpm.sock';
$options[] = '/var/run/php/php7.1-fpm.sock';
$options[] = '/var/run/php/php7.0-fpm.sock';
$options[] = '/opt/remi/php72/root/tmp/php-fpm.sock';
$options[] = '/opt/remi/php71/root/tmp/php-fpm.sock';
$options[] = '/opt/remi/php70/root/tmp/php-fpm.sock';

foreach ($options as $test) {
if (Provision::fs()->exists($test)) {
return 'unix:' . $test;
}
}

return '127.0.0.1:5000';
}
}
@@ -0,0 +1,53 @@
<?php
/**
* @file ServerConfiguration.php
*
* NGINX Configuration for Server Context.
*/

namespace Aegir\Provision\Service\Http\Nginx\Configuration;

use Aegir\Provision\ConfigFile;

class ServerConfiguration extends ConfigFile {

public $template = 'templates/server.tpl.php';
public $description = 'web server configuration file';

function filename() {
if ($this->service->getType()) {
$file = $this->service->getType() . '.conf';
return $this->service->provider->getProvision()->getConfig()->get('config_path') . '/' . $this->service->provider->name . '/' . $file;
}
else {
return FALSE;
}
}

function process() {
parent::process();

// Run verify to load in nginx properties.
$this->service->verify();

$app_dir = $this->getContext()->server_config_path . DIRECTORY_SEPARATOR . $this->service->getType();
$this->data['http_port'] = $this->service->properties['http_port'];
$this->data['include_statement'] = '# INCLUDE STATEMENT';
$this->data['http_pred_path'] = "{$app_dir}/pre.d";
$this->data['http_postd_path'] = "{$app_dir}/post.d";
$this->data['http_platformd_path'] = "{$app_dir}/platform.d";
$this->data['http_vhostd_path'] = "{$app_dir}/vhost.d";
$this->data['extra_config'] = "";

$this->fs->mkdir($this->data['http_pred_path']);
$this->fs->mkdir($this->data['http_postd_path']);
$this->fs->mkdir($this->data['http_platformd_path']);
$this->fs->mkdir($this->data['http_vhostd_path']);

$this->data['script_user'] = $this->service->provider->getProperty('script_user');
$this->data['aegir_root'] = $this->service->provider->getProperty('aegir_root');

$this->data['php_fpm_sock_location'] = $this->service->getProperty('php_fpm_sock_location');

}
}
@@ -0,0 +1,51 @@
<?php
/**
* @file ServerConfiguration.php
*
* NGINX Configuration for Server Context.
*/

namespace Aegir\Provision\Service\Http\Nginx\Configuration;

class SiteCommonConfiguration extends ServerConfiguration {

public $template = 'templates/vhost_include.tpl.php';

public $description = 'Common site configuration';


function write() {
parent::write();
//
// if (isset($this->data['application_name'])) {
// $file = $this->data['application_name'] . '_vhost_common.conf';
// $legacy_simple_file = $this->data['application_name'] . '_simple_include.conf';
// $legacy_advanced_file = $this->data['application_name'] . '_advanced_include.conf';
// // We link both legacy files on the remote server to the right version.
// $cmda = sprintf('ln -sf %s %s',
// escapeshellarg($this->data['server']->include_path . '/' . $file),
// escapeshellarg($this->data['server']->include_path . '/' . $legacy_simple_file)
// );
// $cmdb = sprintf('ln -sf %s %s',
// escapeshellarg($this->data['server']->include_path . '/' . $file),
// escapeshellarg($this->data['server']->include_path . '/' . $legacy_advanced_file)
// );
// if ($this->data['server']->shell_exec($cmda)) {
// drush_log(dt("Created legacy_simple_file symlink for %file on %server", array(
// '%file' => $file,
// '%server' => $this->data['server']->remote_host,
// )));
// };
// if ($this->data['server']->shell_exec($cmdb)) {
// drush_log(dt("Created legacy_advanced_file symlink for %file on %server", array(
// '%file' => $file,
// '%server' => $this->data['server']->remote_host,
// )));
// };
// }
}

function filename() {
return $this->service->provider->server_config_path . '/nginx_vhost_common.conf';
}
}
@@ -0,0 +1,22 @@
<?php
/**
* @file Site.php
*
* Apache Configuration for Server Context.
* @see \Provision_Config_Apache_Site
* @see \Provision_Config_Http_Site
*/

namespace Aegir\Provision\Service\Http\Nginx\Configuration;

use Aegir\Provision\Service\Http\Apache\Configuration\SiteConfigFile as BaseSiteConfigFile;

class SiteConfiguration extends BaseSiteConfigFile {

public $template = 'templates/vhost.tpl.php';

function process() {
parent::process();
$this->data['php_fpm_sock_location'] = $this->service->getProperty('php_fpm_sock_location');
}
}
@@ -0,0 +1,215 @@
# Aegir web server main configuration file

#######################################################
### nginx.conf main
#######################################################

<?php

if ($nginx_is_modern) {
print " limit_conn_zone \$binary_remote_addr zone=limreq:10m;\n";
}
else {
print " limit_zone limreq \$binary_remote_addr 10m;\n";
}

if ($nginx_has_gzip) {
print " gzip_static on;\n";
}

if ($nginx_has_upload_progress) {
print " upload_progress uploads 1m;\n";
}
?>

<?php if ($nginx_config_mode == 'extended'): ?>

## Size Limits
client_body_buffer_size 64k;
client_header_buffer_size 32k;
connection_pool_size 256;
fastcgi_buffer_size 128k;
fastcgi_buffers 256 4k;
fastcgi_busy_buffers_size 256k;
fastcgi_temp_file_write_size 256k;
large_client_header_buffers 32 32k;
request_pool_size 4k;
server_names_hash_bucket_size 512;

## Timeouts
client_body_timeout 180;
client_header_timeout 180;
send_timeout 180;
lingering_time 30;
lingering_timeout 5;
fastcgi_connect_timeout 10s;
fastcgi_send_timeout 180s;
fastcgi_read_timeout 180s;

## Open File Performance
open_file_cache max=8000 inactive=30s;
open_file_cache_valid 99s;
open_file_cache_min_uses 3;
open_file_cache_errors on;

## FastCGI Caching
fastcgi_cache_path /var/lib/nginx/speed
levels=2:2
keys_zone=speed:10m
inactive=15m
max_size=3g;

## General Options
ignore_invalid_headers on;
recursive_error_pages on;
reset_timedout_connection on;
fastcgi_intercept_errors on;

## SSL performance
ssl_session_cache shared:SSL:10m;

## Compression
gzip_buffers 16 8k;
gzip_comp_level 8;
gzip_http_version 1.0;
gzip_min_length 50;
gzip_types
application/atom+xml
application/javascript
application/json
application/rss+xml
application/vnd.ms-fontobject
application/x-font-opentype
application/x-font-ttf
application/x-javascript
application/xhtml+xml
application/xml
application/xml+rss
font/opentype
image/svg+xml
image/x-icon
text/css
text/javascript
text/plain
text/xml;
gzip_vary on;
gzip_proxied any;
<?php endif; ?>

## Default index files
index index.php index.html;

## Log Format
log_format main '"$proxy_add_x_forwarded_for" $host [$time_local] '
'"$request" $status $body_bytes_sent '
'$request_length $bytes_sent "$http_referer" '
'"$http_user_agent" $request_time "$gzip_ratio"';

client_body_temp_path /var/lib/nginx/body 1 2;
access_log /var/log/nginx/access.log main;

<?php print $extra_config; ?>
<?php if ($nginx_config_mode == 'extended'): ?>
#######################################################
### nginx default maps
#######################################################

###
### Support separate Speed Booster caches for various mobile devices.
###
map $http_user_agent $device {
default normal;
~*Nokia|BlackBerry.+MIDP|240x|320x|Palm|NetFront|Symbian|SonyEricsson mobile-other;
~*iPhone|iPod|Android|BlackBerry.+AppleWebKit mobile-smart;
~*iPad|Tablet mobile-tablet;
}

###
### Set a cache_uid variable for authenticated users (by @brianmercer and @perusio, fixed by @omega8cc).
###
map $http_cookie $cache_uid {
default '';
~SESS[[:alnum:]]+=(?<session_id>[[:graph:]]+) $session_id;
}

###
### Live switch of $key_uri for Speed Booster cache depending on $args.
###
map $request_uri $key_uri {
default $request_uri;
~(?<no_args_uri>[[:graph:]]+)\?(.*)(utm_|__utm|_campaign|gclid|source=|adv=|req=) $no_args_uri;
}

###
### Deny crawlers.
###
map $http_user_agent $is_crawler {
default '';
~*HTTrack|BrokenLinkCheck|2009042316.*Firefox.*3\.0\.10 is_crawler;
~*SiteBot|PECL|Automatic|CCBot|BuzzTrack|Sistrix|Offline is_crawler;
~*SWEB|Morfeus|GSLFbot|HiScan|Riddler|DBot|SEOkicks|MJ12 is_crawler;
~*PChomebot|Scrap|HTMLParser|Nutch|Mireo|Semrush|Ahrefs is_crawler;
}

###
### Block semalt botnet.
###
map $http_referer $is_botnet {
default '';
~*semalt\.com|kambasoft\.com|savetubevideo\.com|bottlenose\.com|yapoga\.com is_botnet;
~*descargar-musica-gratis\.net|baixar-musicas-gratis\.com is_botnet;
}

###
### Deny all known bots/spiders on some URIs.
###
map $http_user_agent $is_bot {
default '';
~*crawl|bot|spider|tracker|click|parser|google|yahoo|yandex|baidu|bing is_bot;
}

###
### Deny almost all crawlers under high load.
###
map $http_user_agent $deny_on_high_load {
default '';
~*crawl|spider|tracker|click|parser|google|yahoo|yandex|baidu|bing deny_on_high_load;
}

###
### Deny listed requests for security reasons.
###
map $args $is_denied {
default '';
~*delete.+from|insert.+into|select.+from|union.+select|onload|\.php.+src|system\(.+|document\.cookie|\;|\.\.\/ is_denied;
}
<?php endif; ?>

#######################################################
### nginx default server
#######################################################

server {
listen *:<?php print $http_port; ?>;
server_name _;
location / {
return 404;
}
}

# PHP-FPM FastCGI server
# network or unix domain socket configuration

upstream provision-php-fpm {
server <?php print $php_fpm_sock_location; ?>;
}

#######################################################
### nginx virtual domains
#######################################################

# virtual hosts
include <?php print $http_pred_path ?>/*;
include <?php print $http_platformd_path ?>/*;
include <?php print $http_vhostd_path ?>/*;
include <?php print $http_postd_path ?>/*;
@@ -0,0 +1,117 @@
<?php
if ($redirection) {
// $aegir_root = d('@server_master')->aegir_root;
// $satellite_mode = d('@server_master')->satellite_mode;
// Redirect all aliases to the main http url using separate vhosts blocks to avoid if{} in Nginx.
foreach ($aliases as $alias_url) {
print "# alias redirection virtual host\n";
print "server {\n";
print " listen *:{$http_port};\n";
// if we use redirections, we need to change the redirection
// target to be the original site URL ($this->uri instead of
// $alias_url)
if (isset($redirection_target) && $alias_url == $redirection_target) {
$uri = str_replace('/', '.', $uri);
print " server_name {$uri};\n";
} else {
$alias_url = str_replace('/', '.', $alias_url);
print " server_name {$alias_url};\n";
}
print " access_log off;\n";
// if ($satellite_mode == 'boa') {
// print "\n";
// print " ###\n";
// print " ### Allow access to letsencrypt.org ACME challenges directory.\n";
// print " ###\n";
// print " location ^~ /.well-known/acme-challenge {\n";
// print " alias {$aegir_root}/tools/le/.acme-challenges;\n";
// print " try_files \$uri 404;\n";
// print " }\n";
// print "\n";
// }
print " return 301 \$scheme://{$redirection_target}\$request_uri;\n";
print "}\n";
}
}
?>

server {
include fastcgi_params;

# Block https://httpoxy.org/ attacks.
fastcgi_param HTTP_PROXY "";

fastcgi_param MAIN_SITE_NAME <?php print $uri; ?>;
set $main_site_name "<?php print $uri; ?>";
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
<?php
// If any of those parameters is empty for any reason, like after an attempt
// to import complete platform with sites without importing their databases,
// it will break Nginx reload and even shutdown all sites on the system on
// Nginx restart, so we need to use dummy placeholders to avoid affecting
// other sites on the system if this site is broken.
if (!$db_type || !$db_name || !$db_user || !$db_passwd || !$db_host) {
$db_type = 'mysqli';
$db_name = 'none';
$db_user = 'none';
$db_passwd = 'none';
$db_host = 'localhost';
}
?>
fastcgi_param db_type <?php print urlencode($db_type); ?>;
fastcgi_param db_name <?php print urlencode($db_name); ?>;
fastcgi_param db_user <?php print urlencode($db_user); ?>;
fastcgi_param db_passwd <?php print urlencode($db_passwd); ?>;
fastcgi_param db_host <?php print urlencode($db_host); ?>;
<?php
// Until the real source of this problem is fixed elsewhere, we have to
// use this simple fallback to guarantee that empty db_port does not
// break Nginx reload which results with downtime for the affected vhosts.
// if (!$db_port) {
// $db_port = $this->server->db_port ? $this->server->db_port : '3306';
// }
?>
fastcgi_param db_port <?php print urlencode($db_port); ?>;
listen *:<?php print $http_port; ?>;
server_name <?php
// this is the main vhost, so we need to put the redirection
// target as the hostname (if it exists) and not the original URL
// ($uri)
if ($redirection) {
print str_replace('/', '.', $redirection);
} else {
print $uri;
}
if (!$redirection && is_array($aliases)) {
foreach ($aliases as $alias_url) {
if (trim($alias_url)) {
print " " . str_replace('/', '.', $alias_url);
}
}
} ?>;
root <?php print "{$document_root_full}"; ?>;
<?php print $extra_config; ?>
<?php
if ($redirection || $ssl_redirection) {
if ($ssl_redirection && !$redirection) {
// redirect aliases in non-ssl to the same alias on ssl.
print "\n return 301 https://\$host\$request_uri;\n";
}
elseif ($ssl_redirection && $redirection) {
// redirect all aliases + main uri to the main https uri.
print "\n return 301 https://{$redirection}\$request_uri;\n";
}
elseif (!$ssl_redirection && $redirection) {
// Commenting out until we fix where include_path is coming from.
// print " include " . $server->include_path . "/nginx_vhost_common.conf;\n";
}
}
else {
print " include " . $server_config_path . "/nginx_vhost_common.conf;\n";
}
//$if_subsite = $this->data['http_subdird_path'] . '/' . $uri;
//if (provision_hosting_feature_enabled('subdirs') && provision_file()->exists($if_subsite)->status()) {
// print " include " . $if_subsite . "/*.conf;\n";
//}
?>
}
@@ -0,0 +1,18 @@
<?php
$satellite_mode = drush_get_option('satellite_mode');
if (!$satellite_mode && $server->satellite_mode) {
$satellite_mode = $server->satellite_mode;
}
?>

server {
listen *:<?php print $http_port; ?>;
server_name <?php print $this->uri . ' ' . implode(' ', str_replace('/', '.', $this->aliases)); ?>;
<?php if ($satellite_mode == 'boa'): ?>
root /var/www/nginx-default;
index index.html index.htm;
### Do not reveal Aegir front-end URL here.
<?php else: ?>
return 302 <?php print $this->platform->server->web_disable_url . '/' . $this->uri ?>;
<?php endif; ?>
}

Large diffs are not rendered by default.

@@ -134,7 +134,7 @@ function verifyPlatform() {
/**
* Return the default restart command for this service.
*/
static function default_restart_cmd() {
public static function default_restart_cmd() {
return '';
}