Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ For production deployments using cloud object storage (S3, Azure Blob, or GCS),

- **Ingress:** Choose between nginx-based Ingress (with presets for AWS ALB, GCP GCE, and Azure app routing) or Istio Gateway/VirtualService. See `helm/docs/03-configuration.md` for details.
- **TLS:** Terminate TLS at the ingress layer. The chart supports `ingress.tls` configuration for certificate secrets.
- **Outbound access:** Application pods need HTTPS egress to your SCM provider (GitHub, GitLab, Bitbucket, or Azure DevOps) for webhook delivery and API calls.
- **Outbound access:** Application pods need HTTPS egress to your SCM provider (GitHub, GitLab, Bitbucket Cloud, Bitbucket Data Center, or Azure DevOps) for webhook delivery and API calls.
- **Inbound access:** Your SCM provider must be able to reach the ingress endpoint at `global.webBaseUrl` to deliver webhooks.
- **Internal:** All inter-component communication stays within the cluster via ClusterIP services on ports 3000 (web), 5432 (PostgreSQL), 6379 (Valkey), and 8123/9000 (ClickHouse). If enabled, the optional SQL API uses a separate PostgreSQL-protocol service on its configured port.

Expand Down
9 changes: 5 additions & 4 deletions docker-compose/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ This removes the book demo / booking-gated onboarding screen for that org.
- `docs/05-scm-gitlab.md`
- `docs/06-scm-bitbucket.md`
- `docs/07-scm-azure-devops.md`
- `docs/08-admin-bootstrap.md`
- `docs/09-operations.md`
- `docs/10-troubleshooting.md`
- `docs/11-upgrades.md`
- `docs/08-scm-bitbucket-data-center.md`
- `docs/09-admin-bootstrap.md`
- `docs/10-operations.md`
- `docs/11-troubleshooting.md`
- `docs/12-upgrades.md`
2 changes: 1 addition & 1 deletion docker-compose/config/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ This directory stores SCM app configuration artifacts.

