diff --git a/.github/workflows/coolify-helper-next.yml b/.github/workflows/coolify-helper-next.yml index fe14ba759..d9921b363 100644 --- a/.github/workflows/coolify-helper-next.yml +++ b/.github/workflows/coolify-helper-next.yml @@ -18,15 +18,15 @@ jobs: contents: read packages: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Login to ghcr.io - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build image and push to registry - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v5 with: no-cache: true context: . @@ -40,15 +40,15 @@ jobs: contents: read packages: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Login to ghcr.io - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build image and push to registry - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v5 with: no-cache: true context: . @@ -64,13 +64,13 @@ jobs: needs: [ amd64, aarch64 ] steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 - name: Login to ghcr.io - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} diff --git a/.github/workflows/coolify-helper.yml b/.github/workflows/coolify-helper.yml index d174bfaf9..7e8132ec6 100644 --- a/.github/workflows/coolify-helper.yml +++ b/.github/workflows/coolify-helper.yml @@ -18,15 +18,15 @@ jobs: contents: read packages: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Login to ghcr.io - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build image and push to registry - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v5 with: no-cache: true context: . @@ -40,15 +40,15 @@ jobs: contents: read packages: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Login to ghcr.io - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build image and push to registry - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v5 with: no-cache: true context: . @@ -64,13 +64,13 @@ jobs: needs: [ amd64, aarch64 ] steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 - name: Login to ghcr.io - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} diff --git a/.github/workflows/coolify-testing-host.yml b/.github/workflows/coolify-testing-host.yml index a40dfd285..5fdc32991 100644 --- a/.github/workflows/coolify-testing-host.yml +++ b/.github/workflows/coolify-testing-host.yml @@ -18,15 +18,15 @@ jobs: contents: read packages: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Login to ghcr.io - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build image and push to registry - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v5 with: no-cache: true context: . @@ -40,15 +40,15 @@ jobs: contents: read packages: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Login to ghcr.io - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build image and push to registry - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v5 with: no-cache: true context: . @@ -64,13 +64,13 @@ jobs: needs: [ amd64, aarch64 ] steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 - name: Login to ghcr.io - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 36def4911..0edaa4f1c 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -14,7 +14,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Cache Docker layers uses: actions/cache@v2 with: diff --git a/CONTRIBUTION.md b/CONTRIBUTION.md index c6a5b3273..c139793ec 100644 --- a/CONTRIBUTION.md +++ b/CONTRIBUTION.md @@ -30,5 +30,5 @@ ### 4) Start development Mails are caught by Mailpit: `localhost:8025` ## New Service Contribution -Check out the docs [here](https://coolify.io/docs/how-to-add-a-service). +Check out the docs [here](https://coolify.io/docs/resources/services/add-service). diff --git a/README.md b/README.md index 3a211d4b1..db38eb142 100644 --- a/README.md +++ b/README.md @@ -28,12 +28,12 @@ # Donations Thank you so much! -Special thanks to our biggest sponsors, [CCCareers](https://cccareers.org/) and [Appwrite](https://appwrite.io)! +Special thanks to our biggest sponsor, [CCCareers](https://cccareers.org/)! cccareers logo -appwrite logo ## Github Sponsors ($40+) +American Cloud CryptoJobsList typebot BC Direct diff --git a/app/Actions/Proxy/CheckProxy.php b/app/Actions/Proxy/CheckProxy.php index 5a1ae56cf..32a4897a6 100644 --- a/app/Actions/Proxy/CheckProxy.php +++ b/app/Actions/Proxy/CheckProxy.php @@ -35,6 +35,9 @@ public function handle(Server $server, $fromUI = false) $server->save(); return false; } + if ($server->settings->is_cloudflare_tunnel) { + return false; + } $ip = $server->ip; if ($server->id === 0) { $ip = 'host.docker.internal'; diff --git a/app/Actions/Server/ConfigureCloudflared.php b/app/Actions/Server/ConfigureCloudflared.php new file mode 100644 index 000000000..6b08b3de6 --- /dev/null +++ b/app/Actions/Server/ConfigureCloudflared.php @@ -0,0 +1,44 @@ + [ + "coolify-cloudflared" => [ + "container_name" => "coolify-cloudflared", + "image" => "cloudflare/cloudflared:latest", + "restart" => RESTART_MODE, + "network_mode" => "host", + "command" => "tunnel run", + "environment" => [ + "TUNNEL_TOKEN={$cloudflare_token}", + ], + ], + ], + ]; + $config = Yaml::dump($config, 12, 2); + $docker_compose_yml_base64 = base64_encode($config); + $commands = collect([ + "mkdir -p /tmp/cloudflared && cd /tmp/cloudflared", + "echo '$docker_compose_yml_base64' | base64 -d > docker-compose.yml", + "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) { + ray($e); + throw $e; + } + } +} diff --git a/app/Actions/Server/InstallDocker.php b/app/Actions/Server/InstallDocker.php index 553a1c09d..1db766a88 100644 --- a/app/Actions/Server/InstallDocker.php +++ b/app/Actions/Server/InstallDocker.php @@ -13,7 +13,7 @@ public function handle(Server $server) { $supported_os_type = $server->validateOS(); if (!$supported_os_type) { - throw new \Exception('Server OS type is not supported for automated installation. Please install Docker manually before continuing: documentation.'); + throw new \Exception('Server OS type is not supported for automated installation. Please install Docker manually before continuing: documentation.'); } ray('Installing Docker on server: ' . $server->name . ' (' . $server->ip . ')' . ' with OS type: ' . $supported_os_type); $dockerVersion = '24.0'; diff --git a/app/Actions/Server/UpdateCoolify.php b/app/Actions/Server/UpdateCoolify.php index 09efb848e..6c01f75c9 100644 --- a/app/Actions/Server/UpdateCoolify.php +++ b/app/Actions/Server/UpdateCoolify.php @@ -12,10 +12,12 @@ class UpdateCoolify public ?Server $server = null; public ?string $latestVersion = null; public ?string $currentVersion = null; + public bool $async = false; - public function handle(bool $force) + public function handle(bool $force = false, bool $async = false) { try { + $this->async = $async; $settings = InstanceSettings::get(); ray('Running InstanceAutoUpdateJob'); $this->server = Server::find(0); @@ -56,17 +58,31 @@ private function update() { if (isDev()) { ray("Running update on local docker container. Updating to $this->latestVersion"); - remote_process([ - "sleep 10" - ], $this->server); + if ($this->async) { + ray('Running async update'); + remote_process([ + "sleep 10" + ], $this->server); + } else { + instant_remote_process([ + "sleep 10" + ], $this->server); + } ray('Update done'); return; } else { ray('Running update on production server'); - 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" - ], $this->server); + if ($this->async) { + 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" + ], $this->server); + } else { + instant_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" + ], $this->server); + } return; } } diff --git a/app/Console/Commands/Init.php b/app/Console/Commands/Init.php index 66a4f1aff..acdff5b35 100644 --- a/app/Console/Commands/Init.php +++ b/app/Console/Commands/Init.php @@ -34,11 +34,13 @@ public function handle() $this->cleanup_stucked_helper_containers(); $this->call('cleanup:queue'); $this->call('cleanup:stucked-resources'); - try { - $server = Server::find(0)->first(); - $server->setupDynamicProxyConfiguration(); - } catch (\Throwable $e) { - echo "Could not setup dynamic configuration: {$e->getMessage()}\n"; + if (!isCloud()) { + try { + $server = Server::find(0)->first(); + $server->setupDynamicProxyConfiguration(); + } catch (\Throwable $e) { + echo "Could not setup dynamic configuration: {$e->getMessage()}\n"; + } } $settings = InstanceSettings::get(); diff --git a/app/Http/Controllers/Api/Deploy.php b/app/Http/Controllers/Api/Deploy.php index 89a994afe..5469ba1c6 100644 --- a/app/Http/Controllers/Api/Deploy.php +++ b/app/Http/Controllers/Api/Deploy.php @@ -44,7 +44,7 @@ public function deploy(Request $request) $force = $request->query->get('force') ?? false; if ($uuids && $tags) { - return response()->json(['error' => 'You can only use uuid or tag, not both.', 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 400); + return response()->json(['error' => 'You can only use uuid or tag, not both.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 400); } if (is_null($teamId)) { return invalid_token(); @@ -54,7 +54,7 @@ public function deploy(Request $request) } else if ($uuids) { return $this->by_uuids($uuids, $teamId, $force); } - return response()->json(['error' => 'You must provide uuid or tag.', 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 400); + 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) { @@ -62,7 +62,7 @@ private function by_uuids(string $uuid, int $teamId, bool $force = false) $uuids = collect(array_filter($uuids)); if (count($uuids) === 0) { - return response()->json(['error' => 'No UUIDs provided.', 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 400); + return response()->json(['error' => 'No UUIDs provided.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 400); } $deployments = collect(); $payload = collect(); @@ -81,7 +81,7 @@ private function by_uuids(string $uuid, int $teamId, bool $force = false) $payload->put('deployments', $deployments->toArray()); return response()->json($payload->toArray(), 200); } - return response()->json(['error' => "No resources found.", 'docs' => 'https://coolify.io/docs/api/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) { @@ -89,7 +89,7 @@ public function by_tags(string $tags, int $team_id, bool $force = false) $tags = collect(array_filter($tags)); if (count($tags) === 0) { - return response()->json(['error' => 'No TAGs provided.', 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 400); + return response()->json(['error' => 'No TAGs provided.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 400); } $message = collect([]); $deployments = collect(); @@ -127,7 +127,7 @@ public function by_tags(string $tags, int $team_id, bool $force = false) return response()->json($payload->toArray(), 200); } - return response()->json(['error' => "No resources found with this tag.", 'docs' => 'https://coolify.io/docs/api/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 { diff --git a/app/Http/Controllers/Api/Team.php b/app/Http/Controllers/Api/Team.php index 453c2590f..d5b1f6209 100644 --- a/app/Http/Controllers/Api/Team.php +++ b/app/Http/Controllers/Api/Team.php @@ -26,7 +26,7 @@ public function team_by_id(Request $request) $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/team-by-id"], 404); + return response()->json(['error' => 'Team not found.', "docs" => "https://coolify.io/docs/api-reference/get-team-by-teamid"], 404); } return response()->json($team); } @@ -40,7 +40,7 @@ public function members_by_id(Request $request) $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/team-by-id-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); } diff --git a/app/Http/Controllers/Webhook/Github.php b/app/Http/Controllers/Webhook/Github.php index 02ba017ca..214843aab 100644 --- a/app/Http/Controllers/Webhook/Github.php +++ b/app/Http/Controllers/Webhook/Github.php @@ -22,7 +22,6 @@ class Github extends Controller public function manual(Request $request) { try { - ray($request); $return_payloads = collect([]); $x_github_delivery = request()->header('X-GitHub-Delivery'); if (app()->isDownForMaintenance()) { @@ -68,6 +67,10 @@ public function manual(Request $request) if (Str::isMatch('/refs\/heads\/*/', $branch)) { $branch = Str::after($branch, 'refs/heads/'); } + $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 GitHub Push Event with branch: ' . $branch); } if ($x_github_event === 'pull_request') { @@ -118,24 +121,41 @@ public function manual(Request $request) } if ($x_github_event === 'push') { if ($application->isDeployable()) { - ray('Deploying ' . $application->name . ' with branch ' . $branch); - $deployment_uuid = new Cuid2(7); - queue_application_deployment( - application: $application, - deployment_uuid: $deployment_uuid, - force_rebuild: false, - is_webhook: true, - ); - $return_payloads->push([ - 'application' => $application->name, - 'status' => 'success', - 'message' => 'Deployment queued.', - ]); + $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); + $deployment_uuid = new Cuid2(7); + queue_application_deployment( + application: $application, + deployment_uuid: $deployment_uuid, + force_rebuild: false, + is_webhook: true, + ); + $return_payloads->push([ + 'status' => 'success', + 'message' => 'Deployment queued.', + 'application_uuid' => $application->uuid, + 'application_name' => $application->name, + ]); + } else { + $paths = str($application->watch_paths)->explode("\n"); + $return_payloads->push([ + 'status' => 'failed', + 'message' => 'Changed files do not match watch paths. Ignoring deployment.', + 'application_uuid' => $application->uuid, + 'application_name' => $application->name, + 'details' => [ + 'changed_files' => $changed_files, + 'watch_paths' => $paths, + ], + ]); + } } else { $return_payloads->push([ - 'application' => $application->name, 'status' => 'failed', 'message' => 'Deployments disabled.', + 'application_uuid' => $application->uuid, + 'application_name' => $application->name, ]); } } @@ -266,6 +286,10 @@ public function normal(Request $request) if (Str::isMatch('/refs\/heads\/*/', $branch)) { $branch = Str::after($branch, 'refs/heads/'); } + $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('Webhook GitHub Push Event: ' . $id . ' with branch: ' . $branch); } if ($x_github_event === 'pull_request') { @@ -298,32 +322,50 @@ public function normal(Request $request) $isFunctional = $application->destination->server->isFunctional(); if (!$isFunctional) { $return_payloads->push([ - 'application' => $application->name, 'status' => 'failed', 'message' => 'Server is not functional.', + 'application_uuid' => $application->uuid, + 'application_name' => $application->name, ]); continue; } if ($x_github_event === 'push') { if ($application->isDeployable()) { - ray('Deploying ' . $application->name . ' with branch ' . $branch); - $deployment_uuid = new Cuid2(7); - queue_application_deployment( - application: $application, - deployment_uuid: $deployment_uuid, - force_rebuild: false, - is_webhook: true - ); - $return_payloads->push([ - 'application' => $application->name, - 'status' => 'success', - 'message' => 'Deployment queued.', - ]); + $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); + $deployment_uuid = new Cuid2(7); + queue_application_deployment( + application: $application, + deployment_uuid: $deployment_uuid, + force_rebuild: false, + is_webhook: true, + ); + $return_payloads->push([ + 'status' => 'success', + 'message' => 'Deployment queued.', + 'application_uuid' => $application->uuid, + 'application_name' => $application->name, + ]); + } else { + $paths = str($application->watch_paths)->explode("\n"); + $return_payloads->push([ + 'status' => 'failed', + 'message' => 'Changed files do not match watch paths. Ignoring deployment.', + 'application_uuid' => $application->uuid, + 'application_name' => $application->name, + 'details' => [ + 'changed_files' => $changed_files, + 'watch_paths' => $paths, + ], + ]); + } } else { $return_payloads->push([ - 'application' => $application->name, 'status' => 'failed', 'message' => 'Deployments disabled.', + 'application_uuid' => $application->uuid, + 'application_name' => $application->name, ]); } } diff --git a/app/Http/Controllers/Webhook/Gitlab.php b/app/Http/Controllers/Webhook/Gitlab.php index 5b2911e88..65ce9910b 100644 --- a/app/Http/Controllers/Webhook/Gitlab.php +++ b/app/Http/Controllers/Webhook/Gitlab.php @@ -51,6 +51,10 @@ public function manual(Request $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); } if ($x_gitlab_event === 'merge_request') { @@ -113,19 +117,41 @@ public function manual(Request $request) } if ($x_gitlab_event === 'push') { if ($application->isDeployable()) { - ray('Deploying ' . $application->name . ' with branch ' . $branch); - $deployment_uuid = new Cuid2(7); - queue_application_deployment( - application: $application, - deployment_uuid: $deployment_uuid, - force_rebuild: false, - is_webhook: true - ); + $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); + $deployment_uuid = new Cuid2(7); + queue_application_deployment( + application: $application, + deployment_uuid: $deployment_uuid, + force_rebuild: false, + is_webhook: true, + ); + $return_payloads->push([ + 'status' => 'success', + 'message' => 'Deployment queued.', + 'application_uuid' => $application->uuid, + 'application_name' => $application->name, + ]); + } else { + $paths = str($application->watch_paths)->explode("\n"); + $return_payloads->push([ + 'status' => 'failed', + 'message' => 'Changed files do not match watch paths. Ignoring deployment.', + 'application_uuid' => $application->uuid, + 'application_name' => $application->name, + 'details' => [ + 'changed_files' => $changed_files, + 'watch_paths' => $paths, + ], + ]); + } } else { $return_payloads->push([ - 'application' => $application->name, 'status' => 'failed', 'message' => 'Deployments disabled', + 'application_uuid' => $application->uuid, + 'application_name' => $application->name, ]); ray('Deployments disabled for ' . $application->name); } diff --git a/app/Http/Livewire/Server/New/ByIp.php b/app/Http/Livewire/Server/New/ByIp.php deleted file mode 100644 index e69de29bb..000000000 diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index ef210f16b..51c024af6 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -234,7 +234,7 @@ public function handle(): void $this->build_server = $this->server; $this->original_server = $this->server; } - if ($this->restart_only && $this->application->build_pack !== 'dockerimage') { + if ($this->restart_only && $this->application->build_pack !== 'dockerimage' && $this->application->build_pack !== 'dockerfile') { $this->just_restart(); if ($this->server->isProxyShouldRun()) { dispatch(new ContainerStatusJob($this->server)); @@ -326,17 +326,19 @@ private function deploy_simple_dockerfile() ], ); $this->generate_image_names(); - if (!$this->force_rebuild) { - $this->check_image_locally_or_remotely(); - if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) { - $this->create_workdir(); - $this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped."); - $this->generate_compose_file(); - $this->push_to_docker_registry(); - $this->rolling_update(); - return; - } - } + + // Always rebuild dockerfile based container. + // if (!$this->force_rebuild) { + // $this->check_image_locally_or_remotely(); + // if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) { + // $this->create_workdir(); + // $this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped."); + // $this->generate_compose_file(); + // $this->push_to_docker_registry(); + // $this->rolling_update(); + // return; + // } + // } $this->generate_compose_file(); $this->generate_build_env_variables(); $this->add_build_env_variables_to_dockerfile(); @@ -737,7 +739,7 @@ private function framework_based_notification() $nixpacks_php_root_dir = $this->application->environment_variables_preview->where('key', 'NIXPACKS_PHP_ROOT_DIR')->first(); } if ($nixpacks_php_fallback_path?->value === '/index.php' && $nixpacks_php_root_dir?->value === '/app/public' && $this->newVersionIsHealthy === false) { - $this->application_deployment_queue->addLogEntry("There was a change in how Laravel is deployed. Please update your environment variables to match the new deployment method. More details here: https://coolify.io/docs/frameworks/laravel#requirements", 'stderr'); + $this->application_deployment_queue->addLogEntry("There was a change in how Laravel is deployed. Please update your environment variables to match the new deployment method. More details here: https://coolify.io/docs/resources/laravel", 'stderr'); } } private function rolling_update() diff --git a/app/Jobs/ContainerStatusJob.php b/app/Jobs/ContainerStatusJob.php index b40826f97..333d46027 100644 --- a/app/Jobs/ContainerStatusJob.php +++ b/app/Jobs/ContainerStatusJob.php @@ -170,16 +170,13 @@ public function handle() })->first(); if (!$foundTcpProxy) { StartDatabaseProxy::run($service_db); - $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$service_db->service->name}", $this->server)); + // $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$service_db->service->name}", $this->server)); } } } } } else { $database = $databases->where('uuid', $uuid)->first(); - if ($uuid == 'postgresql') { - ray($database); - } if ($database) { $isPublic = data_get($database, 'is_public'); $foundDatabases[] = $database->id; @@ -187,7 +184,6 @@ public function handle() if ($statusFromDb !== $containerStatus) { $database->update(['status' => $containerStatus]); } - ray($database); if ($isPublic) { $foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) { if ($this->server->isSwarm()) { @@ -197,7 +193,6 @@ public function handle() } })->first(); if (!$foundTcpProxy) { - ray('asdffff'); StartDatabaseProxy::run($database); $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server)); } diff --git a/app/Jobs/InstanceAutoUpdateJob.php b/app/Jobs/InstanceAutoUpdateJob.php index 99e0a34f3..fa5c29421 100644 --- a/app/Jobs/InstanceAutoUpdateJob.php +++ b/app/Jobs/InstanceAutoUpdateJob.php @@ -23,6 +23,6 @@ public function __construct(private bool $force = false) public function handle(): void { - UpdateCoolify::run($this->force); + UpdateCoolify::run(force: $this->force, async: false); } } diff --git a/app/Livewire/Admin/Index.php b/app/Livewire/Admin/Index.php index 60bd6f5ea..b72bc8e35 100644 --- a/app/Livewire/Admin/Index.php +++ b/app/Livewire/Admin/Index.php @@ -8,7 +8,31 @@ class Index extends Component { - public $users = []; + public $active_subscribers = []; + public $inactive_subscribers = []; + public $search = ''; + public function submitSearch() { + if ($this->search !== "") { + $this->inactive_subscribers = User::whereDoesntHave('teams', function ($query) { + $query->whereRelation('subscription', 'stripe_subscription_id', '!=', null); + })->where(function ($query) { + $query->where('name', 'like', "%{$this->search}%") + ->orWhere('email', 'like', "%{$this->search}%"); + })->get()->filter(function ($user) { + return $user->id !== 0; + }); + $this->active_subscribers = User::whereHas('teams', function ($query) { + $query->whereRelation('subscription', 'stripe_subscription_id', '!=', null); + })->where(function ($query) { + $query->where('name', 'like', "%{$this->search}%") + ->orWhere('email', 'like', "%{$this->search}%"); + })->get()->filter(function ($user) { + return $user->id !== 0; + }); + } else { + $this->getSubscribers(); + } + } public function mount() { if (!isCloud()) { @@ -17,7 +41,15 @@ public function mount() if (auth()->user()->id !== 0) { return redirect()->route('dashboard'); } - $this->users = User::whereHas('teams', function ($query) { + $this->getSubscribers(); + } + public function getSubscribers() { + $this->inactive_subscribers = User::whereDoesntHave('teams', function ($query) { + $query->whereRelation('subscription', 'stripe_subscription_id', '!=', null); + })->get()->filter(function ($user) { + return $user->id !== 0; + }); + $this->active_subscribers = User::whereHas('teams', function ($query) { $query->whereRelation('subscription', 'stripe_subscription_id', '!=', null); })->get()->filter(function ($user) { return $user->id !== 0; diff --git a/app/Livewire/Project/Application/General.php b/app/Livewire/Project/Application/General.php index 67f6c2115..c6b8f2e51 100644 --- a/app/Livewire/Project/Application/General.php +++ b/app/Livewire/Project/Application/General.php @@ -73,6 +73,7 @@ class General extends Component 'application.settings.is_static' => 'boolean|required', 'application.settings.is_raw_compose_deployment_enabled' => 'boolean|required', 'application.settings.is_build_server_enabled' => 'boolean|required', + 'application.watch_paths' => 'nullable', ]; protected $validationAttributes = [ 'application.name' => 'name', @@ -108,6 +109,7 @@ class General extends Component 'application.settings.is_static' => 'Is static', 'application.settings.is_raw_compose_deployment_enabled' => 'Is raw compose deployment enabled', 'application.settings.is_build_server_enabled' => 'Is build server enabled', + 'application.watch_paths' => 'Watch paths', ]; public function mount() { @@ -251,7 +253,7 @@ public function submit($showToaster = true) if ($this->application->additional_servers->count() === 0) { foreach ($domains as $domain) { if (!validate_dns_entry($domain, $this->application->destination->server)) { - $showToaster && $this->dispatch('error', "Validating DNS ($domain) failed.", "Make sure you have added the DNS records correctly.

