From 34f15c106c003645bf8cc0b6ba428e589ff1cbe5 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Sun, 31 May 2026 21:46:23 +0200 Subject: [PATCH 1/2] fix(webhook): match GitLab SSH repos with custom ports Strip leading port segments from scp-style GitLab repository URLs so manual webhook matching compares the repository path consistently. Cover both ported and unported SSH URL forms. --- .../MatchesManualWebhookApplications.php | 4 ++ tests/Feature/Webhook/WebhookHmacTest.php | 42 +++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/app/Http/Controllers/Webhook/Concerns/MatchesManualWebhookApplications.php b/app/Http/Controllers/Webhook/Concerns/MatchesManualWebhookApplications.php index f1fd0c40f..0463790eb 100644 --- a/app/Http/Controllers/Webhook/Concerns/MatchesManualWebhookApplications.php +++ b/app/Http/Controllers/Webhook/Concerns/MatchesManualWebhookApplications.php @@ -81,6 +81,10 @@ protected function canonicalManualWebhookRepository(?string $gitRepository): ?st $path = data_get($parts, 'path'); } elseif (Str::startsWith($gitRepository, 'git@') && str_contains($gitRepository, ':')) { $path = Str::after($gitRepository, ':'); + // scp-style SSH URLs embed a custom port as "git@host:2222/owner/repo". + // Strip the leading numeric port segment so the path matches the webhook + // payload's owner/repo, consistent with convertGitUrl() in shared.php. + $path = preg_replace('#^\d+/#', '', $path) ?? $path; } else { $path = $gitRepository; } diff --git a/tests/Feature/Webhook/WebhookHmacTest.php b/tests/Feature/Webhook/WebhookHmacTest.php index be2417462..3f49ff43d 100644 --- a/tests/Feature/Webhook/WebhookHmacTest.php +++ b/tests/Feature/Webhook/WebhookHmacTest.php @@ -509,6 +509,48 @@ function createApplicationWithWebhook(string $repo = 'test-org/test-repo', strin expect($response->getContent())->not->toContain('No applications found'); }); + test('gitlab matches scp-style ssh repository URL with custom port', function () { + $app = createApplicationWithWebhook(overrides: [ + 'git_repository' => 'git@gitlab.example.com:2222/services/xyz.git', + 'git_branch' => 'master', + ]); + $secret = $app->manual_webhook_secret_gitlab; + + $response = $this->postJson('/webhooks/source/gitlab/events/manual', [ + 'object_kind' => 'push', + 'ref' => 'refs/heads/master', + 'project' => ['path_with_namespace' => 'services/xyz'], + 'after' => 'abc123', + 'commits' => [], + ], [ + 'X-Gitlab-Token' => $secret, + ]); + + $response->assertOk(); + expect($response->getContent())->not->toContain('No applications found'); + }); + + test('gitlab matches scp-style ssh repository URL without port', function () { + $app = createApplicationWithWebhook(overrides: [ + 'git_repository' => 'git@gitlab.example.com:services/xyz.git', + 'git_branch' => 'master', + ]); + $secret = $app->manual_webhook_secret_gitlab; + + $response = $this->postJson('/webhooks/source/gitlab/events/manual', [ + 'object_kind' => 'push', + 'ref' => 'refs/heads/master', + 'project' => ['path_with_namespace' => 'services/xyz'], + 'after' => 'abc123', + 'commits' => [], + ], [ + 'X-Gitlab-Token' => $secret, + ]); + + $response->assertOk(); + expect($response->getContent())->not->toContain('No applications found'); + }); + test('github matches repository case-insensitively', function () { $app = createApplicationWithWebhook(overrides: [ 'git_repository' => 'https://github.com/Test-Org/Test-Repo.git', From 1b68f11ec0864f3de0a7dac017b2fd68aec0d6f3 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Sun, 31 May 2026 21:47:18 +0200 Subject: [PATCH 2/2] fix(cleanup): disable unreachable self-hosted servers Preserve self-hosted server IPs during unreachable cleanup and force-disable them instead. Keep cloud cleanup behavior overwriting the IP, with test coverage for both paths. --- .../Commands/CleanupUnreachableServers.php | 10 +++++-- .../Feature/CleanupUnreachableServersTest.php | 28 ++++++++++++++++++- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/app/Console/Commands/CleanupUnreachableServers.php b/app/Console/Commands/CleanupUnreachableServers.php index 09563a2c3..666e98a18 100644 --- a/app/Console/Commands/CleanupUnreachableServers.php +++ b/app/Console/Commands/CleanupUnreachableServers.php @@ -18,9 +18,13 @@ public function handle() if ($servers->count() > 0) { foreach ($servers as $server) { echo "Cleanup unreachable server ($server->id) with name $server->name"; - $server->update([ - 'ip' => '1.2.3.4', - ]); + if (isCloud()) { + $server->update([ + 'ip' => '1.2.3.4', + ]); + } else { + $server->forceDisableServer(); + } } } } diff --git a/tests/Feature/CleanupUnreachableServersTest.php b/tests/Feature/CleanupUnreachableServersTest.php index c06944969..8849b1ca0 100644 --- a/tests/Feature/CleanupUnreachableServersTest.php +++ b/tests/Feature/CleanupUnreachableServersTest.php @@ -6,7 +6,30 @@ uses(RefreshDatabase::class); -it('cleans up servers with unreachable_count >= 3 after 7 days', function () { +it('disables (non-destructively) self-hosted servers with unreachable_count >= 3 after 7 days', function () { + config(['constants.coolify.self_hosted' => true]); + + $team = Team::factory()->create(); + $server = Server::factory()->create([ + 'team_id' => $team->id, + 'unreachable_count' => 50, + 'unreachable_notification_sent' => true, + 'updated_at' => now()->subDays(8), + ]); + + $originalIp = (string) $server->ip; + + $this->artisan('cleanup:unreachable-servers')->assertSuccessful(); + + $server->refresh(); + // IP must be preserved — never overwritten on self-hosted. + expect($server->ip)->toBe($originalIp); + expect($server->settings->force_disabled)->toBeTrue(); +}); + +it('overwrites the IP with 1.2.3.4 on cloud for servers with unreachable_count >= 3 after 7 days', function () { + config(['constants.coolify.self_hosted' => false]); + $team = Team::factory()->create(); $server = Server::factory()->create([ 'team_id' => $team->id, @@ -36,6 +59,7 @@ $server->refresh(); expect($server->ip)->toBe($originalIp); + expect($server->settings->force_disabled)->toBeFalse(); }); it('does not clean up servers updated within 7 days', function () { @@ -53,6 +77,7 @@ $server->refresh(); expect($server->ip)->toBe($originalIp); + expect($server->settings->force_disabled)->toBeFalse(); }); it('does not clean up servers without notification sent', function () { @@ -70,4 +95,5 @@ $server->refresh(); expect($server->ip)->toBe($originalIp); + expect($server->settings->force_disabled)->toBeFalse(); });