diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fdfc33186..1d96de7646 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), - Save more than one named SMTP server on the transactional email and email notification pages, and switch between them - Give each SMTP server and Resend its own sender name and address, because each provider sends from its own verified domain - Disable a scheduled task automatically when it fails at least 5 times in a row and has had no successful run for 3 days. Coolify sends a notification on the scheduled task failure channels and shows the reason on the task page until you enable the task again +- Connect Forgejo instances, including Codeberg and self-hosted Forgejo, with the Gitea / Forgejo App source. A new Provider list on the create form fills in the Gitea.com or Codeberg URLs, and webhooks also accept the `X-Forgejo-*` headers ### Changed diff --git a/app/Http/Controllers/Webhook/Gitea.php b/app/Http/Controllers/Webhook/Gitea.php index bb34413b43..a62461bf7f 100644 --- a/app/Http/Controllers/Webhook/Gitea.php +++ b/app/Http/Controllers/Webhook/Gitea.php @@ -48,7 +48,7 @@ class Gitea extends Controller // Only users who may administer the source can complete OAuth and store tokens. if (! $request->user()->can('update', $giteaApp)) { - return redirect()->route('source.all')->with('error', 'You are not authorized to connect this Gitea App.'); + return redirect()->route('source.all')->with('error', 'You are not authorized to connect this Gitea / Forgejo App.'); } $baseUrl = rtrim($giteaApp->html_url, '/'); @@ -85,7 +85,7 @@ class Gitea extends Controller { try { $return_payloads = collect([]); - $x_gitea_event = Str::lower((string) $request->header('X-Gitea-Event')); + $x_gitea_event = Str::lower((string) ($request->header('X-Gitea-Event') ?? $request->header('X-Forgejo-Event'))); if ($x_gitea_event === 'ping') { return response('pong'); @@ -98,7 +98,7 @@ class Gitea extends Controller ]); } - $signature = (string) $request->header('X-Gitea-Signature'); + $signature = (string) ($request->header('X-Gitea-Signature') ?? $request->header('X-Forgejo-Signature')); if ($signature === '') { $signature = Str::after((string) $request->header('X-Hub-Signature-256'), 'sha256='); } @@ -365,8 +365,8 @@ class Gitea extends Controller { try { $return_payloads = collect([]); - $x_gitea_delivery = request()->header('X-Gitea-Delivery'); - $x_gitea_event = Str::lower((string) $request->header('X-Gitea-Event')); + $x_gitea_delivery = $request->header('X-Gitea-Delivery') ?? $request->header('X-Forgejo-Delivery'); + $x_gitea_event = Str::lower((string) ($request->header('X-Gitea-Event') ?? $request->header('X-Forgejo-Event'))); $x_hub_signature_256 = Str::after((string) $request->header('X-Hub-Signature-256'), 'sha256='); $content_type = $request->header('Content-Type'); $payload = $request->collect(); diff --git a/app/Livewire/Project/New/Select.php b/app/Livewire/Project/New/Select.php index 342cb1c942..da625ac934 100644 --- a/app/Livewire/Project/New/Select.php +++ b/app/Livewire/Project/New/Select.php @@ -185,8 +185,8 @@ class Select extends Component ], [ 'id' => 'private-gitea-app', - 'name' => 'Git Repository (with Gitea App)', - 'description' => 'Deploy public or private Gitea repositories through a Gitea App, with automatic webhooks and pull request previews.', + 'name' => 'Git Repository (with Gitea / Forgejo App)', + 'description' => 'Deploy public or private Gitea, Forgejo, or Codeberg repositories through a Gitea / Forgejo App, with automatic webhooks and pull request previews.', 'logo' => asset('svgs/gitea.svg'), ], ]; diff --git a/app/Livewire/Source/Gitea/Change.php b/app/Livewire/Source/Gitea/Change.php index 6db104020e..89fafa57bd 100644 --- a/app/Livewire/Source/Gitea/Change.php +++ b/app/Livewire/Source/Gitea/Change.php @@ -281,7 +281,7 @@ class Change extends Component $this->syncData(true); $this->gitea_app->save(); - $this->dispatch('success', 'Gitea App updated.'); + $this->dispatch('success', 'Gitea / Forgejo App updated.'); $this->dispatch('name-changed', uuid: $this->gitea_app->uuid, name: $this->gitea_app->name); } catch (\Throwable $e) { return handleError($e, $this); @@ -298,7 +298,7 @@ class Change extends Component $this->gitea_app->is_system_wide = $this->isSystemWide; $this->gitea_app->save(); - $this->dispatch('success', 'Gitea App updated.'); + $this->dispatch('success', 'Gitea / Forgejo App updated.'); } catch (\Throwable $e) { return handleError($e, $this); } @@ -310,7 +310,7 @@ class Change extends Component $this->authorize('view', $this->gitea_app); if (! $this->gitea_app->isConnected()) { - $this->dispatch('error', 'Gitea App is not connected. Please complete the OAuth flow first.'); + $this->dispatch('error', 'Gitea / Forgejo App is not connected. Please complete the OAuth flow first.'); return; } diff --git a/app/Livewire/Source/Gitea/Create.php b/app/Livewire/Source/Gitea/Create.php index b119b948bf..51463f6272 100644 --- a/app/Livewire/Source/Gitea/Create.php +++ b/app/Livewire/Source/Gitea/Create.php @@ -13,8 +13,16 @@ class Create extends Component { use AuthorizesRequests; + /** @var array Hosted instances that the provider choice fills in. */ + public const HOSTED_PROVIDERS = [ + 'gitea.com' => 'https://gitea.com', + 'codeberg.org' => 'https://codeberg.org', + ]; + public string $name; + public string $provider = 'gitea.com'; + public string $html_url = 'https://gitea.com'; public string $api_url = 'https://gitea.com/api/v1'; @@ -34,6 +42,20 @@ class Create extends Component $this->name = substr(generate_random_name(), 0, 30); } + public function selectProvider(string $provider): void + { + $this->provider = $provider; + + if (! isset(self::HOSTED_PROVIDERS[$provider])) { + return; + } + + $this->html_url = self::HOSTED_PROVIDERS[$provider]; + $this->api_url = $this->giteaApiUrlFromHtmlUrl($this->html_url); + $this->custom_user = 'git'; + $this->custom_port = 22; + } + public function updatingHtmlUrl(): void { $this->shouldDeriveApiUrlAfterHtmlUrlUpdate = blank($this->api_url) diff --git a/bootstrap/helpers/audit.php b/bootstrap/helpers/audit.php index 46ec4ba2b4..d6016c92fc 100644 --- a/bootstrap/helpers/audit.php +++ b/bootstrap/helpers/audit.php @@ -133,6 +133,7 @@ if (! function_exists('auditLogWebhookFailure')) { 'event_header' => $request?->header('X-GitHub-Event') ?? $request?->header('X-Gitlab-Event') ?? $request?->header('X-Gitea-Event') + ?? $request?->header('X-Forgejo-Event') ?? $request?->header('X-Event-Key'), ]; diff --git a/bootstrap/helpers/gitea.php b/bootstrap/helpers/gitea.php index b1911c481b..446ad5bdbc 100644 --- a/bootstrap/helpers/gitea.php +++ b/bootstrap/helpers/gitea.php @@ -13,7 +13,7 @@ const GITEA_PAGE_SIZE = 50; function refreshGiteaToken(GiteaApp $source): void { if (! $source->refresh_token) { - throw new RuntimeException('Gitea source has no refresh token. Please reconnect the Gitea app.'); + throw new RuntimeException('Gitea / Forgejo source has no refresh token. Please reconnect the Gitea / Forgejo App.'); } $safetyMargin = 60; diff --git a/resources/views/livewire/project/new/gitea-private-repository.blade.php b/resources/views/livewire/project/new/gitea-private-repository.blade.php index da18da3cf7..7f19b105cc 100644 --- a/resources/views/livewire/project/new/gitea-private-repository.blade.php +++ b/resources/views/livewire/project/new/gitea-private-repository.blade.php @@ -3,16 +3,16 @@
-

