diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..3ded74ce3 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1 @@ +> Always use `next` branch as destination branch for PRs, not `main` diff --git a/CONTRIBUTION.md b/CONTRIBUTION.md index c139793ec..02a21573c 100644 --- a/CONTRIBUTION.md +++ b/CONTRIBUTION.md @@ -30,5 +30,5 @@ Your horizon (Laravel scheduler): `localhost:8000/horizon` - Only reachable if y Mails are caught by Mailpit: `localhost:8025` ## New Service Contribution -Check out the docs [here](https://coolify.io/docs/resources/services/add-service). +Check out the docs [here](https://coolify.io/docs/knowledge-base/add-a-service). diff --git a/app/Console/Commands/CleanupStuckedResources.php b/app/Console/Commands/CleanupStuckedResources.php index 32ba67a1e..afc00140c 100644 --- a/app/Console/Commands/CleanupStuckedResources.php +++ b/app/Console/Commands/CleanupStuckedResources.php @@ -62,7 +62,7 @@ class CleanupStuckedResources extends Command $keydbs = StandaloneKeydb::withTrashed()->whereNotNull('deleted_at')->get(); foreach ($keydbs as $keydb) { echo "Deleting stuck keydb: {$keydb->name}\n"; - $redis->forceDelete(); + $keydb->forceDelete(); } } catch (\Throwable $e) { echo "Error in cleaning stuck keydb: {$e->getMessage()}\n"; @@ -71,7 +71,7 @@ class CleanupStuckedResources extends Command $dragonflies = StandaloneDragonfly::withTrashed()->whereNotNull('deleted_at')->get(); foreach ($dragonflies as $dragonfly) { echo "Deleting stuck dragonfly: {$dragonfly->name}\n"; - $redis->forceDelete(); + $dragonfly->forceDelete(); } } catch (\Throwable $e) { echo "Error in cleaning stuck dragonfly: {$e->getMessage()}\n"; @@ -80,7 +80,7 @@ class CleanupStuckedResources extends Command $clickhouses = StandaloneClickhouse::withTrashed()->whereNotNull('deleted_at')->get(); foreach ($clickhouses as $clickhouse) { echo "Deleting stuck clickhouse: {$clickhouse->name}\n"; - $redis->forceDelete(); + $clickhouse->forceDelete(); } } catch (\Throwable $e) { echo "Error in cleaning stuck clickhouse: {$e->getMessage()}\n"; diff --git a/app/Console/Commands/Init.php b/app/Console/Commands/Init.php index acdff5b35..06414f715 100644 --- a/app/Console/Commands/Init.php +++ b/app/Console/Commands/Init.php @@ -20,6 +20,7 @@ class Init extends Command public function handle() { $this->alive(); + get_public_ips(); $full_cleanup = $this->option('full-cleanup'); $cleanup_deployments = $this->option('cleanup-deployments'); if ($cleanup_deployments) { @@ -56,6 +57,7 @@ class Init extends Command $this->cleanup_stucked_helper_containers(); $this->call('cleanup:stucked-resources'); } + private function restore_coolify_db_backup() { try { diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 52afdd47e..1717c5d08 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -710,7 +710,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted private function save_environment_variables() { $envs = collect([]); - $ports = $this->application->settings->is_static ? [80] : $this->application->ports_exposes_array; + $ports = $this->application->main_port(); if ($this->pull_request_id !== 0) { $this->env_filename = ".env-pr-$this->pull_request_id"; foreach ($this->application->environment_variables_preview as $env) { @@ -718,22 +718,24 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted if ($env->version === '4.0.0-beta.239') { $real_value = $env->real_value; } else { - $real_value = escapeEnvVariables($env->real_value); - } - if ($env->is_literal) { - $real_value = '\'' . $real_value . '\''; + if ($env->is_literal) { + $real_value = '\'' . $real_value . '\''; + } else { + $real_value = escapeEnvVariables($env->real_value); + } } $envs->push($env->key . '=' . $real_value); } // Add PORT if not exists, use the first port as default - if ($this->application->environment_variables_preview->filter(fn ($env) => Str::of($env)->startsWith('PORT'))->isEmpty()) { + if ($this->application->environment_variables_preview->where('key', 'PORT')->isEmpty()) { $envs->push("PORT={$ports[0]}"); } // Add HOST if not exists - if ($this->application->environment_variables_preview->filter(fn ($env) => Str::of($env)->startsWith('HOST'))->isEmpty()) { + if ($this->application->environment_variables_preview->where('key', 'HOST')->isEmpty()) { $envs->push("HOST=0.0.0.0"); } - if ($this->application->environment_variables_preview->filter(fn ($env) => Str::of($env)->startsWith('SOURCE_COMMIT'))->isEmpty()) { + // Add SOURCE_COMMIT if not exists + if ($this->application->environment_variables_preview->where('key', 'SOURCE_COMMIT')->isEmpty()) { if (!is_null($this->commit)) { $envs->push("SOURCE_COMMIT={$this->commit}"); } else { @@ -750,22 +752,24 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted if ($env->version === '4.0.0-beta.239') { $real_value = $env->real_value; } else { - $real_value = escapeEnvVariables($env->real_value); - } - if ($env->is_literal) { - $real_value = '\'' . $real_value . '\''; + if ($env->is_literal) { + $real_value = '\'' . $real_value . '\''; + } else { + $real_value = escapeEnvVariables($env->real_value); + } } $envs->push($env->key . '=' . $real_value); } // Add PORT if not exists, use the first port as default - if ($this->application->environment_variables->filter(fn ($env) => Str::of($env)->startsWith('PORT'))->isEmpty()) { + if ($this->application->environment_variables->where('key', 'PORT')->isEmpty()) { $envs->push("PORT={$ports[0]}"); } // Add HOST if not exists - if ($this->application->environment_variables->filter(fn ($env) => Str::of($env)->startsWith('HOST'))->isEmpty()) { + if ($this->application->environment_variables->where('key', 'HOST')->isEmpty()) { $envs->push("HOST=0.0.0.0"); } - if ($this->application->environment_variables->filter(fn ($env) => Str::of($env)->startsWith('SOURCE_COMMIT'))->isEmpty()) { + // Add SOURCE_COMMIT if not exists + if ($this->application->environment_variables->where('key', 'SOURCE_COMMIT')->isEmpty()) { if (!is_null($this->commit)) { $envs->push("SOURCE_COMMIT={$this->commit}"); } else { @@ -1212,7 +1216,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted private function generate_compose_file() { $this->create_workdir(); - $ports = $this->application->settings->is_static ? [80] : $this->application->ports_exposes_array; + $ports = $this->application->main_port(); $onlyPort = null; if (count($ports) > 0) { $onlyPort = $ports[0]; @@ -1506,7 +1510,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted return $local_persistent_volumes_names; } - private function generate_environment_variables($ports) + /*private function generate_environment_variables($ports) { $environment_variables = collect(); if ($this->pull_request_id === 0) { @@ -1524,8 +1528,10 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted } if ($env->is_literal) { $real_value = escapeDollarSign($real_value); + $environment_variables->push("$env->key='$real_value'"); + } else { + $environment_variables->push("$env->key=$real_value"); } - $environment_variables->push("$env->key=$real_value"); } foreach ($this->application->nixpacks_environment_variables as $env) { if ($env->version === '4.0.0-beta.239') { @@ -1535,8 +1541,10 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted } if ($env->is_literal) { $real_value = escapeDollarSign($real_value); + $environment_variables->push("$env->key='$real_value'"); + } else { + $environment_variables->push("$env->key=$real_value"); } - $environment_variables->push("$env->key=$real_value"); } } else { foreach ($this->application->runtime_environment_variables_preview as $env) { @@ -1547,8 +1555,10 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted } if ($env->is_literal) { $real_value = escapeDollarSign($real_value); + $environment_variables->push("$env->key='$real_value'"); + } else { + $environment_variables->push("$env->key=$real_value"); } - $environment_variables->push("$env->key=$real_value"); } foreach ($this->application->nixpacks_environment_variables_preview as $env) { if ($env->version === '4.0.0-beta.239') { @@ -1558,8 +1568,10 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted } if ($env->is_literal) { $real_value = escapeDollarSign($real_value); + $environment_variables->push("$env->key='$real_value'"); + } else { + $environment_variables->push("$env->key=$real_value"); } - $environment_variables->push("$env->key=$real_value"); } } // Add PORT if not exists, use the first port as default @@ -1577,8 +1589,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted $environment_variables->push("SOURCE_COMMIT=unknown"); } } + ray($environment_variables->all()); return $environment_variables->all(); - } + }*/ private function generate_healthcheck_commands() { diff --git a/app/Livewire/Project/Application/Advanced.php b/app/Livewire/Project/Application/Advanced.php index 278cab5a1..d35867e8f 100644 --- a/app/Livewire/Project/Application/Advanced.php +++ b/app/Livewire/Project/Application/Advanced.php @@ -27,6 +27,8 @@ class Advanced extends Component 'application.settings.gpu_count' => 'string|required', 'application.settings.gpu_device_ids' => 'string|required', 'application.settings.gpu_options' => 'string|required', + 'application.settings.is_raw_compose_deployment_enabled' => 'boolean|required', + 'application.settings.connect_to_docker_network' => 'boolean|required', ]; public function mount() { $this->is_force_https_enabled = $this->application->isForceHttpsEnabled(); @@ -54,8 +56,14 @@ class Advanced extends Component $this->application->settings->is_stripprefix_enabled = $this->is_stripprefix_enabled; $this->dispatch('resetDefaultLabels', false); } + if ($this->application->settings->is_raw_compose_deployment_enabled) { + $this->application->parseRawCompose(); + } else { + $this->application->parseCompose(); + } $this->application->settings->save(); $this->dispatch('success', 'Settings saved.'); + $this->dispatch('configurationChanged'); } public function submit() { if ($this->application->settings->gpu_count && $this->application->settings->gpu_device_ids) { diff --git a/app/Livewire/Project/Application/General.php b/app/Livewire/Project/Application/General.php index 022a7b710..6926e52cb 100644 --- a/app/Livewire/Project/Application/General.php +++ b/app/Livewire/Project/Application/General.php @@ -34,7 +34,8 @@ class General extends Component public $parsedServiceDomains = []; protected $listeners = [ - 'resetDefaultLabels' + 'resetDefaultLabels', + 'configurationChanged' => '$refresh' ]; protected $rules = [ 'application.name' => 'required', @@ -72,7 +73,6 @@ class General extends Component 'application.post_deployment_command' => 'nullable', 'application.post_deployment_command_container' => 'nullable', 'application.settings.is_static' => 'boolean|required', - 'application.settings.is_raw_compose_deployment_enabled' => 'boolean|required', 'application.settings.is_build_server_enabled' => 'boolean|required', 'application.watch_paths' => 'nullable', ]; @@ -108,7 +108,6 @@ class General extends Component 'application.docker_compose_custom_start_command' => 'Docker compose custom start command', 'application.docker_compose_custom_build_command' => 'Docker compose custom build command', 'application.settings.is_static' => 'Is static', - 'application.settings.is_raw_compose_deployment_enabled' => 'Is raw compose deployment enabled', 'application.settings.is_build_server_enabled' => 'Is build server enabled', 'application.watch_paths' => 'Watch paths', ]; @@ -337,11 +336,6 @@ class General extends Component check_domain_usage(resource: $this->application); } } - if ($this->application->settings->is_raw_compose_deployment_enabled) { - $this->application->parseRawCompose(); - } else { - $this->parsedServices = $this->application->parseCompose(); - } } $this->application->custom_labels = base64_encode($this->customLabels); $this->application->save(); diff --git a/app/Livewire/Project/Edit.php b/app/Livewire/Project/Edit.php index d222917a6..8a35eff7f 100644 --- a/app/Livewire/Project/Edit.php +++ b/app/Livewire/Project/Edit.php @@ -12,28 +12,6 @@ class Edit extends Component 'project.name' => 'required|min:3|max:255', 'project.description' => 'nullable|string|max:255', ]; - protected $listeners = ['refreshEnvs' => '$refresh', 'saveKey' => 'saveKey']; - - public function saveKey($data) - { - try { - $found = $this->project->environment_variables()->where('key', $data['key'])->first(); - if ($found) { - throw new \Exception('Variable already exists.'); - } - $this->project->environment_variables()->create([ - 'key' => $data['key'], - 'value' => $data['value'], - 'is_multiline' => $data['is_multiline'], - 'is_literal' => $data['is_literal'], - 'type' => 'project', - 'team_id' => currentTeam()->id, - ]); - $this->project->refresh(); - } catch (\Throwable $e) { - return handleError($e, $this); - } - } public function mount() { $projectUuid = request()->route('project_uuid'); diff --git a/app/Livewire/Project/EnvironmentEdit.php b/app/Livewire/Project/EnvironmentEdit.php index 173d946f3..cd952a961 100644 --- a/app/Livewire/Project/EnvironmentEdit.php +++ b/app/Livewire/Project/EnvironmentEdit.php @@ -16,29 +16,6 @@ class EnvironmentEdit extends Component 'environment.name' => 'required|min:3|max:255', 'environment.description' => 'nullable|min:3|max:255', ]; - protected $listeners = ['refreshEnvs' => '$refresh', 'saveKey' => 'saveKey']; - - public function saveKey($data) - { - try { - $found = $this->environment->environment_variables()->where('key', $data['key'])->first(); - if ($found) { - throw new \Exception('Variable already exists.'); - } - $this->environment->environment_variables()->create([ - 'key' => $data['key'], - 'value' => $data['value'], - 'is_multiline' => $data['is_multiline'], - 'is_literal' => $data['is_literal'], - 'type' => 'environment', - 'team_id' => currentTeam()->id, - ]); - $this->environment->refresh(); - } catch (\Throwable $e) { - return handleError($e, $this); - } - } - public function mount() { $this->parameters = get_route_parameters(); diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/Show.php b/app/Livewire/Project/Shared/EnvironmentVariable/Show.php index 9beb7cfab..65e91e60a 100644 --- a/app/Livewire/Project/Shared/EnvironmentVariable/Show.php +++ b/app/Livewire/Project/Shared/EnvironmentVariable/Show.php @@ -50,9 +50,9 @@ class Show extends Component public function checkEnvs() { $this->isDisabled = false; - // if (str($this->env->key)->startsWith('SERVICE_FQDN') || str($this->env->key)->startsWith('SERVICE_URL')) { - // $this->isDisabled = true; - // } + if (str($this->env->key)->startsWith('SERVICE_FQDN') || str($this->env->key)->startsWith('SERVICE_URL')) { + $this->isDisabled = true; + } if ($this->env->is_shown_once) { $this->isLocked = true; } diff --git a/app/Livewire/SharedVariables/Environment/Index.php b/app/Livewire/SharedVariables/Environment/Index.php new file mode 100644 index 000000000..34f33ef5d --- /dev/null +++ b/app/Livewire/SharedVariables/Environment/Index.php @@ -0,0 +1,19 @@ +projects = Project::ownedByCurrentTeam()->get(); + } + public function render() + { + return view('livewire.shared-variables.environment.index'); + } +} diff --git a/app/Livewire/SharedVariables/Environment/Show.php b/app/Livewire/SharedVariables/Environment/Show.php new file mode 100644 index 000000000..29fd91153 --- /dev/null +++ b/app/Livewire/SharedVariables/Environment/Show.php @@ -0,0 +1,47 @@ + '$refresh', 'saveKey' => 'saveKey']; + + public function saveKey($data) + { + try { + $found = $this->environment->environment_variables()->where('key', $data['key'])->first(); + if ($found) { + throw new \Exception('Variable already exists.'); + } + $this->environment->environment_variables()->create([ + 'key' => $data['key'], + 'value' => $data['value'], + 'is_multiline' => $data['is_multiline'], + 'is_literal' => $data['is_literal'], + 'type' => 'environment', + 'team_id' => currentTeam()->id, + ]); + $this->environment->refresh(); + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + public function mount() + { + $this->parameters = get_route_parameters(); + $this->project = Project::ownedByCurrentTeam()->where('uuid', request()->route('project_uuid'))->first(); + $this->environment = $this->project->environments()->where('name', request()->route('environment_name'))->first(); + } + public function render() + { + return view('livewire.shared-variables.environment.show'); + } +} diff --git a/app/Livewire/SharedVariables/Index.php b/app/Livewire/SharedVariables/Index.php new file mode 100644 index 000000000..61c31e9f8 --- /dev/null +++ b/app/Livewire/SharedVariables/Index.php @@ -0,0 +1,13 @@ +projects = Project::ownedByCurrentTeam()->get(); + } + public function render() + { + return view('livewire.shared-variables.project.index'); + } +} diff --git a/app/Livewire/SharedVariables/Project/Show.php b/app/Livewire/SharedVariables/Project/Show.php new file mode 100644 index 000000000..a172c52f0 --- /dev/null +++ b/app/Livewire/SharedVariables/Project/Show.php @@ -0,0 +1,47 @@ + '$refresh', 'saveKey' => 'saveKey']; + + public function saveKey($data) + { + try { + $found = $this->project->environment_variables()->where('key', $data['key'])->first(); + if ($found) { + throw new \Exception('Variable already exists.'); + } + $this->project->environment_variables()->create([ + 'key' => $data['key'], + 'value' => $data['value'], + 'is_multiline' => $data['is_multiline'], + 'is_literal' => $data['is_literal'], + 'type' => 'project', + 'team_id' => currentTeam()->id, + ]); + $this->project->refresh(); + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + public function mount() + { + $projectUuid = request()->route('project_uuid'); + $teamId = currentTeam()->id; + $project = Project::where('team_id', $teamId)->where('uuid', $projectUuid)->first(); + if (!$project) { + return redirect()->route('dashboard'); + } + $this->project = $project; + } + public function render() + { + return view('livewire.shared-variables.project.show'); + } +} diff --git a/app/Livewire/TeamSharedVariablesIndex.php b/app/Livewire/SharedVariables/Team/Index.php similarity index 88% rename from app/Livewire/TeamSharedVariablesIndex.php rename to app/Livewire/SharedVariables/Team/Index.php index eeee30ab9..ef5c7472c 100644 --- a/app/Livewire/TeamSharedVariablesIndex.php +++ b/app/Livewire/SharedVariables/Team/Index.php @@ -1,11 +1,11 @@ '$refresh', 'saveKey' => 'saveKey']; @@ -37,6 +37,6 @@ class TeamSharedVariablesIndex extends Component } public function render() { - return view('livewire.team-shared-variables-index'); + return view('livewire.shared-variables.team.index'); } } diff --git a/app/Livewire/Team/Storage/Create.php b/app/Livewire/Storage/Create.php similarity index 95% rename from app/Livewire/Team/Storage/Create.php rename to app/Livewire/Storage/Create.php index a25fc9821..d1af807d5 100644 --- a/app/Livewire/Team/Storage/Create.php +++ b/app/Livewire/Storage/Create.php @@ -1,6 +1,6 @@ storage->team_id = currentTeam()->id; $this->storage->testConnection(); $this->storage->save(); - return redirect()->route('team.storage.show', $this->storage->uuid); + return redirect()->route('storage.show', $this->storage->uuid); } catch (\Throwable $e) { $this->dispatch('error', 'Failed to create storage.', $e->getMessage()); // return handleError($e, $this); diff --git a/app/Livewire/Team/Storage/Form.php b/app/Livewire/Storage/Form.php similarity index 94% rename from app/Livewire/Team/Storage/Form.php rename to app/Livewire/Storage/Form.php index 1fd0d470b..79c1f0c30 100644 --- a/app/Livewire/Team/Storage/Form.php +++ b/app/Livewire/Storage/Form.php @@ -1,6 +1,6 @@ storage->delete(); - return redirect()->route('team.storage.index'); + return redirect()->route('storage.index'); } catch (\Throwable $e) { return handleError($e, $this); } diff --git a/app/Livewire/Team/Storage/Index.php b/app/Livewire/Storage/Index.php similarity index 73% rename from app/Livewire/Team/Storage/Index.php rename to app/Livewire/Storage/Index.php index c15834564..f071a0af0 100644 --- a/app/Livewire/Team/Storage/Index.php +++ b/app/Livewire/Storage/Index.php @@ -1,6 +1,6 @@ storage = S3Storage::ownedByCurrentTeam()->whereUuid(request()->storage_uuid)->first(); + if (!$this->storage) { + abort(404); + } + } + public function render() + { + return view('livewire.storage.show'); + } +} diff --git a/app/Livewire/Tags/Deployments.php b/app/Livewire/Tags/Deployments.php new file mode 100644 index 000000000..5c43edfb1 --- /dev/null +++ b/app/Livewire/Tags/Deployments.php @@ -0,0 +1,33 @@ +deployments_per_tag_per_server = ApplicationDeploymentQueue::whereIn("status", ["in_progress", "queued"])->whereIn('application_id', $this->resource_ids)->get([ + "id", + "application_id", + "application_name", + "deployment_url", + "pull_request_id", + "server_name", + "server_id", + "status" + ])->sortBy('id')->groupBy('server_name')->toArray(); + } catch (\Exception $e) { + return handleError($e, $this); + } + } +} diff --git a/app/Livewire/Tags/Index.php b/app/Livewire/Tags/Index.php index 75ed06f7b..d04bb53f9 100644 --- a/app/Livewire/Tags/Index.php +++ b/app/Livewire/Tags/Index.php @@ -20,32 +20,22 @@ class Index extends Component public $webhook = null; public $deployments_per_tag_per_server = []; - public function updatedTag() + public function tag_updated() { + if ($this->tag == "") { + return; + } $tag = $this->tags->where('name', $this->tag)->first(); + if (!$tag) { + $this->dispatch('error', "Tag ({$this->tag}) not found."); + $this->tag = ""; + return; + } $this->webhook = generatTagDeployWebhook($tag->name); $this->applications = $tag->applications()->get(); $this->services = $tag->services()->get(); - $this->get_deployments(); - } - public function get_deployments() - { - try { - $resource_ids = $this->applications->pluck('id'); - $this->deployments_per_tag_per_server = ApplicationDeploymentQueue::whereIn("status", ["in_progress", "queued"])->whereIn('application_id', $resource_ids)->get([ - "id", - "application_id", - "application_name", - "deployment_url", - "pull_request_id", - "server_name", - "server_id", - "status" - ])->sortBy('id')->groupBy('server_name')->toArray(); - } catch (\Exception $e) { - return handleError($e, $this); - } } + public function redeploy_all() { try { @@ -67,7 +57,7 @@ class Index extends Component { $this->tags = Tag::ownedByCurrentTeam()->get()->unique('name')->sortBy('name'); if ($this->tag) { - $this->updatedTag(); + $this->tag_updated(); } } public function render() diff --git a/app/Livewire/Team/Storage/Show.php b/app/Livewire/Team/Storage/Show.php index 6fbb6034f..ab2acbbb9 100644 --- a/app/Livewire/Team/Storage/Show.php +++ b/app/Livewire/Team/Storage/Show.php @@ -17,6 +17,6 @@ class Show extends Component } public function render() { - return view('livewire.team.storage.show'); + return view('livewire.storage.show'); } } diff --git a/app/Models/Application.php b/app/Models/Application.php index 66e18565b..f28d389f4 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -346,6 +346,10 @@ class Application extends BaseModel } return null; } + public function main_port() + { + return $this->settings->is_static ? [80] : $this->ports_exposes_array; + } public function environment_variables(): HasMany { return $this->hasMany(EnvironmentVariable::class)->where('is_preview', false)->orderBy('key', 'asc'); @@ -579,9 +583,9 @@ class Application extends BaseModel ['repository' => $customRepository, 'port' => $customPort] = $this->customRepository(); $baseDir = $custom_base_dir ?? $this->generateBaseDir($deployment_uuid); $commands = collect([]); - $git_clone_command = "git clone -b {$this->git_branch}"; + $git_clone_command = "git clone -b \"{$this->git_branch}\""; if ($only_checkout) { - $git_clone_command = "git clone --no-checkout -b {$this->git_branch}"; + $git_clone_command = "git clone --no-checkout -b \"{$this->git_branch}\""; } if ($pull_request_id !== 0) { $pr_branch_name = "pr-{$pull_request_id}-coolify"; diff --git a/app/Models/S3Storage.php b/app/Models/S3Storage.php index 9d60ce491..dc0b93466 100644 --- a/app/Models/S3Storage.php +++ b/app/Models/S3Storage.php @@ -48,7 +48,7 @@ class S3Storage extends BaseModel if ($this->unusable_email_sent === false && is_transactional_emails_active()) { $mail = new MailMessage(); $mail->subject('Coolify: S3 Storage Connection Error'); - $mail->view('emails.s3-connection-error', ['name' => $this->name, 'reason' => $e->getMessage(), 'url' => route('team.storage.show', ['storage_uuid' => $this->uuid])]); + $mail->view('emails.s3-connection-error', ['name' => $this->name, 'reason' => $e->getMessage(), 'url' => route('storage.show', ['storage_uuid' => $this->uuid])]); $users = collect([]); $members = $this->team->members()->get(); foreach ($members as $user) { diff --git a/app/Models/StandaloneClickhouse.php b/app/Models/StandaloneClickhouse.php index 2197d51df..3746a32f5 100644 --- a/app/Models/StandaloneClickhouse.php +++ b/app/Models/StandaloneClickhouse.php @@ -207,4 +207,7 @@ class StandaloneClickhouse extends BaseModel { return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); } + public function database_name() { + return $this->clickhouse_db; + } } diff --git a/app/Models/StandaloneDragonfly.php b/app/Models/StandaloneDragonfly.php index 7b18666b8..adc1ea6cc 100644 --- a/app/Models/StandaloneDragonfly.php +++ b/app/Models/StandaloneDragonfly.php @@ -207,4 +207,7 @@ class StandaloneDragonfly extends BaseModel { return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); } + public function database_name() { + return '0'; + } } diff --git a/app/Models/StandaloneKeydb.php b/app/Models/StandaloneKeydb.php index 1dc55228a..ff91322a0 100644 --- a/app/Models/StandaloneKeydb.php +++ b/app/Models/StandaloneKeydb.php @@ -208,5 +208,7 @@ class StandaloneKeydb extends BaseModel { return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); } - + public function database_name() { + return '0'; + } } diff --git a/app/Models/StandaloneMariadb.php b/app/Models/StandaloneMariadb.php index 5e18bbfde..37d39f882 100644 --- a/app/Models/StandaloneMariadb.php +++ b/app/Models/StandaloneMariadb.php @@ -208,4 +208,7 @@ class StandaloneMariadb extends BaseModel { return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); } + public function database_name() { + return $this->mariadb_database; + } } diff --git a/app/Models/StandaloneMongodb.php b/app/Models/StandaloneMongodb.php index 8e4d327a3..5538efe1a 100644 --- a/app/Models/StandaloneMongodb.php +++ b/app/Models/StandaloneMongodb.php @@ -223,4 +223,7 @@ class StandaloneMongodb extends BaseModel { return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); } + public function database_name() { + return $this->mongo_db; + } } diff --git a/app/Models/StandaloneMysql.php b/app/Models/StandaloneMysql.php index eede451d7..53e9b6f22 100644 --- a/app/Models/StandaloneMysql.php +++ b/app/Models/StandaloneMysql.php @@ -209,4 +209,7 @@ class StandaloneMysql extends BaseModel { return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); } + public function database_name() { + return $this->mysql_database; + } } diff --git a/app/Models/StandalonePostgresql.php b/app/Models/StandalonePostgresql.php index cf449a815..6435c49de 100644 --- a/app/Models/StandalonePostgresql.php +++ b/app/Models/StandalonePostgresql.php @@ -208,4 +208,7 @@ class StandalonePostgresql extends BaseModel { return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); } + public function database_name() { + return $this->postgres_db; + } } diff --git a/app/Models/StandaloneRedis.php b/app/Models/StandaloneRedis.php index da4701df9..de18c8c07 100644 --- a/app/Models/StandaloneRedis.php +++ b/app/Models/StandaloneRedis.php @@ -204,4 +204,7 @@ class StandaloneRedis extends BaseModel { return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); } + public function database_name() { + return '0'; + } } diff --git a/app/Notifications/Database/BackupFailed.php b/app/Notifications/Database/BackupFailed.php index f149a9d06..3aa63ffd9 100644 --- a/app/Notifications/Database/BackupFailed.php +++ b/app/Notifications/Database/BackupFailed.php @@ -17,11 +17,13 @@ class BackupFailed extends Notification implements ShouldQueue public $tries = 1; public string $name; + public string $database_name; public string $frequency; public function __construct(ScheduledDatabaseBackup $backup, public $database, public $output) { $this->name = $database->name; + $this->database_name = $database->database_name(); $this->frequency = $backup->frequency; } @@ -36,6 +38,7 @@ class BackupFailed extends Notification implements ShouldQueue $mail->subject("Coolify: [ACTION REQUIRED] Backup FAILED for {$this->database->name}"); $mail->view('emails.backup-failed', [ 'name' => $this->name, + 'database_name' => $this->database_name, 'frequency' => $this->frequency, 'output' => $this->output, ]); @@ -44,11 +47,11 @@ class BackupFailed extends Notification implements ShouldQueue public function toDiscord(): string { - return "Coolify: Database backup for {$this->name} with frequency of {$this->frequency} was FAILED.\n\nReason: {$this->output}"; + return "Coolify: Database backup for {$this->name} (db:{$this->database_name}) with frequency of {$this->frequency} was FAILED.\n\nReason: {$this->output}"; } public function toTelegram(): array { - $message = "Coolify: Database backup for {$this->name} with frequency of {$this->frequency} was FAILED.\n\nReason: {$this->output}"; + $message = "Coolify: Database backup for {$this->name} (db:{$this->database_name}) with frequency of {$this->frequency} was FAILED.\n\nReason: {$this->output}"; return [ "message" => $message, ]; diff --git a/app/Notifications/Database/BackupSuccess.php b/app/Notifications/Database/BackupSuccess.php index bbe0bc6d3..9ca3234e1 100644 --- a/app/Notifications/Database/BackupSuccess.php +++ b/app/Notifications/Database/BackupSuccess.php @@ -14,11 +14,13 @@ class BackupSuccess extends Notification implements ShouldQueue public $tries = 1; public string $name; + public string $database_name; public string $frequency; public function __construct(ScheduledDatabaseBackup $backup, public $database) { $this->name = $database->name; + $this->database_name = $database->database_name(); $this->frequency = $backup->frequency; } @@ -33,6 +35,7 @@ class BackupSuccess extends Notification implements ShouldQueue $mail->subject("Coolify: Backup successfully done for {$this->database->name}"); $mail->view('emails.backup-success', [ 'name' => $this->name, + 'database_name' => $this->database_name, 'frequency' => $this->frequency, ]); return $mail; @@ -40,11 +43,11 @@ class BackupSuccess extends Notification implements ShouldQueue public function toDiscord(): string { - return "Coolify: Database backup for {$this->name} with frequency of {$this->frequency} was successful."; + return "Coolify: Database backup for {$this->name} (db:{$this->database_name}) with frequency of {$this->frequency} was successful."; } public function toTelegram(): array { - $message = "Coolify: Database backup for {$this->name} with frequency of {$this->frequency} was successful."; + $message = "Coolify: Database backup for {$this->name} (db:{$this->database_name}) with frequency of {$this->frequency} was successful."; return [ "message" => $message, ]; diff --git a/app/View/Components/Forms/Datalist.php b/app/View/Components/Forms/Datalist.php new file mode 100644 index 000000000..d4ed44266 --- /dev/null +++ b/app/View/Components/Forms/Datalist.php @@ -0,0 +1,38 @@ +id)) $this->id = new Cuid2(7); + if (is_null($this->name)) $this->name = $this->id; + + $this->label = Str::title($this->label); + return view('components.forms.datalist'); + } +} diff --git a/app/View/Components/Forms/Textarea.php b/app/View/Components/Forms/Textarea.php index 86547cf65..28f4a45ba 100644 --- a/app/View/Components/Forms/Textarea.php +++ b/app/View/Components/Forms/Textarea.php @@ -23,10 +23,11 @@ class Textarea extends Component public bool $disabled = false, public bool $readonly = false, public bool $allowTab = false, + public bool $spellcheck = false, public ?string $helper = null, public bool $realtimeValidation = false, public bool $allowToPeak = true, - public string $defaultClass = "input scrollbar", + public string $defaultClass = "input scrollbar font-mono", public string $defaultClassInput = "input" ) { // diff --git a/bootstrap/helpers/services.php b/bootstrap/helpers/services.php index 8e3c0337e..5b71a8b10 100644 --- a/bootstrap/helpers/services.php +++ b/bootstrap/helpers/services.php @@ -18,7 +18,7 @@ function collectRegex(string $name) } function replaceVariables($variable) { - return $variable->replaceFirst('$', '')->replaceFirst('{', '')->replaceLast('}', ''); + return $variable->after('${')->before('}'); } function getFilesystemVolumesFromServer(ServiceApplication|ServiceDatabase|Application $oneService, bool $isInit = false) diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index 966b1db59..b2c34900e 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -29,11 +29,13 @@ use DanHarrin\LivewireRateLimiting\Exceptions\TooManyRequestsException; use Illuminate\Database\UniqueConstraintViolationException; use Illuminate\Mail\Message; use Illuminate\Notifications\Messages\MailMessage; +use Illuminate\Process\Pool; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Mail; +use Illuminate\Support\Facades\Process; use Illuminate\Support\Facades\Request; use Illuminate\Support\Facades\Route; use Illuminate\Support\Str; @@ -1266,6 +1268,11 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal $serviceLabels->push("$removedLabelName=$removedLabel"); } } + if ($serviceLabels->count() > 0) { + $serviceLabels = $serviceLabels->map(function ($value, $key) { + return escapeDollarSign($value); + }); + } $baseName = generateApplicationContainerName($resource, $pull_request_id); $containerName = "$serviceName-$baseName"; if (count($serviceVolumes) > 0) { @@ -1424,6 +1431,14 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal foreach ($definedNetwork as $key => $network) { $networks->put($network, null); } + if (data_get($resource, 'settings.connect_to_docker_network')) { + $network = $resource->destination->network; + $networks->put($network, null); + $topLevelNetworks->put($network, [ + 'name' => $network, + 'external' => true + ]); + } data_set($service, 'networks', $networks->toArray()); // Get variables from the service foreach ($serviceVariables as $variableName => $variable) { @@ -1585,7 +1600,6 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal $fqdns = data_get($domains, "$serviceName.domain"); if ($fqdns) { $fqdns = str($fqdns)->explode(','); - $uuid = new Cuid2(7); if ($pull_request_id !== 0) { $fqdns = $fqdns->map(function ($fqdn) use ($pull_request_id, $resource) { $preview = ApplicationPreview::findPreviewByApplicationAndPullId($resource->id, $pull_request_id); @@ -1604,13 +1618,13 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal }); } $serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik( - uuid: $uuid, + uuid: $resource->uuid, domains: $fqdns, serviceLabels: $serviceLabels )); $serviceLabels = $serviceLabels->merge(fqdnLabelsForCaddy( network: $resource->destination->network, - uuid: $uuid, + uuid: $resource->uuid, domains: $fqdns, serviceLabels: $serviceLabels )); @@ -2004,3 +2018,37 @@ function parseLineForSudo(string $command, Server $server): string return $command; } + +function get_public_ips() +{ + try { + echo "Refreshing public ips!\n"; + $settings = InstanceSettings::get(); + [$first, $second] = Process::concurrently(function (Pool $pool) { + $pool->path(__DIR__)->command('curl -4s https://ifconfig.io'); + $pool->path(__DIR__)->command('curl -6s https://ifconfig.io'); + }); + $ipv4 = $first->output(); + if ($ipv4) { + $ipv4 = trim($ipv4); + $validate_ipv4 = filter_var($ipv4, FILTER_VALIDATE_IP); + if ($validate_ipv4 == false) { + echo "Invalid ipv4: $ipv4\n"; + return; + } + $settings->update(['public_ipv4' => $ipv4]); + } + $ipv6 = $second->output(); + if ($ipv6) { + $ipv6 = trim($ipv6); + $validate_ipv6 = filter_var($ipv6, FILTER_VALIDATE_IP); + if ($validate_ipv6 == false) { + echo "Invalid ipv6: $ipv6\n"; + return; + } + $settings->update(['public_ipv6' => $ipv6]); + } + } catch (\Throwable $e) { + echo "Error: {$e->getMessage()}\n"; + } +} diff --git a/config/sentry.php b/config/sentry.php index 7ae7ccfda..2b48e1f14 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.266', + 'release' => '4.0.0-beta.269', // 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 7001afde8..bf35359e8 100644 --- a/config/version.php +++ b/config/version.php @@ -1,3 +1,3 @@ boolean('connect_to_docker_network')->default(false); + + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('application_settings', function (Blueprint $table) { + $table->dropColumn('connect_to_docker_network'); + }); + } +}; diff --git a/database/seeders/ProductionSeeder.php b/database/seeders/ProductionSeeder.php index 206e92f76..16dc3583e 100644 --- a/database/seeders/ProductionSeeder.php +++ b/database/seeders/ProductionSeeder.php @@ -177,27 +177,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA== } } - try { - $settings = InstanceSettings::get(); - if (is_null($settings->public_ipv4)) { - $ipv4 = Process::run('curl -4s https://ifconfig.io')->output(); - if ($ipv4) { - $ipv4 = trim($ipv4); - $ipv4 = filter_var($ipv4, FILTER_VALIDATE_IP); - $settings->update(['public_ipv4' => $ipv4]); - } - } - if (is_null($settings->public_ipv6)) { - $ipv6 = Process::run('curl -6s https://ifconfig.io')->output(); - if ($ipv6) { - $ipv6 = trim($ipv6); - $ipv6 = filter_var($ipv6, FILTER_VALIDATE_IP); - $settings->update(['public_ipv6' => $ipv6]); - } - } - } catch (\Throwable $e) { - echo "Error: {$e->getMessage()}\n"; - } + get_public_ips(); $oauth_settings_seeder = new OauthSettingSeeder(); $oauth_settings_seeder->run(); diff --git a/public/svgs/odoo.svg b/public/svgs/odoo.svg new file mode 100644 index 000000000..073cf91ef --- /dev/null +++ b/public/svgs/odoo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/css/app.css b/resources/css/app.css index a10103ce8..1f218490d 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -133,6 +133,11 @@ tr td:first-child { @apply inline-block w-3 h-3 text-xs font-bold leading-none border rounded-full border-neutral-200 dark:border-black; } +.badge-absolute { + @apply absolute top-0 right-0 w-2 h-2 border-none rounded-t-none rounded-r-none; + +} + .badge-success { @apply bg-success; } @@ -195,7 +200,7 @@ tr td:first-child { } .box { - @apply flex lg:flex-row flex-col p-2 transition-colors cursor-pointer min-h-[4rem] dark:bg-coolgray-100 bg-white border text-black dark:text-white hover:text-black border-neutral-200 dark:border-black hover:bg-neutral-100 dark:hover:bg-coollabs-100 dark:hover:text-white hover:no-underline; + @apply relative flex lg:flex-row flex-col p-2 transition-colors cursor-pointer min-h-[4rem] dark:bg-coolgray-100 bg-white border text-black dark:text-white hover:text-black border-neutral-200 dark:border-black hover:bg-neutral-100 dark:hover:bg-coollabs-100 dark:hover:text-white hover:no-underline; } .box-boarding { @apply flex lg:flex-row flex-col p-2 transition-colors cursor-pointer min-h-[4rem] dark:bg-coolgray-100 dark:text-white bg-neutral-50 border border-neutral-200 dark:border-black hover:bg-neutral-100 dark:hover:bg-coollabs-100 dark:hover:text-white hover:text-black hover:no-underline text-black ; diff --git a/resources/views/components/forms/datalist.blade.php b/resources/views/components/forms/datalist.blade.php new file mode 100644 index 000000000..c9710b728 --- /dev/null +++ b/resources/views/components/forms/datalist.blade.php @@ -0,0 +1,44 @@ +
+ + @error($id) + + @enderror + {{-- --}} +
diff --git a/resources/views/components/forms/textarea.blade.php b/resources/views/components/forms/textarea.blade.php index f5bdc3e7d..2c32c585e 100644 --- a/resources/views/components/forms/textarea.blade.php +++ b/resources/views/components/forms/textarea.blade.php @@ -56,7 +56,7 @@ @else -