Merge pull request #5673 from coollabsio/next

v4.0.0-beta.410
This commit is contained in:
Andras Bacsai
2025-04-23 13:46:50 +02:00
committed by GitHub
22 changed files with 428 additions and 94 deletions

View File

@@ -45,6 +45,7 @@ class ApplicationsController extends Controller
'private_key_id', 'private_key_id',
'value', 'value',
'real_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.'], 'docker_compose_domains' => ['type' => 'array', 'description' => 'The Docker Compose domains.'],
'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'], 'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'],
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'], '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.'], 'docker_compose_domains' => ['type' => 'array', 'description' => 'The Docker Compose domains.'],
'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'], 'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'],
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'], '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.'], 'docker_compose_domains' => ['type' => 'array', 'description' => 'The Docker Compose domains.'],
'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'], 'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'],
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'], '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']], '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.'], '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.'], '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']], '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.'], '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.'], '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) 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(); $teamId = getTeamIdFromToken();
if (is_null($teamId)) { if (is_null($teamId)) {
return invalidTokenResponse(); return invalidTokenResponse();
@@ -721,6 +736,8 @@ class ApplicationsController extends Controller
if ($return instanceof \Illuminate\Http\JsonResponse) { if ($return instanceof \Illuminate\Http\JsonResponse) {
return $return; 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(), [ $validator = customApiValidator($request->all(), [
'name' => 'string|max:255', 'name' => 'string|max:255',
'description' => 'string|nullable', 'description' => 'string|nullable',
@@ -729,6 +746,9 @@ class ApplicationsController extends Controller
'environment_uuid' => 'string|nullable', 'environment_uuid' => 'string|nullable',
'server_uuid' => 'string|required', 'server_uuid' => 'string|required',
'destination_uuid' => 'string', '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); $extraFields = array_diff(array_keys($request->all()), $allowedFields);
@@ -1758,25 +1778,19 @@ class ApplicationsController extends Controller
if (is_null($teamId)) { if (is_null($teamId)) {
return invalidTokenResponse(); return invalidTokenResponse();
} }
if ($request->collect()->count() == 0) {
return response()->json([
'message' => 'Invalid request.',
], 400);
}
$return = validateIncomingRequest($request); $return = validateIncomingRequest($request);
if ($return instanceof \Illuminate\Http\JsonResponse) { if ($return instanceof \Illuminate\Http\JsonResponse) {
return $return; return $return;
} }
$application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
$application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
if (! $application) { if (! $application) {
return response()->json([ return response()->json([
'message' => 'Application not found', 'message' => 'Application not found',
], 404); ], 404);
} }
$server = $application->destination->server; $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 = [ $validationRules = [
'name' => 'string|max:255', 'name' => 'string|max:255',
@@ -1789,6 +1803,9 @@ class ApplicationsController extends Controller
'docker_compose_custom_start_command' => 'string|nullable', 'docker_compose_custom_start_command' => 'string|nullable',
'docker_compose_custom_build_command' => 'string|nullable', 'docker_compose_custom_build_command' => 'string|nullable',
'custom_nginx_configuration' => '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); $validationRules = array_merge(sharedDataApplications(), $validationRules);
$validator = customApiValidator($request->all(), $validationRules); $validator = customApiValidator($request->all(), $validationRules);
@@ -1844,6 +1861,29 @@ class ApplicationsController extends Controller
'errors' => $errors, 'errors' => $errors,
], 422); ], 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; $domains = $request->domains;
$requestHasDomains = $request->has('domains'); $requestHasDomains = $request->has('domains');
if ($requestHasDomains && $server->isProxyShouldRun()) { if ($requestHasDomains && $server->isProxyShouldRun()) {
@@ -2562,10 +2602,6 @@ class ApplicationsController extends Controller
])->setStatusCode(201); ])->setStatusCode(201);
} }
} }
return response()->json([
'message' => 'Something went wrong.',
], 500);
} }
#[OA\Delete( #[OA\Delete(

