Compare commits
64 Commits
28809828c2
...
v0.0.4
| Author | SHA1 | Date | |
|---|---|---|---|
| 46e2d3166f | |||
| 3437df8676 | |||
| 3361358b3c | |||
| 3ca052fea7 | |||
| 001e33dd2f | |||
| f30f4c7081 | |||
| 5bc81e3a30 | |||
| d941ce231e | |||
| e22bc0f10c | |||
| 8ff903b68f | |||
| 3bb7fe3690 | |||
| 26d139c34e | |||
| 8ceb61de9a | |||
| 24c807a941 | |||
| da7830d0e3 | |||
| 98a3b26a27 | |||
| da630f648d | |||
| c489f4864e | |||
| 320342cfb4 | |||
| 45ad9fbe39 | |||
| 3f2ec8ebd3 | |||
| 2c2a27c9f7 | |||
| 05a1ecea64 | |||
| d32437e8b2 | |||
| 8f95ff4676 | |||
| 7442da7105 | |||
| 07e41849e9 | |||
| 27fb08d5ba | |||
| d78064179f | |||
| 2157d9ecce | |||
| b686f810fb | |||
| c856bf0686 | |||
| c650c1dae0 | |||
| b3b6612ef2 | |||
| baf4cca4fb | |||
| e604f61151 | |||
| 8e74e58cad | |||
| be48f26e75 | |||
| b6265f4b1d | |||
| c0e535c630 | |||
| 2b6815e287 | |||
| a42c9b27e7 | |||
| e2c8335381 | |||
| b56641c659 | |||
| 61ba975e21 | |||
| 75a60070ba | |||
| a02a009b63 | |||
| 74adb915fc | |||
| bdca0a5ffc | |||
| 674cfe6b0d | |||
| bb50b55ac5 | |||
| 29016fcdb7 | |||
| c09574e659 | |||
| 1f39f5edf1 | |||
| 8661721ccc | |||
| 93d5ad9a53 | |||
| 77c3ea7d56 | |||
| fa76d95c04 | |||
| 6012f0f505 | |||
| 35046566a1 | |||
| 46f79e4d26 | |||
| 7fa6dcf183 | |||
| a5c2ac60d9 | |||
| 7d6d22cf69 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
/lure
|
||||
/dist/
|
||||
/dist/
|
||||
/version.txt
|
||||
@@ -1,6 +1,7 @@
|
||||
before:
|
||||
hooks:
|
||||
- go mod tidy
|
||||
- go generate
|
||||
builds:
|
||||
- id: lure
|
||||
env:
|
||||
@@ -21,6 +22,7 @@ archives:
|
||||
arm64: aarch64
|
||||
nfpms:
|
||||
- id: lure
|
||||
package_name: linux-user-repository
|
||||
file_name_template: '{{.PackageName}}-{{.Version}}-{{.Os}}-{{.Arch}}'
|
||||
description: "Linux User REpository"
|
||||
replacements:
|
||||
@@ -28,18 +30,22 @@ nfpms:
|
||||
amd64: x86_64
|
||||
arm64: aarch64
|
||||
homepage: 'https://gitea.arsenm.dev/Arsen6331/lure'
|
||||
maintainer: 'Arsen Musyaelyan <arsen@arsenm.dev>'
|
||||
maintainer: 'Arsen Musayelyan <arsen@arsenm.dev>'
|
||||
license: GPLv3
|
||||
formats:
|
||||
- apk
|
||||
- deb
|
||||
- rpm
|
||||
provides:
|
||||
- lure
|
||||
conflicts:
|
||||
- lure
|
||||
aurs:
|
||||
- name: lure-bin
|
||||
homepage: 'https://gitea.arsenm.dev/Arsen6331/lure'
|
||||
description: "Linux User REpository"
|
||||
maintainers:
|
||||
- 'Arsen Musyaelyan <arsen@arsenm.dev>'
|
||||
- 'Arsen Musayelyan <arsen@arsenm.dev>'
|
||||
license: GPLv3
|
||||
private_key: '{{ .Env.AUR_KEY }}'
|
||||
git_url: 'ssh://aur@aur.archlinux.org/lure-bin.git'
|
||||
|
||||
8
.woodpecker.yml
Normal file
8
.woodpecker.yml
Normal file
@@ -0,0 +1,8 @@
|
||||
pipeline:
|
||||
release:
|
||||
image: goreleaser/goreleaser
|
||||
commands:
|
||||
- goreleaser release
|
||||
secrets: [ gitea_token, aur_key ]
|
||||
when:
|
||||
event: tag
|
||||
5
Makefile
5
Makefile
@@ -1,4 +1,4 @@
|
||||
lure:
|
||||
lure: version.txt
|
||||
go build
|
||||
|
||||
clean:
|
||||
@@ -9,5 +9,8 @@ install: lure
|
||||
|
||||
uninstall:
|
||||
rm -f /usr/local/bin/lure
|
||||
|
||||
version.txt:
|
||||
go generate
|
||||
|
||||
.PHONY: install clean uninstall
|
||||
16
README.md
16
README.md
@@ -1,6 +1,7 @@
|
||||
# LURE (Linux User REpository)
|
||||
|
||||
[](https://goreportcard.com/report/go.arsenm.dev/lure)
|
||||
[](https://ci.arsenm.dev/Arsen6331/lure)
|
||||
[](https://aur.archlinux.org/packages/lure-bin/)
|
||||
|
||||
LURE is intended to bring the AUR to all distros. It is currently in an ***alpha*** state and may not be stable. It can download a repository, build packages in it using a bash script similar to [PKGBUILD](https://wiki.archlinux.org/title/PKGBUILD), and then install them using your system package manager.
|
||||
@@ -41,20 +42,6 @@ The documentation for LURE is in the [docs](docs) directory in this repo.
|
||||
|
||||
---
|
||||
|
||||
## Cross-packaging for other Distributions
|
||||
|
||||
You can create packages for different distributions
|
||||
setting the environment variables `LURE_DISTRO` and `LURE_PKG_FORMAT`.
|
||||
|
||||
```
|
||||
LURE_DISTRO=arch LURE_PKG_FORMAT=archlinux lure build
|
||||
LURE_DISTRO=alpine LURE_PKG_FORMAT=apk lure build
|
||||
LURE_DISTRO=opensuse LURE_PKG_FORMAT=rpm lure build
|
||||
LURE_DISTRO=debian LURE_PKG_FORMAT=deb lure build
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Repositories
|
||||
|
||||
Unlike the AUR, LURE supports using multiple repos. Also unlike the AUR, LURE's repos are a single git repo containing all the build scripts. Inside each LURE repo, there should be a separate directory for each package containing a `lure.sh` script, which is a PKGBUILD-like build script for LURE. The default repository is hosted on Github: https://github.com/Arsen6331/lure-repo.
|
||||
@@ -74,7 +61,6 @@ As mentioned before, LURE has zero dependencies after it's built. All functional
|
||||
|
||||
## Planned Features
|
||||
|
||||
- Source patching via .patch files
|
||||
- Automated install script
|
||||
- Automated docker-based testing tool
|
||||
- Web interface for repos
|
||||
234
build.go
234
build.go
@@ -19,6 +19,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
@@ -28,18 +29,20 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
_ "github.com/goreleaser/nfpm/v2/apk"
|
||||
_ "github.com/goreleaser/nfpm/v2/arch"
|
||||
_ "github.com/goreleaser/nfpm/v2/deb"
|
||||
_ "github.com/goreleaser/nfpm/v2/rpm"
|
||||
"github.com/urfave/cli/v2"
|
||||
"golang.org/x/exp/slices"
|
||||
"golang.org/x/sys/cpu"
|
||||
|
||||
"github.com/goreleaser/nfpm/v2"
|
||||
"github.com/goreleaser/nfpm/v2/files"
|
||||
"go.arsenm.dev/lure/distro"
|
||||
"go.arsenm.dev/lure/download"
|
||||
"go.arsenm.dev/lure/internal/cpu"
|
||||
"go.arsenm.dev/lure/internal/shutils"
|
||||
"go.arsenm.dev/lure/internal/shutils/decoder"
|
||||
"go.arsenm.dev/lure/manager"
|
||||
"mvdan.cc/sh/v3/expand"
|
||||
@@ -74,7 +77,7 @@ type Scripts struct {
|
||||
PreInstall string `sh:"preinstall"`
|
||||
PostInstall string `sh:"postinstall"`
|
||||
PreRemove string `sh:"preremove"`
|
||||
PostRemove string `sh:"postinstall"`
|
||||
PostRemove string `sh:"postremove"`
|
||||
PreUpgrade string `sh:"preupgrade"`
|
||||
PostUpgrade string `sh:"postupgrade"`
|
||||
PreTrans string `sh:"pretrans"`
|
||||
@@ -89,12 +92,23 @@ func buildCmd(c *cli.Context) error {
|
||||
log.Fatal("Unable to detect supported package manager on system").Send()
|
||||
}
|
||||
|
||||
_, pkgNames, err := buildPackage(c.Context, script, mgr)
|
||||
pkgPaths, _, err := buildPackage(c.Context, script, mgr)
|
||||
if err != nil {
|
||||
log.Fatal("Error building package").Err(err).Send()
|
||||
}
|
||||
|
||||
log.Info("Package(s) built successfully").Any("names", pkgNames).Send()
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
log.Fatal("Error getting working directory").Err(err).Send()
|
||||
}
|
||||
|
||||
for _, pkgPath := range pkgPaths {
|
||||
name := filepath.Base(pkgPath)
|
||||
err = os.Rename(pkgPath, filepath.Join(wd, name))
|
||||
if err != nil {
|
||||
log.Fatal("Error moving the package").Err(err).Send()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -105,8 +119,13 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var distroChanged bool
|
||||
if distID, ok := os.LookupEnv("LURE_DISTRO"); ok {
|
||||
info.ID = distID
|
||||
// Since the distro was overwritten, we don't know what the
|
||||
// like distros are, so set to nil
|
||||
info.Like = nil
|
||||
distroChanged = true
|
||||
}
|
||||
|
||||
fl, err := os.Open(script)
|
||||
@@ -126,6 +145,7 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st
|
||||
runner, err := interp.New(
|
||||
interp.Env(expand.ListEnviron(env...)),
|
||||
interp.StdIO(os.Stdin, os.Stdout, os.Stderr),
|
||||
interp.ExecHandler(helpers.ExecHandler),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -138,12 +158,56 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st
|
||||
|
||||
dec := decoder.New(info, runner)
|
||||
|
||||
// If distro was changed, the list of like distros
|
||||
// no longer applies, so disable its use
|
||||
if distroChanged {
|
||||
dec.LikeDistros = false
|
||||
}
|
||||
|
||||
fn, ok := dec.GetFunc("version")
|
||||
if ok {
|
||||
log.Info("Executing version()").Send()
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
err = fn(
|
||||
ctx,
|
||||
interp.Dir(filepath.Dir(script)),
|
||||
interp.StdIO(os.Stdin, buf, os.Stderr),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
newVer := strings.TrimSpace(buf.String())
|
||||
err = setVersion(ctx, runner, newVer)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
log.Info("Updating version").Str("new", newVer).Send()
|
||||
}
|
||||
|
||||
var vars BuildVars
|
||||
err = dec.DecodeVars(&vars)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if !archMatches(vars.Architectures) {
|
||||
var buildAnyway bool
|
||||
survey.AskOne(
|
||||
&survey.Confirm{
|
||||
Message: "Your system's CPU architecture doesn't match this package. Do you want to build anyway?",
|
||||
Default: true,
|
||||
},
|
||||
&buildAnyway,
|
||||
)
|
||||
if !buildAnyway {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
log.Info("Building package").Str("name", vars.Name).Str("version", vars.Version).Send()
|
||||
|
||||
baseDir := filepath.Join(cacheDir, "pkgs", vars.Name)
|
||||
@@ -165,9 +229,21 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if len(vars.BuildDepends) > 0 {
|
||||
installed, err := mgr.ListInstalled(nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var buildDeps []string
|
||||
for _, pkgName := range vars.BuildDepends {
|
||||
if _, ok := installed[pkgName]; !ok {
|
||||
buildDeps = append(buildDeps, pkgName)
|
||||
}
|
||||
}
|
||||
|
||||
if len(buildDeps) > 0 {
|
||||
log.Info("Installing build dependencies").Send()
|
||||
installPkgs(ctx, vars.BuildDepends, mgr)
|
||||
installPkgs(ctx, buildDeps, mgr, false)
|
||||
}
|
||||
|
||||
var builtDeps, builtNames, repoDeps []string
|
||||
@@ -194,16 +270,18 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
err = setDirVars(ctx, runner, srcdir, pkgdir)
|
||||
repodir := filepath.Dir(script)
|
||||
|
||||
err = setDirVars(ctx, runner, srcdir, pkgdir, repodir)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
fn, ok := dec.GetFunc("prepare")
|
||||
fn, ok = dec.GetFunc("prepare")
|
||||
if ok {
|
||||
log.Info("Executing prepare()").Send()
|
||||
|
||||
err = fn(ctx, srcdir)
|
||||
err = fn(ctx, interp.Dir(srcdir))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -213,7 +291,7 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st
|
||||
if ok {
|
||||
log.Info("Executing build()").Send()
|
||||
|
||||
err = fn(ctx, srcdir)
|
||||
err = fn(ctx, interp.Dir(srcdir))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -223,10 +301,12 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st
|
||||
if ok {
|
||||
log.Info("Executing package()").Send()
|
||||
|
||||
err = fn(ctx, srcdir)
|
||||
err = fn(ctx, interp.Dir(srcdir))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
} else {
|
||||
log.Fatal("The package() function is required").Send()
|
||||
}
|
||||
|
||||
uniq(
|
||||
@@ -241,7 +321,6 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st
|
||||
Arch: runtime.GOARCH,
|
||||
Version: vars.Version,
|
||||
Release: strconv.Itoa(vars.Release),
|
||||
Epoch: strconv.FormatUint(uint64(vars.Epoch), 10),
|
||||
Homepage: vars.Homepage,
|
||||
License: strings.Join(vars.Licenses, ", "),
|
||||
Maintainer: vars.Maintainer,
|
||||
@@ -253,6 +332,10 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st
|
||||
},
|
||||
}
|
||||
|
||||
if vars.Epoch != 0 {
|
||||
pkgInfo.Epoch = strconv.FormatUint(uint64(vars.Epoch), 10)
|
||||
}
|
||||
|
||||
setScripts(&vars, pkgInfo, filepath.Dir(script))
|
||||
|
||||
if slices.Contains(vars.Architectures, "all") {
|
||||
@@ -260,7 +343,7 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st
|
||||
}
|
||||
|
||||
if pkgInfo.Arch == "arm" {
|
||||
pkgInfo.Arch = checkARMVariant()
|
||||
pkgInfo.Arch = cpu.ARMVariant()
|
||||
}
|
||||
|
||||
contents := []*files.Content{}
|
||||
@@ -357,6 +440,29 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if len(buildDeps) > 0 {
|
||||
var removeBuildDeps bool
|
||||
err = survey.AskOne(&survey.Confirm{
|
||||
Message: "Would you like to remove build dependencies?",
|
||||
}, &removeBuildDeps)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if removeBuildDeps {
|
||||
err = mgr.Remove(
|
||||
&manager.Opts{
|
||||
AsRoot: true,
|
||||
NoConfirm: true,
|
||||
},
|
||||
buildDeps...,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uniq(&pkgPaths, &pkgNames)
|
||||
|
||||
return pkgPaths, pkgNames, nil
|
||||
@@ -364,14 +470,21 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st
|
||||
|
||||
func genBuildEnv(info *distro.OSRelease) []string {
|
||||
env := os.Environ()
|
||||
|
||||
arch := runtime.GOARCH
|
||||
if arch == "arm" {
|
||||
arch = cpu.ARMVariant()
|
||||
}
|
||||
|
||||
env = append(
|
||||
env,
|
||||
"DISTRO_NAME="+info.Name,
|
||||
"DISTRO_PRETTY_NAME="+info.PrettyName,
|
||||
"DISTRO_ID="+info.ID,
|
||||
"DISTRO_BUILD_ID="+info.BuildID,
|
||||
"DISTRO_VERSION_ID="+info.VersionID,
|
||||
"DISTRO_ID_LIKE="+strings.Join(info.Like, " "),
|
||||
|
||||
"ARCH="+runtime.GOARCH,
|
||||
"ARCH="+arch,
|
||||
"NCPU="+strconv.Itoa(runtime.NumCPU()),
|
||||
)
|
||||
|
||||
@@ -390,7 +503,7 @@ func getSources(ctx context.Context, srcdir string, bv *BuildVars) error {
|
||||
EncloseGit: true,
|
||||
}
|
||||
|
||||
if bv.Checksums[i] != "SKIP" {
|
||||
if !strings.EqualFold(bv.Checksums[i], "SKIP") {
|
||||
checksum, err := hex.DecodeString(bv.Checksums[i])
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -409,8 +522,8 @@ func getSources(ctx context.Context, srcdir string, bv *BuildVars) error {
|
||||
|
||||
// setDirVars sets srcdir and pkgdir. It's a very hacky way of doing so,
|
||||
// but setting the runner's Env and Vars fields doesn't seem to work.
|
||||
func setDirVars(ctx context.Context, runner *interp.Runner, srcdir, pkgdir string) error {
|
||||
cmd := "srcdir='" + srcdir + "'\npkgdir='" + pkgdir + "'\n"
|
||||
func setDirVars(ctx context.Context, runner *interp.Runner, srcdir, pkgdir, repodir string) error {
|
||||
cmd := "srcdir='" + srcdir + "'\npkgdir='" + pkgdir + "'\nrepodir='" + repodir + "'\n"
|
||||
fl, err := syntax.NewParser().Parse(strings.NewReader(cmd), "vars")
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -418,24 +531,6 @@ func setDirVars(ctx context.Context, runner *interp.Runner, srcdir, pkgdir strin
|
||||
return runner.Run(ctx, fl)
|
||||
}
|
||||
|
||||
// checkARMVariant checks which variant of ARM lure is running
|
||||
// on, by using the same detection method as Go itself
|
||||
func checkARMVariant() string {
|
||||
armEnv := os.Getenv("LURE_ARM_VARIANT")
|
||||
// ensure value has "arm" prefix, such as arm5 or arm6
|
||||
if strings.HasPrefix(armEnv, "arm") {
|
||||
return armEnv
|
||||
}
|
||||
|
||||
if cpu.ARM.HasVFPv3 {
|
||||
return "arm7"
|
||||
} else if cpu.ARM.HasVFP {
|
||||
return "arm6"
|
||||
} else {
|
||||
return "arm5"
|
||||
}
|
||||
}
|
||||
|
||||
func setScripts(vars *BuildVars, info *nfpm.Info, scriptDir string) {
|
||||
if vars.Scripts.PreInstall != "" {
|
||||
info.Scripts.PreInstall = filepath.Join(scriptDir, vars.Scripts.PreInstall)
|
||||
@@ -472,6 +567,73 @@ func setScripts(vars *BuildVars, info *nfpm.Info, scriptDir string) {
|
||||
}
|
||||
}
|
||||
|
||||
// getBuildVars only gets the build variables, while disabling exec, stat, open, and readdir
|
||||
func getBuildVars(ctx context.Context, script string, info *distro.OSRelease) (*BuildVars, error) {
|
||||
fl, err := os.Open(script)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
file, err := syntax.NewParser().Parse(fl, "lure.sh")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fl.Close()
|
||||
|
||||
runner, err := interp.New(
|
||||
interp.Env(expand.ListEnviron()),
|
||||
interp.ExecHandler(shutils.NopExec),
|
||||
interp.StatHandler(shutils.NopStat),
|
||||
interp.OpenHandler(shutils.NopOpen),
|
||||
interp.ReadDirHandler(shutils.NopReadDir),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = runner.Run(ctx, file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dec := decoder.New(info, runner)
|
||||
|
||||
var vars BuildVars
|
||||
err = dec.DecodeVars(&vars)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &vars, nil
|
||||
}
|
||||
|
||||
func archMatches(architectures []string) bool {
|
||||
if slices.Contains(architectures, "all") {
|
||||
return true
|
||||
}
|
||||
|
||||
arch := runtime.GOARCH
|
||||
|
||||
if arch == "arm" {
|
||||
arch = cpu.ARMVariant()
|
||||
}
|
||||
|
||||
if slices.Contains(architectures, "arm") {
|
||||
architectures = append(architectures, cpu.ARMVariant())
|
||||
}
|
||||
|
||||
return slices.Contains(architectures, arch)
|
||||
}
|
||||
|
||||
func setVersion(ctx context.Context, r *interp.Runner, to string) error {
|
||||
fl, err := syntax.NewParser().Parse(strings.NewReader("version='"+to+"'"), "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return r.Run(ctx, fl)
|
||||
}
|
||||
|
||||
func uniq(ss ...*[]string) {
|
||||
for _, s := range ss {
|
||||
slices.Sort(*s)
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"go.arsenm.dev/lure/internal/shutils"
|
||||
"mvdan.cc/sh/v3/expand"
|
||||
@@ -35,7 +36,8 @@ type OSRelease struct {
|
||||
Name string
|
||||
PrettyName string
|
||||
ID string
|
||||
BuildID string
|
||||
Like []string
|
||||
VersionID string
|
||||
ANSIColor string
|
||||
HomeURL string
|
||||
DocumentationURL string
|
||||
@@ -80,16 +82,22 @@ func ParseOSRelease(ctx context.Context) (*OSRelease, error) {
|
||||
return nil, ErrParse
|
||||
}
|
||||
|
||||
return &OSRelease{
|
||||
out := &OSRelease{
|
||||
Name: runner.Vars["NAME"].Str,
|
||||
PrettyName: runner.Vars["PRETTY_NAME"].Str,
|
||||
ID: runner.Vars["ID"].Str,
|
||||
BuildID: runner.Vars["BUILD_ID"].Str,
|
||||
VersionID: runner.Vars["VERSION_ID"].Str,
|
||||
ANSIColor: runner.Vars["ANSI_COLOR"].Str,
|
||||
HomeURL: runner.Vars["HOME_URL"].Str,
|
||||
DocumentationURL: runner.Vars["DOCUMENTATION_URL"].Str,
|
||||
SupportURL: runner.Vars["SUPPORT_URL"].Str,
|
||||
BugReportURL: runner.Vars["BUG_REPORT_URL"].Str,
|
||||
Logo: runner.Vars["LOGO"].Str,
|
||||
}, nil
|
||||
}
|
||||
|
||||
if runner.Vars["ID_LIKE"].IsSet() {
|
||||
out.Like = strings.Split(runner.Vars["ID_LIKE"].Str, " ")
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
@@ -2,4 +2,5 @@
|
||||
|
||||
- [Build Scripts](build-scripts.md)
|
||||
- [Usage](usage.md)
|
||||
- [Configuration](configuration.md)
|
||||
- [Configuration](configuration.md)
|
||||
- [Adding Packages to LURE's repo](adding-packages.md)
|
||||
23
docs/adding-packages.md
Normal file
23
docs/adding-packages.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# Adding Packages to LURE's repo
|
||||
|
||||
## Requirements
|
||||
|
||||
- `go` (1.18+)
|
||||
- `git`
|
||||
- `lure-analyzer`
|
||||
- `go install go.arsenm.dev/lure-repo-bot/cmd/lure-analyzer@latest`
|
||||
- `shfmt`
|
||||
- May be available in distro repos
|
||||
- `go install mvdan.cc/sh/v3/cmd/shfmt@latest`
|
||||
|
||||
---
|
||||
|
||||
## How to submit a package
|
||||
|
||||
LURE's repo is hosted on Github at https://github.com/Arsen6331/lure-repo. In it, there are multiple directories each containing a `lure.sh` file. In order to add a package to LURE's repo, simply create a PR with a [build script](./build-scripts.md) and place it in a directory with the same name as the package.
|
||||
|
||||
Upon submitting the PR, [lure-repo-bot](https://github.com/Arsen6331/lure-repo-bot) will pull your PR and analyze it, providing suggestions for fixes as review comments. If there are no problems, the bot will approve your changes. If there are issues, re-request review from the bot after you've finished applying the fixes and it will automatically review the PR again.
|
||||
|
||||
All scripts submitted to the LURE repo should be formatted with `shfmt`. If they are not properly formatted, Github Actions will add suggestions in the "Files Changed" tab of the PR.
|
||||
|
||||
Once your PR is merged, LURE will pull the changed repo and your package will be available for people to install.
|
||||
@@ -26,6 +26,28 @@ LURE uses build scripts similar to the AUR's PKGBUILDs. This is the documentatio
|
||||
- [checksums](#checksums)
|
||||
- [backup](#backup)
|
||||
- [scripts](#scripts)
|
||||
- [Functions](#functions)
|
||||
- [prepare](#prepare)
|
||||
- [version](#version-1)
|
||||
- [build](#build)
|
||||
- [package](#package)
|
||||
- [Environment Variables](#environment-variables)
|
||||
- [DISTRO_NAME](#distro_name)
|
||||
- [DISTRO_PRETTY_NAME](#distro_pretty_name)
|
||||
- [DISTRO_ID](#distro_id)
|
||||
- [DISTRO_VERSION_ID](#distro_version_id)
|
||||
- [ARCH](#arch)
|
||||
- [NCPU](#ncpu)
|
||||
- [Helper Commands](#helper-commands)
|
||||
- [install-binary](#install-binary)
|
||||
- [install-systemd](#install-systemd)
|
||||
- [install-systemd-user](#install-systemd-user)
|
||||
- [install-config](#install-config)
|
||||
- [install-license](#install-license)
|
||||
- [install-completion](#install-completion)
|
||||
- [install-manual](#install-manual)
|
||||
- [install-desktop](#install-desktop)
|
||||
- [install-library](#install-library)
|
||||
|
||||
---
|
||||
|
||||
@@ -50,6 +72,15 @@ Names are checked in the following order:
|
||||
|
||||
Distro detection is performed by reading the `/usr/lib/os-release` and `/etc/os-release` files.
|
||||
|
||||
### Like distros
|
||||
|
||||
Inside the `os-release` file, there is a list of "like" distros. LURE takes this into account. For example, if a script contains `deps_debian` but not `deps_ubuntu`, Ubuntu builds will use `deps_debian` because Ubuntu is based on debian.
|
||||
|
||||
Most specificity is preferred, so if both `deps_debian` and `deps_ubuntu` is provided, Ubuntu and all Ubuntu-based distros will use `deps_ubuntu` while Debian and all Debian-based distros
|
||||
that are not Ubuntu-based will use `deps_debian`.
|
||||
|
||||
Like distros are disabled when using the `LURE_DISTRO` environment variable.
|
||||
|
||||
## Variables
|
||||
|
||||
Any variables marked with `(*)` are required
|
||||
@@ -110,7 +141,7 @@ LURE_ARM_VARIANT=arm5 lure install ...
|
||||
|
||||
### licenses
|
||||
|
||||
The `licenses` array contains the licenses used by this package. Some valid values include `GPLv3` and `MIT`.
|
||||
The `licenses` array contains the licenses used by this package. In order to standardize license names, values should be [SPDX Identifiers](https://spdx.org/licenses/) such as `Apache-2.0`, `MIT`, and `GPL-3.0-only`. If the project uses a license that is not standardized in SPDX, use the value `Custom`. If the project has multiple nonstandard licenses, include `Custom` as many times as there are nonstandard licenses.
|
||||
|
||||
### provides
|
||||
|
||||
@@ -154,6 +185,7 @@ If the URL scheme starts with `git+`, the source will be downloaded as a git rep
|
||||
- `~branch`: Specify which branch of the repo to check out.
|
||||
- `~commit`: Specify which commit of the repo to check out.
|
||||
- `~depth`: Specify what depth should be used when cloning the repo. Must be an integer.
|
||||
- `~name`: Specify the name of the directory into which the git repo should be cloned.
|
||||
|
||||
Examples:
|
||||
|
||||
@@ -204,4 +236,235 @@ The `pretrans` and `posttrans` scripts are only available in `.rpm` packages.
|
||||
|
||||
The rest of the scripts are available in all packages.
|
||||
|
||||
---
|
||||
---
|
||||
|
||||
## Functions
|
||||
|
||||
This section documents user-defined functions that can be added to build scripts. Any functions marked with `(*)` are required.
|
||||
|
||||
All functions except for `version()` are executed in the `$srcdir` directory
|
||||
|
||||
### version
|
||||
|
||||
The `version()` function is the first to run. It updates the `version` variable. This allows for automatically deriving the version from sources. This is most useful for git packages, which usually don't need to be changed, so their `version` variable stays the same.
|
||||
|
||||
An example of using this for git:
|
||||
|
||||
```bash
|
||||
version() {
|
||||
cd "$srcdir/itd"
|
||||
printf "r%s.%s" "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)"
|
||||
}
|
||||
```
|
||||
|
||||
The AUR equivalent is the [`pkgver()` function](https://wiki.archlinux.org/title/VCS_package_guidelines#The_pkgver()_function)
|
||||
|
||||
This function does not run in `$srcdir` because it is executed before the source directory is even created. Instead, it runs in `$repodir`.
|
||||
|
||||
### prepare
|
||||
|
||||
The `prepare()` function is meant to prepare the sources for building and packaging. This is the function in which patches should be applied, for example, by the `patch` command, and where tools like `go generate` should be executed.
|
||||
|
||||
### build
|
||||
|
||||
The `build()` function is where the package is actually built. Use the same commands that would be used to manually compile the software. Often, this function is just one line:
|
||||
|
||||
```bash
|
||||
build() {
|
||||
make
|
||||
}
|
||||
```
|
||||
|
||||
### package (*)
|
||||
|
||||
The `package()` function is where the built files are placed into the directory that will be used by LURE to build the package.
|
||||
|
||||
Any files that should be installed on the filesystem should go in the `$pkgdir` directory in this function. For example, if you have a binary called `bin` that should be placed in `/usr/bin` and a config file called `bin.cfg` that should be placed in `/etc`, the `package()` function might look like this:
|
||||
|
||||
```bash
|
||||
package() {
|
||||
install -Dm755 bin ${pkgdir}/usr/bin/bin
|
||||
install -Dm644 bin.cfg ${pkgdir}/etc/bin.cfg
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Environment Variables
|
||||
|
||||
LURE exposes several values as environment variables for use in build scripts.
|
||||
|
||||
### DISTRO_NAME
|
||||
|
||||
The `DISTRO_NAME` variable is the name of the distro as defined in its `os-release` file.
|
||||
|
||||
For example, it's set to `Fedora Linux` in a Fedora 36 docker image
|
||||
|
||||
### DISTRO_PRETTY_NAME
|
||||
|
||||
The `DISTRO_PRETTY_NAME` variable is the "pretty" name of the distro as defined in its `os-release` file.
|
||||
|
||||
For example, it's set to `Fedora Linux 36 (Container Image)` in a Fedora 36 docker image
|
||||
|
||||
### DISTRO_ID
|
||||
|
||||
The `DISTRO_ID` variable is the identifier of the distro as defined in its `os-release` file. This is the same as what LURE uses for overrides.
|
||||
|
||||
For example, it's set to `fedora` in a Fedora 36 docker image
|
||||
|
||||
### DISTRO_ID_LIKE
|
||||
|
||||
The `DISTRO_ID_LIKE` variable contains identifiers of similar distros to the one running, separated by spaces.
|
||||
|
||||
For example, it's set to `opensuse suse` in an OpenSUSE Tumbleweed docker image and `rhel fedora` in a CentOS 8 docker image.
|
||||
|
||||
### DISTRO_VERSION_ID
|
||||
|
||||
The `DISTRO_VERSION_ID` variable is the version identifier of the distro as defined in its `os-release` file.
|
||||
|
||||
For example, it's set to `36` in a Fedora 36 docker image and `11` in a Debian Bullseye docker image
|
||||
|
||||
### ARCH
|
||||
|
||||
The `ARCH` variable is the architecture of the machine running the script. It uses the same naming convention as the values in the `architectures` array
|
||||
|
||||
### NCPU
|
||||
|
||||
The `NCPU` variable is the amount of CPUs available on the machine running the script. It will be set to `8` on a quad core machine with hyperthreading, for example.
|
||||
|
||||
---
|
||||
|
||||
## Helper Commands
|
||||
|
||||
LURE provides various commands to help packagers create proper cross-distro packages. These commands should be used wherever possible instead of doing the tasks manually.
|
||||
|
||||
### install-binary
|
||||
|
||||
`install-binary` accepts 1-2 arguments. The first argument is the binary you'd like to install. The second is the filename that should be used.
|
||||
|
||||
If the filename argument is not provided, tha name of the input file will be used.
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
install-binary ./itd
|
||||
install-binary ./itd itd-2
|
||||
```
|
||||
|
||||
### install-systemd
|
||||
|
||||
`install-systemd` installs regular systemd system services (see `install-systemd-user` for user services)
|
||||
|
||||
It accepts 1-2 arguments. The first argument is the service you'd like to install. The second is the filename that should be used.
|
||||
|
||||
If the filename argument is not provided, tha name of the input file will be used.
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
install-systemd ./syncthing@.service
|
||||
install-systemd-user ./syncthing@.service sync-thing@.service
|
||||
```
|
||||
|
||||
### install-systemd-user
|
||||
|
||||
`install-systemd-user` installs systemd user services (services like `itd` meant to be started with `--user`).
|
||||
|
||||
It accepts 1-2 arguments. The first argument is the service you'd like to install. The second is the filename that should be used.
|
||||
|
||||
If the filename argument is not provided, tha name of the input file will be used.
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
install-systemd-user ./itd.service
|
||||
install-systemd-user ./itd.service infinitime-daemon.service
|
||||
```
|
||||
|
||||
### install-config
|
||||
|
||||
`install-config` installs configuration files into the `/etc` directory
|
||||
|
||||
It accepts 1-2 arguments. The first argument is the config you'd like to install. The second is the filename that should be used.
|
||||
|
||||
If the filename argument is not provided, tha name of the input file will be used.
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
install-config ./itd.toml
|
||||
install-config ./itd.example.toml itd.toml
|
||||
```
|
||||
|
||||
### install-license
|
||||
|
||||
`install-license` installs a license file
|
||||
|
||||
It accepts 1-2 arguments. The first argument is the config you'd like to install. The second is the filename that should be used.
|
||||
|
||||
If the filename argument is not provided, tha name of the input file will be used.
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
install-license ./LICENSE itd/LICENSE
|
||||
```
|
||||
|
||||
### install-completion
|
||||
|
||||
`install-completion` installs shell completions
|
||||
|
||||
It currently supports `bash`, `zsh`, and `fish`
|
||||
|
||||
Completions are read from stdin, so they can either be piped in or retrieved from files
|
||||
|
||||
Two arguments are required for this function. The first one is the name of the shell and the second is the name of the completion.
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
./k9s completion fish | install-completion fish k9s
|
||||
install-completion bash k9s <./k9s/completions/k9s.fish
|
||||
```
|
||||
|
||||
### install-manual
|
||||
|
||||
`install-manual` installs manpages. It accepts a single argument, which is the path to the manpage.
|
||||
|
||||
The install path will be determined based on the number at the end of the filename. If a number cannot be extracted, an error will be returned.
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
install-manual ./man/strelaysrv.1
|
||||
install-manual ./mdoc.7
|
||||
```
|
||||
|
||||
### install-desktop
|
||||
|
||||
`install-desktop` installs desktop files for applications. It accepts 1-2 arguments. The first argument is the config you'd like to install. The second is the filename that should be used.
|
||||
|
||||
If the filename argument is not provided, tha name of the input file will be used.
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
install-desktop ./${name}/share/admc.desktop
|
||||
install-desktop ./${name}/share/admc.desktop admc-app.desktop
|
||||
```
|
||||
|
||||
### install-library
|
||||
|
||||
`install-library` installs shared and static libraries to the correct location.
|
||||
|
||||
This is the most important helper as it contains logic to figure out where to install libraries based on the target distro and CPU architecture. It should almost always be used to install all libraries.
|
||||
|
||||
It accepts 1-2 arguments. The first argument is the config you'd like to install. The second is the filename that should be used.
|
||||
|
||||
If the filename argument is not provided, tha name of the input file will be used.
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
install-library ./${name}/build/libadldap.so
|
||||
```
|
||||
@@ -111,6 +111,8 @@ Example:
|
||||
lure ref
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Environment Variables
|
||||
|
||||
### LURE_DISTRO
|
||||
@@ -137,4 +139,22 @@ The `LURE_ARM_VARIANT` environment variable dictates which ARM variant to build
|
||||
|
||||
- `arm5`
|
||||
- `arm6`
|
||||
- `arm7`
|
||||
- `arm7`
|
||||
|
||||
---
|
||||
|
||||
## Cross-packaging for other Distributions
|
||||
|
||||
You can create packages for different distributions
|
||||
setting the environment variables `LURE_DISTRO` and `LURE_PKG_FORMAT` as mentioned above.
|
||||
|
||||
Examples:
|
||||
|
||||
```
|
||||
LURE_DISTRO=arch LURE_PKG_FORMAT=archlinux lure build
|
||||
LURE_DISTRO=alpine LURE_PKG_FORMAT=apk lure build
|
||||
LURE_DISTRO=opensuse LURE_PKG_FORMAT=rpm lure build
|
||||
LURE_DISTRO=debian LURE_PKG_FORMAT=deb lure build
|
||||
```
|
||||
|
||||
---
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"hash"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -67,6 +68,22 @@ func Get(ctx context.Context, opts GetOptions) error {
|
||||
}
|
||||
query := src.Query()
|
||||
|
||||
if strings.HasPrefix(src.Scheme, "git+") {
|
||||
err = getGit(ctx, src, query, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
err = getFile(ctx, src, query, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getGit(ctx context.Context, src *url.URL, query url.Values, opts GetOptions) (err error) {
|
||||
tag := query.Get("~tag")
|
||||
query.Del("~tag")
|
||||
|
||||
@@ -79,6 +96,9 @@ func Get(ctx context.Context, opts GetOptions) error {
|
||||
depthStr := query.Get("~depth")
|
||||
query.Del("~depth")
|
||||
|
||||
name := query.Get("~name")
|
||||
query.Del("~name")
|
||||
|
||||
var refName plumbing.ReferenceName
|
||||
if tag != "" {
|
||||
refName = plumbing.NewTagReferenceName(tag)
|
||||
@@ -86,169 +106,185 @@ func Get(ctx context.Context, opts GetOptions) error {
|
||||
refName = plumbing.NewBranchReferenceName(branch)
|
||||
}
|
||||
|
||||
if strings.HasPrefix(src.Scheme, "git+") {
|
||||
src.Scheme = strings.TrimPrefix(src.Scheme, "git+")
|
||||
src.RawQuery = query.Encode()
|
||||
src.Scheme = strings.TrimPrefix(src.Scheme, "git+")
|
||||
src.RawQuery = query.Encode()
|
||||
|
||||
name := path.Base(src.Path)
|
||||
if name == "" {
|
||||
name = path.Base(src.Path)
|
||||
name = strings.TrimSuffix(name, ".git")
|
||||
}
|
||||
|
||||
dstDir := opts.Destination
|
||||
if opts.EncloseGit {
|
||||
dstDir = filepath.Join(opts.Destination, name)
|
||||
}
|
||||
dstDir := opts.Destination
|
||||
if opts.EncloseGit {
|
||||
dstDir = filepath.Join(opts.Destination, name)
|
||||
}
|
||||
|
||||
depth := 0
|
||||
if depthStr != "" {
|
||||
depth, err = strconv.Atoi(depthStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
cloneOpts := &git.CloneOptions{
|
||||
URL: src.String(),
|
||||
Progress: os.Stderr,
|
||||
Depth: depth,
|
||||
}
|
||||
|
||||
repo, err := git.PlainCloneContext(ctx, dstDir, false, cloneOpts)
|
||||
depth := 0
|
||||
if depthStr != "" {
|
||||
depth, err = strconv.Atoi(depthStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
w, err := repo.Worktree()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cloneOpts := &git.CloneOptions{
|
||||
URL: src.String(),
|
||||
Progress: os.Stderr,
|
||||
Depth: depth,
|
||||
}
|
||||
|
||||
checkoutOpts := &git.CheckoutOptions{}
|
||||
if refName != "" {
|
||||
checkoutOpts.Branch = refName
|
||||
} else if commit != "" {
|
||||
checkoutOpts.Hash = plumbing.NewHash(commit)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
repo, err := git.PlainCloneContext(ctx, dstDir, false, cloneOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return w.Checkout(checkoutOpts)
|
||||
w, err := repo.Worktree()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
checkoutOpts := &git.CheckoutOptions{}
|
||||
if refName != "" {
|
||||
checkoutOpts.Branch = refName
|
||||
} else if commit != "" {
|
||||
checkoutOpts.Hash = plumbing.NewHash(commit)
|
||||
} else {
|
||||
name := query.Get("~name")
|
||||
query.Del("~name")
|
||||
return nil
|
||||
}
|
||||
|
||||
archive := query.Get("~archive")
|
||||
query.Del("~archive")
|
||||
return w.Checkout(checkoutOpts)
|
||||
}
|
||||
|
||||
src.RawQuery = query.Encode()
|
||||
func getFile(ctx context.Context, src *url.URL, query url.Values, opts GetOptions) error {
|
||||
name := query.Get("~name")
|
||||
query.Del("~name")
|
||||
|
||||
if name == "" {
|
||||
name = path.Base(src.Path)
|
||||
}
|
||||
archive := query.Get("~archive")
|
||||
query.Del("~archive")
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, src.String(), nil)
|
||||
src.RawQuery = query.Encode()
|
||||
|
||||
if name == "" {
|
||||
name = path.Base(src.Path)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, src.String(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hash := sha256.New()
|
||||
|
||||
format, input, err := archiver.Identify(name, res.Body)
|
||||
if err == archiver.ErrNoMatch || archive == "false" {
|
||||
fl, err := os.Create(filepath.Join(opts.Destination, name))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
w := io.MultiWriter(hash, fl)
|
||||
|
||||
_, err = io.Copy(w, input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hash := sha256.New()
|
||||
res.Body.Close()
|
||||
fl.Close()
|
||||
|
||||
format, input, err := archiver.Identify(name, res.Body)
|
||||
if err == archiver.ErrNoMatch || archive == "false" {
|
||||
fl, err := os.Create(filepath.Join(opts.Destination, name))
|
||||
if err != nil {
|
||||
return err
|
||||
if opts.SHA256Sum != nil {
|
||||
sum := hash.Sum(nil)
|
||||
if !bytes.Equal(opts.SHA256Sum, sum) {
|
||||
return ErrChecksumMismatch
|
||||
}
|
||||
|
||||
w := io.MultiWriter(hash, fl)
|
||||
|
||||
_, err = io.Copy(w, input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res.Body.Close()
|
||||
fl.Close()
|
||||
|
||||
if opts.SHA256Sum != nil {
|
||||
sum := hash.Sum(nil)
|
||||
if !bytes.Equal(opts.SHA256Sum, sum) {
|
||||
return ErrChecksumMismatch
|
||||
}
|
||||
}
|
||||
} else if err != nil {
|
||||
}
|
||||
} else if err != nil {
|
||||
return err
|
||||
} else {
|
||||
err = extractFile(ctx, input, hash, format, name, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
r := io.TeeReader(input, hash)
|
||||
fname := format.Name()
|
||||
|
||||
switch format := format.(type) {
|
||||
case archiver.Extractor:
|
||||
err = format.Extract(ctx, r, nil, func(ctx context.Context, f archiver.File) error {
|
||||
fr, err := f.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fr.Close()
|
||||
|
||||
path := filepath.Join(opts.Destination, f.NameInArchive)
|
||||
|
||||
err = os.MkdirAll(filepath.Dir(path), 0o755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if f.IsDir() {
|
||||
err = os.Mkdir(path, 0o755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
outFl, err := os.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer outFl.Close()
|
||||
|
||||
_, err = io.Copy(outFl, fr)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case archiver.Decompressor:
|
||||
rc, err := format.OpenReader(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
path := filepath.Join(opts.Destination, name)
|
||||
path = strings.TrimSuffix(path, fname)
|
||||
|
||||
outFl, err := os.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = io.Copy(outFl, rc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if opts.SHA256Sum != nil {
|
||||
sum := hash.Sum(nil)
|
||||
if !bytes.Equal(opts.SHA256Sum, sum) {
|
||||
return ErrChecksumMismatch
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func extractFile(ctx context.Context, input io.Reader, hash hash.Hash, format archiver.Format, name string, opts GetOptions) (err error) {
|
||||
r := io.TeeReader(input, hash)
|
||||
fname := format.Name()
|
||||
|
||||
switch format := format.(type) {
|
||||
case archiver.Extractor:
|
||||
err = format.Extract(ctx, r, nil, func(ctx context.Context, f archiver.File) error {
|
||||
fr, err := f.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fr.Close()
|
||||
fi, err := f.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fm := fi.Mode()
|
||||
|
||||
path := filepath.Join(opts.Destination, f.NameInArchive)
|
||||
|
||||
err = os.MkdirAll(filepath.Dir(path), 0o755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if f.IsDir() {
|
||||
err = os.Mkdir(path, 0o755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
outFl, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, fm.Perm())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer outFl.Close()
|
||||
|
||||
_, err = io.Copy(outFl, fr)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case archiver.Decompressor:
|
||||
rc, err := format.OpenReader(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
path := filepath.Join(opts.Destination, name)
|
||||
path = strings.TrimSuffix(path, fname)
|
||||
|
||||
outFl, err := os.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = io.Copy(outFl, rc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if opts.SHA256Sum != nil {
|
||||
sum := hash.Sum(nil)
|
||||
if !bytes.Equal(opts.SHA256Sum, sum) {
|
||||
return ErrChecksumMismatch
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
9
go.mod
9
go.mod
@@ -1,13 +1,11 @@
|
||||
module go.arsenm.dev/lure
|
||||
|
||||
go 1.19
|
||||
|
||||
replace github.com/goreleaser/nfpm/v2 => github.com/Arsen6331/nfpm/v2 v2.0.0-20220922210414-eae88e8ea4b5
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/AlecAivazis/survey/v2 v2.3.6
|
||||
github.com/go-git/go-git/v5 v5.4.2
|
||||
github.com/goreleaser/nfpm/v2 v2.18.1
|
||||
github.com/goreleaser/nfpm/v2 v2.20.0
|
||||
github.com/mholt/archiver/v4 v4.0.0-alpha.7
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/pelletier/go-toml/v2 v2.0.5
|
||||
@@ -39,7 +37,6 @@ require (
|
||||
github.com/go-git/go-billy/v5 v5.3.1 // indirect
|
||||
github.com/gobwas/glob v0.2.3 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/rpmpack v0.0.0-20220314092521-38642b5e571e // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/gookit/color v1.5.1 // indirect
|
||||
github.com/goreleaser/chglog v0.2.2 // indirect
|
||||
@@ -49,7 +46,7 @@ require (
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/kevinburke/ssh_config v1.1.0 // indirect
|
||||
github.com/klauspost/compress v1.15.5 // indirect
|
||||
github.com/klauspost/compress v1.15.11 // indirect
|
||||
github.com/klauspost/pgzip v1.2.5 // indirect
|
||||
github.com/mattn/go-colorable v0.1.2 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
|
||||
14
go.sum
14
go.sum
@@ -2,8 +2,6 @@ github.com/AlecAivazis/survey/v2 v2.3.6 h1:NvTuVHISgTHEHeBFqt6BHOe4Ny/NwGZr7w+F8
|
||||
github.com/AlecAivazis/survey/v2 v2.3.6/go.mod h1:4AuI9b7RjAR+G7v9+C4YSlX/YL3K3cWNXgWXOhllqvI=
|
||||
github.com/AlekSi/pointer v1.2.0 h1:glcy/gc4h8HnG2Z3ZECSzZ1IX1x2JxRVuDzaJwQE0+w=
|
||||
github.com/AlekSi/pointer v1.2.0/go.mod h1:gZGfd3dpW4vEc/UlyfKKi1roIqcCgwOIvb0tSNSBle0=
|
||||
github.com/Arsen6331/nfpm/v2 v2.0.0-20220922210414-eae88e8ea4b5 h1:SFWe7Ho60w43hXEIxCdiAXZvUyM9GF/L90jMK42s8gU=
|
||||
github.com/Arsen6331/nfpm/v2 v2.0.0-20220922210414-eae88e8ea4b5/go.mod h1:O4K1mvEORY78CSCInptGG5MWJ19yr9xFTgWWUtY1R7o=
|
||||
github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ=
|
||||
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||
@@ -72,11 +70,8 @@ github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJA
|
||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
github.com/google/rpmpack v0.0.0-20220314092521-38642b5e571e h1:6Jn9JtfCn20uycra92LxTkq5yfBKNSFlRJPBk8/Cxhg=
|
||||
github.com/google/rpmpack v0.0.0-20220314092521-38642b5e571e/go.mod h1:83rLnx5vhPyN/mDzBYJWtiPf+9xnSVQynTpqZWe7OnY=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gookit/color v1.5.1 h1:Vjg2VEcdHpwq+oY63s/ksHrgJYCTo0bwWvmmYWdE9fQ=
|
||||
@@ -86,6 +81,8 @@ github.com/goreleaser/chglog v0.2.2 h1:V7nf07baXtGAgGevvqgW2MM4kZ6gOr12vKNSAU3VI
|
||||
github.com/goreleaser/chglog v0.2.2/go.mod h1:2s5JwtCOWjZa8AIneL+xdUl9SRuigCjRHNHsX30dupE=
|
||||
github.com/goreleaser/fileglob v1.3.0 h1:/X6J7U8lbDpQtBvGcwwPS6OpzkNVlVEsFUVRx9+k+7I=
|
||||
github.com/goreleaser/fileglob v1.3.0/go.mod h1:Jx6BoXv3mbYkEzwm9THo7xbr5egkAraxkGorbJb4RxU=
|
||||
github.com/goreleaser/nfpm/v2 v2.20.0 h1:Q/CrX54KUMluz6+M/pjTbknFd5Dao8qXi0C6ZuFCtfY=
|
||||
github.com/goreleaser/nfpm/v2 v2.20.0/go.mod h1:/Fh6XfwT/T+D4qtNC2iXmHSD/1UT20JkvBXyJ6nFmOY=
|
||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
|
||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
|
||||
github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw=
|
||||
@@ -103,9 +100,8 @@ github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT
|
||||
github.com/kevinburke/ssh_config v1.1.0 h1:pH/t1WS9NzT8go394IqZeJTMHVm6Cr6ZJ6AQ+mdNo/o=
|
||||
github.com/kevinburke/ssh_config v1.1.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/compress v1.15.5 h1:qyCLMz2JCrKADihKOh9FxnW3houKeNsp2h5OEz0QSEA=
|
||||
github.com/klauspost/compress v1.15.5/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
|
||||
github.com/klauspost/compress v1.15.11 h1:Lcadnb3RKGin4FYM/orgq0qde+nc15E5Cbqg4B9Sx9c=
|
||||
github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
|
||||
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=
|
||||
|
||||
212
helpers.go
Normal file
212
helpers.go
Normal file
@@ -0,0 +1,212 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"go.arsenm.dev/lure/internal/shutils"
|
||||
"golang.org/x/exp/slices"
|
||||
"mvdan.cc/sh/v3/interp"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNoPipe = errors.New("command requires data to be piped in")
|
||||
ErrNoDetectManNum = errors.New("manual number cannot be detected from the filename")
|
||||
)
|
||||
|
||||
var helpers = shutils.ExecFuncs{
|
||||
"install-binary": installHelperCmd("/usr/bin", 0o755),
|
||||
"install-systemd-user": installHelperCmd("/usr/lib/systemd/user", 0o644),
|
||||
"install-systemd": installHelperCmd("/usr/lib/systemd/system", 0o644),
|
||||
"install-config": installHelperCmd("/etc", 0o644),
|
||||
"install-license": installHelperCmd("/usr/share/licenses", 0o644),
|
||||
"install-desktop": installHelperCmd("/usr/share/applications", 0o644),
|
||||
"install-manual": installManualCmd,
|
||||
"install-completion": installCompletionCmd,
|
||||
"install-library": installLibraryCmd,
|
||||
}
|
||||
|
||||
func installHelperCmd(prefix string, perms os.FileMode) shutils.ExecFunc {
|
||||
return func(hc interp.HandlerContext, cmd string, args []string) error {
|
||||
if len(args) < 1 {
|
||||
return shutils.InsufficientArgsError(cmd, 1, len(args))
|
||||
}
|
||||
|
||||
from := args[0]
|
||||
to := ""
|
||||
if len(args) > 1 {
|
||||
to = filepath.Join(hc.Env.Get("pkgdir").Str, prefix, args[1])
|
||||
} else {
|
||||
to = filepath.Join(hc.Env.Get("pkgdir").Str, prefix, filepath.Base(from))
|
||||
}
|
||||
|
||||
err := helperInstall(from, to, perms)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %w", cmd, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func installManualCmd(hc interp.HandlerContext, cmd string, args []string) error {
|
||||
if len(args) < 1 {
|
||||
return shutils.InsufficientArgsError(cmd, 1, len(args))
|
||||
}
|
||||
|
||||
from := args[0]
|
||||
number := filepath.Base(from)
|
||||
// The man page may be compressed with gzip.
|
||||
// If it is, the .gz extension must be removed to properly
|
||||
// detect the number at the end of the filename.
|
||||
number = strings.TrimSuffix(number, ".gz")
|
||||
number = strings.TrimPrefix(filepath.Ext(number), ".")
|
||||
|
||||
// If number is not actually a number, return an error
|
||||
if _, err := strconv.Atoi(number); err != nil {
|
||||
return fmt.Errorf("install-manual: %w", ErrNoDetectManNum)
|
||||
}
|
||||
|
||||
prefix := "/usr/share/man/man" + number
|
||||
to := filepath.Join(hc.Env.Get("pkgdir").Str, prefix, filepath.Base(from))
|
||||
|
||||
return helperInstall(from, to, 0o644)
|
||||
}
|
||||
|
||||
func installCompletionCmd(hc interp.HandlerContext, cmd string, args []string) error {
|
||||
// If the command's stdin is the same as the system's,
|
||||
// that means nothing was piped in. In this case, return an error.
|
||||
if hc.Stdin == os.Stdin {
|
||||
return fmt.Errorf("install-completion: %w", ErrNoPipe)
|
||||
}
|
||||
|
||||
if len(args) < 2 {
|
||||
return shutils.InsufficientArgsError(cmd, 2, len(args))
|
||||
}
|
||||
|
||||
shell := args[0]
|
||||
name := args[1]
|
||||
|
||||
var prefix string
|
||||
switch shell {
|
||||
case "bash":
|
||||
prefix = "/usr/share/bash-completion/completion"
|
||||
case "zsh":
|
||||
prefix = "/usr/share/zsh/site-functions"
|
||||
name = "_" + name
|
||||
case "fish":
|
||||
prefix = "/usr/share/fish/vendor_completions.d"
|
||||
name += ".fish"
|
||||
}
|
||||
|
||||
path := filepath.Join(hc.Env.Get("pkgdir").Str, prefix, name)
|
||||
|
||||
err := os.MkdirAll(filepath.Dir(path), 0o755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dst, err := os.OpenFile(path, os.O_TRUNC|os.O_CREATE|os.O_RDWR, 0o644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dst.Close()
|
||||
|
||||
_, err = io.Copy(dst, hc.Stdin)
|
||||
return err
|
||||
}
|
||||
|
||||
func installLibraryCmd(hc interp.HandlerContext, cmd string, args []string) error {
|
||||
prefix := getLibPrefix(hc)
|
||||
fn := installHelperCmd(prefix, 0o755)
|
||||
return fn(hc, cmd, args)
|
||||
}
|
||||
|
||||
// See https://wiki.debian.org/Multiarch/Tuples
|
||||
var multiarchTupleMap = map[string]string{
|
||||
"386": "i386-linux-gnu",
|
||||
"amd64": "x86_64-linux-gnu",
|
||||
"arm5": "arm-linux-gnueabi",
|
||||
"arm6": "arm-linux-gnueabihf",
|
||||
"arm7": "arm-linux-gnueabihf",
|
||||
"arm64": "aarch64-linux-gnu",
|
||||
"mips": "mips-linux-gnu",
|
||||
"mipsle": "mipsel-linux-gnu",
|
||||
"mips64": "mips64-linux-gnuabi64",
|
||||
"mips64le": "mips64el-linux-gnuabi64",
|
||||
"ppc64": "powerpc64-linux-gnu",
|
||||
"ppc64le": "powerpc64le-linux-gnu",
|
||||
"s390x": "s390x-linux-gnu",
|
||||
"riscv64": "riscv64-linux-gnu",
|
||||
"loong64": "loongarch64-linux-gnu",
|
||||
}
|
||||
|
||||
// usrLibDistros is a list of distros that don't support
|
||||
// /usr/lib64, and must use /usr/lib
|
||||
var usrLibDistros = []string{
|
||||
"arch",
|
||||
"alpine",
|
||||
"void",
|
||||
"chimera",
|
||||
}
|
||||
|
||||
// Based on CMake's GNUInstallDirs
|
||||
func getLibPrefix(hc interp.HandlerContext) string {
|
||||
if dir, ok := os.LookupEnv("LURE_LIB_DIR"); ok {
|
||||
return dir
|
||||
}
|
||||
|
||||
out := "/usr/lib"
|
||||
|
||||
distroID := hc.Env.Get("DISTRO_ID").Str
|
||||
distroLike := strings.Split(hc.Env.Get("DISTRO_ID_LIKE").Str, " ")
|
||||
|
||||
for _, usrLibDistro := range usrLibDistros {
|
||||
if distroID == usrLibDistro || slices.Contains(distroLike, usrLibDistro) {
|
||||
return out
|
||||
}
|
||||
}
|
||||
|
||||
wordSize := unsafe.Sizeof(uintptr(0))
|
||||
if wordSize == 8 {
|
||||
out = "/usr/lib64"
|
||||
}
|
||||
|
||||
architecture := hc.Env.Get("ARCH").Str
|
||||
|
||||
if distroID == "debian" || slices.Contains(distroLike, "debian") {
|
||||
triple, ok := multiarchTupleMap[architecture]
|
||||
if ok {
|
||||
out = filepath.Join("/usr/lib", triple)
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func helperInstall(from, to string, perms os.FileMode) error {
|
||||
err := os.MkdirAll(filepath.Dir(to), 0o755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
src, err := os.Open(from)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
dst, err := os.OpenFile(to, os.O_TRUNC|os.O_CREATE|os.O_RDWR, perms)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dst.Close()
|
||||
|
||||
_, err = io.Copy(dst, src)
|
||||
return err
|
||||
}
|
||||
40
info.go
40
info.go
@@ -23,12 +23,7 @@ import (
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
"go.arsenm.dev/lure/distro"
|
||||
"go.arsenm.dev/lure/internal/shutils"
|
||||
"go.arsenm.dev/lure/internal/shutils/decoder"
|
||||
"gopkg.in/yaml.v3"
|
||||
"mvdan.cc/sh/v3/expand"
|
||||
"mvdan.cc/sh/v3/interp"
|
||||
"mvdan.cc/sh/v3/syntax"
|
||||
)
|
||||
|
||||
func infoCmd(c *cli.Context) error {
|
||||
@@ -50,40 +45,9 @@ func infoCmd(c *cli.Context) error {
|
||||
// if multiple are matched, only use the first one
|
||||
script := found[0]
|
||||
|
||||
fl, err := os.Open(script)
|
||||
vars, err := getBuildVars(c.Context, script, info)
|
||||
if err != nil {
|
||||
log.Fatal("Error opening script").Err(err).Send()
|
||||
}
|
||||
|
||||
file, err := syntax.NewParser().Parse(fl, "lure.sh")
|
||||
if err != nil {
|
||||
log.Fatal("Error parsing script").Err(err).Send()
|
||||
}
|
||||
|
||||
fl.Close()
|
||||
|
||||
runner, err := interp.New(
|
||||
interp.Env(expand.ListEnviron()),
|
||||
interp.ExecHandler(shutils.NopExec),
|
||||
interp.StatHandler(shutils.NopStat),
|
||||
interp.OpenHandler(shutils.NopOpen),
|
||||
interp.ReadDirHandler(shutils.NopReadDir),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal("Error creating runner").Err(err).Send()
|
||||
}
|
||||
|
||||
err = runner.Run(c.Context, file)
|
||||
if err != nil {
|
||||
log.Fatal("Error running script").Err(err).Send()
|
||||
}
|
||||
|
||||
dec := decoder.New(info, runner)
|
||||
|
||||
var vars BuildVars
|
||||
err = dec.DecodeVars(&vars)
|
||||
if err != nil {
|
||||
log.Fatal("Error decoding script variables").Err(err).Send()
|
||||
log.Fatal("Error getting build variables").Err(err).Send()
|
||||
}
|
||||
|
||||
err = yaml.NewEncoder(os.Stdout).Encode(vars)
|
||||
|
||||
18
install.go
18
install.go
@@ -36,21 +36,23 @@ func installCmd(c *cli.Context) error {
|
||||
log.Fatal("Unable to detect supported package manager on system").Send()
|
||||
}
|
||||
|
||||
installPkgs(c.Context, args.Slice(), mgr)
|
||||
installPkgs(c.Context, args.Slice(), mgr, true)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func installPkgs(ctx context.Context, pkgs []string, mgr manager.Manager) {
|
||||
err := pullRepos(ctx)
|
||||
if err != nil {
|
||||
log.Fatal("Error pulling repositories").Err(err).Send()
|
||||
func installPkgs(ctx context.Context, pkgs []string, mgr manager.Manager, pull bool) {
|
||||
if pull {
|
||||
err := pullRepos(ctx)
|
||||
if err != nil {
|
||||
log.Fatal("Error pulling repositories").Err(err).Send()
|
||||
}
|
||||
}
|
||||
|
||||
scripts, notFound := findPkgs(pkgs)
|
||||
|
||||
if len(notFound) > 0 {
|
||||
err = mgr.Install(notFound...)
|
||||
err := mgr.Install(nil, notFound...)
|
||||
if err != nil {
|
||||
log.Fatal("Error installing native packages").Err(err).Send()
|
||||
}
|
||||
@@ -66,7 +68,7 @@ func installScripts(ctx context.Context, mgr manager.Manager, scripts []string)
|
||||
log.Fatal("Error building package").Err(err).Send()
|
||||
}
|
||||
|
||||
err = mgr.InstallLocal(builtPkgs...)
|
||||
err = mgr.InstallLocal(nil, builtPkgs...)
|
||||
if err != nil {
|
||||
log.Fatal("Error installing package").Err(err).Send()
|
||||
}
|
||||
@@ -84,7 +86,7 @@ func removeCmd(c *cli.Context) error {
|
||||
log.Fatal("Unable to detect supported package manager on system").Send()
|
||||
}
|
||||
|
||||
err := mgr.Remove(c.Args().Slice()...)
|
||||
err := mgr.Remove(nil, c.Args().Slice()...)
|
||||
if err != nil {
|
||||
log.Fatal("Error removing packages").Err(err).Send()
|
||||
}
|
||||
|
||||
44
internal/cpu/cpu.go
Normal file
44
internal/cpu/cpu.go
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2022 Arsen Musayelyan
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package cpu
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/sys/cpu"
|
||||
)
|
||||
|
||||
// ARMVariant checks which variant of ARM lure is running
|
||||
// on, by using the same detection method as Go itself
|
||||
func ARMVariant() string {
|
||||
armEnv := os.Getenv("LURE_ARM_VARIANT")
|
||||
// ensure value has "arm" prefix, such as arm5 or arm6
|
||||
if strings.HasPrefix(armEnv, "arm") {
|
||||
return armEnv
|
||||
}
|
||||
|
||||
if cpu.ARM.HasVFPv3 {
|
||||
return "arm7"
|
||||
} else if cpu.ARM.HasVFP {
|
||||
return "arm6"
|
||||
} else {
|
||||
return "arm5"
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"go.arsenm.dev/lure/distro"
|
||||
"go.arsenm.dev/lure/internal/cpu"
|
||||
"golang.org/x/exp/slices"
|
||||
"mvdan.cc/sh/v3/expand"
|
||||
"mvdan.cc/sh/v3/interp"
|
||||
@@ -46,14 +47,17 @@ func (nfe VarNotFoundError) Error() string {
|
||||
|
||||
// Decoder provides methods for decoding variable values
|
||||
type Decoder struct {
|
||||
info *distro.OSRelease
|
||||
runner *interp.Runner
|
||||
info *distro.OSRelease
|
||||
runner *interp.Runner
|
||||
// Enable distro overrides (true by default)
|
||||
Overrides bool
|
||||
// Enable using like distros for overrides (true by default)
|
||||
LikeDistros bool
|
||||
}
|
||||
|
||||
// New creates a new variable decoder
|
||||
func New(info *distro.OSRelease, runner *interp.Runner) *Decoder {
|
||||
return &Decoder{info, runner, true}
|
||||
return &Decoder{info, runner, true, true}
|
||||
}
|
||||
|
||||
// DecodeVar decodes a variable to val using reflection.
|
||||
@@ -138,7 +142,7 @@ func (d *Decoder) DecodeVars(val any) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type ScriptFunc func(ctx context.Context, sir string, args ...string) error
|
||||
type ScriptFunc func(ctx context.Context, opts ...interp.RunnerOption) error
|
||||
|
||||
// GetFunc returns a function corresponding to a bash function
|
||||
// with the given name
|
||||
@@ -148,10 +152,11 @@ func (d *Decoder) GetFunc(name string) (ScriptFunc, bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return func(ctx context.Context, dir string, args ...string) error {
|
||||
return func(ctx context.Context, opts ...interp.RunnerOption) error {
|
||||
sub := d.runner.Subshell()
|
||||
interp.Params(args...)(sub)
|
||||
interp.Dir(dir)(sub)
|
||||
for _, opt := range opts {
|
||||
opt(sub)
|
||||
}
|
||||
return sub.Run(ctx, fn)
|
||||
}, true
|
||||
}
|
||||
@@ -196,10 +201,35 @@ func (d *Decoder) genPossibleNames(name string) []string {
|
||||
return []string{name}
|
||||
}
|
||||
|
||||
return []string{
|
||||
fmt.Sprintf("%s_%s_%s", name, runtime.GOARCH, d.info.ID),
|
||||
fmt.Sprintf("%s_%s", name, d.info.ID),
|
||||
fmt.Sprintf("%s_%s", name, runtime.GOARCH),
|
||||
name,
|
||||
architectures := []string{runtime.GOARCH}
|
||||
|
||||
if runtime.GOARCH == "arm" {
|
||||
// More specific goes first
|
||||
architectures[0] = cpu.ARMVariant()
|
||||
architectures = append(architectures, "arm")
|
||||
}
|
||||
|
||||
distros := []string{d.info.ID}
|
||||
if d.LikeDistros {
|
||||
distros = append(distros, d.info.Like...)
|
||||
}
|
||||
|
||||
var out []string
|
||||
for _, arch := range architectures {
|
||||
for _, distro := range distros {
|
||||
out = append(
|
||||
out,
|
||||
fmt.Sprintf("%s_%s_%s", name, arch, distro),
|
||||
fmt.Sprintf("%s_%s", name, distro),
|
||||
)
|
||||
}
|
||||
out = append(out, fmt.Sprintf("%s_%s", name, arch))
|
||||
}
|
||||
out = append(out, name)
|
||||
|
||||
for index, item := range out {
|
||||
out[index] = strings.ReplaceAll(item, "-", "_")
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -20,21 +20,34 @@ package shutils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"mvdan.cc/sh/v3/interp"
|
||||
)
|
||||
|
||||
type ExecFuncs map[string]func(interp.HandlerContext, []string) uint8
|
||||
func InsufficientArgsError(cmd string, exp, got int) error {
|
||||
argsWord := "arguments"
|
||||
if exp == 1 {
|
||||
argsWord = "argument"
|
||||
}
|
||||
|
||||
return fmt.Errorf("%s: command requires at least %d %s, got %d", cmd, exp, argsWord, got)
|
||||
}
|
||||
|
||||
type ExecFunc func(hc interp.HandlerContext, name string, args []string) error
|
||||
|
||||
type ExecFuncs map[string]ExecFunc
|
||||
|
||||
func (ef ExecFuncs) ExecHandler(ctx context.Context, args []string) error {
|
||||
name := args[0]
|
||||
|
||||
if fn, ok := ef[name]; ok {
|
||||
hctx := interp.HandlerCtx(ctx)
|
||||
ec := fn(hctx, args)
|
||||
if ec != 0 {
|
||||
return interp.NewExitStatus(ec)
|
||||
if len(args) > 1 {
|
||||
return fn(hctx, args[0], args[1:])
|
||||
} else {
|
||||
return fn(hctx, args[0], nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
func NopReadDir(context.Context, string) ([]os.FileInfo, error) {
|
||||
@@ -34,7 +33,7 @@ func NopStat(context.Context, string, bool) (os.FileInfo, error) {
|
||||
}
|
||||
|
||||
func NopExec(context.Context, []string) error {
|
||||
return exec.ErrNotFound
|
||||
return nil
|
||||
}
|
||||
|
||||
func NopOpen(context.Context, string, int, os.FileMode) (io.ReadWriteCloser, error) {
|
||||
@@ -44,11 +43,11 @@ func NopOpen(context.Context, string, int, os.FileMode) (io.ReadWriteCloser, err
|
||||
type NopRWC struct{}
|
||||
|
||||
func (NopRWC) Read([]byte) (int, error) {
|
||||
return 0, os.ErrClosed
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
func (NopRWC) Write([]byte) (int, error) {
|
||||
return 0, os.ErrClosed
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
func (NopRWC) Close() error {
|
||||
|
||||
59
list.go
59
list.go
@@ -1,16 +1,28 @@
|
||||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2022 Arsen Musayelyan
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
"go.arsenm.dev/lure/distro"
|
||||
"go.arsenm.dev/lure/internal/shutils"
|
||||
"go.arsenm.dev/lure/internal/shutils/decoder"
|
||||
"mvdan.cc/sh/v3/expand"
|
||||
"mvdan.cc/sh/v3/interp"
|
||||
"mvdan.cc/sh/v3/syntax"
|
||||
)
|
||||
|
||||
func listCmd(c *cli.Context) error {
|
||||
@@ -25,40 +37,9 @@ func listCmd(c *cli.Context) error {
|
||||
}
|
||||
|
||||
for _, script := range pkgs {
|
||||
fl, err := os.Open(script)
|
||||
vars, err := getBuildVars(c.Context, script, info)
|
||||
if err != nil {
|
||||
log.Fatal("Error opening script").Err(err).Send()
|
||||
}
|
||||
|
||||
file, err := syntax.NewParser().Parse(fl, "lure.sh")
|
||||
if err != nil {
|
||||
log.Fatal("Error parsing script").Err(err).Send()
|
||||
}
|
||||
|
||||
fl.Close()
|
||||
|
||||
runner, err := interp.New(
|
||||
interp.Env(expand.ListEnviron()),
|
||||
interp.ExecHandler(shutils.NopExec),
|
||||
interp.StatHandler(shutils.NopStat),
|
||||
interp.OpenHandler(shutils.NopOpen),
|
||||
interp.ReadDirHandler(shutils.NopReadDir),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal("Error creating runner").Err(err).Send()
|
||||
}
|
||||
|
||||
err = runner.Run(c.Context, file)
|
||||
if err != nil {
|
||||
log.Fatal("Error running script").Err(err).Send()
|
||||
}
|
||||
|
||||
dec := decoder.New(info, runner)
|
||||
|
||||
var vars BuildVars
|
||||
err = dec.DecodeVars(&vars)
|
||||
if err != nil {
|
||||
log.Fatal("Error decoding script variables").Err(err).Send()
|
||||
log.Fatal("Error getting build variables").Err(err).Send()
|
||||
}
|
||||
|
||||
fmt.Println(vars.Name, vars.Version)
|
||||
|
||||
10
main.go
10
main.go
@@ -128,6 +128,11 @@ func main() {
|
||||
Aliases: []string{"ref"},
|
||||
Action: refreshCmd,
|
||||
},
|
||||
{
|
||||
Name: "version",
|
||||
Usage: "Display the current LURE version and exit",
|
||||
Action: displayVersion,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -136,3 +141,8 @@ func main() {
|
||||
log.Error("Error while running app").Err(err).Send()
|
||||
}
|
||||
}
|
||||
|
||||
func displayVersion(c *cli.Context) error {
|
||||
print(version)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -47,8 +47,9 @@ func (a *APK) SetRootCmd(s string) {
|
||||
a.rootCmd = s
|
||||
}
|
||||
|
||||
func (a *APK) Sync() error {
|
||||
cmd := exec.Command(getRootCmd(a.rootCmd), "apk", "update")
|
||||
func (a *APK) Sync(opts *Opts) error {
|
||||
opts = ensureOpts(opts)
|
||||
cmd := a.getCmd(opts, "apk", "update")
|
||||
setCmdEnv(cmd)
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
@@ -57,8 +58,9 @@ func (a *APK) Sync() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *APK) Install(pkgs ...string) error {
|
||||
cmd := exec.Command(getRootCmd(a.rootCmd), "apk", "add")
|
||||
func (a *APK) Install(opts *Opts, pkgs ...string) error {
|
||||
opts = ensureOpts(opts)
|
||||
cmd := a.getCmd(opts, "apk", "add")
|
||||
cmd.Args = append(cmd.Args, pkgs...)
|
||||
setCmdEnv(cmd)
|
||||
err := cmd.Run()
|
||||
@@ -68,8 +70,9 @@ func (a *APK) Install(pkgs ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *APK) InstallLocal(pkgs ...string) error {
|
||||
cmd := exec.Command(getRootCmd(a.rootCmd), "apk", "add", "--allow-untrusted")
|
||||
func (a *APK) InstallLocal(opts *Opts, pkgs ...string) error {
|
||||
opts = ensureOpts(opts)
|
||||
cmd := a.getCmd(opts, "apk", "add", "--allow-untrusted")
|
||||
cmd.Args = append(cmd.Args, pkgs...)
|
||||
setCmdEnv(cmd)
|
||||
err := cmd.Run()
|
||||
@@ -79,8 +82,9 @@ func (a *APK) InstallLocal(pkgs ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *APK) Remove(pkgs ...string) error {
|
||||
cmd := exec.Command(getRootCmd(a.rootCmd), "apt", "del")
|
||||
func (a *APK) Remove(opts *Opts, pkgs ...string) error {
|
||||
opts = ensureOpts(opts)
|
||||
cmd := a.getCmd(opts, "apk", "del")
|
||||
cmd.Args = append(cmd.Args, pkgs...)
|
||||
setCmdEnv(cmd)
|
||||
err := cmd.Run()
|
||||
@@ -90,8 +94,9 @@ func (a *APK) Remove(pkgs ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *APK) Upgrade(pkgs ...string) error {
|
||||
cmd := exec.Command(getRootCmd(a.rootCmd), "apk", "upgrade")
|
||||
func (a *APK) Upgrade(opts *Opts, pkgs ...string) error {
|
||||
opts = ensureOpts(opts)
|
||||
cmd := a.getCmd(opts, "apk", "upgrade")
|
||||
cmd.Args = append(cmd.Args, pkgs...)
|
||||
setCmdEnv(cmd)
|
||||
err := cmd.Run()
|
||||
@@ -101,13 +106,21 @@ func (a *APK) Upgrade(pkgs ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *APK) UpgradeAll() error {
|
||||
return a.Upgrade()
|
||||
func (a *APK) UpgradeAll(opts *Opts) error {
|
||||
opts = ensureOpts(opts)
|
||||
return a.Upgrade(opts)
|
||||
}
|
||||
|
||||
func (a *APK) ListInstalled() (map[string]string, error) {
|
||||
func (a *APK) ListInstalled(opts *Opts) (map[string]string, error) {
|
||||
opts = ensureOpts(opts)
|
||||
out := map[string]string{}
|
||||
cmd := exec.Command(getRootCmd(a.rootCmd), "apk", "list", "-I")
|
||||
|
||||
var cmd *exec.Cmd
|
||||
if opts.AsRoot {
|
||||
cmd = exec.Command(getRootCmd(a.rootCmd), "apk", "list", "-I")
|
||||
} else {
|
||||
cmd = exec.Command("apk", "list", "-I")
|
||||
}
|
||||
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
@@ -141,3 +154,19 @@ func (a *APK) ListInstalled() (map[string]string, error) {
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (a *APK) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd {
|
||||
var cmd *exec.Cmd
|
||||
if opts.AsRoot {
|
||||
cmd = exec.Command(getRootCmd(a.rootCmd), mgrCmd)
|
||||
cmd.Args = append(cmd.Args, args...)
|
||||
} else {
|
||||
cmd = exec.Command(mgrCmd, args...)
|
||||
}
|
||||
|
||||
if !opts.NoConfirm {
|
||||
cmd.Args = append(cmd.Args, "-i")
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -47,8 +47,9 @@ func (a *APT) SetRootCmd(s string) {
|
||||
a.rootCmd = s
|
||||
}
|
||||
|
||||
func (a *APT) Sync() error {
|
||||
cmd := exec.Command(getRootCmd(a.rootCmd), "apt", "update", "-y")
|
||||
func (a *APT) Sync(opts *Opts) error {
|
||||
opts = ensureOpts(opts)
|
||||
cmd := a.getCmd(opts, "apt", "update")
|
||||
setCmdEnv(cmd)
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
@@ -57,8 +58,9 @@ func (a *APT) Sync() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *APT) Install(pkgs ...string) error {
|
||||
cmd := exec.Command(getRootCmd(a.rootCmd), "apt", "install", "-y")
|
||||
func (a *APT) Install(opts *Opts, pkgs ...string) error {
|
||||
opts = ensureOpts(opts)
|
||||
cmd := a.getCmd(opts, "apt", "install")
|
||||
cmd.Args = append(cmd.Args, pkgs...)
|
||||
setCmdEnv(cmd)
|
||||
err := cmd.Run()
|
||||
@@ -68,12 +70,14 @@ func (a *APT) Install(pkgs ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *APT) InstallLocal(pkgs ...string) error {
|
||||
return a.Install(pkgs...)
|
||||
func (a *APT) InstallLocal(opts *Opts, pkgs ...string) error {
|
||||
opts = ensureOpts(opts)
|
||||
return a.Install(opts, pkgs...)
|
||||
}
|
||||
|
||||
func (a *APT) Remove(pkgs ...string) error {
|
||||
cmd := exec.Command(getRootCmd(a.rootCmd), "apt", "remove", "-y")
|
||||
func (a *APT) Remove(opts *Opts, pkgs ...string) error {
|
||||
opts = ensureOpts(opts)
|
||||
cmd := a.getCmd(opts, "apt", "remove")
|
||||
cmd.Args = append(cmd.Args, pkgs...)
|
||||
setCmdEnv(cmd)
|
||||
err := cmd.Run()
|
||||
@@ -83,12 +87,14 @@ func (a *APT) Remove(pkgs ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *APT) Upgrade(pkgs ...string) error {
|
||||
return a.Install(pkgs...)
|
||||
func (a *APT) Upgrade(opts *Opts, pkgs ...string) error {
|
||||
opts = ensureOpts(opts)
|
||||
return a.Install(opts, pkgs...)
|
||||
}
|
||||
|
||||
func (a *APT) UpgradeAll() error {
|
||||
cmd := exec.Command(getRootCmd(a.rootCmd), "apt", "upgrade", "-y")
|
||||
func (a *APT) UpgradeAll(opts *Opts) error {
|
||||
opts = ensureOpts(opts)
|
||||
cmd := a.getCmd(opts, "apt", "upgrade")
|
||||
setCmdEnv(cmd)
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
@@ -97,9 +103,16 @@ func (a *APT) UpgradeAll() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *APT) ListInstalled() (map[string]string, error) {
|
||||
func (a *APT) ListInstalled(opts *Opts) (map[string]string, error) {
|
||||
opts = ensureOpts(opts)
|
||||
out := map[string]string{}
|
||||
cmd := exec.Command(getRootCmd(a.rootCmd), "dpkg-query", "-f", "${Package}\u200b${Version}\\n", "-W")
|
||||
|
||||
var cmd *exec.Cmd
|
||||
if opts.AsRoot {
|
||||
cmd = exec.Command(getRootCmd(a.rootCmd), "dpkg-query", "-f", "${Package}\u200b${Version}\\n", "-W")
|
||||
} else {
|
||||
cmd = exec.Command("dpkg-query", "-f", "${Package}\u200b${Version}\\n", "-W")
|
||||
}
|
||||
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
@@ -127,3 +140,19 @@ func (a *APT) ListInstalled() (map[string]string, error) {
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (a *APT) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd {
|
||||
var cmd *exec.Cmd
|
||||
if opts.AsRoot {
|
||||
cmd = exec.Command(getRootCmd(a.rootCmd), mgrCmd)
|
||||
cmd.Args = append(cmd.Args, args...)
|
||||
} else {
|
||||
cmd = exec.Command(mgrCmd, args...)
|
||||
}
|
||||
|
||||
if opts.NoConfirm {
|
||||
cmd.Args = append(cmd.Args, "-y")
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -47,8 +47,9 @@ func (d *DNF) SetRootCmd(s string) {
|
||||
d.rootCmd = s
|
||||
}
|
||||
|
||||
func (d *DNF) Sync() error {
|
||||
cmd := exec.Command(getRootCmd(d.rootCmd), "dnf", "upgrade", "--assumeno")
|
||||
func (d *DNF) Sync(opts *Opts) error {
|
||||
opts = ensureOpts(opts)
|
||||
cmd := d.getCmd(opts, "dnf", "upgrade")
|
||||
setCmdEnv(cmd)
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
@@ -57,8 +58,9 @@ func (d *DNF) Sync() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNF) Install(pkgs ...string) error {
|
||||
cmd := exec.Command(getRootCmd(d.rootCmd), "dnf", "install", "-y")
|
||||
func (d *DNF) Install(opts *Opts, pkgs ...string) error {
|
||||
opts = ensureOpts(opts)
|
||||
cmd := d.getCmd(opts, "dnf", "install")
|
||||
cmd.Args = append(cmd.Args, pkgs...)
|
||||
setCmdEnv(cmd)
|
||||
err := cmd.Run()
|
||||
@@ -68,12 +70,14 @@ func (d *DNF) Install(pkgs ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNF) InstallLocal(pkgs ...string) error {
|
||||
return d.Install(pkgs...)
|
||||
func (d *DNF) InstallLocal(opts *Opts, pkgs ...string) error {
|
||||
opts = ensureOpts(opts)
|
||||
return d.Install(opts, pkgs...)
|
||||
}
|
||||
|
||||
func (d *DNF) Remove(pkgs ...string) error {
|
||||
cmd := exec.Command(getRootCmd(d.rootCmd), "dnf", "remove", "-y")
|
||||
func (d *DNF) Remove(opts *Opts, pkgs ...string) error {
|
||||
opts = ensureOpts(opts)
|
||||
cmd := d.getCmd(opts, "dnf", "remove")
|
||||
cmd.Args = append(cmd.Args, pkgs...)
|
||||
setCmdEnv(cmd)
|
||||
err := cmd.Run()
|
||||
@@ -83,8 +87,9 @@ func (d *DNF) Remove(pkgs ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNF) Upgrade(pkgs ...string) error {
|
||||
cmd := exec.Command(getRootCmd(d.rootCmd), "dnf", "upgrade", "-y")
|
||||
func (d *DNF) Upgrade(opts *Opts, pkgs ...string) error {
|
||||
opts = ensureOpts(opts)
|
||||
cmd := d.getCmd(opts, "dnf", "upgrade")
|
||||
cmd.Args = append(cmd.Args, pkgs...)
|
||||
setCmdEnv(cmd)
|
||||
err := cmd.Run()
|
||||
@@ -94,8 +99,9 @@ func (d *DNF) Upgrade(pkgs ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNF) UpgradeAll() error {
|
||||
cmd := exec.Command(getRootCmd(d.rootCmd), "dnf", "upgrade", "-y")
|
||||
func (d *DNF) UpgradeAll(opts *Opts) error {
|
||||
opts = ensureOpts(opts)
|
||||
cmd := d.getCmd(opts, "dnf", "upgrade")
|
||||
setCmdEnv(cmd)
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
@@ -104,9 +110,16 @@ func (d *DNF) UpgradeAll() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNF) ListInstalled() (map[string]string, error) {
|
||||
func (d *DNF) ListInstalled(opts *Opts) (map[string]string, error) {
|
||||
opts = ensureOpts(opts)
|
||||
out := map[string]string{}
|
||||
cmd := exec.Command(getRootCmd(d.rootCmd), "rpm", "-qa", "--queryformat", "%{NAME}\u200b%|EPOCH?{%{EPOCH}:}:{}|%{VERSION}-%{RELEASE}\\n")
|
||||
|
||||
var cmd *exec.Cmd
|
||||
if opts.AsRoot {
|
||||
cmd = exec.Command(getRootCmd(d.rootCmd), "rpm", "-qa", "--queryformat", "%{NAME}\u200b%|EPOCH?{%{EPOCH}:}:{}|%{VERSION}-%{RELEASE}\\n")
|
||||
} else {
|
||||
cmd = exec.Command("rpm", "-qa", "--queryformat", "%{NAME}\u200b%|EPOCH?{%{EPOCH}:}:{}|%{VERSION}-%{RELEASE}\\n")
|
||||
}
|
||||
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
@@ -124,6 +137,7 @@ func (d *DNF) ListInstalled() (map[string]string, error) {
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
version = strings.TrimPrefix(version, "0:")
|
||||
out[name] = version
|
||||
}
|
||||
|
||||
@@ -134,3 +148,19 @@ func (d *DNF) ListInstalled() (map[string]string, error) {
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (d *DNF) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd {
|
||||
var cmd *exec.Cmd
|
||||
if opts.AsRoot {
|
||||
cmd = exec.Command(getRootCmd(d.rootCmd), mgrCmd)
|
||||
cmd.Args = append(cmd.Args, args...)
|
||||
} else {
|
||||
cmd = exec.Command(mgrCmd, args...)
|
||||
}
|
||||
|
||||
if opts.NoConfirm {
|
||||
cmd.Args = append(cmd.Args, "-y")
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -23,6 +23,16 @@ import (
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
type Opts struct {
|
||||
AsRoot bool
|
||||
NoConfirm bool
|
||||
}
|
||||
|
||||
var DefaultOpts = &Opts{
|
||||
AsRoot: true,
|
||||
NoConfirm: false,
|
||||
}
|
||||
|
||||
// DefaultRootCmd is the command used for privilege elevation by default
|
||||
var DefaultRootCmd = "sudo"
|
||||
|
||||
@@ -52,19 +62,19 @@ type Manager interface {
|
||||
// Sets the command used to elevate privileges. Defaults to DefaultRootCmd.
|
||||
SetRootCmd(string)
|
||||
// Sync fetches repositories without installing anything
|
||||
Sync() error
|
||||
Sync(*Opts) error
|
||||
// Install installs packages
|
||||
Install(...string) error
|
||||
Install(*Opts, ...string) error
|
||||
// Remove uninstalls packages
|
||||
Remove(...string) error
|
||||
Remove(*Opts, ...string) error
|
||||
// Upgrade upgrades packages
|
||||
Upgrade(...string) error
|
||||
Upgrade(*Opts, ...string) error
|
||||
// InstallLocal installs packages from local files rather than repos
|
||||
InstallLocal(...string) error
|
||||
InstallLocal(*Opts, ...string) error
|
||||
// UpgradeAll upgrades all packages
|
||||
UpgradeAll() error
|
||||
UpgradeAll(*Opts) error
|
||||
// ListInstalled returns all installed packages mapped to their versions
|
||||
ListInstalled() (map[string]string, error)
|
||||
ListInstalled(*Opts) (map[string]string, error)
|
||||
}
|
||||
|
||||
// Detect returns the package manager detected on the system
|
||||
@@ -101,3 +111,10 @@ func setCmdEnv(cmd *exec.Cmd) {
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
}
|
||||
|
||||
func ensureOpts(opts *Opts) *Opts {
|
||||
if opts == nil {
|
||||
return DefaultOpts
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
@@ -47,8 +47,9 @@ func (p *Pacman) SetRootCmd(s string) {
|
||||
p.rootCmd = s
|
||||
}
|
||||
|
||||
func (p *Pacman) Sync() error {
|
||||
cmd := exec.Command(getRootCmd(p.rootCmd), "pacman", "--noconfirm", "-Sy")
|
||||
func (p *Pacman) Sync(opts *Opts) error {
|
||||
opts = ensureOpts(opts)
|
||||
cmd := p.getCmd(opts, "pacman", "-Sy")
|
||||
setCmdEnv(cmd)
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
@@ -57,8 +58,9 @@ func (p *Pacman) Sync() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Pacman) Install(pkgs ...string) error {
|
||||
cmd := exec.Command(getRootCmd(p.rootCmd), "pacman", "--noconfirm", "-S")
|
||||
func (p *Pacman) Install(opts *Opts, pkgs ...string) error {
|
||||
opts = ensureOpts(opts)
|
||||
cmd := p.getCmd(opts, "pacman", "-S", "--needed")
|
||||
cmd.Args = append(cmd.Args, pkgs...)
|
||||
setCmdEnv(cmd)
|
||||
err := cmd.Run()
|
||||
@@ -68,8 +70,9 @@ func (p *Pacman) Install(pkgs ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Pacman) InstallLocal(pkgs ...string) error {
|
||||
cmd := exec.Command(getRootCmd(p.rootCmd), "pacman", "--noconfirm", "-U")
|
||||
func (p *Pacman) InstallLocal(opts *Opts, pkgs ...string) error {
|
||||
opts = ensureOpts(opts)
|
||||
cmd := p.getCmd(opts, "pacman", "-U", "--needed")
|
||||
cmd.Args = append(cmd.Args, pkgs...)
|
||||
setCmdEnv(cmd)
|
||||
err := cmd.Run()
|
||||
@@ -79,8 +82,9 @@ func (p *Pacman) InstallLocal(pkgs ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Pacman) Remove(pkgs ...string) error {
|
||||
cmd := exec.Command(getRootCmd(p.rootCmd), "pacman", "--noconfirm", "-R")
|
||||
func (p *Pacman) Remove(opts *Opts, pkgs ...string) error {
|
||||
opts = ensureOpts(opts)
|
||||
cmd := p.getCmd(opts, "pacman", "-R")
|
||||
cmd.Args = append(cmd.Args, pkgs...)
|
||||
setCmdEnv(cmd)
|
||||
err := cmd.Run()
|
||||
@@ -90,12 +94,14 @@ func (p *Pacman) Remove(pkgs ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Pacman) Upgrade(pkgs ...string) error {
|
||||
return p.Install(pkgs...)
|
||||
func (p *Pacman) Upgrade(opts *Opts, pkgs ...string) error {
|
||||
opts = ensureOpts(opts)
|
||||
return p.Install(opts, pkgs...)
|
||||
}
|
||||
|
||||
func (p *Pacman) UpgradeAll() error {
|
||||
cmd := exec.Command(getRootCmd(p.rootCmd), "pacman", "--noconfirm", "-Su")
|
||||
func (p *Pacman) UpgradeAll(opts *Opts) error {
|
||||
opts = ensureOpts(opts)
|
||||
cmd := p.getCmd(opts, "pacman", "-Su")
|
||||
setCmdEnv(cmd)
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
@@ -104,9 +110,16 @@ func (p *Pacman) UpgradeAll() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Pacman) ListInstalled() (map[string]string, error) {
|
||||
func (p *Pacman) ListInstalled(opts *Opts) (map[string]string, error) {
|
||||
opts = ensureOpts(opts)
|
||||
out := map[string]string{}
|
||||
cmd := exec.Command(getRootCmd(p.rootCmd), "pacman", "-Q")
|
||||
|
||||
var cmd *exec.Cmd
|
||||
if opts.AsRoot {
|
||||
cmd = exec.Command(getRootCmd(p.rootCmd), "pacman", "-Q")
|
||||
} else {
|
||||
cmd = exec.Command("pacman", "-Q")
|
||||
}
|
||||
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
@@ -134,3 +147,19 @@ func (p *Pacman) ListInstalled() (map[string]string, error) {
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (p *Pacman) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd {
|
||||
var cmd *exec.Cmd
|
||||
if opts.AsRoot {
|
||||
cmd = exec.Command(getRootCmd(p.rootCmd), mgrCmd)
|
||||
cmd.Args = append(cmd.Args, args...)
|
||||
} else {
|
||||
cmd = exec.Command(mgrCmd, args...)
|
||||
}
|
||||
|
||||
if opts.NoConfirm {
|
||||
cmd.Args = append(cmd.Args, "--noconfirm")
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -47,8 +47,9 @@ func (y *YUM) SetRootCmd(s string) {
|
||||
y.rootCmd = s
|
||||
}
|
||||
|
||||
func (y *YUM) Sync() error {
|
||||
cmd := exec.Command(getRootCmd(y.rootCmd), "yum", "upgrade", "--assumeno")
|
||||
func (y *YUM) Sync(opts *Opts) error {
|
||||
opts = ensureOpts(opts)
|
||||
cmd := y.getCmd(opts, "yum", "upgrade")
|
||||
setCmdEnv(cmd)
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
@@ -57,8 +58,9 @@ func (y *YUM) Sync() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (y *YUM) Install(pkgs ...string) error {
|
||||
cmd := exec.Command(getRootCmd(y.rootCmd), "yum", "install", "-y")
|
||||
func (y *YUM) Install(opts *Opts, pkgs ...string) error {
|
||||
opts = ensureOpts(opts)
|
||||
cmd := y.getCmd(opts, "yum", "install")
|
||||
cmd.Args = append(cmd.Args, pkgs...)
|
||||
setCmdEnv(cmd)
|
||||
err := cmd.Run()
|
||||
@@ -68,12 +70,14 @@ func (y *YUM) Install(pkgs ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (y *YUM) InstallLocal(pkgs ...string) error {
|
||||
return y.Install(pkgs...)
|
||||
func (y *YUM) InstallLocal(opts *Opts, pkgs ...string) error {
|
||||
opts = ensureOpts(opts)
|
||||
return y.Install(opts, pkgs...)
|
||||
}
|
||||
|
||||
func (y *YUM) Remove(pkgs ...string) error {
|
||||
cmd := exec.Command(getRootCmd(y.rootCmd), "yum", "remove", "-y")
|
||||
func (y *YUM) Remove(opts *Opts, pkgs ...string) error {
|
||||
opts = ensureOpts(opts)
|
||||
cmd := y.getCmd(opts, "yum", "remove")
|
||||
cmd.Args = append(cmd.Args, pkgs...)
|
||||
setCmdEnv(cmd)
|
||||
err := cmd.Run()
|
||||
@@ -83,8 +87,9 @@ func (y *YUM) Remove(pkgs ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (y *YUM) Upgrade(pkgs ...string) error {
|
||||
cmd := exec.Command(getRootCmd(y.rootCmd), "yum", "upgrade", "-y")
|
||||
func (y *YUM) Upgrade(opts *Opts, pkgs ...string) error {
|
||||
opts = ensureOpts(opts)
|
||||
cmd := y.getCmd(opts, "yum", "upgrade")
|
||||
cmd.Args = append(cmd.Args, pkgs...)
|
||||
setCmdEnv(cmd)
|
||||
err := cmd.Run()
|
||||
@@ -94,8 +99,9 @@ func (y *YUM) Upgrade(pkgs ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (y *YUM) UpgradeAll() error {
|
||||
cmd := exec.Command(getRootCmd(y.rootCmd), "yum", "upgrade", "-y")
|
||||
func (y *YUM) UpgradeAll(opts *Opts) error {
|
||||
opts = ensureOpts(opts)
|
||||
cmd := y.getCmd(opts, "yum", "upgrade")
|
||||
setCmdEnv(cmd)
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
@@ -104,9 +110,16 @@ func (y *YUM) UpgradeAll() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (y *YUM) ListInstalled() (map[string]string, error) {
|
||||
func (y *YUM) ListInstalled(opts *Opts) (map[string]string, error) {
|
||||
opts = ensureOpts(opts)
|
||||
out := map[string]string{}
|
||||
cmd := exec.Command(getRootCmd(y.rootCmd), "rpm", "-qa", "--queryformat", "%{NAME}\u200b%|EPOCH?{%{EPOCH}:}:{}|%{VERSION}-%{RELEASE}\\n")
|
||||
|
||||
var cmd *exec.Cmd
|
||||
if opts.AsRoot {
|
||||
cmd = exec.Command(getRootCmd(y.rootCmd), "rpm", "-qa", "--queryformat", "%{NAME}\u200b%|EPOCH?{%{EPOCH}:}:{}|%{VERSION}-%{RELEASE}\\n")
|
||||
} else {
|
||||
cmd = exec.Command("rpm", "-qa", "--queryformat", "%{NAME}\u200b%|EPOCH?{%{EPOCH}:}:{}|%{VERSION}-%{RELEASE}\\n")
|
||||
}
|
||||
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
@@ -124,6 +137,7 @@ func (y *YUM) ListInstalled() (map[string]string, error) {
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
version = strings.TrimPrefix(version, "0:")
|
||||
out[name] = version
|
||||
}
|
||||
|
||||
@@ -134,3 +148,19 @@ func (y *YUM) ListInstalled() (map[string]string, error) {
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (y *YUM) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd {
|
||||
var cmd *exec.Cmd
|
||||
if opts.AsRoot {
|
||||
cmd = exec.Command(getRootCmd(y.rootCmd), mgrCmd)
|
||||
cmd.Args = append(cmd.Args, args...)
|
||||
} else {
|
||||
cmd = exec.Command(mgrCmd, args...)
|
||||
}
|
||||
|
||||
if opts.NoConfirm {
|
||||
cmd.Args = append(cmd.Args, "-y")
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -47,8 +47,9 @@ func (z *Zypper) SetRootCmd(s string) {
|
||||
z.rootCmd = s
|
||||
}
|
||||
|
||||
func (z *Zypper) Sync() error {
|
||||
cmd := exec.Command(getRootCmd(z.rootCmd), "zypper", "refresh")
|
||||
func (z *Zypper) Sync(opts *Opts) error {
|
||||
opts = ensureOpts(opts)
|
||||
cmd := z.getCmd(opts, "zypper", "refresh")
|
||||
setCmdEnv(cmd)
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
@@ -57,8 +58,9 @@ func (z *Zypper) Sync() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (z *Zypper) Install(pkgs ...string) error {
|
||||
cmd := exec.Command(getRootCmd(z.rootCmd), "zypper", "install", "-y")
|
||||
func (z *Zypper) Install(opts *Opts, pkgs ...string) error {
|
||||
opts = ensureOpts(opts)
|
||||
cmd := z.getCmd(opts, "zypper", "install", "-y")
|
||||
cmd.Args = append(cmd.Args, pkgs...)
|
||||
setCmdEnv(cmd)
|
||||
err := cmd.Run()
|
||||
@@ -68,12 +70,14 @@ func (z *Zypper) Install(pkgs ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (z *Zypper) InstallLocal(pkgs ...string) error {
|
||||
return z.Install(pkgs...)
|
||||
func (z *Zypper) InstallLocal(opts *Opts, pkgs ...string) error {
|
||||
opts = ensureOpts(opts)
|
||||
return z.Install(opts, pkgs...)
|
||||
}
|
||||
|
||||
func (z *Zypper) Remove(pkgs ...string) error {
|
||||
cmd := exec.Command(getRootCmd(z.rootCmd), "zypper", "remove", "-y")
|
||||
func (z *Zypper) Remove(opts *Opts, pkgs ...string) error {
|
||||
opts = ensureOpts(opts)
|
||||
cmd := z.getCmd(opts, "zypper", "remove", "-y")
|
||||
cmd.Args = append(cmd.Args, pkgs...)
|
||||
setCmdEnv(cmd)
|
||||
err := cmd.Run()
|
||||
@@ -83,8 +87,9 @@ func (z *Zypper) Remove(pkgs ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (z *Zypper) Upgrade(pkgs ...string) error {
|
||||
cmd := exec.Command(getRootCmd(z.rootCmd), "zypper", "update", "-y")
|
||||
func (z *Zypper) Upgrade(opts *Opts, pkgs ...string) error {
|
||||
opts = ensureOpts(opts)
|
||||
cmd := z.getCmd(opts, "zypper", "update", "-y")
|
||||
cmd.Args = append(cmd.Args, pkgs...)
|
||||
setCmdEnv(cmd)
|
||||
err := cmd.Run()
|
||||
@@ -94,8 +99,9 @@ func (z *Zypper) Upgrade(pkgs ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (z *Zypper) UpgradeAll() error {
|
||||
cmd := exec.Command(getRootCmd(z.rootCmd), "zypper", "update", "-y")
|
||||
func (z *Zypper) UpgradeAll(opts *Opts) error {
|
||||
opts = ensureOpts(opts)
|
||||
cmd := z.getCmd(opts, "zypper", "update", "-y")
|
||||
setCmdEnv(cmd)
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
@@ -104,9 +110,16 @@ func (z *Zypper) UpgradeAll() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (z *Zypper) ListInstalled() (map[string]string, error) {
|
||||
func (z *Zypper) ListInstalled(opts *Opts) (map[string]string, error) {
|
||||
opts = ensureOpts(opts)
|
||||
out := map[string]string{}
|
||||
cmd := exec.Command(getRootCmd(z.rootCmd), "rpm", "-qa", "--queryformat", "%{NAME}\u200b%|EPOCH?{%{EPOCH}:}:{}|%{VERSION}-%{RELEASE}\\n")
|
||||
|
||||
var cmd *exec.Cmd
|
||||
if opts.AsRoot {
|
||||
cmd = exec.Command(getRootCmd(z.rootCmd), "rpm", "-qa", "--queryformat", "%{NAME}\u200b%|EPOCH?{%{EPOCH}:}:{}|%{VERSION}-%{RELEASE}\\n")
|
||||
} else {
|
||||
cmd = exec.Command("rpm", "-qa", "--queryformat", "%{NAME}\u200b%|EPOCH?{%{EPOCH}:}:{}|%{VERSION}-%{RELEASE}\\n")
|
||||
}
|
||||
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
@@ -124,6 +137,7 @@ func (z *Zypper) ListInstalled() (map[string]string, error) {
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
version = strings.TrimPrefix(version, "0:")
|
||||
out[name] = version
|
||||
}
|
||||
|
||||
@@ -134,3 +148,19 @@ func (z *Zypper) ListInstalled() (map[string]string, error) {
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (z *Zypper) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd {
|
||||
var cmd *exec.Cmd
|
||||
if opts.AsRoot {
|
||||
cmd = exec.Command(getRootCmd(z.rootCmd), mgrCmd)
|
||||
cmd.Args = append(cmd.Args, args...)
|
||||
} else {
|
||||
cmd = exec.Command(mgrCmd, args...)
|
||||
}
|
||||
|
||||
if opts.NoConfirm {
|
||||
cmd.Args = append(cmd.Args, "-y")
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
47
repo.go
47
repo.go
@@ -41,6 +41,12 @@ func (p PkgNotFoundError) Error() string {
|
||||
return "package '" + p.pkgName + "' could not be found in any repository"
|
||||
}
|
||||
|
||||
type RepoConfig struct {
|
||||
Repo struct {
|
||||
MinVersion string `toml:"minVersion"`
|
||||
}
|
||||
}
|
||||
|
||||
func addrepoCmd(c *cli.Context) error {
|
||||
name := c.String("name")
|
||||
repoURL := c.String("url")
|
||||
@@ -146,7 +152,9 @@ func findPkg(pkg string) ([]string, error) {
|
||||
func pkgPrompt(options []string) ([]string, error) {
|
||||
names := make([]string, len(options))
|
||||
for i, option := range options {
|
||||
names[i] = filepath.Base(filepath.Dir(option))
|
||||
pkgDir := filepath.Dir(option)
|
||||
repoDir := filepath.Dir(pkgDir)
|
||||
names[i] = filepath.Base(repoDir) + "/" + filepath.Base(pkgDir)
|
||||
}
|
||||
|
||||
prompt := &survey.MultiSelect{
|
||||
@@ -221,6 +229,26 @@ func pullRepos(ctx context.Context) error {
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fl, err := w.Filesystem.Open("lure-repo.toml")
|
||||
if err != nil {
|
||||
log.Warn("Git repository does not appear to be a valid LURE repo").Str("repo", repo.Name).Send()
|
||||
continue
|
||||
}
|
||||
|
||||
var repoCfg RepoConfig
|
||||
err = toml.NewDecoder(fl).Decode(&repoCfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fl.Close()
|
||||
|
||||
currentVer, _, _ := strings.Cut(version, "-")
|
||||
if vercmp(currentVer, repoCfg.Repo.MinVersion) == -1 {
|
||||
log.Warn("LURE repo's minumum LURE version is greater than the current version. Try updating LURE if something doesn't work.").Str("repo", repo.Name).Send()
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
err = os.RemoveAll(repoDir)
|
||||
@@ -244,6 +272,23 @@ func pullRepos(ctx context.Context) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fl, err := os.Open(filepath.Join(repoDir, "lure-repo.toml"))
|
||||
if err != nil {
|
||||
log.Warn("Git repository does not appear to be a valid LURE repo").Str("repo", repo.Name).Send()
|
||||
}
|
||||
|
||||
var repoCfg RepoConfig
|
||||
err = toml.NewDecoder(fl).Decode(&repoCfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fl.Close()
|
||||
|
||||
currentVer, _, _ := strings.Cut(version, "-")
|
||||
if vercmp(currentVer, repoCfg.Repo.MinVersion) == -1 {
|
||||
log.Warn("LURE repo's minumum LURE version is greater than the current version. Try updating LURE if something doesn't work.").Str("repo", repo.Name).Send()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
3
scripts/gen-version.sh
Executable file
3
scripts/gen-version.sh
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
|
||||
git describe --tags > version.txt
|
||||
48
upgrade.go
48
upgrade.go
@@ -21,16 +21,10 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
"go.arsenm.dev/lure/distro"
|
||||
"go.arsenm.dev/lure/internal/shutils"
|
||||
"go.arsenm.dev/lure/internal/shutils/decoder"
|
||||
"go.arsenm.dev/lure/manager"
|
||||
"mvdan.cc/sh/v3/expand"
|
||||
"mvdan.cc/sh/v3/interp"
|
||||
"mvdan.cc/sh/v3/syntax"
|
||||
)
|
||||
|
||||
func upgradeCmd(c *cli.Context) error {
|
||||
@@ -44,13 +38,18 @@ func upgradeCmd(c *cli.Context) error {
|
||||
log.Fatal("Unable to detect supported package manager on system").Send()
|
||||
}
|
||||
|
||||
err = pullRepos(c.Context)
|
||||
if err != nil {
|
||||
log.Fatal("Error pulling repos").Err(err).Send()
|
||||
}
|
||||
|
||||
updates, err := checkForUpdates(c.Context, mgr, info)
|
||||
if err != nil {
|
||||
log.Fatal("Error checking for updates").Err(err).Send()
|
||||
}
|
||||
|
||||
if len(updates) > 0 {
|
||||
installPkgs(c.Context, updates, mgr)
|
||||
installPkgs(c.Context, updates, mgr, false)
|
||||
} else {
|
||||
log.Info("There is nothing to do.").Send()
|
||||
}
|
||||
@@ -59,7 +58,7 @@ func upgradeCmd(c *cli.Context) error {
|
||||
}
|
||||
|
||||
func checkForUpdates(ctx context.Context, mgr manager.Manager, info *distro.OSRelease) ([]string, error) {
|
||||
installed, err := mgr.ListInstalled()
|
||||
installed, err := mgr.ListInstalled(nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -74,38 +73,9 @@ func checkForUpdates(ctx context.Context, mgr manager.Manager, info *distro.OSRe
|
||||
// since we're not using a glob, we can assume a single item
|
||||
script := scripts[0]
|
||||
|
||||
fl, err := os.Open(script)
|
||||
vars, err := getBuildVars(ctx, script, info)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
file, err := syntax.NewParser().Parse(fl, "lure.sh")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
runner, err := interp.New(
|
||||
interp.Env(expand.ListEnviron()),
|
||||
interp.ExecHandler(shutils.NopExec),
|
||||
interp.StatHandler(shutils.NopStat),
|
||||
interp.OpenHandler(shutils.NopOpen),
|
||||
interp.ReadDirHandler(shutils.NopReadDir),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = runner.Run(ctx, file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dec := decoder.New(info, runner)
|
||||
|
||||
var vars BuildVars
|
||||
err = dec.DecodeVars(&vars)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
log.Fatal("Error getting build variables").Err(err).Send()
|
||||
}
|
||||
|
||||
repoVer := vars.Version
|
||||
|
||||
@@ -19,12 +19,18 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
//go:generate scripts/gen-version.sh
|
||||
|
||||
//go:embed version.txt
|
||||
var version string
|
||||
|
||||
// vercmp compares two version strings.
|
||||
// It returns 1 if v1 is greater,
|
||||
// 0 if the versions are equal,
|
||||
|
||||
Reference in New Issue
Block a user