Gitea App

-

Connect a Gitea App before selecting a private repository.

+

Gitea / Forgejo App

+

Connect a Gitea / Forgejo App before selecting a private repository.

- - + @@ -23,10 +23,10 @@
-

Choose Gitea App

-

Select the Gitea App that can access the repository you want to deploy.

+

Choose Gitea / Forgejo App

+

Select the Gitea / Forgejo App that can access the repository you want to deploy.

- +
@@ -59,7 +59,7 @@

Choose repository

-

Search repositories available through the selected Gitea App.

+

Search repositories available through the selected Gitea / Forgejo App.

@if ($gitea_app_id) @@ -85,7 +85,7 @@
@else + description="Review this Gitea / Forgejo App configuration and grant access to a repository." /> @endif
diff --git a/resources/views/livewire/source/gitea/change.blade.php b/resources/views/livewire/source/gitea/change.blade.php index 2bb4d74b29..23798ab58b 100644 --- a/resources/views/livewire/source/gitea/change.blade.php +++ b/resources/views/livewire/source/gitea/change.blade.php @@ -1,6 +1,6 @@
- {{ $name ?: 'Gitea App' }} | Sources | Coolify + {{ $name ?: 'Gitea / Forgejo App' }} | Sources | Coolify @if ($isConnected) @@ -8,12 +8,12 @@

