Merge branch 'develop' into feature/file-uploads
This commit is contained in:
commit
54f9c5f187
136 changed files with 2178 additions and 971 deletions
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Console\Commands\Maintenance;
|
||||
|
||||
use Carbon\CarbonImmutable;
|
||||
use InvalidArgumentException;
|
||||
use Illuminate\Console\Command;
|
||||
use Pterodactyl\Repositories\Eloquent\BackupRepository;
|
||||
|
||||
class PruneOrphanedBackupsCommand extends Command
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'p:maintenance:prune-backups {--since-minutes=30}';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Marks all backups that have not completed in the last "n" minutes as being failed.';
|
||||
|
||||
/**
|
||||
* @param \Pterodactyl\Repositories\Eloquent\BackupRepository $repository
|
||||
*/
|
||||
public function handle(BackupRepository $repository)
|
||||
{
|
||||
$since = $this->option('since-minutes');
|
||||
if (! is_digit($since)) {
|
||||
throw new InvalidArgumentException('The --since-minutes option must be a valid numeric digit.');
|
||||
}
|
||||
|
||||
$query = $repository->getBuilder()
|
||||
->whereNull('completed_at')
|
||||
->whereDate('created_at', '<=', CarbonImmutable::now()->subMinutes($since));
|
||||
|
||||
$count = $query->count();
|
||||
if (! $count) {
|
||||
$this->info('There are no orphaned backups to be marked as failed.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->warn("Marking {$count} backups that have not been marked as completed in the last {$since} minutes as failed.");
|
||||
|
||||
$query->update([
|
||||
'is_successful' => false,
|
||||
'completed_at' => CarbonImmutable::now(),
|
||||
'updated_at' => CarbonImmutable::now(),
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -22,7 +22,16 @@ class Kernel extends ConsoleKernel
|
|||
*/
|
||||
protected function schedule(Schedule $schedule)
|
||||
{
|
||||
// Execute scheduled commands for servers every minute, as if there was a normal cron running.
|
||||
$schedule->command('p:schedule:process')->everyMinute()->withoutOverlapping();
|
||||
|
||||
// Every 30 minutes, run the backup pruning command so that any abandoned backups can be removed
|
||||
// from the UI view for the server.
|
||||
$schedule->command('p:maintenance:prune-backups', [
|
||||
'--since-minutes' => '30',
|
||||
])->everyThirtyMinutes();
|
||||
|
||||
// Every day cleanup any internal backups of service files.
|
||||
$schedule->command('p:maintenance:clean-service-backups')->daily();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -213,6 +213,13 @@ class Handler extends ExceptionHandler
|
|||
'detail' => 'An error was encountered while processing this request.',
|
||||
];
|
||||
|
||||
if ($exception instanceof ModelNotFoundException || $exception->getPrevious() instanceof ModelNotFoundException) {
|
||||
// Show a nicer error message compared to the standard "No query results for model"
|
||||
// response that is normally returned. If we are in debug mode this will get overwritten
|
||||
// with a more specific error message to help narrow down things.
|
||||
$error['detail'] = 'The requested resource could not be found on the server.';
|
||||
}
|
||||
|
||||
if (config('app.debug')) {
|
||||
$error = array_merge($error, [
|
||||
'detail' => $exception->getMessage(),
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Pterodactyl\Exceptions\Http\Connection;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Http\Response;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Pterodactyl\Exceptions\DisplayException;
|
||||
|
@ -22,18 +23,34 @@ class DaemonConnectionException extends DisplayException
|
|||
* @param \GuzzleHttp\Exception\GuzzleException $previous
|
||||
* @param bool $useStatusCode
|
||||
*/
|
||||
public function __construct(GuzzleException $previous, bool $useStatusCode = false)
|
||||
public function __construct(GuzzleException $previous, bool $useStatusCode = true)
|
||||
{
|
||||
/** @var \GuzzleHttp\Psr7\Response|null $response */
|
||||
$response = method_exists($previous, 'getResponse') ? $previous->getResponse() : null;
|
||||
|
||||
if ($useStatusCode) {
|
||||
$this->statusCode = is_null($response) ? 500 : $response->getStatusCode();
|
||||
$this->statusCode = is_null($response) ? $this->statusCode : $response->getStatusCode();
|
||||
}
|
||||
|
||||
parent::__construct(trans('admin/server.exceptions.daemon_exception', [
|
||||
$message = trans('admin/server.exceptions.daemon_exception', [
|
||||
'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(),
|
||||
]), $previous, DisplayException::LEVEL_WARNING);
|
||||
]);
|
||||
|
||||
// Attempt to pull the actual error message off the response and return that if it is not
|
||||
// a 500 level error.
|
||||
if ($this->statusCode < 500 && ! is_null($response)) {
|
||||
$body = $response->getBody();
|
||||
if (is_string($body) || (is_object($body) && method_exists($body, '__toString'))) {
|
||||
$body = json_decode(is_string($body) ? $body : $body->__toString(), true);
|
||||
$message = "[Wings Error]: " . Arr::get($body, 'error', $message);
|
||||
}
|
||||
}
|
||||
|
||||
$level = $this->statusCode >= 500 && $this->statusCode !== 504
|
||||
? DisplayException::LEVEL_ERROR
|
||||
: DisplayException::LEVEL_WARNING;
|
||||
|
||||
parent::__construct($message, $previous, $level);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace Pterodactyl\Http\Controllers\Api\Client;
|
||||
|
||||
use Pterodactyl\Models\User;
|
||||
use Pterodactyl\Models\Server;
|
||||
use Pterodactyl\Models\Permission;
|
||||
use Spatie\QueryBuilder\QueryBuilder;
|
||||
|
@ -39,31 +38,27 @@ class ClientController extends ClientApiController
|
|||
public function index(GetServersRequest $request): array
|
||||
{
|
||||
$user = $request->user();
|
||||
$level = $request->getFilterLevel();
|
||||
$transformer = $this->getTransformer(ServerTransformer::class);
|
||||
|
||||
// Start the query builder and ensure we eager load any requested relationships from the request.
|
||||
$builder = Server::query()->with($this->getIncludesForTransformer($transformer, ['node']));
|
||||
$builder = QueryBuilder::for(
|
||||
Server::query()->with($this->getIncludesForTransformer($transformer, ['node']))
|
||||
)->allowedFilters('uuid', 'name', 'external_id');
|
||||
|
||||
if ($level === User::FILTER_LEVEL_OWNER) {
|
||||
$builder = $builder->where('owner_id', $request->user()->id);
|
||||
}
|
||||
// If set to all, display all servers they can access, including those they access as an
|
||||
// admin. If set to subuser, only return the servers they can access because they are owner,
|
||||
// or marked as a subuser of the server.
|
||||
elseif (($level === User::FILTER_LEVEL_ALL && ! $user->root_admin) || $level === User::FILTER_LEVEL_SUBUSER) {
|
||||
// Either return all of the servers the user has access to because they are an admin `?type=admin` or
|
||||
// just return all of the servers the user has access to because they are the owner or a subuser of the
|
||||
// server.
|
||||
if ($request->input('type') === 'admin') {
|
||||
$builder = $user->root_admin
|
||||
? $builder->whereNotIn('id', $user->accessibleServers()->pluck('id')->all())
|
||||
// If they aren't an admin but want all the admin servers don't fail the request, just
|
||||
// make it a query that will never return any results back.
|
||||
: $builder->whereRaw('1 = 2');
|
||||
} elseif ($request->input('type') === 'owner') {
|
||||
$builder = $builder->where('owner_id', $user->id);
|
||||
} else {
|
||||
$builder = $builder->whereIn('id', $user->accessibleServers()->pluck('id')->all());
|
||||
}
|
||||
// If set to admin, only display the servers a user can access because they are an administrator.
|
||||
// This means only servers the user would not have access to if they were not an admin (because they
|
||||
// are not an owner or subuser) are returned.
|
||||
elseif ($level === User::FILTER_LEVEL_ADMIN && $user->root_admin) {
|
||||
$builder = $builder->whereNotIn('id', $user->accessibleServers()->pluck('id')->all());
|
||||
}
|
||||
|
||||
$builder = QueryBuilder::for($builder)->allowedFilters(
|
||||
'uuid', 'name', 'external_id'
|
||||
);
|
||||
|
||||
$servers = $builder->paginate(min($request->query('per_page', 50), 100))->appends($request->query());
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@ namespace Pterodactyl\Http\Controllers\Api\Client\Servers;
|
|||
use Illuminate\Http\Response;
|
||||
use Pterodactyl\Models\Server;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use GuzzleHttp\Exception\BadResponseException;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
use Pterodactyl\Repositories\Wings\DaemonCommandRepository;
|
||||
|
@ -45,11 +44,13 @@ class CommandController extends ClientApiController
|
|||
{
|
||||
try {
|
||||
$this->repository->setServer($server)->send($request->input('command'));
|
||||
} catch (RequestException $exception) {
|
||||
if ($exception instanceof BadResponseException) {
|
||||
} catch (DaemonConnectionException $exception) {
|
||||
$previous = $exception->getPrevious();
|
||||
|
||||
if ($previous instanceof BadResponseException) {
|
||||
if (
|
||||
$exception->getResponse() instanceof ResponseInterface
|
||||
&& $exception->getResponse()->getStatusCode() === Response::HTTP_BAD_GATEWAY
|
||||
$previous->getResponse() instanceof ResponseInterface
|
||||
&& $previous->getResponse()->getStatusCode() === Response::HTTP_BAD_GATEWAY
|
||||
) {
|
||||
throw new HttpException(
|
||||
Response::HTTP_BAD_GATEWAY, 'Server must be online in order to send commands.', $exception
|
||||
|
@ -57,7 +58,7 @@ class CommandController extends ClientApiController
|
|||
}
|
||||
}
|
||||
|
||||
throw new DaemonConnectionException($exception);
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
return $this->returnNoContent();
|
||||
|
|
|
@ -6,19 +6,18 @@ use Carbon\CarbonImmutable;
|
|||
use Illuminate\Http\Response;
|
||||
use Pterodactyl\Models\Server;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use GuzzleHttp\Exception\TransferException;
|
||||
use Pterodactyl\Services\Nodes\NodeJWTService;
|
||||
use Illuminate\Contracts\Routing\ResponseFactory;
|
||||
use Pterodactyl\Repositories\Wings\DaemonFileRepository;
|
||||
use Pterodactyl\Transformers\Daemon\FileObjectTransformer;
|
||||
use Pterodactyl\Http\Controllers\Api\Client\ClientApiController;
|
||||
use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
|
||||
use Pterodactyl\Http\Requests\Api\Client\Servers\Files\CopyFileRequest;
|
||||
use Pterodactyl\Http\Requests\Api\Client\Servers\Files\ListFilesRequest;
|
||||
use Pterodactyl\Http\Requests\Api\Client\Servers\Files\DeleteFileRequest;
|
||||
use Pterodactyl\Http\Requests\Api\Client\Servers\Files\RenameFileRequest;
|
||||
use Pterodactyl\Http\Requests\Api\Client\Servers\Files\CreateFolderRequest;
|
||||
use Pterodactyl\Http\Requests\Api\Client\Servers\Files\CompressFilesRequest;
|
||||
use Pterodactyl\Http\Requests\Api\Client\Servers\Files\DecompressFilesRequest;
|
||||
use Pterodactyl\Http\Requests\Api\Client\Servers\Files\GetFileContentsRequest;
|
||||
use Pterodactyl\Http\Requests\Api\Client\Servers\Files\WriteFileContentRequest;
|
||||
|
||||
|
@ -69,13 +68,9 @@ class FileController extends ClientApiController
|
|||
*/
|
||||
public function directory(ListFilesRequest $request, Server $server): array
|
||||
{
|
||||
try {
|
||||
$contents = $this->fileRepository
|
||||
->setServer($server)
|
||||
->getDirectory($request->get('directory') ?? '/');
|
||||
} catch (TransferException $exception) {
|
||||
throw new DaemonConnectionException($exception, true);
|
||||
}
|
||||
$contents = $this->fileRepository
|
||||
->setServer($server)
|
||||
->getDirectory($request->get('directory') ?? '/');
|
||||
|
||||
return $this->fractal->collection($contents)
|
||||
->transformWith($this->getTransformer(FileObjectTransformer::class))
|
||||
|
@ -88,7 +83,9 @@ class FileController extends ClientApiController
|
|||
* @param \Pterodactyl\Http\Requests\Api\Client\Servers\Files\GetFileContentsRequest $request
|
||||
* @param \Pterodactyl\Models\Server $server
|
||||
* @return \Illuminate\Http\Response
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Http\Server\FileSizeTooLargeException
|
||||
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
|
||||
*/
|
||||
public function contents(GetFileContentsRequest $request, Server $server): Response
|
||||
{
|
||||
|
@ -139,6 +136,8 @@ class FileController extends ClientApiController
|
|||
* @param \Pterodactyl\Http\Requests\Api\Client\Servers\Files\WriteFileContentRequest $request
|
||||
* @param \Pterodactyl\Models\Server $server
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
|
||||
*/
|
||||
public function write(WriteFileContentRequest $request, Server $server): JsonResponse
|
||||
{
|
||||
|
@ -156,6 +155,8 @@ class FileController extends ClientApiController
|
|||
* @param \Pterodactyl\Http\Requests\Api\Client\Servers\Files\CreateFolderRequest $request
|
||||
* @param \Pterodactyl\Models\Server $server
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
|
||||
*/
|
||||
public function create(CreateFolderRequest $request, Server $server): JsonResponse
|
||||
{
|
||||
|
@ -172,6 +173,8 @@ class FileController extends ClientApiController
|
|||
* @param \Pterodactyl\Http\Requests\Api\Client\Servers\Files\RenameFileRequest $request
|
||||
* @param \Pterodactyl\Models\Server $server
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
|
||||
*/
|
||||
public function rename(RenameFileRequest $request, Server $server): JsonResponse
|
||||
{
|
||||
|
@ -188,6 +191,8 @@ class FileController extends ClientApiController
|
|||
* @param \Pterodactyl\Http\Requests\Api\Client\Servers\Files\CopyFileRequest $request
|
||||
* @param \Pterodactyl\Models\Server $server
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
|
||||
*/
|
||||
public function copy(CopyFileRequest $request, Server $server): JsonResponse
|
||||
{
|
||||
|
@ -202,9 +207,14 @@ class FileController extends ClientApiController
|
|||
* @param \Pterodactyl\Http\Requests\Api\Client\Servers\Files\CompressFilesRequest $request
|
||||
* @param \Pterodactyl\Models\Server $server
|
||||
* @return array
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
|
||||
*/
|
||||
public function compress(CompressFilesRequest $request, Server $server): array
|
||||
{
|
||||
// Allow up to five minutes for this request to process before timing out.
|
||||
set_time_limit(300);
|
||||
|
||||
$file = $this->fileRepository->setServer($server)
|
||||
->compressFiles(
|
||||
$request->input('root'), $request->input('files')
|
||||
|
@ -215,12 +225,32 @@ class FileController extends ClientApiController
|
|||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Pterodactyl\Http\Requests\Api\Client\Servers\Files\DecompressFilesRequest $request
|
||||
* @param \Pterodactyl\Models\Server $server
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
|
||||
*/
|
||||
public function decompress(DecompressFilesRequest $request, Server $server): JsonResponse
|
||||
{
|
||||
// Allow up to five minutes for this request to process before timing out.
|
||||
set_time_limit(300);
|
||||
|
||||
$this->fileRepository->setServer($server)
|
||||
->decompressFile($request->input('root'), $request->input('file'));
|
||||
|
||||
return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes files or folders for the server in the given root directory.
|
||||
*
|
||||
* @param \Pterodactyl\Http\Requests\Api\Client\Servers\Files\DeleteFileRequest $request
|
||||
* @param \Pterodactyl\Models\Server $server
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
|
||||
*/
|
||||
public function delete(DeleteFileRequest $request, Server $server): JsonResponse
|
||||
{
|
||||
|
|
|
@ -33,6 +33,8 @@ class PowerController extends ClientApiController
|
|||
* @param \Pterodactyl\Http\Requests\Api\Client\Servers\SendPowerRequest $request
|
||||
* @param \Pterodactyl\Models\Server $server
|
||||
* @return \Illuminate\Http\Response
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
|
||||
*/
|
||||
public function index(SendPowerRequest $request, Server $server): Response
|
||||
{
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Http\Controllers\Api\Client\Servers;
|
||||
|
||||
use Carbon\CarbonImmutable;
|
||||
use Pterodactyl\Models\Server;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Pterodactyl\Services\Servers\VariableValidatorService;
|
||||
use Pterodactyl\Repositories\Eloquent\ServerVariableRepository;
|
||||
use Pterodactyl\Transformers\Api\Client\EggVariableTransformer;
|
||||
use Pterodactyl\Http\Controllers\Api\Client\ClientApiController;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
use Pterodactyl\Http\Requests\Api\Client\Servers\Startup\UpdateStartupVariableRequest;
|
||||
|
||||
class StartupController extends ClientApiController
|
||||
{
|
||||
/**
|
||||
* @var \Pterodactyl\Services\Servers\VariableValidatorService
|
||||
*/
|
||||
private $service;
|
||||
|
||||
/**
|
||||
* @var \Pterodactyl\Repositories\Eloquent\ServerVariableRepository
|
||||
*/
|
||||
private $repository;
|
||||
|
||||
/**
|
||||
* StartupController constructor.
|
||||
*
|
||||
* @param \Pterodactyl\Services\Servers\VariableValidatorService $service
|
||||
* @param \Pterodactyl\Repositories\Eloquent\ServerVariableRepository $repository
|
||||
*/
|
||||
public function __construct(VariableValidatorService $service, ServerVariableRepository $repository)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->service = $service;
|
||||
$this->repository = $repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a single variable for a server.
|
||||
*
|
||||
* @param \Pterodactyl\Http\Requests\Api\Client\Servers\Startup\UpdateStartupVariableRequest $request
|
||||
* @param \Pterodactyl\Models\Server $server
|
||||
* @return array
|
||||
*
|
||||
* @throws \Illuminate\Validation\ValidationException
|
||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||
*/
|
||||
public function update(UpdateStartupVariableRequest $request, Server $server)
|
||||
{
|
||||
/** @var \Pterodactyl\Models\EggVariable $variable */
|
||||
$variable = $server->variables()->where('env_variable', $request->input('key'))->first();
|
||||
|
||||
if (is_null($variable) || !$variable->user_viewable || !$variable->user_editable) {
|
||||
throw new BadRequestHttpException(
|
||||
"The environment variable you are trying to edit [\"{$request->input('key')}\"] does not exist."
|
||||
);
|
||||
}
|
||||
|
||||
// Revalidate the variable value using the egg variable specific validation rules for it.
|
||||
$this->validate($request, ['value' => $variable->rules]);
|
||||
|
||||
$this->repository->updateOrCreate([
|
||||
'server_id' => $server->id,
|
||||
'variable_id' => $variable->id,
|
||||
], [
|
||||
'variable_value' => $request->input('value'),
|
||||
]);
|
||||
|
||||
$variable = $variable->refresh();
|
||||
$variable->server_value = $request->input('value');
|
||||
|
||||
return $this->fractal->item($variable)
|
||||
->transformWith($this->getTransformer(EggVariableTransformer::class))
|
||||
->toArray();
|
||||
}
|
||||
}
|
|
@ -3,7 +3,9 @@
|
|||
namespace Pterodactyl\Http\Controllers\Api\Client\Servers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Pterodactyl\Models\User;
|
||||
use Pterodactyl\Models\Server;
|
||||
use Pterodactyl\Models\Subuser;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Pterodactyl\Models\Permission;
|
||||
use Pterodactyl\Repositories\Eloquent\SubuserRepository;
|
||||
|
@ -57,6 +59,21 @@ class SubuserController extends ClientApiController
|
|||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a single subuser associated with this server instance.
|
||||
*
|
||||
* @param \Pterodactyl\Http\Requests\Api\Client\Servers\Subusers\GetSubuserRequest $request
|
||||
* @return array
|
||||
*/
|
||||
public function view(GetSubuserRequest $request)
|
||||
{
|
||||
$subuser = $request->attributes->get('subuser');
|
||||
|
||||
return $this->fractal->item($subuser)
|
||||
->transformWith($this->getTransformer(SubuserTransformer::class))
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new subuser for the given server.
|
||||
*
|
||||
|
@ -84,15 +101,16 @@ class SubuserController extends ClientApiController
|
|||
* Update a given subuser in the system for the server.
|
||||
*
|
||||
* @param \Pterodactyl\Http\Requests\Api\Client\Servers\Subusers\UpdateSubuserRequest $request
|
||||
* @param \Pterodactyl\Models\Server $server
|
||||
* @return array
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||
*/
|
||||
public function update(UpdateSubuserRequest $request, Server $server): array
|
||||
public function update(UpdateSubuserRequest $request): array
|
||||
{
|
||||
$subuser = $request->endpointSubuser();
|
||||
/** @var \Pterodactyl\Models\Subuser $subuser */
|
||||
$subuser = $request->attributes->get('subuser');
|
||||
|
||||
$this->repository->update($subuser->id, [
|
||||
'permissions' => $this->getDefaultPermissions($request),
|
||||
]);
|
||||
|
@ -106,14 +124,16 @@ class SubuserController extends ClientApiController
|
|||
* Removes a subusers from a server's assignment.
|
||||
*
|
||||
* @param \Pterodactyl\Http\Requests\Api\Client\Servers\Subusers\DeleteSubuserRequest $request
|
||||
* @param \Pterodactyl\Models\Server $server
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function delete(DeleteSubuserRequest $request, Server $server)
|
||||
public function delete(DeleteSubuserRequest $request)
|
||||
{
|
||||
$this->repository->delete($request->endpointSubuser()->id);
|
||||
/** @var \Pterodactyl\Models\Subuser $subuser */
|
||||
$subuser = $request->attributes->get('subuser');
|
||||
|
||||
return JsonResponse::create([], JsonResponse::HTTP_NO_CONTENT);
|
||||
$this->repository->delete($subuser->id);
|
||||
|
||||
return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
namespace Pterodactyl\Http\Controllers\Api\Remote\Backups;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Carbon\CarbonImmutable;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Pterodactyl\Http\Controllers\Controller;
|
||||
use Pterodactyl\Repositories\Eloquent\BackupRepository;
|
||||
|
@ -31,25 +32,16 @@ class BackupStatusController extends Controller
|
|||
* @param \Pterodactyl\Http\Requests\Api\Remote\ReportBackupCompleteRequest $request
|
||||
* @param string $backup
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||
*/
|
||||
public function __invoke(ReportBackupCompleteRequest $request, string $backup)
|
||||
{
|
||||
/** @var \Pterodactyl\Models\Backup $backup */
|
||||
$backup = $this->repository->findFirstWhere([['uuid', '=', $backup]]);
|
||||
$this->repository->updateWhere([['uuid', '=', $backup]], [
|
||||
'is_successful' => $request->input('successful') ? true : false,
|
||||
'sha256_hash' => $request->input('checksum'),
|
||||
'bytes' => $request->input('size'),
|
||||
'completed_at' => CarbonImmutable::now(),
|
||||
]);
|
||||
|
||||
if ($request->input('successful')) {
|
||||
$this->repository->update($backup->id, [
|
||||
'sha256_hash' => $request->input('checksum'),
|
||||
'bytes' => $request->input('size'),
|
||||
'completed_at' => Carbon::now(),
|
||||
], true, true);
|
||||
} else {
|
||||
$this->repository->delete($backup->id);
|
||||
}
|
||||
|
||||
return JsonResponse::create([], JsonResponse::HTTP_NO_CONTENT);
|
||||
return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Http\Middleware\Api\Client\Server;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class SubuserBelongsToServer
|
||||
{
|
||||
/**
|
||||
* Ensure that the user being accessed in the request is a user that is currently assigned
|
||||
* as a subuser for this server instance. We'll let the requests themselves handle wether or
|
||||
* not the user making the request can actually modify or delete the subuser record.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
/** @var \Pterodactyl\Models\Server $server */
|
||||
$server = $request->route()->parameter('server');
|
||||
/** @var \Pterodactyl\Models\User $user */
|
||||
$user = $request->route()->parameter('user');
|
||||
|
||||
// Don't do anything if there isn't a user present in the request.
|
||||
if (is_null($user)) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
$request->attributes->set('subuser', $server->subusers()->where('user_id', $user->id)->firstOrFail());
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@
|
|||
namespace Pterodactyl\Http\Middleware\Api\Client;
|
||||
|
||||
use Closure;
|
||||
use Pterodactyl\Models\User;
|
||||
use Pterodactyl\Models\Backup;
|
||||
use Pterodactyl\Models\Database;
|
||||
use Illuminate\Container\Container;
|
||||
|
@ -52,6 +53,10 @@ class SubstituteClientApiBindings extends ApiSubstituteBindings
|
|||
return Backup::query()->where('uuid', $value)->firstOrFail();
|
||||
});
|
||||
|
||||
$this->router->model('user', User::class, function ($value) {
|
||||
return User::query()->where('uuid', $value)->firstOrFail();
|
||||
});
|
||||
|
||||
return parent::handle($request, $next);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,10 +29,6 @@ class DatabaseHostFormRequest extends AdminFormRequest
|
|||
$this->merge(['node_id' => null]);
|
||||
}
|
||||
|
||||
$this->merge([
|
||||
'host' => gethostbyname($this->input('host')),
|
||||
]);
|
||||
|
||||
return parent::getValidatorInstance();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ class BaseSettingsFormRequest extends AdminFormRequest
|
|||
'app:name' => 'required|string|max:255',
|
||||
'pterodactyl:auth:2fa_required' => 'required|integer|in:0,1,2',
|
||||
'app:locale' => ['required', 'string', Rule::in(array_keys($this->getAvailableLanguages()))],
|
||||
'app:analytics' => 'nullable|string',
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -31,6 +32,7 @@ class BaseSettingsFormRequest extends AdminFormRequest
|
|||
'app:name' => 'Company Name',
|
||||
'pterodactyl:auth:2fa_required' => 'Require 2-Factor Authentication',
|
||||
'app:locale' => 'Default Language',
|
||||
'app:analytics' => 'Google Analytics',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,4 +17,14 @@ class StoreApiKeyRequest extends ClientApiRequest
|
|||
'allowed_ips.*' => 'ip',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|string[]
|
||||
*/
|
||||
public function messages()
|
||||
{
|
||||
return [
|
||||
'allowed_ips.*' => 'All of the IP addresses entered must be valid IPv4 addresses.',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
|
||||
namespace Pterodactyl\Http\Requests\Api\Client;
|
||||
|
||||
use Pterodactyl\Models\User;
|
||||
|
||||
class GetServersRequest extends ClientApiRequest
|
||||
{
|
||||
/**
|
||||
|
@ -13,28 +11,4 @@ class GetServersRequest extends ClientApiRequest
|
|||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the filtering method for servers when the client base endpoint is requested.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getFilterLevel(): int
|
||||
{
|
||||
switch ($this->input('type')) {
|
||||
case 'all':
|
||||
return User::FILTER_LEVEL_ALL;
|
||||
break;
|
||||
case 'admin':
|
||||
return User::FILTER_LEVEL_ADMIN;
|
||||
break;
|
||||
case 'owner':
|
||||
return User::FILTER_LEVEL_OWNER;
|
||||
break;
|
||||
case 'subuser-of':
|
||||
default:
|
||||
return User::FILTER_LEVEL_SUBUSER;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Http\Requests\Api\Client\Servers\Files;
|
||||
|
||||
use Pterodactyl\Models\Permission;
|
||||
use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest;
|
||||
|
||||
class DecompressFilesRequest extends ClientApiRequest
|
||||
{
|
||||
/**
|
||||
* Checks that the authenticated user is allowed to create new files for the server. We don't
|
||||
* rely on the archive permission here as it makes more sense to make sure the user can create
|
||||
* additional files rather than make an archive.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function permission(): string
|
||||
{
|
||||
return Permission::ACTION_FILE_CREATE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'root' => 'sometimes|nullable|string',
|
||||
'file' => 'required|string',
|
||||
];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Http\Requests\Api\Client\Servers\Startup;
|
||||
|
||||
use Pterodactyl\Models\Permission;
|
||||
use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest;
|
||||
|
||||
class UpdateStartupVariableRequest extends ClientApiRequest
|
||||
{
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function permission()
|
||||
{
|
||||
return Permission::ACTION_STARTUP_UPDATE;
|
||||
}
|
||||
|
||||
/**
|
||||
* The actual validation of the variable's value will happen inside the controller.
|
||||
*
|
||||
* @return array|string[]
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'key' => 'required|string',
|
||||
'value' => 'present|string',
|
||||
];
|
||||
}
|
||||
}
|
|
@ -3,12 +3,10 @@
|
|||
namespace Pterodactyl\Http\Requests\Api\Client\Servers\Subusers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Pterodactyl\Models\Server;
|
||||
use Pterodactyl\Models\User;
|
||||
use Pterodactyl\Exceptions\Http\HttpForbiddenException;
|
||||
use Pterodactyl\Repositories\Eloquent\SubuserRepository;
|
||||
use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest;
|
||||
use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use Pterodactyl\Services\Servers\GetUserPermissionsService;
|
||||
|
||||
abstract class SubuserRequest extends ClientApiRequest
|
||||
{
|
||||
|
@ -30,10 +28,10 @@ abstract class SubuserRequest extends ClientApiRequest
|
|||
return false;
|
||||
}
|
||||
|
||||
// If there is a subuser present in the URL, validate that it is not the same as the
|
||||
// current request user. You're not allowed to modify yourself.
|
||||
if ($this->route()->hasParameter('subuser')) {
|
||||
if ($this->endpointSubuser()->user_id === $this->user()->id) {
|
||||
$user = $this->route()->parameter('user');
|
||||
// Don't allow a user to edit themselves on the server.
|
||||
if ($user instanceof User) {
|
||||
if ($user->uuid === $this->user()->uuid) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -71,68 +69,14 @@ abstract class SubuserRequest extends ClientApiRequest
|
|||
// Otherwise, get the current subuser's permission set, and ensure that the
|
||||
// permissions they are trying to assign are not _more_ than the ones they
|
||||
// already have.
|
||||
if (count(array_diff($permissions, $this->currentUserPermissions())) > 0) {
|
||||
/** @var \Pterodactyl\Models\Subuser|null $subuser */
|
||||
/** @var \Pterodactyl\Services\Servers\GetUserPermissionsService $service */
|
||||
$service = $this->container->make(GetUserPermissionsService::class);
|
||||
|
||||
if (count(array_diff($permissions, $service->handle($server, $user))) > 0) {
|
||||
throw new HttpForbiddenException(
|
||||
'Cannot assign permissions to a subuser that your account does not actively possess.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the currently authenticated user's permissions.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws \Illuminate\Contracts\Container\BindingResolutionException
|
||||
*/
|
||||
public function currentUserPermissions(): array
|
||||
{
|
||||
/** @var \Pterodactyl\Repositories\Eloquent\SubuserRepository $repository */
|
||||
$repository = $this->container->make(SubuserRepository::class);
|
||||
|
||||
/* @var \Pterodactyl\Models\Subuser $model */
|
||||
try {
|
||||
$model = $repository->findFirstWhere([
|
||||
['server_id', $this->route()->parameter('server')->id],
|
||||
['user_id', $this->user()->id],
|
||||
]);
|
||||
} catch (RecordNotFoundException $exception) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $model->permissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the subuser model for the given request which can then be validated. If
|
||||
* required request parameters are missing a 404 error will be returned, otherwise
|
||||
* a model exception will be returned if the model is not found.
|
||||
*
|
||||
* This returns the subuser based on the endpoint being hit, not the actual subuser
|
||||
* for the account making the request.
|
||||
*
|
||||
* @return \Pterodactyl\Models\Subuser
|
||||
*
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
|
||||
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
|
||||
* @throws \Illuminate\Contracts\Container\BindingResolutionException
|
||||
*/
|
||||
public function endpointSubuser()
|
||||
{
|
||||
/** @var \Pterodactyl\Repositories\Eloquent\SubuserRepository $repository */
|
||||
$repository = $this->container->make(SubuserRepository::class);
|
||||
|
||||
$parameters = $this->route()->parameters();
|
||||
if (
|
||||
! isset($parameters['server'], $parameters['server'])
|
||||
|| ! is_string($parameters['subuser'])
|
||||
|| ! $parameters['server'] instanceof Server
|
||||
) {
|
||||
throw new NotFoundHttpException;
|
||||
}
|
||||
|
||||
return $this->model ?: $this->model = $repository->getUserForServer(
|
||||
$parameters['server']->id, $parameters['subuser']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,6 +37,7 @@ class AssetComposer
|
|||
'enabled' => config('recaptcha.enabled', false),
|
||||
'siteKey' => config('recaptcha.website_key') ?? '',
|
||||
],
|
||||
'analytics' => config('app.analytics') ?? '',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
|
|||
* @property int $id
|
||||
* @property int $server_id
|
||||
* @property int $uuid
|
||||
* @property bool $is_successful
|
||||
* @property string $name
|
||||
* @property string[] $ignored_files
|
||||
* @property string $disk
|
||||
|
@ -44,6 +45,7 @@ class Backup extends Model
|
|||
*/
|
||||
protected $casts = [
|
||||
'id' => 'int',
|
||||
'is_successful' => 'bool',
|
||||
'bytes' => 'int',
|
||||
'ignored_files' => 'array',
|
||||
];
|
||||
|
@ -59,6 +61,7 @@ class Backup extends Model
|
|||
* @var array
|
||||
*/
|
||||
protected $attributes = [
|
||||
'is_successful' => true,
|
||||
'sha256_hash' => null,
|
||||
'bytes' => 0,
|
||||
];
|
||||
|
@ -69,6 +72,7 @@ class Backup extends Model
|
|||
public static $validationRules = [
|
||||
'server_id' => 'bail|required|numeric|exists:servers,id',
|
||||
'uuid' => 'required|uuid',
|
||||
'is_successful' => 'boolean',
|
||||
'name' => 'required|string',
|
||||
'ignored_files' => 'array',
|
||||
'disk' => 'required|string',
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
namespace Pterodactyl\Models;
|
||||
|
||||
use Pterodactyl\Rules\ResolvesToIPAddress;
|
||||
|
||||
class DatabaseHost extends Model
|
||||
{
|
||||
/**
|
||||
|
@ -51,13 +53,25 @@ class DatabaseHost extends Model
|
|||
*/
|
||||
public static $validationRules = [
|
||||
'name' => 'required|string|max:255',
|
||||
'host' => 'required|unique:database_hosts,host',
|
||||
'host' => 'required|string',
|
||||
'port' => 'required|numeric|between:1,65535',
|
||||
'username' => 'required|string|max:32',
|
||||
'password' => 'nullable|string',
|
||||
'node_id' => 'sometimes|nullable|integer|exists:nodes,id',
|
||||
];
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getRules()
|
||||
{
|
||||
$rules = parent::getRules();
|
||||
|
||||
$rules['host'] = array_merge($rules['host'], [ new ResolvesToIPAddress() ]);
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the node associated with a database host.
|
||||
*
|
||||
|
|
|
@ -2,6 +2,27 @@
|
|||
|
||||
namespace Pterodactyl\Models;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property int $egg_id
|
||||
* @property string $name
|
||||
* @property string $description
|
||||
* @property string $env_variable
|
||||
* @property string $default_value
|
||||
* @property bool $user_viewable
|
||||
* @property bool $user_editable
|
||||
* @property string $rules
|
||||
* @property \Carbon\CarbonImmutable $created_at
|
||||
* @property \Carbon\CarbonImmutable $updated_at
|
||||
*
|
||||
* @property bool $required
|
||||
* @property \Pterodactyl\Models\Egg $egg
|
||||
* @property \Pterodactyl\Models\ServerVariable $serverVariable
|
||||
*
|
||||
* The "server_value" variable is only present on the object if you've loaded this model
|
||||
* using the server relationship.
|
||||
* @property string|null $server_value
|
||||
*/
|
||||
class EggVariable extends Model
|
||||
{
|
||||
/**
|
||||
|
@ -17,6 +38,11 @@ class EggVariable extends Model
|
|||
*/
|
||||
const RESERVED_ENV_NAMES = 'SERVER_MEMORY,SERVER_IP,SERVER_PORT,ENV,HOME,USER,STARTUP,SERVER_UUID,UUID';
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $immutableDates = true;
|
||||
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
|
@ -38,8 +64,8 @@ class EggVariable extends Model
|
|||
*/
|
||||
protected $casts = [
|
||||
'egg_id' => 'integer',
|
||||
'user_viewable' => 'integer',
|
||||
'user_editable' => 'integer',
|
||||
'user_viewable' => 'bool',
|
||||
'user_editable' => 'bool',
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -65,12 +91,19 @@ class EggVariable extends Model
|
|||
];
|
||||
|
||||
/**
|
||||
* @param $value
|
||||
* @return bool
|
||||
*/
|
||||
public function getRequiredAttribute($value)
|
||||
public function getRequiredAttribute()
|
||||
{
|
||||
return $this->rules === 'required' || str_contains($this->rules, ['required|', '|required']);
|
||||
return in_array('required', explode('|', $this->rules));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasOne
|
||||
*/
|
||||
public function egg()
|
||||
{
|
||||
return $this->hasOne(Egg::class);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -55,6 +55,9 @@ class Permission extends Model
|
|||
const ACTION_FILE_ARCHIVE = 'file.archive';
|
||||
const ACTION_FILE_SFTP = 'file.sftp';
|
||||
|
||||
const ACTION_STARTUP_READ = 'startup.read';
|
||||
const ACTION_STARTUP_UPDATE = 'startup.update';
|
||||
|
||||
const ACTION_SETTINGS_RENAME = 'settings.rename';
|
||||
const ACTION_SETTINGS_REINSTALL = 'settings.reinstall';
|
||||
|
||||
|
@ -169,8 +172,8 @@ class Permission extends Model
|
|||
'startup' => [
|
||||
'description' => 'Permissions that control a user\'s ability to view this server\'s startup parameters.',
|
||||
'keys' => [
|
||||
'read' => '',
|
||||
'update' => '',
|
||||
'read' => 'Allows a user to view the startup variables for a server.',
|
||||
'update' => 'Allows a user to modify the startup variables for the server.',
|
||||
],
|
||||
],
|
||||
|
||||
|
|
|
@ -38,14 +38,14 @@ use Znck\Eloquent\Traits\BelongsToThrough;
|
|||
* @property \Carbon\Carbon $updated_at
|
||||
*
|
||||
* @property \Pterodactyl\Models\User $user
|
||||
* @property \Pterodactyl\Models\User[]|\Illuminate\Database\Eloquent\Collection $subusers
|
||||
* @property \Pterodactyl\Models\Subuser[]|\Illuminate\Database\Eloquent\Collection $subusers
|
||||
* @property \Pterodactyl\Models\Allocation $allocation
|
||||
* @property \Pterodactyl\Models\Allocation[]|\Illuminate\Database\Eloquent\Collection $allocations
|
||||
* @property \Pterodactyl\Models\Pack|null $pack
|
||||
* @property \Pterodactyl\Models\Node $node
|
||||
* @property \Pterodactyl\Models\Nest $nest
|
||||
* @property \Pterodactyl\Models\Egg $egg
|
||||
* @property \Pterodactyl\Models\ServerVariable[]|\Illuminate\Database\Eloquent\Collection $variables
|
||||
* @property \Pterodactyl\Models\EggVariable[]|\Illuminate\Database\Eloquent\Collection $variables
|
||||
* @property \Pterodactyl\Models\Schedule[]|\Illuminate\Database\Eloquent\Collection $schedule
|
||||
* @property \Pterodactyl\Models\Database[]|\Illuminate\Database\Eloquent\Collection $databases
|
||||
* @property \Pterodactyl\Models\Location $location
|
||||
|
@ -270,7 +270,9 @@ class Server extends Model
|
|||
*/
|
||||
public function variables()
|
||||
{
|
||||
return $this->hasMany(ServerVariable::class);
|
||||
return $this->hasMany(EggVariable::class, 'egg_id', 'egg_id')
|
||||
->select(['egg_variables.*', 'server_variables.variable_value as server_value'])
|
||||
->leftJoin('server_variables', 'server_variables.variable_id', '=', 'egg_variables.id');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -57,11 +57,6 @@ class User extends Model implements
|
|||
const USER_LEVEL_USER = 0;
|
||||
const USER_LEVEL_ADMIN = 1;
|
||||
|
||||
const FILTER_LEVEL_ALL = 0;
|
||||
const FILTER_LEVEL_OWNER = 1;
|
||||
const FILTER_LEVEL_ADMIN = 2;
|
||||
const FILTER_LEVEL_SUBUSER = 3;
|
||||
|
||||
/**
|
||||
* The resource name for this model when it is transformed into an
|
||||
* API representation using fractal.
|
||||
|
|
|
@ -21,6 +21,7 @@ class SettingsServiceProvider extends ServiceProvider
|
|||
protected $keys = [
|
||||
'app:name',
|
||||
'app:locale',
|
||||
'app:analytics',
|
||||
'recaptcha:enabled',
|
||||
'recaptcha:secret_key',
|
||||
'recaptcha:website_key',
|
||||
|
|
|
@ -27,6 +27,7 @@ class BackupRepository extends EloquentRepository
|
|||
return $this->getBuilder()
|
||||
->withTrashed()
|
||||
->where('server_id', $server)
|
||||
->where('is_successful', true)
|
||||
->where('created_at', '>=', Carbon::now()->subMinutes($minutes)->toDateTimeString())
|
||||
->get()
|
||||
->toBase();
|
||||
|
|
|
@ -143,6 +143,10 @@ class ServerRepository extends EloquentRepository implements ServerRepositoryInt
|
|||
*/
|
||||
public function getVariablesWithValues(int $id, bool $returnAsObject = false)
|
||||
{
|
||||
$this->getBuilder()
|
||||
->with('variables', 'egg.variables')
|
||||
->findOrFail($id);
|
||||
|
||||
try {
|
||||
$instance = $this->getBuilder()->with('variables', 'egg.variables')->find($id, $this->getColumns());
|
||||
} catch (ModelNotFoundException $exception) {
|
||||
|
|
|
@ -18,30 +18,6 @@ class SubuserRepository extends EloquentRepository implements SubuserRepositoryI
|
|||
return Subuser::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a subuser model for the given user and server combination. If no record
|
||||
* exists an exception will be thrown.
|
||||
*
|
||||
* @param int $server
|
||||
* @param string $uuid
|
||||
* @return \Pterodactyl\Models\Subuser
|
||||
*
|
||||
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
|
||||
*/
|
||||
public function getUserForServer(int $server, string $uuid): Subuser
|
||||
{
|
||||
/** @var \Pterodactyl\Models\Subuser $model */
|
||||
$model = $this->getBuilder()
|
||||
->with('server', 'user')
|
||||
->select('subusers.*')
|
||||
->join('users', 'users.id', '=', 'subusers.user_id')
|
||||
->where('subusers.server_id', $server)
|
||||
->where('users.uuid', $uuid)
|
||||
->firstOrFail();
|
||||
|
||||
return $model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a subuser with the associated server relationship.
|
||||
*
|
||||
|
|
|
@ -5,6 +5,8 @@ namespace Pterodactyl\Repositories\Wings;
|
|||
use Webmozart\Assert\Assert;
|
||||
use Pterodactyl\Models\Server;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use GuzzleHttp\Exception\TransferException;
|
||||
use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
|
||||
|
||||
class DaemonCommandRepository extends DaemonRepository
|
||||
{
|
||||
|
@ -13,16 +15,22 @@ class DaemonCommandRepository extends DaemonRepository
|
|||
*
|
||||
* @param string|string[] $command
|
||||
* @return \Psr\Http\Message\ResponseInterface
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
|
||||
*/
|
||||
public function send($command): ResponseInterface
|
||||
{
|
||||
Assert::isInstanceOf($this->server, Server::class);
|
||||
|
||||
return $this->getHttpClient()->post(
|
||||
sprintf('/api/servers/%s/commands', $this->server->uuid),
|
||||
[
|
||||
'json' => ['commands' => is_array($command) ? $command : [$command]],
|
||||
]
|
||||
);
|
||||
try {
|
||||
return $this->getHttpClient()->post(
|
||||
sprintf('/api/servers/%s/commands', $this->server->uuid),
|
||||
[
|
||||
'json' => ['commands' => is_array($command) ? $command : [$command]],
|
||||
]
|
||||
);
|
||||
} catch (TransferException $exception) {
|
||||
throw new DaemonConnectionException($exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,9 @@ namespace Pterodactyl\Repositories\Wings;
|
|||
use Webmozart\Assert\Assert;
|
||||
use Pterodactyl\Models\Server;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use GuzzleHttp\Exception\TransferException;
|
||||
use Pterodactyl\Exceptions\Http\Server\FileSizeTooLargeException;
|
||||
use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
|
||||
|
||||
class DaemonFileRepository extends DaemonRepository
|
||||
{
|
||||
|
@ -18,17 +20,22 @@ class DaemonFileRepository extends DaemonRepository
|
|||
*
|
||||
* @throws \GuzzleHttp\Exception\TransferException
|
||||
* @throws \Pterodactyl\Exceptions\Http\Server\FileSizeTooLargeException
|
||||
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
|
||||
*/
|
||||
public function getContent(string $path, int $notLargerThan = null): string
|
||||
{
|
||||
Assert::isInstanceOf($this->server, Server::class);
|
||||
|
||||
$response = $this->getHttpClient()->get(
|
||||
sprintf('/api/servers/%s/files/contents', $this->server->uuid),
|
||||
[
|
||||
'query' => ['file' => $path],
|
||||
]
|
||||
);
|
||||
try {
|
||||
$response = $this->getHttpClient()->get(
|
||||
sprintf('/api/servers/%s/files/contents', $this->server->uuid),
|
||||
[
|
||||
'query' => ['file' => $path],
|
||||
]
|
||||
);
|
||||
} catch (TransferException $exception) {
|
||||
throw new DaemonConnectionException($exception);
|
||||
}
|
||||
|
||||
$length = (int) $response->getHeader('Content-Length')[0] ?? 0;
|
||||
|
||||
|
@ -47,19 +54,23 @@ class DaemonFileRepository extends DaemonRepository
|
|||
* @param string $content
|
||||
* @return \Psr\Http\Message\ResponseInterface
|
||||
*
|
||||
* @throws \GuzzleHttp\Exception\TransferException
|
||||
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
|
||||
*/
|
||||
public function putContent(string $path, string $content): ResponseInterface
|
||||
{
|
||||
Assert::isInstanceOf($this->server, Server::class);
|
||||
|
||||
return $this->getHttpClient()->post(
|
||||
sprintf('/api/servers/%s/files/write', $this->server->uuid),
|
||||
[
|
||||
'query' => ['file' => $path],
|
||||
'body' => $content,
|
||||
]
|
||||
);
|
||||
try {
|
||||
return $this->getHttpClient()->post(
|
||||
sprintf('/api/servers/%s/files/write', $this->server->uuid),
|
||||
[
|
||||
'query' => ['file' => $path],
|
||||
'body' => $content,
|
||||
]
|
||||
);
|
||||
} catch (TransferException $exception) {
|
||||
throw new DaemonConnectionException($exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -68,18 +79,22 @@ class DaemonFileRepository extends DaemonRepository
|
|||
* @param string $path
|
||||
* @return array
|
||||
*
|
||||
* @throws \GuzzleHttp\Exception\TransferException
|
||||
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
|
||||
*/
|
||||
public function getDirectory(string $path): array
|
||||
{
|
||||
Assert::isInstanceOf($this->server, Server::class);
|
||||
|
||||
$response = $this->getHttpClient()->get(
|
||||
sprintf('/api/servers/%s/files/list-directory', $this->server->uuid),
|
||||
[
|
||||
'query' => ['directory' => $path],
|
||||
]
|
||||
);
|
||||
try {
|
||||
$response = $this->getHttpClient()->get(
|
||||
sprintf('/api/servers/%s/files/list-directory', $this->server->uuid),
|
||||
[
|
||||
'query' => ['directory' => $path],
|
||||
]
|
||||
);
|
||||
} catch (TransferException $exception) {
|
||||
throw new DaemonConnectionException($exception);
|
||||
}
|
||||
|
||||
return json_decode($response->getBody(), true);
|
||||
}
|
||||
|
@ -90,20 +105,26 @@ class DaemonFileRepository extends DaemonRepository
|
|||
* @param string $name
|
||||
* @param string $path
|
||||
* @return \Psr\Http\Message\ResponseInterface
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
|
||||
*/
|
||||
public function createDirectory(string $name, string $path): ResponseInterface
|
||||
{
|
||||
Assert::isInstanceOf($this->server, Server::class);
|
||||
|
||||
return $this->getHttpClient()->post(
|
||||
sprintf('/api/servers/%s/files/create-directory', $this->server->uuid),
|
||||
[
|
||||
'json' => [
|
||||
'name' => urldecode($name),
|
||||
'path' => urldecode($path),
|
||||
],
|
||||
]
|
||||
);
|
||||
try {
|
||||
return $this->getHttpClient()->post(
|
||||
sprintf('/api/servers/%s/files/create-directory', $this->server->uuid),
|
||||
[
|
||||
'json' => [
|
||||
'name' => urldecode($name),
|
||||
'path' => urldecode($path),
|
||||
],
|
||||
]
|
||||
);
|
||||
} catch (TransferException $exception) {
|
||||
throw new DaemonConnectionException($exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -112,20 +133,26 @@ class DaemonFileRepository extends DaemonRepository
|
|||
* @param string|null $root
|
||||
* @param array $files
|
||||
* @return \Psr\Http\Message\ResponseInterface
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
|
||||
*/
|
||||
public function renameFiles(?string $root, array $files): ResponseInterface
|
||||
{
|
||||
Assert::isInstanceOf($this->server, Server::class);
|
||||
|
||||
return $this->getHttpClient()->put(
|
||||
sprintf('/api/servers/%s/files/rename', $this->server->uuid),
|
||||
[
|
||||
'json' => [
|
||||
'root' => $root ?? '/',
|
||||
'files' => $files,
|
||||
],
|
||||
]
|
||||
);
|
||||
try {
|
||||
return $this->getHttpClient()->put(
|
||||
sprintf('/api/servers/%s/files/rename', $this->server->uuid),
|
||||
[
|
||||
'json' => [
|
||||
'root' => $root ?? '/',
|
||||
'files' => $files,
|
||||
],
|
||||
]
|
||||
);
|
||||
} catch (TransferException $exception) {
|
||||
throw new DaemonConnectionException($exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -133,19 +160,25 @@ class DaemonFileRepository extends DaemonRepository
|
|||
*
|
||||
* @param string $location
|
||||
* @return \Psr\Http\Message\ResponseInterface
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
|
||||
*/
|
||||
public function copyFile(string $location): ResponseInterface
|
||||
{
|
||||
Assert::isInstanceOf($this->server, Server::class);
|
||||
|
||||
return $this->getHttpClient()->post(
|
||||
sprintf('/api/servers/%s/files/copy', $this->server->uuid),
|
||||
[
|
||||
'json' => [
|
||||
'location' => urldecode($location),
|
||||
],
|
||||
]
|
||||
);
|
||||
try {
|
||||
return $this->getHttpClient()->post(
|
||||
sprintf('/api/servers/%s/files/copy', $this->server->uuid),
|
||||
[
|
||||
'json' => [
|
||||
'location' => urldecode($location),
|
||||
],
|
||||
]
|
||||
);
|
||||
} catch (TransferException $exception) {
|
||||
throw new DaemonConnectionException($exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -154,20 +187,26 @@ class DaemonFileRepository extends DaemonRepository
|
|||
* @param string|null $root
|
||||
* @param array $files
|
||||
* @return \Psr\Http\Message\ResponseInterface
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
|
||||
*/
|
||||
public function deleteFiles(?string $root, array $files): ResponseInterface
|
||||
{
|
||||
Assert::isInstanceOf($this->server, Server::class);
|
||||
|
||||
return $this->getHttpClient()->post(
|
||||
sprintf('/api/servers/%s/files/delete', $this->server->uuid),
|
||||
[
|
||||
'json' => [
|
||||
'root' => $root ?? '/',
|
||||
'files' => $files,
|
||||
],
|
||||
]
|
||||
);
|
||||
try {
|
||||
return $this->getHttpClient()->post(
|
||||
sprintf('/api/servers/%s/files/delete', $this->server->uuid),
|
||||
[
|
||||
'json' => [
|
||||
'root' => $root ?? '/',
|
||||
'files' => $files,
|
||||
],
|
||||
]
|
||||
);
|
||||
} catch (TransferException $exception) {
|
||||
throw new DaemonConnectionException($exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -176,21 +215,58 @@ class DaemonFileRepository extends DaemonRepository
|
|||
* @param string|null $root
|
||||
* @param array $files
|
||||
* @return array
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
|
||||
*/
|
||||
public function compressFiles(?string $root, array $files): array
|
||||
{
|
||||
Assert::isInstanceOf($this->server, Server::class);
|
||||
|
||||
$response = $this->getHttpClient()->post(
|
||||
sprintf('/api/servers/%s/files/compress', $this->server->uuid),
|
||||
[
|
||||
'json' => [
|
||||
'root' => $root ?? '/',
|
||||
'files' => $files,
|
||||
],
|
||||
]
|
||||
);
|
||||
try {
|
||||
$response = $this->getHttpClient()->post(
|
||||
sprintf('/api/servers/%s/files/compress', $this->server->uuid),
|
||||
[
|
||||
'json' => [
|
||||
'root' => $root ?? '/',
|
||||
'files' => $files,
|
||||
],
|
||||
// Wait for up to 15 minutes for the archive to be completed when calling this endpoint
|
||||
// since it will likely take quite awhile for large directories.
|
||||
'timeout' => 60 * 15,
|
||||
]
|
||||
);
|
||||
} catch (TransferException $exception) {
|
||||
throw new DaemonConnectionException($exception);
|
||||
}
|
||||
|
||||
return json_decode($response->getBody(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decompresses a given archive file.
|
||||
*
|
||||
* @param string|null $root
|
||||
* @param string $file
|
||||
* @return \Psr\Http\Message\ResponseInterface
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
|
||||
*/
|
||||
public function decompressFile(?string $root, string $file): ResponseInterface
|
||||
{
|
||||
Assert::isInstanceOf($this->server, Server::class);
|
||||
|
||||
try {
|
||||
return $this->getHttpClient()->post(
|
||||
sprintf('/api/servers/%s/files/decompress', $this->server->uuid),
|
||||
[
|
||||
'json' => [
|
||||
'root' => $root ?? '/',
|
||||
'file' => $file,
|
||||
],
|
||||
]
|
||||
);
|
||||
} catch (TransferException $exception) {
|
||||
throw new DaemonConnectionException($exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@ namespace Pterodactyl\Repositories\Wings;
|
|||
use Webmozart\Assert\Assert;
|
||||
use Pterodactyl\Models\Server;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use GuzzleHttp\Exception\TransferException;
|
||||
use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
|
||||
|
||||
class DaemonPowerRepository extends DaemonRepository
|
||||
{
|
||||
|
@ -13,14 +15,20 @@ class DaemonPowerRepository extends DaemonRepository
|
|||
*
|
||||
* @param string $action
|
||||
* @return \Psr\Http\Message\ResponseInterface
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
|
||||
*/
|
||||
public function send(string $action): ResponseInterface
|
||||
{
|
||||
Assert::isInstanceOf($this->server, Server::class);
|
||||
|
||||
return $this->getHttpClient()->post(
|
||||
sprintf('/api/servers/%s/power', $this->server->uuid),
|
||||
['json' => ['action' => $action]]
|
||||
);
|
||||
try {
|
||||
return $this->getHttpClient()->post(
|
||||
sprintf('/api/servers/%s/power', $this->server->uuid),
|
||||
['json' => ['action' => $action]]
|
||||
);
|
||||
} catch (TransferException $exception) {
|
||||
throw new DaemonConnectionException($exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ class DaemonServerRepository extends DaemonRepository
|
|||
sprintf('/api/servers/%s', $this->server->uuid)
|
||||
);
|
||||
} catch (TransferException $exception) {
|
||||
throw new DaemonConnectionException($exception);
|
||||
throw new DaemonConnectionException($exception, false);
|
||||
}
|
||||
|
||||
return json_decode($response->getBody()->__toString(), true);
|
||||
|
|
49
app/Rules/ResolvesToIPAddress.php
Normal file
49
app/Rules/ResolvesToIPAddress.php
Normal file
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Rules;
|
||||
|
||||
use Illuminate\Contracts\Validation\Rule;
|
||||
|
||||
class ResolvesToIPAddress implements Rule
|
||||
{
|
||||
/**
|
||||
* Validate that a given string can correctly resolve to a valid IPv4 address.
|
||||
*
|
||||
* @param string $attribute
|
||||
* @param mixed $value
|
||||
* @return bool
|
||||
*/
|
||||
public function passes($attribute, $value): bool
|
||||
{
|
||||
// inet_pton returns false if the value passed through is not a valid IP address, so we'll just
|
||||
// use that a nice ugly PHP hack to determine if we should pass this off to the gethostbyname
|
||||
// call below.
|
||||
$isIP = inet_pton($attribute) !== false;
|
||||
|
||||
// If the value received is not an IP address try to look it up using the gethostbyname() call.
|
||||
// If that returns the same value that we passed in then it means it did not resolve to anything
|
||||
// and we should fail this validation call.
|
||||
return $isIP || gethostbyname($value) !== $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a validation message for use when this rule fails.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function message(): string
|
||||
{
|
||||
return 'The :attribute must be a valid IPv4 address or hostname that resolves to a valid IPv4 address.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the rule to a validation string. This is necessary to avoid
|
||||
* issues with Eloquence which tries to use this rule as a string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return 'p_resolves_to_ip_address';
|
||||
}
|
||||
}
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace Pterodactyl\Services\Backups;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use Carbon\CarbonImmutable;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
@ -101,14 +100,14 @@ class InitiateBackupService
|
|||
public function handle(Server $server, string $name = null): Backup
|
||||
{
|
||||
// Do not allow the user to continue if this server is already at its limit.
|
||||
if (! $server->backup_limit || $server->backups()->count() >= $server->backup_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(
|
||||
Carbon::now()->diffInSeconds($previous->last()->created_at->addMinutes(10)),
|
||||
CarbonImmutable::now()->diffInSeconds($previous->last()->created_at->addMinutes(10)),
|
||||
'Only two backups may be generated within a 10 minute span of time.'
|
||||
);
|
||||
}
|
||||
|
|
|
@ -51,12 +51,29 @@ class EggConfigurationService
|
|||
);
|
||||
|
||||
return [
|
||||
'startup' => json_decode($server->egg->inherit_config_startup),
|
||||
'startup' => $this->convertStartupToNewFormat(json_decode($server->egg->inherit_config_startup, true)),
|
||||
'stop' => $this->convertStopToNewFormat($server->egg->inherit_config_stop),
|
||||
'configs' => $configs,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the "done" variable into an array if it is not currently one.
|
||||
*
|
||||
* @param array $startup
|
||||
* @return array
|
||||
*/
|
||||
protected function convertStartupToNewFormat(array $startup)
|
||||
{
|
||||
$done = Arr::get($startup, 'done');
|
||||
|
||||
return [
|
||||
'done' => is_string($done) ? [$done] : $done,
|
||||
'user_interaction' => Arr::get($startup, 'userInteraction') ?? Arr::get($startup, 'user_interaction') ?? [],
|
||||
'strip_ansi' => Arr::get($startup, 'strip_ansi') ?? false,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a legacy stop string into a new generation stop option for a server.
|
||||
*
|
||||
|
|
|
@ -30,7 +30,7 @@ class GetUserPermissionsService
|
|||
}
|
||||
|
||||
/** @var \Pterodactyl\Models\Subuser|null $subuserPermissions */
|
||||
$subuserPermissions = $server->subusers->where('user_id', $user->id)->first();
|
||||
$subuserPermissions = $server->subusers()->where('user_id', $user->id)->first();
|
||||
|
||||
return $subuserPermissions ? $subuserPermissions->permissions : [];
|
||||
}
|
||||
|
|
27
app/Services/Servers/StartupCommandService.php
Normal file
27
app/Services/Servers/StartupCommandService.php
Normal file
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Services\Servers;
|
||||
|
||||
use Pterodactyl\Models\Server;
|
||||
|
||||
class StartupCommandService
|
||||
{
|
||||
/**
|
||||
* Generates a startup command for a given server instance.
|
||||
*
|
||||
* @param \Pterodactyl\Models\Server $server
|
||||
* @return string
|
||||
*/
|
||||
public function handle(Server $server): string
|
||||
{
|
||||
$find = ['{{SERVER_MEMORY}}', '{{SERVER_IP}}', '{{SERVER_PORT}}'];
|
||||
$replace = [$server->memory, $server->allocation->ip, $server->allocation->port];
|
||||
|
||||
foreach ($server->variables as $variable) {
|
||||
$find[] = '{{' . $variable->env_variable . '}}';
|
||||
$replace[] = $variable->user_viewable ? ($variable->server_value ?? $variable->default_value) : '[hidden]';
|
||||
}
|
||||
|
||||
return str_replace($find, $replace, $server->startup);
|
||||
}
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Services\Servers;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
|
||||
|
||||
class StartupCommandViewService
|
||||
{
|
||||
/**
|
||||
* @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface
|
||||
*/
|
||||
private $repository;
|
||||
|
||||
/**
|
||||
* StartupCommandViewService constructor.
|
||||
*
|
||||
* @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository
|
||||
*/
|
||||
public function __construct(ServerRepositoryInterface $repository)
|
||||
{
|
||||
$this->repository = $repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a startup command for a server and return all of the user-viewable variables
|
||||
* as well as their assigned values.
|
||||
*
|
||||
* @param int $server
|
||||
* @return \Illuminate\Support\Collection
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||
*/
|
||||
public function handle(int $server): Collection
|
||||
{
|
||||
$response = $this->repository->getVariablesWithValues($server, true);
|
||||
$server = $this->repository->getPrimaryAllocation($response->server);
|
||||
|
||||
$find = ['{{SERVER_MEMORY}}', '{{SERVER_IP}}', '{{SERVER_PORT}}'];
|
||||
$replace = [$server->memory, $server->getRelation('allocation')->ip, $server->getRelation('allocation')->port];
|
||||
|
||||
$variables = $server->getRelation('egg')->getRelation('variables')
|
||||
->each(function ($variable) use (&$find, &$replace, $response) {
|
||||
$find[] = '{{' . $variable->env_variable . '}}';
|
||||
$replace[] = $variable->user_viewable ? $response->data[$variable->env_variable] : '[hidden]';
|
||||
})->filter(function ($variable) {
|
||||
return $variable->user_viewable === 1;
|
||||
});
|
||||
|
||||
return collect([
|
||||
'startup' => str_replace($find, $replace, $server->startup),
|
||||
'variables' => $variables,
|
||||
'server_values' => $response->data,
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -22,6 +22,7 @@ class BackupTransformer extends BaseClientTransformer
|
|||
{
|
||||
return [
|
||||
'uuid' => $backup->uuid,
|
||||
'is_successful' => $backup->is_successful,
|
||||
'name' => $backup->name,
|
||||
'ignored_files' => $backup->ignored_files,
|
||||
'sha256_hash' => $backup->sha256_hash,
|
||||
|
|
|
@ -4,6 +4,7 @@ namespace Pterodactyl\Transformers\Api\Client;
|
|||
|
||||
use Pterodactyl\Models\Database;
|
||||
use League\Fractal\Resource\Item;
|
||||
use Pterodactyl\Models\Permission;
|
||||
use Illuminate\Contracts\Encryption\Encrypter;
|
||||
use Pterodactyl\Contracts\Extensions\HashidsInterface;
|
||||
|
||||
|
@ -65,12 +66,16 @@ class DatabaseTransformer extends BaseClientTransformer
|
|||
/**
|
||||
* Include the database password in the request.
|
||||
*
|
||||
* @param \Pterodactyl\Models\Database $model
|
||||
* @return \League\Fractal\Resource\Item
|
||||
* @param \Pterodactyl\Models\Database $database
|
||||
* @return \League\Fractal\Resource\Item|\League\Fractal\Resource\NullResource
|
||||
*/
|
||||
public function includePassword(Database $model): Item
|
||||
public function includePassword(Database $database): Item
|
||||
{
|
||||
return $this->item($model, function (Database $model) {
|
||||
if (!$this->getUser()->can(Permission::ACTION_DATABASE_VIEW_PASSWORD, $database->server)) {
|
||||
return $this->null();
|
||||
}
|
||||
|
||||
return $this->item($database, function (Database $model) {
|
||||
return [
|
||||
'password' => $this->encrypter->decrypt($model->password),
|
||||
];
|
||||
|
|
33
app/Transformers/Api/Client/EggVariableTransformer.php
Normal file
33
app/Transformers/Api/Client/EggVariableTransformer.php
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Transformers\Api\Client;
|
||||
|
||||
use Pterodactyl\Models\EggVariable;
|
||||
|
||||
class EggVariableTransformer extends BaseClientTransformer
|
||||
{
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getResourceName(): string
|
||||
{
|
||||
return EggVariable::RESOURCE_NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Pterodactyl\Models\EggVariable $variable
|
||||
* @return array
|
||||
*/
|
||||
public function transform(EggVariable $variable)
|
||||
{
|
||||
return [
|
||||
'name' => $variable->name,
|
||||
'description' => $variable->description,
|
||||
'env_variable' => $variable->env_variable,
|
||||
'default_value' => $variable->default_value,
|
||||
'server_value' => $variable->server_value,
|
||||
'is_editable' => $variable->user_editable,
|
||||
'rules' => $variable->rules,
|
||||
];
|
||||
}
|
||||
}
|
|
@ -6,13 +6,17 @@ use Pterodactyl\Models\Egg;
|
|||
use Pterodactyl\Models\Server;
|
||||
use Pterodactyl\Models\Subuser;
|
||||
use Pterodactyl\Models\Allocation;
|
||||
use Pterodactyl\Models\Permission;
|
||||
use Illuminate\Container\Container;
|
||||
use Pterodactyl\Models\EggVariable;
|
||||
use Pterodactyl\Services\Servers\StartupCommandService;
|
||||
|
||||
class ServerTransformer extends BaseClientTransformer
|
||||
{
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $defaultIncludes = ['allocations'];
|
||||
protected $defaultIncludes = ['allocations', 'variables'];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
|
@ -36,6 +40,9 @@ class ServerTransformer extends BaseClientTransformer
|
|||
*/
|
||||
public function transform(Server $server): array
|
||||
{
|
||||
/** @var \Pterodactyl\Services\Servers\StartupCommandService $service */
|
||||
$service = Container::getInstance()->make(StartupCommandService::class);
|
||||
|
||||
return [
|
||||
'server_owner' => $this->getKey()->user_id === $server->owner_id,
|
||||
'identifier' => $server->uuidShort,
|
||||
|
@ -54,6 +61,7 @@ class ServerTransformer extends BaseClientTransformer
|
|||
'io' => $server->io,
|
||||
'cpu' => $server->cpu,
|
||||
],
|
||||
'invocation' => $service->handle($server),
|
||||
'feature_limits' => [
|
||||
'databases' => $server->database_limit,
|
||||
'allocations' => $server->allocation_limit,
|
||||
|
@ -68,11 +76,16 @@ class ServerTransformer extends BaseClientTransformer
|
|||
* Returns the allocations associated with this server.
|
||||
*
|
||||
* @param \Pterodactyl\Models\Server $server
|
||||
* @return \League\Fractal\Resource\Collection
|
||||
* @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException
|
||||
*/
|
||||
public function includeAllocations(Server $server)
|
||||
{
|
||||
if (! $this->getUser()->can(Permission::ACTION_ALLOCATION_READ, $server)) {
|
||||
return $this->null();
|
||||
}
|
||||
|
||||
return $this->collection(
|
||||
$server->allocations,
|
||||
$this->makeTransformer(AllocationTransformer::class),
|
||||
|
@ -80,6 +93,25 @@ class ServerTransformer extends BaseClientTransformer
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Pterodactyl\Models\Server $server
|
||||
* @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException
|
||||
*/
|
||||
public function includeVariables(Server $server)
|
||||
{
|
||||
if (! $this->getUser()->can(Permission::ACTION_STARTUP_READ, $server)) {
|
||||
return $this->null();
|
||||
}
|
||||
|
||||
return $this->collection(
|
||||
$server->variables->where('user_viewable', true),
|
||||
$this->makeTransformer(EggVariableTransformer::class),
|
||||
EggVariable::RESOURCE_NAME
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the egg associated with this server.
|
||||
*
|
||||
|
@ -96,11 +128,16 @@ class ServerTransformer extends BaseClientTransformer
|
|||
* Returns the subusers associated with this server.
|
||||
*
|
||||
* @param \Pterodactyl\Models\Server $server
|
||||
* @return \League\Fractal\Resource\Collection
|
||||
* @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException
|
||||
*/
|
||||
public function includeSubusers(Server $server)
|
||||
{
|
||||
if (! $this->getUser()->can(Permission::ACTION_USER_READ, $server)) {
|
||||
return $this->null();
|
||||
}
|
||||
|
||||
return $this->collection($server->subusers, $this->makeTransformer(SubuserTransformer::class), Subuser::RESOURCE_NAME);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,11 +27,11 @@ class StatsTransformer extends BaseClientTransformer
|
|||
'current_state' => Arr::get($data, 'state', 'stopped'),
|
||||
'is_suspended' => Arr::get($data, 'suspended', false),
|
||||
'resources' => [
|
||||
'memory_bytes' => Arr::get($data, 'resources.memory_bytes', 0),
|
||||
'cpu_absolute' => Arr::get($data, 'resources.cpu_absolute', 0),
|
||||
'disk_bytes' => Arr::get($data, 'resources.disk_bytes', 0),
|
||||
'network_rx_bytes' => Arr::get($data, 'resources.network.rx_bytes', 0),
|
||||
'network_tx_bytes' => Arr::get($data, 'resources.network.tx_bytes', 0),
|
||||
'memory_bytes' => Arr::get($data, 'memory_bytes', 0),
|
||||
'cpu_absolute' => Arr::get($data, 'cpu_absolute', 0),
|
||||
'disk_bytes' => Arr::get($data, 'disk_bytes', 0),
|
||||
'network_rx_bytes' => Arr::get($data, 'network.rx_bytes', 0),
|
||||
'network_tx_bytes' => Arr::get($data, 'network.tx_bytes', 0),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue