From 77e3208f00562712536eb131e7c4642a163999c2 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 7 Sep 2023 13:23:34 +0200 Subject: [PATCH] feat: public database --- .gitignore | 1 + .../Livewire/Project/Database/Heading.php | 5 ++ .../Project/Database/Postgresql/General.php | 38 ++++++++++ bootstrap/helpers/databases.php | 2 +- bootstrap/helpers/proxy.php | 73 +++++++++++++++++++ bootstrap/helpers/shared.php | 4 + .../views/components/forms/checkbox.blade.php | 2 +- .../database/postgresql/general.blade.php | 9 ++- resources/views/vendor/toaster/hub.blade.php | 4 +- 9 files changed, 132 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index d75e3c80f..ac8a1e090 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,4 @@ _ide_helper.php _ide_helper_models.php .rnd /.ssh +scripts/load-test/* diff --git a/app/Http/Livewire/Project/Database/Heading.php b/app/Http/Livewire/Project/Database/Heading.php index 347c439a6..178cbfca5 100644 --- a/app/Http/Livewire/Project/Database/Heading.php +++ b/app/Http/Livewire/Project/Database/Heading.php @@ -42,8 +42,13 @@ public function stop() ["docker rm -f {$this->database->uuid}"], $this->database->destination->server ); + if ($this->database->is_public) { + stopPostgresProxy($this->database); + $this->database->is_public = false; + } $this->database->status = 'stopped'; $this->database->save(); + $this->emit('refresh'); // $this->database->environment->project->team->notify(new StatusChanged($this->database)); } diff --git a/app/Http/Livewire/Project/Database/Postgresql/General.php b/app/Http/Livewire/Project/Database/Postgresql/General.php index c58bd51e3..da9470783 100644 --- a/app/Http/Livewire/Project/Database/Postgresql/General.php +++ b/app/Http/Livewire/Project/Database/Postgresql/General.php @@ -12,6 +12,7 @@ class General extends Component public StandalonePostgresql $database; public string $new_filename; public string $new_content; + public string $db_url; protected $listeners = ['refresh', 'save_init_script', 'delete_init_script']; @@ -26,6 +27,8 @@ class General extends Component 'database.init_scripts' => 'nullable', 'database.image' => 'required', 'database.ports_mappings' => 'nullable', + 'database.is_public' => 'nullable|boolean', + 'database.public_port' => 'nullable|integer', ]; protected $validationAttributes = [ 'database.name' => 'Name', @@ -38,8 +41,43 @@ class General extends Component 'database.init_scripts' => 'Init Scripts', 'database.image' => 'Image', 'database.ports_mappings' => 'Port Mapping', + 'database.is_public' => 'Is Public', + 'database.public_port' => 'Public Port', ]; + public function mount() + { + $this->getDbUrl(); + } + public function getDbUrl() { + if ($this->database->is_public) { + $this->db_url = "postgres://{$this->database->postgres_user}:{$this->database->postgres_password}@{$this->database->destination->server->ip}:{$this->database->public_port}/{$this->database->postgres_db}"; + } else { + $this->db_url = "postgres://{$this->database->postgres_user}:{$this->database->postgres_password}@{$this->database->uuid}:5432/{$this->database->postgres_db}"; + } + } + 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) { + startPostgresProxy($this->database); + $this->emit('success', 'Database is now publicly accessible.'); + } else { + stopPostgresProxy($this->database); + $this->emit('success', 'Database is no longer publicly accessible.'); + } + $this->getDbUrl(); + $this->database->save(); + } catch(Exception $e) { + $this->database->is_public = !$this->database->is_public; + return general_error_handler(err: $e, that: $this); + } + } public function save_init_script($script) { $this->database->init_scripts = filter($this->database->init_scripts, fn ($s) => $s['filename'] !== $script['filename']); diff --git a/bootstrap/helpers/databases.php b/bootstrap/helpers/databases.php index c005ad15d..3b8e893c7 100644 --- a/bootstrap/helpers/databases.php +++ b/bootstrap/helpers/databases.php @@ -20,7 +20,7 @@ function create_standalone_postgresql($environment_id, $destination_uuid): Stand } return StandalonePostgresql::create([ 'name' => generate_database_name('postgresql'), - 'postgres_password' => \Illuminate\Support\Str::password(), + 'postgres_password' => \Illuminate\Support\Str::password(symbols: false), 'environment_id' => $environment_id, 'destination_id' => $destination->id, 'destination_type' => $destination->getMorphClass(), diff --git a/bootstrap/helpers/proxy.php b/bootstrap/helpers/proxy.php index 164c46e94..c78a560c7 100644 --- a/bootstrap/helpers/proxy.php +++ b/bootstrap/helpers/proxy.php @@ -1,6 +1,7 @@ uuid}-proxy"; + $configuration_dir = database_proxy_dir($database->uuid); + $nginxconf = <<public_port; + proxy_pass $database->uuid:5432; + } +} +EOF; + $docker_compose = [ + 'version' => '3.8', + 'services' => [ + $containerName => [ + 'image' => "nginx:stable-alpine", + 'container_name' => $containerName, + 'restart' => RESTART_MODE, + 'volumes' => [ + "$configuration_dir/nginx.conf:/etc/nginx/nginx.conf:ro", + ], + 'ports' => [ + "$database->public_port:$database->public_port", + ], + 'networks' => [ + $database->destination->network, + ], + 'healthcheck' => [ + 'test' => [ + 'CMD-SHELL', + 'stat /etc/nginx/nginx.conf || exit 1', + ], + 'interval' => '5s', + 'timeout' => '5s', + 'retries' => 3, + 'start_period' => '1s' + ], + ] + ], + 'networks' => [ + $database->destination->network => [ + 'external' => true, + 'name' => $database->destination->network, + 'attachable' => true, + ] + ] + ]; + $dockercompose_base64 = base64_encode(Yaml::dump($docker_compose, 4, 2)); + $nginxconf_base64 = base64_encode($nginxconf); + instant_remote_process([ + "mkdir -p $configuration_dir", + "echo '{$nginxconf_base64}' | base64 -d > $configuration_dir/nginx.conf", + "echo '{$dockercompose_base64}' | base64 -d > $configuration_dir/docker-compose.yaml", + "docker compose --project-directory {$configuration_dir} up -d >/dev/null", + + + ], $database->destination->server); +} +function stopPostgresProxy(StandalonePostgresql $database) +{ + instant_remote_process(["docker rm -f {$database->uuid}-proxy"], $database->destination->server); +} diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index ac8beecf3..15f8fc0db 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -28,6 +28,10 @@ function database_configuration_dir(): string { return '/data/coolify/databases'; } +function database_proxy_dir($uuid): string +{ + return "/data/coolify/databases/$uuid/proxy"; +} function backup_dir(): string { diff --git a/resources/views/components/forms/checkbox.blade.php b/resources/views/components/forms/checkbox.blade.php index 0b7499450..de4abb87a 100644 --- a/resources/views/components/forms/checkbox.blade.php +++ b/resources/views/components/forms/checkbox.blade.php @@ -24,7 +24,7 @@ class="w-4 h-4 stroke-current"> @endif merge(['class' => $defaultClass]) }} - @if ($instantSave) wire:click='{{ $instantSave === 'instantSave' || $instantSave == '1' ? 'instantSave' : $instantSave }}' + @if ($instantSave) wire:loading.attr="disabled" wire:click='{{ $instantSave === 'instantSave' || $instantSave == '1' ? 'instantSave' : $instantSave }}' wire:model.defer={{ $id }} @else wire:model.defer={{ $value ?? $id }} @endif /> diff --git a/resources/views/livewire/project/database/postgresql/general.blade.php b/resources/views/livewire/project/database/postgresql/general.blade.php index 7fe29c2bf..8b184f024 100644 --- a/resources/views/livewire/project/database/postgresql/general.blade.php +++ b/resources/views/livewire/project/database/postgresql/general.blade.php @@ -51,10 +51,15 @@ -
+

Network

- + + + +
+
diff --git a/resources/views/vendor/toaster/hub.blade.php b/resources/views/vendor/toaster/hub.blade.php index 5aff1b506..3bca749b6 100644 --- a/resources/views/vendor/toaster/hub.blade.php +++ b/resources/views/vendor/toaster/hub.blade.php @@ -22,7 +22,7 @@ class="relative flex duration-300 transform transition ease-in-out max-w-md w-full pointer-events-auto {{ $position->is('center') ? 'text-center' : 'text-left' }}" :class="toast.select({ error: 'text-white', info: 'text-white', success: 'text-white', warning: 'text-white' })" > -
- + @if ($closeable)