Check this documentation for further help."); + $showToaster && $this->dispatch('error', "Validating DNS ($domain) failed.", "Make sure you have added the DNS records correctly.

Check this documentation for further help."); } } } diff --git a/app/Livewire/Project/Application/Heading.php b/app/Livewire/Project/Application/Heading.php index 3047f9f23..089eb5e9f 100644 --- a/app/Livewire/Project/Application/Heading.php +++ b/app/Livewire/Project/Application/Heading.php @@ -56,11 +56,11 @@ public function deploy(bool $force_rebuild = false) return; } if (data_get($this->application, 'settings.is_build_server_enabled') && str($this->application->docker_registry_image_name)->isEmpty()) { - $this->dispatch('error', 'Failed to deploy.', 'To use a build server, you must first set a Docker image.
More information here: documentation'); + $this->dispatch('error', 'Failed to deploy.', 'To use a build server, you must first set a Docker image.
More information here: documentation'); return; } if ($this->application->additional_servers->count() > 0 && str($this->application->docker_registry_image_name)->isEmpty()) { - $this->dispatch('error', 'Failed to deploy.', 'Before deploying to multiple servers, you must first set a Docker image in the General tab.
More information here: documentation'); + $this->dispatch('error', 'Failed to deploy.', 'Before deploying to multiple servers, you must first set a Docker image in the General tab.
More information here: documentation'); return; } $this->setDeploymentUuid(); @@ -99,7 +99,7 @@ public function stop() public function restart() { if ($this->application->additional_servers->count() > 0 && str($this->application->docker_registry_image_name)->isEmpty()) { - $this->dispatch('error', 'Failed to deploy', 'Before deploying to multiple servers, you must first set a Docker image in the General tab.
More information here: documentation'); + $this->dispatch('error', 'Failed to deploy', 'Before deploying to multiple servers, you must first set a Docker image in the General tab.
More information here: documentation'); return; } $this->setDeploymentUuid(); diff --git a/app/Livewire/Project/New/GithubPrivateRepository.php b/app/Livewire/Project/New/GithubPrivateRepository.php index 7d59cfb04..322fd4a4e 100644 --- a/app/Livewire/Project/New/GithubPrivateRepository.php +++ b/app/Livewire/Project/New/GithubPrivateRepository.php @@ -7,14 +7,12 @@ use App\Models\Project; use App\Models\StandaloneDocker; use App\Models\SwarmDocker; -use App\Traits\SaveFromRedirect; use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Route; use Livewire\Component; class GithubPrivateRepository extends Component { - use SaveFromRedirect; public $current_step = 'github_apps'; public $github_apps; public GithubApp $github_app; diff --git a/app/Livewire/Project/New/Select.php b/app/Livewire/Project/New/Select.php index 00a243624..2662cff09 100644 --- a/app/Livewire/Project/New/Select.php +++ b/app/Livewire/Project/New/Select.php @@ -49,7 +49,6 @@ public function mount() } public function render() { - $this->loadServices(); return view('livewire.project.new.select'); } @@ -71,9 +70,14 @@ public function updatedSelectedEnvironment() // } // } + public function updatedSearch() + { + $this->loadServices(); + } public function loadServices(bool $force = false) { try { + $this->loadingServices = true; if (count($this->allServices) > 0 && !$force) { if (!$this->search) { $this->services = $this->allServices; diff --git a/app/Livewire/Project/Service/ServiceApplicationView.php b/app/Livewire/Project/Service/ServiceApplicationView.php index 0b3a4cef6..0986987f9 100644 --- a/app/Livewire/Project/Service/ServiceApplicationView.php +++ b/app/Livewire/Project/Service/ServiceApplicationView.php @@ -24,6 +24,16 @@ public function render() { return view('livewire.project.service.service-application-view'); } + public function updatedApplicationFqdn() + { + $this->application->fqdn = str($this->application->fqdn)->replaceEnd(',', '')->trim(); + $this->application->fqdn = str($this->application->fqdn)->replaceStart(',', '')->trim(); + $this->application->fqdn = str($this->application->fqdn)->trim()->explode(',')->map(function ($domain) { + return str($domain)->trim()->lower(); + }); + $this->application->fqdn = $this->application->fqdn->unique()->implode(','); + $this->application->save(); + } public function instantSave() { $this->submit(); @@ -59,7 +69,11 @@ public function submit() $this->validate(); $this->application->save(); updateCompose($this->application); - $this->dispatch('success', 'Service saved.'); + if (str($this->application->fqdn)->contains(',')) { + $this->dispatch('warning', 'Some services do not support multiple domains, which can lead to problems and is NOT RECOMMENDED.'); + } else { + $this->dispatch('success', 'Service saved.'); + } } catch (\Throwable $e) { return handleError($e, $this); } finally { diff --git a/app/Livewire/Project/Shared/Destination.php b/app/Livewire/Project/Shared/Destination.php index 787b9da20..fa19e8c42 100644 --- a/app/Livewire/Project/Shared/Destination.php +++ b/app/Livewire/Project/Shared/Destination.php @@ -56,7 +56,7 @@ public function stop(int $server_id) public function redeploy(int $network_id, int $server_id) { if ($this->resource->additional_servers->count() > 0 && str($this->resource->docker_registry_image_name)->isEmpty()) { - $this->dispatch('error', 'Failed to deploy.', 'Before deploying to multiple servers, you must first set a Docker image in the General tab.
More information here: documentation'); + $this->dispatch('error', 'Failed to deploy.', 'Before deploying to multiple servers, you must first set a Docker image in the General tab.
More information here: documentation'); return; } $deployment_uuid = new Cuid2(7); diff --git a/app/Livewire/Security/PrivateKey/Create.php b/app/Livewire/Security/PrivateKey/Create.php index cd1c06568..30449b220 100644 --- a/app/Livewire/Security/PrivateKey/Create.php +++ b/app/Livewire/Security/PrivateKey/Create.php @@ -26,7 +26,7 @@ class Create extends Component 'value' => 'private Key', ]; - public function generateNewKey() + public function generateNewRSAKey() { try { $this->rateLimit(10); @@ -37,6 +37,17 @@ public function generateNewKey() return handleError($e, $this); } } + public function generateNewEDKey() + { + try { + $this->rateLimit(10); + $this->name = generate_random_name(); + $this->description = 'Created by Coolify'; + ['private' => $this->value, 'public' => $this->publicKey] = generateSSHKey('ed25519'); + } catch(\Throwable $e) { + return handleError($e, $this); + } + } public function updated($updateProperty) { if ($updateProperty === 'value') { diff --git a/app/Livewire/Security/PrivateKey/Show.php b/app/Livewire/Security/PrivateKey/Show.php index 0540b2e29..0a292731b 100644 --- a/app/Livewire/Security/PrivateKey/Show.php +++ b/app/Livewire/Security/PrivateKey/Show.php @@ -8,7 +8,7 @@ class Show extends Component { public PrivateKey $private_key; - public $public_key; + public $public_key = "Loading..."; protected $rules = [ 'private_key.name' => 'required|string', 'private_key.description' => 'nullable|string', @@ -25,11 +25,13 @@ public function mount() { try { $this->private_key = PrivateKey::ownedByCurrentTeam(['name', 'description', 'private_key', 'is_git_related'])->whereUuid(request()->private_key_uuid)->firstOrFail(); - $this->public_key = $this->private_key->publicKey(); }catch(\Throwable $e) { return handleError($e, $this); } } + public function loadPublicKey() { + $this->public_key = $this->private_key->publicKey(); + } public function delete() { try { diff --git a/app/Livewire/Server/ConfigureCloudflareTunnels.php b/app/Livewire/Server/ConfigureCloudflareTunnels.php new file mode 100644 index 000000000..03a48c3e1 --- /dev/null +++ b/app/Livewire/Server/ConfigureCloudflareTunnels.php @@ -0,0 +1,45 @@ +where('id', $this->server_id)->firstOrFail(); + $server->settings->is_cloudflare_tunnel = true; + $server->settings->save(); + $this->dispatch('success', 'Cloudflare Tunnels configured successfully.'); + $this->dispatch('serverInstalled'); + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + public function submit() + { + try { + $server = Server::ownedByCurrentTeam()->where('id', $this->server_id)->firstOrFail(); + ConfigureCloudflared::run($server, $this->cloudflare_token); + $server->settings->is_cloudflare_tunnel = true; + $server->ip = $this->ssh_domain; + $server->save(); + $server->settings->save(); + $this->dispatch('success', 'Cloudflare Tunnels configured successfully.'); + $this->dispatch('serverInstalled'); + } catch(\Throwable $e) { + return handleError($e, $this); + } + } + public function render() + { + return view('livewire.server.configure-cloudflare-tunnels'); + } +} diff --git a/app/Livewire/Server/Form.php b/app/Livewire/Server/Form.php index 098a7c1ff..ff9aaf701 100644 --- a/app/Livewire/Server/Form.php +++ b/app/Livewire/Server/Form.php @@ -82,7 +82,7 @@ public function checkLocalhostConnection() $this->server->settings->is_usable = true; $this->server->settings->save(); } else { - $this->dispatch('error', 'Server is not reachable.', 'Please validate your configuration and connection.

