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 01/13] 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 02/13] 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 03/13] 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 04/13] 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 05/13] 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 06/13] 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' From 342e24368dd52858454a2685aad705e51fa820c6 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Tue, 14 Feb 2023 11:12:29 +0100 Subject: [PATCH 07/13] Removed diff-based updates --- go.mod | 2 +- update.go | 2 +- updater/updater.go | 4 +-- updater/updater_darwin.go | 6 ++-- updater/updater_default.go | 70 +++++++------------------------------- 5 files changed, 20 insertions(+), 64 deletions(-) diff --git a/go.mod b/go.mod index 8f84b2819..ed6eb8fee 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,6 @@ require ( github.com/gin-gonic/gin v1.8.1 github.com/go-ini/ini v1.62.0 github.com/googollee/go-socket.io v0.0.0-20181101151912-c8aeb1ed9b49 - github.com/kr/binarydist v0.1.0 github.com/mattn/go-shellwords v1.0.12 github.com/mitchellh/go-homedir v1.1.0 github.com/oleksandr/bonjour v0.0.0-20210301155756-30f43c61b915 @@ -56,6 +55,7 @@ require ( github.com/juju/errors v0.0.0-20200330140219-3fe23663418f // indirect github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect github.com/klauspost/compress v1.15.13 // indirect + github.com/kr/binarydist v0.1.0 // indirect github.com/kr/fs v0.1.0 // indirect github.com/leodido/go-urn v1.2.1 // indirect github.com/manveru/faker v0.0.0-20171103152722-9fbc68a78c4d // indirect diff --git a/update.go b/update.go index c50f53f38..33c028bce 100644 --- a/update.go +++ b/update.go @@ -35,7 +35,7 @@ import ( ) func updateHandler(c *gin.Context) { - restartPath, err := updater.CheckForUpdates(version, *updateURL, *updateURL, *appName) + restartPath, err := updater.CheckForUpdates(version, *updateURL, *appName) if err != nil { c.JSON(500, gin.H{"error": err.Error()}) return diff --git a/updater/updater.go b/updater/updater.go index b15afe3f8..47122767b 100644 --- a/updater/updater.go +++ b/updater/updater.go @@ -36,8 +36,8 @@ func Start(src string) string { // CheckForUpdates checks if there is a new version of the binary available and // if so downloads it. -func CheckForUpdates(currentVersion string, updateAPIURL, updateBinURL string, cmdName string) (string, error) { - return checkForUpdates(currentVersion, updateAPIURL, updateBinURL, cmdName) +func CheckForUpdates(currentVersion string, updateURL string, cmdName string) (string, error) { + return checkForUpdates(currentVersion, updateURL, cmdName) } const ( diff --git a/updater/updater_darwin.go b/updater/updater_darwin.go index 2610a62dd..63c3d9cd5 100644 --- a/updater/updater_darwin.go +++ b/updater/updater_darwin.go @@ -55,7 +55,7 @@ func start(src string) string { return "" } -func checkForUpdates(currentVersion string, updateAPIURL, updateBinURL string, cmdName string) (string, error) { +func checkForUpdates(currentVersion string, updateURL string, cmdName string) (string, error) { executablePath, err := os.Executable() if err != nil { return "", fmt.Errorf("could not app path: %w", err) @@ -70,7 +70,7 @@ func checkForUpdates(currentVersion string, updateAPIURL, updateBinURL string, c } // Fetch information about updates - info, err := fetchInfo(updateAPIURL, cmdName) + info, err := fetchInfo(updateURL, cmdName) if err != nil { return "", err } @@ -88,7 +88,7 @@ func checkForUpdates(currentVersion string, updateAPIURL, updateBinURL string, c defer tmp.RemoveAll() // Download the update. - downloadURL := updateBinURL + cmdName + "/" + info.Version + "/ArduinoCreateAgent.app_notarized.zip" + downloadURL := updateURL + cmdName + "/" + info.Version + "/ArduinoCreateAgent.app_notarized.zip" logrus.WithField("url", downloadURL).Info("Downloading update") download, err := fetch(downloadURL) if err != nil { diff --git a/updater/updater_default.go b/updater/updater_default.go index 4562907fc..61603ab0c 100644 --- a/updater/updater_default.go +++ b/updater/updater_default.go @@ -28,7 +28,6 @@ import ( "path/filepath" "strings" - "github.com/kr/binarydist" log "github.com/sirupsen/logrus" "gopkg.in/inconshreveable/go-update.v0" ) @@ -60,7 +59,6 @@ import ( // var errHashMismatch = errors.New("new file hash mismatch after patch") -var errDiffURLUndefined = errors.New("DiffURL is not defined, I cannot fetch and apply patch, reverting to full bin") var up = update.New() func start(src string) string { @@ -81,16 +79,14 @@ func start(src string) string { return "" } -func checkForUpdates(currentVersion string, updateAPIURL, updateBinURL string, cmdName string) (string, error) { +func checkForUpdates(currentVersion string, updateURL string, cmdName string) (string, error) { path, err := os.Executable() if err != nil { return "", err } var up = &Updater{ CurrentVersion: currentVersion, - APIURL: updateAPIURL, - BinURL: updateBinURL, - DiffURL: "", + UpdateURL: updateURL, Dir: "update/", CmdName: cmdName, } @@ -139,9 +135,7 @@ func removeTempSuffixFromPath(path string) string { // // updater := &selfupdate.Updater{ // CurrentVersion: version, -// ApiURL: "http://updates.yourdomain.com/", -// BinURL: "http://updates.yourdownmain.com/", -// DiffURL: "http://updates.yourdomain.com/", +// UpdateURL: "http://updates.yourdomain.com/", // Dir: "update/", // CmdName: "myapp", // app name // } @@ -150,10 +144,8 @@ func removeTempSuffixFromPath(path string) string { // } type Updater struct { CurrentVersion string // Currently running version. - APIURL string // Base URL for API requests (json files). + UpdateURL string // Base URL for API requests (json files). CmdName string // Command name is appended to the ApiURL like http://apiurl/CmdName/. This represents one binary. - BinURL string // Base URL for full binary downloads. - DiffURL string // Base URL for diff downloads. Dir string // Directory to store selfupdate state. Info *availableUpdateInfo // Information about the available update. } @@ -183,31 +175,6 @@ func verifySha(bin []byte, sha []byte) bool { return bytes.Equal(h.Sum(nil), sha) } -func (u *Updater) fetchAndApplyPatch(old io.Reader) ([]byte, error) { - if u.DiffURL == "" { - return nil, errDiffURLUndefined - } - r, err := fetch(u.DiffURL + u.CmdName + "/" + u.CurrentVersion + "/" + u.Info.Version + "/" + plat) - if err != nil { - return nil, err - } - defer r.Close() - var buf bytes.Buffer - err = binarydist.Patch(old, &buf, r) - return buf.Bytes(), err -} - -func (u *Updater) fetchAndVerifyPatch(old io.Reader) ([]byte, error) { - bin, err := u.fetchAndApplyPatch(old) - if err != nil { - return nil, err - } - if !verifySha(bin, u.Info.Sha256) { - return nil, errHashMismatch - } - return bin, nil -} - func (u *Updater) fetchAndVerifyFullBin() ([]byte, error) { bin, err := u.fetchBin() if err != nil { @@ -221,7 +188,7 @@ func (u *Updater) fetchAndVerifyFullBin() ([]byte, error) { } func (u *Updater) fetchBin() ([]byte, error) { - r, err := fetch(u.BinURL + u.CmdName + "/" + u.Info.Version + "/" + plat + ".gz") + r, err := fetch(u.UpdateURL + u.CmdName + "/" + u.Info.Version + "/" + plat + ".gz") if err != nil { return nil, err } @@ -258,7 +225,7 @@ func (u *Updater) update() error { } defer old.Close() - info, err := fetchInfo(u.APIURL, u.CmdName) + info, err := fetchInfo(u.UpdateURL, u.CmdName) if err != nil { log.Println(err) return err @@ -267,26 +234,15 @@ func (u *Updater) update() error { if u.Info.Version == u.CurrentVersion { return nil } - bin, err := u.fetchAndVerifyPatch(old) - if err != nil { - switch err { - case errHashMismatch: - log.Println("update: hash mismatch from patched binary") - case errDiffURLUndefined: - log.Println("update: ", err) - default: - log.Println("update: patching binary, ", err) - } - bin, err = u.fetchAndVerifyFullBin() - if err != nil { - if err == errHashMismatch { - log.Println("update: hash mismatch from full binary") - } else { - log.Println("update: fetching full binary,", err) - } - return err + bin, err := u.fetchAndVerifyFullBin() + if err != nil { + if err == errHashMismatch { + log.Println("update: hash mismatch from full binary") + } else { + log.Println("update: fetching full binary,", err) } + return err } // close the old binary before installing because on windows From a484330b9c58bcee2748ff3b92b4549b8f7139f8 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Tue, 14 Feb 2023 11:22:37 +0100 Subject: [PATCH 08/13] Fixed update URL for different update methods --- updater/updater.go | 4 ++-- updater/updater_darwin.go | 17 +++++++++++++++-- updater/updater_default.go | 3 ++- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/updater/updater.go b/updater/updater.go index 47122767b..5277516b8 100644 --- a/updater/updater.go +++ b/updater/updater.go @@ -44,8 +44,8 @@ const ( plat = runtime.GOOS + "-" + runtime.GOARCH ) -func fetchInfo(updateAPIURL string, cmdName string) (*availableUpdateInfo, error) { - r, err := fetch(updateAPIURL + cmdName + "/" + plat + ".json") +func fetchInfo(updateAPIURL string) (*availableUpdateInfo, error) { + r, err := fetch(updateAPIURL) if err != nil { return nil, err } diff --git a/updater/updater_darwin.go b/updater/updater_darwin.go index 63c3d9cd5..090683db5 100644 --- a/updater/updater_darwin.go +++ b/updater/updater_darwin.go @@ -45,6 +45,7 @@ import ( "fmt" "io" "os" + "runtime" "github.com/arduino/go-paths-helper" "github.com/codeclysm/extract/v3" @@ -70,7 +71,13 @@ func checkForUpdates(currentVersion string, updateURL string, cmdName string) (s } // Fetch information about updates - info, err := fetchInfo(updateURL, cmdName) + + // updateURL: "https://downloads.arduino.cc/" + // cmdName: "CreateAgent/Stable" + // plat: "darwin-amd64" + // info URL: "https://downloads.arduino.cc/CreateAgent/Stable/darwin-amd64-bundle.json" + infoURL := updateURL + cmdName + "/" + plat + "-bundle.json" + info, err := fetchInfo(infoURL) if err != nil { return "", err } @@ -88,7 +95,13 @@ func checkForUpdates(currentVersion string, updateURL string, cmdName string) (s defer tmp.RemoveAll() // Download the update. - downloadURL := updateURL + cmdName + "/" + info.Version + "/ArduinoCreateAgent.app_notarized.zip" + + // updateURL: "https://downloads.arduino.cc/" + // cmdName == "CreateAgent/Stable" + // info.Version == "1.2.8" + // runtime.GOARCH: "amd64" + // downloadURL: "https://downloads.arduino.cc/CreateAgent/Stable/1.2.8/ArduinoCreateAgent.app_arm64_notarized.zip" + downloadURL := updateURL + cmdName + "/" + info.Version + "/ArduinoCreateAgent.app_" + runtime.GOARCH + "_notarized.zip" logrus.WithField("url", downloadURL).Info("Downloading update") download, err := fetch(downloadURL) if err != nil { diff --git a/updater/updater_default.go b/updater/updater_default.go index 61603ab0c..b78e28f77 100644 --- a/updater/updater_default.go +++ b/updater/updater_default.go @@ -225,7 +225,8 @@ func (u *Updater) update() error { } defer old.Close() - info, err := fetchInfo(u.UpdateURL, u.CmdName) + infoURL := u.UpdateURL + u.CmdName + "/" + plat + ".json" + info, err := fetchInfo(infoURL) if err != nil { log.Println(err) return err From 599a22140da62485814935541ccdaa5d40fe8d87 Mon Sep 17 00:00:00 2001 From: Umberto Baldi Date: Tue, 14 Feb 2023 11:52:54 +0100 Subject: [PATCH 09/13] fix invalid workflow --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f9f8b6910..9ac64657f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -126,10 +126,10 @@ jobs: # 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: | + 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' + 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} From 1c0d4138fd9c8c2540e0f295491ee3b525252083 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Tue, 14 Feb 2023 15:24:44 +0100 Subject: [PATCH 10/13] Always complete the old auto-upgrade procedure This is required for clients upgrading from versions <=1.2.7 --- updater/updater.go | 33 +++++++++++++++++++++++++++++++++ updater/updater_darwin.go | 17 +++++++++++++++++ updater/updater_default.go | 30 ------------------------------ 3 files changed, 50 insertions(+), 30 deletions(-) diff --git a/updater/updater.go b/updater/updater.go index 5277516b8..db4e5454f 100644 --- a/updater/updater.go +++ b/updater/updater.go @@ -22,7 +22,10 @@ import ( "fmt" "io" "net/http" + "os" + "path/filepath" "runtime" + "strings" log "github.com/sirupsen/logrus" ) @@ -77,3 +80,33 @@ func fetch(url string) (io.ReadCloser, error) { } return resp.Body, nil } + +// addTempSuffixToPath adds the "-temp" suffix to the path to an executable file (a ".exe" extension is replaced with "-temp.exe") +func addTempSuffixToPath(path string) string { + if filepath.Ext(path) == "exe" { + path = strings.Replace(path, ".exe", "-temp.exe", -1) + } else { + path = path + "-temp" + } + + return path +} + +// removeTempSuffixFromPath removes "-temp" suffix from the path to an executable file (a "-temp.exe" extension is replaced with ".exe") +func removeTempSuffixFromPath(path string) string { + return strings.Replace(path, "-temp", "", -1) +} + +func copyExe(from, to string) error { + data, err := os.ReadFile(from) + if err != nil { + log.Println("Cannot read file: ", from) + return err + } + err = os.WriteFile(to, data, 0755) + if err != nil { + log.Println("Cannot write file: ", to) + return err + } + return nil +} diff --git a/updater/updater_darwin.go b/updater/updater_darwin.go index 090683db5..6b14b1046 100644 --- a/updater/updater_darwin.go +++ b/updater/updater_darwin.go @@ -46,13 +46,30 @@ import ( "io" "os" "runtime" + "strings" "github.com/arduino/go-paths-helper" "github.com/codeclysm/extract/v3" "github.com/sirupsen/logrus" + log "github.com/sirupsen/logrus" ) func start(src string) string { + if strings.Contains(src, "-temp") { + // This is required to transition from the previous auto-update system to the new one. + // Updating from version <=1.2.7 will produce the `ArduinoCreateAgent-temp` file and we should + // complete the upgrade by copying the `ArduinoCreateAgent-temp` executable back to the original. + // + // This procedure will be automatically skipped starting from version >1.2.7. + + newPath := removeTempSuffixFromPath(src) + if err := copyExe(src, newPath); err != nil { + log.Println("Copy error: ", err) + panic(err) + } + return newPath + } + return "" } diff --git a/updater/updater_default.go b/updater/updater_default.go index b78e28f77..2cf01b8ad 100644 --- a/updater/updater_default.go +++ b/updater/updater_default.go @@ -97,36 +97,6 @@ func checkForUpdates(currentVersion string, updateURL string, cmdName string) (s return addTempSuffixToPath(path), nil } -func copyExe(from, to string) error { - data, err := os.ReadFile(from) - if err != nil { - log.Println("Cannot read file: ", from) - return err - } - err = os.WriteFile(to, data, 0755) - if err != nil { - log.Println("Cannot write file: ", to) - return err - } - return nil -} - -// addTempSuffixToPath adds the "-temp" suffix to the path to an executable file (a ".exe" extension is replaced with "-temp.exe") -func addTempSuffixToPath(path string) string { - if filepath.Ext(path) == "exe" { - path = strings.Replace(path, ".exe", "-temp.exe", -1) - } else { - path = path + "-temp" - } - - return path -} - -// removeTempSuffixFromPath removes "-temp" suffix from the path to an executable file (a "-temp.exe" extension is replaced with ".exe") -func removeTempSuffixFromPath(path string) string { - return strings.Replace(path, "-temp", "", -1) -} - // Updater is the configuration and runtime data for doing an update. // // Note that ApiURL, BinURL and DiffURL should have the same value if all files are available at the same location. From 097bc2bf8d5d34d5a3e0bab26fed3fd34b45b6df Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Tue, 14 Feb 2023 18:19:46 +0100 Subject: [PATCH 11/13] Moved the syscall to openApplicationAtURL inside Systray --- systray/exec_darwin.go | 60 +++++++++++++++++++++++++++++++++++++++ systray/exec_default.go | 26 +++++++++++++++++ systray/systray.go | 4 +-- updater/updater_darwin.go | 26 +---------------- 4 files changed, 88 insertions(+), 28 deletions(-) create mode 100644 systray/exec_darwin.go create mode 100644 systray/exec_default.go diff --git a/systray/exec_darwin.go b/systray/exec_darwin.go new file mode 100644 index 000000000..c68da72b1 --- /dev/null +++ b/systray/exec_darwin.go @@ -0,0 +1,60 @@ +package systray + +/* +#cgo CFLAGS: -x objective-c +#cgo LDFLAGS: -framework Cocoa +#import + +char **makeCharArray(int size) { + return calloc(sizeof(char*), size); +} + +void setCharArray(char **a, int n, char *s) { + a[n] = s; +} + +void freeCharArray(char **a, int size) { + int i; + for (i = 0; i < size; i++) { + free(a[i]); + } + free(a); +} + +void runApplication(const char *path, const char **argv, int argc) { + NSMutableArray *stringArray = [NSMutableArray array]; + for (int i=0; i *arguments = [NSArray arrayWithArray:stringArray]; + + NSWorkspace *ws = [NSWorkspace sharedWorkspace]; + NSURL *url = [NSURL fileURLWithPath:@(path) isDirectory:NO]; + + NSWorkspaceOpenConfiguration* configuration = [NSWorkspaceOpenConfiguration new]; + //[configuration setEnvironment:env]; + [configuration setPromptsUserIfNeeded:YES]; + [configuration setCreatesNewApplicationInstance:YES]; + [configuration setArguments:arguments]; + 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" + +func execApp(path string, args ...string) error { + argc := C.int(len(args)) + argv := C.makeCharArray(argc) + for i, arg := range args { + C.setCharArray(argv, C.int(i), C.CString(arg)) + } + + C.runApplication(C.CString(path), argv, argc) + + C.freeCharArray(argv, argc) + return nil +} diff --git a/systray/exec_default.go b/systray/exec_default.go new file mode 100644 index 000000000..ea28618b5 --- /dev/null +++ b/systray/exec_default.go @@ -0,0 +1,26 @@ +// Copyright 2022 Arduino SA +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +//go:build !darwin + +package systray + +import "os/exec" + +// default execApp from golang +func execApp(path string, args ...string) error { + cmd := exec.Command(path, args...) + return cmd.Start() +} diff --git a/systray/systray.go b/systray/systray.go index 85656bbc9..4df4fadbc 100644 --- a/systray/systray.go +++ b/systray/systray.go @@ -18,7 +18,6 @@ package systray import ( "fmt" "os" - "os/exec" "strings" "github.com/arduino/go-paths-helper" @@ -69,8 +68,7 @@ func (s *Systray) Restart() { } // Launch executable - cmd := exec.Command(s.path, args...) - err := cmd.Start() + err := execApp(s.path, args...) if err != nil { log.Printf("Error restarting process: %v\n", err) return diff --git a/updater/updater_darwin.go b/updater/updater_darwin.go index 6b14b1046..f0bc6e5ed 100644 --- a/updater/updater_darwin.go +++ b/updater/updater_darwin.go @@ -15,29 +15,6 @@ 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" @@ -179,8 +156,7 @@ func checkForUpdates(currentVersion string, updateURL string, cmdName string) (s // Restart agent logrus.WithField("path", currentAppPath).Info("Running new app") - C.runApplication(C.CString(currentAppPath.String())) // Close old agent - return "quit", nil + return currentAppPath.String(), nil } From 18a25e95b7c7926222ea164c3ab15f95f92b5f0b Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Wed, 15 Feb 2023 11:36:31 +0100 Subject: [PATCH 12/13] Added fallback restart for macosx. --- systray/exec_darwin.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/systray/exec_darwin.go b/systray/exec_darwin.go index c68da72b1..e66aeddc9 100644 --- a/systray/exec_darwin.go +++ b/systray/exec_darwin.go @@ -45,8 +45,18 @@ void runApplication(const char *path, const char **argv, int argc) { } */ import "C" +import ( + "os/exec" + "path/filepath" +) func execApp(path string, args ...string) error { + if filepath.Ext(path) != ".app" { + // If not .app, fallback to standard process execution + cmd := exec.Command(path, args...) + return cmd.Start() + } + argc := C.int(len(args)) argv := C.makeCharArray(argc) for i, arg := range args { From 06e01fe1837dbcf1b633db18de2ea3c579cba8f6 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Wed, 15 Feb 2023 12:57:17 +0100 Subject: [PATCH 13/13] added some more logging --- systray/exec_darwin.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/systray/exec_darwin.go b/systray/exec_darwin.go index e66aeddc9..ba66e0c39 100644 --- a/systray/exec_darwin.go +++ b/systray/exec_darwin.go @@ -48,15 +48,19 @@ import "C" import ( "os/exec" "path/filepath" + + "github.com/sirupsen/logrus" ) func execApp(path string, args ...string) error { if filepath.Ext(path) != ".app" { // If not .app, fallback to standard process execution + logrus.WithField("path", path).WithField("args", args).Info("Running new app with os/exec.Exec") cmd := exec.Command(path, args...) return cmd.Start() } + logrus.WithField("path", path).WithField("args", args).Info("Running new app with openApplicationAtURL") argc := C.int(len(args)) argv := C.makeCharArray(argc) for i, arg := range args {