From b8dd7704b3a64e089ca3cf694d68d33b5b96e396 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 12 Oct 2023 13:35:57 +0200 Subject: [PATCH 01/17] update resource delete command --- app/Console/Commands/ResourcesDelete.php | 27 ++++++++++++------------ config/sentry.php | 2 +- config/version.php | 2 +- versions.json | 2 +- 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/app/Console/Commands/ResourcesDelete.php b/app/Console/Commands/ResourcesDelete.php index 3b0134f47..1c90f8e6b 100644 --- a/app/Console/Commands/ResourcesDelete.php +++ b/app/Console/Commands/ResourcesDelete.php @@ -43,23 +43,24 @@ class ResourcesDelete extends Command $this->deleteDatabase(); } elseif ($resource === 'Service') { $this->deleteService(); - } elseif($resource === 'Server') { + } elseif ($resource === 'Server') { $this->deleteServer(); } } - private function deleteServer() { + private function deleteServer() + { $servers = Server::all(); if ($servers->count() === 0) { $this->error('There are no applications to delete.'); return; } $serversToDelete = multiselect( - 'What server do you want to delete?', - $servers->pluck('id')->sort()->toArray(), + label: 'What server do you want to delete?', + options: $servers->pluck('name', 'id')->sortKeys(), ); - foreach ($serversToDelete as $id) { - $toDelete = Server::find($id); + foreach ($serversToDelete as $server) { + $toDelete = $servers->where('id', $server)->first(); $this->info($toDelete); $confirmed = confirm("Are you sure you want to delete all selected resources?"); if (!$confirmed) { @@ -77,11 +78,12 @@ class ResourcesDelete extends Command } $applicationsToDelete = multiselect( 'What application do you want to delete?', - $applications->pluck('name')->sort()->toArray(), + $applications->pluck('name', 'id')->sortKeys(), ); foreach ($applicationsToDelete as $application) { - $toDelete = $applications->where('name', $application)->first(); + ray($application); + $toDelete = $applications->where('id', $application)->first(); $this->info($toDelete); $confirmed = confirm("Are you sure you want to delete all selected resources? "); if (!$confirmed) { @@ -99,11 +101,11 @@ class ResourcesDelete extends Command } $databasesToDelete = multiselect( 'What database do you want to delete?', - $databases->pluck('name')->sort()->toArray(), + $databases->pluck('name', 'id')->sortKeys(), ); foreach ($databasesToDelete as $database) { - $toDelete = $databases->where('name', $database)->first(); + $toDelete = $databases->where('id', $database)->first(); $this->info($toDelete); $confirmed = confirm("Are you sure you want to delete all selected resources?"); if (!$confirmed) { @@ -111,7 +113,6 @@ class ResourcesDelete extends Command } $toDelete->delete(); } - } private function deleteService() { @@ -122,11 +123,11 @@ class ResourcesDelete extends Command } $servicesToDelete = multiselect( 'What service do you want to delete?', - $services->pluck('name')->sort()->toArray(), + $services->pluck('name', 'id')->sortKeys(), ); foreach ($servicesToDelete as $service) { - $toDelete = $services->where('name', $service)->first(); + $toDelete = $services->where('id', $service)->first(); $this->info($toDelete); $confirmed = confirm("Are you sure you want to delete all selected resources?"); if (!$confirmed) { diff --git a/config/sentry.php b/config/sentry.php index b1e4266f8..7a6e8e5d2 100644 --- a/config/sentry.php +++ b/config/sentry.php @@ -7,7 +7,7 @@ return [ // The release version of your application // Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD')) - 'release' => '4.0.0-beta.81', + 'release' => '4.0.0-beta.82', // When left empty or `null` the Laravel environment will be used 'environment' => config('app.env'), diff --git a/config/version.php b/config/version.php index a85ede531..7e5447038 100644 --- a/config/version.php +++ b/config/version.php @@ -1,3 +1,3 @@ Date: Thu, 12 Oct 2023 17:18:33 +0200 Subject: [PATCH 02/17] init: redis --- app/Actions/Database/StartRedis.php | 144 ++++++++++++++++++ app/Http/Controllers/DatabaseController.php | 6 +- app/Http/Controllers/ProjectController.php | 9 +- .../Livewire/Project/Database/Heading.php | 8 +- .../Project/Database/Postgresql/General.php | 4 +- .../Project/Database/Redis/General.php | 87 +++++++++++ .../Shared/EnvironmentVariable/All.php | 3 + app/Http/Livewire/Project/Shared/Logs.php | 12 +- app/Models/Environment.php | 10 +- app/Models/Project.php | 4 + app/Models/Server.php | 4 +- app/Models/StandaloneDocker.php | 4 + app/Models/StandaloneRedis.php | 103 +++++++++++++ bootstrap/helpers/constants.php | 2 +- bootstrap/helpers/databases.php | 16 ++ bootstrap/helpers/proxy.php | 13 +- ...2_132430_create_standalone_redis_table.php | 54 +++++++ ...e_redis_to_environment_variables_table.php | 28 ++++ database/seeders/StandaloneRedisSeeder.php | 23 +++ .../project/database/redis/general.blade.php | 27 ++++ .../livewire/project/new/select.blade.php | 10 ++ .../project/service/storage.blade.php | 3 +- .../project/database/configuration.blade.php | 25 +-- resources/views/project/resources.blade.php | 2 +- 24 files changed, 570 insertions(+), 31 deletions(-) create mode 100644 app/Actions/Database/StartRedis.php create mode 100644 app/Http/Livewire/Project/Database/Redis/General.php create mode 100644 app/Models/StandaloneRedis.php create mode 100644 database/migrations/2023_10_12_132430_create_standalone_redis_table.php create mode 100644 database/migrations/2023_10_12_132431_add_standalone_redis_to_environment_variables_table.php create mode 100644 database/seeders/StandaloneRedisSeeder.php create mode 100644 resources/views/livewire/project/database/redis/general.blade.php diff --git a/app/Actions/Database/StartRedis.php b/app/Actions/Database/StartRedis.php new file mode 100644 index 000000000..575240869 --- /dev/null +++ b/app/Actions/Database/StartRedis.php @@ -0,0 +1,144 @@ +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(); + $docker_compose = [ + 'version' => '3.8', + 'services' => [ + $container_name => [ + 'image' => $this->database->image, + 'command' => "redis-server --requirepass {$this->database->redis_password} --appendonly yes", + 'container_name' => $container_name, + 'environment' => $environment_variables, + 'restart' => RESTART_MODE, + 'networks' => [ + $this->database->destination->network, + ], + 'healthcheck' => [ + 'test' => [ + 'CMD-SHELL', + 'redis-cli', + 'ping' + ], + '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 (count($this->init_scripts) > 0) { + // foreach ($this->init_scripts as $init_script) { + // $docker_compose['services'][$container_name]['volumes'][] = [ + // 'type' => 'bind', + // 'source' => $init_script, + // 'target' => '/docker-entrypoint-initdb.d/' . basename($init_script), + // '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, $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(); + ray('Generate Environment Variables')->green(); + ray($this->database->runtime_environment_variables)->green(); + 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('REDIS_PASSWORD'))->isEmpty()) { + $environment_variables->push("REDIS_PASSWORD={$this->database->redis_password}"); + } + + return $environment_variables->all(); + } +} diff --git a/app/Http/Controllers/DatabaseController.php b/app/Http/Controllers/DatabaseController.php index 958d6d5ab..e8f85110b 100644 --- a/app/Http/Controllers/DatabaseController.php +++ b/app/Http/Controllers/DatabaseController.php @@ -19,7 +19,7 @@ class DatabaseController extends Controller if (!$environment) { return redirect()->route('dashboard'); } - $database = $environment->databases->where('uuid', request()->route('database_uuid'))->first(); + $database = $environment->databases()->where('uuid', request()->route('database_uuid'))->first(); if (!$database) { return redirect()->route('dashboard'); } @@ -37,7 +37,7 @@ class DatabaseController extends Controller if (!$environment) { return redirect()->route('dashboard'); } - $database = $environment->databases->where('uuid', request()->route('database_uuid'))->first(); + $database = $environment->databases()->where('uuid', request()->route('database_uuid'))->first(); if (!$database) { return redirect()->route('dashboard'); } @@ -64,7 +64,7 @@ class DatabaseController extends Controller if (!$environment) { return redirect()->route('dashboard'); } - $database = $environment->databases->where('uuid', request()->route('database_uuid'))->first(); + $database = $environment->databases()->where('uuid', request()->route('database_uuid'))->first(); if (!$database) { return redirect()->route('dashboard'); } diff --git a/app/Http/Controllers/ProjectController.php b/app/Http/Controllers/ProjectController.php index 7b8cfbfd1..a4db0b1f8 100644 --- a/app/Http/Controllers/ProjectController.php +++ b/app/Http/Controllers/ProjectController.php @@ -59,11 +59,16 @@ class ProjectController extends Controller return redirect()->route('dashboard'); } if (in_array($type, DATABASE_TYPES)) { - $standalone_postgresql = create_standalone_postgresql($environment->id, $destination_uuid); + if ($type->value() === "postgresql") { + $database = create_standalone_postgresql($environment->id, $destination_uuid); + } else if ($type->value() === 'redis') { + $database = create_standalone_redis($environment->id, $destination_uuid); + } + ray($database); return redirect()->route('project.database.configuration', [ 'project_uuid' => $project->uuid, 'environment_name' => $environment->name, - 'database_uuid' => $standalone_postgresql->uuid, + 'database_uuid' => $database->uuid, ]); } if ($type->startsWith('one-click-service-') && !is_null( (int)$server_id)) { diff --git a/app/Http/Livewire/Project/Database/Heading.php b/app/Http/Livewire/Project/Database/Heading.php index 3389ac80a..2c739f087 100644 --- a/app/Http/Livewire/Project/Database/Heading.php +++ b/app/Http/Livewire/Project/Database/Heading.php @@ -3,6 +3,7 @@ namespace App\Http\Livewire\Project\Database; use App\Actions\Database\StartPostgresql; +use App\Actions\Database\StartRedis; use App\Jobs\ContainerStatusJob; use Livewire\Component; @@ -26,6 +27,7 @@ class Heading extends Component { dispatch_sync(new ContainerStatusJob($this->database->destination->server)); $this->database->refresh(); + $this->emit('refresh'); } public function mount() @@ -40,7 +42,7 @@ class Heading extends Component $this->database->destination->server ); if ($this->database->is_public) { - stopPostgresProxy($this->database); + stopDatabaseProxy($this->database); $this->database->is_public = false; } $this->database->status = 'exited'; @@ -55,5 +57,9 @@ class Heading extends Component $activity = resolve(StartPostgresql::class)($this->database->destination->server, $this->database); $this->emit('newMonitorActivity', $activity->id); } + if ($this->database->type() === 'standalone-redis') { + $activity = StartRedis::run($this->database->destination->server, $this->database); + $this->emit('newMonitorActivity', $activity->id); + } } } diff --git a/app/Http/Livewire/Project/Database/Postgresql/General.php b/app/Http/Livewire/Project/Database/Postgresql/General.php index 5a03908e1..bbd1de0ff 100644 --- a/app/Http/Livewire/Project/Database/Postgresql/General.php +++ b/app/Http/Livewire/Project/Database/Postgresql/General.php @@ -67,10 +67,10 @@ class General extends Component } if ($this->database->is_public) { $this->emit('success', 'Starting TCP proxy...'); - startPostgresProxy($this->database); + startDatabaseProxy($this->database); $this->emit('success', 'Database is now publicly accessible.'); } else { - stopPostgresProxy($this->database); + stopDatabaseProxy($this->database); $this->emit('success', 'Database is no longer publicly accessible.'); } $this->getDbUrl(); diff --git a/app/Http/Livewire/Project/Database/Redis/General.php b/app/Http/Livewire/Project/Database/Redis/General.php new file mode 100644 index 000000000..446d720c0 --- /dev/null +++ b/app/Http/Livewire/Project/Database/Redis/General.php @@ -0,0 +1,87 @@ + 'required', + 'database.description' => 'nullable', + 'database.redis_password' => 'required', + '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.redis_password' => 'Postgres User', + '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) { + $this->emit('success', 'Starting TCP proxy...'); + startDatabaseProxy($this->database); + $this->emit('success', 'Database is now publicly accessible.'); + } else { + stopDatabaseProxy($this->database); + $this->emit('success', 'Database is no longer publicly accessible.'); + } + $this->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->getDbUrl(); + } + public function getDbUrl() { + + if ($this->database->is_public) { + $this->db_url = "redis://{$this->database->redis_password}@{$this->database->destination->server->getIp}:{$this->database->public_port}/0"; + } else { + $this->db_url = "redis://{$this->database->redis_password}@{$this->database->uuid}:5432/0"; + } + } + public function render() + { + return view('livewire.project.database.redis.general'); + } +} diff --git a/app/Http/Livewire/Project/Shared/EnvironmentVariable/All.php b/app/Http/Livewire/Project/Shared/EnvironmentVariable/All.php index a95c94977..ce7c5ae40 100644 --- a/app/Http/Livewire/Project/Shared/EnvironmentVariable/All.php +++ b/app/Http/Livewire/Project/Shared/EnvironmentVariable/All.php @@ -75,6 +75,9 @@ class All extends Component case 'standalone-postgresql': $environment->standalone_postgresql_id = $this->resource->id; break; + case 'standalone-redis': + $environment->standalone_redis_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 69848a7c5..15a4e510c 100644 --- a/app/Http/Livewire/Project/Shared/Logs.php +++ b/app/Http/Livewire/Project/Shared/Logs.php @@ -6,12 +6,13 @@ use App\Models\Application; use App\Models\Server; use App\Models\Service; use App\Models\StandalonePostgresql; +use App\Models\StandaloneRedis; use Livewire\Component; class Logs extends Component { public ?string $type = null; - public Application|StandalonePostgresql|Service $resource; + public Application|StandalonePostgresql|Service|StandaloneRedis $resource; public Server $server; public ?string $container = null; public $parameters; @@ -33,7 +34,14 @@ class Logs extends Component } } else if (data_get($this->parameters, 'database_uuid')) { $this->type = 'database'; - $this->resource = StandalonePostgresql::where('uuid', $this->parameters['database_uuid'])->firstOrFail(); + $resource = StandalonePostgresql::where('uuid', $this->parameters['database_uuid'])->first(); + if (is_null($resource)) { + $resource = StandaloneRedis::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; $this->container = $this->resource->uuid; diff --git a/app/Models/Environment.php b/app/Models/Environment.php index 624787ba6..f66bf48f2 100644 --- a/app/Models/Environment.php +++ b/app/Models/Environment.php @@ -14,7 +14,7 @@ class Environment extends Model public function can_delete_environment() { - return $this->applications()->count() == 0 && $this->postgresqls()->count() == 0 && $this->services()->count() == 0; + return $this->applications()->count() == 0 && $this->redis()->count() == 0 && $this->postgresqls()->count() == 0 && $this->services()->count() == 0; } public function applications() @@ -26,10 +26,16 @@ class Environment extends Model { return $this->hasMany(StandalonePostgresql::class); } + public function redis() + { + return $this->hasMany(StandaloneRedis::class); + } public function databases() { - return $this->postgresqls(); + $postgresqls = $this->postgresqls; + $redis = $this->redis; + return $postgresqls->concat($redis); } public function project() diff --git a/app/Models/Project.php b/app/Models/Project.php index 13f86bf66..f8f9622b8 100644 --- a/app/Models/Project.php +++ b/app/Models/Project.php @@ -52,4 +52,8 @@ class Project extends BaseModel { return $this->hasManyThrough(StandalonePostgresql::class, Environment::class); } + public function redis() + { + return $this->hasManyThrough(StandaloneRedis::class, Environment::class); + } } diff --git a/app/Models/Server.php b/app/Models/Server.php index 313040afa..8b1ebc976 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -123,7 +123,9 @@ class Server extends BaseModel { return $this->destinations()->map(function ($standaloneDocker) { $postgresqls = $standaloneDocker->postgresqls; - return $postgresqls?->concat([]) ?? collect([]); + $redis = $standaloneDocker->redis; + return $postgresqls->merge($redis); + // return $postgresqls?->concat([]) ?? collect([]); })->flatten(); } public function applications() diff --git a/app/Models/StandaloneDocker.php b/app/Models/StandaloneDocker.php index 16d85d956..a594b854a 100644 --- a/app/Models/StandaloneDocker.php +++ b/app/Models/StandaloneDocker.php @@ -15,6 +15,10 @@ class StandaloneDocker extends BaseModel { return $this->morphMany(StandalonePostgresql::class, 'destination'); } + public function redis() + { + return $this->morphMany(StandaloneRedis::class, 'destination'); + } public function server() { diff --git a/app/Models/StandaloneRedis.php b/app/Models/StandaloneRedis.php new file mode 100644 index 000000000..f48dc7bb6 --- /dev/null +++ b/app/Models/StandaloneRedis.php @@ -0,0 +1,103 @@ + 'redis-data-' . $database->uuid, + 'mount_path' => '/data', + 'host_path' => null, + 'resource_id' => $database->id, + 'resource_type' => $database->getMorphClass(), + 'is_readonly' => true + ]); + }); + static::deleting(function ($database) { + // Stop Container + instant_remote_process( + ["docker rm -f {$database->uuid}"], + $database->destination->server, + false + ); + // Stop TCP Proxy + if ($database->is_public) { + instant_remote_process(["docker rm -f {$database->uuid}-proxy"], $database->destination->server, false); + } + $database->scheduledBackups()->delete(); + $database->persistentStorages()->delete(); + $database->environment_variables()->delete(); + // Remove Volume + instant_remote_process(['docker volume rm postgres-data-' . $database->uuid], $database->destination->server, false); + }); + } + + public function portsMappings(): Attribute + { + return Attribute::make( + set: fn ($value) => $value === "" ? null : $value, + ); + } + + // Normal Deployments + + public function portsMappingsArray(): Attribute + { + return Attribute::make( + get: fn () => is_null($this->ports_mappings) + ? [] + : explode(',', $this->ports_mappings), + + ); + } + + public function type(): string + { + return 'standalone-redis'; + } + + 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/bootstrap/helpers/constants.php b/bootstrap/helpers/constants.php index c6fbc3c4d..f915ea041 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 3b8e893c7..3c4f0dfd9 100644 --- a/bootstrap/helpers/databases.php +++ b/bootstrap/helpers/databases.php @@ -3,6 +3,7 @@ use App\Models\Server; use App\Models\StandaloneDocker; use App\Models\StandalonePostgresql; +use App\Models\StandaloneRedis; use Visus\Cuid2\Cuid2; function generate_database_name(string $type): string @@ -27,6 +28,21 @@ function create_standalone_postgresql($environment_id, $destination_uuid): Stand ]); } +function create_standalone_redis($environment_id, $destination_uuid): StandaloneRedis +{ + $destination = StandaloneDocker::where('uuid', $destination_uuid)->first(); + if (!$destination) { + throw new Exception('Destination not found'); + } + return StandaloneRedis::create([ + 'name' => generate_database_name('redis'), + 'redis_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. * @param string $filename diff --git a/bootstrap/helpers/proxy.php b/bootstrap/helpers/proxy.php index abada1bda..81cb92c49 100644 --- a/bootstrap/helpers/proxy.php +++ b/bootstrap/helpers/proxy.php @@ -3,6 +3,7 @@ use App\Actions\Proxy\SaveConfiguration; use App\Models\Server; use App\Models\StandalonePostgresql; +use App\Models\StandaloneRedis; use Symfony\Component\Yaml\Yaml; function get_proxy_path() @@ -187,8 +188,14 @@ function setup_default_redirect_404(string|null $redirect_url, Server $server) } } -function startPostgresProxy(StandalonePostgresql $database) +function startDatabaseProxy(StandalonePostgresql|StandaloneRedis $database) { + $internalPort = null; + if ($database->getMorphClass()=== 'App\Models\StandaloneRedis') { + $internalPort = 6379; + } else if ($database->getMorphClass()=== 'App\Models\StandalonePostgresql') { + $internalPort = 5432; + } $containerName = "{$database->uuid}-proxy"; $configuration_dir = database_proxy_dir($database->uuid); $nginxconf = <<public_port; - proxy_pass $database->uuid:5432; + proxy_pass $database->uuid:$internalPort; } } EOF; @@ -260,7 +267,7 @@ EOF; "docker compose --project-directory {$configuration_dir} up --build -d >/dev/null", ], $database->destination->server); } -function stopPostgresProxy(StandalonePostgresql $database) +function stopDatabaseProxy(StandalonePostgresql|StandaloneRedis $database) { instant_remote_process(["docker rm -f {$database->uuid}-proxy"], $database->destination->server); } diff --git a/database/migrations/2023_10_12_132430_create_standalone_redis_table.php b/database/migrations/2023_10_12_132430_create_standalone_redis_table.php new file mode 100644 index 000000000..e6c94dbb6 --- /dev/null +++ b/database/migrations/2023_10_12_132430_create_standalone_redis_table.php @@ -0,0 +1,54 @@ +id(); + $table->string('uuid')->unique(); + $table->string('name'); + $table->string('description')->nullable(); + + $table->text('redis_password'); + $table->longText('redis_conf')->nullable(); + + $table->string('status')->default('exited'); + + $table->string('image')->default('redis:7.2'); + + $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_redis'); + } +}; diff --git a/database/migrations/2023_10_12_132431_add_standalone_redis_to_environment_variables_table.php b/database/migrations/2023_10_12_132431_add_standalone_redis_to_environment_variables_table.php new file mode 100644 index 000000000..2fdacc9e5 --- /dev/null +++ b/database/migrations/2023_10_12_132431_add_standalone_redis_to_environment_variables_table.php @@ -0,0 +1,28 @@ +foreignId('standalone_redis_id')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('environment_variables', function (Blueprint $table) { + $table->dropColumn('standalone_redis_id'); + }); + } +}; diff --git a/database/seeders/StandaloneRedisSeeder.php b/database/seeders/StandaloneRedisSeeder.php new file mode 100644 index 000000000..cbe10bb00 --- /dev/null +++ b/database/seeders/StandaloneRedisSeeder.php @@ -0,0 +1,23 @@ + 'Local PostgreSQL', + 'description' => 'Local PostgreSQL for testing', + 'redis_password' => 'redis', + 'environment_id' => 1, + 'destination_id' => 0, + 'destination_type' => StandaloneDocker::class, + ]); + } +} diff --git a/resources/views/livewire/project/database/redis/general.blade.php b/resources/views/livewire/project/database/redis/general.blade.php new file mode 100644 index 000000000..952e48bef --- /dev/null +++ b/resources/views/livewire/project/database/redis/general.blade.php @@ -0,0 +1,27 @@ +
+
+
+

General

+ + Save + +
+
+ + + +
+
+

Network

+
+ + + +
+ +
+
+
diff --git a/resources/views/livewire/project/new/select.blade.php b/resources/views/livewire/project/new/select.blade.php index 2aa62e83e..5edd410b5 100644 --- a/resources/views/livewire/project/new/select.blade.php +++ b/resources/views/livewire/project/new/select.blade.php @@ -94,6 +94,16 @@ +
+
+
+ New Redis +
+
+ The open source, in-memory data store for cache, streaming engine, and message broker. +
+
+
{{--
diff --git a/resources/views/livewire/project/service/storage.blade.php b/resources/views/livewire/project/service/storage.blade.php index 97a089cf8..0888c1c8e 100644 --- a/resources/views/livewire/project/service/storage.blade.php +++ b/resources/views/livewire/project/service/storage.blade.php @@ -1,7 +1,8 @@
@if ( $resource->getMorphClass() == 'App\Models\Application' || - $resource->getMorphClass() == 'App\Models\StandalonePostgresql') + $resource->getMorphClass() == 'App\Models\StandalonePostgresql' || + $resource->getMorphClass() == 'App\Models\StandaloneRedis')

Storages

@@ -39,6 +37,9 @@ @if ($database->type() === 'standalone-postgresql') @endif + @if ($database->type() === 'standalone-redis') + + @endif
diff --git a/resources/views/project/resources.blade.php b/resources/views/project/resources.blade.php index ac728fc0d..b842f82ee 100644 --- a/resources/views/project/resources.blade.php +++ b/resources/views/project/resources.blade.php @@ -46,7 +46,7 @@
@endforeach - @foreach ($environment->databases->sortBy('name') as $databases) + @foreach ($environment->databases()->sortBy('name') as $databases)
From 8f9949160c208ef658dadae34a065f445d2eb667 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 12 Oct 2023 17:29:29 +0200 Subject: [PATCH 03/17] feat: add custom redis conf --- app/Actions/Database/StartRedis.php | 37 +++++++++++++------ .../Project/Database/Redis/General.php | 7 +++- .../project/database/redis/general.blade.php | 1 + 3 files changed, 33 insertions(+), 12 deletions(-) diff --git a/app/Actions/Database/StartRedis.php b/app/Actions/Database/StartRedis.php index 575240869..fe02b113e 100644 --- a/app/Actions/Database/StartRedis.php +++ b/app/Actions/Database/StartRedis.php @@ -20,6 +20,9 @@ class StartRedis public function handle(Server $server, StandaloneRedis $database) { $this->database = $database; + + $startCommand = "redis-server --requirepass {$this->database->redis_password} --appendonly yes"; + $container_name = $this->database->uuid; $this->configuration_dir = database_configuration_dir() . '/' . $container_name; @@ -31,12 +34,14 @@ class StartRedis $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_redis(); + $docker_compose = [ 'version' => '3.8', 'services' => [ $container_name => [ 'image' => $this->database->image, - 'command' => "redis-server --requirepass {$this->database->redis_password} --appendonly yes", + 'command' => $startCommand, 'container_name' => $container_name, 'environment' => $environment_variables, 'restart' => RESTART_MODE, @@ -80,16 +85,15 @@ class StartRedis if (count($volume_names) > 0) { $docker_compose['volumes'] = $volume_names; } - // if (count($this->init_scripts) > 0) { - // foreach ($this->init_scripts as $init_script) { - // $docker_compose['services'][$container_name]['volumes'][] = [ - // 'type' => 'bind', - // 'source' => $init_script, - // 'target' => '/docker-entrypoint-initdb.d/' . basename($init_script), - // 'read_only' => true, - // ]; - // } - // } + if (!is_null($this->database->redis_conf)) { + $docker_compose['services'][$container_name]['volumes'][] = [ + 'type' => 'bind', + 'source' => $this->configuration_dir . '/redis.conf', + 'target' => '/usr/local/etc/redis/redis.conf', + 'read_only' => true, + ]; + $docker_compose['services'][$container_name]['command'] = $startCommand . ' /usr/local/etc/redis/redis.conf'; + } $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"; @@ -141,4 +145,15 @@ class StartRedis return $environment_variables->all(); } + private function add_custom_redis() + { + if (is_null($this->database->redis_conf)) { + return; + } + $filename = 'redis.conf'; + $content = $this->database->redis_conf; + $content_base64 = base64_encode($content); + $this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}"; + + } } diff --git a/app/Http/Livewire/Project/Database/Redis/General.php b/app/Http/Livewire/Project/Database/Redis/General.php index 446d720c0..c8e15062a 100644 --- a/app/Http/Livewire/Project/Database/Redis/General.php +++ b/app/Http/Livewire/Project/Database/Redis/General.php @@ -16,6 +16,7 @@ class General extends Component protected $rules = [ 'database.name' => 'required', 'database.description' => 'nullable', + 'database.redis_conf' => 'nullable', 'database.redis_password' => 'required', 'database.image' => 'required', 'database.ports_mappings' => 'nullable', @@ -25,7 +26,8 @@ class General extends Component protected $validationAttributes = [ 'database.name' => 'Name', 'database.description' => 'Description', - 'database.redis_password' => 'Postgres User', + 'database.redis_conf' => 'Redis Configuration', + 'database.redis_password' => 'Redis Password', 'database.image' => 'Image', 'database.ports_mappings' => 'Port Mapping', 'database.is_public' => 'Is Public', @@ -34,6 +36,9 @@ class General extends Component public function submit() { try { $this->validate(); + if ($this->database->redis_conf === "") { + $this->database->redis_conf = null; + } $this->database->save(); $this->emit('success', 'Database updated successfully.'); } catch (Exception $e) { diff --git a/resources/views/livewire/project/database/redis/general.blade.php b/resources/views/livewire/project/database/redis/general.blade.php index 952e48bef..a8a44b251 100644 --- a/resources/views/livewire/project/database/redis/general.blade.php +++ b/resources/views/livewire/project/database/redis/general.blade.php @@ -23,5 +23,6 @@
+
From b196c138d95d0e5979a6f9411b20aea617c1434e Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 13 Oct 2023 09:31:44 +0200 Subject: [PATCH 04/17] fix: server ip could be hostname in self-hosted --- app/Http/Livewire/Server/Form.php | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/app/Http/Livewire/Server/Form.php b/app/Http/Livewire/Server/Form.php index f773155d5..62668d6ba 100644 --- a/app/Http/Livewire/Server/Form.php +++ b/app/Http/Livewire/Server/Form.php @@ -21,7 +21,7 @@ class Form extends Component protected $rules = [ 'server.name' => 'required|min:6', 'server.description' => 'nullable', - 'server.ip' => 'required|ip', + 'server.ip' => 'required', 'server.user' => 'required', 'server.port' => 'required', 'server.settings.is_cloudflare_tunnel' => 'required', @@ -45,7 +45,8 @@ class Form extends Component $this->wildcard_domain = $this->server->settings->wildcard_domain; $this->cleanup_after_percentage = $this->server->settings->cleanup_after_percentage; } - public function serverRefresh() { + public function serverRefresh() + { $this->validateServer(); } public function instantSave() @@ -61,7 +62,8 @@ class Form extends Component $activity = InstallDocker::run($this->server); $this->emit('newMonitorActivity', $activity->id); } - public function checkLocalhostConnection() { + public function checkLocalhostConnection() + { $uptime = $this->server->validateConnection(); if ($uptime) { $this->emit('success', 'Server is reachable.'); @@ -80,7 +82,7 @@ class Form extends Component if ($uptime) { $install && $this->emit('success', 'Server is reachable.'); } else { - $install &&$this->emit('error', 'Server is not reachable. Please check your connection and configuration.'); + $install && $this->emit('error', 'Server is not reachable. Please check your connection and configuration.'); return; } $dockerInstalled = $this->server->validateDockerEngine(); @@ -120,7 +122,15 @@ class Form extends Component } public function submit() { - $this->validate(); + $validDomainsForServers = collect(['host.docker.internal', 'coolify-testing-host']); + if ($validDomainsForServers->contains($this->server->ip)) { + $this->validate(); + } else { + $this->validate(); + $this->validate([ + 'server.ip' => 'required|ip', + ]); + } $uniqueIPs = Server::all()->reject(function (Server $server) { return $server->id === $this->server->id; })->pluck('ip')->toArray(); From a6118f5daf0dfa077393366fd5ab9780a5a0a2aa Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 13 Oct 2023 09:34:11 +0200 Subject: [PATCH 05/17] fix --- app/Http/Livewire/Server/Form.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/Http/Livewire/Server/Form.php b/app/Http/Livewire/Server/Form.php index 62668d6ba..816514aeb 100644 --- a/app/Http/Livewire/Server/Form.php +++ b/app/Http/Livewire/Server/Form.php @@ -122,14 +122,13 @@ class Form extends Component } public function submit() { - $validDomainsForServers = collect(['host.docker.internal', 'coolify-testing-host']); - if ($validDomainsForServers->contains($this->server->ip)) { - $this->validate(); - } else { + if(isCloud() && !isDev()) { $this->validate(); $this->validate([ 'server.ip' => 'required|ip', ]); + } else { + $this->validate(); } $uniqueIPs = Server::all()->reject(function (Server $server) { return $server->id === $this->server->id; From 38c6c1ee40d9654636d6baf5a3e646cf0081abff Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 13 Oct 2023 09:36:37 +0200 Subject: [PATCH 06/17] fix: urls should be password fields --- .../livewire/project/database/postgresql/general.blade.php | 2 +- .../views/livewire/project/database/redis/general.blade.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/views/livewire/project/database/postgresql/general.blade.php b/resources/views/livewire/project/database/postgresql/general.blade.php index 9a1e7bb47..61a95f9f8 100644 --- a/resources/views/livewire/project/database/postgresql/general.blade.php +++ b/resources/views/livewire/project/database/postgresql/general.blade.php @@ -62,7 +62,7 @@ label="Public Port" />
- +
diff --git a/resources/views/livewire/project/database/redis/general.blade.php b/resources/views/livewire/project/database/redis/general.blade.php index a8a44b251..ab04e9a09 100644 --- a/resources/views/livewire/project/database/redis/general.blade.php +++ b/resources/views/livewire/project/database/redis/general.blade.php @@ -21,7 +21,7 @@ label="Public Port" />
- +
From c970907c732ab29d2a72ddc77f30c8d61417c851 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 13 Oct 2023 09:39:40 +0200 Subject: [PATCH 07/17] fix: no backup for redis --- app/Http/Controllers/DatabaseController.php | 8 ++++++++ .../views/components/databases/navbar.blade.php | 13 ++++++------- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/app/Http/Controllers/DatabaseController.php b/app/Http/Controllers/DatabaseController.php index e8f85110b..684e427f9 100644 --- a/app/Http/Controllers/DatabaseController.php +++ b/app/Http/Controllers/DatabaseController.php @@ -68,6 +68,14 @@ class DatabaseController extends Controller if (!$database) { return redirect()->route('dashboard'); } + // No backups for redis + if ($database->getMorphClass() === 'App\Models\StandaloneRedis') { + return redirect()->route('project.database.configuration', [ + 'project_uuid' => $project->uuid, + 'environment_name' => $environment->name, + 'database_uuid' => $database->uuid, + ]); + } return view('project.database.backups.all', [ 'database' => $database, 's3s' => currentTeam()->s3s, diff --git a/resources/views/components/databases/navbar.blade.php b/resources/views/components/databases/navbar.blade.php index 6681565ba..84eb2479e 100644 --- a/resources/views/components/databases/navbar.blade.php +++ b/resources/views/components/databases/navbar.blade.php @@ -7,14 +7,13 @@ href="{{ route('project.database.logs', $parameters) }}"> - - - - {{-- --}} + @if ($database->getMorphClass() === 'App\Models\StandalonePostgresql') + + + + @endif
- {{-- --}} - @if ($database->status !== 'exited')
- + From 59eae3a44e72a1ee47732c9ced2c12f5f28a0f7b Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 13 Oct 2023 14:25:30 +0200 Subject: [PATCH 10/17] fix: proxy check for ports, do not kill anything listening on port 80/443 --- app/Actions/Proxy/CheckProxy.php | 42 +++++++++ app/Actions/Proxy/StartProxy.php | 88 +++++++++---------- app/Http/Livewire/Server/Proxy/Deploy.php | 18 +++- app/Http/Livewire/Server/Proxy/Modal.php | 2 - app/Http/Livewire/Server/Proxy/Status.php | 40 +++++++-- app/Jobs/ContainerStatusJob.php | 10 +-- bootstrap/helpers/proxy.php | 2 +- .../livewire/server/proxy/deploy.blade.php | 16 +++- .../livewire/server/proxy/status.blade.php | 6 +- 9 files changed, 152 insertions(+), 72 deletions(-) create mode 100644 app/Actions/Proxy/CheckProxy.php diff --git a/app/Actions/Proxy/CheckProxy.php b/app/Actions/Proxy/CheckProxy.php new file mode 100644 index 000000000..84dc4e955 --- /dev/null +++ b/app/Actions/Proxy/CheckProxy.php @@ -0,0 +1,42 @@ +isProxyShouldRun()) { + throw new \Exception("Proxy should not run"); + } + $status = getContainerStatus($server, 'coolify-proxy'); + if ($status === 'running') { + $server->proxy->set('status', 'running'); + $server->save(); + return 'OK'; + } + $ip = $server->ip; + if ($server->id === 0) { + $ip = 'host.docker.internal'; + } + + $connection = @fsockopen($ip, '80'); + $connection = @fsockopen($ip, '443'); + $port80 = is_resource($connection) && fclose($connection); + $port443 = is_resource($connection) && fclose($connection); + ray($ip); + if ($port80) { + throw new \Exception("Port 80 is in use.
You must stop the process using this port.
Docs: https://coolify.io/docs
Discord: https://coollabs.io/discord"); + } + if ($port443) { + throw new \Exception("Port 443 is in use.
You must stop the process using this port.
Docs: https://coolify.io/docs
Discord: https://coollabs.io/discord>"); + } + } +} diff --git a/app/Actions/Proxy/StartProxy.php b/app/Actions/Proxy/StartProxy.php index d6ae7eec8..14b7fefb5 100644 --- a/app/Actions/Proxy/StartProxy.php +++ b/app/Actions/Proxy/StartProxy.php @@ -2,7 +2,6 @@ namespace App\Actions\Proxy; -use App\Enums\ProxyTypes; use App\Models\Server; use Illuminate\Support\Str; use Lorisleiva\Actions\Concerns\AsAction; @@ -11,56 +10,49 @@ use Spatie\Activitylog\Models\Activity; class StartProxy { use AsAction; - public function handle(Server $server, bool $async = true): Activity|string + public function handle(Server $server, bool $async = true): string|Activity { - $commands = collect([]); - $proxyType = $server->proxyType(); - if ($proxyType === ProxyTypes::NONE->value) { - return 'OK'; - } - $proxy_path = get_proxy_path(); - $configuration = CheckConfiguration::run($server); - if (!$configuration) { - throw new \Exception("Configuration is not synced"); - } - SaveConfiguration::run($server, $configuration); - $docker_compose_yml_base64 = base64_encode($configuration); - $server->proxy->last_applied_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value; - $server->save(); + try { + CheckProxy::run($server); - $commands = $commands->merge([ - "apt-get update > /dev/null 2>&1 || true", - "command -v lsof >/dev/null || echo '####### Installing lsof.'", - "command -v lsof >/dev/null || apt install -y lsof", - "command -v lsof >/dev/null || command -v fuser >/dev/null || apt install -y psmisc", - "mkdir -p $proxy_path && cd $proxy_path", - "echo '####### Creating Docker Compose file.'", - "echo '####### Pulling docker image.'", - 'docker compose pull', - "echo '####### Stopping existing coolify-proxy.'", - "docker compose down -v --remove-orphans > /dev/null 2>&1", - "command -v fuser >/dev/null || command -v lsof >/dev/null || echo '####### Could not kill existing processes listening on port 80 & 443. Please stop the process holding these ports...'", - "command -v lsof >/dev/null && lsof -nt -i:80 | xargs -r kill -9 || true", - "command -v lsof >/dev/null && lsof -nt -i:443 | xargs -r kill -9 || true", - "command -v fuser >/dev/null && fuser -k 80/tcp || true", - "command -v fuser >/dev/null && fuser -k 443/tcp || true", - "systemctl disable nginx > /dev/null 2>&1 || true", - "systemctl disable apache2 > /dev/null 2>&1 || true", - "systemctl disable apache > /dev/null 2>&1 || true", - "echo '####### Starting coolify-proxy.'", - 'docker compose up -d --remove-orphans', - "echo '####### Proxy installed successfully.'" - ]); - $commands = $commands->merge(connectProxyToNetworks($server)); - if ($async) { - $activity = remote_process($commands, $server); - return $activity; - } else { - instant_remote_process($commands, $server); - $server->proxy->set('status', 'running'); - $server->proxy->set('type', $proxyType); + $proxyType = $server->proxyType(); + $commands = collect([]); + $proxy_path = get_proxy_path(); + $configuration = CheckConfiguration::run($server); + if (!$configuration) { + throw new \Exception("Configuration is not synced"); + } + SaveConfiguration::run($server, $configuration); + $docker_compose_yml_base64 = base64_encode($configuration); + $server->proxy->last_applied_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value; $server->save(); - return 'OK'; + $commands = $commands->merge([ + "mkdir -p $proxy_path && cd $proxy_path", + "echo 'Creating required Docker Compose file.'", + "echo 'Pulling docker image.'", + 'docker compose pull', + "echo 'Stopping existing coolify-proxy.'", + "docker compose down -v --remove-orphans > /dev/null 2>&1", + "echo 'Starting coolify-proxy.'", + 'docker compose up -d --remove-orphans', + "echo 'Proxy started successfully.'" + ]); + $commands = $commands->merge(connectProxyToNetworks($server)); + if ($async) { + $activity = remote_process($commands, $server); + return $activity; + } else { + instant_remote_process($commands, $server); + $server->proxy->set('status', 'running'); + $server->proxy->set('type', $proxyType); + $server->save(); + return 'OK'; + } + } catch(\Throwable $e) { + ray($e); + throw $e; } + + } } diff --git a/app/Http/Livewire/Server/Proxy/Deploy.php b/app/Http/Livewire/Server/Proxy/Deploy.php index ad5884e18..44ff4b917 100644 --- a/app/Http/Livewire/Server/Proxy/Deploy.php +++ b/app/Http/Livewire/Server/Proxy/Deploy.php @@ -2,6 +2,7 @@ namespace App\Http\Livewire\Server\Proxy; +use App\Actions\Proxy\CheckProxy; use App\Actions\Proxy\StartProxy; use App\Models\Server; use Livewire\Component; @@ -11,18 +12,29 @@ class Deploy extends Component public Server $server; public bool $traefikDashboardAvailable = false; public ?string $currentRoute = null; - protected $listeners = ['proxyStatusUpdated', 'traefikDashboardAvailable', 'serverRefresh' => 'proxyStatusUpdated']; + protected $listeners = ['proxyStatusUpdated', 'traefikDashboardAvailable', 'serverRefresh' => 'proxyStatusUpdated', "checkProxy", "startProxy"]; - public function mount() { + public function mount() + { $this->currentRoute = request()->route()->getName(); } - public function traefikDashboardAvailable(bool $data) { + public function traefikDashboardAvailable(bool $data) + { $this->traefikDashboardAvailable = $data; } public function proxyStatusUpdated() { $this->server->refresh(); } + public function checkProxy() { + try { + CheckProxy::run($this->server); + $this->emit('startProxyPolling'); + $this->emit('proxyChecked'); + } catch (\Throwable $e) { + return handleError($e, $this); + } + } public function startProxy() { try { diff --git a/app/Http/Livewire/Server/Proxy/Modal.php b/app/Http/Livewire/Server/Proxy/Modal.php index ef9948910..2674abe3d 100644 --- a/app/Http/Livewire/Server/Proxy/Modal.php +++ b/app/Http/Livewire/Server/Proxy/Modal.php @@ -11,8 +11,6 @@ class Modal extends Component public function proxyStatusUpdated() { - $this->server->proxy->set('status', 'running'); - $this->server->save(); $this->emit('proxyStatusUpdated'); } } diff --git a/app/Http/Livewire/Server/Proxy/Status.php b/app/Http/Livewire/Server/Proxy/Status.php index 5cfd22082..90deb1455 100644 --- a/app/Http/Livewire/Server/Proxy/Status.php +++ b/app/Http/Livewire/Server/Proxy/Status.php @@ -2,6 +2,7 @@ namespace App\Http\Livewire\Server\Proxy; +use App\Actions\Proxy\CheckProxy; use App\Jobs\ContainerStatusJob; use App\Models\Server; use Livewire\Component; @@ -9,12 +10,42 @@ use Livewire\Component; class Status extends Component { public Server $server; + public bool $polling = false; + public int $numberOfPolls = 0; - protected $listeners = ['proxyStatusUpdated']; + protected $listeners = ['proxyStatusUpdated', 'startProxyPolling']; + public function startProxyPolling() + { + $this->polling = true; + } public function proxyStatusUpdated() { $this->server->refresh(); } + public function checkProxy(bool $notification = false) + { + try { + if ($this->polling) { + if ($this->numberOfPolls >= 10) { + $this->polling = false; + $this->numberOfPolls = 0; + $notification && $this->emit('error', 'Proxy is not running.'); + return; + } + $this->numberOfPolls++; + } + CheckProxy::run($this->server); + $this->emit('proxyStatusUpdated'); + if ($this->server->proxy->status === 'running') { + $this->polling = false; + $notification && $this->emit('success', 'Proxy is running.'); + } else { + $notification && $this->emit('error', 'Proxy is not running.'); + } + } catch (\Throwable $e) { + return handleError($e, $this); + } + } public function getProxyStatus() { try { @@ -24,11 +55,4 @@ class Status extends Component return handleError($e, $this); } } - public function getProxyStatusWithNoti() - { - if ($this->server->isFunctional()) { - $this->emit('success', 'Refreshed proxy status.'); - $this->getProxyStatus(); - } - } } diff --git a/app/Jobs/ContainerStatusJob.php b/app/Jobs/ContainerStatusJob.php index 9b4ef5699..00e6b31fb 100644 --- a/app/Jobs/ContainerStatusJob.php +++ b/app/Jobs/ContainerStatusJob.php @@ -27,11 +27,6 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted public $tries = 1; public $timeout = 120; - public function __construct(public Server $server) - { - - } - public function middleware(): array { return [(new WithoutOverlapping($this->server->uuid))->dontRelease()]; @@ -41,6 +36,11 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted { return $this->server->uuid; } + + public function __construct(public Server $server) + { + } + public function handle() { try { diff --git a/bootstrap/helpers/proxy.php b/bootstrap/helpers/proxy.php index 81cb92c49..08f134d15 100644 --- a/bootstrap/helpers/proxy.php +++ b/bootstrap/helpers/proxy.php @@ -22,7 +22,7 @@ function connectProxyToNetworks(Server $server) } $commands = $networks->map(function ($network) { return [ - "echo '####### Connecting coolify-proxy to $network network...'", + "echo 'Connecting coolify-proxy to $network network...'", "docker network ls --format '{{.Name}}' | grep '^$network$' >/dev/null || docker network create --attachable $network >/dev/null", "docker network connect $network coolify-proxy >/dev/null 2>&1 || true", ]; diff --git a/resources/views/livewire/server/proxy/deploy.blade.php b/resources/views/livewire/server/proxy/deploy.blade.php index 41444fde4..933133408 100644 --- a/resources/views/livewire/server/proxy/deploy.blade.php +++ b/resources/views/livewire/server/proxy/deploy.blade.php @@ -20,8 +20,9 @@ @endif - + @@ -30,7 +31,7 @@ @else -