Skip to content

Instantly share code, notes, and snippets.

@brianaqp
Created October 21, 2025 15:55
Show Gist options
  • Select an option

  • Save brianaqp/45fe885c6ec0f41ffb3c28fbcca92ee6 to your computer and use it in GitHub Desktop.

Select an option

Save brianaqp/45fe885c6ec0f41ffb3c28fbcca92ee6 to your computer and use it in GitHub Desktop.
Script to install .zip applications using Gtihub releases with semantic release
package main
import (
"context"
"io"
"log"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/google/go-github/v76/github"
"github.com/mholt/archives"
)
// Utility to check errors with an identifier
func checkError(identifier string, err error) {
if err != nil {
log.Panicf("Error. %s: %v\n", identifier, err)
}
}
// Compare semantic versions: returns 1 if v1 > v2, -1 if v1 < v2, 0 if equal
func compareVersions(v1, v2 string) int {
s1 := strings.Split(strings.TrimSpace(v1), ".")
s2 := strings.Split(strings.TrimSpace(v2), ".")
for i := 0; i < 3; i++ {
n1, _ := strconv.Atoi(s1[i])
n2, _ := strconv.Atoi(s2[i])
if n1 > n2 {
return 1
} else if n1 < n2 {
return -1
}
}
return 0
}
func main() {
workingDir, err := os.Getwd()
checkError("get-working-dir", err)
// --- Configuration
owner := "your-github-username"
repo := "your-repo-name"
appDir := "app"
versionFile := filepath.Join(appDir, "internal", "version.txt")
githubToken := os.Getenv("GITHUB_TOKEN")
client := github.NewClient(nil).WithAuthToken(githubToken)
// --- Fetch latest release
ctx := context.TODO()
release, githubResp, err := client.Repositories.GetLatestRelease(ctx, owner, repo)
checkError("get-latest-release", err)
log.Printf("GitHub request status: %d", githubResp.StatusCode)
// --- Check local version
doesAppExist := false
var localTag string
if _, err = os.Stat(appDir); err == nil {
data, err := os.ReadFile(versionFile)
checkError("read-version-file", err)
localTag = string(data)
doesAppExist = true
}
remoteTag := release.GetTagName()
if remoteTag == "" {
log.Panicln("Remote tag is empty. Aborting.")
}
if doesAppExist && compareVersions(remoteTag, localTag) == -1 {
log.Printf("No newer version found. Remote: %s, Local: %s", remoteTag, localTag)
return
}
// --- Download release zipball
zipURL := release.GetZipballURL()
if zipURL == "" {
log.Fatalln("Zipball URL is empty. Aborting.")
return
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, zipURL, nil)
checkError("create-request", err)
req.Header.Set("Authorization", "Bearer "+githubToken)
req.Header.Set("Accept", "application/zip")
resp, err := http.DefaultClient.Do(req)
checkError("download-zipball", err)
defer resp.Body.Close()
if resp.StatusCode == http.StatusFound || resp.StatusCode == http.StatusMovedPermanently {
loc := resp.Header.Get("Location")
if loc == "" {
log.Println("Redirect without location. Aborting.")
return
}
resp.Body.Close()
resp, err = http.Get(loc)
checkError("follow-redirect", err)
defer resp.Body.Close()
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
log.Fatalf("Download error, status: %d", resp.StatusCode)
}
log.Printf("Zip downloaded. Storing in memory.")
// --- Backup existing app directory
if doesAppExist {
log.Println("Renaming app/ to temp/")
os.Rename(appDir, "temp")
}
// --- Extract zipball
zipPath := "release.zip"
out, err := os.Create(zipPath)
checkError("save-release-zip", err)
_, err = io.Copy(out, resp.Body)
out.Close()
checkError("copy-zip-to-disk", err)
zipFile, err := os.Open(zipPath)
checkError("open-release-zip", err)
defer func() {
zipFile.Close()
os.Remove(zipPath)
}()
handleFile := func(ctx context.Context, file archives.FileInfo) error {
rc, err := file.Open()
if err != nil {
return err
}
defer rc.Close()
outPath := filepath.Join(appDir, file.Name())
if file.IsDir() {
return os.MkdirAll(outPath, 0755)
}
if err := os.MkdirAll(filepath.Dir(outPath), 0755); err != nil {
return err
}
outFile, err := os.Create(outPath)
if err != nil {
return err
}
defer outFile.Close()
_, err = io.Copy(outFile, rc)
return err
}
err = archives.Zip{}.Extract(context.Background(), zipFile, handleFile)
if err != nil {
log.Println("Extraction error, restoring backup.")
if _, err := os.Stat(appDir); err == nil {
os.RemoveAll(appDir)
}
os.Rename("temp", appDir)
}
if doesAppExist {
os.RemoveAll("temp")
}
log.Println("Update completed successfully.")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment