From a22d2468fd4fcceff226227476c23f0c002f4eb8 Mon Sep 17 00:00:00 2001 From: Akis Kesoglou Date: Wed, 20 Dec 2023 10:50:32 +0200 Subject: [PATCH] Run on CI --- .github/actions/publish/action.yml | 166 +++++++++++++++-------------- .github/actions/test/action.yml | 34 ++++-- .github/workflows/flowzone.yml | 2 +- .github/workflows/winget.yml | 3 +- .gitignore | 3 + afterSignHook.js | 25 ----- dev-app-update.yml | 4 - forge.config.ts | 90 ++++++++-------- package.json | 43 +++++++- 9 files changed, 205 insertions(+), 165 deletions(-) delete mode 100644 afterSignHook.js delete mode 100644 dev-app-update.yml diff --git a/.github/actions/publish/action.yml b/.github/actions/publish/action.yml index e0fc7e0846..55feef2f07 100644 --- a/.github/actions/publish/action.yml +++ b/.github/actions/publish/action.yml @@ -10,12 +10,12 @@ inputs: required: true # --- custom environment - XCODE_APP_LOADER_EMAIL: - type: string - default: "accounts+apple@balena.io" NODE_VERSION: type: string - default: "18.x" + # Beware that native modules will be built for this version, + # which might not be compatible with the one used by pkg (see forge.sidecar.ts) + # https://github.com/vercel/pkg-fetch/releases + default: "18.18" VERBOSE: type: string default: "true" @@ -32,7 +32,7 @@ runs: - name: Extract custom source artifact if: runner.os != 'Windows' - shell: pwsh + shell: bash working-directory: . run: tar -xf ${{ runner.temp }}/custom.tgz @@ -48,22 +48,54 @@ runs: node-version: ${{ inputs.NODE_VERSION }} cache: npm - - name: Install yq - shell: bash --noprofile --norc -eo pipefail -x {0} - run: choco install yq - if: runner.os == 'Windows' + - name: Install host dependencies + if: runner.os == 'Linux' + shell: bash + run: sudo apt-get install -y --no-install-recommends fakeroot dpkg rpm + + - name: Install host dependencies + if: runner.os == 'macOS' + # FIXME: Python 3.12 dropped distutils that node-gyp depends upon. + # This is a temporary workaround to make the job use Python 3.11 until + # we update to npm 10+. + uses: actions/setup-python@d27e3f3d7c64b4bbf8e4abfb9b63b83e846e0435 # v4 + with: + python-version: '3.11' # https://www.electron.build/code-signing.html - # https://github.com/Apple-Actions/import-codesign-certs + # https://dev.to/rwwagner90/signing-electron-apps-with-github-actions-4cof - name: Import Apple code signing certificate if: runner.os == 'macOS' - uses: apple-actions/import-codesign-certs@v1 - with: - p12-file-base64: ${{ fromJSON(inputs.secrets).APPLE_SIGNING }} - p12-password: ${{ fromJSON(inputs.secrets).APPLE_SIGNING_PASSWORD }} + shell: bash + run: | + KEY_CHAIN=build.keychain + CERTIFICATE_P12=certificate.p12 + + # Recreate the certificate from the secure environment variable + echo $CERTIFICATE_P12_B64 | base64 --decode > $CERTIFICATE_P12 + + # Create a keychain + security create-keychain -p actions $KEY_CHAIN + + # Make the keychain the default so identities are found + security default-keychain -s $KEY_CHAIN + + # Unlock the keychain + security unlock-keychain -p actions $KEY_CHAIN + + security import $CERTIFICATE_P12 -k $KEY_CHAIN -P $CERTIFICATE_PASSWORD -T /usr/bin/codesign + + security set-key-partition-list -S apple-tool:,apple: -s -k actions $KEY_CHAIN + + # remove certs + rm -fr *.p12 + env: + CERTIFICATE_P12_B64: ${{ fromJSON(inputs.secrets).APPLE_SIGNING }} + CERTIFICATE_PASSWORD: ${{ fromJSON(inputs.secrets).APPLE_SIGNING_PASSWORD }} - name: Import Windows code signing certificate if: runner.os == 'Windows' + id: import_win_signing_cert shell: powershell run: | Set-Content -Path ${{ runner.temp }}/certificate.base64 -Value $env:WINDOWS_CERTIFICATE @@ -75,91 +107,64 @@ runs: -CertStoreLocation Cert:\CurrentUser\My ` -Password (ConvertTo-SecureString -String $env:WINDOWS_CERTIFICATE_PASSWORD -Force -AsPlainText) - Remove-Item -path ${{ runner.temp }} -include certificate.pfx + echo "certFilePath=${{ runner.temp }}/certificate.pfx" >> $GITHUB_OUTPUT env: WINDOWS_CERTIFICATE: ${{ fromJSON(inputs.secrets).WINDOWS_SIGNING }} WINDOWS_CERTIFICATE_PASSWORD: ${{ fromJSON(inputs.secrets).WINDOWS_SIGNING_PASSWORD }} - # ... or refactor (e.g.) https://github.com/samuelmeuli/action-electron-builder - # https://github.com/product-os/scripts/tree/master/electron - # https://github.com/product-os/scripts/tree/master/shared - # https://github.com/product-os/balena-concourse/blob/master/pipelines/github-events/template.yml - name: Package release - id: package_release - shell: bash --noprofile --norc -eo pipefail -x {0} + shell: bash + # IMPORTANT: before making changes to this step please consult @engineering in balena's chat. run: | - set -ea + if [[ '${{ inputs.VERBOSE }}' =~ on|On|Yes|yes|true|True ]]; then + export DEBUG='electron-forge:*,electron-packager,electron-rebuild' + fi - [[ '${{ inputs.VERBOSE }}' =~ on|On|Yes|yes|true|True ]] && set -x + APPLICATION_VERSION="$(jq -r '.version' package.json)" - runner_os="$(echo "${RUNNER_OS}" | tr '[:upper:]' '[:lower:]')" - runner_arch="$(echo "${RUNNER_ARCH}" | tr '[:upper:]' '[:lower:]')" + if [[ "${RUNNER_OS}" == Linux ]]; then + BUILD_ARCHS="x64" - ELECTRON_BUILDER_ARCHITECTURE="${runner_arch}" - APPLICATION_VERSION="$(jq -r '.version' package.json)" - ARCHITECTURE_FLAGS="--${ELECTRON_BUILDER_ARCHITECTURE}" - - if [[ $runner_os =~ linux ]]; then - ELECTRON_BUILDER_OS='--linux' - TARGETS="$(yq e .linux.target[] electron-builder.yml)" - - elif [[ $runner_os =~ darwin|macos|osx ]]; then - CSC_KEY_PASSWORD=${{ fromJSON(inputs.secrets).APPLE_SIGNING_PASSWORD }} - CSC_KEYCHAIN=signing_temp - CSC_LINK=${{ fromJSON(inputs.secrets).APPLE_SIGNING }} - ELECTRON_BUILDER_OS='--mac' - TARGETS="$(yq e .mac.target[] electron-builder.yml)" - - elif [[ $runner_os =~ windows|win ]]; then - ARCHITECTURE_FLAGS="--ia32 ${ARCHITECTURE_FLAGS}" - CSC_KEY_PASSWORD=${{ fromJSON(inputs.secrets).WINDOWS_SIGNING_PASSWORD }} - CSC_LINK=${{ fromJSON(inputs.secrets).WINDOWS_SIGNING }} - ELECTRON_BUILDER_OS='--win' - TARGETS="$(yq e .win.target[] electron-builder.yml)" + elif [[ "${RUNNER_OS}" == macOS ]]; then + BUILD_ARCHS="x64,arm64" + + elif [[ "${RUNNER_OS}" == Windows ]]; then + #BUILD_ARCHS="ia32,x64" -- distutils fails to build for ia32 + BUILD_ARCHS="x64" else - exit 1 + echo "ERROR: unexpected runner OS: ${RUNNER_OS}" + exit 1 fi - npm link electron-builder - - for target in ${TARGETS}; do - electron-builder ${ELECTRON_BUILDER_OS} ${target} ${ARCHITECTURE_FLAGS} \ - --c.extraMetadata.analytics.sentry.token='https://739bbcfc0ba4481481138d3fc831136d@o95242.ingest.sentry.io/4504451487301632' \ - --c.extraMetadata.analytics.amplitude.token='balena-etcher' \ - --c.extraMetadata.packageType="${target}" - - find dist -type f -maxdepth 1 - done + npx electron-forge make --arch="${BUILD_ARCHS}" echo "version=${APPLICATION_VERSION}" >> $GITHUB_OUTPUT + # collect all artifacts from subdirectories under a common top-level directory + mkdir -p dist + find ./out/make -type f \( \ + -iname "*.zip" -o \ + -iname "*.dmg" -o \ + -iname "*.rpm" -o \ + -iname "*.deb" -o \ + -iname "*.AppImage" -o \ + -iname "*Setup.exe" \ + \) -ls -exec cp '{}' dist/ \; env: - # Apple notarization (afterSignHook.js) - XCODE_APP_LOADER_EMAIL: ${{ inputs.XCODE_APP_LOADER_EMAIL }} + # ensure we sign the artifacts + NODE_ENV: production + # analytics tokens + SENTRY_TOKEN: https://739bbcfc0ba4481481138d3fc831136d@o95242.ingest.sentry.io/4504451487301632 + AMPLITUDE_TOKEN: 'balena-etcher' + # Apple notarization + XCODE_APP_LOADER_EMAIL: ${{ fromJSON(inputs.secrets).XCODE_APP_LOADER_EMAIL }} XCODE_APP_LOADER_PASSWORD: ${{ fromJSON(inputs.secrets).XCODE_APP_LOADER_PASSWORD }} - # https://github.blog/2020-08-03-github-actions-improvements-for-fork-and-pull-request-workflows/#improvements-for-public-repository-forks - # https://docs.github.com/en/actions/managing-workflow-runs/approving-workflow-runs-from-public-forks#about-workflow-runs-from-public-forks - CSC_FOR_PULL_REQUEST: true - - # https://www.electron.build/auto-update.html#staged-rollouts - - name: Configure staged rollout(s) - shell: bash --noprofile --norc -eo pipefail -x {0} - run: | - set -ea - - [[ '${{ inputs.VERBOSE }}' =~ on|On|Yes|yes|true|True ]] && set -x - - percentage="$(cat < repo.yml | yq e .triggerNotification.stagingPercentage)" - - find dist -type f -maxdepth 1 \ - -name "latest*.yml" \ - -exec yq -i e .version=\"${{ steps.package_release.outputs.version }}\" {} \; - - find dist -type f -maxdepth 1 \ - -name "latest*.yml" \ - -exec yq -i e .stagingPercentage=\"$percentage\" {} \; + XCODE_APP_LOADER_TEAM_ID: ${{ fromJSON(inputs.secrets).XCODE_APP_LOADER_TEAM_ID }} + # Windows signing + WINDOWS_SIGNING_CERT_PATH: ${{ steps.import_win_signing_cert.outputs.certFilePath }} + WINDOWS_SIGNING_PASSWORD: ${{ fromJSON(inputs.secrets).WINDOWS_SIGNING_PASSWORD }} - name: Upload artifacts uses: actions/upload-artifact@v4 @@ -167,3 +172,4 @@ runs: name: gh-release-${{ github.event.pull_request.head.sha || github.event.head_commit.id }}-${{ runner.os }} path: dist retention-days: 1 + if-no-files-found: error diff --git a/.github/actions/test/action.yml b/.github/actions/test/action.yml index e516fb51ce..f40fba33d8 100644 --- a/.github/actions/test/action.yml +++ b/.github/actions/test/action.yml @@ -12,7 +12,7 @@ inputs: # --- custom environment NODE_VERSION: type: string - default: "16.x" + default: "18.x" VERBOSE: type: string default: "true" @@ -28,27 +28,45 @@ runs: node-version: ${{ inputs.NODE_VERSION }} cache: npm - - name: Test release - shell: bash --noprofile --norc -eo pipefail -x {0} + - name: Install host dependencies + if: runner.os == 'Linux' + shell: bash run: | - set -ea + sudo apt-get install -y --no-install-recommends xvfb libudev-dev + cat < package.json | jq -r '.hostDependencies[][]' - | \ + xargs -L1 echo | sed 's/|//g' | xargs -L1 \ + sudo apt-get --ignore-missing install || true + + - name: Install host dependencies + if: runner.os == 'macOS' + # FIXME: Python 3.12 dropped distutils that node-gyp depends upon. + # This is a temporary workaround to make the job use Python 3.11 until + # we update to npm 10+. + uses: actions/setup-python@d27e3f3d7c64b4bbf8e4abfb9b63b83e846e0435 # v4 + with: + python-version: '3.11' - [[ '${{ inputs.VERBOSE }}' =~ on|On|Yes|yes|true|True ]] && set -x + - name: Test release + shell: bash + run: | + if [[ '${{ inputs.VERBOSE }}' =~ on|On|Yes|yes|true|True ]]; then + export DEBUG='electron-forge:*,electron-packager,electron-rebuild' + fi runner_os="$(echo "${RUNNER_OS}" | tr '[:upper:]' '[:lower:]')" - npm run flowzone-preinstall-${runner_os} npm ci + npm run lint npm run package npm run test-${runner_os} env: # https://www.electronjs.org/docs/latest/api/environment-variables - ELECTRON_NO_ATTACH_CONSOLE: true + ELECTRON_NO_ATTACH_CONSOLE: 'true' - name: Compress custom source if: runner.os != 'Windows' - shell: pwsh + shell: bash run: tar -acf ${{ runner.temp }}/custom.tgz . - name: Compress custom source diff --git a/.github/workflows/flowzone.yml b/.github/workflows/flowzone.yml index 3653da56bc..bac8c673c0 100644 --- a/.github/workflows/flowzone.yml +++ b/.github/workflows/flowzone.yml @@ -18,7 +18,7 @@ jobs: (github.event.pull_request.head.repo.full_name != github.repository && github.event_name == 'pull_request_target') secrets: inherit with: - tests_run_on: '["ubuntu-20.04","macos-latest","windows-2019"]' + tests_run_on: '["ubuntu-20.04","macos-12","windows-2019"]' restrict_custom_actions: false github_prerelease: true cloudflare_website: "etcher" diff --git a/.github/workflows/winget.yml b/.github/workflows/winget.yml index 59f5bc9a28..efe1cbbdc6 100644 --- a/.github/workflows/winget.yml +++ b/.github/workflows/winget.yml @@ -9,5 +9,6 @@ jobs: - uses: vedantmgoyal2009/winget-releaser@v1 with: identifier: Balena.Etcher - installers-regex: 'balenaEtcher-Setup.*.exe$' + # matches something like "balenaEtcher-1.19.0.Setup.exe" + installers-regex: 'balenaEtcher-[\d.-]+\.Setup.exe$' token: ${{ secrets.WINGET_PAT }} diff --git a/.gitignore b/.gitignore index 60aeea3630..f523e5fe17 100644 --- a/.gitignore +++ b/.gitignore @@ -96,6 +96,9 @@ out/ # ---- Do not modify entries above this line ---- +# Build artifacts +dist/ + # Certificates *.spc *.pvk diff --git a/afterSignHook.js b/afterSignHook.js deleted file mode 100644 index f11464426b..0000000000 --- a/afterSignHook.js +++ /dev/null @@ -1,25 +0,0 @@ -'use strict' - -const { notarize } = require('electron-notarize') -const { ELECTRON_SKIP_NOTARIZATION } = process.env - -async function main(context) { - const { electronPlatformName, appOutDir } = context - if (electronPlatformName !== 'darwin' || ELECTRON_SKIP_NOTARIZATION === 'true') { - return - } - - const appName = context.packager.appInfo.productFilename - const appleId = process.env.XCODE_APP_LOADER_EMAIL || 'accounts+apple@balena.io' - const appleIdPassword = process.env.XCODE_APP_LOADER_PASSWORD - - // https://github.com/electron/notarize/blob/main/README.md - await notarize({ - appBundleId: 'io.balena.etcher', - appPath: `${appOutDir}/${appName}.app`, - appleId, - appleIdPassword - }) -} - -exports.default = main diff --git a/dev-app-update.yml b/dev-app-update.yml deleted file mode 100644 index 41f675986c..0000000000 --- a/dev-app-update.yml +++ /dev/null @@ -1,4 +0,0 @@ -owner: balena-io -repo: etcher -provider: github -updaterCacheDirName: balena-etcher-updater diff --git a/forge.config.ts b/forge.config.ts index 5020705563..d9662ec05b 100644 --- a/forge.config.ts +++ b/forge.config.ts @@ -10,7 +10,24 @@ import { WebpackPlugin } from '@electron-forge/plugin-webpack'; import { mainConfig, rendererConfig } from './webpack.config'; -import { productDescription } from './package.json'; +import { hostDependencies, productDescription } from './package.json'; + +const osxSigningConfig: any = {}; +let winSigningConfig: any = {}; + +if (process.env.NODE_ENV === 'production') { + osxSigningConfig.osxNotarize = { + tool: 'notarytool', + appleId: process.env.XCODE_APP_LOADER_EMAIL, + appleIdPassword: process.env.XCODE_APP_LOADER_PASSWORD, + teamId: process.env.XCODE_APP_LOADER_TEAM_ID, + }; + + winSigningConfig = { + certificateFile: process.env.WINDOWS_SIGNING_CERT_PATH, + certificatePassword: process.env.WINDOWS_SIGNING_PASSWORD, + }; +} const config: ForgeConfig = { packagerConfig: { @@ -23,20 +40,24 @@ const config: ForgeConfig = { appCopyright: 'Copyright 2016-2023 Balena Ltd', darwinDarkModeSupport: true, protocols: [{ name: 'etcher', schemes: ['etcher'] }], - - // osxSign: {}, - // osxNotarize: {}, - extraResource: [ 'lib/shared/catalina-sudo/sudo-askpass.osascript-zh.js', 'lib/shared/catalina-sudo/sudo-askpass.osascript-en.js', ], + osxSign: { + optionsForFile: () => ({ + entitlements: './entitlements.mac.plist', + hardenedRuntime: true, + }), + }, + ...osxSigningConfig, }, rebuildConfig: {}, makers: [ new MakerZIP(), new MakerSquirrel({ setupIcon: 'assets/icon.ico', + ...winSigningConfig, }), new MakerDMG({ background: './assets/dmg/background.tiff', @@ -85,43 +106,7 @@ const config: ForgeConfig = { scripts: { postinst: './after-install.tpl', }, - depends: [ - 'gconf-service', - 'gconf2', - 'libasound2', - 'libatk1.0-0', - 'libc6', - 'libcairo2', - 'libcups2', - 'libdbus-1-3', - 'libexpat1', - 'libfontconfig1', - 'libfreetype6', - 'libgbm1', - 'libgcc1', - 'libgconf-2-4', - 'libgdk-pixbuf2.0-0', - 'libglib2.0-0', - 'libgtk-3-0', - 'liblzma5', - 'libnotify4', - 'libnspr4', - 'libnss3', - 'libpango1.0-0 | libpango-1.0-0', - 'libstdc++6', - 'libx11-6', - 'libxcomposite1', - 'libxcursor1', - 'libxdamage1', - 'libxext6', - 'libxfixes3', - 'libxi6', - 'libxrandr2', - 'libxrender1', - 'libxss1', - 'libxtst6', - 'polkit-1-auth-agent | policykit-1-gnome | polkit-kde-1', - ], + depends: hostDependencies['debian'], }, }), ], @@ -145,6 +130,27 @@ const config: ForgeConfig = { }, }), ], + hooks: { + readPackageJson: async (_config, packageJson) => { + packageJson.analytics = {}; + + if (process.env.SENTRY_TOKEN) { + packageJson.analytics.sentry = { + token: process.env.SENTRY_TOKEN, + }; + } + + if (process.env.AMPLITUDE_TOKEN) { + packageJson.analytics.amplitude = { + token: 'balena-etcher', + }; + } + + // packageJson.packageType = 'dmg' | 'AppImage' | 'rpm' | 'deb' | 'zip' | 'nsis' | 'portable' + + return packageJson; + }, + }, }; export default config; diff --git a/package.json b/package.json index 85ce1e6ace..f282e5bdc9 100644 --- a/package.json +++ b/package.json @@ -16,10 +16,6 @@ "scripts": { "build:rebuild-mountutils": "cd node_modules/mountutils && npm rebuild", "build:sidecar": "npm run build:rebuild-mountutils && tsc --project tsconfig.sidecar.json && pkg build/util/api.js -c pkg-sidecar.json --target node18 --output generated/etcher-util", - "flowzone-preinstall-linux": "sudo apt-get update && sudo apt-get install -y xvfb libudev-dev && cat < electron-builder.yml | yq e .deb.depends[] - | xargs -L1 echo | sed 's/|//g' | xargs -L1 sudo apt-get --ignore-missing install || true", - "flowzone-preinstall-macos": "true", - "flowzone-preinstall-windows": "npx node-gyp install", - "flowzone-preinstall": "npm run flowzone-preinstall-linux", "lint-css": "prettier --write lib/**/*.css", "lint-ts": "balena-lint --fix --typescript typings lib tests forge.config.ts webpack.config.ts", "lint": "npm run lint-ts && npm run lint-css", @@ -124,6 +120,45 @@ "typescript": "4.4.4", "url-loader": "4.1.1" }, + "hostDependencies": { + "debian": [ + "gconf-service", + "gconf2", + "libasound2", + "libatk1.0-0", + "libc6", + "libcairo2", + "libcups2", + "libdbus-1-3", + "libexpat1", + "libfontconfig1", + "libfreetype6", + "libgbm1", + "libgcc1", + "libgconf-2-4", + "libgdk-pixbuf2.0-0", + "libglib2.0-0", + "libgtk-3-0", + "liblzma5", + "libnotify4", + "libnspr4", + "libnss3", + "libpango1.0-0 | libpango-1.0-0", + "libstdc++6", + "libx11-6", + "libxcomposite1", + "libxcursor1", + "libxdamage1", + "libxext6", + "libxfixes3", + "libxi6", + "libxrandr2", + "libxrender1", + "libxss1", + "libxtst6", + "polkit-1-auth-agent | policykit-1-gnome | polkit-kde-1" + ] + }, "engines": { "node": ">=18 <20" },