- {{ $name ?: 'Gitea App' }} + {{ $name ?: 'Gitea / Forgejo App' }}

- {{ filled($organization) ? 'Gitea App for '.$organization : 'Private Gitea source' }} + {{ filled($organization) ? 'Gitea / Forgejo App for '.$organization : 'Private Gitea / Forgejo source' }}

@@ -23,11 +23,11 @@ @endcan @can('delete', $gitea_app) - @endcan
@@ -44,20 +44,20 @@ @if (! isCloud())
@if ($isSystemWide)
- System-wide Gitea Apps are shared across all teams on this Coolify instance. Prefer team-specific apps when you need repository isolation. + System-wide Gitea / Forgejo Apps are shared across all teams on this Coolify instance. Prefer team-specific apps when you need repository isolation.
@endif @endif
+ helper="Configure this webhook URL in Gitea or Forgejo (user, organization, or repository settings → Webhooks → Add webhook → Gitea or Forgejo).">
+ helper="Set this as the Secret in a Gitea webhook (user, organization, or repository settings → Webhooks → Add webhook → Gitea or Forgejo), content type application/json, trigger on Push and Pull Request events. Stored encrypted." />
@@ -73,7 +73,7 @@ + helper="Client credentials from your Gitea or Forgejo OAuth2 application.">
@@ -85,10 +85,10 @@
+ helper="Override endpoints and SSH settings for self-hosted Gitea or Forgejo.">
+ label="Instance URL" /> @@ -110,10 +110,10 @@
+ description="Applications currently using this Gitea / Forgejo App." flush> @if ($applications->isEmpty()) @else
@@ -164,19 +164,19 @@

- {{ $name ?: 'Gitea App' }} + {{ $name ?: 'Gitea / Forgejo App' }}

- Finish connecting this Gitea App before using it as a source + Finish connecting this Gitea / Forgejo App before using it as a source

@can('delete', $gitea_app)
-
@endcan @@ -194,7 +194,7 @@ } }"> + description="In Gitea or Forgejo, open Settings → Applications → Manage OAuth2 Applications and create a new application with the redirect URI below.">
@@ -216,7 +216,7 @@
+ description="Paste the Client ID and Client secret from Gitea or Forgejo, then save."> Save @@ -224,10 +224,10 @@
+ helper="The Client ID from your Gitea or Forgejo OAuth2 application." /> + helper="The Client secret from your Gitea or Forgejo OAuth2 application. Saved encrypted." />
@@ -235,7 +235,7 @@ @if (! isCloud() || isDev())
- Gitea will redirect back to this Coolify URL. It must match the Redirect URI on your Gitea OAuth2 application exactly. + Gitea or Forgejo will redirect back to this Coolify URL. It must match the Redirect URI on your Gitea or Forgejo OAuth2 application exactly.
+ helper="Gitea or Forgejo will use this custom public URL. Do not include /webhooks." />
@endif @@ -276,16 +276,16 @@
- + + helper="Usually your instance URL with /api/v1 appended." /> @if (! isCloud())
+ helper="If checked, this Gitea / Forgejo App will be available for everyone in this Coolify instance." />
@endif
@@ -294,11 +294,11 @@ @if ($clientId) - +
- Connect to Gitea + Connect to Gitea / Forgejo diff --git a/resources/views/livewire/source/gitea/create.blade.php b/resources/views/livewire/source/gitea/create.blade.php index 5e6659b23a..63189bb517 100644 --- a/resources/views/livewire/source/gitea/create.blade.php +++ b/resources/views/livewire/source/gitea/create.blade.php @@ -1,9 +1,17 @@ @can('create', \App\Models\GiteaApp::class)

