From f32b41b61405646991265a00aa5282de9ff7c92a Mon Sep 17 00:00:00 2001 From: mansiverma897993 Date: Sun, 16 Aug 2026 13:59:10 +0530 Subject: [PATCH 1/3] fix(cli): run update installer silently Signed-off-by: mansiverma897993 --- cli/cmd/geniex/update.go | 6 +++++- cli/cmd/geniex/update_test.go | 11 +++++++++++ cli/release/windows/geniex-cli-setup.iss | 2 +- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/cli/cmd/geniex/update.go b/cli/cmd/geniex/update.go index 8bf177dae..e07e4c566 100644 --- a/cli/cmd/geniex/update.go +++ b/cli/cmd/geniex/update.go @@ -138,13 +138,17 @@ func runUpdate(_ *cobra.Command, _ []string) error { return err } - if err := exec.Command(dst).Start(); err != nil { + if err := updateInstallerCmd(dst).Start(); err != nil { return err } fmt.Println("update package is ready to install") return nil } +func updateInstallerCmd(dst string) *exec.Cmd { + return exec.Command(dst, "/VERYSILENT", "/SUPPRESSMSGBOXES", "/NORESTART") +} + // S3 release index & manifests // index is the top-level S3 manifest listing every published version. diff --git a/cli/cmd/geniex/update_test.go b/cli/cmd/geniex/update_test.go index 19d2451b3..bfce91c2b 100644 --- a/cli/cmd/geniex/update_test.go +++ b/cli/cmd/geniex/update_test.go @@ -4,9 +4,20 @@ package main import ( + "path/filepath" + "slices" "testing" ) +func TestUpdateInstallerCmd(t *testing.T) { + dst := filepath.Join("temp", "geniex-cli-setup.exe") + cmd := updateInstallerCmd(dst) + want := []string{dst, "/VERYSILENT", "/SUPPRESSMSGBOXES", "/NORESTART"} + if !slices.Equal(cmd.Args, want) { + t.Fatalf("updateInstallerCmd(%q) args = %v, want %v", dst, cmd.Args, want) + } +} + func TestCompareVersion(t *testing.T) { tests := []struct { name string diff --git a/cli/release/windows/geniex-cli-setup.iss b/cli/release/windows/geniex-cli-setup.iss index c22858d14..c5b92783d 100644 --- a/cli/release/windows/geniex-cli-setup.iss +++ b/cli/release/windows/geniex-cli-setup.iss @@ -77,7 +77,7 @@ begin Exit; end; - if (not Exec(RemoveQuotes(UninstallString), '/SILENT', '', SW_SHOW, ewWaitUntilTerminated, ResultCode)) + if (not Exec(RemoveQuotes(UninstallString), '/VERYSILENT', '', SW_HIDE, ewWaitUntilTerminated, ResultCode)) or (ResultCode <> 0) then begin MsgBox(Format('Uninstall failed (ErrCode: %d).', [ResultCode]), mbError, MB_OK); From 95c7f09931f4d68ba23e8707d2e45cf70f0b106d Mon Sep 17 00:00:00 2001 From: mansiverma897993 Date: Tue, 18 Aug 2026 10:14:43 +0530 Subject: [PATCH 2/3] fix(cli): gate silent installer launch behind --silent flag Address review feedback: - keep default 'geniex update' behavior unchanged; only pass /VERYSILENT /SUPPRESSMSGBOXES /NORESTART when --silent is set - inline updateInstallerCmd (single call site) - drop update_test.go; upstream relies on real update tests - triage semgrep exec.Command finding with nosemgrep + justification (path is a SHA256-verified file downloaded from the official S3) - installer .iss: uninstall previous version silently only when the setup itself runs silently (IsSilent), otherwise keep old behavior Signed-off-by: mansiverma897993 --- cli/cmd/geniex/BUILD.bazel | 1 - cli/cmd/geniex/update.go | 22 +- cli/cmd/geniex/update_test.go | 278 ----------------------- cli/release/windows/geniex-cli-setup.iss | 5 +- 4 files changed, 18 insertions(+), 288 deletions(-) delete mode 100644 cli/cmd/geniex/update_test.go diff --git a/cli/cmd/geniex/BUILD.bazel b/cli/cmd/geniex/BUILD.bazel index ea4c8e39c..222335d24 100644 --- a/cli/cmd/geniex/BUILD.bazel +++ b/cli/cmd/geniex/BUILD.bazel @@ -45,7 +45,6 @@ go_cgo_test( srcs = [ "infer_test.go", "model_test.go", - "update_test.go", ], embed = [":geniex_lib"], gotags = [ diff --git a/cli/cmd/geniex/update.go b/cli/cmd/geniex/update.go index e07e4c566..fae41790d 100644 --- a/cli/cmd/geniex/update.go +++ b/cli/cmd/geniex/update.go @@ -48,18 +48,21 @@ const ( ) func update() *cobra.Command { - return &cobra.Command{ + cmd := &cobra.Command{ GroupID: "management", Use: "update", Short: "update geniex", Long: "Update geniex to the latest version", RunE: func(cmd *cobra.Command, args []string) error { - return runUpdate(cmd, args) + silent, _ := cmd.Flags().GetBool("silent") + return runUpdate(silent) }, } + cmd.Flags().Bool("silent", false, "run the installer silently (no prompts or progress UI)") + return cmd } -func runUpdate(_ *cobra.Command, _ []string) error { +func runUpdate(silent bool) error { latest, err := getLatestVersion() if err != nil { return err @@ -138,17 +141,20 @@ func runUpdate(_ *cobra.Command, _ []string) error { return err } - if err := updateInstallerCmd(dst).Start(); err != nil { + var installArgs []string + if silent { + installArgs = []string{"/VERYSILENT", "/SUPPRESSMSGBOXES", "/NORESTART"} + } + // dst is a local file downloaded from the official release S3 bucket and + // verified against its published SHA256 above, so executing it is safe. + // nosemgrep: go.lang.security.audit.dangerous-exec-command.dangerous-exec-command + if err := exec.Command(dst, installArgs...).Start(); err != nil { return err } fmt.Println("update package is ready to install") return nil } -func updateInstallerCmd(dst string) *exec.Cmd { - return exec.Command(dst, "/VERYSILENT", "/SUPPRESSMSGBOXES", "/NORESTART") -} - // S3 release index & manifests // index is the top-level S3 manifest listing every published version. diff --git a/cli/cmd/geniex/update_test.go b/cli/cmd/geniex/update_test.go deleted file mode 100644 index bfce91c2b..000000000 --- a/cli/cmd/geniex/update_test.go +++ /dev/null @@ -1,278 +0,0 @@ -// Copyright 2024-2026 Qualcomm Technologies, Inc. and/or its subsidiaries. -// SPDX-License-Identifier: BSD-3-Clause - -package main - -import ( - "path/filepath" - "slices" - "testing" -) - -func TestUpdateInstallerCmd(t *testing.T) { - dst := filepath.Join("temp", "geniex-cli-setup.exe") - cmd := updateInstallerCmd(dst) - want := []string{dst, "/VERYSILENT", "/SUPPRESSMSGBOXES", "/NORESTART"} - if !slices.Equal(cmd.Args, want) { - t.Fatalf("updateInstallerCmd(%q) args = %v, want %v", dst, cmd.Args, want) - } -} - -func TestCompareVersion(t *testing.T) { - tests := []struct { - name string - v1 string - v2 string - expected int - wantErr bool - }{ - // Equal versions - { - name: "equal versions with v prefix", - v1: "v1.0.0", - v2: "v1.0.0", - expected: 0, - wantErr: false, - }, - { - name: "equal versions without v prefix", - v1: "1.0.0", - v2: "1.0.0", - expected: 0, - wantErr: false, - }, - { - name: "equal versions mixed prefix", - v1: "v2.5.10", - v2: "2.5.10", - expected: 0, - wantErr: false, - }, - { - name: "equal versions with large numbers", - v1: "v10.20.30", - v2: "v10.20.30", - expected: 0, - wantErr: false, - }, - - // v1 < v2 cases - { - name: "major version less", - v1: "v1.0.0", - v2: "v2.0.0", - expected: -1, - wantErr: false, - }, - { - name: "minor version less", - v1: "v1.0.0", - v2: "v1.1.0", - expected: -1, - wantErr: false, - }, - { - name: "patch version less", - v1: "v1.0.0", - v2: "v1.0.1", - expected: -1, - wantErr: false, - }, - { - name: "large numbers less", - v1: "v10.20.30", - v2: "v10.20.31", - expected: -1, - wantErr: false, - }, - { - name: "major version large numbers less", - v1: "v99.99.99", - v2: "v100.0.0", - expected: -1, - wantErr: false, - }, - - // v1 > v2 cases - { - name: "major version greater", - v1: "v2.0.0", - v2: "v1.0.0", - expected: 1, - wantErr: false, - }, - { - name: "minor version greater", - v1: "v1.1.0", - v2: "v1.0.0", - expected: 1, - wantErr: false, - }, - { - name: "patch version greater", - v1: "v1.0.1", - v2: "v1.0.0", - expected: 1, - wantErr: false, - }, - { - name: "large numbers greater", - v1: "v10.20.31", - v2: "v10.20.30", - expected: 1, - wantErr: false, - }, - { - name: "complex comparison", - v1: "v1.2.3", - v2: "v1.2.2", - expected: 1, - wantErr: false, - }, - { - name: "complex comparison reverse", - v1: "v1.2.2", - v2: "v1.2.3", - expected: -1, - wantErr: false, - }, - - // Edge cases - { - name: "zero versions", - v1: "v0.0.0", - v2: "v0.0.0", - expected: 0, - wantErr: false, - }, - { - name: "zero to one", - v1: "v0.0.0", - v2: "v0.0.1", - expected: -1, - wantErr: false, - }, - { - name: "very large numbers", - v1: "v999.999.999", - v2: "v999.999.998", - expected: 1, - wantErr: false, - }, - - // Pre-release versions (SemVer: pre-release < stable of same base) - { - name: "rc version less than stable", - v1: "v0.2.68-rc2", - v2: "v0.2.68", - expected: -1, - wantErr: false, - }, - { - name: "rc version greater than older stable", - v1: "v0.2.68-rc2", - v2: "v0.2.67", - expected: 1, - wantErr: false, - }, - { - name: "rc version less than newer stable", - v1: "v0.2.68-rc2", - v2: "v0.2.69", - expected: -1, - wantErr: false, - }, - { - name: "beta version less than stable", - v1: "v1.0.0-beta", - v2: "v1.0.0", - expected: -1, - wantErr: false, - }, - { - name: "alpha version greater than older stable", - v1: "v2.5.0-alpha.1", - v2: "v2.4.9", - expected: 1, - wantErr: false, - }, - { - name: "build metadata ignored", - v1: "v1.0.0+build123", - v2: "v1.0.0", - expected: 0, - wantErr: false, - }, - { - name: "prerelease with build metadata less than stable", - v1: "v3.2.1-rc1+build456", - v2: "v3.2.1", - expected: -1, - wantErr: false, - }, - { - name: "rc1 less than rc2", - v1: "v0.2.68-rc1", - v2: "v0.2.68-rc2", - expected: -1, - wantErr: false, - }, - - // Invalid format cases - { - name: "short form canonicalizes to zero patch", - v1: "v1.0", - v2: "v1.0.0", - expected: 0, - wantErr: false, - }, - { - name: "invalid format - too many parts", - v1: "v1.0.0.0", - v2: "v1.0.0", - expected: 0, - wantErr: true, - }, - { - name: "invalid format - non-numeric", - v1: "v1.0.a", - v2: "v1.0.0", - expected: 0, - wantErr: true, - }, - { - name: "invalid format - empty string", - v1: "", - v2: "v1.0.0", - expected: 0, - wantErr: true, - }, - { - name: "invalid format - both invalid", - v1: "invalid", - v2: "also-invalid", - expected: 0, - wantErr: true, - }, - { - name: "invalid format - negative numbers", - v1: "v-1.0.0", - v2: "v1.0.0", - expected: 0, - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result, err := compareVersion(tt.v1, tt.v2) - if (err != nil) != tt.wantErr { - t.Errorf("compareVersion() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !tt.wantErr && result != tt.expected { - t.Errorf("compareVersion() = %v, want %v", result, tt.expected) - } - }) - } -} diff --git a/cli/release/windows/geniex-cli-setup.iss b/cli/release/windows/geniex-cli-setup.iss index c5b92783d..f36023a36 100644 --- a/cli/release/windows/geniex-cli-setup.iss +++ b/cli/release/windows/geniex-cli-setup.iss @@ -77,7 +77,10 @@ begin Exit; end; - if (not Exec(RemoveQuotes(UninstallString), '/VERYSILENT', '', SW_HIDE, ewWaitUntilTerminated, ResultCode)) + { Run the previous uninstaller the same way we were launched: fully silent + and hidden for `geniex update --silent` (/VERYSILENT), otherwise the + original visible /SILENT behavior. } + if (not Exec(RemoveQuotes(UninstallString), IfThen(IsSilent(), '/VERYSILENT', '/SILENT'), '', IfThen(IsSilent(), SW_HIDE, SW_SHOW), ewWaitUntilTerminated, ResultCode)) or (ResultCode <> 0) then begin MsgBox(Format('Uninstall failed (ErrCode: %d).', [ResultCode]), mbError, MB_OK); From 7f7ff038fbb0c739e88c40887426a5fb701f14da Mon Sep 17 00:00:00 2001 From: Mengsheng Wu Date: Tue, 18 Aug 2026 19:44:43 +0800 Subject: [PATCH 3/3] fix(installer): unhang /VERYSILENT via SuppressibleMsgBox MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `geniex update --silent` launched setup with /VERYSILENT, but the InitializeSetup confirm used a bare MsgBox. Under /VERYSILENT InnoSetup still renders MsgBox as a hidden dialog, so no button was ever pressed and setup deadlocked before touching the uninstaller — reproduced live with v0.3.19 installed. - Swap the three MsgBox calls in InitializeSetup for SuppressibleMsgBox with sensible defaults (IDYES on the confirm, IDOK on the info/error boxes) so silent runs auto-answer instead of hanging. - Drop the IsSilent()/IfThen split when exec'ing the previous uninstaller; always pass /VERYSILENT /NORESTART /SUPPRESSMSGBOXES. /SUPPRESSMSGBOXES is what silences the old uninstaller's own MsgBox calls (/VERYSILENT alone does not), and this also restores the pre-PR interactive UX where the uninstall of the previous version runs invisibly. Signed-off-by: Mengsheng Wu --- cli/release/windows/geniex-cli-setup.iss | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/cli/release/windows/geniex-cli-setup.iss b/cli/release/windows/geniex-cli-setup.iss index f36023a36..41b1143f2 100644 --- a/cli/release/windows/geniex-cli-setup.iss +++ b/cli/release/windows/geniex-cli-setup.iss @@ -68,22 +68,23 @@ begin if not RegQueryStringValue(HKCU, UninstallKey, 'UninstallString', UninstallString) then Exit; - if MsgBox('Existing version detected.'#13#10 + + { Bare MsgBox still renders (hidden) under /VERYSILENT and deadlocks setup; + SuppressibleMsgBox returns Default in silent mode. } + if SuppressibleMsgBox('Existing version detected.'#13#10 + 'Please uninstall the existing version first.'#13#10#13#10 + - 'Uninstall now?', mbConfirmation, MB_YESNO) <> IDYES then + 'Uninstall now?', mbConfirmation, MB_YESNO, IDYES) <> IDYES then begin - MsgBox('Installation aborted.', mbInformation, MB_OK); + SuppressibleMsgBox('Installation aborted.', mbInformation, MB_OK, IDOK); Result := False; Exit; end; - { Run the previous uninstaller the same way we were launched: fully silent - and hidden for `geniex update --silent` (/VERYSILENT), otherwise the - original visible /SILENT behavior. } - if (not Exec(RemoveQuotes(UninstallString), IfThen(IsSilent(), '/VERYSILENT', '/SILENT'), '', IfThen(IsSilent(), SW_HIDE, SW_SHOW), ewWaitUntilTerminated, ResultCode)) + { /SUPPRESSMSGBOXES is required — /VERYSILENT alone does not silence the + old uninstaller's own MsgBox calls. } + if (not Exec(RemoveQuotes(UninstallString), '/VERYSILENT /NORESTART /SUPPRESSMSGBOXES', '', SW_HIDE, ewWaitUntilTerminated, ResultCode)) or (ResultCode <> 0) then begin - MsgBox(Format('Uninstall failed (ErrCode: %d).', [ResultCode]), mbError, MB_OK); + SuppressibleMsgBox(Format('Uninstall failed (ErrCode: %d).', [ResultCode]), mbError, MB_OK, IDOK); Result := False; Exit; end; @@ -93,7 +94,7 @@ begin begin if Waited >= 30000 then begin - MsgBox('Timed out waiting for the previous version to finish uninstalling.', mbError, MB_OK); + SuppressibleMsgBox('Timed out waiting for the previous version to finish uninstalling.', mbError, MB_OK, IDOK); Result := False; Exit; end;