Compare commits
No commits in common. "master" and "v0.0.4" have entirely different histories.
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
@ -1 +0,0 @@
|
|||||||
liberapay: lure
|
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,5 +1,3 @@
|
|||||||
/lure
|
/lure
|
||||||
/lure-api-server
|
|
||||||
/cmd/lure-api-server/lure-api-server
|
|
||||||
/dist/
|
/dist/
|
||||||
/internal/config/version.txt
|
/version.txt
|
@ -1,95 +1,71 @@
|
|||||||
before:
|
before:
|
||||||
hooks:
|
hooks:
|
||||||
- go mod tidy
|
- go mod tidy
|
||||||
|
- go generate
|
||||||
builds:
|
builds:
|
||||||
- id: lure
|
- id: lure
|
||||||
env:
|
env:
|
||||||
- CGO_ENABLED=0
|
- CGO_ENABLED=0
|
||||||
binary: lure
|
binary: lure
|
||||||
ldflags:
|
|
||||||
- -X go.elara.ws/lure/internal/config.Version={{.Version}}
|
|
||||||
goos:
|
goos:
|
||||||
- linux
|
- linux
|
||||||
goarch:
|
goarch:
|
||||||
- amd64
|
|
||||||
- 386
|
- 386
|
||||||
- arm64
|
- amd64
|
||||||
- arm
|
- arm
|
||||||
|
- arm64
|
||||||
- riscv64
|
- riscv64
|
||||||
archives:
|
archives:
|
||||||
- name_template: >-
|
- replacements:
|
||||||
{{- .ProjectName}}-
|
386: i386
|
||||||
{{- .Version}}-
|
amd64: x86_64
|
||||||
{{- .Os}}-
|
arm64: aarch64
|
||||||
{{- if .Arch | eq "amd64"}}x86_64
|
|
||||||
{{- else if .Arch | eq "386"}}i386
|
|
||||||
{{- else if .Arch | eq "arm64"}}aarch64
|
|
||||||
{{- else }}{{ .Arch }}{{ end -}}
|
|
||||||
files:
|
|
||||||
- scripts/completion/*
|
|
||||||
nfpms:
|
nfpms:
|
||||||
- id: lure
|
- id: lure
|
||||||
package_name: linux-user-repository
|
package_name: linux-user-repository
|
||||||
file_name_template: >-
|
file_name_template: '{{.PackageName}}-{{.Version}}-{{.Os}}-{{.Arch}}'
|
||||||
{{- .PackageName}}-
|
|
||||||
{{- .Version}}-
|
|
||||||
{{- .Os}}-
|
|
||||||
{{- if .Arch | eq "amd64"}}x86_64
|
|
||||||
{{- else if .Arch | eq "386"}}i386
|
|
||||||
{{- else if .Arch | eq "arm64"}}aarch64
|
|
||||||
{{- else }}{{ .Arch }}{{ end -}}
|
|
||||||
description: "Linux User REpository"
|
description: "Linux User REpository"
|
||||||
homepage: 'https://lure.sh'
|
replacements:
|
||||||
maintainer: 'Elara Musayelyan <elara@elara.ws>'
|
386: i386
|
||||||
|
amd64: x86_64
|
||||||
|
arm64: aarch64
|
||||||
|
homepage: 'https://gitea.arsenm.dev/Arsen6331/lure'
|
||||||
|
maintainer: 'Arsen Musayelyan <arsen@arsenm.dev>'
|
||||||
license: GPLv3
|
license: GPLv3
|
||||||
formats:
|
formats:
|
||||||
- apk
|
- apk
|
||||||
- deb
|
- deb
|
||||||
- rpm
|
- rpm
|
||||||
- archlinux
|
|
||||||
provides:
|
provides:
|
||||||
- linux-user-repository
|
- lure
|
||||||
conflicts:
|
conflicts:
|
||||||
- linux-user-repository
|
- lure
|
||||||
recommends:
|
|
||||||
- aria2
|
|
||||||
contents:
|
|
||||||
- src: scripts/completion/bash
|
|
||||||
dst: /usr/share/bash-completion/completions/lure
|
|
||||||
- src: scripts/completion/zsh
|
|
||||||
dst: /usr/share/zsh/site-functions/_lure
|
|
||||||
aurs:
|
aurs:
|
||||||
- name: linux-user-repository-bin
|
- name: lure-bin
|
||||||
homepage: 'https://lure.sh'
|
homepage: 'https://gitea.arsenm.dev/Arsen6331/lure'
|
||||||
description: "Linux User REpository"
|
description: "Linux User REpository"
|
||||||
maintainers:
|
maintainers:
|
||||||
- 'Elara Musayelyan <elara@elara.ws>'
|
- 'Arsen Musayelyan <arsen@arsenm.dev>'
|
||||||
license: GPLv3
|
license: GPLv3
|
||||||
private_key: '{{ .Env.AUR_KEY }}'
|
private_key: '{{ .Env.AUR_KEY }}'
|
||||||
git_url: 'ssh://aur@aur.archlinux.org/linux-user-repository-bin.git'
|
git_url: 'ssh://aur@aur.archlinux.org/lure-bin.git'
|
||||||
provides:
|
provides:
|
||||||
- linux-user-repository
|
- lure
|
||||||
conflicts:
|
conflicts:
|
||||||
- linux-user-repository
|
- lure
|
||||||
depends:
|
depends:
|
||||||
- sudo
|
- sudo
|
||||||
- pacman
|
- pacman
|
||||||
optdepends:
|
|
||||||
- 'aria2: for downloading torrent sources'
|
|
||||||
package: |-
|
package: |-
|
||||||
# binaries
|
# binaries
|
||||||
install -Dm755 ./lure "${pkgdir}/usr/bin/lure"
|
install -Dm755 "./lure" "${pkgdir}/usr/bin/lure"
|
||||||
|
|
||||||
# completions
|
|
||||||
install -Dm755 ./scripts/completion/bash ${pkgdir}/usr/share/bash-completion/completions/lure
|
|
||||||
install -Dm755 ./scripts/completion/zsh ${pkgdir}/usr/share/zsh/site-functions/_lure
|
|
||||||
release:
|
release:
|
||||||
gitea:
|
gitea:
|
||||||
owner: lure
|
owner: Arsen6331
|
||||||
name: lure
|
name: lure
|
||||||
gitea_urls:
|
gitea_urls:
|
||||||
api: 'https://gitea.elara.ws/api/v1/'
|
api: 'https://gitea.arsenm.dev/api/v1/'
|
||||||
download: 'https://gitea.elara.ws'
|
download: 'https://gitea.arsenm.dev'
|
||||||
skip_tls_verify: false
|
skip_tls_verify: false
|
||||||
checksum:
|
checksum:
|
||||||
name_template: 'checksums.txt'
|
name_template: 'checksums.txt'
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
platform: linux/amd64
|
|
||||||
pipeline:
|
pipeline:
|
||||||
release:
|
release:
|
||||||
image: goreleaser/goreleaser
|
image: goreleaser/goreleaser
|
||||||
|
20
Makefile
20
Makefile
@ -1,20 +1,16 @@
|
|||||||
PREFIX ?= /usr/local
|
lure: version.txt
|
||||||
GIT_VERSION = $(shell git describe --tags )
|
go build
|
||||||
|
|
||||||
lure:
|
|
||||||
CGO_ENABLED=0 go build -ldflags="-X 'go.elara.ws/lure/internal/config.Version=$(GIT_VERSION)'"
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f lure
|
rm -f lure
|
||||||
|
|
||||||
install: lure installmisc
|
install: lure
|
||||||
install -Dm755 lure $(DESTDIR)$(PREFIX)/bin/lure
|
sudo install -Dm755 lure /usr/local/bin/lure
|
||||||
|
|
||||||
installmisc:
|
|
||||||
install -Dm755 scripts/completion/bash $(DESTDIR)$(PREFIX)/share/bash-completion/completions/lure
|
|
||||||
install -Dm755 scripts/completion/zsh $(DESTDIR)$(PREFIX)/share/zsh/site-functions/_lure
|
|
||||||
|
|
||||||
uninstall:
|
uninstall:
|
||||||
rm -f /usr/local/bin/lure
|
rm -f /usr/local/bin/lure
|
||||||
|
|
||||||
|
version.txt:
|
||||||
|
go generate
|
||||||
|
|
||||||
.PHONY: install clean uninstall installmisc lure
|
.PHONY: install clean uninstall
|
65
README.md
65
README.md
@ -1,35 +1,20 @@
|
|||||||
<img src="assets/logo.png" alt="LURE Logo" width="200">
|
|
||||||
|
|
||||||
# LURE (Linux User REpository)
|
# LURE (Linux User REpository)
|
||||||
|
|
||||||
[![Go Report Card](https://goreportcard.com/badge/go.elara.ws/lure)](https://goreportcard.com/report/go.elara.ws/lure)
|
[![Go Report Card](https://goreportcard.com/badge/go.arsenm.dev/lure)](https://goreportcard.com/report/go.arsenm.dev/lure)
|
||||||
[![status-badge](https://ci.elara.ws/api/badges/lure/lure/status.svg)](https://ci.elara.ws/lure/lure)
|
[![status-badge](https://ci.arsenm.dev/api/badges/Arsen6331/lure/status.svg)](https://ci.arsenm.dev/Arsen6331/lure)
|
||||||
[![SWH](https://archive.softwareheritage.org/badge/origin/https://gitea.elara.ws/lure/lure.git/)](https://archive.softwareheritage.org/browse/origin/?origin_url=https://gitea.elara.ws/lure/lure.git)
|
[![lure-bin AUR package](https://img.shields.io/aur/version/lure-bin?label=lure-bin&logo=archlinux)](https://aur.archlinux.org/packages/lure-bin/)
|
||||||
[![linux-user-repository-bin AUR package](https://img.shields.io/aur/version/linux-user-repository-bin?label=linux-user-repository-bin&logo=archlinux)](https://aur.archlinux.org/packages/linux-user-repository-bin/)
|
|
||||||
|
|
||||||
LURE is a distro-agnostic build system for Linux, similar to the [AUR](https://wiki.archlinux.org/title/Arch_User_Repository). It is currently in **beta**. Most major bugs have been fixed, and most major features have been added. LURE is ready for general use, but may still break or change occasionally.
|
LURE is intended to bring the AUR to all distros. It is currently in an ***alpha*** state and may not be stable. It can download a repository, build packages in it using a bash script similar to [PKGBUILD](https://wiki.archlinux.org/title/PKGBUILD), and then install them using your system package manager.
|
||||||
|
|
||||||
LURE is written in pure Go and has zero dependencies after building. The only things LURE requires are a command for privilege elevation such as `sudo`, `doas`, etc. as well as a supported package manager. Currently, LURE supports `apt`, `pacman`, `apk`, `dnf`, `yum`, and `zypper`. If a supported package manager exists on your system, it will be detected and used automatically.
|
LURE is written in pure Go and has zero dependencies after it's built. The only things LURE needs are a command for privilege elevation such as `sudo`, `doas`, etc. as well as a supported package manager. Currently, LURE supports `apt`, `pacman`, `apk`, `dnf`, `yum`, and `zypper`. If a supported package manager exists on your system, it will be detected and used automatically.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### Install script
|
Distro packages and binary archives are provided at the latest Gitea release: https://gitea.arsenm.dev/Arsen6331/lure/releases/latest
|
||||||
|
|
||||||
The LURE install script will automatically download and install the appropriate LURE package on your system. To use it, simply run the following command:
|
LURE is also available on the AUR as [lure-bin](https://aur.archlinux.org/packages/lure-bin)
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -fsSL lure.sh/install | bash
|
|
||||||
```
|
|
||||||
|
|
||||||
**IMPORTANT**: This will download and run the script from https://lure.sh/install. Please look through any script you download from the internet (including this one) before running it.
|
|
||||||
|
|
||||||
### Packages
|
|
||||||
|
|
||||||
Distro packages and binary archives are provided at the latest Gitea release: https://gitea.elara.ws/lure/lure/releases/latest
|
|
||||||
|
|
||||||
LURE is also available on the AUR as [linux-user-repository-bin](https://aur.archlinux.org/packages/linux-user-repository-bin)
|
|
||||||
|
|
||||||
### Building from source
|
### Building from source
|
||||||
|
|
||||||
@ -43,7 +28,11 @@ sudo make install
|
|||||||
|
|
||||||
## Why?
|
## Why?
|
||||||
|
|
||||||
LURE was created because packaging software for multiple Linux distros can be difficult and error-prone, and installing those packages can be a nightmare for users unless they're available in their distro's official repositories. It automates the process of building and installing unofficial packages.
|
The AUR is an amazing feature, and it's one of the main reasons I use Arch on all my daily driver devices. It is really simple while providing really useful functionality. I feel such a solution shouldn't be stuck in only a single distro, so I made LURE.
|
||||||
|
|
||||||
|
Like the AUR, it uses simple bash build scripts, but it doesn't depend on bash being installed at all. It uses an embedded, pure Go implementation of bash instead. Similarly, it uses Git to download the repos and sources, but doesn't depend on Git being installed.
|
||||||
|
|
||||||
|
This means it's really easy to deploy LURE on any distro that it has support for and on any CPU architecture. It also supports and automatically detects many package managers, so it's not limited to just `pacman`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -53,25 +42,25 @@ The documentation for LURE is in the [docs](docs) directory in this repo.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Web Interface
|
|
||||||
|
|
||||||
LURE has an open source web interface, licensed under the AGPLv3 (https://gitea.elara.ws/lure/lure-web), and it's available at https://lure.sh/.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Repositories
|
## Repositories
|
||||||
|
|
||||||
LURE's repos are git repositories that contain a directory for each package, with a `lure.sh` file inside. The `lure.sh` file tells LURE how to build the package and information about it. `lure.sh` scripts are similar to the AUR's PKGBUILD scripts.
|
Unlike the AUR, LURE supports using multiple repos. Also unlike the AUR, LURE's repos are a single git repo containing all the build scripts. Inside each LURE repo, there should be a separate directory for each package containing a `lure.sh` script, which is a PKGBUILD-like build script for LURE. The default repository is hosted on Github: https://github.com/Arsen6331/lure-repo.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Acknowledgements
|
## Dependencies
|
||||||
|
|
||||||
Thanks to the following projects for making LURE possible:
|
As mentioned before, LURE has zero dependencies after it's built. All functionality that could be pure Go is pure Go. Thanks to the following packages for making this possible:
|
||||||
|
|
||||||
- https://github.com/mvdan/sh
|
- Bash: https://github.com/mvdan/sh
|
||||||
- https://github.com/go-git/go-git
|
- Git: https://github.com/go-git/go-git
|
||||||
- https://github.com/mholt/archiver
|
- Archiver: https://github.com/mholt/archiver
|
||||||
- https://github.com/goreleaser/nfpm
|
- nfpm: https://github.com/goreleaser/nfpm
|
||||||
- https://github.com/charmbracelet/bubbletea
|
|
||||||
- https://gitlab.com/cznic/sqlite
|
---
|
||||||
|
|
||||||
|
## Planned Features
|
||||||
|
|
||||||
|
- Automated install script
|
||||||
|
- Automated docker-based testing tool
|
||||||
|
- Web interface for repos
|
BIN
assets/logo.png
BIN
assets/logo.png
Binary file not shown.
Before Width: | Height: | Size: 28 KiB |
654
build.go
654
build.go
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* LURE - Linux User REpository
|
* LURE - Linux User REpository
|
||||||
* Copyright (C) 2023 Elara Musayelyan
|
* Copyright (C) 2022 Arsen Musayelyan
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -19,82 +19,624 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/AlecAivazis/survey/v2"
|
||||||
|
_ "github.com/goreleaser/nfpm/v2/apk"
|
||||||
|
_ "github.com/goreleaser/nfpm/v2/arch"
|
||||||
|
_ "github.com/goreleaser/nfpm/v2/deb"
|
||||||
|
_ "github.com/goreleaser/nfpm/v2/rpm"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"lure.sh/lure/internal/config"
|
"golang.org/x/exp/slices"
|
||||||
"lure.sh/lure/internal/osutils"
|
|
||||||
"lure.sh/lure/internal/types"
|
"github.com/goreleaser/nfpm/v2"
|
||||||
"lure.sh/lure/pkg/build"
|
"github.com/goreleaser/nfpm/v2/files"
|
||||||
"lure.sh/lure/pkg/loggerctx"
|
"go.arsenm.dev/lure/distro"
|
||||||
"lure.sh/lure/pkg/manager"
|
"go.arsenm.dev/lure/download"
|
||||||
"lure.sh/lure/pkg/repos"
|
"go.arsenm.dev/lure/internal/cpu"
|
||||||
|
"go.arsenm.dev/lure/internal/shutils"
|
||||||
|
"go.arsenm.dev/lure/internal/shutils/decoder"
|
||||||
|
"go.arsenm.dev/lure/manager"
|
||||||
|
"mvdan.cc/sh/v3/expand"
|
||||||
|
"mvdan.cc/sh/v3/interp"
|
||||||
|
"mvdan.cc/sh/v3/syntax"
|
||||||
)
|
)
|
||||||
|
|
||||||
var buildCmd = &cli.Command{
|
// BuildVars represents the script variables required
|
||||||
Name: "build",
|
// to build a package
|
||||||
Usage: "Build a local package",
|
type BuildVars struct {
|
||||||
Flags: []cli.Flag{
|
Name string `sh:"name,required"`
|
||||||
&cli.StringFlag{
|
Version string `sh:"version,required"`
|
||||||
Name: "script",
|
Release int `sh:"release,required"`
|
||||||
Aliases: []string{"s"},
|
Epoch uint `sh:"epoch"`
|
||||||
Value: "lure.sh",
|
Description string `sh:"desc"`
|
||||||
Usage: "Path to the build script",
|
Homepage string `sh:"homepage"`
|
||||||
},
|
Maintainer string `sh:"maintainer"`
|
||||||
&cli.StringFlag{
|
Architectures []string `sh:"architectures"`
|
||||||
Name: "package",
|
Licenses []string `sh:"license"`
|
||||||
Aliases: []string{"p"},
|
Provides []string `sh:"provides"`
|
||||||
Usage: "Name of the package to build and its repo (example: default/go-bin)",
|
Conflicts []string `sh:"conflicts"`
|
||||||
},
|
Depends []string `sh:"deps"`
|
||||||
&cli.BoolFlag{
|
BuildDepends []string `sh:"build_deps"`
|
||||||
Name: "clean",
|
Replaces []string `sh:"replaces"`
|
||||||
Aliases: []string{"c"},
|
Sources []string `sh:"sources"`
|
||||||
Usage: "Build package from scratch even if there's an already built package available",
|
Checksums []string `sh:"checksums"`
|
||||||
},
|
Backup []string `sh:"backup"`
|
||||||
},
|
Scripts Scripts `sh:"scripts"`
|
||||||
Action: func(c *cli.Context) error {
|
}
|
||||||
ctx := c.Context
|
|
||||||
log := loggerctx.From(ctx)
|
|
||||||
|
|
||||||
script := c.String("script")
|
type Scripts struct {
|
||||||
if c.String("package") != "" {
|
PreInstall string `sh:"preinstall"`
|
||||||
script = filepath.Join(config.GetPaths(ctx).RepoDir, c.String("package"), "lure.sh")
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
err := repos.Pull(ctx, config.Config(ctx).Repos)
|
func buildCmd(c *cli.Context) error {
|
||||||
|
script := c.String("script")
|
||||||
|
|
||||||
|
mgr := manager.Detect()
|
||||||
|
if mgr == nil {
|
||||||
|
log.Fatal("Unable to detect supported package manager on system").Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
pkgPaths, _, err := buildPackage(c.Context, script, mgr)
|
||||||
|
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 = os.Rename(pkgPath, filepath.Join(wd, name))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Error pulling repositories").Err(err).Send()
|
log.Fatal("Error moving the package").Err(err).Send()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mgr := manager.Detect()
|
return nil
|
||||||
if mgr == nil {
|
}
|
||||||
log.Fatal("Unable to detect a supported package manager on the system").Send()
|
|
||||||
}
|
|
||||||
|
|
||||||
pkgPaths, _, err := build.BuildPackage(ctx, types.BuildOpts{
|
func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]string, []string, error) {
|
||||||
Script: script,
|
info, err := distro.ParseOSRelease(ctx)
|
||||||
Manager: mgr,
|
if err != nil {
|
||||||
Clean: c.Bool("clean"),
|
return nil, nil, err
|
||||||
Interactive: c.Bool("interactive"),
|
}
|
||||||
})
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
env := genBuildEnv(info)
|
||||||
|
|
||||||
|
runner, err := interp.New(
|
||||||
|
interp.Env(expand.ListEnviron(env...)),
|
||||||
|
interp.StdIO(os.Stdin, os.Stdout, os.Stderr),
|
||||||
|
interp.ExecHandler(helpers.ExecHandler),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
fn, ok := dec.GetFunc("version")
|
||||||
|
if ok {
|
||||||
|
log.Info("Executing version()").Send()
|
||||||
|
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
|
||||||
|
err = fn(
|
||||||
|
ctx,
|
||||||
|
interp.Dir(filepath.Dir(script)),
|
||||||
|
interp.StdIO(os.Stdin, buf, os.Stderr),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Error building package").Err(err).Send()
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
wd, err := os.Getwd()
|
newVer := strings.TrimSpace(buf.String())
|
||||||
|
err = setVersion(ctx, runner, newVer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Error getting working directory").Err(err).Send()
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, pkgPath := range pkgPaths {
|
log.Info("Updating version").Str("new", newVer).Send()
|
||||||
name := filepath.Base(pkgPath)
|
}
|
||||||
err = osutils.Move(pkgPath, filepath.Join(wd, name))
|
|
||||||
|
var vars BuildVars
|
||||||
|
err = dec.DecodeVars(&vars)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !archMatches(vars.Architectures) {
|
||||||
|
var buildAnyway bool
|
||||||
|
survey.AskOne(
|
||||||
|
&survey.Confirm{
|
||||||
|
Message: "Your system's CPU architecture doesn't match this package. Do you want to build anyway?",
|
||||||
|
Default: true,
|
||||||
|
},
|
||||||
|
&buildAnyway,
|
||||||
|
)
|
||||||
|
if !buildAnyway {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Building package").Str("name", vars.Name).Str("version", vars.Version).Send()
|
||||||
|
|
||||||
|
baseDir := filepath.Join(cacheDir, "pkgs", vars.Name)
|
||||||
|
srcdir := filepath.Join(baseDir, "src")
|
||||||
|
pkgdir := filepath.Join(baseDir, "pkg")
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
var buildDeps []string
|
||||||
|
for _, pkgName := range vars.BuildDepends {
|
||||||
|
if _, ok := installed[pkgName]; !ok {
|
||||||
|
buildDeps = append(buildDeps, pkgName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(buildDeps) > 0 {
|
||||||
|
log.Info("Installing build dependencies").Send()
|
||||||
|
installPkgs(ctx, buildDeps, mgr, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
var builtDeps, builtNames, repoDeps []string
|
||||||
|
if len(vars.Depends) > 0 {
|
||||||
|
log.Info("Installing dependencies").Send()
|
||||||
|
|
||||||
|
scripts, notFound := findPkgs(vars.Depends)
|
||||||
|
for _, script := range scripts {
|
||||||
|
pkgPaths, pkgNames, err := buildPackage(ctx, script, mgr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Error moving the package").Err(err).Send()
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
repodir := filepath.Dir(script)
|
||||||
|
|
||||||
|
err = setDirVars(ctx, runner, srcdir, pkgdir, repodir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
uniq(
|
||||||
|
&repoDeps,
|
||||||
|
&builtDeps,
|
||||||
|
&builtNames,
|
||||||
|
)
|
||||||
|
|
||||||
|
pkgInfo := &nfpm.Info{
|
||||||
|
Name: vars.Name,
|
||||||
|
Description: vars.Description,
|
||||||
|
Arch: runtime.GOARCH,
|
||||||
|
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...),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if vars.Epoch != 0 {
|
||||||
|
pkgInfo.Epoch = strconv.FormatUint(uint64(vars.Epoch), 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
setScripts(&vars, pkgInfo, filepath.Dir(script))
|
||||||
|
|
||||||
|
if slices.Contains(vars.Architectures, "all") {
|
||||||
|
pkgInfo.Arch = "all"
|
||||||
|
}
|
||||||
|
|
||||||
|
if pkgInfo.Arch == "arm" {
|
||||||
|
pkgInfo.Arch = cpu.ARMVariant()
|
||||||
|
}
|
||||||
|
|
||||||
|
contents := []*files.Content{}
|
||||||
|
filepath.Walk(pkgdir, func(path string, fi os.FileInfo, err error) error {
|
||||||
|
trimmed := strings.TrimPrefix(path, 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
|
||||||
|
}
|
||||||
|
|
||||||
|
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 nil
|
||||||
},
|
})
|
||||||
|
|
||||||
|
pkgInfo.Overridables.Contents = contents
|
||||||
|
|
||||||
|
pkgFormat := mgr.Format()
|
||||||
|
if format, ok := os.LookupEnv("LURE_PKG_FORMAT"); ok {
|
||||||
|
pkgFormat = format
|
||||||
|
}
|
||||||
|
|
||||||
|
packager, err := nfpm.Get(pkgFormat)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
err = packager.Package(pkgInfo, pkgFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(buildDeps) > 0 {
|
||||||
|
var removeBuildDeps bool
|
||||||
|
err = survey.AskOne(&survey.Confirm{
|
||||||
|
Message: "Would you like to remove build dependencies?",
|
||||||
|
}, &removeBuildDeps)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if removeBuildDeps {
|
||||||
|
err = mgr.Remove(
|
||||||
|
&manager.Opts{
|
||||||
|
AsRoot: true,
|
||||||
|
NoConfirm: true,
|
||||||
|
},
|
||||||
|
buildDeps...,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uniq(&pkgPaths, &pkgNames)
|
||||||
|
|
||||||
|
return pkgPaths, pkgNames, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func genBuildEnv(info *distro.OSRelease) []string {
|
||||||
|
env := os.Environ()
|
||||||
|
|
||||||
|
arch := runtime.GOARCH
|
||||||
|
if arch == "arm" {
|
||||||
|
arch = cpu.ARMVariant()
|
||||||
|
}
|
||||||
|
|
||||||
|
env = append(
|
||||||
|
env,
|
||||||
|
"DISTRO_NAME="+info.Name,
|
||||||
|
"DISTRO_PRETTY_NAME="+info.PrettyName,
|
||||||
|
"DISTRO_ID="+info.ID,
|
||||||
|
"DISTRO_VERSION_ID="+info.VersionID,
|
||||||
|
"DISTRO_ID_LIKE="+strings.Join(info.Like, " "),
|
||||||
|
|
||||||
|
"ARCH="+arch,
|
||||||
|
"NCPU="+strconv.Itoa(runtime.NumCPU()),
|
||||||
|
)
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, src := range bv.Sources {
|
||||||
|
opts := download.GetOptions{
|
||||||
|
SourceURL: src,
|
||||||
|
Destination: srcdir,
|
||||||
|
EncloseGit: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.EqualFold(bv.Checksums[i], "SKIP") {
|
||||||
|
checksum, err := hex.DecodeString(bv.Checksums[i])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
opts.SHA256Sum = checksum
|
||||||
|
}
|
||||||
|
|
||||||
|
err := download.Get(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, repodir string) error {
|
||||||
|
cmd := "srcdir='" + srcdir + "'\npkgdir='" + pkgdir + "'\nrepodir='" + repodir + "'\n"
|
||||||
|
fl, err := syntax.NewParser().Parse(strings.NewReader(cmd), "vars")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getBuildVars only gets the build variables, while disabling exec, stat, open, and readdir
|
||||||
|
func getBuildVars(ctx context.Context, script string, info *distro.OSRelease) (*BuildVars, error) {
|
||||||
|
fl, err := os.Open(script)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := syntax.NewParser().Parse(fl, "lure.sh")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fl.Close()
|
||||||
|
|
||||||
|
runner, err := interp.New(
|
||||||
|
interp.Env(expand.ListEnviron()),
|
||||||
|
interp.ExecHandler(shutils.NopExec),
|
||||||
|
interp.StatHandler(shutils.NopStat),
|
||||||
|
interp.OpenHandler(shutils.NopOpen),
|
||||||
|
interp.ReadDirHandler(shutils.NopReadDir),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = runner.Run(ctx, file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
dec := decoder.New(info, runner)
|
||||||
|
|
||||||
|
var vars BuildVars
|
||||||
|
err = dec.DecodeVars(&vars)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &vars, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func archMatches(architectures []string) bool {
|
||||||
|
if slices.Contains(architectures, "all") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
arch := runtime.GOARCH
|
||||||
|
|
||||||
|
if arch == "arm" {
|
||||||
|
arch = cpu.ARMVariant()
|
||||||
|
}
|
||||||
|
|
||||||
|
if slices.Contains(architectures, "arm") {
|
||||||
|
architectures = append(architectures, cpu.ARMVariant())
|
||||||
|
}
|
||||||
|
|
||||||
|
return slices.Contains(architectures, arch)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setVersion(ctx context.Context, r *interp.Runner, to string) error {
|
||||||
|
fl, err := syntax.NewParser().Parse(strings.NewReader("version='"+to+"'"), "")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return r.Run(ctx, fl)
|
||||||
|
}
|
||||||
|
|
||||||
|
func uniq(ss ...*[]string) {
|
||||||
|
for _, s := range ss {
|
||||||
|
slices.Sort(*s)
|
||||||
|
*s = slices.Compact(*s)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
125
config.go
Normal file
125
config.go
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
/*
|
||||||
|
* LURE - Linux User REpository
|
||||||
|
* Copyright (C) 2022 Arsen Musayelyan
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/pelletier/go-toml/v2"
|
||||||
|
"go.arsenm.dev/lure/manager"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
cacheDir string
|
||||||
|
cfgPath string
|
||||||
|
config Config
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
RootCmd string `toml:"rootCmd"`
|
||||||
|
Repos []Repo `toml:"repo"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Repo struct {
|
||||||
|
Name string `toml:"name"`
|
||||||
|
URL string `toml:"url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultConfig = Config{
|
||||||
|
RootCmd: "sudo",
|
||||||
|
Repos: []Repo{
|
||||||
|
{
|
||||||
|
Name: "default",
|
||||||
|
URL: "https://github.com/Arsen6331/lure-repo.git",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
cfg, cache, err := makeDirs()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error creating directories").Err(err).Send()
|
||||||
|
}
|
||||||
|
cacheDir = cache
|
||||||
|
|
||||||
|
cfgPath = filepath.Join(cfg, "lure.toml")
|
||||||
|
|
||||||
|
cfgFl, err := os.Open(cfgPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error opening config file").Err(err).Send()
|
||||||
|
}
|
||||||
|
defer cfgFl.Close()
|
||||||
|
|
||||||
|
err = toml.NewDecoder(cfgFl).Decode(&config)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error decoding config file").Err(err).Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
manager.DefaultRootCmd = config.RootCmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeDirs() (string, string, error) {
|
||||||
|
cfgDir, err := os.UserConfigDir()
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
baseCfgPath := filepath.Join(cfgDir, "lure")
|
||||||
|
|
||||||
|
err = os.MkdirAll(baseCfgPath, 0o755)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
cfgPath := filepath.Join(baseCfgPath, "lure.toml")
|
||||||
|
|
||||||
|
if _, err := os.Stat(cfgPath); err != nil {
|
||||||
|
cfgFl, err := os.Create(cfgPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = toml.NewEncoder(cfgFl).Encode(&defaultConfig)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
cfgFl.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
cacheDir, err := os.UserCacheDir()
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
baseCachePath := filepath.Join(cacheDir, "lure")
|
||||||
|
|
||||||
|
err = os.MkdirAll(filepath.Join(baseCachePath, "repo"), 0o755)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.MkdirAll(filepath.Join(baseCachePath, "pkgs"), 0o755)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseCfgPath, baseCachePath, nil
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* LURE - Linux User REpository
|
* LURE - Linux User REpository
|
||||||
* Copyright (C) 2023 Elara Musayelyan
|
* Copyright (C) 2022 Arsen Musayelyan
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -20,16 +20,18 @@ package distro
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"lure.sh/lure/internal/shutils/handlers"
|
"go.arsenm.dev/lure/internal/shutils"
|
||||||
"mvdan.cc/sh/v3/expand"
|
"mvdan.cc/sh/v3/expand"
|
||||||
"mvdan.cc/sh/v3/interp"
|
"mvdan.cc/sh/v3/interp"
|
||||||
"mvdan.cc/sh/v3/syntax"
|
"mvdan.cc/sh/v3/syntax"
|
||||||
)
|
)
|
||||||
|
|
||||||
// OSRelease contains information from an os-release file
|
var ErrParse = errors.New("could not parse os-release file")
|
||||||
|
|
||||||
type OSRelease struct {
|
type OSRelease struct {
|
||||||
Name string
|
Name string
|
||||||
PrettyName string
|
PrettyName string
|
||||||
@ -44,17 +46,8 @@ type OSRelease struct {
|
|||||||
Logo string
|
Logo string
|
||||||
}
|
}
|
||||||
|
|
||||||
var parsed *OSRelease
|
// OSReleaseName returns the NAME field of the
|
||||||
|
|
||||||
// OSReleaseName returns a struct parsed from the system's os-release
|
|
||||||
// file. It checks /etc/os-release as well as /usr/lib/os-release.
|
|
||||||
// The first time it's called, it'll parse the os-release file.
|
|
||||||
// Subsequent calls will return the same value.
|
|
||||||
func ParseOSRelease(ctx context.Context) (*OSRelease, error) {
|
func ParseOSRelease(ctx context.Context) (*OSRelease, error) {
|
||||||
if parsed != nil {
|
|
||||||
return parsed, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
fl, err := os.Open("/usr/lib/os-release")
|
fl, err := os.Open("/usr/lib/os-release")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fl, err = os.Open("/etc/os-release")
|
fl, err = os.Open("/etc/os-release")
|
||||||
@ -74,10 +67,10 @@ func ParseOSRelease(ctx context.Context) (*OSRelease, error) {
|
|||||||
// as well as no environment variables in order to prevent vulnerabilities
|
// as well as no environment variables in order to prevent vulnerabilities
|
||||||
// caused by changing the os-release file.
|
// caused by changing the os-release file.
|
||||||
runner, err := interp.New(
|
runner, err := interp.New(
|
||||||
interp.OpenHandler(handlers.NopOpen),
|
interp.OpenHandler(shutils.NopOpen),
|
||||||
interp.ExecHandler(handlers.NopExec),
|
interp.ExecHandler(shutils.NopExec),
|
||||||
interp.ReadDirHandler(handlers.NopReadDir),
|
interp.ReadDirHandler(shutils.NopReadDir),
|
||||||
interp.StatHandler(handlers.NopStat),
|
interp.StatHandler(shutils.NopStat),
|
||||||
interp.Env(expand.ListEnviron()),
|
interp.Env(expand.ListEnviron()),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -86,7 +79,7 @@ func ParseOSRelease(ctx context.Context) (*OSRelease, error) {
|
|||||||
|
|
||||||
err = runner.Run(ctx, file)
|
err = runner.Run(ctx, file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, ErrParse
|
||||||
}
|
}
|
||||||
|
|
||||||
out := &OSRelease{
|
out := &OSRelease{
|
||||||
@ -102,17 +95,9 @@ func ParseOSRelease(ctx context.Context) (*OSRelease, error) {
|
|||||||
Logo: runner.Vars["LOGO"].Str,
|
Logo: runner.Vars["LOGO"].Str,
|
||||||
}
|
}
|
||||||
|
|
||||||
distroUpdated := false
|
if runner.Vars["ID_LIKE"].IsSet() {
|
||||||
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, " ")
|
out.Like = strings.Split(runner.Vars["ID_LIKE"].Str, " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
parsed = out
|
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
@ -1,8 +1,6 @@
|
|||||||
# LURE Docs
|
# LURE Docs
|
||||||
|
|
||||||
- [Configuration](configuration.md)
|
- [Build Scripts](build-scripts.md)
|
||||||
- [Usage](usage.md)
|
- [Usage](usage.md)
|
||||||
- [Packages](packages)
|
- [Configuration](configuration.md)
|
||||||
- [Build Scripts](packages/build-scripts.md)
|
- [Adding Packages to LURE's repo](adding-packages.md)
|
||||||
- [Package Conventions](packages/conventions.md)
|
|
||||||
- [Adding Packages to LURE's repo](packages/adding-packages.md)
|
|
@ -5,23 +5,19 @@
|
|||||||
- `go` (1.18+)
|
- `go` (1.18+)
|
||||||
- `git`
|
- `git`
|
||||||
- `lure-analyzer`
|
- `lure-analyzer`
|
||||||
- `go install go.elara.ws/lure-repo-bot/cmd/lure-analyzer@latest`
|
- `go install go.arsenm.dev/lure-repo-bot/cmd/lure-analyzer@latest`
|
||||||
- `shfmt`
|
- `shfmt`
|
||||||
- May be available in distro repos
|
- May be available in distro repos
|
||||||
- `go install mvdan.cc/sh/v3/cmd/shfmt@latest`
|
- `go install mvdan.cc/sh/v3/cmd/shfmt@latest`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## How to test a package
|
|
||||||
|
|
||||||
To test packages you can first create [a `lure.sh` shell file](./build-scripts.md) and then run the `lure build` comand to build the local `lure.sh` file into a package for your distro (more info about the `build` command [here](./usage.md#build)). You can then install this file to your distro and test it.
|
|
||||||
|
|
||||||
## How to submit a package
|
## How to submit a package
|
||||||
|
|
||||||
LURE's repo is hosted on Github at https://github.com/Elara6331/lure-repo. In it, there are multiple directories each containing a `lure.sh` file. In order to add a package to LURE's repo, simply create a PR with a [build script](./build-scripts.md) and place it in a directory with the same name as the package.
|
LURE's repo is hosted on Github at https://github.com/Arsen6331/lure-repo. In it, there are multiple directories each containing a `lure.sh` file. In order to add a package to LURE's repo, simply create a PR with a [build script](./build-scripts.md) and place it in a directory with the same name as the package.
|
||||||
|
|
||||||
Upon submitting the PR, [lure-repo-bot](https://github.com/Elara6331/lure-repo-bot) will pull your PR and analyze it, providing suggestions for fixes as review comments. If there are no problems, the bot will approve your changes. If there are issues, re-request review from the bot after you've finished applying the fixes and it will automatically review the PR again.
|
Upon submitting the PR, [lure-repo-bot](https://github.com/Arsen6331/lure-repo-bot) will pull your PR and analyze it, providing suggestions for fixes as review comments. If there are no problems, the bot will approve your changes. If there are issues, re-request review from the bot after you've finished applying the fixes and it will automatically review the PR again.
|
||||||
|
|
||||||
All scripts submitted to the LURE repo should be formatted with `shfmt`. If they are not properly formatted, Github Actions will add suggestions in the "Files Changed" tab of the PR.
|
All scripts submitted to the LURE repo should be formatted with `shfmt`. If they are not properly formatted, Github Actions will add suggestions in the "Files Changed" tab of the PR.
|
||||||
|
|
||||||
Once your PR is merged, LURE will pull the changed repo and your package will be available for people to install.
|
Once your PR is merged, LURE will pull the changed repo and your package will be available for people to install.
|
@ -21,7 +21,6 @@ LURE uses build scripts similar to the AUR's PKGBUILDs. This is the documentatio
|
|||||||
- [conflicts](#conflicts)
|
- [conflicts](#conflicts)
|
||||||
- [deps](#deps)
|
- [deps](#deps)
|
||||||
- [build_deps](#build_deps)
|
- [build_deps](#build_deps)
|
||||||
- [opt_deps](#opt_deps)
|
|
||||||
- [replaces](#replaces)
|
- [replaces](#replaces)
|
||||||
- [sources](#sources)
|
- [sources](#sources)
|
||||||
- [checksums](#checksums)
|
- [checksums](#checksums)
|
||||||
@ -49,13 +48,12 @@ LURE uses build scripts similar to the AUR's PKGBUILDs. This is the documentatio
|
|||||||
- [install-manual](#install-manual)
|
- [install-manual](#install-manual)
|
||||||
- [install-desktop](#install-desktop)
|
- [install-desktop](#install-desktop)
|
||||||
- [install-library](#install-library)
|
- [install-library](#install-library)
|
||||||
- [git-version](#git-version)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Distro Overrides
|
## Distro Overrides
|
||||||
|
|
||||||
Allowing LURE to run on different distros provides some challenges. For example, some distros use different names for their packages. This is solved using distro overrides. Any variable or function used in a LURE build script may be overridden based on distro and CPU architecture. The way you do this is by appending the distro and/or architecture to the end of the name. For example, [ITD](https://gitea.elara.ws/Elara6331/itd) depends on the `pactl` command as well as DBus and BlueZ. These are named somewhat differently on different distros. For ITD, I use the following for the dependencies:
|
Allowing LURE to run on different distros provides some challenges. For example, some distros use different names for their packages. This is solved using distro overrides. Any variable or function used in a LURE build script may be overridden based on distro and CPU architecture. The way you do this is by appending the distro and/or architecture to the end of the name. For example, [ITD](https://gitea.arsenm.dev/Arsen6331/itd) depends on the `pactl` command as well as DBus and BlueZ. These are named somewhat differently on different distros. For ITD, I use the following for the dependencies:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
deps=('dbus' 'bluez' 'pulseaudio-utils')
|
deps=('dbus' 'bluez' 'pulseaudio-utils')
|
||||||
@ -118,7 +116,7 @@ The `homepage` field contains the URL to the website of the project packaged by
|
|||||||
The `maintainer` field contains the name and email address of the person maintaining the package. Example:
|
The `maintainer` field contains the name and email address of the person maintaining the package. Example:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Elara Musayelyan <elara@elara.ws>
|
Arsen Musayelyan <arsen@arsenm.dev>
|
||||||
```
|
```
|
||||||
|
|
||||||
While LURE does not require this field to be set, Debian has deprecated unset maintainer fields, and may disallow their use in `.deb` packages in the future.
|
While LURE does not require this field to be set, Debian has deprecated unset maintainer fields, and may disallow their use in `.deb` packages in the future.
|
||||||
@ -161,17 +159,6 @@ The `deps` array contains the dependencies for the package. LURE repos will be c
|
|||||||
|
|
||||||
The `build_deps` array contains the dependencies that are required to build the package. They will be installed before the build starts. Similarly to the `deps` array, LURE repos will be checked first.
|
The `build_deps` array contains the dependencies that are required to build the package. They will be installed before the build starts. Similarly to the `deps` array, LURE repos will be checked first.
|
||||||
|
|
||||||
### opt_deps
|
|
||||||
|
|
||||||
The `opt_deps` array contains optional dependencies for the package. A description can be added after ": ", but it's not required.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
opt_deps_arch=(
|
|
||||||
'git: Download git repositories'
|
|
||||||
'aria2: Download files'
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
### replaces
|
### replaces
|
||||||
|
|
||||||
The `replaces` array contains the packages that are replaced by this package. Generally, if package managers find a package with a `replaces` field set, they will remove the listed package(s) and install that one instead. This is only useful if the packages are being stored in a repo for your package manager.
|
The `replaces` array contains the packages that are replaced by this package. Generally, if package managers find a package with a `replaces` field set, they will remove the listed package(s) and install that one instead. This is only useful if the packages are being stored in a repo for your package manager.
|
||||||
@ -194,26 +181,25 @@ https://example.com/archive.tar.gz?~archive=false
|
|||||||
|
|
||||||
If the URL scheme starts with `git+`, the source will be downloaded as a git repo. The git download mode supports multiple parameters:
|
If the URL scheme starts with `git+`, the source will be downloaded as a git repo. The git download mode supports multiple parameters:
|
||||||
|
|
||||||
- `~rev`: Specify which revision of the repo to check out.
|
- `~tag`: Specify which tag of the repo to check out.
|
||||||
|
- `~branch`: Specify which branch of the repo to check out.
|
||||||
|
- `~commit`: Specify which commit of the repo to check out.
|
||||||
- `~depth`: Specify what depth should be used when cloning the repo. Must be an integer.
|
- `~depth`: Specify what depth should be used when cloning the repo. Must be an integer.
|
||||||
- `~name`: Specify the name of the directory into which the git repo should be cloned.
|
- `~name`: Specify the name of the directory into which the git repo should be cloned.
|
||||||
- `~recursive`: If set to true, submodules will be cloned recursively. It is false by default.
|
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
git+https://gitea.elara.ws/Elara6331/itd?~rev=resource-loading&~depth=1
|
git+https://gitea.arsenm.dev/Arsen6331/itd?~branch=resource-loading&~depth=1
|
||||||
```
|
```
|
||||||
|
|
||||||
```text
|
```text
|
||||||
git+https://gitea.elara.ws/lure/lure?~rev=v0.0.1&~recursive=true
|
git+https://gitea.arsenm.dev/Arsen6331/lure?~tag=v0.0.1
|
||||||
```
|
```
|
||||||
|
|
||||||
### checksums
|
### checksums
|
||||||
|
|
||||||
The `checksums` array must be the same length as the `sources` array. It contains checksums for the source files. The files are checked against the checksums and the build fails if they don't match.
|
The `checksums` array must be the same length as the `sources` array. It contains sha256 checksums for the source files. The files are checked against the checksums and the build fails if they don't match.
|
||||||
|
|
||||||
By default, checksums are expected to be sha256. To change the algorithm, add it before the hash with a colon in between. For example, `md5:bc0c6f5dcd06bddbca9a0163e4c9f2e1`. The following algorithms are currently supported: `sha256`, `sha224`, `sha512`, `sha384`, `sha1`, and `md5`.
|
|
||||||
|
|
||||||
To skip the check for a particular source, set the corresponding checksum to `SKIP`.
|
To skip the check for a particular source, set the corresponding checksum to `SKIP`.
|
||||||
|
|
||||||
@ -256,11 +242,11 @@ The rest of the scripts are available in all packages.
|
|||||||
|
|
||||||
This section documents user-defined functions that can be added to build scripts. Any functions marked with `(*)` are required.
|
This section documents user-defined functions that can be added to build scripts. Any functions marked with `(*)` are required.
|
||||||
|
|
||||||
All functions are executed in the `$srcdir` directory
|
All functions except for `version()` are executed in the `$srcdir` directory
|
||||||
|
|
||||||
### version
|
### version
|
||||||
|
|
||||||
The `version()` function updates the `version` variable. This allows for automatically deriving the version from sources. This is most useful for git packages, which usually don't need to be changed, so their `version` variable stays the same.
|
The `version()` function is the first to run. It updates the `version` variable. This allows for automatically deriving the version from sources. This is most useful for git packages, which usually don't need to be changed, so their `version` variable stays the same.
|
||||||
|
|
||||||
An example of using this for git:
|
An example of using this for git:
|
||||||
|
|
||||||
@ -273,6 +259,8 @@ version() {
|
|||||||
|
|
||||||
The AUR equivalent is the [`pkgver()` function](https://wiki.archlinux.org/title/VCS_package_guidelines#The_pkgver()_function)
|
The AUR equivalent is the [`pkgver()` function](https://wiki.archlinux.org/title/VCS_package_guidelines#The_pkgver()_function)
|
||||||
|
|
||||||
|
This function does not run in `$srcdir` because it is executed before the source directory is even created. Instead, it runs in `$repodir`.
|
||||||
|
|
||||||
### prepare
|
### prepare
|
||||||
|
|
||||||
The `prepare()` function is meant to prepare the sources for building and packaging. This is the function in which patches should be applied, for example, by the `patch` command, and where tools like `go generate` should be executed.
|
The `prepare()` function is meant to prepare the sources for building and packaging. This is the function in which patches should be applied, for example, by the `patch` command, and where tools like `go generate` should be executed.
|
||||||
@ -436,7 +424,7 @@ Examples:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
./k9s completion fish | install-completion fish k9s
|
./k9s completion fish | install-completion fish k9s
|
||||||
install-completion bash k9s <./k9s/completions/k9s.bash
|
install-completion bash k9s <./k9s/completions/k9s.fish
|
||||||
```
|
```
|
||||||
|
|
||||||
### install-manual
|
### install-manual
|
||||||
@ -479,21 +467,4 @@ Examples:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
install-library ./${name}/build/libadldap.so
|
install-library ./${name}/build/libadldap.so
|
||||||
```
|
|
||||||
|
|
||||||
### git-version
|
|
||||||
|
|
||||||
`git-version` returns a version number based on the git revision of a repository.
|
|
||||||
|
|
||||||
If an argument is provided, it will be used as the path to the repo. Otherwise, the current directory will be used.
|
|
||||||
|
|
||||||
The version number will be the amount of revisions, a dot, and the short hash of the current revision. For example: `118.e4b8348`.
|
|
||||||
|
|
||||||
The AUR's convention includes an `r` at the beginning of the version number. This is ommitted because some distros expect the version number to start with a digit.
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git-version
|
|
||||||
git-version "$srcdir/itd"
|
|
||||||
```
|
```
|
@ -36,7 +36,7 @@ The `repo` array in the config specifies which repos are added to LURE. Each rep
|
|||||||
```toml
|
```toml
|
||||||
[[repo]]
|
[[repo]]
|
||||||
name = 'default'
|
name = 'default'
|
||||||
url = 'https://github.com/Elara6331/lure-repo.git'
|
url = 'https://github.com/Arsen6331/lure-repo.git'
|
||||||
```
|
```
|
||||||
|
|
||||||
The `default` repo is added by default. Any amount of repos may be added.
|
The `default` repo is added by default. Any amount of repos may be added.
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
# LURE Docs > Packages
|
|
||||||
|
|
||||||
- [Build Scripts](build-scripts.md)
|
|
||||||
- [Package Conventions](conventions.md)
|
|
||||||
- [Adding Packages to LURE's repo](adding-packages.md)
|
|
@ -1,36 +0,0 @@
|
|||||||
# Package Conventions
|
|
||||||
|
|
||||||
## General
|
|
||||||
|
|
||||||
Packages should have the name(s) of what they contain in their `provides` and `conflicts` arrays. That way, they can be installed by users without needing to know the full package name. For example, there are two LURE packages for ITD: `itd-bin`, and `itd-git`. Both of them have provides and conflicts arrays specifying the two commands they install: `itd`, and `itctl`. This means that if a user wants to install ITD, they simply have to type `lure in itd` and LURE will prompt them for which one they want to install.
|
|
||||||
|
|
||||||
## Binary packages
|
|
||||||
|
|
||||||
Packages that install download and install precompiled binaries should have a `-bin` suffix.
|
|
||||||
|
|
||||||
## Git packages
|
|
||||||
|
|
||||||
Packages that build and install programs from source code cloned directly from Git should have a `-git` suffix.
|
|
||||||
|
|
||||||
The versions of these packages should consist of the amount of revisions followed by the current revision, separated by a period. For example: `183.80187b0`. Note that unlike the AUR, there is no `r` at the beginning. This is because some package managers refuse to install packages whose version numbers don't start with a digit.
|
|
||||||
|
|
||||||
This version number can be obtained using the following command:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
printf "%s.%s" "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)"
|
|
||||||
```
|
|
||||||
|
|
||||||
The `version()` function for such packages should use the LURE-provided `git-version` helper command, like so:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
version() {
|
|
||||||
cd "$srcdir/$name"
|
|
||||||
git-version
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This uses LURE's embedded Git implementation, which ensures that the user doesn't need Git installed on their system in order to install `-git` packages.
|
|
||||||
|
|
||||||
## Other packages
|
|
||||||
|
|
||||||
Packages that download sources for a specific version of a program should not have any suffix, even if those sources are downloaded from Git.
|
|
@ -12,8 +12,6 @@
|
|||||||
- [addrepo](#addrepo)
|
- [addrepo](#addrepo)
|
||||||
- [removerepo](#removerepo)
|
- [removerepo](#removerepo)
|
||||||
- [refresh](#refresh)
|
- [refresh](#refresh)
|
||||||
- [fix](#fix)
|
|
||||||
- [version](#version)
|
|
||||||
- [Environment Variables](#environment-variables)
|
- [Environment Variables](#environment-variables)
|
||||||
- [LURE_DISTRO](#lure_distro)
|
- [LURE_DISTRO](#lure_distro)
|
||||||
- [LURE_PKG_FORMAT](#lure_pkg_format)
|
- [LURE_PKG_FORMAT](#lure_pkg_format)
|
||||||
@ -25,21 +23,12 @@
|
|||||||
|
|
||||||
### install
|
### install
|
||||||
|
|
||||||
The install command installs a package from the LURE repos. Any packages that aren't found in LURE's repos get forwarded to the system package manager for installation.
|
The install command installs a command from the LURE repos. Any packages that aren't found in LURE's repos get forwarded to the system package manager for installation.
|
||||||
|
|
||||||
The package arguments do not have to be exact. LURE will check the `provides` array if an exact match is not found. There is also support for using "%" as a wildcard.
|
Example:
|
||||||
|
|
||||||
If multiple packages are found, you will be prompted to select which you want to install.
|
|
||||||
|
|
||||||
By default, if a package has already been built, LURE will install the cached package rather than re-build it. Use the `-c` or `--clean` flag to force a re-build.
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
lure in itd-bin # only finds itd-bin
|
lure in itd-bin
|
||||||
lure in itd # finds itd-bin and itd-git
|
|
||||||
lure in it% # finds itd-bin, itd-git, and itgui-git
|
|
||||||
lure in -c itd-bin
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### remove
|
### remove
|
||||||
@ -56,8 +45,6 @@ lure rm firefox
|
|||||||
|
|
||||||
The upgrade command looks through the packages installed on your system and sees if any of them match LURE repo packages. If they do, their versions are compared using the `rpmvercmp` algorithm. If LURE repos contain a newer version, the package is upgraded.
|
The upgrade command looks through the packages installed on your system and sees if any of them match LURE repo packages. If they do, their versions are compared using the `rpmvercmp` algorithm. If LURE repos contain a newer version, the package is upgraded.
|
||||||
|
|
||||||
By default, if a package has already been built, LURE will install the cached package rather than re-build it. Use the `-c` or `--clean` flag to force a re-build.
|
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
@ -68,36 +55,20 @@ lure up
|
|||||||
|
|
||||||
The info command displays information about a package in LURE's repos.
|
The info command displays information about a package in LURE's repos.
|
||||||
|
|
||||||
The package arguments do not have to be exact. LURE will check the `provides` array if an exact match is not found. There is also support for using "%" as a wildcard.
|
|
||||||
|
|
||||||
If multiple packages are found, you will be prompted to select which you want to show.
|
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
lure info itd-bin # only finds itd-bin
|
lure info itd-bin
|
||||||
lure info itd # finds itd-bin and itd-git
|
|
||||||
lure info it% # finds itd-bin, itd-git, and itgui-git
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### list
|
### list
|
||||||
|
|
||||||
The list command lists all LURE repo packages as well as their versions
|
The list command lists all LURE repo packages as well as their versions
|
||||||
|
|
||||||
This command accepts a single optional argument. This argument is a pattern to filter found packages against.
|
Example:
|
||||||
|
|
||||||
The pattern does not have to be exact. LURE will check the `provides` array if an exact match is not found. There is also support for using "%" as a wildcard.
|
|
||||||
|
|
||||||
There is a `-I` or `--installed` flag that filters out any packages that are not installed on the system
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
lure ls # lists all LURE packages
|
lure ls
|
||||||
lure ls -I # lists all installed packages
|
|
||||||
lure ls i% # lists all packages starting with "i"
|
|
||||||
lure ls %d # lists all packages ending with "d"
|
|
||||||
lure ls -I i% # lists all installed packages that start with "i"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### build
|
### build
|
||||||
@ -117,7 +88,7 @@ The addrepo command adds a repository to LURE if it doesn't already exist. The `
|
|||||||
Example:
|
Example:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
lure ar -n default -u https://github.com/Elara6331/lure-repo
|
lure ar -n default -u https://github.com/Arsen6331/lure-repo
|
||||||
```
|
```
|
||||||
|
|
||||||
### removerepo
|
### removerepo
|
||||||
@ -140,26 +111,6 @@ Example:
|
|||||||
lure ref
|
lure ref
|
||||||
```
|
```
|
||||||
|
|
||||||
### fix
|
|
||||||
|
|
||||||
The fix command attempts to fix issues with LURE by deleting and rebuilding LURE's cache
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
lure fix
|
|
||||||
```
|
|
||||||
|
|
||||||
### version
|
|
||||||
|
|
||||||
The version command returns the current LURE version and exits
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
lure version
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Environment Variables
|
## Environment Variables
|
||||||
|
292
download/download.go
Normal file
292
download/download.go
Normal file
@ -0,0 +1,292 @@
|
|||||||
|
/*
|
||||||
|
* LURE - Linux User REpository
|
||||||
|
* Copyright (C) 2022 Arsen Musayelyan
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package download
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
|
"errors"
|
||||||
|
"hash"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-git/go-git/v5"
|
||||||
|
"github.com/go-git/go-git/v5/plumbing"
|
||||||
|
"github.com/mholt/archiver/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrChecksumMismatch = errors.New("checksums did not match")
|
||||||
|
|
||||||
|
type GetOptions struct {
|
||||||
|
SourceURL string
|
||||||
|
Destination string
|
||||||
|
SHA256Sum []byte
|
||||||
|
// EncloseGit determines if Get will create an enclosing
|
||||||
|
// directory for git repos
|
||||||
|
EncloseGit bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get downloads from a URL
|
||||||
|
func Get(ctx context.Context, opts GetOptions) error {
|
||||||
|
dest, err := filepath.Abs(opts.Destination)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
opts.Destination = dest
|
||||||
|
|
||||||
|
err = os.MkdirAll(opts.Destination, 0o755)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
src, err := url.Parse(opts.SourceURL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
query := src.Query()
|
||||||
|
|
||||||
|
if strings.HasPrefix(src.Scheme, "git+") {
|
||||||
|
err = getGit(ctx, src, query, opts)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = getFile(ctx, src, query, opts)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getGit(ctx context.Context, src *url.URL, query url.Values, opts GetOptions) (err error) {
|
||||||
|
tag := query.Get("~tag")
|
||||||
|
query.Del("~tag")
|
||||||
|
|
||||||
|
branch := query.Get("~branch")
|
||||||
|
query.Del("~branch")
|
||||||
|
|
||||||
|
commit := query.Get("~commit")
|
||||||
|
query.Del("~commit")
|
||||||
|
|
||||||
|
depthStr := query.Get("~depth")
|
||||||
|
query.Del("~depth")
|
||||||
|
|
||||||
|
name := query.Get("~name")
|
||||||
|
query.Del("~name")
|
||||||
|
|
||||||
|
var refName plumbing.ReferenceName
|
||||||
|
if tag != "" {
|
||||||
|
refName = plumbing.NewTagReferenceName(tag)
|
||||||
|
} else if branch != "" {
|
||||||
|
refName = plumbing.NewBranchReferenceName(branch)
|
||||||
|
}
|
||||||
|
|
||||||
|
src.Scheme = strings.TrimPrefix(src.Scheme, "git+")
|
||||||
|
src.RawQuery = query.Encode()
|
||||||
|
|
||||||
|
if name == "" {
|
||||||
|
name = path.Base(src.Path)
|
||||||
|
name = strings.TrimSuffix(name, ".git")
|
||||||
|
}
|
||||||
|
|
||||||
|
dstDir := opts.Destination
|
||||||
|
if opts.EncloseGit {
|
||||||
|
dstDir = filepath.Join(opts.Destination, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
depth := 0
|
||||||
|
if depthStr != "" {
|
||||||
|
depth, err = strconv.Atoi(depthStr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cloneOpts := &git.CloneOptions{
|
||||||
|
URL: src.String(),
|
||||||
|
Progress: os.Stderr,
|
||||||
|
Depth: depth,
|
||||||
|
}
|
||||||
|
|
||||||
|
repo, err := git.PlainCloneContext(ctx, dstDir, false, cloneOpts)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
w, err := repo.Worktree()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
checkoutOpts := &git.CheckoutOptions{}
|
||||||
|
if refName != "" {
|
||||||
|
checkoutOpts.Branch = refName
|
||||||
|
} else if commit != "" {
|
||||||
|
checkoutOpts.Hash = plumbing.NewHash(commit)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return w.Checkout(checkoutOpts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFile(ctx context.Context, src *url.URL, query url.Values, opts GetOptions) error {
|
||||||
|
name := query.Get("~name")
|
||||||
|
query.Del("~name")
|
||||||
|
|
||||||
|
archive := query.Get("~archive")
|
||||||
|
query.Del("~archive")
|
||||||
|
|
||||||
|
src.RawQuery = query.Encode()
|
||||||
|
|
||||||
|
if name == "" {
|
||||||
|
name = path.Base(src.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, src.String(), nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
hash := sha256.New()
|
||||||
|
|
||||||
|
format, input, err := archiver.Identify(name, res.Body)
|
||||||
|
if err == archiver.ErrNoMatch || archive == "false" {
|
||||||
|
fl, err := os.Create(filepath.Join(opts.Destination, name))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
w := io.MultiWriter(hash, fl)
|
||||||
|
|
||||||
|
_, err = io.Copy(w, input)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Body.Close()
|
||||||
|
fl.Close()
|
||||||
|
|
||||||
|
if opts.SHA256Sum != nil {
|
||||||
|
sum := hash.Sum(nil)
|
||||||
|
if !bytes.Equal(opts.SHA256Sum, sum) {
|
||||||
|
return ErrChecksumMismatch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
err = extractFile(ctx, input, hash, format, name, opts)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractFile(ctx context.Context, input io.Reader, hash hash.Hash, format archiver.Format, name string, opts GetOptions) (err error) {
|
||||||
|
r := io.TeeReader(input, hash)
|
||||||
|
fname := format.Name()
|
||||||
|
|
||||||
|
switch format := format.(type) {
|
||||||
|
case archiver.Extractor:
|
||||||
|
err = format.Extract(ctx, r, nil, func(ctx context.Context, f archiver.File) error {
|
||||||
|
fr, err := f.Open()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer fr.Close()
|
||||||
|
fi, err := f.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fm := fi.Mode()
|
||||||
|
|
||||||
|
path := filepath.Join(opts.Destination, f.NameInArchive)
|
||||||
|
|
||||||
|
err = os.MkdirAll(filepath.Dir(path), 0o755)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.IsDir() {
|
||||||
|
err = os.Mkdir(path, 0o755)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
outFl, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, fm.Perm())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer outFl.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(outFl, fr)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case archiver.Decompressor:
|
||||||
|
rc, err := format.OpenReader(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer rc.Close()
|
||||||
|
|
||||||
|
path := filepath.Join(opts.Destination, name)
|
||||||
|
path = strings.TrimSuffix(path, fname)
|
||||||
|
|
||||||
|
outFl, err := os.Create(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.Copy(outFl, rc)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.SHA256Sum != nil {
|
||||||
|
sum := hash.Sum(nil)
|
||||||
|
if !bytes.Equal(opts.SHA256Sum, sum) {
|
||||||
|
return ErrChecksumMismatch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
64
fix.go
64
fix.go
@ -1,64 +0,0 @@
|
|||||||
/*
|
|
||||||
* LURE - Linux User REpository
|
|
||||||
* Copyright (C) 2023 Elara 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 (
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
"lure.sh/lure/internal/config"
|
|
||||||
"lure.sh/lure/internal/db"
|
|
||||||
"lure.sh/lure/pkg/loggerctx"
|
|
||||||
"lure.sh/lure/pkg/repos"
|
|
||||||
)
|
|
||||||
|
|
||||||
var fixCmd = &cli.Command{
|
|
||||||
Name: "fix",
|
|
||||||
Usage: "Attempt to fix problems with LURE",
|
|
||||||
Action: func(c *cli.Context) error {
|
|
||||||
ctx := c.Context
|
|
||||||
log := loggerctx.From(ctx)
|
|
||||||
|
|
||||||
db.Close()
|
|
||||||
paths := config.GetPaths(ctx)
|
|
||||||
|
|
||||||
log.Info("Removing cache directory").Send()
|
|
||||||
|
|
||||||
err := os.RemoveAll(paths.CacheDir)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Unable to remove cache directory").Err(err).Send()
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("Rebuilding cache").Send()
|
|
||||||
|
|
||||||
err = os.MkdirAll(paths.CacheDir, 0o755)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Unable to create new cache directory").Err(err).Send()
|
|
||||||
}
|
|
||||||
|
|
||||||
err = repos.Pull(ctx, config.Config(ctx).Repos)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Error pulling repos").Err(err).Send()
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("Done").Send()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
45
gen.go
45
gen.go
@ -1,45 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
"lure.sh/lure/pkg/gen"
|
|
||||||
)
|
|
||||||
|
|
||||||
var genCmd = &cli.Command{
|
|
||||||
Name: "generate",
|
|
||||||
Usage: "Generate a LURE script from a template",
|
|
||||||
Aliases: []string{"gen"},
|
|
||||||
Subcommands: []*cli.Command{
|
|
||||||
genPipCmd,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var genPipCmd = &cli.Command{
|
|
||||||
Name: "pip",
|
|
||||||
Usage: "Generate a LURE script for a pip module",
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "name",
|
|
||||||
Aliases: []string{"n"},
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "version",
|
|
||||||
Aliases: []string{"v"},
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "description",
|
|
||||||
Aliases: []string{"d"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Action: func(c *cli.Context) error {
|
|
||||||
return gen.Pip(os.Stdout, gen.PipOptions{
|
|
||||||
Name: c.String("name"),
|
|
||||||
Version: c.String("version"),
|
|
||||||
Description: c.String("description"),
|
|
||||||
})
|
|
||||||
},
|
|
||||||
}
|
|
130
go.mod
130
go.mod
@ -1,126 +1,74 @@
|
|||||||
module lure.sh/lure
|
module go.arsenm.dev/lure
|
||||||
|
|
||||||
go 1.21
|
go 1.18
|
||||||
|
|
||||||
toolchain go1.21.3
|
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/AlecAivazis/survey/v2 v2.3.7
|
github.com/AlecAivazis/survey/v2 v2.3.6
|
||||||
github.com/PuerkitoBio/purell v1.2.0
|
github.com/go-git/go-git/v5 v5.4.2
|
||||||
github.com/alecthomas/chroma/v2 v2.9.1
|
github.com/goreleaser/nfpm/v2 v2.20.0
|
||||||
github.com/charmbracelet/bubbles v0.16.1
|
github.com/mholt/archiver/v4 v4.0.0-alpha.7
|
||||||
github.com/charmbracelet/bubbletea v0.24.2
|
|
||||||
github.com/charmbracelet/lipgloss v0.8.0
|
|
||||||
github.com/go-git/go-billy/v5 v5.5.0
|
|
||||||
github.com/go-git/go-git/v5 v5.9.0
|
|
||||||
github.com/goreleaser/nfpm/v2 v2.33.0
|
|
||||||
github.com/jmoiron/sqlx v1.3.5
|
|
||||||
github.com/mattn/go-isatty v0.0.19
|
|
||||||
github.com/mholt/archiver/v4 v4.0.0-alpha.8
|
|
||||||
github.com/mitchellh/mapstructure v1.5.0
|
github.com/mitchellh/mapstructure v1.5.0
|
||||||
github.com/muesli/reflow v0.3.0
|
github.com/pelletier/go-toml/v2 v2.0.5
|
||||||
github.com/pelletier/go-toml/v2 v2.1.0
|
github.com/urfave/cli/v2 v2.16.3
|
||||||
github.com/schollz/progressbar/v3 v3.13.1
|
go.arsenm.dev/logger v0.0.0-20220630204155-5ba23e583f0a
|
||||||
github.com/urfave/cli/v2 v2.25.7
|
golang.org/x/exp v0.0.0-20220916125017-b168a2c6b86b
|
||||||
github.com/vmihailenco/msgpack/v5 v5.3.5
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f
|
||||||
go.elara.ws/logger v0.0.0-20230421022458-e80700db2090
|
|
||||||
go.elara.ws/translate v0.0.0-20230421025926-32ccfcd110e6
|
|
||||||
go.elara.ws/vercmp v0.0.0-20230622214216-0b2b067575c4
|
|
||||||
golang.org/x/crypto v0.13.0
|
|
||||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9
|
|
||||||
golang.org/x/sys v0.12.0
|
|
||||||
golang.org/x/text v0.13.0
|
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
lure.sh/fakeroot v0.0.0-20231024000108-b130d64a68ee
|
mvdan.cc/sh/v3 v3.5.1
|
||||||
modernc.org/sqlite v1.25.0
|
|
||||||
mvdan.cc/sh/v3 v3.7.0
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
dario.cat/mergo v1.0.0 // indirect
|
|
||||||
github.com/AlekSi/pointer v1.2.0 // indirect
|
github.com/AlekSi/pointer v1.2.0 // indirect
|
||||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||||
github.com/Masterminds/semver/v3 v3.2.1 // indirect
|
github.com/Masterminds/semver v1.5.0 // indirect
|
||||||
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
|
github.com/Masterminds/semver/v3 v3.1.1 // indirect
|
||||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
github.com/Masterminds/sprig v2.22.0+incompatible // indirect
|
||||||
github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect
|
github.com/Microsoft/go-winio v0.5.1 // indirect
|
||||||
github.com/acomagu/bufpipe v1.0.4 // indirect
|
github.com/ProtonMail/go-crypto v0.0.0-20210512092938-c05353c2d58c // indirect
|
||||||
|
github.com/acomagu/bufpipe v1.0.3 // indirect
|
||||||
github.com/andybalholm/brotli v1.0.4 // indirect
|
github.com/andybalholm/brotli v1.0.4 // indirect
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
|
||||||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect
|
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect
|
||||||
github.com/bodgit/plumbing v1.2.0 // indirect
|
|
||||||
github.com/bodgit/sevenzip v1.3.0 // indirect
|
|
||||||
github.com/bodgit/windows v1.0.0 // indirect
|
|
||||||
github.com/cavaliergopher/cpio v1.0.1 // indirect
|
github.com/cavaliergopher/cpio v1.0.1 // indirect
|
||||||
github.com/cloudflare/circl v1.3.3 // indirect
|
|
||||||
github.com/connesc/cipherio v0.2.1 // indirect
|
|
||||||
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||||
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
|
|
||||||
github.com/dlclark/regexp2 v1.10.0 // indirect
|
|
||||||
github.com/dsnet/compress v0.0.1 // indirect
|
github.com/dsnet/compress v0.0.1 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/emirpasic/gods v1.12.0 // indirect
|
||||||
github.com/emirpasic/gods v1.18.1 // indirect
|
github.com/frankban/quicktest v1.14.3 // indirect
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
github.com/go-git/gcfg v1.5.0 // indirect
|
||||||
|
github.com/go-git/go-billy/v5 v5.3.1 // indirect
|
||||||
github.com/gobwas/glob v0.2.3 // indirect
|
github.com/gobwas/glob v0.2.3 // indirect
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
|
||||||
github.com/golang/snappy v0.0.4 // indirect
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
github.com/google/rpmpack v0.5.0 // indirect
|
|
||||||
github.com/google/uuid v1.3.0 // indirect
|
github.com/google/uuid v1.3.0 // indirect
|
||||||
github.com/gookit/color v1.5.1 // indirect
|
github.com/gookit/color v1.5.1 // indirect
|
||||||
github.com/goreleaser/chglog v0.5.0 // indirect
|
github.com/goreleaser/chglog v0.2.2 // indirect
|
||||||
github.com/goreleaser/fileglob v1.3.0 // indirect
|
github.com/goreleaser/fileglob v1.3.0 // indirect
|
||||||
github.com/hashicorp/errwrap v1.0.0 // indirect
|
github.com/huandu/xstrings v1.3.2 // indirect
|
||||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
github.com/imdario/mergo v0.3.13 // indirect
|
||||||
github.com/huandu/xstrings v1.3.3 // indirect
|
|
||||||
github.com/imdario/mergo v0.3.16 // indirect
|
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
github.com/kevinburke/ssh_config v1.1.0 // indirect
|
||||||
github.com/klauspost/compress v1.17.0 // indirect
|
github.com/klauspost/compress v1.15.11 // indirect
|
||||||
github.com/klauspost/pgzip v1.2.6 // indirect
|
github.com/klauspost/pgzip v1.2.5 // indirect
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
|
||||||
github.com/mattn/go-colorable v0.1.2 // indirect
|
github.com/mattn/go-colorable v0.1.2 // indirect
|
||||||
github.com/mattn/go-localereader v0.0.1 // indirect
|
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
|
||||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
|
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
|
||||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
|
|
||||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||||
|
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||||
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect
|
|
||||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
|
||||||
github.com/muesli/termenv v0.15.2 // indirect
|
|
||||||
github.com/nwaples/rardecode/v2 v2.0.0-beta.2 // indirect
|
github.com/nwaples/rardecode/v2 v2.0.0-beta.2 // indirect
|
||||||
github.com/pierrec/lz4/v4 v4.1.15 // indirect
|
github.com/pierrec/lz4/v4 v4.1.14 // indirect
|
||||||
github.com/pjbgf/sha1cd v0.3.0 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
|
||||||
github.com/rivo/uniseg v0.4.4 // indirect
|
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
github.com/sergi/go-diff v1.2.0 // indirect
|
github.com/sergi/go-diff v1.2.0 // indirect
|
||||||
github.com/shopspring/decimal v1.2.0 // indirect
|
|
||||||
github.com/skeema/knownhosts v1.2.0 // indirect
|
|
||||||
github.com/spf13/cast v1.5.1 // indirect
|
|
||||||
github.com/therootcompany/xz v1.0.1 // indirect
|
github.com/therootcompany/xz v1.0.1 // indirect
|
||||||
github.com/ulikunitz/xz v0.5.11 // indirect
|
github.com/ulikunitz/xz v0.5.10 // indirect
|
||||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
github.com/xanzy/ssh-agent v0.3.1 // indirect
|
||||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
|
||||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
|
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
|
||||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||||
gitlab.com/digitalxero/go-conventional-commit v1.0.7 // indirect
|
gitlab.com/digitalxero/go-conventional-commit v1.0.7 // indirect
|
||||||
go4.org v0.0.0-20200411211856-f5505b9728dd // indirect
|
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect
|
||||||
golang.org/x/mod v0.12.0 // indirect
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
|
||||||
golang.org/x/net v0.15.0 // indirect
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
|
||||||
golang.org/x/sync v0.3.0 // indirect
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
|
||||||
golang.org/x/term v0.12.0 // indirect
|
golang.org/x/text v0.3.7 // indirect
|
||||||
golang.org/x/tools v0.13.0 // indirect
|
|
||||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||||
lukechampine.com/uint128 v1.2.0 // indirect
|
|
||||||
modernc.org/cc/v3 v3.40.0 // indirect
|
|
||||||
modernc.org/ccgo/v3 v3.16.13 // indirect
|
|
||||||
modernc.org/libc v1.24.1 // indirect
|
|
||||||
modernc.org/mathutil v1.5.0 // indirect
|
|
||||||
modernc.org/memory v1.6.0 // indirect
|
|
||||||
modernc.org/opt v0.1.3 // indirect
|
|
||||||
modernc.org/strutil v1.1.3 // indirect
|
|
||||||
modernc.org/token v1.0.1 // indirect
|
|
||||||
)
|
)
|
||||||
|
607
go.sum
607
go.sum
@ -1,565 +1,237 @@
|
|||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
github.com/AlecAivazis/survey/v2 v2.3.6 h1:NvTuVHISgTHEHeBFqt6BHOe4Ny/NwGZr7w+F8S9ziyw=
|
||||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
github.com/AlecAivazis/survey/v2 v2.3.6/go.mod h1:4AuI9b7RjAR+G7v9+C4YSlX/YL3K3cWNXgWXOhllqvI=
|
||||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
|
||||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
|
||||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
|
||||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
|
||||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
|
||||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
|
||||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
|
||||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
|
||||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
|
||||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
|
||||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
|
||||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
|
||||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
|
||||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
|
||||||
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
|
||||||
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
|
||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
|
||||||
github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ=
|
|
||||||
github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo=
|
|
||||||
github.com/AlekSi/pointer v1.2.0 h1:glcy/gc4h8HnG2Z3ZECSzZ1IX1x2JxRVuDzaJwQE0+w=
|
github.com/AlekSi/pointer v1.2.0 h1:glcy/gc4h8HnG2Z3ZECSzZ1IX1x2JxRVuDzaJwQE0+w=
|
||||||
github.com/AlekSi/pointer v1.2.0/go.mod h1:gZGfd3dpW4vEc/UlyfKKi1roIqcCgwOIvb0tSNSBle0=
|
github.com/AlekSi/pointer v1.2.0/go.mod h1:gZGfd3dpW4vEc/UlyfKKi1roIqcCgwOIvb0tSNSBle0=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
|
||||||
github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ=
|
github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ=
|
||||||
github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
|
|
||||||
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||||
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||||
github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
|
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
|
||||||
github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
|
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||||
github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
|
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
|
||||||
github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA=
|
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
||||||
github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM=
|
github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60=
|
||||||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
|
||||||
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
|
||||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
|
||||||
|
github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
|
||||||
|
github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY=
|
||||||
|
github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
|
||||||
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
|
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
|
||||||
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
|
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
|
||||||
github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg=
|
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
|
||||||
github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
|
github.com/ProtonMail/go-crypto v0.0.0-20210512092938-c05353c2d58c h1:bNpaLLv2Y4kslsdkdCwAYu8Bak1aGVtxwi8Z/wy4Yuo=
|
||||||
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k=
|
github.com/ProtonMail/go-crypto v0.0.0-20210512092938-c05353c2d58c/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
|
||||||
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw=
|
github.com/ProtonMail/go-mime v0.0.0-20220302105931-303f85f7fe0f h1:CGq7OieOz3wyQJ1fO8S0eO9TCW1JyvLrf8fhzz1i8ko=
|
||||||
github.com/ProtonMail/gopenpgp/v2 v2.7.1 h1:Awsg7MPc2gD3I7IFac2qE3Gdls0lZW8SzrFZ3k1oz0s=
|
github.com/ProtonMail/gopenpgp/v2 v2.2.2 h1:u2m7xt+CZWj88qK1UUNBoXeJCFJwJCZ/Ff4ymGoxEXs=
|
||||||
github.com/ProtonMail/gopenpgp/v2 v2.7.1/go.mod h1:/BU5gfAVwqyd8EfC3Eu7zmuhwYQpKs+cGD8M//iiaxs=
|
github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk=
|
||||||
github.com/PuerkitoBio/purell v1.2.0 h1:/Jdm5QfyM8zdlqT6WVZU4cfP23sot6CEHA4CS49Ezig=
|
github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
|
||||||
github.com/PuerkitoBio/purell v1.2.0/go.mod h1:OhLRTaaIzhvIyofkJfB24gokC7tM42Px5UhoT32THBk=
|
|
||||||
github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ=
|
|
||||||
github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
|
|
||||||
github.com/alecthomas/assert/v2 v2.2.1 h1:XivOgYcduV98QCahG8T5XTezV5bylXe+lBxLG2K2ink=
|
|
||||||
github.com/alecthomas/assert/v2 v2.2.1/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ=
|
|
||||||
github.com/alecthomas/chroma/v2 v2.9.1 h1:0O3lTQh9FxazJ4BYE/MOi/vDGuHn7B+6Bu902N2UZvU=
|
|
||||||
github.com/alecthomas/chroma/v2 v2.9.1/go.mod h1:4TQu7gdfuPjSh76j78ietmqh9LiurGF0EpseFXdKMBw=
|
|
||||||
github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk=
|
|
||||||
github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
|
||||||
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
|
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
|
||||||
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
|
||||||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4=
|
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4=
|
||||||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
|
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
|
||||||
github.com/bodgit/plumbing v1.2.0 h1:gg4haxoKphLjml+tgnecR4yLBV5zo4HAZGCtAh3xCzM=
|
|
||||||
github.com/bodgit/plumbing v1.2.0/go.mod h1:b9TeRi7Hvc6Y05rjm8VML3+47n4XTZPtQ/5ghqic2n8=
|
|
||||||
github.com/bodgit/sevenzip v1.3.0 h1:1ljgELgtHqvgIp8W8kgeEGHIWP4ch3xGI8uOBZgLVKY=
|
|
||||||
github.com/bodgit/sevenzip v1.3.0/go.mod h1:omwNcgZTEooWM8gA/IJ2Nk/+ZQ94+GsytRzOJJ8FBlM=
|
|
||||||
github.com/bodgit/windows v1.0.0 h1:rLQ/XjsleZvx4fR1tB/UxQrK+SJ2OFHzfPjLWWOhDIA=
|
|
||||||
github.com/bodgit/windows v1.0.0/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM=
|
|
||||||
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
|
||||||
github.com/caarlos0/go-rpmutils v0.2.1-0.20211112020245-2cd62ff89b11 h1:IRrDwVlWQr6kS1U8/EtyA1+EHcc4yl8pndcqXWrEamg=
|
github.com/caarlos0/go-rpmutils v0.2.1-0.20211112020245-2cd62ff89b11 h1:IRrDwVlWQr6kS1U8/EtyA1+EHcc4yl8pndcqXWrEamg=
|
||||||
github.com/caarlos0/go-rpmutils v0.2.1-0.20211112020245-2cd62ff89b11/go.mod h1:je2KZ+LxaCNvCoKg32jtOIULcFogJKcL1ZWUaIBjKj0=
|
|
||||||
github.com/caarlos0/testfs v0.4.4 h1:3PHvzHi5Lt+g332CiShwS8ogTgS3HjrmzZxCm6JCDr8=
|
github.com/caarlos0/testfs v0.4.4 h1:3PHvzHi5Lt+g332CiShwS8ogTgS3HjrmzZxCm6JCDr8=
|
||||||
github.com/caarlos0/testfs v0.4.4/go.mod h1:bRN55zgG4XCUVVHZCeU+/Tz1Q6AxEJOEJTliBy+1DMk=
|
github.com/caarlos0/testfs v0.4.4/go.mod h1:bRN55zgG4XCUVVHZCeU+/Tz1Q6AxEJOEJTliBy+1DMk=
|
||||||
github.com/cavaliergopher/cpio v1.0.1 h1:KQFSeKmZhv0cr+kawA3a0xTQCU4QxXF1vhU7P7av2KM=
|
github.com/cavaliergopher/cpio v1.0.1 h1:KQFSeKmZhv0cr+kawA3a0xTQCU4QxXF1vhU7P7av2KM=
|
||||||
github.com/cavaliergopher/cpio v1.0.1/go.mod h1:pBdaqQjnvXxdS/6CvNDwIANIFSP0xRKI16PX4xejRQc=
|
github.com/cavaliergopher/cpio v1.0.1/go.mod h1:pBdaqQjnvXxdS/6CvNDwIANIFSP0xRKI16PX4xejRQc=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
|
||||||
github.com/charmbracelet/bubbles v0.16.1 h1:6uzpAAaT9ZqKssntbvZMlksWHruQLNxg49H5WdeuYSY=
|
|
||||||
github.com/charmbracelet/bubbles v0.16.1/go.mod h1:2QCp9LFlEsBQMvIYERr7Ww2H2bA7xen1idUDIzm/+Xc=
|
|
||||||
github.com/charmbracelet/bubbletea v0.24.2 h1:uaQIKx9Ai6Gdh5zpTbGiWpytMU+CfsPp06RaW2cx/SY=
|
|
||||||
github.com/charmbracelet/bubbletea v0.24.2/go.mod h1:XdrNrV4J8GiyshTtx3DNuYkR1FDaJmO3l2nejekbsgg=
|
|
||||||
github.com/charmbracelet/lipgloss v0.8.0 h1:IS00fk4XAHcf8uZKc3eHeMUTCxUH6NkaTrdyCQk84RU=
|
|
||||||
github.com/charmbracelet/lipgloss v0.8.0/go.mod h1:p4eYUZZJ/0oXTuCQKFF8mqyKCz0ja6y+7DniDDw5KKU=
|
|
||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
|
||||||
github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs=
|
|
||||||
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
|
||||||
github.com/connesc/cipherio v0.2.1 h1:FGtpTPMbKNNWByNrr9aEBtaJtXjqOzkIXNYJp6OEycw=
|
|
||||||
github.com/connesc/cipherio v0.2.1/go.mod h1:ukY0MWJDFnJEbXMQtOcn2VmTpRfzcTz4OoVrWGGJZcA=
|
|
||||||
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=
|
|
||||||
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
|
github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI=
|
||||||
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||||
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
|
|
||||||
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
|
||||||
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
|
|
||||||
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0=
|
|
||||||
github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
|
||||||
github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q=
|
github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q=
|
||||||
github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo=
|
github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo=
|
||||||
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
|
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
|
||||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
|
||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
|
||||||
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU=
|
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||||
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
|
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
|
||||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
|
||||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
|
||||||
github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA=
|
github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
|
||||||
github.com/frankban/quicktest v1.14.5/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
|
||||||
github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
|
github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34=
|
||||||
github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4=
|
github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
github.com/go-git/go-git-fixtures/v4 v4.2.1 h1:n9gGL1Ct/yIw+nfsfr8s4+sbhT+Ncu2SubfXjIWgci8=
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0=
|
||||||
github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU=
|
github.com/go-git/go-git/v5 v5.4.2 h1:BXyZu9t0VkbiHtqrsvdq39UDhGJTl1h55VW6CSC4aY4=
|
||||||
github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow=
|
github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc=
|
||||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f h1:Pz0DHeFij3XFhoBRGUDPzSJ+w2UcK5/0JvF8DRI58r8=
|
|
||||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo=
|
|
||||||
github.com/go-git/go-git/v5 v5.9.0 h1:cD9SFA7sHVRdJ7AYck1ZaAa/yeuBvGPxwXDL8cxrObY=
|
|
||||||
github.com/go-git/go-git/v5 v5.9.0/go.mod h1:RKIqga24sWdMGZF+1Ekv9kylsDz6LzdTSI2s/OsZWE0=
|
|
||||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
|
||||||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
|
||||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
|
||||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
|
||||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
|
||||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
|
||||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
|
||||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
|
||||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
|
||||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
|
||||||
github.com/golang/mock v1.4.3 h1:GV+pQPG/EUUbkh47niozDcADz6go/dUwhVzdUQHIVRw=
|
|
||||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
|
||||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
|
||||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
|
||||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
|
||||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
|
||||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
|
||||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
|
||||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
|
|
||||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
|
|
||||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
|
||||||
github.com/google/rpmpack v0.5.0 h1:L16KZ3QvkFGpYhmp23iQip+mx1X39foEsqszjMNBm8A=
|
|
||||||
github.com/google/rpmpack v0.5.0/go.mod h1:uqVAUVQLq8UY2hCDfmJ/+rtO3aw7qyhc90rCVEabEfI=
|
|
||||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
|
||||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
|
||||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
|
||||||
github.com/gookit/color v1.5.1 h1:Vjg2VEcdHpwq+oY63s/ksHrgJYCTo0bwWvmmYWdE9fQ=
|
github.com/gookit/color v1.5.1 h1:Vjg2VEcdHpwq+oY63s/ksHrgJYCTo0bwWvmmYWdE9fQ=
|
||||||
github.com/gookit/color v1.5.1/go.mod h1:wZFzea4X8qN6vHOSP2apMb4/+w/orMznEzYsIHPaqKM=
|
github.com/gookit/color v1.5.1/go.mod h1:wZFzea4X8qN6vHOSP2apMb4/+w/orMznEzYsIHPaqKM=
|
||||||
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||||
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
|
github.com/goreleaser/chglog v0.2.2 h1:V7nf07baXtGAgGevvqgW2MM4kZ6gOr12vKNSAU3VIZ0=
|
||||||
github.com/goreleaser/chglog v0.5.0 h1:Sk6BMIpx8+vpAf8KyPit34OgWui8c7nKTMHhYx88jJ4=
|
github.com/goreleaser/chglog v0.2.2/go.mod h1:2s5JwtCOWjZa8AIneL+xdUl9SRuigCjRHNHsX30dupE=
|
||||||
github.com/goreleaser/chglog v0.5.0/go.mod h1:Ri46M3lrMuv76FHszs3vtABR8J8k1w9JHYAzxeeOl28=
|
|
||||||
github.com/goreleaser/fileglob v1.3.0 h1:/X6J7U8lbDpQtBvGcwwPS6OpzkNVlVEsFUVRx9+k+7I=
|
github.com/goreleaser/fileglob v1.3.0 h1:/X6J7U8lbDpQtBvGcwwPS6OpzkNVlVEsFUVRx9+k+7I=
|
||||||
github.com/goreleaser/fileglob v1.3.0/go.mod h1:Jx6BoXv3mbYkEzwm9THo7xbr5egkAraxkGorbJb4RxU=
|
github.com/goreleaser/fileglob v1.3.0/go.mod h1:Jx6BoXv3mbYkEzwm9THo7xbr5egkAraxkGorbJb4RxU=
|
||||||
github.com/goreleaser/nfpm/v2 v2.33.0 h1:yBv6jgkPwih4va/S42rceSjJ2Znt3Og/Ntc76oP0tfI=
|
github.com/goreleaser/nfpm/v2 v2.20.0 h1:Q/CrX54KUMluz6+M/pjTbknFd5Dao8qXi0C6ZuFCtfY=
|
||||||
github.com/goreleaser/nfpm/v2 v2.33.0/go.mod h1:8wwWWvJWmn84xo/Sqiv0aMvEGTHlHZTXTEuVSgQpkIM=
|
github.com/goreleaser/nfpm/v2 v2.20.0/go.mod h1:/Fh6XfwT/T+D4qtNC2iXmHSD/1UT20JkvBXyJ6nFmOY=
|
||||||
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
|
|
||||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
|
||||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
|
||||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
|
||||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
|
||||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
|
||||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
|
||||||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
|
||||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
|
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
|
||||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
|
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
|
||||||
github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4=
|
github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw=
|
||||||
github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||||
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
|
||||||
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
|
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
|
||||||
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
|
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||||
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
|
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
|
||||||
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
|
|
||||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
|
||||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
|
||||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
|
||||||
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
|
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||||
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
|
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||||
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
github.com/kevinburke/ssh_config v1.1.0 h1:pH/t1WS9NzT8go394IqZeJTMHVm6Cr6ZJ6AQ+mdNo/o=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kevinburke/ssh_config v1.1.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||||
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||||
github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM=
|
github.com/klauspost/compress v1.15.11 h1:Lcadnb3RKGin4FYM/orgq0qde+nc15E5Cbqg4B9Sx9c=
|
||||||
github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
|
||||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||||
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
|
github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE=
|
||||||
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||||
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||||
|
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
|
|
||||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
|
||||||
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
|
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
|
||||||
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
|
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
|
||||||
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
||||||
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
|
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
|
||||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
|
||||||
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
|
||||||
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
|
||||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
|
||||||
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
|
||||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
|
||||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
|
||||||
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
|
||||||
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
|
||||||
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
|
||||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
|
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
|
||||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||||
github.com/mholt/archiver/v4 v4.0.0-alpha.8 h1:tRGQuDVPh66WCOelqe6LIGh0gwmfwxUrSSDunscGsRM=
|
github.com/mholt/archiver/v4 v4.0.0-alpha.7 h1:xzByj8G8tj0Oq7ZYYU4+ixL/CVb5ruWCm0EZQ1PjOkE=
|
||||||
github.com/mholt/archiver/v4 v4.0.0-alpha.8/go.mod h1:5f7FUYGXdJWUjESffJaYR4R60VhnHxb2X3T1teMyv5A=
|
github.com/mholt/archiver/v4 v4.0.0-alpha.7/go.mod h1:Fs8qUkO74HHaidabihzYephJH8qmGD/nCP6tE5xC9BM=
|
||||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
|
|
||||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
|
|
||||||
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
|
||||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||||
|
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||||
|
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
|
||||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||||
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34=
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
|
|
||||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
|
||||||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
|
||||||
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
|
||||||
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
|
||||||
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
|
|
||||||
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
|
|
||||||
github.com/nwaples/rardecode/v2 v2.0.0-beta.2 h1:e3mzJFJs4k83GXBEiTaQ5HgSc/kOK8q0rDaRO0MPaOk=
|
github.com/nwaples/rardecode/v2 v2.0.0-beta.2 h1:e3mzJFJs4k83GXBEiTaQ5HgSc/kOK8q0rDaRO0MPaOk=
|
||||||
github.com/nwaples/rardecode/v2 v2.0.0-beta.2/go.mod h1:yntwv/HfMc/Hbvtq9I19D1n58te3h6KsqCf3GxyfBGY=
|
github.com/nwaples/rardecode/v2 v2.0.0-beta.2/go.mod h1:yntwv/HfMc/Hbvtq9I19D1n58te3h6KsqCf3GxyfBGY=
|
||||||
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
|
github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg=
|
||||||
github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
|
github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas=
|
||||||
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
|
github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE=
|
||||||
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||||
github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
|
||||||
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
|
|
||||||
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
|
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
|
||||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
|
||||||
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
|
|
||||||
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
|
||||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
|
||||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
|
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||||
github.com/schollz/progressbar/v3 v3.13.1 h1:o8rySDYiQ59Mwzy2FELeHY5ZARXZTVJC7iHD6PEFUiE=
|
|
||||||
github.com/schollz/progressbar/v3 v3.13.1/go.mod h1:xvrbki8kfT1fzWzBT/UZd9L6GA+jdL7HAgq2RFnO6fQ=
|
|
||||||
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
|
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
|
||||||
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||||
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
|
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||||
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
|
||||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||||
github.com/skeema/knownhosts v1.2.0 h1:h9r9cf0+u7wSE+M183ZtMGgOJKiL96brpaz5ekfJCpM=
|
github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs=
|
||||||
github.com/skeema/knownhosts v1.2.0/go.mod h1:g4fPeYpque7P0xefxtGzV81ihjC8sX2IqpAoNkjxbMo=
|
github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg=
|
||||||
github.com/smartystreets/assertions v1.13.1 h1:Ef7KhSmjZcK6AVf9YbJdvPYG9avaF0ZxudX+ThRdWfU=
|
|
||||||
github.com/smartystreets/assertions v1.13.1/go.mod h1:cXr/IwVfSo/RbCSPhoAPv73p3hlSdrBH/b3SdnW/LMY=
|
|
||||||
github.com/smartystreets/goconvey v1.8.0 h1:Oi49ha/2MURE0WexF052Z0m+BNSGirfjg5RL+JXWq3w=
|
|
||||||
github.com/smartystreets/goconvey v1.8.0/go.mod h1:EdX8jtrTIj26jmjCOVNMVSIYAtgexqXKHOXW2Dx9JLg=
|
|
||||||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
|
||||||
github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
|
|
||||||
github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
||||||
|
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
|
||||||
github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw=
|
github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw=
|
||||||
github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY=
|
github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY=
|
||||||
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
|
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
|
||||||
github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8=
|
github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8=
|
||||||
github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||||
github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs=
|
github.com/urfave/cli/v2 v2.16.3 h1:gHoFIwpPjoyIMbJp/VFd+/vuD0dAgFK4B6DpEMFJfQk=
|
||||||
github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
|
github.com/urfave/cli/v2 v2.16.3/go.mod h1:1CNUng3PtjQMtRzJO4FMXBQvkGtuYRxxiR9xMa7jMwI=
|
||||||
github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU=
|
github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
|
||||||
github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
|
github.com/xanzy/ssh-agent v0.3.1 h1:AmzO1SSWxw73zxFZPRwaMN1MohDw8UyHnmuxyceTEGo=
|
||||||
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
|
github.com/xanzy/ssh-agent v0.3.1/go.mod h1:QIE4lCeL7nkC25x+yA3LBIYfwCc1TFziCtG7cBAac6w=
|
||||||
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
|
|
||||||
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
|
||||||
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
|
||||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
|
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
|
||||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
|
|
||||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8=
|
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8=
|
||||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
|
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
|
||||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
||||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
|
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
|
||||||
gitlab.com/digitalxero/go-conventional-commit v1.0.7 h1:8/dO6WWG+98PMhlZowt/YjuiKhqhGlOCwlIV8SqqGh8=
|
gitlab.com/digitalxero/go-conventional-commit v1.0.7 h1:8/dO6WWG+98PMhlZowt/YjuiKhqhGlOCwlIV8SqqGh8=
|
||||||
gitlab.com/digitalxero/go-conventional-commit v1.0.7/go.mod h1:05Xc2BFsSyC5tKhK0y+P3bs0AwUtNuTp+mTpbCU/DZ0=
|
gitlab.com/digitalxero/go-conventional-commit v1.0.7/go.mod h1:05Xc2BFsSyC5tKhK0y+P3bs0AwUtNuTp+mTpbCU/DZ0=
|
||||||
go.elara.ws/logger v0.0.0-20230421022458-e80700db2090 h1:RVC8XvWo6Yw4HUshqx4TSzuBDScDghafU6QFRJ4xPZg=
|
go.arsenm.dev/logger v0.0.0-20220630204155-5ba23e583f0a h1:9SF7Af1OnN9BlWk9yNYCZuv/uAhDMhzfZxtWJEqy6EE=
|
||||||
go.elara.ws/logger v0.0.0-20230421022458-e80700db2090/go.mod h1:qng49owViqsW5Aey93lwBXONw20oGbJIoLVscB16mPM=
|
go.arsenm.dev/logger v0.0.0-20220630204155-5ba23e583f0a/go.mod h1:RV2qydKDdoyaRkhAq8JEGvojR8eJ6bjq5WnSIlH7gYw=
|
||||||
go.elara.ws/translate v0.0.0-20230421025926-32ccfcd110e6 h1:4xCBxLPBn3Y2DuIcj8zQ1tQOFLrpu6tEIGUWn/Q6zPM=
|
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
go.elara.ws/translate v0.0.0-20230421025926-32ccfcd110e6/go.mod h1:NmfCFqwq7X/aqa/ZVkIysj17JyMEY4Bb5E921kMswNo=
|
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||||
go.elara.ws/vercmp v0.0.0-20230622214216-0b2b067575c4 h1:Ep54XceQlKhcCHl9awG+wWP4kz4kIP3c3Lzw/Gc/zwY=
|
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||||
go.elara.ws/vercmp v0.0.0-20230622214216-0b2b067575c4/go.mod h1:/7PNW7nFnDR5W7UXZVc04gdVLR/wBNgkm33KgIz0OBk=
|
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA=
|
||||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
golang.org/x/exp v0.0.0-20220916125017-b168a2c6b86b h1:SCE/18RnFsLrjydh/R/s5EVvHoZprqEQUuoxK8q2Pc4=
|
||||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
golang.org/x/exp v0.0.0-20220916125017-b168a2c6b86b/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
|
||||||
go4.org v0.0.0-20200411211856-f5505b9728dd h1:BNJlw5kRTzdmyfh5U8F93HA2OwkP7ZGwA51eJ/0wKOU=
|
|
||||||
go4.org v0.0.0-20200411211856-f5505b9728dd/go.mod h1:CIiUVy99QCPfoE13bO4EZaz5GZMZXMSBGhxRdsvzbkg=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
|
||||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
|
||||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
|
||||||
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
|
||||||
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
|
||||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
|
||||||
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
|
|
||||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
|
||||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
|
||||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
|
||||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
|
||||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
|
||||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
|
||||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
|
||||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
|
||||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
|
||||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
|
||||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
|
||||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
|
||||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
|
||||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
|
||||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
|
||||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
|
||||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
|
||||||
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
|
|
||||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
|
||||||
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
|
||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
|
||||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
|
||||||
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
|
|
||||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
|
||||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
|
||||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
|
||||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
|
||||||
golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU=
|
|
||||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
|
||||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
|
||||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
|
||||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
|
||||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
|
||||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
|
||||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
|
||||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
|
||||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
|
||||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
|
||||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
|
||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
|
||||||
golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
|
|
||||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
|
||||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
|
||||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
|
||||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
|
||||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
|
||||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
|
||||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
|
||||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
|
||||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
|
||||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
|
||||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
|
||||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
|
||||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
|
||||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
|
||||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
|
||||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
|
||||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
|
||||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
|
||||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
|
||||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
|
||||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
|
||||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
|
||||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
|
||||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
|
||||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
|
||||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
|
||||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
|
||||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
|
||||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
@ -569,45 +241,8 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
mvdan.cc/sh/v3 v3.5.1 h1:hmP3UOw4f+EYexsJjFxvU38+kn+V/s2CclXHanIBkmQ=
|
||||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
mvdan.cc/sh/v3 v3.5.1/go.mod h1:1JcoyAKm1lZw/2bZje/iYKWicU/KMd0rsyJeKHnsK4E=
|
||||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
|
||||||
lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
|
|
||||||
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
|
||||||
lure.sh/fakeroot v0.0.0-20231024000108-b130d64a68ee h1:kSXIuMid56Q29WEl7EQb5QUtmGqQqAu66EZ2G0OSUfU=
|
|
||||||
lure.sh/fakeroot v0.0.0-20231024000108-b130d64a68ee/go.mod h1:/v0u0AZ+wbzUWhV02KzciOf1KFNh7/7rbkz5Z0b5gDA=
|
|
||||||
modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw=
|
|
||||||
modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0=
|
|
||||||
modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw=
|
|
||||||
modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY=
|
|
||||||
modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
|
|
||||||
modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
|
|
||||||
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
|
|
||||||
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
|
|
||||||
modernc.org/libc v1.24.1 h1:uvJSeCKL/AgzBo2yYIPPTy82v21KgGnizcGYfBHaNuM=
|
|
||||||
modernc.org/libc v1.24.1/go.mod h1:FmfO1RLrU3MHJfyi9eYYmZBfi/R+tqZ6+hQ3yQQUkak=
|
|
||||||
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
|
|
||||||
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
|
||||||
modernc.org/memory v1.6.0 h1:i6mzavxrE9a30whzMfwf7XWVODx2r5OYXvU46cirX7o=
|
|
||||||
modernc.org/memory v1.6.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
|
|
||||||
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
|
||||||
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
|
||||||
modernc.org/sqlite v1.25.0 h1:AFweiwPNd/b3BoKnBOfFm+Y260guGMF+0UFk0savqeA=
|
|
||||||
modernc.org/sqlite v1.25.0/go.mod h1:FL3pVXie73rg3Rii6V/u5BoHlSoyeZeIgKZEgHARyCU=
|
|
||||||
modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
|
|
||||||
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
|
|
||||||
modernc.org/tcl v1.15.2 h1:C4ybAYCGJw968e+Me18oW55kD/FexcHbqH2xak1ROSY=
|
|
||||||
modernc.org/tcl v1.15.2/go.mod h1:3+k/ZaEbKrC8ePv8zJWPtBSW0V7Gg9g8rkmhI1Kfs3c=
|
|
||||||
modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg=
|
|
||||||
modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
|
||||||
modernc.org/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY=
|
|
||||||
modernc.org/z v1.7.3/go.mod h1:Ipv4tsdxZRbQyLq9Q1M6gdbkxYzdlrciF2Hi/lS7nWE=
|
|
||||||
mvdan.cc/sh/v3 v3.7.0 h1:lSTjdP/1xsddtaKfGg7Myu7DnlHItd3/M2tomOcNNBg=
|
|
||||||
mvdan.cc/sh/v3 v3.7.0/go.mod h1:K2gwkaesF/D7av7Kxl0HbF5kGOd2ArupNTX3X44+8l8=
|
|
||||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
|
||||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
|
||||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
|
||||||
|
86
helper.go
86
helper.go
@ -1,86 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
"lure.sh/lure/internal/cpu"
|
|
||||||
"lure.sh/lure/internal/shutils/helpers"
|
|
||||||
"lure.sh/lure/pkg/distro"
|
|
||||||
"lure.sh/lure/pkg/loggerctx"
|
|
||||||
"mvdan.cc/sh/v3/expand"
|
|
||||||
"mvdan.cc/sh/v3/interp"
|
|
||||||
)
|
|
||||||
|
|
||||||
var helperCmd = &cli.Command{
|
|
||||||
Name: "helper",
|
|
||||||
Usage: "Run a LURE helper command",
|
|
||||||
ArgsUsage: `<helper_name|"list">`,
|
|
||||||
Subcommands: []*cli.Command{helperListCmd},
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "dest-dir",
|
|
||||||
Aliases: []string{"d"},
|
|
||||||
Usage: "The directory that the install commands will install to",
|
|
||||||
Value: "dest",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Action: func(c *cli.Context) error {
|
|
||||||
ctx := c.Context
|
|
||||||
log := loggerctx.From(ctx)
|
|
||||||
|
|
||||||
if c.Args().Len() < 1 {
|
|
||||||
cli.ShowSubcommandHelpAndExit(c, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
helper, ok := helpers.Helpers[c.Args().First()]
|
|
||||||
if !ok {
|
|
||||||
log.Fatal("No such helper command").Str("name", c.Args().First()).Send()
|
|
||||||
}
|
|
||||||
|
|
||||||
wd, err := os.Getwd()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Error getting working directory").Err(err).Send()
|
|
||||||
}
|
|
||||||
|
|
||||||
info, err := distro.ParseOSRelease(ctx)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Error getting working directory").Err(err).Send()
|
|
||||||
}
|
|
||||||
|
|
||||||
hc := interp.HandlerContext{
|
|
||||||
Env: expand.ListEnviron(
|
|
||||||
"pkgdir="+c.String("dest-dir"),
|
|
||||||
"DISTRO_ID="+info.ID,
|
|
||||||
"DISTRO_ID_LIKE="+strings.Join(info.Like, " "),
|
|
||||||
"ARCH="+cpu.Arch(),
|
|
||||||
),
|
|
||||||
Dir: wd,
|
|
||||||
Stdin: os.Stdin,
|
|
||||||
Stdout: os.Stdout,
|
|
||||||
Stderr: os.Stderr,
|
|
||||||
}
|
|
||||||
|
|
||||||
return helper(hc, c.Args().First(), c.Args().Slice()[1:])
|
|
||||||
},
|
|
||||||
CustomHelpTemplate: cli.CommandHelpTemplate,
|
|
||||||
BashComplete: func(ctx *cli.Context) {
|
|
||||||
for name := range helpers.Helpers {
|
|
||||||
fmt.Println(name)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var helperListCmd = &cli.Command{
|
|
||||||
Name: "list",
|
|
||||||
Usage: "List all the available helper commands",
|
|
||||||
Aliases: []string{"ls"},
|
|
||||||
Action: func(ctx *cli.Context) error {
|
|
||||||
for name := range helpers.Helpers {
|
|
||||||
fmt.Println(name)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
@ -1,22 +1,4 @@
|
|||||||
/*
|
package main
|
||||||
* LURE - Linux User REpository
|
|
||||||
* Copyright (C) 2023 Elara 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 helpers
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
@ -28,10 +10,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/go-git/go-git/v5"
|
"go.arsenm.dev/lure/internal/shutils"
|
||||||
"github.com/go-git/go-git/v5/plumbing/object"
|
|
||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
"lure.sh/lure/internal/shutils/handlers"
|
|
||||||
"mvdan.cc/sh/v3/interp"
|
"mvdan.cc/sh/v3/interp"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -40,34 +20,25 @@ var (
|
|||||||
ErrNoDetectManNum = errors.New("manual number cannot be detected from the filename")
|
ErrNoDetectManNum = errors.New("manual number cannot be detected from the filename")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Helpers contains all the helper commands
|
var helpers = shutils.ExecFuncs{
|
||||||
var Helpers = handlers.ExecFuncs{
|
|
||||||
"install-binary": installHelperCmd("/usr/bin", 0o755),
|
"install-binary": installHelperCmd("/usr/bin", 0o755),
|
||||||
"install-systemd-user": installHelperCmd("/usr/lib/systemd/user", 0o644),
|
"install-systemd-user": installHelperCmd("/usr/lib/systemd/user", 0o644),
|
||||||
"install-systemd": installHelperCmd("/usr/lib/systemd/system", 0o644),
|
"install-systemd": installHelperCmd("/usr/lib/systemd/system", 0o644),
|
||||||
"install-config": installHelperCmd("/etc", 0o644),
|
"install-config": installHelperCmd("/etc", 0o644),
|
||||||
"install-license": installHelperCmd("/usr/share/licenses", 0o644),
|
"install-license": installHelperCmd("/usr/share/licenses", 0o644),
|
||||||
"install-desktop": installHelperCmd("/usr/share/applications", 0o644),
|
"install-desktop": installHelperCmd("/usr/share/applications", 0o644),
|
||||||
"install-icon": installHelperCmd("/usr/share/pixmaps", 0o644),
|
|
||||||
"install-manual": installManualCmd,
|
"install-manual": installManualCmd,
|
||||||
"install-completion": installCompletionCmd,
|
"install-completion": installCompletionCmd,
|
||||||
"install-library": installLibraryCmd,
|
"install-library": installLibraryCmd,
|
||||||
"git-version": gitVersionCmd,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restricted contains restricted read-only helper commands
|
func installHelperCmd(prefix string, perms os.FileMode) shutils.ExecFunc {
|
||||||
// that don't modify any state
|
|
||||||
var Restricted = handlers.ExecFuncs{
|
|
||||||
"git-version": gitVersionCmd,
|
|
||||||
}
|
|
||||||
|
|
||||||
func installHelperCmd(prefix string, perms os.FileMode) handlers.ExecFunc {
|
|
||||||
return func(hc interp.HandlerContext, cmd string, args []string) error {
|
return func(hc interp.HandlerContext, cmd string, args []string) error {
|
||||||
if len(args) < 1 {
|
if len(args) < 1 {
|
||||||
return handlers.InsufficientArgsError(cmd, 1, len(args))
|
return shutils.InsufficientArgsError(cmd, 1, len(args))
|
||||||
}
|
}
|
||||||
|
|
||||||
from := resolvePath(hc, args[0])
|
from := args[0]
|
||||||
to := ""
|
to := ""
|
||||||
if len(args) > 1 {
|
if len(args) > 1 {
|
||||||
to = filepath.Join(hc.Env.Get("pkgdir").Str, prefix, args[1])
|
to = filepath.Join(hc.Env.Get("pkgdir").Str, prefix, args[1])
|
||||||
@ -85,10 +56,10 @@ func installHelperCmd(prefix string, perms os.FileMode) handlers.ExecFunc {
|
|||||||
|
|
||||||
func installManualCmd(hc interp.HandlerContext, cmd string, args []string) error {
|
func installManualCmd(hc interp.HandlerContext, cmd string, args []string) error {
|
||||||
if len(args) < 1 {
|
if len(args) < 1 {
|
||||||
return handlers.InsufficientArgsError(cmd, 1, len(args))
|
return shutils.InsufficientArgsError(cmd, 1, len(args))
|
||||||
}
|
}
|
||||||
|
|
||||||
from := resolvePath(hc, args[0])
|
from := args[0]
|
||||||
number := filepath.Base(from)
|
number := filepath.Base(from)
|
||||||
// The man page may be compressed with gzip.
|
// The man page may be compressed with gzip.
|
||||||
// If it is, the .gz extension must be removed to properly
|
// If it is, the .gz extension must be removed to properly
|
||||||
@ -115,7 +86,7 @@ func installCompletionCmd(hc interp.HandlerContext, cmd string, args []string) e
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(args) < 2 {
|
if len(args) < 2 {
|
||||||
return handlers.InsufficientArgsError(cmd, 2, len(args))
|
return shutils.InsufficientArgsError(cmd, 2, len(args))
|
||||||
}
|
}
|
||||||
|
|
||||||
shell := args[0]
|
shell := args[0]
|
||||||
@ -124,7 +95,7 @@ func installCompletionCmd(hc interp.HandlerContext, cmd string, args []string) e
|
|||||||
var prefix string
|
var prefix string
|
||||||
switch shell {
|
switch shell {
|
||||||
case "bash":
|
case "bash":
|
||||||
prefix = "/usr/share/bash-completion/completions"
|
prefix = "/usr/share/bash-completion/completion"
|
||||||
case "zsh":
|
case "zsh":
|
||||||
prefix = "/usr/share/zsh/site-functions"
|
prefix = "/usr/share/zsh/site-functions"
|
||||||
name = "_" + name
|
name = "_" + name
|
||||||
@ -208,52 +179,16 @@ func getLibPrefix(hc interp.HandlerContext) string {
|
|||||||
|
|
||||||
architecture := hc.Env.Get("ARCH").Str
|
architecture := hc.Env.Get("ARCH").Str
|
||||||
|
|
||||||
if distroID == "debian" || slices.Contains(distroLike, "debian") ||
|
if distroID == "debian" || slices.Contains(distroLike, "debian") {
|
||||||
distroID == "ubuntu" || slices.Contains(distroLike, "ubuntu") {
|
triple, ok := multiarchTupleMap[architecture]
|
||||||
|
|
||||||
tuple, ok := multiarchTupleMap[architecture]
|
|
||||||
if ok {
|
if ok {
|
||||||
out = filepath.Join("/usr/lib", tuple)
|
out = filepath.Join("/usr/lib", triple)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
func gitVersionCmd(hc interp.HandlerContext, cmd string, args []string) error {
|
|
||||||
path := hc.Dir
|
|
||||||
if len(args) > 0 {
|
|
||||||
path = resolvePath(hc, args[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
r, err := git.PlainOpen(path)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("git-version: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
revNum := 0
|
|
||||||
commits, err := r.Log(&git.LogOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("git-version: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
commits.ForEach(func(*object.Commit) error {
|
|
||||||
revNum++
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
HEAD, err := r.Head()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("git-version: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
hash := HEAD.Hash().String()
|
|
||||||
|
|
||||||
fmt.Fprintf(hc.Stdout, "%d.%s\n", revNum, hash[:7])
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func helperInstall(from, to string, perms os.FileMode) error {
|
func helperInstall(from, to string, perms os.FileMode) error {
|
||||||
err := os.MkdirAll(filepath.Dir(to), 0o755)
|
err := os.MkdirAll(filepath.Dir(to), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -275,10 +210,3 @@ func helperInstall(from, to string, perms os.FileMode) error {
|
|||||||
_, err = io.Copy(dst, src)
|
_, err = io.Copy(dst, src)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func resolvePath(hc interp.HandlerContext, path string) string {
|
|
||||||
if !filepath.IsAbs(path) {
|
|
||||||
return filepath.Join(hc.Dir, path)
|
|
||||||
}
|
|
||||||
return path
|
|
||||||
}
|
|
99
info.go
99
info.go
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* LURE - Linux User REpository
|
* LURE - Linux User REpository
|
||||||
* Copyright (C) 2023 Elara Musayelyan
|
* Copyright (C) 2022 Arsen Musayelyan
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -19,88 +19,41 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"lure.sh/lure/internal/cliutils"
|
"go.arsenm.dev/lure/distro"
|
||||||
"lure.sh/lure/internal/config"
|
|
||||||
"lure.sh/lure/internal/overrides"
|
|
||||||
"lure.sh/lure/pkg/distro"
|
|
||||||
"lure.sh/lure/pkg/loggerctx"
|
|
||||||
"lure.sh/lure/pkg/repos"
|
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var infoCmd = &cli.Command{
|
func infoCmd(c *cli.Context) error {
|
||||||
Name: "info",
|
args := c.Args()
|
||||||
Usage: "Print information about a package",
|
if args.Len() < 1 {
|
||||||
Flags: []cli.Flag{
|
log.Fatalf("Command info expected at least 1 argument, got %d", args.Len()).Send()
|
||||||
&cli.BoolFlag{
|
}
|
||||||
Name: "all",
|
|
||||||
Aliases: []string{"a"},
|
|
||||||
Usage: "Show all information, not just for the current distro",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Action: func(c *cli.Context) error {
|
|
||||||
ctx := c.Context
|
|
||||||
log := loggerctx.From(ctx)
|
|
||||||
|
|
||||||
args := c.Args()
|
info, err := distro.ParseOSRelease(c.Context)
|
||||||
if args.Len() < 1 {
|
if err != nil {
|
||||||
log.Fatalf("Command info expected at least 1 argument, got %d", args.Len()).Send()
|
log.Fatal("Error parsing os-release").Err(err).Send()
|
||||||
}
|
}
|
||||||
|
|
||||||
err := repos.Pull(ctx, config.Config(ctx).Repos)
|
found, err := findPkg(args.First())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Error pulling repositories").Err(err).Send()
|
log.Fatal("Error finding package").Err(err).Send()
|
||||||
}
|
}
|
||||||
|
|
||||||
found, _, err := repos.FindPkgs(ctx, args.Slice())
|
// if multiple are matched, only use the first one
|
||||||
if err != nil {
|
script := found[0]
|
||||||
log.Fatal("Error finding packages").Err(err).Send()
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(found) == 0 {
|
vars, err := getBuildVars(c.Context, script, info)
|
||||||
os.Exit(1)
|
if err != nil {
|
||||||
}
|
log.Fatal("Error getting build variables").Err(err).Send()
|
||||||
|
}
|
||||||
|
|
||||||
pkgs := cliutils.FlattenPkgs(ctx, found, "show", c.Bool("interactive"))
|
err = yaml.NewEncoder(os.Stdout).Encode(vars)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error encoding script variables").Err(err).Send()
|
||||||
|
}
|
||||||
|
|
||||||
var names []string
|
return nil
|
||||||
all := c.Bool("all")
|
|
||||||
|
|
||||||
if !all {
|
|
||||||
info, err := distro.ParseOSRelease(ctx)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Error parsing os-release file").Err(err).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 {
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("---")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
137
install.go
137
install.go
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* LURE - Linux User REpository
|
* LURE - Linux User REpository
|
||||||
* Copyright (C) 2023 Elara Musayelyan
|
* Copyright (C) 2022 Arsen Musayelyan
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -19,104 +19,77 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"context"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"lure.sh/lure/internal/cliutils"
|
"go.arsenm.dev/lure/manager"
|
||||||
"lure.sh/lure/internal/config"
|
|
||||||
"lure.sh/lure/internal/db"
|
|
||||||
"lure.sh/lure/internal/types"
|
|
||||||
"lure.sh/lure/pkg/build"
|
|
||||||
"lure.sh/lure/pkg/loggerctx"
|
|
||||||
"lure.sh/lure/pkg/manager"
|
|
||||||
"lure.sh/lure/pkg/repos"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var installCmd = &cli.Command{
|
func installCmd(c *cli.Context) error {
|
||||||
Name: "install",
|
args := c.Args()
|
||||||
Usage: "Install a new package",
|
if args.Len() < 1 {
|
||||||
Aliases: []string{"in"},
|
log.Fatalf("Command install expected at least 1 argument, got %d", args.Len()).Send()
|
||||||
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 {
|
|
||||||
ctx := c.Context
|
|
||||||
log := loggerctx.From(ctx)
|
|
||||||
|
|
||||||
args := c.Args()
|
mgr := manager.Detect()
|
||||||
if args.Len() < 1 {
|
if mgr == nil {
|
||||||
log.Fatalf("Command install expected at least 1 argument, got %d", args.Len()).Send()
|
log.Fatal("Unable to detect supported package manager on system").Send()
|
||||||
}
|
}
|
||||||
|
|
||||||
mgr := manager.Detect()
|
installPkgs(c.Context, args.Slice(), mgr, true)
|
||||||
if mgr == nil {
|
|
||||||
log.Fatal("Unable to detect a supported package manager on the system").Send()
|
|
||||||
}
|
|
||||||
|
|
||||||
err := repos.Pull(ctx, config.Config(ctx).Repos)
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func installPkgs(ctx context.Context, pkgs []string, mgr manager.Manager, pull bool) {
|
||||||
|
if pull {
|
||||||
|
err := pullRepos(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Error pulling repositories").Err(err).Send()
|
log.Fatal("Error pulling repositories").Err(err).Send()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
found, notFound, err := repos.FindPkgs(ctx, args.Slice())
|
scripts, notFound := findPkgs(pkgs)
|
||||||
|
|
||||||
|
if len(notFound) > 0 {
|
||||||
|
err := mgr.Install(nil, notFound...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Error finding packages").Err(err).Send()
|
log.Fatal("Error installing native packages").Err(err).Send()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pkgs := cliutils.FlattenPkgs(ctx, found, "install", c.Bool("interactive"))
|
installScripts(ctx, mgr, scripts)
|
||||||
build.InstallPkgs(ctx, pkgs, notFound, types.BuildOpts{
|
|
||||||
Manager: mgr,
|
|
||||||
Clean: c.Bool("clean"),
|
|
||||||
Interactive: c.Bool("interactive"),
|
|
||||||
})
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
BashComplete: func(c *cli.Context) {
|
|
||||||
log := loggerctx.From(c.Context)
|
|
||||||
result, err := db.GetPkgs(c.Context, "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)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var removeCmd = &cli.Command{
|
func installScripts(ctx context.Context, mgr manager.Manager, scripts []string) {
|
||||||
Name: "remove",
|
for _, script := range scripts {
|
||||||
Usage: "Remove an installed package",
|
builtPkgs, _, err := buildPackage(ctx, script, mgr)
|
||||||
Aliases: []string{"rm"},
|
|
||||||
Action: func(c *cli.Context) error {
|
|
||||||
log := loggerctx.From(c.Context)
|
|
||||||
|
|
||||||
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 a supported package manager on the system").Send()
|
|
||||||
}
|
|
||||||
|
|
||||||
err := mgr.Remove(nil, c.Args().Slice()...)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Error removing packages").Err(err).Send()
|
log.Fatal("Error building package").Err(err).Send()
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
err = mgr.InstallLocal(nil, builtPkgs...)
|
||||||
},
|
if err != nil {
|
||||||
|
log.Fatal("Error installing package").Err(err).Send()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
mgr := manager.Detect()
|
||||||
|
if mgr == nil {
|
||||||
|
log.Fatal("Unable to detect supported package manager on system").Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
err := mgr.Remove(nil, c.Args().Slice()...)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error removing packages").Err(err).Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1,172 +0,0 @@
|
|||||||
/*
|
|
||||||
* LURE - Linux User REpository
|
|
||||||
* Copyright (C) 2023 Elara 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 cliutils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/AlecAivazis/survey/v2"
|
|
||||||
"lure.sh/lure/internal/config"
|
|
||||||
"lure.sh/lure/internal/db"
|
|
||||||
"lure.sh/lure/internal/pager"
|
|
||||||
"lure.sh/lure/internal/translations"
|
|
||||||
"lure.sh/lure/pkg/loggerctx"
|
|
||||||
)
|
|
||||||
|
|
||||||
// YesNoPrompt asks the user a yes or no question, using def as the default answer
|
|
||||||
func YesNoPrompt(ctx context.Context, msg string, interactive, def bool) (bool, error) {
|
|
||||||
if interactive {
|
|
||||||
var answer bool
|
|
||||||
err := survey.AskOne(
|
|
||||||
&survey.Confirm{
|
|
||||||
Message: translations.Translator(ctx).TranslateTo(msg, config.Language(ctx)),
|
|
||||||
Default: def,
|
|
||||||
},
|
|
||||||
&answer,
|
|
||||||
)
|
|
||||||
return answer, err
|
|
||||||
} else {
|
|
||||||
return def, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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(ctx context.Context, script, name, style string, interactive bool) error {
|
|
||||||
log := loggerctx.From(ctx)
|
|
||||||
|
|
||||||
if !interactive {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
scriptPrompt := translations.Translator(ctx).TranslateTo("Would you like to view the build script for", config.Language(ctx)) + " " + name
|
|
||||||
view, err := YesNoPrompt(ctx, scriptPrompt, interactive, false)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if view {
|
|
||||||
err = ShowScript(script, name, style)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cont, err := YesNoPrompt(ctx, "Would you still like to continue?", interactive, false)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !cont {
|
|
||||||
log.Fatal(translations.Translator(ctx).TranslateTo("User chose not to continue after reading script", config.Language(ctx))).Send()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ShowScript uses the built-in pager to display a script at a
|
|
||||||
// given path, in the given syntax highlighting style.
|
|
||||||
func ShowScript(path, name, style string) error {
|
|
||||||
scriptFl, err := os.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer scriptFl.Close()
|
|
||||||
|
|
||||||
str, err := pager.SyntaxHighlightBash(scriptFl, style)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
pgr := pager.New(name, str)
|
|
||||||
return pgr.Run()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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(ctx context.Context, found map[string][]db.Package, verb string, interactive bool) []db.Package {
|
|
||||||
log := loggerctx.From(ctx)
|
|
||||||
var outPkgs []db.Package
|
|
||||||
for _, pkgs := range found {
|
|
||||||
if len(pkgs) > 1 && interactive {
|
|
||||||
choice, err := PkgPrompt(ctx, pkgs, verb, interactive)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Error prompting for choice of package").Send()
|
|
||||||
}
|
|
||||||
outPkgs = append(outPkgs, choice)
|
|
||||||
} else if len(pkgs) == 1 || !interactive {
|
|
||||||
outPkgs = append(outPkgs, pkgs[0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return outPkgs
|
|
||||||
}
|
|
||||||
|
|
||||||
// PkgPrompt asks the user to choose between multiple packages.
|
|
||||||
func PkgPrompt(ctx context.Context, options []db.Package, verb string, interactive bool) (db.Package, error) {
|
|
||||||
if !interactive {
|
|
||||||
return options[0], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
names := make([]string, len(options))
|
|
||||||
for i, option := range options {
|
|
||||||
names[i] = option.Repository + "/" + option.Name + " " + option.Version
|
|
||||||
}
|
|
||||||
|
|
||||||
prompt := &survey.Select{
|
|
||||||
Options: names,
|
|
||||||
Message: translations.Translator(ctx).TranslateTo("Choose which package to "+verb, config.Language(ctx)),
|
|
||||||
}
|
|
||||||
|
|
||||||
var choice int
|
|
||||||
err := survey.AskOne(prompt, &choice)
|
|
||||||
if err != nil {
|
|
||||||
return db.Package{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return options[choice], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ChooseOptDepends asks the user to choose between multiple optional dependencies.
|
|
||||||
// The user may choose multiple items.
|
|
||||||
func ChooseOptDepends(ctx context.Context, options []string, verb string, interactive bool) ([]string, error) {
|
|
||||||
if !interactive {
|
|
||||||
return []string{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
prompt := &survey.MultiSelect{
|
|
||||||
Options: options,
|
|
||||||
Message: translations.Translator(ctx).TranslateTo("Choose which optional package(s) to install", config.Language(ctx)),
|
|
||||||
}
|
|
||||||
|
|
||||||
var choices []int
|
|
||||||
err := survey.AskOne(prompt, &choices)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
out := make([]string, len(choices))
|
|
||||||
for i, choiceIndex := range choices {
|
|
||||||
out[i], _, _ = strings.Cut(options[choiceIndex], ": ")
|
|
||||||
}
|
|
||||||
|
|
||||||
return out, nil
|
|
||||||
}
|
|
@ -1,79 +0,0 @@
|
|||||||
/*
|
|
||||||
* LURE - Linux User REpository
|
|
||||||
* Copyright (C) 2023 Elara 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 config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"os"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/pelletier/go-toml/v2"
|
|
||||||
"lure.sh/lure/internal/types"
|
|
||||||
"lure.sh/lure/pkg/loggerctx"
|
|
||||||
)
|
|
||||||
|
|
||||||
var defaultConfig = &types.Config{
|
|
||||||
RootCmd: "sudo",
|
|
||||||
PagerStyle: "native",
|
|
||||||
IgnorePkgUpdates: []string{},
|
|
||||||
Repos: []types.Repo{
|
|
||||||
{
|
|
||||||
Name: "default",
|
|
||||||
URL: "https://github.com/lure-sh/lure-repo.git",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
configMtx sync.Mutex
|
|
||||||
config *types.Config
|
|
||||||
)
|
|
||||||
|
|
||||||
// Config returns a LURE configuration struct.
|
|
||||||
// The first time it's called, it'll load the config from a file.
|
|
||||||
// Subsequent calls will just return the same value.
|
|
||||||
func Config(ctx context.Context) *types.Config {
|
|
||||||
configMtx.Lock()
|
|
||||||
defer configMtx.Unlock()
|
|
||||||
log := loggerctx.From(ctx)
|
|
||||||
|
|
||||||
if config == nil {
|
|
||||||
cfgFl, err := os.Open(GetPaths(ctx).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
|
|
||||||
}
|
|
@ -1,67 +0,0 @@
|
|||||||
/*
|
|
||||||
* LURE - Linux User REpository
|
|
||||||
* Copyright (C) 2023 Elara 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 config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"lure.sh/lure/pkg/loggerctx"
|
|
||||||
"golang.org/x/text/language"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
langMtx sync.Mutex
|
|
||||||
lang language.Tag
|
|
||||||
langSet bool
|
|
||||||
)
|
|
||||||
|
|
||||||
// Language returns the system language.
|
|
||||||
// The first time it's called, it'll detect the langauge based on
|
|
||||||
// the $LANG environment variable.
|
|
||||||
// Subsequent calls will just return the same value.
|
|
||||||
func Language(ctx context.Context) language.Tag {
|
|
||||||
langMtx.Lock()
|
|
||||||
defer langMtx.Unlock()
|
|
||||||
log := loggerctx.From(ctx)
|
|
||||||
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
|
|
||||||
}
|
|
||||||
return lang
|
|
||||||
}
|
|
||||||
|
|
||||||
// SystemLang returns the system language based on
|
|
||||||
// the $LANG environment variable.
|
|
||||||
func SystemLang() string {
|
|
||||||
lang := os.Getenv("LANG")
|
|
||||||
lang, _, _ = strings.Cut(lang, ".")
|
|
||||||
if lang == "" || lang == "C" {
|
|
||||||
lang = "en"
|
|
||||||
}
|
|
||||||
return lang
|
|
||||||
}
|
|
@ -1,108 +0,0 @@
|
|||||||
/*
|
|
||||||
* LURE - Linux User REpository
|
|
||||||
* Copyright (C) 2023 Elara 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 config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/pelletier/go-toml/v2"
|
|
||||||
"lure.sh/lure/pkg/loggerctx"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Paths contains various paths used by LURE
|
|
||||||
type Paths struct {
|
|
||||||
ConfigDir string
|
|
||||||
ConfigPath string
|
|
||||||
CacheDir string
|
|
||||||
RepoDir string
|
|
||||||
PkgsDir string
|
|
||||||
DBPath string
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
pathsMtx sync.Mutex
|
|
||||||
paths *Paths
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetPaths returns a Paths struct.
|
|
||||||
// The first time it's called, it'll generate the struct
|
|
||||||
// using information from the system.
|
|
||||||
// Subsequent calls will return the same value.
|
|
||||||
func GetPaths(ctx context.Context) *Paths {
|
|
||||||
pathsMtx.Lock()
|
|
||||||
defer pathsMtx.Unlock()
|
|
||||||
|
|
||||||
log := loggerctx.From(ctx)
|
|
||||||
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
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
package config
|
|
||||||
|
|
||||||
// Version contains the version of LURE. If the version
|
|
||||||
// isn't known, it'll be set to "unknown"
|
|
||||||
var Version = "unknown"
|
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* LURE - Linux User REpository
|
* LURE - Linux User REpository
|
||||||
* Copyright (C) 2023 Elara Musayelyan
|
* Copyright (C) 2022 Arsen Musayelyan
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -20,17 +20,14 @@ package cpu
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"golang.org/x/exp/slices"
|
|
||||||
"golang.org/x/sys/cpu"
|
"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
|
// on, by using the same detection method as Go itself
|
||||||
func armVariant() string {
|
func ARMVariant() string {
|
||||||
armEnv := os.Getenv("LURE_ARM_VARIANT")
|
armEnv := os.Getenv("LURE_ARM_VARIANT")
|
||||||
// ensure value has "arm" prefix, such as arm5 or arm6
|
// ensure value has "arm" prefix, such as arm5 or arm6
|
||||||
if strings.HasPrefix(armEnv, "arm") {
|
if strings.HasPrefix(armEnv, "arm") {
|
||||||
@ -45,73 +42,3 @@ func armVariant() string {
|
|||||||
return "arm5"
|
return "arm5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Arch returns the canonical CPU architecture of the system
|
|
||||||
func Arch() string {
|
|
||||||
arch := os.Getenv("LURE_ARCH")
|
|
||||||
if arch == "" {
|
|
||||||
arch = runtime.GOARCH
|
|
||||||
}
|
|
||||||
if arch == "arm" {
|
|
||||||
arch = armVariant()
|
|
||||||
}
|
|
||||||
return arch
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsCompatibleWith(target string, list []string) bool {
|
|
||||||
if target == "all" || slices.Contains(list, "all") {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
@ -1,346 +0,0 @@
|
|||||||
/*
|
|
||||||
* LURE - Linux User REpository
|
|
||||||
* Copyright (C) 2023 Elara 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 db
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
"database/sql/driver"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
|
||||||
"lure.sh/lure/internal/config"
|
|
||||||
"lure.sh/lure/pkg/loggerctx"
|
|
||||||
"golang.org/x/exp/slices"
|
|
||||||
"modernc.org/sqlite"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CurrentVersion is the current version of the database.
|
|
||||||
// The database is reset if its version doesn't match this.
|
|
||||||
const CurrentVersion = 2
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
sqlite.MustRegisterScalarFunction("json_array_contains", 2, jsonArrayContains)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Package is a LURE package's database representation
|
|
||||||
type Package struct {
|
|
||||||
Name string `sh:"name,required" db:"name"`
|
|
||||||
Version string `sh:"version,required" db:"version"`
|
|
||||||
Release int `sh:"release,required" db:"release"`
|
|
||||||
Epoch uint `sh:"epoch" db:"epoch"`
|
|
||||||
Description JSON[map[string]string] `db:"description"`
|
|
||||||
Homepage JSON[map[string]string] `db:"homepage"`
|
|
||||||
Maintainer JSON[map[string]string] `db:"maintainer"`
|
|
||||||
Architectures JSON[[]string] `sh:"architectures" db:"architectures"`
|
|
||||||
Licenses JSON[[]string] `sh:"license" db:"licenses"`
|
|
||||||
Provides JSON[[]string] `sh:"provides" db:"provides"`
|
|
||||||
Conflicts JSON[[]string] `sh:"conflicts" db:"conflicts"`
|
|
||||||
Replaces JSON[[]string] `sh:"replaces" db:"replaces"`
|
|
||||||
Depends JSON[map[string][]string] `db:"depends"`
|
|
||||||
BuildDepends JSON[map[string][]string] `db:"builddepends"`
|
|
||||||
OptDepends JSON[map[string][]string] `db:"optdepends"`
|
|
||||||
Repository string `db:"repository"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type version struct {
|
|
||||||
Version int `db:"version"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
mu sync.Mutex
|
|
||||||
conn *sqlx.DB
|
|
||||||
closed = true
|
|
||||||
)
|
|
||||||
|
|
||||||
// DB returns the LURE database.
|
|
||||||
// The first time it's called, it opens the SQLite database file.
|
|
||||||
// Subsequent calls return the same connection.
|
|
||||||
func DB(ctx context.Context) *sqlx.DB {
|
|
||||||
log := loggerctx.From(ctx)
|
|
||||||
if conn != nil && !closed {
|
|
||||||
return getConn()
|
|
||||||
}
|
|
||||||
_, err := open(ctx, config.GetPaths(ctx).DBPath)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Error opening database").Err(err).Send()
|
|
||||||
}
|
|
||||||
return getConn()
|
|
||||||
}
|
|
||||||
|
|
||||||
func getConn() *sqlx.DB {
|
|
||||||
mu.Lock()
|
|
||||||
defer mu.Unlock()
|
|
||||||
return conn
|
|
||||||
}
|
|
||||||
|
|
||||||
func open(ctx context.Context, dsn string) (*sqlx.DB, error) {
|
|
||||||
db, err := sqlx.Open("sqlite", dsn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
mu.Lock()
|
|
||||||
conn = db
|
|
||||||
closed = false
|
|
||||||
mu.Unlock()
|
|
||||||
|
|
||||||
err = initDB(ctx, dsn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return db, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close closes the database
|
|
||||||
func Close() error {
|
|
||||||
closed = true
|
|
||||||
if conn != nil {
|
|
||||||
return conn.Close()
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// initDB initializes the database
|
|
||||||
func initDB(ctx context.Context, dsn string) error {
|
|
||||||
log := loggerctx.From(ctx)
|
|
||||||
conn = conn.Unsafe()
|
|
||||||
_, err := conn.ExecContext(ctx, `
|
|
||||||
CREATE TABLE IF NOT EXISTS pkgs (
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
repository TEXT NOT NULL,
|
|
||||||
version TEXT NOT NULL,
|
|
||||||
release INT NOT NULL,
|
|
||||||
epoch INT,
|
|
||||||
description TEXT CHECK(description = 'null' OR (JSON_VALID(description) AND JSON_TYPE(description) = 'object')),
|
|
||||||
homepage TEXT CHECK(homepage = 'null' OR (JSON_VALID(homepage) AND JSON_TYPE(homepage) = 'object')),
|
|
||||||
maintainer TEXT CHECK(maintainer = 'null' OR (JSON_VALID(maintainer) AND JSON_TYPE(maintainer) = 'object')),
|
|
||||||
architectures TEXT CHECK(architectures = 'null' OR (JSON_VALID(architectures) AND JSON_TYPE(architectures) = 'array')),
|
|
||||||
licenses TEXT CHECK(licenses = 'null' OR (JSON_VALID(licenses) AND JSON_TYPE(licenses) = 'array')),
|
|
||||||
provides TEXT CHECK(provides = 'null' OR (JSON_VALID(provides) AND JSON_TYPE(provides) = 'array')),
|
|
||||||
conflicts TEXT CHECK(conflicts = 'null' OR (JSON_VALID(conflicts) AND JSON_TYPE(conflicts) = 'array')),
|
|
||||||
replaces TEXT CHECK(replaces = 'null' OR (JSON_VALID(replaces) AND JSON_TYPE(replaces) = 'array')),
|
|
||||||
depends TEXT CHECK(depends = 'null' OR (JSON_VALID(depends) AND JSON_TYPE(depends) = 'object')),
|
|
||||||
builddepends TEXT CHECK(builddepends = 'null' OR (JSON_VALID(builddepends) AND JSON_TYPE(builddepends) = 'object')),
|
|
||||||
optdepends TEXT CHECK(optdepends = 'null' OR (JSON_VALID(optdepends) AND JSON_TYPE(optdepends) = 'object')),
|
|
||||||
UNIQUE(name, repository)
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS lure_db_version (
|
|
||||||
version INT NOT NULL
|
|
||||||
);
|
|
||||||
`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ver, ok := GetVersion(ctx)
|
|
||||||
if ok && ver != CurrentVersion {
|
|
||||||
log.Warn("Database version mismatch; resetting").Int("version", ver).Int("expected", CurrentVersion).Send()
|
|
||||||
reset(ctx)
|
|
||||||
return initDB(ctx, dsn)
|
|
||||||
} else if !ok {
|
|
||||||
log.Warn("Database version does not exist. Run lure fix if something isn't working.").Send()
|
|
||||||
return addVersion(ctx, CurrentVersion)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// reset drops all the database tables
|
|
||||||
func reset(ctx context.Context) error {
|
|
||||||
_, err := DB(ctx).ExecContext(ctx, "DROP TABLE IF EXISTS pkgs;")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = DB(ctx).ExecContext(ctx, "DROP TABLE IF EXISTS lure_db_version;")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsEmpty returns true if the database has no packages in it, otherwise it returns false.
|
|
||||||
func IsEmpty(ctx context.Context) bool {
|
|
||||||
var count int
|
|
||||||
err := DB(ctx).GetContext(ctx, &count, "SELECT count(1) FROM pkgs;")
|
|
||||||
if err != nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return count == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetVersion returns the database version and a boolean indicating
|
|
||||||
// whether the database contained a version number
|
|
||||||
func GetVersion(ctx context.Context) (int, bool) {
|
|
||||||
var ver version
|
|
||||||
err := DB(ctx).GetContext(ctx, &ver, "SELECT * FROM lure_db_version LIMIT 1;")
|
|
||||||
if err != nil {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
return ver.Version, true
|
|
||||||
}
|
|
||||||
|
|
||||||
func addVersion(ctx context.Context, ver int) error {
|
|
||||||
_, err := DB(ctx).ExecContext(ctx, `INSERT INTO lure_db_version(version) VALUES (?);`, ver)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// InsertPackage adds a package to the database
|
|
||||||
func InsertPackage(ctx context.Context, pkg Package) error {
|
|
||||||
_, err := DB(ctx).NamedExecContext(ctx, `
|
|
||||||
INSERT OR REPLACE INTO pkgs (
|
|
||||||
name,
|
|
||||||
repository,
|
|
||||||
version,
|
|
||||||
release,
|
|
||||||
epoch,
|
|
||||||
description,
|
|
||||||
homepage,
|
|
||||||
maintainer,
|
|
||||||
architectures,
|
|
||||||
licenses,
|
|
||||||
provides,
|
|
||||||
conflicts,
|
|
||||||
replaces,
|
|
||||||
depends,
|
|
||||||
builddepends,
|
|
||||||
optdepends
|
|
||||||
) VALUES (
|
|
||||||
:name,
|
|
||||||
:repository,
|
|
||||||
:version,
|
|
||||||
:release,
|
|
||||||
:epoch,
|
|
||||||
:description,
|
|
||||||
:homepage,
|
|
||||||
:maintainer,
|
|
||||||
:architectures,
|
|
||||||
:licenses,
|
|
||||||
:provides,
|
|
||||||
:conflicts,
|
|
||||||
:replaces,
|
|
||||||
:depends,
|
|
||||||
:builddepends,
|
|
||||||
:optdepends
|
|
||||||
);
|
|
||||||
`, pkg)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPkgs returns a result containing packages that match the where conditions
|
|
||||||
func GetPkgs(ctx context.Context, where string, args ...any) (*sqlx.Rows, error) {
|
|
||||||
stream, err := DB(ctx).QueryxContext(ctx, "SELECT * FROM pkgs WHERE "+where, args...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return stream, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPkg returns a single package that matches the where conditions
|
|
||||||
func GetPkg(ctx context.Context, where string, args ...any) (*Package, error) {
|
|
||||||
out := &Package{}
|
|
||||||
err := DB(ctx).GetContext(ctx, out, "SELECT * FROM pkgs WHERE "+where+" LIMIT 1", args...)
|
|
||||||
return out, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeletePkgs deletes all packages matching the where conditions
|
|
||||||
func DeletePkgs(ctx context.Context, where string, args ...any) error {
|
|
||||||
_, err := DB(ctx).ExecContext(ctx, "DELETE FROM pkgs WHERE "+where, args...)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// jsonArrayContains is an SQLite function that checks if a JSON array
|
|
||||||
// in the database contains a given value
|
|
||||||
func jsonArrayContains(ctx *sqlite.FunctionContext, args []driver.Value) (driver.Value, error) {
|
|
||||||
value, ok := args[0].(string)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("both arguments to json_array_contains must be strings")
|
|
||||||
}
|
|
||||||
|
|
||||||
item, ok := args[1].(string)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("both arguments to json_array_contains must be strings")
|
|
||||||
}
|
|
||||||
|
|
||||||
var array []string
|
|
||||||
err := json.Unmarshal([]byte(value), &array)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return slices.Contains(array, item), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// JSON represents a JSON value in the database
|
|
||||||
type JSON[T any] struct {
|
|
||||||
Val T
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewJSON creates a new database JSON value
|
|
||||||
func NewJSON[T any](v T) JSON[T] {
|
|
||||||
return JSON[T]{Val: v}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *JSON[T]) Scan(val any) error {
|
|
||||||
if val == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
switch val := val.(type) {
|
|
||||||
case string:
|
|
||||||
err := json.Unmarshal([]byte(val), &s.Val)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case sql.NullString:
|
|
||||||
if val.Valid {
|
|
||||||
err := json.Unmarshal([]byte(val.String), &s.Val)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return errors.New("sqlite json types must be strings")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s JSON[T]) Value() (driver.Value, error) {
|
|
||||||
data, err := json.Marshal(s.Val)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return string(data), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s JSON[T]) MarshalYAML() (any, error) {
|
|
||||||
return s.Val, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s JSON[T]) String() string {
|
|
||||||
return fmt.Sprint(s.Val)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s JSON[T]) GoString() string {
|
|
||||||
return fmt.Sprintf("%#v", s.Val)
|
|
||||||
}
|
|
@ -1,250 +0,0 @@
|
|||||||
/*
|
|
||||||
* LURE - Linux User REpository
|
|
||||||
* Copyright (C) 2023 Elara 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 db_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
|
||||||
"lure.sh/lure/internal/db"
|
|
||||||
)
|
|
||||||
|
|
||||||
var testPkg = db.Package{
|
|
||||||
Name: "test",
|
|
||||||
Version: "0.0.1",
|
|
||||||
Release: 1,
|
|
||||||
Epoch: 2,
|
|
||||||
Description: db.NewJSON(map[string]string{
|
|
||||||
"en": "Test package",
|
|
||||||
"ru": "Проверочный пакет",
|
|
||||||
}),
|
|
||||||
Homepage: db.NewJSON(map[string]string{
|
|
||||||
"en": "https://lure.sh/",
|
|
||||||
}),
|
|
||||||
Maintainer: db.NewJSON(map[string]string{
|
|
||||||
"en": "Elara Musayelyan <elara@elara.ws>",
|
|
||||||
"ru": "Элара Мусаелян <elara@elara.ws>",
|
|
||||||
}),
|
|
||||||
Architectures: db.NewJSON([]string{"arm64", "amd64"}),
|
|
||||||
Licenses: db.NewJSON([]string{"GPL-3.0-or-later"}),
|
|
||||||
Provides: db.NewJSON([]string{"test"}),
|
|
||||||
Conflicts: db.NewJSON([]string{"test"}),
|
|
||||||
Replaces: db.NewJSON([]string{"test-old"}),
|
|
||||||
Depends: db.NewJSON(map[string][]string{
|
|
||||||
"": {"sudo"},
|
|
||||||
}),
|
|
||||||
BuildDepends: db.NewJSON(map[string][]string{
|
|
||||||
"": {"golang"},
|
|
||||||
"arch": {"go"},
|
|
||||||
}),
|
|
||||||
Repository: "default",
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInit(t *testing.T) {
|
|
||||||
_, err := db.Open(":memory:")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
defer db.Close()
|
|
||||||
|
|
||||||
_, err = db.DB().Exec("SELECT * FROM pkgs")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ver, ok := db.GetVersion()
|
|
||||||
if !ok {
|
|
||||||
t.Errorf("Expected version to be present")
|
|
||||||
} else if ver != db.CurrentVersion {
|
|
||||||
t.Errorf("Expected version %d, got %d", db.CurrentVersion, ver)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInsertPackage(t *testing.T) {
|
|
||||||
_, err := db.Open(":memory:")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
defer db.Close()
|
|
||||||
|
|
||||||
err = db.InsertPackage(testPkg)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
dbPkg := db.Package{}
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(testPkg, dbPkg) {
|
|
||||||
t.Errorf("Expected test package to be the same as database package")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetPkgs(t *testing.T) {
|
|
||||||
_, err := db.Open(":memory:")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
defer db.Close()
|
|
||||||
|
|
||||||
x1 := testPkg
|
|
||||||
x1.Name = "x1"
|
|
||||||
x2 := testPkg
|
|
||||||
x2.Name = "x2"
|
|
||||||
|
|
||||||
err = db.InsertPackage(x1)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = db.InsertPackage(x2)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := db.GetPkgs("name LIKE 'x%'")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for result.Next() {
|
|
||||||
var dbPkg db.Package
|
|
||||||
err = result.StructScan(&dbPkg)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !strings.HasPrefix(dbPkg.Name, "x") {
|
|
||||||
t.Errorf("Expected package name to start with 'x', got %s", dbPkg.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetPkg(t *testing.T) {
|
|
||||||
_, err := db.Open(":memory:")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
defer db.Close()
|
|
||||||
|
|
||||||
x1 := testPkg
|
|
||||||
x1.Name = "x1"
|
|
||||||
x2 := testPkg
|
|
||||||
x2.Name = "x2"
|
|
||||||
|
|
||||||
err = db.InsertPackage(x1)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = db.InsertPackage(x2)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
pkg, err := db.GetPkg("name LIKE 'x%' ORDER BY name")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if pkg.Name != "x1" {
|
|
||||||
t.Errorf("Expected x1 package, got %s", pkg.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(*pkg, x1) {
|
|
||||||
t.Errorf("Expected x1 to be %v, got %v", x1, *pkg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDeletePkgs(t *testing.T) {
|
|
||||||
_, err := db.Open(":memory:")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
defer db.Close()
|
|
||||||
|
|
||||||
x1 := testPkg
|
|
||||||
x1.Name = "x1"
|
|
||||||
x2 := testPkg
|
|
||||||
x2.Name = "x2"
|
|
||||||
|
|
||||||
err = db.InsertPackage(x1)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = db.InsertPackage(x2)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = db.DeletePkgs("name = 'x1'")
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var dbPkg db.Package
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
if dbPkg.Name != "x2" {
|
|
||||||
t.Errorf("Expected x2 package, got %s", dbPkg.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestJsonArrayContains(t *testing.T) {
|
|
||||||
_, err := db.Open(":memory:")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
defer db.Close()
|
|
||||||
|
|
||||||
x1 := testPkg
|
|
||||||
x1.Name = "x1"
|
|
||||||
x2 := testPkg
|
|
||||||
x2.Name = "x2"
|
|
||||||
x2.Provides.Val = append(x2.Provides.Val, "x")
|
|
||||||
|
|
||||||
err = db.InsertPackage(x1)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = db.InsertPackage(x2)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var dbPkg db.Package
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
if dbPkg.Name != "x2" {
|
|
||||||
t.Errorf("Expected x2 package, got %s", dbPkg.Name)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,379 +0,0 @@
|
|||||||
/*
|
|
||||||
* LURE - Linux User REpository
|
|
||||||
* Copyright (C) 2023 Elara 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 dl contains abstractions for downloadingfiles and directories
|
|
||||||
// from various sources.
|
|
||||||
package dl
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/md5"
|
|
||||||
"crypto/sha1"
|
|
||||||
"crypto/sha256"
|
|
||||||
"crypto/sha512"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"hash"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/PuerkitoBio/purell"
|
|
||||||
"github.com/vmihailenco/msgpack/v5"
|
|
||||||
"golang.org/x/crypto/blake2b"
|
|
||||||
"golang.org/x/crypto/blake2s"
|
|
||||||
"golang.org/x/exp/slices"
|
|
||||||
"lure.sh/lure/internal/dlcache"
|
|
||||||
"lure.sh/lure/pkg/loggerctx"
|
|
||||||
)
|
|
||||||
|
|
||||||
const manifestFileName = ".lure_cache_manifest"
|
|
||||||
|
|
||||||
// ErrChecksumMismatch occurs when the checksum of a downloaded file
|
|
||||||
// does not match the expected checksum provided in the Options struct.
|
|
||||||
var (
|
|
||||||
ErrChecksumMismatch = errors.New("dl: checksums did not match")
|
|
||||||
ErrNoSuchHashAlgo = errors.New("dl: invalid hashing algorithm")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Downloaders contains all the downloaders in the order in which
|
|
||||||
// they should be checked
|
|
||||||
var Downloaders = []Downloader{
|
|
||||||
GitDownloader{},
|
|
||||||
TorrentDownloader{},
|
|
||||||
FileDownloader{},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type represents the type of download (file or directory)
|
|
||||||
type Type uint8
|
|
||||||
|
|
||||||
const (
|
|
||||||
TypeFile Type = iota
|
|
||||||
TypeDir
|
|
||||||
)
|
|
||||||
|
|
||||||
func (t Type) String() string {
|
|
||||||
switch t {
|
|
||||||
case TypeFile:
|
|
||||||
return "file"
|
|
||||||
case TypeDir:
|
|
||||||
return "dir"
|
|
||||||
}
|
|
||||||
return "<unknown>"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Options contains the options for downloading
|
|
||||||
// files and directories
|
|
||||||
type Options struct {
|
|
||||||
Hash []byte
|
|
||||||
HashAlgorithm string
|
|
||||||
Name string
|
|
||||||
URL string
|
|
||||||
Destination string
|
|
||||||
CacheDisabled bool
|
|
||||||
PostprocDisabled bool
|
|
||||||
Progress io.Writer
|
|
||||||
LocalDir string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (opts Options) NewHash() (hash.Hash, error) {
|
|
||||||
switch opts.HashAlgorithm {
|
|
||||||
case "", "sha256":
|
|
||||||
return sha256.New(), nil
|
|
||||||
case "sha224":
|
|
||||||
return sha256.New224(), nil
|
|
||||||
case "sha512":
|
|
||||||
return sha512.New(), nil
|
|
||||||
case "sha384":
|
|
||||||
return sha512.New384(), nil
|
|
||||||
case "sha1":
|
|
||||||
return sha1.New(), nil
|
|
||||||
case "md5":
|
|
||||||
return md5.New(), nil
|
|
||||||
case "blake2s-128":
|
|
||||||
return blake2s.New256(nil)
|
|
||||||
case "blake2s-256":
|
|
||||||
return blake2s.New256(nil)
|
|
||||||
case "blake2b-256":
|
|
||||||
return blake2b.New(32, nil)
|
|
||||||
case "blake2b-512":
|
|
||||||
return blake2b.New(64, nil)
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("%w: %s", ErrNoSuchHashAlgo, opts.HashAlgorithm)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Manifest holds information about the type and name
|
|
||||||
// of a downloaded file or directory. It is stored inside
|
|
||||||
// each cache directory for later use.
|
|
||||||
type Manifest struct {
|
|
||||||
Type Type
|
|
||||||
Name string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Downloader interface {
|
|
||||||
// Name returns the name of the downloader
|
|
||||||
Name() string
|
|
||||||
// MatchURL checks if the given URL matches
|
|
||||||
// the downloader.
|
|
||||||
MatchURL(string) bool
|
|
||||||
// Download downloads the object at the URL
|
|
||||||
// provided in the options, to the destination
|
|
||||||
// given in the options. It returns a type,
|
|
||||||
// a name for the downloaded object (this may be empty),
|
|
||||||
// and an error.
|
|
||||||
Download(Options) (Type, string, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdatingDownloader extends the Downloader interface
|
|
||||||
// with an Update method for protocols such as git, which
|
|
||||||
// allow for incremental updates without changing the URL.
|
|
||||||
type UpdatingDownloader interface {
|
|
||||||
Downloader
|
|
||||||
// Update checks for and performs any
|
|
||||||
// available updates for the object
|
|
||||||
// described in the options. It returns
|
|
||||||
// true if an update was performed, or
|
|
||||||
// false if no update was required.
|
|
||||||
Update(Options) (bool, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Download downloads a file or directory using the specified options.
|
|
||||||
// It first gets the appropriate downloader for the URL, then checks
|
|
||||||
// if caching is enabled. If caching is enabled, it attempts to get
|
|
||||||
// the cache directory for the URL and update it if necessary.
|
|
||||||
// If the source is found in the cache, it links it to the destination
|
|
||||||
// using hard links. If the source is not found in the cache,
|
|
||||||
// it downloads the source to a new cache directory and links it
|
|
||||||
// to the destination.
|
|
||||||
func Download(ctx context.Context, opts Options) (err error) {
|
|
||||||
log := loggerctx.From(ctx)
|
|
||||||
normalized, err := normalizeURL(opts.URL)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
opts.URL = normalized
|
|
||||||
|
|
||||||
d := getDownloader(opts.URL)
|
|
||||||
|
|
||||||
if opts.CacheDisabled {
|
|
||||||
_, _, err = d.Download(opts)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var t Type
|
|
||||||
cacheDir, ok := dlcache.Get(ctx, opts.URL)
|
|
||||||
if ok {
|
|
||||||
var updated bool
|
|
||||||
if d, ok := d.(UpdatingDownloader); ok {
|
|
||||||
log.Info("Source can be updated, updating if required").Str("source", opts.Name).Str("downloader", d.Name()).Send()
|
|
||||||
|
|
||||||
updated, err = d.Update(Options{
|
|
||||||
Hash: opts.Hash,
|
|
||||||
HashAlgorithm: opts.HashAlgorithm,
|
|
||||||
Name: opts.Name,
|
|
||||||
URL: opts.URL,
|
|
||||||
Destination: cacheDir,
|
|
||||||
Progress: opts.Progress,
|
|
||||||
LocalDir: opts.LocalDir,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m, err := getManifest(cacheDir)
|
|
||||||
if err == nil {
|
|
||||||
t = m.Type
|
|
||||||
|
|
||||||
dest := filepath.Join(opts.Destination, m.Name)
|
|
||||||
ok, err := handleCache(cacheDir, dest, m.Name, t)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if ok && !updated {
|
|
||||||
log.Info("Source found in cache and linked to destination").Str("source", opts.Name).Stringer("type", t).Send()
|
|
||||||
return nil
|
|
||||||
} else if ok {
|
|
||||||
log.Info("Source updated and linked to destination").Str("source", opts.Name).Stringer("type", t).Send()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// If we cannot read the manifest,
|
|
||||||
// this cache entry is invalid and
|
|
||||||
// the source must be re-downloaded.
|
|
||||||
err = os.RemoveAll(cacheDir)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("Downloading source").Str("source", opts.Name).Str("downloader", d.Name()).Send()
|
|
||||||
|
|
||||||
cacheDir, err = dlcache.New(ctx, opts.URL)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
t, name, err := d.Download(Options{
|
|
||||||
Hash: opts.Hash,
|
|
||||||
HashAlgorithm: opts.HashAlgorithm,
|
|
||||||
Name: opts.Name,
|
|
||||||
URL: opts.URL,
|
|
||||||
Destination: cacheDir,
|
|
||||||
Progress: opts.Progress,
|
|
||||||
LocalDir: opts.LocalDir,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = writeManifest(cacheDir, Manifest{t, name})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
dest := filepath.Join(opts.Destination, name)
|
|
||||||
_, err = handleCache(cacheDir, dest, name, t)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// writeManifest writes the manifest to the specified cache directory.
|
|
||||||
func writeManifest(cacheDir string, m Manifest) error {
|
|
||||||
fl, err := os.Create(filepath.Join(cacheDir, manifestFileName))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer fl.Close()
|
|
||||||
return msgpack.NewEncoder(fl).Encode(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// getManifest reads the manifest from the specified cache directory.
|
|
||||||
func getManifest(cacheDir string) (m Manifest, err error) {
|
|
||||||
fl, err := os.Open(filepath.Join(cacheDir, manifestFileName))
|
|
||||||
if err != nil {
|
|
||||||
return Manifest{}, err
|
|
||||||
}
|
|
||||||
defer fl.Close()
|
|
||||||
|
|
||||||
err = msgpack.NewDecoder(fl).Decode(&m)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleCache links the cache directory or a file within it to the destination
|
|
||||||
func handleCache(cacheDir, dest, name string, t Type) (bool, error) {
|
|
||||||
switch t {
|
|
||||||
case TypeFile:
|
|
||||||
cd, err := os.Open(cacheDir)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
names, err := cd.Readdirnames(0)
|
|
||||||
if err == io.EOF {
|
|
||||||
break
|
|
||||||
} else if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
cd.Close()
|
|
||||||
|
|
||||||
if slices.Contains(names, name) {
|
|
||||||
err = os.Link(filepath.Join(cacheDir, name), dest)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
case TypeDir:
|
|
||||||
err := linkDir(cacheDir, dest)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// linkDir recursively walks through a directory, creating
|
|
||||||
// hard links for each file from the src directory to the
|
|
||||||
// dest directory. If it encounters a directory, it will
|
|
||||||
// create a directory with the same name and permissions
|
|
||||||
// in the dest directory, because hard links cannot be
|
|
||||||
// created for directories.
|
|
||||||
func linkDir(src, dest string) error {
|
|
||||||
return filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if info.Name() == manifestFileName {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
rel, err := filepath.Rel(src, path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
newPath := filepath.Join(dest, rel)
|
|
||||||
if info.IsDir() {
|
|
||||||
return os.MkdirAll(newPath, info.Mode())
|
|
||||||
}
|
|
||||||
|
|
||||||
return os.Link(path, newPath)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func getDownloader(u string) Downloader {
|
|
||||||
for _, d := range Downloaders {
|
|
||||||
if d.MatchURL(u) {
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// normalizeURL normalizes a URL string, so that insignificant
|
|
||||||
// differences don't change the hash.
|
|
||||||
func normalizeURL(u string) (string, error) {
|
|
||||||
const normalizationFlags = purell.FlagRemoveTrailingSlash |
|
|
||||||
purell.FlagRemoveDefaultPort |
|
|
||||||
purell.FlagLowercaseHost |
|
|
||||||
purell.FlagLowercaseScheme |
|
|
||||||
purell.FlagRemoveDuplicateSlashes |
|
|
||||||
purell.FlagRemoveFragment |
|
|
||||||
purell.FlagRemoveUnnecessaryHostDots |
|
|
||||||
purell.FlagSortQuery |
|
|
||||||
purell.FlagDecodeHexHost |
|
|
||||||
purell.FlagDecodeOctalHost |
|
|
||||||
purell.FlagDecodeUnnecessaryEscapes |
|
|
||||||
purell.FlagRemoveEmptyPortSeparator
|
|
||||||
|
|
||||||
u, err := purell.NormalizeURLString(u, normalizationFlags)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fix magnet URLs after normalization
|
|
||||||
u = strings.Replace(u, "magnet://", "magnet:", 1)
|
|
||||||
return u, nil
|
|
||||||
}
|
|
@ -1,264 +0,0 @@
|
|||||||
/*
|
|
||||||
* LURE - Linux User REpository
|
|
||||||
* Copyright (C) 2023 Elara 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 dl
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"io"
|
|
||||||
"mime"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/mholt/archiver/v4"
|
|
||||||
"github.com/schollz/progressbar/v3"
|
|
||||||
"lure.sh/lure/internal/shutils/handlers"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FileDownloader downloads files using HTTP
|
|
||||||
type FileDownloader struct{}
|
|
||||||
|
|
||||||
// Name always returns "file"
|
|
||||||
func (FileDownloader) Name() string {
|
|
||||||
return "file"
|
|
||||||
}
|
|
||||||
|
|
||||||
// MatchURL always returns true, as FileDownloader
|
|
||||||
// is used as a fallback if nothing else matches
|
|
||||||
func (FileDownloader) MatchURL(string) bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Download downloads a file using HTTP. If the file is
|
|
||||||
// compressed using a supported format, it will be extracted
|
|
||||||
func (FileDownloader) Download(opts Options) (Type, string, error) {
|
|
||||||
u, err := url.Parse(opts.URL)
|
|
||||||
if err != nil {
|
|
||||||
return 0, "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
query := u.Query()
|
|
||||||
|
|
||||||
name := query.Get("~name")
|
|
||||||
query.Del("~name")
|
|
||||||
|
|
||||||
archive := query.Get("~archive")
|
|
||||||
query.Del("~archive")
|
|
||||||
|
|
||||||
u.RawQuery = query.Encode()
|
|
||||||
|
|
||||||
var r io.ReadCloser
|
|
||||||
var size int64
|
|
||||||
if u.Scheme == "local" {
|
|
||||||
localFl, err := os.Open(filepath.Join(opts.LocalDir, u.Path))
|
|
||||||
if err != nil {
|
|
||||||
return 0, "", err
|
|
||||||
}
|
|
||||||
fi, err := localFl.Stat()
|
|
||||||
if err != nil {
|
|
||||||
return 0, "", err
|
|
||||||
}
|
|
||||||
size = fi.Size()
|
|
||||||
if name == "" {
|
|
||||||
name = fi.Name()
|
|
||||||
}
|
|
||||||
r = localFl
|
|
||||||
} else {
|
|
||||||
res, err := http.Get(u.String())
|
|
||||||
if err != nil {
|
|
||||||
return 0, "", err
|
|
||||||
}
|
|
||||||
size = res.ContentLength
|
|
||||||
if name == "" {
|
|
||||||
name = getFilename(res)
|
|
||||||
}
|
|
||||||
r = res.Body
|
|
||||||
}
|
|
||||||
defer r.Close()
|
|
||||||
|
|
||||||
opts.PostprocDisabled = archive == "false"
|
|
||||||
|
|
||||||
path := filepath.Join(opts.Destination, name)
|
|
||||||
fl, err := os.Create(path)
|
|
||||||
if err != nil {
|
|
||||||
return 0, "", err
|
|
||||||
}
|
|
||||||
defer fl.Close()
|
|
||||||
|
|
||||||
var bar io.WriteCloser
|
|
||||||
if opts.Progress != nil {
|
|
||||||
bar = progressbar.NewOptions64(
|
|
||||||
size,
|
|
||||||
progressbar.OptionSetDescription(name),
|
|
||||||
progressbar.OptionSetWriter(opts.Progress),
|
|
||||||
progressbar.OptionShowBytes(true),
|
|
||||||
progressbar.OptionSetWidth(10),
|
|
||||||
progressbar.OptionThrottle(65*time.Millisecond),
|
|
||||||
progressbar.OptionShowCount(),
|
|
||||||
progressbar.OptionOnCompletion(func() {
|
|
||||||
_, _ = io.WriteString(opts.Progress, "\n")
|
|
||||||
}),
|
|
||||||
progressbar.OptionSpinnerType(14),
|
|
||||||
progressbar.OptionFullWidth(),
|
|
||||||
progressbar.OptionSetRenderBlankState(true),
|
|
||||||
)
|
|
||||||
defer bar.Close()
|
|
||||||
} else {
|
|
||||||
bar = handlers.NopRWC{}
|
|
||||||
}
|
|
||||||
|
|
||||||
h, err := opts.NewHash()
|
|
||||||
if err != nil {
|
|
||||||
return 0, "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
var w io.Writer
|
|
||||||
if opts.Hash != nil {
|
|
||||||
w = io.MultiWriter(fl, h, bar)
|
|
||||||
} else {
|
|
||||||
w = io.MultiWriter(fl, bar)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = io.Copy(w, r)
|
|
||||||
if err != nil {
|
|
||||||
return 0, "", err
|
|
||||||
}
|
|
||||||
r.Close()
|
|
||||||
|
|
||||||
if opts.Hash != nil {
|
|
||||||
sum := h.Sum(nil)
|
|
||||||
if !bytes.Equal(sum, opts.Hash) {
|
|
||||||
return 0, "", ErrChecksumMismatch
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.PostprocDisabled {
|
|
||||||
return TypeFile, name, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = fl.Seek(0, io.SeekStart)
|
|
||||||
if err != nil {
|
|
||||||
return 0, "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
format, ar, err := archiver.Identify(name, fl)
|
|
||||||
if err == archiver.ErrNoMatch {
|
|
||||||
return TypeFile, name, nil
|
|
||||||
} else if err != nil {
|
|
||||||
return 0, "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = extractFile(ar, format, name, opts)
|
|
||||||
if err != nil {
|
|
||||||
return 0, "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = os.Remove(path)
|
|
||||||
return TypeDir, "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
// extractFile extracts an archive or decompresses a file
|
|
||||||
func extractFile(r io.Reader, format archiver.Format, name string, opts Options) (err error) {
|
|
||||||
fname := format.Name()
|
|
||||||
|
|
||||||
switch format := format.(type) {
|
|
||||||
case archiver.Extractor:
|
|
||||||
err = format.Extract(context.Background(), r, nil, func(ctx context.Context, f archiver.File) error {
|
|
||||||
fr, err := f.Open()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer fr.Close()
|
|
||||||
fi, err := f.Stat()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fm := fi.Mode()
|
|
||||||
|
|
||||||
path := filepath.Join(opts.Destination, f.NameInArchive)
|
|
||||||
|
|
||||||
err = os.MkdirAll(filepath.Dir(path), 0o755)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.IsDir() {
|
|
||||||
err = os.Mkdir(path, 0o755)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
outFl, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, fm.Perm())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer outFl.Close()
|
|
||||||
|
|
||||||
_, err = io.Copy(outFl, fr)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case archiver.Decompressor:
|
|
||||||
rc, err := format.OpenReader(r)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer rc.Close()
|
|
||||||
|
|
||||||
path := filepath.Join(opts.Destination, name)
|
|
||||||
path = strings.TrimSuffix(path, fname)
|
|
||||||
|
|
||||||
outFl, err := os.Create(path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = io.Copy(outFl, rc)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getFilename attempts to parse the Content-Disposition
|
|
||||||
// HTTP response header and extract a filename. If the
|
|
||||||
// header does not exist, it will use the last element
|
|
||||||
// of the path.
|
|
||||||
func getFilename(res *http.Response) (name string) {
|
|
||||||
_, params, err := mime.ParseMediaType(res.Header.Get("Content-Disposition"))
|
|
||||||
if err != nil {
|
|
||||||
return path.Base(res.Request.URL.Path)
|
|
||||||
}
|
|
||||||
if filename, ok := params["filename"]; ok {
|
|
||||||
return filename
|
|
||||||
} else {
|
|
||||||
return path.Base(res.Request.URL.Path)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,198 +0,0 @@
|
|||||||
/*
|
|
||||||
* LURE - Linux User REpository
|
|
||||||
* Copyright (C) 2023 Elara 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 dl
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"net/url"
|
|
||||||
"path"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/go-git/go-git/v5"
|
|
||||||
"github.com/go-git/go-git/v5/config"
|
|
||||||
"github.com/go-git/go-git/v5/plumbing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GitDownloader downloads Git repositories
|
|
||||||
type GitDownloader struct{}
|
|
||||||
|
|
||||||
// Name always returns "git"
|
|
||||||
func (GitDownloader) Name() string {
|
|
||||||
return "git"
|
|
||||||
}
|
|
||||||
|
|
||||||
// MatchURL matches any URLs that start with "git+"
|
|
||||||
func (GitDownloader) MatchURL(u string) bool {
|
|
||||||
return strings.HasPrefix(u, "git+")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Download uses git to clone the repository from the specified URL.
|
|
||||||
// It allows specifying the revision, depth and recursion options
|
|
||||||
// via query string
|
|
||||||
func (GitDownloader) Download(opts Options) (Type, string, error) {
|
|
||||||
u, err := url.Parse(opts.URL)
|
|
||||||
if err != nil {
|
|
||||||
return 0, "", err
|
|
||||||
}
|
|
||||||
u.Scheme = strings.TrimPrefix(u.Scheme, "git+")
|
|
||||||
|
|
||||||
query := u.Query()
|
|
||||||
|
|
||||||
rev := query.Get("~rev")
|
|
||||||
query.Del("~rev")
|
|
||||||
|
|
||||||
name := query.Get("~name")
|
|
||||||
query.Del("~name")
|
|
||||||
|
|
||||||
depthStr := query.Get("~depth")
|
|
||||||
query.Del("~depth")
|
|
||||||
|
|
||||||
recursive := query.Get("~recursive")
|
|
||||||
query.Del("~recursive")
|
|
||||||
|
|
||||||
u.RawQuery = query.Encode()
|
|
||||||
|
|
||||||
depth := 0
|
|
||||||
if depthStr != "" {
|
|
||||||
depth, err = strconv.Atoi(depthStr)
|
|
||||||
if err != nil {
|
|
||||||
return 0, "", err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
co := &git.CloneOptions{
|
|
||||||
URL: u.String(),
|
|
||||||
Depth: depth,
|
|
||||||
Progress: opts.Progress,
|
|
||||||
RecurseSubmodules: git.NoRecurseSubmodules,
|
|
||||||
}
|
|
||||||
|
|
||||||
if recursive == "true" {
|
|
||||||
co.RecurseSubmodules = git.DefaultSubmoduleRecursionDepth
|
|
||||||
}
|
|
||||||
|
|
||||||
r, err := git.PlainClone(opts.Destination, false, co)
|
|
||||||
if err != nil {
|
|
||||||
return 0, "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = r.Fetch(&git.FetchOptions{
|
|
||||||
RefSpecs: []config.RefSpec{"+refs/*:refs/*"},
|
|
||||||
})
|
|
||||||
if err != git.NoErrAlreadyUpToDate && err != nil {
|
|
||||||
return 0, "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if rev != "" {
|
|
||||||
h, err := r.ResolveRevision(plumbing.Revision(rev))
|
|
||||||
if err != nil {
|
|
||||||
return 0, "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
w, err := r.Worktree()
|
|
||||||
if err != nil {
|
|
||||||
return 0, "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = w.Checkout(&git.CheckoutOptions{
|
|
||||||
Hash: *h,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return 0, "", err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if name == "" {
|
|
||||||
name = strings.TrimSuffix(path.Base(u.Path), ".git")
|
|
||||||
}
|
|
||||||
|
|
||||||
return TypeDir, name, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update uses git to pull the repository and update it
|
|
||||||
// to the latest revision. It allows specifying the depth
|
|
||||||
// and recursion options via query string. It returns
|
|
||||||
// true if update was successful and false if the
|
|
||||||
// repository is already up-to-date
|
|
||||||
func (GitDownloader) Update(opts Options) (bool, error) {
|
|
||||||
u, err := url.Parse(opts.URL)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
u.Scheme = strings.TrimPrefix(u.Scheme, "git+")
|
|
||||||
|
|
||||||
query := u.Query()
|
|
||||||
query.Del("~rev")
|
|
||||||
|
|
||||||
depthStr := query.Get("~depth")
|
|
||||||
query.Del("~depth")
|
|
||||||
|
|
||||||
recursive := query.Get("~recursive")
|
|
||||||
query.Del("~recursive")
|
|
||||||
|
|
||||||
u.RawQuery = query.Encode()
|
|
||||||
|
|
||||||
r, err := git.PlainOpen(opts.Destination)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
w, err := r.Worktree()
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
depth := 0
|
|
||||||
if depthStr != "" {
|
|
||||||
depth, err = strconv.Atoi(depthStr)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
po := &git.PullOptions{
|
|
||||||
Depth: depth,
|
|
||||||
Progress: opts.Progress,
|
|
||||||
RecurseSubmodules: git.NoRecurseSubmodules,
|
|
||||||
}
|
|
||||||
|
|
||||||
if recursive == "true" {
|
|
||||||
po.RecurseSubmodules = git.DefaultSubmoduleRecursionDepth
|
|
||||||
}
|
|
||||||
|
|
||||||
m, err := getManifest(opts.Destination)
|
|
||||||
manifestOK := err == nil
|
|
||||||
|
|
||||||
err = w.Pull(po)
|
|
||||||
if errors.Is(err, git.NoErrAlreadyUpToDate) {
|
|
||||||
return false, nil
|
|
||||||
} else if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if manifestOK {
|
|
||||||
err = writeManifest(opts.Destination, m)
|
|
||||||
if err != nil {
|
|
||||||
return true, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true, nil
|
|
||||||
}
|
|
@ -1,90 +0,0 @@
|
|||||||
package dl
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
urlMatchRegex = regexp.MustCompile(`(magnet|torrent\+https?):.*`)
|
|
||||||
ErrAria2NotFound = errors.New("aria2 must be installed for torrent functionality")
|
|
||||||
ErrDestinationEmpty = errors.New("the destination directory is empty")
|
|
||||||
)
|
|
||||||
|
|
||||||
type TorrentDownloader struct{}
|
|
||||||
|
|
||||||
// Name always returns "file"
|
|
||||||
func (TorrentDownloader) Name() string {
|
|
||||||
return "torrent"
|
|
||||||
}
|
|
||||||
|
|
||||||
// MatchURL returns true if the URL is a magnet link
|
|
||||||
// or an http(s) link with a "torrent+" prefix
|
|
||||||
func (TorrentDownloader) MatchURL(u string) bool {
|
|
||||||
return urlMatchRegex.MatchString(u)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Download downloads a file over the BitTorrent protocol.
|
|
||||||
func (TorrentDownloader) Download(opts Options) (Type, string, error) {
|
|
||||||
aria2Path, err := exec.LookPath("aria2c")
|
|
||||||
if err != nil {
|
|
||||||
return 0, "", ErrAria2NotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
opts.URL = strings.TrimPrefix(opts.URL, "torrent+")
|
|
||||||
|
|
||||||
cmd := exec.Command(aria2Path, "--summary-interval=0", "--log-level=warn", "--seed-time=0", "--dir="+opts.Destination, opts.URL)
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
err = cmd.Run()
|
|
||||||
if err != nil {
|
|
||||||
return 0, "", fmt.Errorf("aria2c returned an error: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = removeTorrentFiles(opts.Destination)
|
|
||||||
if err != nil {
|
|
||||||
return 0, "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return determineType(opts.Destination)
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeTorrentFiles(path string) error {
|
|
||||||
filePaths, err := filepath.Glob(filepath.Join(path, "*.torrent"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, filePath := range filePaths {
|
|
||||||
err = os.Remove(filePath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func determineType(path string) (Type, string, error) {
|
|
||||||
files, err := os.ReadDir(path)
|
|
||||||
if err != nil {
|
|
||||||
return 0, "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(files) > 1 {
|
|
||||||
return TypeDir, "", nil
|
|
||||||
} else if len(files) == 1 {
|
|
||||||
if files[0].IsDir() {
|
|
||||||
return TypeDir, files[0].Name(), nil
|
|
||||||
} else {
|
|
||||||
return TypeFile, files[0].Name(), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0, "", ErrDestinationEmpty
|
|
||||||
}
|
|
@ -1,93 +0,0 @@
|
|||||||
/*
|
|
||||||
* LURE - Linux User REpository
|
|
||||||
* Copyright (C) 2023 Elara 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 dlcache
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/sha1"
|
|
||||||
"encoding/hex"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"lure.sh/lure/internal/config"
|
|
||||||
)
|
|
||||||
|
|
||||||
// BasePath returns the base path of the download cache
|
|
||||||
func BasePath(ctx context.Context) string {
|
|
||||||
return filepath.Join(config.GetPaths(ctx).CacheDir, "dl")
|
|
||||||
}
|
|
||||||
|
|
||||||
// New creates a new directory with the given ID in the cache.
|
|
||||||
// If a directory with the same ID already exists,
|
|
||||||
// it will be deleted before creating a new one.
|
|
||||||
func New(ctx context.Context, id string) (string, error) {
|
|
||||||
h, err := hashID(id)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
itemPath := filepath.Join(BasePath(ctx), h)
|
|
||||||
|
|
||||||
fi, err := os.Stat(itemPath)
|
|
||||||
if err == nil || (fi != nil && !fi.IsDir()) {
|
|
||||||
err = os.RemoveAll(itemPath)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = os.MkdirAll(itemPath, 0o755)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return itemPath, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get checks if an entry with the given ID
|
|
||||||
// already exists in the cache, and if so,
|
|
||||||
// returns the directory and true. If it
|
|
||||||
// does not exist, it returns an empty string
|
|
||||||
// and false.
|
|
||||||
func Get(ctx context.Context, id string) (string, bool) {
|
|
||||||
h, err := hashID(id)
|
|
||||||
if err != nil {
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
itemPath := filepath.Join(BasePath(ctx), h)
|
|
||||||
|
|
||||||
_, err = os.Stat(itemPath)
|
|
||||||
if err != nil {
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
|
|
||||||
return itemPath, true
|
|
||||||
}
|
|
||||||
|
|
||||||
// hashID hashes the input ID with SHA1
|
|
||||||
// and returns the hex string of the hashed
|
|
||||||
// ID.
|
|
||||||
func hashID(id string) (string, error) {
|
|
||||||
h := sha1.New()
|
|
||||||
_, err := io.WriteString(h, id)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return hex.EncodeToString(h.Sum(nil)), nil
|
|
||||||
}
|
|
@ -1,76 +0,0 @@
|
|||||||
/*
|
|
||||||
* LURE - Linux User REpository
|
|
||||||
* Copyright (C) 2023 Elara 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 dlcache_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/sha1"
|
|
||||||
"encoding/hex"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"lure.sh/lure/internal/config"
|
|
||||||
"lure.sh/lure/internal/dlcache"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
dir, err := os.MkdirTemp("/tmp", "lure-dlcache-test.*")
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
config.GetPaths(context.Background()).RepoDir = dir
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNew(t *testing.T) {
|
|
||||||
const id = "https://example.com"
|
|
||||||
dir, err := dlcache.New(id)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
exp := filepath.Join(dlcache.BasePath(), sha1sum(id))
|
|
||||||
if dir != exp {
|
|
||||||
t.Errorf("Expected %s, got %s", exp, dir)
|
|
||||||
}
|
|
||||||
|
|
||||||
fi, err := os.Stat(dir)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("stat: expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !fi.IsDir() {
|
|
||||||
t.Errorf("Expected cache item to be a directory")
|
|
||||||
}
|
|
||||||
|
|
||||||
dir2, ok := dlcache.Get(id)
|
|
||||||
if !ok {
|
|
||||||
t.Errorf("Expected Get() to return valid value")
|
|
||||||
}
|
|
||||||
if dir2 != dir {
|
|
||||||
t.Errorf("Expected %s from Get(), got %s", dir, dir2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func sha1sum(id string) string {
|
|
||||||
h := sha1.New()
|
|
||||||
_, _ = io.WriteString(h, id)
|
|
||||||
return hex.EncodeToString(h.Sum(nil))
|
|
||||||
}
|
|
@ -1,110 +0,0 @@
|
|||||||
/*
|
|
||||||
* LURE - Linux User REpository
|
|
||||||
* Copyright (C) 2023 Elara 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 osutils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Move attempts to use os.Rename and if that fails (such as for a cross-device move),
|
|
||||||
// it instead copies the source to the destination and then removes the source.
|
|
||||||
func Move(sourcePath, destPath string) error {
|
|
||||||
// Try to rename the source to the destination
|
|
||||||
err := os.Rename(sourcePath, destPath)
|
|
||||||
if err == nil {
|
|
||||||
return nil // Successful move
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rename failed, so copy the source to the destination
|
|
||||||
err = copyDirOrFile(sourcePath, destPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy successful, remove the original source
|
|
||||||
err = os.RemoveAll(sourcePath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func copyDirOrFile(sourcePath, destPath string) error {
|
|
||||||
sourceInfo, err := os.Stat(sourcePath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if sourceInfo.IsDir() {
|
|
||||||
return copyDir(sourcePath, destPath, sourceInfo)
|
|
||||||
} else if sourceInfo.Mode().IsRegular() {
|
|
||||||
return copyFile(sourcePath, destPath, sourceInfo)
|
|
||||||
} else {
|
|
||||||
// ignore non-regular files
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func copyDir(sourcePath, destPath string, sourceInfo os.FileInfo) error {
|
|
||||||
err := os.MkdirAll(destPath, sourceInfo.Mode())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
entries, err := os.ReadDir(sourcePath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, entry := range entries {
|
|
||||||
sourceEntry := filepath.Join(sourcePath, entry.Name())
|
|
||||||
destEntry := filepath.Join(destPath, entry.Name())
|
|
||||||
|
|
||||||
err = copyDirOrFile(sourceEntry, destEntry)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func copyFile(sourcePath, destPath string, sourceInfo os.FileInfo) error {
|
|
||||||
sourceFile, err := os.Open(sourcePath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer sourceFile.Close()
|
|
||||||
|
|
||||||
destFile, err := os.OpenFile(destPath, os.O_CREATE|os.O_TRUNC|os.O_RDWR, sourceInfo.Mode())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer destFile.Close()
|
|
||||||
|
|
||||||
_, err = io.Copy(destFile, sourceFile)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,223 +0,0 @@
|
|||||||
/*
|
|
||||||
* LURE - Linux User REpository
|
|
||||||
* Copyright (C) 2023 Elara 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 overrides
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"lure.sh/lure/internal/cpu"
|
|
||||||
"lure.sh/lure/internal/db"
|
|
||||||
"lure.sh/lure/pkg/distro"
|
|
||||||
"golang.org/x/exp/slices"
|
|
||||||
"golang.org/x/text/language"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Opts struct {
|
|
||||||
Name string
|
|
||||||
Overrides bool
|
|
||||||
LikeDistros bool
|
|
||||||
Languages []string
|
|
||||||
LanguageTags []language.Tag
|
|
||||||
}
|
|
||||||
|
|
||||||
var DefaultOpts = &Opts{
|
|
||||||
Overrides: true,
|
|
||||||
LikeDistros: true,
|
|
||||||
Languages: []string{"en"},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resolve generates a slice of possible override names in the order that they should be checked
|
|
||||||
func Resolve(info *distro.OSRelease, opts *Opts) ([]string, error) {
|
|
||||||
if opts == nil {
|
|
||||||
opts = DefaultOpts
|
|
||||||
}
|
|
||||||
|
|
||||||
if !opts.Overrides {
|
|
||||||
return []string{opts.Name}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
langs, err := parseLangs(opts.Languages, opts.LanguageTags)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
architectures, err := cpu.CompatibleArches(cpu.Arch())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
distros := []string{info.ID}
|
|
||||||
if opts.LikeDistros {
|
|
||||||
distros = append(distros, info.Like...)
|
|
||||||
}
|
|
||||||
|
|
||||||
var out []string
|
|
||||||
for _, lang := range langs {
|
|
||||||
for _, distro := range distros {
|
|
||||||
for _, arch := range architectures {
|
|
||||||
out = append(out, opts.Name+"_"+arch+"_"+distro+"_"+lang)
|
|
||||||
}
|
|
||||||
|
|
||||||
out = append(out, opts.Name+"_"+distro+"_"+lang)
|
|
||||||
}
|
|
||||||
|
|
||||||
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.TrimPrefix(strings.ReplaceAll(item, "-", "_"), "_")
|
|
||||||
}
|
|
||||||
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Opts) WithName(name string) *Opts {
|
|
||||||
out := &Opts{}
|
|
||||||
*out = *o
|
|
||||||
|
|
||||||
out.Name = name
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Opts) WithOverrides(v bool) *Opts {
|
|
||||||
out := &Opts{}
|
|
||||||
*out = *o
|
|
||||||
|
|
||||||
out.Overrides = v
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Opts) WithLikeDistros(v bool) *Opts {
|
|
||||||
out := &Opts{}
|
|
||||||
*out = *o
|
|
||||||
|
|
||||||
out.LikeDistros = v
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Opts) WithLanguages(langs []string) *Opts {
|
|
||||||
out := &Opts{}
|
|
||||||
*out = *o
|
|
||||||
|
|
||||||
out.Languages = langs
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Opts) WithLanguageTags(langs []string) *Opts {
|
|
||||||
out := &Opts{}
|
|
||||||
*out = *o
|
|
||||||
|
|
||||||
out.Languages = langs
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResolvedPackage is a LURE package after its overrides
|
|
||||||
// have been resolved
|
|
||||||
type ResolvedPackage struct {
|
|
||||||
Name string `sh:"name"`
|
|
||||||
Version string `sh:"version"`
|
|
||||||
Release int `sh:"release"`
|
|
||||||
Epoch uint `sh:"epoch"`
|
|
||||||
Description string `db:"description"`
|
|
||||||
Homepage string `db:"homepage"`
|
|
||||||
Maintainer string `db:"maintainer"`
|
|
||||||
Architectures []string `sh:"architectures"`
|
|
||||||
Licenses []string `sh:"license"`
|
|
||||||
Provides []string `sh:"provides"`
|
|
||||||
Conflicts []string `sh:"conflicts"`
|
|
||||||
Replaces []string `sh:"replaces"`
|
|
||||||
Depends []string `sh:"deps"`
|
|
||||||
BuildDepends []string `sh:"build_deps"`
|
|
||||||
OptDepends []string `sh:"opt_deps"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func ResolvePackage(pkg *db.Package, overrides []string) *ResolvedPackage {
|
|
||||||
out := &ResolvedPackage{}
|
|
||||||
outVal := reflect.ValueOf(out).Elem()
|
|
||||||
pkgVal := reflect.ValueOf(pkg).Elem()
|
|
||||||
|
|
||||||
for i := 0; i < outVal.NumField(); i++ {
|
|
||||||
fieldVal := outVal.Field(i)
|
|
||||||
fieldType := fieldVal.Type()
|
|
||||||
pkgFieldVal := pkgVal.FieldByName(outVal.Type().Field(i).Name)
|
|
||||||
pkgFieldType := pkgFieldVal.Type()
|
|
||||||
|
|
||||||
if strings.HasPrefix(pkgFieldType.String(), "db.JSON") {
|
|
||||||
pkgFieldVal = pkgFieldVal.FieldByName("Val")
|
|
||||||
pkgFieldType = pkgFieldVal.Type()
|
|
||||||
}
|
|
||||||
|
|
||||||
if pkgFieldType.AssignableTo(fieldType) {
|
|
||||||
fieldVal.Set(pkgFieldVal)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if pkgFieldVal.Kind() == reflect.Map && pkgFieldType.Elem().AssignableTo(fieldType) {
|
|
||||||
for _, override := range overrides {
|
|
||||||
overrideVal := pkgFieldVal.MapIndex(reflect.ValueOf(override))
|
|
||||||
if !overrideVal.IsValid() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
fieldVal.Set(overrideVal)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseLangs(langs []string, tags []language.Tag) ([]string, error) {
|
|
||||||
out := make([]string, len(tags)+len(langs))
|
|
||||||
for i, tag := range tags {
|
|
||||||
base, _ := tag.Base()
|
|
||||||
out[i] = base.String()
|
|
||||||
}
|
|
||||||
for i, lang := range langs {
|
|
||||||
tag, err := language.Parse(lang)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
base, _ := tag.Base()
|
|
||||||
out[len(tags)+i] = base.String()
|
|
||||||
}
|
|
||||||
slices.Sort(out)
|
|
||||||
out = slices.Compact(out)
|
|
||||||
return out, nil
|
|
||||||
}
|
|
@ -1,195 +0,0 @@
|
|||||||
/*
|
|
||||||
* LURE - Linux User REpository
|
|
||||||
* Copyright (C) 2023 Elara 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 overrides_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"lure.sh/lure/internal/overrides"
|
|
||||||
"lure.sh/lure/pkg/distro"
|
|
||||||
"golang.org/x/text/language"
|
|
||||||
)
|
|
||||||
|
|
||||||
var info = &distro.OSRelease{
|
|
||||||
ID: "centos",
|
|
||||||
Like: []string{"rhel", "fedora"},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestResolve(t *testing.T) {
|
|
||||||
names, err := overrides.Resolve(info, nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := []string{
|
|
||||||
"amd64_centos_en",
|
|
||||||
"centos_en",
|
|
||||||
"amd64_rhel_en",
|
|
||||||
"rhel_en",
|
|
||||||
"amd64_fedora_en",
|
|
||||||
"fedora_en",
|
|
||||||
"amd64_en",
|
|
||||||
"en",
|
|
||||||
"amd64_centos",
|
|
||||||
"centos",
|
|
||||||
"amd64_rhel",
|
|
||||||
"rhel",
|
|
||||||
"amd64_fedora",
|
|
||||||
"fedora",
|
|
||||||
"amd64",
|
|
||||||
"",
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(names, expected) {
|
|
||||||
t.Errorf("expected %v, got %v", expected, names)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestResolveName(t *testing.T) {
|
|
||||||
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_amd64_centos",
|
|
||||||
"deps_centos",
|
|
||||||
"deps_amd64_rhel",
|
|
||||||
"deps_rhel",
|
|
||||||
"deps_amd64_fedora",
|
|
||||||
"deps_fedora",
|
|
||||||
"deps_amd64",
|
|
||||||
"deps",
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(names, expected) {
|
|
||||||
t.Errorf("expected %v, got %v", expected, names)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
LikeDistros: false,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := []string{
|
|
||||||
"amd64_centos",
|
|
||||||
"centos",
|
|
||||||
"amd64",
|
|
||||||
"",
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(names, expected) {
|
|
||||||
t.Errorf("expected %v, got %v", expected, names)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestResolveNoOverrides(t *testing.T) {
|
|
||||||
names, err := overrides.Resolve(info, &overrides.Opts{
|
|
||||||
Name: "deps",
|
|
||||||
Overrides: false,
|
|
||||||
LikeDistros: false,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := []string{"deps"}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(names, expected) {
|
|
||||||
t.Errorf("expected %v, got %v", expected, names)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestResolveLangs(t *testing.T) {
|
|
||||||
names, err := overrides.Resolve(info, &overrides.Opts{
|
|
||||||
Overrides: true,
|
|
||||||
Languages: []string{"ru_RU", "en", "en_US"},
|
|
||||||
LanguageTags: []language.Tag{language.BritishEnglish},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := []string{
|
|
||||||
"amd64_centos_en",
|
|
||||||
"centos_en",
|
|
||||||
"amd64_en",
|
|
||||||
"en",
|
|
||||||
"amd64_centos_ru",
|
|
||||||
"centos_ru",
|
|
||||||
"amd64_ru",
|
|
||||||
"ru",
|
|
||||||
"amd64_centos",
|
|
||||||
"centos",
|
|
||||||
"amd64",
|
|
||||||
"",
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(names, expected) {
|
|
||||||
t.Errorf("expected %v, got %v", expected, names)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,37 +0,0 @@
|
|||||||
/*
|
|
||||||
* LURE - Linux User REpository
|
|
||||||
* Copyright (C) 2023 Elara 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 pager
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/alecthomas/chroma/v2/quick"
|
|
||||||
)
|
|
||||||
|
|
||||||
func SyntaxHighlightBash(r io.Reader, style string) (string, error) {
|
|
||||||
data, err := io.ReadAll(r)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
w := &bytes.Buffer{}
|
|
||||||
err = quick.Highlight(w, string(data), "bash", "terminal", style)
|
|
||||||
return w.String(), err
|
|
||||||
}
|
|
@ -1,143 +0,0 @@
|
|||||||
/*
|
|
||||||
* LURE - Linux User REpository
|
|
||||||
* Copyright (C) 2023 Elara 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 pager
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/charmbracelet/bubbles/viewport"
|
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
|
||||||
"github.com/charmbracelet/lipgloss"
|
|
||||||
"github.com/muesli/reflow/wordwrap"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
titleStyle lipgloss.Style
|
|
||||||
infoStyle lipgloss.Style
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
b1 := lipgloss.RoundedBorder()
|
|
||||||
b1.Right = "\u251C"
|
|
||||||
titleStyle = lipgloss.NewStyle().BorderStyle(b1).Padding(0, 1)
|
|
||||||
|
|
||||||
b2 := lipgloss.RoundedBorder()
|
|
||||||
b2.Left = "\u2524"
|
|
||||||
infoStyle = titleStyle.Copy().BorderStyle(b2)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Pager struct {
|
|
||||||
model pagerModel
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(name, content string) *Pager {
|
|
||||||
return &Pager{
|
|
||||||
model: pagerModel{
|
|
||||||
name: name,
|
|
||||||
content: content,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Pager) Run() error {
|
|
||||||
prog := tea.NewProgram(
|
|
||||||
p.model,
|
|
||||||
tea.WithMouseCellMotion(),
|
|
||||||
tea.WithAltScreen(),
|
|
||||||
)
|
|
||||||
|
|
||||||
_, err := prog.Run()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
type pagerModel struct {
|
|
||||||
name string
|
|
||||||
content string
|
|
||||||
ready bool
|
|
||||||
viewport viewport.Model
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pm pagerModel) Init() tea.Cmd {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pm pagerModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
||||||
var (
|
|
||||||
cmd tea.Cmd
|
|
||||||
cmds []tea.Cmd
|
|
||||||
)
|
|
||||||
|
|
||||||
switch msg := msg.(type) {
|
|
||||||
case tea.KeyMsg:
|
|
||||||
k := msg.String()
|
|
||||||
if k == "ctrl+c" || k == "q" || k == "esc" {
|
|
||||||
return pm, tea.Quit
|
|
||||||
}
|
|
||||||
case tea.WindowSizeMsg:
|
|
||||||
headerHeight := lipgloss.Height(pm.headerView())
|
|
||||||
footerHeight := lipgloss.Height(pm.footerView())
|
|
||||||
verticalMarginHeight := headerHeight + footerHeight
|
|
||||||
|
|
||||||
if !pm.ready {
|
|
||||||
pm.viewport = viewport.New(msg.Width, msg.Height-verticalMarginHeight)
|
|
||||||
pm.viewport.HighPerformanceRendering = true
|
|
||||||
pm.viewport.YPosition = headerHeight + 1
|
|
||||||
pm.viewport.SetContent(wordwrap.String(pm.content, msg.Width))
|
|
||||||
pm.ready = true
|
|
||||||
} else {
|
|
||||||
pm.viewport.Width = msg.Width
|
|
||||||
pm.viewport.Height = msg.Height - verticalMarginHeight
|
|
||||||
}
|
|
||||||
|
|
||||||
cmds = append(cmds, viewport.Sync(pm.viewport))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle keyboard and mouse events in the viewport
|
|
||||||
pm.viewport, cmd = pm.viewport.Update(msg)
|
|
||||||
cmds = append(cmds, cmd)
|
|
||||||
|
|
||||||
return pm, tea.Batch(cmds...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pm pagerModel) View() string {
|
|
||||||
if !pm.ready {
|
|
||||||
return "\n Initializing..."
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%s\n%s\n%s", pm.headerView(), pm.viewport.View(), pm.footerView())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pm pagerModel) headerView() string {
|
|
||||||
title := titleStyle.Render(pm.name)
|
|
||||||
line := strings.Repeat("─", max(0, pm.viewport.Width-lipgloss.Width(title)))
|
|
||||||
return lipgloss.JoinHorizontal(lipgloss.Center, title, line)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pm pagerModel) footerView() string {
|
|
||||||
info := infoStyle.Render(fmt.Sprintf("%3.f%%", pm.viewport.ScrollPercent()*100))
|
|
||||||
line := strings.Repeat("─", max(0, pm.viewport.Width-lipgloss.Width(info)))
|
|
||||||
return lipgloss.JoinHorizontal(lipgloss.Center, line, info)
|
|
||||||
}
|
|
||||||
|
|
||||||
func max(a, b int) int {
|
|
||||||
if a > b {
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* LURE - Linux User REpository
|
* LURE - Linux User REpository
|
||||||
* Copyright (C) 2023 Elara Musayelyan
|
* Copyright (C) 2022 Arsen Musayelyan
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -21,19 +21,21 @@ package decoder
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/mitchellh/mapstructure"
|
"github.com/mitchellh/mapstructure"
|
||||||
"lure.sh/lure/internal/overrides"
|
"go.arsenm.dev/lure/distro"
|
||||||
"lure.sh/lure/pkg/distro"
|
"go.arsenm.dev/lure/internal/cpu"
|
||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
"mvdan.cc/sh/v3/expand"
|
"mvdan.cc/sh/v3/expand"
|
||||||
"mvdan.cc/sh/v3/interp"
|
"mvdan.cc/sh/v3/interp"
|
||||||
"mvdan.cc/sh/v3/syntax"
|
"mvdan.cc/sh/v3/syntax"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrNotPointerToStruct = errors.New("val must be a pointer to a struct")
|
var ErrInvalidType = errors.New("val must be a pointer to a struct")
|
||||||
|
|
||||||
type VarNotFoundError struct {
|
type VarNotFoundError struct {
|
||||||
name string
|
name string
|
||||||
@ -43,29 +45,19 @@ func (nfe VarNotFoundError) Error() string {
|
|||||||
return "required variable '" + nfe.name + "' could not be found"
|
return "required variable '" + nfe.name + "' could not be found"
|
||||||
}
|
}
|
||||||
|
|
||||||
type InvalidTypeError struct {
|
|
||||||
name string
|
|
||||||
vartype string
|
|
||||||
exptype string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ite InvalidTypeError) Error() string {
|
|
||||||
return "variable '" + ite.name + "' is of type " + ite.vartype + ", but " + ite.exptype + " is expected"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decoder provides methods for decoding variable values
|
// Decoder provides methods for decoding variable values
|
||||||
type Decoder struct {
|
type Decoder struct {
|
||||||
info *distro.OSRelease
|
info *distro.OSRelease
|
||||||
Runner *interp.Runner
|
runner *interp.Runner
|
||||||
// Enable distro overrides (true by default)
|
// Enable distro overrides (true by default)
|
||||||
Overrides bool
|
Overrides bool
|
||||||
// Enable using like distros for overrides
|
// Enable using like distros for overrides (true by default)
|
||||||
LikeDistros bool
|
LikeDistros bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new variable decoder
|
// New creates a new variable decoder
|
||||||
func New(info *distro.OSRelease, runner *interp.Runner) *Decoder {
|
func New(info *distro.OSRelease, runner *interp.Runner) *Decoder {
|
||||||
return &Decoder{info, runner, true, len(info.Like) > 0}
|
return &Decoder{info, runner, true, true}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DecodeVar decodes a variable to val using reflection.
|
// DecodeVar decodes a variable to val using reflection.
|
||||||
@ -78,20 +70,8 @@ func (d *Decoder) DecodeVar(name string, val any) error {
|
|||||||
|
|
||||||
dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
||||||
WeaklyTypedInput: true,
|
WeaklyTypedInput: true,
|
||||||
DecodeHook: mapstructure.DecodeHookFuncValue(func(from, to reflect.Value) (interface{}, error) {
|
Result: val,
|
||||||
if strings.Contains(to.Type().String(), "db.JSON") {
|
TagName: "sh",
|
||||||
valType := to.FieldByName("Val").Type()
|
|
||||||
if !from.Type().AssignableTo(valType) {
|
|
||||||
return nil, InvalidTypeError{name, from.Type().String(), valType.String()}
|
|
||||||
}
|
|
||||||
|
|
||||||
to.FieldByName("Val").Set(from)
|
|
||||||
return to, nil
|
|
||||||
}
|
|
||||||
return from.Interface(), nil
|
|
||||||
}),
|
|
||||||
Result: val,
|
|
||||||
TagName: "sh",
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -112,11 +92,11 @@ func (d *Decoder) DecodeVar(name string, val any) error {
|
|||||||
func (d *Decoder) DecodeVars(val any) error {
|
func (d *Decoder) DecodeVars(val any) error {
|
||||||
valKind := reflect.TypeOf(val).Kind()
|
valKind := reflect.TypeOf(val).Kind()
|
||||||
if valKind != reflect.Pointer {
|
if valKind != reflect.Pointer {
|
||||||
return ErrNotPointerToStruct
|
return ErrInvalidType
|
||||||
} else {
|
} else {
|
||||||
elemKind := reflect.TypeOf(val).Elem().Kind()
|
elemKind := reflect.TypeOf(val).Elem().Kind()
|
||||||
if elemKind != reflect.Struct {
|
if elemKind != reflect.Struct {
|
||||||
return ErrNotPointerToStruct
|
return ErrInvalidType
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,7 +153,7 @@ func (d *Decoder) GetFunc(name string) (ScriptFunc, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return func(ctx context.Context, opts ...interp.RunnerOption) error {
|
return func(ctx context.Context, opts ...interp.RunnerOption) error {
|
||||||
sub := d.Runner.Subshell()
|
sub := d.runner.Subshell()
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
opt(sub)
|
opt(sub)
|
||||||
}
|
}
|
||||||
@ -182,13 +162,9 @@ func (d *Decoder) GetFunc(name string) (ScriptFunc, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Decoder) getFunc(name string) *syntax.Stmt {
|
func (d *Decoder) getFunc(name string) *syntax.Stmt {
|
||||||
names, err := overrides.Resolve(d.info, overrides.DefaultOpts.WithName(name))
|
names := d.genPossibleNames(name)
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, fnName := range names {
|
for _, fnName := range names {
|
||||||
fn, ok := d.Runner.Funcs[fnName]
|
fn, ok := d.runner.Funcs[fnName]
|
||||||
if ok {
|
if ok {
|
||||||
return fn
|
return fn
|
||||||
}
|
}
|
||||||
@ -199,17 +175,13 @@ func (d *Decoder) getFunc(name string) *syntax.Stmt {
|
|||||||
// getVar gets a variable based on its name, taking into account
|
// getVar gets a variable based on its name, taking into account
|
||||||
// override variables and nameref variables.
|
// override variables and nameref variables.
|
||||||
func (d *Decoder) getVar(name string) *expand.Variable {
|
func (d *Decoder) getVar(name string) *expand.Variable {
|
||||||
names, err := overrides.Resolve(d.info, overrides.DefaultOpts.WithName(name))
|
names := d.genPossibleNames(name)
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, varName := range names {
|
for _, varName := range names {
|
||||||
val, ok := d.Runner.Vars[varName]
|
val, ok := d.runner.Vars[varName]
|
||||||
if ok {
|
if ok {
|
||||||
// Resolve nameref variables
|
// Resolve nameref variables
|
||||||
_, resolved := val.Resolve(expand.FuncEnviron(func(s string) string {
|
_, 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 val.String()
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
@ -221,3 +193,43 @@ func (d *Decoder) getVar(name string) *expand.Variable {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// genPossibleNames generates a slice of the possible names that
|
||||||
|
// could be used in the order that they should be checked
|
||||||
|
func (d *Decoder) genPossibleNames(name string) []string {
|
||||||
|
if !d.Overrides {
|
||||||
|
return []string{name}
|
||||||
|
}
|
||||||
|
|
||||||
|
architectures := []string{runtime.GOARCH}
|
||||||
|
|
||||||
|
if runtime.GOARCH == "arm" {
|
||||||
|
// More specific goes first
|
||||||
|
architectures[0] = cpu.ARMVariant()
|
||||||
|
architectures = append(architectures, "arm")
|
||||||
|
}
|
||||||
|
|
||||||
|
distros := []string{d.info.ID}
|
||||||
|
if d.LikeDistros {
|
||||||
|
distros = append(distros, d.info.Like...)
|
||||||
|
}
|
||||||
|
|
||||||
|
var out []string
|
||||||
|
for _, arch := range architectures {
|
||||||
|
for _, distro := range distros {
|
||||||
|
out = append(
|
||||||
|
out,
|
||||||
|
fmt.Sprintf("%s_%s_%s", name, arch, distro),
|
||||||
|
fmt.Sprintf("%s_%s", name, distro),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
out = append(out, fmt.Sprintf("%s_%s", name, arch))
|
||||||
|
}
|
||||||
|
out = append(out, name)
|
||||||
|
|
||||||
|
for index, item := range out {
|
||||||
|
out[index] = strings.ReplaceAll(item, "-", "_")
|
||||||
|
}
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
@ -1,224 +0,0 @@
|
|||||||
/*
|
|
||||||
* LURE - Linux User REpository
|
|
||||||
* Copyright (C) 2023 Elara 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 decoder_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"os"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"lure.sh/lure/internal/shutils/decoder"
|
|
||||||
"lure.sh/lure/pkg/distro"
|
|
||||||
"mvdan.cc/sh/v3/interp"
|
|
||||||
"mvdan.cc/sh/v3/syntax"
|
|
||||||
)
|
|
||||||
|
|
||||||
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"`
|
|
||||||
}
|
|
||||||
|
|
||||||
const testScript = `
|
|
||||||
name='test'
|
|
||||||
version='0.0.1'
|
|
||||||
release=1
|
|
||||||
epoch=2
|
|
||||||
desc="Test package"
|
|
||||||
homepage='https://lure.arsenm.dev'
|
|
||||||
maintainer='Arsen Musayelyan <arsen@arsenm.dev>'
|
|
||||||
architectures=('arm64' 'amd64')
|
|
||||||
license=('GPL-3.0-or-later')
|
|
||||||
provides=('test')
|
|
||||||
conflicts=('test')
|
|
||||||
replaces=('test-old')
|
|
||||||
replaces_test_os=('test-legacy')
|
|
||||||
|
|
||||||
deps=('sudo')
|
|
||||||
|
|
||||||
build_deps=('golang')
|
|
||||||
build_deps_arch=('go')
|
|
||||||
|
|
||||||
test() {
|
|
||||||
echo "Test"
|
|
||||||
}
|
|
||||||
|
|
||||||
package() {
|
|
||||||
install-binary test
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
var osRelease = &distro.OSRelease{
|
|
||||||
ID: "test_os",
|
|
||||||
Like: []string{"arch"},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDecodeVars(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
fl, err := syntax.NewParser().Parse(strings.NewReader(testScript), "lure.sh")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
runner, err := interp.New()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = runner.Run(ctx, fl)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
dec := decoder.New(osRelease, runner)
|
|
||||||
|
|
||||||
var bv BuildVars
|
|
||||||
err = dec.DecodeVars(&bv)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := BuildVars{
|
|
||||||
Name: "test",
|
|
||||||
Version: "0.0.1",
|
|
||||||
Release: 1,
|
|
||||||
Epoch: 2,
|
|
||||||
Description: "Test package",
|
|
||||||
Homepage: "https://lure.arsenm.dev",
|
|
||||||
Maintainer: "Arsen Musayelyan <arsen@arsenm.dev>",
|
|
||||||
Architectures: []string{"arm64", "amd64"},
|
|
||||||
Licenses: []string{"GPL-3.0-or-later"},
|
|
||||||
Provides: []string{"test"},
|
|
||||||
Conflicts: []string{"test"},
|
|
||||||
Replaces: []string{"test-legacy"},
|
|
||||||
Depends: []string{"sudo"},
|
|
||||||
BuildDepends: []string{"go"},
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(bv, expected) {
|
|
||||||
t.Errorf("Expected %v, got %v", expected, bv)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDecodeVarsMissing(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
const testScript = `
|
|
||||||
name='test'
|
|
||||||
epoch=2
|
|
||||||
desc="Test package"
|
|
||||||
homepage='https://lure.arsenm.dev'
|
|
||||||
maintainer='Arsen Musayelyan <arsen@arsenm.dev>'
|
|
||||||
architectures=('arm64' 'amd64')
|
|
||||||
license=('GPL-3.0-or-later')
|
|
||||||
provides=('test')
|
|
||||||
conflicts=('test')
|
|
||||||
replaces=('test-old')
|
|
||||||
replaces_test_os=('test-legacy')
|
|
||||||
|
|
||||||
deps=('sudo')
|
|
||||||
|
|
||||||
build_deps=('golang')
|
|
||||||
build_deps_arch=('go')
|
|
||||||
|
|
||||||
test() {
|
|
||||||
echo "Test"
|
|
||||||
}
|
|
||||||
|
|
||||||
package() {
|
|
||||||
install-binary test
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
fl, err := syntax.NewParser().Parse(strings.NewReader(testScript), "lure.sh")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
runner, err := interp.New()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = runner.Run(ctx, fl)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
dec := decoder.New(osRelease, runner)
|
|
||||||
|
|
||||||
var bv BuildVars
|
|
||||||
err = dec.DecodeVars(&bv)
|
|
||||||
|
|
||||||
var notFoundErr decoder.VarNotFoundError
|
|
||||||
if !errors.As(err, ¬FoundErr) {
|
|
||||||
t.Fatalf("Expected VarNotFoundError, got %T %v", err, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetFunc(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
fl, err := syntax.NewParser().Parse(strings.NewReader(testScript), "lure.sh")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
runner, err := interp.New()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = runner.Run(ctx, fl)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
dec := decoder.New(osRelease, runner)
|
|
||||||
fn, ok := dec.GetFunc("test")
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("Expected test() function to exist")
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := &bytes.Buffer{}
|
|
||||||
err = fn(ctx, interp.StdIO(os.Stdin, buf, buf))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if buf.String() != "Test\n" {
|
|
||||||
t.Fatalf(`Expected "Test\n", got %#v`, buf.String())
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* LURE - Linux User REpository
|
* LURE - Linux User REpository
|
||||||
* Copyright (C) 2023 Elara Musayelyan
|
* Copyright (C) 2022 Arsen Musayelyan
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -16,7 +16,7 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package handlers
|
package shutils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -39,25 +39,18 @@ type ExecFunc func(hc interp.HandlerContext, name string, args []string) error
|
|||||||
|
|
||||||
type ExecFuncs map[string]ExecFunc
|
type ExecFuncs map[string]ExecFunc
|
||||||
|
|
||||||
// ExecHandler returns a new ExecHandlerFunc that falls back to fallback
|
func (ef ExecFuncs) ExecHandler(ctx context.Context, args []string) error {
|
||||||
// if the command cannot be found in the map. If fallback is nil, the default
|
name := args[0]
|
||||||
// handler is used.
|
|
||||||
func (ef ExecFuncs) ExecHandler(fallback interp.ExecHandlerFunc) interp.ExecHandlerFunc {
|
|
||||||
return func(ctx context.Context, args []string) error {
|
|
||||||
name := args[0]
|
|
||||||
|
|
||||||
if fn, ok := ef[name]; ok {
|
if fn, ok := ef[name]; ok {
|
||||||
hctx := interp.HandlerCtx(ctx)
|
hctx := interp.HandlerCtx(ctx)
|
||||||
if len(args) > 1 {
|
if len(args) > 1 {
|
||||||
return fn(hctx, args[0], args[1:])
|
return fn(hctx, args[0], args[1:])
|
||||||
} else {
|
} else {
|
||||||
return fn(hctx, args[0], nil)
|
return fn(hctx, args[0], nil)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if fallback == nil {
|
|
||||||
fallback = interp.DefaultExecHandler(2 * time.Second)
|
|
||||||
}
|
|
||||||
return fallback(ctx, args)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defExec := interp.DefaultExecHandler(2 * time.Second)
|
||||||
|
return defExec(ctx, args)
|
||||||
}
|
}
|
@ -1,124 +0,0 @@
|
|||||||
/*
|
|
||||||
* LURE - Linux User REpository
|
|
||||||
* Copyright (C) 2023 Elara 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 handlers_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"lure.sh/lure/internal/shutils/handlers"
|
|
||||||
"lure.sh/lure/internal/shutils/decoder"
|
|
||||||
"lure.sh/lure/pkg/distro"
|
|
||||||
"mvdan.cc/sh/v3/interp"
|
|
||||||
"mvdan.cc/sh/v3/syntax"
|
|
||||||
)
|
|
||||||
|
|
||||||
const testScript = `
|
|
||||||
name='test'
|
|
||||||
version='0.0.1'
|
|
||||||
release=1
|
|
||||||
epoch=2
|
|
||||||
desc="Test package"
|
|
||||||
homepage='https://lure.sh'
|
|
||||||
maintainer='Elara Musayelyan <elara@elara.ws>'
|
|
||||||
architectures=('arm64' 'amd64')
|
|
||||||
license=('GPL-3.0-or-later')
|
|
||||||
provides=('test')
|
|
||||||
conflicts=('test')
|
|
||||||
replaces=('test-old')
|
|
||||||
replaces_test_os=('test-legacy')
|
|
||||||
|
|
||||||
deps=('sudo')
|
|
||||||
|
|
||||||
build_deps=('golang')
|
|
||||||
build_deps_arch=('go')
|
|
||||||
|
|
||||||
test() {
|
|
||||||
test-cmd "Hello, World"
|
|
||||||
test-fb
|
|
||||||
}
|
|
||||||
|
|
||||||
package() {
|
|
||||||
install-binary test
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
var osRelease = &distro.OSRelease{
|
|
||||||
ID: "test_os",
|
|
||||||
Like: []string{"arch"},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestExecFuncs(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
fl, err := syntax.NewParser().Parse(strings.NewReader(testScript), "lure.sh")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
runner, err := interp.New()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = runner.Run(ctx, fl)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
dec := decoder.New(osRelease, runner)
|
|
||||||
fn, ok := dec.GetFunc("test")
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("Expected test() function to exist")
|
|
||||||
}
|
|
||||||
|
|
||||||
eh := shutils.ExecFuncs{
|
|
||||||
"test-cmd": func(hc interp.HandlerContext, name string, args []string) error {
|
|
||||||
if name != "test-cmd" {
|
|
||||||
t.Errorf("Expected name to be 'test-cmd', got '%s'", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(args) < 1 {
|
|
||||||
t.Fatalf("Expected at least one argument, got %d", len(args))
|
|
||||||
}
|
|
||||||
|
|
||||||
if args[0] != "Hello, World" {
|
|
||||||
t.Errorf("Expected first argument to be 'Hello, World', got '%s'", args[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
fbInvoked := false
|
|
||||||
fbHandler := func(context.Context, []string) error {
|
|
||||||
fbInvoked = true
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
err = fn(ctx, interp.ExecHandler(eh.ExecHandler(fbHandler)))
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !fbInvoked {
|
|
||||||
t.Errorf("Expected fallback handler to be invoked")
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,114 +0,0 @@
|
|||||||
package handlers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"lure.sh/fakeroot"
|
|
||||||
"mvdan.cc/sh/v3/expand"
|
|
||||||
"mvdan.cc/sh/v3/interp"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FakerootExecHandler was extracted from github.com/mvdan/sh/interp/handler.go
|
|
||||||
// and modified to run commands in a fakeroot environent.
|
|
||||||
func FakerootExecHandler(killTimeout time.Duration) interp.ExecHandlerFunc {
|
|
||||||
return func(ctx context.Context, args []string) error {
|
|
||||||
hc := interp.HandlerCtx(ctx)
|
|
||||||
path, err := interp.LookPathDir(hc.Dir, hc.Env, args[0])
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintln(hc.Stderr, err)
|
|
||||||
return interp.NewExitStatus(127)
|
|
||||||
}
|
|
||||||
cmd := &exec.Cmd{
|
|
||||||
Path: path,
|
|
||||||
Args: args,
|
|
||||||
Env: execEnv(hc.Env),
|
|
||||||
Dir: hc.Dir,
|
|
||||||
Stdin: hc.Stdin,
|
|
||||||
Stdout: hc.Stdout,
|
|
||||||
Stderr: hc.Stderr,
|
|
||||||
}
|
|
||||||
|
|
||||||
err = fakeroot.Apply(cmd)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = cmd.Start()
|
|
||||||
if err == nil {
|
|
||||||
if done := ctx.Done(); done != nil {
|
|
||||||
go func() {
|
|
||||||
<-done
|
|
||||||
|
|
||||||
if killTimeout <= 0 || runtime.GOOS == "windows" {
|
|
||||||
_ = cmd.Process.Signal(os.Kill)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: don't temporarily leak this goroutine
|
|
||||||
// if the program stops itself with the
|
|
||||||
// interrupt.
|
|
||||||
go func() {
|
|
||||||
time.Sleep(killTimeout)
|
|
||||||
_ = cmd.Process.Signal(os.Kill)
|
|
||||||
}()
|
|
||||||
_ = cmd.Process.Signal(os.Interrupt)
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
err = cmd.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
switch x := err.(type) {
|
|
||||||
case *exec.ExitError:
|
|
||||||
// started, but errored - default to 1 if OS
|
|
||||||
// doesn't have exit statuses
|
|
||||||
if status, ok := x.Sys().(syscall.WaitStatus); ok {
|
|
||||||
if status.Signaled() {
|
|
||||||
if ctx.Err() != nil {
|
|
||||||
return ctx.Err()
|
|
||||||
}
|
|
||||||
return interp.NewExitStatus(uint8(128 + status.Signal()))
|
|
||||||
}
|
|
||||||
return interp.NewExitStatus(uint8(status.ExitStatus()))
|
|
||||||
}
|
|
||||||
return interp.NewExitStatus(1)
|
|
||||||
case *exec.Error:
|
|
||||||
// did not start
|
|
||||||
fmt.Fprintf(hc.Stderr, "%v\n", err)
|
|
||||||
return interp.NewExitStatus(127)
|
|
||||||
default:
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// execEnv was extracted from github.com/mvdan/sh/interp/vars.go
|
|
||||||
func execEnv(env expand.Environ) []string {
|
|
||||||
list := make([]string, 0, 64)
|
|
||||||
env.Each(func(name string, vr expand.Variable) bool {
|
|
||||||
if !vr.IsSet() {
|
|
||||||
// If a variable is set globally but unset in the
|
|
||||||
// runner, we need to ensure it's not part of the final
|
|
||||||
// list. Seems like zeroing the element is enough.
|
|
||||||
// This is a linear search, but this scenario should be
|
|
||||||
// rare, and the number of variables shouldn't be large.
|
|
||||||
for i, kv := range list {
|
|
||||||
if strings.HasPrefix(kv, name+"=") {
|
|
||||||
list[i] = ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if vr.Exported && vr.Kind == expand.String {
|
|
||||||
list = append(list, name+"="+vr.String())
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
return list
|
|
||||||
}
|
|
@ -1,58 +0,0 @@
|
|||||||
/*
|
|
||||||
* LURE - Linux User REpository
|
|
||||||
* Copyright (C) 2023 Elara 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 handlers_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"lure.sh/lure/internal/shutils/handlers"
|
|
||||||
"mvdan.cc/sh/v3/interp"
|
|
||||||
"mvdan.cc/sh/v3/syntax"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNopExec(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
fl, err := syntax.NewParser().Parse(strings.NewReader(`/bin/echo test`), "lure.sh")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := &bytes.Buffer{}
|
|
||||||
runner, err := interp.New(
|
|
||||||
interp.ExecHandler(handlers.NopExec),
|
|
||||||
interp.StdIO(os.Stdin, buf, buf),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = runner.Run(ctx, fl)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if buf.String() != "" {
|
|
||||||
t.Fatalf("Expected empty string, got %#v", buf.String())
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,80 +0,0 @@
|
|||||||
/*
|
|
||||||
* LURE - Linux User REpository
|
|
||||||
* Copyright (C) 2023 Elara 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 handlers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"io"
|
|
||||||
"io/fs"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/exp/slices"
|
|
||||||
"mvdan.cc/sh/v3/interp"
|
|
||||||
)
|
|
||||||
|
|
||||||
func RestrictedReadDir(allowedPrefixes ...string) interp.ReadDirHandlerFunc {
|
|
||||||
return func(ctx context.Context, s string) ([]fs.FileInfo, error) {
|
|
||||||
path := filepath.Clean(s)
|
|
||||||
for _, allowedPrefix := range allowedPrefixes {
|
|
||||||
if strings.HasPrefix(path, allowedPrefix) {
|
|
||||||
return interp.DefaultReadDirHandler()(ctx, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, fs.ErrNotExist
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func RestrictedStat(allowedPrefixes ...string) interp.StatHandlerFunc {
|
|
||||||
return func(ctx context.Context, s string, b bool) (fs.FileInfo, error) {
|
|
||||||
path := filepath.Clean(s)
|
|
||||||
for _, allowedPrefix := range allowedPrefixes {
|
|
||||||
if strings.HasPrefix(path, allowedPrefix) {
|
|
||||||
return interp.DefaultStatHandler()(ctx, s, b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, fs.ErrNotExist
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func RestrictedOpen(allowedPrefixes ...string) interp.OpenHandlerFunc {
|
|
||||||
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(path, allowedPrefix) {
|
|
||||||
return interp.DefaultOpenHandler()(ctx, s, i, fm)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return NopRWC{}, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func RestrictedExec(allowedCmds ...string) interp.ExecHandlerFunc {
|
|
||||||
return func(ctx context.Context, args []string) error {
|
|
||||||
if slices.Contains(allowedCmds, args[0]) {
|
|
||||||
return interp.DefaultExecHandler(2*time.Second)(ctx, args)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* LURE - Linux User REpository
|
* LURE - Linux User REpository
|
||||||
* Copyright (C) 2023 Elara Musayelyan
|
* Copyright (C) 2022 Arsen Musayelyan
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -16,7 +16,7 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package handlers
|
package shutils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -46,8 +46,8 @@ func (NopRWC) Read([]byte) (int, error) {
|
|||||||
return 0, io.EOF
|
return 0, io.EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
func (NopRWC) Write(b []byte) (int, error) {
|
func (NopRWC) Write([]byte) (int, error) {
|
||||||
return len(b), nil
|
return 0, io.EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
func (NopRWC) Close() error {
|
func (NopRWC) Close() error {
|
@ -1,155 +0,0 @@
|
|||||||
[[translation]]
|
|
||||||
id = 1228660974
|
|
||||||
value = 'Pulling repository'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 2779805870
|
|
||||||
value = 'Repository up to date'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 1433222829
|
|
||||||
value = 'Would you like to view the build script for'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 2470847050
|
|
||||||
value = 'Failed to prompt user to view build script'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 855659503
|
|
||||||
value = 'Would you still like to continue?'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 1997041569
|
|
||||||
value = 'User chose not to continue after reading script'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 2347700990
|
|
||||||
value = 'Building package'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 2105058868
|
|
||||||
value = 'Downloading sources'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 1884485082
|
|
||||||
value = 'Downloading source'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 1519177982
|
|
||||||
value = 'Error building package'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 2125220917
|
|
||||||
value = 'Choose which package(s) to install'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 812531604
|
|
||||||
value = 'Error prompting for choice of package'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 1040982801
|
|
||||||
value = 'Updating version'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 1014897988
|
|
||||||
value = 'Remove build dependencies?'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 2205430948
|
|
||||||
value = 'Installing build dependencies'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 2522710805
|
|
||||||
value = 'Installing dependencies'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 3602138206
|
|
||||||
value = 'Error installing package'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 2235794125
|
|
||||||
value = 'Would you like to remove build dependencies?'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 2562049386
|
|
||||||
value = "Your system's CPU architecture doesn't match this package. Do you want to build anyway?"
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 4006393493
|
|
||||||
value = 'The checksums array must be the same length as sources'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 3759891273
|
|
||||||
value = 'The package() function is required'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 1057080231
|
|
||||||
value = 'Executing package()'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 2687735200
|
|
||||||
value = 'Executing prepare()'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 535572372
|
|
||||||
value = 'Executing version()'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 436644691
|
|
||||||
value = 'Executing build()'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 1393316459
|
|
||||||
value = 'This package is already installed'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 1267660189
|
|
||||||
value = 'Source can be updated, updating if required'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 21753247
|
|
||||||
value = 'Source found in cache, linked to destination'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 257354570
|
|
||||||
value = 'Compressing package'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 2952487371
|
|
||||||
value = 'Building package metadata'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 3121791194
|
|
||||||
value = 'Running LURE as root is forbidden as it may cause catastrophic damage to your system'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 1256604213
|
|
||||||
value = 'Waiting for torrent metadata'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 432261354
|
|
||||||
value = 'Downloading torrent file'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 1579384326
|
|
||||||
value = 'name'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 3206337475
|
|
||||||
value = 'version'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 1810056261
|
|
||||||
value = 'new'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 1602912115
|
|
||||||
value = 'source'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 2363381545
|
|
||||||
value = 'type'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 3419504365
|
|
||||||
value = 'downloader'
|
|
@ -1,151 +0,0 @@
|
|||||||
[[translation]]
|
|
||||||
id = 1228660974
|
|
||||||
value = 'Скачивание репозитория'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 2779805870
|
|
||||||
value = 'Репозиторий уже обновлен'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 1433222829
|
|
||||||
value = 'Показать скрипт для пакета'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 2470847050
|
|
||||||
value = 'Не удалось предложить просмотреть скрипт'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 855659503
|
|
||||||
value = 'Продолжить?'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 1997041569
|
|
||||||
value = 'Пользователь решил не продолжать после просмотра скрипта'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 2347700990
|
|
||||||
value = 'Сборка пакета'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 2105058868
|
|
||||||
value = 'Скачивание файлов'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 1884485082
|
|
||||||
value = 'Скачивание источника'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 1519177982
|
|
||||||
value = 'Ошибка при сборке пакета'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 2125220917
|
|
||||||
value = 'Выберите, какие пакеты установить'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 812531604
|
|
||||||
value = 'Ошибка при запросе выбора пакета'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 1040982801
|
|
||||||
value = 'Обновление версии'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 2235794125
|
|
||||||
value = 'Удалить зависимости сборки?'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 2205430948
|
|
||||||
value = 'Установка зависимостей сборки'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 2522710805
|
|
||||||
value = 'Установка зависимостей'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 3602138206
|
|
||||||
value = 'Ошибка при установке пакета'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 1057080231
|
|
||||||
value = 'Вызов функции package()'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 2687735200
|
|
||||||
value = 'Вызов функции prepare()'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 535572372
|
|
||||||
value = 'Вызов функции version()'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 436644691
|
|
||||||
value = 'Вызов функции build()'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 2562049386
|
|
||||||
value = "Архитектура процессора вашей системы не соответствует этому пакету. Продолжать несмотря на это?"
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 3759891273
|
|
||||||
value = 'Функция package() необходима'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 4006393493
|
|
||||||
value = 'Массив checksums должен быть той же длины, что и sources'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 1393316459
|
|
||||||
value = 'Этот пакет уже установлен'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 1267660189
|
|
||||||
value = 'Источник может быть обновлен, если требуется, обновляем'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 21753247
|
|
||||||
value = 'Источник найден в кэше'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 257354570
|
|
||||||
value = 'Сжатие пакета'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 2952487371
|
|
||||||
value = 'Создание метаданных пакета'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 3121791194
|
|
||||||
value = 'Запуск LURE от имени root запрещен, так как это может привести к катастрофическому повреждению вашей системы'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 1256604213
|
|
||||||
value = 'Ожидание метаданных торрента'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 432261354
|
|
||||||
value = 'Скачивание торрент-файла'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 1579384326
|
|
||||||
value = 'название'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 3206337475
|
|
||||||
value = 'версия'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 1810056261
|
|
||||||
value = 'новая'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 1602912115
|
|
||||||
value = 'источник'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 2363381545
|
|
||||||
value = 'вид'
|
|
||||||
|
|
||||||
[[translation]]
|
|
||||||
id = 3419504365
|
|
||||||
value = 'протокол-скачивание'
|
|
@ -1,56 +0,0 @@
|
|||||||
/*
|
|
||||||
* LURE - Linux User REpository
|
|
||||||
* Copyright (C) 2023 Elara 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 translations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"embed"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"go.elara.ws/logger"
|
|
||||||
"lure.sh/lure/pkg/loggerctx"
|
|
||||||
"go.elara.ws/translate"
|
|
||||||
"golang.org/x/text/language"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed files
|
|
||||||
var translationFS embed.FS
|
|
||||||
|
|
||||||
var (
|
|
||||||
mu sync.Mutex
|
|
||||||
translator *translate.Translator
|
|
||||||
)
|
|
||||||
|
|
||||||
func Translator(ctx context.Context) *translate.Translator {
|
|
||||||
mu.Lock()
|
|
||||||
defer mu.Unlock()
|
|
||||||
log := loggerctx.From(ctx)
|
|
||||||
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(ctx context.Context, l logger.Logger, lang language.Tag) *translate.TranslatedLogger {
|
|
||||||
return translate.NewLogger(l, *Translator(ctx), lang)
|
|
||||||
}
|
|
@ -1,70 +0,0 @@
|
|||||||
/*
|
|
||||||
* LURE - Linux User REpository
|
|
||||||
* Copyright (C) 2023 Elara 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 types
|
|
||||||
|
|
||||||
import "lure.sh/lure/pkg/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"`
|
|
||||||
OptDepends []string `sh:"opt_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
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
/*
|
|
||||||
* LURE - Linux User REpository
|
|
||||||
* Copyright (C) 2023 Elara 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 types
|
|
||||||
|
|
||||||
// Config represents the LURE configuration file
|
|
||||||
type Config struct {
|
|
||||||
RootCmd string `toml:"rootCmd"`
|
|
||||||
PagerStyle string `toml:"pagerStyle"`
|
|
||||||
IgnorePkgUpdates []string `toml:"ignorePkgUpdates"`
|
|
||||||
Repos []Repo `toml:"repo"`
|
|
||||||
Unsafe Unsafe `toml:"unsafe"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Repo represents a LURE repo within a configuration file
|
|
||||||
type Repo struct {
|
|
||||||
Name string `toml:"name"`
|
|
||||||
URL string `toml:"url"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Unsafe struct {
|
|
||||||
AllowRunAsRoot bool `toml:"allowRunAsRoot"`
|
|
||||||
}
|
|
@ -1,26 +0,0 @@
|
|||||||
/*
|
|
||||||
* LURE - Linux User REpository
|
|
||||||
* Copyright (C) 2023 Elara 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 types
|
|
||||||
|
|
||||||
// RepoConfig represents a LURE repo's lure-repo.toml file.
|
|
||||||
type RepoConfig struct {
|
|
||||||
Repo struct {
|
|
||||||
MinVersion string `toml:"minVersion"`
|
|
||||||
}
|
|
||||||
}
|
|
95
list.go
95
list.go
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* LURE - Linux User REpository
|
* LURE - Linux User REpository
|
||||||
* Copyright (C) 2023 Elara Musayelyan
|
* Copyright (C) 2022 Arsen Musayelyan
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -22,87 +22,28 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"lure.sh/lure/internal/config"
|
"go.arsenm.dev/lure/distro"
|
||||||
"lure.sh/lure/internal/db"
|
|
||||||
"lure.sh/lure/pkg/loggerctx"
|
|
||||||
"lure.sh/lure/pkg/manager"
|
|
||||||
"lure.sh/lure/pkg/repos"
|
|
||||||
"golang.org/x/exp/slices"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var listCmd = &cli.Command{
|
func listCmd(c *cli.Context) error {
|
||||||
Name: "list",
|
info, err := distro.ParseOSRelease(c.Context)
|
||||||
Usage: "List LURE repo packages",
|
if err != nil {
|
||||||
Aliases: []string{"ls"},
|
log.Fatal("Error parsing os-release").Err(err).Send()
|
||||||
Flags: []cli.Flag{
|
}
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "installed",
|
|
||||||
Aliases: []string{"I"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Action: func(c *cli.Context) error {
|
|
||||||
ctx := c.Context
|
|
||||||
log := loggerctx.From(ctx)
|
|
||||||
|
|
||||||
err := repos.Pull(ctx, config.Config(ctx).Repos)
|
pkgs, err := findPkg("*")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error finding packages").Err(err).Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, script := range pkgs {
|
||||||
|
vars, err := getBuildVars(c.Context, script, info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Error pulling repositories").Err(err).Send()
|
log.Fatal("Error getting build variables").Err(err).Send()
|
||||||
}
|
}
|
||||||
|
|
||||||
where := "true"
|
fmt.Println(vars.Name, vars.Version)
|
||||||
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(ctx, where, args...)
|
return nil
|
||||||
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 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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for result.Next() {
|
|
||||||
var pkg db.Package
|
|
||||||
err := result.StructScan(&pkg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if slices.Contains(config.Config(ctx).IgnorePkgUpdates, pkg.Name) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
185
main.go
185
main.go
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* LURE - Linux User REpository
|
* LURE - Linux User REpository
|
||||||
* Copyright (C) 2023 Elara Musayelyan
|
* Copyright (C) 2022 Arsen Musayelyan
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -22,94 +22,127 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"strings"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/mattn/go-isatty"
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"go.elara.ws/logger"
|
"go.arsenm.dev/logger"
|
||||||
"lure.sh/lure/internal/config"
|
|
||||||
"lure.sh/lure/internal/db"
|
|
||||||
"lure.sh/lure/internal/translations"
|
|
||||||
"lure.sh/lure/pkg/loggerctx"
|
|
||||||
"lure.sh/lure/pkg/manager"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var app = &cli.App{
|
var log = logger.NewPretty(os.Stderr)
|
||||||
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,
|
|
||||||
genCmd,
|
|
||||||
helperCmd,
|
|
||||||
versionCmd,
|
|
||||||
},
|
|
||||||
Before: func(c *cli.Context) error {
|
|
||||||
ctx := c.Context
|
|
||||||
log := loggerctx.From(ctx)
|
|
||||||
|
|
||||||
cmd := c.Args().First()
|
|
||||||
if cmd != "helper" && !config.Config(ctx).Unsafe.AllowRunAsRoot && os.Geteuid() == 0 {
|
|
||||||
log.Fatal("Running LURE as root is forbidden as it may cause catastrophic damage to your system").Send()
|
|
||||||
}
|
|
||||||
|
|
||||||
if trimmed := strings.TrimSpace(c.String("pm-args")); trimmed != "" {
|
|
||||||
args := strings.Split(trimmed, " ")
|
|
||||||
manager.Args = append(manager.Args, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
After: func(ctx *cli.Context) error {
|
|
||||||
return db.Close()
|
|
||||||
},
|
|
||||||
EnableBashCompletion: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
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() {
|
func main() {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
log := translations.NewLogger(ctx, logger.NewCLI(os.Stderr), config.Language(ctx))
|
|
||||||
ctx = loggerctx.With(ctx, log)
|
|
||||||
|
|
||||||
// Set the root command to the one set in the LURE config
|
|
||||||
manager.DefaultRootCmd = config.Config(ctx).RootCmd
|
|
||||||
|
|
||||||
ctx, cancel := signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM)
|
ctx, cancel := signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
go func() {
|
||||||
|
<-ctx.Done()
|
||||||
|
// Exit the program after a maximum of 200ms
|
||||||
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
os.Exit(0)
|
||||||
|
}()
|
||||||
|
|
||||||
|
app := &cli.App{
|
||||||
|
Name: "lure",
|
||||||
|
Usage: "Linux User REpository",
|
||||||
|
Commands: []*cli.Command{
|
||||||
|
{
|
||||||
|
Name: "install",
|
||||||
|
Usage: "Install a new package",
|
||||||
|
Aliases: []string{"in"},
|
||||||
|
Action: installCmd,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "remove",
|
||||||
|
Usage: "Remove an installed package",
|
||||||
|
Aliases: []string{"rm"},
|
||||||
|
Action: removeCmd,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "upgrade",
|
||||||
|
Usage: "Upgrade all installed packages",
|
||||||
|
Aliases: []string{"up"},
|
||||||
|
Action: upgradeCmd,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "info",
|
||||||
|
Usage: "Print information about a package",
|
||||||
|
Action: infoCmd,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
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: "version",
|
||||||
|
Usage: "Display the current LURE version and exit",
|
||||||
|
Action: displayVersion,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
err := app.RunContext(ctx, os.Args)
|
err := app.RunContext(ctx, os.Args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Error while running app").Err(err).Send()
|
log.Error("Error while running app").Err(err).Send()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func displayVersion(c *cli.Context) error {
|
||||||
|
print(version)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* LURE - Linux User REpository
|
* LURE - Linux User REpository
|
||||||
* Copyright (C) 2023 Elara Musayelyan
|
* Copyright (C) 2022 Arsen Musayelyan
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -112,8 +112,15 @@ func (a *APK) UpgradeAll(opts *Opts) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *APK) ListInstalled(opts *Opts) (map[string]string, error) {
|
func (a *APK) ListInstalled(opts *Opts) (map[string]string, error) {
|
||||||
|
opts = ensureOpts(opts)
|
||||||
out := map[string]string{}
|
out := map[string]string{}
|
||||||
cmd := exec.Command("apk", "list", "-I")
|
|
||||||
|
var cmd *exec.Cmd
|
||||||
|
if opts.AsRoot {
|
||||||
|
cmd = exec.Command(getRootCmd(a.rootCmd), "apk", "list", "-I")
|
||||||
|
} else {
|
||||||
|
cmd = exec.Command("apk", "list", "-I")
|
||||||
|
}
|
||||||
|
|
||||||
stdout, err := cmd.StdoutPipe()
|
stdout, err := cmd.StdoutPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -152,7 +159,6 @@ func (a *APK) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd {
|
|||||||
var cmd *exec.Cmd
|
var cmd *exec.Cmd
|
||||||
if opts.AsRoot {
|
if opts.AsRoot {
|
||||||
cmd = exec.Command(getRootCmd(a.rootCmd), mgrCmd)
|
cmd = exec.Command(getRootCmd(a.rootCmd), mgrCmd)
|
||||||
cmd.Args = append(cmd.Args, opts.Args...)
|
|
||||||
cmd.Args = append(cmd.Args, args...)
|
cmd.Args = append(cmd.Args, args...)
|
||||||
} else {
|
} else {
|
||||||
cmd = exec.Command(mgrCmd, args...)
|
cmd = exec.Command(mgrCmd, args...)
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* LURE - Linux User REpository
|
* LURE - Linux User REpository
|
||||||
* Copyright (C) 2023 Elara Musayelyan
|
* Copyright (C) 2022 Arsen Musayelyan
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -104,8 +104,15 @@ func (a *APT) UpgradeAll(opts *Opts) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *APT) ListInstalled(opts *Opts) (map[string]string, error) {
|
func (a *APT) ListInstalled(opts *Opts) (map[string]string, error) {
|
||||||
|
opts = ensureOpts(opts)
|
||||||
out := map[string]string{}
|
out := map[string]string{}
|
||||||
cmd := exec.Command("dpkg-query", "-f", "${Package}\u200b${Version}\\n", "-W")
|
|
||||||
|
var cmd *exec.Cmd
|
||||||
|
if opts.AsRoot {
|
||||||
|
cmd = exec.Command(getRootCmd(a.rootCmd), "dpkg-query", "-f", "${Package}\u200b${Version}\\n", "-W")
|
||||||
|
} else {
|
||||||
|
cmd = exec.Command("dpkg-query", "-f", "${Package}\u200b${Version}\\n", "-W")
|
||||||
|
}
|
||||||
|
|
||||||
stdout, err := cmd.StdoutPipe()
|
stdout, err := cmd.StdoutPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -138,7 +145,6 @@ func (a *APT) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd {
|
|||||||
var cmd *exec.Cmd
|
var cmd *exec.Cmd
|
||||||
if opts.AsRoot {
|
if opts.AsRoot {
|
||||||
cmd = exec.Command(getRootCmd(a.rootCmd), mgrCmd)
|
cmd = exec.Command(getRootCmd(a.rootCmd), mgrCmd)
|
||||||
cmd.Args = append(cmd.Args, opts.Args...)
|
|
||||||
cmd.Args = append(cmd.Args, args...)
|
cmd.Args = append(cmd.Args, args...)
|
||||||
} else {
|
} else {
|
||||||
cmd = exec.Command(mgrCmd, args...)
|
cmd = exec.Command(mgrCmd, args...)
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* LURE - Linux User REpository
|
* LURE - Linux User REpository
|
||||||
* Copyright (C) 2023 Elara Musayelyan
|
* Copyright (C) 2022 Arsen Musayelyan
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -60,7 +60,7 @@ func (d *DNF) Sync(opts *Opts) error {
|
|||||||
|
|
||||||
func (d *DNF) Install(opts *Opts, pkgs ...string) error {
|
func (d *DNF) Install(opts *Opts, pkgs ...string) error {
|
||||||
opts = ensureOpts(opts)
|
opts = ensureOpts(opts)
|
||||||
cmd := d.getCmd(opts, "dnf", "install", "--allowerasing")
|
cmd := d.getCmd(opts, "dnf", "install")
|
||||||
cmd.Args = append(cmd.Args, pkgs...)
|
cmd.Args = append(cmd.Args, pkgs...)
|
||||||
setCmdEnv(cmd)
|
setCmdEnv(cmd)
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
@ -111,8 +111,15 @@ func (d *DNF) UpgradeAll(opts *Opts) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *DNF) ListInstalled(opts *Opts) (map[string]string, error) {
|
func (d *DNF) ListInstalled(opts *Opts) (map[string]string, error) {
|
||||||
|
opts = ensureOpts(opts)
|
||||||
out := map[string]string{}
|
out := map[string]string{}
|
||||||
cmd := exec.Command("rpm", "-qa", "--queryformat", "%{NAME}\u200b%|EPOCH?{%{EPOCH}:}:{}|%{VERSION}-%{RELEASE}\\n")
|
|
||||||
|
var cmd *exec.Cmd
|
||||||
|
if opts.AsRoot {
|
||||||
|
cmd = exec.Command(getRootCmd(d.rootCmd), "rpm", "-qa", "--queryformat", "%{NAME}\u200b%|EPOCH?{%{EPOCH}:}:{}|%{VERSION}-%{RELEASE}\\n")
|
||||||
|
} else {
|
||||||
|
cmd = exec.Command("rpm", "-qa", "--queryformat", "%{NAME}\u200b%|EPOCH?{%{EPOCH}:}:{}|%{VERSION}-%{RELEASE}\\n")
|
||||||
|
}
|
||||||
|
|
||||||
stdout, err := cmd.StdoutPipe()
|
stdout, err := cmd.StdoutPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -146,7 +153,6 @@ func (d *DNF) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd {
|
|||||||
var cmd *exec.Cmd
|
var cmd *exec.Cmd
|
||||||
if opts.AsRoot {
|
if opts.AsRoot {
|
||||||
cmd = exec.Command(getRootCmd(d.rootCmd), mgrCmd)
|
cmd = exec.Command(getRootCmd(d.rootCmd), mgrCmd)
|
||||||
cmd.Args = append(cmd.Args, opts.Args...)
|
|
||||||
cmd.Args = append(cmd.Args, args...)
|
cmd.Args = append(cmd.Args, args...)
|
||||||
} else {
|
} else {
|
||||||
cmd = exec.Command(mgrCmd, args...)
|
cmd = exec.Command(mgrCmd, args...)
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* LURE - Linux User REpository
|
* LURE - Linux User REpository
|
||||||
* Copyright (C) 2023 Elara Musayelyan
|
* Copyright (C) 2022 Arsen Musayelyan
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -23,12 +23,9 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Args []string
|
|
||||||
|
|
||||||
type Opts struct {
|
type Opts struct {
|
||||||
AsRoot bool
|
AsRoot bool
|
||||||
NoConfirm bool
|
NoConfirm bool
|
||||||
Args []string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var DefaultOpts = &Opts{
|
var DefaultOpts = &Opts{
|
||||||
@ -117,8 +114,7 @@ func setCmdEnv(cmd *exec.Cmd) {
|
|||||||
|
|
||||||
func ensureOpts(opts *Opts) *Opts {
|
func ensureOpts(opts *Opts) *Opts {
|
||||||
if opts == nil {
|
if opts == nil {
|
||||||
opts = DefaultOpts
|
return DefaultOpts
|
||||||
}
|
}
|
||||||
opts.Args = append(opts.Args, Args...)
|
|
||||||
return opts
|
return opts
|
||||||
}
|
}
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* LURE - Linux User REpository
|
* LURE - Linux User REpository
|
||||||
* Copyright (C) 2023 Elara Musayelyan
|
* Copyright (C) 2022 Arsen Musayelyan
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -111,8 +111,15 @@ func (p *Pacman) UpgradeAll(opts *Opts) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *Pacman) ListInstalled(opts *Opts) (map[string]string, error) {
|
func (p *Pacman) ListInstalled(opts *Opts) (map[string]string, error) {
|
||||||
|
opts = ensureOpts(opts)
|
||||||
out := map[string]string{}
|
out := map[string]string{}
|
||||||
cmd := exec.Command("pacman", "-Q")
|
|
||||||
|
var cmd *exec.Cmd
|
||||||
|
if opts.AsRoot {
|
||||||
|
cmd = exec.Command(getRootCmd(p.rootCmd), "pacman", "-Q")
|
||||||
|
} else {
|
||||||
|
cmd = exec.Command("pacman", "-Q")
|
||||||
|
}
|
||||||
|
|
||||||
stdout, err := cmd.StdoutPipe()
|
stdout, err := cmd.StdoutPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -145,7 +152,6 @@ func (p *Pacman) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd {
|
|||||||
var cmd *exec.Cmd
|
var cmd *exec.Cmd
|
||||||
if opts.AsRoot {
|
if opts.AsRoot {
|
||||||
cmd = exec.Command(getRootCmd(p.rootCmd), mgrCmd)
|
cmd = exec.Command(getRootCmd(p.rootCmd), mgrCmd)
|
||||||
cmd.Args = append(cmd.Args, opts.Args...)
|
|
||||||
cmd.Args = append(cmd.Args, args...)
|
cmd.Args = append(cmd.Args, args...)
|
||||||
} else {
|
} else {
|
||||||
cmd = exec.Command(mgrCmd, args...)
|
cmd = exec.Command(mgrCmd, args...)
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* LURE - Linux User REpository
|
* LURE - Linux User REpository
|
||||||
* Copyright (C) 2023 Elara Musayelyan
|
* Copyright (C) 2022 Arsen Musayelyan
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -60,7 +60,7 @@ func (y *YUM) Sync(opts *Opts) error {
|
|||||||
|
|
||||||
func (y *YUM) Install(opts *Opts, pkgs ...string) error {
|
func (y *YUM) Install(opts *Opts, pkgs ...string) error {
|
||||||
opts = ensureOpts(opts)
|
opts = ensureOpts(opts)
|
||||||
cmd := y.getCmd(opts, "yum", "install", "--allowerasing")
|
cmd := y.getCmd(opts, "yum", "install")
|
||||||
cmd.Args = append(cmd.Args, pkgs...)
|
cmd.Args = append(cmd.Args, pkgs...)
|
||||||
setCmdEnv(cmd)
|
setCmdEnv(cmd)
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
@ -111,8 +111,15 @@ func (y *YUM) UpgradeAll(opts *Opts) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (y *YUM) ListInstalled(opts *Opts) (map[string]string, error) {
|
func (y *YUM) ListInstalled(opts *Opts) (map[string]string, error) {
|
||||||
|
opts = ensureOpts(opts)
|
||||||
out := map[string]string{}
|
out := map[string]string{}
|
||||||
cmd := exec.Command("rpm", "-qa", "--queryformat", "%{NAME}\u200b%|EPOCH?{%{EPOCH}:}:{}|%{VERSION}-%{RELEASE}\\n")
|
|
||||||
|
var cmd *exec.Cmd
|
||||||
|
if opts.AsRoot {
|
||||||
|
cmd = exec.Command(getRootCmd(y.rootCmd), "rpm", "-qa", "--queryformat", "%{NAME}\u200b%|EPOCH?{%{EPOCH}:}:{}|%{VERSION}-%{RELEASE}\\n")
|
||||||
|
} else {
|
||||||
|
cmd = exec.Command("rpm", "-qa", "--queryformat", "%{NAME}\u200b%|EPOCH?{%{EPOCH}:}:{}|%{VERSION}-%{RELEASE}\\n")
|
||||||
|
}
|
||||||
|
|
||||||
stdout, err := cmd.StdoutPipe()
|
stdout, err := cmd.StdoutPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -146,7 +153,6 @@ func (y *YUM) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd {
|
|||||||
var cmd *exec.Cmd
|
var cmd *exec.Cmd
|
||||||
if opts.AsRoot {
|
if opts.AsRoot {
|
||||||
cmd = exec.Command(getRootCmd(y.rootCmd), mgrCmd)
|
cmd = exec.Command(getRootCmd(y.rootCmd), mgrCmd)
|
||||||
cmd.Args = append(cmd.Args, opts.Args...)
|
|
||||||
cmd.Args = append(cmd.Args, args...)
|
cmd.Args = append(cmd.Args, args...)
|
||||||
} else {
|
} else {
|
||||||
cmd = exec.Command(mgrCmd, args...)
|
cmd = exec.Command(mgrCmd, args...)
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* LURE - Linux User REpository
|
* LURE - Linux User REpository
|
||||||
* Copyright (C) 2023 Elara Musayelyan
|
* Copyright (C) 2022 Arsen Musayelyan
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -111,8 +111,15 @@ func (z *Zypper) UpgradeAll(opts *Opts) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (z *Zypper) ListInstalled(opts *Opts) (map[string]string, error) {
|
func (z *Zypper) ListInstalled(opts *Opts) (map[string]string, error) {
|
||||||
|
opts = ensureOpts(opts)
|
||||||
out := map[string]string{}
|
out := map[string]string{}
|
||||||
cmd := exec.Command("rpm", "-qa", "--queryformat", "%{NAME}\u200b%|EPOCH?{%{EPOCH}:}:{}|%{VERSION}-%{RELEASE}\\n")
|
|
||||||
|
var cmd *exec.Cmd
|
||||||
|
if opts.AsRoot {
|
||||||
|
cmd = exec.Command(getRootCmd(z.rootCmd), "rpm", "-qa", "--queryformat", "%{NAME}\u200b%|EPOCH?{%{EPOCH}:}:{}|%{VERSION}-%{RELEASE}\\n")
|
||||||
|
} else {
|
||||||
|
cmd = exec.Command("rpm", "-qa", "--queryformat", "%{NAME}\u200b%|EPOCH?{%{EPOCH}:}:{}|%{VERSION}-%{RELEASE}\\n")
|
||||||
|
}
|
||||||
|
|
||||||
stdout, err := cmd.StdoutPipe()
|
stdout, err := cmd.StdoutPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -146,7 +153,6 @@ func (z *Zypper) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd {
|
|||||||
var cmd *exec.Cmd
|
var cmd *exec.Cmd
|
||||||
if opts.AsRoot {
|
if opts.AsRoot {
|
||||||
cmd = exec.Command(getRootCmd(z.rootCmd), mgrCmd)
|
cmd = exec.Command(getRootCmd(z.rootCmd), mgrCmd)
|
||||||
cmd.Args = append(cmd.Args, opts.Args...)
|
|
||||||
cmd.Args = append(cmd.Args, args...)
|
cmd.Args = append(cmd.Args, args...)
|
||||||
} else {
|
} else {
|
||||||
cmd = exec.Command(mgrCmd, args...)
|
cmd = exec.Command(mgrCmd, args...)
|
@ -1,838 +0,0 @@
|
|||||||
/*
|
|
||||||
* LURE - Linux User REpository
|
|
||||||
* Copyright (C) 2023 Elara 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 build
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"slices"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
_ "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/goreleaser/nfpm/v2"
|
|
||||||
"github.com/goreleaser/nfpm/v2/files"
|
|
||||||
"lure.sh/lure/internal/cliutils"
|
|
||||||
"lure.sh/lure/internal/config"
|
|
||||||
"lure.sh/lure/internal/cpu"
|
|
||||||
"lure.sh/lure/internal/db"
|
|
||||||
"lure.sh/lure/internal/dl"
|
|
||||||
"lure.sh/lure/internal/shutils/decoder"
|
|
||||||
"lure.sh/lure/internal/shutils/handlers"
|
|
||||||
"lure.sh/lure/internal/shutils/helpers"
|
|
||||||
"lure.sh/lure/internal/types"
|
|
||||||
"lure.sh/lure/pkg/distro"
|
|
||||||
"lure.sh/lure/pkg/loggerctx"
|
|
||||||
"lure.sh/lure/pkg/manager"
|
|
||||||
"lure.sh/lure/pkg/repos"
|
|
||||||
"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) {
|
|
||||||
log := loggerctx.From(ctx)
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// The first pass is just used to get variable values and runs before
|
|
||||||
// the script is displayed, so it's restricted so as to prevent malicious
|
|
||||||
// code from executing.
|
|
||||||
vars, err := executeFirstPass(ctx, info, fl, opts.Script)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
dirs := getDirs(ctx, vars, opts.Script)
|
|
||||||
|
|
||||||
// If opts.Clean isn't set and we find the package already built,
|
|
||||||
// just return it rather than rebuilding
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ask the user if they'd like to see the build script
|
|
||||||
err = cliutils.PromptViewScript(ctx, opts.Script, vars.Name, config.Config(ctx).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()
|
|
||||||
|
|
||||||
// The second pass will be used to execute the actual code,
|
|
||||||
// so it's unrestricted. The script has already been displayed
|
|
||||||
// to the user by this point, so it should be safe
|
|
||||||
dec, err := executeSecondPass(ctx, info, fl, dirs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the installed packages on the system
|
|
||||||
installed, err := opts.Manager.ListInstalled(nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
cont, err := performChecks(ctx, vars, opts.Interactive, installed)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
} else if !cont {
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare the directories for building
|
|
||||||
err = prepareDirs(dirs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
buildDeps, err := installBuildDeps(ctx, vars, opts, installed)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = installOptDeps(ctx, vars, opts, installed)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
builtPaths, builtNames, repoDeps, err := buildLUREDeps(ctx, opts, vars)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("Downloading sources").Send()
|
|
||||||
|
|
||||||
err = getSources(ctx, dirs, 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()
|
|
||||||
|
|
||||||
pkgFormat := getPkgFormat(opts.Manager)
|
|
||||||
|
|
||||||
pkgInfo, err := buildPkgMetadata(vars, dirs, pkgFormat, append(repoDeps, builtNames...))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
packager, err := nfpm.Get(pkgFormat)
|
|
||||||
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(ctx, 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)
|
|
||||||
|
|
||||||
// Remove any duplicates from the pkgPaths and pkgNames.
|
|
||||||
// Duplicates can be introduced if several of the dependencies
|
|
||||||
// depend on the same packages.
|
|
||||||
pkgPaths = removeDuplicates(pkgPaths)
|
|
||||||
pkgNames = removeDuplicates(pkgNames)
|
|
||||||
|
|
||||||
return pkgPaths, pkgNames, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseScript parses the build script using the built-in bash implementation
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// executeFirstPass executes the parsed script in a restricted environment
|
|
||||||
// to extract the build variables without executing any actual code.
|
|
||||||
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})
|
|
||||||
|
|
||||||
runner, err := interp.New(
|
|
||||||
interp.Env(expand.ListEnviron(env...)),
|
|
||||||
interp.StdIO(os.Stdin, os.Stdout, os.Stderr),
|
|
||||||
interp.ExecHandler(helpers.Restricted.ExecHandler(handlers.NopExec)),
|
|
||||||
interp.ReadDirHandler(handlers.RestrictedReadDir(scriptDir)),
|
|
||||||
interp.StatHandler(handlers.RestrictedStat(scriptDir)),
|
|
||||||
interp.OpenHandler(handlers.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
|
|
||||||
}
|
|
||||||
|
|
||||||
// getDirs returns the appropriate directories for the script
|
|
||||||
func getDirs(ctx context.Context, vars *types.BuildVars, script string) types.Directories {
|
|
||||||
baseDir := filepath.Join(config.GetPaths(ctx).PkgsDir, vars.Name)
|
|
||||||
return types.Directories{
|
|
||||||
BaseDir: baseDir,
|
|
||||||
SrcDir: filepath.Join(baseDir, "src"),
|
|
||||||
PkgDir: filepath.Join(baseDir, "pkg"),
|
|
||||||
ScriptDir: filepath.Dir(script),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// executeSecondPass executes the build script for the second time, this time without any restrictions.
|
|
||||||
// It returns a decoder that can be used to retrieve functions and variables from the script.
|
|
||||||
func executeSecondPass(ctx context.Context, info *distro.OSRelease, fl *syntax.File, dirs types.Directories) (*decoder.Decoder, error) {
|
|
||||||
env := createBuildEnvVars(info, dirs)
|
|
||||||
|
|
||||||
fakeroot := handlers.FakerootExecHandler(2 * time.Second)
|
|
||||||
runner, err := interp.New(
|
|
||||||
interp.Env(expand.ListEnviron(env...)),
|
|
||||||
interp.StdIO(os.Stdin, os.Stdout, os.Stderr),
|
|
||||||
interp.ExecHandler(helpers.Helpers.ExecHandler(fakeroot)),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = runner.Run(ctx, fl)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return decoder.New(info, runner), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// prepareDirs prepares the directories for building.
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// performChecks checks various things on the system to ensure that the package can be installed.
|
|
||||||
func performChecks(ctx context.Context, vars *types.BuildVars, interactive bool, installed map[string]string) (bool, error) {
|
|
||||||
log := loggerctx.From(ctx)
|
|
||||||
if !cpu.IsCompatibleWith(cpu.Arch(), vars.Architectures) {
|
|
||||||
cont, err := cliutils.YesNoPrompt(ctx, "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
|
|
||||||
}
|
|
||||||
|
|
||||||
// installBuildDeps installs any build dependencies that aren't already installed and returns
|
|
||||||
// a slice containing the names of all the packages it installed.
|
|
||||||
func installBuildDeps(ctx context.Context, vars *types.BuildVars, opts types.BuildOpts, installed map[string]string) ([]string, error) {
|
|
||||||
log := loggerctx.From(ctx)
|
|
||||||
var buildDeps []string
|
|
||||||
if len(vars.BuildDepends) > 0 {
|
|
||||||
found, notFound, err := repos.FindPkgs(ctx, vars.BuildDepends)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
found = removeAlreadyInstalled(found, installed)
|
|
||||||
|
|
||||||
log.Info("Installing build dependencies").Send()
|
|
||||||
|
|
||||||
flattened := cliutils.FlattenPkgs(ctx, found, "install", opts.Interactive)
|
|
||||||
buildDeps = packageNames(flattened)
|
|
||||||
InstallPkgs(ctx, flattened, notFound, opts)
|
|
||||||
}
|
|
||||||
return buildDeps, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// installOptDeps asks the user which, if any, optional dependencies they want to install.
|
|
||||||
// If the user chooses to install any optional dependencies, it performs the installation.
|
|
||||||
func installOptDeps(ctx context.Context, vars *types.BuildVars, opts types.BuildOpts, installed map[string]string) error {
|
|
||||||
if len(vars.OptDepends) > 0 {
|
|
||||||
optDeps, err := cliutils.ChooseOptDepends(ctx, vars.OptDepends, "install", opts.Interactive)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(optDeps) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
found, notFound, err := repos.FindPkgs(ctx, optDeps)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
found = removeAlreadyInstalled(found, installed)
|
|
||||||
flattened := cliutils.FlattenPkgs(ctx, found, "install", opts.Interactive)
|
|
||||||
InstallPkgs(ctx, flattened, notFound, opts)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// buildLUREDeps builds all the LURE dependencies of the package. It returns the paths and names
|
|
||||||
// of the packages it built, as well as all the dependencies it didn't find in the LURE repo so
|
|
||||||
// they can be installed from the system repos.
|
|
||||||
func buildLUREDeps(ctx context.Context, opts types.BuildOpts, vars *types.BuildVars) (builtPaths, builtNames, repoDeps []string, err error) {
|
|
||||||
log := loggerctx.From(ctx)
|
|
||||||
if len(vars.Depends) > 0 {
|
|
||||||
log.Info("Installing dependencies").Send()
|
|
||||||
|
|
||||||
found, notFound, err := repos.FindPkgs(ctx, 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(ctx, found, "install", opts.Interactive)
|
|
||||||
scripts := GetScriptPaths(ctx, 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)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove any potential duplicates, which can be introduced if
|
|
||||||
// several of the dependencies depend on the same packages.
|
|
||||||
repoDeps = removeDuplicates(repoDeps)
|
|
||||||
builtPaths = removeDuplicates(builtPaths)
|
|
||||||
builtNames = removeDuplicates(builtNames)
|
|
||||||
return builtPaths, builtNames, repoDeps, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// executeFunctions executes the special LURE functions, such as version(), prepare(), etc.
|
|
||||||
func executeFunctions(ctx context.Context, dec *decoder.Decoder, dirs types.Directories, vars *types.BuildVars) (err error) {
|
|
||||||
log := loggerctx.From(ctx)
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// buildPkgMetadata builds the metadata for the package that's going to be built.
|
|
||||||
func buildPkgMetadata(vars *types.BuildVars, dirs types.Directories, pkgFormat string, 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 pkgFormat == "apk" {
|
|
||||||
// Alpine refuses to install packages that provide themselves, so remove any such provides
|
|
||||||
pkgInfo.Overridables.Provides = slices.DeleteFunc(pkgInfo.Overridables.Provides, func(s string) bool {
|
|
||||||
return s == pkgInfo.Name
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// buildContents builds the contents section of the package, which contains the files
|
|
||||||
// that will be placed into the final package.
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the directory is empty, skip it
|
|
||||||
_, 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(),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
return f.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
if fi.Mode()&os.ModeSymlink != 0 {
|
|
||||||
link, err := os.Readlink(path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Remove pkgdir from the symlink's path
|
|
||||||
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 the file is supposed to be backed up, set its type to config|noreplace
|
|
||||||
if slices.Contains(vars.Backup, trimmed) {
|
|
||||||
fileContent.Type = "config|noreplace"
|
|
||||||
}
|
|
||||||
|
|
||||||
contents = append(contents, fileContent)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
return contents, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// removeBuildDeps asks the user if they'd like to remove the build dependencies that were
|
|
||||||
// installed by installBuildDeps. If so, it uses the package manager to do that.
|
|
||||||
func removeBuildDeps(ctx context.Context, buildDeps []string, opts types.BuildOpts) error {
|
|
||||||
if len(buildDeps) > 0 {
|
|
||||||
remove, err := cliutils.YesNoPrompt(ctx, "Would you like to remove the build dependencies?", opts.Interactive, false)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if remove {
|
|
||||||
err = opts.Manager.Remove(
|
|
||||||
&manager.Opts{
|
|
||||||
AsRoot: true,
|
|
||||||
NoConfirm: true,
|
|
||||||
},
|
|
||||||
buildDeps...,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkForBuiltPackage tries to detect a previously-built package and returns its path
|
|
||||||
// and true if it finds one. If it doesn't find it, it returns "", false, 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// pkgFileName returns the filename of the package if it were to be built.
|
|
||||||
// This is used to check if the package has already been built.
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// getPkgFormat returns the package format of the package manager,
|
|
||||||
// or LURE_PKG_FORMAT if that's set.
|
|
||||||
func getPkgFormat(mgr manager.Manager) string {
|
|
||||||
pkgFormat := mgr.Format()
|
|
||||||
if format, ok := os.LookupEnv("LURE_PKG_FORMAT"); ok {
|
|
||||||
pkgFormat = format
|
|
||||||
}
|
|
||||||
return pkgFormat
|
|
||||||
}
|
|
||||||
|
|
||||||
// createBuildEnvVars creates the environment variables that will be set in the
|
|
||||||
// build script when it's executed.
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// getSources downloads the sources from the script.
|
|
||||||
func getSources(ctx context.Context, dirs types.Directories, bv *types.BuildVars) error {
|
|
||||||
log := loggerctx.From(ctx)
|
|
||||||
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: dirs.SrcDir,
|
|
||||||
Progress: os.Stderr,
|
|
||||||
LocalDir: dirs.ScriptDir,
|
|
||||||
}
|
|
||||||
|
|
||||||
if !strings.EqualFold(bv.Checksums[i], "SKIP") {
|
|
||||||
// If the checksum contains a colon, use the part before the colon
|
|
||||||
// as the algorithm and the part after as the actual checksum.
|
|
||||||
// Otherwise, use the default sha256 with the whole string as the checksum.
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// setScripts adds any hook scripts to the package metadata.
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// setVersion changes the version variable in the script runner.
|
|
||||||
// It's used to set the version to the output of the version() function.
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// removeAlreadyInstalled returns a map without any dependencies that are already installed
|
|
||||||
func removeAlreadyInstalled(found map[string][]db.Package, installed map[string]string) map[string][]db.Package {
|
|
||||||
filteredPackages := make(map[string][]db.Package)
|
|
||||||
|
|
||||||
for name, pkgList := range found {
|
|
||||||
filteredPkgList := []db.Package{}
|
|
||||||
for _, pkg := range pkgList {
|
|
||||||
if _, isInstalled := installed[pkg.Name]; !isInstalled {
|
|
||||||
filteredPkgList = append(filteredPkgList, pkg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
filteredPackages[name] = filteredPkgList
|
|
||||||
}
|
|
||||||
|
|
||||||
return filteredPackages
|
|
||||||
}
|
|
||||||
|
|
||||||
// packageNames returns the names of all the given packages
|
|
||||||
func packageNames(pkgs []db.Package) []string {
|
|
||||||
names := make([]string, len(pkgs))
|
|
||||||
for i, p := range pkgs {
|
|
||||||
names[i] = p.Name
|
|
||||||
}
|
|
||||||
return names
|
|
||||||
}
|
|
||||||
|
|
||||||
// removeDuplicates removes any duplicates from the given slice
|
|
||||||
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
|
|
||||||
}
|
|
@ -1,72 +0,0 @@
|
|||||||
/*
|
|
||||||
* LURE - Linux User REpository
|
|
||||||
* Copyright (C) 2023 Elara 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 build
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"lure.sh/lure/internal/config"
|
|
||||||
"lure.sh/lure/internal/db"
|
|
||||||
"lure.sh/lure/internal/types"
|
|
||||||
"lure.sh/lure/pkg/loggerctx"
|
|
||||||
)
|
|
||||||
|
|
||||||
// InstallPkgs installs native packages via the package manager,
|
|
||||||
// then builds and installs the LURE packages
|
|
||||||
func InstallPkgs(ctx context.Context, lurePkgs []db.Package, nativePkgs []string, opts types.BuildOpts) {
|
|
||||||
log := loggerctx.From(ctx)
|
|
||||||
|
|
||||||
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(ctx, lurePkgs), opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetScriptPaths returns a slice of script paths corresponding to the
|
|
||||||
// given packages
|
|
||||||
func GetScriptPaths(ctx context.Context, pkgs []db.Package) []string {
|
|
||||||
var scripts []string
|
|
||||||
for _, pkg := range pkgs {
|
|
||||||
scriptPath := filepath.Join(config.GetPaths(ctx).RepoDir, pkg.Repository, pkg.Name, "lure.sh")
|
|
||||||
scripts = append(scripts, scriptPath)
|
|
||||||
}
|
|
||||||
return scripts
|
|
||||||
}
|
|
||||||
|
|
||||||
// InstallScripts builds and installs the given LURE build scripts
|
|
||||||
func InstallScripts(ctx context.Context, scripts []string, opts types.BuildOpts) {
|
|
||||||
log := loggerctx.From(ctx)
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
package gen
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"text/template"
|
|
||||||
)
|
|
||||||
|
|
||||||
var funcs = template.FuncMap{
|
|
||||||
"tolower": strings.ToLower,
|
|
||||||
"firstchar": func(s string) string {
|
|
||||||
return s[:1]
|
|
||||||
},
|
|
||||||
}
|
|
@ -1,84 +0,0 @@
|
|||||||
package gen
|
|
||||||
|
|
||||||
import (
|
|
||||||
_ "embed"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"text/template"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed tmpls/pip.tmpl.sh
|
|
||||||
var pipTmpl string
|
|
||||||
|
|
||||||
type PipOptions struct {
|
|
||||||
Name string
|
|
||||||
Version string
|
|
||||||
Description string
|
|
||||||
}
|
|
||||||
|
|
||||||
type pypiAPIResponse struct {
|
|
||||||
Info pypiInfo `json:"info"`
|
|
||||||
URLs []pypiURL `json:"urls"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (res pypiAPIResponse) SourceURL() (pypiURL, error) {
|
|
||||||
for _, url := range res.URLs {
|
|
||||||
if url.PackageType == "sdist" {
|
|
||||||
return url, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return pypiURL{}, errors.New("package doesn't have a source distribution")
|
|
||||||
}
|
|
||||||
|
|
||||||
type pypiInfo struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Version string `json:"version"`
|
|
||||||
Summary string `json:"summary"`
|
|
||||||
Homepage string `json:"home_page"`
|
|
||||||
License string `json:"license"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type pypiURL struct {
|
|
||||||
Digests map[string]string `json:"digests"`
|
|
||||||
Filename string `json:"filename"`
|
|
||||||
PackageType string `json:"packagetype"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func Pip(w io.Writer, opts PipOptions) error {
|
|
||||||
tmpl, err := template.New("pip").
|
|
||||||
Funcs(funcs).
|
|
||||||
Parse(pipTmpl)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
url := fmt.Sprintf(
|
|
||||||
"https://pypi.org/pypi/%s/%s/json",
|
|
||||||
opts.Name,
|
|
||||||
opts.Version,
|
|
||||||
)
|
|
||||||
|
|
||||||
res, err := http.Get(url)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer res.Body.Close()
|
|
||||||
if res.StatusCode != 200 {
|
|
||||||
return fmt.Errorf("pypi: %s", res.Status)
|
|
||||||
}
|
|
||||||
|
|
||||||
var resp pypiAPIResponse
|
|
||||||
err = json.NewDecoder(res.Body).Decode(&resp)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.Description != "" {
|
|
||||||
resp.Info.Summary = opts.Description
|
|
||||||
}
|
|
||||||
|
|
||||||
return tmpl.Execute(w, resp)
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
name='{{.Info.Name | tolower}}'
|
|
||||||
version='{{.Info.Version}}'
|
|
||||||
release='1'
|
|
||||||
desc='{{.Info.Summary}}'
|
|
||||||
homepage='{{.Info.Homepage}}'
|
|
||||||
maintainer='Example <user@example.com>'
|
|
||||||
architectures=('all')
|
|
||||||
license=('{{if .Info.License | ne ""}}{{.Info.License}}{{else}}custom:Unknown{{end}}')
|
|
||||||
provides=('{{.Info.Name | tolower}}')
|
|
||||||
conflicts=('{{.Info.Name | tolower}}')
|
|
||||||
|
|
||||||
deps=("python3")
|
|
||||||
deps_arch=("python")
|
|
||||||
deps_alpine=("python3")
|
|
||||||
|
|
||||||
build_deps=("python3" "python3-setuptools")
|
|
||||||
build_deps_arch=("python" "python-setuptools")
|
|
||||||
build_deps_alpine=("python3" "py3-setuptools")
|
|
||||||
|
|
||||||
sources=("https://files.pythonhosted.org/packages/source/{{.SourceURL.Filename | firstchar}}/{{.Info.Name}}/{{.SourceURL.Filename}}")
|
|
||||||
checksums=('blake2b-256:{{.SourceURL.Digests.blake2b_256}}')
|
|
||||||
|
|
||||||
build() {
|
|
||||||
cd "$srcdir/{{.Info.Name}}-${version}"
|
|
||||||
python3 setup.py build
|
|
||||||
}
|
|
||||||
|
|
||||||
package() {
|
|
||||||
cd "$srcdir/{{.Info.Name}}-${version}"
|
|
||||||
python3 setup.py install --root="${pkgdir}/" --optimize=1 || return 1
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
package loggerctx
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"go.elara.ws/logger"
|
|
||||||
)
|
|
||||||
|
|
||||||
// loggerCtxKey is used as the context key for loggers
|
|
||||||
type loggerCtxKey struct{}
|
|
||||||
|
|
||||||
// With returns a copy of ctx containing log
|
|
||||||
func With(ctx context.Context, log logger.Logger) context.Context {
|
|
||||||
return context.WithValue(ctx, loggerCtxKey{}, log)
|
|
||||||
}
|
|
||||||
|
|
||||||
// From attempts to get a logger from ctx. If ctx doesn't
|
|
||||||
// contain a logger, it returns a nop logger.
|
|
||||||
func From(ctx context.Context) logger.Logger {
|
|
||||||
if val := ctx.Value(loggerCtxKey{}); val != nil {
|
|
||||||
if log, ok := val.(logger.Logger); ok && log != nil {
|
|
||||||
return log
|
|
||||||
} else {
|
|
||||||
return logger.NewNop()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return logger.NewNop()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,83 +0,0 @@
|
|||||||
/*
|
|
||||||
* LURE - Linux User REpository
|
|
||||||
* Copyright (C) 2023 Elara 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 repos
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"lure.sh/lure/internal/db"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FindPkgs looks for packages matching the inputs inside the database.
|
|
||||||
// It returns a map that maps the package name input to any packages found for it.
|
|
||||||
// It also returns a slice that contains the names of all packages that were not found.
|
|
||||||
func FindPkgs(ctx context.Context, pkgs []string) (map[string][]db.Package, []string, error) {
|
|
||||||
found := map[string][]db.Package{}
|
|
||||||
notFound := []string(nil)
|
|
||||||
|
|
||||||
for _, pkgName := range pkgs {
|
|
||||||
if pkgName == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := db.GetPkgs(ctx, "json_array_contains(provides, ?)", pkgName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
added := 0
|
|
||||||
for result.Next() {
|
|
||||||
var pkg db.Package
|
|
||||||
err = result.StructScan(&pkg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
added++
|
|
||||||
found[pkgName] = append(found[pkgName], pkg)
|
|
||||||
}
|
|
||||||
result.Close()
|
|
||||||
|
|
||||||
if added == 0 {
|
|
||||||
result, err := db.GetPkgs(ctx, "name LIKE ?", pkgName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for result.Next() {
|
|
||||||
var pkg db.Package
|
|
||||||
err = result.StructScan(&pkg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
added++
|
|
||||||
found[pkgName] = append(found[pkgName], pkg)
|
|
||||||
}
|
|
||||||
|
|
||||||
result.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
if added == 0 {
|
|
||||||
notFound = append(notFound, pkgName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return found, notFound, nil
|
|
||||||
}
|
|
@ -1,148 +0,0 @@
|
|||||||
/*
|
|
||||||
* LURE - Linux User REpository
|
|
||||||
* Copyright (C) 2023 Elara 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 repos_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"lure.sh/lure/internal/db"
|
|
||||||
"lure.sh/lure/internal/types"
|
|
||||||
"lure.sh/lure/pkg/repos"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestFindPkgs(t *testing.T) {
|
|
||||||
_, err := db.Open(":memory:")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
defer db.Close()
|
|
||||||
|
|
||||||
setCfgDirs(t)
|
|
||||||
defer removeCacheDir(t)
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
err = repos.Pull(ctx, []types.Repo{
|
|
||||||
{
|
|
||||||
Name: "default",
|
|
||||||
URL: "https://github.com/Arsen6331/lure-repo.git",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
found, notFound, err := repos.FindPkgs([]string{"itd", "nonexistentpackage1", "nonexistentpackage2"})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(notFound, []string{"nonexistentpackage1", "nonexistentpackage2"}) {
|
|
||||||
t.Errorf("Expected 'nonexistentpackage{1,2} not to be found")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(found) != 1 {
|
|
||||||
t.Errorf("Expected 1 package found, got %d", len(found))
|
|
||||||
}
|
|
||||||
|
|
||||||
itdPkgs, ok := found["itd"]
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("Expected 'itd' packages to be found")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(itdPkgs) < 2 {
|
|
||||||
t.Errorf("Expected two 'itd' packages to be found")
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, pkg := range itdPkgs {
|
|
||||||
if !strings.HasPrefix(pkg.Name, "itd") {
|
|
||||||
t.Errorf("Expected package name of all found packages to start with 'itd', got %s on element %d", pkg.Name, i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFindPkgsEmpty(t *testing.T) {
|
|
||||||
_, err := db.Open(":memory:")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
defer db.Close()
|
|
||||||
|
|
||||||
setCfgDirs(t)
|
|
||||||
defer removeCacheDir(t)
|
|
||||||
|
|
||||||
err = db.InsertPackage(db.Package{
|
|
||||||
Name: "test1",
|
|
||||||
Repository: "default",
|
|
||||||
Version: "0.0.1",
|
|
||||||
Release: 1,
|
|
||||||
Description: db.NewJSON(map[string]string{
|
|
||||||
"en": "Test package 1",
|
|
||||||
"ru": "Проверочный пакет 1",
|
|
||||||
}),
|
|
||||||
Provides: db.NewJSON([]string{""}),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = db.InsertPackage(db.Package{
|
|
||||||
Name: "test2",
|
|
||||||
Repository: "default",
|
|
||||||
Version: "0.0.1",
|
|
||||||
Release: 1,
|
|
||||||
Description: db.NewJSON(map[string]string{
|
|
||||||
"en": "Test package 2",
|
|
||||||
"ru": "Проверочный пакет 2",
|
|
||||||
}),
|
|
||||||
Provides: db.NewJSON([]string{"test"}),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
found, notFound, err := repos.FindPkgs([]string{"test", ""})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(notFound) != 0 {
|
|
||||||
t.Errorf("Expected all packages to be found")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(found) != 1 {
|
|
||||||
t.Errorf("Expected 1 package found, got %d", len(found))
|
|
||||||
}
|
|
||||||
|
|
||||||
testPkgs, ok := found["test"]
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("Expected 'test' packages to be found")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(testPkgs) != 1 {
|
|
||||||
t.Errorf("Expected one 'test' package to be found, got %d", len(testPkgs))
|
|
||||||
}
|
|
||||||
|
|
||||||
if testPkgs[0].Name != "test2" {
|
|
||||||
t.Errorf("Expected 'test2' package, got '%s'", testPkgs[0].Name)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,441 +0,0 @@
|
|||||||
/*
|
|
||||||
* LURE - Linux User REpository
|
|
||||||
* Copyright (C) 2023 Elara 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 repos
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/go-git/go-billy/v5"
|
|
||||||
"github.com/go-git/go-billy/v5/osfs"
|
|
||||||
"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/pelletier/go-toml/v2"
|
|
||||||
"go.elara.ws/vercmp"
|
|
||||||
"lure.sh/lure/internal/config"
|
|
||||||
"lure.sh/lure/internal/db"
|
|
||||||
"lure.sh/lure/internal/shutils/decoder"
|
|
||||||
"lure.sh/lure/internal/shutils/handlers"
|
|
||||||
"lure.sh/lure/internal/types"
|
|
||||||
"lure.sh/lure/pkg/distro"
|
|
||||||
"lure.sh/lure/pkg/loggerctx"
|
|
||||||
"mvdan.cc/sh/v3/expand"
|
|
||||||
"mvdan.cc/sh/v3/interp"
|
|
||||||
"mvdan.cc/sh/v3/syntax"
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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 if possible.
|
|
||||||
// If repos is set to nil, the repos in the LURE config will be used.
|
|
||||||
func Pull(ctx context.Context, repos []types.Repo) error {
|
|
||||||
log := loggerctx.From(ctx)
|
|
||||||
|
|
||||||
if repos == nil {
|
|
||||||
repos = config.Config(ctx).Repos
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, repo := range repos {
|
|
||||||
repoURL, err := url.Parse(repo.URL)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("Pulling repository").Str("name", repo.Name).Send()
|
|
||||||
repoDir := filepath.Join(config.GetPaths(ctx).RepoDir, repo.Name)
|
|
||||||
|
|
||||||
var repoFS billy.Filesystem
|
|
||||||
gitDir := filepath.Join(repoDir, ".git")
|
|
||||||
// Only pull repos that contain valid git repos
|
|
||||||
if fi, err := os.Stat(gitDir); err == nil && fi.IsDir() {
|
|
||||||
r, err := git.PlainOpen(repoDir)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
w, err := r.Worktree()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
old, err := r.Head()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = w.PullContext(ctx, &git.PullOptions{Progress: os.Stderr})
|
|
||||||
if errors.Is(err, git.NoErrAlreadyUpToDate) {
|
|
||||||
log.Info("Repository up to date").Str("name", repo.Name).Send()
|
|
||||||
} else if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
repoFS = w.Filesystem
|
|
||||||
|
|
||||||
// Make sure the DB is created even if the repo is up to date
|
|
||||||
if !errors.Is(err, git.NoErrAlreadyUpToDate) || db.IsEmpty(ctx) {
|
|
||||||
new, err := r.Head()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 db.IsEmpty(ctx) {
|
|
||||||
err = processRepoFull(ctx, repo, repoDir)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
err = processRepoChanges(ctx, repo, r, w, old, new)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
err = os.RemoveAll(repoDir)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = os.MkdirAll(repoDir, 0o755)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = git.PlainCloneContext(ctx, repoDir, false, &git.CloneOptions{
|
|
||||||
URL: repoURL.String(),
|
|
||||||
Progress: os.Stderr,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = processRepoFull(ctx, repo, repoDir)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
repoFS = osfs.New(repoDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
fl, err := repoFS.Open("lure-repo.toml")
|
|
||||||
if err != nil {
|
|
||||||
log.Warn("Git repository does not appear to be a valid LURE repo").Str("repo", repo.Name).Send()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
var repoCfg types.RepoConfig
|
|
||||||
err = toml.NewDecoder(fl).Decode(&repoCfg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fl.Close()
|
|
||||||
|
|
||||||
// If the version doesn't have a "v" prefix, it's not a standard version.
|
|
||||||
// It may be "unknown" or a git version, but either way, there's no way
|
|
||||||
// to compare it to the repo version, so only compare versions with the "v".
|
|
||||||
if strings.HasPrefix(config.Version, "v") {
|
|
||||||
if vercmp.Compare(config.Version, repoCfg.Repo.MinVersion) == -1 {
|
|
||||||
log.Warn("LURE repo's minumum LURE version is greater than the current version. Try updating LURE if something doesn't work.").Str("repo", repo.Name).Send()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type actionType uint8
|
|
||||||
|
|
||||||
const (
|
|
||||||
actionDelete actionType = iota
|
|
||||||
actionUpdate
|
|
||||||
)
|
|
||||||
|
|
||||||
type action struct {
|
|
||||||
Type actionType
|
|
||||||
File string
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
newCommit, err := r.CommitObject(new.Hash())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
patch, err := oldCommit.Patch(newCommit)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var actions []action
|
|
||||||
for _, fp := range patch.FilePatches() {
|
|
||||||
from, to := fp.Files()
|
|
||||||
|
|
||||||
if !isValid(from, to) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if to == nil {
|
|
||||||
actions = append(actions, action{
|
|
||||||
Type: actionDelete,
|
|
||||||
File: from.Path(),
|
|
||||||
})
|
|
||||||
} else if from == nil {
|
|
||||||
actions = append(actions, action{
|
|
||||||
Type: actionUpdate,
|
|
||||||
File: to.Path(),
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
if from.Path() != to.Path() {
|
|
||||||
actions = append(actions,
|
|
||||||
action{
|
|
||||||
Type: actionDelete,
|
|
||||||
File: from.Path(),
|
|
||||||
},
|
|
||||||
action{
|
|
||||||
Type: actionUpdate,
|
|
||||||
File: to.Path(),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
actions = append(actions, action{
|
|
||||||
Type: actionUpdate,
|
|
||||||
File: to.Path(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
repoDir := w.Filesystem.Root()
|
|
||||||
parser := syntax.NewParser()
|
|
||||||
|
|
||||||
for _, action := range actions {
|
|
||||||
env := append(os.Environ(), "scriptdir="+filepath.Dir(filepath.Join(repoDir, action.File)))
|
|
||||||
runner, err := interp.New(
|
|
||||||
interp.Env(expand.ListEnviron(env...)),
|
|
||||||
interp.ExecHandler(handlers.NopExec),
|
|
||||||
interp.ReadDirHandler(handlers.RestrictedReadDir(repoDir)),
|
|
||||||
interp.StatHandler(handlers.RestrictedStat(repoDir)),
|
|
||||||
interp.OpenHandler(handlers.RestrictedOpen(repoDir)),
|
|
||||||
interp.StdIO(handlers.NopRWC{}, handlers.NopRWC{}, handlers.NopRWC{}),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch action.Type {
|
|
||||||
case actionDelete:
|
|
||||||
if filepath.Base(action.File) != "lure.sh" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
scriptFl, err := oldCommit.File(action.File)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
r, err := scriptFl.Reader()
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var pkg db.Package
|
|
||||||
err = parseScript(ctx, parser, runner, r, &pkg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = db.DeletePkgs(ctx, "name = ? AND repository = ?", pkg.Name, repo.Name)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case actionUpdate:
|
|
||||||
if filepath.Base(action.File) != "lure.sh" {
|
|
||||||
action.File = filepath.Join(filepath.Dir(action.File), "lure.sh")
|
|
||||||
}
|
|
||||||
|
|
||||||
scriptFl, err := newCommit.File(action.File)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
r, err := scriptFl.Reader()
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
pkg := db.Package{
|
|
||||||
Description: db.NewJSON(map[string]string{}),
|
|
||||||
Homepage: db.NewJSON(map[string]string{}),
|
|
||||||
Maintainer: db.NewJSON(map[string]string{}),
|
|
||||||
Depends: db.NewJSON(map[string][]string{}),
|
|
||||||
BuildDepends: db.NewJSON(map[string][]string{}),
|
|
||||||
Repository: repo.Name,
|
|
||||||
}
|
|
||||||
|
|
||||||
err = parseScript(ctx, parser, runner, r, &pkg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
resolveOverrides(runner, &pkg)
|
|
||||||
|
|
||||||
err = db.InsertPackage(ctx, pkg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// isValid makes sure the path of the file being updated is valid.
|
|
||||||
// It checks to make sure the file is not within a nested directory
|
|
||||||
// and that it is called lure.sh.
|
|
||||||
func isValid(from, to diff.File) bool {
|
|
||||||
var path string
|
|
||||||
if from != nil {
|
|
||||||
path = from.Path()
|
|
||||||
}
|
|
||||||
if to != nil {
|
|
||||||
path = to.Path()
|
|
||||||
}
|
|
||||||
|
|
||||||
match, _ := filepath.Match("*/*.sh", path)
|
|
||||||
return match
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
parser := syntax.NewParser()
|
|
||||||
|
|
||||||
for _, match := range matches {
|
|
||||||
env := append(os.Environ(), "scriptdir="+filepath.Dir(match))
|
|
||||||
runner, err := interp.New(
|
|
||||||
interp.Env(expand.ListEnviron(env...)),
|
|
||||||
interp.ExecHandler(handlers.NopExec),
|
|
||||||
interp.ReadDirHandler(handlers.RestrictedReadDir(repoDir)),
|
|
||||||
interp.StatHandler(handlers.RestrictedStat(repoDir)),
|
|
||||||
interp.OpenHandler(handlers.RestrictedOpen(repoDir)),
|
|
||||||
interp.StdIO(handlers.NopRWC{}, handlers.NopRWC{}, handlers.NopRWC{}),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
scriptFl, err := os.Open(match)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
pkg := db.Package{
|
|
||||||
Description: db.NewJSON(map[string]string{}),
|
|
||||||
Homepage: db.NewJSON(map[string]string{}),
|
|
||||||
Maintainer: db.NewJSON(map[string]string{}),
|
|
||||||
Depends: db.NewJSON(map[string][]string{}),
|
|
||||||
BuildDepends: db.NewJSON(map[string][]string{}),
|
|
||||||
Repository: repo.Name,
|
|
||||||
}
|
|
||||||
|
|
||||||
err = parseScript(ctx, parser, runner, scriptFl, &pkg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
resolveOverrides(runner, &pkg)
|
|
||||||
|
|
||||||
err = db.InsertPackage(ctx, pkg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseScript(ctx context.Context, parser *syntax.Parser, runner *interp.Runner, r io.ReadCloser, pkg *db.Package) error {
|
|
||||||
defer r.Close()
|
|
||||||
fl, err := parser.Parse(r, "lure.sh")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
runner.Reset()
|
|
||||||
err = runner.Run(ctx, fl)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
d := decoder.New(&distro.OSRelease{}, runner)
|
|
||||||
d.Overrides = false
|
|
||||||
d.LikeDistros = false
|
|
||||||
return d.DecodeVars(pkg)
|
|
||||||
}
|
|
||||||
|
|
||||||
var overridable = map[string]string{
|
|
||||||
"deps": "Depends",
|
|
||||||
"build_deps": "BuildDepends",
|
|
||||||
"desc": "Description",
|
|
||||||
"homepage": "Homepage",
|
|
||||||
"maintainer": "Maintainer",
|
|
||||||
}
|
|
||||||
|
|
||||||
func resolveOverrides(runner *interp.Runner, pkg *db.Package) {
|
|
||||||
pkgVal := reflect.ValueOf(pkg).Elem()
|
|
||||||
for name, val := range runner.Vars {
|
|
||||||
for prefix, field := range overridable {
|
|
||||||
if strings.HasPrefix(name, prefix) {
|
|
||||||
override := strings.TrimPrefix(name, prefix)
|
|
||||||
override = strings.TrimPrefix(override, "_")
|
|
||||||
|
|
||||||
field := pkgVal.FieldByName(field)
|
|
||||||
varVal := field.FieldByName("Val")
|
|
||||||
varType := varVal.Type()
|
|
||||||
|
|
||||||
switch varType.Elem().String() {
|
|
||||||
case "[]string":
|
|
||||||
varVal.SetMapIndex(reflect.ValueOf(override), reflect.ValueOf(val.List))
|
|
||||||
case "string":
|
|
||||||
varVal.SetMapIndex(reflect.ValueOf(override), reflect.ValueOf(val.Str))
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,109 +0,0 @@
|
|||||||
/*
|
|
||||||
* LURE - Linux User REpository
|
|
||||||
* Copyright (C) 2023 Elara 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 repos_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"lure.sh/lure/internal/config"
|
|
||||||
"lure.sh/lure/internal/db"
|
|
||||||
"lure.sh/lure/internal/types"
|
|
||||||
"lure.sh/lure/pkg/repos"
|
|
||||||
)
|
|
||||||
|
|
||||||
func setCfgDirs(t *testing.T) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
paths := config.GetPaths()
|
|
||||||
|
|
||||||
var err error
|
|
||||||
paths.CacheDir, err = os.MkdirTemp("/tmp", "lure-pull-test.*")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
paths.RepoDir = filepath.Join(paths.CacheDir, "repo")
|
|
||||||
paths.PkgsDir = filepath.Join(paths.CacheDir, "pkgs")
|
|
||||||
|
|
||||||
err = os.MkdirAll(paths.RepoDir, 0o755)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = os.MkdirAll(paths.PkgsDir, 0o755)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
paths.DBPath = filepath.Join(paths.CacheDir, "db")
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeCacheDir(t *testing.T) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
err := os.RemoveAll(config.GetPaths().CacheDir)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPull(t *testing.T) {
|
|
||||||
_, err := db.Open(":memory:")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
defer db.Close()
|
|
||||||
|
|
||||||
setCfgDirs(t)
|
|
||||||
defer removeCacheDir(t)
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
err = repos.Pull(ctx, []types.Repo{
|
|
||||||
{
|
|
||||||
Name: "default",
|
|
||||||
URL: "https://github.com/Arsen6331/lure-repo.git",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := db.GetPkgs("name LIKE 'itd%'")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var pkgAmt int
|
|
||||||
for result.Next() {
|
|
||||||
var dbPkg db.Package
|
|
||||||
err = result.StructScan(&dbPkg)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Expected no error, got %s", err)
|
|
||||||
}
|
|
||||||
pkgAmt++
|
|
||||||
}
|
|
||||||
|
|
||||||
if pkgAmt < 2 {
|
|
||||||
t.Errorf("Expected 2 packages to match, got %d", pkgAmt)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,167 +0,0 @@
|
|||||||
package search
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"io/fs"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"lure.sh/lure/internal/config"
|
|
||||||
"lure.sh/lure/internal/db"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Filter represents search filters.
|
|
||||||
type Filter int
|
|
||||||
|
|
||||||
// Filters
|
|
||||||
const (
|
|
||||||
FilterNone Filter = iota
|
|
||||||
FilterInRepo
|
|
||||||
FilterSupportsArch
|
|
||||||
)
|
|
||||||
|
|
||||||
// SoryBy represents a value that packages can be sorted by.
|
|
||||||
type SortBy int
|
|
||||||
|
|
||||||
// Sort values
|
|
||||||
const (
|
|
||||||
SortByNone = iota
|
|
||||||
SortByName
|
|
||||||
SortByRepo
|
|
||||||
SortByVersion
|
|
||||||
)
|
|
||||||
|
|
||||||
// Package represents a package from LURE's database
|
|
||||||
type Package struct {
|
|
||||||
Name string
|
|
||||||
Version string
|
|
||||||
Release int
|
|
||||||
Epoch uint
|
|
||||||
Description map[string]string
|
|
||||||
Homepage map[string]string
|
|
||||||
Maintainer map[string]string
|
|
||||||
Architectures []string
|
|
||||||
Licenses []string
|
|
||||||
Provides []string
|
|
||||||
Conflicts []string
|
|
||||||
Replaces []string
|
|
||||||
Depends map[string][]string
|
|
||||||
BuildDepends map[string][]string
|
|
||||||
OptDepends map[string][]string
|
|
||||||
Repository string
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertPkg(p db.Package) Package {
|
|
||||||
return Package{
|
|
||||||
Name: p.Name,
|
|
||||||
Version: p.Version,
|
|
||||||
Release: p.Release,
|
|
||||||
Epoch: p.Epoch,
|
|
||||||
Description: p.Description.Val,
|
|
||||||
Homepage: p.Homepage.Val,
|
|
||||||
Maintainer: p.Maintainer.Val,
|
|
||||||
Architectures: p.Architectures.Val,
|
|
||||||
Licenses: p.Licenses.Val,
|
|
||||||
Provides: p.Provides.Val,
|
|
||||||
Conflicts: p.Conflicts.Val,
|
|
||||||
Replaces: p.Replaces.Val,
|
|
||||||
Depends: p.Depends.Val,
|
|
||||||
BuildDepends: p.BuildDepends.Val,
|
|
||||||
OptDepends: p.OptDepends.Val,
|
|
||||||
Repository: p.Repository,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Options contains the options for a search.
|
|
||||||
type Options struct {
|
|
||||||
Filter Filter
|
|
||||||
FilterValue string
|
|
||||||
SortBy SortBy
|
|
||||||
Limit int64
|
|
||||||
Query string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Search searches for packages in the database based on the given options.
|
|
||||||
func Search(ctx context.Context, opts Options) ([]Package, error) {
|
|
||||||
query := "(name LIKE ? OR description LIKE ? OR json_array_contains(provides, ?))"
|
|
||||||
args := []any{"%" + opts.Query + "%", "%" + opts.Query + "%", opts.Query}
|
|
||||||
|
|
||||||
if opts.Filter != FilterNone {
|
|
||||||
switch opts.Filter {
|
|
||||||
case FilterInRepo:
|
|
||||||
query += " AND repository = ?"
|
|
||||||
case FilterSupportsArch:
|
|
||||||
query += " AND json_array_contains(architectures, ?)"
|
|
||||||
}
|
|
||||||
args = append(args, opts.FilterValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.SortBy != SortByNone {
|
|
||||||
switch opts.SortBy {
|
|
||||||
case SortByName:
|
|
||||||
query += " ORDER BY name"
|
|
||||||
case SortByRepo:
|
|
||||||
query += " ORDER BY repository"
|
|
||||||
case SortByVersion:
|
|
||||||
query += " ORDER BY version"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.Limit != 0 {
|
|
||||||
query += " LIMIT " + strconv.FormatInt(opts.Limit, 10)
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := db.GetPkgs(ctx, query, args...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var out []Package
|
|
||||||
for result.Next() {
|
|
||||||
pkg := db.Package{}
|
|
||||||
err = result.StructScan(&pkg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
out = append(out, convertPkg(pkg))
|
|
||||||
}
|
|
||||||
|
|
||||||
return out, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPkg gets a single package from the database and returns it.
|
|
||||||
func GetPkg(ctx context.Context, repo, name string) (Package, error) {
|
|
||||||
pkg, err := db.GetPkg(ctx, "name = ? AND repository = ?", name, repo)
|
|
||||||
return convertPkg(*pkg), err
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrInvalidArgument is an error returned by GetScript when one of its arguments
|
|
||||||
// contain invalid characters
|
|
||||||
ErrInvalidArgument = errors.New("name and repository must not contain . or /")
|
|
||||||
|
|
||||||
// ErrScriptNotFound is returned by GetScript if it can't find the script requested
|
|
||||||
// by the user.
|
|
||||||
ErrScriptNotFound = errors.New("requested script not found")
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetScript returns a reader containing the build script for a given package.
|
|
||||||
func GetScript(ctx context.Context, repo, name string) (io.ReadCloser, error) {
|
|
||||||
if strings.Contains(name, "./") || strings.ContainsAny(repo, "./") {
|
|
||||||
return nil, ErrInvalidArgument
|
|
||||||
}
|
|
||||||
|
|
||||||
scriptPath := filepath.Join(config.GetPaths(ctx).RepoDir, repo, name, "lure.sh")
|
|
||||||
fl, err := os.Open(scriptPath)
|
|
||||||
if errors.Is(err, fs.ErrNotExist) {
|
|
||||||
return nil, ErrScriptNotFound
|
|
||||||
} else if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return fl, nil
|
|
||||||
}
|
|
369
repo.go
369
repo.go
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* LURE - Linux User REpository
|
* LURE - Linux User REpository
|
||||||
* Copyright (C) 2023 Elara Musayelyan
|
* Copyright (C) 2022 Arsen Musayelyan
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -19,144 +19,277 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/AlecAivazis/survey/v2"
|
||||||
|
"github.com/go-git/go-git/v5"
|
||||||
"github.com/pelletier/go-toml/v2"
|
"github.com/pelletier/go-toml/v2"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"lure.sh/lure/internal/config"
|
"go.arsenm.dev/lure/download"
|
||||||
"lure.sh/lure/internal/db"
|
|
||||||
"lure.sh/lure/internal/types"
|
|
||||||
"lure.sh/lure/pkg/loggerctx"
|
|
||||||
"lure.sh/lure/pkg/repos"
|
|
||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
var addrepoCmd = &cli.Command{
|
type PkgNotFoundError struct {
|
||||||
Name: "addrepo",
|
pkgName string
|
||||||
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 {
|
|
||||||
ctx := c.Context
|
|
||||||
log := loggerctx.From(ctx)
|
|
||||||
|
|
||||||
name := c.String("name")
|
func (p PkgNotFoundError) Error() string {
|
||||||
repoURL := c.String("url")
|
return "package '" + p.pkgName + "' could not be found in any repository"
|
||||||
|
}
|
||||||
|
|
||||||
cfg := config.Config(ctx)
|
type RepoConfig struct {
|
||||||
|
Repo struct {
|
||||||
|
MinVersion string `toml:"minVersion"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, repo := range cfg.Repos {
|
func addrepoCmd(c *cli.Context) error {
|
||||||
if repo.URL == repoURL {
|
name := c.String("name")
|
||||||
log.Fatal("Repo already exists").Str("name", repo.Name).Send()
|
repoURL := c.String("url")
|
||||||
}
|
|
||||||
|
for _, repo := range config.Repos {
|
||||||
|
if repo.URL == repoURL {
|
||||||
|
log.Fatal("Repo already exists").Str("name", repo.Name).Send()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
config.Repos = append(config.Repos, Repo{
|
||||||
|
Name: name,
|
||||||
|
URL: repoURL,
|
||||||
|
})
|
||||||
|
|
||||||
|
cfgFl, err := os.Create(cfgPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error opening config file").Err(err).Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
err = toml.NewEncoder(cfgFl).Encode(&config)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error encoding config").Err(err).Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func removerepoCmd(c *cli.Context) error {
|
||||||
|
name := c.String("name")
|
||||||
|
|
||||||
|
found := false
|
||||||
|
index := 0
|
||||||
|
for i, repo := range config.Repos {
|
||||||
|
if repo.Name == name {
|
||||||
|
index = i
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
log.Fatal("Repo does not exist").Str("name", name).Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
config.Repos = slices.Delete(config.Repos, index, index+1)
|
||||||
|
|
||||||
|
cfgFl, err := os.Create(cfgPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error opening config file").Err(err).Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
err = toml.NewEncoder(cfgFl).Encode(&config)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error encoding config").Err(err).Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.RemoveAll(filepath.Join(cacheDir, "repo", name))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error removing repo directory").Err(err).Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func refreshCmd(c *cli.Context) error {
|
||||||
|
err := pullRepos(c.Context)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error pulling repos").Err(err).Send()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func findPkg(pkg string) ([]string, error) {
|
||||||
|
baseRepoDir := filepath.Join(cacheDir, "repo")
|
||||||
|
|
||||||
|
var out []string
|
||||||
|
for _, repo := range config.Repos {
|
||||||
|
repoDir := filepath.Join(baseRepoDir, repo.Name)
|
||||||
|
err := os.MkdirAll(repoDir, 0o755)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg.Repos = append(cfg.Repos, types.Repo{
|
glob := filepath.Join(repoDir, pkg, "lure.sh")
|
||||||
Name: name,
|
matches, err := filepath.Glob(glob)
|
||||||
URL: repoURL,
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(matches) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
out = append(out, matches...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(out) == 0 {
|
||||||
|
return nil, PkgNotFoundError{pkgName: pkg}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func pkgPrompt(options []string) ([]string, error) {
|
||||||
|
names := make([]string, len(options))
|
||||||
|
for i, option := range options {
|
||||||
|
pkgDir := filepath.Dir(option)
|
||||||
|
repoDir := filepath.Dir(pkgDir)
|
||||||
|
names[i] = filepath.Base(repoDir) + "/" + filepath.Base(pkgDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt := &survey.MultiSelect{
|
||||||
|
Options: names,
|
||||||
|
Message: "Choose which package(s) to install",
|
||||||
|
}
|
||||||
|
|
||||||
|
var choices []int
|
||||||
|
err := survey.AskOne(prompt, &choices)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
out := make([]string, len(choices))
|
||||||
|
for i, choiceIndex := range choices {
|
||||||
|
out[i] = options[choiceIndex]
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func findPkgs(pkgs []string) (scripts, notFound []string) {
|
||||||
|
for _, pkg := range pkgs {
|
||||||
|
found, err := findPkg(pkg)
|
||||||
|
if _, ok := err.(PkgNotFoundError); ok {
|
||||||
|
notFound = append(notFound, pkg)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(found) == 1 {
|
||||||
|
scripts = append(scripts, found...)
|
||||||
|
} else {
|
||||||
|
choices, err := pkgPrompt(found)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error prompting for package choices").Err(err).Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
scripts = append(scripts, choices...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func pullRepos(ctx context.Context) error {
|
||||||
|
baseRepoDir := filepath.Join(cacheDir, "repo")
|
||||||
|
|
||||||
|
for _, repo := range config.Repos {
|
||||||
|
repoURL, err := url.Parse(repo.URL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Pulling repository").Str("name", repo.Name).Send()
|
||||||
|
repoDir := filepath.Join(baseRepoDir, repo.Name)
|
||||||
|
|
||||||
|
gitDir := filepath.Join(repoDir, ".git")
|
||||||
|
if fi, err := os.Stat(gitDir); err == nil && fi.IsDir() {
|
||||||
|
r, err := git.PlainOpen(repoDir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
w, err := r.Worktree()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = w.PullContext(ctx, &git.PullOptions{Progress: os.Stderr})
|
||||||
|
if err == git.NoErrAlreadyUpToDate {
|
||||||
|
log.Info("Repository up to date").Str("name", repo.Name).Send()
|
||||||
|
continue
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fl, err := w.Filesystem.Open("lure-repo.toml")
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("Git repository does not appear to be a valid LURE repo").Str("repo", repo.Name).Send()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var repoCfg RepoConfig
|
||||||
|
err = toml.NewDecoder(fl).Decode(&repoCfg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fl.Close()
|
||||||
|
|
||||||
|
currentVer, _, _ := strings.Cut(version, "-")
|
||||||
|
if vercmp(currentVer, repoCfg.Repo.MinVersion) == -1 {
|
||||||
|
log.Warn("LURE repo's minumum LURE version is greater than the current version. Try updating LURE if something doesn't work.").Str("repo", repo.Name).Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.RemoveAll(repoDir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.MkdirAll(repoDir, 0o755)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasPrefix(repoURL.Scheme, "git+") {
|
||||||
|
repoURL.Scheme = "git+" + repoURL.Scheme
|
||||||
|
}
|
||||||
|
|
||||||
|
err = download.Get(ctx, download.GetOptions{
|
||||||
|
SourceURL: repoURL.String(),
|
||||||
|
Destination: repoDir,
|
||||||
})
|
})
|
||||||
|
|
||||||
cfgFl, err := os.Create(config.GetPaths(ctx).ConfigPath)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Error opening config file").Err(err).Send()
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = toml.NewEncoder(cfgFl).Encode(cfg)
|
fl, err := os.Open(filepath.Join(repoDir, "lure-repo.toml"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Error encoding config").Err(err).Send()
|
log.Warn("Git repository does not appear to be a valid LURE repo").Str("repo", repo.Name).Send()
|
||||||
}
|
}
|
||||||
|
|
||||||
err = repos.Pull(ctx, cfg.Repos)
|
var repoCfg RepoConfig
|
||||||
|
err = toml.NewDecoder(fl).Decode(&repoCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Error pulling repos").Err(err).Send()
|
return err
|
||||||
}
|
}
|
||||||
|
fl.Close()
|
||||||
|
|
||||||
return nil
|
currentVer, _, _ := strings.Cut(version, "-")
|
||||||
},
|
if vercmp(currentVer, repoCfg.Repo.MinVersion) == -1 {
|
||||||
}
|
log.Warn("LURE repo's minumum LURE version is greater than the current version. Try updating LURE if something doesn't work.").Str("repo", repo.Name).Send()
|
||||||
|
}
|
||||||
var removerepoCmd = &cli.Command{
|
}
|
||||||
Name: "removerepo",
|
|
||||||
Usage: "Remove an existing repository",
|
return nil
|
||||||
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 {
|
|
||||||
ctx := c.Context
|
|
||||||
log := loggerctx.From(ctx)
|
|
||||||
|
|
||||||
name := c.String("name")
|
|
||||||
cfg := config.Config(ctx)
|
|
||||||
|
|
||||||
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(ctx).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(ctx).RepoDir, name))
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Error removing repo directory").Err(err).Send()
|
|
||||||
}
|
|
||||||
|
|
||||||
err = db.DeletePkgs(ctx, "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 {
|
|
||||||
ctx := c.Context
|
|
||||||
log := loggerctx.From(ctx)
|
|
||||||
err := repos.Pull(ctx, config.Config(ctx).Repos)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Error pulling repos").Err(err).Send()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
#! /bin/bash
|
|
||||||
|
|
||||||
: ${PROG:=$(basename ${BASH_SOURCE})}
|
|
||||||
|
|
||||||
_cli_bash_autocomplete() {
|
|
||||||
if [[ "${COMP_WORDS[0]}" != "source" ]]; then
|
|
||||||
local cur opts base
|
|
||||||
COMPREPLY=()
|
|
||||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
|
||||||
if [[ "$cur" == "-"* ]]; then
|
|
||||||
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} ${cur} --generate-bash-completion )
|
|
||||||
else
|
|
||||||
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion )
|
|
||||||
fi
|
|
||||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
complete -o bashdefault -o default -o nospace -F _cli_bash_autocomplete $PROG
|
|
||||||
unset PROG
|
|
@ -1,20 +0,0 @@
|
|||||||
#compdef lure
|
|
||||||
|
|
||||||
_cli_zsh_autocomplete() {
|
|
||||||
local -a opts
|
|
||||||
local cur
|
|
||||||
cur=${words[-1]}
|
|
||||||
if [[ "$cur" == "-"* ]]; then
|
|
||||||
opts=("${(@f)$(${words[@]:0:#words[@]-1} ${cur} --generate-bash-completion)}")
|
|
||||||
else
|
|
||||||
opts=("${(@f)$(${words[@]:0:#words[@]-1} --generate-bash-completion)}")
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "${opts[1]}" != "" ]]; then
|
|
||||||
_describe 'values' opts
|
|
||||||
else
|
|
||||||
_files
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
compdef _cli_zsh_autocomplete lure
|
|
3
scripts/gen-version.sh
Executable file
3
scripts/gen-version.sh
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
git describe --tags > version.txt
|
@ -1,99 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# LURE - Linux User REpository
|
|
||||||
# Copyright (C) 2023 Elara 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/>.
|
|
||||||
|
|
||||||
info() {
|
|
||||||
echo $'\x1b[32m[INFO]\x1b[0m' $@
|
|
||||||
}
|
|
||||||
|
|
||||||
warn() {
|
|
||||||
echo $'\x1b[31m[WARN]\x1b[0m' $@
|
|
||||||
}
|
|
||||||
|
|
||||||
error() {
|
|
||||||
echo $'\x1b[31;1m[ERR]\x1b[0m' $@
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
installPkg() {
|
|
||||||
rootCmd=""
|
|
||||||
if command -v doas &>/dev/null; then
|
|
||||||
rootCmd="doas"
|
|
||||||
elif command -v sudo &>/dev/null; then
|
|
||||||
rootCmd="sudo"
|
|
||||||
else
|
|
||||||
warn "No privilege elevation command (e.g. sudo, doas) detected"
|
|
||||||
fi
|
|
||||||
|
|
||||||
case $1 in
|
|
||||||
pacman) $rootCmd pacman --noconfirm -U ${@:2} ;;
|
|
||||||
apk) $rootCmd apk add --allow-untrusted ${@:2} ;;
|
|
||||||
zypper) $rootCmd zypper --no-gpg-checks install ${@:2} ;;
|
|
||||||
*) $rootCmd $1 install -y ${@:2} ;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
if ! command -v curl &>/dev/null; then
|
|
||||||
error "This script requires the curl command. Please install it and run again."
|
|
||||||
fi
|
|
||||||
|
|
||||||
pkgFormat=""
|
|
||||||
pkgMgr=""
|
|
||||||
if command -v pacman &>/dev/null; then
|
|
||||||
info "Detected pacman"
|
|
||||||
pkgFormat="pkg.tar.zst"
|
|
||||||
pkgMgr="pacman"
|
|
||||||
elif command -v apt &>/dev/null; then
|
|
||||||
info "Detected apt"
|
|
||||||
pkgFormat="deb"
|
|
||||||
pkgMgr="apt"
|
|
||||||
elif command -v dnf &>/dev/null; then
|
|
||||||
info "Detected dnf"
|
|
||||||
pkgFormat="rpm"
|
|
||||||
pkgMgr="dnf"
|
|
||||||
elif command -v yum &>/dev/null; then
|
|
||||||
info "Detected yum"
|
|
||||||
pkgFormat="rpm"
|
|
||||||
pkgMgr="yum"
|
|
||||||
elif command -v zypper &>/dev/null; then
|
|
||||||
info "Detected zypper"
|
|
||||||
pkgFormat="rpm"
|
|
||||||
pkgMgr="zypper"
|
|
||||||
elif command -v apk &>/dev/null; then
|
|
||||||
info "Detected apk"
|
|
||||||
pkgFormat="apk"
|
|
||||||
pkgMgr="apk"
|
|
||||||
else
|
|
||||||
error "No supported package manager detected!"
|
|
||||||
fi
|
|
||||||
|
|
||||||
latestVersion=$(curl -sI 'https://gitea.elara.ws/lure/lure/releases/latest' | grep -io 'location: .*' | rev | cut -d '/' -f1 | rev | tr -d '[:space:]')
|
|
||||||
info "Found latest LURE version:" $latestVersion
|
|
||||||
|
|
||||||
fname="$(mktemp -u -p /tmp "lure.XXXXXXXXXX").${pkgFormat}"
|
|
||||||
url="https://gitea.elara.ws/lure/lure/releases/download/${latestVersion}/linux-user-repository-${latestVersion#v}-linux-$(uname -m).${pkgFormat}"
|
|
||||||
|
|
||||||
info "Downloading LURE package"
|
|
||||||
curl -L $url -o $fname
|
|
||||||
|
|
||||||
info "Installing LURE package"
|
|
||||||
installPkg $pkgMgr $fname
|
|
||||||
|
|
||||||
info "Cleaning up"
|
|
||||||
rm $fname
|
|
||||||
|
|
||||||
info "Done!"
|
|
124
upgrade.go
124
upgrade.go
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* LURE - Linux User REpository
|
* LURE - Linux User REpository
|
||||||
* Copyright (C) 2023 Elara Musayelyan
|
* Copyright (C) 2022 Arsen Musayelyan
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -23,109 +23,75 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"lure.sh/lure/internal/config"
|
"go.arsenm.dev/lure/distro"
|
||||||
"lure.sh/lure/internal/db"
|
"go.arsenm.dev/lure/manager"
|
||||||
"lure.sh/lure/internal/types"
|
|
||||||
"lure.sh/lure/pkg/build"
|
|
||||||
"lure.sh/lure/pkg/distro"
|
|
||||||
"lure.sh/lure/pkg/loggerctx"
|
|
||||||
"lure.sh/lure/pkg/manager"
|
|
||||||
"lure.sh/lure/pkg/repos"
|
|
||||||
"go.elara.ws/vercmp"
|
|
||||||
"golang.org/x/exp/maps"
|
|
||||||
"golang.org/x/exp/slices"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var upgradeCmd = &cli.Command{
|
func upgradeCmd(c *cli.Context) error {
|
||||||
Name: "upgrade",
|
info, err := distro.ParseOSRelease(c.Context)
|
||||||
Usage: "Upgrade all installed packages",
|
if err != nil {
|
||||||
Aliases: []string{"up"},
|
log.Fatal("Error parsing os-release file").Err(err).Send()
|
||||||
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 {
|
|
||||||
ctx := c.Context
|
|
||||||
log := loggerctx.From(ctx)
|
|
||||||
|
|
||||||
info, err := distro.ParseOSRelease(ctx)
|
mgr := manager.Detect()
|
||||||
if err != nil {
|
if mgr == nil {
|
||||||
log.Fatal("Error parsing os-release file").Err(err).Send()
|
log.Fatal("Unable to detect supported package manager on system").Send()
|
||||||
}
|
}
|
||||||
|
|
||||||
mgr := manager.Detect()
|
err = pullRepos(c.Context)
|
||||||
if mgr == nil {
|
if err != nil {
|
||||||
log.Fatal("Unable to detect a supported package manager on the system").Send()
|
log.Fatal("Error pulling repos").Err(err).Send()
|
||||||
}
|
}
|
||||||
|
|
||||||
err = repos.Pull(ctx, config.Config(ctx).Repos)
|
updates, err := checkForUpdates(c.Context, mgr, info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Error pulling repos").Err(err).Send()
|
log.Fatal("Error checking for updates").Err(err).Send()
|
||||||
}
|
}
|
||||||
|
|
||||||
updates, err := checkForUpdates(ctx, mgr, info)
|
if len(updates) > 0 {
|
||||||
if err != nil {
|
installPkgs(c.Context, updates, mgr, false)
|
||||||
log.Fatal("Error checking for updates").Err(err).Send()
|
} else {
|
||||||
}
|
log.Info("There is nothing to do.").Send()
|
||||||
|
}
|
||||||
|
|
||||||
if len(updates) > 0 {
|
return nil
|
||||||
build.InstallPkgs(ctx, 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
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkForUpdates(ctx context.Context, mgr manager.Manager, info *distro.OSRelease) ([]db.Package, error) {
|
func checkForUpdates(ctx context.Context, mgr manager.Manager, info *distro.OSRelease) ([]string, error) {
|
||||||
installed, err := mgr.ListInstalled(nil)
|
installed, err := mgr.ListInstalled(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
pkgNames := maps.Keys(installed)
|
var out []string
|
||||||
found, _, err := repos.FindPkgs(ctx, pkgNames)
|
for name, version := range installed {
|
||||||
if err != nil {
|
scripts, err := findPkg(name)
|
||||||
return nil, err
|
if err != nil {
|
||||||
}
|
|
||||||
|
|
||||||
var out []db.Package
|
|
||||||
for pkgName, pkgs := range found {
|
|
||||||
if slices.Contains(config.Config(ctx).IgnorePkgUpdates, pkgName) {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(pkgs) > 1 {
|
// since we're not using a glob, we can assume a single item
|
||||||
// Puts the element with the highest version first
|
script := scripts[0]
|
||||||
slices.SortFunc(pkgs, func(a, b db.Package) int {
|
|
||||||
return vercmp.Compare(a.Version, b.Version)
|
vars, err := getBuildVars(ctx, script, info)
|
||||||
})
|
if err != nil {
|
||||||
|
log.Fatal("Error getting build variables").Err(err).Send()
|
||||||
}
|
}
|
||||||
|
|
||||||
// First element is the package we want to install
|
repoVer := vars.Version
|
||||||
pkg := pkgs[0]
|
if vars.Release != 0 && vars.Epoch == 0 {
|
||||||
|
repoVer = fmt.Sprintf("%s-%d", vars.Version, vars.Release)
|
||||||
repoVer := pkg.Version
|
} else if vars.Release != 0 && vars.Epoch != 0 {
|
||||||
if pkg.Release != 0 && pkg.Epoch == 0 {
|
repoVer = fmt.Sprintf("%d:%s-%d", vars.Epoch, vars.Version, vars.Release)
|
||||||
repoVer = fmt.Sprintf("%s-%d", pkg.Version, pkg.Release)
|
|
||||||
} else if pkg.Release != 0 && pkg.Epoch != 0 {
|
|
||||||
repoVer = fmt.Sprintf("%d:%s-%d", pkg.Epoch, pkg.Version, pkg.Release)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c := vercmp.Compare(repoVer, installed[pkgName])
|
c := vercmp(repoVer, version)
|
||||||
if c == 0 || c == -1 {
|
if c == 0 || c == -1 {
|
||||||
continue
|
continue
|
||||||
} else if c == 1 {
|
} else if c == 1 {
|
||||||
out = append(out, pkg)
|
out = append(out, name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
170
version.go
Normal file
170
version.go
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
/*
|
||||||
|
* LURE - Linux User REpository
|
||||||
|
* Copyright (C) 2022 Arsen Musayelyan
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:generate scripts/gen-version.sh
|
||||||
|
|
||||||
|
//go:embed version.txt
|
||||||
|
var version string
|
||||||
|
|
||||||
|
// vercmp compares two version strings.
|
||||||
|
// It returns 1 if v1 is greater,
|
||||||
|
// 0 if the versions are equal,
|
||||||
|
// and -1 if v2 is greater
|
||||||
|
func vercmp(v1, v2 string) int {
|
||||||
|
if v1 == v2 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return sepVerCmp(sepLabel(v1), sepLabel(v2))
|
||||||
|
}
|
||||||
|
|
||||||
|
func sepVerCmp(e1, e2 []string) int {
|
||||||
|
if slices.Equal(e1, e2) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// proc stores the amount of elements processed
|
||||||
|
proc := 0
|
||||||
|
|
||||||
|
for i := 0; i < len(e1); i++ {
|
||||||
|
proc++
|
||||||
|
|
||||||
|
if i >= len(e2) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
elem1 := e1[i]
|
||||||
|
elem2 := e2[i]
|
||||||
|
|
||||||
|
if elem1 == elem2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if isNumElem(elem1) && isNumElem(elem2) {
|
||||||
|
elem1v, err := strconv.ParseInt(elem1, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
// error should never happen due to isNumElem()
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
elem2v, err := strconv.ParseInt(elem2, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
// error should never happen due to isNumElem()
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if elem1v > elem2v {
|
||||||
|
return 1
|
||||||
|
} else if elem1v < elem2v {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
} else if isNumElem(elem1) && isAlphaElem(elem2) {
|
||||||
|
return 1
|
||||||
|
} else if isAlphaElem(elem1) && isNumElem(elem2) {
|
||||||
|
return -1
|
||||||
|
} else if isAlphaElem(elem1) && isAlphaElem(elem2) {
|
||||||
|
if elem1 > elem2 {
|
||||||
|
return 1
|
||||||
|
} else if elem1 < elem2 {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if proc < len(e2) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func sepLabel(label string) []string {
|
||||||
|
const (
|
||||||
|
other = iota
|
||||||
|
alpha
|
||||||
|
num
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
curType uint8
|
||||||
|
out []string
|
||||||
|
sb strings.Builder
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, char := range label {
|
||||||
|
if isNum(char) {
|
||||||
|
if curType != num && curType != other {
|
||||||
|
out = append(out, sb.String())
|
||||||
|
sb.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.WriteRune(char)
|
||||||
|
curType = num
|
||||||
|
} else if isAlpha(char) {
|
||||||
|
if curType != alpha && curType != other {
|
||||||
|
out = append(out, sb.String())
|
||||||
|
sb.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.WriteRune(char)
|
||||||
|
curType = alpha
|
||||||
|
} else {
|
||||||
|
if curType != other {
|
||||||
|
out = append(out, sb.String())
|
||||||
|
sb.Reset()
|
||||||
|
}
|
||||||
|
curType = other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if sb.Len() != 0 {
|
||||||
|
out = append(out, sb.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func isNumElem(s string) bool {
|
||||||
|
// Check only the first rune as all elements
|
||||||
|
// should consist of the same type of rune
|
||||||
|
return isNum([]rune(s[:1])[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
func isNum(r rune) bool {
|
||||||
|
return r >= '0' && r <= '9'
|
||||||
|
}
|
||||||
|
|
||||||
|
func isAlphaElem(s string) bool {
|
||||||
|
// Check only the first rune as all elements
|
||||||
|
// should consist of the same type of rune
|
||||||
|
return isAlpha([]rune(s[:1])[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
func isAlpha(r rune) bool {
|
||||||
|
return (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z')
|
||||||
|
}
|
82
version_test.go
Normal file
82
version_test.go
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
* LURE - Linux User REpository
|
||||||
|
* Copyright (C) 2022 Arsen Musayelyan
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSepLabel(t *testing.T) {
|
||||||
|
type item struct {
|
||||||
|
label string
|
||||||
|
expected []string
|
||||||
|
}
|
||||||
|
|
||||||
|
table := []item{
|
||||||
|
{"2.0.1", []string{"2", "0", "1"}},
|
||||||
|
{"v0.0.1", []string{"v", "0", "0", "1"}},
|
||||||
|
{"2xFg33.+f.5", []string{"2", "xFg", "33", "f", "5"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, it := range table {
|
||||||
|
t.Run(it.label, func(t *testing.T) {
|
||||||
|
s := sepLabel(it.label)
|
||||||
|
if !slices.Equal(s, it.expected) {
|
||||||
|
t.Errorf("Expected %v, got %v", it.expected, s)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerCmp(t *testing.T) {
|
||||||
|
type item struct {
|
||||||
|
v1, v2 string
|
||||||
|
expected int
|
||||||
|
}
|
||||||
|
|
||||||
|
table := []item{
|
||||||
|
{"1.0010", "1.9", 1},
|
||||||
|
{"1.05", "1.5", 0},
|
||||||
|
{"1.0", "1", 1},
|
||||||
|
{"1", "1.0", -1},
|
||||||
|
{"2.50", "2.5", 1},
|
||||||
|
{"FC5", "fc4", -1},
|
||||||
|
{"2a", "2.0", -1},
|
||||||
|
{"1.0", "1.fc4", 1},
|
||||||
|
{"3.0.0_fc", "3.0.0.fc", 0},
|
||||||
|
{"4.1__", "4.1+", 0},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, it := range table {
|
||||||
|
t.Run(it.v1+"/"+it.v2, func(t *testing.T) {
|
||||||
|
c := vercmp(it.v1, it.v2)
|
||||||
|
if c != it.expected {
|
||||||
|
t.Errorf("Expected %d, got %d", it.expected, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure opposite comparison gives opposite value
|
||||||
|
c = -vercmp(it.v2, it.v1)
|
||||||
|
if c != it.expected {
|
||||||
|
t.Errorf("Expected %d, got %d (opposite)", it.expected, c)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user