Check this documentation for further help.'); + $this->dispatch('error', 'Server is not reachable.', 'Please validate your configuration and connection.

Check this documentation for further help.'); return; } } diff --git a/app/Livewire/Server/Resources.php b/app/Livewire/Server/Resources.php index f781418d9..1c8a8267e 100644 --- a/app/Livewire/Server/Resources.php +++ b/app/Livewire/Server/Resources.php @@ -42,7 +42,11 @@ public function refreshStatus() { $this->dispatch('success', 'Resource statuses refreshed.'); } public function loadUnmanagedContainers() { - $this->unmanagedContainers = $this->server->loadUnmanagedContainers(); + try { + $this->unmanagedContainers = $this->server->loadUnmanagedContainers(); + } catch (\Throwable $e) { + return handleError($e, $this); + } } public function mount() { $this->unmanagedContainers = collect(); diff --git a/app/Livewire/Server/ShowPrivateKey.php b/app/Livewire/Server/ShowPrivateKey.php index 43b55fbb6..d44d2cb5f 100644 --- a/app/Livewire/Server/ShowPrivateKey.php +++ b/app/Livewire/Server/ShowPrivateKey.php @@ -39,7 +39,7 @@ public function checkConnection() if ($uptime) { $this->dispatch('success', 'Server is reachable.'); } else { - $this->dispatch('error', 'Server is not reachable.
Please validate your configuration and connection.

