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