<?php namespace App\Models; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\SoftDeletes; class StandaloneMysql extends BaseModel { use HasFactory, SoftDeletes; protected $guarded = []; protected $appends = ['internal_db_url', 'external_db_url', 'database_type']; protected $casts = [ 'mysql_password' => 'encrypted', 'mysql_root_password' => 'encrypted', ]; protected static function booted() { static::created(function ($database) { LocalPersistentVolume::create([ 'name' => 'mysql-data-'.$database->uuid, 'mount_path' => '/var/lib/mysql', 'host_path' => null, 'resource_id' => $database->id, 'resource_type' => $database->getMorphClass(), 'is_readonly' => true, ]); }); static::forceDeleting(function ($database) { $database->persistentStorages()->delete(); $database->scheduledBackups()->delete(); $database->environment_variables()->delete(); $database->tags()->detach(); }); } public function isConfigurationChanged(bool $save = false) { $newConfigHash = $this->image.$this->ports_mappings.$this->mysql_conf; $newConfigHash .= json_encode($this->environment_variables()->get('value')->sort()); $newConfigHash = md5($newConfigHash); $oldConfigHash = data_get($this, 'config_hash'); if ($oldConfigHash === null) { if ($save) { $this->config_hash = $newConfigHash; $this->save(); } return true; } if ($oldConfigHash === $newConfigHash) { return false; } else { if ($save) { $this->config_hash = $newConfigHash; $this->save(); } return true; } } public function isExited() { return (bool) str($this->status)->startsWith('exited'); } public function workdir() { return database_configuration_dir()."/{$this->uuid}"; } public function delete_configurations() { $server = data_get($this, 'destination.server'); $workdir = $this->workdir(); if (str($workdir)->endsWith($this->uuid)) { instant_remote_process(['rm -rf '.$this->workdir()], $server, false); } } public function delete_volumes(Collection $persistentStorages) { if ($persistentStorages->count() === 0) { return; } $server = data_get($this, 'destination.server'); foreach ($persistentStorages as $storage) { instant_remote_process(["docker volume rm -f $storage->name"], $server, false); } } public function realStatus() { return $this->getRawOriginal('status'); } public function status(): Attribute { return Attribute::make( set: function ($value) { if (str($value)->contains('(')) { $status = str($value)->before('(')->trim()->value(); $health = str($value)->after('(')->before(')')->trim()->value() ?? 'unhealthy'; } elseif (str($value)->contains(':')) { $status = str($value)->before(':')->trim()->value(); $health = str($value)->after(':')->trim()->value() ?? 'unhealthy'; } else { $status = $value; $health = 'unhealthy'; } return "$status:$health"; }, get: function ($value) { if (str($value)->contains('(')) { $status = str($value)->before('(')->trim()->value(); $health = str($value)->after('(')->before(')')->trim()->value() ?? 'unhealthy'; } elseif (str($value)->contains(':')) { $status = str($value)->before(':')->trim()->value(); $health = str($value)->after(':')->trim()->value() ?? 'unhealthy'; } else { $status = $value; $health = 'unhealthy'; } return "$status:$health"; }, ); } public function tags() { return $this->morphToMany(Tag::class, 'taggable'); } public function project() { return data_get($this, 'environment.project'); } public function team() { return data_get($this, 'environment.project.team'); } public function link() { if (data_get($this, 'environment.project.uuid')) { return route('project.database.configuration', [ 'project_uuid' => data_get($this, 'environment.project.uuid'), 'environment_name' => data_get($this, 'environment.name'), 'database_uuid' => data_get($this, 'uuid'), ]); } return null; } public function databaseType(): Attribute { return new Attribute( get: fn () => $this->type(), ); } public function type(): string { return 'standalone-mysql'; } public function isLogDrainEnabled() { return data_get($this, 'is_log_drain_enabled', false); } public function portsMappings(): Attribute { return Attribute::make( set: fn ($value) => $value === '' ? null : $value, ); } public function portsMappingsArray(): Attribute { return Attribute::make( get: fn () => is_null($this->ports_mappings) ? [] : explode(',', $this->ports_mappings), ); } protected function internalDbUrl(): Attribute { return new Attribute( get: fn () => "mysql://{$this->mysql_user}:{$this->mysql_password}@{$this->uuid}:3306/{$this->mysql_database}", ); } protected function externalDbUrl(): Attribute { return new Attribute( get: function () { if ($this->is_public && $this->public_port) { return "mysql://{$this->mysql_user}:{$this->mysql_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->mysql_database}"; } return null; } ); } public function environment() { return $this->belongsTo(Environment::class); } public function fileStorages() { return $this->morphMany(LocalFileVolume::class, 'resource'); } public function destination() { return $this->morphTo(); } public function environment_variables(): HasMany { return $this->hasMany(EnvironmentVariable::class); } public function runtime_environment_variables(): HasMany { return $this->hasMany(EnvironmentVariable::class); } public function persistentStorages() { return $this->morphMany(LocalPersistentVolume::class, 'resource'); } public function scheduledBackups() { return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); } public function getMetrics(int $mins = 5) { $server = $this->destination->server; $container_name = $this->uuid; if ($server->isMetricsEnabled()) { $from = now()->subMinutes($mins)->toIso8601ZuluString(); $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false); if (str($metrics)->contains('error')) { $error = json_decode($metrics, true); $error = data_get($error, 'error', 'Something is not okay, are you okay?'); if ($error == 'Unauthorized') { $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.'; } throw new \Exception($error); } $metrics = str($metrics)->explode("\n")->skip(1)->all(); $parsedCollection = collect($metrics)->flatMap(function ($item) { return collect(explode("\n", trim($item)))->map(function ($line) { [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line)); $cpu_usage_percent = number_format($cpu_usage_percent, 2); return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage]; }); }); return $parsedCollection->toArray(); } } }