Check this documentation for further help.'); + $this->dispatch('error', 'Server is not reachable.
Please validate your configuration and connection.

Check this documentation for further help.'); return; } } catch (\Throwable $e) { diff --git a/app/Livewire/Server/ValidateAndInstall.php b/app/Livewire/Server/ValidateAndInstall.php index ff623e972..6d4173956 100644 --- a/app/Livewire/Server/ValidateAndInstall.php +++ b/app/Livewire/Server/ValidateAndInstall.php @@ -75,7 +75,7 @@ public function validateConnection() { $this->uptime = $this->server->validateConnection(); if (!$this->uptime) { - $this->error = 'Server is not reachable. Please validate your configuration and connection.

Check this documentation for further help.'; + $this->error = 'Server is not reachable. Please validate your configuration and connection.

Check this documentation for further help.'; return; } $this->dispatch('validateOS'); diff --git a/app/Livewire/Settings/Email.php b/app/Livewire/Settings/Email.php index 3563e2e64..77b82df43 100644 --- a/app/Livewire/Settings/Email.php +++ b/app/Livewire/Settings/Email.php @@ -58,6 +58,8 @@ public function submitResend() { try { $this->resetErrorBag(); $this->validate([ + 'settings.smtp_from_address' => 'required|email', + 'settings.smtp_from_name' => 'required', 'settings.resend_api_key' => 'required' ]); $this->settings->save(); @@ -90,6 +92,8 @@ public function submit() try { $this->resetErrorBag(); $this->validate([ + 'settings.smtp_from_address' => 'required|email', + 'settings.smtp_from_name' => 'required', 'settings.smtp_host' => 'required', 'settings.smtp_port' => 'required|numeric', 'settings.smtp_encryption' => 'nullable', diff --git a/app/Livewire/Subscription/Index.php b/app/Livewire/Subscription/Index.php index c87f7d0b6..b367e6dcc 100644 --- a/app/Livewire/Subscription/Index.php +++ b/app/Livewire/Subscription/Index.php @@ -15,7 +15,10 @@ public function mount() if (!isCloud()) { return redirect(RouteServiceProvider::HOME); } - if (data_get(currentTeam(), 'subscription')) { + if (auth()->user()?->isMember()) { + return redirect()->route('dashboard'); + } + if (data_get(currentTeam(), 'subscription') && isSubscriptionActive()) { return redirect()->route('subscription.show'); } $this->settings = InstanceSettings::get(); diff --git a/app/Livewire/Subscription/Show.php b/app/Livewire/Subscription/Show.php index ad677ce53..2ae89806d 100644 --- a/app/Livewire/Subscription/Show.php +++ b/app/Livewire/Subscription/Show.php @@ -11,6 +11,9 @@ public function mount() if (!isCloud()) { return redirect()->route('dashboard'); } + if (auth()->user()?->isMember()) { + return redirect()->route('dashboard'); + } if (!data_get(currentTeam(), 'subscription')) { return redirect()->route('subscription.index'); } diff --git a/app/Livewire/Upgrade.php b/app/Livewire/Upgrade.php index 0c6541d8f..37c2a64ef 100644 --- a/app/Livewire/Upgrade.php +++ b/app/Livewire/Upgrade.php @@ -37,8 +37,8 @@ public function upgrade() return; } $this->showProgress = true; - UpdateCoolify::run(true); - $this->dispatch('success', "Upgrading to {$this->latestVersion} version..."); + UpdateCoolify::run(force: true, async: true); + $this->dispatch('success', "Updating Coolify to {$this->latestVersion} version..."); } catch (\Throwable $e) { return handleError($e, $this); } diff --git a/app/Models/Application.php b/app/Models/Application.php index bc3b27bb0..4d330a00c 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -6,6 +6,7 @@ use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\SoftDeletes; +use Illuminate\Support\Collection; use Spatie\Activitylog\Models\Activity; use Illuminate\Support\Str; use RuntimeException; @@ -501,9 +502,9 @@ public function isConfigurationChanged(bool $save = false) { $newConfigHash = $this->fqdn . $this->git_repository . $this->git_branch . $this->git_commit_sha . $this->build_pack . $this->static_image . $this->install_command . $this->build_command . $this->start_command . $this->port_exposes . $this->port_mappings . $this->base_directory . $this->publish_directory . $this->dockerfile . $this->dockerfile_location . $this->custom_labels; if ($this->pull_request_id === 0 || $this->pull_request_id === null) { - $newConfigHash .= json_encode($this->environment_variables()); + $newConfigHash .= json_encode($this->environment_variables()->get('updated_at')); } else { - $newConfigHash .= json_encode($this->environment_variables_preview->all()); + $newConfigHash .= json_encode($this->environment_variables_preview->get('updated_at')); } $newConfigHash = md5($newConfigHash); $oldConfigHash = data_get($this, 'config_hash'); @@ -903,4 +904,27 @@ public function fqdns(): Attribute : explode(',', $this->fqdn), ); } + public function watchPaths(): Attribute + { + return Attribute::make( + set: function ($value) { + if ($value) { + return trim($value); + } + } + ); + } + public function isWatchPathsTriggered(Collection $modified_files): bool + { + if (is_null($this->watch_paths)) { + return false; + } + $watch_paths = collect(explode("\n", $this->watch_paths)); + $matches = $modified_files->filter(function ($file) use ($watch_paths) { + return $watch_paths->contains(function ($glob) use ($file) { + return fnmatch($glob, $file); + }); + }); + return $matches->count() > 0; + } } diff --git a/app/Models/EnvironmentVariable.php b/app/Models/EnvironmentVariable.php index 611543b2d..32277769e 100644 --- a/app/Models/EnvironmentVariable.php +++ b/app/Models/EnvironmentVariable.php @@ -25,19 +25,18 @@ protected static function booted() static::created(function (EnvironmentVariable $environment_variable) { if ($environment_variable->application_id && !$environment_variable->is_preview) { $found = ModelsEnvironmentVariable::where('key', $environment_variable->key)->where('application_id', $environment_variable->application_id)->where('is_preview', true)->first(); - $application = Application::find($environment_variable->application_id); - if ($application->build_pack === 'dockerfile') { - return; - } if (!$found) { - ModelsEnvironmentVariable::create([ - 'key' => $environment_variable->key, - 'value' => $environment_variable->value, - 'is_build_time' => $environment_variable->is_build_time, - 'is_multiline' => $environment_variable->is_multiline, - 'application_id' => $environment_variable->application_id, - 'is_preview' => true - ]); + $application = Application::find($environment_variable->application_id); + if ($application->build_pack !== 'dockerfile') { + ModelsEnvironmentVariable::create([ + 'key' => $environment_variable->key, + 'value' => $environment_variable->value, + 'is_build_time' => $environment_variable->is_build_time, + 'is_multiline' => $environment_variable->is_multiline, + 'application_id' => $environment_variable->application_id, + 'is_preview' => true + ]); + } } } $environment_variable->update([ diff --git a/app/Models/Server.php b/app/Models/Server.php index 08235a26d..cbe895936 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -550,21 +550,21 @@ public function startUnmanaged($id) } public function loadUnmanagedContainers() { - if ($this->isFunctional()) { - $containers = instant_remote_process(["docker ps -a --format '{{json .}}' "], $this); - $containers = format_docker_command_output_to_json($containers); - $containers = $containers->map(function ($container) { - $labels = data_get($container, 'Labels'); - if (!str($labels)->contains("coolify.managed")) { - return $container; - } - return null; - }); - $containers = $containers->filter(); - return collect($containers); - } else { - return collect([]); - } + if ($this->isFunctional()) { + $containers = instant_remote_process(["docker ps -a --format '{{json .}}' "], $this); + $containers = format_docker_command_output_to_json($containers); + $containers = $containers->map(function ($container) { + $labels = data_get($container, 'Labels'); + if (!str($labels)->contains("coolify.managed")) { + return $container; + } + return null; + }); + $containers = $containers->filter(); + return collect($containers); + } else { + return collect([]); + } } public function hasDefinedResources() { diff --git a/app/Models/Team.php b/app/Models/Team.php index a3dd4e473..29e434a5d 100644 --- a/app/Models/Team.php +++ b/app/Models/Team.php @@ -21,9 +21,11 @@ class Team extends Model implements SendsDiscord, SendsEmail protected static function booted() { - // static::saved(function () { - // refreshSession(); - // }); + static::saving(function ($team) { + if (auth()->user()?->isMember()) { + throw new \Exception('You are not allowed to update this team.'); + } + }); } public function routeNotificationForDiscord() diff --git a/app/Models/User.php b/app/Models/User.php index e2ecae56a..0fa8ead2f 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -127,6 +127,10 @@ public function isOwner() { return $this->role() === 'owner'; } + public function isMember() + { + return $this->role() === 'member'; + } public function isAdminFromSession() { if (auth()->user()->id === 0) { diff --git a/app/Notifications/Server/HighDiskUsage.php b/app/Notifications/Server/HighDiskUsage.php index d8794600d..33e49387e 100644 --- a/app/Notifications/Server/HighDiskUsage.php +++ b/app/Notifications/Server/HighDiskUsage.php @@ -53,13 +53,13 @@ public function toMail(): MailMessage public function toDiscord(): string { - $message = "Coolify: Server '{$this->server->name}' high disk usage detected!\nDisk usage: {$this->disk_usage}%. Threshold: {$this->cleanup_after_percentage}%.\nPlease cleanup your disk to prevent data-loss.\nHere are some tips: https://coolify.io/docs/automated-cleanup."; + $message = "Coolify: Server '{$this->server->name}' high disk usage detected!\nDisk usage: {$this->disk_usage}%. Threshold: {$this->cleanup_after_percentage}%.\nPlease cleanup your disk to prevent data-loss.\nHere are some tips: https://coolify.io/docs/knowledge-base/server/automated-cleanup."; return $message; } public function toTelegram(): array { return [ - "message" => "Coolify: Server '{$this->server->name}' high disk usage detected!\nDisk usage: {$this->disk_usage}%. Threshold: {$this->cleanup_after_percentage}%.\nPlease cleanup your disk to prevent data-loss.\nHere are some tips: https://coolify.io/docs/automated-cleanup." + "message" => "Coolify: Server '{$this->server->name}' high disk usage detected!\nDisk usage: {$this->disk_usage}%. Threshold: {$this->cleanup_after_percentage}%.\nPlease cleanup your disk to prevent data-loss.\nHere are some tips: https://coolify.io/docs/knowledge-base/server/automated-cleanup." ]; } } diff --git a/bootstrap/helpers/api.php b/bootstrap/helpers/api.php index 4fcdbac4f..46c0caa0a 100644 --- a/bootstrap/helpers/api.php +++ b/bootstrap/helpers/api.php @@ -7,5 +7,5 @@ function get_team_id_from_token() } function invalid_token() { - return response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api/authentication'], 400); + return response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api-reference/authorization'], 400); } diff --git a/bootstrap/helpers/applications.php b/bootstrap/helpers/applications.php index bf3f15514..8d810da0f 100644 --- a/bootstrap/helpers/applications.php +++ b/bootstrap/helpers/applications.php @@ -6,6 +6,7 @@ use App\Models\ApplicationDeploymentQueue; use App\Models\Server; use App\Models\StandaloneDocker; +use Illuminate\Support\Collection; use Spatie\Url\Url; function queue_application_deployment(Application $application, string $deployment_uuid, int | null $pull_request_id = 0, string $commit = 'HEAD', bool $force_rebuild = false, bool $is_webhook = false, bool $restart_only = false, ?string $git_type = null, bool $no_questions_asked = false, Server $server = null, StandaloneDocker $destination = null, bool $only_this_server = false) diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index bfff8098a..458dbae85 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -39,6 +39,7 @@ use Lcobucci\JWT\Signer\Key\InMemory; use Lcobucci\JWT\Signer\Hmac\Sha256; use Lcobucci\JWT\Token\Builder; +use phpseclib3\Crypt\EC; use Poliander\Cron\CronExpression; use Visus\Cuid2\Cuid2; use phpseclib3\Crypt\RSA; @@ -110,7 +111,7 @@ function handleError(?Throwable $error = null, ?Livewire\Component $livewire = n ray($error); if ($error instanceof TooManyRequestsException) { if (isset($livewire)) { - return $livewire->dispatch('error', 'Too many requests. Please try again in {$error->secondsUntilAvailable} seconds.'); + return $livewire->dispatch('error', "Too many requests. Please try again in {$error->secondsUntilAvailable} seconds."); } return "Too many requests. Please try again in {$error->secondsUntilAvailable} seconds."; } @@ -165,13 +166,22 @@ function generate_random_name(?string $cuid = null): string } return Str::kebab("{$generator->getName()}-$cuid"); } -function generateSSHKey() +function generateSSHKey(string $type = 'rsa') { - $key = RSA::createKey(); - return [ - 'private' => $key->toString('PKCS1'), - 'public' => $key->getPublicKey()->toString('OpenSSH', ['comment' => 'coolify-generated-ssh-key']) - ]; + if ($type === 'rsa') { + $key = RSA::createKey(); + return [ + 'private' => $key->toString('PKCS1'), + 'public' => $key->getPublicKey()->toString('OpenSSH', ['comment' => 'coolify-generated-ssh-key']) + ]; + } else if ($type === 'ed25519') { + $key = EC::createKey('Ed25519'); + return [ + 'private' => $key->toString('OpenSSH'), + 'public' => $key->getPublicKey()->toString('OpenSSH', ['comment' => 'coolify-generated-ssh-key']) + ]; + } + throw new Exception('Invalid key type'); } function formatPrivateKey(string $privateKey) { @@ -282,7 +292,7 @@ function base_url(bool $withPort = true): string function isSubscribed() { - return auth()->user()->currentTeam()->subscription()->exists() || auth()->user()->isInstanceAdmin(); + return isSubscriptionActive() || auth()->user()->isInstanceAdmin(); } function isDev(): bool { @@ -944,11 +954,10 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal if (!$isDatabase) { if ($savedService->fqdn) { - $fqdn = $savedService->fqdn . ',' . $fqdn; + data_set($savedService, 'fqdn', $savedService->fqdn . ',' . $fqdn); } else { - $fqdn = $fqdn; + data_set($savedService, 'fqdn', $fqdn); } - $savedService->fqdn = $fqdn; $savedService->save(); } EnvironmentVariable::create([ @@ -960,7 +969,6 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal ]); } // Caddy needs exact port in some cases. - if ($predefinedPort && !$key->endsWith("_{$predefinedPort}")) { if ($resource->server->proxyType() === 'CADDY') { $env = EnvironmentVariable::where([ @@ -1459,13 +1467,13 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal ])->first(); $value = Str::of(replaceVariables($value)); $key = $value; - if ($value->startsWith('SERVICE_')) { $foundEnv = EnvironmentVariable::where([ 'key' => $key, 'application_id' => $resource->id, ])->first(); ['command' => $command, 'forService' => $forService, 'generatedValue' => $generatedValue, 'port' => $port] = parseEnvVariable($value); + ray($command, $generatedValue); if (!is_null($command)) { if ($command?->value() === 'FQDN' || $command?->value() === 'URL') { if (Str::lower($forService) === $serviceName) { diff --git a/bootstrap/helpers/subscriptions.php b/bootstrap/helpers/subscriptions.php index 928bbbcaf..5158c4e7e 100644 --- a/bootstrap/helpers/subscriptions.php +++ b/bootstrap/helpers/subscriptions.php @@ -66,7 +66,7 @@ function isSubscriptionActive() // return $subscription->paddle_status === 'active'; // } if (isStripe()) { - return $subscription->stripe_invoice_paid === true && $subscription->stripe_cancel_at_period_end === false; + return $subscription->stripe_invoice_paid === true; } return false; } diff --git a/config/sentry.php b/config/sentry.php index ec6abd3c9..a3fbc0a3a 100644 --- a/config/sentry.php +++ b/config/sentry.php @@ -7,7 +7,7 @@ // The release version of your application // Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD')) - 'release' => '4.0.0-beta.245', + 'release' => '4.0.0-beta.251', // When left empty or `null` the Laravel environment will be used 'environment' => config('app.env'), diff --git a/config/version.php b/config/version.php index 8b2443f54..d88e65a1b 100644 --- a/config/version.php +++ b/config/version.php @@ -1,3 +1,3 @@ longText('watch_paths')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('applications', function (Blueprint $table) { + $table->dropColumn('watch_paths'); + }); + } +}; diff --git a/resources/css/app.css b/resources/css/app.css index fa29bf90f..371efd8ed 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -13,7 +13,7 @@ body { .input, .select { - @apply text-black dark:bg-coolgray-100 dark:text-white ring-neutral-300 dark:ring-coolgray-300; + @apply text-black dark:bg-coolgray-100 dark:text-white ring-neutral-200 dark:ring-coolgray-300; } /* Readonly */ @@ -41,7 +41,7 @@ option { } .button { - @apply flex items-center justify-center gap-2 px-3 py-1 text-sm text-white normal-case rounded cursor-pointer hover:bg-black/80 bg-coolgray-200 hover:bg-coolgray-500 hover:text-white disabled:bg-coolgray-100/10 disabled:cursor-not-allowed min-w-fit focus:outline-1 dark:disabled:text-neutral-600; + @apply flex items-center justify-center gap-2 px-2 py-1 text-sm text-black normal-case border rounded cursor-pointer bg-neutral-200/50 border-neutral-300 hover:bg-neutral-300 dark:bg-coolgray-200 dark:text-white dark:hover:text-white dark:hover:bg-coolgray-500 dark:border-black hover:text-black disabled:bg-coolgray-100/10 disabled:cursor-not-allowed min-w-fit focus:outline-1 dark:disabled:text-neutral-600; } button[isError]:not(:disabled) { @@ -78,7 +78,7 @@ label { } table { - @apply min-w-full divide-y dark:divide-coolgray-200 divide-neutral-300; + @apply min-w-full divide-y dark:divide-coolgray-200 divide-neutral-300 ; } thead { @@ -90,7 +90,7 @@ tbody { } tr { - @apply text-neutral-400; + @apply text-black dark:text-neutral-400 dark:hover:bg-black hover:bg-neutral-200; } tr th { @@ -195,19 +195,22 @@ .kbd-custom { } .box { - @apply flex lg:flex-row flex-col p-2 transition-colors cursor-pointer min-h-[4rem] dark:bg-coolgray-100 bg-white border border-neutral-200 dark:border-black hover:bg-neutral-100 dark:hover:bg-coollabs-100 dark:hover:text-white hover:no-underline; + @apply flex lg:flex-row flex-col p-2 transition-colors cursor-pointer min-h-[4rem] dark:bg-coolgray-100 bg-white border text-black dark:text-white hover:text-black border-neutral-200 dark:border-black hover:bg-neutral-100 dark:hover:bg-coollabs-100 dark:hover:text-white hover:no-underline; } .box-boarding { - @apply flex lg:flex-row flex-col p-2 transition-colors cursor-pointer min-h-[4rem] dark:bg-coolgray-100 dark:text-white bg-neutral-50 border border-neutral-200 dark:border-black hover:bg-neutral-100 dark:hover:bg-coollabs-100 dark:hover:text-white hover:text-black hover:no-underline text-black; + @apply flex lg:flex-row flex-col p-2 transition-colors cursor-pointer min-h-[4rem] dark:bg-coolgray-100 dark:text-white bg-neutral-50 border border-neutral-200 dark:border-black hover:bg-neutral-100 dark:hover:bg-coollabs-100 dark:hover:text-white hover:text-black hover:no-underline text-black ; +} +.box-without-bg { + @apply flex p-2 transition-colors dark:hover:text-white hover:no-underline min-h-[4rem] border border-neutral-200 dark:border-black; +} +.box-without-bg-without-border { + @apply flex p-2 transition-colors dark:hover:text-white hover:no-underline min-h-[4rem] ; } .on-box { @apply rounded hover:bg-neutral-300 dark:hover:bg-coolgray-500/20; } -.box-without-bg { - @apply flex p-2 transition-colors dark:hover:text-white hover:no-underline min-h-[4rem]; -} .box-title { @apply font-bold text-black dark:text-white group-hover:dark:text-white; @@ -280,7 +283,7 @@ .subtitle { } .fullscreen { - @apply fixed top-0 left-0 w-full h-full z-[9999] bg-coolgray-100 overflow-y-auto scrollbar pb-4; + @apply fixed top-0 left-0 w-full h-full z-[9999] dark:bg-coolgray-100 bg-white overflow-y-auto scrollbar pb-4; } .toast { diff --git a/resources/js/app.js b/resources/js/app.js index 47313863b..befec919e 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -1,6 +1,6 @@ -import { createApp } from "vue"; -import MagicBar from "./components/MagicBar.vue"; +// import { createApp } from "vue"; +// import MagicBar from "./components/MagicBar.vue"; -const app = createApp({}); -app.component("magic-bar", MagicBar); -app.mount("#vue"); +// const app = createApp({}); +// app.component("magic-bar", MagicBar); +// app.mount("#vue"); diff --git a/resources/views/auth/forgot-password.blade.php b/resources/views/auth/forgot-password.blade.php index 08d52facd..54d57a302 100644 --- a/resources/views/auth/forgot-password.blade.php +++ b/resources/views/auth/forgot-password.blade.php @@ -1,18 +1,15 @@ -
-
-
- -
Coolify
-
- {{-- --}} +
+
+ + Coolify +
+ {{ __('auth.forgot_password') }}
- -
-

