Merge branch 'develop' into matthewpi/security-keys-backport
This commit is contained in:
commit
f631ac1946
1153 changed files with 25099 additions and 37002 deletions
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace Pterodactyl\Services\Acl\Api;
|
||||
|
||||
use ReflectionClass;
|
||||
use Pterodactyl\Models\ApiKey;
|
||||
|
||||
class AdminAcl
|
||||
|
@ -63,7 +62,7 @@ class AdminAcl
|
|||
*/
|
||||
public static function getResourceList(): array
|
||||
{
|
||||
$reflect = new ReflectionClass(__CLASS__);
|
||||
$reflect = new \ReflectionClass(__CLASS__);
|
||||
|
||||
return collect($reflect->getConstants())->filter(function ($value, $key) {
|
||||
return substr($key, 0, 9) === 'RESOURCE_';
|
||||
|
|
|
@ -99,7 +99,7 @@ class ActivityLogService
|
|||
}
|
||||
|
||||
/**
|
||||
* Sets a custom property on the activty log instance.
|
||||
* Sets a custom property on the activity log instance.
|
||||
*
|
||||
* @param string|array $key
|
||||
* @param mixed $value
|
||||
|
@ -115,7 +115,7 @@ class ActivityLogService
|
|||
}
|
||||
|
||||
/**
|
||||
* Attachs the instance request metadata to the activity log event.
|
||||
* Attaches the instance request metadata to the activity log event.
|
||||
*/
|
||||
public function withRequestMetadata(): self
|
||||
{
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace Pterodactyl\Services\Allocations;
|
||||
|
||||
use Exception;
|
||||
use IPTools\Network;
|
||||
use Pterodactyl\Models\Node;
|
||||
use Illuminate\Database\ConnectionInterface;
|
||||
|
@ -40,23 +39,24 @@ class AssignmentService
|
|||
*/
|
||||
public function handle(Node $node, array $data): void
|
||||
{
|
||||
$explode = explode('/', $data['allocation_ip']);
|
||||
$allocationIp = $data['allocation_ip'];
|
||||
$explode = explode('/', $allocationIp);
|
||||
if (count($explode) !== 1) {
|
||||
if (!ctype_digit($explode[1]) || ($explode[1] > self::CIDR_MIN_BITS || $explode[1] < self::CIDR_MAX_BITS)) {
|
||||
throw new CidrOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
$underlying = 'Unknown IP';
|
||||
try {
|
||||
// TODO: how should we approach supporting IPv6 with this?
|
||||
// gethostbyname only supports IPv4, but the alternative (dns_get_record) returns
|
||||
// an array of records, which is not ideal for this use case, we need a SINGLE
|
||||
// IP to use, not multiple.
|
||||
$underlying = gethostbyname($data['allocation_ip']);
|
||||
$underlying = gethostbyname($allocationIp);
|
||||
$parsed = Network::parse($underlying);
|
||||
} catch (Exception $exception) {
|
||||
/* @noinspection PhpUndefinedVariableInspection */
|
||||
throw new DisplayException("Could not parse provided allocation IP address ({$underlying}): {$exception->getMessage()}", $exception);
|
||||
} catch (\Exception $exception) {
|
||||
throw new DisplayException("Could not parse provided allocation IP address for $allocationIp ($underlying): {$exception->getMessage()}", $exception);
|
||||
}
|
||||
|
||||
$this->connection->beginTransaction();
|
||||
|
|
|
@ -73,7 +73,10 @@ class DeleteBackupService
|
|||
/** @var \Pterodactyl\Extensions\Filesystem\S3Filesystem $adapter */
|
||||
$adapter = $this->manager->adapter(Backup::ADAPTER_AWS_S3);
|
||||
|
||||
$adapter->getClient()->deleteObject([
|
||||
/** @var \Aws\S3\S3Client $client */
|
||||
$client = $adapter->getClient();
|
||||
|
||||
$client->deleteObject([
|
||||
'Bucket' => $adapter->getBucket(),
|
||||
'Key' => sprintf('%s/%s.tar.gz', $backup->server->uuid, $backup->uuid),
|
||||
]);
|
||||
|
|
|
@ -98,17 +98,17 @@ class InitiateBackupService
|
|||
// Get the oldest backup the server has that is not "locked" (indicating a backup that should
|
||||
// never be automatically purged). If we find a backup we will delete it and then continue with
|
||||
// this process. If no backup is found that can be used an exception is thrown.
|
||||
/** @var \Pterodactyl\Models\Backup $oldest */
|
||||
$oldest = $successful->where('is_locked', false)->orderBy('created_at')->first();
|
||||
if (!$oldest) {
|
||||
throw new TooManyBackupsException($server->backup_limit);
|
||||
}
|
||||
|
||||
/* @var Backup $oldest */
|
||||
$this->deleteBackupService->handle($oldest);
|
||||
}
|
||||
|
||||
return $this->connection->transaction(function () use ($server, $name) {
|
||||
/** @var \Pterodactyl\Models\Backup $backup */
|
||||
/** @var Backup $backup */
|
||||
$backup = $this->repository->create([
|
||||
'server_id' => $server->id,
|
||||
'uuid' => Uuid::uuid4()->toString(),
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
namespace Pterodactyl\Services\Databases;
|
||||
|
||||
use Exception;
|
||||
use InvalidArgumentException;
|
||||
use Pterodactyl\Models\Server;
|
||||
use Pterodactyl\Models\Database;
|
||||
use Pterodactyl\Helpers\Utilities;
|
||||
|
@ -86,7 +85,7 @@ class DatabaseManagementService
|
|||
|
||||
// Protect against developer mistakes...
|
||||
if (empty($data['database']) || !preg_match(self::MATCH_NAME_REGEX, $data['database'])) {
|
||||
throw new InvalidArgumentException('The database name passed to DatabaseManagementService::handle MUST be prefixed with "s{server_id}_".');
|
||||
throw new \InvalidArgumentException('The database name passed to DatabaseManagementService::handle MUST be prefixed with "s{server_id}_".');
|
||||
}
|
||||
|
||||
$data = array_merge($data, [
|
||||
|
@ -117,14 +116,15 @@ class DatabaseManagementService
|
|||
|
||||
return $database;
|
||||
});
|
||||
} catch (Exception $exception) {
|
||||
} catch (\Exception $exception) {
|
||||
try {
|
||||
/** @var ?Database $database */
|
||||
if ($database instanceof Database) {
|
||||
$this->repository->dropDatabase($database->database);
|
||||
$this->repository->dropUser($database->username, $database->remote);
|
||||
$this->repository->flush();
|
||||
}
|
||||
} catch (Exception $deletionException) {
|
||||
} catch (\Exception $deletionException) {
|
||||
// Do nothing here. We've already encountered an issue before this point so no
|
||||
// reason to prioritize this error over the initial one.
|
||||
}
|
||||
|
|
|
@ -27,21 +27,22 @@ class DeployServerDatabaseService
|
|||
Assert::notEmpty($data['database'] ?? null);
|
||||
Assert::notEmpty($data['remote'] ?? null);
|
||||
|
||||
$hosts = DatabaseHost::query()->get()->toBase();
|
||||
if ($hosts->isEmpty()) {
|
||||
throw new NoSuitableDatabaseHostException();
|
||||
} else {
|
||||
$nodeHosts = $hosts->where('node_id', $server->node_id)->toBase();
|
||||
|
||||
if ($nodeHosts->isEmpty() && !config('pterodactyl.client_features.databases.allow_random')) {
|
||||
$databaseHostId = $server->node->database_host_id;
|
||||
if (is_null($databaseHostId)) {
|
||||
if (!config('pterodactyl.client_features.databases.allow_random')) {
|
||||
throw new NoSuitableDatabaseHostException();
|
||||
}
|
||||
|
||||
$hosts = DatabaseHost::query()->get()->toBase();
|
||||
if ($hosts->isEmpty()) {
|
||||
throw new NoSuitableDatabaseHostException();
|
||||
}
|
||||
|
||||
$databaseHostId = $hosts->random()->id;
|
||||
}
|
||||
|
||||
return $this->managementService->create($server, [
|
||||
'database_host_id' => $nodeHosts->isEmpty()
|
||||
? $hosts->random()->id
|
||||
: $nodeHosts->random()->id,
|
||||
'database_host_id' => $databaseHostId,
|
||||
'database' => DatabaseManagementService::generateUniqueDatabaseName($data['database'], $server->id),
|
||||
'remote' => $data['remote'],
|
||||
]);
|
||||
|
|
|
@ -64,7 +64,7 @@ class AllocationSelectionService
|
|||
// Ranges are stored in the ports array as an array which can be
|
||||
// better processed in the repository.
|
||||
if (preg_match(AssignmentService::PORT_RANGE_REGEX, $port, $matches)) {
|
||||
if (abs($matches[2] - $matches[1]) > AssignmentService::PORT_RANGE_LIMIT) {
|
||||
if (abs(intval($matches[2]) - intval($matches[1])) > AssignmentService::PORT_RANGE_LIMIT) {
|
||||
throw new DisplayException(trans('exceptions.allocations.too_many_ports'));
|
||||
}
|
||||
|
||||
|
|
|
@ -72,18 +72,18 @@ class FindViableNodesService
|
|||
Assert::integer($this->memory, 'Memory usage must be an int, got %s');
|
||||
|
||||
$query = Node::query()->select('nodes.*')
|
||||
->selectRaw('IFNULL(SUM(servers.memory), 0) as sum_memory')
|
||||
->selectRaw('IFNULL(SUM(servers.disk), 0) as sum_disk')
|
||||
->selectRaw('COALESCE(SUM(servers.memory), 0) as sum_memory')
|
||||
->selectRaw('COALESCE(SUM(servers.disk), 0) as sum_disk')
|
||||
->leftJoin('servers', 'servers.node_id', '=', 'nodes.id')
|
||||
->where('nodes.public', 1);
|
||||
|
||||
if (!empty($this->locations)) {
|
||||
$query = $query->whereIn('nodes.location_id', $this->locations);
|
||||
$query = $query->whereIn('location_id', $this->locations);
|
||||
}
|
||||
|
||||
$results = $query->groupBy('nodes.id')
|
||||
->havingRaw('(IFNULL(SUM(servers.memory), 0) + ?) <= (nodes.memory * (1 + (nodes.memory_overallocate / 100)))', [$this->memory])
|
||||
->havingRaw('(IFNULL(SUM(servers.disk), 0) + ?) <= (nodes.disk * (1 + (nodes.disk_overallocate / 100)))', [$this->disk]);
|
||||
->havingRaw('(COALESCE(SUM(servers.memory), 0) + ?) <= (nodes.memory * (1.0 + (nodes.memory_overallocate / 100.0)))', [$this->memory])
|
||||
->havingRaw('(COALESCE(SUM(servers.disk), 0) + ?) <= (nodes.disk * (1.0 + (nodes.disk_overallocate / 100.0)))', [$this->disk]);
|
||||
|
||||
if (!is_null($page)) {
|
||||
$results = $results->paginate($perPage ?? 50, ['*'], 'page', $page);
|
||||
|
|
|
@ -4,7 +4,6 @@ namespace Pterodactyl\Services\Eggs;
|
|||
|
||||
use Illuminate\Support\Arr;
|
||||
use Pterodactyl\Models\Egg;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Collection;
|
||||
use Pterodactyl\Exceptions\Service\InvalidFileUploadException;
|
||||
|
||||
|
@ -13,17 +12,10 @@ class EggParserService
|
|||
/**
|
||||
* Takes an uploaded file and parses out the egg configuration from within.
|
||||
*
|
||||
* @throws \JsonException
|
||||
* @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException
|
||||
*/
|
||||
public function handle(UploadedFile $file): array
|
||||
public function handle(array $parsed): array
|
||||
{
|
||||
if ($file->getError() !== UPLOAD_ERR_OK || !$file->isFile()) {
|
||||
throw new InvalidFileUploadException('The selected file is not valid and cannot be imported.');
|
||||
}
|
||||
|
||||
/** @var array $parsed */
|
||||
$parsed = json_decode($file->openFile()->fread($file->getSize()), true, 512, JSON_THROW_ON_ERROR);
|
||||
if (!in_array(Arr::get($parsed, 'meta.version') ?? '', ['PTDL_v1', 'PTDL_v2'])) {
|
||||
throw new InvalidFileUploadException('The JSON file provided is not in a format that can be recognized.');
|
||||
}
|
||||
|
@ -46,7 +38,6 @@ class EggParserService
|
|||
'update_url' => Arr::get($parsed, 'meta.update_url'),
|
||||
'config_files' => Arr::get($parsed, 'config.files'),
|
||||
'config_startup' => Arr::get($parsed, 'config.startup'),
|
||||
'config_logs' => Arr::get($parsed, 'config.logs'),
|
||||
'config_stop' => Arr::get($parsed, 'config.stop'),
|
||||
'startup' => Arr::get($parsed, 'startup'),
|
||||
'script_install' => Arr::get($parsed, 'scripts.installation.script'),
|
||||
|
|
|
@ -45,7 +45,6 @@ class EggExporterService
|
|||
'config' => [
|
||||
'files' => $egg->inherit_config_files,
|
||||
'startup' => $egg->inherit_config_startup,
|
||||
'logs' => $egg->inherit_config_logs,
|
||||
'stop' => $egg->inherit_config_stop,
|
||||
],
|
||||
'scripts' => [
|
||||
|
|
|
@ -6,28 +6,107 @@ use Ramsey\Uuid\Uuid;
|
|||
use Illuminate\Support\Arr;
|
||||
use Pterodactyl\Models\Egg;
|
||||
use Pterodactyl\Models\Nest;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Pterodactyl\Models\EggVariable;
|
||||
use Illuminate\Database\ConnectionInterface;
|
||||
use Pterodactyl\Exceptions\DisplayException;
|
||||
use Pterodactyl\Services\Eggs\EggParserService;
|
||||
use Symfony\Component\Yaml\Exception\ParseException;
|
||||
use Pterodactyl\Exceptions\Service\Egg\BadJsonFormatException;
|
||||
use Pterodactyl\Exceptions\Service\Egg\BadYamlFormatException;
|
||||
use Pterodactyl\Exceptions\Service\InvalidFileUploadException;
|
||||
|
||||
class EggImporterService
|
||||
{
|
||||
public function __construct(protected ConnectionInterface $connection, protected EggParserService $parser)
|
||||
{
|
||||
public function __construct(
|
||||
private ConnectionInterface $connection,
|
||||
private EggParserService $eggParserService
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Take an uploaded JSON file and parse it into a new egg.
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException|\Throwable
|
||||
* @deprecated use `handleFile` or `handleContent` instead
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||
* @throws \Pterodactyl\Exceptions\Service\Egg\BadJsonFormatException
|
||||
* @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException
|
||||
* @throws \Pterodactyl\Exceptions\Service\Egg\BadYamlFormatException
|
||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
||||
* @throws \Pterodactyl\Exceptions\DisplayException
|
||||
*/
|
||||
public function handle(UploadedFile $file, int $nest): Egg
|
||||
public function handle(UploadedFile $file, int $nestId): Egg
|
||||
{
|
||||
$parsed = $this->parser->handle($file);
|
||||
return $this->handleFile($nestId, $file);
|
||||
}
|
||||
|
||||
/**
|
||||
* ?
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||
* @throws \Pterodactyl\Exceptions\Service\Egg\BadJsonFormatException
|
||||
* @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException
|
||||
* @throws \Pterodactyl\Exceptions\Service\Egg\BadYamlFormatException
|
||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
||||
* @throws \Pterodactyl\Exceptions\DisplayException
|
||||
*/
|
||||
public function handleFile(int $nestId, UploadedFile $file): Egg
|
||||
{
|
||||
if ($file->getError() !== UPLOAD_ERR_OK || !$file->isFile()) {
|
||||
throw new InvalidFileUploadException(sprintf('The selected file ["%s"] was not in a valid format to import. (is_file: %s is_valid: %s err_code: %s err: %s)', $file->getFilename(), $file->isFile() ? 'true' : 'false', $file->isValid() ? 'true' : 'false', $file->getError(), $file->getErrorMessage()));
|
||||
}
|
||||
|
||||
return $this->handleContent($nestId, $file->openFile()->fread($file->getSize()), 'application/json');
|
||||
}
|
||||
|
||||
/**
|
||||
* ?
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||
* @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException
|
||||
* @throws \Pterodactyl\Exceptions\Service\Egg\BadYamlFormatException
|
||||
* @throws \Pterodactyl\Exceptions\Service\Egg\BadJsonFormatException
|
||||
* @throws \Pterodactyl\Exceptions\DisplayException
|
||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
||||
*/
|
||||
public function handleContent(int $nestId, string $content, string $contentType): Egg
|
||||
{
|
||||
switch (true) {
|
||||
case str_starts_with($contentType, 'application/json'):
|
||||
$parsed = json_decode($content, true);
|
||||
if (json_last_error() !== 0) {
|
||||
throw new BadJsonFormatException(trans('exceptions.nest.importer.json_error', ['error' => json_last_error_msg()]));
|
||||
}
|
||||
|
||||
return $this->handleArray($nestId, $parsed);
|
||||
case str_starts_with($contentType, 'application/yaml'):
|
||||
try {
|
||||
$parsed = Yaml::parse($content);
|
||||
|
||||
return $this->handleArray($nestId, $parsed);
|
||||
} catch (ParseException $exception) {
|
||||
throw new BadYamlFormatException('There was an error while attempting to parse the YAML: ' . $exception->getMessage() . '.');
|
||||
}
|
||||
default:
|
||||
throw new DisplayException('unknown content type');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ?
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||
* @throws \Pterodactyl\Exceptions\Service\InvalidFileUploadException
|
||||
*/
|
||||
private function handleArray(int $nestId, array $parsed): Egg
|
||||
{
|
||||
$parsed = $this->eggParserService->handle($parsed);
|
||||
|
||||
/** @var \Pterodactyl\Models\Nest $nest */
|
||||
$nest = Nest::query()->with('eggs', 'eggs.variables')->findOrFail($nest);
|
||||
$nest = Nest::query()->with('eggs', 'eggs.variables')->findOrFail($nestId);
|
||||
|
||||
return $this->connection->transaction(function () use ($nest, $parsed) {
|
||||
$egg = (new Egg())->forceFill([
|
||||
|
@ -37,7 +116,7 @@ class EggImporterService
|
|||
'copy_script_from' => null,
|
||||
]);
|
||||
|
||||
$egg = $this->parser->fillFromParsed($egg, $parsed);
|
||||
$egg = $this->eggParserService->fillFromParsed($egg, $parsed);
|
||||
$egg->save();
|
||||
|
||||
foreach ($parsed['variables'] ?? [] as $variable) {
|
||||
|
|
|
@ -8,14 +8,18 @@ use Illuminate\Support\Collection;
|
|||
use Pterodactyl\Models\EggVariable;
|
||||
use Illuminate\Database\ConnectionInterface;
|
||||
use Pterodactyl\Services\Eggs\EggParserService;
|
||||
use Pterodactyl\Exceptions\Service\Egg\BadJsonFormatException;
|
||||
use Pterodactyl\Exceptions\Service\InvalidFileUploadException;
|
||||
|
||||
class EggUpdateImporterService
|
||||
{
|
||||
/**
|
||||
* EggUpdateImporterService constructor.
|
||||
*/
|
||||
public function __construct(protected ConnectionInterface $connection, protected EggParserService $parser)
|
||||
{
|
||||
public function __construct(
|
||||
private ConnectionInterface $connection,
|
||||
private EggParserService $eggParserService
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -25,10 +29,18 @@ class EggUpdateImporterService
|
|||
*/
|
||||
public function handle(Egg $egg, UploadedFile $file): Egg
|
||||
{
|
||||
$parsed = $this->parser->handle($file);
|
||||
if ($file->getError() !== UPLOAD_ERR_OK || !$file->isFile()) {
|
||||
throw new InvalidFileUploadException(sprintf('The selected file ["%s"] was not in a valid format to import. (is_file: %s is_valid: %s err_code: %s err: %s)', $file->getFilename(), $file->isFile() ? 'true' : 'false', $file->isValid() ? 'true' : 'false', $file->getError(), $file->getErrorMessage()));
|
||||
}
|
||||
|
||||
$parsed = json_decode($file->openFile()->fread($file->getSize()), true);
|
||||
if (json_last_error() !== 0) {
|
||||
throw new BadJsonFormatException(trans('exceptions.nest.importer.json_error', ['error' => json_last_error_msg()]));
|
||||
}
|
||||
$parsed = $this->eggParserService->handle($parsed);
|
||||
|
||||
return $this->connection->transaction(function () use ($egg, $parsed) {
|
||||
$egg = $this->parser->fillFromParsed($egg, $parsed);
|
||||
$egg = $this->eggParserService->fillFromParsed($egg, $parsed);
|
||||
$egg->save();
|
||||
|
||||
// Update existing variables or create new ones.
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
namespace Pterodactyl\Services\Eggs\Variables;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use Pterodactyl\Models\Egg;
|
||||
use Pterodactyl\Models\EggVariable;
|
||||
use Pterodactyl\Exceptions\DisplayException;
|
||||
use Pterodactyl\Traits\Services\ValidatesValidationRules;
|
||||
use Illuminate\Contracts\Validation\Factory as ValidationFactory;
|
||||
use Pterodactyl\Contracts\Repository\EggVariableRepositoryInterface;
|
||||
use Pterodactyl\Exceptions\Service\Egg\Variable\ReservedVariableNameException;
|
||||
|
||||
class VariableUpdateService
|
||||
|
@ -17,7 +17,7 @@ class VariableUpdateService
|
|||
/**
|
||||
* VariableUpdateService constructor.
|
||||
*/
|
||||
public function __construct(private EggVariableRepositoryInterface $repository, private ValidationFactory $validator)
|
||||
public function __construct(private ValidationFactory $validator)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -34,24 +34,21 @@ class VariableUpdateService
|
|||
* Update a specific egg variable.
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\DisplayException
|
||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||
* @throws \Pterodactyl\Exceptions\Service\Egg\Variable\ReservedVariableNameException
|
||||
*/
|
||||
public function handle(EggVariable $variable, array $data): mixed
|
||||
public function handle(Egg $egg, array $data): void
|
||||
{
|
||||
if (!is_null(array_get($data, 'env_variable'))) {
|
||||
if (in_array(strtoupper(array_get($data, 'env_variable')), explode(',', EggVariable::RESERVED_ENV_NAMES))) {
|
||||
throw new ReservedVariableNameException(trans('exceptions.service.variables.reserved_name', ['name' => array_get($data, 'env_variable')]));
|
||||
}
|
||||
|
||||
$search = $this->repository->setColumns('id')->findCountWhere([
|
||||
['env_variable', '=', $data['env_variable']],
|
||||
['egg_id', '=', $variable->egg_id],
|
||||
['id', '!=', $variable->id],
|
||||
]);
|
||||
$count = $egg->variables()
|
||||
->where('egg_variables.env_variable', $data['env_variable'])
|
||||
->where('egg_variables.id', '!=', $data['id'])
|
||||
->count();
|
||||
|
||||
if ($search > 0) {
|
||||
if ($count > 0) {
|
||||
throw new DisplayException(trans('exceptions.service.variables.env_not_unique', ['name' => array_get($data, 'env_variable')]));
|
||||
}
|
||||
}
|
||||
|
@ -66,13 +63,13 @@ class VariableUpdateService
|
|||
|
||||
$options = array_get($data, 'options') ?? [];
|
||||
|
||||
return $this->repository->withoutFreshModel()->update($variable->id, [
|
||||
$egg->variables()->where('egg_variables.id', $data['id'])->update([
|
||||
'name' => $data['name'] ?? '',
|
||||
'description' => $data['description'] ?? '',
|
||||
'env_variable' => $data['env_variable'] ?? '',
|
||||
'default_value' => $data['default_value'] ?? '',
|
||||
'user_viewable' => in_array('user_viewable', $options),
|
||||
'user_editable' => in_array('user_editable', $options),
|
||||
'user_viewable' => $data['user_viewable'],
|
||||
'user_editable' => $data['user_editable'],
|
||||
'rules' => $data['rules'] ?? '',
|
||||
]);
|
||||
}
|
||||
|
|
|
@ -1,117 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Services\Helpers;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Filesystem\FilesystemManager;
|
||||
use Illuminate\Contracts\Filesystem\Filesystem;
|
||||
use Pterodactyl\Exceptions\ManifestDoesNotExistException;
|
||||
|
||||
class AssetHashService
|
||||
{
|
||||
/**
|
||||
* Location of the manifest file generated by gulp.
|
||||
*/
|
||||
public const MANIFEST_PATH = './assets/manifest.json';
|
||||
|
||||
private Filesystem $filesystem;
|
||||
|
||||
protected static mixed $manifest = null;
|
||||
|
||||
/**
|
||||
* AssetHashService constructor.
|
||||
*/
|
||||
public function __construct(FilesystemManager $filesystem)
|
||||
{
|
||||
$this->filesystem = $filesystem->createLocalDriver(['root' => public_path()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify a URL to append the asset hash.
|
||||
*/
|
||||
public function url(string $resource): string
|
||||
{
|
||||
$file = last(explode('/', $resource));
|
||||
$data = Arr::get($this->manifest(), $file) ?? $file;
|
||||
|
||||
return str_replace($file, Arr::get($data, 'src') ?? $file, $resource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the data integrity hash for a resource.
|
||||
*/
|
||||
public function integrity(string $resource): string
|
||||
{
|
||||
$file = last(explode('/', $resource));
|
||||
$data = array_get($this->manifest(), $file, $file);
|
||||
|
||||
return Arr::get($data, 'integrity') ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a built CSS import using the provided URL.
|
||||
*/
|
||||
public function css(string $resource): string
|
||||
{
|
||||
$attributes = [
|
||||
'href' => $this->url($resource),
|
||||
'rel' => 'stylesheet preload',
|
||||
'as' => 'style',
|
||||
'crossorigin' => 'anonymous',
|
||||
'referrerpolicy' => 'no-referrer',
|
||||
];
|
||||
|
||||
if (config('pterodactyl.assets.use_hash')) {
|
||||
$attributes['integrity'] = $this->integrity($resource);
|
||||
}
|
||||
|
||||
$output = '<link';
|
||||
foreach ($attributes as $key => $value) {
|
||||
$output .= " $key=\"$value\"";
|
||||
}
|
||||
|
||||
return $output . '>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a built JS import using the provided URL.
|
||||
*/
|
||||
public function js(string $resource): string
|
||||
{
|
||||
$attributes = [
|
||||
'src' => $this->url($resource),
|
||||
'crossorigin' => 'anonymous',
|
||||
];
|
||||
|
||||
if (config('pterodactyl.assets.use_hash')) {
|
||||
$attributes['integrity'] = $this->integrity($resource);
|
||||
}
|
||||
|
||||
$output = '<script';
|
||||
foreach ($attributes as $key => $value) {
|
||||
$output .= " $key=\"$value\"";
|
||||
}
|
||||
|
||||
return $output . '></script>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the asset manifest and store it in the cache for quicker lookups.
|
||||
*/
|
||||
protected function manifest(): array
|
||||
{
|
||||
if (static::$manifest === null) {
|
||||
self::$manifest = json_decode(
|
||||
$this->filesystem->get(self::MANIFEST_PATH),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
$manifest = static::$manifest;
|
||||
if ($manifest === null) {
|
||||
throw new ManifestDoesNotExistException();
|
||||
}
|
||||
|
||||
return $manifest;
|
||||
}
|
||||
}
|
|
@ -3,46 +3,54 @@
|
|||
namespace Pterodactyl\Services\Helpers;
|
||||
|
||||
use Exception;
|
||||
use GuzzleHttp\Client;
|
||||
use Carbon\CarbonImmutable;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Contracts\Cache\Repository as CacheRepository;
|
||||
use Pterodactyl\Exceptions\Service\Helper\CdnVersionFetchingException;
|
||||
|
||||
class SoftwareVersionService
|
||||
{
|
||||
public const VERSION_CACHE_KEY = 'pterodactyl:versioning_data';
|
||||
public const GIT_VERSION_CACHE_KEY = 'pterodactyl:git_data';
|
||||
|
||||
private static array $result;
|
||||
|
||||
/**
|
||||
* SoftwareVersionService constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
protected CacheRepository $cache,
|
||||
protected Client $client
|
||||
) {
|
||||
public function __construct(private CacheRepository $cache)
|
||||
{
|
||||
self::$result = $this->cacheVersionData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the latest version of the panel from the CDN servers.
|
||||
* Return the current version of the panel that is being used.
|
||||
*/
|
||||
public function getPanel(): string
|
||||
public function getCurrentVersion(): string
|
||||
{
|
||||
return config('app.version');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the latest version of the panel from the CDN servers.
|
||||
*/
|
||||
public function getLatestPanel(): string
|
||||
{
|
||||
return Arr::get(self::$result, 'panel') ?? 'error';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the latest version of the daemon from the CDN servers.
|
||||
* Returns the latest version of the Wings from the CDN servers.
|
||||
*/
|
||||
public function getDaemon(): string
|
||||
public function getLatestWings(): string
|
||||
{
|
||||
return Arr::get(self::$result, 'wings') ?? 'error';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL to the discord server.
|
||||
* Returns the URL to the discord server.
|
||||
*/
|
||||
public function getDiscord(): string
|
||||
{
|
||||
|
@ -50,7 +58,7 @@ class SoftwareVersionService
|
|||
}
|
||||
|
||||
/**
|
||||
* Get the URL for donations.
|
||||
* Returns the URL for donations.
|
||||
*/
|
||||
public function getDonations(): string
|
||||
{
|
||||
|
@ -62,23 +70,80 @@ class SoftwareVersionService
|
|||
*/
|
||||
public function isLatestPanel(): bool
|
||||
{
|
||||
if (config('app.version') === 'canary') {
|
||||
$version = $this->getCurrentVersion();
|
||||
if ($version === 'canary') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return version_compare(config('app.version'), $this->getPanel()) >= 0;
|
||||
return version_compare($version, $this->getLatestPanel()) >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a passed daemon version string is the latest.
|
||||
*/
|
||||
public function isLatestDaemon(string $version): bool
|
||||
public function isLatestWings(string $version): bool
|
||||
{
|
||||
if ($version === 'develop') {
|
||||
if ($version === 'develop' || Str::startsWith($version, 'dev-')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return version_compare($version, $this->getDaemon()) >= 0;
|
||||
return version_compare($version, $this->getLatestWings()) >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* ?
|
||||
*/
|
||||
public function getVersionData(): array
|
||||
{
|
||||
$versionData = $this->versionData();
|
||||
if ($versionData['is_git']) {
|
||||
$git = $versionData['version'];
|
||||
} else {
|
||||
$git = null;
|
||||
}
|
||||
|
||||
return [
|
||||
'panel' => [
|
||||
'current' => $this->getCurrentVersion(),
|
||||
'latest' => $this->getLatestPanel(),
|
||||
],
|
||||
|
||||
'wings' => [
|
||||
'latest' => $this->getLatestWings(),
|
||||
],
|
||||
|
||||
'git' => $git,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return version information for the footer.
|
||||
*/
|
||||
protected function versionData(): array
|
||||
{
|
||||
return $this->cache->remember(self::GIT_VERSION_CACHE_KEY, CarbonImmutable::now()->addSeconds(15), function () {
|
||||
$configVersion = $this->getCurrentVersion();
|
||||
|
||||
if (file_exists(base_path('.git/HEAD'))) {
|
||||
$head = explode(' ', file_get_contents(base_path('.git/HEAD')));
|
||||
|
||||
if (array_key_exists(1, $head)) {
|
||||
$path = base_path('.git/' . trim($head[1]));
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($path) && file_exists($path)) {
|
||||
return [
|
||||
'version' => substr(file_get_contents($path), 0, 8),
|
||||
'is_git' => true,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'version' => $configVersion,
|
||||
'is_git' => false,
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -88,10 +153,10 @@ class SoftwareVersionService
|
|||
{
|
||||
return $this->cache->remember(self::VERSION_CACHE_KEY, CarbonImmutable::now()->addMinutes(config('pterodactyl.cdn.cache_time', 60)), function () {
|
||||
try {
|
||||
$response = $this->client->request('GET', config('pterodactyl.cdn.url'));
|
||||
$response = Http::get(config('pterodactyl.cdn.url'));
|
||||
|
||||
if ($response->getStatusCode() === 200) {
|
||||
return json_decode($response->getBody(), true);
|
||||
if ($response->status() === 200) {
|
||||
return json_decode($response->body(), true);
|
||||
}
|
||||
|
||||
throw new CdnVersionFetchingException();
|
||||
|
|
|
@ -13,7 +13,7 @@ class NodeCreationService
|
|||
/**
|
||||
* NodeCreationService constructor.
|
||||
*/
|
||||
public function __construct(private Encrypter $encrypter, protected NodeRepositoryInterface $repository)
|
||||
public function __construct(protected NodeRepositoryInterface $repository)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,7 @@ class NodeCreationService
|
|||
public function handle(array $data): Node
|
||||
{
|
||||
$data['uuid'] = Uuid::uuid4()->toString();
|
||||
$data['daemon_token'] = $this->encrypter->encrypt(Str::random(Node::DAEMON_TOKEN_LENGTH));
|
||||
$data['daemon_token'] = app(Encrypter::class)->encrypt(Str::random(Node::DAEMON_TOKEN_LENGTH));
|
||||
$data['daemon_token_id'] = Str::random(Node::DAEMON_TOKEN_ID_LENGTH);
|
||||
|
||||
return $this->repository->create($data, true, true);
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace Pterodactyl\Services\Nodes;
|
||||
|
||||
use DateTimeImmutable;
|
||||
use Carbon\CarbonImmutable;
|
||||
use Illuminate\Support\Str;
|
||||
use Pterodactyl\Models\Node;
|
||||
|
@ -19,7 +18,7 @@ class NodeJWTService
|
|||
|
||||
private ?User $user = null;
|
||||
|
||||
private ?DateTimeImmutable $expiresAt;
|
||||
private ?\DateTimeImmutable $expiresAt;
|
||||
|
||||
private ?string $subject = null;
|
||||
|
||||
|
@ -44,7 +43,7 @@ class NodeJWTService
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function setExpiresAt(DateTimeImmutable $date): self
|
||||
public function setExpiresAt(\DateTimeImmutable $date): self
|
||||
{
|
||||
$this->expiresAt = $date;
|
||||
|
||||
|
|
|
@ -27,13 +27,13 @@ class ProcessScheduleService
|
|||
*/
|
||||
public function handle(Schedule $schedule, bool $now = false): void
|
||||
{
|
||||
/** @var \Pterodactyl\Models\Task $task */
|
||||
$task = $schedule->tasks()->orderBy('sequence_id')->first();
|
||||
|
||||
if (is_null($task)) {
|
||||
throw new DisplayException('Cannot process schedule for task execution: no tasks are registered.');
|
||||
}
|
||||
|
||||
/* @var \Pterodactyl\Models\Task $task */
|
||||
$this->connection->transaction(function () use ($schedule, $task) {
|
||||
$schedule->forceFill([
|
||||
'is_processing' => true,
|
||||
|
@ -56,7 +56,7 @@ class ProcessScheduleService
|
|||
|
||||
return;
|
||||
}
|
||||
} catch (Exception $exception) {
|
||||
} catch (\Exception $exception) {
|
||||
if (!$exception instanceof DaemonConnectionException) {
|
||||
// If we encountered some exception during this process that wasn't just an
|
||||
// issue connecting to Wings run the failed sequence for a job. Otherwise we
|
||||
|
@ -78,7 +78,7 @@ class ProcessScheduleService
|
|||
// @see https://github.com/pterodactyl/panel/issues/2550
|
||||
try {
|
||||
$this->dispatcher->dispatchNow($job);
|
||||
} catch (Exception $exception) {
|
||||
} catch (\Exception $exception) {
|
||||
$job->failed($exception);
|
||||
|
||||
throw $exception;
|
||||
|
|
|
@ -46,12 +46,12 @@ class BuildModificationService
|
|||
|
||||
// If any of these values are passed through in the data array go ahead and set
|
||||
// them correctly on the server model.
|
||||
$merge = Arr::only($data, ['oom_disabled', 'memory', 'swap', 'io', 'cpu', 'threads', 'disk', 'allocation_id']);
|
||||
$merge = Arr::only($data, ['oom_killer', 'memory', 'swap', 'io', 'cpu', 'threads', 'disk', 'allocation_id']);
|
||||
|
||||
$server->forceFill(array_merge($merge, [
|
||||
'database_limit' => Arr::get($data, 'database_limit', 0) ?? null,
|
||||
'allocation_limit' => Arr::get($data, 'allocation_limit', 0) ?? null,
|
||||
'backup_limit' => Arr::get($data, 'backup_limit', 0) ?? 0,
|
||||
'database_limit' => Arr::get($data, 'database_limit', 0) ?? null,
|
||||
]))->saveOrFail();
|
||||
|
||||
return $server->refresh();
|
||||
|
@ -88,14 +88,13 @@ class BuildModificationService
|
|||
// Handle the addition of allocations to this server. Only assign allocations that are not currently
|
||||
// assigned to a different server, and only allocations on the same node as the server.
|
||||
if (!empty($data['add_allocations'])) {
|
||||
$query = Allocation::query()
|
||||
->where('node_id', $server->node_id)
|
||||
$query = $server->node->allocations()
|
||||
->whereIn('id', $data['add_allocations'])
|
||||
->whereNull('server_id');
|
||||
|
||||
// Keep track of all the allocations we're just now adding so that we can use the first
|
||||
// one to reset the default allocation to.
|
||||
$freshlyAllocated = $query->pluck('id')->first();
|
||||
$freshlyAllocated = $query->first()->id ?? null;
|
||||
|
||||
$query->update(['server_id' => $server->id, 'notes' => null]);
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ class ServerConfigurationStructureService
|
|||
],
|
||||
'suspended' => $server->isSuspended(),
|
||||
'environment' => $this->environment->handle($server),
|
||||
'invocation' => $server->startup,
|
||||
'invocation' => !is_null($server->startup) ? $server->startup : $server->egg->startup,
|
||||
'skip_egg_scripts' => $server->skip_scripts,
|
||||
'build' => [
|
||||
'memory_limit' => $server->memory,
|
||||
|
@ -59,22 +59,19 @@ class ServerConfigurationStructureService
|
|||
'cpu_limit' => $server->cpu,
|
||||
'threads' => $server->threads,
|
||||
'disk_space' => $server->disk,
|
||||
'oom_disabled' => $server->oom_disabled,
|
||||
// TODO: remove oom_disabled and use oom_killer, this requires a Wings update.
|
||||
'oom_disabled' => !$server->oom_killer,
|
||||
'oom_killer' => $server->oom_killer,
|
||||
],
|
||||
'container' => [
|
||||
'image' => $server->image,
|
||||
// This field is deprecated — use the value in the "build" block.
|
||||
//
|
||||
// TODO: remove this key in V2.
|
||||
'oom_disabled' => $server->oom_disabled,
|
||||
'requires_rebuild' => false,
|
||||
],
|
||||
'allocations' => [
|
||||
'force_outgoing_ip' => $server->egg->force_outgoing_ip,
|
||||
'default' => [
|
||||
'ip' => $server->allocation->ip,
|
||||
'port' => $server->allocation->port,
|
||||
],
|
||||
'force_outgoing_ip' => $server->egg->force_outgoing_ip,
|
||||
'mappings' => $server->getAllocationMappings(),
|
||||
],
|
||||
'mounts' => $server->mounts->map(function (Mount $mount) {
|
||||
|
@ -110,7 +107,7 @@ class ServerConfigurationStructureService
|
|||
return $item->pluck('port');
|
||||
})->toArray(),
|
||||
'env' => $this->environment->handle($server),
|
||||
'oom_disabled' => $server->oom_disabled,
|
||||
'oom_disabled' => !$server->oom_killer,
|
||||
'memory' => (int) $server->memory,
|
||||
'swap' => (int) $server->swap,
|
||||
'io' => (int) $server->io,
|
||||
|
|
|
@ -82,7 +82,7 @@ class ServerCreationService
|
|||
//
|
||||
// If that connection fails out we will attempt to perform a cleanup by just
|
||||
// deleting the server itself from the system.
|
||||
/** @var \Pterodactyl\Models\Server $server */
|
||||
/** @var Server $server */
|
||||
$server = $this->connection->transaction(function () use ($data, $eggVariableData) {
|
||||
// Create the server and assign any additional allocations to it.
|
||||
$server = $this->createModel($data);
|
||||
|
@ -115,7 +115,7 @@ class ServerCreationService
|
|||
*/
|
||||
private function configureDeployment(array $data, DeploymentObject $deployment): Allocation
|
||||
{
|
||||
/** @var \Illuminate\Support\Collection $nodes */
|
||||
/** @var Collection $nodes */
|
||||
$nodes = $this->findViableNodesService->setLocations($deployment->getLocations())
|
||||
->setDisk(Arr::get($data, 'disk'))
|
||||
->setMemory(Arr::get($data, 'memory'))
|
||||
|
@ -136,7 +136,7 @@ class ServerCreationService
|
|||
{
|
||||
$uuid = $this->generateUniqueUuidCombo();
|
||||
|
||||
/** @var \Pterodactyl\Models\Server $model */
|
||||
/** @var Server $model */
|
||||
$model = $this->repository->create([
|
||||
'external_id' => Arr::get($data, 'external_id'),
|
||||
'uuid' => $uuid,
|
||||
|
@ -153,7 +153,7 @@ class ServerCreationService
|
|||
'io' => Arr::get($data, 'io'),
|
||||
'cpu' => Arr::get($data, 'cpu'),
|
||||
'threads' => Arr::get($data, 'threads'),
|
||||
'oom_disabled' => Arr::get($data, 'oom_disabled') ?? true,
|
||||
'oom_killer' => Arr::get($data, 'oom_killer') ?? false,
|
||||
'allocation_id' => Arr::get($data, 'allocation_id'),
|
||||
'nest_id' => Arr::get($data, 'nest_id'),
|
||||
'egg_id' => Arr::get($data, 'egg_id'),
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace Pterodactyl\Services\Servers;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Http\Response;
|
||||
use Pterodactyl\Models\Server;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
@ -61,7 +60,7 @@ class ServerDeletionService
|
|||
foreach ($server->databases as $database) {
|
||||
try {
|
||||
$this->databaseManagementService->delete($database);
|
||||
} catch (Exception $exception) {
|
||||
} catch (\Exception $exception) {
|
||||
if (!$this->force) {
|
||||
throw $exception;
|
||||
}
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Services\Servers;
|
||||
|
||||
use Pterodactyl\Models\Server;
|
||||
use Pterodactyl\Repositories\Wings\DaemonServerRepository;
|
||||
|
||||
class TransferService
|
||||
{
|
||||
/**
|
||||
* TransferService constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
private DaemonServerRepository $daemonServerRepository
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests an archive from the daemon.
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
|
||||
*/
|
||||
public function requestArchive(Server $server): void
|
||||
{
|
||||
$this->daemonServerRepository->setServer($server)->requestArchive();
|
||||
}
|
||||
}
|
|
@ -58,8 +58,6 @@ class SubuserCreationService
|
|||
$user = $this->userCreationService->handle([
|
||||
'email' => $email,
|
||||
'username' => $username,
|
||||
'name_first' => 'Server',
|
||||
'name_last' => 'Subuser',
|
||||
'root_admin' => false,
|
||||
]);
|
||||
}
|
||||
|
|
189
app/Services/Telemetry/TelemetryCollectionService.php
Normal file
189
app/Services/Telemetry/TelemetryCollectionService.php
Normal file
|
@ -0,0 +1,189 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Services\Telemetry;
|
||||
|
||||
use Exception;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use Illuminate\Support\Arr;
|
||||
use Pterodactyl\Models\Egg;
|
||||
use Pterodactyl\Models\Nest;
|
||||
use Pterodactyl\Models\Node;
|
||||
use Pterodactyl\Models\User;
|
||||
use Pterodactyl\Models\Mount;
|
||||
use Pterodactyl\Models\Backup;
|
||||
use Pterodactyl\Models\Server;
|
||||
use Pterodactyl\Models\Location;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Pterodactyl\Models\Allocation;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Pterodactyl\Services\Helpers\SoftwareVersionService;
|
||||
use Pterodactyl\Repositories\Eloquent\SettingsRepository;
|
||||
use Pterodactyl\Repositories\Wings\DaemonConfigurationRepository;
|
||||
|
||||
class TelemetryCollectionService
|
||||
{
|
||||
/**
|
||||
* TelemetryCollectionService constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
private DaemonConfigurationRepository $daemonConfigurationRepository,
|
||||
private SettingsRepository $settingsRepository,
|
||||
private SoftwareVersionService $softwareVersionService
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects telemetry data and sends it to the Pterodactyl Telemetry Service.
|
||||
*/
|
||||
public function __invoke(): void
|
||||
{
|
||||
try {
|
||||
$data = $this->collect();
|
||||
} catch (Exception) {
|
||||
return;
|
||||
}
|
||||
|
||||
Http::post('https://telemetry.pterodactyl.io', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects telemetry data and returns it as an array.
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
||||
*/
|
||||
public function collect(): array
|
||||
{
|
||||
$uuid = $this->settingsRepository->get('app:telemetry:uuid');
|
||||
if (is_null($uuid)) {
|
||||
$uuid = Uuid::uuid4()->toString();
|
||||
$this->settingsRepository->set('app:telemetry:uuid', $uuid);
|
||||
}
|
||||
|
||||
$nodes = Node::all()->map(function ($node) {
|
||||
try {
|
||||
$info = $this->daemonConfigurationRepository->setNode($node)->getSystemInformation(2);
|
||||
} catch (Exception) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $node->uuid,
|
||||
'version' => Arr::get($info, 'version', ''),
|
||||
|
||||
'docker' => [
|
||||
'version' => Arr::get($info, 'docker.version', ''),
|
||||
|
||||
'cgroups' => [
|
||||
'driver' => Arr::get($info, 'docker.cgroups.driver', ''),
|
||||
'version' => Arr::get($info, 'docker.cgroups.version', ''),
|
||||
],
|
||||
|
||||
'containers' => [
|
||||
'total' => Arr::get($info, 'docker.containers.total', -1),
|
||||
'running' => Arr::get($info, 'docker.containers.running', -1),
|
||||
'paused' => Arr::get($info, 'docker.containers.paused', -1),
|
||||
'stopped' => Arr::get($info, 'docker.containers.stopped', -1),
|
||||
],
|
||||
|
||||
'storage' => [
|
||||
'driver' => Arr::get($info, 'docker.storage.driver', ''),
|
||||
'filesystem' => Arr::get($info, 'docker.storage.filesystem', ''),
|
||||
],
|
||||
|
||||
'runc' => [
|
||||
'version' => Arr::get($info, 'docker.runc.version', ''),
|
||||
],
|
||||
],
|
||||
|
||||
'system' => [
|
||||
'architecture' => Arr::get($info, 'system.architecture', ''),
|
||||
'cpuThreads' => Arr::get($info, 'system.cpu_threads', ''),
|
||||
'memoryBytes' => Arr::get($info, 'system.memory_bytes', ''),
|
||||
'kernelVersion' => Arr::get($info, 'system.kernel_version', ''),
|
||||
'os' => Arr::get($info, 'system.os', ''),
|
||||
'osType' => Arr::get($info, 'system.os_type', ''),
|
||||
],
|
||||
];
|
||||
})->filter(fn ($node) => !is_null($node))->toArray();
|
||||
|
||||
return [
|
||||
'id' => $uuid,
|
||||
|
||||
'panel' => [
|
||||
'version' => $this->softwareVersionService->getCurrentVersion(),
|
||||
'phpVersion' => phpversion(),
|
||||
|
||||
'drivers' => [
|
||||
'backup' => [
|
||||
'type' => config('backups.default'),
|
||||
],
|
||||
|
||||
'cache' => [
|
||||
'type' => config('cache.default'),
|
||||
],
|
||||
|
||||
'database' => [
|
||||
'type' => config('database.default'),
|
||||
'version' => DB::getPdo()->getAttribute(\PDO::ATTR_SERVER_VERSION),
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
'resources' => [
|
||||
'allocations' => [
|
||||
'count' => Allocation::count(),
|
||||
'used' => Allocation::whereNotNull('server_id')->count(),
|
||||
],
|
||||
|
||||
'backups' => [
|
||||
'count' => Backup::count(),
|
||||
'bytes' => Backup::sum('bytes'),
|
||||
],
|
||||
|
||||
'eggs' => [
|
||||
'count' => Egg::count(),
|
||||
// Egg UUIDs are generated randomly on import, so there is not a consistent way to
|
||||
// determine if servers are using default eggs or not.
|
||||
// 'server_usage' => Egg::all()
|
||||
// ->flatMap(fn (Egg $egg) => [$egg->uuid => $egg->servers->count()])
|
||||
// ->filter(fn (int $count) => $count > 0)
|
||||
// ->toArray(),
|
||||
],
|
||||
|
||||
'locations' => [
|
||||
'count' => Location::count(),
|
||||
],
|
||||
|
||||
'mounts' => [
|
||||
'count' => Mount::count(),
|
||||
],
|
||||
|
||||
'nests' => [
|
||||
'count' => Nest::count(),
|
||||
// Nest UUIDs are generated randomly on import, so there is not a consistent way to
|
||||
// determine if servers are using default eggs or not.
|
||||
// 'server_usage' => Nest::all()
|
||||
// ->flatMap(fn (Nest $nest) => [$nest->uuid => $nest->eggs->sum(fn (Egg $egg) => $egg->servers->count())])
|
||||
// ->filter(fn (int $count) => $count > 0)
|
||||
// ->toArray(),
|
||||
],
|
||||
|
||||
'nodes' => [
|
||||
'count' => Node::count(),
|
||||
],
|
||||
|
||||
'servers' => [
|
||||
'count' => Server::count(),
|
||||
'suspended' => Server::where('status', Server::STATUS_SUSPENDED)->count(),
|
||||
],
|
||||
|
||||
'users' => [
|
||||
'count' => User::count(),
|
||||
'admins' => User::where('root_admin', true)->count(),
|
||||
],
|
||||
],
|
||||
|
||||
'nodes' => $nodes,
|
||||
];
|
||||
}
|
||||
}
|
|
@ -2,8 +2,6 @@
|
|||
|
||||
namespace Pterodactyl\Services\Users;
|
||||
|
||||
use Exception;
|
||||
use RuntimeException;
|
||||
use Pterodactyl\Models\User;
|
||||
use Illuminate\Contracts\Encryption\Encrypter;
|
||||
use Pterodactyl\Contracts\Repository\UserRepositoryInterface;
|
||||
|
@ -38,8 +36,8 @@ class TwoFactorSetupService
|
|||
for ($i = 0; $i < $this->config->get('pterodactyl.auth.2fa.bytes', 16); ++$i) {
|
||||
$secret .= substr(self::VALID_BASE32_CHARACTERS, random_int(0, 31), 1);
|
||||
}
|
||||
} catch (Exception $exception) {
|
||||
throw new RuntimeException($exception->getMessage(), 0, $exception);
|
||||
} catch (\Exception $exception) {
|
||||
throw new \RuntimeException($exception->getMessage(), 0, $exception);
|
||||
}
|
||||
|
||||
$this->repository->withoutFreshModel()->update($user->id, [
|
||||
|
|
|
@ -25,7 +25,7 @@ class UserDeletionService
|
|||
*
|
||||
* @throws \Pterodactyl\Exceptions\DisplayException
|
||||
*/
|
||||
public function handle(int|User $user): ?bool
|
||||
public function handle(int|User $user): void
|
||||
{
|
||||
if ($user instanceof User) {
|
||||
$user = $user->id;
|
||||
|
@ -36,6 +36,6 @@ class UserDeletionService
|
|||
throw new DisplayException($this->translator->get('admin/user.exceptions.user_has_servers'));
|
||||
}
|
||||
|
||||
return $this->repository->delete($user);
|
||||
$this->repository->delete($user);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue