PterodactylClient.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455
  1. <?php
  2. namespace App\Classes;
  3. use App\Models\Pterodactyl\Egg;
  4. use App\Models\Pterodactyl\Nest;
  5. use App\Models\Pterodactyl\Node;
  6. use App\Models\Product;
  7. use App\Models\Server;
  8. use Exception;
  9. use Illuminate\Http\Client\PendingRequest;
  10. use Illuminate\Http\Client\Response;
  11. use Illuminate\Support\Facades\Http;
  12. use App\Settings\PterodactylSettings;
  13. use App\Settings\ServerSettings;
  14. class PterodactylClient
  15. {
  16. //TODO: Extend error handling (maybe logger for more errors when debugging)
  17. private int $per_page_limit = 200;
  18. private int $allocation_limit = 200;
  19. public PendingRequest $client;
  20. public PendingRequest $application;
  21. public function __construct(PterodactylSettings $ptero_settings)
  22. {
  23. $server_settings = new ServerSettings();
  24. try {
  25. $this->client = $this->client($ptero_settings);
  26. $this->application = $this->clientAdmin($ptero_settings);
  27. $this->per_page_limit = $ptero_settings->per_page_limit;
  28. $this->allocation_limit = $server_settings->allocation_limit;
  29. } catch (Exception $exception) {
  30. logger('Failed to construct Pterodactyl client, Settings table not available?', ['exception' => $exception]);
  31. }
  32. }
  33. /**
  34. * @return PendingRequest
  35. */
  36. public function client(PterodactylSettings $ptero_settings)
  37. {
  38. return Http::withHeaders([
  39. 'Authorization' => 'Bearer ' . $ptero_settings->user_token,
  40. 'Content-type' => 'application/json',
  41. 'Accept' => 'Application/vnd.pterodactyl.v1+json',
  42. ])->baseUrl($ptero_settings->getUrl() . 'api' . '/');
  43. }
  44. public function clientAdmin(PterodactylSettings $ptero_settings)
  45. {
  46. return Http::withHeaders([
  47. 'Authorization' => 'Bearer ' . $ptero_settings->admin_token,
  48. 'Content-type' => 'application/json',
  49. 'Accept' => 'Application/vnd.pterodactyl.v1+json',
  50. ])->baseUrl($ptero_settings->getUrl() . 'api' . '/');
  51. }
  52. /**
  53. * @return Exception
  54. */
  55. private function getException(string $message = '', int $status = 0): Exception
  56. {
  57. if ($status == 404) {
  58. return new Exception('Ressource does not exist on pterodactyl - ' . $message, 404);
  59. }
  60. if ($status == 403) {
  61. return new Exception('No permission on pterodactyl, check pterodactyl token and permissions - ' . $message, 403);
  62. }
  63. if ($status == 401) {
  64. return new Exception('No pterodactyl token set - ' . $message, 401);
  65. }
  66. if ($status == 500) {
  67. return new Exception('Pterodactyl server error - ' . $message, 500);
  68. }
  69. return new Exception('Request Failed, is pterodactyl set-up correctly? - ' . $message);
  70. }
  71. /**
  72. * @param Nest $nest
  73. * @return mixed
  74. *
  75. * @throws Exception
  76. */
  77. public function getEggs(Nest $nest)
  78. {
  79. try {
  80. $response = $this->application->get("application/nests/{$nest->id}/eggs?include=nest,variables&per_page=" . $this->per_page_limit);
  81. } catch (Exception $e) {
  82. throw self::getException($e->getMessage());
  83. }
  84. if ($response->failed()) {
  85. throw self::getException('Failed to get eggs from pterodactyl - ', $response->status());
  86. }
  87. return $response->json()['data'];
  88. }
  89. /**
  90. * @return mixed
  91. *
  92. * @throws Exception
  93. */
  94. public function getNodes()
  95. {
  96. try {
  97. $response = $this->application->get('application/nodes?per_page=' . $this->per_page_limit);
  98. } catch (Exception $e) {
  99. throw self::getException($e->getMessage());
  100. }
  101. if ($response->failed()) {
  102. throw self::getException('Failed to get nodes from pterodactyl - ', $response->status());
  103. }
  104. return $response->json()['data'];
  105. }
  106. /**
  107. * @return mixed
  108. *
  109. * @throws Exception
  110. * @description Returns the infos of a single node
  111. */
  112. public function getNode($id)
  113. {
  114. try {
  115. $response = $this->application->get('application/nodes/' . $id);
  116. } catch (Exception $e) {
  117. throw self::getException($e->getMessage());
  118. }
  119. if ($response->failed()) {
  120. throw self::getException('Failed to get node id ' . $id . ' - ' . $response->status());
  121. }
  122. return $response->json()['attributes'];
  123. }
  124. public function getServers()
  125. {
  126. try {
  127. $response = $this->application->get('application/servers?per_page=' . $this->per_page_limit);
  128. } catch (Exception $e) {
  129. throw self::getException($e->getMessage());
  130. }
  131. if ($response->failed()) {
  132. throw self::getException('Failed to get list of servers - ', $response->status());
  133. }
  134. return $response->json()['data'];
  135. }
  136. /**
  137. * @return null
  138. *
  139. * @throws Exception
  140. */
  141. public function getNests()
  142. {
  143. try {
  144. $response = $this->application->get('application/nests?per_page=' . $this->per_page_limit);
  145. } catch (Exception $e) {
  146. throw self::getException($e->getMessage());
  147. }
  148. if ($response->failed()) {
  149. throw self::getException('Failed to get nests from pterodactyl', $response->status());
  150. }
  151. return $response->json()['data'];
  152. }
  153. /**
  154. * @return mixed
  155. *
  156. * @throws Exception
  157. */
  158. public function getLocations()
  159. {
  160. try {
  161. $response = $this->application->get('application/locations?per_page=' . $this->per_page_limit);
  162. } catch (Exception $e) {
  163. throw self::getException($e->getMessage());
  164. }
  165. if ($response->failed()) {
  166. throw self::getException('Failed to get locations from pterodactyl - ', $response->status());
  167. }
  168. return $response->json()['data'];
  169. }
  170. /**
  171. * @param Node $node
  172. * @return mixed
  173. *
  174. * @throws Exception
  175. */
  176. public function getFreeAllocationId(Node $node)
  177. {
  178. return self::getFreeAllocations($node)[0]['attributes']['id'] ?? null;
  179. }
  180. /**
  181. * @param Node $node
  182. * @return array|mixed|null
  183. *
  184. * @throws Exception
  185. */
  186. public function getFreeAllocations(Node $node)
  187. {
  188. $response = self::getAllocations($node);
  189. $freeAllocations = [];
  190. if (isset($response['data'])) {
  191. if (!empty($response['data'])) {
  192. foreach ($response['data'] as $allocation) {
  193. if (!$allocation['attributes']['assigned']) {
  194. array_push($freeAllocations, $allocation);
  195. }
  196. }
  197. }
  198. }
  199. return $freeAllocations;
  200. }
  201. /**
  202. * @param Node $node
  203. * @return array|mixed
  204. *
  205. * @throws Exception
  206. */
  207. public function getAllocations(Node $node)
  208. {
  209. try {
  210. $response = $this->application->get("application/nodes/{$node->id}/allocations?per_page={$this->allocation_limit}");
  211. } catch (Exception $e) {
  212. throw self::getException($e->getMessage());
  213. }
  214. if ($response->failed()) {
  215. throw self::getException('Failed to get allocations from pterodactyl - ', $response->status());
  216. }
  217. return $response->json();
  218. }
  219. /**
  220. * @param Server $server
  221. * @param Egg $egg
  222. * @param int $allocationId
  223. * @return Response
  224. */
  225. public function createServer(Server $server, Egg $egg, int $allocationId)
  226. {
  227. return $this->application->post('application/servers', [
  228. 'name' => $server->name,
  229. 'external_id' => $server->id,
  230. 'user' => $server->user->pterodactyl_id,
  231. 'egg' => $egg->id,
  232. 'docker_image' => $egg->docker_image,
  233. 'startup' => $egg->startup,
  234. 'environment' => $egg->getEnvironmentVariables(),
  235. 'oom_disabled' => !$server->product->oom_killer,
  236. 'limits' => [
  237. 'memory' => $server->product->memory,
  238. 'swap' => $server->product->swap,
  239. 'disk' => $server->product->disk,
  240. 'io' => $server->product->io,
  241. 'cpu' => $server->product->cpu,
  242. ],
  243. 'feature_limits' => [
  244. 'databases' => $server->product->databases,
  245. 'backups' => $server->product->backups,
  246. 'allocations' => $server->product->allocations,
  247. ],
  248. 'allocation' => [
  249. 'default' => $allocationId,
  250. ],
  251. ]);
  252. }
  253. public function suspendServer(Server $server)
  254. {
  255. try {
  256. $response = $this->application->post("application/servers/$server->pterodactyl_id/suspend");
  257. } catch (Exception $e) {
  258. throw self::getException($e->getMessage());
  259. }
  260. if ($response->failed()) {
  261. throw self::getException('Failed to suspend server from pterodactyl - ', $response->status());
  262. }
  263. return $response;
  264. }
  265. public function unSuspendServer(Server $server)
  266. {
  267. try {
  268. $response = $this->application->post("application/servers/$server->pterodactyl_id/unsuspend");
  269. } catch (Exception $e) {
  270. throw self::getException($e->getMessage());
  271. }
  272. if ($response->failed()) {
  273. throw self::getException('Failed to unsuspend server from pterodactyl - ', $response->status());
  274. }
  275. return $response;
  276. }
  277. /**
  278. * Get user by pterodactyl id
  279. *
  280. * @param int $pterodactylId
  281. * @return mixed
  282. */
  283. public function getUser(int $pterodactylId)
  284. {
  285. try {
  286. $response = $this->application->get("application/users/{$pterodactylId}");
  287. } catch (Exception $e) {
  288. throw self::getException($e->getMessage());
  289. }
  290. if ($response->failed()) {
  291. throw self::getException('Failed to get user from pterodactyl - ', $response->status());
  292. }
  293. return $response->json()['attributes'];
  294. }
  295. /**
  296. * Get serverAttributes by pterodactyl id
  297. *
  298. * @param int $pterodactylId
  299. * @return mixed
  300. */
  301. public function getServerAttributes(int $pterodactylId, bool $deleteOn404 = false)
  302. {
  303. try {
  304. $response = $this->application->get("application/servers/{$pterodactylId}?include=egg,node,nest,location");
  305. } catch (Exception $e) {
  306. throw self::getException($e->getMessage());
  307. }
  308. //print response body
  309. if ($response->failed()) {
  310. if ($deleteOn404) { //Delete the server if it does not exist (server deleted on pterodactyl)
  311. Server::where('pterodactyl_id', $pterodactylId)->first()->delete();
  312. return;
  313. } else {
  314. throw self::getException('Failed to get server attributes from pterodactyl - ', $response->status());
  315. }
  316. }
  317. return $response->json()['attributes'];
  318. }
  319. /**
  320. * Update Server Resources
  321. *
  322. * @param Server $server
  323. * @param Product $product
  324. * @return Response
  325. */
  326. public function updateServer(Server $server, Product $product)
  327. {
  328. return $this->application->patch("application/servers/{$server->pterodactyl_id}/build", [
  329. 'allocation' => $server->allocation,
  330. 'memory' => $product->memory,
  331. 'swap' => $product->swap,
  332. 'disk' => $product->disk,
  333. 'io' => $product->io,
  334. 'cpu' => $product->cpu,
  335. 'threads' => null,
  336. 'oom_disabled' => !$server->product->oom_killer,
  337. 'feature_limits' => [
  338. 'databases' => $product->databases,
  339. 'backups' => $product->backups,
  340. 'allocations' => $product->allocations,
  341. ],
  342. ]);
  343. }
  344. /**
  345. * Update the owner of a server
  346. *
  347. * @param int $userId
  348. * @param Server $server
  349. * @return mixed
  350. */
  351. public function updateServerOwner(Server $server, int $userId)
  352. {
  353. return $this->application->patch("application/servers/{$server->pterodactyl_id}/details", [
  354. 'name' => $server->name,
  355. 'user' => $userId,
  356. ]);
  357. }
  358. /**
  359. * Power Action Specific Server
  360. *
  361. * @param Server $server
  362. * @param string $action
  363. * @return Response
  364. */
  365. public function powerAction(Server $server, $action)
  366. {
  367. return $this->client->post("client/servers/{$server->identifier}/power", [
  368. 'signal' => $action,
  369. ]);
  370. }
  371. /**
  372. * Get info about user
  373. */
  374. public function getClientUser()
  375. {
  376. return $this->client->get('client/account');
  377. }
  378. /**
  379. * Check if node has enough free resources to allocate the given resources
  380. *
  381. * @param Node $node
  382. * @param int $requireMemory
  383. * @param int $requireDisk
  384. * @return bool
  385. */
  386. public function checkNodeResources(Node $node, int $requireMemory, int $requireDisk)
  387. {
  388. try {
  389. $response = $this->application->get("application/nodes/{$node->id}");
  390. } catch (Exception $e) {
  391. throw self::getException($e->getMessage());
  392. }
  393. $node = $response['attributes'];
  394. $freeMemory = ($node['memory'] * ($node['memory_overallocate'] + 100) / 100) - $node['allocated_resources']['memory'];
  395. $freeDisk = ($node['disk'] * ($node['disk_overallocate'] + 100) / 100) - $node['allocated_resources']['disk'];
  396. if ($freeMemory < $requireMemory) {
  397. return false;
  398. }
  399. if ($freeDisk < $requireDisk) {
  400. return false;
  401. }
  402. return true;
  403. }
  404. }