{{ __('auth.forgot_password') }}

-
-
- @if (is_transactional_emails_active()) +
+
+ @if (is_transactional_emails_active())
@csrf @@ -37,7 +34,9 @@ {{ session('status') }}
@endif +
-
+ + diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php index 82ea5062b..2c61581c6 100644 --- a/resources/views/auth/login.blade.php +++ b/resources/views/auth/login.blade.php @@ -4,51 +4,31 @@ Coolify -
+
@csrf @env('local') - + - + {{ __('auth.forgot_password') }}? @else - - + + {{ __('auth.forgot_password') }}? @endenv {{ __('auth.login') }} - @if ($is_registration_enabled) - - {{ __('auth.register_now') }} - - @endif - @if ($enabled_oauth_providers->isNotEmpty()) -
- -
- or -
-
- @endif - @foreach ($enabled_oauth_providers as $provider_setting) - - {{ __("auth.login.$provider_setting->provider") }} - - @endforeach + @if (!$is_registration_enabled)
{{ __('auth.registration_disabled') }}
@endif @@ -70,6 +50,27 @@ class="w-full bg-white shadow md:mt-0 sm:max-w-md xl:p-0 dark:bg-base ">
@endif + @if ($is_registration_enabled) + + {{ __('auth.register_now') }} + + @endif + @if ($enabled_oauth_providers->isNotEmpty()) +
+ +
+ or +
+
+ @endif + @foreach ($enabled_oauth_providers as $provider_setting) + + {{ __("auth.login.$provider_setting->provider") }} + + @endforeach
diff --git a/resources/views/auth/two-factor-challenge.blade.php b/resources/views/auth/two-factor-challenge.blade.php index 33094920c..6761992a9 100644 --- a/resources/views/auth/two-factor-challenge.blade.php +++ b/resources/views/auth/two-factor-challenge.blade.php @@ -1,42 +1,40 @@ -
-
-
-
Coolify
- {{-- --}} -
-
-
- @csrf -
- - {{--
Use - Recovery Code -
--}} -
-
- - {{--
Use - One-Time Code -
--}} -
- {{ __('auth.login') }} -
- @if ($errors->any()) -
- @foreach ($errors->all() as $error) -

