package selfupdate import ( "archive/tar" "archive/zip" "compress/gzip" "errors" "fmt" "io" "os" "path" "path/filepath" "strings" ) // extract unpacks archivePath into dstDir and returns the absolute path // to the eeco binary inside. The archives produced by scripts/build.sh // place a single directory `${GOOS}_${GOARCH}/` at the root containing // `eeco{.exe}` + README + LICENSE; extract resolves whichever entry // matches the binary name and ignores the others. func extract(archivePath, dstDir, goos string) (string, error) { binName := "eeco" if goos == "windows" { binName = "eeco.exe" } switch { case strings.HasSuffix(archivePath, ".tar.gz"): return extractTarGz(archivePath, dstDir, binName) case strings.HasSuffix(archivePath, ".zip"): return extractZip(archivePath, dstDir, binName) default: return "", fmt.Errorf("unknown archive format: %s", archivePath) } } func extractTarGz(archivePath, dstDir, binName string) (string, error) { f, err := os.Open(archivePath) if err != nil { return "", err } defer f.Close() gz, err := gzip.NewReader(f) if err != nil { return "", err } defer gz.Close() tr := tar.NewReader(gz) for { h, err := tr.Next() if errors.Is(err, io.EOF) { break } if err != nil { return "", err } if h.Typeflag != tar.TypeReg { continue } if path.Base(h.Name) != binName { continue } dst := filepath.Join(dstDir, binName) if err := writeBinary(dst, tr, modeFromHeader(h.Mode)); err != nil { return "", err } return dst, nil } return "", fmt.Errorf("binary %s not found in archive", binName) } func extractZip(archivePath, dstDir, binName string) (string, error) { zr, err := zip.OpenReader(archivePath) if err != nil { return "", err } defer zr.Close() for _, zf := range zr.File { if path.Base(zf.Name) != binName { continue } rc, err := zf.Open() if err != nil { return "", err } dst := filepath.Join(dstDir, binName) err = writeBinary(dst, rc, modeFromHeader(int64(zf.Mode()))) rc.Close() if err != nil { return "", err } return dst, nil } return "", fmt.Errorf("binary %s not found in archive", binName) } func writeBinary(dst string, src io.Reader, mode os.FileMode) error { if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil { return err } f, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, mode) if err != nil { return err } if _, err := io.Copy(f, src); err != nil { f.Close() return err } return f.Close() } func modeFromHeader(mode int64) os.FileMode { m := os.FileMode(mode & int64(os.ModePerm)) if m == 0 { m = 0o755 } return m | 0o100 } func copyFile(src, dst string) error { in, err := os.Open(src) if err != nil { return err } defer in.Close() info, err := in.Stat() if err != nil { return err } if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil { return err } out, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, info.Mode()) if err != nil { return err } if _, err := io.Copy(out, in); err != nil { out.Close() return err } return out.Close() }