diff --git a/.github/workflows/development-build.yml b/.github/workflows/development-build.yml index d64af3b59..268b885ac 100644 --- a/.github/workflows/development-build.yml +++ b/.github/workflows/development-build.yml @@ -26,7 +26,7 @@ jobs: uses: docker/build-push-action@v5 with: context: . - file: docker/prod-ssu/Dockerfile + file: docker/prod/Dockerfile platforms: linux/amd64 push: true tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }} @@ -47,7 +47,7 @@ jobs: uses: docker/build-push-action@v5 with: context: . - file: docker/prod-ssu/Dockerfile + file: docker/prod/Dockerfile platforms: linux/aarch64 push: true tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}-aarch64 diff --git a/.github/workflows/production-build.yml b/.github/workflows/production-build.yml index 3709b5b46..e4bad6a65 100644 --- a/.github/workflows/production-build.yml +++ b/.github/workflows/production-build.yml @@ -29,7 +29,7 @@ jobs: uses: docker/build-push-action@v5 with: context: . - file: docker/prod-ssu/Dockerfile + file: docker/prod/Dockerfile platforms: linux/amd64 push: true tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} @@ -51,7 +51,7 @@ jobs: uses: docker/build-push-action@v5 with: context: . - file: docker/prod-ssu/Dockerfile + file: docker/prod/Dockerfile platforms: linux/aarch64 push: true tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 diff --git a/README.md b/README.md index 97580b875..c4e575e16 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ +[![Bounty Issues](https://img.shields.io/static/v1?labelColor=grey&color=6366f1&label=Algora&message=%F0%9F%92%8E+Bounty+issues&style=for-the-badge)](https://console.algora.io/org/coollabsio/bounties/new) +[![Open Bounties](https://img.shields.io/endpoint?url=https%3A%2F%2Fconsole.algora.io%2Fapi%2Fshields%2Fcoollabsio%2Fbounties%3Fstatus%3Dopen&style=for-the-badge)](https://console.algora.io/org/coollabsio/bounties?status=open) +[![Rewarded Bounties](https://img.shields.io/endpoint?url=https%3A%2F%2Fconsole.algora.io%2Fapi%2Fshields%2Fcoollabsio%2Fbounties%3Fstatus%3Dcompleted&style=for-the-badge)](https://console.algora.io/org/coollabsio/bounties?status=completed) # About the Project Coolify is an open-source & self-hostable alternative to Heroku / Netlify / Vercel / etc. @@ -39,9 +42,11 @@ Special thanks to our biggest sponsor, [CCCareers](https://cccareers.org/)! QuantCDN +Lightspeed.run FlintCompany American Cloud CryptoJobsList +Thompson Edolo UXWizz Younes Barrad Automaze diff --git a/app/Actions/CoolifyTask/RunRemoteProcess.php b/app/Actions/CoolifyTask/RunRemoteProcess.php index 2c90750e6..16924476b 100644 --- a/app/Actions/CoolifyTask/RunRemoteProcess.php +++ b/app/Actions/CoolifyTask/RunRemoteProcess.php @@ -60,7 +60,7 @@ class RunRemoteProcess $decoded = json_decode( data_get($activity, 'description'), associative: true, - flags: JSON_THROW_ON_ERROR + flags: JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE ); } catch (\JsonException $exception) { return ''; @@ -164,8 +164,7 @@ class RunRemoteProcess public function encodeOutput($type, $output) { - $outputStack = json_decode($this->activity->description, associative: true, flags: JSON_THROW_ON_ERROR); - + $outputStack = json_decode($this->activity->description, associative: true, flags: JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE); $outputStack[] = [ 'type' => $type, 'output' => $output, @@ -174,12 +173,12 @@ class RunRemoteProcess 'order' => $this->getLatestCounter(), ]; - return json_encode($outputStack, flags: JSON_THROW_ON_ERROR); + return json_encode($outputStack, flags: JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE); } protected function getLatestCounter(): int { - $description = json_decode($this->activity->description, associative: true, flags: JSON_THROW_ON_ERROR); + $description = json_decode($this->activity->description, associative: true, flags: JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE); if ($description === null || count($description) === 0) { return 1; } diff --git a/app/Actions/Database/StartClickhouse.php b/app/Actions/Database/StartClickhouse.php index 5f567802f..414d6b407 100644 --- a/app/Actions/Database/StartClickhouse.php +++ b/app/Actions/Database/StartClickhouse.php @@ -29,6 +29,7 @@ class StartClickhouse ]; $persistent_storages = $this->generate_local_persistent_volumes(); + $persistent_file_volumes = $this->database->fileStorages()->get(); $volume_names = $this->generate_local_persistent_volumes_only_volume_names(); $environment_variables = $this->generate_environment_variables(); @@ -93,6 +94,11 @@ class StartClickhouse if (count($persistent_storages) > 0) { $docker_compose['services'][$container_name]['volumes'] = $persistent_storages; } + if (count($persistent_file_volumes) > 0) { + $docker_compose['services'][$container_name]['volumes'] = $persistent_file_volumes->map(function ($item) { + return "$item->fs_path:$item->mount_path"; + })->toArray(); + } if (count($volume_names) > 0) { $docker_compose['volumes'] = $volume_names; } diff --git a/app/Actions/Database/StartDragonfly.php b/app/Actions/Database/StartDragonfly.php index 92daf195d..04348c40a 100644 --- a/app/Actions/Database/StartDragonfly.php +++ b/app/Actions/Database/StartDragonfly.php @@ -32,6 +32,7 @@ class StartDragonfly ]; $persistent_storages = $this->generate_local_persistent_volumes(); + $persistent_file_volumes = $this->database->fileStorages()->get(); $volume_names = $this->generate_local_persistent_volumes_only_volume_names(); $environment_variables = $this->generate_environment_variables(); @@ -94,6 +95,11 @@ class StartDragonfly if (count($persistent_storages) > 0) { $docker_compose['services'][$container_name]['volumes'] = $persistent_storages; } + if (count($persistent_file_volumes) > 0) { + $docker_compose['services'][$container_name]['volumes'] = $persistent_file_volumes->map(function ($item) { + return "$item->fs_path:$item->mount_path"; + })->toArray(); + } if (count($volume_names) > 0) { $docker_compose['volumes'] = $volume_names; } diff --git a/app/Actions/Database/StartKeydb.php b/app/Actions/Database/StartKeydb.php index 8c833efd5..672308d89 100644 --- a/app/Actions/Database/StartKeydb.php +++ b/app/Actions/Database/StartKeydb.php @@ -32,6 +32,7 @@ class StartKeydb ]; $persistent_storages = $this->generate_local_persistent_volumes(); + $persistent_file_volumes = $this->database->fileStorages()->get(); $volume_names = $this->generate_local_persistent_volumes_only_volume_names(); $environment_variables = $this->generate_environment_variables(); $this->add_custom_keydb(); @@ -92,6 +93,11 @@ class StartKeydb if (count($persistent_storages) > 0) { $docker_compose['services'][$container_name]['volumes'] = $persistent_storages; } + if (count($persistent_file_volumes) > 0) { + $docker_compose['services'][$container_name]['volumes'] = $persistent_file_volumes->map(function ($item) { + return "$item->fs_path:$item->mount_path"; + })->toArray(); + } if (count($volume_names) > 0) { $docker_compose['volumes'] = $volume_names; } diff --git a/app/Actions/Database/StartMariadb.php b/app/Actions/Database/StartMariadb.php index c79df0dc5..652d8fa29 100644 --- a/app/Actions/Database/StartMariadb.php +++ b/app/Actions/Database/StartMariadb.php @@ -28,6 +28,7 @@ class StartMariadb ]; $persistent_storages = $this->generate_local_persistent_volumes(); + $persistent_file_volumes = $this->database->fileStorages()->get(); $volume_names = $this->generate_local_persistent_volumes_only_volume_names(); $environment_variables = $this->generate_environment_variables(); $this->add_custom_mysql(); @@ -86,6 +87,11 @@ class StartMariadb if (count($persistent_storages) > 0) { $docker_compose['services'][$container_name]['volumes'] = $persistent_storages; } + if (count($persistent_file_volumes) > 0) { + $docker_compose['services'][$container_name]['volumes'] = $persistent_file_volumes->map(function ($item) { + return "$item->fs_path:$item->mount_path"; + })->toArray(); + } if (count($volume_names) > 0) { $docker_compose['volumes'] = $volume_names; } diff --git a/app/Actions/Database/StartMongodb.php b/app/Actions/Database/StartMongodb.php index 46b426ad8..38e2621bd 100644 --- a/app/Actions/Database/StartMongodb.php +++ b/app/Actions/Database/StartMongodb.php @@ -30,6 +30,7 @@ class StartMongodb ]; $persistent_storages = $this->generate_local_persistent_volumes(); + $persistent_file_volumes = $this->database->fileStorages()->get(); $volume_names = $this->generate_local_persistent_volumes_only_volume_names(); $environment_variables = $this->generate_environment_variables(); $this->add_custom_mongo_conf(); @@ -94,6 +95,11 @@ class StartMongodb if (count($persistent_storages) > 0) { $docker_compose['services'][$container_name]['volumes'] = $persistent_storages; } + if (count($persistent_file_volumes) > 0) { + $docker_compose['services'][$container_name]['volumes'] = $persistent_file_volumes->map(function ($item) { + return "$item->fs_path:$item->mount_path"; + })->toArray(); + } if (count($volume_names) > 0) { $docker_compose['volumes'] = $volume_names; } diff --git a/app/Actions/Database/StartMysql.php b/app/Actions/Database/StartMysql.php index 6fdc8cdad..604e72fde 100644 --- a/app/Actions/Database/StartMysql.php +++ b/app/Actions/Database/StartMysql.php @@ -28,6 +28,7 @@ class StartMysql ]; $persistent_storages = $this->generate_local_persistent_volumes(); + $persistent_file_volumes = $this->database->fileStorages()->get(); $volume_names = $this->generate_local_persistent_volumes_only_volume_names(); $environment_variables = $this->generate_environment_variables(); $this->add_custom_mysql(); @@ -86,6 +87,11 @@ class StartMysql if (count($persistent_storages) > 0) { $docker_compose['services'][$container_name]['volumes'] = $persistent_storages; } + if (count($persistent_file_volumes) > 0) { + $docker_compose['services'][$container_name]['volumes'] = $persistent_file_volumes->map(function ($item) { + return "$item->fs_path:$item->mount_path"; + })->toArray(); + } if (count($volume_names) > 0) { $docker_compose['volumes'] = $volume_names; } diff --git a/app/Actions/Database/StartPostgresql.php b/app/Actions/Database/StartPostgresql.php index 8db874ea6..554e347d9 100644 --- a/app/Actions/Database/StartPostgresql.php +++ b/app/Actions/Database/StartPostgresql.php @@ -29,6 +29,7 @@ class StartPostgresql ]; $persistent_storages = $this->generate_local_persistent_volumes(); + $persistent_file_volumes = $this->database->fileStorages()->get(); $volume_names = $this->generate_local_persistent_volumes_only_volume_names(); $environment_variables = $this->generate_environment_variables(); $this->generate_init_scripts(); @@ -92,6 +93,11 @@ class StartPostgresql if (count($persistent_storages) > 0) { $docker_compose['services'][$container_name]['volumes'] = $persistent_storages; } + if (count($persistent_file_volumes) > 0) { + $docker_compose['services'][$container_name]['volumes'] = $persistent_file_volumes->map(function ($item) { + return "$item->fs_path:$item->mount_path"; + })->toArray(); + } if (count($volume_names) > 0) { $docker_compose['volumes'] = $volume_names; } diff --git a/app/Actions/Database/StartRedis.php b/app/Actions/Database/StartRedis.php index 5b6ab2999..055d82600 100644 --- a/app/Actions/Database/StartRedis.php +++ b/app/Actions/Database/StartRedis.php @@ -32,6 +32,7 @@ class StartRedis ]; $persistent_storages = $this->generate_local_persistent_volumes(); + $persistent_file_volumes = $this->database->fileStorages()->get(); $volume_names = $this->generate_local_persistent_volumes_only_volume_names(); $environment_variables = $this->generate_environment_variables(); $this->add_custom_redis(); @@ -96,6 +97,11 @@ class StartRedis if (count($persistent_storages) > 0) { $docker_compose['services'][$container_name]['volumes'] = $persistent_storages; } + if (count($persistent_file_volumes) > 0) { + $docker_compose['services'][$container_name]['volumes'] = $persistent_file_volumes->map(function ($item) { + return "$item->fs_path:$item->mount_path"; + })->toArray(); + } if (count($volume_names) > 0) { $docker_compose['volumes'] = $volume_names; } diff --git a/app/Actions/Docker/GetContainersStatus.php b/app/Actions/Docker/GetContainersStatus.php index f0e1de8f6..a8a9185ba 100644 --- a/app/Actions/Docker/GetContainersStatus.php +++ b/app/Actions/Docker/GetContainersStatus.php @@ -221,7 +221,19 @@ class GetContainersStatus } $name = data_get($exitedService, 'name'); $fqdn = data_get($exitedService, 'fqdn'); - $containerName = $name ? "$name, available at $fqdn" : $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'); @@ -231,7 +243,7 @@ class GetContainersStatus } else { $url = null; } - $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url)); + // $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url)); $exitedService->update(['status' => 'exited']); } @@ -258,7 +270,7 @@ class GetContainersStatus $url = null; } - $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url)); + // $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url)); } $notRunningApplicationPreviews = $previews->pluck('id')->diff($foundApplicationPreviews); foreach ($notRunningApplicationPreviews as $previewId) { @@ -283,7 +295,7 @@ class GetContainersStatus $url = null; } - $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url)); + // $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url)); } $notRunningDatabases = $databases->pluck('id')->diff($foundDatabases); foreach ($notRunningDatabases as $database) { @@ -307,7 +319,7 @@ class GetContainersStatus } else { $url = null; } - $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url)); + // $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url)); } // Check if proxy is running @@ -539,7 +551,19 @@ class GetContainersStatus } $name = data_get($exitedService, 'name'); $fqdn = data_get($exitedService, 'fqdn'); - $containerName = $name ? "$name, available at $fqdn" : $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'); @@ -549,7 +573,7 @@ class GetContainersStatus } else { $url = null; } - $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url)); + // $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url)); $exitedService->update(['status' => 'exited']); } @@ -576,7 +600,7 @@ class GetContainersStatus $url = null; } - $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url)); + // $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url)); } $notRunningApplicationPreviews = $previews->pluck('id')->diff($foundApplicationPreviews); foreach ($notRunningApplicationPreviews as $previewId) { @@ -601,7 +625,7 @@ class GetContainersStatus $url = null; } - $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url)); + // $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url)); } $notRunningDatabases = $databases->pluck('id')->diff($foundDatabases); foreach ($notRunningDatabases as $database) { @@ -625,7 +649,7 @@ class GetContainersStatus } else { $url = null; } - $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url)); + // $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url)); } // Check if proxy is running diff --git a/app/Actions/Proxy/CheckProxy.php b/app/Actions/Proxy/CheckProxy.php index 9323ca1d1..b1fb6a3cb 100644 --- a/app/Actions/Proxy/CheckProxy.php +++ b/app/Actions/Proxy/CheckProxy.php @@ -10,7 +10,18 @@ class CheckProxy use AsAction; public function handle(Server $server, $fromUI = false) { - if ($server->proxyType() === 'NONE') { + if (!$server->isFunctional()) { + return false; + } + if ($server->isBuildServer()) { + if ($server->proxy) { + $server->proxy = null; + $server->save(); + } + return false; + } + $proxyType = $server->proxyType(); + if (is_null($proxyType) || $proxyType === 'NONE' || $server->proxy->force_stop) { return false; } ['uptime' => $uptime, 'error' => $error] = $server->validateConnection(); diff --git a/app/Actions/Proxy/StartProxy.php b/app/Actions/Proxy/StartProxy.php index 5b1ffa409..92a5e5b56 100644 --- a/app/Actions/Proxy/StartProxy.php +++ b/app/Actions/Proxy/StartProxy.php @@ -15,7 +15,7 @@ class StartProxy { try { $proxyType = $server->proxyType(); - if (is_null($proxyType) || $proxyType === 'NONE') { + if (is_null($proxyType) || $proxyType === 'NONE' || $server->proxy->force_stop || $server->isBuildServer()) { return 'OK'; } $commands = collect([]); diff --git a/app/Actions/Server/UpdateCoolify.php b/app/Actions/Server/UpdateCoolify.php index ac548a951..c992ed786 100644 --- a/app/Actions/Server/UpdateCoolify.php +++ b/app/Actions/Server/UpdateCoolify.php @@ -12,12 +12,10 @@ class UpdateCoolify public ?Server $server = null; public ?string $latestVersion = null; public ?string $currentVersion = null; - public bool $async = false; - public function handle(bool $force = false, bool $async = false) + public function handle($manual_update = false) { try { - $this->async = $async; $settings = InstanceSettings::get(); ray('Running InstanceAutoUpdateJob'); $this->server = Server::find(0); @@ -27,24 +25,18 @@ class UpdateCoolify CleanupDocker::run($this->server, false); $this->latestVersion = get_latest_version_of_coolify(); $this->currentVersion = config('version'); - // if ($settings->next_channel) { - // ray('next channel enabled'); - // $this->latestVersion = 'next'; - // } - if ($force) { - $this->update(); - } else { + if (!$manual_update) { if (!$settings->is_auto_update_enabled) { - return 'Auto update is disabled'; + return; } if ($this->latestVersion === $this->currentVersion) { - return 'Already on latest version'; + return; } if (version_compare($this->latestVersion, $this->currentVersion, '<')) { - return 'Latest version is lower than current version?!'; + return; } - $this->update(); } + $this->update(); } catch (\Throwable $e) { ray('InstanceAutoUpdateJob failed'); ray($e->getMessage()); @@ -56,34 +48,16 @@ class UpdateCoolify private function update() { if (isDev()) { - ray("Running update on local docker container. Updating to $this->latestVersion"); - if ($this->async) { - ray('Running async update'); - remote_process([ - "sleep 10" - ], $this->server); - } else { - instant_remote_process([ - "sleep 10" - ], $this->server); - } - ray('Update done'); - return; - } else { - ray('Running update on production server'); - if ($this->async) { - remote_process([ - "curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh -o /data/coolify/source/upgrade.sh", - "bash /data/coolify/source/upgrade.sh $this->latestVersion" - ], $this->server); - } else { - instant_remote_process([ - "curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh -o /data/coolify/source/upgrade.sh", - "bash /data/coolify/source/upgrade.sh $this->latestVersion" - ], $this->server); - } - send_internal_notification("Instance updated from {$this->currentVersion} -> {$this->latestVersion}"); + remote_process([ + "sleep 10" + ], $this->server); return; } + remote_process([ + "curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh -o /data/coolify/source/upgrade.sh", + "bash /data/coolify/source/upgrade.sh $this->latestVersion" + ], $this->server); + send_internal_notification("Instance updated from {$this->currentVersion} -> {$this->latestVersion}"); + return; } } diff --git a/app/Console/Commands/ServicesGenerate.php b/app/Console/Commands/ServicesGenerate.php index 7619a1d85..d96d4743c 100644 --- a/app/Console/Commands/ServicesGenerate.php +++ b/app/Console/Commands/ServicesGenerate.php @@ -40,7 +40,7 @@ class ServicesGenerate extends Command $serviceTemplatesJson[$name] = $parsed; } } - $serviceTemplatesJson = json_encode($serviceTemplatesJson, JSON_PRETTY_PRINT); + $serviceTemplatesJson = json_encode($serviceTemplatesJson); file_put_contents(base_path('templates/service-templates.json'), $serviceTemplatesJson); } diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 23289f90e..ab8794877 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -10,6 +10,9 @@ use App\Jobs\InstanceAutoUpdateJob; use App\Jobs\ContainerStatusJob; use App\Jobs\PullHelperImageJob; use App\Jobs\PullSentinelImageJob; +use App\Jobs\PullTemplatesAndVersions; +use App\Jobs\PullTemplatesFromCDN; +use App\Jobs\PullVersionsFromCDN; use App\Jobs\ServerStatusJob; use App\Models\InstanceSettings; use App\Models\ScheduledDatabaseBackup; @@ -29,6 +32,8 @@ class Kernel extends ConsoleKernel // Instance Jobs $schedule->command('horizon:snapshot')->everyMinute(); $schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer(); + $schedule->job(new PullVersionsFromCDN)->everyTenMinutes()->onOneServer(); + $schedule->job(new PullTemplatesFromCDN)->everyTwoHours()->onOneServer(); // $schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer(); // Server Jobs $this->check_scheduled_backups($schedule); @@ -41,7 +46,8 @@ class Kernel extends ConsoleKernel // Instance Jobs $schedule->command('horizon:snapshot')->everyFiveMinutes(); $schedule->command('cleanup:unreachable-servers')->daily(); - + $schedule->job(new PullVersionsFromCDN)->everyTenMinutes()->onOneServer(); + $schedule->job(new PullTemplatesFromCDN)->everyTwoHours()->onOneServer(); $schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer(); // $schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer(); diff --git a/app/Http/Controllers/Webhook/Bitbucket.php b/app/Http/Controllers/Webhook/Bitbucket.php index 7b569c278..1fc7ea453 100644 --- a/app/Http/Controllers/Webhook/Bitbucket.php +++ b/app/Http/Controllers/Webhook/Bitbucket.php @@ -82,7 +82,7 @@ class Bitbucket extends Controller $return_payloads->push([ 'application' => $application->name, 'status' => 'failed', - 'message' => 'Invalid token.', + 'message' => 'Invalid signature.', ]); ray('Invalid signature'); continue; diff --git a/app/Http/Controllers/Webhook/Gitea.php b/app/Http/Controllers/Webhook/Gitea.php new file mode 100644 index 000000000..775e2c17e --- /dev/null +++ b/app/Http/Controllers/Webhook/Gitea.php @@ -0,0 +1,222 @@ +header('X-Gitea-Delivery'); + if (app()->isDownForMaintenance()) { + ray('Maintenance mode is on'); + $epoch = now()->valueOf(); + $files = Storage::disk('webhooks-during-maintenance')->files(); + $gitea_delivery_found = collect($files)->filter(function ($file) use ($x_gitea_delivery) { + return Str::contains($file, $x_gitea_delivery); + })->first(); + if ($gitea_delivery_found) { + ray('Webhook already found'); + return; + } + $data = [ + 'attributes' => $request->attributes->all(), + 'request' => $request->request->all(), + 'query' => $request->query->all(), + 'server' => $request->server->all(), + 'files' => $request->files->all(), + 'cookies' => $request->cookies->all(), + 'headers' => $request->headers->all(), + 'content' => $request->getContent(), + ]; + $json = json_encode($data); + Storage::disk('webhooks-during-maintenance')->put("{$epoch}_Gitea::manual_{$x_gitea_delivery}", $json); + return; + } + $x_gitea_event = Str::lower($request->header('X-Gitea-Event')); + $x_hub_signature_256 = Str::after($request->header('X-Hub-Signature-256'), 'sha256='); + $content_type = $request->header('Content-Type'); + $payload = $request->collect(); + if ($x_gitea_event === 'ping') { + // Just pong + return response('pong'); + } + + if ($content_type !== 'application/json') { + $payload = json_decode(data_get($payload, 'payload'), true); + } + if ($x_gitea_event === 'push') { + $branch = data_get($payload, 'ref'); + $full_name = data_get($payload, 'repository.full_name'); + if (Str::isMatch('/refs\/heads\/*/', $branch)) { + $branch = Str::after($branch, 'refs/heads/'); + } + $added_files = data_get($payload, 'commits.*.added'); + $removed_files = data_get($payload, 'commits.*.removed'); + $modified_files = data_get($payload, 'commits.*.modified'); + $changed_files = collect($added_files)->concat($removed_files)->concat($modified_files)->unique()->flatten(); + ray($changed_files); + ray('Manual Webhook Gitea Push Event with branch: ' . $branch); + } + if ($x_gitea_event === 'pull_request') { + $action = data_get($payload, 'action'); + $full_name = data_get($payload, 'repository.full_name'); + $pull_request_id = data_get($payload, 'number'); + $pull_request_html_url = data_get($payload, 'pull_request.html_url'); + $branch = data_get($payload, 'pull_request.head.ref'); + $base_branch = data_get($payload, 'pull_request.base.ref'); + ray('Webhook Gitea Pull Request Event with branch: ' . $branch . ' and base branch: ' . $base_branch . ' and pull request id: ' . $pull_request_id); + } + if (!$branch) { + return response('Nothing to do. No branch found in the request.'); + } + $applications = Application::where('git_repository', 'like', "%$full_name%"); + if ($x_gitea_event === 'push') { + $applications = $applications->where('git_branch', $branch)->get(); + if ($applications->isEmpty()) { + return response("Nothing to do. No applications found with deploy key set, branch is '$branch' and Git Repository name has $full_name."); + } + } + if ($x_gitea_event === 'pull_request') { + $applications = $applications->where('git_branch', $base_branch)->get(); + if ($applications->isEmpty()) { + return response("Nothing to do. No applications found with branch '$base_branch'."); + } + } + foreach ($applications as $application) { + $webhook_secret = data_get($application, 'manual_webhook_secret_gitea'); + $hmac = hash_hmac('sha256', $request->getContent(), $webhook_secret); + if (!hash_equals($x_hub_signature_256, $hmac) && !isDev()) { + ray('Invalid signature'); + $return_payloads->push([ + 'application' => $application->name, + 'status' => 'failed', + 'message' => 'Invalid signature.', + ]); + continue; + } + $isFunctional = $application->destination->server->isFunctional(); + if (!$isFunctional) { + $return_payloads->push([ + 'application' => $application->name, + 'status' => 'failed', + 'message' => 'Server is not functional.', + ]); + continue; + } + if ($x_gitea_event === 'push') { + if ($application->isDeployable()) { + $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); + queue_application_deployment( + application: $application, + deployment_uuid: $deployment_uuid, + force_rebuild: false, + commit: data_get($payload, 'after', 'HEAD'), + is_webhook: true, + ); + $return_payloads->push([ + 'status' => 'success', + 'message' => 'Deployment queued.', + 'application_uuid' => $application->uuid, + 'application_name' => $application->name, + ]); + } else { + $paths = str($application->watch_paths)->explode("\n"); + $return_payloads->push([ + 'status' => 'failed', + 'message' => 'Changed files do not match watch paths. Ignoring deployment.', + 'application_uuid' => $application->uuid, + 'application_name' => $application->name, + 'details' => [ + 'changed_files' => $changed_files, + 'watch_paths' => $paths, + ], + ]); + } + } else { + $return_payloads->push([ + 'status' => 'failed', + 'message' => 'Deployments disabled.', + 'application_uuid' => $application->uuid, + 'application_name' => $application->name, + ]); + } + } + if ($x_gitea_event === 'pull_request') { + if ($action === 'opened' || $action === 'synchronize' || $action === 'reopened') { + if ($application->isPRDeployable()) { + $deployment_uuid = new Cuid2(7); + $found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first(); + if (!$found) { + ApplicationPreview::create([ + 'git_type' => 'gitea', + 'application_id' => $application->id, + 'pull_request_id' => $pull_request_id, + 'pull_request_html_url' => $pull_request_html_url, + ]); + } + queue_application_deployment( + application: $application, + pull_request_id: $pull_request_id, + deployment_uuid: $deployment_uuid, + force_rebuild: false, + commit: data_get($payload, 'head.sha', 'HEAD'), + is_webhook: true, + git_type: 'gitea' + ); + $return_payloads->push([ + 'application' => $application->name, + 'status' => 'success', + 'message' => 'Preview deployment queued.', + ]); + } else { + $return_payloads->push([ + 'application' => $application->name, + 'status' => 'failed', + 'message' => 'Preview deployments disabled.', + ]); + } + } + if ($action === 'closed') { + $found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first(); + if ($found) { + $found->delete(); + $container_name = generateApplicationContainerName($application, $pull_request_id); + // ray('Stopping container: ' . $container_name); + instant_remote_process(["docker rm -f $container_name"], $application->destination->server); + $return_payloads->push([ + 'application' => $application->name, + 'status' => 'success', + 'message' => 'Preview deployment closed.', + ]); + } else { + $return_payloads->push([ + 'application' => $application->name, + 'status' => 'failed', + 'message' => 'No preview deployment found.', + ]); + } + } + } + } + ray($return_payloads); + return response($return_payloads); + } catch (Exception $e) { + ray($e->getMessage()); + return handleError($e); + } + } +} diff --git a/app/Http/Controllers/Webhook/Github.php b/app/Http/Controllers/Webhook/Github.php index baa23deec..bddfaff92 100644 --- a/app/Http/Controllers/Webhook/Github.php +++ b/app/Http/Controllers/Webhook/Github.php @@ -106,7 +106,7 @@ class Github extends Controller $return_payloads->push([ 'application' => $application->name, 'status' => 'failed', - 'message' => 'Invalid token.', + 'message' => 'Invalid signature.', ]); continue; } diff --git a/app/Http/Controllers/Webhook/Gitlab.php b/app/Http/Controllers/Webhook/Gitlab.php index dfa9394eb..a36929781 100644 --- a/app/Http/Controllers/Webhook/Gitlab.php +++ b/app/Http/Controllers/Webhook/Gitlab.php @@ -109,7 +109,7 @@ class Gitlab extends Controller $return_payloads->push([ 'application' => $application->name, 'status' => 'failed', - 'message' => 'Invalid token.', + 'message' => 'Invalid signature.', ]); ray('Invalid signature'); continue; diff --git a/app/Http/Controllers/Webhook/Stripe.php b/app/Http/Controllers/Webhook/Stripe.php index 7d6721252..200d3dd1c 100644 --- a/app/Http/Controllers/Webhook/Stripe.php +++ b/app/Http/Controllers/Webhook/Stripe.php @@ -150,6 +150,10 @@ class Stripe extends Controller $subscription = Subscription::where('stripe_customer_id', $customerId)->first(); } if (!$subscription) { + if ($status === 'incomplete_expired') { + send_internal_notification('Subscription incomplete expired for customer: ' . $customerId); + return response("Subscription incomplete expired", 200); + } send_internal_notification('No subscription found for: ' . $customerId); return response("No subscription found", 400); } @@ -166,9 +170,11 @@ class Stripe extends Controller $quantity = data_get($data, 'items.data.0.quantity', 10); } $team = data_get($subscription, 'team'); - $team->update([ - 'custom_server_limit' => $quantity, - ]); + if ($team) { + $team->update([ + 'custom_server_limit' => $quantity, + ]); + } ServerLimitCheckJob::dispatch($team); } $subscription->update([ @@ -210,7 +216,9 @@ class Stripe extends Controller $customerId = data_get($data, 'customer'); $subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail(); $team = data_get($subscription, 'team'); - $team->trialEnded(); + if ($team) { + $team->trialEnded(); + } $subscription->update([ 'stripe_subscription_id' => null, 'stripe_plan_id' => null, diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 47319ac10..e60bfac90 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -67,6 +67,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted // Save original server between phases private Server $original_server; private Server $mainServer; + private bool $is_this_additional_server = false; private ?ApplicationPreview $preview = null; private ?string $git_type = null; private bool $only_this_server = false; @@ -112,6 +113,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted public $tries = 1; public function __construct(int $application_deployment_queue_id) { + ray()->clearAll(); $this->application_deployment_queue = ApplicationDeploymentQueue::find($application_deployment_queue_id); $this->application = Application::find($this->application_deployment_queue->application_id); $this->build_pack = data_get($this->application, 'build_pack'); @@ -123,6 +125,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted $this->rollback = $this->application_deployment_queue->rollback; $this->force_rebuild = $this->application_deployment_queue->force_rebuild; $this->restart_only = $this->application_deployment_queue->restart_only; + $this->restart_only = $this->restart_only && $this->application->build_pack !== 'dockerimage' && $this->application->build_pack !== 'dockerfile'; $this->only_this_server = $this->application_deployment_queue->only_this_server; $this->git_type = data_get($this->application_deployment_queue, 'git_type'); @@ -136,6 +139,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted $this->destination = $this->server->destinations()->where('id', $this->application_deployment_queue->destination_id)->first(); $this->server = $this->mainServer = $this->destination->server; $this->serverUser = $this->server->user; + $this->is_this_additional_server = $this->application->additional_servers()->wherePivot('server_id', $this->server->id)->count() > 0; + $this->basedir = $this->application->generateBaseDir($this->deployment_uuid); $this->workdir = "{$this->basedir}" . rtrim($this->application->base_directory, '/'); $this->configuration_dir = application_configuration_dir() . "/{$this->application->uuid}"; @@ -149,28 +154,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted // Set preview fqdn if ($this->pull_request_id !== 0) { - $this->preview = ApplicationPreview::findPreviewByApplicationAndPullId($this->application->id, $this->pull_request_id); - if ($this->application->fqdn) { - if (str($this->application->fqdn)->contains(',')) { - $url = Url::fromString(str($this->application->fqdn)->explode(',')[0]); - $preview_fqdn = getFqdnWithoutPort(str($this->application->fqdn)->explode(',')[0]); - } else { - $url = Url::fromString($this->application->fqdn); - if (data_get($this->preview, 'fqdn')) { - $preview_fqdn = getFqdnWithoutPort(data_get($this->preview, 'fqdn')); - } - } - $template = $this->application->preview_url_template; - $host = $url->getHost(); - $schema = $url->getScheme(); - $random = new Cuid2(7); - $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); - $preview_fqdn = "$schema://$preview_fqdn"; - $this->preview->fqdn = $preview_fqdn; - $this->preview->save(); - } + $this->preview = $this->application->generate_preview_fqdn($this->pull_request_id); if ($this->application->is_github_based()) { ApplicationPullRequestUpdateJob::dispatch(application: $this->application, preview: $this->preview, deployment_uuid: $this->deployment_uuid, status: ProcessStatus::IN_PROGRESS); } @@ -184,6 +168,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted public function handle(): void { + $this->application_deployment_queue->update([ + 'status' => ApplicationDeploymentStatus::IN_PROGRESS->value, + ]); if (!$this->server->isFunctional()) { $this->application_deployment_queue->addLogEntry("Server is not functional."); $this->fail("Server is not functional."); @@ -281,7 +268,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted } private function decide_what_to_do() { - if ($this->restart_only && $this->application->build_pack !== 'dockerimage' && $this->application->build_pack !== 'dockerfile') { + if ($this->restart_only) { $this->just_restart(); return; } else if ($this->pull_request_id !== 0) { @@ -331,18 +318,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted ], ); $this->generate_image_names(); - - // Always rebuild dockerfile based container. - // if (!$this->force_rebuild) { - // $this->check_image_locally_or_remotely(); - // if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) { - // $this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped."); - // $this->generate_compose_file(); - // $this->push_to_docker_registry(); - // $this->rolling_update(); - // return; - // } - // } $this->generate_compose_file(); $this->generate_build_env_variables(); $this->add_build_env_variables_to_dockerfile(); @@ -390,15 +365,29 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted if ($this->application->settings->is_raw_compose_deployment_enabled) { $this->application->parseRawCompose(); $yaml = $composeFile = $this->application->docker_compose_raw; + $this->save_environment_variables(); } else { $composeFile = $this->application->parseCompose(pull_request_id: $this->pull_request_id); + $this->save_environment_variables(); + if (!is_null($this->env_filename)) { + $services = collect($composeFile['services']); + $services = $services->map(function ($service, $name) { + $service['env_file'] = [$this->env_filename]; + return $service; + }); + $composeFile['services'] = $services->toArray(); + } + if (is_null($composeFile)) { + $this->application_deployment_queue->addLogEntry("Failed to parse docker-compose file."); + $this->fail("Failed to parse docker-compose file."); + return; + } $yaml = Yaml::dump($composeFile->toArray(), 10); } $this->docker_compose_base64 = base64_encode($yaml); $this->execute_remote_command([ executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d | tee {$this->workdir}{$this->docker_compose_location} > /dev/null"), "hidden" => true ]); - $this->save_environment_variables(); // Build new container to limit downtime. $this->application_deployment_queue->addLogEntry("Pulling & building required images."); @@ -407,13 +396,18 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted [executeInDocker($this->deployment_uuid, "cd {$this->basedir} && {$this->docker_compose_custom_build_command}"), "hidden" => true], ); } else { + $command = "{$this->coolify_variables} docker compose"; + if ($this->env_filename) { + $command .= " --env-file {$this->workdir}/{$this->env_filename}"; + } + $command .= " --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build"; $this->execute_remote_command( - [executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build"), "hidden" => true], + [executeInDocker($this->deployment_uuid, $command), "hidden" => true], ); } $this->stop_running_container(force: true); - + $this->application_deployment_queue->addLogEntry("Starting new application."); $networkId = $this->application->uuid; if ($this->pull_request_id !== 0) { $networkId = "{$this->application->uuid}-{$this->pull_request_id}"; @@ -422,7 +416,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted // TODO } else { $this->execute_remote_command([ - "docker network create --attachable '{$networkId}' >/dev/null || true", "hidden" => true, "ignore_errors" => true + "docker network inspect '{$networkId}' >/dev/null 2>&1 || docker network create --attachable '{$networkId}' >/dev/null || true", "hidden" => true, "ignore_errors" => true ], [ "docker network connect {$networkId} coolify-proxy || true", "hidden" => true, "ignore_errors" => true ]); @@ -438,9 +432,15 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted } else { $this->write_deployment_configurations(); $server_workdir = $this->application->workdir(); - ray("{$this->coolify_variables} docker compose --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d"); + + $command = "{$this->coolify_variables} docker compose"; + if ($this->env_filename) { + $command .= " --env-file {$this->workdir}/{$this->env_filename}"; + } + $command .= " --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d"; + $this->execute_remote_command( - ["{$this->coolify_variables} docker compose --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d", "hidden" => true], + ["command" => $command, "hidden" => true], ); } } else { @@ -450,8 +450,13 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted ); $this->write_deployment_configurations(); } else { + $command = "{$this->coolify_variables} docker compose"; + if ($this->env_filename) { + $command .= " --env-file {$this->workdir}/{$this->env_filename}"; + } + $command .= " --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up -d"; $this->execute_remote_command( - [executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up -d"), "hidden" => true], + [executeInDocker($this->deployment_uuid, $command), "hidden" => true], ); $this->write_deployment_configurations(); } @@ -470,16 +475,11 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted } $this->prepare_builder_image(); $this->check_git_if_build_needed(); - $this->set_base_dir(); $this->generate_image_names(); $this->clone_repository(); if (!$this->force_rebuild) { $this->check_image_locally_or_remotely(); - if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) { - $this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped."); - $this->generate_compose_file(); - $this->push_to_docker_registry(); - $this->rolling_update(); + if ($this->should_skip_build()) { return; } } @@ -499,21 +499,12 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted $this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch} to {$this->server->name}."); $this->prepare_builder_image(); $this->check_git_if_build_needed(); - $this->set_base_dir(); $this->generate_image_names(); if (!$this->force_rebuild) { $this->check_image_locally_or_remotely(); - if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) { - $this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped."); - $this->generate_compose_file(); - ray('pushing to docker registry'); - $this->push_to_docker_registry(); - $this->rolling_update(); + if ($this->should_skip_build()) { return; } - if ($this->application->isConfigurationChanged()) { - $this->application_deployment_queue->addLogEntry("Configuration changed. Rebuilding image."); - } } $this->clone_repository(); $this->cleanup_git(); @@ -532,15 +523,10 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted $this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch} to {$this->server->name}."); $this->prepare_builder_image(); $this->check_git_if_build_needed(); - $this->set_base_dir(); $this->generate_image_names(); if (!$this->force_rebuild) { $this->check_image_locally_or_remotely(); - if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) { - $this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped."); - $this->generate_compose_file(); - $this->push_to_docker_registry(); - $this->rolling_update(); + if ($this->should_skip_build()) { return; } } @@ -608,7 +594,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted ray('additional_servers'); $forceFail = true; } - if ($this->application->additional_servers()->wherePivot('server_id', $this->server->id)->count() > 0) { + if ($this->is_this_additional_server) { ray('this is an additional_servers, no pushy pushy'); return; } @@ -623,8 +609,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted ], ); if ($this->application->docker_registry_image_tag) { - // Tag image with latest - $this->application_deployment_queue->addLogEntry("Tagging and pushing image with latest tag."); + // Tag image with docker_registry_image_tag + $this->application_deployment_queue->addLogEntry("Tagging and pushing image with {$this->application->docker_registry_image_tag} tag."); $this->execute_remote_command( [ executeInDocker($this->deployment_uuid, "docker tag {$this->production_image_name} {$this->application->docker_registry_image_name}:{$this->application->docker_registry_image_tag}"), 'ignore_errors' => true, 'hidden' => true @@ -634,7 +620,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted ], ); } - $this->application_deployment_queue->addLogEntry("Image pushed to docker registry."); } catch (Exception $e) { $this->application_deployment_queue->addLogEntry("Failed to push image to docker registry. Please check debug logs for more information."); if ($forceFail) { @@ -665,9 +650,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted } } else { $this->dockerImageTag = str($this->commit)->substr(0, 128); - if ($this->application->docker_registry_image_tag) { - $this->dockerImageTag = $this->application->docker_registry_image_tag; - } + // if ($this->application->docker_registry_image_tag) { + // $this->dockerImageTag = $this->application->docker_registry_image_tag; + // } if ($this->application->docker_registry_image_name) { $this->build_image_name = "{$this->application->docker_registry_image_name}:{$this->dockerImageTag}-build"; $this->production_image_name = "{$this->application->docker_registry_image_name}:{$this->dockerImageTag}"; @@ -682,19 +667,42 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted $this->application_deployment_queue->addLogEntry("Restarting {$this->customRepository}:{$this->application->git_branch} on {$this->server->name}."); $this->prepare_builder_image(); $this->check_git_if_build_needed(); - $this->set_base_dir(); $this->generate_image_names(); $this->check_image_locally_or_remotely(); + if ($this->should_skip_build()) { + return; + } + } + private function should_skip_build() + { if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty()) { - $this->application_deployment_queue->addLogEntry("Image found ({$this->production_image_name}) with the same Git Commit SHA. Restarting container."); - $this->generate_compose_file(); - $this->rolling_update(); - $this->post_deployment(); + if ($this->is_this_additional_server) { + $this->application_deployment_queue->addLogEntry("Image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped."); + $this->generate_compose_file(); + $this->push_to_docker_registry(); + $this->rolling_update(); + if ($this->restart_only) { + $this->post_deployment(); + } + return true; + } + if (!$this->application->isConfigurationChanged()) { + $this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped."); + $this->generate_compose_file(); + $this->push_to_docker_registry(); + $this->rolling_update(); + return true; + } else { + $this->application_deployment_queue->addLogEntry("Configuration changed. Rebuilding image."); + } } else { - $this->application_deployment_queue->addLogEntry("Image not found ({$this->production_image_name}). Redeploying the application."); + $this->application_deployment_queue->addLogEntry("Image not found ({$this->production_image_name}). Building new image."); + } + if ($this->restart_only) { $this->restart_only = false; $this->decide_what_to_do(); } + return false; } private function check_image_locally_or_remotely() { @@ -814,16 +822,29 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted $this->env_filename = null; if ($this->use_build_server) { $this->server = $this->original_server; - } - $this->execute_remote_command( - [ - "command" => "rm -f $this->configuration_dir/{$this->env_filename}", - "hidden" => true, - "ignore_errors" => true - ] - ); - if ($this->use_build_server) { + $this->execute_remote_command( + [ + "command" => "rm -f $this->configuration_dir/{$this->env_filename}", + "hidden" => true, + "ignore_errors" => true + ] + ); $this->server = $this->build_server; + $this->execute_remote_command( + [ + "command" => "rm -f $this->configuration_dir/{$this->env_filename}", + "hidden" => true, + "ignore_errors" => true + ] + ); + } else { + $this->execute_remote_command( + [ + "command" => "rm -f $this->configuration_dir/{$this->env_filename}", + "hidden" => true, + "ignore_errors" => true + ] + ); } } else { $envs_base64 = base64_encode($envs->implode("\n")); @@ -835,38 +856,20 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted ); if ($this->use_build_server) { $this->server = $this->original_server; - } - $this->execute_remote_command( - [ - "echo '$envs_base64' | base64 -d | tee $this->configuration_dir/{$this->env_filename} > /dev/null" - ] - ); - if ($this->use_build_server) { + $this->execute_remote_command( + [ + "echo '$envs_base64' | base64 -d | tee $this->configuration_dir/{$this->env_filename} > /dev/null" + ] + ); $this->server = $this->build_server; + } else { + $this->execute_remote_command( + [ + "echo '$envs_base64' | base64 -d | tee $this->configuration_dir/{$this->env_filename} > /dev/null" + ] + ); } } - // $this->execute_remote_command([ - // executeInDocker($this->deployment_uuid, "cat $this->workdir/.env 2>/dev/null || true"), - // "hidden" => true, - // "save" => "dotenv" - // ]); - // if (str($this->saved_outputs->get('dotenv'))->isNotEmpty()) { - // $base64_dotenv = base64_encode($this->saved_outputs->get('dotenv')->value()); - // $this->execute_remote_command( - // [ - // "echo '{$base64_dotenv}' | base64 -d | tee $this->configuration_dir/.env > /dev/null" - // ] - // ); - // } else { - // $this->execute_remote_command( - // [ - // "command" => "rm -f $this->configuration_dir/.env", - // "hidden" => true, - // "ignore_errors" => true - // ] - // ); - // } - } @@ -988,6 +991,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted } if (Str::of($this->saved_outputs->get('health_check'))->replace('"', '')->value() === 'unhealthy') { $this->newVersionIsHealthy = false; + $this->query_logs(); break; } $counter++; @@ -997,9 +1001,25 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted $sleeptime++; } } + if (Str::of($this->saved_outputs->get('health_check'))->replace('"', '')->value() === 'starting') { + $this->query_logs(); + } } } } + private function query_logs() + { + $this->application_deployment_queue->addLogEntry("----------------------------------------"); + $this->application_deployment_queue->addLogEntry("Container logs:"); + $this->execute_remote_command( + [ + "command" => "docker logs -n 100 {$this->container_name}", + "type" => "stderr", + "ignore_errors" => true, + ], + ); + $this->application_deployment_queue->addLogEntry("----------------------------------------"); + } private function deploy_pull_request() { if ($this->application->build_pack === 'dockercompose') { @@ -1015,7 +1035,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted $this->prepare_builder_image(); $this->check_git_if_build_needed(); $this->clone_repository(); - $this->set_base_dir(); $this->cleanup_git(); if ($this->application->build_pack === 'nixpacks') { $this->generate_nixpacks_confs(); @@ -1032,14 +1051,32 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted } private function create_workdir() { - $this->execute_remote_command( - [ - "command" => executeInDocker($this->deployment_uuid, "mkdir -p {$this->workdir}") - ], - [ - "command" => "mkdir -p {$this->configuration_dir}" - ], - ); + if ($this->use_build_server) { + $this->server = $this->original_server; + $this->execute_remote_command( + [ + "command" => "mkdir -p {$this->configuration_dir}" + ], + ); + $this->server = $this->build_server; + $this->execute_remote_command( + [ + "command" => executeInDocker($this->deployment_uuid, "mkdir -p {$this->workdir}") + ], + [ + "command" => "mkdir -p {$this->configuration_dir}" + ], + ); + } else { + $this->execute_remote_command( + [ + "command" => executeInDocker($this->deployment_uuid, "mkdir -p {$this->workdir}") + ], + [ + "command" => "mkdir -p {$this->configuration_dir}" + ], + ); + } } private function prepare_builder_image() { @@ -1119,10 +1156,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted ])); } } - private function set_base_dir() - { - $this->application_deployment_queue->addLogEntry("Setting base directory to {$this->workdir}."); - } private function set_coolify_variables() { $this->coolify_variables = "SOURCE_COMMIT={$this->commit} "; @@ -1175,7 +1208,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted ], ); } - ray("GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$this->customPort} -o Port={$this->customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null\" git ls-remote {$this->fullRepoUrl} {$local_branch}"); if ($this->saved_outputs->get('git_commit_sha') && !$this->rollback) { $this->commit = $this->saved_outputs->get('git_commit_sha')->before("\t"); $this->application_deployment_queue->commit = $this->commit; @@ -1191,7 +1223,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted if ($this->pull_request_id !== 0) { $this->application_deployment_queue->addLogEntry("Checking out tag pull/{$this->pull_request_id}/head."); } - ray($importCommands); $this->execute_remote_command( [ $importCommands, "hidden" => true @@ -1205,8 +1236,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted "save" => "commit_message" ] ); - ray($this->saved_outputs->get('commit_message')); - raY($this->commit); if ($this->saved_outputs->get('commit_message')) { $commit_message = str($this->saved_outputs->get('commit_message'))->limit(47); $this->application_deployment_queue->commit_message = $commit_message->value(); @@ -1257,8 +1286,21 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted // Do any modifications here $this->generate_env_variables(); $merged_envs = $this->env_args->merge(collect(data_get($parsed, 'variables', []))); + $aptPkgs = data_get($parsed, 'phases.setup.aptPkgs', []); + if (count($aptPkgs) === 0) { + data_set($parsed, 'phases.setup.aptPkgs', ['curl', 'wget']); + } else { + if (!in_array('curl', $aptPkgs)) { + $aptPkgs[] = 'curl'; + } + if (!in_array('wget', $aptPkgs)) { + $aptPkgs[] = 'wget'; + } + data_set($parsed, 'phases.setup.aptPkgs', $aptPkgs); + } data_set($parsed, 'variables', $merged_envs->toArray()); $this->nixpacks_plan = json_encode($parsed, JSON_PRETTY_PRINT); + $this->application_deployment_queue->addLogEntry("Final Nixpacks plan: {$this->nixpacks_plan}", hidden: true); } } } @@ -1326,6 +1368,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted $onlyPort = $ports[0]; } $persistent_storages = $this->generate_local_persistent_volumes(); + $persistent_file_volumes = $this->application->fileStorages()->get(); $volume_names = $this->generate_local_persistent_volumes_only_volume_names(); // $environment_variables = $this->generate_environment_variables($ports); $this->save_environment_variables(); @@ -1526,6 +1569,11 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted if (count($persistent_storages) > 0) { $docker_compose['services'][$this->container_name]['volumes'] = $persistent_storages; } + if (count($persistent_file_volumes) > 0) { + $docker_compose['services'][$this->container_name]['volumes'] = $persistent_file_volumes->map(function ($item) { + return "$item->fs_path:$item->mount_path"; + })->toArray(); + } if (count($volume_names) > 0) { $docker_compose['volumes'] = $volume_names; } @@ -1764,7 +1812,11 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); } else { // Pure Dockerfile based deployment if ($this->application->dockerfile) { - $build_command = "docker build --pull {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}"; + if ($this->force_rebuild) { + $build_command = "docker build --no-cache --pull {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}"; + } else { + $build_command = "docker build --pull {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}"; + } $base64_build_command = base64_encode($build_command); $this->execute_remote_command( [ @@ -1935,16 +1987,16 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); if ($containers->count() == 0) { return; } - $this->application_deployment_queue->addLogEntry("Executing pre-deployment command (see debug log for output): {$this->application->pre_deployment_command}"); + $this->application_deployment_queue->addLogEntry("Executing pre-deployment command (see debug log for output)."); foreach ($containers as $container) { $containerName = data_get($container, 'Names'); if ($containers->count() == 1 || str_starts_with($containerName, $this->application->pre_deployment_command_container . '-' . $this->application->uuid)) { - $cmd = 'sh -c "' . str_replace('"', '\"', $this->application->pre_deployment_command) . '"'; + $cmd = "sh -c '" . str_replace("'", "'\''", $this->application->pre_deployment_command) . "'"; $exec = "docker exec {$containerName} {$cmd}"; $this->execute_remote_command( [ - executeInDocker($this->deployment_uuid, $exec), 'hidden' => true + 'command' => $exec, 'hidden' => true ], ); return; @@ -1958,17 +2010,17 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); if (empty($this->application->post_deployment_command)) { return; } - $this->application_deployment_queue->addLogEntry("Executing post-deployment command (see debug log for output): {$this->application->post_deployment_command}"); + $this->application_deployment_queue->addLogEntry("Executing post-deployment command (see debug log for output)."); $containers = getCurrentApplicationContainerStatus($this->server, $this->application->id, $this->pull_request_id); foreach ($containers as $container) { $containerName = data_get($container, 'Names'); if ($containers->count() == 1 || str_starts_with($containerName, $this->application->post_deployment_command_container . '-' . $this->application->uuid)) { - $cmd = 'sh -c "' . str_replace('"', '\"', $this->application->post_deployment_command) . '"'; + $cmd = "sh -c '" . str_replace("'", "'\''", $this->application->post_deployment_command) . "'"; $exec = "docker exec {$containerName} {$cmd}"; $this->execute_remote_command( [ - executeInDocker($this->deployment_uuid, $exec), 'hidden' => true + 'command' => $exec, 'hidden' => true ], ); return; diff --git a/app/Jobs/ApplicationDeploymentJobNew.php b/app/Jobs/ApplicationDeploymentJobNew.php new file mode 100644 index 000000000..dedd736da --- /dev/null +++ b/app/Jobs/ApplicationDeploymentJobNew.php @@ -0,0 +1,2153 @@ +application_deployment_queue = ApplicationDeploymentQueue::find($application_deployment_queue_id); + $this->application = Application::find($this->application_deployment_queue->application_id); + $this->build_pack = data_get($this->application, 'build_pack'); + + $this->application_deployment_queue_id = $application_deployment_queue_id; + $this->deployment_uuid = $this->application_deployment_queue->deployment_uuid; + $this->pull_request_id = $this->application_deployment_queue->pull_request_id; + $this->is_pull_request = $this->pull_request_id !== 0; + + $this->commit = $this->application_deployment_queue->commit; + $this->rollback = $this->application_deployment_queue->rollback; + $this->force_rebuild = $this->application_deployment_queue->force_rebuild; + $this->restart_only = $this->application_deployment_queue->restart_only; + $this->only_this_server = $this->application_deployment_queue->only_this_server; + + $this->git_type = data_get($this->application_deployment_queue, 'git_type'); + + $source = data_get($this->application, 'source'); + if ($source) { + $this->source = $source->getMorphClass()::where('id', $this->application->source->id)->first(); + } + $this->server = Server::find($this->application_deployment_queue->server_id); + $this->timeout = $this->server->settings->dynamic_timeout; + $this->destination = $this->server->destinations()->where('id', $this->application_deployment_queue->destination_id)->first(); + $this->server = $this->mainServer = $this->destination->server; + $this->serverUser = $this->server->user; + $this->basedir = $this->application->generateBaseDir($this->deployment_uuid); + $this->workdir = "{$this->basedir}" . rtrim($this->application->base_directory, '/'); + $this->configuration_dir = application_configuration_dir() . "/{$this->application->uuid}"; + $this->is_debug_enabled = $this->application->settings->is_debug_enabled; + + $this->container_name = generateApplicationContainerName($this->application, $this->pull_request_id); + ray('New container name: ', $this->container_name); + } + + public function handle(): void + { + $this->application_deployment_queue->update([ + 'status' => ApplicationDeploymentStatus::IN_PROGRESS->value, + ]); + if (!$this->server->isFunctional()) { + $this->application_deployment_queue->addLogEntry("Server is not functional."); + $this->fail("Server is not functional."); + return; + } + savePrivateKeyToFs($this->server); + $this->saved_outputs = collect(); + + // Set preview fqdn + if ($this->is_pull_request) { + $this->preview = ApplicationPreview::findPreviewByApplicationAndPullId($this->application->id, $this->pull_request_id); + if ($this->application->fqdn) { + if (str($this->application->fqdn)->contains(',')) { + $url = Url::fromString(str($this->application->fqdn)->explode(',')[0]); + $preview_fqdn = getFqdnWithoutPort(str($this->application->fqdn)->explode(',')[0]); + } else { + $url = Url::fromString($this->application->fqdn); + if (data_get($this->preview, 'fqdn')) { + $preview_fqdn = getFqdnWithoutPort(data_get($this->preview, 'fqdn')); + } + } + $template = $this->application->preview_url_template; + $host = $url->getHost(); + $schema = $url->getScheme(); + $random = new Cuid2(7); + $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); + $preview_fqdn = "$schema://$preview_fqdn"; + $this->preview->fqdn = $preview_fqdn; + $this->preview->save(); + } + if ($this->application->is_github_based()) { + ApplicationPullRequestUpdateJob::dispatch(application: $this->application, preview: $this->preview, deployment_uuid: $this->deployment_uuid, status: ProcessStatus::IN_PROGRESS); + } + if ($this->application->build_pack === 'dockerfile') { + if (data_get($this->application, 'dockerfile_location')) { + $this->dockerfile_location = $this->application->dockerfile_location; + } + } + } + try { + // Generate custom host<->ip mapping + $allContainers = instant_remote_process(["docker network inspect {$this->destination->network} -f '{{json .Containers}}' "], $this->server); + + if (!is_null($allContainers)) { + $allContainers = format_docker_command_output_to_json($allContainers); + $ips = collect([]); + if (count($allContainers) > 0) { + $allContainers = $allContainers[0]; + $allContainers = collect($allContainers)->sort()->values(); + foreach ($allContainers as $container) { + $containerName = data_get($container, 'Name'); + if ($containerName === 'coolify-proxy') { + continue; + } + if (preg_match('/-(\d{12})/', $containerName)) { + continue; + } + $containerIp = data_get($container, 'IPv4Address'); + if ($containerName && $containerIp) { + $containerIp = str($containerIp)->before('/'); + $ips->put($containerName, $containerIp->value()); + } + } + } + $this->addHosts = $ips->map(function ($ip, $name) { + return "--add-host $name:$ip"; + })->implode(' '); + } + + if ($this->application->dockerfile_target_build) { + $this->buildTarget = " --target {$this->application->dockerfile_target_build} "; + } + + // Check custom port + ['repository' => $this->customRepository, 'port' => $this->customPort] = $this->application->customRepository(); + + if (data_get($this->application, 'settings.is_build_server_enabled')) { + $teamId = data_get($this->application, 'environment.project.team.id'); + $buildServers = Server::buildServers($teamId)->get(); + if ($buildServers->count() === 0) { + $this->application_deployment_queue->addLogEntry("No suitable build server found. Using the deployment server."); + $this->build_server = $this->server; + $this->original_server = $this->server; + } else { + $this->build_server = $buildServers->random(); + $this->application_deployment_queue->addLogEntry("Found a suitable build server ({$this->build_server->name})."); + $this->original_server = $this->server; + $this->use_build_server = true; + } + } else { + // Set build server & original_server to the same as deployment server + $this->build_server = $this->server; + $this->original_server = $this->server; + } + $this->decide_what_to_do(); + } catch (Exception $e) { + if ($this->pull_request_id !== 0 && $this->application->is_github_based()) { + ApplicationPullRequestUpdateJob::dispatch(application: $this->application, preview: $this->preview, deployment_uuid: $this->deployment_uuid, status: ProcessStatus::ERROR); + } + ray($e); + $this->fail($e); + throw $e; + } finally { + if ($this->use_build_server) { + $this->server = $this->build_server; + } else { + $this->write_deployment_configurations(); + } + $this->execute_remote_command( + [ + "docker rm -f {$this->deployment_uuid} >/dev/null 2>&1", + "hidden" => true, + "ignore_errors" => true, + ] + ); + + + // $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')); + } + } + private function execute_remote_command(...$commands) + { + static::$batch_counter++; + if ($commands instanceof Collection) { + $commandsText = $commands; + } else { + $commandsText = collect($commands); + } + if ($this->server instanceof Server === false) { + throw new \RuntimeException('Server is not set or is not an instance of Server model'); + } + $commandsText->each(function ($single_command) { + $command = data_get($single_command, 'command') ?? $single_command[0] ?? null; + if ($command === null) { + throw new \RuntimeException('Command is not set'); + } + $hidden = data_get($single_command, 'hidden', false); + $customType = data_get($single_command, 'type'); + $ignore_errors = data_get($single_command, 'ignore_errors', false); + $append = data_get($single_command, 'append', true); + $this->save = data_get($single_command, 'save'); + if ($this->server->isNonRoot()) { + if (str($command)->startsWith('docker exec')) { + $command = str($command)->replace('docker exec', 'sudo docker exec'); + } else { + $command = parseLineForSudo($command, $this->server); + } + } + $remote_command = generateSshCommand($this->server, $command); + $process = Process::timeout(3600)->idleTimeout(3600)->start($remote_command, function (string $type, string $output) use ($command, $hidden, $customType, $append) { + $output = Str::of($output)->trim(); + if ($output->startsWith('╔')) { + $output = "\n" . $output; + } + $new_log_entry = [ + 'command' => remove_iip($command), + 'output' => remove_iip($output), + 'type' => $customType ?? $type === 'err' ? 'stderr' : 'stdout', + 'timestamp' => Carbon::now('UTC'), + 'hidden' => $hidden, + 'batch' => static::$batch_counter, + ]; + if (!$this->application_deployment_queue->logs) { + $new_log_entry['order'] = 1; + } else { + $previous_logs = json_decode($this->application_deployment_queue->logs, associative: true, flags: JSON_THROW_ON_ERROR); + $new_log_entry['order'] = count($previous_logs) + 1; + } + $previous_logs[] = $new_log_entry; + $this->application_deployment_queue->logs = json_encode($previous_logs, flags: JSON_THROW_ON_ERROR); + $this->application_deployment_queue->save(); + + if ($this->save) { + if (data_get($this->saved_outputs, $this->save, null) === null) { + data_set($this->saved_outputs, $this->save, str()); + } + if ($append) { + $this->saved_outputs[$this->save] .= str($output)->trim(); + $this->saved_outputs[$this->save] = str($this->saved_outputs[$this->save]); + } else { + $this->saved_outputs[$this->save] = str($output)->trim(); + } + } + }); + $this->application_deployment_queue->update([ + 'current_process_id' => $process->id(), + ]); + + $process_result = $process->wait(); + if ($process_result->exitCode() !== 0) { + if (!$ignore_errors) { + $this->application_deployment_queue->status = ApplicationDeploymentStatus::FAILED->value; + $this->application_deployment_queue->save(); + throw new \RuntimeException($process_result->errorOutput()); + } + } + }); + } + private function decide_what_to_do() + { + if ($this->restart_only && $this->application->build_pack !== 'dockerimage' && $this->application->build_pack !== 'dockerfile') { + $this->just_restart(); + return; + } else if ($this->is_pull_request) { + $this->deploy_pull_request(); + } else if ($this->application->dockerfile) { + $this->deploy_simple_dockerfile(); + } else if ($this->application->build_pack === 'dockercompose') { + $this->deploy_docker_compose_buildpack(); + } else if ($this->application->build_pack === 'dockerimage') { + $this->deploy_dockerimage_buildpack(); + } else if ($this->application->build_pack === 'dockerfile') { + $this->deploy_dockerfile_buildpack(); + } else if ($this->application->build_pack === 'static') { + $this->deploy_static_buildpack(); + } else { + $this->deploy_nixpacks_buildpack(); + } + $this->post_deployment(); + } + private function post_deployment() + { + + if ($this->server->isProxyShouldRun()) { + GetContainersStatus::dispatch($this->server); + // dispatch(new ContainerStatusJob($this->server)); + } + $this->next(ApplicationDeploymentStatus::FINISHED->value); + if ($this->is_pull_request) { + if ($this->application->is_github_based()) { + ApplicationPullRequestUpdateJob::dispatch(application: $this->application, preview: $this->preview, deployment_uuid: $this->deployment_uuid, status: ProcessStatus::FINISHED); + } + } + $this->run_post_deployment_command(); + $this->application->isConfigurationChanged(true); + } + private function deploy_simple_dockerfile() + { + if ($this->use_build_server) { + $this->server = $this->build_server; + } + $dockerfile_base64 = base64_encode($this->application->dockerfile); + $this->application_deployment_queue->addLogEntry("Starting deployment of {$this->application->name} to {$this->server->name}."); + $this->prepare_builder_image(); + $this->execute_remote_command( + [ + executeInDocker($this->deployment_uuid, "echo '$dockerfile_base64' | base64 -d | tee {$this->workdir}{$this->dockerfile_location} > /dev/null") + ], + ); + $this->generate_image_names(); + + // Always rebuild dockerfile based container. + // if (!$this->force_rebuild) { + // $this->check_image_locally_or_remotely(); + // if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) { + // $this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped."); + // $this->generate_compose_file(); + // $this->push_to_docker_registry(); + // $this->rolling_update(); + // return; + // } + // } + $this->generate_compose_file(); + $this->generate_build_env_variables(); + $this->add_build_env_variables_to_dockerfile(); + $this->build_image(); + $this->push_to_docker_registry(); + $this->rolling_update(); + } + private function deploy_dockerimage_buildpack() + { + $this->dockerImage = $this->application->docker_registry_image_name; + if (str($this->application->docker_registry_image_tag)->isEmpty()) { + $this->dockerImageTag = 'latest'; + } else { + $this->dockerImageTag = $this->application->docker_registry_image_tag; + } + ray("echo 'Starting deployment of {$this->dockerImage}:{$this->dockerImageTag} to {$this->server->name}.'"); + $this->application_deployment_queue->addLogEntry("Starting deployment of {$this->dockerImage}:{$this->dockerImageTag} to {$this->server->name}."); + $this->generate_image_names(); + $this->prepare_builder_image(); + $this->generate_compose_file(); + $this->rolling_update(); + } + private function deploy_docker_compose_buildpack() + { + if (data_get($this->application, 'docker_compose_location')) { + $this->docker_compose_location = $this->application->docker_compose_location; + } + if (data_get($this->application, 'docker_compose_custom_start_command')) { + $this->docker_compose_custom_start_command = $this->application->docker_compose_custom_start_command; + } + if (data_get($this->application, 'docker_compose_custom_build_command')) { + $this->docker_compose_custom_build_command = $this->application->docker_compose_custom_build_command; + } + if ($this->pull_request_id === 0) { + $this->application_deployment_queue->addLogEntry("Starting deployment of {$this->application->name} to {$this->server->name}."); + } else { + $this->application_deployment_queue->addLogEntry("Starting pull request (#{$this->pull_request_id}) deployment of {$this->customRepository}:{$this->application->git_branch} to {$this->server->name}."); + } + $this->prepare_builder_image(); + $this->check_git_if_build_needed(); + $this->clone_repository(); + $this->generate_image_names(); + $this->cleanup_git(); + $this->application->loadComposeFile(isInit: false); + if ($this->application->settings->is_raw_compose_deployment_enabled) { + $this->application->parseRawCompose(); + $yaml = $composeFile = $this->application->docker_compose_raw; + } else { + $composeFile = $this->application->parseCompose(pull_request_id: $this->pull_request_id); + $yaml = Yaml::dump($composeFile->toArray(), 10); + } + $this->docker_compose_base64 = base64_encode($yaml); + $this->execute_remote_command([ + executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d | tee {$this->workdir}{$this->docker_compose_location} > /dev/null"), "hidden" => true + ]); + $this->save_environment_variables(); + // Build new container to limit downtime. + $this->application_deployment_queue->addLogEntry("Pulling & building required images."); + + if ($this->docker_compose_custom_build_command) { + $this->execute_remote_command( + [executeInDocker($this->deployment_uuid, "cd {$this->basedir} && {$this->docker_compose_custom_build_command}"), "hidden" => true], + ); + } else { + $this->execute_remote_command( + [executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build"), "hidden" => true], + ); + } + + $this->stop_running_container(force: true); + + $networkId = $this->application->uuid; + if ($this->is_pull_request) { + $networkId = "{$this->application->uuid}-{$this->pull_request_id}"; + } + if ($this->server->isSwarm()) { + // TODO + } else { + $this->execute_remote_command([ + "docker network inspect '{$networkId}' >/dev/null 2>&1 || docker network create --attachable '{$networkId}' >/dev/null || true", "hidden" => true, "ignore_errors" => true + ], [ + "docker network connect {$networkId} coolify-proxy || true", "hidden" => true, "ignore_errors" => true + ]); + } + + // Start compose file + if ($this->application->settings->is_raw_compose_deployment_enabled) { + if ($this->docker_compose_custom_start_command) { + $this->execute_remote_command( + [executeInDocker($this->deployment_uuid, "cd {$this->workdir} && {$this->docker_compose_custom_start_command}"), "hidden" => true], + ); + $this->write_deployment_configurations(); + } else { + $this->write_deployment_configurations(); + $server_workdir = $this->application->workdir(); + ray("{$this->coolify_variables} docker compose --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d"); + $this->execute_remote_command( + ["{$this->coolify_variables} docker compose --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d", "hidden" => true], + ); + } + } else { + if ($this->docker_compose_custom_start_command) { + $this->execute_remote_command( + [executeInDocker($this->deployment_uuid, "cd {$this->basedir} && {$this->docker_compose_custom_start_command}"), "hidden" => true], + ); + $this->write_deployment_configurations(); + } else { + $this->execute_remote_command( + [executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up -d"), "hidden" => true], + ); + $this->write_deployment_configurations(); + } + } + + $this->application_deployment_queue->addLogEntry("New container started."); + } + private function deploy_dockerfile_buildpack() + { + $this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch} to {$this->server->name}."); + if ($this->use_build_server) { + $this->server = $this->build_server; + } + if (data_get($this->application, 'dockerfile_location')) { + $this->dockerfile_location = $this->application->dockerfile_location; + } + $this->prepare_builder_image(); + $this->check_git_if_build_needed(); + $this->set_base_dir(); + $this->generate_image_names(); + $this->clone_repository(); + if (!$this->force_rebuild) { + $this->check_image_locally_or_remotely(); + if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) { + $this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped."); + $this->generate_compose_file(); + $this->push_to_docker_registry(); + $this->rolling_update(); + return; + } + } + $this->cleanup_git(); + $this->generate_compose_file(); + $this->generate_build_env_variables(); + $this->add_build_env_variables_to_dockerfile(); + $this->build_image(); + $this->push_to_docker_registry(); + $this->rolling_update(); + } + private function deploy_nixpacks_buildpack() + { + if ($this->use_build_server) { + $this->server = $this->build_server; + } + $this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch} to {$this->server->name}."); + $this->prepare_builder_image(); + $this->check_git_if_build_needed(); + $this->set_base_dir(); + $this->generate_image_names(); + if (!$this->force_rebuild) { + $this->check_image_locally_or_remotely(); + if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) { + $this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped."); + $this->generate_compose_file(); + ray('pushing to docker registry'); + $this->push_to_docker_registry(); + $this->rolling_update(); + return; + } + if ($this->application->isConfigurationChanged()) { + $this->application_deployment_queue->addLogEntry("Configuration changed. Rebuilding image."); + } + } + $this->clone_repository(); + $this->cleanup_git(); + $this->generate_nixpacks_confs(); + $this->generate_compose_file(); + $this->generate_build_env_variables(); + $this->build_image(); + $this->push_to_docker_registry(); + $this->rolling_update(); + } + private function deploy_static_buildpack() + { + if ($this->use_build_server) { + $this->server = $this->build_server; + } + $this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch} to {$this->server->name}."); + $this->prepare_builder_image(); + $this->check_git_if_build_needed(); + $this->set_base_dir(); + $this->generate_image_names(); + if (!$this->force_rebuild) { + $this->check_image_locally_or_remotely(); + if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) { + $this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped."); + $this->generate_compose_file(); + $this->push_to_docker_registry(); + $this->rolling_update(); + return; + } + } + $this->clone_repository(); + $this->cleanup_git(); + $this->generate_compose_file(); + $this->build_image(); + $this->push_to_docker_registry(); + $this->rolling_update(); + } + + private function write_deployment_configurations() + { + if (isset($this->docker_compose_base64)) { + if ($this->use_build_server) { + $this->server = $this->original_server; + } + $readme = generate_readme_file($this->application->name, $this->application_deployment_queue->updated_at); + if ($this->pull_request_id === 0) { + $composeFileName = "$this->configuration_dir/docker-compose.yml"; + } else { + $composeFileName = "$this->configuration_dir/docker-compose-pr-{$this->pull_request_id}.yml"; + $this->docker_compose_location = "/docker-compose-pr-{$this->pull_request_id}.yml"; + } + $this->execute_remote_command( + [ + "mkdir -p $this->configuration_dir" + ], + [ + "echo '{$this->docker_compose_base64}' | base64 -d | tee $composeFileName > /dev/null", + ], + [ + "echo '{$readme}' > $this->configuration_dir/README.md", + ] + ); + if ($this->use_build_server) { + $this->server = $this->build_server; + } + } + } + private function push_to_docker_registry() + { + $forceFail = true; + if (str($this->application->docker_registry_image_name)->isEmpty()) { + ray('empty docker_registry_image_name'); + return; + } + if ($this->restart_only) { + ray('restart_only'); + return; + } + if ($this->application->build_pack === 'dockerimage') { + ray('dockerimage'); + return; + } + if ($this->use_build_server) { + ray('use_build_server'); + $forceFail = true; + } + if ($this->server->isSwarm() && $this->build_pack !== 'dockerimage') { + ray('isSwarm'); + $forceFail = true; + } + if ($this->application->additional_servers->count() > 0) { + ray('additional_servers'); + $forceFail = true; + } + if ($this->application->additional_servers()->wherePivot('server_id', $this->server->id)->count() > 0) { + ray('this is an additional_servers, no pushy pushy'); + return; + } + ray('push_to_docker_registry noww: ' . $this->production_image_name); + try { + instant_remote_process(["docker images --format '{{json .}}' {$this->production_image_name}"], $this->server); + $this->application_deployment_queue->addLogEntry("----------------------------------------"); + $this->application_deployment_queue->addLogEntry("Pushing image to docker registry ({$this->production_image_name})."); + $this->execute_remote_command( + [ + executeInDocker($this->deployment_uuid, "docker push {$this->production_image_name}"), 'hidden' => true + ], + ); + if ($this->application->docker_registry_image_tag) { + // Tag image with latest + $this->application_deployment_queue->addLogEntry("Tagging and pushing image with latest tag."); + $this->execute_remote_command( + [ + executeInDocker($this->deployment_uuid, "docker tag {$this->production_image_name} {$this->application->docker_registry_image_name}:{$this->application->docker_registry_image_tag}"), 'ignore_errors' => true, 'hidden' => true + ], + [ + executeInDocker($this->deployment_uuid, "docker push {$this->application->docker_registry_image_name}:{$this->application->docker_registry_image_tag}"), 'ignore_errors' => true, 'hidden' => true + ], + ); + } + } catch (Exception $e) { + $this->application_deployment_queue->addLogEntry("Failed to push image to docker registry. Please check debug logs for more information."); + if ($forceFail) { + throw new RuntimeException($e->getMessage(), 69420); + } + ray($e); + } + } + private function generate_image_names() + { + if ($this->application->dockerfile) { + if ($this->application->docker_registry_image_name) { + $this->build_image_name = "{$this->application->docker_registry_image_name}:build"; + $this->production_image_name = "{$this->application->docker_registry_image_name}:latest"; + } else { + $this->build_image_name = "{$this->application->uuid}:build"; + $this->production_image_name = "{$this->application->uuid}:latest"; + } + } else if ($this->application->build_pack === 'dockerimage') { + $this->production_image_name = "{$this->dockerImage}:{$this->dockerImageTag}"; + } else if ($this->is_pull_request) { + if ($this->application->docker_registry_image_name) { + $this->build_image_name = "{$this->application->docker_registry_image_name}:pr-{$this->pull_request_id}-build"; + $this->production_image_name = "{$this->application->docker_registry_image_name}:pr-{$this->pull_request_id}"; + } else { + $this->build_image_name = "{$this->application->uuid}:pr-{$this->pull_request_id}-build"; + $this->production_image_name = "{$this->application->uuid}:pr-{$this->pull_request_id}"; + } + } else { + $this->dockerImageTag = str($this->commit)->substr(0, 128); + if ($this->application->docker_registry_image_tag) { + $this->dockerImageTag = $this->application->docker_registry_image_tag; + } + if ($this->application->docker_registry_image_name) { + $this->build_image_name = "{$this->application->docker_registry_image_name}:{$this->dockerImageTag}-build"; + $this->production_image_name = "{$this->application->docker_registry_image_name}:{$this->dockerImageTag}"; + } else { + $this->build_image_name = "{$this->application->uuid}:{$this->dockerImageTag}-build"; + $this->production_image_name = "{$this->application->uuid}:{$this->dockerImageTag}"; + } + } + } + private function just_restart() + { + $this->application_deployment_queue->addLogEntry("Restarting {$this->customRepository}:{$this->application->git_branch} on {$this->server->name}."); + $this->prepare_builder_image(); + $this->check_git_if_build_needed(); + $this->set_base_dir(); + $this->generate_image_names(); + $this->check_image_locally_or_remotely(); + if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty()) { + $this->application_deployment_queue->addLogEntry("Image found ({$this->production_image_name}) with the same Git Commit SHA. Restarting container."); + $this->generate_compose_file(); + $this->rolling_update(); + $this->post_deployment(); + } else { + $this->application_deployment_queue->addLogEntry("Image not found ({$this->production_image_name}). Redeploying the application."); + $this->restart_only = false; + $this->decide_what_to_do(); + } + } + private function check_image_locally_or_remotely() + { + $this->execute_remote_command([ + "docker images -q {$this->production_image_name} 2>/dev/null", "hidden" => true, "save" => "local_image_found" + ]); + if (str($this->saved_outputs->get('local_image_found'))->isEmpty() && $this->application->docker_registry_image_name) { + $this->execute_remote_command([ + "docker pull {$this->production_image_name} 2>/dev/null", "ignore_errors" => true, "hidden" => true + ]); + $this->execute_remote_command([ + "docker images -q {$this->production_image_name} 2>/dev/null", "hidden" => true, "save" => "local_image_found" + ]); + } + } + private function save_environment_variables() + { + $envs = collect([]); + $local_branch = $this->branch; + if ($this->is_pull_request) { + $local_branch = "pull/{$this->pull_request_id}/head"; + } + $sort = $this->application->settings->is_env_sorting_enabled; + if ($sort) { + $sorted_environment_variables = $this->application->environment_variables->sortBy('key'); + $sorted_environment_variables_preview = $this->application->environment_variables_preview->sortBy('key'); + } else { + $sorted_environment_variables = $this->application->environment_variables->sortBy('id'); + $sorted_environment_variables_preview = $this->application->environment_variables_preview->sortBy('id'); + } + $ports = $this->application->main_port(); + if ($this->is_pull_request) { + $this->env_filename = ".env-pr-$this->pull_request_id"; + // Add SOURCE_COMMIT if not exists + if ($this->application->environment_variables_preview->where('key', 'SOURCE_COMMIT')->isEmpty()) { + if (!is_null($this->commit)) { + $envs->push("SOURCE_COMMIT={$this->commit}"); + } else { + $envs->push("SOURCE_COMMIT=unknown"); + } + } + if ($this->application->environment_variables_preview->where('key', 'COOLIFY_FQDN')->isEmpty()) { + $envs->push("COOLIFY_FQDN={$this->preview->fqdn}"); + } + if ($this->application->environment_variables_preview->where('key', 'COOLIFY_URL')->isEmpty()) { + $url = str($this->preview->fqdn)->replace('http://', '')->replace('https://', ''); + $envs->push("COOLIFY_URL={$url}"); + } + if ($this->application->environment_variables_preview->where('key', 'COOLIFY_BRANCH')->isEmpty()) { + $envs->push("COOLIFY_BRANCH={$local_branch}"); + } + foreach ($sorted_environment_variables_preview as $env) { + $real_value = $env->real_value; + if ($env->version === '4.0.0-beta.239') { + $real_value = $env->real_value; + } else { + if ($env->is_literal) { + $real_value = '\'' . $real_value . '\''; + } else { + $real_value = escapeEnvVariables($env->real_value); + } + } + $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]}"); + } + // Add HOST if not exists + if ($this->application->environment_variables_preview->where('key', 'HOST')->isEmpty()) { + $envs->push("HOST=0.0.0.0"); + } + } else { + $this->env_filename = ".env"; + // Add SOURCE_COMMIT if not exists + if ($this->application->environment_variables->where('key', 'SOURCE_COMMIT')->isEmpty()) { + if (!is_null($this->commit)) { + $envs->push("SOURCE_COMMIT={$this->commit}"); + } else { + $envs->push("SOURCE_COMMIT=unknown"); + } + } + if ($this->application->environment_variables->where('key', 'COOLIFY_FQDN')->isEmpty()) { + $envs->push("COOLIFY_FQDN={$this->application->fqdn}"); + } + if ($this->application->environment_variables->where('key', 'COOLIFY_URL')->isEmpty()) { + $url = str($this->application->fqdn)->replace('http://', '')->replace('https://', ''); + $envs->push("COOLIFY_URL={$url}"); + } + if ($this->application->environment_variables_preview->where('key', 'COOLIFY_BRANCH')->isEmpty()) { + $envs->push("COOLIFY_BRANCH={$local_branch}"); + } + foreach ($sorted_environment_variables as $env) { + $real_value = $env->real_value; + if ($env->version === '4.0.0-beta.239') { + $real_value = $env->real_value; + } else { + if ($env->is_literal) { + $real_value = '\'' . $real_value . '\''; + } else { + $real_value = escapeEnvVariables($env->real_value); + } + } + $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]}"); + } + // 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) { + $this->server = $this->original_server; + $this->execute_remote_command( + [ + "command" => "rm -f $this->configuration_dir/{$this->env_filename}", + "hidden" => true, + "ignore_errors" => true + ] + ); + $this->server = $this->build_server; + $this->execute_remote_command( + [ + "command" => "rm -f $this->configuration_dir/{$this->env_filename}", + "hidden" => true, + "ignore_errors" => true + ] + ); + } else { + $this->execute_remote_command( + [ + "command" => "rm -f $this->configuration_dir/{$this->env_filename}", + "hidden" => true, + "ignore_errors" => true + ] + ); + } + } else { + $envs_base64 = base64_encode($envs->implode("\n")); + $this->execute_remote_command( + [ + executeInDocker($this->deployment_uuid, "echo '$envs_base64' | base64 -d | tee $this->workdir/{$this->env_filename} > /dev/null") + ], + + ); + if ($this->use_build_server) { + $this->server = $this->original_server; + $this->execute_remote_command( + [ + "echo '$envs_base64' | base64 -d | tee $this->configuration_dir/{$this->env_filename} > /dev/null" + ] + ); + $this->server = $this->build_server; + } else { + $this->execute_remote_command( + [ + "echo '$envs_base64' | base64 -d | tee $this->configuration_dir/{$this->env_filename} > /dev/null" + ] + ); + } + } + } + + + private function framework_based_notification() + { + // Laravel old env variables + if ($this->pull_request_id === 0) { + $nixpacks_php_fallback_path = $this->application->environment_variables->where('key', 'NIXPACKS_PHP_FALLBACK_PATH')->first(); + $nixpacks_php_root_dir = $this->application->environment_variables->where('key', 'NIXPACKS_PHP_ROOT_DIR')->first(); + } else { + $nixpacks_php_fallback_path = $this->application->environment_variables_preview->where('key', 'NIXPACKS_PHP_FALLBACK_PATH')->first(); + $nixpacks_php_root_dir = $this->application->environment_variables_preview->where('key', 'NIXPACKS_PHP_ROOT_DIR')->first(); + } + if ($nixpacks_php_fallback_path?->value === '/index.php' && $nixpacks_php_root_dir?->value === '/app/public' && $this->newVersionIsHealthy === false) { + $this->application_deployment_queue->addLogEntry("There was a change in how Laravel is deployed. Please update your environment variables to match the new deployment method. More details here: https://coolify.io/docs/resources/laravel", 'stderr'); + } + } + private function rolling_update() + { + if ($this->server->isSwarm()) { + $this->application_deployment_queue->addLogEntry("Rolling update started."); + $this->execute_remote_command( + [ + executeInDocker($this->deployment_uuid, "docker stack deploy --with-registry-auth -c {$this->workdir}{$this->docker_compose_location} {$this->application->uuid}") + ], + ); + $this->application_deployment_queue->addLogEntry("Rolling update completed."); + } else { + if ($this->use_build_server) { + $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->is_pull_request || 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."); + } + if ((bool) $this->application->settings->is_consistent_container_name_enabled) { + $this->application_deployment_queue->addLogEntry("Consistent container name feature enabled, rolling update is not supported."); + } + if (isset($this->application->settings->custom_internal_name)) { + $this->application_deployment_queue->addLogEntry("Custom internal name is set, rolling update is not supported."); + } + if ($this->is_pull_request) { + $this->application->settings->is_consistent_container_name_enabled = true; + $this->application_deployment_queue->addLogEntry("Pull request deployment, rolling update is not supported."); + } + if (str($this->application->custom_docker_run_options)->contains('--ip') || str($this->application->custom_docker_run_options)->contains('--ip6')) { + $this->application_deployment_queue->addLogEntry("Custom IP address is set, rolling update is not supported."); + } + $this->stop_running_container(force: true); + $this->start_by_compose_file(); + } else { + $this->application_deployment_queue->addLogEntry("----------------------------------------"); + $this->application_deployment_queue->addLogEntry("Rolling update started."); + $this->start_by_compose_file(); + $this->health_check(); + $this->stop_running_container(); + $this->application_deployment_queue->addLogEntry("Rolling update completed."); + } + } + $this->framework_based_notification(); + } + private function health_check() + { + if ($this->server->isSwarm()) { + // Implement healthcheck for swarm + } else { + if ($this->application->isHealthcheckDisabled() && $this->application->custom_healthcheck_found === false) { + $this->newVersionIsHealthy = true; + return; + } + if ($this->application->custom_healthcheck_found) { + $this->application_deployment_queue->addLogEntry("Custom healthcheck found, skipping default healthcheck."); + } + // ray('New container name: ', $this->container_name); + if ($this->container_name) { + $counter = 1; + $this->application_deployment_queue->addLogEntry("Waiting for healthcheck to pass on the new container."); + if ($this->full_healthcheck_url) { + $this->application_deployment_queue->addLogEntry("Healthcheck URL (inside the container): {$this->full_healthcheck_url}"); + } + $this->application_deployment_queue->addLogEntry("Waiting for the start period ({$this->application->health_check_start_period} seconds) before starting healthcheck."); + $sleeptime = 0; + while ($sleeptime < $this->application->health_check_start_period) { + Sleep::for(1)->seconds(); + $sleeptime++; + } + while ($counter <= $this->application->health_check_retries) { + $this->execute_remote_command( + [ + "docker inspect --format='{{json .State.Health.Status}}' {$this->container_name}", + "hidden" => true, + "save" => "health_check", + "append" => false + ], + [ + "docker inspect --format='{{json .State.Health.Log}}' {$this->container_name}", + "hidden" => true, + "save" => "health_check_logs", + "append" => false + ], + ); + $this->application_deployment_queue->addLogEntry("Attempt {$counter} of {$this->application->health_check_retries} | Healthcheck status: {$this->saved_outputs->get('health_check')}"); + $health_check_logs = data_get(collect(json_decode($this->saved_outputs->get('health_check_logs')))->last(), 'Output', '(no logs)'); + if (empty($health_check_logs)) { + $health_check_logs = '(no logs)'; + } + $health_check_return_code = data_get(collect(json_decode($this->saved_outputs->get('health_check_logs')))->last(), 'ExitCode', '(no return code)'); + if ($health_check_logs !== '(no logs)' || $health_check_return_code !== '(no return code)') { + $this->application_deployment_queue->addLogEntry("Healthcheck logs: {$health_check_logs} | Return code: {$health_check_return_code}"); + } + + if (Str::of($this->saved_outputs->get('health_check'))->replace('"', '')->value() === 'healthy') { + $this->newVersionIsHealthy = true; + $this->application->update(['status' => 'running']); + $this->application_deployment_queue->addLogEntry("New container is healthy."); + break; + } + if (Str::of($this->saved_outputs->get('health_check'))->replace('"', '')->value() === 'unhealthy') { + $this->newVersionIsHealthy = false; + $this->query_logs(); + break; + } + $counter++; + $sleeptime = 0; + while ($sleeptime < $this->application->health_check_interval) { + Sleep::for(1)->seconds(); + $sleeptime++; + } + } + if (Str::of($this->saved_outputs->get('health_check'))->replace('"', '')->value() === 'starting') { + $this->query_logs(); + } + } + } + } + private function query_logs() + { + $this->application_deployment_queue->addLogEntry("----------------------------------------"); + $this->application_deployment_queue->addLogEntry("Container logs:"); + $this->execute_remote_command( + [ + "command" => "docker logs -n 100 {$this->container_name}", + "type" => "stderr", + "ignore_errors" => true, + ], + ); + $this->application_deployment_queue->addLogEntry("----------------------------------------"); + } + private function deploy_pull_request() + { + if ($this->application->build_pack === 'dockercompose') { + $this->deploy_docker_compose_buildpack(); + return; + } + if ($this->use_build_server) { + $this->server = $this->build_server; + } + $this->newVersionIsHealthy = true; + $this->generate_image_names(); + $this->application_deployment_queue->addLogEntry("Starting pull request (#{$this->pull_request_id}) deployment of {$this->customRepository}:{$this->application->git_branch}."); + $this->prepare_builder_image(); + $this->check_git_if_build_needed(); + $this->clone_repository(); + $this->set_base_dir(); + $this->cleanup_git(); + if ($this->application->build_pack === 'nixpacks') { + $this->generate_nixpacks_confs(); + } + $this->generate_compose_file(); + $this->generate_build_env_variables(); + if ($this->application->build_pack === 'dockerfile') { + $this->add_build_env_variables_to_dockerfile(); + } + $this->build_image(); + $this->push_to_docker_registry(); + // $this->stop_running_container(); + $this->rolling_update(); + } + private function create_workdir() + { + if ($this->use_build_server) { + $this->server = $this->original_server; + $this->execute_remote_command( + [ + "command" => "mkdir -p {$this->configuration_dir}" + ], + ); + $this->server = $this->build_server; + $this->execute_remote_command( + [ + "command" => executeInDocker($this->deployment_uuid, "mkdir -p {$this->workdir}") + ], + [ + "command" => "mkdir -p {$this->configuration_dir}" + ], + ); + } else { + $this->execute_remote_command( + [ + "command" => executeInDocker($this->deployment_uuid, "mkdir -p {$this->workdir}") + ], + [ + "command" => "mkdir -p {$this->configuration_dir}" + ], + ); + } + } + private function prepare_builder_image() + { + $helperImage = config('coolify.helper_image'); + // Get user home directory + $this->serverUserHomeDir = instant_remote_process(["echo \$HOME"], $this->server); + $this->dockerConfigFileExists = instant_remote_process(["test -f {$this->serverUserHomeDir}/.docker/config.json && echo 'OK' || echo 'NOK'"], $this->server); + if ($this->use_build_server) { + if ($this->dockerConfigFileExists === 'NOK') { + throw new RuntimeException('Docker config file (~/.docker/config.json) not found on the build server. Please run "docker login" to login to the docker registry on the server.'); + } + $runCommand = "docker run -d --name {$this->deployment_uuid} --rm -v {$this->serverUserHomeDir}/.docker/config.json:/root/.docker/config.json:ro -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}"; + } else { + if ($this->dockerConfigFileExists === 'OK') { + $runCommand = "docker run -d --network {$this->destination->network} --name {$this->deployment_uuid} --rm -v {$this->serverUserHomeDir}/.docker/config.json:/root/.docker/config.json:ro -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}"; + } else { + $runCommand = "docker run -d --network {$this->destination->network} --name {$this->deployment_uuid} --rm -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}"; + } + } + $this->application_deployment_queue->addLogEntry("Preparing container with helper image: $helperImage."); + $this->execute_remote_command( + [ + "command" => "docker rm -f {$this->deployment_uuid}", + "ignore_errors" => true, + "hidden" => true + ] + ); + $this->execute_remote_command( + [ + $runCommand, + "hidden" => true, + ], + [ + "command" => executeInDocker($this->deployment_uuid, "mkdir -p {$this->basedir}") + ], + ); + $this->run_pre_deployment_command(); + } + private function deploy_to_additional_destinations() + { + if ($this->application->additional_networks->count() === 0) { + return; + } + if ($this->is_pull_request) { + return; + } + $destination_ids = $this->application->additional_networks->pluck('id'); + if ($this->server->isSwarm()) { + $this->application_deployment_queue->addLogEntry("Additional destinations are not supported in swarm mode."); + return; + } + if ($destination_ids->contains($this->destination->id)) { + ray('Same destination found in additional destinations. Skipping.'); + return; + } + foreach ($destination_ids as $destination_id) { + $destination = StandaloneDocker::find($destination_id); + $server = $destination->server; + if ($server->team_id !== $this->mainServer->team_id) { + $this->application_deployment_queue->addLogEntry("Skipping deployment to {$server->name}. Not in the same team?!"); + continue; + } + // ray('Deploying to additional destination: ', $server->name); + $deployment_uuid = new Cuid2(); + queue_application_deployment( + deployment_uuid: $deployment_uuid, + application: $this->application, + server: $server, + destination: $destination, + no_questions_asked: true, + ); + $this->application_deployment_queue->addLogEntry("Deployment to {$server->name}. Logs: " . route('project.application.deployment.show', [ + 'project_uuid' => data_get($this->application, 'environment.project.uuid'), + 'application_uuid' => data_get($this->application, 'uuid'), + 'deployment_uuid' => $deployment_uuid, + 'environment_name' => data_get($this->application, 'environment.name'), + ])); + } + } + private function set_base_dir() + { + $this->application_deployment_queue->addLogEntry("Setting base directory to {$this->workdir}."); + } + private function set_coolify_variables() + { + $this->coolify_variables = "SOURCE_COMMIT={$this->commit} "; + if ($this->pull_request_id === 0) { + $fqdn = $this->application->fqdn; + } else { + $fqdn = $this->preview->fqdn; + } + if (isset($fqdn)) { + $this->coolify_variables .= "COOLIFY_FQDN={$fqdn} "; + $url = str($fqdn)->replace('http://', '')->replace('https://', ''); + $this->coolify_variables .= "COOLIFY_URL={$url} "; + } + if (isset($this->application->git_branch)) { + $this->coolify_variables .= "COOLIFY_BRANCH={$this->application->git_branch} "; + } + } + private function check_git_if_build_needed() + { + $this->generate_git_import_commands(); + $local_branch = $this->branch; + if ($this->is_pull_request) { + $local_branch = "pull/{$this->pull_request_id}/head"; + } + $private_key = data_get($this->application, 'private_key.private_key'); + if ($private_key) { + $private_key = base64_encode($private_key); + $this->execute_remote_command( + [ + executeInDocker($this->deployment_uuid, "mkdir -p /root/.ssh") + ], + [ + executeInDocker($this->deployment_uuid, "echo '{$private_key}' | base64 -d | tee /root/.ssh/id_rsa > /dev/null") + ], + [ + executeInDocker($this->deployment_uuid, "chmod 600 /root/.ssh/id_rsa") + ], + [ + executeInDocker($this->deployment_uuid, "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$this->customPort} -o Port={$this->customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git ls-remote {$this->fullRepoUrl} {$local_branch}"), + "hidden" => true, + "save" => "git_commit_sha" + ], + ); + } else { + $this->execute_remote_command( + [ + executeInDocker($this->deployment_uuid, "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$this->customPort} -o Port={$this->customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null\" git ls-remote {$this->fullRepoUrl} {$local_branch}"), + "hidden" => true, + "save" => "git_commit_sha" + ], + ); + } + if ($this->saved_outputs->get('git_commit_sha') && !$this->rollback) { + $this->commit = $this->saved_outputs->get('git_commit_sha')->before("\t"); + $this->application_deployment_queue->commit = $this->commit; + $this->application_deployment_queue->save(); + } + $this->set_coolify_variables(); + } + private function clone_repository() + { + $importCommands = $this->generate_git_import_commands(); + $this->application_deployment_queue->addLogEntry("\n----------------------------------------"); + $this->application_deployment_queue->addLogEntry("Importing {$this->customRepository}:{$this->application->git_branch} (commit sha {$this->application->git_commit_sha}) to {$this->basedir}."); + if ($this->is_pull_request) { + $this->application_deployment_queue->addLogEntry("Checking out tag pull/{$this->pull_request_id}/head."); + } + $this->execute_remote_command( + [ + $importCommands, "hidden" => true + ] + ); + $this->create_workdir(); + $this->execute_remote_command( + [ + executeInDocker($this->deployment_uuid, "cd {$this->workdir} && git log -1 {$this->commit} --pretty=%B"), + "hidden" => true, + "save" => "commit_message" + ] + ); + if ($this->saved_outputs->get('commit_message')) { + $commit_message = str($this->saved_outputs->get('commit_message'))->limit(47); + $this->application_deployment_queue->commit_message = $commit_message->value(); + ApplicationDeploymentQueue::whereCommit($this->commit)->whereApplicationId($this->application->id)->update( + ['commit_message' => $commit_message->value()] + ); + } + } + + private function generate_git_import_commands() + { + ['commands' => $commands, 'branch' => $this->branch, 'fullRepoUrl' => $this->fullRepoUrl] = $this->application->generateGitImportCommands( + deployment_uuid: $this->deployment_uuid, + pull_request_id: $this->pull_request_id, + git_type: $this->git_type, + commit: $this->commit + ); + return $commands; + } + + private function cleanup_git() + { + $this->execute_remote_command( + [executeInDocker($this->deployment_uuid, "rm -fr {$this->basedir}/.git")], + ); + } + + private function generate_nixpacks_confs() + { + $nixpacks_command = $this->nixpacks_build_cmd(); + $this->application_deployment_queue->addLogEntry("Generating nixpacks configuration with: $nixpacks_command"); + $this->execute_remote_command( + [executeInDocker($this->deployment_uuid, $nixpacks_command), "save" => "nixpacks_plan", "hidden" => true], + [executeInDocker($this->deployment_uuid, "nixpacks detect {$this->workdir}"), "save" => "nixpacks_type", "hidden" => true], + ); + if ($this->saved_outputs->get('nixpacks_type')) { + $this->nixpacks_type = $this->saved_outputs->get('nixpacks_type'); + if (str($this->nixpacks_type)->isEmpty()) { + throw new RuntimeException('Nixpacks failed to detect the application type. Please check the documentation of Nixpacks: https://nixpacks.com/docs/providers'); + } + } + if ($this->saved_outputs->get('nixpacks_plan')) { + $this->nixpacks_plan = $this->saved_outputs->get('nixpacks_plan'); + if ($this->nixpacks_plan) { + $this->application_deployment_queue->addLogEntry("Found application type: {$this->nixpacks_type}."); + $this->application_deployment_queue->addLogEntry("If you need further customization, please check the documentation of Nixpacks: https://nixpacks.com/docs/providers/{$this->nixpacks_type}"); + $parsed = Toml::Parse($this->nixpacks_plan); + // Do any modifications here + $this->generate_env_variables(); + $merged_envs = $this->env_args->merge(collect(data_get($parsed, 'variables', []))); + $aptPkgs = data_get($parsed, 'phases.setup.aptPkgs', []); + if (count($aptPkgs) === 0) { + data_set($parsed, 'phases.setup.aptPkgs', ['curl', 'wget']); + } else { + if (!in_array('curl', $aptPkgs)) { + $aptPkgs[] = 'curl'; + } + if (!in_array('wget', $aptPkgs)) { + $aptPkgs[] = 'wget'; + } + data_set($parsed, 'phases.setup.aptPkgs', $aptPkgs); + } + data_set($parsed, 'variables', $merged_envs->toArray()); + $this->nixpacks_plan = json_encode($parsed, JSON_PRETTY_PRINT); + $this->application_deployment_queue->addLogEntry("Final Nixpacks plan: {$this->nixpacks_plan}", hidden: true); + } + } + } + + private function nixpacks_build_cmd() + { + $this->generate_nixpacks_env_variables(); + $nixpacks_command = "nixpacks plan -f toml {$this->env_nixpacks_args}"; + if ($this->application->build_command) { + $nixpacks_command .= " --build-cmd \"{$this->application->build_command}\""; + } + if ($this->application->start_command) { + $nixpacks_command .= " --start-cmd \"{$this->application->start_command}\""; + } + if ($this->application->install_command) { + $nixpacks_command .= " --install-cmd \"{$this->application->install_command}\""; + } + $nixpacks_command .= " {$this->workdir}"; + return $nixpacks_command; + } + private function generate_nixpacks_env_variables() + { + $this->env_nixpacks_args = collect([]); + if ($this->pull_request_id === 0) { + foreach ($this->application->nixpacks_environment_variables as $env) { + if (!is_null($env->real_value)) { + $this->env_nixpacks_args->push("--env {$env->key}={$env->real_value}"); + } + } + } else { + foreach ($this->application->nixpacks_environment_variables_preview as $env) { + if (!is_null($env->real_value)) { + $this->env_nixpacks_args->push("--env {$env->key}={$env->real_value}"); + } + } + } + + $this->env_nixpacks_args = $this->env_nixpacks_args->implode(' '); + } + private function generate_env_variables() + { + $this->env_args = collect([]); + $this->env_args->put('SOURCE_COMMIT', $this->commit); + if ($this->pull_request_id === 0) { + foreach ($this->application->build_environment_variables as $env) { + if (!is_null($env->real_value)) { + $this->env_args->put($env->key, $env->real_value); + } + } + } else { + foreach ($this->application->build_environment_variables_preview as $env) { + if (!is_null($env->real_value)) { + $this->env_args->put($env->key, $env->real_value); + } + } + } + } + + private function generate_compose_file() + { + $this->create_workdir(); + $ports = $this->application->main_port(); + $onlyPort = null; + if (count($ports) > 0) { + $onlyPort = $ports[0]; + } + $persistent_storages = $this->generate_local_persistent_volumes(); + $persistent_file_volumes = $this->application->fileStorages()->get(); + $volume_names = $this->generate_local_persistent_volumes_only_volume_names(); + // $environment_variables = $this->generate_environment_variables($ports); + $this->save_environment_variables(); + if (data_get($this->application, 'custom_labels')) { + $this->application->parseContainerLabels(); + $labels = collect(preg_split("/\r\n|\n|\r/", base64_decode($this->application->custom_labels))); + $labels = $labels->filter(function ($value, $key) { + return !Str::startsWith($value, 'coolify.'); + }); + $found_caddy_labels = $labels->filter(function ($value, $key) { + return Str::startsWith($value, 'caddy_'); + }); + if ($found_caddy_labels->count() === 0) { + if ($this->is_pull_request) { + $domains = str(data_get($this->preview, 'fqdn'))->explode(','); + } else { + $domains = str(data_get($this->application, 'fqdn'))->explode(','); + } + $labels = $labels->merge(fqdnLabelsForCaddy( + network: $this->application->destination->network, + uuid: $this->application->uuid, + domains: $domains, + onlyPort: $onlyPort, + is_force_https_enabled: $this->application->isForceHttpsEnabled(), + is_gzip_enabled: $this->application->isGzipEnabled(), + is_stripprefix_enabled: $this->application->isStripprefixEnabled() + )); + } + $this->application->custom_labels = base64_encode($labels->implode("\n")); + $this->application->save(); + } else { + $labels = collect(generateLabelsApplication($this->application, $this->preview)); + } + if ($this->is_pull_request) { + $labels = collect(generateLabelsApplication($this->application, $this->preview)); + } + if ($this->application->settings->is_container_label_escape_enabled) { + $labels = $labels->map(function ($value, $key) { + return escapeDollarSign($value); + }); + } + $labels = $labels->merge(defaultLabels($this->application->id, $this->application->uuid, $this->pull_request_id))->toArray(); + + // Check for custom HEALTHCHECK + if ($this->application->build_pack === 'dockerfile' || $this->application->dockerfile) { + $this->execute_remote_command([ + executeInDocker($this->deployment_uuid, "cat {$this->workdir}{$this->dockerfile_location}"), "hidden" => true, "save" => 'dockerfile_from_repo', "ignore_errors" => true + ]); + $dockerfile = collect(Str::of($this->saved_outputs->get('dockerfile_from_repo'))->trim()->explode("\n")); + $this->application->parseHealthcheckFromDockerfile($dockerfile); + } + $docker_compose = [ + 'services' => [ + $this->container_name => [ + 'image' => $this->production_image_name, + 'container_name' => $this->container_name, + 'restart' => RESTART_MODE, + 'expose' => $ports, + 'networks' => [ + $this->destination->network => [ + 'aliases' => [ + $this->container_name + ] + ] + ], + 'mem_limit' => $this->application->limits_memory, + 'memswap_limit' => $this->application->limits_memory_swap, + 'mem_swappiness' => $this->application->limits_memory_swappiness, + 'mem_reservation' => $this->application->limits_memory_reservation, + 'cpus' => (float) $this->application->limits_cpus, + 'cpu_shares' => $this->application->limits_cpu_shares, + ] + ], + 'networks' => [ + $this->destination->network => [ + 'external' => true, + 'name' => $this->destination->network, + 'attachable' => true + ] + ] + ]; + if (isset($this->application->settings->custom_internal_name)) { + $docker_compose['services'][$this->container_name]['networks'][$this->destination->network]['aliases'][] = $this->application->settings->custom_internal_name; + } + // if (str($this->saved_outputs->get('dotenv'))->isNotEmpty()) { + // if (data_get($docker_compose, "services.{$this->container_name}.env_file")) { + // $docker_compose['services'][$this->container_name]['env_file'][] = '.env'; + // } else { + // $docker_compose['services'][$this->container_name]['env_file'] = ['.env']; + // } + // } + // if ($this->env_filename) { + // if (data_get($docker_compose, "services.{$this->container_name}.env_file")) { + // $docker_compose['services'][$this->container_name]['env_file'][] = $this->env_filename; + // } else { + // $docker_compose['services'][$this->container_name]['env_file'] = [$this->env_filename]; + // } + // } + if (!is_null($this->env_filename)) { + $docker_compose['services'][$this->container_name]['env_file'] = [$this->env_filename]; + } + $docker_compose['services'][$this->container_name]['healthcheck'] = [ + 'test' => [ + 'CMD-SHELL', + $this->generate_healthcheck_commands() + ], + 'interval' => $this->application->health_check_interval . 's', + 'timeout' => $this->application->health_check_timeout . 's', + 'retries' => $this->application->health_check_retries, + 'start_period' => $this->application->health_check_start_period . 's' + ]; + + if (!is_null($this->application->limits_cpuset)) { + data_set($docker_compose, 'services.' . $this->container_name . '.cpuset', $this->application->limits_cpuset); + } + if ($this->server->isSwarm()) { + data_forget($docker_compose, 'services.' . $this->container_name . '.container_name'); + data_forget($docker_compose, 'services.' . $this->container_name . '.expose'); + data_forget($docker_compose, 'services.' . $this->container_name . '.restart'); + + data_forget($docker_compose, 'services.' . $this->container_name . '.mem_limit'); + data_forget($docker_compose, 'services.' . $this->container_name . '.memswap_limit'); + data_forget($docker_compose, 'services.' . $this->container_name . '.mem_swappiness'); + data_forget($docker_compose, 'services.' . $this->container_name . '.mem_reservation'); + data_forget($docker_compose, 'services.' . $this->container_name . '.cpus'); + data_forget($docker_compose, 'services.' . $this->container_name . '.cpuset'); + data_forget($docker_compose, 'services.' . $this->container_name . '.cpu_shares'); + + $docker_compose['services'][$this->container_name]['deploy'] = [ + 'mode' => 'replicated', + 'replicas' => data_get($this->application, 'swarm_replicas', 1), + 'update_config' => [ + 'order' => 'start-first' + ], + 'rollback_config' => [ + 'order' => 'start-first' + ], + 'labels' => $labels, + 'resources' => [ + 'limits' => [ + 'cpus' => $this->application->limits_cpus, + 'memory' => $this->application->limits_memory, + ], + 'reservations' => [ + 'cpus' => $this->application->limits_cpus, + 'memory' => $this->application->limits_memory, + ] + ] + ]; + if (data_get($this->application, 'settings.is_swarm_only_worker_nodes')) { + $docker_compose['services'][$this->container_name]['deploy']['placement'] = [ + 'constraints' => [ + 'node.role == worker' + ] + ]; + } + if ($this->is_pull_request) { + $docker_compose['services'][$this->container_name]['deploy']['replicas'] = 1; + } + } else { + $docker_compose['services'][$this->container_name]['labels'] = $labels; + } + if ($this->server->isLogDrainEnabled() && $this->application->isLogDrainEnabled()) { + $docker_compose['services'][$this->container_name]['logging'] = [ + 'driver' => 'fluentd', + 'options' => [ + 'fluentd-address' => "tcp://127.0.0.1:24224", + 'fluentd-async' => "true", + 'fluentd-sub-second-precision' => "true", + ] + ]; + } + if ($this->application->settings->is_gpu_enabled) { + $docker_compose['services'][$this->container_name]['deploy']['resources']['reservations']['devices'] = [ + [ + 'driver' => data_get($this->application, 'settings.gpu_driver', 'nvidia'), + 'capabilities' => ['gpu'], + 'options' => data_get($this->application, 'settings.gpu_options', []) + ] + ]; + if (data_get($this->application, 'settings.gpu_count')) { + $count = data_get($this->application, 'settings.gpu_count'); + if ($count === 'all') { + $docker_compose['services'][$this->container_name]['deploy']['resources']['reservations']['devices'][0]['count'] = $count; + } else { + $docker_compose['services'][$this->container_name]['deploy']['resources']['reservations']['devices'][0]['count'] = (int) $count; + } + } else if (data_get($this->application, 'settings.gpu_device_ids')) { + $docker_compose['services'][$this->container_name]['deploy']['resources']['reservations']['devices'][0]['ids'] = data_get($this->application, 'settings.gpu_device_ids'); + } + } + if ($this->application->isHealthcheckDisabled()) { + data_forget($docker_compose, 'services.' . $this->container_name . '.healthcheck'); + } + if (count($this->application->ports_mappings_array) > 0 && $this->pull_request_id === 0) { + $docker_compose['services'][$this->container_name]['ports'] = $this->application->ports_mappings_array; + } + if (count($persistent_storages) > 0) { + $docker_compose['services'][$this->container_name]['volumes'] = $persistent_storages; + } + if (count($persistent_file_volumes) > 0) { + $docker_compose['services'][$this->container_name]['volumes'] = $persistent_file_volumes->map(function ($item) { + return "$item->fs_path:$item->mount_path"; + })->toArray(); + } + if (count($volume_names) > 0) { + $docker_compose['volumes'] = $volume_names; + } + // if ($this->build_pack === 'dockerfile') { + // $docker_compose['services'][$this->container_name]['build'] = [ + // 'context' => $this->workdir, + // 'dockerfile' => $this->workdir . $this->dockerfile_location, + // ]; + // } + + if ($this->pull_request_id === 0) { + $custom_compose = convert_docker_run_to_compose($this->application->custom_docker_run_options); + if ((bool)$this->application->settings->is_consistent_container_name_enabled) { + $docker_compose['services'][$this->application->uuid] = $docker_compose['services'][$this->container_name]; + if (count($custom_compose) > 0) { + $ipv4 = data_get($custom_compose, 'ip.0'); + $ipv6 = data_get($custom_compose, 'ip6.0'); + data_forget($custom_compose, 'ip'); + data_forget($custom_compose, 'ip6'); + if ($ipv4 || $ipv6) { + data_forget($docker_compose['services'][$this->application->uuid], 'networks'); + } + if ($ipv4) { + $docker_compose['services'][$this->application->uuid]['networks'][$this->destination->network]['ipv4_address'] = $ipv4; + } + if ($ipv6) { + $docker_compose['services'][$this->application->uuid]['networks'][$this->destination->network]['ipv6_address'] = $ipv6; + } + $docker_compose['services'][$this->application->uuid] = array_merge_recursive($docker_compose['services'][$this->application->uuid], $custom_compose); + } + } else { + if (count($custom_compose) > 0) { + $ipv4 = data_get($custom_compose, 'ip.0'); + $ipv6 = data_get($custom_compose, 'ip6.0'); + data_forget($custom_compose, 'ip'); + data_forget($custom_compose, 'ip6'); + if ($ipv4 || $ipv6) { + data_forget($docker_compose['services'][$this->container_name], 'networks'); + } + if ($ipv4) { + $docker_compose['services'][$this->container_name]['networks'][$this->destination->network]['ipv4_address'] = $ipv4; + } + if ($ipv6) { + $docker_compose['services'][$this->container_name]['networks'][$this->destination->network]['ipv6_address'] = $ipv6; + } + $docker_compose['services'][$this->container_name] = array_merge_recursive($docker_compose['services'][$this->container_name], $custom_compose); + } + } + } + + $this->docker_compose = Yaml::dump($docker_compose, 10); + $this->docker_compose_base64 = base64_encode($this->docker_compose); + $this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d | tee {$this->workdir}/docker-compose.yml > /dev/null"), "hidden" => true]); + } + + private function generate_local_persistent_volumes() + { + $local_persistent_volumes = []; + foreach ($this->application->persistentStorages as $persistentStorage) { + if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) { + $volume_name = $persistentStorage->host_path; + } else { + $volume_name = $persistentStorage->name; + } + if ($this->is_pull_request) { + $volume_name = $volume_name . '-pr-' . $this->pull_request_id; + } + $local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path; + } + return $local_persistent_volumes; + } + + private function generate_local_persistent_volumes_only_volume_names() + { + $local_persistent_volumes_names = []; + foreach ($this->application->persistentStorages as $persistentStorage) { + if ($persistentStorage->host_path) { + continue; + } + $name = $persistentStorage->name; + + if ($this->is_pull_request) { + $name = $name . '-pr-' . $this->pull_request_id; + } + + $local_persistent_volumes_names[$name] = [ + 'name' => $name, + 'external' => false, + ]; + } + return $local_persistent_volumes_names; + } + + private function generate_healthcheck_commands() + { + if (!$this->application->health_check_port) { + $health_check_port = $this->application->ports_exposes_array[0]; + } else { + $health_check_port = $this->application->health_check_port; + } + if ($this->application->settings->is_static || $this->application->build_pack === 'static') { + $health_check_port = 80; + } + if ($this->application->health_check_path) { + $this->full_healthcheck_url = "{$this->application->health_check_method}: {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}{$this->application->health_check_path}"; + $generated_healthchecks_commands = [ + "curl -s -X {$this->application->health_check_method} -f {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}{$this->application->health_check_path} > /dev/null || wget -q -O- {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}{$this->application->health_check_path} > /dev/null || exit 1" + ]; + } else { + $this->full_healthcheck_url = "{$this->application->health_check_method}: {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}/"; + $generated_healthchecks_commands = [ + "curl -s -X {$this->application->health_check_method} -f {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}/ > /dev/null || wget -q -O- {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}/ > /dev/null || exit 1" + ]; + } + return implode(' ', $generated_healthchecks_commands); + } + private function pull_latest_image($image) + { + $this->application_deployment_queue->addLogEntry("Pulling latest image ($image) from the registry."); + $this->execute_remote_command( + [ + executeInDocker($this->deployment_uuid, "docker pull {$image}"), "hidden" => true + ] + ); + } + private function build_image() + { + $this->application_deployment_queue->addLogEntry("----------------------------------------"); + if ($this->application->build_pack === 'static') { + $this->application_deployment_queue->addLogEntry("Static deployment. Copying static assets to the image."); + } else { + $this->application_deployment_queue->addLogEntry("Building docker image started."); + $this->application_deployment_queue->addLogEntry("To check the current progress, click on Show Debug Logs."); + } + + if ($this->application->settings->is_static || $this->application->build_pack === 'static') { + if ($this->application->static_image) { + $this->pull_latest_image($this->application->static_image); + $this->application_deployment_queue->addLogEntry("Continuing with the building process."); + } + if ($this->application->build_pack === 'static') { + $dockerfile = base64_encode("FROM {$this->application->static_image} +WORKDIR /usr/share/nginx/html/ +LABEL coolify.deploymentId={$this->deployment_uuid} +COPY . . +RUN rm -f /usr/share/nginx/html/nginx.conf +RUN rm -f /usr/share/nginx/html/Dockerfile +COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); + $nginx_config = base64_encode("server { + listen 80; + listen [::]:80; + server_name localhost; + + location / { + root /usr/share/nginx/html; + index index.html; + try_files \$uri \$uri.html \$uri/index.html \$uri/ /index.html =404; + } + + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } + }"); + } else { + if ($this->application->build_pack === 'nixpacks') { + $this->nixpacks_plan = base64_encode($this->nixpacks_plan); + $this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->nixpacks_plan}' | base64 -d | tee /artifacts/thegameplan.json > /dev/null"), "hidden" => true]); + if ($this->force_rebuild) { + $this->execute_remote_command([ + executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --no-cache --no-error-without-start -n {$this->build_image_name} {$this->workdir}"), "hidden" => true + ]); + } else { + $this->execute_remote_command([ + executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --cache-key '{$this->application->uuid}' --no-error-without-start -n {$this->build_image_name} {$this->workdir}"), "hidden" => true + ]); + } + $this->execute_remote_command([executeInDocker($this->deployment_uuid, "rm /artifacts/thegameplan.json"), "hidden" => true]); + } else { + if ($this->force_rebuild) { + $build_command = "docker build --no-cache {$this->buildTarget} --network {$this->destination->network} -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->build_image_name {$this->workdir}"; + $base64_build_command = base64_encode($build_command); + } else { + $build_command = "docker build {$this->buildTarget} --network {$this->destination->network} -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->build_image_name {$this->workdir}"; + $base64_build_command = base64_encode($build_command); + } + $this->execute_remote_command( + [ + executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), "hidden" => true + ], + [ + executeInDocker($this->deployment_uuid, "bash /artifacts/build.sh"), "hidden" => true + ] + ); + } + + $dockerfile = base64_encode("FROM {$this->application->static_image} +WORKDIR /usr/share/nginx/html/ +LABEL coolify.deploymentId={$this->deployment_uuid} +COPY --from=$this->build_image_name /app/{$this->application->publish_directory} . +COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); + + $nginx_config = base64_encode("server { + listen 80; + listen [::]:80; + server_name localhost; + + location / { + root /usr/share/nginx/html; + index index.html; + try_files \$uri \$uri.html \$uri/index.html \$uri/ /index.html =404; + } + + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } + }"); + } + $build_command = "docker build {$this->addHosts} --network host -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}"; + $base64_build_command = base64_encode($build_command); + $this->execute_remote_command( + [ + executeInDocker($this->deployment_uuid, "echo '{$dockerfile}' | base64 -d | tee {$this->workdir}/Dockerfile > /dev/null") + ], + [ + executeInDocker($this->deployment_uuid, "echo '{$nginx_config}' | base64 -d | tee {$this->workdir}/nginx.conf > /dev/null") + ], + [ + executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), "hidden" => true + ], + [ + executeInDocker($this->deployment_uuid, "bash /artifacts/build.sh"), "hidden" => true + ] + ); + } else { + // Pure Dockerfile based deployment + if ($this->application->dockerfile) { + $build_command = "docker build --pull {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}"; + $base64_build_command = base64_encode($build_command); + $this->execute_remote_command( + [ + executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), "hidden" => true + ], + [ + executeInDocker($this->deployment_uuid, "bash /artifacts/build.sh"), "hidden" => true + ] + ); + } else { + if ($this->application->build_pack === 'nixpacks') { + $this->nixpacks_plan = base64_encode($this->nixpacks_plan); + $this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->nixpacks_plan}' | base64 -d | tee /artifacts/thegameplan.json > /dev/null"), "hidden" => true]); + if ($this->force_rebuild) { + $this->execute_remote_command([ + executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --no-cache --no-error-without-start -n {$this->production_image_name} {$this->workdir}"), "hidden" => true + ]); + } else { + $this->execute_remote_command([ + executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --cache-key '{$this->application->uuid}' --no-error-without-start -n {$this->production_image_name} {$this->workdir}"), "hidden" => true + ]); + } + $this->execute_remote_command([executeInDocker($this->deployment_uuid, "rm /artifacts/thegameplan.json"), "hidden" => true]); + } else { + if ($this->force_rebuild) { + $build_command = "docker build --no-cache {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}"; + $base64_build_command = base64_encode($build_command); + } else { + $build_command = "docker build {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}"; + $base64_build_command = base64_encode($build_command); + } + $this->execute_remote_command( + [ + executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), "hidden" => true + ], + [ + executeInDocker($this->deployment_uuid, "bash /artifacts/build.sh"), "hidden" => true + ] + ); + } + } + } + $this->application_deployment_queue->addLogEntry("Building docker image completed."); + } + + 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; + }); + } + $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("----------------------------------------"); + $this->application_deployment_queue->addLogEntry("WARNING: Dockerfile or Docker Image based deployment detected. The healthcheck needs a curl or wget command to check the health of the application. Please make sure that it is available in the image or turn off healthcheck on Coolify's UI."); + $this->application_deployment_queue->addLogEntry("----------------------------------------"); + } + $this->application_deployment_queue->addLogEntry("New container is not healthy, rolling back to the old container."); + $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], + ); + } + } + + private function build_by_compose_file() + { + $this->application_deployment_queue->addLogEntry("Pulling & building required images."); + if ($this->application->build_pack === 'dockerimage') { + $this->application_deployment_queue->addLogEntry("Pulling latest images from the registry."); + $this->execute_remote_command( + [executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} pull"), "hidden" => true], + [executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-directory {$this->workdir} build"), "hidden" => true], + ); + } else { + $this->execute_remote_command( + [executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build"), "hidden" => true], + ); + } + $this->application_deployment_queue->addLogEntry("New images built."); + } + + private function start_by_compose_file() + { + if ($this->application->build_pack === 'dockerimage') { + $this->application_deployment_queue->addLogEntry("Pulling latest images from the registry."); + $this->execute_remote_command( + [executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} pull"), "hidden" => true], + [executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-directory {$this->workdir} up --build -d"), "hidden" => true], + ); + } else { + if ($this->use_build_server) { + $this->execute_remote_command( + ["{$this->coolify_variables} docker compose --project-directory {$this->configuration_dir} -f {$this->configuration_dir}{$this->docker_compose_location} up --build -d", "hidden" => true], + ); + } else { + $this->execute_remote_command( + [executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up --build -d"), "hidden" => true], + ); + } + } + $this->application_deployment_queue->addLogEntry("New container started."); + } + + private function generate_build_env_variables() + { + $this->build_args = collect(["--build-arg SOURCE_COMMIT=\"{$this->commit}\""]); + if ($this->pull_request_id === 0) { + foreach ($this->application->build_environment_variables as $env) { + $value = escapeshellarg($env->real_value); + $this->build_args->push("--build-arg {$env->key}={$value}"); + } + } else { + foreach ($this->application->build_environment_variables_preview as $env) { + $value = escapeshellarg($env->real_value); + $this->build_args->push("--build-arg {$env->key}={$value}"); + } + } + + $this->build_args = $this->build_args->implode(' '); + } + + private function add_build_env_variables_to_dockerfile() + { + $this->execute_remote_command([ + executeInDocker($this->deployment_uuid, "cat {$this->workdir}{$this->dockerfile_location}"), "hidden" => true, "save" => 'dockerfile' + ]); + $dockerfile = collect(Str::of($this->saved_outputs->get('dockerfile'))->trim()->explode("\n")); + if ($this->pull_request_id === 0) { + foreach ($this->application->build_environment_variables as $env) { + $dockerfile->splice(1, 0, "ARG {$env->key}={$env->real_value}"); + } + } else { + foreach ($this->application->build_environment_variables_preview as $env) { + $dockerfile->splice(1, 0, "ARG {$env->key}={$env->real_value}"); + } + } + $dockerfile_base64 = base64_encode($dockerfile->implode("\n")); + $this->execute_remote_command([ + executeInDocker($this->deployment_uuid, "echo '{$dockerfile_base64}' | base64 -d | tee {$this->workdir}{$this->dockerfile_location} > /dev/null"), + "hidden" => true + ]); + } + + private function run_pre_deployment_command() + { + if (empty($this->application->pre_deployment_command)) { + return; + } + $containers = getCurrentApplicationContainerStatus($this->server, $this->application->id, $this->pull_request_id); + if ($containers->count() == 0) { + return; + } + $this->application_deployment_queue->addLogEntry("Executing pre-deployment command (see debug log for output)."); + + foreach ($containers as $container) { + $containerName = data_get($container, 'Names'); + if ($containers->count() == 1 || str_starts_with($containerName, $this->application->pre_deployment_command_container . '-' . $this->application->uuid)) { + $cmd = "sh -c '" . str_replace("'", "'\''", $this->application->pre_deployment_command) . "'"; + $exec = "docker exec {$containerName} {$cmd}"; + $this->execute_remote_command( + [ + 'command' => $exec, 'hidden' => true + ], + ); + return; + } + } + throw new RuntimeException('Pre-deployment command: Could not find a valid container. Is the container name correct?'); + } + + private function run_post_deployment_command() + { + if (empty($this->application->post_deployment_command)) { + return; + } + $this->application_deployment_queue->addLogEntry("Executing post-deployment command (see debug log for output)."); + + $containers = getCurrentApplicationContainerStatus($this->server, $this->application->id, $this->pull_request_id); + foreach ($containers as $container) { + $containerName = data_get($container, 'Names'); + if ($containers->count() == 1 || str_starts_with($containerName, $this->application->post_deployment_command_container . '-' . $this->application->uuid)) { + $cmd = "sh -c '" . str_replace("'", "'\''", $this->application->post_deployment_command) . "'"; + $exec = "docker exec {$containerName} {$cmd}"; + $this->execute_remote_command( + [ + 'command' => $exec, 'hidden' => true + ], + ); + return; + } + } + throw new RuntimeException('Post-deployment command: Could not find a valid container. Is the container name correct?'); + } + + private function next(string $status) + { + queue_next_deployment($this->application); + // If the deployment is cancelled by the user, don't update the status + if ( + $this->application_deployment_queue->status !== ApplicationDeploymentStatus::CANCELLED_BY_USER->value && $this->application_deployment_queue->status !== ApplicationDeploymentStatus::FAILED->value + ) { + $this->application_deployment_queue->update([ + 'status' => $status, + ]); + } + if ($this->application_deployment_queue->status === ApplicationDeploymentStatus::FAILED->value) { + $this->application->environment->project->team?->notify(new DeploymentFailed($this->application, $this->deployment_uuid, $this->preview)); + return; + } + if ($status === ApplicationDeploymentStatus::FINISHED->value) { + if (!$this->only_this_server) { + $this->deploy_to_additional_destinations(); + } + $this->application->environment->project->team?->notify(new DeploymentSuccess($this->application, $this->deployment_uuid, $this->preview)); + } + } + + public function failed(Throwable $exception): void + { + $this->next(ApplicationDeploymentStatus::FAILED->value); + $this->application_deployment_queue->addLogEntry("Oops something is not okay, are you okay? 😢", 'stderr'); + if (str($exception->getMessage())->isNotEmpty()) { + $this->application_deployment_queue->addLogEntry($exception->getMessage(), 'stderr'); + } + + if ($this->application->build_pack !== 'dockercompose') { + $code = $exception->getCode(); + 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 + $this->application_deployment_queue->addLogEntry("Deployment failed. Removing the new version of your application.", 'stderr'); + $this->execute_remote_command( + ["docker rm -f $this->container_name >/dev/null 2>&1", "hidden" => true, "ignore_errors" => true] + ); + } + } + } +} diff --git a/app/Jobs/CheckLogDrainContainerJob.php b/app/Jobs/CheckLogDrainContainerJob.php index 4da49b988..8776b67c3 100644 --- a/app/Jobs/CheckLogDrainContainerJob.php +++ b/app/Jobs/CheckLogDrainContainerJob.php @@ -70,7 +70,7 @@ class CheckLogDrainContainerJob implements ShouldQueue, ShouldBeEncrypted } if (!$this->server->log_drain_notification_sent) { ray('Log drain container still unhealthy. Sending notification...'); - $this->server->team?->notify(new ContainerStopped('Coolify Log Drainer', $this->server, null)); + // $this->server->team?->notify(new ContainerStopped('Coolify Log Drainer', $this->server, null)); $this->server->update(['log_drain_notification_sent' => true]); } } else { diff --git a/app/Jobs/InstanceAutoUpdateJob.php b/app/Jobs/InstanceAutoUpdateJob.php index dc35aa2b1..ae629dab9 100644 --- a/app/Jobs/InstanceAutoUpdateJob.php +++ b/app/Jobs/InstanceAutoUpdateJob.php @@ -18,12 +18,12 @@ class InstanceAutoUpdateJob implements ShouldQueue, ShouldBeUnique, ShouldBeEncr public $timeout = 600; public $tries = 1; - public function __construct(private bool $force = false) + public function __construct() { } public function handle(): void { - UpdateCoolify::run(force: $this->force, async: false); + UpdateCoolify::run(); } } diff --git a/app/Jobs/PullTemplatesFromCDN.php b/app/Jobs/PullTemplatesFromCDN.php new file mode 100644 index 000000000..66e7611a7 --- /dev/null +++ b/app/Jobs/PullTemplatesFromCDN.php @@ -0,0 +1,42 @@ +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()); + ray($e->getMessage()); + } + } +} diff --git a/app/Jobs/PullVersionsFromCDN.php b/app/Jobs/PullVersionsFromCDN.php new file mode 100644 index 000000000..0d4084a30 --- /dev/null +++ b/app/Jobs/PullVersionsFromCDN.php @@ -0,0 +1,41 @@ +get('https://cdn.coollabs.io/coolify/versions.json'); + if ($response->successful()) { + $versions = $response->json(); + File::put(base_path('versions.json'), json_encode($versions, JSON_PRETTY_PRINT)); + } else { + send_internal_notification('PullTemplatesAndVersions failed with: ' . $response->status() . ' ' . $response->body()); + } + } + } catch (\Throwable $e) { + send_internal_notification('PullTemplatesAndVersions failed with: ' . $e->getMessage()); + ray($e->getMessage()); + } + } +} diff --git a/app/Jobs/ServerStatusJob.php b/app/Jobs/ServerStatusJob.php index 449ab85a0..d104185c0 100644 --- a/app/Jobs/ServerStatusJob.php +++ b/app/Jobs/ServerStatusJob.php @@ -43,7 +43,7 @@ class ServerStatusJob implements ShouldQueue, ShouldBeEncrypted try { if ($this->server->isFunctional()) { $this->cleanup(notify: false); - $this->removeCoolifyYaml(); + $this->remove_unnecessary_coolify_yaml(); if (config('coolify.is_sentinel_enabled')) { $this->server->checkSentinel(); } @@ -53,8 +53,44 @@ class ServerStatusJob implements ShouldQueue, ShouldBeEncrypted ray($e->getMessage()); return handleError($e); } + try { + // $this->check_docker_engine(); + } catch (\Throwable $e) { + // Do nothing + } } - private function removeCoolifyYaml() + private function check_docker_engine() + { + $version = instant_remote_process([ + "docker info", + ], $this->server, false); + if (is_null($version)) { + $os = instant_remote_process([ + "cat /etc/os-release | grep ^ID=", + ], $this->server, false); + $os = str($os)->after('ID=')->trim(); + if ($os === 'ubuntu') { + try { + instant_remote_process([ + "systemctl start docker", + ], $this->server); + } catch (\Throwable $e) { + ray($e->getMessage()); + return handleError($e); + } + } else { + try { + instant_remote_process([ + "service docker start", + ], $this->server); + } catch (\Throwable $e) { + ray($e->getMessage()); + return handleError($e); + } + } + } + } + private function remove_unnecessary_coolify_yaml() { // This will remote the coolify.yaml file from the server as it is not needed on cloud servers if (isCloud() && $this->server->id !== 0) { diff --git a/app/Listeners/ProxyStartedNotification.php b/app/Listeners/ProxyStartedNotification.php index e6be605ce..1a4fe97bb 100644 --- a/app/Listeners/ProxyStartedNotification.php +++ b/app/Listeners/ProxyStartedNotification.php @@ -17,5 +17,7 @@ class ProxyStartedNotification $this->server = data_get($event, 'data'); $this->server->setupDefault404Redirect(); $this->server->setupDynamicProxyConfiguration(); + $this->server->proxy->force_stop = false; + $this->server->save(); } } diff --git a/app/Livewire/Dev/Compose.php b/app/Livewire/Dev/Compose.php index ec2c4f54d..8c361ba2a 100644 --- a/app/Livewire/Dev/Compose.php +++ b/app/Livewire/Dev/Compose.php @@ -10,7 +10,7 @@ class Compose extends Component public string $base64 = ''; public $services; public function mount() { - $this->services = getServiceTemplates(); + $this->services = get_service_templates(); } public function setService(string $selected) { $this->base64 = data_get($this->services, $selected . '.compose'); diff --git a/app/Livewire/ForcePasswordReset.php b/app/Livewire/ForcePasswordReset.php index e4a66ebd6..7bbec9d32 100644 --- a/app/Livewire/ForcePasswordReset.php +++ b/app/Livewire/ForcePasswordReset.php @@ -24,7 +24,7 @@ class ForcePasswordReset extends Component } public function render() { - return view('livewire.force-password-reset'); + return view('livewire.force-password-reset')->layout('layouts.simple'); } public function submit() { diff --git a/app/Livewire/Project/Application/General.php b/app/Livewire/Project/Application/General.php index 718312d2d..58a5ee267 100644 --- a/app/Livewire/Project/Application/General.php +++ b/app/Livewire/Project/Application/General.php @@ -141,7 +141,7 @@ class General extends Component $this->initialDockerComposeLocation = $this->application->docker_compose_location; if ($this->application->build_pack === 'dockercompose' && !$this->application->docker_compose_raw) { $this->initLoadingCompose = true; - $this->dispatch('info', 'Loading docker compose file...'); + $this->dispatch('info', 'Loading docker compose file.'); } if (str($this->application->status)->startsWith('running') && is_null($this->application->config_hash)) { @@ -287,7 +287,7 @@ class General extends Component if ($this->application->additional_servers->count() === 0) { foreach ($domains as $domain) { if (!validate_dns_entry($domain, $this->application->destination->server)) { - $showToaster && $this->dispatch('error', "Validating DNS ($domain) failed.", "Make sure you have added the DNS records correctly.

Check this documentation for further help."); + $showToaster && $this->dispatch('error', "Validating DNS failed.", "Make sure you have added the DNS records correctly.

$domain->{$this->application->destination->server->ip}

Check this documentation for further help."); } } } @@ -352,7 +352,7 @@ class General extends Component $domain = data_get($service, 'domain'); if ($domain) { if (!validate_dns_entry($domain, $this->application->destination->server)) { - $showToaster && $this->dispatch('error', "Validating DNS ($domain) failed.", "Make sure you have added the DNS records correctly.

Check this documentation for further help."); + $showToaster && $this->dispatch('error', "Validating DNS failed.", "Make sure you have added the DNS records correctly.

$domain->{$this->application->destination->server->ip}

Check this documentation for further help."); } check_domain_usage(resource: $this->application); } diff --git a/app/Livewire/Project/Application/Previews.php b/app/Livewire/Project/Application/Previews.php index d057479ea..1f4a144a9 100644 --- a/app/Livewire/Project/Application/Previews.php +++ b/app/Livewire/Project/Application/Previews.php @@ -2,10 +2,12 @@ namespace App\Livewire\Project\Application; +use App\Actions\Docker\GetContainersStatus; use App\Models\Application; use App\Models\ApplicationPreview; use Illuminate\Support\Collection; use Livewire\Component; +use Spatie\Url\Url; use Visus\Cuid2\Cuid2; class Previews extends Component @@ -16,6 +18,9 @@ class Previews extends Component public Collection $pull_requests; public int $rate_limit_remaining; + protected $rules = [ + 'application.previews.*.fqdn' => 'string|nullable', + ]; public function mount() { $this->pull_requests = collect(); @@ -33,7 +38,71 @@ class Previews extends Component return handleError($e, $this); } } + public function save_preview($preview_id) + { + try { + $success = true; + $preview = $this->application->previews->find($preview_id); + if (isset($preview->fqdn)) { + $preview->fqdn = str($preview->fqdn)->replaceEnd(',', '')->trim(); + $preview->fqdn = str($preview->fqdn)->replaceStart(',', '')->trim(); + $preview->fqdn = str($preview->fqdn)->trim()->lower(); + if (!validate_dns_entry($preview->fqdn, $this->application->destination->server)) { + $this->dispatch('error', "Validating DNS failed.", "Make sure you have added the DNS records correctly.

$preview->fqdn->{$this->application->destination->server->ip}

Check this documentation for further help."); + $success = false; + } + check_domain_usage(resource: $this->application, domain: $preview->fqdn); + } + if (!$preview) { + throw new \Exception('Preview not found'); + } + $success && $preview->save(); + $success && $this->dispatch('success', 'Preview saved.

Do not forget to redeploy the preview to apply the changes.'); + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + public function generate_preview($preview_id) + { + $preview = $this->application->previews->find($preview_id); + if (!$preview) { + $this->dispatch('error', 'Preview not found.'); + return; + } + $fqdn = generateFqdn($this->application->destination->server, $this->application->uuid); + + $url = Url::fromString($fqdn); + $template = $this->application->preview_url_template; + $host = $url->getHost(); + $schema = $url->getScheme(); + $random = new Cuid2(7); + $preview_fqdn = str_replace('{{random}}', $random, $template); + $preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn); + $preview_fqdn = str_replace('{{pr_id}}', $preview_id, $preview_fqdn); + $preview_fqdn = "$schema://$preview_fqdn"; + $preview->fqdn = $preview_fqdn; + $preview->save(); + $this->dispatch('success', 'Domain generated.'); + } + public function add(int $pull_request_id, string|null $pull_request_html_url = null) + { + try { + $this->setDeploymentUuid(); + $found = ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->first(); + if (!$found && !is_null($pull_request_html_url)) { + ApplicationPreview::create([ + 'application_id' => $this->application->id, + 'pull_request_id' => $pull_request_id, + 'pull_request_html_url' => $pull_request_html_url + ]); + } + $this->application->generate_preview_fqdn($pull_request_id); + $this->application->refresh(); + } catch (\Throwable $e) { + return handleError($e, $this); + } + } public function deploy(int $pull_request_id, string|null $pull_request_html_url = null) { try { @@ -71,6 +140,25 @@ class Previews extends Component } public function stop(int $pull_request_id) + { + try { + if ($this->application->destination->server->isSwarm()) { + instant_remote_process(["docker stack rm {$this->application->uuid}-{$pull_request_id}"], $this->application->destination->server); + } else { + $containers = getCurrentApplicationContainerStatus($this->application->destination->server, $this->application->id, $pull_request_id); + foreach ($containers as $container) { + $name = str_replace('/', '', $container['Names']); + instant_remote_process(["docker rm -f $name"], $this->application->destination->server, throwError: false); + } + } + GetContainersStatus::dispatchSync($this->application->destination->server); + $this->application->refresh(); + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + + public function delete(int $pull_request_id) { try { if ($this->application->destination->server->isSwarm()) { diff --git a/app/Livewire/Project/Database/Backup/Execution.php b/app/Livewire/Project/Database/Backup/Execution.php index 000b6fb2b..ed015dbbf 100644 --- a/app/Livewire/Project/Database/Backup/Execution.php +++ b/app/Livewire/Project/Database/Backup/Execution.php @@ -2,12 +2,13 @@ namespace App\Livewire\Project\Database\Backup; +use App\Models\ScheduledDatabaseBackup; use Livewire\Component; class Execution extends Component { public $database; - public $backup; + public ?ScheduledDatabaseBackup $backup; public $executions; public $s3s; public function mount() diff --git a/app/Livewire/Project/Database/BackupEdit.php b/app/Livewire/Project/Database/BackupEdit.php index d7f7f5503..90eadfe43 100644 --- a/app/Livewire/Project/Database/BackupEdit.php +++ b/app/Livewire/Project/Database/BackupEdit.php @@ -2,12 +2,13 @@ namespace App\Livewire\Project\Database; +use App\Models\ScheduledDatabaseBackup; use Livewire\Component; use Spatie\Url\Url; class BackupEdit extends Component { - public $backup; + public ?ScheduledDatabaseBackup $backup; public $s3s; public ?string $status = null; public array $parameters; @@ -36,7 +37,7 @@ class BackupEdit extends Component { $this->parameters = get_route_parameters(); if (is_null(data_get($this->backup, 's3_storage_id'))) { - $this->backup->s3_storage_id = 'default'; + data_set($this->backup, 's3_storage_id', 'default'); } } diff --git a/app/Livewire/Project/Database/BackupExecutions.php b/app/Livewire/Project/Database/BackupExecutions.php index 101bb4593..5e9319cfd 100644 --- a/app/Livewire/Project/Database/BackupExecutions.php +++ b/app/Livewire/Project/Database/BackupExecutions.php @@ -2,11 +2,12 @@ namespace App\Livewire\Project\Database; +use App\Models\ScheduledDatabaseBackup; use Livewire\Component; class BackupExecutions extends Component { - public $backup; + public ?ScheduledDatabaseBackup $backup = null; public $executions = []; public $setDeletableBackup; public function getListeners() @@ -20,8 +21,11 @@ class BackupExecutions extends Component public function cleanupFailed() { - $this->backup?->executions()->where('status', 'failed')->delete(); - $this->refreshBackupExecutions(); + if ($this->backup) { + $this->backup->executions()->where('status', 'failed')->delete(); + $this->refreshBackupExecutions(); + $this->dispatch('success', 'Failed backups cleaned up.'); + } } public function deleteBackup($exeuctionId) { @@ -45,6 +49,8 @@ class BackupExecutions extends Component } public function refreshBackupExecutions(): void { - $this->executions = $this->backup->executions()->get()->sortByDesc('created_at'); + if ($this->backup) { + $this->executions = $this->backup->executions()->get()->sortByDesc('created_at'); + } } } diff --git a/app/Livewire/Project/Database/CreateScheduledBackup.php b/app/Livewire/Project/Database/CreateScheduledBackup.php index e58ea9df3..2b9aa987b 100644 --- a/app/Livewire/Project/Database/CreateScheduledBackup.php +++ b/app/Livewire/Project/Database/CreateScheduledBackup.php @@ -3,6 +3,7 @@ namespace App\Livewire\Project\Database; use App\Models\ScheduledDatabaseBackup; +use Illuminate\Support\Collection; use Livewire\Component; class CreateScheduledBackup extends Component @@ -12,7 +13,7 @@ class CreateScheduledBackup extends Component public bool $enabled = true; public bool $save_s3 = false; public $s3_storage_id; - public $s3s; + public Collection $s3s; protected $rules = [ 'frequency' => 'required|string', diff --git a/app/Livewire/Project/Database/ScheduledBackups.php b/app/Livewire/Project/Database/ScheduledBackups.php index 399fddac4..61c2a3bb1 100644 --- a/app/Livewire/Project/Database/ScheduledBackups.php +++ b/app/Livewire/Project/Database/ScheduledBackups.php @@ -2,6 +2,7 @@ namespace App\Livewire\Project\Database; +use App\Models\ScheduledDatabaseBackup; use Livewire\Component; class ScheduledBackups extends Component @@ -9,7 +10,7 @@ class ScheduledBackups extends Component public $database; public $parameters; public $type; - public $selectedBackup; + public ?ScheduledDatabaseBackup $selectedBackup; public $selectedBackupId; public $s3s; protected $listeners = ['refreshScheduledBackups']; diff --git a/app/Livewire/Project/New/Select.php b/app/Livewire/Project/New/Select.php index d4deebeb4..2b5ef9eca 100644 --- a/app/Livewire/Project/New/Select.php +++ b/app/Livewire/Project/New/Select.php @@ -15,10 +15,10 @@ class Select extends Component public string $type; public string $server_id; public string $destination_uuid; - public Countable|array|Server $allServers = []; - public Countable|array|Server $servers = []; - public Collection|array $standaloneDockers = []; - public Collection|array $swarmDockers = []; + public Collection|null|Server $allServers; + public Collection|null|Server $servers; + public ?Collection $standaloneDockers; + public ?Collection $swarmDockers; public array $parameters; public Collection|array $services = []; public Collection|array $allServices = []; @@ -91,7 +91,7 @@ class Select extends Component }); } else { $this->search = null; - $this->allServices = getServiceTemplates(); + $this->allServices = get_service_templates($force); $this->services = $this->allServices->filter(function ($service, $key) { return str_contains(strtolower($key), strtolower($this->search)); }); @@ -107,7 +107,11 @@ class Select extends Component if ($this->includeSwarm) { $this->servers = $this->allServers; } else { - $this->servers = $this->allServers->where('settings.is_swarm_worker', false)->where('settings.is_swarm_manager', false)->where('settings.is_build_server', false); + if ($this->allServers instanceof Collection) { + $this->servers = $this->allServers->where('settings.is_swarm_worker', false)->where('settings.is_swarm_manager', false)->where('settings.is_build_server', false); + } else { + $this->servers = $this->allServers; + } } } public function setType(string $type) @@ -126,13 +130,21 @@ class Select extends Component case 'mongodb': $this->isDatabase = true; $this->includeSwarm = false; - $this->servers = $this->allServers->where('settings.is_swarm_worker', false)->where('settings.is_swarm_manager', false)->where('settings.is_build_server', false); + if ($this->allServers instanceof Collection) { + $this->servers = $this->allServers->where('settings.is_swarm_worker', false)->where('settings.is_swarm_manager', false)->where('settings.is_build_server', false); + } else { + $this->servers = $this->allServers; + } break; } if (str($type)->startsWith('one-click-service') || str($type)->startsWith('docker-compose-empty')) { $this->isDatabase = true; $this->includeSwarm = false; - $this->servers = $this->allServers->where('settings.is_swarm_worker', false)->where('settings.is_swarm_manager', false)->where('settings.is_build_server', false); + if ($this->allServers instanceof Collection) { + $this->servers = $this->allServers->where('settings.is_swarm_worker', false)->where('settings.is_swarm_manager', false)->where('settings.is_build_server', false); + } else { + $this->servers = $this->allServers; + } } if ($type === "existing-postgresql") { $this->current_step = $type; diff --git a/app/Livewire/Project/Resource/Create.php b/app/Livewire/Project/Resource/Create.php index 8ea77950e..48c5b107d 100644 --- a/app/Livewire/Project/Resource/Create.php +++ b/app/Livewire/Project/Resource/Create.php @@ -25,7 +25,7 @@ class Create extends Component return redirect()->route('dashboard'); } if (isset($type) && isset($destination_uuid) && isset($server_id)) { - $services = getServiceTemplates(); + $services = get_service_templates(); if (in_array($type, DATABASE_TYPES)) { if ($type->value() === "postgresql") { diff --git a/app/Livewire/Project/Service/Configuration.php b/app/Livewire/Project/Service/Configuration.php index 86c9a8a31..eaa794a93 100644 --- a/app/Livewire/Project/Service/Configuration.php +++ b/app/Livewire/Project/Service/Configuration.php @@ -67,7 +67,6 @@ class Configuration extends Component GetContainersStatus::run($this->service->server); // dispatch_sync(new ContainerStatusJob($this->service->server)); $this->dispatch('refresh')->self(); - $this->dispatch('updateStatus'); } catch (\Exception $e) { return handleError($e, $this); } diff --git a/app/Livewire/Project/Service/FileStorage.php b/app/Livewire/Project/Service/FileStorage.php index 1703caf8f..f10c49794 100644 --- a/app/Livewire/Project/Service/FileStorage.php +++ b/app/Livewire/Project/Service/FileStorage.php @@ -7,13 +7,20 @@ use App\Models\LocalFileVolume; use App\Models\ServiceApplication; use App\Models\ServiceDatabase; use App\Models\StandaloneClickhouse; +use App\Models\StandaloneDragonfly; +use App\Models\StandaloneKeydb; +use App\Models\StandaloneMariadb; +use App\Models\StandaloneMongodb; +use App\Models\StandaloneMysql; +use App\Models\StandalonePostgresql; +use App\Models\StandaloneRedis; use Livewire\Component; use Illuminate\Support\Str; class FileStorage extends Component { public LocalFileVolume $fileStorage; - public ServiceApplication|ServiceDatabase|StandaloneClickhouse|Application $resource; + public ServiceApplication|StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse|ServiceDatabase|Application $resource; public string $fs_path; public ?string $workdir = null; @@ -27,7 +34,7 @@ class FileStorage extends Component { $this->resource = $this->fileStorage->service; if (Str::of($this->fileStorage->fs_path)->startsWith('.')) { - $this->workdir = $this->resource->service->workdir(); + $this->workdir = $this->resource->service?->workdir(); $this->fs_path = Str::of($this->fileStorage->fs_path)->after('.'); } else { $this->workdir = null; diff --git a/app/Livewire/Project/Service/Navbar.php b/app/Livewire/Project/Service/Navbar.php index 141859ed4..392178633 100644 --- a/app/Livewire/Project/Service/Navbar.php +++ b/app/Livewire/Project/Service/Navbar.php @@ -30,7 +30,6 @@ class Navbar extends Component $userId = auth()->user()->id; return [ "echo-private:user.{$userId},ServiceStatusChanged" => 'serviceStarted', - "updateStatus"=> '$refresh', ]; } public function serviceStarted() diff --git a/app/Livewire/Project/Service/StackForm.php b/app/Livewire/Project/Service/StackForm.php index 03bf99245..7eca5bf2d 100644 --- a/app/Livewire/Project/Service/StackForm.php +++ b/app/Livewire/Project/Service/StackForm.php @@ -42,7 +42,7 @@ class StackForm extends Component $this->validationAttributes["fields.$key.value"] = $fieldKey; } } - $this->fields = $this->fields->sortDesc(); + $this->fields = $this->fields->sortBy('name'); } public function saveCompose($raw) { @@ -52,7 +52,7 @@ class StackForm extends Component public function instantSave() { $this->service->save(); - $this->dispatch('success', 'Service settings saved.'); + $this->dispatch('success', 'Service settings saved.'); } public function submit() diff --git a/app/Livewire/Project/Shared/Danger.php b/app/Livewire/Project/Shared/Danger.php index 2ed764cd1..158549b06 100644 --- a/app/Livewire/Project/Shared/Danger.php +++ b/app/Livewire/Project/Shared/Danger.php @@ -24,6 +24,7 @@ class Danger extends Component public function delete() { try { + // $this->authorize('delete', $this->resource); $this->resource->delete(); DeleteResourceJob::dispatch($this->resource, $this->delete_configurations); return redirect()->route('project.resource.index', [ diff --git a/app/Livewire/Project/Shared/GetLogs.php b/app/Livewire/Project/Shared/GetLogs.php index e14cd6113..0060fa16e 100644 --- a/app/Livewire/Project/Shared/GetLogs.php +++ b/app/Livewire/Project/Shared/GetLogs.php @@ -43,6 +43,11 @@ class GetLogs extends Component $this->showTimeStamps = $this->resource->is_include_timestamps; } } + if ($this->resource?->getMorphClass() === 'App\Models\Application') { + if (str($this->container)->contains('-pr-')) { + $this->pull_request = "Pull Request: " . str($this->container)->afterLast('-pr-')->beforeLast('_')->value(); + } + } } } public function doSomethingWithThisChunkOfOutput($output) @@ -77,13 +82,6 @@ class GetLogs extends Component if (!$this->server->isFunctional()) { return; } - if ($this->resource?->getMorphClass() === 'App\Models\Application') { - if (str($this->container)->contains('-pr-')) { - $this->pull_request = "Pull Request: " . str($this->container)->afterLast('-pr-')->beforeLast('_')->value(); - } else { - $this->pull_request = 'branch'; - } - } if (!$refresh && ($this->resource?->getMorphClass() === 'App\Models\Service' || str($this->container)->contains('-pr-'))) return; if (!$this->numberOfLines) { $this->numberOfLines = 1000; diff --git a/app/Livewire/Project/Shared/Logs.php b/app/Livewire/Project/Shared/Logs.php index f1d70bf28..52a7b568d 100644 --- a/app/Livewire/Project/Shared/Logs.php +++ b/app/Livewire/Project/Shared/Logs.php @@ -103,6 +103,14 @@ class Logs extends Component } } $this->containers = $this->containers->sort(); + if (data_get($this->query,'pull_request_id')) { + $this->containers = $this->containers->filter(function ($container) { + return str_contains($container, $this->query['pull_request_id']); + }); + ray($this->containers); + + } + $this->loadMetrics(); } catch (\Exception $e) { return handleError($e, $this); diff --git a/app/Livewire/Project/Shared/Storages/Add.php b/app/Livewire/Project/Shared/Storages/Add.php index 2bfbf7baf..156078805 100644 --- a/app/Livewire/Project/Shared/Storages/Add.php +++ b/app/Livewire/Project/Shared/Storages/Add.php @@ -3,21 +3,31 @@ namespace App\Livewire\Project\Shared\Storages; use App\Models\Application; +use App\Models\LocalFileVolume; use Livewire\Component; class Add extends Component { + public $resource; public $uuid; public $parameters; public $isSwarm = false; public string $name; public string $mount_path; public ?string $host_path = null; + public string $file_storage_path; + public ?string $file_storage_content = null; + public string $file_storage_directory_source; + public string $file_storage_directory_destination; public $rules = [ 'name' => 'required|string', 'mount_path' => 'required|string', 'host_path' => 'string|nullable', + 'file_storage_path' => 'string', + 'file_storage_content' => 'nullable|string', + 'file_storage_directory_source' => 'string', + 'file_storage_directory_destination' => 'string', ]; protected $listeners = ['clearAddStorage' => 'clear']; @@ -26,10 +36,16 @@ class Add extends Component 'name' => 'name', 'mount_path' => 'mount', 'host_path' => 'host', + 'file_storage_path' => 'file storage path', + 'file_storage_content' => 'file storage content', + 'file_storage_directory_source' => 'file storage directory source', + 'file_storage_directory_destination' => 'file storage directory destination', ]; public function mount() { + $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')) { $applicationUuid = $this->parameters['application_uuid']; @@ -43,18 +59,75 @@ class Add extends Component } } } - - public function submit() + public function submitFileStorage() { try { - $this->validate($this->rules); + $this->validate([ + 'file_storage_path' => 'string', + 'file_storage_content' => 'nullable|string', + ]); + $this->file_storage_path = trim($this->file_storage_path); + $this->file_storage_path = str($this->file_storage_path)->start('/')->value(); + if ($this->resource->getMorphClass() === 'App\Models\Application') { + $fs_path = application_configuration_dir() . '/' . $this->resource->uuid . $this->file_storage_path; + } + LocalFileVolume::create( + [ + 'fs_path' => $fs_path, + 'mount_path' => $this->file_storage_path, + 'content' => $this->file_storage_content, + 'is_directory' => false, + 'resource_id' => $this->resource->id, + 'resource_type' => get_class($this->resource) + ], + ); + $this->dispatch('refresh_storages'); + } catch (\Throwable $e) { + return handleError($e, $this); + } + + } + public function submitFileStorageDirectory() + { + try { + $this->validate([ + 'file_storage_directory_source' => 'string', + 'file_storage_directory_destination' => 'string', + ]); + $this->file_storage_directory_source = trim($this->file_storage_directory_source); + $this->file_storage_directory_source = str($this->file_storage_directory_source)->start('/')->value(); + $this->file_storage_directory_destination = trim($this->file_storage_directory_destination); + $this->file_storage_directory_destination = str($this->file_storage_directory_destination)->start('/')->value(); + LocalFileVolume::create( + [ + 'fs_path' => $this->file_storage_directory_source, + 'mount_path' => $this->file_storage_directory_destination, + 'is_directory' => true, + 'resource_id' => $this->resource->id, + 'resource_type' => get_class($this->resource) + ], + ); + $this->dispatch('refresh_storages'); + } catch (\Throwable $e) { + return handleError($e, $this); + } + + } + public function submitPersistentVolume() + { + try { + $this->validate([ + 'name' => 'required|string', + 'mount_path' => 'required|string', + 'host_path' => 'string|nullable', + ]); $name = $this->uuid . '-' . $this->name; $this->dispatch('addNewVolume', [ 'name' => $name, 'mount_path' => $this->mount_path, 'host_path' => $this->host_path, ]); - $this->dispatch('closeStorageModal'); + } catch (\Throwable $e) { return handleError($e, $this); } diff --git a/app/Livewire/Project/Shared/Storages/Show.php b/app/Livewire/Project/Shared/Storages/Show.php index 5e35b796a..283930174 100644 --- a/app/Livewire/Project/Shared/Storages/Show.php +++ b/app/Livewire/Project/Shared/Storages/Show.php @@ -12,6 +12,8 @@ class Show extends Component public bool $isReadOnly = false; public ?string $modalId = null; public bool $isFirst = true; + public bool $isService = false; + public ?string $startedAt = null; protected $rules = [ 'storage.name' => 'required|string', diff --git a/app/Livewire/Project/Shared/Webhooks.php b/app/Livewire/Project/Shared/Webhooks.php index 6bb9428d5..35a383ece 100644 --- a/app/Livewire/Project/Shared/Webhooks.php +++ b/app/Livewire/Project/Shared/Webhooks.php @@ -11,10 +11,12 @@ class Webhooks extends Component public ?string $githubManualWebhook = null; public ?string $gitlabManualWebhook = null; public ?string $bitbucketManualWebhook = null; + public ?string $giteaManualWebhook = null; protected $rules = [ 'resource.manual_webhook_secret_github' => 'nullable|string', 'resource.manual_webhook_secret_gitlab' => 'nullable|string', 'resource.manual_webhook_secret_bitbucket' => 'nullable|string', + 'resource.manual_webhook_secret_gitea' => 'nullable|string', ]; public function saveSecret() { @@ -32,6 +34,7 @@ class Webhooks extends Component $this->githubManualWebhook = generateGitManualWebhook($this->resource, 'github'); $this->gitlabManualWebhook = generateGitManualWebhook($this->resource, 'gitlab'); $this->bitbucketManualWebhook = generateGitManualWebhook($this->resource, 'bitbucket'); + $this->giteaManualWebhook = generateGitManualWebhook($this->resource, 'gitea'); } public function render() { diff --git a/app/Livewire/Server/Form.php b/app/Livewire/Server/Form.php index c1dcd34ce..44f016aca 100644 --- a/app/Livewire/Server/Form.php +++ b/app/Livewire/Server/Form.php @@ -58,6 +58,12 @@ class Form extends Component $this->server->refresh(); $this->server->settings->refresh(); } + public function updatedServerSettingsIsBuildServer() + { + $this->dispatch('serverInstalled'); + $this->dispatch('serverRefresh'); + $this->dispatch('proxyStatusUpdated'); + } public function instantSave() { try { diff --git a/app/Livewire/Server/New/ByIp.php b/app/Livewire/Server/New/ByIp.php index 1ce3df273..c56e9bec6 100644 --- a/app/Livewire/Server/New/ByIp.php +++ b/app/Livewire/Server/New/ByIp.php @@ -97,6 +97,9 @@ class ByIp extends Component if ($this->is_swarm_worker) { $payload['swarm_cluster'] = $this->selected_swarm_cluster; } + if ($this->is_build_server) { + data_forget($payload, 'proxy'); + } $server = Server::create($payload); if ($this->is_build_server) { $this->is_swarm_manager = false; diff --git a/app/Livewire/Server/Proxy/Deploy.php b/app/Livewire/Server/Proxy/Deploy.php index 82925c396..5587451a4 100644 --- a/app/Livewire/Server/Proxy/Deploy.php +++ b/app/Livewire/Server/Proxy/Deploy.php @@ -72,6 +72,8 @@ class Deploy extends Component public function startProxy() { try { + $this->server->proxy->force_stop = false; + $this->server->save(); $activity = StartProxy::run($this->server); $this->dispatch('activityMonitor', $activity->id, ProxyStatusChanged::class); } catch (\Throwable $e) { @@ -86,17 +88,15 @@ class Deploy extends Component instant_remote_process([ "docker service rm coolify-proxy_traefik", ], $this->server); - $this->server->proxy->status = 'exited'; - $this->server->save(); - $this->dispatch('proxyStatusUpdated'); } else { instant_remote_process([ "docker rm -f coolify-proxy", ], $this->server); - $this->server->proxy->status = 'exited'; - $this->server->save(); - $this->dispatch('proxyStatusUpdated'); } + $this->server->proxy->status = 'exited'; + $this->server->proxy->force_stop = true; + $this->server->save(); + $this->dispatch('proxyStatusUpdated'); } catch (\Throwable $e) { return handleError($e, $this); } diff --git a/app/Livewire/Server/ValidateAndInstall.php b/app/Livewire/Server/ValidateAndInstall.php index b148fe2c4..aef7b800c 100644 --- a/app/Livewire/Server/ValidateAndInstall.php +++ b/app/Livewire/Server/ValidateAndInstall.php @@ -129,6 +129,9 @@ class ValidateAndInstall extends Component } } + if ($this->server->isBuildServer()) { + return; + } $this->dispatch('startProxy'); } public function render() diff --git a/app/Livewire/Settings/Backup.php b/app/Livewire/Settings/Backup.php index 121b73af8..82b3075c0 100644 --- a/app/Livewire/Settings/Backup.php +++ b/app/Livewire/Settings/Backup.php @@ -36,7 +36,7 @@ class Backup extends Component public function mount() { - $this->backup = $this->database?->scheduledBackups->first() ?? []; + $this->backup = $this->database?->scheduledBackups->first() ?? null; $this->executions = $this->backup?->executions ?? []; } public function add_coolify_database() diff --git a/app/Livewire/Settings/Configuration.php b/app/Livewire/Settings/Configuration.php index 54dbe1bdb..68dc59a7f 100644 --- a/app/Livewire/Settings/Configuration.php +++ b/app/Livewire/Settings/Configuration.php @@ -70,9 +70,8 @@ class Configuration extends Component $this->validate(); if ($this->settings->is_dns_validation_enabled && $this->settings->fqdn) { - ray('asdf'); if (!validate_dns_entry($this->settings->fqdn, $this->server)) { - $this->dispatch('error', "Validating DNS ({$this->settings->fqdn}) failed.

Make sure you have added the DNS records correctly.

Check this documentation for further help."); + $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; } } diff --git a/app/Livewire/Team/AdminView.php b/app/Livewire/Team/AdminView.php index 12546ff1b..97bc6c04f 100644 --- a/app/Livewire/Team/AdminView.php +++ b/app/Livewire/Team/AdminView.php @@ -10,6 +10,8 @@ class AdminView extends Component { public $users; public ?string $search = ""; + public bool $lots_of_users = false; + private $number_of_users_to_show = 20; public function mount() { if (!isInstanceAdmin()) { @@ -32,8 +34,14 @@ class AdminView extends Component } public function getUsers() { - $this->users = User::where('id', '!=', auth()->id())->get(); - // $this->users = User::all(); + $users = User::where('id', '!=', auth()->id())->get(); + if ($users->count() > $this->number_of_users_to_show) { + $this->lots_of_users = true; + $this->users = $users->take($this->number_of_users_to_show); + } else { + $this->lots_of_users = false; + $this->users = $users; + } } private function finalizeDeletion(User $user, Team $team) { @@ -59,6 +67,9 @@ class AdminView extends Component } public function delete($id) { + if (!auth()->user()->isInstanceAdmin()) { + return $this->dispatch('error', 'You are not authorized to delete users'); + } $user = User::find($id); $teams = $user->teams; foreach ($teams as $team) { diff --git a/app/Livewire/Team/InviteLink.php b/app/Livewire/Team/InviteLink.php index cc9054888..c03bb0c45 100644 --- a/app/Livewire/Team/InviteLink.php +++ b/app/Livewire/Team/InviteLink.php @@ -17,6 +17,10 @@ class InviteLink extends Component public string $email; public string $role = 'member'; + protected $rules = [ + 'email' => 'required|email', + 'role' => 'required|string', + ]; public function mount() { $this->email = isDev() ? 'test3@example.com' : ''; @@ -34,6 +38,7 @@ class InviteLink extends Component private function generate_invite_link(bool $sendEmail = false) { try { + $this->validate(); $member_emails = currentTeam()->members()->get()->pluck('email'); if ($member_emails->contains($this->email)) { return handleError(livewire: $this, customErrorMessage: "$this->email is already a member of " . currentTeam()->name . "."); diff --git a/app/Livewire/Upgrade.php b/app/Livewire/Upgrade.php index 5ef966f43..e81ee93e6 100644 --- a/app/Livewire/Upgrade.php +++ b/app/Livewire/Upgrade.php @@ -5,11 +5,9 @@ namespace App\Livewire; use App\Actions\Server\UpdateCoolify; use Livewire\Component; -use DanHarrin\LivewireRateLimiting\WithRateLimiting; class Upgrade extends Component { - use WithRateLimiting; public bool $showProgress = false; public bool $updateInProgress = false; public bool $isUpgradeAvailable = false; @@ -31,9 +29,8 @@ class Upgrade extends Component if ($this->updateInProgress) { return; } - $this->rateLimit(1, 60); $this->updateInProgress = true; - UpdateCoolify::run(force: true, async: true); + UpdateCoolify::run(manual_update: true); } catch (\Throwable $e) { return handleError($e, $this); } diff --git a/app/Models/Application.php b/app/Models/Application.php index 0f3425dd6..e0ed328f9 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -10,6 +10,7 @@ use Illuminate\Support\Collection; use Spatie\Activitylog\Models\Activity; use Illuminate\Support\Str; use RuntimeException; +use Spatie\Url\Url; use Symfony\Component\Yaml\Yaml; use Visus\Cuid2\Cuid2; @@ -213,6 +214,13 @@ class Application extends BaseModel $git_repository = str_replace(['git@', ':', '.git'], ['', '/', ''], $this->git_repository); return "https://{$git_repository}/commit/{$link}"; } + if (str($this->git_repository)->contains('bitbucket')) { + $git_repository = str_replace('.git', '', $this->git_repository); + $url = Url::fromString($git_repository); + $url = $url->withUserInfo(''); + $url = $url->withPath($url->getPath() . '/commits/' . $link); + return $url->__toString(); + } return $this->git_repository; } public function dockerfileLocation(): Attribute @@ -661,7 +669,9 @@ class Application extends BaseModel $git_clone_command = "{$git_clone_command} $source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository} {$baseDir}"; $fullRepoUrl = "$source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository}"; } - $git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command, public: false); + if (!$only_checkout) { + $git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command, public: false); + } if ($exec_in_docker) { $commands->push(executeInDocker($deployment_uuid, $git_clone_command)); } else { @@ -880,7 +890,8 @@ class Application extends BaseModel // } $commands = collect([ "rm -rf /tmp/{$uuid}", - "mkdir -p /tmp/{$uuid} && cd /tmp/{$uuid}", + "mkdir -p /tmp/{$uuid}", + "cd /tmp/{$uuid}", $cloneCommand, "git sparse-checkout init --cone", "git sparse-checkout set {$fileList->implode(' ')}", @@ -891,29 +902,15 @@ class Application extends BaseModel if (!$composeFileContent) { $this->docker_compose_location = $initialDockerComposeLocation; $this->save(); + $commands = collect([ + "rm -rf /tmp/{$uuid}", + ]); + instant_remote_process($commands, $this->destination->server, false); throw new \RuntimeException("Docker Compose file not found at: $workdir$composeFile

Check if you used the right extension (.yaml or .yml) in the compose file name."); } else { $this->docker_compose_raw = $composeFileContent; $this->save(); } - // if ($composeFile === $prComposeFile) { - // $this->docker_compose_pr_raw = $composeFileContent; - // $this->save(); - // } else { - // $commands = collect([ - // "cd /tmp/{$uuid}", - // "cat .$workdir$prComposeFile", - // ]); - // $composePrFileContent = instant_remote_process($commands, $this->destination->server, false); - // if (!$composePrFileContent) { - // $this->docker_compose_pr_location = $initialDockerComposePrLocation; - // $this->save(); - // throw new \Exception("Could not load compose file from $workdir$prComposeFile"); - // } else { - // $this->docker_compose_pr_raw = $composePrFileContent; - // $this->save(); - // } - // } $commands = collect([ "rm -rf /tmp/{$uuid}", @@ -1055,4 +1052,29 @@ class Application extends BaseModel } } } + function generate_preview_fqdn(int $pull_request_id) { + $preview = ApplicationPreview::findPreviewByApplicationAndPullId($this->id, $pull_request_id); + if (is_null(data_get($preview, 'fqdn')) && $this->fqdn) { + if (str($this->fqdn)->contains(',')) { + $url = Url::fromString(str($this->fqdn)->explode(',')[0]); + $preview_fqdn = getFqdnWithoutPort(str($this->fqdn)->explode(',')[0]); + } else { + $url = Url::fromString($this->fqdn); + if (data_get($preview, 'fqdn')) { + $preview_fqdn = getFqdnWithoutPort(data_get($preview, 'fqdn')); + } + } + $template = $this->preview_url_template; + $host = $url->getHost(); + $schema = $url->getScheme(); + $random = new Cuid2(7); + $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); + $preview_fqdn = "$schema://$preview_fqdn"; + $preview->fqdn = $preview_fqdn; + $preview->save(); + } + return $preview; + } } diff --git a/app/Models/Server.php b/app/Models/Server.php index 2f4c29080..38c427dc4 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -525,7 +525,7 @@ $schema://$host { // Reached max number of retries if ($this->unreachable_notification_sent === false) { ray('Server unreachable, sending notification...'); - $this->team?->notify(new Unreachable($this)); + // $this->team?->notify(new Unreachable($this)); $this->update(['unreachable_notification_sent' => true]); } if ($this->settings->is_reachable === true) { @@ -825,7 +825,7 @@ $schema://$host { 'unreachable_count' => 0, ]); if (data_get($server, 'unreachable_notification_sent') === true) { - $server->team?->notify(new Revived($server)); + // $server->team?->notify(new Revived($server)); $server->update(['unreachable_notification_sent' => false]); } return ['uptime' => true, 'error' => null]; @@ -897,7 +897,9 @@ $schema://$host { } public function validateDockerEngineVersion() { - $dockerVersion = instant_remote_process(["docker version|head -2|grep -i version| awk '{print $2}'"], $this, false); + $dockerVersionRaw = instant_remote_process(["docker version --format json"], $this, false); + $dockerVersionJson = json_decode($dockerVersionRaw, true); + $dockerVersion = data_get($dockerVersionJson, 'Server.Version', '0.0.0'); $dockerVersion = checkMinimumDockerEngineVersion($dockerVersion); if (is_null($dockerVersion)) { $this->settings->is_usable = false; @@ -927,4 +929,7 @@ $schema://$host { } return $this->user !== 'root'; } + public function isBuildServer() { + return $this->settings->is_build_server; + } } diff --git a/app/Models/Service.php b/app/Models/Service.php index 4c20b71de..ac7c15dcf 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -472,6 +472,72 @@ class Service extends BaseModel } $fields->put('Admin', $data->toArray()); break; + case str($image)?->contains('vaultwarden'): + $data = collect([]); + + $DATABASE_URL = $this->environment_variables()->where('key', 'DATABASE_URL')->first(); + $ADMIN_TOKEN = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_64_ADMIN')->first(); + $SIGNUP_ALLOWED = $this->environment_variables()->where('key', 'SIGNUP_ALLOWED')->first(); + $PUSH_ENABLED = $this->environment_variables()->where('key', 'PUSH_ENABLED')->first(); + $PUSH_INSTALLATION_ID = $this->environment_variables()->where('key', 'PUSH_SERVICE_ID')->first(); + $PUSH_INSTALLATION_KEY = $this->environment_variables()->where('key', 'PUSH_SERVICE_KEY')->first(); + + if ($DATABASE_URL) { + $data = $data->merge([ + 'Database URL' => [ + 'key' => data_get($DATABASE_URL, 'key'), + 'value' => data_get($DATABASE_URL, 'value'), + ], + ]); + } + if ($ADMIN_TOKEN) { + $data = $data->merge([ + 'Admin Password' => [ + 'key' => data_get($ADMIN_TOKEN, 'key'), + 'value' => data_get($ADMIN_TOKEN, 'value'), + 'isPassword' => true, + ], + ]); + } + if ($SIGNUP_ALLOWED) { + $data = $data->merge([ + 'Signup Allowed' => [ + 'key' => data_get($SIGNUP_ALLOWED, 'key'), + 'value' => data_get($SIGNUP_ALLOWED, 'value'), + 'rules' => 'required|string|in:true,false', + ], + ]); + } + + if ($PUSH_ENABLED) { + $data = $data->merge([ + 'Push Enabled' => [ + 'key' => data_get($PUSH_ENABLED, 'key'), + 'value' => data_get($PUSH_ENABLED, 'value'), + 'rules' => 'required|string|in:true,false', + ], + ]); + } + if ($PUSH_INSTALLATION_ID) { + $data = $data->merge([ + 'Push Installation ID' => [ + 'key' => data_get($PUSH_INSTALLATION_ID, 'key'), + 'value' => data_get($PUSH_INSTALLATION_ID, 'value'), + ], + ]); + } + if ($PUSH_INSTALLATION_KEY) { + $data = $data->merge([ + 'Push Installation Key' => [ + 'key' => data_get($PUSH_INSTALLATION_KEY, 'key'), + 'value' => data_get($PUSH_INSTALLATION_KEY, 'value'), + 'isPassword' => true, + ], + ]); + } + + $fields->put('Vaultwarden', $data); + break; } } $databases = $this->databases()->get(); @@ -659,7 +725,7 @@ class Service extends BaseModel return route('project.service.scheduled-tasks', [ 'project_uuid' => data_get($this, 'environment.project.uuid'), 'environment_name' => data_get($this, 'environment.name'), - 'application_uuid' => data_get($this, 'uuid'), + 'service_uuid' => data_get($this, 'uuid'), 'task_uuid' => $task_uuid ]); } @@ -667,7 +733,7 @@ class Service extends BaseModel } public function documentation() { - $services = getServiceTemplates(); + $services = get_service_templates(); $service = data_get($services, str($this->name)->beforeLast('-')->value, []); return data_get($service, 'documentation', config('constants.docs.base_url')); } diff --git a/app/Models/ServiceApplication.php b/app/Models/ServiceApplication.php index 820ef6fee..f8fcda004 100644 --- a/app/Models/ServiceApplication.php +++ b/app/Models/ServiceApplication.php @@ -40,6 +40,13 @@ class ServiceApplication extends BaseModel { return 'service'; } + public function team() + { + return data_get($this, 'environment.project.team'); + } + public function workdir() { + return service_configuration_dir() . "/{$this->service->uuid}"; + } public function serviceType() { $found = str(collect(SPECIFIC_SERVICES)->filter(function ($service) { diff --git a/app/Models/ServiceDatabase.php b/app/Models/ServiceDatabase.php index 76c174d08..9d90641e1 100644 --- a/app/Models/ServiceDatabase.php +++ b/app/Models/ServiceDatabase.php @@ -59,6 +59,13 @@ class ServiceDatabase extends BaseModel } return "{$realIp}:{$port}"; } + public function team() + { + return data_get($this, 'environment.project.team'); + } + public function workdir() { + return service_configuration_dir() . "/{$this->service->uuid}"; + } public function service() { return $this->belongsTo(Service::class); diff --git a/app/Policies/ApplicationPolicy.php b/app/Policies/ApplicationPolicy.php new file mode 100644 index 000000000..860479a94 --- /dev/null +++ b/app/Policies/ApplicationPolicy.php @@ -0,0 +1,69 @@ +isAdmin()) { + return true; + } + return false; + } + + /** + * Determine whether the user can restore the model. + */ + public function restore(User $user, Application $application): bool + { + return true; + } + + /** + * Determine whether the user can permanently delete the model. + */ + public function forceDelete(User $user, Application $application): bool + { + return true; + } +} diff --git a/app/Policies/ServicePolicy.php b/app/Policies/ServicePolicy.php new file mode 100644 index 000000000..93882be9a --- /dev/null +++ b/app/Policies/ServicePolicy.php @@ -0,0 +1,79 @@ +isAdmin()) { + return true; + } + return false; + } + + /** + * Determine whether the user can restore the model. + */ + public function restore(User $user, Service $service): bool + { + return true; + } + + /** + * Determine whether the user can permanently delete the model. + */ + public function forceDelete(User $user, Service $service): bool + { + if ($user->isAdmin()) { + return true; + } + return false; + } + public function stop(User $user, Service $service): bool + { + if ($user->isAdmin()) { + return true; + } + return false; + } +} diff --git a/bootstrap/helpers/applications.php b/bootstrap/helpers/applications.php index a1995c645..39d21bcca 100644 --- a/bootstrap/helpers/applications.php +++ b/bootstrap/helpers/applications.php @@ -43,16 +43,10 @@ function queue_application_deployment(Application $application, string $deployme ]); if ($no_questions_asked) { - $deployment->update([ - 'status' => ApplicationDeploymentStatus::IN_PROGRESS->value, - ]); dispatch(new ApplicationDeploymentJob( application_deployment_queue_id: $deployment->id, )); } else if (next_queuable($server_id, $application_id)) { - $deployment->update([ - 'status' => ApplicationDeploymentStatus::IN_PROGRESS->value, - ]); dispatch(new ApplicationDeploymentJob( application_deployment_queue_id: $deployment->id, )); @@ -63,6 +57,7 @@ function force_start_deployment(ApplicationDeploymentQueue $deployment) $deployment->update([ 'status' => ApplicationDeploymentStatus::IN_PROGRESS->value, ]); + dispatch(new ApplicationDeploymentJob( application_deployment_queue_id: $deployment->id, )); @@ -75,6 +70,7 @@ function queue_next_deployment(Application $application) $next_found->update([ 'status' => ApplicationDeploymentStatus::IN_PROGRESS->value, ]); + dispatch(new ApplicationDeploymentJob( application_deployment_queue_id: $next_found->id, )); diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php index a087c92c5..0ce578758 100644 --- a/bootstrap/helpers/docker.php +++ b/bootstrap/helpers/docker.php @@ -173,14 +173,14 @@ function defaultLabels($id, $name, $pull_request_id = 0, string $type = 'applica function generateServiceSpecificFqdns(ServiceApplication|Application $resource) { if ($resource->getMorphClass() === 'App\Models\ServiceApplication') { - $uuid = $resource->uuid; - $server = $resource->service->server; - $environment_variables = $resource->service->environment_variables; + $uuid = data_get($resource, 'uuid'); + $server = data_get($resource, 'service.server'); + $environment_variables = data_get($resource, 'service.environment_variables'); $type = $resource->serviceType(); } else if ($resource->getMorphClass() === 'App\Models\Application') { - $uuid = $resource->uuid; - $server = $resource->destination->server; - $environment_variables = $resource->environment_variables; + $uuid = data_get($resource, 'uuid'); + $server = data_get($resource, 'destination.server'); + $environment_variables = data_get($resource, 'environment_variables'); $type = $resource->serviceType(); } if (is_null($server) || is_null($type)) { @@ -234,7 +234,7 @@ function generateServiceSpecificFqdns(ServiceApplication|Application $resource) } return $payload; } -function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true, ?bool $is_stripprefix_enabled = true, ?string $service_name = null) +function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true, ?bool $is_stripprefix_enabled = true, ?string $service_name = null, ?string $image = null) { $labels = collect([]); if ($serviceLabels) { @@ -247,7 +247,6 @@ function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains, $url = Url::fromString($domain); $host = $url->getHost(); $path = $url->getPath(); - // $stripped_path = str($path)->replaceEnd('/', ''); $schema = $url->getScheme(); $port = $url->getPort(); @@ -273,7 +272,7 @@ function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains, } return $labels->sort(); } -function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true, ?bool $is_stripprefix_enabled = true, ?string $service_name = null, bool $generate_unique_uuid = false) +function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true, ?bool $is_stripprefix_enabled = true, ?string $service_name = null, bool $generate_unique_uuid = false, ?string $image = null) { $labels = collect([]); $labels->push('traefik.enable=true'); @@ -331,7 +330,10 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_ $http_label = "http-{$loop}-{$uuid}-{$service_name}"; $https_label = "https-{$loop}-{$uuid}-{$service_name}"; } - + if (str($image)->contains('ghost')) { + $labels->push("traefik.http.middlewares.redir-ghost.redirectregex.regex=^{$path}/(.*)"); + $labels->push("traefik.http.middlewares.redir-ghost.redirectregex.replacement=/$1"); + } if ($schema === 'https') { // Set labels for https $labels->push("traefik.http.routers.{$https_label}.rule=Host(`{$host}`) && PathPrefix(`{$path}`)"); @@ -341,9 +343,10 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_ $labels->push("traefik.http.services.{$https_label}.loadbalancer.server.port=$port"); } if ($path !== '/') { - if ($is_stripprefix_enabled) { + $middlewares = collect([]); + if ($is_stripprefix_enabled && !str($image)->contains('ghost')) { $labels->push("traefik.http.middlewares.{$https_label}-stripprefix.stripprefix.prefixes={$path}"); - $middlewares = collect(["{$https_label}-stripprefix"]); + $middlewares->push("{$https_label}-stripprefix"); } if ($is_gzip_enabled) { $middlewares->push('gzip'); @@ -354,6 +357,9 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_ if ($redirect && $redirect_middleware) { $middlewares->push($redirect_middleware); } + if (str($image)->contains('ghost')) { + $middlewares->push('redir-ghost'); + } if ($middlewares->isNotEmpty()) { $middlewares = $middlewares->join(','); $labels->push("traefik.http.routers.{$https_label}.middlewares={$middlewares}"); @@ -369,6 +375,9 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_ if ($redirect && $redirect_middleware) { $middlewares->push($redirect_middleware); } + if (str($image)->contains('ghost')) { + $middlewares->push('redir-ghost'); + } if ($middlewares->isNotEmpty()) { $middlewares = $middlewares->join(','); $labels->push("traefik.http.routers.{$https_label}.middlewares={$middlewares}"); @@ -396,9 +405,10 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_ $labels->push("traefik.http.routers.{$http_label}.service={$http_label}"); } if ($path !== '/') { - if ($is_stripprefix_enabled) { + $middlewares = collect([]); + if ($is_stripprefix_enabled && !str($image)->contains('ghost')) { $labels->push("traefik.http.middlewares.{$http_label}-stripprefix.stripprefix.prefixes={$path}"); - $middlewares = collect(["{$http_label}-stripprefix"]); + $middlewares->push("{$https_label}-stripprefix"); } if ($is_gzip_enabled) { $middlewares->push('gzip'); @@ -409,6 +419,9 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_ if ($redirect && $redirect_middleware) { $middlewares->push($redirect_middleware); } + if (str($image)->contains('ghost')) { + $middlewares->push('redir-ghost'); + } if ($middlewares->isNotEmpty()) { $middlewares = $middlewares->join(','); $labels->push("traefik.http.routers.{$http_label}.middlewares={$middlewares}"); @@ -424,6 +437,9 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_ if ($redirect && $redirect_middleware) { $middlewares->push($redirect_middleware); } + if (str($image)->contains('ghost')) { + $middlewares->push('redir-ghost'); + } if ($middlewares->isNotEmpty()) { $middlewares = $middlewares->join(','); $labels->push("traefik.http.routers.{$http_label}.middlewares={$middlewares}"); @@ -449,13 +465,32 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview $appUuid = $appUuid . '-pr-' . $pull_request_id; } $labels = collect([]); - if ($application->fqdn) { - if ($pull_request_id !== 0) { - $domains = Str::of(data_get($preview, 'fqdn'))->explode(','); - } else { + if ($pull_request_id === 0) { + if ($application->fqdn) { $domains = Str::of(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() + )); + // 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() + )); + } + } else { + if ($preview->fqdn) { + $domains = Str::of(data_get($preview, 'fqdn'))->explode(','); } - // Add Traefik labels $labels = $labels->merge(fqdnLabelsForTraefik( uuid: $appUuid, domains: $domains, @@ -474,6 +509,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview is_gzip_enabled: $application->isGzipEnabled(), is_stripprefix_enabled: $application->isStripprefixEnabled() )); + } return $labels->all(); } diff --git a/bootstrap/helpers/services.php b/bootstrap/helpers/services.php index 26a69222a..5021071d8 100644 --- a/bootstrap/helpers/services.php +++ b/bootstrap/helpers/services.php @@ -110,16 +110,18 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource) $generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first(); $fqdn = Url::fromString($resourceFqdns); $port = $fqdn->getPort(); + $path = $fqdn->getPath(); $fqdn = $fqdn->getScheme() . '://' . $fqdn->getHost(); if ($generatedEnv) { - $generatedEnv->value = $fqdn; + $generatedEnv->value = $fqdn . $path; $generatedEnv->save(); } if ($port) { $variableName = $variableName . "_$port"; $generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first(); + // ray($generatedEnv); if ($generatedEnv) { - $generatedEnv->value = $fqdn . ':' . $port; + $generatedEnv->value = $fqdn . $path; $generatedEnv->save(); } } @@ -127,17 +129,18 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource) $generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first(); $url = Url::fromString($fqdn); $port = $url->getPort(); + $path = $url->getPath(); $url = $url->getHost(); if ($generatedEnv) { $url = Str::of($fqdn)->after('://'); - $generatedEnv->value = $url; + $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 . ':' . $port; + $generatedEnv->value = $url . $path; $generatedEnv->save(); } } @@ -146,6 +149,7 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource) $host = Url::fromString($fqdn); $port = $host->getPort(); $url = $host->getHost(); + $path = $host->getPath(); $host = $host->getScheme() . '://' . $host->getHost(); if ($port) { $port_envs = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', 'like', "SERVICE_FQDN_%_$port")->get(); @@ -153,10 +157,10 @@ 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; + $env->value = $host . $path; $env->save(); } - $port_env->value = $host . ':' . $port; + $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(); @@ -164,17 +168,17 @@ 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; + $env->value = $url . $path; $env->save(); } - $port_env_url->value = $url . ':' . $port; + $port_env_url->value = $url . $path; $port_env_url->save(); } } else { $variableName = "SERVICE_FQDN_" . Str::of($resource->name)->upper()->replace('-', ''); $generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first(); $fqdn = Url::fromString($fqdn); - $fqdn = $fqdn->getScheme() . '://' . $fqdn->getHost(); + $fqdn = $fqdn->getScheme() . '://' . $fqdn->getHost() . $fqdn->getPath(); if ($generatedEnv) { $generatedEnv->value = $fqdn; $generatedEnv->save(); @@ -182,7 +186,7 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource) $variableName = "SERVICE_URL_" . Str::of($resource->name)->upper()->replace('-', ''); $generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first(); $url = Url::fromString($fqdn); - $url = $url->getHost(); + $url = $url->getHost() . $url->getPath(); if ($generatedEnv) { $url = Str::of($fqdn)->after('://'); $generatedEnv->value = $url; diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index 6453108eb..5c65eb218 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -165,9 +165,12 @@ function get_latest_sentinel_version(): string function get_latest_version_of_coolify(): string { try { - $response = Http::get('https://cdn.coollabs.io/coolify/versions.json'); - $versions = $response->json(); + $versions = File::get(base_path('versions.json')); + $versions = json_decode($versions, true); return data_get($versions, 'coolify.v4.version'); + // $response = Http::get('https://cdn.coollabs.io/coolify/versions.json'); + // $versions = $response->json(); + // return data_get($versions, 'coolify.v4.version'); } catch (\Throwable $e) { //throw $e; ray($e->getMessage()); @@ -462,24 +465,24 @@ function sslip(Server $server) return "http://{$server->ip}.sslip.io"; } -function getServiceTemplates() +function get_service_templates(bool $force = false): Collection { - if (isDev()) { - $services = File::get(base_path('templates/service-templates.json')); - $services = collect(json_decode($services))->sortKeys(); - } else { + if ($force) { try { $response = Http::retry(3, 50)->get(config('constants.services.official')); if ($response->failed()) { return collect([]); } $services = $response->json(); - $services = collect($services)->sortKeys(); + return collect($services); } catch (\Throwable $e) { - $services = collect([]); + $services = File::get(base_path('templates/service-templates.json')); + return collect(json_decode($services))->sortKeys(); } + } else { + $services = File::get(base_path('templates/service-templates.json')); + return collect(json_decode($services))->sortKeys(); } - return $services; } function getResourceByUuid(string $uuid, ?int $teamId = null) @@ -573,6 +576,13 @@ function getTopLevelNetworks(Service|Application $resource) // Collect/create/update networks if ($serviceNetworks->count() > 0) { foreach ($serviceNetworks as $networkName => $networkDetails) { + if ($networkName === 'default') { + continue; + } + // ignore alias + if ($networkDetails['aliases'] ?? false) { + continue; + } $networkExists = $topLevelNetworks->contains(function ($value, $key) use ($networkName) { return $value == $networkName || $key == $networkName; }); @@ -615,6 +625,13 @@ function getTopLevelNetworks(Service|Application $resource) // Collect/create/update networks if ($serviceNetworks->count() > 0) { foreach ($serviceNetworks as $networkName => $networkDetails) { + if ($networkName === 'default') { + continue; + } + // ignore alias + if ($networkDetails['aliases'] ?? false) { + continue; + } $networkExists = $topLevelNetworks->contains(function ($value, $key) use ($networkName) { return $value == $networkName || $key == $networkName; }); @@ -639,9 +656,10 @@ function getTopLevelNetworks(Service|Application $resource) return $topLevelNetworks->keys(); } } + function parseDockerComposeFile(Service|Application $resource, bool $isNew = false, int $pull_request_id = 0, bool $is_pr = false) { - // ray()->clearAll(); + ray()->clearAll(); if ($resource->getMorphClass() === 'App\Models\Service') { if ($resource->docker_compose_raw) { try { @@ -649,7 +667,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal } catch (\Exception $e) { throw new \Exception($e->getMessage()); } - $allServices = getServiceTemplates(); + $allServices = get_service_templates(); $topLevelVolumes = collect(data_get($yaml, 'volumes', [])); $topLevelNetworks = collect(data_get($yaml, 'networks', [])); $services = data_get($yaml, 'services'); @@ -663,6 +681,16 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal } } $definedNetwork = collect([$resource->uuid]); + if ($topLevelVolumes->count() > 0) { + $tempTopLevelVolumes = collect([]); + foreach ($topLevelVolumes as $volumeName => $volume) { + if (is_null($volume)) { + continue; + } + $tempTopLevelVolumes->put($volumeName, $volume); + } + $topLevelVolumes = collect($tempTopLevelVolumes); + } $services = collect($services)->map(function ($service, $serviceName) use ($topLevelVolumes, $topLevelNetworks, $definedNetwork, $isNew, $generatedServiceFQDNS, $resource, $allServices) { // Workarounds for beta users. if ($serviceName === 'registry') { @@ -761,6 +789,13 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal // Collect/create/update networks if ($serviceNetworks->count() > 0) { foreach ($serviceNetworks as $networkName => $networkDetails) { + if ($networkName === 'default') { + continue; + } + // ignore alias + if ($networkDetails['aliases'] ?? false) { + continue; + } $networkExists = $topLevelNetworks->contains(function ($value, $key) use ($networkName) { return $value == $networkName || $key == $networkName; }); @@ -812,7 +847,6 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal // default: // ipv4_address: 192.168.203.254 // $networks->put($serviceNetwork, null); - ray($key); $networks->put($key, $serviceNetwork); } } @@ -876,6 +910,9 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal ] ); } else if ($type->value() === 'volume') { + if ($topLevelVolumes->has($source->value())) { + return $volume; + } $slugWithoutUuid = Str::slug($source, '-'); $name = "{$savedService->service->uuid}_{$slugWithoutUuid}"; if (is_string($volume)) { @@ -1007,7 +1044,6 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal 'service_id' => $resource->id, ])->first(); if ($env) { - $env_url = Url::fromString($savedService->fqdn); $env_port = $env_url->getPort(); if ($env_port !== $predefinedPort) { @@ -1049,6 +1085,17 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal } if ($foundEnv) { $fqdn = data_get($foundEnv, 'value'); + // if ($savedService->fqdn) { + // $savedServiceFqdn = Url::fromString($savedService->fqdn); + // $parsedFqdn = Url::fromString($fqdn); + // $savedServicePath = $savedServiceFqdn->getPath(); + // $parsedFqdnPath = $parsedFqdn->getPath(); + // if ($savedServicePath != $parsedFqdnPath) { + // $fqdn = $parsedFqdn->withPath($savedServicePath)->__toString(); + // $foundEnv->value = $fqdn; + // $foundEnv->save(); + // } + // } } else { if ($command->value() === 'URL') { $fqdn = Str::of($fqdn)->after('://')->value(); @@ -1153,7 +1200,8 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal serviceLabels: $serviceLabels, is_gzip_enabled: $savedService->isGzipEnabled(), is_stripprefix_enabled: $savedService->isStripprefixEnabled(), - service_name: $serviceName + service_name: $serviceName, + image: data_get($service, 'image') )); $serviceLabels = $serviceLabels->merge(fqdnLabelsForCaddy( network: $resource->destination->network, @@ -1163,7 +1211,8 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal serviceLabels: $serviceLabels, is_gzip_enabled: $savedService->isGzipEnabled(), is_stripprefix_enabled: $savedService->isStripprefixEnabled(), - service_name: $serviceName + service_name: $serviceName, + image: data_get($service, 'image') )); } } @@ -1189,13 +1238,14 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal if (!data_get($service, 'restart')) { data_set($service, 'restart', RESTART_MODE); } - if (data_get($service, 'restart') === 'no') { + if (data_get($service, 'restart') === 'no' || data_get($service, 'exclude_from_hc')) { $savedService->update(['exclude_from_status' => true]); } data_set($service, 'container_name', $containerName); data_forget($service, 'volumes.*.content'); data_forget($service, 'volumes.*.isDirectory'); data_forget($service, 'volumes.*.is_directory'); + data_forget($service, 'exclude_from_hc'); // Remove unnecessary variables from service.environment // $withoutServiceEnvs = collect([]); @@ -1217,6 +1267,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal 'volumes' => $topLevelVolumes->toArray(), 'networks' => $topLevelNetworks->toArray(), ]; + $yaml = data_forget($yaml, 'services.*.volumes.*.content'); $resource->docker_compose_raw = Yaml::dump($yaml, 10, 2); $resource->docker_compose = Yaml::dump($finalServices, 10, 2); $resource->save(); @@ -1249,6 +1300,18 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal if ($pull_request_id !== 0) { $topLevelVolumes = collect([]); } + + if ($topLevelVolumes->count() > 0) { + $tempTopLevelVolumes = collect([]); + foreach ($topLevelVolumes as $volumeName => $volume) { + if (is_null($volume)) { + continue; + } + $tempTopLevelVolumes->put($volumeName, $volume); + } + $topLevelVolumes = collect($tempTopLevelVolumes); + } + $topLevelNetworks = collect(data_get($yaml, 'networks', [])); $services = data_get($yaml, 'services'); @@ -1312,13 +1375,17 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal if ($pull_request_id !== 0) { $name = $name . "-pr-$pull_request_id"; $volume = str("$name:$mount"); - $topLevelVolumes->put($name, [ - 'name' => $name, - ]); + if (!$topLevelVolumes->has($name)) { + $topLevelVolumes->put($name, [ + 'name' => $name, + ]); + } } else { - $topLevelVolumes->put($name->value(), [ - 'name' => $name->value(), - ]); + if (!$topLevelVolumes->has($name->value())) { + $topLevelVolumes->put($name->value(), [ + 'name' => $name->value(), + ]); + } } } } else { @@ -1362,9 +1429,11 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal data_set($volume, 'source', $source . ':' . $target); } if (!str($source)->startsWith('/')) { - $topLevelVolumes->put($source, [ - 'name' => $source, - ]); + if (!$topLevelVolumes->has($source)) { + $topLevelVolumes->put($source, [ + 'name' => $source, + ]); + } } } } @@ -1391,6 +1460,13 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal // Collect/create/update networks if ($serviceNetworks->count() > 0) { foreach ($serviceNetworks as $networkName => $networkDetails) { + if ($networkName === 'default') { + continue; + } + // ignore alias + if ($networkDetails['aliases'] ?? false) { + continue; + } $networkExists = $topLevelNetworks->contains(function ($value, $key) use ($networkName) { return $value == $networkName || $key == $networkName; }); @@ -1642,13 +1718,15 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal uuid: $resource->uuid, domains: $fqdns, serviceLabels: $serviceLabels, - generate_unique_uuid: $resource->build_pack === 'dockercompose' + generate_unique_uuid: $resource->build_pack === 'dockercompose', + image: data_get($service, 'image') )); $serviceLabels = $serviceLabels->merge(fqdnLabelsForCaddy( network: $resource->destination->network, uuid: $resource->uuid, domains: $fqdns, - serviceLabels: $serviceLabels + serviceLabels: $serviceLabels, + image: data_get($service, 'image') )); } } diff --git a/composer.json b/composer.json index cb98eba57..b8ab3baa1 100644 --- a/composer.json +++ b/composer.json @@ -14,16 +14,16 @@ "guzzlehttp/guzzle": "^7.5.0", "laravel/fortify": "^v1.16.0", "laravel/framework": "^v10.7.1", - "laravel/horizon": "^5.15", + "laravel/horizon": "^5.23.1", "laravel/prompts": "^0.1.6", "laravel/sanctum": "^v3.2.1", - "laravel/socialite": "^5.12", + "laravel/socialite": "^v5.14.0", "laravel/tinker": "^v2.8.1", "laravel/ui": "^4.2", "lcobucci/jwt": "^5.0.0", "league/flysystem-aws-s3-v3": "^3.0", "league/flysystem-sftp-v3": "^3.0", - "livewire/livewire": "^3.0", + "livewire/livewire": "3.4.9", "lorisleiva/laravel-actions": "^2.7", "nubs/random-name-generator": "^2.2", "phpseclib/phpseclib": "~3.0", diff --git a/composer.lock b/composer.lock index 55cd48f95..bcc419441 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "e6fd1d5c5183226a78df717b52343393", + "content-hash": "bbef91ebb38f73fc712a7d3173bf7b39", "packages": [ { "name": "amphp/amp", - "version": "v3.0.0", + "version": "v3.0.2", "source": { "type": "git", "url": "https://github.com/amphp/amp.git", - "reference": "aaf0ec1d5a2c20b523258995a10e80c1fb765871" + "reference": "138801fb68cfc9c329da8a7b39d01ce7291ee4b0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/amp/zipball/aaf0ec1d5a2c20b523258995a10e80c1fb765871", - "reference": "aaf0ec1d5a2c20b523258995a10e80c1fb765871", + "url": "https://api.github.com/repos/amphp/amp/zipball/138801fb68cfc9c329da8a7b39d01ce7291ee4b0", + "reference": "138801fb68cfc9c329da8a7b39d01ce7291ee4b0", "shasum": "" }, "require": { @@ -27,7 +27,7 @@ "require-dev": { "amphp/php-cs-fixer-config": "^2", "phpunit/phpunit": "^9", - "psalm/phar": "^4.13" + "psalm/phar": "5.23.1" }, "type": "library", "autoload": { @@ -77,7 +77,7 @@ ], "support": { "issues": "https://github.com/amphp/amp/issues", - "source": "https://github.com/amphp/amp/tree/v3.0.0" + "source": "https://github.com/amphp/amp/tree/v3.0.2" }, "funding": [ { @@ -85,7 +85,7 @@ "type": "github" } ], - "time": "2022-12-18T16:52:44+00:00" + "time": "2024-05-10T21:37:46+00:00" }, { "name": "amphp/byte-stream", @@ -164,16 +164,16 @@ }, { "name": "amphp/cache", - "version": "v2.0.0", + "version": "v2.0.1", "source": { "type": "git", "url": "https://github.com/amphp/cache.git", - "reference": "218bb3888d380eb9dd926cd06f803573c84391d3" + "reference": "46912e387e6aa94933b61ea1ead9cf7540b7797c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/cache/zipball/218bb3888d380eb9dd926cd06f803573c84391d3", - "reference": "218bb3888d380eb9dd926cd06f803573c84391d3", + "url": "https://api.github.com/repos/amphp/cache/zipball/46912e387e6aa94933b61ea1ead9cf7540b7797c", + "reference": "46912e387e6aa94933b61ea1ead9cf7540b7797c", "shasum": "" }, "require": { @@ -217,7 +217,7 @@ "homepage": "https://amphp.org/cache", "support": { "issues": "https://github.com/amphp/cache/issues", - "source": "https://github.com/amphp/cache/tree/v2.0.0" + "source": "https://github.com/amphp/cache/tree/v2.0.1" }, "funding": [ { @@ -225,20 +225,20 @@ "type": "github" } ], - "time": "2023-01-09T21:04:12+00:00" + "time": "2024-04-19T03:38:06+00:00" }, { "name": "amphp/dns", - "version": "v2.1.1", + "version": "v2.1.2", "source": { "type": "git", "url": "https://github.com/amphp/dns.git", - "reference": "3e3f413fbbaacd9632b1612941b363fa26a72e52" + "reference": "04c88e67bef804203df934703bd422ea72f46b0e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/dns/zipball/3e3f413fbbaacd9632b1612941b363fa26a72e52", - "reference": "3e3f413fbbaacd9632b1612941b363fa26a72e52", + "url": "https://api.github.com/repos/amphp/dns/zipball/04c88e67bef804203df934703bd422ea72f46b0e", + "reference": "04c88e67bef804203df934703bd422ea72f46b0e", "shasum": "" }, "require": { @@ -305,7 +305,7 @@ ], "support": { "issues": "https://github.com/amphp/dns/issues", - "source": "https://github.com/amphp/dns/tree/v2.1.1" + "source": "https://github.com/amphp/dns/tree/v2.1.2" }, "funding": [ { @@ -313,7 +313,7 @@ "type": "github" } ], - "time": "2024-01-30T23:25:30+00:00" + "time": "2024-04-19T03:49:29+00:00" }, { "name": "amphp/parallel", @@ -530,16 +530,16 @@ }, { "name": "amphp/process", - "version": "v2.0.2", + "version": "v2.0.3", "source": { "type": "git", "url": "https://github.com/amphp/process.git", - "reference": "a79dc87100be857db2c4bbfd5369585a6d1e658c" + "reference": "52e08c09dec7511d5fbc1fb00d3e4e79fc77d58d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/process/zipball/a79dc87100be857db2c4bbfd5369585a6d1e658c", - "reference": "a79dc87100be857db2c4bbfd5369585a6d1e658c", + "url": "https://api.github.com/repos/amphp/process/zipball/52e08c09dec7511d5fbc1fb00d3e4e79fc77d58d", + "reference": "52e08c09dec7511d5fbc1fb00d3e4e79fc77d58d", "shasum": "" }, "require": { @@ -586,7 +586,7 @@ "homepage": "https://amphp.org/process", "support": { "issues": "https://github.com/amphp/process/issues", - "source": "https://github.com/amphp/process/tree/v2.0.2" + "source": "https://github.com/amphp/process/tree/v2.0.3" }, "funding": [ { @@ -594,7 +594,7 @@ "type": "github" } ], - "time": "2024-02-13T20:38:21+00:00" + "time": "2024-04-19T03:13:44+00:00" }, { "name": "amphp/serialization", @@ -656,16 +656,16 @@ }, { "name": "amphp/socket", - "version": "v2.3.0", + "version": "v2.3.1", "source": { "type": "git", "url": "https://github.com/amphp/socket.git", - "reference": "acc0a2f65ab498025ba5641f7cce499c4b1ed4b5" + "reference": "58e0422221825b79681b72c50c47a930be7bf1e1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/socket/zipball/acc0a2f65ab498025ba5641f7cce499c4b1ed4b5", - "reference": "acc0a2f65ab498025ba5641f7cce499c4b1ed4b5", + "url": "https://api.github.com/repos/amphp/socket/zipball/58e0422221825b79681b72c50c47a930be7bf1e1", + "reference": "58e0422221825b79681b72c50c47a930be7bf1e1", "shasum": "" }, "require": { @@ -728,7 +728,7 @@ ], "support": { "issues": "https://github.com/amphp/socket/issues", - "source": "https://github.com/amphp/socket/tree/v2.3.0" + "source": "https://github.com/amphp/socket/tree/v2.3.1" }, "funding": [ { @@ -736,7 +736,7 @@ "type": "github" } ], - "time": "2024-03-19T20:01:53+00:00" + "time": "2024-04-21T14:33:03+00:00" }, { "name": "amphp/sync", @@ -867,16 +867,16 @@ }, { "name": "aws/aws-crt-php", - "version": "v1.2.4", + "version": "v1.2.5", "source": { "type": "git", "url": "https://github.com/awslabs/aws-crt-php.git", - "reference": "eb0c6e4e142224a10b08f49ebf87f32611d162b2" + "reference": "0ea1f04ec5aa9f049f97e012d1ed63b76834a31b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/eb0c6e4e142224a10b08f49ebf87f32611d162b2", - "reference": "eb0c6e4e142224a10b08f49ebf87f32611d162b2", + "url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/0ea1f04ec5aa9f049f97e012d1ed63b76834a31b", + "reference": "0ea1f04ec5aa9f049f97e012d1ed63b76834a31b", "shasum": "" }, "require": { @@ -915,22 +915,22 @@ ], "support": { "issues": "https://github.com/awslabs/aws-crt-php/issues", - "source": "https://github.com/awslabs/aws-crt-php/tree/v1.2.4" + "source": "https://github.com/awslabs/aws-crt-php/tree/v1.2.5" }, - "time": "2023-11-08T00:42:13+00:00" + "time": "2024-04-19T21:30:56+00:00" }, { "name": "aws/aws-sdk-php", - "version": "3.301.6", + "version": "3.308.4", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "18c0ebd71d3071304f1ea02aa9af75f95863177a" + "reference": "c88e9df7e076b6e2c652a1c87d2c3af0a9ac30b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/18c0ebd71d3071304f1ea02aa9af75f95863177a", - "reference": "18c0ebd71d3071304f1ea02aa9af75f95863177a", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/c88e9df7e076b6e2c652a1c87d2c3af0a9ac30b6", + "reference": "c88e9df7e076b6e2c652a1c87d2c3af0a9ac30b6", "shasum": "" }, "require": { @@ -1010,34 +1010,34 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.301.6" + "source": "https://github.com/aws/aws-sdk-php/tree/3.308.4" }, - "time": "2024-03-22T18:05:21+00:00" + "time": "2024-05-28T18:05:38+00:00" }, { "name": "bacon/bacon-qr-code", - "version": "2.0.8", + "version": "v3.0.0", "source": { "type": "git", "url": "https://github.com/Bacon/BaconQrCode.git", - "reference": "8674e51bb65af933a5ffaf1c308a660387c35c22" + "reference": "510de6eca6248d77d31b339d62437cc995e2fb41" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/8674e51bb65af933a5ffaf1c308a660387c35c22", - "reference": "8674e51bb65af933a5ffaf1c308a660387c35c22", + "url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/510de6eca6248d77d31b339d62437cc995e2fb41", + "reference": "510de6eca6248d77d31b339d62437cc995e2fb41", "shasum": "" }, "require": { "dasprid/enum": "^1.0.3", "ext-iconv": "*", - "php": "^7.1 || ^8.0" + "php": "^8.1" }, "require-dev": { - "phly/keep-a-changelog": "^2.1", - "phpunit/phpunit": "^7 | ^8 | ^9", - "spatie/phpunit-snapshot-assertions": "^4.2.9", - "squizlabs/php_codesniffer": "^3.4" + "phly/keep-a-changelog": "^2.12", + "phpunit/phpunit": "^10.5.11 || 11.0.4", + "spatie/phpunit-snapshot-assertions": "^5.1.5", + "squizlabs/php_codesniffer": "^3.9" }, "suggest": { "ext-imagick": "to generate QR code images" @@ -1064,31 +1064,31 @@ "homepage": "https://github.com/Bacon/BaconQrCode", "support": { "issues": "https://github.com/Bacon/BaconQrCode/issues", - "source": "https://github.com/Bacon/BaconQrCode/tree/2.0.8" + "source": "https://github.com/Bacon/BaconQrCode/tree/v3.0.0" }, - "time": "2022-12-07T17:46:57+00:00" + "time": "2024-04-18T11:16:25+00:00" }, { "name": "brick/math", - "version": "0.11.0", + "version": "0.12.1", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "0ad82ce168c82ba30d1c01ec86116ab52f589478" + "reference": "f510c0a40911935b77b86859eb5223d58d660df1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/0ad82ce168c82ba30d1c01ec86116ab52f589478", - "reference": "0ad82ce168c82ba30d1c01ec86116ab52f589478", + "url": "https://api.github.com/repos/brick/math/zipball/f510c0a40911935b77b86859eb5223d58d660df1", + "reference": "f510c0a40911935b77b86859eb5223d58d660df1", "shasum": "" }, "require": { - "php": "^8.0" + "php": "^8.1" }, "require-dev": { "php-coveralls/php-coveralls": "^2.2", - "phpunit/phpunit": "^9.0", - "vimeo/psalm": "5.0.0" + "phpunit/phpunit": "^10.1", + "vimeo/psalm": "5.16.0" }, "type": "library", "autoload": { @@ -1108,12 +1108,17 @@ "arithmetic", "bigdecimal", "bignum", + "bignumber", "brick", - "math" + "decimal", + "integer", + "math", + "mathematics", + "rational" ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.11.0" + "source": "https://github.com/brick/math/tree/0.12.1" }, "funding": [ { @@ -1121,7 +1126,7 @@ "type": "github" } ], - "time": "2023-01-15T23:15:59+00:00" + "time": "2023-11-29T23:19:16+00:00" }, { "name": "carbonphp/carbon-doctrine-types", @@ -1260,16 +1265,16 @@ }, { "name": "danharrin/livewire-rate-limiting", - "version": "v1.3.0", + "version": "v1.3.1", "source": { "type": "git", "url": "https://github.com/danharrin/livewire-rate-limiting.git", - "reference": "bf16003f0d977b5a41071526d697eec94ac41735" + "reference": "1a1b299e20de61f88ed6e94ea0bbcfc33aab1ddb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/danharrin/livewire-rate-limiting/zipball/bf16003f0d977b5a41071526d697eec94ac41735", - "reference": "bf16003f0d977b5a41071526d697eec94ac41735", + "url": "https://api.github.com/repos/danharrin/livewire-rate-limiting/zipball/1a1b299e20de61f88ed6e94ea0bbcfc33aab1ddb", + "reference": "1a1b299e20de61f88ed6e94ea0bbcfc33aab1ddb", "shasum": "" }, "require": { @@ -1310,7 +1315,7 @@ "type": "github" } ], - "time": "2024-01-21T14:53:34+00:00" + "time": "2024-05-06T09:10:03+00:00" }, { "name": "dasprid/enum", @@ -1364,21 +1369,21 @@ }, { "name": "daverandom/libdns", - "version": "v2.0.3", + "version": "v2.1.0", "source": { "type": "git", "url": "https://github.com/DaveRandom/LibDNS.git", - "reference": "42c2d700d1178c9f9e78664793463f7f1aea248c" + "reference": "b84c94e8fe6b7ee4aecfe121bfe3b6177d303c8a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/DaveRandom/LibDNS/zipball/42c2d700d1178c9f9e78664793463f7f1aea248c", - "reference": "42c2d700d1178c9f9e78664793463f7f1aea248c", + "url": "https://api.github.com/repos/DaveRandom/LibDNS/zipball/b84c94e8fe6b7ee4aecfe121bfe3b6177d303c8a", + "reference": "b84c94e8fe6b7ee4aecfe121bfe3b6177d303c8a", "shasum": "" }, "require": { "ext-ctype": "*", - "php": ">=7.0" + "php": ">=7.1" }, "suggest": { "ext-intl": "Required for IDN support" @@ -1402,9 +1407,9 @@ ], "support": { "issues": "https://github.com/DaveRandom/LibDNS/issues", - "source": "https://github.com/DaveRandom/LibDNS/tree/v2.0.3" + "source": "https://github.com/DaveRandom/LibDNS/tree/v2.1.0" }, - "time": "2022-09-20T18:15:38+00:00" + "time": "2024-04-12T12:12:48+00:00" }, { "name": "dflydev/dot-access-data", @@ -1576,16 +1581,16 @@ }, { "name": "doctrine/dbal", - "version": "3.8.3", + "version": "3.8.4", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "db922ba9436b7b18a23d1653a0b41ff2369ca41c" + "reference": "b05e48a745f722801f55408d0dbd8003b403dbbd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/db922ba9436b7b18a23d1653a0b41ff2369ca41c", - "reference": "db922ba9436b7b18a23d1653a0b41ff2369ca41c", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/b05e48a745f722801f55408d0dbd8003b403dbbd", + "reference": "b05e48a745f722801f55408d0dbd8003b403dbbd", "shasum": "" }, "require": { @@ -1669,7 +1674,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.8.3" + "source": "https://github.com/doctrine/dbal/tree/3.8.4" }, "funding": [ { @@ -1685,7 +1690,7 @@ "type": "tidelift" } ], - "time": "2024-03-03T15:55:06+00:00" + "time": "2024-04-25T07:04:44+00:00" }, { "name": "doctrine/deprecations", @@ -1736,16 +1741,16 @@ }, { "name": "doctrine/event-manager", - "version": "2.0.0", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/doctrine/event-manager.git", - "reference": "750671534e0241a7c50ea5b43f67e23eb5c96f32" + "reference": "b680156fa328f1dfd874fd48c7026c41570b9c6e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/event-manager/zipball/750671534e0241a7c50ea5b43f67e23eb5c96f32", - "reference": "750671534e0241a7c50ea5b43f67e23eb5c96f32", + "url": "https://api.github.com/repos/doctrine/event-manager/zipball/b680156fa328f1dfd874fd48c7026c41570b9c6e", + "reference": "b680156fa328f1dfd874fd48c7026c41570b9c6e", "shasum": "" }, "require": { @@ -1755,10 +1760,10 @@ "doctrine/common": "<2.9" }, "require-dev": { - "doctrine/coding-standard": "^10", + "doctrine/coding-standard": "^12", "phpstan/phpstan": "^1.8.8", - "phpunit/phpunit": "^9.5", - "vimeo/psalm": "^4.28" + "phpunit/phpunit": "^10.5", + "vimeo/psalm": "^5.24" }, "type": "library", "autoload": { @@ -1807,7 +1812,7 @@ ], "support": { "issues": "https://github.com/doctrine/event-manager/issues", - "source": "https://github.com/doctrine/event-manager/tree/2.0.0" + "source": "https://github.com/doctrine/event-manager/tree/2.0.1" }, "funding": [ { @@ -1823,7 +1828,7 @@ "type": "tidelift" } ], - "time": "2022-10-12T20:59:15+00:00" + "time": "2024-05-22T20:47:39+00:00" }, { "name": "doctrine/inflector", @@ -2121,6 +2126,69 @@ ], "time": "2023-10-06T06:47:41+00:00" }, + { + "name": "firebase/php-jwt", + "version": "v6.10.1", + "source": { + "type": "git", + "url": "https://github.com/firebase/php-jwt.git", + "reference": "500501c2ce893c824c801da135d02661199f60c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/500501c2ce893c824c801da135d02661199f60c5", + "reference": "500501c2ce893c824c801da135d02661199f60c5", + "shasum": "" + }, + "require": { + "php": "^8.0" + }, + "require-dev": { + "guzzlehttp/guzzle": "^7.4", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.5", + "psr/cache": "^2.0||^3.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0" + }, + "suggest": { + "ext-sodium": "Support EdDSA (Ed25519) signatures", + "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present" + }, + "type": "library", + "autoload": { + "psr-4": { + "Firebase\\JWT\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Neuman Vong", + "email": "neuman+pear@twilio.com", + "role": "Developer" + }, + { + "name": "Anant Narayanan", + "email": "anant@php.net", + "role": "Developer" + } + ], + "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", + "homepage": "https://github.com/firebase/php-jwt", + "keywords": [ + "jwt", + "php" + ], + "support": { + "issues": "https://github.com/firebase/php-jwt/issues", + "source": "https://github.com/firebase/php-jwt/tree/v6.10.1" + }, + "time": "2024-05-18T18:05:11+00:00" + }, { "name": "fruitcake/php-cors", "version": "v1.3.0", @@ -2842,24 +2910,25 @@ }, { "name": "laravel/fortify", - "version": "v1.21.0", + "version": "v1.21.3", "source": { "type": "git", "url": "https://github.com/laravel/fortify.git", - "reference": "b34e672e1d341f6e520f81712f73e56f6cb80767" + "reference": "a725684d17959c4750f3b441ff2e94ecde7793a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/fortify/zipball/b34e672e1d341f6e520f81712f73e56f6cb80767", - "reference": "b34e672e1d341f6e520f81712f73e56f6cb80767", + "url": "https://api.github.com/repos/laravel/fortify/zipball/a725684d17959c4750f3b441ff2e94ecde7793a1", + "reference": "a725684d17959c4750f3b441ff2e94ecde7793a1", "shasum": "" }, "require": { - "bacon/bacon-qr-code": "^2.0", + "bacon/bacon-qr-code": "^3.0", "ext-json": "*", "illuminate/support": "^10.0|^11.0", "php": "^8.1", - "pragmarx/google2fa": "^8.0" + "pragmarx/google2fa": "^8.0", + "symfony/console": "^6.0|^7.0" }, "require-dev": { "mockery/mockery": "^1.0", @@ -2902,20 +2971,20 @@ "issues": "https://github.com/laravel/fortify/issues", "source": "https://github.com/laravel/fortify" }, - "time": "2024-03-08T19:55:45+00:00" + "time": "2024-05-08T18:07:38+00:00" }, { "name": "laravel/framework", - "version": "v10.48.4", + "version": "v10.48.12", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "7e0701bf59cb76a51f7c1f7bea51c0c0c29c0b72" + "reference": "590afea38e708022662629fbf5184351fa82cf08" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/7e0701bf59cb76a51f7c1f7bea51c0c0c29c0b72", - "reference": "7e0701bf59cb76a51f7c1f7bea51c0c0c29c0b72", + "url": "https://api.github.com/repos/laravel/framework/zipball/590afea38e708022662629fbf5184351fa82cf08", + "reference": "590afea38e708022662629fbf5184351fa82cf08", "shasum": "" }, "require": { @@ -3109,20 +3178,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2024-03-21T13:36:36+00:00" + "time": "2024-05-28T15:46:19+00:00" }, { "name": "laravel/horizon", - "version": "v5.23.1", + "version": "v5.24.4", "source": { "type": "git", "url": "https://github.com/laravel/horizon.git", - "reference": "7475de7eb5b465c2da84218002fe1a62b8175da0" + "reference": "8d31ff178bf5493efc2b2629c10612054f31f584" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/horizon/zipball/7475de7eb5b465c2da84218002fe1a62b8175da0", - "reference": "7475de7eb5b465c2da84218002fe1a62b8175da0", + "url": "https://api.github.com/repos/laravel/horizon/zipball/8d31ff178bf5493efc2b2629c10612054f31f584", + "reference": "8d31ff178bf5493efc2b2629c10612054f31f584", "shasum": "" }, "require": { @@ -3135,6 +3204,7 @@ "nesbot/carbon": "^2.17|^3.0", "php": "^8.0", "ramsey/uuid": "^4.0", + "symfony/console": "^6.0|^7.0", "symfony/error-handler": "^6.0|^7.0", "symfony/process": "^6.0|^7.0" }, @@ -3185,22 +3255,22 @@ ], "support": { "issues": "https://github.com/laravel/horizon/issues", - "source": "https://github.com/laravel/horizon/tree/v5.23.1" + "source": "https://github.com/laravel/horizon/tree/v5.24.4" }, - "time": "2024-02-20T15:14:10+00:00" + "time": "2024-05-03T13:34:14+00:00" }, { "name": "laravel/prompts", - "version": "v0.1.16", + "version": "v0.1.23", "source": { "type": "git", "url": "https://github.com/laravel/prompts.git", - "reference": "ca6872ab6aec3ab61db3a61f83a6caf764ec7781" + "reference": "9bc4df7c699b0452c6b815e64a2d84b6d7f99400" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/prompts/zipball/ca6872ab6aec3ab61db3a61f83a6caf764ec7781", - "reference": "ca6872ab6aec3ab61db3a61f83a6caf764ec7781", + "url": "https://api.github.com/repos/laravel/prompts/zipball/9bc4df7c699b0452c6b815e64a2d84b6d7f99400", + "reference": "9bc4df7c699b0452c6b815e64a2d84b6d7f99400", "shasum": "" }, "require": { @@ -3240,11 +3310,12 @@ "license": [ "MIT" ], + "description": "Add beautiful and user-friendly forms to your command-line applications.", "support": { "issues": "https://github.com/laravel/prompts/issues", - "source": "https://github.com/laravel/prompts/tree/v0.1.16" + "source": "https://github.com/laravel/prompts/tree/v0.1.23" }, - "time": "2024-02-21T19:25:27+00:00" + "time": "2024-05-27T13:53:20+00:00" }, { "name": "laravel/sanctum", @@ -3374,26 +3445,28 @@ }, { "name": "laravel/socialite", - "version": "v5.12.1", + "version": "v5.14.0", "source": { "type": "git", "url": "https://github.com/laravel/socialite.git", - "reference": "7dae1b072573809f32ab6dcf4aebb57c8b3e8acf" + "reference": "c7b0193a3753a29aff8ce80aa2f511917e6ed68a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/socialite/zipball/7dae1b072573809f32ab6dcf4aebb57c8b3e8acf", - "reference": "7dae1b072573809f32ab6dcf4aebb57c8b3e8acf", + "url": "https://api.github.com/repos/laravel/socialite/zipball/c7b0193a3753a29aff8ce80aa2f511917e6ed68a", + "reference": "c7b0193a3753a29aff8ce80aa2f511917e6ed68a", "shasum": "" }, "require": { "ext-json": "*", + "firebase/php-jwt": "^6.4", "guzzlehttp/guzzle": "^6.0|^7.0", "illuminate/contracts": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", "illuminate/http": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", "league/oauth1-client": "^1.10.1", - "php": "^7.2|^8.0" + "php": "^7.2|^8.0", + "phpseclib/phpseclib": "^3.0" }, "require-dev": { "mockery/mockery": "^1.0", @@ -3440,7 +3513,7 @@ "issues": "https://github.com/laravel/socialite/issues", "source": "https://github.com/laravel/socialite" }, - "time": "2024-02-16T08:58:20+00:00" + "time": "2024-05-03T20:31:38+00:00" }, { "name": "laravel/tinker", @@ -3510,16 +3583,16 @@ }, { "name": "laravel/ui", - "version": "v4.5.0", + "version": "v4.5.2", "source": { "type": "git", "url": "https://github.com/laravel/ui.git", - "reference": "da3811f409297d13feccd5858ce748e7474b3d11" + "reference": "c75396f63268c95b053c8e4814eb70e0875e9628" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/ui/zipball/da3811f409297d13feccd5858ce748e7474b3d11", - "reference": "da3811f409297d13feccd5858ce748e7474b3d11", + "url": "https://api.github.com/repos/laravel/ui/zipball/c75396f63268c95b053c8e4814eb70e0875e9628", + "reference": "c75396f63268c95b053c8e4814eb70e0875e9628", "shasum": "" }, "require": { @@ -3527,7 +3600,8 @@ "illuminate/filesystem": "^9.21|^10.0|^11.0", "illuminate/support": "^9.21|^10.0|^11.0", "illuminate/validation": "^9.21|^10.0|^11.0", - "php": "^8.0" + "php": "^8.0", + "symfony/console": "^6.0|^7.0" }, "require-dev": { "orchestra/testbench": "^7.35|^8.15|^9.0", @@ -3566,22 +3640,22 @@ "ui" ], "support": { - "source": "https://github.com/laravel/ui/tree/v4.5.0" + "source": "https://github.com/laravel/ui/tree/v4.5.2" }, - "time": "2024-03-04T13:58:27+00:00" + "time": "2024-05-08T18:07:10+00:00" }, { "name": "lcobucci/jwt", - "version": "5.2.0", + "version": "5.3.0", "source": { "type": "git", "url": "https://github.com/lcobucci/jwt.git", - "reference": "0ba88aed12c04bd2ed9924f500673f32b67a6211" + "reference": "08071d8d2c7f4b00222cc4b1fb6aa46990a80f83" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lcobucci/jwt/zipball/0ba88aed12c04bd2ed9924f500673f32b67a6211", - "reference": "0ba88aed12c04bd2ed9924f500673f32b67a6211", + "url": "https://api.github.com/repos/lcobucci/jwt/zipball/08071d8d2c7f4b00222cc4b1fb6aa46990a80f83", + "reference": "08071d8d2c7f4b00222cc4b1fb6aa46990a80f83", "shasum": "" }, "require": { @@ -3629,7 +3703,7 @@ ], "support": { "issues": "https://github.com/lcobucci/jwt/issues", - "source": "https://github.com/lcobucci/jwt/tree/5.2.0" + "source": "https://github.com/lcobucci/jwt/tree/5.3.0" }, "funding": [ { @@ -3641,7 +3715,7 @@ "type": "patreon" } ], - "time": "2023-11-20T21:17:42+00:00" + "time": "2024-04-11T23:07:54+00:00" }, { "name": "league/commonmark", @@ -3833,16 +3907,16 @@ }, { "name": "league/flysystem", - "version": "3.25.1", + "version": "3.28.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "abbd664eb4381102c559d358420989f835208f18" + "reference": "e611adab2b1ae2e3072fa72d62c62f52c2bf1f0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/abbd664eb4381102c559d358420989f835208f18", - "reference": "abbd664eb4381102c559d358420989f835208f18", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/e611adab2b1ae2e3072fa72d62c62f52c2bf1f0c", + "reference": "e611adab2b1ae2e3072fa72d62c62f52c2bf1f0c", "shasum": "" }, "require": { @@ -3866,10 +3940,13 @@ "composer/semver": "^3.0", "ext-fileinfo": "*", "ext-ftp": "*", + "ext-mongodb": "^1.3", "ext-zip": "*", "friendsofphp/php-cs-fixer": "^3.5", "google/cloud-storage": "^1.23", + "guzzlehttp/psr7": "^2.6", "microsoft/azure-storage-blob": "^1.1", + "mongodb/mongodb": "^1.2", "phpseclib/phpseclib": "^3.0.36", "phpstan/phpstan": "^1.10", "phpunit/phpunit": "^9.5.11|^10.0", @@ -3907,32 +3984,22 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.25.1" + "source": "https://github.com/thephpleague/flysystem/tree/3.28.0" }, - "funding": [ - { - "url": "https://ecologi.com/frankdejonge", - "type": "custom" - }, - { - "url": "https://github.com/frankdejonge", - "type": "github" - } - ], - "time": "2024-03-16T12:53:19+00:00" + "time": "2024-05-22T10:09:12+00:00" }, { "name": "league/flysystem-aws-s3-v3", - "version": "3.25.1", + "version": "3.28.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git", - "reference": "6a5be0e6d6a93574e80805c9cc108a4b63c824d8" + "reference": "22071ef1604bc776f5ff2468ac27a752514665c8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/6a5be0e6d6a93574e80805c9cc108a4b63c824d8", - "reference": "6a5be0e6d6a93574e80805c9cc108a4b63c824d8", + "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/22071ef1604bc776f5ff2468ac27a752514665c8", + "reference": "22071ef1604bc776f5ff2468ac27a752514665c8", "shasum": "" }, "require": { @@ -3972,32 +4039,22 @@ "storage" ], "support": { - "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.25.1" + "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.28.0" }, - "funding": [ - { - "url": "https://ecologi.com/frankdejonge", - "type": "custom" - }, - { - "url": "https://github.com/frankdejonge", - "type": "github" - } - ], - "time": "2024-03-15T19:58:44+00:00" + "time": "2024-05-06T20:05:52+00:00" }, { "name": "league/flysystem-local", - "version": "3.25.1", + "version": "3.28.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-local.git", - "reference": "61a6a90d6e999e4ddd9ce5adb356de0939060b92" + "reference": "13f22ea8be526ea58c2ddff9e158ef7c296e4f40" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/61a6a90d6e999e4ddd9ce5adb356de0939060b92", - "reference": "61a6a90d6e999e4ddd9ce5adb356de0939060b92", + "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/13f22ea8be526ea58c2ddff9e158ef7c296e4f40", + "reference": "13f22ea8be526ea58c2ddff9e158ef7c296e4f40", "shasum": "" }, "require": { @@ -4031,32 +4088,22 @@ "local" ], "support": { - "source": "https://github.com/thephpleague/flysystem-local/tree/3.25.1" + "source": "https://github.com/thephpleague/flysystem-local/tree/3.28.0" }, - "funding": [ - { - "url": "https://ecologi.com/frankdejonge", - "type": "custom" - }, - { - "url": "https://github.com/frankdejonge", - "type": "github" - } - ], - "time": "2024-03-15T19:58:44+00:00" + "time": "2024-05-06T20:05:52+00:00" }, { "name": "league/flysystem-sftp-v3", - "version": "3.25.1", + "version": "3.28.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-sftp-v3.git", - "reference": "5cf169f33e4351832373f9d1b663c137589aa260" + "reference": "abedadd3c64d4f0e276d6ecc796ec8194d136b41" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-sftp-v3/zipball/5cf169f33e4351832373f9d1b663c137589aa260", - "reference": "5cf169f33e4351832373f9d1b663c137589aa260", + "url": "https://api.github.com/repos/thephpleague/flysystem-sftp-v3/zipball/abedadd3c64d4f0e276d6ecc796ec8194d136b41", + "reference": "abedadd3c64d4f0e276d6ecc796ec8194d136b41", "shasum": "" }, "require": { @@ -4090,19 +4137,9 @@ "sftp" ], "support": { - "source": "https://github.com/thephpleague/flysystem-sftp-v3/tree/3.25.1" + "source": "https://github.com/thephpleague/flysystem-sftp-v3/tree/3.28.0" }, - "funding": [ - { - "url": "https://ecologi.com/frankdejonge", - "type": "custom" - }, - { - "url": "https://github.com/frankdejonge", - "type": "github" - } - ], - "time": "2024-03-16T11:44:18+00:00" + "time": "2024-05-06T20:05:52+00:00" }, { "name": "league/mime-type-detection", @@ -4634,16 +4671,16 @@ }, { "name": "monolog/monolog", - "version": "3.5.0", + "version": "3.6.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "c915e2634718dbc8a4a15c61b0e62e7a44e14448" + "reference": "4b18b21a5527a3d5ffdac2fd35d3ab25a9597654" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/c915e2634718dbc8a4a15c61b0e62e7a44e14448", - "reference": "c915e2634718dbc8a4a15c61b0e62e7a44e14448", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/4b18b21a5527a3d5ffdac2fd35d3ab25a9597654", + "reference": "4b18b21a5527a3d5ffdac2fd35d3ab25a9597654", "shasum": "" }, "require": { @@ -4666,7 +4703,7 @@ "phpstan/phpstan": "^1.9", "phpstan/phpstan-deprecation-rules": "^1.0", "phpstan/phpstan-strict-rules": "^1.4", - "phpunit/phpunit": "^10.1", + "phpunit/phpunit": "^10.5.17", "predis/predis": "^1.1 || ^2", "ruflin/elastica": "^7", "symfony/mailer": "^5.4 || ^6", @@ -4719,7 +4756,7 @@ ], "support": { "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/3.5.0" + "source": "https://github.com/Seldaek/monolog/tree/3.6.0" }, "funding": [ { @@ -4731,7 +4768,7 @@ "type": "tidelift" } ], - "time": "2023-10-27T15:32:31+00:00" + "time": "2024-04-12T21:02:21+00:00" }, { "name": "mtdowling/jmespath.php", @@ -5331,16 +5368,16 @@ }, { "name": "paragonie/constant_time_encoding", - "version": "v2.6.3", + "version": "v2.7.0", "source": { "type": "git", "url": "https://github.com/paragonie/constant_time_encoding.git", - "reference": "58c3f47f650c94ec05a151692652a868995d2938" + "reference": "52a0d99e69f56b9ec27ace92ba56897fe6993105" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/58c3f47f650c94ec05a151692652a868995d2938", - "reference": "58c3f47f650c94ec05a151692652a868995d2938", + "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/52a0d99e69f56b9ec27ace92ba56897fe6993105", + "reference": "52a0d99e69f56b9ec27ace92ba56897fe6993105", "shasum": "" }, "require": { @@ -5394,7 +5431,7 @@ "issues": "https://github.com/paragonie/constant_time_encoding/issues", "source": "https://github.com/paragonie/constant_time_encoding" }, - "time": "2022-06-14T06:56:20+00:00" + "time": "2024-05-08T12:18:48+00:00" }, { "name": "paragonie/random_compat", @@ -5448,16 +5485,16 @@ }, { "name": "paragonie/sodium_compat", - "version": "v1.20.0", + "version": "v1.21.1", "source": { "type": "git", "url": "https://github.com/paragonie/sodium_compat.git", - "reference": "e592a3e06d1fa0d43988c7c7d9948ca836f644b6" + "reference": "bb312875dcdd20680419564fe42ba1d9564b9e37" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/e592a3e06d1fa0d43988c7c7d9948ca836f644b6", - "reference": "e592a3e06d1fa0d43988c7c7d9948ca836f644b6", + "url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/bb312875dcdd20680419564fe42ba1d9564b9e37", + "reference": "bb312875dcdd20680419564fe42ba1d9564b9e37", "shasum": "" }, "require": { @@ -5528,9 +5565,9 @@ ], "support": { "issues": "https://github.com/paragonie/sodium_compat/issues", - "source": "https://github.com/paragonie/sodium_compat/tree/v1.20.0" + "source": "https://github.com/paragonie/sodium_compat/tree/v1.21.1" }, - "time": "2023-04-30T00:54:53+00:00" + "time": "2024-04-22T22:05:04+00:00" }, { "name": "php-http/client-common", @@ -5603,16 +5640,16 @@ }, { "name": "php-http/discovery", - "version": "1.19.2", + "version": "1.19.4", "source": { "type": "git", "url": "https://github.com/php-http/discovery.git", - "reference": "61e1a1eb69c92741f5896d9e05fb8e9d7e8bb0cb" + "reference": "0700efda8d7526335132360167315fdab3aeb599" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-http/discovery/zipball/61e1a1eb69c92741f5896d9e05fb8e9d7e8bb0cb", - "reference": "61e1a1eb69c92741f5896d9e05fb8e9d7e8bb0cb", + "url": "https://api.github.com/repos/php-http/discovery/zipball/0700efda8d7526335132360167315fdab3aeb599", + "reference": "0700efda8d7526335132360167315fdab3aeb599", "shasum": "" }, "require": { @@ -5636,7 +5673,8 @@ "php-http/httplug": "^1.0 || ^2.0", "php-http/message-factory": "^1.0", "phpspec/phpspec": "^5.1 || ^6.1 || ^7.3", - "symfony/phpunit-bridge": "^6.2" + "sebastian/comparator": "^3.0.5 || ^4.0.8", + "symfony/phpunit-bridge": "^6.4.4 || ^7.0.1" }, "type": "composer-plugin", "extra": { @@ -5675,9 +5713,9 @@ ], "support": { "issues": "https://github.com/php-http/discovery/issues", - "source": "https://github.com/php-http/discovery/tree/1.19.2" + "source": "https://github.com/php-http/discovery/tree/1.19.4" }, - "time": "2023-11-30T16:49:05+00:00" + "time": "2024-03-29T13:00:05+00:00" }, { "name": "php-http/httplug", @@ -6210,16 +6248,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.27.0", + "version": "1.29.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "86e4d5a4b036f8f0be1464522f4c6b584c452757" + "reference": "536889f2b340489d328f5ffb7b02bb6b183ddedc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/86e4d5a4b036f8f0be1464522f4c6b584c452757", - "reference": "86e4d5a4b036f8f0be1464522f4c6b584c452757", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/536889f2b340489d328f5ffb7b02bb6b183ddedc", + "reference": "536889f2b340489d328f5ffb7b02bb6b183ddedc", "shasum": "" }, "require": { @@ -6251,22 +6289,22 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.27.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.29.0" }, - "time": "2024-03-21T13:14:53+00:00" + "time": "2024-05-06T12:04:23+00:00" }, { "name": "phpstan/phpstan", - "version": "1.10.65", + "version": "1.11.2", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "3c657d057a0b7ecae19cb12db446bbc99d8839c6" + "reference": "0d5d4294a70deb7547db655c47685d680e39cfec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/3c657d057a0b7ecae19cb12db446bbc99d8839c6", - "reference": "3c657d057a0b7ecae19cb12db446bbc99d8839c6", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/0d5d4294a70deb7547db655c47685d680e39cfec", + "reference": "0d5d4294a70deb7547db655c47685d680e39cfec", "shasum": "" }, "require": { @@ -6309,13 +6347,9 @@ { "url": "https://github.com/phpstan", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" } ], - "time": "2024-03-23T10:30:26+00:00" + "time": "2024-05-24T13:23:04+00:00" }, { "name": "pimple/pimple", @@ -6786,20 +6820,20 @@ }, { "name": "psr/http-factory", - "version": "1.0.2", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/php-fig/http-factory.git", - "reference": "e616d01114759c4c489f93b099585439f795fe35" + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35", - "reference": "e616d01114759c4c489f93b099585439f795fe35", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", "shasum": "" }, "require": { - "php": ">=7.0.0", + "php": ">=7.1", "psr/http-message": "^1.0 || ^2.0" }, "type": "library", @@ -6823,7 +6857,7 @@ "homepage": "https://www.php-fig.org/" } ], - "description": "Common interfaces for PSR-7 HTTP message factories", + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", "keywords": [ "factory", "http", @@ -6835,9 +6869,9 @@ "response" ], "support": { - "source": "https://github.com/php-fig/http-factory/tree/1.0.2" + "source": "https://github.com/php-fig/http-factory" }, - "time": "2023-04-10T20:10:41+00:00" + "time": "2024-04-15T12:06:14+00:00" }, { "name": "psr/http-message", @@ -6995,16 +7029,16 @@ }, { "name": "psy/psysh", - "version": "v0.12.2", + "version": "v0.12.3", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "9185c66c2165bbf4d71de78a69dccf4974f9538d" + "reference": "b6b6cce7d3ee8fbf31843edce5e8f5a72eff4a73" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/9185c66c2165bbf4d71de78a69dccf4974f9538d", - "reference": "9185c66c2165bbf4d71de78a69dccf4974f9538d", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/b6b6cce7d3ee8fbf31843edce5e8f5a72eff4a73", + "reference": "b6b6cce7d3ee8fbf31843edce5e8f5a72eff4a73", "shasum": "" }, "require": { @@ -7068,22 +7102,22 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.12.2" + "source": "https://github.com/bobthecow/psysh/tree/v0.12.3" }, - "time": "2024-03-17T01:53:00+00:00" + "time": "2024-04-02T15:57:53+00:00" }, { "name": "purplepixie/phpdns", - "version": "2.1.0", + "version": "2.1.1", "source": { "type": "git", "url": "https://github.com/purplepixie/phpdns.git", - "reference": "e1e4f18a60d01947e2aac7157325a9e2e7755bf7" + "reference": "18cd3a43fadcfd16e2789e3c78a264945f6cbfad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/purplepixie/phpdns/zipball/e1e4f18a60d01947e2aac7157325a9e2e7755bf7", - "reference": "e1e4f18a60d01947e2aac7157325a9e2e7755bf7", + "url": "https://api.github.com/repos/purplepixie/phpdns/zipball/18cd3a43fadcfd16e2789e3c78a264945f6cbfad", + "reference": "18cd3a43fadcfd16e2789e3c78a264945f6cbfad", "shasum": "" }, "require": { @@ -7116,9 +7150,9 @@ "description": "PHP DNS Direct Query Module", "support": { "issues": "https://github.com/purplepixie/phpdns/issues", - "source": "https://github.com/purplepixie/phpdns/tree/2.1.0" + "source": "https://github.com/purplepixie/phpdns/tree/2.1.1" }, - "time": "2023-11-06T15:37:19+00:00" + "time": "2024-05-27T13:27:50+00:00" }, { "name": "pusher/pusher-php-server", @@ -7316,20 +7350,20 @@ }, { "name": "ramsey/uuid", - "version": "4.7.5", + "version": "4.7.6", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "5f0df49ae5ad6efb7afa69e6bfab4e5b1e080d8e" + "reference": "91039bc1faa45ba123c4328958e620d382ec7088" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/5f0df49ae5ad6efb7afa69e6bfab4e5b1e080d8e", - "reference": "5f0df49ae5ad6efb7afa69e6bfab4e5b1e080d8e", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/91039bc1faa45ba123c4328958e620d382ec7088", + "reference": "91039bc1faa45ba123c4328958e620d382ec7088", "shasum": "" }, "require": { - "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11", + "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12", "ext-json": "*", "php": "^8.0", "ramsey/collection": "^1.2 || ^2.0" @@ -7392,7 +7426,7 @@ ], "support": { "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.7.5" + "source": "https://github.com/ramsey/uuid/tree/4.7.6" }, "funding": [ { @@ -7404,25 +7438,25 @@ "type": "tidelift" } ], - "time": "2023-11-08T05:53:05+00:00" + "time": "2024-04-27T21:32:50+00:00" }, { "name": "rector/rector", - "version": "1.0.3", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/rectorphp/rector.git", - "reference": "c59507a9090b465d65e1aceed91e5b81986e375b" + "reference": "556509e2dcf527369892b7d411379c4a02f31859" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rectorphp/rector/zipball/c59507a9090b465d65e1aceed91e5b81986e375b", - "reference": "c59507a9090b465d65e1aceed91e5b81986e375b", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/556509e2dcf527369892b7d411379c4a02f31859", + "reference": "556509e2dcf527369892b7d411379c4a02f31859", "shasum": "" }, "require": { "php": "^7.2|^8.0", - "phpstan/phpstan": "^1.10.57" + "phpstan/phpstan": "^1.11" }, "conflict": { "rector/rector-doctrine": "*", @@ -7430,6 +7464,9 @@ "rector/rector-phpunit": "*", "rector/rector-symfony": "*" }, + "suggest": { + "ext-dom": "To manipulate phpunit.xml via the custom-rule command" + }, "bin": [ "bin/rector" ], @@ -7452,7 +7489,7 @@ ], "support": { "issues": "https://github.com/rectorphp/rector/issues", - "source": "https://github.com/rectorphp/rector/tree/1.0.3" + "source": "https://github.com/rectorphp/rector/tree/1.1.0" }, "funding": [ { @@ -7460,7 +7497,7 @@ "type": "github" } ], - "time": "2024-03-14T15:04:18+00:00" + "time": "2024-05-18T09:40:27+00:00" }, { "name": "resend/resend-laravel", @@ -7910,21 +7947,21 @@ }, { "name": "socialiteproviders/manager", - "version": "v4.5.1", + "version": "v4.6.0", "source": { "type": "git", "url": "https://github.com/SocialiteProviders/Manager.git", - "reference": "a67f194f0f4c4c7616c549afc697b78df9658d44" + "reference": "dea5190981c31b89e52259da9ab1ca4e2b258b21" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/SocialiteProviders/Manager/zipball/a67f194f0f4c4c7616c549afc697b78df9658d44", - "reference": "a67f194f0f4c4c7616c549afc697b78df9658d44", + "url": "https://api.github.com/repos/SocialiteProviders/Manager/zipball/dea5190981c31b89e52259da9ab1ca4e2b258b21", + "reference": "dea5190981c31b89e52259da9ab1ca4e2b258b21", "shasum": "" }, "require": { "illuminate/support": "^8.0 || ^9.0 || ^10.0 || ^11.0", - "laravel/socialite": "^5.2", + "laravel/socialite": "^5.5", "php": "^8.0" }, "require-dev": { @@ -7980,7 +8017,7 @@ "issues": "https://github.com/socialiteproviders/manager/issues", "source": "https://github.com/socialiteproviders/manager" }, - "time": "2024-02-17T08:58:03+00:00" + "time": "2024-05-04T07:57:39+00:00" }, { "name": "socialiteproviders/microsoft-azure", @@ -8035,16 +8072,16 @@ }, { "name": "spatie/backtrace", - "version": "1.5.3", + "version": "1.6.1", "source": { "type": "git", "url": "https://github.com/spatie/backtrace.git", - "reference": "483f76a82964a0431aa836b6ed0edde0c248e3ab" + "reference": "8373b9d51638292e3bfd736a9c19a654111b4a23" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/backtrace/zipball/483f76a82964a0431aa836b6ed0edde0c248e3ab", - "reference": "483f76a82964a0431aa836b6ed0edde0c248e3ab", + "url": "https://api.github.com/repos/spatie/backtrace/zipball/8373b9d51638292e3bfd736a9c19a654111b4a23", + "reference": "8373b9d51638292e3bfd736a9c19a654111b4a23", "shasum": "" }, "require": { @@ -8052,6 +8089,7 @@ }, "require-dev": { "ext-json": "*", + "laravel/serializable-closure": "^1.3", "phpunit/phpunit": "^9.3", "spatie/phpunit-snapshot-assertions": "^4.2", "symfony/var-dumper": "^5.1" @@ -8081,7 +8119,7 @@ "spatie" ], "support": { - "source": "https://github.com/spatie/backtrace/tree/1.5.3" + "source": "https://github.com/spatie/backtrace/tree/1.6.1" }, "funding": [ { @@ -8093,7 +8131,7 @@ "type": "other" } ], - "time": "2023-06-28T12:59:17+00:00" + "time": "2024-04-24T13:22:11+00:00" }, { "name": "spatie/laravel-activitylog", @@ -8188,20 +8226,20 @@ }, { "name": "spatie/laravel-data", - "version": "3.11.2", + "version": "3.12.0", "source": { "type": "git", "url": "https://github.com/spatie/laravel-data.git", - "reference": "21b4d115a502dfd96ab2b11c62746325e9a28924" + "reference": "d44e04839407bc32b029be59ba80090a5f720e91" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-data/zipball/21b4d115a502dfd96ab2b11c62746325e9a28924", - "reference": "21b4d115a502dfd96ab2b11c62746325e9a28924", + "url": "https://api.github.com/repos/spatie/laravel-data/zipball/d44e04839407bc32b029be59ba80090a5f720e91", + "reference": "d44e04839407bc32b029be59ba80090a5f720e91", "shasum": "" }, "require": { - "illuminate/contracts": "^9.30|^10.0", + "illuminate/contracts": "^9.30|^10.0|^11.0", "php": "^8.1", "phpdocumentor/type-resolver": "^1.5", "spatie/laravel-package-tools": "^1.9.0", @@ -8261,7 +8299,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-data/issues", - "source": "https://github.com/spatie/laravel-data/tree/3.11.2" + "source": "https://github.com/spatie/laravel-data/tree/3.12.0" }, "funding": [ { @@ -8269,7 +8307,7 @@ "type": "github" } ], - "time": "2024-02-22T08:34:10+00:00" + "time": "2024-04-24T09:27:45+00:00" }, { "name": "spatie/laravel-package-tools", @@ -8333,16 +8371,16 @@ }, { "name": "spatie/laravel-ray", - "version": "1.35.1", + "version": "1.36.2", "source": { "type": "git", "url": "https://github.com/spatie/laravel-ray.git", - "reference": "f504d3787d88c7e5de7a4290658f7ad9b1352f22" + "reference": "1852faa96e5aa6778ea3401ec3176eee77268718" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-ray/zipball/f504d3787d88c7e5de7a4290658f7ad9b1352f22", - "reference": "f504d3787d88c7e5de7a4290658f7ad9b1352f22", + "url": "https://api.github.com/repos/spatie/laravel-ray/zipball/1852faa96e5aa6778ea3401ec3176eee77268718", + "reference": "1852faa96e5aa6778ea3401ec3176eee77268718", "shasum": "" }, "require": { @@ -8371,7 +8409,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.29.x-dev" + "dev-main": "1.x-dev" }, "laravel": { "providers": [ @@ -8404,7 +8442,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-ray/issues", - "source": "https://github.com/spatie/laravel-ray/tree/1.35.1" + "source": "https://github.com/spatie/laravel-ray/tree/1.36.2" }, "funding": [ { @@ -8416,7 +8454,7 @@ "type": "other" } ], - "time": "2024-02-13T14:19:41+00:00" + "time": "2024-05-02T08:26:02+00:00" }, { "name": "spatie/laravel-schemaless-attributes", @@ -8626,16 +8664,16 @@ }, { "name": "spatie/ray", - "version": "1.41.1", + "version": "1.41.2", "source": { "type": "git", "url": "https://github.com/spatie/ray.git", - "reference": "051a0facb1d2462fafef87ff77eb74d6f2d12944" + "reference": "c44f8cfbf82c69909b505de61d8d3f2d324e93fc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/ray/zipball/051a0facb1d2462fafef87ff77eb74d6f2d12944", - "reference": "051a0facb1d2462fafef87ff77eb74d6f2d12944", + "url": "https://api.github.com/repos/spatie/ray/zipball/c44f8cfbf82c69909b505de61d8d3f2d324e93fc", + "reference": "c44f8cfbf82c69909b505de61d8d3f2d324e93fc", "shasum": "" }, "require": { @@ -8646,7 +8684,7 @@ "spatie/backtrace": "^1.1", "spatie/macroable": "^1.0|^2.0", "symfony/stopwatch": "^4.0|^5.1|^6.0|^7.0", - "symfony/var-dumper": "^4.2|^5.1|^6.0|^7.0" + "symfony/var-dumper": "^4.2|^5.1|^6.0|^7.0.3" }, "require-dev": { "illuminate/support": "6.x|^8.18|^9.0", @@ -8662,6 +8700,11 @@ "bin/remove-ray.sh" ], "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, "autoload": { "files": [ "src/helpers.php" @@ -8690,7 +8733,7 @@ ], "support": { "issues": "https://github.com/spatie/ray/issues", - "source": "https://github.com/spatie/ray/tree/1.41.1" + "source": "https://github.com/spatie/ray/tree/1.41.2" }, "funding": [ { @@ -8702,7 +8745,7 @@ "type": "other" } ], - "time": "2024-01-25T10:15:50+00:00" + "time": "2024-04-24T14:21:46+00:00" }, { "name": "spatie/url", @@ -8827,16 +8870,16 @@ }, { "name": "symfony/console", - "version": "v6.4.4", + "version": "v6.4.7", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "0d9e4eb5ad413075624378f474c4167ea202de78" + "reference": "a170e64ae10d00ba89e2acbb590dc2e54da8ad8f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/0d9e4eb5ad413075624378f474c4167ea202de78", - "reference": "0d9e4eb5ad413075624378f474c4167ea202de78", + "url": "https://api.github.com/repos/symfony/console/zipball/a170e64ae10d00ba89e2acbb590dc2e54da8ad8f", + "reference": "a170e64ae10d00ba89e2acbb590dc2e54da8ad8f", "shasum": "" }, "require": { @@ -8901,7 +8944,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.4.4" + "source": "https://github.com/symfony/console/tree/v6.4.7" }, "funding": [ { @@ -8917,20 +8960,20 @@ "type": "tidelift" } ], - "time": "2024-02-22T20:27:10+00:00" + "time": "2024-04-18T09:22:46+00:00" }, { "name": "symfony/css-selector", - "version": "v7.0.3", + "version": "v7.0.7", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "ec60a4edf94e63b0556b6a0888548bb400a3a3be" + "reference": "b08a4ad89e84b29cec285b7b1f781a7ae51cf4bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/ec60a4edf94e63b0556b6a0888548bb400a3a3be", - "reference": "ec60a4edf94e63b0556b6a0888548bb400a3a3be", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/b08a4ad89e84b29cec285b7b1f781a7ae51cf4bc", + "reference": "b08a4ad89e84b29cec285b7b1f781a7ae51cf4bc", "shasum": "" }, "require": { @@ -8966,7 +9009,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v7.0.3" + "source": "https://github.com/symfony/css-selector/tree/v7.0.7" }, "funding": [ { @@ -8982,20 +9025,20 @@ "type": "tidelift" } ], - "time": "2024-01-23T15:02:46+00:00" + "time": "2024-04-18T09:29:19+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.4.0", + "version": "v3.5.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf" + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf", - "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", "shasum": "" }, "require": { @@ -9004,7 +9047,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.4-dev" + "dev-main": "3.5-dev" }, "thanks": { "name": "symfony/contracts", @@ -9033,7 +9076,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.4.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0" }, "funding": [ { @@ -9049,20 +9092,20 @@ "type": "tidelift" } ], - "time": "2023-05-23T14:45:45+00:00" + "time": "2024-04-18T09:32:20+00:00" }, { "name": "symfony/error-handler", - "version": "v6.4.4", + "version": "v6.4.7", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "c725219bdf2afc59423c32793d5019d2a904e13a" + "reference": "667a072466c6a53827ed7b119af93806b884cbb3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/c725219bdf2afc59423c32793d5019d2a904e13a", - "reference": "c725219bdf2afc59423c32793d5019d2a904e13a", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/667a072466c6a53827ed7b119af93806b884cbb3", + "reference": "667a072466c6a53827ed7b119af93806b884cbb3", "shasum": "" }, "require": { @@ -9108,7 +9151,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v6.4.4" + "source": "https://github.com/symfony/error-handler/tree/v6.4.7" }, "funding": [ { @@ -9124,20 +9167,20 @@ "type": "tidelift" } ], - "time": "2024-02-22T20:27:10+00:00" + "time": "2024-04-18T09:22:46+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v7.0.3", + "version": "v7.0.7", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "834c28d533dd0636f910909d01b9ff45cc094b5e" + "reference": "db2a7fab994d67d92356bb39c367db115d9d30f9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/834c28d533dd0636f910909d01b9ff45cc094b5e", - "reference": "834c28d533dd0636f910909d01b9ff45cc094b5e", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/db2a7fab994d67d92356bb39c367db115d9d30f9", + "reference": "db2a7fab994d67d92356bb39c367db115d9d30f9", "shasum": "" }, "require": { @@ -9188,7 +9231,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v7.0.3" + "source": "https://github.com/symfony/event-dispatcher/tree/v7.0.7" }, "funding": [ { @@ -9204,20 +9247,20 @@ "type": "tidelift" } ], - "time": "2024-01-23T15:02:46+00:00" + "time": "2024-04-18T09:29:19+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.4.0", + "version": "v3.5.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "a76aed96a42d2b521153fb382d418e30d18b59df" + "reference": "8f93aec25d41b72493c6ddff14e916177c9efc50" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/a76aed96a42d2b521153fb382d418e30d18b59df", - "reference": "a76aed96a42d2b521153fb382d418e30d18b59df", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/8f93aec25d41b72493c6ddff14e916177c9efc50", + "reference": "8f93aec25d41b72493c6ddff14e916177c9efc50", "shasum": "" }, "require": { @@ -9227,7 +9270,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.4-dev" + "dev-main": "3.5-dev" }, "thanks": { "name": "symfony/contracts", @@ -9264,7 +9307,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.4.0" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.5.0" }, "funding": [ { @@ -9280,20 +9323,20 @@ "type": "tidelift" } ], - "time": "2023-05-23T14:45:45+00:00" + "time": "2024-04-18T09:32:20+00:00" }, { "name": "symfony/finder", - "version": "v6.4.0", + "version": "v6.4.7", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "11d736e97f116ac375a81f96e662911a34cd50ce" + "reference": "511c48990be17358c23bf45c5d71ab85d40fb764" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/11d736e97f116ac375a81f96e662911a34cd50ce", - "reference": "11d736e97f116ac375a81f96e662911a34cd50ce", + "url": "https://api.github.com/repos/symfony/finder/zipball/511c48990be17358c23bf45c5d71ab85d40fb764", + "reference": "511c48990be17358c23bf45c5d71ab85d40fb764", "shasum": "" }, "require": { @@ -9328,7 +9371,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v6.4.0" + "source": "https://github.com/symfony/finder/tree/v6.4.7" }, "funding": [ { @@ -9344,27 +9387,27 @@ "type": "tidelift" } ], - "time": "2023-10-31T17:30:12+00:00" + "time": "2024-04-23T10:36:43+00:00" }, { "name": "symfony/http-client", - "version": "v6.4.5", + "version": "v6.4.7", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "f3c86a60a3615f466333a11fd42010d4382a82c7" + "reference": "3683d8107cf1efdd24795cc5f7482be1eded34ac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/f3c86a60a3615f466333a11fd42010d4382a82c7", - "reference": "f3c86a60a3615f466333a11fd42010d4382a82c7", + "url": "https://api.github.com/repos/symfony/http-client/zipball/3683d8107cf1efdd24795cc5f7482be1eded34ac", + "reference": "3683d8107cf1efdd24795cc5f7482be1eded34ac", "shasum": "" }, "require": { "php": ">=8.1", "psr/log": "^1|^2|^3", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/http-client-contracts": "^3", + "symfony/http-client-contracts": "^3.4.1", "symfony/service-contracts": "^2.5|^3" }, "conflict": { @@ -9382,7 +9425,7 @@ "amphp/http-client": "^4.2.1", "amphp/http-tunnel": "^1.0", "amphp/socket": "^1.1", - "guzzlehttp/promises": "^1.4", + "guzzlehttp/promises": "^1.4|^2.0", "nyholm/psr7": "^1.0", "php-http/httplug": "^1.0|^2.0", "psr/http-client": "^1.0", @@ -9421,7 +9464,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v6.4.5" + "source": "https://github.com/symfony/http-client/tree/v6.4.7" }, "funding": [ { @@ -9437,20 +9480,20 @@ "type": "tidelift" } ], - "time": "2024-03-02T12:45:30+00:00" + "time": "2024-04-18T09:22:46+00:00" }, { "name": "symfony/http-client-contracts", - "version": "v3.4.0", + "version": "v3.5.0", "source": { "type": "git", "url": "https://github.com/symfony/http-client-contracts.git", - "reference": "1ee70e699b41909c209a0c930f11034b93578654" + "reference": "20414d96f391677bf80078aa55baece78b82647d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/1ee70e699b41909c209a0c930f11034b93578654", - "reference": "1ee70e699b41909c209a0c930f11034b93578654", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/20414d96f391677bf80078aa55baece78b82647d", + "reference": "20414d96f391677bf80078aa55baece78b82647d", "shasum": "" }, "require": { @@ -9459,7 +9502,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.4-dev" + "dev-main": "3.5-dev" }, "thanks": { "name": "symfony/contracts", @@ -9499,7 +9542,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/http-client-contracts/tree/v3.4.0" + "source": "https://github.com/symfony/http-client-contracts/tree/v3.5.0" }, "funding": [ { @@ -9515,20 +9558,20 @@ "type": "tidelift" } ], - "time": "2023-07-30T20:28:31+00:00" + "time": "2024-04-18T09:32:20+00:00" }, { "name": "symfony/http-foundation", - "version": "v6.4.4", + "version": "v6.4.7", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "ebc713bc6e6f4b53f46539fc158be85dfcd77304" + "reference": "b4db6b833035477cb70e18d0ae33cb7c2b521759" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/ebc713bc6e6f4b53f46539fc158be85dfcd77304", - "reference": "ebc713bc6e6f4b53f46539fc158be85dfcd77304", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/b4db6b833035477cb70e18d0ae33cb7c2b521759", + "reference": "b4db6b833035477cb70e18d0ae33cb7c2b521759", "shasum": "" }, "require": { @@ -9576,7 +9619,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v6.4.4" + "source": "https://github.com/symfony/http-foundation/tree/v6.4.7" }, "funding": [ { @@ -9592,20 +9635,20 @@ "type": "tidelift" } ], - "time": "2024-02-08T15:01:18+00:00" + "time": "2024-04-18T09:22:46+00:00" }, { "name": "symfony/http-kernel", - "version": "v6.4.5", + "version": "v6.4.7", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "f6947cb939d8efee137797382cb4db1af653ef75" + "reference": "b7b5e6cdef670a0c82d015a966ffc7e855861a98" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/f6947cb939d8efee137797382cb4db1af653ef75", - "reference": "f6947cb939d8efee137797382cb4db1af653ef75", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/b7b5e6cdef670a0c82d015a966ffc7e855861a98", + "reference": "b7b5e6cdef670a0c82d015a966ffc7e855861a98", "shasum": "" }, "require": { @@ -9660,6 +9703,7 @@ "symfony/translation-contracts": "^2.5|^3", "symfony/uid": "^5.4|^6.0|^7.0", "symfony/validator": "^6.4|^7.0", + "symfony/var-dumper": "^5.4|^6.4|^7.0", "symfony/var-exporter": "^6.2|^7.0", "twig/twig": "^2.13|^3.0.4" }, @@ -9689,7 +9733,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v6.4.5" + "source": "https://github.com/symfony/http-kernel/tree/v6.4.7" }, "funding": [ { @@ -9705,20 +9749,20 @@ "type": "tidelift" } ], - "time": "2024-03-04T21:00:47+00:00" + "time": "2024-04-29T11:24:44+00:00" }, { "name": "symfony/mailer", - "version": "v6.4.4", + "version": "v6.4.7", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "791c5d31a8204cf3db0c66faab70282307f4376b" + "reference": "2c446d4e446995bed983c0b5bb9ff837e8de7dbd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/791c5d31a8204cf3db0c66faab70282307f4376b", - "reference": "791c5d31a8204cf3db0c66faab70282307f4376b", + "url": "https://api.github.com/repos/symfony/mailer/zipball/2c446d4e446995bed983c0b5bb9ff837e8de7dbd", + "reference": "2c446d4e446995bed983c0b5bb9ff837e8de7dbd", "shasum": "" }, "require": { @@ -9769,7 +9813,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v6.4.4" + "source": "https://github.com/symfony/mailer/tree/v6.4.7" }, "funding": [ { @@ -9785,20 +9829,20 @@ "type": "tidelift" } ], - "time": "2024-02-03T21:33:47+00:00" + "time": "2024-04-18T09:22:46+00:00" }, { "name": "symfony/mime", - "version": "v6.4.3", + "version": "v6.4.7", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "5017e0a9398c77090b7694be46f20eb796262a34" + "reference": "decadcf3865918ecfcbfa90968553994ce935a5e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/5017e0a9398c77090b7694be46f20eb796262a34", - "reference": "5017e0a9398c77090b7694be46f20eb796262a34", + "url": "https://api.github.com/repos/symfony/mime/zipball/decadcf3865918ecfcbfa90968553994ce935a5e", + "reference": "decadcf3865918ecfcbfa90968553994ce935a5e", "shasum": "" }, "require": { @@ -9819,6 +9863,7 @@ "league/html-to-markdown": "^5.0", "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.4|^7.0", "symfony/property-access": "^5.4|^6.0|^7.0", "symfony/property-info": "^5.4|^6.0|^7.0", "symfony/serializer": "^6.3.2|^7.0" @@ -9853,7 +9898,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v6.4.3" + "source": "https://github.com/symfony/mime/tree/v6.4.7" }, "funding": [ { @@ -9869,20 +9914,20 @@ "type": "tidelift" } ], - "time": "2024-01-30T08:32:12+00:00" + "time": "2024-04-18T09:22:46+00:00" }, { "name": "symfony/options-resolver", - "version": "v7.0.0", + "version": "v7.0.7", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "700ff4096e346f54cb628ea650767c8130f1001f" + "reference": "23cc173858776ad451e31f053b1c9f47840b2cfa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/700ff4096e346f54cb628ea650767c8130f1001f", - "reference": "700ff4096e346f54cb628ea650767c8130f1001f", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/23cc173858776ad451e31f053b1c9f47840b2cfa", + "reference": "23cc173858776ad451e31f053b1c9f47840b2cfa", "shasum": "" }, "require": { @@ -9920,7 +9965,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v7.0.0" + "source": "https://github.com/symfony/options-resolver/tree/v7.0.7" }, "funding": [ { @@ -9936,7 +9981,7 @@ "type": "tidelift" } ], - "time": "2023-08-08T10:20:21+00:00" + "time": "2024-04-18T09:29:19+00:00" }, { "name": "symfony/polyfill-ctype", @@ -10731,16 +10776,16 @@ }, { "name": "symfony/process", - "version": "v6.4.4", + "version": "v6.4.7", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "710e27879e9be3395de2b98da3f52a946039f297" + "reference": "cdb1c81c145fd5aa9b0038bab694035020943381" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/710e27879e9be3395de2b98da3f52a946039f297", - "reference": "710e27879e9be3395de2b98da3f52a946039f297", + "url": "https://api.github.com/repos/symfony/process/zipball/cdb1c81c145fd5aa9b0038bab694035020943381", + "reference": "cdb1c81c145fd5aa9b0038bab694035020943381", "shasum": "" }, "require": { @@ -10772,7 +10817,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v6.4.4" + "source": "https://github.com/symfony/process/tree/v6.4.7" }, "funding": [ { @@ -10788,7 +10833,7 @@ "type": "tidelift" } ], - "time": "2024-02-20T12:31:00+00:00" + "time": "2024-04-18T09:22:46+00:00" }, { "name": "symfony/psr-http-message-bridge", @@ -10881,16 +10926,16 @@ }, { "name": "symfony/routing", - "version": "v6.4.5", + "version": "v6.4.7", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "7fe30068e207d9c31c0138501ab40358eb2d49a4" + "reference": "276e06398f71fa2a973264d94f28150f93cfb907" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/7fe30068e207d9c31c0138501ab40358eb2d49a4", - "reference": "7fe30068e207d9c31c0138501ab40358eb2d49a4", + "url": "https://api.github.com/repos/symfony/routing/zipball/276e06398f71fa2a973264d94f28150f93cfb907", + "reference": "276e06398f71fa2a973264d94f28150f93cfb907", "shasum": "" }, "require": { @@ -10944,7 +10989,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v6.4.5" + "source": "https://github.com/symfony/routing/tree/v6.4.7" }, "funding": [ { @@ -10960,25 +11005,26 @@ "type": "tidelift" } ], - "time": "2024-02-27T12:33:30+00:00" + "time": "2024-04-18T09:22:46+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.4.1", + "version": "v3.5.0", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "fe07cbc8d837f60caf7018068e350cc5163681a0" + "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/fe07cbc8d837f60caf7018068e350cc5163681a0", - "reference": "fe07cbc8d837f60caf7018068e350cc5163681a0", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", + "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", "shasum": "" }, "require": { "php": ">=8.1", - "psr/container": "^1.1|^2.0" + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" }, "conflict": { "ext-psr": "<1.1|>=2" @@ -10986,7 +11032,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.4-dev" + "dev-main": "3.5-dev" }, "thanks": { "name": "symfony/contracts", @@ -11026,7 +11072,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.4.1" + "source": "https://github.com/symfony/service-contracts/tree/v3.5.0" }, "funding": [ { @@ -11042,20 +11088,20 @@ "type": "tidelift" } ], - "time": "2023-12-26T14:02:43+00:00" + "time": "2024-04-18T09:32:20+00:00" }, { "name": "symfony/stopwatch", - "version": "v7.0.3", + "version": "v7.0.7", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "983900d6fddf2b0cbaacacbbad07610854bd8112" + "reference": "41a7a24aa1dc82adf46a06bc292d1923acfe6b84" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/983900d6fddf2b0cbaacacbbad07610854bd8112", - "reference": "983900d6fddf2b0cbaacacbbad07610854bd8112", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/41a7a24aa1dc82adf46a06bc292d1923acfe6b84", + "reference": "41a7a24aa1dc82adf46a06bc292d1923acfe6b84", "shasum": "" }, "require": { @@ -11088,7 +11134,7 @@ "description": "Provides a way to profile code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/stopwatch/tree/v7.0.3" + "source": "https://github.com/symfony/stopwatch/tree/v7.0.7" }, "funding": [ { @@ -11104,20 +11150,20 @@ "type": "tidelift" } ], - "time": "2024-01-23T15:02:46+00:00" + "time": "2024-04-18T09:29:19+00:00" }, { "name": "symfony/string", - "version": "v7.0.4", + "version": "v7.0.7", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "f5832521b998b0bec40bee688ad5de98d4cf111b" + "reference": "e405b5424dc2528e02e31ba26b83a79fd4eb8f63" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/f5832521b998b0bec40bee688ad5de98d4cf111b", - "reference": "f5832521b998b0bec40bee688ad5de98d4cf111b", + "url": "https://api.github.com/repos/symfony/string/zipball/e405b5424dc2528e02e31ba26b83a79fd4eb8f63", + "reference": "e405b5424dc2528e02e31ba26b83a79fd4eb8f63", "shasum": "" }, "require": { @@ -11174,7 +11220,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.0.4" + "source": "https://github.com/symfony/string/tree/v7.0.7" }, "funding": [ { @@ -11190,20 +11236,20 @@ "type": "tidelift" } ], - "time": "2024-02-01T13:17:36+00:00" + "time": "2024-04-18T09:29:19+00:00" }, { "name": "symfony/translation", - "version": "v6.4.4", + "version": "v6.4.7", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "bce6a5a78e94566641b2594d17e48b0da3184a8e" + "reference": "7495687c58bfd88b7883823747b0656d90679123" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/bce6a5a78e94566641b2594d17e48b0da3184a8e", - "reference": "bce6a5a78e94566641b2594d17e48b0da3184a8e", + "url": "https://api.github.com/repos/symfony/translation/zipball/7495687c58bfd88b7883823747b0656d90679123", + "reference": "7495687c58bfd88b7883823747b0656d90679123", "shasum": "" }, "require": { @@ -11269,7 +11315,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v6.4.4" + "source": "https://github.com/symfony/translation/tree/v6.4.7" }, "funding": [ { @@ -11285,20 +11331,20 @@ "type": "tidelift" } ], - "time": "2024-02-20T13:16:58+00:00" + "time": "2024-04-18T09:22:46+00:00" }, { "name": "symfony/translation-contracts", - "version": "v3.4.1", + "version": "v3.5.0", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "06450585bf65e978026bda220cdebca3f867fde7" + "reference": "b9d2189887bb6b2e0367a9fc7136c5239ab9b05a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/06450585bf65e978026bda220cdebca3f867fde7", - "reference": "06450585bf65e978026bda220cdebca3f867fde7", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/b9d2189887bb6b2e0367a9fc7136c5239ab9b05a", + "reference": "b9d2189887bb6b2e0367a9fc7136c5239ab9b05a", "shasum": "" }, "require": { @@ -11307,7 +11353,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.4-dev" + "dev-main": "3.5-dev" }, "thanks": { "name": "symfony/contracts", @@ -11347,7 +11393,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v3.4.1" + "source": "https://github.com/symfony/translation-contracts/tree/v3.5.0" }, "funding": [ { @@ -11363,20 +11409,20 @@ "type": "tidelift" } ], - "time": "2023-12-26T14:02:43+00:00" + "time": "2024-04-18T09:32:20+00:00" }, { "name": "symfony/uid", - "version": "v6.4.3", + "version": "v6.4.7", "source": { "type": "git", "url": "https://github.com/symfony/uid.git", - "reference": "1d31267211cc3a2fff32bcfc7c1818dac41b6fc0" + "reference": "a66efcb71d8bc3a207d9d78e0bd67f3321510355" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/uid/zipball/1d31267211cc3a2fff32bcfc7c1818dac41b6fc0", - "reference": "1d31267211cc3a2fff32bcfc7c1818dac41b6fc0", + "url": "https://api.github.com/repos/symfony/uid/zipball/a66efcb71d8bc3a207d9d78e0bd67f3321510355", + "reference": "a66efcb71d8bc3a207d9d78e0bd67f3321510355", "shasum": "" }, "require": { @@ -11421,7 +11467,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/uid/tree/v6.4.3" + "source": "https://github.com/symfony/uid/tree/v6.4.7" }, "funding": [ { @@ -11437,20 +11483,20 @@ "type": "tidelift" } ], - "time": "2024-01-23T14:51:35+00:00" + "time": "2024-04-18T09:22:46+00:00" }, { "name": "symfony/var-dumper", - "version": "v6.4.4", + "version": "v6.4.7", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "b439823f04c98b84d4366c79507e9da6230944b1" + "reference": "7a9cd977cd1c5fed3694bee52990866432af07d7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/b439823f04c98b84d4366c79507e9da6230944b1", - "reference": "b439823f04c98b84d4366c79507e9da6230944b1", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/7a9cd977cd1c5fed3694bee52990866432af07d7", + "reference": "7a9cd977cd1c5fed3694bee52990866432af07d7", "shasum": "" }, "require": { @@ -11506,7 +11552,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.4.4" + "source": "https://github.com/symfony/var-dumper/tree/v6.4.7" }, "funding": [ { @@ -11522,20 +11568,20 @@ "type": "tidelift" } ], - "time": "2024-02-15T11:23:52+00:00" + "time": "2024-04-18T09:22:46+00:00" }, { "name": "symfony/yaml", - "version": "v6.4.3", + "version": "v6.4.7", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "d75715985f0f94f978e3a8fa42533e10db921b90" + "reference": "53e8b1ef30a65f78eac60fddc5ee7ebbbdb1dee0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/d75715985f0f94f978e3a8fa42533e10db921b90", - "reference": "d75715985f0f94f978e3a8fa42533e10db921b90", + "url": "https://api.github.com/repos/symfony/yaml/zipball/53e8b1ef30a65f78eac60fddc5ee7ebbbdb1dee0", + "reference": "53e8b1ef30a65f78eac60fddc5ee7ebbbdb1dee0", "shasum": "" }, "require": { @@ -11578,7 +11624,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v6.4.3" + "source": "https://github.com/symfony/yaml/tree/v6.4.7" }, "funding": [ { @@ -11594,7 +11640,7 @@ "type": "tidelift" } ], - "time": "2024-01-23T14:51:35+00:00" + "time": "2024-04-28T10:28:08+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -11706,7 +11752,6 @@ "issues": "https://github.com/visus-io/php-cuid2/issues", "source": "https://github.com/visus-io/php-cuid2/tree/2.0.0" }, - "abandoned": true, "time": "2023-03-23T19:18:36+00:00" }, { @@ -12037,16 +12082,16 @@ }, { "name": "zbateson/mail-mime-parser", - "version": "2.4.0", + "version": "2.4.1", "source": { "type": "git", "url": "https://github.com/zbateson/mail-mime-parser.git", - "reference": "20b3e48eb799537683780bc8782fbbe9bc25934a" + "reference": "ff49e02f6489b38f7cc3d1bd3971adc0f872569c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zbateson/mail-mime-parser/zipball/20b3e48eb799537683780bc8782fbbe9bc25934a", - "reference": "20b3e48eb799537683780bc8782fbbe9bc25934a", + "url": "https://api.github.com/repos/zbateson/mail-mime-parser/zipball/ff49e02f6489b38f7cc3d1bd3971adc0f872569c", + "reference": "ff49e02f6489b38f7cc3d1bd3971adc0f872569c", "shasum": "" }, "require": { @@ -12108,7 +12153,7 @@ "type": "github" } ], - "time": "2023-02-14T22:58:03+00:00" + "time": "2024-04-28T00:58:54+00:00" }, { "name": "zbateson/mb-wrapper", @@ -12662,16 +12707,16 @@ }, { "name": "laravel/pint", - "version": "v1.14.0", + "version": "v1.16.0", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "6b127276e3f263f7bb17d5077e9e0269e61b2a0e" + "reference": "1b3a3dc5bc6a81ff52828ba7277621f1d49d6d98" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/6b127276e3f263f7bb17d5077e9e0269e61b2a0e", - "reference": "6b127276e3f263f7bb17d5077e9e0269e61b2a0e", + "url": "https://api.github.com/repos/laravel/pint/zipball/1b3a3dc5bc6a81ff52828ba7277621f1d49d6d98", + "reference": "1b3a3dc5bc6a81ff52828ba7277621f1d49d6d98", "shasum": "" }, "require": { @@ -12682,13 +12727,13 @@ "php": "^8.1.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.49.0", - "illuminate/view": "^10.43.0", - "larastan/larastan": "^2.8.1", - "laravel-zero/framework": "^10.3.0", - "mockery/mockery": "^1.6.7", + "friendsofphp/php-cs-fixer": "^3.57.1", + "illuminate/view": "^10.48.10", + "larastan/larastan": "^2.9.6", + "laravel-zero/framework": "^10.4.0", + "mockery/mockery": "^1.6.12", "nunomaduro/termwind": "^1.15.1", - "pestphp/pest": "^2.33.6" + "pestphp/pest": "^2.34.7" }, "bin": [ "builds/pint" @@ -12724,20 +12769,20 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2024-02-20T17:38:05+00:00" + "time": "2024-05-21T18:08:25+00:00" }, { "name": "mockery/mockery", - "version": "1.6.11", + "version": "1.6.12", "source": { "type": "git", "url": "https://github.com/mockery/mockery.git", - "reference": "81a161d0b135df89951abd52296adf97deb0723d" + "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mockery/mockery/zipball/81a161d0b135df89951abd52296adf97deb0723d", - "reference": "81a161d0b135df89951abd52296adf97deb0723d", + "url": "https://api.github.com/repos/mockery/mockery/zipball/1f4efdd7d3beafe9807b08156dfcb176d18f1699", + "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699", "shasum": "" }, "require": { @@ -12807,7 +12852,7 @@ "security": "https://github.com/mockery/mockery/security/advisories", "source": "https://github.com/mockery/mockery" }, - "time": "2024-03-21T18:34:15+00:00" + "time": "2024-05-16T03:13:13+00:00" }, { "name": "myclabs/deep-copy", @@ -12966,16 +13011,16 @@ }, { "name": "pestphp/pest", - "version": "v2.34.5", + "version": "v2.34.7", "source": { "type": "git", "url": "https://github.com/pestphp/pest.git", - "reference": "863a0cc83744c677ffdb28a6a2b841dd049e57ce" + "reference": "a7a3e4240e341d0fee1c54814ce18adc26ce5a76" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pestphp/pest/zipball/863a0cc83744c677ffdb28a6a2b841dd049e57ce", - "reference": "863a0cc83744c677ffdb28a6a2b841dd049e57ce", + "url": "https://api.github.com/repos/pestphp/pest/zipball/a7a3e4240e341d0fee1c54814ce18adc26ce5a76", + "reference": "a7a3e4240e341d0fee1c54814ce18adc26ce5a76", "shasum": "" }, "require": { @@ -12985,10 +13030,10 @@ "pestphp/pest-plugin": "^2.1.1", "pestphp/pest-plugin-arch": "^2.7.0", "php": "^8.1.0", - "phpunit/phpunit": "^10.5.15" + "phpunit/phpunit": "^10.5.17" }, "conflict": { - "phpunit/phpunit": ">10.5.15", + "phpunit/phpunit": ">10.5.17", "sebastian/exporter": "<5.1.0", "webmozart/assert": "<1.11.0" }, @@ -13058,7 +13103,7 @@ ], "support": { "issues": "https://github.com/pestphp/pest/issues", - "source": "https://github.com/pestphp/pest/tree/v2.34.5" + "source": "https://github.com/pestphp/pest/tree/v2.34.7" }, "funding": [ { @@ -13070,7 +13115,7 @@ "type": "github" } ], - "time": "2024-03-22T08:44:19+00:00" + "time": "2024-04-05T07:44:17+00:00" }, { "name": "pestphp/pest-plugin", @@ -13399,28 +13444,35 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "5.3.0", + "version": "5.4.1", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "622548b623e81ca6d78b721c5e029f4ce664f170" + "reference": "9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170", - "reference": "622548b623e81ca6d78b721c5e029f4ce664f170", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c", + "reference": "9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c", "shasum": "" }, "require": { + "doctrine/deprecations": "^1.1", "ext-filter": "*", - "php": "^7.2 || ^8.0", + "php": "^7.4 || ^8.0", "phpdocumentor/reflection-common": "^2.2", - "phpdocumentor/type-resolver": "^1.3", + "phpdocumentor/type-resolver": "^1.7", + "phpstan/phpdoc-parser": "^1.7", "webmozart/assert": "^1.9.1" }, "require-dev": { - "mockery/mockery": "~1.3.2", - "psalm/phar": "^4.8" + "mockery/mockery": "~1.3.5", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-webmozart-assert": "^1.2", + "phpunit/phpunit": "^9.5", + "vimeo/psalm": "^5.13" }, "type": "library", "extra": { @@ -13444,15 +13496,15 @@ }, { "name": "Jaap van Otterdijk", - "email": "account@ijaap.nl" + "email": "opensource@ijaap.nl" } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", "support": { "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.3.0" + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.4.1" }, - "time": "2021-10-19T17:43:47+00:00" + "time": "2024-05-21T05:55:05+00:00" }, { "name": "phpunit/php-code-coverage", @@ -13777,16 +13829,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.5.15", + "version": "10.5.17", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "86376e05e8745ed81d88232ff92fee868247b07b" + "reference": "c1f736a473d21957ead7e94fcc029f571895abf5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/86376e05e8745ed81d88232ff92fee868247b07b", - "reference": "86376e05e8745ed81d88232ff92fee868247b07b", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c1f736a473d21957ead7e94fcc029f571895abf5", + "reference": "c1f736a473d21957ead7e94fcc029f571895abf5", "shasum": "" }, "require": { @@ -13858,7 +13910,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.15" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.17" }, "funding": [ { @@ -13874,7 +13926,7 @@ "type": "tidelift" } ], - "time": "2024-03-22T04:17:47+00:00" + "time": "2024-04-05T04:39:01+00:00" }, { "name": "sebastian/cli-parser", @@ -14839,16 +14891,16 @@ }, { "name": "spatie/flare-client-php", - "version": "1.4.4", + "version": "1.6.0", "source": { "type": "git", "url": "https://github.com/spatie/flare-client-php.git", - "reference": "17082e780752d346c2db12ef5d6bee8e835e399c" + "reference": "220a7c8745e9fa427d54099f47147c4b97fe6462" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/flare-client-php/zipball/17082e780752d346c2db12ef5d6bee8e835e399c", - "reference": "17082e780752d346c2db12ef5d6bee8e835e399c", + "url": "https://api.github.com/repos/spatie/flare-client-php/zipball/220a7c8745e9fa427d54099f47147c4b97fe6462", + "reference": "220a7c8745e9fa427d54099f47147c4b97fe6462", "shasum": "" }, "require": { @@ -14896,7 +14948,7 @@ ], "support": { "issues": "https://github.com/spatie/flare-client-php/issues", - "source": "https://github.com/spatie/flare-client-php/tree/1.4.4" + "source": "https://github.com/spatie/flare-client-php/tree/1.6.0" }, "funding": [ { @@ -14904,20 +14956,20 @@ "type": "github" } ], - "time": "2024-01-31T14:18:45+00:00" + "time": "2024-05-22T09:45:39+00:00" }, { "name": "spatie/ignition", - "version": "1.12.0", + "version": "1.14.1", "source": { "type": "git", "url": "https://github.com/spatie/ignition.git", - "reference": "5b6f801c605a593106b623e45ca41496a6e7d56d" + "reference": "c23cc018c5f423d2f413b99f84655fceb6549811" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/ignition/zipball/5b6f801c605a593106b623e45ca41496a6e7d56d", - "reference": "5b6f801c605a593106b623e45ca41496a6e7d56d", + "url": "https://api.github.com/repos/spatie/ignition/zipball/c23cc018c5f423d2f413b99f84655fceb6549811", + "reference": "c23cc018c5f423d2f413b99f84655fceb6549811", "shasum": "" }, "require": { @@ -14987,20 +15039,20 @@ "type": "github" } ], - "time": "2024-01-03T15:49:39+00:00" + "time": "2024-05-03T15:56:16+00:00" }, { "name": "spatie/laravel-ignition", - "version": "2.4.2", + "version": "2.7.0", "source": { "type": "git", "url": "https://github.com/spatie/laravel-ignition.git", - "reference": "351504f4570e32908839fc5a2dc53bf77d02f85e" + "reference": "f52124d50122611e8a40f628cef5c19ff6cc5b57" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/351504f4570e32908839fc5a2dc53bf77d02f85e", - "reference": "351504f4570e32908839fc5a2dc53bf77d02f85e", + "url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/f52124d50122611e8a40f628cef5c19ff6cc5b57", + "reference": "f52124d50122611e8a40f628cef5c19ff6cc5b57", "shasum": "" }, "require": { @@ -15009,8 +15061,8 @@ "ext-mbstring": "*", "illuminate/support": "^10.0|^11.0", "php": "^8.1", - "spatie/flare-client-php": "^1.3.5", - "spatie/ignition": "^1.9", + "spatie/flare-client-php": "^1.5", + "spatie/ignition": "^1.14", "symfony/console": "^6.2.3|^7.0", "symfony/var-dumper": "^6.2.3|^7.0" }, @@ -15018,11 +15070,11 @@ "livewire/livewire": "^2.11|^3.3.5", "mockery/mockery": "^1.5.1", "openai-php/client": "^0.8.1", - "orchestra/testbench": "^8.0|^9.0", - "pestphp/pest": "^2.30", - "phpstan/extension-installer": "^1.2", + "orchestra/testbench": "8.22.3|^9.0", + "pestphp/pest": "^2.34", + "phpstan/extension-installer": "^1.3.1", "phpstan/phpstan-deprecation-rules": "^1.1.1", - "phpstan/phpstan-phpunit": "^1.3.3", + "phpstan/phpstan-phpunit": "^1.3.16", "vlucas/phpdotenv": "^5.5" }, "suggest": { @@ -15079,7 +15131,7 @@ "type": "github" } ], - "time": "2024-02-09T16:08:40+00:00" + "time": "2024-05-02T13:42:49+00:00" }, { "name": "ta-tikoma/phpunit-architecture-test", diff --git a/config/constants.php b/config/constants.php index 53f43ae5a..51bc63b7b 100644 --- a/config/constants.php +++ b/config/constants.php @@ -21,8 +21,8 @@ return [ ], 'services' => [ // Temporary disabled until cache is implemented - // 'official' => 'https://cdn.coollabs.io/coolify/service-templates.json', - 'official' => 'https://raw.githubusercontent.com/coollabsio/coolify/main/templates/service-templates.json', + 'official' => 'https://cdn.coollabs.io/coolify/service-templates.json', + // 'official' => 'https://raw.githubusercontent.com/coollabsio/coolify/main/templates/service-templates.json', ], 'limits' => [ 'trial_period' => 0, diff --git a/config/sentry.php b/config/sentry.php index 693c33c3d..ad068446a 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.285', + 'release' => '4.0.0-beta.294', // 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 e09c4fd6a..c57853f01 100644 --- a/config/version.php +++ b/config/version.php @@ -1,3 +1,3 @@ text('post_deployment_command')->nullable()->change(); + $table->text('pre_deployment_command')->nullable()->change(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('applications', function (Blueprint $table) { + $table->string('post_deployment_command')->nullable()->change(); + $table->string('pre_deployment_command')->nullable()->change(); + }); + } +}; diff --git a/database/migrations/2024_05_23_091713_add_gitea_webhook_to_applications.php b/database/migrations/2024_05_23_091713_add_gitea_webhook_to_applications.php new file mode 100644 index 000000000..716f1f44c --- /dev/null +++ b/database/migrations/2024_05_23_091713_add_gitea_webhook_to_applications.php @@ -0,0 +1,29 @@ +string('manual_webhook_secret_gitea')->nullable(); + + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('applications', function (Blueprint $table) { + $table->dropColumn('manual_webhook_secret_gitea'); + }); + } +}; diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 91e90b989..7eda14d41 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -2,7 +2,7 @@ services: coolify: build: context: . - dockerfile: ./docker/dev-ssu/Dockerfile + dockerfile: ./docker/dev/Dockerfile ports: - "${APP_PORT:-8000}:80" environment: diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index f3dda9748..357138ca1 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -13,6 +13,7 @@ services: - /data/coolify/backups:/var/www/html/storage/app/backups - /data/coolify/webhooks-during-maintenance:/var/www/html/storage/app/webhooks-during-maintenance environment: + - PHP_MEMORY_LIMIT - APP_ID - APP_ENV=production - APP_DEBUG diff --git a/docker/dev-ssu/Dockerfile b/docker/dev/Dockerfile similarity index 93% rename from docker/dev-ssu/Dockerfile rename to docker/dev/Dockerfile index f0e353d28..f75a0ff1e 100644 --- a/docker/dev-ssu/Dockerfile +++ b/docker/dev/Dockerfile @@ -18,9 +18,9 @@ RUN apt-get install postgresql-client-$POSTGRES_VERSION -y # Coolify requirements RUN apt-get install -y php8.2-pgsql openssh-client git git-lfs jq lsof RUN apt-get -y autoremove && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/* -COPY --chmod=755 docker/dev-ssu/etc/s6-overlay/ /etc/s6-overlay/ +COPY --chmod=755 docker/dev/etc/s6-overlay/ /etc/s6-overlay/ -COPY docker/dev-ssu/nginx.conf /etc/nginx/conf.d/custom.conf +COPY docker/dev/nginx.conf /etc/nginx/conf.d/custom.conf RUN echo "alias ll='ls -al'" >>/etc/bash.bashrc RUN echo "alias a='php artisan'" >>/etc/bash.bashrc diff --git a/docker/dev-ssu/etc/s6-overlay/s6-rc.d/horizon/dependencies.d/init-setup b/docker/dev/etc/s6-overlay/s6-rc.d/horizon/dependencies.d/init-setup similarity index 100% rename from docker/dev-ssu/etc/s6-overlay/s6-rc.d/horizon/dependencies.d/init-setup rename to docker/dev/etc/s6-overlay/s6-rc.d/horizon/dependencies.d/init-setup diff --git a/docker/dev-ssu/etc/s6-overlay/s6-rc.d/horizon/run b/docker/dev/etc/s6-overlay/s6-rc.d/horizon/run similarity index 100% rename from docker/dev-ssu/etc/s6-overlay/s6-rc.d/horizon/run rename to docker/dev/etc/s6-overlay/s6-rc.d/horizon/run diff --git a/docker/dev-ssu/etc/s6-overlay/s6-rc.d/horizon/type b/docker/dev/etc/s6-overlay/s6-rc.d/horizon/type similarity index 100% rename from docker/dev-ssu/etc/s6-overlay/s6-rc.d/horizon/type rename to docker/dev/etc/s6-overlay/s6-rc.d/horizon/type diff --git a/docker/dev-ssu/etc/s6-overlay/s6-rc.d/init-setup/type b/docker/dev/etc/s6-overlay/s6-rc.d/init-setup/type similarity index 100% rename from docker/dev-ssu/etc/s6-overlay/s6-rc.d/init-setup/type rename to docker/dev/etc/s6-overlay/s6-rc.d/init-setup/type diff --git a/docker/dev-ssu/etc/s6-overlay/s6-rc.d/init-setup/up b/docker/dev/etc/s6-overlay/s6-rc.d/init-setup/up similarity index 100% rename from docker/dev-ssu/etc/s6-overlay/s6-rc.d/init-setup/up rename to docker/dev/etc/s6-overlay/s6-rc.d/init-setup/up diff --git a/docker/dev-ssu/etc/s6-overlay/s6-rc.d/scheduler-worker/dependencies.d/init-setup b/docker/dev/etc/s6-overlay/s6-rc.d/scheduler-worker/dependencies.d/init-setup similarity index 100% rename from docker/dev-ssu/etc/s6-overlay/s6-rc.d/scheduler-worker/dependencies.d/init-setup rename to docker/dev/etc/s6-overlay/s6-rc.d/scheduler-worker/dependencies.d/init-setup diff --git a/docker/dev-ssu/etc/s6-overlay/s6-rc.d/scheduler-worker/run b/docker/dev/etc/s6-overlay/s6-rc.d/scheduler-worker/run similarity index 100% rename from docker/dev-ssu/etc/s6-overlay/s6-rc.d/scheduler-worker/run rename to docker/dev/etc/s6-overlay/s6-rc.d/scheduler-worker/run diff --git a/docker/dev-ssu/etc/s6-overlay/s6-rc.d/scheduler-worker/type b/docker/dev/etc/s6-overlay/s6-rc.d/scheduler-worker/type similarity index 100% rename from docker/dev-ssu/etc/s6-overlay/s6-rc.d/scheduler-worker/type rename to docker/dev/etc/s6-overlay/s6-rc.d/scheduler-worker/type diff --git a/docker/dev-ssu/etc/s6-overlay/s6-rc.d/user/contents.d/horizon b/docker/dev/etc/s6-overlay/s6-rc.d/user/contents.d/horizon similarity index 100% rename from docker/dev-ssu/etc/s6-overlay/s6-rc.d/user/contents.d/horizon rename to docker/dev/etc/s6-overlay/s6-rc.d/user/contents.d/horizon diff --git a/docker/dev-ssu/etc/s6-overlay/s6-rc.d/user/contents.d/init-setup b/docker/dev/etc/s6-overlay/s6-rc.d/user/contents.d/init-setup similarity index 100% rename from docker/dev-ssu/etc/s6-overlay/s6-rc.d/user/contents.d/init-setup rename to docker/dev/etc/s6-overlay/s6-rc.d/user/contents.d/init-setup diff --git a/docker/dev-ssu/etc/s6-overlay/s6-rc.d/user/contents.d/scheduler-worker b/docker/dev/etc/s6-overlay/s6-rc.d/user/contents.d/scheduler-worker similarity index 100% rename from docker/dev-ssu/etc/s6-overlay/s6-rc.d/user/contents.d/scheduler-worker rename to docker/dev/etc/s6-overlay/s6-rc.d/user/contents.d/scheduler-worker diff --git a/docker/dev-ssu/nginx.conf b/docker/dev/nginx.conf similarity index 100% rename from docker/dev-ssu/nginx.conf rename to docker/dev/nginx.conf diff --git a/docker/prod-ssu/Dockerfile b/docker/prod/Dockerfile similarity index 94% rename from docker/prod-ssu/Dockerfile rename to docker/prod/Dockerfile index 2192f4f0e..4ee3fade2 100644 --- a/docker/prod-ssu/Dockerfile +++ b/docker/prod/Dockerfile @@ -31,10 +31,10 @@ RUN apt-get update RUN apt-get install postgresql-client-$POSTGRES_VERSION -y # Coolify requirements -RUN apt-get install -y php8.2-pgsql openssh-client git git-lfs jq lsof +RUN apt-get install -y php8.2-pgsql openssh-client git git-lfs jq lsof vim RUN apt-get -y autoremove && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/* -COPY docker/prod-ssu/nginx.conf /etc/nginx/conf.d/custom.conf +COPY docker/prod/nginx.conf /etc/nginx/conf.d/custom.conf COPY --from=base --chown=9999:9999 /var/www/html . @@ -42,7 +42,7 @@ COPY --chown=9999:9999 . . RUN composer dump-autoload COPY --from=static-assets --chown=9999:9999 /app/public/build ./public/build -COPY --chmod=755 docker/prod-ssu/etc/s6-overlay/ /etc/s6-overlay/ +COPY --chmod=755 docker/prod/etc/s6-overlay/ /etc/s6-overlay/ RUN php artisan route:cache RUN php artisan view:cache diff --git a/docker/prod-ssu/etc/s6-overlay/s6-rc.d/db-migration/type b/docker/prod/etc/s6-overlay/s6-rc.d/db-migration/type similarity index 100% rename from docker/prod-ssu/etc/s6-overlay/s6-rc.d/db-migration/type rename to docker/prod/etc/s6-overlay/s6-rc.d/db-migration/type diff --git a/docker/prod-ssu/etc/s6-overlay/s6-rc.d/db-migration/up b/docker/prod/etc/s6-overlay/s6-rc.d/db-migration/up similarity index 100% rename from docker/prod-ssu/etc/s6-overlay/s6-rc.d/db-migration/up rename to docker/prod/etc/s6-overlay/s6-rc.d/db-migration/up diff --git a/docker/prod-ssu/etc/s6-overlay/s6-rc.d/horizon/run b/docker/prod/etc/s6-overlay/s6-rc.d/horizon/run similarity index 100% rename from docker/prod-ssu/etc/s6-overlay/s6-rc.d/horizon/run rename to docker/prod/etc/s6-overlay/s6-rc.d/horizon/run diff --git a/docker/prod-ssu/etc/s6-overlay/s6-rc.d/horizon/type b/docker/prod/etc/s6-overlay/s6-rc.d/horizon/type similarity index 100% rename from docker/prod-ssu/etc/s6-overlay/s6-rc.d/horizon/type rename to docker/prod/etc/s6-overlay/s6-rc.d/horizon/type diff --git a/docker/prod-ssu/etc/s6-overlay/s6-rc.d/init-script/dependencies.d/init-seeder b/docker/prod/etc/s6-overlay/s6-rc.d/init-script/dependencies.d/init-seeder similarity index 100% rename from docker/prod-ssu/etc/s6-overlay/s6-rc.d/init-script/dependencies.d/init-seeder rename to docker/prod/etc/s6-overlay/s6-rc.d/init-script/dependencies.d/init-seeder diff --git a/docker/prod-ssu/etc/s6-overlay/s6-rc.d/init-script/type b/docker/prod/etc/s6-overlay/s6-rc.d/init-script/type similarity index 100% rename from docker/prod-ssu/etc/s6-overlay/s6-rc.d/init-script/type rename to docker/prod/etc/s6-overlay/s6-rc.d/init-script/type diff --git a/docker/prod-ssu/etc/s6-overlay/s6-rc.d/init-script/up b/docker/prod/etc/s6-overlay/s6-rc.d/init-script/up similarity index 100% rename from docker/prod-ssu/etc/s6-overlay/s6-rc.d/init-script/up rename to docker/prod/etc/s6-overlay/s6-rc.d/init-script/up diff --git a/docker/prod-ssu/etc/s6-overlay/s6-rc.d/init-seeder/dependencies.d/db-migration b/docker/prod/etc/s6-overlay/s6-rc.d/init-seeder/dependencies.d/db-migration similarity index 100% rename from docker/prod-ssu/etc/s6-overlay/s6-rc.d/init-seeder/dependencies.d/db-migration rename to docker/prod/etc/s6-overlay/s6-rc.d/init-seeder/dependencies.d/db-migration diff --git a/docker/prod-ssu/etc/s6-overlay/s6-rc.d/init-seeder/type b/docker/prod/etc/s6-overlay/s6-rc.d/init-seeder/type similarity index 100% rename from docker/prod-ssu/etc/s6-overlay/s6-rc.d/init-seeder/type rename to docker/prod/etc/s6-overlay/s6-rc.d/init-seeder/type diff --git a/docker/prod-ssu/etc/s6-overlay/s6-rc.d/init-seeder/up b/docker/prod/etc/s6-overlay/s6-rc.d/init-seeder/up similarity index 100% rename from docker/prod-ssu/etc/s6-overlay/s6-rc.d/init-seeder/up rename to docker/prod/etc/s6-overlay/s6-rc.d/init-seeder/up diff --git a/docker/prod-ssu/etc/s6-overlay/s6-rc.d/scheduler-worker/run b/docker/prod/etc/s6-overlay/s6-rc.d/scheduler-worker/run similarity index 100% rename from docker/prod-ssu/etc/s6-overlay/s6-rc.d/scheduler-worker/run rename to docker/prod/etc/s6-overlay/s6-rc.d/scheduler-worker/run diff --git a/docker/prod-ssu/etc/s6-overlay/s6-rc.d/scheduler-worker/type b/docker/prod/etc/s6-overlay/s6-rc.d/scheduler-worker/type similarity index 100% rename from docker/prod-ssu/etc/s6-overlay/s6-rc.d/scheduler-worker/type rename to docker/prod/etc/s6-overlay/s6-rc.d/scheduler-worker/type diff --git a/docker/prod-ssu/etc/s6-overlay/s6-rc.d/user/contents.d/db-migration b/docker/prod/etc/s6-overlay/s6-rc.d/user/contents.d/db-migration similarity index 100% rename from docker/prod-ssu/etc/s6-overlay/s6-rc.d/user/contents.d/db-migration rename to docker/prod/etc/s6-overlay/s6-rc.d/user/contents.d/db-migration diff --git a/docker/prod-ssu/etc/s6-overlay/s6-rc.d/user/contents.d/horizon b/docker/prod/etc/s6-overlay/s6-rc.d/user/contents.d/horizon similarity index 100% rename from docker/prod-ssu/etc/s6-overlay/s6-rc.d/user/contents.d/horizon rename to docker/prod/etc/s6-overlay/s6-rc.d/user/contents.d/horizon diff --git a/docker/prod-ssu/etc/s6-overlay/s6-rc.d/user/contents.d/init-script b/docker/prod/etc/s6-overlay/s6-rc.d/user/contents.d/init-script similarity index 100% rename from docker/prod-ssu/etc/s6-overlay/s6-rc.d/user/contents.d/init-script rename to docker/prod/etc/s6-overlay/s6-rc.d/user/contents.d/init-script diff --git a/docker/prod-ssu/etc/s6-overlay/s6-rc.d/user/contents.d/init-seeder b/docker/prod/etc/s6-overlay/s6-rc.d/user/contents.d/init-seeder similarity index 100% rename from docker/prod-ssu/etc/s6-overlay/s6-rc.d/user/contents.d/init-seeder rename to docker/prod/etc/s6-overlay/s6-rc.d/user/contents.d/init-seeder diff --git a/docker/prod-ssu/etc/s6-overlay/s6-rc.d/user/contents.d/scheduler-worker b/docker/prod/etc/s6-overlay/s6-rc.d/user/contents.d/scheduler-worker similarity index 100% rename from docker/prod-ssu/etc/s6-overlay/s6-rc.d/user/contents.d/scheduler-worker rename to docker/prod/etc/s6-overlay/s6-rc.d/user/contents.d/scheduler-worker diff --git a/docker/prod-ssu/nginx.conf b/docker/prod/nginx.conf similarity index 100% rename from docker/prod-ssu/nginx.conf rename to docker/prod/nginx.conf diff --git a/package-lock.json b/package-lock.json index 17804b797..0010d87fa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,22 +6,22 @@ "": { "dependencies": { "@tailwindcss/forms": "0.5.7", - "@tailwindcss/typography": "0.5.12", - "alpinejs": "3.13.8", - "ioredis": "5.3.2", + "@tailwindcss/typography": "0.5.13", + "alpinejs": "3.14.0", + "ioredis": "5.4.1", "tailwindcss-scrollbar": "0.1.0" }, "devDependencies": { "@vitejs/plugin-vue": "4.5.1", "autoprefixer": "10.4.19", - "axios": "1.6.8", - "laravel-echo": "1.16.0", + "axios": "1.7.2", + "laravel-echo": "1.16.1", "laravel-vite-plugin": "0.8.1", "postcss": "8.4.38", "pusher-js": "8.4.0-rc2", "tailwindcss": "3.4.3", "vite": "4.5.3", - "vue": "3.4.21" + "vue": "3.4.27" } }, "node_modules/@alloc/quick-lru": { @@ -36,9 +36,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.0.tgz", - "integrity": "sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.5.tgz", + "integrity": "sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -496,9 +496,9 @@ } }, "node_modules/@tailwindcss/typography": { - "version": "0.5.12", - "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.12.tgz", - "integrity": "sha512-CNwpBpconcP7ppxmuq3qvaCxiRWnbhANpY/ruH4L5qs2GCiVDJXde/pjj2HWPV1+Q4G9+V/etrwUYopdcjAlyg==", + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.13.tgz", + "integrity": "sha512-ADGcJ8dX21dVVHIwTRgzrcunY6YY9uSlAHHGVKvkA+vLc5qLwEszvKts40lx7z0qc4clpjclwLeK5rVCV2P/uw==", "dependencies": { "lodash.castarray": "^4.4.0", "lodash.isplainobject": "^4.0.6", @@ -535,77 +535,77 @@ } }, "node_modules/@vue/compiler-core": { - "version": "3.4.21", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.21.tgz", - "integrity": "sha512-MjXawxZf2SbZszLPYxaFCjxfibYrzr3eYbKxwpLR9EQN+oaziSu3qKVbwBERj1IFIB8OLUewxB5m/BFzi613og==", + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.27.tgz", + "integrity": "sha512-E+RyqY24KnyDXsCuQrI+mlcdW3ALND6U7Gqa/+bVwbcpcR3BRRIckFoz7Qyd4TTlnugtwuI7YgjbvsLmxb+yvg==", "dev": true, "dependencies": { - "@babel/parser": "^7.23.9", - "@vue/shared": "3.4.21", + "@babel/parser": "^7.24.4", + "@vue/shared": "3.4.27", "entities": "^4.5.0", "estree-walker": "^2.0.2", - "source-map-js": "^1.0.2" + "source-map-js": "^1.2.0" } }, "node_modules/@vue/compiler-core/node_modules/@vue/shared": { - "version": "3.4.21", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.21.tgz", - "integrity": "sha512-PuJe7vDIi6VYSinuEbUIQgMIRZGgM8e4R+G+/dQTk0X1NEdvgvvgv7m+rfmDH1gZzyA1OjjoWskvHlfRNfQf3g==", + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.27.tgz", + "integrity": "sha512-DL3NmY2OFlqmYYrzp39yi3LDkKxa5vZVwxWdQ3rG0ekuWscHraeIbnI8t+aZK7qhYqEqWKTUdijadunb9pnrgA==", "dev": true }, "node_modules/@vue/compiler-dom": { - "version": "3.4.21", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.21.tgz", - "integrity": "sha512-IZC6FKowtT1sl0CR5DpXSiEB5ayw75oT2bma1BEhV7RRR1+cfwLrxc2Z8Zq/RGFzJ8w5r9QtCOvTjQgdn0IKmA==", + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.27.tgz", + "integrity": "sha512-kUTvochG/oVgE1w5ViSr3KUBh9X7CWirebA3bezTbB5ZKBQZwR2Mwj9uoSKRMFcz4gSMzzLXBPD6KpCLb9nvWw==", "dev": true, "dependencies": { - "@vue/compiler-core": "3.4.21", - "@vue/shared": "3.4.21" + "@vue/compiler-core": "3.4.27", + "@vue/shared": "3.4.27" } }, "node_modules/@vue/compiler-dom/node_modules/@vue/shared": { - "version": "3.4.21", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.21.tgz", - "integrity": "sha512-PuJe7vDIi6VYSinuEbUIQgMIRZGgM8e4R+G+/dQTk0X1NEdvgvvgv7m+rfmDH1gZzyA1OjjoWskvHlfRNfQf3g==", + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.27.tgz", + "integrity": "sha512-DL3NmY2OFlqmYYrzp39yi3LDkKxa5vZVwxWdQ3rG0ekuWscHraeIbnI8t+aZK7qhYqEqWKTUdijadunb9pnrgA==", "dev": true }, "node_modules/@vue/compiler-sfc": { - "version": "3.4.21", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.21.tgz", - "integrity": "sha512-me7epoTxYlY+2CUM7hy9PCDdpMPfIwrOvAXud2Upk10g4YLv9UBW7kL798TvMeDhPthkZ0CONNrK2GoeI1ODiQ==", + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.27.tgz", + "integrity": "sha512-nDwntUEADssW8e0rrmE0+OrONwmRlegDA1pD6QhVeXxjIytV03yDqTey9SBDiALsvAd5U4ZrEKbMyVXhX6mCGA==", "dev": true, "dependencies": { - "@babel/parser": "^7.23.9", - "@vue/compiler-core": "3.4.21", - "@vue/compiler-dom": "3.4.21", - "@vue/compiler-ssr": "3.4.21", - "@vue/shared": "3.4.21", + "@babel/parser": "^7.24.4", + "@vue/compiler-core": "3.4.27", + "@vue/compiler-dom": "3.4.27", + "@vue/compiler-ssr": "3.4.27", + "@vue/shared": "3.4.27", "estree-walker": "^2.0.2", - "magic-string": "^0.30.7", - "postcss": "^8.4.35", - "source-map-js": "^1.0.2" + "magic-string": "^0.30.10", + "postcss": "^8.4.38", + "source-map-js": "^1.2.0" } }, "node_modules/@vue/compiler-sfc/node_modules/@vue/shared": { - "version": "3.4.21", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.21.tgz", - "integrity": "sha512-PuJe7vDIi6VYSinuEbUIQgMIRZGgM8e4R+G+/dQTk0X1NEdvgvvgv7m+rfmDH1gZzyA1OjjoWskvHlfRNfQf3g==", + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.27.tgz", + "integrity": "sha512-DL3NmY2OFlqmYYrzp39yi3LDkKxa5vZVwxWdQ3rG0ekuWscHraeIbnI8t+aZK7qhYqEqWKTUdijadunb9pnrgA==", "dev": true }, "node_modules/@vue/compiler-ssr": { - "version": "3.4.21", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.21.tgz", - "integrity": "sha512-M5+9nI2lPpAsgXOGQobnIueVqc9sisBFexh5yMIMRAPYLa7+5wEJs8iqOZc1WAa9WQbx9GR2twgznU8LTIiZ4Q==", + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.27.tgz", + "integrity": "sha512-CVRzSJIltzMG5FcidsW0jKNQnNRYC8bT21VegyMMtHmhW3UOI7knmUehzswXLrExDLE6lQCZdrhD4ogI7c+vuw==", "dev": true, "dependencies": { - "@vue/compiler-dom": "3.4.21", - "@vue/shared": "3.4.21" + "@vue/compiler-dom": "3.4.27", + "@vue/shared": "3.4.27" } }, "node_modules/@vue/compiler-ssr/node_modules/@vue/shared": { - "version": "3.4.21", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.21.tgz", - "integrity": "sha512-PuJe7vDIi6VYSinuEbUIQgMIRZGgM8e4R+G+/dQTk0X1NEdvgvvgv7m+rfmDH1gZzyA1OjjoWskvHlfRNfQf3g==", + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.27.tgz", + "integrity": "sha512-DL3NmY2OFlqmYYrzp39yi3LDkKxa5vZVwxWdQ3rG0ekuWscHraeIbnI8t+aZK7qhYqEqWKTUdijadunb9pnrgA==", "dev": true }, "node_modules/@vue/reactivity": { @@ -617,64 +617,64 @@ } }, "node_modules/@vue/runtime-core": { - "version": "3.4.21", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.21.tgz", - "integrity": "sha512-pQthsuYzE1XcGZznTKn73G0s14eCJcjaLvp3/DKeYWoFacD9glJoqlNBxt3W2c5S40t6CCcpPf+jG01N3ULyrA==", + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.27.tgz", + "integrity": "sha512-7aYA9GEbOOdviqVvcuweTLe5Za4qBZkUY7SvET6vE8kyypxVgaT1ixHLg4urtOlrApdgcdgHoTZCUuTGap/5WA==", "dev": true, "dependencies": { - "@vue/reactivity": "3.4.21", - "@vue/shared": "3.4.21" + "@vue/reactivity": "3.4.27", + "@vue/shared": "3.4.27" } }, "node_modules/@vue/runtime-core/node_modules/@vue/reactivity": { - "version": "3.4.21", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.21.tgz", - "integrity": "sha512-UhenImdc0L0/4ahGCyEzc/pZNwVgcglGy9HVzJ1Bq2Mm9qXOpP8RyNTjookw/gOCUlXSEtuZ2fUg5nrHcoqJcw==", + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.27.tgz", + "integrity": "sha512-kK0g4NknW6JX2yySLpsm2jlunZJl2/RJGZ0H9ddHdfBVHcNzxmQ0sS0b09ipmBoQpY8JM2KmUw+a6sO8Zo+zIA==", "dev": true, "dependencies": { - "@vue/shared": "3.4.21" + "@vue/shared": "3.4.27" } }, "node_modules/@vue/runtime-core/node_modules/@vue/shared": { - "version": "3.4.21", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.21.tgz", - "integrity": "sha512-PuJe7vDIi6VYSinuEbUIQgMIRZGgM8e4R+G+/dQTk0X1NEdvgvvgv7m+rfmDH1gZzyA1OjjoWskvHlfRNfQf3g==", + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.27.tgz", + "integrity": "sha512-DL3NmY2OFlqmYYrzp39yi3LDkKxa5vZVwxWdQ3rG0ekuWscHraeIbnI8t+aZK7qhYqEqWKTUdijadunb9pnrgA==", "dev": true }, "node_modules/@vue/runtime-dom": { - "version": "3.4.21", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.21.tgz", - "integrity": "sha512-gvf+C9cFpevsQxbkRBS1NpU8CqxKw0ebqMvLwcGQrNpx6gqRDodqKqA+A2VZZpQ9RpK2f9yfg8VbW/EpdFUOJw==", + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.27.tgz", + "integrity": "sha512-ScOmP70/3NPM+TW9hvVAz6VWWtZJqkbdf7w6ySsws+EsqtHvkhxaWLecrTorFxsawelM5Ys9FnDEMt6BPBDS0Q==", "dev": true, "dependencies": { - "@vue/runtime-core": "3.4.21", - "@vue/shared": "3.4.21", + "@vue/runtime-core": "3.4.27", + "@vue/shared": "3.4.27", "csstype": "^3.1.3" } }, "node_modules/@vue/runtime-dom/node_modules/@vue/shared": { - "version": "3.4.21", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.21.tgz", - "integrity": "sha512-PuJe7vDIi6VYSinuEbUIQgMIRZGgM8e4R+G+/dQTk0X1NEdvgvvgv7m+rfmDH1gZzyA1OjjoWskvHlfRNfQf3g==", + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.27.tgz", + "integrity": "sha512-DL3NmY2OFlqmYYrzp39yi3LDkKxa5vZVwxWdQ3rG0ekuWscHraeIbnI8t+aZK7qhYqEqWKTUdijadunb9pnrgA==", "dev": true }, "node_modules/@vue/server-renderer": { - "version": "3.4.21", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.21.tgz", - "integrity": "sha512-aV1gXyKSN6Rz+6kZ6kr5+Ll14YzmIbeuWe7ryJl5muJ4uwSwY/aStXTixx76TwkZFJLm1aAlA/HSWEJ4EyiMkg==", + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.27.tgz", + "integrity": "sha512-dlAMEuvmeA3rJsOMJ2J1kXU7o7pOxgsNHVr9K8hB3ImIkSuBrIdy0vF66h8gf8Tuinf1TK3mPAz2+2sqyf3KzA==", "dev": true, "dependencies": { - "@vue/compiler-ssr": "3.4.21", - "@vue/shared": "3.4.21" + "@vue/compiler-ssr": "3.4.27", + "@vue/shared": "3.4.27" }, "peerDependencies": { - "vue": "3.4.21" + "vue": "3.4.27" } }, "node_modules/@vue/server-renderer/node_modules/@vue/shared": { - "version": "3.4.21", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.21.tgz", - "integrity": "sha512-PuJe7vDIi6VYSinuEbUIQgMIRZGgM8e4R+G+/dQTk0X1NEdvgvvgv7m+rfmDH1gZzyA1OjjoWskvHlfRNfQf3g==", + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.27.tgz", + "integrity": "sha512-DL3NmY2OFlqmYYrzp39yi3LDkKxa5vZVwxWdQ3rG0ekuWscHraeIbnI8t+aZK7qhYqEqWKTUdijadunb9pnrgA==", "dev": true }, "node_modules/@vue/shared": { @@ -683,9 +683,9 @@ "integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA==" }, "node_modules/alpinejs": { - "version": "3.13.8", - "resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.13.8.tgz", - "integrity": "sha512-XolbBJryCndomtaHd/KHQjQeD/L72FJxy/YhLLFD4Lr7zzGcpcbg+UgXteMR2pYg1KhRUr6V4O3GfN1zJAmRWw==", + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.14.0.tgz", + "integrity": "sha512-YCWF95PMJqePe9ll6KMyDt/nLhh2R7RhqBf4loEmLzIskcHque4Br/9UgAa6cw13H0Cm3FM9e1hzDwP5z5wlDA==", "dependencies": { "@vue/reactivity": "~3.1.1" } @@ -756,9 +756,9 @@ } }, "node_modules/axios": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", - "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", + "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", "dev": true, "dependencies": { "follow-redirects": "^1.15.6", @@ -1238,9 +1238,9 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/ioredis": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.3.2.tgz", - "integrity": "sha512-1DKMMzlIHM02eBBVOFQ1+AolGjs6+xEcM4PDL7NqOS6szq7H9jSaEkIUH6/a5Hl241LzW6JLSiAbNvTQjUupUA==", + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.4.1.tgz", + "integrity": "sha512-2YZsvl7jopIa1gaePkeMtd9rAcSjOOjPtpcLlOeusyO+XH2SK5ZcT+UCrElPP+WVIInh2TzeI4XW9ENaSLVVHA==", "dependencies": { "@ioredis/commands": "^1.1.1", "cluster-key-slot": "^1.1.0", @@ -1318,9 +1318,9 @@ } }, "node_modules/laravel-echo": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/laravel-echo/-/laravel-echo-1.16.0.tgz", - "integrity": "sha512-BJGUa4tcKvYmTkzTmcBGMHiO2tq+k7Do5wPmLbRswWfzKwyfZEUR+J5iwBTPEfLLwNPZlA9Kjo6R/NV6pmyIpg==", + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/laravel-echo/-/laravel-echo-1.16.1.tgz", + "integrity": "sha512-++Ylb6M3ariC9Rk5WE5gZjj6wcEV5kvLF8b+geJ5/rRIfdoOA+eG6b9qJPrarMD9rY28Apx+l3eelIrCc2skVg==", "dev": true, "engines": { "node": ">=10" @@ -1381,15 +1381,12 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" }, "node_modules/magic-string": { - "version": "0.30.8", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz", - "integrity": "sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==", + "version": "0.30.10", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz", + "integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==", "dev": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" - }, - "engines": { - "node": ">=12" } }, "node_modules/merge2": { @@ -2085,16 +2082,16 @@ } }, "node_modules/vue": { - "version": "3.4.21", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.21.tgz", - "integrity": "sha512-5hjyV/jLEIKD/jYl4cavMcnzKwjMKohureP8ejn3hhEjwhWIhWeuzL2kJAjzl/WyVsgPY56Sy4Z40C3lVshxXA==", + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.27.tgz", + "integrity": "sha512-8s/56uK6r01r1icG/aEOHqyMVxd1bkYcSe9j8HcKtr/xTOFWvnzIVTehNW+5Yt89f+DLBe4A569pnZLS5HzAMA==", "dev": true, "dependencies": { - "@vue/compiler-dom": "3.4.21", - "@vue/compiler-sfc": "3.4.21", - "@vue/runtime-dom": "3.4.21", - "@vue/server-renderer": "3.4.21", - "@vue/shared": "3.4.21" + "@vue/compiler-dom": "3.4.27", + "@vue/compiler-sfc": "3.4.27", + "@vue/runtime-dom": "3.4.27", + "@vue/server-renderer": "3.4.27", + "@vue/shared": "3.4.27" }, "peerDependencies": { "typescript": "*" @@ -2106,9 +2103,9 @@ } }, "node_modules/vue/node_modules/@vue/shared": { - "version": "3.4.21", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.21.tgz", - "integrity": "sha512-PuJe7vDIi6VYSinuEbUIQgMIRZGgM8e4R+G+/dQTk0X1NEdvgvvgv7m+rfmDH1gZzyA1OjjoWskvHlfRNfQf3g==", + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.27.tgz", + "integrity": "sha512-DL3NmY2OFlqmYYrzp39yi3LDkKxa5vZVwxWdQ3rG0ekuWscHraeIbnI8t+aZK7qhYqEqWKTUdijadunb9pnrgA==", "dev": true }, "node_modules/wrappy": { diff --git a/package.json b/package.json index ba733a457..4d6b321c8 100644 --- a/package.json +++ b/package.json @@ -8,20 +8,20 @@ "devDependencies": { "@vitejs/plugin-vue": "4.5.1", "autoprefixer": "10.4.19", - "axios": "1.6.8", - "laravel-echo": "1.16.0", + "axios": "1.7.2", + "laravel-echo": "1.16.1", "laravel-vite-plugin": "0.8.1", "postcss": "8.4.38", "pusher-js": "8.4.0-rc2", "tailwindcss": "3.4.3", "vite": "4.5.3", - "vue": "3.4.21" + "vue": "3.4.27" }, "dependencies": { "@tailwindcss/forms": "0.5.7", - "@tailwindcss/typography": "0.5.12", - "alpinejs": "3.13.8", - "ioredis": "5.3.2", + "@tailwindcss/typography": "0.5.13", + "alpinejs": "3.14.0", + "ioredis": "5.4.1", "tailwindcss-scrollbar": "0.1.0" } } diff --git a/public/svgs/chatwoot.svg b/public/svgs/chatwoot.svg new file mode 100644 index 000000000..1a7bf0bfd --- /dev/null +++ b/public/svgs/chatwoot.svg @@ -0,0 +1,10 @@ + + + diff --git a/public/svgs/docuseal.png b/public/svgs/docuseal.png new file mode 100644 index 000000000..3f1aed12c Binary files /dev/null and b/public/svgs/docuseal.png differ diff --git a/public/svgs/glance.png b/public/svgs/glance.png new file mode 100644 index 000000000..8323483bf Binary files /dev/null and b/public/svgs/glance.png differ diff --git a/public/svgs/mediawiki.ico b/public/svgs/mediawiki.ico new file mode 100644 index 000000000..525171399 Binary files /dev/null and b/public/svgs/mediawiki.ico differ diff --git a/resources/css/app.css b/resources/css/app.css index cae83b0de..42496cffe 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -4,6 +4,7 @@ html, body { + zoom: 0.95; @apply h-full bg-neutral-50 text-neutral-800 dark:bg-base dark:text-neutral-400; } diff --git a/resources/views/components/modal-confirmation.blade.php b/resources/views/components/modal-confirmation.blade.php index 27bc161af..395b03ff4 100644 --- a/resources/views/components/modal-confirmation.blade.php +++ b/resources/views/components/modal-confirmation.blade.php @@ -1,54 +1,68 @@ @props([ 'title' => 'Are you sure?', - 'buttonTitle' => 'Open Modal', 'isErrorButton' => false, + 'buttonTitle' => 'REWRITE THIS BUTTON TITLE PLEASSSSEEEE', 'buttonFullWidth' => false, + 'customButton' => null, 'disabled' => false, 'action' => 'delete', 'content' => null, ])
- @if ($content) -
- {{ $content }} -
- @else - @if ($disabled) - @if ($buttonFullWidth) - - {{ $buttonTitle }} - - @else - - {{ $buttonTitle }} - - @endif - @elseif ($isErrorButton) - @if ($buttonFullWidth) - - {{ $buttonTitle }} - - @else - - {{ $buttonTitle }} - - @endif + @if ($customButton) + @if ($buttonFullWidth) + + {{ $customButton }} + @else - @if ($buttonFullWidth) - - {{ $buttonTitle }} - + + {{ $customButton }} + + @endif + @else + @if ($content) +
+ {{ $content }} +
+ @else + @if ($disabled) + @if ($buttonFullWidth) + + {{ $buttonTitle }} + + @else + + {{ $buttonTitle }} + + @endif + @elseif ($isErrorButton) + @if ($buttonFullWidth) + + {{ $buttonTitle }} + + @else + + {{ $buttonTitle }} + + @endif @else - - {{ $buttonTitle }} - + @if ($buttonFullWidth) + + {{ $buttonTitle }} + + @else + + {{ $buttonTitle }} + + @endif @endif @endif @endif