{{ $error }}

- @endforeach -
- @endif - @if (session('status')) -
- {{ session('status') }} -
- @endif +
+
+ + Coolify + +
+
+
+ @csrf +
+ +
Enter + Recovery Code +
+
+
+ +
+ {{ __('auth.login') }} +
+ @if ($errors->any()) +
+ @foreach ($errors->all() as $error) +

{{ $error }}

+ @endforeach +
+ @endif + @if (session('status')) +
+ {{ session('status') }} +
+ @endif +
-
+ diff --git a/resources/views/components/forms/input.blade.php b/resources/views/components/forms/input.blade.php index f6b255a9f..2c6c1ef49 100644 --- a/resources/views/components/forms/input.blade.php +++ b/resources/views/components/forms/input.blade.php @@ -3,7 +3,7 @@ 'w-full' => !$isMultiline, ])> @if ($label) -
diff --git a/resources/views/components/pricing-plans.blade.php b/resources/views/components/pricing-plans.blade.php index ca71bf93e..5977323f2 100644 --- a/resources/views/components/pricing-plans.blade.php +++ b/resources/views/components/pricing-plans.blade.php @@ -48,7 +48,7 @@ class="font-bold dark:text-warning">{{ config('constants.limits.trial_period') }
For the detailed list of features, please visit our landing page: coolify.io
+ class="grid max-w-sm grid-cols-1 -mt-16 divide-y divide-neutral-200 dark:divide-coolgray-500 isolate gap-y-16 sm:mx-auto lg:-mx-8 lg:mt-0 lg:max-w-none lg:grid-cols-3 lg:divide-x lg:divide-y-0 xl:-mx-4">

Basic

@@ -186,7 +186,7 @@ class="grid max-w-sm grid-cols-1 -mt-16 divide-y divide-coolgray-500 isolate gap
-
+

Ultimate

diff --git a/resources/views/components/resource-view.blade.php b/resources/views/components/resource-view.blade.php index 6857a0dc0..d6cc12026 100644 --- a/resources/views/components/resource-view.blade.php +++ b/resources/views/components/resource-view.blade.php @@ -1,7 +1,7 @@

!$upgrade, - 'hover:border-red-500 cursor-not-allowed' => $upgrade, + 'transition-all duration-150 box-without-bg dark:bg-coolgray-100 bg-white group', + 'hover:border-l-coollabs cursor-pointer' => !$upgrade, + 'hover:border-l-red-500 cursor-not-allowed' => $upgrade, ]) @if (!$upgrade) wire:click={{ $wire }} @endif>
{{ $logo }} diff --git a/resources/views/components/status/index.blade.php b/resources/views/components/status/index.blade.php index c8902cd45..4ee3319ff 100644 --- a/resources/views/components/status/index.blade.php +++ b/resources/views/components/status/index.blade.php @@ -8,7 +8,7 @@ @endif @if (!str($resource->status)->contains('exited') && $showRefreshButton) -
-
-
diff --git a/resources/views/livewire/project/application/general.blade.php b/resources/views/livewire/project/application/general.blade.php index 413957e7d..7f950dfa4 100644 --- a/resources/views/livewire/project/application/general.blade.php +++ b/resources/views/livewire/project/application/general.blade.php @@ -48,7 +48,7 @@
+ helper="WARNING: Advanced use cases only. Your docker compose file will be deployed as-is. Nothing is modified by Coolify. You need to configure the proxy parts. More info in the documentation." />
@if (count($parsedServices) > 0 && !$application->settings->is_raw_compose_deployment_enabled)

Domains

@@ -84,13 +84,13 @@

Docker Registry

