#!/bin/bash # # This script is used for stage 1 of the release process. It operates exclusively on the airlock. # This script, for the RELEASEMANAGER (RM): # - builds and uploads all binaries to airlock, # - assumes all keys are available, and signs everything # This script, for other builders: # - builds all reproducible binaries, # - downloads binaries built by the release manager (from airlock), compares and signs them, # - and then uploads sigs # Note: the .dmg should be built separately beforehand and copied into dist/ # (as it is built on a separate machine) # # # env vars: # - ELECBUILD_NOCACHE: if set, forces rebuild of docker images # # "uploadserver" is set in /etc/hosts # # Note: steps before doing a new release: # - update locale: # 1. cd /opt/electrum-locale && ./update && git push # 2. cd to the submodule dir, and git pull # 3. cd .. && git push # - update RELEASE-NOTES and version.py # - $ git tag -s $VERSION -m $VERSION # # ----- # Then, typical release flow: # - RM runs release.sh # - Another SFTPUSER BUILDER runs `$ ./release.sh` # - now airlock contains new binaries and two sigs for each # - deploy.sh will verify sigs and move binaries across airlock # - new binaries are now publicly available on uploadserver, but not linked from website yet # - other BUILDERS can now also try to reproduce binaries and open PRs with sigs against spesmilo/electrum-signatures # - these PRs can get merged as they come # - run add_cosigner # - after some time, RM can run release_www.sh to create and commit website-update # - then run WWW_DIR/publish.sh to update website # - at least two people need to run WWW_DIR/publish.sh # set -e PROJECT_ROOT="$(dirname "$(readlink -e "$0")")/.." CONTRIB="$PROJECT_ROOT/contrib" cd "$PROJECT_ROOT" . "$CONTRIB"/build_tools_util.sh # rm -rf dist/* # rm -f .buildozer GPGUSER=$1 if [ -z "$GPGUSER" ]; then fail "usage: $0 gpg_username" fi export SSHUSER="$GPGUSER" RELEASEMANAGER="" if [ "$GPGUSER" == "ThomasV" ]; then PUBKEY="--local-user 6694D8DE7BE8EE5631BED9502BD5824B7F9470E6" export SSHUSER=thomasv RELEASEMANAGER=1 elif [ "$GPGUSER" == "sombernight_releasekey" ]; then PUBKEY="--local-user 0EEDCFD5CAFB459067349B23CA9EEEC43DF911DC" export SSHUSER=sombernight fi if [ ! -z "$RELEASEMANAGER" ] ; then echo -n "Code signing passphrase:" read -s password # tests password against keystore keytool -list -storepass $password # the same password is used for windows signing export WIN_SIGNING_PASSWORD=$password fi VERSION=$("$CONTRIB"/print_electrum_version.py) info "VERSION: $VERSION" REV=$(git describe --tags) info "REV: $REV" COMMIT=$(git rev-parse HEAD) export ELECBUILD_COMMIT="${COMMIT}^{commit}" git_status=$(git status --porcelain) if [ ! -z "$git_status" ]; then echo "$git_status" fail "git repo not clean, aborting" fi set -x # create tarball tarball="Electrum-$VERSION.tar.gz" if test -f "dist/$tarball"; then info "file exists: $tarball" else ./contrib/build-linux/sdist/build.sh fi # create source-only tarball srctarball="Electrum-sourceonly-$VERSION.tar.gz" if test -f "dist/$srctarball"; then info "file exists: $srctarball" else OMIT_UNCLEAN_FILES=1 ./contrib/build-linux/sdist/build.sh fi # appimage appimage="electrum-$REV-x86_64.AppImage" if test -f "dist/$appimage"; then info "file exists: $appimage" else ./contrib/build-linux/appimage/build.sh fi # windows win1="electrum-$REV.exe" win2="electrum-$REV-portable.exe" win3="electrum-$REV-setup.exe" if test -f "dist/$win1"; then info "file exists: $win1" else pushd . if test -f "contrib/build-wine/dist/$win1"; then info "unsigned file exists: $win1" else ./contrib/build-wine/build.sh fi cd contrib/build-wine/ if [ ! -z "$RELEASEMANAGER" ] ; then ./sign.sh cp ./signed/*.exe "$PROJECT_ROOT/dist/" else cp ./dist/*.exe "$PROJECT_ROOT/dist/" fi popd fi # android apk1="Electrum-$VERSION.0-armeabi-v7a-release.apk" apk1_unsigned="Electrum-$VERSION.0-armeabi-v7a-release-unsigned.apk" apk2="Electrum-$VERSION.0-arm64-v8a-release.apk" apk2_unsigned="Electrum-$VERSION.0-arm64-v8a-release-unsigned.apk" if test -f "dist/$apk1"; then info "file exists: $apk1" else if [ ! -z "$RELEASEMANAGER" ] ; then ./contrib/android/build.sh kivy all release $password else ./contrib/android/build.sh kivy all release-unsigned mv "dist/$apk1_unsigned" "dist/$apk1" mv "dist/$apk2_unsigned" "dist/$apk2" fi fi # the macos binary is built on a separate machine. # the file that needs to be copied over is the codesigned release binary (regardless of builder role) dmg=electrum-$VERSION.dmg if ! test -f "dist/$dmg"; then if [ ! -z "$RELEASEMANAGER" ] ; then # RM fail "dmg is missing, aborting. Please build and codesign the dmg on a mac and copy it over." else # other builders fail "dmg is missing, aborting. Please build the unsigned dmg on a mac, compare it with file built by RM, and if matches, copy RM's dmg." fi fi # now that we have all binaries, if we are the RM, sign them. if [ ! -z "$RELEASEMANAGER" ] ; then if test -f "dist/$dmg.asc"; then info "packages are already signed" else info "signing packages" ./contrib/sign_packages "$GPGUSER" fi fi info "build complete" sha256sum dist/*.tar.gz sha256sum dist/*.AppImage sha256sum contrib/build-wine/dist/*.exe echo -n "proceed (y/n)? " read answer if [ "$answer" != "y" ]; then echo "exit" exit 1 fi if [ -z "$RELEASEMANAGER" ] ; then # people OTHER THAN release manager. # download binaries built by RM rm -rf "$PROJECT_ROOT/dist/releasemanager" mkdir --parent "$PROJECT_ROOT/dist/releasemanager" cd "$PROJECT_ROOT/dist/releasemanager" # TODO check somehow that RM had finished uploading sftp -oBatchMode=no -b - "$SSHUSER@uploadserver" << ! cd electrum-downloads-airlock cd "$VERSION" mget * bye ! # check we have each binary test -f "$tarball" || fail "tarball not found among sftp downloads" test -f "$srctarball" || fail "srctarball not found among sftp downloads" test -f "$appimage" || fail "appimage not found among sftp downloads" test -f "$win1" || fail "win1 not found among sftp downloads" test -f "$win2" || fail "win2 not found among sftp downloads" test -f "$win3" || fail "win3 not found among sftp downloads" test -f "$apk1" || fail "apk1 not found among sftp downloads" test -f "$apk2" || fail "apk2 not found among sftp downloads" test -f "$dmg" || fail "dmg not found among sftp downloads" test -f "$PROJECT_ROOT/dist/$tarball" || fail "tarball not found among built files" test -f "$PROJECT_ROOT/dist/$srctarball" || fail "srctarball not found among built files" test -f "$PROJECT_ROOT/dist/$appimage" || fail "appimage not found among built files" test -f "$CONTRIB/build-wine/dist/$win1" || fail "win1 not found among built files" test -f "$CONTRIB/build-wine/dist/$win2" || fail "win2 not found among built files" test -f "$CONTRIB/build-wine/dist/$win3" || fail "win3 not found among built files" test -f "$PROJECT_ROOT/dist/$apk1" || fail "apk1 not found among built files" test -f "$PROJECT_ROOT/dist/$apk2" || fail "apk2 not found among built files" test -f "$PROJECT_ROOT/dist/$dmg" || fail "dmg not found among built files" # compare downloaded binaries against ones we built cmp --silent "$tarball" "$PROJECT_ROOT/dist/$tarball" || fail "files are different. tarball." cmp --silent "$srctarball" "$PROJECT_ROOT/dist/$srctarball" || fail "files are different. srctarball." cmp --silent "$appimage" "$PROJECT_ROOT/dist/$appimage" || fail "files are different. appimage." rm -rf "$CONTRIB/build-wine/signed/" && mkdir --parents "$CONTRIB/build-wine/signed/" cp -f "$win1" "$win2" "$win3" "$CONTRIB/build-wine/signed/" "$CONTRIB/build-wine/unsign.sh" || fail "files are different. windows." "$CONTRIB/android/apkdiff.py" "$apk1" "$PROJECT_ROOT/dist/$apk1" || fail "files are different. android." "$CONTRIB/android/apkdiff.py" "$apk2" "$PROJECT_ROOT/dist/$apk2" || fail "files are different. android." cmp --silent "$dmg" "$PROJECT_ROOT/dist/$dmg" || fail "files are different. macos." # all files matched. sign them. rm -rf "$PROJECT_ROOT/dist/sigs/" mkdir --parents "$PROJECT_ROOT/dist/sigs/" for fname in "$tarball" "$srctarball" "$appimage" "$win1" "$win2" "$win3" "$apk1" "$apk2" "$dmg" ; do signame="$fname.$GPGUSER.asc" gpg --sign --armor --detach $PUBKEY --output "$PROJECT_ROOT/dist/sigs/$signame" "$fname" done # upload sigs ELECBUILD_UPLOADFROM="$PROJECT_ROOT/dist/sigs/" "$CONTRIB/upload.sh" else # ONLY release manager cd "$PROJECT_ROOT" if [ $REV != $VERSION ]; then fail "versions differ, not uploading" fi # upload the files if test -f dist/uploaded; then info "files already uploaded" else ./contrib/upload.sh touch dist/uploaded fi fi info "release.sh finished successfully." info "After two people ran release.sh, the binaries will be publicly available on uploadserver." info "Then, we wait for additional signers, and run add_cosigner for them." info "Finally, release_www.sh needs to be run, for the website to be updated."