Sign inSign up

docker/sbx-kit-github-clone:latest

Multi-platform
Manifest digest

sha256:2aa2a7cebf9b7b2d74f059bc1486f9a7a6ce06718e89f0d77b2dd0c029be404b

Last pushed

9 days by cdupuis

Type

Sandbox Kit

Manifest digest

sha256:2aa2a7cebf9b7b2d74f059bc1486f9a7a6ce06718e89f0d77b2dd0c029be404b

yaml
schemaVersion: "3"
displayName: GitHub Clone
description: Clones a GitHub repository into the sandbox at create time, using the gh CLI with a proxy-managed GH_TOKEN sentinel. Auto-installs gh if the base image doesn't have it.
version: 1.0.0
kind: mixin
capabilities:
    - type: com.docker.sandbox/network-policy@1
      config:
        install:
            allow:
                - api.github.com
                - github.com
                - codeload.github.com
                - raw.githubusercontent.com
                - cli.github.com
                - archive.ubuntu.com
                - security.ubuntu.com
                - ports.ubuntu.com
                - download.docker.com
        runtime:
            allow:
                - api.github.com
                - github.com
                - codeload.github.com
                - raw.githubusercontent.com
    - type: com.docker.sandbox/lifecycle@1
      config:
        install:
            - command: |
                set -euo pipefail

                NEED=""
                command -v gh  >/dev/null 2>&1 || NEED="$NEED gh"
                command -v git >/dev/null 2>&1 || NEED="$NEED git"

                if [ -z "$NEED" ]; then
                  exit 0
                fi

                if ! command -v apt-get >/dev/null 2>&1; then
                  echo "github-clone: missing:$NEED and apt-get is not available — this kit only knows how to auto-install on Debian/Ubuntu bases. Layer a mixin that installs the missing tools, or pick a base agent that ships them." >&2
                  exit 1
                fi

                export DEBIAN_FRONTEND=noninteractive

                # Add GitHub's official apt source only when gh is what's missing.
                # Recipe follows https://github.com/cli/cli/blob/trunk/docs/install_linux.md.
                case " $NEED " in
                  *' gh '*)
                    install -m 0755 -d /etc/apt/keyrings
                    if command -v curl >/dev/null 2>&1; then
                      curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
                        -o /etc/apt/keyrings/githubcli-archive-keyring.gpg
                    elif command -v wget >/dev/null 2>&1; then
                      wget -qO /etc/apt/keyrings/githubcli-archive-keyring.gpg \
                        https://cli.github.com/packages/githubcli-archive-keyring.gpg
                    else
                      # Fall back to installing curl first via apt so we can fetch
                      # the keyring; the base at least has ca-certificates for the
                      # https handshake (every *-docker base does).
                      apt-get update -qq
                      apt-get install -y -qq --no-install-recommends curl ca-certificates
                      curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
                        -o /etc/apt/keyrings/githubcli-archive-keyring.gpg
                    fi
                    chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg
                    echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
                      > /etc/apt/sources.list.d/github-cli.list
                    ;;
                esac

                apt-get update -qq
                # `$NEED` is intentionally unquoted — it's a space-separated list
                # of package names constructed from the two `command -v` checks
                # above, not user input.
                apt-get install -y -qq --no-install-recommends $NEED
                rm -rf /var/lib/apt/lists/*
              description: Install gh (and git) via apt if the base image doesn't already ship them
              user: "0"
            - command: |
                set -euo pipefail

                REPO='${{ kit.args.repo }}'
                REF='${{ kit.args.ref }}'
                DIR='${{ kit.args.dir }}'

                # Idempotent re-run: if the target already has content, leave it
                # alone rather than fail or clobber user changes.
                if [ -d "$DIR" ] && [ -n "$(ls -A "$DIR" 2>/dev/null || true)" ]; then
                  echo "github-clone: '$DIR' already exists and is non-empty; skipping clone." >&2
                  exit 0
                fi

                mkdir -p "$(dirname "$DIR")"

                # The whole clone runs as root — the agent user (uid 1000) can't
                # create a top-level directory like /project because / is
                # root-owned 755. `chown -R agent:agent` at the end hands the
                # resulting tree over. This matches the pattern every other kit
                # in this repo uses (e.g. github-ssh, claude-sbx-statusline).
                #
                # gh reads GH_TOKEN itself, so no extra plumbing is needed for
                # the clone. Extra flags after `--` are passed straight to
                # `git clone`.
                if [ -n "$REF" ]; then
                  # Fast path: shallow-clone a branch or tag directly. Fallback:
                  # if REF is a commit SHA (not a branch/tag), --branch fails, so
                  # do a full clone and check the SHA out.
                  if ! gh repo clone "$REPO" "$DIR" -- --depth 1 --branch "$REF" 2>/dev/null; then
                    rm -rf "$DIR"
                    gh repo clone "$REPO" "$DIR"
                    git -C "$DIR" checkout "$REF"
                  fi
                else
                  gh repo clone "$REPO" "$DIR" -- --depth 1
                fi

                chown -R agent:agent "$DIR"

                # Route git HTTPS auth for github.com through gh's credential
                # helper so any later `git push` / `git pull` from within the
                # sandbox picks up the same proxy-managed GH_TOKEN sentinel.
                # Written to /etc/gitconfig with `--system` so any user in the
                # container inherits it — equivalent to running `gh auth
                # setup-git` per-user, but keeps this hook root-scoped and works
                # even when no GitHub credential is bound on the host (the empty
                # first helper resets whatever the base image may have set; the
                # two-line pattern is what gh's own setup-git writes).
                git config --system --unset-all credential."https://github.com".helper 2>/dev/null || true
                git config --system --add       credential."https://github.com".helper ''
                git config --system --add       credential."https://github.com".helper '!gh auth git-credential'
              description: Clone ${{ kit.args.repo }} into ${{ kit.args.dir }} via gh
              env:
                - GH_TOKEN
              user: "0"
    - type: com.docker.sandbox/agent-context@1
      config:
        content: |
            ## github-clone

            A GitHub repository (`${{ kit.args.repo }}`) has been cloned into
            `${{ kit.args.dir }}` at sandbox-create time. Treat that directory as
            the working copy for this task rather than `~/workspace`.

            Authentication is proxy-mediated: `GH_TOKEN` in the container is a
            sentinel — the sandbox proxy swaps in the real token bound on the host
            (via `sbx secret set -g github`) when talking to `api.github.com` and
            `github.com`. `gh` and `git` HTTPS operations both use this path, so
            `gh api …`, `gh pr create`, `git push`, and friends work without any
            extra setup.

            Public repos work with no host-side credential bound; only writes and
            private-repo access need one.
args:
    dir:
        default: /project
        description: |
            Absolute directory inside the sandbox to clone into. If the directory
            already exists and is non-empty, the clone is skipped (the mixin
            re-runs cleanly on `sbx create` retries).
        pattern: ^/[A-Za-z0-9._/-]+$
    ref:
        default: ""
        description: |
            Optional git ref (branch, tag, or 7–40 hex commit SHA) to check out.
            Empty (the default) clones the repo's default branch.
        pattern: ^[A-Za-z0-9._/-]*$
    repo:
        required: true
        description: |
            GitHub repository to clone. Accepts the `owner/name` shorthand, a full
            `https://github.com/owner/name[.git]` URL, or an
            `[email protected]:owner/name[.git]` SSH URL. Any other form is rejected
            before the clone shells out.
        pattern: ^(?:[A-Za-z0-9._-]+/[A-Za-z0-9._-]+(?:\.git)?|https://github\.com/[A-Za-z0-9._-]+/[A-Za-z0-9._-]+(?:\.git)?|git@github\.com:[A-Za-z0-9._-]+/[A-Za-z0-9._-]+(?:\.git)?)$