Skip to content

Conversation

@andrasbacsai
Copy link
Member

Changes

  • Add coolify.json import functionality to quickly create and configure applications
  • Implement APPLICATION_CONFIG_IMPORT_FROM_JSON queue job for async processing
  • Add configuration parsing, validation, and error handling
  • Support environment variables, build commands, and directory mounts from coolify.json
  • Filter out SERVICE_* magic environment variables during export and import
  • Create upload-config view for alternative configuration upload method

Copilot AI review requested due to automatic review settings December 4, 2025 13:11
@andrasbacsai andrasbacsai added the 🐰 Release The Rabbit Run CodeRabbitAI review label Dec 4, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 4, 2025

Note

Other AI code review bot(s) detected

CodeRabbit 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.

Walkthrough

This pull request adds comprehensive support for coolify.json configuration files, enabling applications to import deployment settings directly from repositories. The implementation includes a new use_coolify_json flag across application creation flows, a dedicated Livewire component for JSON import operations, enhanced git configuration loading with robust validation, and schema definitions for configuration validation. UI elements were added to multiple deployment forms, and the Application model's setConfig() method was refactored to handle repository-sourced configurations, environment variables, build settings, and deployment parameters. Several informational logging statements were removed from job classes. This is a proper self-hosted, server-based configuration approach—no serverless fantasy here, just real infrastructure you own and control.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch coolify-json-detection

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copilot finished reviewing on behalf of andrasbacsai December 4, 2025 13:14
Copy link

Copilot AI left a 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() and Application::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_json parameter (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

Comment on lines +2805 to +2809
if ($settings->connect_to_docker_network) {
$configSettings['connect_to_docker_network'] = true;
Copy link

Copilot AI Dec 4, 2025

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.

Suggested change
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;

Copilot uses AI. Check for mistakes.
},
{
"key": "DATABASE_PASSWORD",
"value": "$SERVICE_PASSWORD_32"
Copy link

Copilot AI Dec 4, 2025

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.

Copilot uses AI. Check for mistakes.
Comment on lines +205 to +220
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());
}
Copy link

Copilot AI Dec 4, 2025

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.

Copilot uses AI. Check for mistakes.
Comment on lines +205 to +219
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());
Copy link

Copilot AI Dec 4, 2025

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.

Suggested change
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());
}

Copilot uses AI. Check for mistakes.
'enabled' => data_get($task, 'enabled', true),
'timeout' => data_get($task, 'timeout', 300),
'application_id' => $this->id,
'team_id' => $this->team()->id,
Copy link

Copilot AI Dec 4, 2025

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.

Suggested change
'team_id' => $this->team()->id,
'team_id' => $this->team_id,

Copilot uses AI. Check for mistakes.
Comment on lines +605 to +606
"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"]
Copy link

Copilot AI Dec 4, 2025

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.

Copilot uses AI. Check for mistakes.
Comment on lines +226 to +231
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());
Copy link

Copilot AI Dec 4, 2025

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.

Suggested change
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());
}

Copilot uses AI. Check for mistakes.
'another_unknown' => 'value',
];

$knownFields = ['version', 'name', 'description', 'build', 'domains', 'environment_variables', 'health_check', 'limits', 'settings'];
Copy link

Copilot AI Dec 4, 2025

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.

Suggested change
$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',
];

Copilot uses AI. Check for mistakes.

$uuid = new Cuid2;
$cloneCommand = "git clone --no-checkout -b $branch $repository .";
$cloneCommand = "git clone --no-checkout -b {$branch} {$repository} .";
Copy link

Copilot AI Dec 4, 2025

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).

Copilot uses AI. Check for mistakes.
$cloneCommand,
'git sparse-checkout init --cone',
"git sparse-checkout set {$fileList->implode(' ')}",
"git sparse-checkout set {$fileList}",
Copy link

Copilot AI Dec 4, 2025

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.

Copilot uses AI. Check for mistakes.
…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.
@andrasbacsai andrasbacsai force-pushed the coolify-json-detection branch from afced58 to 4c1c740 Compare December 4, 2025 15:30
Copy link
Contributor

@coderabbitai coderabbitai bot left a 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 AuthorizesRequests trait. 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 root

Right 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:

  1. Shell injection on git & sparse‑checkout commands (critical)

    • $branch, $repository, and $base_directory flow 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.  
    
    
  1. 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 $output is literally the contents of coolify.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;
       }
    
    
    
  1. Ensure only JSON reaches json_decode (recommended)

    • git clone, git sparse-checkout, and git read-tree can emit to stdout; that junk will be concatenated with the JSON and break decoding. You’re already redirecting stderr for cat; extend that idea and redirect stdout/stderr for the git commands to /dev/null so the only output is the file contents from the final cat. (See >/dev/null 2>&1 in the clone/sparse‑checkout suggestions above.)
  2. 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 knownFields list is still missing several schema‑level keys that Application::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 the checkCoolifyConfig flag when importing coolify.json

Right now the component always tries to load/apply coolify.json, even if the (now-added) checkCoolifyConfig flag 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 $checkCoolifyConfig property 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 knownFields array in this test is missing several valid top-level fields that the actual implementation in loadConfigFromGit (bootstrap/helpers/shared.php) and setConfig (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_32 and $SERVICE_FQDN_API with the $ prefix, but the implementation in Application::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_32 but should be SERVICE_PASSWORD_32 to 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) returns data_get($this, 'environment.project.team'), which gives you the Team model, not a relationship builder. Calling ->id on 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

📥 Commits

Reviewing files that changed from the base of the PR and between 558a885 and 4c1c740.

📒 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/pint before 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
Use handleError() 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/pint before committing code

Files:

  • app/Livewire/Project/Application/General.php
  • resources/views/livewire/project/new/github-private-repository-deploy-key.blade.php
  • resources/views/livewire/project/new/github-private-repository.blade.php
  • app/Livewire/Project/New/GithubPrivateRepository.php
  • resources/views/livewire/global-search.blade.php
  • app/Livewire/GlobalSearch.php
  • resources/views/livewire/project/application/general.blade.php
  • app/Livewire/Project/New/PublicGitRepository.php
  • resources/views/livewire/project/new/coolify-json-import.blade.php
  • app/Livewire/Project/Application/Configuration.php
  • resources/views/livewire/project/shared/upload-config.blade.php
  • app/Livewire/Project/New/GithubPrivateRepositoryDeployKey.php
  • app/Http/Controllers/Api/ApplicationsController.php
  • bootstrap/helpers/shared.php
  • resources/views/livewire/project/new/public-git-repository.blade.php
  • app/Livewire/Project/New/CoolifyJsonImport.php
  • tests/Unit/CoolifyJsonConfigTest.php
  • bootstrap/helpers/api.php
  • app/Livewire/Project/New/Select.php
  • app/Models/Application.php
  • resources/views/livewire/project/new/select.blade.php
  • resources/views/livewire/project/resource/create.blade.php
app/Livewire/**/*.php

📄 CodeRabbit inference engine (CLAUDE.md)

app/Livewire/**/*.php: Add the AuthorizesRequests trait 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.php
  • app/Livewire/Project/New/GithubPrivateRepository.php
  • app/Livewire/GlobalSearch.php
  • app/Livewire/Project/New/PublicGitRepository.php
  • app/Livewire/Project/Application/Configuration.php
  • app/Livewire/Project/New/GithubPrivateRepositoryDeployKey.php
  • app/Livewire/Project/New/CoolifyJsonImport.php
  • app/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.
Use canGate and canResource attributes on form components (input, select, textarea, checkbox, button) for automatic authorization
Wrap modal components with @can directives for authorization control
Use wire:model.live for real-time two-way data binding between Livewire component and view

Files:

  • resources/views/livewire/project/new/github-private-repository-deploy-key.blade.php
  • resources/views/livewire/project/new/github-private-repository.blade.php
  • resources/views/livewire/global-search.blade.php
  • resources/views/livewire/project/application/general.blade.php
  • resources/views/livewire/project/new/coolify-json-import.blade.php
  • resources/views/livewire/project/shared/upload-config.blade.php
  • resources/views/livewire/project/new/public-git-repository.blade.php
  • resources/views/livewire/project/new/select.blade.php
  • resources/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
Use gap utilities for spacing instead of margins

