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+)
+
+
+
+
@@ -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 @@
Username should be