$type_data * @property string $provider * @property int $provider_id * @property array $provider_data * @property array $authentication * @property string $public_key * @property string $status * @property bool $auto_update * @property int $available_updates * @property int $security_updates * @property int|float $progress * @property ?string $progress_step * @property Project $project * @property User $creator * @property ServerProvider $serverProvider * @property Collection $logs * @property Collection $sites * @property Collection $services * @property Collection $databases * @property Collection $databaseUsers * @property Collection $firewallRules * @property Collection $cronJobs * @property Collection $queues * @property Collection $backups * @property Collection $sshKeys * @property Collection $tags * @property string $hostname * @property int $updates * @property ?Carbon $last_update_check */ class Server extends AbstractModel { /** @use HasFactory<\Database\Factories\ServerFactory> */ use HasFactory; protected $fillable = [ 'project_id', 'user_id', 'name', 'ssh_user', 'ip', 'local_ip', 'port', 'os', 'type', 'type_data', 'provider', 'provider_id', 'provider_data', 'authentication', 'public_key', 'status', 'auto_update', 'available_updates', 'security_updates', 'progress', 'progress_step', 'updates', 'last_update_check', ]; protected $casts = [ 'project_id' => 'integer', 'user_id' => 'integer', 'type_data' => 'json', 'port' => 'integer', 'provider_data' => 'json', 'authentication' => 'encrypted:json', 'auto_update' => 'boolean', 'available_updates' => 'integer', 'security_updates' => 'integer', 'progress' => 'float', 'updates' => 'integer', 'last_update_check' => 'datetime', ]; protected $hidden = [ 'authentication', ]; public static function boot(): void { parent::boot(); static::deleting(function (Server $server): void { DB::beginTransaction(); try { $server->sites()->each(function ($site): void { /** @var Site $site */ $site->workers()->delete(); $site->ssls()->delete(); $site->deployments()->delete(); $site->deploymentScript()->delete(); }); $server->sites()->delete(); $server->logs()->each(function ($log): void { /** @var ServerLog $log */ $log->delete(); }); $server->services()->delete(); $server->databases()->delete(); $server->databaseUsers()->delete(); $server->firewallRules()->delete(); $server->cronJobs()->delete(); $server->workers()->delete(); $server->daemons()->delete(); $server->sshKeys()->detach(); if (File::exists($server->sshKey()['public_key_path'])) { File::delete($server->sshKey()['public_key_path']); } if (File::exists($server->sshKey()['private_key_path'])) { File::delete($server->sshKey()['private_key_path']); } $server->provider()->delete(); DB::commit(); } catch (Throwable $e) { DB::rollBack(); throw $e; } }); } /** * @var array */ public static array $statusColors = [ ServerStatus::READY => 'success', ServerStatus::INSTALLING => 'warning', ServerStatus::DISCONNECTED => 'gray', ServerStatus::INSTALLATION_FAILED => 'danger', ServerStatus::UPDATING => 'warning', ]; public function isReady(): bool { return $this->status === ServerStatus::READY; } public function isInstalling(): bool { return in_array($this->status, [ServerStatus::INSTALLING, ServerStatus::INSTALLATION_FAILED]); } public function isInstallationFailed(): bool { return $this->status === ServerStatus::INSTALLATION_FAILED; } /** * @return BelongsTo */ public function project(): BelongsTo { return $this->belongsTo(Project::class, 'project_id'); } /** * @return BelongsTo */ public function creator(): BelongsTo { return $this->belongsTo(User::class, 'user_id'); } /** * @return BelongsTo */ public function serverProvider(): BelongsTo { return $this->belongsTo(ServerProvider::class, 'provider_id'); } /** * @return HasMany */ public function logs(): HasMany { return $this->hasMany(ServerLog::class); } /** * @return HasMany */ public function sites(): HasMany { return $this->hasMany(Site::class); } /** * @return HasMany */ public function services(): HasMany { return $this->hasMany(Service::class); } /** * @return HasMany */ public function databases(): HasMany { return $this->hasMany(Database::class); } /** * @return HasMany */ public function databaseUsers(): HasMany { return $this->hasMany(DatabaseUser::class); } /** * @return HasMany */ public function firewallRules(): HasMany { return $this->hasMany(FirewallRule::class); } /** * @return HasMany */ public function cronJobs(): HasMany { return $this->hasMany(CronJob::class); } /** * @return HasMany */ public function workers(): HasMany { return $this->hasMany(Worker::class); } /** * @return HasMany */ public function backups(): HasMany { return $this->hasMany(Backup::class); } /** * @return HasMany */ public function daemons(): HasMany { return $this->workers()->whereNull('site_id'); } /** * @return HasMany */ public function metrics(): HasMany { return $this->hasMany(Metric::class); } /** * @return BelongsToMany */ public function sshKeys(): BelongsToMany { return $this->belongsToMany(SshKey::class, 'server_ssh_keys') ->withPivot('status') ->withTimestamps(); } /** * @return MorphToMany */ public function tags(): MorphToMany { return $this->morphToMany(Tag::class, 'taggable'); } public function getSshUser(): string { if ($this->ssh_user) { return $this->ssh_user; } return config('core.ssh_user'); } /** * @return array */ public function getSshUsers(): array { $users = ['root', $this->getSshUser()]; $isolatedSites = $this->sites()->pluck('user')->toArray(); $users = array_merge($users, $isolatedSites); return array_unique($users); } public function service(string $type, mixed $version = null): ?Service { /** @var ?Service $service */ $service = $this->services() ->where(function ($query) use ($type, $version): void { $query->where('type', $type); if ($version) { $query->where('version', $version); } }) ->first(); return $service; } public function defaultService(string $type): ?Service { /** @var ?Service $service */ $service = $this->services() ->where('type', $type) ->where('is_default', 1) ->first(); // If no default service found, get the first service with status ready or stopped if (! $service) { /** @var ?Service $service */ $service = $this->services() ->where('type', $type) ->whereIn('status', [ServiceStatus::READY, ServiceStatus::STOPPED]) ->first(); if ($service) { $service->is_default = true; $service->save(); } } return $service; } public function ssh(?string $user = null): \App\Helpers\SSH|SSHFake { return SSH::init($this, $user); } /** * @return array */ public function installedPHPVersions(): array { $versions = []; $phps = $this->services()->where('type', 'php')->get(['version']); /** @var Service $php */ foreach ($phps as $php) { $versions[] = $php->version; } return $versions; } /** * @return array */ public function installedNodejsVersions(): array { $versions = []; $nodes = $this->services()->where('type', 'nodejs')->get(['version']); /** @var Service $node */ foreach ($nodes as $node) { $versions[] = $node->version; } return $versions; } public function type(): ServerType { $typeClass = config('core.server_types_class')[$this->type]; /** @var ServerType $type */ $type = new $typeClass($this); return $type; } public function provider(): \App\ServerProviders\ServerProvider { $providerClass = config('core.server_providers_class')[$this->provider]; /** @var \App\ServerProviders\ServerProvider $provider */ $provider = new $providerClass($this->serverProvider ?? new ServerProvider, $this); return $provider; } public function webserver(?string $version = null): ?Service { if ($version === null || $version === '' || $version === '0') { return $this->defaultService('webserver'); } return $this->service('webserver', $version); } public function database(?string $version = null): ?Service { if ($version === null || $version === '' || $version === '0') { return $this->defaultService('database'); } return $this->service('database', $version); } public function firewall(?string $version = null): ?Service { if ($version === null || $version === '' || $version === '0') { return $this->defaultService('firewall'); } return $this->service('firewall', $version); } public function processManager(?string $version = null): ?Service { if ($version === null || $version === '' || $version === '0') { return $this->defaultService('process_manager'); } return $this->service('process_manager', $version); } public function php(?string $version = null): ?Service { if ($version === null || $version === '' || $version === '0') { return $this->defaultService('php'); } return $this->service('php', $version); } public function nodejs(?string $version = null): ?Service { if ($version === null || $version === '' || $version === '0') { return $this->defaultService('nodejs'); } return $this->service('nodejs', $version); } public function memoryDatabase(?string $version = null): ?Service { if ($version === null || $version === '' || $version === '0') { return $this->defaultService('memory_database'); } return $this->service('memory_database', $version); } public function monitoring(?string $version = null): ?Service { if ($version === null || $version === '' || $version === '0') { return $this->defaultService('monitoring'); } return $this->service('monitoring', $version); } /** * @return array */ public function sshKey(): array { /** @var FilesystemAdapter $storageDisk */ $storageDisk = Storage::disk(config('core.key_pairs_disk')); return [ 'public_key' => str(Storage::disk(config('core.key_pairs_disk'))->get($this->id.'.pub'))->replace("\n", '')->toString(), 'public_key_path' => $storageDisk->path($this->id.'.pub'), 'private_key_path' => $storageDisk->path((string) $this->id), ]; } public function checkConnection(): self { return app(CheckConnection::class)->check($this); } public function hostname(): string { return Str::of($this->name)->slug(); } public function os(): OS { return new OS($this); } public function systemd(): Systemd { return new Systemd($this); } public function cron(): Cron { return new Cron($this); } /** * @throws SSHError */ public function checkForUpdates(): void { $this->updates = $this->os()->availableUpdates(); $this->last_update_check = now(); $this->save(); } public function getAvailableUpdatesAttribute(?int $value): int { if ($value === null || $value === 0) { return 0; } return $value; } /** * @throws Throwable */ public function download(string $path, string $disk = 'tmp'): void { $this->ssh()->download( Storage::disk($disk)->path(basename($path)), $path ); } }