View File

@@ -809,6 +809,6 @@ class ServersController extends Controller
} }
ValidateServer::dispatch($server); ValidateServer::dispatch($server);
return response()->json(['message' => 'Validation started.']); return response()->json(['message' => 'Validation started.'], 201);
} }
} }

View File

@@ -380,6 +380,9 @@ class ServicesController extends Controller
$service = new Service; $service = new Service;
$result = $this->upsert_service($request, $service, $teamId); $result = $this->upsert_service($request, $service, $teamId);
if ($result instanceof \Illuminate\Http\JsonResponse) {
return $result;
}
return response()->json(serializeApiResponse($result))->setStatusCode(201); return response()->json(serializeApiResponse($result))->setStatusCode(201);
} else { } else {
@@ -608,12 +611,14 @@ class ServicesController extends Controller
} }
$service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first(); $service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first();
if (! $service) { if (! $service) {
return response()->json(['message' => 'Service not found.'], 404); return response()->json(['message' => 'Service not found.'], 404);
} }
$result = $this->upsert_service($request, $service, $teamId); $result = $this->upsert_service($request, $service, $teamId);
if ($result instanceof \Illuminate\Http\JsonResponse) {
return $result;
}
return response()->json(serializeApiResponse($result))->setStatusCode(200); return response()->json(serializeApiResponse($result))->setStatusCode(200);
} }

View File

