Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
pterodactyl
GitHub Repository: pterodactyl/panel
Path: blob/1.0-develop/app/Services/Servers/ServerCreationService.php
10276 views
1
<?php
2
3
namespace Pterodactyl\Services\Servers;
4
5
use Ramsey\Uuid\Uuid;
6
use Illuminate\Support\Arr;
7
use Pterodactyl\Models\Egg;
8
use Pterodactyl\Models\User;
9
use Webmozart\Assert\Assert;
10
use Pterodactyl\Models\Server;
11
use Illuminate\Support\Collection;
12
use Pterodactyl\Models\Allocation;
13
use Illuminate\Database\ConnectionInterface;
14
use Pterodactyl\Models\Objects\DeploymentObject;
15
use Pterodactyl\Repositories\Eloquent\ServerRepository;
16
use Pterodactyl\Repositories\Wings\DaemonServerRepository;
17
use Pterodactyl\Services\Deployment\FindViableNodesService;
18
use Pterodactyl\Repositories\Eloquent\ServerVariableRepository;
19
use Pterodactyl\Services\Deployment\AllocationSelectionService;
20
use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
21
22
class ServerCreationService
23
{
24
/**
25
* ServerCreationService constructor.
26
*/
27
public function __construct(
28
private AllocationSelectionService $allocationSelectionService,
29
private ConnectionInterface $connection,
30
private DaemonServerRepository $daemonServerRepository,
31
private FindViableNodesService $findViableNodesService,
32
private ServerRepository $repository,
33
private ServerDeletionService $serverDeletionService,
34
private ServerVariableRepository $serverVariableRepository,
35
private VariableValidatorService $validatorService,
36
) {
37
}
38
39
/**
40
* Create a server on the Panel and trigger a request to the Daemon to begin the server
41
* creation process. This function will attempt to set as many additional values
42
* as possible given the input data. For example, if an allocation_id is passed with
43
* no node_id the node_is will be picked from the allocation.
44
*
45
* @throws \Throwable
46
* @throws \Pterodactyl\Exceptions\DisplayException
47
* @throws \Illuminate\Validation\ValidationException
48
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
49
* @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableNodeException
50
* @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableAllocationException
51
*/
52
public function handle(array $data, ?DeploymentObject $deployment = null): Server
53
{
54
// If a deployment object has been passed we need to get the allocation
55
// that the server should use, and assign the node from that allocation.
56
if ($deployment instanceof DeploymentObject) {
57
$allocation = $this->configureDeployment($data, $deployment);
58
$data['allocation_id'] = $allocation->id;
59
$data['node_id'] = $allocation->node_id;
60
}
61
62
// Auto-configure the node based on the selected allocation
63
// if no node was defined.
64
if (empty($data['node_id'])) {
65
Assert::false(empty($data['allocation_id']), 'Expected a non-empty allocation_id in server creation data.');
66
67
$data['node_id'] = Allocation::query()->findOrFail($data['allocation_id'])->node_id;
68
}
69
70
if (empty($data['nest_id'])) {
71
Assert::false(empty($data['egg_id']), 'Expected a non-empty egg_id in server creation data.');
72
73
$data['nest_id'] = Egg::query()->findOrFail($data['egg_id'])->nest_id;
74
}
75
76
$eggVariableData = $this->validatorService
77
->setUserLevel(User::USER_LEVEL_ADMIN)
78
->handle(Arr::get($data, 'egg_id'), Arr::get($data, 'environment', []));
79
80
// Due to the design of the Daemon, we need to persist this server to the disk
81
// before we can actually create it on the Daemon.
82
//
83
// If that connection fails out we will attempt to perform a cleanup by just
84
// deleting the server itself from the system.
85
/** @var Server $server */
86
$server = $this->connection->transaction(function () use ($data, $eggVariableData) {
87
// Create the server and assign any additional allocations to it.
88
$server = $this->createModel($data);
89
90
$this->storeAssignedAllocations($server, $data);
91
$this->storeEggVariables($server, $eggVariableData);
92
93
return $server;
94
}, 5);
95
96
try {
97
$this->daemonServerRepository->setServer($server)->create(
98
Arr::get($data, 'start_on_completion', false) ?? false
99
);
100
} catch (DaemonConnectionException $exception) {
101
$this->serverDeletionService->withForce()->handle($server);
102
103
throw $exception;
104
}
105
106
return $server;
107
}
108
109
/**
110
* Gets an allocation to use for automatic deployment.
111
*
112
* @throws \Pterodactyl\Exceptions\DisplayException
113
* @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableAllocationException
114
* @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableNodeException
115
*/
116
private function configureDeployment(array $data, DeploymentObject $deployment): Allocation
117
{
118
/** @var Collection $nodes */
119
$nodes = $this->findViableNodesService->setLocations($deployment->getLocations())
120
->setDisk(Arr::get($data, 'disk'))
121
->setMemory(Arr::get($data, 'memory'))
122
->handle();
123
124
return $this->allocationSelectionService->setDedicated($deployment->isDedicated())
125
->setNodes($nodes->pluck('id')->toArray())
126
->setPorts($deployment->getPorts())
127
->handle();
128
}
129
130
/**
131
* Store the server in the database and return the model.
132
*
133
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
134
*/
135
private function createModel(array $data): Server
136
{
137
$uuid = $this->generateUniqueUuidCombo();
138
139
/** @var Server $model */
140
$model = $this->repository->create([
141
'external_id' => Arr::get($data, 'external_id'),
142
'uuid' => $uuid,
143
'uuidShort' => substr($uuid, 0, 8),
144
'node_id' => Arr::get($data, 'node_id'),
145
'name' => Arr::get($data, 'name'),
146
'description' => Arr::get($data, 'description') ?? '',
147
'status' => Server::STATUS_INSTALLING,
148
'skip_scripts' => Arr::get($data, 'skip_scripts') ?? isset($data['skip_scripts']),
149
'owner_id' => Arr::get($data, 'owner_id'),
150
'memory' => Arr::get($data, 'memory'),
151
'swap' => Arr::get($data, 'swap'),
152
'disk' => Arr::get($data, 'disk'),
153
'io' => Arr::get($data, 'io'),
154
'cpu' => Arr::get($data, 'cpu'),
155
'threads' => Arr::get($data, 'threads'),
156
'oom_disabled' => Arr::get($data, 'oom_disabled') ?? true,
157
'allocation_id' => Arr::get($data, 'allocation_id'),
158
'nest_id' => Arr::get($data, 'nest_id'),
159
'egg_id' => Arr::get($data, 'egg_id'),
160
'startup' => Arr::get($data, 'startup'),
161
'image' => Arr::get($data, 'image'),
162
'database_limit' => Arr::get($data, 'database_limit') ?? 0,
163
'allocation_limit' => Arr::get($data, 'allocation_limit') ?? 0,
164
'backup_limit' => Arr::get($data, 'backup_limit') ?? 0,
165
]);
166
167
return $model;
168
}
169
170
/**
171
* Configure the allocations assigned to this server.
172
*/
173
private function storeAssignedAllocations(Server $server, array $data): void
174
{
175
$records = [$data['allocation_id']];
176
if (isset($data['allocation_additional']) && is_array($data['allocation_additional'])) {
177
$records = array_merge($records, $data['allocation_additional']);
178
}
179
180
Allocation::query()->whereIn('id', $records)->update([
181
'server_id' => $server->id,
182
]);
183
}
184
185
/**
186
* Process environment variables passed for this server and store them in the database.
187
*/
188
private function storeEggVariables(Server $server, Collection $variables): void
189
{
190
$records = $variables->map(function ($result) use ($server) {
191
return [
192
'server_id' => $server->id,
193
'variable_id' => $result->id,
194
'variable_value' => $result->value ?? '',
195
];
196
})->toArray();
197
198
if (!empty($records)) {
199
$this->serverVariableRepository->insert($records);
200
}
201
}
202
203
/**
204
* Create a unique UUID and UUID-Short combo for a server.
205
*/
206
private function generateUniqueUuidCombo(): string
207
{
208
$uuid = Uuid::uuid4()->toString();
209
210
if (!$this->repository->isUniqueUuidCombo($uuid, substr($uuid, 0, 8))) {
211
return $this->generateUniqueUuidCombo();
212
}
213
214
return $uuid;
215
}
216
}
217
218