commit
						5ebbd769e8
					
				
							
								
								
									
										25
									
								
								.github/workflows/fix-php-code-style-issues.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								.github/workflows/fix-php-code-style-issues.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,25 @@ | ||||
| name: Fix PHP code style issues | ||||
| 
 | ||||
| on: [push] | ||||
| 
 | ||||
| permissions: | ||||
|   contents: write | ||||
| 
 | ||||
| jobs: | ||||
|   php-code-styling: | ||||
|     runs-on: ubuntu-latest | ||||
|     timeout-minutes: 5 | ||||
| 
 | ||||
|     steps: | ||||
|       - name: Checkout code | ||||
|         uses: actions/checkout@v4 | ||||
|         with: | ||||
|           ref: ${{ github.head_ref }} | ||||
| 
 | ||||
|       - name: Fix PHP code style issues | ||||
|         uses: aglipanci/laravel-pint-action@2.4 | ||||
| 
 | ||||
|       - name: Commit changes | ||||
|         uses: stefanzweifel/git-auto-commit-action@v5 | ||||
|         with: | ||||
|           commit_message: Fix styling | ||||
| @ -3,17 +3,17 @@ | ||||
| namespace App\Actions\Application; | ||||
| 
 | ||||
| use App\Models\Application; | ||||
| use App\Models\StandaloneDocker; | ||||
| use App\Notifications\Application\StatusChanged; | ||||
| use Lorisleiva\Actions\Concerns\AsAction; | ||||
| 
 | ||||
| class StopApplication | ||||
| { | ||||
|     use AsAction; | ||||
| 
 | ||||
|     public function handle(Application $application) | ||||
|     { | ||||
|         if ($application->destination->server->isSwarm()) { | ||||
|             instant_remote_process(["docker stack rm {$application->uuid}"], $application->destination->server); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
| @ -23,7 +23,7 @@ class StopApplication | ||||
|             $servers->push($server); | ||||
|         }); | ||||
|         foreach ($servers as $server) { | ||||
|             if (!$server->isFunctional()) { | ||||
|             if (! $server->isFunctional()) { | ||||
|                 return 'Server is not functional'; | ||||
|             } | ||||
|             $containers = getCurrentApplicationContainerStatus($server, $application->id, 0); | ||||
|  | ||||
| @ -9,12 +9,13 @@ use Lorisleiva\Actions\Concerns\AsAction; | ||||
| class StopApplicationOneServer | ||||
| { | ||||
|     use AsAction; | ||||
| 
 | ||||
|     public function handle(Application $application, Server $server) | ||||
|     { | ||||
|         if ($application->destination->server->isSwarm()) { | ||||
|             return; | ||||
|         } | ||||
|         if (!$server->isFunctional()) { | ||||
|         if (! $server->isFunctional()) { | ||||
|             return 'Server is not functional'; | ||||
|         } | ||||
|         try { | ||||
| @ -32,6 +33,7 @@ class StopApplicationOneServer | ||||
|             } | ||||
|         } catch (\Exception $e) { | ||||
|             ray($e->getMessage()); | ||||
| 
 | ||||
|             return $e->getMessage(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -14,6 +14,7 @@ use Spatie\Activitylog\Models\Activity; | ||||
| class PrepareCoolifyTask | ||||
| { | ||||
|     protected Activity $activity; | ||||
| 
 | ||||
|     protected CoolifyTaskArgs $remoteProcessArgs; | ||||
| 
 | ||||
|     public function __construct(CoolifyTaskArgs $remoteProcessArgs) | ||||
| @ -28,12 +29,12 @@ class PrepareCoolifyTask | ||||
|                 ->withProperties($properties) | ||||
|                 ->performedOn($remoteProcessArgs->model) | ||||
|                 ->event($remoteProcessArgs->type) | ||||
|                 ->log("[]"); | ||||
|                 ->log('[]'); | ||||
|         } else { | ||||
|             $this->activity = activity() | ||||
|                 ->withProperties($remoteProcessArgs->toArray()) | ||||
|                 ->event($remoteProcessArgs->type) | ||||
|                 ->log("[]"); | ||||
|                 ->log('[]'); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| @ -42,6 +43,7 @@ class PrepareCoolifyTask | ||||
|         $job = new CoolifyTask($this->activity, ignore_errors: $this->remoteProcessArgs->ignore_errors, call_event_on_finish: $this->remoteProcessArgs->call_event_on_finish, call_event_data: $this->remoteProcessArgs->call_event_data); | ||||
|         dispatch($job); | ||||
|         $this->activity->refresh(); | ||||
| 
 | ||||
|         return $this->activity; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -69,7 +69,7 @@ class RunRemoteProcess | ||||
|         return collect($decoded) | ||||
|             ->sortBy(fn ($i) => $i['order']) | ||||
|             ->map(fn ($i) => $i['output']) | ||||
|             ->implode(""); | ||||
|             ->implode(''); | ||||
|     } | ||||
| 
 | ||||
|     public function __invoke(): ProcessResult | ||||
| @ -91,7 +91,7 @@ class RunRemoteProcess | ||||
|             if ($processResult->exitCode() == 0) { | ||||
|                 $status = ProcessStatus::FINISHED; | ||||
|             } | ||||
|             if ($processResult->exitCode() != 0 && !$this->ignore_errors) { | ||||
|             if ($processResult->exitCode() != 0 && ! $this->ignore_errors) { | ||||
|                 $status = ProcessStatus::ERROR; | ||||
|             } | ||||
|             // if (($processResult->exitCode() == 0 && $this->is_finished) || $this->activity->properties->get('status') === ProcessStatus::FINISHED->value) {
 | ||||
| @ -109,14 +109,14 @@ class RunRemoteProcess | ||||
|             'status' => $status->value, | ||||
|         ]); | ||||
|         $this->activity->save(); | ||||
|         if ($processResult->exitCode() != 0 && !$this->ignore_errors) { | ||||
|         if ($processResult->exitCode() != 0 && ! $this->ignore_errors) { | ||||
|             throw new \RuntimeException($processResult->errorOutput(), $processResult->exitCode()); | ||||
|         } | ||||
|         if ($this->call_event_on_finish) { | ||||
|             try { | ||||
|                 if ($this->call_event_data) { | ||||
|                     event(resolve("App\\Events\\$this->call_event_on_finish", [ | ||||
|                         "data" => $this->call_event_data, | ||||
|                         'data' => $this->call_event_data, | ||||
|                     ])); | ||||
|                 } else { | ||||
|                     event(resolve("App\\Events\\$this->call_event_on_finish", [ | ||||
| @ -127,6 +127,7 @@ class RunRemoteProcess | ||||
|                 ray($e); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return $processResult; | ||||
|     } | ||||
| 
 | ||||
| @ -182,6 +183,7 @@ class RunRemoteProcess | ||||
|         if ($description === null || count($description) === 0) { | ||||
|             return 1; | ||||
|         } | ||||
| 
 | ||||
|         return end($description)['order'] + 1; | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -4,24 +4,25 @@ namespace App\Actions\Database; | ||||
| 
 | ||||
| use App\Models\StandaloneClickhouse; | ||||
| use Illuminate\Support\Str; | ||||
| use Symfony\Component\Yaml\Yaml; | ||||
| use Lorisleiva\Actions\Concerns\AsAction; | ||||
| use Symfony\Component\Yaml\Yaml; | ||||
| 
 | ||||
| class StartClickhouse | ||||
| { | ||||
|     use AsAction; | ||||
| 
 | ||||
|     public StandaloneClickhouse $database; | ||||
| 
 | ||||
|     public array $commands = []; | ||||
| 
 | ||||
|     public string $configuration_dir; | ||||
| 
 | ||||
|     public function handle(StandaloneClickhouse $database) | ||||
|     { | ||||
|         $this->database = $database; | ||||
| 
 | ||||
| 
 | ||||
|         $container_name = $this->database->uuid; | ||||
|         $this->configuration_dir = database_configuration_dir() . '/' . $container_name; | ||||
|         $this->configuration_dir = database_configuration_dir().'/'.$container_name; | ||||
| 
 | ||||
|         $this->commands = [ | ||||
|             "echo 'Starting {$database->name}.'", | ||||
| @ -57,7 +58,7 @@ class StartClickhouse | ||||
|                         'interval' => '5s', | ||||
|                         'timeout' => '5s', | ||||
|                         'retries' => 10, | ||||
|                         'start_period' => '5s' | ||||
|                         'start_period' => '5s', | ||||
|                     ], | ||||
|                     'mem_limit' => $this->database->limits_memory, | ||||
|                     'memswap_limit' => $this->database->limits_memory_swap, | ||||
| @ -65,27 +66,27 @@ class StartClickhouse | ||||
|                     'mem_reservation' => $this->database->limits_memory_reservation, | ||||
|                     'cpus' => (float) $this->database->limits_cpus, | ||||
|                     'cpu_shares' => $this->database->limits_cpu_shares, | ||||
|                 ] | ||||
|                 ], | ||||
|             ], | ||||
|             'networks' => [ | ||||
|                 $this->database->destination->network => [ | ||||
|                     'external' => true, | ||||
|                     'name' => $this->database->destination->network, | ||||
|                     'attachable' => true, | ||||
|                 ] | ||||
|             ] | ||||
|                 ], | ||||
|             ], | ||||
|         ]; | ||||
|         if (!is_null($this->database->limits_cpuset)) { | ||||
|         if (! is_null($this->database->limits_cpuset)) { | ||||
|             data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset); | ||||
|         } | ||||
|         if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) { | ||||
|             $docker_compose['services'][$container_name]['logging'] = [ | ||||
|                 'driver' => 'fluentd', | ||||
|                 'options' => [ | ||||
|                     'fluentd-address' => "tcp://127.0.0.1:24224", | ||||
|                     'fluentd-async' => "true", | ||||
|                     'fluentd-sub-second-precision' => "true", | ||||
|                 ] | ||||
|                     'fluentd-address' => 'tcp://127.0.0.1:24224', | ||||
|                     'fluentd-async' => 'true', | ||||
|                     'fluentd-sub-second-precision' => 'true', | ||||
|                 ], | ||||
|             ]; | ||||
|         } | ||||
|         if (count($this->database->ports_mappings_array) > 0) { | ||||
| @ -111,6 +112,7 @@ class StartClickhouse | ||||
|         $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull"; | ||||
|         $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; | ||||
|         $this->commands[] = "echo 'Database started.'"; | ||||
| 
 | ||||
|         return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged'); | ||||
|     } | ||||
| 
 | ||||
| @ -119,12 +121,13 @@ class StartClickhouse | ||||
|         $local_persistent_volumes = []; | ||||
|         foreach ($this->database->persistentStorages as $persistentStorage) { | ||||
|             if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) { | ||||
|                 $local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path; | ||||
|                 $local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path; | ||||
|             } else { | ||||
|                 $volume_name = $persistentStorage->name; | ||||
|                 $local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path; | ||||
|                 $local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return $local_persistent_volumes; | ||||
|     } | ||||
| 
 | ||||
| @ -141,6 +144,7 @@ class StartClickhouse | ||||
|                 'external' => false, | ||||
|             ]; | ||||
|         } | ||||
| 
 | ||||
|         return $local_persistent_volumes_names; | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -69,19 +69,19 @@ class StartDatabaseProxy | ||||
|         } | ||||
|         if ($type === 'App\Models\StandaloneRedis') { | ||||
|             $internalPort = 6379; | ||||
|         } else if ($type === 'App\Models\StandalonePostgresql') { | ||||
|         } elseif ($type === 'App\Models\StandalonePostgresql') { | ||||
|             $internalPort = 5432; | ||||
|         } else if ($type === 'App\Models\StandaloneMongodb') { | ||||
|         } elseif ($type === 'App\Models\StandaloneMongodb') { | ||||
|             $internalPort = 27017; | ||||
|         } else if ($type === 'App\Models\StandaloneMysql') { | ||||
|         } elseif ($type === 'App\Models\StandaloneMysql') { | ||||
|             $internalPort = 3306; | ||||
|         } else if ($type === 'App\Models\StandaloneMariadb') { | ||||
|         } elseif ($type === 'App\Models\StandaloneMariadb') { | ||||
|             $internalPort = 3306; | ||||
|         } else if ($type === 'App\Models\StandaloneKeydb') { | ||||
|         } elseif ($type === 'App\Models\StandaloneKeydb') { | ||||
|             $internalPort = 6379; | ||||
|         } else if ($type === 'App\Models\StandaloneDragonfly') { | ||||
|         } elseif ($type === 'App\Models\StandaloneDragonfly') { | ||||
|             $internalPort = 6379; | ||||
|         } else if ($type === 'App\Models\StandaloneClickhouse') { | ||||
|         } elseif ($type === 'App\Models\StandaloneClickhouse') { | ||||
|             $internalPort = 9000; | ||||
|         } | ||||
|         $configuration_dir = database_proxy_dir($database->uuid); | ||||
| @ -101,7 +101,7 @@ class StartDatabaseProxy | ||||
|        } | ||||
|     } | ||||
|     EOF; | ||||
|         $dockerfile = <<< EOF | ||||
|         $dockerfile = <<< 'EOF' | ||||
|     FROM nginx:stable-alpine | ||||
| 
 | ||||
|     COPY nginx.conf /etc/nginx/nginx.conf | ||||
| @ -113,7 +113,7 @@ class StartDatabaseProxy | ||||
|                         'context' => $configuration_dir, | ||||
|                         'dockerfile' => 'Dockerfile', | ||||
|                     ], | ||||
|                     'image' => "nginx:stable-alpine", | ||||
|                     'image' => 'nginx:stable-alpine', | ||||
|                     'container_name' => $proxyContainerName, | ||||
|                     'restart' => RESTART_MODE, | ||||
|                     'ports' => [ | ||||
| @ -130,17 +130,17 @@ class StartDatabaseProxy | ||||
|                         'interval' => '5s', | ||||
|                         'timeout' => '5s', | ||||
|                         'retries' => 3, | ||||
|                         'start_period' => '1s' | ||||
|                         'start_period' => '1s', | ||||
|                     ], | ||||
|                 ], | ||||
|                 ] | ||||
|             ], | ||||
|             'networks' => [ | ||||
|                 $network => [ | ||||
|                     'external' => true, | ||||
|                     'name' => $network, | ||||
|                     'attachable' => true, | ||||
|                 ] | ||||
|             ] | ||||
|                 ], | ||||
|             ], | ||||
|         ]; | ||||
|         $dockercompose_base64 = base64_encode(Yaml::dump($docker_compose, 4, 2)); | ||||
|         $nginxconf_base64 = base64_encode($nginxconf); | ||||
|  | ||||
| @ -3,19 +3,19 @@ | ||||
| namespace App\Actions\Database; | ||||
| 
 | ||||
| use App\Models\StandaloneDragonfly; | ||||
| use Illuminate\Support\Facades\Storage; | ||||
| use Illuminate\Support\Str; | ||||
| use Symfony\Component\Yaml\Yaml; | ||||
| use Lorisleiva\Actions\Concerns\AsAction; | ||||
| use Symfony\Component\Yaml\Yaml; | ||||
| 
 | ||||
| class StartDragonfly | ||||
| { | ||||
|     use AsAction; | ||||
| 
 | ||||
|     public StandaloneDragonfly $database; | ||||
|     public array $commands = []; | ||||
|     public string $configuration_dir; | ||||
| 
 | ||||
|     public array $commands = []; | ||||
| 
 | ||||
|     public string $configuration_dir; | ||||
| 
 | ||||
|     public function handle(StandaloneDragonfly $database) | ||||
|     { | ||||
| @ -24,7 +24,7 @@ class StartDragonfly | ||||
|         $startCommand = "dragonfly --requirepass {$this->database->dragonfly_password}"; | ||||
| 
 | ||||
|         $container_name = $this->database->uuid; | ||||
|         $this->configuration_dir = database_configuration_dir() . '/' . $container_name; | ||||
|         $this->configuration_dir = database_configuration_dir().'/'.$container_name; | ||||
| 
 | ||||
|         $this->commands = [ | ||||
|             "echo 'Starting {$database->name}.'", | ||||
| @ -48,7 +48,7 @@ class StartDragonfly | ||||
|                         $this->database->destination->network, | ||||
|                     ], | ||||
|                     'ulimits' => [ | ||||
|                         'memlock'=> '-1' | ||||
|                         'memlock' => '-1', | ||||
|                     ], | ||||
|                     'labels' => [ | ||||
|                         'coolify.managed' => 'true', | ||||
| @ -58,7 +58,7 @@ class StartDragonfly | ||||
|                         'interval' => '5s', | ||||
|                         'timeout' => '5s', | ||||
|                         'retries' => 10, | ||||
|                         'start_period' => '5s' | ||||
|                         'start_period' => '5s', | ||||
|                     ], | ||||
|                     'mem_limit' => $this->database->limits_memory, | ||||
|                     'memswap_limit' => $this->database->limits_memory_swap, | ||||
| @ -66,27 +66,27 @@ class StartDragonfly | ||||
|                     'mem_reservation' => $this->database->limits_memory_reservation, | ||||
|                     'cpus' => (float) $this->database->limits_cpus, | ||||
|                     'cpu_shares' => $this->database->limits_cpu_shares, | ||||
|                 ] | ||||
|                 ], | ||||
|             ], | ||||
|             'networks' => [ | ||||
|                 $this->database->destination->network => [ | ||||
|                     'external' => true, | ||||
|                     'name' => $this->database->destination->network, | ||||
|                     'attachable' => true, | ||||
|                 ] | ||||
|             ] | ||||
|                 ], | ||||
|             ], | ||||
|         ]; | ||||
|         if (!is_null($this->database->limits_cpuset)) { | ||||
|         if (! is_null($this->database->limits_cpuset)) { | ||||
|             data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset); | ||||
|         } | ||||
|         if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) { | ||||
|             $docker_compose['services'][$container_name]['logging'] = [ | ||||
|                 'driver' => 'fluentd', | ||||
|                 'options' => [ | ||||
|                     'fluentd-address' => "tcp://127.0.0.1:24224", | ||||
|                     'fluentd-async' => "true", | ||||
|                     'fluentd-sub-second-precision' => "true", | ||||
|                 ] | ||||
|                     'fluentd-address' => 'tcp://127.0.0.1:24224', | ||||
|                     'fluentd-async' => 'true', | ||||
|                     'fluentd-sub-second-precision' => 'true', | ||||
|                 ], | ||||
|             ]; | ||||
|         } | ||||
|         if (count($this->database->ports_mappings_array) > 0) { | ||||
| @ -112,6 +112,7 @@ class StartDragonfly | ||||
|         $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull"; | ||||
|         $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; | ||||
|         $this->commands[] = "echo 'Database started.'"; | ||||
| 
 | ||||
|         return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged'); | ||||
|     } | ||||
| 
 | ||||
| @ -120,12 +121,13 @@ class StartDragonfly | ||||
|         $local_persistent_volumes = []; | ||||
|         foreach ($this->database->persistentStorages as $persistentStorage) { | ||||
|             if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) { | ||||
|                 $local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path; | ||||
|                 $local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path; | ||||
|             } else { | ||||
|                 $volume_name = $persistentStorage->name; | ||||
|                 $local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path; | ||||
|                 $local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return $local_persistent_volumes; | ||||
|     } | ||||
| 
 | ||||
| @ -142,6 +144,7 @@ class StartDragonfly | ||||
|                 'external' => false, | ||||
|             ]; | ||||
|         } | ||||
| 
 | ||||
|         return $local_persistent_volumes_names; | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -5,17 +5,18 @@ namespace App\Actions\Database; | ||||
| use App\Models\StandaloneKeydb; | ||||
| use Illuminate\Support\Facades\Storage; | ||||
| use Illuminate\Support\Str; | ||||
| use Symfony\Component\Yaml\Yaml; | ||||
| use Lorisleiva\Actions\Concerns\AsAction; | ||||
| use Symfony\Component\Yaml\Yaml; | ||||
| 
 | ||||
| class StartKeydb | ||||
| { | ||||
|     use AsAction; | ||||
| 
 | ||||
|     public StandaloneKeydb $database; | ||||
|     public array $commands = []; | ||||
|     public string $configuration_dir; | ||||
| 
 | ||||
|     public array $commands = []; | ||||
| 
 | ||||
|     public string $configuration_dir; | ||||
| 
 | ||||
|     public function handle(StandaloneKeydb $database) | ||||
|     { | ||||
| @ -24,7 +25,7 @@ class StartKeydb | ||||
|         $startCommand = "keydb-server --requirepass {$this->database->keydb_password} --appendonly yes"; | ||||
| 
 | ||||
|         $container_name = $this->database->uuid; | ||||
|         $this->configuration_dir = database_configuration_dir() . '/' . $container_name; | ||||
|         $this->configuration_dir = database_configuration_dir().'/'.$container_name; | ||||
| 
 | ||||
|         $this->commands = [ | ||||
|             "echo 'Starting {$database->name}.'", | ||||
| @ -56,7 +57,7 @@ class StartKeydb | ||||
|                         'interval' => '5s', | ||||
|                         'timeout' => '5s', | ||||
|                         'retries' => 10, | ||||
|                         'start_period' => '5s' | ||||
|                         'start_period' => '5s', | ||||
|                     ], | ||||
|                     'mem_limit' => $this->database->limits_memory, | ||||
|                     'memswap_limit' => $this->database->limits_memory_swap, | ||||
| @ -64,27 +65,27 @@ class StartKeydb | ||||
|                     'mem_reservation' => $this->database->limits_memory_reservation, | ||||
|                     'cpus' => (float) $this->database->limits_cpus, | ||||
|                     'cpu_shares' => $this->database->limits_cpu_shares, | ||||
|                 ] | ||||
|                 ], | ||||
|             ], | ||||
|             'networks' => [ | ||||
|                 $this->database->destination->network => [ | ||||
|                     'external' => true, | ||||
|                     'name' => $this->database->destination->network, | ||||
|                     'attachable' => true, | ||||
|                 ] | ||||
|             ] | ||||
|                 ], | ||||
|             ], | ||||
|         ]; | ||||
|         if (!is_null($this->database->limits_cpuset)) { | ||||
|         if (! is_null($this->database->limits_cpuset)) { | ||||
|             data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset); | ||||
|         } | ||||
|         if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) { | ||||
|             $docker_compose['services'][$container_name]['logging'] = [ | ||||
|                 'driver' => 'fluentd', | ||||
|                 'options' => [ | ||||
|                     'fluentd-address' => "tcp://127.0.0.1:24224", | ||||
|                     'fluentd-async' => "true", | ||||
|                     'fluentd-sub-second-precision' => "true", | ||||
|                 ] | ||||
|                     'fluentd-address' => 'tcp://127.0.0.1:24224', | ||||
|                     'fluentd-async' => 'true', | ||||
|                     'fluentd-sub-second-precision' => 'true', | ||||
|                 ], | ||||
|             ]; | ||||
|         } | ||||
|         if (count($this->database->ports_mappings_array) > 0) { | ||||
| @ -101,10 +102,10 @@ class StartKeydb | ||||
|         if (count($volume_names) > 0) { | ||||
|             $docker_compose['volumes'] = $volume_names; | ||||
|         } | ||||
|         if (!is_null($this->database->keydb_conf) || !empty($this->database->keydb_conf)) { | ||||
|         if (! is_null($this->database->keydb_conf) || ! empty($this->database->keydb_conf)) { | ||||
|             $docker_compose['services'][$container_name]['volumes'][] = [ | ||||
|                 'type' => 'bind', | ||||
|                 'source' => $this->configuration_dir . '/keydb.conf', | ||||
|                 'source' => $this->configuration_dir.'/keydb.conf', | ||||
|                 'target' => '/etc/keydb/keydb.conf', | ||||
|                 'read_only' => true, | ||||
|             ]; | ||||
| @ -119,6 +120,7 @@ class StartKeydb | ||||
|         $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull"; | ||||
|         $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; | ||||
|         $this->commands[] = "echo 'Database started.'"; | ||||
| 
 | ||||
|         return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged'); | ||||
|     } | ||||
| 
 | ||||
| @ -127,12 +129,13 @@ class StartKeydb | ||||
|         $local_persistent_volumes = []; | ||||
|         foreach ($this->database->persistentStorages as $persistentStorage) { | ||||
|             if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) { | ||||
|                 $local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path; | ||||
|                 $local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path; | ||||
|             } else { | ||||
|                 $volume_name = $persistentStorage->name; | ||||
|                 $local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path; | ||||
|                 $local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return $local_persistent_volumes; | ||||
|     } | ||||
| 
 | ||||
| @ -149,6 +152,7 @@ class StartKeydb | ||||
|                 'external' => false, | ||||
|             ]; | ||||
|         } | ||||
| 
 | ||||
|         return $local_persistent_volumes_names; | ||||
|     } | ||||
| 
 | ||||
| @ -165,6 +169,7 @@ class StartKeydb | ||||
| 
 | ||||
|         return $environment_variables->all(); | ||||
|     } | ||||
| 
 | ||||
|     private function add_custom_keydb() | ||||
|     { | ||||
|         if (is_null($this->database->keydb_conf) || empty($this->database->keydb_conf)) { | ||||
|  | ||||
| @ -4,15 +4,17 @@ namespace App\Actions\Database; | ||||
| 
 | ||||
| use App\Models\StandaloneMariadb; | ||||
| use Illuminate\Support\Str; | ||||
| use Symfony\Component\Yaml\Yaml; | ||||
| use Lorisleiva\Actions\Concerns\AsAction; | ||||
| use Symfony\Component\Yaml\Yaml; | ||||
| 
 | ||||
| class StartMariadb | ||||
| { | ||||
|     use AsAction; | ||||
| 
 | ||||
|     public StandaloneMariadb $database; | ||||
| 
 | ||||
|     public array $commands = []; | ||||
| 
 | ||||
|     public string $configuration_dir; | ||||
| 
 | ||||
|     public function handle(StandaloneMariadb $database) | ||||
| @ -20,7 +22,7 @@ class StartMariadb | ||||
|         $this->database = $database; | ||||
| 
 | ||||
|         $container_name = $this->database->uuid; | ||||
|         $this->configuration_dir = database_configuration_dir() . '/' . $container_name; | ||||
|         $this->configuration_dir = database_configuration_dir().'/'.$container_name; | ||||
| 
 | ||||
|         $this->commands = [ | ||||
|             "echo 'Starting {$database->name}.'", | ||||
| @ -46,11 +48,11 @@ class StartMariadb | ||||
|                         'coolify.managed' => 'true', | ||||
|                     ], | ||||
|                     'healthcheck' => [ | ||||
|                         'test' => ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"], | ||||
|                         'test' => ['CMD', 'healthcheck.sh', '--connect', '--innodb_initialized'], | ||||
|                         'interval' => '5s', | ||||
|                         'timeout' => '5s', | ||||
|                         'retries' => 10, | ||||
|                         'start_period' => '5s' | ||||
|                         'start_period' => '5s', | ||||
|                     ], | ||||
|                     'mem_limit' => $this->database->limits_memory, | ||||
|                     'memswap_limit' => $this->database->limits_memory_swap, | ||||
| @ -58,27 +60,27 @@ class StartMariadb | ||||
|                     'mem_reservation' => $this->database->limits_memory_reservation, | ||||
|                     'cpus' => (float) $this->database->limits_cpus, | ||||
|                     'cpu_shares' => $this->database->limits_cpu_shares, | ||||
|                 ] | ||||
|                 ], | ||||
|             ], | ||||
|             'networks' => [ | ||||
|                 $this->database->destination->network => [ | ||||
|                     'external' => true, | ||||
|                     'name' => $this->database->destination->network, | ||||
|                     'attachable' => true, | ||||
|                 ] | ||||
|             ] | ||||
|                 ], | ||||
|             ], | ||||
|         ]; | ||||
|         if (!is_null($this->database->limits_cpuset)) { | ||||
|         if (! is_null($this->database->limits_cpuset)) { | ||||
|             data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset); | ||||
|         } | ||||
|         if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) { | ||||
|             $docker_compose['services'][$container_name]['logging'] = [ | ||||
|                 'driver' => 'fluentd', | ||||
|                 'options' => [ | ||||
|                     'fluentd-address' => "tcp://127.0.0.1:24224", | ||||
|                     'fluentd-async' => "true", | ||||
|                     'fluentd-sub-second-precision' => "true", | ||||
|                 ] | ||||
|                     'fluentd-address' => 'tcp://127.0.0.1:24224', | ||||
|                     'fluentd-async' => 'true', | ||||
|                     'fluentd-sub-second-precision' => 'true', | ||||
|                 ], | ||||
|             ]; | ||||
|         } | ||||
|         if (count($this->database->ports_mappings_array) > 0) { | ||||
| @ -95,10 +97,10 @@ class StartMariadb | ||||
|         if (count($volume_names) > 0) { | ||||
|             $docker_compose['volumes'] = $volume_names; | ||||
|         } | ||||
|         if (!is_null($this->database->mariadb_conf) || !empty($this->database->mariadb_conf)) { | ||||
|         if (! is_null($this->database->mariadb_conf) || ! empty($this->database->mariadb_conf)) { | ||||
|             $docker_compose['services'][$container_name]['volumes'][] = [ | ||||
|                 'type' => 'bind', | ||||
|                 'source' => $this->configuration_dir . '/custom-config.cnf', | ||||
|                 'source' => $this->configuration_dir.'/custom-config.cnf', | ||||
|                 'target' => '/etc/mysql/conf.d/custom-config.cnf', | ||||
|                 'read_only' => true, | ||||
|             ]; | ||||
| @ -112,6 +114,7 @@ class StartMariadb | ||||
|         $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull"; | ||||
|         $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; | ||||
|         $this->commands[] = "echo 'Database started.'"; | ||||
| 
 | ||||
|         return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged'); | ||||
|     } | ||||
| 
 | ||||
| @ -120,12 +123,13 @@ class StartMariadb | ||||
|         $local_persistent_volumes = []; | ||||
|         foreach ($this->database->persistentStorages as $persistentStorage) { | ||||
|             if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) { | ||||
|                 $local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path; | ||||
|                 $local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path; | ||||
|             } else { | ||||
|                 $volume_name = $persistentStorage->name; | ||||
|                 $local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path; | ||||
|                 $local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return $local_persistent_volumes; | ||||
|     } | ||||
| 
 | ||||
| @ -142,6 +146,7 @@ class StartMariadb | ||||
|                 'external' => false, | ||||
|             ]; | ||||
|         } | ||||
| 
 | ||||
|         return $local_persistent_volumes_names; | ||||
|     } | ||||
| 
 | ||||
| @ -166,8 +171,10 @@ class StartMariadb | ||||
|         if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MARIADB_PASSWORD'))->isEmpty()) { | ||||
|             $environment_variables->push("MARIADB_PASSWORD={$this->database->mariadb_password}"); | ||||
|         } | ||||
| 
 | ||||
|         return $environment_variables->all(); | ||||
|     } | ||||
| 
 | ||||
|     private function add_custom_mysql() | ||||
|     { | ||||
|         if (is_null($this->database->mariadb_conf) || empty($this->database->mariadb_conf)) { | ||||
|  | ||||
| @ -4,25 +4,27 @@ namespace App\Actions\Database; | ||||
| 
 | ||||
| use App\Models\StandaloneMongodb; | ||||
| use Illuminate\Support\Str; | ||||
| use Symfony\Component\Yaml\Yaml; | ||||
| use Lorisleiva\Actions\Concerns\AsAction; | ||||
| use Symfony\Component\Yaml\Yaml; | ||||
| 
 | ||||
| class StartMongodb | ||||
| { | ||||
|     use AsAction; | ||||
| 
 | ||||
|     public StandaloneMongodb $database; | ||||
| 
 | ||||
|     public array $commands = []; | ||||
| 
 | ||||
|     public string $configuration_dir; | ||||
| 
 | ||||
|     public function handle(StandaloneMongodb $database) | ||||
|     { | ||||
|         $this->database = $database; | ||||
| 
 | ||||
|         $startCommand = "mongod"; | ||||
|         $startCommand = 'mongod'; | ||||
| 
 | ||||
|         $container_name = $this->database->uuid; | ||||
|         $this->configuration_dir = database_configuration_dir() . '/' . $container_name; | ||||
|         $this->configuration_dir = database_configuration_dir().'/'.$container_name; | ||||
| 
 | ||||
|         $this->commands = [ | ||||
|             "echo 'Starting {$database->name}.'", | ||||
| @ -51,14 +53,14 @@ class StartMongodb | ||||
|                     ], | ||||
|                     'healthcheck' => [ | ||||
|                         'test' => [ | ||||
|                             "CMD", | ||||
|                             "echo", | ||||
|                             "ok" | ||||
|                             'CMD', | ||||
|                             'echo', | ||||
|                             'ok', | ||||
|                         ], | ||||
|                         'interval' => '5s', | ||||
|                         'timeout' => '5s', | ||||
|                         'retries' => 10, | ||||
|                         'start_period' => '5s' | ||||
|                         'start_period' => '5s', | ||||
|                     ], | ||||
|                     'mem_limit' => $this->database->limits_memory, | ||||
|                     'memswap_limit' => $this->database->limits_memory_swap, | ||||
| @ -66,27 +68,27 @@ class StartMongodb | ||||
|                     'mem_reservation' => $this->database->limits_memory_reservation, | ||||
|                     'cpus' => (float) $this->database->limits_cpus, | ||||
|                     'cpu_shares' => $this->database->limits_cpu_shares, | ||||
|                 ] | ||||
|                 ], | ||||
|             ], | ||||
|             'networks' => [ | ||||
|                 $this->database->destination->network => [ | ||||
|                     'external' => true, | ||||
|                     'name' => $this->database->destination->network, | ||||
|                     'attachable' => true, | ||||
|                 ] | ||||
|             ] | ||||
|                 ], | ||||
|             ], | ||||
|         ]; | ||||
|         if (!is_null($this->database->limits_cpuset)) { | ||||
|         if (! is_null($this->database->limits_cpuset)) { | ||||
|             data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset); | ||||
|         } | ||||
|         if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) { | ||||
|             $docker_compose['services'][$container_name]['logging'] = [ | ||||
|                 'driver' => 'fluentd', | ||||
|                 'options' => [ | ||||
|                     'fluentd-address' => "tcp://127.0.0.1:24224", | ||||
|                     'fluentd-async' => "true", | ||||
|                     'fluentd-sub-second-precision' => "true", | ||||
|                 ] | ||||
|                     'fluentd-address' => 'tcp://127.0.0.1:24224', | ||||
|                     'fluentd-async' => 'true', | ||||
|                     'fluentd-sub-second-precision' => 'true', | ||||
|                 ], | ||||
|             ]; | ||||
|         } | ||||
|         if (count($this->database->ports_mappings_array) > 0) { | ||||
| @ -103,19 +105,19 @@ class StartMongodb | ||||
|         if (count($volume_names) > 0) { | ||||
|             $docker_compose['volumes'] = $volume_names; | ||||
|         } | ||||
|         if (!is_null($this->database->mongo_conf) || !empty($this->database->mongo_conf)) { | ||||
|         if (! is_null($this->database->mongo_conf) || ! empty($this->database->mongo_conf)) { | ||||
|             $docker_compose['services'][$container_name]['volumes'][] = [ | ||||
|                 'type' => 'bind', | ||||
|                 'source' => $this->configuration_dir . '/mongod.conf', | ||||
|                 'source' => $this->configuration_dir.'/mongod.conf', | ||||
|                 'target' => '/etc/mongo/mongod.conf', | ||||
|                 'read_only' => true, | ||||
|             ]; | ||||
|             $docker_compose['services'][$container_name]['command'] =  $startCommand . ' --config /etc/mongo/mongod.conf'; | ||||
|             $docker_compose['services'][$container_name]['command'] = $startCommand.' --config /etc/mongo/mongod.conf'; | ||||
|         } | ||||
|         $this->add_default_database(); | ||||
|         $docker_compose['services'][$container_name]['volumes'][] = [ | ||||
|             'type' => 'bind', | ||||
|             'source' => $this->configuration_dir . '/docker-entrypoint-initdb.d', | ||||
|             'source' => $this->configuration_dir.'/docker-entrypoint-initdb.d', | ||||
|             'target' => '/docker-entrypoint-initdb.d', | ||||
|             'read_only' => true, | ||||
|         ]; | ||||
| @ -129,6 +131,7 @@ class StartMongodb | ||||
|         $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull"; | ||||
|         $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; | ||||
|         $this->commands[] = "echo 'Database started.'"; | ||||
| 
 | ||||
|         return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged'); | ||||
|     } | ||||
| 
 | ||||
| @ -137,12 +140,13 @@ class StartMongodb | ||||
|         $local_persistent_volumes = []; | ||||
|         foreach ($this->database->persistentStorages as $persistentStorage) { | ||||
|             if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) { | ||||
|                 $local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path; | ||||
|                 $local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path; | ||||
|             } else { | ||||
|                 $volume_name = $persistentStorage->name; | ||||
|                 $local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path; | ||||
|                 $local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return $local_persistent_volumes; | ||||
|     } | ||||
| 
 | ||||
| @ -159,6 +163,7 @@ class StartMongodb | ||||
|                 'external' => false, | ||||
|             ]; | ||||
|         } | ||||
| 
 | ||||
|         return $local_persistent_volumes_names; | ||||
|     } | ||||
| 
 | ||||
| @ -180,8 +185,10 @@ class StartMongodb | ||||
|         if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MONGO_INITDB_DATABASE'))->isEmpty()) { | ||||
|             $environment_variables->push("MONGO_INITDB_DATABASE={$this->database->mongo_initdb_database}"); | ||||
|         } | ||||
| 
 | ||||
|         return $environment_variables->all(); | ||||
|     } | ||||
| 
 | ||||
|     private function add_custom_mongo_conf() | ||||
|     { | ||||
|         if (is_null($this->database->mongo_conf) || empty($this->database->mongo_conf)) { | ||||
| @ -192,6 +199,7 @@ class StartMongodb | ||||
|         $content_base64 = base64_encode($content); | ||||
|         $this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/{$filename} > /dev/null"; | ||||
|     } | ||||
| 
 | ||||
|     private function add_default_database() | ||||
|     { | ||||
|         $content = "db = db.getSiblingDB(\"{$this->database->mongo_initdb_database}\");db.createCollection('init_collection');db.createUser({user: \"{$this->database->mongo_initdb_root_username}\", pwd: \"{$this->database->mongo_initdb_root_password}\",roles: [{role:\"readWrite\",db:\"{$this->database->mongo_initdb_database}\"}]});"; | ||||
|  | ||||
| @ -4,15 +4,17 @@ namespace App\Actions\Database; | ||||
| 
 | ||||
| use App\Models\StandaloneMysql; | ||||
| use Illuminate\Support\Str; | ||||
| use Symfony\Component\Yaml\Yaml; | ||||
| use Lorisleiva\Actions\Concerns\AsAction; | ||||
| use Symfony\Component\Yaml\Yaml; | ||||
| 
 | ||||
| class StartMysql | ||||
| { | ||||
|     use AsAction; | ||||
| 
 | ||||
|     public StandaloneMysql $database; | ||||
| 
 | ||||
|     public array $commands = []; | ||||
| 
 | ||||
|     public string $configuration_dir; | ||||
| 
 | ||||
|     public function handle(StandaloneMysql $database) | ||||
| @ -20,7 +22,7 @@ class StartMysql | ||||
|         $this->database = $database; | ||||
| 
 | ||||
|         $container_name = $this->database->uuid; | ||||
|         $this->configuration_dir = database_configuration_dir() . '/' . $container_name; | ||||
|         $this->configuration_dir = database_configuration_dir().'/'.$container_name; | ||||
| 
 | ||||
|         $this->commands = [ | ||||
|             "echo 'Starting {$database->name}.'", | ||||
| @ -46,11 +48,11 @@ class StartMysql | ||||
|                         'coolify.managed' => 'true', | ||||
|                     ], | ||||
|                     'healthcheck' => [ | ||||
|                         'test' => ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p{$this->database->mysql_root_password}"], | ||||
|                         'test' => ['CMD', 'mysqladmin', 'ping', '-h', 'localhost', '-u', 'root', "-p{$this->database->mysql_root_password}"], | ||||
|                         'interval' => '5s', | ||||
|                         'timeout' => '5s', | ||||
|                         'retries' => 10, | ||||
|                         'start_period' => '5s' | ||||
|                         'start_period' => '5s', | ||||
|                     ], | ||||
|                     'mem_limit' => $this->database->limits_memory, | ||||
|                     'memswap_limit' => $this->database->limits_memory_swap, | ||||
| @ -58,27 +60,27 @@ class StartMysql | ||||
|                     'mem_reservation' => $this->database->limits_memory_reservation, | ||||
|                     'cpus' => (float) $this->database->limits_cpus, | ||||
|                     'cpu_shares' => $this->database->limits_cpu_shares, | ||||
|                 ] | ||||
|                 ], | ||||
|             ], | ||||
|             'networks' => [ | ||||
|                 $this->database->destination->network => [ | ||||
|                     'external' => true, | ||||
|                     'name' => $this->database->destination->network, | ||||
|                     'attachable' => true, | ||||
|                 ] | ||||
|             ] | ||||
|                 ], | ||||
|             ], | ||||
|         ]; | ||||
|         if (!is_null($this->database->limits_cpuset)) { | ||||
|         if (! is_null($this->database->limits_cpuset)) { | ||||
|             data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset); | ||||
|         } | ||||
|         if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) { | ||||
|             $docker_compose['services'][$container_name]['logging'] = [ | ||||
|                 'driver' => 'fluentd', | ||||
|                 'options' => [ | ||||
|                     'fluentd-address' => "tcp://127.0.0.1:24224", | ||||
|                     'fluentd-async' => "true", | ||||
|                     'fluentd-sub-second-precision' => "true", | ||||
|                 ] | ||||
|                     'fluentd-address' => 'tcp://127.0.0.1:24224', | ||||
|                     'fluentd-async' => 'true', | ||||
|                     'fluentd-sub-second-precision' => 'true', | ||||
|                 ], | ||||
|             ]; | ||||
|         } | ||||
|         if (count($this->database->ports_mappings_array) > 0) { | ||||
| @ -95,10 +97,10 @@ class StartMysql | ||||
|         if (count($volume_names) > 0) { | ||||
|             $docker_compose['volumes'] = $volume_names; | ||||
|         } | ||||
|         if (!is_null($this->database->mysql_conf) || !empty($this->database->mysql_conf)) { | ||||
|         if (! is_null($this->database->mysql_conf) || ! empty($this->database->mysql_conf)) { | ||||
|             $docker_compose['services'][$container_name]['volumes'][] = [ | ||||
|                 'type' => 'bind', | ||||
|                 'source' => $this->configuration_dir . '/custom-config.cnf', | ||||
|                 'source' => $this->configuration_dir.'/custom-config.cnf', | ||||
|                 'target' => '/etc/mysql/conf.d/custom-config.cnf', | ||||
|                 'read_only' => true, | ||||
|             ]; | ||||
| @ -112,7 +114,8 @@ class StartMysql | ||||
|         $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull"; | ||||
|         $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; | ||||
|         $this->commands[] = "echo 'Database started.'"; | ||||
|         return remote_process($this->commands, $database->destination->server,callEventOnFinish: 'DatabaseStatusChanged'); | ||||
| 
 | ||||
|         return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged'); | ||||
|     } | ||||
| 
 | ||||
|     private function generate_local_persistent_volumes() | ||||
| @ -120,12 +123,13 @@ class StartMysql | ||||
|         $local_persistent_volumes = []; | ||||
|         foreach ($this->database->persistentStorages as $persistentStorage) { | ||||
|             if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) { | ||||
|                 $local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path; | ||||
|                 $local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path; | ||||
|             } else { | ||||
|                 $volume_name = $persistentStorage->name; | ||||
|                 $local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path; | ||||
|                 $local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return $local_persistent_volumes; | ||||
|     } | ||||
| 
 | ||||
| @ -142,6 +146,7 @@ class StartMysql | ||||
|                 'external' => false, | ||||
|             ]; | ||||
|         } | ||||
| 
 | ||||
|         return $local_persistent_volumes_names; | ||||
|     } | ||||
| 
 | ||||
| @ -166,8 +171,10 @@ class StartMysql | ||||
|         if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MYSQL_PASSWORD'))->isEmpty()) { | ||||
|             $environment_variables->push("MYSQL_PASSWORD={$this->database->mysql_password}"); | ||||
|         } | ||||
| 
 | ||||
|         return $environment_variables->all(); | ||||
|     } | ||||
| 
 | ||||
|     private function add_custom_mysql() | ||||
|     { | ||||
|         if (is_null($this->database->mysql_conf) || empty($this->database->mysql_conf)) { | ||||
|  | ||||
| @ -4,28 +4,31 @@ namespace App\Actions\Database; | ||||
| 
 | ||||
| use App\Models\StandalonePostgresql; | ||||
| use Illuminate\Support\Str; | ||||
| use Symfony\Component\Yaml\Yaml; | ||||
| use Lorisleiva\Actions\Concerns\AsAction; | ||||
| use Symfony\Component\Yaml\Yaml; | ||||
| 
 | ||||
| class StartPostgresql | ||||
| { | ||||
|     use AsAction; | ||||
| 
 | ||||
|     public StandalonePostgresql $database; | ||||
| 
 | ||||
|     public array $commands = []; | ||||
| 
 | ||||
|     public array $init_scripts = []; | ||||
| 
 | ||||
|     public string $configuration_dir; | ||||
| 
 | ||||
|     public function handle(StandalonePostgresql $database) | ||||
|     { | ||||
|         $this->database = $database; | ||||
|         $container_name = $this->database->uuid; | ||||
|         $this->configuration_dir = database_configuration_dir() . '/' . $container_name; | ||||
|         $this->configuration_dir = database_configuration_dir().'/'.$container_name; | ||||
| 
 | ||||
|         $this->commands = [ | ||||
|             "echo 'Starting {$database->name}.'", | ||||
|             "mkdir -p $this->configuration_dir", | ||||
|             "mkdir -p $this->configuration_dir/docker-entrypoint-initdb.d/" | ||||
|             "mkdir -p $this->configuration_dir/docker-entrypoint-initdb.d/", | ||||
|         ]; | ||||
| 
 | ||||
|         $persistent_storages = $this->generate_local_persistent_volumes(); | ||||
| @ -50,13 +53,13 @@ class StartPostgresql | ||||
|                     ], | ||||
|                     'healthcheck' => [ | ||||
|                         'test' => [ | ||||
|                             "CMD-SHELL", | ||||
|                             "psql -U {$this->database->postgres_user} -d {$this->database->postgres_db} -c 'SELECT 1' || exit 1" | ||||
|                             'CMD-SHELL', | ||||
|                             "psql -U {$this->database->postgres_user} -d {$this->database->postgres_db} -c 'SELECT 1' || exit 1", | ||||
|                         ], | ||||
|                         'interval' => '5s', | ||||
|                         'timeout' => '5s', | ||||
|                         'retries' => 10, | ||||
|                         'start_period' => '5s' | ||||
|                         'start_period' => '5s', | ||||
|                     ], | ||||
|                     'mem_limit' => $this->database->limits_memory, | ||||
|                     'memswap_limit' => $this->database->limits_memory_swap, | ||||
| @ -64,27 +67,27 @@ class StartPostgresql | ||||
|                     'mem_reservation' => $this->database->limits_memory_reservation, | ||||
|                     'cpus' => (float) $this->database->limits_cpus, | ||||
|                     'cpu_shares' => $this->database->limits_cpu_shares, | ||||
|                 ] | ||||
|                 ], | ||||
|             ], | ||||
|             'networks' => [ | ||||
|                 $this->database->destination->network => [ | ||||
|                     'external' => true, | ||||
|                     'name' => $this->database->destination->network, | ||||
|                     'attachable' => true, | ||||
|                 ] | ||||
|             ] | ||||
|                 ], | ||||
|             ], | ||||
|         ]; | ||||
|         if (!is_null($this->database->limits_cpuset)) { | ||||
|         if (! is_null($this->database->limits_cpuset)) { | ||||
|             data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset); | ||||
|         } | ||||
|         if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) { | ||||
|             $docker_compose['services'][$container_name]['logging'] = [ | ||||
|                 'driver' => 'fluentd', | ||||
|                 'options' => [ | ||||
|                     'fluentd-address' => "tcp://127.0.0.1:24224", | ||||
|                     'fluentd-async' => "true", | ||||
|                     'fluentd-sub-second-precision' => "true", | ||||
|                 ] | ||||
|                     'fluentd-address' => 'tcp://127.0.0.1:24224', | ||||
|                     'fluentd-async' => 'true', | ||||
|                     'fluentd-sub-second-precision' => 'true', | ||||
|                 ], | ||||
|             ]; | ||||
|         } | ||||
|         if (count($this->database->ports_mappings_array) > 0) { | ||||
| @ -106,15 +109,15 @@ class StartPostgresql | ||||
|                 $docker_compose['services'][$container_name]['volumes'][] = [ | ||||
|                     'type' => 'bind', | ||||
|                     'source' => $init_script, | ||||
|                     'target' => '/docker-entrypoint-initdb.d/' . basename($init_script), | ||||
|                     'target' => '/docker-entrypoint-initdb.d/'.basename($init_script), | ||||
|                     'read_only' => true, | ||||
|                 ]; | ||||
|             } | ||||
|         } | ||||
|         if (!is_null($this->database->postgres_conf) && !empty($this->database->postgres_conf)) { | ||||
|         if (! is_null($this->database->postgres_conf) && ! empty($this->database->postgres_conf)) { | ||||
|             $docker_compose['services'][$container_name]['volumes'][] = [ | ||||
|                 'type' => 'bind', | ||||
|                 'source' => $this->configuration_dir . '/custom-postgres.conf', | ||||
|                 'source' => $this->configuration_dir.'/custom-postgres.conf', | ||||
|                 'target' => '/etc/postgresql/postgresql.conf', | ||||
|                 'read_only' => true, | ||||
|             ]; | ||||
| @ -133,6 +136,7 @@ class StartPostgresql | ||||
|         $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull"; | ||||
|         $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; | ||||
|         $this->commands[] = "echo 'Database started.'"; | ||||
| 
 | ||||
|         return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged'); | ||||
|     } | ||||
| 
 | ||||
| @ -141,12 +145,13 @@ class StartPostgresql | ||||
|         $local_persistent_volumes = []; | ||||
|         foreach ($this->database->persistentStorages as $persistentStorage) { | ||||
|             if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) { | ||||
|                 $local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path; | ||||
|                 $local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path; | ||||
|             } else { | ||||
|                 $volume_name = $persistentStorage->name; | ||||
|                 $local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path; | ||||
|                 $local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return $local_persistent_volumes; | ||||
|     } | ||||
| 
 | ||||
| @ -163,6 +168,7 @@ class StartPostgresql | ||||
|                 'external' => false, | ||||
|             ]; | ||||
|         } | ||||
| 
 | ||||
|         return $local_persistent_volumes_names; | ||||
|     } | ||||
| 
 | ||||
| @ -187,6 +193,7 @@ class StartPostgresql | ||||
|         if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('POSTGRES_DB'))->isEmpty()) { | ||||
|             $environment_variables->push("POSTGRES_DB={$this->database->postgres_db}"); | ||||
|         } | ||||
| 
 | ||||
|         return $environment_variables->all(); | ||||
|     } | ||||
| 
 | ||||
| @ -203,6 +210,7 @@ class StartPostgresql | ||||
|             $this->init_scripts[] = "$this->configuration_dir/docker-entrypoint-initdb.d/{$filename}"; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private function add_custom_conf() | ||||
|     { | ||||
|         if (is_null($this->database->postgres_conf) || empty($this->database->postgres_conf)) { | ||||
| @ -210,7 +218,7 @@ class StartPostgresql | ||||
|         } | ||||
|         $filename = 'custom-postgres.conf'; | ||||
|         $content = $this->database->postgres_conf; | ||||
|         if (!str($content)->contains('listen_addresses')) { | ||||
|         if (! str($content)->contains('listen_addresses')) { | ||||
|             $content .= "\nlisten_addresses = '*'"; | ||||
|             $this->database->postgres_conf = $content; | ||||
|             $this->database->save(); | ||||
|  | ||||
| @ -5,17 +5,18 @@ namespace App\Actions\Database; | ||||
| use App\Models\StandaloneRedis; | ||||
| use Illuminate\Support\Facades\Storage; | ||||
| use Illuminate\Support\Str; | ||||
| use Symfony\Component\Yaml\Yaml; | ||||
| use Lorisleiva\Actions\Concerns\AsAction; | ||||
| use Symfony\Component\Yaml\Yaml; | ||||
| 
 | ||||
| class StartRedis | ||||
| { | ||||
|     use AsAction; | ||||
| 
 | ||||
|     public StandaloneRedis $database; | ||||
|     public array $commands = []; | ||||
|     public string $configuration_dir; | ||||
| 
 | ||||
|     public array $commands = []; | ||||
| 
 | ||||
|     public string $configuration_dir; | ||||
| 
 | ||||
|     public function handle(StandaloneRedis $database) | ||||
|     { | ||||
| @ -24,7 +25,7 @@ class StartRedis | ||||
|         $startCommand = "redis-server --requirepass {$this->database->redis_password} --appendonly yes"; | ||||
| 
 | ||||
|         $container_name = $this->database->uuid; | ||||
|         $this->configuration_dir = database_configuration_dir() . '/' . $container_name; | ||||
|         $this->configuration_dir = database_configuration_dir().'/'.$container_name; | ||||
| 
 | ||||
|         $this->commands = [ | ||||
|             "echo 'Starting {$database->name}.'", | ||||
| @ -55,12 +56,12 @@ class StartRedis | ||||
|                         'test' => [ | ||||
|                             'CMD-SHELL', | ||||
|                             'redis-cli', | ||||
|                             'ping' | ||||
|                             'ping', | ||||
|                         ], | ||||
|                         'interval' => '5s', | ||||
|                         'timeout' => '5s', | ||||
|                         'retries' => 10, | ||||
|                         'start_period' => '5s' | ||||
|                         'start_period' => '5s', | ||||
|                     ], | ||||
|                     'mem_limit' => $this->database->limits_memory, | ||||
|                     'memswap_limit' => $this->database->limits_memory_swap, | ||||
| @ -68,27 +69,27 @@ class StartRedis | ||||
|                     'mem_reservation' => $this->database->limits_memory_reservation, | ||||
|                     'cpus' => (float) $this->database->limits_cpus, | ||||
|                     'cpu_shares' => $this->database->limits_cpu_shares, | ||||
|                 ] | ||||
|                 ], | ||||
|             ], | ||||
|             'networks' => [ | ||||
|                 $this->database->destination->network => [ | ||||
|                     'external' => true, | ||||
|                     'name' => $this->database->destination->network, | ||||
|                     'attachable' => true, | ||||
|                 ] | ||||
|             ] | ||||
|                 ], | ||||
|             ], | ||||
|         ]; | ||||
|         if (!is_null($this->database->limits_cpuset)) { | ||||
|         if (! is_null($this->database->limits_cpuset)) { | ||||
|             data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset); | ||||
|         } | ||||
|         if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) { | ||||
|             $docker_compose['services'][$container_name]['logging'] = [ | ||||
|                 'driver' => 'fluentd', | ||||
|                 'options' => [ | ||||
|                     'fluentd-address' => "tcp://127.0.0.1:24224", | ||||
|                     'fluentd-async' => "true", | ||||
|                     'fluentd-sub-second-precision' => "true", | ||||
|                 ] | ||||
|                     'fluentd-address' => 'tcp://127.0.0.1:24224', | ||||
|                     'fluentd-async' => 'true', | ||||
|                     'fluentd-sub-second-precision' => 'true', | ||||
|                 ], | ||||
|             ]; | ||||
|         } | ||||
|         if (count($this->database->ports_mappings_array) > 0) { | ||||
| @ -105,10 +106,10 @@ class StartRedis | ||||
|         if (count($volume_names) > 0) { | ||||
|             $docker_compose['volumes'] = $volume_names; | ||||
|         } | ||||
|         if (!is_null($this->database->redis_conf) || !empty($this->database->redis_conf)) { | ||||
|         if (! is_null($this->database->redis_conf) || ! empty($this->database->redis_conf)) { | ||||
|             $docker_compose['services'][$container_name]['volumes'][] = [ | ||||
|                 'type' => 'bind', | ||||
|                 'source' => $this->configuration_dir . '/redis.conf', | ||||
|                 'source' => $this->configuration_dir.'/redis.conf', | ||||
|                 'target' => '/usr/local/etc/redis/redis.conf', | ||||
|                 'read_only' => true, | ||||
|             ]; | ||||
| @ -123,6 +124,7 @@ class StartRedis | ||||
|         $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull"; | ||||
|         $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; | ||||
|         $this->commands[] = "echo 'Database started.'"; | ||||
| 
 | ||||
|         return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged'); | ||||
|     } | ||||
| 
 | ||||
| @ -131,12 +133,13 @@ class StartRedis | ||||
|         $local_persistent_volumes = []; | ||||
|         foreach ($this->database->persistentStorages as $persistentStorage) { | ||||
|             if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) { | ||||
|                 $local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path; | ||||
|                 $local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path; | ||||
|             } else { | ||||
|                 $volume_name = $persistentStorage->name; | ||||
|                 $local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path; | ||||
|                 $local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return $local_persistent_volumes; | ||||
|     } | ||||
| 
 | ||||
| @ -153,6 +156,7 @@ class StartRedis | ||||
|                 'external' => false, | ||||
|             ]; | ||||
|         } | ||||
| 
 | ||||
|         return $local_persistent_volumes_names; | ||||
|     } | ||||
| 
 | ||||
| @ -169,6 +173,7 @@ class StartRedis | ||||
| 
 | ||||
|         return $environment_variables->all(); | ||||
|     } | ||||
| 
 | ||||