@@ -1377,6 +1377,17 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
private function check_git_if_build_needed() 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(); $this->generate_git_import_commands();
$local_branch = $this->branch; $local_branch = $this->branch;
if ($this->pull_request_id !== 0) { if ($this->pull_request_id !== 0) {
@@ -1712,25 +1723,6 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
$labels = $labels->filter(function ($value, $key) { $labels = $labels->filter(function ($value, $key) {
return ! Str::startsWith($value, 'coolify.'); 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->custom_labels = base64_encode($labels->implode("\n"));
$this->application->save(); $this->application->save();
} else { } else {

View File

@@ -92,6 +92,9 @@ class General extends Component
'application.settings.is_container_label_escape_enabled' => 'boolean|required', 'application.settings.is_container_label_escape_enabled' => 'boolean|required',
'application.settings.is_container_label_readonly_enabled' => 'boolean|required', 'application.settings.is_container_label_readonly_enabled' => 'boolean|required',
'application.settings.is_preserve_repository_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.watch_paths' => 'nullable',
'application.redirect' => 'string|required', 'application.redirect' => 'string|required',
]; ];
@@ -178,6 +181,9 @@ class General extends Component
if ($this->application->settings->isDirty('is_spa')) { if ($this->application->settings->isDirty('is_spa')) {
$this->generateNginxConfiguration($this->application->settings->is_spa ? 'spa' : 'static'); $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->application->settings->save();
$this->dispatch('success', 'Settings saved.'); $this->dispatch('success', 'Settings saved.');
$this->application->refresh(); $this->application->refresh();

View File

@@ -111,6 +111,7 @@ class Source extends Component
$this->application->update([ $this->application->update([
'source_id' => $sourceId, 'source_id' => $sourceId,
'source_type' => $sourceType, 'source_type' => $sourceType,
'repository_project_id' => null,
]); ]);
$this->application->refresh(); $this->application->refresh();
$this->getSources(); $this->getSources();

View File

@@ -236,15 +236,6 @@ class All extends Component
return 0; 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 // Check if any of these variables are used in Docker Compose
if ($this->resource->type() === 'service' || $this->resource->build_pack === 'dockercompose') { if ($this->resource->type() === 'service' || $this->resource->build_pack === 'dockercompose') {
foreach ($variablesToDelete as $envVar) { foreach ($variablesToDelete as $envVar) {

View File

@@ -178,13 +178,6 @@ class Show extends Component
public function delete() public function delete()
{ {
try { 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 // Check if the variable is used in Docker Compose
if ($this->type === 'service' || $this->type === 'application' && $this->env->resource()?->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); [$isUsed, $reason] = $this->isEnvironmentVariableUsedInDockerCompose($this->env->key, $this->env->resource()?->docker_compose);

View File

@@ -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.'], '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.'], '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.'], '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 $appends = ['server_status'];
protected $casts = ['custom_network_aliases' => 'array']; protected $casts = [
'custom_network_aliases' => 'array',
'http_basic_auth_password' => 'encrypted',
];
public function customNetworkAliases(): Attribute public function customNetworkAliases(): Attribute
{ {

View File

@@ -17,8 +17,8 @@ use phpseclib3\Crypt\PublicKeyLoader;
'name' => ['type' => 'string'], 'name' => ['type' => 'string'],
'description' => ['type' => 'string'], 'description' => ['type' => 'string'],
'private_key' => ['type' => 'string', 'format' => 'private-key'], 'private_key' => ['type' => 'string', 'format' => 'private-key'],
'public_key' => ['type' => 'string'], 'public_key' => ['type' => 'string', 'description' => 'The public key of the private key.'],
'fingerprint' => ['type' => 'string'], 'fingerprint' => ['type' => 'string', 'description' => 'The fingerprint of the private key.'],
'is_git_related' => ['type' => 'boolean'], 'is_git_related' => ['type' => 'boolean'],
'team_id' => ['type' => 'integer'], 'team_id' => ['type' => 'integer'],
'created_at' => ['type' => 'string'], 'created_at' => ['type' => 'string'],

View File

@@ -486,24 +486,14 @@ $schema://$host {
$base_path = config('constants.coolify.base_config_path'); $base_path = config('constants.coolify.base_config_path');
$proxyType = $this->proxyType(); $proxyType = $this->proxyType();
$proxy_path = "$base_path/proxy"; $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) { if ($proxyType === ProxyTypes::TRAEFIK->value) {
// Do nothing $proxy_path = '/';
} elseif ($proxyType === ProxyTypes::CADDY->value) { } 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) { } 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; return $proxy_path;
} }

View File

@@ -141,6 +141,6 @@ class ServiceDatabase extends BaseModel
str($this->databaseType())->contains('postgres') || str($this->databaseType())->contains('postgres') ||
str($this->databaseType())->contains('postgis') || str($this->databaseType())->contains('postgis') ||
str($this->databaseType())->contains('mariadb') || str($this->databaseType())->contains('mariadb') ||
str($this->databaseType())->contains('mongodb'); str($this->databaseType())->contains('mongo');
} }
} }

View File

@@ -296,7 +296,8 @@ function generateServiceSpecificFqdns(ServiceApplication|Application $resource)
return $payload; 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([]); $labels = collect([]);
if ($serviceLabels) { if ($serviceLabels) {
@@ -304,6 +305,12 @@ function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains,
} else { } else {
$labels->push("caddy_ingress_network={$network}"); $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) { foreach ($domains as $loop => $domain) {
$url = Url::fromString($domain); $url = Url::fromString($domain);
$host = $url->getHost(); $host = $url->getHost();
@@ -340,20 +347,31 @@ function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains,
if ($redirect_direction === 'non-www' && str($host)->startsWith('www.')) { if ($redirect_direction === 'non-www' && str($host)->startsWith('www.')) {
$labels->push("caddy_{$loop}.redir={$schema}://{$host_without_www}{uri}"); $labels->push("caddy_{$loop}.redir={$schema}://{$host_without_www}{uri}");
} }
if (isDev()) { if ($is_http_basic_auth_enabled) {
// $labels->push("caddy_{$loop}.tls=internal"); $labels->push("caddy_{$loop}.basicauth.{$http_basic_auth_username}=\"{$hashedPassword}\"");
} }
} }
return $labels->sort(); 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 = collect([]);
$labels->push('traefik.enable=true'); $labels->push('traefik.enable=true');
$labels->push('traefik.http.middlewares.gzip.compress=true'); $labels->push('traefik.http.middlewares.gzip.compress=true');
$labels->push('traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https'); $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([]); $middlewares_from_labels = collect([]);
if ($serviceLabels) { if ($serviceLabels) {
@@ -439,6 +457,9 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
$labels = $labels->merge($redirect_to_www); $labels = $labels->merge($redirect_to_www);
$middlewares->push($to_www_name); $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_from_labels->each(function ($middleware_name) use ($middlewares) {
$middlewares->push($middleware_name); $middlewares->push($middleware_name);
}); });
@@ -462,6 +483,9 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
$labels = $labels->merge($redirect_to_www); $labels = $labels->merge($redirect_to_www);
$middlewares->push($to_www_name); $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_from_labels->each(function ($middleware_name) use ($middlewares) {
$middlewares->push($middleware_name); $middlewares->push($middleware_name);
}); });
@@ -511,6 +535,9 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
$labels = $labels->merge($redirect_to_www); $labels = $labels->merge($redirect_to_www);
$middlewares->push($to_www_name); $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_from_labels->each(function ($middleware_name) use ($middlewares) {
$middlewares->push($middleware_name); $middlewares->push($middleware_name);
}); });
@@ -534,6 +561,9 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
$labels = $labels->merge($redirect_to_www); $labels = $labels->merge($redirect_to_www);
$middlewares->push($to_www_name); $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_from_labels->each(function ($middleware_name) use ($middlewares) {
$middlewares->push($middleware_name); $middlewares->push($middleware_name);
}); });
@@ -577,7 +607,10 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
is_force_https_enabled: $application->isForceHttpsEnabled(), is_force_https_enabled: $application->isForceHttpsEnabled(),
is_gzip_enabled: $application->isGzipEnabled(), is_gzip_enabled: $application->isGzipEnabled(),
is_stripprefix_enabled: $application->isStripprefixEnabled(), 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; break;
case ProxyTypes::CADDY->value: case ProxyTypes::CADDY->value:
@@ -589,7 +622,10 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
is_force_https_enabled: $application->isForceHttpsEnabled(), is_force_https_enabled: $application->isForceHttpsEnabled(),
is_gzip_enabled: $application->isGzipEnabled(), is_gzip_enabled: $application->isGzipEnabled(),
is_stripprefix_enabled: $application->isStripprefixEnabled(), 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; break;
} }
@@ -601,7 +637,10 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
is_force_https_enabled: $application->isForceHttpsEnabled(), is_force_https_enabled: $application->isForceHttpsEnabled(),
is_gzip_enabled: $application->isGzipEnabled(), is_gzip_enabled: $application->isGzipEnabled(),
is_stripprefix_enabled: $application->isStripprefixEnabled(), 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( $labels = $labels->merge(fqdnLabelsForCaddy(
network: $application->destination->network, network: $application->destination->network,
@@ -611,7 +650,10 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
is_force_https_enabled: $application->isForceHttpsEnabled(), is_force_https_enabled: $application->isForceHttpsEnabled(),
is_gzip_enabled: $application->isGzipEnabled(), is_gzip_enabled: $application->isGzipEnabled(),
is_stripprefix_enabled: $application->isStripprefixEnabled(), 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, onlyPort: $onlyPort,
is_force_https_enabled: $application->isForceHttpsEnabled(), is_force_https_enabled: $application->isForceHttpsEnabled(),
is_gzip_enabled: $application->isGzipEnabled(), 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; break;
case ProxyTypes::CADDY->value: case ProxyTypes::CADDY->value:
@@ -642,7 +687,10 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
onlyPort: $onlyPort, onlyPort: $onlyPort,
is_force_https_enabled: $application->isForceHttpsEnabled(), is_force_https_enabled: $application->isForceHttpsEnabled(),
is_gzip_enabled: $application->isGzipEnabled(), 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; break;
} }
@@ -653,7 +701,10 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
onlyPort: $onlyPort, onlyPort: $onlyPort,
is_force_https_enabled: $application->isForceHttpsEnabled(), is_force_https_enabled: $application->isForceHttpsEnabled(),
is_gzip_enabled: $application->isGzipEnabled(), 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( $labels = $labels->merge(fqdnLabelsForCaddy(
network: $application->destination->network, network: $application->destination->network,
@@ -662,7 +713,10 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
onlyPort: $onlyPort, onlyPort: $onlyPort,
is_force_https_enabled: $application->isForceHttpsEnabled(), is_force_https_enabled: $application->isForceHttpsEnabled(),
is_gzip_enabled: $application->isGzipEnabled(), 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,9 +736,11 @@ function isDatabaseImage(?string $image = null)
$image = str($image)->append(':latest'); $image = str($image)->append(':latest');
} }
$imageName = $image->before(':'); $imageName = $image->before(':');
if (collect(DATABASE_DOCKER_IMAGES)->contains($imageName)) { foreach (DATABASE_DOCKER_IMAGES as $database_docker_image) {
if (str($imageName)->contains($database_docker_image)) {
return true; return true;
} }
}
return false; return false;
} }
@@ -715,6 +771,7 @@ function convertDockerRunToCompose(?string $custom_docker_run_options = null)
'--ip' => 'ip', '--ip' => 'ip',
'--shm-size' => 'shm_size', '--shm-size' => 'shm_size',
'--gpus' => 'gpus', '--gpus' => 'gpus',
'--hostname' => 'hostname',
]); ]);
foreach ($matches as $match) { foreach ($matches as $match) {
$option = $match[1]; $option = $match[1];
@@ -725,6 +782,16 @@ function convertDockerRunToCompose(?string $custom_docker_run_options = null)
$options[$option][] = $value; $options[$option][] = $value;
$options[$option] = array_unique($options[$option]); $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] !== '') { if (isset($match[2]) && $match[2] !== '') {
$value = $match[2]; $value = $match[2];
$options[$option][] = $value; $options[$option][] = $value;
@@ -761,8 +828,8 @@ function convertDockerRunToCompose(?string $custom_docker_run_options = null)
} }
}); });
$compose_options->put($mapping[$option], $ulimits); $compose_options->put($mapping[$option], $ulimits);
} elseif ($option === '--shm-size') { } elseif ($option === '--shm-size' || $option === '--hostname') {
if (! is_null($value) && is_array($value) && count($value) > 0) { if (! is_null($value) && is_array($value) && count($value) > 0 && ! empty(trim($value[0]))) {
$compose_options->put($mapping[$option], $value[0]); $compose_options->put($mapping[$option], $value[0]);
} }
} elseif ($option === '--gpus') { } elseif ($option === '--gpus') {
@@ -770,7 +837,7 @@ function convertDockerRunToCompose(?string $custom_docker_run_options = null)
'driver' => 'nvidia', 'driver' => 'nvidia',
'capabilities' => ['gpu'], '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]) != 'all') {
if (str($value[0])->contains(',')) { if (str($value[0])->contains(',')) {
$payload['device_ids'] = str($value[0])->explode(',')->toArray(); $payload['device_ids'] = str($value[0])->explode(',')->toArray();
@@ -800,7 +867,6 @@ function convertDockerRunToCompose(?string $custom_docker_run_options = null)
continue; continue;
} }
$compose_options->forget($option);
} }
} }

