diff --git a/CHANGELOG.md b/CHANGELOG.md index f7f80c646..eae126054 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,17 +6,18 @@ ## [unreleased] ### 🐛 Bug Fixes -- *(parser)* Transform associative array labels into key=value format for better compatibility -- *(redis)* Update username and password input handling to clarify database sync requirements -- *(source)* Update connected source display to handle cases with no source connected +- *(navbar)* Update error message link to use route for environment variables navigation +- Unsend template +- Replace ports with expose +- *(templates)* Update Unsend compose configuration for improved service integration ### 🚜 Refactor -- *(source)* Conditionally display connected source and change source options based on private key presence +- *(jobs)* Update WithoutOverlapping middleware to use expireAfter for better queue management -### ⚙️ Miscellaneous Tasks +### 📚 Documentation -- *(versions)* Bump coolify version to 4.0.0-beta.409 in configuration files +- Update changelog ## [4.0.0-beta.408] - 2025-04-14 @@ -24,6 +25,8 @@ ### 🚀 Features - *(OpenApi)* Enhance OpenAPI specifications by adding UUID parameters for application, project, and service updates; improve deployment listing with pagination parameters; update command signature for OpenApi generation - *(subscription)* Enhance subscription management with loading states and Stripe status checks +- *(readme)* Add new sponsors Supadata AI and WZ-IT to the README +- *(core)* Enable magic env variables for compose based applications ### 🐛 Bug Fixes @@ -33,6 +36,11 @@ ### 🐛 Bug Fixes - *(mongodb)* Also apply custom config when SSL is enabled - *(templates)* Correct casing of denoKV references in service templates and YAML files - *(deployment)* Handle missing destination in deployment process to prevent errors +- *(parser)* Transform associative array labels into key=value format for better compatibility +- *(redis)* Update username and password input handling to clarify database sync requirements +- *(source)* Update connected source display to handle cases with no source connected +- *(application)* Append base directory to git branch URLs for improved path handling +- *(templates)* Correct casing of "denokv" to "denoKV" in service templates JSON ### 💼 Other @@ -44,6 +52,7 @@ ### 🚜 Refactor - *(Dockerfile)* Remove service generation command from the build process to streamline Dockerfile and improve build efficiency - *(navbar-delete-team)* Simplify modal confirmation layout and enhance button styling for better user experience - *(Server)* Remove debug logging from isReachableChanged method to clean up code and improve performance +- *(source)* Conditionally display connected source and change source options based on private key presence ### 📚 Documentation @@ -56,6 +65,9 @@ ### ⚙️ Miscellaneous Tasks - *(versions)* Update nightly version to 4.0.0-beta.410 - *(pre-commit)* Remove OpenAPI generation command from pre-commit hook - *(versions)* Update realtime version to 1.0.7 and bump dependencies in package.json +- *(versions)* Bump coolify version to 4.0.0-beta.409 in configuration files +- *(versions)* Bump coolify version to 4.0.0-beta.410 and update nightly version to 4.0.0-beta.411 in configuration files +- *(templates)* Update plausible and clickhouse images to latest versions and remove mail service ## [4.0.0-beta.407] - 2025-04-09 diff --git a/README.md b/README.md index 8670e9c76..6e245aa22 100644 --- a/README.md +++ b/README.md @@ -29,99 +29,6 @@ # Support Contact us at [coolify.io/docs/contact](https://coolify.io/docs/contact). -# Donations -To stay completely free and open-source, with no feature behind the paywall and evolve the project, we need your help. If you like Coolify, please consider donating to help us fund the project's future development. - -[coolify.io/sponsorships](https://coolify.io/sponsorships) - -Thank you so much! - -Special thanks to our biggest sponsors! - -### Special Sponsors - -![image](https://github.com/user-attachments/assets/6022bc9c-8435-4d14-9497-8be230ed8cb1) - - -* [CCCareers](https://cccareers.org/) - A career development platform connecting coding bootcamp graduates with job opportunities in the tech industry. -* [Hetzner](http://htznr.li/CoolifyXHetzner) - A German web hosting company offering affordable dedicated servers, cloud services, and web hosting solutions. -* [Logto](https://logto.io/?ref=coolify) - An open-source authentication and authorization solution for building secure login systems and managing user identities. -* [Tolgee](https://tolgee.io/?ref=coolify) - Developer & translator friendly web-based localization platform. -* [BC Direct](https://bc.direct/?ref=coolify.io) - A digital marketing agency specializing in e-commerce solutions and online business growth strategies. -* [QuantCDN](https://www.quantcdn.io/?ref=coolify.io) - A content delivery network (CDN) optimizing website performance through global content distribution. -* [Arcjet](https://arcjet.com/?ref=coolify.io) - A cloud-based platform providing real-time protection against API abuse and bot attacks. -* [SupaGuide](https://supa.guide/?ref=coolify.io) - A comprehensive resource hub offering guides and tutorials for web development using Supabase. -* [GoldenVM](https://billing.goldenvm.com/?ref=coolify.io) - A cloud hosting provider offering scalable infrastructure solutions for businesses of all sizes. -* [Tigris](https://tigrisdata.com/?ref=coolify.io) - A fully managed serverless object storage service compatible with Amazon S3 API. Offers high performance, scalability, and built-in search capabilities for efficient data management. -* [Convex](https://convex.link/coolify.io) - Convex is the open-source reactive database for web app developers. -* [Cloudify.ro](https://cloudify.ro/?ref=coolify.io) - A cloud hosting provider offering scalable infrastructure solutions for businesses of all sizes. -* [Syntaxfm](https://syntax.fm/?ref=coolify.io) - Podcast for web developers. -* [PFGlabs](https://pfglabs.com/?ref=coolify.io) - Build real project with Golang. -* [Treive](https://trieve.ai/?ref=coolify.io) - An AI-powered search and discovery platform for enhancing information retrieval in large datasets. -* [Blacksmith](https://blacksmith.sh/?ref=coolify.io) - A cloud-native platform for automating infrastructure provisioning and management across multiple cloud providers. -* [Brand Dev](https://brand.dev/?ref=coolify.io) - The #1 Brand API for B2B software startups - instantly pull logos, fonts, descriptions, social links, slogans, and so much more from any domain via a single api call. -* [Jobscollider](https://jobscollider.com/remote-jobs?ref=coolify.io) - A job search platform connecting professionals with remote work opportunities across various industries. -* [Hostinger](https://www.hostinger.com/vps/coolify-hosting?ref=coolify.io) - A web hosting provider offering affordable hosting solutions, domain registration, and website building tools. -* [Glueops](https://www.glueops.dev/?ref=coolify.io) - A DevOps consulting company providing infrastructure automation and cloud optimization services. -* [Ubicloud](https://ubicloud.com/?ref=coolify.io) - An open-source alternative to hyperscale cloud providers, offering high-performance cloud computing services. -* [Juxtdigital](https://juxtdigital.dev/?ref=coolify.io) - A digital agency offering web development, design, and digital marketing services for businesses. -* [Saasykit](https://saasykit.com/?ref=coolify.io) - A Laravel-based boilerplate providing essential components and features for building SaaS applications quickly. -* [Massivegrid](https://massivegrid.com/?ref=coolify.io) - A cloud hosting provider offering scalable infrastructure solutions for businesses of all sizes. -* [LiquidWeb](https://liquidweb.com/?utm_source=coolify.io) - A Fast web hosting provider. - - -## Github Sponsors ($40+) -SerpAPI -typebot - - -Lightspeed.run -DartNode - FlintCompany -American Cloud -CryptoJobsList -Codext -Thompson Edolo -UXWizz -Younes Barrad -Automaze -Corentin Clichy -Niklas Lausch -Pixel Infinito -Tyler Whitesides -NiftyCo -Imre Ujlaki -Ilias Ism -Breakcold -Paweł Pierścionek -Michael Mazurczak -Formbricks -StartupFame -jyc.dev -BitLaunch -Internet Garden -Jonas Jaeger -JP -Evercam -Web3 Career - -## Organizations - - - - - - - - - - - - -## Individuals - - - # Cloud If you do not want to self-host Coolify, there is a paid cloud version available: [app.coolify.io](https://app.coolify.io) @@ -137,6 +44,96 @@ ## Why should I use the Cloud version? - Better support - Less maintenance for you +# Donations +To stay completely free and open-source, with no feature behind the paywall and evolve the project, we need your help. If you like Coolify, please consider donating to help us fund the project's future development. + +[coolify.io/sponsorships](https://coolify.io/sponsorships) + +Thank you so much! + +## Big Sponsors + +* [GlueOps](https://www.glueops.dev?ref=coolify.io) - DevOps automation and infrastructure management +* [Algora](https://algora.io?ref=coolify.io) - Open source contribution platform +* [Ubicloud](https://www.ubicloud.com?ref=coolify.io) - Open source cloud infrastructure platform +* [LiquidWeb](https://liquidweb.com?ref=coolify.io) - Premium managed hosting solutions +* [Convex](https://convex.link/coolify.io) - Open-source reactive database for web app developers +* [Arcjet](https://arcjet.com?ref=coolify.io) - Advanced web security and performance solutions +* [SaasyKit](https://saasykit.com?ref=coolify.io) - Complete SaaS starter kit for developers +* [SupaGuide](https://supa.guide?ref=coolify.io) - Your comprehensive guide to Supabase +* [Logto](https://logto.io?ref=coolify.io) - The better identity infrastructure for developers +* [Trieve](https://trieve.ai?ref=coolify.io) - AI-powered search and analytics +* [Supadata AI](https://supadata.ai/?ref=coolify.io) - Scrape YouTube, web, and files. Get AI-ready, clean data +* [Darweb](https://darweb.nl/?ref=coolify.io) - Design. Develop. Deliver. Specialized in 3D CPQ Solutions +* [Hetzner](http://htznr.li/CoolifyXHetzner) - Server, cloud, hosting, and data center solutions +* [COMIT](https://comit.international?ref=coolify.io) - New York Times award–winning contractor +* [Blacksmith](https://blacksmith.sh?ref=coolify.io) - Infrastructure automation platform +* [WZ-IT](https://wz-it.com/?ref=coolify.io) - German agency for customised cloud solutions +* [BC Direct](https://bc.direct?ref=coolify.io) - Your trusted technology consulting partner +* [Tigris](https://www.tigrisdata.com?ref=coolify.io) - Modern developer data platform +* [Hostinger](https://www.hostinger.com/vps/coolify-hosting?ref=coolify.io) - Web hosting and VPS solutions +* [QuantCDN](https://www.quantcdn.io?ref=coolify.io) - Enterprise-grade content delivery network +* [PFGLabs](https://pfglabs.com?ref=coolify.io) - Build Real Projects with Golang +* [JobsCollider](https://jobscollider.com/remote-jobs?ref=coolify.io) - 30,000+ remote jobs for developers +* [Juxtdigital](https://juxtdigital.com?ref=coolify.io) - Digital transformation and web solutions +* [Cloudify.ro](https://cloudify.ro?ref=coolify.io) - Cloud hosting solutions +* [CodeRabbit](https://coderabbit.ai?ref=coolify.io) - Cut Code Review Time & Bugs in Half +* [American Cloud](https://americancloud.com?ref=coolify.io) - US-based cloud infrastructure services +* [MassiveGrid](https://massivegrid.com?ref=coolify.io) - Enterprise cloud hosting solutions +* [Syntax.fm](https://syntax.fm?ref=coolify.io) - Podcast for web developers +* [Tolgee](https://tolgee.io?ref=coolify.io) - The open source localization platform +* [CompAI](https://www.trycomp.ai?ref=coolify.io) - Open source compliance automation platform +* [GoldenVM](https://billing.goldenvm.com?ref=coolify.io) - Premium virtual machine hosting solutions + +## Small Sponsors + +UXWizz +Evercam +Imre Ujlaki +jyc.dev +TheRealJP +360Creators +NiftyCo +Dry Software +Lightspeed.run +LinkDr +Gravity Wiz +BitLaunch +Best for Android +Ilias Ism +Formbricks +Server Searcher +Reshot +Cirun +Typebot +Creating Coding Careers +Internet Garden +Web3 Jobs +Codext +Michael Mazurczak +Fider +Flint +Paweł Pierścionek +RunPod +DartNode +Tyler Whitesides +SerpAPI +Aquarela +Crypto Jobs List +Alfred Nutile +Startup Fame +Younes Barrad +Jonas Jaeger +Pixel Infinito +Corentin Clichy +Thompson Edolo +Devhuset +Arvensis Systems +Niklas Lausch +Cap-go + +...and many more at [GitHub Sponsors](https://github.com/sponsors/coollabsio) + # Recognitions

diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php index aa5f2ff14..f25f63b11 100644 --- a/app/Http/Controllers/Api/ApplicationsController.php +++ b/app/Http/Controllers/Api/ApplicationsController.php @@ -2562,10 +2562,6 @@ public function create_env(Request $request) ])->setStatusCode(201); } } - - return response()->json([ - 'message' => 'Something went wrong.', - ], 500); } #[OA\Delete( diff --git a/app/Http/Controllers/Api/ServersController.php b/app/Http/Controllers/Api/ServersController.php index a9a0a2e53..cbd20400a 100644 --- a/app/Http/Controllers/Api/ServersController.php +++ b/app/Http/Controllers/Api/ServersController.php @@ -809,6 +809,6 @@ public function validate_server(Request $request) } ValidateServer::dispatch($server); - return response()->json(['message' => 'Validation started.']); + return response()->json(['message' => 'Validation started.'], 201); } } diff --git a/app/Http/Controllers/Api/ServicesController.php b/app/Http/Controllers/Api/ServicesController.php index c3730d83f..e792779e1 100644 --- a/app/Http/Controllers/Api/ServicesController.php +++ b/app/Http/Controllers/Api/ServicesController.php @@ -380,6 +380,9 @@ public function create_service(Request $request) $service = new Service; $result = $this->upsert_service($request, $service, $teamId); + if ($result instanceof \Illuminate\Http\JsonResponse) { + return $result; + } return response()->json(serializeApiResponse($result))->setStatusCode(201); } else { @@ -608,12 +611,14 @@ public function update_by_uuid(Request $request) } $service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first(); - if (! $service) { return response()->json(['message' => 'Service not found.'], 404); } $result = $this->upsert_service($request, $service, $teamId); + if ($result instanceof \Illuminate\Http\JsonResponse) { + return $result; + } return response()->json(serializeApiResponse($result))->setStatusCode(200); } diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index ab80a19de..c29093ce0 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -1377,6 +1377,17 @@ private function set_coolify_variables() private function check_git_if_build_needed() { + if ($this->source->getMorphClass() === \App\Models\GithubApp::class && $this->source->is_public === false) { + $repository = githubApi($this->source, "repos/{$this->customRepository}"); + $data = data_get($repository, 'data'); + if (isset($data->id)) { + $repository_project_id = $data->id; + if (blank($this->application->repository_project_id) || $this->application->repository_project_id !== $repository_project_id) { + $this->application->repository_project_id = $repository_project_id; + $this->application->save(); + } + } + } $this->generate_git_import_commands(); $local_branch = $this->branch; if ($this->pull_request_id !== 0) { @@ -1712,25 +1723,6 @@ private function generate_compose_file() $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->pull_request_id !== 0) { - $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 { diff --git a/app/Jobs/CleanupInstanceStuffsJob.php b/app/Jobs/CleanupInstanceStuffsJob.php index 84f14ed02..008492342 100644 --- a/app/Jobs/CleanupInstanceStuffsJob.php +++ b/app/Jobs/CleanupInstanceStuffsJob.php @@ -17,11 +17,13 @@ class CleanupInstanceStuffsJob implements ShouldBeEncrypted, ShouldBeUnique, Sho { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; + public $timeout = 60; + public function __construct() {} public function middleware(): array { - return [(new WithoutOverlapping('cleanup-instance-stuffs'))->dontRelease()]; + return [(new WithoutOverlapping('cleanup-instance-stuffs'))->expireAfter(60)]; } public function handle(): void diff --git a/app/Jobs/DockerCleanupJob.php b/app/Jobs/DockerCleanupJob.php index 05a4aa8de..7e246649d 100644 --- a/app/Jobs/DockerCleanupJob.php +++ b/app/Jobs/DockerCleanupJob.php @@ -31,7 +31,7 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue public function middleware(): array { - return [(new WithoutOverlapping($this->server->uuid))->dontRelease()]; + return [(new WithoutOverlapping($this->server->uuid))->expireAfter(600)]; } public function __construct(public Server $server, public bool $manualCleanup = false) {} diff --git a/app/Jobs/PushServerUpdateJob.php b/app/Jobs/PushServerUpdateJob.php index 93b203fcb..4d40240f9 100644 --- a/app/Jobs/PushServerUpdateJob.php +++ b/app/Jobs/PushServerUpdateJob.php @@ -71,7 +71,7 @@ class PushServerUpdateJob implements ShouldBeEncrypted, ShouldQueue public function middleware(): array { - return [(new WithoutOverlapping($this->server->uuid))->dontRelease()]; + return [(new WithoutOverlapping($this->server->uuid))->expireAfter(30)]; } public function backoff(): int diff --git a/app/Jobs/RestartProxyJob.php b/app/Jobs/RestartProxyJob.php index 7fc716f70..4e1ade0da 100644 --- a/app/Jobs/RestartProxyJob.php +++ b/app/Jobs/RestartProxyJob.php @@ -24,7 +24,7 @@ class RestartProxyJob implements ShouldBeEncrypted, ShouldQueue public function middleware(): array { - return [(new WithoutOverlapping($this->server->uuid))->dontRelease()]; + return [(new WithoutOverlapping($this->server->uuid))->expireAfter(60)]; } public function __construct(public Server $server) {} diff --git a/app/Jobs/ServerCheckJob.php b/app/Jobs/ServerCheckJob.php index 9818d5c6a..ffa298390 100644 --- a/app/Jobs/ServerCheckJob.php +++ b/app/Jobs/ServerCheckJob.php @@ -28,7 +28,7 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue public function middleware(): array { - return [(new WithoutOverlapping($this->server->uuid))->dontRelease()]; + return [(new WithoutOverlapping($this->server->uuid))->expireAfter(60)]; } public function __construct(public Server $server) {} diff --git a/app/Livewire/Project/Application/Source.php b/app/Livewire/Project/Application/Source.php index 013d8b8fe..e27a550c1 100644 --- a/app/Livewire/Project/Application/Source.php +++ b/app/Livewire/Project/Application/Source.php @@ -111,6 +111,7 @@ public function changeSource($sourceId, $sourceType) $this->application->update([ 'source_id' => $sourceId, 'source_type' => $sourceType, + 'repository_project_id' => null, ]); $this->application->refresh(); $this->getSources(); diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/All.php b/app/Livewire/Project/Shared/EnvironmentVariable/All.php index 57952ddb3..699dca187 100644 --- a/app/Livewire/Project/Shared/EnvironmentVariable/All.php +++ b/app/Livewire/Project/Shared/EnvironmentVariable/All.php @@ -236,15 +236,6 @@ private function deleteRemovedVariables($isPreview, $variables) return 0; } - // Check for system variables that shouldn't be deleted - foreach ($variablesToDelete as $envVar) { - if ($this->isProtectedEnvironmentVariable($envVar->key)) { - $this->dispatch('error', "Cannot delete system environment variable '{$envVar->key}'."); - - return 0; - } - } - // Check if any of these variables are used in Docker Compose if ($this->resource->type() === 'service' || $this->resource->build_pack === 'dockercompose') { foreach ($variablesToDelete as $envVar) { diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/Show.php b/app/Livewire/Project/Shared/EnvironmentVariable/Show.php index d58151abf..535ac6c67 100644 --- a/app/Livewire/Project/Shared/EnvironmentVariable/Show.php +++ b/app/Livewire/Project/Shared/EnvironmentVariable/Show.php @@ -178,13 +178,6 @@ public function submit() public function delete() { try { - // Check if the variable is protected - if ($this->isProtectedEnvironmentVariable($this->env->key)) { - $this->dispatch('error', "Cannot delete system environment variable '{$this->env->key}'."); - - return; - } - // Check if the variable is used in Docker Compose if ($this->type === 'service' || $this->type === 'application' && $this->env->resource()?->docker_compose) { [$isUsed, $reason] = $this->isEnvironmentVariableUsedInDockerCompose($this->env->key, $this->env->resource()?->docker_compose); diff --git a/app/Models/Application.php b/app/Models/Application.php index 01cdd565d..cc69155cb 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -458,22 +458,23 @@ public function gitBranchLocation(): Attribute { return Attribute::make( get: function () { + $base_dir = $this->base_directory ?? '/'; if (! is_null($this->source?->html_url) && ! is_null($this->git_repository) && ! is_null($this->git_branch)) { if (str($this->git_repository)->contains('bitbucket')) { - return "{$this->source->html_url}/{$this->git_repository}/src/{$this->git_branch}"; + return "{$this->source->html_url}/{$this->git_repository}/src/{$this->git_branch}{$base_dir}"; } - return "{$this->source->html_url}/{$this->git_repository}/tree/{$this->git_branch}"; + return "{$this->source->html_url}/{$this->git_repository}/tree/{$this->git_branch}{$base_dir}"; } // Convert the SSH URL to HTTPS URL if (strpos($this->git_repository, 'git@') === 0) { $git_repository = str_replace(['git@', ':', '.git'], ['', '/', ''], $this->git_repository); if (str($this->git_repository)->contains('bitbucket')) { - return "https://{$git_repository}/src/{$this->git_branch}"; + return "https://{$git_repository}/src/{$this->git_branch}{$base_dir}"; } - return "https://{$git_repository}/tree/{$this->git_branch}"; + return "https://{$git_repository}/tree/{$this->git_branch}{$base_dir}"; } return $this->git_repository; diff --git a/app/Models/Server.php b/app/Models/Server.php index 41af45f30..caf65cc58 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -492,11 +492,7 @@ public function proxyPath() if ($proxyType === ProxyTypes::TRAEFIK->value) { // Do nothing } elseif ($proxyType === ProxyTypes::CADDY->value) { - if (isDev()) { - $proxy_path = '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/proxy/caddy'; - } else { - $proxy_path = $proxy_path.'/caddy'; - } + $proxy_path = $proxy_path.'/caddy'; } elseif ($proxyType === ProxyTypes::NGINX->value) { if (isDev()) { $proxy_path = '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/proxy/nginx'; diff --git a/app/Models/ServiceDatabase.php b/app/Models/ServiceDatabase.php index c2a0df8cd..40d183033 100644 --- a/app/Models/ServiceDatabase.php +++ b/app/Models/ServiceDatabase.php @@ -141,6 +141,6 @@ public function isBackupSolutionAvailable() str($this->databaseType())->contains('postgres') || str($this->databaseType())->contains('postgis') || str($this->databaseType())->contains('mariadb') || - str($this->databaseType())->contains('mongodb'); + str($this->databaseType())->contains('mongo'); } } diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php index 0a6c8684d..40d4acc0d 100644 --- a/bootstrap/helpers/docker.php +++ b/bootstrap/helpers/docker.php @@ -729,8 +729,10 @@ function isDatabaseImage(?string $image = null) $image = str($image)->append(':latest'); } $imageName = $image->before(':'); - if (collect(DATABASE_DOCKER_IMAGES)->contains($imageName)) { - return true; + foreach (DATABASE_DOCKER_IMAGES as $database_docker_image) { + if (str($imageName)->contains($database_docker_image)) { + return true; + } } return false; diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index 72b92e95a..44e20c9b3 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -3014,168 +3014,159 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int $savedService->image = $image; $savedService->save(); } + } + $environment = collect(data_get($service, 'environment', [])); + $buildArgs = collect(data_get($service, 'build.args', [])); + $environment = $environment->merge($buildArgs); - $environment = collect(data_get($service, 'environment', [])); - $buildArgs = collect(data_get($service, 'build.args', [])); - $environment = $environment->merge($buildArgs); + // convert environment variables to one format + $environment = convertToKeyValueCollection($environment); - // convert environment variables to one format - $environment = convertToKeyValueCollection($environment); + // Add Coolify defined environments + $allEnvironments = $resource->environment_variables()->get(['key', 'value']); - // Add Coolify defined environments - $allEnvironments = $resource->environment_variables()->get(['key', 'value']); + $allEnvironments = $allEnvironments->mapWithKeys(function ($item) { + return [$item['key'] => $item['value']]; + }); + // filter and add magic environments + foreach ($environment as $key => $value) { + // Get all SERVICE_ variables from keys and values + $key = str($key); + $value = str($value); - $allEnvironments = $allEnvironments->mapWithKeys(function ($item) { - return [$item['key'] => $item['value']]; - }); - // filter and add magic environments - foreach ($environment as $key => $value) { - // Get all SERVICE_ variables from keys and values - $key = str($key); - $value = str($value); - - $regex = '/\$(\{?([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)\}?)/'; - preg_match_all($regex, $value, $valueMatches); - if (count($valueMatches[1]) > 0) { - foreach ($valueMatches[1] as $match) { - $match = replaceVariables($match); - if ($match->startsWith('SERVICE_')) { - if ($magicEnvironments->has($match->value())) { - continue; - } - $magicEnvironments->put($match->value(), ''); + $regex = '/\$(\{?([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)\}?)/'; + preg_match_all($regex, $value, $valueMatches); + if (count($valueMatches[1]) > 0) { + foreach ($valueMatches[1] as $match) { + $match = replaceVariables($match); + if ($match->startsWith('SERVICE_')) { + if ($magicEnvironments->has($match->value())) { + continue; } - } - } - - // Get magic environments where we need to preset the FQDN - if ($key->startsWith('SERVICE_FQDN_')) { - // SERVICE_FQDN_APP or SERVICE_FQDN_APP_3000 - if (substr_count(str($key)->value(), '_') === 3) { - $fqdnFor = $key->after('SERVICE_FQDN_')->beforeLast('_')->lower()->value(); - $port = $key->afterLast('_')->value(); - } else { - $fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value(); - $port = null; - } - if ($isApplication) { - $fqdn = generateFqdn($server, "{$resource->name}-$uuid"); - } elseif ($isService) { - if ($fqdnFor) { - $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); - } else { - $fqdn = generateFqdn($server, "{$savedService->name}-$uuid"); - } - } - - if ($value && get_class($value) === \Illuminate\Support\Stringable::class && $value->startsWith('/')) { - $path = $value->value(); - if ($path !== '/') { - $fqdn = "$fqdn$path"; - } - } - $fqdnWithPort = $fqdn; - if ($port) { - $fqdnWithPort = "$fqdn:$port"; - } - if ($isApplication && is_null($resource->fqdn)) { - data_forget($resource, 'environment_variables'); - data_forget($resource, 'environment_variables_preview'); - $resource->fqdn = $fqdnWithPort; - $resource->save(); - } elseif ($isService && is_null($savedService->fqdn)) { - $savedService->fqdn = $fqdnWithPort; - $savedService->save(); - } - - if (substr_count(str($key)->value(), '_') === 2) { - $resource->environment_variables()->firstOrCreate([ - 'key' => $key->value(), - 'resourceable_type' => get_class($resource), - 'resourceable_id' => $resource->id, - ], [ - 'value' => $fqdn, - 'is_build_time' => false, - 'is_preview' => false, - ]); - } - if (substr_count(str($key)->value(), '_') === 3) { - $newKey = str($key)->beforeLast('_'); - $resource->environment_variables()->firstOrCreate([ - 'key' => $newKey->value(), - 'resourceable_type' => get_class($resource), - 'resourceable_id' => $resource->id, - ], [ - 'value' => $fqdn, - 'is_build_time' => false, - 'is_preview' => false, - ]); + $magicEnvironments->put($match->value(), ''); } } } - $allMagicEnvironments = $allMagicEnvironments->merge($magicEnvironments); - if ($magicEnvironments->count() > 0) { - foreach ($magicEnvironments as $key => $value) { - $key = str($key); - $value = replaceVariables($value); - $command = parseCommandFromMagicEnvVariable($key); - $found = $resource->environment_variables()->where('key', $key->value())->where('resourceable_type', get_class($resource))->where('resourceable_id', $resource->id)->first(); - if ($found) { - continue; - } - if ($command->value() === 'FQDN') { - $fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value(); - if (str($fqdnFor)->contains('_')) { - $fqdnFor = str($fqdnFor)->before('_'); - } - if ($isApplication) { - $fqdn = generateFqdn($server, "{$resource->name}-$uuid"); - } elseif ($isService) { - $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); - } - $resource->environment_variables()->firstOrCreate([ - 'key' => $key->value(), - 'resourceable_type' => get_class($resource), - 'resourceable_id' => $resource->id, - ], [ - 'value' => $fqdn, - 'is_build_time' => false, - 'is_preview' => false, - ]); - } elseif ($command->value() === 'URL') { - $fqdnFor = $key->after('SERVICE_URL_')->lower()->value(); - if (str($fqdnFor)->contains('_')) { - $fqdnFor = str($fqdnFor)->before('_'); - } - if ($isApplication) { - $fqdn = generateFqdn($server, "{$resource->name}-$uuid"); - } elseif ($isService) { - $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); - } - $fqdn = str($fqdn)->replace('http://', '')->replace('https://', ''); - $resource->environment_variables()->firstOrCreate([ - 'key' => $key->value(), - 'resourceable_type' => get_class($resource), - 'resourceable_id' => $resource->id, - ], [ - 'value' => $fqdn, - 'is_build_time' => false, - 'is_preview' => false, - ]); + // Get magic environments where we need to preset the FQDN + if ($key->startsWith('SERVICE_FQDN_')) { + // SERVICE_FQDN_APP or SERVICE_FQDN_APP_3000 + if (substr_count(str($key)->value(), '_') === 3) { + $fqdnFor = $key->after('SERVICE_FQDN_')->beforeLast('_')->lower()->value(); + $port = $key->afterLast('_')->value(); + } else { + $fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value(); + $port = null; + } + if ($isApplication) { + $fqdn = generateFqdn($server, "$uuid"); + } elseif ($isService) { + if ($fqdnFor) { + $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); } else { - $value = generateEnvValue($command, $resource); - $resource->environment_variables()->firstOrCreate([ - 'key' => $key->value(), - 'resourceable_type' => get_class($resource), - 'resourceable_id' => $resource->id, - ], [ - 'value' => $value, - 'is_build_time' => false, - 'is_preview' => false, - ]); + $fqdn = generateFqdn($server, "{$savedService->name}-$uuid"); } } + + if ($value && get_class($value) === \Illuminate\Support\Stringable::class && $value->startsWith('/')) { + $path = $value->value(); + if ($path !== '/') { + $fqdn = "$fqdn$path"; + } + } + $fqdnWithPort = $fqdn; + if ($port) { + $fqdnWithPort = "$fqdn:$port"; + } + if ($isApplication && is_null($resource->fqdn)) { + data_forget($resource, 'environment_variables'); + data_forget($resource, 'environment_variables_preview'); + $resource->fqdn = $fqdnWithPort; + $resource->save(); + } elseif ($isService && is_null($savedService->fqdn)) { + $savedService->fqdn = $fqdnWithPort; + $savedService->save(); + } + + if (substr_count(str($key)->value(), '_') === 2) { + $resource->environment_variables()->firstOrCreate([ + 'key' => $key->value(), + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $fqdn, + 'is_build_time' => false, + 'is_preview' => false, + ]); + } + if (substr_count(str($key)->value(), '_') === 3) { + $newKey = str($key)->beforeLast('_'); + $resource->environment_variables()->firstOrCreate([ + 'key' => $newKey->value(), + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $fqdn, + 'is_build_time' => false, + 'is_preview' => false, + ]); + } + } + } + + $allMagicEnvironments = $allMagicEnvironments->merge($magicEnvironments); + if ($magicEnvironments->count() > 0) { + foreach ($magicEnvironments as $key => $value) { + $key = str($key); + $value = replaceVariables($value); + $command = parseCommandFromMagicEnvVariable($key); + $found = $resource->environment_variables()->where('key', $key->value())->where('resourceable_type', get_class($resource))->where('resourceable_id', $resource->id)->first(); + if ($found) { + continue; + } + if ($command->value() === 'FQDN') { + $fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value(); + if (str($fqdnFor)->contains('_')) { + $fqdnFor = str($fqdnFor)->before('_'); + } + $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); + $resource->environment_variables()->firstOrCreate([ + 'key' => $key->value(), + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $fqdn, + 'is_build_time' => false, + 'is_preview' => false, + ]); + } elseif ($command->value() === 'URL') { + $fqdnFor = $key->after('SERVICE_URL_')->lower()->value(); + if (str($fqdnFor)->contains('_')) { + $fqdnFor = str($fqdnFor)->before('_'); + } + $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); + $fqdn = str($fqdn)->replace('http://', '')->replace('https://', ''); + $resource->environment_variables()->firstOrCreate([ + 'key' => $key->value(), + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $fqdn, + 'is_build_time' => false, + 'is_preview' => false, + ]); + } else { + $value = generateEnvValue($command, $resource); + $resource->environment_variables()->firstOrCreate([ + 'key' => $key->value(), + 'resourceable_type' => get_class($resource), + 'resourceable_id' => $resource->id, + ], [ + 'value' => $value, + 'is_build_time' => false, + 'is_preview' => false, + ]); + } } } } @@ -3828,7 +3819,6 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int return $volume; }); - ray($serviceLabels); $payload = collect($service)->merge([ 'container_name' => $containerName, 'restart' => $restart->value(), diff --git a/config/constants.php b/config/constants.php index e7c7b68b9..c057a85db 100644 --- a/config/constants.php +++ b/config/constants.php @@ -2,7 +2,7 @@ return [ 'coolify' => [ - 'version' => '4.0.0-beta.409', + 'version' => '4.0.0-beta.410', 'helper_version' => '1.0.8', 'realtime_version' => '1.0.7', 'self_hosted' => env('SELF_HOSTED', true), diff --git a/openapi.json b/openapi.json index 5070f1be9..b48bd85e9 100644 --- a/openapi.json +++ b/openapi.json @@ -7729,6 +7729,12 @@ "type": "string", "format": "private-key" }, + "public_key": { + "type": "string" + }, + "fingerprint": { + "type": "string" + }, "is_git_related": { "type": "boolean" }, diff --git a/openapi.yaml b/openapi.yaml index 1fd42055b..239da6820 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -5159,6 +5159,10 @@ components: private_key: type: string format: private-key + public_key: + type: string + fingerprint: + type: string is_git_related: type: boolean team_id: diff --git a/other/nightly/versions.json b/other/nightly/versions.json index e940b635b..43fa56cca 100644 --- a/other/nightly/versions.json +++ b/other/nightly/versions.json @@ -1,10 +1,10 @@ { "coolify": { "v4": { - "version": "4.0.0-beta.409" + "version": "4.0.0-beta.410" }, "nightly": { - "version": "4.0.0-beta.410" + "version": "4.0.0-beta.411" }, "helper": { "version": "1.0.8" diff --git a/resources/views/livewire/project/application/source.blade.php b/resources/views/livewire/project/application/source.blade.php index 56bace154..85382055e 100644 --- a/resources/views/livewire/project/application/source.blade.php +++ b/resources/views/livewire/project/application/source.blade.php @@ -81,7 +81,7 @@ class="dark:text-warning">{{ $privateKeyName }} @empty -

