<?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*/222public function user(): BelongsTo223{224return $this->belongsTo(User::class, 'owner_id');225}226227/**228* Gets the subusers associated with a server.229*/230public function subusers(): HasMany231{232return $this->hasMany(Subuser::class, 'server_id', 'id');233}234235/**236* Gets the default allocation for a server.237*/238public function allocation(): HasOne239{240return $this->hasOne(Allocation::class, 'id', 'allocation_id');241}242243/**244* Gets all allocations associated with this server.245*/246public function allocations(): HasMany247{248return $this->hasMany(Allocation::class, 'server_id');249}250251/**252* Gets information for the nest associated with this server.253*/254public function nest(): BelongsTo255{256return $this->belongsTo(Nest::class);257}258259/**260* Gets information for the egg associated with this server.261*/262public function egg(): HasOne263{264return $this->hasOne(Egg::class, 'id', 'egg_id');265}266267/**268* Gets information for the service variables associated with this server.269*/270public function variables(): HasMany271{272return $this->hasMany(EggVariable::class, 'egg_id', 'egg_id')273->select(['egg_variables.*', 'server_variables.variable_value as server_value'])274->leftJoin('server_variables', function (JoinClause $join) {275// Don't forget to join against the server ID as well since the way we're using this relationship276// would actually return all the variables and their values for _all_ servers using that egg,277// rather than only the server for this model.278//279// @see https://github.com/pterodactyl/panel/issues/2250280$join->on('server_variables.variable_id', 'egg_variables.id')281->where('server_variables.server_id', $this->id);282});283}284285/**286* Gets information for the node associated with this server.287*/288public function node(): BelongsTo289{290return $this->belongsTo(Node::class);291}292293/**294* Gets information for the tasks associated with this server.295*/296public function schedules(): HasMany297{298return $this->hasMany(Schedule::class);299}300301/**302* Gets all databases associated with a server.303*/304public function databases(): HasMany305{306return $this->hasMany(Database::class);307}308309/**310* Returns the location that a server belongs to.311*312* @throws \Exception313*/314public function location(): \Znck\Eloquent\Relations\BelongsToThrough315{316return $this->belongsToThrough(Location::class, Node::class);317}318319/**320* Returns the associated server transfer.321*/322public function transfer(): HasOne323{324return $this->hasOne(ServerTransfer::class)->whereNull('successful')->orderByDesc('id');325}326327public function backups(): HasMany328{329return $this->hasMany(Backup::class);330}331332/**333* Returns all mounts that have this server has mounted.334*/335public function mounts(): HasManyThrough336{337return $this->hasManyThrough(Mount::class, MountServer::class, 'server_id', 'id', 'id', 'mount_id');338}339340/**341* Returns all of the activity log entries where the server is the subject.342*/343public function activity(): MorphToMany344{345return $this->morphToMany(ActivityLog::class, 'subject', 'activity_log_subjects');346}347348/**349* Checks if the server is currently in a user-accessible state. If not, an350* exception is raised. This should be called whenever something needs to make351* sure the server is not in a weird state that should block user access.352*353* @throws ServerStateConflictException354*/355public function validateCurrentState()356{357if (358$this->isSuspended()359|| $this->node->isUnderMaintenance()360|| !$this->isInstalled()361|| $this->status === self::STATUS_RESTORING_BACKUP362|| !is_null($this->transfer)363) {364throw new ServerStateConflictException($this);365}366}367368/**369* Checks if the server is currently in a transferable state. If not, an370* exception is raised. This should be called whenever something needs to make371* sure the server is able to be transferred and is not currently being transferred372* or installed.373*/374public function validateTransferState()375{376if (377!$this->isInstalled()378|| $this->status === self::STATUS_RESTORING_BACKUP379|| !is_null($this->transfer)380) {381throw new ServerStateConflictException($this);382}383}384}385386387