Skip to content

Instantly share code, notes, and snippets.

@maxim
Last active May 24, 2026 23:22
Show Gist options
  • Select an option

  • Save maxim/6e15aa45ba010ab030c4 to your computer and use it in GitHub Desktop.

Select an option

Save maxim/6e15aa45ba010ab030c4 to your computer and use it in GitHub Desktop.
Download assets from private Github releases
#!/usr/bin/env bash
#
# gh-dl-release! It works!
#
# This script downloads an asset from latest or specific Github release of a
# private repo. Feel free to extract more of the variables into command line
# parameters.
#
# PREREQUISITES
#
# curl, wget, jq
#
# USAGE
#
# Set all the variables inside the script, make sure you chmod +x it, then
# to download specific version to my_app.tar.gz:
#
# gh-dl-release 2.1.1 my_app.tar.gz
#
# to download latest version:
#
# gh-dl-release latest latest.tar.gz
#
# If your version/tag doesn't match, the script will exit with error.
TOKEN="<github_access_token>"
REPO="<user_or_org>/<repo_name>"
FILE="<name_of_asset_file>" # the name of your release asset file, e.g. build.tar.gz
VERSION=$1 # tag name or the word "latest"
GITHUB="https://api.github.com"
alias errcho='>&2 echo'
function gh_curl() {
curl -H "Authorization: token $TOKEN" \
-H "Accept: application/vnd.github.v3.raw" \
$@
}
if [ "$VERSION" = "latest" ]; then
# Github should return the latest release first.
parser=".[0].assets | map(select(.name == \"$FILE\"))[0].id"
else
parser=". | map(select(.tag_name == \"$VERSION\"))[0].assets | map(select(.name == \"$FILE\"))[0].id"
fi;
asset_id=`gh_curl -s $GITHUB/repos/$REPO/releases | jq "$parser"`
if [ "$asset_id" = "null" ]; then
errcho "ERROR: version not found $VERSION"
exit 1
fi;
wget -q --auth-no-challenge --header='Accept:application/octet-stream' \
https://$TOKEN:@api.github.com/repos/$REPO/releases/assets/$asset_id \
-O $2
@germanattanasio

Copy link
Copy Markdown

@jessp01 to run this I had to remove the gh_curl and do it in inline

@jessp01

jessp01 commented Aug 5, 2019

Copy link
Copy Markdown

Hi @germanattanasio,

I'm not sure I understand what you mean. Both the call to gh_curl():

ASSET_ID=`gh_curl https://$GITHUB_API_ENDPOINT/repos/$REPO/releases | jq "$PARSER"`

and:

curl -sL --header "Authorization: token $TOKEN" --header 'Accept: application/octet-stream' https://$TOKEN:@$GITHUB_API_ENDPOINT/repos/$REPO/releases/assets/$ASSET_ID > $FILE

Are needed. One fetches the asset ID using the API and the other fetches the actual asset/file.
The reason why both requests cannot be made by calling gh_curl() as it is now is because it sets the Accept: application/vnd.github.v3.raw headers, which is needed for the first request but hinders the latter. You could refactor the function so that it sets either header based on the request (fetching the ASSET_ID or downloading the actual asset). To me, it felt like too much trouble for this sort of script :)

@germanattanasio

Copy link
Copy Markdown

I was trying to say that when I run the script with the gh_curl function in Travis I get an error. I ended up doing:

ASSET_ID=`curl -sL -H "Authorization: token $TOKEN" -H "Accept: application/vnd.github.v3.raw" https://$GITHUB_API_ENDPOINT/repos/$REPO/releases | jq "$PARSER"`

@kavinda1995

Copy link
Copy Markdown

Maaaan. You save my day and me ❤️ 😍
Trying to find a solution to fix 404 error in whole day

@hans-zand

Copy link
Copy Markdown

Thanks it was awesome !

@fabriciobastian

fabriciobastian commented Dec 28, 2019

Copy link
Copy Markdown

Thanks! Awesome! I created this action to download assets from releases based on this code

@jairov4

jairov4 commented Apr 21, 2020

Copy link
Copy Markdown

Nice I made my version from this

@jonatasteixeira-zz

Copy link
Copy Markdown

When I tried to download file using curl, I get the response:

curl -sL --header "Authorization: token $TOKEN" --header 'Accept: application/octet-stream' https://$TOKEN:@$GITHUB_API_ENDPOINT/repos/$REPO/releases/assets/$ASSET_ID > $FILE

{
  "message": "Unsupported 'Accept' header: [#<Sinatra::Request::AcceptEntry:0x00007feb2a6c3e88 @entry=\"application/octet-stream\", @type=\"application/octet-stream\", @params={}, @q=1.0>]. Must accept 'application/json'.",
  "documentation_url": "https://developer.github.com/v3/media"
}

@jonatasteixeira-zz

Copy link
Copy Markdown

When I tried to download file using curl, I get the response:

curl -sL --header "Authorization: token $TOKEN" --header 'Accept: application/octet-stream' https://$TOKEN:@$GITHUB_API_ENDPOINT/repos/$REPO/releases/assets/$ASSET_ID > $FILE

{
  "message": "Unsupported 'Accept' header: [#<Sinatra::Request::AcceptEntry:0x00007feb2a6c3e88 @entry=\"application/octet-stream\", @type=\"application/octet-stream\", @params={}, @q=1.0>]. Must accept 'application/json'.",
  "documentation_url": "https://developer.github.com/v3/media"
}

According to: https://curl.haxx.se/docs/CVE-2018-1000007.html is necessary updarte curl to version >= 7.58.0

@gaieges

gaieges commented May 8, 2020

Copy link
Copy Markdown

Just discovered the above for myself. If you get the following error on the call to the s3 redirected url, it's because you're using an older version of curl and require > 7.58.0. The reason is that the authorization header gets passed along to s3, and not only is that insecure, it also breaks the s3 request.

* The requested URL returned error: 400 Bad Request

@jacksonporter

Copy link
Copy Markdown

Thank you! This is fantastic!

@EduardoSantanaSeverino

EduardoSantanaSeverino commented Jun 27, 2020

Copy link
Copy Markdown

This is my version of download the latest asset file of a release for a private repository. In case you want to use this script with Jenkins you would need to sent the token while executing the script.

Steps:

  1. Create a file with: nano download.sh and fill the file with the repository name and owner.
  2. Give permissions to file: chmod +x download.sh
  3. Execute the file: ./download.sh "--GITHUB TOKEN HERE--"

Example of the file:

#!/bin/bash

# This script downloads the first asset from the latest Github release of a
# private repo. 
#
# PREREQUISITES
#
# curl, jq
#
# USAGE
#
# Set owner and repo variables inside the script, make sure you chmod +x it.
#
#     ./download.sh "--GITHUB TOKEN HERE--"
#

# Define variables
echo "---------------------------------------------------------------------"
echo "Define variables"
echo "---------------------------------------------------------------------"

owner="--Repository Owner--"
repo="--Repository Name--"
GITHUB_API_TOKEN=$1
GH_API="https://api.github.com"
GH_REPO="$GH_API/repos/$owner/$repo"
GH_LATEST="$GH_REPO/releases/latest"
AUTH="Authorization: token $GITHUB_API_TOKEN"

# Read asset name and id
echo "---------------------------------------------------------------------"
echo "Read asset name and id"
echo "---------------------------------------------------------------------"

response=$(curl -sH "$AUTH" $GH_LATEST)
id=`echo "$response" | jq '.assets[0] .id' |  tr -d '"'`
name=`echo "$response" | jq '.assets[0] .name' |  tr -d '"'`
GH_ASSET="$GH_REPO/releases/assets/$id"

# Print Details
echo "---------------------------------------------------------------------"
echo "Print Details"
echo "Assets Id: $id"
echo "Name: $name"
echo "Assets URL: $GH_ASSET"
echo "---------------------------------------------------------------------"

# Downloading asset file
echo "---------------------------------------------------------------------"
echo "Downloading asset file"
echo "---------------------------------------------------------------------"
curl -v -L -o "$name" -H "$AUTH" -H 'Accept: application/octet-stream' "$GH_ASSET"

@jboyd01

jboyd01 commented Jul 23, 2020

Copy link
Copy Markdown

+1, good stuff. @jessp01 thanks for posting your refinements.

@a0s

a0s commented Aug 16, 2020

Copy link
Copy Markdown

Dont' forget to enable repo permission (yes, exactly root permission, enabling all nested permissions does not work) for the token

@faisalkhanani-ih

Copy link
Copy Markdown

Can I download a release asset from a private github repo without creating a personal token and only using the github token provided by github in action workflows?

@zero88

zero88 commented Jan 4, 2021

Copy link
Copy Markdown

If anyone interest, give a try: https://github.com/zero88/gh-release-downloader

@faisalkhanani-ih Yes if GitHub token is in workflow within this private Github repo

@SIMULATAN

Copy link
Copy Markdown

Thank you so much for making this snippet, it is a real lifesaver!

In my workflow, I had a beta and release feature, but it always gave me back the latest general release, no matter if it was a prerelease or not.
This shouldn't be a big problem if you automate it because there shouldn't be concurent build and release processes at the same time anyway, but I like to be safe, so I made a little improvement to get the latest prerelease or release, what you want:

Replace line 42 with this:
parser=".[] | select(.prerelease == SHOULD_BE_PRERELEASE) | .assets | map(select(.name == \"$FILE\"))[0].id"

If you replace SHOULD_BE_PRERELEASE with true it gives you back the asset id of the latest prerelease,
if you replace it with false, it gives you the asset id of the latest full release.

You can do the same thing for body or draft and stuff, that is just the way you have to filter it.

Hope this helps!

@inhowe

inhowe commented Jul 11, 2021

Copy link
Copy Markdown

awesome

@GElkayam

Copy link
Copy Markdown

As we needed some releases that were in the 5th page (so per_page=100 didn't help), I rewrote the query to query latest (it gives latest released) and tag. also needed some more information in the error reporting:

#!/usr/bin/env bash
#
# gh-dl-release! It works!
# 
# This script downloads an asset from latest or specific Github release of a
# private repo. Feel free to extract more of the variables into command line
# parameters.
#
# PREREQUISITES
#
# curl, wget, jq
#
# USAGE
#
# Set all the variables inside the script, make sure you chmod +x it, then
# to download specific version to my_app.tar.gz:
#
#     gh-dl-release 2.1.1 my_app.tar.gz
#
# to download latest version:
#
#     gh-dl-release latest latest.tar.gz
#
# If your version/tag doesn't match, the script will exit with error.

TOKEN="$3"
REPO="$4"
FILE="$2"      # the name of your release asset file, e.g. build.tar.gz
VERSION="$1"                       # tag name or the word "latest"
GITHUB="https://api.github.com"

alias errcho='>&2 echo'

function gh_curl() {
  curl -H "Authorization: token $TOKEN" \
       -H "Accept: application/vnd.github.v3.raw" \
       $@
}

parser=".assets | map(select(.name == \"$FILE\"))[0].id"
if [ "$VERSION" = "latest" ]; then
  # Github should return the latest release first.
  asset_id=`gh_curl -s $GITHUB/repos/$REPO/releases/latest | jq "$parser"`
else
  asset_id=`gh_curl -s $GITHUB/repos/$REPO/releases/tags/$VERSION | jq "$parser"`
fi;

if [ -z "$asset_id" ]; then
  errcho "ERROR: version not found $VERSION"
  exit 1
fi;
if [ "$asset_id" = "null" ]; then
  errcho "ERROR: file $FILE not found in version $VERSION"
  exit 2
fi;

wget -q --auth-no-challenge --header='Accept:application/octet-stream' \
  https://$TOKEN:@api.github.com/repos/$REPO/releases/assets/$asset_id \
  -O $2

@sonibla

sonibla commented Nov 29, 2021

Copy link
Copy Markdown

For Windows users, I made a PowerShell equivalent
https://gist.github.com/sonibla/a60fc31b244ceba3220b9bb33316798c

@AchalaDias

AchalaDias commented Jan 8, 2022

Copy link
Copy Markdown

I have updated the code for the latest github api and changed it to supporting for multiple files download. User can set only necessary file names in the array.

TOKEN="<PAT>"
REPO="<REPO>"
VERSION="<RELEASED-VERSION>" 
GITHUB="https://api.github.com"

function gh_curl() {
  curl -H "Authorization: token $TOKEN" \
       -H "Accept: application/vnd.github.v3+json" \
       $@
}

# assets list you want to download
AseertsList=( 
 asset1.zip asset2.zip
)

assets=$(gh_curl -s $GITHUB/repos/$REPO/releases/tags/$VERSION)

for row in $(echo "${assets}" | jq -c '.assets[]'); do
name=$( jq -r  '.name' <<< "${row}" ) 
echo ${name}
    if [[ ${AseertsList[*]} =~ ${name} ]] ; then

        id=$( jq -r  '.id' <<< "${row}" ) 

        wget -q --auth-no-challenge --header='Accept:application/octet-stream' \
        https://$TOKEN:@api.github.com/repos/$REPO/releases/assets/$asset_id \
        -O test/${name}
    fi
done

@shuantsu

shuantsu commented Jan 12, 2022

Copy link
Copy Markdown

using application/octet-stream as Accept header did the trick for me to download from a private repo

@gpaOliveira

Copy link
Copy Markdown

Using wget didn't fit my vanilla GH-Hosted-Runners, so I'm dropping here a curl version of that download that worked for me:

curl -L -s -H "Authorization: token $TOKEN" -H 'Accept:application/octet-stream' \
"https://api.github.com/repos/$REPO/releases/assets/$asset_id" \
-o $asset_name

@ringerc

ringerc commented Oct 18, 2022

Copy link
Copy Markdown

For releases, uses gh release download instead.

gh release download -R MyOrg/MyRepo --pattern 'myproject-*.tar.gz'

for example.

@jamesETsmith

Copy link
Copy Markdown

Here are the official docs for anyone else who stumbles on this thread which say to do (note octet-stream here bc I was downloading a binary):

curl -L \
  -H "Accept:  application/octet-stream" \ 
  -H "Authorization: Bearer <YOUR-TOKEN>"\
  -H "X-GitHub-Api-Version: 2022-11-28" \
  https://api.github.com/repos/OWNER/REPO/releases/assets/ASSET_ID

If (like me) you're confused about the best way to find out ASSET_ID I'd suggest looking at the json output for the latest release, see the docs here and the code here:

curl -L \
  -H "Accept: application/vnd.github+json" \
  -H "Authorization: Bearer <YOUR-TOKEN>"\
  -H "X-GitHub-Api-Version: 2022-11-28" \
  https://api.github.com/repos/OWNER/REPO/releases/latest

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment