golang 어플리케이션 self update 적용하기(github latest version 기반)

메모 차원에서 작성합니다.

dalfox 1.1 버전대 업데이트에서 큰 부분 중 하나가 self-update입니다. 이전에 xspear, a2sv 등에서도 여러가지 방법으로 self-update를 지원했었는데 이번 dalfox는 바이너리로 컴파일되서 배포되기 때문에 고민이 좀 많았습니다. (이전까진 git command 기반으로 update 시켰거든요 😊)

그래서 git api로 release 버전 체크 후 바이너리를 교체할 방법으로 만들려고 하던 중 혹시나 해서 찾아봤더니 정말 좋은 모듈이 있어서 쉽게 해결했습니다.

Self update with go-github-selfupdate

go-get으로 모듈을 설치하신 후 ..

go get -u https://github.com/rhysd/go-github-selfupdate

아래 코드같은 형태로 self update가 가능합니다. 재미있는 점이라면, github의 주소를 기반으로 latest 버전을 체크하고, OS 타입에 맞는 바이너리를 받아 현재 실행중인 바이너리와 바꿔주는겁니다. (만들려고 했던 모든게 다있었네요)

import (
    "bufio"
    "github.com/blang/semver"
    "github.com/rhysd/go-github-selfupdate/selfupdate"
    "log"
    "os"
)

const version = "1.2.3"

func confirmAndSelfUpdate() {
    latest, found, err := selfupdate.DetectLatest("owner/repo")
    if err != nil {
        log.Println("Error occurred while detecting version:", err)
        return
    }

    v := semver.MustParse(version)
    if !found || latest.Version.LTE(v) {
        log.Println("Current version is the latest")
        return
    }

    fmt.Print("Do you want to update to", latest.Version, "? (y/n): ")
    input, err := bufio.NewReader(os.Stdin).ReadString('\n')
    if err != nil || (input != "y\n" && input != "n\n") {
        log.Println("Invalid input")
        return
    }
    if input == "n\n" {
        return
    }

    exe, err := os.Executable()
    if err != nil {
        log.Println("Could not locate executable path")
        return
    }
    if err := selfupdate.UpdateTo(latest.AssetURL, exe); err != nil {
        log.Println("Error occurred while updating binary:", err)
        return
    }
    log.Println("Successfully updated to version", latest.Version)
}

Using on DalFox

샘플코드 참조해서 아래와 같이 구성했습니다. 앱 내부에서 version 정보를 가지고 있는데, 이를 selfupdate의 DetectLatest 함수를 통해 얻은 마지막 버전과 비교 후 낮은 버전이라면 패치를 진행합니다.

package cmd

import (
 "bufio"
 "fmt"
 "github.com/blang/semver"
 "github.com/hahwul/dalfox/pkg/printing"
 "github.com/rhysd/go-github-selfupdate/selfupdate"
 "github.com/spf13/cobra"
 "os"
)

// updateCmd represents the update command
var updateCmd = &cobra.Command{
 Use:   "update",
 Short: "Update DalFox (Binary patch)",
 Run: func(cmd *cobra.Command, args []string) {
  confirmAndSelfUpdate()
 },
}

func init() {
 rootCmd.AddCommand(updateCmd)
}

func confirmAndSelfUpdate() {
 version := printing.VERSION[1:]
 latest, found, err := selfupdate.DetectLatest("hahwul/dalfox")
 if err != nil {
  printing.DalLog("ERROR", "Error occurred while detecting version", optionsStr)
  return
 }

 v := semver.MustParse(version)
 if !found || latest.Version.LTE(v) {
  printing.DalLog("SYSTEM", "Current version is the latest", optionsStr)
  return
 }

 fmt.Print("Do you want to update to", latest.Version, "? (y/n): ")
 input, err := bufio.NewReader(os.Stdin).ReadString('\n')
 if err != nil || (input != "y\n" && input != "n\n") {
  printing.DalLog("ERROR", "Invalid input", optionsStr)
  return
 }
 if input == "n\n" {
  return
 }

 exe, err := os.Executable()
 if err != nil {
  printing.DalLog("SYSTEM", "Could not locate executable path", optionsStr)
  return
 }
 if err := selfupdate.UpdateTo(latest.AssetURL, exe); err != nil {
  printing.DalLog("SYSTEM", "Error occurred while updating binary", optionsStr)
  return
 }
 printing.DalLog("SYSTEM", "Successfully updated to latest version", optionsStr)
}

덕분에 매우 쉽게 해결했습니다 :D