diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php
index aa5f2ff14..3e0627b21 100644
--- a/app/Http/Controllers/Api/ApplicationsController.php
+++ b/app/Http/Controllers/Api/ApplicationsController.php
@@ -45,6 +45,7 @@ class ApplicationsController extends Controller
'private_key_id',
'value',
'real_value',
+ 'http_basic_auth_password',
]);
}
@@ -183,6 +184,9 @@ class ApplicationsController extends Controller
'docker_compose_domains' => ['type' => 'array', 'description' => 'The Docker Compose domains.'],
'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'],
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
+ 'is_http_basic_auth_enabled' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'],
+ 'http_basic_auth_username' => ['type' => 'string', 'nullable' => true, 'description' => 'Username for HTTP Basic Authentication'],
+ 'http_basic_auth_password' => ['type' => 'string', 'nullable' => true, 'description' => 'Password for HTTP Basic Authentication'],
],
)
),
@@ -299,6 +303,9 @@ class ApplicationsController extends Controller
'docker_compose_domains' => ['type' => 'array', 'description' => 'The Docker Compose domains.'],
'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'],
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
+ 'is_http_basic_auth_enabled' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'],
+ 'http_basic_auth_username' => ['type' => 'string', 'nullable' => true, 'description' => 'Username for HTTP Basic Authentication'],
+ 'http_basic_auth_password' => ['type' => 'string', 'nullable' => true, 'description' => 'Password for HTTP Basic Authentication'],
],
)
),
@@ -415,6 +422,9 @@ class ApplicationsController extends Controller
'docker_compose_domains' => ['type' => 'array', 'description' => 'The Docker Compose domains.'],
'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'],
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
+ 'is_http_basic_auth_enabled' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'],
+ 'http_basic_auth_username' => ['type' => 'string', 'nullable' => true, 'description' => 'Username for HTTP Basic Authentication'],
+ 'http_basic_auth_password' => ['type' => 'string', 'nullable' => true, 'description' => 'Password for HTTP Basic Authentication'],
],
)
),
@@ -515,6 +525,9 @@ class ApplicationsController extends Controller
'redirect' => ['type' => 'string', 'nullable' => true, 'description' => 'How to set redirect with Traefik / Caddy. www<->non-www.', 'enum' => ['www', 'non-www', 'both']],
'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application should be deployed instantly.'],
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
+ 'is_http_basic_auth_enabled' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'],
+ 'http_basic_auth_username' => ['type' => 'string', 'nullable' => true, 'description' => 'Username for HTTP Basic Authentication'],
+ 'http_basic_auth_password' => ['type' => 'string', 'nullable' => true, 'description' => 'Password for HTTP Basic Authentication'],
],
)
),
@@ -612,6 +625,9 @@ class ApplicationsController extends Controller
'redirect' => ['type' => 'string', 'nullable' => true, 'description' => 'How to set redirect with Traefik / Caddy. www<->non-www.', 'enum' => ['www', 'non-www', 'both']],
'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application should be deployed instantly.'],
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
+ 'is_http_basic_auth_enabled' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'],
+ 'http_basic_auth_username' => ['type' => 'string', 'nullable' => true, 'description' => 'Username for HTTP Basic Authentication'],
+ 'http_basic_auth_password' => ['type' => 'string', 'nullable' => true, 'description' => 'Password for HTTP Basic Authentication'],
],
)
),
@@ -711,7 +727,6 @@ class ApplicationsController extends Controller
private function create_application(Request $request, $type)
{
- $allowedFields = ['project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'private_key_uuid', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', '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', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'redirect', 'github_app_uuid', 'instant_deploy', 'dockerfile', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'watch_paths', 'use_build_server', 'static_image', 'custom_nginx_configuration'];
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalidTokenResponse();
@@ -721,6 +736,8 @@ class ApplicationsController extends Controller
if ($return instanceof \Illuminate\Http\JsonResponse) {
return $return;
}
+ $allowedFields = ['project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'private_key_uuid', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', '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', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'redirect', 'github_app_uuid', 'instant_deploy', 'dockerfile', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'watch_paths', 'use_build_server', 'static_image', 'custom_nginx_configuration', 'is_http_basic_auth_enabled', 'http_basic_auth_username', 'http_basic_auth_password'];
+
$validator = customApiValidator($request->all(), [
'name' => 'string|max:255',
'description' => 'string|nullable',
@@ -729,6 +746,9 @@ class ApplicationsController extends Controller
'environment_uuid' => 'string|nullable',
'server_uuid' => 'string|required',
'destination_uuid' => 'string',
+ 'is_http_basic_auth_enabled' => 'boolean',
+ 'http_basic_auth_username' => 'string|nullable',
+ 'http_basic_auth_password' => 'string|nullable',
]);
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
@@ -1758,25 +1778,19 @@ class ApplicationsController extends Controller
if (is_null($teamId)) {
return invalidTokenResponse();
}
-
- if ($request->collect()->count() == 0) {
- return response()->json([
- 'message' => 'Invalid request.',
- ], 400);
- }
$return = validateIncomingRequest($request);
if ($return instanceof \Illuminate\Http\JsonResponse) {
return $return;
}
- $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
+ $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
if (! $application) {
return response()->json([
'message' => 'Application not found',
], 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', 'custom_nginx_configuration'];
+ $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', 'is_http_basic_auth_enabled', 'http_basic_auth_username', 'http_basic_auth_password'];
$validationRules = [
'name' => 'string|max:255',
@@ -1789,6 +1803,9 @@ class ApplicationsController extends Controller
'docker_compose_custom_start_command' => 'string|nullable',
'docker_compose_custom_build_command' => 'string|nullable',
'custom_nginx_configuration' => 'string|nullable',
+ 'is_http_basic_auth_enabled' => 'boolean|nullable',
+ 'http_basic_auth_username' => 'string',
+ 'http_basic_auth_password' => 'string',
];
$validationRules = array_merge(sharedDataApplications(), $validationRules);
$validator = customApiValidator($request->all(), $validationRules);
@@ -1844,6 +1861,29 @@ class ApplicationsController extends Controller
'errors' => $errors,
], 422);
}
+
+ if ($request->has('is_http_basic_auth_enabled') && $request->is_http_basic_auth_enabled === true) {
+ if (blank($application->http_basic_auth_username) || blank($application->http_basic_auth_password)) {
+ $validationErrors = [];
+ if (blank($request->http_basic_auth_username)) {
+ $validationErrors['http_basic_auth_username'] = 'The http_basic_auth_username is required.';
+ }
+ if (blank($request->http_basic_auth_password)) {
+ $validationErrors['http_basic_auth_password'] = 'The http_basic_auth_password is required.';
+ }
+ if (count($validationErrors) > 0) {
+ return response()->json([
+ 'message' => 'Validation failed.',
+ 'errors' => $validationErrors,
+ ], 422);
+ }
+ }
+ }
+ if ($request->has('is_http_basic_auth_enabled') && $application->is_container_label_readonly_enabled === false) {
+ $application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
+ $application->save();
+ }
+
$domains = $request->domains;
$requestHasDomains = $request->has('domains');
if ($requestHasDomains && $server->isProxyShouldRun()) {
@@ -2562,10 +2602,6 @@ class ApplicationsController extends Controller
])->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 @@ class ServersController extends Controller
}
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 @@ class ServicesController extends Controller
$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 @@ class ServicesController extends Controller
}
$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 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
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 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
$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/Livewire/Project/Application/General.php b/app/Livewire/Project/Application/General.php
index eaa988b99..74f47232c 100644
--- a/app/Livewire/Project/Application/General.php
+++ b/app/Livewire/Project/Application/General.php
@@ -92,6 +92,9 @@ class General extends Component
'application.settings.is_container_label_escape_enabled' => 'boolean|required',
'application.settings.is_container_label_readonly_enabled' => 'boolean|required',
'application.settings.is_preserve_repository_enabled' => 'boolean|required',
+ 'application.is_http_basic_auth_enabled' => 'boolean|required',
+ 'application.http_basic_auth_username' => 'string|nullable',
+ 'application.http_basic_auth_password' => 'string|nullable',
'application.watch_paths' => 'nullable',
'application.redirect' => 'string|required',
];
@@ -178,6 +181,9 @@ class General extends Component
if ($this->application->settings->isDirty('is_spa')) {
$this->generateNginxConfiguration($this->application->settings->is_spa ? 'spa' : 'static');
}
+ if ($this->application->isDirty('is_http_basic_auth_enabled')) {
+ $this->application->save();
+ }
$this->application->settings->save();
$this->dispatch('success', 'Settings saved.');
$this->application->refresh();
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 @@ class Source extends Component
$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 @@ class All extends Component
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 @@ class Show extends Component
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 4ee41651d..b84b7cd37 100644
--- a/app/Models/Application.php
+++ b/app/Models/Application.php
@@ -103,6 +103,9 @@ use Visus\Cuid2\Cuid2;
'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.'],
+ 'is_http_basic_auth_enabled' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'],
+ 'http_basic_auth_username' => ['type' => 'string', 'nullable' => true, 'description' => 'Username for HTTP Basic Authentication'],
+ 'http_basic_auth_password' => ['type' => 'string', 'nullable' => true, 'description' => 'Password for HTTP Basic Authentication'],
]
)]
@@ -116,7 +119,10 @@ class Application extends BaseModel
protected $appends = ['server_status'];
- protected $casts = ['custom_network_aliases' => 'array'];
+ protected $casts = [
+ 'custom_network_aliases' => 'array',
+ 'http_basic_auth_password' => 'encrypted',
+ ];
public function customNetworkAliases(): Attribute
{
diff --git a/app/Models/PrivateKey.php b/app/Models/PrivateKey.php
index 97c32fa31..dbed7b439 100644
--- a/app/Models/PrivateKey.php
+++ b/app/Models/PrivateKey.php
@@ -17,8 +17,8 @@ use phpseclib3\Crypt\PublicKeyLoader;
'name' => ['type' => 'string'],
'description' => ['type' => 'string'],
'private_key' => ['type' => 'string', 'format' => 'private-key'],
- 'public_key' => ['type' => 'string'],
- 'fingerprint' => ['type' => 'string'],
+ 'public_key' => ['type' => 'string', 'description' => 'The public key of the private key.'],
+ 'fingerprint' => ['type' => 'string', 'description' => 'The fingerprint of the private key.'],
'is_git_related' => ['type' => 'boolean'],
'team_id' => ['type' => 'integer'],
'created_at' => ['type' => 'string'],
diff --git a/app/Models/Server.php b/app/Models/Server.php
index 41af45f30..89f6ad218 100644
--- a/app/Models/Server.php
+++ b/app/Models/Server.php
@@ -486,23 +486,13 @@ $schema://$host {
$base_path = config('constants.coolify.base_config_path');
$proxyType = $this->proxyType();
$proxy_path = "$base_path/proxy";
- // TODO: should use /traefik for already existing configurations?
- // Should move everything except /caddy and /nginx to /traefik
- // The code needs to be modified as well, so maybe it does not worth it
+
if ($proxyType === ProxyTypes::TRAEFIK->value) {
- // Do nothing
+ $proxy_path = '/';
} 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';
- } else {
- $proxy_path = $proxy_path.'/nginx';
- }
+ $proxy_path = $proxy_path.'/nginx';
}
return $proxy_path;
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 @@ class ServiceDatabase extends BaseModel
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 de80adbef..bade67eb8 100644
--- a/bootstrap/helpers/docker.php
+++ b/bootstrap/helpers/docker.php
@@ -296,7 +296,8 @@ 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, string $redirect_direction = 'both', ?string $predefinedPort = 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', ?string $predefinedPort = null, bool $is_http_basic_auth_enabled = false, ?string $http_basic_auth_username = null, ?string $http_basic_auth_password = null)
{
$labels = collect([]);
if ($serviceLabels) {
@@ -304,6 +305,12 @@ function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains,
} else {
$labels->push("caddy_ingress_network={$network}");
}
+
+ $is_http_basic_auth_enabled = $is_http_basic_auth_enabled && $http_basic_auth_username !== null && $http_basic_auth_password !== null;
+ if ($is_http_basic_auth_enabled) {
+ $hashedPassword = password_hash($http_basic_auth_password, PASSWORD_BCRYPT, ['cost' => 10]);
+ }
+
foreach ($domains as $loop => $domain) {
$url = Url::fromString($domain);
$host = $url->getHost();
@@ -340,20 +347,31 @@ function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains,
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");
+ if ($is_http_basic_auth_enabled) {
+ $labels->push("caddy_{$loop}.basicauth.{$http_basic_auth_username}=\"{$hashedPassword}\"");
}
}
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, string $redirect_direction = 'both')
+
+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', bool $is_http_basic_auth_enabled = false, ?string $http_basic_auth_username = null, ?string $http_basic_auth_password = null)
{
$labels = collect([]);
$labels->push('traefik.enable=true');
$labels->push('traefik.http.middlewares.gzip.compress=true');
$labels->push('traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https');
+ $is_http_basic_auth_enabled = $is_http_basic_auth_enabled && $http_basic_auth_username !== null && $http_basic_auth_password !== null;
+ $http_basic_auth_label = "http-basic-auth-{$uuid}";
+ if ($is_http_basic_auth_enabled) {
+ $hashedPassword = password_hash($http_basic_auth_password, PASSWORD_BCRYPT, ['cost' => 10]);
+ }
+
+ if ($is_http_basic_auth_enabled) {
+ $labels->push("traefik.http.middlewares.{$http_basic_auth_label}.basicauth.users={$http_basic_auth_username}:{$hashedPassword}");
+ }
+
$middlewares_from_labels = collect([]);
if ($serviceLabels) {
@@ -439,6 +457,9 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
$labels = $labels->merge($redirect_to_www);
$middlewares->push($to_www_name);
}
+ if ($is_http_basic_auth_enabled) {
+ $middlewares->push($http_basic_auth_label);
+ }
$middlewares_from_labels->each(function ($middleware_name) use ($middlewares) {
$middlewares->push($middleware_name);
});
@@ -462,6 +483,9 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
$labels = $labels->merge($redirect_to_www);
$middlewares->push($to_www_name);
}
+ if ($is_http_basic_auth_enabled) {
+ $middlewares->push($http_basic_auth_label);
+ }
$middlewares_from_labels->each(function ($middleware_name) use ($middlewares) {
$middlewares->push($middleware_name);
});
@@ -511,6 +535,9 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
$labels = $labels->merge($redirect_to_www);
$middlewares->push($to_www_name);
}
+ if ($is_http_basic_auth_enabled) {
+ $middlewares->push($http_basic_auth_label);
+ }
$middlewares_from_labels->each(function ($middleware_name) use ($middlewares) {
$middlewares->push($middleware_name);
});
@@ -534,6 +561,9 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
$labels = $labels->merge($redirect_to_www);
$middlewares->push($to_www_name);
}
+ if ($is_http_basic_auth_enabled) {
+ $middlewares->push($http_basic_auth_label);
+ }
$middlewares_from_labels->each(function ($middleware_name) use ($middlewares) {
$middlewares->push($middleware_name);
});
@@ -577,7 +607,10 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
is_force_https_enabled: $application->isForceHttpsEnabled(),
is_gzip_enabled: $application->isGzipEnabled(),
is_stripprefix_enabled: $application->isStripprefixEnabled(),
- redirect_direction: $application->redirect
+ redirect_direction: $application->redirect,
+ is_http_basic_auth_enabled: $application->is_http_basic_auth_enabled,
+ http_basic_auth_username: $application->http_basic_auth_username,
+ http_basic_auth_password: $application->http_basic_auth_password,
));
break;
case ProxyTypes::CADDY->value:
@@ -589,7 +622,10 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
is_force_https_enabled: $application->isForceHttpsEnabled(),
is_gzip_enabled: $application->isGzipEnabled(),
is_stripprefix_enabled: $application->isStripprefixEnabled(),
- redirect_direction: $application->redirect
+ redirect_direction: $application->redirect,
+ is_http_basic_auth_enabled: $application->is_http_basic_auth_enabled,
+ http_basic_auth_username: $application->http_basic_auth_username,
+ http_basic_auth_password: $application->http_basic_auth_password,
));
break;
}
@@ -601,7 +637,10 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
is_force_https_enabled: $application->isForceHttpsEnabled(),
is_gzip_enabled: $application->isGzipEnabled(),
is_stripprefix_enabled: $application->isStripprefixEnabled(),
- redirect_direction: $application->redirect
+ redirect_direction: $application->redirect,
+ is_http_basic_auth_enabled: $application->is_http_basic_auth_enabled,
+ http_basic_auth_username: $application->http_basic_auth_username,
+ http_basic_auth_password: $application->http_basic_auth_password,
));
$labels = $labels->merge(fqdnLabelsForCaddy(
network: $application->destination->network,
@@ -611,7 +650,10 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
is_force_https_enabled: $application->isForceHttpsEnabled(),
is_gzip_enabled: $application->isGzipEnabled(),
is_stripprefix_enabled: $application->isStripprefixEnabled(),
- redirect_direction: $application->redirect
+ redirect_direction: $application->redirect,
+ is_http_basic_auth_enabled: $application->is_http_basic_auth_enabled,
+ http_basic_auth_username: $application->http_basic_auth_username,
+ http_basic_auth_password: $application->http_basic_auth_password,
));
}
}
@@ -631,7 +673,10 @@ 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(),
+ is_http_basic_auth_enabled: $application->is_http_basic_auth_enabled,
+ http_basic_auth_username: $application->http_basic_auth_username,
+ http_basic_auth_password: $application->http_basic_auth_password,
));
break;
case ProxyTypes::CADDY->value:
@@ -642,7 +687,10 @@ 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(),
+ is_http_basic_auth_enabled: $application->is_http_basic_auth_enabled,
+ http_basic_auth_username: $application->http_basic_auth_username,
+ http_basic_auth_password: $application->http_basic_auth_password,
));
break;
}
@@ -653,7 +701,10 @@ 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(),
+ is_http_basic_auth_enabled: $application->is_http_basic_auth_enabled,
+ http_basic_auth_username: $application->http_basic_auth_username,
+ http_basic_auth_password: $application->http_basic_auth_password,
));
$labels = $labels->merge(fqdnLabelsForCaddy(
network: $application->destination->network,
@@ -662,7 +713,10 @@ 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(),
+ is_http_basic_auth_enabled: $application->is_http_basic_auth_enabled,
+ http_basic_auth_username: $application->http_basic_auth_username,
+ http_basic_auth_password: $application->http_basic_auth_password,
));
}
}
@@ -682,8 +736,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;
@@ -715,6 +771,7 @@ function convertDockerRunToCompose(?string $custom_docker_run_options = null)
'--ip' => 'ip',
'--shm-size' => 'shm_size',
'--gpus' => 'gpus',
+ '--hostname' => 'hostname',
]);
foreach ($matches as $match) {
$option = $match[1];
@@ -725,6 +782,16 @@ function convertDockerRunToCompose(?string $custom_docker_run_options = null)
$options[$option][] = $value;
$options[$option] = array_unique($options[$option]);
}
+ if ($option === '--hostname') {
+ // Match --hostname=value or --hostname value
+ $regexForParsingHostname = '/--hostname(?:=|\s+)([^\s]+)/';
+ preg_match($regexForParsingHostname, $custom_docker_run_options, $hostname_matches);
+ $value = $hostname_matches[1] ?? null;
+ if ($value && ! empty(trim($value))) {
+ $options[$option][] = $value;
+ $options[$option] = array_unique($options[$option]);
+ }
+ }
if (isset($match[2]) && $match[2] !== '') {
$value = $match[2];
$options[$option][] = $value;
@@ -761,8 +828,8 @@ function convertDockerRunToCompose(?string $custom_docker_run_options = null)
}
});
$compose_options->put($mapping[$option], $ulimits);
- } elseif ($option === '--shm-size') {
- if (! is_null($value) && is_array($value) && count($value) > 0) {
+ } elseif ($option === '--shm-size' || $option === '--hostname') {
+ if (! is_null($value) && is_array($value) && count($value) > 0 && ! empty(trim($value[0]))) {
$compose_options->put($mapping[$option], $value[0]);
}
} elseif ($option === '--gpus') {
@@ -770,7 +837,7 @@ function convertDockerRunToCompose(?string $custom_docker_run_options = null)
'driver' => 'nvidia',
'capabilities' => ['gpu'],
];
- if (! is_null($value) && is_array($value) && count($value) > 0) {
+ if (! is_null($value) && is_array($value) && count($value) > 0 && ! empty(trim($value[0]))) {
if (str($value[0]) != 'all') {
if (str($value[0])->contains(',')) {
$payload['device_ids'] = str($value[0])->explode(',')->toArray();
@@ -800,7 +867,6 @@ function convertDockerRunToCompose(?string $custom_docker_run_options = null)
continue;
}
- $compose_options->forget($option);
}
}
diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php
index aa821dc22..44e20c9b3 100644
--- a/bootstrap/helpers/shared.php
+++ b/bootstrap/helpers/shared.php
@@ -3819,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/database/migrations/2025_04_17_110026_add_application_http_basic_auth_fields.php b/database/migrations/2025_04_17_110026_add_application_http_basic_auth_fields.php
new file mode 100644
index 000000000..4b8e11bc8
--- /dev/null
+++ b/database/migrations/2025_04_17_110026_add_application_http_basic_auth_fields.php
@@ -0,0 +1,32 @@
+boolean('is_http_basic_auth_enabled')->default(false);
+ $table->string('http_basic_auth_username')->nullable(true)->default(null);
+ $table->string('http_basic_auth_password')->nullable(true)->default(null);
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::table('applications', function (Blueprint $table) {
+ $table->dropColumn('is_http_basic_auth_enabled');
+ $table->dropColumn('http_basic_auth_username');
+ $table->dropColumn('http_basic_auth_password');
+ });
+ }
+};
diff --git a/openapi.json b/openapi.json
index 5070f1be9..40f663e51 100644
--- a/openapi.json
+++ b/openapi.json
@@ -339,6 +339,20 @@
"type": "boolean",
"nullable": true,
"description": "Use build server."
+ },
+ "is_http_basic_auth_enabled": {
+ "type": "boolean",
+ "description": "HTTP Basic Authentication enabled."
+ },
+ "http_basic_auth_username": {
+ "type": "string",
+ "nullable": true,
+ "description": "Username for HTTP Basic Authentication"
+ },
+ "http_basic_auth_password": {
+ "type": "string",
+ "nullable": true,
+ "description": "Password for HTTP Basic Authentication"
}
},
"type": "object"
@@ -673,6 +687,20 @@
"type": "boolean",
"nullable": true,
"description": "Use build server."
+ },
+ "is_http_basic_auth_enabled": {
+ "type": "boolean",
+ "description": "HTTP Basic Authentication enabled."
+ },
+ "http_basic_auth_username": {
+ "type": "string",
+ "nullable": true,
+ "description": "Username for HTTP Basic Authentication"
+ },
+ "http_basic_auth_password": {
+ "type": "string",
+ "nullable": true,
+ "description": "Password for HTTP Basic Authentication"
}
},
"type": "object"
@@ -1007,6 +1035,20 @@
"type": "boolean",
"nullable": true,
"description": "Use build server."
+ },
+ "is_http_basic_auth_enabled": {
+ "type": "boolean",
+ "description": "HTTP Basic Authentication enabled."
+ },
+ "http_basic_auth_username": {
+ "type": "string",
+ "nullable": true,
+ "description": "Username for HTTP Basic Authentication"
+ },
+ "http_basic_auth_password": {
+ "type": "string",
+ "nullable": true,
+ "description": "Password for HTTP Basic Authentication"
}
},
"type": "object"
@@ -1270,6 +1312,20 @@
"type": "boolean",
"nullable": true,
"description": "Use build server."
+ },
+ "is_http_basic_auth_enabled": {
+ "type": "boolean",
+ "description": "HTTP Basic Authentication enabled."
+ },
+ "http_basic_auth_username": {
+ "type": "string",
+ "nullable": true,
+ "description": "Username for HTTP Basic Authentication"
+ },
+ "http_basic_auth_password": {
+ "type": "string",
+ "nullable": true,
+ "description": "Password for HTTP Basic Authentication"
}
},
"type": "object"
@@ -1516,6 +1572,20 @@
"type": "boolean",
"nullable": true,
"description": "Use build server."
+ },
+ "is_http_basic_auth_enabled": {
+ "type": "boolean",
+ "description": "HTTP Basic Authentication enabled."
+ },
+ "http_basic_auth_username": {
+ "type": "string",
+ "nullable": true,
+ "description": "Username for HTTP Basic Authentication"
+ },
+ "http_basic_auth_password": {
+ "type": "string",
+ "nullable": true,
+ "description": "Password for HTTP Basic Authentication"
}
},
"type": "object"
@@ -7553,6 +7623,20 @@
"type": "string",
"nullable": true,
"description": "Custom Nginx configuration base64 encoded."
+ },
+ "is_http_basic_auth_enabled": {
+ "type": "boolean",
+ "description": "HTTP Basic Authentication enabled."
+ },
+ "http_basic_auth_username": {
+ "type": "string",
+ "nullable": true,
+ "description": "Username for HTTP Basic Authentication"
+ },
+ "http_basic_auth_password": {
+ "type": "string",
+ "nullable": true,
+ "description": "Password for HTTP Basic Authentication"
}
},
"type": "object"
@@ -7729,6 +7813,14 @@
"type": "string",
"format": "private-key"
},
+ "public_key": {
+ "type": "string",
+ "description": "The public key of the private key."
+ },
+ "fingerprint": {
+ "type": "string",
+ "description": "The fingerprint of the private key."
+ },
"is_git_related": {
"type": "boolean"
},
diff --git a/openapi.yaml b/openapi.yaml
index 1fd42055b..c5113d9f7 100644
--- a/openapi.yaml
+++ b/openapi.yaml
@@ -248,6 +248,17 @@ paths:
type: boolean
nullable: true
description: 'Use build server.'
+ is_http_basic_auth_enabled:
+ type: boolean
+ description: 'HTTP Basic Authentication enabled.'
+ http_basic_auth_username:
+ type: string
+ nullable: true
+ description: 'Username for HTTP Basic Authentication'
+ http_basic_auth_password:
+ type: string
+ nullable: true
+ description: 'Password for HTTP Basic Authentication'
type: object
responses:
'201':
@@ -487,6 +498,17 @@ paths:
type: boolean
nullable: true
description: 'Use build server.'
+ is_http_basic_auth_enabled:
+ type: boolean
+ description: 'HTTP Basic Authentication enabled.'
+ http_basic_auth_username:
+ type: string
+ nullable: true
+ description: 'Username for HTTP Basic Authentication'
+ http_basic_auth_password:
+ type: string
+ nullable: true
+ description: 'Password for HTTP Basic Authentication'
type: object
responses:
'201':
@@ -726,6 +748,17 @@ paths:
type: boolean
nullable: true
description: 'Use build server.'
+ is_http_basic_auth_enabled:
+ type: boolean
+ description: 'HTTP Basic Authentication enabled.'
+ http_basic_auth_username:
+ type: string
+ nullable: true
+ description: 'Username for HTTP Basic Authentication'
+ http_basic_auth_password:
+ type: string
+ nullable: true
+ description: 'Password for HTTP Basic Authentication'
type: object
responses:
'201':
@@ -912,6 +945,17 @@ paths:
type: boolean
nullable: true
description: 'Use build server.'
+ is_http_basic_auth_enabled:
+ type: boolean
+ description: 'HTTP Basic Authentication enabled.'
+ http_basic_auth_username:
+ type: string
+ nullable: true
+ description: 'Username for HTTP Basic Authentication'
+ http_basic_auth_password:
+ type: string
+ nullable: true
+ description: 'Password for HTTP Basic Authentication'
type: object
responses:
'201':
@@ -1089,6 +1133,17 @@ paths:
type: boolean
nullable: true
description: 'Use build server.'
+ is_http_basic_auth_enabled:
+ type: boolean
+ description: 'HTTP Basic Authentication enabled.'
+ http_basic_auth_username:
+ type: string
+ nullable: true
+ description: 'Username for HTTP Basic Authentication'
+ http_basic_auth_password:
+ type: string
+ nullable: true
+ description: 'Password for HTTP Basic Authentication'
type: object
responses:
'201':
@@ -5042,6 +5097,17 @@ components:
type: string
nullable: true
description: 'Custom Nginx configuration base64 encoded.'
+ is_http_basic_auth_enabled:
+ type: boolean
+ description: 'HTTP Basic Authentication enabled.'
+ http_basic_auth_username:
+ type: string
+ nullable: true
+ description: 'Username for HTTP Basic Authentication'
+ http_basic_auth_password:
+ type: string
+ nullable: true
+ description: 'Password for HTTP Basic Authentication'
type: object
ApplicationDeploymentQueue:
description: 'Project model'
@@ -5159,6 +5225,12 @@ components:
private_key:
type: string
format: private-key
+ public_key:
+ type: string
+ description: 'The public key of the private key.'
+ fingerprint:
+ type: string
+ description: 'The fingerprint of the private key.'
is_git_related:
type: boolean
team_id:
diff --git a/resources/views/livewire/project/application/general.blade.php b/resources/views/livewire/project/application/general.blade.php
index f971c8a4f..d1b052603 100644
--- a/resources/views/livewire/project/application/general.blade.php
+++ b/resources/views/livewire/project/application/general.blade.php
@@ -181,7 +181,7 @@
@if ($application->build_pack === 'dockerimage')