| #!/bin/sh |
| |
| # To promote and sign a release that has been prepared by the build jobs, use: |
| # release.sh |
| |
| # To _only_ sign an existing release, use: |
| # release.sh -s vx.y.z |
| |
| set -e |
| |
| [ -z "$NODEJS_RELEASE_HOST" ] && NODEJS_RELEASE_HOST=direct.nodejs.org |
| |
| webhost=$NODEJS_RELEASE_HOST |
| webuser=dist |
| promotablecmd=dist-promotable |
| promotecmd=dist-promote |
| signcmd=dist-sign |
| customsshkey="" # let ssh and scp use default key |
| signversion="" |
| cloudflare_bucket="dist-prod" |
| cloudflare_endpoint=https://07be8d2fbc940503ca1be344714cb0d1.r2.cloudflarestorage.com # Node.js Cloudflare account |
| cloudflare_profile="worker" |
| |
| while getopts ":i:s:" option; do |
| case "${option}" in |
| i) |
| customsshkey="-i ${OPTARG}" |
| ;; |
| s) |
| signversion="${OPTARG}" |
| ;; |
| \?) |
| echo "Invalid option -$OPTARG." |
| exit 1 |
| ;; |
| *) |
| echo "Option -$OPTARG takes a parameter." |
| exit 1 |
| ;; |
| esac |
| done |
| shift $((OPTIND-1)) |
| |
| ################################################################################ |
| ## Select a GPG key to use |
| |
| echo "# Selecting GPG key ..." |
| |
| gpgkey=$(gpg --list-secret-keys --keyid-format SHORT | awk -F'( +|/)' '/^(sec|ssb)/{print $3}') |
| keycount=$(echo "$gpgkey" | wc -w) |
| |
| if [ "$keycount" -eq 0 ]; then |
| # shellcheck disable=SC2016 |
| echo 'Need at least one GPG key, please make one with `gpg --gen-key`' |
| echo 'You will also need to submit your key to a public keyserver, e.g.' |
| echo ' https://sks-keyservers.net/i/#submit' |
| exit 1 |
| elif [ "$keycount" -ne 1 ]; then |
| printf "You have multiple GPG keys:\n\n" |
| |
| gpg --list-secret-keys |
| |
| keynum= |
| while [ -z "${keynum##*[!0-9]*}" ] || [ "$keynum" -le 0 ] || [ "$keynum" -gt "$keycount" ]; do |
| echo "$gpgkey" | awk '{ print NR ") " $0; }' |
| printf 'Select a key: ' |
| read -r keynum |
| done |
| echo "" |
| gpgkey=$(echo "$gpgkey" | sed -n "${keynum}p") |
| fi |
| |
| gpgfing=$(gpg --keyid-format 0xLONG --fingerprint "$gpgkey" | grep 'Key fingerprint =' | awk -F' = ' '{print $2}' | tr -d ' ') |
| |
| grep -q "$gpgfing" README.md || (\ |
| echo 'Error: this GPG key fingerprint is not listed in ./README.md' && \ |
| exit 1 \ |
| ) |
| |
| |
| echo "Using GPG key: $gpgkey" |
| echo " Fingerprint: $gpgfing" |
| |
| checktag() { |
| # local version=$1 |
| |
| if ! git tag -v "$1" 2>&1 | grep "${gpgkey}" | grep key > /dev/null; then |
| echo "Could not find signed tag for \"$1\" or GPG key is not yours" |
| exit 1 |
| fi |
| } |
| |
| ################################################################################ |
| ## Create and sign checksums file for a given version |
| |
| sign() { |
| printf "\n# Creating SHASUMS256.txt ...\n" |
| |
| # local version=$1 |
| |
| ghtaggedversion=$(curl -sL "https://raw.githubusercontent.com/nodejs/node/$1/src/node_version.h" \ |
| | awk '/define NODE_(MAJOR|MINOR|PATCH)_VERSION/{ v = v "." $3 } END{ v = "v" substr(v, 2); print v }') |
| if [ "$1" != "${ghtaggedversion}" ]; then |
| echo "Could not find tagged version on github.com/nodejs/node, did you push your tag?" |
| exit 1 |
| fi |
| |
| # /home/dist/${site}/release |
| # shellcheck disable=SC2086,SC2029 |
| shapath=$(ssh ${customsshkey} "${webuser}@${webhost}" $signcmd nodejs $1) |
| |
| echo "${shapath}" | grep -q '^/.*/SHASUMS256.txt$' || (\ |
| echo 'Error: No SHASUMS file returned by sign!' &&\ |
| exit 1) |
| |
| echo "" |
| echo "# Signing SHASUMS for $1..." |
| |
| shafile=$(basename "$shapath") |
| shadir=$(dirname "$shapath") |
| tmpdir="/tmp/_node_release.$$" |
| |
| mkdir -p $tmpdir |
| |
| # shellcheck disable=SC2086 |
| scp ${customsshkey} "${webuser}@${webhost}:${shapath}" "${tmpdir}/${shafile}" |
| |
| gpg --default-key "$gpgkey" --clearsign --digest-algo SHA256 "${tmpdir}/${shafile}" |
| gpg --default-key "$gpgkey" --detach-sign --digest-algo SHA256 "${tmpdir}/${shafile}" |
| |
| echo "Wrote to ${tmpdir}/" |
| |
| echo "Your signed ${shafile}.asc:" |
| echo "" |
| |
| cat "${tmpdir}/${shafile}.asc" |
| |
| echo "" |
| |
| while true; do |
| printf "Upload files to %s? [y/n] " "$webhost" |
| yorn="" |
| read -r yorn |
| |
| if [ "X${yorn}" = "Xn" ]; then |
| break |
| fi |
| |
| if [ "X${yorn}" = "Xy" ]; then |
| # shellcheck disable=SC2086 |
| scp ${customsshkey} "${tmpdir}/${shafile}" "${tmpdir}/${shafile}.asc" "${tmpdir}/${shafile}.sig" "${webuser}@${webhost}:${shadir}/" |
| # shellcheck disable=SC2086,SC2029 |
| ssh ${customsshkey} "${webuser}@${webhost}" chmod 644 "${shadir}/${shafile}.asc" "${shadir}/${shafile}.sig" |
| break |
| fi |
| done |
| |
| while true; do |
| printf "Upload files from %s to R2 staging bucket? [y/n]" "$webhost" |
| yorn="" |
| read -r yorn |
| |
| if [ "X${yorn}" = "Xn" ]; then |
| break |
| fi |
| |
| if [ "X${yorn}" = "Xy" ]; then |
| # Note: the binaries and SHASUMS256.txt should already be in this bucket since the DO will upload them |
| |
| # Remove /home/dist/ part |
| r2dir=$(echo "$shadir" | cut -c 11-) |
| |
| # Copy SHASUM256.txt.asc |
| # shellcheck disable=SC2086,SC2029 |
| ssh ${customsshkey} "$webuser@$webhost" aws s3 cp "${shadir}/${shafile}.asc" "s3://${cloudflare_bucket}/${r2dir}/${shafile}.asc" --endpoint="${cloudflare_endpoint}" --profile=${cloudflare_profile} |
| |
| # Copy SHASUM256.txt.sig |
| # shellcheck disable=SC2086,SC2029 |
| ssh ${customsshkey} "$webuser@$webhost" aws s3 cp "${shadir}/${shafile}.sig" "s3://${cloudflare_bucket}/${r2dir}/${shafile}.sig" --endpoint="${cloudflare_endpoint}" --profile=${cloudflare_profile} |
| |
| break |
| fi |
| done |
| |
| rm -rf $tmpdir |
| } |
| |
| |
| if [ -n "${signversion}" ]; then |
| checktag "$signversion" |
| sign "$signversion" |
| exit 0 |
| fi |
| |
| # else: do a normal release & promote |
| |
| ################################################################################ |
| ## Look for releases to promote |
| |
| printf "\n# Checking for releases ...\n" |
| |
| # shellcheck disable=SC2086,SC2029 |
| promotable=$(ssh ${customsshkey} "$webuser@$webhost" $promotablecmd nodejs) |
| |
| if [ "X${promotable}" = "X" ]; then |
| echo "No releases to promote!" |
| exit 0 |
| fi |
| |
| echo "Found the following releases / builds ready to promote:" |
| echo "" |
| echo "$promotable" | sed 's/^/ * /' |
| echo "" |
| |
| versions=$(echo "$promotable" | cut -d: -f1) |
| |
| ################################################################################ |
| ## Promote releases |
| |
| for version in $versions; do |
| while true; do |
| files=$(echo "$promotable" | grep "^${version}" | sed 's/^'"${version}"': //') |
| printf "Promote %s files (%s)? [y/n] " "${version}" "${files}" |
| yorn="" |
| read -r yorn |
| |
| if [ "X${yorn}" = "Xn" ]; then |
| break |
| fi |
| |
| if [ "X${yorn}" != "Xy" ]; then |
| continue |
| fi |
| |
| checktag "$version" |
| |
| echo "" |
| echo "# Promoting ${version}..." |
| |
| # shellcheck disable=SC2086,SC2029 |
| ssh ${customsshkey} "$webuser@$webhost" $promotecmd nodejs $version && \ |
| sign "$version" |
| |
| break |
| done |
| done |