api(application): v2 backport
This commit is contained in:
parent
4cd0bee231
commit
67bf3e342e
172 changed files with 2922 additions and 1579 deletions
|
@ -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.');
|
||||
}
|
||||
|
|
|
@ -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([
|
||||
|
|
|
@ -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\InvalidFileUploadException;
|
||||
use Pterodactyl\Exceptions\Service\Egg\BadJsonFormatException;
|
||||
|
||||
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,6 +3,7 @@
|
|||
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;
|
||||
|
@ -34,24 +35,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 +64,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'] ?? '',
|
||||
]);
|
||||
}
|
||||
|
|
|
@ -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 = config()->get('app.version');
|
||||
|
||||
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();
|
||||
|
|
|
@ -49,9 +49,9 @@ class BuildModificationService
|
|||
$merge = Arr::only($data, ['oom_disabled', '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();
|
||||
|
|
|
@ -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,
|
||||
|
@ -63,18 +63,13 @@ class ServerConfigurationStructureService
|
|||
],
|
||||
'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) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue