parent
fe91dc2dd1
commit
11220cc573
|
@ -0,0 +1 @@
|
|||
/eget*
|
|
@ -0,0 +1,122 @@
|
|||
package disk
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"go.jolheiser.com/eget/forge"
|
||||
|
||||
"github.com/adrg/xdg"
|
||||
"github.com/mholt/archiver/v3"
|
||||
)
|
||||
|
||||
var (
|
||||
amd64Re = regexp.MustCompile(`amd64|x86_64|64`)
|
||||
linuxRe = regexp.MustCompile(`linux|linux64`)
|
||||
windowsRe = regexp.MustCompile(`windows|win64`)
|
||||
)
|
||||
|
||||
func Install(asset forge.Asset) error {
|
||||
tmp, err := os.MkdirTemp(os.TempDir(), "eget-*")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(tmp)
|
||||
|
||||
resp, err := http.Get(asset.DownloadURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
tmpDest := filepath.Join(tmp, asset.Name)
|
||||
fi, err := os.Create(tmpDest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := io.Copy(fi, resp.Body); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fi.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := unpack(tmpDest, asset.Name); err != nil {
|
||||
return fmt.Errorf("could not unpack download: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func unpack(src, name string) error {
|
||||
dest := filepath.Join(xdg.DataHome, "eget", name)
|
||||
if err := os.MkdirAll(dest, os.ModePerm); err != nil {
|
||||
return fmt.Errorf("could not make all dest dirs: %w", err)
|
||||
}
|
||||
var binExt string
|
||||
if runtime.GOOS == "windows" {
|
||||
binExt = ".exe"
|
||||
}
|
||||
uaIface, err := archiver.ByExtension(src)
|
||||
if err != nil {
|
||||
if filepath.Ext(src) == binExt {
|
||||
return os.Rename(src, filepath.Join(dest, name+binExt))
|
||||
}
|
||||
return err
|
||||
}
|
||||
u, ok := uaIface.(archiver.Unarchiver)
|
||||
if !ok {
|
||||
dest = filepath.Join(dest, name+binExt)
|
||||
d, ok := uaIface.(archiver.Decompressor)
|
||||
if !ok {
|
||||
return fmt.Errorf("format specified by source filename is not an archive or compression format: %s (%T)", src, uaIface)
|
||||
}
|
||||
c := archiver.FileCompressor{Decompressor: d}
|
||||
if err := c.DecompressFile(src, dest); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Chmod(dest, 0o755)
|
||||
}
|
||||
|
||||
tmpDest := filepath.Join(filepath.Dir(src), name)
|
||||
if err := u.Unarchive(src, tmpDest); err != nil {
|
||||
return fmt.Errorf("could not unarchive source: %w", err)
|
||||
}
|
||||
|
||||
tmpSrc := tmpDest
|
||||
infos, err := os.ReadDir(tmpSrc)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not read unarchived dir: %w", err)
|
||||
}
|
||||
if len(infos) == 1 && infos[0].IsDir() {
|
||||
tmpSrc = filepath.Join(tmpDest, infos[0].Name())
|
||||
}
|
||||
|
||||
return moveDir(tmpSrc, dest)
|
||||
}
|
||||
|
||||
func moveDir(src, dest string) error {
|
||||
return filepath.WalkDir(src, func(walkPath string, walkEntry fs.DirEntry, walkErr error) error {
|
||||
if walkErr != nil {
|
||||
return walkErr
|
||||
}
|
||||
if walkEntry.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
rel := strings.TrimPrefix(walkPath, src)
|
||||
destPath := filepath.Join(dest, rel)
|
||||
if err := os.MkdirAll(filepath.Dir(destPath), os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Rename(walkPath, destPath)
|
||||
})
|
||||
}
|
|
@ -1,16 +1,18 @@
|
|||
package forge
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Forge string
|
||||
|
||||
const (
|
||||
ForgeGitea Forge = "gitea"
|
||||
ForgeGitHub Forge = "github"
|
||||
var (
|
||||
amd64Re = regexp.MustCompile(`amd64|x86_64|64`)
|
||||
linuxRe = regexp.MustCompile(`linux|linux64`)
|
||||
windowsRe = regexp.MustCompile(`windows|win64`)
|
||||
)
|
||||
|
||||
type Release struct {
|
||||
|
@ -29,6 +31,9 @@ type Forger interface {
|
|||
}
|
||||
|
||||
func splitURI(uri string) (string, []string, error) {
|
||||
if !strings.HasPrefix(uri, "http") {
|
||||
uri = "https://" + uri
|
||||
}
|
||||
u, err := url.ParseRequestURI(uri)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("could not parse URI: %w", err)
|
||||
|
@ -38,3 +43,35 @@ func splitURI(uri string) (string, []string, error) {
|
|||
u.Path = ""
|
||||
return u.String(), paths, nil
|
||||
}
|
||||
|
||||
func Latest(f Forger) (Asset, error) {
|
||||
var asset Asset
|
||||
|
||||
var re *regexp.Regexp
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
re = linuxRe
|
||||
case "windows":
|
||||
re = windowsRe
|
||||
default:
|
||||
return asset, fmt.Errorf("%q is not a supported OS", runtime.GOOS)
|
||||
}
|
||||
|
||||
release, err := f.Latest()
|
||||
if err != nil {
|
||||
return asset, fmt.Errorf("could not get latest release: %w", err)
|
||||
}
|
||||
|
||||
for _, a := range release.Assets {
|
||||
if amd64Re.MatchString(a.Name) && re.MatchString(a.Name) {
|
||||
fmt.Printf("found %q\n", a.Name)
|
||||
asset = a
|
||||
}
|
||||
}
|
||||
|
||||
if asset.Name == "" {
|
||||
return asset, errors.New("no release found for this OS")
|
||||
}
|
||||
|
||||
return asset, nil
|
||||
}
|
||||
|
|
|
@ -5,8 +5,6 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var _ Forger = (*Gitea)(nil)
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
package forge
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
var _ Forger = (*GitHub)(nil)
|
||||
|
||||
type GitHub struct {
|
||||
Owner string
|
||||
Repo string
|
||||
}
|
||||
|
||||
type GitHubRelease struct {
|
||||
TagName string `json:"tag_name"`
|
||||
Assets []struct {
|
||||
Name string `json:"name"`
|
||||
BrowserDownloadURL string `json:"browser_download_url"`
|
||||
} `json:"assets"`
|
||||
}
|
||||
|
||||
func NewGitHub(uri string) (GitHub, error) {
|
||||
var g GitHub
|
||||
_, paths, err := splitURI(uri)
|
||||
if err != nil {
|
||||
return g, err
|
||||
}
|
||||
g.Owner = paths[0]
|
||||
g.Repo = paths[1]
|
||||
return g, nil
|
||||
}
|
||||
|
||||
func (g GitHub) Name() string {
|
||||
return g.Repo
|
||||
}
|
||||
|
||||
func (g GitHub) Latest() (Release, error) {
|
||||
var release Release
|
||||
|
||||
var r GitHubRelease
|
||||
u := fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/latest", g.Owner, g.Repo)
|
||||
res, err := http.Get(u)
|
||||
if err != nil {
|
||||
return release, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
if err := json.NewDecoder(res.Body).Decode(&r); err != nil {
|
||||
return release, err
|
||||
}
|
||||
|
||||
release.Name = r.TagName
|
||||
for _, a := range r.Assets {
|
||||
release.Assets = append(release.Assets, Asset{
|
||||
Name: a.Name,
|
||||
DownloadURL: a.BrowserDownloadURL,
|
||||
})
|
||||
}
|
||||
|
||||
return release, nil
|
||||
}
|
9
go.mod
9
go.mod
|
@ -3,16 +3,17 @@ module go.jolheiser.com/eget
|
|||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/adrg/xdg v0.4.0 // indirect
|
||||
github.com/adrg/xdg v0.4.0
|
||||
github.com/mholt/archiver/v3 v3.5.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/andybalholm/brotli v1.0.4 // indirect
|
||||
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
|
||||
github.com/golang/snappy v0.0.2 // indirect
|
||||
github.com/klauspost/compress v1.11.4 // indirect
|
||||
github.com/klauspost/pgzip v1.2.5 // indirect
|
||||
github.com/matryer/is v1.4.0 // indirect
|
||||
github.com/mholt/archiver/v3 v3.5.1 // indirect
|
||||
github.com/nwaples/rardecode v1.1.0 // indirect
|
||||
github.com/peterbourgon/ff/v3 v3.3.0 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.2 // indirect
|
||||
github.com/ulikunitz/xz v0.5.9 // indirect
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
|
||||
|
|
9
go.sum
9
go.sum
|
@ -1,9 +1,9 @@
|
|||
github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls=
|
||||
github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E=
|
||||
github.com/andybalholm/brotli v1.0.1 h1:KqhlKozYbRtJvsPrrEeXcO+N2l6NYT5A2QAFmSULpEc=
|
||||
github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
||||
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
|
||||
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY=
|
||||
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
|
||||
|
@ -17,18 +17,16 @@ github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYs
|
|||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE=
|
||||
github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
|
||||
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
||||
github.com/mholt/archiver/v3 v3.5.1 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Clwo=
|
||||
github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4=
|
||||
github.com/nwaples/rardecode v1.1.0 h1:vSxaY8vQhOcVr4mm5e8XllHWTiM4JF507A0Katqw7MQ=
|
||||
github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
|
||||
github.com/peterbourgon/ff/v3 v3.3.0 h1:PaKe7GW8orVFh8Unb5jNHS+JZBwWUMa2se0HM6/BI24=
|
||||
github.com/peterbourgon/ff/v3 v3.3.0/go.mod h1:zjJVUhx+twciwfDl0zBcFzl4dW8axCRyXE/eKY9RztQ=
|
||||
github.com/pierrec/lz4/v4 v4.1.2 h1:qvY3YFXRQE/XB8MlLzJH7mSzBs74eA2gg52YTk6jUPM=
|
||||
github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/ulikunitz/xz v0.5.9 h1:RsKRIA2MO8x56wkkcd3LbtcE/uMszhb6DpRf+3uwa3I=
|
||||
|
@ -39,4 +37,5 @@ golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359 h1:2B5p2L5IfGiD7+b9BOoRMC6Dg
|
|||
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
34
main.go
34
main.go
|
@ -1,6 +1,40 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"go.jolheiser.com/eget/disk"
|
||||
"go.jolheiser.com/eget/forge"
|
||||
)
|
||||
|
||||
var Version = "develop"
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
fmt.Println("eget <project URI>")
|
||||
return
|
||||
}
|
||||
|
||||
var f forge.Forger
|
||||
var err error
|
||||
uri := os.Args[1]
|
||||
|
||||
f, err = forge.NewGitea(uri)
|
||||
if strings.HasPrefix(uri, "github") {
|
||||
f, err = forge.NewGitHub(uri)
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
asset, err := forge.Latest(f)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err := disk.Install(asset); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
|
115
web/web.go
115
web/web.go
|
@ -1,115 +0,0 @@
|
|||
package web
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
|
||||
"github.com/adrg/xdg"
|
||||
"github.com/mholt/archiver/v3"
|
||||
"go.jolheiser.com/eget/forge"
|
||||
)
|
||||
|
||||
var (
|
||||
amd64Re = regexp.MustCompile(`amd64|x86_64|64-bit`)
|
||||
linuxRe = regexp.MustCompile(`linux`)
|
||||
windowsRe = regexp.MustCompile(`windows`)
|
||||
)
|
||||
|
||||
func Install(f forge.Forger) error {
|
||||
var re *regexp.Regexp
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
re = linuxRe
|
||||
case "windows":
|
||||
re = windowsRe
|
||||
default:
|
||||
return fmt.Errorf("%q is not a supported OS", runtime.GOOS)
|
||||
}
|
||||
|
||||
latest, err := f.Latest()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var asset forge.Asset
|
||||
for _, a := range latest.Assets {
|
||||
if amd64Re.MatchString(a.DownloadURL) && re.MatchString(a.DownloadURL) {
|
||||
asset = a
|
||||
}
|
||||
}
|
||||
|
||||
if asset.Name == "" {
|
||||
return errors.New("no release found for this OS")
|
||||
}
|
||||
|
||||
tmp, err := os.MkdirTemp(os.TempDir(), "eget-*")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(tmp)
|
||||
|
||||
resp, err := http.Get(asset.DownloadURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
tmpDest := filepath.Join(tmp, asset.Name)
|
||||
fi, err := os.Create(tmpDest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := io.Copy(fi, resp.Body); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fi.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := unpack(tmpDest, f.Name()); err != nil {
|
||||
return fmt.Errorf("could not unpack download: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func detectRootDir(path string) bool {
|
||||
infos, err := os.ReadDir(path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return len(infos) != 1
|
||||
}
|
||||
|
||||
func unpack(src, name string) error {
|
||||
dest := filepath.Join(xdg.DataHome, name)
|
||||
uaIface, err := archiver.ByExtension(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u, ok := uaIface.(archiver.Unarchiver)
|
||||
if !ok {
|
||||
d, ok := uaIface.(archiver.Decompressor)
|
||||
if !ok {
|
||||
return fmt.Errorf("format specified by source filename is not an archive or compression format: %s (%T)", src, uaIface)
|
||||
}
|
||||
var binExt string
|
||||
if runtime.GOOS == "windows" {
|
||||
binExt = ".exe"
|
||||
}
|
||||
dest = filepath.Join(dest, name+binExt)
|
||||
c := archiver.FileCompressor{Decompressor: d}
|
||||
if err := c.DecompressFile(src, dest); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Chmod(dest, 0o755)
|
||||
}
|
||||
return u.Unarchive(src, dest)
|
||||
}
|
Reference in New Issue