No sources found
+
No other sources found
@endforelse diff --git a/resources/views/livewire/project/database/backup-edit.blade.php b/resources/views/livewire/project/database/backup-edit.blade.php index 603983864..f83af91a0 100644 --- a/resources/views/livewire/project/database/backup-edit.blade.php +++ b/resources/views/livewire/project/database/backup-edit.blade.php @@ -20,7 +20,12 @@
- + @if ($s3s->count() > 0) + + @else + + @endif
@if ($backup->save_s3)
diff --git a/resources/views/livewire/project/service/navbar.blade.php b/resources/views/livewire/project/service/navbar.blade.php index 7424f0940..3f3984116 100644 --- a/resources/views/livewire/project/service/navbar.blade.php +++ b/resources/views/livewire/project/service/navbar.blade.php @@ -129,7 +129,7 @@
diff --git a/routes/api.php b/routes/api.php index b65e59e8b..405d8240e 100644 --- a/routes/api.php +++ b/routes/api.php @@ -16,8 +16,13 @@ use Illuminate\Support\Facades\Route; Route::get('/health', [OtherController::class, 'healthcheck']); -Route::post('/feedback', [OtherController::class, 'feedback']); +Route::group([ + 'prefix' => 'v1', +], function () { + Route::get('/health', [OtherController::class, 'healthcheck']); +}); +Route::post('/feedback', [OtherController::class, 'feedback']); Route::group([ 'middleware' => ['auth:sanctum', 'api.ability:write'], 'prefix' => 'v1', @@ -117,7 +122,7 @@ Route::post('/services', [ServicesController::class, 'create_service'])->middleware(['api.ability:write']); Route::get('/services/{uuid}', [ServicesController::class, 'service_by_uuid'])->middleware(['api.ability:read']); - Route::patch('/services/{uuid}', [ServicesController::class, 'update_by_uuid'])->middleware(['ability:write']); + Route::patch('/services/{uuid}', [ServicesController::class, 'update_by_uuid'])->middleware(['api.ability:write']); Route::delete('/services/{uuid}', [ServicesController::class, 'delete_by_uuid'])->middleware(['api.ability:write']); Route::get('/services/{uuid}/envs', [ServicesController::class, 'envs'])->middleware(['api.ability:read']); diff --git a/templates/compose/plausible.yaml b/templates/compose/plausible.yaml index 71dfbe378..9bbacc508 100644 --- a/templates/compose/plausible.yaml +++ b/templates/compose/plausible.yaml @@ -6,7 +6,7 @@ services: plausible: - image: "ghcr.io/plausible/community-edition:v2.1.4" + image: "ghcr.io/plausible/community-edition:v3.0.1" command: 'sh -c "sleep 10 && /entrypoint.sh db createdb && /entrypoint.sh db migrate && /entrypoint.sh run"' environment: - SERVICE_FQDN_PLAUSIBLE @@ -17,13 +17,19 @@ services: - TOTP_VAULT_KEY=${SERVICE_REALBASE64_32_TOTP} - GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID} - GOOGLE_CLIENT_SECRET=${GOOGLE_CLIENT_SECRET} + - MAILER_ADAPTER=${MAILER_ADAPTER:-Bamboo.LocalAdapter} + - MAILER_EMAIL=${MAILER_EMAIL} + - MAILER_NAME=${MAILER_NAME} + - SMTP_HOST_ADDR=${SMTP_HOST_ADDR} + - SMTP_HOST_PORT=${SMTP_HOST_PORT} + - SMTP_USER_NAME=${SMTP_USER_NAME} + - SMTP_USER_PWD=${SMTP_USER_PWD} + - SMTP_HOST_SSL_ENABLED=${SMTP_HOST_SSL_ENABLED} depends_on: plausible-db: condition: service_healthy plausible-events-db: condition: service_healthy - mail: - condition: service_healthy healthcheck: test: [ @@ -39,15 +45,6 @@ services: retries: 5 start_period: 45s - mail: - image: bytemark/smtp - platform: linux/amd64 - healthcheck: - test: ["CMD-SHELL", "bash -c ':> /dev/tcp/127.0.0.1/25' || exit 1"] - interval: 5s - timeout: 10s - retries: 20 - plausible-db: image: "postgres:16-alpine" volumes: @@ -63,7 +60,9 @@ services: retries: 10 plausible-events-db: - image: "clickhouse/clickhouse-server:24.3.3.102-alpine" + image: "clickhouse/clickhouse-server:24.12-alpine" + environment: + - CLICKHOUSE_SKIP_USER_SETUP=1 volumes: - plausible-events-data:/var/lib/clickhouse - type: bind diff --git a/templates/compose/unsend.yaml b/templates/compose/unsend.yaml index 649b7f704..32323f4bb 100644 --- a/templates/compose/unsend.yaml +++ b/templates/compose/unsend.yaml @@ -35,6 +35,8 @@ services: unsend: image: unsend/unsend:latest + expose: + - 3000 environment: - SERVICE_FQDN_UNSEND_3000 - DATABASE_URL=postgresql://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@postgres:5432/${SERVICE_DB_POSTGRES:-unsend} @@ -48,13 +50,14 @@ services: - REDIS_URL=redis://redis:6379 - NEXT_PUBLIC_IS_CLOUD=${NEXT_PUBLIC_IS_CLOUD:-false} - API_RATE_LIMIT=${API_RATE_LIMIT:-1} + - HOSTNAME=0.0.0.0 depends_on: postgres: condition: service_healthy redis: condition: service_healthy healthcheck: - test: [ "CMD-SHELL", "wget -qO- http://127.0.0.1:3000 || exit 1" ] + test: [ "CMD-SHELL", "wget -qO- http://unsend:3000 || exit 1" ] interval: 5s retries: 10 timeout: 2s diff --git a/templates/service-templates.json b/templates/service-templates.json index 0ee5ddb51..be30cba5c 100644 --- a/templates/service-templates.json +++ b/templates/service-templates.json @@ -563,7 +563,7 @@ "minversion": "0.0.0", "port": "8080" }, - "denokv": { + "denoKV": { "documentation": "https://docs.deno.com/deploy/kv/manual/?utm_source=coolify.io", "slogan": "The Denoland key-value database", "compose": "c2VydmljZXM6CiAgZGVub2t2OgogICAgaW1hZ2U6ICdnaGNyLmlvL2Rlbm9sYW5kL2Rlbm9rdjpsYXRlc3QnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnQUNDRVNTX1RPS0VOPSR7U0VSVklDRV9QQVNTV09SRF9ERU5PS1Z9JwogICAgICAtIFNFUlZJQ0VfRlFETl9ERU5PS1ZfNDUxMgogICAgdm9sdW1lczoKICAgICAgLSAnJHtDT09MSUZZX1ZPTFVNRV9BUFB9Oi9kYXRhJwogICAgY29tbWFuZDogJy0tc3FsaXRlLXBhdGggL2RhdGEvZGVub2t2LnNxbGl0ZSBzZXJ2ZSAtLWFjY2Vzcy10b2tlbiAke1NFUlZJQ0VfUEFTU1dPUkRfREVOT0tWfScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBuYwogICAgICAgIC0gJy16dicKICAgICAgICAtIDEyNy4wLjAuMQogICAgICAgIC0gJzQ1MTInCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiA1cwogICAgICByZXRyaWVzOiAzCg==", @@ -3083,7 +3083,7 @@ "unsend": { "documentation": "https://docs.unsend.dev/get-started/self-hosting?utm_source=coolify.io", "slogan": "Unsend is an open-source alternative to Resend, Sendgrid, Mailgun and Postmark etc.", - "compose": "c2VydmljZXM6CiAgcG9zdGdyZXM6CiAgICBpbWFnZTogJ3Bvc3RncmVzOjE2JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ1BPU1RHUkVTX1VTRVI9JHtTRVJWSUNFX1VTRVJfUE9TVEdSRVN9JwogICAgICAtICdQT1NUR1JFU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9JwogICAgICAtICdQT1NUR1JFU19EQj0ke1NFUlZJQ0VfREJfUE9TVEdSRVM6LXVuc2VuZH0nCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ3BnX2lzcmVhZHkgLVUgJCR7UE9TVEdSRVNfVVNFUn0gLWQgJCR7UE9TVEdSRVNfREJ9JwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCiAgICB2b2x1bWVzOgogICAgICAtICd1bnNlbmQtcG9zdGdyZXMtZGF0YTovdmFyL2xpYi9wb3N0Z3Jlc3FsL2RhdGEnCiAgcmVkaXM6CiAgICBpbWFnZTogJ3JlZGlzOjcnCiAgICB2b2x1bWVzOgogICAgICAtICd1bnNlbmQtcmVkaXMtZGF0YTovZGF0YScKICAgIGNvbW1hbmQ6CiAgICAgIC0gcmVkaXMtc2VydmVyCiAgICAgIC0gJy0tbWF4bWVtb3J5LXBvbGljeScKICAgICAgLSBub2V2aWN0aW9uCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gcmVkaXMtY2xpCiAgICAgICAgLSBQSU5HCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMjAKICB1bnNlbmQ6CiAgICBpbWFnZTogJ3Vuc2VuZC91bnNlbmQ6bGF0ZXN0JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9GUUROX1VOU0VORF8zMDAwCiAgICAgIC0gJ0RBVEFCQVNFX1VSTD1wb3N0Z3Jlc3FsOi8vJHtTRVJWSUNFX1VTRVJfUE9TVEdSRVN9OiR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU31AcG9zdGdyZXM6NTQzMi8ke1NFUlZJQ0VfREJfUE9TVEdSRVM6LXVuc2VuZH0nCiAgICAgIC0gJ05FWFRBVVRIX1VSTD0ke1NFUlZJQ0VfRlFETl9VTlNFTkR9JwogICAgICAtICdORVhUQVVUSF9TRUNSRVQ9JHtTRVJWSUNFX0JBU0U2NF82NF9ORVhUQVVUSFNFQ1JFVH0nCiAgICAgIC0gJ0FXU19BQ0NFU1NfS0VZPSR7QVdTX0FDQ0VTU19LRVk6P30nCiAgICAgIC0gJ0FXU19TRUNSRVRfS0VZPSR7QVdTX1NFQ1JFVF9LRVk6P30nCiAgICAgIC0gJ0FXU19ERUZBVUxUX1JFR0lPTj0ke0FXU19ERUZBVUxUX1JFR0lPTjo/fScKICAgICAgLSAnR0lUSFVCX0lEPSR7R0lUSFVCX0lEfScKICAgICAgLSAnR0lUSFVCX1NFQ1JFVD0ke0dJVEhVQl9TRUNSRVR9JwogICAgICAtICdSRURJU19VUkw9cmVkaXM6Ly9yZWRpczo2Mzc5JwogICAgICAtICdORVhUX1BVQkxJQ19JU19DTE9VRD0ke05FWFRfUFVCTElDX0lTX0NMT1VEOi1mYWxzZX0nCiAgICAgIC0gJ0FQSV9SQVRFX0xJTUlUPSR7QVBJX1JBVEVfTElNSVQ6LTF9JwogICAgZGVwZW5kc19vbjoKICAgICAgcG9zdGdyZXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgcmVkaXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAnd2dldCAtcU8tIGh0dHA6Ly8xMjcuMC4wLjE6MzAwMCB8fCBleGl0IDEnCiAgICAgIGludGVydmFsOiA1cwogICAgICByZXRyaWVzOiAxMAogICAgICB0aW1lb3V0OiAycwo=", + "compose": "c2VydmljZXM6CiAgcG9zdGdyZXM6CiAgICBpbWFnZTogJ3Bvc3RncmVzOjE2JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ1BPU1RHUkVTX1VTRVI9JHtTRVJWSUNFX1VTRVJfUE9TVEdSRVN9JwogICAgICAtICdQT1NUR1JFU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9JwogICAgICAtICdQT1NUR1JFU19EQj0ke1NFUlZJQ0VfREJfUE9TVEdSRVM6LXVuc2VuZH0nCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ3BnX2lzcmVhZHkgLVUgJCR7UE9TVEdSRVNfVVNFUn0gLWQgJCR7UE9TVEdSRVNfREJ9JwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCiAgICB2b2x1bWVzOgogICAgICAtICd1bnNlbmQtcG9zdGdyZXMtZGF0YTovdmFyL2xpYi9wb3N0Z3Jlc3FsL2RhdGEnCiAgcmVkaXM6CiAgICBpbWFnZTogJ3JlZGlzOjcnCiAgICB2b2x1bWVzOgogICAgICAtICd1bnNlbmQtcmVkaXMtZGF0YTovZGF0YScKICAgIGNvbW1hbmQ6CiAgICAgIC0gcmVkaXMtc2VydmVyCiAgICAgIC0gJy0tbWF4bWVtb3J5LXBvbGljeScKICAgICAgLSBub2V2aWN0aW9uCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gcmVkaXMtY2xpCiAgICAgICAgLSBQSU5HCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMjAKICB1bnNlbmQ6CiAgICBpbWFnZTogJ3Vuc2VuZC91bnNlbmQ6bGF0ZXN0JwogICAgZXhwb3NlOgogICAgICAtIDMwMDAKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9VTlNFTkRfMzAwMAogICAgICAtICdEQVRBQkFTRV9VUkw9cG9zdGdyZXNxbDovLyR7U0VSVklDRV9VU0VSX1BPU1RHUkVTfToke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9QHBvc3RncmVzOjU0MzIvJHtTRVJWSUNFX0RCX1BPU1RHUkVTOi11bnNlbmR9JwogICAgICAtICdORVhUQVVUSF9VUkw9JHtTRVJWSUNFX0ZRRE5fVU5TRU5EfScKICAgICAgLSAnTkVYVEFVVEhfU0VDUkVUPSR7U0VSVklDRV9CQVNFNjRfNjRfTkVYVEFVVEhTRUNSRVR9JwogICAgICAtICdBV1NfQUNDRVNTX0tFWT0ke0FXU19BQ0NFU1NfS0VZOj99JwogICAgICAtICdBV1NfU0VDUkVUX0tFWT0ke0FXU19TRUNSRVRfS0VZOj99JwogICAgICAtICdBV1NfREVGQVVMVF9SRUdJT049JHtBV1NfREVGQVVMVF9SRUdJT046P30nCiAgICAgIC0gJ0dJVEhVQl9JRD0ke0dJVEhVQl9JRH0nCiAgICAgIC0gJ0dJVEhVQl9TRUNSRVQ9JHtHSVRIVUJfU0VDUkVUfScKICAgICAgLSAnUkVESVNfVVJMPXJlZGlzOi8vcmVkaXM6NjM3OScKICAgICAgLSAnTkVYVF9QVUJMSUNfSVNfQ0xPVUQ9JHtORVhUX1BVQkxJQ19JU19DTE9VRDotZmFsc2V9JwogICAgICAtICdBUElfUkFURV9MSU1JVD0ke0FQSV9SQVRFX0xJTUlUOi0xfScKICAgICAgLSBIT1NUTkFNRT0wLjAuMC4wCiAgICBkZXBlbmRzX29uOgogICAgICBwb3N0Z3JlczoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgICByZWRpczoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICd3Z2V0IC1xTy0gaHR0cDovL3Vuc2VuZDozMDAwIHx8IGV4aXQgMScKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHJldHJpZXM6IDEwCiAgICAgIHRpbWVvdXQ6IDJzCg==", "tags": [ "resend", "mailer", diff --git a/versions.json b/versions.json index e940b635b..43fa56cca 100644 --- a/versions.json +++ b/versions.json @@ -1,10 +1,10 @@ { "coolify": { "v4": { - "version": "4.0.0-beta.409" + "version": "4.0.0-beta.410" }, "nightly": { - "version": "4.0.0-beta.410" + "version": "4.0.0-beta.411" }, "helper": { "version": "1.0.8"