@if ($application->build_pack !== 'dockerimage' && !$application->destination->server->isSwarm()) + helper="Push the built image to a docker registry. More info here." /> @endif
@if ($application->destination->server->isSwarm()) @if ($application->build_pack !== 'dockerimage')
Docker Swarm requires the image to be available in a registry. More info here.
@endif @endif @@ -132,7 +132,7 @@ class="underline" href="https://coolify.io/docs/docker/registry" @if ($application->build_pack !== 'dockercompose')
@@ -147,8 +147,9 @@ class="underline" href="https://coolify.io/docs/docker/registry"
-
Nixpacks will detect the required configuration automatically. - Framework Specific Docs +
Nixpacks will detect the required configuration automatically. + Framework + Specific Docs
@endif @endif @@ -201,18 +202,22 @@ class="underline" href="https://coolify.io/docs/docker/registry" label="Publish Directory" /> @endif @endif +
-
The following options are for advanced use cases. Only modify them if you - know what are - you doing.
+ @if ($this->application->is_github_based() && !$this->application->is_public_repository()) +
+ +
+ @endif @endif @else @endif diff --git a/resources/views/livewire/project/application/previews.blade.php b/resources/views/livewire/project/application/previews.blade.php index 45d0fba63..6d1eb8614 100644 --- a/resources/views/livewire/project/application/previews.blade.php +++ b/resources/views/livewire/project/application/previews.blade.php @@ -57,7 +57,7 @@ class="dark:text-warning">{{ $application->destination->server->name }}.<
Previews
@foreach ($application->previews as $preview) -
+
PR #{{ data_get($preview, 'pull_request_id') }} | @if (Str::of(data_get($preview, 'status'))->startsWith('running')) @@ -78,7 +78,7 @@ class="dark:text-warning">{{ $application->destination->server->name }}.<
- @if (data_get($preview, 'status') === 'exited') Deploy @@ -88,17 +88,17 @@ class="dark:text-warning">{{ $application->destination->server->name }}.< - + Deployment Logs - + Application Logs - Delete
diff --git a/resources/views/livewire/project/clone-me.blade.php b/resources/views/livewire/project/clone-me.blade.php index 3ab5f0654..b2007fcb6 100644 --- a/resources/views/livewire/project/clone-me.blade.php +++ b/resources/views/livewire/project/clone-me.blade.php @@ -15,8 +15,8 @@
Docker Networks
@foreach ($server->destinations() as $destination) -
{{ $destination->name }}
@@ -30,7 +30,7 @@
These will be cloned to the new project
@foreach ($environment->applications->sortBy('name') as $application) -
+
{{ $application->name }}
{{ $application->description }}
@@ -38,7 +38,7 @@
@endforeach @foreach ($environment->databases()->sortBy('name') as $database) -
+
{{ $database->name }}
{{ $database->description }}
@@ -46,7 +46,7 @@
@endforeach @foreach ($environment->services->sortBy('name') as $service) -
+
{{ $service->name }}
{{ $service->description }}
diff --git a/resources/views/livewire/project/database/backup-executions.blade.php b/resources/views/livewire/project/database/backup-executions.blade.php index 2a08d8921..a361eb8d3 100644 --- a/resources/views/livewire/project/database/backup-executions.blade.php +++ b/resources/views/livewire/project/database/backup-executions.blade.php @@ -1,7 +1,7 @@
@forelse($executions as $execution)
data_get($execution, 'status') === 'success', 'border-red-500' => data_get($execution, 'status') === 'failed', ])> @@ -23,7 +23,7 @@ class="relative flex flex-col p-4 border-dotted border-1 bg-coolgray-100" @class
@if (data_get($execution, 'status') === 'success') - Download @endif diff --git a/resources/views/livewire/project/edit.blade.php b/resources/views/livewire/project/edit.blade.php index e291446f3..56806d287 100644 --- a/resources/views/livewire/project/edit.blade.php +++ b/resources/views/livewire/project/edit.blade.php @@ -24,7 +24,7 @@
You can use these variables anywhere with
@{{ project.VARIABLENAME }}
+ helper="More info here.">
@forelse ($project->environment_variables->sort()->sortBy('real_value') as $env) diff --git a/resources/views/livewire/project/environment-edit.blade.php b/resources/views/livewire/project/environment-edit.blade.php index 58f5b7c5c..5418c5036 100644 --- a/resources/views/livewire/project/environment-edit.blade.php +++ b/resources/views/livewire/project/environment-edit.blade.php @@ -49,7 +49,7 @@
You can use these variables anywhere with @{{environment.VARIABLENAME}} + helper="More info here.">
@forelse ($environment->environment_variables->sort()->sortBy('real_value') as $env) diff --git a/resources/views/livewire/project/new/github-private-repository.blade.php b/resources/views/livewire/project/new/github-private-repository.blade.php index 29438988e..4fc2a0bbc 100644 --- a/resources/views/livewire/project/new/github-private-repository.blade.php +++ b/resources/views/livewire/project/new/github-private-repository.blade.php @@ -1,9 +1,9 @@

Create a new Application

- - + Add New GitHub App - + + + @if ($repositories->count() > 0) diff --git a/resources/views/livewire/project/new/select.blade.php b/resources/views/livewire/project/new/select.blade.php index b218c9557..eea43a8bd 100644 --- a/resources/views/livewire/project/new/select.blade.php +++ b/resources/views/livewire/project/new/select.blade.php @@ -377,16 +377,16 @@ class="w-[4.5rem]

Services

Reload List -
Trademarks Policy: The respective trademarks mentioned here are owned by the respective companies, and use of them does not imply any affiliation or endorsement.
-
- @if ($loadingServices) - - @else + + @if ($loadingServices) + + @else +
@forelse ($services as $serviceName => $service) @if (data_get($service, 'minversion') && version_compare(config('version'), data_get($service, 'minversion'), '<')) @@ -457,74 +457,73 @@ class="w-[4.5rem] @empty
No service found. Please try to reload the list!
@endforelse - @endif -
+ @endif +
- @endif - @if ($current_step === 'servers') -

Select a server

-
-
- @forelse($servers as $server) -
- + {{-- @if ($isDatabase)
Swarm clusters are excluded from this type of resource at the moment. It will be activated soon. Stay tuned.
@endif --}} - @endif - @if ($current_step === 'destinations') -

Select a destination

-
Destinations are used to segregate resources by network. If you are unsure, select the default - Standalone Docker (coolify).
-
- @if ($server->isSwarm()) - @foreach ($swarmDockers as $swarmDocker) -
-
-
- Swarm Docker ({{ $swarmDocker->name }}) -
+ @endif + @if ($current_step === 'destinations') +

Select a destination

+
Destinations are used to segregate resources by network. If you are unsure, select the default + Standalone Docker (coolify).
+
+ @if ($server->isSwarm()) + @foreach ($swarmDockers as $swarmDocker) +
+
+
+ Swarm Docker ({{ $swarmDocker->name }})
- @endforeach - @else - @foreach ($standaloneDockers as $standaloneDocker) -
-
-
- Standalone Docker ({{ $standaloneDocker->name }}) -
-
- Network: {{ $standaloneDocker->network }}
+
+ @endforeach + @else + @foreach ($standaloneDockers as $standaloneDocker) +
+
+
+ Standalone Docker ({{ $standaloneDocker->name }})
+
+ Network: {{ $standaloneDocker->network }}
- @endforeach - @endif -
- @endif - @if ($current_step === 'existing-postgresql') - - - Add Database - - @endif -
+
+ @endforeach + @endif +
+ @endif + @if ($current_step === 'existing-postgresql') +
+ + Add Database + + @endif +
diff --git a/resources/views/livewire/project/service/configuration.blade.php b/resources/views/livewire/project/service/configuration.blade.php index 1f21e2156..54c7dfffd 100644 --- a/resources/views/livewire/project/service/configuration.blade.php +++ b/resources/views/livewire/project/service/configuration.blade.php @@ -51,7 +51,7 @@ $application->status)->contains(['running']), 'border-l border-dashed border-warning' => Str::of( $application->status)->contains(['starting']), - 'flex gap-2 box-without-bg bg-coolgray-100 hover:text-neutral-300 group', + 'flex gap-2 box-without-bg dark:bg-coolgray-100 bg-white dark:hover:text-neutral-300 group', ])>
@@ -95,7 +95,7 @@ $database->status)->contains(['running']), 'border-l border-dashed border-warning' => Str::of( $database->status)->contains(['restarting']), - 'flex gap-2 box-without-bg bg-coolgray-100 hover:text-neutral-300 group', + 'flex gap-2 box-without-bg dark:bg-coolgray-100 bg-white dark:hover:text-neutral-300 group', ])>
diff --git a/resources/views/livewire/project/service/stack-form.blade.php b/resources/views/livewire/project/service/stack-form.blade.php index b3f88c927..da34676f7 100644 --- a/resources/views/livewire/project/service/stack-form.blade.php +++ b/resources/views/livewire/project/service/stack-form.blade.php @@ -15,7 +15,7 @@
+ helper="By default, you do not reach the Coolify defined networks.
Starting a docker compose based resource will have an internal network.
If you connect to a Coolify defined network, you maybe need to use different internal DNS names to connect to a resource.

For more information, check this." />
@if ($fields)
diff --git a/resources/views/livewire/project/shared/destination.blade.php b/resources/views/livewire/project/shared/destination.blade.php index 6e494d3e7..6d0dc0a62 100644 --- a/resources/views/livewire/project/shared/destination.blade.php +++ b/resources/views/livewire/project/shared/destination.blade.php @@ -2,9 +2,10 @@

Servers

Server related configurations.
-
-
-
Primary Server
+
+

Primary Server

+
@if (str($resource->realStatus())->startsWith('running'))
@@ -12,26 +13,29 @@
@endif -
+
Server: {{ data_get($resource, 'destination.server.name') }}
-
+
Network: {{ data_get($resource, 'destination.network') }}
@if ($resource?->additional_networks?->count() > 0) - Deploy - @if (str($resource->realStatus())->startsWith('running')) - Stop - @endif +
+ Deploy + @if (str($resource->realStatus())->startsWith('running')) + Stop + @endif +
@endif
@if ($resource?->additional_networks?->count() > 0) +

Additional Server(s)

@foreach ($resource->additional_networks as $destination) -
-
+
+
@if (str(data_get($destination, 'pivot.status'))->startsWith('running'))
@@ -40,28 +44,31 @@ class="absolute bg-success -top-1 -left-1 badge ">
class="absolute bg-error -top-1 -left-1 badge ">
@endif
- Server: {{ data_get($destination, 'server.name') }} -
-
- Network: {{ data_get($destination, 'network') }} +
+ Server: {{ data_get($destination, 'server.name') }} +
+
+ Network: {{ data_get($destination, 'network') }} +
- Deploy - Promote - to Primary - @if (data_get_str($destination, 'pivot.status')->startsWith('running')) - Stop - @endif - - This will stop the running application in this server and remove it as a deployment - destination.

Please think again. -
- +
+ Deploy + Promote + to Primary + @if (data_get_str($destination, 'pivot.status')->startsWith('running')) + Stop + @endif + + This will stop the running application in this server and remove it as a deployment + destination.

Please think again. +
+
@endforeach @endif @@ -75,10 +82,12 @@ class="absolute bg-error -top-1 -left-1 badge ">
- Server: {{ data_get($network, 'server.name') }} -
-
- Network: {{ data_get($network, 'name') }} +
+ Server: {{ data_get($network, 'server.name') }} +
+
+ Network: {{ data_get($network, 'name') }} +
@endforeach diff --git a/resources/views/livewire/project/shared/get-logs.blade.php b/resources/views/livewire/project/shared/get-logs.blade.php index 7f8ce9495..ad4e74e0c 100644 --- a/resources/views/livewire/project/shared/get-logs.blade.php +++ b/resources/views/livewire/project/shared/get-logs.blade.php @@ -1,5 +1,38 @@
-
+
@if ($resource?->type() === 'application')

{{ $container }}

