| @@ -1500,7 +1500,7 @@ class ApplicationsController extends Controller | ||||
|             ], 404); | ||||
|         } | ||||
|         $server = $application->destination->server; | ||||
|         $allowedFields = ['name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'static_image', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'watch_paths', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'redirect', 'instant_deploy', 'use_build_server']; | ||||
|         $allowedFields = ['name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'static_image', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'watch_paths', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'redirect', 'instant_deploy', 'use_build_server', 'custom_nginx_configuration']; | ||||
| 
 | ||||
|         $validationRules = [ | ||||
|             'name' => 'string|max:255', | ||||
| @@ -1512,6 +1512,7 @@ class ApplicationsController extends Controller | ||||
|             'docker_compose_domains' => 'array|nullable', | ||||
|             'docker_compose_custom_start_command' => 'string|nullable', | ||||
|             'docker_compose_custom_build_command' => 'string|nullable', | ||||
|             'custom_nginx_configuration' => 'string|nullable', | ||||
|         ]; | ||||
|         $validationRules = array_merge($validationRules, sharedDataApplications()); | ||||
|         $validator = customApiValidator($request->all(), $validationRules); | ||||
| @@ -1530,6 +1531,25 @@ class ApplicationsController extends Controller | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         if ($request->has('custom_nginx_configuration')) { | ||||
|             if (! isBase64Encoded($request->custom_nginx_configuration)) { | ||||
|                 return response()->json([ | ||||
|                     'message' => 'Validation failed.', | ||||
|                     'errors' => [ | ||||
|                         'custom_nginx_configuration' => 'The custom_nginx_configuration should be base64 encoded.', | ||||
|                     ], | ||||
|                 ], 422); | ||||
|             } | ||||
|             $customNginxConfiguration = base64_decode($request->custom_nginx_configuration); | ||||
|             if (mb_detect_encoding($customNginxConfiguration, 'ASCII', true) === false) { | ||||
|                 return response()->json([ | ||||
|                     'message' => 'Validation failed.', | ||||
|                     'errors' => [ | ||||
|                         'custom_nginx_configuration' => 'The custom_nginx_configuration should be base64 encoded.', | ||||
|                     ], | ||||
|                 ], 422); | ||||
|             } | ||||
|         } | ||||
|         $return = $this->validateDataApplications($request, $server); | ||||
|         if ($return instanceof \Illuminate\Http\JsonResponse) { | ||||
|             return $return; | ||||
|   | ||||
| @@ -1988,24 +1988,13 @@ 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 | ||||
| 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; | ||||
|                 if (str($this->application->custom_nginx_configuration)->isNotEmpty()) { | ||||
|                     $nginx_config = base64_encode($this->application->custom_nginx_configuration); | ||||
|                 } else { | ||||
|                     $nginx_config = base64_encode(defaultNginxConfiguration()); | ||||
|                 } | ||||
| 
 | ||||
|                 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); | ||||
| @@ -2068,23 +2057,13 @@ 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");
 | ||||
|                 loggy($this->application->custom_nginx_configuration); | ||||
| 
 | ||||
|                 $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; | ||||
|                 if (str($this->application->custom_nginx_configuration)->isNotEmpty()) { | ||||
|                     $nginx_config = base64_encode($this->application->custom_nginx_configuration); | ||||
|                 } else { | ||||
|                     $nginx_config = base64_encode(defaultNginxConfiguration()); | ||||
|                 } | ||||
| 
 | ||||
|                 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); | ||||
|   | ||||
| @@ -84,6 +84,7 @@ class General extends Component | ||||
|         'application.pre_deployment_command_container' => 'nullable', | ||||
|         'application.post_deployment_command' => 'nullable', | ||||
|         'application.post_deployment_command_container' => 'nullable', | ||||
|         'application.custom_nginx_configuration' => 'nullable', | ||||
|         'application.settings.is_static' => 'boolean|required', | ||||
|         'application.settings.is_build_server_enabled' => 'boolean|required', | ||||
|         'application.settings.is_container_label_escape_enabled' => 'boolean|required', | ||||
| @@ -121,6 +122,7 @@ class General extends Component | ||||
|         'application.custom_docker_run_options' => 'Custom docker run commands', | ||||
|         'application.docker_compose_custom_start_command' => 'Docker compose custom start command', | ||||
|         'application.docker_compose_custom_build_command' => 'Docker compose custom build command', | ||||
|         'application.custom_nginx_configuration' => 'Custom Nginx configuration', | ||||
|         'application.settings.is_static' => 'Is static', | ||||
|         'application.settings.is_build_server_enabled' => 'Is build server enabled', | ||||
|         'application.settings.is_container_label_escape_enabled' => 'Is container label escape enabled', | ||||
| @@ -241,6 +243,13 @@ class General extends Component | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function updatedApplicationSettingsIsStatic($value) | ||||
|     { | ||||
|         if ($value) { | ||||
|             $this->generateNginxConfiguration(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function updatedApplicationBuildPack() | ||||
|     { | ||||
|         if ($this->application->build_pack !== 'nixpacks') { | ||||
| @@ -257,6 +266,7 @@ class General extends Component | ||||
|         if ($this->application->build_pack === 'static') { | ||||
|             $this->application->ports_exposes = $this->ports_exposes = 80; | ||||
|             $this->resetDefaultLabels(false); | ||||
|             $this->generateNginxConfiguration(); | ||||
|         } | ||||
|         $this->submit(); | ||||
|         $this->dispatch('buildPackUpdated'); | ||||
| @@ -274,6 +284,13 @@ class General extends Component | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function generateNginxConfiguration() | ||||
|     { | ||||
|         $this->application->custom_nginx_configuration = defaultNginxConfiguration(); | ||||
|         $this->application->save(); | ||||
|         $this->dispatch('success', 'Nginx configuration generated.'); | ||||
|     } | ||||
| 
 | ||||
|     public function resetDefaultLabels($manualReset = false) | ||||
|     { | ||||
|         try { | ||||
|   | ||||
| @@ -98,6 +98,7 @@ use Visus\Cuid2\Cuid2; | ||||
|         'updated_at' => ['type' => 'string', 'format' => 'date-time', 'description' => 'The date and time when the application was last updated.'], | ||||
|         'deleted_at' => ['type' => 'string', 'format' => 'date-time', 'nullable' => true, 'description' => 'The date and time when the application was deleted.'], | ||||
|         'compose_parsing_version' => ['type' => 'string', 'description' => 'How Coolify parse the compose file.'], | ||||
|         'custom_nginx_configuration' => ['type' => 'string', 'nullable' => true, 'description' => 'Custom Nginx configuration base64 encoded.'], | ||||
|     ] | ||||
| )] | ||||
| 
 | ||||
| @@ -114,11 +115,11 @@ class Application extends BaseModel | ||||
|     protected static function booted() | ||||
|     { | ||||
|         static::saving(function ($application) { | ||||
|             if ($application->fqdn === '') { | ||||
|                 $application->fqdn = null; | ||||
|             } | ||||
|             $payload = []; | ||||
|             if ($application->isDirty('fqdn')) { | ||||
|                 if ($application->fqdn === '') { | ||||
|                     $application->fqdn = null; | ||||
|                 } | ||||
|                 $payload['fqdn'] = $application->fqdn; | ||||
|             } | ||||
|             if ($application->isDirty('install_command')) { | ||||
| @@ -139,6 +140,11 @@ class Application extends BaseModel | ||||
|             if ($application->isDirty('status')) { | ||||
|                 $payload['last_online_at'] = now(); | ||||
|             } | ||||
|             if ($application->isDirty('custom_nginx_configuration')) { | ||||
|                 if ($application->custom_nginx_configuration === '') { | ||||
|                     $payload['custom_nginx_configuration'] = null; | ||||
|                 } | ||||
|             } | ||||
|             if (count($payload) > 0) { | ||||
|                 $application->forceFill($payload); | ||||
|             } | ||||
| @@ -632,6 +638,14 @@ class Application extends BaseModel | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     public function customNginxConfiguration(): Attribute | ||||
|     { | ||||
|         return Attribute::make( | ||||
|             set: fn ($value) => base64_encode($value), | ||||
|             get: fn ($value) => base64_decode($value), | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     public function portsExposesArray(): Attribute | ||||
|     { | ||||
|         return Attribute::make( | ||||
| @@ -862,7 +876,7 @@ class Application extends BaseModel | ||||
| 
 | ||||
|     public function isConfigurationChanged(bool $save = false) | ||||
|     { | ||||
|         $newConfigHash = $this->fqdn.$this->git_repository.$this->git_branch.$this->git_commit_sha.$this->build_pack.$this->static_image.$this->install_command.$this->build_command.$this->start_command.$this->ports_exposes.$this->ports_mappings.$this->base_directory.$this->publish_directory.$this->dockerfile.$this->dockerfile_location.$this->custom_labels.$this->custom_docker_run_options.$this->dockerfile_target_build.$this->redirect; | ||||
|         $newConfigHash = base64_encode($this->fqdn.$this->git_repository.$this->git_branch.$this->git_commit_sha.$this->build_pack.$this->static_image.$this->install_command.$this->build_command.$this->start_command.$this->ports_exposes.$this->ports_mappings.$this->base_directory.$this->publish_directory.$this->dockerfile.$this->dockerfile_location.$this->custom_labels.$this->custom_docker_run_options.$this->dockerfile_target_build.$this->redirect.$this->custom_nginx_configuration); | ||||
|         if ($this->pull_request_id === 0 || $this->pull_request_id === null) { | ||||
|             $newConfigHash .= json_encode($this->environment_variables()->get('value')->sort()); | ||||
|         } else { | ||||
|   | ||||
| @@ -4062,3 +4062,33 @@ function isEmailRateLimited(string $limiterKey, int $decaySeconds = 3600, ?calla | ||||
| 
 | ||||
|     return $rateLimited; | ||||
| } | ||||
| 
 | ||||
| function defaultNginxConfiguration(): string | ||||
| { | ||||
|     return 'server { | ||||
|     location / { | ||||
|         root   /usr/share/nginx/html; | ||||
|         index  index.html index.htm; | ||||
|         try_files $uri $uri/index.html =404; | ||||
|     } | ||||
| 
 | ||||
|     error_page 500 502 503 504 /50x.html; | ||||
|     location = /50x.html { | ||||
|         root /usr/share/nginx/html; | ||||
|         try_files $uri @redirect_to_index; | ||||
|         internal; | ||||
|     } | ||||
| 
 | ||||
|     error_page 404 = @handle_404; | ||||
| 
 | ||||
|     location @handle_404 { | ||||
|         root /usr/share/nginx/html; | ||||
|         try_files /404.html @redirect_to_index; | ||||
|         internal; | ||||
|     } | ||||
| 
 | ||||
|     location @redirect_to_index { | ||||
|         return 302 /; | ||||
|     } | ||||
| }'; | ||||
| } | ||||
|   | ||||
| @@ -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.365', | ||||
|     'release' => '4.0.0-beta.366', | ||||
| 
 | ||||
|     // When left empty or `null` the Laravel environment will be used
 | ||||
|     'environment' => config('app.env'), | ||||
|   | ||||
| @@ -1,3 +1,3 @@ | ||||
| <?php | ||||
| 
 | ||||
| return '4.0.0-beta.365'; | ||||
| return '4.0.0-beta.366'; | ||||
|   | ||||
| @@ -0,0 +1,28 @@ | ||||
| <?php | ||||
| 
 | ||||
| use Illuminate\Database\Migrations\Migration; | ||||
| use Illuminate\Database\Schema\Blueprint; | ||||
| use Illuminate\Support\Facades\Schema; | ||||
| 
 | ||||
| return new class extends Migration | ||||
| { | ||||
|     /** | ||||
|      * Run the migrations. | ||||
|      */ | ||||
|     public function up(): void | ||||
|     { | ||||
|         Schema::table('applications', function (Blueprint $table) { | ||||
|             $table->longText('custom_nginx_configuration')->nullable()->after('static_image'); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Reverse the migrations. | ||||
|      */ | ||||
|     public function down(): void | ||||
|     { | ||||
|         Schema::table('applications', function (Blueprint $table) { | ||||
|             $table->dropColumn('custom_nginx_configuration'); | ||||
|         }); | ||||
|     } | ||||
| }; | ||||
| @@ -61,8 +61,16 @@ | ||||
| 
 | ||||
|                 </div> | ||||
|             @endif | ||||
|             @if ($application->settings->is_static || $application->build_pack === 'static') | ||||
|                 <x-forms.textarea id="application.custom_nginx_configuration" | ||||
|                     placeholder="Empty means default configuration will be used." label="Custom Nginx Configuration" | ||||
|                     helper="You can add custom Nginx configuration here." /> | ||||
|                 <x-forms.button wire:click="generateNginxConfiguration">Generate Default Nginx | ||||
|                     Configuration</x-forms.button> | ||||
|             @endif | ||||
|             @if ($application->build_pack !== 'dockercompose') | ||||
|                 <div class="flex items-end gap-2"> | ||||
| 
 | ||||
|                     <x-forms.input placeholder="https://coolify.io" wire:model.blur="application.fqdn" label="Domains" | ||||
|                         helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- http://app.coolify.io,https://cloud.coolify.io/dashboard<br>- http://app.coolify.io/api/v3<br>- http://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container. " /> | ||||
|                     <x-forms.button wire:click="getWildcardDomain">Generate Domain | ||||
|   | ||||
| @@ -1,10 +1,10 @@ | ||||
| { | ||||
|     "coolify": { | ||||
|         "v4": { | ||||
|             "version": "4.0.0-beta.365" | ||||
|             "version": "4.0.0-beta.366" | ||||
|         }, | ||||
|         "nightly": { | ||||
|             "version": "4.0.0-beta.366" | ||||
|             "version": "4.0.0-beta.367" | ||||
|         }, | ||||
|         "helper": { | ||||
|             "version": "1.0.3" | ||||
| @@ -16,4 +16,4 @@ | ||||
|             "version": "0.0.15" | ||||
|         } | ||||
|     } | ||||
| } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 Andras Bacsai
					Andras Bacsai