wip: compose based apps
This commit is contained in:
		| @@ -71,6 +71,15 @@ class SyncBunny extends Command | |||||||
|             ]); |             ]); | ||||||
|         }); |         }); | ||||||
|         try { |         try { | ||||||
|  |             if (!$only_template && !$only_version) { | ||||||
|  |                 $this->info('About to sync files (docker-compose.prod.yaml, upgrade.sh, install.sh, etc) to BunnyCDN.'); | ||||||
|  |             } | ||||||
|  |             if ($only_template) { | ||||||
|  |                 $this->info('About to sync service-templates.json to BunnyCDN.'); | ||||||
|  |             } | ||||||
|  |             if ($only_version) { | ||||||
|  |                 $this->info('About to sync versions.json to BunnyCDN.'); | ||||||
|  |             } | ||||||
|             $confirmed = confirm('Are you sure you want to sync?'); |             $confirmed = confirm('Are you sure you want to sync?'); | ||||||
|             if (!$confirmed) { |             if (!$confirmed) { | ||||||
|                 return; |                 return; | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ use App\Models\Application; | |||||||
| use Illuminate\Support\Collection; | use Illuminate\Support\Collection; | ||||||
| use Illuminate\Support\Str; | use Illuminate\Support\Str; | ||||||
| use Livewire\Component; | use Livewire\Component; | ||||||
|  | use Visus\Cuid2\Cuid2; | ||||||
| 
 | 
 | ||||||
| class General extends Component | class General extends Component | ||||||
| { | { | ||||||
| @@ -27,6 +28,9 @@ class General extends Component | |||||||
| 
 | 
 | ||||||
|     public bool $is_static; |     public bool $is_static; | ||||||
| 
 | 
 | ||||||
|  |     public $parsedServices = []; | ||||||
|  |     public $parsedServiceDomains = []; | ||||||
|  | 
 | ||||||
|     protected $listeners = [ |     protected $listeners = [ | ||||||
|         'resetDefaultLabels' |         'resetDefaultLabels' | ||||||
|     ]; |     ]; | ||||||
| @@ -50,6 +54,9 @@ class General extends Component | |||||||
|         'application.docker_registry_image_name' => 'nullable', |         'application.docker_registry_image_name' => 'nullable', | ||||||
|         'application.docker_registry_image_tag' => 'nullable', |         'application.docker_registry_image_tag' => 'nullable', | ||||||
|         'application.dockerfile_location' => 'nullable', |         'application.dockerfile_location' => 'nullable', | ||||||
|  |         'application.docker_compose_location' => 'nullable', | ||||||
|  |         'application.docker_compose' => 'nullable', | ||||||
|  |         'application.docker_compose_raw' => 'nullable', | ||||||
|         'application.custom_labels' => 'nullable', |         'application.custom_labels' => 'nullable', | ||||||
|         'application.dockerfile_target_build' => 'nullable', |         'application.dockerfile_target_build' => 'nullable', | ||||||
|         'application.settings.is_static' => 'boolean|required', |         'application.settings.is_static' => 'boolean|required', | ||||||
| @@ -74,6 +81,9 @@ class General extends Component | |||||||
|         'application.docker_registry_image_name' => 'Docker registry image name', |         'application.docker_registry_image_name' => 'Docker registry image name', | ||||||
|         'application.docker_registry_image_tag' => 'Docker registry image tag', |         'application.docker_registry_image_tag' => 'Docker registry image tag', | ||||||
|         'application.dockerfile_location' => 'Dockerfile location', |         'application.dockerfile_location' => 'Dockerfile location', | ||||||
|  |         'application.docker_compose_location' => 'Docker compose location', | ||||||
|  |         'application.docker_compose' => 'Docker compose', | ||||||
|  |         'application.docker_compose_raw' => 'Docker compose raw', | ||||||
|         'application.custom_labels' => 'Custom labels', |         'application.custom_labels' => 'Custom labels', | ||||||
|         'application.dockerfile_target_build' => 'Dockerfile target build', |         'application.dockerfile_target_build' => 'Dockerfile target build', | ||||||
|         'application.settings.is_static' => 'Is static', |         'application.settings.is_static' => 'Is static', | ||||||
| @@ -81,6 +91,14 @@ class General extends Component | |||||||
| 
 | 
 | ||||||
|     public function mount() |     public function mount() | ||||||
|     { |     { | ||||||
|  |         try { | ||||||
|  |             $this->parsedServices = $this->application->parseCompose(); | ||||||
|  |             ray($this->parsedServices); | ||||||
|  |         } catch (\Throwable $e) { | ||||||
|  |             $this->emit('error', $e->getMessage()); | ||||||
|  |         } | ||||||
|  |         $this->parsedServiceDomains = $this->application->docker_compose_domains ? json_decode($this->application->docker_compose_domains, true) : []; | ||||||
|  | 
 | ||||||
|         $this->ports_exposes = $this->application->ports_exposes; |         $this->ports_exposes = $this->application->ports_exposes; | ||||||
|         if (str($this->application->status)->startsWith('running') && is_null($this->application->config_hash)) { |         if (str($this->application->status)->startsWith('running') && is_null($this->application->config_hash)) { | ||||||
|             $this->application->isConfigurationChanged(true); |             $this->application->isConfigurationChanged(true); | ||||||
| @@ -98,6 +116,38 @@ class General extends Component | |||||||
|         $this->application->settings->save(); |         $this->application->settings->save(); | ||||||
|         $this->emit('success', 'Settings saved.'); |         $this->emit('success', 'Settings saved.'); | ||||||
|     } |     } | ||||||
|  |     public function loadComposeFile($isInit = false) | ||||||
|  |     { | ||||||
|  |         if ($isInit && $this->application->docker_compose_raw) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         $uuid = new Cuid2(); | ||||||
|  |         ['commands' => $cloneCommand] = $this->application->generateGitImportCommands(deployment_uuid: $uuid, only_checkout: true, exec_in_docker: false, custom_base_dir: '.'); | ||||||
|  |         $workdir = rtrim($this->application->base_directory, '/'); | ||||||
|  |         $composeFile = $this->application->docker_compose_location; | ||||||
|  |         $commands = collect([ | ||||||
|  |             "mkdir -p /tmp/{$uuid} && cd /tmp/{$uuid}", | ||||||
|  |             $cloneCommand, | ||||||
|  |             "git sparse-checkout init --cone", | ||||||
|  |             "git sparse-checkout set .$workdir$composeFile", | ||||||
|  |             "git read-tree -mu HEAD", | ||||||
|  |             "cat .$workdir$composeFile", | ||||||
|  |         ]); | ||||||
|  |         $composeFileContent = instant_remote_process($commands, $this->application->destination->server, false); | ||||||
|  |         if (!$composeFileContent) { | ||||||
|  |             $this->emit('error', "Could not load compose file from $workdir$composeFile"); | ||||||
|  |             return; | ||||||
|  |         } else { | ||||||
|  |             $this->application->docker_compose_raw = $composeFileContent; | ||||||
|  |             $this->application->save(); | ||||||
|  |         } | ||||||
|  |         $commands = collect([ | ||||||
|  |             "rm -rf /tmp/{$uuid}", | ||||||
|  |         ]); | ||||||
|  |         instant_remote_process($commands, $this->application->destination->server, false); | ||||||
|  |         $this->parsedServices = $this->application->parseCompose(); | ||||||
|  |         $this->emit('success', 'Compose file loaded.'); | ||||||
|  |     } | ||||||
|     public function updatedApplicationBuildPack() |     public function updatedApplicationBuildPack() | ||||||
|     { |     { | ||||||
|         if ($this->application->build_pack !== 'nixpacks') { |         if ($this->application->build_pack !== 'nixpacks') { | ||||||
| @@ -172,8 +222,10 @@ class General extends Component | |||||||
|                 $this->customLabels = str($this->customLabels)->replace(',', "\n"); |                 $this->customLabels = str($this->customLabels)->replace(',', "\n"); | ||||||
|             } |             } | ||||||
|             $this->application->custom_labels = $this->customLabels->explode("\n")->implode(','); |             $this->application->custom_labels = $this->customLabels->explode("\n")->implode(','); | ||||||
|  |             $this->application->docker_compose_domains = json_encode($this->parsedServiceDomains); | ||||||
|             $this->application->save(); |             $this->application->save(); | ||||||
|             $showToaster && $this->emit('success', 'Application settings updated!'); |             $showToaster && $this->emit('success', 'Application settings updated!'); | ||||||
|  |             $this->parsedServices = $this->application->parseCompose(); | ||||||
|         } catch (\Throwable $e) { |         } catch (\Throwable $e) { | ||||||
|             return handleError($e, $this); |             return handleError($e, $this); | ||||||
|         } finally { |         } finally { | ||||||
|   | |||||||
| @@ -73,6 +73,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted | |||||||
|     private $docker_compose; |     private $docker_compose; | ||||||
|     private $docker_compose_base64; |     private $docker_compose_base64; | ||||||
|     private string $dockerfile_location = '/Dockerfile'; |     private string $dockerfile_location = '/Dockerfile'; | ||||||
|  |     private string $docker_compose_location = '/docker-compose.yml'; | ||||||
|     private ?string $addHosts = null; |     private ?string $addHosts = null; | ||||||
|     private ?string $buildTarget = null; |     private ?string $buildTarget = null; | ||||||
|     private $log_model; |     private $log_model; | ||||||
| @@ -114,7 +115,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted | |||||||
|         $this->destination = $this->application->destination->getMorphClass()::where('id', $this->application->destination->id)->first(); |         $this->destination = $this->application->destination->getMorphClass()::where('id', $this->application->destination->id)->first(); | ||||||
|         $this->server = $this->mainServer = $this->destination->server; |         $this->server = $this->mainServer = $this->destination->server; | ||||||
|         $this->serverUser = $this->server->user; |         $this->serverUser = $this->server->user; | ||||||
|         $this->basedir = "/artifacts/{$this->deployment_uuid}"; |         $this->basedir = $this->application->generateBaseDir($this->deployment_uuid); | ||||||
|         $this->workdir = "{$this->basedir}" . rtrim($this->application->base_directory, '/'); |         $this->workdir = "{$this->basedir}" . rtrim($this->application->base_directory, '/'); | ||||||
|         $this->configuration_dir = application_configuration_dir() . "/{$this->application->uuid}"; |         $this->configuration_dir = application_configuration_dir() . "/{$this->application->uuid}"; | ||||||
|         $this->is_debug_enabled = $this->application->settings->is_debug_enabled; |         $this->is_debug_enabled = $this->application->settings->is_debug_enabled; | ||||||
| @@ -183,16 +184,10 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // Check custom port
 |         // Check custom port
 | ||||||
|         preg_match('/(?<=:)\d+(?=\/)/', $this->application->git_repository, $matches); |         ['repository' => $this->customRepository, 'port' => $this->customPort] = $this->application->customRepository(); | ||||||
|         if (count($matches) === 1) { | 
 | ||||||
|             $this->customPort = $matches[0]; |  | ||||||
|             $gitHost = str($this->application->git_repository)->before(':'); |  | ||||||
|             $gitRepo = str($this->application->git_repository)->after('/'); |  | ||||||
|             $this->customRepository = "$gitHost:$gitRepo"; |  | ||||||
|         } else { |  | ||||||
|             $this->customRepository = $this->application->git_repository; |  | ||||||
|         } |  | ||||||
|         try { |         try { | ||||||
|  |             ray($this->application->build_pack); | ||||||
|             if ($this->restart_only && $this->application->build_pack !== 'dockerimage') { |             if ($this->restart_only && $this->application->build_pack !== 'dockerimage') { | ||||||
|                 $this->just_restart(); |                 $this->just_restart(); | ||||||
|                 if ($this->server->isProxyShouldRun()) { |                 if ($this->server->isProxyShouldRun()) { | ||||||
| @@ -203,6 +198,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted | |||||||
|                 return; |                 return; | ||||||
|             } else if ($this->application->dockerfile) { |             } else if ($this->application->dockerfile) { | ||||||
|                 $this->deploy_simple_dockerfile(); |                 $this->deploy_simple_dockerfile(); | ||||||
|  |             } else if ($this->application->build_pack === 'dockercompose') { | ||||||
|  |                 $this->deploy_docker_compose_buildpack(); | ||||||
|             } else if ($this->application->build_pack === 'dockerimage') { |             } else if ($this->application->build_pack === 'dockerimage') { | ||||||
|                 $this->deploy_dockerimage_buildpack(); |                 $this->deploy_dockerimage_buildpack(); | ||||||
|             } else if ($this->application->build_pack === 'dockerfile') { |             } else if ($this->application->build_pack === 'dockerfile') { | ||||||
| @@ -397,19 +394,20 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted | |||||||
|             ]); |             ]); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     // private function save_environment_variables()
 |     private function save_environment_variables() | ||||||
|     // {
 |     { | ||||||
|     //     $envs = collect([]);
 |         $envs = collect([]); | ||||||
|     //     foreach ($this->application->environment_variables as $env) {
 |         foreach ($this->application->environment_variables as $env) { | ||||||
|     //         $envs->push($env->key . '=' . $env->value);
 |             $envs->push($env->key . '=' . $env->value); | ||||||
|     //     }
 |         } | ||||||
|     //     $envs_base64 = base64_encode($envs->implode("\n"));
 |         $envs_base64 = base64_encode($envs->implode("\n")); | ||||||
|     //     $this->execute_remote_command(
 |         $this->execute_remote_command( | ||||||
|     //         [
 |             [ | ||||||
|     //             executeInDocker($this->deployment_uuid, "echo '$envs_base64' | base64 -d > $this->workdir/.env")
 |                 executeInDocker($this->deployment_uuid, "echo '$envs_base64' | base64 -d > $this->workdir/.env") | ||||||
|     //         ],
 |             ], | ||||||
|     //     );
 |         ); | ||||||
|     // }
 |     } | ||||||
|  | 
 | ||||||
|     private function deploy_simple_dockerfile() |     private function deploy_simple_dockerfile() | ||||||
|     { |     { | ||||||
|         $dockerfile_base64 = base64_encode($this->application->dockerfile); |         $dockerfile_base64 = base64_encode($this->application->dockerfile); | ||||||
| @@ -447,7 +445,36 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted | |||||||
|         $this->generate_compose_file(); |         $this->generate_compose_file(); | ||||||
|         $this->rolling_update(); |         $this->rolling_update(); | ||||||
|     } |     } | ||||||
|  |     private function deploy_docker_compose_buildpack() | ||||||
|  |     { | ||||||
|  |         if (data_get($this->application, 'docker_compose_location')) { | ||||||
|  |             $this->docker_compose_location = $this->application->docker_compose_location; | ||||||
|  |         } | ||||||
|  |         $this->application_deployment_queue->addLogEntry("Starting deployment of {$this->application->name}."); | ||||||
| 
 | 
 | ||||||
|  |         $this->server->executeRemoteCommand( | ||||||
|  |             commands: $this->application->prepareHelperImage($this->deployment_uuid), | ||||||
|  |             loggingModel: $this->application_deployment_queue | ||||||
|  |         ); | ||||||
|  |         $this->check_git_if_build_needed(); | ||||||
|  |         $this->clone_repository(); | ||||||
|  |         $this->generate_image_names(); | ||||||
|  |         $this->cleanup_git(); | ||||||
|  |         $composeFile = $this->application->parseCompose(); | ||||||
|  |         $yaml = Yaml::dump($composeFile->toArray(), 10); | ||||||
|  |         $this->docker_compose_base64 = base64_encode($yaml); | ||||||
|  |         $this->execute_remote_command([ | ||||||
|  |             executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d > {$this->workdir}/docker-compose.yaml"), "hidden" => true | ||||||
|  |         ]); | ||||||
|  |         $this->execute_remote_command([ | ||||||
|  |             "docker network create --attachable '{$this->application->uuid}' >/dev/null || true", "hidden" => true, "ignore_errors" => true | ||||||
|  |         ], [ | ||||||
|  |             "docker network connect {$this->application->uuid} coolify-proxy || true", "hidden" => true, "ignore_errors" => true | ||||||
|  |         ]); | ||||||
|  |         $this->save_environment_variables(); | ||||||
|  |         $this->stop_running_container(force: true); | ||||||
|  |         $this->start_by_compose_file(); | ||||||
|  |     } | ||||||
|     private function deploy_dockerfile_buildpack() |     private function deploy_dockerfile_buildpack() | ||||||
|     { |     { | ||||||
|         if (data_get($this->application, 'dockerfile_location')) { |         if (data_get($this->application, 'dockerfile_location')) { | ||||||
| @@ -472,7 +499,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted | |||||||
|         //     $this->push_to_docker_registry();
 |         //     $this->push_to_docker_registry();
 | ||||||
|         //     $this->deploy_to_additional_destinations();
 |         //     $this->deploy_to_additional_destinations();
 | ||||||
|         // } else {
 |         // } else {
 | ||||||
|             $this->rolling_update(); |         $this->rolling_update(); | ||||||
|         // }
 |         // }
 | ||||||
|     } |     } | ||||||
|     private function deploy_nixpacks_buildpack() |     private function deploy_nixpacks_buildpack() | ||||||
| @@ -725,6 +752,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted | |||||||
|     private function clone_repository() |     private function clone_repository() | ||||||
|     { |     { | ||||||
|         $importCommands = $this->generate_git_import_commands(); |         $importCommands = $this->generate_git_import_commands(); | ||||||
|  |         ray($importCommands); | ||||||
|         $this->execute_remote_command( |         $this->execute_remote_command( | ||||||
|             [ |             [ | ||||||
|                 "echo '\n----------------------------------------'", |                 "echo '\n----------------------------------------'", | ||||||
| @@ -740,90 +768,14 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted | |||||||
| 
 | 
 | ||||||
|     private function generate_git_import_commands() |     private function generate_git_import_commands() | ||||||
|     { |     { | ||||||
|         $this->branch = $this->application->git_branch; |  | ||||||
|         $commands = collect([]); |  | ||||||
|         $git_clone_command = "git clone -q -b {$this->application->git_branch}"; |  | ||||||
|         if ($this->pull_request_id !== 0) { |  | ||||||
|             $pr_branch_name = "pr-{$this->pull_request_id}-coolify"; |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         if ($this->application->deploymentType() === 'source') { |         ['commands' => $commands, 'branch' => $this->branch, 'fullRepoUrl' => $this->fullRepoUrl] = $this->application->generateGitImportCommands($this->deployment_uuid, $this->pull_request_id, $this->git_type); | ||||||
|             $source_html_url = data_get($this->application, 'source.html_url'); |         return $commands; | ||||||
|             $url = parse_url(filter_var($source_html_url, FILTER_SANITIZE_URL)); |  | ||||||
|             $source_html_url_host = $url['host']; |  | ||||||
|             $source_html_url_scheme = $url['scheme']; |  | ||||||
| 
 |  | ||||||
|             if ($this->source->getMorphClass() == 'App\Models\GithubApp') { |  | ||||||
|                 if ($this->source->is_public) { |  | ||||||
|                     $this->fullRepoUrl = "{$this->source->html_url}/{$this->customRepository}"; |  | ||||||
|                     $git_clone_command = "{$git_clone_command} {$this->source->html_url}/{$this->customRepository} {$this->basedir}"; |  | ||||||
|                     $git_clone_command = $this->set_git_import_settings($git_clone_command); |  | ||||||
| 
 |  | ||||||
|                     $commands->push(executeInDocker($this->deployment_uuid, $git_clone_command)); |  | ||||||
|                 } else { |  | ||||||
|                     $github_access_token = generate_github_installation_token($this->source); |  | ||||||
|                     $commands->push(executeInDocker($this->deployment_uuid, "git clone -q -b {$this->application->git_branch} $source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$this->customRepository}.git {$this->basedir}")); |  | ||||||
|                     $this->fullRepoUrl = "$source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$this->customRepository}.git"; |  | ||||||
|                 } |  | ||||||
|                 if ($this->pull_request_id !== 0) { |  | ||||||
|                     $this->branch = "pull/{$this->pull_request_id}/head:$pr_branch_name"; |  | ||||||
|                     $commands->push(executeInDocker($this->deployment_uuid, "cd {$this->basedir} && git fetch origin $this->branch && git checkout $pr_branch_name")); |  | ||||||
|                 } |  | ||||||
|                 return $commands->implode(' && '); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         if ($this->application->deploymentType() === 'deploy_key') { |  | ||||||
|             $this->fullRepoUrl = $this->customRepository; |  | ||||||
|             $private_key = data_get($this->application, 'private_key.private_key'); |  | ||||||
|             if (is_null($private_key)) { |  | ||||||
|                 throw new RuntimeException('Private key not found. Please add a private key to the application and try again.'); |  | ||||||
|             } |  | ||||||
|             $private_key = base64_encode($private_key); |  | ||||||
|             $git_clone_command_base = "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$this->customPort} -o Port={$this->customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" {$git_clone_command} {$this->customRepository} {$this->basedir}"; |  | ||||||
|             $git_clone_command = $this->set_git_import_settings($git_clone_command_base); |  | ||||||
|             $commands = collect([ |  | ||||||
|                 executeInDocker($this->deployment_uuid, "mkdir -p /root/.ssh"), |  | ||||||
|                 executeInDocker($this->deployment_uuid, "echo '{$private_key}' | base64 -d > /root/.ssh/id_rsa"), |  | ||||||
|                 executeInDocker($this->deployment_uuid, "chmod 600 /root/.ssh/id_rsa"), |  | ||||||
|             ]); |  | ||||||
|             if ($this->pull_request_id !== 0) { |  | ||||||
|                 ray($this->git_type); |  | ||||||
|                 if ($this->git_type === 'gitlab') { |  | ||||||
|                     $this->branch = "merge-requests/{$this->pull_request_id}/head:$pr_branch_name"; |  | ||||||
|                     $commands->push(executeInDocker($this->deployment_uuid, "echo 'Checking out $this->branch'")); |  | ||||||
|                     $git_clone_command = "{$git_clone_command} && cd {$this->basedir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$this->customPort} -o Port={$this->customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $this->branch && git checkout $pr_branch_name"; |  | ||||||
|                 } |  | ||||||
|                 if ($this->git_type === 'github') { |  | ||||||
|                     $this->branch = "pull/{$this->pull_request_id}/head:$pr_branch_name"; |  | ||||||
|                     $commands->push(executeInDocker($this->deployment_uuid, "echo 'Checking out $this->branch'")); |  | ||||||
|                     $git_clone_command = "{$git_clone_command} && cd {$this->basedir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$this->customPort} -o Port={$this->customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $this->branch && git checkout $pr_branch_name"; |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             $commands->push(executeInDocker($this->deployment_uuid, $git_clone_command)); |  | ||||||
|             return $commands->implode(' && '); |  | ||||||
|         } |  | ||||||
|         if ($this->application->deploymentType() === 'other') { |  | ||||||
|             $this->fullRepoUrl = $this->customRepository; |  | ||||||
|             $git_clone_command = "{$git_clone_command} {$this->customRepository} {$this->basedir}"; |  | ||||||
|             $git_clone_command = $this->set_git_import_settings($git_clone_command); |  | ||||||
|             $commands->push(executeInDocker($this->deployment_uuid, $git_clone_command)); |  | ||||||
|             return $commands->implode(' && '); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private function set_git_import_settings($git_clone_command) |     private function set_git_import_settings($git_clone_command) | ||||||
|     { |     { | ||||||
|         if ($this->application->git_commit_sha !== 'HEAD') { |         return $this->application->setGitImportSettings($this->deployment_uuid, $git_clone_command); | ||||||
|             $git_clone_command = "{$git_clone_command} && cd {$this->basedir} && git -c advice.detachedHead=false checkout {$this->application->git_commit_sha} >/dev/null 2>&1"; |  | ||||||
|         } |  | ||||||
|         if ($this->application->settings->is_git_submodules_enabled) { |  | ||||||
|             $git_clone_command = "{$git_clone_command} && cd {$this->basedir} && git submodule update --init --recursive"; |  | ||||||
|         } |  | ||||||
|         if ($this->application->settings->is_git_lfs_enabled) { |  | ||||||
|             $git_clone_command = "{$git_clone_command} && cd {$this->basedir} && git lfs pull"; |  | ||||||
|         } |  | ||||||
|         return $git_clone_command; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private function cleanup_git() |     private function cleanup_git() | ||||||
| @@ -879,6 +831,26 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted | |||||||
|         $this->env_args = $this->env_args->implode(' '); |         $this->env_args = $this->env_args->implode(' '); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     private function modify_compose_file() | ||||||
|  |     { | ||||||
|  |         // ray("{$this->workdir}{$this->docker_compose_location}");
 | ||||||
|  |         $this->execute_remote_command([executeInDocker($this->deployment_uuid, "cat {$this->workdir}{$this->docker_compose_location}"), "hidden" => true, "save" => 'compose_file']); | ||||||
|  |         if ($this->saved_outputs->get('compose_file')) { | ||||||
|  |             $compose = $this->saved_outputs->get('compose_file'); | ||||||
|  |         } | ||||||
|  |         try { | ||||||
|  |             $yaml = Yaml::parse($compose); | ||||||
|  |         } catch (\Exception $e) { | ||||||
|  |             throw new \Exception($e->getMessage()); | ||||||
|  |         } | ||||||
|  |         $services = data_get($yaml, 'services'); | ||||||
|  |         $topLevelNetworks = collect(data_get($yaml, 'networks', [])); | ||||||
|  |         $definedNetwork = collect([$this->application->uuid]); | ||||||
|  | 
 | ||||||
|  |         $services = collect($services)->map(function ($service, $serviceName) use ($topLevelNetworks, $definedNetwork) { | ||||||
|  |             $serviceNetworks = collect(data_get($service, 'networks', [])); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|     private function generate_compose_file() |     private function generate_compose_file() | ||||||
|     { |     { | ||||||
|         $ports = $this->application->settings->is_static ? [80] : $this->application->ports_exposes_array; |         $ports = $this->application->settings->is_static ? [80] : $this->application->ports_exposes_array; | ||||||
| @@ -1209,6 +1181,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); | |||||||
|         $this->execute_remote_command(["echo -n 'Removing old container.'"]); |         $this->execute_remote_command(["echo -n 'Removing old container.'"]); | ||||||
|         if ($this->newVersionIsHealthy || $force) { |         if ($this->newVersionIsHealthy || $force) { | ||||||
|             $containers = getCurrentApplicationContainerStatus($this->server, $this->application->id, $this->pull_request_id); |             $containers = getCurrentApplicationContainerStatus($this->server, $this->application->id, $this->pull_request_id); | ||||||
|  |             ray($containers); | ||||||
|             if ($this->pull_request_id !== 0) { |             if ($this->pull_request_id !== 0) { | ||||||
|                 $containers = $containers->filter(function ($container) { |                 $containers = $containers->filter(function ($container) { | ||||||
|                     return data_get($container, 'Names') === $this->container_name; |                     return data_get($container, 'Names') === $this->container_name; | ||||||
|   | |||||||
| @@ -6,6 +6,8 @@ use Illuminate\Database\Eloquent\Casts\Attribute; | |||||||
| use Illuminate\Database\Eloquent\Relations\HasMany; | use Illuminate\Database\Eloquent\Relations\HasMany; | ||||||
| use Spatie\Activitylog\Models\Activity; | use Spatie\Activitylog\Models\Activity; | ||||||
| use Illuminate\Support\Str; | use Illuminate\Support\Str; | ||||||
|  | use RuntimeException; | ||||||
|  | use Symfony\Component\Yaml\Yaml; | ||||||
| 
 | 
 | ||||||
| class Application extends BaseModel | class Application extends BaseModel | ||||||
| { | { | ||||||
| @@ -123,6 +125,21 @@ class Application extends BaseModel | |||||||
|             } |             } | ||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
|  |     public function dockerComposeLocation(): Attribute | ||||||
|  |     { | ||||||
|  |         return Attribute::make( | ||||||
|  |             set: function ($value) { | ||||||
|  |                 if (is_null($value) || $value === '') { | ||||||
|  |                     return '/docker-compose.yml'; | ||||||
|  |                 } else { | ||||||
|  |                     if ($value !== '/') { | ||||||
|  |                         return Str::start(Str::replaceEnd('/', '', $value), '/'); | ||||||
|  |                     } | ||||||
|  |                     return Str::start($value, '/'); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|     public function baseDirectory(): Attribute |     public function baseDirectory(): Attribute | ||||||
|     { |     { | ||||||
|         return Attribute::make( |         return Attribute::make( | ||||||
| @@ -157,7 +174,16 @@ class Application extends BaseModel | |||||||
|                 : explode(',', $this->ports_exposes) |                 : explode(',', $this->ports_exposes) | ||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
| 
 |     public function serviceType() | ||||||
|  |     { | ||||||
|  |         $found = str(collect(SPECIFIC_SERVICES)->filter(function ($service) { | ||||||
|  |             return str($this->image)->before(':')->value() === $service; | ||||||
|  |         })->first()); | ||||||
|  |         if ($found->isNotEmpty()) { | ||||||
|  |             return $found; | ||||||
|  |         } | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|     public function environment_variables(): HasMany |     public function environment_variables(): HasMany | ||||||
|     { |     { | ||||||
|         return $this->hasMany(EnvironmentVariable::class)->where('is_preview', false)->orderBy('key', 'asc'); |         return $this->hasMany(EnvironmentVariable::class)->where('is_preview', false)->orderBy('key', 'asc'); | ||||||
| @@ -342,7 +368,8 @@ class Application extends BaseModel | |||||||
|         } |         } | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
|     public function healthCheckUrl() { |     public function healthCheckUrl() | ||||||
|  |     { | ||||||
|         if ($this->dockerfile || $this->build_pack === 'dockerfile' || $this->build_pack === 'dockerimage') { |         if ($this->dockerfile || $this->build_pack === 'dockerfile' || $this->build_pack === 'dockerimage') { | ||||||
|             return null; |             return null; | ||||||
|         } |         } | ||||||
| @@ -358,4 +385,204 @@ class Application extends BaseModel | |||||||
|         } |         } | ||||||
|         return $full_healthcheck_url; |         return $full_healthcheck_url; | ||||||
|     } |     } | ||||||
|  |     function customRepository() | ||||||
|  |     { | ||||||
|  |         preg_match('/(?<=:)\d+(?=\/)/', $this->git_repository, $matches); | ||||||
|  |         $port = 22; | ||||||
|  |         if (count($matches) === 1) { | ||||||
|  |             $port = $matches[0]; | ||||||
|  |             $gitHost = str($this->git_repository)->before(':'); | ||||||
|  |             $gitRepo = str($this->git_repository)->after('/'); | ||||||
|  |             $repository = "$gitHost:$gitRepo"; | ||||||
|  |         } else { | ||||||
|  |             $repository = $this->git_repository; | ||||||
|  |         } | ||||||
|  |         return [ | ||||||
|  |             'repository' => $repository, | ||||||
|  |             'port' => $port | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  |     function generateBaseDir(string $uuid) | ||||||
|  |     { | ||||||
|  |         return "/artifacts/{$uuid}"; | ||||||
|  |     } | ||||||
|  |     function setGitImportSettings(string $deployment_uuid, string $git_clone_command) | ||||||
|  |     { | ||||||
|  |         $baseDir = $this->generateBaseDir($deployment_uuid); | ||||||
|  |         if ($this->git_commit_sha !== 'HEAD') { | ||||||
|  |             $git_clone_command = "{$git_clone_command} && cd {$baseDir} && git -c advice.detachedHead=false checkout {$this->git_commit_sha} >/dev/null 2>&1"; | ||||||
|  |         } | ||||||
|  |         if ($this->settings->is_git_submodules_enabled) { | ||||||
|  |             $git_clone_command = "{$git_clone_command} && cd {$baseDir} && git submodule update --init --recursive"; | ||||||
|  |         } | ||||||
|  |         if ($this->settings->is_git_lfs_enabled) { | ||||||
|  |             $git_clone_command = "{$git_clone_command} && cd {$baseDir} && git lfs pull"; | ||||||
|  |         } | ||||||
|  |         return $git_clone_command; | ||||||
|  |     } | ||||||
|  |     function generateGitImportCommands(string $deployment_uuid, int $pull_request_id = 0, ?string $git_type = null, bool $exec_in_docker = true, bool $only_checkout = false, ?string $custom_base_dir = null) | ||||||
|  |     { | ||||||
|  |         $branch = $this->git_branch; | ||||||
|  |         ['repository' => $customRepository, 'port' => $customPort] = $this->customRepository(); | ||||||
|  |         $baseDir = $custom_base_dir ?? $this->generateBaseDir($deployment_uuid); | ||||||
|  |         $commands = collect([]); | ||||||
|  |         $git_clone_command = "git clone -b {$this->git_branch}"; | ||||||
|  |         if ($only_checkout) { | ||||||
|  |             $git_clone_command = "git clone --no-checkout -b {$this->git_branch}"; | ||||||
|  |         } | ||||||
|  |         if ($pull_request_id !== 0) { | ||||||
|  |             $pr_branch_name = "pr-{$pull_request_id}-coolify"; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if ($this->deploymentType() === 'source') { | ||||||
|  |             $source_html_url = data_get($this, 'source.html_url'); | ||||||
|  |             $url = parse_url(filter_var($source_html_url, FILTER_SANITIZE_URL)); | ||||||
|  |             $source_html_url_host = $url['host']; | ||||||
|  |             $source_html_url_scheme = $url['scheme']; | ||||||
|  | 
 | ||||||
|  |             if ($this->source->getMorphClass() == 'App\Models\GithubApp') { | ||||||
|  |                 if ($this->source->is_public) { | ||||||
|  |                     $fullRepoUrl = "{$this->source->html_url}/{$customRepository}"; | ||||||
|  |                     $git_clone_command = "{$git_clone_command} {$this->source->html_url}/{$customRepository} {$baseDir}"; | ||||||
|  |                     if (!$only_checkout) { | ||||||
|  |                         $git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command); | ||||||
|  |                     } | ||||||
|  |                     if ($exec_in_docker) { | ||||||
|  |                         $commands->push(executeInDocker($deployment_uuid, $git_clone_command)); | ||||||
|  |                     } else { | ||||||
|  |                         $commands->push($git_clone_command); | ||||||
|  |                     } | ||||||
|  |                 } else { | ||||||
|  |                     $github_access_token = generate_github_installation_token($this->source); | ||||||
|  |                     if ($exec_in_docker) { | ||||||
|  |                         $commands->push(executeInDocker($deployment_uuid, "{$git_clone_command} $source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository}.git {$baseDir}")); | ||||||
|  |                         $fullRepoUrl = "$source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository}.git"; | ||||||
|  |                     } else { | ||||||
|  |                         $commands->push("{$git_clone_command} $source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository} {$baseDir}"); | ||||||
|  |                         $fullRepoUrl = "$source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository}"; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 if ($pull_request_id !== 0) { | ||||||
|  |                     $branch = "pull/{$pull_request_id}/head:$pr_branch_name"; | ||||||
|  |                     if ($exec_in_docker) { | ||||||
|  |                         $commands->push(executeInDocker($deployment_uuid, "cd {$baseDir} && git fetch origin {$branch} && git checkout $pr_branch_name")); | ||||||
|  |                     } else { | ||||||
|  |                         $commands->push("cd {$baseDir} && git fetch origin {$branch} && git checkout $pr_branch_name"); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 return [ | ||||||
|  |                     'commands' => $commands->implode(' && '), | ||||||
|  |                     'branch' => $branch, | ||||||
|  |                     'fullRepoUrl' => $fullRepoUrl | ||||||
|  |                 ]; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         if ($this->deploymentType() === 'deploy_key') { | ||||||
|  |             $fullRepoUrl = $customRepository; | ||||||
|  |             $private_key = data_get($this, 'private_key.private_key'); | ||||||
|  |             if (is_null($private_key)) { | ||||||
|  |                 throw new RuntimeException('Private key not found. Please add a private key to the application and try again.'); | ||||||
|  |             } | ||||||
|  |             $private_key = base64_encode($private_key); | ||||||
|  |             $git_clone_command_base = "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" {$git_clone_command} {$customRepository} {$baseDir}"; | ||||||
|  |             if (!$only_checkout) { | ||||||
|  |                 $git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command_base); | ||||||
|  |             } | ||||||
|  |             if ($exec_in_docker) { | ||||||
|  |                 $commands = collect([ | ||||||
|  |                     executeInDocker($deployment_uuid, "mkdir -p /root/.ssh"), | ||||||
|  |                     executeInDocker($deployment_uuid, "echo '{$private_key}' | base64 -d > /root/.ssh/id_rsa"), | ||||||
|  |                     executeInDocker($deployment_uuid, "chmod 600 /root/.ssh/id_rsa"), | ||||||
|  |                 ]); | ||||||
|  |             } else { | ||||||
|  |                 $commands = collect([ | ||||||
|  |                     "mkdir -p /root/.ssh", | ||||||
|  |                     "echo '{$private_key}' | base64 -d > /root/.ssh/id_rsa", | ||||||
|  |                     "chmod 600 /root/.ssh/id_rsa", | ||||||
|  |                 ]); | ||||||
|  |             } | ||||||
|  |             if ($pull_request_id !== 0) { | ||||||
|  |                 if ($git_type === 'gitlab') { | ||||||
|  |                     $branch = "merge-requests/{$pull_request_id}/head:$pr_branch_name"; | ||||||
|  |                     if ($exec_in_docker) { | ||||||
|  |                         $commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'")); | ||||||
|  |                     } else { | ||||||
|  |                         $commands->push("echo 'Checking out $branch'"); | ||||||
|  |                     } | ||||||
|  |                     $git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && git checkout $pr_branch_name"; | ||||||
|  |                 } | ||||||
|  |                 if ($git_type === 'github') { | ||||||
|  |                     $branch = "pull/{$pull_request_id}/head:$pr_branch_name"; | ||||||
|  |                     if ($exec_in_docker) { | ||||||
|  |                         $commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'")); | ||||||
|  |                     } else { | ||||||
|  |                         $commands->push("echo 'Checking out $branch'"); | ||||||
|  |                     } | ||||||
|  |                     $git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && git checkout $pr_branch_name"; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if ($exec_in_docker) { | ||||||
|  |                 $commands->push(executeInDocker($deployment_uuid, $git_clone_command)); | ||||||
|  |             } else { | ||||||
|  |                 $commands->push($git_clone_command); | ||||||
|  |             } | ||||||
|  |             return [ | ||||||
|  |                 'commands' => $commands->implode(' && '), | ||||||
|  |                 'branch' => $branch, | ||||||
|  |                 'fullRepoUrl' => $fullRepoUrl | ||||||
|  |             ]; | ||||||
|  |         } | ||||||
|  |         if ($this->deploymentType() === 'other') { | ||||||
|  |             $fullRepoUrl = $customRepository; | ||||||
|  |             $git_clone_command = "{$git_clone_command} {$customRepository} {$baseDir}"; | ||||||
|  |             $git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command); | ||||||
|  |             if ($exec_in_docker) { | ||||||
|  |                 $commands->push(executeInDocker($deployment_uuid, $git_clone_command)); | ||||||
|  |             } else { | ||||||
|  |                 $commands->push($git_clone_command); | ||||||
|  |             } | ||||||
|  |             return [ | ||||||
|  |                 'commands' => $commands->implode(' && '), | ||||||
|  |                 'branch' => $branch, | ||||||
|  |                 'fullRepoUrl' => $fullRepoUrl | ||||||
|  |             ]; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     public function prepareHelperImage(string $deploymentUuid) | ||||||
|  |     { | ||||||
|  |         $basedir = $this->generateBaseDir($deploymentUuid); | ||||||
|  |         $helperImage = config('coolify.helper_image'); | ||||||
|  |         $server = data_get($this, 'destination.server'); | ||||||
|  |         $network = data_get($this, 'destination.network'); | ||||||
|  | 
 | ||||||
|  |         $serverUserHomeDir = instant_remote_process(["echo \$HOME"], $server); | ||||||
|  |         $dockerConfigFileExists = instant_remote_process(["test -f {$serverUserHomeDir}/.docker/config.json && echo 'OK' || echo 'NOK'"], $server); | ||||||
|  | 
 | ||||||
|  |         $commands = collect([]); | ||||||
|  |         if ($dockerConfigFileExists === 'OK') { | ||||||
|  |             $commands->push([ | ||||||
|  |                 "command" => "docker run -d --network $network -v /:/host --name $deploymentUuid --rm -v {$serverUserHomeDir}/.docker/config.json:/root/.docker/config.json:ro -v /var/run/docker.sock:/var/run/docker.sock $helperImage", | ||||||
|  |                 "hidden" => true, | ||||||
|  |             ]); | ||||||
|  |         } else { | ||||||
|  |             $commands->push([ | ||||||
|  |                 "command" => "docker run -d --network {$network} -v /:/host --name {$deploymentUuid} --rm -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}", | ||||||
|  |                 "hidden" => true, | ||||||
|  |             ]); | ||||||
|  |         } | ||||||
|  |         $commands->push([ | ||||||
|  |             "command" => executeInDocker($deploymentUuid, "mkdir -p {$basedir}"), | ||||||
|  |             "hidden" => true, | ||||||
|  |         ]); | ||||||
|  |         return $commands; | ||||||
|  |     } | ||||||
|  |     function parseCompose() | ||||||
|  |     { | ||||||
|  |         if ($this->docker_compose_raw) { | ||||||
|  |             return parseDockerComposeFile($this); | ||||||
|  |         } else { | ||||||
|  |             return collect([]); | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -392,7 +392,7 @@ class Server extends BaseModel | |||||||
|     { |     { | ||||||
|         return instant_remote_process(["docker network create coolify --attachable >/dev/null 2>&1 || true"], $this, false); |         return instant_remote_process(["docker network create coolify --attachable >/dev/null 2>&1 || true"], $this, false); | ||||||
|     } |     } | ||||||
|     public function executeRemoteCommand(Collection $commands, ApplicationDeploymentQueue $loggingModel) |     public function executeRemoteCommand(Collection $commands, ?ApplicationDeploymentQueue $loggingModel = null) | ||||||
|     { |     { | ||||||
|         static::$batch_counter++; |         static::$batch_counter++; | ||||||
|         foreach ($commands as $command) { |         foreach ($commands as $command) { | ||||||
| @@ -419,33 +419,35 @@ class Server extends BaseModel | |||||||
|                     'hidden' => $hidden, |                     'hidden' => $hidden, | ||||||
|                     'batch' => static::$batch_counter, |                     'batch' => static::$batch_counter, | ||||||
|                 ]; |                 ]; | ||||||
|                 if (!$loggingModel->logs) { |                 if ($loggingModel) { | ||||||
|                     $newLogEntry['order'] = 1; |                     if (!$loggingModel->logs) { | ||||||
|                 } else { |                         $newLogEntry['order'] = 1; | ||||||
|                     $previousLogs = json_decode($loggingModel->logs, associative: true, flags: JSON_THROW_ON_ERROR); |                     } else { | ||||||
|                     $newLogEntry['order'] = count($previousLogs) + 1; |                         $previousLogs = json_decode($loggingModel->logs, associative: true, flags: JSON_THROW_ON_ERROR); | ||||||
|                 } |                         $newLogEntry['order'] = count($previousLogs) + 1; | ||||||
|                 if ($name) { |                     } | ||||||
|                     $newLogEntry['name'] = $name; |                     if ($name) { | ||||||
|                 } |                         $newLogEntry['name'] = $name; | ||||||
|  |                     } | ||||||
| 
 | 
 | ||||||
|                 $previousLogs[] = $newLogEntry; |                     $previousLogs[] = $newLogEntry; | ||||||
|                 $loggingModel->logs = json_encode($previousLogs, flags: JSON_THROW_ON_ERROR); |                     $loggingModel->logs = json_encode($previousLogs, flags: JSON_THROW_ON_ERROR); | ||||||
|                 $loggingModel->save(); |                     $loggingModel->save(); | ||||||
|                 // if ($name) {
 |                 } | ||||||
|                 //     $loggingModel['savedOutputs'][$name] = str($output)->trim();
 |  | ||||||
|                 // }
 |  | ||||||
|             }); |             }); | ||||||
|             $loggingModel->update([ |             if ($loggingModel) { | ||||||
|                 'current_process_id' => $process->id(), |                 $loggingModel->update([ | ||||||
|             ]); |                     'current_process_id' => $process->id(), | ||||||
| 
 |                 ]); | ||||||
|  |             } | ||||||
|             $processResult = $process->wait(); |             $processResult = $process->wait(); | ||||||
|             if ($processResult->exitCode() !== 0) { |             if ($processResult->exitCode() !== 0) { | ||||||
|                 if (!$ignoreErrors) { |                 if (!$ignoreErrors) { | ||||||
|                     $status = ApplicationDeploymentStatus::FAILED->value; |                     if ($loggingModel) { | ||||||
|                     $loggingModel->status = $status; |                         $status = ApplicationDeploymentStatus::FAILED->value; | ||||||
|                     $loggingModel->save(); |                         $loggingModel->status = $status; | ||||||
|  |                         $loggingModel->save(); | ||||||
|  |                     } | ||||||
|                     throw new \RuntimeException($processResult->errorOutput()); |                     throw new \RuntimeException($processResult->errorOutput()); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|   | |||||||
| @@ -371,521 +371,6 @@ class Service extends BaseModel | |||||||
| 
 | 
 | ||||||
|     public function parse(bool $isNew = false): Collection |     public function parse(bool $isNew = false): Collection | ||||||
|     { |     { | ||||||
|         // ray()->clearAll();
 |         return parseDockerComposeFile($this, $isNew); | ||||||
|         if ($this->docker_compose_raw) { |  | ||||||
|             try { |  | ||||||
|                 $yaml = Yaml::parse($this->docker_compose_raw); |  | ||||||
|             } catch (\Exception $e) { |  | ||||||
|                 throw new \Exception($e->getMessage()); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             $topLevelVolumes = collect(data_get($yaml, 'volumes', [])); |  | ||||||
|             $topLevelNetworks = collect(data_get($yaml, 'networks', [])); |  | ||||||
|             $dockerComposeVersion = data_get($yaml, 'version') ?? '3.8'; |  | ||||||
|             $services = data_get($yaml, 'services'); |  | ||||||
| 
 |  | ||||||
|             $generatedServiceFQDNS = collect([]); |  | ||||||
|             if (is_null($this->destination)) { |  | ||||||
|                 $destination = $this->server->destinations()->first(); |  | ||||||
|                 if ($destination) { |  | ||||||
|                     $this->destination()->associate($destination); |  | ||||||
|                     $this->save(); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             $definedNetwork = collect([$this->uuid]); |  | ||||||
| 
 |  | ||||||
|             $services = collect($services)->map(function ($service, $serviceName) use ($topLevelVolumes, $topLevelNetworks, $definedNetwork, $isNew, $generatedServiceFQDNS) { |  | ||||||
|                 $serviceVolumes = collect(data_get($service, 'volumes', [])); |  | ||||||
|                 $servicePorts = collect(data_get($service, 'ports', [])); |  | ||||||
|                 $serviceNetworks = collect(data_get($service, 'networks', [])); |  | ||||||
|                 $serviceVariables = collect(data_get($service, 'environment', [])); |  | ||||||
|                 $serviceLabels = collect(data_get($service, 'labels', [])); |  | ||||||
|                 if ($serviceLabels->count() > 0) { |  | ||||||
|                     $removedLabels = collect([]); |  | ||||||
|                     $serviceLabels = $serviceLabels->filter(function ($serviceLabel, $serviceLabelName) use ($removedLabels) { |  | ||||||
|                         if (!str($serviceLabel)->contains('=')) { |  | ||||||
|                             $removedLabels->put($serviceLabelName, $serviceLabel); |  | ||||||
|                             return false; |  | ||||||
|                         } |  | ||||||
|                         return $serviceLabel; |  | ||||||
|                     }); |  | ||||||
|                     foreach($removedLabels as $removedLabelName =>$removedLabel) { |  | ||||||
|                         $serviceLabels->push("$removedLabelName=$removedLabel"); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 $containerName = "$serviceName-{$this->uuid}"; |  | ||||||
| 
 |  | ||||||
|                 // Decide if the service is a database
 |  | ||||||
|                 $isDatabase = false; |  | ||||||
|                 $image = data_get_str($service, 'image'); |  | ||||||
|                 if ($image->contains(':')) { |  | ||||||
|                     $image = Str::of($image); |  | ||||||
|                 } else { |  | ||||||
|                     $image = Str::of($image)->append(':latest'); |  | ||||||
|                 } |  | ||||||
|                 $imageName = $image->before(':'); |  | ||||||
| 
 |  | ||||||
|                 if (collect(DATABASE_DOCKER_IMAGES)->contains($imageName)) { |  | ||||||
|                     $isDatabase = true; |  | ||||||
|                 } |  | ||||||
|                 data_set($service, 'is_database', $isDatabase); |  | ||||||
| 
 |  | ||||||
|                 // Create new serviceApplication or serviceDatabase
 |  | ||||||
|                 if ($isDatabase) { |  | ||||||
|                     if ($isNew) { |  | ||||||
|                         $savedService = ServiceDatabase::create([ |  | ||||||
|                             'name' => $serviceName, |  | ||||||
|                             'image' => $image, |  | ||||||
|                             'service_id' => $this->id |  | ||||||
|                         ]); |  | ||||||
|                     } else { |  | ||||||
|                         $savedService = ServiceDatabase::where([ |  | ||||||
|                             'name' => $serviceName, |  | ||||||
|                             'service_id' => $this->id |  | ||||||
|                         ])->first(); |  | ||||||
|                     } |  | ||||||
|                 } else { |  | ||||||
|                     if ($isNew) { |  | ||||||
|                         $savedService = ServiceApplication::create([ |  | ||||||
|                             'name' => $serviceName, |  | ||||||
|                             'image' => $image, |  | ||||||
|                             'service_id' => $this->id |  | ||||||
|                         ]); |  | ||||||
|                     } else { |  | ||||||
|                         $savedService = ServiceApplication::where([ |  | ||||||
|                             'name' => $serviceName, |  | ||||||
|                             'service_id' => $this->id |  | ||||||
|                         ])->first(); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|                 if (is_null($savedService)) { |  | ||||||
|                     if ($isDatabase) { |  | ||||||
|                         $savedService = ServiceDatabase::create([ |  | ||||||
|                             'name' => $serviceName, |  | ||||||
|                             'image' => $image, |  | ||||||
|                             'service_id' => $this->id |  | ||||||
|                         ]); |  | ||||||
|                     } else { |  | ||||||
|                         $savedService = ServiceApplication::create([ |  | ||||||
|                             'name' => $serviceName, |  | ||||||
|                             'image' => $image, |  | ||||||
|                             'service_id' => $this->id |  | ||||||
|                         ]); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 // Check if image changed
 |  | ||||||
|                 if ($savedService->image !== $image) { |  | ||||||
|                     $savedService->image = $image; |  | ||||||
|                     $savedService->save(); |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 // Collect/create/update networks
 |  | ||||||
|                 if ($serviceNetworks->count() > 0) { |  | ||||||
|                     foreach ($serviceNetworks as $networkName => $networkDetails) { |  | ||||||
|                         $networkExists = $topLevelNetworks->contains(function ($value, $key) use ($networkName) { |  | ||||||
|                             return $value == $networkName || $key == $networkName; |  | ||||||
|                         }); |  | ||||||
|                         if (!$networkExists) { |  | ||||||
|                             $topLevelNetworks->put($networkDetails, null); |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 // Collect/create/update ports
 |  | ||||||
|                 $collectedPorts = collect([]); |  | ||||||
|                 if ($servicePorts->count() > 0) { |  | ||||||
|                     foreach ($servicePorts as $sport) { |  | ||||||
|                         if (is_string($sport) || is_numeric($sport)) { |  | ||||||
|                             $collectedPorts->push($sport); |  | ||||||
|                         } |  | ||||||
|                         if (is_array($sport)) { |  | ||||||
|                             $target = data_get($sport, 'target'); |  | ||||||
|                             $published = data_get($sport, 'published'); |  | ||||||
|                             $protocol = data_get($sport, 'protocol'); |  | ||||||
|                             $collectedPorts->push("$target:$published/$protocol"); |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|                 $savedService->ports = $collectedPorts->implode(','); |  | ||||||
|                 $savedService->save(); |  | ||||||
| 
 |  | ||||||
|                 // Add Coolify specific networks
 |  | ||||||
|                 $definedNetworkExists = $topLevelNetworks->contains(function ($value, $_) use ($definedNetwork) { |  | ||||||
|                     return $value == $definedNetwork; |  | ||||||
|                 }); |  | ||||||
|                 if (!$definedNetworkExists) { |  | ||||||
|                     foreach ($definedNetwork as $network) { |  | ||||||
|                         $topLevelNetworks->put($network,  [ |  | ||||||
|                             'name' => $network, |  | ||||||
|                             'external' => true |  | ||||||
|                         ]); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|                 $networks = collect(); |  | ||||||
|                 foreach ($serviceNetworks as $key => $serviceNetwork) { |  | ||||||
|                     if (gettype($serviceNetwork) === 'string') { |  | ||||||
|                         // networks:
 |  | ||||||
|                         //  - appwrite
 |  | ||||||
|                         $networks->put($serviceNetwork, null); |  | ||||||
|                     } else if (gettype($serviceNetwork) === 'array') { |  | ||||||
|                         // networks:
 |  | ||||||
|                         //   default:
 |  | ||||||
|                         //     ipv4_address: 192.168.203.254
 |  | ||||||
|                         // $networks->put($serviceNetwork, null);
 |  | ||||||
|                         ray($key); |  | ||||||
|                         $networks->put($key, $serviceNetwork); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|                 foreach ($definedNetwork as $key => $network) { |  | ||||||
|                     $networks->put($network, null); |  | ||||||
|                 } |  | ||||||
|                 data_set($service, 'networks', $networks->toArray()); |  | ||||||
| 
 |  | ||||||
|                 // Collect/create/update volumes
 |  | ||||||
|                 if ($serviceVolumes->count() > 0) { |  | ||||||
|                     $serviceVolumes = $serviceVolumes->map(function ($volume) use ($savedService, $topLevelVolumes) { |  | ||||||
|                         $type = null; |  | ||||||
|                         $source = null; |  | ||||||
|                         $target = null; |  | ||||||
|                         $content = null; |  | ||||||
|                         $isDirectory = false; |  | ||||||
|                         if (is_string($volume)) { |  | ||||||
|                             $source = Str::of($volume)->before(':'); |  | ||||||
|                             $target = Str::of($volume)->after(':')->beforeLast(':'); |  | ||||||
|                             if ($source->startsWith('./') || $source->startsWith('/') || $source->startsWith('~')) { |  | ||||||
|                                 $type = Str::of('bind'); |  | ||||||
|                             } else { |  | ||||||
|                                 $type = Str::of('volume'); |  | ||||||
|                             } |  | ||||||
|                         } else if (is_array($volume)) { |  | ||||||
|                             $type = data_get_str($volume, 'type'); |  | ||||||
|                             $source = data_get_str($volume, 'source'); |  | ||||||
|                             $target = data_get_str($volume, 'target'); |  | ||||||
|                             $content = data_get($volume, 'content'); |  | ||||||
|                             $isDirectory = (bool) data_get($volume, 'isDirectory', false); |  | ||||||
|                             $foundConfig = $savedService->fileStorages()->whereMountPath($target)->first(); |  | ||||||
|                             if ($foundConfig) { |  | ||||||
|                                 $contentNotNull = data_get($foundConfig, 'content'); |  | ||||||
|                                 if ($contentNotNull) { |  | ||||||
|                                     $content = $contentNotNull; |  | ||||||
|                                 } |  | ||||||
|                                 $isDirectory = (bool) data_get($foundConfig, 'is_directory'); |  | ||||||
|                             } |  | ||||||
|                         } |  | ||||||
|                         if ($type->value() === 'bind') { |  | ||||||
|                             if ($source->value() === "/var/run/docker.sock") { |  | ||||||
|                                 return $volume; |  | ||||||
|                             } |  | ||||||
|                             if ($source->value() === '/tmp' || $source->value() === '/tmp/') { |  | ||||||
|                                 return $volume; |  | ||||||
|                             } |  | ||||||
|                             LocalFileVolume::updateOrCreate( |  | ||||||
|                                 [ |  | ||||||
|                                     'mount_path' => $target, |  | ||||||
|                                     'resource_id' => $savedService->id, |  | ||||||
|                                     'resource_type' => get_class($savedService) |  | ||||||
|                                 ], |  | ||||||
|                                 [ |  | ||||||
|                                     'fs_path' => $source, |  | ||||||
|                                     'mount_path' => $target, |  | ||||||
|                                     'content' => $content, |  | ||||||
|                                     'is_directory' => $isDirectory, |  | ||||||
|                                     'resource_id' => $savedService->id, |  | ||||||
|                                     'resource_type' => get_class($savedService) |  | ||||||
|                                 ] |  | ||||||
|                             ); |  | ||||||
|                         } else if ($type->value() === 'volume') { |  | ||||||
|                             $slugWithoutUuid = Str::slug($source, '-'); |  | ||||||
|                             $name = "{$savedService->service->uuid}_{$slugWithoutUuid}"; |  | ||||||
|                             if (is_string($volume)) { |  | ||||||
|                                 $source = Str::of($volume)->before(':'); |  | ||||||
|                                 $target = Str::of($volume)->after(':')->beforeLast(':'); |  | ||||||
|                                 $source = $name; |  | ||||||
|                                 $volume = "$source:$target"; |  | ||||||
|                             } else if (is_array($volume)) { |  | ||||||
|                                 data_set($volume, 'source', $name); |  | ||||||
|                             } |  | ||||||
|                             $topLevelVolumes->put($name, [ |  | ||||||
|                                 'name' => $name, |  | ||||||
|                             ]); |  | ||||||
|                             LocalPersistentVolume::updateOrCreate( |  | ||||||
|                                 [ |  | ||||||
|                                     'mount_path' => $target, |  | ||||||
|                                     'resource_id' => $savedService->id, |  | ||||||
|                                     'resource_type' => get_class($savedService) |  | ||||||
|                                 ], |  | ||||||
|                                 [ |  | ||||||
|                                     'name' => $name, |  | ||||||
|                                     'mount_path' => $target, |  | ||||||
|                                     'resource_id' => $savedService->id, |  | ||||||
|                                     'resource_type' => get_class($savedService) |  | ||||||
|                                 ] |  | ||||||
|                             ); |  | ||||||
|                         } |  | ||||||
|                         $savedService->getFilesFromServer(isInit: true); |  | ||||||
|                         return $volume; |  | ||||||
|                     }); |  | ||||||
|                     data_set($service, 'volumes', $serviceVolumes->toArray()); |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 // Add env_file with at least .env to the service
 |  | ||||||
|                 // $envFile = collect(data_get($service, 'env_file', []));
 |  | ||||||
|                 // if ($envFile->count() > 0) {
 |  | ||||||
|                 //     if (!$envFile->contains('.env')) {
 |  | ||||||
|                 //         $envFile->push('.env');
 |  | ||||||
|                 //     }
 |  | ||||||
|                 // } else {
 |  | ||||||
|                 //     $envFile = collect(['.env']);
 |  | ||||||
|                 // }
 |  | ||||||
|                 // data_set($service, 'env_file', $envFile->toArray());
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|                 // Get variables from the service
 |  | ||||||
|                 foreach ($serviceVariables as $variableName => $variable) { |  | ||||||
|                     if (is_numeric($variableName)) { |  | ||||||
|                         $variable = Str::of($variable); |  | ||||||
|                         if ($variable->contains('=')) { |  | ||||||
|                             // - SESSION_SECRET=123
 |  | ||||||
|                             // - SESSION_SECRET=
 |  | ||||||
|                             $key = $variable->before('='); |  | ||||||
|                             $value = $variable->after('='); |  | ||||||
|                         } else { |  | ||||||
|                             // - SESSION_SECRET
 |  | ||||||
|                             $key = $variable; |  | ||||||
|                             $value = null; |  | ||||||
|                         } |  | ||||||
|                     } else { |  | ||||||
|                         // SESSION_SECRET: 123
 |  | ||||||
|                         // SESSION_SECRET:
 |  | ||||||
|                         $key = Str::of($variableName); |  | ||||||
|                         $value = Str::of($variable); |  | ||||||
|                     } |  | ||||||
|                     // TODO: here is the problem
 |  | ||||||
|                     if ($key->startsWith('SERVICE_FQDN')) { |  | ||||||
|                         if ($isNew || $savedService->fqdn === null) { |  | ||||||
|                             $name = $key->after('SERVICE_FQDN_')->beforeLast('_')->lower(); |  | ||||||
|                             $fqdn = generateFqdn($this->server, "{$name->value()}-{$this->uuid}"); |  | ||||||
|                             if (substr_count($key->value(), '_') === 3) { |  | ||||||
|                                 // SERVICE_FQDN_UMAMI_1000
 |  | ||||||
|                                 $port = $key->afterLast('_'); |  | ||||||
|                             } else { |  | ||||||
|                                 // SERVICE_FQDN_UMAMI
 |  | ||||||
|                                 $port = null; |  | ||||||
|                             } |  | ||||||
|                             if ($port) { |  | ||||||
|                                 $fqdn = "$fqdn:$port"; |  | ||||||
|                             } |  | ||||||
|                             if (substr_count($key->value(), '_') >= 2) { |  | ||||||
|                                 if (is_null($value)) { |  | ||||||
|                                     $value = Str::of('/'); |  | ||||||
|                                 } |  | ||||||
|                                 $path = $value->value(); |  | ||||||
|                                 if ($generatedServiceFQDNS->count() > 0) { |  | ||||||
|                                     $alreadyGenerated = $generatedServiceFQDNS->has($key->value()); |  | ||||||
|                                     if ($alreadyGenerated) { |  | ||||||
|                                         $fqdn = $generatedServiceFQDNS->get($key->value()); |  | ||||||
|                                     } else { |  | ||||||
|                                         $generatedServiceFQDNS->put($key->value(), $fqdn); |  | ||||||
|                                     } |  | ||||||
|                                 } else { |  | ||||||
|                                     $generatedServiceFQDNS->put($key->value(), $fqdn); |  | ||||||
|                                 } |  | ||||||
|                                 $fqdn = "$fqdn$path"; |  | ||||||
|                             } |  | ||||||
| 
 |  | ||||||
|                             if (!$isDatabase) { |  | ||||||
|                                 if ($savedService->fqdn) { |  | ||||||
|                                     $fqdn = $savedService->fqdn . ',' . $fqdn; |  | ||||||
|                                 } else { |  | ||||||
|                                     $fqdn = $fqdn; |  | ||||||
|                                 } |  | ||||||
|                                 $savedService->fqdn = $fqdn; |  | ||||||
|                                 $savedService->save(); |  | ||||||
|                             } |  | ||||||
|                         } |  | ||||||
|                         // data_forget($service, "environment.$variableName");
 |  | ||||||
|                         // $yaml = data_forget($yaml, "services.$serviceName.environment.$variableName");
 |  | ||||||
|                         // if (count(data_get($yaml, 'services.' . $serviceName . '.environment')) === 0) {
 |  | ||||||
|                         //     $yaml = data_forget($yaml, "services.$serviceName.environment");
 |  | ||||||
|                         // }
 |  | ||||||
|                         continue; |  | ||||||
|                     } |  | ||||||
|                     if ($value?->startsWith('$')) { |  | ||||||
|                         $value = Str::of(replaceVariables($value)); |  | ||||||
|                         $key = $value; |  | ||||||
|                         $foundEnv = EnvironmentVariable::where([ |  | ||||||
|                             'key' => $key, |  | ||||||
|                             'service_id' => $this->id, |  | ||||||
|                         ])->first(); |  | ||||||
|                         if ($value->startsWith('SERVICE_')) { |  | ||||||
|                             // Count _ in $value
 |  | ||||||
|                             $count = substr_count($value->value(), '_'); |  | ||||||
|                             if ($count === 2) { |  | ||||||
|                                 // SERVICE_FQDN_UMAMI
 |  | ||||||
|                                 $command = $value->after('SERVICE_')->beforeLast('_'); |  | ||||||
|                                 $forService = $value->afterLast('_'); |  | ||||||
|                                 $generatedValue = null; |  | ||||||
|                                 $port = null; |  | ||||||
|                             } |  | ||||||
|                             if ($count === 3) { |  | ||||||
|                                 // SERVICE_FQDN_UMAMI_1000
 |  | ||||||
|                                 $command = $value->after('SERVICE_')->before('_'); |  | ||||||
|                                 $forService = $value->after('SERVICE_')->after('_')->before('_'); |  | ||||||
|                                 $generatedValue = null; |  | ||||||
|                                 $port = $value->afterLast('_'); |  | ||||||
|                             } |  | ||||||
|                             if ($command->value() === 'FQDN' || $command->value() === 'URL') { |  | ||||||
|                                 if (Str::lower($forService) === $serviceName) { |  | ||||||
|                                     $fqdn = generateFqdn($this->server, $containerName); |  | ||||||
|                                 } else { |  | ||||||
|                                     $fqdn = generateFqdn($this->server, Str::lower($forService) . '-' . $this->uuid); |  | ||||||
|                                 } |  | ||||||
|                                 if ($port) { |  | ||||||
|                                     $fqdn = "$fqdn:$port"; |  | ||||||
|                                 } |  | ||||||
|                                 if ($foundEnv) { |  | ||||||
|                                     $fqdn = data_get($foundEnv, 'value'); |  | ||||||
|                                 } else { |  | ||||||
|                                     if ($command->value() === 'URL') { |  | ||||||
|                                         $fqdn = Str::of($fqdn)->after('://')->value(); |  | ||||||
|                                     } |  | ||||||
|                                     EnvironmentVariable::create([ |  | ||||||
|                                         'key' => $key, |  | ||||||
|                                         'value' => $fqdn, |  | ||||||
|                                         'is_build_time' => false, |  | ||||||
|                                         'service_id' => $this->id, |  | ||||||
|                                         'is_preview' => false, |  | ||||||
|                                     ]); |  | ||||||
|                                 } |  | ||||||
|                                 if (!$isDatabase) { |  | ||||||
|                                     if ($command->value() === 'FQDN' && is_null($savedService->fqdn)) { |  | ||||||
|                                         $savedService->fqdn = $fqdn; |  | ||||||
|                                         $savedService->save(); |  | ||||||
|                                     } |  | ||||||
|                                 } |  | ||||||
|                             } else { |  | ||||||
|                                 switch ($command) { |  | ||||||
|                                     case 'PASSWORD': |  | ||||||
|                                         $generatedValue = Str::password(symbols: false); |  | ||||||
|                                         break; |  | ||||||
|                                     case 'PASSWORD_64': |  | ||||||
|                                         $generatedValue = Str::password(length: 64, symbols: false); |  | ||||||
|                                         break; |  | ||||||
|                                     case 'BASE64_64': |  | ||||||
|                                         $generatedValue = Str::random(64); |  | ||||||
|                                         break; |  | ||||||
|                                     case 'BASE64_128': |  | ||||||
|                                         $generatedValue = Str::random(128); |  | ||||||
|                                         break; |  | ||||||
|                                     case 'BASE64': |  | ||||||
|                                         $generatedValue = Str::random(32); |  | ||||||
|                                         break; |  | ||||||
|                                     case 'USER': |  | ||||||
|                                         $generatedValue = Str::random(16); |  | ||||||
|                                         break; |  | ||||||
|                                 } |  | ||||||
| 
 |  | ||||||
|                                 if (!$foundEnv) { |  | ||||||
|                                     EnvironmentVariable::create([ |  | ||||||
|                                         'key' => $key, |  | ||||||
|                                         'value' => $generatedValue, |  | ||||||
|                                         'is_build_time' => false, |  | ||||||
|                                         'service_id' => $this->id, |  | ||||||
|                                         'is_preview' => false, |  | ||||||
|                                     ]); |  | ||||||
|                                 } |  | ||||||
|                             } |  | ||||||
|                         } else { |  | ||||||
|                             if ($value->contains(':-')) { |  | ||||||
|                                 $key = $value->before(':'); |  | ||||||
|                                 $defaultValue = $value->after(':-'); |  | ||||||
|                             } else if ($value->contains('-')) { |  | ||||||
|                                 $key = $value->before('-'); |  | ||||||
|                                 $defaultValue = $value->after('-'); |  | ||||||
|                             } else if ($value->contains(':?')) { |  | ||||||
|                                 $key = $value->before(':'); |  | ||||||
|                                 $defaultValue = $value->after(':?'); |  | ||||||
|                             } else if ($value->contains('?')) { |  | ||||||
|                                 $key = $value->before('?'); |  | ||||||
|                                 $defaultValue = $value->after('?'); |  | ||||||
|                             } else { |  | ||||||
|                                 $key = $value; |  | ||||||
|                                 $defaultValue = null; |  | ||||||
|                             } |  | ||||||
|                             if ($foundEnv) { |  | ||||||
|                                 $defaultValue = data_get($foundEnv, 'value'); |  | ||||||
|                             } |  | ||||||
|                             EnvironmentVariable::updateOrCreate([ |  | ||||||
|                                 'key' => $key, |  | ||||||
|                                 'service_id' => $this->id, |  | ||||||
|                             ], [ |  | ||||||
|                                 'value' => $defaultValue, |  | ||||||
|                                 'is_build_time' => false, |  | ||||||
|                                 'service_id' => $this->id, |  | ||||||
|                                 'is_preview' => false, |  | ||||||
|                             ]); |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 // Add labels to the service
 |  | ||||||
|                 if ($savedService->serviceType()) { |  | ||||||
|                     $fqdns = generateServiceSpecificFqdns($savedService, forTraefik: true); |  | ||||||
|                 } else { |  | ||||||
|                     $fqdns = collect(data_get($savedService, 'fqdns')); |  | ||||||
|                 } |  | ||||||
|                 $defaultLabels = defaultLabels($this->id, $containerName, type: 'service', subType: $isDatabase ? 'database' : 'application', subId: $savedService->id); |  | ||||||
|                 $serviceLabels = $serviceLabels->merge($defaultLabels); |  | ||||||
|                 if (!$isDatabase && $fqdns->count() > 0) { |  | ||||||
|                     if ($fqdns) { |  | ||||||
|                         $serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik($this->uuid, $fqdns, true)); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|                 if ($this->server->isLogDrainEnabled() && $savedService->isLogDrainEnabled()) { |  | ||||||
|                     data_set($service, 'logging', [ |  | ||||||
|                         'driver' => 'fluentd', |  | ||||||
|                         'options' => [ |  | ||||||
|                             'fluentd-address' => "tcp://127.0.0.1:24224", |  | ||||||
|                             'fluentd-async' => "true", |  | ||||||
|                             'fluentd-sub-second-precision' => "true", |  | ||||||
|                         ] |  | ||||||
|                     ]); |  | ||||||
|                 } |  | ||||||
|                 data_set($service, 'labels', $serviceLabels->toArray()); |  | ||||||
|                 data_forget($service, 'is_database'); |  | ||||||
|                 data_set($service, 'restart', RESTART_MODE); |  | ||||||
|                 data_set($service, 'container_name', $containerName); |  | ||||||
|                 data_forget($service, 'volumes.*.content'); |  | ||||||
|                 data_forget($service, 'volumes.*.isDirectory'); |  | ||||||
|                 // Remove unnecessary variables from service.environment
 |  | ||||||
|                 // $withoutServiceEnvs = collect([]);
 |  | ||||||
|                 // collect(data_get($service, 'environment'))->each(function ($value, $key) use ($withoutServiceEnvs) {
 |  | ||||||
|                 //     ray($key, $value);
 |  | ||||||
|                 //     if (!Str::of($key)->startsWith('$SERVICE_') && !Str::of($value)->startsWith('SERVICE_')) {
 |  | ||||||
|                 //         $k = Str::of($value)->before("=");
 |  | ||||||
|                 //         $v = Str::of($value)->after("=");
 |  | ||||||
|                 //         $withoutServiceEnvs->put($k->value(), $v->value());
 |  | ||||||
|                 //     }
 |  | ||||||
|                 // });
 |  | ||||||
|                 // ray($withoutServiceEnvs);
 |  | ||||||
|                 // data_set($service, 'environment', $withoutServiceEnvs->toArray());
 |  | ||||||
|                 return $service; |  | ||||||
|             }); |  | ||||||
|             $finalServices = [ |  | ||||||
|                 'version' => $dockerComposeVersion, |  | ||||||
|                 'services' => $services->toArray(), |  | ||||||
|                 'volumes' => $topLevelVolumes->toArray(), |  | ||||||
|                 'networks' => $topLevelNetworks->toArray(), |  | ||||||
|             ]; |  | ||||||
|             $this->docker_compose_raw = Yaml::dump($yaml, 10, 2); |  | ||||||
|             $this->docker_compose = Yaml::dump($finalServices, 10, 2); |  | ||||||
|             $this->save(); |  | ||||||
|             $this->saveComposeConfigs(); |  | ||||||
|             return collect([]); |  | ||||||
|         } else { |  | ||||||
|             return collect([]); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -12,6 +12,7 @@ use Illuminate\Support\Str; | |||||||
| trait ExecuteRemoteCommand | trait ExecuteRemoteCommand | ||||||
| { | { | ||||||
|     public ?string $save = null; |     public ?string $save = null; | ||||||
|  |     public static int $batch_counter = 0; | ||||||
|     public function execute_remote_command(...$commands) |     public function execute_remote_command(...$commands) | ||||||
|     { |     { | ||||||
|         static::$batch_counter++; |         static::$batch_counter++; | ||||||
| @@ -24,7 +25,6 @@ trait ExecuteRemoteCommand | |||||||
|             throw new \RuntimeException('Server is not set or is not an instance of Server model'); |             throw new \RuntimeException('Server is not set or is not an instance of Server model'); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|         $commandsText->each(function ($single_command) { |         $commandsText->each(function ($single_command) { | ||||||
|             $command = data_get($single_command, 'command') ?? $single_command[0] ?? null; |             $command = data_get($single_command, 'command') ?? $single_command[0] ?? null; | ||||||
|             if ($command === null) { |             if ($command === null) { | ||||||
|   | |||||||
| @@ -3,6 +3,9 @@ | |||||||
| use App\Models\Application; | use App\Models\Application; | ||||||
| use App\Models\ApplicationPreview; | use App\Models\ApplicationPreview; | ||||||
| use App\Models\Server; | use App\Models\Server; | ||||||
|  | use App\Models\Service; | ||||||
|  | use App\Models\ServiceApplication; | ||||||
|  | use App\Models\ServiceDatabase; | ||||||
| use Illuminate\Support\Collection; | use Illuminate\Support\Collection; | ||||||
| use Illuminate\Support\Str; | use Illuminate\Support\Str; | ||||||
| use Spatie\Url\Url; | use Spatie\Url\Url; | ||||||
| @@ -137,18 +140,28 @@ function defaultLabels($id, $name, $pull_request_id = 0, string $type = 'applica | |||||||
|     $labels->push('coolify.name=' . $name); |     $labels->push('coolify.name=' . $name); | ||||||
|     $labels->push('coolify.pullRequestId=' . $pull_request_id); |     $labels->push('coolify.pullRequestId=' . $pull_request_id); | ||||||
|     if ($type === 'service') { |     if ($type === 'service') { | ||||||
|         $labels->push('coolify.service.subId=' . $subId); |         $subId && $labels->push('coolify.service.subId=' . $subId); | ||||||
|         $labels->push('coolify.service.subType=' . $subType); |         $subType && $labels->push('coolify.service.subType=' . $subType); | ||||||
|     } |     } | ||||||
|     return $labels; |     return $labels; | ||||||
| } | } | ||||||
| function generateServiceSpecificFqdns($service, $forTraefik = false) | function generateServiceSpecificFqdns(ServiceApplication|Application $resource, $forTraefik = false) | ||||||
| { | { | ||||||
|     $variables = collect($service->service->environment_variables); |     if ($resource->getMorphClass() === 'App\Models\ServiceApplication') { | ||||||
|     $type = $service->serviceType(); |         $uuid = $resource->uuid; | ||||||
|  |         $server = $resource->service->server; | ||||||
|  |         $environment_variables = $resource->service->environment_variables; | ||||||
|  |         $type = $resource->serviceType(); | ||||||
|  |     } else if ($resource->getMorphClass() === 'App\Models\Application') { | ||||||
|  |         $uuid = $resource->uuid; | ||||||
|  |         $server = $resource->destination->server; | ||||||
|  |         $environment_variables = $resource->environment_variables; | ||||||
|  |         $type = $resource->serviceType(); | ||||||
|  |     } | ||||||
|  |     $variables = collect($environment_variables); | ||||||
|     $payload = collect([]); |     $payload = collect([]); | ||||||
|     switch ($type) { |     switch ($type) { | ||||||
|         case $type->contains('minio'): |         case $type?->contains('minio'): | ||||||
|             $MINIO_BROWSER_REDIRECT_URL = $variables->where('key', 'MINIO_BROWSER_REDIRECT_URL')->first(); |             $MINIO_BROWSER_REDIRECT_URL = $variables->where('key', 'MINIO_BROWSER_REDIRECT_URL')->first(); | ||||||
|             $MINIO_SERVER_URL = $variables->where('key', 'MINIO_SERVER_URL')->first(); |             $MINIO_SERVER_URL = $variables->where('key', 'MINIO_SERVER_URL')->first(); | ||||||
|             if (is_null($MINIO_BROWSER_REDIRECT_URL) || is_null($MINIO_SERVER_URL)) { |             if (is_null($MINIO_BROWSER_REDIRECT_URL) || is_null($MINIO_SERVER_URL)) { | ||||||
| @@ -156,12 +169,12 @@ function generateServiceSpecificFqdns($service, $forTraefik = false) | |||||||
|             } |             } | ||||||
|             if (is_null($MINIO_BROWSER_REDIRECT_URL?->value)) { |             if (is_null($MINIO_BROWSER_REDIRECT_URL?->value)) { | ||||||
|                 $MINIO_BROWSER_REDIRECT_URL?->update([ |                 $MINIO_BROWSER_REDIRECT_URL?->update([ | ||||||
|                     "value" => generateFqdn($service->service->server, 'console-' . $service->uuid) |                     "value" => generateFqdn($server, 'console-' . $uuid) | ||||||
|                 ]); |                 ]); | ||||||
|             } |             } | ||||||
|             if (is_null($MINIO_SERVER_URL?->value)) { |             if (is_null($MINIO_SERVER_URL?->value)) { | ||||||
|                 $MINIO_SERVER_URL?->update([ |                 $MINIO_SERVER_URL?->update([ | ||||||
|                     "value" => generateFqdn($service->service->server, 'minio-' . $service->uuid) |                     "value" => generateFqdn($server, 'minio-' . $uuid) | ||||||
|                 ]); |                 ]); | ||||||
|             } |             } | ||||||
|             if ($forTraefik) { |             if ($forTraefik) { | ||||||
|   | |||||||
| @@ -12,6 +12,7 @@ function get_proxy_path() | |||||||
| } | } | ||||||
| function connectProxyToNetworks(Server $server) | function connectProxyToNetworks(Server $server) | ||||||
| { | { | ||||||
|  |     // TODO: Connect to service + compose based application networks as well.
 | ||||||
|     $networks = collect($server->standaloneDockers)->map(function ($docker) { |     $networks = collect($server->standaloneDockers)->map(function ($docker) { | ||||||
|         return $docker['network']; |         return $docker['network']; | ||||||
|     })->unique(); |     })->unique(); | ||||||
|   | |||||||
| @@ -1,9 +1,14 @@ | |||||||
| <?php | <?php | ||||||
| 
 | 
 | ||||||
| use App\Models\Application; | use App\Models\Application; | ||||||
|  | use App\Models\EnvironmentVariable; | ||||||
| use App\Models\InstanceSettings; | use App\Models\InstanceSettings; | ||||||
|  | use App\Models\LocalFileVolume; | ||||||
|  | use App\Models\LocalPersistentVolume; | ||||||
| use App\Models\Server; | use App\Models\Server; | ||||||
| use App\Models\Service; | use App\Models\Service; | ||||||
|  | use App\Models\ServiceApplication; | ||||||
|  | use App\Models\ServiceDatabase; | ||||||
| use App\Models\StandaloneMariadb; | use App\Models\StandaloneMariadb; | ||||||
| use App\Models\StandaloneMongodb; | use App\Models\StandaloneMongodb; | ||||||
| use App\Models\StandaloneMysql; | use App\Models\StandaloneMysql; | ||||||
| @@ -31,6 +36,7 @@ use Poliander\Cron\CronExpression; | |||||||
| use Visus\Cuid2\Cuid2; | use Visus\Cuid2\Cuid2; | ||||||
| use phpseclib3\Crypt\RSA; | use phpseclib3\Crypt\RSA; | ||||||
| use Spatie\Url\Url; | use Spatie\Url\Url; | ||||||
|  | use Symfony\Component\Yaml\Yaml; | ||||||
| 
 | 
 | ||||||
| function base_configuration_dir(): string | function base_configuration_dir(): string | ||||||
| { | { | ||||||
| @@ -472,7 +478,8 @@ function generateDeployWebhook($resource) | |||||||
|     $url = $api . $endpoint . "?uuid=$uuid&force=false"; |     $url = $api . $endpoint . "?uuid=$uuid&force=false"; | ||||||
|     return $url; |     return $url; | ||||||
| } | } | ||||||
| function generateGitManualWebhook($resource, $type) { | function generateGitManualWebhook($resource, $type) | ||||||
|  | { | ||||||
|     if ($resource->source_id !== 0 && !is_null($resource->source_id)) { |     if ($resource->source_id !== 0 && !is_null($resource->source_id)) { | ||||||
|         return null; |         return null; | ||||||
|     } |     } | ||||||
| @@ -487,3 +494,852 @@ function removeAnsiColors($text) | |||||||
| { | { | ||||||
|     return preg_replace('/\e[[][A-Za-z0-9];?[0-9]*m?/', '', $text); |     return preg_replace('/\e[[][A-Za-z0-9];?[0-9]*m?/', '', $text); | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | function parseDockerComposeFile(Service|Application $resource, bool $isNew = false) | ||||||
|  | { | ||||||
|  |     ray()->clearAll(); | ||||||
|  |     if ($resource->getMorphClass() === 'App\Models\Service') { | ||||||
|  |         if ($resource->docker_compose_raw) { | ||||||
|  |             try { | ||||||
|  |                 $yaml = Yaml::parse($resource->docker_compose_raw); | ||||||
|  |             } catch (\Exception $e) { | ||||||
|  |                 throw new \Exception($e->getMessage()); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             $topLevelVolumes = collect(data_get($yaml, 'volumes', [])); | ||||||
|  |             $topLevelNetworks = collect(data_get($yaml, 'networks', [])); | ||||||
|  |             $dockerComposeVersion = data_get($yaml, 'version') ?? '3.8'; | ||||||
|  |             $services = data_get($yaml, 'services'); | ||||||
|  | 
 | ||||||
|  |             $generatedServiceFQDNS = collect([]); | ||||||
|  |             if (is_null($resource->destination)) { | ||||||
|  |                 $destination = $resource->server->destinations()->first(); | ||||||
|  |                 if ($destination) { | ||||||
|  |                     $resource->destination()->associate($destination); | ||||||
|  |                     $resource->save(); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             $definedNetwork = collect([$resource->uuid]); | ||||||
|  | 
 | ||||||
|  |             $services = collect($services)->map(function ($service, $serviceName) use ($topLevelVolumes, $topLevelNetworks, $definedNetwork, $isNew, $generatedServiceFQDNS, $resource) { | ||||||
|  |                 $serviceVolumes = collect(data_get($service, 'volumes', [])); | ||||||
|  |                 $servicePorts = collect(data_get($service, 'ports', [])); | ||||||
|  |                 $serviceNetworks = collect(data_get($service, 'networks', [])); | ||||||
|  |                 $serviceVariables = collect(data_get($service, 'environment', [])); | ||||||
|  |                 $serviceLabels = collect(data_get($service, 'labels', [])); | ||||||
|  |                 if ($serviceLabels->count() > 0) { | ||||||
|  |                     $removedLabels = collect([]); | ||||||
|  |                     $serviceLabels = $serviceLabels->filter(function ($serviceLabel, $serviceLabelName) use ($removedLabels) { | ||||||
|  |                         if (!str($serviceLabel)->contains('=')) { | ||||||
|  |                             $removedLabels->put($serviceLabelName, $serviceLabel); | ||||||
|  |                             return false; | ||||||
|  |                         } | ||||||
|  |                         return $serviceLabel; | ||||||
|  |                     }); | ||||||
|  |                     foreach ($removedLabels as $removedLabelName => $removedLabel) { | ||||||
|  |                         $serviceLabels->push("$removedLabelName=$removedLabel"); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 $containerName = "$serviceName-{$resource->uuid}"; | ||||||
|  | 
 | ||||||
|  |                 // Decide if the service is a database
 | ||||||
|  |                 $isDatabase = false; | ||||||
|  |                 $image = data_get_str($service, 'image'); | ||||||
|  |                 if ($image->contains(':')) { | ||||||
|  |                     $image = Str::of($image); | ||||||
|  |                 } else { | ||||||
|  |                     $image = Str::of($image)->append(':latest'); | ||||||
|  |                 } | ||||||
|  |                 $imageName = $image->before(':'); | ||||||
|  | 
 | ||||||
|  |                 if (collect(DATABASE_DOCKER_IMAGES)->contains($imageName)) { | ||||||
|  |                     $isDatabase = true; | ||||||
|  |                 } | ||||||
|  |                 data_set($service, 'is_database', $isDatabase); | ||||||
|  | 
 | ||||||
|  |                 // Create new serviceApplication or serviceDatabase
 | ||||||
|  |                 if ($isDatabase) { | ||||||
|  |                     if ($isNew) { | ||||||
|  |                         $savedService = ServiceDatabase::create([ | ||||||
|  |                             'name' => $serviceName, | ||||||
|  |                             'image' => $image, | ||||||
|  |                             'service_id' => $resource->id | ||||||
|  |                         ]); | ||||||
|  |                     } else { | ||||||
|  |                         $savedService = ServiceDatabase::where([ | ||||||
|  |                             'name' => $serviceName, | ||||||
|  |                             'service_id' => $resource->id | ||||||
|  |                         ])->first(); | ||||||
|  |                     } | ||||||
|  |                 } else { | ||||||
|  |                     if ($isNew) { | ||||||
|  |                         $savedService = ServiceApplication::create([ | ||||||
|  |                             'name' => $serviceName, | ||||||
|  |                             'image' => $image, | ||||||
|  |                             'service_id' => $resource->id | ||||||
|  |                         ]); | ||||||
|  |                     } else { | ||||||
|  |                         $savedService = ServiceApplication::where([ | ||||||
|  |                             'name' => $serviceName, | ||||||
|  |                             'service_id' => $resource->id | ||||||
|  |                         ])->first(); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 if (is_null($savedService)) { | ||||||
|  |                     if ($isDatabase) { | ||||||
|  |                         $savedService = ServiceDatabase::create([ | ||||||
|  |                             'name' => $serviceName, | ||||||
|  |                             'image' => $image, | ||||||
|  |                             'service_id' => $resource->id | ||||||
|  |                         ]); | ||||||
|  |                     } else { | ||||||
|  |                         $savedService = ServiceApplication::create([ | ||||||
|  |                             'name' => $serviceName, | ||||||
|  |                             'image' => $image, | ||||||
|  |                             'service_id' => $resource->id | ||||||
|  |                         ]); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 // Check if image changed
 | ||||||
|  |                 if ($savedService->image !== $image) { | ||||||
|  |                     $savedService->image = $image; | ||||||
|  |                     $savedService->save(); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 // Collect/create/update networks
 | ||||||
|  |                 if ($serviceNetworks->count() > 0) { | ||||||
|  |                     foreach ($serviceNetworks as $networkName => $networkDetails) { | ||||||
|  |                         $networkExists = $topLevelNetworks->contains(function ($value, $key) use ($networkName) { | ||||||
|  |                             return $value == $networkName || $key == $networkName; | ||||||
|  |                         }); | ||||||
|  |                         if (!$networkExists) { | ||||||
|  |                             $topLevelNetworks->put($networkDetails, null); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 // Collect/create/update ports
 | ||||||
|  |                 $collectedPorts = collect([]); | ||||||
|  |                 if ($servicePorts->count() > 0) { | ||||||
|  |                     foreach ($servicePorts as $sport) { | ||||||
|  |                         if (is_string($sport) || is_numeric($sport)) { | ||||||
|  |                             $collectedPorts->push($sport); | ||||||
|  |                         } | ||||||
|  |                         if (is_array($sport)) { | ||||||
|  |                             $target = data_get($sport, 'target'); | ||||||
|  |                             $published = data_get($sport, 'published'); | ||||||
|  |                             $protocol = data_get($sport, 'protocol'); | ||||||
|  |                             $collectedPorts->push("$target:$published/$protocol"); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 $savedService->ports = $collectedPorts->implode(','); | ||||||
|  |                 $savedService->save(); | ||||||
|  | 
 | ||||||
|  |                 // Add Coolify specific networks
 | ||||||
|  |                 $definedNetworkExists = $topLevelNetworks->contains(function ($value, $_) use ($definedNetwork) { | ||||||
|  |                     return $value == $definedNetwork; | ||||||
|  |                 }); | ||||||
|  |                 if (!$definedNetworkExists) { | ||||||
|  |                     foreach ($definedNetwork as $network) { | ||||||
|  |                         $topLevelNetworks->put($network,  [ | ||||||
|  |                             'name' => $network, | ||||||
|  |                             'external' => true | ||||||
|  |                         ]); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 $networks = collect(); | ||||||
|  |                 foreach ($serviceNetworks as $key => $serviceNetwork) { | ||||||
|  |                     if (gettype($serviceNetwork) === 'string') { | ||||||
|  |                         // networks:
 | ||||||
|  |                         //  - appwrite
 | ||||||
|  |                         $networks->put($serviceNetwork, null); | ||||||
|  |                     } else if (gettype($serviceNetwork) === 'array') { | ||||||
|  |                         // networks:
 | ||||||
|  |                         //   default:
 | ||||||
|  |                         //     ipv4_address: 192.168.203.254
 | ||||||
|  |                         // $networks->put($serviceNetwork, null);
 | ||||||
|  |                         ray($key); | ||||||
|  |                         $networks->put($key, $serviceNetwork); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 foreach ($definedNetwork as $key => $network) { | ||||||
|  |                     $networks->put($network, null); | ||||||
|  |                 } | ||||||
|  |                 data_set($service, 'networks', $networks->toArray()); | ||||||
|  | 
 | ||||||
|  |                 // Collect/create/update volumes
 | ||||||
|  |                 if ($serviceVolumes->count() > 0) { | ||||||
|  |                     $serviceVolumes = $serviceVolumes->map(function ($volume) use ($savedService, $topLevelVolumes) { | ||||||
|  |                         $type = null; | ||||||
|  |                         $source = null; | ||||||
|  |                         $target = null; | ||||||
|  |                         $content = null; | ||||||
|  |                         $isDirectory = false; | ||||||
|  |                         if (is_string($volume)) { | ||||||
|  |                             $source = Str::of($volume)->before(':'); | ||||||
|  |                             $target = Str::of($volume)->after(':')->beforeLast(':'); | ||||||
|  |                             if ($source->startsWith('./') || $source->startsWith('/') || $source->startsWith('~')) { | ||||||
|  |                                 $type = Str::of('bind'); | ||||||
|  |                             } else { | ||||||
|  |                                 $type = Str::of('volume'); | ||||||
|  |                             } | ||||||
|  |                         } else if (is_array($volume)) { | ||||||
|  |                             $type = data_get_str($volume, 'type'); | ||||||
|  |                             $source = data_get_str($volume, 'source'); | ||||||
|  |                             $target = data_get_str($volume, 'target'); | ||||||
|  |                             $content = data_get($volume, 'content'); | ||||||
|  |                             $isDirectory = (bool) data_get($volume, 'isDirectory', false); | ||||||
|  |                             $foundConfig = $savedService->fileStorages()->whereMountPath($target)->first(); | ||||||
|  |                             if ($foundConfig) { | ||||||
|  |                                 $contentNotNull = data_get($foundConfig, 'content'); | ||||||
|  |                                 if ($contentNotNull) { | ||||||
|  |                                     $content = $contentNotNull; | ||||||
|  |                                 } | ||||||
|  |                                 $isDirectory = (bool) data_get($foundConfig, 'is_directory'); | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                         if ($type->value() === 'bind') { | ||||||
|  |                             if ($source->value() === "/var/run/docker.sock") { | ||||||
|  |                                 return $volume; | ||||||
|  |                             } | ||||||
|  |                             if ($source->value() === '/tmp' || $source->value() === '/tmp/') { | ||||||
|  |                                 return $volume; | ||||||
|  |                             } | ||||||
|  |                             LocalFileVolume::updateOrCreate( | ||||||
|  |                                 [ | ||||||
|  |                                     'mount_path' => $target, | ||||||
|  |                                     'resource_id' => $savedService->id, | ||||||
|  |                                     'resource_type' => get_class($savedService) | ||||||
|  |                                 ], | ||||||
|  |                                 [ | ||||||
|  |                                     'fs_path' => $source, | ||||||
|  |                                     'mount_path' => $target, | ||||||
|  |                                     'content' => $content, | ||||||
|  |                                     'is_directory' => $isDirectory, | ||||||
|  |                                     'resource_id' => $savedService->id, | ||||||
|  |                                     'resource_type' => get_class($savedService) | ||||||
|  |                                 ] | ||||||
|  |                             ); | ||||||
|  |                         } else if ($type->value() === 'volume') { | ||||||
|  |                             $slugWithoutUuid = Str::slug($source, '-'); | ||||||
|  |                             $name = "{$savedService->service->uuid}_{$slugWithoutUuid}"; | ||||||
|  |                             if (is_string($volume)) { | ||||||
|  |                                 $source = Str::of($volume)->before(':'); | ||||||
|  |                                 $target = Str::of($volume)->after(':')->beforeLast(':'); | ||||||
|  |                                 $source = $name; | ||||||
|  |                                 $volume = "$source:$target"; | ||||||
|  |                             } else if (is_array($volume)) { | ||||||
|  |                                 data_set($volume, 'source', $name); | ||||||
|  |                             } | ||||||
|  |                             $topLevelVolumes->put($name, [ | ||||||
|  |                                 'name' => $name, | ||||||
|  |                             ]); | ||||||
|  |                             LocalPersistentVolume::updateOrCreate( | ||||||
|  |                                 [ | ||||||
|  |                                     'mount_path' => $target, | ||||||
|  |                                     'resource_id' => $savedService->id, | ||||||
|  |                                     'resource_type' => get_class($savedService) | ||||||
|  |                                 ], | ||||||
|  |                                 [ | ||||||
|  |                                     'name' => $name, | ||||||
|  |                                     'mount_path' => $target, | ||||||
|  |                                     'resource_id' => $savedService->id, | ||||||
|  |                                     'resource_type' => get_class($savedService) | ||||||
|  |                                 ] | ||||||
|  |                             ); | ||||||
|  |                         } | ||||||
|  |                         $savedService->getFilesFromServer(isInit: true); | ||||||
|  |                         return $volume; | ||||||
|  |                     }); | ||||||
|  |                     data_set($service, 'volumes', $serviceVolumes->toArray()); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 // Add env_file with at least .env to the service
 | ||||||
|  |                 // $envFile = collect(data_get($service, 'env_file', []));
 | ||||||
|  |                 // if ($envFile->count() > 0) {
 | ||||||
|  |                 //     if (!$envFile->contains('.env')) {
 | ||||||
|  |                 //         $envFile->push('.env');
 | ||||||
|  |                 //     }
 | ||||||
|  |                 // } else {
 | ||||||
|  |                 //     $envFile = collect(['.env']);
 | ||||||
|  |                 // }
 | ||||||
|  |                 // data_set($service, 'env_file', $envFile->toArray());
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |                 // Get variables from the service
 | ||||||
|  |                 foreach ($serviceVariables as $variableName => $variable) { | ||||||
|  |                     if (is_numeric($variableName)) { | ||||||
|  |                         $variable = Str::of($variable); | ||||||
|  |                         if ($variable->contains('=')) { | ||||||
|  |                             // - SESSION_SECRET=123
 | ||||||
|  |                             // - SESSION_SECRET=
 | ||||||
|  |                             $key = $variable->before('='); | ||||||
|  |                             $value = $variable->after('='); | ||||||
|  |                         } else { | ||||||
|  |                             // - SESSION_SECRET
 | ||||||
|  |                             $key = $variable; | ||||||
|  |                             $value = null; | ||||||
|  |                         } | ||||||
|  |                     } else { | ||||||
|  |                         // SESSION_SECRET: 123
 | ||||||
|  |                         // SESSION_SECRET:
 | ||||||
|  |                         $key = Str::of($variableName); | ||||||
|  |                         $value = Str::of($variable); | ||||||
|  |                     } | ||||||
|  |                     // TODO: here is the problem
 | ||||||
|  |                     if ($key->startsWith('SERVICE_FQDN')) { | ||||||
|  |                         if ($isNew || $savedService->fqdn === null) { | ||||||
|  |                             $name = $key->after('SERVICE_FQDN_')->beforeLast('_')->lower(); | ||||||
|  |                             $fqdn = generateFqdn($resource->server, "{$name->value()}-{$resource->uuid}"); | ||||||
|  |                             if (substr_count($key->value(), '_') === 3) { | ||||||
|  |                                 // SERVICE_FQDN_UMAMI_1000
 | ||||||
|  |                                 $port = $key->afterLast('_'); | ||||||
|  |                             } else { | ||||||
|  |                                 // SERVICE_FQDN_UMAMI
 | ||||||
|  |                                 $port = null; | ||||||
|  |                             } | ||||||
|  |                             if ($port) { | ||||||
|  |                                 $fqdn = "$fqdn:$port"; | ||||||
|  |                             } | ||||||
|  |                             if (substr_count($key->value(), '_') >= 2) { | ||||||
|  |                                 if (is_null($value)) { | ||||||
|  |                                     $value = Str::of('/'); | ||||||
|  |                                 } | ||||||
|  |                                 $path = $value->value(); | ||||||
|  |                                 if ($generatedServiceFQDNS->count() > 0) { | ||||||
|  |                                     $alreadyGenerated = $generatedServiceFQDNS->has($key->value()); | ||||||
|  |                                     if ($alreadyGenerated) { | ||||||
|  |                                         $fqdn = $generatedServiceFQDNS->get($key->value()); | ||||||
|  |                                     } else { | ||||||
|  |                                         $generatedServiceFQDNS->put($key->value(), $fqdn); | ||||||
|  |                                     } | ||||||
|  |                                 } else { | ||||||
|  |                                     $generatedServiceFQDNS->put($key->value(), $fqdn); | ||||||
|  |                                 } | ||||||
|  |                                 $fqdn = "$fqdn$path"; | ||||||
|  |                             } | ||||||
|  | 
 | ||||||
|  |                             if (!$isDatabase) { | ||||||
|  |                                 if ($savedService->fqdn) { | ||||||
|  |                                     $fqdn = $savedService->fqdn . ',' . $fqdn; | ||||||
|  |                                 } else { | ||||||
|  |                                     $fqdn = $fqdn; | ||||||
|  |                                 } | ||||||
|  |                                 $savedService->fqdn = $fqdn; | ||||||
|  |                                 $savedService->save(); | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                         // data_forget($service, "environment.$variableName");
 | ||||||
|  |                         // $yaml = data_forget($yaml, "services.$serviceName.environment.$variableName");
 | ||||||
|  |                         // if (count(data_get($yaml, 'services.' . $serviceName . '.environment')) === 0) {
 | ||||||
|  |                         //     $yaml = data_forget($yaml, "services.$serviceName.environment");
 | ||||||
|  |                         // }
 | ||||||
|  |                         continue; | ||||||
|  |                     } | ||||||
|  |                     if ($value?->startsWith('$')) { | ||||||
|  |                         $value = Str::of(replaceVariables($value)); | ||||||
|  |                         $key = $value; | ||||||
|  |                         $foundEnv = EnvironmentVariable::where([ | ||||||
|  |                             'key' => $key, | ||||||
|  |                             'service_id' => $resource->id, | ||||||
|  |                         ])->first(); | ||||||
|  |                         if ($value->startsWith('SERVICE_')) { | ||||||
|  |                             // Count _ in $value
 | ||||||
|  |                             $count = substr_count($value->value(), '_'); | ||||||
|  |                             if ($count === 2) { | ||||||
|  |                                 // SERVICE_FQDN_UMAMI
 | ||||||
|  |                                 $command = $value->after('SERVICE_')->beforeLast('_'); | ||||||
|  |                                 $forService = $value->afterLast('_'); | ||||||
|  |                                 $generatedValue = null; | ||||||
|  |                                 $port = null; | ||||||
|  |                             } | ||||||
|  |                             if ($count === 3) { | ||||||
|  |                                 // SERVICE_FQDN_UMAMI_1000
 | ||||||
|  |                                 $command = $value->after('SERVICE_')->before('_'); | ||||||
|  |                                 $forService = $value->after('SERVICE_')->after('_')->before('_'); | ||||||
|  |                                 $generatedValue = null; | ||||||
|  |                                 $port = $value->afterLast('_'); | ||||||
|  |                             } | ||||||
|  |                             if ($command->value() === 'FQDN' || $command->value() === 'URL') { | ||||||
|  |                                 if (Str::lower($forService) === $serviceName) { | ||||||
|  |                                     $fqdn = generateFqdn($resource->server, $containerName); | ||||||
|  |                                 } else { | ||||||
|  |                                     $fqdn = generateFqdn($resource->server, Str::lower($forService) . '-' . $resource->uuid); | ||||||
|  |                                 } | ||||||
|  |                                 if ($port) { | ||||||
|  |                                     $fqdn = "$fqdn:$port"; | ||||||
|  |                                 } | ||||||
|  |                                 if ($foundEnv) { | ||||||
|  |                                     $fqdn = data_get($foundEnv, 'value'); | ||||||
|  |                                 } else { | ||||||
|  |                                     if ($command->value() === 'URL') { | ||||||
|  |                                         $fqdn = Str::of($fqdn)->after('://')->value(); | ||||||
|  |                                     } | ||||||
|  |                                     EnvironmentVariable::create([ | ||||||
|  |                                         'key' => $key, | ||||||
|  |                                         'value' => $fqdn, | ||||||
|  |                                         'is_build_time' => false, | ||||||
|  |                                         'service_id' => $resource->id, | ||||||
|  |                                         'is_preview' => false, | ||||||
|  |                                     ]); | ||||||
|  |                                 } | ||||||
|  |                                 if (!$isDatabase) { | ||||||
|  |                                     if ($command->value() === 'FQDN' && is_null($savedService->fqdn)) { | ||||||
|  |                                         $savedService->fqdn = $fqdn; | ||||||
|  |                                         $savedService->save(); | ||||||
|  |                                     } | ||||||
|  |                                 } | ||||||
|  |                             } else { | ||||||
|  |                                 switch ($command) { | ||||||
|  |                                     case 'PASSWORD': | ||||||
|  |                                         $generatedValue = Str::password(symbols: false); | ||||||
|  |                                         break; | ||||||
|  |                                     case 'PASSWORD_64': | ||||||
|  |                                         $generatedValue = Str::password(length: 64, symbols: false); | ||||||
|  |                                         break; | ||||||
|  |                                     case 'BASE64_64': | ||||||
|  |                                         $generatedValue = Str::random(64); | ||||||
|  |                                         break; | ||||||
|  |                                     case 'BASE64_128': | ||||||
|  |                                         $generatedValue = Str::random(128); | ||||||
|  |                                         break; | ||||||
|  |                                     case 'BASE64': | ||||||
|  |                                         $generatedValue = Str::random(32); | ||||||
|  |                                         break; | ||||||
|  |                                     case 'USER': | ||||||
|  |                                         $generatedValue = Str::random(16); | ||||||
|  |                                         break; | ||||||
|  |                                 } | ||||||
|  | 
 | ||||||
|  |                                 if (!$foundEnv) { | ||||||
|  |                                     EnvironmentVariable::create([ | ||||||
|  |                                         'key' => $key, | ||||||
|  |                                         'value' => $generatedValue, | ||||||
|  |                                         'is_build_time' => false, | ||||||
|  |                                         'service_id' => $resource->id, | ||||||
|  |                                         'is_preview' => false, | ||||||
|  |                                     ]); | ||||||
|  |                                 } | ||||||
|  |                             } | ||||||
|  |                         } else { | ||||||
|  |                             if ($value->contains(':-')) { | ||||||
|  |                                 $key = $value->before(':'); | ||||||
|  |                                 $defaultValue = $value->after(':-'); | ||||||
|  |                             } else if ($value->contains('-')) { | ||||||
|  |                                 $key = $value->before('-'); | ||||||
|  |                                 $defaultValue = $value->after('-'); | ||||||
|  |                             } else if ($value->contains(':?')) { | ||||||
|  |                                 $key = $value->before(':'); | ||||||
|  |                                 $defaultValue = $value->after(':?'); | ||||||
|  |                             } else if ($value->contains('?')) { | ||||||
|  |                                 $key = $value->before('?'); | ||||||
|  |                                 $defaultValue = $value->after('?'); | ||||||
|  |                             } else { | ||||||
|  |                                 $key = $value; | ||||||
|  |                                 $defaultValue = null; | ||||||
|  |                             } | ||||||
|  |                             if ($foundEnv) { | ||||||
|  |                                 $defaultValue = data_get($foundEnv, 'value'); | ||||||
|  |                             } | ||||||
|  |                             EnvironmentVariable::updateOrCreate([ | ||||||
|  |                                 'key' => $key, | ||||||
|  |                                 'service_id' => $resource->id, | ||||||
|  |                             ], [ | ||||||
|  |                                 'value' => $defaultValue, | ||||||
|  |                                 'is_build_time' => false, | ||||||
|  |                                 'service_id' => $resource->id, | ||||||
|  |                                 'is_preview' => false, | ||||||
|  |                             ]); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 // Add labels to the service
 | ||||||
|  |                 if ($savedService->serviceType()) { | ||||||
|  |                     $fqdns = generateServiceSpecificFqdns($savedService, forTraefik: true); | ||||||
|  |                 } else { | ||||||
|  |                     $fqdns = collect(data_get($savedService, 'fqdns')); | ||||||
|  |                 } | ||||||
|  |                 $defaultLabels = defaultLabels($resource->id, $containerName, type: 'service', subType: $isDatabase ? 'database' : 'application', subId: $savedService->id); | ||||||
|  |                 $serviceLabels = $serviceLabels->merge($defaultLabels); | ||||||
|  |                 if (!$isDatabase && $fqdns->count() > 0) { | ||||||
|  |                     if ($fqdns) { | ||||||
|  |                         $serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik($resource->uuid, $fqdns, true)); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 if ($resource->server->isLogDrainEnabled() && $savedService->isLogDrainEnabled()) { | ||||||
|  |                     data_set($service, 'logging', [ | ||||||
|  |                         'driver' => 'fluentd', | ||||||
|  |                         'options' => [ | ||||||
|  |                             'fluentd-address' => "tcp://127.0.0.1:24224", | ||||||
|  |                             'fluentd-async' => "true", | ||||||
|  |                             'fluentd-sub-second-precision' => "true", | ||||||
|  |                         ] | ||||||
|  |                     ]); | ||||||
|  |                 } | ||||||
|  |                 data_set($service, 'labels', $serviceLabels->toArray()); | ||||||
|  |                 data_forget($service, 'is_database'); | ||||||
|  |                 data_set($service, 'restart', RESTART_MODE); | ||||||
|  |                 data_set($service, 'container_name', $containerName); | ||||||
|  |                 data_forget($service, 'volumes.*.content'); | ||||||
|  |                 data_forget($service, 'volumes.*.isDirectory'); | ||||||
|  |                 // Remove unnecessary variables from service.environment
 | ||||||
|  |                 // $withoutServiceEnvs = collect([]);
 | ||||||
|  |                 // collect(data_get($service, 'environment'))->each(function ($value, $key) use ($withoutServiceEnvs) {
 | ||||||
|  |                 //     ray($key, $value);
 | ||||||
|  |                 //     if (!Str::of($key)->startsWith('$SERVICE_') && !Str::of($value)->startsWith('SERVICE_')) {
 | ||||||
|  |                 //         $k = Str::of($value)->before("=");
 | ||||||
|  |                 //         $v = Str::of($value)->after("=");
 | ||||||
|  |                 //         $withoutServiceEnvs->put($k->value(), $v->value());
 | ||||||
|  |                 //     }
 | ||||||
|  |                 // });
 | ||||||
|  |                 // ray($withoutServiceEnvs);
 | ||||||
|  |                 // data_set($service, 'environment', $withoutServiceEnvs->toArray());
 | ||||||
|  |                 return $service; | ||||||
|  |             }); | ||||||
|  |             $finalServices = [ | ||||||
|  |                 'version' => $dockerComposeVersion, | ||||||
|  |                 'services' => $services->toArray(), | ||||||
|  |                 'volumes' => $topLevelVolumes->toArray(), | ||||||
|  |                 'networks' => $topLevelNetworks->toArray(), | ||||||
|  |             ]; | ||||||
|  |             $resource->docker_compose_raw = Yaml::dump($yaml, 10, 2); | ||||||
|  |             $resource->docker_compose = Yaml::dump($finalServices, 10, 2); | ||||||
|  |             $resource->save(); | ||||||
|  |             $resource->saveComposeConfigs(); | ||||||
|  |             return collect([]); | ||||||
|  |         } else { | ||||||
|  |             return collect([]); | ||||||
|  |         } | ||||||
|  |     } else if ($resource->getMorphClass() === 'App\Models\Application') { | ||||||
|  |         try { | ||||||
|  |             $yaml = Yaml::parse($resource->docker_compose_raw); | ||||||
|  |         } catch (\Exception $e) { | ||||||
|  |             throw new \Exception($e->getMessage()); | ||||||
|  |         } | ||||||
|  |         $server = $resource->destination->server; | ||||||
|  |         $topLevelVolumes = collect(data_get($yaml, 'volumes', [])); | ||||||
|  |         $topLevelNetworks = collect(data_get($yaml, 'networks', [])); | ||||||
|  |         $dockerComposeVersion = data_get($yaml, 'version') ?? '3.8'; | ||||||
|  |         $services = data_get($yaml, 'services'); | ||||||
|  | 
 | ||||||
|  |         $generatedServiceFQDNS = collect([]); | ||||||
|  |         if (is_null($resource->destination)) { | ||||||
|  |             $destination = $server->destinations()->first(); | ||||||
|  |             if ($destination) { | ||||||
|  |                 $resource->destination()->associate($destination); | ||||||
|  |                 $resource->save(); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         $definedNetwork = collect([$resource->uuid]); | ||||||
|  |         $services = collect($services)->map(function ($service, $serviceName) use ($topLevelVolumes, $topLevelNetworks, $definedNetwork, $isNew, $generatedServiceFQDNS, $resource, $server) { | ||||||
|  |             $serviceVolumes = collect(data_get($service, 'volumes', [])); | ||||||
|  |             $servicePorts = collect(data_get($service, 'ports', [])); | ||||||
|  |             $serviceNetworks = collect(data_get($service, 'networks', [])); | ||||||
|  |             $serviceVariables = collect(data_get($service, 'environment', [])); | ||||||
|  |             $serviceLabels = collect(data_get($service, 'labels', [])); | ||||||
|  |             if ($serviceLabels->count() > 0) { | ||||||
|  |                 $removedLabels = collect([]); | ||||||
|  |                 $serviceLabels = $serviceLabels->filter(function ($serviceLabel, $serviceLabelName) use ($removedLabels) { | ||||||
|  |                     if (!str($serviceLabel)->contains('=')) { | ||||||
|  |                         $removedLabels->put($serviceLabelName, $serviceLabel); | ||||||
|  |                         return false; | ||||||
|  |                     } | ||||||
|  |                     return $serviceLabel; | ||||||
|  |                 }); | ||||||
|  |                 foreach ($removedLabels as $removedLabelName => $removedLabel) { | ||||||
|  |                     $serviceLabels->push("$removedLabelName=$removedLabel"); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             $containerName = "$serviceName-{$resource->uuid}"; | ||||||
|  | 
 | ||||||
|  |             // Decide if the service is a database
 | ||||||
|  |             $isDatabase = false; | ||||||
|  |             $image = data_get_str($service, 'image'); | ||||||
|  |             if ($image->contains(':')) { | ||||||
|  |                 $image = Str::of($image); | ||||||
|  |             } else { | ||||||
|  |                 $image = Str::of($image)->append(':latest'); | ||||||
|  |             } | ||||||
|  |             $imageName = $image->before(':'); | ||||||
|  | 
 | ||||||
|  |             if (collect(DATABASE_DOCKER_IMAGES)->contains($imageName)) { | ||||||
|  |                 $isDatabase = true; | ||||||
|  |             } | ||||||
|  |             data_set($service, 'is_database', $isDatabase); | ||||||
|  | 
 | ||||||
|  |             // Collect/create/update networks
 | ||||||
|  |             if ($serviceNetworks->count() > 0) { | ||||||
|  |                 foreach ($serviceNetworks as $networkName => $networkDetails) { | ||||||
|  |                     $networkExists = $topLevelNetworks->contains(function ($value, $key) use ($networkName) { | ||||||
|  |                         return $value == $networkName || $key == $networkName; | ||||||
|  |                     }); | ||||||
|  |                     if (!$networkExists) { | ||||||
|  |                         $topLevelNetworks->put($networkDetails, null); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             // Collect/create/update ports
 | ||||||
|  |             $collectedPorts = collect([]); | ||||||
|  |             if ($servicePorts->count() > 0) { | ||||||
|  |                 foreach ($servicePorts as $sport) { | ||||||
|  |                     if (is_string($sport) || is_numeric($sport)) { | ||||||
|  |                         $collectedPorts->push($sport); | ||||||
|  |                     } | ||||||
|  |                     if (is_array($sport)) { | ||||||
|  |                         $target = data_get($sport, 'target'); | ||||||
|  |                         $published = data_get($sport, 'published'); | ||||||
|  |                         $protocol = data_get($sport, 'protocol'); | ||||||
|  |                         $collectedPorts->push("$target:$published/$protocol"); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             if ($collectedPorts->count() > 0) { | ||||||
|  |                 // ray($collectedPorts->implode(','));
 | ||||||
|  |             } | ||||||
|  |             $definedNetworkExists = $topLevelNetworks->contains(function ($value, $_) use ($definedNetwork) { | ||||||
|  |                 return $value == $definedNetwork; | ||||||
|  |             }); | ||||||
|  |             if (!$definedNetworkExists) { | ||||||
|  |                 foreach ($definedNetwork as $network) { | ||||||
|  |                     $topLevelNetworks->put($network,  [ | ||||||
|  |                         'name' => $network, | ||||||
|  |                         'external' => true | ||||||
|  |                     ]); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             $networks = collect(); | ||||||
|  |             foreach ($serviceNetworks as $key => $serviceNetwork) { | ||||||
|  |                 if (gettype($serviceNetwork) === 'string') { | ||||||
|  |                     // networks:
 | ||||||
|  |                     //  - appwrite
 | ||||||
|  |                     $networks->put($serviceNetwork, null); | ||||||
|  |                 } else if (gettype($serviceNetwork) === 'array') { | ||||||
|  |                     // networks:
 | ||||||
|  |                     //   default:
 | ||||||
|  |                     //     ipv4_address: 192.168.203.254
 | ||||||
|  |                     // $networks->put($serviceNetwork, null);
 | ||||||
|  |                     $networks->put($key, $serviceNetwork); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             foreach ($definedNetwork as $key => $network) { | ||||||
|  |                 $networks->put($network, null); | ||||||
|  |             } | ||||||
|  |             data_set($service, 'networks', $networks->toArray()); | ||||||
|  |             // Get variables from the service
 | ||||||
|  |             foreach ($serviceVariables as $variableName => $variable) { | ||||||
|  |                 if (is_numeric($variableName)) { | ||||||
|  |                     $variable = Str::of($variable); | ||||||
|  |                     if ($variable->contains('=')) { | ||||||
|  |                         // - SESSION_SECRET=123
 | ||||||
|  |                         // - SESSION_SECRET=
 | ||||||
|  |                         $key = $variable->before('='); | ||||||
|  |                         $value = $variable->after('='); | ||||||
|  |                     } else { | ||||||
|  |                         // - SESSION_SECRET
 | ||||||
|  |                         $key = $variable; | ||||||
|  |                         $value = null; | ||||||
|  |                     } | ||||||
|  |                 } else { | ||||||
|  |                     // SESSION_SECRET: 123
 | ||||||
|  |                     // SESSION_SECRET:
 | ||||||
|  |                     $key = Str::of($variableName); | ||||||
|  |                     $value = Str::of($variable); | ||||||
|  |                 } | ||||||
|  |                 // TODO: here is the problem
 | ||||||
|  |                 if ($key->startsWith('SERVICE_FQDN')) { | ||||||
|  |                     if ($isNew) { | ||||||
|  |                         $name = $key->after('SERVICE_FQDN_')->beforeLast('_')->lower(); | ||||||
|  |                         $fqdn = generateFqdn($server, "{$name->value()}-{$resource->uuid}"); | ||||||
|  |                         if (substr_count($key->value(), '_') === 3) { | ||||||
|  |                             // SERVICE_FQDN_UMAMI_1000
 | ||||||
|  |                             $port = $key->afterLast('_'); | ||||||
|  |                         } else { | ||||||
|  |                             // SERVICE_FQDN_UMAMI
 | ||||||
|  |                             $port = null; | ||||||
|  |                         } | ||||||
|  |                         if ($port) { | ||||||
|  |                             $fqdn = "$fqdn:$port"; | ||||||
|  |                         } | ||||||
|  |                         if (substr_count($key->value(), '_') >= 2) { | ||||||
|  |                             if (is_null($value)) { | ||||||
|  |                                 $value = Str::of('/'); | ||||||
|  |                             } | ||||||
|  |                             $path = $value->value(); | ||||||
|  |                             if ($generatedServiceFQDNS->count() > 0) { | ||||||
|  |                                 $alreadyGenerated = $generatedServiceFQDNS->has($key->value()); | ||||||
|  |                                 if ($alreadyGenerated) { | ||||||
|  |                                     $fqdn = $generatedServiceFQDNS->get($key->value()); | ||||||
|  |                                 } else { | ||||||
|  |                                     $generatedServiceFQDNS->put($key->value(), $fqdn); | ||||||
|  |                                 } | ||||||
|  |                             } else { | ||||||
|  |                                 $generatedServiceFQDNS->put($key->value(), $fqdn); | ||||||
|  |                             } | ||||||
|  |                             $fqdn = "$fqdn$path"; | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                     continue; | ||||||
|  |                 } | ||||||
|  |                 if ($value?->startsWith('$')) { | ||||||
|  |                     $value = Str::of(replaceVariables($value)); | ||||||
|  |                     $key = $value; | ||||||
|  |                     $foundEnv = EnvironmentVariable::where([ | ||||||
|  |                         'key' => $key, | ||||||
|  |                         'application_id' => $resource->id, | ||||||
|  |                     ])->first(); | ||||||
|  |                     if ($value->startsWith('SERVICE_')) { | ||||||
|  |                         // Count _ in $value
 | ||||||
|  |                         $count = substr_count($value->value(), '_'); | ||||||
|  |                         if ($count === 2) { | ||||||
|  |                             // SERVICE_FQDN_UMAMI
 | ||||||
|  |                             $command = $value->after('SERVICE_')->beforeLast('_'); | ||||||
|  |                             $forService = $value->afterLast('_'); | ||||||
|  |                             $generatedValue = null; | ||||||
|  |                             $port = null; | ||||||
|  |                         } | ||||||
|  |                         if ($count === 3) { | ||||||
|  |                             // SERVICE_FQDN_UMAMI_1000
 | ||||||
|  |                             $command = $value->after('SERVICE_')->before('_'); | ||||||
|  |                             $forService = $value->after('SERVICE_')->after('_')->before('_'); | ||||||
|  |                             $generatedValue = null; | ||||||
|  |                             $port = $value->afterLast('_'); | ||||||
|  |                         } | ||||||
|  |                         if ($command->value() === 'FQDN' || $command->value() === 'URL') { | ||||||
|  |                             if (Str::lower($forService) === $serviceName) { | ||||||
|  |                                 $fqdn = generateFqdn($server, $containerName); | ||||||
|  |                             } else { | ||||||
|  |                                 $fqdn = generateFqdn($server, Str::lower($forService) . '-' . $resource->uuid); | ||||||
|  |                             } | ||||||
|  |                             if ($port) { | ||||||
|  |                                 $fqdn = "$fqdn:$port"; | ||||||
|  |                             } | ||||||
|  |                             if ($foundEnv) { | ||||||
|  |                                 $fqdn = data_get($foundEnv, 'value'); | ||||||
|  |                             } else { | ||||||
|  |                                 if ($command->value() === 'URL') { | ||||||
|  |                                     $fqdn = Str::of($fqdn)->after('://')->value(); | ||||||
|  |                                 } | ||||||
|  |                                 EnvironmentVariable::create([ | ||||||
|  |                                     'key' => $key, | ||||||
|  |                                     'value' => $fqdn, | ||||||
|  |                                     'is_build_time' => false, | ||||||
|  |                                     'application_id' => $resource->id, | ||||||
|  |                                     'is_preview' => false, | ||||||
|  |                                 ]); | ||||||
|  |                             } | ||||||
|  |                         } else { | ||||||
|  |                             switch ($command) { | ||||||
|  |                                 case 'PASSWORD': | ||||||
|  |                                     $generatedValue = Str::password(symbols: false); | ||||||
|  |                                     break; | ||||||
|  |                                 case 'PASSWORD_64': | ||||||
|  |                                     $generatedValue = Str::password(length: 64, symbols: false); | ||||||
|  |                                     break; | ||||||
|  |                                 case 'BASE64_64': | ||||||
|  |                                     $generatedValue = Str::random(64); | ||||||
|  |                                     break; | ||||||
|  |                                 case 'BASE64_128': | ||||||
|  |                                     $generatedValue = Str::random(128); | ||||||
|  |                                     break; | ||||||
|  |                                 case 'BASE64': | ||||||
|  |                                     $generatedValue = Str::random(32); | ||||||
|  |                                     break; | ||||||
|  |                                 case 'USER': | ||||||
|  |                                     $generatedValue = Str::random(16); | ||||||
|  |                                     break; | ||||||
|  |                             } | ||||||
|  | 
 | ||||||
|  |                             if (!$foundEnv) { | ||||||
|  |                                 EnvironmentVariable::create([ | ||||||
|  |                                     'key' => $key, | ||||||
|  |                                     'value' => $generatedValue, | ||||||
|  |                                     'is_build_time' => false, | ||||||
|  |                                     'application_id' => $resource->id, | ||||||
|  |                                     'is_preview' => false, | ||||||
|  |                                 ]); | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                     } else { | ||||||
|  |                         if ($value->contains(':-')) { | ||||||
|  |                             $key = $value->before(':'); | ||||||
|  |                             $defaultValue = $value->after(':-'); | ||||||
|  |                         } else if ($value->contains('-')) { | ||||||
|  |                             $key = $value->before('-'); | ||||||
|  |                             $defaultValue = $value->after('-'); | ||||||
|  |                         } else if ($value->contains(':?')) { | ||||||
|  |                             $key = $value->before(':'); | ||||||
|  |                             $defaultValue = $value->after(':?'); | ||||||
|  |                         } else if ($value->contains('?')) { | ||||||
|  |                             $key = $value->before('?'); | ||||||
|  |                             $defaultValue = $value->after('?'); | ||||||
|  |                         } else { | ||||||
|  |                             $key = $value; | ||||||
|  |                             $defaultValue = null; | ||||||
|  |                         } | ||||||
|  |                         if ($foundEnv) { | ||||||
|  |                             $defaultValue = data_get($foundEnv, 'value'); | ||||||
|  |                         } | ||||||
|  |                         EnvironmentVariable::updateOrCreate([ | ||||||
|  |                             'key' => $key, | ||||||
|  |                             'application_id' => $resource->id, | ||||||
|  |                         ], [ | ||||||
|  |                             'value' => $defaultValue, | ||||||
|  |                             'is_build_time' => false, | ||||||
|  |                             'service_id' => $resource->id, | ||||||
|  |                             'is_preview' => false, | ||||||
|  |                         ]); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             // Add labels to the service
 | ||||||
|  |             if ($resource->serviceType()) { | ||||||
|  |                 $fqdns = generateServiceSpecificFqdns($resource, forTraefik: true); | ||||||
|  |             } else { | ||||||
|  |                 $domains = collect(json_decode($resource->docker_compose_domains)) ?? []; | ||||||
|  |                 if ($domains) { | ||||||
|  |                     $fqdns = data_get($domains, "$serviceName.domain"); | ||||||
|  |                     if ($fqdns) { | ||||||
|  |                         $fqdns = str($fqdns)->explode(','); | ||||||
|  |                         $serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik($resource->uuid, $fqdns, true)); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             $defaultLabels = defaultLabels($resource->id, $containerName, type: 'application'); | ||||||
|  | 
 | ||||||
|  |             $serviceLabels = $serviceLabels->merge($defaultLabels); | ||||||
|  | 
 | ||||||
|  |             if ($server->isLogDrainEnabled() && $resource->isLogDrainEnabled()) { | ||||||
|  |                 data_set($service, 'logging', [ | ||||||
|  |                     'driver' => 'fluentd', | ||||||
|  |                     'options' => [ | ||||||
|  |                         'fluentd-address' => "tcp://127.0.0.1:24224", | ||||||
|  |                         'fluentd-async' => "true", | ||||||
|  |                         'fluentd-sub-second-precision' => "true", | ||||||
|  |                     ] | ||||||
|  |                 ]); | ||||||
|  |             } | ||||||
|  |             data_set($service, 'labels', $serviceLabels->toArray()); | ||||||
|  |             data_forget($service, 'is_database'); | ||||||
|  |             data_set($service, 'restart', RESTART_MODE); | ||||||
|  |             data_set($service, 'container_name', $containerName); | ||||||
|  |             data_forget($service, 'volumes.*.content'); | ||||||
|  |             data_forget($service, 'volumes.*.isDirectory'); | ||||||
|  |             return $service; | ||||||
|  |         }); | ||||||
|  |         $finalServices = [ | ||||||
|  |             'version' => $dockerComposeVersion, | ||||||
|  |             'services' => $services->toArray(), | ||||||
|  |             'volumes' => $topLevelVolumes->toArray(), | ||||||
|  |             'networks' => $topLevelNetworks->toArray(), | ||||||
|  |         ]; | ||||||
|  |         $resource->docker_compose_raw = Yaml::dump($yaml, 10, 2); | ||||||
|  |         $resource->docker_compose = Yaml::dump($finalServices, 10, 2); | ||||||
|  |         $resource->save(); | ||||||
|  |         return collect($finalServices); | ||||||
|  |     } | ||||||
|  | } | ||||||
|   | |||||||
| @@ -0,0 +1,35 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | use Illuminate\Database\Migrations\Migration; | ||||||
|  | use Illuminate\Database\Schema\Blueprint; | ||||||
|  | use Illuminate\Support\Facades\Schema; | ||||||
|  | 
 | ||||||
|  | return new class extends Migration | ||||||
|  | { | ||||||
|  |     /** | ||||||
|  |      * Run the migrations. | ||||||
|  |      */ | ||||||
|  |     public function up(): void | ||||||
|  |     { | ||||||
|  |         Schema::table('applications', function (Blueprint $table) { | ||||||
|  |             $table->string('docker_compose_location')->nullable()->default('/docker-compose.yml')->after('dockerfile_location'); | ||||||
|  |             $table->longText('docker_compose')->nullable()->after('docker_compose_location'); | ||||||
|  |             $table->longText('docker_compose_raw')->nullable()->after('docker_compose'); | ||||||
|  |             $table->text('docker_compose_domains')->nullable()->after('docker_compose_raw'); | ||||||
|  | 
 | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Reverse the migrations. | ||||||
|  |      */ | ||||||
|  |     public function down(): void | ||||||
|  |     { | ||||||
|  |         Schema::table('applications', function (Blueprint $table) { | ||||||
|  |             $table->dropColumn('docker_compose_location'); | ||||||
|  |             $table->dropColumn('docker_compose'); | ||||||
|  |             $table->dropColumn('docker_compose_raw'); | ||||||
|  |             $table->dropColumn('docker_compose_domains'); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | }; | ||||||
| @@ -16,7 +16,8 @@ | |||||||
|     <x-applications.advanced :application="$application" /> |     <x-applications.advanced :application="$application" /> | ||||||
| 
 | 
 | ||||||
|     @if ($application->status !== 'exited') |     @if ($application->status !== 'exited') | ||||||
|         <button title="With rolling update if possible" wire:click='deploy' class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400"> |         <button title="With rolling update if possible" wire:click='deploy' | ||||||
|  |             class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400"> | ||||||
|             <svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-orange-400" viewBox="0 0 24 24" stroke-width="2" |             <svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-orange-400" viewBox="0 0 24 24" stroke-width="2" | ||||||
|                 stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> |                 stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> | ||||||
|                 <path stroke="none" d="M0 0h24v24H0z" fill="none"></path> |                 <path stroke="none" d="M0 0h24v24H0z" fill="none"></path> | ||||||
| @@ -27,15 +28,19 @@ | |||||||
|             </svg> |             </svg> | ||||||
|             Redeploy |             Redeploy | ||||||
|         </button> |         </button> | ||||||
|         <button title="Restart without rebuilding" wire:click='restart' class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400"> |         @if ($application->build_pack !== 'dockercompose') | ||||||
|             <svg class="w-5 h-5 text-warning" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> |             <button title="Restart without rebuilding" wire:click='restart' | ||||||
|                 <g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"> |                 class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400"> | ||||||
|                     <path d="M19.933 13.041a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747"/> |                 <svg class="w-5 h-5 text-warning" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> | ||||||
|                     <path d="M20 4v5h-5"/> |                     <g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" | ||||||
|                 </g> |                         stroke-width="2"> | ||||||
|             </svg> |                         <path d="M19.933 13.041a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747" /> | ||||||
|             Restart |                         <path d="M20 4v5h-5" /> | ||||||
|         </button> |                     </g> | ||||||
|  |                 </svg> | ||||||
|  |                 Restart | ||||||
|  |             </button> | ||||||
|  |         @endif | ||||||
|         <button wire:click='stop' class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400"> |         <button wire:click='stop' class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400"> | ||||||
|             <svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24" stroke-width="2" |             <svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24" stroke-width="2" | ||||||
|                 stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> |                 stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> | ||||||
|   | |||||||
| @@ -16,20 +16,23 @@ | |||||||
|                 <x-forms.input id="application.name" label="Name" required /> |                 <x-forms.input id="application.name" label="Name" required /> | ||||||
|                 <x-forms.input id="application.description" label="Description" /> |                 <x-forms.input id="application.description" label="Description" /> | ||||||
|             </div> |             </div> | ||||||
|             <div class="flex items-end gap-2"> |             @if ($application->build_pack !== 'dockercompose') | ||||||
|                 <x-forms.input placeholder="https://coolify.io" id="application.fqdn" label="Domains" |                 <div class="flex items-end gap-2"> | ||||||
|                     helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- http://app.coolify.io, https://cloud.coolify.io/dashboard<br>- http://app.coolify.io/api/v3<br>- http://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container. " /> |                     <x-forms.input placeholder="https://coolify.io" id="application.fqdn" label="Domains" | ||||||
|                 <x-forms.button wire:click="getWildcardDomain">Generate Domain |                         helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- http://app.coolify.io, https://cloud.coolify.io/dashboard<br>- http://app.coolify.io/api/v3<br>- http://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container. " /> | ||||||
|                 </x-forms.button> |                     <x-forms.button wire:click="getWildcardDomain">Generate Domain | ||||||
|             </div> |                     </x-forms.button> | ||||||
|             @if (!$application->dockerfile) |                 </div> | ||||||
|  |             @endif | ||||||
|  |             @if (!$application->dockerfile && $application->build_pack !== 'dockerimage') | ||||||
|                 <div class="flex flex-col gap-2"> |                 <div class="flex flex-col gap-2"> | ||||||
|                     <div class="flex gap-2"> |                     <div class="flex gap-2"> | ||||||
|                         <x-forms.select wire:model="application.build_pack" label="Build Pack" required> |                         <x-forms.select wire:model="application.build_pack" label="Build Pack" required> | ||||||
|                             <option value="nixpacks">Nixpacks</option> |                             <option value="nixpacks">Nixpacks</option> | ||||||
|                             <option value="static">Static</option> |                             <option value="static">Static</option> | ||||||
|                             <option value="dockerfile">Dockerfile</option> |                             <option value="dockerfile">Dockerfile</option> | ||||||
|                             <option value="dockerimage">Docker Image</option> |                             <option value="dockercompose">Docker Compose</option> | ||||||
|  |                             {{-- <option value="dockerimage">Docker Image</option> --}} | ||||||
|                         </x-forms.select> |                         </x-forms.select> | ||||||
|                         @if ($application->settings->is_static || $application->build_pack === 'static') |                         @if ($application->settings->is_static || $application->build_pack === 'static') | ||||||
|                             <x-forms.select id="application.static_image" label="Static Image" required> |                             <x-forms.select id="application.static_image" label="Static Image" required> | ||||||
| @@ -47,26 +50,27 @@ | |||||||
|                     @endif |                     @endif | ||||||
|                 </div> |                 </div> | ||||||
|             @endif |             @endif | ||||||
|             <h3>Docker Registry</h3> |             @if ($application->build_pack !== 'dockerimage' && $application->build_pack !== 'dockercompose') | ||||||
|             @if ($application->build_pack !== 'dockerimage') |                 <h3>Docker Registry</h3> | ||||||
|                 <div>Push the built image to a docker registry. More info <a class="underline" |                 <div>Push the built image to a docker registry. More info <a class="underline" | ||||||
|                         href="https://coolify.io/docs/docker-registries" target="_blank">here</a>.</div> |                         href="https://coolify.io/docs/docker-registries" target="_blank">here</a>.</div> | ||||||
|             @endif |                 <div class="flex flex-col gap-2 xl:flex-row"> | ||||||
|             <div class="flex flex-col gap-2 xl:flex-row"> |                     @if ($application->build_pack === 'dockerimage') | ||||||
|                 @if ($application->build_pack === 'dockerimage') |                         <x-forms.input id="application.docker_registry_image_name" label="Docker Image" /> | ||||||
|                     <x-forms.input id="application.docker_registry_image_name" label="Docker Image" /> |                         <x-forms.input id="application.docker_registry_image_tag" label="Docker Image Tag" /> | ||||||
|                     <x-forms.input id="application.docker_registry_image_tag" label="Docker Image Tag" /> |                     @else | ||||||
|                 @else |                         <x-forms.input id="application.docker_registry_image_name" | ||||||
|                     <x-forms.input id="application.docker_registry_image_name" |                             helper="Empty means it won't push the image to a docker registry." | ||||||
|                         helper="Empty means it won't push the image to a docker registry." |                             placeholder="Empty means it won't push the image to a docker registry." | ||||||
|                         placeholder="Empty means it won't push the image to a docker registry." label="Docker Image" /> |                             label="Docker Image" /> | ||||||
|                     <x-forms.input id="application.docker_registry_image_tag" |                         <x-forms.input id="application.docker_registry_image_tag" | ||||||
|                         placeholder="Empty means only push commit sha tag." |                             placeholder="Empty means only push commit sha tag." | ||||||
|                         helper="If set, it will tag the built image with this tag too. <br><br>Example: If you set it to 'latest', it will push the image with the commit sha tag + with the latest tag." |                             helper="If set, it will tag the built image with this tag too. <br><br>Example: If you set it to 'latest', it will push the image with the commit sha tag + with the latest tag." | ||||||
|                         label="Docker Image Tag" /> |                             label="Docker Image Tag" /> | ||||||
|                 @endif |                     @endif | ||||||
| 
 | 
 | ||||||
|             </div> |                 </div> | ||||||
|  |             @endif | ||||||
| 
 | 
 | ||||||
|             @if ($application->build_pack !== 'dockerimage') |             @if ($application->build_pack !== 'dockerimage') | ||||||
|                 <h3>Build</h3> |                 <h3>Build</h3> | ||||||
| @@ -91,7 +95,13 @@ | |||||||
|                     @if ($application->build_pack === 'dockerfile' && !$application->dockerfile) |                     @if ($application->build_pack === 'dockerfile' && !$application->dockerfile) | ||||||
|                         <x-forms.input placeholder="/Dockerfile" id="application.dockerfile_location" |                         <x-forms.input placeholder="/Dockerfile" id="application.dockerfile_location" | ||||||
|                             label="Dockerfile Location" |                             label="Dockerfile Location" | ||||||
|                             helper="It is calculated together with the Base Directory: {{ Str::start($application->base_directory . $application->dockerfile_location, '/') }}" /> |                             helper="It is calculated together with the Base Directory:<br><span class='text-warning'>{{ Str::start($application->base_directory . $application->dockerfile_location, '/') }}</span>" /> | ||||||
|  |                     @endif | ||||||
|  |                     @if ($application->build_pack === 'dockercompose') | ||||||
|  |                         <span wire:init='loadComposeFile(true)'></span> | ||||||
|  |                         <x-forms.input placeholder="/docker-compose.yml" id="application.docker_compose_location" | ||||||
|  |                             label="Docker Compose Location" | ||||||
|  |                             helper="It is calculated together with the Base Directory:<br><span class='text-warning'>{{ Str::start($application->base_directory . $application->docker_compose_location, '/') }}</span>" /> | ||||||
|                     @endif |                     @endif | ||||||
|                     @if ($application->build_pack === 'dockerfile') |                     @if ($application->build_pack === 'dockerfile') | ||||||
|                         <x-forms.input id="application.dockerfile_target_build" label="Docker Build Stage Target" |                         <x-forms.input id="application.dockerfile_target_build" label="Docker Build Stage Target" | ||||||
| @@ -108,23 +118,39 @@ | |||||||
|                     @endif |                     @endif | ||||||
|                 </div> |                 </div> | ||||||
|             @endif |             @endif | ||||||
|  |             @if ($application->build_pack === 'dockercompose') | ||||||
|  |                 <x-forms.button wire:click="loadComposeFile">Reload Compose File</x-forms.button> | ||||||
|  |                 @if (count($parsedServices) > 0) | ||||||
|  |                     @foreach (data_get($parsedServices, 'services') as $serviceName => $service) | ||||||
|  |                         @if (!collect(DATABASE_DOCKER_IMAGES)->contains(data_get($service, 'image'))) | ||||||
|  |                             <x-forms.input  helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- http://app.coolify.io, https://cloud.coolify.io/dashboard<br>- http://app.coolify.io/api/v3<br>- http://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container. " label="Domains for {{ str($serviceName)->headline() }}" | ||||||
|  |                                 id="parsedServiceDomains.{{ $serviceName }}.domain"></x-forms.input> | ||||||
|  |                         @endif | ||||||
|  |                     @endforeach | ||||||
|  |                 @endif | ||||||
|  |                 <x-forms.textarea rows="20" readonly id="application.docker_compose" | ||||||
|  |                     label="Docker Compose Content" /> | ||||||
|  |             @endif | ||||||
| 
 | 
 | ||||||
|             @if ($application->dockerfile) |             @if ($application->dockerfile) | ||||||
|                 <x-forms.textarea label="Dockerfile" id="application.dockerfile" rows="6"> </x-forms.textarea> |                 <x-forms.textarea label="Dockerfile" id="application.dockerfile" rows="6"> </x-forms.textarea> | ||||||
|             @endif |             @endif | ||||||
|             <h3>Network</h3> |             @if ($application->build_pack !== 'dockercompose') | ||||||
|             <div class="flex flex-col gap-2 xl:flex-row"> |                 <h3>Network</h3> | ||||||
|                 @if ($application->settings->is_static || $application->build_pack === 'static') |                 <div class="flex flex-col gap-2 xl:flex-row"> | ||||||
|                     <x-forms.input id="application.ports_exposes" label="Ports Exposes" readonly /> |                     @if ($application->settings->is_static || $application->build_pack === 'static') | ||||||
|                 @else |                         <x-forms.input id="application.ports_exposes" label="Ports Exposes" readonly /> | ||||||
|                     <x-forms.input placeholder="3000,3001" id="application.ports_exposes" label="Ports Exposes" required |                     @else | ||||||
|                         helper="A comma separated list of ports your application uses. The first port will be used as default healthcheck port if nothing defined in the Healthcheck menu. Be sure to set this correctly." /> |                         <x-forms.input placeholder="3000,3001" id="application.ports_exposes" label="Ports Exposes" | ||||||
|                 @endif |                             required | ||||||
|                 <x-forms.input placeholder="3000:3000" id="application.ports_mappings" label="Ports Mappings" |                             helper="A comma separated list of ports your application uses. The first port will be used as default healthcheck port if nothing defined in the Healthcheck menu. Be sure to set this correctly." /> | ||||||
|                     helper="A comma separated list of ports you would like to map to the host system. Useful when you do not want to use domains.<br><br><span class='inline-block font-bold text-warning'>Example:</span><br>3000:3000,3002:3002<br><br>Rolling update is not supported if you have a port mapped to the host." /> |                     @endif | ||||||
|             </div> |                     <x-forms.input placeholder="3000:3000" id="application.ports_mappings" label="Ports Mappings" | ||||||
|             <x-forms.textarea label="Container Labels" rows="15" id="customLabels"></x-forms.textarea> |                         helper="A comma separated list of ports you would like to map to the host system. Useful when you do not want to use domains.<br><br><span class='inline-block font-bold text-warning'>Example:</span><br>3000:3000,3002:3002<br><br>Rolling update is not supported if you have a port mapped to the host." /> | ||||||
|             <x-forms.button wire:click="resetDefaultLabels">Reset to Coolify Generated Labels</x-forms.button> |                 </div> | ||||||
|  |                 <x-forms.textarea label="Container Labels" rows="15" id="customLabels"></x-forms.textarea> | ||||||
|  |                 <x-forms.button wire:click="resetDefaultLabels">Reset to Coolify Generated Labels</x-forms.button> | ||||||
|  |             @endif | ||||||
|         </div> |         </div> | ||||||
|     </form> |     </form> | ||||||
| </div> | </div> | ||||||
|   | |||||||
| @@ -67,7 +67,7 @@ | |||||||
|                             Based on a Docker Compose |                             Based on a Docker Compose | ||||||
|                         </div> |                         </div> | ||||||
|                         <div class="description"> |                         <div class="description"> | ||||||
|                             You can deploy complex application easily with Docker Compose. |                             You can deploy complex application easily with Docker Compose, without Git. | ||||||
|                         </div> |                         </div> | ||||||
|                     </div> |                     </div> | ||||||
|                 </div> |                 </div> | ||||||
| @@ -77,7 +77,7 @@ | |||||||
|                             Based on an existing Docker Image |                             Based on an existing Docker Image | ||||||
|                         </div> |                         </div> | ||||||
|                         <div class="description"> |                         <div class="description"> | ||||||
|                             You can deploy an existing Docker Image form any Registry. |                             You can deploy an existing Docker Image form any Registry, without Git. | ||||||
|                         </div> |                         </div> | ||||||
|                     </div> |                     </div> | ||||||
|                 </div> |                 </div> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Andras Bacsai
					Andras Bacsai