|     private function add_custom_redis() | ||||
|     { | ||||
|         if (is_null($this->database->redis_conf) || empty($this->database->redis_conf)) { | ||||
|  | ||||
| @ -19,7 +19,7 @@ class StopDatabase | ||||
|     public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $database) | ||||
|     { | ||||
|         $server = $database->destination->server; | ||||
|         if (!$server->isFunctional()) { | ||||
|         if (! $server->isFunctional()) { | ||||
|             return 'Server is not functional'; | ||||
|         } | ||||
|         instant_remote_process( | ||||
|  | ||||
| @ -17,7 +17,9 @@ use Lorisleiva\Actions\Concerns\AsAction; | ||||
| class GetContainersStatus | ||||
| { | ||||
|     use AsAction; | ||||
| 
 | ||||
|     public $applications; | ||||
| 
 | ||||
|     public $server; | ||||
| 
 | ||||
|     public function handle(Server $server) | ||||
| @ -26,9 +28,9 @@ class GetContainersStatus | ||||
|         //     $server = Server::find(0);
 | ||||
|         // }
 | ||||
|         $this->server = $server; | ||||
|         if (!$this->server->isFunctional()) { | ||||
|         if (! $this->server->isFunctional()) { | ||||
|             return 'Server is not ready.'; | ||||
|         }; | ||||
|         } | ||||
|         $this->applications = $this->server->applications(); | ||||
|         $skip_these_applications = collect([]); | ||||
|         foreach ($this->applications as $application) { | ||||
| @ -41,7 +43,7 @@ class GetContainersStatus | ||||
|             } | ||||
|         } | ||||
|         $this->applications = $this->applications->filter(function ($value, $key) use ($skip_these_applications) { | ||||
|             return !$skip_these_applications->pluck('id')->contains($value->id); | ||||
|             return ! $skip_these_applications->pluck('id')->contains($value->id); | ||||
|         }); | ||||
|         $this->old_way(); | ||||
|         // if ($this->server->isSwarm()) {
 | ||||
| @ -133,7 +135,7 @@ class GetContainersStatus | ||||
|                                                 return data_get($value, 'name') === "$uuid-proxy"; | ||||
|                                             } | ||||
|                                         })->first(); | ||||
|                                         if (!$foundTcpProxy) { | ||||
|                                         if (! $foundTcpProxy) { | ||||
|                                             StartDatabaseProxy::run($service_db); | ||||
|                                             // $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$service_db->service->name}", $this->server));
 | ||||
|                                         } | ||||
| @ -158,7 +160,7 @@ class GetContainersStatus | ||||
|                                             return data_get($value, 'name') === "$uuid-proxy"; | ||||
|                                         } | ||||
|                                     })->first(); | ||||
|                                     if (!$foundTcpProxy) { | ||||
|                                     if (! $foundTcpProxy) { | ||||
|                                         StartDatabaseProxy::run($database); | ||||
|                                         $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server)); | ||||
|                                     } | ||||
| @ -177,7 +179,7 @@ class GetContainersStatus | ||||
|                     $subType = data_get($labels, 'coolify.service.subType'); | ||||
|                     $subId = data_get($labels, 'coolify.service.subId'); | ||||
|                     $service = $services->where('id', $serviceLabelId)->first(); | ||||
|                     if (!$service) { | ||||
|                     if (! $service) { | ||||
|                         continue; | ||||
|                     } | ||||
|                     if ($subType === 'application') { | ||||
| @ -239,7 +241,7 @@ class GetContainersStatus | ||||
|                 $environmentName = data_get($service, 'environment.name'); | ||||
| 
 | ||||
|                 if ($projectUuid && $serviceUuid && $environmentName) { | ||||
|                     $url =  base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/service/" . $serviceUuid; | ||||
|                     $url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/service/'.$serviceUuid; | ||||
|                 } else { | ||||
|                     $url = null; | ||||
|                 } | ||||
| @ -265,7 +267,7 @@ class GetContainersStatus | ||||
|                 $environment = data_get($application, 'environment.name'); | ||||
| 
 | ||||
|                 if ($projectUuid && $applicationUuid && $environment) { | ||||
|                     $url =  base_url() . '/project/' . $projectUuid . "/" . $environment . "/application/" . $applicationUuid; | ||||
|                     $url = base_url().'/project/'.$projectUuid.'/'.$environment.'/application/'.$applicationUuid; | ||||
|                 } else { | ||||
|                     $url = null; | ||||
|                 } | ||||
| @ -290,7 +292,7 @@ class GetContainersStatus | ||||
|                 $applicationUuid = data_get($preview, 'application.uuid'); | ||||
| 
 | ||||
|                 if ($projectUuid && $applicationUuid && $environmentName) { | ||||
|                     $url =  base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/application/" . $applicationUuid; | ||||
|                     $url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/application/'.$applicationUuid; | ||||
|                 } else { | ||||
|                     $url = null; | ||||
|                 } | ||||
| @ -315,7 +317,7 @@ class GetContainersStatus | ||||
|                 $databaseUuid = data_get($database, 'uuid'); | ||||
| 
 | ||||
|                 if ($projectUuid && $databaseUuid && $environmentName) { | ||||
|                     $url = base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/database/" . $databaseUuid; | ||||
|                     $url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/database/'.$databaseUuid; | ||||
|                 } else { | ||||
|                     $url = null; | ||||
|                 } | ||||
| @ -332,7 +334,7 @@ class GetContainersStatus | ||||
|                     return data_get($value, 'name') === 'coolify-proxy'; | ||||
|                 } | ||||
|             })->first(); | ||||
|             if (!$foundProxyContainer) { | ||||
|             if (! $foundProxyContainer) { | ||||
|                 try { | ||||
|                     $shouldStart = CheckProxy::run($this->server); | ||||
|                     if ($shouldStart) { | ||||
| @ -351,9 +353,11 @@ class GetContainersStatus | ||||
|         } catch (\Exception $e) { | ||||
|             // send_internal_notification("ContainerStatusJob failed on ({$this->server->id}) with: " . $e->getMessage());
 | ||||
|             ray($e->getMessage()); | ||||
| 
 | ||||
|             return handleError($e); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private function old_way() | ||||
|     { | ||||
|         if ($this->server->isSwarm()) { | ||||
| @ -361,8 +365,8 @@ class GetContainersStatus | ||||
|             $containerReplicates = instant_remote_process(["docker service ls --format '{{json .}}'"], $this->server, false); | ||||
|         } else { | ||||
|             // Precheck for containers
 | ||||
|             $containers = instant_remote_process(["docker container ls -q"], $this->server, false); | ||||
|             if (!$containers) { | ||||
|             $containers = instant_remote_process(['docker container ls -q'], $this->server, false); | ||||
|             if (! $containers) { | ||||
|                 return; | ||||
|             } | ||||
|             $containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this->server, false); | ||||
| @ -390,6 +394,7 @@ class GetContainersStatus | ||||
|                             data_set($container, 'State.Health.Status', 'unhealthy'); | ||||
|                         } | ||||
|                     } | ||||
| 
 | ||||
|                     return $container; | ||||
|                 }); | ||||
|             } | ||||
| @ -463,7 +468,7 @@ class GetContainersStatus | ||||
|                                                 return data_get($value, 'Name') === "/$uuid-proxy"; | ||||
|                                             } | ||||
|                                         })->first(); | ||||
|                                         if (!$foundTcpProxy) { | ||||
|                                         if (! $foundTcpProxy) { | ||||
|                                             StartDatabaseProxy::run($service_db); | ||||
|                                             // $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$service_db->service->name}", $this->server));
 | ||||
|                                         } | ||||
| @ -488,7 +493,7 @@ class GetContainersStatus | ||||
|                                         return data_get($value, 'Name') === "/$uuid-proxy"; | ||||
|                                     } | ||||
|                                 })->first(); | ||||
|                                 if (!$foundTcpProxy) { | ||||
|                                 if (! $foundTcpProxy) { | ||||
|                                     StartDatabaseProxy::run($database); | ||||
|                                     $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server)); | ||||
|                                 } | ||||
| @ -507,7 +512,7 @@ class GetContainersStatus | ||||
|                 $subType = data_get($labels, 'coolify.service.subType'); | ||||
|                 $subId = data_get($labels, 'coolify.service.subId'); | ||||
|                 $service = $services->where('id', $serviceLabelId)->first(); | ||||
|                 if (!$service) { | ||||
|                 if (! $service) { | ||||
|                     continue; | ||||
|                 } | ||||
|                 if ($subType === 'application') { | ||||
| @ -569,7 +574,7 @@ class GetContainersStatus | ||||
|             $environmentName = data_get($service, 'environment.name'); | ||||
| 
 | ||||
|             if ($projectUuid && $serviceUuid && $environmentName) { | ||||
|                 $url =  base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/service/" . $serviceUuid; | ||||
|                 $url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/service/'.$serviceUuid; | ||||
|             } else { | ||||
|                 $url = null; | ||||
|             } | ||||
| @ -595,7 +600,7 @@ class GetContainersStatus | ||||
|             $environment = data_get($application, 'environment.name'); | ||||
| 
 | ||||
|             if ($projectUuid && $applicationUuid && $environment) { | ||||
|                 $url =  base_url() . '/project/' . $projectUuid . "/" . $environment . "/application/" . $applicationUuid; | ||||
|                 $url = base_url().'/project/'.$projectUuid.'/'.$environment.'/application/'.$applicationUuid; | ||||
|             } else { | ||||
|                 $url = null; | ||||
|             } | ||||
| @ -620,7 +625,7 @@ class GetContainersStatus | ||||
|             $applicationUuid = data_get($preview, 'application.uuid'); | ||||
| 
 | ||||
|             if ($projectUuid && $applicationUuid && $environmentName) { | ||||
|                 $url =  base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/application/" . $applicationUuid; | ||||
|                 $url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/application/'.$applicationUuid; | ||||
|             } else { | ||||
|                 $url = null; | ||||
|             } | ||||
| @ -645,7 +650,7 @@ class GetContainersStatus | ||||
|             $databaseUuid = data_get($database, 'uuid'); | ||||
| 
 | ||||
|             if ($projectUuid && $databaseUuid && $environmentName) { | ||||
|                 $url = base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/database/" . $databaseUuid; | ||||
|                 $url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/database/'.$databaseUuid; | ||||
|             } else { | ||||
|                 $url = null; | ||||
|             } | ||||
| @ -661,7 +666,7 @@ class GetContainersStatus | ||||
|                 return data_get($value, 'Name') === '/coolify-proxy'; | ||||
|             } | ||||
|         })->first(); | ||||
|         if (!$foundProxyContainer) { | ||||
|         if (! $foundProxyContainer) { | ||||
|             try { | ||||
|                 $shouldStart = CheckProxy::run($this->server); | ||||
|                 if ($shouldStart) { | ||||
|  | ||||
| @ -21,7 +21,7 @@ class CreateNewUser implements CreatesNewUsers | ||||
|     public function create(array $input): User | ||||
|     { | ||||
|         $settings = InstanceSettings::get(); | ||||
|         if (!$settings->is_registration_enabled) { | ||||
|         if (! $settings->is_registration_enabled) { | ||||
|             abort(403); | ||||
|         } | ||||
|         Validator::make($input, [ | ||||
| @ -66,6 +66,7 @@ class CreateNewUser implements CreatesNewUsers | ||||
|         } | ||||
|         // Set session variable
 | ||||
|         session(['currentTeam' => $user->currentTeam = $team]); | ||||
| 
 | ||||
|         return $user; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -6,10 +6,10 @@ use App\Models\InstanceSettings; | ||||
| use Illuminate\Support\Facades\Http; | ||||
| use Lorisleiva\Actions\Concerns\AsAction; | ||||
| 
 | ||||
| 
 | ||||
| class CheckResaleLicense | ||||
| { | ||||
|     use AsAction; | ||||
| 
 | ||||
|     public function handle() | ||||
|     { | ||||
|         try { | ||||
| @ -18,6 +18,7 @@ class CheckResaleLicense | ||||
|                 $settings->update([ | ||||
|                     'is_resale_license_active' => true, | ||||
|                 ]); | ||||
| 
 | ||||
|                 return; | ||||
|             } | ||||
|             // if (!$settings->resale_license) {
 | ||||
| @ -38,6 +39,7 @@ class CheckResaleLicense | ||||
|                 $settings->update([ | ||||
|                     'is_resale_license_active' => true, | ||||
|                 ]); | ||||
| 
 | ||||
|                 return; | ||||
|             } | ||||
|             $data = Http::withHeaders([ | ||||
| @ -51,6 +53,7 @@ class CheckResaleLicense | ||||
|                 $settings->update([ | ||||
|                     'is_resale_license_active' => true, | ||||
|                 ]); | ||||
| 
 | ||||
|                 return; | ||||
|             } | ||||
|             if (data_get($data, 'license_key.status') === 'active') { | ||||
|  | ||||
| @ -2,13 +2,14 @@ | ||||
| 
 | ||||
| namespace App\Actions\Proxy; | ||||
| 
 | ||||
| use Lorisleiva\Actions\Concerns\AsAction; | ||||
| use App\Models\Server; | ||||
| use Illuminate\Support\Str; | ||||
| use Lorisleiva\Actions\Concerns\AsAction; | ||||
| 
 | ||||
| class CheckConfiguration | ||||
| { | ||||
|     use AsAction; | ||||
| 
 | ||||
|     public function handle(Server $server, bool $reset = false) | ||||
|     { | ||||
|         $proxyType = $server->proxyType(); | ||||
| @ -22,12 +23,13 @@ class CheckConfiguration | ||||
|         ]; | ||||
|         $proxy_configuration = instant_remote_process($payload, $server, false); | ||||
| 
 | ||||
|         if ($reset || !$proxy_configuration || is_null($proxy_configuration)) { | ||||
|         if ($reset || ! $proxy_configuration || is_null($proxy_configuration)) { | ||||
|             $proxy_configuration = Str::of(generate_default_proxy_configuration($server))->trim()->value; | ||||
|         } | ||||
|         if (!$proxy_configuration || is_null($proxy_configuration)) { | ||||
|             throw new \Exception("Could not generate proxy configuration"); | ||||
|         if (! $proxy_configuration || is_null($proxy_configuration)) { | ||||
|             throw new \Exception('Could not generate proxy configuration'); | ||||
|         } | ||||
| 
 | ||||
|         return $proxy_configuration; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -8,9 +8,10 @@ use Lorisleiva\Actions\Concerns\AsAction; | ||||
| class CheckProxy | ||||
| { | ||||
|     use AsAction; | ||||
| 
 | ||||
|     public function handle(Server $server, $fromUI = false) | ||||
|     { | ||||
|         if (!$server->isFunctional()) { | ||||
|         if (! $server->isFunctional()) { | ||||
|             return false; | ||||
|         } | ||||
|         if ($server->isBuildServer()) { | ||||
| @ -18,6 +19,7 @@ class CheckProxy | ||||
|                 $server->proxy = null; | ||||
|                 $server->save(); | ||||
|             } | ||||
| 
 | ||||
|             return false; | ||||
|         } | ||||
|         $proxyType = $server->proxyType(); | ||||
| @ -25,12 +27,12 @@ class CheckProxy | ||||
|             return false; | ||||
|         } | ||||
|         ['uptime' => $uptime, 'error' => $error] = $server->validateConnection(); | ||||
|         if (!$uptime) { | ||||
|         if (! $uptime) { | ||||
|             throw new \Exception($error); | ||||
|         } | ||||
|         if (!$server->isProxyShouldRun()) { | ||||
|         if (! $server->isProxyShouldRun()) { | ||||
|             if ($fromUI) { | ||||
|                 throw new \Exception("Proxy should not run. You selected the Custom Proxy."); | ||||
|                 throw new \Exception('Proxy should not run. You selected the Custom Proxy.'); | ||||
|             } else { | ||||
|                 return false; | ||||
|             } | ||||
| @ -42,12 +44,14 @@ class CheckProxy | ||||
|             if ($status === 'running') { | ||||
|                 return false; | ||||
|             } | ||||
| 
 | ||||
|             return true; | ||||
|         } else { | ||||
|             $status = getContainerStatus($server, 'coolify-proxy'); | ||||
|             if ($status === 'running') { | ||||
|                 $server->proxy->set('status', 'running'); | ||||
|                 $server->save(); | ||||
| 
 | ||||
|                 return false; | ||||
|             } | ||||
|             if ($server->settings->is_cloudflare_tunnel) { | ||||
| @ -76,6 +80,7 @@ class CheckProxy | ||||
|                     return false; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             return true; | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -11,6 +11,7 @@ use Spatie\Activitylog\Models\Activity; | ||||
| class StartProxy | ||||
| { | ||||
|     use AsAction; | ||||
| 
 | ||||
|     public function handle(Server $server, bool $async = true): string|Activity | ||||
|     { | ||||
|         try { | ||||
| @ -21,8 +22,8 @@ class StartProxy | ||||
|             $commands = collect([]); | ||||
|             $proxy_path = $server->proxyPath(); | ||||
|             $configuration = CheckConfiguration::run($server); | ||||
|             if (!$configuration) { | ||||
|                 throw new \Exception("Configuration is not synced"); | ||||
|             if (! $configuration) { | ||||
|                 throw new \Exception('Configuration is not synced'); | ||||
|             } | ||||
|             SaveConfiguration::run($server, $configuration); | ||||
|             $docker_compose_yml_base64 = base64_encode($configuration); | ||||
| @ -34,11 +35,11 @@ class StartProxy | ||||
|                     "cd $proxy_path", | ||||
|                     "echo 'Creating required Docker Compose file.'", | ||||
|                     "echo 'Starting coolify-proxy.'", | ||||
|                     "docker stack deploy -c docker-compose.yml coolify-proxy", | ||||
|                     "echo 'Proxy started successfully.'" | ||||
|                     'docker stack deploy -c docker-compose.yml coolify-proxy', | ||||
|                     "echo 'Proxy started successfully.'", | ||||
|                 ]); | ||||
|             } else { | ||||
|                 $caddfile = "import /dynamic/*.caddy"; | ||||
|                 $caddfile = 'import /dynamic/*.caddy'; | ||||
|                 $commands = $commands->merge([ | ||||
|                     "mkdir -p $proxy_path/dynamic", | ||||
|                     "cd $proxy_path", | ||||
| @ -47,16 +48,17 @@ class StartProxy | ||||
|                     "echo 'Pulling docker image.'", | ||||
|                     'docker compose pull', | ||||
|                     "echo 'Stopping existing coolify-proxy.'", | ||||
|                     "docker compose down -v --remove-orphans > /dev/null 2>&1", | ||||
|                     'docker compose down -v --remove-orphans > /dev/null 2>&1', | ||||
|                     "echo 'Starting coolify-proxy.'", | ||||
|                     'docker compose up -d --remove-orphans', | ||||
|                     "echo 'Proxy started successfully.'" | ||||
|                     "echo 'Proxy started successfully.'", | ||||
|                 ]); | ||||
|                 $commands = $commands->merge(connectProxyToNetworks($server)); | ||||
|             } | ||||
| 
 | ||||
|             if ($async) { | ||||
|                 $activity = remote_process($commands, $server, callEventOnFinish: 'ProxyStarted', callEventData: $server); | ||||
| 
 | ||||
|                 return $activity; | ||||
|             } else { | ||||
|                 instant_remote_process($commands, $server); | ||||
| @ -64,6 +66,7 @@ class StartProxy | ||||
|                 $server->proxy->set('type', $proxyType); | ||||
|                 $server->save(); | ||||
|                 ProxyStarted::dispatch($server); | ||||
| 
 | ||||
|                 return 'OK'; | ||||
|             } | ||||
|         } catch (\Throwable $e) { | ||||
|  | ||||
| @ -2,12 +2,13 @@ | ||||
| 
 | ||||
| namespace App\Actions\Server; | ||||
| 
 | ||||
| use Lorisleiva\Actions\Concerns\AsAction; | ||||
| use App\Models\Server; | ||||
| use Lorisleiva\Actions\Concerns\AsAction; | ||||
| 
 | ||||
| class CleanupDocker | ||||
| { | ||||
|     use AsAction; | ||||
| 
 | ||||
|     public function handle(Server $server, bool $force = true) | ||||
|     { | ||||
|         if ($force) { | ||||
|  | ||||
| @ -9,18 +9,19 @@ use Symfony\Component\Yaml\Yaml; | ||||
| class ConfigureCloudflared | ||||
| { | ||||
|     use AsAction; | ||||
| 
 | ||||
|     public function handle(Server $server, string $cloudflare_token) | ||||
|     { | ||||
|         try { | ||||
|             $config = [ | ||||
|                 "services" => [ | ||||
|                     "coolify-cloudflared" => [ | ||||
|                         "container_name" => "coolify-cloudflared", | ||||
|                         "image" => "cloudflare/cloudflared:latest", | ||||
|                         "restart" => RESTART_MODE, | ||||
|                         "network_mode" => "host", | ||||
|                         "command" => "tunnel run", | ||||
|                         "environment" => [ | ||||
|                 'services' => [ | ||||
|                     'coolify-cloudflared' => [ | ||||
|                         'container_name' => 'coolify-cloudflared', | ||||
|                         'image' => 'cloudflare/cloudflared:latest', | ||||
|                         'restart' => RESTART_MODE, | ||||
|                         'network_mode' => 'host', | ||||
|                         'command' => 'tunnel run', | ||||
|                         'environment' => [ | ||||
|                             "TUNNEL_TOKEN={$cloudflare_token}", | ||||
|                         ], | ||||
|                     ], | ||||
| @ -29,12 +30,12 @@ class ConfigureCloudflared | ||||
|             $config = Yaml::dump($config, 12, 2); | ||||
|             $docker_compose_yml_base64 = base64_encode($config); | ||||
|             $commands = collect([ | ||||
|                 "mkdir -p /tmp/cloudflared", | ||||
|                 "cd /tmp/cloudflared", | ||||
|                 'mkdir -p /tmp/cloudflared', | ||||
|                 'cd /tmp/cloudflared', | ||||
|                 "echo '$docker_compose_yml_base64' | base64 -d | tee docker-compose.yml > /dev/null", | ||||
|                 "docker compose pull", | ||||
|                 "docker compose down -v --remove-orphans > /dev/null 2>&1", | ||||
|                 "docker compose up -d --remove-orphans", | ||||
|                 'docker compose pull', | ||||
|                 'docker compose down -v --remove-orphans > /dev/null 2>&1', | ||||
|                 'docker compose up -d --remove-orphans', | ||||
|             ]); | ||||
|             instant_remote_process($commands, $server); | ||||
|         } catch (\Throwable $e) { | ||||
| @ -42,7 +43,7 @@ class ConfigureCloudflared | ||||
|             throw $e; | ||||
|         } finally { | ||||
|             $commands = collect([ | ||||
|                 "rm -fr /tmp/cloudflared", | ||||
|                 'rm -fr /tmp/cloudflared', | ||||
|             ]); | ||||
|             instant_remote_process($commands, $server); | ||||
|         } | ||||
|  | ||||
| @ -2,20 +2,21 @@ | ||||
| 
 | ||||
| namespace App\Actions\Server; | ||||
| 
 | ||||
| use Lorisleiva\Actions\Concerns\AsAction; | ||||
| use App\Models\Server; | ||||
| use App\Models\StandaloneDocker; | ||||
| use Lorisleiva\Actions\Concerns\AsAction; | ||||
| 
 | ||||
| class InstallDocker | ||||
| { | ||||
|     use AsAction; | ||||
| 
 | ||||
|     public function handle(Server $server) | ||||
|     { | ||||
|         $supported_os_type = $server->validateOS(); | ||||
|         if (!$supported_os_type) { | ||||
|         if (! $supported_os_type) { | ||||
|             throw new \Exception('Server OS type is not supported for automated installation. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://coolify.io/docs/installation#manually">documentation</a>.'); | ||||
|         } | ||||
|         ray('Installing Docker on server: ' . $server->name . ' (' . $server->ip . ')' . ' with OS type: ' . $supported_os_type); | ||||
|         ray('Installing Docker on server: '.$server->name.' ('.$server->ip.')'.' with OS type: '.$supported_os_type); | ||||
|         $dockerVersion = '24.0'; | ||||
|         $config = base64_encode('{ | ||||
|             "log-driver": "json-file", | ||||
| @ -36,40 +37,41 @@ class InstallDocker | ||||
|         if (isDev() && $server->id === 0) { | ||||
|             $command = $command->merge([ | ||||
|                 "echo 'Installing Prerequisites...'", | ||||
|                 "sleep 1", | ||||
|                 'sleep 1', | ||||
|                 "echo 'Installing Docker Engine...'", | ||||
|                 "echo 'Configuring Docker Engine (merging existing configuration with the required)...'", | ||||
|                 "sleep 4", | ||||
|                 'sleep 4', | ||||
|                 "echo 'Restarting Docker Engine...'", | ||||
|                 "ls -l /tmp" | ||||
|                 'ls -l /tmp', | ||||
|             ]); | ||||
| 
 | ||||
|             return remote_process($command, $server); | ||||
|         } else { | ||||
|             if ($supported_os_type->contains('debian')) { | ||||
|                 $command = $command->merge([ | ||||
|                     "echo 'Installing Prerequisites...'", | ||||
|                     "apt-get update -y", | ||||
|                     "command -v curl >/dev/null || apt install -y curl", | ||||
|                     "command -v wget >/dev/null || apt install -y wget", | ||||
|                     "command -v git >/dev/null || apt install -y git", | ||||
|                     "command -v jq >/dev/null || apt install -y jq", | ||||
|                     'apt-get update -y', | ||||
|                     'command -v curl >/dev/null || apt install -y curl', | ||||
|                     'command -v wget >/dev/null || apt install -y wget', | ||||
|                     'command -v git >/dev/null || apt install -y git', | ||||
|                     'command -v jq >/dev/null || apt install -y jq', | ||||
|                 ]); | ||||
|             } else if ($supported_os_type->contains('rhel')) { | ||||
|             } elseif ($supported_os_type->contains('rhel')) { | ||||
|                 $command = $command->merge([ | ||||
|                     "echo 'Installing Prerequisites...'", | ||||
|                     "command -v curl >/dev/null || dnf install -y curl", | ||||
|                     "command -v wget >/dev/null || dnf install -y wget", | ||||
|                     "command -v git >/dev/null || dnf install -y git", | ||||
|                     "command -v jq >/dev/null || dnf install -y jq", | ||||
|                     'command -v curl >/dev/null || dnf install -y curl', | ||||
|                     'command -v wget >/dev/null || dnf install -y wget', | ||||
|                     'command -v git >/dev/null || dnf install -y git', | ||||
|                     'command -v jq >/dev/null || dnf install -y jq', | ||||
|                 ]); | ||||
|             } else if ($supported_os_type->contains('sles')) { | ||||
|             } elseif ($supported_os_type->contains('sles')) { | ||||
|                 $command = $command->merge([ | ||||
|                     "echo 'Installing Prerequisites...'", | ||||
|                     "zypper update -y", | ||||
|                     "command -v curl >/dev/null || zypper install -y curl", | ||||
|                     "command -v wget >/dev/null || zypper install -y wget", | ||||
|                     "command -v git >/dev/null || zypper install -y git", | ||||
|                     "command -v jq >/dev/null || zypper install -y jq", | ||||
|                     'zypper update -y', | ||||
|                     'command -v curl >/dev/null || zypper install -y curl', | ||||
|                     'command -v wget >/dev/null || zypper install -y wget', | ||||
|                     'command -v git >/dev/null || zypper install -y git', | ||||
|                     'command -v jq >/dev/null || zypper install -y jq', | ||||
|                 ]); | ||||
|             } else { | ||||
|                 throw new \Exception('Unsupported OS'); | ||||
| @ -78,29 +80,30 @@ class InstallDocker | ||||
|                 "echo 'Installing Docker Engine...'", | ||||
|                 "curl https://releases.rancher.com/install-docker/{$dockerVersion}.sh | sh || curl https://get.docker.com | sh -s -- --version {$dockerVersion}", | ||||
|                 "echo 'Configuring Docker Engine (merging existing configuration with the required)...'", | ||||
|                 "test -s /etc/docker/daemon.json && cp /etc/docker/daemon.json \"/etc/docker/daemon.json.original-$(date +\"%Y%m%d-%H%M%S\")\"", | ||||
|                 'test -s /etc/docker/daemon.json && cp /etc/docker/daemon.json "/etc/docker/daemon.json.original-$(date +"%Y%m%d-%H%M%S")"', | ||||
|                 "test ! -s /etc/docker/daemon.json && echo '{$config}' | base64 -d | tee /etc/docker/daemon.json > /dev/null", | ||||
|                 "echo '{$config}' | base64 -d | tee /etc/docker/daemon.json.coolify > /dev/null", | ||||
|                 "jq . /etc/docker/daemon.json.coolify | tee /etc/docker/daemon.json.coolify.pretty > /dev/null", | ||||
|                 "mv /etc/docker/daemon.json.coolify.pretty /etc/docker/daemon.json.coolify", | ||||
|                 'jq . /etc/docker/daemon.json.coolify | tee /etc/docker/daemon.json.coolify.pretty > /dev/null', | ||||
|                 'mv /etc/docker/daemon.json.coolify.pretty /etc/docker/daemon.json.coolify', | ||||
|                 "jq -s '.[0] * .[1]' /etc/docker/daemon.json.coolify /etc/docker/daemon.json | tee /etc/docker/daemon.json.appended > /dev/null", | ||||
|                 "mv /etc/docker/daemon.json.appended /etc/docker/daemon.json", | ||||
|                 'mv /etc/docker/daemon.json.appended /etc/docker/daemon.json', | ||||
|                 "echo 'Restarting Docker Engine...'", | ||||
|                 "systemctl enable docker >/dev/null 2>&1 || true", | ||||
|                 "systemctl restart docker", | ||||
|                 'systemctl enable docker >/dev/null 2>&1 || true', | ||||
|                 'systemctl restart docker', | ||||
|             ]); | ||||
|             if ($server->isSwarm()) { | ||||
|                 $command = $command->merge([ | ||||
|                     "docker network create --attachable --driver overlay coolify-overlay >/dev/null 2>&1 || true", | ||||
|                     'docker network create --attachable --driver overlay coolify-overlay >/dev/null 2>&1 || true', | ||||
|                 ]); | ||||
|             } else { | ||||
|                 $command = $command->merge([ | ||||
|                     "docker network create --attachable coolify >/dev/null 2>&1 || true", | ||||
|                     'docker network create --attachable coolify >/dev/null 2>&1 || true', | ||||
|                 ]); | ||||
|                 $command = $command->merge([ | ||||
|                     "echo 'Done!'", | ||||
|                 ]); | ||||
|             } | ||||
| 
 | ||||
|             return remote_process($command, $server); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -2,21 +2,22 @@ | ||||
| 
 | ||||
| namespace App\Actions\Server; | ||||
| 
 | ||||
| use Lorisleiva\Actions\Concerns\AsAction; | ||||
| use App\Models\Server; | ||||
| use Lorisleiva\Actions\Concerns\AsAction; | ||||
| 
 | ||||
| class InstallLogDrain | ||||
| { | ||||
|     use AsAction; | ||||
| 
 | ||||
|     public function handle(Server $server) | ||||
|     { | ||||
|         if ($server->settings->is_logdrain_newrelic_enabled) { | ||||
|             $type = 'newrelic'; | ||||
|         } else if ($server->settings->is_logdrain_highlight_enabled) { | ||||
|         } elseif ($server->settings->is_logdrain_highlight_enabled) { | ||||
|             $type = 'highlight'; | ||||
|         } else if ($server->settings->is_logdrain_axiom_enabled) { | ||||
|         } elseif ($server->settings->is_logdrain_axiom_enabled) { | ||||
|             $type = 'axiom'; | ||||
|         } else if ($server->settings->is_logdrain_custom_enabled) { | ||||
|         } elseif ($server->settings->is_logdrain_custom_enabled) { | ||||
|             $type = 'custom'; | ||||
|         } else { | ||||
|             $type = 'none'; | ||||
| @ -25,11 +26,12 @@ class InstallLogDrain | ||||
|             if ($type === 'none') { | ||||
|                 $command = [ | ||||
|                     "echo 'Stopping old Fluent Bit'", | ||||
|                     "docker rm -f coolify-log-drain || true", | ||||
|                     'docker rm -f coolify-log-drain || true', | ||||
|                 ]; | ||||
| 
 | ||||
|                 return instant_remote_process($command, $server); | ||||
|             } else if ($type === 'newrelic') { | ||||
|                 if (!$server->settings->is_logdrain_newrelic_enabled) { | ||||
|             } elseif ($type === 'newrelic') { | ||||
|                 if (! $server->settings->is_logdrain_newrelic_enabled) { | ||||
|                     throw new \Exception('New Relic log drain is not enabled.'); | ||||
|                 } | ||||
|                 $config = base64_encode(" | ||||
| @ -59,11 +61,11 @@ class InstallLogDrain | ||||
|     # https://log-api.newrelic.com/log/v1 - US
 | ||||
|     base_uri \${BASE_URI} | ||||
| ");
 | ||||
|             } else if ($type === 'highlight') { | ||||
|                 if (!$server->settings->is_logdrain_highlight_enabled) { | ||||
|             } elseif ($type === 'highlight') { | ||||
|                 if (! $server->settings->is_logdrain_highlight_enabled) { | ||||
|                     throw new \Exception('Highlight log drain is not enabled.'); | ||||
|                 } | ||||
|                 $config = base64_encode(" | ||||
|                 $config = base64_encode(' | ||||
| [SERVICE] | ||||
|     Flush     5 | ||||
|     Daemon    off | ||||
| @ -71,7 +73,7 @@ class InstallLogDrain | ||||
|     Parsers_File  parsers.conf | ||||
| [INPUT] | ||||
|     Name              forward | ||||
|     tag               \${HIGHLIGHT_PROJECT_ID} | ||||
|     tag               ${HIGHLIGHT_PROJECT_ID} | ||||
|     Buffer_Chunk_Size 1M | ||||
|     Buffer_Max_Size   6M | ||||
| [OUTPUT] | ||||
| @ -79,9 +81,9 @@ class InstallLogDrain | ||||
|     Match               * | ||||
|     Host                otel.highlight.io | ||||
|     Port                24224 | ||||
| ");
 | ||||
|             } else if ($type === 'axiom') { | ||||
|                 if (!$server->settings->is_logdrain_axiom_enabled) { | ||||
| '); | ||||
|             } elseif ($type === 'axiom') { | ||||
|                 if (! $server->settings->is_logdrain_axiom_enabled) { | ||||
|                     throw new \Exception('Axiom log drain is not enabled.'); | ||||
|                 } | ||||
|                 $config = base64_encode(" | ||||
| @ -116,8 +118,8 @@ class InstallLogDrain | ||||
|     json_date_format iso8601 | ||||
|     tls On | ||||
| ");
 | ||||
|             } else if ($type === 'custom') { | ||||
|                 if (!$server->settings->is_logdrain_custom_enabled) { | ||||
|             } elseif ($type === 'custom') { | ||||
|                 if (! $server->settings->is_logdrain_custom_enabled) { | ||||
|                     throw new \Exception('Custom log drain is not enabled.'); | ||||
|                 } | ||||
|                 $config = base64_encode($server->settings->logdrain_custom_config); | ||||
| @ -133,7 +135,7 @@ class InstallLogDrain | ||||
|     Regex       /^(?!\s*$).+/ | ||||
| ");
 | ||||
|             } | ||||
|             $compose = base64_encode(" | ||||
|             $compose = base64_encode(' | ||||
| services: | ||||
|   coolify-log-drain: | ||||
|     image: cr.fluentbit.io/fluent/fluent-bit:2.0 | ||||
| @ -147,7 +149,7 @@ services: | ||||
|     ports: | ||||
|       - 127.0.0.1:24224:24224 | ||||
|     restart: unless-stopped | ||||
| ");
 | ||||
| '); | ||||
|             $readme = base64_encode('# New Relic Log Drain
 | ||||
| This log drain is based on [Fluent Bit](https://fluentbit.io/) and New Relic Log Forwarder. | ||||
| 
 | ||||
| @ -160,11 +162,11 @@ Files: | ||||
|             $base_uri = $server->settings->logdrain_newrelic_base_uri; | ||||
|             $base_path = config('coolify.base_config_path'); | ||||
| 
 | ||||
|             $config_path = $base_path . '/log-drains'; | ||||
|             $fluent_bit_config = $config_path . '/fluent-bit.conf'; | ||||
|             $parsers_config = $config_path . '/parsers.conf'; | ||||
|             $compose_path = $config_path . '/docker-compose.yml'; | ||||
|             $readme_path = $config_path . '/README.md'; | ||||
|             $config_path = $base_path.'/log-drains'; | ||||
|             $fluent_bit_config = $config_path.'/fluent-bit.conf'; | ||||
|             $parsers_config = $config_path.'/parsers.conf'; | ||||
|             $compose_path = $config_path.'/docker-compose.yml'; | ||||
|             $readme_path = $config_path.'/README.md'; | ||||
|             $command = [ | ||||
|                 "echo 'Saving configuration'", | ||||
|                 "mkdir -p $config_path", | ||||
| @ -180,18 +182,18 @@ Files: | ||||
|                     "echo LICENSE_KEY=$license_key >> $config_path/.env", | ||||
|                     "echo BASE_URI=$base_uri >> $config_path/.env", | ||||
|                 ]; | ||||
|             } else if ($type === 'highlight') { | ||||
|             } elseif ($type === 'highlight') { | ||||
|                 $add_envs_command = [ | ||||
|                     "echo HIGHLIGHT_PROJECT_ID={$server->settings->logdrain_highlight_project_id} >> $config_path/.env", | ||||
|                 ]; | ||||
|             } else if ($type === 'axiom') { | ||||
|             } elseif ($type === 'axiom') { | ||||
|                 $add_envs_command = [ | ||||
|                     "echo AXIOM_DATASET_NAME={$server->settings->logdrain_axiom_dataset_name} >> $config_path/.env", | ||||
|                     "echo AXIOM_API_KEY={$server->settings->logdrain_axiom_api_key} >> $config_path/.env", | ||||
|                 ]; | ||||
|             } else if ($type === 'custom') { | ||||
|             } elseif ($type === 'custom') { | ||||
|                 $add_envs_command = [ | ||||
|                     "touch $config_path/.env" | ||||
|                     "touch $config_path/.env", | ||||
|                 ]; | ||||
|             } else { | ||||
|                 throw new \Exception('Unknown log drain type.'); | ||||
| @ -203,6 +205,7 @@ Files: | ||||
|                 "cd $config_path && docker compose up -d --remove-orphans", | ||||
|             ]; | ||||
|             $command = array_merge($command, $add_envs_command, $restart_command); | ||||
| 
 | ||||
|             return instant_remote_process($command, $server); | ||||
|         } catch (\Throwable $e) { | ||||
|             return handleError($e); | ||||
|  | ||||
| @ -2,12 +2,13 @@ | ||||
| 
 | ||||
| namespace App\Actions\Server; | ||||
| 
 | ||||
| use Lorisleiva\Actions\Concerns\AsAction; | ||||
| use App\Models\Server; | ||||
| use Lorisleiva\Actions\Concerns\AsAction; | ||||
| 
 | ||||
| class StartSentinel | ||||
| { | ||||
|     use AsAction; | ||||
| 
 | ||||
|     public function handle(Server $server, $version = 'latest', bool $restart = false) | ||||
|     { | ||||
|         if ($restart) { | ||||
| @ -15,8 +16,8 @@ class StartSentinel | ||||
|         } | ||||
|         instant_remote_process([ | ||||
|             "docker run --rm --pull always -d -e \"SCHEDULER=true\" --name coolify-sentinel -v /var/run/docker.sock:/var/run/docker.sock -v /data/coolify/metrics:/app/metrics -v /data/coolify/logs:/app/logs --pid host --health-cmd \"curl --fail http://127.0.0.1:8888/api/health || exit 1\" --health-interval 10s --health-retries 3 ghcr.io/coollabsio/sentinel:$version", | ||||
|             "chown -R 9999:root /data/coolify/metrics /data/coolify/logs", | ||||
|             "chmod -R 700 /data/coolify/metrics /data/coolify/logs" | ||||
|             'chown -R 9999:root /data/coolify/metrics /data/coolify/logs', | ||||
|             'chmod -R 700 /data/coolify/metrics /data/coolify/logs', | ||||
|         ], $server, false); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -2,15 +2,18 @@ | ||||
| 
 | ||||
| namespace App\Actions\Server; | ||||
| 
 | ||||
| use Lorisleiva\Actions\Concerns\AsAction; | ||||
| use App\Models\InstanceSettings; | ||||
| use App\Models\Server; | ||||
| use Lorisleiva\Actions\Concerns\AsAction; | ||||
| 
 | ||||
| class UpdateCoolify | ||||
| { | ||||
|     use AsAction; | ||||
| 
 | ||||
|     public ?Server $server = null; | ||||
| 
 | ||||
|     public ?string $latestVersion = null; | ||||
| 
 | ||||
|     public ?string $currentVersion = null; | ||||
| 
 | ||||
|     public function handle($manual_update = false) | ||||
| @ -19,14 +22,14 @@ class UpdateCoolify | ||||
|             $settings = InstanceSettings::get(); | ||||
|             ray('Running InstanceAutoUpdateJob'); | ||||
|             $this->server = Server::find(0); | ||||
|             if (!$this->server) { | ||||
|             if (! $this->server) { | ||||
|                 return; | ||||
|             } | ||||
|             CleanupDocker::run($this->server, false); | ||||
|             $this->latestVersion = get_latest_version_of_coolify(); | ||||
|             $this->currentVersion = config('version'); | ||||
|             if (!$manual_update) { | ||||
|                 if (!$settings->is_auto_update_enabled) { | ||||
|             if (! $manual_update) { | ||||
|                 if (! $settings->is_auto_update_enabled) { | ||||
|                     return; | ||||
|                 } | ||||
|                 if ($this->latestVersion === $this->currentVersion) { | ||||
| @ -46,14 +49,15 @@ class UpdateCoolify | ||||
|     { | ||||
|         if (isDev()) { | ||||
|             remote_process([ | ||||
|                 "sleep 10" | ||||
|                 'sleep 10', | ||||
|             ], $this->server); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
|         remote_process([ | ||||
|             "curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh -o /data/coolify/source/upgrade.sh", | ||||
|             "bash /data/coolify/source/upgrade.sh $this->latestVersion" | ||||
|             'curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh -o /data/coolify/source/upgrade.sh', | ||||
|             "bash /data/coolify/source/upgrade.sh $this->latestVersion", | ||||
|         ], $this->server); | ||||
|         return; | ||||
| 
 | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -2,12 +2,13 @@ | ||||
| 
 | ||||
| namespace App\Actions\Service; | ||||
| 
 | ||||
| use Lorisleiva\Actions\Concerns\AsAction; | ||||
| use App\Models\Service; | ||||
| use Lorisleiva\Actions\Concerns\AsAction; | ||||
| 
 | ||||
| class DeleteService | ||||
| { | ||||
|     use AsAction; | ||||
| 
 | ||||
|     public function handle(Service $service) | ||||
|     { | ||||
|         try { | ||||
|  | ||||
| @ -2,26 +2,27 @@ | ||||
| 
 | ||||
| namespace App\Actions\Service; | ||||
| 
 | ||||
| use Lorisleiva\Actions\Concerns\AsAction; | ||||
| use App\Models\Service; | ||||
| use Lorisleiva\Actions\Concerns\AsAction; | ||||
| use Symfony\Component\Yaml\Yaml; | ||||
| 
 | ||||
| class StartService | ||||
| { | ||||
|     use AsAction; | ||||
| 
 | ||||
|     public function handle(Service $service) | ||||
|     { | ||||
|         ray('Starting service: ' . $service->name); | ||||
|         ray('Starting service: '.$service->name); | ||||
|         $service->saveComposeConfigs(); | ||||
|         $commands[] = "cd " . $service->workdir(); | ||||
|         $commands[] = 'cd '.$service->workdir(); | ||||
|         $commands[] = "echo 'Saved configuration files to {$service->workdir()}.'"; | ||||
|         $commands[] = "echo 'Creating Docker network.'"; | ||||
|         $commands[] = "docker network inspect $service->uuid >/dev/null 2>&1 || docker network create --attachable $service->uuid"; | ||||
|         $commands[] = "echo Starting service."; | ||||
|         $commands[] = 'echo Starting service.'; | ||||
|         $commands[] = "echo 'Pulling images.'"; | ||||
|         $commands[] = "docker compose pull"; | ||||
|         $commands[] = 'docker compose pull'; | ||||
|         $commands[] = "echo 'Starting containers.'"; | ||||
|         $commands[] = "docker compose up -d --remove-orphans --force-recreate --build"; | ||||
|         $commands[] = 'docker compose up -d --remove-orphans --force-recreate --build'; | ||||
|         $commands[] = "docker network connect $service->uuid coolify-proxy >/dev/null 2>&1 || true"; | ||||
|         if (data_get($service, 'connect_to_docker_network')) { | ||||
|             $compose = data_get($service, 'docker_compose', []); | ||||
| @ -32,6 +33,7 @@ class StartService | ||||
|             } | ||||
|         } | ||||
|         $activity = remote_process($commands, $service->server, type_uuid: $service->uuid, callEventOnFinish: 'ServiceStatusChanged'); | ||||
| 
 | ||||
|         return $activity; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -2,20 +2,21 @@ | ||||
| 
 | ||||
| namespace App\Actions\Service; | ||||
| 
 | ||||
| use Lorisleiva\Actions\Concerns\AsAction; | ||||
| use App\Models\Service; | ||||
| use Lorisleiva\Actions\Concerns\AsAction; | ||||
| 
 | ||||
| class StopService | ||||
| { | ||||
|     use AsAction; | ||||
| 
 | ||||
|     public function handle(Service $service) | ||||
|     { | ||||
|         try { | ||||
|             $server = $service->destination->server; | ||||
|             if (!$server->isFunctional()) { | ||||
|             if (! $server->isFunctional()) { | ||||
|                 return 'Server is not functional'; | ||||
|             } | ||||
|             ray('Stopping service: ' . $service->name); | ||||
|             ray('Stopping service: '.$service->name); | ||||
|             $applications = $service->applications()->get(); | ||||
|             foreach ($applications as $application) { | ||||
|                 instant_remote_process(["docker rm -f {$application->name}-{$service->uuid}"], $service->server); | ||||
| @ -33,6 +34,7 @@ class StopService | ||||
|         } catch (\Exception $e) { | ||||
|             echo $e->getMessage(); | ||||
|             ray($e->getMessage()); | ||||
| 
 | ||||
|             return $e->getMessage(); | ||||
|         } | ||||
| 
 | ||||
|  | ||||
| @ -8,18 +8,21 @@ use Lorisleiva\Actions\Concerns\AsAction; | ||||
| class ComplexStatusCheck | ||||
| { | ||||
|     use AsAction; | ||||
| 
 | ||||
|     public function handle(Application $application) | ||||
|     { | ||||
|         $servers = $application->additional_servers; | ||||
|         $servers->push($application->destination->server); | ||||
|         foreach ($servers as $server) { | ||||
|             $is_main_server = $application->destination->server->id === $server->id; | ||||
|             if (!$server->isFunctional()) { | ||||
|             if (! $server->isFunctional()) { | ||||
|                 if ($is_main_server) { | ||||
|                     $application->update(['status' => 'exited:unhealthy']); | ||||
| 
 | ||||
|                     continue; | ||||
|                 } else { | ||||
|                     $application->additional_servers()->updateExistingPivot($server->id, ['status' => 'exited:unhealthy']); | ||||
| 
 | ||||
|                     continue; | ||||
|                 } | ||||
|             } | ||||
| @ -44,9 +47,11 @@ class ComplexStatusCheck | ||||
|             } else { | ||||
|                 if ($is_main_server) { | ||||
|                     $application->update(['status' => 'exited:unhealthy']); | ||||
| 
 | ||||
|                     continue; | ||||
|                 } else { | ||||
|                     $application->additional_servers()->updateExistingPivot($server->id, ['status' => 'exited:unhealthy']); | ||||
| 
 | ||||
|                     continue; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
| @ -8,17 +8,20 @@ use Lorisleiva\Actions\Concerns\AsAction; | ||||
| class PullImage | ||||
| { | ||||
|     use AsAction; | ||||
| 
 | ||||
|     public function handle(Service $resource) | ||||
|     { | ||||
|         $resource->saveComposeConfigs(); | ||||
| 
 | ||||
|         $commands[] = "cd " . $resource->workdir(); | ||||
|         $commands[] = 'cd '.$resource->workdir(); | ||||
|         $commands[] = "echo 'Saved configuration files to {$resource->workdir()}.'"; | ||||
|         $commands[] = "docker compose pull"; | ||||
|         $commands[] = 'docker compose pull'; | ||||
| 
 | ||||
|         $server = data_get($resource, 'server'); | ||||
| 
 | ||||
|         if (!$server) return; | ||||
|         if (! $server) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         instant_remote_process($commands, $resource->server); | ||||
|     } | ||||
|  | ||||
| @ -2,7 +2,6 @@ | ||||
| 
 | ||||
| namespace App\Console\Commands; | ||||
| 
 | ||||
| use App\Models\Server; | ||||
| use App\Models\User; | ||||
| use Illuminate\Console\Command; | ||||
| 
 | ||||
| @ -29,9 +28,10 @@ class AdminRemoveUser extends Command | ||||
|     { | ||||
|         try { | ||||
|             $email = $this->argument('email'); | ||||
|             $confirm = $this->confirm('Are you sure you want to remove user with email: ' . $email . '?'); | ||||
|             if (!$confirm) { | ||||
|             $confirm = $this->confirm('Are you sure you want to remove user with email: '.$email.'?'); | ||||
|             if (! $confirm) { | ||||
|                 $this->info('User removal cancelled.'); | ||||
| 
 | ||||
|                 return; | ||||
|             } | ||||
|             $this->info("Removing user with email: $email"); | ||||
| @ -40,6 +40,7 @@ class AdminRemoveUser extends Command | ||||
|             foreach ($teams as $team) { | ||||
|                 if ($team->members->count() > 1) { | ||||
|                     $this->error('User is a member of a team with more than one member. Please remove user from team first.'); | ||||
| 
 | ||||
|                     return; | ||||
|                 } | ||||
|                 $team->delete(); | ||||
| @ -48,6 +49,7 @@ class AdminRemoveUser extends Command | ||||
|         } catch (\Exception $e) { | ||||
|             $this->error('Failed to remove user.'); | ||||
|             $this->error($e->getMessage()); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -8,6 +8,7 @@ use Illuminate\Console\Command; | ||||
| class CleanupApplicationDeploymentQueue extends Command | ||||
| { | ||||
|     protected $signature = 'cleanup:application-deployment-queue {--team-id=}'; | ||||
| 
 | ||||
|     protected $description = 'CleanupApplicationDeploymentQueue'; | ||||
| 
 | ||||
|     public function handle() | ||||
| @ -15,10 +16,10 @@ class CleanupApplicationDeploymentQueue extends Command | ||||
|         $team_id = $this->option('team-id'); | ||||
|         $servers = \App\Models\Server::where('team_id', $team_id)->get(); | ||||
|         foreach ($servers as $server) { | ||||
|             $deployments = ApplicationDeploymentQueue::whereIn("status", ["in_progress", "queued"])->where("server_id", $server->id)->get(); | ||||
|             $deployments = ApplicationDeploymentQueue::whereIn('status', ['in_progress', 'queued'])->where('server_id', $server->id)->get(); | ||||
|             foreach ($deployments as $deployment) { | ||||
|                 $deployment->update(['status' => 'failed']); | ||||
|                 instant_remote_process(['docker rm -f ' . $deployment->deployment_uuid], $server, false); | ||||
|                 instant_remote_process(['docker rm -f '.$deployment->deployment_uuid], $server, false); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -8,6 +8,7 @@ use Illuminate\Support\Facades\DB; | ||||
| class CleanupDatabase extends Command | ||||
| { | ||||
|     protected $signature = 'cleanup:database {--yes}'; | ||||
| 
 | ||||
|     protected $description = 'Cleanup database'; | ||||
| 
 | ||||
|     public function handle() | ||||
|  | ||||
| @ -8,6 +8,7 @@ use Illuminate\Support\Facades\Redis; | ||||
| class CleanupQueue extends Command | ||||
| { | ||||
|     protected $signature = 'cleanup:queue'; | ||||
| 
 | ||||
|     protected $description = 'Cleanup Queue'; | ||||
| 
 | ||||
|     public function handle() | ||||
|  | ||||
| @ -20,6 +20,7 @@ use Illuminate\Console\Command; | ||||
| class CleanupStuckedResources extends Command | ||||
| { | ||||
|     protected $signature = 'cleanup:stucked-resources'; | ||||
| 
 | ||||
|     protected $description = 'Cleanup Stucked Resources'; | ||||
| 
 | ||||
|     public function handle() | ||||
| @ -28,6 +29,7 @@ class CleanupStuckedResources extends Command | ||||
|         echo "Running cleanup stucked resources.\n"; | ||||
|         $this->cleanup_stucked_resources(); | ||||
|     } | ||||
| 
 | ||||
|     private function cleanup_stucked_resources() | ||||
|     { | ||||
| 
 | ||||
| @ -142,7 +144,7 @@ class CleanupStuckedResources extends Command | ||||
|         try { | ||||
|             $scheduled_tasks = ScheduledTask::all(); | ||||
|             foreach ($scheduled_tasks as $scheduled_task) { | ||||
|                 if (!$scheduled_task->service && !$scheduled_task->application) { | ||||
|                 if (! $scheduled_task->service && ! $scheduled_task->application) { | ||||
|                     echo "Deleting stuck scheduledtask: {$scheduled_task->name}\n"; | ||||
|                     $scheduled_task->delete(); | ||||
|                 } | ||||
| @ -155,19 +157,22 @@ class CleanupStuckedResources extends Command | ||||
|         try { | ||||
|             $applications = Application::all(); | ||||
|             foreach ($applications as $application) { | ||||
|                 if (!data_get($application, 'environment')) { | ||||
|                     echo 'Application without environment: ' . $application->name . '\n'; | ||||
|                 if (! data_get($application, 'environment')) { | ||||
|                     echo 'Application without environment: '.$application->name.'\n'; | ||||
|                     $application->forceDelete(); | ||||
| 
 | ||||
|                     continue; | ||||
|                 } | ||||
|                 if (!$application->destination()) { | ||||
|                     echo 'Application without destination: ' . $application->name . '\n'; | ||||
|                 if (! $application->destination()) { | ||||
|                     echo 'Application without destination: '.$application->name.'\n'; | ||||
|                     $application->forceDelete(); | ||||
| 
 | ||||
|                     continue; | ||||
|                 } | ||||
|                 if (!data_get($application, 'destination.server')) { | ||||
|                     echo 'Application without server: ' . $application->name . '\n'; | ||||
|                 if (! data_get($application, 'destination.server')) { | ||||
|                     echo 'Application without server: '.$application->name.'\n'; | ||||
|                     $application->forceDelete(); | ||||
| 
 | ||||
|                     continue; | ||||
|                 } | ||||
|             } | ||||
| @ -177,19 +182,22 @@ class CleanupStuckedResources extends Command | ||||
|         try { | ||||
|             $postgresqls = StandalonePostgresql::all()->where('id', '!=', 0); | ||||
|             foreach ($postgresqls as $postgresql) { | ||||
|                 if (!data_get($postgresql, 'environment')) { | ||||
|                     echo 'Postgresql without environment: ' . $postgresql->name . '\n'; | ||||
|                 if (! data_get($postgresql, 'environment')) { | ||||
|                     echo 'Postgresql without environment: '.$postgresql->name.'\n'; | ||||
|                     $postgresql->forceDelete(); | ||||
| 
 | ||||
|                     continue; | ||||
|                 } | ||||
|                 if (!$postgresql->destination()) { | ||||
|                     echo 'Postgresql without destination: ' . $postgresql->name . '\n'; | ||||
|                 if (! $postgresql->destination()) { | ||||
|                     echo 'Postgresql without destination: '.$postgresql->name.'\n'; | ||||
|                     $postgresql->forceDelete(); | ||||
| 
 | ||||
|                     continue; | ||||
|                 } | ||||
|                 if (!data_get($postgresql, 'destination.server')) { | ||||
|                     echo 'Postgresql without server: ' . $postgresql->name . '\n'; | ||||
|                 if (! data_get($postgresql, 'destination.server')) { | ||||
|                     echo 'Postgresql without server: '.$postgresql->name.'\n'; | ||||
|                     $postgresql->forceDelete(); | ||||
| 
 | ||||
|                     continue; | ||||
|                 } | ||||
|             } | ||||
| @ -199,19 +207,22 @@ class CleanupStuckedResources extends Command | ||||
|         try { | ||||
|             $redis = StandaloneRedis::all(); | ||||
|             foreach ($redis as $redis) { | ||||
|                 if (!data_get($redis, 'environment')) { | ||||
|                     echo 'Redis without environment: ' . $redis->name . '\n'; | ||||
|                 if (! data_get($redis, 'environment')) { | ||||
|                     echo 'Redis without environment: '.$redis->name.'\n'; | ||||
|                     $redis->forceDelete(); | ||||
| 
 | ||||
|                     continue; | ||||
|                 } | ||||
|                 if (!$redis->destination()) { | ||||
|                     echo 'Redis without destination: ' . $redis->name . '\n'; | ||||
|                 if (! $redis->destination()) { | ||||
|                     echo 'Redis without destination: '.$redis->name.'\n'; | ||||
|                     $redis->forceDelete(); | ||||
| 
 | ||||
|                     continue; | ||||
|                 } | ||||
|                 if (!data_get($redis, 'destination.server')) { | ||||
|                     echo 'Redis without server: ' . $redis->name . '\n'; | ||||
|                 if (! data_get($redis, 'destination.server')) { | ||||
|                     echo 'Redis without server: '.$redis->name.'\n'; | ||||
|                     $redis->forceDelete(); | ||||
| 
 | ||||
|                     continue; | ||||
|                 } | ||||
|             } | ||||
| @ -222,19 +233,22 @@ class CleanupStuckedResources extends Command | ||||
|         try { | ||||
|             $mongodbs = StandaloneMongodb::all(); | ||||
|             foreach ($mongodbs as $mongodb) { | ||||
|                 if (!data_get($mongodb, 'environment')) { | ||||
|                     echo 'Mongodb without environment: ' . $mongodb->name . '\n'; | ||||
|                 if (! data_get($mongodb, 'environment')) { | ||||
|                     echo 'Mongodb without environment: '.$mongodb->name.'\n'; | ||||
|                     $mongodb->forceDelete(); | ||||
| 
 | ||||
|                     continue; | ||||
|                 } | ||||
|                 if (!$mongodb->destination()) { | ||||
|                     echo 'Mongodb without destination: ' . $mongodb->name . '\n'; | ||||
|                 if (! $mongodb->destination()) { | ||||
|                     echo 'Mongodb without destination: '.$mongodb->name.'\n'; | ||||
|                     $mongodb->forceDelete(); | ||||
| 
 | ||||
|                     continue; | ||||
|                 } | ||||
|                 if (!data_get($mongodb, 'destination.server')) { | ||||
|                     echo 'Mongodb without server:  ' . $mongodb->name . '\n'; | ||||
|                 if (! data_get($mongodb, 'destination.server')) { | ||||
|                     echo 'Mongodb without server:  '.$mongodb->name.'\n'; | ||||
|                     $mongodb->forceDelete(); | ||||
| 
 | ||||
|                     continue; | ||||
|                 } | ||||
|             } | ||||
| @ -245,19 +259,22 @@ class CleanupStuckedResources extends Command | ||||
|         try { | ||||
|             $mysqls = StandaloneMysql::all(); | ||||
|             foreach ($mysqls as $mysql) { | ||||
|                 if (!data_get($mysql, 'environment')) { | ||||
|                     echo 'Mysql without environment: ' . $mysql->name . '\n'; | ||||
|                 if (! data_get($mysql, 'environment')) { | ||||
|                     echo 'Mysql without environment: '.$mysql->name.'\n'; | ||||
|                     $mysql->forceDelete(); | ||||
| 
 | ||||
|                     continue; | ||||
|                 } | ||||
|                 if (!$mysql->destination()) { | ||||
|                     echo 'Mysql without destination: ' . $mysql->name . '\n'; | ||||
|                 if (! $mysql->destination()) { | ||||
|                     echo 'Mysql without destination: '.$mysql->name.'\n'; | ||||
|                     $mysql->forceDelete(); | ||||
| 
 | ||||
|                     continue; | ||||
|                 } | ||||
|                 if (!data_get($mysql, 'destination.server')) { | ||||
|                     echo 'Mysql without server: ' . $mysql->name . '\n'; | ||||
|                 if (! data_get($mysql, 'destination.server')) { | ||||
|                     echo 'Mysql without server: '.$mysql->name.'\n'; | ||||
|                     $mysql->forceDelete(); | ||||
| 
 | ||||
|                     continue; | ||||
|                 } | ||||
|             } | ||||
| @ -268,19 +285,22 @@ class CleanupStuckedResources extends Command | ||||
|         try { | ||||
|             $mariadbs = StandaloneMariadb::all(); | ||||
|             foreach ($mariadbs as $mariadb) { | ||||
|                 if (!data_get($mariadb, 'environment')) { | ||||
|                     echo 'Mariadb without environment: ' . $mariadb->name . '\n'; | ||||
|                 if (! data_get($mariadb, 'environment')) { | ||||
|                     echo 'Mariadb without environment: '.$mariadb->name.'\n'; | ||||
|                     $mariadb->forceDelete(); | ||||
| 
 | ||||
|                     continue; | ||||
|                 } | ||||
|                 if (!$mariadb->destination()) { | ||||
|                     echo 'Mariadb without destination: ' . $mariadb->name . '\n'; | ||||
|                 if (! $mariadb->destination()) { | ||||
|                     echo 'Mariadb without destination: '.$mariadb->name.'\n'; | ||||
|                     $mariadb->forceDelete(); | ||||
| 
 | ||||
|                     continue; | ||||
|                 } | ||||
|                 if (!data_get($mariadb, 'destination.server')) { | ||||
|                     echo 'Mariadb without server: ' . $mariadb->name . '\n'; | ||||
|                 if (! data_get($mariadb, 'destination.server')) { | ||||
|                     echo 'Mariadb without server: '.$mariadb->name.'\n'; | ||||
|                     $mariadb->forceDelete(); | ||||
| 
 | ||||
|                     continue; | ||||
|                 } | ||||
|             } | ||||
| @ -291,19 +311,22 @@ class CleanupStuckedResources extends Command | ||||
|         try { | ||||
|             $services = Service::all(); | ||||
|             foreach ($services as $service) { | ||||
|                 if (!data_get($service, 'environment')) { | ||||
|                     echo 'Service without environment: ' . $service->name . '\n'; | ||||
|                 if (! data_get($service, 'environment')) { | ||||
|                     echo 'Service without environment: '.$service->name.'\n'; | ||||
|                     $service->forceDelete(); | ||||
| 
 | ||||
|                     continue; | ||||
|                 } | ||||
|                 if (!$service->destination()) { | ||||
|                     echo 'Service without destination: ' . $service->name . '\n'; | ||||
|                 if (! $service->destination()) { | ||||
|                     echo 'Service without destination: '.$service->name.'\n'; | ||||
|                     $service->forceDelete(); | ||||
| 
 | ||||
|                     continue; | ||||
|                 } | ||||
|                 if (!data_get($service, 'server')) { | ||||
|                     echo 'Service without server: ' . $service->name . '\n'; | ||||
|                 if (! data_get($service, 'server')) { | ||||
|                     echo 'Service without server: '.$service->name.'\n'; | ||||
|                     $service->forceDelete(); | ||||
| 
 | ||||
|                     continue; | ||||
|                 } | ||||
|             } | ||||
| @ -313,9 +336,10 @@ class CleanupStuckedResources extends Command | ||||
|         try { | ||||
|             $serviceApplications = ServiceApplication::all(); | ||||
|             foreach ($serviceApplications as $service) { | ||||
|                 if (!data_get($service, 'service')) { | ||||
|                     echo 'ServiceApplication without service: ' . $service->name . '\n'; | ||||
|                 if (! data_get($service, 'service')) { | ||||
|                     echo 'ServiceApplication without service: '.$service->name.'\n'; | ||||
|                     $service->forceDelete(); | ||||
| 
 | ||||
|                     continue; | ||||
|                 } | ||||
|             } | ||||
| @ -325,9 +349,10 @@ class CleanupStuckedResources extends Command | ||||
|         try { | ||||
|             $serviceDatabases = ServiceDatabase::all(); | ||||
|             foreach ($serviceDatabases as $service) { | ||||
|                 if (!data_get($service, 'service')) { | ||||
|                     echo 'ServiceDatabase without service: ' . $service->name . '\n'; | ||||
|                 if (! data_get($service, 'service')) { | ||||
|                     echo 'ServiceDatabase without service: '.$service->name.'\n'; | ||||
|                     $service->forceDelete(); | ||||
| 
 | ||||
|                     continue; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
| @ -8,6 +8,7 @@ use Illuminate\Console\Command; | ||||
| class CleanupUnreachableServers extends Command | ||||
| { | ||||
|     protected $signature = 'cleanup:unreachable-servers'; | ||||
| 
 | ||||
|     protected $description = 'Cleanup Unreachable Servers (7 days)'; | ||||
| 
 | ||||
|     public function handle() | ||||
| @ -19,7 +20,7 @@ class CleanupUnreachableServers extends Command | ||||
|                 echo "Cleanup unreachable server ($server->id) with name $server->name"; | ||||
|                 send_internal_notification("Server $server->name is unreachable for 7 days. Cleaning up..."); | ||||
|                 $server->update([ | ||||
|                     'ip' => '1.2.3.4' | ||||
|                     'ip' => '1.2.3.4', | ||||
|                 ]); | ||||
|             } | ||||
|         } | ||||
|  | ||||
| @ -10,6 +10,7 @@ use Illuminate\Support\Facades\Process; | ||||
| class Dev extends Command | ||||
| { | ||||
|     protected $signature = 'dev:init'; | ||||
| 
 | ||||
|     protected $description = 'Init the app in dev mode'; | ||||
| 
 | ||||
|     public function handle() | ||||
| @ -21,7 +22,7 @@ class Dev extends Command | ||||
|         } | ||||
|         // Seed database if it's empty
 | ||||
|         $settings = InstanceSettings::find(0); | ||||
|         if (!$settings) { | ||||
|         if (! $settings) { | ||||
|             echo "Initializing instance, seeding database.\n"; | ||||
|             Artisan::call('migrate --seed'); | ||||
|         } else { | ||||
|  | ||||
| @ -2,12 +2,10 @@ | ||||
| 
 | ||||
| namespace App\Console\Commands; | ||||
| 
 | ||||
| use App\Jobs\DatabaseBackupStatusJob; | ||||
| use App\Jobs\SendConfirmationForWaitlistJob; | ||||
| use App\Models\Application; | ||||
| use App\Models\ApplicationPreview; | ||||
| use App\Models\ScheduledDatabaseBackup; | ||||
| use App\Models\ScheduledDatabaseBackupExecution; | ||||
| use App\Models\Server; | ||||
| use App\Models\StandalonePostgresql; | ||||
| use App\Models\Team; | ||||
| @ -49,7 +47,9 @@ class Emails extends Command | ||||
|      * Execute the console command. | ||||
|      */ | ||||
|     private ?MailMessage $mail = null; | ||||
| 
 | ||||
|     private ?string $email = null; | ||||
| 
 | ||||
|     public function handle() | ||||
|     { | ||||
|         $type = select( | ||||
| @ -73,21 +73,22 @@ class Emails extends Command | ||||
|         ); | ||||
|         $emailsGathered = ['realusers-before-trial', 'realusers-server-lost-connection']; | ||||
|         if (isDev()) { | ||||
|             $this->email = "test@example.com"; | ||||
|             $this->email = 'test@example.com'; | ||||
|         } else { | ||||
|             if (!in_array($type, $emailsGathered)) { | ||||
|             if (! in_array($type, $emailsGathered)) { | ||||
|                 $this->email = text('Email Address to send to:'); | ||||
|             } | ||||
|         } | ||||
|         set_transanctional_email_settings(); | ||||
| 
 | ||||
|         $this->mail = new MailMessage(); | ||||
|         $this->mail->subject("Test Email"); | ||||
|         $this->mail->subject('Test Email'); | ||||
|         switch ($type) { | ||||
|             case 'updates': | ||||
|                 $teams = Team::all(); | ||||
|                 if (!$teams || $teams->isEmpty()) { | ||||
|                     echo 'No teams found.' . PHP_EOL; | ||||
|                 if (! $teams || $teams->isEmpty()) { | ||||
|                     echo 'No teams found.'.PHP_EOL; | ||||
| 
 | ||||
|                     return; | ||||
|                 } | ||||
|                 $emails = []; | ||||
| @ -99,7 +100,7 @@ class Emails extends Command | ||||
|                     } | ||||
|                 } | ||||
|                 $emails = array_unique($emails); | ||||
|                 $this->info("Sending to " . count($emails) . " emails."); | ||||
|                 $this->info('Sending to '.count($emails).' emails.'); | ||||
|                 foreach ($emails as $email) { | ||||
|                     $this->info($email); | ||||
|                 } | ||||
| @ -111,7 +112,7 @@ class Emails extends Command | ||||
|                         $unsubscribeUrl = route('unsubscribe.marketing.emails', [ | ||||
|                             'token' => encrypt($email), | ||||
|                         ]); | ||||
|                         $this->mail->view('emails.updates', ["unsubscribeUrl" => $unsubscribeUrl]); | ||||
|                         $this->mail->view('emails.updates', ['unsubscribeUrl' => $unsubscribeUrl]); | ||||
|                         $this->sendEmail($email); | ||||
|                     } | ||||
|                 } | ||||
| @ -157,7 +158,7 @@ class Emails extends Command | ||||
|             case 'application-deployment-failed': | ||||
|                 $application = Application::all()->first(); | ||||
|                 $preview = ApplicationPreview::all()->first(); | ||||
|                 if (!$preview) { | ||||
|                 if (! $preview) { | ||||
|                     $preview = ApplicationPreview::create([ | ||||
|                         'application_id' => $application->id, | ||||
|                         'pull_request_id' => 1, | ||||
| @ -178,7 +179,7 @@ class Emails extends Command | ||||
|             case 'backup-failed': | ||||
|                 $backup = ScheduledDatabaseBackup::all()->first(); | ||||
|                 $db = StandalonePostgresql::all()->first(); | ||||
|                 if (!$backup) { | ||||
|                 if (! $backup) { | ||||
|                     $backup = ScheduledDatabaseBackup::create([ | ||||
|                         'enabled' => true, | ||||
|                         'frequency' => 'daily', | ||||
| @ -188,14 +189,14 @@ class Emails extends Command | ||||
|                         'team_id' => 0, | ||||
|                     ]); | ||||
|                 } | ||||
|                 $output = 'Because of an error, the backup of the database ' . $db->name . ' failed.'; | ||||
|                 $output = 'Because of an error, the backup of the database '.$db->name.' failed.'; | ||||
|                 $this->mail = (new BackupFailed($backup, $db, $output))->toMail(); | ||||
|                 $this->sendEmail(); | ||||
|                 break; | ||||
|             case 'backup-success': | ||||
|                 $backup = ScheduledDatabaseBackup::all()->first(); | ||||
|                 $db = StandalonePostgresql::all()->first(); | ||||
|                 if (!$backup) { | ||||
|                 if (! $backup) { | ||||
|                     $backup = ScheduledDatabaseBackup::create([ | ||||
|                         'enabled' => true, | ||||
|                         'frequency' => 'daily', | ||||
| @ -244,8 +245,9 @@ class Emails extends Command | ||||
|                 $this->mail->view('emails.before-trial-conversion'); | ||||
|                 $this->mail->subject('Trial period has been added for all subscription plans.'); | ||||
|                 $teams = Team::doesntHave('subscription')->where('id', '!=', 0)->get(); | ||||
|                 if (!$teams || $teams->isEmpty()) { | ||||
|                     echo 'No teams found.' . PHP_EOL; | ||||
|                 if (! $teams || $teams->isEmpty()) { | ||||
|                     echo 'No teams found.'.PHP_EOL; | ||||
| 
 | ||||
|                     return; | ||||
|                 } | ||||
|                 $emails = []; | ||||
| @ -257,7 +259,7 @@ class Emails extends Command | ||||
|                     } | ||||
|                 } | ||||
|                 $emails = array_unique($emails); | ||||
|                 $this->info("Sending to " . count($emails) . " emails."); | ||||
|                 $this->info('Sending to '.count($emails).' emails.'); | ||||
|                 foreach ($emails as $email) { | ||||
|                     $this->info($email); | ||||
|                 } | ||||
| @ -271,7 +273,7 @@ class Emails extends Command | ||||
|             case 'realusers-server-lost-connection': | ||||
|                 $serverId = text('Server Id'); | ||||
|                 $server = Server::find($serverId); | ||||
|                 if (!$server) { | ||||
|                 if (! $server) { | ||||
|                     throw new Exception('Server not found'); | ||||
|                 } | ||||
|                 $admins = []; | ||||
| @ -281,7 +283,7 @@ class Emails extends Command | ||||
|                         $admins[] = $member->email; | ||||
|                     } | ||||
|                 } | ||||
|                 $this->info('Sending to ' . count($admins) . ' admins.'); | ||||
|                 $this->info('Sending to '.count($admins).' admins.'); | ||||
|                 foreach ($admins as $admin) { | ||||
|                     $this->info($admin); | ||||
|                 } | ||||
| @ -289,14 +291,15 @@ class Emails extends Command | ||||
|                 $this->mail->view('emails.server-lost-connection', [ | ||||
|                     'name' => $server->name, | ||||
|                 ]); | ||||
|                 $this->mail->subject('Action required: Server ' . $server->name . ' lost connection.'); | ||||
|                 $this->mail->subject('Action required: Server '.$server->name.' lost connection.'); | ||||
|                 foreach ($admins as $email) { | ||||
|                     $this->sendEmail($email); | ||||
|                 } | ||||
|                 break; | ||||
|         } | ||||
|     } | ||||
|     private function sendEmail(string $email = null) | ||||
| 
 | ||||
|     private function sendEmail(?string $email = null) | ||||
|     { | ||||
|         if ($email) { | ||||
|             $this->email = $email; | ||||
| @ -307,7 +310,7 @@ class Emails extends Command | ||||
|             fn (Message $message) => $message | ||||
|                 ->to($this->email) | ||||
|                 ->subject($this->mail->subject) | ||||
|                 ->html((string)$this->mail->render()) | ||||
|                 ->html((string) $this->mail->render()) | ||||
|         ); | ||||
|         $this->info("Email sent to $this->email successfully. 📧"); | ||||
|     } | ||||
|  | ||||
| @ -7,7 +7,9 @@ use Illuminate\Console\Command; | ||||
| class Horizon extends Command | ||||
| { | ||||
|     protected $signature = 'start:horizon'; | ||||
| 
 | ||||
|     protected $description = 'Start Horizon'; | ||||
| 
 | ||||
|     public function handle() | ||||
|     { | ||||
|         if (config('coolify.is_horizon_enabled')) { | ||||
|  | ||||
| @ -15,6 +15,7 @@ use Illuminate\Support\Facades\Http; | ||||
| class Init extends Command | ||||
| { | ||||
|     protected $signature = 'app:init {--full-cleanup} {--cleanup-deployments}'; | ||||
| 
 | ||||
|     protected $description = 'Cleanup instance related stuffs'; | ||||
| 
 | ||||
|     public function handle() | ||||
| @ -26,6 +27,7 @@ class Init extends Command | ||||
|         if ($cleanup_deployments) { | ||||
|             echo "Running cleanup deployments.\n"; | ||||
|             $this->cleanup_in_progress_application_deployments(); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
|         if ($full_cleanup) { | ||||
| @ -35,7 +37,7 @@ class Init extends Command | ||||
|             $this->cleanup_stucked_helper_containers(); | ||||
|             $this->call('cleanup:queue'); | ||||
|             $this->call('cleanup:stucked-resources'); | ||||
|             if (!isCloud()) { | ||||
|             if (! isCloud()) { | ||||
|                 try { | ||||
|                     $server = Server::find(0)->first(); | ||||
|                     $server->setupDynamicProxyConfiguration(); | ||||
| @ -45,13 +47,14 @@ class Init extends Command | ||||
|             } | ||||
| 
 | ||||
|             $settings = InstanceSettings::get(); | ||||
|             if (!is_null(env('AUTOUPDATE', null))) { | ||||
|             if (! is_null(env('AUTOUPDATE', null))) { | ||||
|                 if (env('AUTOUPDATE') == true) { | ||||
|                     $settings->update(['is_auto_update_enabled' => true]); | ||||
|                 } else { | ||||
|                     $settings->update(['is_auto_update_enabled' => false]); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
|         $this->cleanup_stucked_helper_containers(); | ||||
| @ -66,7 +69,7 @@ class Init extends Command | ||||
|                 echo "Restoring coolify db backup\n"; | ||||
|                 $database->restore(); | ||||
|                 $scheduledBackup = ScheduledDatabaseBackup::find(0); | ||||
|                 if (!$scheduledBackup) { | ||||
|                 if (! $scheduledBackup) { | ||||
|                     ScheduledDatabaseBackup::create([ | ||||
|                         'id' => 0, | ||||
|                         'enabled' => true, | ||||
| @ -82,6 +85,7 @@ class Init extends Command | ||||
|             echo "Error in restoring coolify db backup: {$e->getMessage()}\n"; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private function cleanup_stucked_helper_containers() | ||||
|     { | ||||
|         $servers = Server::all(); | ||||
| @ -91,6 +95,7 @@ class Init extends Command | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private function alive() | ||||
|     { | ||||
|         $id = config('app.id'); | ||||
| @ -99,6 +104,7 @@ class Init extends Command | ||||
|         $do_not_track = data_get($settings, 'do_not_track'); | ||||
|         if ($do_not_track == true) { | ||||
|             echo "Skipping alive as do_not_track is enabled\n"; | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
|         try { | ||||
|  | ||||
| @ -3,6 +3,7 @@ | ||||
| namespace App\Console\Commands; | ||||
| 
 | ||||
| use Illuminate\Console\Command; | ||||
| 
 | ||||
| use function Termwind\ask; | ||||
| use function Termwind\render; | ||||
| use function Termwind\style; | ||||
| @ -32,6 +33,7 @@ class NotifyDemo extends Command | ||||
| 
 | ||||
|         if (blank($channel)) { | ||||
|             $this->showHelp(); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|  | ||||
| @ -35,6 +35,7 @@ class RootChangeEmail extends Command | ||||
|             $this->info('Root user\'s email updated successfully.'); | ||||
|         } catch (\Exception $e) { | ||||
|             $this->error('Failed to update root user\'s email.'); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -34,6 +34,7 @@ class RootResetPassword extends Command | ||||
|         $passwordAgain = password('Again'); | ||||
|         if ($password != $passwordAgain) { | ||||
|             $this->error('Passwords do not match.'); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
|         $this->info('Updating root password...'); | ||||
| @ -42,6 +43,7 @@ class RootResetPassword extends Command | ||||
|             $this->info('Root password updated successfully.'); | ||||
|         } catch (\Exception $e) { | ||||
|             $this->error('Failed to update root password.'); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -7,7 +7,9 @@ use Illuminate\Console\Command; | ||||
| class Scheduler extends Command | ||||
| { | ||||
|     protected $signature = 'start:scheduler'; | ||||
| 
 | ||||
|     protected $description = 'Start Scheduler'; | ||||
| 
 | ||||
|     public function handle() | ||||
|     { | ||||
|         if (config('coolify.is_scheduler_enabled')) { | ||||
|  | ||||
| @ -48,11 +48,13 @@ class ServicesDelete extends Command | ||||
|             $this->deleteServer(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private function deleteServer() | ||||
|     { | ||||
|         $servers = Server::all(); | ||||
|         if ($servers->count() === 0) { | ||||
|             $this->error('There are no applications to delete.'); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
|         $serversToDelete = multiselect( | ||||
| @ -64,19 +66,21 @@ class ServicesDelete extends Command | ||||
|             $toDelete = $servers->where('id', $server)->first(); | ||||
|             if ($toDelete) { | ||||
|                 $this->info($toDelete); | ||||
|                 $confirmed = confirm("Are you sure you want to delete all selected resources?"); | ||||
|                 if (!$confirmed) { | ||||
|                 $confirmed = confirm('Are you sure you want to delete all selected resources?'); | ||||
|                 if (! $confirmed) { | ||||
|                     break; | ||||
|                 } | ||||
|                 $toDelete->delete(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private function deleteApplication() | ||||
|     { | ||||
|         $applications = Application::all(); | ||||
|         if ($applications->count() === 0) { | ||||
|             $this->error('There are no applications to delete.'); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
|         $applicationsToDelete = multiselect( | ||||
| @ -88,19 +92,21 @@ class ServicesDelete extends Command | ||||
|             $toDelete = $applications->where('id', $application)->first(); | ||||
|             if ($toDelete) { | ||||
|                 $this->info($toDelete); | ||||
|                 $confirmed = confirm("Are you sure you want to delete all selected resources? "); | ||||
|                 if (!$confirmed) { | ||||
|                 $confirmed = confirm('Are you sure you want to delete all selected resources? '); | ||||
|                 if (! $confirmed) { | ||||
|                     break; | ||||
|                 } | ||||
|                 DeleteResourceJob::dispatch($toDelete); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private function deleteDatabase() | ||||
|     { | ||||
|         $databases = StandalonePostgresql::all(); | ||||
|         if ($databases->count() === 0) { | ||||
|             $this->error('There are no databases to delete.'); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
|         $databasesToDelete = multiselect( | ||||
| @ -112,19 +118,21 @@ class ServicesDelete extends Command | ||||
|             $toDelete = $databases->where('id', $database)->first(); | ||||
|             if ($toDelete) { | ||||
|                 $this->info($toDelete); | ||||
|                 $confirmed = confirm("Are you sure you want to delete all selected resources?"); | ||||
|                 if (!$confirmed) { | ||||
|                 $confirmed = confirm('Are you sure you want to delete all selected resources?'); | ||||
|                 if (! $confirmed) { | ||||
|                     return; | ||||
|                 } | ||||
|                 DeleteResourceJob::dispatch($toDelete); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private function deleteService() | ||||
|     { | ||||
|         $services = Service::all(); | ||||
|         if ($services->count() === 0) { | ||||
|             $this->error('There are no services to delete.'); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
|         $servicesToDelete = multiselect( | ||||
| @ -136,8 +144,8 @@ class ServicesDelete extends Command | ||||
|             $toDelete = $services->where('id', $service)->first(); | ||||
|             if ($toDelete) { | ||||
|                 $this->info($toDelete); | ||||
|                 $confirmed = confirm("Are you sure you want to delete all selected resources?"); | ||||
|                 if (!$confirmed) { | ||||
|                 $confirmed = confirm('Are you sure you want to delete all selected resources?'); | ||||
|                 if (! $confirmed) { | ||||
|                     return; | ||||
|                 } | ||||
|                 DeleteResourceJob::dispatch($toDelete); | ||||
|  | ||||
| @ -50,12 +50,13 @@ class ServicesGenerate extends Command | ||||
|         // $this->info($content);
 | ||||
|         $ignore = collect(preg_grep('/^# ignore:/', explode("\n", $content)))->values(); | ||||
|         if ($ignore->count() > 0) { | ||||
|             $ignore = (bool)str($ignore[0])->after('# ignore:')->trim()->value(); | ||||
|             $ignore = (bool) str($ignore[0])->after('# ignore:')->trim()->value(); | ||||
|         } else { | ||||
|             $ignore = false; | ||||
|         } | ||||
|         if ($ignore) { | ||||
|             $this->info("Ignoring $file"); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
|         $this->info("Processing $file"); | ||||
| @ -125,6 +126,7 @@ class ServicesGenerate extends Command | ||||
|             $env_file_base64 = base64_encode($env_file_content); | ||||
|             $payload['envs'] = $env_file_base64; | ||||
|         } | ||||
| 
 | ||||
|         return $payload; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -33,30 +33,31 @@ class SyncBunny extends Command | ||||
|         $that = $this; | ||||
|         $only_template = $this->option('templates'); | ||||
|         $only_version = $this->option('release'); | ||||
|         $bunny_cdn = "https://cdn.coollabs.io"; | ||||
|         $bunny_cdn_path = "coolify"; | ||||
|         $bunny_cdn_storage_name = "coolcdn"; | ||||
|         $bunny_cdn = 'https://cdn.coollabs.io'; | ||||
|         $bunny_cdn_path = 'coolify'; | ||||
|         $bunny_cdn_storage_name = 'coolcdn'; | ||||
| 
 | ||||
|         $parent_dir = realpath(dirname(__FILE__) . '/../../..'); | ||||
|         $parent_dir = realpath(dirname(__FILE__).'/../../..'); | ||||
| 
 | ||||
|         $compose_file = "docker-compose.yml"; | ||||
|         $compose_file_prod = "docker-compose.prod.yml"; | ||||
|         $install_script = "install.sh"; | ||||
|         $upgrade_script = "upgrade.sh"; | ||||
|         $production_env = ".env.production"; | ||||
|         $service_template = "service-templates.json"; | ||||
|         $compose_file = 'docker-compose.yml'; | ||||
|         $compose_file_prod = 'docker-compose.prod.yml'; | ||||
|         $install_script = 'install.sh'; | ||||
|         $upgrade_script = 'upgrade.sh'; | ||||
|         $production_env = '.env.production'; | ||||
|         $service_template = 'service-templates.json'; | ||||
| 
 | ||||
|         $versions = "versions.json"; | ||||
|         $versions = 'versions.json'; | ||||
| 
 | ||||
|         PendingRequest::macro('storage', function ($fileName) use ($that) { | ||||
|             $headers = [ | ||||
|                 'AccessKey' => env('BUNNY_STORAGE_API_KEY'), | ||||
|                 'Accept' => 'application/json', | ||||
|                 'Content-Type' => 'application/octet-stream' | ||||
|                 'Content-Type' => 'application/octet-stream', | ||||
|             ]; | ||||
|             $fileStream = fopen($fileName, "r"); | ||||
|             $fileStream = fopen($fileName, 'r'); | ||||
|             $file = fread($fileStream, filesize($fileName)); | ||||
|             $that->info('Uploading: ' . $fileName); | ||||
|             $that->info('Uploading: '.$fileName); | ||||
| 
 | ||||
|             return PendingRequest::baseUrl('https://storage.bunnycdn.com')->withHeaders($headers)->withBody($file)->throw(); | ||||
|         }); | ||||
|         PendingRequest::macro('purge', function ($url) use ($that) { | ||||
| @ -64,20 +65,21 @@ class SyncBunny extends Command | ||||
|                 'AccessKey' => env('BUNNY_API_KEY'), | ||||
|                 'Accept' => 'application/json', | ||||
|             ]; | ||||
|             $that->info('Purging: ' . $url); | ||||
|             $that->info('Purging: '.$url); | ||||
| 
 | ||||
|             return PendingRequest::withHeaders($headers)->get('https://api.bunny.net/purge', [ | ||||
|                 "url" => $url, | ||||
|                 "async" => false | ||||
|                 'url' => $url, | ||||
|                 'async' => false, | ||||
|             ]); | ||||
|         }); | ||||
|         try { | ||||
|             if (!$only_template && !$only_version) { | ||||
|             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.'); | ||||
|                 $confirmed = confirm("Are you sure you want to sync?"); | ||||
|                 if (!$confirmed) { | ||||
|                 $confirmed = confirm('Are you sure you want to sync?'); | ||||
|                 if (! $confirmed) { | ||||
|                     return; | ||||
|                 } | ||||
|                 Http::pool(fn (Pool $pool) => [ | ||||
| @ -85,15 +87,16 @@ class SyncBunny extends Command | ||||
|                     $pool->purge("$bunny_cdn/$bunny_cdn_path/$service_template"), | ||||
|                 ]); | ||||
|                 $this->info('Service template uploaded & purged...'); | ||||
| 
 | ||||
|                 return; | ||||
|             } else if ($only_version) { | ||||
|             } elseif ($only_version) { | ||||
|                 $this->info('About to sync versions.json to BunnyCDN.'); | ||||
|                 $file = file_get_contents("$parent_dir/$versions"); | ||||
|                 $json = json_decode($file, true); | ||||
|                 $actual_version = data_get($json, 'coolify.v4.version'); | ||||
| 
 | ||||
|                 $confirmed = confirm("Are you sure you want to sync to {$actual_version}?"); | ||||
|                 if (!$confirmed) { | ||||
|                 if (! $confirmed) { | ||||
|                     return; | ||||
|                 } | ||||
|                 Http::pool(fn (Pool $pool) => [ | ||||
| @ -101,10 +104,10 @@ class SyncBunny extends Command | ||||
|                     $pool->purge("$bunny_cdn/$bunny_cdn_path/$versions"), | ||||
|                 ]); | ||||
|                 $this->info('versions.json uploaded & purged...'); | ||||
| 
 | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
| 
 | ||||
|             Http::pool(fn (Pool $pool) => [ | ||||
|                 $pool->storage(fileName: "$parent_dir/$compose_file")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file"), | ||||
|                 $pool->storage(fileName: "$parent_dir/$compose_file_prod")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file_prod"), | ||||
| @ -119,9 +122,9 @@ class SyncBunny extends Command | ||||
|                 $pool->purge("$bunny_cdn/$bunny_cdn_path/$upgrade_script"), | ||||
|                 $pool->purge("$bunny_cdn/$bunny_cdn_path/$install_script"), | ||||
|             ]); | ||||
|             $this->info("All files uploaded & purged..."); | ||||
|             $this->info('All files uploaded & purged...'); | ||||
|         } catch (\Throwable $e) { | ||||
|             $this->error("Error: " . $e->getMessage()); | ||||
|             $this->error('Error: '.$e->getMessage()); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -13,7 +13,9 @@ use Illuminate\Support\Str; | ||||
| class WaitlistInvite extends Command | ||||
| { | ||||
|     public Waitlist|User|null $next_patient = null; | ||||
|     public string|null $password = null; | ||||
| 
 | ||||
|     public ?string $password = null; | ||||
| 
 | ||||
|     /** | ||||
|      * The name and signature of the console command. | ||||
|      * | ||||
| @ -38,7 +40,9 @@ class WaitlistInvite extends Command | ||||
|             $this->main(); | ||||
|         } | ||||
|     } | ||||
|     private function main() { | ||||
| 
 | ||||
|     private function main() | ||||
|     { | ||||
|         if ($this->argument('email')) { | ||||
|             if ($this->option('only-email')) { | ||||
|                 $this->next_patient = User::whereEmail($this->argument('email'))->first(); | ||||
| @ -50,8 +54,9 @@ class WaitlistInvite extends Command | ||||
|             } else { | ||||
|                 $this->next_patient = Waitlist::where('email', $this->argument('email'))->first(); | ||||
|             } | ||||
|             if (!$this->next_patient) { | ||||
|             if (! $this->next_patient) { | ||||
|                 $this->error("{$this->argument('email')} not found in the waitlist."); | ||||
| 
 | ||||
|                 return; | ||||
|             } | ||||
|         } else { | ||||
| @ -60,6 +65,7 @@ class WaitlistInvite extends Command | ||||
|         if ($this->next_patient) { | ||||
|             if ($this->option('only-email')) { | ||||
|                 $this->send_email(); | ||||
| 
 | ||||
|                 return; | ||||
|             } | ||||
|             $this->register_user(); | ||||
| @ -69,10 +75,11 @@ class WaitlistInvite extends Command | ||||
|             $this->info('No verified user found in the waitlist. 👀'); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private function register_user() | ||||
|     { | ||||
|         $already_registered = User::whereEmail($this->next_patient->email)->first(); | ||||
|         if (!$already_registered) { | ||||
|         if (! $already_registered) { | ||||
|             $this->password = Str::password(); | ||||
|             User::create([ | ||||
|                 'name' => Str::of($this->next_patient->email)->before('@'), | ||||
| @ -85,11 +92,13 @@ class WaitlistInvite extends Command | ||||
|             throw new \Exception('User already registered'); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private function remove_from_waitlist() | ||||
|     { | ||||
|         $this->next_patient->delete(); | ||||
|         $this->info("User removed from waitlist successfully."); | ||||
|         $this->info('User removed from waitlist successfully.'); | ||||
|     } | ||||
| 
 | ||||
|     private function send_email() | ||||
|     { | ||||
|         $token = Crypt::encryptString("{$this->next_patient->email}@@@$this->password"); | ||||
| @ -100,6 +109,6 @@ class WaitlistInvite extends Command | ||||
|         ]); | ||||
|         $mail->subject('Congratulations! You are invited to join Coolify Cloud.'); | ||||
|         send_user_an_email($mail, $this->next_patient->email); | ||||
|         $this->info("Email sent successfully. 📧"); | ||||
|         $this->info('Email sent successfully. 📧'); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -4,13 +4,13 @@ namespace App\Console; | ||||
| 
 | ||||
| use App\Jobs\CheckLogDrainContainerJob; | ||||
| use App\Jobs\CleanupInstanceStuffsJob; | ||||
| use App\Jobs\DatabaseBackupJob; | ||||
| use App\Jobs\ScheduledTaskJob; | ||||
| use App\Jobs\ContainerStatusJob; | ||||
| use App\Jobs\DatabaseBackupJob; | ||||
| use App\Jobs\PullCoolifyImageJob; | ||||
| use App\Jobs\PullHelperImageJob; | ||||
| use App\Jobs\PullSentinelImageJob; | ||||
| use App\Jobs\PullTemplatesFromCDN; | ||||
| use App\Jobs\ScheduledTaskJob; | ||||
| use App\Jobs\ServerStatusJob; | ||||
| use App\Models\ScheduledDatabaseBackup; | ||||
| use App\Models\ScheduledTask; | ||||
| @ -22,6 +22,7 @@ use Illuminate\Foundation\Console\Kernel as ConsoleKernel; | ||||
| class Kernel extends ConsoleKernel | ||||
| { | ||||
|     private $all_servers; | ||||
| 
 | ||||
|     protected function schedule(Schedule $schedule): void | ||||
|     { | ||||
|         $this->all_servers = Server::all(); | ||||
| @ -55,6 +56,7 @@ class Kernel extends ConsoleKernel | ||||
|             $schedule->command('uploads:clear')->everyTwoMinutes(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private function pull_images($schedule) | ||||
|     { | ||||
|         $servers = $this->all_servers->where('settings.is_usable', true)->where('settings.is_reachable', true)->where('ip', '!=', '1.2.3.4'); | ||||
| @ -65,6 +67,7 @@ class Kernel extends ConsoleKernel | ||||
|             $schedule->job(new PullHelperImageJob($server))->everyFiveMinutes()->onOneServer(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private function check_resources($schedule) | ||||
|     { | ||||
|         if (isCloud()) { | ||||
| @ -86,6 +89,7 @@ class Kernel extends ConsoleKernel | ||||
|             $schedule->job(new ServerStatusJob($server))->everyMinute()->onOneServer(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private function check_scheduled_backups($schedule) | ||||
|     { | ||||
|         $scheduled_backups = ScheduledDatabaseBackup::all(); | ||||
| @ -93,12 +97,13 @@ class Kernel extends ConsoleKernel | ||||
|             return; | ||||
|         } | ||||
|         foreach ($scheduled_backups as $scheduled_backup) { | ||||
|             if (!$scheduled_backup->enabled) { | ||||
|             if (! $scheduled_backup->enabled) { | ||||
|                 continue; | ||||
|             } | ||||
|             if (is_null(data_get($scheduled_backup, 'database'))) { | ||||
|                 ray('database not found'); | ||||
|                 $scheduled_backup->delete(); | ||||
| 
 | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
| @ -124,9 +129,10 @@ class Kernel extends ConsoleKernel | ||||
|             $service = $scheduled_task->service; | ||||
|             $application = $scheduled_task->application; | ||||
| 
 | ||||
|             if (!$application && !$service) { | ||||
|             if (! $application && ! $service) { | ||||
|                 ray('application/service attached to scheduled task does not exist'); | ||||
|                 $scheduled_task->delete(); | ||||
| 
 | ||||
|                 continue; | ||||
|             } | ||||
|             if ($application) { | ||||
| @ -150,7 +156,7 @@ class Kernel extends ConsoleKernel | ||||
| 
 | ||||
|     protected function commands(): void | ||||
|     { | ||||
|         $this->load(__DIR__ . '/Commands'); | ||||
|         $this->load(__DIR__.'/Commands'); | ||||
| 
 | ||||
|         require base_path('routes/console.php'); | ||||
|     } | ||||
|  | ||||
| @ -18,12 +18,12 @@ class CoolifyTaskArgs extends Data | ||||
|         public ?string $type_uuid = null, | ||||
|         public ?int $process_id = null, | ||||
|         public ?Model $model = null, | ||||
|         public ?string  $status = null , | ||||
|         public ?string $status = null, | ||||
|         public bool $ignore_errors = false, | ||||
|         public $call_event_on_finish = null, | ||||
|         public $call_event_data = null | ||||
|     ) { | ||||
|         if(is_null($status)){ | ||||
|         if (is_null($status)) { | ||||
|             $this->status = ProcessStatus::QUEUED->value; | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -2,9 +2,7 @@ | ||||
| 
 | ||||
| namespace App\Events; | ||||
| 
 | ||||
| use Illuminate\Broadcasting\Channel; | ||||
| use Illuminate\Broadcasting\InteractsWithSockets; | ||||
| use Illuminate\Broadcasting\PresenceChannel; | ||||
| use Illuminate\Broadcasting\PrivateChannel; | ||||
| use Illuminate\Contracts\Broadcasting\ShouldBroadcast; | ||||
| use Illuminate\Foundation\Events\Dispatchable; | ||||
| @ -13,14 +11,16 @@ use Illuminate\Queue\SerializesModels; | ||||
| class ApplicationStatusChanged implements ShouldBroadcast | ||||
| { | ||||
|     use Dispatchable, InteractsWithSockets, SerializesModels; | ||||
| 
 | ||||
|     public $teamId; | ||||
| 
 | ||||
|     public function __construct($teamId = null) | ||||
|     { | ||||
|         if (is_null($teamId)) { | ||||
|             $teamId = auth()->user()->currentTeam()->id ?? null; | ||||
|         } | ||||
|         if (is_null($teamId)) { | ||||
|             throw new \Exception("Team id is null"); | ||||
|             throw new \Exception('Team id is null'); | ||||
|         } | ||||
|         $this->teamId = $teamId; | ||||
|     } | ||||
|  | ||||
| @ -2,9 +2,7 @@ | ||||
| 
 | ||||
| namespace App\Events; | ||||
| 
 | ||||
| use Illuminate\Broadcasting\Channel; | ||||
| use Illuminate\Broadcasting\InteractsWithSockets; | ||||
| use Illuminate\Broadcasting\PresenceChannel; | ||||
| use Illuminate\Broadcasting\PrivateChannel; | ||||
| use Illuminate\Contracts\Broadcasting\ShouldBroadcast; | ||||
| use Illuminate\Foundation\Events\Dispatchable; | ||||
| @ -13,14 +11,16 @@ use Illuminate\Queue\SerializesModels; | ||||
| class BackupCreated implements ShouldBroadcast | ||||
| { | ||||
|     use Dispatchable, InteractsWithSockets, SerializesModels; | ||||
| 
 | ||||
|     public $teamId; | ||||
| 
 | ||||
|     public function __construct($teamId = null) | ||||
|     { | ||||
|         if (is_null($teamId)) { | ||||
|             $teamId = auth()->user()->currentTeam()->id ?? null; | ||||
|         } | ||||
|         if (is_null($teamId)) { | ||||
|             throw new \Exception("Team id is null"); | ||||
|             throw new \Exception('Team id is null'); | ||||
|         } | ||||
|         $this->teamId = $teamId; | ||||
|     } | ||||
|  | ||||
| @ -2,9 +2,7 @@ | ||||
| 
 | ||||
| namespace App\Events; | ||||
| 
 | ||||
| use Illuminate\Broadcasting\Channel; | ||||
| use Illuminate\Broadcasting\InteractsWithSockets; | ||||
| use Illuminate\Broadcasting\PresenceChannel; | ||||
| use Illuminate\Broadcasting\PrivateChannel; | ||||
| use Illuminate\Contracts\Broadcasting\ShouldBroadcast; | ||||
| use Illuminate\Foundation\Events\Dispatchable; | ||||
| @ -13,14 +11,16 @@ use Illuminate\Queue\SerializesModels; | ||||
| class DatabaseStatusChanged implements ShouldBroadcast | ||||
| { | ||||
|     use Dispatchable, InteractsWithSockets, SerializesModels; | ||||
| 
 | ||||
|     public $userId; | ||||
| 
 | ||||
|     public function __construct($userId = null) | ||||
|     { | ||||
|         if (is_null($userId)) { | ||||
|             $userId = auth()->user()->id ?? null; | ||||
|         } | ||||
|         if (is_null($userId)) { | ||||
|             throw new \Exception("User id is null"); | ||||
|             throw new \Exception('User id is null'); | ||||
|         } | ||||
|         $this->userId = $userId; | ||||
|     } | ||||
|  | ||||
| @ -9,6 +9,7 @@ use Illuminate\Queue\SerializesModels; | ||||
| class ProxyStarted | ||||
| { | ||||
|     use Dispatchable, InteractsWithSockets, SerializesModels; | ||||
| 
 | ||||
|     public function __construct(public $data) | ||||
|     { | ||||
| 
 | ||||
|  | ||||
| @ -2,9 +2,7 @@ | ||||
| 
 | ||||
| namespace App\Events; | ||||
| 
 | ||||
| use Illuminate\Broadcasting\Channel; | ||||
| use Illuminate\Broadcasting\InteractsWithSockets; | ||||
| use Illuminate\Broadcasting\PresenceChannel; | ||||
| use Illuminate\Broadcasting\PrivateChannel; | ||||
| use Illuminate\Contracts\Broadcasting\ShouldBroadcast; | ||||
| use Illuminate\Foundation\Events\Dispatchable; | ||||
| @ -13,14 +11,16 @@ use Illuminate\Queue\SerializesModels; | ||||
| class ProxyStatusChanged implements ShouldBroadcast | ||||
| { | ||||
|     use Dispatchable, InteractsWithSockets, SerializesModels; | ||||
| 
 | ||||
|     public $teamId; | ||||
| 
 | ||||
|     public function __construct($teamId = null) | ||||
|     { | ||||
|         if (is_null($teamId)) { | ||||
|             $teamId = auth()->user()->currentTeam()->id ?? null; | ||||
|         } | ||||
|         if (is_null($teamId)) { | ||||
|             throw new \Exception("Team id is null"); | ||||
|             throw new \Exception('Team id is null'); | ||||
|         } | ||||
|         $this->teamId = $teamId; | ||||
|     } | ||||
|  | ||||
| @ -2,9 +2,7 @@ | ||||
| 
 | ||||
| namespace App\Events; | ||||
| 
 | ||||
| use Illuminate\Broadcasting\Channel; | ||||
| use Illuminate\Broadcasting\InteractsWithSockets; | ||||
| use Illuminate\Broadcasting\PresenceChannel; | ||||
| use Illuminate\Broadcasting\PrivateChannel; | ||||
| use Illuminate\Contracts\Broadcasting\ShouldBroadcast; | ||||
| use Illuminate\Foundation\Events\Dispatchable; | ||||
| @ -13,14 +11,16 @@ use Illuminate\Queue\SerializesModels; | ||||
| class ServiceStatusChanged implements ShouldBroadcast | ||||
| { | ||||
|     use Dispatchable, InteractsWithSockets, SerializesModels; | ||||
| 
 | ||||
|     public $userId; | ||||
| 
 | ||||
|     public function __construct($userId = null) | ||||
|     { | ||||
|         if (is_null($userId)) { | ||||
|             $userId = auth()->user()->id ?? null; | ||||
|         } | ||||
|         if (is_null($userId)) { | ||||
|             throw new \Exception("User id is null"); | ||||
|             throw new \Exception('User id is null'); | ||||
|         } | ||||
|         $this->userId = $userId; | ||||
|     } | ||||
|  | ||||
| @ -2,9 +2,7 @@ | ||||
| 
 | ||||
| namespace App\Events; | ||||
| 
 | ||||
| use Illuminate\Broadcasting\Channel; | ||||
| use Illuminate\Broadcasting\InteractsWithSockets; | ||||
| use Illuminate\Broadcasting\PresenceChannel; | ||||
| use Illuminate\Broadcasting\PrivateChannel; | ||||
| use Illuminate\Contracts\Broadcasting\ShouldBroadcast; | ||||
| use Illuminate\Foundation\Events\Dispatchable; | ||||
| @ -13,7 +11,9 @@ use Illuminate\Queue\SerializesModels; | ||||
| class TestEvent implements ShouldBroadcast | ||||
| { | ||||
|     use Dispatchable, InteractsWithSockets, SerializesModels; | ||||
| 
 | ||||
|     public $teamId; | ||||
| 
 | ||||
|     public function __construct() | ||||
|     { | ||||
|         $this->teamId = auth()->user()->currentTeam()->id; | ||||
|  | ||||
| @ -13,7 +13,6 @@ use Throwable; | ||||
| 
 | ||||
| class Handler extends ExceptionHandler | ||||
| { | ||||
| 
 | ||||
|     /** | ||||
|      * A list of exception types with their corresponding custom log levels. | ||||
|      * | ||||
| @ -22,14 +21,16 @@ class Handler extends ExceptionHandler | ||||
|     protected $levels = [ | ||||
|         //
 | ||||
|     ]; | ||||
| 
 | ||||
|     /** | ||||
|      * A list of the exception types that are not reported. | ||||
|      * | ||||
|      * @var array<int, class-string<\Throwable>> | ||||
|      */ | ||||
|     protected $dontReport = [ | ||||
|         ProcessException::class | ||||
|         ProcessException::class, | ||||
|     ]; | ||||
| 
 | ||||
|     /** | ||||
|      * A list of the inputs that are never flashed to the session on validation exceptions. | ||||
|      * | ||||
| @ -40,6 +41,7 @@ class Handler extends ExceptionHandler | ||||
|         'password', | ||||
|         'password_confirmation', | ||||
|     ]; | ||||
| 
 | ||||
|     private InstanceSettings $settings; | ||||
| 
 | ||||
|     protected function unauthenticated($request, AuthenticationException $exception) | ||||
| @ -47,8 +49,10 @@ class Handler extends ExceptionHandler | ||||
|         if ($request->is('api/*') || $request->expectsJson() || $this->shouldReturnJson($request, $exception)) { | ||||
|             return response()->json(['message' => $exception->getMessage()], 401); | ||||
|         } | ||||
| 
 | ||||
|         return redirect()->guest($exception->redirectTo() ?? route('login')); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Register the exception handling callbacks for the application. | ||||
|      */ | ||||
| @ -72,7 +76,7 @@ class Handler extends ExceptionHandler | ||||
|                     $scope->setUser( | ||||
|                         [ | ||||
|                             'email' => $email, | ||||
|                             'instanceAdmin' => $instanceAdmin | ||||
|                             'instanceAdmin' => $instanceAdmin, | ||||
|                         ] | ||||
|                     ); | ||||
|                 } | ||||
|  | ||||
| @ -6,5 +6,4 @@ use Exception; | ||||
| 
 | ||||
| class ProcessException extends Exception | ||||
| { | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -27,18 +27,20 @@ class Deploy extends Controller | ||||
|             return invalid_token(); | ||||
|         } | ||||
|         $servers = Server::whereTeamId($teamId)->get(); | ||||
|         $deployments_per_server = ApplicationDeploymentQueue::whereIn("status", ["in_progress", "queued"])->whereIn("server_id", $servers->pluck("id"))->get([ | ||||
|             "id", | ||||
|             "application_id", | ||||
|             "application_name", | ||||
|             "deployment_url", | ||||
|             "pull_request_id", | ||||
|             "server_name", | ||||
|             "server_id", | ||||
|             "status" | ||||
|         $deployments_per_server = ApplicationDeploymentQueue::whereIn('status', ['in_progress', 'queued'])->whereIn('server_id', $servers->pluck('id'))->get([ | ||||
|             'id', | ||||
|             'application_id', | ||||
|             'application_name', | ||||
|             'deployment_url', | ||||
|             'pull_request_id', | ||||
|             'server_name', | ||||
|             'server_id', | ||||
|             'status', | ||||
|         ])->sortBy('id')->toArray(); | ||||
| 
 | ||||
|         return response()->json($deployments_per_server, 200); | ||||
|     } | ||||
| 
 | ||||
|     public function deploy(Request $request) | ||||
|     { | ||||
|         $teamId = get_team_id_from_token(); | ||||
| @ -54,11 +56,13 @@ class Deploy extends Controller | ||||
|         } | ||||
|         if ($tags) { | ||||
|             return $this->by_tags($tags, $teamId, $force); | ||||
|         } else if ($uuids) { | ||||
|         } elseif ($uuids) { | ||||
|             return $this->by_uuids($uuids, $teamId, $force); | ||||
|         } | ||||
| 
 | ||||
|         return response()->json(['error' => 'You must provide uuid or tag.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 400); | ||||
|     } | ||||
| 
 | ||||
|     private function by_uuids(string $uuid, int $teamId, bool $force = false) | ||||
|     { | ||||
|         $uuids = explode(',', $uuid); | ||||
| @ -82,10 +86,13 @@ class Deploy extends Controller | ||||
|         } | ||||
|         if ($deployments->count() > 0) { | ||||
|             $payload->put('deployments', $deployments->toArray()); | ||||
| 
 | ||||
|             return response()->json($payload->toArray(), 200); | ||||
|         } | ||||
|         return response()->json(['error' => "No resources found.", 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 404); | ||||
| 
 | ||||
|         return response()->json(['error' => 'No resources found.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 404); | ||||
|     } | ||||
| 
 | ||||
|     public function by_tags(string $tags, int $team_id, bool $force = false) | ||||
|     { | ||||
|         $tags = explode(',', $tags); | ||||
| @ -99,7 +106,7 @@ class Deploy extends Controller | ||||
|         $payload = collect(); | ||||
|         foreach ($tags as $tag) { | ||||
|             $found_tag = Tag::where(['name' => $tag, 'team_id' => $team_id])->first(); | ||||
|             if (!$found_tag) { | ||||
|             if (! $found_tag) { | ||||
|                 // $message->push("Tag {$tag} not found.");
 | ||||
|                 continue; | ||||
|             } | ||||
| @ -107,6 +114,7 @@ class Deploy extends Controller | ||||
|             $services = $found_tag->services()->get(); | ||||
|             if ($applications->count() === 0 && $services->count() === 0) { | ||||
|                 $message->push("No resources found for tag {$tag}."); | ||||
| 
 | ||||
|                 continue; | ||||
|             } | ||||
|             foreach ($applications as $resource) { | ||||
| @ -127,11 +135,13 @@ class Deploy extends Controller | ||||
|             if ($deployments->count() > 0) { | ||||
|                 $payload->put('details', $deployments->toArray()); | ||||
|             } | ||||
| 
 | ||||
|             return response()->json($payload->toArray(), 200); | ||||
|         } | ||||
| 
 | ||||
|         return response()->json(['error' => "No resources found with this tag.", 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 404); | ||||
|         return response()->json(['error' => 'No resources found with this tag.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 404); | ||||
|     } | ||||
| 
 | ||||
|     public function deploy_resource($resource, bool $force = false): array | ||||
|     { | ||||
|         $message = null; | ||||
| @ -148,58 +158,59 @@ class Deploy extends Controller | ||||
|                 force_rebuild: $force, | ||||
|             ); | ||||
|             $message = "Application {$resource->name} deployment queued."; | ||||
|         } else if ($type === 'App\Models\StandalonePostgresql') { | ||||
|         } elseif ($type === 'App\Models\StandalonePostgresql') { | ||||
|             StartPostgresql::run($resource); | ||||
|             $resource->update([ | ||||
|                 'started_at' => now(), | ||||
|             ]); | ||||
|             $message = "Database {$resource->name} started."; | ||||
|         } else if ($type === 'App\Models\StandaloneRedis') { | ||||
|         } elseif ($type === 'App\Models\StandaloneRedis') { | ||||
|             StartRedis::run($resource); | ||||
|             $resource->update([ | ||||
|                 'started_at' => now(), | ||||
|             ]); | ||||
|             $message = "Database {$resource->name} started."; | ||||
|         } else if ($type === 'App\Models\StandaloneKeydb') { | ||||
|         } elseif ($type === 'App\Models\StandaloneKeydb') { | ||||
|             StartKeydb::run($resource); | ||||
|             $resource->update([ | ||||
|                 'started_at' => now(), | ||||
|             ]); | ||||
|             $message = "Database {$resource->name} started."; | ||||
|         } else if ($type === 'App\Models\StandaloneDragonfly') { | ||||
|         } elseif ($type === 'App\Models\StandaloneDragonfly') { | ||||
|             StartDragonfly::run($resource); | ||||
|             $resource->update([ | ||||
|                 'started_at' => now(), | ||||
|             ]); | ||||
|             $message = "Database {$resource->name} started."; | ||||
|         } else if ($type === 'App\Models\StandaloneClickhouse') { | ||||
|         } elseif ($type === 'App\Models\StandaloneClickhouse') { | ||||
|             StartClickhouse::run($resource); | ||||
|             $resource->update([ | ||||
|                 'started_at' => now(), | ||||
|             ]); | ||||
|             $message = "Database {$resource->name} started."; | ||||
|         } else if ($type === 'App\Models\StandaloneMongodb') { | ||||
|         } elseif ($type === 'App\Models\StandaloneMongodb') { | ||||
|             StartMongodb::run($resource); | ||||
|             $resource->update([ | ||||
|                 'started_at' => now(), | ||||
|             ]); | ||||
|             $message = "Database {$resource->name} started."; | ||||
|         } else if ($type === 'App\Models\StandaloneMysql') { | ||||
|         } elseif ($type === 'App\Models\StandaloneMysql') { | ||||
|             StartMysql::run($resource); | ||||
|             $resource->update([ | ||||
|                 'started_at' => now(), | ||||
|             ]); | ||||
|             $message = "Database {$resource->name} started."; | ||||
|         } else if ($type === 'App\Models\StandaloneMariadb') { | ||||
|         } elseif ($type === 'App\Models\StandaloneMariadb') { | ||||
|             StartMariadb::run($resource); | ||||
|             $resource->update([ | ||||
|                 'started_at' => now(), | ||||
|             ]); | ||||
|             $message = "Database {$resource->name} started."; | ||||
|         } else if ($type === 'App\Models\Service') { | ||||
|         } elseif ($type === 'App\Models\Service') { | ||||
|             StartService::run($resource); | ||||
|             $message = "Service {$resource->name} started. It could take a while, be patient."; | ||||
|         } | ||||
| 
 | ||||
|         return ['message' => $message, 'deployment_uuid' => $deployment_uuid]; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -38,7 +38,7 @@ class Domains extends Controller | ||||
|                             'ip' => $settings->public_ipv6, | ||||
|                         ]); | ||||
|                     } | ||||
|                     if (!$settings->public_ipv4 && !$settings->public_ipv6) { | ||||
|                     if (! $settings->public_ipv4 && ! $settings->public_ipv6) { | ||||
|                         $domains->push([ | ||||
|                             'domain' => $fqdn, | ||||
|                             'ip' => $ip, | ||||
| @ -74,7 +74,7 @@ class Domains extends Controller | ||||
|                                     'ip' => $settings->public_ipv6, | ||||
|                                 ]); | ||||
|                             } | ||||
|                             if (!$settings->public_ipv4 && !$settings->public_ipv6) { | ||||
|                             if (! $settings->public_ipv4 && ! $settings->public_ipv6) { | ||||
|                                 $domains->push([ | ||||
|                                     'domain' => $fqdn, | ||||
|                                     'ip' => $ip, | ||||
|  | ||||
| @ -15,8 +15,10 @@ class Project extends Controller | ||||
|             return invalid_token(); | ||||
|         } | ||||
|         $projects = ModelsProject::whereTeamId($teamId)->select('id', 'name', 'uuid')->get(); | ||||
| 
 | ||||
|         return response()->json($projects); | ||||
|     } | ||||
| 
 | ||||
|     public function project_by_uuid(Request $request) | ||||
|     { | ||||
|         $teamId = get_team_id_from_token(); | ||||
| @ -24,8 +26,10 @@ class Project extends Controller | ||||
|             return invalid_token(); | ||||
|         } | ||||
|         $project = ModelsProject::whereTeamId($teamId)->whereUuid(request()->uuid)->first()->load(['environments']); | ||||
| 
 | ||||
|         return response()->json($project); | ||||
|     } | ||||
| 
 | ||||
|     public function environment_details(Request $request) | ||||
|     { | ||||
|         $teamId = get_team_id_from_token(); | ||||
| @ -34,6 +38,7 @@ class Project extends Controller | ||||
|         } | ||||
|         $project = ModelsProject::whereTeamId($teamId)->whereUuid(request()->uuid)->first(); | ||||
|         $environment = $project->environments()->whereName(request()->environment_name)->first()->load(['applications', 'postgresqls', 'redis', 'mongodbs', 'mysqls', 'mariadbs', 'services']); | ||||
| 
 | ||||
|         return response()->json($environment); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -30,9 +30,10 @@ class Resources extends Controller | ||||
|                 $payload['status'] = $resource->status; | ||||
|             } | ||||
|             $payload['type'] = $resource->type(); | ||||
| 
 | ||||
|             return $payload; | ||||
|         }); | ||||
| 
 | ||||
|         return response()->json($resources); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -17,10 +17,13 @@ class Server extends Controller | ||||
|         $servers = ModelsServer::whereTeamId($teamId)->select('id', 'name', 'uuid', 'ip', 'user', 'port')->get()->load(['settings'])->map(function ($server) { | ||||
|             $server['is_reachable'] = $server->settings->is_reachable; | ||||
|             $server['is_usable'] = $server->settings->is_usable; | ||||
| 
 | ||||
|             return $server; | ||||
|         }); | ||||
| 
 | ||||
|         return response()->json($servers); | ||||
|     } | ||||
| 
 | ||||
|     public function server_by_uuid(Request $request) | ||||
|     { | ||||
|         $with_resources = $request->query('resources'); | ||||
| @ -47,11 +50,13 @@ class Server extends Controller | ||||
|                 } else { | ||||
|                     $payload['status'] = $resource->status; | ||||
|                 } | ||||
| 
 | ||||
|                 return $payload; | ||||
|             }); | ||||
|         } else { | ||||
|             $server->load(['settings']); | ||||
|         } | ||||
| 
 | ||||
|         return response()->json($server); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -14,8 +14,10 @@ class Team extends Controller | ||||
|             return invalid_token(); | ||||
|         } | ||||
|         $teams = auth()->user()->teams; | ||||
| 
 | ||||
|         return response()->json($teams); | ||||
|     } | ||||
| 
 | ||||
|     public function team_by_id(Request $request) | ||||
|     { | ||||
|         $id = $request->id; | ||||
| @ -26,10 +28,12 @@ class Team extends Controller | ||||
|         $teams = auth()->user()->teams; | ||||
|         $team = $teams->where('id', $id)->first(); | ||||
|         if (is_null($team)) { | ||||
|             return response()->json(['error' => 'Team not found.',  "docs" => "https://coolify.io/docs/api-reference/get-team-by-teamid"], 404); | ||||
|             return response()->json(['error' => 'Team not found.',  'docs' => 'https://coolify.io/docs/api-reference/get-team-by-teamid'], 404); | ||||
|         } | ||||
| 
 | ||||
|         return response()->json($team); | ||||
|     } | ||||
| 
 | ||||
|     public function members_by_id(Request $request) | ||||
|     { | ||||
|         $id = $request->id; | ||||
| @ -40,10 +44,12 @@ class Team extends Controller | ||||
|         $teams = auth()->user()->teams; | ||||
|         $team = $teams->where('id', $id)->first(); | ||||
|         if (is_null($team)) { | ||||
|             return response()->json(['error' => 'Team not found.', "docs" => "https://coolify.io/docs/api-reference/get-team-by-teamid-members"], 404); | ||||
|             return response()->json(['error' => 'Team not found.', 'docs' => 'https://coolify.io/docs/api-reference/get-team-by-teamid-members'], 404); | ||||
|         } | ||||
| 
 | ||||
|         return response()->json($team->members); | ||||
|     } | ||||
| 
 | ||||
|     public function current_team(Request $request) | ||||
|     { | ||||
|         $teamId = get_team_id_from_token(); | ||||
| @ -51,8 +57,10 @@ class Team extends Controller | ||||
|             return invalid_token(); | ||||
|         } | ||||
|         $team = auth()->user()->currentTeam(); | ||||
| 
 | ||||
|         return response()->json($team); | ||||
|     } | ||||
| 
 | ||||
|     public function current_team_members(Request $request) | ||||
|     { | ||||
|         $teamId = get_team_id_from_token(); | ||||
| @ -60,6 +68,7 @@ class Team extends Controller | ||||
|             return invalid_token(); | ||||
|         } | ||||
|         $team = auth()->user()->currentTeam(); | ||||
| 
 | ||||
|         return response()->json($team->members); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -14,40 +14,49 @@ use Illuminate\Routing\Controller as BaseController; | ||||
| use Illuminate\Support\Facades\Auth; | ||||
| use Illuminate\Support\Facades\Crypt; | ||||
| use Illuminate\Support\Facades\Hash; | ||||
| use Illuminate\Support\Facades\Password; | ||||
| use Illuminate\Support\Str; | ||||
| use Laravel\Fortify\Fortify; | ||||
| use Laravel\Fortify\Contracts\FailedPasswordResetLinkRequestResponse; | ||||
| use Laravel\Fortify\Contracts\SuccessfulPasswordResetLinkRequestResponse; | ||||
| use Illuminate\Support\Facades\Password; | ||||
| use Laravel\Fortify\Fortify; | ||||
| 
 | ||||
| class Controller extends BaseController | ||||
| { | ||||
|     use AuthorizesRequests, ValidatesRequests; | ||||
| 
 | ||||
|     public function realtime_test() { | ||||
|     public function realtime_test() | ||||
|     { | ||||
|         if (auth()->user()?->currentTeam()->id !== 0) { | ||||
|             return redirect(RouteServiceProvider::HOME); | ||||
|         } | ||||
|         TestEvent::dispatch(); | ||||
| 
 | ||||
|         return 'Look at your other tab.'; | ||||
|     } | ||||
|     public function verify() { | ||||
| 
 | ||||
|     public function verify() | ||||
|     { | ||||
|         return view('auth.verify-email'); | ||||
|     } | ||||
|     public function email_verify(EmailVerificationRequest $request) { | ||||
| 
 | ||||
|     public function email_verify(EmailVerificationRequest $request) | ||||
|     { | ||||
|         $request->fulfill(); | ||||
|         $name = request()->user()?->name; | ||||
| 
 | ||||
|         // send_internal_notification("User {$name} verified their email address.");
 | ||||
|         return redirect(RouteServiceProvider::HOME); | ||||
|     } | ||||
|     public function forgot_password(Request $request) { | ||||
| 
 | ||||
|     public function forgot_password(Request $request) | ||||
|     { | ||||
|         if (is_transactional_emails_active()) { | ||||
|             $arrayOfRequest = $request->only(Fortify::email()); | ||||
|             $request->merge([ | ||||
|                 'email' => Str::lower($arrayOfRequest['email']), | ||||
|             ]); | ||||
|             $type = set_transanctional_email_settings(); | ||||
|             if (!$type) { | ||||
|             if (! $type) { | ||||
|                 return response()->json(['message' => 'Transactional emails are not active'], 400); | ||||
|             } | ||||
|             $request->validate([Fortify::email() => 'required|email']); | ||||
| @ -60,10 +69,13 @@ class Controller extends BaseController | ||||
|             if ($status == Password::RESET_THROTTLED) { | ||||
|                 return response('Already requested a password reset in the past minutes.', 400); | ||||
|             } | ||||
| 
 | ||||
|             return app(FailedPasswordResetLinkRequestResponse::class, ['status' => $status]); | ||||
|         } | ||||
| 
 | ||||
|         return response()->json(['message' => 'Transactional emails are not active'], 400); | ||||
|     } | ||||
| 
 | ||||
|     public function link() | ||||
|     { | ||||
|         $token = request()->get('token'); | ||||
| @ -72,7 +84,7 @@ class Controller extends BaseController | ||||
|             $email = Str::of($decrypted)->before('@@@'); | ||||
|             $password = Str::of($decrypted)->after('@@@'); | ||||
|             $user = User::whereEmail($email)->first(); | ||||
|             if (!$user) { | ||||
|             if (! $user) { | ||||
|                 return redirect()->route('login'); | ||||
|             } | ||||
|             if (Hash::check($password, $user->password)) { | ||||
| @ -90,9 +102,11 @@ class Controller extends BaseController | ||||
|                 } | ||||
|                 Auth::login($user); | ||||
|                 session(['currentTeam' => $team]); | ||||
| 
 | ||||
|                 return redirect()->route('dashboard'); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return redirect()->route('login')->with('error', 'Invalid credentials.'); | ||||
|     } | ||||
| 
 | ||||
| @ -108,11 +122,12 @@ class Controller extends BaseController | ||||
|                 if ($resetPassword) { | ||||
|                     $user->update([ | ||||
|                         'password' => Hash::make($invitationUuid), | ||||
|                         'force_password_reset' => true | ||||
|                         'force_password_reset' => true, | ||||
|                     ]); | ||||
|                 } | ||||
|                 if ($user->teams()->where('team_id', $invitation->team->id)->exists()) { | ||||
|                     $invitation->delete(); | ||||
| 
 | ||||
|                     return redirect()->route('team.index'); | ||||
|                 } | ||||
|                 $user->teams()->attach($invitation->team->id, ['role' => $invitation->role]); | ||||
| @ -121,6 +136,7 @@ class Controller extends BaseController | ||||
|                     return redirect()->route('login'); | ||||
|                 } | ||||
|                 refreshSession($invitation->team); | ||||
| 
 | ||||
|                 return redirect()->route('team.index'); | ||||
|             } else { | ||||
|                 abort(401); | ||||
| @ -143,6 +159,7 @@ class Controller extends BaseController | ||||
|                 abort(401); | ||||
|             } | ||||
|             $invitation->delete(); | ||||
| 
 | ||||
|             return redirect()->route('team.index'); | ||||
|         } catch (\Throwable $e) { | ||||
|             throw $e; | ||||
|  | ||||
| @ -12,34 +12,35 @@ class MagicController extends Controller | ||||
|     public function servers() | ||||
|     { | ||||
|         return response()->json([ | ||||
|             'servers' => Server::isUsable()->get() | ||||
|             'servers' => Server::isUsable()->get(), | ||||
|         ]); | ||||
|     } | ||||
| 
 | ||||
|     public function destinations() | ||||
|     { | ||||
|         return response()->json([ | ||||
|             'destinations' => Server::destinationsByServer(request()->query('server_id'))->sortBy('name') | ||||
|             'destinations' => Server::destinationsByServer(request()->query('server_id'))->sortBy('name'), | ||||
|         ]); | ||||
|     } | ||||
| 
 | ||||
|     public function projects() | ||||
|     { | ||||
|         return response()->json([ | ||||
|             'projects' => Project::ownedByCurrentTeam()->get() | ||||
|             'projects' => Project::ownedByCurrentTeam()->get(), | ||||
|         ]); | ||||
|     } | ||||
| 
 | ||||
|     public function environments() | ||||
|     { | ||||
|         $project = Project::ownedByCurrentTeam()->whereUuid(request()->query('project_uuid'))->first(); | ||||
|         if (!$project) { | ||||
|         if (! $project) { | ||||
|             return response()->json([ | ||||
|                 'environments' => [] | ||||
|                 'environments' => [], | ||||
|             ]); | ||||
|         } | ||||
| 
 | ||||
|         return response()->json([ | ||||
|             'environments' => $project->environments | ||||
|             'environments' => $project->environments, | ||||
|         ]); | ||||
|     } | ||||
| 
 | ||||
| @ -49,8 +50,9 @@ class MagicController extends Controller | ||||
|             ['name' => request()->query('name') ?? generate_random_name()], | ||||
|             ['team_id' => currentTeam()->id] | ||||
|         ); | ||||
| 
 | ||||
|         return response()->json([ | ||||
|             'project_uuid' => $project->uuid | ||||
|             'project_uuid' => $project->uuid, | ||||
|         ]); | ||||
|     } | ||||
| 
 | ||||
| @ -60,6 +62,7 @@ class MagicController extends Controller | ||||
|             ['name' => request()->query('name') ?? generate_random_name()], | ||||
|             ['project_id' => Project::ownedByCurrentTeam()->whereUuid(request()->query('project_uuid'))->firstOrFail()->id] | ||||
|         ); | ||||
| 
 | ||||
|         return response()->json([ | ||||
|             'environment_name' => $environment->name, | ||||
|         ]); | ||||
| @ -75,6 +78,7 @@ class MagicController extends Controller | ||||
|         ); | ||||
|         auth()->user()->teams()->attach($team, ['role' => 'admin']); | ||||
|         refreshSession(); | ||||
| 
 | ||||
|         return redirect(request()->header('Referer')); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -2,15 +2,15 @@ | ||||
| 
 | ||||
| namespace App\Http\Controllers; | ||||
| 
 | ||||
| use App\Http\Controllers\Controller; | ||||
| use App\Models\User; | ||||
| 
 | ||||
| use Illuminate\Support\Facades\Auth; | ||||
| 
 | ||||
| class OauthController extends Controller { | ||||
| class OauthController extends Controller | ||||
| { | ||||
|     public function redirect(string $provider) | ||||
|     { | ||||
|         $socialite_provider = get_socialite_provider($provider); | ||||
| 
 | ||||
|         return $socialite_provider->redirect(); | ||||
|     } | ||||
| 
 | ||||
| @ -19,16 +19,18 @@ class OauthController extends Controller { | ||||
|         try { | ||||
|             $oauthUser = get_socialite_provider($provider)->user(); | ||||
|             $user = User::whereEmail($oauthUser->email)->first(); | ||||
|             if (!$user) { | ||||
|             if (! $user) { | ||||
|                 $user = User::create([ | ||||
|                     'name' => $oauthUser->name, | ||||
|                     'email' => $oauthUser->email, | ||||
|                 ]); | ||||
|             } | ||||
|             Auth::login($user); | ||||
| 
 | ||||
|             return redirect('/'); | ||||
|         } catch (\Exception $e) { | ||||
|             ray($e->getMessage()); | ||||
| 
 | ||||
|             return redirect()->route('login')->withErrors([__('auth.failed.callback')]); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -2,14 +2,11 @@ | ||||
| 
 | ||||
| namespace App\Http\Controllers; | ||||
| 
 | ||||
| use Illuminate\Routing\Controller as BaseController; | ||||
| use Illuminate\Http\JsonResponse; | ||||
| use Pion\Laravel\ChunkUpload\Exceptions\UploadFailedException; | ||||
| use Illuminate\Http\Request; | ||||
| use Illuminate\Http\UploadedFile; | ||||
| use Illuminate\Routing\Controller as BaseController; | ||||
| use Illuminate\Support\Facades\Storage; | ||||
| use Pion\Laravel\ChunkUpload\Exceptions\UploadMissingFileException; | ||||
| use Pion\Laravel\ChunkUpload\Handler\AbstractHandler; | ||||
| use Pion\Laravel\ChunkUpload\Handler\HandlerFactory; | ||||
| use Pion\Laravel\ChunkUpload\Receiver\FileReceiver; | ||||
| 
 | ||||
| @ -21,7 +18,7 @@ class UploadController extends BaseController | ||||
|         if (is_null($resource)) { | ||||
|             return response()->json(['error' => 'You do not have permission for this database'], 500); | ||||
|         } | ||||
|         $receiver = new FileReceiver("file", $request, HandlerFactory::classFromRequest($request)); | ||||
|         $receiver = new FileReceiver('file', $request, HandlerFactory::classFromRequest($request)); | ||||
| 
 | ||||
|         if ($receiver->isUploaded() === false) { | ||||
|             throw new UploadMissingFileException(); | ||||
| @ -34,9 +31,10 @@ class UploadController extends BaseController | ||||
|         } | ||||
| 
 | ||||
|         $handler = $save->handler(); | ||||
| 
 | ||||
|         return response()->json([ | ||||
|             "done" => $handler->getPercentageDone(), | ||||
|             'status' => true | ||||
|             'done' => $handler->getPercentageDone(), | ||||
|             'status' => true, | ||||
|         ]); | ||||
|     } | ||||
|     // protected function saveFileToS3($file)
 | ||||
| @ -64,19 +62,20 @@ class UploadController extends BaseController | ||||
|     { | ||||
|         $mime = str_replace('/', '-', $file->getMimeType()); | ||||
|         $filePath = "upload/{$resource->uuid}"; | ||||
|         $finalPath = storage_path("app/" . $filePath); | ||||
|         $finalPath = storage_path('app/'.$filePath); | ||||
|         $file->move($finalPath, 'restore'); | ||||
| 
 | ||||
|         return response()->json([ | ||||
|             'mime_type' => $mime | ||||
|             'mime_type' => $mime, | ||||
|         ]); | ||||
|     } | ||||
| 
 | ||||
|     protected function createFilename(UploadedFile $file) | ||||
|     { | ||||
|         $extension = $file->getClientOriginalExtension(); | ||||
|         $filename = str_replace("." . $extension, "", $file->getClientOriginalName()); // Filename without extension
 | ||||
|         $filename = str_replace('.'.$extension, '', $file->getClientOriginalName()); // Filename without extension
 | ||||
| 
 | ||||
|         $filename .= "_" . md5(time()) . "." . $extension; | ||||
|         $filename .= '_'.md5(time()).'.'.$extension; | ||||
| 
 | ||||
|         return $filename; | ||||
|     } | ||||
|  | ||||
| @ -30,15 +30,16 @@ class Bitbucket extends Controller | ||||
|                 ]; | ||||
|                 $json = json_encode($data); | ||||
|                 Storage::disk('webhooks-during-maintenance')->put("{$epoch}_Bitbicket::manual_bitbucket", $json); | ||||
| 
 | ||||
|                 return; | ||||
|             } | ||||
|             $return_payloads = collect([]); | ||||
|             $payload = $request->collect(); | ||||
|             $headers = $request->headers->all(); | ||||
|             $x_bitbucket_token = data_get($headers, 'x-hub-signature.0', ""); | ||||
|             $x_bitbucket_event = data_get($headers, 'x-event-key.0', ""); | ||||
|             $x_bitbucket_token = data_get($headers, 'x-hub-signature.0', ''); | ||||
|             $x_bitbucket_event = data_get($headers, 'x-event-key.0', ''); | ||||
|             $handled_events = collect(['repo:push', 'pullrequest:created', 'pullrequest:rejected', 'pullrequest:fulfilled']); | ||||
|             if (!$handled_events->contains($x_bitbucket_event)) { | ||||
|             if (! $handled_events->contains($x_bitbucket_event)) { | ||||
|                 return response([ | ||||
|                     'status' => 'failed', | ||||
|                     'message' => 'Nothing to do. Event not handled.', | ||||
| @ -48,13 +49,13 @@ class Bitbucket extends Controller | ||||
|                 $branch = data_get($payload, 'push.changes.0.new.name'); | ||||
|                 $full_name = data_get($payload, 'repository.full_name'); | ||||
|                 $commit = data_get($payload, 'push.changes.0.new.target.hash'); | ||||
|                 if (!$branch) { | ||||
|                 if (! $branch) { | ||||
|                     return response([ | ||||
|                         'status' => 'failed', | ||||
|                         'message' => 'Nothing to do. No branch found in the request.', | ||||
|                     ]); | ||||
|                 } | ||||
|                 ray('Manual webhook bitbucket push event with branch: ' . $branch); | ||||
|                 ray('Manual webhook bitbucket push event with branch: '.$branch); | ||||
|             } | ||||
|             if ($x_bitbucket_event === 'pullrequest:created' || $x_bitbucket_event === 'pullrequest:rejected' || $x_bitbucket_event === 'pullrequest:fulfilled') { | ||||
|                 $branch = data_get($payload, 'pullrequest.destination.branch.name'); | ||||
| @ -76,30 +77,32 @@ class Bitbucket extends Controller | ||||
|                 $webhook_secret = data_get($application, 'manual_webhook_secret_bitbucket'); | ||||
|                 $payload = $request->getContent(); | ||||
| 
 | ||||
|                 list($algo, $hash) = explode('=', $x_bitbucket_token, 2); | ||||
|                 [$algo, $hash] = explode('=', $x_bitbucket_token, 2); | ||||
|                 $payloadHash = hash_hmac($algo, $payload, $webhook_secret); | ||||
|                 if (!hash_equals($hash, $payloadHash) && !isDev()) { | ||||
|                 if (! hash_equals($hash, $payloadHash) && ! isDev()) { | ||||
|                     $return_payloads->push([ | ||||
|                         'application' => $application->name, | ||||
|                         'status' => 'failed', | ||||
|                         'message' => 'Invalid signature.', | ||||
|                     ]); | ||||
|                     ray('Invalid signature'); | ||||
| 
 | ||||
|                     continue; | ||||
|                 } | ||||
|                 $isFunctional = $application->destination->server->isFunctional(); | ||||
|                 if (!$isFunctional) { | ||||
|                 if (! $isFunctional) { | ||||
|                     $return_payloads->push([ | ||||
|                         'application' => $application->name, | ||||
|                         'status' => 'failed', | ||||
|                         'message' => 'Server is not functional.', | ||||
|                     ]); | ||||
|                     ray('Server is not functional: ' . $application->destination->server->name); | ||||
|                     ray('Server is not functional: '.$application->destination->server->name); | ||||
| 
 | ||||
|                     continue; | ||||
|                 } | ||||
|                 if ($x_bitbucket_event === 'repo:push') { | ||||
|                     if ($application->isDeployable()) { | ||||
|                         ray('Deploying ' . $application->name . ' with branch ' . $branch); | ||||
|                         ray('Deploying '.$application->name.' with branch '.$branch); | ||||
|                         $deployment_uuid = new Cuid2(7); | ||||
|                         queue_application_deployment( | ||||
|                             application: $application, | ||||
| @ -123,10 +126,10 @@ class Bitbucket extends Controller | ||||
|                 } | ||||
|                 if ($x_bitbucket_event === 'pullrequest:created') { | ||||
|                     if ($application->isPRDeployable()) { | ||||
|                         ray('Deploying preview for ' . $application->name . ' with branch ' . $branch . ' and base branch ' . $base_branch . ' and pull request id ' . $pull_request_id); | ||||
|                         ray('Deploying preview for '.$application->name.' with branch '.$branch.' and base branch '.$base_branch.' and pull request id '.$pull_request_id); | ||||
|                         $deployment_uuid = new Cuid2(7); | ||||
|                         $found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first(); | ||||
|                         if (!$found) { | ||||
|                         if (! $found) { | ||||
|                             ApplicationPreview::create([ | ||||
|                                 'git_type' => 'bitbucket', | ||||
|                                 'application_id' => $application->id, | ||||
| @ -178,9 +181,11 @@ class Bitbucket extends Controller | ||||
|                 } | ||||
|             } | ||||
|             ray($return_payloads); | ||||
| 
 | ||||
|             return response($return_payloads); | ||||
|         } catch (Exception $e) { | ||||
|             ray($e); | ||||
| 
 | ||||
|             return handleError($e); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -27,6 +27,7 @@ class Gitea extends Controller | ||||
|                 })->first(); | ||||
|                 if ($gitea_delivery_found) { | ||||
|                     ray('Webhook already found'); | ||||
| 
 | ||||
|                     return; | ||||
|                 } | ||||
|                 $data = [ | ||||
| @ -41,6 +42,7 @@ class Gitea extends Controller | ||||
|                 ]; | ||||
|                 $json = json_encode($data); | ||||
|                 Storage::disk('webhooks-during-maintenance')->put("{$epoch}_Gitea::manual_{$x_gitea_delivery}", $json); | ||||
| 
 | ||||
|                 return; | ||||
|             } | ||||
|             $x_gitea_event = Str::lower($request->header('X-Gitea-Event')); | ||||
| @ -66,7 +68,7 @@ class Gitea extends Controller | ||||
|                 $modified_files = data_get($payload, 'commits.*.modified'); | ||||
|                 $changed_files = collect($added_files)->concat($removed_files)->concat($modified_files)->unique()->flatten(); | ||||
|                 ray($changed_files); | ||||
|                 ray('Manual Webhook Gitea Push Event with branch: ' . $branch); | ||||
|                 ray('Manual Webhook Gitea Push Event with branch: '.$branch); | ||||
|             } | ||||
|             if ($x_gitea_event === 'pull_request') { | ||||
|                 $action = data_get($payload, 'action'); | ||||
| @ -75,9 +77,9 @@ class Gitea extends Controller | ||||
|                 $pull_request_html_url = data_get($payload, 'pull_request.html_url'); | ||||
|                 $branch = data_get($payload, 'pull_request.head.ref'); | ||||
|                 $base_branch = data_get($payload, 'pull_request.base.ref'); | ||||
|                 ray('Webhook Gitea Pull Request Event with branch: ' . $branch . ' and base branch: ' . $base_branch . ' and pull request id: ' . $pull_request_id); | ||||
|                 ray('Webhook Gitea Pull Request Event with branch: '.$branch.' and base branch: '.$base_branch.' and pull request id: '.$pull_request_id); | ||||
|             } | ||||
|             if (!$branch) { | ||||
|             if (! $branch) { | ||||
|                 return response('Nothing to do. No branch found in the request.'); | ||||
|             } | ||||
|             $applications = Application::where('git_repository', 'like', "%$full_name%"); | ||||
| @ -96,29 +98,31 @@ class Gitea extends Controller | ||||
|             foreach ($applications as $application) { | ||||
|                 $webhook_secret = data_get($application, 'manual_webhook_secret_gitea'); | ||||
|                 $hmac = hash_hmac('sha256', $request->getContent(), $webhook_secret); | ||||
|                 if (!hash_equals($x_hub_signature_256, $hmac) && !isDev()) { | ||||
|                 if (! hash_equals($x_hub_signature_256, $hmac) && ! isDev()) { | ||||
|                     ray('Invalid signature'); | ||||
|                     $return_payloads->push([ | ||||
|                         'application' => $application->name, | ||||
|                         'status' => 'failed', | ||||
|                         'message' => 'Invalid signature.', | ||||
|                     ]); | ||||
| 
 | ||||
|                     continue; | ||||
|                 } | ||||
|                 $isFunctional = $application->destination->server->isFunctional(); | ||||
|                 if (!$isFunctional) { | ||||
|                 if (! $isFunctional) { | ||||
|                     $return_payloads->push([ | ||||
|                         'application' => $application->name, | ||||
|                         'status' => 'failed', | ||||
|                         'message' => 'Server is not functional.', | ||||
|                     ]); | ||||
| 
 | ||||
|                     continue; | ||||
|                 } | ||||
|                 if ($x_gitea_event === 'push') { | ||||
|                     if ($application->isDeployable()) { | ||||
|                         $is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files); | ||||
|                         if ($is_watch_path_triggered || is_null($application->watch_paths)) { | ||||
|                             ray('Deploying ' . $application->name . ' with branch ' . $branch); | ||||
|                             ray('Deploying '.$application->name.' with branch '.$branch); | ||||
|                             $deployment_uuid = new Cuid2(7); | ||||
|                             queue_application_deployment( | ||||
|                                 application: $application, | ||||
| @ -160,7 +164,7 @@ class Gitea extends Controller | ||||
|                         if ($application->isPRDeployable()) { | ||||
|                             $deployment_uuid = new Cuid2(7); | ||||
|                             $found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first(); | ||||
|                             if (!$found) { | ||||
|                             if (! $found) { | ||||
|                                 ApplicationPreview::create([ | ||||
|                                     'git_type' => 'gitea', | ||||
|                                     'application_id' => $application->id, | ||||
| @ -213,9 +217,11 @@ class Gitea extends Controller | ||||
|                 } | ||||
|             } | ||||
|             ray($return_payloads); | ||||
| 
 | ||||
|             return response($return_payloads); | ||||
|         } catch (Exception $e) { | ||||
|             ray($e->getMessage()); | ||||
| 
 | ||||
|             return handleError($e); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -33,6 +33,7 @@ class Github extends Controller | ||||
|                 })->first(); | ||||
|                 if ($github_delivery_found) { | ||||
|                     ray('Webhook already found'); | ||||
| 
 | ||||
|                     return; | ||||
|                 } | ||||
|                 $data = [ | ||||
| @ -47,6 +48,7 @@ class Github extends Controller | ||||
|                 ]; | ||||
|                 $json = json_encode($data); | ||||
|                 Storage::disk('webhooks-during-maintenance')->put("{$epoch}_Github::manual_{$x_github_delivery}", $json); | ||||
| 
 | ||||
|                 return; | ||||
|             } | ||||
|             $x_github_event = Str::lower($request->header('X-GitHub-Event')); | ||||
| @ -71,7 +73,7 @@ class Github extends Controller | ||||
|                 $removed_files = data_get($payload, 'commits.*.removed'); | ||||
|                 $modified_files = data_get($payload, 'commits.*.modified'); | ||||
|                 $changed_files = collect($added_files)->concat($removed_files)->concat($modified_files)->unique()->flatten(); | ||||
|                 ray('Manual Webhook GitHub Push Event with branch: ' . $branch); | ||||
|                 ray('Manual Webhook GitHub Push Event with branch: '.$branch); | ||||
|             } | ||||
|             if ($x_github_event === 'pull_request') { | ||||
|                 $action = data_get($payload, 'action'); | ||||
| @ -80,9 +82,9 @@ class Github extends Controller | ||||
|                 $pull_request_html_url = data_get($payload, 'pull_request.html_url'); | ||||
|                 $branch = data_get($payload, 'pull_request.head.ref'); | ||||
|                 $base_branch = data_get($payload, 'pull_request.base.ref'); | ||||
|                 ray('Webhook GitHub Pull Request Event with branch: ' . $branch . ' and base branch: ' . $base_branch . ' and pull request id: ' . $pull_request_id); | ||||
|                 ray('Webhook GitHub Pull Request Event with branch: '.$branch.' and base branch: '.$base_branch.' and pull request id: '.$pull_request_id); | ||||
|             } | ||||
|             if (!$branch) { | ||||
|             if (! $branch) { | ||||
|                 return response('Nothing to do. No branch found in the request.'); | ||||
|             } | ||||
|             $applications = Application::where('git_repository', 'like', "%$full_name%"); | ||||
| @ -101,29 +103,31 @@ class Github extends Controller | ||||
|             foreach ($applications as $application) { | ||||
|                 $webhook_secret = data_get($application, 'manual_webhook_secret_github'); | ||||
|                 $hmac = hash_hmac('sha256', $request->getContent(), $webhook_secret); | ||||
|                 if (!hash_equals($x_hub_signature_256, $hmac) && !isDev()) { | ||||
|                 if (! hash_equals($x_hub_signature_256, $hmac) && ! isDev()) { | ||||
|                     ray('Invalid signature'); | ||||
|                     $return_payloads->push([ | ||||
|                         'application' => $application->name, | ||||
|                         'status' => 'failed', | ||||
|                         'message' => 'Invalid signature.', | ||||
|                     ]); | ||||
| 
 | ||||
|                     continue; | ||||
|                 } | ||||
|                 $isFunctional = $application->destination->server->isFunctional(); | ||||
|                 if (!$isFunctional) { | ||||
|                 if (! $isFunctional) { | ||||
|                     $return_payloads->push([ | ||||
|                         'application' => $application->name, | ||||
|                         'status' => 'failed', | ||||
|                         'message' => 'Server is not functional.', | ||||
|                     ]); | ||||
| 
 | ||||
|                     continue; | ||||
|                 } | ||||
|                 if ($x_github_event === 'push') { | ||||
|                     if ($application->isDeployable()) { | ||||
|                         $is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files); | ||||
|                         if ($is_watch_path_triggered || is_null($application->watch_paths)) { | ||||
|                             ray('Deploying ' . $application->name . ' with branch ' . $branch); | ||||
|                             ray('Deploying '.$application->name.' with branch '.$branch); | ||||
|                             $deployment_uuid = new Cuid2(7); | ||||
|                             queue_application_deployment( | ||||
|                                 application: $application, | ||||
| @ -165,7 +169,7 @@ class Github extends Controller | ||||
|                         if ($application->isPRDeployable()) { | ||||
|                             $deployment_uuid = new Cuid2(7); | ||||
|                             $found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first(); | ||||
|                             if (!$found) { | ||||
|                             if (! $found) { | ||||
|                                 ApplicationPreview::create([ | ||||
|                                     'git_type' => 'github', | ||||
|                                     'application_id' => $application->id, | ||||
| @ -218,12 +222,15 @@ class Github extends Controller | ||||
|                 } | ||||
|             } | ||||
|             ray($return_payloads); | ||||
| 
 | ||||
|             return response($return_payloads); | ||||
|         } catch (Exception $e) { | ||||
|             ray($e->getMessage()); | ||||
| 
 | ||||
|             return handleError($e); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function normal(Request $request) | ||||
|     { | ||||
|         try { | ||||
| @ -239,6 +246,7 @@ class Github extends Controller | ||||
|                 })->first(); | ||||
|                 if ($github_delivery_found) { | ||||
|                     ray('Webhook already found'); | ||||
| 
 | ||||
|                     return; | ||||
|                 } | ||||
|                 $data = [ | ||||
| @ -253,6 +261,7 @@ class Github extends Controller | ||||
|                 ]; | ||||
|                 $json = json_encode($data); | ||||
|                 Storage::disk('webhooks-during-maintenance')->put("{$epoch}_Github::normal_{$x_github_delivery}", $json); | ||||
| 
 | ||||
|                 return; | ||||
|             } | ||||
|             $x_github_event = Str::lower($request->header('X-GitHub-Event')); | ||||
| @ -270,7 +279,7 @@ class Github extends Controller | ||||
|             $webhook_secret = data_get($github_app, 'webhook_secret'); | ||||
|             $hmac = hash_hmac('sha256', $request->getContent(), $webhook_secret); | ||||
|             if (config('app.env') !== 'local') { | ||||
|                 if (!hash_equals($x_hub_signature_256, $hmac)) { | ||||
|                 if (! hash_equals($x_hub_signature_256, $hmac)) { | ||||
|                     return response('Invalid signature.'); | ||||
|                 } | ||||
|             } | ||||
| @ -280,6 +289,7 @@ class Github extends Controller | ||||
|                 if ($action === 'new_permissions_accepted') { | ||||
|                     GithubAppPermissionJob::dispatch($github_app); | ||||
|                 } | ||||
| 
 | ||||
|                 return response('cool'); | ||||
|             } | ||||
|             if ($x_github_event === 'push') { | ||||
| @ -292,7 +302,7 @@ class Github extends Controller | ||||
|                 $removed_files = data_get($payload, 'commits.*.removed'); | ||||
|                 $modified_files = data_get($payload, 'commits.*.modified'); | ||||
|                 $changed_files = collect($added_files)->concat($removed_files)->concat($modified_files)->unique()->flatten(); | ||||
|                 ray('Webhook GitHub Push Event: ' . $id . ' with branch: ' . $branch); | ||||
|                 ray('Webhook GitHub Push Event: '.$id.' with branch: '.$branch); | ||||
|             } | ||||
|             if ($x_github_event === 'pull_request') { | ||||
|                 $action = data_get($payload, 'action'); | ||||
| @ -301,9 +311,9 @@ class Github extends Controller | ||||
|                 $pull_request_html_url = data_get($payload, 'pull_request.html_url'); | ||||
|                 $branch = data_get($payload, 'pull_request.head.ref'); | ||||
|                 $base_branch = data_get($payload, 'pull_request.base.ref'); | ||||
|                 ray('Webhook GitHub Pull Request Event: ' . $id . ' with branch: ' . $branch . ' and base branch: ' . $base_branch . ' and pull request id: ' . $pull_request_id); | ||||
|                 ray('Webhook GitHub Pull Request Event: '.$id.' with branch: '.$branch.' and base branch: '.$base_branch.' and pull request id: '.$pull_request_id); | ||||
|             } | ||||
|             if (!$id || !$branch) { | ||||
|             if (! $id || ! $branch) { | ||||
|                 return response('Nothing to do. No id or branch found.'); | ||||
|             } | ||||
|             $applications = Application::where('repository_project_id', $id)->whereRelation('source', 'is_public', false); | ||||
| @ -322,20 +332,21 @@ class Github extends Controller | ||||
| 
 | ||||
|             foreach ($applications as $application) { | ||||
|                 $isFunctional = $application->destination->server->isFunctional(); | ||||
|                 if (!$isFunctional) { | ||||
|                 if (! $isFunctional) { | ||||
|                     $return_payloads->push([ | ||||
|                         'status' => 'failed', | ||||
|                         'message' => 'Server is not functional.', | ||||
|                         'application_uuid' => $application->uuid, | ||||
|                         'application_name' => $application->name, | ||||
|                     ]); | ||||
| 
 | ||||
|                     continue; | ||||
|                 } | ||||
|                 if ($x_github_event === 'push') { | ||||
|                     if ($application->isDeployable()) { | ||||
|                         $is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files); | ||||
|                         if ($is_watch_path_triggered || is_null($application->watch_paths)) { | ||||
|                             ray('Deploying ' . $application->name . ' with branch ' . $branch); | ||||
|                             ray('Deploying '.$application->name.' with branch '.$branch); | ||||
|                             $deployment_uuid = new Cuid2(7); | ||||
|                             queue_application_deployment( | ||||
|                                 application: $application, | ||||
| @ -377,7 +388,7 @@ class Github extends Controller | ||||
|                         if ($application->isPRDeployable()) { | ||||
|                             $deployment_uuid = new Cuid2(7); | ||||
|                             $found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first(); | ||||
|                             if (!$found) { | ||||
|                             if (! $found) { | ||||
|                                 ApplicationPreview::create([ | ||||
|                                     'git_type' => 'github', | ||||
|                                     'application_id' => $application->id, | ||||
| @ -431,12 +442,15 @@ class Github extends Controller | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             return response($return_payloads); | ||||
|         } catch (Exception $e) { | ||||
|             ray($e->getMessage()); | ||||
| 
 | ||||
|             return handleError($e); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function redirect(Request $request) | ||||
|     { | ||||
|         try { | ||||
| @ -464,11 +478,13 @@ class Github extends Controller | ||||
|             $github_app->webhook_secret = $webhook_secret; | ||||
|             $github_app->private_key_id = $private_key->id; | ||||
|             $github_app->save(); | ||||
| 
 | ||||
|             return redirect()->route('source.github.show', ['github_app_uuid' => $github_app->uuid]); | ||||
|         } catch (Exception $e) { | ||||
|             return handleError($e); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function install(Request $request) | ||||
|     { | ||||
|         try { | ||||
| @ -488,6 +504,7 @@ class Github extends Controller | ||||
|                 ]; | ||||
|                 $json = json_encode($data); | ||||
|                 Storage::disk('webhooks-during-maintenance')->put("{$epoch}_Github::install_{$installation_id}", $json); | ||||
| 
 | ||||
|                 return; | ||||
|             } | ||||
|             $source = $request->get('source'); | ||||
| @ -497,6 +514,7 @@ class Github extends Controller | ||||
|                 $github_app->installation_id = $installation_id; | ||||
|                 $github_app->save(); | ||||
|             } | ||||
| 
 | ||||
|             return redirect()->route('source.github.show', ['github_app_uuid' => $github_app->uuid]); | ||||
|         } catch (Exception $e) { | ||||
|             return handleError($e); | ||||
|  | ||||
| @ -31,6 +31,7 @@ class Gitlab extends Controller | ||||
|                 ]; | ||||
|                 $json = json_encode($data); | ||||
|                 Storage::disk('webhooks-during-maintenance')->put("{$epoch}_Gitlab::manual_gitlab", $json); | ||||
| 
 | ||||
|                 return; | ||||
|             } | ||||
|             $return_payloads = collect([]); | ||||
| @ -39,11 +40,12 @@ class Gitlab extends Controller | ||||
|             $x_gitlab_token = data_get($headers, 'x-gitlab-token.0'); | ||||
|             $x_gitlab_event = data_get($payload, 'object_kind'); | ||||
|             $allowed_events = ['push', 'merge_request']; | ||||
|             if (!in_array($x_gitlab_event, $allowed_events)) { | ||||
|             if (! in_array($x_gitlab_event, $allowed_events)) { | ||||
|                 $return_payloads->push([ | ||||
|                     'status' => 'failed', | ||||
|                     'message' => 'Event not allowed. Only push and merge_request events are allowed.', | ||||
|                 ]); | ||||
| 
 | ||||
|                 return response($return_payloads); | ||||
|             } | ||||
| 
 | ||||
| @ -53,18 +55,19 @@ class Gitlab extends Controller | ||||
|                 if (Str::isMatch('/refs\/heads\/*/', $branch)) { | ||||
|                     $branch = Str::after($branch, 'refs/heads/'); | ||||
|                 } | ||||
|                 if (!$branch) { | ||||
|                 if (! $branch) { | ||||
|                     $return_payloads->push([ | ||||
|                         'status' => 'failed', | ||||
|                         'message' => 'Nothing to do. No branch found in the request.', | ||||
|                     ]); | ||||
| 
 | ||||
|                     return response($return_payloads); | ||||
|                 } | ||||
|                 $added_files = data_get($payload, 'commits.*.added'); | ||||
|                 $removed_files = data_get($payload, 'commits.*.removed'); | ||||
|                 $modified_files = data_get($payload, 'commits.*.modified'); | ||||
|                 $changed_files = collect($added_files)->concat($removed_files)->concat($modified_files)->unique()->flatten(); | ||||
|                 ray('Manual Webhook GitLab Push Event with branch: ' . $branch); | ||||
|                 ray('Manual Webhook GitLab Push Event with branch: '.$branch); | ||||
|             } | ||||
|             if ($x_gitlab_event === 'merge_request') { | ||||
|                 $action = data_get($payload, 'object_attributes.action'); | ||||
| @ -73,14 +76,15 @@ class Gitlab extends Controller | ||||
|                 $full_name = data_get($payload, 'project.path_with_namespace'); | ||||
|                 $pull_request_id = data_get($payload, 'object_attributes.iid'); | ||||
|                 $pull_request_html_url = data_get($payload, 'object_attributes.url'); | ||||
|                 if (!$branch) { | ||||
|                 if (! $branch) { | ||||
|                     $return_payloads->push([ | ||||
|                         'status' => 'failed', | ||||
|                         'message' => 'Nothing to do. No branch found in the request.', | ||||
|                     ]); | ||||
| 
 | ||||
|                     return response($return_payloads); | ||||
|                 } | ||||
|                 ray('Webhook GitHub Pull Request Event with branch: ' . $branch . ' and base branch: ' . $base_branch . ' and pull request id: ' . $pull_request_id); | ||||
|                 ray('Webhook GitHub Pull Request Event with branch: '.$branch.' and base branch: '.$base_branch.' and pull request id: '.$pull_request_id); | ||||
|             } | ||||
|             $applications = Application::where('git_repository', 'like', "%$full_name%"); | ||||
|             if ($x_gitlab_event === 'push') { | ||||
| @ -90,6 +94,7 @@ class Gitlab extends Controller | ||||
|                         'status' => 'failed', | ||||
|                         'message' => "Nothing to do. No applications found with deploy key set, branch is '$branch' and Git Repository name has $full_name.", | ||||
|                     ]); | ||||
| 
 | ||||
|                     return response($return_payloads); | ||||
|                 } | ||||
|             } | ||||
| @ -100,6 +105,7 @@ class Gitlab extends Controller | ||||
|                         'status' => 'failed', | ||||
|                         'message' => "Nothing to do. No applications found with branch '$base_branch'.", | ||||
|                     ]); | ||||
| 
 | ||||
|                     return response($return_payloads); | ||||
|                 } | ||||
|             } | ||||
| @ -112,23 +118,25 @@ class Gitlab extends Controller | ||||
|                         'message' => 'Invalid signature.', | ||||
|                     ]); | ||||
|                     ray('Invalid signature'); | ||||
| 
 | ||||
|                     continue; | ||||
|                 } | ||||
|                 $isFunctional = $application->destination->server->isFunctional(); | ||||
|                 if (!$isFunctional) { | ||||
|                 if (! $isFunctional) { | ||||
|                     $return_payloads->push([ | ||||
|                         'application' => $application->name, | ||||
|                         'status' => 'failed', | ||||
|                         'message' => 'Server is not functional', | ||||
|                     ]); | ||||
|                     ray('Server is not functional: ' . $application->destination->server->name); | ||||
|                     ray('Server is not functional: '.$application->destination->server->name); | ||||
| 
 | ||||
|                     continue; | ||||
|                 } | ||||
|                 if ($x_gitlab_event === 'push') { | ||||
|                     if ($application->isDeployable()) { | ||||
|                         $is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files); | ||||
|                         if ($is_watch_path_triggered || is_null($application->watch_paths)) { | ||||
|                             ray('Deploying ' . $application->name . ' with branch ' . $branch); | ||||
|                             ray('Deploying '.$application->name.' with branch '.$branch); | ||||
|                             $deployment_uuid = new Cuid2(7); | ||||
|                             queue_application_deployment( | ||||
|                                 application: $application, | ||||
| @ -163,7 +171,7 @@ class Gitlab extends Controller | ||||
|                             'application_uuid' => $application->uuid, | ||||
|                             'application_name' => $application->name, | ||||
|                         ]); | ||||
|                         ray('Deployments disabled for ' . $application->name); | ||||
|                         ray('Deployments disabled for '.$application->name); | ||||
|                     } | ||||
|                 } | ||||
|                 if ($x_gitlab_event === 'merge_request') { | ||||
| @ -171,7 +179,7 @@ class Gitlab extends Controller | ||||
|                         if ($application->isPRDeployable()) { | ||||
|                             $deployment_uuid = new Cuid2(7); | ||||
|                             $found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first(); | ||||
|                             if (!$found) { | ||||
|                             if (! $found) { | ||||
|                                 ApplicationPreview::create([ | ||||
|                                     'git_type' => 'gitlab', | ||||
|                                     'application_id' => $application->id, | ||||
| @ -188,7 +196,7 @@ class Gitlab extends Controller | ||||
|                                 is_webhook: true, | ||||
|                                 git_type: 'gitlab' | ||||
|                             ); | ||||
|                             ray('Deploying preview for ' . $application->name . ' with branch ' . $branch . ' and base branch ' . $base_branch . ' and pull request id ' . $pull_request_id); | ||||
|                             ray('Deploying preview for '.$application->name.' with branch '.$branch.' and base branch '.$base_branch.' and pull request id '.$pull_request_id); | ||||
|                             $return_payloads->push([ | ||||
|                                 'application' => $application->name, | ||||
|                                 'status' => 'success', | ||||
| @ -200,9 +208,9 @@ class Gitlab extends Controller | ||||
|                                 'status' => 'failed', | ||||
|                                 'message' => 'Preview deployments disabled', | ||||
|                             ]); | ||||
|                             ray('Preview deployments disabled for ' . $application->name); | ||||
|                             ray('Preview deployments disabled for '.$application->name); | ||||
|                         } | ||||
|                     } else if ($action === 'closed' || $action === 'close' || $action === 'merge') { | ||||
|                     } elseif ($action === 'closed' || $action === 'close' || $action === 'merge') { | ||||
|                         $found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first(); | ||||
|                         if ($found) { | ||||
|                             $found->delete(); | ||||
| @ -214,6 +222,7 @@ class Gitlab extends Controller | ||||
|                                 'status' => 'success', | ||||
|                                 'message' => 'Preview Deployment closed', | ||||
|                             ]); | ||||
| 
 | ||||
|                             return response($return_payloads); | ||||
|                         } | ||||
|                         $return_payloads->push([ | ||||
| @ -230,9 +239,11 @@ class Gitlab extends Controller | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             return response($return_payloads); | ||||
|         } catch (Exception $e) { | ||||
|             ray($e->getMessage()); | ||||
| 
 | ||||
|             return handleError($e); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -36,6 +36,7 @@ class Stripe extends Controller | ||||
|                 ]; | ||||
|                 $json = json_encode($data); | ||||
|                 Storage::disk('webhooks-during-maintenance')->put("{$epoch}_Stripe::events_stripe", $json); | ||||
| 
 | ||||
|                 return; | ||||
|             } | ||||
|             $webhookSecret = config('subscription.stripe_webhook_secret'); | ||||
| @ -48,7 +49,7 @@ class Stripe extends Controller | ||||
|             ); | ||||
|             $webhook = Webhook::create([ | ||||
|                 'type' => 'stripe', | ||||
|                 'payload' => $request->getContent() | ||||
|                 'payload' => $request->getContent(), | ||||
|             ]); | ||||
|             $type = data_get($event, 'type'); | ||||
|             $data = data_get($event, 'data.object'); | ||||
| @ -65,20 +66,20 @@ class Stripe extends Controller | ||||
|                     $customerId = data_get($data, 'customer'); | ||||
|                     $team = Team::find($teamId); | ||||
|                     $found = $team->members->where('id', $userId)->first(); | ||||
|                     if (!$found->isAdmin()) { | ||||
|                     if (! $found->isAdmin()) { | ||||
|                         send_internal_notification("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}, subscriptionid: {$subscriptionId}."); | ||||
|                         throw new Exception("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}, subscriptionid: {$subscriptionId}."); | ||||
|                     } | ||||
|                     $subscription = Subscription::where('team_id', $teamId)->first(); | ||||
|                     if ($subscription) { | ||||
|                         send_internal_notification('Old subscription activated for team: ' . $teamId); | ||||
|                         send_internal_notification('Old subscription activated for team: '.$teamId); | ||||
|                         $subscription->update([ | ||||
|                             'stripe_subscription_id' => $subscriptionId, | ||||
|                             'stripe_customer_id' => $customerId, | ||||
|                             'stripe_invoice_paid' => true, | ||||
|                         ]); | ||||
|                     } else { | ||||
|                         send_internal_notification('New subscription for team: ' . $teamId); | ||||
|                         send_internal_notification('New subscription for team: '.$teamId); | ||||
|                         Subscription::create([ | ||||
|                             'team_id' => $teamId, | ||||
|                             'stripe_subscription_id' => $subscriptionId, | ||||
| @ -95,7 +96,7 @@ class Stripe extends Controller | ||||
|                         break; | ||||
|                     } | ||||
|                     $subscription = Subscription::where('stripe_customer_id', $customerId)->first(); | ||||
|                     if (!$subscription) { | ||||
|                     if (! $subscription) { | ||||
|                         Sleep::for(5)->seconds(); | ||||
|                         $subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail(); | ||||
|                     } | ||||
| @ -106,34 +107,38 @@ class Stripe extends Controller | ||||
|                 case 'invoice.payment_failed': | ||||
|                     $customerId = data_get($data, 'customer'); | ||||
|                     $subscription = Subscription::where('stripe_customer_id', $customerId)->first(); | ||||
|                     if (!$subscription) { | ||||
|                         send_internal_notification('invoice.payment_failed failed but no subscription found in Coolify for customer: ' . $customerId); | ||||
|                     if (! $subscription) { | ||||
|                         send_internal_notification('invoice.payment_failed failed but no subscription found in Coolify for customer: '.$customerId); | ||||
| 
 | ||||
|                         return response('No subscription found in Coolify.'); | ||||
|                     } | ||||
|                     $team = data_get($subscription, 'team'); | ||||
|                     if (!$team) { | ||||
|                         send_internal_notification('invoice.payment_failed failed but no team found in Coolify for customer: ' . $customerId); | ||||
|                     if (! $team) { | ||||
|                         send_internal_notification('invoice.payment_failed failed but no team found in Coolify for customer: '.$customerId); | ||||
| 
 | ||||
|                         return response('No team found in Coolify.'); | ||||
|                     } | ||||
|                     if (!$subscription->stripe_invoice_paid) { | ||||
|                     if (! $subscription->stripe_invoice_paid) { | ||||
|                         SubscriptionInvoiceFailedJob::dispatch($team); | ||||
|                         send_internal_notification('Invoice payment failed: ' . $customerId); | ||||
|                         send_internal_notification('Invoice payment failed: '.$customerId); | ||||
|                     } else { | ||||
|                         send_internal_notification('Invoice payment failed but already paid: ' . $customerId); | ||||
|                         send_internal_notification('Invoice payment failed but already paid: '.$customerId); | ||||
|                     } | ||||
|                     break; | ||||
|                 case 'payment_intent.payment_failed': | ||||
|                     $customerId = data_get($data, 'customer'); | ||||
|                     $subscription = Subscription::where('stripe_customer_id', $customerId)->first(); | ||||
|                     if (!$subscription) { | ||||
|                         send_internal_notification('payment_intent.payment_failed, no subscription found in Coolify for customer: ' . $customerId); | ||||
|                     if (! $subscription) { | ||||
|                         send_internal_notification('payment_intent.payment_failed, no subscription found in Coolify for customer: '.$customerId); | ||||
| 
 | ||||
|                         return response('No subscription found in Coolify.'); | ||||
|                     } | ||||
|                     if ($subscription->stripe_invoice_paid) { | ||||
|                         send_internal_notification('payment_intent.payment_failed but invoice is active for customer: ' . $customerId); | ||||
|                         send_internal_notification('payment_intent.payment_failed but invoice is active for customer: '.$customerId); | ||||
| 
 | ||||
|                         return; | ||||
|                     } | ||||
|                     send_internal_notification('Subscription payment failed for customer: ' . $customerId); | ||||
|                     send_internal_notification('Subscription payment failed for customer: '.$customerId); | ||||
|                     break; | ||||
|                 case 'customer.subscription.updated': | ||||
|                     $customerId = data_get($data, 'customer'); | ||||
| @ -145,17 +150,19 @@ class Stripe extends Controller | ||||
|                         break; | ||||
|                     } | ||||
|                     $subscription = Subscription::where('stripe_customer_id', $customerId)->first(); | ||||
|                     if (!$subscription) { | ||||
|                     if (! $subscription) { | ||||
|                         Sleep::for(5)->seconds(); | ||||
|                         $subscription = Subscription::where('stripe_customer_id', $customerId)->first(); | ||||
|                     } | ||||
|                     if (!$subscription) { | ||||
|                     if (! $subscription) { | ||||
|                         if ($status === 'incomplete_expired') { | ||||
|                             send_internal_notification('Subscription incomplete expired for customer: ' . $customerId); | ||||
|                             return response("Subscription incomplete expired", 200); | ||||
|                             send_internal_notification('Subscription incomplete expired for customer: '.$customerId); | ||||
| 
 | ||||
|                             return response('Subscription incomplete expired', 200); | ||||
|                         } | ||||
|                         send_internal_notification('No subscription found for: ' . $customerId); | ||||
|                         return response("No subscription found", 400); | ||||
|                         send_internal_notification('No subscription found for: '.$customerId); | ||||
| 
 | ||||
|                         return response('No subscription found', 400); | ||||
|                     } | ||||
|                     $trialEndedAlready = data_get($subscription, 'stripe_trial_already_ended'); | ||||
|                     $cancelAtPeriodEnd = data_get($data, 'cancel_at_period_end'); | ||||
| @ -187,7 +194,7 @@ class Stripe extends Controller | ||||
|                         $subscription->update([ | ||||
|                             'stripe_invoice_paid' => false, | ||||
|                         ]); | ||||
|                         send_internal_notification('Subscription paused or incomplete for customer: ' . $customerId); | ||||
|                         send_internal_notification('Subscription paused or incomplete for customer: '.$customerId); | ||||
|                     } | ||||
| 
 | ||||
|                     // Trial ended but subscribed, reactive servers
 | ||||
| @ -197,9 +204,9 @@ class Stripe extends Controller | ||||
|                     } | ||||
| 
 | ||||
|                     if ($feedback) { | ||||
|                         $reason = "Cancellation feedback for {$customerId}: '" . $feedback . "'"; | ||||
|                         $reason = "Cancellation feedback for {$customerId}: '".$feedback."'"; | ||||
|                         if ($comment) { | ||||
|                             $reason .= ' with comment: \'' . $comment . "'"; | ||||
|                             $reason .= ' with comment: \''.$comment."'"; | ||||
|                         } | ||||
|                         send_internal_notification($reason); | ||||
|                     } | ||||
| @ -207,7 +214,7 @@ class Stripe extends Controller | ||||
|                         if ($cancelAtPeriodEnd) { | ||||
|                             // send_internal_notification('Subscription cancelled at period end for team: ' . $subscription->team->id);
 | ||||
|                         } else { | ||||
|                             send_internal_notification('customer.subscription.updated for customer: ' . $customerId); | ||||
|                             send_internal_notification('customer.subscription.updated for customer: '.$customerId); | ||||
|                         } | ||||
|                     } | ||||
|                     break; | ||||
| @ -226,15 +233,15 @@ class Stripe extends Controller | ||||
|                         'stripe_invoice_paid' => false, | ||||
|                         'stripe_trial_already_ended' => true, | ||||
|                     ]); | ||||
|                     send_internal_notification('customer.subscription.deleted for customer: ' . $customerId); | ||||
|                     send_internal_notification('customer.subscription.deleted for customer: '.$customerId); | ||||
|                     break; | ||||
|                 case 'customer.subscription.trial_will_end': | ||||
|                     // Not used for now
 | ||||
|                     $customerId = data_get($data, 'customer'); | ||||
|                     $subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail(); | ||||
|                     $team = data_get($subscription, 'team'); | ||||
|                     if (!$team) { | ||||
|                         throw new Exception('No team found for subscription: ' . $subscription->id); | ||||
|                     if (! $team) { | ||||
|                         throw new Exception('No team found for subscription: '.$subscription->id); | ||||
|                     } | ||||
|                     SubscriptionTrialEndsSoonJob::dispatch($team); | ||||
|                     break; | ||||
| @ -242,8 +249,8 @@ class Stripe extends Controller | ||||
|                     $customerId = data_get($data, 'customer'); | ||||
|                     $subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail(); | ||||
|                     $team = data_get($subscription, 'team'); | ||||
|                     if (!$team) { | ||||
|                         throw new Exception('No team found for subscription: ' . $subscription->id); | ||||
|                     if (! $team) { | ||||
|                         throw new Exception('No team found for subscription: '.$subscription->id); | ||||
|                     } | ||||
|                     $team->trialEnded(); | ||||
|                     $subscription->update([ | ||||
| @ -251,19 +258,20 @@ class Stripe extends Controller | ||||
|                         'stripe_invoice_paid' => false, | ||||
|                     ]); | ||||
|                     SubscriptionTrialEndedJob::dispatch($team); | ||||
|                     send_internal_notification('Subscription paused for customer: ' . $customerId); | ||||
|                     send_internal_notification('Subscription paused for customer: '.$customerId); | ||||
|                     break; | ||||
|                 default: | ||||
|                     // Unhandled event type
 | ||||
|             } | ||||
|         } catch (Exception $e) { | ||||
|             if ($type !== 'payment_intent.payment_failed') { | ||||
|                 send_internal_notification("Subscription webhook ($type) failed: " . $e->getMessage()); | ||||
|                 send_internal_notification("Subscription webhook ($type) failed: ".$e->getMessage()); | ||||
|             } | ||||
|             $webhook->update([ | ||||
|                 'status' => 'failed', | ||||
|                 'failure_reason' => $e->getMessage(), | ||||
|             ]); | ||||
| 
 | ||||
|             return response($e->getMessage(), 400); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -17,41 +17,49 @@ class Waitlist extends Controller | ||||
|         try { | ||||
|             $found = ModelsWaitlist::where('uuid', $confirmation_code)->where('email', $email)->first(); | ||||
|             if ($found) { | ||||
|                 if (!$found->verified) { | ||||
|                 if (! $found->verified) { | ||||
|                     if ($found->created_at > now()->subMinutes(config('constants.waitlist.expiration'))) { | ||||
|                         $found->verified = true; | ||||
|                         $found->save(); | ||||
|                         send_internal_notification('Waitlist confirmed: ' . $email); | ||||
|                         send_internal_notification('Waitlist confirmed: '.$email); | ||||
| 
 | ||||
|                         return 'Thank you for confirming your email address. We will notify you when you are next in line.'; | ||||
|                     } else { | ||||
|                         $found->delete(); | ||||
|                         send_internal_notification('Waitlist expired: ' . $email); | ||||
|                         send_internal_notification('Waitlist expired: '.$email); | ||||
| 
 | ||||
|                         return 'Your confirmation code has expired. Please sign up again.'; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             return redirect()->route('dashboard'); | ||||
|         } catch (Exception $e) { | ||||
|             send_internal_notification('Waitlist confirmation failed: ' . $e->getMessage()); | ||||
|             send_internal_notification('Waitlist confirmation failed: '.$e->getMessage()); | ||||
|             ray($e->getMessage()); | ||||
| 
 | ||||
|             return redirect()->route('dashboard'); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function cancel(Request $request) | ||||
|     { | ||||
|         $email = request()->get('email'); | ||||
|         $confirmation_code = request()->get('confirmation_code'); | ||||
|         try { | ||||
|             $found = ModelsWaitlist::where('uuid', $confirmation_code)->where('email', $email)->first(); | ||||
|             if ($found && !$found->verified) { | ||||
|             if ($found && ! $found->verified) { | ||||
|                 $found->delete(); | ||||
|                 send_internal_notification('Waitlist cancelled: ' . $email); | ||||
|                 send_internal_notification('Waitlist cancelled: '.$email); | ||||
| 
 | ||||
|                 return 'Your email address has been removed from the waitlist.'; | ||||
|             } | ||||
| 
 | ||||
|             return redirect()->route('dashboard'); | ||||
|         } catch (Exception $e) { | ||||
|             send_internal_notification('Waitlist cancellation failed: ' . $e->getMessage()); | ||||
|             send_internal_notification('Waitlist cancellation failed: '.$e->getMessage()); | ||||
|             ray($e->getMessage()); | ||||
| 
 | ||||
|             return redirect()->route('dashboard'); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -44,7 +44,7 @@ class Kernel extends HttpKernel | ||||
| 
 | ||||
|         'api' => [ | ||||
|             // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
 | ||||
|             \Illuminate\Routing\Middleware\ThrottleRequests::class . ':api', | ||||
|             \Illuminate\Routing\Middleware\ThrottleRequests::class.':api', | ||||
|             \Illuminate\Routing\Middleware\SubstituteBindings::class, | ||||
|         ], | ||||
|     ]; | ||||
|  | ||||
| @ -20,6 +20,7 @@ class CheckForcePasswordReset | ||||
|                 auth()->logout(); | ||||
|                 request()->session()->invalidate(); | ||||
|                 request()->session()->regenerateToken(); | ||||
| 
 | ||||
|                 return $next($request); | ||||
|             } | ||||
|             $force_password_reset = auth()->user()->force_password_reset; | ||||
| @ -27,9 +28,11 @@ class CheckForcePasswordReset | ||||
|                 if ($request->routeIs('auth.force-password-reset') || $request->path() === 'force-password-reset' || $request->path() === 'livewire/update' || $request->path() === 'logout') { | ||||
|                     return $next($request); | ||||
|                 } | ||||
| 
 | ||||
|                 return redirect()->route('auth.force-password-reset'); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return $next($request); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -5,8 +5,8 @@ namespace App\Http\Middleware; | ||||
| use App\Providers\RouteServiceProvider; | ||||
| use Closure; | ||||
| use Illuminate\Http\Request; | ||||
| use Symfony\Component\HttpFoundation\Response; | ||||
| use Illuminate\Support\Str; | ||||
| use Symfony\Component\HttpFoundation\Response; | ||||
| 
 | ||||
| class DecideWhatToDoWithUser | ||||
| { | ||||
| @ -16,33 +16,37 @@ class DecideWhatToDoWithUser | ||||
|             $currentTeam = auth()->user()?->recreate_personal_team(); | ||||
|             refreshSession($currentTeam); | ||||
|         } | ||||
|         if(auth()?->user()?->currentTeam()){ | ||||
|         if (auth()?->user()?->currentTeam()) { | ||||
|             refreshSession(auth()->user()->currentTeam()); | ||||
|         } | ||||
|         if (!auth()->user() || !isCloud() || isInstanceAdmin()) { | ||||
|             if (!isCloud() && showBoarding() && !in_array($request->path(), allowedPathsForBoardingAccounts())) { | ||||
|         if (! auth()->user() || ! isCloud() || isInstanceAdmin()) { | ||||
|             if (! isCloud() && showBoarding() && ! in_array($request->path(), allowedPathsForBoardingAccounts())) { | ||||
|                 return redirect()->route('onboarding'); | ||||
|             } | ||||
| 
 | ||||
|             return $next($request); | ||||
|         } | ||||
|         if (!auth()->user()->hasVerifiedEmail()) { | ||||
|         if (! auth()->user()->hasVerifiedEmail()) { | ||||
|             if ($request->path() === 'verify' || in_array($request->path(), allowedPathsForInvalidAccounts()) || $request->routeIs('verify.verify')) { | ||||
|                 return $next($request); | ||||
|             } | ||||
| 
 | ||||
|             return redirect()->route('verify.email'); | ||||
|         } | ||||
|         if (!isSubscriptionActive() && !isSubscriptionOnGracePeriod()) { | ||||
|             if (!in_array($request->path(), allowedPathsForUnsubscribedAccounts())) { | ||||
|         if (! isSubscriptionActive() && ! isSubscriptionOnGracePeriod()) { | ||||
|             if (! in_array($request->path(), allowedPathsForUnsubscribedAccounts())) { | ||||
|                 if (Str::startsWith($request->path(), 'invitations')) { | ||||
|                     return $next($request); | ||||
|                 } | ||||
| 
 | ||||
|                 return redirect()->route('subscription.index'); | ||||
|             } | ||||
|         } | ||||
|         if (showBoarding() && !in_array($request->path(), allowedPathsForBoardingAccounts())) { | ||||
|         if (showBoarding() && ! in_array($request->path(), allowedPathsForBoardingAccounts())) { | ||||
|             if (Str::startsWith($request->path(), 'invitations')) { | ||||
|                 return $next($request); | ||||
|             } | ||||
| 
 | ||||
|             return redirect()->route('onboarding'); | ||||
|         } | ||||
|         if (auth()->user()->hasVerifiedEmail() && $request->path() === 'verify') { | ||||
| @ -51,6 +55,7 @@ class DecideWhatToDoWithUser | ||||
|         if (isSubscriptionActive() && $request->routeIs('subscription.index')) { | ||||
|             return redirect(RouteServiceProvider::HOME); | ||||
|         } | ||||
| 
 | ||||
|         return $next($request); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -13,6 +13,6 @@ class PreventRequestsDuringMaintenance extends Middleware | ||||
|      */ | ||||
|     protected $except = [ | ||||
|         'webhooks/*', | ||||
|         '/api/health' | ||||
|         '/api/health', | ||||
|     ]; | ||||
| } | ||||
|  | ||||
| @ -24,6 +24,7 @@ class RedirectIfAuthenticated | ||||
|                 return redirect(RouteServiceProvider::HOME); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return $next($request); | ||||
|     } | ||||
| } | ||||
|  | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -12,11 +12,12 @@ use Illuminate\Foundation\Bus\Dispatchable; | ||||
| use Illuminate\Queue\InteractsWithQueue; | ||||
| use Illuminate\Queue\SerializesModels; | ||||
| 
 | ||||
| class ApplicationPullRequestUpdateJob implements ShouldQueue, ShouldBeEncrypted | ||||
| class ApplicationPullRequestUpdateJob implements ShouldBeEncrypted, ShouldQueue | ||||
| { | ||||
|     use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; | ||||
| 
 | ||||
|     public string $build_logs_url; | ||||
| 
 | ||||
|     public string $body; | ||||
| 
 | ||||
|     public function __construct( | ||||
| @ -32,25 +33,27 @@ class ApplicationPullRequestUpdateJob implements ShouldQueue, ShouldBeEncrypted | ||||
|         try { | ||||
|             if ($this->application->is_public_repository()) { | ||||
|                 ray('Public repository. Skipping comment update.'); | ||||
| 
 | ||||
|                 return; | ||||
|             } | ||||
|             if ($this->status === ProcessStatus::CLOSED) { | ||||
|                 $this->delete_comment(); | ||||
| 
 | ||||
|                 return; | ||||
|             } else if ($this->status === ProcessStatus::IN_PROGRESS) { | ||||
|             } elseif ($this->status === ProcessStatus::IN_PROGRESS) { | ||||
|                 $this->body = "The preview deployment is in progress. 🟡\n\n"; | ||||
|             } else if ($this->status === ProcessStatus::FINISHED) { | ||||
|             } elseif ($this->status === ProcessStatus::FINISHED) { | ||||
|                 $this->body = "The preview deployment is ready. 🟢\n\n"; | ||||
|                 if ($this->preview->fqdn) { | ||||
|                     $this->body .= "[Open Preview]({$this->preview->fqdn}) | "; | ||||
|                 } | ||||
|             } else if ($this->status === ProcessStatus::ERROR) { | ||||
|             } elseif ($this->status === ProcessStatus::ERROR) { | ||||
|                 $this->body = "The preview deployment failed. 🔴\n\n"; | ||||
|             } | ||||
|             $this->build_logs_url = base_url() . "/project/{$this->application->environment->project->uuid}/{$this->application->environment->name}/application/{$this->application->uuid}/deployment/{$this->deployment_uuid}"; | ||||
|             $this->build_logs_url = base_url()."/project/{$this->application->environment->project->uuid}/{$this->application->environment->name}/application/{$this->application->uuid}/deployment/{$this->deployment_uuid}"; | ||||
| 
 | ||||
|             $this->body .= "[Open Build Logs](" . $this->build_logs_url . ")\n\n\n"; | ||||
|             $this->body .= "Last updated at: " . now()->toDateTimeString() . " CET"; | ||||
|             $this->body .= '[Open Build Logs]('.$this->build_logs_url.")\n\n\n"; | ||||
|             $this->body .= 'Last updated at: '.now()->toDateTimeString().' CET'; | ||||
| 
 | ||||
|             ray('Updating comment', $this->body); | ||||
|             if ($this->preview->pull_request_issue_comment_id) { | ||||
| @ -60,6 +63,7 @@ class ApplicationPullRequestUpdateJob implements ShouldQueue, ShouldBeEncrypted | ||||
|             } | ||||
|         } catch (\Throwable $e) { | ||||
|             ray($e); | ||||
| 
 | ||||
|             return $e; | ||||
|         } | ||||
|     } | ||||
| @ -83,6 +87,7 @@ class ApplicationPullRequestUpdateJob implements ShouldQueue, ShouldBeEncrypted | ||||
|         $this->preview->pull_request_issue_comment_id = $data['id']; | ||||
|         $this->preview->save(); | ||||
|     } | ||||
| 
 | ||||
|     private function delete_comment() | ||||
|     { | ||||
|         githubApi(source: $this->application->source, endpoint: "/repos/{$this->application->git_repository}/issues/comments/{$this->preview->pull_request_issue_comment_id}", method: 'delete'); | ||||
|  | ||||
| @ -10,19 +10,23 @@ use Illuminate\Foundation\Bus\Dispatchable; | ||||
| use Illuminate\Queue\InteractsWithQueue; | ||||
| use Illuminate\Queue\SerializesModels; | ||||
| 
 | ||||
| 
 | ||||
| class ApplicationRestartJob implements ShouldQueue, ShouldBeEncrypted | ||||
| class ApplicationRestartJob implements ShouldBeEncrypted, ShouldQueue | ||||
| { | ||||
|     use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, ExecuteRemoteCommand; | ||||
|     use Dispatchable, ExecuteRemoteCommand, InteractsWithQueue, Queueable, SerializesModels; | ||||
| 
 | ||||
|     public $timeout = 3600; | ||||
| 
 | ||||
|     public $tries = 1; | ||||
| 
 | ||||
|     public string $applicationDeploymentQueueId; | ||||
| 
 | ||||
|     public function __construct(string $applicationDeploymentQueueId) | ||||
|     { | ||||
|         $this->applicationDeploymentQueueId = $applicationDeploymentQueueId; | ||||
|     } | ||||
|     public function handle() { | ||||
| 
 | ||||
|     public function handle() | ||||
|     { | ||||
|         ray('Restarting application'); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -15,13 +15,14 @@ use Illuminate\Queue\Middleware\WithoutOverlapping; | ||||
| use Illuminate\Queue\SerializesModels; | ||||
| use Illuminate\Support\Sleep; | ||||
| 
 | ||||
| class CheckLogDrainContainerJob implements ShouldQueue, ShouldBeEncrypted | ||||
| class CheckLogDrainContainerJob implements ShouldBeEncrypted, ShouldQueue | ||||
| { | ||||
|     use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; | ||||
| 
 | ||||
|     public function __construct(public Server $server) | ||||
|     { | ||||
|     } | ||||
| 
 | ||||
|     public function middleware(): array | ||||
|     { | ||||
|         return [(new WithoutOverlapping($this->server->id))->dontRelease()]; | ||||
| @ -31,6 +32,7 @@ class CheckLogDrainContainerJob implements ShouldQueue, ShouldBeEncrypted | ||||
|     { | ||||
|         return $this->server->id; | ||||
|     } | ||||
| 
 | ||||
|     public function healthcheck() | ||||
|     { | ||||
|         $status = instant_remote_process(["docker inspect --format='{{json .State.Status}}' coolify-log-drain"], $this->server, false); | ||||
| @ -40,15 +42,16 @@ class CheckLogDrainContainerJob implements ShouldQueue, ShouldBeEncrypted | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function handle() | ||||
|     { | ||||
|         // ray("checking log drain statuses for {$this->server->id}");
 | ||||
|         try { | ||||
|             if (!$this->server->isFunctional()) { | ||||
|             if (! $this->server->isFunctional()) { | ||||
|                 return; | ||||
|             }; | ||||
|             $containers = instant_remote_process(["docker container ls -q"], $this->server, false); | ||||
|             if (!$containers) { | ||||
|             } | ||||
|             $containers = instant_remote_process(['docker container ls -q'], $this->server, false); | ||||
|             if (! $containers) { | ||||
|                 return; | ||||
|             } | ||||
|             $containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this->server); | ||||
| @ -57,7 +60,7 @@ class CheckLogDrainContainerJob implements ShouldQueue, ShouldBeEncrypted | ||||
|             $foundLogDrainContainer = $containers->filter(function ($value, $key) { | ||||
|                 return data_get($value, 'Name') === '/coolify-log-drain'; | ||||
|             })->first(); | ||||
|             if (!$foundLogDrainContainer || !$this->healthcheck()) { | ||||
|             if (! $foundLogDrainContainer || ! $this->healthcheck()) { | ||||
|                 ray('Log drain container not found or unhealthy. Restarting...'); | ||||
|                 InstallLogDrain::run($this->server); | ||||
|                 Sleep::for(10)->seconds(); | ||||
| @ -66,9 +69,10 @@ class CheckLogDrainContainerJob implements ShouldQueue, ShouldBeEncrypted | ||||
|                         $this->server->team?->notify(new ContainerRestarted('Coolify Log Drainer', $this->server)); | ||||
|                         $this->server->update(['log_drain_notification_sent' => false]); | ||||
|                     } | ||||
| 
 | ||||
|                     return; | ||||
|                 } | ||||
|                 if (!$this->server->log_drain_notification_sent) { | ||||
|                 if (! $this->server->log_drain_notification_sent) { | ||||
|                     ray('Log drain container still unhealthy. Sending notification...'); | ||||
|                     // $this->server->team?->notify(new ContainerStopped('Coolify Log Drainer', $this->server, null));
 | ||||
|                     $this->server->update(['log_drain_notification_sent' => true]); | ||||
| @ -80,8 +84,11 @@ class CheckLogDrainContainerJob implements ShouldQueue, ShouldBeEncrypted | ||||
|                 } | ||||
|             } | ||||
|         } catch (\Throwable $e) { | ||||
|             if (!isCloud()) send_internal_notification("CheckLogDrainContainerJob failed on ({$this->server->id}) with: " . $e->getMessage()); | ||||
|             if (! isCloud()) { | ||||
|                 send_internal_notification("CheckLogDrainContainerJob failed on ({$this->server->id}) with: ".$e->getMessage()); | ||||
|             } | ||||
|             ray($e->getMessage()); | ||||
| 
 | ||||
|             return handleError($e); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -10,7 +10,7 @@ use Illuminate\Foundation\Bus\Dispatchable; | ||||
| use Illuminate\Queue\InteractsWithQueue; | ||||
| use Illuminate\Queue\SerializesModels; | ||||
| 
 | ||||
| class CheckResaleLicenseJob implements ShouldQueue, ShouldBeEncrypted | ||||
| class CheckResaleLicenseJob implements ShouldBeEncrypted, ShouldQueue | ||||
| { | ||||
|     use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; | ||||
| 
 | ||||
| @ -23,7 +23,7 @@ class CheckResaleLicenseJob implements ShouldQueue, ShouldBeEncrypted | ||||
|         try { | ||||
|             CheckResaleLicense::run(); | ||||
|         } catch (\Throwable $e) { | ||||
|             send_internal_notification('CheckResaleLicenseJob failed with: ' . $e->getMessage()); | ||||
|             send_internal_notification('CheckResaleLicenseJob failed with: '.$e->getMessage()); | ||||
|             ray($e); | ||||
|             throw $e; | ||||
|         } | ||||
|  | ||||
| @ -11,7 +11,7 @@ use Illuminate\Foundation\Bus\Dispatchable; | ||||
| use Illuminate\Queue\InteractsWithQueue; | ||||
| use Illuminate\Queue\SerializesModels; | ||||
| 
 | ||||
| class CleanupHelperContainersJob implements ShouldQueue, ShouldBeUnique, ShouldBeEncrypted | ||||
| class CleanupHelperContainersJob implements ShouldBeEncrypted, ShouldBeUnique, ShouldQueue | ||||
| { | ||||
|     use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; | ||||
| 
 | ||||
| @ -22,18 +22,18 @@ class CleanupHelperContainersJob implements ShouldQueue, ShouldBeUnique, ShouldB | ||||
|     public function handle(): void | ||||
|     { | ||||
|         try { | ||||
|             ray('Cleaning up helper containers on ' . $this->server->name); | ||||
|             ray('Cleaning up helper containers on '.$this->server->name); | ||||
|             $containers = instant_remote_process(['docker container ps --filter "ancestor=ghcr.io/coollabsio/coolify-helper:next" --filter "ancestor=ghcr.io/coollabsio/coolify-helper:latest" --format \'{{json .}}\''], $this->server, false); | ||||
|             $containers = format_docker_command_output_to_json($containers); | ||||
|             if ($containers->count() > 0) { | ||||
|                 foreach ($containers as $container) { | ||||
|                     $containerId = data_get($container,'ID'); | ||||
|                     ray('Removing container ' . $containerId); | ||||
|                     instant_remote_process(['docker container rm -f ' . $containerId], $this->server, false); | ||||
|                     $containerId = data_get($container, 'ID'); | ||||
|                     ray('Removing container '.$containerId); | ||||
|                     instant_remote_process(['docker container rm -f '.$containerId], $this->server, false); | ||||
|                 } | ||||
|             } | ||||
|         } catch (\Throwable $e) { | ||||
|             send_internal_notification('CleanupHelperContainersJob failed with error: ' . $e->getMessage()); | ||||
|             send_internal_notification('CleanupHelperContainersJob failed with error: '.$e->getMessage()); | ||||
|             ray($e->getMessage()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -12,7 +12,7 @@ use Illuminate\Foundation\Bus\Dispatchable; | ||||
| use Illuminate\Queue\InteractsWithQueue; | ||||
| use Illuminate\Queue\SerializesModels; | ||||
| 
 | ||||
| class CleanupInstanceStuffsJob implements ShouldQueue, ShouldBeUnique, ShouldBeEncrypted | ||||
| class CleanupInstanceStuffsJob implements ShouldBeEncrypted, ShouldBeUnique, ShouldQueue | ||||
| { | ||||
|     use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; | ||||
| 
 | ||||
| @ -31,13 +31,13 @@ class CleanupInstanceStuffsJob implements ShouldQueue, ShouldBeUnique, ShouldBeE | ||||
|         try { | ||||
|             // $this->cleanup_waitlist();
 | ||||
|         } catch (\Throwable $e) { | ||||
|             send_internal_notification('CleanupInstanceStuffsJob failed with error: ' . $e->getMessage()); | ||||
|             send_internal_notification('CleanupInstanceStuffsJob failed with error: '.$e->getMessage()); | ||||
|             ray($e->getMessage()); | ||||
|         } | ||||
|         try { | ||||
|             $this->cleanup_invitation_link(); | ||||
|         } catch (\Throwable $e) { | ||||
|             send_internal_notification('CleanupInstanceStuffsJob failed with error: ' . $e->getMessage()); | ||||
|             send_internal_notification('CleanupInstanceStuffsJob failed with error: '.$e->getMessage()); | ||||
|             ray($e->getMessage()); | ||||
|         } | ||||
|     } | ||||
| @ -49,6 +49,7 @@ class CleanupInstanceStuffsJob implements ShouldQueue, ShouldBeUnique, ShouldBeE | ||||
|             $item->delete(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private function cleanup_invitation_link() | ||||
|     { | ||||
|         $invitation = TeamInvitation::all(); | ||||
|  | ||||
| @ -12,18 +12,21 @@ use Illuminate\Queue\InteractsWithQueue; | ||||
| use Illuminate\Queue\Middleware\WithoutOverlapping; | ||||
| use Illuminate\Queue\SerializesModels; | ||||
| 
 | ||||
| class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted | ||||
| class ContainerStatusJob implements ShouldBeEncrypted, ShouldQueue | ||||
| { | ||||
|     use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; | ||||
| 
 | ||||
|     public $tries = 4; | ||||
| 
 | ||||
|     public function backoff(): int | ||||
|     { | ||||
|         return isDev() ? 1 : 3; | ||||
|     } | ||||
| 
 | ||||
|     public function __construct(public Server $server) | ||||
|     { | ||||
|     } | ||||
| 
 | ||||
|     public function middleware(): array | ||||
|     { | ||||
|         return [(new WithoutOverlapping($this->server->uuid))]; | ||||
|  | ||||
| @ -11,7 +11,7 @@ use Illuminate\Queue\InteractsWithQueue; | ||||
| use Illuminate\Queue\SerializesModels; | ||||
| use Spatie\Activitylog\Models\Activity; | ||||
| 
 | ||||
| class CoolifyTask implements ShouldQueue, ShouldBeEncrypted | ||||
| class CoolifyTask implements ShouldBeEncrypted, ShouldQueue | ||||
| { | ||||
|     use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; | ||||
| 
 | ||||
| @ -35,7 +35,7 @@ class CoolifyTask implements ShouldQueue, ShouldBeEncrypted | ||||
|             'activity' => $this->activity, | ||||
|             'ignore_errors' => $this->ignore_errors, | ||||
|             'call_event_on_finish' => $this->call_event_on_finish, | ||||
|             'call_event_data' => $this->call_event_data | ||||
|             'call_event_data' => $this->call_event_data, | ||||
|         ]); | ||||
| 
 | ||||
|         $remote_process(); | ||||
|  | ||||
| @ -25,26 +25,37 @@ use Illuminate\Queue\InteractsWithQueue; | ||||
| use Illuminate\Queue\Middleware\WithoutOverlapping; | ||||
| use Illuminate\Queue\SerializesModels; | ||||
| use Illuminate\Support\Str; | ||||
| use Throwable; | ||||
| 
 | ||||
| class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted | ||||
| class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue | ||||
| { | ||||
|     use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; | ||||
| 
 | ||||
|     public ?Team $team = null; | ||||
| 
 | ||||
|     public Server $server; | ||||
| 
 | ||||
|     public ScheduledDatabaseBackup $backup; | ||||
| 
 | ||||
|     public StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|ServiceDatabase $database; | ||||
| 
 | ||||
|     public ?string $container_name = null; | ||||
| 
 | ||||
|     public ?string $directory_name = null; | ||||
| 
 | ||||
|     public ?ScheduledDatabaseBackupExecution $backup_log = null; | ||||
| 
 | ||||
|     public string $backup_status = 'failed'; | ||||
| 
 | ||||
|     public ?string $backup_location = null; | ||||
| 
 | ||||
|     public string $backup_dir; | ||||
| 
 | ||||
|     public string $backup_file; | ||||
| 
 | ||||
|     public int $size = 0; | ||||
| 
 | ||||
|     public ?string $backup_output = null; | ||||
| 
 | ||||
|     public ?S3Storage $s3 = null; | ||||
| 
 | ||||
|     public function __construct($backup) | ||||
| @ -84,11 +95,13 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted | ||||
|                 $this->backup->update(['status' => 'failed']); | ||||
|                 StopDatabase::run($this->database); | ||||
|                 $this->database->delete(); | ||||
| 
 | ||||
|                 return; | ||||
|             } | ||||
|             $status = Str::of(data_get($this->database, 'status')); | ||||
|             if (!$status->startsWith('running') && $this->database->id !== 0) { | ||||
|             if (! $status->startsWith('running') && $this->database->id !== 0) { | ||||
|                 ray('database not running'); | ||||
| 
 | ||||
|                 return; | ||||
|             } | ||||
|             if (data_get($this->backup, 'database_type') === 'App\Models\ServiceDatabase') { | ||||
| @ -97,7 +110,7 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted | ||||
|                 $serviceName = str($this->database->service->name)->slug(); | ||||
|                 if (str($databaseType)->contains('postgres')) { | ||||
|                     $this->container_name = "{$this->database->name}-$serviceUuid"; | ||||
|                     $this->directory_name = $serviceName . '-' . $this->container_name; | ||||
|                     $this->directory_name = $serviceName.'-'.$this->container_name; | ||||
|                     $commands[] = "docker exec $this->container_name env | grep POSTGRES_"; | ||||
|                     $envs = instant_remote_process($commands, $this->server); | ||||
|                     $envs = str($envs)->explode("\n"); | ||||
| @ -120,9 +133,9 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted | ||||
|                     } else { | ||||
|                         $databasesToBackup = $this->database->postgres_user; | ||||
|                     } | ||||
|                 } else if (str($databaseType)->contains('mysql')) { | ||||
|                 } elseif (str($databaseType)->contains('mysql')) { | ||||
|                     $this->container_name = "{$this->database->name}-$serviceUuid"; | ||||
|                     $this->directory_name = $serviceName . '-' . $this->container_name; | ||||
|                     $this->directory_name = $serviceName.'-'.$this->container_name; | ||||
|                     $commands[] = "docker exec $this->container_name env | grep MYSQL_"; | ||||
|                     $envs = instant_remote_process($commands, $this->server); | ||||
|                     $envs = str($envs)->explode("\n"); | ||||
| @ -143,9 +156,9 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted | ||||
|                     } else { | ||||
|                         throw new \Exception('MYSQL_DATABASE not found'); | ||||
|                     } | ||||
|                 } else if (str($databaseType)->contains('mariadb')) { | ||||
|                 } elseif (str($databaseType)->contains('mariadb')) { | ||||
|                     $this->container_name = "{$this->database->name}-$serviceUuid"; | ||||
|                     $this->directory_name = $serviceName . '-' . $this->container_name; | ||||
|                     $this->directory_name = $serviceName.'-'.$this->container_name; | ||||
|                     $commands[] = "docker exec $this->container_name env"; | ||||
|                     $envs = instant_remote_process($commands, $this->server); | ||||
|                     $envs = str($envs)->explode("\n"); | ||||
| @ -184,7 +197,7 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted | ||||
|             } else { | ||||
|                 $databaseName = str($this->database->name)->slug()->value(); | ||||
|                 $this->container_name = $this->database->uuid; | ||||
|                 $this->directory_name = $databaseName . '-' . $this->container_name; | ||||
|                 $this->directory_name = $databaseName.'-'.$this->container_name; | ||||
|                 $databaseType = $this->database->type(); | ||||
|                 $databasesToBackup = data_get($this->backup, 'databases_to_backup'); | ||||
|             } | ||||
| @ -192,11 +205,11 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted | ||||
|             if (is_null($databasesToBackup)) { | ||||
|                 if (str($databaseType)->contains('postgres')) { | ||||
|                     $databasesToBackup = [$this->database->postgres_db]; | ||||
|                 } else if (str($databaseType)->contains('mongodb')) { | ||||
|                 } elseif (str($databaseType)->contains('mongodb')) { | ||||
|                     $databasesToBackup = ['*']; | ||||
|                 } else if (str($databaseType)->contains('mysql')) { | ||||
|                 } elseif (str($databaseType)->contains('mysql')) { | ||||
|                     $databasesToBackup = [$this->database->mysql_database]; | ||||
|                 } else if (str($databaseType)->contains('mariadb')) { | ||||
|                 } elseif (str($databaseType)->contains('mariadb')) { | ||||
|                     $databasesToBackup = [$this->database->mariadb_database]; | ||||
|                 } else { | ||||
|                     return; | ||||
| @ -206,16 +219,16 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted | ||||
|                     // Format: db1,db2,db3
 | ||||
|                     $databasesToBackup = explode(',', $databasesToBackup); | ||||
|                     $databasesToBackup = array_map('trim', $databasesToBackup); | ||||
|                 } else if (str($databaseType)->contains('mongodb')) { | ||||
|                 } elseif (str($databaseType)->contains('mongodb')) { | ||||
|                     // Format: db1:collection1,collection2|db2:collection3,collection4
 | ||||
|                     $databasesToBackup = explode('|', $databasesToBackup); | ||||
|                     $databasesToBackup = array_map('trim', $databasesToBackup); | ||||
|                     ray($databasesToBackup); | ||||
|                 } else if (str($databaseType)->contains('mysql')) { | ||||
|                 } elseif (str($databaseType)->contains('mysql')) { | ||||
|                     // Format: db1,db2,db3
 | ||||
|                     $databasesToBackup = explode(',', $databasesToBackup); | ||||
|                     $databasesToBackup = array_map('trim', $databasesToBackup); | ||||
|                 } else if (str($databaseType)->contains('mariadb')) { | ||||
|                 } elseif (str($databaseType)->contains('mariadb')) { | ||||
|                     // Format: db1,db2,db3
 | ||||
|                     $databasesToBackup = explode(',', $databasesToBackup); | ||||
|                     $databasesToBackup = array_map('trim', $databasesToBackup); | ||||
| @ -223,28 +236,28 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted | ||||
|                     return; | ||||
|                 } | ||||
|             } | ||||
|             $this->backup_dir = backup_dir() . "/databases/" . Str::of($this->team->name)->slug() . '-' . $this->team->id . '/' . $this->directory_name; | ||||
|             $this->backup_dir = backup_dir().'/databases/'.Str::of($this->team->name)->slug().'-'.$this->team->id.'/'.$this->directory_name; | ||||
| 
 | ||||
|             if ($this->database->name === 'coolify-db') { | ||||
|                 $databasesToBackup = ['coolify']; | ||||
|                 $this->directory_name = $this->container_name = "coolify-db"; | ||||
|                 $this->directory_name = $this->container_name = 'coolify-db'; | ||||
|                 $ip = Str::slug($this->server->ip); | ||||
|                 $this->backup_dir = backup_dir() . "/coolify" . "/coolify-db-$ip"; | ||||
|                 $this->backup_dir = backup_dir().'/coolify'."/coolify-db-$ip"; | ||||
|             } | ||||
|             foreach ($databasesToBackup as $database) { | ||||
|                 $size = 0; | ||||
|                 ray('Backing up ' . $database); | ||||
|                 ray('Backing up '.$database); | ||||
|                 try { | ||||
|                     if (str($databaseType)->contains('postgres')) { | ||||
|                         $this->backup_file = "/pg-dump-$database-" . Carbon::now()->timestamp . ".dmp"; | ||||
|                         $this->backup_location = $this->backup_dir . $this->backup_file; | ||||
|                         $this->backup_file = "/pg-dump-$database-".Carbon::now()->timestamp.'.dmp'; | ||||
|                         $this->backup_location = $this->backup_dir.$this->backup_file; | ||||
|                         $this->backup_log = ScheduledDatabaseBackupExecution::create([ | ||||
|                             'database_name' => $database, | ||||
|                             'filename' => $this->backup_location, | ||||
|                             'scheduled_database_backup_id' => $this->backup->id, | ||||
|                         ]); | ||||
|                         $this->backup_standalone_postgresql($database); | ||||
|                     } else if (str($databaseType)->contains('mongodb')) { | ||||
|                     } elseif (str($databaseType)->contains('mongodb')) { | ||||
|                         if ($database === '*') { | ||||
|                             $database = 'all'; | ||||
|                             $databaseName = 'all'; | ||||
| @ -255,26 +268,26 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted | ||||
|                                 $databaseName = $database; | ||||
|                             } | ||||
|                         } | ||||
|                         $this->backup_file = "/mongo-dump-$databaseName-" . Carbon::now()->timestamp . ".tar.gz"; | ||||
|                         $this->backup_location = $this->backup_dir . $this->backup_file; | ||||
|                         $this->backup_file = "/mongo-dump-$databaseName-".Carbon::now()->timestamp.'.tar.gz'; | ||||
|                         $this->backup_location = $this->backup_dir.$this->backup_file; | ||||
|                         $this->backup_log = ScheduledDatabaseBackupExecution::create([ | ||||
|                             'database_name' => $databaseName, | ||||
|                             'filename' => $this->backup_location, | ||||
|                             'scheduled_database_backup_id' => $this->backup->id, | ||||
|                         ]); | ||||
|                         $this->backup_standalone_mongodb($database); | ||||
|                     } else if (str($databaseType)->contains('mysql')) { | ||||
|                         $this->backup_file = "/mysql-dump-$database-" . Carbon::now()->timestamp . ".dmp"; | ||||
|                         $this->backup_location = $this->backup_dir . $this->backup_file; | ||||
|                     } elseif (str($databaseType)->contains('mysql')) { | ||||
|                         $this->backup_file = "/mysql-dump-$database-".Carbon::now()->timestamp.'.dmp'; | ||||
|                         $this->backup_location = $this->backup_dir.$this->backup_file; | ||||
|                         $this->backup_log = ScheduledDatabaseBackupExecution::create([ | ||||
|                             'database_name' => $database, | ||||
|                             'filename' => $this->backup_location, | ||||
|                             'scheduled_database_backup_id' => $this->backup->id, | ||||
|                         ]); | ||||
|                         $this->backup_standalone_mysql($database); | ||||
|                     } else if (str($databaseType)->contains('mariadb')) { | ||||
|                         $this->backup_file = "/mariadb-dump-$database-" . Carbon::now()->timestamp . ".dmp"; | ||||
|                         $this->backup_location = $this->backup_dir . $this->backup_file; | ||||
|                     } elseif (str($databaseType)->contains('mariadb')) { | ||||
|                         $this->backup_file = "/mariadb-dump-$database-".Carbon::now()->timestamp.'.dmp'; | ||||
|                         $this->backup_location = $this->backup_dir.$this->backup_file; | ||||
|                         $this->backup_log = ScheduledDatabaseBackupExecution::create([ | ||||
|                             'database_name' => $database, | ||||
|                             'filename' => $this->backup_location, | ||||
| @ -301,27 +314,28 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted | ||||
|                             'status' => 'failed', | ||||
|                             'message' => $this->backup_output, | ||||
|                             'size' => $size, | ||||
|                             'filename' => null | ||||
|                             'filename' => null, | ||||
|                         ]); | ||||
|                     } | ||||
|                     send_internal_notification('DatabaseBackupJob failed with: ' . $e->getMessage()); | ||||
|                     send_internal_notification('DatabaseBackupJob failed with: '.$e->getMessage()); | ||||
|                     $this->team?->notify(new BackupFailed($this->backup, $this->database, $this->backup_output, $database)); | ||||
|                 } | ||||
|             } | ||||
|         } catch (\Throwable $e) { | ||||
|             send_internal_notification('DatabaseBackupJob failed with: ' . $e->getMessage()); | ||||
|             send_internal_notification('DatabaseBackupJob failed with: '.$e->getMessage()); | ||||
|             throw $e; | ||||
|         } finally { | ||||
|             BackupCreated::dispatch($this->team->id); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private function backup_standalone_mongodb(string $databaseWithCollections): void | ||||
|     { | ||||
|         try { | ||||
|             ray($this->database->toArray()); | ||||
|             $url = $this->database->get_db_url(useInternal: true); | ||||
|             if ($databaseWithCollections === 'all') { | ||||
|                 $commands[] = "mkdir -p " . $this->backup_dir; | ||||
|                 $commands[] = 'mkdir -p '.$this->backup_dir; | ||||
|                 if (str($this->database->image)->startsWith('mongo:4.0')) { | ||||
|                     $commands[] = "docker exec $this->container_name mongodump --uri=$url --gzip --archive > $this->backup_location"; | ||||
|                 } else { | ||||
| @ -335,7 +349,7 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted | ||||
|                     $databaseName = $databaseWithCollections; | ||||
|                     $collectionsToExclude = collect(); | ||||
|                 } | ||||
|                 $commands[] = "mkdir -p " . $this->backup_dir; | ||||
|                 $commands[] = 'mkdir -p '.$this->backup_dir; | ||||
|                 if ($collectionsToExclude->count() === 0) { | ||||
|                     if (str($this->database->image)->startsWith('mongo:4.0')) { | ||||
|                         $commands[] = "docker exec $this->container_name mongodump --uri=$url --gzip --archive > $this->backup_location"; | ||||
| @ -344,9 +358,9 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted | ||||
|                     } | ||||
|                 } else { | ||||
|                     if (str($this->database->image)->startsWith('mongo:4.0')) { | ||||
|                         $commands[] = "docker exec $this->container_name mongodump --uri=$url --gzip --excludeCollection " . $collectionsToExclude->implode(' --excludeCollection ') . " --archive > $this->backup_location"; | ||||
|                         $commands[] = "docker exec $this->container_name mongodump --uri=$url --gzip --excludeCollection ".$collectionsToExclude->implode(' --excludeCollection ')." --archive > $this->backup_location"; | ||||
|                     } else { | ||||
|                         $commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --db $databaseName --gzip --excludeCollection " . $collectionsToExclude->implode(' --excludeCollection ') . " --archive > $this->backup_location"; | ||||
|                         $commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --db $databaseName --gzip --excludeCollection ".$collectionsToExclude->implode(' --excludeCollection ')." --archive > $this->backup_location"; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| @ -355,34 +369,36 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted | ||||
|             if ($this->backup_output === '') { | ||||
|                 $this->backup_output = null; | ||||
|             } | ||||
|             ray('Backup done for ' . $this->container_name . ' at ' . $this->server->name . ':' . $this->backup_location); | ||||
|             ray('Backup done for '.$this->container_name.' at '.$this->server->name.':'.$this->backup_location); | ||||
|         } catch (\Throwable $e) { | ||||
|             $this->add_to_backup_output($e->getMessage()); | ||||
|             ray('Backup failed for ' . $this->container_name . ' at ' . $this->server->name . ':' . $this->backup_location . '\n\nError:' . $e->getMessage()); | ||||
|             ray('Backup failed for '.$this->container_name.' at '.$this->server->name.':'.$this->backup_location.'\n\nError:'.$e->getMessage()); | ||||
|             throw $e; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private function backup_standalone_postgresql(string $database): void | ||||
|     { | ||||
|         try { | ||||
|             $commands[] = "mkdir -p " . $this->backup_dir; | ||||
|             $commands[] = 'mkdir -p '.$this->backup_dir; | ||||
|             $commands[] = "docker exec $this->container_name pg_dump --format=custom --no-acl --no-owner --username {$this->database->postgres_user} $database > $this->backup_location"; | ||||
|             $this->backup_output = instant_remote_process($commands, $this->server); | ||||
|             $this->backup_output = trim($this->backup_output); | ||||
|             if ($this->backup_output === '') { | ||||
|                 $this->backup_output = null; | ||||
|             } | ||||
|             ray('Backup done for ' . $this->container_name . ' at ' . $this->server->name . ':' . $this->backup_location); | ||||
|             ray('Backup done for '.$this->container_name.' at '.$this->server->name.':'.$this->backup_location); | ||||
|         } catch (\Throwable $e) { | ||||
|             $this->add_to_backup_output($e->getMessage()); | ||||
|             ray('Backup failed for ' . $this->container_name . ' at ' . $this->server->name . ':' . $this->backup_location . '\n\nError:' . $e->getMessage()); | ||||
|             ray('Backup failed for '.$this->container_name.' at '.$this->server->name.':'.$this->backup_location.'\n\nError:'.$e->getMessage()); | ||||
|             throw $e; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private function backup_standalone_mysql(string $database): void | ||||
|     { | ||||
|         try { | ||||
|             $commands[] = "mkdir -p " . $this->backup_dir; | ||||
|             $commands[] = 'mkdir -p '.$this->backup_dir; | ||||
|             $commands[] = "docker exec $this->container_name mysqldump -u root -p{$this->database->mysql_root_password} $database > $this->backup_location"; | ||||
|             ray($commands); | ||||
|             $this->backup_output = instant_remote_process($commands, $this->server); | ||||
| @ -390,17 +406,18 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted | ||||
|             if ($this->backup_output === '') { | ||||
|                 $this->backup_output = null; | ||||
|             } | ||||
|             ray('Backup done for ' . $this->container_name . ' at ' . $this->server->name . ':' . $this->backup_location); | ||||
|             ray('Backup done for '.$this->container_name.' at '.$this->server->name.':'.$this->backup_location); | ||||
|         } catch (\Throwable $e) { | ||||
|             $this->add_to_backup_output($e->getMessage()); | ||||
|             ray('Backup failed for ' . $this->container_name . ' at ' . $this->server->name . ':' . $this->backup_location . '\n\nError:' . $e->getMessage()); | ||||
|             ray('Backup failed for '.$this->container_name.' at '.$this->server->name.':'.$this->backup_location.'\n\nError:'.$e->getMessage()); | ||||
|             throw $e; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private function backup_standalone_mariadb(string $database): void | ||||
|     { | ||||
|         try { | ||||
|             $commands[] = "mkdir -p " . $this->backup_dir; | ||||
|             $commands[] = 'mkdir -p '.$this->backup_dir; | ||||
|             $commands[] = "docker exec $this->container_name mariadb-dump -u root -p{$this->database->mariadb_root_password} $database > $this->backup_location"; | ||||
|             ray($commands); | ||||
|             $this->backup_output = instant_remote_process($commands, $this->server); | ||||
| @ -408,17 +425,18 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted | ||||
|             if ($this->backup_output === '') { | ||||
|                 $this->backup_output = null; | ||||
|             } | ||||
|             ray('Backup done for ' . $this->container_name . ' at ' . $this->server->name . ':' . $this->backup_location); | ||||
|             ray('Backup done for '.$this->container_name.' at '.$this->server->name.':'.$this->backup_location); | ||||
|         } catch (\Throwable $e) { | ||||
|             $this->add_to_backup_output($e->getMessage()); | ||||
|             ray('Backup failed for ' . $this->container_name . ' at ' . $this->server->name . ':' . $this->backup_location . '\n\nError:' . $e->getMessage()); | ||||
|             ray('Backup failed for '.$this->container_name.' at '.$this->server->name.':'.$this->backup_location.'\n\nError:'.$e->getMessage()); | ||||
|             throw $e; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private function add_to_backup_output($output): void | ||||
|     { | ||||
|         if ($this->backup_output) { | ||||
|             $this->backup_output = $this->backup_output . "\n" . $output; | ||||
|             $this->backup_output = $this->backup_output."\n".$output; | ||||
|         } else { | ||||
|             $this->backup_output = $output; | ||||
|         } | ||||
| @ -464,7 +482,7 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted | ||||
|             $commands[] = "docker exec backup-of-{$this->backup->uuid} mc cp $this->backup_location temporary/$bucket{$this->backup_dir}/"; | ||||
|             instant_remote_process($commands, $this->server); | ||||
|             $this->add_to_backup_output('Uploaded to S3.'); | ||||
|             ray('Uploaded to S3. ' . $this->backup_location . ' to s3://' . $bucket . $this->backup_dir); | ||||
|             ray('Uploaded to S3. '.$this->backup_location.' to s3://'.$bucket.$this->backup_dir); | ||||
|         } catch (\Throwable $e) { | ||||
|             $this->add_to_backup_output($e->getMessage()); | ||||
|             throw $e; | ||||
|  | ||||
| @ -3,19 +3,16 @@ | ||||
| namespace App\Jobs; | ||||
| 
 | ||||
| use App\Models\ScheduledDatabaseBackup; | ||||
| use App\Models\Server; | ||||
| use App\Models\Team; | ||||
| use App\Notifications\Database\DailyBackup; | ||||
| use App\Notifications\Server\HighDiskUsage; | ||||
| use Illuminate\Bus\Queueable; | ||||
| use Illuminate\Contracts\Queue\ShouldBeEncrypted; | ||||
| use Illuminate\Contracts\Queue\ShouldQueue; | ||||
| use Illuminate\Foundation\Bus\Dispatchable; | ||||
| use Illuminate\Queue\InteractsWithQueue; | ||||
| use Illuminate\Queue\Middleware\WithoutOverlapping; | ||||
| use Illuminate\Queue\SerializesModels; | ||||
| 
 | ||||
| class DatabaseBackupStatusJob implements ShouldQueue, ShouldBeEncrypted | ||||
| class DatabaseBackupStatusJob implements ShouldBeEncrypted, ShouldQueue | ||||
| { | ||||
|     use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; | ||||
| 
 | ||||
| @ -42,13 +39,6 @@ class DatabaseBackupStatusJob implements ShouldQueue, ShouldBeEncrypted | ||||
|         //     }
 | ||||
|         // }
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|         // $scheduled_backups = ScheduledDatabaseBackup::all();
 | ||||
|         // $databases = collect();
 | ||||
|         // $teams = collect();
 | ||||
|  | ||||
| @ -24,7 +24,7 @@ use Illuminate\Queue\InteractsWithQueue; | ||||
| use Illuminate\Queue\SerializesModels; | ||||
| use Illuminate\Support\Facades\Artisan; | ||||
| 
 | ||||
| class DeleteResourceJob implements ShouldQueue, ShouldBeEncrypted | ||||
| class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue | ||||
| { | ||||
|     use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; | ||||
| 
 | ||||
| @ -60,7 +60,7 @@ class DeleteResourceJob implements ShouldQueue, ShouldBeEncrypted | ||||
|             } | ||||
|         } catch (\Throwable $e) { | ||||
|             ray($e->getMessage()); | ||||
|             send_internal_notification('ContainerStoppingJob failed with: ' . $e->getMessage()); | ||||
|             send_internal_notification('ContainerStoppingJob failed with: '.$e->getMessage()); | ||||
|             throw $e; | ||||
|         } finally { | ||||
|             Artisan::queue('cleanup:stucked-resources'); | ||||
|  | ||||
| @ -10,21 +10,22 @@ use Illuminate\Contracts\Queue\ShouldBeEncrypted; | ||||
| use Illuminate\Contracts\Queue\ShouldQueue; | ||||
| use Illuminate\Foundation\Bus\Dispatchable; | ||||
| use Illuminate\Queue\InteractsWithQueue; | ||||
| use Illuminate\Queue\Middleware\WithoutOverlapping; | ||||
| use Illuminate\Queue\SerializesModels; | ||||
| use Illuminate\Support\Facades\Log; | ||||
| use RuntimeException; | ||||
| 
 | ||||
| class DockerCleanupJob implements ShouldQueue, ShouldBeEncrypted | ||||
| class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue | ||||
| { | ||||
|     use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; | ||||
| 
 | ||||
|     public $timeout = 300; | ||||
| 
 | ||||
|     public ?int $usageBefore = null; | ||||
| 
 | ||||
|     public function __construct(public Server $server) | ||||
|     { | ||||
|     } | ||||
| 
 | ||||
|     public function handle(): void | ||||
|     { | ||||
|         try { | ||||
| @ -32,35 +33,36 @@ class DockerCleanupJob implements ShouldQueue, ShouldBeEncrypted | ||||
|             $this->server->applications()->each(function ($application) use (&$isInprogress) { | ||||
|                 if ($application->isDeploymentInprogress()) { | ||||
|                     $isInprogress = true; | ||||
| 
 | ||||
|                     return; | ||||
|                 } | ||||
|             }); | ||||
|             if ($isInprogress) { | ||||
|                 throw new RuntimeException('DockerCleanupJob: ApplicationDeploymentQueue is not empty, skipping...'); | ||||
|             } | ||||
|             if (!$this->server->isFunctional()) { | ||||
|             if (! $this->server->isFunctional()) { | ||||
|                 return; | ||||
|             } | ||||
|             $this->usageBefore = $this->server->getDiskUsage(); | ||||
|             ray('Usage before: ' . $this->usageBefore); | ||||
|             ray('Usage before: '.$this->usageBefore); | ||||
|             if ($this->usageBefore >= $this->server->settings->cleanup_after_percentage) { | ||||
|                 ray('Cleaning up ' . $this->server->name); | ||||
|                 ray('Cleaning up '.$this->server->name); | ||||
|                 CleanupDocker::run($this->server); | ||||
|                 $usageAfter = $this->server->getDiskUsage(); | ||||
|                 if ($usageAfter < $this->usageBefore) { | ||||
|                     $this->server->team?->notify(new DockerCleanup($this->server, 'Saved ' . ($this->usageBefore - $usageAfter) . '% disk space.')); | ||||
|                     $this->server->team?->notify(new DockerCleanup($this->server, 'Saved '.($this->usageBefore - $usageAfter).'% disk space.')); | ||||
|                     // ray('Saved ' . ($this->usageBefore - $usageAfter) . '% disk space on ' . $this->server->name);
 | ||||
|                     // send_internal_notification('DockerCleanupJob done: Saved ' . ($this->usageBefore - $usageAfter) . '% disk space on ' . $this->server->name);
 | ||||
|                     Log::info('DockerCleanupJob done: Saved ' . ($this->usageBefore - $usageAfter) . '% disk space on ' . $this->server->name); | ||||
|                     Log::info('DockerCleanupJob done: Saved '.($this->usageBefore - $usageAfter).'% disk space on '.$this->server->name); | ||||
|                 } else { | ||||
|                     Log::info('DockerCleanupJob failed to save disk space on ' . $this->server->name); | ||||
|                     Log::info('DockerCleanupJob failed to save disk space on '.$this->server->name); | ||||
|                 } | ||||
|             } else { | ||||
|                 ray('No need to clean up ' . $this->server->name); | ||||
|                 Log::info('No need to clean up ' . $this->server->name); | ||||
|                 ray('No need to clean up '.$this->server->name); | ||||
|                 Log::info('No need to clean up '.$this->server->name); | ||||
|             } | ||||
|         } catch (\Throwable $e) { | ||||
|             send_internal_notification('DockerCleanupJob failed with: ' . $e->getMessage()); | ||||
|             send_internal_notification('DockerCleanupJob failed with: '.$e->getMessage()); | ||||
|             ray($e->getMessage()); | ||||
|             throw $e; | ||||
|         } | ||||
|  | ||||
| @ -12,18 +12,21 @@ use Illuminate\Queue\Middleware\WithoutOverlapping; | ||||
| use Illuminate\Queue\SerializesModels; | ||||
| use Illuminate\Support\Facades\Http; | ||||
| 
 | ||||
| class GithubAppPermissionJob implements ShouldQueue, ShouldBeEncrypted | ||||
| class GithubAppPermissionJob implements ShouldBeEncrypted, ShouldQueue | ||||
| { | ||||
|     use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; | ||||
| 
 | ||||
|     public $tries = 4; | ||||
| 
 | ||||
|     public function backoff(): int | ||||
|     { | ||||
|         return isDev() ? 1 : 3; | ||||
|     } | ||||
| 
 | ||||
|     public function __construct(public GithubApp $github_app) | ||||
|     { | ||||
|     } | ||||
| 
 | ||||
|     public function middleware(): array | ||||
|     { | ||||
|         return [(new WithoutOverlapping($this->github_app->uuid))]; | ||||
| @ -40,7 +43,7 @@ class GithubAppPermissionJob implements ShouldQueue, ShouldBeEncrypted | ||||
|             $github_access_token = generate_github_jwt_token($this->github_app); | ||||
|             $response = Http::withHeaders([ | ||||
|                 'Authorization' => "Bearer $github_access_token", | ||||
|                 'Accept' => 'application/vnd.github+json' | ||||
|                 'Accept' => 'application/vnd.github+json', | ||||
|             ])->get("{$this->github_app->api_url}/app"); | ||||
|             $response = $response->json(); | ||||
|             $permissions = data_get($response, 'permissions'); | ||||
| @ -51,7 +54,7 @@ class GithubAppPermissionJob implements ShouldQueue, ShouldBeEncrypted | ||||
|             $this->github_app->save(); | ||||
|             $this->github_app->makeVisible('client_secret')->makeVisible('webhook_secret'); | ||||
|         } catch (\Throwable $e) { | ||||
|             send_internal_notification('GithubAppPermissionJob failed with: ' . $e->getMessage()); | ||||
|             send_internal_notification('GithubAppPermissionJob failed with: '.$e->getMessage()); | ||||
|             ray($e->getMessage()); | ||||
|             throw $e; | ||||
|         } | ||||
|  | ||||
| @ -11,11 +11,12 @@ use Illuminate\Foundation\Bus\Dispatchable; | ||||
| use Illuminate\Queue\InteractsWithQueue; | ||||
| use Illuminate\Queue\SerializesModels; | ||||
| 
 | ||||
| class InstanceAutoUpdateJob implements ShouldQueue, ShouldBeUnique, ShouldBeEncrypted | ||||
| class InstanceAutoUpdateJob implements ShouldBeEncrypted, ShouldBeUnique, ShouldQueue | ||||
| { | ||||
|     use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; | ||||
| 
 | ||||
|     public $timeout = 600; | ||||
| 
 | ||||
|     public $tries = 1; | ||||
| 
 | ||||
|     public function __construct() | ||||
|  | ||||
| @ -13,7 +13,7 @@ use Illuminate\Queue\SerializesModels; | ||||
| use Illuminate\Support\Facades\File; | ||||
| use Illuminate\Support\Facades\Http; | ||||
| 
 | ||||
| class PullCoolifyImageJob implements ShouldQueue, ShouldBeEncrypted | ||||
| class PullCoolifyImageJob implements ShouldBeEncrypted, ShouldQueue | ||||
| { | ||||
|     use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; | ||||
| 
 | ||||
| @ -22,6 +22,7 @@ class PullCoolifyImageJob implements ShouldQueue, ShouldBeEncrypted | ||||
|     public function __construct() | ||||
|     { | ||||
|     } | ||||
| 
 | ||||
|     public function handle(): void | ||||
|     { | ||||
|         try { | ||||
| @ -39,7 +40,7 @@ class PullCoolifyImageJob implements ShouldQueue, ShouldBeEncrypted | ||||
| 
 | ||||
|             $settings = InstanceSettings::get(); | ||||
|             $current_version = config('version'); | ||||
|             if (!$settings->is_auto_update_enabled) { | ||||
|             if (! $settings->is_auto_update_enabled) { | ||||
|                 return; | ||||
|             } | ||||
|             if ($latest_version === $current_version) { | ||||
| @ -49,8 +50,8 @@ class PullCoolifyImageJob implements ShouldQueue, ShouldBeEncrypted | ||||
|                 return; | ||||
|             } | ||||
|             instant_remote_process([ | ||||
|                 "curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh -o /data/coolify/source/upgrade.sh", | ||||
|                 "bash /data/coolify/source/upgrade.sh $latest_version" | ||||
|                 'curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh -o /data/coolify/source/upgrade.sh', | ||||
|                 "bash /data/coolify/source/upgrade.sh $latest_version", | ||||
|             ], $server); | ||||
|         } catch (\Throwable $e) { | ||||
|             throw $e; | ||||
|  | ||||
| @ -11,7 +11,7 @@ use Illuminate\Queue\InteractsWithQueue; | ||||
| use Illuminate\Queue\Middleware\WithoutOverlapping; | ||||
| use Illuminate\Queue\SerializesModels; | ||||
| 
 | ||||
| class PullHelperImageJob implements ShouldQueue, ShouldBeEncrypted | ||||
| class PullHelperImageJob implements ShouldBeEncrypted, ShouldQueue | ||||
| { | ||||
|     use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; | ||||
| 
 | ||||
| @ -26,9 +26,11 @@ class PullHelperImageJob implements ShouldQueue, ShouldBeEncrypted | ||||
|     { | ||||
|         return $this->server->uuid; | ||||
|     } | ||||
| 
 | ||||
|     public function __construct(public Server $server) | ||||
|     { | ||||
|     } | ||||
| 
 | ||||
|     public function handle(): void | ||||
|     { | ||||
|         try { | ||||
| @ -37,7 +39,7 @@ class PullHelperImageJob implements ShouldQueue, ShouldBeEncrypted | ||||
|             instant_remote_process(["docker pull -q {$helperImage}"], $this->server, false); | ||||
|             ray('PullHelperImageJob done'); | ||||
|         } catch (\Throwable $e) { | ||||
|             send_internal_notification('PullHelperImageJob failed with: ' . $e->getMessage()); | ||||
|             send_internal_notification('PullHelperImageJob failed with: '.$e->getMessage()); | ||||
|             ray($e->getMessage()); | ||||
|             throw $e; | ||||
|         } | ||||
|  | ||||
| @ -12,7 +12,7 @@ use Illuminate\Queue\InteractsWithQueue; | ||||
| use Illuminate\Queue\Middleware\WithoutOverlapping; | ||||
| use Illuminate\Queue\SerializesModels; | ||||
| 
 | ||||
| class PullSentinelImageJob implements ShouldQueue, ShouldBeEncrypted | ||||
| class PullSentinelImageJob implements ShouldBeEncrypted, ShouldQueue | ||||
| { | ||||
|     use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; | ||||
| 
 | ||||
| @ -27,15 +27,18 @@ class PullSentinelImageJob implements ShouldQueue, ShouldBeEncrypted | ||||
|     { | ||||
|         return $this->server->uuid; | ||||
|     } | ||||
| 
 | ||||
|     public function __construct(public Server $server) | ||||
|     { | ||||
|     } | ||||
| 
 | ||||
|     public function handle(): void | ||||
|     { | ||||
|         try { | ||||
|             $version = get_latest_sentinel_version(); | ||||
|             if (!$version) { | ||||
|             if (! $version) { | ||||
|                 ray('Failed to get latest Sentinel version'); | ||||
| 
 | ||||
|                 return; | ||||
|             } | ||||
|             $local_version = instant_remote_process(['docker exec coolify-sentinel sh -c "curl http://127.0.0.1:8888/api/version"'], $this->server, false); | ||||
| @ -44,11 +47,12 @@ class PullSentinelImageJob implements ShouldQueue, ShouldBeEncrypted | ||||
|             } | ||||
|             if (version_compare($local_version, $version, '<')) { | ||||
|                 StartSentinel::run($this->server, $version, true); | ||||
| 
 | ||||
|                 return; | ||||
|             } | ||||
|             ray('Sentinel image is up to date'); | ||||
|         } catch (\Throwable $e) { | ||||
|             send_internal_notification('PullSentinelImageJob failed with: ' . $e->getMessage()); | ||||
|             send_internal_notification('PullSentinelImageJob failed with: '.$e->getMessage()); | ||||
|             ray($e->getMessage()); | ||||
|             throw $e; | ||||
|         } | ||||
|  | ||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user