From f801bb98cd11c1d43881ab3d23af5fd1226cca13 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Tue, 24 Oct 2023 14:31:28 +0200 Subject: [PATCH] feat: mysql, mariadb --- app/Actions/Database/StartDatabaseProxy.php | 8 +- app/Actions/Database/StartMariadb.php | 158 ++++++++++++++++++ app/Actions/Database/StartMysql.php | 158 ++++++++++++++++++ app/Actions/Database/StopDatabase.php | 4 +- app/Actions/Database/StopDatabaseProxy.php | 4 +- app/Http/Controllers/ProjectController.php | 8 +- app/Http/Livewire/Project/CloneProject.php | 4 + .../Project/Database/BackupExecution.php | 23 --- .../Project/Database/BackupExecutions.php | 15 +- .../Database/CreateScheduledBackup.php | 4 + .../Livewire/Project/Database/Heading.php | 14 +- .../Project/Database/Mariadb/General.php | 95 +++++++++++ .../Project/Database/Mongodb/General.php | 11 +- .../Project/Database/Mysql/General.php | 95 +++++++++++ .../Project/Database/Postgresql/General.php | 9 +- .../Project/Database/Redis/General.php | 11 +- .../Shared/EnvironmentVariable/All.php | 6 + app/Http/Livewire/Project/Shared/Logs.php | 13 +- app/Jobs/DatabaseBackupJob.php | 72 +++++++- app/Jobs/StopResourceJob.php | 10 +- app/Models/Environment.php | 12 +- app/Models/Project.php | 12 ++ app/Models/Server.php | 13 +- app/Models/StandaloneDocker.php | 18 ++ app/Models/StandaloneMariadb.php | 106 ++++++++++++ app/Models/StandaloneMysql.php | 106 ++++++++++++ app/Models/StandalonePostgresql.php | 2 - bootstrap/helpers/constants.php | 2 +- bootstrap/helpers/databases.php | 32 ++++ ..._103548_create_standalone_mysqls_table.php | 57 +++++++ ...20523_create_standalone_mariadbs_table.php | 57 +++++++ ...e_mysql_to_environment_variables_table.php | 30 ++++ .../components/databases/navbar.blade.php | 6 +- .../project/database/backup-edit.blade.php | 8 + .../database/backup-execution.blade.php | 8 - .../database/backup-executions.blade.php | 26 ++- .../database/mariadb/general.blade.php | 58 +++++++ .../project/database/mysql/general.blade.php | 58 +++++++ .../livewire/project/new/select.blade.php | 20 +++ .../database/backups/executions.blade.php | 2 +- .../project/database/configuration.blade.php | 42 +++-- 41 files changed, 1309 insertions(+), 88 deletions(-) create mode 100644 app/Actions/Database/StartMariadb.php create mode 100644 app/Actions/Database/StartMysql.php delete mode 100644 app/Http/Livewire/Project/Database/BackupExecution.php create mode 100644 app/Http/Livewire/Project/Database/Mariadb/General.php create mode 100644 app/Http/Livewire/Project/Database/Mysql/General.php create mode 100644 app/Models/StandaloneMariadb.php create mode 100644 app/Models/StandaloneMysql.php create mode 100644 database/migrations/2023_10_24_103548_create_standalone_mysqls_table.php create mode 100644 database/migrations/2023_10_24_120523_create_standalone_mariadbs_table.php create mode 100644 database/migrations/2023_10_24_120524_add_standalone_mysql_to_environment_variables_table.php delete mode 100644 resources/views/livewire/project/database/backup-execution.blade.php create mode 100644 resources/views/livewire/project/database/mariadb/general.blade.php create mode 100644 resources/views/livewire/project/database/mysql/general.blade.php diff --git a/app/Actions/Database/StartDatabaseProxy.php b/app/Actions/Database/StartDatabaseProxy.php index 15009019d..ffc91b86a 100644 --- a/app/Actions/Database/StartDatabaseProxy.php +++ b/app/Actions/Database/StartDatabaseProxy.php @@ -2,7 +2,9 @@ namespace App\Actions\Database; +use App\Models\StandaloneMariadb; use App\Models\StandaloneMongodb; +use App\Models\StandaloneMysql; use App\Models\StandalonePostgresql; use App\Models\StandaloneRedis; use Lorisleiva\Actions\Concerns\AsAction; @@ -12,7 +14,7 @@ class StartDatabaseProxy { use AsAction; - public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb $database) + public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb $database) { $internalPort = null; if ($database->getMorphClass() === 'App\Models\StandaloneRedis') { @@ -21,6 +23,10 @@ class StartDatabaseProxy $internalPort = 5432; } else if ($database->getMorphClass() === 'App\Models\StandaloneMongodb') { $internalPort = 27017; + } else if ($database->getMorphClass() === 'App\Models\StandaloneMysql') { + $internalPort = 3306; + } else if ($database->getMorphClass() === 'App\Models\StandaloneMariadb') { + $internalPort = 3306; } $containerName = "{$database->uuid}-proxy"; $configuration_dir = database_proxy_dir($database->uuid); diff --git a/app/Actions/Database/StartMariadb.php b/app/Actions/Database/StartMariadb.php new file mode 100644 index 000000000..75fd69adc --- /dev/null +++ b/app/Actions/Database/StartMariadb.php @@ -0,0 +1,158 @@ +database = $database; + + $container_name = $this->database->uuid; + $this->configuration_dir = database_configuration_dir() . '/' . $container_name; + + $this->commands = [ + "echo '####### Starting {$database->name}.'", + "mkdir -p $this->configuration_dir", + ]; + + $persistent_storages = $this->generate_local_persistent_volumes(); + $volume_names = $this->generate_local_persistent_volumes_only_volume_names(); + $environment_variables = $this->generate_environment_variables(); + $this->add_custom_mysql(); + $docker_compose = [ + 'version' => '3.8', + 'services' => [ + $container_name => [ + 'image' => $this->database->image, + 'container_name' => $container_name, + 'environment' => $environment_variables, + 'restart' => RESTART_MODE, + 'networks' => [ + $this->database->destination->network, + ], + 'labels' => [ + 'coolify.managed' => 'true', + ], + 'healthcheck' => [ + 'test' => ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"], + 'interval' => '5s', + 'timeout' => '5s', + 'retries' => 10, + 'start_period' => '5s' + ], + 'mem_limit' => $this->database->limits_memory, + 'memswap_limit' => $this->database->limits_memory_swap, + 'mem_swappiness' => $this->database->limits_memory_swappiness, + 'mem_reservation' => $this->database->limits_memory_reservation, + 'cpus' => $this->database->limits_cpus, + 'cpuset' => $this->database->limits_cpuset, + 'cpu_shares' => $this->database->limits_cpu_shares, + ] + ], + 'networks' => [ + $this->database->destination->network => [ + 'external' => true, + 'name' => $this->database->destination->network, + 'attachable' => true, + ] + ] + ]; + if (count($this->database->ports_mappings_array) > 0) { + $docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array; + } + if (count($persistent_storages) > 0) { + $docker_compose['services'][$container_name]['volumes'] = $persistent_storages; + } + if (count($volume_names) > 0) { + $docker_compose['volumes'] = $volume_names; + } + if (!is_null($this->database->mariadb_conf)) { + $docker_compose['services'][$container_name]['volumes'][] = [ + 'type' => 'bind', + 'source' => $this->configuration_dir . '/custom-config.cnf', + 'target' => '/etc/mysql/conf.d/custom-config.cnf', + 'read_only' => true, + ]; + } + $docker_compose = Yaml::dump($docker_compose, 10); + $docker_compose_base64 = base64_encode($docker_compose); + $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml"; + $readme = generate_readme_file($this->database->name, now()); + $this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md"; + $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; + $this->commands[] = "echo '####### {$database->name} started.'"; + return remote_process($this->commands, $database->destination->server); + } + + private function generate_local_persistent_volumes() + { + $local_persistent_volumes = []; + foreach ($this->database->persistentStorages as $persistentStorage) { + $volume_name = $persistentStorage->host_path ?? $persistentStorage->name; + $local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path; + } + return $local_persistent_volumes; + } + + private function generate_local_persistent_volumes_only_volume_names() + { + $local_persistent_volumes_names = []; + foreach ($this->database->persistentStorages as $persistentStorage) { + if ($persistentStorage->host_path) { + continue; + } + $name = $persistentStorage->name; + $local_persistent_volumes_names[$name] = [ + 'name' => $name, + 'external' => false, + ]; + } + return $local_persistent_volumes_names; + } + + private function generate_environment_variables() + { + $environment_variables = collect(); + foreach ($this->database->runtime_environment_variables as $env) { + $environment_variables->push("$env->key=$env->value"); + } + + if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MARIADB_ROOT_PASSWORD'))->isEmpty()) { + $environment_variables->push("MARIADB_ROOT_PASSWORD={$this->database->mariadb_root_password}"); + } + + if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MARIADB_DATABASE'))->isEmpty()) { + $environment_variables->push("MARIADB_DATABASE={$this->database->mariadb_database}"); + } + + if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MARIADB_USER'))->isEmpty()) { + $environment_variables->push("MARIADB_USER={$this->database->mariadb_user}"); + } + if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MARIADB_PASSWORD'))->isEmpty()) { + $environment_variables->push("MARIADB_PASSWORD={$this->database->mariadb_password}"); + } + return $environment_variables->all(); + } + private function add_custom_mysql() + { + if (is_null($this->database->mariadb_conf)) { + return; + } + $filename = 'custom-config.cnf'; + $content = $this->database->mariadb_conf; + $content_base64 = base64_encode($content); + $this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}"; + } +} diff --git a/app/Actions/Database/StartMysql.php b/app/Actions/Database/StartMysql.php new file mode 100644 index 000000000..8ee0db6e9 --- /dev/null +++ b/app/Actions/Database/StartMysql.php @@ -0,0 +1,158 @@ +database = $database; + + $container_name = $this->database->uuid; + $this->configuration_dir = database_configuration_dir() . '/' . $container_name; + + $this->commands = [ + "echo '####### Starting {$database->name}.'", + "mkdir -p $this->configuration_dir", + ]; + + $persistent_storages = $this->generate_local_persistent_volumes(); + $volume_names = $this->generate_local_persistent_volumes_only_volume_names(); + $environment_variables = $this->generate_environment_variables(); + $this->add_custom_mysql(); + $docker_compose = [ + 'version' => '3.8', + 'services' => [ + $container_name => [ + 'image' => $this->database->image, + 'container_name' => $container_name, + 'environment' => $environment_variables, + 'restart' => RESTART_MODE, + 'networks' => [ + $this->database->destination->network, + ], + 'labels' => [ + 'coolify.managed' => 'true', + ], + 'healthcheck' => [ + 'test' => ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p{$this->database->mysql_root_password}"], + 'interval' => '5s', + 'timeout' => '5s', + 'retries' => 10, + 'start_period' => '5s' + ], + 'mem_limit' => $this->database->limits_memory, + 'memswap_limit' => $this->database->limits_memory_swap, + 'mem_swappiness' => $this->database->limits_memory_swappiness, + 'mem_reservation' => $this->database->limits_memory_reservation, + 'cpus' => $this->database->limits_cpus, + 'cpuset' => $this->database->limits_cpuset, + 'cpu_shares' => $this->database->limits_cpu_shares, + ] + ], + 'networks' => [ + $this->database->destination->network => [ + 'external' => true, + 'name' => $this->database->destination->network, + 'attachable' => true, + ] + ] + ]; + if (count($this->database->ports_mappings_array) > 0) { + $docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array; + } + if (count($persistent_storages) > 0) { + $docker_compose['services'][$container_name]['volumes'] = $persistent_storages; + } + if (count($volume_names) > 0) { + $docker_compose['volumes'] = $volume_names; + } + if (!is_null($this->database->mysql_conf)) { + $docker_compose['services'][$container_name]['volumes'][] = [ + 'type' => 'bind', + 'source' => $this->configuration_dir . '/custom-config.cnf', + 'target' => '/etc/mysql/conf.d/custom-config.cnf', + 'read_only' => true, + ]; + } + $docker_compose = Yaml::dump($docker_compose, 10); + $docker_compose_base64 = base64_encode($docker_compose); + $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml"; + $readme = generate_readme_file($this->database->name, now()); + $this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md"; + $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; + $this->commands[] = "echo '####### {$database->name} started.'"; + return remote_process($this->commands, $database->destination->server); + } + + private function generate_local_persistent_volumes() + { + $local_persistent_volumes = []; + foreach ($this->database->persistentStorages as $persistentStorage) { + $volume_name = $persistentStorage->host_path ?? $persistentStorage->name; + $local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path; + } + return $local_persistent_volumes; + } + + private function generate_local_persistent_volumes_only_volume_names() + { + $local_persistent_volumes_names = []; + foreach ($this->database->persistentStorages as $persistentStorage) { + if ($persistentStorage->host_path) { + continue; + } + $name = $persistentStorage->name; + $local_persistent_volumes_names[$name] = [ + 'name' => $name, + 'external' => false, + ]; + } + return $local_persistent_volumes_names; + } + + private function generate_environment_variables() + { + $environment_variables = collect(); + foreach ($this->database->runtime_environment_variables as $env) { + $environment_variables->push("$env->key=$env->value"); + } + + if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MYSQL_ROOT_PASSWORD'))->isEmpty()) { + $environment_variables->push("MYSQL_ROOT_PASSWORD={$this->database->mysql_root_password}"); + } + + if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MYSQL_DATABASE'))->isEmpty()) { + $environment_variables->push("MYSQL_DATABASE={$this->database->mysql_database}"); + } + + if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MYSQL_USER'))->isEmpty()) { + $environment_variables->push("MYSQL_USER={$this->database->mysql_user}"); + } + if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MYSQL_PASSWORD'))->isEmpty()) { + $environment_variables->push("MYSQL_PASSWORD={$this->database->mysql_password}"); + } + return $environment_variables->all(); + } + private function add_custom_mysql() + { + if (is_null($this->database->mysql_conf)) { + return; + } + $filename = 'custom-config.cnf'; + $content = $this->database->mysql_conf; + $content_base64 = base64_encode($content); + $this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}"; + } +} diff --git a/app/Actions/Database/StopDatabase.php b/app/Actions/Database/StopDatabase.php index 7e3f5f4c2..4f6a8c6c2 100644 --- a/app/Actions/Database/StopDatabase.php +++ b/app/Actions/Database/StopDatabase.php @@ -2,7 +2,9 @@ namespace App\Actions\Database; +use App\Models\StandaloneMariadb; use App\Models\StandaloneMongodb; +use App\Models\StandaloneMysql; use App\Models\StandalonePostgresql; use App\Models\StandaloneRedis; use Lorisleiva\Actions\Concerns\AsAction; @@ -11,7 +13,7 @@ class StopDatabase { use AsAction; - public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb $database) + public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb $database) { $server = $database->destination->server; instant_remote_process( diff --git a/app/Actions/Database/StopDatabaseProxy.php b/app/Actions/Database/StopDatabaseProxy.php index 840e8ed56..d52d1961c 100644 --- a/app/Actions/Database/StopDatabaseProxy.php +++ b/app/Actions/Database/StopDatabaseProxy.php @@ -2,7 +2,9 @@ namespace App\Actions\Database; +use App\Models\StandaloneMariadb; use App\Models\StandaloneMongodb; +use App\Models\StandaloneMysql; use App\Models\StandalonePostgresql; use App\Models\StandaloneRedis; use Lorisleiva\Actions\Concerns\AsAction; @@ -11,7 +13,7 @@ class StopDatabaseProxy { use AsAction; - public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb $database) + public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb $database) { instant_remote_process(["docker rm -f {$database->uuid}-proxy"], $database->destination->server); $database->is_public = false; diff --git a/app/Http/Controllers/ProjectController.php b/app/Http/Controllers/ProjectController.php index 1d1a5b14e..0e3983f7e 100644 --- a/app/Http/Controllers/ProjectController.php +++ b/app/Http/Controllers/ProjectController.php @@ -63,8 +63,12 @@ class ProjectController extends Controller $database = create_standalone_postgresql($environment->id, $destination_uuid); } else if ($type->value() === 'redis') { $database = create_standalone_redis($environment->id, $destination_uuid); - } else if ($type->value() === 'mongodb') { + } else if ($type->value() === 'mongodb') { $database = create_standalone_mongodb($environment->id, $destination_uuid); + } else if ($type->value() === 'mysql') { + $database = create_standalone_mysql($environment->id, $destination_uuid); + }else if ($type->value() === 'mariadb') { + $database = create_standalone_mariadb($environment->id, $destination_uuid); } return redirect()->route('project.database.configuration', [ 'project_uuid' => $project->uuid, @@ -72,7 +76,7 @@ class ProjectController extends Controller 'database_uuid' => $database->uuid, ]); } - if ($type->startsWith('one-click-service-') && !is_null( (int)$server_id)) { + if ($type->startsWith('one-click-service-') && !is_null((int)$server_id)) { $oneClickServiceName = $type->after('one-click-service-')->value(); $oneClickService = data_get($services, "$oneClickServiceName.compose"); $oneClickDotEnvs = data_get($services, "$oneClickServiceName.envs", null); diff --git a/app/Http/Livewire/Project/CloneProject.php b/app/Http/Livewire/Project/CloneProject.php index e12138d10..735bbc0da 100644 --- a/app/Http/Livewire/Project/CloneProject.php +++ b/app/Http/Livewire/Project/CloneProject.php @@ -117,6 +117,10 @@ class CloneProject extends Component $payload['standalone_redis_id'] = $newDatabase->id; } else if ($database->type() === 'standalone_mongodb') { $payload['standalone_mongodb_id'] = $newDatabase->id; + } else if ($database->type() === 'standalone_mysql') { + $payload['standalone_mysql_id'] = $newDatabase->id; + }else if ($database->type() === 'standalone_mariadb') { + $payload['standalone_mariadb_id'] = $newDatabase->id; } $newEnvironmentVariable = $environmentVarible->replicate()->fill($payload); $newEnvironmentVariable->save(); diff --git a/app/Http/Livewire/Project/Database/BackupExecution.php b/app/Http/Livewire/Project/Database/BackupExecution.php deleted file mode 100644 index 2f9d7dcb5..000000000 --- a/app/Http/Livewire/Project/Database/BackupExecution.php +++ /dev/null @@ -1,23 +0,0 @@ -execution->filename, $this->execution->scheduledDatabaseBackup->database->destination->server); - $this->execution->delete(); - $this->emit('success', 'Backup deleted successfully.'); - $this->emit('refreshBackupExecutions'); - } -} diff --git a/app/Http/Livewire/Project/Database/BackupExecutions.php b/app/Http/Livewire/Project/Database/BackupExecutions.php index 93da317f7..2f808d992 100644 --- a/app/Http/Livewire/Project/Database/BackupExecutions.php +++ b/app/Http/Livewire/Project/Database/BackupExecutions.php @@ -8,8 +8,21 @@ class BackupExecutions extends Component { public $backup; public $executions; - protected $listeners = ['refreshBackupExecutions']; + public $setDeletableBackup; + protected $listeners = ['refreshBackupExecutions', 'deleteBackup']; + public function deleteBackup($exeuctionId) + { + $execution = $this->backup->executions()->where('id', $exeuctionId)->first(); + if (is_null($execution)) { + $this->emit('error', 'Backup execution not found.'); + return; + } + delete_backup_locally($execution->filename, $execution->scheduledDatabaseBackup->database->destination->server); + $execution->delete(); + $this->emit('success', 'Backup deleted successfully.'); + $this->emit('refreshBackupExecutions'); + } public function refreshBackupExecutions(): void { $this->executions = $this->backup->executions; diff --git a/app/Http/Livewire/Project/Database/CreateScheduledBackup.php b/app/Http/Livewire/Project/Database/CreateScheduledBackup.php index ac34e93bd..f804c389d 100644 --- a/app/Http/Livewire/Project/Database/CreateScheduledBackup.php +++ b/app/Http/Livewire/Project/Database/CreateScheduledBackup.php @@ -48,6 +48,10 @@ class CreateScheduledBackup extends Component ]; if ($this->database->type() === 'standalone-postgresql') { $payload['databases_to_backup'] = $this->database->postgres_db; + } else if ($this->database->type() === 'standalone-mysql') { + $payload['databases_to_backup'] = $this->database->mysql_database; + }else if ($this->database->type() === 'standalone-mariadb') { + $payload['databases_to_backup'] = $this->database->mariadb_database; } ScheduledDatabaseBackup::create($payload); $this->emit('refreshScheduledBackups'); diff --git a/app/Http/Livewire/Project/Database/Heading.php b/app/Http/Livewire/Project/Database/Heading.php index 6045e2b7f..7b14e5368 100644 --- a/app/Http/Livewire/Project/Database/Heading.php +++ b/app/Http/Livewire/Project/Database/Heading.php @@ -2,7 +2,9 @@ namespace App\Http\Livewire\Project\Database; +use App\Actions\Database\StartMariadb; use App\Actions\Database\StartMongodb; +use App\Actions\Database\StartMysql; use App\Actions\Database\StartPostgresql; use App\Actions\Database\StartRedis; use App\Actions\Database\StopDatabase; @@ -49,14 +51,18 @@ class Heading extends Component if ($this->database->type() === 'standalone-postgresql') { $activity = StartPostgresql::run($this->database); $this->emit('newMonitorActivity', $activity->id); - } - if ($this->database->type() === 'standalone-redis') { + } else if ($this->database->type() === 'standalone-redis') { $activity = StartRedis::run($this->database); $this->emit('newMonitorActivity', $activity->id); - } - if ($this->database->type() === 'standalone-mongodb') { + } else if ($this->database->type() === 'standalone-mongodb') { $activity = StartMongodb::run($this->database); $this->emit('newMonitorActivity', $activity->id); + } else if ($this->database->type() === 'standalone-mysql') { + $activity = StartMysql::run($this->database); + $this->emit('newMonitorActivity', $activity->id); + } else if ($this->database->type() === 'standalone-mariadb') { + $activity = StartMariadb::run($this->database); + $this->emit('newMonitorActivity', $activity->id); } } } diff --git a/app/Http/Livewire/Project/Database/Mariadb/General.php b/app/Http/Livewire/Project/Database/Mariadb/General.php new file mode 100644 index 000000000..4d04371d0 --- /dev/null +++ b/app/Http/Livewire/Project/Database/Mariadb/General.php @@ -0,0 +1,95 @@ + 'required', + 'database.description' => 'nullable', + 'database.mariadb_root_password' => 'required', + 'database.mariadb_user' => 'required', + 'database.mariadb_password' => 'required', + 'database.mariadb_database' => 'required', + 'database.mariadb_conf' => 'nullable', + 'database.image' => 'required', + 'database.ports_mappings' => 'nullable', + 'database.is_public' => 'nullable|boolean', + 'database.public_port' => 'nullable|integer', + ]; + protected $validationAttributes = [ + 'database.name' => 'Name', + 'database.description' => 'Description', + 'database.mariadb_root_password' => 'Root Password', + 'database.mariadb_user' => 'User', + 'database.mariadb_password' => 'Password', + 'database.mariadb_database' => 'Database', + 'database.mariadb_conf' => 'MariaDB Configuration', + 'database.image' => 'Image', + 'database.ports_mappings' => 'Port Mapping', + 'database.is_public' => 'Is Public', + 'database.public_port' => 'Public Port', + ]; + public function submit() + { + try { + $this->validate(); + $this->database->save(); + $this->emit('success', 'Database updated successfully.'); + } catch (Exception $e) { + return handleError($e, $this); + } + } + public function instantSave() + { + try { + if ($this->database->is_public && !$this->database->public_port) { + $this->emit('error', 'Public port is required.'); + $this->database->is_public = false; + return; + } + if ($this->database->is_public) { + if (!str($this->database->status)->startsWith('running')) { + $this->emit('error', 'Database must be started to be publicly accessible.'); + $this->database->is_public = false; + return; + } + StartDatabaseProxy::run($this->database); + $this->emit('success', 'Database is now publicly accessible.'); + } else { + StopDatabaseProxy::run($this->database); + $this->emit('success', 'Database is no longer publicly accessible.'); + } + $this->db_url = $this->database->getDbUrl(); + $this->database->save(); + } catch (\Throwable $e) { + $this->database->is_public = !$this->database->is_public; + return handleError($e, $this); + } + } + public function refresh(): void + { + $this->database->refresh(); + } + + public function mount() + { + $this->db_url = $this->database->getDbUrl(); + } + + public function render() + { + return view('livewire.project.database.mariadb.general'); + } +} diff --git a/app/Http/Livewire/Project/Database/Mongodb/General.php b/app/Http/Livewire/Project/Database/Mongodb/General.php index e0fc3c277..2e6bed4a4 100644 --- a/app/Http/Livewire/Project/Database/Mongodb/General.php +++ b/app/Http/Livewire/Project/Database/Mongodb/General.php @@ -39,7 +39,8 @@ class General extends Component 'database.is_public' => 'Is Public', 'database.public_port' => 'Public Port', ]; - public function submit() { + public function submit() + { try { $this->validate(); if ($this->database->mongo_conf === "") { @@ -60,7 +61,11 @@ class General extends Component return; } if ($this->database->is_public) { - $this->emit('success', 'Starting TCP proxy...'); + if (!str($this->database->status)->startsWith('running')) { + $this->emit('error', 'Database must be started to be publicly accessible.'); + $this->database->is_public = false; + return; + } StartDatabaseProxy::run($this->database); $this->emit('success', 'Database is now publicly accessible.'); } else { @@ -69,7 +74,7 @@ class General extends Component } $this->db_url = $this->database->getDbUrl(); $this->database->save(); - } catch(\Throwable $e) { + } catch (\Throwable $e) { $this->database->is_public = !$this->database->is_public; return handleError($e, $this); } diff --git a/app/Http/Livewire/Project/Database/Mysql/General.php b/app/Http/Livewire/Project/Database/Mysql/General.php new file mode 100644 index 000000000..ca7eb6ebe --- /dev/null +++ b/app/Http/Livewire/Project/Database/Mysql/General.php @@ -0,0 +1,95 @@ + 'required', + 'database.description' => 'nullable', + 'database.mysql_root_password' => 'required', + 'database.mysql_user' => 'required', + 'database.mysql_password' => 'required', + 'database.mysql_database' => 'required', + 'database.mysql_conf' => 'nullable', + 'database.image' => 'required', + 'database.ports_mappings' => 'nullable', + 'database.is_public' => 'nullable|boolean', + 'database.public_port' => 'nullable|integer', + ]; + protected $validationAttributes = [ + 'database.name' => 'Name', + 'database.description' => 'Description', + 'database.mysql_root_password' => 'Root Password', + 'database.mysql_user' => 'User', + 'database.mysql_password' => 'Password', + 'database.mysql_database' => 'Database', + 'database.mysql_conf' => 'MySQL Configuration', + 'database.image' => 'Image', + 'database.ports_mappings' => 'Port Mapping', + 'database.is_public' => 'Is Public', + 'database.public_port' => 'Public Port', + ]; + public function submit() + { + try { + $this->validate(); + $this->database->save(); + $this->emit('success', 'Database updated successfully.'); + } catch (Exception $e) { + return handleError($e, $this); + } + } + public function instantSave() + { + try { + if ($this->database->is_public && !$this->database->public_port) { + $this->emit('error', 'Public port is required.'); + $this->database->is_public = false; + return; + } + if ($this->database->is_public) { + if (!str($this->database->status)->startsWith('running')) { + $this->emit('error', 'Database must be started to be publicly accessible.'); + $this->database->is_public = false; + return; + } + StartDatabaseProxy::run($this->database); + $this->emit('success', 'Database is now publicly accessible.'); + } else { + StopDatabaseProxy::run($this->database); + $this->emit('success', 'Database is no longer publicly accessible.'); + } + $this->db_url = $this->database->getDbUrl(); + $this->database->save(); + } catch (\Throwable $e) { + $this->database->is_public = !$this->database->is_public; + return handleError($e, $this); + } + } + public function refresh(): void + { + $this->database->refresh(); + } + + public function mount() + { + $this->db_url = $this->database->getDbUrl(); + } + + public function render() + { + return view('livewire.project.database.mysql.general'); + } +} diff --git a/app/Http/Livewire/Project/Database/Postgresql/General.php b/app/Http/Livewire/Project/Database/Postgresql/General.php index df1f0da85..4e3bda418 100644 --- a/app/Http/Livewire/Project/Database/Postgresql/General.php +++ b/app/Http/Livewire/Project/Database/Postgresql/General.php @@ -60,7 +60,11 @@ class General extends Component return; } if ($this->database->is_public) { - $this->emit('success', 'Starting TCP proxy...'); + if (!str($this->database->status)->startsWith('running')) { + $this->emit('error', 'Database must be started to be publicly accessible.'); + $this->database->is_public = false; + return; + } StartDatabaseProxy::run($this->database); $this->emit('success', 'Database is now publicly accessible.'); } else { @@ -69,11 +73,10 @@ class General extends Component } $this->db_url = $this->database->getDbUrl(); $this->database->save(); - } catch(\Throwable $e) { + } catch (\Throwable $e) { $this->database->is_public = !$this->database->is_public; return handleError($e, $this); } - } public function save_init_script($script) { diff --git a/app/Http/Livewire/Project/Database/Redis/General.php b/app/Http/Livewire/Project/Database/Redis/General.php index 6f33ae30a..dd2e8151d 100644 --- a/app/Http/Livewire/Project/Database/Redis/General.php +++ b/app/Http/Livewire/Project/Database/Redis/General.php @@ -35,7 +35,8 @@ class General extends Component 'database.is_public' => 'Is Public', 'database.public_port' => 'Public Port', ]; - public function submit() { + public function submit() + { try { $this->validate(); if ($this->database->redis_conf === "") { @@ -56,7 +57,11 @@ class General extends Component return; } if ($this->database->is_public) { - $this->emit('success', 'Starting TCP proxy...'); + if (!str($this->database->status)->startsWith('running')) { + $this->emit('error', 'Database must be started to be publicly accessible.'); + $this->database->is_public = false; + return; + } StartDatabaseProxy::run($this->database); $this->emit('success', 'Database is now publicly accessible.'); } else { @@ -65,7 +70,7 @@ class General extends Component } $this->db_url = $this->database->getDbUrl(); $this->database->save(); - } catch(\Throwable $e) { + } catch (\Throwable $e) { $this->database->is_public = !$this->database->is_public; return handleError($e, $this); } diff --git a/app/Http/Livewire/Project/Shared/EnvironmentVariable/All.php b/app/Http/Livewire/Project/Shared/EnvironmentVariable/All.php index f453b4bf3..9b714a590 100644 --- a/app/Http/Livewire/Project/Shared/EnvironmentVariable/All.php +++ b/app/Http/Livewire/Project/Shared/EnvironmentVariable/All.php @@ -81,6 +81,12 @@ class All extends Component case 'standalone-mongodb': $environment->standalone_mongodb_id = $this->resource->id; break; + case 'standalone-mysql': + $environment->standalone_mysql_id = $this->resource->id; + break; + case 'standalone-mariadb': + $environment->standalone_mariadb_id = $this->resource->id; + break; case 'service': $environment->service_id = $this->resource->id; break; diff --git a/app/Http/Livewire/Project/Shared/Logs.php b/app/Http/Livewire/Project/Shared/Logs.php index 80cdf82c4..2b0561800 100644 --- a/app/Http/Livewire/Project/Shared/Logs.php +++ b/app/Http/Livewire/Project/Shared/Logs.php @@ -5,7 +5,9 @@ namespace App\Http\Livewire\Project\Shared; use App\Models\Application; use App\Models\Server; use App\Models\Service; +use App\Models\StandaloneMariadb; use App\Models\StandaloneMongodb; +use App\Models\StandaloneMysql; use App\Models\StandalonePostgresql; use App\Models\StandaloneRedis; use Livewire\Component; @@ -13,7 +15,7 @@ use Livewire\Component; class Logs extends Component { public ?string $type = null; - public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb $resource; + public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb $resource; public Server $server; public ?string $container = null; public $parameters; @@ -41,11 +43,16 @@ class Logs extends Component if (is_null($resource)) { $resource = StandaloneMongodb::where('uuid', $this->parameters['database_uuid'])->first(); if (is_null($resource)) { - abort(404); + $resource = StandaloneMysql::where('uuid', $this->parameters['database_uuid'])->first(); + if (is_null($resource)) { + $resource = StandaloneMariadb::where('uuid', $this->parameters['database_uuid'])->first(); + if (is_null($resource)) { + abort(404); + } + } } } } - $this->resource = $resource; $this->status = $this->resource->status; $this->server = $this->resource->destination->server; diff --git a/app/Jobs/DatabaseBackupJob.php b/app/Jobs/DatabaseBackupJob.php index d73474018..da660c449 100644 --- a/app/Jobs/DatabaseBackupJob.php +++ b/app/Jobs/DatabaseBackupJob.php @@ -6,7 +6,9 @@ use App\Models\S3Storage; use App\Models\ScheduledDatabaseBackup; use App\Models\ScheduledDatabaseBackupExecution; use App\Models\Server; +use App\Models\StandaloneMariadb; use App\Models\StandaloneMongodb; +use App\Models\StandaloneMysql; use App\Models\StandalonePostgresql; use App\Models\Team; use App\Notifications\Database\BackupFailed; @@ -28,7 +30,7 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted public ?Team $team = null; public Server $server; public ScheduledDatabaseBackup $backup; - public StandalonePostgresql|StandaloneMongodb $database; + public StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb $database; public ?string $container_name = null; public ?ScheduledDatabaseBackupExecution $backup_log = null; @@ -75,6 +77,10 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted $databasesToBackup = [$this->database->postgres_db]; } else if ($databaseType === 'standalone-mongodb') { $databasesToBackup = ['*']; + } else if ($databaseType === 'standalone-mysql') { + $databasesToBackup = [$this->database->mysql_database]; + } else if ($databaseType === 'standalone-mariadb') { + $databasesToBackup = [$this->database->mariadb_database]; } else { return; } @@ -88,6 +94,14 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted $databasesToBackup = explode('|', $databasesToBackup); $databasesToBackup = array_map('trim', $databasesToBackup); ray($databasesToBackup); + } else if ($databaseType === 'standalone-mysql') { + // Format: db1,db2,db3 + $databasesToBackup = explode(',', $databasesToBackup); + $databasesToBackup = array_map('trim', $databasesToBackup); + } else if ($databaseType === 'standalone-mariadb') { + // Format: db1,db2,db3 + $databasesToBackup = explode(',', $databasesToBackup); + $databasesToBackup = array_map('trim', $databasesToBackup); } else { return; } @@ -124,7 +138,6 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted } else { $databaseName = $database; } - ray($databaseName); } $this->backup_file = "/mongo-dump-$databaseName-" . Carbon::now()->timestamp . ".tar.gz"; $this->backup_location = $this->backup_dir . $this->backup_file; @@ -134,6 +147,24 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted 'scheduled_database_backup_id' => $this->backup->id, ]); $this->backup_standalone_mongodb($database); + } else if ($databaseType === 'standalone-mysql') { + $this->backup_file = "/mysql-dump-$database-" . Carbon::now()->timestamp . ".dmp"; + $this->backup_location = $this->backup_dir . $this->backup_file; + $this->backup_log = ScheduledDatabaseBackupExecution::create([ + 'database_name' => $database, + 'filename' => $this->backup_location, + 'scheduled_database_backup_id' => $this->backup->id, + ]); + $this->backup_standalone_mysql($database); + } else if ($databaseType === 'standalone-mariadb') { + $this->backup_file = "/mariadb-dump-$database-" . Carbon::now()->timestamp . ".dmp"; + $this->backup_location = $this->backup_dir . $this->backup_file; + $this->backup_log = ScheduledDatabaseBackupExecution::create([ + 'database_name' => $database, + 'filename' => $this->backup_location, + 'scheduled_database_backup_id' => $this->backup->id, + ]); + $this->backup_standalone_mariadb($database); } else { throw new \Exception('Unsupported database type'); } @@ -218,7 +249,42 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted throw $e; } } - + private function backup_standalone_mysql(string $database): void + { + try { + $commands[] = "mkdir -p " . $this->backup_dir; + $commands[] = "docker exec $this->container_name mysqldump -u root -p{$this->database->mysql_root_password} $database > $this->backup_location"; + ray($commands); + $this->backup_output = instant_remote_process($commands, $this->server); + $this->backup_output = trim($this->backup_output); + if ($this->backup_output === '') { + $this->backup_output = null; + } + ray('Backup done for ' . $this->container_name . ' at ' . $this->server->name . ':' . $this->backup_location); + } catch (\Throwable $e) { + $this->add_to_backup_output($e->getMessage()); + ray('Backup failed for ' . $this->container_name . ' at ' . $this->server->name . ':' . $this->backup_location . '\n\nError:' . $e->getMessage()); + throw $e; + } + } + private function backup_standalone_mariadb(string $database): void + { + try { + $commands[] = "mkdir -p " . $this->backup_dir; + $commands[] = "docker exec $this->container_name mariadb-dump -u root -p{$this->database->mariadb_root_password} $database > $this->backup_location"; + ray($commands); + $this->backup_output = instant_remote_process($commands, $this->server); + $this->backup_output = trim($this->backup_output); + if ($this->backup_output === '') { + $this->backup_output = null; + } + ray('Backup done for ' . $this->container_name . ' at ' . $this->server->name . ':' . $this->backup_location); + } catch (\Throwable $e) { + $this->add_to_backup_output($e->getMessage()); + ray('Backup failed for ' . $this->container_name . ' at ' . $this->server->name . ':' . $this->backup_location . '\n\nError:' . $e->getMessage()); + throw $e; + } + } private function add_to_backup_output($output): void { if ($this->backup_output) { diff --git a/app/Jobs/StopResourceJob.php b/app/Jobs/StopResourceJob.php index 721f7f698..76c5588b8 100644 --- a/app/Jobs/StopResourceJob.php +++ b/app/Jobs/StopResourceJob.php @@ -7,7 +7,9 @@ use App\Actions\Database\StopDatabase; use App\Actions\Service\StopService; use App\Models\Application; use App\Models\Service; +use App\Models\StandaloneMariadb; use App\Models\StandaloneMongodb; +use App\Models\StandaloneMysql; use App\Models\StandalonePostgresql; use App\Models\StandaloneRedis; use Illuminate\Bus\Queueable; @@ -21,7 +23,7 @@ class StopResourceJob implements ShouldQueue, ShouldBeEncrypted { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - public function __construct(public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb $resource) + public function __construct(public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb $resource) { } @@ -45,6 +47,12 @@ class StopResourceJob implements ShouldQueue, ShouldBeEncrypted case 'standalone-mongodb': StopDatabase::run($this->resource); break; + case 'standalone-mysql': + StopDatabase::run($this->resource); + break; + case 'standalone-mariadb': + StopDatabase::run($this->resource); + break; case 'service': StopService::run($this->resource); break; diff --git a/app/Models/Environment.php b/app/Models/Environment.php index 55dbeee94..430a02cdb 100644 --- a/app/Models/Environment.php +++ b/app/Models/Environment.php @@ -34,13 +34,23 @@ class Environment extends Model { return $this->hasMany(StandaloneMongodb::class); } + public function mysqls() + { + return $this->hasMany(StandaloneMysql::class); + } + public function mariadbs() + { + return $this->hasMany(StandaloneMariadb::class); + } public function databases() { $postgresqls = $this->postgresqls; $redis = $this->redis; $mongodbs = $this->mongodbs; - return $postgresqls->concat($redis)->concat($mongodbs); + $mysqls = $this->mysqls; + $mariadbs = $this->mariadbs; + return $postgresqls->concat($redis)->concat($mongodbs)->concat($mysqls)->concat($mariadbs); } public function project() diff --git a/app/Models/Project.php b/app/Models/Project.php index a910348b4..1668d4059 100644 --- a/app/Models/Project.php +++ b/app/Models/Project.php @@ -56,4 +56,16 @@ class Project extends BaseModel { return $this->hasManyThrough(StandaloneRedis::class, Environment::class); } + public function mongodbs() + { + return $this->hasManyThrough(StandaloneMongodb::class, Environment::class); + } + public function mysqls() + { + return $this->hasMany(StandaloneMysql::class, Environment::class); + } + public function mariadbs() + { + return $this->hasMany(StandaloneMariadb::class, Environment::class); + } } diff --git a/app/Models/Server.php b/app/Models/Server.php index 7ff517ef6..11be55764 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -122,10 +122,12 @@ class Server extends BaseModel public function databases() { return $this->destinations()->map(function ($standaloneDocker) { - $postgresqls = data_get($standaloneDocker,'postgresqls',collect([])); - $redis = data_get($standaloneDocker,'redis',collect([])); - $mongodbs = data_get($standaloneDocker,'mongodbs',collect([])); - return $postgresqls->concat($redis)->concat($mongodbs); + $postgresqls = data_get($standaloneDocker, 'postgresqls', collect([])); + $redis = data_get($standaloneDocker, 'redis', collect([])); + $mongodbs = data_get($standaloneDocker, 'mongodbs', collect([])); + $mysqls = data_get($standaloneDocker, 'mysqls', collect([])); + $mariadbs = data_get($standaloneDocker, 'mariadbs', collect([])); + return $postgresqls->concat($redis)->concat($mongodbs)->concat($mysqls)->concat($mariadbs); })->flatten(); } public function applications() @@ -258,7 +260,8 @@ class Server extends BaseModel $this->settings->save(); return true; } - public function validateCoolifyNetwork() { + public function validateCoolifyNetwork() + { return instant_remote_process(["docker network create coolify --attachable >/dev/null 2>&1 || true"], $this, false); } } diff --git a/app/Models/StandaloneDocker.php b/app/Models/StandaloneDocker.php index 9e70b7514..277a250c9 100644 --- a/app/Models/StandaloneDocker.php +++ b/app/Models/StandaloneDocker.php @@ -24,6 +24,14 @@ class StandaloneDocker extends BaseModel { return $this->morphMany(StandaloneMongodb::class, 'destination'); } + public function mysqls() + { + return $this->morphMany(StandaloneMysql::class, 'destination'); + } + public function mariadbs() + { + return $this->morphMany(StandaloneMariadb::class, 'destination'); + } public function server() { @@ -35,6 +43,16 @@ class StandaloneDocker extends BaseModel return $this->morphMany(Service::class, 'destination'); } + public function databases() + { + $postgresqls = $this->postgresqls; + $redis = $this->redis; + $mongodbs = $this->mongodbs; + $mysqls = $this->mysqls; + $mariadbs = $this->mariadbs; + return $postgresqls->concat($redis)->concat($mongodbs)->concat($mysqls)->concat($mariadbs); + } + public function attachedTo() { return $this->applications?->count() > 0 || $this->databases?->count() > 0; diff --git a/app/Models/StandaloneMariadb.php b/app/Models/StandaloneMariadb.php new file mode 100644 index 000000000..5e721857b --- /dev/null +++ b/app/Models/StandaloneMariadb.php @@ -0,0 +1,106 @@ + 'encrypted', + ]; + + protected static function booted() + { + static::created(function ($database) { + LocalPersistentVolume::create([ + 'name' => 'mariadb-data-' . $database->uuid, + 'mount_path' => '/var/lib/mysql', + 'host_path' => null, + 'resource_id' => $database->id, + 'resource_type' => $database->getMorphClass(), + 'is_readonly' => true + ]); + }); + static::deleting(function ($database) { + $storages = $database->persistentStorages()->get(); + foreach ($storages as $storage) { + instant_remote_process(["docker volume rm -f $storage->name"], $database->destination->server, false); + } + $database->scheduledBackups()->delete(); + $database->persistentStorages()->delete(); + $database->environment_variables()->delete(); + }); + } + public function type(): string + { + return 'standalone-mariadb'; + } + + 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), + + ); + } + + public function getDbUrl(bool $useInternal = false): string + { + if ($this->is_public && !$useInternal) { + return "mysql://{$this->mariadb_user}:{$this->mariadb_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->mariadb_database}"; + } else { + return "mysql://{$this->mariadb_user}:{$this->mariadb_password}@{$this->uuid}:3306/{$this->mariadb_database}"; + } + } + + 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'); + } +} diff --git a/app/Models/StandaloneMysql.php b/app/Models/StandaloneMysql.php new file mode 100644 index 000000000..53a797d49 --- /dev/null +++ b/app/Models/StandaloneMysql.php @@ -0,0 +1,106 @@ + '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::deleting(function ($database) { + $storages = $database->persistentStorages()->get(); + foreach ($storages as $storage) { + instant_remote_process(["docker volume rm -f $storage->name"], $database->destination->server, false); + } + $database->scheduledBackups()->delete(); + $database->persistentStorages()->delete(); + $database->environment_variables()->delete(); + }); + } + public function type(): string + { + return 'standalone-mysql'; + } + + 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), + + ); + } + + public function getDbUrl(bool $useInternal = false): string + { + if ($this->is_public && !$useInternal) { + return "mysql://{$this->mysql_user}:{$this->mysql_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->mysql_database}"; + } else { + return "mysql://{$this->mysql_user}:{$this->mysql_password}@{$this->uuid}:3306/{$this->mysql_database}"; + } + } + + 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'); + } +} diff --git a/app/Models/StandalonePostgresql.php b/app/Models/StandalonePostgresql.php index 3a0432180..bbfabbf67 100644 --- a/app/Models/StandalonePostgresql.php +++ b/app/Models/StandalonePostgresql.php @@ -46,8 +46,6 @@ class StandalonePostgresql extends BaseModel ); } - // Normal Deployments - public function portsMappingsArray(): Attribute { return Attribute::make( diff --git a/bootstrap/helpers/constants.php b/bootstrap/helpers/constants.php index 586ba531d..10c1353d3 100644 --- a/bootstrap/helpers/constants.php +++ b/bootstrap/helpers/constants.php @@ -1,6 +1,6 @@ '* * * * *', 'hourly' => '0 * * * *', diff --git a/bootstrap/helpers/databases.php b/bootstrap/helpers/databases.php index 0c5c8898e..007c414bd 100644 --- a/bootstrap/helpers/databases.php +++ b/bootstrap/helpers/databases.php @@ -2,7 +2,9 @@ use App\Models\Server; use App\Models\StandaloneDocker; +use App\Models\StandaloneMariadb; use App\Models\StandaloneMongodb; +use App\Models\StandaloneMysql; use App\Models\StandalonePostgresql; use App\Models\StandaloneRedis; use Visus\Cuid2\Cuid2; @@ -58,6 +60,36 @@ function create_standalone_mongodb($environment_id, $destination_uuid): Standalo 'destination_type' => $destination->getMorphClass(), ]); } +function create_standalone_mysql($environment_id, $destination_uuid): StandaloneMysql +{ + $destination = StandaloneDocker::where('uuid', $destination_uuid)->first(); + if (!$destination) { + throw new Exception('Destination not found'); + } + return StandaloneMysql::create([ + 'name' => generate_database_name('mysql'), + 'mysql_root_password' => \Illuminate\Support\Str::password(symbols: false), + 'mysql_password' => \Illuminate\Support\Str::password(symbols: false), + 'environment_id' => $environment_id, + 'destination_id' => $destination->id, + 'destination_type' => $destination->getMorphClass(), + ]); +} +function create_standalone_mariadb($environment_id, $destination_uuid): StandaloneMariadb +{ + $destination = StandaloneDocker::where('uuid', $destination_uuid)->first(); + if (!$destination) { + throw new Exception('Destination not found'); + } + return StandaloneMariadb::create([ + 'name' => generate_database_name('mariadb'), + 'mariadb_root_password' => \Illuminate\Support\Str::password(symbols: false), + 'mariadb_password' => \Illuminate\Support\Str::password(symbols: false), + 'environment_id' => $environment_id, + 'destination_id' => $destination->id, + 'destination_type' => $destination->getMorphClass(), + ]); +} /** * Delete file locally on the filesystem. diff --git a/database/migrations/2023_10_24_103548_create_standalone_mysqls_table.php b/database/migrations/2023_10_24_103548_create_standalone_mysqls_table.php new file mode 100644 index 000000000..2b069424a --- /dev/null +++ b/database/migrations/2023_10_24_103548_create_standalone_mysqls_table.php @@ -0,0 +1,57 @@ +id(); + $table->string('uuid')->unique(); + $table->string('name'); + $table->string('description')->nullable(); + + $table->text('mysql_root_password'); + $table->string('mysql_user')->default('mysql'); + $table->text('mysql_password'); + $table->string('mysql_database')->default('default'); + $table->longText('mysql_conf')->nullable(); + + $table->string('status')->default('exited'); + + $table->string('image')->default('mysql:8'); + $table->boolean('is_public')->default(false); + $table->integer('public_port')->nullable(); + $table->text('ports_mappings')->nullable(); + + $table->string('limits_memory')->default("0"); + $table->string('limits_memory_swap')->default("0"); + $table->integer('limits_memory_swappiness')->default(60); + $table->string('limits_memory_reservation')->default("0"); + + $table->string('limits_cpus')->default("0"); + $table->string('limits_cpuset')->nullable()->default("0"); + $table->integer('limits_cpu_shares')->default(1024); + + $table->timestamp('started_at')->nullable(); + $table->morphs('destination'); + + $table->foreignId('environment_id')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('standalone_mysqls'); + } +}; diff --git a/database/migrations/2023_10_24_120523_create_standalone_mariadbs_table.php b/database/migrations/2023_10_24_120523_create_standalone_mariadbs_table.php new file mode 100644 index 000000000..4d7b89f4c --- /dev/null +++ b/database/migrations/2023_10_24_120523_create_standalone_mariadbs_table.php @@ -0,0 +1,57 @@ +id(); + $table->string('uuid')->unique(); + $table->string('name'); + $table->string('description')->nullable(); + + $table->text('mariadb_root_password'); + $table->string('mariadb_user')->default('mariadb'); + $table->text('mariadb_password'); + $table->string('mariadb_database')->default('default'); + $table->longText('mariadb_conf')->nullable(); + + $table->string('status')->default('exited'); + + $table->string('image')->default('mariadb:11'); + $table->boolean('is_public')->default(false); + $table->integer('public_port')->nullable(); + $table->text('ports_mappings')->nullable(); + + $table->string('limits_memory')->default("0"); + $table->string('limits_memory_swap')->default("0"); + $table->integer('limits_memory_swappiness')->default(60); + $table->string('limits_memory_reservation')->default("0"); + + $table->string('limits_cpus')->default("0"); + $table->string('limits_cpuset')->nullable()->default("0"); + $table->integer('limits_cpu_shares')->default(1024); + + $table->timestamp('started_at')->nullable(); + $table->morphs('destination'); + + $table->foreignId('environment_id')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('standalone_mariadbs'); + } +}; diff --git a/database/migrations/2023_10_24_120524_add_standalone_mysql_to_environment_variables_table.php b/database/migrations/2023_10_24_120524_add_standalone_mysql_to_environment_variables_table.php new file mode 100644 index 000000000..a511e9b21 --- /dev/null +++ b/database/migrations/2023_10_24_120524_add_standalone_mysql_to_environment_variables_table.php @@ -0,0 +1,30 @@ +foreignId('standalone_mysql_id')->nullable(); + $table->foreignId('standalone_mariadb_id')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('environment_variables', function (Blueprint $table) { + $table->dropColumn('standalone_mysql_id'); + $table->dropColumn('standalone_mariadb_id'); + }); + } +}; diff --git a/resources/views/components/databases/navbar.blade.php b/resources/views/components/databases/navbar.blade.php index 64dc1d288..ecf4ca573 100644 --- a/resources/views/components/databases/navbar.blade.php +++ b/resources/views/components/databases/navbar.blade.php @@ -7,7 +7,11 @@ href="{{ route('project.database.logs', $parameters) }}"> - @if ($database->getMorphClass() === 'App\Models\StandalonePostgresql' || $database->getMorphClass() === 'App\Models\StandaloneMongodb') + @if ( + $database->getMorphClass() === 'App\Models\StandalonePostgresql' || + $database->getMorphClass() === 'App\Models\StandaloneMongodb' || + $database->getMorphClass() === 'App\Models\StandaloneMysql' || + $database->getMorphClass() === 'App\Models\StandaloneMariadb') diff --git a/resources/views/livewire/project/database/backup-edit.blade.php b/resources/views/livewire/project/database/backup-edit.blade.php index 7d634a37b..0542ee087 100644 --- a/resources/views/livewire/project/database/backup-edit.blade.php +++ b/resources/views/livewire/project/database/backup-edit.blade.php @@ -35,6 +35,14 @@ + @elseif($backup->database_type === 'App\Models\StandaloneMysql') + + @elseif($backup->database_type === 'App\Models\StandaloneMariadb') + @endif
diff --git a/resources/views/livewire/project/database/backup-execution.blade.php b/resources/views/livewire/project/database/backup-execution.blade.php deleted file mode 100644 index cf306858d..000000000 --- a/resources/views/livewire/project/database/backup-execution.blade.php +++ /dev/null @@ -1,8 +0,0 @@ -
-
- - {{-- @if (data_get($execution, 'status') !== 'failed') --}} - {{-- Download --}} - {{-- @endif --}} - Delete -
diff --git a/resources/views/livewire/project/database/backup-executions.blade.php b/resources/views/livewire/project/database/backup-executions.blade.php index 30af9e93e..7b0a2ca51 100644 --- a/resources/views/livewire/project/database/backup-executions.blade.php +++ b/resources/views/livewire/project/database/backup-executions.blade.php @@ -1,9 +1,10 @@
@forelse($executions as $execution) -
data_get($execution, 'status') === 'success', - 'border-red-500' => data_get($execution, 'status') === 'failed', - ])> + data_get($execution, 'status') === 'success', + 'border-red-500' => data_get($execution, 'status') === 'failed', + ])>
Database: {{ data_get($execution, 'database_name', 'N/A') }}
Status: {{ data_get($execution, 'status') }}
Started At: {{ data_get($execution, 'created_at') }}
@@ -14,9 +15,24 @@ kB / {{ round((int) data_get($execution, 'size') / 1024 / 1024, 3) }} MB
Location: {{ data_get($execution, 'filename', 'N/A') }}
- +
+
+ + {{-- @if (data_get($execution, 'status') !== 'failed') --}} + {{-- Download --}} + {{-- @endif --}} + Delete +
@empty
No executions found.
@endforelse +
diff --git a/resources/views/livewire/project/database/mariadb/general.blade.php b/resources/views/livewire/project/database/mariadb/general.blade.php new file mode 100644 index 000000000..6b0205cbe --- /dev/null +++ b/resources/views/livewire/project/database/mariadb/general.blade.php @@ -0,0 +1,58 @@ +
+
+
+

