Pterodactyl.php 12 KB

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