Major refactor
ci/woodpecker/push/woodpecker Pipeline was successful
Details
ci/woodpecker/push/woodpecker Pipeline was successful
Details
This commit is contained in:
parent
d59c4036ef
commit
45522e3f3a
2
Makefile
2
Makefile
|
@ -19,4 +19,4 @@ uninstall:
|
|||
internal/config/version.txt:
|
||||
go generate ./internal/config
|
||||
|
||||
.PHONY: install clean uninstall
|
||||
.PHONY: install clean uninstall installmisc
|
760
build.go
760
build.go
|
@ -19,736 +19,84 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
_ "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"
|
||||
|
||||
"github.com/goreleaser/nfpm/v2"
|
||||
"github.com/goreleaser/nfpm/v2/files"
|
||||
"go.elara.ws/logger/log"
|
||||
"go.elara.ws/lure/distro"
|
||||
"go.elara.ws/lure/internal/cliutils"
|
||||
"go.elara.ws/lure/internal/build"
|
||||
"go.elara.ws/lure/internal/config"
|
||||
"go.elara.ws/lure/internal/cpu"
|
||||
"go.elara.ws/lure/internal/db"
|
||||
"go.elara.ws/lure/internal/dl"
|
||||
"go.elara.ws/lure/internal/repos"
|
||||
"go.elara.ws/lure/internal/osutils"
|
||||
"go.elara.ws/lure/internal/shutils"
|
||||
"go.elara.ws/lure/internal/shutils/decoder"
|
||||
"go.elara.ws/lure/internal/repos"
|
||||
"go.elara.ws/lure/internal/types"
|
||||
"go.elara.ws/lure/manager"
|
||||
"mvdan.cc/sh/v3/expand"
|
||||
"mvdan.cc/sh/v3/interp"
|
||||
"mvdan.cc/sh/v3/syntax"
|
||||
)
|
||||
|
||||
// BuildVars represents the script variables required
|
||||
// to build a package
|
||||
type BuildVars struct {
|
||||
Name string `sh:"name,required"`
|
||||
Version string `sh:"version,required"`
|
||||
Release int `sh:"release,required"`
|
||||
Epoch uint `sh:"epoch"`
|
||||
Description string `sh:"desc"`
|
||||
Homepage string `sh:"homepage"`
|
||||
Maintainer string `sh:"maintainer"`
|
||||
Architectures []string `sh:"architectures"`
|
||||
Licenses []string `sh:"license"`
|
||||
Provides []string `sh:"provides"`
|
||||
Conflicts []string `sh:"conflicts"`
|
||||
Depends []string `sh:"deps"`
|
||||
BuildDepends []string `sh:"build_deps"`
|
||||
Replaces []string `sh:"replaces"`
|
||||
Sources []string `sh:"sources"`
|
||||
Checksums []string `sh:"checksums"`
|
||||
Backup []string `sh:"backup"`
|
||||
Scripts Scripts `sh:"scripts"`
|
||||
}
|
||||
|
||||
type Scripts struct {
|
||||
PreInstall string `sh:"preinstall"`
|
||||
PostInstall string `sh:"postinstall"`
|
||||
PreRemove string `sh:"preremove"`
|
||||
PostRemove string `sh:"postremove"`
|
||||
PreUpgrade string `sh:"preupgrade"`
|
||||
PostUpgrade string `sh:"postupgrade"`
|
||||
PreTrans string `sh:"pretrans"`
|
||||
PostTrans string `sh:"posttrans"`
|
||||
}
|
||||
|
||||
func buildCmd(c *cli.Context) error {
|
||||
script := c.String("script")
|
||||
if c.String("package") != "" {
|
||||
script = filepath.Join(config.RepoDir, c.String("package"), "lure.sh")
|
||||
}
|
||||
|
||||
err := repos.Pull(c.Context, gdb, cfg.Repos)
|
||||
if err != nil {
|
||||
log.Fatal("Error pulling repositories").Err(err).Send()
|
||||
}
|
||||
|
||||
mgr := manager.Detect()
|
||||
if mgr == nil {
|
||||
log.Fatal("Unable to detect supported package manager on system").Send()
|
||||
}
|
||||
|
||||
pkgPaths, _, err := buildPackage(c.Context, script, mgr, c.Bool("clean"), c.Bool("interactive"))
|
||||
if err != nil {
|
||||
log.Fatal("Error building package").Err(err).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 = osutils.Move(pkgPath, filepath.Join(wd, name))
|
||||
if err != nil {
|
||||
log.Fatal("Error moving the package").Err(err).Send()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// buildPackage builds the script at the given path. It returns two slices. One contains the paths
|
||||
// to the built package(s), the other contains the names of the built package(s).
|
||||
func buildPackage(ctx context.Context, script string, mgr manager.Manager, clean, interactive bool) ([]string, []string, error) {
|
||||
info, err := distro.ParseOSRelease(ctx)
|
||||
if err != nil {
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
file, err := syntax.NewParser().Parse(fl, "lure.sh")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
fl.Close()
|
||||
|
||||
scriptDir := filepath.Dir(script)
|
||||
env := genBuildEnv(info, scriptDir)
|
||||
|
||||
// The first pass is just used to get variable values and runs before
|
||||
// the script is displayed, so it is restricted so as to prevent malicious
|
||||
// code from executing.
|
||||
runner, err := interp.New(
|
||||
interp.Env(expand.ListEnviron(env...)),
|
||||
interp.StdIO(os.Stdin, os.Stdout, os.Stderr),
|
||||
interp.ExecHandler(rHelpers.ExecHandler(shutils.NopExec)),
|
||||
interp.ReadDirHandler(shutils.RestrictedReadDir(scriptDir)),
|
||||
interp.StatHandler(shutils.RestrictedStat(scriptDir)),
|
||||
interp.OpenHandler(shutils.RestrictedOpen(scriptDir)),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
err = runner.Run(ctx, file)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
var vars BuildVars
|
||||
err = dec.DecodeVars(&vars)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
baseDir := filepath.Join(config.PkgsDir, vars.Name)
|
||||
srcdir := filepath.Join(baseDir, "src")
|
||||
pkgdir := filepath.Join(baseDir, "pkg")
|
||||
|
||||
if !clean {
|
||||
builtPkgPath, ok, err := checkForBuiltPackage(mgr, &vars, getPkgFormat(mgr), baseDir)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if ok {
|
||||
return []string{builtPkgPath}, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
err = cliutils.PromptViewScript(script, vars.Name, cfg.PagerStyle, interactive, translator)
|
||||
if err != nil {
|
||||
log.Fatal("Failed to prompt user to view build script").Err(err).Send()
|
||||
}
|
||||
|
||||
if !archMatches(vars.Architectures) {
|
||||
buildAnyway, err := cliutils.YesNoPrompt("Your system's CPU architecture doesn't match this package. Do you want to build anyway?", interactive, true, translator)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if !buildAnyway {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
log.Info("Building package").Str("name", vars.Name).Str("version", vars.Version).Send()
|
||||
|
||||
// The second pass will be used to execute the actual functions,
|
||||
// so it cannot be restricted. The script has already been displayed
|
||||
// to the user by this point, so it should be safe
|
||||
runner, err = interp.New(
|
||||
interp.Env(expand.ListEnviron(env...)),
|
||||
interp.StdIO(os.Stdin, os.Stdout, os.Stderr),
|
||||
interp.ExecHandler(helpers.ExecHandler(nil)),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
err = runner.Run(ctx, file)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
err = os.RemoveAll(baseDir)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
err = os.MkdirAll(srcdir, 0o755)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
err = os.MkdirAll(pkgdir, 0o755)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
installed, err := mgr.ListInstalled(nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if instVer, ok := installed[vars.Name]; ok {
|
||||
log.Warn("This package is already installed").
|
||||
Str("name", vars.Name).
|
||||
Str("version", instVer).
|
||||
Send()
|
||||
}
|
||||
|
||||
var buildDeps []string
|
||||
if len(vars.BuildDepends) > 0 {
|
||||
found, notFound, err := repos.FindPkgs(gdb, vars.BuildDepends)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
found = filterBuildDeps(found, installed)
|
||||
|
||||
log.Info("Installing build dependencies").Send()
|
||||
|
||||
flattened := cliutils.FlattenPkgs(found, "install", interactive, translator)
|
||||
buildDeps = packageNames(flattened)
|
||||
installPkgs(ctx, flattened, notFound, mgr, clean, interactive)
|
||||
}
|
||||
|
||||
var builtDeps, builtNames, repoDeps []string
|
||||
if len(vars.Depends) > 0 {
|
||||
log.Info("Installing dependencies").Send()
|
||||
|
||||
found, notFound, err := repos.FindPkgs(gdb, vars.Depends)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
scripts := getScriptPaths(cliutils.FlattenPkgs(found, "install", interactive, translator))
|
||||
for _, script := range scripts {
|
||||
pkgPaths, pkgNames, err := buildPackage(ctx, script, mgr, clean, interactive)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
builtDeps = append(builtDeps, pkgPaths...)
|
||||
builtNames = append(builtNames, pkgNames...)
|
||||
builtNames = append(builtNames, filepath.Base(filepath.Dir(script)))
|
||||
}
|
||||
repoDeps = notFound
|
||||
}
|
||||
|
||||
log.Info("Downloading sources").Send()
|
||||
|
||||
err = getSources(ctx, srcdir, &vars)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
err = setDirVars(ctx, runner, srcdir, pkgdir)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
fn, ok := dec.GetFunc("version")
|
||||
if ok {
|
||||
log.Info("Executing version()").Send()
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
err = fn(
|
||||
ctx,
|
||||
interp.Dir(srcdir),
|
||||
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
|
||||
}
|
||||
vars.Version = newVer
|
||||
|
||||
log.Info("Updating version").Str("new", newVer).Send()
|
||||
}
|
||||
|
||||
fn, ok = dec.GetFunc("prepare")
|
||||
if ok {
|
||||
log.Info("Executing prepare()").Send()
|
||||
|
||||
err = fn(ctx, interp.Dir(srcdir))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
fn, ok = dec.GetFunc("build")
|
||||
if ok {
|
||||
log.Info("Executing build()").Send()
|
||||
|
||||
err = fn(ctx, interp.Dir(srcdir))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
fn, ok = dec.GetFunc("package")
|
||||
if ok {
|
||||
log.Info("Executing package()").Send()
|
||||
|
||||
err = fn(ctx, interp.Dir(srcdir))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
} else {
|
||||
log.Fatal("The package() function is required").Send()
|
||||
}
|
||||
|
||||
log.Info("Building package metadata").Str("name", vars.Name).Send()
|
||||
|
||||
uniq(
|
||||
&repoDeps,
|
||||
&builtDeps,
|
||||
&builtNames,
|
||||
)
|
||||
|
||||
pkgInfo := &nfpm.Info{
|
||||
Name: vars.Name,
|
||||
Description: vars.Description,
|
||||
Arch: cpu.Arch(),
|
||||
Platform: "linux",
|
||||
Version: vars.Version,
|
||||
Release: strconv.Itoa(vars.Release),
|
||||
Homepage: vars.Homepage,
|
||||
License: strings.Join(vars.Licenses, ", "),
|
||||
Maintainer: vars.Maintainer,
|
||||
Overridables: nfpm.Overridables{
|
||||
Conflicts: vars.Conflicts,
|
||||
Replaces: vars.Replaces,
|
||||
Provides: vars.Provides,
|
||||
Depends: append(repoDeps, builtNames...),
|
||||
var buildCmd = &cli.Command{
|
||||
Name: "build",
|
||||
Usage: "Build a local package",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "script",
|
||||
Aliases: []string{"s"},
|
||||
Value: "lure.sh",
|
||||
Usage: "Path to the build script",
|
||||
},
|
||||
}
|
||||
&cli.StringFlag{
|
||||
Name: "package",
|
||||
Aliases: []string{"p"},
|
||||
Usage: "Name of the package to build and its repo (example: default/go-bin)",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "clean",
|
||||
Aliases: []string{"c"},
|
||||
Usage: "Build package from scratch even if there's an already built package available",
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
script := c.String("script")
|
||||
if c.String("package") != "" {
|
||||
script = filepath.Join(config.GetPaths().RepoDir, c.String("package"), "lure.sh")
|
||||
}
|
||||
|
||||
if vars.Epoch != 0 {
|
||||
pkgInfo.Epoch = strconv.FormatUint(uint64(vars.Epoch), 10)
|
||||
}
|
||||
err := repos.Pull(c.Context, config.Config().Repos)
|
||||
if err != nil {
|
||||
log.Fatal("Error pulling repositories").Err(err).Send()
|
||||
}
|
||||
|
||||
setScripts(&vars, pkgInfo, filepath.Dir(script))
|
||||
mgr := manager.Detect()
|
||||
if mgr == nil {
|
||||
log.Fatal("Unable to detect a supported package manager on the system").Send()
|
||||
}
|
||||
|
||||
if slices.Contains(vars.Architectures, "all") {
|
||||
pkgInfo.Arch = "all"
|
||||
}
|
||||
pkgPaths, _, err := build.BuildPackage(c.Context, types.BuildOpts{
|
||||
Script: script,
|
||||
Manager: mgr,
|
||||
Clean: c.Bool("clean"),
|
||||
Interactive: c.Bool("interactive"),
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal("Error building package").Err(err).Send()
|
||||
}
|
||||
|
||||
contents := []*files.Content{}
|
||||
filepath.Walk(pkgdir, func(path string, fi os.FileInfo, err error) error {
|
||||
trimmed := strings.TrimPrefix(path, pkgdir)
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
log.Fatal("Error getting working directory").Err(err).Send()
|
||||
}
|
||||
|
||||
if fi.IsDir() {
|
||||
f, err := os.Open(path)
|
||||
for _, pkgPath := range pkgPaths {
|
||||
name := filepath.Base(pkgPath)
|
||||
err = osutils.Move(pkgPath, filepath.Join(wd, name))
|
||||
if err != nil {
|
||||
return err
|
||||
log.Fatal("Error moving the package").Err(err).Send()
|
||||
}
|
||||
|
||||
_, err = f.Readdirnames(1)
|
||||
if err != io.EOF {
|
||||
return nil
|
||||
}
|
||||
|
||||
contents = append(contents, &files.Content{
|
||||
Source: path,
|
||||
Destination: trimmed,
|
||||
Type: "dir",
|
||||
FileInfo: &files.ContentFileInfo{
|
||||
MTime: fi.ModTime(),
|
||||
},
|
||||
})
|
||||
|
||||
f.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
if fi.Mode()&os.ModeSymlink != 0 {
|
||||
link, err := os.Readlink(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
link = strings.TrimPrefix(link, pkgdir)
|
||||
|
||||
contents = append(contents, &files.Content{
|
||||
Source: link,
|
||||
Destination: trimmed,
|
||||
Type: "symlink",
|
||||
FileInfo: &files.ContentFileInfo{
|
||||
MTime: fi.ModTime(),
|
||||
Mode: fi.Mode(),
|
||||
},
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
fileContent := &files.Content{
|
||||
Source: path,
|
||||
Destination: trimmed,
|
||||
FileInfo: &files.ContentFileInfo{
|
||||
MTime: fi.ModTime(),
|
||||
Mode: fi.Mode(),
|
||||
Size: fi.Size(),
|
||||
},
|
||||
}
|
||||
|
||||
if slices.Contains(vars.Backup, trimmed) {
|
||||
fileContent.Type = "config|noreplace"
|
||||
}
|
||||
|
||||
contents = append(contents, fileContent)
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
pkgInfo.Overridables.Contents = contents
|
||||
|
||||
packager, err := nfpm.Get(getPkgFormat(mgr))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
pkgName := packager.ConventionalFileName(pkgInfo)
|
||||
pkgPath := filepath.Join(baseDir, pkgName)
|
||||
|
||||
pkgPaths := append(builtDeps, pkgPath)
|
||||
pkgNames := append(builtNames, vars.Name)
|
||||
|
||||
pkgFile, err := os.Create(pkgPath)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
log.Info("Compressing package").Str("name", pkgName).Send()
|
||||
|
||||
err = packager.Package(pkgInfo, pkgFile)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if len(buildDeps) > 0 {
|
||||
removeBuildDeps, err := cliutils.YesNoPrompt("Would you like to remove build dependencies?", interactive, false, translator)
|
||||
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
|
||||
}
|
||||
|
||||
func checkForBuiltPackage(mgr manager.Manager, vars *BuildVars, pkgFormat, baseDir string) (string, bool, error) {
|
||||
filename, err := pkgFileName(vars, pkgFormat)
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
|
||||
pkgPath := filepath.Join(baseDir, filename)
|
||||
|
||||
_, err = os.Stat(pkgPath)
|
||||
if err != nil {
|
||||
return "", false, nil
|
||||
}
|
||||
|
||||
return pkgPath, true, nil
|
||||
}
|
||||
|
||||
func pkgFileName(vars *BuildVars, pkgFormat string) (string, error) {
|
||||
pkgInfo := &nfpm.Info{
|
||||
Name: vars.Name,
|
||||
Arch: cpu.Arch(),
|
||||
Version: vars.Version,
|
||||
Release: strconv.Itoa(vars.Release),
|
||||
Epoch: strconv.FormatUint(uint64(vars.Epoch), 10),
|
||||
}
|
||||
|
||||
packager, err := nfpm.Get(pkgFormat)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return packager.ConventionalFileName(pkgInfo), nil
|
||||
}
|
||||
|
||||
func getPkgFormat(mgr manager.Manager) string {
|
||||
pkgFormat := mgr.Format()
|
||||
if format, ok := os.LookupEnv("LURE_PKG_FORMAT"); ok {
|
||||
pkgFormat = format
|
||||
}
|
||||
return pkgFormat
|
||||
}
|
||||
|
||||
func genBuildEnv(info *distro.OSRelease, scriptdir string) []string {
|
||||
env := os.Environ()
|
||||
|
||||
env = append(
|
||||
env,
|
||||
"DISTRO_NAME="+info.Name,
|
||||
"DISTRO_PRETTY_NAME="+info.PrettyName,
|
||||
"DISTRO_ID="+info.ID,
|
||||
"DISTRO_VERSION_ID="+info.VersionID,
|
||||
"DISTRO_ID_LIKE="+strings.Join(info.Like, " "),
|
||||
|
||||
"ARCH="+cpu.Arch(),
|
||||
"NCPU="+strconv.Itoa(runtime.NumCPU()),
|
||||
|
||||
"scriptdir="+scriptdir,
|
||||
)
|
||||
|
||||
return env
|
||||
}
|
||||
|
||||
func getSources(ctx context.Context, srcdir string, bv *BuildVars) error {
|
||||
if len(bv.Sources) != len(bv.Checksums) {
|
||||
log.Fatal("The checksums array must be the same length as sources").Send()
|
||||
}
|
||||
|
||||
for i, src := range bv.Sources {
|
||||
opts := dl.Options{
|
||||
Name: fmt.Sprintf("%s[%d]", bv.Name, i),
|
||||
URL: src,
|
||||
Destination: srcdir,
|
||||
Progress: os.Stderr,
|
||||
}
|
||||
|
||||
if !strings.EqualFold(bv.Checksums[i], "SKIP") {
|
||||
algo, hashData, ok := strings.Cut(bv.Checksums[i], ":")
|
||||
if ok {
|
||||
checksum, err := hex.DecodeString(hashData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
opts.Hash = checksum
|
||||
opts.HashAlgorithm = algo
|
||||
} else {
|
||||
checksum, err := hex.DecodeString(bv.Checksums[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
opts.Hash = checksum
|
||||
}
|
||||
}
|
||||
|
||||
err := dl.Download(ctx, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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"
|
||||
fl, err := syntax.NewParser().Parse(strings.NewReader(cmd), "vars")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return runner.Run(ctx, fl)
|
||||
}
|
||||
|
||||
func setScripts(vars *BuildVars, info *nfpm.Info, scriptDir string) {
|
||||
if vars.Scripts.PreInstall != "" {
|
||||
info.Scripts.PreInstall = filepath.Join(scriptDir, vars.Scripts.PreInstall)
|
||||
}
|
||||
|
||||
if vars.Scripts.PostInstall != "" {
|
||||
info.Scripts.PostInstall = filepath.Join(scriptDir, vars.Scripts.PostInstall)
|
||||
}
|
||||
|
||||
if vars.Scripts.PreRemove != "" {
|
||||
info.Scripts.PreRemove = filepath.Join(scriptDir, vars.Scripts.PreRemove)
|
||||
}
|
||||
|
||||
if vars.Scripts.PostRemove != "" {
|
||||
info.Scripts.PostRemove = filepath.Join(scriptDir, vars.Scripts.PostRemove)
|
||||
}
|
||||
|
||||
if vars.Scripts.PreUpgrade != "" {
|
||||
info.ArchLinux.Scripts.PreUpgrade = filepath.Join(scriptDir, vars.Scripts.PreUpgrade)
|
||||
info.APK.Scripts.PreUpgrade = filepath.Join(scriptDir, vars.Scripts.PreUpgrade)
|
||||
}
|
||||
|
||||
if vars.Scripts.PostUpgrade != "" {
|
||||
info.ArchLinux.Scripts.PostUpgrade = filepath.Join(scriptDir, vars.Scripts.PostUpgrade)
|
||||
info.APK.Scripts.PostUpgrade = filepath.Join(scriptDir, vars.Scripts.PostUpgrade)
|
||||
}
|
||||
|
||||
if vars.Scripts.PreTrans != "" {
|
||||
info.RPM.Scripts.PreTrans = filepath.Join(scriptDir, vars.Scripts.PreTrans)
|
||||
}
|
||||
|
||||
if vars.Scripts.PostTrans != "" {
|
||||
info.RPM.Scripts.PostTrans = filepath.Join(scriptDir, vars.Scripts.PostTrans)
|
||||
}
|
||||
}
|
||||
|
||||
// archMatches checks if your system architecture matches
|
||||
// one of the provided architectures
|
||||
func archMatches(architectures []string) bool {
|
||||
if slices.Contains(architectures, "all") {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, arch := range architectures {
|
||||
if strings.HasPrefix(arch, "arm") {
|
||||
architectures = append(architectures, cpu.CompatibleARMReverse(arch)...)
|
||||
}
|
||||
}
|
||||
|
||||
return slices.Contains(architectures, cpu.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 filterBuildDeps(found map[string][]db.Package, installed map[string]string) map[string][]db.Package {
|
||||
out := map[string][]db.Package{}
|
||||
for name, pkgs := range found {
|
||||
var inner []db.Package
|
||||
for _, pkg := range pkgs {
|
||||
if _, ok := installed[pkg.Name]; !ok {
|
||||
addToFiltered := true
|
||||
for _, provides := range pkg.Provides.Val {
|
||||
if _, ok := installed[provides]; ok {
|
||||
addToFiltered = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if addToFiltered {
|
||||
inner = append(inner, pkg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(inner) > 0 {
|
||||
out[name] = inner
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func packageNames(pkgs []db.Package) []string {
|
||||
names := make([]string, len(pkgs))
|
||||
for i, p := range pkgs {
|
||||
names[i] = p.Name
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
// uniq removes all duplicates from string slices
|
||||
func uniq(ss ...*[]string) {
|
||||
for _, s := range ss {
|
||||
slices.Sort(*s)
|
||||
*s = slices.Compact(*s)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
|
@ -25,7 +25,6 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/twitchtv/twirp"
|
||||
"go.elara.ws/logger/log"
|
||||
"go.elara.ws/lure/internal/api"
|
||||
|
@ -34,9 +33,7 @@ import (
|
|||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
type lureWebAPI struct {
|
||||
db *sqlx.DB
|
||||
}
|
||||
type lureWebAPI struct{}
|
||||
|
||||
func (l lureWebAPI) Search(ctx context.Context, req *api.SearchRequest) (*api.SearchResponse, error) {
|
||||
query := "(name LIKE ? OR description LIKE ? OR json_array_contains(provides, ?))"
|
||||
|
@ -67,7 +64,7 @@ func (l lureWebAPI) Search(ctx context.Context, req *api.SearchRequest) (*api.Se
|
|||
query += " LIMIT " + strconv.FormatInt(req.Limit, 10)
|
||||
}
|
||||
|
||||
result, err := db.GetPkgs(l.db, query, args...)
|
||||
result, err := db.GetPkgs(query, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -86,7 +83,7 @@ func (l lureWebAPI) Search(ctx context.Context, req *api.SearchRequest) (*api.Se
|
|||
}
|
||||
|
||||
func (l lureWebAPI) GetPkg(ctx context.Context, req *api.GetPackageRequest) (*api.Package, error) {
|
||||
pkg, err := db.GetPkg(l.db, "name = ? AND repository = ?", req.Name, req.Repository)
|
||||
pkg, err := db.GetPkg("name = ? AND repository = ?", req.Name, req.Repository)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -98,7 +95,7 @@ func (l lureWebAPI) GetBuildScript(ctx context.Context, req *api.GetBuildScriptR
|
|||
return nil, twirp.NewError(twirp.InvalidArgument, "name and repository must not contain . or /")
|
||||
}
|
||||
|
||||
scriptPath := filepath.Join(config.RepoDir, req.Repository, req.Name, "lure.sh")
|
||||
scriptPath := filepath.Join(config.GetPaths().RepoDir, req.Repository, req.Name, "lure.sh")
|
||||
_, err := os.Stat(scriptPath)
|
||||
if os.IsNotExist(err) {
|
||||
return nil, twirp.NewError(twirp.NotFound, "requested package not found")
|
||||
|
|
|
@ -8,7 +8,6 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"go.elara.ws/lure/internal/db"
|
||||
)
|
||||
|
||||
|
@ -17,12 +16,12 @@ var logoData string
|
|||
|
||||
var _ http.HandlerFunc
|
||||
|
||||
func handleBadge(gdb *sqlx.DB) http.HandlerFunc {
|
||||
func handleBadge() http.HandlerFunc {
|
||||
return func(res http.ResponseWriter, req *http.Request) {
|
||||
repo := chi.URLParam(req, "repo")
|
||||
name := chi.URLParam(req, "pkg")
|
||||
|
||||
pkg, err := db.GetPkg(gdb, "name = ? AND repository = ?", name, repo)
|
||||
pkg, err := db.GetPkg("name = ? AND repository = ?", name, repo)
|
||||
if err != nil {
|
||||
http.Error(res, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2023 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 (
|
||||
"go.elara.ws/logger/log"
|
||||
"go.elara.ws/lure/internal/config"
|
||||
"go.elara.ws/lure/internal/types"
|
||||
)
|
||||
|
||||
var cfg types.Config
|
||||
|
||||
func init() {
|
||||
err := config.Decode(&cfg)
|
||||
if err != nil {
|
||||
log.Fatal("Error decoding config file").Err(err).Send()
|
||||
}
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2023 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 (
|
||||
"github.com/jmoiron/sqlx"
|
||||
"go.elara.ws/logger/log"
|
||||
"go.elara.ws/lure/internal/config"
|
||||
"go.elara.ws/lure/internal/db"
|
||||
)
|
||||
|
||||
var gdb *sqlx.DB
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
gdb, err = db.Open(config.DBPath)
|
||||
if err != nil {
|
||||
log.Fatal("Error opening database").Err(err).Send()
|
||||
}
|
||||
}
|
|
@ -30,6 +30,7 @@ import (
|
|||
"go.elara.ws/logger"
|
||||
"go.elara.ws/logger/log"
|
||||
"go.elara.ws/lure/internal/api"
|
||||
"go.elara.ws/lure/internal/config"
|
||||
"go.elara.ws/lure/internal/repos"
|
||||
)
|
||||
|
||||
|
@ -54,7 +55,7 @@ func main() {
|
|||
log.Logger = logger.NewMulti(log.Logger, logger.NewJSON(fl))
|
||||
}
|
||||
|
||||
err := repos.Pull(ctx, gdb, cfg.Repos)
|
||||
err := repos.Pull(ctx, config.Config().Repos)
|
||||
if err != nil {
|
||||
log.Fatal("Error pulling repositories").Err(err).Send()
|
||||
}
|
||||
|
@ -63,14 +64,14 @@ func main() {
|
|||
go repoPullWorker(ctx, sigCh)
|
||||
|
||||
apiServer := api.NewAPIServer(
|
||||
lureWebAPI{db: gdb},
|
||||
lureWebAPI{},
|
||||
twirp.WithServerPathPrefix(""),
|
||||
)
|
||||
|
||||
r := chi.NewRouter()
|
||||
r.With(allowAllCORSHandler, withAcceptLanguage).Handle("/*", apiServer)
|
||||
r.Post("/webhook", handleWebhook(sigCh))
|
||||
r.Get("/badge/{repo}/{pkg}", handleBadge(gdb))
|
||||
r.Get("/badge/{repo}/{pkg}", handleBadge())
|
||||
|
||||
ln, err := net.Listen("tcp", *addr)
|
||||
if err != nil {
|
||||
|
|
|
@ -30,6 +30,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"go.elara.ws/logger/log"
|
||||
"go.elara.ws/lure/internal/config"
|
||||
"go.elara.ws/lure/internal/repos"
|
||||
)
|
||||
|
||||
|
@ -87,7 +88,7 @@ func repoPullWorker(ctx context.Context, sigCh <-chan struct{}) {
|
|||
for {
|
||||
select {
|
||||
case <-sigCh:
|
||||
err := repos.Pull(ctx, gdb, cfg.Repos)
|
||||
err := repos.Pull(ctx, config.Config().Repos)
|
||||
if err != nil {
|
||||
log.Warn("Error while pulling repositories").Err(err).Send()
|
||||
}
|
||||
|
|
36
config.go
36
config.go
|
@ -1,36 +0,0 @@
|
|||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2023 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 (
|
||||
"go.elara.ws/logger/log"
|
||||
"go.elara.ws/lure/internal/config"
|
||||
"go.elara.ws/lure/internal/types"
|
||||
"go.elara.ws/lure/manager"
|
||||
)
|
||||
|
||||
var cfg types.Config
|
||||
|
||||
func init() {
|
||||
err := config.Decode(&cfg)
|
||||
if err != nil {
|
||||
log.Fatal("Error decoding config file").Err(err).Send()
|
||||
}
|
||||
manager.DefaultRootCmd = cfg.RootCmd
|
||||
}
|
36
db.go
36
db.go
|
@ -1,36 +0,0 @@
|
|||
/*
|
||||
* LURE - Linux User REpository
|
||||
* Copyright (C) 2023 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 (
|
||||
"github.com/jmoiron/sqlx"
|
||||
"go.elara.ws/lure/internal/config"
|
||||
"go.elara.ws/lure/internal/db"
|
||||
)
|
||||
|
||||
var gdb *sqlx.DB
|
||||
|
||||
func loadDB() error {
|
||||
ldb, err := db.Open(config.DBPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gdb = ldb
|
||||
return nil
|
||||
}
|
|
@ -103,7 +103,14 @@ func ParseOSRelease(ctx context.Context) (*OSRelease, error) {
|
|||
Logo: runner.Vars["LOGO"].Str,
|
||||
}
|
||||
|
||||
if runner.Vars["ID_LIKE"].IsSet() {
|
||||
distroUpdated := false
|
||||
if distID, ok := os.LookupEnv("LURE_DISTRO"); ok {
|
||||
out.ID = distID
|
||||
}
|
||||
|
||||
if distLike, ok := os.LookupEnv("LURE_DISTRO_LIKE"); ok {
|
||||
out.Like = strings.Split(distLike, " ")
|
||||
} else if runner.Vars["ID_LIKE"].IsSet() && !distroUpdated {
|
||||
out.Like = strings.Split(runner.Vars["ID_LIKE"].Str, " ")
|
||||
}
|
||||
|
||||
|
|
48
fix.go
48
fix.go
|
@ -28,36 +28,34 @@ import (
|
|||
"go.elara.ws/lure/internal/repos"
|
||||
)
|
||||
|
||||
func fixCmd(c *cli.Context) error {
|
||||
gdb.Close()
|
||||
var fixCmd = &cli.Command{
|
||||
Name: "fix",
|
||||
Usage: "Attempt to fix problems with LURE",
|
||||
Action: func(c *cli.Context) error {
|
||||
db.Close()
|
||||
paths := config.GetPaths()
|
||||
|
||||
log.Info("Removing cache directory").Send()
|
||||
log.Info("Removing cache directory").Send()
|
||||
|
||||
err := os.RemoveAll(config.CacheDir)
|
||||
if err != nil {
|
||||
log.Fatal("Unable to remove cache directory").Err(err).Send()
|
||||
}
|
||||
err := os.RemoveAll(paths.CacheDir)
|
||||
if err != nil {
|
||||
log.Fatal("Unable to remove cache directory").Err(err).Send()
|
||||
}
|
||||
|
||||
log.Info("Rebuilding cache").Send()
|
||||
log.Info("Rebuilding cache").Send()
|
||||
|
||||
err = os.MkdirAll(config.CacheDir, 0o755)
|
||||
if err != nil {
|
||||
log.Fatal("Unable to create new cache directory").Err(err).Send()
|
||||
}
|
||||
err = os.MkdirAll(paths.CacheDir, 0o755)
|
||||
if err != nil {
|
||||
log.Fatal("Unable to create new cache directory").Err(err).Send()
|
||||
}
|
||||
|
||||
// Make sure the DB is rebuilt when repos are pulled
|
||||
gdb, err = db.Open(config.DBPath)
|
||||
if err != nil {
|
||||
log.Fatal("Error initializing database").Err(err).Send()
|
||||
}
|
||||
config.DBPresent = false
|
||||
err = repos.Pull(c.Context, config.Config().Repos)
|
||||
if err != nil {
|
||||
log.Fatal("Error pulling repos").Err(err).Send()
|
||||
}
|
||||
|
||||
err = repos.Pull(c.Context, gdb, cfg.Repos)
|
||||
if err != nil {
|
||||
log.Fatal("Error pulling repos").Err(err).Send()
|
||||
}
|
||||
log.Info("Done").Send()
|
||||
|
||||
log.Info("Done").Send()
|
||||
|
||||
return nil
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
|
105
info.go
105
info.go
|
@ -33,61 +33,72 @@ import (
|
|||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func infoCmd(c *cli.Context) error {
|
||||
args := c.Args()
|
||||
if args.Len() < 1 {
|
||||
log.Fatalf("Command info expected at least 1 argument, got %d", args.Len()).Send()
|
||||
}
|
||||
|
||||
err := repos.Pull(c.Context, gdb, cfg.Repos)
|
||||
if err != nil {
|
||||
log.Fatal("Error pulling repositories").Err(err).Send()
|
||||
}
|
||||
|
||||
found, _, err := repos.FindPkgs(gdb, args.Slice())
|
||||
if err != nil {
|
||||
log.Fatal("Error finding packages").Err(err).Send()
|
||||
}
|
||||
|
||||
if len(found) == 0 {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
pkgs := cliutils.FlattenPkgs(found, "show", c.Bool("interactive"), translator)
|
||||
|
||||
var names []string
|
||||
all := c.Bool("all")
|
||||
|
||||
if !all {
|
||||
info, err := distro.ParseOSRelease(c.Context)
|
||||
if err != nil {
|
||||
log.Fatal("Error parsing os-release file").Err(err).Send()
|
||||
var infoCmd = &cli.Command{
|
||||
Name: "info",
|
||||
Usage: "Print information about a package",
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "all",
|
||||
Aliases: []string{"a"},
|
||||
Usage: "Show all information, not just for the current distro",
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
args := c.Args()
|
||||
if args.Len() < 1 {
|
||||
log.Fatalf("Command info expected at least 1 argument, got %d", args.Len()).Send()
|
||||
}
|
||||
names, err = overrides.Resolve(
|
||||
info,
|
||||
overrides.DefaultOpts.
|
||||
WithLanguages([]string{config.SystemLang()}),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal("Error resolving overrides").Err(err).Send()
|
||||
}
|
||||
}
|
||||
|
||||
for _, pkg := range pkgs {
|
||||
err := repos.Pull(c.Context, config.Config().Repos)
|
||||
if err != nil {
|
||||
log.Fatal("Error pulling repositories").Err(err).Send()
|
||||
}
|
||||
|
||||
found, _, err := repos.FindPkgs(args.Slice())
|
||||
if err != nil {
|
||||
log.Fatal("Error finding packages").Err(err).Send()
|
||||
}
|
||||
|
||||
if len(found) == 0 {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
pkgs := cliutils.FlattenPkgs(found, "show", c.Bool("interactive"))
|
||||
|
||||
var names []string
|
||||
all := c.Bool("all")
|
||||
|
||||
if !all {
|
||||
err = yaml.NewEncoder(os.Stdout).Encode(overrides.ResolvePackage(&pkg, names))
|
||||
info, err := distro.ParseOSRelease(c.Context)
|
||||
if err != nil {
|
||||
log.Fatal("Error encoding script variables").Err(err).Send()
|
||||
log.Fatal("Error parsing os-release file").Err(err).Send()
|
||||
}
|
||||
} else {
|
||||
err = yaml.NewEncoder(os.Stdout).Encode(pkg)
|
||||
names, err = overrides.Resolve(
|
||||
info,
|
||||
overrides.DefaultOpts.
|
||||
WithLanguages([]string{config.SystemLang()}),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal("Error encoding script variables").Err(err).Send()
|
||||
log.Fatal("Error resolving overrides").Err(err).Send()
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("---")
|
||||
}
|
||||
for _, pkg := range pkgs {
|
||||
if !all {
|
||||
err = yaml.NewEncoder(os.Stdout).Encode(overrides.ResolvePackage(&pkg, names))
|
||||
if err != nil {
|
||||
log.Fatal("Error encoding script variables").Err(err).Send()
|
||||
}
|
||||
} else {
|
||||
err = yaml.NewEncoder(os.Stdout).Encode(pkg)
|
||||
if err != nil {
|
||||
log.Fatal("Error encoding script variables").Err(err).Send()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
fmt.Println("---")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
|
154
install.go
154
install.go
|
@ -19,98 +19,98 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
|
||||
"go.elara.ws/logger/log"
|
||||
"fmt"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
"go.elara.ws/logger/log"
|
||||
"go.elara.ws/lure/internal/build"
|
||||
"go.elara.ws/lure/internal/cliutils"
|
||||
"go.elara.ws/lure/internal/config"
|
||||
"go.elara.ws/lure/internal/db"
|
||||
"go.elara.ws/lure/internal/repos"
|
||||
"go.elara.ws/lure/internal/types"
|
||||
"go.elara.ws/lure/manager"
|
||||
)
|
||||
|
||||
func installCmd(c *cli.Context) error {
|
||||
args := c.Args()
|
||||
if args.Len() < 1 {
|
||||
log.Fatalf("Command install expected at least 1 argument, got %d", args.Len()).Send()
|
||||
}
|
||||
|
||||
mgr := manager.Detect()
|
||||
if mgr == nil {
|
||||
log.Fatal("Unable to detect supported package manager on system").Send()
|
||||
}
|
||||
|
||||
err := repos.Pull(c.Context, gdb, cfg.Repos)
|
||||
if err != nil {
|
||||
log.Fatal("Error pulling repositories").Err(err).Send()
|
||||
}
|
||||
|
||||
found, notFound, err := repos.FindPkgs(gdb, args.Slice())
|
||||
if err != nil {
|
||||
log.Fatal("Error finding packages").Err(err).Send()
|
||||
}
|
||||
|
||||
installPkgs(c.Context, cliutils.FlattenPkgs(found, "install", c.Bool("interactive"), translator), notFound, mgr, c.Bool("clean"), c.Bool("interactive"))
|
||||
return nil
|
||||
}
|
||||
|
||||
// installPkgs installs non-LURE packages via the package manager, then builds and installs LURE
|
||||
// packages
|
||||
func installPkgs(ctx context.Context, pkgs []db.Package, notFound []string, mgr manager.Manager, clean, interactive bool) {
|
||||
if len(notFound) > 0 {
|
||||
err := mgr.Install(nil, notFound...)
|
||||
if err != nil {
|
||||
log.Fatal("Error installing native packages").Err(err).Send()
|
||||
}
|
||||
}
|
||||
|
||||
installScripts(ctx, mgr, getScriptPaths(pkgs), clean, interactive)
|
||||
}
|
||||
|
||||
// getScriptPaths generates a slice of script paths corresponding to the
|
||||
// given packages
|
||||
func getScriptPaths(pkgs []db.Package) []string {
|
||||
var scripts []string
|
||||
for _, pkg := range pkgs {
|
||||
scriptPath := filepath.Join(config.RepoDir, pkg.Repository, pkg.Name, "lure.sh")
|
||||
scripts = append(scripts, scriptPath)
|
||||
}
|
||||
return scripts
|
||||
}
|
||||
|
||||
// installScripts builds and installs LURE build scripts
|
||||
func installScripts(ctx context.Context, mgr manager.Manager, scripts []string, clean, interactive bool) {
|
||||
for _, script := range scripts {
|
||||
builtPkgs, _, err := buildPackage(ctx, script, mgr, clean, interactive)
|
||||
if err != nil {
|
||||
log.Fatal("Error building package").Err(err).Send()
|
||||
var installCmd = &cli.Command{
|
||||
Name: "install",
|
||||
Usage: "Install a new package",
|
||||
Aliases: []string{"in"},
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "clean",
|
||||
Aliases: []string{"c"},
|
||||
Usage: "Build package from scratch even if there's an already built package available",
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
args := c.Args()
|
||||
if args.Len() < 1 {
|
||||
log.Fatalf("Command install expected at least 1 argument, got %d", args.Len()).Send()
|
||||
}
|
||||
|
||||
err = mgr.InstallLocal(nil, builtPkgs...)
|
||||
if err != nil {
|
||||
log.Fatal("Error installing package").Err(err).Send()
|
||||
mgr := manager.Detect()
|
||||
if mgr == nil {
|
||||
log.Fatal("Unable to detect a supported package manager on the system").Send()
|
||||
}
|
||||
}
|
||||
|
||||
err := repos.Pull(c.Context, config.Config().Repos)
|
||||
if err != nil {
|
||||
log.Fatal("Error pulling repositories").Err(err).Send()
|
||||
}
|
||||
|
||||
found, notFound, err := repos.FindPkgs(args.Slice())
|
||||
if err != nil {
|
||||
log.Fatal("Error finding packages").Err(err).Send()
|
||||
}
|
||||
|
||||
pkgs := cliutils.FlattenPkgs(found, "install", c.Bool("interactive"))
|
||||
build.InstallPkgs(c.Context, pkgs, notFound, types.BuildOpts{
|
||||
Manager: mgr,
|
||||
Clean: c.Bool("clean"),
|
||||
Interactive: c.Bool("interactive"),
|
||||
})
|
||||
return nil
|
||||
},
|
||||
BashComplete: func(c *cli.Context) {
|
||||
result, err := db.GetPkgs("true")
|
||||
if err != nil {
|
||||
log.Fatal("Error getting packages").Err(err).Send()
|
||||
}
|
||||
defer result.Close()
|
||||
|
||||
for result.Next() {
|
||||
var pkg db.Package
|
||||
err = result.StructScan(&pkg)
|
||||
if err != nil {
|
||||
log.Fatal("Error iterating over packages").Err(err).Send()
|
||||
}
|
||||
|
||||
fmt.Println(pkg.Name)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func removeCmd(c *cli.Context) error {
|
||||
args := c.Args()
|
||||
if args.Len() < 1 {
|
||||
log.Fatalf("Command remove expected at least 1 argument, got %d", args.Len()).Send()
|
||||
}
|
||||
var removeCmd = &cli.Command{
|
||||
Name: "remove",
|
||||
Usage: "Remove an installed package",
|
||||
Aliases: []string{"rm"},
|
||||
Action: func(c *cli.Context) error {
|
||||
args := c.Args()
|
||||
if args.Len() < 1 {
|
||||
log.Fatalf("Command remove expected at least 1 argument, got %d", args.Len()).Send()
|
||||
}
|
||||
|
||||
mgr := manager.Detect()
|
||||
if mgr == nil {
|
||||
log.Fatal("Unable to detect supported package manager on system").Send()
|
||||
}
|
||||
mgr := manager.Detect()
|
||||
if mgr == nil {
|
||||
log.Fatal("Unable to detect a supported package manager on the system").Send()
|
||||
}
|
||||
|
||||
err := mgr.Remove(nil, c.Args().Slice()...)
|
||||
if err != nil {
|
||||
log.Fatal("Error removing packages").Err(err).Send()
|
||||
}
|
||||
err := mgr.Remove(nil, c.Args().Slice()...)
|
||||
if err != nil {
|
||||
log.Fatal("Error removing packages").Err(err).Send()
|
||||
}
|
||||
|
||||
return nil
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.28.1
|
||||
// protoc v3.21.9
|
||||
// protoc v4.24.2
|
||||
// source: lure.proto
|
||||
|
||||
package api
|
||||
|
|
|
@ -0,0 +1,728 @@
|
|||
package build
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/goreleaser/nfpm/v2"
|
||||
"github.com/goreleaser/nfpm/v2/files"
|
||||
"go.elara.ws/logger/log"
|
||||
"go.elara.ws/lure/distro"
|
||||
"go.elara.ws/lure/internal/cliutils"
|
||||
"go.elara.ws/lure/internal/config"
|
||||
"go.elara.ws/lure/internal/cpu"
|
||||
"go.elara.ws/lure/internal/db"
|
||||
"go.elara.ws/lure/internal/dl"
|
||||
"go.elara.ws/lure/internal/repos"
|
||||
"go.elara.ws/lure/internal/shutils"
|
||||
"go.elara.ws/lure/internal/shutils/decoder"
|
||||
"go.elara.ws/lure/internal/shutils/helpers"
|
||||
"go.elara.ws/lure/internal/types"
|
||||
"go.elara.ws/lure/manager"
|
||||
"mvdan.cc/sh/v3/expand"
|
||||
"mvdan.cc/sh/v3/interp"
|
||||
"mvdan.cc/sh/v3/syntax"
|
||||
)
|
||||
|
||||
// BuildPackage builds the script at the given path. It returns two slices. One contains the paths
|
||||
// to the built package(s), the other contains the names of the built package(s).
|
||||
func BuildPackage(ctx context.Context, opts types.BuildOpts) ([]string, []string, error) {
|
||||
info, err := distro.ParseOSRelease(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
fl, err := parseScript(info, opts.Script)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
vars, err := executeFirstPass(ctx, info, fl, opts.Script)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
dirs := getDirs(vars, opts.Script)
|
||||
|
||||
if !opts.Clean {
|
||||
builtPkgPath, ok, err := checkForBuiltPackage(opts.Manager, vars, getPkgFormat(opts.Manager), dirs.BaseDir)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if ok {
|
||||
return []string{builtPkgPath}, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
err = cliutils.PromptViewScript(opts.Script, vars.Name, config.Config().PagerStyle, opts.Interactive)
|
||||
if err != nil {
|
||||
log.Fatal("Failed to prompt user to view build script").Err(err).Send()
|
||||
}
|
||||
|
||||
log.Info("Building package").Str("name", vars.Name).Str("version", vars.Version).Send()
|
||||
|
||||
dec, err := executeSecondPass(ctx, info, fl, dirs)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
installed, err := opts.Manager.ListInstalled(nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
cont, err := performChecks(vars, opts.Interactive, installed)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
} else if !cont {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
err = prepareDirs(dirs)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
buildDeps, err := installBuildDeps(ctx, vars, opts, installed)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
builtPaths, builtNames, repoDeps, err := installDeps(ctx, opts, vars)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
log.Info("Downloading sources").Send()
|
||||
|
||||
err = getSources(ctx, dirs.SrcDir, vars)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
err = executeFunctions(ctx, dec, dirs, vars)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
log.Info("Building package metadata").Str("name", vars.Name).Send()
|
||||
|
||||
pkgInfo, err := buildPkgMetadata(vars, dirs, append(repoDeps, builtNames...))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
packager, err := nfpm.Get(getPkgFormat(opts.Manager))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
pkgName := packager.ConventionalFileName(pkgInfo)
|
||||
pkgPath := filepath.Join(dirs.BaseDir, pkgName)
|
||||
|
||||
pkgFile, err := os.Create(pkgPath)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
log.Info("Compressing package").Str("name", pkgName).Send()
|
||||
|
||||
err = packager.Package(pkgInfo, pkgFile)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
err = removeBuildDeps(buildDeps, opts)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Add the path and name of the package we just built to the
|
||||
// appropriate slices
|
||||
pkgPaths := append(builtPaths, pkgPath)
|
||||
pkgNames := append(builtNames, vars.Name)
|
||||
|
||||
pkgPaths = removeDuplicates(pkgPaths)
|
||||
pkgNames = removeDuplicates(pkgNames)
|
||||
|
||||
return pkgPaths, pkgNames, nil
|
||||
}
|
||||
|
||||
func parseScript(info *distro.OSRelease, script string) (*syntax.File, error) {
|
||||
fl, err := os.Open(script)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer fl.Close()
|
||||
|
||||
file, err := syntax.NewParser().Parse(fl, "lure.sh")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return file, nil
|
||||
}
|
||||
|
||||
func executeFirstPass(ctx context.Context, info *distro.OSRelease, fl *syntax.File, script string) (*types.BuildVars, error) {
|
||||
scriptDir := filepath.Dir(script)
|
||||
env := createBuildEnvVars(info, types.Directories{ScriptDir: scriptDir})
|
||||
|
||||
// The first pass is just used to get variable values and runs before
|
||||
// the script is displayed, so it is restricted so as to prevent malicious
|
||||
// code from executing.
|
||||
runner, err := interp.New(
|
||||
interp.Env(expand.ListEnviron(env...)),
|
||||
interp.StdIO(os.Stdin, os.Stdout, os.Stderr),
|
||||
interp.ExecHandler(helpers.Restricted.ExecHandler(shutils.NopExec)),
|
||||
interp.ReadDirHandler(shutils.RestrictedReadDir(scriptDir)),
|
||||
interp.StatHandler(shutils.RestrictedStat(scriptDir)),
|
||||
interp.OpenHandler(shutils.RestrictedOpen(scriptDir)),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = runner.Run(ctx, fl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dec := decoder.New(info, runner)
|
||||
|
||||
var vars types.BuildVars
|
||||
err = dec.DecodeVars(&vars)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &vars, nil
|
||||
}
|
||||
|
||||
func getDirs(vars *types.BuildVars, script string) types.Directories {
|
||||
baseDir := filepath.Join(config.GetPaths().PkgsDir, vars.Name)
|
||||
return types.Directories{
|
||||
BaseDir: baseDir,
|
||||
SrcDir: filepath.Join(baseDir, "src"),
|
||||
PkgDir: filepath.Join(baseDir, "pkg"),
|
||||
ScriptDir: filepath.Dir(script),
|
||||
}
|
||||
}
|
||||
|
||||
func executeSecondPass(ctx context.Context, info *distro.OSRelease, fl *syntax.File, dirs types.Directories) (*decoder.Decoder, error) {
|
||||
env := createBuildEnvVars(info, dirs)
|
||||
// The second pass will be used to execute the actual functions,
|
||||
// so it cannot be restricted. The script has already been displayed
|
||||
// to the user by this point, so it should be safe
|
||||
runner, err := interp.New(
|
||||
interp.Env(expand.ListEnviron(env...)),
|
||||
interp.StdIO(os.Stdin, os.Stdout, os.Stderr),
|
||||
interp.ExecHandler(helpers.Helpers.ExecHandler(nil)),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = runner.Run(ctx, fl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return decoder.New(info, runner), nil
|
||||
}
|
||||
|
||||
func prepareDirs(dirs types.Directories) error {
|
||||
err := os.RemoveAll(dirs.BaseDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.MkdirAll(dirs.SrcDir, 0o755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.MkdirAll(dirs.PkgDir, 0o755)
|
||||
}
|
||||
|
||||
func performChecks(vars *types.BuildVars, interactive bool, installed map[string]string) (bool, error) {
|
||||
if !cpu.IsCompatibleWith(cpu.Arch(), vars.Architectures) {
|
||||
cont, err := cliutils.YesNoPrompt("Your system's CPU architecture doesn't match this package. Do you want to build anyway?", interactive, true)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if !cont {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
if instVer, ok := installed[vars.Name]; ok {
|
||||
log.Warn("This package is already installed").
|
||||
Str("name", vars.Name).
|
||||
Str("version", instVer).
|
||||
Send()
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func installBuildDeps(ctx context.Context, vars *types.BuildVars, opts types.BuildOpts, installed map[string]string) ([]string, error) {
|
||||
var buildDeps []string
|
||||
if len(vars.BuildDepends) > 0 {
|
||||
found, notFound, err := repos.FindPkgs(vars.BuildDepends)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
found = filterBuildDeps(found, installed)
|
||||
|
||||
log.Info("Installing build dependencies").Send()
|
||||
|
||||
flattened := cliutils.FlattenPkgs(found, "install", opts.Interactive)
|
||||
buildDeps = packageNames(flattened)
|
||||
InstallPkgs(ctx, flattened, notFound, opts)
|
||||
}
|
||||
return buildDeps, nil
|
||||
}
|
||||
|
||||
func installDeps(ctx context.Context, opts types.BuildOpts, vars *types.BuildVars) (builtPaths, builtNames, repoDeps []string, err error) {
|
||||
if len(vars.Depends) > 0 {
|
||||
log.Info("Installing dependencies").Send()
|
||||
|
||||
found, notFound, err := repos.FindPkgs(vars.Depends)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
repoDeps = notFound
|
||||
|
||||
// If there are multiple options for some packages, flatten them all into a single slice
|
||||
pkgs := cliutils.FlattenPkgs(found, "install", opts.Interactive)
|
||||
scripts := GetScriptPaths(pkgs)
|
||||
for _, script := range scripts {
|
||||
newOpts := opts
|
||||
newOpts.Script = script
|
||||
|
||||
// Build the dependency
|
||||
pkgPaths, pkgNames, err := BuildPackage(ctx, newOpts)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
// Append the paths of all the built packages to builtPaths
|
||||
builtPaths = append(builtPaths, pkgPaths...)
|
||||
// Append the names of all the built packages to builtNames
|
||||
builtNames = append(builtNames, pkgNames...)
|
||||
// Append the name of the current package to builtNames
|
||||
builtNames = append(builtNames, filepath.Base(filepath.Dir(script)))
|
||||
}
|
||||
}
|
||||
|
||||
repoDeps = removeDuplicates(repoDeps)
|
||||
builtPaths = removeDuplicates(builtPaths)
|
||||
builtNames = removeDuplicates(builtNames)
|
||||
return builtPaths, builtNames, repoDeps, nil
|
||||
}
|
||||
|
||||
func executeFunctions(ctx context.Context, dec *decoder.Decoder, dirs types.Directories, vars *types.BuildVars) (err error) {
|
||||
version, ok := dec.GetFunc("version")
|
||||
if ok {
|
||||
log.Info("Executing version()").Send()
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
err = version(
|
||||
ctx,
|
||||
interp.Dir(dirs.SrcDir),
|
||||
interp.StdIO(os.Stdin, buf, os.Stderr),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newVer := strings.TrimSpace(buf.String())
|
||||
err = setVersion(ctx, dec.Runner, newVer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
vars.Version = newVer
|
||||
|
||||
log.Info("Updating version").Str("new", newVer).Send()
|
||||
}
|
||||
|
||||
prepare, ok := dec.GetFunc("prepare")
|
||||
if ok {
|
||||
log.Info("Executing prepare()").Send()
|
||||
|
||||
err = prepare(ctx, interp.Dir(dirs.SrcDir))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
build, ok := dec.GetFunc("build")
|
||||
if ok {
|
||||
log.Info("Executing build()").Send()
|
||||
|
||||
err = build(ctx, interp.Dir(dirs.SrcDir))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
packageFn, ok := dec.GetFunc("package")
|
||||
if ok {
|
||||
log.Info("Executing package()").Send()
|
||||
|
||||
err = packageFn(ctx, interp.Dir(dirs.SrcDir))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
log.Fatal("The package() function is required").Send()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildPkgMetadata(vars *types.BuildVars, dirs types.Directories, deps []string) (*nfpm.Info, error) {
|
||||
pkgInfo := &nfpm.Info{
|
||||
Name: vars.Name,
|
||||
Description: vars.Description,
|
||||
Arch: cpu.Arch(),
|
||||
Platform: "linux",
|
||||
Version: vars.Version,
|
||||
Release: strconv.Itoa(vars.Release),
|
||||
Homepage: vars.Homepage,
|
||||
License: strings.Join(vars.Licenses, ", "),
|
||||
Maintainer: vars.Maintainer,
|
||||
Overridables: nfpm.Overridables{
|
||||
Conflicts: vars.Conflicts,
|
||||
Replaces: vars.Replaces,
|
||||
Provides: vars.Provides,
|
||||
Depends: deps,
|
||||
},
|
||||
}
|
||||
|
||||
if vars.Epoch != 0 {
|
||||
pkgInfo.Epoch = strconv.FormatUint(uint64(vars.Epoch), 10)
|
||||
}
|
||||
|
||||
setScripts(vars, pkgInfo, dirs.ScriptDir)
|
||||
|
||||
if slices.Contains(vars.Architectures, "all") {
|
||||
pkgInfo.Arch = "all"
|
||||
}
|
||||
|
||||
contents, err := buildContents(vars, dirs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pkgInfo.Overridables.Contents = contents
|
||||
|
||||
return pkgInfo, nil
|
||||
}
|
||||
|
||||
func buildContents(vars *types.BuildVars, dirs types.Directories) ([]*files.Content, error) {
|
||||
contents := []*files.Content{}
|
||||
err := filepath.Walk(dirs.PkgDir, func(path string, fi os.FileInfo, err error) error {
|
||||
trimmed := strings.TrimPrefix(path, dirs.PkgDir)
|
||||
|
||||
if fi.IsDir() {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = f.Readdirnames(1)
|
||||
if err != io.EOF {
|
||||
return nil
|
||||
}
|
||||
|
||||
contents = append(contents, &files.Content{
|
||||
Source: path,
|
||||
Destination: trimmed,
|
||||
Type: "dir",
|
||||
FileInfo: &files.ContentFileInfo{
|
||||
MTime: fi.ModTime(),
|
||||
},
|
||||
})
|
||||
|
||||
f.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
if fi.Mode()&os.ModeSymlink != 0 {
|
||||
link, err := os.Readlink(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
link = strings.TrimPrefix(link, dirs.PkgDir)
|
||||
|
||||
contents = append(contents, &files.Content{
|
||||
Source: link,
|
||||
Destination: trimmed,
|
||||
Type: "symlink",
|
||||
FileInfo: &files.ContentFileInfo{
|
||||
MTime: fi.ModTime(),
|
||||
Mode: fi.Mode(),
|
||||
},
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
fileContent := &files.Content{
|
||||
Source: path,
|
||||
Destination: trimmed,
|
||||
FileInfo: &files.ContentFileInfo{
|
||||
MTime: fi.ModTime(),
|
||||
Mode: fi.Mode(),
|
||||
Size: fi.Size(),
|
||||
},
|
||||
}
|
||||
|
||||
if slices.Contains(vars.Backup, trimmed) {
|
||||
fileContent.Type = "config|noreplace"
|
||||
}
|
||||
|
||||
contents = append(contents, fileContent)
|
||||
|
||||
return nil
|
||||
})
|
||||
return contents, err
|
||||
}
|
||||
|
||||
func removeBuildDeps(buildDeps []string, opts types.BuildOpts) error {
|
||||
if len(buildDeps) > 0 {
|
||||
removeBuildDeps, err := cliutils.YesNoPrompt("Would you like to remove the build dependencies?", opts.Interactive, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if removeBuildDeps {
|
||||
err = opts.Manager.Remove(
|
||||
&manager.Opts{
|
||||
AsRoot: true,
|
||||
NoConfirm: true,
|
||||
},
|
||||
buildDeps...,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkForBuiltPackage(mgr manager.Manager, vars *types.BuildVars, pkgFormat, baseDir string) (string, bool, error) {
|
||||
filename, err := pkgFileName(vars, pkgFormat)
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
|
||||
pkgPath := filepath.Join(baseDir, filename)
|
||||
|
||||
_, err = os.Stat(pkgPath)
|
||||
if err != nil {
|
||||
return "", false, nil
|
||||
}
|
||||
|
||||
return pkgPath, true, nil
|
||||
}
|
||||
|
||||
func pkgFileName(vars *types.BuildVars, pkgFormat string) (string, error) {
|
||||
pkgInfo := &nfpm.Info{
|
||||
Name: vars.Name,
|
||||
Arch: cpu.Arch(),
|
||||
Version: vars.Version,
|
||||
Release: strconv.Itoa(vars.Release),
|
||||
Epoch: strconv.FormatUint(uint64(vars.Epoch), 10),
|
||||
}
|
||||
|
||||
packager, err := nfpm.Get(pkgFormat)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return packager.ConventionalFileName(pkgInfo), nil
|
||||
}
|
||||
|
||||
func getPkgFormat(mgr manager.Manager) string {
|
||||
pkgFormat := mgr.Format()
|
||||
if format, ok := os.LookupEnv("LURE_PKG_FORMAT"); ok {
|
||||
pkgFormat = format
|
||||
}
|
||||
return pkgFormat
|
||||
}
|
||||
|
||||
func createBuildEnvVars(info *distro.OSRelease, dirs types.Directories) []string {
|
||||
env := os.Environ()
|
||||
|
||||
env = append(
|
||||
env,
|
||||
"DISTRO_NAME="+info.Name,
|
||||
"DISTRO_PRETTY_NAME="+info.PrettyName,
|
||||
"DISTRO_ID="+info.ID,
|
||||
"DISTRO_VERSION_ID="+info.VersionID,
|
||||
"DISTRO_ID_LIKE="+strings.Join(info.Like, " "),
|
||||
"ARCH="+cpu.Arch(),
|
||||
"NCPU="+strconv.Itoa(runtime.NumCPU()),
|
||||
)
|
||||
|
||||
if dirs.ScriptDir != "" {
|
||||
env = append(env, "scriptdir="+dirs.ScriptDir)
|
||||
}
|
||||
|
||||
if dirs.PkgDir != "" {
|
||||
env = append(env, "pkgdir="+dirs.PkgDir)
|
||||
}
|
||||
|
||||
if dirs.SrcDir != "" {
|
||||
env = append(env, "srcdir="+dirs.SrcDir)
|
||||
}
|
||||
|
||||
return env
|
||||
}
|
||||
|
||||
func getSources(ctx context.Context, srcdir string, bv *types.BuildVars) error {
|
||||
if len(bv.Sources) != len(bv.Checksums) {
|
||||
log.Fatal("The checksums array must be the same length as sources").Send()
|
||||
}
|
||||
|
||||
for i, src := range bv.Sources {
|
||||
opts := dl.Options{
|
||||
Name: fmt.Sprintf("%s[%d]", bv.Name, i),
|
||||
URL: src,
|
||||
Destination: srcdir,
|
||||
Progress: os.Stderr,
|
||||
}
|
||||
|
||||
if !strings.EqualFold(bv.Checksums[i], "SKIP") {
|
||||
algo, hashData, ok := strings.Cut(bv.Checksums[i], ":")
|
||||
if ok {
|
||||
checksum, err := hex.DecodeString(hashData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
opts.Hash = checksum
|
||||
opts.HashAlgorithm = algo
|
||||
} else {
|
||||
checksum, err := hex.DecodeString(bv.Checksums[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
opts.Hash = checksum
|
||||
}
|
||||
}
|
||||
|
||||
err := dl.Download(ctx, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setScripts(vars *types.BuildVars, info *nfpm.Info, scriptDir string) {
|
||||
if vars.Scripts.PreInstall != "" {
|
||||
info.Scripts.PreInstall = filepath.Join(scriptDir, vars.Scripts.PreInstall)
|
||||
}
|
||||
|
||||
if vars.Scripts.PostInstall != "" {
|
||||
info.Scripts.PostInstall = filepath.Join(scriptDir, vars.Scripts.PostInstall)
|
||||
}
|
||||
|
||||
if vars.Scripts.PreRemove != "" {
|
||||
info.Scripts.PreRemove = filepath.Join(scriptDir, vars.Scripts.PreRemove)
|
||||
}
|
||||
|
||||
if vars.Scripts.PostRemove != "" {
|
||||
info.Scripts.PostRemove = filepath.Join(scriptDir, vars.Scripts.PostRemove)
|
||||
}
|
||||
|
||||
if vars.Scripts.PreUpgrade != "" {
|
||||
info.ArchLinux.Scripts.PreUpgrade = filepath.Join(scriptDir, vars.Scripts.PreUpgrade)
|
||||
info.APK.Scripts.PreUpgrade = filepath.Join(scriptDir, vars.Scripts.PreUpgrade)
|
||||
}
|
||||
|
||||
if vars.Scripts.PostUpgrade != "" {
|
||||
info.ArchLinux.Scripts.PostUpgrade = filepath.Join(scriptDir, vars.Scripts.PostUpgrade)
|
||||
info.APK.Scripts.PostUpgrade = filepath.Join(scriptDir, vars.Scripts.PostUpgrade)
|
||||
}
|
||||
|
||||
if vars.Scripts.PreTrans != "" {
|
||||
info.RPM.Scripts.PreTrans = filepath.Join(scriptDir, vars.Scripts.PreTrans)
|
||||
}
|
||||
|
||||
if vars.Scripts.PostTrans != "" {
|
||||
info.RPM.Scripts.PostTrans = filepath.Join(scriptDir, vars.Scripts.PostTrans)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// filterBuildDeps returns a map without any dependencies that are already installed
|
||||
func filterBuildDeps(found map[string][]db.Package, installed map[string]string) map[string][]db.Package {
|
||||
out := map[string][]db.Package{}
|
||||
for name, pkgs := range found {
|
||||
var inner []db.Package
|
||||
for _, pkg := range pkgs {
|
||||
if _, ok := installed[pkg.Name]; !ok {
|
||||
addToFiltered := true
|
||||
for _, provides := range pkg.Provides.Val {
|
||||
if _, ok := installed[provides]; ok {
|
||||
addToFiltered = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if addToFiltered {
|
||||
inner = append(inner, pkg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(inner) > 0 {
|
||||
out[name] = inner
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func packageNames(pkgs []db.Package) []string {
|
||||
names := make([]string, len(pkgs))
|
||||
for i, p := range pkgs {
|
||||
names[i] = p.Name
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
func removeDuplicates(slice []string) []string {
|
||||
seen := map[string]struct{}{}
|
||||
result := []string{}
|
||||
|
||||
for _, s := range slice {
|
||||
if _, ok := seen[s]; !ok {
|
||||
seen[s] = struct{}{}
|
||||
result = append(result, s)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
package build
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
|
||||
"go.elara.ws/logger/log"
|
||||
"go.elara.ws/lure/internal/config"
|
||||
"go.elara.ws/lure/internal/db"
|
||||
"go.elara.ws/lure/internal/types"
|
||||
)
|
||||
|
||||
// InstallPkgs installs non-LURE packages via the package manager, then builds and installs LURE
|
||||
// packages
|
||||
func InstallPkgs(ctx context.Context, lurePkgs []db.Package, nativePkgs []string, opts types.BuildOpts) {
|
||||
if len(nativePkgs) > 0 {
|
||||
err := opts.Manager.Install(nil, nativePkgs...)
|
||||
if err != nil {
|
||||
log.Fatal("Error installing native packages").Err(err).Send()
|
||||
}
|
||||
}
|
||||
|
||||
InstallScripts(ctx, GetScriptPaths(lurePkgs), opts)
|
||||
}
|
||||
|
||||
// GetScriptPaths generates a slice of script paths corresponding to the
|
||||
// given packages
|
||||
func GetScriptPaths(pkgs []db.Package) []string {
|
||||
var scripts []string
|
||||
for _, pkg := range pkgs {
|
||||
scriptPath := filepath.Join(config.GetPaths().RepoDir, pkg.Repository, pkg.Name, "lure.sh")
|
||||
scripts = append(scripts, scriptPath)
|
||||
}
|
||||
return scripts
|
||||
}
|
||||
|
||||
// InstallScripts builds and installs LURE build scripts
|
||||
func InstallScripts(ctx context.Context, scripts []string, opts types.BuildOpts) {
|
||||
for _, script := range scripts {
|
||||
opts.Script = script
|
||||
builtPkgs, _, err := BuildPackage(ctx, opts)
|
||||
if err != nil {
|
||||
log.Fatal("Error building package").Err(err).Send()
|
||||
}
|
||||
|
||||
err = opts.Manager.InstallLocal(nil, builtPkgs...)
|
||||
if err != nil {
|
||||
log.Fatal("Error installing package").Err(err).Send()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -26,16 +26,16 @@ import (
|
|||
"go.elara.ws/lure/internal/config"
|
||||
"go.elara.ws/lure/internal/db"
|
||||
"go.elara.ws/lure/internal/pager"
|
||||
"go.elara.ws/translate"
|
||||
"go.elara.ws/lure/internal/translations"
|
||||
)
|
||||
|
||||
// YesNoPrompt asks the user a yes or no question, using def as the default answer
|
||||
func YesNoPrompt(msg string, interactive, def bool, t translate.Translator) (bool, error) {
|
||||
func YesNoPrompt(msg string, interactive, def bool) (bool, error) {
|
||||
if interactive {
|
||||
var answer bool
|
||||
err := survey.AskOne(
|
||||
&survey.Confirm{
|
||||
Message: t.TranslateTo(msg, config.Language),
|
||||
Message: translations.Translator().TranslateTo(msg, config.Language()),
|
||||
Default: def,
|
||||
},
|
||||
&answer,
|
||||
|
@ -49,12 +49,13 @@ func YesNoPrompt(msg string, interactive, def bool, t translate.Translator) (boo
|
|||
// PromptViewScript asks the user if they'd like to see a script,
|
||||
// shows it if they answer yes, then asks if they'd still like to
|
||||
// continue, and exits if they answer no.
|
||||
func PromptViewScript(script, name, style string, interactive bool, t translate.Translator) error {
|
||||
func PromptViewScript(script, name, style string, interactive bool) error {
|
||||
if !interactive {
|
||||
return nil
|
||||
}
|
||||
|
||||
view, err := YesNoPrompt(t.TranslateTo("Would you like to view the build script for", config.Language)+" "+name, interactive, false, t)
|
||||
scriptPrompt := translations.Translator().TranslateTo("Would you like to view the build script for", config.Language()) + " " + name
|
||||
view, err := YesNoPrompt(scriptPrompt, interactive, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -65,13 +66,13 @@ func PromptViewScript(script, name, style string, interactive bool, t translate.
|
|||
return err
|
||||
}
|
||||
|
||||
cont, err := YesNoPrompt("Would you still like to continue?", interactive, false, t)
|
||||
cont, err := YesNoPrompt("Would you still like to continue?", interactive, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !cont {
|
||||
log.Fatal(t.TranslateTo("User chose not to continue after reading script", config.Language)).Send()
|
||||
log.Fatal(translations.Translator().TranslateTo("User chose not to continue after reading script", config.Language())).Send()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -98,11 +99,11 @@ func ShowScript(path, name, style string) error {
|
|||
|
||||
// FlattenPkgs attempts to flatten the a map of slices of packages into a single slice
|
||||
// of packages by prompting the user if multiple packages match.
|
||||
func FlattenPkgs(found map[string][]db.Package, verb string, interactive bool, t translate.Translator) []db.Package {
|
||||
func FlattenPkgs(found map[string][]db.Package, verb string, interactive bool) []db.Package {
|
||||
var outPkgs []db.Package
|
||||
for _, pkgs := range found {
|
||||
if len(pkgs) > 1 && interactive {
|
||||
choices, err := PkgPrompt(pkgs, verb, interactive, t)
|
||||
choices, err := PkgPrompt(pkgs, verb, interactive)
|
||||
if err != nil {
|
||||
log.Fatal("Error prompting for choice of package").Send()
|
||||
}
|
||||
|
@ -116,7 +117,7 @@ func FlattenPkgs(found map[string][]db.Package, verb string, interactive bool, t
|
|||
|
||||
// PkgPrompt asks the user to choose between multiple packages.
|
||||
// The user may choose multiple packages.
|
||||
func PkgPrompt(options []db.Package, verb string, interactive bool, t translate.Translator) ([]db.Package, error) {
|
||||
func PkgPrompt(options []db.Package, verb string, interactive bool) ([]db.Package, error) {
|
||||
if !interactive {
|
||||
return []db.Package{options[0]}, nil
|
||||
}
|
||||
|
@ -128,7 +129,7 @@ func PkgPrompt(options []db.Package, verb string, interactive bool, t translate.
|
|||
|
||||
prompt := &survey.MultiSelect{
|
||||
Options: names,
|
||||
Message: t.TranslateTo("Choose which package(s) to "+verb, config.Language),
|
||||
Message: translations.Translator().TranslateTo("Choose which package(s) to "+verb, config.Language()),
|
||||
}
|
||||
|
||||
var choices []int
|
||||
|
|
|
@ -22,10 +22,11 @@ import (
|
|||
"os"
|
||||
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
"go.elara.ws/logger/log"
|
||||
"go.elara.ws/lure/internal/types"
|
||||
)
|
||||
|
||||
var defaultConfig = types.Config{
|
||||
var defaultConfig = &types.Config{
|
||||
RootCmd: "sudo",
|
||||
PagerStyle: "native",
|
||||
IgnorePkgUpdates: []string{},
|
||||
|
@ -37,18 +38,30 @@ var defaultConfig = types.Config{
|
|||
},
|
||||
}
|
||||
|
||||
// Decode decodes the config file into the given
|
||||
// pointer
|
||||
func Decode(cfg *types.Config) error {
|
||||
cfgFl, err := os.Open(ConfigPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cfgFl.Close()
|
||||
var config *types.Config
|
||||
|
||||
// Write defaults to pointer in case some values are not set in the config
|
||||
*cfg = defaultConfig
|
||||
// Set repos to nil so as to avoid a duplicate default
|
||||
cfg.Repos = nil
|
||||
return toml.NewDecoder(cfgFl).Decode(cfg)
|
||||
func Config() *types.Config {
|
||||
if config == nil {
|
||||
cfgFl, err := os.Open(GetPaths().ConfigPath)
|
||||
if err != nil {
|
||||
log.Warn("Error opening config file, using defaults").Err(err).Send()
|
||||
return defaultConfig
|
||||
}
|
||||
defer cfgFl.Close()
|
||||
|
||||
// Copy the default configuration into config
|
||||
defCopy := *defaultConfig
|
||||
config = &defCopy
|
||||
config.Repos = nil
|
||||
|
||||
err = toml.NewDecoder(cfgFl).Decode(config)
|
||||
if err != nil {
|
||||
log.Warn("Error decoding config file, using defaults").Err(err).Send()
|
||||
// Set config back to nil so that we try again next time
|
||||
config = nil
|
||||
return defaultConfig
|
||||
}
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
|
|
@ -26,69 +26,69 @@ import (
|
|||
"go.elara.ws/logger/log"
|
||||
)
|
||||
|
||||
var (
|
||||
type Paths struct {
|
||||
ConfigDir string
|
||||
ConfigPath string
|
||||
CacheDir string
|
||||
RepoDir string
|
||||
PkgsDir string
|
||||
DBPath string
|
||||
)
|
||||
|
||||
// DBPresent is true if the database
|
||||
// was present when LURE was started
|
||||
var DBPresent bool
|
||||
|
||||
func init() {
|
||||
cfgDir, err := os.UserConfigDir()
|
||||
if err != nil {
|
||||
log.Fatal("Unable to detect user config directory").Err(err).Send()
|
||||
}
|
||||
|
||||
ConfigDir = filepath.Join(cfgDir, "lure")
|
||||
|
||||
err = os.MkdirAll(ConfigDir, 0o755)
|
||||
if err != nil {
|
||||
log.Fatal("Unable to create LURE config directory").Err(err).Send()
|
||||
}
|
||||
|
||||
ConfigPath = filepath.Join(ConfigDir, "lure.toml")
|
||||
|
||||
if _, err := os.Stat(ConfigPath); err != nil {
|
||||
cfgFl, err := os.Create(ConfigPath)
|
||||
if err != nil {
|
||||
log.Fatal("Unable to create LURE config file").Err(err).Send()
|
||||
}
|
||||
|
||||
err = toml.NewEncoder(cfgFl).Encode(&defaultConfig)
|
||||
if err != nil {
|
||||
log.Fatal("Error encoding default configuration").Err(err).Send()
|
||||
}
|
||||
|
||||
cfgFl.Close()
|
||||
}
|
||||
|
||||
cacheDir, err := os.UserCacheDir()
|
||||
if err != nil {
|
||||
log.Fatal("Unable to detect cache directory").Err(err).Send()
|
||||
}
|
||||
|
||||
CacheDir = filepath.Join(cacheDir, "lure")
|
||||
RepoDir = filepath.Join(CacheDir, "repo")
|
||||
PkgsDir = filepath.Join(CacheDir, "pkgs")
|
||||
|
||||
err = os.MkdirAll(RepoDir, 0o755)
|
||||
if err != nil {
|
||||
log.Fatal("Unable to create repo cache directory").Err(err).Send()
|
||||
}
|
||||
|
||||
err = os.MkdirAll(PkgsDir, 0o755)
|
||||
if err != nil {
|
||||
log.Fatal("Unable to create package cache directory").Err(err).Send()
|
||||
}
|
||||
|
||||
DBPath = filepath.Join(CacheDir, "db")
|
||||
|
||||
fi, err := os.Stat(DBPath)
|
||||
DBPresent = err == nil && !fi.IsDir()
|
||||
}
|
||||
|
||||
var paths *Paths
|
||||
|
||||
func GetPaths() *Paths {
|
||||
if paths == nil {
|
||||
paths = &Paths{}
|
||||
|
||||
cfgDir, err := os.UserConfigDir()
|
||||
if err != nil {
|
||||
log.Fatal("Unable to detect user config directory").Err(err).Send()
|
||||
}
|
||||
|
||||
paths.ConfigDir = filepath.Join(cfgDir, "lure")
|
||||
|
||||
err = os.MkdirAll(paths.ConfigDir, 0o755)
|
||||
if err != nil {
|
||||
log.Fatal("Unable to create LURE config directory").Err(err).Send()
|
||||
}
|
||||
|
||||
paths.ConfigPath = filepath.Join(paths.ConfigDir, "lure.toml")
|
||||
|
||||
if _, err := os.Stat(paths.ConfigPath); err != nil {
|
||||
cfgFl, err := os.Create(paths.ConfigPath)
|
||||
if err != nil {
|
||||
log.Fatal("Unable to create LURE config file").Err(err).Send()
|
||||
}
|
||||
|
||||
err = toml.NewEncoder(cfgFl).Encode(&defaultConfig)
|
||||
if err != nil {
|
||||
log.Fatal("Error encoding default configuration").Err(err).Send()
|
||||
}
|
||||
|
||||
cfgFl.Close()
|
||||
}
|
||||
|
||||
cacheDir, err := os.UserCacheDir()
|
||||
if err != nil {
|
||||
log.Fatal("Unable to detect cache directory").Err(err).Send()
|
||||
}
|
||||
|
||||
paths.CacheDir = filepath.Join(cacheDir, "lure")
|
||||
paths.RepoDir = filepath.Join(paths.CacheDir, "repo")
|
||||
paths.PkgsDir = filepath.Join(paths.CacheDir, "pkgs")
|
||||
|
||||
err = os.MkdirAll(paths.RepoDir, 0o755)
|
||||
if err != nil {
|
||||
log.Fatal("Unable to create repo cache directory").Err(err).Send()
|
||||
}
|
||||
|
||||
err = os.MkdirAll(paths.PkgsDir, 0o755)
|
||||
if err != nil {
|
||||
log.Fatal("Unable to create package cache directory").Err(err).Send()
|
||||
}
|
||||
|
||||
paths.DBPath = filepath.Join(paths.CacheDir, "db")
|
||||
}
|
||||
return paths
|
||||
}
|
||||
|
|
|
@ -26,16 +26,23 @@ import (
|
|||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
var Language language.Tag
|
||||
var (
|
||||
lang language.Tag
|
||||
langSet bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
lang := SystemLang()
|
||||
tag, err := language.Parse(lang)
|
||||
if err != nil {
|
||||
log.Fatal("Error parsing system language").Err(err).Send()
|
||||
func Language() language.Tag {
|
||||
if !langSet {
|
||||
syslang := SystemLang()
|
||||
tag, err := language.Parse(syslang)
|
||||
if err != nil {
|
||||
log.Fatal("Error parsing system language").Err(err).Send()
|
||||
}
|
||||
base, _ := tag.Base()
|
||||
lang = language.Make(base.String())
|
||||
langSet = true
|
||||
}
|
||||
base, _ := tag.Base()
|
||||
Language = language.Make(base.String())
|
||||
return lang
|
||||
}
|
||||
|
||||
func SystemLang() string {
|
||||
|
|
|
@ -20,5 +20,7 @@ package config
|
|||
|
||||
import _ "embed"
|
||||
|
||||
//go:generate ../../scripts/gen-version.sh
|
||||
|
||||
//go:embed version.txt
|
||||
var Version string
|
||||
|
|
|
@ -21,14 +21,15 @@ package cpu
|
|||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/sys/cpu"
|
||||
)
|
||||
|
||||
// ARMVariant checks which variant of ARM lure is running
|
||||
// armVariant checks which variant of ARM lure is running
|
||||
// on, by using the same detection method as Go itself
|
||||
func ARMVariant() string {
|
||||
func armVariant() string {
|
||||
armEnv := os.Getenv("LURE_ARM_VARIANT")
|
||||
// ensure value has "arm" prefix, such as arm5 or arm6
|
||||
if strings.HasPrefix(armEnv, "arm") {
|
||||
|
@ -44,34 +45,6 @@ func ARMVariant() string {
|
|||
}
|
||||
}
|
||||
|
||||
// CompatibleARM returns all the compatible ARM variants given the system architecture
|
||||
func CompatibleARM(variant string) []string {
|
||||
switch variant {
|
||||
case "arm7", "arm":
|
||||
return []string{"arm7", "arm6", "arm5"}
|
||||
case "arm6":
|
||||
return []string{"arm6", "arm5"}
|
||||
case "arm5":
|
||||
return []string{"arm5"}
|
||||
default:
|
||||
return []string{variant}
|
||||
}
|
||||
}
|
||||
|
||||
// CompatibleARMReverse returns all the compatible ARM variants given the package's architecture
|
||||
func CompatibleARMReverse(variant string) []string {
|
||||
switch variant {
|
||||
case "arm7":
|
||||
return []string{"arm7"}
|
||||
case "arm6":
|
||||
return []string{"arm6", "arm7"}
|
||||
case "arm5", "arm":
|
||||
return []string{"arm5", "arm6", "arm7"}
|
||||
default:
|
||||
return []string{variant}
|
||||
}
|
||||
}
|
||||
|
||||
// Arch returns the canonical CPU architecture of the system
|
||||
func Arch() string {
|
||||
arch := os.Getenv("LURE_ARCH")
|
||||
|
@ -79,20 +52,65 @@ func Arch() string {
|
|||
arch = runtime.GOARCH
|
||||
}
|
||||
if arch == "arm" {
|
||||
arch = ARMVariant()
|
||||
arch = armVariant()
|
||||
}
|
||||
return arch
|
||||
}
|
||||
|
||||
// Arches returns all the architectures the system is compatible with
|
||||
func Arches() []string {
|
||||
arch := os.Getenv("LURE_ARCH")
|
||||
if arch == "" {
|
||||
arch = runtime.GOARCH
|
||||
func IsCompatibleWith(target string, list []string) bool {
|
||||
if target == "all" {
|
||||
return true
|
||||
}
|
||||
if strings.HasPrefix(arch, "arm") {
|
||||
return append(CompatibleARM(arch), "arm")
|
||||
} else {
|
||||
return []string{Arch()}
|
||||
|
||||
for _, arch := range list {
|
||||
if strings.HasPrefix(target, "arm") && strings.HasPrefix(arch, "arm") {
|
||||
targetVer, err := getARMVersion(target)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
archVer, err := getARMVersion(arch)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if targetVer >= archVer {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if target == arch {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func CompatibleArches(arch string) ([]string, error) {
|
||||
if strings.HasPrefix(arch, "arm") {
|
||||
ver, err := getARMVersion(arch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ver > 5 {
|
||||
var out []string
|
||||
for i := ver; i >= 5; i-- {
|
||||
out = append(out, "arm"+strconv.Itoa(i))
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
}
|
||||
|
||||
return []string{arch}, nil
|
||||
}
|
||||
|
||||
func getARMVersion(arch string) (int, error) {
|
||||
// Extract the version number from ARM architecture
|
||||
version := strings.TrimPrefix(arch, "arm")
|
||||
if version == "" {
|
||||
return 5, nil // Default to arm5 if version is not specified
|
||||
}
|
||||
return strconv.Atoi(version)
|
||||
}
|
||||
|
|
|
@ -62,40 +62,52 @@ type version struct {
|
|||
Version int `db:"version"`
|
||||
}
|
||||
|
||||
func Open(dsn string) (*sqlx.DB, error) {
|
||||
if dsn != ":memory:" {
|
||||
fi, err := os.Stat(config.DBPath)
|
||||
if err == nil {
|
||||
// TODO: This should be removed by the first stable release.
|
||||
if fi.IsDir() {
|
||||
log.Warn("Your database is using the old database engine; rebuilding").Send()
|
||||
var (
|
||||
conn *sqlx.DB
|
||||
closed = true
|
||||
)
|
||||
|
||||
err = os.RemoveAll(config.DBPath)
|
||||
if err != nil {
|
||||
log.Fatal("Error removing old database").Err(err).Send()
|
||||
}
|
||||
config.DBPresent = false
|
||||
}
|
||||
}
|
||||
func DB() *sqlx.DB {
|
||||
if conn != nil && !closed {
|
||||
return conn
|
||||
}
|
||||
|
||||
db, err := sqlx.Open("sqlite", dsn)
|
||||
db, err := Open(config.GetPaths().DBPath)
|
||||
if err != nil {
|
||||
log.Fatal("Error opening database").Err(err).Send()
|
||||
}
|
||||
conn = db
|
||||
return conn
|
||||
}
|
||||
|
||||
err = Init(db, dsn)
|
||||
func Open(dsn string) (*sqlx.DB, error) {
|
||||
db, err := sqlx.Open("sqlite", dsn)
|
||||
if err != nil {
|
||||
log.Fatal("Error initializing database").Err(err).Send()
|
||||
return nil, err
|
||||
}
|
||||
conn = db
|
||||
closed = false
|
||||
|
||||
err = initDB(dsn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return db, nil
|
||||
}
|
||||
|
||||
func Close() error {
|
||||
closed = true
|
||||
if conn != nil {
|
||||
return conn.Close()
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Init initializes the database
|
||||
func Init(db *sqlx.DB, dsn string) error {
|
||||
*db = *db.Unsafe()
|
||||
_, err := db.Exec(`
|
||||
func initDB(dsn string) error {
|
||||
conn = conn.Unsafe()
|
||||
_, err := conn.Exec(`
|
||||
CREATE TABLE IF NOT EXISTS pkgs (
|
||||
name TEXT NOT NULL,
|
||||
repository TEXT NOT NULL,
|
||||
|
@ -123,49 +135,57 @@ func Init(db *sqlx.DB, dsn string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
ver, ok := GetVersion(db)
|
||||
ver, ok := GetVersion()
|
||||
if !ok {
|
||||
log.Warn("Database version does not exist. Run lure fix if something isn't working.").Send()
|
||||
return addVersion(db, CurrentVersion)
|
||||
return addVersion(CurrentVersion)
|
||||
}
|
||||
|
||||
if ver != CurrentVersion {
|
||||
log.Warn("Database version mismatch; rebuilding").Int("version", ver).Int("expected", CurrentVersion).Send()
|
||||
|
||||
db.Close()
|
||||
err = os.Remove(config.DBPath)
|
||||
conn.Close()
|
||||
err = os.Remove(config.GetPaths().DBPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config.DBPresent = false
|
||||
|
||||
tdb, err := Open(dsn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*db = *tdb
|
||||
conn = tdb
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetVersion(db *sqlx.DB) (int, bool) {
|
||||
func IsEmpty() bool {
|
||||
var count int
|
||||
err := DB().Get(&count, "SELECT count(1) FROM pkgs;")
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
return count == 0
|
||||
}
|
||||
|
||||
func GetVersion() (int, bool) {
|
||||
var ver version
|
||||
err := db.Get(&ver, "SELECT * FROM lure_db_version LIMIT 1;")
|
||||
err := DB().Get(&ver, "SELECT * FROM lure_db_version LIMIT 1;")
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
return ver.Version, true
|
||||
}
|
||||
|
||||
func addVersion(db *sqlx.DB, ver int) error {
|
||||
_, err := db.Exec(`INSERT INTO lure_db_version(version) VALUES (?);`, ver)
|
||||
func addVersion(ver int) error {
|
||||
_, err := DB().Exec(`INSERT INTO lure_db_version(version) VALUES (?);`, ver)
|
||||
return err
|
||||
}
|
||||
|
||||
// InsertPackage adds a package to the database
|
||||
func InsertPackage(db *sqlx.DB, pkg Package) error {
|
||||
_, err := db.NamedExec(`
|
||||
func InsertPackage(pkg Package) error {
|
||||
_, err := DB().NamedExec(`
|
||||
INSERT OR REPLACE INTO pkgs (
|
||||
name,
|
||||
repository,
|
||||
|
@ -204,8 +224,8 @@ func InsertPackage(db *sqlx.DB, pkg Package) error {
|
|||
}
|
||||
|
||||
// GetPkgs returns a result containing packages that match the where conditions
|
||||
func GetPkgs(db *sqlx.DB, where string, args ...any) (*sqlx.Rows, error) {
|
||||
stream, err := db.Queryx("SELECT * FROM pkgs WHERE "+where, args...)
|
||||
func GetPkgs(where string, args ...any) (*sqlx.Rows, error) {
|
||||
stream, err := DB().Queryx("SELECT * FROM pkgs WHERE "+where, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -213,15 +233,15 @@ func GetPkgs(db *sqlx.DB, where string, args ...any) (*sqlx.Rows, error) {
|
|||
}
|
||||
|
||||
// GetPkg returns a single package that match the where conditions
|
||||
func GetPkg(db *sqlx.DB, where string, args ...any) (*Package, error) {
|
||||
func GetPkg(where string, args ...any) (*Package, error) {
|
||||
out := &Package{}
|
||||
err := db.Get(out, "SELECT * FROM pkgs WHERE "+where+" LIMIT 1", args...)
|
||||
err := DB().Get(out, "SELECT * FROM pkgs WHERE "+where+" LIMIT 1", args...)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// DeletePkgs deletes all packages matching the where conditions
|
||||
func DeletePkgs(db *sqlx.DB, where string, args ...any) error {
|
||||
_, err := db.Exec("DELETE FROM pkgs WHERE "+where, args...)
|
||||
func DeletePkgs(where string, args ...any) error {
|
||||
_, err := DB().Exec("DELETE FROM pkgs WHERE "+where, args...)
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
@ -37,11 +37,11 @@ var testPkg = db.Package{
|
|||
"ru": "Проверочный пакет",
|
||||
}),
|
||||
Homepage: db.NewJSON(map[string]string{
|
||||
"en": "https://lure.arsenm.dev",
|
||||
"en": "https://lure.elara.ws/",
|
||||
}),
|
||||
Maintainer: db.NewJSON(map[string]string{
|
||||
"en": "Arsen Musayelyan <arsen@arsenm.dev>",
|
||||
"ru": "Арсен Мусаелян <arsen@arsenm.dev>",
|
||||
"en": "Elara Musayelyan <elara@elara.ws>",
|
||||
"ru": "Элара Мусаелян <arsen@arsenm.dev>",
|
||||
}),
|
||||
Architectures: db.NewJSON([]string{"arm64", "amd64"}),
|
||||
Licenses: db.NewJSON([]string{"GPL-3.0-or-later"}),
|
||||
|
@ -59,23 +59,18 @@ var testPkg = db.Package{
|
|||
}
|
||||
|
||||
func TestInit(t *testing.T) {
|
||||
gdb, err := sqlx.Open("sqlite", ":memory:")
|
||||
_, err := db.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
defer gdb.Close()
|
||||
defer db.Close()
|
||||
|
||||
err = db.Init(gdb, ":memory:")
|
||||
_, err = db.DB().Exec("SELECT * FROM pkgs")
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
|
||||
_, err = gdb.Exec("SELECT * FROM pkgs")
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
|
||||
ver, ok := db.GetVersion(gdb)
|
||||
ver, ok := db.GetVersion()
|
||||
if !ok {
|
||||
t.Errorf("Expected version to be present")
|
||||
} else if ver != db.CurrentVersion {
|
||||
|
@ -84,19 +79,19 @@ func TestInit(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestInsertPackage(t *testing.T) {
|
||||
gdb, err := db.Open(":memory:")
|
||||
_, err := db.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
defer gdb.Close()
|
||||
defer db.Close()
|
||||
|
||||
err = db.InsertPackage(gdb, testPkg)
|
||||
err = db.InsertPackage(testPkg)
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
|
||||
dbPkg := db.Package{}
|
||||
err = sqlx.Get(gdb, &dbPkg, "SELECT * FROM pkgs WHERE name = 'test' AND repository = 'default'")
|
||||
err = sqlx.Get(db.DB(), &dbPkg, "SELECT * FROM pkgs WHERE name = 'test' AND repository = 'default'")
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
|
@ -107,28 +102,28 @@ func TestInsertPackage(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestGetPkgs(t *testing.T) {
|
||||
gdb, err := db.Open(":memory:")
|
||||
_, err := db.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
defer gdb.Close()
|
||||
defer db.Close()
|
||||
|
||||
x1 := testPkg
|
||||
x1.Name = "x1"
|
||||
x2 := testPkg
|
||||
x2.Name = "x2"
|
||||
|
||||
err = db.InsertPackage(gdb, x1)
|
||||
err = db.InsertPackage(x1)
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, got %s", err)
|
||||
}
|
||||
|
||||
err = db.InsertPackage(gdb, x2)
|
||||
err = db.InsertPackage(x2)
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, got %s", err)
|
||||
}
|
||||
|
||||
result, err := db.GetPkgs(gdb, "name LIKE 'x%'")
|
||||
result, err := db.GetPkgs("name LIKE 'x%'")
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
|
@ -147,28 +142,28 @@ func TestGetPkgs(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestGetPkg(t *testing.T) {
|
||||
gdb, err := db.Open(":memory:")
|
||||
_, err := db.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
defer gdb.Close()
|
||||
defer db.Close()
|
||||
|
||||
x1 := testPkg
|
||||
x1.Name = "x1"
|
||||
x2 := testPkg
|
||||
x2.Name = "x2"
|
||||
|
||||
err = db.InsertPackage(gdb, x1)
|
||||
err = db.InsertPackage(x1)
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, got %s", err)
|
||||
}
|
||||
|
||||
err = db.InsertPackage(gdb, x2)
|
||||
err = db.InsertPackage(x2)
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, got %s", err)
|
||||
}
|
||||
|
||||
pkg, err := db.GetPkg(gdb, "name LIKE 'x%' ORDER BY name")
|
||||
pkg, err := db.GetPkg("name LIKE 'x%' ORDER BY name")
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
|
@ -183,34 +178,34 @@ func TestGetPkg(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestDeletePkgs(t *testing.T) {
|
||||
gdb, err := db.Open(":memory:")
|
||||
_, err := db.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
defer gdb.Close()
|
||||
defer db.Close()
|
||||
|
||||
x1 := testPkg
|
||||
x1.Name = "x1"
|
||||
x2 := testPkg
|
||||
x2.Name = "x2"
|
||||
|
||||
err = db.InsertPackage(gdb, x1)
|
||||
err = db.InsertPackage(x1)
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, got %s", err)
|
||||
}
|
||||
|
||||
err = db.InsertPackage(gdb, x2)
|
||||
err = db.InsertPackage(x2)
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, got %s", err)
|
||||
}
|
||||
|
||||
err = db.DeletePkgs(gdb, "name = 'x1'")
|
||||
err = db.DeletePkgs("name = 'x1'")
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, got %s", err)
|
||||
}
|
||||
|
||||
var dbPkg db.Package
|
||||
err = gdb.Get(&dbPkg, "SELECT * FROM pkgs WHERE name LIKE 'x%' ORDER BY name LIMIT 1;")
|
||||
err = db.DB().Get(&dbPkg, "SELECT * FROM pkgs WHERE name LIKE 'x%' ORDER BY name LIMIT 1;")
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, got %s", err)
|
||||
}
|
||||
|
@ -221,11 +216,11 @@ func TestDeletePkgs(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestJsonArrayContains(t *testing.T) {
|
||||
gdb, err := db.Open(":memory:")
|
||||
_, err := db.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
defer gdb.Close()
|
||||
defer db.Close()
|
||||
|
||||
x1 := testPkg
|
||||
x1.Name = "x1"
|
||||
|
@ -233,18 +228,18 @@ func TestJsonArrayContains(t *testing.T) {
|
|||
x2.Name = "x2"
|
||||
x2.Provides.Val = append(x2.Provides.Val, "x")
|
||||
|
||||
err = db.InsertPackage(gdb, x1)
|
||||
err = db.InsertPackage(x1)
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, got %s", err)
|
||||
}
|
||||
|
||||
err = db.InsertPackage(gdb, x2)
|
||||
err = db.InsertPackage(x2)
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, got %s", err)
|
||||
}
|
||||
|
||||
var dbPkg db.Package
|
||||
err = gdb.Get(&dbPkg, "SELECT * FROM pkgs WHERE json_array_contains(provides, 'x');")
|
||||
err = db.DB().Get(&dbPkg, "SELECT * FROM pkgs WHERE json_array_contains(provides, 'x');")
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
|
|
|
@ -28,8 +28,10 @@ import (
|
|||
"go.elara.ws/lure/internal/config"
|
||||
)
|
||||
|
||||
// BasePath stores the base path to the download cache
|
||||
var BasePath = filepath.Join(config.CacheDir, "dl")
|
||||
// BasePath returns the base path of the download cache
|
||||
func BasePath() string {
|
||||
return filepath.Join(config.GetPaths().RepoDir, "dl")
|
||||
}
|
||||
|
||||
// New creates a new directory with the given ID in the cache.
|
||||
// If a directory with the same ID already exists,
|
||||
|
@ -39,7 +41,7 @@ func New(id string) (string, error) {
|
|||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
itemPath := filepath.Join(BasePath, h)
|
||||
itemPath := filepath.Join(BasePath(), h)
|
||||
|
||||
fi, err := os.Stat(itemPath)
|
||||
if err == nil || (fi != nil && !fi.IsDir()) {
|
||||
|
@ -67,7 +69,7 @@ func Get(id string) (string, bool) {
|
|||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
itemPath := filepath.Join(BasePath, h)
|
||||
itemPath := filepath.Join(BasePath(), h)
|
||||
|
||||
_, err = os.Stat(itemPath)
|
||||
if err != nil {
|
||||
|
|
|
@ -26,6 +26,7 @@ import (
|
|||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"go.elara.ws/lure/internal/config"
|
||||
"go.elara.ws/lure/internal/dlcache"
|
||||
)
|
||||
|
||||
|
@ -34,7 +35,7 @@ func init() {
|
|||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
dlcache.BasePath = dir
|
||||
config.GetPaths().RepoDir = dir
|
||||
}
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
|
@ -44,7 +45,7 @@ func TestNew(t *testing.T) {
|
|||
t.Errorf("Expected no error, got %s", err)
|
||||
}
|
||||
|
||||
exp := filepath.Join(dlcache.BasePath, sha1sum(id))
|
||||
exp := filepath.Join(dlcache.BasePath(), sha1sum(id))
|
||||
if dir != exp {
|
||||
t.Errorf("Expected %s, got %s", exp, dir)
|
||||
}
|
||||
|
|
|
@ -58,7 +58,10 @@ func Resolve(info *distro.OSRelease, opts *Opts) ([]string, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
architectures := cpu.Arches()
|
||||
architectures, err := cpu.CompatibleArches(cpu.Arch())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
distros := []string{info.ID}
|
||||
if opts.LikeDistros {
|
||||
|
@ -66,47 +69,38 @@ func Resolve(info *distro.OSRelease, opts *Opts) ([]string, error) {
|
|||
}
|
||||
|
||||
var out []string
|
||||
for _, arch := range architectures {
|
||||
for _, lang := range langs {
|
||||
for _, distro := range distros {
|
||||
if opts.Name == "" {
|
||||
out = append(
|
||||
out,
|
||||
arch+"_"+distro,
|
||||
distro,
|
||||
)
|
||||
} else {
|
||||
out = append(
|
||||
out,
|
||||
opts.Name+"_"+arch+"_"+distro,
|
||||
opts.Name+"_"+distro,
|
||||
)
|
||||
for _, arch := range architectures {
|
||||
out = append(out, opts.Name+"_"+arch+"_"+distro+"_"+lang)
|
||||
}
|
||||
|
||||
out = append(out, opts.Name+"_"+distro+"_"+lang)
|
||||
}
|
||||
if opts.Name == "" {
|
||||
out = append(out, arch)
|
||||
} else {
|
||||
out = append(out, opts.Name+"_"+arch)
|
||||
|
||||
for _, arch := range architectures {
|
||||
out = append(out, opts.Name+"_"+arch+"_"+lang)
|
||||
}
|
||||
|
||||
out = append(out, opts.Name+"_"+lang)
|
||||
}
|
||||
|
||||
for _, distro := range distros {
|
||||
for _, arch := range architectures {
|
||||
out = append(out, opts.Name+"_"+arch+"_"+distro)
|
||||
}
|
||||
|
||||
out = append(out, opts.Name+"_"+distro)
|
||||
}
|
||||
|
||||
for _, arch := range architectures {
|
||||
out = append(out, opts.Name+"_"+arch)
|
||||
}
|
||||
|
||||
out = append(out, opts.Name)
|
||||
|
||||
for index, item := range out {
|
||||
out[index] = strings.ReplaceAll(item, "-", "_")
|
||||
}
|
||||
|
||||
if len(langs) > 0 {
|
||||
tmp := out
|
||||
out = make([]string, 0, len(tmp)+(len(tmp)*len(langs)))
|
||||
for _, lang := range langs {
|
||||
for _, val := range tmp {
|
||||
if val == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
out = append(out, val+"_"+lang)
|
||||
}
|
||||
}
|
||||
out = append(out, tmp...)
|
||||
out[index] = strings.TrimPrefix(strings.ReplaceAll(item, "-", "_"), "_")
|
||||
}
|
||||
|
||||
return out, nil
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
package overrides_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
|
@ -46,6 +47,7 @@ func TestResolve(t *testing.T) {
|
|||
"amd64_fedora_en",
|
||||
"fedora_en",
|
||||
"amd64_en",
|
||||
"en",
|
||||
"amd64_centos",
|
||||
"centos",
|
||||
"amd64_rhel",
|
||||
|
@ -87,6 +89,43 @@ func TestResolveName(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestResolveArch(t *testing.T) {
|
||||
os.Setenv("LURE_ARCH", "arm7")
|
||||
defer os.Setenv("LURE_ARCH", "")
|
||||
|
||||
names, err := overrides.Resolve(info, &overrides.Opts{
|
||||
Name: "deps",
|
||||
Overrides: true,
|
||||
LikeDistros: true,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
|
||||
expected := []string{
|
||||
"deps_arm7_centos",
|
||||
"deps_arm6_centos",
|
||||
"deps_arm5_centos",
|
||||
"deps_centos",
|
||||
"deps_arm7_rhel",
|
||||
"deps_arm6_rhel",
|
||||
"deps_arm5_rhel",
|
||||
"deps_rhel",
|
||||
"deps_arm7_fedora",
|
||||
"deps_arm6_fedora",
|
||||
"deps_arm5_fedora",
|
||||
"deps_fedora",
|
||||
"deps_arm7",
|
||||
"deps_arm6",
|
||||
"deps_arm5",
|
||||
"deps",
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(names, expected) {
|
||||
t.Errorf("expected %v, got %v", expected, names)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveNoLikeDistros(t *testing.T) {
|
||||
names, err := overrides.Resolve(info, &overrides.Opts{
|
||||
Overrides: true,
|
||||
|
@ -139,9 +178,11 @@ func TestResolveLangs(t *testing.T) {
|
|||
"amd64_centos_en",
|
||||
"centos_en",
|
||||
"amd64_en",
|
||||
"en",
|
||||
"amd64_centos_ru",
|
||||
"centos_ru",
|
||||
"amd64_ru",
|
||||
"ru",
|
||||
"amd64_centos",
|
||||
"centos",
|
||||
"amd64",
|
||||
|
|
|
@ -34,13 +34,13 @@ var (
|
|||
)
|
||||
|
||||
func init() {
|
||||
b := lipgloss.RoundedBorder()
|
||||
b.Right = "\u251C"
|
||||
titleStyle = lipgloss.NewStyle().BorderStyle(b).Padding(0, 1)
|
||||
b1 := lipgloss.RoundedBorder()
|
||||
b1.Right = "\u251C"
|
||||
titleStyle = lipgloss.NewStyle().BorderStyle(b1).Padding(0, 1)
|
||||
|
||||
b = lipgloss.RoundedBorder()
|
||||
b.Left = "\u2524"
|
||||
infoStyle = titleStyle.Copy().BorderStyle(b)
|
||||
b2 := lipgloss.RoundedBorder()
|
||||
b2.Left = "\u2524"
|
||||
infoStyle = titleStyle.Copy().BorderStyle(b2)
|
||||
}
|
||||
|
||||
type Pager struct {
|
||||
|
|
|
@ -18,15 +18,12 @@
|
|||
|
||||
package repos
|
||||
|
||||
import (
|
||||
"github.com/jmoiron/sqlx"
|
||||
"go.elara.ws/lure/internal/db"
|
||||
)
|
||||
import "go.elara.ws/lure/internal/db"
|
||||
|
||||
// FindPkgs looks for packages matching the inputs inside the database.
|
||||
// It returns a map that maps the package name input to the packages found for it.
|
||||
// It also returns a slice that contains the names of all packages that were not found.
|
||||
func FindPkgs(gdb *sqlx.DB, pkgs []string) (map[string][]db.Package, []string, error) {
|
||||
func FindPkgs(pkgs []string) (map[string][]db.Package, []string, error) {
|
||||
found := map[string][]db.Package{}
|
||||
notFound := []string(nil)
|
||||
|
||||
|
@ -35,7 +32,7 @@ func FindPkgs(gdb *sqlx.DB, pkgs []string) (map[string][]db.Package, []string, e
|
|||
continue
|
||||
}
|
||||
|
||||
result, err := db.GetPkgs(gdb, "name LIKE ?", pkgName)
|
||||
result, err := db.GetPkgs("name LIKE ?", pkgName)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -54,7 +51,7 @@ func FindPkgs(gdb *sqlx.DB, pkgs []string) (map[string][]db.Package, []string, e
|
|||
result.Close()
|
||||
|
||||
if added == 0 {
|
||||
result, err := db.GetPkgs(gdb, "json_array_contains(provides, ?)", pkgName)
|
||||
result, err := db.GetPkgs("json_array_contains(provides, ?)", pkgName)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
|
|
@ -30,18 +30,18 @@ import (
|
|||
)
|
||||
|
||||
func TestFindPkgs(t *testing.T) {
|
||||
gdb, err := db.Open(":memory:")
|
||||
_, err := db.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
defer gdb.Close()
|
||||
defer db.Close()
|
||||
|
||||
setCfgDirs(t)
|
||||
defer removeCacheDir(t)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
err = repos.Pull(ctx, gdb, []types.Repo{
|
||||
err = repos.Pull(ctx, []types.Repo{
|
||||
{
|
||||
Name: "default",
|
||||
URL: "https://github.com/Arsen6331/lure-repo.git",
|
||||
|
@ -51,7 +51,7 @@ func TestFindPkgs(t *testing.T) {
|
|||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
|
||||
found, notFound, err := repos.FindPkgs(gdb, []string{"itd", "nonexistentpackage1", "nonexistentpackage2"})
|
||||
found, notFound, err := repos.FindPkgs([]string{"itd", "nonexistentpackage1", "nonexistentpackage2"})
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
|
@ -81,16 +81,16 @@ func TestFindPkgs(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestFindPkgsEmpty(t *testing.T) {
|
||||
gdb, err := db.Open(":memory:")
|
||||
_, err := db.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
defer gdb.Close()
|
||||
defer db.Close()
|
||||
|
||||
setCfgDirs(t)
|
||||
defer removeCacheDir(t)
|
||||
|
||||
err = db.InsertPackage(gdb, db.Package{
|
||||
err = db.InsertPackage(db.Package{
|
||||
Name: "test1",
|
||||
Repository: "default",
|
||||
Version: "0.0.1",
|
||||
|
@ -105,7 +105,7 @@ func TestFindPkgsEmpty(t *testing.T) {
|
|||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
|
||||
err = db.InsertPackage(gdb, db.Package{
|
||||
err = db.InsertPackage(db.Package{
|
||||
Name: "test2",
|
||||
Repository: "default",
|
||||
Version: "0.0.1",
|
||||
|
@ -120,7 +120,7 @@ func TestFindPkgsEmpty(t *testing.T) {
|
|||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
|
||||
found, notFound, err := repos.FindPkgs(gdb, []string{"test", ""})
|
||||
found, notFound, err := repos.FindPkgs([]string{"test", ""})
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
|
|
|
@ -33,7 +33,6 @@ import (
|
|||
"github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
"github.com/go-git/go-git/v5/plumbing/format/diff"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
"go.elara.ws/logger/log"
|
||||
"go.elara.ws/lure/distro"
|
||||
|
@ -51,7 +50,7 @@ import (
|
|||
// Pull pulls the provided repositories. If a repo doesn't exist, it will be cloned
|
||||
// and its packages will be written to the DB. If it does exist, it will be pulled.
|
||||
// In this case, only changed packages will be processed.
|
||||
func Pull(ctx context.Context, gdb *sqlx.DB, repos []types.Repo) error {
|
||||
func Pull(ctx context.Context, repos []types.Repo) error {
|
||||
for _, repo := range repos {
|
||||
repoURL, err := url.Parse(repo.URL)
|
||||
if err != nil {
|
||||
|
@ -59,7 +58,7 @@ func Pull(ctx context.Context, gdb *sqlx.DB, repos []types.Repo) error {
|
|||
}
|
||||
|
||||
log.Info("Pulling repository").Str("name", repo.Name).Send()
|
||||
repoDir := filepath.Join(config.RepoDir, repo.Name)
|
||||
repoDir := filepath.Join(config.GetPaths().RepoDir, repo.Name)
|
||||
|
||||
var repoFS billy.Filesystem
|
||||
gitDir := filepath.Join(repoDir, ".git")
|
||||
|
@ -89,7 +88,7 @@ func Pull(ctx context.Context, gdb *sqlx.DB, repos []types.Repo) error {
|
|||
repoFS = w.Filesystem
|
||||
|
||||
// Make sure the DB is created even if the repo is up to date
|
||||
if !errors.Is(err, git.NoErrAlreadyUpToDate) || !config.DBPresent {
|
||||
if !errors.Is(err, git.NoErrAlreadyUpToDate) || db.IsEmpty() {
|
||||
new, err := r.Head()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -98,13 +97,13 @@ func Pull(ctx context.Context, gdb *sqlx.DB, repos []types.Repo) error {
|
|||
// If the DB was not present at startup, that means it's
|
||||
// empty. In this case, we need to update the DB fully
|
||||
// rather than just incrementally.
|
||||
if config.DBPresent {
|
||||
err = processRepoChanges(ctx, repo, r, w, old, new, gdb)
|
||||
if db.IsEmpty() {
|
||||
err = processRepoChanges(ctx, repo, r, w, old, new)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
err = processRepoFull(ctx, repo, repoDir, gdb)
|
||||
err = processRepoFull(ctx, repo, repoDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -129,7 +128,7 @@ func Pull(ctx context.Context, gdb *sqlx.DB, repos []types.Repo) error {
|
|||
return err
|
||||
}
|
||||
|
||||
err = processRepoFull(ctx, repo, repoDir, gdb)
|
||||
err = processRepoFull(ctx, repo, repoDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -171,7 +170,7 @@ type action struct {
|
|||
File string
|
||||
}
|
||||
|
||||
func processRepoChanges(ctx context.Context, repo types.Repo, r *git.Repository, w *git.Worktree, old, new *plumbing.Reference, gdb *sqlx.DB) error {
|
||||
func processRepoChanges(ctx context.Context, repo types.Repo, r *git.Repository, w *git.Worktree, old, new *plumbing.Reference) error {
|
||||
oldCommit, err := r.CommitObject(old.Hash())
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -265,7 +264,7 @@ func processRepoChanges(ctx context.Context, repo types.Repo, r *git.Repository,
|
|||
return err
|
||||
}
|
||||
|
||||
err = db.DeletePkgs(gdb, "name = ? AND repository = ?", pkg.Name, repo.Name)
|
||||
err = db.DeletePkgs("name = ? AND repository = ?", pkg.Name, repo.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -300,7 +299,7 @@ func processRepoChanges(ctx context.Context, repo types.Repo, r *git.Repository,
|
|||
|
||||
resolveOverrides(runner, &pkg)
|
||||
|
||||
err = db.InsertPackage(gdb, pkg)
|
||||
err = db.InsertPackage(pkg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -326,7 +325,7 @@ func isValid(from, to diff.File) bool {
|
|||
return match
|
||||
}
|
||||
|
||||
func processRepoFull(ctx context.Context, repo types.Repo, repoDir string, gdb *sqlx.DB) error {
|
||||
func processRepoFull(ctx context.Context, repo types.Repo, repoDir string) error {
|
||||
glob := filepath.Join(repoDir, "/*/lure.sh")
|
||||
matches, err := filepath.Glob(glob)
|
||||
if err != nil {
|
||||
|
@ -370,7 +369,7 @@ func processRepoFull(ctx context.Context, repo types.Repo, repoDir string, gdb *
|
|||
|
||||
resolveOverrides(runner, &pkg)
|
||||
|
||||
err = db.InsertPackage(gdb, pkg)
|
||||
err = db.InsertPackage(pkg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -33,50 +33,52 @@ import (
|
|||
func setCfgDirs(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
paths := config.GetPaths()
|
||||
|
||||
var err error
|
||||
config.CacheDir, err = os.MkdirTemp("/tmp", "lure-pull-test.*")
|
||||
paths.CacheDir, err = os.MkdirTemp("/tmp", "lure-pull-test.*")
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
|
||||
config.RepoDir = filepath.Join(config.CacheDir, "repo")
|
||||
config.PkgsDir = filepath.Join(config.CacheDir, "pkgs")
|
||||
paths.RepoDir = filepath.Join(paths.CacheDir, "repo")
|
||||
paths.PkgsDir = filepath.Join(paths.CacheDir, "pkgs")
|
||||
|
||||
err = os.MkdirAll(config.RepoDir, 0o755)
|
||||
err = os.MkdirAll(paths.RepoDir, 0o755)
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
|
||||
err = os.MkdirAll(config.PkgsDir, 0o755)
|
||||
err = os.MkdirAll(paths.PkgsDir, 0o755)
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
|
||||
config.DBPath = filepath.Join(config.CacheDir, "db")
|
||||
paths.DBPath = filepath.Join(paths.CacheDir, "db")
|
||||
}
|
||||
|
||||
func removeCacheDir(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
err := os.RemoveAll(config.CacheDir)
|
||||
err := os.RemoveAll(config.GetPaths().CacheDir)
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPull(t *testing.T) {
|
||||
gdb, err := db.Open(":memory:")
|
||||
_, err := db.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
defer gdb.Close()
|
||||
defer db.Close()
|
||||
|
||||
setCfgDirs(t)
|
||||
defer removeCacheDir(t)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
err = repos.Pull(ctx, gdb, []types.Repo{
|
||||
err = repos.Pull(ctx, []types.Repo{
|
||||
{
|
||||
Name: "default",
|
||||
URL: "https://github.com/Arsen6331/lure-repo.git",
|
||||
|
@ -86,7 +88,7 @@ func TestPull(t *testing.T) {
|
|||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
|
||||
result, err := db.GetPkgs(gdb, "name LIKE 'itd%'")
|
||||
result, err := db.GetPkgs("name LIKE 'itd%'")
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %s", err)
|
||||
}
|
||||
|
|
|
@ -56,16 +56,16 @@ func (ite InvalidTypeError) Error() string {
|
|||
// Decoder provides methods for decoding variable values
|
||||
type Decoder struct {
|
||||
info *distro.OSRelease
|
||||
runner *interp.Runner
|
||||
Runner *interp.Runner
|
||||
// Enable distro overrides (true by default)
|
||||
Overrides bool
|
||||
// Enable using like distros for overrides (true by default)
|
||||
// Enable using like distros for overrides
|
||||
LikeDistros bool
|
||||
}
|
||||
|
||||
// New creates a new variable decoder
|
||||
func New(info *distro.OSRelease, runner *interp.Runner) *Decoder {
|
||||
return &Decoder{info, runner, true, true}
|
||||
return &Decoder{info, runner, true, len(info.Like) > 0}
|
||||
}
|
||||
|
||||
// DecodeVar decodes a variable to val using reflection.
|
||||
|
@ -173,7 +173,7 @@ func (d *Decoder) GetFunc(name string) (ScriptFunc, bool) {
|
|||
}
|
||||
|
||||
return func(ctx context.Context, opts ...interp.RunnerOption) error {
|
||||
sub := d.runner.Subshell()
|
||||
sub := d.Runner.Subshell()
|
||||
for _, opt := range opts {
|
||||
opt(sub)
|
||||
}
|
||||
|
@ -188,7 +188,7 @@ func (d *Decoder) getFunc(name string) *syntax.Stmt {
|
|||
}
|
||||
|
||||
for _, fnName := range names {
|
||||
fn, ok := d.runner.Funcs[fnName]
|
||||
fn, ok := d.Runner.Funcs[fnName]
|
||||
if ok {
|
||||
return fn
|
||||
}
|
||||
|
@ -205,11 +205,11 @@ func (d *Decoder) getVar(name string) *expand.Variable {
|
|||
}
|
||||
|
||||
for _, varName := range names {
|
||||
val, ok := d.runner.Vars[varName]
|
||||
val, ok := d.Runner.Vars[varName]
|
||||
if ok {
|
||||
// Resolve nameref variables
|
||||
_, resolved := val.Resolve(expand.FuncEnviron(func(s string) string {
|
||||
if val, ok := d.runner.Vars[s]; ok {
|
||||
if val, ok := d.Runner.Vars[s]; ok {
|
||||
return val.String()
|
||||
}
|
||||
return ""
|
||||
|
|
|
@ -36,8 +36,8 @@ const testScript = `
|
|||
release=1
|
||||
epoch=2
|
||||
desc="Test package"
|
||||
homepage='https://lure.arsenm.dev'
|
||||
maintainer='Arsen Musayelyan <arsen@arsenm.dev>'
|
||||
homepage='https://lure.elara.ws'
|
||||
maintainer='Elara Musayelyan <elara@elara.ws>'
|
||||
architectures=('arm64' 'amd64')
|
||||
license=('GPL-3.0-or-later')
|
||||
provides=('test')
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package main
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
@ -40,7 +40,8 @@ var (
|
|||
ErrNoDetectManNum = errors.New("manual number cannot be detected from the filename")
|
||||
)
|
||||
|
||||
var helpers = shutils.ExecFuncs{
|
||||
// Helpers contains all the helper commands
|
||||
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),
|
||||
|
@ -54,8 +55,9 @@ var helpers = shutils.ExecFuncs{
|
|||
"git-version": gitVersionCmd,
|
||||
}
|
||||
|
||||
// rHelpers contains restricted read-only helpers that don't modify any state
|
||||
var rHelpers = shutils.ExecFuncs{
|
||||
// Restricted contains restricted read-only helper commands
|
||||
// that don't modify any state
|
||||
var Restricted = shutils.ExecFuncs{
|
||||
"git-version": gitVersionCmd,
|
||||
}
|
||||
|
|
@ -21,7 +21,8 @@ package shutils
|
|||
import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"io/fs"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -30,33 +31,36 @@ import (
|
|||
)
|
||||
|
||||
func RestrictedReadDir(allowedPrefixes ...string) interp.ReadDirHandlerFunc {
|
||||
return func(ctx context.Context, s string) ([]os.FileInfo, error) {
|
||||
return func(ctx context.Context, s string) ([]fs.FileInfo, error) {
|
||||
path := filepath.Clean(s)
|
||||
for _, allowedPrefix := range allowedPrefixes {
|
||||
if strings.HasPrefix(s, allowedPrefix) {
|
||||
if strings.HasPrefix(path, allowedPrefix) {
|
||||
return interp.DefaultReadDirHandler()(ctx, s)
|
||||
}
|
||||
}
|
||||
|
||||
return nil, os.ErrNotExist
|
||||
return nil, fs.ErrNotExist
|
||||
}
|
||||
}
|
||||
|
||||
func RestrictedStat(allowedPrefixes ...string) interp.StatHandlerFunc {
|
||||
return func(ctx context.Context, s string, b bool) (os.FileInfo, error) {
|
||||
return func(ctx context.Context, s string, b bool) (fs.FileInfo, error) {
|
||||
path := filepath.Clean(s)
|
||||
for _, allowedPrefix := range allowedPrefixes {
|
||||
if strings.HasPrefix(s, allowedPrefix) {
|
||||
if strings.HasPrefix(path, allowedPrefix) {
|
||||
return interp.DefaultStatHandler()(ctx, s, b)
|
||||
}
|
||||
}
|
||||
|
||||
return nil, os.ErrNotExist
|
||||
return nil, fs.ErrNotExist
|
||||
}
|
||||
}
|
||||
|
||||
func RestrictedOpen(allowedPrefixes ...string) interp.OpenHandlerFunc {
|
||||
return func(ctx context.Context, s string, i int, fm os.FileMode) (io.ReadWriteCloser, error) {
|
||||
return func(ctx context.Context, s string, i int, fm fs.FileMode) (io.ReadWriteCloser, error) {
|
||||
path := filepath.Clean(s)
|
||||
for _, allowedPrefix := range allowedPrefixes {
|
||||
if strings.HasPrefix(s, allowedPrefix) {
|
||||
if strings.HasPrefix(path, allowedPrefix) {
|
||||
return interp.DefaultOpenHandler()(ctx, s, i, fm)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
package translations
|
||||
|
||||
import (
|
||||
"embed"
|
||||
|
||||
"go.elara.ws/logger"
|
||||
"go.elara.ws/logger/log"
|
||||
"go.elara.ws/translate"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
//go:embed files
|
||||
var translationFS embed.FS
|
||||
|
||||
var translator *translate.Translator
|
||||
|
||||
func Translator() *translate.Translator {
|
||||
if translator == nil {
|
||||
t, err := translate.NewFromFS(translationFS)
|
||||
if err != nil {
|
||||
log.Fatal("Error creating new translator").Err(err).Send()
|
||||
}
|
||||
translator = &t
|
||||
}
|
||||
return translator
|
||||
}
|
||||
|
||||
func NewLogger(l logger.Logger, lang language.Tag) *translate.TranslatedLogger {
|
||||
return translate.NewLogger(l, *Translator(), lang)
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
package types
|
||||
|
||||
import "go.elara.ws/lure/manager"
|
||||
|
||||
type BuildOpts struct {
|
||||
Script string
|
||||
Manager manager.Manager
|
||||
Clean bool
|
||||
Interactive bool
|
||||
}
|
||||
|
||||
// BuildVars represents the script variables required
|
||||
// to build a package
|
||||
type BuildVars struct {
|
||||
Name string `sh:"name,required"`
|
||||
Version string `sh:"version,required"`
|
||||
Release int `sh:"release,required"`
|
||||
Epoch uint `sh:"epoch"`
|
||||
Description string `sh:"desc"`
|
||||
Homepage string `sh:"homepage"`
|
||||
Maintainer string `sh:"maintainer"`
|
||||
Architectures []string `sh:"architectures"`
|
||||
Licenses []string `sh:"license"`
|
||||
Provides []string `sh:"provides"`
|
||||
Conflicts []string `sh:"conflicts"`
|
||||
Depends []string `sh:"deps"`
|
||||
BuildDepends []string `sh:"build_deps"`
|
||||
Replaces []string `sh:"replaces"`
|
||||
Sources []string `sh:"sources"`
|
||||
Checksums []string `sh:"checksums"`
|
||||
Backup []string `sh:"backup"`
|
||||
Scripts Scripts `sh:"scripts"`
|
||||
}
|
||||
|
||||
type Scripts struct {
|
||||
PreInstall string `sh:"preinstall"`
|
||||
PostInstall string `sh:"postinstall"`
|
||||
PreRemove string `sh:"preremove"`
|
||||
PostRemove string `sh:"postremove"`
|
||||
PreUpgrade string `sh:"preupgrade"`
|
||||
PostUpgrade string `sh:"postupgrade"`
|
||||
PreTrans string `sh:"pretrans"`
|
||||
PostTrans string `sh:"posttrans"`
|
||||
}
|
||||
|
||||
type Directories struct {
|
||||
BaseDir string
|
||||
SrcDir string
|
||||
PkgDir string
|
||||
ScriptDir string
|
||||
}
|
110
list.go
110
list.go
|
@ -23,71 +23,83 @@ import (
|
|||
|
||||
"github.com/urfave/cli/v2"
|
||||
"go.elara.ws/logger/log"
|
||||
"go.elara.ws/lure/internal/config"
|
||||
"go.elara.ws/lure/internal/db"
|
||||
"go.elara.ws/lure/internal/repos"
|
||||
"go.elara.ws/lure/manager"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
func listCmd(c *cli.Context) error {
|
||||
err := repos.Pull(c.Context, gdb, cfg.Repos)
|
||||
if err != nil {
|
||||
log.Fatal("Error pulling repositories").Err(err).Send()
|
||||
}
|
||||
|
||||
where := "true"
|
||||
args := []any(nil)
|
||||
if c.NArg() > 0 {
|
||||
where = "name LIKE ? OR json_array_contains(provides, ?)"
|
||||
args = []any{c.Args().First(), c.Args().First()}
|
||||
}
|
||||
|
||||
result, err := db.GetPkgs(gdb, where, args...)
|
||||
if err != nil {
|
||||
log.Fatal("Error getting packages").Err(err).Send()
|
||||
}
|
||||
defer result.Close()
|
||||
|
||||
var installed map[string]string
|
||||
if c.Bool("installed") {
|
||||
mgr := manager.Detect()
|
||||
if mgr == nil {
|
||||
log.Fatal("Unable to detect supported package manager on system").Send()
|
||||
}
|
||||
|
||||
installed, err = mgr.ListInstalled(&manager.Opts{AsRoot: false})
|
||||
var listCmd = &cli.Command{
|
||||
Name: "list",
|
||||
Usage: "List LURE repo packages",
|
||||
Aliases: []string{"ls"},
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "installed",
|
||||
Aliases: []string{"I"},
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
err := repos.Pull(c.Context, config.Config().Repos)
|
||||
if err != nil {
|
||||
log.Fatal("Error listing installed packages").Err(err).Send()
|
||||
log.Fatal("Error pulling repositories").Err(err).Send()
|
||||
}
|
||||
}
|
||||
|
||||
for result.Next() {
|
||||
var pkg db.Package
|
||||
err := result.StructScan(&pkg)
|
||||
where := "true"
|
||||
args := []any(nil)
|
||||
if c.NArg() > 0 {
|
||||
where = "name LIKE ? OR json_array_contains(provides, ?)"
|
||||
args = []any{c.Args().First(), c.Args().First()}
|
||||
}
|
||||
|
||||
result, err := db.GetPkgs(where, args...)
|
||||
if err != nil {
|
||||
return err
|
||||
log.Fatal("Error getting packages").Err(err).Send()
|
||||
}
|
||||
defer result.Close()
|
||||
|
||||
if slices.Contains(cfg.IgnorePkgUpdates, pkg.Name) {
|
||||
continue
|
||||
}
|
||||
|
||||
version := pkg.Version
|
||||
var installed map[string]string
|
||||
if c.Bool("installed") {
|
||||
instVersion, ok := installed[pkg.Name]
|
||||
if !ok {
|
||||
continue
|
||||
} else {
|
||||
version = instVersion
|
||||
mgr := manager.Detect()
|
||||
if mgr == nil {
|
||||
log.Fatal("Unable to detect a supported package manager on the system").Send()
|
||||
}
|
||||
|
||||
installed, err = mgr.ListInstalled(&manager.Opts{AsRoot: false})
|
||||
if err != nil {
|
||||
log.Fatal("Error listing installed packages").Err(err).Send()
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("%s/%s %s\n", pkg.Repository, pkg.Name, version)
|
||||
}
|
||||
for result.Next() {
|
||||
var pkg db.Package
|
||||
err := result.StructScan(&pkg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Fatal("Error iterating over packages").Err(err).Send()
|
||||
}
|
||||
if slices.Contains(config.Config().IgnorePkgUpdates, pkg.Name) {
|
||||
continue
|
||||
}
|
||||
|
||||
return nil
|
||||
version := pkg.Version
|
||||
if c.Bool("installed") {
|
||||
instVersion, ok := installed[pkg.Name]
|
||||
if !ok {
|
||||
continue
|
||||
} else {
|
||||
version = instVersion
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("%s/%s %s\n", pkg.Repository, pkg.Name, version)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Fatal("Error iterating over packages").Err(err).Send()
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
|
276
main.go
276
main.go
|
@ -20,13 +20,11 @@ package main
|
|||
|
||||
import (
|
||||
"context"
|
||||
"embed"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
//"time"
|
||||
|
||||
"github.com/mattn/go-isatty"
|
||||
"github.com/urfave/cli/v2"
|
||||
|
@ -34,238 +32,78 @@ import (
|
|||
"go.elara.ws/logger/log"
|
||||
"go.elara.ws/lure/internal/config"
|
||||
"go.elara.ws/lure/internal/db"
|
||||
"go.elara.ws/lure/internal/translations"
|
||||
"go.elara.ws/lure/manager"
|
||||
"go.elara.ws/translate"
|
||||
)
|
||||
|
||||
//go:generate scripts/gen-version.sh
|
||||
var app = &cli.App{
|
||||
Name: "lure",
|
||||
Usage: "Linux User REpository",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "pm-args",
|
||||
Aliases: []string{"P"},
|
||||
Usage: "Arguments to be passed on to the package manager",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "interactive",
|
||||
Aliases: []string{"i"},
|
||||
Value: isatty.IsTerminal(os.Stdin.Fd()),
|
||||
Usage: "Enable interactive questions and prompts",
|
||||
},
|
||||
},
|
||||
Commands: []*cli.Command{
|
||||
installCmd,
|
||||
removeCmd,
|
||||
upgradeCmd,
|
||||
infoCmd,
|
||||
listCmd,
|
||||
buildCmd,
|
||||
addrepoCmd,
|
||||
removerepoCmd,
|
||||
refreshCmd,
|
||||
fixCmd,
|
||||
versionCmd,
|
||||
},
|
||||
Before: func(c *cli.Context) error {
|
||||
args := strings.Split(c.String("pm-args"), " ")
|
||||
if len(args) == 1 && args[0] == "" {
|
||||
return nil
|
||||
}
|
||||
manager.Args = append(manager.Args, args...)
|
||||
return nil
|
||||
},
|
||||
After: func(ctx *cli.Context) error {
|
||||
return db.Close()
|
||||
},
|
||||
EnableBashCompletion: true,
|
||||
}
|
||||
|
||||
//go:embed translations
|
||||
var translationFS embed.FS
|
||||
|
||||
var translator translate.Translator
|
||||
|
||||
func init() {
|
||||
logger := logger.NewCLI(os.Stderr)
|
||||
|
||||
t, err := translate.NewFromFS(translationFS)
|
||||
if err != nil {
|
||||
logger.Fatal("Error creating new translator").Err(err).Send()
|
||||
}
|
||||
translator = t
|
||||
|
||||
log.Logger = translate.NewLogger(logger, t, config.Language)
|
||||
var versionCmd = &cli.Command{
|
||||
Name: "version",
|
||||
Usage: "Print the current LURE version and exit",
|
||||
Action: func(ctx *cli.Context) error {
|
||||
println(config.Version)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func main() {
|
||||
if !cfg.Unsafe.AllowRunAsRoot && os.Geteuid() == 0 {
|
||||
log.Logger = translations.NewLogger(logger.NewCLI(os.Stderr), config.Language())
|
||||
|
||||
if !config.Config().Unsafe.AllowRunAsRoot && os.Geteuid() == 0 {
|
||||
log.Fatal("Running LURE as root is forbidden as it may cause catastrophic damage to your system").Send()
|
||||
}
|
||||
|
||||
err := loadDB()
|
||||
if err != nil {
|
||||
log.Fatal("Error loading database").Err(err).Send()
|
||||
}
|
||||
// Set the root command to the one set in the LURE config
|
||||
manager.DefaultRootCmd = config.Config().RootCmd
|
||||
|
||||
ctx := context.Background()
|
||||
ctx, cancel := signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM)
|
||||
defer cancel()
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
// Exit the program after a maximum of 200ms
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
gdb.Close()
|
||||
os.Exit(0)
|
||||
}()
|
||||
|
||||
app := &cli.App{
|
||||
Name: "lure",
|
||||
Usage: "Linux User REpository",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "pm-args",
|
||||
Aliases: []string{"P"},
|
||||
Usage: "Arguments to be passed on to the package manager",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "interactive",
|
||||
Aliases: []string{"i"},
|
||||
Value: isatty.IsTerminal(os.Stdin.Fd()),
|
||||
Usage: "Enable interactive questions and prompts",
|
||||
},
|
||||
},
|
||||
Commands: []*cli.Command{
|
||||
{
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "clean",
|
||||
Aliases: []string{"c"},
|
||||
Usage: "Build package from scratch even if there's an already built package available",
|
||||
},
|
||||
},
|
||||
Name: "install",
|
||||
Usage: "Install a new package",
|
||||
Aliases: []string{"in"},
|
||||
Action: installCmd,
|
||||
BashComplete: completionInstall,
|
||||
},
|
||||
{
|
||||
Name: "remove",
|
||||
Usage: "Remove an installed package",
|
||||
Aliases: []string{"rm"},
|
||||
Action: removeCmd,
|
||||
},
|
||||
{
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "clean",
|
||||
Aliases: []string{"c"},
|
||||
Usage: "Build package from scratch even if there's an already built package available",
|
||||
},
|
||||
},
|
||||
Name: "upgrade",
|
||||
Usage: "Upgrade all installed packages",
|
||||
Aliases: []string{"up"},
|
||||
Action: upgradeCmd,
|
||||
},
|
||||
{
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "all",
|
||||
Aliases: []string{"a"},
|
||||
Usage: "Show all information, not just for the current distro",
|
||||
},
|
||||
},
|
||||
Name: "info",
|
||||
Usage: "Print information about a package",
|
||||
Action: infoCmd,
|
||||
},
|
||||
{
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "installed",
|
||||
Aliases: []string{"I"},
|
||||
},
|
||||
},
|
||||
Name: "list",
|
||||
Usage: "List LURE repo packages",
|
||||
Aliases: []string{"ls"},
|
||||
Action: listCmd,
|
||||
},
|
||||
{
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "script",
|
||||
Aliases: []string{"s"},
|
||||
Value: "lure.sh",
|
||||
Usage: "Path to the build script",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "package",
|
||||
Aliases: []string{"p"},
|
||||
Usage: "Name of the package to build and its repo (example: default/go-bin)",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "clean",
|
||||
Aliases: []string{"c"},
|
||||
Usage: "Build package from scratch even if there's an already built package available",
|
||||
},
|
||||
},
|
||||
Name: "build",
|
||||
Usage: "Build a local package",
|
||||
Action: buildCmd,
|
||||
},
|
||||
{
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "name",
|
||||
Aliases: []string{"n"},
|
||||
Required: true,
|
||||
Usage: "Name of the new repo",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "url",
|
||||
Aliases: []string{"u"},
|
||||
Required: true,
|
||||
Usage: "URL of the new repo",
|
||||
},
|
||||
},
|
||||
Name: "addrepo",
|
||||
Usage: "Add a new repository",
|
||||
Aliases: []string{"ar"},
|
||||
Action: addrepoCmd,
|
||||
},
|
||||
{
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "name",
|
||||
Aliases: []string{"n"},
|
||||
Required: true,
|
||||
Usage: "Name of the repo to be deleted",
|
||||
},
|
||||
},
|
||||
Name: "removerepo",
|
||||
Usage: "Remove an existing repository",
|
||||
Aliases: []string{"rr"},
|
||||
Action: removerepoCmd,
|
||||
},
|
||||
{
|
||||
Name: "refresh",
|
||||
Usage: "Pull all repositories that have changed",
|
||||
Aliases: []string{"ref"},
|
||||
Action: refreshCmd,
|
||||
},
|
||||
{
|
||||
Name: "fix",
|
||||
Usage: "Attempt to fix problems with LURE",
|
||||
Action: fixCmd,
|
||||
},
|
||||
{
|
||||
Name: "version",
|
||||
Usage: "Display the current LURE version and exit",
|
||||
Action: displayVersion,
|
||||
},
|
||||
},
|
||||
Before: func(c *cli.Context) error {
|
||||
args := strings.Split(c.String("pm-args"), " ")
|
||||
if len(args) == 1 && args[0] == "" {
|
||||
args = nil
|
||||
}
|
||||
|
||||
manager.Args = append(manager.Args, args...)
|
||||
return nil
|
||||
},
|
||||
After: func(ctx *cli.Context) error {
|
||||
return gdb.Close()
|
||||
},
|
||||
EnableBashCompletion: true,
|
||||
}
|
||||
|
||||
err = app.RunContext(ctx, os.Args)
|
||||
err := app.RunContext(ctx, os.Args)
|
||||
if err != nil {
|
||||
log.Error("Error while running app").Err(err).Send()
|
||||
}
|
||||
}
|
||||
|
||||
func displayVersion(c *cli.Context) error {
|
||||
print(config.Version)
|
||||
return nil
|
||||
}
|
||||
|
||||
func completionInstall(c *cli.Context) {
|
||||
result, err := db.GetPkgs(gdb, "true")
|
||||
if err != nil {
|
||||
log.Fatal("Error getting packages").Err(err).Send()
|
||||
}
|
||||
defer result.Close()
|
||||
|
||||
for result.Next() {
|
||||
var pkg db.Package
|
||||
err = result.StructScan(&pkg)
|
||||
if err != nil {
|
||||
log.Fatal("Error iterating over packages").Err(err).Send()
|
||||
}
|
||||
|
||||
fmt.Println(pkg.Name)
|
||||
}
|
||||
}
|
||||
|
|
178
repo.go
178
repo.go
|
@ -32,83 +32,123 @@ import (
|
|||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
func addrepoCmd(c *cli.Context) error {
|
||||
name := c.String("name")
|
||||
repoURL := c.String("url")
|
||||
var addrepoCmd = &cli.Command{
|
||||
Name: "addrepo",
|
||||
Usage: "Add a new repository",
|
||||
Aliases: []string{"ar"},
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "name",
|
||||
Aliases: []string{"n"},
|
||||
Required: true,
|
||||
Usage: "Name of the new repo",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "url",
|
||||
Aliases: []string{"u"},
|
||||
Required: true,
|
||||
Usage: "URL of the new repo",
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
name := c.String("name")
|
||||
repoURL := c.String("url")
|
||||
|
||||
for _, repo := range cfg.Repos {
|
||||
if repo.URL == repoURL {
|
||||
log.Fatal("Repo already exists").Str("name", repo.Name).Send()
|
||||
cfg := config.Config()
|
||||
|
||||
for _, repo := range cfg.Repos {
|
||||
if repo.URL == repoURL {
|
||||
log.Fatal("Repo already exists").Str("name", repo.Name).Send()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cfg.Repos = append(cfg.Repos, types.Repo{
|
||||
Name: name,
|
||||
URL: repoURL,
|
||||
})
|
||||
cfg.Repos = append(cfg.Repos, types.Repo{
|
||||
Name: name,
|
||||
URL: repoURL,
|
||||
})
|
||||
|
||||
cfgFl, err := os.Create(config.ConfigPath)
|
||||
if err != nil {
|
||||
log.Fatal("Error opening config file").Err(err).Send()
|
||||
}
|
||||
|
||||
err = toml.NewEncoder(cfgFl).Encode(&cfg)
|
||||
if err != nil {
|
||||
log.Fatal("Error encoding config").Err(err).Send()
|
||||
}
|
||||
|
||||
err = repos.Pull(c.Context, gdb, cfg.Repos)
|
||||
if err != nil {
|
||||
log.Fatal("Error pulling repos").Err(err).Send()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func removerepoCmd(c *cli.Context) error {
|
||||
name := c.String("name")
|
||||
|
||||
found := false
|
||||
index := 0
|
||||
for i, repo := range cfg.Repos {
|
||||
if repo.Name == name {
|
||||
index = i
|
||||
found = true
|
||||
cfgFl, err := os.Create(config.GetPaths().ConfigPath)
|
||||
if err != nil {
|
||||
log.Fatal("Error opening config file").Err(err).Send()
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
log.Fatal("Repo does not exist").Str("name", name).Send()
|
||||
}
|
||||
|
||||
cfg.Repos = slices.Delete(cfg.Repos, index, index+1)
|
||||
err = toml.NewEncoder(cfgFl).Encode(cfg)
|
||||
if err != nil {
|
||||
log.Fatal("Error encoding config").Err(err).Send()
|
||||
}
|
||||
|
||||
cfgFl, err := os.Create(config.ConfigPath)
|
||||
if err != nil {
|
||||
log.Fatal("Error opening config file").Err(err).Send()
|
||||
}
|
||||
err = repos.Pull(c.Context, cfg.Repos)
|
||||
if err != nil {
|
||||
log.Fatal("Error pulling repos").Err(err).Send()
|
||||
}
|
||||
|
||||
err = toml.NewEncoder(cfgFl).Encode(&cfg)
|
||||
if err != nil {
|
||||
log.Fatal("Error encoding config").Err(err).Send()
|
||||
}
|
||||
|
||||
err = os.RemoveAll(filepath.Join(config.RepoDir, name))
|
||||
if err != nil {
|
||||
log.Fatal("Error removing repo directory").Err(err).Send()
|
||||
}
|
||||
|
||||
err = db.DeletePkgs(gdb, "repository = ?", name)
|
||||
if err != nil {
|
||||
log.Fatal("Error removing packages from database").Err(err).Send()
|
||||
}
|
||||
|
||||
return nil
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func refreshCmd(c *cli.Context) error {
|
||||
err := repos.Pull(c.Context, gdb, cfg.Repos)
|
||||
if err != nil {
|
||||
log.Fatal("Error pulling repos").Err(err).Send()
|
||||
}
|
||||
return nil
|
||||
var removerepoCmd = &cli.Command{
|
||||
Name: "removerepo",
|
||||
Usage: "Remove an existing repository",
|
||||
Aliases: []string{"rr"},
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "name",
|
||||
Aliases: []string{"n"},
|
||||
Required: true,
|
||||
Usage: "Name of the repo to be deleted",
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
name := c.String("name")
|
||||
cfg := config.Config()
|
||||
|
||||
found := false
|
||||
index := 0
|
||||
for i, repo := range cfg.Repos {
|
||||
if repo.Name == name {
|
||||
index = i
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
log.Fatal("Repo does not exist").Str("name", name).Send()
|
||||
}
|
||||
|
||||
cfg.Repos = slices.Delete(cfg.Repos, index, index+1)
|
||||
|
||||
cfgFl, err := os.Create(config.GetPaths().ConfigPath)
|
||||
if err != nil {
|
||||
log.Fatal("Error opening config file").Err(err).Send()
|
||||
}
|
||||
|
||||
err = toml.NewEncoder(cfgFl).Encode(&cfg)
|
||||
if err != nil {
|
||||
log.Fatal("Error encoding config").Err(err).Send()
|
||||
}
|
||||
|
||||
err = os.RemoveAll(filepath.Join(config.GetPaths().RepoDir, name))
|
||||
if err != nil {
|
||||
log.Fatal("Error removing repo directory").Err(err).Send()
|
||||
}
|
||||
|
||||
err = db.DeletePkgs("repository = ?", name)
|
||||
if err != nil {
|
||||
log.Fatal("Error removing packages from database").Err(err).Send()
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var refreshCmd = &cli.Command{
|
||||
Name: "refresh",
|
||||
Usage: "Pull all repositories that have changed",
|
||||
Aliases: []string{"ref"},
|
||||
Action: func(c *cli.Context) error {
|
||||
err := repos.Pull(c.Context, config.Config().Repos)
|
||||
if err != nil {
|
||||
log.Fatal("Error pulling repos").Err(err).Send()
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
#!/bin/bash
|
||||
|
||||
git describe --tags > internal/config/version.txt
|
||||
git describe --tags > version.txt
|
69
upgrade.go
69
upgrade.go
|
@ -25,42 +25,61 @@ import (
|
|||
"github.com/urfave/cli/v2"
|
||||
"go.elara.ws/logger/log"
|
||||
"go.elara.ws/lure/distro"
|
||||
"go.elara.ws/lure/internal/build"
|
||||
"go.elara.ws/lure/internal/config"
|
||||
"go.elara.ws/lure/internal/db"
|
||||
"go.elara.ws/lure/internal/repos"
|
||||
"go.elara.ws/lure/internal/types"
|
||||
"go.elara.ws/lure/manager"
|
||||
"go.elara.ws/vercmp"
|
||||
"golang.org/x/exp/maps"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
func upgradeCmd(c *cli.Context) error {
|
||||
info, err := distro.ParseOSRelease(c.Context)
|
||||
if err != nil {
|
||||
log.Fatal("Error parsing os-release file").Err(err).Send()
|
||||
}
|
||||
var upgradeCmd = &cli.Command{
|
||||
Name: "upgrade",
|
||||
Usage: "Upgrade all installed packages",
|
||||
Aliases: []string{"up"},
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "clean",
|
||||
Aliases: []string{"c"},
|
||||
Usage: "Build package from scratch even if there's an already built package available",
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
info, err := distro.ParseOSRelease(c.Context)
|
||||
if err != nil {
|
||||
log.Fatal("Error parsing os-release file").Err(err).Send()
|
||||
}
|
||||
|
||||
mgr := manager.Detect()
|
||||
if mgr == nil {
|
||||
log.Fatal("Unable to detect supported package manager on system").Send()
|
||||
}
|
||||
mgr := manager.Detect()
|
||||
if mgr == nil {
|
||||
log.Fatal("Unable to detect a supported package manager on the system").Send()
|
||||
}
|
||||
|
||||
err = repos.Pull(c.Context, gdb, cfg.Repos)
|
||||
if err != nil {
|
||||
log.Fatal("Error pulling repos").Err(err).Send()
|
||||
}
|
||||
err = repos.Pull(c.Context, config.Config().Repos)
|
||||
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()
|
||||
}
|
||||
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, nil, mgr, c.Bool("clean"), c.Bool("interactive"))
|
||||
} else {
|
||||
log.Info("There is nothing to do.").Send()
|
||||
}
|
||||
if len(updates) > 0 {
|
||||
build.InstallPkgs(c.Context, updates, nil, types.BuildOpts{
|
||||
Manager: mgr,
|
||||
Clean: c.Bool("clean"),
|
||||
Interactive: c.Bool("interactive"),
|
||||
})
|
||||
} else {
|
||||
log.Info("There is nothing to do.").Send()
|
||||
}
|
||||
|
||||
return nil
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func checkForUpdates(ctx context.Context, mgr manager.Manager, info *distro.OSRelease) ([]db.Package, error) {
|
||||
|
@ -70,14 +89,14 @@ func checkForUpdates(ctx context.Context, mgr manager.Manager, info *distro.OSRe
|
|||
}
|
||||
|
||||
pkgNames := maps.Keys(installed)
|
||||
found, _, err := repos.FindPkgs(gdb, pkgNames)
|
||||
found, _, err := repos.FindPkgs(pkgNames)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var out []db.Package
|
||||
for pkgName, pkgs := range found {
|
||||
if slices.Contains(cfg.IgnorePkgUpdates, pkgName) {
|
||||
if slices.Contains(config.Config().IgnorePkgUpdates, pkgName) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue