diff --git a/app/Contracts/Repository/Daemon/ServerRepositoryInterface.php b/app/Contracts/Repository/Daemon/ServerRepositoryInterface.php index d8659de3d..a0cfc8e2b 100644 --- a/app/Contracts/Repository/Daemon/ServerRepositoryInterface.php +++ b/app/Contracts/Repository/Daemon/ServerRepositoryInterface.php @@ -47,9 +47,10 @@ interface ServerRepositoryInterface extends BaseRepositoryInterface /** * Mark a server to be reinstalled on the system. * + * @param array|null $data * @return \Psr\Http\Message\ResponseInterface */ - public function reinstall(); + public function reinstall($data = null); /** * Mark a server as needing a container rebuild the next time the server is booted. @@ -71,4 +72,11 @@ interface ServerRepositoryInterface extends BaseRepositoryInterface * @return \Psr\Http\Message\ResponseInterface */ public function unsuspend(); + + /** + * Delete a server on the daemon. + * + * @return \Psr\Http\Message\ResponseInterface + */ + public function delete(); } diff --git a/app/Contracts/Repository/RepositoryInterface.php b/app/Contracts/Repository/RepositoryInterface.php index cb8241e38..1f498b6ea 100644 --- a/app/Contracts/Repository/RepositoryInterface.php +++ b/app/Contracts/Repository/RepositoryInterface.php @@ -141,6 +141,19 @@ interface RepositoryInterface */ public function updateWhereIn($column, array $values, array $fields); + /** + * Update a record if it exists in the database, otherwise create it. + * + * @param array $where + * @param array $fields + * @param bool $validate + * @param bool $force + * @return mixed + * + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + public function updateOrCreate(array $where, array $fields, $validate = true, $force = false); + /** * Update multiple records matching the passed clauses. * diff --git a/app/Contracts/Repository/ServerRepositoryInterface.php b/app/Contracts/Repository/ServerRepositoryInterface.php index ece4cee6d..4b9d3b961 100644 --- a/app/Contracts/Repository/ServerRepositoryInterface.php +++ b/app/Contracts/Repository/ServerRepositoryInterface.php @@ -77,4 +77,14 @@ interface ServerRepositoryInterface extends RepositoryInterface, SearchableInter * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ public function getWithDatabases($id); + + /** + * Return data about the daemon service in a consumable format. + * + * @param int $id + * @return array + * + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function getDaemonServiceData($id); } diff --git a/app/Http/Controllers/Admin/ServersController.php b/app/Http/Controllers/Admin/ServersController.php index 851cd287c..dd69f2d55 100644 --- a/app/Http/Controllers/Admin/ServersController.php +++ b/app/Http/Controllers/Admin/ServersController.php @@ -25,7 +25,6 @@ namespace Pterodactyl\Http\Controllers\Admin; use Illuminate\Contracts\Config\Repository as ConfigRepository; -use Log; use Alert; use Javascript; use Prologue\Alerts\AlertsMessageBag; @@ -38,17 +37,17 @@ use Pterodactyl\Contracts\Repository\ServiceRepositoryInterface; use Pterodactyl\Http\Requests\Admin\ServerFormRequest; use Pterodactyl\Models\Server; use Illuminate\Http\Request; -use GuzzleHttp\Exception\TransferException; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Repositories\Eloquent\DatabaseHostRepository; -use Pterodactyl\Exceptions\DisplayValidationException; -use Pterodactyl\Services\Database\CreationService as DatabaseCreationService; +use Pterodactyl\Services\Database\DatabaseManagementService; use Pterodactyl\Services\Servers\BuildModificationService; use Pterodactyl\Services\Servers\ContainerRebuildService; use Pterodactyl\Services\Servers\CreationService; +use Pterodactyl\Services\Servers\DeletionService; use Pterodactyl\Services\Servers\DetailsModificationService; use Pterodactyl\Services\Servers\ReinstallService; +use Pterodactyl\Services\Servers\StartupModificationService; use Pterodactyl\Services\Servers\SuspensionService; class ServersController extends Controller @@ -84,15 +83,20 @@ class ServersController extends Controller protected $databaseRepository; /** - * @var \Pterodactyl\Services\Database\CreationService + * @var \Pterodactyl\Services\Database\DatabaseManagementService */ - protected $databaseCreationService; + protected $databaseManagementService; /** * @var \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface */ protected $databaseHostRepository; + /** + * @var \Pterodactyl\Services\Servers\DeletionService + */ + protected $deletionService; + /** * @var \Pterodactyl\Services\Servers\DetailsModificationService */ @@ -128,6 +132,11 @@ class ServersController extends Controller */ protected $serviceRepository; + /** + * @var \Pterodactyl\Services\Servers\StartupModificationService + */ + private $startupModificationService; + /** * @var \Pterodactyl\Services\Servers\SuspensionService */ @@ -142,15 +151,17 @@ class ServersController extends Controller * @param \Illuminate\Contracts\Config\Repository $config * @param \Pterodactyl\Services\Servers\ContainerRebuildService $containerRebuildService * @param \Pterodactyl\Services\Servers\CreationService $service - * @param \Pterodactyl\Services\Database\CreationService $databaseCreationService + * @param \Pterodactyl\Services\Database\DatabaseManagementService $databaseManagementService * @param \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface $databaseRepository * @param \Pterodactyl\Repositories\Eloquent\DatabaseHostRepository $databaseHostRepository + * @param \Pterodactyl\Services\Servers\DeletionService $deletionService * @param \Pterodactyl\Services\Servers\DetailsModificationService $detailsModificationService * @param \Pterodactyl\Contracts\Repository\LocationRepositoryInterface $locationRepository * @param \Pterodactyl\Contracts\Repository\NodeRepositoryInterface $nodeRepository * @param \Pterodactyl\Services\Servers\ReinstallService $reinstallService * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository * @param \Pterodactyl\Contracts\Repository\ServiceRepositoryInterface $serviceRepository + * @param \Pterodactyl\Services\Servers\StartupModificationService $startupModificationService * @param \Pterodactyl\Services\Servers\SuspensionService $suspensionService */ public function __construct( @@ -160,15 +171,17 @@ class ServersController extends Controller ConfigRepository $config, ContainerRebuildService $containerRebuildService, CreationService $service, - DatabaseCreationService $databaseCreationService, + DatabaseManagementService $databaseManagementService, DatabaseRepositoryInterface $databaseRepository, DatabaseHostRepository $databaseHostRepository, + DeletionService $deletionService, DetailsModificationService $detailsModificationService, LocationRepositoryInterface $locationRepository, NodeRepositoryInterface $nodeRepository, ReinstallService $reinstallService, ServerRepositoryInterface $repository, ServiceRepositoryInterface $serviceRepository, + StartupModificationService $startupModificationService, SuspensionService $suspensionService ) { $this->alert = $alert; @@ -176,16 +189,18 @@ class ServersController extends Controller $this->buildModificationService = $buildModificationService; $this->config = $config; $this->containerRebuildService = $containerRebuildService; - $this->databaseCreationService = $databaseCreationService; + $this->databaseManagementService = $databaseManagementService; $this->databaseRepository = $databaseRepository; $this->databaseHostRepository = $databaseHostRepository; $this->detailsModificationService = $detailsModificationService; + $this->deletionService = $deletionService; $this->locationRepository = $locationRepository; $this->nodeRepository = $nodeRepository; $this->reinstallService = $reinstallService; $this->repository = $repository; $this->service = $service; $this->serviceRepository = $serviceRepository; + $this->startupModificationService = $startupModificationService; $this->suspensionService = $suspensionService; } @@ -234,21 +249,15 @@ class ServersController extends Controller * @param \Pterodactyl\Http\Requests\Admin\ServerFormRequest $request * @return \Illuminate\Http\RedirectResponse * + * @throws \Pterodactyl\Exceptions\DisplayException * @throws \Pterodactyl\Exceptions\Model\DataValidationException */ public function store(ServerFormRequest $request) { - try { - $server = $this->service->create($request->except('_token')); + $server = $this->service->create($request->except('_token')); + $this->alert->success(trans('admin/server.alerts.server_created'))->flash(); - return redirect()->route('admin.servers.view', $server->id); - } catch (TransferException $ex) { - Log::warning($ex); - Alert::danger('A TransferException was encountered while trying to contact the daemon, please ensure it is online and accessible. This error has been logged.') - ->flash(); - } - - return redirect()->route('admin.servers.new')->withInput(); + return redirect()->route('admin.servers.view', $server->id); } /** @@ -508,9 +517,9 @@ class ServersController extends Controller */ public function updateBuild(Request $request, Server $server) { - $this->buildModificationService->handle($server, $request->intersect([ - 'allocation_id', 'add_allocations', 'remove_allocations', - 'memory', 'swap', 'io', 'cpu', 'disk', + $this->buildModificationService->handle($server, $request->only([ + 'allocation_id', 'add_allocations', 'remove_allocations', + 'memory', 'swap', 'io', 'cpu', 'disk', ])); $this->alert->success(trans('admin/server.alerts.build_updated'))->flash(); @@ -520,69 +529,38 @@ class ServersController extends Controller /** * Start the server deletion process. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Illuminate\Http\Request $request + * @param \Pterodactyl\Models\Server $server * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\DisplayException */ - public function delete(Request $request, $id) + public function delete(Request $request, Server $server) { - $repo = new ServerRepository; + $this->deletionService->withForce($request->has('force_delete'))->handle($server); + $this->alert->success(trans('admin/server.alerts.server_deleted'))->flash(); - try { - $repo->delete($id, $request->has('force_delete')); - Alert::success('Server was successfully deleted from the system.')->flash(); - - return redirect()->route('admin.servers'); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (TransferException $ex) { - Log::warning($ex); - Alert::danger('A TransferException occurred while attempting to delete this server from the daemon, please ensure it is running. This error has been logged.') - ->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An unhandled exception occured while attemping to delete this server. This error has been logged.') - ->flash(); - } - - return redirect()->route('admin.servers.view.delete', $id); + return redirect()->route('admin.servers'); } /** * Update the startup command as well as variables. * - * @param \Illuminate\Http\Request $request - * @param int $id + * @param \Illuminate\Http\Request $request + * @param \Pterodactyl\Models\Server $server * @return \Illuminate\Http\RedirectResponse + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException */ - public function saveStartup(Request $request, $id) + public function saveStartup(Request $request, Server $server) { - $repo = new ServerRepository; + $this->startupModificationService->isAdmin()->handle( + $server, $request->except('_token') + ); + $this->alert->success(trans('admin/server.alerts.startup_changed'))->flash(); - try { - if ($repo->updateStartup($id, $request->except('_token'), true)) { - Alert::success('Service configuration successfully modfied for this server, reinstalling now.') - ->flash(); - - return redirect()->route('admin.servers.view', $id); - } else { - Alert::success('Startup variables were successfully modified and assigned for this server.')->flash(); - } - } catch (DisplayValidationException $ex) { - return redirect()->route('admin.servers.view.startup', $id)->withErrors(json_decode($ex->getMessage())); - } catch (DisplayException $ex) { - Alert::danger($ex->getMessage())->flash(); - } catch (TransferException $ex) { - Log::warning($ex); - Alert::danger('A TransferException occurred while attempting to update the startup for this server, please ensure the daemon is running. This error has been logged.') - ->flash(); - } catch (\Exception $ex) { - Log::error($ex); - Alert::danger('An unhandled exception occured while attemping to update startup variables for this server. This error has been logged.') - ->flash(); - } - - return redirect()->route('admin.servers.view.startup', $id); + return redirect()->route('admin.servers.view.startup', $server->id); } /** @@ -598,7 +576,7 @@ class ServersController extends Controller */ public function newDatabase(Request $request, $server) { - $this->databaseCreationService->create($server, [ + $this->databaseManagementService->create($server, [ 'database' => $request->input('database'), 'remote' => $request->input('remote'), 'database_host_id' => $request->input('database_host_id'), @@ -624,7 +602,7 @@ class ServersController extends Controller ['id', '=', $request->input('database')], ]); - $this->databaseCreationService->changePassword($database->id, str_random(20)); + $this->databaseManagementService->changePassword($database->id, str_random(20)); return response('', 204); } @@ -646,7 +624,7 @@ class ServersController extends Controller ['id', '=', $database], ]); - $this->databaseCreationService->delete($database->id); + $this->databaseManagementService->delete($database->id); return response('', 204); } diff --git a/app/Http/Requests/Admin/ServerFormRequest.php b/app/Http/Requests/Admin/ServerFormRequest.php index 19445b58f..c521a2f8b 100644 --- a/app/Http/Requests/Admin/ServerFormRequest.php +++ b/app/Http/Requests/Admin/ServerFormRequest.php @@ -75,14 +75,14 @@ class ServerFormRequest extends AdminFormRequest return ! ($input->auto_deploy); }); - if ($this->input('pack_id') !== 0) { - $validator->sometimes('pack_id', [ - Rule::exists('packs', 'id')->where(function ($query) { - $query->where('selectable', 1); - $query->where('option_id', $this->input('option_id')); - }), - ]); - } + $validator->sometimes('pack_id', [ + Rule::exists('packs', 'id')->where(function ($query) { + $query->where('selectable', 1); + $query->where('option_id', $this->input('option_id')); + }), + ], function ($input) { + return $input->pack_id !== 0 && $input->pack_id !== null; + }); }); } } diff --git a/app/Repositories/Daemon/ServerRepository.php b/app/Repositories/Daemon/ServerRepository.php index e3e197dbf..359963eb6 100644 --- a/app/Repositories/Daemon/ServerRepository.php +++ b/app/Repositories/Daemon/ServerRepository.php @@ -59,7 +59,7 @@ class ServerRepository extends BaseRepository implements ServerRepositoryInterfa 'io' => (int) $server->io, 'cpu' => (int) $server->cpu, 'disk' => (int) $server->disk, - 'image' => (int) $server->image, + 'image' => $server->image, ], 'service' => [ 'type' => $server->option->service->folder, @@ -97,9 +97,15 @@ class ServerRepository extends BaseRepository implements ServerRepositoryInterfa /** * {@inheritdoc} */ - public function reinstall() + public function reinstall($data = null) { - return $this->getHttpClient()->request('POST', '/server/reinstall'); + if (is_null($data)) { + return $this->getHttpClient()->request('POST', '/server/reinstall'); + } + + return $this->getHttpClient()->request('POST', '/server/reinstall', [ + 'json' => $data, + ]); } /** @@ -125,4 +131,12 @@ class ServerRepository extends BaseRepository implements ServerRepositoryInterfa { return $this->getHttpClient()->request('POST', '/server/unsuspend'); } + + /** + * {@inheritdoc} + */ + public function delete() + { + return $this->getHttpClient()->request('DELETE', '/servers'); + } } diff --git a/app/Repositories/Eloquent/EloquentRepository.php b/app/Repositories/Eloquent/EloquentRepository.php index 5746a562c..fa39848a9 100644 --- a/app/Repositories/Eloquent/EloquentRepository.php +++ b/app/Repositories/Eloquent/EloquentRepository.php @@ -176,4 +176,19 @@ abstract class EloquentRepository extends Repository implements RepositoryInterf { return $this->getBuilder()->insert($data); } + + /** + * {@inheritdoc} + * @return bool|\Illuminate\Database\Eloquent\Model + */ + public function updateOrCreate(array $where, array $fields, $validate = true, $force = false) + { + $instance = $this->withColumns('id')->findWhere($where)->first(); + + if (! $instance) { + return $this->create(array_merge($where, $fields), $validate, $force); + } + + return $this->update($instance->id, $fields, $validate, $force); + } } diff --git a/app/Repositories/Eloquent/ServerRepository.php b/app/Repositories/Eloquent/ServerRepository.php index 073950e29..967e208d4 100644 --- a/app/Repositories/Eloquent/ServerRepository.php +++ b/app/Repositories/Eloquent/ServerRepository.php @@ -129,4 +129,22 @@ class ServerRepository extends SearchableRepository implements ServerRepositoryI return $instance; } + + /** + * {@inheritdoc} + */ + public function getDaemonServiceData($id) + { + $instance = $this->getBuilder()->with('option.service', 'pack')->find($id, $this->getColumns()); + + if (! $instance) { + throw new RecordNotFoundException(); + } + + return [ + 'type' => $instance->option->service->folder, + 'option' => $instance->option->tag, + 'pack' => (! is_null($instance->pack_id)) ? $instance->pack->uuid : null, + ]; + } } diff --git a/app/Services/Database/CreationService.php b/app/Services/Database/DatabaseManagementService.php similarity index 99% rename from app/Services/Database/CreationService.php rename to app/Services/Database/DatabaseManagementService.php index 71e589412..a0343fc7a 100644 --- a/app/Services/Database/CreationService.php +++ b/app/Services/Database/DatabaseManagementService.php @@ -29,7 +29,7 @@ use Illuminate\Contracts\Encryption\Encrypter; use Pterodactyl\Extensions\DynamicDatabaseConnection; use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface; -class CreationService +class DatabaseManagementService { /** * @var \Illuminate\Database\DatabaseManager diff --git a/app/Services/Servers/BuildModificationService.php b/app/Services/Servers/BuildModificationService.php index 26f64632f..19ce0c3a5 100644 --- a/app/Services/Servers/BuildModificationService.php +++ b/app/Services/Servers/BuildModificationService.php @@ -106,13 +106,18 @@ class BuildModificationService } /** - * Return the build array. + * Return the build array or an item out of the build array. * - * @return array + * @param string|null $attribute + * @return array|mixed|null */ - public function getBuild() + public function getBuild($attribute = null) { - return $this->build; + if (is_null($attribute)) { + return $this->build; + } + + return array_get($this->build, $attribute); } /** @@ -133,17 +138,7 @@ class BuildModificationService $data['allocation_id'] = array_get($data, 'allocation_id', $server->allocation_id); $this->database->beginTransaction(); - $this->setBuild('memory', (int) array_get($data, 'memory', $server->memory)); - $this->setBuild('swap', (int) array_get($data, 'swap', $server->swap)); - $this->setBuild('io', (int) array_get($data, 'io', $server->io)); - $this->setBuild('cpu', (int) array_get($data, 'cpu', $server->cpu)); - $this->setBuild('disk', (int) array_get($data, 'disk', $server->disk)); - $this->processAllocations($server, $data); - $allocations = $this->allocationRepository->findWhere([ - ['server_id', '=', $server->id], - ]); - if (isset($data['allocation_id']) && $data['allocation_id'] != $server->allocation_id) { try { $allocation = $this->allocationRepository->findFirstWhere([ @@ -157,6 +152,24 @@ class BuildModificationService $this->setBuild('default', ['ip' => $allocation->ip, 'port' => $allocation->port]); } + $server = $this->repository->update($server->id, [ + 'memory' => array_get($data, 'memory', $server->memory), + 'swap' => array_get($data, 'swap', $server->swap), + 'io' => array_get($data, 'io', $server->io), + 'cpu' => array_get($data, 'cpu', $server->cpu), + 'disk' => array_get($data, 'disk', $server->disk), + 'allocation_id' => array_get($data, 'allocation_id', $server->allocation_id), + ]); + + $allocations = $this->allocationRepository->findWhere([ + ['server_id', '=', $server->id], + ]); + + $this->setBuild('memory', (int) $server->memory); + $this->setBuild('swap', (int) $server->swap); + $this->setBuild('io', (int) $server->io); + $this->setBuild('cpu', (int) $server->cpu); + $this->setBuild('disk', (int) $server->disk); $this->setBuild('ports|overwrite', $allocations->groupBy('ip')->map(function ($item) { return $item->pluck('port'); })->toArray()); diff --git a/app/Services/Servers/CreationService.php b/app/Services/Servers/CreationService.php index fbe895052..5c641d2d1 100644 --- a/app/Services/Servers/CreationService.php +++ b/app/Services/Servers/CreationService.php @@ -138,7 +138,7 @@ class CreationService public function create(array $data) { // @todo auto-deployment - $validator = $this->validatorService->setAdmin()->setFields($data['environment'])->validate($data['option_id']); + $validator = $this->validatorService->isAdmin()->setFields($data['environment'])->validate($data['option_id']); $uniqueShort = bin2hex(random_bytes(4)); $this->database->beginTransaction(); @@ -151,7 +151,7 @@ class CreationService 'description' => $data['description'], 'skip_scripts' => isset($data['skip_scripts']), 'suspended' => false, - 'owner_id' => $data['user_id'], + 'owner_id' => $data['owner_id'], 'memory' => $data['memory'], 'swap' => $data['swap'], 'disk' => $data['disk'], diff --git a/app/Services/Servers/DeletionService.php b/app/Services/Servers/DeletionService.php new file mode 100644 index 000000000..5d63ed094 --- /dev/null +++ b/app/Services/Servers/DeletionService.php @@ -0,0 +1,153 @@ +. + * + * 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. + */ + +namespace Pterodactyl\Services\Servers; + +use Illuminate\Log\Writer; +use Pterodactyl\Models\Server; +use GuzzleHttp\Exception\RequestException; +use Illuminate\Database\ConnectionInterface; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Services\Database\DatabaseManagementService; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; +use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface; +use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; + +class DeletionService +{ + /** + * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface + */ + protected $daemonServerRepository; + + /** + * @var \Illuminate\Database\ConnectionInterface + */ + protected $database; + + /** + * @var \Pterodactyl\Services\Database\DatabaseManagementService + */ + protected $databaseManagementService; + + /** + * @var \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface + */ + protected $databaseRepository; + + /** + * @var bool + */ + protected $force = false; + + /** + * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface + */ + protected $repository; + + /** + * @var \Illuminate\Log\Writer + */ + protected $writer; + + /** + * DeletionService constructor. + * + * @param \Illuminate\Database\ConnectionInterface $database + * @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonServerRepository + * @param \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface $databaseRepository + * @param \Pterodactyl\Services\Database\DatabaseManagementService $databaseManagementService + * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository + * @param \Illuminate\Log\Writer $writer + */ + public function __construct( + ConnectionInterface $database, + DaemonServerRepositoryInterface $daemonServerRepository, + DatabaseRepositoryInterface $databaseRepository, + DatabaseManagementService $databaseManagementService, + ServerRepositoryInterface $repository, + Writer $writer + ) { + $this->daemonServerRepository = $daemonServerRepository; + $this->database = $database; + $this->databaseManagementService = $databaseManagementService; + $this->databaseRepository = $databaseRepository; + $this->repository = $repository; + $this->writer = $writer; + } + + /** + * Set if the server should be forcibly deleted from the panel (ignoring daemon errors) or not. + * + * @param bool $bool + * @return $this + */ + public function withForce($bool = true) + { + $this->force = $bool; + + return $this; + } + + /** + * Delete a server from the panel and remove any associated databases from hosts. + * + * @param int|\Pterodactyl\Models\Server $server + * @throws \Pterodactyl\Exceptions\DisplayException + */ + public function handle($server) + { + if (! $server instanceof Server) { + $server = $this->repository->withColumns(['id', 'node_id', 'uuid'])->find($server); + } + + try { + $this->daemonServerRepository->setNode($server->node_id)->setAccessServer($server->uuid)->delete(); + } catch (RequestException $exception) { + $response = $exception->getResponse(); + + if (is_null($response) || (! is_null($response) && $response->getStatusCode() !== 404)) { + $this->writer->warning($exception); + + // If not forcing the deletion, throw an exception, otherwise just log it and + // continue with server deletion process in the panel. + if (! $this->force) { + throw new DisplayException(trans('admin/server.exceptions.daemon_exception', [ + 'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(), + ])); + } + } + } + + $this->database->beginTransaction(); + + $this->databaseRepository->withColumns('id')->findWhere([['server_id', '=', $server->id]])->each(function ($item) { + $this->databaseManagementService->delete($item->id); + }); + + $this->repository->delete($server->id); + + $this->database->commit(); + } +} diff --git a/app/Services/Servers/StartupModificationService.php b/app/Services/Servers/StartupModificationService.php new file mode 100644 index 000000000..00eaaafd8 --- /dev/null +++ b/app/Services/Servers/StartupModificationService.php @@ -0,0 +1,195 @@ +. + * + * 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. + */ + +namespace Pterodactyl\Services\Servers; + +use GuzzleHttp\Exception\RequestException; +use Illuminate\Database\ConnectionInterface; +use Illuminate\Log\Writer; +use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; +use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; +use Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Models\Server; + +class StartupModificationService +{ + /** + * @var bool + */ + protected $admin = false; + + /** + * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface + */ + protected $daemonServerRepository; + + /** + * @var \Illuminate\Database\ConnectionInterface + */ + protected $database; + + /** + * @var \Pterodactyl\Services\Servers\EnvironmentService + */ + protected $environmentService; + + /** + * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface + */ + protected $repository; + + /** + * @var \Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface + */ + protected $serverVariableRepository; + + /** + * @var \Pterodactyl\Services\Servers\VariableValidatorService + */ + protected $validatorService; + + /** + * @var \Illuminate\Log\Writer + */ + protected $writer; + + /** + * StartupModificationService constructor. + * + * @param \Illuminate\Database\ConnectionInterface $database + * @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonServerRepository + * @param \Pterodactyl\Services\Servers\EnvironmentService $environmentService + * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository + * @param \Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface $serverVariableRepository + * @param \Pterodactyl\Services\Servers\VariableValidatorService $validatorService + * @param \Illuminate\Log\Writer $writer + */ + public function __construct( + ConnectionInterface $database, + DaemonServerRepositoryInterface $daemonServerRepository, + EnvironmentService $environmentService, + ServerRepositoryInterface $repository, + ServerVariableRepositoryInterface $serverVariableRepository, + VariableValidatorService $validatorService, + Writer $writer + ) { + $this->daemonServerRepository = $daemonServerRepository; + $this->database = $database; + $this->environmentService = $environmentService; + $this->repository = $repository; + $this->serverVariableRepository = $serverVariableRepository; + $this->validatorService = $validatorService; + $this->writer = $writer; + } + + /** + * Determine if this function should run at an administrative level. + * + * @param bool $bool + * @return $this + */ + public function isAdmin($bool = true) + { + $this->admin = $bool; + + return $this; + } + + /** + * Process startup modification for a server. + * + * @param int|\Pterodactyl\Models\Server $server + * @param array $data + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + */ + public function handle($server, array $data) + { + if (! $server instanceof Server) { + $server = $this->repository->find($server); + } + + if ( + $server->service_id != array_get($data, 'service_id', $server->service_id) || + $server->option_id != array_get($data, 'option_id', $server->option_id) || + $server->pack_id != array_get($data, 'pack_id', $server->pack_id) + ) { + $hasServiceChanges = true; + } + + $this->database->beginTransaction(); + if (isset($data['environment'])) { + $validator = $this->validatorService->isAdmin($this->admin) + ->setFields($data['environment']) + ->validate(array_get($data, 'option_id', $server->option_id)); + + foreach ($validator->getResults() as $result) { + $this->serverVariableRepository->withoutFresh()->updateOrCreate([ + 'server_id' => $server->id, + 'variable_id' => $result['id'], + ], [ + 'variable_value' => $result['value'], + ]); + } + } + + $daemonData = [ + 'build' => [ + 'env|overwrite' => $this->environmentService->process($server), + ], + ]; + + if ($this->admin) { + $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), + 'skip_scripts' => isset($data['skip_scripts']), + ]); + + if (isset($hasServiceChanges)) { + $daemonData['service'] = array_merge( + $this->repository->withColumns(['id', 'option_id', 'pack_id'])->getDaemonServiceData($server), + ['skip_scripts' => isset($data['skip_scripts'])] + ); + } + } + + try { + $this->daemonServerRepository->setNode($server->node_id)->setAccessServer($server->uuid)->update($daemonData); + $this->database->commit(); + } catch (RequestException $exception) { + $response = $exception->getResponse(); + $this->writer->warning($exception); + + throw new DisplayException(trans('admin/server.exceptions.daemon_exception', [ + 'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(), + ])); + } + } +} diff --git a/app/Services/Servers/VariableValidatorService.php b/app/Services/Servers/VariableValidatorService.php index 3be004944..d0bb2ccd0 100644 --- a/app/Services/Servers/VariableValidatorService.php +++ b/app/Services/Servers/VariableValidatorService.php @@ -103,11 +103,12 @@ class VariableValidatorService /** * Set this function to be running at the administrative level. * + * @param bool $bool * @return $this */ - public function setAdmin() + public function isAdmin($bool = true) { - $this->isAdmin = true; + $this->isAdmin = $bool; return $this; } diff --git a/database/migrations/2017_07_24_194433_DeleteTaskWhenParentServerIsDeleted.php b/database/migrations/2017_07_24_194433_DeleteTaskWhenParentServerIsDeleted.php new file mode 100644 index 000000000..8a3d78426 --- /dev/null +++ b/database/migrations/2017_07_24_194433_DeleteTaskWhenParentServerIsDeleted.php @@ -0,0 +1,36 @@ +dropForeign(['server_id']); + + $table->foreign('server_id')->references('id')->on('servers')->onDelete('cascade'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('tasks', function (Blueprint $table) { + $table->dropForeign(['server_id']); + + $table->foreign('server_id')->references('id')->on('servers'); + }); + } +} diff --git a/resources/lang/en/admin/server.php b/resources/lang/en/admin/server.php index ab4057d9e..6cf48223a 100644 --- a/resources/lang/en/admin/server.php +++ b/resources/lang/en/admin/server.php @@ -31,6 +31,9 @@ return [ 'default_allocation_not_found' => 'The requested default allocation was not found in this server\'s allocations.', ], 'alerts' => [ + 'startup_changed' => 'The startup configuration for this server has been updated. If this server\'s service or option was changed a reinstall will be occuring now.', + 'server_deleted' => 'Server has successfully been deleted from the system.', + 'server_created' => 'Server was successfully created on the panel. Please allow the daemon a few minutes to completely install this server.', 'build_updated' => 'The build details for this server have been updated. Some changes may require a restart to take effect.', 'suspension_toggled' => 'Server suspension status has been changed to :status.', 'rebuild_on_boot' => 'This server has been marked as requiring a Docker Container rebuild. This will happen the next time the server is started.', diff --git a/resources/themes/pterodactyl/admin/servers/new.blade.php b/resources/themes/pterodactyl/admin/servers/new.blade.php index 8a68b197f..874dc55f8 100644 --- a/resources/themes/pterodactyl/admin/servers/new.blade.php +++ b/resources/themes/pterodactyl/admin/servers/new.blade.php @@ -49,7 +49,7 @@