diff --git a/app/Livewire/Source/Github/Change.php b/app/Livewire/Source/Github/Change.php index 351407dac..4bd0b798a 100644 --- a/app/Livewire/Source/Github/Change.php +++ b/app/Livewire/Source/Github/Change.php @@ -47,19 +47,19 @@ class Change extends Component public int $customPort; - public int $appId; + public ?int $appId = null; - public int $installationId; + public ?int $installationId = null; - public string $clientId; + public ?string $clientId = null; - public string $clientSecret; + public ?string $clientSecret = null; - public string $webhookSecret; + public ?string $webhookSecret = null; public bool $isSystemWide; - public int $privateKeyId; + public ?int $privateKeyId = null; public ?string $contents = null; @@ -78,16 +78,16 @@ class Change extends Component 'htmlUrl' => 'required|string', 'customUser' => 'required|string', 'customPort' => 'required|int', - 'appId' => 'required|int', - 'installationId' => 'required|int', - 'clientId' => 'required|string', - 'clientSecret' => 'required|string', - 'webhookSecret' => 'required|string', + 'appId' => 'nullable|int', + 'installationId' => 'nullable|int', + 'clientId' => 'nullable|string', + 'clientSecret' => 'nullable|string', + 'webhookSecret' => 'nullable|string', 'isSystemWide' => 'required|bool', 'contents' => 'nullable|string', 'metadata' => 'nullable|string', 'pullRequests' => 'nullable|string', - 'privateKeyId' => 'required|int', + 'privateKeyId' => 'nullable|int', ]; public function boot() @@ -148,47 +148,48 @@ public function checkPermissions() try { $this->authorize('view', $this->github_app); + // Validate required fields before attempting to fetch permissions + $missingFields = []; + + if (! $this->github_app->app_id) { + $missingFields[] = 'App ID'; + } + + if (! $this->github_app->private_key_id) { + $missingFields[] = 'Private Key'; + } + + if (! empty($missingFields)) { + $fieldsList = implode(', ', $missingFields); + $this->dispatch('error', "Cannot fetch permissions. Please set the following required fields first: {$fieldsList}"); + + return; + } + + // Verify the private key exists and is accessible + if (! $this->github_app->privateKey) { + $this->dispatch('error', 'Private Key not found. Please select a valid private key.'); + + return; + } + GithubAppPermissionJob::dispatchSync($this->github_app); $this->github_app->refresh()->makeVisible('client_secret')->makeVisible('webhook_secret'); $this->dispatch('success', 'Github App permissions updated.'); } catch (\Throwable $e) { + // Provide better error message for unsupported key formats + $errorMessage = $e->getMessage(); + if (str_contains($errorMessage, 'DECODER routines::unsupported') || + str_contains($errorMessage, 'parse your key')) { + $this->dispatch('error', 'The selected private key format is not supported for GitHub Apps.

Please use an RSA private key in PEM format (BEGIN RSA PRIVATE KEY).

OpenSSH format keys (BEGIN OPENSSH PRIVATE KEY) are not supported.'); + + return; + } + return handleError($e, $this); } } - // public function check() - // { - - // Need administration:read:write permission - // https://docs.github.com/en/rest/actions/self-hosted-runners?apiVersion=2022-11-28#list-self-hosted-runners-for-a-repository - - // $github_access_token = generateGithubInstallationToken($this->github_app); - // $repositories = Http::withToken($github_access_token)->get("{$this->github_app->api_url}/installation/repositories?per_page=100"); - // $runners_by_repository = collect([]); - // $repositories = $repositories->json()['repositories']; - // foreach ($repositories as $repository) { - // $runners_downloads = Http::withToken($github_access_token)->get("{$this->github_app->api_url}/repos/{$repository['full_name']}/actions/runners/downloads"); - // $runners = Http::withToken($github_access_token)->get("{$this->github_app->api_url}/repos/{$repository['full_name']}/actions/runners"); - // $token = Http::withHeaders([ - // 'Authorization' => "Bearer $github_access_token", - // 'Accept' => 'application/vnd.github+json' - // ])->withBody(null)->post("{$this->github_app->api_url}/repos/{$repository['full_name']}/actions/runners/registration-token"); - // $token = $token->json(); - // $remove_token = Http::withHeaders([ - // 'Authorization' => "Bearer $github_access_token", - // 'Accept' => 'application/vnd.github+json' - // ])->withBody(null)->post("{$this->github_app->api_url}/repos/{$repository['full_name']}/actions/runners/remove-token"); - // $remove_token = $remove_token->json(); - // $runners_by_repository->put($repository['full_name'], [ - // 'token' => $token, - // 'remove_token' => $remove_token, - // 'runners' => $runners->json(), - // 'runners_downloads' => $runners_downloads->json() - // ]); - // } - - // } - public function mount() { try { @@ -340,10 +341,13 @@ public function createGithubAppManually() $this->authorize('update', $this->github_app); $this->github_app->makeVisible('client_secret')->makeVisible('webhook_secret'); - $this->github_app->app_id = '1234567890'; - $this->github_app->installation_id = '1234567890'; + $this->github_app->app_id = 1234567890; + $this->github_app->installation_id = 1234567890; $this->github_app->save(); - $this->dispatch('success', 'Github App updated.'); + + // Redirect to avoid Livewire morphing issues when view structure changes + return redirect()->route('source.github.show', ['github_app_uuid' => $this->github_app->uuid]) + ->with('success', 'Github App updated. You can now configure the details.'); } public function instantSave() diff --git a/app/Livewire/Source/Github/Create.php b/app/Livewire/Source/Github/Create.php index f5d851b64..2f1482c89 100644 --- a/app/Livewire/Source/Github/Create.php +++ b/app/Livewire/Source/Github/Create.php @@ -50,11 +50,9 @@ public function createGitHubApp() 'html_url' => $this->html_url, 'custom_user' => $this->custom_user, 'custom_port' => $this->custom_port, + 'is_system_wide' => $this->is_system_wide, 'team_id' => currentTeam()->id, ]; - if (isCloud()) { - $payload['is_system_wide'] = $this->is_system_wide; - } $github_app = GithubApp::create($payload); if (session('from')) { session(['from' => session('from') + ['source_id' => $github_app->id]]); diff --git a/app/Models/GithubApp.php b/app/Models/GithubApp.php index 5550df81f..0d643306c 100644 --- a/app/Models/GithubApp.php +++ b/app/Models/GithubApp.php @@ -12,6 +12,7 @@ class GithubApp extends BaseModel protected $casts = [ 'is_public' => 'boolean', + 'is_system_wide' => 'boolean', 'type' => 'string', ]; diff --git a/resources/views/livewire/source/github/change.blade.php b/resources/views/livewire/source/github/change.blade.php index 7e6256259..0758afb70 100644 --- a/resources/views/livewire/source/github/change.blade.php +++ b/resources/views/livewire/source/github/change.blade.php @@ -1,7 +1,7 @@
@if (data_get($github_app, 'app_id'))
-
+

GitHub App

@if (data_get($github_app, 'installation_id')) @@ -40,8 +40,8 @@ @else
-
-
+
+
Sync Name @@ -72,24 +72,29 @@ class="bg-transparent border-transparent hover:bg-transparent hover:border-trans helper="If checked, this GitHub App will be available for everyone in this Coolify instance." instantSave id="isSystemWide" />
+ @if ($isSystemWide) + + System-wide GitHub Apps are shared across all teams on this Coolify instance. This means any team can use this GitHub App to deploy applications from your repositories. For better security and isolation, it's recommended to create team-specific GitHub Apps instead. + + @endif @endif -
+
-
+
-
+
-
+
+

Permissions

@can('view', $github_app) Refetch @@ -120,7 +125,7 @@ class="bg-transparent border-transparent hover:bg-transparent hover:border-trans @endcan
-
+
Here you can find all resources that are using this source.
-
+ @if ($applications->isEmpty()) +
+ No resources are currently using this GitHub App. +
+ @else
-
-
-
- - - - - - - - - - - @forelse ($applications->sortBy('name',SORT_NATURAL) as $resource) +
+
+
+
+
- Project - - EnvironmentName - Type -
+ - - - - + + + + - @empty - @endforelse - -
- {{ data_get($resource->project(), 'name') }} - - {{ data_get($resource, 'environment.name') }} - {{ $resource->name }} - - - {{ str($resource->type())->headline() }} + Project + + EnvironmentName + Type +
+ + + @foreach ($applications->sortBy('name',SORT_NATURAL) as $resource) + + + {{ data_get($resource->project(), 'name') }} + + + {{ data_get($resource, 'environment.name') }} + + {{ $resource->name }} + + + + {{ str($resource->type())->headline() }} + + @endforeach + + +
-
+ @endif
@endif @else -
+

GitHub App

@can('delete', $github_app) @@ -228,7 +238,7 @@ class=""
@can('create', $github_app) @if (!isCloud() || isDev()) -
+
@if ($ipv4) @@ -250,7 +260,7 @@ class=""
@else -
+

Register a GitHub App

@@ -261,11 +271,11 @@ class="" @endif
- - - {{-- --}}
@else diff --git a/resources/views/livewire/source/github/create.blade.php b/resources/views/livewire/source/github/create.blade.php index f47575784..9d5189b43 100644 --- a/resources/views/livewire/source/github/create.blade.php +++ b/resources/views/livewire/source/github/create.blade.php @@ -9,17 +9,28 @@ placeholder="If empty, your GitHub user will be used." id="organization" label="Organization (on GitHub)" />
@if (!isCloud()) -
- +
+
+ +
+
+ +
+ System-wide GitHub Apps are shared across all teams on this Coolify instance. This means any team + can use this GitHub App to deploy applications from your repositories. For better security and + isolation, it's recommended to create team-specific GitHub Apps instead. +
+
+
@endif
+ activeAccordion: '', + setActiveAccordion(id) { + this.activeAccordion = (this.activeAccordion == id) ? '' : id + } + }" class="relative w-full py-2 mx-auto overflow-hidden text-sm font-normal rounded-md">