http://modulesgarden.com * CONTACT -> contact@modulesgarden.com * * * This software is furnished under a license and may be used and copied * only in accordance with the terms of such license and with the * inclusion of the above copyright notice. This software or any other * copies thereof may not be provided or otherwise made available to any * other person. No title to and ownership of the software is hereby * transferred. * * **********************************************************************/ namespace ModulesGarden\Servers\ProxmoxVps\App\Http\Actions; use MGProvision\Proxmox\v2\models\Kvm; use MGProvision\Proxmox\v2\models\Node; use MGProvision\Proxmox\v2\repository\ClusterResourcesRepository; use ModulesGarden\ProxmoxAddon as addon; use ModulesGarden\ProxmoxAddon\App\Configuration\Addon\Update\AddonUpgradeService; use ModulesGarden\ProxmoxAddon\App\Models\VmIpAddress; use ModulesGarden\ProxmoxAddon\App\Services\ApiService; use ModulesGarden\ProxmoxAddon\App\Services\Utility; use ModulesGarden\ProxmoxAddon\App\Services\Vps\HostingService; use ModulesGarden\ProxmoxAddon\App\Services\Vps\NetworkService; use ModulesGarden\ProxmoxAddon\App\Services\Vps\ProductService; use ModulesGarden\ProxmoxAddon\App\Services\Vps\UserService; use ModulesGarden\ProxmoxAddon\Core\Queue\Models\Job; use ModulesGarden\Servers\ProxmoxVps\App\Enum\CustomField; use ModulesGarden\Servers\ProxmoxVps\App\Helpers\AppParams; use ModulesGarden\Servers\ProxmoxVps\App\Helpers\ProxmoxAddonValidator; use ModulesGarden\Servers\ProxmoxVps\Core\App\Controllers\Instances\AddonController; use ModulesGarden\Servers\ProxmoxVps\Core\Models\Whmcs\Server; use ModulesGarden\Servers\ProxmoxVps\Core\UI\Traits\WhmcsParams; use ModulesGarden\Servers\ProxmoxVps\App\Enum\ConfigurableOption; class CreateAccount extends AddonController { use WhmcsParams; use ProductService; use ApiService; use UserService; use HostingService; use addon\App\Traits\Vps\SnippetTrait; private $errors = []; /** * @var AddonUpgradeService */ private $addonUpgradeService; /** * @var Kvm|null */ private $templateVm; public function execute($params = null) { if(!ProxmoxAddonValidator::isInstalled()){ return ProxmoxAddonValidator::failAsString(); } (new AppParams())->initFromWhmcsParams(); $this->addonUpgradeService = new AddonUpgradeService(); $this->addonUpgradeService->run(); try { //init nameservers $this->initNameServers(); //Validate $this->validate(); //Clone validate if ($this->configuration()->isQemu()) { $this->cloneValidate(); } //Check free resources $this->checkResources(); //update hostname if($params['customfields']['proxmoxHostname']){ $this->hosting()->domain = $params['customfields']['proxmoxHostname']; $this->hosting()->save(); } //throw errors if ($this->errors) { throw new \Exception(implode(", ", $this->errors)); } //Create User if (!$this->isUser()) { $this->userCreate(); }elseif($this->isUser() ){ $this->createUserIfNotExist($this->getUser()); } //Assing ip if (!Utility::isIpManagerProxmoxVPSIntegration()) { if ($this->isCustomIpRequest() && $this->hasCustomIp()) { $this->addCustomIp(); } else { $networkService = new NetworkService(); list($requestIPv4, $requestIPv6) = $networkService->getIpAddressesRequest(); $node = $this->getNode()->getNode(); $networkService->hasIp($requestIPv4, $requestIPv6, $node) ->addIp($requestIPv4, $requestIPv6, $node); $this->customFieldUpdate(CustomField::NODE, $node); } } //automaticaly configure administartor username for window if($this->templateVm instanceof Kvm && $this->configuration()->isAgent() && $this->configuration()->isAgentServicePassword() && !$this->configuration()->isAgentTemplateUser() && preg_match('/w/', $this->templateVm->config()['ostype'])){ $ciuser = $this->templateVm->config()['ciuser']; $this->hosting()->update(['username' => $ciuser ?: 'Administrator' ]); } elseif($this->configuration()->isQemu() && $this->configuration()->getCiuser()){ $this->hosting()->update(['username' =>$this->configuration()->getCiuser() ]); } //hosting limit $this->saveUsageLimit(); //Create task if ($this->configuration()->isLxc()) { $taskName = addon\App\Jobs\Vps\CreateLxcJob::class; } else { if ($this->configuration()->isQemu()) { $taskName = addon\App\Jobs\Vps\CreateQemuJob::class; $osTemplate = $this->getWhmcsConfigOption(ConfigurableOption::OS_TEMPLATE, $this->configuration()->getOsTemplate()); if (!empty($osTemplate) && $osTemplate != "0") { $taskName = addon\App\Jobs\Vps\CloneQemuJob::class; } } } $arguments = [ 'hostingId' => $this->getWhmcsParamByKey('serviceid') ]; if($this->configuration()->isQemu() && $this->hasSnippet()){ $parent = addon\Core\Helper\queue(addon\App\Jobs\Vps\CreateSnippet::class, $arguments, null, "hosting", $this->getWhmcsParamByKey("serviceid")); } addon\Core\Helper\queue($taskName, $arguments, $parent->id, "hosting", $this->getWhmcsParamByKey("serviceid")); return "success"; } catch (\Exception $ex) { if (addon\App\Models\ModuleSettings::isDebug()) { logModuleCall( 'ProxmoxVps', __CLASS__, [], null, $ex->getMessage() . " " . $ex->getTraceAsString() ); } return $ex->getMessage(); } } private function validate() { //vmid if ($this->getWhmcsCustomField(CustomField::VMID)) { $this->errors[] = "Custom Field \"VMID\" is not empty"; } //node if ($this->getWhmcsCustomField(CustomField::NODE)) { $this->errors[] = "Custom Field \"Node\" is not empty"; } if (!in_array($this->configuration()->getVirtualization(), ["qemu", "lxc"])) { $this->errors[] = 'Unknown virtualization type: ' . $this->configuration()->getVirtualization(); } //Network bridge if (!$this->configuration()->getBridge()) { $this->errors[] = "''Bridge' - is empty"; } //lxc if ($this->configuration()->isLxc()) { //OS Template if (!$this->getWhmcsConfigOption(ConfigurableOption::OS_TEMPLATE, $this->configuration()->getOsTemplate()) ) { $this->errors[] = "'OS Template' - is empty"; } //Storage if (!$this->configuration()->getStorage()) { $this->errors[] = "'Default Storage' - is empty"; } $taskName = addon\App\Jobs\Vps\CreateLxcJob::class . '@handle'; } else { if ($this->configuration()->isQemu()) { //searchdomain if ($this->configuration()->getSearchdomain() && !preg_match('/^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$/', $this->configuration()->getSearchdomain())) { $this->errors[] = sprintf('DNS Domain \'%s\' is invalid', $this->configuration()->getSearchdomain()); } //SSH Key if ($this->getWhmcsParamByKey('customfields')['sshkeys'] && !preg_match('/ssh\-/', $this->getWhmcsParamByKey('customfields')['sshkeys'])) { $this->errors[] = ' SSH Public Key is invalid'; } $taskName = addon\App\Jobs\Vps\CreateQemuJob::class . '@handle'; $osTemplate = $this->getWhmcsConfigOption(ConfigurableOption::OS_TEMPLATE, $this->configuration()->getOsTemplate() ); //clone if (!empty($osTemplate) && $osTemplate != "0") { $taskName = addon\App\Jobs\Vps\CloneQemuJob::class . '@handle'; } } } $query = Job::where("job", $taskName)->whereIn("status", ['waiting', "running", ""])->where("rel_id", $this->getWhmcsParamByKey("serviceid"))->where("rel_type", "hosting") ->orWhere("job", $taskName)->where("status", 'error')->where("rel_id", $this->getWhmcsParamByKey("serviceid"))->where("rel_type", "hosting")->where("retry_count","<",100); if ( $query->count() ) { $this->errors[] = "Job 'create' already exist"; } //Name servers if ($this->configuration()->isLxc() || $this->configuration()->isCloudInitServiceNameservers()) { $ns = []; for ($i = 1; $i <= 2; $i++) { $n = trim($this->hosting()->{"ns{$i}"}); if (!empty($n) && !filter_var($n, FILTER_VALIDATE_IP)) { $n = gethostbyname($n); } if (!empty($n) && !filter_var($n, FILTER_VALIDATE_IP)) { $ns[] = "'{$n}'"; } } if (!empty($ns)) { $this->errors[] = sprintf(" Name Server value: %s does not look like a valid IP Address", implode(", ", $ns)); } } //Disk size $diskSize = $this->getWhmcsConfigOption(ConfigurableOption::DISK_SIZE); if ($diskSize && $this->configuration()->getDiskUnit() == "mb" && $diskSize < 1024) { $this->errors[] = sprintf("Disk size of %sMB cannot be created", $diskSize); } $rate = $this->getWhmcsConfigOption(addon\App\Enum\Vps\ConfigurableOption::NETWORK_RATE) ? $this->getWhmcsConfigOption(addon\App\Enum\Vps\ConfigurableOption::NETWORK_RATE) :$this->configuration()->getRate(); if ($rate && !is_numeric($rate)) { $this->errors[] = sprintf("Network Rate - invalid format: %s ", $rate) ; } } private function initNameServers() { $this->setHostingId($this->getWhmcsParamByKey("serviceid")); if ($this->configuration()->isServerNameservers()) { $server = Server::select('id', 'nameserver1ip', 'nameserver2ip')->findOrFail($this->getWhmcsParamByKey('serverid')); $this->hosting()->ns1 = trim($server->nameserver1ip); $this->hosting()->ns2 = trim($server->nameserver2ip); $this->hosting()->save(); } else { if ($this->configuration()->isLxc() || $this->configuration()->isCloudInitServiceNameservers()) { $domain = $this->getWhmcsCustomField(CustomField::HOSTNAME, $this->getWhmcsParamByKey('domain')); $update = []; for ($i = 1; $i <= 2; $i++) { $n = trim($this->hosting()->{"ns{$i}"}); if (!empty($n) && !filter_var($n, FILTER_VALIDATE_IP)) { $host = preg_match('/\./', $n) ? $n : "{$n}.{$domain}"; $n = gethostbyname($host); if (!$n || !filter_var($n, FILTER_VALIDATE_IP)) { throw new \Exception(sprintf("Nameserver '%s' cannot be resolved", $host)); } } if (!empty($n) && filter_var($n, FILTER_VALIDATE_IP)) { $update['ns' . $i] = $n; $this->hosting()->{"ns{$i}"} = $n; } } if ($update) { $this->hosting()->update($update); } } } } private function isCustomIpRequest() { //if there is custom ip on dedicated ip or assinged ips $model = $this->getWhmcsParamByKey('model'); if (!$model) { $model = \WHMCS\Service\Service::where("id", $this->getWhmcsParamByKey('serviceid'))->first(); } if (!$model->dedicatedip && !$model->assignedips) { return false; } $ips = $this->getCustomIp(); if (!$ips) { return false; } foreach ($ips as $k => $ip) { if (!filter_var((string)$ip, FILTER_VALIDATE_IP)) { throw new \Exception(sprintf("IP Address '%s' is not valid", $ip)); } } return true; } private function getCustomIp() { $ips = []; $model = $this->getWhmcsParamByKey('model'); if (!$model) { $model = \WHMCS\Service\Service::where("id", $this->getWhmcsParamByKey('serviceid'))->first(); } if ($model->dedicatedip) { $ips[] = $model->dedicatedip; } if ($model->assignedips) { $ex = explode(PHP_EOL, $model->assignedips); foreach ($ex as $ip) { if (!$ip) { continue; } $ips[] = str_replace(["\r", "\n"], ["", ""], $ip); } } return array_diff($ips, [""]); } private function hasCustomIp() { $nodeName = $this->getNode()->getNode(); $ipAddresses = $this->getCustomIp(); $virtualization = $this->configuration()->isQemu() ? "KVM" : "LXC"; $ipCollections = addon\App\Models\IpAddress::where('hosting_id', '0') ->where('private', '0') ->whereIn('ip', $ipAddresses) ->whereIn('sid', [$this->getWhmcsParamByKey('serverid'), '0']) ->whereIn('visualization', [$virtualization, 'Auto']) ->whereIn('node', [$nodeName, '0', ""]); $count = $ipCollections->count(); return $count == count($ipAddresses); } private function addCustomIp() { $nodeName = $this->getNode()->getNode(); $ipAddresses = $this->getCustomIp(); $virtualization = $this->configuration()->isQemu() ? "KVM" : "LXC"; addon\App\Models\IpAddress::where('hosting_id', '0') ->where('private', '0') ->whereIn('ip', $ipAddresses) ->whereIn('sid', [$this->getWhmcsParamByKey('serverid'), '0']) ->whereIn('visualization', [$virtualization, 'Auto']) ->whereIn('node', [$nodeName, '0', ""]) ->update(["hosting_id" => $this->getWhmcsParamByKey('serviceid'), 'last_check' => Utility::timeStamp()]); //get ips $collection = addon\App\Models\IpAddress::where('hosting_id', $this->getWhmcsParamByKey('serviceid')) ->whereNotIn('ip', function ($query) { $query->select('ip')->from((new VmIpAddress)->getTable()); }); foreach ($collection->get() as $ip) { /*@var $ip VmIpAddress */ $newIp = new VmIpAddress(); $ipData = $ip->toArray(); $ipData['hosting_id'] = $this->getWhmcsParamByKey('serviceid'); $ipData['server_id'] = $this->getWhmcsParamByKey('serverid'); $newIp->fill($ipData)->save(); } return $this; } private function checkResources() { if (!$this->configuration()->isCheckResources()) { return; } //get free space $storage = $this->configuration()->isQemu() ? $this->configuration()->getDiskStorage() : $this->configuration()->getStorage(); $resurces = $this->getNode()->getFreeSpace($storage); //Memory if ($this->getWhmcsConfigOption(ConfigurableOption::MEMORY)) { $memoryBytes = $this->getWhmcsConfigOption(ConfigurableOption::MEMORY); Utility::unitFormat($memoryBytes, $this->configuration()->getMemoryUnit(), 'bytes'); } else { $memoryBytes = $this->configuration()->getMemory(); Utility::unitFormat($memoryBytes, "mb", 'bytes'); } //Validation if ($memoryBytes && $resurces['memory'] && $memoryBytes > $resurces['memory']) { $this->errors[] = sprintf("Unable to allocate %s of Memory. Memory available: %s", addon\App\Libs\Format::convertBytes($memoryBytes), addon\App\Libs\Format::convertBytes($resurces['memory'])); } //SWAP if ($this->configuration()->isLxc()) { if ($this->getWhmcsConfigOption(ConfigurableOption::SWAP)) { $swapBytes = $this->getWhmcsConfigOption(ConfigurableOption::SWAP); Utility::unitFormat($swapBytes, strtolower($this->configuration()->getSwapUnit()), 'bytes'); } else { $swapBytes = $this->configuration()->getSwap(); Utility::unitFormat($swapBytes, "mb", 'bytes'); } //Validation if ($swapBytes && $resurces['swap'] && $swapBytes > $resurces['swap']) { $this->errors[] = sprintf("Unable to allocate %s of SWAP. SWAP available: %s", addon\App\Libs\Format::convertBytes($swapBytes), addon\App\Libs\Format::convertBytes($resurces['swap'])); } } //Disk if ($this->getWhmcsConfigOption(ConfigurableOption::DISK_SIZE)) { $diskBytes = $this->getWhmcsConfigOption(ConfigurableOption::DISK_SIZE); Utility::unitFormat($diskBytes, $this->configuration()->getDiskUnit(), 'bytes'); } else { $diskBytes = $this->configuration()->getDiskSize(); Utility::unitFormat($diskBytes, "gb", 'bytes'); } //Validation if ($diskBytes && $resurces['storage'] && $diskBytes > $resurces['storage']) { $this->errors[] = sprintf("Unable to allocate %s of Disk Space. Disk Space available: %s", addon\App\Libs\Format::convertBytes($diskBytes), addon\App\Libs\Format::convertBytes($resurces['storage'])); } } private function cloneValidate() { $osTemplate = $this->getWhmcsConfigOption(ConfigurableOption::OS_TEMPLATE, $this->configuration()->getOsTemplate() ); if (empty($osTemplate) && $osTemplate == "0") { return; } //Support for configurable options i.e vmname|OS Name if (is_string($osTemplate) && !preg_match('/\//', $osTemplate)) { $templateNode = $this->getNode()->getNode(); $clusterRepository = new ClusterResourcesRepository(); $clusterRepository->setApi($this->api()); if(!$this->configuration()->isOsTemplatesInAllNodes()){ $clusterRepository->findByNodes([$templateNode]); } $clusterRepository->findKvmTemplate(); foreach ($clusterRepository->fetch() as $resurce) { if ($resurce->getName() == $osTemplate ) { $templateVmid = $resurce->getVmid(); $templateNode = $resurce->getNode(); break; } } if (!$templateVmid) { $this->errors[] = sprintf("Unable to find KVM template: %s on node: %s", $osTemplate, $this->getNode()->getNode()); return; } //Support for configurable options like nodename/vmid|OS Name } else { if (preg_match('/\//', $osTemplate)) { list($templateNode, $templateVmid) = explode("/", $osTemplate); } } if (!in_array($templateNode, (array)$this->api()->get_node_list())) { $this->errors[] = sprintf("Unable to find node %s on cluster %s", $templateNode, $this->api()->getPveHostname()); return; } //template exist on node? $node = new Node($templateNode); $node->setApi($this->api()); if (!$node->hasKvm($templateVmid)) { $this->errors[] = sprintf("KVM template VMID '%s' does not exist on node '%s'", $templateVmid, $templateNode); return; } //Disk size validation $this->templateVm = new Kvm($templateNode, $templateVmid); $this->templateVm->setApi($this->api()); //Disk $diskSize = $this->configuration()->getDiskSize(); if ($this->getWhmcsConfigOption(ConfigurableOption::DISK_SIZE)) { $diskSize = $this->getWhmcsConfigOption(ConfigurableOption::DISK_SIZE); Utility::unitFormat($diskSize, $this->configuration()->getDiskUnit(), 'bytes'); }else{ Utility::unitFormat($diskSize, "gb", 'bytes'); } if ($this->templateVm->getMasterHardDisk()->getBytes() > $diskSize) { $this->errors[] = sprintf('Downgrading disk size is restricted. Request Disk size: %s, VM disk size: %s', addon\App\Libs\Format::convertBytes($diskSize), addon\App\Libs\Format::convertBytes($this->templateVm->getMasterHardDisk()->getBytes())); } } }