├── .devcontainer ├── Dockerfile ├── devcontainer.json └── library-scripts │ └── github-debian.sh ├── LICENSE ├── README.md └── gh-setup-git-credential-helper /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | # See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.192.0/containers/debian/.devcontainer/base.Dockerfile 2 | 3 | # [Choice] Debian version: bullseye, buster, stretch 4 | ARG VARIANT="buster" 5 | FROM mcr.microsoft.com/vscode/devcontainers/base:0-${VARIANT} 6 | 7 | COPY library-scripts/github-debian.sh /tmp/library-scripts/ 8 | RUN apt-get update && export DEBIAN_FRONTEND=noninteractive && bash /tmp/library-scripts/github-debian.sh 9 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: 2 | // https://github.com/microsoft/vscode-dev-containers/tree/v0.192.0/containers/debian 3 | { 4 | "name": "Debian", 5 | "build": { 6 | "dockerfile": "Dockerfile", 7 | // Update 'VARIANT' to pick an Debian version: bullseye, buster, stretch 8 | "args": { "VARIANT": "buster" } 9 | }, 10 | 11 | // Set *default* container specific settings.json values on container create. 12 | "settings": {}, 13 | 14 | // Add the IDs of extensions you want installed when the container is created. 15 | "extensions": [], 16 | 17 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 18 | // "forwardPorts": [], 19 | 20 | // Uncomment to use the Docker CLI from inside the container. See https://aka.ms/vscode-remote/samples/docker-from-docker. 21 | // "mounts": [ "source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind" ], 22 | 23 | // Uncomment when using a ptrace-based debugger like C++, Go, and Rust 24 | // "runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined" ], 25 | 26 | // Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. 27 | "remoteUser": "vscode", 28 | "postStartCommand": "gh extension install jongio/gh-setup-git-credential-helper" 29 | } 30 | -------------------------------------------------------------------------------- /.devcontainer/library-scripts/github-debian.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | #------------------------------------------------------------------------------------------------------------- 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. 5 | #------------------------------------------------------------------------------------------------------------- 6 | # 7 | # Docs: https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/github.md 8 | # Maintainer: The VS Code and Codespaces Teams 9 | # 10 | # Syntax: ./github-debian.sh [version] 11 | 12 | CLI_VERSION=${2:-"latest"} 13 | 14 | GITHUB_CLI_ARCHIVE_GPG_KEY=C99B11DEB97541F0 15 | GPG_KEY_SERVERS="keyserver hkp://keyserver.ubuntu.com:80 16 | keyserver hkps://keys.openpgp.org 17 | keyserver hkp://keyserver.pgp.com" 18 | 19 | set -e 20 | 21 | if [ "$(id -u)" -ne 0 ]; then 22 | echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.' 23 | exit 1 24 | fi 25 | 26 | # Get central common setting 27 | get_common_setting() { 28 | if [ "${common_settings_file_loaded}" != "true" ]; then 29 | curl -sfL "https://aka.ms/vscode-dev-containers/script-library/settings.env" 2>/dev/null -o /tmp/vsdc-settings.env || echo "Could not download settings file. Skipping." 30 | common_settings_file_loaded=true 31 | fi 32 | if [ -f "/tmp/vsdc-settings.env" ]; then 33 | local multi_line="" 34 | if [ "$2" = "true" ]; then multi_line="-z"; fi 35 | local result="$(grep ${multi_line} -oP "$1=\"?\K[^\"]+" /tmp/vsdc-settings.env | tr -d '\0')" 36 | if [ ! -z "${result}" ]; then declare -g $1="${result}"; fi 37 | fi 38 | echo "$1=${!1}" 39 | } 40 | 41 | # Import the specified key in a variable name passed in as 42 | receive_gpg_keys() { 43 | get_common_setting $1 44 | local keys=${!1} 45 | get_common_setting GPG_KEY_SERVERS true 46 | 47 | # Use a temporary locaiton for gpg keys to avoid polluting image 48 | export GNUPGHOME="/tmp/tmp-gnupg" 49 | mkdir -p ${GNUPGHOME} 50 | chmod 700 ${GNUPGHOME} 51 | echo -e "disable-ipv6\n${GPG_KEY_SERVERS}" > ${GNUPGHOME}/dirmngr.conf 52 | # GPG key download sometimes fails for some reason and retrying fixes it. 53 | local retry_count=0 54 | local gpg_ok="false" 55 | set +e 56 | until [ "${gpg_ok}" = "true" ] || [ "${retry_count}" -eq "5" ]; 57 | do 58 | echo "(*) Downloading GPG key..." 59 | ( echo "${keys}" | xargs -n 1 gpg --recv-keys) 2>&1 && gpg_ok="true" 60 | if [ "${gpg_ok}" != "true" ]; then 61 | echo "(*) Failed getting key, retring in 10s..." 62 | (( retry_count++ )) 63 | sleep 10s 64 | fi 65 | done 66 | set -e 67 | if [ "${gpg_ok}" = "false" ]; then 68 | echo "(!) Failed to install rvm." 69 | exit 1 70 | fi 71 | } 72 | 73 | # Figure out correct version of a three part version number is not passed 74 | find_version_from_git_tags() { 75 | local variable_name=$1 76 | local requested_version=${!variable_name} 77 | if [ "${requested_version}" = "none" ]; then return; fi 78 | local repository=$2 79 | local prefix=${3:-"tags/v"} 80 | local separator=${4:-"."} 81 | local last_part_optional=${5:-"false"} 82 | if [ "$(echo "${requested_version}" | grep -o "." | wc -l)" != "2" ]; then 83 | local escaped_separator=${separator//./\\.} 84 | local last_part 85 | if [ "${last_part_optional}" = "true" ]; then 86 | last_part="(${escaped_separator}[0-9]+)?" 87 | else 88 | last_part="${escaped_separator}[0-9]+" 89 | fi 90 | local regex="${prefix}\\K[0-9]+${escaped_separator}[0-9]+${last_part}$" 91 | local version_list="$(git ls-remote --tags ${repository} | grep -oP "${regex}" | tr -d ' ' | tr "${separator}" "." | sort -rV)" 92 | if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ] || [ "${requested_version}" = "lts" ]; then 93 | declare -g ${variable_name}="$(echo "${version_list}" | head -n 1)" 94 | else 95 | set +e 96 | declare -g ${variable_name}="$(echo "${version_list}" | grep -E -m 1 "^${requested_version//./\\.}([\\.\\s]|$)")" 97 | set -e 98 | fi 99 | fi 100 | if [ -z "${!variable_name}" ] || ! echo "${version_list}" | grep "^${!variable_name//./\\.}$" > /dev/null 2>&1; then 101 | echo -e "Invalid ${variable_name} value: ${requested_version}\nValid values:\n${version_list}" >&2 102 | exit 1 103 | fi 104 | echo "${variable_name}=${!variable_name}" 105 | } 106 | 107 | # Import the specified key in a variable name passed in as 108 | receive_gpg_keys() { 109 | get_common_setting $1 110 | local keys=${!1} 111 | get_common_setting GPG_KEY_SERVERS true 112 | local keyring_args="" 113 | if [ ! -z "$2" ]; then 114 | keyring_args="--no-default-keyring --keyring $2" 115 | fi 116 | 117 | # Use a temporary locaiton for gpg keys to avoid polluting image 118 | export GNUPGHOME="/tmp/tmp-gnupg" 119 | mkdir -p ${GNUPGHOME} 120 | chmod 700 ${GNUPGHOME} 121 | echo -e "disable-ipv6\n${GPG_KEY_SERVERS}" > ${GNUPGHOME}/dirmngr.conf 122 | # GPG key download sometimes fails for some reason and retrying fixes it. 123 | local retry_count=0 124 | local gpg_ok="false" 125 | set +e 126 | until [ "${gpg_ok}" = "true" ] || [ "${retry_count}" -eq "5" ]; 127 | do 128 | echo "(*) Downloading GPG key..." 129 | ( echo "${keys}" | xargs -n 1 gpg -q ${keyring_args} --recv-keys) 2>&1 && gpg_ok="true" 130 | if [ "${gpg_ok}" != "true" ]; then 131 | echo "(*) Failed getting key, retring in 10s..." 132 | (( retry_count++ )) 133 | sleep 10s 134 | fi 135 | done 136 | set -e 137 | if [ "${gpg_ok}" = "false" ]; then 138 | echo "(!) Failed to install rvm." 139 | exit 1 140 | fi 141 | } 142 | 143 | # Function to run apt-get if needed 144 | apt_get_update_if_needed() 145 | { 146 | if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then 147 | echo "Running apt-get update..." 148 | apt-get update 149 | else 150 | echo "Skipping apt-get update." 151 | fi 152 | } 153 | 154 | # Checks if packages are installed and installs them if not 155 | check_packages() { 156 | if ! dpkg -s "$@" > /dev/null 2>&1; then 157 | apt_get_update_if_needed 158 | apt-get -y install --no-install-recommends "$@" 159 | fi 160 | } 161 | 162 | export DEBIAN_FRONTEND=noninteractive 163 | 164 | # Install curl, apt-transport-https, curl, gpg, or dirmngr, git if missing 165 | check_packages curl ca-certificates apt-transport-https dirmngr gnupg2 166 | if ! type git > /dev/null 2>&1; then 167 | apt_get_update_if_needed 168 | apt-get -y install --no-install-recommends git 169 | fi 170 | 171 | # Soft version matching 172 | if [ "${CLI_VERSION}" != "latest" ] && [ "${CLI_VERSION}" != "lts" ] && [ "${CLI_VERSION}" != "stable" ]; then 173 | find_version_from_git_tags CLI_VERSION "https://github.com/cli/cli" 174 | version_suffix="=${CLI_VERSION}" 175 | else 176 | version_suffix="" 177 | fi 178 | 179 | # Install the GitHub CLI 180 | echo "Downloading github CLI..." 181 | # Import key safely (new method rather than deprecated apt-key approach) and install 182 | . /etc/os-release 183 | receive_gpg_keys GITHUB_CLI_ARCHIVE_GPG_KEY /usr/share/keyrings/githubcli-archive-keyring.gpg 184 | echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages ${VERSION_CODENAME} main" > /etc/apt/sources.list.d/github-cli.list 185 | apt-get update 186 | apt-get -y install "gh${version_suffix}" 187 | rm -rf "/tmp/gh/gnupg" 188 | echo "Done!" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021 Jon Gallant 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 14 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 15 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 16 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 17 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 18 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 19 | OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GitHub CLI Extension - Setup Git Credential Helper 2 | 3 | When you fork a repo the default GITHUB_TOKEN has limited permissions that doesn't allow you to do common things like add secrets and doesn't allow you to use the GitHub CLI as a gitcredential helper. 4 | 5 | This extension solves all of that for you. 6 | 7 | It will: 8 | 9 | 1. Clear the `GITHUB_TOKEN` variable, so `gh auth login` won't complain that it already has a value. 10 | 2. Call `gh auth login` 11 | 3. Set `gh auth git-credentials` as your gitcredentials helper. 12 | 13 | 14 | The full context for this extension can be found here: https://blog.jongallant.com/2021/06/codespaces-gh-cli-git-credentials/ 15 | 16 | Here are the related GitHub issues: 17 | 18 | - https://github.com/cli/cli/issues/3796 19 | - https://github.com/cli/cli/issues/3797 20 | - https://github.com/cli/cli/issues/3798 21 | 22 | 23 | 24 | ## Installation 25 | 26 | 1. Install [GitHub CLI v2.0+](https://github.com/cli/cli) 27 | 1. Run `gh extension install jongio/gh-setup-git-credential-helper` 28 | 29 | ## Run 30 | 1. Run `gh setup-git-credential-helper` from within your Codespace or DevContainer 31 | 1. You will see "Successfully added 'gh auth git-credential' as your git credential helper." if it succeeded. Please file an issue if you have any problems. 32 | 33 | ## Uninstall 34 | 1. Run `gh extension remove gh-setup-git-credential-helper` 35 | 36 | ## Install in DevContainer 37 | 38 | See the code in this repo in /.devcontainer to see how to add this to your devcontainer 39 | 40 | 1. Copy the `github-debian.sh` script to library-scripts 41 | 1. Add the install command to your Dockerfile 42 | ```bash 43 | RUN apt-get update && export DEBIAN_FRONTEND=noninteractive && bash /tmp/library-scripts/github-debian.sh 44 | ``` 45 | 1. Manually run `gh setup-git-credential-helper` when your container starts. 46 | 47 | 48 | ## Error Messages 49 | 50 | Including the following error messages for SEO - this extension should fix all of these issues. 51 | 52 | ``` 53 | failed to fetch public key: 54 | HTTP 403: Resource not accessible by integration 55 | ``` 56 | 57 | ``` 58 | The value of the GITHUB_TOKEN environment variable is being used for authentication. 59 | To have GitHub CLI store credentials instead, first clear the value from the environment. 60 | ``` 61 | 62 | ``` 63 | git push --set-upstream origin env-dev5 64 | remote: Invalid username or password. 65 | fatal: Authentication failed for 'https://github.com/jongio/golang-sample-app/' 66 | ``` -------------------------------------------------------------------------------- /gh-setup-git-credential-helper: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Clear all credential helpers 4 | git config --global --unset-all 'credential.https://github.com.helper' 5 | git config --global --add 'credential.https://github.com.helper' '!gh auth git-credential' 6 | 7 | # Clear GITHUB_TOKEN, so we can re-auth 8 | export GITHUB_TOKEN= 9 | 10 | # Login to the GH CLI 11 | gh auth login 12 | 13 | echo "Successfully added 'gh auth git-credential' as your git credential helper." 14 | --------------------------------------------------------------------------------