diff --git a/README.md b/README.md index 01c1ccef9..eb66299ac 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,10 @@ Special thanks to our biggest sponsor, [CCCareers](https://cccareers.org/)! ## Github Sponsors ($40+) BC Direct typebot +QuantCDN + + + FlintCompany American Cloud CryptoJobsList UXWizz @@ -62,6 +66,7 @@ Special thanks to our biggest sponsor, [CCCareers](https://cccareers.org/)! + ## Individuals diff --git a/app/Actions/Docker/GetContainersStatus.php b/app/Actions/Docker/GetContainersStatus.php index 667a8c92e..8f4bfdf25 100644 --- a/app/Actions/Docker/GetContainersStatus.php +++ b/app/Actions/Docker/GetContainersStatus.php @@ -339,7 +339,7 @@ class GetContainersStatus instant_remote_process($connectProxyToDockerNetworks, $this->server, false); } } catch (\Exception $e) { - send_internal_notification("ContainerStatusJob failed on ({$this->server->id}) with: " . $e->getMessage()); + // send_internal_notification("ContainerStatusJob failed on ({$this->server->id}) with: " . $e->getMessage()); ray($e->getMessage()); return handleError($e); } diff --git a/app/Console/Commands/AdminRemoveUser.php b/app/Console/Commands/AdminRemoveUser.php new file mode 100644 index 000000000..76af0a97f --- /dev/null +++ b/app/Console/Commands/AdminRemoveUser.php @@ -0,0 +1,54 @@ +argument('email'); + $confirm = $this->confirm('Are you sure you want to remove user with email: ' . $email . '?'); + if (!$confirm) { + $this->info('User removal cancelled.'); + return; + } + $this->info("Removing user with email: $email"); + $user = User::whereEmail($email)->firstOrFail(); + $teams = $user->teams; + foreach ($teams as $team) { + if ($team->members->count() > 1) { + $this->error('User is a member of a team with more than one member. Please remove user from team first.'); + return; + } + $team->delete(); + } + $user->delete(); + } catch (\Exception $e) { + $this->error('Failed to remove user.'); + $this->error($e->getMessage()); + return; + } + } +} diff --git a/app/Console/Commands/Cloud.php b/app/Console/Commands/Cloud.php deleted file mode 100644 index 1386b296c..000000000 --- a/app/Console/Commands/Cloud.php +++ /dev/null @@ -1,33 +0,0 @@ -whereNotNull('team.subscription')->where('team.subscription.stripe_trial_already_ended',true)->each(function($server){ - $this->info($server->name); - }); - } -} diff --git a/app/Console/Commands/RootResetPassword.php b/app/Console/Commands/RootResetPassword.php index df385002e..af2b1a45c 100644 --- a/app/Console/Commands/RootResetPassword.php +++ b/app/Console/Commands/RootResetPassword.php @@ -29,7 +29,6 @@ class RootResetPassword extends Command */ public function handle() { - // $this->info('You are about to reset the root password.'); $password = password('Give me a new password for root user: '); $passwordAgain = password('Again'); diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 89d679a47..99ca75352 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -18,6 +18,7 @@ use App\Models\Server; use App\Models\Team; use Illuminate\Console\Scheduling\Schedule; use Illuminate\Foundation\Console\Kernel as ConsoleKernel; +use Illuminate\Support\Sleep; class Kernel extends ConsoleKernel { @@ -76,13 +77,28 @@ class Kernel extends ConsoleKernel $containerServers = $servers->where('settings.is_swarm_worker', false)->where('settings.is_build_server', false); } foreach ($containerServers as $server) { - $schedule->job(new ContainerStatusJob($server))->everyMinute()->onOneServer(); + $schedule->job(new ContainerStatusJob($server))->everyTwoMinutes()->onOneServer()->before(function () { + if (isCloud()) { + $wait = rand(5, 20); + Sleep::for($wait)->seconds(); + } + }); if ($server->isLogDrainEnabled()) { - $schedule->job(new CheckLogDrainContainerJob($server))->everyMinute()->onOneServer(); + $schedule->job(new CheckLogDrainContainerJob($server))->everyTwoMinutes()->onOneServer()->before(function () { + if (isCloud()) { + $wait = rand(5, 20); + Sleep::for($wait)->seconds(); + } + }); } } foreach ($servers as $server) { - $schedule->job(new ServerStatusJob($server))->everyMinute()->onOneServer(); + $schedule->job(new ServerStatusJob($server))->everyTwoMinutes()->onOneServer()->before(function () { + if (isCloud()) { + $wait = rand(5, 20); + Sleep::for($wait)->seconds(); + } + }); } } private function instance_auto_update($schedule) @@ -138,7 +154,16 @@ class Kernel extends ConsoleKernel $scheduled_task->delete(); continue; } - + if ($application) { + if (str($application->status)->contains('running') === false) { + continue; + } + } + if ($service) { + if (str($service->status())->contains('running') === false) { + continue; + } + } if (isset(VALID_CRON_STRINGS[$scheduled_task->frequency])) { $scheduled_task->frequency = VALID_CRON_STRINGS[$scheduled_task->frequency]; } diff --git a/app/Http/Controllers/Webhook/Bitbucket.php b/app/Http/Controllers/Webhook/Bitbucket.php index 485720c23..7b569c278 100644 --- a/app/Http/Controllers/Webhook/Bitbucket.php +++ b/app/Http/Controllers/Webhook/Bitbucket.php @@ -47,7 +47,7 @@ class Bitbucket extends Controller if ($x_bitbucket_event === 'repo:push') { $branch = data_get($payload, 'push.changes.0.new.name'); $full_name = data_get($payload, 'repository.full_name'); - + $commit = data_get($payload, 'push.changes.0.new.target.hash'); if (!$branch) { return response([ 'status' => 'failed', @@ -104,6 +104,7 @@ class Bitbucket extends Controller queue_application_deployment( application: $application, deployment_uuid: $deployment_uuid, + commit: $commit, force_rebuild: false, is_webhook: true ); diff --git a/app/Http/Controllers/Webhook/Github.php b/app/Http/Controllers/Webhook/Github.php index 214843aab..baa23deec 100644 --- a/app/Http/Controllers/Webhook/Github.php +++ b/app/Http/Controllers/Webhook/Github.php @@ -129,6 +129,7 @@ class Github extends Controller application: $application, deployment_uuid: $deployment_uuid, force_rebuild: false, + commit: data_get($payload, 'after', 'HEAD'), is_webhook: true, ); $return_payloads->push([ @@ -177,6 +178,7 @@ class Github extends Controller pull_request_id: $pull_request_id, deployment_uuid: $deployment_uuid, force_rebuild: false, + commit: data_get($payload, 'head.sha', 'HEAD'), is_webhook: true, git_type: 'github' ); @@ -338,6 +340,7 @@ class Github extends Controller queue_application_deployment( application: $application, deployment_uuid: $deployment_uuid, + commit: data_get($payload, 'after', 'HEAD'), force_rebuild: false, is_webhook: true, ); @@ -387,6 +390,7 @@ class Github extends Controller pull_request_id: $pull_request_id, deployment_uuid: $deployment_uuid, force_rebuild: false, + commit: data_get($payload, 'head.sha', 'HEAD'), is_webhook: true, git_type: 'github' ); diff --git a/app/Http/Controllers/Webhook/Gitlab.php b/app/Http/Controllers/Webhook/Gitlab.php index 65ce9910b..dfa9394eb 100644 --- a/app/Http/Controllers/Webhook/Gitlab.php +++ b/app/Http/Controllers/Webhook/Gitlab.php @@ -38,6 +38,15 @@ class Gitlab extends Controller $headers = $request->headers->all(); $x_gitlab_token = data_get($headers, 'x-gitlab-token.0'); $x_gitlab_event = data_get($payload, 'object_kind'); + $allowed_events = ['push', 'merge_request']; + if (!in_array($x_gitlab_event, $allowed_events)) { + $return_payloads->push([ + 'status' => 'failed', + 'message' => 'Event not allowed. Only push and merge_request events are allowed.', + ]); + return response($return_payloads); + } + if ($x_gitlab_event === 'push') { $branch = data_get($payload, 'ref'); $full_name = data_get($payload, 'project.path_with_namespace'); @@ -124,6 +133,7 @@ class Gitlab extends Controller queue_application_deployment( application: $application, deployment_uuid: $deployment_uuid, + commit: data_get($payload, 'after', 'HEAD'), force_rebuild: false, is_webhook: true, ); @@ -173,6 +183,7 @@ class Gitlab extends Controller application: $application, pull_request_id: $pull_request_id, deployment_uuid: $deployment_uuid, + commit: data_get($payload, 'object_attributes.last_commit.id', 'HEAD'), force_rebuild: false, is_webhook: true, git_type: 'gitlab' diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 69bfa29d0..4c0da8d4d 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -107,6 +107,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted private ?string $fullRepoUrl = null; private ?string $branch = null; + private ?string $coolify_variables = null; + public $tries = 1; public function __construct(int $application_deployment_queue_id) { @@ -406,7 +408,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted ); } else { $this->execute_remote_command( - [executeInDocker($this->deployment_uuid, "SOURCE_COMMIT={$this->commit} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build"), "hidden" => true], + [executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build"), "hidden" => true], ); } @@ -436,9 +438,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted } else { $this->write_deployment_configurations(); $server_workdir = $this->application->workdir(); - ray("SOURCE_COMMIT={$this->commit} docker compose --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d"); + ray("{$this->coolify_variables} docker compose --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d"); $this->execute_remote_command( - ["SOURCE_COMMIT={$this->commit} docker compose --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d", "hidden" => true], + ["{$this->coolify_variables} docker compose --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d", "hidden" => true], ); } } else { @@ -449,7 +451,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted $this->write_deployment_configurations(); } else { $this->execute_remote_command( - [executeInDocker($this->deployment_uuid, "SOURCE_COMMIT={$this->commit} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up -d"), "hidden" => true], + [executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up -d"), "hidden" => true], ); $this->write_deployment_configurations(); } @@ -743,6 +745,16 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted $envs->push("SOURCE_COMMIT=unknown"); } } + if ($this->application->environment_variables_preview->where('key', 'COOLIFY_FQDN')->isEmpty()) { + $envs->push("COOLIFY_FQDN={$this->preview->fqdn}"); + } + if ($this->application->environment_variables_preview->where('key', 'COOLIFY_URL')->isEmpty()) { + $url = str($this->preview->fqdn)->replace('http://', '')->replace('https://', ''); + $envs->push("COOLIFY_URL={$url}"); + } + if ($this->application->environment_variables_preview->where('key', 'COOLIFY_BRANCH')->isEmpty()) { + $envs->push("COOLIFY_BRANCH={$this->application->git_branch}"); + } $envs = $envs->sort(function ($a, $b) { return strpos($a, '$') === false ? -1 : 1; }); @@ -777,6 +789,16 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted $envs->push("SOURCE_COMMIT=unknown"); } } + if ($this->application->environment_variables->where('key', 'COOLIFY_FQDN')->isEmpty()) { + $envs->push("COOLIFY_FQDN={$this->application->fqdn}"); + } + if ($this->application->environment_variables->where('key', 'COOLIFY_URL')->isEmpty()) { + $url = str($this->application->fqdn)->replace('http://', '')->replace('https://', ''); + $envs->push("COOLIFY_URL={$url}"); + } + if ($this->application->environment_variables_preview->where('key', 'COOLIFY_BRANCH')->isEmpty()) { + $envs->push("COOLIFY_BRANCH={$this->application->git_branch}"); + } $envs = $envs->sort(function ($a, $b) { return strpos($a, '$') === false ? -1 : 1; }); @@ -1080,6 +1102,23 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted { $this->application_deployment_queue->addLogEntry("Setting base directory to {$this->workdir}."); } + private function set_coolify_variables() + { + $this->coolify_variables = "SOURCE_COMMIT={$this->commit} "; + if ($this->pull_request_id === 0) { + $fqdn = $this->application->fqdn; + } else { + $fqdn = $this->preview->fqdn; + } + if (isset($fqdn)) { + $this->coolify_variables .= "COOLIFY_FQDN={$fqdn} "; + $url = str($fqdn)->replace('http://', '')->replace('https://', ''); + $this->coolify_variables .= "COOLIFY_URL={$url} "; + } + if (isset($this->application->git_branch)) { + $this->coolify_variables .= "COOLIFY_BRANCH={$this->application->git_branch} "; + } + } private function check_git_if_build_needed() { $this->generate_git_import_commands(); @@ -1113,7 +1152,10 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted } if ($this->saved_outputs->get('git_commit_sha') && !$this->rollback) { $this->commit = $this->saved_outputs->get('git_commit_sha')->before("\t"); + $this->application_deployment_queue->commit = $this->commit; + $this->application_deployment_queue->save(); } + $this->set_coolify_variables(); } private function clone_repository() { @@ -1129,6 +1171,18 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted ] ); $this->create_workdir(); + $this->execute_remote_command( + [ + executeInDocker($this->deployment_uuid, "cd {$this->workdir} && git log -1 {$this->commit} --pretty=%B"), + "hidden" => true, + "save" => "commit_message" + ] + ); + if ($this->saved_outputs->get('commit_message')) { + $this->application_deployment_queue->commit_message = $this->saved_outputs->get('commit_message'); + ApplicationDeploymentQueue::whereCommit($this->commit)->whereApplicationId($this->application->id)->update(['commit_message' => $this->saved_outputs->get('commit_message')]); + $this->application_deployment_queue->save(); + } } private function generate_git_import_commands() @@ -1277,9 +1331,11 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted if ($this->pull_request_id !== 0) { $labels = collect(generateLabelsApplication($this->application, $this->preview)); } - $labels = $labels->map(function ($value, $key) { - return escapeDollarSign($value); - }); + if ($this->application->settings->is_container_label_escape_enabled) { + $labels = $labels->map(function ($value, $key) { + return escapeDollarSign($value); + }); + } $labels = $labels->merge(defaultLabels($this->application->id, $this->application->uuid, $this->pull_request_id))->toArray(); // Check for custom HEALTHCHECK @@ -1767,11 +1823,11 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); $this->application_deployment_queue->addLogEntry("Pulling latest images from the registry."); $this->execute_remote_command( [executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} pull"), "hidden" => true], - [executeInDocker($this->deployment_uuid, "SOURCE_COMMIT={$this->commit} docker compose --project-directory {$this->workdir} build"), "hidden" => true], + [executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-directory {$this->workdir} build"), "hidden" => true], ); } else { $this->execute_remote_command( - [executeInDocker($this->deployment_uuid, "SOURCE_COMMIT={$this->commit} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build"), "hidden" => true], + [executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build"), "hidden" => true], ); } $this->application_deployment_queue->addLogEntry("New images built."); @@ -1783,16 +1839,16 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); $this->application_deployment_queue->addLogEntry("Pulling latest images from the registry."); $this->execute_remote_command( [executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} pull"), "hidden" => true], - [executeInDocker($this->deployment_uuid, "SOURCE_COMMIT={$this->commit} docker compose --project-directory {$this->workdir} up --build -d"), "hidden" => true], + [executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-directory {$this->workdir} up --build -d"), "hidden" => true], ); } else { if ($this->use_build_server) { $this->execute_remote_command( - ["SOURCE_COMMIT={$this->commit} docker compose --project-directory {$this->configuration_dir} -f {$this->configuration_dir}{$this->docker_compose_location} up --build -d", "hidden" => true], + ["{$this->coolify_variables} docker compose --project-directory {$this->configuration_dir} -f {$this->configuration_dir}{$this->docker_compose_location} up --build -d", "hidden" => true], ); } else { $this->execute_remote_command( - [executeInDocker($this->deployment_uuid, "SOURCE_COMMIT={$this->commit} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up --build -d"), "hidden" => true], + [executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up --build -d"), "hidden" => true], ); } } diff --git a/app/Jobs/ContainerStatusJob.php b/app/Jobs/ContainerStatusJob.php index 9ffd68f11..11e7013ee 100644 --- a/app/Jobs/ContainerStatusJob.php +++ b/app/Jobs/ContainerStatusJob.php @@ -37,336 +37,5 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted public function handle() { GetContainersStatus::run($this->server); - return; - // if (!$this->server->isFunctional()) { - // return 'Server is not ready.'; - // }; - // $applications = $this->server->applications(); - // $skip_these_applications = collect([]); - // foreach ($applications as $application) { - // if ($application->additional_servers->count() > 0) { - // $skip_these_applications->push($application); - // ComplexStatusCheck::run($application); - // $applications = $applications->filter(function ($value, $key) use ($application) { - // return $value->id !== $application->id; - // }); - // } - // } - // $applications = $applications->filter(function ($value, $key) use ($skip_these_applications) { - // return !$skip_these_applications->pluck('id')->contains($value->id); - // }); - // try { - // if ($this->server->isSwarm()) { - // $containers = instant_remote_process(["docker service inspect $(docker service ls -q) --format '{{json .}}'"], $this->server, false); - // $containerReplicates = instant_remote_process(["docker service ls --format '{{json .}}'"], $this->server, false); - // } else { - // // Precheck for containers - // $containers = instant_remote_process(["docker container ls -q"], $this->server, false); - // if (!$containers) { - // return; - // } - // $containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this->server, false); - // $containerReplicates = null; - // } - // if (is_null($containers)) { - // return; - // } - - // $containers = format_docker_command_output_to_json($containers); - // if ($containerReplicates) { - // $containerReplicates = format_docker_command_output_to_json($containerReplicates); - // foreach ($containerReplicates as $containerReplica) { - // $name = data_get($containerReplica, 'Name'); - // $containers = $containers->map(function ($container) use ($name, $containerReplica) { - // if (data_get($container, 'Spec.Name') === $name) { - // $replicas = data_get($containerReplica, 'Replicas'); - // $running = str($replicas)->explode('/')[0]; - // $total = str($replicas)->explode('/')[1]; - // if ($running === $total) { - // data_set($container, 'State.Status', 'running'); - // data_set($container, 'State.Health.Status', 'healthy'); - // } else { - // data_set($container, 'State.Status', 'starting'); - // data_set($container, 'State.Health.Status', 'unhealthy'); - // } - // } - // return $container; - // }); - // } - // } - // $databases = $this->server->databases(); - // $services = $this->server->services()->get(); - // $previews = $this->server->previews(); - // $foundApplications = []; - // $foundApplicationPreviews = []; - // $foundDatabases = []; - // $foundServices = []; - - // foreach ($containers as $container) { - // if ($this->server->isSwarm()) { - // $labels = data_get($container, 'Spec.Labels'); - // $uuid = data_get($labels, 'coolify.name'); - // } else { - // $labels = data_get($container, 'Config.Labels'); - // } - // $containerStatus = data_get($container, 'State.Status'); - // $containerHealth = data_get($container, 'State.Health.Status', 'unhealthy'); - // $containerStatus = "$containerStatus ($containerHealth)"; - // $labels = Arr::undot(format_docker_labels_to_json($labels)); - // $applicationId = data_get($labels, 'coolify.applicationId'); - // if ($applicationId) { - // $pullRequestId = data_get($labels, 'coolify.pullRequestId'); - // if ($pullRequestId) { - // if (str($applicationId)->contains('-')) { - // $applicationId = str($applicationId)->before('-'); - // } - // $preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id', $pullRequestId)->first(); - // if ($preview) { - // $foundApplicationPreviews[] = $preview->id; - // $statusFromDb = $preview->status; - // if ($statusFromDb !== $containerStatus) { - // $preview->update(['status' => $containerStatus]); - // } - // } else { - // //Notify user that this container should not be there. - // } - // } else { - // $application = $applications->where('id', $applicationId)->first(); - // if ($application) { - // $foundApplications[] = $application->id; - // $statusFromDb = $application->status; - // if ($statusFromDb !== $containerStatus) { - // $application->update(['status' => $containerStatus]); - // } - // } else { - // //Notify user that this container should not be there. - // } - // } - // } else { - // $uuid = data_get($labels, 'com.docker.compose.service'); - // $type = data_get($labels, 'coolify.type'); - - // if ($uuid) { - // if ($type === 'service') { - // $database_id = data_get($labels, 'coolify.service.subId'); - // if ($database_id) { - // $service_db = ServiceDatabase::where('id', $database_id)->first(); - // if ($service_db) { - // $uuid = $service_db->service->uuid; - // $isPublic = data_get($service_db, 'is_public'); - // if ($isPublic) { - // $foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) { - // if ($this->server->isSwarm()) { - // return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid"; - // } else { - // return data_get($value, 'Name') === "/$uuid-proxy"; - // } - // })->first(); - // if (!$foundTcpProxy) { - // StartDatabaseProxy::run($service_db); - // // $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$service_db->service->name}", $this->server)); - // } - // } - // } - // } - // } else { - // $database = $databases->where('uuid', $uuid)->first(); - // if ($database) { - // $isPublic = data_get($database, 'is_public'); - // $foundDatabases[] = $database->id; - // $statusFromDb = $database->status; - // if ($statusFromDb !== $containerStatus) { - // $database->update(['status' => $containerStatus]); - // } - // if ($isPublic) { - // $foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) { - // if ($this->server->isSwarm()) { - // return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid"; - // } else { - // return data_get($value, 'Name') === "/$uuid-proxy"; - // } - // })->first(); - // if (!$foundTcpProxy) { - // StartDatabaseProxy::run($database); - // $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server)); - // } - // } - // } else { - // // Notify user that this container should not be there. - // } - // } - // } - // if (data_get($container, 'Name') === '/coolify-db') { - // $foundDatabases[] = 0; - // } - // } - // $serviceLabelId = data_get($labels, 'coolify.serviceId'); - // if ($serviceLabelId) { - // $subType = data_get($labels, 'coolify.service.subType'); - // $subId = data_get($labels, 'coolify.service.subId'); - // $service = $services->where('id', $serviceLabelId)->first(); - // if (!$service) { - // continue; - // } - // if ($subType === 'application') { - // $service = $service->applications()->where('id', $subId)->first(); - // } else { - // $service = $service->databases()->where('id', $subId)->first(); - // } - // if ($service) { - // $foundServices[] = "$service->id-$service->name"; - // $statusFromDb = $service->status; - // if ($statusFromDb !== $containerStatus) { - // // ray('Updating status: ' . $containerStatus); - // $service->update(['status' => $containerStatus]); - // } - // } - // } - // } - // $exitedServices = collect([]); - // foreach ($services as $service) { - // $apps = $service->applications()->get(); - // $dbs = $service->databases()->get(); - // foreach ($apps as $app) { - // if (in_array("$app->id-$app->name", $foundServices)) { - // continue; - // } else { - // $exitedServices->push($app); - // } - // } - // foreach ($dbs as $db) { - // if (in_array("$db->id-$db->name", $foundServices)) { - // continue; - // } else { - // $exitedServices->push($db); - // } - // } - // } - // $exitedServices = $exitedServices->unique('id'); - // foreach ($exitedServices as $exitedService) { - // if (str($exitedService->status)->startsWith('exited')) { - // continue; - // } - // $name = data_get($exitedService, 'name'); - // $fqdn = data_get($exitedService, 'fqdn'); - // $containerName = $name ? "$name, available at $fqdn" : $fqdn; - // $projectUuid = data_get($service, 'environment.project.uuid'); - // $serviceUuid = data_get($service, 'uuid'); - // $environmentName = data_get($service, 'environment.name'); - - // if ($projectUuid && $serviceUuid && $environmentName) { - // $url = base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/service/" . $serviceUuid; - // } else { - // $url = null; - // } - // $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url)); - // $exitedService->update(['status' => 'exited']); - // } - - // $notRunningApplications = $applications->pluck('id')->diff($foundApplications); - // foreach ($notRunningApplications as $applicationId) { - // $application = $applications->where('id', $applicationId)->first(); - // if (str($application->status)->startsWith('exited')) { - // continue; - // } - // $application->update(['status' => 'exited']); - - // $name = data_get($application, 'name'); - // $fqdn = data_get($application, 'fqdn'); - - // $containerName = $name ? "$name ($fqdn)" : $fqdn; - - // $projectUuid = data_get($application, 'environment.project.uuid'); - // $applicationUuid = data_get($application, 'uuid'); - // $environment = data_get($application, 'environment.name'); - - // if ($projectUuid && $applicationUuid && $environment) { - // $url = base_url() . '/project/' . $projectUuid . "/" . $environment . "/application/" . $applicationUuid; - // } else { - // $url = null; - // } - - // $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url)); - // } - // $notRunningApplicationPreviews = $previews->pluck('id')->diff($foundApplicationPreviews); - // foreach ($notRunningApplicationPreviews as $previewId) { - // $preview = $previews->where('id', $previewId)->first(); - // if (str($preview->status)->startsWith('exited')) { - // continue; - // } - // $preview->update(['status' => 'exited']); - - // $name = data_get($preview, 'name'); - // $fqdn = data_get($preview, 'fqdn'); - - // $containerName = $name ? "$name ($fqdn)" : $fqdn; - - // $projectUuid = data_get($preview, 'application.environment.project.uuid'); - // $environmentName = data_get($preview, 'application.environment.name'); - // $applicationUuid = data_get($preview, 'application.uuid'); - - // if ($projectUuid && $applicationUuid && $environmentName) { - // $url = base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/application/" . $applicationUuid; - // } else { - // $url = null; - // } - - // $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url)); - // } - // $notRunningDatabases = $databases->pluck('id')->diff($foundDatabases); - // foreach ($notRunningDatabases as $database) { - // $database = $databases->where('id', $database)->first(); - // if (str($database->status)->startsWith('exited')) { - // continue; - // } - // $database->update(['status' => 'exited']); - - // $name = data_get($database, 'name'); - // $fqdn = data_get($database, 'fqdn'); - - // $containerName = $name; - - // $projectUuid = data_get($database, 'environment.project.uuid'); - // $environmentName = data_get($database, 'environment.name'); - // $databaseUuid = data_get($database, 'uuid'); - - // if ($projectUuid && $databaseUuid && $environmentName) { - // $url = base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/database/" . $databaseUuid; - // } else { - // $url = null; - // } - // $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url)); - // } - - // // Check if proxy is running - // $this->server->proxyType(); - // $foundProxyContainer = $containers->filter(function ($value, $key) { - // if ($this->server->isSwarm()) { - // return data_get($value, 'Spec.Name') === 'coolify-proxy_traefik'; - // } else { - // return data_get($value, 'Name') === '/coolify-proxy'; - // } - // })->first(); - // if (!$foundProxyContainer) { - // try { - // $shouldStart = CheckProxy::run($this->server); - // if ($shouldStart) { - // StartProxy::run($this->server, false); - // $this->server->team?->notify(new ContainerRestarted('coolify-proxy', $this->server)); - // } - // } catch (\Throwable $e) { - // ray($e); - // } - // } else { - // $this->server->proxy->status = data_get($foundProxyContainer, 'State.Status'); - // $this->server->save(); - // $connectProxyToDockerNetworks = connectProxyToNetworks($this->server); - // instant_remote_process($connectProxyToDockerNetworks, $this->server, false); - // } - // } catch (\Throwable $e) { - // send_internal_notification("ContainerStatusJob failed on ({$this->server->id}) with: " . $e->getMessage()); - // ray($e->getMessage()); - // return handleError($e); - // } } } diff --git a/app/Jobs/ScheduledTaskJob.php b/app/Jobs/ScheduledTaskJob.php index 4a38a005b..6e2ad06b0 100644 --- a/app/Jobs/ScheduledTaskJob.php +++ b/app/Jobs/ScheduledTaskJob.php @@ -77,8 +77,12 @@ class ScheduledTaskJob implements ShouldQueue $this->containers[] = data_get($application, 'name') . '-' . data_get($this->resource, 'uuid'); } }); + $this->resource->databases()->get()->each(function ($database) { + if (str(data_get($database, 'status'))->contains('running')) { + $this->containers[] = data_get($database, 'name') . '-' . data_get($this->resource, 'uuid'); + } + }); } - if (count($this->containers) == 0) { throw new \Exception('ScheduledTaskJob failed: No containers running.'); } @@ -89,7 +93,7 @@ class ScheduledTaskJob implements ShouldQueue foreach ($this->containers as $containerName) { if (count($this->containers) == 1 || str_starts_with($containerName, $this->task->container . '-' . $this->resource->uuid)) { - $cmd = 'sh -c "' . str_replace('"', '\"', $this->task->command) . '"'; + $cmd = "sh -c '" . str_replace("'", "'\''", $this->task->command) . "'"; $exec = "docker exec {$containerName} {$cmd}"; $this->task_output = instant_remote_process([$exec], $this->server, true); $this->task_log->update([ diff --git a/app/Jobs/ServerLimitCheckJob.php b/app/Jobs/ServerLimitCheckJob.php index 052260895..9d0e5db94 100644 --- a/app/Jobs/ServerLimitCheckJob.php +++ b/app/Jobs/ServerLimitCheckJob.php @@ -40,7 +40,7 @@ class ServerLimitCheckJob implements ShouldQueue, ShouldBeEncrypted try { $servers = $this->team->servers; $servers_count = $servers->count(); - $limit = $this->team->limits['serverLimit']; + $limit = data_get($this->team->limits, 'serverLimit', 2); $number_of_servers_to_disable = $servers_count - $limit; ray('ServerLimitCheckJob', $this->team->uuid, $servers_count, $limit, $number_of_servers_to_disable); if ($number_of_servers_to_disable > 0) { diff --git a/app/Livewire/Project/Application/General.php b/app/Livewire/Project/Application/General.php index 885f84247..66c59cff5 100644 --- a/app/Livewire/Project/Application/General.php +++ b/app/Livewire/Project/Application/General.php @@ -22,6 +22,7 @@ class General extends Component public ?string $git_commit_sha = null; public string $build_pack; public ?string $ports_exposes = null; + public bool $is_container_label_escape_enabled = true; public $customLabels; public bool $labelsChanged = false; @@ -74,6 +75,7 @@ class General extends Component 'application.post_deployment_command_container' => 'nullable', 'application.settings.is_static' => 'boolean|required', 'application.settings.is_build_server_enabled' => 'boolean|required', + 'application.settings.is_container_label_escape_enabled' => 'boolean|required', 'application.watch_paths' => 'nullable', ]; protected $validationAttributes = [ @@ -109,6 +111,7 @@ class General extends Component 'application.docker_compose_custom_build_command' => 'Docker compose custom build command', 'application.settings.is_static' => 'Is static', 'application.settings.is_build_server_enabled' => 'Is build server enabled', + 'application.settings.is_container_label_escape_enabled' => 'Is container label escape enabled', 'application.watch_paths' => 'Watch paths', ]; public function mount() @@ -124,6 +127,7 @@ class General extends Component } $this->parsedServiceDomains = $this->application->docker_compose_domains ? json_decode($this->application->docker_compose_domains, true) : []; $this->ports_exposes = $this->application->ports_exposes; + $this->is_container_label_escape_enabled = $this->application->settings->is_container_label_escape_enabled; $this->customLabels = $this->application->parseContainerLabels(); if (!$this->customLabels && $this->application->destination->server->proxyType() !== 'NONE') { $this->customLabels = str(implode("|", generateLabelsApplication($this->application)))->replace("|", "\n"); @@ -145,7 +149,7 @@ class General extends Component $this->application->settings->save(); $this->dispatch('success', 'Settings saved.'); $this->application->refresh(); - if ($this->ports_exposes !== $this->application->ports_exposes) { + if ($this->ports_exposes !== $this->application->ports_exposes || $this->is_container_label_escape_enabled !== $this->application->settings->is_container_label_escape_enabled) { $this->resetDefaultLabels(false); } } @@ -204,6 +208,9 @@ class General extends Component $this->application->docker_compose_domains = json_encode($this->parsedServiceDomains); $this->application->save(); $this->dispatch('success', 'Domain generated.'); + if ($this->application->build_pack === 'dockercompose') { + $this->loadComposeFile(); + } return $domain; } public function updatedApplicationBaseDirectory() @@ -257,9 +264,12 @@ class General extends Component { $this->customLabels = str(implode("|", generateLabelsApplication($this->application)))->replace("|", "\n"); $this->ports_exposes = $this->application->ports_exposes; - + $this->is_container_label_escape_enabled = $this->application->settings->is_container_label_escape_enabled; $this->application->custom_labels = base64_encode($this->customLabels); $this->application->save(); + if ($this->application->build_pack === 'dockercompose') { + $this->loadComposeFile(); + } } public function checkFqdns($showToaster = true) @@ -300,11 +310,11 @@ class General extends Component if ($this->application->build_pack === 'dockercompose' && $this->initialDockerComposeLocation !== $this->application->docker_compose_location) { $compose_return = $this->loadComposeFile(); if ($compose_return instanceof \Livewire\Features\SupportEvents\Event) { - return; + return; } } $this->validate(); - if ($this->ports_exposes !== $this->application->ports_exposes) { + if ($this->ports_exposes !== $this->application->ports_exposes || $this->is_container_label_escape_enabled !== $this->application->settings->is_container_label_escape_enabled) { $this->resetDefaultLabels(); } if (data_get($this->application, 'build_pack') === 'dockerimage') { diff --git a/app/Livewire/Project/Service/Configuration.php b/app/Livewire/Project/Service/Configuration.php index 0b26af22f..86c9a8a31 100644 --- a/app/Livewire/Project/Service/Configuration.php +++ b/app/Livewire/Project/Service/Configuration.php @@ -3,7 +3,6 @@ namespace App\Livewire\Project\Service; use App\Actions\Docker\GetContainersStatus; -use App\Jobs\ContainerStatusJob; use App\Models\Service; use Livewire\Component; diff --git a/app/Livewire/Project/Service/EditCompose.php b/app/Livewire/Project/Service/EditCompose.php index 0f9c449f9..d6e867956 100644 --- a/app/Livewire/Project/Service/EditCompose.php +++ b/app/Livewire/Project/Service/EditCompose.php @@ -12,6 +12,7 @@ class EditCompose extends Component protected $rules = [ 'service.docker_compose_raw' => 'required', 'service.docker_compose' => 'required', + 'service.is_container_label_escape_enabled' => 'required', ]; public function mount() { @@ -23,6 +24,14 @@ class EditCompose extends Component $this->dispatch('info', "Saving new docker compose..."); $this->dispatch('saveCompose', $this->service->docker_compose_raw); } + public function instantSave() + { + $this->validate([ + 'service.is_container_label_escape_enabled' => 'required', + ]); + $this->service->save(['is_container_label_escape_enabled' => $this->service->is_container_label_escape_enabled]); + $this->dispatch('success', "Service updated successfully"); + } public function render() { return view('livewire.project.service.edit-compose'); diff --git a/app/Livewire/Project/Shared/ScheduledTask/Add.php b/app/Livewire/Project/Shared/ScheduledTask/Add.php index 3a7a3fa23..c415ff3e4 100644 --- a/app/Livewire/Project/Shared/ScheduledTask/Add.php +++ b/app/Livewire/Project/Shared/ScheduledTask/Add.php @@ -2,11 +2,14 @@ namespace App\Livewire\Project\Shared\ScheduledTask; +use Illuminate\Support\Collection; use Livewire\Component; class Add extends Component { public $parameters; + public string $type; + public Collection $containerNames; public string $name; public string $command; public string $frequency; @@ -29,6 +32,9 @@ class Add extends Component public function mount() { $this->parameters = get_route_parameters(); + if ($this->containerNames->count() > 0) { + $this->container = $this->containerNames->first(); + } } public function submit() @@ -40,6 +46,11 @@ class Add extends Component $this->dispatch('error', 'Invalid Cron / Human expression.'); return; } + if (empty($this->container) || $this->container == 'null') { + if ($this->type == 'service') { + $this->container = $this->subServiceName; + } + } $this->dispatch('saveScheduledTask', [ 'name' => $this->name, 'command' => $this->command, diff --git a/app/Livewire/Project/Shared/ScheduledTask/All.php b/app/Livewire/Project/Shared/ScheduledTask/All.php index 975d695fa..5182639d7 100644 --- a/app/Livewire/Project/Shared/ScheduledTask/All.php +++ b/app/Livewire/Project/Shared/ScheduledTask/All.php @@ -3,14 +3,13 @@ namespace App\Livewire\Project\Shared\ScheduledTask; use App\Models\ScheduledTask; +use Illuminate\Support\Collection; use Livewire\Component; -use Visus\Cuid2\Cuid2; -use Illuminate\Support\Str; class All extends Component { public $resource; - public string|null $modalId = null; + public Collection $containerNames; public ?string $variables = null; public array $parameters; protected $listeners = ['refreshTasks', 'saveScheduledTask' => 'submit']; @@ -18,7 +17,18 @@ class All extends Component public function mount() { $this->parameters = get_route_parameters(); - $this->modalId = new Cuid2(7); + if ($this->resource->type() == 'service') { + $this->containerNames = $this->resource->applications()->pluck('name'); + $this->containerNames = $this->containerNames->merge($this->resource->databases()->pluck('name')); + } elseif ($this->resource->type() == 'application') { + if ($this->resource->build_pack === 'dockercompose') { + $parsed = $this->resource->parseCompose(); + $containers = collect($parsed['services'])->keys(); + $this->containerNames = $containers; + } else { + $this->containerNames = collect([]); + } + } } public function refreshTasks() { diff --git a/app/Livewire/Project/Shared/ScheduledTask/Show.php b/app/Livewire/Project/Shared/ScheduledTask/Show.php index 87b752509..7490c7055 100644 --- a/app/Livewire/Project/Shared/ScheduledTask/Show.php +++ b/app/Livewire/Project/Shared/ScheduledTask/Show.php @@ -17,6 +17,7 @@ class Show extends Component public string $type; protected $rules = [ + 'task.enabled' => 'required|boolean', 'task.name' => 'required|string', 'task.command' => 'required|string', 'task.frequency' => 'required|string', @@ -45,9 +46,18 @@ class Show extends Component $this->task = ModelsScheduledTask::where('uuid', request()->route('task_uuid'))->first(); } + public function instantSave() + { + $this->validateOnly('task.enabled'); + $this->task->save(['enabled' => $this->task->enabled]); + $this->dispatch('success', 'Scheduled task updated.'); + $this->dispatch('refreshTasks'); + } public function submit() { $this->validate(); + $this->task->name = str($this->task->name)->trim()->value(); + $this->task->container = str($this->task->container)->trim()->value(); $this->task->save(); $this->dispatch('success', 'Scheduled task updated.'); $this->dispatch('refreshTasks'); @@ -60,11 +70,9 @@ class Show extends Component if ($this->type == 'application') { return redirect()->route('project.application.configuration', $this->parameters); - } - else { + } else { return redirect()->route('project.service.configuration', $this->parameters); } - } catch (\Exception $e) { return handleError($e); } diff --git a/app/Models/Application.php b/app/Models/Application.php index 1ce33cefb..fd3faa166 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -146,9 +146,13 @@ class Application extends BaseModel if (!is_null($this->source?->html_url) && !is_null($this->git_repository) && !is_null($this->git_branch)) { return "{$this->source->html_url}/{$this->git_repository}/tree/{$this->git_branch}"; } + // Convert the SSH URL to HTTPS URL + if (strpos($this->git_repository, 'git@') === 0) { + $git_repository = str_replace(['git@', ':', '.git'], ['', '/', ''], $this->git_repository); + return "https://{$git_repository}/tree/{$this->git_branch}"; + } return $this->git_repository; } - ); } @@ -159,6 +163,11 @@ class Application extends BaseModel if (!is_null($this->source?->html_url) && !is_null($this->git_repository) && !is_null($this->git_branch)) { return "{$this->source->html_url}/{$this->git_repository}/settings/hooks"; } + // Convert the SSH URL to HTTPS URL + if (strpos($this->git_repository, 'git@') === 0) { + $git_repository = str_replace(['git@', ':', '.git'], ['', '/', ''], $this->git_repository); + return "https://{$git_repository}/settings/hooks"; + } return $this->git_repository; } ); @@ -171,10 +180,26 @@ class Application extends BaseModel if (!is_null($this->source?->html_url) && !is_null($this->git_repository) && !is_null($this->git_branch)) { return "{$this->source->html_url}/{$this->git_repository}/commits/{$this->git_branch}"; } + // Convert the SSH URL to HTTPS URL + if (strpos($this->git_repository, 'git@') === 0) { + $git_repository = str_replace(['git@', ':', '.git'], ['', '/', ''], $this->git_repository); + return "https://{$git_repository}/commits/{$this->git_branch}"; + } return $this->git_repository; } ); } + public function gitCommitLink($link): string + { + if (!is_null($this->source?->html_url) && !is_null($this->git_repository) && !is_null($this->git_branch)) { + return "{$this->source->html_url}/{$this->git_repository}/commit/{$link}"; + } + if (strpos($this->git_repository, 'git@') === 0) { + $git_repository = str_replace(['git@', ':', '.git'], ['', '/', ''], $this->git_repository); + return "https://{$git_repository}/commit/{$link}"; + } + return $this->git_repository; + } public function dockerfileLocation(): Attribute { return Attribute::make( diff --git a/app/Models/ApplicationDeploymentQueue.php b/app/Models/ApplicationDeploymentQueue.php index 7f3f36d0a..c55f89e21 100644 --- a/app/Models/ApplicationDeploymentQueue.php +++ b/app/Models/ApplicationDeploymentQueue.php @@ -9,7 +9,8 @@ class ApplicationDeploymentQueue extends Model { protected $guarded = []; - public function setStatus(string $status) { + public function setStatus(string $status) + { $this->update([ 'status' => $status, ]); @@ -21,7 +22,13 @@ class ApplicationDeploymentQueue extends Model } return collect(json_decode($this->logs))->where('name', $name)->first()?->output ?? null; } - + public function commitMessage() + { + if (empty($this->commit_message) || is_null($this->commit_message)) { + return null; + } + return str($this->commit_message)->trim()->limit(50)->value(); + } public function addLogEntry(string $message, string $type = 'stdout', bool $hidden = false) { if ($type === 'error') { diff --git a/app/Models/Service.php b/app/Models/Service.php index cd8e578d6..d8950137b 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -450,14 +450,16 @@ class Service extends BaseModel $data = collect([]); $admin_user = $this->environment_variables()->where('key', 'SERVICE_USER_ADMIN')->first(); $admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_ADMIN')->first(); - $data = $data->merge([ - 'User' => [ - 'key' => 'SERVICE_USER_ADMIN', - 'value' => data_get($admin_user, 'value', 'admin'), - 'readonly' => true, - 'rules' => 'required', - ], - ]); + if ($admin_user) { + $data = $data->merge([ + 'User' => [ + 'key' => 'SERVICE_USER_ADMIN', + 'value' => data_get($admin_user, 'value', 'admin'), + 'readonly' => true, + 'rules' => 'required', + ], + ]); + } if ($admin_password) { $data = $data->merge([ 'Password' => [ @@ -677,6 +679,17 @@ class Service extends BaseModel { return $this->belongsTo(Server::class); } + public function byUuid(string $uuid) { + $app = $this->applications()->whereUuid($uuid)->first(); + if ($app) { + return $app; + } + $db = $this->databases()->whereUuid($uuid)->first(); + if ($db) { + return $db; + } + return null; + } public function byName(string $name) { $app = $this->applications()->whereName($name)->first(); diff --git a/app/Models/User.php b/app/Models/User.php index 0fa8ead2f..0e66fdaea 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -183,6 +183,7 @@ class User extends Authenticatable implements SendsEmail if (data_get($this, 'pivot')) { return $this->pivot->role; } - return auth()->user()->teams->where('id', currentTeam()->id)->first()->pivot->role; + $user = auth()->user()->teams->where('id', currentTeam()->id)->first(); + return data_get($user, 'pivot.role'); } } diff --git a/bootstrap/helpers/applications.php b/bootstrap/helpers/applications.php index c0aaf4abf..a1995c645 100644 --- a/bootstrap/helpers/applications.php +++ b/bootstrap/helpers/applications.php @@ -6,7 +6,6 @@ use App\Models\Application; use App\Models\ApplicationDeploymentQueue; use App\Models\Server; use App\Models\StandaloneDocker; -use Illuminate\Support\Collection; use Spatie\Url\Url; function queue_application_deployment(Application $application, string $deployment_uuid, int | null $pull_request_id = 0, string $commit = 'HEAD', bool $force_rebuild = false, bool $is_webhook = false, bool $restart_only = false, ?string $git_type = null, bool $no_questions_asked = false, Server $server = null, StandaloneDocker $destination = null, bool $only_this_server = false, bool $rollback = false) diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index b93b0ec3d..7a960066c 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -1174,6 +1174,13 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal ] ]); } + if ($serviceLabels->count() > 0) { + if ($resource->is_container_label_escape_enabled) { + $serviceLabels = $serviceLabels->map(function ($value, $key) { + return escapeDollarSign($value); + }); + } + } data_set($service, 'labels', $serviceLabels->toArray()); data_forget($service, 'is_database'); if (!data_get($service, 'restart')) { @@ -1259,6 +1266,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal $servicePorts = collect(data_get($service, 'ports', [])); $serviceNetworks = collect(data_get($service, 'networks', [])); $serviceVariables = collect(data_get($service, 'environment', [])); + $serviceDependencies = collect(data_get($service, 'depends_on', [])); $serviceLabels = collect(data_get($service, 'labels', [])); $serviceBuildVariables = collect(data_get($service, 'build.args', [])); $serviceVariables = $serviceVariables->merge($serviceBuildVariables); @@ -1275,11 +1283,7 @@ 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) { @@ -1370,6 +1374,13 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal data_set($service, 'volumes', $serviceVolumes->toArray()); } + if ($pull_request_id !== 0 && count($serviceDependencies) > 0) { + $serviceDependencies = $serviceDependencies->map(function ($dependency) use ($pull_request_id) { + return $dependency . "-pr-$pull_request_id"; + }); + data_set($service, 'depends_on', $serviceDependencies->toArray()); + } + // Decide if the service is a database $isDatabase = isDatabaseImage(data_get_str($service, 'image')); data_set($service, 'is_database', $isDatabase); @@ -1652,6 +1663,13 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal ] ]); } + if ($serviceLabels->count() > 0) { + if ($resource->settings->is_container_label_escape_enabled) { + $serviceLabels = $serviceLabels->map(function ($value, $key) { + return escapeDollarSign($value); + }); + } + } data_set($service, 'labels', $serviceLabels->toArray()); data_forget($service, 'is_database'); if (!data_get($service, 'restart')) { diff --git a/config/sentry.php b/config/sentry.php index e65072dcc..4ab6d5b12 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.277', + 'release' => '4.0.0-beta.278', // 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 fea91e1f0..980c395f0 100644 --- a/config/version.php +++ b/config/version.php @@ -1,3 +1,3 @@ longText('stripe_comment')->change(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('subscriptions', function (Blueprint $table) { + $table->string('stripe_comment')->change(); + }); + } +}; diff --git a/database/migrations/2024_05_15_091757_add_commit_message_to_app_deployment_queue.php b/database/migrations/2024_05_15_091757_add_commit_message_to_app_deployment_queue.php new file mode 100644 index 000000000..78608f503 --- /dev/null +++ b/database/migrations/2024_05_15_091757_add_commit_message_to_app_deployment_queue.php @@ -0,0 +1,28 @@ +string('commit_message', 50)->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('application_deployment_queues', function (Blueprint $table) { + $table->dropColumn('commit_message'); + }); + } +}; diff --git a/database/migrations/2024_05_15_151236_add_container_escape_toggle.php b/database/migrations/2024_05_15_151236_add_container_escape_toggle.php new file mode 100644 index 000000000..aa1384518 --- /dev/null +++ b/database/migrations/2024_05_15_151236_add_container_escape_toggle.php @@ -0,0 +1,34 @@ +boolean('is_container_label_escape_enabled')->default(true); + }); + Schema::table('services', function (Blueprint $table) { + $table->boolean('is_container_label_escape_enabled')->default(true); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('application_settings', function (Blueprint $table) { + $table->dropColumn('is_container_label_escape_enabled'); + }); + Schema::table('services', function (Blueprint $table) { + $table->dropColumn('is_container_label_escape_enabled'); + }); + } +}; diff --git a/docker/coolify-helper/Dockerfile b/docker/coolify-helper/Dockerfile index 5798c92bd..768d2ca89 100644 --- a/docker/coolify-helper/Dockerfile +++ b/docker/coolify-helper/Dockerfile @@ -2,15 +2,15 @@ FROM alpine:3.17 ARG TARGETPLATFORM # https://download.docker.com/linux/static/stable/ -ARG DOCKER_VERSION=24.0.9 +ARG DOCKER_VERSION=26.1.2 # https://github.com/docker/compose/releases -ARG DOCKER_COMPOSE_VERSION=2.25.0 +ARG DOCKER_COMPOSE_VERSION=2.27.0 # https://github.com/docker/buildx/releases -ARG DOCKER_BUILDX_VERSION=0.13.1 +ARG DOCKER_BUILDX_VERSION=0.14.0 # https://github.com/buildpacks/pack/releases ARG PACK_VERSION=0.33.2 # https://github.com/railwayapp/nixpacks/releases -ARG NIXPACKS_VERSION=1.21.2 +ARG NIXPACKS_VERSION=1.21.3 USER root WORKDIR /artifacts diff --git a/docker/dev-ssu/Dockerfile b/docker/dev-ssu/Dockerfile index 0c7ce2b2a..f0e353d28 100644 --- a/docker/dev-ssu/Dockerfile +++ b/docker/dev-ssu/Dockerfile @@ -2,7 +2,7 @@ FROM serversideup/php:8.2-fpm-nginx-v2.2.1 ARG TARGETPLATFORM # https://github.com/cloudflare/cloudflared/releases -ARG CLOUDFLARED_VERSION=2024.2.1 +ARG CLOUDFLARED_VERSION=2024.4.1 ARG POSTGRES_VERSION=15 RUN apt-get update diff --git a/docker/prod-ssu/Dockerfile b/docker/prod-ssu/Dockerfile index dcc1c334d..2192f4f0e 100644 --- a/docker/prod-ssu/Dockerfile +++ b/docker/prod-ssu/Dockerfile @@ -15,7 +15,7 @@ FROM serversideup/php:8.2-fpm-nginx-v2.2.1 ARG TARGETPLATFORM # https://github.com/cloudflare/cloudflared/releases -ARG CLOUDFLARED_VERSION=2024.2.1 +ARG CLOUDFLARED_VERSION=2024.4.1 ARG POSTGRES_VERSION=15 WORKDIR /var/www/html diff --git a/docker/testing-host/Dockerfile b/docker/testing-host/Dockerfile index 6d0d0d5c5..deb09eeba 100644 --- a/docker/testing-host/Dockerfile +++ b/docker/testing-host/Dockerfile @@ -2,11 +2,11 @@ FROM debian:12-slim ARG TARGETPLATFORM # https://download.docker.com/linux/static/stable/ -ARG DOCKER_VERSION=24.0.5 +ARG DOCKER_VERSION=26.1.2 # https://github.com/docker/compose/releases -ARG DOCKER_COMPOSE_VERSION=2.21.0 +ARG DOCKER_COMPOSE_VERSION=2.27.0 # https://github.com/docker/buildx/releases -ARG DOCKER_BUILDX_VERSION=0.11.2 +ARG DOCKER_BUILDX_VERSION=0.14.0 USER root WORKDIR /root diff --git a/public/svgs/twenty.svg b/public/svgs/twenty.svg new file mode 100644 index 000000000..eef3a382a --- /dev/null +++ b/public/svgs/twenty.svg @@ -0,0 +1 @@ + diff --git a/resources/views/destination/all.blade.php b/resources/views/destination/all.blade.php index 7de97eb46..bb1b99bf5 100644 --- a/resources/views/destination/all.blade.php +++ b/resources/views/destination/all.blade.php @@ -11,22 +11,22 @@
@forelse ($destinations as $destination) @if ($destination->getMorphClass() === 'App\Models\StandaloneDocker') -
- -
{{ $destination->name }}
-
server: {{ $destination->server->name }}
+
+
{{ $destination->name }}
+
server: {{ $destination->server->name }}
+
-
@endif @if ($destination->getMorphClass() === 'App\Models\SwarmDocker') -
- -
{{ $destination->name }}
-
server: {{ $destination->server->name }}
+
+
{{ $destination->name }}
+
server: {{ $destination->server->name }}
+
-
@endif @empty
diff --git a/resources/views/livewire/boarding/index.blade.php b/resources/views/livewire/boarding/index.blade.php index 29bddbbaf..3df18ff92 100644 --- a/resources/views/livewire/boarding/index.blade.php +++ b/resources/views/livewire/boarding/index.blade.php @@ -57,8 +57,7 @@ Localhost is not reachable with the following public key.

Please make sure you have the correct public key in your ~/.ssh/authorized_keys file for - user - 'root' or skip the boarding process and add a new private key manually to Coolify and to the + user or skip the boarding process and add a new private key manually to Coolify and to the server.
Check this
Please make sure you have the correct public key in your ~/.ssh/authorized_keys file for - user - 'root' or skip the boarding process and add a new private key manually to Coolify and to the + user or skip the boarding process and add a new private key manually to Coolify and to the server. +
+ Check this
documentation for further + help. Check @@ -229,10 +231,6 @@ Continue - -

Username should be for now. We are working on to use - non-root users.

-
@elseif ($currentState === 'validate-server') diff --git a/resources/views/livewire/layout-popups.blade.php b/resources/views/livewire/layout-popups.blade.php index 3f4436b8b..b2cc76f2f 100644 --- a/resources/views/livewire/layout-popups.blade.php +++ b/resources/views/livewire/layout-popups.blade.php @@ -15,7 +15,7 @@ checkPusherInterval = setInterval(() => { if (window.Echo && window.Echo.connector.pusher.connection.state !== 'connected') { checkNumber++; - if (checkNumber > 4) { + if (checkNumber > 5) { this.popups.realtime = true; console.error( 'Coolify could not connect to its real-time service. This will cause unusual problems on the UI if not fixed! Please check the related documentation (https://coolify.io/docs/knowledge-base/cloudflare/tunnels) or get help on Discord (https://coollabs.io/discord).)' @@ -23,31 +23,36 @@ clearInterval(checkPusherInterval); } } - }, 1000); + }, 2000); } } }"> @auth - - - WARNING: Realtime Error?! - - - Coolify could not connect to its real-time service.
This will cause unusual problems on the UI - if - not fixed!

