allocationSelectionService = $allocationSelectionService; $this->allocationRepository = $allocationRepository; $this->configurationStructureService = $configurationStructureService; $this->connection = $connection; $this->findViableNodesService = $findViableNodesService; $this->validatorService = $validatorService; $this->eggRepository = $eggRepository; $this->repository = $repository; $this->serverVariableRepository = $serverVariableRepository; $this->daemonServerRepository = $daemonServerRepository; $this->serverDeletionService = $serverDeletionService; } /** * Create a server on the Panel and trigger a request to the Daemon to begin the server * creation process. This function will attempt to set as many additional values * as possible given the input data. For example, if an allocation_id is passed with * no node_id the node_is will be picked from the allocation. * * @param array $data * @param \Pterodactyl\Models\Objects\DeploymentObject|null $deployment * @return \Pterodactyl\Models\Server * * @throws \Throwable * @throws \Pterodactyl\Exceptions\DisplayException * @throws \Illuminate\Validation\ValidationException * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException * @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableNodeException * @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableAllocationException */ public function handle(array $data, DeploymentObject $deployment = null): Server { $this->connection->beginTransaction(); // If a deployment object has been passed we need to get the allocation // that the server should use, and assign the node from that allocation. if ($deployment instanceof DeploymentObject) { $allocation = $this->configureDeployment($data, $deployment); $data['allocation_id'] = $allocation->id; $data['node_id'] = $allocation->node_id; } // Auto-configure the node based on the selected allocation // if no node was defined. if (is_null(Arr::get($data, 'node_id'))) { $data['node_id'] = $this->getNodeFromAllocation($data['allocation_id']); } if (is_null(Arr::get($data, 'nest_id'))) { /** @var \Pterodactyl\Models\Egg $egg */ $egg = $this->eggRepository->setColumns(['id', 'nest_id'])->find(Arr::get($data, 'egg_id')); $data['nest_id'] = $egg->nest_id; } $eggVariableData = $this->validatorService ->setUserLevel(User::USER_LEVEL_ADMIN) ->handle(Arr::get($data, 'egg_id'), Arr::get($data, 'environment', [])); // Create the server and assign any additional allocations to it. $server = $this->createModel($data); $this->storeAssignedAllocations($server, $data); $this->storeEggVariables($server, $eggVariableData); // Due to the design of the Daemon, we need to persist this server to the disk // before we can actually create it on the Daemon. // // If that connection fails out we will attempt to perform a cleanup by just // deleting the server itself from the system. $this->connection->commit(); $structure = $this->configurationStructureService->handle($server); try { $this->daemonServerRepository->setServer($server)->create($structure); } catch (DaemonConnectionException $exception) { $this->serverDeletionService->withForce(true)->handle($server); throw $exception; } return $server; } /** * Gets an allocation to use for automatic deployment. * * @param array $data * @param \Pterodactyl\Models\Objects\DeploymentObject $deployment * * @return \Pterodactyl\Models\Allocation * @throws \Pterodactyl\Exceptions\DisplayException * @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableAllocationException * @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableNodeException */ private function configureDeployment(array $data, DeploymentObject $deployment): Allocation { $nodes = $this->findViableNodesService->setLocations($deployment->getLocations()) ->setDisk(Arr::get($data, 'disk')) ->setMemory(Arr::get($data, 'memory')) ->handle(); return $this->allocationSelectionService->setDedicated($deployment->isDedicated()) ->setNodes($nodes) ->setPorts($deployment->getPorts()) ->handle(); } /** * Store the server in the database and return the model. * * @param array $data * @return \Pterodactyl\Models\Server * * @throws \Pterodactyl\Exceptions\Model\DataValidationException */ private function createModel(array $data): Server { $uuid = $this->generateUniqueUuidCombo(); /** @var \Pterodactyl\Models\Server $model */ $model = $this->repository->create([ 'external_id' => Arr::get($data, 'external_id'), 'uuid' => $uuid, 'uuidShort' => substr($uuid, 0, 8), 'node_id' => Arr::get($data, 'node_id'), 'name' => Arr::get($data, 'name'), 'description' => Arr::get($data, 'description') ?? '', 'skip_scripts' => Arr::get($data, 'skip_scripts') ?? isset($data['skip_scripts']), 'suspended' => false, 'owner_id' => Arr::get($data, 'owner_id'), 'memory' => Arr::get($data, 'memory'), 'swap' => Arr::get($data, 'swap'), 'disk' => Arr::get($data, 'disk'), 'io' => Arr::get($data, 'io'), 'cpu' => Arr::get($data, 'cpu'), 'threads' => Arr::get($data, 'threads'), 'oom_disabled' => Arr::get($data, 'oom_disabled') ?? true, 'allocation_id' => Arr::get($data, 'allocation_id'), 'nest_id' => Arr::get($data, 'nest_id'), 'egg_id' => Arr::get($data, 'egg_id'), 'startup' => Arr::get($data, 'startup'), 'image' => Arr::get($data, 'image'), 'database_limit' => Arr::get($data, 'database_limit') ?? 0, 'allocation_limit' => Arr::get($data, 'allocation_limit') ?? 0, 'backup_limit' => Arr::get($data, 'backup_limit') ?? 0, ]); return $model; } /** * Configure the allocations assigned to this server. * * @param \Pterodactyl\Models\Server $server * @param array $data */ private function storeAssignedAllocations(Server $server, array $data) { $records = [$data['allocation_id']]; if (isset($data['allocation_additional']) && is_array($data['allocation_additional'])) { $records = array_merge($records, $data['allocation_additional']); } $this->allocationRepository->updateWhereIn('id', $records, [ 'server_id' => $server->id, ]); } /** * Process environment variables passed for this server and store them in the database. * * @param \Pterodactyl\Models\Server $server * @param \Illuminate\Support\Collection $variables */ private function storeEggVariables(Server $server, Collection $variables) { $records = $variables->map(function ($result) use ($server) { return [ 'server_id' => $server->id, 'variable_id' => $result->id, 'variable_value' => $result->value, ]; })->toArray(); if (! empty($records)) { $this->serverVariableRepository->insert($records); } } /** * Get the node that an allocation belongs to. * * @param int $id * @return int * * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ private function getNodeFromAllocation(int $id): int { /** @var \Pterodactyl\Models\Allocation $allocation */ $allocation = $this->allocationRepository->setColumns(['id', 'node_id'])->find($id); return $allocation->node_id; } /** * Create a unique UUID and UUID-Short combo for a server. * * @return string */ private function generateUniqueUuidCombo(): string { $uuid = Uuid::uuid4()->toString(); if (! $this->repository->isUniqueUuidCombo($uuid, substr($uuid, 0, 8))) { return $this->generateUniqueUuidCombo(); } return $uuid; } }