-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(upgrade): support upgrade ssx online
- Loading branch information
Showing
9 changed files
with
376 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
package cmd | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
"os" | ||
"path/filepath" | ||
"runtime" | ||
"strings" | ||
|
||
"github.com/pkg/errors" | ||
"github.com/spf13/cobra" | ||
"github.com/tidwall/gjson" | ||
"github.com/vimiix/ssx/internal/file" | ||
"github.com/vimiix/ssx/internal/lg" | ||
"github.com/vimiix/ssx/internal/utils" | ||
) | ||
|
||
const ( | ||
GITHUB_LATEST_API = "https://api.github.com/repos/vimiix/ssx/releases/latest" | ||
GITHUB_PKG_FMT = "https://github.com/vimiix/ssx/releases/download/v{VERSION}/ssx_v{VERSION}_{OS}_{ARCH}.tar.gz" | ||
) | ||
|
||
type upgradeOpt struct { | ||
PkgPath string | ||
Version string | ||
} | ||
|
||
func newUpgradeCmd() *cobra.Command { | ||
opt := &upgradeOpt{} | ||
cmd := &cobra.Command{ | ||
Use: "upgrade", | ||
Short: "upgrade ssx version", | ||
Example: `# Upgrade online | ||
ssx upgrade [<version>] | ||
# Upgrade with local filepath or specify new package URL path | ||
ssx upgrade -p <PATH> | ||
# If both version and package path are specified, | ||
# ssx prefer to use package path.`, | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
if len(args) > 0 { | ||
opt.Version = args[0] | ||
} | ||
return upgrade(cmd.Context(), opt) | ||
}} | ||
cmd.Flags().StringVarP(&opt.PkgPath, "package", "p", "", "new package file or URL path") | ||
return cmd | ||
} | ||
|
||
func unifyArch() (string, error) { | ||
switch runtime.GOARCH { | ||
case "amd64", "x86_64": | ||
return "x86_64", nil | ||
case "arm64", "aarch64": | ||
return "arm64", nil | ||
default: | ||
return "", errors.Errorf("not supported architecture: %s", runtime.GOARCH) | ||
} | ||
} | ||
|
||
func upgrade(ctx context.Context, opt *upgradeOpt) error { | ||
tempDir, err := os.MkdirTemp("", "*") | ||
if err != nil { | ||
return err | ||
} | ||
lg.Debug("make temp dir: %s", tempDir) | ||
defer os.RemoveAll(tempDir) | ||
var localPkg string | ||
if opt.PkgPath != "" { | ||
if strings.Contains(opt.PkgPath, "://") { | ||
localPkg = filepath.Join(tempDir, "ssx.tar.gz") | ||
lg.Info("downloading package from %s", opt.PkgPath) | ||
if err := utils.DownloadFile(ctx, opt.PkgPath, localPkg); err != nil { | ||
return err | ||
} | ||
} else { | ||
if !file.IsExist(opt.PkgPath) { | ||
return errors.Errorf("file not found: %s", opt.PkgPath) | ||
} | ||
localPkg = opt.PkgPath | ||
} | ||
} else if opt.Version != "" { | ||
semVer := strings.TrimPrefix(opt.Version, "v") | ||
if len(strings.Split(semVer, ".")) != 3 { | ||
return errors.Errorf("bad version: %s", opt.Version) | ||
} | ||
arch, err := unifyArch() | ||
if err != nil { | ||
return err | ||
} | ||
replacer := strings.NewReplacer("{VERSION}", semVer, "{OS}", runtime.GOOS, "{ARCH}", arch) | ||
urlStr := replacer.Replace(GITHUB_PKG_FMT) | ||
localPkg = filepath.Join(tempDir, "ssx.tar.gz") | ||
lg.Info("downloading package from %s", urlStr) | ||
if err := utils.DownloadFile(ctx, urlStr, localPkg); err != nil { | ||
return err | ||
} | ||
} else { | ||
lg.Info("detecting latest package url") | ||
urlStr, err := getLatestPkgURL() | ||
if err != nil { | ||
return err | ||
} | ||
if urlStr == "" { | ||
return errors.New("failed to get latest package url") | ||
} | ||
localPkg = filepath.Join(tempDir, "ssx.tar.gz") | ||
lg.Info("downloading latest package from %s", urlStr) | ||
if err := utils.DownloadFile(ctx, urlStr, localPkg); err != nil { | ||
return err | ||
} | ||
} | ||
lg.Info("extracting package") | ||
if err := utils.Untar(localPkg, tempDir); err != nil { | ||
return err | ||
} | ||
newBin := filepath.Join(tempDir, "ssx") | ||
if !file.IsExist(newBin) { | ||
return errors.New("not found ssx binary after extracting package") | ||
} | ||
execPath, err := os.Executable() | ||
if err != nil { | ||
return err | ||
} | ||
execAbsPath, err := filepath.Abs(execPath) | ||
if err != nil { | ||
return err | ||
} | ||
lg.Info("replacing old binary with new binary") | ||
if err := replaceBinary(newBin, execAbsPath); err != nil { | ||
return err | ||
} | ||
lg.Info("upgrade success") | ||
return nil | ||
} | ||
|
||
func getLatestPkgURL() (string, error) { | ||
arch, err := unifyArch() | ||
if err != nil { | ||
return "", err | ||
} | ||
r, err := http.Get(GITHUB_LATEST_API) | ||
if err != nil { | ||
return "", err | ||
} | ||
defer r.Body.Close() | ||
jsonBody, err := io.ReadAll(r.Body) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
res := gjson.Get(string(jsonBody), | ||
fmt.Sprintf(`assets.#(name%%"*%s_%s.tar.gz").browser_download_url`, runtime.GOOS, arch)) | ||
return res.String(), nil | ||
} | ||
|
||
func replaceBinary(newBin string, oldBin string) error { | ||
bakBin := oldBin + ".bak" | ||
lg.Debug("backup old binary from %s to %s", oldBin, bakBin) | ||
if err := os.Link(oldBin, bakBin); err != nil { | ||
return err | ||
} | ||
// if err := file.CopyFile(oldBin, bakName, 0700); err != nil { | ||
// return err | ||
// } | ||
|
||
lg.Debug("remove old binary") | ||
if err := os.RemoveAll(oldBin); err != nil { | ||
return err | ||
} | ||
|
||
lg.Debug("make the new binary effective") | ||
if err := file.CopyFile(newBin, oldBin, 0700); err != nil { | ||
_ = os.RemoveAll(oldBin) | ||
renameErr := os.Rename(bakBin, oldBin) | ||
if renameErr != nil { | ||
lg.Warn("restore old binary failed, please rename it manually\n"+ | ||
" mv %s %s", bakBin, oldBin) | ||
} | ||
return err | ||
} | ||
_ = os.RemoveAll(bakBin) | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,3 @@ | ||
// Copyright 2022 Enmotech Inc. All rights reserved. | ||
|
||
package encrypt | ||
|
||
import ( | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package file | ||
|
||
import ( | ||
"io" | ||
"os" | ||
) | ||
|
||
// CopyFile copies the contents of src to dst | ||
func CopyFile(src, dst string, perm os.FileMode) error { | ||
sf, err := os.Open(src) | ||
if err != nil { | ||
return err | ||
} | ||
defer sf.Close() | ||
tf, err := os.OpenFile(dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, perm) | ||
if err != nil { | ||
return err | ||
} | ||
defer tf.Close() | ||
_, err = io.Copy(tf, sf) | ||
return err | ||
} | ||
|
||
// IsExist check given path if exists | ||
func IsExist(path string) bool { | ||
if path == "" { | ||
return false | ||
} | ||
_, err := os.Stat(path) | ||
if err != nil { | ||
return os.IsExist(err) | ||
} | ||
return true | ||
} |
Oops, something went wrong.