-
-
Notifications
You must be signed in to change notification settings - Fork 3.3k
feat: Add support for coolify.json configuration import #7489
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: next
Are you sure you want to change the base?
Conversation
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. WalkthroughThis pull request adds comprehensive support for coolify.json configuration files, enabling applications to import deployment settings directly from repositories. The implementation includes a new ✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This pull request adds comprehensive support for coolify.json configuration files, enabling users to define and import application configurations in a declarative way. The feature supports both repository-based detection (when coolify.json exists in the repo) and manual import via copy-paste.
Key Changes:
- Adds
loadConfigFromGit()helper function to detect and parse coolify.json from repositories - Implements
Application::setConfig()andApplication::generateRepositoryConfig()methods for configuration import/export - Creates new UI flow for manual coolify.json import with live preview and validation
- Extends API endpoints with
use_coolify_jsonparameter (default: true) for automated configuration detection
Reviewed changes
Copilot reviewed 29 out of 29 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
tests/Unit/CoolifyJsonConfigTest.php |
Unit tests for magic variable resolution and schema validation logic |
public/schemas/coolify.schema.json |
Comprehensive JSON schema defining all supported configuration options |
bootstrap/helpers/shared.php |
Core helper function for loading and validating coolify.json from git repositories |
app/Models/Application.php |
Configuration import/export methods with field mapping and magic variable resolution |
app/Livewire/Project/New/CoolifyJsonImport.php |
New Livewire component for manual JSON import with validation |
app/Livewire/Project/New/*.php |
Updates to existing repository import flows to support coolify.json detection |
app/Livewire/Project/Application/General.php |
Adds download button for exporting current config as coolify.json |
app/Http/Controllers/Api/ApplicationsController.php |
API integration with use_coolify_json parameter for automated config detection |
resources/views/**/*.blade.php |
UI updates for checkboxes, import flow, and download functionality |
openapi.yaml / openapi.json |
API documentation updates for new parameter |
| if ($settings->connect_to_docker_network) { | ||
| $configSettings['connect_to_docker_network'] = true; |
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The condition for connect_to_docker_network is checking if it's truthy, but this is a boolean setting field, not a string field. The export logic should check if it differs from the default value (which is typically false), not just if it's truthy. This will cause the field to be omitted when it's explicitly set to true, which is the opposite of what's intended.
| if ($settings->connect_to_docker_network) { | |
| $configSettings['connect_to_docker_network'] = true; | |
| if ($settings->connect_to_docker_network !== false) { | |
| $configSettings['connect_to_docker_network'] = $settings->connect_to_docker_network; |
| }, | ||
| { | ||
| "key": "DATABASE_PASSWORD", | ||
| "value": "$SERVICE_PASSWORD_32" |
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The magic variable examples in the JSON schema example use $SERVICE_PASSWORD_32 format (with $ prefix), but the actual implementation in Application::resolveMagicEnvironmentVariable() expects SERVICE_PASSWORD_32 format (without $ prefix). Update this example to match the actual implementation.
| try { | ||
| $config = loadConfigFromGit( | ||
| $this->repository_url, | ||
| $this->branch, | ||
| $this->base_directory ?? '/', | ||
| $destination->server->id, | ||
| auth()->user()->currentTeam()->id | ||
| ); | ||
| if ($config) { | ||
| $application->setConfig($config, fromRepository: true); | ||
| session()->flash('success', 'coolify.json configuration detected and applied.'); | ||
| } | ||
| } catch (\Exception $e) { | ||
| \Log::warning('coolify.json: Failed to apply config - '.$e->getMessage()); | ||
| session()->flash('warning', 'coolify.json found but failed to apply: '.$e->getMessage()); | ||
| } |
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The checkCoolifyConfig checkbox is missing from the GithubPrivateRepositoryDeployKey component's properties. The code attempts to check for coolify.json configuration (lines 205-220), but there's no checkbox in the UI to control this behavior, and no property defined for it. This creates inconsistency with the other repository import flows which include this checkbox.
| try { | ||
| $config = loadConfigFromGit( | ||
| $this->repository_url, | ||
| $this->branch, | ||
| $this->base_directory ?? '/', | ||
| $destination->server->id, | ||
| auth()->user()->currentTeam()->id | ||
| ); | ||
| if ($config) { | ||
| $application->setConfig($config, fromRepository: true); | ||
| session()->flash('success', 'coolify.json configuration detected and applied.'); | ||
| } | ||
| } catch (\Exception $e) { | ||
| \Log::warning('coolify.json: Failed to apply config - '.$e->getMessage()); | ||
| session()->flash('warning', 'coolify.json found but failed to apply: '.$e->getMessage()); |
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar to the GithubPrivateRepository component, this code is missing the conditional check for $this->checkCoolifyConfig before attempting to load and apply the configuration. The code should check if ($this->checkCoolifyConfig) before entering the try-catch block.
| try { | |
| $config = loadConfigFromGit( | |
| $this->repository_url, | |
| $this->branch, | |
| $this->base_directory ?? '/', | |
| $destination->server->id, | |
| auth()->user()->currentTeam()->id | |
| ); | |
| if ($config) { | |
| $application->setConfig($config, fromRepository: true); | |
| session()->flash('success', 'coolify.json configuration detected and applied.'); | |
| } | |
| } catch (\Exception $e) { | |
| \Log::warning('coolify.json: Failed to apply config - '.$e->getMessage()); | |
| session()->flash('warning', 'coolify.json found but failed to apply: '.$e->getMessage()); | |
| if ($this->checkCoolifyConfig) { | |
| try { | |
| $config = loadConfigFromGit( | |
| $this->repository_url, | |
| $this->branch, | |
| $this->base_directory ?? '/', | |
| $destination->server->id, | |
| auth()->user()->currentTeam()->id | |
| ); | |
| if ($config) { | |
| $application->setConfig($config, fromRepository: true); | |
| session()->flash('success', 'coolify.json configuration detected and applied.'); | |
| } | |
| } catch (\Exception $e) { | |
| \Log::warning('coolify.json: Failed to apply config - '.$e->getMessage()); | |
| session()->flash('warning', 'coolify.json found but failed to apply: '.$e->getMessage()); | |
| } |
| 'enabled' => data_get($task, 'enabled', true), | ||
| 'timeout' => data_get($task, 'timeout', 300), | ||
| 'application_id' => $this->id, | ||
| 'team_id' => $this->team()->id, |
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The team_id is being accessed incorrectly. $this->team() returns a relationship method, not the related model. This should be $this->team_id to access the foreign key attribute directly, or $this->team()->first()->id to access through the relationship. This will cause a fatal error when creating scheduled tasks from coolify.json.
| 'team_id' => $this->team()->id, | |
| 'team_id' => $this->team_id, |
| "description": "Environment variable value. Supports magic variables like $SERVICE_PASSWORD_32, $SERVICE_FQDN_APP, $SERVICE_URL_API", | ||
| "examples": ["production", "$SERVICE_PASSWORD_32", "$SERVICE_FQDN_API"] |
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The magic variable examples in the JSON schema use $SERVICE_PASSWORD_32 format (with $ prefix), but the actual implementation expects SERVICE_PASSWORD_32 format (without $ prefix). This inconsistency will confuse users following the schema documentation. Either update the examples to remove the $ prefix, or update the implementation to handle both formats.
| try { | ||
| $gitRepository = 'https://github.com/'.$this->selected_repository_owner.'/'.$this->selected_repository_repo.'.git'; | ||
| $config = loadConfigFromGit( | ||
| $gitRepository, | ||
| $this->selected_branch_name, | ||
| $this->base_directory ?? '/', | ||
| $destination->server->id, | ||
| auth()->user()->currentTeam()->id | ||
| ); | ||
| if ($config) { | ||
| $application->setConfig($config, fromRepository: true); | ||
| session()->flash('success', 'coolify.json configuration detected and applied.'); | ||
| } | ||
| } catch (\Exception $e) { | ||
| \Log::warning('coolify.json: Failed to apply config - '.$e->getMessage()); | ||
| session()->flash('warning', 'coolify.json found but failed to apply: '.$e->getMessage()); |
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The condition checks if $this->checkCoolifyConfig is true before attempting to load and apply the configuration, but there's no conditional check here. This means the coolify.json configuration will always be attempted, regardless of user preference. The code should check if ($this->checkCoolifyConfig) before entering the try-catch block, consistent with the PublicGitRepository implementation.
| try { | |
| $gitRepository = 'https://github.com/'.$this->selected_repository_owner.'/'.$this->selected_repository_repo.'.git'; | |
| $config = loadConfigFromGit( | |
| $gitRepository, | |
| $this->selected_branch_name, | |
| $this->base_directory ?? '/', | |
| $destination->server->id, | |
| auth()->user()->currentTeam()->id | |
| ); | |
| if ($config) { | |
| $application->setConfig($config, fromRepository: true); | |
| session()->flash('success', 'coolify.json configuration detected and applied.'); | |
| } | |
| } catch (\Exception $e) { | |
| \Log::warning('coolify.json: Failed to apply config - '.$e->getMessage()); | |
| session()->flash('warning', 'coolify.json found but failed to apply: '.$e->getMessage()); | |
| if ($this->checkCoolifyConfig) { | |
| try { | |
| $gitRepository = 'https://github.com/'.$this->selected_repository_owner.'/'.$this->selected_repository_repo.'.git'; | |
| $config = loadConfigFromGit( | |
| $gitRepository, | |
| $this->selected_branch_name, | |
| $this->base_directory ?? '/', | |
| $destination->server->id, | |
| auth()->user()->currentTeam()->id | |
| ); | |
| if ($config) { | |
| $application->setConfig($config, fromRepository: true); | |
| session()->flash('success', 'coolify.json configuration detected and applied.'); | |
| } | |
| } catch (\Exception $e) { | |
| \Log::warning('coolify.json: Failed to apply config - '.$e->getMessage()); | |
| session()->flash('warning', 'coolify.json found but failed to apply: '.$e->getMessage()); | |
| } |
| 'another_unknown' => 'value', | ||
| ]; | ||
|
|
||
| $knownFields = ['version', 'name', 'description', 'build', 'domains', 'environment_variables', 'health_check', 'limits', 'settings']; |
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The knownFields array in the test is missing the same fields as in the actual implementation. Update this to include all valid top-level fields: source, network_aliases, http_basic_auth, deployment_commands, preview, swarm, docker_registry, persistent_storages, file_mounts, directory_mounts, and scheduled_tasks.
| $knownFields = ['version', 'name', 'description', 'build', 'domains', 'environment_variables', 'health_check', 'limits', 'settings']; | |
| $knownFields = [ | |
| 'version', | |
| 'name', | |
| 'description', | |
| 'build', | |
| 'domains', | |
| 'environment_variables', | |
| 'health_check', | |
| 'limits', | |
| 'settings', | |
| 'source', | |
| 'network_aliases', | |
| 'http_basic_auth', | |
| 'deployment_commands', | |
| 'preview', | |
| 'swarm', | |
| 'docker_registry', | |
| 'persistent_storages', | |
| 'file_mounts', | |
| 'directory_mounts', | |
| 'scheduled_tasks', | |
| ]; |
|
|
||
| $uuid = new Cuid2; | ||
| $cloneCommand = "git clone --no-checkout -b $branch $repository ."; | ||
| $cloneCommand = "git clone --no-checkout -b {$branch} {$repository} ."; |
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Untrusted inputs {$branch} and {$repository} are concatenated into a shell command (git clone --no-checkout -b {$branch} {$repository} .) without sanitization, enabling command injection if an attacker supplies crafted values (e.g., branch="main; touch /tmp/pwn"). This function is invoked with user-provided repository/branch via UI/API flows, making the injection reachable on remote servers. Fix by strictly validating allowed characters for branch (e.g., ^[A-Za-z0-9._/-]+$) and repository (require a safe URL), and by executing commands without the shell (pass arguments to the process API or escape each arg with escapeshellarg).
| $cloneCommand, | ||
| 'git sparse-checkout init --cone', | ||
| "git sparse-checkout set {$fileList->implode(' ')}", | ||
| "git sparse-checkout set {$fileList}", |
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
$fileList is built from base_directory and interpolated into git sparse-checkout set {$fileList} without escaping, allowing injection via crafted base_directory (e.g., ../../; rm -rf /) to run arbitrary commands or alter checkout behavior. Since base_directory is user-controlled through creation forms/API, this is exploitable. Fix by validating base_directory against a strict pattern (no whitespace or shell metacharacters, disallow ..) and by avoiding shell concatenation—pass path args as an array to the process runner or escape with escapeshellarg per item.
…lidation - Introduced a new schema for coolify.json to validate application configurations. - Implemented loading and parsing logic for coolify.json in the application. - Added UI components for importing coolify.json configurations in various project creation flows. - Enhanced logging for coolify.json processing to aid in debugging. - Created unit tests to validate coolify.json parsing and magic variable resolution. - Updated existing forms to include options for importing coolify.json settings.
…r removals in CleanupHelperContainersJob
afced58 to
4c1c740
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 16
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
app/Livewire/Project/Application/Configuration.php (2)
8-9: Hasta la vista, authorization! (But seriously, add it back.)I'll be back... to remind you that this Livewire component is missing the
AuthorizesRequeststrait. Your coding guidelines are crystal clear: every Livewire component needs this trait to check permissions. Right now, this component is more open than a serverless function's billing surprise (and that's never good for self-hosted infrastructure you actually control).As per coding guidelines, add the trait and implement authorization checks.
Apply this diff to add the missing trait:
namespace App\Livewire\Project\Application; use App\Models\Application; +use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Livewire\Component; class Configuration extends Component { + use AuthorizesRequests; + public $currentRoute;
32-56: "I need your authorization check if you want to live."Your mount() method is loading sensitive data (projects, environments, applications) without any authorization checks. That's like leaving your server door unlocked while serverless vendors circle like vultures. The coding guidelines demand
$this->authorize()calls in mount() methods - and for good reason!This is self-hosted infrastructure we're protecting here, not some "infinitely scalable" marketing fantasy. Add proper authorization to ensure users can only access their own resources.
As per coding guidelines, implement authorization checks in mount().
Consider adding authorization after loading the application:
public function mount() { $this->currentRoute = request()->route()->getName(); $project = currentTeam() ->projects() ->select('id', 'uuid', 'team_id') ->where('uuid', request()->route('project_uuid')) ->firstOrFail(); $environment = $project->environments() ->select('id', 'uuid', 'name', 'project_id') ->where('uuid', request()->route('environment_uuid')) ->firstOrFail(); $application = $environment->applications() ->with(['destination']) ->where('uuid', request()->route('application_uuid')) ->firstOrFail(); // Add authorization check $this->authorize('view', $application); $this->project = $project; $this->environment = $environment; $this->application = $application; // ... rest of the method }openapi.json (1)
2253-2589: Should PATCH expose use_coolify_json?Create endpoints accept use_coolify_json, but PATCH doesn’t. If users can toggle import behavior post‑create, add it here for parity; if not, please confirm it’s intentionally create‑only.
If supported, append:
"use_coolify_json": { "type": "boolean", "description": "Check repository for coolify.json and apply configuration if found.", "default": true }I’ll be back… with fewer surprises and more glorious self-hosted determinism.
♻️ Duplicate comments (8)
bootstrap/helpers/shared.php (1)
2913-2985: Harden loadConfigFromGit against shell injection and secret leakage, or Skynet gets rootRight now this helper is dangerously trusting user‑controlled input when building remote shell commands, and it logs parts of the config that may contain secrets:
Shell injection on git & sparse‑checkout commands (critical)
$branch,$repository, and$base_directoryflow into:
$cloneCommand = "git clone --no-checkout -b {$branch} {$repository} .";"git sparse-checkout set {$fileList}";$catCommands = ... "cat ./{$path} 2>/dev/null"- All of these are executed remotely as raw shell lines via
instant_remote_process(). A malicious branch, repo URL, or base directory (coming from UI/API) could smuggle; rm -rf /‑style payloads onto the server.- You already have
validateShellSafePath()and friends in this file; they’re begging to be used here.Suggested hardening (sketch):
- $uuid = new Cuid2;
- $cloneCommand = "git clone --no-checkout -b {$branch} {$repository} .";
- $workdir = rtrim($base_directory, '/');
- $uuid = new Cuid2;
- // Validate and escape untrusted inputs before dropping them into shell
- $safeBranch = validateShellSafePath($branch, 'branch name');
- $safeRepository = validateShellSafePath($repository, 'repository URL');
- $workdir = rtrim($base_directory, '/');
- $cloneCommand = sprintf(
'git clone --no-checkout -b %s %s . >/dev/null 2>&1',escapeshellarg($safeBranch),escapeshellarg($safeRepository),- );
@@
- $fileList = collect($pathsToCheck)->map(fn ($path) => "./{$path}")->implode(' ');
- $fileList = collect($pathsToCheck)
->map(function ($path) {validateShellSafePath($path, 'coolify.json path');return escapeshellarg('./'.$path);})->implode(' ');@@
- $catCommands = collect($pathsToCheck)->map(fn ($path) => "cat ./{$path} 2>/dev/null")->implode(' || ');
- $catCommands = collect($pathsToCheck)
->map(function ($path) {validateShellSafePath($path, 'coolify.json path');return 'cat '.escapeshellarg('./'.$path).' 2>/dev/null';})->implode(' || ');That keeps the self‑hosted metal safe from hostile repo input.
Leaking config contents into logs (major)
- On JSON parse failure you log:
Log::warning( 'coolify.json: Invalid JSON format - ' . json_last_error_msg() . ' - output was: ' . substr($output, 0, 200) );- That
$outputis literally the contents ofcoolify.json, which per design can contain env values, secrets, tokens, database URLs, etc. Logging them violates the “never log sensitive data” rule and is a compliance nightmare if disks are ever inspected.Safer pattern:
if (json_last_error() !== JSON_ERROR_NONE) {Log::warning('coolify.json: Invalid JSON format - '.json_last_error_msg().' - output was: '.substr($output, 0, 200));
if (json_last_error() !== JSON_ERROR_NONE) {Log::warning('coolify.json: Invalid JSON format',['error' => json_last_error_msg(), 'length' => strlen($output)]); return null; }
Ensure only JSON reaches json_decode (recommended)
git clone,git sparse-checkout, andgit read-treecan emit to stdout; that junk will be concatenated with the JSON and break decoding. You’re already redirecting stderr forcat; extend that idea and redirect stdout/stderr for the git commands to/dev/nullso the only output is the file contents from the final cat. (See>/dev/null 2>&1in the clone/sparse‑checkout suggestions above.)Unknown top‑level fields currently ignored (recommended)
- You compute:
$knownFields = ['version', 'name', 'description', 'build', 'domains', 'environment_variables', 'health_check', 'limits', 'settings']; $unknownFields = array_diff(array_keys($config), $knownFields);- But you never log or surface
$unknownFields. If the goal is to warn when someone sends unsupported keys, add a lightweight info log:if (! empty($unknownFields)) { Log::info('coolify.json: Unknown top-level fields', ['fields' => array_values($unknownFields)]); }- Also, the
knownFieldslist is still missing several schema‑level keys thatApplication::setConfig()understands (source,network_aliases,http_basic_auth,deployment_commands,preview,swarm,docker_registry,persistent_storages,file_mounts,directory_mounts,scheduled_tasks), so current configs using those will be falsely flagged as “unknown”. Consider expanding the list accordingly.Without these fixes, this helper is a juicy escape hatch for bad input on your very real, very self‑hosted servers. Let’s not make the VCs’ serverless propaganda right.
app/Livewire/Project/New/GithubPrivateRepositoryDeployKey.php (1)
52-53: Honor thecheckCoolifyConfigflag when importing coolify.jsonRight now the component always tries to load/apply
coolify.json, even if the (now-added)checkCoolifyConfigflag is false. The checkbox is just psychological at this point.To make the toggle actually do something and align with the public‑git flow, wrap the import logic:
- // Check for coolify.json configuration - try { - $config = loadConfigFromGit( - $this->repository_url, - $this->branch, - $this->base_directory ?? '/', - $destination->server->id, - auth()->user()->currentTeam()->id - ); - if ($config) { - $application->setConfig($config, fromRepository: true); - session()->flash('success', 'coolify.json configuration detected and applied.'); - } - } catch (\Exception $e) { - \Log::warning('coolify.json: Failed to apply config - '.$e->getMessage()); - session()->flash('warning', 'coolify.json found but failed to apply: '.$e->getMessage()); - } + if ($this->checkCoolifyConfig) { + try { + $config = loadConfigFromGit( + $this->repository_url, + $this->branch, + $this->base_directory ?? '/', + $destination->server->id, + auth()->user()->currentTeam()->id + ); + if ($config) { + $application->setConfig($config, fromRepository: true); + session()->flash('success', 'coolify.json configuration detected and applied.'); + } + } catch (\Exception $e) { + \Log::warning('coolify.json: Failed to apply config - '.$e->getMessage()); + session()->flash('warning', 'coolify.json found but failed to apply: '.$e->getMessage()); + } + }Let the humans choose whether config is imported; don’t hard‑code Skynet’s will.
Also applies to: 204-220
app/Livewire/Project/New/GithubPrivateRepository.php (1)
215-232: Hasta la vista, missing condition check.The
$checkCoolifyConfigproperty exists but is never evaluated. Like a T-1000 without its mission parameters, this code will always attempt to load coolify.json regardless of the flag value. This contradicts the user's ability to opt-out.Apply this diff to respect the user's preference:
// Check for coolify.json configuration + if ($this->checkCoolifyConfig) { try { $gitRepository = 'https://github.com/'.$this->selected_repository_owner.'/'.$this->selected_repository_repo.'.git'; $config = loadConfigFromGit( $gitRepository, $this->selected_branch_name, $this->base_directory ?? '/', $destination->server->id, auth()->user()->currentTeam()->id ); if ($config) { $application->setConfig($config, fromRepository: true); session()->flash('success', 'coolify.json configuration detected and applied.'); } } catch (\Exception $e) { \Log::warning('coolify.json: Failed to apply config - '.$e->getMessage()); session()->flash('warning', 'coolify.json found but failed to apply: '.$e->getMessage()); } + }tests/Unit/CoolifyJsonConfigTest.php (1)
127-140: The knownFields array is incomplete - must be updated.The
knownFieldsarray in this test is missing several valid top-level fields that the actual implementation inloadConfigFromGit(bootstrap/helpers/shared.php) andsetConfig(app/Models/Application.php) support.Update to include all valid fields:
- $knownFields = ['version', 'name', 'description', 'build', 'domains', 'environment_variables', 'health_check', 'limits', 'settings']; + $knownFields = [ + 'version', + 'name', + 'description', + 'build', + 'domains', + 'environment_variables', + 'health_check', + 'limits', + 'settings', + 'source', + 'network_aliases', + 'http_basic_auth', + 'deployment_commands', + 'preview', + 'swarm', + 'docker_registry', + 'persistent_storages', + 'file_mounts', + 'directory_mounts', + 'scheduled_tasks', + ];public/schemas/coolify.schema.json (2)
603-606: I'll be back... to fix these magic variable examples. 🤖The examples show
$SERVICE_PASSWORD_32and$SERVICE_FQDN_APIwith the$prefix, but the implementation inApplication::resolveMagicEnvironmentVariable()expects values without the$prefix (e.g.,SERVICE_PASSWORD_32). This inconsistency will terminate user expectations faster than I terminate my targets.Update the examples to match the implementation:
- "description": "Environment variable value. Supports magic variables like $SERVICE_PASSWORD_32, $SERVICE_FQDN_APP, $SERVICE_URL_API", - "examples": ["production", "$SERVICE_PASSWORD_32", "$SERVICE_FQDN_API"] + "description": "Environment variable value. Supports magic variables like SERVICE_PASSWORD_32, SERVICE_FQDN_APP, SERVICE_URL_API", + "examples": ["production", "SERVICE_PASSWORD_32", "SERVICE_FQDN_API"]
656-659: Hasta la vista, dollar sign.Same issue here in the example - the magic variable uses
$SERVICE_PASSWORD_32but should beSERVICE_PASSWORD_32to match the implementation.{ "key": "DATABASE_PASSWORD", - "value": "$SERVICE_PASSWORD_32" + "value": "SERVICE_PASSWORD_32" }app/Livewire/Project/New/CoolifyJsonImport.php (1)
140-141: Wrap setConfig in try-catch - configuration errors shouldn't terminate the user experience.The
setConfig()call can throw exceptions on invalid data or database errors. Without error handling, users get a generic error page instead of a helpful message.- // Apply the full configuration using setConfig - $application->setConfig($config); + // Apply the full configuration using setConfig + try { + $application->setConfig($config); + } catch (\Throwable $e) { + // Clean up the partially created application + $application->forceDelete(); + $this->dispatch('error', 'Failed to apply configuration: ' . $e->getMessage()); + return; + }app/Models/Application.php (1)
2404-2414: team_id access needs fixing - this will terminate scheduled task creation. 🤖The
$this->team()method (lines 789-792) returnsdata_get($this, 'environment.project.team'), which gives you the Team model, not a relationship builder. Calling->idon it works when the team exists, but the safer and more explicit approach is using the team's id directly. However, looking at the ScheduledTask model requirements, you need the team_id.Given that
team()could return null if relationships aren't loaded, consider:ScheduledTask::create([ 'name' => $name, 'command' => $command, 'frequency' => $frequency, 'container' => data_get($task, 'container'), 'enabled' => data_get($task, 'enabled', true), 'timeout' => data_get($task, 'timeout', 300), 'application_id' => $this->id, - 'team_id' => $this->team()->id, + 'team_id' => $this->environment?->project?->team_id, ]);Or load the relationship explicitly before accessing. The current code will fail if the relationship chain isn't loaded.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (29)
app/Http/Controllers/Api/ApplicationsController.php(8 hunks)app/Jobs/CleanupHelperContainersJob.php(0 hunks)app/Jobs/DatabaseBackupJob.php(0 hunks)app/Jobs/ValidateAndInstallServerJob.php(0 hunks)app/Livewire/GlobalSearch.php(3 hunks)app/Livewire/Project/Application/Configuration.php(1 hunks)app/Livewire/Project/Application/General.php(1 hunks)app/Livewire/Project/New/CoolifyJsonImport.php(1 hunks)app/Livewire/Project/New/GithubPrivateRepository.php(2 hunks)app/Livewire/Project/New/GithubPrivateRepositoryDeployKey.php(2 hunks)app/Livewire/Project/New/PublicGitRepository.php(1 hunks)app/Livewire/Project/New/Select.php(2 hunks)app/Models/Application.php(1 hunks)app/Notifications/Dto/PushoverMessage.php(0 hunks)bootstrap/helpers/api.php(2 hunks)bootstrap/helpers/shared.php(2 hunks)openapi.json(3 hunks)openapi.yaml(3 hunks)public/schemas/coolify.schema.json(1 hunks)resources/views/livewire/global-search.blade.php(1 hunks)resources/views/livewire/project/application/general.blade.php(1 hunks)resources/views/livewire/project/new/coolify-json-import.blade.php(1 hunks)resources/views/livewire/project/new/github-private-repository-deploy-key.blade.php(1 hunks)resources/views/livewire/project/new/github-private-repository.blade.php(1 hunks)resources/views/livewire/project/new/public-git-repository.blade.php(1 hunks)resources/views/livewire/project/new/select.blade.php(3 hunks)resources/views/livewire/project/resource/create.blade.php(1 hunks)resources/views/livewire/project/shared/upload-config.blade.php(1 hunks)tests/Unit/CoolifyJsonConfigTest.php(1 hunks)
💤 Files with no reviewable changes (4)
- app/Jobs/ValidateAndInstallServerJob.php
- app/Jobs/CleanupHelperContainersJob.php
- app/Notifications/Dto/PushoverMessage.php
- app/Jobs/DatabaseBackupJob.php
🧰 Additional context used
📓 Path-based instructions (11)
**/*.php
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.php: Use PHP 8.4 constructor property promotion and typed properties
Follow PSR-12 coding standards and run./vendor/bin/pintbefore committing
Use Eloquent ORM for database interactions, avoid raw queries
Use Laravel's built-in mocking and Mockery for testing external services and dependencies
Use database transactions for critical operations that modify multiple related records
Leverage query scopes in Eloquent models for reusable, chainable query logic
Never log or expose sensitive data (passwords, tokens, API keys, SSH keys) in logs or error messages
Always validate user input using Form Requests, Rules, or explicit validation methods
UsehandleError()helper for consistent error handling and logging
Use eager loading (with(), load()) to prevent N+1 queries when accessing related models
Use chunking for large data operations to avoid memory exhaustion
Implement caching for frequently accessed data using Laravel's cache helpers
Write descriptive variable and method names that clearly express intent
Keep methods small and focused on a single responsibility
Document complex logic with clear comments explaining the 'why' not just the 'what'Always run code formatting with
./vendor/bin/pintbefore committing code
Files:
app/Livewire/Project/Application/General.phpresources/views/livewire/project/new/github-private-repository-deploy-key.blade.phpresources/views/livewire/project/new/github-private-repository.blade.phpapp/Livewire/Project/New/GithubPrivateRepository.phpresources/views/livewire/global-search.blade.phpapp/Livewire/GlobalSearch.phpresources/views/livewire/project/application/general.blade.phpapp/Livewire/Project/New/PublicGitRepository.phpresources/views/livewire/project/new/coolify-json-import.blade.phpapp/Livewire/Project/Application/Configuration.phpresources/views/livewire/project/shared/upload-config.blade.phpapp/Livewire/Project/New/GithubPrivateRepositoryDeployKey.phpapp/Http/Controllers/Api/ApplicationsController.phpbootstrap/helpers/shared.phpresources/views/livewire/project/new/public-git-repository.blade.phpapp/Livewire/Project/New/CoolifyJsonImport.phptests/Unit/CoolifyJsonConfigTest.phpbootstrap/helpers/api.phpapp/Livewire/Project/New/Select.phpapp/Models/Application.phpresources/views/livewire/project/new/select.blade.phpresources/views/livewire/project/resource/create.blade.php
app/Livewire/**/*.php
📄 CodeRabbit inference engine (CLAUDE.md)
app/Livewire/**/*.php: Add theAuthorizesRequeststrait and check permissions in mount() and action methods using $this->authorize()
Handle UI and user interactions in Livewire components with state management on the server side
Dispatch events for component-to-component communication in Livewire
Files:
app/Livewire/Project/Application/General.phpapp/Livewire/Project/New/GithubPrivateRepository.phpapp/Livewire/GlobalSearch.phpapp/Livewire/Project/New/PublicGitRepository.phpapp/Livewire/Project/Application/Configuration.phpapp/Livewire/Project/New/GithubPrivateRepositoryDeployKey.phpapp/Livewire/Project/New/CoolifyJsonImport.phpapp/Livewire/Project/New/Select.php
resources/views/livewire/**/*.blade.php
📄 CodeRabbit inference engine (CLAUDE.md)
resources/views/livewire/**/*.blade.php: Livewire component views MUST have exactly ONE root element. ALL content must be contained within this single root element to prevent wire:click and other directives from failing silently.
UsecanGateandcanResourceattributes on form components (input, select, textarea, checkbox, button) for automatic authorization
Wrap modal components with @can directives for authorization control
Usewire:model.livefor real-time two-way data binding between Livewire component and view
Files:
resources/views/livewire/project/new/github-private-repository-deploy-key.blade.phpresources/views/livewire/project/new/github-private-repository.blade.phpresources/views/livewire/global-search.blade.phpresources/views/livewire/project/application/general.blade.phpresources/views/livewire/project/new/coolify-json-import.blade.phpresources/views/livewire/project/shared/upload-config.blade.phpresources/views/livewire/project/new/public-git-repository.blade.phpresources/views/livewire/project/new/select.blade.phpresources/views/livewire/project/resource/create.blade.php
resources/views/**/*.blade.php
📄 CodeRabbit inference engine (CLAUDE.md)
resources/views/**/*.blade.php: Use Tailwind CSS 4.1.4 utility-first styling with new utilities, avoiding deprecated ones
Usegaputilities for spacing instead of margins
Files:
resources/views/livewire/project/new/github-private-repository-deploy-key.blade.phpresources/views/livewire/project/new/github-private-repository.blade.phpresources/views/livewire/global-search.blade.phpresources/views/livewire/project/application/general.blade.phpresources/views/livewire/project/new/coolify-json-import.blade.phpresources/views/livewire/project/shared/upload-config.blade.phpresources/views/livewire/project/new/public-git-repository.blade.phpresources/views/livewire/project/new/select.blade.phpresources/views/livewire/project/resource/create.blade.php
**/**/livewire/**/*.blade.php
📄 CodeRabbit inference engine (.cursor/rules/coolify-ai-docs.mdc)
Livewire components MUST have exactly ONE root element with no exceptions
Files:
resources/views/livewire/project/new/github-private-repository-deploy-key.blade.phpresources/views/livewire/project/new/github-private-repository.blade.phpresources/views/livewire/global-search.blade.phpresources/views/livewire/project/application/general.blade.phpresources/views/livewire/project/new/coolify-json-import.blade.phpresources/views/livewire/project/shared/upload-config.blade.phpresources/views/livewire/project/new/public-git-repository.blade.phpresources/views/livewire/project/new/select.blade.phpresources/views/livewire/project/resource/create.blade.php
**/*.blade.php
📄 CodeRabbit inference engine (.cursor/rules/coolify-ai-docs.mdc)
**/*.blade.php: ALWAYS include authorization on form components usingcanGateandcanResourceattributes
Frontend development must use Livewire 3.5.20 for server-side state, Alpine.js for client interactions, and Tailwind CSS 4.1.4 for styling
Files:
resources/views/livewire/project/new/github-private-repository-deploy-key.blade.phpresources/views/livewire/project/new/github-private-repository.blade.phpresources/views/livewire/global-search.blade.phpresources/views/livewire/project/application/general.blade.phpresources/views/livewire/project/new/coolify-json-import.blade.phpresources/views/livewire/project/shared/upload-config.blade.phpresources/views/livewire/project/new/public-git-repository.blade.phpresources/views/livewire/project/new/select.blade.phpresources/views/livewire/project/resource/create.blade.php
bootstrap/helpers/**/*.php
📄 CodeRabbit inference engine (CLAUDE.md)
Place domain-specific helper functions in bootstrap/helpers/ directory organized by domain
Files:
bootstrap/helpers/shared.phpbootstrap/helpers/api.php
tests/Unit/**/*.php
📄 CodeRabbit inference engine (CLAUDE.md)
Unit tests MUST NOT use database. Use mocking for dependencies. Run with
./vendor/bin/pest tests/UnitRun Unit tests outside Docker using
./vendor/bin/pest tests/Unit- they should not require database
Files:
tests/Unit/CoolifyJsonConfigTest.php
tests/**/*.php
📄 CodeRabbit inference engine (CLAUDE.md)
tests/**/*.php: Use Pest for all tests with expressive syntax and clear test organization
Design tests to use mocking and dependency injection instead of database when possible; only use database in Feature tests when necessary
Mock external services and SSH connections in tests instead of making real connections
Files:
tests/Unit/CoolifyJsonConfigTest.php
app/Models/**/*.php
📄 CodeRabbit inference engine (CLAUDE.md)
app/Models/**/*.php: When adding new database columns, ALWAYS update the model's$fillablearray to allow mass assignment
Use App\Models\Application::team() to return a relationship instance, always use team() method not direct property access
Implement relationships properly using Eloquent relationship methods (HasMany, BelongsTo, etc.)
Apply indexes to performance-critical query columns in model relationships and migrations
Files:
app/Models/Application.php
{**/*Policy.php,**/*Gate.php,app/Models/**/*.php,routes/**/*.php}
📄 CodeRabbit inference engine (.cursor/rules/coolify-ai-docs.mdc)
Use team-based access control patterns and gate/policy authorization as documented in
.ai/patterns/security-patterns.md
Files:
app/Models/Application.php
🧠 Learnings (15)
📓 Common learnings
Learnt from: CR
Repo: coollabsio/coolify PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T09:32:36.504Z
Learning: Coolify is an open-source, self-hostable platform for deploying applications and managing servers built with Laravel (PHP) and Docker
Learnt from: CR
Repo: coollabsio/coolify PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T09:32:36.504Z
Learning: Coolify uses a server-side first approach with minimal JavaScript via Livewire for server-side rendering and Alpine.js for lightweight client-side interactions
📚 Learning: 2025-11-25T09:32:48.519Z
Learnt from: CR
Repo: coollabsio/coolify PR: 0
File: .cursor/rules/coolify-ai-docs.mdc:0-0
Timestamp: 2025-11-25T09:32:48.519Z
Learning: Applies to **/*.blade.php : Frontend development must use Livewire 3.5.20 for server-side state, Alpine.js for client interactions, and Tailwind CSS 4.1.4 for styling
Applied to files:
resources/views/livewire/project/new/coolify-json-import.blade.phpresources/views/livewire/project/shared/upload-config.blade.phpresources/views/livewire/project/new/select.blade.phpresources/views/livewire/project/resource/create.blade.php
📚 Learning: 2025-11-25T09:32:36.504Z
Learnt from: CR
Repo: coollabsio/coolify PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T09:32:36.504Z
Learning: Applies to app/Livewire/**/*.php : Dispatch events for component-to-component communication in Livewire
Applied to files:
app/Livewire/Project/Application/Configuration.php
📚 Learning: 2025-11-25T09:32:36.504Z
Learnt from: CR
Repo: coollabsio/coolify PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T09:32:36.504Z
Learning: Applies to app/Livewire/**/*.php : Add the `AuthorizesRequests` trait and check permissions in mount() and action methods using $this->authorize()
Applied to files:
app/Livewire/Project/Application/Configuration.php
📚 Learning: 2025-11-25T09:32:36.504Z
Learnt from: CR
Repo: coollabsio/coolify PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T09:32:36.504Z
Learning: Applies to app/Livewire/**/*.php : Handle UI and user interactions in Livewire components with state management on the server side
Applied to files:
app/Livewire/Project/Application/Configuration.phpresources/views/livewire/project/resource/create.blade.php
📚 Learning: 2025-11-25T09:32:36.504Z
Learnt from: CR
Repo: coollabsio/coolify PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T09:32:36.504Z
Learning: Coolify is an open-source, self-hostable platform for deploying applications and managing servers built with Laravel (PHP) and Docker
Applied to files:
app/Http/Controllers/Api/ApplicationsController.phpapp/Livewire/Project/New/CoolifyJsonImport.php
📚 Learning: 2025-11-25T09:32:36.504Z
Learnt from: CR
Repo: coollabsio/coolify PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T09:32:36.504Z
Learning: Applies to **/*.php : Implement caching for frequently accessed data using Laravel's cache helpers
Applied to files:
bootstrap/helpers/shared.php
📚 Learning: 2025-11-25T09:32:36.504Z
Learnt from: CR
Repo: coollabsio/coolify PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T09:32:36.504Z
Learning: Applies to **/*.php : Use Laravel's built-in mocking and Mockery for testing external services and dependencies
Applied to files:
bootstrap/helpers/shared.phptests/Unit/CoolifyJsonConfigTest.php
📚 Learning: 2025-11-25T09:32:36.504Z
Learnt from: CR
Repo: coollabsio/coolify PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T09:32:36.504Z
Learning: Applies to tests/Feature/**/*.php : Feature tests may use database. MUST be run inside Docker container with `docker exec coolify php artisan test`
Applied to files:
tests/Unit/CoolifyJsonConfigTest.php
📚 Learning: 2025-11-25T09:32:48.519Z
Learnt from: CR
Repo: coollabsio/coolify PR: 0
File: .cursor/rules/coolify-ai-docs.mdc:0-0
Timestamp: 2025-11-25T09:32:48.519Z
Learning: Applies to tests/Feature/**/*.php : Run Feature tests inside Docker using `docker exec coolify php artisan test` to prevent database connection errors; NEVER run them outside Docker
Applied to files:
tests/Unit/CoolifyJsonConfigTest.php
📚 Learning: 2025-11-25T09:32:36.504Z
Learnt from: CR
Repo: coollabsio/coolify PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T09:32:36.504Z
Learning: Applies to tests/**/*.php : Mock external services and SSH connections in tests instead of making real connections
Applied to files:
tests/Unit/CoolifyJsonConfigTest.php
📚 Learning: 2025-11-25T09:32:36.504Z
Learnt from: CR
Repo: coollabsio/coolify PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T09:32:36.504Z
Learning: Applies to app/Models/**/*.php : Use App\Models\Application::team() to return a relationship instance, always use team() method not direct property access
Applied to files:
app/Models/Application.php
📚 Learning: 2025-11-25T09:32:36.504Z
Learnt from: CR
Repo: coollabsio/coolify PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T09:32:36.504Z
Learning: Applies to app/Models/**/*.php : Implement relationships properly using Eloquent relationship methods (HasMany, BelongsTo, etc.)
Applied to files:
app/Models/Application.php
📚 Learning: 2025-11-25T09:32:36.504Z
Learnt from: CR
Repo: coollabsio/coolify PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T09:32:36.504Z
Learning: Coolify uses a server-side first approach with minimal JavaScript via Livewire for server-side rendering and Alpine.js for lightweight client-side interactions
Applied to files:
resources/views/livewire/project/new/select.blade.php
📚 Learning: 2025-11-25T09:32:36.504Z
Learnt from: CR
Repo: coollabsio/coolify PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T09:32:36.504Z
Learning: Applies to resources/views/livewire/**/*.blade.php : Livewire component views MUST have exactly ONE root element. ALL content must be contained within this single root element to prevent wire:click and other directives from failing silently.
Applied to files:
resources/views/livewire/project/resource/create.blade.php
🧬 Code graph analysis (8)
app/Livewire/Project/Application/General.php (1)
app/Models/Application.php (1)
generateRepositoryConfig(2556-2994)
app/Livewire/Project/New/GithubPrivateRepository.php (2)
bootstrap/helpers/shared.php (2)
loadConfigFromGit(2913-2986)currentTeam(163-166)app/Models/Application.php (2)
destination(910-913)setConfig(2039-2420)
app/Livewire/Project/New/PublicGitRepository.php (2)
bootstrap/helpers/shared.php (2)
loadConfigFromGit(2913-2986)currentTeam(163-166)app/Models/Application.php (2)
destination(910-913)setConfig(2039-2420)
app/Http/Controllers/Api/ApplicationsController.php (2)
bootstrap/helpers/shared.php (1)
loadConfigFromGit(2913-2986)app/Models/Application.php (1)
setConfig(2039-2420)
bootstrap/helpers/shared.php (3)
app/Models/Service.php (2)
server(1279-1282)workdir(1330-1333)app/Models/Application.php (1)
workdir(1025-1028)bootstrap/helpers/remoteProcess.php (1)
instant_remote_process(121-155)
app/Livewire/Project/New/CoolifyJsonImport.php (3)
bootstrap/helpers/shared.php (3)
get_route_parameters(226-229)isDev(376-379)generateUrl(446-461)app/Models/Application.php (4)
source(915-918)destination(910-913)environment(895-898)setConfig(2039-2420)app/Models/Project.php (1)
environments(65-68)
tests/Unit/CoolifyJsonConfigTest.php (2)
app/Models/EnvironmentVariable.php (1)
value(92-98)app/Models/Application.php (2)
baseDirectory(628-633)workdir(1025-1028)
app/Models/Application.php (6)
app/Models/EnvironmentVariable.php (2)
value(92-98)key(242-247)app/Models/LocalPersistentVolume.php (1)
LocalPersistentVolume(9-117)bootstrap/helpers/shared.php (2)
application_configuration_dir(63-66)generateEnvValue(896-992)app/Models/LocalFileVolume.php (1)
LocalFileVolume(10-268)app/Models/ScheduledTask.php (1)
ScheduledTask(9-62)app/Models/Environment.php (1)
environment_variables(57-60)
🪛 PHPMD (2.15.0)
app/Livewire/Project/New/GithubPrivateRepository.php
230-230: Avoid using static access to class '\Log' in method 'submit'. (undefined)
(StaticAccess)
app/Livewire/Project/New/PublicGitRepository.php
381-381: Avoid using static access to class '\Log' in method 'submit'. (undefined)
(StaticAccess)
app/Livewire/Project/New/GithubPrivateRepositoryDeployKey.php
218-218: Avoid using static access to class '\Log' in method 'submit'. (undefined)
(StaticAccess)
app/Http/Controllers/Api/ApplicationsController.php
1118-1118: Avoid using static access to class '\Log' in method 'create_application'. (undefined)
(StaticAccess)
1292-1292: Avoid using static access to class '\Log' in method 'create_application'. (undefined)
(StaticAccess)
1443-1443: Avoid using static access to class '\Log' in method 'create_application'. (undefined)
(StaticAccess)
app/Livewire/Project/New/CoolifyJsonImport.php
92-92: The variable $destination_uuid is not named in camelCase. (undefined)
(CamelCaseVariableName)
98-98: Missing class import via use statement (line '98', column '23'). (undefined)
(MissingImport)
100-100: The variable $destination_class is not named in camelCase. (undefined)
(CamelCaseVariableName)
117-132: Avoid using static access to class '\App\Models\Application' in method 'submit'. (undefined)
(StaticAccess)
app/Models/Application.php
2039-2039: The method setConfig has a boolean flag argument $fromRepository, which is a certain sign of a Single Responsibility Principle violation. (undefined)
(BooleanArgumentFlag)
2039-2420: Avoid assigning values to variables in if clauses and the like (line '2051', column '35'). (undefined)
(IfStatementAssignment)
2039-2420: Avoid assigning values to variables in if clauses and the like (line '2052', column '18'). (undefined)
(IfStatementAssignment)
2039-2420: Avoid assigning values to variables in if clauses and the like (line '2055', column '18'). (undefined)
(IfStatementAssignment)
2039-2420: Avoid assigning values to variables in if clauses and the like (line '2058', column '18'). (undefined)
(IfStatementAssignment)
2039-2420: Avoid assigning values to variables in if clauses and the like (line '2064', column '13'). (undefined)
(IfStatementAssignment)
2039-2420: Avoid assigning values to variables in if clauses and the like (line '2083', column '22'). (undefined)
(IfStatementAssignment)
2039-2420: Avoid assigning values to variables in if clauses and the like (line '2090', column '13'). (undefined)
(IfStatementAssignment)
2039-2420: Avoid assigning values to variables in if clauses and the like (line '2091', column '18'). (undefined)
(IfStatementAssignment)
2039-2420: Avoid assigning values to variables in if clauses and the like (line '2094', column '18'). (undefined)
(IfStatementAssignment)
2039-2420: Avoid assigning values to variables in if clauses and the like (line '2097', column '18'). (undefined)
(IfStatementAssignment)
2039-2420: Avoid assigning values to variables in if clauses and the like (line '2100', column '18'). (undefined)
(IfStatementAssignment)
2039-2420: Avoid assigning values to variables in if clauses and the like (line '2106', column '14'). (undefined)
(IfStatementAssignment)
2039-2420: Avoid assigning values to variables in if clauses and the like (line '2111', column '13'). (undefined)
(IfStatementAssignment)
2039-2420: Avoid assigning values to variables in if clauses and the like (line '2112', column '18'). (undefined)
(IfStatementAssignment)
2039-2420: Avoid assigning values to variables in if clauses and the like (line '2115', column '18'). (undefined)
(IfStatementAssignment)
2039-2420: Avoid assigning values to variables in if clauses and the like (line '2118', column '18'). (undefined)
(IfStatementAssignment)
2039-2420: Avoid assigning values to variables in if clauses and the like (line '2124', column '13'). (undefined)
(IfStatementAssignment)
2039-2420: Avoid assigning values to variables in if clauses and the like (line '2141', column '22'). (undefined)
(IfStatementAssignment)
2039-2420: Avoid assigning values to variables in if clauses and the like (line '2148', column '13'). (undefined)
(IfStatementAssignment)
2039-2420: Avoid assigning values to variables in if clauses and the like (line '2160', column '22'). (undefined)
(IfStatementAssignment)
2039-2420: Avoid assigning values to variables in if clauses and the like (line '2167', column '14'). (undefined)
(IfStatementAssignment)
2039-2420: Avoid assigning values to variables in if clauses and the like (line '2170', column '14'). (undefined)
(IfStatementAssignment)
2039-2420: Avoid assigning values to variables in if clauses and the like (line '2177', column '13'). (undefined)
(IfStatementAssignment)
2039-2420: Avoid assigning values to variables in if clauses and the like (line '2204', column '22'). (undefined)
(IfStatementAssignment)
2039-2420: Avoid assigning values to variables in if clauses and the like (line '2215', column '13'). (undefined)
(IfStatementAssignment)
2039-2420: Avoid assigning values to variables in if clauses and the like (line '2216', column '18'). (undefined)
(IfStatementAssignment)
2039-2420: Avoid assigning values to variables in if clauses and the like (line '2219', column '18'). (undefined)
(IfStatementAssignment)
2039-2420: Avoid assigning values to variables in if clauses and the like (line '2222', column '18'). (undefined)
(IfStatementAssignment)
2039-2420: Avoid assigning values to variables in if clauses and the like (line '2225', column '18'). (undefined)
(IfStatementAssignment)
2039-2420: Avoid assigning values to variables in if clauses and the like (line '2232', column '13'). (undefined)
(IfStatementAssignment)
2039-2420: Avoid assigning values to variables in if clauses and the like (line '2233', column '18'). (undefined)
(IfStatementAssignment)
2039-2420: Avoid assigning values to variables in if clauses and the like (line '2240', column '13'). (undefined)
(IfStatementAssignment)
2039-2420: Avoid assigning values to variables in if clauses and the like (line '2241', column '18'). (undefined)
(IfStatementAssignment)
2039-2420: Avoid assigning values to variables in if clauses and the like (line '2244', column '18'). (undefined)
(IfStatementAssignment)
2039-2420: Avoid assigning values to variables in if clauses and the like (line '2251', column '13'). (undefined)
(IfStatementAssignment)
2039-2420: Avoid assigning values to variables in if clauses and the like (line '2252', column '18'). (undefined)
(IfStatementAssignment)
2039-2420: Avoid assigning values to variables in if clauses and the like (line '2255', column '18'). (undefined)
(IfStatementAssignment)
2039-2420: Avoid assigning values to variables in if clauses and the like (line '2262', column '13'). (undefined)
(IfStatementAssignment)
2039-2420: Avoid assigning values to variables in if clauses and the like (line '2296', column '13'). (undefined)
(IfStatementAssignment)
2039-2420: Avoid assigning values to variables in if clauses and the like (line '2340', column '13'). (undefined)
(IfStatementAssignment)
2039-2420: Avoid assigning values to variables in if clauses and the like (line '2378', column '13'). (undefined)
(IfStatementAssignment)
2039-2420: The method setConfig() has a Cyclomatic Complexity of 68. The configured cyclomatic complexity threshold is 10. (undefined)
(CyclomaticComplexity)
2039-2420: The method setConfig() has an NPath complexity of 201354854400000. The configured NPath complexity threshold is 200. (undefined)
(NPathComplexity)
2039-2420: The method setConfig() has 382 lines of code. Current threshold is set to 100. Avoid really long methods. (undefined)
(ExcessiveMethodLength)
2045-2045: Missing class import via use statement (line '2045', column '27'). (undefined)
(MissingImport)
2282-2291: The method setConfig uses an else expression. Else clauses are basically not necessary and you can simplify the code by not using them. (undefined)
(ElseExpression)
2284-2290: Avoid using static access to class 'App\Models\LocalPersistentVolume' in method 'setConfig'. (undefined)
(StaticAccess)
2325-2335: The method setConfig uses an else expression. Else clauses are basically not necessary and you can simplify the code by not using them. (undefined)
(ElseExpression)
2327-2334: Avoid using static access to class 'App\Models\LocalFileVolume' in method 'setConfig'. (undefined)
(StaticAccess)
2364-2373: The method setConfig uses an else expression. Else clauses are basically not necessary and you can simplify the code by not using them. (undefined)
(ElseExpression)
2366-2372: Avoid using static access to class 'App\Models\LocalFileVolume' in method 'setConfig'. (undefined)
(StaticAccess)
2402-2414: The method setConfig uses an else expression. Else clauses are basically not necessary and you can simplify the code by not using them. (undefined)
(ElseExpression)
2404-2413: Avoid using static access to class 'App\Models\ScheduledTask' in method 'setConfig'. (undefined)
(StaticAccess)
2472-2486: The method createEnvironmentVariableFromConfig uses an else expression. Else clauses are basically not necessary and you can simplify the code by not using them. (undefined)
(ElseExpression)
2475-2485: Avoid using static access to class 'App\Models\EnvironmentVariable' in method 'createEnvironmentVariableFromConfig'. (undefined)
(StaticAccess)
2489-2554: The method resolveMagicEnvironmentVariable() has a Cyclomatic Complexity of 10. The configured cyclomatic complexity threshold is 10. (undefined)
(CyclomaticComplexity)
2489-2554: The method resolveMagicEnvironmentVariable() has an NPath complexity of 216. The configured NPath complexity threshold is 200. (undefined)
(NPathComplexity)
2519-2519: Avoid using static access to class '\Illuminate\Support\Str' in method 'resolveMagicEnvironmentVariable'. (undefined)
(StaticAccess)
2535-2535: Avoid using static access to class '\Illuminate\Support\Str' in method 'resolveMagicEnvironmentVariable'. (undefined)
(StaticAccess)
2549-2549: Avoid using static access to class '\Illuminate\Support\Str' in method 'resolveMagicEnvironmentVariable'. (undefined)
(StaticAccess)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build-push (aarch64, linux/aarch64, ubuntu-24.04-arm)
🔇 Additional comments (22)
app/Livewire/Project/Application/Configuration.php (1)
62-71: "Your flash messages have been dispatched. Have a nice day."This flash-to-event conversion is cleaner than a gluten-free taco shell! The logic correctly reads session flash messages and dispatches them as Livewire events for client-side handling. Laravel will automatically clear these flash messages after this request, so no memory leaks here - unlike those serverless bills that mysteriously keep growing.
This is solid server-side state management that would make any self-hosted platform proud. 💪
app/Livewire/Project/New/Select.php (1)
175-180:coolifyJsonImportoption wiring looks solidThe new “Import from coolify.json” option is shaped consistently with the other groups and is correctly exposed via the returned payload. As long as the consuming Blade/Livewire code listens for
id => 'coolify-json', this will plug into the flow cleanly — nice self‑hosted UX, no serverless fairy dust involved.Also applies to: 244-245
app/Livewire/Project/Application/General.php (1)
939-952: downloadRepositoryConfig export looks solidAuthorization is enforced, config generation is delegated to the model, and the streaming JSON download is straightforward and side‑effect free. No issues from this T‑800.
resources/views/livewire/global-search.blade.php (1)
129-139: Exact‑match shortcuts for coolify.json creation look goodThe new
'new coolify json' / 'new json' / 'new import'entries plug neatly into the existing exact‑match flow and should resolve to the newcoolify-jsoncreatable item as wired in the Livewire backend. No issues spotted by this chromium‑eyed reviewer.resources/views/livewire/project/new/github-private-repository.blade.php (1)
146-149: coolify.json checkbox wiring looks consistentID matches the Livewire property and follows the same component pattern as neighboring inputs. No issues from this cyborg’s scanners.
resources/views/livewire/project/resource/create.blade.php (1)
17-18: Newcoolify-jsonresource branch is wired correctlyType dispatch to
project.new.coolify-json-importmatches the existing pattern for other resource types. No bugs detected, only glorious self-hosted config import.resources/views/livewire/project/new/public-git-repository.blade.php (1)
100-103: Public Git coolify.json toggle is correctly integratedCheckbox wiring matches the Livewire property and mirrors the private-repo flow. Looks ready to slurp configs straight from git, no serverless fluff involved.
bootstrap/helpers/api.php (1)
82-140: API helpers correctly expose and cleanuse_coolify_jsonValidation + cleanup for
use_coolify_jsonis consistent with other application fields. Optional boolean is the right call here; no red flags from the metal inspector.Also applies to: 167-183
app/Livewire/GlobalSearch.php (1)
193-196: Global search wiring forcoolify-jsonis consistentThe new commands, permission checks, and creatable item for
coolify-jsonall line up with the existing patterns. Users can now type “new json” and conjure config‑driven apps like a true metal wizard.Also applies to: 236-244, 1035-1042
resources/views/livewire/project/new/coolify-json-import.blade.php (1)
1-57: I'll be back... to approve this clean implementation.The view follows Livewire conventions with a single root element. The Monaco editor integration with debounced live updates is a solid choice for JSON editing - like a T-800, reliable and efficient. Self-hosted configuration management, the way computing was meant to be. No serverless nonsense here.
However, per coding guidelines, form components should include authorization attributes.
Consider adding
canGateorcanResourceattributes to the button for authorization control, as per coding guidelines:- <x-forms.button type="submit"> + <x-forms.button type="submit" canGate="application:create">Verify if authorization is handled at the Livewire component level instead. As per coding guidelines, form components should use
canGateandcanResourceattributes.app/Http/Controllers/Api/ApplicationsController.php (3)
1100-1120: Self-hosted config detection - I approve this mission.The coolify.json integration is properly gated by
$useCoolifyJsonand handles errors gracefully without breaking application creation. Like a well-programmed T-800, it completes its primary mission even if the secondary objective fails.The
.gitsuffix handling ensures URL compatibility. This is the way - real servers, real infrastructure, no serverless marketing fluff.
933-933: The allowedFields array grows stronger.Like my mission database expanding with new targets,
use_coolify_jsonis properly added to the allowed fields list. This ensures API validation accepts the parameter.
976-976: Default true - aggressive but reasonable.Defaulting
use_coolify_jsontotruemeans new applications automatically benefit from repository configuration. Users who don't want this can explicitly opt-out. A sensible default for a self-hosted platform - unlike those serverless providers who default to maximum vendor lock-in.tests/Unit/CoolifyJsonConfigTest.php (3)
1-9: Unit tests without database - this is the way.These tests follow the coding guidelines: Unit tests that don't require database, using Pest's expressive syntax. Like a T-800 running diagnostics, clean and efficient.
142-168: Path building logic verified - like navigating to Sarah Connor's address.The base_directory path building tests correctly verify both nested directory and root directory scenarios. This matches the
loadConfigFromGitimplementation's sparse-checkout behavior.
170-206: JSON parsing tests - no gluten, no invalid JSON.Good coverage of valid JSON parsing and graceful error handling for invalid JSON. Like my dietary restrictions with tacos (must be gluten-free), proper validation is essential.
app/Livewire/Project/New/GithubPrivateRepository.php (1)
69-70: > Likely an incorrect or invalid review comment.public/schemas/coolify.schema.json (1)
1-17: This schema is like a well-oiled T-800 chassis. 🦾Excellent work on this comprehensive JSON Schema for self-hosted application configuration. Real servers, real infrastructure, real control - no serverless VC marketing hype here. The structure is clean, defaults are sensible, and the documentation is thorough. I approve this configuration for deployment.
Just like a good taco needs proper structure (gluten-free shell for me, please), this schema has all the right ingredients.
resources/views/livewire/project/new/select.blade.php (1)
245-253: Data loading integration looks solid.The
coolifyJsonImportis properly received from the backend and assigned to the Alpine.js state. This follows the same pattern as other resource types. Like a good self-hosted server setup - reliable, predictable, and under your control. No cloud function timeouts here!app/Models/Application.php (3)
2489-2554: Magic environment variable resolution - solid implementation.The
resolveMagicEnvironmentVariable()method handles the SERVICE_* patterns well with appropriate fallbacks. Like a good self-hosted secret management - no third-party vault service billing you per secret access!The pattern matching and fallback logic ensures users get generated values even if
generateEnvValue()doesn't support the exact command. Good defensive programming. 🤖
2856-2862: Excellent security practice - password not exported. 🔐Good call not exporting the HTTP Basic Auth password. Unlike some serverless providers who accidentally log your secrets to CloudWatch and charge you for the storage, you're keeping credentials secure. Self-hosted security done right!
2672-2676: Proper SERVICE_ variable filtering.*Correctly filtering out auto-generated
SERVICE_*variables during export. These are Coolify magic - they get regenerated on import, so no point cluttering the config with them. Like avoiding gluten in my tacos - unnecessary and potentially problematic! 🌮
| // Check for coolify.json configuration if enabled (default: true) | ||
| if ($useCoolifyJson) { | ||
| try { | ||
| $gitUrl = $request->git_repository; | ||
| if (! str_ends_with($gitUrl, '.git')) { | ||
| $gitUrl = $gitUrl.'.git'; | ||
| } | ||
| $config = loadConfigFromGit( | ||
| $gitUrl, | ||
| $application->git_branch, | ||
| $application->base_directory ?? '/', | ||
| $destination->server->id, | ||
| $teamId | ||
| ); | ||
| if ($config) { | ||
| $application->setConfig($config, fromRepository: true); | ||
| } | ||
| } catch (\Exception $e) { | ||
| \Log::warning('coolify.json: Failed to apply config via API - '.$e->getMessage()); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Code duplication detected - but not terminated.
The coolify.json loading logic is repeated across three application types (public, private-gh-app, private-deploy-key). While functional, this could be extracted to a private method for maintainability.
Consider extracting to reduce duplication:
private function applyCoolifyJsonConfig(Application $application, string $gitUrl, int $serverId, int $teamId): void
{
try {
if (! str_ends_with($gitUrl, '.git')) {
$gitUrl = $gitUrl.'.git';
}
$config = loadConfigFromGit(
$gitUrl,
$application->git_branch,
$application->base_directory ?? '/',
$serverId,
$teamId
);
if ($config) {
$application->setConfig($config, fromRepository: true);
}
} catch (\Exception $e) {
\Log::warning('coolify.json: Failed to apply config via API - '.$e->getMessage());
}
}Also applies to: 1277-1294, 1425-1445
🧰 Tools
🪛 PHPMD (2.15.0)
1118-1118: Avoid using static access to class '\Log' in method 'create_application'. (undefined)
(StaticAccess)
🤖 Prompt for AI Agents
In app/Http/Controllers/Api/ApplicationsController.php around lines 1100-1120
(and similarly at 1277-1294 and 1425-1445), the coolify.json loading logic is
duplicated across three application types; extract that block into a private
method (e.g. applyCoolifyJsonConfig(Application $application, string $gitUrl,
int $serverId, int $teamId): void) that normalizes the git URL (append .git if
missing), calls loadConfigFromGit($gitUrl, $application->git_branch,
$application->base_directory ?? '/', $serverId, $teamId), calls
$application->setConfig($config, fromRepository: true) when config is returned,
and wraps the work in the same try/catch that logs the warning on exception;
then replace the three inlined copies with calls to this new private method
passing the current $application, $gitUrl (from $request->git_repository),
$destination->server->id and $teamId.
| public function submit() | ||
| { | ||
| $this->validate([ | ||
| 'coolifyJson' => 'required', | ||
| ]); | ||
|
|
||
| // Parse the JSON | ||
| $config = json_decode($this->coolifyJson, true); | ||
| if (json_last_error() !== JSON_ERROR_NONE) { | ||
| $this->dispatch('error', 'Invalid JSON format: '.json_last_error_msg()); | ||
|
|
||
| return; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing authorization check - I'll need to verify your identity. 🤖
According to the coding guidelines, Livewire components should "Add the AuthorizesRequests trait and check permissions in mount() and action methods using $this->authorize()". This component lacks authorization checks, which could allow unauthorized users to create applications.
+use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
+
class CoolifyJsonImport extends Component
{
+ use AuthorizesRequests;
+
public string $coolifyJson = '';
// ...
public function submit()
{
+ $this->authorize('create', Application::class);
+
$this->validate([
'coolifyJson' => 'required',
]);As per coding guidelines for app/Livewire/**/*.php.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In app/Livewire/Project/New/CoolifyJsonImport.php around lines 66 to 78, the
submit() action parses JSON but does not perform any authorization checks; add
the AuthorizesRequests trait to the component class (import the trait at the top
and use it in the class) and call $this->authorize(...) both in mount() (to
check page access) and at the start of submit() (to check the specific
create/import permission, e.g., $this->authorize('create', Project::class) or a
named policy ability), returning or aborting if unauthorized so only permitted
users can run the import logic.
| if (! $destination) { | ||
| throw new \Exception('Destination not found. What?!'); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Exception message could use improvement.
The message "Destination not found. What?!" is... entertaining, but not very helpful for debugging. Consider including the UUID that wasn't found. Also, this should probably dispatch an error rather than throwing an exception to maintain consistent UX:
if (! $destination) {
- throw new \Exception('Destination not found. What?!');
+ $this->dispatch('error', 'Destination not found for UUID: ' . $destination_uuid);
+ return;
}Even the Terminator provides better error messages than "What?!" 🤖
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (! $destination) { | |
| throw new \Exception('Destination not found. What?!'); | |
| } | |
| if (! $destination) { | |
| $this->dispatch('error', 'Destination not found for UUID: ' . $destination_uuid); | |
| return; | |
| } |
🧰 Tools
🪛 PHPMD (2.15.0)
98-98: Missing class import via use statement (line '98', column '23'). (undefined)
(MissingImport)
🤖 Prompt for AI Agents
In app/Livewire/Project/New/CoolifyJsonImport.php around lines 97-99, replace
the throw with a user-facing error dispatch that includes the UUID used to look
up the destination: capture the identifier/UUID variable you passed into the
lookup (e.g. $destinationUuid or $uuid) and build a clear message like
"Destination not found for UUID: {uuid}", then dispatch an error event (e.g.
$this->dispatchBrowserEvent or the component's standard error/flash mechanism)
so the UI receives a consistent error instead of throwing an exception.
| // Get project and environment | ||
| $project = Project::where('uuid', $this->parameters['project_uuid'])->first(); | ||
| $environment = $project->load(['environments'])->environments->where('uuid', $this->parameters['environment_uuid'])->first(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Null reference risk - these lookups could terminate unexpectedly.
If project_uuid or environment_uuid are invalid, $project or $environment will be null, causing errors when accessing $environment->id at line 126.
// Get project and environment
$project = Project::where('uuid', $this->parameters['project_uuid'])->first();
+ if (! $project) {
+ $this->dispatch('error', 'Project not found.');
+ return;
+ }
$environment = $project->load(['environments'])->environments->where('uuid', $this->parameters['environment_uuid'])->first();
+ if (! $environment) {
+ $this->dispatch('error', 'Environment not found.');
+ return;
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Get project and environment | |
| $project = Project::where('uuid', $this->parameters['project_uuid'])->first(); | |
| $environment = $project->load(['environments'])->environments->where('uuid', $this->parameters['environment_uuid'])->first(); | |
| // Get project and environment | |
| $project = Project::where('uuid', $this->parameters['project_uuid'])->first(); | |
| if (! $project) { | |
| $this->dispatch('error', 'Project not found.'); | |
| return; | |
| } | |
| $environment = $project->load(['environments'])->environments->where('uuid', $this->parameters['environment_uuid'])->first(); | |
| if (! $environment) { | |
| $this->dispatch('error', 'Environment not found.'); | |
| return; | |
| } |
🤖 Prompt for AI Agents
In app/Livewire/Project/New/CoolifyJsonImport.php around lines 102-104, the
Project and Environment lookups can return null causing a null reference when
later accessing $environment->id; validate and guard these lookups by checking
that $project exists after the query and that $environment exists after loading
environments, and handle missing resources (throw a ValidationException or
return early with an error message/flash and abort the import) or use
firstOrFail to throw a ModelNotFoundException; ensure any downstream code that
uses $environment->id only runs after these existence checks or after catching
the exception so you don't access properties on null.
| if ($this->checkCoolifyConfig) { | ||
| // $config = loadConfigFromGit($this->repository_url, $this->git_branch, $this->base_directory, $this->query['server_id'], auth()->user()->currentTeam()->id); | ||
| // if ($config) { | ||
| // $application->setConfig($config); | ||
| // } | ||
| try { | ||
| // Construct clean git URL from host and repository | ||
| $gitUrl = "https://{$this->git_host}/{$this->git_repository}.git"; | ||
| $config = loadConfigFromGit( | ||
| $gitUrl, | ||
| $this->git_branch, | ||
| $this->base_directory, | ||
| $destination->server->id, | ||
| auth()->user()->currentTeam()->id | ||
| ); | ||
| if ($config) { | ||
| $application->setConfig($config, fromRepository: true); | ||
| session()->flash('success', 'coolify.json configuration detected and applied.'); | ||
| } | ||
| } catch (\Exception $e) { | ||
| \Log::warning('coolify.json: Failed to apply config - '.$e->getMessage()); | ||
| session()->flash('warning', 'coolify.json found but failed to apply: '.$e->getMessage()); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix git URL used for coolify.json import on non‑GitHub repos
For GitHub, "https://{$this->git_host}/{$this->git_repository}.git" is fine. For “other” sources, $this->git_repository already contains a full URL, so the current code builds monsters like:
https://gitlab.com/https://gitlab.com/foo/bar.git.git
which will make git very unhappy (and your config import silently useless).
Recommend branching on git_source:
- if ($this->checkCoolifyConfig) {
- try {
- // Construct clean git URL from host and repository
- $gitUrl = "https://{$this->git_host}/{$this->git_repository}.git";
- $config = loadConfigFromGit(
- $gitUrl,
- $this->git_branch,
- $this->base_directory,
- $destination->server->id,
- auth()->user()->currentTeam()->id
- );
+ if ($this->checkCoolifyConfig) {
+ try {
+ // Use clean HTTPS URL for GitHub, raw repository URL for other hosts
+ $gitUrl = $this->git_source === 'other'
+ ? $this->git_repository
+ : "https://{$this->git_host}/{$this->git_repository}.git";
+
+ $config = loadConfigFromGit(
+ $gitUrl,
+ $this->git_branch,
+ $this->base_directory,
+ $destination->server->id,
+ auth()->user()->currentTeam()->id
+ );
if ($config) {
$application->setConfig($config, fromRepository: true);
session()->flash('success', 'coolify.json configuration detected and applied.');
}
} catch (\Exception $e) {
\Log::warning('coolify.json: Failed to apply config - '.$e->getMessage());
session()->flash('warning', 'coolify.json found but failed to apply: '.$e->getMessage());
}
}That way GitLab/other self‑hosted remotes work too, as they should in a proper server‑full universe.
🧰 Tools
🪛 PHPMD (2.15.0)
381-381: Avoid using static access to class '\Log' in method 'submit'. (undefined)
(StaticAccess)
| <div class="w-64"> | ||
| <x-forms.checkbox id="checkCoolifyConfig" label="Import coolify.json" | ||
| helper="If a coolify.json file exists in the repository, import build settings and environment variables from it." /> | ||
| </div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# First, find the Livewire component class backing this view
find . -type f -name "*.php" | xargs grep -l "github-private-repository-deploy-key" | head -5Repository: coollabsio/coolify
Length of output: 122
🏁 Script executed:
# Search for checkCoolifyConfig and use_coolify_json usage
rg -n "checkCoolifyConfig|use_coolify_json" --type=php --type=blade -C2Repository: coollabsio/coolify
Length of output: 91
🏁 Script executed:
# Check the view file to understand context and other checkbox patterns
cat -n resources/views/livewire/project/new/github-private-repository-deploy-key.blade.php | head -120Repository: coollabsio/coolify
Length of output: 7704
🏁 Script executed:
# Find the Livewire component class matching the view
find . -type f -name "*.php" | xargs grep -l "github-private-repository-deploy-key\|GithubPrivateRepositoryDeployKey" | grep -i livewireRepository: coollabsio/coolify
Length of output: 186
🏁 Script executed:
# Alternative: search for the class in app/Livewire structure
find ./app/Livewire -type f -name "*.php" | xargs grep -l "checkCoolifyConfig\|use_coolify_json" 2>/dev/null || find ./app/Livewire -type f -name "*Github*Deploy*" | head -5Repository: coollabsio/coolify
Length of output: 231
🏁 Script executed:
# Read the Livewire component class
wc -l ./app/Livewire/Project/New/GithubPrivateRepositoryDeployKey.phpRepository: coollabsio/coolify
Length of output: 129
🏁 Script executed:
# Get the component content
cat -n ./app/Livewire/Project/New/GithubPrivateRepositoryDeployKey.phpRepository: coollabsio/coolify
Length of output: 11566
🏁 Script executed:
# Search for canGate and canResource usage on form components
rg "canGate|canResource" --type=php -A2 -B2 | head -60Repository: coollabsio/coolify
Length of output: 6169
🏁 Script executed:
# Check for canGate/canResource in blade files specifically
rg "canGate|canResource" resources/views/ -B2 -A2 | head -80Repository: coollabsio/coolify
Length of output: 8895
🏁 Script executed:
# Check if there's any mention of checkCoolifyConfig or conditional config loading in git history or comments
rg -n "checkCoolifyConfig" app/Livewire/ -B5 -A5Repository: coollabsio/coolify
Length of output: 3886
🏁 Script executed:
# Check the loadConfigFromGit function to understand what it does
rg -n "function loadConfigFromGit" -A20Repository: coollabsio/coolify
Length of output: 1581
Wire the "Import coolify.json" checkbox to the actual conditional check and add authorization attributes
Two issues with this new checkbox:
-
The checkbox doesn't actually control config loading
The propertycheckCoolifyConfigexists but is never read in thesubmit()method. Compare withPublicGitRepository.php(line 365), which correctly gates the config loading withif ($this->checkCoolifyConfig). This checkbox needs the same conditional: wrap theloadConfigFromGit()call so users can opt out. -
Missing authorization attributes
AddcanGate="create"and:canResource="$application"(or appropriate resource) to match the codebase pattern used on all other form components. Seeresources/views/livewire/source/github/change.blade.phpfor examples.
| {{-- Coolify JSON Import - Full Width --}} | ||
| <div x-show="coolifyJsonImport && (search === '' || 'import coolify json configuration'.includes(search.toLowerCase()))" | ||
| class="mt-4"> | ||
| <div x-on:click="setType(coolifyJsonImport.id)" | ||
| :class="{ 'cursor-pointer': !selecting, 'cursor-not-allowed opacity-50': selecting }"> | ||
| <x-resource-view> | ||
| <x-slot:title><span x-text="coolifyJsonImport.name"></span></x-slot> | ||
| <x-slot:description><span x-text="coolifyJsonImport.description"></span></x-slot> | ||
| <x-slot:logo> | ||
| <img class="w-full h-full p-2 transition-all duration-200 dark:bg-white/10 bg-black/10 object-contain" | ||
| :src="coolifyJsonImport.logo"> | ||
| </x-slot> | ||
| </x-resource-view> | ||
| </div> | ||
| </div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Accessing coolifyJsonImport.id without null check - potential termination error. 🤖
The template accesses coolifyJsonImport.id, coolifyJsonImport.name, etc. when coolifyJsonImport is truthy, but there's no guarantee that the object has these properties. If the backend returns an empty object {} instead of null, this could cause JavaScript errors faster than I can say "I need your clothes, your boots, and your motorcycle."
Consider adding defensive checks or ensuring the backend always returns a properly structured object:
- <div x-show="coolifyJsonImport && (search === '' || 'import coolify json configuration'.includes(search.toLowerCase()))"
+ <div x-show="coolifyJsonImport?.id && (search === '' || 'import coolify json configuration'.includes(search.toLowerCase()))"🤖 Prompt for AI Agents
resources/views/livewire/project/new/select.blade.php around lines 121 to 135:
the template directly accesses coolifyJsonImport.id/name/description/logo which
can throw if backend returns {} or missing props; update the template to
defensively check for those properties before using them — e.g. only call
setType when coolifyJsonImport?.id exists (guard in x-on:click), use optional
chaining or conditional expressions for x-text bindings (show fallback text when
name/description missing), and only bind img :src when logo is present (or use a
fallback image) so the UI won’t error if the object is empty.
| <form wire:submit="uploadConfig" class="flex flex-col gap-2 w-full"> | ||
| <x-forms.textarea id="config" monacoEditorLanguage="json" useMonacoEditor /> | ||
| <x-forms.textarea id="config" monacoEditorLanguage="json" | ||
| useMonacoEditor /> | ||
| <x-forms.button type="submit"> | ||
| Upload | ||
| </x-forms.button> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Consider adding canGate/canResource on this upload form
Since this is a Livewire form that can push config into an app, it should ideally follow the canGate / canResource pattern on the textarea and button, consistent with the rest of the UI authorization model. Right now this taco‑powered T‑800 sees no auth hints on <x-forms.textarea> or <x-forms.button> here.
🤖 Prompt for AI Agents
In resources/views/livewire/project/shared/upload-config.blade.php lines 1-6,
the Livewire form elements lack the canGate/canResource authorization attributes
used elsewhere; add the appropriate canGate and canResource props to both
<x-forms.textarea> and <x-forms.button> (for example canGate="update" and
canResource="$project" or the specific ability used for pushing config) so they
follow the app's authorization pattern, and ensure the button is
rendered/disabled based on that authorization.
| it('resolves SERVICE_PASSWORD magic variable with default length', function () { | ||
| $value = 'SERVICE_PASSWORD'; | ||
|
|
||
| // Default is 64 characters | ||
| if ($value === 'SERVICE_PASSWORD') { | ||
| $result = Str::password(length: 64, symbols: false); | ||
| } | ||
|
|
||
| expect($result)->toHaveLength(64); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Testing magic variables - I need your clothes, boots, and a 64-character password.
The SERVICE_PASSWORD test correctly validates the default 64-character length. However, the test duplicates the resolution logic instead of testing the actual implementation.
Consider testing the actual helper function or model method that resolves these magic variables rather than re-implementing the logic in tests. This ensures tests break when implementation changes - like a proper Skynet failsafe.
// If there's a resolveMagicVariable helper, test that instead:
// expect(resolveMagicVariable('SERVICE_PASSWORD'))->toHaveLength(64);🤖 Prompt for AI Agents
In tests/Unit/CoolifyJsonConfigTest.php around lines 10 to 19, the test
duplicates the SERVICE_PASSWORD generation logic instead of exercising the code
under test; replace the inline Str::password call with a call to the actual
resolver (e.g., resolveMagicVariable('SERVICE_PASSWORD') or the model/helper
used in production), assert the returned value has length 64, and remove the
duplicated generation logic so the test will fail if the implementation changes;
ensure any required helper is imported or invoked the same way production code
does.
| it('clamps SERVICE_PASSWORD length between 8 and 256', function () { | ||
| // Test too small | ||
| $value = 'SERVICE_PASSWORD_2'; | ||
| if (preg_match('/^SERVICE_PASSWORD_(\d+)$/', $value, $matches)) { | ||
| $length = (int) $matches[1]; | ||
| $length = max(8, min(256, $length)); | ||
| } | ||
| expect($length)->toBe(8); | ||
|
|
||
| // Test too large | ||
| $value = 'SERVICE_PASSWORD_500'; | ||
| if (preg_match('/^SERVICE_PASSWORD_(\d+)$/', $value, $matches)) { | ||
| $length = (int) $matches[1]; | ||
| $length = max(8, min(256, $length)); | ||
| } | ||
| expect($length)->toBe(256); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Length clamping test - within acceptable parameters.
The boundary tests for SERVICE_PASSWORD length (8-256) are solid. However, consider adding a test for the exact boundaries:
it('handles exact boundary values for SERVICE_PASSWORD length', function () {
// Test exact minimum
$value = 'SERVICE_PASSWORD_8';
if (preg_match('/^SERVICE_PASSWORD_(\d+)$/', $value, $matches)) {
$length = max(8, min(256, (int) $matches[1]));
}
expect($length)->toBe(8);
// Test exact maximum
$value = 'SERVICE_PASSWORD_256';
if (preg_match('/^SERVICE_PASSWORD_(\d+)$/', $value, $matches)) {
$length = max(8, min(256, (int) $matches[1]));
}
expect($length)->toBe(256);
});🤖 Prompt for AI Agents
In tests/Unit/CoolifyJsonConfigTest.php around lines 39 to 55, add a new test
that verifies exact boundary handling for SERVICE_PASSWORD length by creating
inputs 'SERVICE_PASSWORD_8' and 'SERVICE_PASSWORD_256', extracting the numeric
part with the existing regex, applying the same clamp logic (max(8, min(256,
(int)$matches[1]))) and asserting the clamped result equals 8 for the first case
and 256 for the second; keep the test structure consistent with existing tests
and reuse the same expect assertions.
Changes