diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index 2d354057e..4f12f436c 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -4,5 +4,5 @@ contact_links:
url: https://coollabs.io/discord
about: Reach out to us on Discord.
- name: 🙋♂️ Feature Requests
- url: https://github.com/coollabsio/coolify/discussions/categories/feature-requests-ideas
+ url: https://github.com/coollabsio/coolify/discussions/categories/new-features
about: All feature requests will be discussed here.
diff --git a/.github/workflows/fix-php-code-style-issues.yml b/.github/workflows/fix-php-code-style-issues
similarity index 100%
rename from .github/workflows/fix-php-code-style-issues.yml
rename to .github/workflows/fix-php-code-style-issues
diff --git a/.github/workflows/pr-build.yml b/.github/workflows/pr-build.yml
new file mode 100644
index 000000000..cf2fae8f3
--- /dev/null
+++ b/.github/workflows/pr-build.yml
@@ -0,0 +1,81 @@
+name: PR Build (v4)
+
+on:
+ pull_request:
+ types:
+ - opened
+ branches-ignore: ["main", "v3"]
+ paths-ignore:
+ - .github/workflows/coolify-helper.yml
+ - docker/coolify-helper/Dockerfile
+
+env:
+ REGISTRY: ghcr.io
+ IMAGE_NAME: "coollabsio/coolify"
+
+jobs:
+ amd64:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: Login to ghcr.io
+ uses: docker/login-action@v3
+ with:
+ registry: ${{ env.REGISTRY }}
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+ - name: Build image and push to registry
+ uses: docker/build-push-action@v5
+ with:
+ context: .
+ file: docker/prod/Dockerfile
+ platforms: linux/amd64
+ push: true
+ tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.number }}
+ aarch64:
+ runs-on: [self-hosted, arm64]
+ permissions:
+ contents: read
+ packages: write
+ steps:
+ - uses: actions/checkout@v4
+ - name: Login to ghcr.io
+ uses: docker/login-action@v3
+ with:
+ registry: ${{ env.REGISTRY }}
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+ - name: Build image and push to registry
+ uses: docker/build-push-action@v5
+ with:
+ context: .
+ file: docker/prod/Dockerfile
+ platforms: linux/aarch64
+ push: true
+ tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.number }}-aarch64
+ merge-manifest:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ packages: write
+ needs: [amd64, aarch64]
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ - name: Set up QEMU
+ uses: docker/setup-qemu-action@v3
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+ - name: Login to ghcr.io
+ uses: docker/login-action@v3
+ with:
+ registry: ${{ env.REGISTRY }}
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+ - name: Create & publish manifest
+ run: |
+ docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.number }}-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.number }}
+ - uses: sarisia/actions-status-discord@v1
+ if: always()
+ with:
+ webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
diff --git a/app/Actions/Application/StopApplication.php b/app/Actions/Application/StopApplication.php
index 1f05e29ac..7155f9a0a 100644
--- a/app/Actions/Application/StopApplication.php
+++ b/app/Actions/Application/StopApplication.php
@@ -31,15 +31,13 @@ class StopApplication
} else {
$containers = getCurrentApplicationContainerStatus($server, $application->id, 0);
}
- ray($containers);
if ($containers->count() > 0) {
foreach ($containers as $container) {
$containerName = data_get($container, 'Names');
if ($containerName) {
- instant_remote_process(
- ["docker rm -f {$containerName}"],
- $server
- );
+ instant_remote_process(command: ["docker stop --time=30 $containerName"], server: $server, throwError: false);
+ instant_remote_process(command: ["docker rm $containerName"], server: $server, throwError: false);
+ instant_remote_process(command: ["docker rm -f {$containerName}"], server: $server, throwError: false);
}
}
}
diff --git a/app/Actions/Database/StopDatabase.php b/app/Actions/Database/StopDatabase.php
index e4903ff35..d562ec56f 100644
--- a/app/Actions/Database/StopDatabase.php
+++ b/app/Actions/Database/StopDatabase.php
@@ -22,10 +22,11 @@ class StopDatabase
if (! $server->isFunctional()) {
return 'Server is not functional';
}
- instant_remote_process(
- ["docker rm -f {$database->uuid}"],
- $server
- );
+
+ instant_remote_process(command: ["docker stop --time=30 $database->uuid"], server: $server, throwError: false);
+ instant_remote_process(command: ["docker rm $database->uuid"], server: $server, throwError: false);
+ instant_remote_process(command: ["docker rm -f $database->uuid"], server: $server, throwError: false);
+
if ($database->is_public) {
StopDatabaseProxy::run($database);
}
diff --git a/app/Actions/Docker/GetContainersStatus.php b/app/Actions/Docker/GetContainersStatus.php
index 9b32e89f3..fdaa88ebf 100644
--- a/app/Actions/Docker/GetContainersStatus.php
+++ b/app/Actions/Docker/GetContainersStatus.php
@@ -12,6 +12,7 @@ use App\Models\ServiceDatabase;
use App\Notifications\Container\ContainerRestarted;
use App\Notifications\Container\ContainerStopped;
use Illuminate\Support\Arr;
+use Illuminate\Support\Collection;
use Lorisleiva\Actions\Concerns\AsAction;
class GetContainersStatus
@@ -20,13 +21,16 @@ class GetContainersStatus
public $applications;
+ public ?Collection $containers;
+
+ public ?Collection $containerReplicates;
+
public $server;
- public function handle(Server $server)
+ public function handle(Server $server, ?Collection $containers = null, ?Collection $containerReplicates = null)
{
- // if (isDev()) {
- // $server = Server::find(0);
- // }
+ $this->containers = $containers;
+ $this->containerReplicates = $containerReplicates;
$this->server = $server;
if (! $this->server->isFunctional()) {
return 'Server is not ready.';
@@ -66,322 +70,312 @@ class GetContainersStatus
// }
}
- private function sentinel()
- {
- try {
- $containers = $this->server->getContainers();
- if ($containers->count() === 0) {
- return;
- }
- $databases = $this->server->databases();
- $services = $this->server->services()->get();
- $previews = $this->server->previews();
- $foundApplications = [];
- $foundApplicationPreviews = [];
- $foundDatabases = [];
- $foundServices = [];
+ // private function sentinel()
+ // {
+ // try {
+ // $this->containers = $this->server->getContainersWithSentinel();
+ // if ($this->containers->count() === 0) {
+ // return;
+ // }
+ // $databases = $this->server->databases();
+ // $services = $this->server->services()->get();
+ // $previews = $this->server->previews();
+ // $foundApplications = [];
+ // $foundApplicationPreviews = [];
+ // $foundDatabases = [];
+ // $foundServices = [];
- foreach ($containers as $container) {
- $labels = Arr::undot(data_get($container, 'labels'));
- $containerStatus = data_get($container, 'state');
- $containerHealth = data_get($container, 'health_status', 'unhealthy');
- $containerStatus = "$containerStatus ($containerHealth)";
- $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 = $this->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()) {
- // TODO: fix this with sentinel
- 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()) {
- // TODO: fix this with sentinel
- 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');
- if ($name) {
- if ($fqdn) {
- $containerName = "$name, available at $fqdn";
- } else {
- $containerName = $name;
- }
- } else {
- if ($fqdn) {
- $containerName = $fqdn;
- } else {
- $containerName = null;
- }
- }
- $projectUuid = data_get($service, 'environment.project.uuid');
- $serviceUuid = data_get($service, 'uuid');
- $environmentName = data_get($service, 'environment.name');
+ // foreach ($this->containers as $container) {
+ // $labels = Arr::undot(data_get($container, 'labels'));
+ // $containerStatus = data_get($container, 'state');
+ // $containerHealth = data_get($container, 'health_status', 'unhealthy');
+ // $containerStatus = "$containerStatus ($containerHealth)";
+ // $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 = $this->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 = $this->containers->filter(function ($value, $key) use ($uuid) {
+ // if ($this->server->isSwarm()) {
+ // // TODO: fix this with sentinel
+ // 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 = $this->containers->filter(function ($value, $key) use ($uuid) {
+ // if ($this->server->isSwarm()) {
+ // // TODO: fix this with sentinel
+ // 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');
+ // if ($name) {
+ // if ($fqdn) {
+ // $containerName = "$name, available at $fqdn";
+ // } else {
+ // $containerName = $name;
+ // }
+ // } else {
+ // if ($fqdn) {
+ // $containerName = $fqdn;
+ // } else {
+ // $containerName = null;
+ // }
+ // }
+ // $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']);
- }
+ // 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 = $this->applications->pluck('id')->diff($foundApplications);
- foreach ($notRunningApplications as $applicationId) {
- $application = $this->applications->where('id', $applicationId)->first();
- if (str($application->status)->startsWith('exited')) {
- continue;
- }
- $application->update(['status' => 'exited']);
+ // $notRunningApplications = $this->applications->pluck('id')->diff($foundApplications);
+ // foreach ($notRunningApplications as $applicationId) {
+ // $application = $this->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');
+ // $name = data_get($application, 'name');
+ // $fqdn = data_get($application, 'fqdn');
- $containerName = $name ? "$name ($fqdn)" : $fqdn;
+ // $containerName = $name ? "$name ($fqdn)" : $fqdn;
- $projectUuid = data_get($application, 'environment.project.uuid');
- $applicationUuid = data_get($application, 'uuid');
- $environment = data_get($application, 'environment.name');
+ // $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;
- }
+ // 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']);
+ // // $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');
+ // $name = data_get($preview, 'name');
+ // $fqdn = data_get($preview, 'fqdn');
- $containerName = $name ? "$name ($fqdn)" : $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');
+ // $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;
- }
+ // 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']);
+ // // $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');
+ // $name = data_get($database, 'name');
+ // $fqdn = data_get($database, 'fqdn');
- $containerName = $name;
+ // $containerName = $name;
- $projectUuid = data_get($database, 'environment.project.uuid');
- $environmentName = data_get($database, 'environment.name');
- $databaseUuid = data_get($database, 'uuid');
+ // $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));
- }
+ // 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()) {
- // TODO: fix this with sentinel
- 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');
- $this->server->save();
- $connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
- instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
- }
- } catch (\Exception $e) {
- // send_internal_notification("ContainerStatusJob failed on ({$this->server->id}) with: " . $e->getMessage());
- ray($e->getMessage());
+ // // Check if proxy is running
+ // $this->server->proxyType();
+ // $foundProxyContainer = $this->containers->filter(function ($value, $key) {
+ // if ($this->server->isSwarm()) {
+ // // TODO: fix this with sentinel
+ // 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');
+ // $this->server->save();
+ // $connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
+ // instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
+ // }
+ // } catch (\Exception $e) {
+ // // send_internal_notification("ContainerStatusJob failed on ({$this->server->id}) with: " . $e->getMessage());
+ // ray($e->getMessage());
- return handleError($e);
- }
- }
+ // return handleError($e);
+ // }
+ // }
private function old_way()
{
- 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 ($this->containers === null) {
+ ['containers' => $this->containers,'containerReplicates' => $this->containerReplicates] = $this->server->getContainers();
}
- if (is_null($containers)) {
+
+ if (is_null($this->containers)) {
return;
}
- $containers = format_docker_command_output_to_json($containers);
- if ($containerReplicates) {
- $containerReplicates = format_docker_command_output_to_json($containerReplicates);
- foreach ($containerReplicates as $containerReplica) {
+ if ($this->containerReplicates) {
+ foreach ($this->containerReplicates as $containerReplica) {
$name = data_get($containerReplica, 'Name');
- $containers = $containers->map(function ($container) use ($name, $containerReplica) {
+ $this->containers = $this->containers->map(function ($container) use ($name, $containerReplica) {
if (data_get($container, 'Spec.Name') === $name) {
$replicas = data_get($containerReplica, 'Replicas');
$running = str($replicas)->explode('/')[0];
@@ -407,7 +401,7 @@ class GetContainersStatus
$foundDatabases = [];
$foundServices = [];
- foreach ($containers as $container) {
+ foreach ($this->containers as $container) {
if ($this->server->isSwarm()) {
$labels = data_get($container, 'Spec.Labels');
$uuid = data_get($labels, 'coolify.name');
@@ -461,7 +455,7 @@ class GetContainersStatus
if ($uuid) {
$isPublic = data_get($service_db, 'is_public');
if ($isPublic) {
- $foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
+ $foundTcpProxy = $this->containers->filter(function ($value, $key) use ($uuid) {
if ($this->server->isSwarm()) {
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
} else {
@@ -486,7 +480,7 @@ class GetContainersStatus
$database->update(['status' => $containerStatus]);
}
if ($isPublic) {
- $foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
+ $foundTcpProxy = $this->containers->filter(function ($value, $key) use ($uuid) {
if ($this->server->isSwarm()) {
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
} else {
@@ -659,7 +653,7 @@ class GetContainersStatus
// Check if proxy is running
$this->server->proxyType();
- $foundProxyContainer = $containers->filter(function ($value, $key) {
+ $foundProxyContainer = $this->containers->filter(function ($value, $key) {
if ($this->server->isSwarm()) {
return data_get($value, 'Spec.Name') === 'coolify-proxy_traefik';
} else {
diff --git a/app/Actions/Server/CleanupDocker.php b/app/Actions/Server/CleanupDocker.php
index 35cab4d11..0009e001d 100644
--- a/app/Actions/Server/CleanupDocker.php
+++ b/app/Actions/Server/CleanupDocker.php
@@ -11,7 +11,6 @@ class CleanupDocker
public function handle(Server $server, bool $force = true)
{
-
// cleanup docker images, containers, and builder caches
if ($force) {
instant_remote_process(['docker image prune -af'], $server, false);
@@ -22,15 +21,5 @@ class CleanupDocker
instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $server, false);
instant_remote_process(['docker builder prune -f'], $server, false);
}
- // cleanup networks
- // $networks = collectDockerNetworksByServer($server);
- // $proxyNetworks = collectProxyDockerNetworksByServer($server);
- // $diff = $proxyNetworks->diff($networks);
- // if ($diff->count() > 0) {
- // $diff->map(function ($network) use ($server) {
- // instant_remote_process(["docker network disconnect $network coolify-proxy"], $server);
- // instant_remote_process(["docker network rm $network"], $server);
- // });
- // }
}
}
diff --git a/app/Actions/Server/InstallLogDrain.php b/app/Actions/Server/InstallLogDrain.php
index 6f74e020b..034d89fe7 100644
--- a/app/Actions/Server/InstallLogDrain.php
+++ b/app/Actions/Server/InstallLogDrain.php
@@ -24,12 +24,7 @@ class InstallLogDrain
}
try {
if ($type === 'none') {
- $command = [
- "echo 'Stopping old Fluent Bit'",
- 'docker rm -f coolify-log-drain || true',
- ];
-
- return instant_remote_process($command, $server);
+ return 'No log drain is enabled.';
} elseif ($type === 'newrelic') {
if (! $server->settings->is_logdrain_newrelic_enabled) {
throw new \Exception('New Relic log drain is not enabled.');
diff --git a/app/Actions/Server/StopLogDrain.php b/app/Actions/Server/StopLogDrain.php
new file mode 100644
index 000000000..a5bce94a5
--- /dev/null
+++ b/app/Actions/Server/StopLogDrain.php
@@ -0,0 +1,20 @@
+server = Server::find(0);
if (! $this->server) {
return;
@@ -48,7 +47,6 @@ class UpdateCoolify
private function update()
{
if (isDev()) {
- ray('Running in dev mode');
remote_process([
'sleep 10',
], $this->server);
diff --git a/app/Actions/Service/StopService.php b/app/Actions/Service/StopService.php
index c7b1170a7..3dd91b4e2 100644
--- a/app/Actions/Service/StopService.php
+++ b/app/Actions/Service/StopService.php
@@ -19,18 +19,21 @@ class StopService
ray('Stopping service: '.$service->name);
$applications = $service->applications()->get();
foreach ($applications as $application) {
- instant_remote_process(["docker rm -f {$application->name}-{$service->uuid}"], $service->server, false);
+ instant_remote_process(command: ["docker stop --time=30 {$application->name}-{$service->uuid}"], server: $server, throwError: false);
+ instant_remote_process(command: ["docker rm {$application->name}-{$service->uuid}"], server: $server, throwError: false);
+ instant_remote_process(command: ["docker rm -f {$application->name}-{$service->uuid}"], server: $server, throwError: false);
$application->update(['status' => 'exited']);
}
$dbs = $service->databases()->get();
foreach ($dbs as $db) {
- instant_remote_process(["docker rm -f {$db->name}-{$service->uuid}"], $service->server, false);
+ instant_remote_process(command: ["docker stop --time=30 {$db->name}-{$service->uuid}"], server: $server, throwError: false);
+ instant_remote_process(command: ["docker rm {$db->name}-{$service->uuid}"], server: $server, throwError: false);
+ instant_remote_process(command: ["docker rm -f {$db->name}-{$service->uuid}"], server: $server, throwError: false);
$db->update(['status' => 'exited']);
}
instant_remote_process(["docker network disconnect {$service->uuid} coolify-proxy"], $service->server);
instant_remote_process(["docker network rm {$service->uuid}"], $service->server);
} catch (\Exception $e) {
- echo $e->getMessage();
ray($e->getMessage());
return $e->getMessage();
diff --git a/app/Console/Commands/CleanupDatabase.php b/app/Console/Commands/CleanupDatabase.php
index 1e177ca62..6f130626b 100644
--- a/app/Console/Commands/CleanupDatabase.php
+++ b/app/Console/Commands/CleanupDatabase.php
@@ -18,7 +18,12 @@ class CleanupDatabase extends Command
} else {
echo "Running database cleanup in dry-run mode...\n";
}
- $keep_days = 60;
+ if (isCloud()) {
+ // Later on we can increase this to 180 days or dynamically set
+ $keep_days = 60;
+ } else {
+ $keep_days = 60;
+ }
echo "Keep days: $keep_days\n";
// Cleanup failed jobs table
$failed_jobs = DB::table('failed_jobs')->where('failed_at', '<', now()->subDays(1));
diff --git a/app/Console/Commands/Init.php b/app/Console/Commands/Init.php
index 4a276cfc4..30d761a10 100644
--- a/app/Console/Commands/Init.php
+++ b/app/Console/Commands/Init.php
@@ -3,6 +3,7 @@
namespace App\Console\Commands;
use App\Actions\Server\StopSentinel;
+use App\Enums\ActivityTypes;
use App\Enums\ApplicationDeploymentStatus;
use App\Jobs\CleanupHelperContainersJob;
use App\Models\ApplicationDeploymentQueue;
@@ -12,22 +13,27 @@ use App\Models\ScheduledDatabaseBackup;
use App\Models\Server;
use App\Models\StandalonePostgresql;
use Illuminate\Console\Command;
+use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Http;
class Init extends Command
{
- protected $signature = 'app:init {--full-cleanup} {--cleanup-deployments}';
+ protected $signature = 'app:init {--full-cleanup} {--cleanup-deployments} {--cleanup-proxy-networks}';
protected $description = 'Cleanup instance related stuffs';
+ public $servers = null;
+
public function handle()
{
+ $this->servers = Server::all();
$this->alive();
get_public_ips();
if (version_compare('4.0.0-beta.312', config('version'), '<=')) {
- $servers = Server::all();
- foreach ($servers as $server) {
- $server->settings->update(['is_metrics_enabled' => false]);
+ foreach ($this->servers as $server) {
+ if ($server->settings->is_metrics_enabled === true) {
+ $server->settings->update(['is_metrics_enabled' => false]);
+ }
if ($server->isFunctional()) {
StopSentinel::dispatch($server);
}
@@ -36,7 +42,7 @@ class Init extends Command
$full_cleanup = $this->option('full-cleanup');
$cleanup_deployments = $this->option('cleanup-deployments');
-
+ $cleanup_proxy_networks = $this->option('cleanup-proxy-networks');
$this->replace_slash_in_environment_name();
if ($cleanup_deployments) {
echo "Running cleanup deployments.\n";
@@ -44,17 +50,26 @@ class Init extends Command
return;
}
+ if ($cleanup_proxy_networks) {
+ echo "Running cleanup proxy networks.\n";
+ $this->cleanup_unused_network_from_coolify_proxy();
+
+ return;
+ }
if ($full_cleanup) {
// Required for falsely deleted coolify db
$this->restore_coolify_db_backup();
+ $this->update_traefik_labels();
+ $this->cleanup_unused_network_from_coolify_proxy();
+ $this->cleanup_unnecessary_dynamic_proxy_configuration();
$this->cleanup_in_progress_application_deployments();
$this->cleanup_stucked_helper_containers();
$this->call('cleanup:queue');
$this->call('cleanup:stucked-resources');
if (! isCloud()) {
try {
- $server = Server::find(0)->first();
- $server->setupDynamicProxyConfiguration();
+ $localhost = $this->servers->where('id', 0)->first();
+ $localhost->setupDynamicProxyConfiguration();
} catch (\Throwable $e) {
echo "Could not setup dynamic configuration: {$e->getMessage()}\n";
}
@@ -68,6 +83,13 @@ class Init extends Command
$settings->update(['is_auto_update_enabled' => false]);
}
}
+ if (isCloud()) {
+ $response = Http::retry(3, 1000)->get(config('constants.services.official'));
+ if ($response->successful()) {
+ $services = $response->json();
+ File::put(base_path('templates/service-templates.json'), json_encode($services));
+ }
+ }
return;
}
@@ -75,6 +97,79 @@ class Init extends Command
$this->call('cleanup:stucked-resources');
}
+ private function update_traefik_labels()
+ {
+ try {
+ Server::where('proxy->type', 'TRAEFIK_V2')->update(['proxy->type' => 'TRAEFIK']);
+ } catch (\Throwable $e) {
+ echo "Error in updating traefik labels: {$e->getMessage()}\n";
+ }
+ }
+
+ private function cleanup_unnecessary_dynamic_proxy_configuration()
+ {
+ if (isCloud()) {
+ foreach ($this->servers as $server) {
+ try {
+ if (! $server->isFunctional()) {
+ continue;
+ }
+ if ($server->id === 0) {
+ continue;
+ }
+ $file = $server->proxyPath().'/dynamic/coolify.yaml';
+
+ return instant_remote_process([
+ "rm -f $file",
+ ], $server, false);
+ } catch (\Throwable $e) {
+ echo "Error in cleaning up unnecessary dynamic proxy configuration: {$e->getMessage()}\n";
+ }
+
+ }
+ }
+ }
+
+ private function cleanup_unused_network_from_coolify_proxy()
+ {
+ foreach ($this->servers as $server) {
+ if (! $server->isFunctional()) {
+ continue;
+ }
+ if (! $server->isProxyShouldRun()) {
+ continue;
+ }
+ try {
+ ['networks' => $networks, 'allNetworks' => $allNetworks] = collectDockerNetworksByServer($server);
+ $removeNetworks = $allNetworks->diff($networks);
+ $commands = collect();
+ foreach ($removeNetworks as $network) {
+ $out = instant_remote_process(["docker network inspect -f json $network | jq '.[].Containers | if . == {} then null else . end'"], $server, false);
+ if (empty($out)) {
+ $commands->push("docker network disconnect $network coolify-proxy >/dev/null 2>&1 || true");
+ $commands->push("docker network rm $network >/dev/null 2>&1 || true");
+ } else {
+ $data = collect(json_decode($out, true));
+ if ($data->count() === 1) {
+ // If only coolify-proxy itself is connected to that network (it should not be possible, but who knows)
+ $isCoolifyProxyItself = data_get($data->first(), 'Name') === 'coolify-proxy';
+ if ($isCoolifyProxyItself) {
+ $commands->push("docker network disconnect $network coolify-proxy >/dev/null 2>&1 || true");
+ $commands->push("docker network rm $network >/dev/null 2>&1 || true");
+ }
+ }
+ }
+ }
+ if ($commands->isNotEmpty()) {
+ echo "Cleaning up unused networks from coolify proxy\n";
+ remote_process(command: $commands, type: ActivityTypes::INLINE->value, server: $server, ignore_errors: false);
+ }
+ } catch (\Throwable $e) {
+ echo "Error in cleaning up unused networks from coolify proxy: {$e->getMessage()}\n";
+ }
+ }
+ }
+
private function restore_coolify_db_backup()
{
try {
@@ -102,8 +197,7 @@ class Init extends Command
private function cleanup_stucked_helper_containers()
{
- $servers = Server::all();
- foreach ($servers as $server) {
+ foreach ($this->servers as $server) {
if ($server->isFunctional()) {
CleanupHelperContainersJob::dispatch($server);
}
@@ -148,7 +242,6 @@ class Init extends Command
private function cleanup_in_progress_application_deployments()
{
// Cleanup any failed deployments
-
try {
if (isCloud()) {
return;
diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php
index c6ee39524..e8f213b16 100644
--- a/app/Console/Kernel.php
+++ b/app/Console/Kernel.php
@@ -2,9 +2,8 @@
namespace App\Console;
-use App\Jobs\CheckLogDrainContainerJob;
+use App\Jobs\CheckForUpdatesJob;
use App\Jobs\CleanupInstanceStuffsJob;
-use App\Jobs\ContainerStatusJob;
use App\Jobs\DatabaseBackupJob;
use App\Jobs\DockerCleanupJob;
use App\Jobs\PullCoolifyImageJob;
@@ -12,7 +11,9 @@ use App\Jobs\PullHelperImageJob;
use App\Jobs\PullSentinelImageJob;
use App\Jobs\PullTemplatesFromCDN;
use App\Jobs\ScheduledTaskJob;
-use App\Jobs\ServerStatusJob;
+use App\Jobs\ServerCheckJob;
+use App\Jobs\UpdateCoolifyJob;
+use App\Models\InstanceSettings;
use App\Models\ScheduledDatabaseBackup;
use App\Models\ScheduledTask;
use App\Models\Server;
@@ -27,25 +28,26 @@ class Kernel extends ConsoleKernel
protected function schedule(Schedule $schedule): void
{
$this->all_servers = Server::all();
+ $settings = InstanceSettings::get();
+
if (isDev()) {
// Instance Jobs
$schedule->command('horizon:snapshot')->everyMinute();
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer();
- $schedule->job(new PullTemplatesFromCDN)->everyTwoHours()->onOneServer();
+
// Server Jobs
$this->check_scheduled_backups($schedule);
$this->check_resources($schedule);
- $this->check_scheduled_backups($schedule);
$this->check_scheduled_tasks($schedule);
$schedule->command('uploads:clear')->everyTwoMinutes();
} else {
// Instance Jobs
$schedule->command('horizon:snapshot')->everyFiveMinutes();
$schedule->command('cleanup:unreachable-servers')->daily();
- $schedule->job(new PullCoolifyImageJob)->everyTenMinutes()->onOneServer();
- $schedule->job(new PullTemplatesFromCDN)->everyThirtyMinutes()->onOneServer();
+ $schedule->job(new PullCoolifyImageJob)->cron($settings->update_check_frequency)->onOneServer();
+ $schedule->job(new PullTemplatesFromCDN)->cron($settings->update_check_frequency)->onOneServer();
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
- // $schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
+ $this->schedule_updates($schedule);
// Server Jobs
$this->check_scheduled_backups($schedule);
@@ -60,12 +62,26 @@ class Kernel extends ConsoleKernel
private function pull_images($schedule)
{
+ $settings = InstanceSettings::get();
$servers = $this->all_servers->where('settings.is_usable', true)->where('settings.is_reachable', true)->where('ip', '!=', '1.2.3.4');
foreach ($servers as $server) {
if ($server->isSentinelEnabled()) {
- $schedule->job(new PullSentinelImageJob($server))->everyFiveMinutes()->onOneServer();
+ $schedule->job(new PullSentinelImageJob($server))->cron($settings->update_check_frequency)->onOneServer();
}
- $schedule->job(new PullHelperImageJob($server))->everyFiveMinutes()->onOneServer();
+ $schedule->job(new PullHelperImageJob($server))->cron($settings->update_check_frequency)->onOneServer();
+ }
+ }
+
+ private function schedule_updates($schedule)
+ {
+ $settings = InstanceSettings::get();
+
+ $updateCheckFrequency = $settings->update_check_frequency;
+ $schedule->job(new CheckForUpdatesJob)->cron($updateCheckFrequency)->onOneServer();
+
+ if ($settings->is_auto_update_enabled) {
+ $autoUpdateFrequency = $settings->auto_update_frequency;
+ $schedule->job(new UpdateCoolifyJob)->cron($autoUpdateFrequency)->onOneServer();
}
}
@@ -75,19 +91,11 @@ class Kernel extends ConsoleKernel
$servers = $this->all_servers->whereNotNull('team.subscription')->where('team.subscription.stripe_trial_already_ended', false)->where('ip', '!=', '1.2.3.4');
$own = Team::find(0)->servers;
$servers = $servers->merge($own);
- $containerServers = $servers->where('settings.is_swarm_worker', false)->where('settings.is_build_server', false);
} else {
$servers = $this->all_servers->where('ip', '!=', '1.2.3.4');
- $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();
- if ($server->isLogDrainEnabled()) {
- $schedule->job(new CheckLogDrainContainerJob($server))->everyMinute()->onOneServer();
- }
}
foreach ($servers as $server) {
- $schedule->job(new ServerStatusJob($server))->everyMinute()->onOneServer();
+ $schedule->job(new ServerCheckJob($server))->everyMinute()->onOneServer();
$schedule->job(new DockerCleanupJob($server))->everyTenMinutes()->onOneServer();
}
}
diff --git a/app/Enums/ProxyTypes.php b/app/Enums/ProxyTypes.php
index 5ccf82e21..b3dc56746 100644
--- a/app/Enums/ProxyTypes.php
+++ b/app/Enums/ProxyTypes.php
@@ -5,7 +5,7 @@ namespace App\Enums;
enum ProxyTypes: string
{
case NONE = 'NONE';
- case TRAEFIK_V2 = 'TRAEFIK_V2';
+ case TRAEFIK = 'TRAEFIK';
case NGINX = 'NGINX';
case CADDY = 'CADDY';
}
diff --git a/app/Events/FileStorageChanged.php b/app/Events/FileStorageChanged.php
new file mode 100644
index 000000000..27fdc6b5c
--- /dev/null
+++ b/app/Events/FileStorageChanged.php
@@ -0,0 +1,32 @@
+teamId = $teamId;
+ }
+
+ public function broadcastOn(): array
+ {
+ return [
+ new PrivateChannel("team.{$this->teamId}"),
+ ];
+ }
+}
diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php
index 54ee8ef11..cd4d724b4 100644
--- a/app/Http/Controllers/Api/ApplicationsController.php
+++ b/app/Http/Controllers/Api/ApplicationsController.php
@@ -739,7 +739,7 @@ class ApplicationsController extends Controller
$application->isConfigurationChanged(true);
if ($instantDeploy) {
- $deployment_uuid = new Cuid2(7);
+ $deployment_uuid = new Cuid2;
queue_application_deployment(
application: $application,
@@ -835,7 +835,7 @@ class ApplicationsController extends Controller
$application->isConfigurationChanged(true);
if ($instantDeploy) {
- $deployment_uuid = new Cuid2(7);
+ $deployment_uuid = new Cuid2;
queue_application_deployment(
application: $application,
@@ -927,7 +927,7 @@ class ApplicationsController extends Controller
$application->isConfigurationChanged(true);
if ($instantDeploy) {
- $deployment_uuid = new Cuid2(7);
+ $deployment_uuid = new Cuid2;
queue_application_deployment(
application: $application,
@@ -947,7 +947,7 @@ class ApplicationsController extends Controller
]));
} elseif ($type === 'dockerfile') {
if (! $request->has('name')) {
- $request->offsetSet('name', 'dockerfile-'.new Cuid2(7));
+ $request->offsetSet('name', 'dockerfile-'.new Cuid2);
}
$validator = customApiValidator($request->all(), [
sharedDataApplications(),
@@ -1009,7 +1009,7 @@ class ApplicationsController extends Controller
$application->isConfigurationChanged(true);
if ($instantDeploy) {
- $deployment_uuid = new Cuid2(7);
+ $deployment_uuid = new Cuid2;
queue_application_deployment(
application: $application,
@@ -1025,7 +1025,7 @@ class ApplicationsController extends Controller
]));
} elseif ($type === 'dockerimage') {
if (! $request->has('name')) {
- $request->offsetSet('name', 'docker-image-'.new Cuid2(7));
+ $request->offsetSet('name', 'docker-image-'.new Cuid2);
}
$validator = customApiValidator($request->all(), [
sharedDataApplications(),
@@ -1067,7 +1067,7 @@ class ApplicationsController extends Controller
$application->isConfigurationChanged(true);
if ($instantDeploy) {
- $deployment_uuid = new Cuid2(7);
+ $deployment_uuid = new Cuid2;
queue_application_deployment(
application: $application,
@@ -1099,7 +1099,7 @@ class ApplicationsController extends Controller
], 422);
}
if (! $request->has('name')) {
- $request->offsetSet('name', 'service'.new Cuid2(7));
+ $request->offsetSet('name', 'service'.new Cuid2);
}
$validator = customApiValidator($request->all(), [
sharedDataApplications(),
@@ -1320,7 +1320,7 @@ class ApplicationsController extends Controller
#[OA\Patch(
summary: 'Update',
description: 'Update application by UUID.',
- path: '/applications',
+ path: '/applications/{uuid}',
security: [
['bearerAuth' => []],
],
@@ -2322,7 +2322,7 @@ class ApplicationsController extends Controller
return response()->json(['message' => 'Application not found.'], 404);
}
- $deployment_uuid = new Cuid2(7);
+ $deployment_uuid = new Cuid2;
queue_application_deployment(
application: $application,
@@ -2479,7 +2479,7 @@ class ApplicationsController extends Controller
return response()->json(['message' => 'Application not found.'], 404);
}
- $deployment_uuid = new Cuid2(7);
+ $deployment_uuid = new Cuid2;
queue_application_deployment(
application: $application,
diff --git a/app/Http/Controllers/Api/DeployController.php b/app/Http/Controllers/Api/DeployController.php
index 2ee56d0cd..437162058 100644
--- a/app/Http/Controllers/Api/DeployController.php
+++ b/app/Http/Controllers/Api/DeployController.php
@@ -84,7 +84,7 @@ class DeployController extends Controller
],
tags: ['Deployments'],
parameters: [
- new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Deployment Uuid', schema: new OA\Schema(type: 'integer')),
+ new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Deployment Uuid', schema: new OA\Schema(type: 'string')),
],
responses: [
new OA\Response(
@@ -290,7 +290,7 @@ class DeployController extends Controller
}
switch ($resource?->getMorphClass()) {
case 'App\Models\Application':
- $deployment_uuid = new Cuid2(7);
+ $deployment_uuid = new Cuid2;
queue_application_deployment(
application: $resource,
deployment_uuid: $deployment_uuid,
diff --git a/app/Http/Controllers/Api/OpenApi.php b/app/Http/Controllers/Api/OpenApi.php
index 59731ef40..60337a76c 100644
--- a/app/Http/Controllers/Api/OpenApi.php
+++ b/app/Http/Controllers/Api/OpenApi.php
@@ -5,7 +5,7 @@ namespace App\Http\Controllers\Api;
use OpenApi\Attributes as OA;
#[OA\Info(title: 'Coolify', version: '0.1')]
-#[OA\Server(url: 'https://app.coolify.io/api/v1')]
+#[OA\Server(url: 'https://app.coolify.io/api/v1', description: 'Coolify Cloud API. Change the host to your own instance if you are self-hosting.')]
#[OA\SecurityScheme(
type: 'http',
scheme: 'bearer',
diff --git a/app/Http/Controllers/Api/ProjectController.php b/app/Http/Controllers/Api/ProjectController.php
index 8316d5e82..6aec31e9b 100644
--- a/app/Http/Controllers/Api/ProjectController.php
+++ b/app/Http/Controllers/Api/ProjectController.php
@@ -61,7 +61,7 @@ class ProjectController extends Controller
],
tags: ['Projects'],
parameters: [
- new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Project UUID', schema: new OA\Schema(type: 'integer')),
+ new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Project UUID', schema: new OA\Schema(type: 'string')),
],
responses: [
new OA\Response(
@@ -107,7 +107,7 @@ class ProjectController extends Controller
],
tags: ['Projects'],
parameters: [
- new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Project UUID', schema: new OA\Schema(type: 'integer')),
+ new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Project UUID', schema: new OA\Schema(type: 'string')),
new OA\Parameter(name: 'environment_name', in: 'path', required: true, description: 'Environment name', schema: new OA\Schema(type: 'string')),
],
responses: [
diff --git a/app/Http/Controllers/Api/SecurityController.php b/app/Http/Controllers/Api/SecurityController.php
index 11e8e27ca..67128234e 100644
--- a/app/Http/Controllers/Api/SecurityController.php
+++ b/app/Http/Controllers/Api/SecurityController.php
@@ -73,7 +73,7 @@ class SecurityController extends Controller
],
tags: ['Private Keys'],
parameters: [
- new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Private Key Uuid', schema: new OA\Schema(type: 'integer')),
+ new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Private Key Uuid', schema: new OA\Schema(type: 'string')),
],
responses: [
new OA\Response(
@@ -318,7 +318,7 @@ class SecurityController extends Controller
],
tags: ['Private Keys'],
parameters: [
- new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Private Key Uuid', schema: new OA\Schema(type: 'integer')),
+ new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Private Key Uuid', schema: new OA\Schema(type: 'string')),
],
responses: [
new OA\Response(
diff --git a/app/Http/Controllers/Api/ServersController.php b/app/Http/Controllers/Api/ServersController.php
index fa159077d..9044c4a35 100644
--- a/app/Http/Controllers/Api/ServersController.php
+++ b/app/Http/Controllers/Api/ServersController.php
@@ -105,7 +105,7 @@ class ServersController extends Controller
],
tags: ['Servers'],
parameters: [
- new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Server\'s Uuid', schema: new OA\Schema(type: 'integer')),
+ new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Server\'s Uuid', schema: new OA\Schema(type: 'string')),
],
responses: [
new OA\Response(
@@ -182,7 +182,7 @@ class ServersController extends Controller
],
tags: ['Servers'],
parameters: [
- new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Server\'s Uuid', schema: new OA\Schema(type: 'integer')),
+ new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Server\'s Uuid', schema: new OA\Schema(type: 'string')),
],
responses: [
new OA\Response(
@@ -259,7 +259,7 @@ class ServersController extends Controller
],
tags: ['Servers'],
parameters: [
- new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Server\'s Uuid', schema: new OA\Schema(type: 'integer')),
+ new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Server\'s Uuid', schema: new OA\Schema(type: 'string')),
],
responses: [
new OA\Response(
@@ -525,7 +525,7 @@ class ServersController extends Controller
'private_key_id' => $privateKey->id,
'team_id' => $teamId,
'proxy' => [
- 'type' => ProxyTypes::TRAEFIK_V2->value,
+ 'type' => ProxyTypes::TRAEFIK->value,
'status' => ProxyStatus::EXITED->value,
],
]);
@@ -732,7 +732,7 @@ class ServersController extends Controller
],
tags: ['Servers'],
parameters: [
- new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Server UUID', schema: new OA\Schema(type: 'integer')),
+ new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Server UUID', schema: new OA\Schema(type: 'string')),
],
responses: [
new OA\Response(
diff --git a/app/Http/Controllers/Api/ServicesController.php b/app/Http/Controllers/Api/ServicesController.php
index 7d58987d8..3a5b59f55 100644
--- a/app/Http/Controllers/Api/ServicesController.php
+++ b/app/Http/Controllers/Api/ServicesController.php
@@ -413,6 +413,8 @@ class ServicesController extends Controller
return response()->json(['message' => 'Service not found.'], 404);
}
+ $service = $service->load(['applications', 'databases']);
+
return response()->json($this->removeSensitiveData($service));
}
diff --git a/app/Http/Controllers/Webhook/Bitbucket.php b/app/Http/Controllers/Webhook/Bitbucket.php
index 059438ff4..ef85d59e3 100644
--- a/app/Http/Controllers/Webhook/Bitbucket.php
+++ b/app/Http/Controllers/Webhook/Bitbucket.php
@@ -103,7 +103,7 @@ class Bitbucket extends Controller
if ($x_bitbucket_event === 'repo:push') {
if ($application->isDeployable()) {
ray('Deploying '.$application->name.' with branch '.$branch);
- $deployment_uuid = new Cuid2(7);
+ $deployment_uuid = new Cuid2;
queue_application_deployment(
application: $application,
deployment_uuid: $deployment_uuid,
@@ -127,7 +127,7 @@ class Bitbucket extends Controller
if ($x_bitbucket_event === 'pullrequest:created') {
if ($application->isPRDeployable()) {
ray('Deploying preview for '.$application->name.' with branch '.$branch.' and base branch '.$base_branch.' and pull request id '.$pull_request_id);
- $deployment_uuid = new Cuid2(7);
+ $deployment_uuid = new Cuid2;
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
if (! $found) {
if ($application->build_pack === 'dockercompose') {
diff --git a/app/Http/Controllers/Webhook/Gitea.php b/app/Http/Controllers/Webhook/Gitea.php
index e6d91efd6..e042b74c9 100644
--- a/app/Http/Controllers/Webhook/Gitea.php
+++ b/app/Http/Controllers/Webhook/Gitea.php
@@ -123,7 +123,7 @@ class Gitea extends Controller
$is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
if ($is_watch_path_triggered || is_null($application->watch_paths)) {
ray('Deploying '.$application->name.' with branch '.$branch);
- $deployment_uuid = new Cuid2(7);
+ $deployment_uuid = new Cuid2;
queue_application_deployment(
application: $application,
deployment_uuid: $deployment_uuid,
@@ -162,7 +162,7 @@ class Gitea extends Controller
if ($x_gitea_event === 'pull_request') {
if ($action === 'opened' || $action === 'synchronize' || $action === 'reopened') {
if ($application->isPRDeployable()) {
- $deployment_uuid = new Cuid2(7);
+ $deployment_uuid = new Cuid2;
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
if (! $found) {
if ($application->build_pack === 'dockercompose') {
diff --git a/app/Http/Controllers/Webhook/Github.php b/app/Http/Controllers/Webhook/Github.php
index ee51b6e0d..5f3ba933b 100644
--- a/app/Http/Controllers/Webhook/Github.php
+++ b/app/Http/Controllers/Webhook/Github.php
@@ -128,7 +128,7 @@ class Github extends Controller
$is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
if ($is_watch_path_triggered || is_null($application->watch_paths)) {
ray('Deploying '.$application->name.' with branch '.$branch);
- $deployment_uuid = new Cuid2(7);
+ $deployment_uuid = new Cuid2;
queue_application_deployment(
application: $application,
deployment_uuid: $deployment_uuid,
@@ -167,7 +167,7 @@ class Github extends Controller
if ($x_github_event === 'pull_request') {
if ($action === 'opened' || $action === 'synchronize' || $action === 'reopened') {
if ($application->isPRDeployable()) {
- $deployment_uuid = new Cuid2(7);
+ $deployment_uuid = new Cuid2;
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
if (! $found) {
if ($application->build_pack === 'dockercompose') {
@@ -357,7 +357,7 @@ class Github extends Controller
$is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
if ($is_watch_path_triggered || is_null($application->watch_paths)) {
ray('Deploying '.$application->name.' with branch '.$branch);
- $deployment_uuid = new Cuid2(7);
+ $deployment_uuid = new Cuid2;
queue_application_deployment(
application: $application,
deployment_uuid: $deployment_uuid,
@@ -396,7 +396,7 @@ class Github extends Controller
if ($x_github_event === 'pull_request') {
if ($action === 'opened' || $action === 'synchronize' || $action === 'reopened') {
if ($application->isPRDeployable()) {
- $deployment_uuid = new Cuid2(7);
+ $deployment_uuid = new Cuid2;
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
if (! $found) {
ApplicationPreview::create([
diff --git a/app/Http/Controllers/Webhook/Gitlab.php b/app/Http/Controllers/Webhook/Gitlab.php
index f6e6cf7e7..ec7f51a0d 100644
--- a/app/Http/Controllers/Webhook/Gitlab.php
+++ b/app/Http/Controllers/Webhook/Gitlab.php
@@ -137,7 +137,7 @@ class Gitlab extends Controller
$is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
if ($is_watch_path_triggered || is_null($application->watch_paths)) {
ray('Deploying '.$application->name.' with branch '.$branch);
- $deployment_uuid = new Cuid2(7);
+ $deployment_uuid = new Cuid2;
queue_application_deployment(
application: $application,
deployment_uuid: $deployment_uuid,
@@ -177,7 +177,7 @@ class Gitlab extends Controller
if ($x_gitlab_event === 'merge_request') {
if ($action === 'open' || $action === 'opened' || $action === 'synchronize' || $action === 'reopened' || $action === 'reopen' || $action === 'update') {
if ($application->isPRDeployable()) {
- $deployment_uuid = new Cuid2(7);
+ $deployment_uuid = new Cuid2;
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
if (! $found) {
if ($application->build_pack === 'dockercompose') {
diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php
index 8a79515b5..473cbc679 100644
--- a/app/Jobs/ApplicationDeploymentJob.php
+++ b/app/Jobs/ApplicationDeploymentJob.php
@@ -307,14 +307,6 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
]
);
- // $this->execute_remote_command(
- // [
- // "docker image prune -f >/dev/null 2>&1",
- // "hidden" => true,
- // "ignore_errors" => true,
- // ]
- // );
-
ApplicationStatusChanged::dispatch(data_get($this->application, 'environment.project.team.id'));
}
}
@@ -497,13 +489,13 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
} else {
$this->write_deployment_configurations();
$server_workdir = $this->application->workdir();
+ $this->docker_compose_location = '/docker-compose.yaml';
$command = "{$this->coolify_variables} docker compose";
if ($this->env_filename) {
- $command .= " --env-file {$this->workdir}/{$this->env_filename}";
+ $command .= " --env-file {$server_workdir}/{$this->env_filename}";
}
$command .= " --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d";
-
$this->execute_remote_command(
['command' => $command, 'hidden' => true],
);
@@ -636,21 +628,26 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
$this->server = $this->original_server;
}
$readme = generate_readme_file($this->application->name, $this->application_deployment_queue->updated_at);
+
+ $mainDir = $this->configuration_dir;
+ if ($this->application->settings->is_raw_compose_deployment_enabled) {
+ $mainDir = $this->application->workdir();
+ }
if ($this->pull_request_id === 0) {
- $composeFileName = "$this->configuration_dir/docker-compose.yaml";
+ $composeFileName = "$mainDir/docker-compose.yaml";
} else {
- $composeFileName = "$this->configuration_dir/docker-compose-pr-{$this->pull_request_id}.yaml";
+ $composeFileName = "$mainDir/docker-compose-pr-{$this->pull_request_id}.yaml";
$this->docker_compose_location = "/docker-compose-pr-{$this->pull_request_id}.yaml";
}
$this->execute_remote_command(
[
- "mkdir -p $this->configuration_dir",
+ "mkdir -p $mainDir",
],
[
"echo '{$this->docker_compose_base64}' | base64 -d | tee $composeFileName > /dev/null",
],
[
- "echo '{$readme}' > $this->configuration_dir/README.md",
+ "echo '{$readme}' > $mainDir/README.md",
]
);
if ($this->use_build_server) {
@@ -874,8 +871,10 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
$envs->push($env->key.'='.$real_value);
}
// Add PORT if not exists, use the first port as default
- if ($this->application->environment_variables_preview->where('key', 'PORT')->isEmpty()) {
- $envs->push("PORT={$ports[0]}");
+ if ($this->build_pack !== 'dockercompose') {
+ if ($this->application->environment_variables_preview->where('key', 'PORT')->isEmpty()) {
+ $envs->push("PORT={$ports[0]}");
+ }
}
// Add HOST if not exists
if ($this->application->environment_variables_preview->where('key', 'HOST')->isEmpty()) {
@@ -918,15 +917,16 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
$envs->push($env->key.'='.$real_value);
}
// Add PORT if not exists, use the first port as default
- if ($this->application->environment_variables->where('key', 'PORT')->isEmpty()) {
- $envs->push("PORT={$ports[0]}");
+ if ($this->build_pack !== 'dockercompose') {
+ if ($this->application->environment_variables->where('key', 'PORT')->isEmpty()) {
+ $envs->push("PORT={$ports[0]}");
+ }
}
// Add HOST if not exists
if ($this->application->environment_variables->where('key', 'HOST')->isEmpty()) {
$envs->push('HOST=0.0.0.0');
}
}
-
if ($envs->isEmpty()) {
$this->env_filename = null;
if ($this->use_build_server) {
@@ -1025,7 +1025,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
$this->write_deployment_configurations();
$this->server = $this->original_server;
}
- if (count($this->application->ports_mappings_array) > 0 || (bool) $this->application->settings->is_consistent_container_name_enabled || isset($this->application->settings->custom_internal_name) || $this->pull_request_id !== 0 || str($this->application->custom_docker_run_options)->contains('--ip') || str($this->application->custom_docker_run_options)->contains('--ip6')) {
+ if (count($this->application->ports_mappings_array) > 0 || (bool) $this->application->settings->is_consistent_container_name_enabled || str($this->application->settings->custom_internal_name)->isNotEmpty() || $this->pull_request_id !== 0 || str($this->application->custom_docker_run_options)->contains('--ip') || str($this->application->custom_docker_run_options)->contains('--ip6')) {
$this->application_deployment_queue->addLogEntry('----------------------------------------');
if (count($this->application->ports_mappings_array) > 0) {
$this->application_deployment_queue->addLogEntry('Application has ports mapped to the host system, rolling update is not supported.');
@@ -2027,27 +2027,43 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
$this->application_deployment_queue->addLogEntry('Building docker image completed.');
}
+ /**
+ * @param int $timeout in seconds
+ */
+ private function graceful_shutdown_container(string $containerName, int $timeout = 30)
+ {
+ try {
+ $this->execute_remote_command(
+ ["docker stop --time=$timeout $containerName", 'hidden' => true, 'ignore_errors' => true],
+ ["docker rm $containerName", 'hidden' => true, 'ignore_errors' => true]
+ );
+ } catch (\Exception $error) {
+ // report error if needed
+ }
+
+ $this->execute_remote_command(
+ ["docker rm -f $containerName", 'hidden' => true, 'ignore_errors' => true]
+ );
+
+ }
+
private function stop_running_container(bool $force = false)
{
$this->application_deployment_queue->addLogEntry('Removing old containers.');
if ($this->newVersionIsHealthy || $force) {
- $containers = getCurrentApplicationContainerStatus($this->server, $this->application->id, $this->pull_request_id);
- if ($this->pull_request_id === 0) {
- $containers = $containers->filter(function ($container) {
- return data_get($container, 'Names') !== $this->container_name && data_get($container, 'Names') !== $this->container_name.'-pr-'.$this->pull_request_id;
+ if ($this->application->settings->is_consistent_container_name_enabled || str($this->application->settings->custom_internal_name)->isNotEmpty()) {
+ $this->graceful_shutdown_container($this->container_name);
+ } else {
+ $containers = getCurrentApplicationContainerStatus($this->server, $this->application->id, $this->pull_request_id);
+ if ($this->pull_request_id === 0) {
+ $containers = $containers->filter(function ($container) {
+ return data_get($container, 'Names') !== $this->container_name && data_get($container, 'Names') !== $this->container_name.'-pr-'.$this->pull_request_id;
+ });
+ }
+ $containers->each(function ($container) {
+ $this->graceful_shutdown_container(data_get($container, 'Names'));
});
}
- $containers->each(function ($container) {
- $containerName = data_get($container, 'Names');
- $this->execute_remote_command(
- ["docker rm -f $containerName >/dev/null 2>&1", 'hidden' => true, 'ignore_errors' => true],
- );
- });
- if ($this->application->settings->is_consistent_container_name_enabled || isset($this->application->settings->custom_internal_name)) {
- $this->execute_remote_command(
- ["docker rm -f $this->container_name >/dev/null 2>&1", 'hidden' => true, 'ignore_errors' => true],
- );
- }
} else {
if ($this->application->dockerfile || $this->application->build_pack === 'dockerfile' || $this->application->build_pack === 'dockerimage') {
$this->application_deployment_queue->addLogEntry('----------------------------------------');
@@ -2058,9 +2074,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
$this->application_deployment_queue->update([
'status' => ApplicationDeploymentStatus::FAILED->value,
]);
- $this->execute_remote_command(
- ["docker rm -f $this->container_name >/dev/null 2>&1", 'hidden' => true, 'ignore_errors' => true],
- );
+ $this->graceful_shutdown_container($this->container_name);
}
}
@@ -2235,7 +2249,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
ray($code);
if ($code !== 69420) {
// 69420 means failed to push the image to the registry, so we don't need to remove the new version as it is the currently running one
- if ($this->application->settings->is_consistent_container_name_enabled || isset($this->application->settings->custom_internal_name)) {
+ if ($this->application->settings->is_consistent_container_name_enabled || str($this->application->settings->custom_internal_name)->isNotEmpty()) {
// do not remove already running container
} else {
$this->application_deployment_queue->addLogEntry('Deployment failed. Removing the new version of your application.', 'stderr');
diff --git a/app/Jobs/CheckForUpdatesJob.php b/app/Jobs/CheckForUpdatesJob.php
new file mode 100644
index 000000000..86b66fbfb
--- /dev/null
+++ b/app/Jobs/CheckForUpdatesJob.php
@@ -0,0 +1,42 @@
+get('https://cdn.coollabs.io/coolify/versions.json');
+ if ($response->successful()) {
+ $versions = $response->json();
+ $latest_version = data_get($versions, 'coolify.v4.version');
+ $current_version = config('version');
+
+ if (version_compare($latest_version, $current_version, '>')) {
+ // New version available
+ $settings->update(['new_version_available' => true]);
+ } else {
+ $settings->update(['new_version_available' => false]);
+ }
+ }
+ } catch (\Throwable $e) {
+ // Consider implementing a notification to administrators
+ }
+ }
+}
diff --git a/app/Jobs/DatabaseBackupJob.php b/app/Jobs/DatabaseBackupJob.php
index 4afe50d53..79b00e9cd 100644
--- a/app/Jobs/DatabaseBackupJob.php
+++ b/app/Jobs/DatabaseBackupJob.php
@@ -90,6 +90,7 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
{
try {
BackupCreated::dispatch($this->team->id);
+
// Check if team is exists
if (is_null($this->team)) {
$this->backup->update(['status' => 'failed']);
@@ -476,7 +477,7 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
} else {
$network = $this->database->destination->network;
}
- $commands[] = "docker run --pull=always -d --network {$network} --name backup-of-{$this->backup->uuid} --rm -v $this->backup_location:$this->backup_location:ro ghcr.io/coollabsio/coolify-helper";
+ $commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup->uuid} --rm -v $this->backup_location:$this->backup_location:ro ghcr.io/coollabsio/coolify-helper";
$commands[] = "docker exec backup-of-{$this->backup->uuid} mc config host add temporary {$endpoint} $key $secret";
$commands[] = "docker exec backup-of-{$this->backup->uuid} mc cp $this->backup_location temporary/$bucket{$this->backup_dir}/";
instant_remote_process($commands, $this->server);
diff --git a/app/Jobs/DockerCleanupJob.php b/app/Jobs/DockerCleanupJob.php
index f99a65b5d..5010263ae 100644
--- a/app/Jobs/DockerCleanupJob.php
+++ b/app/Jobs/DockerCleanupJob.php
@@ -26,17 +26,6 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
public function handle(): void
{
try {
- // $isInprogress = false;
- // $this->server->applications()->each(function ($application) use (&$isInprogress) {
- // if ($application->isDeploymentInprogress()) {
- // $isInprogress = true;
-
- // return;
- // }
- // });
- // if ($isInprogress) {
- // throw new RuntimeException('DockerCleanupJob: ApplicationDeploymentQueue is not empty, skipping...');
- // }
if (! $this->server->isFunctional()) {
return;
}
@@ -48,6 +37,12 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
}
$this->usageBefore = $this->server->getDiskUsage();
+ if (str($this->usageBefore)->isEmpty() || $this->usageBefore === null || $this->usageBefore === 0) {
+ Log::info('DockerCleanupJob force cleanup on '.$this->server->name);
+ CleanupDocker::run(server: $this->server, force: true);
+
+ return;
+ }
if ($this->usageBefore >= $this->server->settings->cleanup_after_percentage) {
CleanupDocker::run(server: $this->server, force: false);
$usageAfter = $this->server->getDiskUsage();
diff --git a/app/Jobs/PullCoolifyImageJob.php b/app/Jobs/PullCoolifyImageJob.php
index 253b0b9f0..f0912493f 100644
--- a/app/Jobs/PullCoolifyImageJob.php
+++ b/app/Jobs/PullCoolifyImageJob.php
@@ -2,6 +2,7 @@
namespace App\Jobs;
+use App\Models\InstanceSettings;
use App\Models\Server;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
@@ -16,16 +17,13 @@ class PullCoolifyImageJob implements ShouldBeEncrypted, ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
- public $timeout = 1000;
-
- public function __construct() {}
-
public function handle(): void
{
try {
if (isDev() || isCloud()) {
return;
}
+ $settings = InstanceSettings::get();
$server = Server::findOrFail(0);
$response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json');
if ($response->successful()) {
@@ -35,7 +33,6 @@ class PullCoolifyImageJob implements ShouldBeEncrypted, ShouldQueue
$latest_version = get_latest_version_of_coolify();
instant_remote_process(["docker pull -q ghcr.io/coollabsio/coolify:{$latest_version}"], $server, false);
- $settings = \App\Models\InstanceSettings::get();
$current_version = config('version');
if (! $settings->is_auto_update_enabled) {
return;
@@ -46,10 +43,6 @@ class PullCoolifyImageJob implements ShouldBeEncrypted, ShouldQueue
if (version_compare($latest_version, $current_version, '<')) {
return;
}
- instant_remote_process([
- 'curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh -o /data/coolify/source/upgrade.sh',
- "bash /data/coolify/source/upgrade.sh $latest_version",
- ], $server);
} catch (\Throwable $e) {
throw $e;
}
diff --git a/app/Jobs/PullTemplatesFromCDN.php b/app/Jobs/PullTemplatesFromCDN.php
index 396ff29f4..72c971033 100644
--- a/app/Jobs/PullTemplatesFromCDN.php
+++ b/app/Jobs/PullTemplatesFromCDN.php
@@ -22,15 +22,16 @@ class PullTemplatesFromCDN implements ShouldBeEncrypted, ShouldQueue
public function handle(): void
{
try {
- if (! isDev()) {
- ray('PullTemplatesAndVersions service-templates');
- $response = Http::retry(3, 1000)->get(config('constants.services.official'));
- if ($response->successful()) {
- $services = $response->json();
- File::put(base_path('templates/service-templates.json'), json_encode($services));
- } else {
- send_internal_notification('PullTemplatesAndVersions failed with: '.$response->status().' '.$response->body());
- }
+ if (isDev() || isCloud()) {
+ return;
+ }
+ ray('PullTemplatesAndVersions service-templates');
+ $response = Http::retry(3, 1000)->get(config('constants.services.official'));
+ if ($response->successful()) {
+ $services = $response->json();
+ File::put(base_path('templates/service-templates.json'), json_encode($services));
+ } else {
+ send_internal_notification('PullTemplatesAndVersions failed with: '.$response->status().' '.$response->body());
}
} catch (\Throwable $e) {
send_internal_notification('PullTemplatesAndVersions failed with: '.$e->getMessage());
diff --git a/app/Jobs/ServerCheckJob.php b/app/Jobs/ServerCheckJob.php
new file mode 100644
index 000000000..3976477e7
--- /dev/null
+++ b/app/Jobs/ServerCheckJob.php
@@ -0,0 +1,440 @@
+server->uuid))];
+ }
+
+ public function uniqueId(): int
+ {
+ return $this->server->uuid;
+ }
+
+ public function handle()
+ {
+ try {
+ $this->applications = $this->server->applications();
+ $this->databases = $this->server->databases();
+ $this->services = $this->server->services()->get();
+ $this->previews = $this->server->previews();
+
+ $up = $this->serverStatus();
+ if (! $up) {
+ ray('Server is not reachable.');
+
+ return 'Server is not reachable.';
+ }
+ if (! $this->server->isFunctional()) {
+ ray('Server is not ready.');
+
+ return 'Server is not ready.';
+ }
+ if (! $this->server->isSwarmWorker() && ! $this->server->isBuildServer()) {
+ ['containers' => $this->containers, 'containerReplicates' => $containerReplicates] = $this->server->getContainers();
+ if (is_null($this->containers)) {
+ return 'No containers found.';
+ }
+ GetContainersStatus::run($this->server, $this->containers, $containerReplicates);
+ $this->checkLogDrainContainer();
+ $this->checkSentinel();
+ }
+
+ } catch (\Throwable $e) {
+ ray($e->getMessage());
+
+ return handleError($e);
+ }
+
+ }
+
+ private function checkSentinel()
+ {
+ if ($this->server->isSentinelEnabled()) {
+ $sentinelContainerFound = $this->containers->filter(function ($value, $key) {
+ return data_get($value, 'Name') === '/coolify-sentinel';
+ })->first();
+ if ($sentinelContainerFound) {
+ $status = data_get($sentinelContainerFound, 'State.Status');
+ if ($status !== 'running') {
+ PullSentinelImageJob::dispatch($this);
+ }
+ }
+ }
+ }
+
+ private function serverStatus()
+ {
+ ['uptime' => $uptime] = $this->server->validateConnection();
+ if ($uptime) {
+ if ($this->server->unreachable_notification_sent === true) {
+ $this->server->update(['unreachable_notification_sent' => false]);
+ }
+ } else {
+ // $this->server->team?->notify(new Unreachable($this->server));
+ foreach ($this->applications as $application) {
+ $application->update(['status' => 'exited']);
+ }
+ foreach ($this->databases as $database) {
+ $database->update(['status' => 'exited']);
+ }
+ foreach ($this->services as $service) {
+ $apps = $service->applications()->get();
+ $dbs = $service->databases()->get();
+ foreach ($apps as $app) {
+ $app->update(['status' => 'exited']);
+ }
+ foreach ($dbs as $db) {
+ $db->update(['status' => 'exited']);
+ }
+ }
+
+ return false;
+ }
+
+ return true;
+
+ }
+
+ private function checkLogDrainContainer()
+ {
+ $foundLogDrainContainer = $this->containers->filter(function ($value, $key) {
+ return data_get($value, 'Name') === '/coolify-log-drain';
+ })->first();
+ if ($foundLogDrainContainer) {
+ $status = data_get($foundLogDrainContainer, 'State.Status');
+ if ($status !== 'running') {
+ InstallLogDrain::dispatch($this->server);
+ }
+ } else {
+ InstallLogDrain::dispatch($this->server);
+ }
+ }
+
+ private function containerStatus()
+ {
+
+ $foundApplications = [];
+ $foundApplicationPreviews = [];
+ $foundDatabases = [];
+ $foundServices = [];
+
+ foreach ($this->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 = $this->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 = data_get($service_db, 'service.uuid');
+ if ($uuid) {
+ $isPublic = data_get($service_db, 'is_public');
+ if ($isPublic) {
+ $foundTcpProxy = $this->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 = $this->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 = $this->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 = $this->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 ($this->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');
+ if ($name) {
+ if ($fqdn) {
+ $containerName = "$name, available at $fqdn";
+ } else {
+ $containerName = $name;
+ }
+ } else {
+ if ($fqdn) {
+ $containerName = $fqdn;
+ } else {
+ $containerName = null;
+ }
+ }
+ $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 = $this->applications->pluck('id')->diff($foundApplications);
+ foreach ($notRunningApplications as $applicationId) {
+ $application = $this->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 = $this->previews->pluck('id')->diff($foundApplicationPreviews);
+ foreach ($notRunningApplicationPreviews as $previewId) {
+ $preview = $this->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 = $this->databases->pluck('id')->diff($foundDatabases);
+ foreach ($notRunningDatabases as $database) {
+ $database = $this->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 = $this->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);
+ }
+ }
+}
diff --git a/app/Jobs/UpdateCoolifyJob.php b/app/Jobs/UpdateCoolifyJob.php
new file mode 100644
index 000000000..4c65a711f
--- /dev/null
+++ b/app/Jobs/UpdateCoolifyJob.php
@@ -0,0 +1,51 @@
+new_version_available) {
+ Log::info('No new version available. Skipping update.');
+
+ return;
+ }
+
+ $server = Server::findOrFail(0);
+ if (! $server) {
+ Log::error('Server not found. Cannot proceed with update.');
+
+ return;
+ }
+
+ Log::info('Starting Coolify update process...');
+ UpdateCoolify::run(false); // false means it's not a manual update
+
+ $settings->update(['new_version_available' => false]);
+ Log::info('Coolify update completed successfully.');
+
+ } catch (\Throwable $e) {
+ Log::error('UpdateCoolifyJob failed: '.$e->getMessage());
+ // Consider implementing a notification to administrators
+ }
+ }
+}
diff --git a/app/Livewire/Boarding/Index.php b/app/Livewire/Boarding/Index.php
index 8127ca009..147a1ad6f 100644
--- a/app/Livewire/Boarding/Index.php
+++ b/app/Livewire/Boarding/Index.php
@@ -179,7 +179,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
public function getProxyType()
{
// Set Default Proxy Type
- $this->selectProxy(ProxyTypes::TRAEFIK_V2->value);
+ $this->selectProxy(ProxyTypes::TRAEFIK->value);
// $proxyTypeSet = $this->createdServer->proxy->type;
// if (!$proxyTypeSet) {
// $this->currentState = 'select-proxy';
diff --git a/app/Livewire/Destination/New/Docker.php b/app/Livewire/Destination/New/Docker.php
index f822cfa5f..4fc938df8 100644
--- a/app/Livewire/Destination/New/Docker.php
+++ b/app/Livewire/Destination/New/Docker.php
@@ -52,7 +52,7 @@ class Docker extends Component
if (request()->query('network_name')) {
$this->network = request()->query('network_name');
} else {
- $this->network = new Cuid2(7);
+ $this->network = new Cuid2;
}
if ($this->servers->count() > 0) {
$this->name = str("{$this->servers->first()->name}-{$this->network}")->kebab();
diff --git a/app/Livewire/MonacoEditor.php b/app/Livewire/MonacoEditor.php
index 156c63d3a..42d276e64 100644
--- a/app/Livewire/MonacoEditor.php
+++ b/app/Livewire/MonacoEditor.php
@@ -39,7 +39,7 @@ class MonacoEditor extends Component
public function render()
{
if (is_null($this->id)) {
- $this->id = new Cuid2(7);
+ $this->id = new Cuid2;
}
if (is_null($this->name)) {
diff --git a/app/Livewire/NavbarDeleteTeam.php b/app/Livewire/NavbarDeleteTeam.php
new file mode 100644
index 000000000..ec196c154
--- /dev/null
+++ b/app/Livewire/NavbarDeleteTeam.php
@@ -0,0 +1,35 @@
+delete();
+
+ $currentTeam->members->each(function ($user) use ($currentTeam) {
+ if ($user->id === auth()->user()->id) {
+ return;
+ }
+ $user->teams()->detach($currentTeam);
+ $session = DB::table('sessions')->where('user_id', $user->id)->first();
+ if ($session) {
+ DB::table('sessions')->where('id', $session->id)->delete();
+ }
+ });
+
+ refreshSession();
+
+ return redirect()->route('team.index');
+ }
+
+ public function render()
+ {
+ return view('livewire.navbar-delete-team');
+ }
+}
diff --git a/app/Livewire/Project/Application/Advanced.php b/app/Livewire/Project/Application/Advanced.php
index 3b402b3ec..cde3322db 100644
--- a/app/Livewire/Project/Application/Advanced.php
+++ b/app/Livewire/Project/Application/Advanced.php
@@ -91,11 +91,25 @@ class Advanced extends Component
public function saveCustomName()
{
- if (isset($this->application->settings->custom_internal_name)) {
+ if (str($this->application->settings->custom_internal_name)->isNotEmpty()) {
$this->application->settings->custom_internal_name = str($this->application->settings->custom_internal_name)->slug()->value();
} else {
$this->application->settings->custom_internal_name = null;
}
+ $customInternalName = $this->application->settings->custom_internal_name;
+ $server = $this->application->destination->server;
+ $allApplications = $server->applications();
+
+ $foundSameInternalName = $allApplications->filter(function ($application) {
+ return $application->id !== $this->application->id && $application->settings->custom_internal_name === $this->application->settings->custom_internal_name;
+ });
+ if ($foundSameInternalName->isNotEmpty()) {
+ $this->dispatch('error', 'This custom container name is already in use by another application on this server.');
+ $this->application->settings->custom_internal_name = $customInternalName;
+ $this->application->settings->refresh();
+
+ return;
+ }
$this->application->settings->save();
$this->dispatch('success', 'Custom name saved.');
}
diff --git a/app/Livewire/Project/Application/General.php b/app/Livewire/Project/Application/General.php
index 7dfd9bad4..77593bf0a 100644
--- a/app/Livewire/Project/Application/General.php
+++ b/app/Livewire/Project/Application/General.php
@@ -214,7 +214,7 @@ class General extends Component
}
$this->dispatch('success', 'Docker compose file loaded.');
$this->dispatch('compose_loaded');
- $this->dispatch('refresh_storages');
+ $this->dispatch('refreshStorages');
$this->dispatch('refreshEnvs');
} catch (\Throwable $e) {
$this->application->docker_compose_location = $this->initialDockerComposeLocation;
@@ -228,7 +228,7 @@ class General extends Component
public function generateDomain(string $serviceName)
{
- $uuid = new Cuid2(7);
+ $uuid = new Cuid2;
$domain = generateFqdn($this->application->destination->server, $uuid);
$this->parsedServiceDomains[$serviceName]['domain'] = $domain;
$this->application->docker_compose_domains = json_encode($this->parsedServiceDomains);
diff --git a/app/Livewire/Project/Application/Heading.php b/app/Livewire/Project/Application/Heading.php
index feb54c7f0..c02949e17 100644
--- a/app/Livewire/Project/Application/Heading.php
+++ b/app/Livewire/Project/Application/Heading.php
@@ -5,8 +5,6 @@ namespace App\Livewire\Project\Application;
use App\Actions\Application\StopApplication;
use App\Actions\Docker\GetContainersStatus;
use App\Events\ApplicationStatusChanged;
-use App\Jobs\ContainerStatusJob;
-use App\Jobs\ServerStatusJob;
use App\Models\Application;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
@@ -46,11 +44,7 @@ class Heading extends Component
{
if ($this->application->destination->server->isFunctional()) {
GetContainersStatus::dispatch($this->application->destination->server)->onQueue('high');
- // dispatch(new ContainerStatusJob($this->application->destination->server));
- } else {
- dispatch(new ServerStatusJob($this->application->destination->server));
}
-
if ($showNotification) {
$this->dispatch('success', 'Success', 'Application status updated.');
}
@@ -102,7 +96,7 @@ class Heading extends Component
protected function setDeploymentUuid()
{
- $this->deploymentUuid = new Cuid2(7);
+ $this->deploymentUuid = new Cuid2;
$this->parameters['deployment_uuid'] = $this->deploymentUuid;
}
diff --git a/app/Livewire/Project/Application/Previews.php b/app/Livewire/Project/Application/Previews.php
index df64c3fd3..30bc0a9d1 100644
--- a/app/Livewire/Project/Application/Previews.php
+++ b/app/Livewire/Project/Application/Previews.php
@@ -85,7 +85,7 @@ class Previews extends Component
$template = $this->application->preview_url_template;
$host = $url->getHost();
$schema = $url->getScheme();
- $random = new Cuid2(7);
+ $random = new Cuid2;
$preview_fqdn = str_replace('{{random}}', $random, $template);
$preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn);
$preview_fqdn = str_replace('{{pr_id}}', $preview->pull_request_id, $preview_fqdn);
@@ -170,7 +170,7 @@ class Previews extends Component
protected function setDeploymentUuid()
{
- $this->deployment_uuid = new Cuid2(7);
+ $this->deployment_uuid = new Cuid2;
$this->parameters['deployment_uuid'] = $this->deployment_uuid;
}
diff --git a/app/Livewire/Project/Application/PreviewsCompose.php b/app/Livewire/Project/Application/PreviewsCompose.php
index bf4478e53..b3e838bb3 100644
--- a/app/Livewire/Project/Application/PreviewsCompose.php
+++ b/app/Livewire/Project/Application/PreviewsCompose.php
@@ -44,7 +44,7 @@ class PreviewsCompose extends Component
$template = $this->preview->application->preview_url_template;
$host = $url->getHost();
$schema = $url->getScheme();
- $random = new Cuid2(7);
+ $random = new Cuid2;
$preview_fqdn = str_replace('{{random}}', $random, $template);
$preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn);
$preview_fqdn = str_replace('{{pr_id}}', $this->preview->pull_request_id, $preview_fqdn);
diff --git a/app/Livewire/Project/Application/Rollback.php b/app/Livewire/Project/Application/Rollback.php
index ed0ac1cef..1e58a1458 100644
--- a/app/Livewire/Project/Application/Rollback.php
+++ b/app/Livewire/Project/Application/Rollback.php
@@ -23,7 +23,7 @@ class Rollback extends Component
public function rollbackImage($commit)
{
- $deployment_uuid = new Cuid2(7);
+ $deployment_uuid = new Cuid2;
queue_application_deployment(
application: $this->application,
diff --git a/app/Livewire/Project/CloneMe.php b/app/Livewire/Project/CloneMe.php
index 5373f1b3f..4d2bc6589 100644
--- a/app/Livewire/Project/CloneMe.php
+++ b/app/Livewire/Project/CloneMe.php
@@ -47,7 +47,7 @@ class CloneMe extends Component
$this->environment = $this->project->environments->where('name', $this->environment_name)->first();
$this->project_id = $this->project->id;
$this->servers = currentTeam()->servers;
- $this->newName = str($this->project->name.'-clone-'.(string) new Cuid2(7))->slug();
+ $this->newName = str($this->project->name.'-clone-'.(string) new Cuid2)->slug();
}
public function render()
@@ -106,7 +106,7 @@ class CloneMe extends Component
$databases = $this->environment->databases();
$services = $this->environment->services;
foreach ($applications as $application) {
- $uuid = (string) new Cuid2(7);
+ $uuid = (string) new Cuid2;
$newApplication = $application->replicate()->fill([
'uuid' => $uuid,
'fqdn' => generateFqdn($this->server, $uuid),
@@ -133,7 +133,7 @@ class CloneMe extends Component
}
}
foreach ($databases as $database) {
- $uuid = (string) new Cuid2(7);
+ $uuid = (string) new Cuid2;
$newDatabase = $database->replicate()->fill([
'uuid' => $uuid,
'status' => 'exited',
@@ -161,7 +161,7 @@ class CloneMe extends Component
}
}
foreach ($services as $service) {
- $uuid = (string) new Cuid2(7);
+ $uuid = (string) new Cuid2;
$newService = $service->replicate()->fill([
'uuid' => $uuid,
'environment_id' => $environment->id,
diff --git a/app/Livewire/Project/New/DockerImage.php b/app/Livewire/Project/New/DockerImage.php
index fdad052c7..d3f5b5261 100644
--- a/app/Livewire/Project/New/DockerImage.php
+++ b/app/Livewire/Project/New/DockerImage.php
@@ -48,7 +48,7 @@ class DockerImage extends Component
$environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first();
ray($image, $tag);
$application = Application::create([
- 'name' => 'docker-image-'.new Cuid2(7),
+ 'name' => 'docker-image-'.new Cuid2,
'repository_project_id' => 0,
'git_repository' => 'coollabsio/coolify',
'git_branch' => 'main',
diff --git a/app/Livewire/Project/New/SimpleDockerfile.php b/app/Livewire/Project/New/SimpleDockerfile.php
index 6f6bc9185..3c7f42329 100644
--- a/app/Livewire/Project/New/SimpleDockerfile.php
+++ b/app/Livewire/Project/New/SimpleDockerfile.php
@@ -53,7 +53,7 @@ CMD ["nginx", "-g", "daemon off;"]
$port = 80;
}
$application = Application::create([
- 'name' => 'dockerfile-'.new Cuid2(7),
+ 'name' => 'dockerfile-'.new Cuid2,
'repository_project_id' => 0,
'git_repository' => 'coollabsio/coolify',
'git_branch' => 'main',
diff --git a/app/Livewire/Project/Service/Configuration.php b/app/Livewire/Project/Service/Configuration.php
index 47534ded1..c82012aaa 100644
--- a/app/Livewire/Project/Service/Configuration.php
+++ b/app/Livewire/Project/Service/Configuration.php
@@ -25,7 +25,6 @@ class Configuration extends Component
return [
"echo-private:user.{$userId},ServiceStatusChanged" => 'check_status',
'check_status',
- 'refresh' => '$refresh',
];
}
@@ -76,8 +75,7 @@ class Configuration extends Component
{
try {
GetContainersStatus::run($this->service->server);
- // dispatch_sync(new ContainerStatusJob($this->service->server));
- $this->dispatch('refresh')->self();
+ $this->dispatch('$refresh');
} catch (\Exception $e) {
return handleError($e, $this);
}
diff --git a/app/Livewire/Project/Service/FileStorage.php b/app/Livewire/Project/Service/FileStorage.php
index 2eea0891f..2d9c95daa 100644
--- a/app/Livewire/Project/Service/FileStorage.php
+++ b/app/Livewire/Project/Service/FileStorage.php
@@ -26,6 +26,8 @@ class FileStorage extends Component
public ?string $workdir = null;
+ public bool $permanently_delete = true;
+
protected $rules = [
'fileStorage.is_directory' => 'required',
'fileStorage.fs_path' => 'required',
@@ -56,7 +58,7 @@ class FileStorage extends Component
} catch (\Throwable $e) {
return handleError($e, $this);
} finally {
- $this->dispatch('refresh_storages');
+ $this->dispatch('refreshStorages');
}
}
@@ -71,20 +73,27 @@ class FileStorage extends Component
} catch (\Throwable $e) {
return handleError($e, $this);
} finally {
- $this->dispatch('refresh_storages');
+ $this->dispatch('refreshStorages');
}
}
public function delete()
{
try {
- $this->fileStorage->deleteStorageOnServer();
+ $message = 'File deleted.';
+ if ($this->fileStorage->is_directory) {
+ $message = 'Directory deleted.';
+ }
+ if ($this->permanently_delete) {
+ $message = 'Directory deleted from the server.';
+ $this->fileStorage->deleteStorageOnServer();
+ }
$this->fileStorage->delete();
- $this->dispatch('success', 'File deleted.');
+ $this->dispatch('success', $message);
} catch (\Throwable $e) {
return handleError($e, $this);
} finally {
- $this->dispatch('refresh_storages');
+ $this->dispatch('refreshStorages');
}
}
diff --git a/app/Livewire/Project/Service/Navbar.php b/app/Livewire/Project/Service/Navbar.php
index f2fd5ae96..674182df5 100644
--- a/app/Livewire/Project/Service/Navbar.php
+++ b/app/Livewire/Project/Service/Navbar.php
@@ -49,6 +49,11 @@ class Navbar extends Component
}
}
+ public function check_status_without_notification()
+ {
+ $this->dispatch('check_status');
+ }
+
public function check_status()
{
$this->dispatch('check_status');
@@ -63,6 +68,8 @@ class Navbar extends Component
public function checkDeployments()
{
try {
+ // TODO: This is a temporary solution. We need to refactor this.
+ // We need to delete null bytes somehow.
$activity = Activity::where('properties->type_uuid', $this->service->uuid)->latest()->first();
$status = data_get($activity, 'properties.status');
if ($status === 'queued' || $status === 'in_progress') {
@@ -70,7 +77,7 @@ class Navbar extends Component
} else {
$this->isDeploymentProgress = false;
}
- } catch (\Exception $e) {
+ } catch (\Throwable $e) {
$this->isDeploymentProgress = false;
}
}
diff --git a/app/Livewire/Project/Service/Storage.php b/app/Livewire/Project/Service/Storage.php
index 161c38097..4b64a8b5e 100644
--- a/app/Livewire/Project/Service/Storage.php
+++ b/app/Livewire/Project/Service/Storage.php
@@ -9,14 +9,36 @@ class Storage extends Component
{
public $resource;
+ public $fileStorage;
+
public function getListeners()
{
+ $teamId = auth()->user()->currentTeam()->id;
+
return [
+ "echo-private:team.{$teamId},FileStorageChanged" => 'refreshStoragesFromEvent',
+ 'refreshStorages',
'addNewVolume',
- 'refresh_storages' => '$refresh',
];
}
+ public function mount()
+ {
+ $this->refreshStorages();
+ }
+
+ public function refreshStoragesFromEvent()
+ {
+ $this->refreshStorages();
+ $this->dispatch('warning', 'File storage changed. Usually it means that the file / directory is already defined on the server, so Coolify set it up for you properly on the UI.');
+ }
+
+ public function refreshStorages()
+ {
+ $this->fileStorage = $this->resource->fileStorages()->get();
+ $this->dispatch('$refresh');
+ }
+
public function addNewVolume($data)
{
try {
@@ -30,7 +52,7 @@ class Storage extends Component
$this->resource->refresh();
$this->dispatch('success', 'Storage added successfully');
$this->dispatch('clearAddStorage');
- $this->dispatch('refresh_storages');
+ $this->dispatch('refreshStorages');
} catch (\Throwable $e) {
return handleError($e, $this);
}
diff --git a/app/Livewire/Project/Shared/Danger.php b/app/Livewire/Project/Shared/Danger.php
index 30c35410f..5f0178be4 100644
--- a/app/Livewire/Project/Shared/Danger.php
+++ b/app/Livewire/Project/Shared/Danger.php
@@ -22,7 +22,7 @@ class Danger extends Component
public function mount()
{
- $this->modalId = new Cuid2(7);
+ $this->modalId = new Cuid2;
$parameters = get_route_parameters();
$this->projectUuid = data_get($parameters, 'project_uuid');
$this->environmentName = data_get($parameters, 'environment_name');
diff --git a/app/Livewire/Project/Shared/Destination.php b/app/Livewire/Project/Shared/Destination.php
index 22ada8ab8..a2c018beb 100644
--- a/app/Livewire/Project/Shared/Destination.php
+++ b/app/Livewire/Project/Shared/Destination.php
@@ -67,7 +67,7 @@ class Destination extends Component
return;
}
- $deployment_uuid = new Cuid2(7);
+ $deployment_uuid = new Cuid2;
$server = Server::find($server_id);
$destination = StandaloneDocker::find($network_id);
queue_application_deployment(
diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/Add.php b/app/Livewire/Project/Shared/EnvironmentVariable/Add.php
index b732b6b52..a859c90b0 100644
--- a/app/Livewire/Project/Shared/EnvironmentVariable/Add.php
+++ b/app/Livewire/Project/Shared/EnvironmentVariable/Add.php
@@ -48,14 +48,14 @@ class Add extends Component
public function submit()
{
$this->validate();
- if (str($this->value)->startsWith('{{') && str($this->value)->endsWith('}}')) {
- $type = str($this->value)->after('{{')->before('.')->value;
- if (! collect(SHARED_VARIABLE_TYPES)->contains($type)) {
- $this->dispatch('error', 'Invalid shared variable type.', 'Valid types are: team, project, environment.');
+ // if (str($this->value)->startsWith('{{') && str($this->value)->endsWith('}}')) {
+ // $type = str($this->value)->after('{{')->before('.')->value;
+ // if (! collect(SHARED_VARIABLE_TYPES)->contains($type)) {
+ // $this->dispatch('error', 'Invalid shared variable type.', 'Valid types are: team, project, environment.');
- return;
- }
- }
+ // return;
+ // }
+ // }
$this->dispatch('saveKey', [
'key' => $this->key,
'value' => $this->value,
diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/All.php b/app/Livewire/Project/Shared/EnvironmentVariable/All.php
index d1edaf4f5..9e6760293 100644
--- a/app/Livewire/Project/Shared/EnvironmentVariable/All.php
+++ b/app/Livewire/Project/Shared/EnvironmentVariable/All.php
@@ -39,7 +39,7 @@ class All extends Component
if (str($this->resourceClass)->contains($resourceWithPreviews) && ! $simpleDockerfile) {
$this->showPreview = true;
}
- $this->modalId = new Cuid2(7);
+ $this->modalId = new Cuid2;
$this->sortMe();
$this->getDevView();
}
@@ -125,14 +125,14 @@ class All extends Component
continue;
}
$found->value = $variable;
- if (str($found->value)->startsWith('{{') && str($found->value)->endsWith('}}')) {
- $type = str($found->value)->after('{{')->before('.')->value;
- if (! collect(SHARED_VARIABLE_TYPES)->contains($type)) {
- $this->dispatch('error', 'Invalid shared variable type.', 'Valid types are: team, project, environment.');
+ // if (str($found->value)->startsWith('{{') && str($found->value)->endsWith('}}')) {
+ // $type = str($found->value)->after('{{')->before('.')->value;
+ // if (! collect(SHARED_VARIABLE_TYPES)->contains($type)) {
+ // $this->dispatch('error', 'Invalid shared variable type.', 'Valid types are: team, project, environment.');
- return;
- }
- }
+ // return;
+ // }
+ // }
$found->save();
continue;
@@ -140,14 +140,14 @@ class All extends Component
$environment = new EnvironmentVariable;
$environment->key = $key;
$environment->value = $variable;
- if (str($environment->value)->startsWith('{{') && str($environment->value)->endsWith('}}')) {
- $type = str($environment->value)->after('{{')->before('.')->value;
- if (! collect(SHARED_VARIABLE_TYPES)->contains($type)) {
- $this->dispatch('error', 'Invalid shared variable type.', 'Valid types are: team, project, environment.');
+ // if (str($environment->value)->startsWith('{{') && str($environment->value)->endsWith('}}')) {
+ // $type = str($environment->value)->after('{{')->before('.')->value;
+ // if (! collect(SHARED_VARIABLE_TYPES)->contains($type)) {
+ // $this->dispatch('error', 'Invalid shared variable type.', 'Valid types are: team, project, environment.');
- return;
- }
- }
+ // return;
+ // }
+ // }
$environment->is_build_time = false;
$environment->is_multiline = false;
$environment->is_preview = $isPreview ? true : false;
diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/Show.php b/app/Livewire/Project/Shared/EnvironmentVariable/Show.php
index d78b47363..e63871602 100644
--- a/app/Livewire/Project/Shared/EnvironmentVariable/Show.php
+++ b/app/Livewire/Project/Shared/EnvironmentVariable/Show.php
@@ -58,7 +58,7 @@ class Show extends Component
if ($this->env->getMorphClass() === 'App\Models\SharedEnvironmentVariable') {
$this->isSharedVariable = true;
}
- $this->modalId = new Cuid2(7);
+ $this->modalId = new Cuid2;
$this->parameters = get_route_parameters();
$this->checkEnvs();
}
@@ -108,14 +108,14 @@ class Show extends Component
} else {
$this->validate();
}
- if (str($this->env->value)->startsWith('{{') && str($this->env->value)->endsWith('}}')) {
- $type = str($this->env->value)->after('{{')->before('.')->value;
- if (! collect(SHARED_VARIABLE_TYPES)->contains($type)) {
- $this->dispatch('error', 'Invalid shared variable type.', 'Valid types are: team, project, environment.');
+ // if (str($this->env->value)->startsWith('{{') && str($this->env->value)->endsWith('}}')) {
+ // $type = str($this->env->value)->after('{{')->before('.')->value;
+ // if (! collect(SHARED_VARIABLE_TYPES)->contains($type)) {
+ // $this->dispatch('error', 'Invalid shared variable type.', 'Valid types are: team, project, environment.');
- return;
- }
- }
+ // return;
+ // }
+ // }
$this->serialize();
$this->env->save();
$this->dispatch('success', 'Environment variable updated.');
diff --git a/app/Livewire/Project/Shared/ResourceOperations.php b/app/Livewire/Project/Shared/ResourceOperations.php
index 586a125ae..ec09eb80f 100644
--- a/app/Livewire/Project/Shared/ResourceOperations.php
+++ b/app/Livewire/Project/Shared/ResourceOperations.php
@@ -39,7 +39,7 @@ class ResourceOperations extends Component
if (! $new_destination) {
return $this->addError('destination_id', 'Destination not found.');
}
- $uuid = (string) new Cuid2(7);
+ $uuid = (string) new Cuid2;
$server = $new_destination->server;
if ($this->resource->getMorphClass() === 'App\Models\Application') {
$new_resource = $this->resource->replicate()->fill([
@@ -87,7 +87,7 @@ class ResourceOperations extends Component
$this->resource->getMorphClass() === 'App\Models\StandaloneDragonfly' ||
$this->resource->getMorphClass() === 'App\Models\StandaloneClickhouse'
) {
- $uuid = (string) new Cuid2(7);
+ $uuid = (string) new Cuid2;
$new_resource = $this->resource->replicate()->fill([
'uuid' => $uuid,
'name' => $this->resource->name.'-clone-'.$uuid,
@@ -121,7 +121,7 @@ class ResourceOperations extends Component
return redirect()->to($route);
} elseif ($this->resource->type() === 'service') {
- $uuid = (string) new Cuid2(7);
+ $uuid = (string) new Cuid2;
$new_resource = $this->resource->replicate()->fill([
'uuid' => $uuid,
'name' => $this->resource->name.'-clone-'.$uuid,
diff --git a/app/Livewire/Project/Shared/ScheduledTask/Show.php b/app/Livewire/Project/Shared/ScheduledTask/Show.php
index dbd420d94..8be4ff643 100644
--- a/app/Livewire/Project/Shared/ScheduledTask/Show.php
+++ b/app/Livewire/Project/Shared/ScheduledTask/Show.php
@@ -47,7 +47,7 @@ class Show extends Component
$this->resource = Service::where('uuid', $this->parameters['service_uuid'])->firstOrFail();
}
- $this->modalId = new Cuid2(7);
+ $this->modalId = new Cuid2;
$this->task = ModelsScheduledTask::where('uuid', request()->route('task_uuid'))->first();
}
diff --git a/app/Livewire/Project/Shared/Storages/Add.php b/app/Livewire/Project/Shared/Storages/Add.php
index d22f3b05f..27e0c6e44 100644
--- a/app/Livewire/Project/Shared/Storages/Add.php
+++ b/app/Livewire/Project/Shared/Storages/Add.php
@@ -54,7 +54,11 @@ class Add extends Component
public function mount()
{
- $this->file_storage_directory_source = application_configuration_dir()."/{$this->resource->uuid}";
+ if (str($this->resource->getMorphClass())->contains('Standalone')) {
+ $this->file_storage_directory_source = database_configuration_dir()."/{$this->resource->uuid}";
+ } else {
+ $this->file_storage_directory_source = application_configuration_dir()."/{$this->resource->uuid}";
+ }
$this->uuid = $this->resource->uuid;
$this->parameters = get_route_parameters();
if (data_get($this->parameters, 'application_uuid')) {
@@ -92,7 +96,7 @@ class Add extends Component
'resource_type' => get_class($this->resource),
],
);
- $this->dispatch('refresh_storages');
+ $this->dispatch('refreshStorages');
} catch (\Throwable $e) {
return handleError($e, $this);
}
@@ -119,7 +123,7 @@ class Add extends Component
'resource_type' => get_class($this->resource),
],
);
- $this->dispatch('refresh_storages');
+ $this->dispatch('refreshStorages');
} catch (\Throwable $e) {
return handleError($e, $this);
}
diff --git a/app/Livewire/Project/Shared/Storages/All.php b/app/Livewire/Project/Shared/Storages/All.php
index d2014694e..c26315d3b 100644
--- a/app/Livewire/Project/Shared/Storages/All.php
+++ b/app/Livewire/Project/Shared/Storages/All.php
@@ -8,5 +8,5 @@ class All extends Component
{
public $resource;
- protected $listeners = ['refresh_storages' => '$refresh'];
+ protected $listeners = ['refreshStorages' => '$refresh'];
}
diff --git a/app/Livewire/Project/Shared/Storages/Show.php b/app/Livewire/Project/Shared/Storages/Show.php
index b64cbfa11..08f51ce08 100644
--- a/app/Livewire/Project/Shared/Storages/Show.php
+++ b/app/Livewire/Project/Shared/Storages/Show.php
@@ -39,6 +39,6 @@ class Show extends Component
public function delete()
{
$this->storage->delete();
- $this->dispatch('refresh_storages');
+ $this->dispatch('refreshStorages');
}
}
diff --git a/app/Livewire/Server/LogDrains.php b/app/Livewire/Server/LogDrains.php
index 3d7b34de1..6e09eecdd 100644
--- a/app/Livewire/Server/LogDrains.php
+++ b/app/Livewire/Server/LogDrains.php
@@ -3,6 +3,7 @@
namespace App\Livewire\Server;
use App\Actions\Server\InstallLogDrain;
+use App\Actions\Server\StopLogDrain;
use App\Models\Server;
use Livewire\Component;
@@ -132,6 +133,9 @@ class LogDrains extends Component
'is_logdrain_axiom_enabled' => false,
]);
}
+ if (! $this->server->isLogDrainEnabled()) {
+ StopLogDrain::dispatch($this->server);
+ }
$this->server->settings->save();
$this->dispatch('success', 'Settings saved.');
diff --git a/app/Livewire/Server/New/ByIp.php b/app/Livewire/Server/New/ByIp.php
index 0f4c1afea..5f69835d7 100644
--- a/app/Livewire/Server/New/ByIp.php
+++ b/app/Livewire/Server/New/ByIp.php
@@ -104,7 +104,7 @@ class ByIp extends Component
'private_key_id' => $this->private_key_id,
'proxy' => [
// set default proxy type to traefik v2
- 'type' => ProxyTypes::TRAEFIK_V2->value,
+ 'type' => ProxyTypes::TRAEFIK->value,
'status' => ProxyStatus::EXITED->value,
],
];
diff --git a/app/Livewire/Server/Proxy.php b/app/Livewire/Server/Proxy.php
index a170ee029..123b29d70 100644
--- a/app/Livewire/Server/Proxy.php
+++ b/app/Livewire/Server/Proxy.php
@@ -20,6 +20,10 @@ class Proxy extends Component
protected $listeners = ['proxyStatusUpdated', 'saveConfiguration' => 'submit'];
+ protected $rules = [
+ 'server.settings.generate_exact_labels' => 'required|boolean',
+ ];
+
public function mount()
{
$this->selectedProxy = $this->server->proxyType();
@@ -31,13 +35,13 @@ class Proxy extends Component
$this->dispatch('refresh')->self();
}
- public function change_proxy()
+ public function changeProxy()
{
$this->server->proxy = null;
$this->server->save();
}
- public function select_proxy($proxy_type)
+ public function selectProxy($proxy_type)
{
$this->server->proxy->set('status', 'exited');
$this->server->proxy->set('type', $proxy_type);
@@ -49,6 +53,17 @@ class Proxy extends Component
$this->dispatch('proxyStatusUpdated');
}
+ public function instantSave()
+ {
+ try {
+ $this->validate();
+ $this->server->settings->save();
+ $this->dispatch('success', 'Settings saved.');
+ } catch (\Throwable $e) {
+ return handleError($e, $this);
+ }
+ }
+
public function submit()
{
try {
diff --git a/app/Livewire/Server/Proxy/DynamicConfigurations.php b/app/Livewire/Server/Proxy/DynamicConfigurations.php
index c858481db..6277a24bd 100644
--- a/app/Livewire/Server/Proxy/DynamicConfigurations.php
+++ b/app/Livewire/Server/Proxy/DynamicConfigurations.php
@@ -21,7 +21,6 @@ class DynamicConfigurations extends Component
return [
"echo-private:team.{$teamId},ProxyStatusChanged" => 'loadDynamicConfigurations',
'loadDynamicConfigurations',
- 'refresh' => '$refresh',
];
}
@@ -42,7 +41,7 @@ class DynamicConfigurations extends Component
$contents[$without_extension] = instant_remote_process(["cat {$proxy_path}/dynamic/{$file}"], $this->server);
}
$this->contents = $contents;
- $this->dispatch('refresh');
+ $this->dispatch('$refresh');
}
public function mount()
diff --git a/app/Livewire/Server/Proxy/NewDynamicConfiguration.php b/app/Livewire/Server/Proxy/NewDynamicConfiguration.php
index e5de6eda0..2155f1e82 100644
--- a/app/Livewire/Server/Proxy/NewDynamicConfiguration.php
+++ b/app/Livewire/Server/Proxy/NewDynamicConfiguration.php
@@ -2,6 +2,7 @@
namespace App\Livewire\Server\Proxy;
+use App\Enums\ProxyTypes;
use App\Models\Server;
use Livewire\Component;
use Symfony\Component\Yaml\Yaml;
@@ -45,7 +46,7 @@ class NewDynamicConfiguration extends Component
return redirect()->route('server.index');
}
$proxy_type = $this->server->proxyType();
- if ($proxy_type === 'TRAEFIK_V2') {
+ if ($proxy_type === ProxyTypes::TRAEFIK->value) {
if (! str($this->fileName)->endsWith('.yaml') && ! str($this->fileName)->endsWith('.yml')) {
$this->fileName = "{$this->fileName}.yaml";
}
@@ -69,7 +70,7 @@ class NewDynamicConfiguration extends Component
return;
}
}
- if ($proxy_type === 'TRAEFIK_V2') {
+ if ($proxy_type === ProxyTypes::TRAEFIK->value) {
$yaml = Yaml::parse($this->value);
$yaml = Yaml::dump($yaml, 10, 2);
$this->value = $yaml;
diff --git a/app/Livewire/Settings/Configuration.php b/app/Livewire/Settings/Configuration.php
deleted file mode 100644
index 7439e112f..000000000
--- a/app/Livewire/Settings/Configuration.php
+++ /dev/null
@@ -1,111 +0,0 @@
- 'nullable',
- 'settings.resale_license' => 'nullable',
- 'settings.public_port_min' => 'required',
- 'settings.public_port_max' => 'required',
- 'settings.custom_dns_servers' => 'nullable',
- 'settings.instance_name' => 'nullable',
- 'settings.allowed_ips' => 'nullable',
- ];
-
- protected $validationAttributes = [
- 'settings.fqdn' => 'FQDN',
- 'settings.resale_license' => 'Resale License',
- 'settings.public_port_min' => 'Public port min',
- 'settings.public_port_max' => 'Public port max',
- 'settings.custom_dns_servers' => 'Custom DNS servers',
- 'settings.allowed_ips' => 'Allowed IPs',
- ];
-
- public function mount()
- {
- $this->do_not_track = $this->settings->do_not_track;
- $this->is_auto_update_enabled = $this->settings->is_auto_update_enabled;
- $this->is_registration_enabled = $this->settings->is_registration_enabled;
- $this->is_dns_validation_enabled = $this->settings->is_dns_validation_enabled;
- $this->is_api_enabled = $this->settings->is_api_enabled;
- }
-
- public function instantSave()
- {
- $this->settings->do_not_track = $this->do_not_track;
- $this->settings->is_auto_update_enabled = $this->is_auto_update_enabled;
- $this->settings->is_registration_enabled = $this->is_registration_enabled;
- $this->settings->is_dns_validation_enabled = $this->is_dns_validation_enabled;
- $this->settings->is_api_enabled = $this->is_api_enabled;
- $this->settings->save();
- $this->dispatch('success', 'Settings updated!');
- }
-
- public function submit()
- {
- try {
- $error_show = false;
- $this->server = Server::findOrFail(0);
- $this->resetErrorBag();
- if ($this->settings->public_port_min > $this->settings->public_port_max) {
- $this->addError('settings.public_port_min', 'The minimum port must be lower than the maximum port.');
-
- return;
- }
- $this->validate();
-
- if ($this->settings->is_dns_validation_enabled && $this->settings->fqdn) {
- if (! validate_dns_entry($this->settings->fqdn, $this->server)) {
- $this->dispatch('error', "Validating DNS failed.
Make sure you have added the DNS records correctly.
{$this->settings->fqdn}->{$this->server->ip}
Check this documentation for further help.");
- $error_show = true;
- }
- }
- if ($this->settings->fqdn) {
- check_domain_usage(domain: $this->settings->fqdn);
- }
- $this->settings->custom_dns_servers = str($this->settings->custom_dns_servers)->replaceEnd(',', '')->trim();
- $this->settings->custom_dns_servers = str($this->settings->custom_dns_servers)->trim()->explode(',')->map(function ($dns) {
- return str($dns)->trim()->lower();
- });
- $this->settings->custom_dns_servers = $this->settings->custom_dns_servers->unique();
- $this->settings->custom_dns_servers = $this->settings->custom_dns_servers->implode(',');
-
- $this->settings->allowed_ips = str($this->settings->allowed_ips)->replaceEnd(',', '')->trim();
- $this->settings->allowed_ips = str($this->settings->allowed_ips)->trim()->explode(',')->map(function ($ip) {
- return str($ip)->trim();
- });
- $this->settings->allowed_ips = $this->settings->allowed_ips->unique();
- $this->settings->allowed_ips = $this->settings->allowed_ips->implode(',');
-
- $this->settings->save();
- $this->server->setupDynamicProxyConfiguration();
- if (! $error_show) {
- $this->dispatch('success', 'Instance settings updated successfully!');
- }
- } catch (\Exception $e) {
- return handleError($e, $this);
- }
- }
-}
diff --git a/app/Livewire/Settings/Index.php b/app/Livewire/Settings/Index.php
index e71d2de00..f593fb78b 100644
--- a/app/Livewire/Settings/Index.php
+++ b/app/Livewire/Settings/Index.php
@@ -2,39 +2,170 @@
namespace App\Livewire\Settings;
+use App\Jobs\CheckForUpdatesJob;
use App\Models\InstanceSettings;
-use App\Models\S3Storage;
-use App\Models\StandalonePostgresql;
+use App\Models\Server;
use Livewire\Component;
class Index extends Component
{
public InstanceSettings $settings;
- public StandalonePostgresql $database;
+ public bool $do_not_track;
- public $s3s;
+ public bool $is_auto_update_enabled;
+
+ public bool $is_registration_enabled;
+
+ public bool $is_dns_validation_enabled;
+
+ public bool $is_api_enabled;
+
+ public string $auto_update_frequency;
+
+ public string $update_check_frequency;
+
+ protected string $dynamic_config_path = '/data/coolify/proxy/dynamic';
+
+ protected Server $server;
+
+ protected $rules = [
+ 'settings.fqdn' => 'nullable',
+ 'settings.resale_license' => 'nullable',
+ 'settings.public_port_min' => 'required',
+ 'settings.public_port_max' => 'required',
+ 'settings.custom_dns_servers' => 'nullable',
+ 'settings.instance_name' => 'nullable',
+ 'settings.allowed_ips' => 'nullable',
+ 'settings.is_auto_update_enabled' => 'boolean',
+ 'auto_update_frequency' => 'string',
+ 'update_check_frequency' => 'string',
+ ];
+
+ protected $validationAttributes = [
+ 'settings.fqdn' => 'FQDN',
+ 'settings.resale_license' => 'Resale License',
+ 'settings.public_port_min' => 'Public port min',
+ 'settings.public_port_max' => 'Public port max',
+ 'settings.custom_dns_servers' => 'Custom DNS servers',
+ 'settings.allowed_ips' => 'Allowed IPs',
+ 'settings.is_auto_update_enabled' => 'Auto Update Enabled',
+ 'auto_update_frequency' => 'Auto Update Frequency',
+ 'update_check_frequency' => 'Update Check Frequency',
+ ];
public function mount()
{
if (isInstanceAdmin()) {
- $settings = \App\Models\InstanceSettings::get();
- $database = StandalonePostgresql::whereName('coolify-db')->first();
- $s3s = S3Storage::whereTeamId(0)->get() ?? [];
- if ($database) {
- if ($database->status !== 'running') {
- $database->status = 'running';
- $database->save();
- }
- $this->database = $database;
- }
- $this->settings = $settings;
- $this->s3s = $s3s;
+ $this->settings = InstanceSettings::get();
+ $this->do_not_track = $this->settings->do_not_track;
+ $this->is_auto_update_enabled = $this->settings->is_auto_update_enabled;
+ $this->is_registration_enabled = $this->settings->is_registration_enabled;
+ $this->is_dns_validation_enabled = $this->settings->is_dns_validation_enabled;
+ $this->is_api_enabled = $this->settings->is_api_enabled;
+ $this->auto_update_frequency = $this->settings->auto_update_frequency;
+ $this->update_check_frequency = $this->settings->update_check_frequency;
} else {
return redirect()->route('dashboard');
}
}
+ public function instantSave()
+ {
+ $this->settings->do_not_track = $this->do_not_track;
+ $this->settings->is_auto_update_enabled = $this->is_auto_update_enabled;
+ $this->settings->is_registration_enabled = $this->is_registration_enabled;
+ $this->settings->is_dns_validation_enabled = $this->is_dns_validation_enabled;
+ $this->settings->is_api_enabled = $this->is_api_enabled;
+ $this->settings->auto_update_frequency = $this->auto_update_frequency;
+ $this->settings->update_check_frequency = $this->update_check_frequency;
+ $this->settings->save();
+ $this->dispatch('success', 'Settings updated!');
+ }
+
+ public function submit()
+ {
+ try {
+ $error_show = false;
+ $this->server = Server::findOrFail(0);
+ $this->resetErrorBag();
+ if ($this->settings->public_port_min > $this->settings->public_port_max) {
+ $this->addError('settings.public_port_min', 'The minimum port must be lower than the maximum port.');
+
+ return;
+ }
+ $this->validate();
+
+ if ($this->is_auto_update_enabled && ! validate_cron_expression($this->auto_update_frequency)) {
+ $this->dispatch('error', 'Invalid Cron / Human expression for Auto Update Frequency.');
+ if (empty($this->auto_update_frequency)) {
+ $this->auto_update_frequency = '0 0 * * *';
+ }
+
+ return;
+ }
+
+ if (! validate_cron_expression($this->update_check_frequency)) {
+ $this->dispatch('error', 'Invalid Cron / Human expression for Update Check Frequency.');
+ if (empty($this->update_check_frequency)) {
+ $this->update_check_frequency = '0 * * * *';
+ }
+
+ return;
+ }
+
+ if ($this->settings->is_dns_validation_enabled && $this->settings->fqdn) {
+ if (! validate_dns_entry($this->settings->fqdn, $this->server)) {
+ $this->dispatch('error', "Validating DNS failed.
Make sure you have added the DNS records correctly.
{$this->settings->fqdn}->{$this->server->ip}
Check this documentation for further help.");
+ $error_show = true;
+ }
+ }
+ if ($this->settings->fqdn) {
+ check_domain_usage(domain: $this->settings->fqdn);
+ }
+ $this->settings->custom_dns_servers = str($this->settings->custom_dns_servers)->replaceEnd(',', '')->trim();
+ $this->settings->custom_dns_servers = str($this->settings->custom_dns_servers)->trim()->explode(',')->map(function ($dns) {
+ return str($dns)->trim()->lower();
+ });
+ $this->settings->custom_dns_servers = $this->settings->custom_dns_servers->unique();
+ $this->settings->custom_dns_servers = $this->settings->custom_dns_servers->implode(',');
+
+ $this->settings->allowed_ips = str($this->settings->allowed_ips)->replaceEnd(',', '')->trim();
+ $this->settings->allowed_ips = str($this->settings->allowed_ips)->trim()->explode(',')->map(function ($ip) {
+ return str($ip)->trim();
+ });
+ $this->settings->allowed_ips = $this->settings->allowed_ips->unique();
+ $this->settings->allowed_ips = $this->settings->allowed_ips->implode(',');
+
+ $this->settings->do_not_track = $this->do_not_track;
+ $this->settings->is_auto_update_enabled = $this->is_auto_update_enabled;
+ $this->settings->is_registration_enabled = $this->is_registration_enabled;
+ $this->settings->is_dns_validation_enabled = $this->is_dns_validation_enabled;
+ $this->settings->is_api_enabled = $this->is_api_enabled;
+ $this->settings->auto_update_frequency = $this->auto_update_frequency;
+ $this->settings->update_check_frequency = $this->update_check_frequency;
+ $this->settings->save();
+ $this->server->setupDynamicProxyConfiguration();
+ if (! $error_show) {
+ $this->dispatch('success', 'Instance settings updated successfully!');
+ }
+ } catch (\Exception $e) {
+ return handleError($e, $this);
+ }
+ }
+
+ public function checkManually()
+ {
+ CheckForUpdatesJob::dispatchSync();
+ $this->dispatch('updateAvailable');
+ $settings = InstanceSettings::get();
+ if ($settings->new_version_available) {
+ $this->dispatch('success', 'New version available!');
+ } else {
+ $this->dispatch('success', 'No new version available.');
+ }
+ }
+
public function render()
{
return view('livewire.settings.index');
diff --git a/app/Livewire/Settings/Backup.php b/app/Livewire/SettingsBackup.php
similarity index 77%
rename from app/Livewire/Settings/Backup.php
rename to app/Livewire/SettingsBackup.php
index 08ad04b2d..da3aee491 100644
--- a/app/Livewire/Settings/Backup.php
+++ b/app/Livewire/SettingsBackup.php
@@ -1,6 +1,6 @@
backup = $this->database?->scheduledBackups->first() ?? null;
- $this->executions = $this->backup?->executions ?? [];
+ if (isInstanceAdmin()) {
+ $settings = InstanceSettings::get();
+ $database = StandalonePostgresql::whereName('coolify-db')->first();
+ $s3s = S3Storage::whereTeamId(0)->get() ?? [];
+ if ($database) {
+ if ($database->status !== 'running') {
+ $database->status = 'running';
+ $database->save();
+ }
+ $this->database = $database;
+ }
+ $this->settings = $settings;
+ $this->s3s = $s3s;
+ $this->backup = $this->database?->scheduledBackups?->first() ?? null;
+ $this->executions = $this->backup?->executions ?? [];
+ } else {
+ return redirect()->route('dashboard');
+ }
}
public function add_coolify_database()
diff --git a/app/Livewire/Settings/Email.php b/app/Livewire/SettingsEmail.php
similarity index 93%
rename from app/Livewire/Settings/Email.php
rename to app/Livewire/SettingsEmail.php
index bd7f8201e..3eb8ea646 100644
--- a/app/Livewire/Settings/Email.php
+++ b/app/Livewire/SettingsEmail.php
@@ -1,12 +1,12 @@
emails = auth()->user()->email;
+ if (isInstanceAdmin()) {
+ $this->settings = InstanceSettings::get();
+ $this->emails = auth()->user()->email;
+ } else {
+ return redirect()->route('dashboard');
+ }
+
}
public function submitFromFields()
diff --git a/app/Livewire/Settings/Auth.php b/app/Livewire/SettingsOauth.php
similarity index 95%
rename from app/Livewire/Settings/Auth.php
rename to app/Livewire/SettingsOauth.php
index 783b163e0..c3884589f 100644
--- a/app/Livewire/Settings/Auth.php
+++ b/app/Livewire/SettingsOauth.php
@@ -1,11 +1,11 @@
'checkUpdate'];
+
public function checkUpdate()
{
- $this->latestVersion = get_latest_version_of_coolify();
- $currentVersion = config('version');
- version_compare($currentVersion, $this->latestVersion, '<') ? $this->isUpgradeAvailable = true : $this->isUpgradeAvailable = false;
- if (isDev()) {
- $this->isUpgradeAvailable = true;
+ try {
+ $settings = InstanceSettings::get();
+ $response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json');
+ if ($response->successful()) {
+ $versions = $response->json();
+ $this->latestVersion = data_get($versions, 'coolify.v4.version');
+ }
+ $this->isUpgradeAvailable = $settings->new_version_available;
+
+ } catch (\Throwable $e) {
+ return handleError($e, $this);
}
+
}
public function upgrade()
diff --git a/app/Models/Application.php b/app/Models/Application.php
index 7b39292e0..e2871da4b 100644
--- a/app/Models/Application.php
+++ b/app/Models/Application.php
@@ -104,6 +104,8 @@ class Application extends BaseModel
protected $guarded = [];
+ protected $appends = ['server_status'];
+
protected static function booted()
{
static::saving(function ($application) {
@@ -232,12 +234,24 @@ class Application extends BaseModel
public function failedTaskLink($task_uuid)
{
if (data_get($this, 'environment.project.uuid')) {
- return route('project.application.scheduled-tasks', [
+ $route = route('project.application.scheduled-tasks', [
'project_uuid' => data_get($this, 'environment.project.uuid'),
'environment_name' => data_get($this, 'environment.name'),
'application_uuid' => data_get($this, 'uuid'),
'task_uuid' => $task_uuid,
]);
+ $settings = InstanceSettings::get();
+ if (data_get($settings, 'fqdn')) {
+ $url = Url::fromString($route);
+ $url = $url->withPort(null);
+ $fqdn = data_get($settings, 'fqdn');
+ $fqdn = str_replace(['http://', 'https://'], '', $fqdn);
+ $url = $url->withHost($fqdn);
+
+ return $url->__toString();
+ }
+
+ return $route;
}
return null;
@@ -275,12 +289,20 @@ class Application extends BaseModel
return Attribute::make(
get: function () {
if (! is_null($this->source?->html_url) && ! is_null($this->git_repository) && ! is_null($this->git_branch)) {
+ if (str($this->git_repository)->contains('bitbucket')) {
+ return "{$this->source->html_url}/{$this->git_repository}/src/{$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);
+ if (str($this->git_repository)->contains('bitbucket')) {
+ return "https://{$git_repository}/src/{$this->git_branch}";
+ }
+
return "https://{$git_repository}/tree/{$this->git_branch}";
}
@@ -431,6 +453,11 @@ class Application extends BaseModel
);
}
+ public function isRunning()
+ {
+ return (bool) str($this->status)->startsWith('running');
+ }
+
public function isExited()
{
return (bool) str($this->status)->startsWith('exited');
@@ -441,6 +468,28 @@ class Application extends BaseModel
return $this->getRawOriginal('status');
}
+ protected function serverStatus(): Attribute
+ {
+ return Attribute::make(
+ get: function () {
+ if ($this->additional_servers->count() === 0) {
+ return $this->destination->server->isFunctional();
+ } else {
+ $additional_servers_status = $this->additional_servers->pluck('pivot.status');
+ $main_server_status = $this->destination->server->isFunctional();
+ foreach ($additional_servers_status as $status) {
+ $server_status = str($status)->before(':')->value();
+ if ($main_server_status !== $server_status) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ }
+ );
+ }
+
public function status(): Attribute
{
return Attribute::make(
@@ -1270,7 +1319,7 @@ class Application extends BaseModel
$template = $this->preview_url_template;
$host = $url->getHost();
$schema = $url->getScheme();
- $random = new Cuid2(7);
+ $random = new Cuid2;
$preview_fqdn = str_replace('{{random}}', $random, $template);
$preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn);
$preview_fqdn = str_replace('{{pr_id}}', $pull_request_id, $preview_fqdn);
diff --git a/app/Models/ApplicationPreview.php b/app/Models/ApplicationPreview.php
index 3bdd24014..57d20e3aa 100644
--- a/app/Models/ApplicationPreview.php
+++ b/app/Models/ApplicationPreview.php
@@ -35,6 +35,11 @@ class ApplicationPreview extends BaseModel
return self::where('application_id', $application_id)->where('pull_request_id', $pull_request_id)->firstOrFail();
}
+ public function isRunning()
+ {
+ return (bool) str($this->status)->startsWith('running');
+ }
+
public function application()
{
return $this->belongsTo(Application::class);
@@ -49,7 +54,7 @@ class ApplicationPreview extends BaseModel
$template = $this->application->preview_url_template;
$host = $url->getHost();
$schema = $url->getScheme();
- $random = new Cuid2(7);
+ $random = new Cuid2;
$preview_fqdn = str_replace('{{random}}', $random, $template);
$preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn);
$preview_fqdn = str_replace('{{pr_id}}', $this->pull_request_id, $preview_fqdn);
diff --git a/app/Models/BaseModel.php b/app/Models/BaseModel.php
index 7e028a6b5..17201ea6e 100644
--- a/app/Models/BaseModel.php
+++ b/app/Models/BaseModel.php
@@ -14,7 +14,7 @@ abstract class BaseModel extends Model
static::creating(function (Model $model) {
// Generate a UUID if one isn't set
if (! $model->uuid) {
- $model->uuid = (string) new Cuid2(7);
+ $model->uuid = (string) new Cuid2;
}
});
}
diff --git a/app/Models/EnvironmentVariable.php b/app/Models/EnvironmentVariable.php
index 28cf0ef93..5e1d8ae13 100644
--- a/app/Models/EnvironmentVariable.php
+++ b/app/Models/EnvironmentVariable.php
@@ -5,7 +5,6 @@ namespace App\Models;
use App\Models\EnvironmentVariable as ModelsEnvironmentVariable;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
-use Illuminate\Support\Str;
use OpenApi\Attributes as OA;
use Symfony\Component\Yaml\Yaml;
use Visus\Cuid2\Cuid2;
@@ -200,28 +199,33 @@ class EnvironmentVariable extends Model
return null;
}
$environment_variable = trim($environment_variable);
- $type = str($environment_variable)->after('{{')->before('.')->value;
- if (str($environment_variable)->startsWith('{{'.$type) && str($environment_variable)->endsWith('}}')) {
- $variable = Str::after($environment_variable, "{$type}.");
- $variable = Str::before($variable, '}}');
- $variable = str($variable)->trim()->value;
+ $sharedEnvsFound = str($environment_variable)->matchAll('/{{(.*?)}}/');
+ if ($sharedEnvsFound->isEmpty()) {
+ return $environment_variable;
+ }
+ foreach ($sharedEnvsFound as $sharedEnv) {
+ $type = str($sharedEnv)->match('/(.*?)\./');
if (! collect(SHARED_VARIABLE_TYPES)->contains($type)) {
- return $variable;
+ continue;
}
- if ($type === 'environment') {
+ $variable = str($sharedEnv)->match('/\.(.*)/');
+ if ($type->value() === 'environment') {
$id = $resource->environment->id;
- } elseif ($type === 'project') {
+ } elseif ($type->value() === 'project') {
$id = $resource->environment->project->id;
- } else {
+ } elseif ($type->value() === 'team') {
$id = $resource->team()->id;
}
+ if (is_null($id)) {
+ continue;
+ }
$environment_variable_found = SharedEnvironmentVariable::where('type', $type)->where('key', $variable)->where('team_id', $resource->team()->id)->where("{$type}_id", $id)->first();
if ($environment_variable_found) {
- return $environment_variable_found;
+ $environment_variable = str($environment_variable)->replace("{{{$sharedEnv}}}", $environment_variable_found->value);
}
}
- return $environment_variable;
+ return str($environment_variable)->value();
}
private function get_environment_variables(?string $environment_variable = null): ?string
diff --git a/app/Models/InstanceSettings.php b/app/Models/InstanceSettings.php
index bd3c41a1f..5bd421956 100644
--- a/app/Models/InstanceSettings.php
+++ b/app/Models/InstanceSettings.php
@@ -18,6 +18,9 @@ class InstanceSettings extends Model implements SendsEmail
'resale_license' => 'encrypted',
'smtp_password' => 'encrypted',
'allowed_ip_ranges' => 'array',
+ 'is_auto_update_enabled' => 'boolean',
+ 'auto_update_frequency' => 'string',
+ 'update_check_frequency' => 'string',
];
public function fqdn(): Attribute
diff --git a/app/Models/LocalFileVolume.php b/app/Models/LocalFileVolume.php
index decfb1a8d..a436f5797 100644
--- a/app/Models/LocalFileVolume.php
+++ b/app/Models/LocalFileVolume.php
@@ -2,6 +2,7 @@
namespace App\Models;
+use App\Events\FileStorageChanged;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class LocalFileVolume extends BaseModel
@@ -33,16 +34,23 @@ class LocalFileVolume extends BaseModel
$workdir = $this->resource->workdir();
$server = $this->resource->destination->server;
}
- $commands = collect([
- "cd $workdir",
- ]);
+ $commands = collect([]);
$fs_path = data_get($this, 'fs_path');
+ $isFile = instant_remote_process(["test -f $fs_path && echo OK || echo NOK"], $server);
+ $isDir = instant_remote_process(["test -d $fs_path && echo OK || echo NOK"], $server);
if ($fs_path && $fs_path != '/' && $fs_path != '.' && $fs_path != '..') {
- $commands->push("rm -rf $fs_path");
- }
- ray($commands);
+ ray($isFile, $isDir);
+ if ($isFile === 'OK') {
+ $commands->push("rm -rf $fs_path > /dev/null 2>&1 || true");
- return instant_remote_process($commands, $server);
+ } elseif ($isDir === 'OK') {
+ $commands->push("rm -rf $fs_path > /dev/null 2>&1 || true");
+ $commands->push("rmdir $fs_path > /dev/null 2>&1 || true");
+ }
+ }
+ if ($commands->count() > 0) {
+ return instant_remote_process($commands, $server);
+ }
}
public function saveStorageOnServer()
@@ -55,13 +63,10 @@ class LocalFileVolume extends BaseModel
$workdir = $this->resource->workdir();
$server = $this->resource->destination->server;
}
- $commands = collect([
- "mkdir -p $workdir > /dev/null 2>&1 || true",
- "cd $workdir",
- ]);
- $is_directory = $this->is_directory;
- if ($is_directory) {
+ $commands = collect([]);
+ if ($this->is_directory) {
$commands->push("mkdir -p $this->fs_path > /dev/null 2>&1 || true");
+ $commands->push("cd $workdir");
}
if (str($this->fs_path)->startsWith('.') || str($this->fs_path)->startsWith('/') || str($this->fs_path)->startsWith('~')) {
$parent_dir = str($this->fs_path)->beforeLast('/');
@@ -79,8 +84,11 @@ class LocalFileVolume extends BaseModel
$isFile = instant_remote_process(["test -f $path && echo OK || echo NOK"], $server);
$isDir = instant_remote_process(["test -d $path && echo OK || echo NOK"], $server);
if ($isFile == 'OK' && $fileVolume->is_directory) {
+ $content = instant_remote_process(["cat $path"], $server, false);
$fileVolume->is_directory = false;
+ $fileVolume->content = $content;
$fileVolume->save();
+ FileStorageChanged::dispatch(data_get($server, 'team_id'));
throw new \Exception('The following file is a file on the server, but you are trying to mark it as a directory. Please delete the file on the server or mark it as directory.');
} elseif ($isDir == 'OK' && ! $fileVolume->is_directory) {
$fileVolume->is_directory = true;
diff --git a/app/Models/Server.php b/app/Models/Server.php
index 9166f5a0f..99dca3ecf 100644
--- a/app/Models/Server.php
+++ b/app/Models/Server.php
@@ -5,6 +5,7 @@ namespace App\Models;
use App\Actions\Server\InstallDocker;
use App\Enums\ProxyTypes;
use App\Jobs\PullSentinelImageJob;
+use App\Notifications\Server\Revived;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Support\Collection;
@@ -150,7 +151,7 @@ class Server extends BaseModel
$dynamic_conf_path = $this->proxyPath().'/dynamic';
$proxy_type = $this->proxyType();
$redirect_url = $this->proxy->redirect_url;
- if ($proxy_type === 'TRAEFIK_V2') {
+ if ($proxy_type === ProxyTypes::TRAEFIK->value) {
$default_redirect_file = "$dynamic_conf_path/default_redirect_404.yaml";
} elseif ($proxy_type === 'CADDY') {
$default_redirect_file = "$dynamic_conf_path/default_redirect_404.caddy";
@@ -180,7 +181,7 @@ respond 404
return;
}
- if ($proxy_type === 'TRAEFIK_V2') {
+ if ($proxy_type === ProxyTypes::TRAEFIK->value) {
$dynamic_conf = [
'http' => [
'routers' => [
@@ -254,7 +255,7 @@ respond 404
{
$settings = \App\Models\InstanceSettings::get();
$dynamic_config_path = $this->proxyPath().'/dynamic';
- if ($this->proxyType() === 'TRAEFIK_V2') {
+ if ($this->proxyType() === ProxyTypes::TRAEFIK->value) {
$file = "$dynamic_config_path/coolify.yaml";
if (empty($settings->fqdn) || (isCloud() && $this->id !== 0) || ! $this->isLocalhost()) {
instant_remote_process([
@@ -402,7 +403,7 @@ $schema://$host {
// TODO: should use /traefik for already exisiting configurations?
// Should move everything except /caddy and /nginx to /traefik
// The code needs to be modified as well, so maybe it does not worth it
- if ($proxyType === ProxyTypes::TRAEFIK_V2->value) {
+ if ($proxyType === ProxyTypes::TRAEFIK->value) {
$proxy_path = $proxy_path;
} elseif ($proxyType === ProxyTypes::CADDY->value) {
$proxy_path = $proxy_path.'/caddy';
@@ -420,7 +421,7 @@ $schema://$host {
// return $proxyType;
// }
// if (is_null($proxyType)) {
- // $this->proxy->type = ProxyTypes::TRAEFIK_V2->value;
+ // $this->proxy->type = ProxyTypes::TRAEFIK->value;
// $this->proxy->status = ProxyStatus::EXITED->value;
// $this->save();
// }
@@ -677,7 +678,49 @@ $schema://$host {
return instant_remote_process(["docker start $id"], $this);
}
- public function getContainers(): Collection
+ public function getContainers()
+ {
+ $containers = collect([]);
+ $containerReplicates = collect([]);
+ if ($this->isSwarm()) {
+ $containers = instant_remote_process(["docker service inspect $(docker service ls -q) --format '{{json .}}'"], $this, false);
+ $containers = format_docker_command_output_to_json($containers);
+ $containerReplicates = instant_remote_process(["docker service ls --format '{{json .}}'"], $this, false);
+ 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;
+ });
+ }
+ }
+ } else {
+ $containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this, false);
+ $containers = format_docker_command_output_to_json($containers);
+ $containerReplicates = collect([]);
+ }
+
+ return [
+ 'containers' => $containers ?? collect([]),
+ 'containerReplicates' => $containerReplicates ?? collect([]),
+ ];
+ }
+
+ public function getContainersWithSentinel(): Collection
{
$sentinel_found = instant_remote_process(['docker inspect coolify-sentinel'], $this, false);
$sentinel_found = json_decode($sentinel_found, true);
@@ -690,21 +733,6 @@ $schema://$host {
$containers = data_get(json_decode($containers, true), 'containers', []);
return collect($containers);
- } else {
- if ($this->isSwarm()) {
- $containers = instant_remote_process(["docker service inspect $(docker service ls -q) --format '{{json .}}'"], $this, false);
- } else {
- $containers = instant_remote_process(['docker container ls -q'], $this, false);
- if (! $containers) {
- return collect([]);
- }
- $containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this, false);
- }
- if (is_null($containers)) {
- return collect([]);
- }
-
- return format_docker_command_output_to_json($containers);
}
}
diff --git a/app/Models/Service.php b/app/Models/Service.php
index 8336b90c8..33238281e 100644
--- a/app/Models/Service.php
+++ b/app/Models/Service.php
@@ -2,11 +2,13 @@
namespace App\Models;
+use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Collection;
use OpenApi\Attributes as OA;
+use Spatie\Url\Url;
use Symfony\Component\Yaml\Yaml;
#[OA\Schema(
@@ -38,6 +40,8 @@ class Service extends BaseModel
protected $guarded = [];
+ protected $appends = ['server_status'];
+
public function isConfigurationChanged(bool $save = false)
{
$domains = $this->applications()->get()->pluck('fqdn')->sort()->toArray();
@@ -76,6 +80,20 @@ class Service extends BaseModel
}
}
+ protected function serverStatus(): Attribute
+ {
+ return Attribute::make(
+ get: function () {
+ return $this->server->isFunctional();
+ }
+ );
+ }
+
+ public function isRunning()
+ {
+ return (bool) str($this->status())->contains('running');
+ }
+
public function isExited()
{
return (bool) str($this->status())->contains('exited');
@@ -575,6 +593,30 @@ class Service extends BaseModel
$fields->put('Vaultwarden', $data);
break;
+ case str($image)->contains('gitlab/gitlab'):
+ $password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_GITLAB')->first();
+ $data = collect([]);
+ if ($password) {
+ $data = $data->merge([
+ 'Root Password' => [
+ 'key' => data_get($password, 'key'),
+ 'value' => data_get($password, 'value'),
+ 'rules' => 'required',
+ 'isPassword' => true,
+ ],
+ ]);
+ }
+ $data = $data->merge([
+ 'Root User' => [
+ 'key' => 'N/A',
+ 'value' => 'root',
+ 'rules' => 'required',
+ 'isPassword' => true,
+ ],
+ ]);
+
+ $fields->put('GitLab', $data->toArray());
+ break;
}
}
$databases = $this->databases()->get();
@@ -764,12 +806,24 @@ class Service extends BaseModel
public function failedTaskLink($task_uuid)
{
if (data_get($this, 'environment.project.uuid')) {
- return route('project.service.scheduled-tasks', [
+ $route = route('project.service.scheduled-tasks', [
'project_uuid' => data_get($this, 'environment.project.uuid'),
'environment_name' => data_get($this, 'environment.name'),
'service_uuid' => data_get($this, 'uuid'),
'task_uuid' => $task_uuid,
]);
+ $settings = InstanceSettings::get();
+ if (data_get($settings, 'fqdn')) {
+ $url = Url::fromString($route);
+ $url = $url->withPort(null);
+ $fqdn = data_get($settings, 'fqdn');
+ $fqdn = str_replace(['http://', 'https://'], '', $fqdn);
+ $url = $url->withHost($fqdn);
+
+ return $url->__toString();
+ }
+
+ return $route;
}
return null;
diff --git a/app/Models/StandaloneClickhouse.php b/app/Models/StandaloneClickhouse.php
index f930226da..4cd194cd8 100644
--- a/app/Models/StandaloneClickhouse.php
+++ b/app/Models/StandaloneClickhouse.php
@@ -14,7 +14,7 @@ class StandaloneClickhouse extends BaseModel
protected $guarded = [];
- protected $appends = ['internal_db_url', 'external_db_url', 'database_type'];
+ protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
protected $casts = [
'clickhouse_password' => 'encrypted',
@@ -40,6 +40,15 @@ class StandaloneClickhouse extends BaseModel
});
}
+ protected function serverStatus(): Attribute
+ {
+ return Attribute::make(
+ get: function () {
+ return $this->destination->server->isFunctional();
+ }
+ );
+ }
+
public function isConfigurationChanged(bool $save = false)
{
$newConfigHash = $this->image.$this->ports_mappings;
diff --git a/app/Models/StandaloneDragonfly.php b/app/Models/StandaloneDragonfly.php
index be04e2d83..8726b2546 100644
--- a/app/Models/StandaloneDragonfly.php
+++ b/app/Models/StandaloneDragonfly.php
@@ -14,7 +14,7 @@ class StandaloneDragonfly extends BaseModel
protected $guarded = [];
- protected $appends = ['internal_db_url', 'external_db_url', 'database_type'];
+ protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
protected $casts = [
'dragonfly_password' => 'encrypted',
@@ -40,6 +40,15 @@ class StandaloneDragonfly extends BaseModel
});
}
+ protected function serverStatus(): Attribute
+ {
+ return Attribute::make(
+ get: function () {
+ return $this->destination->server->isFunctional();
+ }
+ );
+ }
+
public function isConfigurationChanged(bool $save = false)
{
$newConfigHash = $this->image.$this->ports_mappings;
diff --git a/app/Models/StandaloneKeydb.php b/app/Models/StandaloneKeydb.php
index 1b7d5a958..607cacade 100644
--- a/app/Models/StandaloneKeydb.php
+++ b/app/Models/StandaloneKeydb.php
@@ -14,7 +14,7 @@ class StandaloneKeydb extends BaseModel
protected $guarded = [];
- protected $appends = ['internal_db_url', 'external_db_url'];
+ protected $appends = ['internal_db_url', 'external_db_url', 'server_status'];
protected $casts = [
'keydb_password' => 'encrypted',
@@ -40,6 +40,15 @@ class StandaloneKeydb extends BaseModel
});
}
+ protected function serverStatus(): Attribute
+ {
+ return Attribute::make(
+ get: function () {
+ return $this->destination->server->isFunctional();
+ }
+ );
+ }
+
public function isConfigurationChanged(bool $save = false)
{
$newConfigHash = $this->image.$this->ports_mappings.$this->keydb_conf;
diff --git a/app/Models/StandaloneMariadb.php b/app/Models/StandaloneMariadb.php
index 2081d9c89..d88653e41 100644
--- a/app/Models/StandaloneMariadb.php
+++ b/app/Models/StandaloneMariadb.php
@@ -14,7 +14,7 @@ class StandaloneMariadb extends BaseModel
protected $guarded = [];
- protected $appends = ['internal_db_url', 'external_db_url', 'database_type'];
+ protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
protected $casts = [
'mariadb_password' => 'encrypted',
@@ -40,6 +40,15 @@ class StandaloneMariadb extends BaseModel
});
}
+ protected function serverStatus(): Attribute
+ {
+ return Attribute::make(
+ get: function () {
+ return $this->destination->server->isFunctional();
+ }
+ );
+ }
+
public function isConfigurationChanged(bool $save = false)
{
$newConfigHash = $this->image.$this->ports_mappings.$this->mariadb_conf;
diff --git a/app/Models/StandaloneMongodb.php b/app/Models/StandaloneMongodb.php
index 066b34ab7..f09e932bf 100644
--- a/app/Models/StandaloneMongodb.php
+++ b/app/Models/StandaloneMongodb.php
@@ -14,7 +14,7 @@ class StandaloneMongodb extends BaseModel
protected $guarded = [];
- protected $appends = ['internal_db_url', 'external_db_url', 'database_type'];
+ protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
protected static function booted()
{
@@ -44,6 +44,15 @@ class StandaloneMongodb extends BaseModel
});
}
+ protected function serverStatus(): Attribute
+ {
+ return Attribute::make(
+ get: function () {
+ return $this->destination->server->isFunctional();
+ }
+ );
+ }
+
public function isConfigurationChanged(bool $save = false)
{
$newConfigHash = $this->image.$this->ports_mappings.$this->mongo_conf;
diff --git a/app/Models/StandaloneMysql.php b/app/Models/StandaloneMysql.php
index 375b56133..f4e56fab2 100644
--- a/app/Models/StandaloneMysql.php
+++ b/app/Models/StandaloneMysql.php
@@ -14,7 +14,7 @@ class StandaloneMysql extends BaseModel
protected $guarded = [];
- protected $appends = ['internal_db_url', 'external_db_url', 'database_type'];
+ protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
protected $casts = [
'mysql_password' => 'encrypted',
@@ -41,6 +41,15 @@ class StandaloneMysql extends BaseModel
});
}
+ protected function serverStatus(): Attribute
+ {
+ return Attribute::make(
+ get: function () {
+ return $this->destination->server->isFunctional();
+ }
+ );
+ }
+
public function isConfigurationChanged(bool $save = false)
{
$newConfigHash = $this->image.$this->ports_mappings.$this->mysql_conf;
diff --git a/app/Models/StandalonePostgresql.php b/app/Models/StandalonePostgresql.php
index ab6bcc626..311c09c36 100644
--- a/app/Models/StandalonePostgresql.php
+++ b/app/Models/StandalonePostgresql.php
@@ -14,7 +14,7 @@ class StandalonePostgresql extends BaseModel
protected $guarded = [];
- protected $appends = ['internal_db_url', 'external_db_url', 'database_type'];
+ protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
protected $casts = [
'init_scripts' => 'array',
@@ -46,6 +46,15 @@ class StandalonePostgresql extends BaseModel
return database_configuration_dir()."/{$this->uuid}";
}
+ protected function serverStatus(): Attribute
+ {
+ return Attribute::make(
+ get: function () {
+ return $this->destination->server->isFunctional();
+ }
+ );
+ }
+
public function delete_configurations()
{
$server = data_get($this, 'destination.server');
diff --git a/app/Models/StandaloneRedis.php b/app/Models/StandaloneRedis.php
index df6cc8aeb..8a202ea9e 100644
--- a/app/Models/StandaloneRedis.php
+++ b/app/Models/StandaloneRedis.php
@@ -14,7 +14,7 @@ class StandaloneRedis extends BaseModel
protected $guarded = [];
- protected $appends = ['internal_db_url', 'external_db_url', 'database_type'];
+ protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
protected static function booted()
{
@@ -36,6 +36,15 @@ class StandaloneRedis extends BaseModel
});
}
+ protected function serverStatus(): Attribute
+ {
+ return Attribute::make(
+ get: function () {
+ return $this->destination->server->isFunctional();
+ }
+ );
+ }
+
public function isConfigurationChanged(bool $save = false)
{
$newConfigHash = $this->image.$this->ports_mappings.$this->redis_conf;
diff --git a/app/Models/User.php b/app/Models/User.php
index 3625b9930..ecc4ef6b6 100644
--- a/app/Models/User.php
+++ b/app/Models/User.php
@@ -180,6 +180,10 @@ class User extends Authenticatable implements SendsEmail
{
$found_root_team = auth()->user()->teams->filter(function ($team) {
if ($team->id == 0) {
+ if (! auth()->user()->isAdmin()) {
+ return false;
+ }
+
return true;
}
diff --git a/app/Notifications/Server/Revived.php b/app/Notifications/Server/Revived.php
index e05e13e9b..3f2b3b696 100644
--- a/app/Notifications/Server/Revived.php
+++ b/app/Notifications/Server/Revived.php
@@ -12,6 +12,7 @@ use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
+use Illuminate\Support\Facades\RateLimiter;
class Revived extends Notification implements ShouldQueue
{
@@ -44,8 +45,20 @@ class Revived extends Notification implements ShouldQueue
if ($isTelegramEnabled) {
$channels[] = TelegramChannel::class;
}
+ $executed = RateLimiter::attempt(
+ 'notification-server-revived-'.$this->server->uuid,
+ 1,
+ function () use ($channels) {
+ return $channels;
+ },
+ 7200,
+ );
- return $channels;
+ if (! $executed) {
+ return [];
+ }
+
+ return $executed;
}
public function toMail(): MailMessage
diff --git a/app/Notifications/Server/Unreachable.php b/app/Notifications/Server/Unreachable.php
index f178c9be3..2fb83559a 100644
--- a/app/Notifications/Server/Unreachable.php
+++ b/app/Notifications/Server/Unreachable.php
@@ -10,6 +10,7 @@ use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
+use Illuminate\Support\Facades\RateLimiter;
class Unreachable extends Notification implements ShouldQueue
{
@@ -35,8 +36,20 @@ class Unreachable extends Notification implements ShouldQueue
if ($isTelegramEnabled) {
$channels[] = TelegramChannel::class;
}
+ $executed = RateLimiter::attempt(
+ 'notification-server-unreachable-'.$this->server->uuid,
+ 1,
+ function () use ($channels) {
+ return $channels;
+ },
+ 7200,
+ );
- return $channels;
+ if (! $executed) {
+ return [];
+ }
+
+ return $executed;
}
public function toMail(): MailMessage
diff --git a/app/Providers/FortifyServiceProvider.php b/app/Providers/FortifyServiceProvider.php
index 9b0a81026..53a2e9281 100644
--- a/app/Providers/FortifyServiceProvider.php
+++ b/app/Providers/FortifyServiceProvider.php
@@ -44,6 +44,8 @@ class FortifyServiceProvider extends ServiceProvider
{
Fortify::createUsersUsing(CreateNewUser::class);
Fortify::registerView(function () {
+ $isFirstUser = User::count() === 0;
+
$settings = \App\Models\InstanceSettings::get();
if (! $settings->is_registration_enabled) {
return redirect()->route('login');
@@ -51,7 +53,9 @@ class FortifyServiceProvider extends ServiceProvider
if (config('coolify.waitlist')) {
return redirect()->route('waitlist.index');
} else {
- return view('auth.register');
+ return view('auth.register', [
+ 'isFirstUser' => $isFirstUser,
+ ]);
}
});
diff --git a/app/View/Components/Forms/Datalist.php b/app/View/Components/Forms/Datalist.php
index df0c1cb11..25643753d 100644
--- a/app/View/Components/Forms/Datalist.php
+++ b/app/View/Components/Forms/Datalist.php
@@ -30,7 +30,7 @@ class Datalist extends Component
public function render(): View|Closure|string
{
if (is_null($this->id)) {
- $this->id = new Cuid2(7);
+ $this->id = new Cuid2;
}
if (is_null($this->name)) {
$this->name = $this->id;
diff --git a/app/View/Components/Forms/Input.php b/app/View/Components/Forms/Input.php
index 35448d5e5..6c9378cac 100644
--- a/app/View/Components/Forms/Input.php
+++ b/app/View/Components/Forms/Input.php
@@ -27,7 +27,7 @@ class Input extends Component
public function render(): View|Closure|string
{
if (is_null($this->id)) {
- $this->id = new Cuid2(7);
+ $this->id = new Cuid2;
}
if (is_null($this->name)) {
$this->name = $this->id;
diff --git a/app/View/Components/Forms/Select.php b/app/View/Components/Forms/Select.php
index 21c147c2b..dd5ba66b7 100644
--- a/app/View/Components/Forms/Select.php
+++ b/app/View/Components/Forms/Select.php
@@ -30,7 +30,7 @@ class Select extends Component
public function render(): View|Closure|string
{
if (is_null($this->id)) {
- $this->id = new Cuid2(7);
+ $this->id = new Cuid2;
}
if (is_null($this->name)) {
$this->name = $this->id;
diff --git a/app/View/Components/Forms/Textarea.php b/app/View/Components/Forms/Textarea.php
index 7d1860500..3f887877c 100644
--- a/app/View/Components/Forms/Textarea.php
+++ b/app/View/Components/Forms/Textarea.php
@@ -41,7 +41,7 @@ class Textarea extends Component
public function render(): View|Closure|string
{
if (is_null($this->id)) {
- $this->id = new Cuid2(7);
+ $this->id = new Cuid2;
}
if (is_null($this->name)) {
$this->name = $this->id;
diff --git a/bootstrap/helpers/databases.php b/bootstrap/helpers/databases.php
index 5bfdcce78..b8dcc1f3c 100644
--- a/bootstrap/helpers/databases.php
+++ b/bootstrap/helpers/databases.php
@@ -14,7 +14,7 @@ use Visus\Cuid2\Cuid2;
function generate_database_name(string $type): string
{
- $cuid = new Cuid2(7);
+ $cuid = new Cuid2;
return $type.'-database-'.$cuid;
}
diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php
index 0aa5c6b74..a534dc5ff 100644
--- a/bootstrap/helpers/docker.php
+++ b/bootstrap/helpers/docker.php
@@ -1,5 +1,6 @@
$domain) {
try {
if ($generate_unique_uuid) {
- $uuid = new Cuid2(7);
+ $uuid = new Cuid2;
}
$url = Url::fromString($domain);
@@ -539,26 +540,55 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
if ($pull_request_id === 0) {
if ($application->fqdn) {
$domains = str(data_get($application, 'fqdn'))->explode(',');
- $labels = $labels->merge(fqdnLabelsForTraefik(
- uuid: $appUuid,
- domains: $domains,
- onlyPort: $onlyPort,
- is_force_https_enabled: $application->isForceHttpsEnabled(),
- is_gzip_enabled: $application->isGzipEnabled(),
- is_stripprefix_enabled: $application->isStripprefixEnabled(),
- redirect_direction: $application->redirect
- ));
- // Add Caddy labels
- $labels = $labels->merge(fqdnLabelsForCaddy(
- network: $application->destination->network,
- uuid: $appUuid,
- domains: $domains,
- onlyPort: $onlyPort,
- is_force_https_enabled: $application->isForceHttpsEnabled(),
- is_gzip_enabled: $application->isGzipEnabled(),
- is_stripprefix_enabled: $application->isStripprefixEnabled(),
- redirect_direction: $application->redirect
- ));
+ $shouldGenerateLabelsExactly = $application->destination->server->settings->generate_exact_labels;
+ if ($shouldGenerateLabelsExactly) {
+ switch ($application->destination->server->proxyType()) {
+ case ProxyTypes::TRAEFIK->value:
+ $labels = $labels->merge(fqdnLabelsForTraefik(
+ uuid: $appUuid,
+ domains: $domains,
+ onlyPort: $onlyPort,
+ is_force_https_enabled: $application->isForceHttpsEnabled(),
+ is_gzip_enabled: $application->isGzipEnabled(),
+ is_stripprefix_enabled: $application->isStripprefixEnabled(),
+ redirect_direction: $application->redirect
+ ));
+ break;
+ case ProxyTypes::CADDY->value:
+ $labels = $labels->merge(fqdnLabelsForCaddy(
+ network: $application->destination->network,
+ uuid: $appUuid,
+ domains: $domains,
+ onlyPort: $onlyPort,
+ is_force_https_enabled: $application->isForceHttpsEnabled(),
+ is_gzip_enabled: $application->isGzipEnabled(),
+ is_stripprefix_enabled: $application->isStripprefixEnabled(),
+ redirect_direction: $application->redirect
+ ));
+ break;
+ }
+ } else {
+ $labels = $labels->merge(fqdnLabelsForTraefik(
+ uuid: $appUuid,
+ domains: $domains,
+ onlyPort: $onlyPort,
+ is_force_https_enabled: $application->isForceHttpsEnabled(),
+ is_gzip_enabled: $application->isGzipEnabled(),
+ is_stripprefix_enabled: $application->isStripprefixEnabled(),
+ redirect_direction: $application->redirect
+ ));
+ $labels = $labels->merge(fqdnLabelsForCaddy(
+ network: $application->destination->network,
+ uuid: $appUuid,
+ domains: $domains,
+ onlyPort: $onlyPort,
+ is_force_https_enabled: $application->isForceHttpsEnabled(),
+ is_gzip_enabled: $application->isGzipEnabled(),
+ is_stripprefix_enabled: $application->isStripprefixEnabled(),
+ redirect_direction: $application->redirect
+ ));
+ }
+
}
} else {
if (data_get($preview, 'fqdn')) {
@@ -566,24 +596,50 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
} else {
$domains = collect([]);
}
- $labels = $labels->merge(fqdnLabelsForTraefik(
- uuid: $appUuid,
- domains: $domains,
- onlyPort: $onlyPort,
- is_force_https_enabled: $application->isForceHttpsEnabled(),
- is_gzip_enabled: $application->isGzipEnabled(),
- is_stripprefix_enabled: $application->isStripprefixEnabled()
- ));
- // Add Caddy labels
- $labels = $labels->merge(fqdnLabelsForCaddy(
- network: $application->destination->network,
- uuid: $appUuid,
- domains: $domains,
- onlyPort: $onlyPort,
- is_force_https_enabled: $application->isForceHttpsEnabled(),
- is_gzip_enabled: $application->isGzipEnabled(),
- is_stripprefix_enabled: $application->isStripprefixEnabled()
- ));
+ $shouldGenerateLabelsExactly = $application->destination->server->settings->generate_exact_labels;
+ if ($shouldGenerateLabelsExactly) {
+ switch ($application->destination->server->proxyType()) {
+ case ProxyTypes::TRAEFIK->value:
+ $labels = $labels->merge(fqdnLabelsForTraefik(
+ uuid: $appUuid,
+ domains: $domains,
+ onlyPort: $onlyPort,
+ is_force_https_enabled: $application->isForceHttpsEnabled(),
+ is_gzip_enabled: $application->isGzipEnabled(),
+ is_stripprefix_enabled: $application->isStripprefixEnabled()
+ ));
+ break;
+ case ProxyTypes::CADDY->value:
+ $labels = $labels->merge(fqdnLabelsForCaddy(
+ network: $application->destination->network,
+ uuid: $appUuid,
+ domains: $domains,
+ onlyPort: $onlyPort,
+ is_force_https_enabled: $application->isForceHttpsEnabled(),
+ is_gzip_enabled: $application->isGzipEnabled(),
+ is_stripprefix_enabled: $application->isStripprefixEnabled()
+ ));
+ break;
+ }
+ } else {
+ $labels = $labels->merge(fqdnLabelsForTraefik(
+ uuid: $appUuid,
+ domains: $domains,
+ onlyPort: $onlyPort,
+ is_force_https_enabled: $application->isForceHttpsEnabled(),
+ is_gzip_enabled: $application->isGzipEnabled(),
+ is_stripprefix_enabled: $application->isStripprefixEnabled()
+ ));
+ $labels = $labels->merge(fqdnLabelsForCaddy(
+ network: $application->destination->network,
+ uuid: $appUuid,
+ domains: $domains,
+ onlyPort: $onlyPort,
+ is_force_https_enabled: $application->isForceHttpsEnabled(),
+ is_gzip_enabled: $application->isGzipEnabled(),
+ is_stripprefix_enabled: $application->isStripprefixEnabled()
+ ));
+ }
}
diff --git a/bootstrap/helpers/proxy.php b/bootstrap/helpers/proxy.php
index 23c9a2333..f18b09f56 100644
--- a/bootstrap/helpers/proxy.php
+++ b/bootstrap/helpers/proxy.php
@@ -24,6 +24,7 @@ function collectProxyDockerNetworksByServer(Server $server)
}
function collectDockerNetworksByServer(Server $server)
{
+ $allNetworks = collect([]);
if ($server->isSwarm()) {
$networks = collect($server->swarmDockers)->map(function ($docker) {
return $docker['network'];
@@ -34,18 +35,28 @@ function collectDockerNetworksByServer(Server $server)
return $docker['network'];
});
}
+ $allNetworks = $allNetworks->merge($networks);
// Service networks
foreach ($server->services()->get() as $service) {
- $networks->push($service->networks());
+ if ($service->isRunning()) {
+ $networks->push($service->networks());
+ }
+ $allNetworks->push($service->networks());
}
// Docker compose based apps
$docker_compose_apps = $server->dockerComposeBasedApplications();
foreach ($docker_compose_apps as $app) {
- $networks->push($app->uuid);
+ if ($app->isRunning()) {
+ $networks->push($app->uuid);
+ }
+ $allNetworks->push($app->uuid);
}
// Docker compose based preview deployments
$docker_compose_previews = $server->dockerComposeBasedPreviewDeployments();
foreach ($docker_compose_previews as $preview) {
+ if (! $preview->isRunning()) {
+ continue;
+ }
$pullRequestId = $preview->pull_request_id;
$applicationId = $preview->application_id;
$application = Application::find($applicationId);
@@ -54,23 +65,30 @@ function collectDockerNetworksByServer(Server $server)
}
$network = "{$application->uuid}-{$pullRequestId}";
$networks->push($network);
+ $allNetworks->push($network);
}
$networks = collect($networks)->flatten()->unique();
+ $allNetworks = $allNetworks->flatten()->unique();
if ($server->isSwarm()) {
if ($networks->count() === 0) {
$networks = collect(['coolify-overlay']);
+ $allNetworks = collect(['coolify-overlay']);
}
} else {
if ($networks->count() === 0) {
$networks = collect(['coolify']);
+ $allNetworks = collect(['coolify']);
}
}
- return $networks;
+ return [
+ 'networks' => $networks,
+ 'allNetworks' => $allNetworks,
+ ];
}
function connectProxyToNetworks(Server $server)
{
- $networks = collectDockerNetworksByServer($server);
+ ['networks' => $networks] = collectDockerNetworksByServer($server);
if ($server->isSwarm()) {
$commands = $networks->map(function ($network) {
return [
@@ -118,7 +136,7 @@ function generate_default_proxy_configuration(Server $server)
'external' => true,
];
});
- if ($proxy_type === 'TRAEFIK_V2') {
+ if ($proxy_type === ProxyTypes::TRAEFIK->value) {
$labels = [
'traefik.enable=true',
'traefik.http.routers.traefik.entrypoints=http',
diff --git a/bootstrap/helpers/services.php b/bootstrap/helpers/services.php
index d4e9ebcde..1cc2ac36d 100644
--- a/bootstrap/helpers/services.php
+++ b/bootstrap/helpers/services.php
@@ -78,7 +78,7 @@ function getFilesystemVolumesFromServer(ServiceApplication|ServiceDatabase|Appli
$fileVolume->is_directory = true;
$fileVolume->save();
instant_remote_process(["mkdir -p $fileLocation"], $server);
- } elseif ($isFile == 'NOK' && $isDir == 'NOK' && ! $fileVolume->is_directory && $isInit && ! $content) {
+ } elseif ($isFile == 'NOK' && $isDir == 'NOK' && ! $fileVolume->is_directory && $isInit && is_null($content)) {
// Does not exists (no dir or file), not flagged as directory, is init, has no content => create directory
$fileVolume->content = null;
$fileVolume->is_directory = true;
@@ -122,14 +122,22 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource)
$path = $fqdn->getPath();
$fqdn = $fqdn->getScheme().'://'.$fqdn->getHost();
if ($generatedEnv) {
- $generatedEnv->value = $fqdn.$path;
+ if ($path === '/') {
+ $generatedEnv->value = $fqdn;
+ } else {
+ $generatedEnv->value = $fqdn.$path;
+ }
$generatedEnv->save();
}
if ($port) {
$variableName = $variableName."_$port";
$generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first();
if ($generatedEnv) {
- $generatedEnv->value = $fqdn.$path;
+ if ($path === '/') {
+ $generatedEnv->value = $fqdn;
+ } else {
+ $generatedEnv->value = $fqdn.$path;
+ }
$generatedEnv->save();
}
}
@@ -141,14 +149,22 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource)
$url = $url->getHost();
if ($generatedEnv) {
$url = str($fqdn)->after('://');
- $generatedEnv->value = $url.$path;
+ if ($path === '/') {
+ $generatedEnv->value = $url;
+ } else {
+ $generatedEnv->value = $url.$path;
+ }
$generatedEnv->save();
}
if ($port) {
$variableName = $variableName."_$port";
$generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first();
if ($generatedEnv) {
- $generatedEnv->value = $url.$path;
+ if ($path === '/') {
+ $generatedEnv->value = $url;
+ } else {
+ $generatedEnv->value = $url.$path;
+ }
$generatedEnv->save();
}
}
@@ -165,10 +181,18 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource)
$service_fqdn = str($port_env->key)->beforeLast('_')->after('SERVICE_FQDN_');
$env = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', 'SERVICE_FQDN_'.$service_fqdn)->first();
if ($env) {
- $env->value = $host.$path;
+ if ($path === '/') {
+ $env->value = $host;
+ } else {
+ $env->value = $host.$path;
+ }
$env->save();
}
- $port_env->value = $host.$path;
+ if ($path === '/') {
+ $port_env->value = $host;
+ } else {
+ $port_env->value = $host.$path;
+ }
$port_env->save();
}
$port_envs_url = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', 'like', "SERVICE_URL_%_$port")->get();
@@ -176,10 +200,18 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource)
$service_url = str($port_env_url->key)->beforeLast('_')->after('SERVICE_URL_');
$env = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', 'SERVICE_URL_'.$service_url)->first();
if ($env) {
- $env->value = $url.$path;
+ if ($path === '/') {
+ $env->value = $url;
+ } else {
+ $env->value = $url.$path;
+ }
$env->save();
}
- $port_env_url->value = $url.$path;
+ if ($path === '/') {
+ $port_env_url->value = $url;
+ } else {
+ $port_env_url->value = $url.$path;
+ }
$port_env_url->save();
}
} else {
diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php
index 56487c8a6..aae4fafd4 100644
--- a/bootstrap/helpers/shared.php
+++ b/bootstrap/helpers/shared.php
@@ -1,6 +1,7 @@
json();
- // return data_get($versions, 'coolify.v4.version');
} catch (\Throwable $e) {
- //throw $e;
ray($e->getMessage());
return '0.0.0';
@@ -200,7 +197,7 @@ function generate_random_name(?string $cuid = null): string
]
);
if (is_null($cuid)) {
- $cuid = new Cuid2(7);
+ $cuid = new Cuid2;
}
return Str::kebab("{$generator->getName()}-$cuid");
@@ -236,7 +233,7 @@ function formatPrivateKey(string $privateKey)
function generate_application_name(string $git_repository, string $git_branch, ?string $cuid = null): string
{
if (is_null($cuid)) {
- $cuid = new Cuid2(7);
+ $cuid = new Cuid2;
}
return Str::kebab("$git_repository:$git_branch-$cuid");
@@ -505,7 +502,7 @@ function get_service_templates(bool $force = false): Collection
{
if ($force) {
try {
- $response = Http::retry(3, 50)->get(config('constants.services.official'));
+ $response = Http::retry(3, 1000)->get(config('constants.services.official'));
if ($response->failed()) {
return collect([]);
}
@@ -1308,27 +1305,58 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
$serviceLabels = $serviceLabels->merge($defaultLabels);
if (! $isDatabase && $fqdns->count() > 0) {
if ($fqdns) {
- $serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik(
- uuid: $resource->uuid,
- domains: $fqdns,
- is_force_https_enabled: true,
- serviceLabels: $serviceLabels,
- is_gzip_enabled: $savedService->isGzipEnabled(),
- is_stripprefix_enabled: $savedService->isStripprefixEnabled(),
- service_name: $serviceName,
- image: data_get($service, 'image')
- ));
- $serviceLabels = $serviceLabels->merge(fqdnLabelsForCaddy(
- network: $resource->destination->network,
- uuid: $resource->uuid,
- domains: $fqdns,
- is_force_https_enabled: true,
- serviceLabels: $serviceLabels,
- is_gzip_enabled: $savedService->isGzipEnabled(),
- is_stripprefix_enabled: $savedService->isStripprefixEnabled(),
- service_name: $serviceName,
- image: data_get($service, 'image')
- ));
+ $shouldGenerateLabelsExactly = $resource->server->settings->generate_exact_labels;
+ if ($shouldGenerateLabelsExactly) {
+ switch ($resource->server->proxyType()) {
+ case ProxyTypes::TRAEFIK->value:
+ $serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik(
+ uuid: $resource->uuid,
+ domains: $fqdns,
+ is_force_https_enabled: true,
+ serviceLabels: $serviceLabels,
+ is_gzip_enabled: $savedService->isGzipEnabled(),
+ is_stripprefix_enabled: $savedService->isStripprefixEnabled(),
+ service_name: $serviceName,
+ image: data_get($service, 'image')
+ ));
+ break;
+ case ProxyTypes::CADDY->value:
+ $serviceLabels = $serviceLabels->merge(fqdnLabelsForCaddy(
+ network: $resource->destination->network,
+ uuid: $resource->uuid,
+ domains: $fqdns,
+ is_force_https_enabled: true,
+ serviceLabels: $serviceLabels,
+ is_gzip_enabled: $savedService->isGzipEnabled(),
+ is_stripprefix_enabled: $savedService->isStripprefixEnabled(),
+ service_name: $serviceName,
+ image: data_get($service, 'image')
+ ));
+ break;
+ }
+ } else {
+ $serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik(
+ uuid: $resource->uuid,
+ domains: $fqdns,
+ is_force_https_enabled: true,
+ serviceLabels: $serviceLabels,
+ is_gzip_enabled: $savedService->isGzipEnabled(),
+ is_stripprefix_enabled: $savedService->isStripprefixEnabled(),
+ service_name: $serviceName,
+ image: data_get($service, 'image')
+ ));
+ $serviceLabels = $serviceLabels->merge(fqdnLabelsForCaddy(
+ network: $resource->destination->network,
+ uuid: $resource->uuid,
+ domains: $fqdns,
+ is_force_https_enabled: true,
+ serviceLabels: $serviceLabels,
+ is_gzip_enabled: $savedService->isGzipEnabled(),
+ is_stripprefix_enabled: $savedService->isStripprefixEnabled(),
+ service_name: $serviceName,
+ image: data_get($service, 'image')
+ ));
+ }
}
}
if ($resource->server->isLogDrainEnabled() && $savedService->isLogDrainEnabled()) {
@@ -2022,7 +2050,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
$template = $resource->preview_url_template;
$host = $url->getHost();
$schema = $url->getScheme();
- $random = new Cuid2(7);
+ $random = new Cuid2;
$preview_fqdn = str_replace('{{random}}', $random, $template);
$preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn);
$preview_fqdn = str_replace('{{pr_id}}', $pull_request_id, $preview_fqdn);
@@ -2034,26 +2062,64 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
});
}
}
- $serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik(
- uuid: $resource->uuid,
- domains: $fqdns,
- serviceLabels: $serviceLabels,
- generate_unique_uuid: $resource->build_pack === 'dockercompose',
- image: data_get($service, 'image'),
- is_force_https_enabled: $resource->isForceHttpsEnabled(),
- is_gzip_enabled: $resource->isGzipEnabled(),
- is_stripprefix_enabled: $resource->isStripprefixEnabled(),
- ));
- $serviceLabels = $serviceLabels->merge(fqdnLabelsForCaddy(
- network: $resource->destination->network,
- uuid: $resource->uuid,
- domains: $fqdns,
- serviceLabels: $serviceLabels,
- image: data_get($service, 'image'),
- is_force_https_enabled: $resource->isForceHttpsEnabled(),
- is_gzip_enabled: $resource->isGzipEnabled(),
- is_stripprefix_enabled: $resource->isStripprefixEnabled(),
- ));
+ $shouldGenerateLabelsExactly = $server->settings->generate_exact_labels;
+ if ($shouldGenerateLabelsExactly) {
+ switch ($server->proxyType()) {
+ case ProxyTypes::TRAEFIK->value:
+ $serviceLabels = $serviceLabels->merge(
+ fqdnLabelsForTraefik(
+ uuid: $resource->uuid,
+ domains: $fqdns,
+ serviceLabels: $serviceLabels,
+ generate_unique_uuid: $resource->build_pack === 'dockercompose',
+ image: data_get($service, 'image'),
+ is_force_https_enabled: $resource->isForceHttpsEnabled(),
+ is_gzip_enabled: $resource->isGzipEnabled(),
+ is_stripprefix_enabled: $resource->isStripprefixEnabled(),
+ )
+ );
+ break;
+ case ProxyTypes::CADDY->value:
+ $serviceLabels = $serviceLabels->merge(
+ fqdnLabelsForCaddy(
+ network: $resource->destination->network,
+ uuid: $resource->uuid,
+ domains: $fqdns,
+ serviceLabels: $serviceLabels,
+ image: data_get($service, 'image'),
+ is_force_https_enabled: $resource->isForceHttpsEnabled(),
+ is_gzip_enabled: $resource->isGzipEnabled(),
+ is_stripprefix_enabled: $resource->isStripprefixEnabled(),
+ )
+ );
+ break;
+ }
+ } else {
+ $serviceLabels = $serviceLabels->merge(
+ fqdnLabelsForTraefik(
+ uuid: $resource->uuid,
+ domains: $fqdns,
+ serviceLabels: $serviceLabels,
+ generate_unique_uuid: $resource->build_pack === 'dockercompose',
+ image: data_get($service, 'image'),
+ is_force_https_enabled: $resource->isForceHttpsEnabled(),
+ is_gzip_enabled: $resource->isGzipEnabled(),
+ is_stripprefix_enabled: $resource->isStripprefixEnabled(),
+ )
+ );
+ $serviceLabels = $serviceLabels->merge(
+ fqdnLabelsForCaddy(
+ network: $resource->destination->network,
+ uuid: $resource->uuid,
+ domains: $fqdns,
+ serviceLabels: $serviceLabels,
+ image: data_get($service, 'image'),
+ is_force_https_enabled: $resource->isForceHttpsEnabled(),
+ is_gzip_enabled: $resource->isGzipEnabled(),
+ is_stripprefix_enabled: $resource->isStripprefixEnabled(),
+ )
+ );
+ }
}
}
}
diff --git a/config/sentry.php b/config/sentry.php
index b2f6ded80..a27a18d30 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.319',
+ 'release' => '4.0.0-beta.320',
// 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 8b5a5afc6..05acb11ca 100644
--- a/config/version.php
+++ b/config/version.php
@@ -1,3 +1,3 @@
string('auto_update_frequency')->default('0 0 * * *');
+ $table->string('update_check_frequency')->default('0 * * * *');
+ $table->boolean('new_version_available')->default(false);
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::table('instance_settings', function (Blueprint $table) {
+ $table->dropColumn('update_check_frequency');
+ $table->dropColumn('auto_update_frequency');
+ $table->dropColumn('new_version_available');
+ });
+ }
+};
diff --git a/database/migrations/2024_08_07_155324_add_proxy_label_chooser.php b/database/migrations/2024_08_07_155324_add_proxy_label_chooser.php
new file mode 100644
index 000000000..7bc8a0657
--- /dev/null
+++ b/database/migrations/2024_08_07_155324_add_proxy_label_chooser.php
@@ -0,0 +1,28 @@
+boolean('generate_exact_labels')->default(false);
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::table('server_settings', function (Blueprint $table) {
+ $table->dropColumn('generate_exact_labels');
+ });
+ }
+};
diff --git a/database/seeders/ProductionSeeder.php b/database/seeders/ProductionSeeder.php
index e030c0ae6..c88a35f6a 100644
--- a/database/seeders/ProductionSeeder.php
+++ b/database/seeders/ProductionSeeder.php
@@ -100,7 +100,7 @@ class ProductionSeeder extends Seeder
'private_key_id' => 0,
];
$server_details['proxy'] = ServerMetadata::from([
- 'type' => ProxyTypes::TRAEFIK_V2->value,
+ 'type' => ProxyTypes::TRAEFIK->value,
'status' => ProxyStatus::EXITED->value,
]);
$server = Server::create($server_details);
@@ -153,7 +153,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
'private_key_id' => 0,
];
$server_details['proxy'] = ServerMetadata::from([
- 'type' => ProxyTypes::TRAEFIK_V2->value,
+ 'type' => ProxyTypes::TRAEFIK->value,
'status' => ProxyStatus::EXITED->value,
]);
$server = Server::create($server_details);
diff --git a/openapi.yaml b/openapi.yaml
index a63f830b7..00d7bff43 100644
--- a/openapi.yaml
+++ b/openapi.yaml
@@ -5,6 +5,7 @@ info:
servers:
-
url: 'https://app.coolify.io/api/v1'
+ description: 'Coolify Cloud API. Change the host to your own instance if you are self-hosting.'
paths:
/applications:
get:
@@ -29,225 +30,6 @@ paths:
security:
-
bearerAuth: []
- patch:
- tags:
- - Applications
- summary: Update
- description: 'Update application by UUID.'
- operationId: ff28a22d25b1f658c40b54d2073abbca
- requestBody:
- description: 'Application updated.'
- required: true
- content:
- application/json:
- schema:
- properties:
- project_uuid:
- type: string
- description: 'The project UUID.'
- server_uuid:
- type: string
- description: 'The server UUID.'
- environment_name:
- type: string
- description: 'The environment name.'
- github_app_uuid:
- type: string
- description: 'The Github App UUID.'
- git_repository:
- type: string
- description: 'The git repository URL.'
- git_branch:
- type: string
- description: 'The git branch.'
- ports_exposes:
- type: string
- description: 'The ports to expose.'
- destination_uuid:
- type: string
- description: 'The destination UUID.'
- build_pack:
- type: string
- enum: [nixpacks, static, dockerfile, dockercompose]
- description: 'The build pack type.'
- name:
- type: string
- description: 'The application name.'
- description:
- type: string
- description: 'The application description.'
- domains:
- type: string
- description: 'The application domains.'
- git_commit_sha:
- type: string
- description: 'The git commit SHA.'
- docker_registry_image_name:
- type: string
- description: 'The docker registry image name.'
- docker_registry_image_tag:
- type: string
- description: 'The docker registry image tag.'
- is_static:
- type: boolean
- description: 'The flag to indicate if the application is static.'
- install_command:
- type: string
- description: 'The install command.'
- build_command:
- type: string
- description: 'The build command.'
- start_command:
- type: string
- description: 'The start command.'
- ports_mappings:
- type: string
- description: 'The ports mappings.'
- base_directory:
- type: string
- description: 'The base directory for all commands.'
- publish_directory:
- type: string
- description: 'The publish directory.'
- health_check_enabled:
- type: boolean
- description: 'Health check enabled.'
- health_check_path:
- type: string
- description: 'Health check path.'
- health_check_port:
- type: string
- nullable: true
- description: 'Health check port.'
- health_check_host:
- type: string
- nullable: true
- description: 'Health check host.'
- health_check_method:
- type: string
- description: 'Health check method.'
- health_check_return_code:
- type: integer
- description: 'Health check return code.'
- health_check_scheme:
- type: string
- description: 'Health check scheme.'
- health_check_response_text:
- type: string
- nullable: true
- description: 'Health check response text.'
- health_check_interval:
- type: integer
- description: 'Health check interval in seconds.'
- health_check_timeout:
- type: integer
- description: 'Health check timeout in seconds.'
- health_check_retries:
- type: integer
- description: 'Health check retries count.'
- health_check_start_period:
- type: integer
- description: 'Health check start period in seconds.'
- limits_memory:
- type: string
- description: 'Memory limit.'
- limits_memory_swap:
- type: string
- description: 'Memory swap limit.'
- limits_memory_swappiness:
- type: integer
- description: 'Memory swappiness.'
- limits_memory_reservation:
- type: string
- description: 'Memory reservation.'
- limits_cpus:
- type: string
- description: 'CPU limit.'
- limits_cpuset:
- type: string
- nullable: true
- description: 'CPU set.'
- limits_cpu_shares:
- type: integer
- description: 'CPU shares.'
- custom_labels:
- type: string
- description: 'Custom labels.'
- custom_docker_run_options:
- type: string
- description: 'Custom docker run options.'
- post_deployment_command:
- type: string
- description: 'Post deployment command.'
- post_deployment_command_container:
- type: string
- description: 'Post deployment command container.'
- pre_deployment_command:
- type: string
- description: 'Pre deployment command.'
- pre_deployment_command_container:
- type: string
- description: 'Pre deployment command container.'
- manual_webhook_secret_github:
- type: string
- description: 'Manual webhook secret for Github.'
- manual_webhook_secret_gitlab:
- type: string
- description: 'Manual webhook secret for Gitlab.'
- manual_webhook_secret_bitbucket:
- type: string
- description: 'Manual webhook secret for Bitbucket.'
- manual_webhook_secret_gitea:
- type: string
- description: 'Manual webhook secret for Gitea.'
- redirect:
- type: string
- nullable: true
- description: 'How to set redirect with Traefik / Caddy. www<->non-www.'
- enum: [www, non-www, both]
- instant_deploy:
- type: boolean
- description: 'The flag to indicate if the application should be deployed instantly.'
- dockerfile:
- type: string
- description: 'The Dockerfile content.'
- docker_compose_location:
- type: string
- description: 'The Docker Compose location.'
- docker_compose_raw:
- type: string
- description: 'The Docker Compose raw content.'
- docker_compose_custom_start_command:
- type: string
- description: 'The Docker Compose custom start command.'
- docker_compose_custom_build_command:
- type: string
- description: 'The Docker Compose custom build command.'
- docker_compose_domains:
- type: array
- description: 'The Docker Compose domains.'
- watch_paths:
- type: string
- description: 'The watch paths.'
- type: object
- responses:
- '200':
- description: 'Application updated.'
- content:
- application/json:
- schema:
- properties:
- uuid: { type: string }
- type: object
- '401':
- $ref: '#/components/responses/401'
- '400':
- $ref: '#/components/responses/400'
- '404':
- $ref: '#/components/responses/404'
- security:
- -
- bearerAuth: []
/applications/public:
post:
tags:
@@ -1369,6 +1151,225 @@ paths:
security:
-
bearerAuth: []
+ patch:
+ tags:
+ - Applications
+ summary: Update
+ description: 'Update application by UUID.'
+ operationId: 62a3b1775e8cba5d39a236ebb69830b7
+ requestBody:
+ description: 'Application updated.'
+ required: true
+ content:
+ application/json:
+ schema:
+ properties:
+ project_uuid:
+ type: string
+ description: 'The project UUID.'
+ server_uuid:
+ type: string
+ description: 'The server UUID.'
+ environment_name:
+ type: string
+ description: 'The environment name.'
+ github_app_uuid:
+ type: string
+ description: 'The Github App UUID.'
+ git_repository:
+ type: string
+ description: 'The git repository URL.'
+ git_branch:
+ type: string
+ description: 'The git branch.'
+ ports_exposes:
+ type: string
+ description: 'The ports to expose.'
+ destination_uuid:
+ type: string
+ description: 'The destination UUID.'
+ build_pack:
+ type: string
+ enum: [nixpacks, static, dockerfile, dockercompose]
+ description: 'The build pack type.'
+ name:
+ type: string
+ description: 'The application name.'
+ description:
+ type: string
+ description: 'The application description.'
+ domains:
+ type: string
+ description: 'The application domains.'
+ git_commit_sha:
+ type: string
+ description: 'The git commit SHA.'
+ docker_registry_image_name:
+ type: string
+ description: 'The docker registry image name.'
+ docker_registry_image_tag:
+ type: string
+ description: 'The docker registry image tag.'
+ is_static:
+ type: boolean
+ description: 'The flag to indicate if the application is static.'
+ install_command:
+ type: string
+ description: 'The install command.'
+ build_command:
+ type: string
+ description: 'The build command.'
+ start_command:
+ type: string
+ description: 'The start command.'
+ ports_mappings:
+ type: string
+ description: 'The ports mappings.'
+ base_directory:
+ type: string
+ description: 'The base directory for all commands.'
+ publish_directory:
+ type: string
+ description: 'The publish directory.'
+ health_check_enabled:
+ type: boolean
+ description: 'Health check enabled.'
+ health_check_path:
+ type: string
+ description: 'Health check path.'
+ health_check_port:
+ type: string
+ nullable: true
+ description: 'Health check port.'
+ health_check_host:
+ type: string
+ nullable: true
+ description: 'Health check host.'
+ health_check_method:
+ type: string
+ description: 'Health check method.'
+ health_check_return_code:
+ type: integer
+ description: 'Health check return code.'
+ health_check_scheme:
+ type: string
+ description: 'Health check scheme.'
+ health_check_response_text:
+ type: string
+ nullable: true
+ description: 'Health check response text.'
+ health_check_interval:
+ type: integer
+ description: 'Health check interval in seconds.'
+ health_check_timeout:
+ type: integer
+ description: 'Health check timeout in seconds.'
+ health_check_retries:
+ type: integer
+ description: 'Health check retries count.'
+ health_check_start_period:
+ type: integer
+ description: 'Health check start period in seconds.'
+ limits_memory:
+ type: string
+ description: 'Memory limit.'
+ limits_memory_swap:
+ type: string
+ description: 'Memory swap limit.'
+ limits_memory_swappiness:
+ type: integer
+ description: 'Memory swappiness.'
+ limits_memory_reservation:
+ type: string
+ description: 'Memory reservation.'
+ limits_cpus:
+ type: string
+ description: 'CPU limit.'
+ limits_cpuset:
+ type: string
+ nullable: true
+ description: 'CPU set.'
+ limits_cpu_shares:
+ type: integer
+ description: 'CPU shares.'
+ custom_labels:
+ type: string
+ description: 'Custom labels.'
+ custom_docker_run_options:
+ type: string
+ description: 'Custom docker run options.'
+ post_deployment_command:
+ type: string
+ description: 'Post deployment command.'
+ post_deployment_command_container:
+ type: string
+ description: 'Post deployment command container.'
+ pre_deployment_command:
+ type: string
+ description: 'Pre deployment command.'
+ pre_deployment_command_container:
+ type: string
+ description: 'Pre deployment command container.'
+ manual_webhook_secret_github:
+ type: string
+ description: 'Manual webhook secret for Github.'
+ manual_webhook_secret_gitlab:
+ type: string
+ description: 'Manual webhook secret for Gitlab.'
+ manual_webhook_secret_bitbucket:
+ type: string
+ description: 'Manual webhook secret for Bitbucket.'
+ manual_webhook_secret_gitea:
+ type: string
+ description: 'Manual webhook secret for Gitea.'
+ redirect:
+ type: string
+ nullable: true
+ description: 'How to set redirect with Traefik / Caddy. www<->non-www.'
+ enum: [www, non-www, both]
+ instant_deploy:
+ type: boolean
+ description: 'The flag to indicate if the application should be deployed instantly.'
+ dockerfile:
+ type: string
+ description: 'The Dockerfile content.'
+ docker_compose_location:
+ type: string
+ description: 'The Docker Compose location.'
+ docker_compose_raw:
+ type: string
+ description: 'The Docker Compose raw content.'
+ docker_compose_custom_start_command:
+ type: string
+ description: 'The Docker Compose custom start command.'
+ docker_compose_custom_build_command:
+ type: string
+ description: 'The Docker Compose custom build command.'
+ docker_compose_domains:
+ type: array
+ description: 'The Docker Compose domains.'
+ watch_paths:
+ type: string
+ description: 'The watch paths.'
+ type: object
+ responses:
+ '200':
+ description: 'Application updated.'
+ content:
+ application/json:
+ schema:
+ properties:
+ uuid: { type: string }
+ type: object
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ '404':
+ $ref: '#/components/responses/404'
+ security:
+ -
+ bearerAuth: []
'/applications/{uuid}/envs':
get:
tags:
@@ -2833,7 +2834,7 @@ paths:
description: 'Deployment Uuid'
required: true
schema:
- type: integer
+ type: string
responses:
'200':
description: 'Get deployment by UUID.'
@@ -3062,7 +3063,7 @@ paths:
description: 'Project UUID'
required: true
schema:
- type: integer
+ type: string
responses:
'200':
description: 'Project details'
@@ -3166,7 +3167,7 @@ paths:
description: 'Project UUID'
required: true
schema:
- type: integer
+ type: string
-
name: environment_name
in: path
@@ -3325,7 +3326,7 @@ paths:
description: 'Private Key Uuid'
required: true
schema:
- type: integer
+ type: string
responses:
'200':
description: 'Get all private keys.'
@@ -3357,7 +3358,7 @@ paths:
description: 'Private Key Uuid'
required: true
schema:
- type: integer
+ type: string
responses:
'200':
description: 'Private Key deleted.'
@@ -3477,7 +3478,7 @@ paths:
description: "Server's Uuid"
required: true
schema:
- type: integer
+ type: string
responses:
'200':
description: 'Get server by UUID'
@@ -3597,7 +3598,7 @@ paths:
description: "Server's Uuid"
required: true
schema:
- type: integer
+ type: string
responses:
'200':
description: 'Get resources by server'
@@ -3629,7 +3630,7 @@ paths:
description: "Server's Uuid"
required: true
schema:
- type: integer
+ type: string
responses:
'200':
description: 'Get domains by server'
@@ -3661,7 +3662,7 @@ paths:
description: 'Server UUID'
required: true
schema:
- type: integer
+ type: string
responses:
'201':
description: 'Server validation started.'
diff --git a/public/svgs/gitlab.svg b/public/svgs/gitlab.svg
new file mode 100644
index 000000000..1c7cb0719
--- /dev/null
+++ b/public/svgs/gitlab.svg
@@ -0,0 +1 @@
+
diff --git a/public/svgs/minecraft.svg b/public/svgs/minecraft.svg
new file mode 100644
index 000000000..10ae9042c
--- /dev/null
+++ b/public/svgs/minecraft.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/resources/views/auth/register.blade.php b/resources/views/auth/register.blade.php
index 297640111..1d236a6c4 100644
--- a/resources/views/auth/register.blade.php
+++ b/resources/views/auth/register.blade.php
@@ -16,9 +16,16 @@ $email = getOldOrLocal('email', 'test3@example.com');