View File

@@ -3819,7 +3819,6 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
return $volume; return $volume;
}); });
ray($serviceLabels);
$payload = collect($service)->merge([ $payload = collect($service)->merge([
'container_name' => $containerName, 'container_name' => $containerName,
'restart' => $restart->value(), 'restart' => $restart->value(),

View File

@@ -0,0 +1,32 @@
<?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->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');
});
}
};

View File

@@ -339,6 +339,20 @@
"type": "boolean", "type": "boolean",
"nullable": true, "nullable": true,
"description": "Use build server." "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" "type": "object"
@@ -673,6 +687,20 @@
"type": "boolean", "type": "boolean",
"nullable": true, "nullable": true,
"description": "Use build server." "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" "type": "object"
@@ -1007,6 +1035,20 @@
"type": "boolean", "type": "boolean",
"nullable": true, "nullable": true,
"description": "Use build server." "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" "type": "object"
@@ -1270,6 +1312,20 @@
"type": "boolean", "type": "boolean",
"nullable": true, "nullable": true,
"description": "Use build server." "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" "type": "object"
@@ -1516,6 +1572,20 @@
"type": "boolean", "type": "boolean",
"nullable": true, "nullable": true,
"description": "Use build server." "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" "type": "object"
@@ -7553,6 +7623,20 @@
"type": "string", "type": "string",
"nullable": true, "nullable": true,
"description": "Custom Nginx configuration base64 encoded." "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" "type": "object"
@@ -7729,6 +7813,14 @@
"type": "string", "type": "string",
"format": "private-key" "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": { "is_git_related": {
"type": "boolean" "type": "boolean"
}, },

