| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366 |
- <?php
- declare(strict_types=1);
- namespace Hestia\System;
- use function Hestiacp\quoteshellarg\quoteshellarg;
- class HestiaApp {
- /** @var string[] */
- public $errors;
- 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, $data = []): bool {
- $composer =
- $this->getUserHomeDir() .
- DIRECTORY_SEPARATOR .
- ".composer" .
- DIRECTORY_SEPARATOR .
- "composer";
- if (!is_file($composer)) {
- $this->installComposer($data["version"]);
- } else {
- $this->updateComposer($data["version"]);
- }
- if (empty($data["php_version"])) {
- $data["php_version"] = "";
- }
- if (!empty($args) && is_array($args)) {
- array_unshift($args, "php" . $data["php_version"], $composer);
- } else {
- $args = ["php" . $data["php_version"], $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", "--yes"]);
- }
- 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 $dbtype = "mysql",
- 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,
- $dbtype,
- "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"];
- if (!empty($version)) {
- if ($version != "default") {
- $test = preg_match("/^.*PHP-([0-9])\_([0-9])/", $version, $match);
- return $match[1] . "." . $match[2];
- } else {
- $supported = $this->run("v-list-sys-php", "json", $result);
- return $result->json[0];
- }
- } else {
- $supported = $this->run("v-list-sys-php", "json", $result);
- return $result->json[0];
- }
- }
- 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 = [];
- 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;
- }
- public function cleanupTmpDir(): void {
- $files = glob(self::TMPDIR_DOWNLOADS . "/*");
- foreach ($files as $file) {
- if (is_file($file)) {
- unlink($file);
- }
- }
- }
- public function __destruct() {
- $this->cleanupTmpDir();
- }
- }
|