<?php12namespace Pterodactyl\Models;34use Illuminate\Notifications\Notifiable;5use Illuminate\Database\Query\JoinClause;6use Znck\Eloquent\Traits\BelongsToThrough;7use Illuminate\Database\Eloquent\Relations\HasOne;8use Illuminate\Database\Eloquent\Relations\HasMany;9use Illuminate\Database\Eloquent\Relations\BelongsTo;10use Illuminate\Database\Eloquent\Factories\HasFactory;11use Illuminate\Database\Eloquent\Relations\MorphToMany;12use Illuminate\Database\Eloquent\Relations\HasManyThrough;13use Pterodactyl\Exceptions\Http\Server\ServerStateConflictException;1415/**16* \Pterodactyl\Models\Server.17*18* @property int $id19* @property string|null $external_id20* @property string $uuid21* @property string $uuidShort22* @property int $node_id23* @property string $name24* @property string $description25* @property string|null $status26* @property bool $skip_scripts27* @property int $owner_id28* @property int $memory29* @property int $swap30* @property int $disk31* @property int $io32* @property int $cpu33* @property string|null $threads34* @property bool $oom_disabled35* @property int $allocation_id36* @property int $nest_id37* @property int $egg_id38* @property string $startup39* @property string $image40* @property int|null $allocation_limit41* @property int|null $database_limit42* @property int $backup_limit43* @property \Illuminate\Support\Carbon|null $created_at44* @property \Illuminate\Support\Carbon|null $updated_at45* @property \Illuminate\Support\Carbon|null $installed_at46* @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\ActivityLog[] $activity47* @property int|null $activity_count48* @property Allocation|null $allocation49* @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Allocation[] $allocations50* @property int|null $allocations_count51* @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Backup[] $backups52* @property int|null $backups_count53* @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Database[] $databases54* @property int|null $databases_count55* @property Egg|null $egg56* @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Mount[] $mounts57* @property int|null $mounts_count58* @property Nest $nest59* @property Node $node60* @property \Illuminate\Notifications\DatabaseNotificationCollection|\Illuminate\Notifications\DatabaseNotification[] $notifications61* @property int|null $notifications_count62* @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Schedule[] $schedules63* @property int|null $schedules_count64* @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Subuser[] $subusers65* @property int|null $subusers_count66* @property ServerTransfer|null $transfer67* @property User $user68* @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\EggVariable[] $variables69* @property int|null $variables_count70*71* @method static \Database\Factories\ServerFactory factory(...$parameters)72* @method static \Illuminate\Database\Eloquent\Builder|Server newModelQuery()73* @method static \Illuminate\Database\Eloquent\Builder|Server newQuery()74* @method static \Illuminate\Database\Eloquent\Builder|Server query()75* @method static \Illuminate\Database\Eloquent\Builder|Server whereAllocationId($value)76* @method static \Illuminate\Database\Eloquent\Builder|Server whereAllocationLimit($value)77* @method static \Illuminate\Database\Eloquent\Builder|Server whereBackupLimit($value)78* @method static \Illuminate\Database\Eloquent\Builder|Server whereCpu($value)79* @method static \Illuminate\Database\Eloquent\Builder|Server whereCreatedAt($value)80* @method static \Illuminate\Database\Eloquent\Builder|Server whereDatabaseLimit($value)81* @method static \Illuminate\Database\Eloquent\Builder|Server whereDescription($value)82* @method static \Illuminate\Database\Eloquent\Builder|Server whereDisk($value)83* @method static \Illuminate\Database\Eloquent\Builder|Server whereEggId($value)84* @method static \Illuminate\Database\Eloquent\Builder|Server whereExternalId($value)85* @method static \Illuminate\Database\Eloquent\Builder|Server whereId($value)86* @method static \Illuminate\Database\Eloquent\Builder|Server whereImage($value)87* @method static \Illuminate\Database\Eloquent\Builder|Server whereIo($value)88* @method static \Illuminate\Database\Eloquent\Builder|Server whereMemory($value)89* @method static \Illuminate\Database\Eloquent\Builder|Server whereName($value)90* @method static \Illuminate\Database\Eloquent\Builder|Server whereNestId($value)91* @method static \Illuminate\Database\Eloquent\Builder|Server whereNodeId($value)92* @method static \Illuminate\Database\Eloquent\Builder|Server whereOomDisabled($value)93* @method static \Illuminate\Database\Eloquent\Builder|Server whereOwnerId($value)94* @method static \Illuminate\Database\Eloquent\Builder|Server whereSkipScripts($value)95* @method static \Illuminate\Database\Eloquent\Builder|Server whereStartup($value)96* @method static \Illuminate\Database\Eloquent\Builder|Server whereStatus($value)97* @method static \Illuminate\Database\Eloquent\Builder|Server whereSwap($value)98* @method static \Illuminate\Database\Eloquent\Builder|Server whereThreads($value)99* @method static \Illuminate\Database\Eloquent\Builder|Server whereUpdatedAt($value)100* @method static \Illuminate\Database\Eloquent\Builder|Server whereUuid($value)101* @method static \Illuminate\Database\Eloquent\Builder|Server whereUuidShort($value)102*103* @mixin \Eloquent104*/105class Server extends Model106{107/** @use HasFactory<\Database\Factories\ServerFactory> */108use HasFactory;109use BelongsToThrough;110use Notifiable;111112/**113* The resource name for this model when it is transformed into an114* API representation using fractal.115*/116public const RESOURCE_NAME = 'server';117118public const STATUS_INSTALLING = 'installing';119public const STATUS_INSTALL_FAILED = 'install_failed';120public const STATUS_REINSTALL_FAILED = 'reinstall_failed';121public const STATUS_SUSPENDED = 'suspended';122public const STATUS_RESTORING_BACKUP = 'restoring_backup';123124/**125* The table associated with the model.126*/127protected $table = 'servers';128129/**130* Default values when creating the model. We want to switch to disabling OOM killer131* on server instances unless the user specifies otherwise in the request.132*/133protected $attributes = [134'status' => self::STATUS_INSTALLING,135'oom_disabled' => true,136'installed_at' => null,137];138139/**140* The default relationships to load for all server models.141*/142protected $with = ['allocation'];143144/**145* Fields that are not mass assignable.146*/147protected $guarded = ['id', self::CREATED_AT, self::UPDATED_AT, 'deleted_at', 'installed_at'];148149public static array $validationRules = [150'external_id' => 'sometimes|nullable|string|between:1,191|unique:servers',151'owner_id' => 'required|integer|exists:users,id',152'name' => 'required|string|min:1|max:191',153'node_id' => 'required|exists:nodes,id',154'description' => 'string',155'status' => 'nullable|string',156'memory' => 'required|numeric|min:0',157'swap' => 'required|numeric|min:-1',158'io' => 'required|numeric|between:10,1000',159'cpu' => 'required|numeric|min:0',160'threads' => 'nullable|regex:/^[0-9-,]+$/',161'oom_disabled' => 'sometimes|boolean',162'disk' => 'required|numeric|min:0',163'allocation_id' => 'required|bail|unique:servers|exists:allocations,id',164'nest_id' => 'required|exists:nests,id',165'egg_id' => 'required|exists:eggs,id',166'startup' => 'required|string',167'skip_scripts' => 'sometimes|boolean',168'image' => ['required', 'string', 'max:191', 'regex:/^~?[\w\.\/\-:@ ]*$/'],169'database_limit' => 'present|nullable|integer|min:0',170'allocation_limit' => 'sometimes|nullable|integer|min:0',171'backup_limit' => 'present|nullable|integer|min:0',172];173174/**175* Cast values to correct type.176*/177protected $casts = [178'node_id' => 'integer',179'skip_scripts' => 'boolean',180'owner_id' => 'integer',181'memory' => 'integer',182'swap' => 'integer',183'disk' => 'integer',184'io' => 'integer',185'cpu' => 'integer',186'oom_disabled' => 'boolean',187'allocation_id' => 'integer',188'nest_id' => 'integer',189'egg_id' => 'integer',190'database_limit' => 'integer',191'allocation_limit' => 'integer',192'backup_limit' => 'integer',193self::CREATED_AT => 'datetime',194self::UPDATED_AT => 'datetime',195'deleted_at' => 'datetime',196'installed_at' => 'datetime',197];198199/**200* Returns the format for server allocations when communicating with the Daemon.201*/202public function getAllocationMappings(): array203{204return $this->allocations->where('node_id', $this->node_id)->groupBy('ip')->map(function ($item) {205return $item->pluck('port');206})->toArray();207}208209public function isInstalled(): bool210{211return $this->status !== self::STATUS_INSTALLING && $this->status !== self::STATUS_INSTALL_FAILED;212}213214public function isSuspended(): bool215{216return $this->status === self::STATUS_SUSPENDED;217}218219/**220* Gets the user who owns the server.221*222* @return \Illuminate\Database\Eloquent\Relations\BelongsTo<\Pterodactyl\Models\User, $this>223*/224public function user(): BelongsTo225{226return $this->belongsTo(User::class, 'owner_id');227}228229/**230* Gets the subusers associated with a server.231*232* @return \Illuminate\Database\Eloquent\Relations\HasMany<\Pterodactyl\Models\Subuser, $this>233*/234public function subusers(): HasMany235{236return $this->hasMany(Subuser::class, 'server_id', 'id');237}238239/**240* Gets the default allocation for a server.241*242* @return \Illuminate\Database\Eloquent\Relations\HasOne<\Pterodactyl\Models\Allocation, $this>243*/244public function allocation(): HasOne245{246return $this->hasOne(Allocation::class, 'id', 'allocation_id');247}248249/**250* Gets all allocations associated with this server.251*252* @return \Illuminate\Database\Eloquent\Relations\HasMany<\Pterodactyl\Models\Allocation, $this>253*/254public function allocations(): HasMany255{256return $this->hasMany(Allocation::class, 'server_id');257}258259/**260* Gets information for the nest associated with this server.261*262* @return \Illuminate\Database\Eloquent\Relations\BelongsTo<\Pterodactyl\Models\Nest, $this>263*/264public function nest(): BelongsTo265{266return $this->belongsTo(Nest::class);267}268269/**270* Gets information for the egg associated with this server.271*272* @return \Illuminate\Database\Eloquent\Relations\HasOne<\Pterodactyl\Models\Egg, $this>273*/274public function egg(): HasOne275{276return $this->hasOne(Egg::class, 'id', 'egg_id');277}278279/**280* Gets information for the service variables associated with this server.281*282* @return \Illuminate\Database\Eloquent\Relations\HasMany<\Pterodactyl\Models\EggVariable, $this>283*/284public function variables(): HasMany285{286return $this->hasMany(EggVariable::class, 'egg_id', 'egg_id')287->select(['egg_variables.*', 'server_variables.variable_value as server_value'])288->leftJoin('server_variables', function (JoinClause $join) {289// Don't forget to join against the server ID as well since the way we're using this relationship290// would actually return all the variables and their values for _all_ servers using that egg,291// rather than only the server for this model.292//293// @see https://github.com/pterodactyl/panel/issues/2250294$join->on('server_variables.variable_id', 'egg_variables.id')295->where('server_variables.server_id', $this->id);296});297}298299/**300* Gets information for the node associated with this server.301*302* @return \Illuminate\Database\Eloquent\Relations\BelongsTo<\Pterodactyl\Models\Node, $this>303*/304public function node(): BelongsTo305{306return $this->belongsTo(Node::class);307}308309/**310* Gets information for the tasks associated with this server.311*312* @return \Illuminate\Database\Eloquent\Relations\HasMany<\Pterodactyl\Models\Schedule, $this>313*/314public function schedules(): HasMany315{316return $this->hasMany(Schedule::class);317}318319/**320* Gets all databases associated with a server.321*322* @return \Illuminate\Database\Eloquent\Relations\HasMany<\Pterodactyl\Models\Database, $this>323*/324public function databases(): HasMany325{326return $this->hasMany(Database::class);327}328329/**330* Returns the location that a server belongs to.331*332* @return \Znck\Eloquent\Relations\BelongsToThrough<\Pterodactyl\Models\Location, \Pterodactyl\Models\Node>333*334* @throws \Exception335*/336public function location(): \Znck\Eloquent\Relations\BelongsToThrough337{338return $this->belongsToThrough(Location::class, Node::class); // @phpstan-ignore return.type339}340341/**342* Returns the associated server transfer.343*344* @return \Illuminate\Database\Eloquent\Relations\HasOne<\Pterodactyl\Models\ServerTransfer, $this>345*/346public function transfer(): HasOne347{348return $this->hasOne(ServerTransfer::class)->whereNull('successful')->orderByDesc('id');349}350351/**352* @return \Illuminate\Database\Eloquent\Relations\HasMany<\Pterodactyl\Models\Backup, $this>353*/354public function backups(): HasMany355{356return $this->hasMany(Backup::class);357}358359/**360* Returns all mounts that have this server has mounted.361*362* @return \Illuminate\Database\Eloquent\Relations\HasManyThrough<\Pterodactyl\Models\Mount, \Pterodactyl\Models\MountServer, $this>363*/364public function mounts(): HasManyThrough365{366return $this->hasManyThrough(Mount::class, MountServer::class, 'server_id', 'id', 'id', 'mount_id');367}368369/**370* Returns all of the activity log entries where the server is the subject.371*372* @return \Illuminate\Database\Eloquent\Relations\MorphToMany<\Pterodactyl\Models\ActivityLog, $this>373*/374public function activity(): MorphToMany375{376return $this->morphToMany(ActivityLog::class, 'subject', 'activity_log_subjects');377}378379/**380* Checks if the server is currently in a user-accessible state. If not, an381* exception is raised. This should be called whenever something needs to make382* sure the server is not in a weird state that should block user access.383*384* @throws ServerStateConflictException385*/386public function validateCurrentState()387{388if (389$this->isSuspended()390|| $this->node->isUnderMaintenance()391|| !$this->isInstalled()392|| $this->status === self::STATUS_RESTORING_BACKUP393|| !is_null($this->transfer)394) {395throw new ServerStateConflictException($this);396}397}398399/**400* Checks if the server is currently in a transferable state. If not, an401* exception is raised. This should be called whenever something needs to make402* sure the server is able to be transferred and is not currently being transferred403* or installed.404*/405public function validateTransferState()406{407if (408!$this->isInstalled()409|| $this->status === self::STATUS_RESTORING_BACKUP410|| !is_null($this->transfer)411) {412throw new ServerStateConflictException($this);413}414}415}416417418