> ## Documentation Index
> Fetch the complete documentation index at: https://oxy.tech/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# GitHub App Setup

> Configure a GitHub App to enable workspace import and Git integration in multi-workspace mode

<RewriteBanner />

<Note>
  **For operators.** This guide is for the person who installs and configures the Oxygen server. End users who want to import a repository just click **Import from GitHub** in the Workspaces UI — they don't need to read this.
</Note>

## Overview

In multi-workspace mode, Oxygen uses a **GitHub App** (not a personal OAuth app) to:

* Import repositories as workspaces ("Import from GitHub")
* Clone repositories on the server when a workspace is created
* Read and push commits through the built-in IDE Git panel

The same GitHub App handles both user authentication and repository access. This guide walks through creating the app, configuring permissions, and setting the required environment variables.

<Note>
  GitHub App integration is only needed for **multi-workspace mode** (the default `oxy serve`). If you run `oxy serve --local`, the workspace files already exist on disk and no GitHub integration is required.
</Note>

***

## Step 1: Create the GitHub App

1. Go to **GitHub → Settings → Developer settings → GitHub Apps → New GitHub App**
   *(For an organization app, go to **Organization Settings → Developer settings**)*

2. Fill in the app details:

   | Field                                       | Value                                                     |
   | ------------------------------------------- | --------------------------------------------------------- |
   | **GitHub App name**                         | e.g. `my-company-oxy`                                     |
   | **Homepage URL**                            | Your Oxygen instance URL (e.g. `https://app.example.com`) |
   | **Callback URL**                            | `https://your-domain.com/github/callback`                 |
   | **Webhook**                                 | Uncheck "Active" — Oxygen does not use GitHub webhooks    |
   | **Where can this GitHub App be installed?** | "Any account" (or "Only on this account" for org-only)    |

3. Click **Create GitHub App**.

***

## Step 2: Configure Permissions

After creating the app, go to **Permissions & Events** and set:

### Repository permissions

| Permission   | Level        | Purpose                                   |
| ------------ | ------------ | ----------------------------------------- |
| **Contents** | Read & Write | Clone repositories, read and push commits |
| **Metadata** | Read-only    | Required by GitHub for all apps           |

### Organization permissions

| Permission  | Level     | Purpose                                                                                                            |
| ----------- | --------- | ------------------------------------------------------------------------------------------------------------------ |
| **Members** | Read-only | **Required** — allows regular org members (not just admins) to see this installation during the OAuth connect flow |

<Warning>
  Without **Organization → Members: Read**, only GitHub org owners and admins will be able to connect their account. Regular org members will see "GitHub App not installed" even after the app has been installed on the org. Enable this permission and re-install the app if this happens.
</Warning>

Leave all other permissions at "No access" and save.

***

## Step 3: Generate a Private Key

1. Scroll to the **Private keys** section on the app settings page.
2. Click **Generate a private key**.
3. GitHub downloads a `.pem` file — keep this safe.

The private key content is the value for `GITHUB_APP_PRIVATE_KEY`. It should start with `-----BEGIN RSA PRIVATE KEY-----`.

***

## Step 4: Note Your App Credentials

From the app settings page, collect these values:

| Value             | Where to find it                    | Environment variable   |
| ----------------- | ----------------------------------- | ---------------------- |
| **App ID**        | Top of the app settings page        | `GITHUB_APP_ID`        |
| **App slug**      | URL: `github.com/apps/{slug}`       | `GITHUB_APP_SLUG`      |
| **Client ID**     | "OAuth App" section                 | `GITHUB_CLIENT_ID`     |
| **Client secret** | Generate one in "OAuth App" section | `GITHUB_CLIENT_SECRET` |

***

## Step 5: Set Environment Variables

Set the following on your Oxygen server:

```bash theme={null}
# GitHub App credentials
GITHUB_APP_ID=123456
GITHUB_APP_SLUG=my-company-oxy
GITHUB_APP_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA...
...your key content...
-----END RSA PRIVATE KEY-----"

# GitHub OAuth credentials (same App — used for user login)
GITHUB_CLIENT_ID=Iv23liqo62PgXXXXXX
GITHUB_CLIENT_SECRET=abc123...

# HMAC secret for signing OAuth state and selection tokens
# Generate once and store permanently — changing this invalidates all in-flight OAuth flows
GITHUB_STATE_SECRET=$(openssl rand -hex 32)
```

<Warning>
  `GITHUB_STATE_SECRET` must stay the same across server restarts and deploys. If it changes, users in the middle of an OAuth flow will see "Invalid state parameter" and have to start over. Generate it once, store it in your secrets manager, and never rotate it unless you intentionally want to invalidate active sessions.
</Warning>

<Warning>
  The `GITHUB_APP_PRIVATE_KEY` value must include the full PEM block with literal newlines, not `\n` escape sequences. In a `.env` file, wrap the value in double quotes and paste the key with actual line breaks.
</Warning>

### Formatting the private key in `.env`

```bash theme={null}
# .env
GITHUB_APP_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA0Z3VS5JJcds3xHn/ygWep4Xh3bzbFa/DJdTpPOBdLQmBFHDm
...rest of key...
-----END RSA PRIVATE KEY-----"
```

### Formatting the private key in Kubernetes secrets

```bash theme={null}
# Encode the .pem file contents as base64
cat your-app.private-key.pem | base64 -w 0
```

Store the base64 value in your Kubernetes secret and reference it with `envFrom` or `externalSecrets`.

***

## Step 6: Install the App

Before users can import repositories, the GitHub App must be installed on the target GitHub organization or user account:

