Skip to content

Commit 2ee08a1

Browse files
committed
Update logic for server transfer controller
1 parent 6c61577 commit 2ee08a1

File tree

6 files changed

+184
-137
lines changed

6 files changed

+184
-137
lines changed

app/Http/Controllers/Api/Remote/Servers/ServerTransferController.php

Lines changed: 93 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,19 @@
33
namespace Pterodactyl\Http\Controllers\Api\Remote\Servers;
44

55
use Cake\Chronos\Chronos;
6-
use Lcobucci\JWT\Builder;
6+
use Illuminate\Support\Arr;
77
use Illuminate\Http\Request;
8-
use Lcobucci\JWT\Signer\Key;
9-
use Psr\Log\LoggerInterface;
108
use Illuminate\Http\Response;
119
use Illuminate\Http\JsonResponse;
12-
use Lcobucci\JWT\Signer\Hmac\Sha256;
10+
use Pterodactyl\Models\Allocation;
11+
use Illuminate\Support\Facades\Log;
12+
use Pterodactyl\Models\ServerTransfer;
1313
use Illuminate\Database\ConnectionInterface;
1414
use Pterodactyl\Http\Controllers\Controller;
15-
use Pterodactyl\Repositories\Eloquent\NodeRepository;
15+
use Pterodactyl\Services\Nodes\NodeJWTService;
1616
use Pterodactyl\Repositories\Eloquent\ServerRepository;
1717
use Pterodactyl\Repositories\Wings\DaemonServerRepository;
1818
use Pterodactyl\Repositories\Wings\DaemonTransferRepository;
19-
use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface;
2019
use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
2120
use Pterodactyl\Services\Servers\ServerConfigurationStructureService;
2221

@@ -32,16 +31,6 @@ class ServerTransferController extends Controller
3231
*/
3332
private $repository;
3433

35-
/**
36-
* @var \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface
37-
*/
38-
private $allocationRepository;
39-
40-
/**
41-
* @var \Pterodactyl\Repositories\Eloquent\NodeRepository
42-
*/
43-
private $nodeRepository;
44-
4534
/**
4635
* @var \Pterodactyl\Repositories\Wings\DaemonServerRepository
4736
*/
@@ -58,40 +47,34 @@ class ServerTransferController extends Controller
5847
private $configurationStructureService;
5948

6049
/**
61-
* @var \Psr\Log\LoggerInterface
50+
* @var \Pterodactyl\Services\Nodes\NodeJWTService
6251
*/
63-
private $writer;
52+
private $jwtService;
6453

6554
/**
6655
* ServerTransferController constructor.
6756
*
6857
* @param \Illuminate\Database\ConnectionInterface $connection
6958
* @param \Pterodactyl\Repositories\Eloquent\ServerRepository $repository
70-
* @param \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface $allocationRepository
71-
* @param \Pterodactyl\Repositories\Eloquent\NodeRepository $nodeRepository
7259
* @param \Pterodactyl\Repositories\Wings\DaemonServerRepository $daemonServerRepository
7360
* @param \Pterodactyl\Repositories\Wings\DaemonTransferRepository $daemonTransferRepository
7461
* @param \Pterodactyl\Services\Servers\ServerConfigurationStructureService $configurationStructureService
75-
* @param \Psr\Log\LoggerInterface $writer
62+
* @param \Pterodactyl\Services\Nodes\NodeJWTService $jwtService
7663
*/
7764
public function __construct(
7865
ConnectionInterface $connection,
7966
ServerRepository $repository,
80-
AllocationRepositoryInterface $allocationRepository,
81-
NodeRepository $nodeRepository,
8267
DaemonServerRepository $daemonServerRepository,
8368
DaemonTransferRepository $daemonTransferRepository,
8469
ServerConfigurationStructureService $configurationStructureService,
85-
LoggerInterface $writer
70+
NodeJWTService $jwtService
8671
) {
8772
$this->connection = $connection;
8873
$this->repository = $repository;
89-
$this->allocationRepository = $allocationRepository;
90-
$this->nodeRepository = $nodeRepository;
9174
$this->daemonServerRepository = $daemonServerRepository;
9275
$this->daemonTransferRepository = $daemonTransferRepository;
9376
$this->configurationStructureService = $configurationStructureService;
94-
$this->writer = $writer;
77+
$this->jwtService = $jwtService;
9578
}
9679

9780
/**
@@ -101,7 +84,6 @@ public function __construct(
10184
* @param string $uuid
10285
* @return \Illuminate\Http\JsonResponse
10386
*
104-
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
10587
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
10688
* @throws \Throwable
10789
*/
@@ -111,62 +93,43 @@ public function archive(Request $request, string $uuid)
11193

11294
// Unsuspend the server and don't continue the transfer.
11395
if (! $request->input('successful')) {
114-
$transfer = $server->transfer;
115-
116-
$transfer->successful = false;
117-
$transfer->saveOrFail();
118-
119-
$allocationIds = json_decode($transfer->new_additional_allocations);
120-
array_push($allocationIds, $transfer->new_allocation);
121-
122-
// Release the reserved allocations.
123-
$this->allocationRepository->updateWhereIn('id', $allocationIds, ['server_id' => null]);
124-
125-
return new JsonResponse([], Response::HTTP_NO_CONTENT);
96+
return $this->processFailedTransfer($server->transfer);
12697
}
12798

