mirror of
https://github.com/coollabsio/coolify.git
synced 2026-06-14 03:19:51 +00:00
51062e73a6
Skip Docker healthcheck configuration when standalone database health checks are disabled, and document default health check settings in the database API schema.
382 lines
11 KiB
PHP
382 lines
11 KiB
PHP
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use App\Traits\ClearsGlobalSearchCache;
|
|
use App\Traits\HasDatabaseHealthCheck;
|
|
use App\Traits\HasMetrics;
|
|
use App\Traits\HasSafeStringAttribute;
|
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
|
|
|
class StandaloneMysql extends BaseModel
|
|
{
|
|
use ClearsGlobalSearchCache, HasDatabaseHealthCheck, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes;
|
|
|
|
protected $fillable = [
|
|
'uuid',
|
|
'name',
|
|
'description',
|
|
'mysql_root_password',
|
|
'mysql_user',
|
|
'mysql_password',
|
|
'mysql_database',
|
|
'mysql_conf',
|
|
'status',
|
|
'image',
|
|
'is_public',
|
|
'public_port',
|
|
'ports_mappings',
|
|
'limits_memory',
|
|
'limits_memory_swap',
|
|
'limits_memory_swappiness',
|
|
'limits_memory_reservation',
|
|
'limits_cpus',
|
|
'limits_cpuset',
|
|
'limits_cpu_shares',
|
|
'started_at',
|
|
'restart_count',
|
|
'last_restart_at',
|
|
'last_restart_type',
|
|
'last_online_at',
|
|
'public_port_timeout',
|
|
'enable_ssl',
|
|
'ssl_mode',
|
|
'is_log_drain_enabled',
|
|
'is_include_timestamps',
|
|
'custom_docker_run_options',
|
|
'destination_type',
|
|
'destination_id',
|
|
'environment_id',
|
|
'health_check_enabled',
|
|
'health_check_interval',
|
|
'health_check_timeout',
|
|
'health_check_retries',
|
|
'health_check_start_period',
|
|
];
|
|
|
|
protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
|
|
|
|
protected $casts = [
|
|
'health_check_enabled' => 'boolean',
|
|
'health_check_interval' => 'integer',
|
|
'health_check_timeout' => 'integer',
|
|
'health_check_retries' => 'integer',
|
|
'health_check_start_period' => 'integer',
|
|
'mysql_password' => 'encrypted',
|
|
'mysql_root_password' => 'encrypted',
|
|
'public_port_timeout' => 'integer',
|
|
'restart_count' => 'integer',
|
|
'last_restart_at' => 'datetime',
|
|
'last_restart_type' => 'string',
|
|
];
|
|
|
|
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(),
|
|
]);
|
|
});
|
|
static::forceDeleting(function ($database) {
|
|
$database->persistentStorages()->delete();
|
|
$database->scheduledBackups()->delete();
|
|
$database->environment_variables()->delete();
|
|
$database->tags()->detach();
|
|
});
|
|
static::saving(function ($database) {
|
|
if ($database->isDirty('status')) {
|
|
$database->last_online_at = now();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get query builder for MySQL databases owned by current team.
|
|
* If you need all databases without further query chaining, use ownedByCurrentTeamCached() instead.
|
|
*/
|
|
public static function ownedByCurrentTeam()
|
|
{
|
|
return StandaloneMysql::whereRelation('environment.project.team', 'id', currentTeam()->id)->orderBy('name');
|
|
}
|
|
|
|
/**
|
|
* Get all MySQL databases owned by current team (cached for request duration).
|
|
*/
|
|
public static function ownedByCurrentTeamCached()
|
|
{
|
|
return once(function () {
|
|
return StandaloneMysql::ownedByCurrentTeam()->get();
|
|
});
|
|
}
|
|
|
|
protected function serverStatus(): Attribute
|
|
{
|
|
return Attribute::make(
|
|
get: function () {
|
|
return $this->destination->server->isFunctional();
|
|
}
|
|
);
|
|
}
|
|
|
|
public function isConfigurationChanged(bool $save = false)
|
|
{
|
|
$newConfigHash = $this->image.$this->ports_mappings.$this->mysql_conf;
|
|
$newConfigHash .= $this->healthCheckConfigurationHash();
|
|
$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 isRunning()
|
|
{
|
|
return (bool) str($this->status)->contains('running');
|
|
}
|
|
|
|
public function isExited()
|
|
{
|
|
return (bool) str($this->status)->startsWith('exited');
|
|
}
|
|
|
|
public function workdir()
|
|
{
|
|
return database_configuration_dir()."/{$this->uuid}";
|
|
}
|
|
|
|
public function deleteConfigurations()
|
|
{
|
|
$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 deleteVolumes()
|
|
{
|
|
$persistentStorages = $this->persistentStorages()->get() ?? collect();
|
|
if ($persistentStorages->count() === 0) {
|
|
return;
|
|
}
|
|
$server = data_get($this, 'destination.server');
|
|
foreach ($persistentStorages as $storage) {
|
|
instant_remote_process(['docker volume rm -f '.escapeshellarg($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 sslCertificates()
|
|
{
|
|
return $this->morphMany(SslCertificate::class, 'resource');
|
|
}
|
|
|
|
public function link()
|
|
{
|
|
if (data_get($this, 'environment.project.uuid')) {
|
|
return route('project.database.configuration', [
|
|
'project_uuid' => data_get($this, 'environment.project.uuid'),
|
|
'environment_uuid' => data_get($this, 'environment.uuid'),
|
|
'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: function () {
|
|
$encodedUser = rawurlencode($this->mysql_user);
|
|
$encodedPass = rawurlencode($this->mysql_password);
|
|
$url = "mysql://{$encodedUser}:{$encodedPass}@{$this->uuid}:3306/{$this->mysql_database}";
|
|
if ($this->enable_ssl) {
|
|
$url .= "?ssl-mode={$this->ssl_mode}";
|
|
if (in_array($this->ssl_mode, ['VERIFY_CA', 'VERIFY_IDENTITY'])) {
|
|
$url .= '&ssl-ca=/etc/ssl/certs/coolify-ca.crt';
|
|
}
|
|
}
|
|
|
|
return $url;
|
|
},
|
|
);
|
|
}
|
|
|
|
protected function externalDbUrl(): Attribute
|
|
{
|
|
return new Attribute(
|
|
get: function () {
|
|
if ($this->is_public && $this->public_port) {
|
|
$serverIp = $this->destination->server->getIp;
|
|
if (empty($serverIp)) {
|
|
return null;
|
|
}
|
|
$encodedUser = rawurlencode($this->mysql_user);
|
|
$encodedPass = rawurlencode($this->mysql_password);
|
|
$url = "mysql://{$encodedUser}:{$encodedPass}@{$serverIp}:{$this->public_port}/{$this->mysql_database}";
|
|
if ($this->enable_ssl) {
|
|
$url .= "?ssl-mode={$this->ssl_mode}";
|
|
if (in_array($this->ssl_mode, ['VERIFY_CA', 'VERIFY_IDENTITY'])) {
|
|
$url .= '&ssl-ca=/etc/ssl/certs/coolify-ca.crt';
|
|
}
|
|
}
|
|
|
|
return $url;
|
|
}
|
|
|
|
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 runtime_environment_variables()
|
|
{
|
|
return $this->morphMany(EnvironmentVariable::class, 'resourceable');
|
|
}
|
|
|
|
public function persistentStorages()
|
|
{
|
|
return $this->morphMany(LocalPersistentVolume::class, 'resource');
|
|
}
|
|
|
|
public function scheduledBackups()
|
|
{
|
|
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
|
|
}
|
|
|
|
public function isBackupSolutionAvailable()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
public function environment_variables()
|
|
{
|
|
return $this->morphMany(EnvironmentVariable::class, 'resourceable');
|
|
}
|
|
}
|