fix: GitHub Enterprise Server support for self-managed GitHub Apps (#8507)

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* chore: update ee-repo-ref to 6bb0ff0 (includes GHE fixes)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
This commit is contained in:
Alexander Petric
2026-03-26 02:26:57 -04:00
committed by GitHub
parent cb8b264dee
commit 935fb44c84
8 changed files with 52 additions and 45 deletions
@@ -15,7 +15,7 @@
]
},
"nullable": [
true
null
]
},
"hash": "5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55"
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "SELECT args as \"args: sqlx::types::Json<Box<RawValue>>\"\n FROM v2_job\n WHERE id = $1",
"query": "SELECT args as \"args: sqlx::types::Json<Box<RawValue>>\"\n FROM v2_job\n WHERE id = $1",
"describe": {
"columns": [
{
@@ -18,5 +18,5 @@
true
]
},
"hash": "d1dcc7fc8a1e1bc4dad263ec5163a94fca9dd95cc3b26b33611eab9d2a261141"
"hash": "97cf826b271cf064182382c924188fee392ed9cff6ae446abc86170984304a25"
}
+1 -1
View File
@@ -1 +1 @@
a1274aa11a83f608eacc32c0d449ca3527d98c15
6bb0ff0c40300dfc6049f8e027d8161a9f104d50
@@ -689,20 +689,20 @@ export const settings: Record<string, Setting[]> = {
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<string, string> = {
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<string, string> = {
'Object Storage': 'object_storage',
Jobs: 'jobs',
'Private Hub': 'private_hub',
'GitHub Enterprise App': 'github_enterprise_app',
'GitHub App': 'github_enterprise_app',
WebSocket: 'websocket'
}
@@ -22,7 +22,7 @@
<div class="space-y-6">
<Toggle
size="xs"
options={{ right: 'Self-managed GitHub App (for GHES or custom GitHub App)' }}
options={{ right: 'Self-managed GitHub App' }}
checked={selfManaged}
on:change={() => {
$values['github_enterprise_app'] = {
@@ -45,14 +45,14 @@
<div class="mt-2 p-3 bg-surface rounded text-2xs text-secondary space-y-2">
<p>
<strong>1.</strong> On your GitHub instance, go to
<strong>Settings &rarr; Developer settings &rarr; GitHub Apps &rarr; New GitHub App</strong
<strong
>Settings &rarr; Developer settings &rarr; GitHub Apps &rarr; New GitHub App</strong
>.
</p>
<p><strong>2.</strong> Fill in the required fields:</p>
<ul class="list-disc ml-4 space-y-1">
<li>
<strong>GitHub App name</strong>: e.g. <code>windmill-sync</code> (this becomes the app
slug)
<strong>GitHub App name</strong>: e.g. <code>windmill-sync</code> (this becomes the app slug)
</li>
<li>
<strong>Homepage URL</strong>: your Windmill instance URL
@@ -80,13 +80,12 @@
<strong>App ID</strong> and <strong>Client ID</strong>.
</p>
<p>
<strong>6.</strong> Scroll down and click <strong>Generate a private key</strong>. Save the
downloaded <code>.pem</code> file — paste its contents into the Private Key field below.
<strong>6.</strong> Scroll down and click <strong>Generate a private key</strong>. Save
the downloaded <code>.pem</code> file — paste its contents into the Private Key field below.
</p>
<p>
<strong>7.</strong> The <strong>App Slug</strong> is the URL-friendly name shown in the
app's URL (e.g. <code>github.com/apps/<strong>windmill-sync</strong></code
>).
app's URL (e.g. <code>github.com/apps/<strong>windmill-sync</strong></code>).
</p>
<p>
<strong>8.</strong> The <strong>Base URL</strong> is your GitHub instance root (e.g.
+5 -1
View File
@@ -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}`
}
+8 -4
View File
@@ -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"
}
+19 -19
View File
@@ -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 @@
<div class="w-full max-w-3xl p-8 rounded-md border bg-surface shadow-sm">
{#if isLoading}
<div class="flex flex-col items-center justify-center py-8">
<div class="animate-spin h-8 w-8 border-2 border-primary border-t-transparent rounded-full mb-4"></div>
<div
class="animate-spin h-8 w-8 border-2 border-primary border-t-transparent rounded-full mb-4"
></div>
<p class="text-lg text-secondary">Processing GitHub app installation...</p>
</div>
{:else if isSuccess}
@@ -139,7 +138,8 @@
Windmill GitHub app installation completed successfully
</h1>
<p class="text-secondary mb-8">
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.
</p>
<button
onclick={closeWindow}