From 33fc3518c0fe4cf02ca61270b5a73fdcae550fe7 Mon Sep 17 00:00:00 2001 From: Evan Simkowitz Date: Wed, 6 Mar 2024 16:07:48 -0800 Subject: [PATCH] Sign and notarize directly in build-helper (#389) * Sign and notarize in CI * add dmg * remove flag * fix env var * add team id * conditionally set apple specific env vars * publish to a staging location * upload unzipped * add script to publish to staging, update publish url * turn off autodiscovery again * update scripts * deprecate old method * move stuff * remove autodiscovery --- .github/workflows/build-helper.yml | 11 +++-- buildres/.gitignore | 3 +- buildres/README.md | 42 ++++++++++--------- buildres/{ => deprecated}/generate-hash.js | 0 buildres/{ => deprecated}/osx-notarize.js | 0 buildres/{ => deprecated}/osx-sign.js | 0 buildres/{ => deprecated}/prepare-macos.sh | 0 .../{ => deprecated}/update-latest-mac.js | 0 buildres/{ => deprecated}/upload-release.sh | 0 buildres/download-staged-artifact.sh | 16 +++++++ buildres/publish-from-staging.sh | 12 ++++++ electron-builder.config.js | 21 +++++++--- src/electron/emain.ts | 9 +--- src/util/util.ts | 2 +- 14 files changed, 78 insertions(+), 38 deletions(-) rename buildres/{ => deprecated}/generate-hash.js (100%) rename buildres/{ => deprecated}/osx-notarize.js (100%) rename buildres/{ => deprecated}/osx-sign.js (100%) rename buildres/{ => deprecated}/prepare-macos.sh (100%) rename buildres/{ => deprecated}/update-latest-mac.js (100%) rename buildres/{ => deprecated}/upload-release.sh (100%) create mode 100644 buildres/download-staged-artifact.sh create mode 100644 buildres/publish-from-staging.sh diff --git a/.github/workflows/build-helper.yml b/.github/workflows/build-helper.yml index dfdfdb489..7507364a1 100644 --- a/.github/workflows/build-helper.yml +++ b/.github/workflows/build-helper.yml @@ -25,7 +25,7 @@ jobs: with: repository: scripthaus-dev/scripthaus path: scripthaus - - name: Install Linux Build Dependencies + - name: Install Linux Build Dependencies (Linux only) if: matrix.platform == 'linux' run: | sudo apt-get update @@ -58,6 +58,11 @@ jobs: run: ./scripthaus/scripthaus run ${{ matrix.scripthaus }} env: GOARCH: ${{ matrix.arch }} + CSC_LINK: ${{ matrix.platform == 'darwin' && secrets.PROD_MACOS_CERTIFICATE}} + CSC_KEY_PASSWORD: ${{ matrix.platform == 'darwin' && secrets.PROD_MACOS_CERTIFICATE_PWD }} + APPLE_ID: ${{ matrix.platform == 'darwin' && secrets.PROD_MACOS_NOTARIZATION_APPLE_ID }} + APPLE_APP_SPECIFIC_PASSWORD: ${{ matrix.platform == 'darwin' && secrets.PROD_MACOS_NOTARIZATION_PWD }} + APPLE_TEAM_ID: ${{ matrix.platform == 'darwin' && secrets.PROD_MACOS_NOTARIZATION_TEAM_ID }} - uses: actions/upload-artifact@v4 with: name: waveterm-build-${{ matrix.platform }} @@ -75,10 +80,8 @@ jobs: - name: Set `version.txt` run: | echo "${{ needs.runbuild.outputs.WAVETERM_VERSION }}" >> buildtemp/version.txt - - name: Zip Builds - run: (cd buildtemp; zip -q ../waveterm-builds.zip *) - name: Upload to S3 - run: aws s3 cp waveterm-builds.zip s3://waveterm-github-artifacts/ + run: aws s3 cp buildtemp/ s3://waveterm-github-artifacts/staging/${{ needs.runbuild.outputs.WAVETERM_VERSION }}/ --recursive --exclude "builder-*.yml" env: AWS_ACCESS_KEY_ID: "${{ secrets.S3_USERID }}" AWS_SECRET_ACCESS_KEY: "${{ secrets.S3_SECRETKEY }}" diff --git a/buildres/.gitignore b/buildres/.gitignore index b3dc2fc87..a3e60d5ba 100644 --- a/buildres/.gitignore +++ b/buildres/.gitignore @@ -1,3 +1,4 @@ -*/ +*builds/ +*-staged/ *.zip *.dmg diff --git a/buildres/README.md b/buildres/README.md index 4044f2ca3..3f3a52a15 100644 --- a/buildres/README.md +++ b/buildres/README.md @@ -2,7 +2,7 @@ ## Build Helper workflow -Our release builds are managed by the "Build Helper" GitHub Action, which is defined +Our release builds are managed by the "Build Helper" GitHub Action, which is defined in [`build-helper.yml`](../.github/workflows/build-helper.yml). Under the hood, this will call the `build-package` and `build-package-linux` scripts in @@ -11,29 +11,42 @@ WebPack and then the `wavesrv` and `mshell` binaries, then it will call `electro to generate the distributable app packages. The configuration for `electron-builder` is [`electron-builder.config.js`](../electron-builder.config.js). -We are working to fully automate the building of release artifacts. For now, -manual steps are still required to sign and notarize the macOS artifacts. The -Linux artifacts do not require additional modification before being published. +This will also sign and notarize the macOS app package. -## Local signing and notarizing for macOS +Once a build is complete, it will be placed in `s3://waveterm-github-artifacts/staging/`. +It can be downloaded for testing using the [`download-staged-artifact.sh`](./download-staged-artifact.sh) +script. When you are ready to publish the artifacts to the public release feed, use the +[`publish-from-staging.sh`](./publish-from-staging.sh) script to directly copy the artifacts from +the staging bucket to the releases bucket. -The [`prepare-macos.sh`](./prepare-macos.sh) script will download the latest build +## Automatic updates + +Thanks to `electron-updater`, we are able to provide automatic app updates for macOS and Linux, +as long as the app was distributed as a DMG, AppImage, RPM, or DEB file. + +With each release, `latest-mac.yml` and `latest-linux.yml` files will be produced that point to the +newest release. These also include file sizes and checksums to aid in validating the packages. The app +will check these files in our S3 bucket every hour to see if a new version is available. + +## Local signing and notarizing for macOS (Deprecated) + +The [`prepare-macos.sh`](./deprecated/prepare-macos.sh) script will download the latest build artifacts from S3 and sign and notarize the macOS binaries within it. It will then generate a DMG and a new ZIP archive with the new signed app. This will call a few different JS scripts to perform more complicated operations. -[`osx-sign.js`](./osx-sign.js) and [`osx-notarize.js`](./osx-notarize.js) call +[`osx-sign.js`](./deprecated/osx-sign.js) and [`osx-notarize.js`](./deprecated/osx-notarize.js) call underlying Electron APIs to sign and notarize the package. -[`update-latest-mac.js`](./update-latest-mac.js) will then update the `latest-mac.yml` +[`update-latest-mac.js`](./deprecated/update-latest-mac.js) will then update the `latest-mac.yml` file with the SHA512 checksum and file size of the new signed and notarized installer. This is important for the `electron-updater` auto-update mechanism to then find and validate new releases. -## Uploading release artifacts for distribution +## Uploading release artifacts for distribution (Deprecated) ### Upload script Once the build has been fully validated and is ready to be released, the -[`upload-release.sh`](./upload-release.sh) script is then used to grab the completed +[`upload-release.sh`](./deprecated/upload-release.sh) script is then used to grab the completed artifacts and upload them to the `dl.waveterm.dev` S3 bucket for distribution. ### Homebrew @@ -56,12 +69,3 @@ command. More information can be found We also exclude most of our `node_modules` from packaging, as WebPack handles packaging of any dependencies for us. The one exception is `monaco-editor`. - -## Automatic updates - -Thanks to `electron-updater`, we are able to provide automatic app updates for macOS and Linux, -as long as the app was distributed as a DMG, AppImage, RPM, or DEB file. - -With each release, `latest-mac.yml` and `latest-linux.yml` files will be produced that point to the -newest release. These also include file sizes and checksums to aid in validating the packages. The app -will check these files in our S3 bucket every hour to see if a new version is available. diff --git a/buildres/generate-hash.js b/buildres/deprecated/generate-hash.js similarity index 100% rename from buildres/generate-hash.js rename to buildres/deprecated/generate-hash.js diff --git a/buildres/osx-notarize.js b/buildres/deprecated/osx-notarize.js similarity index 100% rename from buildres/osx-notarize.js rename to buildres/deprecated/osx-notarize.js diff --git a/buildres/osx-sign.js b/buildres/deprecated/osx-sign.js similarity index 100% rename from buildres/osx-sign.js rename to buildres/deprecated/osx-sign.js diff --git a/buildres/prepare-macos.sh b/buildres/deprecated/prepare-macos.sh similarity index 100% rename from buildres/prepare-macos.sh rename to buildres/deprecated/prepare-macos.sh diff --git a/buildres/update-latest-mac.js b/buildres/deprecated/update-latest-mac.js similarity index 100% rename from buildres/update-latest-mac.js rename to buildres/deprecated/update-latest-mac.js diff --git a/buildres/upload-release.sh b/buildres/deprecated/upload-release.sh similarity index 100% rename from buildres/upload-release.sh rename to buildres/deprecated/upload-release.sh diff --git a/buildres/download-staged-artifact.sh b/buildres/download-staged-artifact.sh new file mode 100644 index 000000000..994306eae --- /dev/null +++ b/buildres/download-staged-artifact.sh @@ -0,0 +1,16 @@ +# Downloads the artifacts for the specified version from the staging bucket for local testing. +# Usage: download-staged-artifact.sh +# Example: download-staged-artifact.sh 0.1.0 + +# Retrieve version from the first argument +VERSION=$1 +if [ -z "$VERSION" ]; then + echo "Usage: $0 " + exit +fi + +# Download the artifacts for the specified version from the staging bucket +DOWNLOAD_DIR=$VERSION-staged +rm -rf $DOWNLOAD_DIR +mkdir -p $DOWNLOAD_DIR +aws s3 cp s3://waveterm-github-artifacts/staging/$VERSION/ $DOWNLOAD_DIR/ --recursive diff --git a/buildres/publish-from-staging.sh b/buildres/publish-from-staging.sh new file mode 100644 index 000000000..85b7fa19f --- /dev/null +++ b/buildres/publish-from-staging.sh @@ -0,0 +1,12 @@ +# Takes a release from our staging bucket and publishes it to the public download bucket. +# Usage: publish-from-staging.sh +# Example: publish-from-staging.sh 0.1.0 + +# Takes the version as an argument +VERSION=$1 +if [ -z "$VERSION" ]; then + echo "Usage: $0 " + exit +fi + +aws s3 cp s3://waveterm-github-artifacts/staging/$VERSION/ s3://dl.waveterm.dev/releases/ --recursive \ No newline at end of file diff --git a/electron-builder.config.js b/electron-builder.config.js index 0f131c48b..b9d871dcf 100644 --- a/electron-builder.config.js +++ b/electron-builder.config.js @@ -46,13 +46,24 @@ const config = { }, asarUnpack: ["bin/**/*"], mac: { - target: { - target: "zip", - arch: "universal", - }, + target: [ + { + target: "zip", + arch: "universal", + }, + { + target: "dmg", + arch: "universal", + }, + ], icon: "public/waveterm.icns", category: "public.app-category.developer-tools", minimumSystemVersion: "10.15.0", + notarize: process.env.APPLE_TEAM_ID + ? { + teamId: process.env.APPLE_TEAM_ID, + } + : false, binaries: fs .readdirSync("bin", { recursive: true, withFileTypes: true }) .filter((f) => f.isFile()) @@ -77,7 +88,7 @@ const config = { }, publish: { provider: "generic", - url: "https://waveterm-test-autoupdate.s3.us-west-2.amazonaws.com/autoupdate", + url: "https://dl.waveterm.dev/releases", }, }; diff --git a/src/electron/emain.ts b/src/electron/emain.ts index cf550ed7d..789b1fe0c 100644 --- a/src/electron/emain.ts +++ b/src/electron/emain.ts @@ -819,13 +819,6 @@ function initUpdater(): NodeJS.Timeout { } setAppUpdateStatus("unavailable"); - let feedURL = `https://dl.waveterm.dev/autoupdate/${unamePlatform}/${unameArch}`; - let serverType: "default" | "json" = "default"; - - if (unamePlatform == "darwin") { - feedURL += "/RELEASES.json"; - serverType = "json"; - } autoUpdater.removeAllListeners(); @@ -865,7 +858,7 @@ function initUpdater(): NodeJS.Timeout { // check for updates right away and keep checking later autoUpdater.checkForUpdates(); - return setInterval(autoUpdater.checkForUpdates, 3600000); // 1 hour in ms + return setInterval(() => fireAndForget(autoUpdater.checkForUpdates), 3600000); // 1 hour in ms } /** diff --git a/src/util/util.ts b/src/util/util.ts index 36a4c44ec..f148d242f 100644 --- a/src/util/util.ts +++ b/src/util/util.ts @@ -387,7 +387,7 @@ function ces(s: string) { * A wrapper function for running a promise and catching any errors * @param f The promise to run */ -function fireAndForget(f: () => Promise) { +function fireAndForget(f: () => Promise) { f().catch((e) => { console.log("fireAndForget error", e); });