diff --git a/CHANGELOG.md b/CHANGELOG.md index 936c2b6a0..cfc6c0f9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ This file is a running track of new features and fixes to each version of the pa This project follows [Semantic Versioning](http://semver.org) guidelines. +## v1.1.3 +### Fixed +* Server bulk power actions command will no longer attempt to run commands against installing or suspended servers. + ## v1.1.2 ### Fixed * Fixes an exception thrown while trying to validate IP access for the client API. diff --git a/README.md b/README.md index ace35378b..418135fa3 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ I would like to extend my sincere thanks to the following sponsors for helping f | [**MineStrator**](https://minestrator.com/) | Looking for a French highend hosting company for you minecraft server? More than 14,000 members on our discord, trust us. | | [**DedicatedMC**](https://dedicatedmc.io/) | DedicatedMC provides Raw Power hosting at affordable pricing, making sure to never compromise on your performance and giving you the best performance money can buy. | | [**Skynode**](https://www.skynode.pro/) | Skynode provides blazing fast game servers along with a top-notch user experience. Whatever our clients are looking for, we're able to provide it! | -| [**XCORE-SERVER.de**](https://xcore-server.de/) | XCORE-SERVER.de offers High-End Servers for hosting and gaming since 2012. Fast, excellent and well-known for eSports Gaming. | +| [**XCORE**](https://xcore-server.de/) | XCORE offers High-End Servers for hosting and gaming since 2012. Fast, excellent and well-known for eSports Gaming. | | [**RoyaleHosting**](https://royalehosting.net/) | Build your dreams and deploy them with RoyaleHosting’s reliable servers and network. Easy to use, provisioned in a couple of minutes. | | [**Spill Hosting**](https://spillhosting.no/) | Spill Hosting is a Norwegian hosting service, which aims to cheap services on quality servers. Premium i9-9900K processors will run your game like a dream. | | [**DeinServerHost**](https://deinserverhost.de/) | DeinServerHost offers Dedicated, vps and Gameservers for many popular Games like Minecraft and Rust in Germany since 2013. | diff --git a/app/Console/Commands/Server/BulkPowerActionCommand.php b/app/Console/Commands/Server/BulkPowerActionCommand.php index 383879902..32d9868b5 100644 --- a/app/Console/Commands/Server/BulkPowerActionCommand.php +++ b/app/Console/Commands/Server/BulkPowerActionCommand.php @@ -2,30 +2,15 @@ namespace Pterodactyl\Console\Commands\Server; +use Pterodactyl\Models\Server; use Illuminate\Console\Command; -use GuzzleHttp\Exception\RequestException; use Illuminate\Validation\ValidationException; use Illuminate\Validation\Factory as ValidatorFactory; use Pterodactyl\Repositories\Wings\DaemonPowerRepository; -use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; +use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; class BulkPowerActionCommand extends Command { - /** - * @var \Pterodactyl\Repositories\Wings\DaemonPowerRepository - */ - private $powerRepository; - - /** - * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface - */ - private $repository; - - /** - * @var \Illuminate\Validation\Factory - */ - private $validator; - /** * @var string */ @@ -39,37 +24,20 @@ class BulkPowerActionCommand extends Command */ protected $description = 'Perform bulk power management on large groupings of servers or nodes at once.'; - /** - * BulkPowerActionCommand constructor. - * - * @param \Pterodactyl\Repositories\Wings\DaemonPowerRepository $powerRepository - * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository - * @param \Illuminate\Validation\Factory $validator - */ - public function __construct( - DaemonPowerRepository $powerRepository, - ServerRepositoryInterface $repository, - ValidatorFactory $validator - ) { - parent::__construct(); - - $this->repository = $repository; - $this->validator = $validator; - $this->powerRepository = $powerRepository; - } - /** * Handle the bulk power request. * + * @param \Pterodactyl\Repositories\Wings\DaemonPowerRepository $powerRepository + * @param \Illuminate\Validation\Factory $validator * @throws \Illuminate\Validation\ValidationException */ - public function handle() + public function handle(DaemonPowerRepository $powerRepository, ValidatorFactory $validator) { $action = $this->argument('action'); $nodes = empty($this->option('nodes')) ? [] : explode(',', $this->option('nodes')); $servers = empty($this->option('servers')) ? [] : explode(',', $this->option('servers')); - $validator = $this->validator->make([ + $validator = $validator->make([ 'action' => $action, 'nodes' => $nodes, 'servers' => $servers, @@ -89,23 +57,18 @@ class BulkPowerActionCommand extends Command throw new ValidationException($validator); } - $count = $this->repository->getServersForPowerActionCount($servers, $nodes); + $count = $this->getQueryBuilder($servers, $nodes)->count(); if (! $this->confirm(trans('command/messages.server.power.confirm', ['action' => $action, 'count' => $count])) && $this->input->isInteractive()) { return; } $bar = $this->output->createProgressBar($count); - $servers = $this->repository->getServersForPowerAction($servers, $nodes); - - $servers->each(function ($server) use ($action, &$bar) { + $this->getQueryBuilder($servers, $nodes)->each(function (Server $server) use ($action, $powerRepository, &$bar) { $bar->clear(); try { - $this->powerRepository - ->setNode($server->node) - ->setServer($server) - ->send($action); - } catch (RequestException $exception) { + $powerRepository->setServer($server)->send($action); + } catch (DaemonConnectionException $exception) { $this->output->error(trans('command/messages.server.power.action_failed', [ 'name' => $server->name, 'id' => $server->id, @@ -120,4 +83,28 @@ class BulkPowerActionCommand extends Command $this->line(''); } + + /** + * Returns the query builder instance that will return the servers that should be affected. + * + * @param array $servers + * @param array $nodes + * @return \Illuminate\Database\Eloquent\Builder + */ + protected function getQueryBuilder(array $servers, array $nodes) + { + $instance = Server::query() + ->where('suspended', false) + ->where('installed', Server::STATUS_INSTALLED); + + if (! empty($nodes) && ! empty($servers)) { + $instance->whereIn('id', $servers)->orWhereIn('node_id', $nodes); + } else if (empty($nodes) && ! empty($servers)) { + $instance->whereIn('id', $servers); + } else if (! empty($nodes) && empty($servers)) { + $instance->whereIn('node_id', $nodes); + } + + return $instance->with('node'); + } } diff --git a/app/Contracts/Repository/ServerRepositoryInterface.php b/app/Contracts/Repository/ServerRepositoryInterface.php index d7e3dfc5d..55cf79a39 100644 --- a/app/Contracts/Repository/ServerRepositoryInterface.php +++ b/app/Contracts/Repository/ServerRepositoryInterface.php @@ -95,25 +95,6 @@ interface ServerRepositoryInterface extends RepositoryInterface */ public function getByUuid(string $uuid): Server; - /** - * Return all of the servers that should have a power action performed against them. - * - * @param int[] $servers - * @param int[] $nodes - * @param bool $returnCount - * @return int|\Illuminate\Support\LazyCollection - */ - public function getServersForPowerAction(array $servers = [], array $nodes = [], bool $returnCount = false); - - /** - * Return the total number of servers that will be affected by the query. - * - * @param int[] $servers - * @param int[] $nodes - * @return int - */ - public function getServersForPowerActionCount(array $servers = [], array $nodes = []): int; - /** * Check if a given UUID and UUID-Short string are unique to a server. * diff --git a/app/Http/Requests/Api/Client/Account/UpdatePasswordRequest.php b/app/Http/Requests/Api/Client/Account/UpdatePasswordRequest.php index f52b71297..3bbff3d48 100644 --- a/app/Http/Requests/Api/Client/Account/UpdatePasswordRequest.php +++ b/app/Http/Requests/Api/Client/Account/UpdatePasswordRequest.php @@ -2,7 +2,6 @@ namespace Pterodactyl\Http\Requests\Api\Client\Account; -use Pterodactyl\Models\User; use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest; use Pterodactyl\Exceptions\Http\Base\InvalidPasswordProvidedException; @@ -32,8 +31,8 @@ class UpdatePasswordRequest extends ClientApiRequest */ public function rules(): array { - $rules = User::getRulesForUpdate($this->user()); - - return ['password' => array_merge($rules['password'], ['confirmed'])]; + return [ + 'password' => ['required', 'string', 'confirmed', 'min:8'], + ]; } } diff --git a/app/Jobs/Schedule/RunTaskJob.php b/app/Jobs/Schedule/RunTaskJob.php index 8784a37cb..ee3c3e16d 100644 --- a/app/Jobs/Schedule/RunTaskJob.php +++ b/app/Jobs/Schedule/RunTaskJob.php @@ -69,7 +69,7 @@ class RunTaskJob extends Job implements ShouldQueue $commandRepository->setServer($server)->send($this->task->payload); break; case 'backup': - $backupService->setIgnoredFiles(explode(PHP_EOL, $this->task->payload))->handle($server, null); + $backupService->setIgnoredFiles(explode(PHP_EOL, $this->task->payload))->handle($server, null, true); break; default: throw new InvalidArgumentException('Cannot run a task that points to a non-existent action.'); diff --git a/app/Repositories/Eloquent/ServerRepository.php b/app/Repositories/Eloquent/ServerRepository.php index fdfd82fc2..5c16664b6 100644 --- a/app/Repositories/Eloquent/ServerRepository.php +++ b/app/Repositories/Eloquent/ServerRepository.php @@ -194,45 +194,6 @@ class ServerRepository extends EloquentRepository implements ServerRepositoryInt } } - /** - * Return all of the servers that should have a power action performed against them. - * - * @param int[] $servers - * @param int[] $nodes - * @param bool $returnCount - * @return int|\Illuminate\Support\LazyCollection - */ - public function getServersForPowerAction(array $servers = [], array $nodes = [], bool $returnCount = false) - { - $instance = $this->getBuilder(); - - if (! empty($nodes) && ! empty($servers)) { - $instance->whereIn('id', $servers)->orWhereIn('node_id', $nodes); - } else if (empty($nodes) && ! empty($servers)) { - $instance->whereIn('id', $servers); - } else if (! empty($nodes) && empty($servers)) { - $instance->whereIn('node_id', $nodes); - } - - if ($returnCount) { - return $instance->count(); - } - - return $instance->with('node')->cursor(); - } - - /** - * Return the total number of servers that will be affected by the query. - * - * @param int[] $servers - * @param int[] $nodes - * @return int - */ - public function getServersForPowerActionCount(array $servers = [], array $nodes = []): int - { - return $this->getServersForPowerAction($servers, $nodes, true); - } - /** * Check if a given UUID and UUID-Short string are unique to a server. * diff --git a/app/Services/Backups/InitiateBackupService.php b/app/Services/Backups/InitiateBackupService.php index ca8c3decd..5c34961a4 100644 --- a/app/Services/Backups/InitiateBackupService.php +++ b/app/Services/Backups/InitiateBackupService.php @@ -13,6 +13,7 @@ use Pterodactyl\Repositories\Eloquent\BackupRepository; use Pterodactyl\Repositories\Wings\DaemonBackupRepository; use Pterodactyl\Exceptions\Service\Backup\TooManyBackupsException; use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException; +use Pterodactyl\Services\Backups\DeleteBackupService; class InitiateBackupService { @@ -41,24 +42,32 @@ class InitiateBackupService */ private $backupManager; + /** + * @var \Pterodactyl\Services\Backups\DeleteBackupService + */ + private $deleteBackupService; + /** * InitiateBackupService constructor. * * @param \Pterodactyl\Repositories\Eloquent\BackupRepository $repository * @param \Illuminate\Database\ConnectionInterface $connection * @param \Pterodactyl\Repositories\Wings\DaemonBackupRepository $daemonBackupRepository + * @param \Pterodactyl\Services\Backups\DeleteBackupService $deleteBackupService * @param \Pterodactyl\Extensions\Backups\BackupManager $backupManager */ public function __construct( BackupRepository $repository, ConnectionInterface $connection, DaemonBackupRepository $daemonBackupRepository, + DeleteBackupService $deleteBackupService, BackupManager $backupManager ) { $this->repository = $repository; $this->connection = $connection; $this->daemonBackupRepository = $daemonBackupRepository; $this->backupManager = $backupManager; + $this->deleteBackupService = $deleteBackupService; } /** @@ -96,13 +105,8 @@ class InitiateBackupService * @throws \Pterodactyl\Exceptions\Service\Backup\TooManyBackupsException * @throws \Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException */ - public function handle(Server $server, string $name = null): Backup + public function handle(Server $server, string $name = null, bool $override = false): Backup { - // Do not allow the user to continue if this server is already at its limit. - if (! $server->backup_limit || $server->backups()->where('is_successful', true)->count() >= $server->backup_limit) { - throw new TooManyBackupsException($server->backup_limit); - } - $previous = $this->repository->getBackupsGeneratedDuringTimespan($server->id, 10); if ($previous->count() >= 2) { throw new TooManyRequestsHttpException( @@ -111,6 +115,18 @@ class InitiateBackupService ); } + // Check if the server has reached or exceeded it's backup limit + if (!$server->backup_limit || $server->backups()->where('is_successful', true)->count() >= $server->backup_limit) { + // Do not allow the user to continue if this server is already at its limit and can't override. + if (!$override || $server->backup_limit <= 0) { + throw new TooManyBackupsException($server->backup_limit); + } + + // Remove oldest backup + $oldestBackup = $server->backups()->where('is_successful', true)->orderByDesc('created_at')->first(); + $this->deleteBackupService->handle($oldestBackup); + } + return $this->connection->transaction(function () use ($server, $name) { /** @var \Pterodactyl\Models\Backup $backup */ $backup = $this->repository->create([ diff --git a/config/database.php b/config/database.php index 5a4e8bc77..e69e19ed0 100644 --- a/config/database.php +++ b/config/database.php @@ -45,7 +45,7 @@ return [ 'collation' => 'utf8mb4_unicode_ci', 'prefix' => env('DB_PREFIX', ''), 'strict' => env('DB_STRICT_MODE', false), - 'timezone' => env('DB_TIMEZONE', Time::getMySQLTimezoneOffset(env('APP_TIMEZONE'))) + 'timezone' => env('DB_TIMEZONE', Time::getMySQLTimezoneOffset(env('APP_TIMEZONE', 'UTC'))) ], /* @@ -68,7 +68,7 @@ return [ 'collation' => 'utf8mb4_unicode_ci', 'prefix' => '', 'strict' => false, - 'timezone' => env('DB_TIMEZONE', Time::getMySQLTimezoneOffset(env('APP_TIMEZONE'))) + 'timezone' => env('DB_TIMEZONE', Time::getMySQLTimezoneOffset(env('APP_TIMEZONE', 'UTC'))) ], ], diff --git a/database/migrations/2020_03_22_163911_merge_permissions_table_into_subusers.php b/database/migrations/2020_03_22_163911_merge_permissions_table_into_subusers.php index 67461ecc8..f46481b47 100644 --- a/database/migrations/2020_03_22_163911_merge_permissions_table_into_subusers.php +++ b/database/migrations/2020_03_22_163911_merge_permissions_table_into_subusers.php @@ -83,10 +83,10 @@ class MergePermissionsTableIntoSubusers extends Migration ->map(function ($value) { return self::$permissionsMap[$value] ?? null; })->filter(function ($value) { - return !is_null($value) && $value !== Permission::ACTION_WEBSOCKET_CONNECT; + return ! is_null($value) && $value !== Permission::ACTION_WEBSOCKET_CONNECT; }) // All subusers get this permission, so make sure it gets pushed into the array. - ->merge([ Permission::ACTION_WEBSOCKET_CONNECT ]) + ->merge([Permission::ACTION_WEBSOCKET_CONNECT]) ->unique() ->values() ->toJson(); @@ -103,12 +103,12 @@ class MergePermissionsTableIntoSubusers extends Migration */ public function down() { - $flipped = array_flip(self::$permissionsMap); + $flipped = array_flip(array_filter(self::$permissionsMap)); foreach (DB::select('SELECT id, permissions FROM subusers') as $datum) { $values = []; foreach (json_decode($datum->permissions, true) as $permission) { - if (!empty($v = $flipped[$permission])) { + if (! empty($v = $flipped[$permission])) { $values[] = $datum->id; $values[] = $v; } diff --git a/package.json b/package.json index 2f947b532..e62c7ea55 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "xterm-addon-fit": "^0.4.0", "xterm-addon-search": "^0.7.0", "xterm-addon-search-bar": "^0.2.0", + "xterm-addon-web-links": "^0.4.0", "yup": "^0.29.1" }, "devDependencies": { diff --git a/resources/scripts/components/dashboard/forms/UpdatePasswordForm.tsx b/resources/scripts/components/dashboard/forms/UpdatePasswordForm.tsx index 7dec924a5..1c91ef4d9 100644 --- a/resources/scripts/components/dashboard/forms/UpdatePasswordForm.tsx +++ b/resources/scripts/components/dashboard/forms/UpdatePasswordForm.tsx @@ -77,7 +77,7 @@ export default () => {
By pressing {'"I Accept"'} below you are indicating your agreement to the { rel={'noreferrer noopener'} href="https://account.mojang.com/documents/minecraft_eula" > - Mojang EULA + Minecraft® EULA .