diff --git a/app/Actions/Service/DeleteService.php b/app/Actions/Service/DeleteService.php index 1a411cf59..420f40f3b 100644 --- a/app/Actions/Service/DeleteService.php +++ b/app/Actions/Service/DeleteService.php @@ -45,6 +45,9 @@ class DeleteService foreach ($service->databases()->get() as $database) { $database->forceDelete(); } + foreach ($service->scheduled_tasks as $task) { + $task->delete(); + } $service->tags()->detach(); } } diff --git a/app/Console/Commands/CleanupStuckedResources.php b/app/Console/Commands/CleanupStuckedResources.php index 38d87e24b..d2882e9b3 100644 --- a/app/Console/Commands/CleanupStuckedResources.php +++ b/app/Console/Commands/CleanupStuckedResources.php @@ -3,6 +3,7 @@ namespace App\Console\Commands; use App\Models\Application; +use App\Models\ScheduledTask; use App\Models\Service; use App\Models\ServiceApplication; use App\Models\ServiceDatabase; @@ -108,6 +109,17 @@ class CleanupStuckedResources extends Command } catch (\Throwable $e) { echo "Error in cleaning stuck serviceapp: {$e->getMessage()}\n"; } + try { + $scheduled_tasks = ScheduledTask::all(); + foreach ($scheduled_tasks as $scheduled_task) { + if (!$scheduled_task->service && !$scheduled_task->application) { + echo "Deleting stuck scheduledtask: {$scheduled_task->name}\n"; + $scheduled_task->delete(); + } + } + } catch (\Throwable $e) { + echo "Error in cleaning stuck scheduledtasks: {$e->getMessage()}\n"; + } // Cleanup any resources that are not attached to any environment or destination or server try { diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 37266ca5d..bba2da818 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -120,8 +120,8 @@ class Kernel extends ConsoleKernel return; } foreach ($scheduled_tasks as $scheduled_task) { - $service = $scheduled_task->service()->get(); - $application = $scheduled_task->application()->get(); + $service = $scheduled_task->service; + $application = $scheduled_task->application; if (!$application && !$service) { ray('application/service attached to scheduled task does not exist'); diff --git a/app/Jobs/ServerStatusJob.php b/app/Jobs/ServerStatusJob.php index b327c3a7c..d0243fa9a 100644 --- a/app/Jobs/ServerStatusJob.php +++ b/app/Jobs/ServerStatusJob.php @@ -16,7 +16,7 @@ class ServerStatusJob implements ShouldQueue, ShouldBeEncrypted { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - public ?int $disk_usage = null; + public int|string|null $disk_usage = null; public $tries = 4; public function backoff(): int { diff --git a/app/Livewire/Project/Service/FileStorage.php b/app/Livewire/Project/Service/FileStorage.php index ab7736c97..da85a1a6e 100644 --- a/app/Livewire/Project/Service/FileStorage.php +++ b/app/Livewire/Project/Service/FileStorage.php @@ -41,7 +41,7 @@ class FileStorage extends Component $this->fileStorage->content = null; } $this->fileStorage->save(); - $this->fileStorage->saveStorageOnServer($this->service); + $this->fileStorage->saveStorageOnServer(); $this->dispatch('success', 'File updated successfully.'); } catch (\Throwable $e) { $this->fileStorage->setRawAttributes($original); diff --git a/app/Models/Application.php b/app/Models/Application.php index 0ba1d24d1..b37476565 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -48,6 +48,9 @@ class Application extends BaseModel $application->persistentStorages()->delete(); $application->environment_variables()->delete(); $application->environment_variables_preview()->delete(); + foreach ($application->scheduled_tasks as $task) { + $task->delete(); + } $application->tags()->detach(); }); } diff --git a/app/Models/LocalFileVolume.php b/app/Models/LocalFileVolume.php index 6750f2dc4..76a15f655 100644 --- a/app/Models/LocalFileVolume.php +++ b/app/Models/LocalFileVolume.php @@ -13,24 +13,34 @@ class LocalFileVolume extends BaseModel protected static function booted() { static::created(function (LocalFileVolume $fileVolume) { - $fileVolume->saveStorageOnServer($fileVolume->service); + $fileVolume->load(['service']); + $fileVolume->saveStorageOnServer(); }); } public function service() { return $this->morphTo('resource'); } - public function saveStorageOnServer(ServiceApplication|ServiceDatabase $service) + public function saveStorageOnServer() { - ray('saveStorageOnServer'); - $workdir = $service->service->workdir(); - $server = $service->service->server; + $workdir = $this->resource->service->workdir(); + $server = $this->resource->service->server; $commands = collect([ "mkdir -p $workdir > /dev/null 2>&1 || true", "cd $workdir" ]); + $is_directory = $this->is_directory; + if ($is_directory) { + $commands->push("mkdir -p $this->fs_path > /dev/null 2>&1 || true"); + } + if (str($this->fs_path)->startsWith('.') || str($this->fs_path)->startsWith('/') || str($this->fs_path)->startsWith('~')) { + $parent_dir = str($this->fs_path)->beforeLast('/'); + if ($parent_dir != '') { + $commands->push("mkdir -p $parent_dir > /dev/null 2>&1 || true"); + } + } $fileVolume = $this; - $path = Str::of(data_get($fileVolume, 'fs_path')); + $path = str(data_get($fileVolume, 'fs_path')); $content = data_get($fileVolume, 'content'); if ($path->startsWith('.')) { $path = $path->after('.'); @@ -39,17 +49,18 @@ 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) { - throw new \Exception("File $path 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."); + 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."); } else if ($isDir == 'OK' && !$fileVolume->is_directory) { - throw new \Exception("File $path is a directory on the server, but you are trying to mark it as a file. Please delete the directory on the server or mark it as directory."); + throw new \Exception("The following file is a directory on the server, but you are trying to mark it as a file.