View File

@@ -248,6 +248,17 @@ paths:
type: boolean type: boolean
nullable: true nullable: true
description: 'Use build server.' 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 type: object
responses: responses:
'201': '201':
@@ -487,6 +498,17 @@ paths:
type: boolean type: boolean
nullable: true nullable: true
description: 'Use build server.' 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 type: object
responses: responses:
'201': '201':
@@ -726,6 +748,17 @@ paths:
type: boolean type: boolean
nullable: true nullable: true
description: 'Use build server.' 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 type: object
responses: responses:
'201': '201':
@@ -912,6 +945,17 @@ paths:
type: boolean type: boolean
nullable: true nullable: true
description: 'Use build server.' 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 type: object
responses: responses:
'201': '201':
@@ -1089,6 +1133,17 @@ paths:
type: boolean type: boolean
nullable: true nullable: true
description: 'Use build server.' 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 type: object
responses: responses:
'201': '201':
@@ -5042,6 +5097,17 @@ components:
type: string type: string
nullable: true nullable: true
description: 'Custom Nginx configuration base64 encoded.' 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 type: object
ApplicationDeploymentQueue: ApplicationDeploymentQueue:
description: 'Project model' description: 'Project model'
@@ -5159,6 +5225,12 @@ components:
private_key: private_key:
type: string type: string
format: private-key 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: is_git_related:
type: boolean type: boolean
team_id: team_id:

