Pterodactyl.php 12 KB

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