33namespace Pterodactyl \Http \Controllers \Api \Remote \Servers ;
44
55use Cake \Chronos \Chronos ;
6- use Lcobucci \ JWT \ Builder ;
6+ use Illuminate \ Support \ Arr ;
77use Illuminate \Http \Request ;
8- use Lcobucci \JWT \Signer \Key ;
9- use Psr \Log \LoggerInterface ;
108use Illuminate \Http \Response ;
119use 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 ;
1313use Illuminate \Database \ConnectionInterface ;
1414use Pterodactyl \Http \Controllers \Controller ;
15- use Pterodactyl \Repositories \ Eloquent \ NodeRepository ;
15+ use Pterodactyl \Services \ Nodes \ NodeJWTService ;
1616use Pterodactyl \Repositories \Eloquent \ServerRepository ;
1717use Pterodactyl \Repositories \Wings \DaemonServerRepository ;
1818use Pterodactyl \Repositories \Wings \DaemonTransferRepository ;
19- use Pterodactyl \Contracts \Repository \AllocationRepositoryInterface ;
2019use Pterodactyl \Exceptions \Http \Connection \DaemonConnectionException ;
2120use 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 }
0 commit comments