128-
$server->node_id = $server->transfer->new_node;
129-
130-
$data = $this->configurationStructureService->handle($server);
131-
$data['suspended'] = false;
132-
$data['service']['skip_scripts'] = true;
99+
// We want to generate a new configuration using the new node_id value from the
100+
// transfer, and not the old node value.
101+
$data = $this->configurationStructureService->handle($server, [
102+
'node_id' => $server->transfer->new_node,
103+
]);
133104

134105
$allocations = $server->getAllocationMappings();
135-
$data['allocations']['default']['ip'] = array_key_first($allocations);
136-
$data['allocations']['default']['port'] = $allocations[$data['allocations']['default']['ip']][0];
137-
138-
$now = Chronos::now();
139-
$signer = new Sha256;
140-
141-
$token = (new Builder)->issuedBy(config('app.url'))
142-
->permittedFor($server->node->getConnectionAddress())
143-
->identifiedBy(hash('sha256', $server->uuid), true)
144-
->issuedAt($now->getTimestamp())
145-
->canOnlyBeUsedAfter($now->getTimestamp())
146-
->expiresAt($now->addMinutes(15)->getTimestamp())
147-
->relatedTo($server->uuid, true)
148-
->getToken($signer, new Key($server->node->getDecryptedKey()));
149-
150-
// Update the archived field on the transfer to make clients connect to the websocket
151-
// on the new node to be able to receive transfer logs.
152-
$server->transfer->forceFill([
153-
'archived' => true,
154-
])->saveOrFail();
155-
156-
// On the daemon transfer repository, make sure to set the node after the server
157-
// because setServer() tells the repository to use the server's node and not the one
158-
// we want to specify.
159-
try {
160-
/** @var \Pterodactyl\Models\Node $newNode */
161-
$newNode = $this->nodeRepository->find($server->transfer->new_node);
162-
106+
$primary = array_key_first($allocations);
107+
Arr::set($data, 'allocations.default.ip', $primary);
108+
Arr::set($data, 'allocations.default.port', $allocations[$primary][0]);
109+
Arr::set($data, 'service.skip_scripts', true);
110+
Arr::set($data, 'suspended', false);
111+
112+
$this->connection->transaction(function () use ($data, $server) {
113+
// This token is used by the new node the server is being transfered to. It allows
114+
// that node to communicate with the old node during the process to initiate the
115+
// actual file transfer.
116+
$token = $this->jwtService
117+
->setExpiresAt(Chronos::now()->addMinutes(15))
118+
->setSubject($server->uuid)
119+
->handle($server->node, $server->uuid, 'sha256');
120+
121+
// Update the archived field on the transfer to make clients connect to the websocket
122+
// on the new node to be able to receive transfer logs.
123+
$server->transfer->forceFill(['archived' => true])->saveOrFail();
124+
125+
// On the daemon transfer repository, make sure to set the node after the server
126+
// because setServer() tells the repository to use the server's node and not the one
127+
// we want to specify.
163128
$this->daemonTransferRepository
164129
->setServer($server)
165-
->setNode($newNode)
130+
->setNode($server->transfer->newNode)
166131
->notify($server, $data, $server->node, $token->__toString());
167-
} catch (DaemonConnectionException $exception) {
168-
throw $exception;
169-
}
132+
});
170133

171134
return new JsonResponse([], Response::HTTP_NO_CONTENT);
172135
}
@@ -182,25 +145,8 @@ public function archive(Request $request, string $uuid)
182145
public function failure(string $uuid)
183146
{
184147
$server = $this->repository->getByUuid($uuid);
185-
$transfer = $server->transfer;
186-
187-
$allocationIds = json_decode($transfer->new_additional_allocations);
188-
array_push($allocationIds, $transfer->new_allocation);
189-
190-
// Begin a transaction.
191-
$this->connection->beginTransaction();
192-
193-
// Mark the transfer as unsuccessful.
194-
$transfer->successful = false;
195-
$transfer->saveOrFail();
196-
197-
// Remove the new allocations.
198-
$this->allocationRepository->updateWhereIn('id', $allocationIds, ['server_id' => null]);
199148

200-
// Commit the transaction.
201-
$this->connection->commit();
202-
203-
return new JsonResponse([], Response::HTTP_NO_CONTENT);
149+
return $this->processFailedTransfer($server->transfer);
204150
}
205151

