#!/usr/bin/env php hst-.+)\.hst\.domain\.tld$; location / { set $backend_upstream "http://$lxcname:80"; proxy_pass $backend_upstream; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $remote_addr; } } server { listen 8083; server_name ~^(?hst-.+)\.hst\.domain\.tld$; location / { set $backend_upstream "https://$lxcname:8083"; proxy_pass $backend_upstream; } } # use lxc resolver /etc/nginx/nginx.conf # test resolver ip ex: dig +short @10.240.232.1 hst-ub1804-ngx-a2-mphp http { ... resolver 10.240.232.1 ipv6=off valid=5s; ... } */ ## Uncomment and configure the following vars # define('DOMAIN', 'hst.domain.tld'); # define('SHARED_HOST_FOLDER', '/home/myuser/projectfiles'); # define('HST_PASS', ''); // <- # openssl rand -base64 12 # define('HST_EMAIL', 'user@domain.tld'); define("HST_BRANCH", "~localsrc"); define("HST_ARGS", "--force --interactive no --clamav no -p " . HST_PASS . " --email " . HST_EMAIL); define("LXC_TIMEOUT", 30); if ( !defined("SHARED_HOST_FOLDER") || !defined("HST_PASS") || !defined("HST_EMAIL") || !defined("HST_BRANCH") || !defined("DOMAIN") ) { die("Error: missing variables" . PHP_EOL); } $containers = [ // ['description'=>'hst-d9-ngx-a2-mphp', 'os'=>'debian9', 'nginx'=>true, 'apache2'=>true, 'php'=>'multiphp', 'dns'=>'auto', 'exim'=>'auto'], [ "description" => "ub1804 ngx mphp", "os" => "ubuntu18.04", "nginx" => true, "apache2" => false, "php" => "multiphp", "dns" => "auto", "exim" => "auto", ], [ "description" => "ub1804 ngx fpm", "os" => "ubuntu18.04", "nginx" => true, "apache2" => false, "php" => "fpm", "dns" => "auto", "exim" => "auto", ], [ "description" => "ub1804 ngx a2", "os" => "ubuntu18.04", "nginx" => true, "apache2" => true, "php" => "auto", "dns" => "auto", "exim" => "auto", ], [ "description" => "ub1804 ngx a2 mphp", "os" => "ubuntu18.04", "nginx" => true, "apache2" => true, "php" => "multiphp", "dns" => "auto", "exim" => "auto", ], [ "description" => "ub1804 ngx a2 fpm", "os" => "ubuntu18.04", "nginx" => true, "apache2" => true, "php" => "fpm", "dns" => "auto", "exim" => "auto", ], [ "description" => "ub1804 a2 mphp", "os" => "ubuntu18.04", "nginx" => false, "apache2" => true, "php" => "multiphp", "dns" => "auto", "exim" => "auto", ], [ "description" => "ub1804 a2 fpm", "os" => "ubuntu18.04", "nginx" => false, "apache2" => true, "php" => "fpm", "dns" => "auto", "exim" => "auto", ], [ "description" => "ub1804 a2", "os" => "ubuntu18.04", "nginx" => false, "apache2" => true, "php" => "auto", "dns" => "auto", ], [ "description" => "ub1604 a2 mphp", "os" => "ubuntu16.04", "nginx" => false, "apache2" => true, "php" => "multiphp", "dns" => "auto", "exim" => "auto", ], ]; array_walk($containers, function (&$element) { $lxc_name = "hst-"; // hostname and lxc name prefix. Update nginx reverse proxy config after altering this value $hst_args = HST_ARGS; $element["hst_installer"] = "hst-install-ubuntu.sh"; $element["lxc_image"] = "ubuntu:18.04"; if ($element["os"] == "ubuntu16.04") { $element["lxc_image"] = "ubuntu:16.04"; $lxc_name .= "ub1604"; } elseif ($element["os"] == "debian8") { $element["lxc_image"] = "images:debian/8"; $element["hst_installer"] = "hst-install-debian.sh"; $lxc_name .= "d8"; } elseif ($element["os"] == "debian9") { $element["lxc_image"] = "images:debian/9"; $element["hst_installer"] = "hst-install-debian.sh"; $lxc_name .= "d9"; } else { $lxc_name .= "ub1804"; $element["os"] = "ubuntu18.04"; } if ($element["nginx"] === true) { $lxc_name .= "-ngx"; $hst_args .= " --nginx yes"; } else { $hst_args .= " --nginx no"; } if ($element["apache2"] === true) { $lxc_name .= "-a2"; $hst_args .= " --apache yes"; } else { $hst_args .= " --apache no"; } if ($element["php"] == "fpm") { $lxc_name .= "-fpm"; $hst_args .= " --phpfpm yes"; } elseif ($element["php"] == "multiphp") { $lxc_name .= "-mphp"; $hst_args .= " --multiphp yes"; } if (isset($element["dns"])) { if ($element["dns"] === true || $element["dns"] == "auto") { $hst_args .= " --named yes"; } else { $hst_args .= " --named no"; } } if (isset($element["exim"])) { if ($element["exim"] === true || $element["exim"] == "auto") { $hst_args .= " --exim yes"; } else { $hst_args .= " --exim no"; } } if (isset($element["webmail"])) { if ($element["webmail"] === true || $element["webmail"] == "auto") { $hst_args .= " --dovecot yes"; } else { $hst_args .= " --dovecot no"; } } $element["lxc_name"] = $lxc_name; $element["hostname"] = $lxc_name . "." . DOMAIN; // $hst_args .= ' --with-debs /home/ubuntu/source/hestiacp/src/pkgs/develop/' . $element['os']; $hst_args .= " --with-debs /tmp/hestiacp-src/debs"; $hst_args .= " --hostname " . $element["hostname"]; $element["hst_args"] = $hst_args; }); function lxc_run($args, &$rc) { $cmd_args = ""; if (is_array($args)) { foreach ($args as $arg) { $cmd_args .= " " . escapeshellarg($arg); } } else { $cmd_args = $args; } exec("lxc " . $cmd_args . " 2>/dev/null", $cmdout, $rc); if (isset($rc) && $rc !== 0) { return false; } if (json_decode(implode(PHP_EOL, $cmdout), true) === null) { return $cmdout; } return json_decode(implode(PHP_EOL, $cmdout), true); } function getHestiaVersion($branch) { $control_file = ""; if ($branch === "~localsrc") { $control_file = file_get_contents(SHARED_HOST_FOLDER . "/hestiacp/src/rpm/hestia/control"); } else { $control_file = file_get_contents( "https://dev.brepo.ru/bayrepo/hestiacp/raw/branch/master/src/rpm/hestia/control", ); } foreach (explode(PHP_EOL, $control_file) as $line) { if (empty($line)) { continue; } [$key, $value] = explode(":", $line); if (strtolower($key) === "version") { return trim($value); } } throw new Exception("Error reading Hestia version for branch: [${branch}]", 1); } function get_lxc_ip($name) { $result = lxc_run(["list", "--format", "csv", "-c", "n,4"], $rc); if (empty($result)) { return false; } foreach ($result as $line) { [$cnt, $address] = explode(",", $line); if ($cnt == $name) { $iface = explode(" ", $address); if (filter_var($iface[0], FILTER_VALIDATE_IP)) { return $iface[0]; } else { return false; } } } } function check_lxc_container($container) { echo "Check container:" . $container["lxc_name"] . PHP_EOL; lxc_run(["info", $container["lxc_name"]], $rc); if (isset($rc) && $rc === 0) { return; } $pid = pcntl_fork(); if ($pid > 0) { return $pid; } echo "Creating container " . $container["lxc_name"] . PHP_EOL; lxc_run(["init", $container["lxc_image"], $container["lxc_name"]], $rc); exec( "lxc config set " . escapeshellarg($container["lxc_name"]) . ' raw.idmap "both 1000 1000" 2>/dev/null', $devnull, $rc, ); exec( "lxc config device add " . escapeshellarg($container["lxc_name"]) . " hestiasrc disk path=/home/ubuntu/source source=" . SHARED_HOST_FOLDER . " 2>/dev/null", $devnull, $rc, ); lxc_run(["start", $container["lxc_name"]], $rc); $lxc_retry = 0; do { $lxc_retry++; $cip = get_lxc_ip($container["lxc_name"]); if ($cip) { echo "Container " . $container["lxc_name"] . " IP: $cip" . PHP_EOL; } sleep(1); } while ($lxc_retry <= LXC_TIMEOUT && filter_var($cip, FILTER_VALIDATE_IP) === false); echo "Updating container: " . $container["lxc_name"] . PHP_EOL; exec("lxc exec " . $container["lxc_name"] . " -- apt update", $devnull, $rc); exit(0); } function hst_installer_worker($container) { $pid = pcntl_fork(); if ($pid > 0) { return $pid; } system( "lxc exec " . $container["lxc_name"] . ' -- bash -c "/home/ubuntu/source/hestiacp/src/hst_autocompile.sh --hestia \"' . HST_BRANCH . '\" no"', ); $hver = getHestiaVersion(HST_BRANCH); echo "Install Hestia ${hver} on " . $container["lxc_name"] . PHP_EOL; echo "Args: " . $container["hst_args"] . PHP_EOL; system( "lxc exec " . $container["lxc_name"] . ' -- bash -c "cd \"/home/ubuntu/source/hestiacp\"; install/' . $container["hst_installer"] . " " . $container["hst_args"] . '" 2>&1 > /tmp/hst_installer_' . $container["lxc_name"], ); exit(0); } // Create and update containers $worker_pool = []; foreach ($containers as $container) { $worker_pid = check_lxc_container($container); if ($worker_pid > 0) { $worker_pool[] = $worker_pid; } } echo count($worker_pool) . " LXC workers started" . PHP_EOL; # waiting for workers to finish while (count($worker_pool)) { echo "Wait for LXC workers to finish" . PHP_EOL; $child_pid = pcntl_wait($status); if ($child_pid) { $worker_pos = array_search($child_pid, $worker_pool); unset($worker_pool[$worker_pos]); } } // Install Hestia $worker_pool = []; foreach ($containers as $container) { # Is hestia installed? lxc_run("exec " . $container["lxc_name"] . ' -- sudo --login "v-list-sys-config"', $rc); if (isset($rc) && $rc === 0) { continue; } $worker_pid = hst_installer_worker($container); if ($worker_pid > 0) { $worker_pool[] = $worker_pid; } } echo count($worker_pool) . " background workers started" . PHP_EOL; # waiting for workers to finish while (count($worker_pool)) { echo "Wait for workers to finish" . PHP_EOL; $child_pid = pcntl_wait($status); if ($child_pid) { $worker_pos = array_search($child_pid, $worker_pool); unset($worker_pool[$worker_pos]); } } // Custom config foreach ($containers as $container) { echo "Apply custom config on: " . $container["lxc_name"] . PHP_EOL; # Allow running a reverse proxy in front of Hestia system( "lxc exec " . $container["lxc_name"] . ' -- bash -c "sed -i \'s/session.cookie_secure] = on\$/session.cookie_secure] = off/\' /usr/local/hestia/php/etc/php-fpm.conf"', ); # get rid off "mesg: ttyname failed: No such device" error system( "lxc exec " . $container["lxc_name"] . ' -- bash -c "sed -i -re \'s/^(mesg n)(.*)$/#\1\2/g\' /root/.profile"', ); # Use LE sandbox server, prevents hitting rate limits system( "lxc exec " . $container["lxc_name"] . ' -- bash -c "sed -i \'/LE_STAGING/d\' /usr/local/hestia/conf/hestia.conf"', ); system( "lxc exec " . $container["lxc_name"] . ' -- bash -c "echo \'LE_STAGING=\"yes\"\' >> /usr/local/hestia/conf/hestia.conf"', ); system("lxc exec " . $container["lxc_name"] . ' -- bash -c "service hestia restart"'); } echo "Hestia containers configured" . PHP_EOL;