1. Go to `https://github.com/apps/{your-app-slug}`
2. Click **Install** and select the organization/account
3. Choose "All repositories" or specific repositories
4. Click **Install**

After installation, users can connect their account by clicking **Sign in with GitHub** in the Workspaces UI.

***

## Step 7: Verify Callback URL Routing

Oxygen derives the OAuth callback URL automatically from the `Origin` header of the browser request — no hardcoding is needed. However, ensure:

1. **The callback URL in GitHub** matches the domain your users access. For example, if users go to `https://app.example.com`, the callback must be `https://app.example.com/github/callback`.

2. **Your reverse proxy forwards the `Origin` header** to the Oxygen backend. Most proxies (nginx, Caddy, ALB) do this by default.

3. **`OXY_API_URL`** is set if your backend is at a different URL than the frontend:
   ```bash theme={null}
   OXY_API_URL=https://app.example.com/api
   ```

***

## How the Connect Flow Works for Users

When a user clicks **Import from GitHub** in the Workspaces UI, Oxygen walks them through connecting their GitHub account and selecting a repository.

### Step 1 — Connect a GitHub account

Oxygen opens a GitHub OAuth popup. The user authorizes the app, and GitHub redirects back with a short-lived code that Oxygen exchanges for an access token. This token is used to discover which GitHub App installations are visible to that user.

There are three possible outcomes:

| Outcome                          | What the user sees                                                               |
| -------------------------------- | -------------------------------------------------------------------------------- |
| **One installation found**       | Oxygen auto-connects it — the user skips straight to repository selection        |
| **Multiple installations found** | A picker lists the available GitHub accounts/organizations; the user chooses one |
| **No installation found**        | A message tells the user to ask their admin to install the GitHub App first      |

### Step 2 — Select a repository

Once an installation (GitHub account or organization) is connected, the user sees the repositories that the GitHub App has access to. They pick one and Oxygen clones it as a new workspace.

### Connecting a different account

If a user wants to import from a different GitHub account or organization, they can click **Use a different GitHub account** in the connect dialog. This opens a fresh OAuth popup and lets them authorize a different account — the newly authorized account replaces the previous one for future imports.

<Note>
  The GitHub App must be installed on any account or organization before its repositories appear in the picker. See [Step 6: Install the App](#step-6-install-the-app) above, or ask the org owner to install it.
</Note>

***

## Security: How Installation Selection Is Protected

When multiple installations are shown, Oxygen issues a short-lived **selection token** — an HMAC-signed value encoding the eligible installation IDs, bound to the user's ID and valid for 10 minutes. When the user picks one:

1. The client sends `{ installation_id, selection_token }` back to the server
2. The server verifies the signature, confirms the user ID matches, checks the token is not expired, and validates that the chosen ID is in the token's list
3. Only then is the namespace created

This ensures a user can only connect an installation they were explicitly shown — installation IDs from other tenants cannot be guessed or injected.

***

## Full Environment Variable Reference

```bash theme={null}
# ── Required for GitHub workspace import ─────────────────────────────
GITHUB_APP_ID=123456                    # Numeric app ID from app settings page
GITHUB_APP_SLUG=my-company-oxy          # App slug (from the app's URL on github.com)
GITHUB_APP_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----"          # Full PEM block from the downloaded .pem file
GITHUB_CLIENT_ID=Iv23liqo62PgXXXXXX    # OAuth Client ID from app settings
GITHUB_CLIENT_SECRET=abc123...          # OAuth Client Secret from app settings
GITHUB_STATE_SECRET=random-64-char-hex  # Random secret for HMAC signing (state + selection tokens)
```

***

## Troubleshooting

**"GitHub App not installed" — but the app IS installed**

The most common cause: the **Organization → Members: Read** permission is not enabled on the GitHub App.
`GET /user/installations` only returns installations the OAuth user can see. Without Members: Read,
only org owners and admins are included; regular members get an empty list, which Oxygen reports as `not_installed`.

Fix:

1. Go to your GitHub App settings → **Permissions & Events → Organization permissions**
2. Set **Members** to **Read-only** and save
3. GitHub will ask you to re-approve the updated permissions — click **Accept**
4. Try connecting again

**"GitHub callback redirects to localhost"**

The `redirect_uri` is derived from the `Origin` header. Check that your proxy is forwarding the `Origin` header and that the correct callback URL is registered in the GitHub App settings.

**"GitHub App ID not configured in environment"**

`GITHUB_APP_ID` is not set or not being loaded. Verify your `.env` file is loaded at server startup or that the environment variable is injected by your deployment platform.

**"Invalid state parameter"**

The HMAC verification on the OAuth state failed. This usually means `GITHUB_STATE_SECRET` changed between when the user started the OAuth flow and when GitHub redirected back. Ensure the secret is consistent across server restarts (don't regenerate it on every deploy).

**"Invalid or expired selection token"**

The user took more than 10 minutes to pick an installation, or the server restarted and `GITHUB_STATE_SECRET` changed. The user should click **Sign in with GitHub** again to get a fresh token.

**Users see "Sign in with GitHub" but nothing happens**

Check that `GITHUB_CLIENT_ID` and `GITHUB_CLIENT_SECRET` are set correctly. Also verify the callback URL in the GitHub App settings matches your deployment domain exactly.

**"Use a different GitHub account" connects the wrong account**

The connect popup opens in a new browser window. If the user is already signed into GitHub as a different account in that window, they may need to sign out of GitHub first before authorizing. After a successful authorization, Oxygen always uses the most recently connected account.