- Please ensure that you have opened the - required ports, - check the - related documentation or get - help on Discord.
-
- - Acknowledge & Disable This Popup - -
+ @if (!isCloud()) + + + WARNING: Realtime Error?! + + + Coolify could not connect to its real-time service.
This will cause unusual problems on the + UI + if + not fixed!

+ Please ensure that you have opened the + required ports, + check the + related documentation or get + help on Discord. +
+
+ + Acknowledge & Disable This Popup + +
+ @endif
@endauth diff --git a/resources/views/livewire/project/application/deployment/index.blade.php b/resources/views/livewire/project/application/deployment/index.blade.php index 9c5e44080..ea7262a27 100644 --- a/resources/views/livewire/project/application/deployment/index.blade.php +++ b/resources/views/livewire/project/application/deployment/index.blade.php @@ -27,18 +27,15 @@ @endif @forelse ($deployments as $deployment) - - data_get($deployment, 'status') === 'queued', - 'border-warning hover:bg-warning hover:text-black' => +
data_get($deployment, 'status') === 'in_progress' || data_get($deployment, 'status') === 'cancelled-by-user', - 'border-error dark:hover:bg-error hover:bg-neutral-200' => - data_get($deployment, 'status') === 'failed', - 'border-success dark:hover:bg-success hover:bg-neutral-200' => - data_get($deployment, 'status') === 'finished', - ]) href="{{ $current_url . '/' . data_get($deployment, 'deployment_uuid') }}"> + 'border-error' => data_get($deployment, 'status') === 'failed', + 'border-success' => data_get($deployment, 'status') === 'finished', + ]) + x-on:click.stop="goto('{{ $current_url . '/' . data_get($deployment, 'deployment_uuid') }}')">
{{ $deployment->created_at }} UTC @@ -64,11 +61,27 @@ @endif
@else -
- Manual +
+ @if (data_get($deployment, 'rollback') === true) + Rollback + @else + Manual + @endif + @if (data_get($deployment, 'commit')) +
+
+ @if ($deployment->commitMessage()) + ({{data_get_str($deployment, 'commit')->limit(7)}} - {{ $deployment->commitMessage() }}) + @else + {{ data_get_str($deployment, 'commit')->limit(7) }} + @endif +
+
+ @endif
@endif - @if (data_get($deployment, 'server_name')) + @if (data_get($deployment, 'server_name') && $application->additional_servers->count() > 0)
Server: {{ data_get($deployment, 'server_name') }}
@@ -85,15 +98,19 @@ 0s
-
+
@empty
No deployments found
@endforelse + @if ($deployments_count > 0)