Files:

  • resources/views/livewire/project/new/github-private-repository-deploy-key.blade.php
  • resources/views/livewire/project/new/github-private-repository.blade.php
  • resources/views/livewire/global-search.blade.php
  • resources/views/livewire/project/application/general.blade.php
  • resources/views/livewire/project/new/coolify-json-import.blade.php
  • resources/views/livewire/project/shared/upload-config.blade.php
  • resources/views/livewire/project/new/public-git-repository.blade.php
  • resources/views/livewire/project/new/select.blade.php
  • resources/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.php
  • resources/views/livewire/project/new/github-private-repository.blade.php
  • resources/views/livewire/global-search.blade.php
  • resources/views/livewire/project/application/general.blade.php
  • resources/views/livewire/project/new/coolify-json-import.blade.php
  • resources/views/livewire/project/shared/upload-config.blade.php
  • resources/views/livewire/project/new/public-git-repository.blade.php
  • resources/views/livewire/project/new/select.blade.php
  • resources/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 using canGate and canResource attributes
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.php
  • resources/views/livewire/project/new/github-private-repository.blade.php
  • resources/views/livewire/global-search.blade.php
  • resources/views/livewire/project/application/general.blade.php
  • resources/views/livewire/project/new/coolify-json-import.blade.php
  • resources/views/livewire/project/shared/upload-config.blade.php
  • resources/views/livewire/project/new/public-git-repository.blade.php
  • resources/views/livewire/project/new/select.blade.php
  • resources/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.php
  • bootstrap/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/Unit

Run 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 $fillable array 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.php
  • resources/views/livewire/project/shared/upload-config.blade.php
  • resources/views/livewire/project/new/select.blade.php
  • resources/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.php
  • resources/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.php
  • app/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.php
  • 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/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: coolifyJsonImport option wiring looks solid

The 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 solid

Authorization 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 good

The new 'new coolify json' / 'new json' / 'new import' entries plug neatly into the existing exact‑match flow and should resolve to the new coolify-json creatable 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 consistent

ID 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: New coolify-json resource branch is wired correctly

Type dispatch to project.new.coolify-json-import matches 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 integrated

Checkbox 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 clean use_coolify_json

Validation + cleanup for use_coolify_json is 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 for coolify-json is consistent

The new commands, permission checks, and creatable item for coolify-json all 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 canGate or canResource attributes 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 canGate and canResource attributes.

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 $useCoolifyJson and 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 .git suffix 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_json is properly added to the allowed fields list. This ensures API validation accepts the parameter.


976-976: Default true - aggressive but reasonable.

Defaulting use_coolify_json to true means 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 loadConfigFromGit implementation'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 coolifyJsonImport is 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! 🌮

Comment on lines +1100 to +1120
// 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());
}
}
Copy link
Contributor

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.

Comment on lines +66 to +78
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;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +97 to +99
if (! $destination) {
throw new \Exception('Destination not found. What?!');
}
Copy link
Contributor

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.

Suggested change
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.

Comment on lines +102 to +104
// 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();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Suggested change
// 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.

Comment on lines 365 to +383
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());
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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)

Comment on lines +108 to +111
<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>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 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 -5

Repository: 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 -C2

Repository: 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 -120

Repository: 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 livewire

Repository: 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 -5

Repository: coollabsio/coolify

Length of output: 231


🏁 Script executed:

# Read the Livewire component class
wc -l ./app/Livewire/Project/New/GithubPrivateRepositoryDeployKey.php

Repository: coollabsio/coolify

Length of output: 129


🏁 Script executed:

# Get the component content
cat -n ./app/Livewire/Project/New/GithubPrivateRepositoryDeployKey.php

Repository: 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 -60

Repository: coollabsio/coolify

Length of output: 6169


🏁 Script executed:

# Check for canGate/canResource in blade files specifically
rg "canGate|canResource" resources/views/ -B2 -A2 | head -80

Repository: 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 -A5

Repository: coollabsio/coolify

Length of output: 3886


🏁 Script executed:

# Check the loadConfigFromGit function to understand what it does
rg -n "function loadConfigFromGit" -A20

Repository: 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:

  1. The checkbox doesn't actually control config loading
    The property checkCoolifyConfig exists but is never read in the submit() method. Compare with PublicGitRepository.php (line 365), which correctly gates the config loading with if ($this->checkCoolifyConfig). This checkbox needs the same conditional: wrap the loadConfigFromGit() call so users can opt out.

  2. Missing authorization attributes
    Add canGate="create" and :canResource="$application" (or appropriate resource) to match the codebase pattern used on all other form components. See resources/views/livewire/source/github/change.blade.php for examples.

Comment on lines +121 to +135
{{-- 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>
Copy link
Contributor

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.

Comment on lines 1 to 6
<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>
Copy link
Contributor

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.

Comment on lines +10 to +19
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);
});
Copy link
Contributor

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.

Comment on lines +39 to +55
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);
});
Copy link
Contributor

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🐰 Release The Rabbit Run CodeRabbitAI review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants