Pterodactyl.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425
  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 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. *
  57. * @throws Exception
  58. */
  59. public static function getEggs(Nest $nest)
  60. {
  61. try {
  62. $response = self::client()->get("/application/nests/{$nest->id}/eggs?include=nest,variables&per_page=".config('SETTINGS::SYSTEM:PTERODACTYL:PER_PAGE_LIMIT'));
  63. } catch (Exception $e) {
  64. throw self::getException($e->getMessage());
  65. }
  66. if ($response->failed()) {
  67. throw self::getException('Failed to get eggs from pterodactyl - ', $response->status());
  68. }
  69. return $response->json()['data'];
  70. }
  71. /**
  72. * @return mixed
  73. *
  74. * @throws Exception
  75. */
  76. public static function getNodes()
  77. {
  78. try {
  79. $response = self::client()->get('/application/nodes?per_page='.config('SETTINGS::SYSTEM:PTERODACTYL:PER_PAGE_LIMIT'));
  80. } catch (Exception $e) {
  81. throw self::getException($e->getMessage());
  82. }
  83. if ($response->failed()) {
  84. throw self::getException('Failed to get nodes from pterodactyl - ', $response->status());
  85. }
  86. return $response->json()['data'];
  87. }
  88. /**
  89. * @return mixed
  90. *
  91. * @throws Exception
  92. * @description Returns the infos of a single node
  93. */
  94. public static function getNode($id)
  95. {
  96. try {
  97. $response = self::client()->get('/application/nodes/'.$id);
  98. } catch (Exception $e) {
  99. throw self::getException($e->getMessage());
  100. }
  101. if ($response->failed()) {
  102. throw self::getException('Failed to get node id '.$id.' - '.$response->status());
  103. }
  104. return $response->json()['attributes'];
  105. }
  106. public static function getServers()
  107. {
  108. try {
  109. $response = self::client()->get('/application/servers?per_page='.config('SETTINGS::SYSTEM:PTERODACTYL:PER_PAGE_LIMIT'));
  110. } catch (Exception $e) {
  111. throw self::getException($e->getMessage());
  112. }
  113. if ($response->failed()) {
  114. throw self::getException('Failed to get list of servers - ', $response->status());
  115. }
  116. return $response->json()['data'];
  117. }
  118. /**
  119. * @return null
  120. *
  121. * @throws Exception
  122. */
  123. public static function getNests()
  124. {
  125. try {
  126. $response = self::client()->get('/application/nests?per_page='.config('SETTINGS::SYSTEM:PTERODACTYL:PER_PAGE_LIMIT'));
  127. } catch (Exception $e) {
  128. throw self::getException($e->getMessage());
  129. }
  130. if ($response->failed()) {
  131. throw self::getException('Failed to get nests from pterodactyl', $response->status());
  132. }
  133. return $response->json()['data'];
  134. }
  135. /**
  136. * @return mixed
  137. *
  138. * @throws Exception
  139. */
  140. public static function getLocations()
  141. {
  142. try {
  143. $response = self::client()->get('/application/locations?per_page='.config('SETTINGS::SYSTEM:PTERODACTYL:PER_PAGE_LIMIT'));
  144. } catch (Exception $e) {
  145. throw self::getException($e->getMessage());
  146. }
  147. if ($response->failed()) {
  148. throw self::getException('Failed to get locations from pterodactyl - ', $response->status());
  149. }
  150. return $response->json()['data'];
  151. }
  152. /**
  153. * @param Node $node
  154. * @return mixed
  155. *
  156. * @throws Exception
  157. */
  158. public static function getFreeAllocationId(Node $node)
  159. {
  160. return self::getFreeAllocations($node)[0]['attributes']['id'] ?? null;
  161. }
  162. /**
  163. * @param Node $node
  164. * @return array|mixed|null
  165. *
  166. * @throws Exception
  167. */
  168. public static function getFreeAllocations(Node $node)
  169. {
  170. $response = self::getAllocations($node);
  171. $freeAllocations = [];
  172. if (isset($response['data'])) {
  173. if (! empty($response['data'])) {
  174. foreach ($response['data'] as $allocation) {
  175. if (! $allocation['attributes']['assigned']) {
  176. array_push($freeAllocations, $allocation);
  177. }
  178. }
  179. }
  180. }
  181. return $freeAllocations;
  182. }
  183. /**
  184. * @param Node $node
  185. * @return array|mixed
  186. *
  187. * @throws Exception
  188. */
  189. public static function getAllocations(Node $node)
  190. {
  191. $per_page = config('SETTINGS::SERVER:ALLOCATION_LIMIT', 200);
  192. try {
  193. $response = self::client()->get("/application/nodes/{$node->id}/allocations?per_page={$per_page}");
  194. } catch (Exception $e) {
  195. throw self::getException($e->getMessage());
  196. }
  197. if ($response->failed()) {
  198. throw self::getException('Failed to get allocations from pterodactyl - ', $response->status());
  199. }
  200. return $response->json();
  201. }
  202. /**
  203. * @param string $route
  204. * @return string
  205. */
  206. public static function url(string $route): string
  207. {
  208. return config('SETTINGS::SYSTEM:PTERODACTYL:URL').$route;
  209. }
  210. /**
  211. * @param Server $server
  212. * @param Egg $egg
  213. * @param int $allocationId
  214. * @return Response
  215. */
  216. public static function createServer(Server $server, Egg $egg, int $allocationId)
  217. {
  218. return self::client()->post('/application/servers', [
  219. 'name' => $server->name,
  220. 'external_id' => $server->id,
  221. 'user' => $server->user->pterodactyl_id,
  222. 'egg' => $egg->id,
  223. 'docker_image' => $egg->docker_image,
  224. 'startup' => $egg->startup,
  225. 'environment' => $egg->getEnvironmentVariables(),
  226. 'limits' => [
  227. 'memory' => $server->product->memory,
  228. 'swap' => $server->product->swap,
  229. 'disk' => $server->product->disk,
  230. 'io' => $server->product->io,
  231. 'cpu' => $server->product->cpu,
  232. ],
  233. 'feature_limits' => [
  234. 'databases' => $server->product->databases,
  235. 'backups' => $server->product->backups,
  236. 'allocations' => $server->product->allocations,
  237. ],
  238. 'allocation' => [
  239. 'default' => $allocationId,
  240. ],
  241. ]);
  242. }
  243. public static function suspendServer(Server $server)
  244. {
  245. try {
  246. $response = self::client()->post("/application/servers/$server->pterodactyl_id/suspend");
  247. } catch (Exception $e) {
  248. throw self::getException($e->getMessage());
  249. }
  250. if ($response->failed()) {
  251. throw self::getException('Failed to suspend server from pterodactyl - ', $response->status());
  252. }
  253. return $response;
  254. }
  255. public static function unSuspendServer(Server $server)
  256. {
  257. try {
  258. $response = self::client()->post("/application/servers/$server->pterodactyl_id/unsuspend");
  259. } catch (Exception $e) {
  260. throw self::getException($e->getMessage());
  261. }
  262. if ($response->failed()) {
  263. throw self::getException('Failed to unsuspend server from pterodactyl - ', $response->status());
  264. }
  265. return $response;
  266. }
  267. /**
  268. * Get user by pterodactyl id
  269. *
  270. * @param int $pterodactylId
  271. * @return mixed
  272. */
  273. public function getUser(int $pterodactylId)
  274. {
  275. try {
  276. $response = self::client()->get("/application/users/{$pterodactylId}");
  277. } catch (Exception $e) {
  278. throw self::getException($e->getMessage());
  279. }
  280. if ($response->failed()) {
  281. throw self::getException('Failed to get user from pterodactyl - ', $response->status());
  282. }
  283. return $response->json()['attributes'];
  284. }
  285. /**
  286. * Get serverAttributes by pterodactyl id
  287. *
  288. * @param int $pterodactylId
  289. * @return mixed
  290. */
  291. public static function getServerAttributes(int $pterodactylId, bool $deleteOn404 = false)
  292. {
  293. try {
  294. $response = self::client()->get("/application/servers/{$pterodactylId}?include=egg,node,nest,location");
  295. } catch (Exception $e) {
  296. throw self::getException($e->getMessage());
  297. }
  298. //print response body
  299. if ($response->failed()) {
  300. if ($deleteOn404) { //Delete the server if it does not exist (server deleted on pterodactyl)
  301. Server::where('pterodactyl_id', $pterodactylId)->first()->delete();
  302. return;
  303. } else {
  304. throw self::getException('Failed to get server attributes from pterodactyl - ', $response->status());
  305. }
  306. }
  307. return $response->json()['attributes'];
  308. }
  309. /**
  310. * Update Server Resources
  311. *
  312. * @param Server $server
  313. * @param Product $product
  314. * @return Response
  315. */
  316. public static function updateServer(Server $server, Product $product)
  317. {
  318. return self::client()->patch("/application/servers/{$server->pterodactyl_id}/build", [
  319. 'allocation' => $server->allocation,
  320. 'memory' => $product->memory,
  321. 'swap' => $product->swap,
  322. 'disk' => $product->disk,
  323. 'io' => $product->io,
  324. 'cpu' => $product->cpu,
  325. 'threads' => null,
  326. 'feature_limits' => [
  327. 'databases' => $product->databases,
  328. 'backups' => $product->backups,
  329. 'allocations' => $product->allocations,
  330. ],
  331. ]);
  332. }
  333. /**
  334. * Power Action Specific Server
  335. *
  336. * @param Server $server
  337. * @param string $action
  338. * @return Response
  339. */
  340. public static function powerAction(Server $server, $action)
  341. {
  342. return self::clientAdmin()->post("/client/servers/{$server->identifier}/power", [
  343. 'signal' => $action,
  344. ]);
  345. }
  346. /**
  347. * Get info about user
  348. */
  349. public static function getClientUser()
  350. {
  351. return self::clientAdmin()->get('/client/account');
  352. }
  353. /**
  354. * Check if node has enough free resources to allocate the given resources
  355. *
  356. * @param Node $node
  357. * @param int $requireMemory
  358. * @param int $requireDisk
  359. * @return bool
  360. */
  361. public static function checkNodeResources(Node $node, int $requireMemory, int $requireDisk)
  362. {
  363. try {
  364. $response = self::client()->get("/application/nodes/{$node->id}");
  365. } catch (Exception $e) {
  366. throw self::getException($e->getMessage());
  367. }
  368. $node = $response['attributes'];
  369. $freeMemory = ($node['memory'] * ($node['memory_overallocate'] + 100) / 100) - $node['allocated_resources']['memory'];
  370. $freeDisk = ($node['disk'] * ($node['disk_overallocate'] + 100) / 100) - $node['allocated_resources']['disk'];
  371. if ($freeMemory < $requireMemory) {
  372. return false;
  373. }
  374. if ($freeDisk < $requireDisk) {
  375. return false;
  376. }
  377. return true;
  378. }
  379. }