From 935fb44c848b8bf9430b5600dd3c3bedb2f89efd Mon Sep 17 00:00:00 2001 From: Alexander Petric Date: Thu, 26 Mar 2026 02:26:57 -0400 Subject: [PATCH] fix: GitHub Enterprise Server support for self-managed GitHub Apps (#8507) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: GitHub Enterprise Server (GHE) support for self-managed GitHub Apps - Fix GHE installation URL: use /github-apps/ path instead of /apps/ for non-github.com hosts - Fix double decodeURIComponent on OAuth state param (URLSearchParams already decodes) - Add client_id to self-managed GitHub App validation - Bump hub scripts to GHE-compatible versions (sync, test, init, clone) - Bump LATEST_GIT_SYNC_SCRIPT_PATH to hub/28176 - Rename "GitHub Enterprise App" → "GitHub App" in UI labels (it works for both) - Formatting fixes in GhesAppSettings.svelte and gh_success page EE ref: windmill-labs/windmill-ee-private@09c9ed1 Co-Authored-By: Claude Opus 4.6 * Update SQLx metadata * fix: handle GHE Cloud (*.ghe.com) app installation URL path GHE Cloud uses /apps/ like github.com, not /github-apps/ like self-hosted GHES. Docs: https://docs.github.com/en/enterprise-cloud@latest/apps/using-github-apps/installing-a-github-app-from-a-third-party Co-Authored-By: Claude Opus 4.6 * fix: handle GHE Cloud (*.ghe.com) installation URL and update ee-repo-ref GHE Cloud uses /apps/ like github.com, not /github-apps/ like self-hosted GHES. Docs: https://docs.github.com/en/enterprise-cloud@latest/apps/using-github-apps/installing-a-github-app-from-a-third-party Co-Authored-By: Claude Opus 4.6 * fix: update hubPaths to deprecate 28176 and use 28180 as latest sync script Aligns with main's LATEST_GIT_SYNC_SCRIPT_PATH bump in PR #8532. Co-Authored-By: Claude Opus 4.6 * chore: update ee-repo-ref to 6bb0ff0 (includes GHE fixes) Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Claude Opus 4.6 Co-authored-by: windmill-internal-app[bot] --- ...153c43903f929ae5d62fbba12610f89c36d55.json | 2 +- ...8fee392ed9cff6ae446abc86170984304a25.json} | 4 +- backend/ee-repo-ref.txt | 2 +- .../src/lib/components/instanceSettings.ts | 18 ++++----- .../instanceSettings/GhesAppSettings.svelte | 15 ++++---- frontend/src/lib/githubApp.ts | 6 ++- frontend/src/lib/hubPaths.json | 12 ++++-- frontend/src/routes/gh_success/+page.svelte | 38 +++++++++---------- 8 files changed, 52 insertions(+), 45 deletions(-) rename backend/.sqlx/{query-d1dcc7fc8a1e1bc4dad263ec5163a94fca9dd95cc3b26b33611eab9d2a261141.json => query-97cf826b271cf064182382c924188fee392ed9cff6ae446abc86170984304a25.json} (69%) diff --git a/backend/.sqlx/query-5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55.json b/backend/.sqlx/query-5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55.json index 36ddb8ab9f..713ccb9dd3 100644 --- a/backend/.sqlx/query-5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55.json +++ b/backend/.sqlx/query-5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55.json @@ -15,7 +15,7 @@ ] }, "nullable": [ - true + null ] }, "hash": "5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55" diff --git a/backend/.sqlx/query-d1dcc7fc8a1e1bc4dad263ec5163a94fca9dd95cc3b26b33611eab9d2a261141.json b/backend/.sqlx/query-97cf826b271cf064182382c924188fee392ed9cff6ae446abc86170984304a25.json similarity index 69% rename from backend/.sqlx/query-d1dcc7fc8a1e1bc4dad263ec5163a94fca9dd95cc3b26b33611eab9d2a261141.json rename to backend/.sqlx/query-97cf826b271cf064182382c924188fee392ed9cff6ae446abc86170984304a25.json index 4a0592701c..644a9eb8f2 100644 --- a/backend/.sqlx/query-d1dcc7fc8a1e1bc4dad263ec5163a94fca9dd95cc3b26b33611eab9d2a261141.json +++ b/backend/.sqlx/query-97cf826b271cf064182382c924188fee392ed9cff6ae446abc86170984304a25.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT args as \"args: sqlx::types::Json>\"\n FROM v2_job\n WHERE id = $1", + "query": "SELECT args as \"args: sqlx::types::Json>\"\n FROM v2_job\n WHERE id = $1", "describe": { "columns": [ { @@ -18,5 +18,5 @@ true ] }, - "hash": "d1dcc7fc8a1e1bc4dad263ec5163a94fca9dd95cc3b26b33611eab9d2a261141" + "hash": "97cf826b271cf064182382c924188fee392ed9cff6ae446abc86170984304a25" } diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index ad995f1326..24741f225a 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -a1274aa11a83f608eacc32c0d449ca3527d98c15 \ No newline at end of file +6bb0ff0c40300dfc6049f8e027d8161a9f104d50 \ No newline at end of file diff --git a/frontend/src/lib/components/instanceSettings.ts b/frontend/src/lib/components/instanceSettings.ts index c6096fab7b..ca6b702795 100644 --- a/frontend/src/lib/components/instanceSettings.ts +++ b/frontend/src/lib/components/instanceSettings.ts @@ -689,20 +689,20 @@ export const settings: Record = { ee_only: 'HashiCorp Vault integration is an Enterprise Edition feature' } ], - 'GitHub Enterprise App': [ + 'GitHub App': [ { - label: 'GitHub Enterprise App', + label: 'GitHub App', description: - 'Configure a self-managed GitHub App for GitHub Enterprise Server (or any GitHub instance) to enable git sync without stats.windmill.dev.', + 'Configure a self-managed GitHub App to enable git sync without stats.windmill.dev.', key: 'github_enterprise_app', fieldType: 'github_enterprise_app', storage: 'setting', ee_only: '', error: - 'When self-managed mode is enabled, Base URL, App ID, App Slug, and Private Key are required.', + 'When self-managed mode is enabled, Base URL, App ID, App Slug, Client ID, and Private Key are required.', isValid: (v: any) => { if (!v?.self_managed) return true - return !!(v?.base_url && v?.app_id && v?.app_slug && v?.private_key) + return !!(v?.base_url && v?.app_id && v?.app_slug && v?.client_id && v?.private_key) } } ], @@ -848,9 +848,9 @@ export const instanceSettingsNavigationGroups = [ items: [ { id: 'github_enterprise_app', - label: 'GitHub Enterprise App', + label: 'GitHub App', aiId: 'instance-settings-github-enterprise-app', - aiDescription: 'Self-managed GitHub App for GitHub Enterprise Server git sync', + aiDescription: 'Self-managed GitHub App for git sync', isEE: true }, { @@ -899,7 +899,7 @@ export const tabToCategoryMap: Record = { object_storage: 'Object Storage', jobs: 'Jobs', private_hub: 'Private Hub', - github_enterprise_app: 'GitHub Enterprise App', + github_enterprise_app: 'GitHub App', websocket: 'WebSocket' } @@ -932,7 +932,7 @@ export const categoryToTabMap: Record = { 'Object Storage': 'object_storage', Jobs: 'jobs', 'Private Hub': 'private_hub', - 'GitHub Enterprise App': 'github_enterprise_app', + 'GitHub App': 'github_enterprise_app', WebSocket: 'websocket' } diff --git a/frontend/src/lib/components/instanceSettings/GhesAppSettings.svelte b/frontend/src/lib/components/instanceSettings/GhesAppSettings.svelte index 6ded7d82f2..3d6c314812 100644 --- a/frontend/src/lib/components/instanceSettings/GhesAppSettings.svelte +++ b/frontend/src/lib/components/instanceSettings/GhesAppSettings.svelte @@ -22,7 +22,7 @@
{ $values['github_enterprise_app'] = { @@ -45,14 +45,14 @@

1. On your GitHub instance, go to - Settings → Developer settings → GitHub Apps → New GitHub AppSettings → Developer settings → GitHub Apps → New GitHub App.

2. Fill in the required fields:

  • - GitHub App name: e.g. windmill-sync (this becomes the app - slug) + GitHub App name: e.g. windmill-sync (this becomes the app slug)
  • Homepage URL: your Windmill instance URL @@ -80,13 +80,12 @@ App ID and Client ID.

    - 6. Scroll down and click Generate a private key. Save the - downloaded .pem file — paste its contents into the Private Key field below. + 6. Scroll down and click Generate a private key. Save + the downloaded .pem file — paste its contents into the Private Key field below.

    7. The App Slug is the URL-friendly name shown in the - app's URL (e.g. github.com/apps/windmill-sync). + app's URL (e.g. github.com/apps/windmill-sync).

    8. The Base URL is your GitHub instance root (e.g. diff --git a/frontend/src/lib/githubApp.ts b/frontend/src/lib/githubApp.ts index 48c2cd1b56..2f6ec1c1de 100644 --- a/frontend/src/lib/githubApp.ts +++ b/frontend/src/lib/githubApp.ts @@ -138,7 +138,11 @@ export async function loadGithubInstallations( const ghesConfig: GetGhesConfigResponse = await GitSyncService.getGhesConfig() if (ghesConfig?.base_url && ghesConfig?.app_slug) { const ghesBaseUrl = ghesConfig.base_url.replace(/\/$/, '') - state.githubInstallationUrl = `${ghesBaseUrl}/apps/${ghesConfig.app_slug}/installations/new?state=${stateParam}` + // GHES (self-hosted) uses /github-apps/, github.com and GHE Cloud (*.ghe.com) use /apps/ + const hostname = new URL(ghesBaseUrl).hostname + const isGHES = hostname !== 'github.com' && !hostname.endsWith('.ghe.com') + const appsPath = isGHES ? 'github-apps' : 'apps' + state.githubInstallationUrl = `${ghesBaseUrl}/${appsPath}/${ghesConfig.app_slug}/installations/new?state=${stateParam}` } else { state.githubInstallationUrl = `https://github.com/apps/windmill-sync-helper/installations/new?state=${stateParam}` } diff --git a/frontend/src/lib/hubPaths.json b/frontend/src/lib/hubPaths.json index d62fffa097..ccdc663d18 100644 --- a/frontend/src/lib/hubPaths.json +++ b/frontend/src/lib/hubPaths.json @@ -22,10 +22,13 @@ "deprecated_gitSync_20": "hub/28102/sync-script-to-git-repo-windmill", "deprecated_gitSync_21": "hub/28131/sync-script-to-git-repo-windmill", "deprecated_gitSync_22": "hub/28159/sync-script-to-git-repo-windmill", - "deprecated_gitSync_latest": "hub/28160/sync-script-to-git-repo-windmill", - "gitSyncTest": "hub/19799/git-repo-test-read-write-windmill", + "deprecated_gitSync_23": "hub/28160/sync-script-to-git-repo-windmill", + "deprecated_gitSync_24": "hub/28176/sync-script-to-git-repo-windmill", + "deprecated_gitSync_latest": "hub/28180/sync-script-to-git-repo-windmill", + "gitSyncTest": "hub/28177/git-repo-test-read-write-windmill", "gitInitRepo_0": "hub/28134/git-sync%3A-init-repository-windmill", - "gitInitRepo": "hub/28158/git-sync%3A-init-repository-windmill", + "gitInitRepo_1": "hub/28158/git-sync%3A-init-repository-windmill", + "gitInitRepo": "hub/28174/git-sync%3A-init-repository-windmill", "slackErrorHandler": "hub/19741/workspace-or-schedule-error-handler-slack", "slackErrorHandler_0": "hub/9079/workspace-or-schedule-error-handler-slack", "slackErrorHandler_1": "hub/9206/workspace-or-schedule-error-handler-slack", @@ -41,5 +44,6 @@ "smtpReport": "hub/9086/smtp", "appReport": "hub/28076/app-report", "cloneRepoToS3forGitRepoViewer_0": "hub/19825/clone_repo_and_upload_to_instance_storage", - "cloneRepoToS3forGitRepoViewer": "hub/19827/clone_repo_and_upload_to_instance_storage" + "cloneRepoToS3forGitRepoViewer_1": "hub/19827/clone_repo_and_upload_to_instance_storage", + "cloneRepoToS3forGitRepoViewer": "hub/28175/clone_repo_and_upload_to_instance_storage" } diff --git a/frontend/src/routes/gh_success/+page.svelte b/frontend/src/routes/gh_success/+page.svelte index eab7502757..32fc31a6ea 100644 --- a/frontend/src/routes/gh_success/+page.svelte +++ b/frontend/src/routes/gh_success/+page.svelte @@ -34,7 +34,7 @@ let workspace_id: string try { - const state = JSON.parse(decodeURIComponent(stateParam)) + const state = JSON.parse(stateParam) workspace_id = state.workspace_id } catch { isLoading = false @@ -51,30 +51,27 @@ } try { - const response = await fetch( - `/api/w/${workspace_id}/github_app/ghes_installation_callback`, - { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - installation_id - }) - } - ) + const response = await fetch(`/api/w/${workspace_id}/github_app/ghes_installation_callback`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + installation_id + }) + }) if (!response.ok) { const errorData = await response.text() - throw new Error(errorData || 'Failed to complete GitHub Enterprise app installation') + throw new Error(errorData || 'Failed to complete GitHub app installation') } isSuccess = true - sendUserToast('GitHub Enterprise app installed successfully', false) + sendUserToast('GitHub app installed successfully', false) } catch (error) { - console.error('Error during GitHub Enterprise app installation:', error) + console.error('Error during GitHub app installation:', error) errorMessage = error instanceof Error ? error.message : 'Unknown error occurred' - sendUserToast(`Error installing GitHub Enterprise app: ${errorMessage}`, true) + sendUserToast(`Error installing GitHub app: ${errorMessage}`, true) } finally { isLoading = false } @@ -129,7 +126,9 @@

    {#if isLoading}
    -
    +

    Processing GitHub app installation...

    {:else if isSuccess} @@ -139,7 +138,8 @@ Windmill GitHub app installation completed successfully

    - The GitHub app has been successfully installed. You can now close this window and return to Windmill to start using the GitHub integration. + The GitHub app has been successfully installed. You can now close this window and return + to Windmill to start using the GitHub integration.