Please delete the directory on the server or mark it as directory."); } if (!$fileVolume->is_directory && $isDir == 'NOK') { - $content = base64_encode($content); - $commands->push("echo '$content' | base64 -d > $path"); + if ($content) { + $content = base64_encode($content); + $commands->push("echo '$content' | base64 -d > $path"); + } } else if ($isDir == 'NOK' && $fileVolume->is_directory) { $commands->push("mkdir -p $path > /dev/null 2>&1 || true"); } - ray($commands->toArray()); return instant_remote_process($commands, $server); } } diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index 10d8f313b..9ab26e59a 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -125,6 +125,9 @@ function handleError(?Throwable $error = null, ?Livewire\Component $livewire = n } if (isset($livewire)) { + if (str($message)->length() > 20) { + return $livewire->dispatch('error', 'Error occured', $message); + } return $livewire->dispatch('error', $message); } throw new Exception($message); @@ -527,28 +530,32 @@ function getTopLevelNetworks(Service|Application $resource) $definedNetwork = collect([$resource->uuid]); $services = collect($services)->map(function ($service, $_) use ($topLevelNetworks, $definedNetwork) { $serviceNetworks = collect(data_get($service, 'networks', [])); + $hasHostNetworkMode = data_get($service, 'network_mode') === 'host' ? true : false; - // Collect/create/update networks - if ($serviceNetworks->count() > 0) { - foreach ($serviceNetworks as $networkName => $networkDetails) { - $networkExists = $topLevelNetworks->contains(function ($value, $key) use ($networkName) { - return $value == $networkName || $key == $networkName; - }); - if (!$networkExists) { - $topLevelNetworks->put($networkDetails, null); + // Only add 'networks' key if 'network_mode' is not 'host' + if (!$hasHostNetworkMode) { + // Collect/create/update networks + if ($serviceNetworks->count() > 0) { + foreach ($serviceNetworks as $networkName => $networkDetails) { + $networkExists = $topLevelNetworks->contains(function ($value, $key) use ($networkName) { + return $value == $networkName || $key == $networkName; + }); + if (!$networkExists) { + $topLevelNetworks->put($networkDetails, null); + } } } - } - $definedNetworkExists = $topLevelNetworks->contains(function ($value, $_) use ($definedNetwork) { - return $value == $definedNetwork; - }); - if (!$definedNetworkExists) { - foreach ($definedNetwork as $network) { - $topLevelNetworks->put($network, [ - 'name' => $network, - 'external' => true - ]); + $definedNetworkExists = $topLevelNetworks->contains(function ($value, $_) use ($definedNetwork) { + return $value == $definedNetwork; + }); + if (!$definedNetworkExists) { + foreach ($definedNetwork as $network) { + $topLevelNetworks->put($network, [ + 'name' => $network, + 'external' => true + ]); + } } } @@ -628,6 +635,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal $serviceNetworks = collect(data_get($service, 'networks', [])); $serviceVariables = collect(data_get($service, 'environment', [])); $serviceLabels = collect(data_get($service, 'labels', [])); + $hasHostNetworkMode = data_get($service, 'network_mode') === 'host' ? true : false; if ($serviceLabels->count() > 0) { $removedLabels = collect([]); $serviceLabels = $serviceLabels->filter(function ($serviceLabel, $serviceLabelName) use ($removedLabels) { @@ -698,7 +706,6 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal $savedService->image = $image; $savedService->save(); } - // Collect/create/update networks if ($serviceNetworks->count() > 0) { foreach ($serviceNetworks as $networkName => $networkDetails) { @@ -729,37 +736,39 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal $savedService->ports = $collectedPorts->implode(','); $savedService->save(); - // Add Coolify specific networks - $definedNetworkExists = $topLevelNetworks->contains(function ($value, $_) use ($definedNetwork) { - return $value == $definedNetwork; - }); - if (!$definedNetworkExists) { - foreach ($definedNetwork as $network) { - $topLevelNetworks->put($network, [ - 'name' => $network, - 'external' => true - ]); + if (!$hasHostNetworkMode) { + // Add Coolify specific networks + $definedNetworkExists = $topLevelNetworks->contains(function ($value, $_) use ($definedNetwork) { + return $value == $definedNetwork; + }); + if (!$definedNetworkExists) { + foreach ($definedNetwork as $network) { + $topLevelNetworks->put($network, [ + 'name' => $network, + 'external' => true + ]); + } } - } - $networks = collect(); - foreach ($serviceNetworks as $key => $serviceNetwork) { - if (gettype($serviceNetwork) === 'string') { - // networks: - // - appwrite - $networks->put($serviceNetwork, null); - } else if (gettype($serviceNetwork) === 'array') { - // networks: - // default: - // ipv4_address: 192.168.203.254 - // $networks->put($serviceNetwork, null); - ray($key); - $networks->put($key, $serviceNetwork); + $networks = collect(); + foreach ($serviceNetworks as $key => $serviceNetwork) { + if (gettype($serviceNetwork) === 'string') { + // networks: + // - appwrite + $networks->put($serviceNetwork, null); + } else if (gettype($serviceNetwork) === 'array') { + // networks: + // default: + // ipv4_address: 192.168.203.254 + // $networks->put($serviceNetwork, null); + ray($key); + $networks->put($key, $serviceNetwork); + } } + foreach ($definedNetwork as $key => $network) { + $networks->put($network, null); + } + data_set($service, 'networks', $networks->toArray()); } - foreach ($definedNetwork as $key => $network) { - $networks->put($network, null); - } - data_set($service, 'networks', $networks->toArray()); // Collect/create/update volumes if ($serviceVolumes->count() > 0) { diff --git a/config/sentry.php b/config/sentry.php index b4d0a5bb8..071a24929 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.215', + 'release' => '4.0.0-beta.216', // 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 5160b78a0..e1bf71945 100644 --- a/config/version.php +++ b/config/version.php @@ -1,3 +1,3 @@
-

