Veltioblog

Go言語バイナリをセルフアップデートする

Go の CLIツールのセルフアップデート機能の実装方法を解説した記事です。[minio/selfupdate](https://github.com/minio/selfupdate)ライブラリを使用し、バイナリのダウンロードとアーカイブからの読み取りを通じてセルフアップデートを実現します。


最近開発している vss という CLI の静的サイトジェネレーターを開発していて、セルフアップデートの仕組みを追加したので実装方法を残しておきます。

リポジトリはこちら -> https://github.com/vssio/go-vss

Go のバイナリをセルフアップデートするためのライブラリ

セルフアップデートを実現するために、今回は minio/selfupdate を利用しました。

公式の Example code を見ると非常に簡単にセルフアップデートの仕組みを導入することができるとわかります。

import (
    "fmt"
    "net/http"

    "github.com/minio/selfupdate"
)

func doUpdate(url string) error {
    resp, err := http.Get(url)
    if err != nil {
        return err
    }
    defer resp.Body.Close()
    err = selfupdate.Apply(resp.Body, selfupdate.Options{})
    if err != nil {
        // error handling
    }
    return err
}

引用: https://github.com/minio/selfupdate/blob/master/README.md

セルフアップデート部分はライブラリが実装してくれているので、自分で実装するのは以下の 2 点です。

バイナルをセルフアップデートする仕組み

簡単に仕組みを説明すると、以下のようになります。

※ Linux を例にしますので、ダウンロードファイルは tar.gz を前提に説明していきます。Windows & Mac は zip ですが、基本的な流れは同じです。

  1. GitHub Releases からバイナリをダウンロードする(デフォルト latest)。
  2. アーカイブファイルを 1 ファイルずつ読み取る。
  3. vss というファイル名のファイルを探す(アーカイブファイルには vss 以外にも README などのファイルが含まれているため)。
  4. vss が見つかったら、そのファイルを読み取る io.Reader を selfupdate.Apply に渡す。
  5. err が nil であれば、アップデート成功。

Go でセルフアップデートするコード

上記の流れを実装すると以下のようになります。

func downloadAndExtract(url string) error {
	// Get the data
	resp, err := http.Get(url)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	// Create a new gzip reader
	gr, err := gzip.NewReader(resp.Body)
	if err != nil {
		return err
	}
	defer gr.Close()

	// Create a tar reader
	tr := tar.NewReader(gr)

	// Iterate through the files in the archive
	for {
		header, err := tr.Next()
		if err == io.EOF {
			break // End of archive
		}
		if err != nil {
			return err
		}

		// Open the output file
		if header.FileInfo().IsDir() {
			continue
		}
		filename := filepath.Join(strings.Split(header.Name, "/")[1:]...)
		if filename == "vss" {
            err = selfupdate.Apply(tr, selfupdate.Options{})
            if err != nil {
                return err
            }
            break // end of update binary
		} else {
			continue
		}
	}

	return nil
}

この関数にダウンロード対象の URL を渡してやれば、セルフアップデートが実行されます 🦭

まとめ