Pterodactyl.php 13 KB

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