diff --git a/.github/workflows/coolify-realtime-next.yml b/.github/workflows/coolify-realtime-next.yml
new file mode 100644
index 000000000..33e048627
--- /dev/null
+++ b/.github/workflows/coolify-realtime-next.yml
@@ -0,0 +1,103 @@
+name: Coolify Realtime Development (v4)
+
+on:
+ push:
+ branches: [ "next" ]
+ paths:
+ - .github/workflows/coolify-realtime.yml
+ - docker/coolify-realtime/Dockerfile
+ - docker/coolify-realtime/terminal-server.js
+ - docker/coolify-realtime/package.json
+ - docker/coolify-realtime/soketi-entrypoint.sh
+
+env:
+ REGISTRY: ghcr.io
+ IMAGE_NAME: "coollabsio/coolify-realtime"
+
+jobs:
+ amd64:
+ runs-on: ubuntu-latest
+ 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: Get Version
+ id: version
+ run: |
+ echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.realtime.version' versions.json)"|xargs >> $GITHUB_OUTPUT
+ - name: Build image and push to registry
+ uses: docker/build-push-action@v5
+ with:
+ no-cache: true
+ context: .
+ file: docker/coolify-realtime/Dockerfile
+ platforms: linux/amd64
+ push: true
+ tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next
+ labels: |
+ coolify.managed=true
+ 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: Get Version
+ id: version
+ run: |
+ echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.realtime.version' versions.json)"|xargs >> $GITHUB_OUTPUT
+ - name: Build image and push to registry
+ uses: docker/build-push-action@v5
+ with:
+ no-cache: true
+ context: .
+ file: docker/coolify-realtime/Dockerfile
+ platforms: linux/aarch64
+ push: true
+ tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next-aarch64
+ labels: |
+ coolify.managed=true
+ 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: Get Version
+ id: version
+ run: |
+ echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.realtime.version' versions.json)"|xargs >> $GITHUB_OUTPUT
+ - name: Create & publish manifest
+ run: |
+ docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next
+ - uses: sarisia/actions-status-discord@v1
+ if: always()
+ with:
+ webhook: ${{ secrets.DISCORD_WEBHOOK_PROD_RELEASE_CHANNEL }}
diff --git a/.github/workflows/coolify-realtime.yml b/.github/workflows/coolify-realtime.yml
index 75e3f1681..30910ae0b 100644
--- a/.github/workflows/coolify-realtime.yml
+++ b/.github/workflows/coolify-realtime.yml
@@ -2,7 +2,7 @@ name: Coolify Realtime (v4)
on:
push:
- branches: [ "main", "next" ]
+ branches: [ "main" ]
paths:
- .github/workflows/coolify-realtime.yml
- docker/coolify-realtime/Dockerfile
diff --git a/app/Actions/Database/StartDragonfly.php b/app/Actions/Database/StartDragonfly.php
index 352c6a59f..3ee46a2e1 100644
--- a/app/Actions/Database/StartDragonfly.php
+++ b/app/Actions/Database/StartDragonfly.php
@@ -46,9 +46,6 @@ class StartDragonfly
'networks' => [
$this->database->destination->network,
],
- 'ulimits' => [
- 'memlock' => '-1',
- ],
'labels' => [
'coolify.managed' => 'true',
],
diff --git a/app/Actions/Fortify/CreateNewUser.php b/app/Actions/Fortify/CreateNewUser.php
index f8882d12a..481757162 100644
--- a/app/Actions/Fortify/CreateNewUser.php
+++ b/app/Actions/Fortify/CreateNewUser.php
@@ -2,7 +2,6 @@
namespace App\Actions\Fortify;
-use App\Models\InstanceSettings;
use App\Models\User;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
@@ -20,7 +19,7 @@ class CreateNewUser implements CreatesNewUsers
*/
public function create(array $input): User
{
- $settings = InstanceSettings::get();
+ $settings = instanceSettings();
if (! $settings->is_registration_enabled) {
abort(403);
}
@@ -48,7 +47,7 @@ class CreateNewUser implements CreatesNewUsers
$team = $user->teams()->first();
// Disable registration after first user is created
- $settings = InstanceSettings::get();
+ $settings = instanceSettings();
$settings->is_registration_enabled = false;
$settings->save();
} else {
diff --git a/app/Actions/License/CheckResaleLicense.php b/app/Actions/License/CheckResaleLicense.php
index dcb4058c0..55af1a8c0 100644
--- a/app/Actions/License/CheckResaleLicense.php
+++ b/app/Actions/License/CheckResaleLicense.php
@@ -2,7 +2,6 @@
namespace App\Actions\License;
-use App\Models\InstanceSettings;
use Illuminate\Support\Facades\Http;
use Lorisleiva\Actions\Concerns\AsAction;
@@ -13,7 +12,7 @@ class CheckResaleLicense
public function handle()
{
try {
- $settings = InstanceSettings::get();
+ $settings = instanceSettings();
if (isDev()) {
$settings->update([
'is_resale_license_active' => true,
diff --git a/app/Actions/Proxy/CheckConfiguration.php b/app/Actions/Proxy/CheckConfiguration.php
index f4fe650c5..bdeafd061 100644
--- a/app/Actions/Proxy/CheckConfiguration.php
+++ b/app/Actions/Proxy/CheckConfiguration.php
@@ -22,7 +22,7 @@ class CheckConfiguration
];
$proxy_configuration = instant_remote_process($payload, $server, false);
if ($reset || ! $proxy_configuration || is_null($proxy_configuration)) {
- $proxy_configuration = str(generate_default_proxy_configuration($server))->trim()->value;
+ $proxy_configuration = str(generate_default_proxy_configuration($server))->trim()->value();
}
if (! $proxy_configuration || is_null($proxy_configuration)) {
throw new \Exception('Could not generate proxy configuration');
diff --git a/app/Actions/Proxy/CheckProxy.php b/app/Actions/Proxy/CheckProxy.php
index cf0f6015c..03a0beddf 100644
--- a/app/Actions/Proxy/CheckProxy.php
+++ b/app/Actions/Proxy/CheckProxy.php
@@ -2,14 +2,17 @@
namespace App\Actions\Proxy;
+use App\Enums\ProxyTypes;
use App\Models\Server;
use Lorisleiva\Actions\Concerns\AsAction;
+use Symfony\Component\Yaml\Yaml;
class CheckProxy
{
use AsAction;
- public function handle(Server $server, $fromUI = false)
+ // It should return if the proxy should be started (true) or not (false)
+ public function handle(Server $server, $fromUI = false): bool
{
if (! $server->isFunctional()) {
return false;
@@ -62,22 +65,42 @@ class CheckProxy
$ip = 'host.docker.internal';
}
- $connection80 = @fsockopen($ip, '80');
- $connection443 = @fsockopen($ip, '443');
- $port80 = is_resource($connection80) && fclose($connection80);
- $port443 = is_resource($connection443) && fclose($connection443);
- if ($port80) {
- if ($fromUI) {
- throw new \Exception("Port 80 is in use.
You must stop the process using this port.
Docs: https://coolify.io/docs
Discord: https://coollabs.io/discord");
+ $portsToCheck = ['80', '443'];
+
+ try {
+ if ($server->proxyType() !== ProxyTypes::NONE->value) {
+ $proxyCompose = CheckConfiguration::run($server);
+ if (isset($proxyCompose)) {
+ $yaml = Yaml::parse($proxyCompose);
+ $portsToCheck = [];
+ if ($server->proxyType() === ProxyTypes::TRAEFIK->value) {
+ $ports = data_get($yaml, 'services.traefik.ports');
+ } elseif ($server->proxyType() === ProxyTypes::CADDY->value) {
+ $ports = data_get($yaml, 'services.caddy.ports');
+ }
+ if (isset($ports)) {
+ foreach ($ports as $port) {
+ $portsToCheck[] = str($port)->before(':')->value();
+ }
+ }
+ }
} else {
- return false;
+ $portsToCheck = [];
}
+ } catch (\Exception $e) {
+ ray($e->getMessage());
}
- if ($port443) {
- if ($fromUI) {
- throw new \Exception("Port 443 is in use.
You must stop the process using this port.
Docs: https://coolify.io/docs
Discord: https://coollabs.io/discord");
- } else {
- return false;
+ if (count($portsToCheck) === 0) {
+ return false;
+ }
+ foreach ($portsToCheck as $port) {
+ $connection = @fsockopen($ip, $port);
+ if (is_resource($connection) && fclose($connection)) {
+ if ($fromUI) {
+ throw new \Exception("Port $port is in use.
You must stop the process using this port.
Docs: https://coolify.io/docs
Discord: https://coollabs.io/discord");
+ } else {
+ return false;
+ }
}
}
diff --git a/app/Actions/Proxy/StartProxy.php b/app/Actions/Proxy/StartProxy.php
index 4ef9618d0..f20c10123 100644
--- a/app/Actions/Proxy/StartProxy.php
+++ b/app/Actions/Proxy/StartProxy.php
@@ -26,7 +26,7 @@ class StartProxy
}
SaveConfiguration::run($server, $configuration);
$docker_compose_yml_base64 = base64_encode($configuration);
- $server->proxy->last_applied_settings = str($docker_compose_yml_base64)->pipe('md5')->value;
+ $server->proxy->last_applied_settings = str($docker_compose_yml_base64)->pipe('md5')->value();
$server->save();
if ($server->isSwarm()) {
$commands = $commands->merge([
diff --git a/app/Actions/Server/CleanupDocker.php b/app/Actions/Server/CleanupDocker.php
index 19bb03383..dc6ac12bf 100644
--- a/app/Actions/Server/CleanupDocker.php
+++ b/app/Actions/Server/CleanupDocker.php
@@ -2,7 +2,6 @@
namespace App\Actions\Server;
-use App\Models\InstanceSettings;
use App\Models\Server;
use Lorisleiva\Actions\Concerns\AsAction;
@@ -12,7 +11,7 @@ class CleanupDocker
public function handle(Server $server)
{
- $settings = InstanceSettings::get();
+ $settings = instanceSettings();
$helperImageVersion = data_get($settings, 'helper_version');
$helperImage = config('coolify.helper_image');
$helperImageWithVersion = "$helperImage:$helperImageVersion";
diff --git a/app/Actions/Server/UpdateCoolify.php b/app/Actions/Server/UpdateCoolify.php
index 901f2cf77..30664df26 100644
--- a/app/Actions/Server/UpdateCoolify.php
+++ b/app/Actions/Server/UpdateCoolify.php
@@ -3,7 +3,6 @@
namespace App\Actions\Server;
use App\Jobs\PullHelperImageJob;
-use App\Models\InstanceSettings;
use App\Models\Server;
use Lorisleiva\Actions\Concerns\AsAction;
@@ -20,7 +19,7 @@ class UpdateCoolify
public function handle($manual_update = false)
{
try {
- $settings = InstanceSettings::get();
+ $settings = instanceSettings();
$this->server = Server::find(0);
if (! $this->server) {
return;
diff --git a/app/Console/Commands/CheckApplicationDeploymentQueue.php b/app/Console/Commands/CheckApplicationDeploymentQueue.php
new file mode 100644
index 000000000..e89d26f2c
--- /dev/null
+++ b/app/Console/Commands/CheckApplicationDeploymentQueue.php
@@ -0,0 +1,50 @@
+option('seconds');
+ $deployments = ApplicationDeploymentQueue::whereIn('status', [
+ ApplicationDeploymentStatus::IN_PROGRESS,
+ ApplicationDeploymentStatus::QUEUED,
+ ])->where('created_at', '<=', now()->subSeconds($seconds))->get();
+ if ($deployments->isEmpty()) {
+ $this->info('No deployments found in the last '.$seconds.' seconds.');
+
+ return;
+ }
+
+ $this->info('Found '.$deployments->count().' deployments created in the last '.$seconds.' seconds.');
+
+ foreach ($deployments as $deployment) {
+ if ($this->option('force')) {
+ $this->info('Deployment '.$deployment->id.' created at '.$deployment->created_at.' is older than '.$seconds.' seconds. Setting status to failed.');
+ $this->cancelDeployment($deployment);
+ } else {
+ $this->info('Deployment '.$deployment->id.' created at '.$deployment->created_at.' is older than '.$seconds.' seconds. Setting status to failed.');
+ if ($this->confirm('Do you want to cancel this deployment?', true)) {
+ $this->cancelDeployment($deployment);
+ }
+ }
+ }
+ }
+
+ private function cancelDeployment(ApplicationDeploymentQueue $deployment)
+ {
+ $deployment->update(['status' => ApplicationDeploymentStatus::FAILED]);
+ if ($deployment->server?->isFunctional()) {
+ remote_process(['docker rm -f '.$deployment->deployment_uuid], $deployment->server, false);
+ }
+ }
+}
diff --git a/app/Console/Commands/CleanupApplicationDeploymentQueue.php b/app/Console/Commands/CleanupApplicationDeploymentQueue.php
index f068e3eb2..3aae28ae6 100644
--- a/app/Console/Commands/CleanupApplicationDeploymentQueue.php
+++ b/app/Console/Commands/CleanupApplicationDeploymentQueue.php
@@ -7,9 +7,9 @@ use Illuminate\Console\Command;
class CleanupApplicationDeploymentQueue extends Command
{
- protected $signature = 'cleanup:application-deployment-queue {--team-id=}';
+ protected $signature = 'cleanup:deployment-queue {--team-id=}';
- protected $description = 'CleanupApplicationDeploymentQueue';
+ protected $description = 'Cleanup application deployment queue.';
public function handle()
{
diff --git a/app/Console/Commands/CleanupStuckedResources.php b/app/Console/Commands/CleanupStuckedResources.php
index dfd09d4b7..66c25ec27 100644
--- a/app/Console/Commands/CleanupStuckedResources.php
+++ b/app/Console/Commands/CleanupStuckedResources.php
@@ -4,6 +4,7 @@ namespace App\Console\Commands;
use App\Jobs\CleanupHelperContainersJob;
use App\Models\Application;
+use App\Models\ApplicationDeploymentQueue;
use App\Models\ApplicationPreview;
use App\Models\ScheduledDatabaseBackup;
use App\Models\ScheduledTask;
@@ -47,6 +48,17 @@ class CleanupStuckedResources extends Command
} catch (\Throwable $e) {
echo "Error in cleaning stucked resources: {$e->getMessage()}\n";
}
+ try {
+ $applicationsDeploymentQueue = ApplicationDeploymentQueue::get();
+ foreach ($applicationsDeploymentQueue as $applicationDeploymentQueue) {
+ if (is_null($applicationDeploymentQueue->application)) {
+ echo "Deleting stuck application deployment queue: {$applicationDeploymentQueue->id}\n";
+ $applicationDeploymentQueue->delete();
+ }
+ }
+ } catch (\Throwable $e) {
+ echo "Error in cleaning stuck application deployment queue: {$e->getMessage()}\n";
+ }
try {
$applications = Application::withTrashed()->whereNotNull('deleted_at')->get();
foreach ($applications as $application) {
diff --git a/app/Console/Commands/Dev.php b/app/Console/Commands/Dev.php
index 964b8e46e..20a2667c3 100644
--- a/app/Console/Commands/Dev.php
+++ b/app/Console/Commands/Dev.php
@@ -48,6 +48,13 @@ class Dev extends Command
echo "Generating APP_KEY.\n";
Artisan::call('key:generate');
}
+
+ // Generate STORAGE link if not exists
+ if (! file_exists(public_path('storage'))) {
+ echo "Generating STORAGE link.\n";
+ Artisan::call('storage:link');
+ }
+
// Seed database if it's empty
$settings = InstanceSettings::find(0);
if (! $settings) {
diff --git a/app/Console/Commands/Init.php b/app/Console/Commands/Init.php
index 2f5d36140..ad7bff86d 100644
--- a/app/Console/Commands/Init.php
+++ b/app/Console/Commands/Init.php
@@ -7,7 +7,6 @@ use App\Enums\ActivityTypes;
use App\Enums\ApplicationDeploymentStatus;
use App\Models\ApplicationDeploymentQueue;
use App\Models\Environment;
-use App\Models\InstanceSettings;
use App\Models\ScheduledDatabaseBackup;
use App\Models\Server;
use App\Models\StandalonePostgresql;
@@ -69,7 +68,7 @@ class Init extends Command
} catch (\Throwable $e) {
echo "Could not setup dynamic configuration: {$e->getMessage()}\n";
}
- $settings = InstanceSettings::get();
+ $settings = instanceSettings();
if (! is_null(env('AUTOUPDATE', null))) {
if (env('AUTOUPDATE') == true) {
$settings->update(['is_auto_update_enabled' => true]);
@@ -196,7 +195,7 @@ class Init extends Command
{
$id = config('app.id');
$version = config('version');
- $settings = InstanceSettings::get();
+ $settings = instanceSettings();
$do_not_track = data_get($settings, 'do_not_track');
if ($do_not_track == true) {
echo "Skipping alive as do_not_track is enabled\n";
diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php
index 03d479400..1430fcdd1 100644
--- a/app/Console/Kernel.php
+++ b/app/Console/Kernel.php
@@ -12,8 +12,8 @@ use App\Jobs\PullSentinelImageJob;
use App\Jobs\PullTemplatesFromCDN;
use App\Jobs\ScheduledTaskJob;
use App\Jobs\ServerCheckJob;
+use App\Jobs\ServerStorageCheckJob;
use App\Jobs\UpdateCoolifyJob;
-use App\Models\InstanceSettings;
use App\Models\ScheduledDatabaseBackup;
use App\Models\ScheduledTask;
use App\Models\Server;
@@ -28,7 +28,7 @@ class Kernel extends ConsoleKernel
protected function schedule(Schedule $schedule): void
{
$this->all_servers = Server::all();
- $settings = InstanceSettings::get();
+ $settings = instanceSettings();
$schedule->job(new CleanupStaleMultiplexedConnections)->hourly();
@@ -66,7 +66,7 @@ class Kernel extends ConsoleKernel
private function pull_images($schedule)
{
- $settings = InstanceSettings::get();
+ $settings = instanceSettings();
$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()) {
@@ -88,7 +88,7 @@ class Kernel extends ConsoleKernel
private function schedule_updates($schedule)
{
- $settings = InstanceSettings::get();
+ $settings = instanceSettings();
$updateCheckFrequency = $settings->update_check_frequency;
$schedule->job(new CheckForUpdatesJob)
@@ -116,6 +116,7 @@ class Kernel extends ConsoleKernel
}
foreach ($servers as $server) {
$schedule->job(new ServerCheckJob($server))->everyMinute()->onOneServer();
+ // $schedule->job(new ServerStorageCheckJob($server))->everyMinute()->onOneServer();
$serverTimezone = $server->settings->server_timezone;
if ($server->settings->force_docker_cleanup) {
$schedule->job(new DockerCleanupJob($server))->cron($server->settings->docker_cleanup_frequency)->timezone($serverTimezone)->onOneServer();
diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php
index 6b69350fe..63fbfc862 100644
--- a/app/Exceptions/Handler.php
+++ b/app/Exceptions/Handler.php
@@ -65,7 +65,7 @@ class Handler extends ExceptionHandler
if ($e instanceof RuntimeException) {
return;
}
- $this->settings = \App\Models\InstanceSettings::get();
+ $this->settings = instanceSettings();
if ($this->settings->do_not_track) {
return;
}
diff --git a/app/Helpers/SshMultiplexingHelper.php b/app/Helpers/SshMultiplexingHelper.php
index b0a832605..1a2146799 100644
--- a/app/Helpers/SshMultiplexingHelper.php
+++ b/app/Helpers/SshMultiplexingHelper.php
@@ -94,7 +94,9 @@ class SshMultiplexingHelper
$muxPersistTime = config('constants.ssh.mux_persist_time');
$scp_command = "timeout $timeout scp ";
-
+ if ($server->isIpv6()) {
+ $scp_command .= '-6 ';
+ }
if (self::isMultiplexingEnabled()) {
$scp_command .= "-o ControlMaster=auto -o ControlPath=$muxSocket -o ControlPersist={$muxPersistTime} ";
self::ensureMultiplexedConnection($server);
@@ -136,8 +138,8 @@ class SshMultiplexingHelper
$ssh_command .= self::getCommonSshOptions($server, $sshKeyLocation, config('constants.ssh.connection_timeout'), config('constants.ssh.server_interval'));
- $command = "PATH=\$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/host/usr/local/sbin:/host/usr/local/bin:/host/usr/sbin:/host/usr/bin:/host/sbin:/host/bin && $command";
$delimiter = Hash::make($command);
+ $delimiter = base64_encode($delimiter);
$command = str_replace($delimiter, '', $command);
$ssh_command .= "{$server->user}@{$server->ip} 'bash -se' << \\$delimiter".PHP_EOL
diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php
index 48e126f27..eef179048 100644
--- a/app/Http/Controllers/Api/ApplicationsController.php
+++ b/app/Http/Controllers/Api/ApplicationsController.php
@@ -177,6 +177,7 @@ class ApplicationsController extends Controller
'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.'],
+ 'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
],
)),
]),
@@ -279,6 +280,7 @@ class ApplicationsController extends Controller
'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.'],
+ 'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
],
)),
]),
@@ -381,6 +383,7 @@ class ApplicationsController extends Controller
'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.'],
+ 'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
],
)),
]),
@@ -468,6 +471,7 @@ class ApplicationsController extends Controller
'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.'],
+ 'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
],
)),
]),
@@ -552,6 +556,7 @@ class ApplicationsController extends Controller
'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.'],
+ 'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
],
)),
]),
@@ -602,6 +607,7 @@ class ApplicationsController extends Controller
'name' => ['type' => 'string', 'description' => 'The application name.'],
'description' => ['type' => 'string', 'description' => 'The application description.'],
'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application should be deployed instantly.'],
+ 'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
],
)),
]),
@@ -627,7 +633,7 @@ class ApplicationsController extends Controller
private function create_application(Request $request, $type)
{
- $allowedFields = ['project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'private_key_uuid', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'redirect', 'github_app_uuid', 'instant_deploy', 'dockerfile', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'watch_paths'];
+ $allowedFields = ['project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'private_key_uuid', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'redirect', 'github_app_uuid', 'instant_deploy', 'dockerfile', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'watch_paths', 'use_build_server'];
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalidTokenResponse();
@@ -665,6 +671,7 @@ class ApplicationsController extends Controller
$fqdn = $request->domains;
$instantDeploy = $request->instant_deploy;
$githubAppUuid = $request->github_app_uuid;
+ $useBuildServer = $request->use_build_server;
$project = Project::whereTeamId($teamId)->whereUuid($request->project_uuid)->first();
if (! $project) {
@@ -737,6 +744,10 @@ class ApplicationsController extends Controller
$application->destination_id = $destination->id;
$application->destination_type = $destination->getMorphClass();
$application->environment_id = $environment->id;
+ if (isset($useBuildServer)) {
+ $application->settings->is_build_server_enabled = $useBuildServer;
+ $application->settings->save();
+ }
$application->save();
$application->refresh();
if (! $application->settings->is_container_label_readonly_enabled) {
@@ -833,6 +844,10 @@ class ApplicationsController extends Controller
$application->environment_id = $environment->id;
$application->source_type = $githubApp->getMorphClass();
$application->source_id = $githubApp->id;
+ if (isset($useBuildServer)) {
+ $application->settings->is_build_server_enabled = $useBuildServer;
+ $application->settings->save();
+ }
$application->save();
$application->refresh();
if (! $application->settings->is_container_label_readonly_enabled) {
@@ -925,6 +940,10 @@ class ApplicationsController extends Controller
$application->destination_id = $destination->id;
$application->destination_type = $destination->getMorphClass();
$application->environment_id = $environment->id;
+ if (isset($useBuildServer)) {
+ $application->settings->is_build_server_enabled = $useBuildServer;
+ $application->settings->save();
+ }
$application->save();
$application->refresh();
if (! $application->settings->is_container_label_readonly_enabled) {
@@ -1004,6 +1023,10 @@ class ApplicationsController extends Controller
$application->destination_id = $destination->id;
$application->destination_type = $destination->getMorphClass();
$application->environment_id = $environment->id;
+ if (isset($useBuildServer)) {
+ $application->settings->is_build_server_enabled = $useBuildServer;
+ $application->settings->save();
+ }
$application->git_repository = 'coollabsio/coolify';
$application->git_branch = 'main';
@@ -1062,6 +1085,10 @@ class ApplicationsController extends Controller
$application->destination_id = $destination->id;
$application->destination_type = $destination->getMorphClass();
$application->environment_id = $environment->id;
+ if (isset($useBuildServer)) {
+ $application->settings->is_build_server_enabled = $useBuildServer;
+ $application->settings->save();
+ }
$application->git_repository = 'coollabsio/coolify';
$application->git_branch = 'main';
@@ -1259,16 +1286,10 @@ class ApplicationsController extends Controller
format: 'uuid',
)
),
- new OA\Parameter(
- name: 'cleanup',
- in: 'query',
- description: 'Delete configurations and volumes.',
- required: false,
- schema: new OA\Schema(
- type: 'boolean',
- default: true,
- )
- ),
+ new OA\Parameter(name: 'delete_configurations', in: 'query', required: false, description: 'Delete configurations.', schema: new OA\Schema(type: 'boolean', default: true)),
+ new OA\Parameter(name: 'delete_volumes', in: 'query', required: false, description: 'Delete volumes.', schema: new OA\Schema(type: 'boolean', default: true)),
+ new OA\Parameter(name: 'docker_cleanup', in: 'query', required: false, description: 'Run docker cleanup.', schema: new OA\Schema(type: 'boolean', default: true)),
+ new OA\Parameter(name: 'delete_connected_networks', in: 'query', required: false, description: 'Delete connected networks.', schema: new OA\Schema(type: 'boolean', default: true)),
],
responses: [
new OA\Response(
@@ -1316,10 +1337,14 @@ class ApplicationsController extends Controller
'message' => 'Application not found',
], 404);
}
+
DeleteResourceJob::dispatch(
resource: $application,
- deleteConfigurations: $cleanup,
- deleteVolumes: $cleanup);
+ deleteConfigurations: $request->query->get('delete_configurations', true),
+ deleteVolumes: $request->query->get('delete_volumes', true),
+ dockerCleanup: $request->query->get('docker_cleanup', true),
+ deleteConnectedNetworks: $request->query->get('delete_connected_networks', true)
+ );
return response()->json([
'message' => 'Application deletion request queued.',
@@ -1404,6 +1429,7 @@ class ApplicationsController extends Controller
'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.'],
+ 'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
],
)),
]),
@@ -1460,7 +1486,7 @@ class ApplicationsController extends Controller
], 404);
}
$server = $application->destination->server;
- $allowedFields = ['name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'static_image', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'watch_paths', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'redirect', 'instant_deploy'];
+ $allowedFields = ['name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'static_image', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'watch_paths', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'redirect', 'instant_deploy', 'use_build_server'];
$validator = customApiValidator($request->all(), [
sharedDataApplications(),
@@ -1538,6 +1564,13 @@ class ApplicationsController extends Controller
}
$instantDeploy = $request->instant_deploy;
+ $use_build_server = $request->use_build_server;
+
+ if (isset($use_build_server)) {
+ $application->settings->is_build_server_enabled = $use_build_server;
+ $application->settings->save();
+ }
+
removeUnnecessaryFieldsFromRequest($request);
$data = $request->all();
diff --git a/app/Http/Controllers/Api/DatabasesController.php b/app/Http/Controllers/Api/DatabasesController.php
index a205704cc..65873f818 100644
--- a/app/Http/Controllers/Api/DatabasesController.php
+++ b/app/Http/Controllers/Api/DatabasesController.php
@@ -1541,16 +1541,10 @@ class DatabasesController extends Controller
format: 'uuid',
)
),
- new OA\Parameter(
- name: 'cleanup',
- in: 'query',
- description: 'Delete configurations and volumes.',
- required: false,
- schema: new OA\Schema(
- type: 'boolean',
- default: true,
- )
- ),
+ new OA\Parameter(name: 'delete_configurations', in: 'query', required: false, description: 'Delete configurations.', schema: new OA\Schema(type: 'boolean', default: true)),
+ new OA\Parameter(name: 'delete_volumes', in: 'query', required: false, description: 'Delete volumes.', schema: new OA\Schema(type: 'boolean', default: true)),
+ new OA\Parameter(name: 'docker_cleanup', in: 'query', required: false, description: 'Run docker cleanup.', schema: new OA\Schema(type: 'boolean', default: true)),
+ new OA\Parameter(name: 'delete_connected_networks', in: 'query', required: false, description: 'Delete connected networks.', schema: new OA\Schema(type: 'boolean', default: true)),
],
responses: [
new OA\Response(
@@ -1595,10 +1589,14 @@ class DatabasesController extends Controller
if (! $database) {
return response()->json(['message' => 'Database not found.'], 404);
}
+
DeleteResourceJob::dispatch(
resource: $database,
- deleteConfigurations: $cleanup,
- deleteVolumes: $cleanup);
+ deleteConfigurations: $request->query->get('delete_configurations', true),
+ deleteVolumes: $request->query->get('delete_volumes', true),
+ dockerCleanup: $request->query->get('docker_cleanup', true),
+ deleteConnectedNetworks: $request->query->get('delete_connected_networks', true)
+ );
return response()->json([
'message' => 'Database deletion request queued.',
diff --git a/app/Http/Controllers/Api/OtherController.php b/app/Http/Controllers/Api/OtherController.php
index c085b88a5..2414b7a42 100644
--- a/app/Http/Controllers/Api/OtherController.php
+++ b/app/Http/Controllers/Api/OtherController.php
@@ -86,7 +86,7 @@ class OtherController extends Controller
if ($teamId !== '0') {
return response()->json(['message' => 'You are not allowed to enable the API.'], 403);
}
- $settings = \App\Models\InstanceSettings::get();
+ $settings = instanceSettings();
$settings->update(['is_api_enabled' => true]);
return response()->json(['message' => 'API enabled.'], 200);
@@ -138,7 +138,7 @@ class OtherController extends Controller
if ($teamId !== '0') {
return response()->json(['message' => 'You are not allowed to disable the API.'], 403);
}
- $settings = \App\Models\InstanceSettings::get();
+ $settings = instanceSettings();
$settings->update(['is_api_enabled' => false]);
return response()->json(['message' => 'API disabled.'], 200);
diff --git a/app/Http/Controllers/Api/ServersController.php b/app/Http/Controllers/Api/ServersController.php
index 5f0d6bb12..a49515579 100644
--- a/app/Http/Controllers/Api/ServersController.php
+++ b/app/Http/Controllers/Api/ServersController.php
@@ -308,7 +308,7 @@ class ServersController extends Controller
$projects = Project::where('team_id', $teamId)->get();
$domains = collect();
$applications = $projects->pluck('applications')->flatten();
- $settings = \App\Models\InstanceSettings::get();
+ $settings = instanceSettings();
if ($applications->count() > 0) {
foreach ($applications as $application) {
$ip = $application->destination->server->ip;
diff --git a/app/Http/Controllers/Api/ServicesController.php b/app/Http/Controllers/Api/ServicesController.php
index 0a6154410..89418517b 100644
--- a/app/Http/Controllers/Api/ServicesController.php
+++ b/app/Http/Controllers/Api/ServicesController.php
@@ -432,6 +432,10 @@ class ServicesController extends Controller
tags: ['Services'],
parameters: [
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Service UUID', schema: new OA\Schema(type: 'string')),
+ new OA\Parameter(name: 'delete_configurations', in: 'query', required: false, description: 'Delete configurations.', schema: new OA\Schema(type: 'boolean', default: true)),
+ new OA\Parameter(name: 'delete_volumes', in: 'query', required: false, description: 'Delete volumes.', schema: new OA\Schema(type: 'boolean', default: true)),
+ new OA\Parameter(name: 'docker_cleanup', in: 'query', required: false, description: 'Run docker cleanup.', schema: new OA\Schema(type: 'boolean', default: true)),
+ new OA\Parameter(name: 'delete_connected_networks', in: 'query', required: false, description: 'Delete connected networks.', schema: new OA\Schema(type: 'boolean', default: true)),
],
responses: [
new OA\Response(
@@ -476,7 +480,14 @@ class ServicesController extends Controller
if (! $service) {
return response()->json(['message' => 'Service not found.'], 404);
}
- DeleteResourceJob::dispatch($service);
+
+ DeleteResourceJob::dispatch(
+ resource: $service,
+ deleteConfigurations: $request->query->get('delete_configurations', true),
+ deleteVolumes: $request->query->get('delete_volumes', true),
+ dockerCleanup: $request->query->get('docker_cleanup', true),
+ deleteConnectedNetworks: $request->query->get('delete_connected_networks', true)
+ );
return response()->json([
'message' => 'Service deletion request queued.',
@@ -516,7 +527,8 @@ class ServicesController extends Controller
items: new OA\Items(ref: '#/components/schemas/EnvironmentVariable')
)
),
- ]),
+ ]
+ ),
new OA\Response(
response: 401,
ref: '#/components/responses/401',
@@ -619,7 +631,8 @@ class ServicesController extends Controller
]
)
),
- ]),
+ ]
+ ),
new OA\Response(
response: 401,
ref: '#/components/responses/401',
@@ -738,7 +751,8 @@ class ServicesController extends Controller
]
)
),
- ]),
+ ]
+ ),
new OA\Response(
response: 401,
ref: '#/components/responses/401',
@@ -853,7 +867,8 @@ class ServicesController extends Controller
]
)
),
- ]),
+ ]
+ ),
new OA\Response(
response: 401,
ref: '#/components/responses/401',
@@ -953,7 +968,8 @@ class ServicesController extends Controller
]
)
),
- ]),
+ ]
+ ),
new OA\Response(
response: 401,
ref: '#/components/responses/401',
@@ -1025,9 +1041,11 @@ class ServicesController extends Controller
type: 'object',
properties: [
'message' => ['type' => 'string', 'example' => 'Service starting request queued.'],
- ])
+ ]
+ )
),
- ]),
+ ]
+ ),
new OA\Response(
response: 401,
ref: '#/components/responses/401',
@@ -1101,9 +1119,11 @@ class ServicesController extends Controller
type: 'object',
properties: [
'message' => ['type' => 'string', 'example' => 'Service stopping request queued.'],
- ])
+ ]
+ )
),
- ]),
+ ]
+ ),
new OA\Response(
response: 401,
ref: '#/components/responses/401',
@@ -1177,9 +1197,11 @@ class ServicesController extends Controller
type: 'object',
properties: [
'message' => ['type' => 'string', 'example' => 'Service restaring request queued.'],
- ])
+ ]
+ )
),
- ]),
+ ]
+ ),
new OA\Response(
response: 401,
ref: '#/components/responses/401',
diff --git a/app/Http/Controllers/OauthController.php b/app/Http/Controllers/OauthController.php
index 9569e8cfa..630d01045 100644
--- a/app/Http/Controllers/OauthController.php
+++ b/app/Http/Controllers/OauthController.php
@@ -2,7 +2,6 @@
namespace App\Http\Controllers;
-use App\Models\InstanceSettings;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Symfony\Component\HttpKernel\Exception\HttpException;
@@ -22,7 +21,7 @@ class OauthController extends Controller
$oauthUser = get_socialite_provider($provider)->user();
$user = User::whereEmail($oauthUser->email)->first();
if (! $user) {
- $settings = InstanceSettings::get();
+ $settings = instanceSettings();
if (! $settings->is_registration_enabled) {
abort(403, 'Registration is disabled');
}
diff --git a/app/Http/Middleware/ApiAllowed.php b/app/Http/Middleware/ApiAllowed.php
index 648720ba4..471e6d602 100644
--- a/app/Http/Middleware/ApiAllowed.php
+++ b/app/Http/Middleware/ApiAllowed.php
@@ -14,7 +14,7 @@ class ApiAllowed
if (isCloud()) {
return $next($request);
}
- $settings = \App\Models\InstanceSettings::get();
+ $settings = instanceSettings();
if ($settings->is_api_enabled === false) {
return response()->json(['success' => true, 'message' => 'API is disabled.'], 403);
}
diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php
index 24565b389..9ae383a9f 100644
--- a/app/Jobs/ApplicationDeploymentJob.php
+++ b/app/Jobs/ApplicationDeploymentJob.php
@@ -12,7 +12,6 @@ use App\Models\ApplicationPreview;
use App\Models\EnvironmentVariable;
use App\Models\GithubApp;
use App\Models\GitlabApp;
-use App\Models\InstanceSettings;
use App\Models\Server;
use App\Models\StandaloneDocker;
use App\Models\SwarmDocker;
@@ -962,7 +961,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
}
}
if ($this->application->environment_variables->where('key', 'COOLIFY_FQDN')->isEmpty()) {
- if ($this->application->compose_parsing_version === '3') {
+ if ((int) $this->application->compose_parsing_version >= 3) {
$envs->push("COOLIFY_URL={$this->application->fqdn}");
} else {
$envs->push("COOLIFY_FQDN={$this->application->fqdn}");
@@ -970,7 +969,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
}
if ($this->application->environment_variables->where('key', 'COOLIFY_URL')->isEmpty()) {
$url = str($this->application->fqdn)->replace('http://', '')->replace('https://', '');
- if ($this->application->compose_parsing_version === '3') {
+ if ((int) $this->application->compose_parsing_version >= 3) {
$envs->push("COOLIFY_FQDN={$url}");
} else {
$envs->push("COOLIFY_URL={$url}");
@@ -1334,7 +1333,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
private function prepare_builder_image()
{
- $settings = InstanceSettings::get();
+ $settings = instanceSettings();
$helperImage = config('coolify.helper_image');
$helperImage = "{$helperImage}:{$settings->helper_version}";
// Get user home directory
diff --git a/app/Jobs/CheckForUpdatesJob.php b/app/Jobs/CheckForUpdatesJob.php
index 747a9a98a..f2348118a 100644
--- a/app/Jobs/CheckForUpdatesJob.php
+++ b/app/Jobs/CheckForUpdatesJob.php
@@ -2,7 +2,6 @@
namespace App\Jobs;
-use App\Models\InstanceSettings;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
@@ -22,7 +21,7 @@ class CheckForUpdatesJob implements ShouldBeEncrypted, ShouldQueue
if (isDev() || isCloud()) {
return;
}
- $settings = InstanceSettings::get();
+ $settings = instanceSettings();
$response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json');
if ($response->successful()) {
$versions = $response->json();
diff --git a/app/Jobs/DatabaseBackupJob.php b/app/Jobs/DatabaseBackupJob.php
index 947dc4317..769739d5e 100644
--- a/app/Jobs/DatabaseBackupJob.php
+++ b/app/Jobs/DatabaseBackupJob.php
@@ -2,9 +2,7 @@
namespace App\Jobs;
-use App\Actions\Database\StopDatabase;
use App\Events\BackupCreated;
-use App\Models\InstanceSettings;
use App\Models\S3Storage;
use App\Models\ScheduledDatabaseBackup;
use App\Models\ScheduledDatabaseBackupExecution;
@@ -25,7 +23,6 @@ use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Str;
-use Visus\Cuid2\Cuid2;
class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
{
@@ -64,32 +61,32 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
public function __construct($backup)
{
$this->backup = $backup;
- $this->team = Team::find($backup->team_id);
- if (is_null($this->team)) {
- return;
- }
- if (data_get($this->backup, 'database_type') === 'App\Models\ServiceDatabase') {
- $this->database = data_get($this->backup, 'database');
- $this->server = $this->database->service->server;
- $this->s3 = $this->backup->s3;
- } else {
- $this->database = data_get($this->backup, 'database');
- $this->server = $this->database->destination->server;
- $this->s3 = $this->backup->s3;
- }
}
public function handle(): void
{
try {
- // Check if team is exists
- if (is_null($this->team)) {
- $this->backup->update(['status' => 'failed']);
- StopDatabase::run($this->database);
- $this->database->delete();
+ $this->team = Team::find($this->backup->team_id);
+ if (! $this->team) {
+ $this->backup->delete();
return;
}
+ if (data_get($this->backup, 'database_type') === 'App\Models\ServiceDatabase') {
+ $this->database = data_get($this->backup, 'database');
+ $this->server = $this->database->service->server;
+ $this->s3 = $this->backup->s3;
+ } else {
+ $this->database = data_get($this->backup, 'database');
+ $this->server = $this->database->destination->server;
+ $this->s3 = $this->backup->s3;
+ }
+ if (is_null($this->server)) {
+ throw new \Exception('Server not found?!');
+ }
+ if (is_null($this->database)) {
+ throw new \Exception('Database not found?!');
+ }
BackupCreated::dispatch($this->team->id);
@@ -239,7 +236,6 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
}
}
$this->backup_dir = backup_dir().'/databases/'.str($this->team->name)->slug().'-'.$this->team->id.'/'.$this->directory_name;
-
if ($this->database->name === 'coolify-db') {
$databasesToBackup = ['coolify'];
$this->directory_name = $this->container_name = 'coolify-db';
@@ -252,6 +248,9 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
try {
if (str($databaseType)->contains('postgres')) {
$this->backup_file = "/pg-dump-$database-".Carbon::now()->timestamp.'.dmp';
+ if ($this->backup->dump_all) {
+ $this->backup_file = '/pg-dump-all-'.Carbon::now()->timestamp.'.gz';
+ }
$this->backup_location = $this->backup_dir.$this->backup_file;
$this->backup_log = ScheduledDatabaseBackupExecution::create([
'database_name' => $database,
@@ -280,6 +279,9 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
$this->backup_standalone_mongodb($database);
} elseif (str($databaseType)->contains('mysql')) {
$this->backup_file = "/mysql-dump-$database-".Carbon::now()->timestamp.'.dmp';
+ if ($this->backup->dump_all) {
+ $this->backup_file = '/mysql-dump-all-'.Carbon::now()->timestamp.'.gz';
+ }
$this->backup_location = $this->backup_dir.$this->backup_file;
$this->backup_log = ScheduledDatabaseBackupExecution::create([
'database_name' => $database,
@@ -289,6 +291,9 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
$this->backup_standalone_mysql($database);
} elseif (str($databaseType)->contains('mariadb')) {
$this->backup_file = "/mariadb-dump-$database-".Carbon::now()->timestamp.'.dmp';
+ if ($this->backup->dump_all) {
+ $this->backup_file = '/mariadb-dump-all-'.Carbon::now()->timestamp.'.gz';
+ }
$this->backup_location = $this->backup_dir.$this->backup_file;
$this->backup_log = ScheduledDatabaseBackupExecution::create([
'database_name' => $database,
@@ -327,7 +332,9 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
send_internal_notification('DatabaseBackupJob failed with: '.$e->getMessage());
throw $e;
} finally {
- BackupCreated::dispatch($this->team->id);
+ if ($this->team) {
+ BackupCreated::dispatch($this->team->id);
+ }
}
}
@@ -386,7 +393,11 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
if ($this->postgres_password) {
$backupCommand .= " -e PGPASSWORD=$this->postgres_password";
}
- $backupCommand .= " $this->container_name pg_dump --format=custom --no-acl --no-owner --username {$this->database->postgres_user} $database > $this->backup_location";
+ if ($this->backup->dump_all) {
+ $backupCommand .= " $this->container_name pg_dumpall --username {$this->database->postgres_user} | gzip > $this->backup_location";
+ } else {
+ $backupCommand .= " $this->container_name pg_dump --format=custom --no-acl --no-owner --username {$this->database->postgres_user} $database > $this->backup_location";
+ }
$commands[] = $backupCommand;
ray($commands);
@@ -407,8 +418,11 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
{
try {
$commands[] = 'mkdir -p '.$this->backup_dir;
- $commands[] = "docker exec $this->container_name mysqldump -u root -p{$this->database->mysql_root_password} $database > $this->backup_location";
- ray($commands);
+ if ($this->backup->dump_all) {
+ $commands[] = "docker exec $this->container_name mysqldump -u root -p{$this->database->mysql_root_password} --all-databases --single-transaction --quick --lock-tables=false --compress | gzip > $this->backup_location";
+ } else {
+ $commands[] = "docker exec $this->container_name mysqldump -u root -p{$this->database->mysql_root_password} $database > $this->backup_location";
+ }
$this->backup_output = instant_remote_process($commands, $this->server);
$this->backup_output = trim($this->backup_output);
if ($this->backup_output === '') {
@@ -426,7 +440,11 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
{
try {
$commands[] = 'mkdir -p '.$this->backup_dir;
- $commands[] = "docker exec $this->container_name mariadb-dump -u root -p{$this->database->mariadb_root_password} $database > $this->backup_location";
+ if ($this->backup->dump_all) {
+ $commands[] = "docker exec $this->container_name mariadb-dump -u root -p{$this->database->mariadb_root_password} --all-databases --single-transaction --quick --lock-tables=false --compress > $this->backup_location";
+ } else {
+ $commands[] = "docker exec $this->container_name mariadb-dump -u root -p{$this->database->mariadb_root_password} $database > $this->backup_location";
+ }
ray($commands);
$this->backup_output = instant_remote_process($commands, $this->server);
$this->backup_output = trim($this->backup_output);
@@ -468,34 +486,6 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
}
}
- // private function upload_to_s3(): void
- // {
- // try {
- // if (is_null($this->s3)) {
- // return;
- // }
- // $key = $this->s3->key;
- // $secret = $this->s3->secret;
- // // $region = $this->s3->region;
- // $bucket = $this->s3->bucket;
- // $endpoint = $this->s3->endpoint;
- // $this->s3->testConnection(shouldSave: true);
- // $configName = new Cuid2;
-
- // $s3_copy_dir = str($this->backup_location)->replace(backup_dir(), '/var/www/html/storage/app/backups/');
- // $commands[] = "docker exec coolify bash -c 'mc config host add {$configName} {$endpoint} $key $secret'";
- // $commands[] = "docker exec coolify bash -c 'mc cp $s3_copy_dir {$configName}/{$bucket}{$this->backup_dir}/'";
- // instant_remote_process($commands, $this->server);
- // $this->add_to_backup_output('Uploaded to S3.');
- // } catch (\Throwable $e) {
- // $this->add_to_backup_output($e->getMessage());
- // throw $e;
- // } finally {
- // $removeConfigCommands[] = "docker exec coolify bash -c 'mc config remove {$configName}'";
- // $removeConfigCommands[] = "docker exec coolify bash -c 'mc alias rm {$configName}'";
- // instant_remote_process($removeConfigCommands, $this->server, false);
- // }
- // }
private function upload_to_s3(): void
{
try {
@@ -517,10 +507,27 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
$this->ensureHelperImageAvailable();
$fullImageName = $this->getFullImageName();
- $commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup->uuid} --rm -v $this->backup_location:$this->backup_location:ro {$fullImageName}";
- $commands[] = "docker exec backup-of-{$this->backup->uuid} mc config host add temporary {$endpoint} $key $secret";
+
+ if (isDev()) {
+ if ($this->database->name === 'coolify-db') {
+ $backup_location_from = '/var/lib/docker/volumes/coolify_dev_backups_data/_data/coolify/coolify-db-'.$this->server->ip.$this->backup_file;
+ $commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup->uuid} --rm -v $backup_location_from:$this->backup_location:ro {$fullImageName}";
+ } else {
+ $backup_location_from = '/var/lib/docker/volumes/coolify_dev_backups_data/_data/databases/'.str($this->team->name)->slug().'-'.$this->team->id.'/'.$this->directory_name.$this->backup_file;
+ $commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup->uuid} --rm -v $backup_location_from:$this->backup_location:ro {$fullImageName}";
+ }
+ } else {
+ $commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup->uuid} --rm -v $this->backup_location:$this->backup_location:ro {$fullImageName}";
+ }
+ if ($this->s3->isHetzner()) {
+ $endpointWithoutBucket = 'https://'.str($endpoint)->after('https://')->after('.')->value();
+ $commands[] = "docker exec backup-of-{$this->backup->uuid} mc alias set --path=off --api=S3v4 temporary {$endpointWithoutBucket} $key $secret";
+ } else {
+ $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);
+
$this->add_to_backup_output('Uploaded to S3.');
} catch (\Throwable $e) {
$this->add_to_backup_output($e->getMessage());
@@ -562,7 +569,7 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
private function getFullImageName(): string
{
- $settings = InstanceSettings::get();
+ $settings = instanceSettings();
$helperImage = config('coolify.helper_image');
$latestVersion = $settings->helper_version;
diff --git a/app/Jobs/DeleteResourceJob.php b/app/Jobs/DeleteResourceJob.php
index ac34d064e..2442d5b06 100644
--- a/app/Jobs/DeleteResourceJob.php
+++ b/app/Jobs/DeleteResourceJob.php
@@ -31,10 +31,10 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue
public function __construct(
public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $resource,
- public bool $deleteConfigurations,
- public bool $deleteVolumes,
- public bool $dockerCleanup,
- public bool $deleteConnectedNetworks
+ public bool $deleteConfigurations = true,
+ public bool $deleteVolumes = true,
+ public bool $dockerCleanup = true,
+ public bool $deleteConnectedNetworks = true
) {}
public function handle()
diff --git a/app/Jobs/PullHelperImageJob.php b/app/Jobs/PullHelperImageJob.php
index ef1659680..4b208fc31 100644
--- a/app/Jobs/PullHelperImageJob.php
+++ b/app/Jobs/PullHelperImageJob.php
@@ -2,7 +2,6 @@
namespace App\Jobs;
-use App\Models\InstanceSettings;
use App\Models\Server;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
@@ -26,7 +25,7 @@ class PullHelperImageJob implements ShouldBeEncrypted, ShouldQueue
$response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json');
if ($response->successful()) {
$versions = $response->json();
- $settings = InstanceSettings::get();
+ $settings = instanceSettings();
$latest_version = data_get($versions, 'coolify.helper.version');
$current_version = $settings->helper_version;
if (version_compare($latest_version, $current_version, '>')) {
diff --git a/app/Jobs/ServerStorageCheckJob.php b/app/Jobs/ServerStorageCheckJob.php
new file mode 100644
index 000000000..376cb8532
--- /dev/null
+++ b/app/Jobs/ServerStorageCheckJob.php
@@ -0,0 +1,59 @@
+server->isFunctional()) {
+ ray('Server is not ready.');
+
+ return 'Server is not ready.';
+ }
+ $team = $this->server->team;
+ $percentage = $this->server->storageCheck();
+ if ($percentage > 1) {
+ ray('Server storage is at '.$percentage.'%');
+ }
+
+ } catch (\Throwable $e) {
+ ray($e->getMessage());
+
+ return handleError($e);
+ }
+
+ }
+}
diff --git a/app/Jobs/UpdateCoolifyJob.php b/app/Jobs/UpdateCoolifyJob.php
index 4c65a711f..2cc705e4a 100644
--- a/app/Jobs/UpdateCoolifyJob.php
+++ b/app/Jobs/UpdateCoolifyJob.php
@@ -3,7 +3,6 @@
namespace App\Jobs;
use App\Actions\Server\UpdateCoolify;
-use App\Models\InstanceSettings;
use App\Models\Server;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
@@ -23,7 +22,7 @@ class UpdateCoolifyJob implements ShouldBeEncrypted, ShouldQueue
{
try {
CheckForUpdatesJob::dispatchSync();
- $settings = InstanceSettings::get();
+ $settings = instanceSettings();
if (! $settings->new_version_available) {
Log::info('No new version available. Skipping update.');
diff --git a/app/Livewire/Dashboard.php b/app/Livewire/Dashboard.php
index 1f0b68dd3..d18a7689e 100644
--- a/app/Livewire/Dashboard.php
+++ b/app/Livewire/Dashboard.php
@@ -30,7 +30,7 @@ class Dashboard extends Component
public function cleanup_queue()
{
- Artisan::queue('cleanup:application-deployment-queue', [
+ Artisan::queue('cleanup:deployment-queue', [
'--team-id' => currentTeam()->id,
]);
}
diff --git a/app/Livewire/Help.php b/app/Livewire/Help.php
index d6dc0d521..934e81661 100644
--- a/app/Livewire/Help.php
+++ b/app/Livewire/Help.php
@@ -47,7 +47,7 @@ class Help extends Component
]
);
$mail->subject("[HELP]: {$this->subject}");
- $settings = \App\Models\InstanceSettings::get();
+ $settings = instanceSettings();
$type = set_transanctional_email_settings($settings);
if (! $type) {
$url = 'https://app.coolify.io/api/feedback';
@@ -61,6 +61,7 @@ class Help extends Component
send_user_an_email($mail, auth()->user()?->email, 'hi@coollabs.io');
}
$this->dispatch('success', 'Feedback sent.', 'We will get in touch with you as soon as possible.');
+ $this->reset('description', 'subject');
} catch (\Throwable $e) {
return handleError($e, $this);
}
diff --git a/app/Livewire/Notifications/Email.php b/app/Livewire/Notifications/Email.php
index 2960ed226..53673292e 100644
--- a/app/Livewire/Notifications/Email.php
+++ b/app/Livewire/Notifications/Email.php
@@ -172,7 +172,7 @@ class Email extends Component
public function copyFromInstanceSettings()
{
- $settings = \App\Models\InstanceSettings::get();
+ $settings = instanceSettings();
if ($settings->smtp_enabled) {
$team = currentTeam();
$team->update([
diff --git a/app/Livewire/Project/Database/BackupEdit.php b/app/Livewire/Project/Database/BackupEdit.php
index 98b2b4263..7e2e4a12b 100644
--- a/app/Livewire/Project/Database/BackupEdit.php
+++ b/app/Livewire/Project/Database/BackupEdit.php
@@ -31,6 +31,7 @@ class BackupEdit extends Component
'backup.save_s3' => 'required|boolean',
'backup.s3_storage_id' => 'nullable|integer',
'backup.databases_to_backup' => 'nullable',
+ 'backup.dump_all' => 'required|boolean',
];
protected $validationAttributes = [
@@ -40,6 +41,7 @@ class BackupEdit extends Component
'backup.save_s3' => 'Save to S3',
'backup.s3_storage_id' => 'S3 Storage',
'backup.databases_to_backup' => 'Databases to Backup',
+ 'backup.dump_all' => 'Backup All Databases',
];
protected $messages = [
diff --git a/app/Livewire/Project/Database/ScheduledBackups.php b/app/Livewire/Project/Database/ScheduledBackups.php
index beb5a9c39..8021e25d3 100644
--- a/app/Livewire/Project/Database/ScheduledBackups.php
+++ b/app/Livewire/Project/Database/ScheduledBackups.php
@@ -26,7 +26,7 @@ class ScheduledBackups extends Component
public function mount(): void
{
if ($this->selectedBackupId) {
- $this->setSelectedBackup($this->selectedBackupId);
+ $this->setSelectedBackup($this->selectedBackupId, true);
}
$this->parameters = get_route_parameters();
if ($this->database->getMorphClass() === 'App\Models\ServiceDatabase') {
@@ -37,10 +37,13 @@ class ScheduledBackups extends Component
$this->s3s = currentTeam()->s3s;
}
- public function setSelectedBackup($backupId)
+ public function setSelectedBackup($backupId, $force = false)
{
+ if ($this->selectedBackupId === $backupId && ! $force) {
+ return;
+ }
$this->selectedBackupId = $backupId;
- $this->selectedBackup = $this->database->scheduledBackups->find($this->selectedBackupId);
+ $this->selectedBackup = $this->database->scheduledBackups->find($backupId);
if (is_null($this->selectedBackup)) {
$this->selectedBackupId = null;
}
diff --git a/app/Livewire/Project/New/Select.php b/app/Livewire/Project/New/Select.php
index 3c5f3901b..4c5c3a691 100644
--- a/app/Livewire/Project/New/Select.php
+++ b/app/Livewire/Project/New/Select.php
@@ -90,6 +90,121 @@ class Select extends Component
// }
// }
+ public function loadServices2()
+ {
+ $services = get_service_templates(true);
+ $services = collect($services)->map(function ($service, $key) {
+ return array_merge($service, [
+ 'name' => str($key)->headline(),
+ 'logo' => asset($service['logo']),
+ ]);
+ })->all();
+ $gitBasedApplications = [
+ [
+ 'id' => 'public',
+ 'name' => 'Public Repository',
+ 'description' => 'You can deploy any kind of public repositories from the supported git providers.',
+ 'logo' => asset('svgs/git.svg'),
+ ],
+ [
+ 'id' => 'private-gh-app',
+ 'name' => 'Private Repository (with GitHub App)',
+ 'description' => 'You can deploy public & private repositories through your GitHub Apps.',
+ 'logo' => asset('svgs/github.svg'),
+ ],
+ [
+ 'id' => 'private-deploy-key',
+ 'name' => 'Private Repository (with Deploy Key)',
+ 'description' => 'You can deploy private repositories with a deploy key.',
+ 'logo' => asset('svgs/git.svg'),
+ ],
+ ];
+ $dockerBasedApplications = [
+ [
+ 'id' => 'dockerfile',
+ 'name' => 'Dockerfile',
+ 'description' => 'You can deploy a simple Dockerfile, without Git.',
+ 'logo' => asset('svgs/docker.svg'),
+ ],
+ [
+ 'id' => 'docker-compose-empty',
+ 'name' => 'Docker Compose Empty',
+ 'description' => 'You can deploy complex application easily with Docker Compose, without Git.',
+ 'logo' => asset('svgs/docker.svg'),
+ ],
+ [
+ 'id' => 'docker-image',
+ 'name' => 'Docker Image',
+ 'description' => 'You can deploy an existing Docker Image from any Registry, without Git.',
+ 'logo' => asset('svgs/docker.svg'),
+ ],
+ ];
+ $databases = [
+ [
+ 'id' => 'postgresql',
+ 'name' => 'PostgreSQL',
+ 'description' => 'PostgreSQL is an object-relational database known for its robustness, advanced features, and strong standards compliance.',
+ 'logo' => '
+',
+ ],
+ [
+ 'id' => 'mysql',
+ 'name' => 'MySQL',
+ 'description' => 'MySQL is an open-source relational database known for its simplicity and performance.',
+ 'logo' => '',
+
+ ],
+ [
+ 'id' => 'mariadb',
+ 'name' => 'MariaDB',
+ 'description' => 'MariaDB is an open-source relational database known for its simplicity and performance.',
+ 'logo' => '',
+ ],
+ [
+ 'id' => 'redis',
+ 'name' => 'Redis',
+ 'description' => 'Redis is an open-source in-memory data structure store known for its simplicity and performance.',
+ 'logo' => '',
+ ],
+ [
+ 'id' => 'keydb',
+ 'name' => 'KeyDB',
+ 'description' => 'KeyDB is an open-source in-memory data structure store known for its simplicity and performance.',
+ 'logo' => '