`SCM_APPS_CONFIG` is emitted into `generated/scm.env` and consumed by Docker Compose.
At least one SCM app is required.
Default slugs are `github`, `gitlab`, `bitbucket`, and `azure-devops`.
Default slugs are `github`, `gitlab`, `bitbucket`, `bitbucket-datacenter`, and `azure-devops`.
Change a slug only if you run multiple instances of the same provider.
11 changes: 11 additions & 0 deletions docker-compose/config/scm-apps.template.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,17 @@
"client_id": "YOUR_BITBUCKET_CLIENT_ID",
"client_secret": "YOUR_BITBUCKET_CLIENT_SECRET"
},
{
"provider": "bitbucket-datacenter",
"domain": "bitbucket.example.com",
"base_url": "https://bitbucket.example.com",
"slug": "bitbucket-datacenter",
"app_id": "bitbucket-datacenter",
"private_key": "YOUR_BITBUCKET_DATA_CENTER_HTTP_ACCESS_TOKEN",
"webhook_secret": "NOT_USED_FOR_BITBUCKET_DATA_CENTER",
"client_id": "YOUR_BITBUCKET_DATA_CENTER_OAUTH_CLIENT_ID",
"client_secret": "YOUR_BITBUCKET_DATA_CENTER_OAUTH_CLIENT_SECRET"
},
{
"provider": "azure-devops",
"domain": "dev.azure.com",
Expand Down
4 changes: 2 additions & 2 deletions docker-compose/docs/01-prereqs.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ Install:
You also need:

- A valid Git AI enterprise `LICENSE_KEY`
- At least one SCM app configured (GitHub, GitLab, Bitbucket, and/or Azure DevOps)
- For most installs, one app per provider with the default slug (`github`, `gitlab`, `bitbucket`, `azure-devops`)
- At least one SCM app configured (GitHub, GitLab, Bitbucket Cloud, Bitbucket Data Center, and/or Azure DevOps)
- For most installs, one app per provider with the default slug (`github`, `gitlab`, `bitbucket`, `bitbucket-datacenter`, `azure-devops`)

## Network Requirements

Expand Down
4 changes: 2 additions & 2 deletions docker-compose/docs/02-quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ Edit `.env`:
task scm:configure
```

The wizard lets you enable GitHub, GitLab, Bitbucket, and Azure DevOps independently.
The wizard lets you enable GitHub, GitLab, Bitbucket Cloud, Bitbucket Data Center, and Azure DevOps independently.
If all are skipped, it exits with an error because at least one SCM is required.
Keep the default slug unless you run multiple instances of the same provider: `github`, `gitlab`, `bitbucket`, `azure-devops`.
Keep the default slug unless you run multiple instances of the same provider: `github`, `gitlab`, `bitbucket`, `bitbucket-datacenter`, `azure-devops`.

## 4) Start Stack

Expand Down
5 changes: 3 additions & 2 deletions docker-compose/docs/03-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,11 @@ Provider buttons on the sign-in page are config-driven:

- GitHub button appears only if a GitHub app is configured.
- GitLab button appears only if a GitLab app is configured.
- Bitbucket button appears only if a Bitbucket app is configured.
- Bitbucket button appears only if a Bitbucket Cloud app is configured.
- A Bitbucket Data Center button appears for each configured instance.
- Azure DevOps button appears only if an Azure DevOps app is configured.

Slug guidance:

- Default slugs: `github`, `gitlab`, `bitbucket`, `azure-devops`
- Default slugs: `github`, `gitlab`, `bitbucket`, `bitbucket-datacenter`, `azure-devops`
- Change a slug only if you run multiple instances of the same provider
78 changes: 78 additions & 0 deletions docker-compose/docs/08-scm-bitbucket-data-center.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# SCM Setup: Bitbucket Data Center (Optional)

This setup is separate from Bitbucket Cloud. Git AI requires Bitbucket Data Center 8.8 or later because it creates a project-level webhook for each connected project.

## Prerequisites

- Git AI can reach the Bitbucket Data Center base URL.
- Bitbucket Data Center can reach `WEB_BASE_URL`.
- Both services use HTTPS with certificates trusted by the other service.
- You can create an incoming application link in Bitbucket.
- A dedicated Bitbucket service account is a project administrator for every project that Git AI will connect.

If Bitbucket is installed under a context path, include it in the base URL, for example `https://bitbucket.example.com/bitbucket`.

## Required URLs

If `WEB_BASE_URL=https://gitai.example.com` and you use the default slug:

- OAuth callback URL: `https://gitai.example.com/api/auth/oauth2/callback/bitbucket-datacenter`
- Project webhook base: `https://gitai.example.com/worker/scm-webhook/bitbucket-datacenter?connection_token=<token>`

Git AI creates and signs project webhooks automatically. Use the default slug `bitbucket-datacenter` unless you configure multiple Bitbucket Data Center instances. The slug is part of both URLs, so the callback URL in Bitbucket must match it exactly.

## Create the OAuth Incoming Link

Follow [Atlassian's incoming-link guide](https://confluence.atlassian.com/bitbucketserver/configure-an-incoming-link-1108483657.html):

1. In Bitbucket, go to **Administration -> Applications -> Application links**.
2. Select **Create link -> External application -> Incoming**.
3. Set **Redirect URL** to `https://gitai.example.com/api/auth/oauth2/callback/bitbucket-datacenter`.
4. Under **Repositories**, select **Read**. Leave the other scopes unselected.
5. Save the link.
6. Copy the generated **Client ID** and **Client secret**.

The authorizing user can only expose projects that their Bitbucket account is allowed to view.

## Create the HTTP Access Token

Git AI uses a separate service-account token to clone repositories and manage project webhooks:

1. Sign in as the dedicated service account.
2. Go to **Profile picture -> Manage account -> HTTP access tokens**.
3. Create a token dedicated to Git AI.
4. Grant **Project admin** and **Repository admin** permissions.
5. Copy the token when it is shown.

The service account must be a project administrator for each connected project. See Atlassian's [HTTP access token guide](https://confluence.atlassian.com/bitbucketserver/http-access-tokens-939515499.html) for token creation and expiry options.

## Credentials Needed by Wizard

- Bitbucket Data Center base URL, including any context path
- App slug (default `bitbucket-datacenter`)
- App identifier (default `bitbucket-datacenter`)
- Personal HTTP access token
- OAuth client ID
- OAuth client secret

Run:

```bash
task scm:configure
```

The wizard derives the `domain` from the base URL and supplies the app-level webhook placeholder. Each project connection receives its own generated webhook secret and path token.

## Post-Setup Verification

1. Open the Git AI sign-in page and confirm **Continue with Bitbucket Data Center** is shown.
2. Sign in through Bitbucket and approve repository read access.
3. In Git AI org SCM settings, connect a Bitbucket Data Center project.
4. In the Bitbucket project's webhook settings, confirm an active **Git AI** webhook was created.
5. Open or update a pull request and confirm its webhook delivery succeeds.

If project connection fails, verify the service-account token is not expired and has project-admin access. If OAuth or webhook delivery fails, verify both base URLs, the context path, DNS, TLS trust, and network reachability in both directions.

## Local Evaluation

For non-production validation, use Atlassian's official [`atlassian/bitbucket` image](https://hub.docker.com/r/atlassian/bitbucket) with a PostgreSQL database and an [Atlassian timebomb license](https://developer.atlassian.com/platform/marketplace/timebomb-licenses-for-testing-server-apps/). Timebomb licenses and HTTP-only OAuth overrides are for testing only; production integrations should use a normal Data Center license and HTTPS.
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ Only admin users are shown as eligible org owners. If you need to create an org
for another user, promote them to admin first with `task admin:grant`.

We recommend creating a single organization for your entire company. One org can
connect multiple SCM providers (GitHub, GitLab, Bitbucket, Azure DevOps) and manage all
connect multiple SCM providers (GitHub, GitLab, Bitbucket Cloud, Bitbucket Data Center, Azure DevOps) and manage all
repositories in one place. You can run this command again to create additional
organizations if needed, but most deployments only need one.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@
- Re-run `task scm:configure`
- Ensure `generated/scm.env` contains one non-empty JSON array
- Ensure at least one provider is configured
- Ensure providers are only `github`, `gitlab`, `bitbucket`, or `azure-devops`
- Ensure providers are only `github`, `gitlab`, `bitbucket`, `bitbucket-datacenter`, or `azure-devops`
- Ensure each SCM app slug is unique
- If you only have one app for a provider, use the default slug: `github`, `gitlab`, `bitbucket`, or `azure-devops`
- If you only have one app for a provider, use the default slug: `github`, `gitlab`, `bitbucket`, `bitbucket-datacenter`, or `azure-devops`
- Ensure Bitbucket Data Center entries include `private_key`; set `base_url` to the full instance URL when Bitbucket uses a context path
- Ensure Azure DevOps entries include `tenant_id` (`common` for multi-tenant apps)

## Analyze jobs failing immediately
Expand All @@ -36,14 +37,16 @@
- Check provider callback URL exactly matches `WEB_BASE_URL` paths:
- GitHub: `/api/auth/callback/github`
- GitLab: `/api/auth/callback/gitlab`
- Bitbucket: `/api/auth/oauth2/callback/bitbucket`
- Bitbucket Cloud: `/api/auth/oauth2/callback/bitbucket`
- Bitbucket Data Center: `/api/auth/oauth2/callback/<bitbucket-data-center-slug>`
- Azure DevOps: `/api/auth/oauth2/callback/azure-devops`

## Webhooks not arriving

- Provider must reach your `WEB_BASE_URL`
- Ensure firewall/DNS/reverse proxy forwards requests
- For GitHub, GitLab, and Bitbucket, confirm the provider webhook secret matches SCM config
- For Bitbucket Data Center, confirm Git AI created the project webhook and the service-account token has project-admin access
- For Azure DevOps, confirm the service hook was created by Git AI and includes the per-connection secret header

## BullMQ dashboard not reachable on `:3001`
Expand Down
File renamed without changes.
85 changes: 79 additions & 6 deletions docker-compose/scripts/configure-scm.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,32 @@ async function askYesNo(rl, prompt, defaultValue) {
}
}

function parseBitbucketDataCenterBaseUrl(value) {
let parsed;
try {
parsed = new URL(value);
} catch {
throw new Error("Bitbucket Data Center base URL must be an absolute HTTP or HTTPS URL.");
}
if (!["http:", "https:"].includes(parsed.protocol)) {
throw new Error("Bitbucket Data Center base URL must use HTTP or HTTPS.");
}
if (parsed.search || parsed.hash) {
throw new Error("Bitbucket Data Center base URL cannot contain a query string or fragment.");
}
return {
baseUrl: parsed.toString().replace(/\/+$/, ""),
domain: parsed.host,
};
}

function validateApps(apps) {
if (!Array.isArray(apps) || apps.length === 0) {
throw new Error("At least one SCM app is required.");
}

const required = ["provider", "domain", "slug", "app_id", "webhook_secret", "client_id", "client_secret"];
const supportedProviders = new Set(["github", "gitlab", "bitbucket", "azure-devops"]);
const supportedProviders = new Set(["github", "gitlab", "bitbucket", "bitbucket-datacenter", "azure-devops"]);
const seenSlugs = new Set();
apps.forEach((app, index) => {
for (const key of required) {
Expand All @@ -84,12 +103,21 @@ function validateApps(apps) {
const provider = app.provider.trim().toLowerCase();
if (!supportedProviders.has(provider)) {
throw new Error(
`SCM app #${index + 1} has unsupported provider '${app.provider}'. Supported: github, gitlab, bitbucket, azure-devops.`
`SCM app #${index + 1} has unsupported provider '${app.provider}'. Supported: github, gitlab, bitbucket, bitbucket-datacenter, azure-devops.`
);
}
if (provider === "azure-devops" && (typeof app.tenant_id !== "string" || app.tenant_id.trim() === "")) {
throw new Error(`SCM app #${index + 1} is missing required field 'tenant_id'.`);
}
if (provider === "bitbucket-datacenter") {
if (typeof app.private_key !== "string" || app.private_key.trim() === "") {
throw new Error(`SCM app #${index + 1} is missing required field 'private_key'.`);
}
if (typeof app.base_url !== "string" || app.base_url.trim() === "") {
throw new Error(`SCM app #${index + 1} is missing required field 'base_url'.`);
}
parseBitbucketDataCenterBaseUrl(app.base_url);
}

const slug = app.slug.trim();
if (seenSlugs.has(slug)) {
Expand All @@ -110,15 +138,16 @@ async function main() {
try {
console.log("Configure SCM apps for self-hosting");
console.log(`Detected WEB_BASE_URL: ${baseUrl}`);
console.log("Keep the default slug unless you run multiple instances of the same provider: github, gitlab, bitbucket, azure-devops.");
console.log("Keep the default slug unless you run multiple instances of the same provider: github, gitlab, bitbucket, bitbucket-datacenter, azure-devops.");
console.log("");

const enableGitHub = await askYesNo(rl, "Configure GitHub?", true);
const enableGitLab = await askYesNo(rl, "Configure GitLab?", false);
const enableBitbucket = await askYesNo(rl, "Configure Bitbucket?", false);
const enableBitbucket = await askYesNo(rl, "Configure Bitbucket Cloud?", false);
const enableBitbucketDataCenter = await askYesNo(rl, "Configure Bitbucket Data Center?", false);
const enableAzureDevOps = await askYesNo(rl, "Configure Azure DevOps?", false);

if (!enableGitHub && !enableGitLab && !enableBitbucket && !enableAzureDevOps) {
if (!enableGitHub && !enableGitLab && !enableBitbucket && !enableBitbucketDataCenter && !enableAzureDevOps) {
console.error("At least one SCM is required. Re-run this command and configure at least one provider.");
process.exit(1);
}
Expand Down Expand Up @@ -181,7 +210,7 @@ async function main() {
}

if (enableBitbucket) {
console.log("\nBitbucket configuration");
console.log("\nBitbucket Cloud configuration");
const domain = await askRequired(rl, "Bitbucket domain", "bitbucket.org");
const slug = await askRequired(
rl,
Expand All @@ -204,6 +233,45 @@ async function main() {
});
}

if (enableBitbucketDataCenter) {
console.log("\nBitbucket Data Center configuration");
const parsedBaseUrl = parseBitbucketDataCenterBaseUrl(
await askRequired(
rl,
"Bitbucket Data Center base URL (include any context path)",
"https://bitbucket.example.com"
)
);
const slug = await askRequired(
rl,
"Bitbucket Data Center app slug (change only for multiple instances)",
"bitbucket-datacenter"
);
const appId = await askRequired(
rl,
"Bitbucket Data Center app identifier",
"bitbucket-datacenter"
);
const personalAccessToken = await askRequired(
rl,
"Bitbucket Data Center personal HTTP access token"
);
const clientId = await askRequired(rl, "Bitbucket Data Center OAuth client ID");
const clientSecret = await askRequired(rl, "Bitbucket Data Center OAuth client secret");

apps.push({
provider: "bitbucket-datacenter",
domain: parsedBaseUrl.domain,
base_url: parsedBaseUrl.baseUrl,
slug,
app_id: appId,
private_key: personalAccessToken,
webhook_secret: "NOT_USED_FOR_BITBUCKET_DATA_CENTER",
client_id: clientId,
client_secret: clientSecret,
});
}

if (enableAzureDevOps) {
console.log("\nAzure DevOps configuration");
const domain = await askRequired(rl, "Azure DevOps domain", "dev.azure.com");
Expand Down Expand Up @@ -273,6 +341,11 @@ async function main() {
console.log(`- Bitbucket callback URL: ${baseUrl}/api/auth/oauth2/callback/bitbucket`);
console.log(`- Bitbucket webhook base: ${baseUrl}/worker/scm-webhook/${bitbucketSlug}?connection_token=<token>`);
}
if (enableBitbucketDataCenter) {
const bitbucketDataCenterSlug = apps.find((a) => a.provider === "bitbucket-datacenter")?.slug;
console.log(`- Bitbucket Data Center callback URL: ${baseUrl}/api/auth/oauth2/callback/${bitbucketDataCenterSlug}`);
console.log(`- Bitbucket Data Center webhook base: ${baseUrl}/worker/scm-webhook/${bitbucketDataCenterSlug}?connection_token=<token>`);
}
if (enableAzureDevOps) {
const azureDevOpsSlug = apps.find((a) => a.provider === "azure-devops")?.slug;
console.log(`- Azure DevOps callback URL: ${baseUrl}/api/auth/oauth2/callback/azure-devops`);
Expand Down
25 changes: 23 additions & 2 deletions docker-compose/scripts/doctor.sh
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ if (!Array.isArray(parsed) || parsed.length === 0) {
process.exit(1);
}
const required = ["provider", "domain", "slug", "app_id", "webhook_secret", "client_id", "client_secret"];
const supportedProviders = new Set(["github", "gitlab", "bitbucket", "azure-devops"]);
const supportedProviders = new Set(["github", "gitlab", "bitbucket", "bitbucket-datacenter", "azure-devops"]);
const seenSlugs = new Set();
for (const [index, app] of parsed.entries()) {
if (typeof app !== "object" || !app) {
Expand All @@ -84,7 +84,7 @@ for (const [index, app] of parsed.entries()) {
}
if (!supportedProviders.has(app.provider.trim().toLowerCase())) {
console.error(
`SCM_APPS_CONFIG[${index}] has unsupported provider '${app.provider}'. Supported: github, gitlab, bitbucket, azure-devops`
`SCM_APPS_CONFIG[${index}] has unsupported provider '${app.provider}'. Supported: github, gitlab, bitbucket, bitbucket-datacenter, azure-devops`
);
process.exit(1);
}
Expand All @@ -95,6 +95,27 @@ for (const [index, app] of parsed.entries()) {
console.error(`SCM_APPS_CONFIG[${index}] is missing tenant_id`);
process.exit(1);
}
if (app.provider.trim().toLowerCase() === "bitbucket-datacenter") {
if (typeof app.private_key !== "string" || app.private_key.trim() === "") {
console.error(`SCM_APPS_CONFIG[${index}] is missing private_key`);
process.exit(1);
}
if (app.base_url !== undefined) {
if (typeof app.base_url !== "string" || app.base_url.trim() === "") {
console.error(`SCM_APPS_CONFIG[${index}] has invalid base_url`);
process.exit(1);
}
try {
const baseUrl = new URL(app.base_url);
if (!["http:", "https:"].includes(baseUrl.protocol) || baseUrl.search || baseUrl.hash) {
throw new Error("must use HTTP or HTTPS without a query string or fragment");
}
} catch (error) {
console.error(`SCM_APPS_CONFIG[${index}] has invalid base_url: ${error.message}`);
process.exit(1);
}
}
}
const slug = app.slug.trim();
if (seenSlugs.has(slug)) {
console.error(`SCM_APPS_CONFIG has duplicate slug '${slug}'`);
Expand Down
Loading