1#!/bin/sh 2 3# To promote and sign a release that has been prepared by the build jobs, use: 4# release.sh 5 6# To _only_ sign an existing release, use: 7# release.sh -s vx.y.z 8 9set -e 10 11[ -z "$NODEJS_RELEASE_HOST" ] && NODEJS_RELEASE_HOST=direct.nodejs.org 12 13webhost=$NODEJS_RELEASE_HOST 14webuser=dist 15promotablecmd=dist-promotable 16promotecmd=dist-promote 17signcmd=dist-sign 18customsshkey="" # let ssh and scp use default key 19signversion="" 20 21while getopts ":i:s:" option; do 22 case "${option}" in 23 i) 24 customsshkey="-i ${OPTARG}" 25 ;; 26 s) 27 signversion="${OPTARG}" 28 ;; 29 \?) 30 echo "Invalid option -$OPTARG." 31 exit 1 32 ;; 33 *) 34 echo "Option -$OPTARG takes a parameter." 35 exit 1 36 ;; 37 esac 38done 39shift $((OPTIND-1)) 40 41################################################################################ 42## Select a GPG key to use 43 44echo "# Selecting GPG key ..." 45 46gpgkey=$(gpg --list-secret-keys --keyid-format SHORT | awk -F'( +|/)' '/^(sec|ssb)/{print $3}') 47keycount=$(echo "$gpgkey" | wc -w) 48 49if [ "$keycount" -eq 0 ]; then 50 # shellcheck disable=SC2016 51 echo 'Need at least one GPG key, please make one with `gpg --gen-key`' 52 echo 'You will also need to submit your key to a public keyserver, e.g.' 53 echo ' https://sks-keyservers.net/i/#submit' 54 exit 1 55elif [ "$keycount" -ne 1 ]; then 56 printf "You have multiple GPG keys:\n\n" 57 58 gpg --list-secret-keys 59 60 keynum= 61 while [ -z "${keynum##*[!0-9]*}" ] || [ "$keynum" -le 0 ] || [ "$keynum" -gt "$keycount" ]; do 62 echo "$gpgkey" | awk '{ print NR ") " $0; }' 63 printf 'Select a key: ' 64 read -r keynum 65 done 66 echo "" 67 gpgkey=$(echo "$gpgkey" | sed -n "${keynum}p") 68fi 69 70gpgfing=$(gpg --keyid-format 0xLONG --fingerprint "$gpgkey" | grep 'Key fingerprint =' | awk -F' = ' '{print $2}' | tr -d ' ') 71 72grep -q "$gpgfing" README.md || (\ 73 echo 'Error: this GPG key fingerprint is not listed in ./README.md' && \ 74 exit 1 \ 75) 76 77 78echo "Using GPG key: $gpgkey" 79echo " Fingerprint: $gpgfing" 80 81checktag() { 82 # local version=$1 83 84 if ! git tag -v "$1" 2>&1 | grep "${gpgkey}" | grep key > /dev/null; then 85 echo "Could not find signed tag for \"$1\" or GPG key is not yours" 86 exit 1 87 fi 88} 89 90################################################################################ 91## Create and sign checksums file for a given version 92 93sign() { 94 printf "\n# Creating SHASUMS256.txt ...\n" 95 96 # local version=$1 97 98 ghtaggedversion=$(curl -sL "https://raw.githubusercontent.com/nodejs/node/$1/src/node_version.h" \ 99 | awk '/define NODE_(MAJOR|MINOR|PATCH)_VERSION/{ v = v "." $3 } END{ v = "v" substr(v, 2); print v }') 100 if [ "$1" != "${ghtaggedversion}" ]; then 101 echo "Could not find tagged version on github.com/nodejs/node, did you push your tag?" 102 exit 1 103 fi 104 105 # shellcheck disable=SC2086,SC2029 106 shapath=$(ssh ${customsshkey} "${webuser}@${webhost}" $signcmd nodejs $1) 107 108 echo "${shapath}" | grep -q '^/.*/SHASUMS256.txt$' || (\ 109 echo 'Error: No SHASUMS file returned by sign!' &&\ 110 exit 1) 111 112 echo "" 113 echo "# Signing SHASUMS for $1..." 114 115 shafile=$(basename "$shapath") 116 shadir=$(dirname "$shapath") 117 tmpdir="/tmp/_node_release.$$" 118 119 mkdir -p $tmpdir 120 121 # shellcheck disable=SC2086 122 scp ${customsshkey} "${webuser}@${webhost}:${shapath}" "${tmpdir}/${shafile}" 123 124 gpg --default-key "$gpgkey" --clearsign --digest-algo SHA256 "${tmpdir}/${shafile}" 125 gpg --default-key "$gpgkey" --detach-sign --digest-algo SHA256 "${tmpdir}/${shafile}" 126 127 echo "Wrote to ${tmpdir}/" 128 129 echo "Your signed ${shafile}.asc:" 130 echo "" 131 132 cat "${tmpdir}/${shafile}.asc" 133 134 echo "" 135 136 while true; do 137 printf "Upload files? [y/n] " 138 yorn="" 139 read -r yorn 140 141 if [ "X${yorn}" = "Xn" ]; then 142 break 143 fi 144 145 if [ "X${yorn}" = "Xy" ]; then 146 # shellcheck disable=SC2086 147 scp ${customsshkey} "${tmpdir}/${shafile}" "${tmpdir}/${shafile}.asc" "${tmpdir}/${shafile}.sig" "${webuser}@${webhost}:${shadir}/" 148 # shellcheck disable=SC2086,SC2029 149 ssh ${customsshkey} "${webuser}@${webhost}" chmod 644 "${shadir}/${shafile}.asc" "${shadir}/${shafile}.sig" 150 break 151 fi 152 done 153 154 rm -rf $tmpdir 155} 156 157 158if [ -n "${signversion}" ]; then 159 checktag "$signversion" 160 sign "$signversion" 161 exit 0 162fi 163 164# else: do a normal release & promote 165 166################################################################################ 167## Look for releases to promote 168 169printf "\n# Checking for releases ...\n" 170 171# shellcheck disable=SC2086,SC2029 172promotable=$(ssh ${customsshkey} "$webuser@$webhost" $promotablecmd nodejs) 173 174if [ "X${promotable}" = "X" ]; then 175 echo "No releases to promote!" 176 exit 0 177fi 178 179echo "Found the following releases / builds ready to promote:" 180echo "" 181echo "$promotable" | sed 's/^/ * /' 182echo "" 183 184versions=$(echo "$promotable" | cut -d: -f1) 185 186################################################################################ 187## Promote releases 188 189for version in $versions; do 190 while true; do 191 files=$(echo "$promotable" | grep "^${version}" | sed 's/^'"${version}"': //') 192 printf "Promote %s files (%s)? [y/n] " "${version}" "${files}" 193 yorn="" 194 read -r yorn 195 196 if [ "X${yorn}" = "Xn" ]; then 197 break 198 fi 199 200 if [ "X${yorn}" != "Xy" ]; then 201 continue 202 fi 203 204 checktag "$version" 205 206 echo "" 207 echo "# Promoting ${version}..." 208 209 # shellcheck disable=SC2086,SC2029 210 ssh ${customsshkey} "$webuser@$webhost" $promotecmd nodejs $version && \ 211 sign "$version" 212 213 break 214 done 215done 216