diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 20ebe0caa..149cde122 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -2042,22 +2042,17 @@ private function generate_build_env_variables() if ($this->pull_request_id === 0) { foreach ($this->application->build_environment_variables as $env) { $value = escapeshellarg($env->real_value); - if (str($value)->contains("\n") && data_get($env, 'is_multiline') === true) { - $value = str_replace("\n", "\\\n", $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); - if (str($value)->contains("\n") && data_get($env, 'is_multiline') === true) { - $value = str_replace("\n", "\\\n", $value); - } $this->build_args->push("--build-arg {$env->key}={$value}"); } } $this->build_args = $this->build_args->implode(' '); + ray($this->build_args); } private function add_build_env_variables_to_dockerfile() @@ -2068,19 +2063,18 @@ private function add_build_env_variables_to_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) { - if (str($env->real_value)->contains("\n") && data_get($env, 'is_multiline') === true) { - $value = str_replace("\n", "\\\n", $env->real_value); + if (data_get($env, 'is_multiline') === true) { + $dockerfile->splice(1, 0, "ARG {$env->key}"); } else { - $value = $env->real_value; + $dockerfile->splice(1, 0, "ARG {$env->key}={$env->real_value}"); } - $dockerfile->splice(1, 0, "ARG {$env->key}={$value}"); } } else { foreach ($this->application->build_environment_variables_preview as $env) { - if (str($env->real_value)->contains("\n") && data_get($env, 'is_multiline') === true) { - $value = str_replace("\n", "\\\n", $env->real_value); + if (data_get($env, 'is_multiline') === true) { + $dockerfile->splice(1, 0, "ARG {$env->key}"); } else { - $value = $env->real_value; + $dockerfile->splice(1, 0, "ARG {$env->key}={$env->real_value}"); } $dockerfile->splice(1, 0, "ARG {$env->key}={$env->real_value}"); } diff --git a/app/Livewire/Project/Application/General.php b/app/Livewire/Project/Application/General.php index a82c0c428..fde85755d 100644 --- a/app/Livewire/Project/Application/General.php +++ b/app/Livewire/Project/Application/General.php @@ -91,6 +91,7 @@ class General extends Component 'application.settings.is_build_server_enabled' => 'boolean|required', 'application.settings.is_container_label_escape_enabled' => 'boolean|required', 'application.watch_paths' => 'nullable', + 'application.redirect' => 'string|required', ]; protected $validationAttributes = [ @@ -128,6 +129,7 @@ class General extends Component 'application.settings.is_build_server_enabled' => 'Is build server enabled', 'application.settings.is_container_label_escape_enabled' => 'Is container label escape enabled', 'application.watch_paths' => 'Watch paths', + 'application.redirect' => 'Redirect', ]; public function mount() @@ -151,7 +153,7 @@ public function mount() $this->is_container_label_escape_enabled = $this->application->settings->is_container_label_escape_enabled; $this->customLabels = $this->application->parseContainerLabels(); if (! $this->customLabels && $this->application->destination->server->proxyType() !== 'NONE') { - $this->customLabels = str(implode('|', generateLabelsApplication($this->application)))->replace('|', "\n"); + $this->customLabels = str(implode("|coolify|", generateLabelsApplication($this->application)))->replace("|coolify|", "\n"); $this->application->custom_labels = base64_encode($this->customLabels); $this->application->save(); } @@ -298,7 +300,7 @@ public function getWildcardDomain() public function resetDefaultLabels() { - $this->customLabels = str(implode('|', generateLabelsApplication($this->application)))->replace('|', "\n"); + $this->customLabels = str(implode("|coolify|", generateLabelsApplication($this->application)))->replace("|coolify|", "\n"); $this->ports_exposes = $this->application->ports_exposes; $this->is_container_label_escape_enabled = $this->application->settings->is_container_label_escape_enabled; $this->application->custom_labels = base64_encode($this->customLabels); @@ -306,6 +308,7 @@ public function resetDefaultLabels() if ($this->application->build_pack === 'dockercompose') { $this->loadComposeFile(); } + $this->dispatch('configurationChanged'); } public function checkFqdns($showToaster = true) @@ -323,10 +326,26 @@ public function checkFqdns($showToaster = true) $this->application->fqdn = $domains->implode(','); } } + public function set_redirect() + { + try { + $has_www = collect($this->application->fqdns)->filter(fn ($fqdn) => str($fqdn)->contains('www.'))->count(); + if ($has_www === 0 && $this->application->redirect === 'www') { + $this->dispatch('error', 'You want to redirect to www, but you do not have a www domain set.

Please add www to your domain list and as an A DNS record (if applicable).'); + return; + } + $this->application->save(); + $this->resetDefaultLabels(); + $this->dispatch('success', 'Redirect updated.'); + } catch (\Throwable $e) { + return handleError($e, $this); + } + } public function submit($showToaster = true) { try { + $this->set_redirect(); $this->application->fqdn = str($this->application->fqdn)->replaceEnd(',', '')->trim(); $this->application->fqdn = str($this->application->fqdn)->replaceStart(',', '')->trim(); $this->application->fqdn = str($this->application->fqdn)->trim()->explode(',')->map(function ($domain) { @@ -339,7 +358,7 @@ public function submit($showToaster = true) $this->application->save(); if (! $this->customLabels && $this->application->destination->server->proxyType() !== 'NONE') { - $this->customLabels = str(implode('|', generateLabelsApplication($this->application)))->replace('|', "\n"); + $this->customLabels = str(implode("|coolify|", generateLabelsApplication($this->application)))->replace("|coolify|", "\n"); $this->application->custom_labels = base64_encode($this->customLabels); $this->application->save(); } diff --git a/app/Livewire/Project/Shared/ResourceOperations.php b/app/Livewire/Project/Shared/ResourceOperations.php index 6c2bca2e0..9b8002e5d 100644 --- a/app/Livewire/Project/Shared/ResourceOperations.php +++ b/app/Livewire/Project/Shared/ResourceOperations.php @@ -51,7 +51,7 @@ public function cloneTo($destination_id) ]); $new_resource->save(); if ($new_resource->destination->server->proxyType() !== 'NONE') { - $customLabels = str(implode('|', generateLabelsApplication($new_resource)))->replace('|', "\n"); + $customLabels = str(implode("|coolify|", generateLabelsApplication($new_resource)))->replace("|coolify|", "\n"); $new_resource->custom_labels = base64_encode($customLabels); $new_resource->save(); } diff --git a/app/Models/Application.php b/app/Models/Application.php index c28e8aefa..dae1a520b 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -632,7 +632,7 @@ public function isLogDrainEnabled() 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; + $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; if ($this->pull_request_id === 0 || $this->pull_request_id === null) { $newConfigHash .= json_encode($this->environment_variables()->get('value')->sort()); } else { @@ -1032,7 +1032,7 @@ public function parseContainerLabels(?ApplicationPreview $preview = null) $customLabels = base64_decode($this->custom_labels); if (mb_detect_encoding($customLabels, 'ASCII', true) === false) { ray('custom_labels contains non-ascii characters'); - $customLabels = str(implode('|', generateLabelsApplication($this, $preview)))->replace('|', "\n"); + $customLabels = str(implode("|coolify|", generateLabelsApplication($this, $preview)))->replace("|coolify|", "\n"); } $this->custom_labels = base64_encode($customLabels); $this->save(); diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php index 902c287f3..6ccf84165 100644 --- a/bootstrap/helpers/docker.php +++ b/bootstrap/helpers/docker.php @@ -246,7 +246,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, ?string $image = 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, string $redirect_direction = 'both') { $labels = collect([]); if ($serviceLabels) { @@ -259,7 +259,7 @@ function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains, $url = Url::fromString($domain); $host = $url->getHost(); $path = $url->getPath(); - + $host_without_www = str($host)->replace('www.', ''); $schema = $url->getScheme(); $port = $url->getPort(); if (is_null($port) && ! is_null($onlyPort)) { @@ -278,6 +278,12 @@ function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains, if ($is_gzip_enabled) { $labels->push("caddy_{$loop}.encode=zstd gzip"); } + if ($redirect_direction === 'www' && !str($host)->startsWith('www.')) { + $labels->push("caddy_{$loop}.redir={$schema}://www.{$host}{uri}"); + } + if ($redirect_direction === 'non-www' && str($host)->startsWith('www.')) { + $labels->push("caddy_{$loop}.redir={$schema}://{$host_without_www}{uri}"); + } if (isDev()) { // $labels->push("caddy_{$loop}.tls=internal"); } @@ -285,7 +291,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, ?string $image = null) +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, string $redirect_direction = 'both') { $labels = collect([]); $labels->push('traefik.enable=true'); @@ -296,6 +302,8 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_ $basic_auth_middleware = null; $redirect = false; $redirect_middleware = null; + + if ($serviceLabels) { $basic_auth = $serviceLabels->contains(function ($value) { return str_contains($value, 'basicauth'); @@ -329,6 +337,7 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_ if ($generate_unique_uuid) { $uuid = new Cuid2(7); } + $url = Url::fromString($domain); $host = $url->getHost(); $path = $url->getPath(); @@ -347,6 +356,19 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_ $labels->push("traefik.http.middlewares.redir-ghost.redirectregex.regex=^{$path}/(.*)"); $labels->push('traefik.http.middlewares.redir-ghost.redirectregex.replacement=/$1'); } + + $to_www_name = "{$loop}-{$uuid}-to-www"; + $to_non_www_name = "{$loop}-{$uuid}-to-non-www"; + $redirect_to_non_www = [ + "traefik.http.middlewares.{$to_non_www_name}.redirectregex.regex=^(http|https)://www\.(.+)", + "traefik.http.middlewares.{$to_non_www_name}.redirectregex.replacement=\${1}://\${2}", + "traefik.http.middlewares.{$to_non_www_name}.redirectregex.permanent=false" + ]; + $redirect_to_www = [ + "traefik.http.middlewares.{$to_www_name}.redirectregex.regex=^(http|https)://(?:www\.)?(.+)", + "traefik.http.middlewares.{$to_www_name}.redirectregex.replacement=\${1}://www.\${2}", + "traefik.http.middlewares.{$to_www_name}.redirectregex.permanent=false" + ]; if ($schema === 'https') { // Set labels for https $labels->push("traefik.http.routers.{$https_label}.rule=Host(`{$host}`) && PathPrefix(`{$path}`)"); @@ -373,6 +395,14 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_ if (str($image)->contains('ghost')) { $middlewares->push('redir-ghost'); } + if ($redirect_direction === 'non-www' && str($host)->startsWith('www.')) { + $labels = $labels->merge($redirect_to_non_www); + $middlewares->push($to_non_www_name); + } + if ($redirect_direction === 'www' && !str($host)->startsWith('www.')) { + $labels = $labels->merge($redirect_to_www); + $middlewares->push($to_www_name); + } if ($middlewares->isNotEmpty()) { $middlewares = $middlewares->join(','); $labels->push("traefik.http.routers.{$https_label}.middlewares={$middlewares}"); @@ -391,6 +421,14 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_ if (str($image)->contains('ghost')) { $middlewares->push('redir-ghost'); } + if ($redirect_direction === 'non-www' && str($host)->startsWith('www.')) { + $labels = $labels->merge($redirect_to_non_www); + $middlewares->push($to_non_www_name); + } + if ($redirect_direction === 'www' && !str($host)->startsWith('www.')) { + $labels = $labels->merge($redirect_to_www); + $middlewares->push($to_www_name); + } if ($middlewares->isNotEmpty()) { $middlewares = $middlewares->join(','); $labels->push("traefik.http.routers.{$https_label}.middlewares={$middlewares}"); @@ -435,6 +473,14 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_ if (str($image)->contains('ghost')) { $middlewares->push('redir-ghost'); } + if ($redirect_direction === 'non-www' && str($host)->startsWith('www.')) { + $labels = $labels->merge($redirect_to_non_www); + $middlewares->push($to_non_www_name); + } + if ($redirect_direction === 'www' && !str($host)->startsWith('www.')) { + $labels = $labels->merge($redirect_to_www); + $middlewares->push($to_www_name); + } if ($middlewares->isNotEmpty()) { $middlewares = $middlewares->join(','); $labels->push("traefik.http.routers.{$http_label}.middlewares={$middlewares}"); @@ -453,6 +499,14 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_ if (str($image)->contains('ghost')) { $middlewares->push('redir-ghost'); } + if ($redirect_direction === 'non-www' && str($host)->startsWith('www.')) { + $labels = $labels->merge($redirect_to_non_www); + $middlewares->push($to_non_www_name); + } + if ($redirect_direction === 'www' && !str($host)->startsWith('www.')) { + $labels = $labels->merge($redirect_to_www); + $middlewares->push($to_www_name); + } if ($middlewares->isNotEmpty()) { $middlewares = $middlewares->join(','); $labels->push("traefik.http.routers.{$http_label}.middlewares={$middlewares}"); @@ -488,7 +542,8 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview onlyPort: $onlyPort, is_force_https_enabled: $application->isForceHttpsEnabled(), is_gzip_enabled: $application->isGzipEnabled(), - is_stripprefix_enabled: $application->isStripprefixEnabled() + is_stripprefix_enabled: $application->isStripprefixEnabled(), + redirect_direction: $application->redirect )); // Add Caddy labels $labels = $labels->merge(fqdnLabelsForCaddy( @@ -498,7 +553,8 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview onlyPort: $onlyPort, is_force_https_enabled: $application->isForceHttpsEnabled(), is_gzip_enabled: $application->isGzipEnabled(), - is_stripprefix_enabled: $application->isStripprefixEnabled() + is_stripprefix_enabled: $application->isStripprefixEnabled(), + redirect_direction: $application->redirect )); } } else { diff --git a/config/sentry.php b/config/sentry.php index c54a55420..33a24edfb 100644 --- a/config/sentry.php +++ b/config/sentry.php @@ -7,7 +7,7 @@ // 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.296', + 'release' => '4.0.0-beta.297', // 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 fc0e7ee70..06c1e6c66 100644 --- a/config/version.php +++ b/config/version.php @@ -1,3 +1,3 @@ string('redirect')->enum('www', 'non-www', 'both')->default('both')->after('domain'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('applications', function (Blueprint $table) { + $table->dropColumn('redirect'); + }); + } +}; diff --git a/resources/views/auth/register.blade.php b/resources/views/auth/register.blade.php index e04d3633d..297640111 100644 --- a/resources/views/auth/register.blade.php +++ b/resources/views/auth/register.blade.php @@ -1,3 +1,13 @@ +environment('local') ? $localValue : ''); +} + +$name = getOldOrLocal('name', 'test3 normal user'); +$email = getOldOrLocal('email', 'test3@example.com'); +?> +
@@ -11,27 +21,16 @@
@csrf - @env('local') - -
- - +
- @else - - -
- - -
- @endenv Register {{ __('auth.already_registered') }} diff --git a/resources/views/components/forms/select.blade.php b/resources/views/components/forms/select.blade.php index 0a19923fc..02308ceb5 100644 --- a/resources/views/components/forms/select.blade.php +++ b/resources/views/components/forms/select.blade.php @@ -9,8 +9,8 @@ @endif @endif - merge(['class' => $defaultClass]) }} @required($required) wire:dirty.class.remove='dark:focus:ring-coolgray-300 dark:ring-coolgray-300' + wire:dirty.class="dark:focus:ring-warning dark:ring-warning" wire:loading.attr="disabled" name={{ $id }} @if ($attributes->whereStartsWith('wire:model')->first()) {{ $attributes->whereStartsWith('wire:model')->first() }} @else wire:model={{ $id }} @endif> {{ $slot }} diff --git a/resources/views/components/popup-small.blade.php b/resources/views/components/popup-small.blade.php index ba6839bab..1bd996727 100644 --- a/resources/views/components/popup-small.blade.php +++ b/resources/views/components/popup-small.blade.php @@ -8,7 +8,7 @@ x-transition:leave-end="translate-y-full" x-init="setTimeout(() => { bannerVisible = true }, bannerVisibleAfter);" class="fixed bottom-0 right-0 h-auto duration-300 ease-out px-5 pb-5 max-w-[46rem] z-[999]" x-cloak>
+ class="flex flex-row items-center justify-between w-full h-full max-w-4xl p-6 mx-auto bg-white border shadow-lg lg:border-t dark:border-coolgray-300 dark:bg-coolgray-100 hover:dark:bg-coolgray-100 lg:p-8 sm:rounded">
@if (isset($icon)) @@ -23,7 +23,7 @@ class="w-full mb-1 text-base font-bold leading-none -translate-y-1 text-neutral-
{{ $description }}
-
+
+ + + + + + + +
Set Direction
+
+ This will reset the container labels. Are you sure? +
+
@endif @if ($application->build_pack !== 'dockercompose') diff --git a/resources/views/livewire/project/resource/index.blade.php b/resources/views/livewire/project/resource/index.blade.php index 56fc1ee43..1f9008e25 100644 --- a/resources/views/livewire/project/resource/index.blade.php +++ b/resources/views/livewire/project/resource/index.blade.php @@ -50,7 +50,7 @@ class="items-center justify-center box">+ Add New Resource
-