Path: blob/1.0-develop/app/Services/Servers/ServerCreationService.php
10276 views
<?php12namespace Pterodactyl\Services\Servers;34use Ramsey\Uuid\Uuid;5use Illuminate\Support\Arr;6use Pterodactyl\Models\Egg;7use Pterodactyl\Models\User;8use Webmozart\Assert\Assert;9use Pterodactyl\Models\Server;10use Illuminate\Support\Collection;11use Pterodactyl\Models\Allocation;12use Illuminate\Database\ConnectionInterface;13use Pterodactyl\Models\Objects\DeploymentObject;14use Pterodactyl\Repositories\Eloquent\ServerRepository;15use Pterodactyl\Repositories\Wings\DaemonServerRepository;16use Pterodactyl\Services\Deployment\FindViableNodesService;17use Pterodactyl\Repositories\Eloquent\ServerVariableRepository;18use Pterodactyl\Services\Deployment\AllocationSelectionService;19use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;2021class ServerCreationService22{23/**24* ServerCreationService constructor.25*/26public function __construct(27private AllocationSelectionService $allocationSelectionService,28private ConnectionInterface $connection,29private DaemonServerRepository $daemonServerRepository,30private FindViableNodesService $findViableNodesService,31private ServerRepository $repository,32private ServerDeletionService $serverDeletionService,33private ServerVariableRepository $serverVariableRepository,34private VariableValidatorService $validatorService,35) {36}3738/**39* Create a server on the Panel and trigger a request to the Daemon to begin the server40* creation process. This function will attempt to set as many additional values41* as possible given the input data. For example, if an allocation_id is passed with42* no node_id the node_is will be picked from the allocation.43*44* @throws \Throwable45* @throws \Pterodactyl\Exceptions\DisplayException46* @throws \Illuminate\Validation\ValidationException47* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException48* @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableNodeException49* @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableAllocationException50*/51public function handle(array $data, ?DeploymentObject $deployment = null): Server52{53// If a deployment object has been passed we need to get the allocation54// that the server should use, and assign the node from that allocation.55if ($deployment instanceof DeploymentObject) {56$allocation = $this->configureDeployment($data, $deployment);57$data['allocation_id'] = $allocation->id;58$data['node_id'] = $allocation->node_id;59}6061// Auto-configure the node based on the selected allocation62// if no node was defined.63if (empty($data['node_id'])) {64Assert::false(empty($data['allocation_id']), 'Expected a non-empty allocation_id in server creation data.');6566$data['node_id'] = Allocation::query()->findOrFail($data['allocation_id'])->node_id;67}6869if (empty($data['nest_id'])) {70Assert::false(empty($data['egg_id']), 'Expected a non-empty egg_id in server creation data.');7172$data['nest_id'] = Egg::query()->findOrFail($data['egg_id'])->nest_id;73}7475$eggVariableData = $this->validatorService76->setUserLevel(User::USER_LEVEL_ADMIN)77->handle(Arr::get($data, 'egg_id'), Arr::get($data, 'environment', []));7879// Due to the design of the Daemon, we need to persist this server to the disk80// before we can actually create it on the Daemon.81//82// If that connection fails out we will attempt to perform a cleanup by just83// deleting the server itself from the system.84/** @var Server $server */85$server = $this->connection->transaction(function () use ($data, $eggVariableData) {86// Create the server and assign any additional allocations to it.87$server = $this->createModel($data);8889$this->storeAssignedAllocations($server, $data);90$this->storeEggVariables($server, $eggVariableData);9192return $server;93}, 5);9495try {96$this->daemonServerRepository->setServer($server)->create(97Arr::get($data, 'start_on_completion', false) ?? false98);99} catch (DaemonConnectionException $exception) {100$this->serverDeletionService->withForce()->handle($server);101102throw $exception;103}104105return $server;106}107108/**109* Gets an allocation to use for automatic deployment.110*111* @throws \Pterodactyl\Exceptions\DisplayException112* @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableAllocationException113* @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableNodeException114*/115private function configureDeployment(array $data, DeploymentObject $deployment): Allocation116{117/** @var Collection $nodes */118$nodes = $this->findViableNodesService->setLocations($deployment->getLocations())119->setDisk(Arr::get($data, 'disk'))120->setMemory(Arr::get($data, 'memory'))121->handle();122123return $this->allocationSelectionService->setDedicated($deployment->isDedicated())124->setNodes($nodes->pluck('id')->toArray())125->setPorts($deployment->getPorts())126->handle();127}128129/**130* Store the server in the database and return the model.131*132* @throws \Pterodactyl\Exceptions\Model\DataValidationException133*/134private function createModel(array $data): Server135{136$uuid = $this->generateUniqueUuidCombo();137138/** @var Server $model */139$model = $this->repository->create([140'external_id' => Arr::get($data, 'external_id'),141'uuid' => $uuid,142'uuidShort' => substr($uuid, 0, 8),143'node_id' => Arr::get($data, 'node_id'),144'name' => Arr::get($data, 'name'),145'description' => Arr::get($data, 'description') ?? '',146'status' => Server::STATUS_INSTALLING,147'skip_scripts' => Arr::get($data, 'skip_scripts') ?? isset($data['skip_scripts']),148'owner_id' => Arr::get($data, 'owner_id'),149'memory' => Arr::get($data, 'memory'),150'swap' => Arr::get($data, 'swap'),151'disk' => Arr::get($data, 'disk'),152'io' => Arr::get($data, 'io'),153'cpu' => Arr::get($data, 'cpu'),154'threads' => Arr::get($data, 'threads'),155'oom_disabled' => Arr::get($data, 'oom_disabled') ?? true,156'allocation_id' => Arr::get($data, 'allocation_id'),157'nest_id' => Arr::get($data, 'nest_id'),158'egg_id' => Arr::get($data, 'egg_id'),159'startup' => Arr::get($data, 'startup'),160'image' => Arr::get($data, 'image'),161'database_limit' => Arr::get($data, 'database_limit') ?? 0,162'allocation_limit' => Arr::get($data, 'allocation_limit') ?? 0,163'backup_limit' => Arr::get($data, 'backup_limit') ?? 0,164]);165166return $model;167}168169/**170* Configure the allocations assigned to this server.171*/172private function storeAssignedAllocations(Server $server, array $data): void173{174$records = [$data['allocation_id']];175if (isset($data['allocation_additional']) && is_array($data['allocation_additional'])) {176$records = array_merge($records, $data['allocation_additional']);177}178179Allocation::query()->whereIn('id', $records)->update([180'server_id' => $server->id,181]);182}183184/**185* Process environment variables passed for this server and store them in the database.186*/187private function storeEggVariables(Server $server, Collection $variables): void188{189$records = $variables->map(function ($result) use ($server) {190return [191'server_id' => $server->id,192'variable_id' => $result->id,193'variable_value' => $result->value ?? '',194];195})->toArray();196197if (!empty($records)) {198$this->serverVariableRepository->insert($records);199}200}201202/**203* Create a unique UUID and UUID-Short combo for a server.204*/205private function generateUniqueUuidCombo(): string206{207$uuid = Uuid::uuid4()->toString();208209if (!$this->repository->isUniqueUuidCombo($uuid, substr($uuid, 0, 8))) {210return $this->generateUniqueUuidCombo();211}212213return $uuid;214}215}216217218