Skip to content

Create custom source-control providers

A provider profile defines a reusable provider type: credential environment variables, endpoint bindings, allowed request shapes, and permitted binaries. A provider instance stores the actual local credential for that type.

Provider profile hosts are not parameterized when an instance is created. Make one profile YAML and one unique profile ID for each source-control host.

GitHub Enterprise profile

Create provider-profiles/provider-github-enterprise-cli.yaml and replace the example hostname with the real GitHub Enterprise Server hostname:

id: github-enterprise-cli
display_name: GitHub Enterprise (CLI)
description: GitHub Enterprise API and read-only Git HTTPS access
category: source_control
credentials:
  - name: api_token
    description: GitHub Enterprise access token
    env_vars: [GH_ENTERPRISE_TOKEN, GITHUB_ENTERPRISE_TOKEN]
    required: true
    auth_style: bearer
    header_name: authorization
discovery:
  credentials: [api_token]
endpoints:
  - host: github.example.com
    port: 443
    protocol: rest
    enforcement: enforce
    rules:
      - allow: { method: GET, path: "/api/v3/**" }
      - allow: { method: HEAD, path: "/api/v3/**" }
      - allow: { method: OPTIONS, path: "/api/v3/**" }
      - allow: { method: GET, path: "/**/info/refs*" }
      - allow: { method: POST, path: "/**/git-upload-pack" }
binaries:
  - /usr/bin/gh
  - /usr/local/bin/gh
  - /usr/bin/git
  - /usr/local/bin/git
  - /usr/lib/git-core/**
  - /usr/bin/codex
  - /usr/local/bin/codex
  - /usr/lib/node_modules/@openai/**
  - /usr/local/bin/claude
  - /usr/bin/claude
  - /usr/local/bin/opencode
  - /usr/bin/opencode
  - /usr/lib/node_modules/@opencode/**
  - /usr/bin/curl

The GitHub Enterprise REST API normally uses /api/v3 and GraphQL uses /api/graphql. Confirm those paths for the deployed version. Keep the GraphQL endpoint in the user policy as described above.

The profile exports the enterprise-specific variables expected by gh. Set the same host in .exoshell.local.toml:

github_host = "github.example.com"

The launcher sets GH_HOST, rewrites matching SSH-style Git remotes to HTTPS, and configures Git to use gh auth git-credential. This selects the CLI and Git host only; it does not replace the host-specific provider profile or GraphQL policy entry.

GitLab self-managed profile

Create provider-profiles/provider-gitlab-enterprise-cli.yaml:

id: gitlab-enterprise-cli
display_name: GitLab Self-Managed (CLI)
description: GitLab API and read-only Git HTTPS access
category: source_control
credentials:
  - name: api_token
    description: GitLab access token
    env_vars: [GITLAB_TOKEN, GLAB_TOKEN]
    required: true
    auth_style: bearer
    header_name: authorization
discovery:
  credentials: [api_token]
endpoints:
  - host: gitlab.example.com
    port: 443
    protocol: rest
    enforcement: enforce
    allow_encoded_slash: true
    rules:
      - allow: { method: GET, path: "**" }
      - allow: { method: HEAD, path: "**" }
      - allow: { method: OPTIONS, path: "**" }
      - allow: { method: POST, path: "/**/git-upload-pack" }
binaries:
  - /usr/bin/glab
  - /usr/local/bin/glab
  - /usr/bin/git
  - /usr/local/bin/git
  - /usr/lib/git-core/**
  - /usr/bin/codex
  - /usr/local/bin/codex
  - /usr/lib/node_modules/@openai/**
  - /usr/local/bin/claude
  - /usr/bin/claude
  - /usr/local/bin/opencode
  - /usr/bin/opencode
  - /usr/lib/node_modules/@opencode/**
  - /usr/bin/curl

allow_encoded_slash is needed for common GitLab API paths. GitLab REST and Git HTTPS share one host, so explicit request rules are used instead of an access preset. Keep /api/graphql in the user policy.

Set the launcher host in .exoshell.local.toml:

gitlab_host = "gitlab.example.com"

The launcher then sets GITLAB_HOST, rewrites matching SSH-style Git remotes to HTTPS, and configures a Git credential helper that sends the provider-backed placeholder. The token-free glab-config.yml in the image registers the host without persisting its token.

Lint and import profiles

Lint each profile before importing it:

openshell provider profile lint \
  -f provider-profiles/provider-github-enterprise-cli.yaml
openshell provider profile lint \
  -f provider-profiles/provider-gitlab-enterprise-cli.yaml

openshell provider profile import \
  -f provider-profiles/provider-github-enterprise-cli.yaml
openshell provider profile import \
  -f provider-profiles/provider-gitlab-enterprise-cli.yaml

For profiles supplied by ExOShell, the profile update helper applies the checkout's description, endpoints, and binaries while preserving other live fields. Use the manual workflow below for custom profiles or changes to other fields.

Profile import is create-only. To update an imported profile, export it first, preserve its resource_version, apply the edits to that exported document, and submit an update:

openshell provider profile export github-enterprise-cli -o yaml \
  > /tmp/github-enterprise-cli.yaml
openshell provider profile update github-enterprise-cli \
  -f /tmp/github-enterprise-cli.yaml

Updating a profile changes every provider instance of that type and reaches running sandboxes on their next configuration sync. OpenShell rejects an update that would make an attached sandbox's effective policy invalid.

Create provider instances

Load each token into the host environment only long enough to create its provider:

read -rsp "GitHub Enterprise token: " GH_ENTERPRISE_TOKEN
printf '\n'
export GH_ENTERPRISE_TOKEN
openshell provider create \
  --name github-enterprise \
  --type github-enterprise-cli \
  --from-existing
unset GH_ENTERPRISE_TOKEN

read -rsp "GitLab token: " GITLAB_TOKEN
printf '\n'
export GITLAB_TOKEN
openshell provider create \
  --name gitlab-enterprise \
  --type gitlab-enterprise-cli \
  --from-existing
unset GITLAB_TOKEN

Do not put token values directly in documentation, TOML, policy YAML, image layers, or shell scripts. --from-existing uses the profile's discovery section and stores the credential through the gateway's credential storage.

Attach the named instances through .exoshell.local.toml:

image = "localhost/example-local:latest"
policy = "policies/policy-local.yaml"
providers = ["github-enterprise", "gitlab-enterprise"]
github_host = "github.example.com"
gitlab_host = "gitlab.example.com"

Provider names are local choices; profile IDs must match the imported profile. Use your own namespace and origin metadata for definitions you maintain; the exoshell- prefix and exoshell-origin: ExOShell identify supplied definitions. Do not attach two GitHub profiles that export the same environment variable, or two GitLab profiles that export GITLAB_TOKEN and GLAB_TOKEN, to one sandbox. Use separate sandboxes when switching between same-service instances.