diff --git a/app/Actions/Database/StartPostgresql.php b/app/Actions/Database/StartPostgresql.php index b3e7dce5f..444af33b3 100644 --- a/app/Actions/Database/StartPostgresql.php +++ b/app/Actions/Database/StartPostgresql.php @@ -9,30 +9,36 @@ use Symfony\Component\Yaml\Yaml; class StartPostgresql { - public $database; + public StandalonePostgresql $database; + public array $commands = []; + public array $init_scripts = []; + public string $base_dir; public function __invoke(Server $server, StandalonePostgresql $database) { $this->database = $database; - $container_name = generate_container_name($this->database->uuid); - $destination = $this->database->destination; - $image = $this->database->image; + $this->base_dir = '/data/coolify/databases/' . $container_name; + + $this->commands = [ + "mkdir -p $this->base_dir", + "mkdir -p $this->base_dir/docker-entrypoint-initdb.d/" + ]; $persistent_storages = $this->generate_local_persistent_volumes(); $volume_names = $this->generate_local_persistent_volumes_only_volume_names(); $environment_variables = $this->generate_environment_variables(); - + $this->generate_init_scripts(); $docker_compose = [ 'version' => '3.8', 'services' => [ $container_name => [ - 'image' => $image, + 'image' => $this->database->image, 'container_name' => $container_name, 'environment' => $environment_variables, 'restart' => 'always', 'networks' => [ - $destination->network, + $this->database->destination->network, ], 'healthcheck' => [ 'test' => [ @@ -58,29 +64,37 @@ class StartPostgresql ] ], 'networks' => [ - $destination->network => [ + $this->database->destination->network => [ 'external' => false, - 'name' => $destination->network, + '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); - $activity = remote_process([ - "mkdir -p /tmp/{$container_name}", - "echo '{$docker_compose_base64}' | base64 -d > /tmp/{$container_name}/docker-compose.yml", - "docker compose -f /tmp/{$container_name}/docker-compose.yml up -d", - - ], $server); - return $activity; + $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->base_dir/docker-compose.yml"; + $this->commands[] = "docker compose -f $this->base_dir/docker-compose.yml up -d"; + return remote_process($this->commands, $server); } private function generate_local_persistent_volumes() @@ -131,4 +145,18 @@ class StartPostgresql } return $environment_variables->all(); } + + private function generate_init_scripts() + { + if (count($this->database->init_scripts) === 0) { + return; + } + foreach ($this->database->init_scripts as $init_script) { + $filename = data_get($init_script, 'filename'); + $content = data_get($init_script, 'content'); + $content_base64 = base64_encode($content); + $this->commands[] = "echo '{$content_base64}' | base64 -d > $this->base_dir/docker-entrypoint-initdb.d/{$filename}"; + $this->init_scripts[] = "$this->base_dir/docker-entrypoint-initdb.d/{$filename}"; + } + } } diff --git a/app/Http/Controllers/DatabaseController.php b/app/Http/Controllers/DatabaseController.php index 33da43d83..8cda4f1f2 100644 --- a/app/Http/Controllers/DatabaseController.php +++ b/app/Http/Controllers/DatabaseController.php @@ -23,7 +23,6 @@ class DatabaseController extends Controller if (!$database) { return redirect()->route('dashboard'); } - ray($database->persistentStorages()->get()); return view('project.database.configuration', ['database' => $database]); } } diff --git a/app/Http/Livewire/Project/Database/InitScript.php b/app/Http/Livewire/Project/Database/InitScript.php new file mode 100644 index 000000000..ba70b75d3 --- /dev/null +++ b/app/Http/Livewire/Project/Database/InitScript.php @@ -0,0 +1,47 @@ + 'required|string', + 'content' => 'required|string', + ]; + protected $validationAttributes = [ + 'filename' => 'Filename', + 'content' => 'Content', + ]; + + public function mount() + { + $this->index = data_get($this->script, 'index'); + $this->filename = data_get($this->script, 'filename'); + $this->content = data_get($this->script, 'content'); + } + + public function submit() + { + $this->validate(); + try { + $this->script['index'] = $this->index; + $this->script['content'] = $this->content; + $this->script['filename'] = $this->filename; + $this->emitUp('save_init_script', $this->script); + } catch (Exception $e) { + return general_error_handler(err: $e, that: $this); + } + } + + public function delete() + { + $this->emitUp('delete_init_script', $this->script); + } +} diff --git a/app/Http/Livewire/Project/Database/Postgresql/General.php b/app/Http/Livewire/Project/Database/Postgresql/General.php index 9a0e0d22f..5977194cb 100644 --- a/app/Http/Livewire/Project/Database/Postgresql/General.php +++ b/app/Http/Livewire/Project/Database/Postgresql/General.php @@ -2,12 +2,18 @@ namespace App\Http\Livewire\Project\Database\Postgresql; +use App\Models\StandalonePostgresql; +use Exception; use Livewire\Component; +use function Aws\filter; class General extends Component { - public $database; - protected $listeners = ['refresh']; + public StandalonePostgresql $database; + public string $new_filename; + public string $new_content; + + protected $listeners = ['refresh', 'save_init_script', 'delete_init_script']; protected $rules = [ 'database.name' => 'required', @@ -19,6 +25,7 @@ class General extends Component 'database.postgres_host_auth_method' => 'nullable', 'database.init_scripts' => 'nullable', 'database.image' => 'required', + 'database.ports_mappings' => 'nullable', ]; protected $validationAttributes = [ 'database.name' => 'Name', @@ -30,20 +37,67 @@ class General extends Component 'database.postgres_host_auth_method' => 'Postgres Host Auth Method', 'database.init_scripts' => 'Init Scripts', 'database.image' => 'Image', + 'database.ports_mappings' => 'Port Mapping', ]; + public function save_init_script($script) + { + $this->database->init_scripts = filter($this->database->init_scripts, fn($s) => $s['filename'] !== $script['filename']); + $this->database->init_scripts = array_merge($this->database->init_scripts, [$script]); + $this->database->save(); + $this->emit('success', 'Init script saved successfully.'); + } + + public function delete_init_script($script) + { + $collection = collect($this->database->init_scripts); + $found = $collection->firstWhere('filename', $script['filename']); + if ($found) { + ray($collection->filter(fn($s) => $s['filename'] !== $script['filename'])->toArray()); + $this->database->init_scripts = $collection->filter(fn($s) => $s['filename'] !== $script['filename'])->toArray(); + $this->database->save(); + $this->refresh(); + $this->emit('success', 'Init script deleted successfully.'); + return; + } + } + public function refresh(): void { $this->database->refresh(); } + public function save_new_init_script() + { + $this->validate([ + 'new_filename' => 'required|string', + 'new_content' => 'required|string', + ]); + $found = collect($this->database->init_scripts)->firstWhere('filename', $this->new_filename); + if ($found) { + $this->emit('error', 'Filename already exists.'); + return; + } + $this->database->init_scripts = array_merge($this->database->init_scripts, [ + [ + 'index' => count($this->database->init_scripts), + 'filename' => $this->new_filename, + 'content' => $this->new_content, + ] + ]); + $this->database->save(); + $this->emit('success', 'Init script added successfully.'); + $this->new_content = ''; + $this->new_filename = ''; + } + public function submit() { try { $this->validate(); $this->database->save(); $this->emit('success', 'Database updated successfully.'); - } catch (\Exception $e) { + } catch (Exception $e) { return general_error_handler(err: $e, that: $this); } } diff --git a/app/Models/StandalonePostgresql.php b/app/Models/StandalonePostgresql.php index e48100c59..d4a225d2c 100644 --- a/app/Models/StandalonePostgresql.php +++ b/app/Models/StandalonePostgresql.php @@ -2,6 +2,7 @@ namespace App\Models; +use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\HasMany; @@ -11,6 +12,7 @@ class StandalonePostgresql extends BaseModel protected $guarded = []; protected $casts = [ + 'init_scripts' => 'array', 'postgres_password' => 'encrypted', ]; @@ -28,6 +30,25 @@ class StandalonePostgresql extends BaseModel }); } + 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() { return 'standalone-postgresql'; diff --git a/app/View/Components/Forms/Textarea.php b/app/View/Components/Forms/Textarea.php index 5a017c56b..7c4d6ae3c 100644 --- a/app/View/Components/Forms/Textarea.php +++ b/app/View/Components/Forms/Textarea.php @@ -24,7 +24,7 @@ class Textarea extends Component public bool $disabled = false, public bool $readonly = false, public string|null $helper = null, - public string $defaultClass = "textarea bg-coolgray-200 rounded text-white scrollbar disabled:bg-coolgray-200/50 disabled:border-none" + public string $defaultClass = "textarea bg-coolgray-200 rounded text-white scrollbar disabled:bg-coolgray-200/50 disabled:border-none placeholder:text-coolgray-500" ) { // diff --git a/database/migrations/2023_08_08_114038_add_port_mappings_to_standalone_postgresqls.php b/database/migrations/2023_08_08_114038_add_port_mappings_to_standalone_postgresqls.php new file mode 100644 index 000000000..33d6ec8e9 --- /dev/null +++ b/database/migrations/2023_08_08_114038_add_port_mappings_to_standalone_postgresqls.php @@ -0,0 +1,21 @@ +text('ports_mappings')->nullable(); + }); + } + + public function down(): void + { + Schema::table('standalone_postgresqls', function (Blueprint $table) { + $table->dropColumn('ports_mappings'); + }); + } +}; diff --git a/database/seeders/StandalonePostgresqlSeeder.php b/database/seeders/StandalonePostgresqlSeeder.php index 51f249f2a..fe6010247 100644 --- a/database/seeders/StandalonePostgresqlSeeder.php +++ b/database/seeders/StandalonePostgresqlSeeder.php @@ -8,9 +8,6 @@ use Illuminate\Database\Seeder; class StandalonePostgresqlSeeder extends Seeder { - /** - * Run the database seeds. - */ public function run(): void { StandalonePostgresql::create([ @@ -20,6 +17,13 @@ class StandalonePostgresqlSeeder extends Seeder 'environment_id' => 1, 'destination_id' => 1, 'destination_type' => StandaloneDocker::class, + 'init_scripts' => [ + [ + 'index' => 0, + 'filename' => 'init_test_db.sql', + 'content' => 'CREATE DATABASE test;' + ] + ] ]); } } diff --git a/resources/views/components/forms/textarea.blade.php b/resources/views/components/forms/textarea.blade.php index 6caae7716..a9b9ff07c 100644 --- a/resources/views/components/forms/textarea.blade.php +++ b/resources/views/components/forms/textarea.blade.php @@ -29,7 +29,8 @@ @endif - @error($id) diff --git a/resources/views/livewire/activity-monitor.blade.php b/resources/views/livewire/activity-monitor.blade.php index 6a4ad54df..6497c2b08 100644 --- a/resources/views/livewire/activity-monitor.blade.php +++ b/resources/views/livewire/activity-monitor.blade.php @@ -14,8 +14,6 @@
{{ RunRemoteProcess::decodeOutput($this->activity) }}
- {{-- @else -
Output will be here...
--}} @endif diff --git a/resources/views/livewire/project/database/init-script.blade.php b/resources/views/livewire/project/database/init-script.blade.php new file mode 100644 index 000000000..5c60ce68f --- /dev/null +++ b/resources/views/livewire/project/database/init-script.blade.php @@ -0,0 +1,10 @@ +
+
+
+ + Save + Delete +
+ + +
diff --git a/resources/views/livewire/project/database/postgresql/general.blade.php b/resources/views/livewire/project/database/postgresql/general.blade.php index 021d2b3d6..ab99311a7 100644 --- a/resources/views/livewire/project/database/postgresql/general.blade.php +++ b/resources/views/livewire/project/database/postgresql/general.blade.php @@ -1,4 +1,18 @@
+ + +

General

@@ -11,28 +25,50 @@ -
@if ($database->started_at) - + readonly helper="You can only modify it before the initial start."/> + + readonly + helper="You can only modify it before the initial start."/> @else - - + + + placeholder="If empty, it will be the same as Username." + helper="You can only modify it before the initial start."/> @endif -
- - +
+ + +
+
+

Network

+ +
+
+
+

Initialization scripts

+ + Add +
+
+ @forelse(data_get($database,'init_scripts') as $script) + + @empty +
No initialization scripts found.
+ @endforelse +
+