206152
/**
@@ -216,33 +162,62 @@ public function success(string $uuid)
216162
$server = $this->repository->getByUuid($uuid);
217163
$transfer = $server->transfer;
218164

219-
$allocationIds = json_decode($transfer->old_additional_allocations);
220-
array_push($allocationIds, $transfer->old_allocation);
221-
222-
// Begin a transaction.
223-
$this->connection->beginTransaction();
224-
225-
// Remove the old allocations.
226-
$this->allocationRepository->updateWhereIn('id', $allocationIds, ['server_id' => null]);
165+
/** @var \Pterodactyl\Models\Server $server */
166+
$server = $this->connection->transaction(function () use ($server, $transfer) {
167+
$allocations = [$transfer->old_allocation];
168+
if (! empty($transfer->old_additional_allocations)) {
169+
array_push($allocations, $transfer->old_additional_allocations);
170+
}
171+
172+
// Remove the old allocations for the server and re-assign the server to the new
173+
// primary allocation and node.
174+
Allocation::query()->whereIn('id', $allocations)->update(['server_id' => null]);
175+
$server->update([
176+
'allocation_id' => $transfer->new_allocation,
177+
'node_id' => $transfer->new_node,
178+
]);
179+
180+
$server = $server->fresh();
181+
$server->transfer->update(['successful' => true]);
182+
183+
return $server;
184+
});
185+
186+
// Delete the server from the old node making sure to point it to the old node so
187+
// that we do not delete it from the new node the server was transfered to.
188+
try {
189+
$this->daemonServerRepository
190+
->setServer($server)
191+
->setNode($transfer->oldNode)
192+
->delete();
193+
} catch (DaemonConnectionException $exception) {
194+
Log::warning($exception, ['transfer_id' => $server->transfer->id]);
195+
}
227196

228-
// Update the server's allocation_id and node_id.
229-
$server->allocation_id = $transfer->new_allocation;
230-
$server->node_id = $transfer->new_node;
231-
$server->saveOrFail();
197+
return new JsonResponse([], Response::HTTP_NO_CONTENT);
198+
}
232199

233-
// Mark the transfer as successful.
234-
$transfer->successful = true;
235-
$transfer->saveOrFail();
200+
/**
201+
* Release all of the reserved allocations for this transfer and mark it as failed in
202+
* the database.
203+
*
204+
* @param \Pterodactyl\Models\ServerTransfer $transfer
205+
* @return \Illuminate\Http\JsonResponse
206+
*
207+
* @throws \Throwable
208+
*/
209+
protected function processFailedTransfer(ServerTransfer $transfer)
210+
{
211+
$this->connection->transaction(function () use (&$transfer) {
212+
$transfer->forceFill(['successful' => false])->saveOrFail();
236213

237-
// Commit the transaction.
238-
$this->connection->commit();
214+
$allocations = [$transfer->new_allocation];
215+
if (! empty($transfer->new_additional_allocations)) {
216+
array_push($allocations, $transfer->new_additional_allocations);
217+
}
239218

240-
// Delete the server from the old node
241-
try {
242-
$this->daemonServerRepository->setServer($server)->delete();
243-
} catch (DaemonConnectionException $exception) {
244-
$this->writer->warning($exception);
245-
}
219+
Allocation::query()->whereIn('id', $allocations)->update(['server_id' => null]);
220+
});
246221

247222
return new JsonResponse([], Response::HTTP_NO_CONTENT);
248223
}

app/Models/ServerTransfer.php

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
* @property int $new_node
1010
* @property int $old_allocation
1111
* @property int $new_allocation
12-
* @property string $old_additional_allocations
13-
* @property string $new_additional_allocations
12+
* @property array|null $old_additional_allocations
13+
* @property array|null $new_additional_allocations
1414
* @property bool|null $successful
1515
* @property bool $archived
1616
* @property \Carbon\Carbon $created_at
@@ -53,8 +53,8 @@ class ServerTransfer extends Model
5353
'new_node' => 'int',
5454
'old_allocation' => 'int',
5555
'new_allocation' => 'int',
56-
'old_additional_allocations' => 'string',
57-
'new_additional_allocations' => 'string',
56+
'old_additional_allocations' => 'array',
57+
'new_additional_allocations' => 'array',
5858
'successful' => 'bool',
5959
'archived' => 'bool',
6060
];
@@ -68,8 +68,10 @@ class ServerTransfer extends Model
6868
'new_node' => 'required|numeric',
6969
'old_allocation' => 'required|numeric',
7070
'new_allocation' => 'required|numeric',
71-
'old_additional_allocations' => 'nullable',
72-
'new_additional_allocations' => 'nullable',
71+
'old_additional_allocations' => 'nullable|array',
72+
'old_additional_allocations.*' => 'numeric',
73+
'new_additional_allocations' => 'nullable|array',
74+
'new_additional_allocations.*' => 'numeric',
7375
'successful' => 'sometimes|nullable|boolean',
7476
];
7577

app/Services/Eggs/EggConfigurationService.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ protected function replacePlaceholders(Server $server, object $configs)
102102
{
103103
// Get the legacy configuration structure for the server so that we
104104
// can property map the egg placeholders to values.
105-
$structure = $this->configurationStructureService->handle($server, true);
105+
$structure = $this->configurationStructureService->handle($server, [], true);
106106

107107
$response = [];
108108
// Normalize the output of the configuration for the new Wings Daemon to more

0 commit comments

Comments
 (0)