- Connect a Gitea OAuth2 application for private repositories, webhooks, and deployments. + Connect a Gitea or Forgejo OAuth2 application (Gitea.com, Codeberg, or your own instance) for private repositories, webhooks, and deployments.

+ +
+ helper="If checked, this Gitea / Forgejo App will be available for everyone in this Coolify instance." />
- System-wide Gitea Apps are available to every team on this instance. Prefer team-specific apps when you need repository isolation. + System-wide Gitea / Forgejo Apps are available to every team on this instance. Prefer team-specific apps when you need repository isolation.
@@ -27,10 +35,10 @@
+ }" x-effect="if ($wire.provider === 'self-hosted') open = true" class="rounded-lg border border-neutral-200 dark:border-white/[0.08]">
- + + helper="Usually your instance URL with /api/v1 appended." />
@@ -54,6 +62,6 @@ @else - You don't have permission to create new Gitea Apps. Contact your team administrator. + You don't have permission to create new Gitea / Forgejo Apps. Contact your team administrator. @endcan diff --git a/resources/views/source/all.blade.php b/resources/views/source/all.blade.php index 528f92d98c..a65829da22 100644 --- a/resources/views/source/all.blade.php +++ b/resources/views/source/all.blade.php @@ -44,12 +44,12 @@ - + @@ -71,7 +71,7 @@ 'name' => $source->name, 'provider' => match ($source->getMorphClass()) { 'App\\Models\\GithubApp' => 'GitHub', - 'App\\Models\\GiteaApp' => 'Gitea', + 'App\\Models\\GiteaApp' => 'Gitea / Forgejo', default => 'GitLab', }, 'organization' => $isGitlab ? $source->group_name : $source->organization, @@ -143,7 +143,7 @@
@elseif ($source->getMorphClass() === 'App\Models\GiteaApp') -
diff --git a/tests/Feature/GiteaSourceChangeViewTest.php b/tests/Feature/GiteaSourceChangeViewTest.php index 0fc92ea783..eea522b93a 100644 --- a/tests/Feature/GiteaSourceChangeViewTest.php +++ b/tests/Feature/GiteaSourceChangeViewTest.php @@ -48,7 +48,7 @@ describe('Gitea source setup view', function () { test('shows incomplete-setup guidance with the credential form', function () { Livewire::withQueryParams(['gitea_app_uuid' => $this->giteaApp->uuid]) ->test(Change::class) - ->assertSee('Finish connecting this Gitea App before using it as a source') + ->assertSee('Finish connecting this Gitea / Forgejo App before using it as a source') ->assertSee('https://gitea.com/user/settings/applications') ->assertSee('Client ID') ->assertSee('Client secret') diff --git a/tests/Feature/GiteaSourceListTest.php b/tests/Feature/GiteaSourceListTest.php index 879d928095..4fee67f288 100644 --- a/tests/Feature/GiteaSourceListTest.php +++ b/tests/Feature/GiteaSourceListTest.php @@ -36,7 +36,7 @@ test('sources page lists gitea apps with a link to their settings', function () $this->get(route('source.all')) ->assertOk() - ->assertSee('New Gitea App') + ->assertSee('New Gitea / Forgejo App') ->assertSee('Team Gitea') ->assertSee('Gitea · acme') ->assertSee(route('source.gitea.show', ['gitea_app_uuid' => $this->giteaApp->uuid]), false);