From 23e3ec444c1c615b225528d71f366b7e0b402802 Mon Sep 17 00:00:00 2001 From: zerone0x <39543393+zerone0x@users.noreply.github.com> Date: Sat, 11 Jul 2026 20:08:50 +0000 Subject: [PATCH] fix(upgrade): run script installer pipeline via shell Fixes #281 Co-Authored-By: bridge --- cmd/gortex/upgrade.go | 15 +++++++++++++-- cmd/gortex/upgrade_test.go | 18 ++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/cmd/gortex/upgrade.go b/cmd/gortex/upgrade.go index 1cc135e1b..0e2e8d5e5 100644 --- a/cmd/gortex/upgrade.go +++ b/cmd/gortex/upgrade.go @@ -196,9 +196,8 @@ func runUpgrade(cmd *cobra.Command, args []string) error { if !upgradeRun { fmt.Fprintf(out, "Run:\n %s\n", command) } else { - parts := strings.Fields(command) fmt.Fprintf(out, "$ %s\n", command) - ex := exec.CommandContext(cmd.Context(), parts[0], parts[1:]...) //nolint:gosec // command is one of the fixed install-method templates + ex := upgradeExecCommand(cmd, command) ex.Stdout, ex.Stderr, ex.Stdin = out, cmd.ErrOrStderr(), os.Stdin if rerr := ex.Run(); rerr != nil { return fmt.Errorf("upgrade command failed: %w", rerr) @@ -212,6 +211,18 @@ func runUpgrade(cmd *cobra.Command, args []string) error { return nil } +func upgradeExecCommand(cmd *cobra.Command, command string) *exec.Cmd { + if upgradeCommandNeedsShell(command) { + return exec.CommandContext(cmd.Context(), "sh", "-c", command) //nolint:gosec // fixed installer template, needs shell for pipe + } + parts := strings.Fields(command) + return exec.CommandContext(cmd.Context(), parts[0], parts[1:]...) //nolint:gosec // command is one of the fixed install-method templates +} + +func upgradeCommandNeedsShell(command string) bool { + return strings.ContainsAny(command, "|&;<>()$`\\\"'") +} + // goBinDir returns the directory `go install` drops binaries into — $GOBIN, or // $GOPATH/bin, or ~/go/bin — for install-method detection. func goBinDir() string { diff --git a/cmd/gortex/upgrade_test.go b/cmd/gortex/upgrade_test.go index b03fd30f5..01dbf1b48 100644 --- a/cmd/gortex/upgrade_test.go +++ b/cmd/gortex/upgrade_test.go @@ -46,6 +46,24 @@ func TestUpgradeInstallMethodDetection(t *testing.T) { } } +func TestUpgradeRunCommandUsesShellOnlyForPipelines(t *testing.T) { + cmd := upgradeExecCommand(rootCmd, "curl -fsSL https://get.gortex.dev | sh") + if got := cmd.Path; got != "sh" { + t.Fatalf("script installer should run through sh, got path %q args %v", got, cmd.Args) + } + if len(cmd.Args) != 3 || cmd.Args[1] != "-c" || cmd.Args[2] != "curl -fsSL https://get.gortex.dev | sh" { + t.Fatalf("script installer command args = %#v", cmd.Args) + } + + cmd = upgradeExecCommand(rootCmd, "go install github.com/zzet/gortex/cmd/gortex@latest") + if got := cmd.Path; got != "go" { + t.Fatalf("plain argv command should exec directly, got path %q args %v", got, cmd.Args) + } + if len(cmd.Args) != 3 || cmd.Args[1] != "install" || cmd.Args[2] != "github.com/zzet/gortex/cmd/gortex@latest" { + t.Fatalf("go install command args = %#v", cmd.Args) + } +} + // TestUpgradeReleaseTagParse covers the /releases/latest redirect tag parse. func TestUpgradeReleaseTagParse(t *testing.T) { cases := map[string]string{