diff --git a/.env.example b/.env.example index 45644374d..4bcc8f12c 100644 --- a/.env.example +++ b/.env.example @@ -32,6 +32,4 @@ QUEUE_HIGH=high QUEUE_STANDARD=standard QUEUE_LOW=low -SQS_KEY=aws-public -SQS_SECRET=aws-secret -SQS_QUEUE_PREFIX=aws-queue-prefix +SERVICE_AUTHOR=undefined@unknown-author.com diff --git a/.travis.yml b/.travis.yml index b0590806e..a29a33663 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,7 +15,7 @@ before_install: before_script: - cp .env.travis .env - composer install --no-interaction --prefer-dist --no-suggest --verbose - - php artisan migrate --seed -v + - php artisan migrate -v script: - vendor/bin/phpunit --coverage-clover coverage.xml notifications: diff --git a/CHANGELOG.md b/CHANGELOG.md index 24157763a..990cc2937 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ This project follows [Semantic Versioning](http://semver.org) guidelines. * New CLI command to disabled 2-Factor Authentication on an account if necessary. * Ability to delete users and locations via the CLI. * You can now require 2FA for all users, admins only, or at will using a simple configuration in the Admin CP. +* Added ability to export and import service options and their associated settings and environment variables via the Admin CP. ### Changed * Theme colors and login pages updated to give a more unique feel to the project. diff --git a/app/Console/Commands/Environment/AppSettingsCommand.php b/app/Console/Commands/Environment/AppSettingsCommand.php index 95591ac6f..709ed226d 100644 --- a/app/Console/Commands/Environment/AppSettingsCommand.php +++ b/app/Console/Commands/Environment/AppSettingsCommand.php @@ -9,7 +9,6 @@ namespace Pterodactyl\Console\Commands\Environment; -use Ramsey\Uuid\Uuid; use Illuminate\Console\Command; use Illuminate\Contracts\Console\Kernel; use Pterodactyl\Traits\Commands\EnvironmentWriterTrait; @@ -38,6 +37,7 @@ class AppSettingsCommand extends Command * @var string */ protected $signature = 'p:environment:setup + {--author= : The email that services created on this instance should be linked to.} {--url= : The URL that this Panel is running on.} {--timezone= : The timezone to use for Panel times.} {--cache= : The cache driver backend to use.} @@ -72,9 +72,10 @@ class AppSettingsCommand extends Command */ public function handle() { - if (is_null($this->config->get('pterodactyl.service.author'))) { - $this->variables['SERVICE_AUTHOR'] = Uuid::uuid4()->toString(); - } + $this->output->comment(trans('command/messages.environment.app.author_help')); + $this->variables['SERVICE_AUTHOR'] = $this->option('author') ?? $this->ask( + trans('command/messages.environment.app.author'), $this->config->get('pterodactyl.service.author', 'undefined@unknown-author.com') + ); $this->output->comment(trans('command/messages.environment.app.app_url_help')); $this->variables['APP_URL'] = $this->option('url') ?? $this->ask( diff --git a/app/Console/Commands/Server/RebuildServerCommand.php b/app/Console/Commands/Server/RebuildServerCommand.php index 562b10bd9..c6b562b06 100644 --- a/app/Console/Commands/Server/RebuildServerCommand.php +++ b/app/Console/Commands/Server/RebuildServerCommand.php @@ -12,12 +12,17 @@ namespace Pterodactyl\Console\Commands\Server; use Webmozart\Assert\Assert; use Illuminate\Console\Command; use GuzzleHttp\Exception\RequestException; -use Pterodactyl\Services\Servers\EnvironmentService; use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; +use Pterodactyl\Services\Servers\ServerConfigurationStructureService; use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; class RebuildServerCommand extends Command { + /** + * @var \Pterodactyl\Services\Servers\ServerConfigurationStructureService + */ + protected $configurationStructureService; + /** * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface */ @@ -28,11 +33,6 @@ class RebuildServerCommand extends Command */ protected $description = 'Rebuild a single server, all servers on a node, or all servers on the panel.'; - /** - * @var \Pterodactyl\Services\Servers\EnvironmentService - */ - protected $environmentService; - /** * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface */ @@ -49,18 +49,18 @@ class RebuildServerCommand extends Command * RebuildServerCommand constructor. * * @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonRepository - * @param \Pterodactyl\Services\Servers\EnvironmentService $environmentService + * @param \Pterodactyl\Services\Servers\ServerConfigurationStructureService $configurationStructureService * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository */ public function __construct( DaemonServerRepositoryInterface $daemonRepository, - EnvironmentService $environmentService, + ServerConfigurationStructureService $configurationStructureService, ServerRepositoryInterface $repository ) { parent::__construct(); + $this->configurationStructureService = $configurationStructureService; $this->daemonRepository = $daemonRepository; - $this->environmentService = $environmentService; $this->repository = $repository; } @@ -74,19 +74,7 @@ class RebuildServerCommand extends Command $servers->each(function ($server) use ($bar) { $bar->clear(); - $json = [ - 'build' => [ - 'image' => $server->image, - 'env|overwrite' => $this->environmentService->process($server), - ], - 'service' => [ - 'type' => $server->option->service->folder, - 'option' => $server->option->tag, - 'pack' => object_get($server, 'pack.uuid'), - 'skip_scripts' => $server->skip_scripts, - ], - 'rebuild' => true, - ]; + $json = array_merge($this->configurationStructureService->handle($server), ['rebuild' => true]); try { $this->daemonRepository->setNode($server->node_id)->setAccessServer($server->uuid)->update($json); diff --git a/app/Contracts/Repository/Daemon/ServerRepositoryInterface.php b/app/Contracts/Repository/Daemon/ServerRepositoryInterface.php index c64853b8c..6b7a86d45 100644 --- a/app/Contracts/Repository/Daemon/ServerRepositoryInterface.php +++ b/app/Contracts/Repository/Daemon/ServerRepositoryInterface.php @@ -9,17 +9,20 @@ namespace Pterodactyl\Contracts\Repository\Daemon; +use Psr\Http\Message\ResponseInterface; + interface ServerRepositoryInterface extends BaseRepositoryInterface { /** * Create a new server on the daemon for the panel. * - * @param int $id + * @param array $structure * @param array $overrides - * @param bool $start * @return \Psr\Http\Message\ResponseInterface + * + * @throws \GuzzleHttp\Exception\RequestException */ - public function create($id, array $overrides = [], $start = false); + public function create(array $structure, array $overrides = []): ResponseInterface; /** * Update server details on the daemon. diff --git a/app/Contracts/Repository/EggRepositoryInterface.php b/app/Contracts/Repository/EggRepositoryInterface.php new file mode 100644 index 000000000..364fd0c6d --- /dev/null +++ b/app/Contracts/Repository/EggRepositoryInterface.php @@ -0,0 +1,61 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Contracts\Repository; + +use Pterodactyl\Models\Egg; +use Illuminate\Database\Eloquent\Collection; + +interface EggRepositoryInterface extends RepositoryInterface +{ + /** + * Return an egg with the variables relation attached. + * + * @param int $id + * @return \Pterodactyl\Models\Egg + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function getWithVariables(int $id): Egg; + + /** + * Return all eggs and their relations to be used in the daemon API. + * + * @return \Illuminate\Database\Eloquent\Collection + */ + public function getAllWithCopyAttributes(): Collection; + + /** + * Return an egg with the scriptFrom and configFrom relations loaded onto the model. + * + * @param int|string $value + * @param string $column + * @return \Pterodactyl\Models\Egg + */ + public function getWithCopyAttributes($value, string $column = 'id'): Egg; + + /** + * Return all of the data needed to export a service. + * + * @param int $id + * @return \Pterodactyl\Models\Egg + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function getWithExportAttributes(int $id): Egg; + + /** + * Confirm a copy script belongs to the same nest as the item trying to use it. + * + * @param int $copyFromId + * @param int $service + * @return bool + */ + public function isCopiableScript(int $copyFromId, int $service): bool; +} diff --git a/app/Contracts/Repository/OptionVariableRepositoryInterface.php b/app/Contracts/Repository/EggVariableRepositoryInterface.php similarity index 78% rename from app/Contracts/Repository/OptionVariableRepositoryInterface.php rename to app/Contracts/Repository/EggVariableRepositoryInterface.php index cb3bdc606..afaf7463b 100644 --- a/app/Contracts/Repository/OptionVariableRepositoryInterface.php +++ b/app/Contracts/Repository/EggVariableRepositoryInterface.php @@ -9,6 +9,6 @@ namespace Pterodactyl\Contracts\Repository; -interface OptionVariableRepositoryInterface extends RepositoryInterface +interface EggVariableRepositoryInterface extends RepositoryInterface { } diff --git a/app/Contracts/Repository/NestRepositoryInterface.php b/app/Contracts/Repository/NestRepositoryInterface.php new file mode 100644 index 000000000..b06f9730b --- /dev/null +++ b/app/Contracts/Repository/NestRepositoryInterface.php @@ -0,0 +1,45 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Contracts\Repository; + +use Pterodactyl\Models\Nest; + +interface NestRepositoryInterface extends RepositoryInterface +{ + /** + * Return a nest or all nests with their associated eggs, variables, and packs. + * + * @param int $id + * @return \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Nest + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function getWithEggs(int $id = null); + + /** + * Return a nest or all nests and the count of eggs, packs, and servers for that nest. + * + * @param int|null $id + * @return \Pterodactyl\Models\Nest|\Illuminate\Database\Eloquent\Collection + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function getWithCounts(int $id = null); + + /** + * Return a nest along with its associated eggs and the servers relation on those eggs. + * + * @param int $id + * @return \Pterodactyl\Models\Nest + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function getWithEggServers(int $id): Nest; +} diff --git a/app/Contracts/Repository/PackRepositoryInterface.php b/app/Contracts/Repository/PackRepositoryInterface.php index e634716c2..d6ad80785 100644 --- a/app/Contracts/Repository/PackRepositoryInterface.php +++ b/app/Contracts/Repository/PackRepositoryInterface.php @@ -14,12 +14,12 @@ use Pterodactyl\Contracts\Repository\Attributes\SearchableInterface; interface PackRepositoryInterface extends RepositoryInterface, SearchableInterface { /** - * Return a paginated listing of packs with their associated option and server count. + * Return a paginated listing of packs with their associated egg and server count. * * @param int $paginate * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator */ - public function paginateWithOptionAndServerCount($paginate = 50); + public function paginateWithEggAndServerCount($paginate = 50); /** * Return a pack with the associated server models attached to it. diff --git a/app/Contracts/Repository/ServiceOptionRepositoryInterface.php b/app/Contracts/Repository/ServiceOptionRepositoryInterface.php deleted file mode 100644 index 825c63d8b..000000000 --- a/app/Contracts/Repository/ServiceOptionRepositoryInterface.php +++ /dev/null @@ -1,38 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Contracts\Repository; - -interface ServiceOptionRepositoryInterface extends RepositoryInterface -{ - /** - * Return a service option with the variables relation attached. - * - * @param int $id - * @return mixed - */ - public function getWithVariables($id); - - /** - * Return a service option with the copyFrom relation loaded onto the model. - * - * @param int $id - * @return mixed - */ - public function getWithCopyFrom($id); - - /** - * Confirm a copy script belongs to the same service as the item trying to use it. - * - * @param int $copyFromId - * @param int $service - * @return bool - */ - public function isCopiableScript($copyFromId, $service); -} diff --git a/app/Contracts/Repository/ServiceRepositoryInterface.php b/app/Contracts/Repository/ServiceRepositoryInterface.php deleted file mode 100644 index 87919ae7a..000000000 --- a/app/Contracts/Repository/ServiceRepositoryInterface.php +++ /dev/null @@ -1,29 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Contracts\Repository; - -interface ServiceRepositoryInterface extends RepositoryInterface -{ - /** - * Return a service or all services with their associated options, variables, and packs. - * - * @param int $id - * @return \Illuminate\Support\Collection - */ - public function getWithOptions($id = null); - - /** - * Return a service along with its associated options and the servers relation on those options. - * - * @param int $id - * @return mixed - */ - public function getWithOptionServers($id); -} diff --git a/app/Contracts/Repository/ServiceVariableRepositoryInterface.php b/app/Contracts/Repository/ServiceVariableRepositoryInterface.php deleted file mode 100644 index e65e8a45f..000000000 --- a/app/Contracts/Repository/ServiceVariableRepositoryInterface.php +++ /dev/null @@ -1,14 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Contracts\Repository; - -interface ServiceVariableRepositoryInterface extends RepositoryInterface -{ -} diff --git a/app/Exceptions/DisplayException.php b/app/Exceptions/DisplayException.php index 72fb86c71..80c5771a5 100644 --- a/app/Exceptions/DisplayException.php +++ b/app/Exceptions/DisplayException.php @@ -14,6 +14,9 @@ use Throwable; class DisplayException extends PterodactylException { + const LEVEL_WARNING = 'warning'; + const LEVEL_ERROR = 'error'; + /** * @var string */ @@ -27,7 +30,7 @@ class DisplayException extends PterodactylException * @param string $level * @internal param mixed $log */ - public function __construct($message, Throwable $previous = null, $level = 'error') + public function __construct($message, Throwable $previous = null, $level = self::LEVEL_ERROR) { $this->level = $level; diff --git a/app/Exceptions/Http/Connection/DaemonConnectionException.php b/app/Exceptions/Http/Connection/DaemonConnectionException.php new file mode 100644 index 000000000..5718af63f --- /dev/null +++ b/app/Exceptions/Http/Connection/DaemonConnectionException.php @@ -0,0 +1,31 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Exceptions\Http\Connection; + +use GuzzleHttp\Exception\GuzzleException; +use Pterodactyl\Exceptions\DisplayException; + +class DaemonConnectionException extends DisplayException +{ + /** + * Throw a displayable exception caused by a daemon connection error. + * + * @param \GuzzleHttp\Exception\GuzzleException $previous + */ + public function __construct(GuzzleException $previous) + { + /** @var \GuzzleHttp\Psr7\Response|null $response */ + $response = method_exists($previous, 'getResponse') ? $previous->getResponse() : null; + + parent::__construct(trans('admin/server.exceptions.daemon_exception', [ + 'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(), + ]), $previous, DisplayException::LEVEL_WARNING); + } +} diff --git a/app/Exceptions/Service/Egg/BadJsonFormatException.php b/app/Exceptions/Service/Egg/BadJsonFormatException.php new file mode 100644 index 000000000..0246f031c --- /dev/null +++ b/app/Exceptions/Service/Egg/BadJsonFormatException.php @@ -0,0 +1,9 @@ +. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Http\Controllers\API\Admin; - -use Fractal; -use Illuminate\Http\Request; -use Pterodactyl\Models\Location; -use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Transformers\Admin\LocationTransformer; - -class LocationController extends Controller -{ - /** - * Controller to handle returning all locations on the system. - * - * @param \Illuminate\Http\Request $request - * @return array - */ - public function index(Request $request) - { - $this->authorize('location-list', $request->apiKey()); - - return Fractal::create() - ->collection(Location::all()) - ->transformWith(new LocationTransformer($request)) - ->withResourceName('location') - ->toArray(); - } -} diff --git a/app/Http/Controllers/API/Admin/NodeController.php b/app/Http/Controllers/API/Admin/NodeController.php deleted file mode 100644 index 8300b77bc..000000000 --- a/app/Http/Controllers/API/Admin/NodeController.php +++ /dev/null @@ -1,159 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Http\Controllers\API\Admin; - -use Log; -use Fractal; -use Illuminate\Http\Request; -use Pterodactyl\Models\Node; -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Repositories\NodeRepository; -use Pterodactyl\Transformers\Admin\NodeTransformer; -use Pterodactyl\Exceptions\DisplayValidationException; -use League\Fractal\Pagination\IlluminatePaginatorAdapter; - -class NodeController extends Controller -{ - /** - * Controller to handle returning all nodes on the system. - * - * @param \Illuminate\Http\Request $request - * @return array - */ - public function index(Request $request) - { - $this->authorize('node-list', $request->apiKey()); - - $nodes = Node::paginate(config('pterodactyl.paginate.api.nodes')); - $fractal = Fractal::create()->collection($nodes) - ->transformWith(new NodeTransformer($request)) - ->withResourceName('user') - ->paginateWith(new IlluminatePaginatorAdapter($nodes)); - - if (config('pterodactyl.api.include_on_list') && $request->input('include')) { - $fractal->parseIncludes(explode(',', $request->input('include'))); - } - - return $fractal->toArray(); - } - - /** - * Display information about a single node on the system. - * - * @param \Illuminate\Http\Request $request - * @param int $id - * @return array - */ - public function view(Request $request, $id) - { - $this->authorize('node-view', $request->apiKey()); - - $fractal = Fractal::create()->item(Node::findOrFail($id)); - if ($request->input('include')) { - $fractal->parseIncludes(explode(',', $request->input('include'))); - } - - return $fractal->transformWith(new NodeTransformer($request)) - ->withResourceName('node') - ->toArray(); - } - - /** - * Display information about a single node on the system. - * - * @param \Illuminate\Http\Request $request - * @param int $id - * @return \Illuminate\Http\JsonResponse - */ - public function viewConfig(Request $request, $id) - { - $this->authorize('node-view-config', $request->apiKey()); - - $node = Node::findOrFail($id); - - return response()->json(json_decode($node->getConfigurationAsJson())); - } - - /** - * Create a new node on the system. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\JsonResponse|array - */ - public function store(Request $request) - { - $this->authorize('node-create', $request->apiKey()); - - $repo = new NodeRepository; - try { - $node = $repo->create(array_merge( - $request->only([ - 'public', 'disk_overallocate', 'memory_overallocate', - ]), - $request->intersect([ - 'name', 'location_id', 'fqdn', - 'scheme', 'memory', 'disk', - 'daemonBase', 'daemonSFTP', 'daemonListen', - ]) - )); - - $fractal = Fractal::create()->item($node)->transformWith(new NodeTransformer($request)); - if ($request->input('include')) { - $fractal->parseIncludes(explode(',', $request->input('include'))); - } - - return $fractal->withResourceName('node')->toArray(); - } catch (DisplayValidationException $ex) { - return response()->json([ - 'error' => json_decode($ex->getMessage()), - ], 400); - } catch (DisplayException $ex) { - return response()->json([ - 'error' => $ex->getMessage(), - ], 400); - } catch (\Exception $ex) { - Log::error($ex); - - return response()->json([ - 'error' => 'An unhandled exception occured while attemping to create this node. Please try again.', - ], 500); - } - } - - /** - * Delete a node from the system. - * - * @param \Illuminate\Http\Request $request - * @param int $id - * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse - */ - public function delete(Request $request, $id) - { - $this->authorize('node-delete', $request->apiKey()); - - $repo = new NodeRepository; - try { - $repo->delete($id); - - return response('', 204); - } catch (DisplayException $ex) { - return response()->json([ - 'error' => $ex->getMessage(), - ], 400); - } catch (\Exception $ex) { - Log::error($ex); - - return response()->json([ - 'error' => 'An unhandled exception occured while attemping to delete this node. Please try again.', - ], 500); - } - } -} diff --git a/app/Http/Controllers/API/Admin/ServerController.php b/app/Http/Controllers/API/Admin/ServerController.php deleted file mode 100644 index 46d90cea4..000000000 --- a/app/Http/Controllers/API/Admin/ServerController.php +++ /dev/null @@ -1,414 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Http\Controllers\API\Admin; - -use Log; -use Fractal; -use Illuminate\Http\Request; -use Pterodactyl\Models\Server; -use GuzzleHttp\Exception\TransferException; -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Repositories\ServerRepository; -use Pterodactyl\Transformers\Admin\ServerTransformer; -use Pterodactyl\Exceptions\DisplayValidationException; -use League\Fractal\Pagination\IlluminatePaginatorAdapter; - -class ServerController extends Controller -{ - /** - * Controller to handle returning all servers on the system. - * - * @param \Illuminate\Http\Request $request - * @return array - */ - public function index(Request $request) - { - $this->authorize('server-list', $request->apiKey()); - - $servers = Server::paginate(config('pterodactyl.paginate.api.servers')); - $fractal = Fractal::create()->collection($servers) - ->transformWith(new ServerTransformer($request)) - ->withResourceName('user') - ->paginateWith(new IlluminatePaginatorAdapter($servers)); - - if (config('pterodactyl.api.include_on_list') && $request->input('include')) { - $fractal->parseIncludes(explode(',', $request->input('include'))); - } - - return $fractal->toArray(); - } - - /** - * Controller to handle returning information on a single server. - * - * @param \Illuminate\Http\Request $request - * @param int $id - * @return array - */ - public function view(Request $request, $id) - { - $this->authorize('server-view', $request->apiKey()); - - $server = Server::findOrFail($id); - $fractal = Fractal::create()->item($server); - - if ($request->input('include')) { - $fractal->parseIncludes(explode(',', $request->input('include'))); - } - - return $fractal->transformWith(new ServerTransformer($request)) - ->withResourceName('server') - ->toArray(); - } - - /** - * Create a new server on the system. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\JsonResponse|array - */ - public function store(Request $request) - { - $this->authorize('server-create', $request->apiKey()); - - $repo = new ServerRepository; - try { - $server = $repo->create($request->all()); - - $fractal = Fractal::create()->item($server)->transformWith(new ServerTransformer($request)); - if ($request->input('include')) { - $fractal->parseIncludes(explode(',', $request->input('include'))); - } - - return $fractal->withResourceName('server')->toArray(); - } catch (DisplayValidationException $ex) { - return response()->json([ - 'error' => json_decode($ex->getMessage()), - ], 400); - } catch (DisplayException $ex) { - return response()->json([ - 'error' => $ex->getMessage(), - ], 400); - } catch (TransferException $ex) { - Log::warning($ex); - - return response()->json([ - 'error' => 'A TransferException was encountered while trying to contact the daemon, please ensure it is online and accessible. This error has been logged.', - ], 504); - } catch (\Exception $ex) { - Log::error($ex); - - return response()->json([ - 'error' => 'An unhandled exception occured while attemping to add this server. Please try again.', - ], 500); - } - } - - /** - * Delete a server from the system. - * - * @param \Illuminate\Http\Request $request - * @param int $id - * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse - */ - public function delete(Request $request, $id) - { - $this->authorize('server-delete', $request->apiKey()); - - $repo = new ServerRepository; - try { - $repo->delete($id, $request->has('force_delete')); - - return response('', 204); - } catch (DisplayException $ex) { - return response()->json([ - 'error' => $ex->getMessage(), - ], 400); - } catch (TransferException $ex) { - Log::warning($ex); - - return response()->json([ - 'error' => 'A TransferException was encountered while trying to contact the daemon, please ensure it is online and accessible. This error has been logged.', - ], 504); - } catch (\Exception $ex) { - Log::error($ex); - - return response()->json([ - 'error' => 'An unhandled exception occured while attemping to add this server. Please try again.', - ], 500); - } - } - - /** - * Update the details for a server. - * - * @param \Illuminate\Http\Request $request - * @param int $id - * @return \Illuminate\Http\JsonResponse|array - */ - public function details(Request $request, $id) - { - $this->authorize('server-edit-details', $request->apiKey()); - - $repo = new ServerRepository; - try { - $server = $repo->updateDetails($id, $request->intersect([ - 'owner_id', 'name', 'description', 'reset_token', - ])); - - $fractal = Fractal::create()->item($server)->transformWith(new ServerTransformer($request)); - if ($request->input('include')) { - $fractal->parseIncludes(explode(',', $request->input('include'))); - } - - return $fractal->withResourceName('server')->toArray(); - } catch (DisplayValidationException $ex) { - return response()->json([ - 'error' => json_decode($ex->getMessage()), - ], 400); - } catch (DisplayException $ex) { - return response()->json([ - 'error' => $ex->getMessage(), - ], 400); - } catch (\Exception $ex) { - Log::error($ex); - - return response()->json([ - 'error' => 'An unhandled exception occured while attemping to modify this server. Please try again.', - ], 500); - } - } - - /** - * Set the new docker container for a server. - * - * @param \Illuminate\Http\Request $request - * @param int $id - * @return \Illuminate\Http\RedirectResponse|array - */ - public function container(Request $request, $id) - { - $this->authorize('server-edit-container', $request->apiKey()); - - $repo = new ServerRepository; - try { - $server = $repo->updateContainer($id, $request->intersect('docker_image')); - - $fractal = Fractal::create()->item($server)->transformWith(new ServerTransformer($request)); - if ($request->input('include')) { - $fractal->parseIncludes(explode(',', $request->input('include'))); - } - - return $fractal->withResourceName('server')->toArray(); - } catch (DisplayValidationException $ex) { - return response()->json([ - 'error' => json_decode($ex->getMessage()), - ], 400); - } catch (TransferException $ex) { - Log::warning($ex); - - return response()->json([ - 'error' => 'A TransferException was encountered while trying to contact the daemon, please ensure it is online and accessible. This error has been logged.', - ], 504); - } catch (\Exception $ex) { - Log::error($ex); - - return response()->json([ - 'error' => 'An unhandled exception occured while attemping to modify this server container. Please try again.', - ], 500); - } - } - - /** - * Toggles the install status for a server. - * - * @param \Illuminate\Http\Request $request - * @param int $id - * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse - */ - public function install(Request $request, $id) - { - $this->authorize('server-install', $request->apiKey()); - - $repo = new ServerRepository; - try { - $repo->toggleInstall($id); - - return response('', 204); - } catch (DisplayException $ex) { - return response()->json([ - 'error' => $ex->getMessage(), - ], 400); - } catch (\Exception $ex) { - Log::error($ex); - - return response()->json([ - 'error' => 'An unhandled exception occured while attemping to toggle the install status for this server. Please try again.', - ], 500); - } - } - - /** - * Setup a server to have a container rebuild. - * - * @param \Illuminate\Http\Request $request - * @param int $id - * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse - */ - public function rebuild(Request $request, $id) - { - $this->authorize('server-rebuild', $request->apiKey()); - $server = Server::with('node')->findOrFail($id); - - try { - $server->node->guzzleClient([ - 'X-Access-Server' => $server->uuid, - 'X-Access-Token' => $server->node->daemonSecret, - ])->request('POST', '/server/rebuild'); - - return response('', 204); - } catch (TransferException $ex) { - Log::warning($ex); - - return response()->json([ - 'error' => 'A TransferException was encountered while trying to contact the daemon, please ensure it is online and accessible. This error has been logged.', - ], 504); - } - } - - /** - * Manage the suspension status for a server. - * - * @param \Illuminate\Http\Request $request - * @param int $id - * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse - */ - public function suspend(Request $request, $id) - { - $this->authorize('server-suspend', $request->apiKey()); - - $repo = new ServerRepository; - $action = $request->input('action'); - if (! in_array($action, ['suspend', 'unsuspend'])) { - return response()->json([ - 'error' => 'The action provided was invalid. Action should be one of: suspend, unsuspend.', - ], 400); - } - - try { - $repo->toggleAccess($id, ($action === 'unsuspend')); - - return response('', 204); - } catch (DisplayException $ex) { - return response()->json([ - 'error' => $ex->getMessage(), - ], 400); - } catch (TransferException $ex) { - Log::warning($ex); - - return response()->json([ - 'error' => 'A TransferException was encountered while trying to contact the daemon, please ensure it is online and accessible. This error has been logged.', - ], 504); - } catch (\Exception $ex) { - Log::error($ex); - - return response()->json([ - 'error' => 'An unhandled exception occured while attemping to ' . $action . ' this server. Please try again.', - ], 500); - } - } - - /** - * Update the build configuration for a server. - * - * @param \Illuminate\Http\Request $request - * @param int $id - * @return \Illuminate\Http\JsonResponse|array - */ - public function build(Request $request, $id) - { - $this->authorize('server-edit-build', $request->apiKey()); - - $repo = new ServerRepository; - try { - $server = $repo->changeBuild($id, $request->intersect([ - 'allocation_id', 'add_allocations', 'remove_allocations', - 'memory', 'swap', 'io', 'cpu', - ])); - - $fractal = Fractal::create()->item($server)->transformWith(new ServerTransformer($request)); - if ($request->input('include')) { - $fractal->parseIncludes(explode(',', $request->input('include'))); - } - - return $fractal->withResourceName('server')->toArray(); - } catch (DisplayValidationException $ex) { - return response()->json([ - 'error' => json_decode($ex->getMessage()), - ], 400); - } catch (DisplayException $ex) { - return response()->json([ - 'error' => $ex->getMessage(), - ], 400); - } catch (TransferException $ex) { - Log::warning($ex); - - return response()->json([ - 'error' => 'A TransferException was encountered while trying to contact the daemon, please ensure it is online and accessible. This error has been logged.', - ], 504); - } catch (\Exception $ex) { - Log::error($ex); - - return response()->json([ - 'error' => 'An unhandled exception occured while attemping to modify the build settings for this server. Please try again.', - ], 500); - } - } - - /** - * Update the startup command as well as variables. - * - * @param \Illuminate\Http\Request $request - * @param int $id - * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse - */ - public function startup(Request $request, $id) - { - $this->authorize('server-edit-startup', $request->apiKey()); - - $repo = new ServerRepository; - try { - $repo->updateStartup($id, $request->all(), true); - - return response('', 204); - } catch (DisplayValidationException $ex) { - return response()->json([ - 'error' => json_decode($ex->getMessage()), - ], 400); - } catch (DisplayException $ex) { - return response()->json([ - 'error' => $ex->getMessage(), - ], 400); - } catch (TransferException $ex) { - Log::warning($ex); - - return response()->json([ - 'error' => 'A TransferException was encountered while trying to contact the daemon, please ensure it is online and accessible. This error has been logged.', - ], 504); - } catch (\Exception $ex) { - Log::error($ex); - - return response()->json([ - 'error' => 'An unhandled exception occured while attemping to modify the startup settings for this server. Please try again.', - ], 500); - } - } -} diff --git a/app/Http/Controllers/API/Admin/ServiceController.php b/app/Http/Controllers/API/Admin/ServiceController.php deleted file mode 100644 index 2751e37a5..000000000 --- a/app/Http/Controllers/API/Admin/ServiceController.php +++ /dev/null @@ -1,59 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Http\Controllers\API\Admin; - -use Fractal; -use Illuminate\Http\Request; -use Pterodactyl\Models\Service; -use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Transformers\Admin\ServiceTransformer; - -class ServiceController extends Controller -{ - /** - * Controller to handle returning all locations on the system. - * - * @param \Illuminate\Http\Request $request - * @return array - */ - public function index(Request $request) - { - $this->authorize('service-list', $request->apiKey()); - - return Fractal::create() - ->collection(Service::all()) - ->transformWith(new ServiceTransformer($request)) - ->withResourceName('service') - ->toArray(); - } - - /** - * Controller to handle returning information on a single server. - * - * @param \Illuminate\Http\Request $request - * @param int $id - * @return array - */ - public function view(Request $request, $id) - { - $this->authorize('service-view', $request->apiKey()); - - $service = Service::findOrFail($id); - $fractal = Fractal::create()->item($service); - - if ($request->input('include')) { - $fractal->parseIncludes(explode(',', $request->input('include'))); - } - - return $fractal->transformWith(new ServiceTransformer($request)) - ->withResourceName('service') - ->toArray(); - } -} diff --git a/app/Http/Controllers/API/Admin/UserController.php b/app/Http/Controllers/API/Admin/UserController.php deleted file mode 100644 index 8f393cae4..000000000 --- a/app/Http/Controllers/API/Admin/UserController.php +++ /dev/null @@ -1,170 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Http\Controllers\API\Admin; - -use Log; -use Fractal; -use Illuminate\Http\Request; -use Pterodactyl\Models\User; -use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Repositories\oldUserRepository; -use Pterodactyl\Transformers\Admin\UserTransformer; -use Pterodactyl\Exceptions\DisplayValidationException; -use League\Fractal\Pagination\IlluminatePaginatorAdapter; - -class UserController extends Controller -{ - /** - * Controller to handle returning all users on the system. - * - * @param \Illuminate\Http\Request $request - * @return array - */ - public function index(Request $request) - { - $this->authorize('user-list', $request->apiKey()); - - $users = User::paginate(config('pterodactyl.paginate.api.users')); - $fractal = Fractal::create()->collection($users) - ->transformWith(new UserTransformer($request)) - ->withResourceName('user') - ->paginateWith(new IlluminatePaginatorAdapter($users)); - - if (config('pterodactyl.api.include_on_list') && $request->input('include')) { - $fractal->parseIncludes(explode(',', $request->input('include'))); - } - - return $fractal->toArray(); - } - - /** - * Display information about a single user on the system. - * - * @param \Illuminate\Http\Request $request - * @param int $id - * @return array - */ - public function view(Request $request, $id) - { - $this->authorize('user-view', $request->apiKey()); - - $fractal = Fractal::create()->item(User::findOrFail($id)); - if ($request->input('include')) { - $fractal->parseIncludes(explode(',', $request->input('include'))); - } - - return $fractal->transformWith(new UserTransformer($request)) - ->withResourceName('user') - ->toArray(); - } - - /** - * Create a new user on the system. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\JsonResponse|array - */ - public function store(Request $request) - { - $this->authorize('user-create', $request->apiKey()); - - $repo = new oldUserRepository; - try { - $user = $repo->create($request->only([ - 'custom_id', 'email', 'password', 'name_first', - 'name_last', 'username', 'root_admin', - ])); - - $fractal = Fractal::create()->item($user)->transformWith(new UserTransformer($request)); - if ($request->input('include')) { - $fractal->parseIncludes(explode(',', $request->input('include'))); - } - - return $fractal->withResourceName('user')->toArray(); - } catch (DisplayValidationException $ex) { - return response()->json([ - 'error' => json_decode($ex->getMessage()), - ], 400); - } catch (\Exception $ex) { - Log::error($ex); - - return response()->json([ - 'error' => 'An unhandled exception occured while attemping to create this user. Please try again.', - ], 500); - } - } - - /** - * Update a user. - * - * @param \Illuminate\Http\Request $request - * @param int $user - * @return \Illuminate\Http\RedirectResponse - */ - public function update(Request $request, $user) - { - $this->authorize('user-edit', $request->apiKey()); - - $repo = new oldUserRepository; - try { - $user = $repo->update($user, $request->intersect([ - 'email', 'password', 'name_first', - 'name_last', 'username', 'root_admin', - ])); - - $fractal = Fractal::create()->item($user)->transformWith(new UserTransformer($request)); - if ($request->input('include')) { - $fractal->parseIncludes(explode(',', $request->input('include'))); - } - - return $fractal->withResourceName('user')->toArray(); - } catch (DisplayValidationException $ex) { - return response()->json([ - 'error' => json_decode($ex->getMessage()), - ], 400); - } catch (\Exception $ex) { - Log::error($ex); - - return response()->json([ - 'error' => 'An unhandled exception occured while attemping to update this user. Please try again.', - ], 500); - } - } - - /** - * Delete a user from the system. - * - * @param \Illuminate\Http\Request $request - * @param int $id - * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse - */ - public function delete(Request $request, $id) - { - $this->authorize('user-delete', $request->apiKey()); - - $repo = new oldUserRepository; - try { - $repo->delete($id); - - return response('', 204); - } catch (DisplayException $ex) { - return response()->json([ - 'error' => $ex->getMessage(), - ], 400); - } catch (\Exception $ex) { - Log::error($ex); - - return response()->json([ - 'error' => 'An unhandled exception occured while attemping to delete this user. Please try again.', - ], 500); - } - } -} diff --git a/app/Http/Controllers/API/Remote/EggRetrievalController.php b/app/Http/Controllers/API/Remote/EggRetrievalController.php new file mode 100644 index 000000000..6e45ae346 --- /dev/null +++ b/app/Http/Controllers/API/Remote/EggRetrievalController.php @@ -0,0 +1,74 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Http\Controllers\API\Remote; + +use Illuminate\Http\JsonResponse; +use Pterodactyl\Http\Controllers\Controller; +use Pterodactyl\Services\Eggs\EggConfigurationService; +use Pterodactyl\Contracts\Repository\EggRepositoryInterface; + +class EggRetrievalController extends Controller +{ + /** + * @var \Pterodactyl\Services\Eggs\EggConfigurationService + */ + protected $configurationFileService; + + /** + * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface + */ + protected $repository; + + /** + * OptionUpdateController constructor. + * + * @param \Pterodactyl\Contracts\Repository\EggRepositoryInterface $repository + * @param \Pterodactyl\Services\Eggs\EggConfigurationService $configurationFileService + */ + public function __construct( + EggRepositoryInterface $repository, + EggConfigurationService $configurationFileService + ) { + $this->configurationFileService = $configurationFileService; + $this->repository = $repository; + } + + /** + * Return a JSON array of Eggs and the SHA1 hash of thier configuration file. + * + * @return \Illuminate\Http\JsonResponse + */ + public function index(): JsonResponse + { + $eggs = $this->repository->getAllWithCopyAttributes(); + + $response = []; + $eggs->each(function ($egg) use (&$response) { + $response[$egg->uuid] = sha1(json_encode($this->configurationFileService->handle($egg))); + }); + + return response()->json($response); + } + + /** + * Return the configuration file for a single Egg for the Daemon. + * + * @param string $uuid + * @return \Illuminate\Http\JsonResponse + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function download(string $uuid): JsonResponse + { + $option = $this->repository->getWithCopyAttributes($uuid, 'uuid'); + + return response()->json($this->configurationFileService->handle($option)); + } +} diff --git a/app/Http/Controllers/API/User/CoreController.php b/app/Http/Controllers/API/User/CoreController.php deleted file mode 100644 index 7d5ae8d8b..000000000 --- a/app/Http/Controllers/API/User/CoreController.php +++ /dev/null @@ -1,36 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Http\Controllers\API\User; - -use Fractal; -use Illuminate\Http\Request; -use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Transformers\User\OverviewTransformer; - -class CoreController extends Controller -{ - /** - * Controller to handle base user request for all of their servers. - * - * @param \Illuminate\Http\Request $request - * @return array - */ - public function index(Request $request) - { - $this->authorize('user.server-list', $request->apiKey()); - - $servers = $request->user()->access('service', 'node', 'allocation', 'option')->get(); - - return Fractal::collection($servers) - ->transformWith(new OverviewTransformer) - ->withResourceName('server') - ->toArray(); - } -} diff --git a/app/Http/Controllers/API/User/ServerController.php b/app/Http/Controllers/API/User/ServerController.php deleted file mode 100644 index 270c05339..000000000 --- a/app/Http/Controllers/API/User/ServerController.php +++ /dev/null @@ -1,84 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Http\Controllers\API\User; - -use Fractal; -use Illuminate\Http\Request; -use Pterodactyl\Models\Server; -use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Transformers\User\ServerTransformer; -use Pterodactyl\Repositories\old_Daemon\PowerRepository; -use Pterodactyl\Repositories\old_Daemon\CommandRepository; - -class ServerController extends Controller -{ - /** - * Controller to handle base request for individual server information. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @return array - */ - public function index(Request $request, $uuid) - { - $this->authorize('user.server-view', $request->apiKey()); - - $server = Server::byUuid($uuid); - $fractal = Fractal::create()->item($server); - - if ($request->input('include')) { - $fractal->parseIncludes(explode(',', $request->input('include'))); - } - - return $fractal->transformWith(new ServerTransformer) - ->withResourceName('server') - ->toArray(); - } - - /** - * Controller to handle request for server power toggle. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @return \Illuminate\Http\Response - */ - public function power(Request $request, $uuid) - { - $this->authorize('user.server-power', $request->apiKey()); - - $server = Server::byUuid($uuid); - $request->user()->can('power-' . $request->input('action'), $server); - - $repo = new PowerRepository($server, $request->user()); - $repo->do($request->input('action')); - - return response('', 204)->header('Content-Type', 'application/json'); - } - - /** - * Controller to handle base request for individual server information. - * - * @param \Illuminate\Http\Request $request - * @param string $uuid - * @return \Illuminate\Http\Response - */ - public function command(Request $request, $uuid) - { - $this->authorize('user.server-command', $request->apiKey()); - - $server = Server::byUuid($uuid); - $request->user()->can('send-command', $server); - - $repo = new CommandRepository($server, $request->user()); - $repo->send($request->input('command')); - - return response('', 204)->header('Content-Type', 'application/json'); - } -} diff --git a/app/Http/Controllers/Admin/Nests/EggController.php b/app/Http/Controllers/Admin/Nests/EggController.php new file mode 100644 index 000000000..56d69e3a7 --- /dev/null +++ b/app/Http/Controllers/Admin/Nests/EggController.php @@ -0,0 +1,128 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Http\Controllers\Admin\Nests; + +use Javascript; +use Illuminate\View\View; +use Pterodactyl\Models\Egg; +use Illuminate\Http\RedirectResponse; +use Prologue\Alerts\AlertsMessageBag; +use Pterodactyl\Http\Controllers\Controller; +use Pterodactyl\Services\Eggs\EggUpdateService; +use Pterodactyl\Services\Eggs\EggCreationService; +use Pterodactyl\Services\Eggs\EggDeletionService; +use Pterodactyl\Http\Requests\Admin\Egg\EggFormRequest; +use Pterodactyl\Contracts\Repository\EggRepositoryInterface; +use Pterodactyl\Contracts\Repository\NestRepositoryInterface; + +class EggController extends Controller +{ + protected $alert; + protected $creationService; + protected $deletionService; + protected $nestRepository; + protected $repository; + protected $updateService; + + public function __construct( + AlertsMessageBag $alert, + EggCreationService $creationService, + EggDeletionService $deletionService, + EggRepositoryInterface $repository, + EggUpdateService $updateService, + NestRepositoryInterface $nestRepository + ) { + $this->alert = $alert; + $this->creationService = $creationService; + $this->deletionService = $deletionService; + $this->nestRepository = $nestRepository; + $this->repository = $repository; + $this->updateService = $updateService; + } + + /** + * Handle a request to display the Egg creation page. + * + * @return \Illuminate\View\View + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function create(): View + { + $nests = $this->nestRepository->getWithEggs(); + Javascript::put(['nests' => $nests->keyBy('id')]); + + return view('admin.eggs.new', ['nests' => $nests]); + } + + /** + * Handle request to store a new Egg. + * + * @param \Pterodactyl\Http\Requests\Admin\Egg\EggFormRequest $request + * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Service\Egg\NoParentConfigurationFoundException + */ + public function store(EggFormRequest $request): RedirectResponse + { + $egg = $this->creationService->handle($request->normalize()); + $this->alert->success(trans('admin/nests.eggs.notices.egg_created'))->flash(); + + return redirect()->route('admin.nests.egg.view', $egg->id); + } + + /** + * Handle request to view a single Egg. + * + * @param \Pterodactyl\Models\Egg $egg + * @return \Illuminate\View\View + */ + public function view(Egg $egg): View + { + return view('admin.eggs.view', ['egg' => $egg]); + } + + /** + * Handle request to update an Egg. + * + * @param \Pterodactyl\Http\Requests\Admin\Egg\EggFormRequest $request + * @param \Pterodactyl\Models\Egg $egg + * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\Egg\NoParentConfigurationFoundException + */ + public function update(EggFormRequest $request, Egg $egg): RedirectResponse + { + $this->updateService->handle($egg, $request->normalize()); + $this->alert->success(trans('admin/nests.eggs.notices.updated'))->flash(); + + return redirect()->route('admin.nests.egg.view', $egg->id); + } + + /** + * Handle request to destroy an egg. + * + * @param \Pterodactyl\Models\Egg $egg + * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\Service\Egg\HasChildrenException + * @throws \Pterodactyl\Exceptions\Service\HasActiveServersException + */ + public function destroy(Egg $egg): RedirectResponse + { + $this->deletionService->handle($egg->id); + $this->alert->success(trans('admin/nests.eggs.notices.deleted'))->flash(); + + return redirect()->route('admin.nests.view', $egg->nest_id); + } +} diff --git a/app/Http/Controllers/Admin/Nests/EggScriptController.php b/app/Http/Controllers/Admin/Nests/EggScriptController.php new file mode 100644 index 000000000..ac67a2a6d --- /dev/null +++ b/app/Http/Controllers/Admin/Nests/EggScriptController.php @@ -0,0 +1,98 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Http\Controllers\Admin\Nests; + +use Illuminate\View\View; +use Illuminate\Http\RedirectResponse; +use Prologue\Alerts\AlertsMessageBag; +use Pterodactyl\Http\Controllers\Controller; +use Pterodactyl\Services\Eggs\Scripts\InstallScriptService; +use Pterodactyl\Contracts\Repository\EggRepositoryInterface; +use Pterodactyl\Http\Requests\Admin\Egg\EggScriptFormRequest; + +class EggScriptController extends Controller +{ + /** + * @var \Prologue\Alerts\AlertsMessageBag + */ + protected $alert; + + /** + * @var \Pterodactyl\Services\Eggs\Scripts\InstallScriptService + */ + protected $installScriptService; + + /** + * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface + */ + protected $repository; + + /** + * EggScriptController constructor. + * + * @param \Prologue\Alerts\AlertsMessageBag $alert + * @param \Pterodactyl\Contracts\Repository\EggRepositoryInterface $repository + * @param \Pterodactyl\Services\Eggs\Scripts\InstallScriptService $installScriptService + */ + public function __construct( + AlertsMessageBag $alert, + EggRepositoryInterface $repository, + InstallScriptService $installScriptService + ) { + $this->alert = $alert; + $this->installScriptService = $installScriptService; + $this->repository = $repository; + } + + /** + * Handle requests to render installation script for an Egg. + * + * @param int $egg + * @return \Illuminate\View\View + */ + public function index(int $egg): View + { + $egg = $this->repository->getWithCopyAttributes($egg); + $copy = $this->repository->findWhere([ + ['copy_script_from', '=', null], + ['nest_id', '=', $egg->nest_id], + ['id', '!=', $egg], + ]); + + $rely = $this->repository->findWhere([ + ['copy_script_from', '=', $egg->id], + ]); + + return view('admin.eggs.scripts', [ + 'copyFromOptions' => $copy, + 'relyOnScript' => $rely, + 'egg' => $egg, + ]); + } + + /** + * Handle a request to update the installation script for an Egg. + * + * @param \Pterodactyl\Http\Requests\Admin\Egg\EggScriptFormRequest $request + * @param int $egg + * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\Egg\InvalidCopyFromException + */ + public function update(EggScriptFormRequest $request, int $egg): RedirectResponse + { + $this->installScriptService->handle($egg, $request->normalize()); + $this->alert->success(trans('admin/nests.eggs.notices.script_updated'))->flash(); + + return redirect()->route('admin.nests.egg.scripts', $egg); + } +} diff --git a/app/Http/Controllers/Admin/Nests/EggShareController.php b/app/Http/Controllers/Admin/Nests/EggShareController.php new file mode 100644 index 000000000..b56f8fd2f --- /dev/null +++ b/app/Http/Controllers/Admin/Nests/EggShareController.php @@ -0,0 +1,118 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Http\Controllers\Admin\Nests; + +use Pterodactyl\Models\Egg; +use Illuminate\Http\RedirectResponse; +use Prologue\Alerts\AlertsMessageBag; +use Pterodactyl\Http\Controllers\Controller; +use Symfony\Component\HttpFoundation\Response; +use Pterodactyl\Services\Eggs\Sharing\EggExporterService; +use Pterodactyl\Services\Eggs\Sharing\EggImporterService; +use Pterodactyl\Http\Requests\Admin\Egg\EggImportFormRequest; +use Pterodactyl\Services\Eggs\Sharing\EggUpdateImporterService; + +class EggShareController extends Controller +{ + /** + * @var \Prologue\Alerts\AlertsMessageBag + */ + protected $alert; + + /** + * @var \Pterodactyl\Services\Eggs\Sharing\EggExporterService + */ + protected $exporterService; + + /** + * @var \Pterodactyl\Services\Eggs\Sharing\EggImporterService + */ + protected $importerService; + + /** + * @var \Pterodactyl\Services\Eggs\Sharing\EggUpdateImporterService + */ + protected $updateImporterService; + + /** + * OptionShareController constructor. + * + * @param \Prologue\Alerts\AlertsMessageBag $alert + * @param \Pterodactyl\Services\Eggs\Sharing\EggExporterService $exporterService + * @param \Pterodactyl\Services\Eggs\Sharing\EggImporterService $importerService + * @param \Pterodactyl\Services\Eggs\Sharing\EggUpdateImporterService $updateImporterService + */ + public function __construct( + AlertsMessageBag $alert, + EggExporterService $exporterService, + EggImporterService $importerService, + EggUpdateImporterService $updateImporterService + ) { + $this->alert = $alert; + $this->exporterService = $exporterService; + $this->importerService = $importerService; + $this->updateImporterService = $updateImporterService; + } + + /** + * @param \Pterodactyl\Models\Egg $egg + * @return \Symfony\Component\HttpFoundation\Response + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function export(Egg $egg): Response + { + return response($this->exporterService->handle($egg->id), 200, [ + 'Content-Transfer-Encoding' => 'binary', + 'Content-Description' => 'File Transfer', + 'Content-Disposition' => 'attachment; filename=egg-' . kebab_case($egg->name) . '.json', + 'Content-Type' => 'application/json', + ]); + } + + /** + * Import a new service option using an XML file. + * + * @param \Pterodactyl\Http\Requests\Admin\Egg\EggImportFormRequest $request + * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\Egg\BadJsonFormatException + * @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException + */ + public function import(EggImportFormRequest $request): RedirectResponse + { + $egg = $this->importerService->handle($request->file('import_file'), $request->input('import_to_nest')); + $this->alert->success(trans('admin/nests.eggs.notices.imported'))->flash(); + + return redirect()->route('admin.nests.egg.view', ['egg' => $egg->id]); + } + + /** + * Update an existing Egg using a new imported file. + * + * @param \Pterodactyl\Http\Requests\Admin\Egg\EggImportFormRequest $request + * @param int $egg + * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\Egg\BadJsonFormatException + * @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException + */ + public function update(EggImportFormRequest $request, int $egg): RedirectResponse + { + $this->updateImporterService->handle($egg, $request->file('import_file')); + $this->alert->success(trans('admin/nests.eggs.notices.updated_via_import'))->flash(); + + return redirect()->route('admin.nests.egg.view', ['egg' => $egg]); + } +} diff --git a/app/Http/Controllers/Admin/Nests/EggVariableController.php b/app/Http/Controllers/Admin/Nests/EggVariableController.php new file mode 100644 index 000000000..8b68743fc --- /dev/null +++ b/app/Http/Controllers/Admin/Nests/EggVariableController.php @@ -0,0 +1,146 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Http\Controllers\Admin\Nests; + +use Illuminate\View\View; +use Pterodactyl\Models\Egg; +use Pterodactyl\Models\EggVariable; +use Illuminate\Http\RedirectResponse; +use Prologue\Alerts\AlertsMessageBag; +use Pterodactyl\Http\Controllers\Controller; +use Pterodactyl\Contracts\Repository\EggRepositoryInterface; +use Pterodactyl\Services\Eggs\Variables\VariableUpdateService; +use Pterodactyl\Http\Requests\Admin\Egg\EggVariableFormRequest; +use Pterodactyl\Services\Eggs\Variables\VariableCreationService; +use Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface; + +class EggVariableController extends Controller +{ + /** + * @var \Prologue\Alerts\AlertsMessageBag + */ + protected $alert; + + /** + * @var \Pterodactyl\Services\Eggs\Variables\VariableCreationService + */ + protected $creationService; + + /** + * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface + */ + protected $repository; + + /** + * @var \Pterodactyl\Services\Eggs\Variables\VariableUpdateService + */ + protected $updateService; + + /** + * @var \Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface + */ + protected $variableRepository; + + /** + * EggVariableController constructor. + * + * @param \Prologue\Alerts\AlertsMessageBag $alert + * @param \Pterodactyl\Services\Eggs\Variables\VariableCreationService $creationService + * @param \Pterodactyl\Services\Eggs\Variables\VariableUpdateService $updateService + * @param \Pterodactyl\Contracts\Repository\EggRepositoryInterface $repository + * @param \Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface $variableRepository + */ + public function __construct( + AlertsMessageBag $alert, + VariableCreationService $creationService, + VariableUpdateService $updateService, + EggRepositoryInterface $repository, + EggVariableRepositoryInterface $variableRepository + ) { + $this->alert = $alert; + $this->creationService = $creationService; + $this->repository = $repository; + $this->updateService = $updateService; + $this->variableRepository = $variableRepository; + } + + /** + * Handle request to view the variables attached to an Egg. + * + * @param int $egg + * @return \Illuminate\View\View + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function view(int $egg): View + { + $egg = $this->repository->getWithVariables($egg); + + return view('admin.eggs.variables', ['egg' => $egg]); + } + + /** + * Handle a request to create a new Egg variable. + * + * @param \Pterodactyl\Http\Requests\Admin\Egg\EggVariableFormRequest $request + * @param \Pterodactyl\Models\Egg $egg + * + * @return \Illuminate\Http\RedirectResponse + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Service\Egg\Variable\ReservedVariableNameException + */ + public function store(EggVariableFormRequest $request, Egg $egg): RedirectResponse + { + $this->creationService->handle($egg->id, $request->normalize()); + $this->alert->success(trans('admin/nests.variables.notices.variable_created'))->flash(); + + return redirect()->route('admin.nests.egg.variables', $egg->id); + } + + /** + * Handle a request to update an existing Egg variable. + * + * @param \Pterodactyl\Http\Requests\Admin\Egg\EggVariableFormRequest $request + * @param \Pterodactyl\Models\Egg $egg + * @param \Pterodactyl\Models\EggVariable $variable + * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\Egg\Variable\ReservedVariableNameException + */ + public function update(EggVariableFormRequest $request, Egg $egg, EggVariable $variable): RedirectResponse + { + $this->updateService->handle($variable, $request->normalize()); + $this->alert->success(trans('admin/nests.variables.notices.variable_updated', [ + 'variable' => $variable->name, + ]))->flash(); + + return redirect()->route('admin.nests.egg.variables', $egg->id); + } + + /** + * Handle a request to delete an existing Egg variable from the Panel. + * + * @param int $egg + * @param \Pterodactyl\Models\EggVariable $variable + * @return \Illuminate\Http\RedirectResponse + */ + public function destroy(int $egg, EggVariable $variable): RedirectResponse + { + $this->variableRepository->delete($variable->id); + $this->alert->success(trans('admin/nests.variables.notices.variable_deleted', [ + 'variable' => $variable->name, + ]))->flash(); + + return redirect()->route('admin.nests.egg.variables', $egg); + } +} diff --git a/app/Http/Controllers/Admin/Nests/NestController.php b/app/Http/Controllers/Admin/Nests/NestController.php new file mode 100644 index 000000000..b62753cad --- /dev/null +++ b/app/Http/Controllers/Admin/Nests/NestController.php @@ -0,0 +1,160 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Http\Controllers\Admin\Nests; + +use Illuminate\View\View; +use Illuminate\Http\RedirectResponse; +use Prologue\Alerts\AlertsMessageBag; +use Pterodactyl\Http\Controllers\Controller; +use Pterodactyl\Services\Nests\NestUpdateService; +use Pterodactyl\Services\Nests\NestCreationService; +use Pterodactyl\Services\Nests\NestDeletionService; +use Pterodactyl\Contracts\Repository\NestRepositoryInterface; +use Pterodactyl\Http\Requests\Admin\Nest\StoreNestFormRequest; + +class NestController extends Controller +{ + /** + * @var \Prologue\Alerts\AlertsMessageBag + */ + protected $alert; + + /** + * @var \Pterodactyl\Services\Nests\NestCreationService + */ + protected $nestCreationService; + + /** + * @var \Pterodactyl\Services\Nests\NestDeletionService + */ + protected $nestDeletionService; + + /** + * @var \Pterodactyl\Contracts\Repository\NestRepositoryInterface + */ + protected $repository; + + /** + * @var \Pterodactyl\Services\Nests\NestUpdateService + */ + protected $nestUpdateService; + + /** + * NestController constructor. + * + * @param \Prologue\Alerts\AlertsMessageBag $alert + * @param \Pterodactyl\Services\Nests\NestCreationService $nestCreationService + * @param \Pterodactyl\Services\Nests\NestDeletionService $nestDeletionService + * @param \Pterodactyl\Contracts\Repository\NestRepositoryInterface $repository + * @param \Pterodactyl\Services\Nests\NestUpdateService $nestUpdateService + */ + public function __construct( + AlertsMessageBag $alert, + NestCreationService $nestCreationService, + NestDeletionService $nestDeletionService, + NestRepositoryInterface $repository, + NestUpdateService $nestUpdateService + ) { + $this->alert = $alert; + $this->nestDeletionService = $nestDeletionService; + $this->nestCreationService = $nestCreationService; + $this->nestUpdateService = $nestUpdateService; + $this->repository = $repository; + } + + /** + * Render nest listing page. + * + * @return \Illuminate\View\View + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function index(): View + { + return view('admin.nests.index', [ + 'nests' => $this->repository->getWithCounts(), + ]); + } + + /** + * Render nest creation page. + * + * @return \Illuminate\View\View + */ + public function create(): View + { + return view('admin.nests.new'); + } + + /** + * Handle the storage of a new nest. + * + * @param \Pterodactyl\Http\Requests\Admin\Nest\StoreNestFormRequest $request + * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + public function store(StoreNestFormRequest $request): RedirectResponse + { + $nest = $this->nestCreationService->handle($request->normalize()); + $this->alert->success(trans('admin/nests.notices.created', ['name' => $nest->name]))->flash(); + + return redirect()->route('admin.nests.view', $nest->id); + } + + /** + * Return details about a nest including all of the eggs and servers per egg. + * + * @param int $nest + * @return \Illuminate\View\View + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function view(int $nest): View + { + return view('admin.nests.view', [ + 'nest' => $this->repository->getWithEggServers($nest), + ]); + } + + /** + * Handle request to update a nest. + * + * @param \Pterodactyl\Http\Requests\Admin\Nest\StoreNestFormRequest $request + * @param int $nest + * + * @return \Illuminate\Http\RedirectResponse + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function update(StoreNestFormRequest $request, int $nest): RedirectResponse + { + $this->nestUpdateService->handle($nest, $request->normalize()); + $this->alert->success(trans('admin/nests.notices.updated'))->flash(); + + return redirect()->route('admin.nests.view', $nest); + } + + /** + * Handle request to delete a nest. + * + * @param int $nest + * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\Service\HasActiveServersException + */ + public function destroy(int $nest): RedirectResponse + { + $this->nestDeletionService->handle($nest); + $this->alert->success(trans('admin/nests.notices.deleted'))->flash(); + + return redirect()->route('admin.nests'); + } +} diff --git a/app/Http/Controllers/Admin/OptionController.php b/app/Http/Controllers/Admin/OptionController.php deleted file mode 100644 index f35c47119..000000000 --- a/app/Http/Controllers/Admin/OptionController.php +++ /dev/null @@ -1,224 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Http\Controllers\Admin; - -use Javascript; -use Illuminate\Http\Request; -use Prologue\Alerts\AlertsMessageBag; -use Pterodactyl\Models\ServiceOption; -use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Http\Requests\Admin\Service\EditOptionScript; -use Pterodactyl\Services\Services\Options\OptionUpdateService; -use Pterodactyl\Contracts\Repository\ServiceRepositoryInterface; -use Pterodactyl\Services\Services\Options\OptionCreationService; -use Pterodactyl\Services\Services\Options\OptionDeletionService; -use Pterodactyl\Http\Requests\Admin\Service\ServiceOptionFormRequest; -use Pterodactyl\Services\Services\Options\InstallScriptUpdateService; -use Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface; -use Pterodactyl\Exceptions\Service\ServiceOption\InvalidCopyFromException; -use Pterodactyl\Exceptions\Service\ServiceOption\NoParentConfigurationFoundException; - -class OptionController extends Controller -{ - /** - * @var \Prologue\Alerts\AlertsMessageBag - */ - protected $alert; - - /** - * @var \Pterodactyl\Services\Services\Options\InstallScriptUpdateService - */ - protected $installScriptUpdateService; - - /** - * @var \Pterodactyl\Services\Services\Options\OptionCreationService - */ - protected $optionCreationService; - - /** - * @var \Pterodactyl\Services\Services\Options\OptionDeletionService - */ - protected $optionDeletionService; - - /** - * @var \Pterodactyl\Services\Services\Options\OptionUpdateService - */ - protected $optionUpdateService; - - /** - * @var \Pterodactyl\Contracts\Repository\ServiceRepositoryInterface - */ - protected $serviceRepository; - - /** - * @var \Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface - */ - protected $serviceOptionRepository; - - /** - * OptionController constructor. - * - * @param \Prologue\Alerts\AlertsMessageBag $alert - * @param \Pterodactyl\Services\Services\Options\InstallScriptUpdateService $installScriptUpdateService - * @param \Pterodactyl\Services\Services\Options\OptionCreationService $optionCreationService - * @param \Pterodactyl\Services\Services\Options\OptionDeletionService $optionDeletionService - * @param \Pterodactyl\Services\Services\Options\OptionUpdateService $optionUpdateService - * @param \Pterodactyl\Contracts\Repository\ServiceRepositoryInterface $serviceRepository - * @param \Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface $serviceOptionRepository - */ - public function __construct( - AlertsMessageBag $alert, - InstallScriptUpdateService $installScriptUpdateService, - OptionCreationService $optionCreationService, - OptionDeletionService $optionDeletionService, - OptionUpdateService $optionUpdateService, - ServiceRepositoryInterface $serviceRepository, - ServiceOptionRepositoryInterface $serviceOptionRepository - ) { - $this->alert = $alert; - $this->installScriptUpdateService = $installScriptUpdateService; - $this->optionCreationService = $optionCreationService; - $this->optionDeletionService = $optionDeletionService; - $this->optionUpdateService = $optionUpdateService; - $this->serviceRepository = $serviceRepository; - $this->serviceOptionRepository = $serviceOptionRepository; - } - - /** - * Handles request to view page for adding new option. - * - * @return \Illuminate\View\View - */ - public function create() - { - $services = $this->serviceRepository->getWithOptions(); - Javascript::put(['services' => $services->keyBy('id')]); - - return view('admin.services.options.new', ['services' => $services]); - } - - /** - * Handle adding a new service option. - * - * @param \Pterodactyl\Http\Requests\Admin\Service\ServiceOptionFormRequest $request - * @return \Illuminate\Http\RedirectResponse - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - */ - public function store(ServiceOptionFormRequest $request) - { - try { - $option = $this->optionCreationService->handle($request->normalize()); - $this->alert->success(trans('admin/services.options.notices.option_created'))->flash(); - } catch (NoParentConfigurationFoundException $exception) { - $this->alert->danger($exception->getMessage())->flash(); - - return redirect()->back()->withInput(); - } - - return redirect()->route('admin.services.option.view', $option->id); - } - - /** - * Delete a given option from the database. - * - * @param \Pterodactyl\Models\ServiceOption $option - * @return \Illuminate\Http\RedirectResponse - * - * @throws \Pterodactyl\Exceptions\Service\HasActiveServersException - */ - public function destroy(ServiceOption $option) - { - $this->optionDeletionService->handle($option->id); - $this->alert->success(trans('admin/services.options.notices.option_deleted'))->flash(); - - return redirect()->route('admin.services.view', $option->service_id); - } - - /** - * Display option overview page. - * - * @param \Pterodactyl\Models\ServiceOption $option - * @return \Illuminate\View\View - */ - public function viewConfiguration(ServiceOption $option) - { - return view('admin.services.options.view', ['option' => $option]); - } - - /** - * Display script management page for an option. - * - * @param int $option - * @return \Illuminate\View\View - * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function viewScripts($option) - { - $option = $this->serviceOptionRepository->getWithCopyFrom($option); - $copyOptions = $this->serviceOptionRepository->findWhere([ - ['copy_script_from', '=', null], - ['service_id', '=', $option->service_id], - ['id', '!=', $option], - ]); - $relyScript = $this->serviceOptionRepository->findWhere([['copy_script_from', '=', $option]]); - - return view('admin.services.options.scripts', [ - 'copyFromOptions' => $copyOptions, - 'relyOnScript' => $relyScript, - 'option' => $option, - ]); - } - - /** - * Handles POST when editing a configration for a service option. - * - * @param \Illuminate\Http\Request $request - * @param \Pterodactyl\Models\ServiceOption $option - * @return \Illuminate\Http\RedirectResponse - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function editConfiguration(Request $request, ServiceOption $option) - { - try { - $this->optionUpdateService->handle($option, $request->all()); - $this->alert->success(trans('admin/services.options.notices.option_updated'))->flash(); - } catch (NoParentConfigurationFoundException $exception) { - $this->alert->danger($exception->getMessage())->flash(); - } - - return redirect()->route('admin.services.option.view', $option->id); - } - - /** - * Handles POST when updating script for a service option. - * - * @param \Pterodactyl\Http\Requests\Admin\Service\EditOptionScript $request - * @param \Pterodactyl\Models\ServiceOption $option - * @return \Illuminate\Http\RedirectResponse - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function updateScripts(EditOptionScript $request, ServiceOption $option) - { - try { - $this->installScriptUpdateService->handle($option, $request->normalize()); - $this->alert->success(trans('admin/services.options.notices.script_updated'))->flash(); - } catch (InvalidCopyFromException $exception) { - $this->alert->danger($exception->getMessage())->flash(); - } - - return redirect()->route('admin.services.option.scripts', $option->id); - } -} diff --git a/app/Http/Controllers/Admin/PackController.php b/app/Http/Controllers/Admin/PackController.php index 4ef6cbecf..ae6c86fda 100644 --- a/app/Http/Controllers/Admin/PackController.php +++ b/app/Http/Controllers/Admin/PackController.php @@ -19,9 +19,9 @@ use Pterodactyl\Services\Packs\PackCreationService; use Pterodactyl\Services\Packs\PackDeletionService; use Pterodactyl\Http\Requests\Admin\PackFormRequest; use Pterodactyl\Services\Packs\TemplateUploadService; +use Pterodactyl\Contracts\Repository\NestRepositoryInterface; use Pterodactyl\Contracts\Repository\PackRepositoryInterface; use Illuminate\Contracts\Config\Repository as ConfigRepository; -use Pterodactyl\Contracts\Repository\ServiceRepositoryInterface; class PackController extends Controller { @@ -61,7 +61,7 @@ class PackController extends Controller protected $updateService; /** - * @var \Pterodactyl\Contracts\Repository\ServiceRepositoryInterface + * @var \Pterodactyl\Contracts\Repository\NestRepositoryInterface */ protected $serviceRepository; @@ -73,15 +73,15 @@ class PackController extends Controller /** * PackController constructor. * - * @param \Prologue\Alerts\AlertsMessageBag $alert - * @param \Illuminate\Contracts\Config\Repository $config - * @param \Pterodactyl\Services\Packs\ExportPackService $exportService - * @param \Pterodactyl\Services\Packs\PackCreationService $creationService - * @param \Pterodactyl\Services\Packs\PackDeletionService $deletionService - * @param \Pterodactyl\Contracts\Repository\PackRepositoryInterface $repository - * @param \Pterodactyl\Services\Packs\PackUpdateService $updateService - * @param \Pterodactyl\Contracts\Repository\ServiceRepositoryInterface $serviceRepository - * @param \Pterodactyl\Services\Packs\TemplateUploadService $templateUploadService + * @param \Prologue\Alerts\AlertsMessageBag $alert + * @param \Illuminate\Contracts\Config\Repository $config + * @param \Pterodactyl\Services\Packs\ExportPackService $exportService + * @param \Pterodactyl\Services\Packs\PackCreationService $creationService + * @param \Pterodactyl\Services\Packs\PackDeletionService $deletionService + * @param \Pterodactyl\Contracts\Repository\PackRepositoryInterface $repository + * @param \Pterodactyl\Services\Packs\PackUpdateService $updateService + * @param \Pterodactyl\Contracts\Repository\NestRepositoryInterface $serviceRepository + * @param \Pterodactyl\Services\Packs\TemplateUploadService $templateUploadService */ public function __construct( AlertsMessageBag $alert, @@ -91,7 +91,7 @@ class PackController extends Controller PackDeletionService $deletionService, PackRepositoryInterface $repository, PackUpdateService $updateService, - ServiceRepositoryInterface $serviceRepository, + NestRepositoryInterface $serviceRepository, TemplateUploadService $templateUploadService ) { $this->alert = $alert; @@ -114,7 +114,7 @@ class PackController extends Controller public function index(Request $request) { return view('admin.packs.index', [ - 'packs' => $this->repository->search($request->input('query'))->paginateWithOptionAndServerCount( + 'packs' => $this->repository->search($request->input('query'))->paginateWithEggAndServerCount( $this->config->get('pterodactyl.paginate.admin.packs') ), ]); @@ -124,11 +124,13 @@ class PackController extends Controller * Display new pack creation form. * * @return \Illuminate\View\View + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ public function create() { return view('admin.packs.new', [ - 'services' => $this->serviceRepository->getWithOptions(), + 'nests' => $this->serviceRepository->getWithEggs(), ]); } @@ -136,11 +138,13 @@ class PackController extends Controller * Display new pack creation modal for use with template upload. * * @return \Illuminate\View\View + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ public function newTemplate() { return view('admin.packs.modal', [ - 'services' => $this->serviceRepository->getWithOptions(), + 'nests' => $this->serviceRepository->getWithEggs(), ]); } @@ -152,7 +156,7 @@ class PackController extends Controller * * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException - * @throws \Pterodactyl\Exceptions\Service\Pack\InvalidFileUploadException + * @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException * @throws \Pterodactyl\Exceptions\Service\Pack\InvalidPackArchiveFormatException * @throws \Pterodactyl\Exceptions\Service\Pack\UnreadableZipArchiveException * @throws \Pterodactyl\Exceptions\Service\Pack\ZipExtractionException @@ -160,7 +164,7 @@ class PackController extends Controller public function store(PackFormRequest $request) { if ($request->has('from_template')) { - $pack = $this->templateUploadService->handle($request->input('option_id'), $request->file('file_upload')); + $pack = $this->templateUploadService->handle($request->input('egg_id'), $request->file('file_upload')); } else { $pack = $this->creationService->handle($request->normalize(), $request->file('file_upload')); } @@ -175,12 +179,13 @@ class PackController extends Controller * * @param int $pack * @return \Illuminate\View\View + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ public function view($pack) { return view('admin.packs.view', [ 'pack' => $this->repository->getWithServers($pack), - 'services' => $this->serviceRepository->getWithOptions(), + 'nests' => $this->serviceRepository->getWithEggs(), ]); } diff --git a/app/Http/Controllers/Admin/ServersController.php b/app/Http/Controllers/Admin/ServersController.php index 529d692b3..5867d4788 100644 --- a/app/Http/Controllers/Admin/ServersController.php +++ b/app/Http/Controllers/Admin/ServersController.php @@ -25,11 +25,11 @@ use Pterodactyl\Services\Servers\BuildModificationService; use Pterodactyl\Services\Database\DatabaseManagementService; use Pterodactyl\Services\Servers\DetailsModificationService; use Pterodactyl\Services\Servers\StartupModificationService; +use Pterodactyl\Contracts\Repository\NestRepositoryInterface; use Pterodactyl\Contracts\Repository\NodeRepositoryInterface; use Pterodactyl\Repositories\Eloquent\DatabaseHostRepository; use Illuminate\Contracts\Config\Repository as ConfigRepository; use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; -use Pterodactyl\Contracts\Repository\ServiceRepositoryInterface; use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface; use Pterodactyl\Contracts\Repository\LocationRepositoryInterface; use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface; @@ -91,6 +91,11 @@ class ServersController extends Controller */ protected $locationRepository; + /** + * @var \Pterodactyl\Contracts\Repository\NestRepositoryInterface + */ + protected $nestRepository; + /** * @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface */ @@ -111,11 +116,6 @@ class ServersController extends Controller */ protected $service; - /** - * @var \Pterodactyl\Contracts\Repository\ServiceRepositoryInterface - */ - protected $serviceRepository; - /** * @var \Pterodactyl\Services\Servers\StartupModificationService */ @@ -144,7 +144,7 @@ class ServersController extends Controller * @param \Pterodactyl\Contracts\Repository\NodeRepositoryInterface $nodeRepository * @param \Pterodactyl\Services\Servers\ReinstallServerService $reinstallService * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository - * @param \Pterodactyl\Contracts\Repository\ServiceRepositoryInterface $serviceRepository + * @param \Pterodactyl\Contracts\Repository\NestRepositoryInterface $nestRepository * @param \Pterodactyl\Services\Servers\StartupModificationService $startupModificationService * @param \Pterodactyl\Services\Servers\SuspensionService $suspensionService */ @@ -164,7 +164,7 @@ class ServersController extends Controller NodeRepositoryInterface $nodeRepository, ReinstallServerService $reinstallService, ServerRepositoryInterface $repository, - ServiceRepositoryInterface $serviceRepository, + NestRepositoryInterface $nestRepository, StartupModificationService $startupModificationService, SuspensionService $suspensionService ) { @@ -179,11 +179,11 @@ class ServersController extends Controller $this->detailsModificationService = $detailsModificationService; $this->deletionService = $deletionService; $this->locationRepository = $locationRepository; + $this->nestRepository = $nestRepository; $this->nodeRepository = $nodeRepository; $this->reinstallService = $reinstallService; $this->repository = $repository; $this->service = $service; - $this->serviceRepository = $serviceRepository; $this->startupModificationService = $startupModificationService; $this->suspensionService = $suspensionService; } @@ -218,19 +218,19 @@ class ServersController extends Controller return redirect()->route('admin.nodes'); } - $services = $this->serviceRepository->getWithOptions(); + $nests = $this->nestRepository->getWithEggs(); Javascript::put([ - 'services' => $services->map(function ($item) { + 'nests' => $nests->map(function ($item) { return array_merge($item->toArray(), [ - 'options' => $item->options->keyBy('id')->toArray(), + 'eggs' => $item->eggs->keyBy('id')->toArray(), ]); })->keyBy('id'), ]); return view('admin.servers.new', [ 'locations' => $this->locationRepository->all(), - 'services' => $services, + 'nests' => $nests, ]); } @@ -331,12 +331,12 @@ class ServersController extends Controller abort(404); } - $services = $this->serviceRepository->getWithOptions(); + $nests = $this->nestRepository->getWithEggs(); Javascript::put([ - 'services' => $services->map(function ($item) { + 'nests' => $nests->map(function ($item) { return array_merge($item->toArray(), [ - 'options' => $item->options->keyBy('id')->toArray(), + 'eggs' => $item->eggs->keyBy('id')->toArray(), ]); })->keyBy('id'), 'server_variables' => $parameters->data, @@ -344,7 +344,7 @@ class ServersController extends Controller return view('admin.servers.view.startup', [ 'server' => $parameters->server, - 'services' => $services, + 'nests' => $nests, ]); } @@ -402,7 +402,7 @@ class ServersController extends Controller public function setDetails(Request $request, Server $server) { $this->detailsModificationService->edit($server, $request->only([ - 'owner_id', 'name', 'description', 'reset_token', + 'owner_id', 'name', 'description', ])); $this->alert->success(trans('admin/server.alerts.details_updated'))->flash(); diff --git a/app/Http/Controllers/Admin/ServiceController.php b/app/Http/Controllers/Admin/ServiceController.php deleted file mode 100644 index 35b5155f6..000000000 --- a/app/Http/Controllers/Admin/ServiceController.php +++ /dev/null @@ -1,176 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Http\Controllers\Admin; - -use Pterodactyl\Models\Service; -use Prologue\Alerts\AlertsMessageBag; -use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Services\Services\ServiceUpdateService; -use Pterodactyl\Services\Services\ServiceCreationService; -use Pterodactyl\Services\Services\ServiceDeletionService; -use Pterodactyl\Http\Requests\Admin\Service\ServiceFormRequest; -use Pterodactyl\Contracts\Repository\ServiceRepositoryInterface; -use Pterodactyl\Http\Requests\Admin\Service\ServiceFunctionsFormRequest; - -class ServiceController extends Controller -{ - /** - * @var \Prologue\Alerts\AlertsMessageBag - */ - protected $alert; - - /** - * @var \Pterodactyl\Services\Services\ServiceCreationService - */ - protected $creationService; - - /** - * @var \Pterodactyl\Services\Services\ServiceDeletionService - */ - protected $deletionService; - - /** - * @var \Pterodactyl\Contracts\Repository\ServiceRepositoryInterface - */ - protected $repository; - - /** - * @var \Pterodactyl\Services\Services\ServiceUpdateService - */ - protected $updateService; - - public function __construct( - AlertsMessageBag $alert, - ServiceCreationService $creationService, - ServiceDeletionService $deletionService, - ServiceRepositoryInterface $repository, - ServiceUpdateService $updateService - ) { - $this->alert = $alert; - $this->creationService = $creationService; - $this->deletionService = $deletionService; - $this->repository = $repository; - $this->updateService = $updateService; - } - - /** - * Display service overview page. - * - * @return \Illuminate\View\View - */ - public function index() - { - return view('admin.services.index', [ - 'services' => $this->repository->getWithOptions(), - ]); - } - - /** - * Display create service page. - * - * @return \Illuminate\View\View - */ - public function create() - { - return view('admin.services.new'); - } - - /** - * Return base view for a service. - * - * @param int $service - * @return \Illuminate\View\View - */ - public function view($service) - { - return view('admin.services.view', [ - 'service' => $this->repository->getWithOptionServers($service), - ]); - } - - /** - * Return function editing view for a service. - * - * @param \Pterodactyl\Models\Service $service - * @return \Illuminate\View\View - */ - public function viewFunctions(Service $service) - { - return view('admin.services.functions', ['service' => $service]); - } - - /** - * Handle post action for new service. - * - * @param \Pterodactyl\Http\Requests\Admin\Service\ServiceFormRequest $request - * @return \Illuminate\Http\RedirectResponse - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - */ - public function store(ServiceFormRequest $request) - { - $service = $this->creationService->handle($request->normalize()); - $this->alert->success(trans('admin/services.notices.service_created', ['name' => $service->name]))->flash(); - - return redirect()->route('admin.services.view', $service->id); - } - - /** - * Edits configuration for a specific service. - * - * @param \Pterodactyl\Http\Requests\Admin\Service\ServiceFormRequest $request - * @param \Pterodactyl\Models\Service $service - * @return \Illuminate\Http\RedirectResponse - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function update(ServiceFormRequest $request, Service $service) - { - $this->updateService->handle($service->id, $request->normalize()); - $this->alert->success(trans('admin/services.notices.service_updated'))->flash(); - - return redirect()->route('admin.services.view', $service); - } - - /** - * Update the functions file for a service. - * - * @param \Pterodactyl\Http\Requests\Admin\Service\ServiceFunctionsFormRequest $request - * @param \Pterodactyl\Models\Service $service - * @return \Illuminate\Http\RedirectResponse - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function updateFunctions(ServiceFunctionsFormRequest $request, Service $service) - { - $this->updateService->handle($service->id, $request->normalize()); - $this->alert->success(trans('admin/services.notices.functions_updated'))->flash(); - - return redirect()->route('admin.services.view.functions', $service->id); - } - - /** - * Delete a service from the panel. - * - * @param \Pterodactyl\Models\Service $service - * @return \Illuminate\Http\RedirectResponse - * - * @throws \Pterodactyl\Exceptions\Service\HasActiveServersException - */ - public function destroy(Service $service) - { - $this->deletionService->handle($service->id); - $this->alert->success(trans('admin/services.notices.service_deleted'))->flash(); - - return redirect()->route('admin.services'); - } -} diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index 5a1280aff..20594f333 100644 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -159,6 +159,7 @@ class UserController extends Controller * @return \Illuminate\Http\RedirectResponse * * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ public function update(UserFormRequest $request, User $user) { diff --git a/app/Http/Controllers/Admin/VariableController.php b/app/Http/Controllers/Admin/VariableController.php deleted file mode 100644 index 786dd756b..000000000 --- a/app/Http/Controllers/Admin/VariableController.php +++ /dev/null @@ -1,133 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Http\Controllers\Admin; - -use Prologue\Alerts\AlertsMessageBag; -use Pterodactyl\Models\ServiceOption; -use Pterodactyl\Models\ServiceVariable; -use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Http\Requests\Admin\OptionVariableFormRequest; -use Pterodactyl\Repositories\Eloquent\ServiceVariableRepository; -use Pterodactyl\Services\Services\Variables\VariableUpdateService; -use Pterodactyl\Services\Services\Variables\VariableCreationService; -use Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface; - -class VariableController extends Controller -{ - /** - * @var \Prologue\Alerts\AlertsMessageBag - */ - protected $alert; - - /** - * @var \Pterodactyl\Services\Services\Variables\VariableCreationService - */ - protected $creationService; - - /** - * @var \Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface - */ - protected $serviceOptionRepository; - - /** - * @var \Pterodactyl\Repositories\Eloquent\ServiceVariableRepository - */ - protected $serviceVariableRepository; - - /** - * @var \Pterodactyl\Services\Services\Variables\VariableUpdateService - */ - protected $updateService; - - public function __construct( - AlertsMessageBag $alert, - ServiceOptionRepositoryInterface $serviceOptionRepository, - ServiceVariableRepository $serviceVariableRepository, - VariableCreationService $creationService, - VariableUpdateService $updateService - ) { - $this->alert = $alert; - $this->creationService = $creationService; - $this->serviceOptionRepository = $serviceOptionRepository; - $this->serviceVariableRepository = $serviceVariableRepository; - $this->updateService = $updateService; - } - - /** - * Handles POST request to create a new option variable. - * - * @param \Pterodactyl\Http\Requests\Admin\OptionVariableFormRequest $request - * @param \Pterodactyl\Models\ServiceOption $option - * @return \Illuminate\Http\RedirectResponse - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Service\ServiceVariable\ReservedVariableNameException - */ - public function store(OptionVariableFormRequest $request, ServiceOption $option) - { - $this->creationService->handle($option->id, $request->normalize()); - $this->alert->success(trans('admin/services.variables.notices.variable_created'))->flash(); - - return redirect()->route('admin.services.option.variables', $option->id); - } - - /** - * Display variable overview page for a service option. - * - * @param int $option - * @return \Illuminate\View\View - */ - public function view($option) - { - $option = $this->serviceOptionRepository->getWithVariables($option); - - return view('admin.services.options.variables', ['option' => $option]); - } - - /** - * Handles POST when editing a configration for a service variable. - * - * @param \Pterodactyl\Http\Requests\Admin\OptionVariableFormRequest $request - * @param \Pterodactyl\Models\ServiceOption $option - * @param \Pterodactyl\Models\ServiceVariable $variable - * @return \Illuminate\Http\RedirectResponse - * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - * @throws \Pterodactyl\Exceptions\Service\ServiceVariable\ReservedVariableNameException - */ - public function update(OptionVariableFormRequest $request, ServiceOption $option, ServiceVariable $variable) - { - $this->updateService->handle($variable, $request->normalize()); - $this->alert->success(trans('admin/services.variables.notices.variable_updated', [ - 'variable' => $variable->name, - ]))->flash(); - - return redirect()->route('admin.services.option.variables', $option->id); - } - - /** - * Delete a service variable from the system. - * - * @param \Pterodactyl\Models\ServiceOption $option - * @param \Pterodactyl\Models\ServiceVariable $variable - * @return \Illuminate\Http\RedirectResponse - */ - public function delete(ServiceOption $option, ServiceVariable $variable) - { - $this->serviceVariableRepository->delete($variable->id); - $this->alert->success(trans('admin/services.variables.notices.variable_deleted', [ - 'variable' => $variable->name, - ]))->flash(); - - return redirect()->route('admin.services.option.variables', $option->id); - } -} diff --git a/app/Http/Controllers/Daemon/ServiceController.php b/app/Http/Controllers/Daemon/ServiceController.php deleted file mode 100644 index 62f379395..000000000 --- a/app/Http/Controllers/Daemon/ServiceController.php +++ /dev/null @@ -1,85 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Http\Controllers\Daemon; - -use Illuminate\Http\Request; -use Pterodactyl\Models\Service; -use Pterodactyl\Models\ServiceOption; -use Pterodactyl\Http\Controllers\Controller; - -class ServiceController extends Controller -{ - /** - * Returns a listing of all services currently on the system, - * as well as the associated files and the file hashes for - * caching purposes. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\JsonResponse - */ - public function listServices(Request $request) - { - $response = []; - foreach (Service::all() as $service) { - $response[$service->folder] = [ - 'main.json' => sha1($this->getConfiguration($service->id)->toJson()), - 'index.js' => sha1($service->index_file), - ]; - } - - return response()->json($response); - } - - /** - * Returns the contents of the requested file for the given service. - * - * @param \Illuminate\Http\Request $request - * @param string $folder - * @param string $file - * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\FileResponse - */ - public function pull(Request $request, $folder, $file) - { - $service = Service::where('folder', $folder)->firstOrFail(); - - if ($file === 'index.js') { - return response($service->index_file)->header('Content-Type', 'text/plain'); - } elseif ($file === 'main.json') { - return response()->json($this->getConfiguration($service->id)); - } - - return abort(404); - } - - /** - * Returns a `main.json` file based on the configuration - * of each service option. - * - * @param int $id - * @return \Illuminate\Support\Collection - */ - protected function getConfiguration($id) - { - $options = ServiceOption::where('service_id', $id)->get(); - - return $options->mapWithKeys(function ($item) use ($options) { - return [ - $item->tag => array_filter([ - 'symlink' => $options->where('id', $item->config_from)->pluck('tag')->pop(), - 'startup' => json_decode($item->config_startup), - 'stop' => $item->config_stop, - 'configs' => json_decode($item->config_files), - 'log' => json_decode($item->config_logs), - 'query' => 'none', - ]), - ]; - }); - } -} diff --git a/app/Http/Controllers/Server/ServerController.php b/app/Http/Controllers/Server/ServerController.php index acaf582f4..9b4208319 100644 --- a/app/Http/Controllers/Server/ServerController.php +++ b/app/Http/Controllers/Server/ServerController.php @@ -54,7 +54,7 @@ class ServerController extends Controller $this->authorize('view-startup', $server); $server->load(['node', 'allocation', 'variables']); - $variables = Models\ServiceVariable::where('option_id', $server->option_id)->get(); + $variables = Models\EggVariable::where('option_id', $server->option_id)->get(); $replacements = [ '{{SERVER_MEMORY}}' => $server->memory, diff --git a/app/Http/Requests/Admin/AdminFormRequest.php b/app/Http/Requests/Admin/AdminFormRequest.php index 365f40d2b..7f7ce8cb7 100644 --- a/app/Http/Requests/Admin/AdminFormRequest.php +++ b/app/Http/Requests/Admin/AdminFormRequest.php @@ -13,6 +13,11 @@ use Illuminate\Foundation\Http\FormRequest; abstract class AdminFormRequest extends FormRequest { + /** + * The rules to apply to the incoming form request. + * + * @return array + */ abstract public function rules(); /** diff --git a/app/Http/Requests/Admin/Egg/EggFormRequest.php b/app/Http/Requests/Admin/Egg/EggFormRequest.php new file mode 100644 index 000000000..539ee3adc --- /dev/null +++ b/app/Http/Requests/Admin/Egg/EggFormRequest.php @@ -0,0 +1,49 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Http\Requests\Admin\Egg; + +use Pterodactyl\Http\Requests\Admin\AdminFormRequest; + +class EggFormRequest extends AdminFormRequest +{ + /** + * {@inheritdoc} + */ + public function rules() + { + $rules = [ + 'name' => 'required|string|max:255', + 'description' => 'required|string', + 'docker_image' => 'required|string|max:255', + 'startup' => 'required|string', + 'config_from' => 'sometimes|bail|nullable|numeric', + 'config_stop' => 'required_without:config_from|nullable|string|max:255', + 'config_startup' => 'required_without:config_from|nullable|json', + 'config_logs' => 'required_without:config_from|nullable|json', + 'config_files' => 'required_without:config_from|nullable|json', + ]; + + if ($this->method() === 'POST') { + $rules['nest_id'] = 'required|numeric|exists:nests,id'; + } + + return $rules; + } + + /** + * @param \Illuminate\Contracts\Validation\Validator $validator + */ + public function withValidator($validator) + { + $validator->sometimes('config_from', 'exists:eggs,id', function () { + return (int) $this->input('config_from') !== 0; + }); + } +} diff --git a/app/Http/Requests/Admin/Egg/EggImportFormRequest.php b/app/Http/Requests/Admin/Egg/EggImportFormRequest.php new file mode 100644 index 000000000..b6adb768e --- /dev/null +++ b/app/Http/Requests/Admin/Egg/EggImportFormRequest.php @@ -0,0 +1,31 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Http\Requests\Admin\Egg; + +use Pterodactyl\Http\Requests\Admin\AdminFormRequest; + +class EggImportFormRequest extends AdminFormRequest +{ + /** + * @return array + */ + public function rules() + { + $rules = [ + 'import_file' => 'bail|required|file|max:1000|mimetypes:application/json,text/plain', + ]; + + if ($this->method() !== 'PUT') { + $rules['import_to_nest'] = 'bail|required|integer|exists:nests,id'; + } + + return $rules; + } +} diff --git a/app/Http/Requests/Admin/Service/EditOptionScript.php b/app/Http/Requests/Admin/Egg/EggScriptFormRequest.php similarity index 88% rename from app/Http/Requests/Admin/Service/EditOptionScript.php rename to app/Http/Requests/Admin/Egg/EggScriptFormRequest.php index 03d1612c9..3f522e96f 100644 --- a/app/Http/Requests/Admin/Service/EditOptionScript.php +++ b/app/Http/Requests/Admin/Egg/EggScriptFormRequest.php @@ -7,11 +7,11 @@ * https://opensource.org/licenses/MIT */ -namespace Pterodactyl\Http\Requests\Admin\Service; +namespace Pterodactyl\Http\Requests\Admin\Egg; use Pterodactyl\Http\Requests\Admin\AdminFormRequest; -class EditOptionScript extends AdminFormRequest +class EggScriptFormRequest extends AdminFormRequest { /** * Return the rules to be used when validating the sent data in the request. diff --git a/app/Http/Requests/Admin/Service/OptionVariableFormRequest.php b/app/Http/Requests/Admin/Egg/EggVariableFormRequest.php similarity index 74% rename from app/Http/Requests/Admin/Service/OptionVariableFormRequest.php rename to app/Http/Requests/Admin/Egg/EggVariableFormRequest.php index 54d8914e8..621fbd772 100644 --- a/app/Http/Requests/Admin/Service/OptionVariableFormRequest.php +++ b/app/Http/Requests/Admin/Egg/EggVariableFormRequest.php @@ -7,11 +7,12 @@ * https://opensource.org/licenses/MIT */ -namespace Pterodactyl\Http\Requests\Admin; +namespace Pterodactyl\Http\Requests\Admin\Egg; -use Pterodactyl\Models\ServiceVariable; +use Pterodactyl\Models\EggVariable; +use Pterodactyl\Http\Requests\Admin\AdminFormRequest; -class OptionVariableFormRequest extends AdminFormRequest +class EggVariableFormRequest extends AdminFormRequest { /** * @return array @@ -21,7 +22,7 @@ class OptionVariableFormRequest extends AdminFormRequest return [ 'name' => 'required|string|min:1|max:255', 'description' => 'sometimes|nullable|string', - 'env_variable' => 'required|regex:/^[\w]{1,255}$/|notIn:' . ServiceVariable::RESERVED_ENV_NAMES, + 'env_variable' => 'required|regex:/^[\w]{1,255}$/|notIn:' . EggVariable::RESERVED_ENV_NAMES, 'default_value' => 'string', 'options' => 'sometimes|required|array', 'rules' => 'bail|required|string', @@ -37,11 +38,9 @@ class OptionVariableFormRequest extends AdminFormRequest { $rules = $this->input('rules'); if ($this->method() === 'PATCH') { - $rules = $this->input('rules', $this->route()->parameter('variable')->rules); + $rules = $this->input('rules', $this->route()->parameter('egg')->rules); } - $validator->sometimes('default_value', $rules, function ($input) { - return $input->default_value; - }); + $validator->addRules(['default_value' => $rules]); } } diff --git a/app/Http/Requests/Admin/Service/ServiceFunctionsFormRequest.php b/app/Http/Requests/Admin/Nest/StoreNestFormRequest.php similarity index 63% rename from app/Http/Requests/Admin/Service/ServiceFunctionsFormRequest.php rename to app/Http/Requests/Admin/Nest/StoreNestFormRequest.php index 249593507..56255e558 100644 --- a/app/Http/Requests/Admin/Service/ServiceFunctionsFormRequest.php +++ b/app/Http/Requests/Admin/Nest/StoreNestFormRequest.php @@ -7,11 +7,11 @@ * https://opensource.org/licenses/MIT */ -namespace Pterodactyl\Http\Requests\Admin\Service; +namespace Pterodactyl\Http\Requests\Admin\Nest; use Pterodactyl\Http\Requests\Admin\AdminFormRequest; -class ServiceFunctionsFormRequest extends AdminFormRequest +class StoreNestFormRequest extends AdminFormRequest { /** * @return array @@ -19,7 +19,8 @@ class ServiceFunctionsFormRequest extends AdminFormRequest public function rules() { return [ - 'index_file' => 'required|nullable|string', + 'name' => 'required|string|min:1|max:255', + 'description' => 'required|nullable|string', ]; } } diff --git a/app/Http/Requests/Admin/ServerFormRequest.php b/app/Http/Requests/Admin/ServerFormRequest.php index 21f0519db..f448a953c 100644 --- a/app/Http/Requests/Admin/ServerFormRequest.php +++ b/app/Http/Requests/Admin/ServerFormRequest.php @@ -63,7 +63,7 @@ class ServerFormRequest extends AdminFormRequest $validator->sometimes('pack_id', [ Rule::exists('packs', 'id')->where(function ($query) { $query->where('selectable', 1); - $query->where('option_id', $this->input('option_id')); + $query->where('egg_id', $this->input('egg_id')); }), ], function ($input) { return $input->pack_id !== 0 && $input->pack_id !== null; diff --git a/app/Http/Requests/Admin/Service/ServiceFormRequest.php b/app/Http/Requests/Admin/Service/ServiceFormRequest.php deleted file mode 100644 index c5af2b697..000000000 --- a/app/Http/Requests/Admin/Service/ServiceFormRequest.php +++ /dev/null @@ -1,37 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Http\Requests\Admin\Service; - -use Pterodactyl\Http\Requests\Admin\AdminFormRequest; - -class ServiceFormRequest extends AdminFormRequest -{ - /** - * @return array - */ - public function rules() - { - $rules = [ - 'name' => 'required|string|min:1|max:255', - 'description' => 'required|nullable|string', - 'folder' => 'required|regex:/^[\w.-]{1,50}$/|unique:services,folder', - 'startup' => 'required|nullable|string', - ]; - - if ($this->method() === 'PATCH') { - $service = $this->route()->parameter('service'); - $rules['folder'] = $rules['folder'] . ',' . $service->id; - - return $rules; - } - - return $rules; - } -} diff --git a/app/Http/Requests/Admin/Service/ServiceOptionFormRequest.php b/app/Http/Requests/Admin/Service/ServiceOptionFormRequest.php deleted file mode 100644 index 3099ac92d..000000000 --- a/app/Http/Requests/Admin/Service/ServiceOptionFormRequest.php +++ /dev/null @@ -1,24 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Http\Requests\Admin\Service; - -use Pterodactyl\Models\ServiceOption; -use Pterodactyl\Http\Requests\Admin\AdminFormRequest; - -class ServiceOptionFormRequest extends AdminFormRequest -{ - /** - * {@inheritdoc} - */ - public function rules() - { - return ServiceOption::getCreateRules(); - } -} diff --git a/app/Jobs/Schedule/RunTaskJob.php b/app/Jobs/Schedule/RunTaskJob.php index 0e025740a..3f1b5195f 100644 --- a/app/Jobs/Schedule/RunTaskJob.php +++ b/app/Jobs/Schedule/RunTaskJob.php @@ -162,7 +162,7 @@ class RunTaskJob extends Job implements ShouldQueue $repository = app()->make(ScheduleRepositoryInterface::class); $repository->withoutFresh()->update($this->schedule, [ 'is_processing' => false, - 'last_run_at' => app()->make(Carbon::class)->now()->toDateTimeString(), + 'last_run_at' => Carbon::now()->toDateTimeString(), ]); } diff --git a/app/Models/Egg.php b/app/Models/Egg.php new file mode 100644 index 000000000..1c1b9e815 --- /dev/null +++ b/app/Models/Egg.php @@ -0,0 +1,241 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Models; + +use Sofa\Eloquence\Eloquence; +use Sofa\Eloquence\Validable; +use Illuminate\Database\Eloquent\Model; +use Sofa\Eloquence\Contracts\CleansAttributes; +use Sofa\Eloquence\Contracts\Validable as ValidableContract; + +class Egg extends Model implements CleansAttributes, ValidableContract +{ + use Eloquence, Validable; + + /** + * The table associated with the model. + * + * @var string + */ + protected $table = 'eggs'; + + /** + * Fields that are not mass assignable. + * + * @var array + */ + protected $fillable = [ + 'name', + 'description', + 'docker_image', + 'config_files', + 'config_startup', + 'config_logs', + 'config_stop', + 'config_from', + 'startup', + 'script_is_privileged', + 'script_install', + 'script_entry', + 'script_container', + 'copy_script_from', + ]; + + /** + * Cast values to correct type. + * + * @var array + */ + protected $casts = [ + 'nest_id' => 'integer', + 'config_from' => 'integer', + 'script_is_privileged' => 'boolean', + 'copy_script_from' => 'integer', + ]; + + /** + * @var array + */ + protected static $applicationRules = [ + 'nest_id' => 'required', + 'uuid' => 'required', + 'name' => 'required', + 'description' => 'required', + 'author' => 'required', + 'docker_image' => 'required', + 'startup' => 'required', + 'config_from' => 'sometimes', + 'config_stop' => 'required_without:config_from', + 'config_startup' => 'required_without:config_from', + 'config_logs' => 'required_without:config_from', + 'config_files' => 'required_without:config_from', + ]; + + /** + * @var array + */ + protected static $dataIntegrityRules = [ + 'nest_id' => 'bail|numeric|exists:nests,id', + 'uuid' => 'string|size:36', + 'name' => 'string|max:255', + 'description' => 'string', + 'author' => 'string|email', + 'docker_image' => 'string|max:255', + 'startup' => 'nullable|string', + 'config_from' => 'bail|nullable|numeric|exists:eggs,id', + 'config_stop' => 'nullable|string|max:255', + 'config_startup' => 'nullable|json', + 'config_logs' => 'nullable|json', + 'config_files' => 'nullable|json', + ]; + + /** + * @var array + */ + protected $attributes = [ + 'config_stop' => null, + 'config_startup' => null, + 'config_logs' => null, + 'config_files' => null, + ]; + + /** + * Returns the install script for the egg; if egg is copying from another + * it will return the copied script. + * + * @return string + */ + public function getCopyScriptInstallAttribute() + { + return (is_null($this->copy_script_from)) ? $this->script_install : $this->scriptFrom->script_install; + } + + /** + * Returns the entry command for the egg; if egg is copying from another + * it will return the copied entry command. + * + * @return string + */ + public function getCopyScriptEntryAttribute() + { + return (is_null($this->copy_script_from)) ? $this->script_entry : $this->scriptFrom->script_entry; + } + + /** + * Returns the install container for the egg; if egg is copying from another + * it will return the copied install container. + * + * @return string + */ + public function getCopyScriptContainerAttribute() + { + return (is_null($this->copy_script_from)) ? $this->script_container : $this->scriptFrom->script_container; + } + + /** + * Return the file configuration for an egg. + * + * @return string + */ + public function getInheritConfigFilesAttribute() + { + return is_null($this->config_from) ? $this->config_files : $this->configFrom->config_files; + } + + /** + * Return the startup configuration for an egg. + * + * @return string + */ + public function getInheritConfigStartupAttribute() + { + return is_null($this->config_from) ? $this->config_startup : $this->configFrom->config_startup; + } + + /** + * Return the log reading configuration for an egg. + * + * @return string + */ + public function getInheritConfigLogsAttribute() + { + return is_null($this->config_from) ? $this->config_logs : $this->configFrom->config_logs; + } + + /** + * Return the stop command configuration for an egg. + * + * @return string + */ + public function getInheritConfigStopAttribute() + { + return is_null($this->config_from) ? $this->config_stop : $this->configFrom->config_stop; + } + + /** + * Gets nest associated with an egg. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function nest() + { + return $this->belongsTo(Nest::class); + } + + /** + * Gets all servers associated with this egg. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function servers() + { + return $this->hasMany(Server::class, 'egg_id'); + } + + /** + * Gets all variables associated with this egg. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function variables() + { + return $this->hasMany(EggVariable::class, 'egg_id'); + } + + /** + * Gets all packs associated with this egg. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function packs() + { + return $this->hasMany(Pack::class, 'egg_id'); + } + + /** + * Get the parent egg from which to copy scripts. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function scriptFrom() + { + return $this->belongsTo(self::class, 'copy_script_from'); + } + + /** + * Get the parent egg from which to copy configuration settings. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function configFrom() + { + return $this->belongsTo(self::class, 'config_from'); + } +} diff --git a/app/Models/ServiceVariable.php b/app/Models/EggVariable.php similarity index 86% rename from app/Models/ServiceVariable.php rename to app/Models/EggVariable.php index 52d475107..5341dd0dd 100644 --- a/app/Models/ServiceVariable.php +++ b/app/Models/EggVariable.php @@ -15,7 +15,7 @@ use Illuminate\Database\Eloquent\Model; use Sofa\Eloquence\Contracts\CleansAttributes; use Sofa\Eloquence\Contracts\Validable as ValidableContract; -class ServiceVariable extends Model implements CleansAttributes, ValidableContract +class EggVariable extends Model implements CleansAttributes, ValidableContract { use Eloquence, Validable; @@ -31,7 +31,7 @@ class ServiceVariable extends Model implements CleansAttributes, ValidableContra * * @var string */ - protected $table = 'service_variables'; + protected $table = 'egg_variables'; /** * Fields that are not mass assignable. @@ -46,7 +46,7 @@ class ServiceVariable extends Model implements CleansAttributes, ValidableContra * @var array */ protected $casts = [ - 'option_id' => 'integer', + 'egg_id' => 'integer', 'user_viewable' => 'integer', 'user_editable' => 'integer', ]; @@ -64,7 +64,7 @@ class ServiceVariable extends Model implements CleansAttributes, ValidableContra * @var array */ protected static $dataIntegrityRules = [ - 'option_id' => 'exists:service_options,id', + 'egg_id' => 'exists:eggs,id', 'name' => 'string|between:1,255', 'description' => 'nullable|string', 'env_variable' => 'regex:/^[\w]{1,255}$/|notIn:' . self::RESERVED_ENV_NAMES, @@ -83,9 +83,6 @@ class ServiceVariable extends Model implements CleansAttributes, ValidableContra ]; /** - * Returns the display executable for the option and will use the parent - * service one if the option does not have one defined. - * * @return bool */ public function getRequiredAttribute($value) diff --git a/app/Models/Service.php b/app/Models/Nest.php similarity index 59% rename from app/Models/Service.php rename to app/Models/Nest.php index e81a9ba90..3631bc6e3 100644 --- a/app/Models/Service.php +++ b/app/Models/Nest.php @@ -15,7 +15,7 @@ use Illuminate\Database\Eloquent\Model; use Sofa\Eloquence\Contracts\CleansAttributes; use Sofa\Eloquence\Contracts\Validable as ValidableContract; -class Service extends Model implements CleansAttributes, ValidableContract +class Nest extends Model implements CleansAttributes, ValidableContract { use Eloquence, Validable; @@ -24,14 +24,17 @@ class Service extends Model implements CleansAttributes, ValidableContract * * @var string */ - protected $table = 'services'; + protected $table = 'nests'; /** * Fields that are mass assignable. * * @var array */ - protected $fillable = ['name', 'author', 'description', 'folder', 'startup', 'index_file']; + protected $fillable = [ + 'name', + 'description', + ]; /** * @var array @@ -40,50 +43,39 @@ class Service extends Model implements CleansAttributes, ValidableContract 'author' => 'required', 'name' => 'required', 'description' => 'sometimes', - 'folder' => 'required', - 'startup' => 'sometimes', - 'index_file' => 'required', ]; /** * @var array */ protected static $dataIntegrityRules = [ - 'author' => 'string|size:36', + 'author' => 'string|email', 'name' => 'string|max:255', 'description' => 'nullable|string', - 'folder' => 'string|max:255|regex:/^[\w.-]{1,50}$/|unique:services,folder', - 'startup' => 'nullable|string', - 'index_file' => 'string', ]; /** - * Gets all service options associated with this service. + * Gets all eggs associated with this service. * * @return \Illuminate\Database\Eloquent\Relations\HasMany */ - public function options() + public function eggs() { - return $this->hasMany(ServiceOption::class); + return $this->hasMany(Egg::class); } /** - * Returns all of the packs associated with a service, regardless of the service option. + * Returns all of the packs associated with a nest, regardless of the egg. * * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough */ public function packs() { - return $this->hasManyThrough( - Pack::class, - ServiceOption::class, - 'service_id', - 'option_id' - ); + return $this->hasManyThrough(Pack::class, Egg::class, 'nest_id', 'egg_id'); } /** - * Gets all servers associated with this service. + * Gets all servers associated with this nest. * * @return \Illuminate\Database\Eloquent\Relations\HasMany */ diff --git a/app/Models/Pack.php b/app/Models/Pack.php index a16c0336d..a029b3614 100644 --- a/app/Models/Pack.php +++ b/app/Models/Pack.php @@ -34,7 +34,7 @@ class Pack extends Model implements CleansAttributes, ValidableContract * @var array */ protected $fillable = [ - 'option_id', 'uuid', 'name', 'version', 'description', 'selectable', 'visible', 'locked', + 'egg_id', 'uuid', 'name', 'version', 'description', 'selectable', 'visible', 'locked', ]; /** @@ -47,7 +47,7 @@ class Pack extends Model implements CleansAttributes, ValidableContract 'selectable' => 'sometimes|required', 'visible' => 'sometimes|required', 'locked' => 'sometimes|required', - 'option_id' => 'required', + 'egg_id' => 'required', ]; /** @@ -60,7 +60,7 @@ class Pack extends Model implements CleansAttributes, ValidableContract 'selectable' => 'boolean', 'visible' => 'boolean', 'locked' => 'boolean', - 'option_id' => 'exists:service_options,id', + 'egg_id' => 'exists:eggs,id', ]; /** @@ -69,7 +69,7 @@ class Pack extends Model implements CleansAttributes, ValidableContract * @var array */ protected $casts = [ - 'option_id' => 'integer', + 'egg_id' => 'integer', 'selectable' => 'boolean', 'visible' => 'boolean', 'locked' => 'boolean', @@ -83,9 +83,8 @@ class Pack extends Model implements CleansAttributes, ValidableContract protected $searchableColumns = [ 'name' => 10, 'uuid' => 8, - 'option.name' => 6, - 'option.tag' => 5, - 'option.docker_image' => 5, + 'egg.name' => 6, + 'egg.docker_image' => 5, 'version' => 2, ]; @@ -114,13 +113,13 @@ class Pack extends Model implements CleansAttributes, ValidableContract } /** - * Gets option associated with a service pack. + * Gets egg associated with a service pack. * * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ - public function option() + public function egg() { - return $this->belongsTo(ServiceOption::class); + return $this->belongsTo(Egg::class); } /** diff --git a/app/Models/Server.php b/app/Models/Server.php index 9657296c2..09563baf1 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -68,8 +68,8 @@ class Server extends Model implements CleansAttributes, ValidableContract 'io' => 'required', 'cpu' => 'required', 'disk' => 'required', - 'service_id' => 'required', - 'option_id' => 'required', + 'nest_id' => 'required', + 'egg_id' => 'required', 'node_id' => 'required', 'allocation_id' => 'required', 'pack_id' => 'sometimes', @@ -92,8 +92,8 @@ class Server extends Model implements CleansAttributes, ValidableContract 'cpu' => 'numeric|min:0', 'disk' => 'numeric|min:0', 'allocation_id' => 'exists:allocations,id', - 'service_id' => 'exists:services,id', - 'option_id' => 'exists:service_options,id', + 'nest_id' => 'exists:nests,id', + 'egg_id' => 'exists:eggs,id', 'pack_id' => 'nullable|numeric|min:0', 'custom_container' => 'nullable|string', 'startup' => 'nullable|string', @@ -119,8 +119,8 @@ class Server extends Model implements CleansAttributes, ValidableContract 'cpu' => 'integer', 'oom_disabled' => 'integer', 'allocation_id' => 'integer', - 'service_id' => 'integer', - 'option_id' => 'integer', + 'nest_id' => 'integer', + 'egg_id' => 'integer', 'pack_id' => 'integer', 'installed' => 'integer', ]; @@ -202,23 +202,23 @@ class Server extends Model implements CleansAttributes, ValidableContract } /** - * Gets information for the service associated with this server. + * Gets information for the nest associated with this server. * * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ - public function service() + public function nest() { - return $this->belongsTo(Service::class); + return $this->belongsTo(Nest::class); } /** - * Gets information for the service option associated with this server. + * Gets information for the egg associated with this server. * * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ - public function option() + public function egg() { - return $this->belongsTo(ServiceOption::class); + return $this->belongsTo(Egg::class); } /** diff --git a/app/Models/ServerVariable.php b/app/Models/ServerVariable.php index 758044e4e..a17683ed6 100644 --- a/app/Models/ServerVariable.php +++ b/app/Models/ServerVariable.php @@ -74,6 +74,6 @@ class ServerVariable extends Model */ public function variable() { - return $this->belongsTo(ServiceVariable::class, 'variable_id'); + return $this->belongsTo(EggVariable::class, 'variable_id'); } } diff --git a/app/Models/ServiceOption.php b/app/Models/ServiceOption.php deleted file mode 100644 index d59ca8f61..000000000 --- a/app/Models/ServiceOption.php +++ /dev/null @@ -1,185 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Models; - -use Sofa\Eloquence\Eloquence; -use Sofa\Eloquence\Validable; -use Illuminate\Database\Eloquent\Model; -use Sofa\Eloquence\Contracts\CleansAttributes; -use Sofa\Eloquence\Contracts\Validable as ValidableContract; - -class ServiceOption extends Model implements CleansAttributes, ValidableContract -{ - use Eloquence, Validable; - - /** - * The table associated with the model. - * - * @var string - */ - protected $table = 'service_options'; - - /** - * Fields that are not mass assignable. - * - * @var array - */ - protected $guarded = ['id', 'created_at', 'updated_at']; - - /** - * Cast values to correct type. - * - * @var array - */ - protected $casts = [ - 'service_id' => 'integer', - 'script_is_privileged' => 'boolean', - ]; - - /** - * @var array - */ - protected static $applicationRules = [ - 'service_id' => 'required', - 'name' => 'required', - 'description' => 'required', - 'tag' => 'required', - 'docker_image' => 'sometimes', - 'startup' => 'sometimes', - 'config_from' => 'sometimes', - 'config_stop' => 'required_without:config_from', - 'config_startup' => 'required_without:config_from', - 'config_logs' => 'required_without:config_from', - 'config_files' => 'required_without:config_from', - ]; - - /** - * @var array - */ - protected static $dataIntegrityRules = [ - 'service_id' => 'numeric|exists:services,id', - 'name' => 'string|max:255', - 'description' => 'string', - 'tag' => 'alpha_num|max:60|unique:service_options,tag', - 'docker_image' => 'string|max:255', - 'startup' => 'nullable|string', - 'config_from' => 'nullable|numeric|exists:service_options,id', - 'config_stop' => 'nullable|string|max:255', - 'config_startup' => 'nullable|json', - 'config_logs' => 'nullable|json', - 'config_files' => 'nullable|json', - ]; - - /** - * @var array - */ - protected $attributes = [ - 'config_stop' => null, - 'config_startup' => null, - 'config_logs' => null, - 'config_files' => null, - 'startup' => null, - 'docker_image' => null, - ]; - - /** - * Returns the display startup string for the option and will use the parent - * service one if the option does not have one defined. - * - * @return string - */ - public function getDisplayStartupAttribute($value) - { - return (is_null($this->startup)) ? $this->service->startup : $this->startup; - } - - /** - * Returns the install script for the option; if option is copying from another - * it will return the copied script. - * - * @return string - */ - public function getCopyScriptInstallAttribute($value) - { - return (is_null($this->copy_script_from)) ? $this->script_install : $this->copyFrom->script_install; - } - - /** - * Returns the entry command for the option; if option is copying from another - * it will return the copied entry command. - * - * @return string - */ - public function getCopyScriptEntryAttribute($value) - { - return (is_null($this->copy_script_from)) ? $this->script_entry : $this->copyFrom->script_entry; - } - - /** - * Returns the install container for the option; if option is copying from another - * it will return the copied install container. - * - * @return string - */ - public function getCopyScriptContainerAttribute($value) - { - return (is_null($this->copy_script_from)) ? $this->script_container : $this->copyFrom->script_container; - } - - /** - * Gets service associated with a service option. - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo - */ - public function service() - { - return $this->belongsTo(Service::class); - } - - /** - * Gets all servers associated with this service option. - * - * @return \Illuminate\Database\Eloquent\Relations\HasMany - */ - public function servers() - { - return $this->hasMany(Server::class, 'option_id'); - } - - /** - * Gets all variables associated with this service. - * - * @return \Illuminate\Database\Eloquent\Relations\HasMany - */ - public function variables() - { - return $this->hasMany(ServiceVariable::class, 'option_id'); - } - - /** - * Gets all packs associated with this service. - * - * @return \Illuminate\Database\Eloquent\Relations\HasMany - */ - public function packs() - { - return $this->hasMany(Pack::class, 'option_id'); - } - - /** - * Get the parent service option from which to copy scripts. - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo - */ - public function copyFrom() - { - return $this->belongsTo(self::class, 'copy_script_from'); - } -} diff --git a/app/Providers/RepositoryServiceProvider.php b/app/Providers/RepositoryServiceProvider.php index 4582b511c..bc393a883 100644 --- a/app/Providers/RepositoryServiceProvider.php +++ b/app/Providers/RepositoryServiceProvider.php @@ -12,6 +12,8 @@ namespace Pterodactyl\Providers; use Illuminate\Support\ServiceProvider; use Pterodactyl\Repositories\Daemon\FileRepository; use Pterodactyl\Repositories\Daemon\PowerRepository; +use Pterodactyl\Repositories\Eloquent\EggRepository; +use Pterodactyl\Repositories\Eloquent\NestRepository; use Pterodactyl\Repositories\Eloquent\NodeRepository; use Pterodactyl\Repositories\Eloquent\PackRepository; use Pterodactyl\Repositories\Eloquent\TaskRepository; @@ -19,7 +21,6 @@ use Pterodactyl\Repositories\Eloquent\UserRepository; use Pterodactyl\Repositories\Daemon\CommandRepository; use Pterodactyl\Repositories\Eloquent\ApiKeyRepository; use Pterodactyl\Repositories\Eloquent\ServerRepository; -use Pterodactyl\Repositories\Eloquent\ServiceRepository; use Pterodactyl\Repositories\Eloquent\SessionRepository; use Pterodactyl\Repositories\Eloquent\SubuserRepository; use Pterodactyl\Repositories\Eloquent\DatabaseRepository; @@ -28,22 +29,21 @@ use Pterodactyl\Repositories\Eloquent\ScheduleRepository; use Pterodactyl\Repositories\Eloquent\DaemonKeyRepository; use Pterodactyl\Repositories\Eloquent\AllocationRepository; use Pterodactyl\Repositories\Eloquent\PermissionRepository; +use Pterodactyl\Contracts\Repository\EggRepositoryInterface; use Pterodactyl\Repositories\Daemon\ConfigurationRepository; +use Pterodactyl\Repositories\Eloquent\EggVariableRepository; +use Pterodactyl\Contracts\Repository\NestRepositoryInterface; use Pterodactyl\Contracts\Repository\NodeRepositoryInterface; use Pterodactyl\Contracts\Repository\PackRepositoryInterface; use Pterodactyl\Contracts\Repository\TaskRepositoryInterface; use Pterodactyl\Contracts\Repository\UserRepositoryInterface; use Pterodactyl\Repositories\Eloquent\DatabaseHostRepository; use Pterodactyl\Repositories\Eloquent\ApiPermissionRepository; -use Pterodactyl\Repositories\Eloquent\ServiceOptionRepository; use Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface; use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; -use Pterodactyl\Repositories\Eloquent\OptionVariableRepository; use Pterodactyl\Repositories\Eloquent\ServerVariableRepository; -use Pterodactyl\Contracts\Repository\ServiceRepositoryInterface; use Pterodactyl\Contracts\Repository\SessionRepositoryInterface; use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface; -use Pterodactyl\Repositories\Eloquent\ServiceVariableRepository; use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface; use Pterodactyl\Contracts\Repository\LocationRepositoryInterface; use Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface; @@ -51,14 +51,12 @@ use Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface; use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface; use Pterodactyl\Contracts\Repository\PermissionRepositoryInterface; use Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface; +use Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface; use Pterodactyl\Contracts\Repository\Daemon\PowerRepositoryInterface; use Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface; use Pterodactyl\Contracts\Repository\ApiPermissionRepositoryInterface; -use Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface; use Pterodactyl\Contracts\Repository\Daemon\CommandRepositoryInterface; -use Pterodactyl\Contracts\Repository\OptionVariableRepositoryInterface; use Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface; -use Pterodactyl\Contracts\Repository\ServiceVariableRepositoryInterface; use Pterodactyl\Contracts\Repository\Daemon\ConfigurationRepositoryInterface; use Pterodactyl\Repositories\Daemon\ServerRepository as DaemonServerRepository; use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; @@ -77,17 +75,16 @@ class RepositoryServiceProvider extends ServiceProvider $this->app->bind(DaemonKeyRepositoryInterface::class, DaemonKeyRepository::class); $this->app->bind(DatabaseRepositoryInterface::class, DatabaseRepository::class); $this->app->bind(DatabaseHostRepositoryInterface::class, DatabaseHostRepository::class); + $this->app->bind(EggRepositoryInterface::class, EggRepository::class); + $this->app->bind(EggVariableRepositoryInterface::class, EggVariableRepository::class); $this->app->bind(LocationRepositoryInterface::class, LocationRepository::class); + $this->app->bind(NestRepositoryInterface::class, NestRepository::class); $this->app->bind(NodeRepositoryInterface::class, NodeRepository::class); - $this->app->bind(OptionVariableRepositoryInterface::class, OptionVariableRepository::class); $this->app->bind(PackRepositoryInterface::class, PackRepository::class); $this->app->bind(PermissionRepositoryInterface::class, PermissionRepository::class); $this->app->bind(ScheduleRepositoryInterface::class, ScheduleRepository::class); $this->app->bind(ServerRepositoryInterface::class, ServerRepository::class); $this->app->bind(ServerVariableRepositoryInterface::class, ServerVariableRepository::class); - $this->app->bind(ServiceRepositoryInterface::class, ServiceRepository::class); - $this->app->bind(ServiceOptionRepositoryInterface::class, ServiceOptionRepository::class); - $this->app->bind(ServiceVariableRepositoryInterface::class, ServiceVariableRepository::class); $this->app->bind(SessionRepositoryInterface::class, SessionRepository::class); $this->app->bind(SubuserRepositoryInterface::class, SubuserRepository::class); $this->app->bind(TaskRepositoryInterface::class, TaskRepository::class); diff --git a/app/Repositories/Daemon/ServerRepository.php b/app/Repositories/Daemon/ServerRepository.php index c1690eb1a..3515b26e4 100644 --- a/app/Repositories/Daemon/ServerRepository.php +++ b/app/Repositories/Daemon/ServerRepository.php @@ -10,61 +10,29 @@ namespace Pterodactyl\Repositories\Daemon; use Webmozart\Assert\Assert; -use Pterodactyl\Services\Servers\EnvironmentService; +use Psr\Http\Message\ResponseInterface; use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface; -use Pterodactyl\Contracts\Repository\ServerRepositoryInterface as DatabaseServerRepositoryInterface; class ServerRepository extends BaseRepository implements ServerRepositoryInterface { /** - * {@inheritdoc} + * Create a new server on the daemon for the panel. + * + * @param array $structure + * @param array $overrides + * @return \Psr\Http\Message\ResponseInterface + * + * @throws \GuzzleHttp\Exception\RequestException */ - public function create($id, array $overrides = [], $start = false) + public function create(array $structure, array $overrides = []): ResponseInterface { - Assert::numeric($id, 'First argument passed to create must be numeric, received %s.'); - Assert::boolean($start, 'Third argument passed to create must be boolean, received %s.'); - - $repository = $this->app->make(DatabaseServerRepositoryInterface::class); - $environment = $this->app->make(EnvironmentService::class); - - $server = $repository->getDataForCreation($id); - - $data = [ - 'uuid' => (string) $server->uuid, - 'user' => $server->username, - 'build' => [ - 'default' => [ - 'ip' => $server->allocation->ip, - 'port' => $server->allocation->port, - ], - 'ports' => $server->allocations->groupBy('ip')->map(function ($item) { - return $item->pluck('port'); - })->toArray(), - 'env' => $environment->process($server), - 'memory' => (int) $server->memory, - 'swap' => (int) $server->swap, - 'io' => (int) $server->io, - 'cpu' => (int) $server->cpu, - 'disk' => (int) $server->disk, - 'image' => $server->image, - ], - 'service' => [ - 'type' => $server->option->service->folder, - 'option' => $server->option->tag, - 'pack' => object_get($server, 'pack.uuid'), - 'skip_scripts' => $server->skip_scripts, - ], - 'rebuild' => false, - 'start_on_completion' => $start, - ]; - // Loop through overrides. foreach ($overrides as $key => $value) { - array_set($data, $key, $value); + array_set($structure, $key, $value); } return $this->getHttpClient()->request('POST', 'servers', [ - 'json' => $data, + 'json' => $structure, ]); } diff --git a/app/Repositories/Eloquent/EggRepository.php b/app/Repositories/Eloquent/EggRepository.php new file mode 100644 index 000000000..c1fcc583f --- /dev/null +++ b/app/Repositories/Eloquent/EggRepository.php @@ -0,0 +1,112 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Repositories\Eloquent; + +use Pterodactyl\Models\Egg; +use Webmozart\Assert\Assert; +use Illuminate\Database\Eloquent\Collection; +use Pterodactyl\Contracts\Repository\EggRepositoryInterface; +use Pterodactyl\Exceptions\Repository\RecordNotFoundException; + +class EggRepository extends EloquentRepository implements EggRepositoryInterface +{ + /** + * {@inheritdoc} + */ + public function model() + { + return Egg::class; + } + + /** + * Return an egg with the variables relation attached. + * + * @param int $id + * @return \Pterodactyl\Models\Egg + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function getWithVariables(int $id): Egg + { + /** @var \Pterodactyl\Models\Egg $instance */ + $instance = $this->getBuilder()->with('variables')->find($id, $this->getColumns()); + if (! $instance) { + throw new RecordNotFoundException; + } + + return $instance; + } + + /** + * Return all eggs and their relations to be used in the daemon API. + * + * @return \Illuminate\Database\Eloquent\Collection + */ + public function getAllWithCopyAttributes(): Collection + { + return $this->getBuilder()->with('scriptFrom', 'configFrom')->get($this->getColumns()); + } + + /** + * Return an egg with the scriptFrom and configFrom relations loaded onto the model. + * + * @param int|string $value + * @param string $column + * @return \Pterodactyl\Models\Egg + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function getWithCopyAttributes($value, string $column = 'id'): Egg + { + Assert::true((is_digit($value) || is_string($value)), 'First argument passed to getWithCopyAttributes must be an integer or string, received %s.'); + + /** @var \Pterodactyl\Models\Egg $instance */ + $instance = $this->getBuilder()->with('scriptFrom', 'configFrom')->where($column, '=', $value)->first($this->getColumns()); + if (! $instance) { + throw new RecordNotFoundException; + } + + return $instance; + } + + /** + * Return all of the data needed to export a service. + * + * @param int $id + * @return \Pterodactyl\Models\Egg + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function getWithExportAttributes(int $id): Egg + { + /** @var \Pterodactyl\Models\Egg $instance */ + $instance = $this->getBuilder()->with('scriptFrom', 'configFrom', 'variables')->find($id, $this->getColumns()); + if (! $instance) { + throw new RecordNotFoundException; + } + + return $instance; + } + + /** + * Confirm a copy script belongs to the same nest as the item trying to use it. + * + * @param int $copyFromId + * @param int $service + * @return bool + */ + public function isCopiableScript(int $copyFromId, int $service): bool + { + return $this->getBuilder()->whereNull('copy_script_from') + ->where('id', '=', $copyFromId) + ->where('nest_id', '=', $service) + ->exists(); + } +} diff --git a/app/Repositories/Eloquent/OptionVariableRepository.php b/app/Repositories/Eloquent/EggVariableRepository.php similarity index 57% rename from app/Repositories/Eloquent/OptionVariableRepository.php rename to app/Repositories/Eloquent/EggVariableRepository.php index ced324fdf..9fe1174b4 100644 --- a/app/Repositories/Eloquent/OptionVariableRepository.php +++ b/app/Repositories/Eloquent/EggVariableRepository.php @@ -9,16 +9,16 @@ namespace Pterodactyl\Repositories\Eloquent; -use Pterodactyl\Models\ServiceVariable; -use Pterodactyl\Contracts\Repository\OptionVariableRepositoryInterface; +use Pterodactyl\Models\EggVariable; +use Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface; -class OptionVariableRepository extends EloquentRepository implements OptionVariableRepositoryInterface +class EggVariableRepository extends EloquentRepository implements EggVariableRepositoryInterface { /** * {@inheritdoc} */ public function model() { - return ServiceVariable::class; + return EggVariable::class; } } diff --git a/app/Repositories/Eloquent/EloquentRepository.php b/app/Repositories/Eloquent/EloquentRepository.php index d6d82f7ab..ea333d2dc 100644 --- a/app/Repositories/Eloquent/EloquentRepository.php +++ b/app/Repositories/Eloquent/EloquentRepository.php @@ -249,6 +249,10 @@ abstract class EloquentRepository extends Repository implements RepositoryInterf Assert::boolean($validate, 'Third argument passed to updateOrCreate must be boolean, received %s.'); Assert::boolean($force, 'Fourth argument passed to updateOrCreate must be boolean, received %s.'); + foreach ($where as $item) { + Assert::true(is_scalar($item) || is_null($item), 'First argument passed to updateOrCreate should be an array of scalar or null values, received an array value of %s.'); + } + $instance = $this->withColumns('id')->findWhere($where)->first(); if (! $instance) { diff --git a/app/Repositories/Eloquent/NestRepository.php b/app/Repositories/Eloquent/NestRepository.php new file mode 100644 index 000000000..923186c26 --- /dev/null +++ b/app/Repositories/Eloquent/NestRepository.php @@ -0,0 +1,92 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Repositories\Eloquent; + +use Pterodactyl\Models\Nest; +use Pterodactyl\Contracts\Repository\NestRepositoryInterface; +use Pterodactyl\Exceptions\Repository\RecordNotFoundException; + +class NestRepository extends EloquentRepository implements NestRepositoryInterface +{ + /** + * {@inheritdoc} + */ + public function model() + { + return Nest::class; + } + + /** + * Return a nest or all nests with their associated eggs, variables, and packs. + * + * @param int $id + * @return \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Nest + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function getWithEggs(int $id = null) + { + $instance = $this->getBuilder()->with('eggs.packs', 'eggs.variables'); + + if (! is_null($id)) { + $instance = $instance->find($id, $this->getColumns()); + if (! $instance) { + throw new RecordNotFoundException; + } + + return $instance; + } + + return $instance->get($this->getColumns()); + } + + /** + * Return a nest or all nests and the count of eggs, packs, and servers for that nest. + * + * @param int|null $id + * @return \Pterodactyl\Models\Nest|\Illuminate\Database\Eloquent\Collection + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function getWithCounts(int $id = null) + { + $instance = $this->getBuilder()->withCount(['eggs', 'packs', 'servers']); + + if (! is_null($id)) { + $instance = $instance->find($id, $this->getColumns()); + if (! $instance) { + throw new RecordNotFoundException; + } + + return $instance; + } + + return $instance->get($this->getColumns()); + } + + /** + * Return a nest along with its associated eggs and the servers relation on those eggs. + * + * @param int $id + * @return \Pterodactyl\Models\Nest + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function getWithEggServers(int $id): Nest + { + $instance = $this->getBuilder()->with('eggs.servers')->find($id, $this->getColumns()); + if (! $instance) { + throw new RecordNotFoundException; + } + + /* @var Nest $instance */ + return $instance; + } +} diff --git a/app/Repositories/Eloquent/NodeRepository.php b/app/Repositories/Eloquent/NodeRepository.php index 9b48fc1d0..b6a94ec46 100644 --- a/app/Repositories/Eloquent/NodeRepository.php +++ b/app/Repositories/Eloquent/NodeRepository.php @@ -116,7 +116,7 @@ class NodeRepository extends EloquentRepository implements NodeRepositoryInterfa */ public function getNodeServers($id) { - $instance = $this->getBuilder()->with('servers.user', 'servers.service', 'servers.option') + $instance = $this->getBuilder()->with('servers.user', 'servers.nest', 'servers.egg') ->find($id, $this->getColumns()); if (! $instance) { diff --git a/app/Repositories/Eloquent/PackRepository.php b/app/Repositories/Eloquent/PackRepository.php index ba88852fe..b2c555512 100644 --- a/app/Repositories/Eloquent/PackRepository.php +++ b/app/Repositories/Eloquent/PackRepository.php @@ -75,11 +75,11 @@ class PackRepository extends EloquentRepository implements PackRepositoryInterfa /** * {@inheritdoc} */ - public function paginateWithOptionAndServerCount($paginate = 50) + public function paginateWithEggAndServerCount($paginate = 50) { Assert::integer($paginate, 'First argument passed to paginateWithOptionAndServerCount must be integer, received %s.'); - return $this->getBuilder()->with('option')->withCount('servers') + return $this->getBuilder()->with('egg')->withCount('servers') ->search($this->searchTerm) ->paginate($paginate, $this->getColumns()); } diff --git a/app/Repositories/Eloquent/ServerRepository.php b/app/Repositories/Eloquent/ServerRepository.php index 5c86e4a89..10805fdea 100644 --- a/app/Repositories/Eloquent/ServerRepository.php +++ b/app/Repositories/Eloquent/ServerRepository.php @@ -47,7 +47,7 @@ class ServerRepository extends EloquentRepository implements ServerRepositoryInt Assert::nullOrIntegerish($server, 'First argument passed to getDataForRebuild must be null or integer, received %s.'); Assert::nullOrIntegerish($node, 'Second argument passed to getDataForRebuild must be null or integer, received %s.'); - $instance = $this->getBuilder()->with('node', 'option.service', 'pack'); + $instance = $this->getBuilder()->with('allocation', 'allocations', 'pack', 'egg', 'node'); if (! is_null($server) && is_null($node)) { $instance = $instance->where('id', '=', $server); @@ -66,7 +66,7 @@ class ServerRepository extends EloquentRepository implements ServerRepositoryInt { Assert::integerish($id, 'First argument passed to findWithVariables must be integer, received %s.'); - $instance = $this->getBuilder()->with('option.variables', 'variables') + $instance = $this->getBuilder()->with('egg.variables', 'variables') ->where($this->getModel()->getKeyName(), '=', $id) ->first($this->getColumns()); @@ -82,7 +82,7 @@ class ServerRepository extends EloquentRepository implements ServerRepositoryInt */ public function getVariablesWithValues($id, $returnWithObject = false) { - $instance = $this->getBuilder()->with('variables', 'option.variables') + $instance = $this->getBuilder()->with('variables', 'egg.variables') ->find($id, $this->getColumns()); if (! $instance) { @@ -90,7 +90,7 @@ class ServerRepository extends EloquentRepository implements ServerRepositoryInt } $data = []; - $instance->option->variables->each(function ($item) use (&$data, $instance) { + $instance->egg->variables->each(function ($item) use (&$data, $instance) { $display = $instance->variables->where('variable_id', $item->id)->pluck('variable_value')->first(); $data[$item->env_variable] = $display ?? $item->default_value; @@ -111,9 +111,7 @@ class ServerRepository extends EloquentRepository implements ServerRepositoryInt */ public function getDataForCreation($id) { - $instance = $this->getBuilder()->with('allocation', 'allocations', 'pack', 'option.service') - ->find($id, $this->getColumns()); - + $instance = $this->getBuilder()->with(['allocation', 'allocations', 'pack', 'egg'])->find($id, $this->getColumns()); if (! $instance) { throw new RecordNotFoundException(); } @@ -142,15 +140,16 @@ class ServerRepository extends EloquentRepository implements ServerRepositoryInt */ public function getDaemonServiceData($id) { - $instance = $this->getBuilder()->with('option.service', 'pack')->find($id, $this->getColumns()); + Assert::integerish($id, 'First argument passed to getDaemonServiceData must be integer, received %s.'); + $instance = $this->getBuilder()->with('egg.nest', 'pack')->find($id, $this->getColumns()); if (! $instance) { throw new RecordNotFoundException(); } return [ - 'type' => $instance->option->service->folder, - 'option' => $instance->option->tag, + 'type' => $instance->egg->nest->folder, + 'option' => $instance->egg->tag, 'pack' => (! is_null($instance->pack_id)) ? $instance->pack->uuid : null, ]; } @@ -213,7 +212,7 @@ class ServerRepository extends EloquentRepository implements ServerRepositoryInt { Assert::stringNotEmpty($uuid, 'First argument passed to getByUuid must be a non-empty string, received %s.'); - $instance = $this->getBuilder()->with('service', 'node')->where(function ($query) use ($uuid) { + $instance = $this->getBuilder()->with('nest', 'node')->where(function ($query) use ($uuid) { $query->where('uuidShort', $uuid)->orWhere('uuid', $uuid); })->first($this->getColumns()); diff --git a/app/Repositories/Eloquent/ServiceOptionRepository.php b/app/Repositories/Eloquent/ServiceOptionRepository.php deleted file mode 100644 index a2bcd0eb8..000000000 --- a/app/Repositories/Eloquent/ServiceOptionRepository.php +++ /dev/null @@ -1,51 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Repositories\Eloquent; - -use Pterodactyl\Models\ServiceOption; -use Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface; - -class ServiceOptionRepository extends EloquentRepository implements ServiceOptionRepositoryInterface -{ - /** - * {@inheritdoc} - */ - public function model() - { - return ServiceOption::class; - } - - /** - * {@inheritdoc} - */ - public function getWithVariables($id) - { - return $this->getBuilder()->with('variables')->find($id, $this->getColumns()); - } - - /** - * {@inheritdoc} - */ - public function getWithCopyFrom($id) - { - return $this->getBuilder()->with('copyFrom')->find($id, $this->getColumns()); - } - - /** - * {@inheritdoc} - */ - public function isCopiableScript($copyFromId, $service) - { - return $this->getBuilder()->whereNull('copy_script_from') - ->where('id', '=', $copyFromId) - ->where('service_id', '=', $service) - ->exists(); - } -} diff --git a/app/Repositories/Eloquent/ServiceRepository.php b/app/Repositories/Eloquent/ServiceRepository.php deleted file mode 100644 index affe52de4..000000000 --- a/app/Repositories/Eloquent/ServiceRepository.php +++ /dev/null @@ -1,62 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Repositories\Eloquent; - -use Webmozart\Assert\Assert; -use Pterodactyl\Models\Service; -use Pterodactyl\Exceptions\Repository\RecordNotFoundException; -use Pterodactyl\Contracts\Repository\ServiceRepositoryInterface; - -class ServiceRepository extends EloquentRepository implements ServiceRepositoryInterface -{ - /** - * {@inheritdoc} - */ - public function model() - { - return Service::class; - } - - /** - * {@inheritdoc} - */ - public function getWithOptions($id = null) - { - Assert::nullOrNumeric($id, 'First argument passed to getWithOptions must be null or numeric, received %s.'); - - $instance = $this->getBuilder()->with('options.packs', 'options.variables'); - - if (! is_null($id)) { - $instance = $instance->find($id, $this->getColumns()); - if (! $instance) { - throw new RecordNotFoundException(); - } - - return $instance; - } - - return $instance->get($this->getColumns()); - } - - /** - * {@inheritdoc} - */ - public function getWithOptionServers($id) - { - Assert::numeric($id, 'First argument passed to getWithOptionServers must be numeric, received %s.'); - - $instance = $this->getBuilder()->with('options.servers')->find($id, $this->getColumns()); - if (! $instance) { - throw new RecordNotFoundException(); - } - - return $instance; - } -} diff --git a/app/Repositories/Eloquent/ServiceVariableRepository.php b/app/Repositories/Eloquent/ServiceVariableRepository.php deleted file mode 100644 index 6ec798827..000000000 --- a/app/Repositories/Eloquent/ServiceVariableRepository.php +++ /dev/null @@ -1,24 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Repositories\Eloquent; - -use Pterodactyl\Models\ServiceVariable; -use Pterodactyl\Contracts\Repository\ServiceVariableRepositoryInterface; - -class ServiceVariableRepository extends EloquentRepository implements ServiceVariableRepositoryInterface -{ - /** - * {@inheritdoc} - */ - public function model() - { - return ServiceVariable::class; - } -} diff --git a/app/Repositories/Wings/ServerRepository.php b/app/Repositories/Wings/ServerRepository.php index 71774f3b4..3a7653d96 100644 --- a/app/Repositories/Wings/ServerRepository.php +++ b/app/Repositories/Wings/ServerRepository.php @@ -9,6 +9,7 @@ namespace Pterodactyl\Repositories\Wings; +use Psr\Http\Message\ResponseInterface; use Pterodactyl\Exceptions\PterodactylException; use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface; @@ -17,7 +18,7 @@ class ServerRepository extends BaseRepository implements ServerRepositoryInterfa /** * {@inheritdoc} */ - public function create($id, array $overrides = [], $start = false) + public function create(array $structure, array $overrides = []): ResponseInterface { throw new PterodactylException('This feature is not yet implemented.'); } diff --git a/app/Services/Eggs/EggConfigurationService.php b/app/Services/Eggs/EggConfigurationService.php new file mode 100644 index 000000000..a73e3f6a8 --- /dev/null +++ b/app/Services/Eggs/EggConfigurationService.php @@ -0,0 +1,54 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Services\Eggs; + +use Pterodactyl\Models\Egg; +use Pterodactyl\Contracts\Repository\EggRepositoryInterface; + +class EggConfigurationService +{ + /** + * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface + */ + protected $repository; + + /** + * EggConfigurationService constructor. + * + * @param \Pterodactyl\Contracts\Repository\EggRepositoryInterface $repository + */ + public function __construct(EggRepositoryInterface $repository) + { + $this->repository = $repository; + } + + /** + * Return an Egg file to be used by the Daemon. + * + * @param int|\Pterodactyl\Models\Egg $egg + * @return array + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle($egg): array + { + if (! $egg instanceof Egg) { + $egg = $this->repository->getWithCopyAttributes($egg); + } + + return [ + 'startup' => json_decode($egg->inherit_config_startup), + 'stop' => $egg->inherit_config_stop, + 'configs' => json_decode($egg->inherit_config_files), + 'log' => json_decode($egg->inherit_config_logs), + 'query' => 'none', + ]; + } +} diff --git a/app/Services/Eggs/EggCreationService.php b/app/Services/Eggs/EggCreationService.php new file mode 100644 index 000000000..aaf9823f1 --- /dev/null +++ b/app/Services/Eggs/EggCreationService.php @@ -0,0 +1,71 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Services\Eggs; + +use Ramsey\Uuid\Uuid; +use Pterodactyl\Models\Egg; +use Pterodactyl\Contracts\Repository\EggRepositoryInterface; +use Illuminate\Contracts\Config\Repository as ConfigRepository; +use Pterodactyl\Exceptions\Service\Egg\NoParentConfigurationFoundException; + +// When a mommy and a daddy pterodactyl really like eachother... +class EggCreationService +{ + /** + * @var \Illuminate\Contracts\Config\Repository + */ + protected $config; + + /** + * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface + */ + protected $repository; + + /** + * EggCreationService constructor. + * + * @param \Illuminate\Contracts\Config\Repository $config + * @param \Pterodactyl\Contracts\Repository\EggRepositoryInterface $repository + */ + public function __construct(ConfigRepository $config, EggRepositoryInterface $repository) + { + $this->config = $config; + $this->repository = $repository; + } + + /** + * Create a new service option and assign it to the given service. + * + * @param array $data + * @return \Pterodactyl\Models\Egg + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Service\Egg\NoParentConfigurationFoundException + */ + public function handle(array $data): Egg + { + $data['config_from'] = array_get($data, 'config_from'); + if (! is_null($data['config_from'])) { + $results = $this->repository->findCountWhere([ + ['nest_id', '=', array_get($data, 'nest_id')], + ['id', '=', array_get($data, 'config_from')], + ]); + + if ($results !== 1) { + throw new NoParentConfigurationFoundException(trans('exceptions.nest.egg.must_be_child')); + } + } + + return $this->repository->create(array_merge($data, [ + 'uuid' => Uuid::uuid4()->toString(), + 'author' => $this->config->get('pterodactyl.service.author'), + ]), true, true); + } +} diff --git a/app/Services/Eggs/EggDeletionService.php b/app/Services/Eggs/EggDeletionService.php new file mode 100644 index 000000000..5179f6a50 --- /dev/null +++ b/app/Services/Eggs/EggDeletionService.php @@ -0,0 +1,66 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Services\Eggs; + +use Pterodactyl\Contracts\Repository\EggRepositoryInterface; +use Pterodactyl\Exceptions\Service\Egg\HasChildrenException; +use Pterodactyl\Exceptions\Service\HasActiveServersException; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; + +class EggDeletionService +{ + /** + * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface + */ + protected $repository; + + /** + * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface + */ + protected $serverRepository; + + /** + * EggDeletionService constructor. + * + * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $serverRepository + * @param \Pterodactyl\Contracts\Repository\EggRepositoryInterface $repository + */ + public function __construct( + ServerRepositoryInterface $serverRepository, + EggRepositoryInterface $repository + ) { + $this->repository = $repository; + $this->serverRepository = $serverRepository; + } + + /** + * Delete an Egg from the database if it has no active servers attached to it. + * + * @param int $egg + * @return int + * + * @throws \Pterodactyl\Exceptions\Service\HasActiveServersException + * @throws \Pterodactyl\Exceptions\Service\Egg\HasChildrenException + */ + public function handle(int $egg): int + { + $servers = $this->serverRepository->findCountWhere([['egg_id', '=', $egg]]); + if ($servers > 0) { + throw new HasActiveServersException(trans('exceptions.nest.egg.delete_has_servers')); + } + + $children = $this->repository->findCountWhere([['config_from', '=', $egg]]); + if ($children > 0) { + throw new HasChildrenException(trans('exceptions.nest.egg.has_children')); + } + + return $this->repository->delete($egg); + } +} diff --git a/app/Services/Eggs/EggUpdateService.php b/app/Services/Eggs/EggUpdateService.php new file mode 100644 index 000000000..2932b7457 --- /dev/null +++ b/app/Services/Eggs/EggUpdateService.php @@ -0,0 +1,62 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Services\Eggs; + +use Pterodactyl\Models\Egg; +use Pterodactyl\Contracts\Repository\EggRepositoryInterface; +use Pterodactyl\Exceptions\Service\Egg\NoParentConfigurationFoundException; + +class EggUpdateService +{ + /** + * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface + */ + protected $repository; + + /** + * EggUpdateService constructor. + * + * @param \Pterodactyl\Contracts\Repository\EggRepositoryInterface $repository + */ + public function __construct(EggRepositoryInterface $repository) + { + $this->repository = $repository; + } + + /** + * Update a service option. + * + * @param int|\Pterodactyl\Models\Egg $egg + * @param array $data + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\Egg\NoParentConfigurationFoundException + */ + public function handle($egg, array $data) + { + if (! $egg instanceof Egg) { + $egg = $this->repository->find($egg); + } + + if (! is_null(array_get($data, 'config_from'))) { + $results = $this->repository->findCountWhere([ + ['nest_id', '=', $egg->nest_id], + ['id', '=', array_get($data, 'config_from')], + ]); + + if ($results !== 1) { + throw new NoParentConfigurationFoundException(trans('exceptions.nest.egg.must_be_child')); + } + } + + $this->repository->withoutFresh()->update($egg->id, $data); + } +} diff --git a/app/Services/Eggs/Scripts/InstallScriptService.php b/app/Services/Eggs/Scripts/InstallScriptService.php new file mode 100644 index 000000000..094469944 --- /dev/null +++ b/app/Services/Eggs/Scripts/InstallScriptService.php @@ -0,0 +1,63 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Services\Eggs\Scripts; + +use Pterodactyl\Models\Egg; +use Pterodactyl\Contracts\Repository\EggRepositoryInterface; +use Pterodactyl\Exceptions\Service\Egg\InvalidCopyFromException; + +class InstallScriptService +{ + /** + * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface + */ + protected $repository; + + /** + * InstallScriptService constructor. + * + * @param \Pterodactyl\Contracts\Repository\EggRepositoryInterface $repository + */ + public function __construct(EggRepositoryInterface $repository) + { + $this->repository = $repository; + } + + /** + * Modify the install script for a given Egg. + * + * @param int|\Pterodactyl\Models\Egg $egg + * @param array $data + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\Egg\InvalidCopyFromException + */ + public function handle($egg, array $data) + { + if (! $egg instanceof Egg) { + $egg = $this->repository->find($egg); + } + + if (! is_null(array_get($data, 'copy_script_from'))) { + if (! $this->repository->isCopiableScript(array_get($data, 'copy_script_from'), $egg->nest_id)) { + throw new InvalidCopyFromException(trans('exceptions.nest.egg.invalid_copy_id')); + } + } + + $this->repository->withoutFresh()->update($egg->id, [ + 'script_install' => array_get($data, 'script_install'), + 'script_is_privileged' => array_get($data, 'script_is_privileged', 1), + 'script_entry' => array_get($data, 'script_entry'), + 'script_container' => array_get($data, 'script_container'), + 'copy_script_from' => array_get($data, 'copy_script_from'), + ]); + } +} diff --git a/app/Services/Eggs/Sharing/EggExporterService.php b/app/Services/Eggs/Sharing/EggExporterService.php new file mode 100644 index 000000000..25a7131fa --- /dev/null +++ b/app/Services/Eggs/Sharing/EggExporterService.php @@ -0,0 +1,77 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Services\Eggs\Sharing; + +use Carbon\Carbon; +use Pterodactyl\Contracts\Repository\EggRepositoryInterface; + +class EggExporterService +{ + /** + * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface + */ + protected $repository; + + /** + * EggExporterService constructor. + * + * @param \Pterodactyl\Contracts\Repository\EggRepositoryInterface $repository + */ + public function __construct(EggRepositoryInterface $repository) + { + $this->repository = $repository; + } + + /** + * Return a JSON representation of an egg and its variables. + * + * @param int $egg + * @return string + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle(int $egg): string + { + $egg = $this->repository->getWithExportAttributes($egg); + + $struct = [ + '_comment' => 'DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PTERODACTYL PANEL - PTERODACTYL.IO', + 'meta' => [ + 'version' => 'PTDL_v1', + ], + 'exported_at' => Carbon::now()->toIso8601String(), + 'name' => $egg->name, + 'author' => $egg->author, + 'description' => $egg->description, + 'image' => $egg->docker_image, + 'startup' => $egg->startup, + 'config' => [ + 'files' => $egg->inherit_config_files, + 'startup' => $egg->inherit_config_startup, + 'logs' => $egg->inherit_config_logs, + 'stop' => $egg->inherit_config_stop, + ], + 'scripts' => [ + 'installation' => [ + 'script' => $egg->copy_script_install, + 'container' => $egg->copy_script_container, + 'entrypoint' => $egg->copy_script_entry, + ], + ], + 'variables' => $egg->variables->transform(function ($item) { + return collect($item->toArray())->except([ + 'id', 'egg_id', 'created_at', 'updated_at', + ])->toArray(); + }), + ]; + + return json_encode($struct, JSON_PRETTY_PRINT); + } +} diff --git a/app/Services/Eggs/Sharing/EggImporterService.php b/app/Services/Eggs/Sharing/EggImporterService.php new file mode 100644 index 000000000..7143bbc6e --- /dev/null +++ b/app/Services/Eggs/Sharing/EggImporterService.php @@ -0,0 +1,124 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Services\Eggs\Sharing; + +use Ramsey\Uuid\Uuid; +use Pterodactyl\Models\Egg; +use Illuminate\Http\UploadedFile; +use Illuminate\Database\ConnectionInterface; +use Pterodactyl\Contracts\Repository\EggRepositoryInterface; +use Pterodactyl\Contracts\Repository\NestRepositoryInterface; +use Pterodactyl\Exceptions\Service\Egg\BadJsonFormatException; +use Pterodactyl\Exceptions\Service\InvalidFileUploadException; +use Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface; + +class EggImporterService +{ + /** + * @var \Illuminate\Database\ConnectionInterface + */ + protected $connection; + + /** + * @var \Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface + */ + protected $eggVariableRepository; + + /** + * @var \Pterodactyl\Contracts\Repository\NestRepositoryInterface + */ + protected $nestRepository; + + /** + * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface + */ + protected $repository; + + /** + * EggImporterService constructor. + * + * @param \Illuminate\Database\ConnectionInterface $connection + * @param \Pterodactyl\Contracts\Repository\EggRepositoryInterface $repository + * @param \Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface $eggVariableRepository + * @param \Pterodactyl\Contracts\Repository\NestRepositoryInterface $nestRepository + */ + public function __construct( + ConnectionInterface $connection, + EggRepositoryInterface $repository, + EggVariableRepositoryInterface $eggVariableRepository, + NestRepositoryInterface $nestRepository + ) { + $this->connection = $connection; + $this->eggVariableRepository = $eggVariableRepository; + $this->repository = $repository; + $this->nestRepository = $nestRepository; + } + + /** + * Take an uploaded JSON file and parse it into a new egg. + * + * @param \Illuminate\Http\UploadedFile $file + * @param int $nest + * @return \Pterodactyl\Models\Egg + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\Egg\BadJsonFormatException + * @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException + */ + public function handle(UploadedFile $file, int $nest): Egg + { + if (! $file->isValid() || ! $file->isFile()) { + throw new InvalidFileUploadException(trans('exceptions.nest.importer.file_error')); + } + + $parsed = json_decode($file->openFile()->fread($file->getSize())); + if (json_last_error() !== 0) { + throw new BadJsonFormatException(trans('exceptions.nest.importer.json_error', [ + 'error' => json_last_error_msg(), + ])); + } + + if (object_get($parsed, 'meta.version') !== 'PTDL_v1') { + throw new InvalidFileUploadException(trans('exceptions.nest.importer.invalid_json_provided')); + } + + $nest = $this->nestRepository->getWithEggs($nest); + $this->connection->beginTransaction(); + + $egg = $this->repository->create([ + 'uuid' => Uuid::uuid4()->toString(), + 'nest_id' => $nest->id, + 'author' => object_get($parsed, 'author'), + 'name' => object_get($parsed, 'name'), + 'description' => object_get($parsed, 'description'), + 'docker_image' => object_get($parsed, 'image'), + 'config_files' => object_get($parsed, 'config.files'), + 'config_startup' => object_get($parsed, 'config.startup'), + 'config_logs' => object_get($parsed, 'config.logs'), + 'config_stop' => object_get($parsed, 'config.stop'), + 'startup' => object_get($parsed, 'startup'), + 'script_install' => object_get($parsed, 'scripts.installation.script'), + 'script_entry' => object_get($parsed, 'scripts.installation.entrypoint'), + 'script_container' => object_get($parsed, 'scripts.installation.container'), + 'copy_script_from' => null, + ], true, true); + + collect($parsed->variables)->each(function ($variable) use ($egg) { + $this->eggVariableRepository->create(array_merge((array) $variable, [ + 'egg_id' => $egg->id, + ])); + }); + + $this->connection->commit(); + + return $egg; + } +} diff --git a/app/Services/Eggs/Sharing/EggUpdateImporterService.php b/app/Services/Eggs/Sharing/EggUpdateImporterService.php new file mode 100644 index 000000000..41bc29f06 --- /dev/null +++ b/app/Services/Eggs/Sharing/EggUpdateImporterService.php @@ -0,0 +1,113 @@ +connection = $connection; + $this->repository = $repository; + $this->variableRepository = $variableRepository; + } + + /** + * Update an existing Egg using an uploaded JSON file. + * + * @param int $egg + * @param \Illuminate\Http\UploadedFile $file + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\Egg\BadJsonFormatException + * @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException + */ + public function handle(int $egg, UploadedFile $file) + { + if (! $file->isValid() || ! $file->isFile()) { + throw new InvalidFileUploadException(trans('exceptions.nest.importer.file_error')); + } + + $parsed = json_decode($file->openFile()->fread($file->getSize())); + if (json_last_error() !== 0) { + throw new BadJsonFormatException(trans('exceptions.nest.importer.json_error', [ + 'error' => json_last_error_msg(), + ])); + } + + if (object_get($parsed, 'meta.version') !== 'PTDL_v1') { + throw new InvalidFileUploadException(trans('exceptions.nest.importer.invalid_json_provided')); + } + + $this->connection->beginTransaction(); + $this->repository->update($egg, [ + 'author' => object_get($parsed, 'author'), + 'name' => object_get($parsed, 'name'), + 'description' => object_get($parsed, 'description'), + 'docker_image' => object_get($parsed, 'image'), + 'config_files' => object_get($parsed, 'config.files'), + 'config_startup' => object_get($parsed, 'config.startup'), + 'config_logs' => object_get($parsed, 'config.logs'), + 'config_stop' => object_get($parsed, 'config.stop'), + 'startup' => object_get($parsed, 'startup'), + 'script_install' => object_get($parsed, 'scripts.installation.script'), + 'script_entry' => object_get($parsed, 'scripts.installation.entrypoint'), + 'script_container' => object_get($parsed, 'scripts.installation.container'), + ], true, true); + + // Update Existing Variables + collect($parsed->variables)->each(function ($variable) use ($egg) { + $this->variableRepository->withoutFresh()->updateOrCreate([ + 'egg_id' => $egg, + 'env_variable' => $variable->env_variable, + ], collect($variable)->except(['egg_id', 'env_variable'])->toArray()); + }); + + $imported = collect($parsed->variables)->pluck('env_variable')->toArray(); + $existing = $this->variableRepository->withColumns(['id', 'env_variable'])->findWhere([['egg_id', '=', $egg]]); + + // Delete variables not present in the import. + collect($existing)->each(function ($variable) use ($egg, $imported) { + if (! in_array($variable->env_variable, $imported)) { + $this->variableRepository->deleteWhere([ + ['egg_id', '=', $egg], + ['env_variable', '=', $variable->env_variable], + ]); + } + }); + + $this->connection->commit(); + } +} diff --git a/app/Services/Eggs/Variables/VariableCreationService.php b/app/Services/Eggs/Variables/VariableCreationService.php new file mode 100644 index 000000000..920ca312e --- /dev/null +++ b/app/Services/Eggs/Variables/VariableCreationService.php @@ -0,0 +1,60 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Services\Eggs\Variables; + +use Pterodactyl\Models\EggVariable; +use Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface; +use Pterodactyl\Exceptions\Service\Egg\Variable\ReservedVariableNameException; + +class VariableCreationService +{ + /** + * @var \Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface + */ + protected $repository; + + /** + * VariableCreationService constructor. + * + * @param \Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface $repository + */ + public function __construct(EggVariableRepositoryInterface $repository) + { + $this->repository = $repository; + } + + /** + * Create a new variable for a given Egg. + * + * @param int $egg + * @param array $data + * @return \Pterodactyl\Models\EggVariable + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Service\Egg\Variable\ReservedVariableNameException + */ + public function handle(int $egg, array $data): EggVariable + { + if (in_array(strtoupper(array_get($data, 'env_variable')), explode(',', EggVariable::RESERVED_ENV_NAMES))) { + throw new ReservedVariableNameException(sprintf( + 'Cannot use the protected name %s for this environment variable.', + array_get($data, 'env_variable') + )); + } + + $options = array_get($data, 'options', []); + + return $this->repository->create(array_merge($data, [ + 'egg_id' => $egg, + 'user_viewable' => in_array('user_viewable', $options), + 'user_editable' => in_array('user_editable', $options), + ])); + } +} diff --git a/app/Services/Services/Variables/VariableUpdateService.php b/app/Services/Eggs/Variables/VariableUpdateService.php similarity index 65% rename from app/Services/Services/Variables/VariableUpdateService.php rename to app/Services/Eggs/Variables/VariableUpdateService.php index 08bb35dc0..9c4f67fa7 100644 --- a/app/Services/Services/Variables/VariableUpdateService.php +++ b/app/Services/Eggs/Variables/VariableUpdateService.php @@ -7,50 +7,50 @@ * https://opensource.org/licenses/MIT */ -namespace Pterodactyl\Services\Services\Variables; +namespace Pterodactyl\Services\Eggs\Variables; -use Pterodactyl\Models\ServiceVariable; +use Pterodactyl\Models\EggVariable; use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Contracts\Repository\ServiceVariableRepositoryInterface; -use Pterodactyl\Exceptions\Service\ServiceVariable\ReservedVariableNameException; +use Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface; +use Pterodactyl\Exceptions\Service\Egg\Variable\ReservedVariableNameException; class VariableUpdateService { /** - * @var \Pterodactyl\Contracts\Repository\ServiceVariableRepositoryInterface + * @var \Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface */ protected $repository; /** * VariableUpdateService constructor. * - * @param \Pterodactyl\Contracts\Repository\ServiceVariableRepositoryInterface $repository + * @param \Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface $repository */ - public function __construct(ServiceVariableRepositoryInterface $repository) + public function __construct(EggVariableRepositoryInterface $repository) { $this->repository = $repository; } /** - * Update a specific service variable. + * Update a specific egg variable. * - * @param int|\Pterodactyl\Models\ServiceVariable $variable - * @param array $data + * @param int|\Pterodactyl\Models\EggVariable $variable + * @param array $data * @return mixed * * @throws \Pterodactyl\Exceptions\DisplayException * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - * @throws \Pterodactyl\Exceptions\Service\ServiceVariable\ReservedVariableNameException + * @throws \Pterodactyl\Exceptions\Service\Egg\Variable\ReservedVariableNameException */ public function handle($variable, array $data) { - if (! $variable instanceof ServiceVariable) { + if (! $variable instanceof EggVariable) { $variable = $this->repository->find($variable); } if (! is_null(array_get($data, 'env_variable'))) { - if (in_array(strtoupper(array_get($data, 'env_variable')), explode(',', ServiceVariable::RESERVED_ENV_NAMES))) { + if (in_array(strtoupper(array_get($data, 'env_variable')), explode(',', EggVariable::RESERVED_ENV_NAMES))) { throw new ReservedVariableNameException(trans('exceptions.service.variables.reserved_name', [ 'name' => array_get($data, 'env_variable'), ])); @@ -58,7 +58,7 @@ class VariableUpdateService $search = $this->repository->withColumns('id')->findCountWhere([ ['env_variable', '=', array_get($data, 'env_variable')], - ['option_id', '=', $variable->option_id], + ['egg_id', '=', $variable->egg_id], ['id', '!=', $variable->id], ]); @@ -71,9 +71,9 @@ class VariableUpdateService $options = array_get($data, 'options', []); - return $this->repository->withoutFresh()->update($variable->id, array_merge([ + return $this->repository->withoutFresh()->update($variable->id, array_merge($data, [ 'user_viewable' => in_array('user_viewable', $options), 'user_editable' => in_array('user_editable', $options), - ], $data)); + ])); } } diff --git a/app/Services/Nests/NestCreationService.php b/app/Services/Nests/NestCreationService.php new file mode 100644 index 000000000..cad638844 --- /dev/null +++ b/app/Services/Nests/NestCreationService.php @@ -0,0 +1,58 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Services\Nests; + +use Ramsey\Uuid\Uuid; +use Pterodactyl\Models\Nest; +use Pterodactyl\Contracts\Repository\NestRepositoryInterface; +use Illuminate\Contracts\Config\Repository as ConfigRepository; + +class NestCreationService +{ + /** + * @var \Illuminate\Contracts\Config\Repository + */ + protected $config; + + /** + * @var \Pterodactyl\Contracts\Repository\NestRepositoryInterface + */ + protected $repository; + + /** + * NestCreationService constructor. + * + * @param \Illuminate\Contracts\Config\Repository $config + * @param \Pterodactyl\Contracts\Repository\NestRepositoryInterface $repository + */ + public function __construct(ConfigRepository $config, NestRepositoryInterface $repository) + { + $this->config = $config; + $this->repository = $repository; + } + + /** + * Create a new nest on the system. + * + * @param array $data + * @return \Pterodactyl\Models\Nest + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + public function handle(array $data): Nest + { + return $this->repository->create([ + 'uuid' => Uuid::uuid4()->toString(), + 'author' => $this->config->get('pterodactyl.service.author'), + 'name' => array_get($data, 'name'), + 'description' => array_get($data, 'description'), + ], true, true); + } +} diff --git a/app/Services/Services/ServiceDeletionService.php b/app/Services/Nests/NestDeletionService.php similarity index 60% rename from app/Services/Services/ServiceDeletionService.php rename to app/Services/Nests/NestDeletionService.php index def352b8d..6bdaf8de2 100644 --- a/app/Services/Services/ServiceDeletionService.php +++ b/app/Services/Nests/NestDeletionService.php @@ -7,13 +7,13 @@ * https://opensource.org/licenses/MIT */ -namespace Pterodactyl\Services\Services; +namespace Pterodactyl\Services\Nests; +use Pterodactyl\Contracts\Repository\NestRepositoryInterface; use Pterodactyl\Exceptions\Service\HasActiveServersException; use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; -use Pterodactyl\Contracts\Repository\ServiceRepositoryInterface; -class ServiceDeletionService +class NestDeletionService { /** * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface @@ -21,39 +21,39 @@ class ServiceDeletionService protected $serverRepository; /** - * @var \Pterodactyl\Contracts\Repository\ServiceRepositoryInterface + * @var \Pterodactyl\Contracts\Repository\NestRepositoryInterface */ protected $repository; /** - * ServiceDeletionService constructor. + * NestDeletionService constructor. * - * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $serverRepository - * @param \Pterodactyl\Contracts\Repository\ServiceRepositoryInterface $repository + * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $serverRepository + * @param \Pterodactyl\Contracts\Repository\NestRepositoryInterface $repository */ public function __construct( ServerRepositoryInterface $serverRepository, - ServiceRepositoryInterface $repository + NestRepositoryInterface $repository ) { $this->serverRepository = $serverRepository; $this->repository = $repository; } /** - * Delete a service from the system only if there are no servers attached to it. + * Delete a nest from the system only if there are no servers attached to it. * - * @param int $service + * @param int $nest * @return int * * @throws \Pterodactyl\Exceptions\Service\HasActiveServersException */ - public function handle($service) + public function handle(int $nest): int { - $count = $this->serverRepository->findCountWhere([['service_id', '=', $service]]); + $count = $this->serverRepository->findCountWhere([['nest_id', '=', $nest]]); if ($count > 0) { throw new HasActiveServersException(trans('exceptions.service.delete_has_servers')); } - return $this->repository->delete($service); + return $this->repository->delete($nest); } } diff --git a/app/Services/Services/ServiceUpdateService.php b/app/Services/Nests/NestUpdateService.php similarity index 50% rename from app/Services/Services/ServiceUpdateService.php rename to app/Services/Nests/NestUpdateService.php index 59662e2a5..12f3a506a 100644 --- a/app/Services/Services/ServiceUpdateService.php +++ b/app/Services/Nests/NestUpdateService.php @@ -7,41 +7,41 @@ * https://opensource.org/licenses/MIT */ -namespace Pterodactyl\Services\Services; +namespace Pterodactyl\Services\Nests; -use Pterodactyl\Contracts\Repository\ServiceRepositoryInterface; +use Pterodactyl\Contracts\Repository\NestRepositoryInterface; -class ServiceUpdateService +class NestUpdateService { /** - * @var \Pterodactyl\Contracts\Repository\ServiceRepositoryInterface + * @var \Pterodactyl\Contracts\Repository\NestRepositoryInterface */ protected $repository; /** - * ServiceUpdateService constructor. + * NestUpdateService constructor. * - * @param \Pterodactyl\Contracts\Repository\ServiceRepositoryInterface $repository + * @param \Pterodactyl\Contracts\Repository\NestRepositoryInterface $repository */ - public function __construct(ServiceRepositoryInterface $repository) + public function __construct(NestRepositoryInterface $repository) { $this->repository = $repository; } /** - * Update a service and prevent changing the author once it is set. + * Update a nest and prevent changing the author once it is set. * - * @param int $service + * @param int $nest * @param array $data * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function handle($service, array $data) + public function handle(int $nest, array $data) { if (! is_null(array_get($data, 'author'))) { unset($data['author']); } - $this->repository->withoutFresh()->update($service, $data); + $this->repository->withoutFresh()->update($nest, $data); } } diff --git a/app/Services/Packs/PackCreationService.php b/app/Services/Packs/PackCreationService.php index ff40f072a..80b452d88 100644 --- a/app/Services/Packs/PackCreationService.php +++ b/app/Services/Packs/PackCreationService.php @@ -13,8 +13,8 @@ use Ramsey\Uuid\Uuid; use Illuminate\Http\UploadedFile; use Illuminate\Database\ConnectionInterface; use Pterodactyl\Contracts\Repository\PackRepositoryInterface; +use Pterodactyl\Exceptions\Service\InvalidFileUploadException; use Illuminate\Contracts\Filesystem\Factory as FilesystemFactory; -use Pterodactyl\Exceptions\Service\Pack\InvalidFileUploadException; use Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException; class PackCreationService @@ -65,7 +65,7 @@ class PackCreationService * * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException - * @throws \Pterodactyl\Exceptions\Service\Pack\InvalidFileUploadException + * @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException */ public function handle(array $data, UploadedFile $file = null) { diff --git a/app/Services/Packs/PackUpdateService.php b/app/Services/Packs/PackUpdateService.php index 6d5555ce5..bb84f7b98 100644 --- a/app/Services/Packs/PackUpdateService.php +++ b/app/Services/Packs/PackUpdateService.php @@ -54,10 +54,10 @@ class PackUpdateService public function handle($pack, array $data) { if (! $pack instanceof Pack) { - $pack = $this->repository->withColumns(['id', 'option_id'])->find($pack); + $pack = $this->repository->withColumns(['id', 'egg_id'])->find($pack); } - if ((int) array_get($data, 'option_id', $pack->option_id) !== $pack->option_id) { + if ((int) array_get($data, 'egg_id', $pack->egg_id) !== $pack->egg_id) { $count = $this->serverRepository->findCountWhere([['pack_id', '=', $pack->id]]); if ($count !== 0) { diff --git a/app/Services/Packs/TemplateUploadService.php b/app/Services/Packs/TemplateUploadService.php index 7007f3bb4..6495edd68 100644 --- a/app/Services/Packs/TemplateUploadService.php +++ b/app/Services/Packs/TemplateUploadService.php @@ -11,8 +11,8 @@ namespace Pterodactyl\Services\Packs; use ZipArchive; use Illuminate\Http\UploadedFile; +use Pterodactyl\Exceptions\Service\InvalidFileUploadException; use Pterodactyl\Exceptions\Service\Pack\ZipExtractionException; -use Pterodactyl\Exceptions\Service\Pack\InvalidFileUploadException; use Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException; use Pterodactyl\Exceptions\Service\Pack\UnreadableZipArchiveException; use Pterodactyl\Exceptions\Service\Pack\InvalidPackArchiveFormatException; @@ -52,18 +52,18 @@ class TemplateUploadService /** * Process an uploaded file to create a new pack from a JSON or ZIP format. * - * @param int $option + * @param int $egg * @param \Illuminate\Http\UploadedFile $file * @return \Pterodactyl\Models\Pack * * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Service\Pack\ZipExtractionException - * @throws \Pterodactyl\Exceptions\Service\Pack\InvalidFileUploadException + * @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException * @throws \Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException * @throws \Pterodactyl\Exceptions\Service\Pack\UnreadableZipArchiveException * @throws \Pterodactyl\Exceptions\Service\Pack\InvalidPackArchiveFormatException */ - public function handle($option, UploadedFile $file) + public function handle($egg, UploadedFile $file) { if (! $file->isValid()) { throw new InvalidFileUploadException(trans('exceptions.packs.invalid_upload')); @@ -76,10 +76,10 @@ class TemplateUploadService } if ($file->getMimeType() === 'application/zip') { - return $this->handleArchive($option, $file); + return $this->handleArchive($egg, $file); } else { $json = json_decode($file->openFile()->fread($file->getSize()), true); - $json['option_id'] = $option; + $json['egg_id'] = $egg; return $this->creationService->handle($json); } @@ -88,18 +88,18 @@ class TemplateUploadService /** * Process a ZIP file to create a pack and stored archive. * - * @param int $option + * @param int $egg * @param \Illuminate\Http\UploadedFile $file * @return \Pterodactyl\Models\Pack * * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Service\Pack\ZipExtractionException - * @throws \Pterodactyl\Exceptions\Service\Pack\InvalidFileUploadException + * @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException * @throws \Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException * @throws \Pterodactyl\Exceptions\Service\Pack\UnreadableZipArchiveException * @throws \Pterodactyl\Exceptions\Service\Pack\InvalidPackArchiveFormatException */ - protected function handleArchive($option, $file) + protected function handleArchive($egg, $file) { if (! $this->archive->open($file->getRealPath())) { throw new UnreadableZipArchiveException(trans('exceptions.packs.unreadable')); @@ -110,7 +110,7 @@ class TemplateUploadService } $json = json_decode($this->archive->getFromName('import.json'), true); - $json['option_id'] = $option; + $json['egg_id'] = $egg; $pack = $this->creationService->handle($json); if (! $this->archive->extractTo(storage_path('app/packs/' . $pack->uuid), 'archive.tar.gz')) { diff --git a/app/Services/Servers/ServerConfigurationStructureService.php b/app/Services/Servers/ServerConfigurationStructureService.php new file mode 100644 index 000000000..b92191711 --- /dev/null +++ b/app/Services/Servers/ServerConfigurationStructureService.php @@ -0,0 +1,83 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Services\Servers; + +use Pterodactyl\Models\Server; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; + +class ServerConfigurationStructureService +{ + const REQUIRED_RELATIONS = ['allocation', 'allocations', 'pack', 'option']; + + /** + * @var \Pterodactyl\Services\Servers\EnvironmentService + */ + protected $environment; + + /** + * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface + */ + protected $repository; + + /** + * ServerConfigurationStructureService constructor. + * + * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository + * @param \Pterodactyl\Services\Servers\EnvironmentService $environment + */ + public function __construct( + ServerRepositoryInterface $repository, + EnvironmentService $environment + ) { + $this->repository = $repository; + $this->environment = $environment; + } + + /** + * @param int|\Pterodactyl\Models\Server $server + * @return array + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function handle($server): array + { + if (! $server instanceof Server || array_diff(self::REQUIRED_RELATIONS, $server->getRelations())) { + $server = $this->repository->getDataForCreation(is_digit($server) ? $server : $server->id); + } + + return [ + 'uuid' => $server->uuid, + 'user' => $server->username, + 'build' => [ + 'default' => [ + 'ip' => $server->allocation->ip, + 'port' => $server->allocation->port, + ], + 'ports' => $server->allocations->groupBy('ip')->map(function ($item) { + return $item->pluck('port'); + })->toArray(), + 'env' => $this->environment->process($server), + 'memory' => (int) $server->memory, + 'swap' => (int) $server->swap, + 'io' => (int) $server->io, + 'cpu' => (int) $server->cpu, + 'disk' => (int) $server->disk, + 'image' => $server->image, + ], + 'keys' => [], + 'service' => [ + 'egg' => $server->egg->uuid, + 'pack' => object_get($server, 'pack.uuid'), + 'skip_scripts' => $server->skip_scripts, + ], + 'rebuild' => false, + 'suspended' => (int) $server->suspended, + ]; + } +} diff --git a/app/Services/Servers/ServerCreationService.php b/app/Services/Servers/ServerCreationService.php index 122bac629..0994abe55 100644 --- a/app/Services/Servers/ServerCreationService.php +++ b/app/Services/Servers/ServerCreationService.php @@ -10,15 +10,14 @@ namespace Pterodactyl\Services\Servers; use Ramsey\Uuid\Uuid; -use Illuminate\Log\Writer; -use Illuminate\Database\DatabaseManager; use GuzzleHttp\Exception\RequestException; -use Pterodactyl\Exceptions\DisplayException; +use Illuminate\Database\ConnectionInterface; use Pterodactyl\Services\Nodes\NodeCreationService; use Pterodactyl\Contracts\Repository\NodeRepositoryInterface; use Pterodactyl\Contracts\Repository\UserRepositoryInterface; use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface; +use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; use Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface; use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; @@ -29,16 +28,21 @@ class ServerCreationService */ protected $allocationRepository; + /** + * @var \Pterodactyl\Services\Servers\ServerConfigurationStructureService + */ + protected $configurationStructureService; + + /** + * @var \Illuminate\Database\ConnectionInterface + */ + protected $connection; + /** * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface */ protected $daemonServerRepository; - /** - * @var \Illuminate\Database\DatabaseManager - */ - protected $database; - /** * @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface */ @@ -69,47 +73,42 @@ class ServerCreationService */ protected $validatorService; - /** - * @var \Illuminate\Log\Writer - */ - protected $writer; - /** * CreationService constructor. * * @param \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface $allocationRepository + * @param \Illuminate\Database\ConnectionInterface $connection * @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonServerRepository - * @param \Illuminate\Database\DatabaseManager $database * @param \Pterodactyl\Contracts\Repository\NodeRepositoryInterface $nodeRepository + * @param \Pterodactyl\Services\Servers\ServerConfigurationStructureService $configurationStructureService * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository * @param \Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface $serverVariableRepository * @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $userRepository * @param \Pterodactyl\Services\Servers\UsernameGenerationService $usernameService * @param \Pterodactyl\Services\Servers\VariableValidatorService $validatorService - * @param \Illuminate\Log\Writer $writer */ public function __construct( AllocationRepositoryInterface $allocationRepository, + ConnectionInterface $connection, DaemonServerRepositoryInterface $daemonServerRepository, - DatabaseManager $database, NodeRepositoryInterface $nodeRepository, + ServerConfigurationStructureService $configurationStructureService, ServerRepositoryInterface $repository, ServerVariableRepositoryInterface $serverVariableRepository, UserRepositoryInterface $userRepository, UsernameGenerationService $usernameService, - VariableValidatorService $validatorService, - Writer $writer + VariableValidatorService $validatorService ) { $this->allocationRepository = $allocationRepository; + $this->configurationStructureService = $configurationStructureService; + $this->connection = $connection; $this->daemonServerRepository = $daemonServerRepository; - $this->database = $database; $this->nodeRepository = $nodeRepository; $this->repository = $repository; $this->serverVariableRepository = $serverVariableRepository; $this->userRepository = $userRepository; $this->usernameService = $usernameService; $this->validatorService = $validatorService; - $this->writer = $writer; } /** @@ -125,10 +124,10 @@ class ServerCreationService public function create(array $data) { // @todo auto-deployment - $validator = $this->validatorService->isAdmin()->setFields($data['environment'])->validate($data['option_id']); + $validator = $this->validatorService->isAdmin()->setFields($data['environment'])->validate($data['egg_id']); $uniqueShort = str_random(8); - $this->database->beginTransaction(); + $this->connection->beginTransaction(); $server = $this->repository->create([ 'uuid' => Uuid::uuid4()->toString(), @@ -146,8 +145,8 @@ class ServerCreationService 'cpu' => $data['cpu'], 'oom_disabled' => isset($data['oom_disabled']), 'allocation_id' => $data['allocation_id'], - 'service_id' => $data['service_id'], - 'option_id' => $data['option_id'], + 'nest_id' => $data['nest_id'], + 'egg_id' => $data['egg_id'], 'pack_id' => (! isset($data['pack_id']) || $data['pack_id'] == 0) ? null : $data['pack_id'], 'startup' => $data['startup'], 'daemonSecret' => str_random(NodeCreationService::DAEMON_SECRET_LENGTH), @@ -175,19 +174,17 @@ class ServerCreationService } $this->serverVariableRepository->insert($records); + $structure = $this->configurationStructureService->handle($server->id); // Create the server on the daemon & commit it to the database. try { - $this->daemonServerRepository->setNode($server->node_id)->create($server->id); - $this->database->commit(); + $this->daemonServerRepository->setNode($server->node_id)->create($structure, [ + 'start_on_completion' => (bool) array_get($data, 'start_on_completion', false), + ]); + $this->connection->commit(); } catch (RequestException $exception) { - $response = $exception->getResponse(); - $this->writer->warning($exception); - $this->database->rollBack(); - - throw new DisplayException(trans('admin/server.exceptions.daemon_exception', [ - 'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(), - ])); + $this->connection->rollBack(); + throw new DaemonConnectionException($exception); } return $server; diff --git a/app/Services/Servers/StartupModificationService.php b/app/Services/Servers/StartupModificationService.php index fe29e6ada..25650b1c0 100644 --- a/app/Services/Servers/StartupModificationService.php +++ b/app/Services/Servers/StartupModificationService.php @@ -110,8 +110,8 @@ class StartupModificationService } if ( - $server->service_id != array_get($data, 'service_id', $server->service_id) || - $server->option_id != array_get($data, 'option_id', $server->option_id) || + $server->nest_id != array_get($data, 'nest_id', $server->nest_id) || + $server->egg_id != array_get($data, 'egg_id', $server->egg_id) || $server->pack_id != array_get($data, 'pack_id', $server->pack_id) ) { $hasServiceChanges = true; @@ -121,7 +121,7 @@ class StartupModificationService if (isset($data['environment'])) { $validator = $this->validatorService->isAdmin($this->admin) ->setFields($data['environment']) - ->validate(array_get($data, 'option_id', $server->option_id)); + ->validate(array_get($data, 'egg_id', $server->egg_id)); foreach ($validator->getResults() as $result) { $this->serverVariableRepository->withoutFresh()->updateOrCreate([ @@ -143,15 +143,15 @@ class StartupModificationService $server = $this->repository->update($server->id, [ 'installed' => 0, 'startup' => array_get($data, 'startup', $server->startup), - 'service_id' => array_get($data, 'service_id', $server->service_id), - 'option_id' => array_get($data, 'option_id', $server->service_id), - 'pack_id' => array_get($data, 'pack_id', $server->pack_id), + 'nest_id' => array_get($data, 'nest_id', $server->nest_id), + 'egg_id' => array_get($data, 'egg_id', $server->egg_id), + 'pack_id' => array_get($data, 'pack_id', $server->pack_id) > 0 ? array_get($data, 'pack_id', $server->pack_id) : null, 'skip_scripts' => isset($data['skip_scripts']), ]); if (isset($hasServiceChanges)) { $daemonData['service'] = array_merge( - $this->repository->withColumns(['id', 'option_id', 'pack_id'])->getDaemonServiceData($server), + $this->repository->withColumns(['id', 'egg_id', 'pack_id'])->getDaemonServiceData($server->id), ['skip_scripts' => isset($data['skip_scripts'])] ); } diff --git a/app/Services/Servers/VariableValidatorService.php b/app/Services/Servers/VariableValidatorService.php index 6c9efe32b..7340d2f7b 100644 --- a/app/Services/Servers/VariableValidatorService.php +++ b/app/Services/Servers/VariableValidatorService.php @@ -12,7 +12,7 @@ namespace Pterodactyl\Services\Servers; use Pterodactyl\Exceptions\DisplayValidationException; use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; use Illuminate\Contracts\Validation\Factory as ValidationFactory; -use Pterodactyl\Contracts\Repository\OptionVariableRepositoryInterface; +use Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface; use Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface; class VariableValidatorService @@ -33,7 +33,7 @@ class VariableValidatorService protected $results = []; /** - * @var \Pterodactyl\Contracts\Repository\OptionVariableRepositoryInterface + * @var \Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface */ protected $optionVariableRepository; @@ -55,13 +55,13 @@ class VariableValidatorService /** * VariableValidatorService constructor. * - * @param \Pterodactyl\Contracts\Repository\OptionVariableRepositoryInterface $optionVariableRepository + * @param \Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface $optionVariableRepository * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $serverRepository * @param \Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface $serverVariableRepository * @param \Illuminate\Contracts\Validation\Factory $validator */ public function __construct( - OptionVariableRepositoryInterface $optionVariableRepository, + EggVariableRepositoryInterface $optionVariableRepository, ServerRepositoryInterface $serverRepository, ServerVariableRepositoryInterface $serverVariableRepository, ValidationFactory $validator @@ -106,7 +106,7 @@ class VariableValidatorService */ public function validate($option) { - $variables = $this->optionVariableRepository->findWhere([['option_id', '=', $option]]); + $variables = $this->optionVariableRepository->findWhere([['egg_id', '=', $option]]); if (count($variables) === 0) { $this->results = []; diff --git a/app/Services/Services/Options/InstallScriptUpdateService.php b/app/Services/Services/Options/InstallScriptUpdateService.php deleted file mode 100644 index 7b302190e..000000000 --- a/app/Services/Services/Options/InstallScriptUpdateService.php +++ /dev/null @@ -1,63 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Services\Services\Options; - -use Pterodactyl\Models\ServiceOption; -use Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface; -use Pterodactyl\Exceptions\Service\ServiceOption\InvalidCopyFromException; - -class InstallScriptUpdateService -{ - /** - * @var \Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface - */ - protected $repository; - - /** - * InstallScriptUpdateService constructor. - * - * @param \Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface $repository - */ - public function __construct(ServiceOptionRepositoryInterface $repository) - { - $this->repository = $repository; - } - - /** - * Modify the option install script for a given service option. - * - * @param int|\Pterodactyl\Models\ServiceOption $option - * @param array $data - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - * @throws \Pterodactyl\Exceptions\Service\ServiceOption\InvalidCopyFromException - */ - public function handle($option, array $data) - { - if (! $option instanceof ServiceOption) { - $option = $this->repository->find($option); - } - - if (! is_null(array_get($data, 'copy_script_from'))) { - if (! $this->repository->isCopiableScript(array_get($data, 'copy_script_from'), $option->service_id)) { - throw new InvalidCopyFromException(trans('exceptions.service.options.invalid_copy_id')); - } - } - - $this->repository->withoutFresh()->update($option->id, [ - 'script_install' => array_get($data, 'script_install'), - 'script_is_privileged' => array_get($data, 'script_is_privileged'), - 'script_entry' => array_get($data, 'script_entry'), - 'script_container' => array_get($data, 'script_container'), - 'copy_script_from' => array_get($data, 'copy_script_from'), - ]); - } -} diff --git a/app/Services/Services/Options/OptionCreationService.php b/app/Services/Services/Options/OptionCreationService.php deleted file mode 100644 index d15a813e4..000000000 --- a/app/Services/Services/Options/OptionCreationService.php +++ /dev/null @@ -1,58 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Services\Services\Options; - -use Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface; -use Pterodactyl\Exceptions\Service\ServiceOption\NoParentConfigurationFoundException; - -class OptionCreationService -{ - /** - * @var \Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface - */ - protected $repository; - - /** - * CreationService constructor. - * - * @param \Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface $repository - */ - public function __construct(ServiceOptionRepositoryInterface $repository) - { - $this->repository = $repository; - } - - /** - * Create a new service option and assign it to the given service. - * - * @param array $data - * @return \Pterodactyl\Models\ServiceOption - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Service\ServiceOption\NoParentConfigurationFoundException - */ - public function handle(array $data) - { - if (! is_null(array_get($data, 'config_from'))) { - $results = $this->repository->findCountWhere([ - ['service_id', '=', array_get($data, 'service_id')], - ['id', '=', array_get($data, 'config_from')], - ]); - - if ($results !== 1) { - throw new NoParentConfigurationFoundException(trans('exceptions.service.options.must_be_child')); - } - } else { - $data['config_from'] = null; - } - - return $this->repository->create($data); - } -} diff --git a/app/Services/Services/Options/OptionDeletionService.php b/app/Services/Services/Options/OptionDeletionService.php deleted file mode 100644 index 27788ca5c..000000000 --- a/app/Services/Services/Options/OptionDeletionService.php +++ /dev/null @@ -1,69 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Services\Services\Options; - -use Webmozart\Assert\Assert; -use Pterodactyl\Exceptions\Service\HasActiveServersException; -use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; -use Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface; -use Pterodactyl\Exceptions\Service\ServiceOption\HasChildrenException; - -class OptionDeletionService -{ - /** - * @var \Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface - */ - protected $repository; - - /** - * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface - */ - protected $serverRepository; - - /** - * OptionDeletionService constructor. - * - * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $serverRepository - * @param \Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface $repository - */ - public function __construct( - ServerRepositoryInterface $serverRepository, - ServiceOptionRepositoryInterface $repository - ) { - $this->repository = $repository; - $this->serverRepository = $serverRepository; - } - - /** - * Delete an option from the database if it has no active servers attached to it. - * - * @param int $option - * @return int - * - * @throws \Pterodactyl\Exceptions\Service\HasActiveServersException - * @throws \Pterodactyl\Exceptions\Service\ServiceOption\HasChildrenException - */ - public function handle($option) - { - Assert::integerish($option, 'First argument passed to handle must be integer, received %s.'); - - $servers = $this->serverRepository->findCountWhere([['option_id', '=', $option]]); - if ($servers > 0) { - throw new HasActiveServersException(trans('exceptions.service.options.delete_has_servers')); - } - - $children = $this->repository->findCountWhere([['config_from', '=', $option]]); - if ($children > 0) { - throw new HasChildrenException(trans('exceptions.service.options.has_children')); - } - - return $this->repository->delete($option); - } -} diff --git a/app/Services/Services/Options/OptionUpdateService.php b/app/Services/Services/Options/OptionUpdateService.php deleted file mode 100644 index 1d2109de5..000000000 --- a/app/Services/Services/Options/OptionUpdateService.php +++ /dev/null @@ -1,62 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Services\Services\Options; - -use Pterodactyl\Models\ServiceOption; -use Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface; -use Pterodactyl\Exceptions\Service\ServiceOption\NoParentConfigurationFoundException; - -class OptionUpdateService -{ - /** - * @var \Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface - */ - protected $repository; - - /** - * OptionUpdateService constructor. - * - * @param \Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface $repository - */ - public function __construct(ServiceOptionRepositoryInterface $repository) - { - $this->repository = $repository; - } - - /** - * Update a service option. - * - * @param int|\Pterodactyl\Models\ServiceOption $option - * @param array $data - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - * @throws \Pterodactyl\Exceptions\Service\ServiceOption\NoParentConfigurationFoundException - */ - public function handle($option, array $data) - { - if (! $option instanceof ServiceOption) { - $option = $this->repository->find($option); - } - - if (! is_null(array_get($data, 'config_from'))) { - $results = $this->repository->findCountWhere([ - ['service_id', '=', $option->service_id], - ['id', '=', array_get($data, 'config_from')], - ]); - - if ($results !== 1) { - throw new NoParentConfigurationFoundException(trans('exceptions.service.options.must_be_child')); - } - } - - $this->repository->withoutFresh()->update($option->id, $data); - } -} diff --git a/app/Services/Services/ServiceCreationService.php b/app/Services/Services/ServiceCreationService.php deleted file mode 100644 index 4d7e77f1a..000000000 --- a/app/Services/Services/ServiceCreationService.php +++ /dev/null @@ -1,64 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Services\Services; - -use Pterodactyl\Traits\Services\CreatesServiceIndex; -use Illuminate\Contracts\Config\Repository as ConfigRepository; -use Pterodactyl\Contracts\Repository\ServiceRepositoryInterface; - -class ServiceCreationService -{ - use CreatesServiceIndex; - - /** - * @var \Illuminate\Contracts\Config\Repository - */ - protected $config; - - /** - * @var \Pterodactyl\Contracts\Repository\ServiceRepositoryInterface - */ - protected $repository; - - /** - * ServiceCreationService constructor. - * - * @param \Illuminate\Contracts\Config\Repository $config - * @param \Pterodactyl\Contracts\Repository\ServiceRepositoryInterface $repository - */ - public function __construct( - ConfigRepository $config, - ServiceRepositoryInterface $repository - ) { - $this->config = $config; - $this->repository = $repository; - } - - /** - * Create a new service on the system. - * - * @param array $data - * @return \Pterodactyl\Models\Service - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - */ - public function handle(array $data) - { - return $this->repository->create(array_merge([ - 'author' => $this->config->get('pterodactyl.service.author'), - ], [ - 'name' => array_get($data, 'name'), - 'description' => array_get($data, 'description'), - 'folder' => array_get($data, 'folder'), - 'startup' => array_get($data, 'startup'), - 'index_file' => $this->getIndexScript(), - ])); - } -} diff --git a/app/Services/Services/Variables/VariableCreationService.php b/app/Services/Services/Variables/VariableCreationService.php deleted file mode 100644 index 110224888..000000000 --- a/app/Services/Services/Variables/VariableCreationService.php +++ /dev/null @@ -1,69 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Services\Services\Variables; - -use Pterodactyl\Models\ServiceOption; -use Pterodactyl\Models\ServiceVariable; -use Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface; -use Pterodactyl\Contracts\Repository\ServiceVariableRepositoryInterface; -use Pterodactyl\Exceptions\Service\ServiceVariable\ReservedVariableNameException; - -class VariableCreationService -{ - /** - * @var \Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface - */ - protected $serviceOptionRepository; - - /** - * @var \Pterodactyl\Contracts\Repository\ServiceVariableRepositoryInterface - */ - protected $serviceVariableRepository; - - public function __construct( - ServiceOptionRepositoryInterface $serviceOptionRepository, - ServiceVariableRepositoryInterface $serviceVariableRepository - ) { - $this->serviceOptionRepository = $serviceOptionRepository; - $this->serviceVariableRepository = $serviceVariableRepository; - } - - /** - * Create a new variable for a given service option. - * - * @param int|\Pterodactyl\Models\ServiceOption $option - * @param array $data - * @return \Pterodactyl\Models\ServiceVariable - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Service\ServiceVariable\ReservedVariableNameException - */ - public function handle($option, array $data) - { - if ($option instanceof ServiceOption) { - $option = $option->id; - } - - if (in_array(strtoupper(array_get($data, 'env_variable')), explode(',', ServiceVariable::RESERVED_ENV_NAMES))) { - throw new ReservedVariableNameException(sprintf( - 'Cannot use the protected name %s for this environment variable.', - array_get($data, 'env_variable') - )); - } - - $options = array_get($data, 'options', []); - - return $this->serviceVariableRepository->create(array_merge([ - 'option_id' => $option, - 'user_viewable' => in_array('user_viewable', $options), - 'user_editable' => in_array('user_editable', $options), - ], $data)); - } -} diff --git a/app/Traits/Services/CreatesServiceIndex.php b/app/Traits/Services/CreatesServiceIndex.php deleted file mode 100644 index 13c925a8b..000000000 --- a/app/Traits/Services/CreatesServiceIndex.php +++ /dev/null @@ -1,56 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Traits\Services; - -trait CreatesServiceIndex -{ - /** - * Returns the default index.js file that is used for services on the daemon. - * - * @return string - */ - public function getIndexScript() - { - return <<<'EOF' -'use strict'; - -/** - * Pterodactyl - Daemon - * Copyright (c) 2015 - 2017 Dane Everitt - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -const rfr = require('rfr'); -const _ = require('lodash'); - -const Core = rfr('src/services/index.js'); - -class Service extends Core {} - -module.exports = Service; -EOF; - } -} diff --git a/app/Transformers/Admin/OptionTransformer.php b/app/Transformers/Admin/OptionTransformer.php index d9af33c09..089f880ec 100644 --- a/app/Transformers/Admin/OptionTransformer.php +++ b/app/Transformers/Admin/OptionTransformer.php @@ -9,8 +9,8 @@ namespace Pterodactyl\Transformers\Admin; +use Pterodactyl\Models\Egg; use Illuminate\Http\Request; -use Pterodactyl\Models\ServiceOption; use League\Fractal\TransformerAbstract; class OptionTransformer extends TransformerAbstract @@ -53,7 +53,7 @@ class OptionTransformer extends TransformerAbstract * * @return array */ - public function transform(ServiceOption $option) + public function transform(Egg $option) { return $option->toArray(); } @@ -63,7 +63,7 @@ class OptionTransformer extends TransformerAbstract * * @return \Leauge\Fractal\Resource\Collection */ - public function includeService(ServiceOption $option) + public function includeService(Egg $option) { if ($this->request && ! $this->request->apiKeyHasPermission('service-view')) { return; @@ -77,7 +77,7 @@ class OptionTransformer extends TransformerAbstract * * @return \Leauge\Fractal\Resource\Collection */ - public function includePacks(ServiceOption $option) + public function includePacks(Egg $option) { if ($this->request && ! $this->request->apiKeyHasPermission('pack-list')) { return; @@ -91,7 +91,7 @@ class OptionTransformer extends TransformerAbstract * * @return \Leauge\Fractal\Resource\Collection */ - public function includeServers(ServiceOption $option) + public function includeServers(Egg $option) { if ($this->request && ! $this->request->apiKeyHasPermission('server-list')) { return; @@ -105,7 +105,7 @@ class OptionTransformer extends TransformerAbstract * * @return \Leauge\Fractal\Resource\Collection */ - public function includeVariables(ServiceOption $option) + public function includeVariables(Egg $option) { if ($this->request && ! $this->request->apiKeyHasPermission('option-view')) { return; diff --git a/app/Transformers/Admin/ServiceTransformer.php b/app/Transformers/Admin/ServiceTransformer.php index 2fd4b79c4..5f8497cf7 100644 --- a/app/Transformers/Admin/ServiceTransformer.php +++ b/app/Transformers/Admin/ServiceTransformer.php @@ -10,7 +10,7 @@ namespace Pterodactyl\Transformers\Admin; use Illuminate\Http\Request; -use Pterodactyl\Models\Service; +use Pterodactyl\Models\Nest; use League\Fractal\TransformerAbstract; class ServiceTransformer extends TransformerAbstract @@ -52,7 +52,7 @@ class ServiceTransformer extends TransformerAbstract * * @return array */ - public function transform(Service $service) + public function transform(Nest $service) { return $service->toArray(); } @@ -62,7 +62,7 @@ class ServiceTransformer extends TransformerAbstract * * @return \Leauge\Fractal\Resource\Collection */ - public function includeOptions(Service $service) + public function includeOptions(Nest $service) { if ($this->request && ! $this->request->apiKeyHasPermission('option-list')) { return; @@ -76,7 +76,7 @@ class ServiceTransformer extends TransformerAbstract * * @return \Leauge\Fractal\Resource\Collection */ - public function includeServers(Service $service) + public function includeServers(Nest $service) { if ($this->request && ! $this->request->apiKeyHasPermission('server-list')) { return; @@ -90,7 +90,7 @@ class ServiceTransformer extends TransformerAbstract * * @return \Leauge\Fractal\Resource\Collection */ - public function includePacks(Service $service) + public function includePacks(Nest $service) { if ($this->request && ! $this->request->apiKeyHasPermission('pack-list')) { return; diff --git a/app/Transformers/Admin/ServiceVariableTransformer.php b/app/Transformers/Admin/ServiceVariableTransformer.php index 507cf1bc8..49f5e449a 100644 --- a/app/Transformers/Admin/ServiceVariableTransformer.php +++ b/app/Transformers/Admin/ServiceVariableTransformer.php @@ -10,8 +10,8 @@ namespace Pterodactyl\Transformers\Admin; use Illuminate\Http\Request; +use Pterodactyl\Models\EggVariable; use League\Fractal\TransformerAbstract; -use Pterodactyl\Models\ServiceVariable; class ServiceVariableTransformer extends TransformerAbstract { @@ -48,7 +48,7 @@ class ServiceVariableTransformer extends TransformerAbstract * * @return array */ - public function transform(ServiceVariable $variable) + public function transform(EggVariable $variable) { return $variable->toArray(); } @@ -58,7 +58,7 @@ class ServiceVariableTransformer extends TransformerAbstract * * @return \Leauge\Fractal\Resource\Collection */ - public function includeVariables(ServiceVariable $variable) + public function includeVariables(EggVariable $variable) { if ($this->request && ! $this->request->apiKeyHasPermission('server-view')) { return; diff --git a/app/helpers.php b/app/helpers.php index 26dd1b8e0..0c9004695 100644 --- a/app/helpers.php +++ b/app/helpers.php @@ -43,3 +43,31 @@ if (! function_exists('is_digit')) { return is_bool($value) ? false : ctype_digit(strval($value)); } } + +if (! function_exists('object_get_strict')) { + /** + * Get an object using dot notation. An object key with a value of null is still considered valid + * and will not trigger the response of a default value (unlike object_get). + * + * @param object $object + * @param string $key + * @param null $default + * @return mixed + */ + function object_get_strict($object, $key, $default = null) + { + if (is_null($key) || trim($key) == '') { + return $object; + } + + foreach (explode('.', $key) as $segment) { + if (! is_object($object) || ! property_exists($object, $segment)) { + return value($default); + } + + $object = $object->{$segment}; + } + + return $object; + } +} diff --git a/database/factories/ModelFactory.php b/database/factories/ModelFactory.php index 1d069a26d..f3e4f4093 100644 --- a/database/factories/ModelFactory.php +++ b/database/factories/ModelFactory.php @@ -88,29 +88,28 @@ $factory->define(Pterodactyl\Models\Node::class, function (Faker\Generator $fake ]; }); -$factory->define(Pterodactyl\Models\Service::class, function (Faker\Generator $faker) { +$factory->define(Pterodactyl\Models\Nest::class, function (Faker\Generator $faker) { return [ 'id' => $faker->unique()->randomNumber(), - 'author' => $faker->unique()->uuid, + 'uuid' => $faker->unique()->uuid, + 'author' => 'testauthor@example.com', 'name' => $faker->word, 'description' => null, - 'folder' => strtolower($faker->unique()->word), - 'startup' => 'java -jar test.jar', - 'index_file' => 'indexjs', ]; }); -$factory->define(Pterodactyl\Models\ServiceOption::class, function (Faker\Generator $faker) { +$factory->define(Pterodactyl\Models\Egg::class, function (Faker\Generator $faker) { return [ 'id' => $faker->unique()->randomNumber(), - 'service_id' => $faker->unique()->randomNumber(), + 'uuid' => $faker->unique()->uuid, + 'nest_id' => $faker->unique()->randomNumber(), 'name' => $faker->name, 'description' => implode(' ', $faker->sentences(3)), - 'tag' => $faker->unique()->randomNumber(5), + 'startup' => 'java -jar test.jar', ]; }); -$factory->define(Pterodactyl\Models\ServiceVariable::class, function (Faker\Generator $faker) { +$factory->define(Pterodactyl\Models\EggVariable::class, function (Faker\Generator $faker) { return [ 'id' => $faker->unique()->randomNumber(), 'name' => $faker->firstName, @@ -120,23 +119,21 @@ $factory->define(Pterodactyl\Models\ServiceVariable::class, function (Faker\Gene 'user_viewable' => 0, 'user_editable' => 0, 'rules' => 'required|string', - 'created_at' => \Carbon\Carbon::now(), - 'updated_at' => \Carbon\Carbon::now(), ]; }); -$factory->state(Pterodactyl\Models\ServiceVariable::class, 'viewable', function () { +$factory->state(Pterodactyl\Models\EggVariable::class, 'viewable', function () { return ['user_viewable' => 1]; }); -$factory->state(Pterodactyl\Models\ServiceVariable::class, 'editable', function () { +$factory->state(Pterodactyl\Models\EggVariable::class, 'editable', function () { return ['user_editable' => 1]; }); $factory->define(Pterodactyl\Models\Pack::class, function (Faker\Generator $faker) { return [ 'id' => $faker->unique()->randomNumber(), - 'option_id' => $faker->randomNumber(), + 'egg_id' => $faker->randomNumber(), 'uuid' => $faker->uuid, 'name' => $faker->word, 'description' => null, diff --git a/database/migrations/2017_03_05_212803_DeleteServiceExecutableOption.php b/database/migrations/2017_03_05_212803_DeleteServiceExecutableOption.php index 2d6413ede..2918e1afd 100644 --- a/database/migrations/2017_03_05_212803_DeleteServiceExecutableOption.php +++ b/database/migrations/2017_03_05_212803_DeleteServiceExecutableOption.php @@ -1,6 +1,5 @@ get() as $option) { - $option->servers->each(function ($s) use ($option) { - $prepend = $option->display_executable; - $prepend = ($prepend === './ShooterGameServer') ? './ShooterGame/Binaries/Linux/ShooterGameServer' : $prepend; - $prepend = ($prepend === 'TerrariaServer.exe') ? 'mono TerrariaServer.exe' : $prepend; - - $s->startup = $prepend . ' ' . $s->startup; - - $container = $s->container; - if (starts_with($container, 'quay.io/pterodactyl/minecraft')) { - $s->container = 'quay.io/pterodactyl/core:java'; - } elseif (starts_with($container, 'quay.io/pterodactyl/srcds')) { - $s->container = 'quay.io/pterodactyl/core:source'; - } elseif (starts_with($container, 'quay.io/pterodactyl/voice')) { - $s->container = 'quay.io/pterodactyl/core:glibc'; - } elseif (starts_with($container, 'quay.io/pterodactyl/terraria')) { - $s->container = 'quay.io/pterodactyl/core:mono'; - } - - $s->save(); - }); - } - Schema::table('services', function (Blueprint $table) { $table->renameColumn('file', 'folder'); $table->dropColumn('executable'); diff --git a/database/migrations/2017_03_10_173607_MigrateToNewServiceSystem.php b/database/migrations/2017_03_10_173607_MigrateToNewServiceSystem.php index ca92c2f69..039976352 100644 --- a/database/migrations/2017_03_10_173607_MigrateToNewServiceSystem.php +++ b/database/migrations/2017_03_10_173607_MigrateToNewServiceSystem.php @@ -6,8 +6,6 @@ * This software is licensed under the terms of the MIT license. * https://opensource.org/licenses/MIT */ -use Pterodactyl\Models\Service; -use Pterodactyl\Models\ServiceOption; use Illuminate\Database\Migrations\Migration; class MigrateToNewServiceSystem extends Migration @@ -18,12 +16,12 @@ class MigrateToNewServiceSystem extends Migration public function up() { DB::transaction(function () { - $service = Service::where('author', config('pterodactyl.service.core'))->where('folder', 'srcds')->first(); + $service = DB::table('services')->where('author', config('pterodactyl.service.core'))->where('folder', 'srcds')->first(); if (! $service) { return; } - $options = ServiceOption::where('service_id', $service->id)->get(); + $options = DB::table('service_options')->where('service_id', $service->id)->get(); $options->each(function ($item) use ($options) { if ($item->tag === 'srcds' && $item->name === 'Insurgency') { $item->tag = 'insurgency'; diff --git a/database/migrations/2017_03_11_215455_ChangeServiceVariablesValidationRules.php b/database/migrations/2017_03_11_215455_ChangeServiceVariablesValidationRules.php index 7784083a1..21fa51465 100644 --- a/database/migrations/2017_03_11_215455_ChangeServiceVariablesValidationRules.php +++ b/database/migrations/2017_03_11_215455_ChangeServiceVariablesValidationRules.php @@ -1,7 +1,6 @@ get() as $variable) { $variable->rules = ($variable->required) ? 'required|regex:' . $variable->rules : 'regex:' . $variable->rules; $variable->save(); } @@ -39,7 +38,7 @@ class ChangeServiceVariablesValidationRules extends Migration }); DB::transaction(function () { - foreach (ServiceVariable::all() as $variable) { + foreach (DB::table('service_variables')->get() as $variable) { $variable->regex = str_replace(['required|regex:', 'regex:'], '', $variable->regex); $variable->save(); } diff --git a/database/migrations/2017_03_12_150648_MoveFunctionsFromFileToDatabase.php b/database/migrations/2017_03_12_150648_MoveFunctionsFromFileToDatabase.php index 5d5a3d164..3628ba7a4 100644 --- a/database/migrations/2017_03_12_150648_MoveFunctionsFromFileToDatabase.php +++ b/database/migrations/2017_03_12_150648_MoveFunctionsFromFileToDatabase.php @@ -1,6 +1,5 @@ where('folder', '!=', 'minecraft')->update([ + DB::table('services')->where('author', 'ptrdctyl-v040-11e6-8b77-86f30ca893d3')->where('folder', '!=', 'minecraft')->update([ 'index_file' => $this->default, ]); - Service::where('author', 'ptrdctyl-v040-11e6-8b77-86f30ca893d3')->where('folder', 'minecraft')->update([ + DB::table('services')->where('author', 'ptrdctyl-v040-11e6-8b77-86f30ca893d3')->where('folder', 'minecraft')->update([ 'index_file' => $this->default_mc, ]); }); diff --git a/database/migrations/2017_09_23_185022_RemoveDaemonSecretFromSubusersTable.php b/database/migrations/2017_09_23_185022_RemoveDaemonSecretFromSubusersTable.php index a0c5e6d10..d4d2dd695 100644 --- a/database/migrations/2017_09_23_185022_RemoveDaemonSecretFromSubusersTable.php +++ b/database/migrations/2017_09_23_185022_RemoveDaemonSecretFromSubusersTable.php @@ -42,12 +42,16 @@ class RemoveDaemonSecretFromSubusersTable extends Migration public function down() { Schema::table('subusers', function (Blueprint $table) { - $table->char('daemonSecret', 36)->after('server_id')->unique(); + $table->char('daemonSecret', 36)->after('server_id'); }); $subusers = DB::table('subusers')->get(); $subusers->each(function ($subuser) { DB::table('daemon_keys')->where('user_id', $subuser->user_id)->where('server_id', $subuser->server_id)->delete(); }); + + Schema::table('subusers', function (Blueprint $table) { + $table->unique('daemonSecret'); + }); } } diff --git a/database/migrations/2017_10_02_202000_ChangeServicesToUseAMoreUniqueIdentifier.php b/database/migrations/2017_10_02_202000_ChangeServicesToUseAMoreUniqueIdentifier.php new file mode 100644 index 000000000..6bb36813d --- /dev/null +++ b/database/migrations/2017_10_02_202000_ChangeServicesToUseAMoreUniqueIdentifier.php @@ -0,0 +1,55 @@ +dropUnique(['name']); + $table->dropUnique(['file']); + + $table->string('author')->change(); + $table->char('uuid', 36)->after('id'); + $table->dropColumn('folder'); + $table->dropColumn('startup'); + $table->dropColumn('index_file'); + }); + + DB::table('services')->get(['id', 'author', 'uuid'])->each(function ($service) { + DB::table('services')->where('id', $service->id)->update([ + 'author' => ($service->author === 'ptrdctyl-v040-11e6-8b77-86f30ca893d3') ? 'support@pterodactyl.io' : 'unknown@unknown-author.com', + 'uuid' => Uuid::uuid4()->toString(), + ]); + }); + + Schema::table('services', function (Blueprint $table) { + $table->unique('uuid'); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::table('services', function (Blueprint $table) { + $table->dropColumn('uuid'); + $table->string('folder')->nullable(); + $table->text('startup')->nullable(); + $table->text('index_file'); + $table->string('author', 36)->change(); + + $table->unique('name'); + $table->unique('folder', 'services_file_unique'); + }); + } +} diff --git a/database/migrations/2017_10_02_202007_ChangeToABetterUniqueServiceConfiguration.php b/database/migrations/2017_10_02_202007_ChangeToABetterUniqueServiceConfiguration.php new file mode 100644 index 000000000..5c9df79a5 --- /dev/null +++ b/database/migrations/2017_10_02_202007_ChangeToABetterUniqueServiceConfiguration.php @@ -0,0 +1,59 @@ +char('uuid', 36)->after('id'); + $table->string('author')->after('service_id'); + $table->dropColumn('tag'); + }); + + DB::transaction(function () { + DB::table('service_options')->select([ + 'service_options.id', + 'service_options.uuid', + 'services.author AS service_author', + ])->join('services', 'services.id', '=', 'service_options.service_id')->get()->each(function ($option) { + DB::table('service_options')->where('id', $option->id)->update([ + 'author' => $option->service_author, + 'uuid' => Uuid::uuid4()->toString(), + ]); + }); + }); + + Schema::table('service_options', function (Blueprint $table) { + $table->unique('uuid'); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::table('service_options', function (Blueprint $table) { + $table->dropColumn('uuid'); + $table->dropColumn('author'); + $table->string('tag'); + }); + + DB::transaction(function () { + DB::table('service_options')->select(['id', 'tag'])->get()->each(function ($option) { + DB::table('service_options')->where('id', $option->id)->update([ + 'tag' => str_random(10), + ]); + }); + }); + } +} diff --git a/database/migrations/2017_10_03_233202_CascadeDeletionWhenServiceOptionIsDeleted.php b/database/migrations/2017_10_03_233202_CascadeDeletionWhenServiceOptionIsDeleted.php new file mode 100644 index 000000000..3b19e3d99 --- /dev/null +++ b/database/migrations/2017_10_03_233202_CascadeDeletionWhenServiceOptionIsDeleted.php @@ -0,0 +1,32 @@ +dropForeign(['option_id']); + + $table->foreign('option_id')->references('id')->on('service_options')->onDelete('CASCADE'); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::table('service_variables', function (Blueprint $table) { + $table->dropForeign(['option_id']); + + $table->foreign('option_id')->references('id')->on('service_options'); + }); + } +} diff --git a/database/migrations/2017_10_06_214026_ServicesToNestsConversion.php b/database/migrations/2017_10_06_214026_ServicesToNestsConversion.php new file mode 100644 index 000000000..e7b70136c --- /dev/null +++ b/database/migrations/2017_10_06_214026_ServicesToNestsConversion.php @@ -0,0 +1,59 @@ +dropForeign(['service_id']); + $table->renameColumn('service_id', 'nest_id'); + + $table->foreign('nest_id')->references('id')->on('nests'); + }); + + Schema::table('service_options', function (Blueprint $table) { + $table->dropForeign(['service_id']); + $table->renameColumn('service_id', 'nest_id'); + + $table->foreign('nest_id')->references('id')->on('nests')->onDelete('CASCADE'); + }); + + Schema::enableForeignKeyConstraints(); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::disableForeignKeyConstraints(); + + Schema::rename('nests', 'services'); + + Schema::table('servers', function (Blueprint $table) { + $table->dropForeign(['nest_id']); + $table->renameColumn('nest_id', 'service_id'); + + $table->foreign('service_id')->references('id')->on('services'); + }); + + Schema::table('service_options', function (Blueprint $table) { + $table->dropForeign(['nest_id']); + $table->renameColumn('nest_id', 'service_id'); + + $table->foreign('service_id')->references('id')->on('services')->onDelete('CASCADE'); + }); + + Schema::enableForeignKeyConstraints(); + } +} diff --git a/database/migrations/2017_10_06_214053_ServiceOptionsToEggsConversion.php b/database/migrations/2017_10_06_214053_ServiceOptionsToEggsConversion.php new file mode 100644 index 000000000..d2c55c057 --- /dev/null +++ b/database/migrations/2017_10_06_214053_ServiceOptionsToEggsConversion.php @@ -0,0 +1,93 @@ +dropForeign(['config_from']); + $table->dropForeign(['copy_script_from']); + }); + + Schema::rename('service_options', 'eggs'); + + Schema::table('packs', function (Blueprint $table) { + $table->dropForeign(['option_id']); + $table->renameColumn('option_id', 'egg_id'); + + $table->foreign('egg_id')->references('id')->on('eggs')->onDelete('CASCADE'); + }); + + Schema::table('servers', function (Blueprint $table) { + $table->dropForeign(['option_id']); + $table->renameColumn('option_id', 'egg_id'); + + $table->foreign('egg_id')->references('id')->on('eggs'); + }); + + Schema::table('eggs', function (Blueprint $table) { + $table->foreign('config_from')->references('id')->on('eggs')->onDelete('SET NULL'); + $table->foreign('copy_script_from')->references('id')->on('eggs')->onDelete('SET NULL'); + }); + + Schema::table('service_variables', function (Blueprint $table) { + $table->dropForeign(['option_id']); + $table->renameColumn('option_id', 'egg_id'); + + $table->foreign('egg_id')->references('id')->on('eggs')->onDelete('CASCADE'); + }); + + Schema::enableForeignKeyConstraints(); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::disableForeignKeyConstraints(); + + Schema::table('eggs', function (Blueprint $table) { + $table->dropForeign(['config_from']); + $table->dropForeign(['copy_script_from']); + }); + + Schema::rename('eggs', 'service_options'); + + Schema::table('packs', function (Blueprint $table) { + $table->dropForeign(['egg_id']); + $table->renameColumn('egg_id', 'option_id'); + + $table->foreign('option_id')->references('id')->on('service_options')->onDelete('CASCADE'); + }); + + Schema::table('servers', function (Blueprint $table) { + $table->dropForeign(['egg_id']); + $table->renameColumn('egg_id', 'option_id'); + + $table->foreign('option_id')->references('id')->on('service_options'); + }); + + Schema::table('service_options', function (Blueprint $table) { + $table->foreign('config_from')->references('id')->on('service_options')->onDelete('SET NULL'); + $table->foreign('copy_script_from')->references('id')->on('service_options')->onDelete('SET NULL'); + }); + + Schema::table('service_variables', function (Blueprint $table) { + $table->dropForeign(['egg_id']); + $table->renameColumn('egg_id', 'option_id'); + + $table->foreign('option_id')->references('id')->on('options')->onDelete('CASCADE'); + }); + + Schema::enableForeignKeyConstraints(); + } +} diff --git a/database/migrations/2017_10_06_215741_ServiceVariablesToEggVariablesConversion.php b/database/migrations/2017_10_06_215741_ServiceVariablesToEggVariablesConversion.php new file mode 100644 index 000000000..ef7d3811d --- /dev/null +++ b/database/migrations/2017_10_06_215741_ServiceVariablesToEggVariablesConversion.php @@ -0,0 +1,43 @@ +dropForeign(['variable_id']); + + $table->foreign('variable_id')->references('id')->on('egg_variables')->onDelete('CASCADE'); + }); + + Schema::enableForeignKeyConstraints(); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::disableForeignKeyConstraints(); + + Schema::rename('egg_variables', 'service_variables'); + + Schema::table('server_variables', function (Blueprint $table) { + $table->dropForeign(['variable_id']); + + $table->foreign('variable_id')->references('id')->on('service_variables')->onDelete('CASCADE'); + }); + + Schema::enableForeignKeyConstraints(); + } +} diff --git a/database/seeds/DatabaseSeeder.php b/database/seeds/DatabaseSeeder.php index 6afdea04d..3f894f471 100644 --- a/database/seeds/DatabaseSeeder.php +++ b/database/seeds/DatabaseSeeder.php @@ -12,12 +12,6 @@ class DatabaseSeeder extends Seeder { Model::unguard(); - $this->call(MinecraftServiceTableSeeder::class); - $this->call(SourceServiceTableSeeder::class); - $this->call(RustServiceTableSeeder::class); - $this->call(TerrariaServiceTableSeeder::class); - $this->call(VoiceServiceTableSeeder::class); - Model::reguard(); } } diff --git a/database/seeds/MinecraftServiceTableSeeder.php b/database/seeds/MinecraftServiceTableSeeder.php deleted file mode 100644 index 114fc8457..000000000 --- a/database/seeds/MinecraftServiceTableSeeder.php +++ /dev/null @@ -1,411 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ -use Illuminate\Database\Seeder; -use Pterodactyl\Models\Service; -use Pterodactyl\Models\ServiceOption; -use Pterodactyl\Models\ServiceVariable; - -class MinecraftServiceTableSeeder extends Seeder -{ - /** - * The core service ID. - * - * @var \Pterodactyl\Models\Service - */ - protected $service; - - /** - * Stores all of the option objects. - * - * @var array - */ - protected $option = []; - - private $default_mc = <<<'EOF' -'use strict'; - -/** - * Pterodactyl - Daemon - * Copyright (c) 2015 - 2017 Dane Everitt - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -const rfr = require('rfr'); -const _ = require('lodash'); - -const Core = rfr('src/services/index.js'); - -class Service extends Core { - onConsole(data) { - // Hide the output spam from Bungeecord getting pinged. - if (_.endsWith(data, '<-> InitialHandler has connected')) return; - return super.onConsole(data); - } -} - -module.exports = Service; -EOF; - - /** - * Run the database seeds. - */ - public function run() - { - $this->addCoreService(); - $this->addCoreOptions(); - $this->addVariables(); - } - - private function addCoreService() - { - $this->service = Service::updateOrCreate([ - 'author' => config('pterodactyl.service.core'), - 'folder' => 'minecraft', - ], [ - 'name' => 'Minecraft', - 'description' => 'Minecraft - the classic game from Mojang. With support for Vanilla MC, Spigot, and many others!', - 'startup' => 'java -Xms128M -Xmx{{SERVER_MEMORY}}M -jar {{SERVER_JARFILE}}', - 'index_file' => $this->default_mc, - ]); - } - - private function addCoreOptions() - { - $script = <<<'EOF' -#!/bin/ash -# Vanilla MC Installation Script -# -# Server Files: /mnt/server -apk update -apk add curl - -cd /mnt/server - -LATEST_VERSION=`curl -s https://s3.amazonaws.com/Minecraft.Download/versions/versions.json | grep -o "[[:digit:]]\.[0-9]*\.[0-9]" | head -n 1` - -if [ -z "$VANILLA_VERSION" ] || [ "$VANILLA_VERSION" == "latest" ]; then - DL_VERSION=$LATEST_VERSION -else - DL_VERSION=$VANILLA_VERSION -fi - -curl -o ${SERVER_JARFILE} https://s3.amazonaws.com/Minecraft.Download/versions/${DL_VERSION}/minecraft_server.${DL_VERSION}.jar -EOF; - - $this->option['vanilla'] = ServiceOption::updateOrCreate([ - 'service_id' => $this->service->id, - 'tag' => 'vanilla', - ], [ - 'name' => 'Vanilla Minecraft', - 'description' => 'Minecraft is a game about placing blocks and going on adventures. Explore randomly generated worlds and build amazing things from the simplest of homes to the grandest of castles. Play in Creative Mode with unlimited resources or mine deep in Survival Mode, crafting weapons and armor to fend off dangerous mobs. Do all this alone or with friends.', - 'docker_image' => 'quay.io/pterodactyl/core:java', - 'config_startup' => '{"done": ")! For help, type ", "userInteraction": [ "Go to eula.txt for more info."]}', - 'config_logs' => '{"custom": false, "location": "logs/latest.log"}', - 'config_files' => '{"server.properties":{"parser": "properties", "find":{"server-ip": "0.0.0.0", "server-port": "{{server.build.default.port}}"}}}', - 'config_stop' => 'stop', - 'config_from' => null, - 'startup' => null, - 'script_install' => $script, - ]); - - $script = <<<'EOF' -#!/bin/ash -# Spigot Installation Script -# -# Server Files: /mnt/server - -## Only download if a path is provided, otherwise continue. -if [ ! -z "${DL_PATH}" ]; then - apk update - apk add curl - - cd /mnt/server - - MODIFIED_DOWNLOAD=`eval echo $(echo ${DL_PATH} | sed -e 's/{{/${/g' -e 's/}}/}/g')` - curl -sSL -o ${SERVER_JARFILE} ${MODIFIED_DOWNLOAD} -fi -EOF; - - $this->option['spigot'] = ServiceOption::updateOrCreate([ - 'service_id' => $this->service->id, - 'tag' => 'spigot', - ], [ - 'name' => 'Spigot', - 'description' => 'Spigot is the most widely-used modded Minecraft server software in the world. It powers many of the top Minecraft server networks around to ensure they can cope with their huge player base and ensure the satisfaction of their players. Spigot works by reducing and eliminating many causes of lag, as well as adding in handy features and settings that help make your job of server administration easier.', - 'docker_image' => 'quay.io/pterodactyl/core:java-glibc', - 'config_startup' => null, - 'config_files' => '{"spigot.yml":{"parser": "yaml", "find":{"settings.restart-on-crash": false}}}', - 'config_logs' => null, - 'config_stop' => null, - 'config_from' => $this->option['vanilla']->id, - 'startup' => null, - 'script_install' => $script, - ]); - - $script = <<<'EOF' -#!/bin/ash -# Sponge Installation Script -# -# Server Files: /mnt/server - -apk update -apk add curl - -cd /mnt/server - -curl -sSL "https://repo.spongepowered.org/maven/org/spongepowered/spongevanilla/${SPONGE_VERSION}/spongevanilla-${SPONGE_VERSION}.jar" -o ${SERVER_JARFILE} -EOF; - - $this->option['sponge'] = ServiceOption::updateOrCreate([ - 'service_id' => $this->service->id, - 'tag' => 'sponge', - ], [ - 'name' => 'Sponge (SpongeVanilla)', - 'description' => 'SpongeVanilla is the SpongeAPI implementation for Vanilla Minecraft.', - 'docker_image' => 'quay.io/pterodactyl/core:java-glibc', - 'config_startup' => '{"userInteraction": [ "You need to agree to the EULA"]}', - 'config_files' => null, - 'config_logs' => null, - 'config_stop' => null, - 'config_from' => $this->option['vanilla']->id, - 'startup' => null, - 'script_install' => $script, - ]); - - $script = <<<'EOF' -#!/bin/ash -# Bungeecord Installation Script -# -# Server Files: /mnt/server -apk update -apk add curl - -cd /mnt/server - -if [ -z "${BUNGEE_VERSION}" ] || [ "${BUNGEE_VERSION}" == "latest" ]; then - BUNGEE_VERSION="lastStableBuild" -fi - -curl -o ${SERVER_JARFILE} https://ci.md-5.net/job/BungeeCord/${BUNGEE_VERSION}/artifact/bootstrap/target/BungeeCord.jar -EOF; - - $this->option['bungeecord'] = ServiceOption::updateOrCreate([ - 'service_id' => $this->service->id, - 'tag' => 'bungeecord', - ], [ - 'name' => 'Bungeecord', - 'description' => 'For a long time, Minecraft server owners have had a dream that encompasses a free, easy, and reliable way to connect multiple Minecraft servers together. BungeeCord is the answer to said dream. Whether you are a small server wishing to string multiple game-modes together, or the owner of the ShotBow Network, BungeeCord is the ideal solution for you. With the help of BungeeCord, you will be able to unlock your community\'s full potential.', - 'docker_image' => 'quay.io/pterodactyl/core:java', - 'config_startup' => '{"done": "Listening on ", "userInteraction": [ "Listening on /0.0.0.0:25577"]}', - 'config_files' => '{"config.yml":{"parser": "yaml", "find":{"listeners[0].host": "0.0.0.0:{{server.build.default.port}}", "servers.*.address":{"127.0.0.1": "{{config.docker.interface}}", "localhost": "{{config.docker.interface}}"}}}}', - 'config_logs' => '{"custom": false, "location": "proxy.log.0"}', - 'config_stop' => 'end', - 'config_from' => null, - 'startup' => null, - 'script_install' => $script, - ]); - - $script = <<<'EOF' -#!/bin/ash -# Forge Installation Script -# -# Server Files: /mnt/server -apk update -apk add curl - -GET_VERSIONS=$(curl -sl http://files.minecraftforge.net/maven/net/minecraftforge/forge/ | grep -A1 Latest | grep -o -e '[1]\.[0-9][0-9] - [0-9][0-9]\.[0-9][0-9]\.[0-9]\.[0-9][0-9][0-9][0-9]') -LATEST_VERSION=$(echo $GET_VERSIONS | sed 's/ //g') - -cd /mnt/server - -curl -sS http://files.minecraftforge.net/maven/net/minecraftforge/forge/$LATEST_VERSION/forge-$LATEST_VERSION-installer.jar -o installer.jar -curl -sS http://files.minecraftforge.net/maven/net/minecraftforge/forge/$LATEST_VERSION/forge-$LATEST_VERSION-universal.jar -o server.jar - -java -jar installer.jar --installServer -rm -rf installer.jar -EOF; - - $this->option['forge'] = ServiceOption::updateOrCreate([ - 'service_id' => $this->service->id, - 'tag' => 'forge', - ], [ - 'name' => 'Forge Minecraft', - 'description' => 'Minecraft Forge Server. Minecraft Forge is a modding API (Application Programming Interface), which makes it easier to create mods, and also make sure mods are compatible with each other.', - 'docker_image' => 'quay.io/pterodactyl/core:java', - 'config_startup' => '{"done": ")! For help, type ", "userInteraction": [ "Go to eula.txt for more info."]}', - 'config_logs' => '{"custom": false, "location": "logs/latest.log"}', - 'config_files' => '{"server.properties":{"parser": "properties", "find":{"server-ip": "0.0.0.0", "server-port": "{{server.build.default.port}}"}}}', - 'config_stop' => 'stop', - 'config_from' => null, - 'startup' => 'java -Xms128M -Xmx{{SERVER_MEMORY}}M -jar {{SERVER_JARFILE}}', - 'script_install' => $script, - 'script_container' => 'frolvlad/alpine-oraclejdk8:cleaned', - ]); - } - - private function addVariables() - { - $this->addVanillaVariables(); - $this->addSpigotVariables(); - $this->addSpongeVariables(); - $this->addBungeecordVariables(); - $this->addForgeVariables(); - } - - private function addVanillaVariables() - { - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['vanilla']->id, - 'env_variable' => 'SERVER_JARFILE', - ], [ - 'name' => 'Server Jar File', - 'description' => 'The name of the server jarfile to run the server with.', - 'default_value' => 'server.jar', - 'user_viewable' => 1, - 'user_editable' => 1, - 'rules' => 'required|regex:/^([\w\d._-]+)(\.jar)$/', - ]); - - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['vanilla']->id, - 'env_variable' => 'VANILLA_VERSION', - ], [ - 'name' => 'Server Version', - 'description' => 'The version of Minecraft Vanilla to install. Use "latest" to install the latest version.', - 'default_value' => 'latest', - 'user_viewable' => 1, - 'user_editable' => 1, - 'rules' => 'required|string|between:3,7', - ]); - } - - private function addSpigotVariables() - { - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['spigot']->id, - 'env_variable' => 'SERVER_JARFILE', - ], [ - 'name' => 'Server Jar File', - 'description' => 'The name of the server jarfile to run the server with.', - 'default_value' => 'server.jar', - 'user_viewable' => 1, - 'user_editable' => 1, - 'rules' => 'required|regex:/^([\w\d._-]+)(\.jar)$/', - ]); - - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['spigot']->id, - 'env_variable' => 'DL_VERSION', - ], [ - 'name' => 'Spigot Version', - 'description' => 'The version of Spigot to download (using the --rev tag). Use "latest" for latest.', - 'default_value' => 'latest', - 'user_viewable' => 1, - 'user_editable' => 1, - 'rules' => 'required|string|between:3,7', - ]); - - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['spigot']->id, - 'env_variable' => 'DL_PATH', - ], [ - 'name' => 'Download Path', - 'description' => 'A URL to use to download Spigot rather than building it on the server. This is not user viewable. Use {{DL_VERSION}} in the URL to automatically insert the assigned version into the URL. If you do not enter a URL Spigot will build directly in the container (this will fail on low memory containers).', - 'default_value' => '', - 'user_viewable' => 0, - 'user_editable' => 0, - 'rules' => 'string', - ]); - } - - private function addSpongeVariables() - { - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['sponge']->id, - 'env_variable' => 'SPONGE_VERSION', - ], [ - 'name' => 'Sponge Version', - 'description' => 'The version of SpongeVanilla to download and use.', - 'default_value' => '1.10.2-5.2.0-BETA-381', - 'user_viewable' => 1, - 'user_editable' => 0, - 'rules' => 'required|regex:/^([a-zA-Z0-9.\-_]+)$/', - ]); - - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['sponge']->id, - 'env_variable' => 'SERVER_JARFILE', - ], [ - 'name' => 'Server Jar File', - 'description' => 'The name of the Jarfile to use when running SpongeVanilla.', - 'default_value' => 'server.jar', - 'user_viewable' => 1, - 'user_editable' => 1, - 'rules' => 'required|regex:/^([\w\d._-]+)(\.jar)$/', - ]); - } - - private function addBungeecordVariables() - { - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['bungeecord']->id, - 'env_variable' => 'BUNGEE_VERSION', - ], [ - 'name' => 'Bungeecord Version', - 'description' => 'The version of Bungeecord to download and use.', - 'default_value' => 'latest', - 'user_viewable' => 1, - 'user_editable' => 1, - 'rules' => 'required|alpha_num|between:1,6', - ]); - - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['bungeecord']->id, - 'env_variable' => 'SERVER_JARFILE', - ], [ - 'name' => 'Bungeecord Jar File', - 'description' => 'The name of the Jarfile to use when running Bungeecord.', - 'default_value' => 'bungeecord.jar', - 'user_viewable' => 1, - 'user_editable' => 1, - 'rules' => 'required|regex:/^([\w\d._-]+)(\.jar)$/', - ]); - } - - private function addForgeVariables() - { - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['forge']->id, - 'env_variable' => 'SERVER_JARFILE', - ], [ - 'name' => 'Server Jar File', - 'description' => 'The name of the Jarfile to use when running Forge Mod.', - 'default_value' => 'server.jar', - 'user_viewable' => 1, - 'user_editable' => 1, - 'rules' => 'required|regex:/^([\w\d._-]+)(\.jar)$/', - ]); - } -} diff --git a/database/seeds/RustServiceTableSeeder.php b/database/seeds/RustServiceTableSeeder.php deleted file mode 100644 index 5b1b49f81..000000000 --- a/database/seeds/RustServiceTableSeeder.php +++ /dev/null @@ -1,416 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ -use Illuminate\Database\Seeder; -use Pterodactyl\Models\Service; -use Pterodactyl\Models\ServiceOption; -use Pterodactyl\Models\ServiceVariable; -use Pterodactyl\Traits\Services\CreatesServiceIndex; - -class RustServiceTableSeeder extends Seeder -{ - use CreatesServiceIndex; - - /** - * The core service ID. - * - * @var Models\Service - */ - protected $service; - - /** - * Stores all of the option objects. - * - * @var array - */ - protected $option = []; - - /** - * Run the database seeds. - */ - public function run() - { - $this->addCoreService(); - $this->addCoreOptions(); - $this->addVariables(); - } - - private function addCoreService() - { - $this->service = Service::updateOrCreate([ - 'author' => config('pterodactyl.service.core'), - 'folder' => 'rust', - ], [ - 'name' => 'Rust', - 'description' => 'The only aim in Rust is to survive. To do this you will need to overcome struggles such as hunger, thirst and cold. Build a fire. Build a shelter. Kill animals for meat. Protect yourself from other players, and kill them for meat. Create alliances with other players and form a town. Do whatever it takes to survive.', - 'startup' => './RustDedicated -batchmode +server.port {{SERVER_PORT}} +server.identity "rust" +rcon.port {{RCON_PORT}} +rcon.web true +server.hostname \"{{HOSTNAME}}\" +server.level \"{{LEVEL}}\" +server.description \"{{DESCRIPTION}}\" +server.url \"{{URL}}\" +server.headerimage \"{{SERVER_IMG}}\" +server.worldsize \"{{WORLD_SIZE}}\" +server.seed \"{{SEED}}\" +server.maxplayers {{MAX_PLAYERS}} +rcon.password \"{{RCON_PASS}}\" {{ADDITIONAL_ARGS}}', - 'index_file' => $this->getIndexScript(), - ]); - } - - private function addCoreOptions() - { - $script = <<<'EOF' -apt update -apt -y --no-install-recommends install curl lib32gcc1 ca-certificates - -cd /tmp -curl -sSL -o steamcmd.tar.gz http://media.steampowered.com/installer/steamcmd_linux.tar.gz - -mkdir -p /mnt/server/steam -tar -xzvf steamcmd.tar.gz -C /mnt/server/steam -cd /mnt/server/steam - -chown -R root:root /mnt - -export HOME=/mnt/server -./steamcmd.sh +login anonymous +force_install_dir /mnt/server +app_update 258550 +quit - -mkdir -p /mnt/server/.steam/sdk32 -cp -v linux32/steamclient.so ../.steam/sdk32/steamclient.so -EOF; - - $this->option['rustvanilla'] = ServiceOption::updateOrCreate([ - 'service_id' => $this->service->id, - 'tag' => 'rustvanilla', - ], [ - 'name' => 'Vanilla', - 'description' => 'Vanilla Rust server.', - 'docker_image' => 'quay.io/pterodactyl/core:rust', - 'config_startup' => '{"done": "Server startup complete", "userInteraction": []}', - 'config_files' => '{}', - 'config_logs' => '{"custom": false, "location": "latest.log"}', - 'config_stop' => 'quit', - 'config_from' => null, - 'startup' => null, - 'script_install' => $script, - 'script_entry' => 'bash', - 'script_container' => 'ubuntu:16.04', - ]); - - $script = <<<'EOF' -apt update -apt -y --no-install-recommends install curl unzip lib32gcc1 ca-certificates - -cd /tmp -curl -sSL -o steamcmd.tar.gz http://media.steampowered.com/installer/steamcmd_linux.tar.gz - -mkdir -p /mnt/server/steam -tar -xzvf steamcmd.tar.gz -C /mnt/server/steam -cd /mnt/server/steam - -chown -R root:root /mnt - -export HOME=/mnt/server -./steamcmd.sh +login anonymous +force_install_dir /mnt/server +app_update 258550 +quit - -curl "https://dl.bintray.com/oxidemod/builds/Oxide-Rust.zip" > /mnt/server/oxide.zip -unzip -o /mnt/server/oxide.zip -d /mnt/server -rm /mnt/server/oxide.zip -echo "This file is used to determine whether the server is an OxideMod server or not. -Do not delete this file or you may loose OxideMod auto updating from the server." > /mnt/server/OXIDE_FLAG - -mkdir -p /mnt/server/.steam/sdk32 -cp -v /mnt/server/steam/linux32/steamclient.so /mnt/server/.steam/sdk32/steamclient.so -EOF; - - $this->option['rustoxide'] = ServiceOption::updateOrCreate([ - 'service_id' => $this->service->id, - 'tag' => 'rustoxide', - ], [ - 'name' => 'OxideMod', - 'description' => 'OxideMod Rust server.', - 'docker_image' => 'quay.io/pterodactyl/core:rust', - 'config_startup' => '{"done": "Server startup complete", "userInteraction": []}', - 'config_files' => '{}', - 'config_logs' => '{"custom": false, "location": "latest.log"}', - 'config_stop' => 'quit', - 'config_from' => null, - 'startup' => null, - 'script_install' => $script, - 'script_entry' => 'bash', - 'script_container' => 'ubuntu:16.04', - ]); - } - - private function addVariables() - { - $this->addVanillaVariables(); - $this->addOxideVariables(); - } - - private function addVanillaVariables() - { - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['rustvanilla']->id, - 'env_variable' => 'HOSTNAME', - ], [ - 'name' => 'Server Name', - 'description' => 'The name of your server in the public server list.', - 'default_value' => 'A Rust Server', - 'user_viewable' => 1, - 'user_editable' => 1, - 'rules' => 'required|string', - ]); - - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['rustvanilla']->id, - 'env_variable' => 'LEVEL', - ], [ - 'name' => 'Level', - 'description' => 'The world file for Rust to use.', - 'default_value' => 'Procedural Map', - 'user_viewable' => 1, - 'user_editable' => 1, - 'rules' => 'required|string', - ]); - - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['rustvanilla']->id, - 'env_variable' => 'DESCRIPTION', - ], [ - 'name' => 'Description', - 'description' => 'The description under your server title. Commonly used for rules & info.', - 'default_value' => 'Powered by Pterodactyl', - 'user_viewable' => 1, - 'user_editable' => 1, - 'rules' => 'required|string', - ]); - - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['rustvanilla']->id, - 'env_variable' => 'URL', - ], [ - 'name' => 'URL', - 'description' => 'The URL for your server. This is what comes up when clicking the "Visit Website" button.', - 'default_value' => 'http://pterodactyl.io', - 'user_viewable' => 1, - 'user_editable' => 1, - 'rules' => 'url', - ]); - - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['rustvanilla']->id, - 'env_variable' => 'WORLD_SIZE', - ], [ - 'name' => 'World Size', - 'description' => 'The world size for a procedural map.', - 'default_value' => '3000', - 'user_viewable' => 1, - 'user_editable' => 1, - 'rules' => 'required|integer', - ]); - - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['rustvanilla']->id, - 'env_variable' => 'SEED', - ], [ - 'name' => 'World Seed', - 'description' => 'The seed for a procedural map.', - 'default_value' => '', - 'user_viewable' => 1, - 'user_editable' => 1, - 'rules' => 'present', - ]); - - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['rustvanilla']->id, - 'env_variable' => 'MAX_PLAYERS', - ], [ - 'name' => 'Max Players', - 'description' => 'The maximum amount of players allowed in the server at once.', - 'default_value' => '40', - 'user_viewable' => 1, - 'user_editable' => 1, - 'rules' => 'required|integer', - ]); - - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['rustvanilla']->id, - 'env_variable' => 'SERVER_IMG', - ], [ - 'name' => 'Server Header Image', - 'description' => 'The header image for the top of your server listing.', - 'default_value' => '', - 'user_viewable' => 1, - 'user_editable' => 1, - 'rules' => 'url', - ]); - - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['rustvanilla']->id, - 'env_variable' => 'RCON_PORT', - ], [ - 'name' => 'RCON Port', - 'description' => 'Port for RCON connections.', - 'default_value' => '8401', - 'user_viewable' => 1, - 'user_editable' => 0, - 'rules' => 'required|integer', - ]); - - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['rustvanilla']->id, - 'env_variable' => 'RCON_PASS', - ], [ - 'name' => 'RCON Password', - 'description' => 'Remote console access password.', - 'default_value' => 'CHANGEME', - 'user_viewable' => 1, - 'user_editable' => 1, - 'rules' => 'required', - ]); - - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['rustvanilla']->id, - 'env_variable' => 'ADDITIONAL_ARGS', - ], [ - 'name' => 'Additional Arguments', - 'description' => 'Add additional startup parameters to the server.', - 'default_value' => '', - 'user_viewable' => 1, - 'user_editable' => 1, - 'rules' => 'present', - ]); - } - - private function addOxideVariables() - { - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['rustoxide']->id, - 'env_variable' => 'HOSTNAME', - ], [ - 'name' => 'Server Name', - 'description' => 'The name of your server in the public server list.', - 'default_value' => 'A Rust Server', - 'user_viewable' => 1, - 'user_editable' => 1, - 'rules' => 'required|string', - ]); - - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['rustoxide']->id, - 'env_variable' => 'LEVEL', - ], [ - 'name' => 'Level', - 'description' => 'The world file for Rust to use.', - 'default_value' => 'Procedural Map', - 'user_viewable' => 1, - 'user_editable' => 1, - 'rules' => 'required|string', - ]); - - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['rustoxide']->id, - 'env_variable' => 'DESCRIPTION', - ], [ - 'name' => 'Description', - 'description' => 'The description under your server title. Commonly used for rules & info.', - 'default_value' => 'Powered by Pterodactyl', - 'user_viewable' => 1, - 'user_editable' => 1, - 'rules' => 'required|string', - ]); - - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['rustoxide']->id, - 'env_variable' => 'URL', - ], [ - 'name' => 'URL', - 'description' => 'The URL for your server. This is what comes up when clicking the "Visit Website" button.', - 'default_value' => 'http://pterodactyl.io', - 'user_viewable' => 1, - 'user_editable' => 1, - 'rules' => 'url', - ]); - - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['rustoxide']->id, - 'env_variable' => 'WORLD_SIZE', - ], [ - 'name' => 'World Size', - 'description' => 'The world size for a procedural map.', - 'default_value' => '3000', - 'user_viewable' => 1, - 'user_editable' => 1, - 'rules' => 'required|integer', - ]); - - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['rustoxide']->id, - 'env_variable' => 'SEED', - ], [ - 'name' => 'World Seed', - 'description' => 'The seed for a procedural map.', - 'default_value' => '', - 'user_viewable' => 1, - 'user_editable' => 1, - 'rules' => 'present', - ]); - - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['rustoxide']->id, - 'env_variable' => 'MAX_PLAYERS', - ], [ - 'name' => 'Max Players', - 'description' => 'The maximum amount of players allowed in the server at once.', - 'default_value' => '40', - 'user_viewable' => 1, - 'user_editable' => 1, - 'rules' => 'required|integer', - ]); - - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['rustoxide']->id, - 'env_variable' => 'SERVER_IMG', - ], [ - 'name' => 'Server Header Image', - 'description' => 'The header image for the top of your server listing.', - 'default_value' => '', - 'user_viewable' => 1, - 'user_editable' => 1, - 'rules' => 'url', - ]); - - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['rustoxide']->id, - 'env_variable' => 'RCON_PORT', - ], [ - 'name' => 'RCON Port', - 'description' => 'Port for RCON connections.', - 'default_value' => '8401', - 'user_viewable' => 1, - 'user_editable' => 0, - 'rules' => 'required|integer', - ]); - - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['rustoxide']->id, - 'env_variable' => 'RCON_PASS', - ], [ - 'name' => 'RCON Password', - 'description' => 'Remote console access password.', - 'default_value' => 'CHANGEME', - 'user_viewable' => 1, - 'user_editable' => 1, - 'rules' => 'required', - ]); - - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['rustoxide']->id, - 'env_variable' => 'ADDITIONAL_ARGS', - ], [ - 'name' => 'Additional Arguments', - 'description' => 'Add additional startup parameters to the server.', - 'default_value' => '', - 'user_viewable' => 1, - 'user_editable' => 1, - 'rules' => 'present', - ]); - } -} diff --git a/database/seeds/SourceServiceTableSeeder.php b/database/seeds/SourceServiceTableSeeder.php deleted file mode 100644 index efeec5ec9..000000000 --- a/database/seeds/SourceServiceTableSeeder.php +++ /dev/null @@ -1,478 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ -use Illuminate\Database\Seeder; -use Pterodactyl\Models\Service; -use Pterodactyl\Models\ServiceOption; -use Pterodactyl\Models\ServiceVariable; -use Pterodactyl\Traits\Services\CreatesServiceIndex; - -class SourceServiceTableSeeder extends Seeder -{ - use CreatesServiceIndex; - - /** - * The core service ID. - * - * @var Models\Service - */ - protected $service; - - /** - * Stores all of the option objects. - * - * @var array - */ - protected $option = []; - - /** - * Run the database seeds. - */ - public function run() - { - $this->addCoreService(); - $this->addCoreOptions(); - $this->addVariables(); - } - - private function addCoreService() - { - $this->service = Service::updateOrCreate([ - 'author' => config('pterodactyl.service.core'), - 'folder' => 'srcds', - ], [ - 'name' => 'Source Engine', - 'description' => 'Includes support for most Source Dedicated Server games.', - 'startup' => './srcds_run -game {{SRCDS_GAME}} -console -port {{SERVER_PORT}} +ip 0.0.0.0 -strictportbind -norestart', - 'index_file' => $this->getIndexScript(), - ]); - } - - private function addCoreOptions() - { - $script = <<<'EOF' -#!/bin/bash -# SRCDS Base Installation Script -# -# Server Files: /mnt/server -apt -y update -apt -y --no-install-recommends install curl lib32gcc1 ca-certificates - -cd /tmp -curl -sSL -o steamcmd.tar.gz http://media.steampowered.com/installer/steamcmd_linux.tar.gz - -mkdir -p /mnt/server/steamcmd -tar -xzvf steamcmd.tar.gz -C /mnt/server/steamcmd -cd /mnt/server/steamcmd - -# SteamCMD fails otherwise for some reason, even running as root. -# This is changed at the end of the install process anyways. -chown -R root:root /mnt - -export HOME=/mnt/server -./steamcmd.sh +login anonymous +force_install_dir /mnt/server +app_update ${SRCDS_APPID} +quit - -mkdir -p /mnt/server/.steam/sdk32 -cp -v linux32/steamclient.so ../.steam/sdk32/steamclient.so -EOF; - - $this->option['source'] = ServiceOption::updateOrCreate([ - 'service_id' => $this->service->id, - 'tag' => 'source', - ], [ - 'name' => 'Custom Source Engine Game', - 'description' => 'This option allows modifying the startup arguments and other details to run a custo SRCDS based game on the panel.', - 'docker_image' => 'quay.io/pterodactyl/core:source', - 'config_startup' => '{"done": "gameserver Steam ID", "userInteraction": []}', - 'config_files' => '{}', - 'config_logs' => '{"custom": true, "location": "logs/latest.log"}', - 'config_stop' => 'quit', - 'config_from' => null, - 'startup' => null, - 'script_install' => $script, - 'script_entry' => 'bash', - 'script_container' => 'ubuntu:16.04', - ]); - - $this->option['insurgency'] = ServiceOption::updateOrCreate([ - 'service_id' => $this->service->id, - 'tag' => 'insurgency', - ], [ - 'name' => 'Insurgency', - 'description' => 'Take to the streets for intense close quarters combat, where a team\'s survival depends upon securing crucial strongholds and destroying enemy supply in this multiplayer and cooperative Source Engine based experience.', - 'docker_image' => 'quay.io/pterodactyl/core:source', - 'config_startup' => null, - 'config_files' => null, - 'config_logs' => null, - 'config_stop' => null, - 'config_from' => $this->option['source']->id, - 'startup' => './srcds_run -game {{SRCDS_GAME}} -console -port {{SERVER_PORT}} +map {{SRCDS_MAP}} +ip 0.0.0.0 -strictportbind -norestart', - 'copy_script_from' => $this->option['source']->id, - ]); - - $this->option['tf2'] = ServiceOption::updateOrCreate([ - 'service_id' => $this->service->id, - 'tag' => 'tf2', - ], [ - 'name' => 'Team Fortress 2', - 'description' => 'Team Fortress 2 is a team-based first-person shooter multiplayer video game developed and published by Valve Corporation. It is the sequel to the 1996 mod Team Fortress for Quake and its 1999 remake.', - 'docker_image' => 'quay.io/pterodactyl/core:source', - 'config_startup' => null, - 'config_files' => null, - 'config_logs' => null, - 'config_stop' => null, - 'config_from' => $this->option['source']->id, - 'startup' => './srcds_run -game {{SRCDS_GAME}} -console -port {{SERVER_PORT}} +map {{SRCDS_MAP}} +ip 0.0.0.0 -strictportbind -norestart', - 'copy_script_from' => $this->option['source']->id, - ]); - - $script = <<<'EOF' -#!/bin/bash -# ARK: Installation Script -# -# Server Files: /mnt/server -apt -y update -apt -y --no-install-recommends install curl lib32gcc1 ca-certificates - -cd /tmp -curl -sSL -o steamcmd.tar.gz http://media.steampowered.com/installer/steamcmd_linux.tar.gz - -mkdir -p /mnt/server/steamcmd -mkdir -p /mnt/server/Engine/Binaries/ThirdParty/SteamCMD/Linux - -tar -xzvf steamcmd.tar.gz -C /mnt/server/steamcmd -tar -xzvf steamcmd.tar.gz -C /mnt/server/Engine/Binaries/ThirdParty/SteamCMD/Linux - -cd /mnt/server/steamcmd - -# SteamCMD fails otherwise for some reason, even running as root. -# This is changed at the end of the install process anyways. -chown -R root:root /mnt - -export HOME=/mnt/server -./steamcmd.sh +login anonymous +force_install_dir /mnt/server +app_update 376030 +quit - -mkdir -p /mnt/server/.steam/sdk32 -cp -v linux32/steamclient.so ../.steam/sdk32/steamclient.so -EOF; - - $this->option['ark'] = ServiceOption::updateOrCreate([ - 'service_id' => $this->service->id, - 'tag' => 'ark', - ], [ - 'name' => 'Ark: Survival Evolved', - 'description' => 'As a man or woman stranded, naked, freezing, and starving on the unforgiving shores of a mysterious island called ARK, use your skill and cunning to kill or tame and ride the plethora of leviathan dinosaurs and other primeval creatures roaming the land. Hunt, harvest resources, craft items, grow crops, research technologies, and build shelters to withstand the elements and store valuables, all while teaming up with (or preying upon) hundreds of other players to survive, dominate... and escape! — Gamepedia: ARK', - 'docker_image' => 'quay.io/pterodactyl/core:source', - 'config_startup' => '{"done": "Setting breakpad minidump AppID"}', - 'config_files' => null, - 'config_logs' => null, - 'config_stop' => '^C', - 'config_from' => $this->option['source']->id, - 'startup' => './ShooterGame/Binaries/Linux/ShooterGameServer TheIsland?listen?ServerPassword={{ARK_PASSWORD}}?ServerAdminPassword={{ARK_ADMIN_PASSWORD}}?Port={{SERVER_PORT}}?MaxPlayers={{SERVER_MAX_PLAYERS}}', - 'script_install' => $script, - 'script_entry' => 'bash', - 'script_container' => 'ubuntu:16.04', - ]); - - $script = <<<'EOF' -#!/bin/bash -# CSGO Installation Script -# -# Server Files: /mnt/server -apt -y update -apt -y --no-install-recommends install curl lib32gcc1 ca-certificates - -cd /tmp -curl -sSL -o steamcmd.tar.gz http://media.steampowered.com/installer/steamcmd_linux.tar.gz - -mkdir -p /mnt/server/steamcmd -tar -xzvf steamcmd.tar.gz -C /mnt/server/steamcmd -cd /mnt/server/steamcmd - -# SteamCMD fails otherwise for some reason, even running as root. -# This is changed at the end of the install process anyways. -chown -R root:root /mnt - -export HOME=/mnt/server -./steamcmd.sh +login anonymous +force_install_dir /mnt/server +app_update 740 +quit - -mkdir -p /mnt/server/.steam/sdk32 -cp -v linux32/steamclient.so ../.steam/sdk32/steamclient.so -EOF; - - $this->option['csgo'] = ServiceOption::updateOrCreate([ - 'service_id' => $this->service->id, - 'tag' => 'csgo', - ], [ - 'name' => 'Counter-Strike: Global Offensive', - 'description' => 'Counter-Strike: Global Offensive is a multiplayer first-person shooter video game developed by Hidden Path Entertainment and Valve Corporation.', - 'docker_image' => 'quay.io/pterodactyl/core:source', - 'config_startup' => '{"done": "VAC secure mode is activated.", "userInteraction": []}', - 'config_files' => null, - 'config_logs' => '{"custom": true, "location": "logs/latest.log"}', - 'config_stop' => 'quit', - 'config_from' => $this->option['source']->id, - 'startup' => './srcds_run -game csgo -console -port {{SERVER_PORT}} +ip 0.0.0.0 +map {{SRCDS_MAP}} -strictportbind -norestart +sv_setsteamaccount {{STEAM_ACC}}', - 'script_install' => $script, - 'script_entry' => 'bash', - 'script_container' => 'ubuntu:16.04', - ]); - - $script = <<<'EOF' -#!/bin/bash -# Garry's Mod Installation Script -# -# Server Files: /mnt/server -apt -y update -apt -y --no-install-recommends install curl lib32gcc1 ca-certificates - -cd /tmp -curl -sSL -o steamcmd.tar.gz http://media.steampowered.com/installer/steamcmd_linux.tar.gz - -mkdir -p /mnt/server/steamcmd -tar -xzvf steamcmd.tar.gz -C /mnt/server/steamcmd -cd /mnt/server/steamcmd - -# SteamCMD fails otherwise for some reason, even running as root. -# This is changed at the end of the install process anyways. -chown -R root:root /mnt - -export HOME=/mnt/server -./steamcmd.sh +login anonymous +force_install_dir /mnt/server +app_update 4020 +quit - -mkdir -p /mnt/server/.steam/sdk32 -cp -v linux32/steamclient.so ../.steam/sdk32/steamclient.so -EOF; - - $this->option['gmod'] = ServiceOption::updateOrCreate([ - 'service_id' => $this->service->id, - 'tag' => 'gmod', - ], [ - 'name' => 'Garrys Mod', - 'description' => 'Garrys Mod, is a sandbox physics game created by Garry Newman, and developed by his company, Facepunch Studios.', - 'docker_image' => 'quay.io/pterodactyl/core:source', - 'config_startup' => '{"done": "VAC secure mode is activated.", "userInteraction": []}', - 'config_files' => null, - 'config_logs' => '{"custom": true, "location": "logs/latest.log"}', - 'config_stop' => 'quit', - 'config_from' => $this->option['source']->id, - 'startup' => './srcds_run -game garrysmod -console -port {{SERVER_PORT}} +ip 0.0.0.0 +map {{SRCDS_MAP}} -strictportbind -norestart +sv_setsteamaccount {{STEAM_ACC}}', - 'script_install' => $script, - 'script_entry' => 'bash', - 'script_container' => 'ubuntu:16.04', - ]); - } - - private function addVariables() - { - $this->addInsurgencyVariables(); - $this->addTF2Variables(); - $this->addArkVariables(); - $this->addCSGOVariables(); - $this->addGMODVariables(); - $this->addCustomVariables(); - } - - private function addInsurgencyVariables() - { - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['insurgency']->id, - 'env_variable' => 'SRCDS_APPID', - ], [ - 'name' => 'Game ID', - 'description' => 'The ID corresponding to the game to download and run using SRCDS.', - 'default_value' => '17705', - 'user_viewable' => 1, - 'user_editable' => 0, - 'rules' => 'required|regex:/^(17705)$/', - ]); - - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['insurgency']->id, - 'env_variable' => 'SRCDS_GAME', - ], [ - 'name' => 'Game Name', - 'description' => 'The name corresponding to the game to download and run using SRCDS.', - 'default_value' => 'insurgency', - 'user_viewable' => 1, - 'user_editable' => 0, - 'rules' => 'required|regex:/^(insurgency)$/', - ]); - - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['insurgency']->id, - 'env_variable' => 'SRCDS_MAP', - ], [ - 'name' => 'Default Map', - 'description' => 'The default map to use when starting the server.', - 'default_value' => 'sinjar', - 'user_viewable' => 1, - 'user_editable' => 1, - 'rules' => 'required|regex:/^(\w{1,20})$/', - ]); - } - - private function addTF2Variables() - { - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['tf2']->id, - 'env_variable' => 'SRCDS_APPID', - ], [ - 'name' => 'Game ID', - 'description' => 'The ID corresponding to the game to download and run using SRCDS.', - 'default_value' => '232250', - 'user_viewable' => 1, - 'user_editable' => 0, - 'rules' => 'required|regex:/^(232250)$/', - ]); - - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['tf2']->id, - 'env_variable' => 'SRCDS_GAME', - ], [ - 'name' => 'Game Name', - 'description' => 'The name corresponding to the game to download and run using SRCDS.', - 'default_value' => 'tf', - 'user_viewable' => 1, - 'user_editable' => 0, - 'rules' => 'required|regex:/^(tf)$/', - ]); - - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['tf2']->id, - 'env_variable' => 'SRCDS_MAP', - ], [ - 'name' => 'Default Map', - 'description' => 'The default map to use when starting the server.', - 'default_value' => 'cp_dustbowl', - 'user_viewable' => 1, - 'user_editable' => 1, - 'rules' => 'required|regex:/^(\w{1,20})$/', - ]); - } - - private function addArkVariables() - { - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['ark']->id, - 'env_variable' => 'ARK_PASSWORD', - ], [ - 'name' => 'Server Password', - 'description' => 'If specified, players must provide this password to join the server.', - 'default_value' => '', - 'user_viewable' => 1, - 'user_editable' => 1, - 'rules' => 'alpha_dash|between:1,100', - ]); - - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['ark']->id, - 'env_variable' => 'ARK_ADMIN_PASSWORD', - ], [ - 'name' => 'Admin Password', - 'description' => 'If specified, players must provide this password (via the in-game console) to gain access to administrator commands on the server.', - 'default_value' => '', - 'user_viewable' => 1, - 'user_editable' => 1, - 'rules' => 'alpha_dash|between:1,100', - ]); - - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['ark']->id, - 'env_variable' => 'SERVER_MAX_PLAYERS', - ], [ - 'name' => 'Maximum Players', - 'description' => 'Specifies the maximum number of players that can play on the server simultaneously.', - 'default_value' => 20, - 'user_viewable' => 1, - 'user_editable' => 1, - 'rules' => 'required|numeric|digits_between:1,4', - ]); - } - - private function addCSGOVariables() - { - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['csgo']->id, - 'env_variable' => 'SRCDS_MAP', - ], [ - 'name' => 'Map', - 'description' => 'The default map for the server.', - 'default_value' => 'de_dust2', - 'user_viewable' => 1, - 'user_editable' => 1, - 'rules' => 'required|string|alpha_dash', - ]); - - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['csgo']->id, - 'env_variable' => 'STEAM_ACC', - ], [ - 'name' => 'Steam Account Token', - 'description' => 'The Steam Account Token required for the server to be displayed publicly.', - 'default_value' => '', - 'user_viewable' => 1, - 'user_editable' => 1, - 'rules' => 'required|string|alpha_num|size:32', - ]); - } - - private function addGMODVariables() - { - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['gmod']->id, - 'env_variable' => 'SRCDS_MAP', - ], [ - 'name' => 'Map', - 'description' => 'The default map for the server.', - 'default_value' => 'gm_flatgrass', - 'user_viewable' => 1, - 'user_editable' => 1, - 'rules' => 'required|string|alpha_dash', - ]); - - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['gmod']->id, - 'env_variable' => 'STEAM_ACC', - ], [ - 'name' => 'Steam Account Token', - 'description' => 'The Steam Account Token required for the server to be displayed publicly.', - 'default_value' => '', - 'user_viewable' => 1, - 'user_editable' => 1, - 'rules' => 'required|string|alpha_num|size:32', - ]); - } - - private function addCustomVariables() - { - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['source']->id, - 'env_variable' => 'SRCDS_APPID', - ], [ - 'name' => 'Game ID', - 'description' => 'The ID corresponding to the game to download and run using SRCDS.', - 'default_value' => '', - 'user_viewable' => 1, - 'user_editable' => 0, - 'rules' => 'required|numeric|digits_between:1,6', - ]); - - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['source']->id, - 'env_variable' => 'SRCDS_GAME', - ], [ - 'name' => 'Game Name', - 'description' => 'The name corresponding to the game to download and run using SRCDS.', - 'default_value' => '', - 'user_viewable' => 1, - 'user_editable' => 0, - 'rules' => 'required|alpha_dash|between:1,100', - ]); - } -} diff --git a/database/seeds/TerrariaServiceTableSeeder.php b/database/seeds/TerrariaServiceTableSeeder.php deleted file mode 100644 index 46b7bb67e..000000000 --- a/database/seeds/TerrariaServiceTableSeeder.php +++ /dev/null @@ -1,116 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ -use Illuminate\Database\Seeder; -use Pterodactyl\Models\Service; -use Pterodactyl\Models\ServiceOption; -use Pterodactyl\Models\ServiceVariable; -use Pterodactyl\Traits\Services\CreatesServiceIndex; - -class TerrariaServiceTableSeeder extends Seeder -{ - use CreatesServiceIndex; - - /** - * The core service ID. - * - * @var Models\Service - */ - protected $service; - - /** - * Stores all of the option objects. - * - * @var array - */ - protected $option = []; - - /** - * Run the database seeds. - */ - public function run() - { - $this->addCoreService(); - $this->addCoreOptions(); - $this->addVariables(); - } - - private function addCoreService() - { - $this->service = Service::updateOrCreate([ - 'author' => config('pterodactyl.service.core'), - 'folder' => 'terraria', - ], [ - 'name' => 'Terraria', - 'description' => 'Terraria is a land of adventure! A land of mystery! A land that\'s yours to shape, defend, and enjoy. Your options in Terraria are limitless. Are you an action gamer with an itchy trigger finger? A master builder? A collector? An explorer? There\'s something for everyone.', - 'startup' => 'mono TerrariaServer.exe -port {{SERVER_PORT}} -autocreate 2 -worldname World', - 'index_file' => $this->getIndexScript(), - ]); - } - - private function addCoreOptions() - { - $script = <<<'EOF' -#!/bin/ash -# TShock Installation Script -# -# Server Files: /mnt/server -apk update -apk add curl unzip - -cd /tmp - -curl -sSLO https://github.com/NyxStudios/TShock/releases/download/v${T_VERSION}/tshock_${T_VERSION}.zip - -unzip -o tshock_${T_VERSION}.zip -d /mnt/server -EOF; - - $this->option['tshock'] = ServiceOption::updateOrCreate([ - 'service_id' => $this->service->id, - 'tag' => 'tshock', - ], [ - 'name' => 'Terraria Server (TShock)', - 'description' => 'TShock is a server modification for Terraria, written in C#, and based upon the Terraria Server API. It uses JSON for configuration management, and offers several features not present in the Terraria Server normally.', - 'docker_image' => 'quay.io/pterodactyl/core:mono', - 'config_startup' => '{"userInteraction": [ "You need to agree to the EULA"]}', - 'config_startup' => '{"done": "Type \'help\' for a list of commands", "userInteraction": []}', - 'config_files' => '{"tshock/config.json":{"parser": "json", "find":{"ServerPort": "{{server.build.default.port}}", "MaxSlots": "{{server.build.env.MAX_SLOTS}}"}}}', - 'config_logs' => '{"custom": false, "location": "ServerLog.txt"}', - 'config_stop' => 'exit', - 'startup' => null, - 'script_install' => $script, - ]); - } - - private function addVariables() - { - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['tshock']->id, - 'env_variable' => 'T_VERSION', - ], [ - 'name' => 'TShock Version', - 'description' => 'Which version of TShock to install and use.', - 'default_value' => '4.3.22', - 'user_viewable' => 1, - 'user_editable' => 1, - 'rules' => 'required|regex:/^([0-9_\.-]{5,10})$/', - ]); - - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['tshock']->id, - 'env_variable' => 'MAX_SLOTS', - ], [ - 'name' => 'Maximum Slots', - 'description' => 'Total number of slots to allow on the server.', - 'default_value' => 20, - 'user_viewable' => 1, - 'user_editable' => 0, - 'rules' => 'required|numeric|digits_between:1,3', - ]); - } -} diff --git a/database/seeds/VoiceServiceTableSeeder.php b/database/seeds/VoiceServiceTableSeeder.php deleted file mode 100644 index b9d0f0977..000000000 --- a/database/seeds/VoiceServiceTableSeeder.php +++ /dev/null @@ -1,183 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ -use Illuminate\Database\Seeder; -use Pterodactyl\Models\Service; -use Pterodactyl\Models\ServiceOption; -use Pterodactyl\Models\ServiceVariable; -use Pterodactyl\Traits\Services\CreatesServiceIndex; - -class VoiceServiceTableSeeder extends Seeder -{ - use CreatesServiceIndex; - - /** - * The core service ID. - * - * @var Service - */ - protected $service; - - /** - * Stores all of the option objects. - * - * @var array - */ - protected $option = []; - - /** - * Run the database seeds. - */ - public function run() - { - $this->addCoreService(); - $this->addCoreOptions(); - $this->addVariables(); - } - - private function addCoreService() - { - $this->service = Service::updateOrCreate([ - 'author' => config('pterodactyl.service.core'), - 'folder' => 'voice', - ], [ - 'name' => 'Voice Servers', - 'description' => 'Voice servers such as Mumble and Teamspeak 3.', - 'startup' => '', - 'index_file' => $this->getIndexScript(), - ]); - } - - private function addCoreOptions() - { - $script = <<<'EOF' -#!/bin/ash -# Mumble Installation Script -# -# Server Files: /mnt/server -apk update -apk add tar curl - -cd /tmp - -curl -sSLO https://github.com/mumble-voip/mumble/releases/download/${MUMBLE_VERSION}/murmur-static_x86-${MUMBLE_VERSION}.tar.bz2 - -tar -xjvf murmur-static_x86-${MUMBLE_VERSION}.tar.bz2 -cp -r murmur-static_x86-${MUMBLE_VERSION}/* /mnt/server -EOF; - - $this->option['mumble'] = ServiceOption::updateOrCreate([ - 'service_id' => $this->service->id, - 'tag' => 'mumble', - ], [ - 'name' => 'Mumble Server', - 'description' => 'Mumble is an open source, low-latency, high quality voice chat software primarily intended for use while gaming.', - 'docker_image' => 'quay.io/pterodactyl/core:glibc', - 'config_startup' => '{"done": "Server listening on", "userInteraction": [ "Generating new server certificate"]}', - 'config_files' => '{"murmur.ini":{"parser": "ini", "find":{"logfile": "murmur.log", "port": "{{server.build.default.port}}", "host": "0.0.0.0", "users": "{{server.build.env.MAX_USERS}}"}}}', - 'config_logs' => '{"custom": true, "location": "logs/murmur.log"}', - 'config_stop' => '^C', - 'config_from' => null, - 'startup' => './murmur.x86 -fg', - 'script_install' => $script, - ]); - - $script = <<<'EOF' -#!/bin/ash -# TS3 Installation Script -# -# Server Files: /mnt/server -apk update -apk add tar curl - -cd /tmp - -curl -sSLO http://dl.4players.de/ts/releases/${TS_VERSION}/teamspeak3-server_linux_amd64-${TS_VERSION}.tar.bz2 - -tar -xjvf teamspeak3-server_linux_amd64-${TS_VERSION}.tar.bz2 -cp -r teamspeak3-server_linux_amd64/* /mnt/server -cp -r /mnt/server/redist/* /mnt/server/ - -echo "machine_id= -default_voice_port=${SERVER_PORT} -voice_ip=0.0.0.0 -licensepath= -filetransfer_port=30033 -filetransfer_ip= -query_port=${SERVER_PORT} -query_ip=0.0.0.0 -query_ip_whitelist=query_ip_whitelist.txt -query_ip_blacklist=query_ip_blacklist.txt -dbplugin=ts3db_sqlite3 -dbpluginparameter= -dbsqlpath=sql/ -dbsqlcreatepath=create_sqlite/ -dbconnections=10 -logpath=logs -logquerycommands=0 -dbclientkeepdays=30 -logappend=0 -query_skipbruteforcecheck=0" > /mnt/server/ts3server.ini -EOF; - - $this->option['ts3'] = ServiceOption::updateOrCreate([ - 'service_id' => $this->service->id, - 'tag' => 'ts3', - ], [ - 'name' => 'Teamspeak3 Server', - 'description' => 'VoIP software designed with security in mind, featuring crystal clear voice quality, endless customization options, and scalabilty up to thousands of simultaneous users.', - 'docker_image' => 'quay.io/pterodactyl/core:glibc', - 'config_startup' => '{"done": "listening on 0.0.0.0:", "userInteraction": []}', - 'config_files' => '{"ts3server.ini":{"parser": "ini", "find":{"default_voice_port": "{{server.build.default.port}}", "voice_ip": "0.0.0.0", "query_port": "{{server.build.default.port}}", "query_ip": "0.0.0.0"}}}', - 'config_logs' => '{"custom": true, "location": "logs/ts3.log"}', - 'config_stop' => '^C', - 'config_from' => null, - 'startup' => './ts3server_minimal_runscript.sh default_voice_port={{SERVER_PORT}} query_port={{SERVER_PORT}}', - 'script_install' => $script, - ]); - } - - private function addVariables() - { - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['mumble']->id, - 'env_variable' => 'MAX_USERS', - ], [ - 'name' => 'Maximum Users', - 'description' => 'Maximum concurrent users on the mumble server.', - 'default_value' => 100, - 'user_viewable' => 1, - 'user_editable' => 0, - 'rules' => 'required|numeric|digits_between:1,5', - ]); - - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['mumble']->id, - 'env_variable' => 'MUMBLE_VERSION', - ], [ - 'name' => 'Server Version', - 'description' => 'Version of Mumble Server to download and use.', - 'default_value' => '1.2.19', - 'user_viewable' => 1, - 'user_editable' => 1, - 'rules' => 'required|regex:/^([0-9_\.-]{5,8})$/', - ]); - - ServiceVariable::updateOrCreate([ - 'option_id' => $this->option['ts3']->id, - 'env_variable' => 'TS_VERSION', - ], [ - 'name' => 'Server Version', - 'description' => 'The version of Teamspeak 3 to use when running the server.', - 'default_value' => '3.0.13.7', - 'user_viewable' => 1, - 'user_editable' => 1, - 'rules' => 'required|regex:/^([0-9_\.-]{5,10})$/', - ]); - } -} diff --git a/public/themes/pterodactyl/js/admin/new-server.js b/public/themes/pterodactyl/js/admin/new-server.js index 2501a4cc9..e15a14b73 100644 --- a/public/themes/pterodactyl/js/admin/new-server.js +++ b/public/themes/pterodactyl/js/admin/new-server.js @@ -18,11 +18,11 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. $(document).ready(function() { - $('#pServiceId').select2({ - placeholder: 'Select a Service', + $('#pNestId').select2({ + placeholder: 'Select a Nest', }).change(); - $('#pOptionId').select2({ - placeholder: 'Select a Service Option', + $('#pEggId').select2({ + placeholder: 'Select a Nest Egg', }); $('#pPackId').select2({ placeholder: 'Select a Service Pack', @@ -116,9 +116,9 @@ $('#pNodeId').on('change', function (event) { }); }); -$('#pServiceId').on('change', function (event) { - $('#pOptionId').html('').select2({ - data: $.map(_.get(Pterodactyl.services, $(this).val() + '.options', []), function (item) { +$('#pNestId').on('change', function (event) { + $('#pEggId').html('').select2({ + data: $.map(_.get(Pterodactyl.nests, $(this).val() + '.eggs', []), function (item) { return { id: item.id, text: item.name, @@ -127,9 +127,9 @@ $('#pServiceId').on('change', function (event) { }).change(); }); -$('#pOptionId').on('change', function (event) { - var parentChain = _.get(Pterodactyl.services, $('#pServiceId').val(), null); - var objectChain = _.get(parentChain, 'options.' + $(this).val(), null); +$('#pEggId').on('change', function (event) { + var parentChain = _.get(Pterodactyl.nests, $('#pNestId').val(), null); + var objectChain = _.get(parentChain, 'eggs.' + $(this).val(), null); $('#pDefaultContainer').val(_.get(objectChain, 'docker_image', 'not defined!')); diff --git a/resources/lang/en/admin/nests.php b/resources/lang/en/admin/nests.php new file mode 100644 index 000000000..19e6b5a1e --- /dev/null +++ b/resources/lang/en/admin/nests.php @@ -0,0 +1,33 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +return [ + 'notices' => [ + 'created' => 'A new nest, :name, has been successfully created.', + 'deleted' => 'Successfully deleted the requested nest from the Panel.', + 'updated' => 'Successfully updated the nest configuration options.', + ], + 'eggs' => [ + 'notices' => [ + 'imported' => 'Successfully imported this Egg and its associated variables.', + 'updated_via_import' => 'This Egg has been updated using the file provided.', + 'deleted' => 'Successfully deleted the requested egg from the Panel.', + 'updated' => 'Egg configuration has been updated successfully.', + 'script_updated' => 'Egg install script has been updated and will run whenever servers are installed.', + 'egg_created' => 'A new egg was laid successfully. You will need to restart any running daemons to apply this new egg.', + ], + ], + 'variables' => [ + 'notices' => [ + 'variable_deleted' => 'The variable ":variable" has been deleted and will no longer be available to servers once rebuilt.', + 'variable_updated' => 'The variable ":variable" has been updated. You will need to rebuild any servers using this variable in order to apply changes.', + 'variable_created' => 'New variable has successfully been created and assigned to this egg.', + ], + ], +]; diff --git a/resources/lang/en/admin/services.php b/resources/lang/en/admin/services.php deleted file mode 100644 index 86db21509..000000000 --- a/resources/lang/en/admin/services.php +++ /dev/null @@ -1,32 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -return [ - 'notices' => [ - 'service_created' => 'A new service, :name, has been successfully created.', - 'service_deleted' => 'Successfully deleted the requested service from the Panel.', - 'service_updated' => 'Successfully updated the service configuration options.', - 'functions_updated' => 'The service functions file has been updated. You will need to reboot your Nodes in order for these changes to be applied.', - ], - 'options' => [ - 'notices' => [ - 'option_deleted' => 'Successfully deleted the requested service option from the Panel.', - 'option_updated' => 'Service option configuration has been updated successfully.', - 'script_updated' => 'Service option install script has been updated and will run whenever servers are installed.', - 'option_created' => 'New service option was created successfully. You will need to restart any running daemons to apply this new service.', - ], - ], - 'variables' => [ - 'notices' => [ - 'variable_deleted' => 'The variable ":variable" has been deleted and will no longer be available to servers once rebuilt.', - 'variable_updated' => 'The variable ":variable" has been updated. You will need to rebuild any servers using this variable in order to apply changes.', - 'variable_created' => 'New variable has successfully been created and assigned to this service option.', - ], - ], -]; diff --git a/resources/lang/en/exceptions.php b/resources/lang/en/exceptions.php index 2a8f1e047..e5bee177d 100644 --- a/resources/lang/en/exceptions.php +++ b/resources/lang/en/exceptions.php @@ -18,18 +18,23 @@ return [ 'invalid_mapping' => 'The mapping provided for :port was invalid and could not be processed.', 'cidr_out_of_range' => 'CIDR notation only allows masks between /25 and /32.', ], - 'service' => [ - 'delete_has_servers' => 'A service with active servers attached to it cannot be deleted from the Panel.', - 'options' => [ - 'delete_has_servers' => 'A service option with active servers attached to it cannot be deleted from the Panel.', - 'invalid_copy_id' => 'The service option selected for copying a script from either does not exist, or is copying a script itself.', - 'must_be_child' => 'The "Copy Settings From" directive for this option must be a child option for the selected service.', - 'has_children' => 'This service option is a parent to one or more other options. Please delete those options before deleting this option.', + 'nest' => [ + 'delete_has_servers' => 'A Nest with active servers attached to it cannot be deleted from the Panel.', + 'egg' => [ + 'delete_has_servers' => 'An Egg with active servers attached to it cannot be deleted from the Panel.', + 'invalid_copy_id' => 'The Egg selected for copying a script from either does not exist, or is copying a script itself.', + 'must_be_child' => 'The "Copy Settings From" directive for this Egg must be a child option for the selected Nest.', + 'has_children' => 'This Egg is a parent to one or more other Eggs. Please delete those Eggs before deleting this Egg.', ], 'variables' => [ - 'env_not_unique' => 'The environment variable :name must be unique to this service option.', + 'env_not_unique' => 'The environment variable :name must be unique to this Egg.', 'reserved_name' => 'The environment variable :name is protected and cannot be assigned to a variable.', ], + 'importer' => [ + 'json_error' => 'There was an error while attempting to parse the JSON file: :error.', + 'file_error' => 'The JSON file provided was not valid.', + 'invalid_json_provided' => 'The JSON file provided is not in a format that can be recognized.', + ], ], 'packs' => [ 'delete_has_servers' => 'Cannot delete a pack that is attached to active servers.', diff --git a/resources/themes/pterodactyl/admin/services/options/new.blade.php b/resources/themes/pterodactyl/admin/eggs/new.blade.php similarity index 73% rename from resources/themes/pterodactyl/admin/services/options/new.blade.php rename to resources/themes/pterodactyl/admin/eggs/new.blade.php index 2a5f3da9f..e432090f3 100644 --- a/resources/themes/pterodactyl/admin/services/options/new.blade.php +++ b/resources/themes/pterodactyl/admin/eggs/new.blade.php @@ -6,20 +6,20 @@ @extends('layouts.admin') @section('title') - Service → New Option + Nests → New Egg @endsection @section('content-header') -

New OptionCreate a new service option to assign to servers.

+

New EggCreate a new Egg to assign to servers.

@endsection @section('content') -
+
@@ -30,39 +30,37 @@
- - + +
+ +

Think of a Nest as a category. You can put multiple Eggs in a nest, but consider putting only Eggs that are related to eachother in each Nest.

+
- + -

A simple, human-readable name to use as an identifier for this service.

+

A simple, human-readable name to use as an identifier for this Egg. This is what users will see as thier gameserver type.

-

A description of this service that will be displayed throughout the panel as needed.

+

A description of this Egg.

- - -

This should be a unique identifer for this service option that is not used for any other service options. Must be alpha-numeric and no more than 60 characters in length.

-
-
- + -

The default docker image that should be used for new servers under this service option. This can be left blank to use the parent service's defined image, and can also be changed per-server.

+

The default docker image that should be used for new servers using this Egg. This can be changed per-server.

- - -

The default statup command that should be used for new servers under this service option. This can be left blank to use the parent service's startup, and can also be changed per-server.

+ + +

The default statup command that should be used for new servers created with this Egg. You can change this per-server as needed.

@@ -87,7 +85,7 @@ -

If you would like to default to settings from another option select the option from the menu above.

+

If you would like to default to settings from another Egg select it from the dropdown above.

@@ -115,7 +113,7 @@
@@ -128,15 +126,15 @@ {!! Theme::js('vendor/lodash/lodash.js') !!} +@endsection diff --git a/resources/themes/pterodactyl/admin/nests/new.blade.php b/resources/themes/pterodactyl/admin/nests/new.blade.php new file mode 100644 index 000000000..ed7fa3cdd --- /dev/null +++ b/resources/themes/pterodactyl/admin/nests/new.blade.php @@ -0,0 +1,52 @@ +{{-- Pterodactyl - Panel --}} +{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} + +{{-- This software is licensed under the terms of the MIT license. --}} +{{-- https://opensource.org/licenses/MIT --}} +@extends('layouts.admin') + +@section('title') + New Nest +@endsection + +@section('content-header') +

New NestConfigure a new nest to deploy to all nodes.

+ +@endsection + +@section('content') + +
+
+
+
+

New Nest

+
+
+
+ +
+ +

This should be a descriptive category name that emcompasses all of the eggs within the nest.

+
+
+
+ +
+ +
+
+
+ +
+
+
+ +@endsection diff --git a/resources/themes/pterodactyl/admin/nests/view.blade.php b/resources/themes/pterodactyl/admin/nests/view.blade.php new file mode 100644 index 000000000..2f30932fa --- /dev/null +++ b/resources/themes/pterodactyl/admin/nests/view.blade.php @@ -0,0 +1,113 @@ +{{-- Pterodactyl - Panel --}} +{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} + +{{-- This software is licensed under the terms of the MIT license. --}} +{{-- https://opensource.org/licenses/MIT --}} +@extends('layouts.admin') + +@section('title') + Nests → {{ $nest->name }} +@endsection + +@section('content-header') +

{{ $nest->name }}{{ str_limit($nest->description, 50) }}

+ +@endsection + +@section('content') +
+
+
+
+
+
+ +
+ +

This should be a descriptive category name that emcompasses all of the options within the service.

+
+
+
+ +
+ +
+
+
+ +
+
+
+
+
+
+
+ +
+ +

The author of this service option. Please direct questions and issues to them unless this is an official option authored by support@pterodactyl.io.

+
+
+
+ +
+ +

A unique identifier that all servers using this option are assigned for identification purposes.

+
+
+
+
+
+
+
+
+
+
+

Nest Eggs

+
+
+ + + + + + + + @foreach($nest->eggs as $egg) + + + + + + + @endforeach +
NameDescriptionServers
{{ $egg->name }}{!! $egg->description !!}{{ $egg->servers->count() }} + +
+
+ +
+
+
+@endsection + +@section('footer-scripts') + @parent + +@endsection diff --git a/resources/themes/pterodactyl/admin/nodes/view/servers.blade.php b/resources/themes/pterodactyl/admin/nodes/view/servers.blade.php index ad1944f9a..b648c760c 100644 --- a/resources/themes/pterodactyl/admin/nodes/view/servers.blade.php +++ b/resources/themes/pterodactyl/admin/nodes/view/servers.blade.php @@ -56,7 +56,7 @@ {{ $server->uuidShort }} {{ $server->name }} {{ $server->user->username }} - {{ $server->service->name }} ({{ $server->option->name }}) + {{ $server->nest->name }} ({{ $server->egg->name }}) NaN / {{ $server->memory === 0 ? '∞' : $server->memory }} MB {{ $server->disk }} MB NaN % diff --git a/resources/themes/pterodactyl/admin/packs/index.blade.php b/resources/themes/pterodactyl/admin/packs/index.blade.php index e7b22e0d6..270bea96b 100644 --- a/resources/themes/pterodactyl/admin/packs/index.blade.php +++ b/resources/themes/pterodactyl/admin/packs/index.blade.php @@ -43,7 +43,7 @@ Pack Name Version Description - Option + Egg Servers @foreach ($packs as $pack) @@ -52,7 +52,7 @@ {{ $pack->name }} {{ $pack->version }} {{ str_limit($pack->description, 150) }} - {{ $pack->option->name }} + {{ $pack->egg->name }} {{ $pack->servers_count }} @endforeach diff --git a/resources/themes/pterodactyl/admin/packs/modal.blade.php b/resources/themes/pterodactyl/admin/packs/modal.blade.php index fd4263575..2ce57f2b1 100644 --- a/resources/themes/pterodactyl/admin/packs/modal.blade.php +++ b/resources/themes/pterodactyl/admin/packs/modal.blade.php @@ -10,17 +10,17 @@
- - + @foreach($nests as $nest) + + @foreach($nest->eggs as $egg) + @endforeach @endforeach -

The option that this pack is assocaited with. Only servers that are assigned this option will be able to access this pack.

+

The Egg that this pack is assocaited with. Only servers that are assigned this Egg will be able to access this pack.

diff --git a/resources/themes/pterodactyl/admin/packs/new.blade.php b/resources/themes/pterodactyl/admin/packs/new.blade.php index fe89c904f..35acdb540 100644 --- a/resources/themes/pterodactyl/admin/packs/new.blade.php +++ b/resources/themes/pterodactyl/admin/packs/new.blade.php @@ -52,12 +52,12 @@

The version of this package, or the version of the files contained within the package.

- - + @foreach($nests as $nest) + + @foreach($nest->eggs as $egg) + @endforeach @endforeach @@ -124,7 +124,7 @@ @section('footer-scripts') @parent diff --git a/resources/themes/pterodactyl/admin/packs/view.blade.php b/resources/themes/pterodactyl/admin/packs/view.blade.php index 25a604d09..e9e5804e5 100644 --- a/resources/themes/pterodactyl/admin/packs/view.blade.php +++ b/resources/themes/pterodactyl/admin/packs/view.blade.php @@ -51,12 +51,12 @@
- - + @foreach($nests as $nest) + + @foreach($nest->eggs as $egg) + @endforeach @endforeach @@ -173,6 +173,6 @@ @section('footer-scripts') @parent @endsection diff --git a/resources/themes/pterodactyl/admin/servers/new.blade.php b/resources/themes/pterodactyl/admin/servers/new.blade.php index a564f6ada..09c14fcec 100644 --- a/resources/themes/pterodactyl/admin/servers/new.blade.php +++ b/resources/themes/pterodactyl/admin/servers/new.blade.php @@ -132,9 +132,7 @@
@@ -160,9 +158,7 @@
@@ -171,38 +167,38 @@
-

Service Configuration

+

Nest Configuration

- - + @foreach($nests as $nest) + + >{{ $nest->name }} @endforeach -

Select the type of service that this server will be running.

+

Select the Nest that this server will be grouped under.

- - -

Select the type of sub-service that this server will be running.

+ + +

Select the Egg that will define how this server should operate.

- + -

Select a service pack to be automatically installed on this server when first created.

+

Select a data pack to be automatically installed on this server when first created.

- +
-

If the selected Option has an install script attached to it, the script will run during install after the pack is installed. If you would like to skip this step, check this box.

+

If the selected Egg has an install script attached to it, the script will run during install after the pack is installed. If you would like to skip this step, check this box.

@@ -214,9 +210,9 @@
- + -

This is the default Docker container that will be used to run this server.

+

This is the default Docker image that will be used to run this server.

diff --git a/resources/themes/pterodactyl/admin/servers/view/details.blade.php b/resources/themes/pterodactyl/admin/servers/view/details.blade.php index 0bf06a31f..275f99ebd 100644 --- a/resources/themes/pterodactyl/admin/servers/view/details.blade.php +++ b/resources/themes/pterodactyl/admin/servers/view/details.blade.php @@ -91,7 +91,7 @@
-

The docker image to use for this server. The default image for this service and option combination is {{ $server->option->docker_image }}.

+

The docker image to use for this server. The default image for this service and option combination is {{ $server->egg->docker_image }}.

- - + @foreach($nests as $nest) + + >{{ $nest->name }} @endforeach -

Select the type of service that this server will be running.

+

Select the Nest that this server will be grouped into.

- - -

Select the type of sub-service that this server will be running.

+ + +

Select the Egg that will provide processing data for this server.

- + -

Select a service pack to be automatically installed on this server when first created.

+

Select a data pack to be automatically installed on this server when first created.

skip_scripts) checked @endif /> - +
-

If the selected Option has an install script attached to it, the script will run during install after the pack is installed. If you would like to skip this step, check this box.

+

If the selected Egg has an install script attached to it, the script will run during install after the pack is installed. If you would like to skip this step, check this box.

@@ -122,11 +122,11 @@ {!! Theme::js('vendor/lodash/lodash.js') !!} -@endsection diff --git a/resources/themes/pterodactyl/admin/services/index.blade.php b/resources/themes/pterodactyl/admin/services/index.blade.php deleted file mode 100644 index cd336f9b7..000000000 --- a/resources/themes/pterodactyl/admin/services/index.blade.php +++ /dev/null @@ -1,53 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} -@extends('layouts.admin') - -@section('title') - Service -@endsection - -@section('content-header') -

ServiceAll services currently available on this system.

- -@endsection - -@section('content') -
-
-
-
-

Configured Service

- -
-
- - - - - - - - - @foreach($services as $service) - - - - - - - - @endforeach -
NameDescriptionOptionsPacksServers
{{ $service->name }}{{ $service->description }}{{ $service->options_count }}{{ $service->packs_count }}{{ $service->servers_count }}
-
-
-
-
-@endsection diff --git a/resources/themes/pterodactyl/admin/services/new.blade.php b/resources/themes/pterodactyl/admin/services/new.blade.php deleted file mode 100644 index 864dadbae..000000000 --- a/resources/themes/pterodactyl/admin/services/new.blade.php +++ /dev/null @@ -1,72 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} -@extends('layouts.admin') - -@section('title') - New Service -@endsection - -@section('content-header') -

New ServiceConfigure a new service to deploy to all nodes.

- -@endsection - -@section('content') -
-
-
-
-
-

New Service

-
-
-
- -
- -

This should be a descriptive category name that emcompasses all of the options within the service.

-
-
-
- -
- -
-
-
-
-
-
-
-
-
- -
- -

Service are downloaded by the daemon and stored in a folder using this name. The storage location is /srv/daemon/services/{NAME} by default.

-
-
-
- -
- -

The default start command to use when running options under this service. This command can be modified per-option and should include the executable to be called in the container.

-
-
-
- -
-
-
-
-@endsection diff --git a/resources/themes/pterodactyl/admin/services/view.blade.php b/resources/themes/pterodactyl/admin/services/view.blade.php deleted file mode 100644 index f8461e7d2..000000000 --- a/resources/themes/pterodactyl/admin/services/view.blade.php +++ /dev/null @@ -1,121 +0,0 @@ -{{-- Pterodactyl - Panel --}} -{{-- Copyright (c) 2015 - 2017 Dane Everitt --}} - -{{-- This software is licensed under the terms of the MIT license. --}} -{{-- https://opensource.org/licenses/MIT --}} -@extends('layouts.admin') - -@section('title') - Service → {{ $service->name }} -@endsection - -@section('content-header') -

{{ $service->name }}{{ str_limit($service->description, 50) }}

- -@endsection - -@section('content') -
-
- -
-
-
-
-
-
-
-
- -
- -

This should be a descriptive category name that emcompasses all of the options within the service.

-
-
-
- -
- -
-
-
-
-
-
-
-
-
- -
- -

Service are downloaded by the daemon and stored in a folder using this name. The storage location is /srv/daemon/services/{NAME} by default.

-
-
-
- -
- -

The default start command to use when running options under this service. This command can be modified per-option and should include the executable to be called in the container.

-
-
-
- -
-
-
-
-
-
-
-
-

Configured Options

-
-
- - - - - - - - @foreach($service->options as $option) - - - - - - - @endforeach -
NameDescriptionTagServers
{{ $option->name }}{!! $option->description !!}{{ $option->tag }}{{ $option->servers->count() }}
-
- -
-
-
-@endsection - -@section('footer-scripts') - @parent - -@endsection diff --git a/resources/themes/pterodactyl/admin/users/view.blade.php b/resources/themes/pterodactyl/admin/users/view.blade.php index 755255505..1c1946ecb 100644 --- a/resources/themes/pterodactyl/admin/users/view.blade.php +++ b/resources/themes/pterodactyl/admin/users/view.blade.php @@ -113,22 +113,23 @@ - @foreach($user->setAccessLevel('subuser')->access()->get() as $server) - - - {{ $server->uuidShort }} - {{ $server->name }} - - @if($server->owner_id === $user->id) - Owner - @else - Subuser - @endif - - {{ $server->node->name }} - @if($server->suspended === 0)Active@elseSuspended@endif - - @endforeach + Oh dear, this hasn't been fixed yet? + {{--@foreach($user->setAccessLevel('subuser')->access()->get() as $server)--}} + {{----}} + {{----}} + {{--{{ $server->uuidShort }}--}} + {{--{{ $server->name }}--}} + {{----}} + {{--@if($server->owner_id === $user->id)--}} + {{--Owner--}} + {{--@else--}} + {{--Subuser--}} + {{--@endif--}} + {{----}} + {{--{{ $server->node->name }}--}} + {{--@if($server->suspended === 0)Active@elseSuspended@endif--}} + {{----}} + {{--@endforeach--}} diff --git a/resources/themes/pterodactyl/layouts/admin.blade.php b/resources/themes/pterodactyl/layouts/admin.blade.php index bfd97b439..6fe6552a4 100644 --- a/resources/themes/pterodactyl/layouts/admin.blade.php +++ b/resources/themes/pterodactyl/layouts/admin.blade.php @@ -112,9 +112,9 @@
  • SERVICE MANAGEMENT
  • -
  • - - Service +
  • + + Nests
  • @@ -197,6 +197,12 @@ }); @endif + + @show diff --git a/resources/themes/pterodactyl/server/index.blade.php b/resources/themes/pterodactyl/server/index.blade.php index be68ffc88..6470a7703 100644 --- a/resources/themes/pterodactyl/server/index.blade.php +++ b/resources/themes/pterodactyl/server/index.blade.php @@ -79,7 +79,7 @@ {!! Theme::js('js/frontend/console.js') !!} {!! Theme::js('vendor/chartjs/chart.min.js') !!} {!! Theme::js('vendor/jquery/date-format.min.js') !!} - @if($server->service->folder === 'minecraft') + @if($server->nest->name === 'Minecraft' && $server->nest->author === 'support@pterodactyl.io') {!! Theme::js('js/plugins/minecraft/eula.js') !!} @endif @endsection diff --git a/routes/admin.php b/routes/admin.php index d5f16e813..da1451cb9 100644 --- a/routes/admin.php +++ b/routes/admin.php @@ -142,35 +142,37 @@ Route::group(['prefix' => 'nodes'], function () { /* |-------------------------------------------------------------------------- -| Service Controller Routes +| Nest Controller Routes |-------------------------------------------------------------------------- | -| Endpoint: /admin/services +| Endpoint: /admin/nests | */ -Route::group(['prefix' => 'services'], function () { - Route::get('/', 'ServiceController@index')->name('admin.services'); - Route::get('/new', 'ServiceController@create')->name('admin.services.new'); - Route::get('/view/{service}', 'ServiceController@view')->name('admin.services.view'); - Route::get('/view/{service}/functions', 'ServiceController@viewFunctions')->name('admin.services.view.functions'); - Route::get('/option/new', 'OptionController@create')->name('admin.services.option.new'); - Route::get('/option/{option}', 'OptionController@viewConfiguration')->name('admin.services.option.view'); - Route::get('/option/{option}/variables', 'VariableController@view')->name('admin.services.option.variables'); - Route::get('/option/{option}/scripts', 'OptionController@viewScripts')->name('admin.services.option.scripts'); +Route::group(['prefix' => 'nests'], function () { + Route::get('/', 'Nests\NestController@index')->name('admin.nests'); + Route::get('/new', 'Nests\NestController@create')->name('admin.nests.new'); + Route::get('/view/{nest}', 'Nests\NestController@view')->name('admin.nests.view'); + Route::get('/egg/new', 'Nests\EggController@create')->name('admin.nests.egg.new'); + Route::get('/egg/{egg}', 'Nests\EggController@view')->name('admin.nests.egg.view'); + Route::get('/egg/{egg}/export', 'Nests\EggShareController@export')->name('admin.nests.egg.export'); + Route::get('/egg/{egg}/variables', 'Nests\EggVariableController@view')->name('admin.nests.egg.variables'); + Route::get('/egg/{egg}/scripts', 'Nests\EggScriptController@index')->name('admin.nests.egg.scripts'); - Route::post('/new', 'ServiceController@store'); - Route::post('/option/new', 'OptionController@store'); - Route::post('/option/{option}/variables', 'VariableController@store'); + Route::post('/new', 'Nests\NestController@store'); + Route::post('/import', 'Nests\EggShareController@import')->name('admin.nests.egg.import'); + Route::post('/egg/new', 'Nests\EggController@store'); + Route::post('/egg/{egg}/variables', 'Nests\EggVariableController@store'); - Route::patch('/view/{service}', 'ServiceController@update'); - Route::patch('/view/{service}/functions', 'ServiceController@updateFunctions'); - Route::patch('/option/{option}', 'OptionController@editConfiguration'); - Route::patch('/option/{option}/scripts', 'OptionController@updateScripts'); - Route::patch('/option/{option}/variables/{variable}', 'VariableController@update')->name('admin.services.option.variables.edit'); + Route::put('/egg/{egg}', 'Nests\EggShareController@update'); - Route::delete('/view/{service}', 'ServiceController@destroy'); - Route::delete('/option/{option}', 'OptionController@destroy'); - Route::delete('/option/{option}/variables/{variable}', 'VariableController@delete'); + Route::patch('/view/{nest}', 'Nests\NestController@update'); + Route::patch('/egg/{egg}', 'Nests\EggController@update'); + Route::patch('/egg/{egg}/scripts', 'Nests\EggScriptController@update'); + Route::patch('/egg/{egg}/variables/{variable}', 'Nests\EggVariableController@update')->name('admin.nests.egg.variables.edit'); + + Route::delete('/view/{nest}', 'Nests\NestController@destroy'); + Route::delete('/egg/{egg}', 'Nests\EggController@destroy'); + Route::delete('/egg/{egg}/variables/{variable}', 'Nests\EggVariableController@destroy'); }); /* diff --git a/routes/api-admin.php b/routes/api-admin.php index 81fe456a9..a36adf3b4 100644 --- a/routes/api-admin.php +++ b/routes/api-admin.php @@ -1,97 +1,97 @@ . * * This software is licensed under the terms of the MIT license. * https://opensource.org/licenses/MIT */ -Route::get('/', 'CoreController@index'); - -/* -|-------------------------------------------------------------------------- -| Server Controller Routes -|-------------------------------------------------------------------------- -| -| Endpoint: /api/admin/servers -| -*/ -Route::group(['prefix' => '/servers'], function () { - Route::get('/', 'ServerController@index'); - Route::get('/{id}', 'ServerController@view'); - - Route::post('/', 'ServerController@store'); - - Route::put('/{id}/details', 'ServerController@details'); - Route::put('/{id}/container', 'ServerController@container'); - Route::put('/{id}/build', 'ServerController@build'); - Route::put('/{id}/startup', 'ServerController@startup'); - - Route::patch('/{id}/install', 'ServerController@install'); - Route::patch('/{id}/rebuild', 'ServerController@rebuild'); - Route::patch('/{id}/suspend', 'ServerController@suspend'); - - Route::delete('/{id}', 'ServerController@delete'); -}); - -/* -|-------------------------------------------------------------------------- -| Location Controller Routes -|-------------------------------------------------------------------------- -| -| Endpoint: /api/admin/locations -| -*/ -Route::group(['prefix' => '/locations'], function () { - Route::get('/', 'LocationController@index'); -}); - -/* -|-------------------------------------------------------------------------- -| Node Controller Routes -|-------------------------------------------------------------------------- -| -| Endpoint: /api/admin/nodes -| -*/ -Route::group(['prefix' => '/nodes'], function () { - Route::get('/', 'NodeController@index'); - Route::get('/{id}', 'NodeController@view'); - Route::get('/{id}/config', 'NodeController@viewConfig'); - - Route::post('/', 'NodeController@store'); - - Route::delete('/{id}', 'NodeController@delete'); -}); - -/* -|-------------------------------------------------------------------------- -| User Controller Routes -|-------------------------------------------------------------------------- -| -| Endpoint: /api/admin/users -| -*/ -Route::group(['prefix' => '/users'], function () { - Route::get('/', 'UserController@index'); - Route::get('/{id}', 'UserController@view'); - - Route::post('/', 'UserController@store'); - - Route::put('/{id}', 'UserController@update'); - - Route::delete('/{id}', 'UserController@delete'); -}); - -/* -|-------------------------------------------------------------------------- -| Service Controller Routes -|-------------------------------------------------------------------------- -| -| Endpoint: /api/admin/services -| -*/ -Route::group(['prefix' => '/services'], function () { - Route::get('/', 'ServiceController@index'); - Route::get('/{id}', 'ServiceController@view'); -}); +//Route::get('/', 'CoreController@index'); +// +///* +//|-------------------------------------------------------------------------- +//| Server Controller Routes +//|-------------------------------------------------------------------------- +//| +//| Endpoint: /api/admin/servers +//| +//*/ +//Route::group(['prefix' => '/servers'], function () { +// Route::get('/', 'ServerController@index'); +// Route::get('/{id}', 'ServerController@view'); +// +// Route::post('/', 'ServerController@store'); +// +// Route::put('/{id}/details', 'ServerController@details'); +// Route::put('/{id}/container', 'ServerController@container'); +// Route::put('/{id}/build', 'ServerController@build'); +// Route::put('/{id}/startup', 'ServerController@startup'); +// +// Route::patch('/{id}/install', 'ServerController@install'); +// Route::patch('/{id}/rebuild', 'ServerController@rebuild'); +// Route::patch('/{id}/suspend', 'ServerController@suspend'); +// +// Route::delete('/{id}', 'ServerController@delete'); +//}); +// +///* +//|-------------------------------------------------------------------------- +//| Location Controller Routes +//|-------------------------------------------------------------------------- +//| +//| Endpoint: /api/admin/locations +//| +//*/ +//Route::group(['prefix' => '/locations'], function () { +// Route::get('/', 'LocationController@index'); +//}); +// +///* +//|-------------------------------------------------------------------------- +//| Node Controller Routes +//|-------------------------------------------------------------------------- +//| +//| Endpoint: /api/admin/nodes +//| +//*/ +//Route::group(['prefix' => '/nodes'], function () { +// Route::get('/', 'NodeController@index'); +// Route::get('/{id}', 'NodeController@view'); +// Route::get('/{id}/config', 'NodeController@viewConfig'); +// +// Route::post('/', 'NodeController@store'); +// +// Route::delete('/{id}', 'NodeController@delete'); +//}); +// +///* +//|-------------------------------------------------------------------------- +//| User Controller Routes +//|-------------------------------------------------------------------------- +//| +//| Endpoint: /api/admin/users +//| +//*/ +//Route::group(['prefix' => '/users'], function () { +// Route::get('/', 'UserController@index'); +// Route::get('/{id}', 'UserController@view'); +// +// Route::post('/', 'UserController@store'); +// +// Route::put('/{id}', 'UserController@update'); +// +// Route::delete('/{id}', 'UserController@delete'); +//}); +// +///* +//|-------------------------------------------------------------------------- +//| Service Controller Routes +//|-------------------------------------------------------------------------- +//| +//| Endpoint: /api/admin/services +//| +//*/ +//Route::group(['prefix' => '/services'], function () { +// Route::get('/', 'ServiceController@index'); +// Route::get('/{id}', 'ServiceController@view'); +//}); diff --git a/routes/api-remote.php b/routes/api-remote.php index 54e5d8da4..28f1edb38 100644 --- a/routes/api-remote.php +++ b/routes/api-remote.php @@ -6,4 +6,9 @@ * This software is licensed under the terms of the MIT license. * https://opensource.org/licenses/MIT */ -Route::get('/authenticate/{token}', 'ValidateKeyController@index')->name('post.api.remote.authenticate'); +Route::get('/authenticate/{token}', 'ValidateKeyController@index')->name('api.remote.authenticate'); + +Route::group(['prefix' => '/eggs'], function () { + Route::get('/', 'EggRetrievalController@index')->name('api.remote.eggs'); + Route::get('/{uuid}', 'EggRetrievalController@download')->name('api.remote.eggs.download'); +}); diff --git a/routes/api.php b/routes/api.php index 655fe066c..96dfe5dde 100644 --- a/routes/api.php +++ b/routes/api.php @@ -1,27 +1,27 @@ . * * This software is licensed under the terms of the MIT license. * https://opensource.org/licenses/MIT */ -Route::get('/', 'CoreController@index')->name('api.user'); - -/* -|-------------------------------------------------------------------------- -| Server Controller Routes -|-------------------------------------------------------------------------- -| -| Endpoint: /api/user/server/{server} -| -*/ -Route::group([ - 'prefix' => '/server/{server}', - 'middleware' => 'server', -], function () { - Route::get('/', 'ServerController@index')->name('api.user.server'); - - Route::post('/power', 'ServerController@power')->name('api.user.server.power'); - Route::post('/command', 'ServerController@command')->name('api.user.server.command'); -}); +//Route::get('/', 'CoreController@index')->name('api.user'); +// +///* +//|-------------------------------------------------------------------------- +//| Server Controller Routes +//|-------------------------------------------------------------------------- +//| +//| Endpoint: /api/user/server/{server} +//| +//*/ +//Route::group([ +// 'prefix' => '/server/{server}', +// 'middleware' => 'server', +//], function () { +// Route::get('/', 'ServerController@index')->name('api.user.server'); +// +// Route::post('/power', 'ServerController@power')->name('api.user.server.power'); +// Route::post('/command', 'ServerController@command')->name('api.user.server.command'); +//}); diff --git a/routes/daemon.php b/routes/daemon.php index e6d34e971..96dd4e682 100644 --- a/routes/daemon.php +++ b/routes/daemon.php @@ -6,8 +6,6 @@ * This software is licensed under the terms of the MIT license. * https://opensource.org/licenses/MIT */ -Route::get('/services', 'ServiceController@listServices')->name('daemon.services'); -Route::get('/services/pull/{service}/{file}', 'ServiceController@pull')->name('daemon.pull'); Route::get('/packs/pull/{uuid}', 'PackController@pull')->name('daemon.pack.pull'); Route::get('/packs/pull/{uuid}/hash', 'PackController@hash')->name('daemon.pack.hash'); Route::get('/details/option/{server}', 'OptionController@details')->name('daemon.option.details'); diff --git a/tests/Assertions/NestedObjectAssertionsTrait.php b/tests/Assertions/NestedObjectAssertionsTrait.php new file mode 100644 index 000000000..b402696d8 --- /dev/null +++ b/tests/Assertions/NestedObjectAssertionsTrait.php @@ -0,0 +1,47 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Assertions; + +use PHPUnit\Framework\Assert; +use PHPUnit_Util_InvalidArgumentHelper; + +trait NestedObjectAssertionsTrait +{ + /** + * Assert that an object value matches an expected value. + * + * @param string $key + * @param mixed $expected + * @param object $object + */ + public function assertObjectNestedValueEquals(string $key, $expected, $object) + { + if (! is_object($object)) { + throw PHPUnit_Util_InvalidArgumentHelper::factory(3, 'object'); + } + + Assert::assertEquals($expected, object_get_strict($object, $key, '__TEST_FAILURE'), 'Assert that an object value equals a provided value.'); + } + + /** + * Assert that an object contains a nested key. + * + * @param string $key + * @param object $object + */ + public function assertObjectHasNestedAttribute(string $key, $object) + { + if (! is_object($object)) { + throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'object'); + } + + Assert::assertNotEquals('__TEST_FAILURE', object_get_strict($object, $key, '__TEST_FAILURE'), 'Assert that an object contains a nested key.'); + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php index 664e4a9c3..5f9ba7482 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -8,8 +8,22 @@ abstract class TestCase extends BaseTestCase { use CreatesApplication; + /** + * Setup tests. + */ public function setUp() { parent::setUp(); + + $this->setKnownUuidFactory(); + } + + /** + * Handles the known UUID handling in certain unit tests. Use the "KnownUuid" trait + * in order to enable this ability. + */ + public function setKnownUuidFactory() + { + // do nothing } } diff --git a/tests/Traits/MocksUuids.php b/tests/Traits/MocksUuids.php new file mode 100644 index 000000000..af57f93a8 --- /dev/null +++ b/tests/Traits/MocksUuids.php @@ -0,0 +1,47 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Traits; + +use Mockery as m; +use Ramsey\Uuid\Uuid; +use Ramsey\Uuid\UuidFactory; + +trait MocksUuids +{ + /** + * The known UUID string. + * + * @var string + */ + protected $knownUuid = 'ffb5c3a6-ab17-43ab-97f0-8ff37ccd7f5f'; + + /** + * Setup a factory mock to produce the same UUID whenever called. + */ + public function setKnownUuidFactory() + { + $uuid = Uuid::fromString($this->getKnownUuid()); + $factoryMock = m::mock(UuidFactory::class . '[uuid4]', [ + 'uuid4' => $uuid, + ]); + + Uuid::setFactory($factoryMock); + } + + /** + * Returns the known UUID for tests to use. + * + * @return string + */ + public function getKnownUuid(): string + { + return $this->knownUuid; + } +} diff --git a/tests/Unit/Services/Eggs/EggConfigurationServiceTest.php b/tests/Unit/Services/Eggs/EggConfigurationServiceTest.php new file mode 100644 index 000000000..1dd124ab4 --- /dev/null +++ b/tests/Unit/Services/Eggs/EggConfigurationServiceTest.php @@ -0,0 +1,90 @@ +repository = m::mock(EggRepositoryInterface::class); + + $this->service = new EggConfigurationService($this->repository); + } + + /** + * Test that the correct array is returned. + */ + public function testCorrectArrayIsReturned() + { + $egg = factory(Egg::class)->make([ + 'config_startup' => '{"test": "start"}', + 'config_stop' => 'test', + 'config_files' => '{"test": "file"}', + 'config_logs' => '{"test": "logs"}', + ]); + + $response = $this->service->handle($egg); + $this->assertNotEmpty($response); + $this->assertTrue(is_array($response), 'Assert response is an array.'); + $this->assertArrayHasKey('startup', $response); + $this->assertArrayHasKey('stop', $response); + $this->assertArrayHasKey('configs', $response); + $this->assertArrayHasKey('log', $response); + $this->assertArrayHasKey('query', $response); + $this->assertEquals('start', object_get($response['startup'], 'test')); + $this->assertEquals('test', 'test'); + $this->assertEquals('file', object_get($response['configs'], 'test')); + $this->assertEquals('logs', object_get($response['log'], 'test')); + $this->assertEquals('none', $response['query']); + } + + /** + * Test that an integer referencing a model can be passed in place of the model. + */ + public function testFunctionHandlesIntegerPassedInPlaceOfModel() + { + $egg = factory(Egg::class)->make([ + 'config_startup' => '{"test": "start"}', + 'config_stop' => 'test', + 'config_files' => '{"test": "file"}', + 'config_logs' => '{"test": "logs"}', + ]); + + $this->repository->shouldReceive('getWithCopyAttributes')->with($egg->id)->once()->andReturn($egg); + + $response = $this->service->handle($egg->id); + $this->assertNotEmpty($response); + $this->assertTrue(is_array($response), 'Assert response is an array.'); + $this->assertArrayHasKey('startup', $response); + $this->assertArrayHasKey('stop', $response); + $this->assertArrayHasKey('configs', $response); + $this->assertArrayHasKey('log', $response); + $this->assertArrayHasKey('query', $response); + $this->assertEquals('start', object_get($response['startup'], 'test')); + $this->assertEquals('test', 'test'); + $this->assertEquals('file', object_get($response['configs'], 'test')); + $this->assertEquals('logs', object_get($response['log'], 'test')); + $this->assertEquals('none', $response['query']); + } +} diff --git a/tests/Unit/Services/Eggs/EggCreationServiceTest.php b/tests/Unit/Services/Eggs/EggCreationServiceTest.php new file mode 100644 index 000000000..7afa07871 --- /dev/null +++ b/tests/Unit/Services/Eggs/EggCreationServiceTest.php @@ -0,0 +1,146 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Unit\Services\Services\Options; + +use Exception; +use Mockery as m; +use Tests\TestCase; +use Pterodactyl\Models\Egg; +use Tests\Traits\MocksUuids; +use Illuminate\Contracts\Config\Repository; +use Pterodactyl\Exceptions\PterodactylException; +use Pterodactyl\Services\Eggs\EggCreationService; +use Pterodactyl\Contracts\Repository\EggRepositoryInterface; +use Pterodactyl\Exceptions\Service\Egg\NoParentConfigurationFoundException; + +class EggCreationServiceTest extends TestCase +{ + use MocksUuids; + + /** + * @var \Illuminate\Contracts\Config\Repository|\Mockery\Mock + */ + protected $config; + + /** + * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface|\Mockery\Mock + */ + protected $repository; + + /** + * @var \Pterodactyl\Services\Eggs\EggCreationService + */ + protected $service; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->config = m::mock(Repository::class); + $this->repository = m::mock(EggRepositoryInterface::class); + + $this->service = new EggCreationService($this->config, $this->repository); + } + + /** + * Test that a new model is created when not using the config from attribute. + */ + public function testCreateNewModelWithoutUsingConfigFrom() + { + $model = factory(Egg::class)->make(); + + $this->config->shouldReceive('get')->with('pterodactyl.service.author')->once()->andReturn('test@example.com'); + $this->repository->shouldReceive('create')->with([ + 'uuid' => $this->getKnownUuid(), + 'author' => 'test@example.com', + 'config_from' => null, + 'name' => $model->name, + ], true, true)->once()->andReturn($model); + + $response = $this->service->handle(['name' => $model->name]); + + $this->assertNotEmpty($response); + $this->assertNull(object_get($response, 'config_from')); + $this->assertEquals($model->name, $response->name); + } + + /** + * Test that a new model is created when using the config from attribute. + */ + public function testCreateNewModelUsingConfigFrom() + { + $model = factory(Egg::class)->make(); + + $this->repository->shouldReceive('findCountWhere')->with([ + ['nest_id', '=', $model->nest_id], + ['id', '=', 12345], + ])->once()->andReturn(1); + + $this->config->shouldReceive('get')->with('pterodactyl.service.author')->once()->andReturn('test@example.com'); + $this->repository->shouldReceive('create')->with([ + 'nest_id' => $model->nest_id, + 'config_from' => 12345, + 'uuid' => $this->getKnownUuid(), + 'author' => 'test@example.com', + ], true, true)->once()->andReturn($model); + + $response = $this->service->handle([ + 'nest_id' => $model->nest_id, + 'config_from' => 12345, + ]); + + $this->assertNotEmpty($response); + $this->assertEquals($response, $model); + } + + /** + * Test that certain data, such as the UUID or author takes priority over data + * that is passed into the function. + */ + public function testDataProvidedByHandlerTakesPriorityOverPassedData() + { + $model = factory(Egg::class)->make(); + + $this->config->shouldReceive('get')->with('pterodactyl.service.author')->once()->andReturn('test@example.com'); + $this->repository->shouldReceive('create')->with([ + 'uuid' => $this->getKnownUuid(), + 'author' => 'test@example.com', + 'config_from' => null, + 'name' => $model->name, + ], true, true)->once()->andReturn($model); + + $response = $this->service->handle(['name' => $model->name, 'uuid' => 'should-be-ignored', 'author' => 'should-be-ignored']); + + $this->assertNotEmpty($response); + $this->assertNull(object_get($response, 'config_from')); + $this->assertEquals($model->name, $response->name); + } + + /** + * Test that an exception is thrown if no parent configuration can be located. + */ + public function testExceptionIsThrownIfNoParentConfigurationIsFound() + { + $this->repository->shouldReceive('findCountWhere')->with([ + ['nest_id', '=', null], + ['id', '=', 1], + ])->once()->andReturn(0); + + try { + $this->service->handle(['config_from' => 1]); + } catch (PterodactylException $exception) { + $this->assertInstanceOf(NoParentConfigurationFoundException::class, $exception); + $this->assertEquals(trans('exceptions.nest.egg.must_be_child'), $exception->getMessage()); + } + } +} diff --git a/tests/Unit/Services/Services/Options/OptionDeletionServiceTest.php b/tests/Unit/Services/Eggs/EggDeletionServiceTest.php similarity index 57% rename from tests/Unit/Services/Services/Options/OptionDeletionServiceTest.php rename to tests/Unit/Services/Eggs/EggDeletionServiceTest.php index a0425bb3c..c3ef3ef1b 100644 --- a/tests/Unit/Services/Services/Options/OptionDeletionServiceTest.php +++ b/tests/Unit/Services/Eggs/EggDeletionServiceTest.php @@ -11,17 +11,17 @@ namespace Tests\Unit\Services\Services\Options; use Mockery as m; use Tests\TestCase; -use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Exceptions\PterodactylException; +use Pterodactyl\Services\Eggs\EggDeletionService; +use Pterodactyl\Contracts\Repository\EggRepositoryInterface; +use Pterodactyl\Exceptions\Service\Egg\HasChildrenException; use Pterodactyl\Exceptions\Service\HasActiveServersException; use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; -use Pterodactyl\Services\Services\Options\OptionDeletionService; -use Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface; -use Pterodactyl\Exceptions\Service\ServiceOption\HasChildrenException; -class OptionDeletionServiceTest extends TestCase +class EggDeletionServiceTest extends TestCase { /** - * @var \Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface|\Mockery\Mock + * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface|\Mockery\Mock */ protected $repository; @@ -31,7 +31,7 @@ class OptionDeletionServiceTest extends TestCase protected $serverRepository; /** - * @var \Pterodactyl\Services\Services\Options\OptionDeletionService + * @var \Pterodactyl\Services\Eggs\EggDeletionService */ protected $service; @@ -42,18 +42,18 @@ class OptionDeletionServiceTest extends TestCase { parent::setUp(); - $this->repository = m::mock(ServiceOptionRepositoryInterface::class); + $this->repository = m::mock(EggRepositoryInterface::class); $this->serverRepository = m::mock(ServerRepositoryInterface::class); - $this->service = new OptionDeletionService($this->serverRepository, $this->repository); + $this->service = new EggDeletionService($this->serverRepository, $this->repository); } /** - * Test that option is deleted if no servers are found. + * Test that Egg is deleted if no servers are found. */ - public function testOptionIsDeletedIfNoServersAreFound() + public function testEggIsDeletedIfNoServersAreFound() { - $this->serverRepository->shouldReceive('findCountWhere')->with([['option_id', '=', 1]])->once()->andReturn(0); + $this->serverRepository->shouldReceive('findCountWhere')->with([['egg_id', '=', 1]])->once()->andReturn(0); $this->repository->shouldReceive('findCountWhere')->with([['config_from', '=', 1]])->once()->andReturn(0); $this->repository->shouldReceive('delete')->with(1)->once()->andReturn(1); @@ -61,33 +61,33 @@ class OptionDeletionServiceTest extends TestCase } /** - * Test that option is not deleted if servers are found. + * Test that Egg is not deleted if servers are found. */ public function testExceptionIsThrownIfServersAreFound() { - $this->serverRepository->shouldReceive('findCountWhere')->with([['option_id', '=', 1]])->once()->andReturn(1); + $this->serverRepository->shouldReceive('findCountWhere')->with([['egg_id', '=', 1]])->once()->andReturn(1); try { $this->service->handle(1); - } catch (DisplayException $exception) { + } catch (PterodactylException $exception) { $this->assertInstanceOf(HasActiveServersException::class, $exception); - $this->assertEquals(trans('exceptions.service.options.delete_has_servers'), $exception->getMessage()); + $this->assertEquals(trans('exceptions.nest.egg.delete_has_servers'), $exception->getMessage()); } } /** - * Test that an exception is thrown if children options exist. + * Test that an exception is thrown if children Eggs exist. */ public function testExceptionIsThrownIfChildrenArePresent() { - $this->serverRepository->shouldReceive('findCountWhere')->with([['option_id', '=', 1]])->once()->andReturn(0); + $this->serverRepository->shouldReceive('findCountWhere')->with([['egg_id', '=', 1]])->once()->andReturn(0); $this->repository->shouldReceive('findCountWhere')->with([['config_from', '=', 1]])->once()->andReturn(1); try { $this->service->handle(1); - } catch (DisplayException $exception) { + } catch (PterodactylException $exception) { $this->assertInstanceOf(HasChildrenException::class, $exception); - $this->assertEquals(trans('exceptions.service.options.has_children'), $exception->getMessage()); + $this->assertEquals(trans('exceptions.nest.egg.has_children'), $exception->getMessage()); } } } diff --git a/tests/Unit/Services/Eggs/EggUpdateServiceTest.php b/tests/Unit/Services/Eggs/EggUpdateServiceTest.php new file mode 100644 index 000000000..89f02c49c --- /dev/null +++ b/tests/Unit/Services/Eggs/EggUpdateServiceTest.php @@ -0,0 +1,113 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Unit\Services\Services\Options; + +use Exception; +use Mockery as m; +use Tests\TestCase; +use Pterodactyl\Models\Egg; +use Pterodactyl\Services\Eggs\EggUpdateService; +use Pterodactyl\Exceptions\PterodactylException; +use Pterodactyl\Contracts\Repository\EggRepositoryInterface; +use Pterodactyl\Exceptions\Service\Egg\NoParentConfigurationFoundException; + +class EggUpdateServiceTest extends TestCase +{ + /** + * @var \Pterodactyl\Models\Egg + */ + protected $model; + + /** + * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface|\Mockery\Mock + */ + protected $repository; + + /** + * @var \Pterodactyl\Services\Eggs\EggUpdateService + */ + protected $service; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->model = factory(Egg::class)->make(); + $this->repository = m::mock(EggRepositoryInterface::class); + + $this->service = new EggUpdateService($this->repository); + } + + /** + * Test that an Egg is updated when no config_from attribute is passed. + */ + public function testEggIsUpdatedWhenNoConfigFromIsProvided() + { + $this->repository->shouldReceive('withoutFresh->update') + ->with($this->model->id, ['test_field' => 'field_value'])->once()->andReturnNull(); + + $this->service->handle($this->model, ['test_field' => 'field_value']); + + $this->assertTrue(true); + } + + /** + * Test that Egg is updated when a valid config_from attribute is passed. + */ + public function testOptionIsUpdatedWhenValidConfigFromIsPassed() + { + $this->repository->shouldReceive('findCountWhere')->with([ + ['nest_id', '=', $this->model->nest_id], + ['id', '=', 1], + ])->once()->andReturn(1); + + $this->repository->shouldReceive('withoutFresh->update') + ->with($this->model->id, ['config_from' => 1])->once()->andReturnNull(); + + $this->service->handle($this->model, ['config_from' => 1]); + + $this->assertTrue(true); + } + + /** + * Test that an exception is thrown if an invalid config_from attribute is passed. + */ + public function testExceptionIsThrownIfInvalidParentConfigIsPassed() + { + $this->repository->shouldReceive('findCountWhere')->with([ + ['nest_id', '=', $this->model->nest_id], + ['id', '=', 1], + ])->once()->andReturn(0); + + try { + $this->service->handle($this->model, ['config_from' => 1]); + } catch (PterodactylException $exception) { + $this->assertInstanceOf(NoParentConfigurationFoundException::class, $exception); + $this->assertEquals(trans('exceptions.nest.egg.must_be_child'), $exception->getMessage()); + } + } + + /** + * Test that an integer linking to a model can be passed in place of the Egg model. + */ + public function testIntegerCanBePassedInPlaceOfModel() + { + $this->repository->shouldReceive('find')->with($this->model->id)->once()->andReturn($this->model); + $this->repository->shouldReceive('withoutFresh->update') + ->with($this->model->id, ['test_field' => 'field_value'])->once()->andReturnNull(); + + $this->service->handle($this->model->id, ['test_field' => 'field_value']); + + $this->assertTrue(true); + } +} diff --git a/tests/Unit/Services/Services/Options/InstallScriptUpdateServiceTest.php b/tests/Unit/Services/Eggs/Scripts/InstallScriptServiceTest.php similarity index 74% rename from tests/Unit/Services/Services/Options/InstallScriptUpdateServiceTest.php rename to tests/Unit/Services/Eggs/Scripts/InstallScriptServiceTest.php index 165c831bc..ac29f44c4 100644 --- a/tests/Unit/Services/Services/Options/InstallScriptUpdateServiceTest.php +++ b/tests/Unit/Services/Eggs/Scripts/InstallScriptServiceTest.php @@ -12,12 +12,12 @@ namespace Tests\Unit\Services\Services\Options; use Exception; use Mockery as m; use Tests\TestCase; -use Pterodactyl\Models\ServiceOption; -use Pterodactyl\Services\Services\Options\InstallScriptUpdateService; -use Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface; -use Pterodactyl\Exceptions\Service\ServiceOption\InvalidCopyFromException; +use Pterodactyl\Models\Egg; +use Pterodactyl\Services\Eggs\Scripts\InstallScriptService; +use Pterodactyl\Contracts\Repository\EggRepositoryInterface; +use Pterodactyl\Exceptions\Service\Egg\InvalidCopyFromException; -class InstallScriptUpdateServiceTest extends TestCase +class InstallScriptServiceTest extends TestCase { /** * @var array @@ -31,17 +31,17 @@ class InstallScriptUpdateServiceTest extends TestCase ]; /** - * @var \Pterodactyl\Models\ServiceOption + * @var \Pterodactyl\Models\Egg */ protected $model; /** - * @var \Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface + * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface|\Mockery\Mock */ protected $repository; /** - * @var \Pterodactyl\Services\Services\Options\InstallScriptUpdateService + * @var \Pterodactyl\Services\Eggs\Scripts\InstallScriptService */ protected $service; @@ -52,10 +52,10 @@ class InstallScriptUpdateServiceTest extends TestCase { parent::setUp(); - $this->model = factory(ServiceOption::class)->make(); - $this->repository = m::mock(ServiceOptionRepositoryInterface::class); + $this->model = factory(Egg::class)->make(); + $this->repository = m::mock(EggRepositoryInterface::class); - $this->service = new InstallScriptUpdateService($this->repository); + $this->service = new InstallScriptService($this->repository); } /** @@ -65,7 +65,7 @@ class InstallScriptUpdateServiceTest extends TestCase { $this->data['copy_script_from'] = 1; - $this->repository->shouldReceive('isCopiableScript')->with(1, $this->model->service_id)->once()->andReturn(true); + $this->repository->shouldReceive('isCopiableScript')->with(1, $this->model->nest_id)->once()->andReturn(true); $this->repository->shouldReceive('withoutFresh')->withNoArgs()->once()->andReturnSelf() ->shouldReceive('update')->with($this->model->id, $this->data)->andReturnNull(); @@ -79,12 +79,12 @@ class InstallScriptUpdateServiceTest extends TestCase { $this->data['copy_script_from'] = 1; - $this->repository->shouldReceive('isCopiableScript')->with(1, $this->model->service_id)->once()->andReturn(false); + $this->repository->shouldReceive('isCopiableScript')->with(1, $this->model->nest_id)->once()->andReturn(false); try { $this->service->handle($this->model, $this->data); } catch (Exception $exception) { $this->assertInstanceOf(InvalidCopyFromException::class, $exception); - $this->assertEquals(trans('exceptions.service.options.invalid_copy_id'), $exception->getMessage()); + $this->assertEquals(trans('exceptions.nest.egg.invalid_copy_id'), $exception->getMessage()); } } diff --git a/tests/Unit/Services/Eggs/Sharing/EggExporterServiceTest.php b/tests/Unit/Services/Eggs/Sharing/EggExporterServiceTest.php new file mode 100644 index 000000000..c40531b97 --- /dev/null +++ b/tests/Unit/Services/Eggs/Sharing/EggExporterServiceTest.php @@ -0,0 +1,83 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Unit\Services\Eggs\Sharing; + +use Mockery as m; +use Carbon\Carbon; +use Tests\TestCase; +use Pterodactyl\Models\Egg; +use Pterodactyl\Models\EggVariable; +use Tests\Assertions\NestedObjectAssertionsTrait; +use Pterodactyl\Services\Eggs\Sharing\EggExporterService; +use Pterodactyl\Contracts\Repository\EggRepositoryInterface; + +class EggExporterServiceTest extends TestCase +{ + use NestedObjectAssertionsTrait; + + /** + * @var \Carbon\Carbon + */ + protected $carbon; + + /** + * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface|\Mockery\Mock + */ + protected $repository; + + /** + * @var \Pterodactyl\Services\Eggs\Sharing\EggExporterService + */ + protected $service; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + Carbon::setTestNow(Carbon::now()); + $this->carbon = new Carbon(); + $this->repository = m::mock(EggRepositoryInterface::class); + + $this->service = new EggExporterService($this->repository); + } + + /** + * Test that a JSON structure is returned. + */ + public function testJsonStructureIsExported() + { + $egg = factory(Egg::class)->make(); + $egg->variables = collect([$variable = factory(EggVariable::class)->make()]); + + $this->repository->shouldReceive('getWithExportAttributes')->with($egg->id)->once()->andReturn($egg); + + $response = $this->service->handle($egg->id); + $this->assertNotEmpty($response); + + $data = json_decode($response); + $this->assertEquals(JSON_ERROR_NONE, json_last_error()); + $this->assertObjectHasNestedAttribute('meta.version', $data); + $this->assertObjectNestedValueEquals('meta.version', 'PTDL_v1', $data); + $this->assertObjectHasNestedAttribute('author', $data); + $this->assertObjectNestedValueEquals('author', $egg->author, $data); + $this->assertObjectHasNestedAttribute('exported_at', $data); + $this->assertObjectNestedValueEquals('exported_at', Carbon::now()->toIso8601String(), $data); + $this->assertObjectHasNestedAttribute('scripts.installation.script', $data); + $this->assertObjectHasNestedAttribute('scripts.installation.container', $data); + $this->assertObjectHasNestedAttribute('scripts.installation.entrypoint', $data); + $this->assertObjectHasAttribute('variables', $data); + $this->assertArrayHasKey('0', $data->variables); + $this->assertObjectHasAttribute('name', $data->variables[0]); + $this->assertObjectNestedValueEquals('name', $variable->name, $data->variables[0]); + } +} diff --git a/tests/Unit/Services/Eggs/Sharing/EggImporterServiceTest.php b/tests/Unit/Services/Eggs/Sharing/EggImporterServiceTest.php new file mode 100644 index 000000000..db6618784 --- /dev/null +++ b/tests/Unit/Services/Eggs/Sharing/EggImporterServiceTest.php @@ -0,0 +1,189 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Unit\Services\Services\Sharing; + +use Mockery as m; +use Tests\TestCase; +use Pterodactyl\Models\Egg; +use Pterodactyl\Models\Nest; +use Tests\Traits\MocksUuids; +use Illuminate\Http\UploadedFile; +use Pterodactyl\Models\EggVariable; +use Illuminate\Database\ConnectionInterface; +use Pterodactyl\Exceptions\PterodactylException; +use Pterodactyl\Services\Eggs\Sharing\EggImporterService; +use Pterodactyl\Contracts\Repository\EggRepositoryInterface; +use Pterodactyl\Contracts\Repository\NestRepositoryInterface; +use Pterodactyl\Exceptions\Service\Egg\BadJsonFormatException; +use Pterodactyl\Exceptions\Service\InvalidFileUploadException; +use Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface; + +class EggImporterServiceTest extends TestCase +{ + use MocksUuids; + + /** + * @var \Illuminate\Database\ConnectionInterface|\Mockery\Mock + */ + protected $connection; + + /** + * @var \Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface|\Mockery\Mock + */ + protected $eggVariableRepository; + + /** + * @var \Illuminate\Http\UploadedFile|\Mockery\Mock + */ + protected $file; + + /** + * @var \Pterodactyl\Contracts\Repository\NestRepositoryInterface|\Mockery\Mock + */ + protected $nestRepository; + + /** + * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface|\Mockery\Mock + */ + protected $repository; + + /** + * @var \Pterodactyl\Services\Eggs\Sharing\EggImporterService + */ + protected $service; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->connection = m::mock(ConnectionInterface::class); + $this->eggVariableRepository = m::mock(EggVariableRepositoryInterface::class); + $this->file = m::mock(UploadedFile::class); + $this->nestRepository = m::mock(NestRepositoryInterface::class); + $this->repository = m::mock(EggRepositoryInterface::class); + + $this->service = new EggImporterService( + $this->connection, $this->repository, $this->eggVariableRepository, $this->nestRepository + ); + } + + /** + * Test that a service option can be successfully imported. + */ + public function testEggConfigurationIsImported() + { + $egg = factory(Egg::class)->make(); + $nest = factory(Nest::class)->make(); + + $this->file->shouldReceive('isValid')->withNoArgs()->once()->andReturn(true); + $this->file->shouldReceive('isFile')->withNoArgs()->once()->andReturn(true); + $this->file->shouldReceive('getSize')->withNoArgs()->once()->andReturn(100); + $this->file->shouldReceive('openFile->fread')->with(100)->once()->andReturn(json_encode([ + 'meta' => ['version' => 'PTDL_v1'], + 'name' => $egg->name, + 'author' => $egg->author, + 'variables' => [ + $variable = factory(EggVariable::class)->make(), + ], + ])); + $this->nestRepository->shouldReceive('getWithEggs')->with($nest->id)->once()->andReturn($nest); + + $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->repository->shouldReceive('create')->with(m::subset([ + 'uuid' => $this->getKnownUuid(), + 'nest_id' => $nest->id, + 'name' => $egg->name, + ]), true, true)->once()->andReturn($egg); + + $this->eggVariableRepository->shouldReceive('create')->with(m::subset([ + 'egg_id' => $egg->id, + 'env_variable' => $variable->env_variable, + ]))->once()->andReturnNull(); + $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); + + $response = $this->service->handle($this->file, $nest->id); + $this->assertNotEmpty($response); + $this->assertInstanceOf(Egg::class, $response); + $this->assertSame($egg, $response); + } + + /** + * Test that an exception is thrown if the file is invalid. + */ + public function testExceptionIsThrownIfFileIsInvalid() + { + $this->file->shouldReceive('isValid')->withNoArgs()->once()->andReturn(false); + try { + $this->service->handle($this->file, 1234); + } catch (PterodactylException $exception) { + $this->assertInstanceOf(InvalidFileUploadException::class, $exception); + $this->assertEquals(trans('exceptions.nest.importer.file_error'), $exception->getMessage()); + } + } + + /** + * Test that an exception is thrown if the file is not a file. + */ + public function testExceptionIsThrownIfFileIsNotAFile() + { + $this->file->shouldReceive('isValid')->withNoArgs()->once()->andReturn(true); + $this->file->shouldReceive('isFile')->withNoArgs()->once()->andReturn(false); + + try { + $this->service->handle($this->file, 1234); + } catch (PterodactylException $exception) { + $this->assertInstanceOf(InvalidFileUploadException::class, $exception); + $this->assertEquals(trans('exceptions.nest.importer.file_error'), $exception->getMessage()); + } + } + + /** + * Test that an exception is thrown if the JSON metadata is invalid. + */ + public function testExceptionIsThrownIfJsonMetaDataIsInvalid() + { + $this->file->shouldReceive('isValid')->withNoArgs()->once()->andReturn(true); + $this->file->shouldReceive('isFile')->withNoArgs()->once()->andReturn(true); + $this->file->shouldReceive('getSize')->withNoArgs()->once()->andReturn(100); + $this->file->shouldReceive('openFile->fread')->with(100)->once()->andReturn(json_encode([ + 'meta' => ['version' => 'hodor'], + ])); + + try { + $this->service->handle($this->file, 1234); + } catch (PterodactylException $exception) { + $this->assertInstanceOf(InvalidFileUploadException::class, $exception); + $this->assertEquals(trans('exceptions.nest.importer.invalid_json_provided'), $exception->getMessage()); + } + } + + /** + * Test that an exception is thrown if bad JSON is provided. + */ + public function testExceptionIsThrownIfBadJsonIsProvided() + { + $this->file->shouldReceive('isValid')->withNoArgs()->once()->andReturn(true); + $this->file->shouldReceive('isFile')->withNoArgs()->once()->andReturn(true); + $this->file->shouldReceive('getSize')->withNoArgs()->once()->andReturn(100); + $this->file->shouldReceive('openFile->fread')->with(100)->once()->andReturn('}'); + + try { + $this->service->handle($this->file, 1234); + } catch (PterodactylException $exception) { + $this->assertInstanceOf(BadJsonFormatException::class, $exception); + $this->assertEquals(trans('exceptions.nest.importer.json_error', [ + 'error' => json_last_error_msg(), + ]), $exception->getMessage()); + } + } +} diff --git a/tests/Unit/Services/Eggs/Sharing/EggUpdateImporterServiceTest.php b/tests/Unit/Services/Eggs/Sharing/EggUpdateImporterServiceTest.php new file mode 100644 index 000000000..b376b80b6 --- /dev/null +++ b/tests/Unit/Services/Eggs/Sharing/EggUpdateImporterServiceTest.php @@ -0,0 +1,212 @@ +connection = m::mock(ConnectionInterface::class); + $this->file = m::mock(UploadedFile::class); + $this->repository = m::mock(EggRepositoryInterface::class); + $this->variableRepository = m::mock(EggVariableRepositoryInterface::class); + + $this->service = new EggUpdateImporterService($this->connection, $this->repository, $this->variableRepository); + } + + /** + * Test that an egg update is handled correctly using an uploaded file. + */ + public function testEggIsUpdated() + { + $egg = factory(Egg::class)->make(); + $variable = factory(EggVariable::class)->make(); + + $this->file->shouldReceive('isValid')->withNoArgs()->once()->andReturn(true); + $this->file->shouldReceive('isFile')->withNoArgs()->once()->andReturn(true); + $this->file->shouldReceive('getSize')->withNoArgs()->once()->andReturn(100); + $this->file->shouldReceive('openFile->fread')->with(100)->once()->andReturn(json_encode([ + 'meta' => ['version' => 'PTDL_v1'], + 'name' => $egg->name, + 'author' => 'newauthor@example.com', + 'variables' => [$variable->toArray()], + ])); + + $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->repository->shouldReceive('update')->with($egg->id, m::subset([ + 'author' => 'newauthor@example.com', + 'name' => $egg->name, + ]), true, true)->once()->andReturn($egg); + + $this->variableRepository->shouldReceive('withoutFresh->updateOrCreate')->with([ + 'egg_id' => $egg->id, + 'env_variable' => $variable->env_variable, + ], collect($variable)->except(['egg_id', 'env_variable'])->toArray())->once()->andReturnNull(); + + $this->variableRepository->shouldReceive('withColumns')->with(['id', 'env_variable'])->once()->andReturnSelf() + ->shouldReceive('findWhere')->with([['egg_id', '=', $egg->id]])->once()->andReturn([$variable]); + + $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); + + $this->service->handle($egg->id, $this->file); + $this->assertTrue(true); + } + + /** + * Test that an imported file with less variables than currently existing deletes + * the un-needed variables from the database. + */ + public function testVariablesMissingFromImportAreDeleted() + { + $egg = factory(Egg::class)->make(); + $variable1 = factory(EggVariable::class)->make(); + $variable2 = factory(EggVariable::class)->make(); + + $this->file->shouldReceive('isValid')->withNoArgs()->once()->andReturn(true); + $this->file->shouldReceive('isFile')->withNoArgs()->once()->andReturn(true); + $this->file->shouldReceive('getSize')->withNoArgs()->once()->andReturn(100); + $this->file->shouldReceive('openFile->fread')->with(100)->once()->andReturn(json_encode([ + 'meta' => ['version' => 'PTDL_v1'], + 'name' => $egg->name, + 'author' => 'newauthor@example.com', + 'variables' => [$variable1->toArray()], + ])); + + $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); + $this->repository->shouldReceive('update')->with($egg->id, m::subset([ + 'author' => 'newauthor@example.com', + 'name' => $egg->name, + ]), true, true)->once()->andReturn($egg); + + $this->variableRepository->shouldReceive('withoutFresh->updateOrCreate')->with([ + 'egg_id' => $egg->id, + 'env_variable' => $variable1->env_variable, + ], collect($variable1)->except(['egg_id', 'env_variable'])->toArray())->once()->andReturnNull(); + + $this->variableRepository->shouldReceive('withColumns')->with(['id', 'env_variable'])->once()->andReturnSelf() + ->shouldReceive('findWhere')->with([['egg_id', '=', $egg->id]])->once()->andReturn([$variable1, $variable2]); + + $this->variableRepository->shouldReceive('deleteWhere')->with([ + ['egg_id', '=', $egg->id], + ['env_variable', '=', $variable2->env_variable], + ])->once()->andReturnNull(); + + $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); + + $this->service->handle($egg->id, $this->file); + $this->assertTrue(true); + } + + /** + * Test that an exception is thrown if the file is invalid. + */ + public function testExceptionIsThrownIfFileIsInvalid() + { + $this->file->shouldReceive('isValid')->withNoArgs()->once()->andReturn(false); + try { + $this->service->handle(1234, $this->file); + } catch (PterodactylException $exception) { + $this->assertInstanceOf(InvalidFileUploadException::class, $exception); + $this->assertEquals(trans('exceptions.nest.importer.file_error'), $exception->getMessage()); + } + } + + /** + * Test that an exception is thrown if the file is not a file. + */ + public function testExceptionIsThrownIfFileIsNotAFile() + { + $this->file->shouldReceive('isValid')->withNoArgs()->once()->andReturn(true); + $this->file->shouldReceive('isFile')->withNoArgs()->once()->andReturn(false); + + try { + $this->service->handle(1234, $this->file); + } catch (PterodactylException $exception) { + $this->assertInstanceOf(InvalidFileUploadException::class, $exception); + $this->assertEquals(trans('exceptions.nest.importer.file_error'), $exception->getMessage()); + } + } + + /** + * Test that an exception is thrown if the JSON metadata is invalid. + */ + public function testExceptionIsThrownIfJsonMetaDataIsInvalid() + { + $this->file->shouldReceive('isValid')->withNoArgs()->once()->andReturn(true); + $this->file->shouldReceive('isFile')->withNoArgs()->once()->andReturn(true); + $this->file->shouldReceive('getSize')->withNoArgs()->once()->andReturn(100); + $this->file->shouldReceive('openFile->fread')->with(100)->once()->andReturn(json_encode([ + 'meta' => ['version' => 'hodor'], + ])); + + try { + $this->service->handle(1234, $this->file); + } catch (PterodactylException $exception) { + $this->assertInstanceOf(InvalidFileUploadException::class, $exception); + $this->assertEquals(trans('exceptions.nest.importer.invalid_json_provided'), $exception->getMessage()); + } + } + + /** + * Test that an exception is thrown if bad JSON is provided. + */ + public function testExceptionIsThrownIfBadJsonIsProvided() + { + $this->file->shouldReceive('isValid')->withNoArgs()->once()->andReturn(true); + $this->file->shouldReceive('isFile')->withNoArgs()->once()->andReturn(true); + $this->file->shouldReceive('getSize')->withNoArgs()->once()->andReturn(100); + $this->file->shouldReceive('openFile->fread')->with(100)->once()->andReturn('}'); + + try { + $this->service->handle(1234, $this->file); + } catch (PterodactylException $exception) { + $this->assertInstanceOf(BadJsonFormatException::class, $exception); + $this->assertEquals(trans('exceptions.nest.importer.json_error', [ + 'error' => json_last_error_msg(), + ]), $exception->getMessage()); + } + } +} diff --git a/tests/Unit/Services/Eggs/Variables/VariableCreationServiceTest.php b/tests/Unit/Services/Eggs/Variables/VariableCreationServiceTest.php new file mode 100644 index 000000000..320d85aa5 --- /dev/null +++ b/tests/Unit/Services/Eggs/Variables/VariableCreationServiceTest.php @@ -0,0 +1,119 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Tests\Unit\Services\Eggs\Variables; + +use Mockery as m; +use Tests\TestCase; +use Pterodactyl\Models\Egg; +use Pterodactyl\Models\EggVariable; +use Pterodactyl\Services\Eggs\Variables\VariableCreationService; +use Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface; + +class VariableCreationServiceTest extends TestCase +{ + /** + * @var \Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface|\Mockery\Mock + */ + protected $repository; + + /** + * @var \Pterodactyl\Services\Eggs\Variables\VariableCreationService + */ + protected $service; + + /** + * Setup tests. + */ + public function setUp() + { + parent::setUp(); + + $this->repository = m::mock(EggVariableRepositoryInterface::class); + + $this->service = new VariableCreationService($this->repository); + } + + /** + * Test basic functionality, data should be stored in the database. + */ + public function testVariableIsCreatedAndStored() + { + $data = ['env_variable' => 'TEST_VAR_123']; + $this->repository->shouldReceive('create')->with([ + 'egg_id' => 1, + 'user_viewable' => false, + 'user_editable' => false, + 'env_variable' => 'TEST_VAR_123', + ])->once()->andReturn(new EggVariable); + + $this->assertInstanceOf(EggVariable::class, $this->service->handle(1, $data)); + } + + /** + * Test that the option key in the data array is properly parsed. + */ + public function testOptionsPassedInArrayKeyAreParsedProperly() + { + $data = ['env_variable' => 'TEST_VAR_123', 'options' => ['user_viewable', 'user_editable']]; + $this->repository->shouldReceive('create')->with([ + 'egg_id' => 1, + 'user_viewable' => true, + 'user_editable' => true, + 'env_variable' => 'TEST_VAR_123', + 'options' => ['user_viewable', 'user_editable'], + ])->once()->andReturn(new EggVariable); + + $this->assertInstanceOf(EggVariable::class, $this->service->handle(1, $data)); + } + + /** + * Test that all of the reserved variables defined in the model trigger an exception. + * + * @dataProvider reservedNamesProvider + * @expectedException \Pterodactyl\Exceptions\Service\Egg\Variable\ReservedVariableNameException + */ + public function testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames(string $variable) + { + $this->service->handle(1, ['env_variable' => $variable]); + } + + /** + * Test that the egg ID applied in the function takes higher priority than an + * ID passed into the handler. + */ + public function testEggIdPassedInDataIsNotApplied() + { + $data = ['egg_id' => 123456, 'env_variable' => 'TEST_VAR_123']; + $this->repository->shouldReceive('create')->with([ + 'egg_id' => 1, + 'user_viewable' => false, + 'user_editable' => false, + 'env_variable' => 'TEST_VAR_123', + ])->once()->andReturn(new EggVariable); + + $this->assertInstanceOf(EggVariable::class, $this->service->handle(1, $data)); + } + + /** + * Provides the data to be used in the tests. + * + * @return array + */ + public function reservedNamesProvider() + { + $data = []; + $exploded = explode(',', EggVariable::RESERVED_ENV_NAMES); + foreach ($exploded as $e) { + $data[] = [$e]; + } + + return $data; + } +} diff --git a/tests/Unit/Services/Services/Variables/VariableUpdateServiceTest.php b/tests/Unit/Services/Eggs/Variables/VariableUpdateServiceTest.php similarity index 69% rename from tests/Unit/Services/Services/Variables/VariableUpdateServiceTest.php rename to tests/Unit/Services/Eggs/Variables/VariableUpdateServiceTest.php index a5a663669..48703f8e4 100644 --- a/tests/Unit/Services/Services/Variables/VariableUpdateServiceTest.php +++ b/tests/Unit/Services/Eggs/Variables/VariableUpdateServiceTest.php @@ -7,30 +7,30 @@ * https://opensource.org/licenses/MIT */ -namespace Tests\Unit\Services\Services\Variables; +namespace Tests\Unit\Services\Eggs\Variables; use Exception; use Mockery as m; use Tests\TestCase; -use Pterodactyl\Models\ServiceVariable; +use Pterodactyl\Models\EggVariable; use Pterodactyl\Exceptions\DisplayException; -use Pterodactyl\Services\Services\Variables\VariableUpdateService; -use Pterodactyl\Contracts\Repository\ServiceVariableRepositoryInterface; +use Pterodactyl\Services\Eggs\Variables\VariableUpdateService; +use Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface; class VariableUpdateServiceTest extends TestCase { /** - * @var \Pterodactyl\Models\ServiceVariable|\Mockery\Mock + * @var \Pterodactyl\Models\EggVariable|\Mockery\Mock */ protected $model; /** - * @var \Pterodactyl\Contracts\Repository\ServiceVariableRepositoryInterface|\Mockery\Mock + * @var \Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface|\Mockery\Mock */ protected $repository; /** - * @var \Pterodactyl\Services\Services\Variables\VariableUpdateService + * @var \Pterodactyl\Services\Eggs\Variables\VariableUpdateService */ protected $service; @@ -41,8 +41,8 @@ class VariableUpdateServiceTest extends TestCase { parent::setUp(); - $this->model = factory(ServiceVariable::class)->make(); - $this->repository = m::mock(ServiceVariableRepositoryInterface::class); + $this->model = factory(EggVariable::class)->make(); + $this->repository = m::mock(EggVariableRepositoryInterface::class); $this->service = new VariableUpdateService($this->repository); } @@ -86,7 +86,7 @@ class VariableUpdateServiceTest extends TestCase $this->repository->shouldReceive('withColumns')->with('id')->once()->andReturnSelf() ->shouldReceive('findCountWhere')->with([ ['env_variable', '=', 'TEST_VAR_123'], - ['option_id', '=', $this->model->option_id], + ['egg_id', '=', $this->model->option_id], ['id', '!=', $this->model->id], ])->once()->andReturn(0); @@ -100,6 +100,28 @@ class VariableUpdateServiceTest extends TestCase $this->assertTrue($this->service->handle($this->model, ['env_variable' => 'TEST_VAR_123'])); } + /** + * Test that data passed into the handler is overwritten inside the handler. + */ + public function testDataPassedIntoHandlerTakesLowerPriorityThanDataSet() + { + $this->repository->shouldReceive('withColumns')->with('id')->once()->andReturnSelf() + ->shouldReceive('findCountWhere')->with([ + ['env_variable', '=', 'TEST_VAR_123'], + ['egg_id', '=', $this->model->option_id], + ['id', '!=', $this->model->id], + ])->once()->andReturn(0); + + $this->repository->shouldReceive('withoutFresh')->withNoArgs()->once()->andReturnSelf() + ->shouldReceive('update')->with($this->model->id, [ + 'user_viewable' => false, + 'user_editable' => false, + 'env_variable' => 'TEST_VAR_123', + ])->once()->andReturn(true); + + $this->assertTrue($this->service->handle($this->model, ['user_viewable' => 123456, 'env_variable' => 'TEST_VAR_123'])); + } + /** * Test that a non-unique environment variable triggers an exception. */ @@ -108,7 +130,7 @@ class VariableUpdateServiceTest extends TestCase $this->repository->shouldReceive('withColumns')->with('id')->once()->andReturnSelf() ->shouldReceive('findCountWhere')->with([ ['env_variable', '=', 'TEST_VAR_123'], - ['option_id', '=', $this->model->option_id], + ['egg_id', '=', $this->model->option_id], ['id', '!=', $this->model->id], ])->once()->andReturn(1); @@ -126,9 +148,9 @@ class VariableUpdateServiceTest extends TestCase * Test that all of the reserved variables defined in the model trigger an exception. * * @dataProvider reservedNamesProvider - * @expectedException \Pterodactyl\Exceptions\Service\ServiceVariable\ReservedVariableNameException + * @expectedException \Pterodactyl\Exceptions\Service\Egg\Variable\ReservedVariableNameException */ - public function testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames($variable) + public function testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames(string $variable) { $this->service->handle($this->model, ['env_variable' => $variable]); } @@ -141,7 +163,7 @@ class VariableUpdateServiceTest extends TestCase public function reservedNamesProvider() { $data = []; - $exploded = explode(',', ServiceVariable::RESERVED_ENV_NAMES); + $exploded = explode(',', EggVariable::RESERVED_ENV_NAMES); foreach ($exploded as $e) { $data[] = [$e]; } diff --git a/tests/Unit/Services/Services/ServiceCreationServiceTest.php b/tests/Unit/Services/Nests/NestCreationServiceTest.php similarity index 52% rename from tests/Unit/Services/Services/ServiceCreationServiceTest.php rename to tests/Unit/Services/Nests/NestCreationServiceTest.php index c1e64d9b9..b5d38e071 100644 --- a/tests/Unit/Services/Services/ServiceCreationServiceTest.php +++ b/tests/Unit/Services/Nests/NestCreationServiceTest.php @@ -11,28 +11,29 @@ namespace Tests\Unit\Services\Services; use Mockery as m; use Tests\TestCase; -use Pterodactyl\Models\Service; +use Ramsey\Uuid\Uuid; +use Pterodactyl\Models\Nest; +use Tests\Traits\MocksUuids; use Illuminate\Contracts\Config\Repository; -use Pterodactyl\Traits\Services\CreatesServiceIndex; -use Pterodactyl\Services\Services\ServiceCreationService; -use Pterodactyl\Contracts\Repository\ServiceRepositoryInterface; +use Pterodactyl\Services\Nests\NestCreationService; +use Pterodactyl\Contracts\Repository\NestRepositoryInterface; -class ServiceCreationServiceTest extends TestCase +class NestCreationServiceTest extends TestCase { - use CreatesServiceIndex; + use MocksUuids; /** - * @var \Illuminate\Contracts\Config\Repository + * @var \Illuminate\Contracts\Config\Repository|\Mockery\Mock */ protected $config; /** - * @var \Pterodactyl\Contracts\Repository\ServiceRepositoryInterface + * @var \Pterodactyl\Contracts\Repository\NestRepositoryInterface|\Mockery\Mock */ protected $repository; /** - * @var \Pterodactyl\Services\Services\ServiceCreationService + * @var \Pterodactyl\Services\Nests\NestCreationService */ protected $service; @@ -44,9 +45,9 @@ class ServiceCreationServiceTest extends TestCase parent::setUp(); $this->config = m::mock(Repository::class); - $this->repository = m::mock(ServiceRepositoryInterface::class); + $this->repository = m::mock(NestRepositoryInterface::class); - $this->service = new ServiceCreationService($this->config, $this->repository); + $this->service = new NestCreationService($this->config, $this->repository); } /** @@ -54,26 +55,22 @@ class ServiceCreationServiceTest extends TestCase */ public function testCreateNewService() { - $model = factory(Service::class)->make(); + $model = factory(Nest::class)->make(); $data = [ 'name' => $model->name, 'description' => $model->description, - 'folder' => $model->folder, - 'startup' => $model->startup, ]; - $this->config->shouldReceive('get')->with('pterodactyl.service.author')->once()->andReturn('0000-author'); + $this->config->shouldReceive('get')->with('pterodactyl.service.author')->once()->andReturn('testauthor@example.com'); $this->repository->shouldReceive('create')->with([ - 'author' => '0000-author', + 'uuid' => $this->getKnownUuid(), + 'author' => 'testauthor@example.com', 'name' => $data['name'], 'description' => $data['description'], - 'folder' => $data['folder'], - 'startup' => $data['startup'], - 'index_file' => $this->getIndexScript(), - ])->once()->andReturn($model); + ], true, true)->once()->andReturn($model); $response = $this->service->handle($data); - $this->assertInstanceOf(Service::class, $response); + $this->assertInstanceOf(Nest::class, $response); $this->assertEquals($model, $response); } } diff --git a/tests/Unit/Services/Services/ServiceDeletionServiceTest.php b/tests/Unit/Services/Nests/NestDeletionServiceTest.php similarity index 70% rename from tests/Unit/Services/Services/ServiceDeletionServiceTest.php rename to tests/Unit/Services/Nests/NestDeletionServiceTest.php index 240f3fcb9..b0bc0b2bb 100644 --- a/tests/Unit/Services/Services/ServiceDeletionServiceTest.php +++ b/tests/Unit/Services/Nests/NestDeletionServiceTest.php @@ -12,25 +12,26 @@ namespace Tests\Unit\Services\Services; use Exception; use Mockery as m; use Tests\TestCase; -use Pterodactyl\Services\Services\ServiceDeletionService; +use Pterodactyl\Exceptions\PterodactylException; +use Pterodactyl\Services\Nests\NestDeletionService; +use Pterodactyl\Contracts\Repository\NestRepositoryInterface; use Pterodactyl\Exceptions\Service\HasActiveServersException; use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; -use Pterodactyl\Contracts\Repository\ServiceRepositoryInterface; -class ServiceDeletionServiceTest extends TestCase +class NestDeletionServiceTest extends TestCase { /** - * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface + * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface|\Mockery\Mock */ protected $serverRepository; /** - * @var \Pterodactyl\Contracts\Repository\ServiceRepositoryInterface + * @var \Pterodactyl\Contracts\Repository\NestRepositoryInterface|\Mockery\Mock */ protected $repository; /** - * @var \Pterodactyl\Services\Services\ServiceDeletionService + * @var \Pterodactyl\Services\Nests\NestDeletionService */ protected $service; @@ -42,9 +43,9 @@ class ServiceDeletionServiceTest extends TestCase parent::setUp(); $this->serverRepository = m::mock(ServerRepositoryInterface::class); - $this->repository = m::mock(ServiceRepositoryInterface::class); + $this->repository = m::mock(NestRepositoryInterface::class); - $this->service = new ServiceDeletionService($this->serverRepository, $this->repository); + $this->service = new NestDeletionService($this->serverRepository, $this->repository); } /** @@ -52,7 +53,7 @@ class ServiceDeletionServiceTest extends TestCase */ public function testServiceIsDeleted() { - $this->serverRepository->shouldReceive('findCountWhere')->with([['service_id', '=', 1]])->once()->andReturn(0); + $this->serverRepository->shouldReceive('findCountWhere')->with([['nest_id', '=', 1]])->once()->andReturn(0); $this->repository->shouldReceive('delete')->with(1)->once()->andReturn(1); $this->assertEquals(1, $this->service->handle(1)); @@ -62,14 +63,16 @@ class ServiceDeletionServiceTest extends TestCase * Test that an exception is thrown when there are servers attached to a service. * * @dataProvider serverCountProvider + * + * @param int $count */ - public function testExceptionIsThrownIfServersAreAttached($count) + public function testExceptionIsThrownIfServersAreAttached(int $count) { - $this->serverRepository->shouldReceive('findCountWhere')->with([['service_id', '=', 1]])->once()->andReturn($count); + $this->serverRepository->shouldReceive('findCountWhere')->with([['nest_id', '=', 1]])->once()->andReturn($count); try { $this->service->handle(1); - } catch (Exception $exception) { + } catch (PterodactylException $exception) { $this->assertInstanceOf(HasActiveServersException::class, $exception); $this->assertEquals(trans('exceptions.service.delete_has_servers'), $exception->getMessage()); } diff --git a/tests/Unit/Services/Services/ServiceUpdateServiceTest.php b/tests/Unit/Services/Nests/NestUpdateServiceTest.php similarity index 75% rename from tests/Unit/Services/Services/ServiceUpdateServiceTest.php rename to tests/Unit/Services/Nests/NestUpdateServiceTest.php index 1f0ee1164..e5b03e974 100644 --- a/tests/Unit/Services/Services/ServiceUpdateServiceTest.php +++ b/tests/Unit/Services/Nests/NestUpdateServiceTest.php @@ -11,18 +11,18 @@ namespace Tests\Unit\Services\Services; use Mockery as m; use Tests\TestCase; -use Pterodactyl\Services\Services\ServiceUpdateService; -use Pterodactyl\Contracts\Repository\ServiceRepositoryInterface; +use Pterodactyl\Services\Nests\NestUpdateService; +use Pterodactyl\Contracts\Repository\NestRepositoryInterface; -class ServiceUpdateServiceTest extends TestCase +class NestUpdateServiceTest extends TestCase { /** - * @var \Pterodactyl\Contracts\Repository\ServiceRepositoryInterface + * @var \Pterodactyl\Contracts\Repository\NestRepositoryInterface|\Mockery\Mock */ protected $repository; /** - * @var \Pterodactyl\Services\Services\ServiceUpdateService + * @var \Pterodactyl\Services\Nests\NestUpdateService */ protected $service; @@ -33,9 +33,9 @@ class ServiceUpdateServiceTest extends TestCase { parent::setUp(); - $this->repository = m::mock(ServiceRepositoryInterface::class); + $this->repository = m::mock(NestRepositoryInterface::class); - $this->service = new ServiceUpdateService($this->repository); + $this->service = new NestUpdateService($this->repository); } /** diff --git a/tests/Unit/Services/Packs/PackCreationServiceTest.php b/tests/Unit/Services/Packs/PackCreationServiceTest.php index 518fa9090..d0baddf30 100644 --- a/tests/Unit/Services/Packs/PackCreationServiceTest.php +++ b/tests/Unit/Services/Packs/PackCreationServiceTest.php @@ -13,28 +13,31 @@ use Exception; use Mockery as m; use Tests\TestCase; use Pterodactyl\Models\Pack; +use Tests\Traits\MocksUuids; use Illuminate\Http\UploadedFile; use Illuminate\Contracts\Filesystem\Factory; use Illuminate\Database\ConnectionInterface; use Pterodactyl\Services\Packs\PackCreationService; use Pterodactyl\Contracts\Repository\PackRepositoryInterface; -use Pterodactyl\Exceptions\Service\Pack\InvalidFileUploadException; +use Pterodactyl\Exceptions\Service\InvalidFileUploadException; use Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException; class PackCreationServiceTest extends TestCase { + use MocksUuids; + /** - * @var \Illuminate\Database\ConnectionInterface + * @var \Illuminate\Database\ConnectionInterface|\Mockery\Mock */ protected $connection; /** - * @var \Illuminate\Http\UploadedFile + * @var \Illuminate\Http\UploadedFile|\Mockery\Mock */ protected $file; /** - * @var \Pterodactyl\Contracts\Repository\PackRepositoryInterface + * @var \Pterodactyl\Contracts\Repository\PackRepositoryInterface|\Mockery\Mock */ protected $repository; @@ -44,15 +47,10 @@ class PackCreationServiceTest extends TestCase protected $service; /** - * @var \Illuminate\Contracts\Filesystem\Factory + * @var \Illuminate\Contracts\Filesystem\Factory|\Mockery\Mock */ protected $storage; - /** - * @var \Ramsey\Uuid\Uuid - */ - protected $uuid; - /** * Setup tests. */ @@ -64,7 +62,6 @@ class PackCreationServiceTest extends TestCase $this->file = m::mock(UploadedFile::class); $this->repository = m::mock(PackRepositoryInterface::class); $this->storage = m::mock(Factory::class); - $this->uuid = m::mock('overload:\Ramsey\Uuid\Uuid'); $this->service = new PackCreationService($this->connection, $this->storage, $this->repository); } @@ -77,17 +74,15 @@ class PackCreationServiceTest extends TestCase $model = factory(Pack::class)->make(); $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); - $this->uuid->shouldReceive('uuid4')->withNoArgs()->once()->andReturn($model->uuid); $this->repository->shouldReceive('create')->with([ - 'uuid' => $model->uuid, + 'uuid' => $this->getKnownUuid(), 'selectable' => false, 'visible' => false, 'locked' => false, 'test-data' => 'value', ])->once()->andReturn($model); - $this->storage->shouldReceive('disk')->withNoArgs()->once()->andReturnSelf() - ->shouldReceive('makeDirectory')->with('packs/' . $model->uuid)->once()->andReturnNull(); + $this->storage->shouldReceive('disk->makeDirectory')->with('packs/' . $model->uuid)->once()->andReturnNull(); $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); $response = $this->service->handle(['test-data' => 'value']); @@ -107,17 +102,15 @@ class PackCreationServiceTest extends TestCase $this->file->shouldReceive('isValid')->withNoArgs()->once()->andReturn(true); $this->file->shouldReceive('getMimeType')->withNoArgs()->once()->andReturn($mime); $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); - $this->uuid->shouldReceive('uuid4')->withNoArgs()->once()->andReturn($model->uuid); $this->repository->shouldReceive('create')->with([ - 'uuid' => $model->uuid, + 'uuid' => $this->getKnownUuid(), 'selectable' => false, 'visible' => false, 'locked' => false, 'test-data' => 'value', ])->once()->andReturn($model); - $this->storage->shouldReceive('disk')->withNoArgs()->once()->andReturnSelf() - ->shouldReceive('makeDirectory')->with('packs/' . $model->uuid)->once()->andReturnNull(); + $this->storage->shouldReceive('disk->makeDirectory')->with('packs/' . $model->uuid)->once()->andReturnNull(); $this->file->shouldReceive('storeAs')->with('packs/' . $model->uuid, 'archive.tar.gz')->once()->andReturnNull(); $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); diff --git a/tests/Unit/Services/Packs/PackUpdateServiceTest.php b/tests/Unit/Services/Packs/PackUpdateServiceTest.php index 02dd45fd4..8a09311f1 100644 --- a/tests/Unit/Services/Packs/PackUpdateServiceTest.php +++ b/tests/Unit/Services/Packs/PackUpdateServiceTest.php @@ -20,12 +20,12 @@ use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; class PackUpdateServiceTest extends TestCase { /** - * @var \Pterodactyl\Contracts\Repository\PackRepositoryInterface + * @var \Pterodactyl\Contracts\Repository\PackRepositoryInterface|\Mockery\Mock */ protected $repository; /** - * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface + * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface|\Mockery\Mock */ protected $serverRepository; @@ -53,8 +53,7 @@ class PackUpdateServiceTest extends TestCase public function testPackIsUpdated() { $model = factory(Pack::class)->make(); - $this->repository->shouldReceive('withoutFresh')->withNoArgs()->once()->andReturnSelf() - ->shouldReceive('update')->with($model->id, [ + $this->repository->shouldReceive('withoutFresh->update')->with($model->id, [ 'locked' => false, 'visible' => false, 'selectable' => false, @@ -67,13 +66,13 @@ class PackUpdateServiceTest extends TestCase /** * Test that an exception is thrown if the pack option ID is changed while servers are using the pack. */ - public function testExceptionIsThrownIfModifyingOptionIdWhenServersAreAttached() + public function testExceptionIsThrownIfModifyingEggIdWhenServersAreAttached() { $model = factory(Pack::class)->make(); $this->serverRepository->shouldReceive('findCountWhere')->with([['pack_id', '=', $model->id]])->once()->andReturn(1); try { - $this->service->handle($model, ['option_id' => 0]); + $this->service->handle($model, ['egg_id' => 0]); } catch (HasActiveServersException $exception) { $this->assertEquals(trans('exceptions.packs.update_has_servers'), $exception->getMessage()); } @@ -86,10 +85,9 @@ class PackUpdateServiceTest extends TestCase { $model = factory(Pack::class)->make(); - $this->repository->shouldReceive('withColumns')->with(['id', 'option_id'])->once()->andReturnSelf() + $this->repository->shouldReceive('withColumns')->with(['id', 'egg_id'])->once()->andReturnSelf() ->shouldReceive('find')->with($model->id)->once()->andReturn($model); - $this->repository->shouldReceive('withoutFresh')->withNoArgs()->once()->andReturnSelf() - ->shouldReceive('update')->with($model->id, [ + $this->repository->shouldReceive('withoutFresh->update')->with($model->id, [ 'locked' => false, 'visible' => false, 'selectable' => false, diff --git a/tests/Unit/Services/Packs/TemplateUploadServiceTest.php b/tests/Unit/Services/Packs/TemplateUploadServiceTest.php index 115241087..c1df4c207 100644 --- a/tests/Unit/Services/Packs/TemplateUploadServiceTest.php +++ b/tests/Unit/Services/Packs/TemplateUploadServiceTest.php @@ -16,8 +16,8 @@ use Pterodactyl\Models\Pack; use Illuminate\Http\UploadedFile; use Pterodactyl\Services\Packs\PackCreationService; use Pterodactyl\Services\Packs\TemplateUploadService; +use Pterodactyl\Exceptions\Service\InvalidFileUploadException; use Pterodactyl\Exceptions\Service\Pack\ZipExtractionException; -use Pterodactyl\Exceptions\Service\Pack\InvalidFileUploadException; use Pterodactyl\Exceptions\Service\Pack\InvalidFileMimeTypeException; use Pterodactyl\Exceptions\Service\Pack\UnreadableZipArchiveException; use Pterodactyl\Exceptions\Service\Pack\InvalidPackArchiveFormatException; @@ -27,17 +27,17 @@ class TemplateUploadServiceTest extends TestCase const JSON_FILE_CONTENTS = '{"test_content": "value"}'; /** - * @var \ZipArchive + * @var \ZipArchive|\Mockery\Mock */ protected $archive; /** - * @var \Pterodactyl\Services\Packs\PackCreationService + * @var \Pterodactyl\Services\Packs\PackCreationService|\Mockery\Mock */ protected $creationService; /** - * @var \Illuminate\Http\UploadedFile + * @var \Illuminate\Http\UploadedFile|\Mockery\Mock */ protected $file; @@ -70,10 +70,9 @@ class TemplateUploadServiceTest extends TestCase $this->file->shouldReceive('isValid')->withNoArgs()->once()->andReturn(true); $this->file->shouldReceive('getMimeType')->withNoArgs()->twice()->andReturn($mime); $this->file->shouldReceive('getSize')->withNoArgs()->once()->andReturn(128); - $this->file->shouldReceive('openFile')->withNoArgs()->once()->andReturnSelf() - ->shouldReceive('fread')->with(128)->once()->andReturn(self::JSON_FILE_CONTENTS); + $this->file->shouldReceive('openFile->fread')->with(128)->once()->andReturn(self::JSON_FILE_CONTENTS); - $this->creationService->shouldReceive('handle')->with(['test_content' => 'value', 'option_id' => 1]) + $this->creationService->shouldReceive('handle')->with(['test_content' => 'value', 'egg_id' => 1]) ->once()->andReturn(factory(Pack::class)->make()); $this->assertInstanceOf(Pack::class, $this->service->handle(1, $this->file)); @@ -94,7 +93,7 @@ class TemplateUploadServiceTest extends TestCase $this->archive->shouldReceive('locateName')->with('import.json')->once()->andReturn(true); $this->archive->shouldReceive('locateName')->with('archive.tar.gz')->once()->andReturn(true); $this->archive->shouldReceive('getFromName')->with('import.json')->once()->andReturn(self::JSON_FILE_CONTENTS); - $this->creationService->shouldReceive('handle')->with(['test_content' => 'value', 'option_id' => 1]) + $this->creationService->shouldReceive('handle')->with(['test_content' => 'value', 'egg_id' => 1]) ->once()->andReturn($model); $this->archive->shouldReceive('extractTo')->with(storage_path('app/packs/' . $model->uuid), 'archive.tar.gz') ->once()->andReturn(true); diff --git a/tests/Unit/Services/Servers/ServerCreationServiceTest.php b/tests/Unit/Services/Servers/ServerCreationServiceTest.php index 5244dfe3b..da2e33af2 100644 --- a/tests/Unit/Services/Servers/ServerCreationServiceTest.php +++ b/tests/Unit/Services/Servers/ServerCreationServiceTest.php @@ -9,14 +9,13 @@ namespace Tests\Unit\Services\Servers; -use Exception; use Mockery as m; use Tests\TestCase; -use Illuminate\Log\Writer; use phpmock\phpunit\PHPMock; -use Illuminate\Database\DatabaseManager; +use Tests\Traits\MocksUuids; use GuzzleHttp\Exception\RequestException; -use Pterodactyl\Exceptions\DisplayException; +use Illuminate\Database\ConnectionInterface; +use Pterodactyl\Exceptions\PterodactylException; use Pterodactyl\Services\Servers\ServerCreationService; use Pterodactyl\Services\Servers\VariableValidatorService; use Pterodactyl\Services\Servers\UsernameGenerationService; @@ -24,20 +23,35 @@ use Pterodactyl\Contracts\Repository\NodeRepositoryInterface; use Pterodactyl\Contracts\Repository\UserRepositoryInterface; use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface; +use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; +use Pterodactyl\Services\Servers\ServerConfigurationStructureService; use Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface; use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; +/** + * @preserveGlobalState disabled + */ class ServerCreationServiceTest extends TestCase { - use PHPMock; + use MocksUuids, PHPMock; /** - * @var \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface + * @var \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface|\Mockery\Mock */ protected $allocationRepository; /** - * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface + * @var \Pterodactyl\Services\Servers\ServerConfigurationStructureService|\Mockery\Mock + */ + protected $configurationStructureService; + + /** + * @var \Illuminate\Database\ConnectionInterface|\Mockery\Mock + */ + protected $connection; + + /** + * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface|\Mockery\Mock */ protected $daemonServerRepository; @@ -59,34 +73,29 @@ class ServerCreationServiceTest extends TestCase 'environment' => [ 'TEST_VAR_1' => 'var1-value', ], - 'service_id' => 1, - 'option_id' => 1, + 'nest_id' => 1, + 'egg_id' => 1, 'startup' => 'startup-param', 'docker_image' => 'some/image', ]; /** - * @var \Illuminate\Database\DatabaseManager - */ - protected $database; - - /** - * @var \GuzzleHttp\Exception\RequestException + * @var \GuzzleHttp\Exception\RequestException|\Mockery\Mock */ protected $exception; /** - * @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface + * @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface|\Mockery\Mock */ protected $nodeRepository; /** - * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface + * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface|\Mockery\Mock */ protected $repository; /** - * @var \Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface + * @var \Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface|\Mockery\Mock */ protected $serverVariableRepository; @@ -96,30 +105,20 @@ class ServerCreationServiceTest extends TestCase protected $service; /** - * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface + * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface|\Mockery\Mock */ protected $userRepository; /** - * @var \Pterodactyl\Services\Servers\UsernameGenerationService + * @var \Pterodactyl\Services\Servers\UsernameGenerationService|\Mockery\Mock */ protected $usernameService; /** - * @var \Pterodactyl\Services\Servers\VariableValidatorService + * @var \Pterodactyl\Services\Servers\VariableValidatorService|\Mockery\Mock */ protected $validatorService; - /** - * @var \Ramsey\Uuid\Uuid - */ - protected $uuid; - - /** - * @var \Illuminate\Log\Writer - */ - protected $writer; - /** * Setup tests. */ @@ -128,8 +127,9 @@ class ServerCreationServiceTest extends TestCase parent::setUp(); $this->allocationRepository = m::mock(AllocationRepositoryInterface::class); + $this->configurationStructureService = m::mock(ServerConfigurationStructureService::class); + $this->connection = m::mock(ConnectionInterface::class); $this->daemonServerRepository = m::mock(DaemonServerRepositoryInterface::class); - $this->database = m::mock(DatabaseManager::class); $this->exception = m::mock(RequestException::class); $this->nodeRepository = m::mock(NodeRepositoryInterface::class); $this->repository = m::mock(ServerRepositoryInterface::class); @@ -137,26 +137,21 @@ class ServerCreationServiceTest extends TestCase $this->userRepository = m::mock(UserRepositoryInterface::class); $this->usernameService = m::mock(UsernameGenerationService::class); $this->validatorService = m::mock(VariableValidatorService::class); - $this->uuid = m::mock('overload:Ramsey\Uuid\Uuid'); - $this->writer = m::mock(Writer::class); $this->getFunctionMock('\\Pterodactyl\\Services\\Servers', 'str_random') ->expects($this->any())->willReturn('random_string'); - $this->getFunctionMock('\\Ramsey\\Uuid\\Uuid', 'uuid4') - ->expects($this->any())->willReturn('s'); - $this->service = new ServerCreationService( $this->allocationRepository, + $this->connection, $this->daemonServerRepository, - $this->database, $this->nodeRepository, + $this->configurationStructureService, $this->repository, $this->serverVariableRepository, $this->userRepository, $this->usernameService, - $this->validatorService, - $this->writer + $this->validatorService ); } @@ -167,39 +162,19 @@ class ServerCreationServiceTest extends TestCase { $this->validatorService->shouldReceive('isAdmin')->withNoArgs()->once()->andReturnSelf() ->shouldReceive('setFields')->with($this->data['environment'])->once()->andReturnSelf() - ->shouldReceive('validate')->with($this->data['option_id'])->once()->andReturnSelf(); + ->shouldReceive('validate')->with($this->data['egg_id'])->once()->andReturnSelf(); - $this->database->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); - $this->uuid->shouldReceive('uuid4')->withNoArgs()->once()->andReturnSelf() - ->shouldReceive('toString')->withNoArgs()->once()->andReturn('uuid-0000'); + $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); $this->usernameService->shouldReceive('generate')->with($this->data['name'], 'random_string') ->once()->andReturn('user_name'); - $this->repository->shouldReceive('create')->with([ - 'uuid' => 'uuid-0000', - 'uuidShort' => 'random_string', + $this->repository->shouldReceive('create')->with(m::subset([ + 'uuid' => $this->getKnownUuid(), 'node_id' => $this->data['node_id'], - 'name' => $this->data['name'], - 'description' => $this->data['description'], - 'skip_scripts' => false, - 'suspended' => false, - 'owner_id' => $this->data['owner_id'], - 'memory' => $this->data['memory'], - 'swap' => $this->data['swap'], - 'disk' => $this->data['disk'], - 'io' => $this->data['io'], - 'cpu' => $this->data['cpu'], - 'oom_disabled' => false, - 'allocation_id' => $this->data['allocation_id'], - 'service_id' => $this->data['service_id'], - 'option_id' => $this->data['option_id'], - 'pack_id' => null, - 'startup' => $this->data['startup'], - 'daemonSecret' => 'random_string', - 'image' => $this->data['docker_image'], - 'username' => 'user_name', - 'sftp_password' => null, - ])->once()->andReturn((object) [ + 'owner_id' => 1, + 'nest_id' => 1, + 'egg_id' => 1, + ]))->once()->andReturn((object) [ 'node_id' => 1, 'id' => 1, ]); @@ -216,9 +191,12 @@ class ServerCreationServiceTest extends TestCase 'variable_id' => 1, 'variable_value' => 'var1-value', ]])->once()->andReturnNull(); + + $this->configurationStructureService->shouldReceive('handle')->with(1)->once()->andReturn(['test' => 'struct']); + $this->daemonServerRepository->shouldReceive('setNode')->with(1)->once()->andReturnSelf() - ->shouldReceive('create')->with(1)->once()->andReturnNull(); - $this->database->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); + ->shouldReceive('create')->with(['test' => 'struct'], ['start_on_completion' => false])->once()->andReturnNull(); + $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); $response = $this->service->create($this->data); @@ -232,8 +210,7 @@ class ServerCreationServiceTest extends TestCase public function testExceptionShouldBeThrownIfTheRequestFails() { $this->validatorService->shouldReceive('isAdmin->setFields->validate->getResults')->once()->andReturn([]); - $this->database->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); - $this->uuid->shouldReceive('uuid4->toString')->once()->andReturn('uuid-0000'); + $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); $this->usernameService->shouldReceive('generate')->once()->andReturn('user_name'); $this->repository->shouldReceive('create')->once()->andReturn((object) [ 'node_id' => 1, @@ -242,18 +219,15 @@ class ServerCreationServiceTest extends TestCase $this->allocationRepository->shouldReceive('assignAllocationsToServer')->once()->andReturnNull(); $this->serverVariableRepository->shouldReceive('insert')->with([])->once()->andReturnNull(); + $this->configurationStructureService->shouldReceive('handle')->once()->andReturnNull(); $this->daemonServerRepository->shouldReceive('setNode->create')->once()->andThrow($this->exception); $this->exception->shouldReceive('getResponse')->withNoArgs()->once()->andReturnNull(); - $this->writer->shouldReceive('warning')->with($this->exception)->once()->andReturnNull(); - $this->database->shouldReceive('rollBack')->withNoArgs()->once()->andReturnNull(); + $this->connection->shouldReceive('rollBack')->withNoArgs()->once()->andReturnNull(); try { $this->service->create($this->data); - } catch (Exception $exception) { - $this->assertInstanceOf(DisplayException::class, $exception); - $this->assertEquals(trans('admin/server.exceptions.daemon_exception', [ - 'code' => 'E_CONN_REFUSED', - ]), $exception->getMessage()); + } catch (PterodactylException $exception) { + $this->assertInstanceOf(DaemonConnectionException::class, $exception); } } } diff --git a/tests/Unit/Services/Servers/VariableValidatorServiceTest.php b/tests/Unit/Services/Servers/VariableValidatorServiceTest.php index 3a028967a..ce2eaf7d8 100644 --- a/tests/Unit/Services/Servers/VariableValidatorServiceTest.php +++ b/tests/Unit/Services/Servers/VariableValidatorServiceTest.php @@ -11,18 +11,18 @@ namespace Tests\Unit\Services\Servers; use Mockery as m; use Tests\TestCase; -use Pterodactyl\Models\ServiceVariable; +use Pterodactyl\Models\EggVariable; use Illuminate\Contracts\Validation\Factory; use Pterodactyl\Exceptions\DisplayValidationException; use Pterodactyl\Services\Servers\VariableValidatorService; use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; -use Pterodactyl\Contracts\Repository\OptionVariableRepositoryInterface; +use Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface; use Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface; class VariableValidatorServiceTest extends TestCase { /** - * @var \Pterodactyl\Contracts\Repository\OptionVariableRepositoryInterface + * @var \Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface */ protected $optionVariableRepository; @@ -60,14 +60,14 @@ class VariableValidatorServiceTest extends TestCase $this->variables = collect( [ - factory(ServiceVariable::class)->states('editable', 'viewable')->make(), - factory(ServiceVariable::class)->states('viewable')->make(), - factory(ServiceVariable::class)->states('editable')->make(), - factory(ServiceVariable::class)->make(), + factory(EggVariable::class)->states('editable', 'viewable')->make(), + factory(EggVariable::class)->states('viewable')->make(), + factory(EggVariable::class)->states('editable')->make(), + factory(EggVariable::class)->make(), ] ); - $this->optionVariableRepository = m::mock(OptionVariableRepositoryInterface::class); + $this->optionVariableRepository = m::mock(EggVariableRepositoryInterface::class); $this->serverRepository = m::mock(ServerRepositoryInterface::class); $this->serverVariableRepository = m::mock(ServerVariableRepositoryInterface::class); $this->validator = m::mock(Factory::class); @@ -115,7 +115,7 @@ class VariableValidatorServiceTest extends TestCase */ public function testEmptyResultSetShouldBeReturnedIfNoVariablesAreFound() { - $this->optionVariableRepository->shouldReceive('findWhere')->with([['option_id', '=', 1]])->andReturn([]); + $this->optionVariableRepository->shouldReceive('findWhere')->with([['egg_id', '=', 1]])->andReturn([]); $response = $this->service->validate(1); @@ -129,7 +129,7 @@ class VariableValidatorServiceTest extends TestCase */ public function testValidatorShouldNotProcessVariablesSetAsNotUserEditableWhenAdminFlagIsNotPassed() { - $this->optionVariableRepository->shouldReceive('findWhere')->with([['option_id', '=', 1]])->andReturn($this->variables); + $this->optionVariableRepository->shouldReceive('findWhere')->with([['egg_id', '=', 1]])->andReturn($this->variables); $this->validator->shouldReceive('make')->with([ 'variable_value' => 'Test_SomeValue_0', @@ -161,7 +161,7 @@ class VariableValidatorServiceTest extends TestCase */ public function testValidatorShouldProcessAllVariablesWhenAdminFlagIsSet() { - $this->optionVariableRepository->shouldReceive('findWhere')->with([['option_id', '=', 1]])->andReturn($this->variables); + $this->optionVariableRepository->shouldReceive('findWhere')->with([['egg_id', '=', 1]])->andReturn($this->variables); foreach ($this->variables as $key => $variable) { $this->validator->shouldReceive('make')->with([ @@ -198,7 +198,7 @@ class VariableValidatorServiceTest extends TestCase */ public function testValidatorShouldThrowExceptionWhenAValidationErrorIsEncountered() { - $this->optionVariableRepository->shouldReceive('findWhere')->with([['option_id', '=', 1]])->andReturn($this->variables); + $this->optionVariableRepository->shouldReceive('findWhere')->with([['egg_id', '=', 1]])->andReturn($this->variables); $this->validator->shouldReceive('make')->with([ 'variable_value' => null, diff --git a/tests/Unit/Services/Services/Options/OptionCreationServiceTest.php b/tests/Unit/Services/Services/Options/OptionCreationServiceTest.php deleted file mode 100644 index dac7e7dd3..000000000 --- a/tests/Unit/Services/Services/Options/OptionCreationServiceTest.php +++ /dev/null @@ -1,107 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Tests\Unit\Services\Services\Options; - -use Exception; -use Mockery as m; -use Tests\TestCase; -use Pterodactyl\Models\ServiceOption; -use Pterodactyl\Services\Services\Options\OptionCreationService; -use Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface; -use Pterodactyl\Exceptions\Service\ServiceOption\NoParentConfigurationFoundException; - -class OptionCreationServiceTest extends TestCase -{ - /** - * @var \Pterodactyl\Models\ServiceOption - */ - protected $model; - - /** - * @var \Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface - */ - protected $repository; - - /** - * @var \Pterodactyl\Services\Services\Options\OptionCreationService - */ - protected $service; - - /** - * Setup tests. - */ - public function setUp() - { - parent::setUp(); - - $this->model = factory(ServiceOption::class)->make(); - $this->repository = m::mock(ServiceOptionRepositoryInterface::class); - - $this->service = new OptionCreationService($this->repository); - } - - /** - * Test that a new model is created when not using the config from attribute. - */ - public function testCreateNewModelWithoutUsingConfigFrom() - { - $this->repository->shouldReceive('create')->with(['name' => $this->model->name, 'config_from' => null]) - ->once()->andReturn($this->model); - - $response = $this->service->handle(['name' => $this->model->name]); - - $this->assertNotEmpty($response); - $this->assertNull(object_get($response, 'config_from')); - $this->assertEquals($this->model->name, $response->name); - } - - /** - * Test that a new model is created when using the config from attribute. - */ - public function testCreateNewModelUsingConfigFrom() - { - $data = [ - 'name' => $this->model->name, - 'service_id' => $this->model->service_id, - 'config_from' => 1, - ]; - - $this->repository->shouldReceive('findCountWhere')->with([ - ['service_id', '=', $data['service_id']], - ['id', '=', $data['config_from']], - ])->once()->andReturn(1); - - $this->repository->shouldReceive('create')->with($data) - ->once()->andReturn($this->model); - - $response = $this->service->handle($data); - - $this->assertNotEmpty($response); - $this->assertEquals($response, $this->model); - } - - /** - * Test that an exception is thrown if no parent configuration can be located. - */ - public function testExceptionIsThrownIfNoParentConfigurationIsFound() - { - $this->repository->shouldReceive('findCountWhere')->with([ - ['service_id', '=', null], - ['id', '=', 1], - ])->once()->andReturn(0); - - try { - $this->service->handle(['config_from' => 1]); - } catch (Exception $exception) { - $this->assertInstanceOf(NoParentConfigurationFoundException::class, $exception); - $this->assertEquals(trans('exceptions.service.options.must_be_child'), $exception->getMessage()); - } - } -} diff --git a/tests/Unit/Services/Services/Options/OptionUpdateServiceTest.php b/tests/Unit/Services/Services/Options/OptionUpdateServiceTest.php deleted file mode 100644 index 0990491ba..000000000 --- a/tests/Unit/Services/Services/Options/OptionUpdateServiceTest.php +++ /dev/null @@ -1,106 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Tests\Unit\Services\Services\Options; - -use Exception; -use Mockery as m; -use Tests\TestCase; -use Pterodactyl\Models\ServiceOption; -use Pterodactyl\Services\Services\Options\OptionUpdateService; -use Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface; -use Pterodactyl\Exceptions\Service\ServiceOption\NoParentConfigurationFoundException; - -class OptionUpdateServiceTest extends TestCase -{ - /** - * @var \Pterodactyl\Models\ServiceOption - */ - protected $model; - - /** - * @var \Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface - */ - protected $repository; - - /** - * @var \Pterodactyl\Services\Services\Options\OptionUpdateService - */ - protected $service; - - /** - * Setup tests. - */ - public function setUp() - { - parent::setUp(); - - $this->model = factory(ServiceOption::class)->make(); - $this->repository = m::mock(ServiceOptionRepositoryInterface::class); - - $this->service = new OptionUpdateService($this->repository); - } - - /** - * Test that an option is updated when no config_from attribute is passed. - */ - public function testOptionIsUpdatedWhenNoConfigFromIsProvided() - { - $this->repository->shouldReceive('withoutFresh')->withNoArgs()->once()->andReturnSelf() - ->shouldReceive('update')->with($this->model->id, ['test_field' => 'field_value'])->once()->andReturnNull(); - - $this->service->handle($this->model, ['test_field' => 'field_value']); - } - - /** - * Test that option is updated when a valid config_from attribute is passed. - */ - public function testOptionIsUpdatedWhenValidConfigFromIsPassed() - { - $this->repository->shouldReceive('findCountWhere')->with([ - ['service_id', '=', $this->model->service_id], - ['id', '=', 1], - ])->once()->andReturn(1); - - $this->repository->shouldReceive('withoutFresh')->withNoArgs()->once()->andReturnSelf() - ->shouldReceive('update')->with($this->model->id, ['config_from' => 1])->once()->andReturnNull(); - - $this->service->handle($this->model, ['config_from' => 1]); - } - - /** - * Test that an exception is thrown if an invalid config_from attribute is passed. - */ - public function testExceptionIsThrownIfInvalidParentConfigIsPassed() - { - $this->repository->shouldReceive('findCountWhere')->with([ - ['service_id', '=', $this->model->service_id], - ['id', '=', 1], - ])->once()->andReturn(0); - - try { - $this->service->handle($this->model, ['config_from' => 1]); - } catch (Exception $exception) { - $this->assertInstanceOf(NoParentConfigurationFoundException::class, $exception); - $this->assertEquals(trans('exceptions.service.options.must_be_child'), $exception->getMessage()); - } - } - - /** - * Test that an integer linking to a model can be passed in place of the ServiceOption model. - */ - public function testIntegerCanBePassedInPlaceOfModel() - { - $this->repository->shouldReceive('find')->with($this->model->id)->once()->andReturn($this->model); - $this->repository->shouldReceive('withoutFresh')->withNoArgs()->once()->andReturnSelf() - ->shouldReceive('update')->with($this->model->id, ['test_field' => 'field_value'])->once()->andReturnNull(); - - $this->service->handle($this->model->id, ['test_field' => 'field_value']); - } -} diff --git a/tests/Unit/Services/Services/Variables/VariableCreationServiceTest.php b/tests/Unit/Services/Services/Variables/VariableCreationServiceTest.php deleted file mode 100644 index 9788ac79b..000000000 --- a/tests/Unit/Services/Services/Variables/VariableCreationServiceTest.php +++ /dev/null @@ -1,127 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Tests\Unit\Services\Services\Variables; - -use Mockery as m; -use Tests\TestCase; -use Pterodactyl\Models\ServiceOption; -use Pterodactyl\Models\ServiceVariable; -use Pterodactyl\Services\Services\Variables\VariableCreationService; -use Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface; -use Pterodactyl\Contracts\Repository\ServiceVariableRepositoryInterface; - -class VariableCreationServiceTest extends TestCase -{ - /** - * @var \Pterodactyl\Contracts\Repository\ServiceOptionRepositoryInterface - */ - protected $serviceOptionRepository; - - /** - * @var \Pterodactyl\Contracts\Repository\ServiceVariableRepositoryInterface - */ - protected $serviceVariableRepository; - - /** - * @var \Pterodactyl\Services\Services\Variables\VariableCreationService - */ - protected $service; - - /** - * Setup tests. - */ - public function setUp() - { - parent::setUp(); - - $this->serviceOptionRepository = m::mock(ServiceOptionRepositoryInterface::class); - $this->serviceVariableRepository = m::mock(ServiceVariableRepositoryInterface::class); - - $this->service = new VariableCreationService($this->serviceOptionRepository, $this->serviceVariableRepository); - } - - /** - * Test basic functionality, data should be stored in the database. - */ - public function testVariableIsCreatedAndStored() - { - $data = ['env_variable' => 'TEST_VAR_123']; - $this->serviceVariableRepository->shouldReceive('create')->with([ - 'option_id' => 1, - 'user_viewable' => false, - 'user_editable' => false, - 'env_variable' => 'TEST_VAR_123', - ])->once()->andReturn(new ServiceVariable); - - $this->assertInstanceOf(ServiceVariable::class, $this->service->handle(1, $data)); - } - - /** - * Test that the option key in the data array is properly parsed. - */ - public function testOptionsPassedInArrayKeyAreParsedProperly() - { - $data = ['env_variable' => 'TEST_VAR_123', 'options' => ['user_viewable', 'user_editable']]; - $this->serviceVariableRepository->shouldReceive('create')->with([ - 'option_id' => 1, - 'user_viewable' => true, - 'user_editable' => true, - 'env_variable' => 'TEST_VAR_123', - 'options' => ['user_viewable', 'user_editable'], - ])->once()->andReturn(new ServiceVariable); - - $this->assertInstanceOf(ServiceVariable::class, $this->service->handle(1, $data)); - } - - /** - * Test that all of the reserved variables defined in the model trigger an exception. - * - * @dataProvider reservedNamesProvider - * @expectedException \Pterodactyl\Exceptions\Service\ServiceVariable\ReservedVariableNameException - */ - public function testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames($variable) - { - $this->service->handle(1, ['env_variable' => $variable]); - } - - /** - * Test that a model can be passed in place of an integer. - */ - public function testModelCanBePassedInPlaceOfInteger() - { - $model = factory(ServiceOption::class)->make(); - $data = ['env_variable' => 'TEST_VAR_123']; - - $this->serviceVariableRepository->shouldReceive('create')->with([ - 'option_id' => $model->id, - 'user_viewable' => false, - 'user_editable' => false, - 'env_variable' => 'TEST_VAR_123', - ])->once()->andReturn(new ServiceVariable); - - $this->assertInstanceOf(ServiceVariable::class, $this->service->handle($model, $data)); - } - - /** - * Provides the data to be used in the tests. - * - * @return array - */ - public function reservedNamesProvider() - { - $data = []; - $exploded = explode(',', ServiceVariable::RESERVED_ENV_NAMES); - foreach ($exploded as $e) { - $data[] = [$e]; - } - - return $data; - } -}