parent
9a2a4201fc
commit
fe91dc2dd1
|
@ -1,5 +1,11 @@
|
||||||
package forge
|
package forge
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
type Forge string
|
type Forge string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -18,5 +24,17 @@ type Asset struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Forger interface {
|
type Forger interface {
|
||||||
|
Name() string
|
||||||
Latest() (Release, error)
|
Latest() (Release, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func splitURI(uri string) (string, []string, error) {
|
||||||
|
u, err := url.ParseRequestURI(uri)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, fmt.Errorf("could not parse URI: %w", err)
|
||||||
|
}
|
||||||
|
u.Scheme = "https"
|
||||||
|
paths := strings.FieldsFunc(u.Path, func(r rune) bool { return r == '/' })
|
||||||
|
u.Path = ""
|
||||||
|
return u.String(), paths, nil
|
||||||
|
}
|
||||||
|
|
|
@ -5,6 +5,8 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ Forger = (*Gitea)(nil)
|
var _ Forger = (*Gitea)(nil)
|
||||||
|
@ -23,6 +25,23 @@ type GiteaRelease struct {
|
||||||
} `json:"assets"`
|
} `json:"assets"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewGitea(uri string) (Gitea, error) {
|
||||||
|
var g Gitea
|
||||||
|
base, paths, err := splitURI(uri)
|
||||||
|
if err != nil {
|
||||||
|
return g, err
|
||||||
|
}
|
||||||
|
|
||||||
|
g.BaseURL = base
|
||||||
|
g.Owner = paths[0]
|
||||||
|
g.Repo = paths[1]
|
||||||
|
return g, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g Gitea) Name() string {
|
||||||
|
return g.Repo
|
||||||
|
}
|
||||||
|
|
||||||
func (g Gitea) Latest() (Release, error) {
|
func (g Gitea) Latest() (Release, error) {
|
||||||
var release Release
|
var release Release
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
package meta
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/adrg/xdg"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Meta struct {
|
||||||
|
Packages []Package `json:"packages"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Package struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func Add(pkg Package) error {
|
||||||
|
m, err := Read()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, p := range m.Packages {
|
||||||
|
if strings.EqualFold(pkg.Name, p.Name) {
|
||||||
|
return errors.New("package already exists locally")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m.Packages = append(m.Packages, pkg)
|
||||||
|
return save(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Read() (Meta, error) {
|
||||||
|
var m Meta
|
||||||
|
fp, err := metaPath()
|
||||||
|
if err != nil {
|
||||||
|
return m, fmt.Errorf("could not get meta file: %w", err)
|
||||||
|
}
|
||||||
|
fi, err := os.Open(fp)
|
||||||
|
if err != nil {
|
||||||
|
return m, fmt.Errorf("could not open meta file: %w", err)
|
||||||
|
}
|
||||||
|
defer fi.Close()
|
||||||
|
|
||||||
|
if err := json.NewDecoder(fi).Decode(&m); err != nil {
|
||||||
|
return m, fmt.Errorf("could not decode meta: %w", err)
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Remove(name string) error {
|
||||||
|
m, err := Read()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for idx, p := range m.Packages {
|
||||||
|
if strings.EqualFold(name, p.Name) {
|
||||||
|
m.Packages = append(m.Packages[:idx], m.Packages[idx+1:]...)
|
||||||
|
return save(m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Errorf("could not find package to remove for %q", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func metaPath() (string, error) {
|
||||||
|
return xdg.DataFile("eget/meta.json")
|
||||||
|
}
|
||||||
|
|
||||||
|
func save(m Meta) error {
|
||||||
|
fp, err := metaPath()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not get meta path: %w", err)
|
||||||
|
}
|
||||||
|
fi, err := os.Create(fp)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not create meta file: %w", err)
|
||||||
|
}
|
||||||
|
defer fi.Close()
|
||||||
|
return json.NewEncoder(fi).Encode(m)
|
||||||
|
}
|
|
@ -0,0 +1,115 @@
|
||||||
|
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