# Self-Managed Source Control

Connect Factory to a self-hosted GitHub Enterprise Server or GitLab instance with one organization-level OAuth app.

Factory connects to a self-managed GitHub Enterprise Server or GitLab instance through an OAuth application that you register on your own server. An admin registers the app once, enters its credentials in Factory, and authorizes it; from then on your organization's repositories are available in sessions like any other repository.

<Info>
  Connecting a self-managed GitHub Enterprise Server or GitLab instance requires the Enterprise plan.
</Info>

## Prerequisites

| Requirement | Detail |
| --- | --- |
| Factory role | **Owner** or **Manager**. Configuring an integration is a privileged action. |
| Server access | Permission to register an OAuth application (GitHub Enterprise Server) or an instance-wide application (GitLab). |
| Reachability | The instance must resolve to a public IP address. Factory validates and pins the resolved address to prevent server-side request forgery, so private-network-only hosts cannot be connected. If your instance sits behind a proxy or firewall, ask [support@factory.ai](mailto:support@factory.ai) for the Factory egress addresses to allowlist. |

Both integrations authorize **once per organization**. A single admin authorization is stored at the org level and used for every Factory operation against that server, so individual members never authorize the server themselves. Use a dedicated service account as the connecting admin rather than a personal employee account: it keeps org-wide access off one person's account lifecycle and lets you bound which repositories are exposed.

<LabeledDivider label='GitHub Enterprise Server' />

## Connect GitHub Enterprise Server

Factory requests the `repo` and `read:org` OAuth scopes and imports only **organization-level** repositories. Personal repositories owned by the authorizing account are never imported.

<Steps>
  <Step title="Register an OAuth app on your server">
    In your GitHub Enterprise Server instance, go to **Settings → Developer settings → OAuth Apps → New OAuth App**. Register it under an organization you own rather than a personal account.

    - **Application name:** `Factory Integration`
    - **Homepage URL:** `https://app.factory.ai`
    - **Authorization callback URL:** `https://api.factory.ai/api/integrations/redirect/github-es/callback`

    If you run Factory on a dedicated control plane, substitute your own Factory API domain in the callback URL.
  </Step>
  <Step title="Copy the credentials">
    Register the app, then copy the **Client ID** and generate a **Client Secret**. Store the secret in your secret manager before leaving the page; GitHub shows it once.
  </Step>
  <Step title="Enter the server details in Factory">
    Open <a href="https://app.factory.ai/settings/integrations">Settings → Integrations</a>, choose **GitHub Enterprise**, and enter your server domain (for example `https://github.your-company.com`), Client ID, and Client Secret. Factory normalizes and validates the domain on save.
  </Step>
  <Step title="Authorize the app">
    Start the connection. Factory redirects you to your instance to approve the OAuth app, then imports the repositories the authorizing account can reach. Enable the ones you want available in Factory from the repository list.
  </Step>
</Steps>

<LabeledDivider label='GitLab Self-Hosted' />

## Connect GitLab Self-Hosted

GitLab authorization runs as a dedicated instance user rather than the admin's own account, so repository access does not follow an individual's account lifecycle.

<Steps>
  <Step title="Create the instance application">
    In your GitLab instance, go to **Admin Area → Applications → Add new application**.

    - **Name:** `Factory Integration`
    - **Redirect URI:** `https://api.factory.ai/api/integrations/redirect/gitlab-sh/callback`
    - **Trusted:** yes
    - **Confidential:** yes
    - **Scopes:** `api`, `read_api`, `read_user`, `read_repository`, `write_repository`, `read_observability`, `write_observability`

    Save the application and note the **Application ID** and **Secret**. Factory requests a conservative subset of these scopes at authorization time (`api`, `read_user`, `read_repository`, `write_repository`) so the flow works on older GitLab versions.
  </Step>
  <Step title="Create the Factory Droid user">
    Go to **Admin Area → Users → New User** and create a user named `Factory Droid` with the username `factory-droid` and any email address you control. Then sign in as, or impersonate, that user before continuing.
  </Step>
  <Step title="Enter the application details in Factory">
    Open <a href="https://app.factory.ai/settings/integrations">Settings → Integrations</a>, click **Connect** next to GitLab, and toggle **Self-Hosted**. Enter your instance domain (use the externally reachable domain if you run split internal and external DNS), the Application ID, and the Secret, then save.
  </Step>
  <Step title="Authorize as the Factory Droid user">
    Click **Manage GitLab Self-Hosted Permissions**. A pop-up authorizes the integration against your instance. You must still be signed in as, or impersonating, the `factory-droid` user at this point, or the authorization is recorded against the wrong account. Your repositories then appear in the repository list, where you enable the ones Factory should use.
  </Step>
</Steps>

<LabeledDivider label='Operating the connection' />

## Token and credential lifecycle

| Event | What happens |
| --- | --- |
| Expiring tokens | Factory refreshes them automatically. No admin action is needed. |
| Non-expiring tokens | The authorization is long-lived and persists until it is revoked on your server. |
| Revoked authorization | The integration disconnects for the whole organization. An admin has to reconnect it. |
| Rotated client secret | Treated the same as a revocation. Update the secret in Factory and reauthorize. |

Because the authorization is org-wide, deleting or deactivating the connecting account on your server disconnects every member at once. This is the main reason to authorize as a service account.

## Troubleshooting

<Troubleshooting>
  <TroubleshootingItem title="A repository I expect is missing">
    Confirm the repository is owned by an organization the authorizing account can see. Personal repositories are never imported, and repositories outside the connected account's visibility are excluded. On GitLab, check that the `factory-droid` user has access to the project.
  </TroubleshootingItem>
  <TroubleshootingItem title="Repositories disappeared or authorization is failing">
    The org-level authorization has expired or was revoked on your server. Reconnect the integration from Settings → Integrations.
  </TroubleshootingItem>
  <TroubleshootingItem title="Factory rejects the server domain">
    The domain must resolve to a public IP address. Verify public DNS resolution from outside your network, and contact [support@factory.ai](mailto:support@factory.ai) for the Factory egress addresses if the instance is behind a proxy.
  </TroubleshootingItem>
  <TroubleshootingItem title="GitLab authorization landed on the wrong user">
    The pop-up authorized whichever GitLab session was active. Sign in as or impersonate `factory-droid`, then run **Manage GitLab Self-Hosted Permissions** again.
  </TroubleshootingItem>
</Troubleshooting>

Integration configuration and authorization changes are recorded as `integration_change` events in the [audit log](/enterprise/audit-log).

<RelatedLinks>
  <RelatedLink href="/enterprise/identity-and-access" title="Identity & Access">
    Roles, SSO, and the service accounts that should own org-wide integrations.
  </RelatedLink>
  <RelatedLink href="/enterprise/network-and-deployment" title="Deployment Patterns">
    Proxy, certificate, and network requirements for reaching your instance.
  </RelatedLink>
  <RelatedLink href="/software-factory/code-review-ci" title="Automated Code Review">
    Run Droid reviews on pull and merge requests in your CI.
  </RelatedLink>
  <RelatedLink href="/enterprise/audit-log" title="Audit Log">
    Track integration configuration changes as audit events.
  </RelatedLink>
</RelatedLinks>
