From c124427c45b69403af7845f5ddd754b19a53ce80 Mon Sep 17 00:00:00 2001 From: Umberto Baldi <34278123+umbynos@users.noreply.github.com> Date: Fri, 27 Jan 2023 15:32:42 +0100 Subject: [PATCH 1/6] Upload the signed bundle to s3 (#756) * remove some code duplication regarding prerelease calculation * add upload of the notarized bundle to s3 download bucket * add upload of the notarized bundle to s3 download bucket --- .github/workflows/release.yml | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9e6837e56..8ec4cf51b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,7 +8,7 @@ on: env: # As defined by the Taskfile's PROJECT_NAME variable PROJECT_NAME: arduino-create-agent - TARGET: "/CreateAgent/Stable" + TARGET: "/CreateAgent/Stable/" OLD_TARGET: "/CreateBridge/" # compatibility with older releases (we can't change config.ini) VERSION_TARGET: "arduino-create-static/agent-metadata/" AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} @@ -26,6 +26,8 @@ env: jobs: # The build job is responsible for: configuring the environment, testing and compiling process build: + outputs: + prerelease: ${{ steps.prerelease.outputs.IS_PRE }} strategy: matrix: os: [ubuntu-20.04, windows-2019, macos-12] @@ -204,13 +206,13 @@ jobs: name: ArduinoCreateAgent.app path: ArduinoCreateAgent.app.tar - # The notarize-macos job will download the macos bundle from the previous job, sign, notarize and re-upload it. + # The notarize-macos job will download the macos bundle from the previous job, sign, notarize and re-upload it, uploading it also on s3 download servers for the autoupdate. notarize-macos: name: Notarize bundle runs-on: macos-12 env: GON_PATH: ${{ github.workspace }}/gon - needs: create-macos-bundle + needs: [build, create-macos-bundle] steps: - name: Download artifact @@ -276,6 +278,10 @@ jobs: - name: Sign and notarize binary run: gon -log-level=debug -log-json "${{ env.GON_CONFIG_PATH }}" + + - name: Upload autoupdate bundle to Arduino downloads servers + run: aws s3 cp ArduinoCreateAgent.app_notarized.zip s3://${{ secrets.DOWNLOADS_BUCKET }}${{ env.TARGET }}${GITHUB_REF/refs\/tags\//}/ # the version should be created in th the build job + if: ${{ needs.build.outputs.prerelease != 'true' }} - name: Upload artifact uses: actions/upload-artifact@v3 @@ -493,7 +499,7 @@ jobs: create-release: runs-on: ubuntu-20.04 - needs: code-sign-mac-installers + needs: [build, code-sign-mac-installers] steps: - name: Checkout @@ -504,14 +510,6 @@ jobs: - name: Download artifact uses: actions/download-artifact@v3 # download all the artifacts - - name: Identify Prerelease - # This is a workaround while waiting for create-release action to implement auto pre-release based on tag - id: prerelease - run: | - curl -L -s https://github.com/fsaintjacques/semver-tool/archive/3.1.0.zip -o /tmp/3.1.0.zip - unzip -p /tmp/3.1.0.zip semver-tool-3.1.0/src/semver >/tmp/semver && chmod +x /tmp/semver - if [[ $(/tmp/semver get prerel ${GITHUB_REF/refs\/tags\//}) ]]; then echo "IS_PRE=true" >> $GITHUB_OUTPUT; fi - # mandatory step because upload-release-action does not support multiple folders - name: prepare artifacts for the release run: | @@ -562,7 +560,7 @@ jobs: release_name: ${{ github.ref }} body: ${{ steps.release_body.outputs.RBODY}} draft: false - prerelease: ${{ steps.prerelease.outputs.IS_PRE }} + prerelease: ${{ needs.build.outputs.prerelease }} - name: Upload release files on Github uses: svenstaro/upload-release-action@v2 @@ -574,10 +572,10 @@ jobs: - name: Upload release files on Arduino downloads servers run: aws s3 sync release/ s3://${{ secrets.DOWNLOADS_BUCKET }}${{ env.TARGET }} - if: steps.prerelease.outputs.IS_PRE != 'true' + if: ${{ needs.build.outputs.prerelease != 'true' }} - name: Update version file (used by frontend to trigger autoupdate and create filename) run: | echo {\"Version\": \"${GITHUB_REF##*/}\"} > /tmp/agent-version.json aws s3 cp /tmp/agent-version.json s3://${{ env.VERSION_TARGET }} - if: steps.prerelease.outputs.IS_PRE != 'true' + if: ${{ needs.build.outputs.prerelease != 'true' }} From 4af62508c356dbb422b7ed3f9282168eaaebf91f Mon Sep 17 00:00:00 2001 From: Umberto Baldi <34278123+umbynos@users.noreply.github.com> Date: Thu, 9 Feb 2023 10:06:08 +0100 Subject: [PATCH 2/6] add json to enable autoupdate with the new agent logic (#759) * add json to enable autoupdate with the new agent logic * binary output of the archive https://unix.stackexchange.com/questions/3675/how-can-i-get-a-base64-encoded-shax-on-the-cli * workaround to allow darwin-arm64 to autoupdate * parallelize bundle creation and notarization. This will be helpful if/when we decide to build for darwin-arm64. For now this is useful because we do not offer a binary for m1 yet, `runtime.GOARCH` on an m1 machine returns ARM64, so the call for the update file would fail --- .github/workflows/release.yml | 57 ++++++++++++++++++++++++++++------- 1 file changed, 46 insertions(+), 11 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8ec4cf51b..33972933c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -123,6 +123,14 @@ jobs: run: go-selfupdate ${{ env.PROJECT_NAME }}${{ matrix.ext }} ${TAG_VERSION} if: matrix.arch != '-386' && steps.prerelease.outputs.IS_PRE != 'true' + # for now we do not distribute m1 build, this is a workaround for now + - name: Copy autoupdate file for darwin-arm64 (m1 arch) + working-directory: public/ + run: | + cp darwin-amd64.json darwin-arm64.json + cp ${TAG_VERSION}/darwin-amd64.gz ${TAG_VERSION}/darwin-arm64.gz + if: matrix.os == `macos-12` && steps.prerelease.outputs.IS_PRE != 'true' + - name: Create autoupdate files for win32 run: go-selfupdate -platform windows${{ matrix.arch }} ${{ env.PROJECT_NAME }}${{ matrix.ext }} ${TAG_VERSION} if: matrix.arch == '-386' && matrix.os == 'windows-2019' && steps.prerelease.outputs.IS_PRE != 'true' @@ -144,6 +152,11 @@ jobs: create-macos-bundle: needs: build + # for not they are exaclty the same + strategy: + matrix: + arch: [amd64, arm64] + runs-on: macos-12 env: EXE_PATH: "skel/ArduinoCreateAgent.app/Contents/MacOS/" @@ -158,7 +171,7 @@ jobs: - name: Download artifact uses: actions/download-artifact@v3 with: - name: ${{ env.PROJECT_NAME }}-macos-12-amd64 + name: ${{ env.PROJECT_NAME }}-macos-12-amd64 # if we want to support darwin-arm64 in the future for real this has to change. path: ${{ env.EXE_PATH }} - name: Remove placeholder file @@ -197,18 +210,24 @@ jobs: EOF - name: Tar bundle to keep permissions - run: tar -cvf ArduinoCreateAgent.app.tar -C skel/ . + run: tar -cvf ArduinoCreateAgent.app_${{ matrix.arch }}.tar -C skel/ . - name: Upload artifacts uses: actions/upload-artifact@v3 with: if-no-files-found: error - name: ArduinoCreateAgent.app - path: ArduinoCreateAgent.app.tar + name: ArduinoCreateAgent.app_${{ matrix.arch }} + path: ArduinoCreateAgent.app_${{ matrix.arch }}.tar # The notarize-macos job will download the macos bundle from the previous job, sign, notarize and re-upload it, uploading it also on s3 download servers for the autoupdate. notarize-macos: name: Notarize bundle + + # for not they are exaclty the same + strategy: + matrix: + arch: [amd64, arm64] + runs-on: macos-12 env: GON_PATH: ${{ github.workspace }}/gon @@ -218,10 +237,10 @@ jobs: - name: Download artifact uses: actions/download-artifact@v3 with: - name: ArduinoCreateAgent.app + name: ArduinoCreateAgent.app_${{ matrix.arch }} - name: un-Tar bundle - run: tar -xvf ArduinoCreateAgent.app.tar + run: tar -xvf ArduinoCreateAgent.app_${{ matrix.arch }}.tar - name: Import Code-Signing Certificates run: | @@ -272,7 +291,7 @@ jobs: # Ask Gon for zip output to force notarization process to take place. # The CI will upload the zip output zip { - output_path = "ArduinoCreateAgent.app_notarized.zip" + output_path = "ArduinoCreateAgent.app_${{ matrix.arch }}_notarized.zip" } EOF @@ -280,14 +299,29 @@ jobs: run: gon -log-level=debug -log-json "${{ env.GON_CONFIG_PATH }}" - name: Upload autoupdate bundle to Arduino downloads servers - run: aws s3 cp ArduinoCreateAgent.app_notarized.zip s3://${{ secrets.DOWNLOADS_BUCKET }}${{ env.TARGET }}${GITHUB_REF/refs\/tags\//}/ # the version should be created in th the build job + run: aws s3 cp ArduinoCreateAgent.app_${{ matrix.arch }}_notarized.zip s3://${{ secrets.DOWNLOADS_BUCKET }}${{ env.TARGET }}${GITHUB_REF/refs\/tags\//}/ # the version should be created in th the build job + if: ${{ needs.build.outputs.prerelease != 'true' }} + + - name: Generate json file used for the new autoupdate + run: | + cat > darwin-${{ matrix.arch }}-bundle.json < Date: Fri, 27 Jan 2023 18:10:21 +0100 Subject: [PATCH 3/6] Implemented the autoupdater for MacOS --- updater/updater_darwin.go | 97 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 96 insertions(+), 1 deletion(-) diff --git a/updater/updater_darwin.go b/updater/updater_darwin.go index 45c67e5c7..1e47d32f0 100644 --- a/updater/updater_darwin.go +++ b/updater/updater_darwin.go @@ -15,10 +15,105 @@ package updater +import ( + "bytes" + "context" + "crypto/sha256" + "fmt" + "io" + "os" + + "github.com/arduino/go-paths-helper" + "github.com/codeclysm/extract/v3" +) + func start(src string) string { return "" } func checkForUpdates(currentVersion string, updateAPIURL, updateBinURL string, cmdName string) (string, error) { - return "", nil + executablePath, err := os.Executable() + if err != nil { + return "", fmt.Errorf("could not app path: %w", err) + } + currentAppPath := paths.New(executablePath).Parent().Parent().Parent() + if currentAppPath.Ext() != ".app" { + return "", fmt.Errorf("could not find app root in %s", executablePath) + } + oldAppPath := currentAppPath.Parent().Join("ArdiunoCreateAgent.old.app") + if oldAppPath.Exist() { + return "", fmt.Errorf("temp app already exists: %s, cannot update", oldAppPath) + } + + // Fetch information about updates + info, err := fetchInfo(updateAPIURL, cmdName) + if err != nil { + return "", err + } + if info.Version == currentVersion { + // No updates available, bye bye + return "", nil + } + + tmp := paths.TempDir().Join("arduino-create-agent") + tmpZip := tmp.Join("update.zip") + tmpAppPath := tmp.Join("ArduinoCreateAgent-update.app") + defer tmp.RemoveAll() + + // Download the update. + download, err := fetch(updateBinURL + cmdName + "/" + plat + "/" + info.Version + "/" + cmdName) + if err != nil { + return "", err + } + defer download.Close() + + f, err := tmpZip.Create() + if err != nil { + return "", err + } + defer f.Close() + + sha := sha256.New() + if _, err := io.Copy(io.MultiWriter(sha, f), download); err != nil { + return "", err + } + f.Close() + + // Check the hash + if s := sha.Sum(nil); !bytes.Equal(s, info.Sha256) { + return "", fmt.Errorf("bad hash: %s (expected %s)", s, info.Sha256) + } + + // Unzip the update + if err := tmpAppPath.MkdirAll(); err != nil { + return "", fmt.Errorf("could not create tmp dir to unzip update: %w", err) + } + + f, err = tmpZip.Open() + if err != nil { + return "", fmt.Errorf("could not open archive for unzip: %w", err) + } + defer f.Close() + if err := extract.Archive(context.Background(), f, tmpAppPath.String(), nil); err != nil { + return "", fmt.Errorf("extracting archive: %w", err) + } + + // Rename current app as .old + if err := currentAppPath.Rename(oldAppPath); err != nil { + return "", fmt.Errorf("could not rename old app as .old: %w", err) + } + + // Install new app + if err := tmpAppPath.CopyDirTo(currentAppPath); err != nil { + // Try rollback changes + _ = currentAppPath.RemoveAll() + _ = oldAppPath.Rename(currentAppPath) + return "", fmt.Errorf("could not install app: %w", err) + } + + // Remove old app + _ = oldAppPath.RemoveAll() + + // Restart agent + return currentAppPath.Join("Contents", "MacOS", "Arduino_Create_Agent").String(), nil } From 3d60bedcdcea6fe476eed41b39346775d5531e42 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Tue, 31 Jan 2023 16:05:04 +0100 Subject: [PATCH 4/6] Added more logging and fixed upgrade procedure --- updater/updater_darwin.go | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/updater/updater_darwin.go b/updater/updater_darwin.go index 1e47d32f0..24d6676d1 100644 --- a/updater/updater_darwin.go +++ b/updater/updater_darwin.go @@ -25,6 +25,7 @@ import ( "github.com/arduino/go-paths-helper" "github.com/codeclysm/extract/v3" + "github.com/sirupsen/logrus" ) func start(src string) string { @@ -56,12 +57,17 @@ func checkForUpdates(currentVersion string, updateAPIURL, updateBinURL string, c } tmp := paths.TempDir().Join("arduino-create-agent") + if err := tmp.MkdirAll(); err != nil { + return "", err + } tmpZip := tmp.Join("update.zip") tmpAppPath := tmp.Join("ArduinoCreateAgent-update.app") defer tmp.RemoveAll() // Download the update. - download, err := fetch(updateBinURL + cmdName + "/" + plat + "/" + info.Version + "/" + cmdName) + downloadURL := updateBinURL + cmdName + "/" + info.Version + "/ArduinoCreateAgent.app_notarized.zip" + logrus.WithField("url", downloadURL).Info("Downloading update") + download, err := fetch(downloadURL) if err != nil { return "", err } @@ -85,6 +91,7 @@ func checkForUpdates(currentVersion string, updateAPIURL, updateBinURL string, c } // Unzip the update + logrus.WithField("tmpDir", tmpAppPath).Info("Unzipping update") if err := tmpAppPath.MkdirAll(); err != nil { return "", fmt.Errorf("could not create tmp dir to unzip update: %w", err) } @@ -99,11 +106,13 @@ func checkForUpdates(currentVersion string, updateAPIURL, updateBinURL string, c } // Rename current app as .old + logrus.WithField("from", currentAppPath).WithField("to", oldAppPath).Info("Renaming old app") if err := currentAppPath.Rename(oldAppPath); err != nil { return "", fmt.Errorf("could not rename old app as .old: %w", err) } // Install new app + logrus.WithField("from", tmpAppPath).WithField("to", currentAppPath).Info("Copying updated app") if err := tmpAppPath.CopyDirTo(currentAppPath); err != nil { // Try rollback changes _ = currentAppPath.RemoveAll() @@ -112,8 +121,11 @@ func checkForUpdates(currentVersion string, updateAPIURL, updateBinURL string, c } // Remove old app + logrus.WithField("to", oldAppPath).Info("Removing old app") _ = oldAppPath.RemoveAll() // Restart agent - return currentAppPath.Join("Contents", "MacOS", "Arduino_Create_Agent").String(), nil + newExecutable := currentAppPath.Join("Contents", "MacOS", "Arduino_Create_Agent") + logrus.WithField("path", newExecutable).Info("Running new app") + return newExecutable.String(), nil } From 803e4ca93f024fef611932142aa3f250d59a082c Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Thu, 2 Feb 2023 16:42:53 +0100 Subject: [PATCH 5/6] Use MacOS openApplicationAtURL syscall to re-run updated app --- update.go | 6 +++++- updater/updater_darwin.go | 31 ++++++++++++++++++++++++++++--- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/update.go b/update.go index 21569f36c..c50f53f38 100644 --- a/update.go +++ b/update.go @@ -41,5 +41,9 @@ func updateHandler(c *gin.Context) { return } c.JSON(200, gin.H{"success": "Please wait a moment while the agent reboots itself"}) - Systray.RestartWith(restartPath) + if restartPath == "quit" { + Systray.Quit() + } else { + Systray.RestartWith(restartPath) + } } diff --git a/updater/updater_darwin.go b/updater/updater_darwin.go index 24d6676d1..2610a62dd 100644 --- a/updater/updater_darwin.go +++ b/updater/updater_darwin.go @@ -15,6 +15,29 @@ package updater +/* +#cgo CFLAGS: -x objective-c +#cgo LDFLAGS: -framework Cocoa +#import + +void runApplication(const char *path) { + NSWorkspace *ws = [NSWorkspace sharedWorkspace]; + NSURL *url = [NSURL fileURLWithPath:@(path) isDirectory:NO]; + + NSWorkspaceOpenConfiguration* configuration = [NSWorkspaceOpenConfiguration new]; + //[configuration setEnvironment:env]; + [configuration setPromptsUserIfNeeded:YES]; + [configuration setCreatesNewApplicationInstance:YES]; + + dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); + [ws openApplicationAtURL:url configuration:configuration completionHandler:^(NSRunningApplication* app, NSError* error) { + dispatch_semaphore_signal(semaphore); + }]; + dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); +} +*/ +import "C" + import ( "bytes" "context" @@ -125,7 +148,9 @@ func checkForUpdates(currentVersion string, updateAPIURL, updateBinURL string, c _ = oldAppPath.RemoveAll() // Restart agent - newExecutable := currentAppPath.Join("Contents", "MacOS", "Arduino_Create_Agent") - logrus.WithField("path", newExecutable).Info("Running new app") - return newExecutable.String(), nil + logrus.WithField("path", currentAppPath).Info("Running new app") + C.runApplication(C.CString(currentAppPath.String())) + + // Close old agent + return "quit", nil } From d8e770c30596398ae4cd772d1367bf15961519d8 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Thu, 2 Feb 2023 18:04:24 +0100 Subject: [PATCH 6/6] Bump minimum required macosx version to 10.15 This is required because the auto-update uses the function warning: 'openApplicationAtURL:configuration:completionHandler:' is only available on macOS 10.15 or newer [-Wunguarded-availability-new] --- .github/workflows/publish-go-tester-task.yml | 6 +++--- .github/workflows/release.yml | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/publish-go-tester-task.yml b/.github/workflows/publish-go-tester-task.yml index 69efdba05..3b5f871d0 100644 --- a/.github/workflows/publish-go-tester-task.yml +++ b/.github/workflows/publish-go-tester-task.yml @@ -132,9 +132,9 @@ jobs: - name: Build the Agent for macos env: - MACOSX_DEPLOYMENT_TARGET: 10.11 # minimum supported version for mac - CGO_CFLAGS: -mmacosx-version-min=10.11 - CGO_LDFLAGS: -mmacosx-version-min=10.11 + MACOSX_DEPLOYMENT_TARGET: 10.15 # minimum supported version for mac + CGO_CFLAGS: -mmacosx-version-min=10.15 + CGO_LDFLAGS: -mmacosx-version-min=10.15 run: task go:build if: runner.os == 'macOS' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 33972933c..f9f8b6910 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -112,9 +112,9 @@ jobs: - name: Build the Agent for macos env: - MACOSX_DEPLOYMENT_TARGET: 10.11 # minimum supported version for mac - CGO_CFLAGS: -mmacosx-version-min=10.11 - CGO_LDFLAGS: -mmacosx-version-min=10.11 + MACOSX_DEPLOYMENT_TARGET: 10.15 # minimum supported version for mac + CGO_CFLAGS: -mmacosx-version-min=10.15 + CGO_LDFLAGS: -mmacosx-version-min=10.15 run: task go:build if: matrix.os == 'macos-12'