General

+ + Save + +
+
+ + + +
+ @if ($database->started_at) +
+ + + + +
+ @else +
Please verify these values. You can only modify them before the initial + start. After that, you need to modify it in the database. +
+
+ + + + +
+ @endif +
+

Network

+
+ + + +
+ +
+ + +
diff --git a/resources/views/livewire/project/database/mysql/general.blade.php b/resources/views/livewire/project/database/mysql/general.blade.php new file mode 100644 index 000000000..09a23a694 --- /dev/null +++ b/resources/views/livewire/project/database/mysql/general.blade.php @@ -0,0 +1,58 @@ +
+
+
+

General

+ + Save + +
+
+ + + +
+ @if ($database->started_at) +
+ + + + +
+ @else +
Please verify these values. You can only modify them before the initial + start. After that, you need to modify it in the database. +
+
+ + + + +
+ @endif +
+

Network

+
+ + + +
+ +
+ + +
diff --git a/resources/views/livewire/project/new/select.blade.php b/resources/views/livewire/project/new/select.blade.php index 6ebefda41..a18d00fc5 100644 --- a/resources/views/livewire/project/new/select.blade.php +++ b/resources/views/livewire/project/new/select.blade.php @@ -114,6 +114,26 @@ +
+
+
+ New MySQL +
+
+ MySQL +
+
+
+
+
+
+ New Mariadb +
+
+ MySQL +
+
+
{{--