Recent executions

+

Recent executions (click to check output)

diff --git a/templates/compose/directus-with-postgresql.yaml b/templates/compose/directus-with-postgresql.yaml index 21fc83f3c..431f30f6a 100644 --- a/templates/compose/directus-with-postgresql.yaml +++ b/templates/compose/directus-with-postgresql.yaml @@ -4,7 +4,7 @@ services: directus: - image: directus/directus:10.7 + image: directus/directus:10 volumes: - directus-uploads:/directus/uploads - directus-extensions:/directus/extensions diff --git a/templates/service-templates.json b/templates/service-templates.json index 73c1b889e..8d13fe743 100644 --- a/templates/service-templates.json +++ b/templates/service-templates.json @@ -68,7 +68,7 @@ "directus-with-postgresql": { "documentation": "https:\/\/docs.directus.io\/self-hosted\/quickstart.html", "slogan": "Directus is an open-source tool that wraps custom SQL databases with a dynamic API, and provides an intuitive admin app for managing its content.", - "compose": "c2VydmljZXM6CiAgZGlyZWN0dXM6CiAgICBpbWFnZTogJ2RpcmVjdHVzL2RpcmVjdHVzOjEwLjcnCiAgICB2b2x1bWVzOgogICAgICAtICdkaXJlY3R1cy11cGxvYWRzOi9kaXJlY3R1cy91cGxvYWRzJwogICAgICAtICdkaXJlY3R1cy1leHRlbnNpb25zOi9kaXJlY3R1cy9leHRlbnNpb25zJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9GUUROX0RJUkVDVFVTCiAgICAgIC0gS0VZPSRTRVJWSUNFX0JBU0U2NF82NF9LRVkKICAgICAgLSBTRUNSRVQ9JFNFUlZJQ0VfQkFTRTY0XzY0X1NFQ1JFVAogICAgICAtICdBRE1JTl9FTUFJTD0ke0FETUlOX0VNQUlMOi1hZG1pbkBleGFtcGxlLmNvbX0nCiAgICAgIC0gQURNSU5fUEFTU1dPUkQ9JFNFUlZJQ0VfUEFTU1dPUkRfQURNSU4KICAgICAgLSBEQl9DTElFTlQ9cG9zdGdyZXMKICAgICAgLSBEQl9IT1NUPXBvc3RncmVzcWwKICAgICAgLSBEQl9QT1JUPTU0MzIKICAgICAgLSAnREJfREFUQUJBU0U9JHtQT1NUR1JFU1FMX0RBVEFCQVNFOi1kaXJlY3R1c30nCiAgICAgIC0gREJfVVNFUj0kU0VSVklDRV9VU0VSX1BPU1RHUkVTUUwKICAgICAgLSBEQl9QQVNTV09SRD0kU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU1FMCiAgICAgIC0gUkVESVNfSE9TVD1yZWRpcwogICAgICAtIFJFRElTX1BPUlQ9NjM3OQogICAgICAtIFdFQlNPQ0tFVFNfRU5BQkxFRD10cnVlCiAgcG9zdGdyZXNxbDoKICAgIGltYWdlOiAncG9zdGdyZXM6MTYtYWxwaW5lJwogICAgdm9sdW1lczoKICAgICAgLSAnZGlyZWN0dXMtcG9zdGdyZXNxbC1kYXRhOi92YXIvbGliL3Bvc3RncmVzcWwvZGF0YScKICAgIGVudmlyb25tZW50OgogICAgICAtICdQT1NUR1JFU19VU0VSPSR7U0VSVklDRV9VU0VSX1BPU1RHUkVTUUx9JwogICAgICAtICdQT1NUR1JFU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVNRTH0nCiAgICAgIC0gJ1BPU1RHUkVTX0RCPSR7UE9TVEdSRVNRTF9EQVRBQkFTRTotZGlyZWN0dXN9JwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdwZ19pc3JlYWR5IC1VICQke1BPU1RHUkVTX1VTRVJ9IC1kICQke1BPU1RHUkVTX0RCfScKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDIwcwogICAgICByZXRyaWVzOiAxMAogIHJlZGlzOgogICAgaW1hZ2U6ICdyZWRpczo3LWFscGluZScKICAgIGNvbW1hbmQ6ICdyZWRpcy1zZXJ2ZXIgLS1hcHBlbmRvbmx5IHllcycKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2RpcmVjdHVzLXJlZGlzLWRhdGE6L2RhdGEnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gcmVkaXMtY2xpCiAgICAgICAgLSBwaW5nCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAK", + "compose": "c2VydmljZXM6CiAgZGlyZWN0dXM6CiAgICBpbWFnZTogJ2RpcmVjdHVzL2RpcmVjdHVzOjEwJwogICAgdm9sdW1lczoKICAgICAgLSAnZGlyZWN0dXMtdXBsb2FkczovZGlyZWN0dXMvdXBsb2FkcycKICAgICAgLSAnZGlyZWN0dXMtZXh0ZW5zaW9uczovZGlyZWN0dXMvZXh0ZW5zaW9ucycKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9ESVJFQ1RVUwogICAgICAtIEtFWT0kU0VSVklDRV9CQVNFNjRfNjRfS0VZCiAgICAgIC0gU0VDUkVUPSRTRVJWSUNFX0JBU0U2NF82NF9TRUNSRVQKICAgICAgLSAnQURNSU5fRU1BSUw9JHtBRE1JTl9FTUFJTDotYWRtaW5AZXhhbXBsZS5jb219JwogICAgICAtIEFETUlOX1BBU1NXT1JEPSRTRVJWSUNFX1BBU1NXT1JEX0FETUlOCiAgICAgIC0gREJfQ0xJRU5UPXBvc3RncmVzCiAgICAgIC0gREJfSE9TVD1wb3N0Z3Jlc3FsCiAgICAgIC0gREJfUE9SVD01NDMyCiAgICAgIC0gJ0RCX0RBVEFCQVNFPSR7UE9TVEdSRVNRTF9EQVRBQkFTRTotZGlyZWN0dXN9JwogICAgICAtIERCX1VTRVI9JFNFUlZJQ0VfVVNFUl9QT1NUR1JFU1FMCiAgICAgIC0gREJfUEFTU1dPUkQ9JFNFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVNRTAogICAgICAtIFJFRElTX0hPU1Q9cmVkaXMKICAgICAgLSBSRURJU19QT1JUPTYzNzkKICAgICAgLSBXRUJTT0NLRVRTX0VOQUJMRUQ9dHJ1ZQogIHBvc3RncmVzcWw6CiAgICBpbWFnZTogJ3Bvc3RncmVzOjE2LWFscGluZScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2RpcmVjdHVzLXBvc3RncmVzcWwtZGF0YTovdmFyL2xpYi9wb3N0Z3Jlc3FsL2RhdGEnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnUE9TVEdSRVNfVVNFUj0ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU1FMfScKICAgICAgLSAnUE9TVEdSRVNfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTUUx9JwogICAgICAtICdQT1NUR1JFU19EQj0ke1BPU1RHUkVTUUxfREFUQUJBU0U6LWRpcmVjdHVzfScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAncGdfaXNyZWFkeSAtVSAkJHtQT1NUR1JFU19VU0VSfSAtZCAkJHtQT1NUR1JFU19EQn0nCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAKICByZWRpczoKICAgIGltYWdlOiAncmVkaXM6Ny1hbHBpbmUnCiAgICBjb21tYW5kOiAncmVkaXMtc2VydmVyIC0tYXBwZW5kb25seSB5ZXMnCiAgICB2b2x1bWVzOgogICAgICAtICdkaXJlY3R1cy1yZWRpcy1kYXRhOi9kYXRhJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIHJlZGlzLWNsaQogICAgICAgIC0gcGluZwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCg==", "tags": [ "directus", "cms", diff --git a/versions.json b/versions.json index eb5387e01..880ee2308 100644 --- a/versions.json +++ b/versions.json @@ -4,7 +4,7 @@ "version": "3.12.36" }, "v4": { - "version": "4.0.0-beta.215" + "version": "4.0.0-beta.216" } } }