@@ -24,7 +57,7 @@
-
-
-
diff --git a/resources/views/livewire/project/shared/webhooks.blade.php b/resources/views/livewire/project/shared/webhooks.blade.php index fe1382597..0fe999232 100644 --- a/resources/views/livewire/project/shared/webhooks.blade.php +++ b/resources/views/livewire/project/shared/webhooks.blade.php @@ -2,11 +2,11 @@

Webhooks

+ helper="For more details goto our docs." />
@if ($resource->type() === 'application') diff --git a/resources/views/livewire/security/private-key/create.blade.php b/resources/views/livewire/security/private-key/create.blade.php index d44e2c470..1bace9f3a 100644 --- a/resources/views/livewire/security/private-key/create.blade.php +++ b/resources/views/livewire/security/private-key/create.blade.php @@ -1,6 +1,12 @@
- {{--
Private Keys are used to connect to your servers without passwords.
--}} - Generate new SSH key for me +
+
Private Keys are used to connect to your servers without passwords.
+
You should not use passphrase protected keys.
+
+
+ Generate new RSA SSH Key + Generate new ED25519 SSH Key +
diff --git a/resources/views/livewire/security/private-key/show.blade.php b/resources/views/livewire/security/private-key/show.blade.php index b56fab610..9935f5565 100644 --- a/resources/views/livewire/security/private-key/show.blade.php +++ b/resources/views/livewire/security/private-key/show.blade.php @@ -1,4 +1,4 @@ -
+
@@ -22,11 +22,11 @@
Private Key *
-
Edit
-
Hide
diff --git a/resources/views/livewire/server/configure-cloudflare-tunnels.blade.php b/resources/views/livewire/server/configure-cloudflare-tunnels.blade.php new file mode 100644 index 000000000..4aaf9203f --- /dev/null +++ b/resources/views/livewire/server/configure-cloudflare-tunnels.blade.php @@ -0,0 +1,8 @@ + + + + Automated Configuration (experimental) +

Or

+ I have already set up the tunnel manually on the server. + diff --git a/resources/views/livewire/server/form.blade.php b/resources/views/livewire/server/form.blade.php index c1d33ae7e..48c00d6f9 100644 --- a/resources/views/livewire/server/form.blade.php +++ b/resources/views/livewire/server/form.blade.php @@ -70,37 +70,63 @@ class="w-full mt-8 mb-4 font-bold box-without-bg bg-coollabs hover:bg-coollabs-1
- @if (!$server->isLocalhost()) - @if ($server->settings->is_build_server) + @if ($server->isFunctional()) + @if (!$server->isLocalhost()) - @else - - @if ($server->isSwarm()) -
Swarm support is experimental.
+
+

Cloudflare Tunnels +

+ +
+ @if ($server->settings->is_cloudflare_tunnel) + + @else + + + @endif +

Swarm (experimental)

+
Read the docs here. +
@if ($server->settings->is_swarm_worker) @else @endif + @if ($server->settings->is_swarm_manager) @else @endif @endif + @else +
+

Cloudflare Tunnels +

+ +
+ @if ($server->settings->is_cloudflare_tunnel) + + @else + + + + @endif @endif +
diff --git a/resources/views/livewire/server/new/by-ip.blade.php b/resources/views/livewire/server/new/by-ip.blade.php index 59c8c6b74..e3b5b6396 100644 --- a/resources/views/livewire/server/new/by-ip.blade.php +++ b/resources/views/livewire/server/new/by-ip.blade.php @@ -29,24 +29,24 @@

Swarm (experimental)

Read the docs here.
@if ($is_swarm_worker || $is_build_server) @else @endif @if ($is_swarm_manager|| $is_build_server) @else @endif @if ($is_swarm_worker && count($swarm_managers) > 0) diff --git a/resources/views/livewire/server/proxy.blade.php b/resources/views/livewire/server/proxy.blade.php index 7ef521916..ee2edea72 100644 --- a/resources/views/livewire/server/proxy.blade.php +++ b/resources/views/livewire/server/proxy.blade.php @@ -17,8 +17,8 @@ xmlns="http://www.w3.org/2000/svg"> - Before switching proxies, please read this.
+ Before switching proxies, please read this.
@if ($server->proxyType() === 'TRAEFIK_V2')
Traefik v2
@elseif ($server->proxyType() === 'CADDY') diff --git a/resources/views/livewire/server/resources.blade.php b/resources/views/livewire/server/resources.blade.php index 378547f1a..8c888b131 100644 --- a/resources/views/livewire/server/resources.blade.php +++ b/resources/views/livewire/server/resources.blade.php @@ -1,165 +1,159 @@
- @if ($server->isFunctional()) -
- -
-
+
+ +
+
+
+
+

Resources

+ Refresh +
+
Here you can find all resources that are managed by Coolify.
+
+ @if ($server->definedResources()->count() > 0)
-
-

Resources

- Refresh -
-
Here you can find all resources that are managed by Coolify.
-
- @if ($server->definedResources()->count() > 0)
-
-
-
-
- - +
+
+
+
+ + + + + + + + + + + @forelse ($server->definedResources()->sortBy('name',SORT_NATURAL) as $resource) - - - - - + + + + + - - - @forelse ($server->definedResources()->sortBy('name',SORT_NATURAL) as $resource) - - - - - - - - @empty - @endforelse - -
+ Project + + Environment + Name + + Type + + Status +
- Project - - Environment - Name - - Type - - Status - + {{ data_get($resource->project(), 'name') }} + + {{ data_get($resource, 'environment.name') }} + + {{ $resource->name }} + + + {{ str($resource->type())->headline() }} + @if ($resource->type() === 'service') + + @else + + @endif +
- {{ data_get($resource->project(), 'name') }} - - {{ data_get($resource, 'environment.name') }} - {{ $resource->name }} - - - {{ str($resource->type())->headline() }} - @if ($resource->type() === 'service') - - @else - - @endif -
-
+ @empty + @endforelse + +
- @else -
No resources found.
- @endif -
-
-
-
-

Resources

- Refresh -
-
Here you can find all other containers running on the server.
- @if ($unmanagedContainers->count() > 0) + @else +
No resources found.
+ @endif +
+
+
+
+

Resources

+ Refresh +
+
Here you can find all other containers running on the server.
+
+ @if ($unmanagedContainers->count() > 0) +
-
-
-
-
- - +
+
+
+
+ + + + + + + + + + @forelse ($unmanagedContainers->sortBy('name',SORT_NATURAL) as $resource) - - - - + + + + - - - @forelse ($unmanagedContainers->sortBy('name',SORT_NATURAL) as $resource) - - - - - - - @empty - @endforelse - -
+ Name + + Image + + Status + + Action +
- Name - - Image - - Status - - Action - + {{ data_get($resource, 'Names') }} + + {{ data_get($resource, 'Image') }} + + {{ data_get($resource, 'State') }} + + @if (data_get($resource, 'State') === 'running') + Restart + Stop + @elseif (data_get($resource, 'State') === 'exited') + Start + @elseif (data_get($resource, 'State') === 'restarting') + Stop + @endif +
- {{ data_get($resource, 'Names') }} - - {{ data_get($resource, 'Image') }} - - {{ data_get($resource, 'State') }} - - @if (data_get($resource, 'State') === 'running') - Restart - Stop - @elseif (data_get($resource, 'State') === 'exited') - Start - @elseif (data_get($resource, 'State') === 'restarting') - Stop - @endif -
-
+ @empty + @endforelse + +
- @else -
No resources found.
- @endif -
+
+ @else +
No resources found.
+ @endif
- @else -
Server is not validated. Validate first.
- @endif +
diff --git a/resources/views/livewire/server/show-private-key.blade.php b/resources/views/livewire/server/show-private-key.blade.php index ac52d601f..62b1c4614 100644 --- a/resources/views/livewire/server/show-private-key.blade.php +++ b/resources/views/livewire/server/show-private-key.blade.php @@ -26,12 +26,12 @@

Choose another Key

@forelse ($privateKeys as $private_key) -
-
-
{{ $private_key->name }}
-
{{ $private_key->description }}
+
+
+
{{ $private_key->name }}
+
{{ $private_key->description }}
+
-
@empty
No private keys found.
@endforelse diff --git a/resources/views/livewire/settings/email.blade.php b/resources/views/livewire/settings/email.blade.php index 1c7bc0bc8..106dd77d4 100644 --- a/resources/views/livewire/settings/email.blade.php +++ b/resources/views/livewire/settings/email.blade.php @@ -2,11 +2,7 @@

Transactional Email

- @if (isCloud()) -
Email settings for password resets, invitations, shared with Pro+ subscribers etc.
- @else -
Email settings for password resets, invitations, etc.
- @endif +
Email settings for password resets, invitations, etc.
- +
- + @forelse ($applications->sortBy('name',SORT_NATURAL) as $resource) - + diff --git a/resources/views/livewire/team-shared-variables-index.blade.php b/resources/views/livewire/team-shared-variables-index.blade.php index 0a697c7d0..640913f1f 100644 --- a/resources/views/livewire/team-shared-variables-index.blade.php +++ b/resources/views/livewire/team-shared-variables-index.blade.php @@ -8,7 +8,7 @@
You can use these variables anywhere with @{{ team.VARIABLENAME }} + helper="More info here.">
diff --git a/resources/views/livewire/team/invitations.blade.php b/resources/views/livewire/team/invitations.blade.php index c9ca4d6b7..f318e4d61 100644 --- a/resources/views/livewire/team/invitations.blade.php +++ b/resources/views/livewire/team/invitations.blade.php @@ -6,7 +6,7 @@
-
@@ -121,9 +121,9 @@
{{ data_get($resource->project(), 'name') }}
+
- + @foreach ($invitations as $invite) - + diff --git a/resources/views/livewire/team/member/index.blade.php b/resources/views/livewire/team/member/index.blade.php index 6a6cc0fd2..41cd61d82 100644 --- a/resources/views/livewire/team/member/index.blade.php +++ b/resources/views/livewire/team/member/index.blade.php @@ -7,7 +7,7 @@
-
Email @@ -20,9 +20,9 @@
{{ $invite->email }} {{ $invite->via }} {{ $invite->role }}
+
- + @foreach (currentTeam()->members as $member) @endforeach diff --git a/versions.json b/versions.json index 3a4b38eff..e1b15938c 100644 --- a/versions.json +++ b/versions.json @@ -1,10 +1,7 @@ { "coolify": { - "main": { - "version": "3.12.36" - }, "v4": { - "version": "4.0.0-beta.245" + "version": "4.0.0-beta.251" } } }
Name @@ -17,7 +17,7 @@ Actions