View File

@@ -181,7 +181,7 @@
@if ($application->build_pack === 'dockerimage') @if ($application->build_pack === 'dockerimage')
<x-forms.input <x-forms.input
helper="You can add custom docker run options that will be used when your container is started.<br>Note: Not all options are supported, as they could mess up Coolify's automation and could cause bad experience for users.<br><br>Check the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>docs.</a>" helper="You can add custom docker run options that will be used when your container is started.<br>Note: Not all options are supported, as they could mess up Coolify's automation and could cause bad experience for users.<br><br>Check the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>docs.</a>"
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse --security-opt apparmor:unconfined --ulimit nofile=1024:1024 --tmpfs /run:rw,noexec,nosuid,size=65536k" placeholder="--cap-add SYS_ADMIN --device=/dev/fuse --security-opt apparmor:unconfined --ulimit nofile=1024:1024 --tmpfs /run:rw,noexec,nosuid,size=65536k --hostname=myapp"
id="application.custom_docker_run_options" label="Custom Docker Options" /> id="application.custom_docker_run_options" label="Custom Docker Options" />
@else @else
@if ($application->could_set_build_commands()) @if ($application->could_set_build_commands())
@@ -274,7 +274,7 @@
@endif @endif
<x-forms.input <x-forms.input
helper="You can add custom docker run options that will be used when your container is started.<br>Note: Not all options are supported, as they could mess up Coolify's automation and could cause bad experience for users.<br><br>Check the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>docs.</a>" helper="You can add custom docker run options that will be used when your container is started.<br>Note: Not all options are supported, as they could mess up Coolify's automation and could cause bad experience for users.<br><br>Check the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>docs.</a>"
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse --security-opt apparmor:unconfined --ulimit nofile=1024:1024 --tmpfs /run:rw,noexec,nosuid,size=65536k" placeholder="--cap-add SYS_ADMIN --device=/dev/fuse --security-opt apparmor:unconfined --ulimit nofile=1024:1024 --tmpfs /run:rw,noexec,nosuid,size=65536k --hostname=myapp"
id="application.custom_docker_run_options" label="Custom Docker Options" /> id="application.custom_docker_run_options" label="Custom Docker Options" />
@if ($application->build_pack !== 'dockercompose') @if ($application->build_pack !== 'dockercompose')
@@ -349,6 +349,21 @@
@endif @endif
</div> </div>
<h3 class="pt-8">HTTP Basic Authentication</h3>
<div>
<div class="w-96">
<x-forms.checkbox helper="This will add the proper proxy labels to the container." instantSave
label="Enable" id="application.is_http_basic_auth_enabled" />
</div>
@if ($application->is_http_basic_auth_enabled)
<div class="flex gap-2 py-2">
<x-forms.input id="application.http_basic_auth_username" label="Username" required />
<x-forms.input id="application.http_basic_auth_password" type="password" label="Password"
required />
</div>
@endif
</div>
@if ($application->settings->is_container_label_readonly_enabled) @if ($application->settings->is_container_label_readonly_enabled)
<x-forms.textarea readonly disabled label="Container Labels" rows="15" id="customLabels" <x-forms.textarea readonly disabled label="Container Labels" rows="15" id="customLabels"
monacoEditorLanguage="ini" useMonacoEditor></x-forms.textarea> monacoEditorLanguage="ini" useMonacoEditor></x-forms.textarea>

