| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304 |
- <?php
- declare(strict_types=1);
- namespace Hestia\System;
- use function Hestiacp\quoteshellarg\quoteshellarg;
- class HestiaApp
- {
- protected const TMPDIR_DOWNLOADS="/tmp/hestia-webapp";
- protected $phpsupport = false;
- public function __construct()
- {
- @mkdir(self::TMPDIR_DOWNLOADS);
- }
- public function run(string $cmd, $args, &$cmd_result=null): bool
- {
- $cli_script = realpath(HESTIA_DIR_BIN . $cmd);
- if (!str_starts_with((string) $cli_script, HESTIA_DIR_BIN)) {
- $errstr = "$cmd is trying to traverse outside of " . HESTIA_DIR_BIN;
- trigger_error($errstr);
- throw new \Exception($errstr);
- }
- $cli_script = '/usr/bin/sudo ' . quoteshellarg($cli_script);
-
- $cli_arguments = '';
- if (!empty($args) && is_array($args)) {
- foreach ($args as $arg) {
- $cli_arguments .= quoteshellarg((string)$arg) . ' ';
- }
- } else {
- $cli_arguments = quoteshellarg($args);
- }
- exec($cli_script . ' ' . $cli_arguments . ' 2>&1', $output, $exit_code);
- $result['code'] = $exit_code;
- $result['args'] = $cli_arguments;
- $result['raw'] = $output;
- $result['text'] = implode(PHP_EOL, $output);
- $result['json'] = json_decode($result['text'], true);
- $cmd_result = (object)$result;
- if ($exit_code > 0) {
- //log error message in nginx-error.log
- trigger_error($result['text']);
- //throw exception if command fails
- throw new \Exception($result['text']);
- }
- return ($exit_code === 0);
- }
- public function runUser(string $cmd, $args, &$cmd_result=null): bool
- {
- if (!empty($args) && is_array($args)) {
- array_unshift($args, $this->user());
- } else {
- $args = [$this->user(), $args];
- }
- return $this->run($cmd, $args, $cmd_result);
- }
- public function installComposer($version)
- {
- exec("curl https://composer.github.io/installer.sig", $output);
- $signature = implode(PHP_EOL, $output);
- if (empty($signature)) {
- throw new \Exception("Error reading composer signature");
- }
- $composer_setup = self::TMPDIR_DOWNLOADS . DIRECTORY_SEPARATOR . 'composer-setup-' . $signature . '.php';
- exec("wget https://getcomposer.org/installer --quiet -O " . quoteshellarg($composer_setup), $output, $return_code);
- if ($return_code !== 0) {
- throw new \Exception("Error downloading composer");
- }
- if ($signature !== hash_file('sha384', $composer_setup)) {
- unlink($composer_setup);
- throw new \Exception("Invalid composer signature");
- }
- $install_folder = $this->getUserHomeDir() . DIRECTORY_SEPARATOR . '.composer';
- if (!file_exists($install_folder)) {
- exec(HESTIA_CMD .'v-rebuild-user '.$this -> user(), $output, $return_code);
- if ($return_code !== 0) {
- throw new \Exception("Unable to rebuild user");
- }
- }
- $this->runUser('v-run-cli-cmd', ["/usr/bin/php", $composer_setup, "--quiet", "--install-dir=".$install_folder, "--filename=composer", "--$version" ], $status);
- unlink($composer_setup);
- if ($status->code !== 0) {
- throw new \Exception("Error installing composer");
- }
- }
- public function updateComposer($version)
- {
- $this->runUser('v-run-cli-cmd', ["composer", "selfupdate","--$version"]);
- }
- public function runComposer($args, &$cmd_result=null, $version=2): bool
- {
- $composer = $this->getUserHomeDir() . DIRECTORY_SEPARATOR . '.composer' . DIRECTORY_SEPARATOR . 'composer';
- if (!is_file($composer)) {
- $this->installComposer($version);
- } else {
- $this->updateComposer($version);
- }
- if (!empty($args) && is_array($args)) {
- array_unshift($args, 'composer');
- } else {
- $args = ['composer', $args];
- }
- return $this->runUser('v-run-cli-cmd', $args, $cmd_result);
- }
- public function runWp($args, &$cmd_result=null): bool
- {
- $wp = $this->getUserHomeDir() . DIRECTORY_SEPARATOR . '.wp-cli' . DIRECTORY_SEPARATOR . 'wp';
- if (!is_file($wp)) {
- $this -> runUser('v-add-user-wp-cli', []);
- } else {
- $this->runUser('v-run-cli-cmd', [$wp, 'cli', 'update']);
- }
- array_unshift($args, $wp);
- return $this->runUser('v-run-cli-cmd', $args, $cmd_result);
- }
- // Logged in user
- public function realuser(): string
- {
- return $_SESSION['user'];
- }
- // Effective user
- public function user(): string
- {
- $user = $this->realuser();
- if ($_SESSION['userContext'] === 'admin' && !empty($_SESSION['look'])) {
- $user = $_SESSION['look'];
- }
- if (strpos($user, DIRECTORY_SEPARATOR) !== false) {
- throw new \Exception("illegal characters in username");
- }
- return $user;
- }
- public function getUserHomeDir()
- {
- $info = posix_getpwnam($this->user());
- return $info['dir'];
- }
- public function userOwnsDomain(string $domain): bool
- {
- return $this->runUser('v-list-web-domain', [$domain, 'json']);
- }
- public function checkDatabaseLimit(){
- $status = $this -> runUser('v-list-user', ['json'], $result);
- $result -> json[$this -> user()];
- if($result -> json[$this -> user()]['DATABASES'] != "unlimited" ){
- if($result -> json[$this -> user()]['DATABASES'] - $result -> json[$this -> user()]['U_DATABASES'] < 1){
- return false;
- }
- }
- return true;
- }
- public function databaseAdd(string $dbname, string $dbuser, string $dbpass, string $charset = 'utf8mb4')
- {
- $v_password = tempnam("/tmp", "hst");
- $fp = fopen($v_password, "w");
- fwrite($fp, $dbpass."\n");
- fclose($fp);
- $status = $this->runUser('v-add-database', [$dbname, $dbuser, $v_password, 'mysql', 'localhost', $charset]);
- if(!$status){
- $this->errors[] = _('Unable to add database!');
- }
- unlink($v_password);
- return $status;
- }
-
- public function getCurrentBackendTemplate(string $domain){
- $status = $this->runUser('v-list-web-domain', [$domain, 'json'],$return_message);
- $version = $return_message -> json[$domain]['BACKEND'];
- $test= preg_match('/^.*PHP-([0-9])\_([0-9])/',$version, $match);
- return $match[1].'.'.$match[2];
-
- }
-
- public function changeWebTemplate(string $domain, string $template)
- {
- $status = $this->runUser('v-change-web-domain-tpl', [$domain, $template]);
- }
- public function changeBackendTemplate(string $domain, string $template)
- {
- $status = $this->runUser('v-change-web-domain-backend-tpl', [$domain, $template]);
- }
- public function listSuportedPHP()
- {
- if (!$this -> phpsupport) {
- $status = $this -> run('v-list-sys-php', 'json', $result);
- $this -> phpsupport = $result -> json;
- }
- return $this -> phpsupport;
- }
- /*
- Return highest available supported php version
- Eg: Package requires: 7.3 or 7.4 and system has 8.0 and 7.4 it will return 7.4
- Package requires: 8.0 or 8.1 and system has 8.0 and 7.4 it will return 8.0
- Package requires: 7.4 or 8.0 and system has 8.0 and 7.4 it will return 8.0
- If package isn't supported by the available php version false will returned
- */
- public function getSupportedPHP($support)
- {
- $versions = $this -> listSuportedPHP();
- $supported = false;
- $supported_versions = array();
- foreach ($versions as $version) {
- if (in_array($version, $support)) {
- $supported = true;
- $supported_versions[] = $version;
- }
- }
- if($supported){
- return $supported_versions;
- }else{
- return false;
- }
- }
- public function getWebDomainIp(string $domain)
- {
- $this->runUser('v-list-web-domain', [$domain, 'json'], $result);
- $ip = $result->json[$domain]['IP'];
- return filter_var($ip, FILTER_VALIDATE_IP);
- }
- public function getWebDomainPath(string $domain)
- {
- return Util::join_paths($this->getUserHomeDir(), "web", $domain);
- }
- public function downloadUrl(string $src, $path=null, &$result=null)
- {
- if (strpos($src, 'http://') !== 0 &&
- strpos($src, 'https://')!== 0) {
- return false;
- }
- exec("/usr/bin/wget --tries 3 --timeout=30 --no-dns-cache -nv " . quoteshellarg($src). " -P " . quoteshellarg(self::TMPDIR_DOWNLOADS) . ' 2>&1', $output, $return_var);
- if ($return_var !== 0) {
- return false;
- }
- if (!preg_match('/URL:\s*(.+?)\s*\[(.+?)\]\s*->\s*"(.+?)"/', implode(PHP_EOL, $output), $matches)) {
- return false;
- }
- if (empty($matches) || count($matches) != 4) {
- return false;
- }
- $status['url'] = $matches[1];
- $status['file'] = $matches[3];
- $result = (object)$status;
- return true;
- }
- public function archiveExtract(string $src, string $path, $skip_components=null)
- {
- if (empty($path)) {
- throw new \Exception("Error extracting archive: missing target folder");
- }
- if (realpath($src)) {
- $archive_file = $src;
- } else {
- if (!$this->downloadUrl($src, null, $download_result)) {
- throw new \Exception("Error downloading archive");
- }
- $archive_file = $download_result->file;
- }
-
- $result = $this->runUser('v-extract-fs-archive', [$archive_file, $path, null, $skip_components]);
- unlink($archive_file);
-
- return $result;
- }
- }
|