mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-23 08:00:45 +00:00
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:
+1
-1
@@ -15,7 +15,7 @@
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
true
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55"
|
||||
|
||||
+2
-2
@@ -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 @@
|
||||
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 → Developer settings → GitHub Apps → New GitHub App</strong
|
||||
<strong
|
||||
>Settings → Developer settings → GitHub Apps → 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.
|
||||
|
||||
@@ -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}`
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user