View File

@@ -81,7 +81,7 @@
</x-modal-confirmation> </x-modal-confirmation>
</div> </div>
@empty @empty
<div class="text-center">No sources found</div> <div>No other sources found</div>
@endforelse @endforelse
</div> </div>
</div> </div>

View File

@@ -20,7 +20,12 @@
</div> </div>
<div class="w-48 pb-2"> <div class="w-48 pb-2">
<x-forms.checkbox instantSave label="Backup Enabled" id="backupEnabled" /> <x-forms.checkbox instantSave label="Backup Enabled" id="backupEnabled" />
@if ($s3s->count() > 0)
<x-forms.checkbox instantSave label="S3 Enabled" id="saveS3" /> <x-forms.checkbox instantSave label="S3 Enabled" id="saveS3" />
@else
<x-forms.checkbox instantSave helper="No validated S3 storage available." label="S3 Enabled" id="saveS3"
disabled />
@endif
</div> </div>
@if ($backup->save_s3) @if ($backup->save_s3)
<div class="pb-6"> <div class="pb-6">

View File

@@ -16,8 +16,13 @@ use App\Models\Server;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
Route::get('/health', [OtherController::class, 'healthcheck']); Route::get('/health', [OtherController::class, 'healthcheck']);
Route::post('/feedback', [OtherController::class, 'feedback']); Route::group([
'prefix' => 'v1',
], function () {
Route::get('/health', [OtherController::class, 'healthcheck']);
});
Route::post('/feedback', [OtherController::class, 'feedback']);
Route::group([ Route::group([
'middleware' => ['auth:sanctum', 'api.ability:write'], 'middleware' => ['auth:sanctum', 'api.ability:write'],
'prefix' => 'v1', 'prefix' => 'v1',
@@ -88,7 +93,6 @@ Route::group([
Route::patch('/applications/{uuid}/envs/bulk', [ApplicationsController::class, 'create_bulk_envs'])->middleware(['api.ability:write']); Route::patch('/applications/{uuid}/envs/bulk', [ApplicationsController::class, 'create_bulk_envs'])->middleware(['api.ability:write']);
Route::patch('/applications/{uuid}/envs', [ApplicationsController::class, 'update_env_by_uuid'])->middleware(['api.ability:write']); Route::patch('/applications/{uuid}/envs', [ApplicationsController::class, 'update_env_by_uuid'])->middleware(['api.ability:write']);
Route::delete('/applications/{uuid}/envs/{env_uuid}', [ApplicationsController::class, 'delete_env_by_uuid'])->middleware(['api.ability:write']); Route::delete('/applications/{uuid}/envs/{env_uuid}', [ApplicationsController::class, 'delete_env_by_uuid'])->middleware(['api.ability:write']);
// Route::post('/applications/{uuid}/execute', [ApplicationsController::class, 'execute_command_by_uuid'])->middleware(['ability:write']);
Route::get('/applications/{uuid}/logs', [ApplicationsController::class, 'logs_by_uuid'])->middleware(['api.ability:read']); Route::get('/applications/{uuid}/logs', [ApplicationsController::class, 'logs_by_uuid'])->middleware(['api.ability:read']);
Route::match(['get', 'post'], '/applications/{uuid}/start', [ApplicationsController::class, 'action_deploy'])->middleware(['api.ability:write']); Route::match(['get', 'post'], '/applications/{uuid}/start', [ApplicationsController::class, 'action_deploy'])->middleware(['api.ability:write']);
@@ -117,7 +121,7 @@ Route::group([
Route::post('/services', [ServicesController::class, 'create_service'])->middleware(['api.ability:write']); Route::post('/services', [ServicesController::class, 'create_service'])->middleware(['api.ability:write']);
Route::get('/services/{uuid}', [ServicesController::class, 'service_by_uuid'])->middleware(['api.ability:read']); Route::get('/services/{uuid}', [ServicesController::class, 'service_by_uuid'])->middleware(['api.ability:read']);
Route::patch('/services/{uuid}', [ServicesController::class, 'update_by_uuid'])->middleware(['ability:write']); Route::patch('/services/{uuid}', [ServicesController::class, 'update_by_uuid'])->middleware(['api.ability:write']);
Route::delete('/services/{uuid}', [ServicesController::class, 'delete_by_uuid'])->middleware(['api.ability:write']); Route::delete('/services/{uuid}', [ServicesController::class, 'delete_by_uuid'])->middleware(['api.ability:write']);
Route::get('/services/{uuid}/envs', [ServicesController::class, 'envs'])->middleware(['api.ability:read']); Route::get('/services/{uuid}/envs', [ServicesController::class, 'envs'])->middleware(['api.ability:read']);

View File

@@ -1,5 +1,34 @@
<?php <?php
test('Hostname', function () {
$input = '--hostname=test';
$output = convertDockerRunToCompose($input);
expect($output)->toBe([
'hostname' => 'test',
]);
});
test('HostnameWithoutEqualSign', function () {
$input = '--hostname test';
$output = convertDockerRunToCompose($input);
expect($output)->toBe([
'hostname' => 'test',
]);
});
test('HostnameWithoutEqualSignAndHyphens', function () {
$input = '--hostname my-super-host';
$output = convertDockerRunToCompose($input);
expect($output)->toBe([
'hostname' => 'my-super-host',
]);
});
test('HostnameWithHyphens', function () {
$input = '--hostname=my-super-host';
$output = convertDockerRunToCompose($input);
expect($output)->toBe([
'hostname' => 'my-super-host',
]);
});
test('ConvertCapAdd', function () { test('ConvertCapAdd', function () {
$input = '--cap-add=NET_ADMIN --cap-add=NET_RAW --cap-add SYS_ADMIN'; $input = '--cap-add=NET_ADMIN --cap-add=NET_RAW --cap-add SYS_ADMIN';
$output = convertDockerRunToCompose($input); $output = convertDockerRunToCompose($input);