From b379e50d90a9d31f32dc0efe14d5bc4147d0a660 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Mon, 11 Nov 2024 14:37:19 +0100 Subject: [PATCH] feat: custom nginx configuration for static deployments + fix 404 redirects in nginx conf --- .../Api/ApplicationsController.php | 22 +++++++++- app/Jobs/ApplicationDeploymentJob.php | 41 +++++-------------- app/Livewire/Project/Application/General.php | 17 ++++++++ app/Models/Application.php | 22 ++++++++-- bootstrap/helpers/shared.php | 28 +++++++++++++ ...d_custom_nginx_configuration_to_static.php | 28 +++++++++++++ .../project/application/general.blade.php | 8 ++++ 7 files changed, 130 insertions(+), 36 deletions(-) create mode 100644 database/migrations/2024_11_11_125335_add_custom_nginx_configuration_to_static.php diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php index f0eeb56d8..cffae12c6 100644 --- a/app/Http/Controllers/Api/ApplicationsController.php +++ b/app/Http/Controllers/Api/ApplicationsController.php @@ -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; diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 5ceed332a..4345b2479 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -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); diff --git a/app/Livewire/Project/Application/General.php b/app/Livewire/Project/Application/General.php index f1575a01f..ff29b74e9 100644 --- a/app/Livewire/Project/Application/General.php +++ b/app/Livewire/Project/Application/General.php @@ -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 { diff --git a/app/Models/Application.php b/app/Models/Application.php index 0ef787b2e..64244c900 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -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 { diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index f6875cc81..e09ae74f1 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -4062,3 +4062,31 @@ 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; + } + + 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 /; + } +}'; +} diff --git a/database/migrations/2024_11_11_125335_add_custom_nginx_configuration_to_static.php b/database/migrations/2024_11_11_125335_add_custom_nginx_configuration_to_static.php new file mode 100644 index 000000000..1c6e2daa0 --- /dev/null +++ b/database/migrations/2024_11_11_125335_add_custom_nginx_configuration_to_static.php @@ -0,0 +1,28 @@ +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'); + }); + } +}; diff --git a/resources/views/livewire/project/application/general.blade.php b/resources/views/livewire/project/application/general.blade.php index 99f6b3d08..19cb4bdfc 100644 --- a/resources/views/livewire/project/application/general.blade.php +++ b/resources/views/livewire/project/application/general.blade.php @@ -61,8 +61,16 @@ @endif + @if ($application->settings->is_static || $application->build_pack === 'static') + + Generate Default Nginx + Configuration + @endif @if ($application->build_pack !== 'dockercompose')
+ Generate Domain