45 Commits

Author SHA1 Message Date
46e2d3166f Add version command
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-22 12:54:03 -08:00
3437df8676 Add a way to check the minimum version supported by a LURE repo 2022-11-22 12:51:45 -08:00
3361358b3c Add documentation for helper commands
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-22 12:26:06 -08:00
3ca052fea7 Use slice for list of distros excluded from /usr/lib64
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-22 11:44:00 -08:00
001e33dd2f Use /usr/lib for libraries on Arch, Alpine, and Void linux
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-21 22:16:06 -08:00
f30f4c7081 Add install-library helper command
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-21 21:45:18 -08:00
5bc81e3a30 Add DISTRO_ID_LIKE environment variable
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-21 21:03:56 -08:00
d941ce231e Install manuals in the proper locations
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-21 16:34:47 -08:00
e22bc0f10c Add install-completion helper command
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-21 15:20:22 -08:00
8ff903b68f Change name of install-bin to install-binary
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-20 20:56:34 -08:00
3bb7fe3690 Add initial helper functions (#39)
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-20 20:52:38 -08:00
26d139c34e Add environment variables to table of contents
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-20 14:17:20 -08:00
8ceb61de9a Run version function before anything else and set the version variable to its output
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-20 14:15:31 -08:00
24c807a941 Resolve ARM variant in the value of the environment variable
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-20 13:57:59 -08:00
da7830d0e3 Make the architecture match on all machines
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-20 13:54:49 -08:00
98a3b26a27 Add documentation for environment variables
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-20 13:50:03 -08:00
da630f648d Expose distro version ID as an environment variable
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-20 21:45:45 +00:00
c489f4864e Parse distro version ID instead of build ID
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-20 21:43:58 +00:00
320342cfb4 Switch badge to self-hosted CI
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-18 08:20:46 +00:00
45ad9fbe39 Add woodpecker CI config
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-18 08:18:02 +00:00
3f2ec8ebd3 Remove epoch from reported version if it is zero 2022-11-14 14:59:16 -08:00
2c2a27c9f7 Fix maintainer spelling in goreleaser config 2022-11-14 14:29:01 -08:00
05a1ecea64 Add provides and conflicts to goreleaser config 2022-11-14 14:25:19 -08:00
d32437e8b2 Show repo name when prompting to select package 2022-11-14 14:04:46 -08:00
8f95ff4676 Actually pass the repodir parameter this time 2022-11-13 19:30:23 -08:00
7442da7105 Add repodir variable 2022-11-13 19:08:58 -08:00
07e41849e9 Remove installed build dependencies from list 2022-11-13 18:49:55 -08:00
27fb08d5ba Make SKIP case-insensitive 2022-11-06 14:48:31 -08:00
d78064179f Change package name to linux-user-repository 2022-11-05 16:40:38 -07:00
2157d9ecce Add GPLv3 headers to all files 2022-11-05 16:11:01 -07:00
b686f810fb Fix link for lure-repo-bot 2022-11-03 14:23:20 -07:00
c856bf0686 Create adding-packages doc 2022-11-03 14:18:57 -07:00
c650c1dae0 Fix postremove script decoding 2022-10-23 01:16:42 -07:00
b3b6612ef2 Update license field docs 2022-10-19 05:37:29 +00:00
baf4cca4fb Remove replace directive and update nfpm 2022-10-15 11:44:46 -07:00
e604f61151 Move packages to working directory after build (Fixes #13) 2022-10-14 01:37:13 -07:00
8e74e58cad Don't pull repos if they've already been pulled in the same command 2022-10-03 15:51:46 -07:00
be48f26e75 Add --needed flag to pacman install commands 2022-10-03 15:48:43 -07:00
b6265f4b1d Add documentation for new ~name parameter 2022-10-03 15:40:37 -07:00
c0e535c630 Separate download.Get() function and add ~name parameter to git downloads 2022-10-03 15:38:38 -07:00
2b6815e287 Add docs for version() function 2022-10-02 20:19:50 -07:00
a42c9b27e7 Add version() function 2022-10-02 20:09:12 -07:00
e2c8335381 Pass function directory using new ScriptFunc arguments 2022-10-02 20:06:00 -07:00
b56641c659 Pass options to subshell when executing a ScriptFunc 2022-10-02 19:59:10 -07:00
61ba975e21 Add CI badge to README 2022-10-01 21:04:42 -07:00
28 changed files with 899 additions and 197 deletions

3
.gitignore vendored
View File

@@ -1,2 +1,3 @@
/lure
/dist/
/dist/
/version.txt

View File

@@ -1,6 +1,7 @@
before:
hooks:
- go mod tidy
- go generate
builds:
- id: lure
env:
@@ -21,6 +22,7 @@ archives:
arm64: aarch64
nfpms:
- id: lure
package_name: linux-user-repository
file_name_template: '{{.PackageName}}-{{.Version}}-{{.Os}}-{{.Arch}}'
description: "Linux User REpository"
replacements:
@@ -28,18 +30,22 @@ nfpms:
amd64: x86_64
arm64: aarch64
homepage: 'https://gitea.arsenm.dev/Arsen6331/lure'
maintainer: 'Arsen Musyaelyan <arsen@arsenm.dev>'
maintainer: 'Arsen Musayelyan <arsen@arsenm.dev>'
license: GPLv3
formats:
- apk
- deb
- rpm
provides:
- lure
conflicts:
- lure
aurs:
- name: lure-bin
homepage: 'https://gitea.arsenm.dev/Arsen6331/lure'
description: "Linux User REpository"
maintainers:
- 'Arsen Musyaelyan <arsen@arsenm.dev>'
- 'Arsen Musayelyan <arsen@arsenm.dev>'
license: GPLv3
private_key: '{{ .Env.AUR_KEY }}'
git_url: 'ssh://aur@aur.archlinux.org/lure-bin.git'

8
.woodpecker.yml Normal file
View File

@@ -0,0 +1,8 @@
pipeline:
release:
image: goreleaser/goreleaser
commands:
- goreleaser release
secrets: [ gitea_token, aur_key ]
when:
event: tag

View File

@@ -1,4 +1,4 @@
lure:
lure: version.txt
go build
clean:
@@ -9,5 +9,8 @@ install: lure
uninstall:
rm -f /usr/local/bin/lure
version.txt:
go generate
.PHONY: install clean uninstall

View File

@@ -1,6 +1,7 @@
# LURE (Linux User REpository)
[![Go Report Card](https://goreportcard.com/badge/go.arsenm.dev/lure)](https://goreportcard.com/report/go.arsenm.dev/lure)
[![status-badge](https://ci.arsenm.dev/api/badges/Arsen6331/lure/status.svg)](https://ci.arsenm.dev/Arsen6331/lure)
[![lure-bin AUR package](https://img.shields.io/aur/version/lure-bin?label=lure-bin&logo=archlinux)](https://aur.archlinux.org/packages/lure-bin/)
LURE is intended to bring the AUR to all distros. It is currently in an ***alpha*** state and may not be stable. It can download a repository, build packages in it using a bash script similar to [PKGBUILD](https://wiki.archlinux.org/title/PKGBUILD), and then install them using your system package manager.

107
build.go
View File

@@ -19,6 +19,7 @@
package main
import (
"bytes"
"context"
"encoding/hex"
"io"
@@ -76,7 +77,7 @@ type Scripts struct {
PreInstall string `sh:"preinstall"`
PostInstall string `sh:"postinstall"`
PreRemove string `sh:"preremove"`
PostRemove string `sh:"postinstall"`
PostRemove string `sh:"postremove"`
PreUpgrade string `sh:"preupgrade"`
PostUpgrade string `sh:"postupgrade"`
PreTrans string `sh:"pretrans"`
@@ -91,12 +92,23 @@ func buildCmd(c *cli.Context) error {
log.Fatal("Unable to detect supported package manager on system").Send()
}
_, pkgNames, err := buildPackage(c.Context, script, mgr)
pkgPaths, _, err := buildPackage(c.Context, script, mgr)
if err != nil {
log.Fatal("Error building package").Err(err).Send()
}
log.Info("Package(s) built successfully").Any("names", pkgNames).Send()
wd, err := os.Getwd()
if err != nil {
log.Fatal("Error getting working directory").Err(err).Send()
}
for _, pkgPath := range pkgPaths {
name := filepath.Base(pkgPath)
err = os.Rename(pkgPath, filepath.Join(wd, name))
if err != nil {
log.Fatal("Error moving the package").Err(err).Send()
}
}
return nil
}
@@ -110,6 +122,9 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st
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
}
@@ -130,6 +145,7 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st
runner, err := interp.New(
interp.Env(expand.ListEnviron(env...)),
interp.StdIO(os.Stdin, os.Stdout, os.Stderr),
interp.ExecHandler(helpers.ExecHandler),
)
if err != nil {
return nil, nil, err
@@ -148,6 +164,30 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st
dec.LikeDistros = false
}
fn, ok := dec.GetFunc("version")
if ok {
log.Info("Executing version()").Send()
buf := &bytes.Buffer{}
err = fn(
ctx,
interp.Dir(filepath.Dir(script)),
interp.StdIO(os.Stdin, buf, os.Stderr),
)
if err != nil {
return nil, nil, err
}
newVer := strings.TrimSpace(buf.String())
err = setVersion(ctx, runner, newVer)
if err != nil {
return nil, nil, err
}
log.Info("Updating version").Str("new", newVer).Send()
}
var vars BuildVars
err = dec.DecodeVars(&vars)
if err != nil {
@@ -189,9 +229,21 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st
return nil, nil, err
}
if len(vars.BuildDepends) > 0 {
installed, err := mgr.ListInstalled(nil)
if err != nil {
return nil, nil, err
}
var buildDeps []string
for _, pkgName := range vars.BuildDepends {
if _, ok := installed[pkgName]; !ok {
buildDeps = append(buildDeps, pkgName)
}
}
if len(buildDeps) > 0 {
log.Info("Installing build dependencies").Send()
installPkgs(ctx, vars.BuildDepends, mgr)
installPkgs(ctx, buildDeps, mgr, false)
}
var builtDeps, builtNames, repoDeps []string
@@ -218,16 +270,18 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st
return nil, nil, err
}
err = setDirVars(ctx, runner, srcdir, pkgdir)
repodir := filepath.Dir(script)
err = setDirVars(ctx, runner, srcdir, pkgdir, repodir)
if err != nil {
return nil, nil, err
}
fn, ok := dec.GetFunc("prepare")
fn, ok = dec.GetFunc("prepare")
if ok {
log.Info("Executing prepare()").Send()
err = fn(ctx, srcdir)
err = fn(ctx, interp.Dir(srcdir))
if err != nil {
return nil, nil, err
}
@@ -237,7 +291,7 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st
if ok {
log.Info("Executing build()").Send()
err = fn(ctx, srcdir)
err = fn(ctx, interp.Dir(srcdir))
if err != nil {
return nil, nil, err
}
@@ -247,7 +301,7 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st
if ok {
log.Info("Executing package()").Send()
err = fn(ctx, srcdir)
err = fn(ctx, interp.Dir(srcdir))
if err != nil {
return nil, nil, err
}
@@ -386,7 +440,7 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st
return nil, nil, err
}
if len(vars.BuildDepends) > 0 {
if len(buildDeps) > 0 {
var removeBuildDeps bool
err = survey.AskOne(&survey.Confirm{
Message: "Would you like to remove build dependencies?",
@@ -401,7 +455,7 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st
AsRoot: true,
NoConfirm: true,
},
vars.BuildDepends...,
buildDeps...,
)
if err != nil {
return nil, nil, err
@@ -416,14 +470,21 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st
func genBuildEnv(info *distro.OSRelease) []string {
env := os.Environ()
arch := runtime.GOARCH
if arch == "arm" {
arch = cpu.ARMVariant()
}
env = append(
env,
"DISTRO_NAME="+info.Name,
"DISTRO_PRETTY_NAME="+info.PrettyName,
"DISTRO_ID="+info.ID,
"DISTRO_BUILD_ID="+info.BuildID,
"DISTRO_VERSION_ID="+info.VersionID,
"DISTRO_ID_LIKE="+strings.Join(info.Like, " "),
"ARCH="+runtime.GOARCH,
"ARCH="+arch,
"NCPU="+strconv.Itoa(runtime.NumCPU()),
)
@@ -442,7 +503,7 @@ func getSources(ctx context.Context, srcdir string, bv *BuildVars) error {
EncloseGit: true,
}
if bv.Checksums[i] != "SKIP" {
if !strings.EqualFold(bv.Checksums[i], "SKIP") {
checksum, err := hex.DecodeString(bv.Checksums[i])
if err != nil {
return err
@@ -461,8 +522,8 @@ func getSources(ctx context.Context, srcdir string, bv *BuildVars) error {
// setDirVars sets srcdir and pkgdir. It's a very hacky way of doing so,
// but setting the runner's Env and Vars fields doesn't seem to work.
func setDirVars(ctx context.Context, runner *interp.Runner, srcdir, pkgdir string) error {
cmd := "srcdir='" + srcdir + "'\npkgdir='" + pkgdir + "'\n"
func setDirVars(ctx context.Context, runner *interp.Runner, srcdir, pkgdir, repodir string) error {
cmd := "srcdir='" + srcdir + "'\npkgdir='" + pkgdir + "'\nrepodir='" + repodir + "'\n"
fl, err := syntax.NewParser().Parse(strings.NewReader(cmd), "vars")
if err != nil {
return err
@@ -548,6 +609,10 @@ func getBuildVars(ctx context.Context, script string, info *distro.OSRelease) (*
}
func archMatches(architectures []string) bool {
if slices.Contains(architectures, "all") {
return true
}
arch := runtime.GOARCH
if arch == "arm" {
@@ -561,6 +626,14 @@ func archMatches(architectures []string) bool {
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)

View File

@@ -37,7 +37,7 @@ type OSRelease struct {
PrettyName string
ID string
Like []string
BuildID string
VersionID string
ANSIColor string
HomeURL string
DocumentationURL string
@@ -86,7 +86,7 @@ func ParseOSRelease(ctx context.Context) (*OSRelease, error) {
Name: runner.Vars["NAME"].Str,
PrettyName: runner.Vars["PRETTY_NAME"].Str,
ID: runner.Vars["ID"].Str,
BuildID: runner.Vars["BUILD_ID"].Str,
VersionID: runner.Vars["VERSION_ID"].Str,
ANSIColor: runner.Vars["ANSI_COLOR"].Str,
HomeURL: runner.Vars["HOME_URL"].Str,
DocumentationURL: runner.Vars["DOCUMENTATION_URL"].Str,

View File

@@ -2,4 +2,5 @@
- [Build Scripts](build-scripts.md)
- [Usage](usage.md)
- [Configuration](configuration.md)
- [Configuration](configuration.md)
- [Adding Packages to LURE's repo](adding-packages.md)

23
docs/adding-packages.md Normal file
View File

@@ -0,0 +1,23 @@
# Adding Packages to LURE's repo
## Requirements
- `go` (1.18+)
- `git`
- `lure-analyzer`
- `go install go.arsenm.dev/lure-repo-bot/cmd/lure-analyzer@latest`
- `shfmt`
- May be available in distro repos
- `go install mvdan.cc/sh/v3/cmd/shfmt@latest`
---
## How to submit a package
LURE's repo is hosted on Github at https://github.com/Arsen6331/lure-repo. In it, there are multiple directories each containing a `lure.sh` file. In order to add a package to LURE's repo, simply create a PR with a [build script](./build-scripts.md) and place it in a directory with the same name as the package.
Upon submitting the PR, [lure-repo-bot](https://github.com/Arsen6331/lure-repo-bot) will pull your PR and analyze it, providing suggestions for fixes as review comments. If there are no problems, the bot will approve your changes. If there are issues, re-request review from the bot after you've finished applying the fixes and it will automatically review the PR again.
All scripts submitted to the LURE repo should be formatted with `shfmt`. If they are not properly formatted, Github Actions will add suggestions in the "Files Changed" tab of the PR.
Once your PR is merged, LURE will pull the changed repo and your package will be available for people to install.

View File

@@ -26,6 +26,28 @@ LURE uses build scripts similar to the AUR's PKGBUILDs. This is the documentatio
- [checksums](#checksums)
- [backup](#backup)
- [scripts](#scripts)
- [Functions](#functions)
- [prepare](#prepare)
- [version](#version-1)
- [build](#build)
- [package](#package)
- [Environment Variables](#environment-variables)
- [DISTRO_NAME](#distro_name)
- [DISTRO_PRETTY_NAME](#distro_pretty_name)
- [DISTRO_ID](#distro_id)
- [DISTRO_VERSION_ID](#distro_version_id)
- [ARCH](#arch)
- [NCPU](#ncpu)
- [Helper Commands](#helper-commands)
- [install-binary](#install-binary)
- [install-systemd](#install-systemd)
- [install-systemd-user](#install-systemd-user)
- [install-config](#install-config)
- [install-license](#install-license)
- [install-completion](#install-completion)
- [install-manual](#install-manual)
- [install-desktop](#install-desktop)
- [install-library](#install-library)
---
@@ -119,7 +141,7 @@ LURE_ARM_VARIANT=arm5 lure install ...
### licenses
The `licenses` array contains the licenses used by this package. Some valid values include `GPLv3` and `MIT`.
The `licenses` array contains the licenses used by this package. In order to standardize license names, values should be [SPDX Identifiers](https://spdx.org/licenses/) such as `Apache-2.0`, `MIT`, and `GPL-3.0-only`. If the project uses a license that is not standardized in SPDX, use the value `Custom`. If the project has multiple nonstandard licenses, include `Custom` as many times as there are nonstandard licenses.
### provides
@@ -163,6 +185,7 @@ If the URL scheme starts with `git+`, the source will be downloaded as a git rep
- `~branch`: Specify which branch of the repo to check out.
- `~commit`: Specify which commit of the repo to check out.
- `~depth`: Specify what depth should be used when cloning the repo. Must be an integer.
- `~name`: Specify the name of the directory into which the git repo should be cloned.
Examples:
@@ -217,13 +240,30 @@ The rest of the scripts are available in all packages.
## Functions
Any variables 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 start in the `$srcdir` directory
All functions except for `version()` are executed in the `$srcdir` directory
### version
The `version()` function is the first to run. It updates the `version` variable. This allows for automatically deriving the version from sources. This is most useful for git packages, which usually don't need to be changed, so their `version` variable stays the same.
An example of using this for git:
```bash
version() {
cd "$srcdir/itd"
printf "r%s.%s" "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)"
}
```
The AUR equivalent is the [`pkgver()` function](https://wiki.archlinux.org/title/VCS_package_guidelines#The_pkgver()_function)
This function does not run in `$srcdir` because it is executed before the source directory is even created. Instead, it runs in `$repodir`.
### prepare
The `prepare()` function runs first. It 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.
### build
@@ -246,4 +286,185 @@ package() {
install -Dm755 bin ${pkgdir}/usr/bin/bin
install -Dm644 bin.cfg ${pkgdir}/etc/bin.cfg
}
```
---
## Environment Variables
LURE exposes several values as environment variables for use in build scripts.
### DISTRO_NAME
The `DISTRO_NAME` variable is the name of the distro as defined in its `os-release` file.
For example, it's set to `Fedora Linux` in a Fedora 36 docker image
### DISTRO_PRETTY_NAME
The `DISTRO_PRETTY_NAME` variable is the "pretty" name of the distro as defined in its `os-release` file.
For example, it's set to `Fedora Linux 36 (Container Image)` in a Fedora 36 docker image
### DISTRO_ID
The `DISTRO_ID` variable is the identifier of the distro as defined in its `os-release` file. This is the same as what LURE uses for overrides.
For example, it's set to `fedora` in a Fedora 36 docker image
### DISTRO_ID_LIKE
The `DISTRO_ID_LIKE` variable contains identifiers of similar distros to the one running, separated by spaces.
For example, it's set to `opensuse suse` in an OpenSUSE Tumbleweed docker image and `rhel fedora` in a CentOS 8 docker image.
### DISTRO_VERSION_ID
The `DISTRO_VERSION_ID` variable is the version identifier of the distro as defined in its `os-release` file.
For example, it's set to `36` in a Fedora 36 docker image and `11` in a Debian Bullseye docker image
### ARCH
The `ARCH` variable is the architecture of the machine running the script. It uses the same naming convention as the values in the `architectures` array
### NCPU
The `NCPU` variable is the amount of CPUs available on the machine running the script. It will be set to `8` on a quad core machine with hyperthreading, for example.
---
## Helper Commands
LURE provides various commands to help packagers create proper cross-distro packages. These commands should be used wherever possible instead of doing the tasks manually.
### install-binary
`install-binary` accepts 1-2 arguments. The first argument is the binary you'd like to install. The second is the filename that should be used.
If the filename argument is not provided, tha name of the input file will be used.
Examples:
```bash
install-binary ./itd
install-binary ./itd itd-2
```
### install-systemd
`install-systemd` installs regular systemd system services (see `install-systemd-user` for user services)
It accepts 1-2 arguments. The first argument is the service you'd like to install. The second is the filename that should be used.
If the filename argument is not provided, tha name of the input file will be used.
Examples:
```bash
install-systemd ./syncthing@.service
install-systemd-user ./syncthing@.service sync-thing@.service
```
### install-systemd-user
`install-systemd-user` installs systemd user services (services like `itd` meant to be started with `--user`).
It accepts 1-2 arguments. The first argument is the service you'd like to install. The second is the filename that should be used.
If the filename argument is not provided, tha name of the input file will be used.
Examples:
```bash
install-systemd-user ./itd.service
install-systemd-user ./itd.service infinitime-daemon.service
```
### install-config
`install-config` installs configuration files into the `/etc` directory
It accepts 1-2 arguments. The first argument is the config you'd like to install. The second is the filename that should be used.
If the filename argument is not provided, tha name of the input file will be used.
Examples:
```bash
install-config ./itd.toml
install-config ./itd.example.toml itd.toml
```
### install-license
`install-license` installs a license file
It accepts 1-2 arguments. The first argument is the config you'd like to install. The second is the filename that should be used.
If the filename argument is not provided, tha name of the input file will be used.
Examples:
```bash
install-license ./LICENSE itd/LICENSE
```
### install-completion
`install-completion` installs shell completions
It currently supports `bash`, `zsh`, and `fish`
Completions are read from stdin, so they can either be piped in or retrieved from files
Two arguments are required for this function. The first one is the name of the shell and the second is the name of the completion.
Examples:
```bash
./k9s completion fish | install-completion fish k9s
install-completion bash k9s <./k9s/completions/k9s.fish
```
### install-manual
`install-manual` installs manpages. It accepts a single argument, which is the path to the manpage.
The install path will be determined based on the number at the end of the filename. If a number cannot be extracted, an error will be returned.
Examples:
```bash
install-manual ./man/strelaysrv.1
install-manual ./mdoc.7
```
### install-desktop
`install-desktop` installs desktop files for applications. It accepts 1-2 arguments. The first argument is the config you'd like to install. The second is the filename that should be used.
If the filename argument is not provided, tha name of the input file will be used.
Examples:
```bash
install-desktop ./${name}/share/admc.desktop
install-desktop ./${name}/share/admc.desktop admc-app.desktop
```
### install-library
`install-library` installs shared and static libraries to the correct location.
This is the most important helper as it contains logic to figure out where to install libraries based on the target distro and CPU architecture. It should almost always be used to install all libraries.
It accepts 1-2 arguments. The first argument is the config you'd like to install. The second is the filename that should be used.
If the filename argument is not provided, tha name of the input file will be used.
Examples:
```bash
install-library ./${name}/build/libadldap.so
```

View File

@@ -23,6 +23,7 @@ import (
"context"
"crypto/sha256"
"errors"
"hash"
"io"
"net/http"
"net/url"
@@ -67,6 +68,22 @@ func Get(ctx context.Context, opts GetOptions) error {
}
query := src.Query()
if strings.HasPrefix(src.Scheme, "git+") {
err = getGit(ctx, src, query, opts)
if err != nil {
return err
}
} else {
err = getFile(ctx, src, query, opts)
if err != nil {
return err
}
}
return nil
}
func getGit(ctx context.Context, src *url.URL, query url.Values, opts GetOptions) (err error) {
tag := query.Get("~tag")
query.Del("~tag")
@@ -79,6 +96,9 @@ func Get(ctx context.Context, opts GetOptions) error {
depthStr := query.Get("~depth")
query.Del("~depth")
name := query.Get("~name")
query.Del("~name")
var refName plumbing.ReferenceName
if tag != "" {
refName = plumbing.NewTagReferenceName(tag)
@@ -86,169 +106,185 @@ func Get(ctx context.Context, opts GetOptions) error {
refName = plumbing.NewBranchReferenceName(branch)
}
if strings.HasPrefix(src.Scheme, "git+") {
src.Scheme = strings.TrimPrefix(src.Scheme, "git+")
src.RawQuery = query.Encode()
src.Scheme = strings.TrimPrefix(src.Scheme, "git+")
src.RawQuery = query.Encode()
name := path.Base(src.Path)
if name == "" {
name = path.Base(src.Path)
name = strings.TrimSuffix(name, ".git")
}
dstDir := opts.Destination
if opts.EncloseGit {
dstDir = filepath.Join(opts.Destination, name)
}
dstDir := opts.Destination
if opts.EncloseGit {
dstDir = filepath.Join(opts.Destination, name)
}
depth := 0
if depthStr != "" {
depth, err = strconv.Atoi(depthStr)
if err != nil {
return err
}
}
cloneOpts := &git.CloneOptions{
URL: src.String(),
Progress: os.Stderr,
Depth: depth,
}
repo, err := git.PlainCloneContext(ctx, dstDir, false, cloneOpts)
depth := 0
if depthStr != "" {
depth, err = strconv.Atoi(depthStr)
if err != nil {
return err
}
}
w, err := repo.Worktree()
if err != nil {
return err
}
cloneOpts := &git.CloneOptions{
URL: src.String(),
Progress: os.Stderr,
Depth: depth,
}
checkoutOpts := &git.CheckoutOptions{}
if refName != "" {
checkoutOpts.Branch = refName
} else if commit != "" {
checkoutOpts.Hash = plumbing.NewHash(commit)
} else {
return nil
}
repo, err := git.PlainCloneContext(ctx, dstDir, false, cloneOpts)
if err != nil {
return err
}
return w.Checkout(checkoutOpts)
w, err := repo.Worktree()
if err != nil {
return err
}
checkoutOpts := &git.CheckoutOptions{}
if refName != "" {
checkoutOpts.Branch = refName
} else if commit != "" {
checkoutOpts.Hash = plumbing.NewHash(commit)
} else {
name := query.Get("~name")
query.Del("~name")
return nil
}
archive := query.Get("~archive")
query.Del("~archive")
return w.Checkout(checkoutOpts)
}
src.RawQuery = query.Encode()
func getFile(ctx context.Context, src *url.URL, query url.Values, opts GetOptions) error {
name := query.Get("~name")
query.Del("~name")
if name == "" {
name = path.Base(src.Path)
}
archive := query.Get("~archive")
query.Del("~archive")
req, err := http.NewRequestWithContext(ctx, http.MethodGet, src.String(), nil)
src.RawQuery = query.Encode()
if name == "" {
name = path.Base(src.Path)
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, src.String(), nil)
if err != nil {
return err
}
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
hash := sha256.New()
format, input, err := archiver.Identify(name, res.Body)
if err == archiver.ErrNoMatch || archive == "false" {
fl, err := os.Create(filepath.Join(opts.Destination, name))
if err != nil {
return err
}
res, err := http.DefaultClient.Do(req)
w := io.MultiWriter(hash, fl)
_, err = io.Copy(w, input)
if err != nil {
return err
}
hash := sha256.New()
res.Body.Close()
fl.Close()
format, input, err := archiver.Identify(name, res.Body)
if err == archiver.ErrNoMatch || archive == "false" {
fl, err := os.Create(filepath.Join(opts.Destination, name))
if err != nil {
return err
if opts.SHA256Sum != nil {
sum := hash.Sum(nil)
if !bytes.Equal(opts.SHA256Sum, sum) {
return ErrChecksumMismatch
}
w := io.MultiWriter(hash, fl)
_, err = io.Copy(w, input)
if err != nil {
return err
}
res.Body.Close()
fl.Close()
if opts.SHA256Sum != nil {
sum := hash.Sum(nil)
if !bytes.Equal(opts.SHA256Sum, sum) {
return ErrChecksumMismatch
}
}
} else if err != nil {
}
} else if err != nil {
return err
} else {
err = extractFile(ctx, input, hash, format, name, opts)
if err != nil {
return err
} else {
r := io.TeeReader(input, hash)
fname := format.Name()
switch format := format.(type) {
case archiver.Extractor:
err = format.Extract(ctx, r, nil, func(ctx context.Context, f archiver.File) error {
fr, err := f.Open()
if err != nil {
return err
}
defer fr.Close()
path := filepath.Join(opts.Destination, f.NameInArchive)
err = os.MkdirAll(filepath.Dir(path), 0o755)
if err != nil {
return err
}
if f.IsDir() {
err = os.Mkdir(path, 0o755)
if err != nil {
return err
}
} else {
outFl, err := os.Create(path)
if err != nil {
return err
}
defer outFl.Close()
_, err = io.Copy(outFl, fr)
return err
}
return nil
})
if err != nil {
return err
}
case archiver.Decompressor:
rc, err := format.OpenReader(r)
if err != nil {
return err
}
defer rc.Close()
path := filepath.Join(opts.Destination, name)
path = strings.TrimSuffix(path, fname)
outFl, err := os.Create(path)
if err != nil {
return err
}
_, err = io.Copy(outFl, rc)
if err != nil {
return err
}
}
if opts.SHA256Sum != nil {
sum := hash.Sum(nil)
if !bytes.Equal(opts.SHA256Sum, sum) {
return ErrChecksumMismatch
}
}
}
}
return nil
}
func extractFile(ctx context.Context, input io.Reader, hash hash.Hash, format archiver.Format, name string, opts GetOptions) (err error) {
r := io.TeeReader(input, hash)
fname := format.Name()
switch format := format.(type) {
case archiver.Extractor:
err = format.Extract(ctx, r, nil, func(ctx context.Context, f archiver.File) error {
fr, err := f.Open()
if err != nil {
return err
}
defer fr.Close()
fi, err := f.Stat()
if err != nil {
return err
}
fm := fi.Mode()
path := filepath.Join(opts.Destination, f.NameInArchive)
err = os.MkdirAll(filepath.Dir(path), 0o755)
if err != nil {
return err
}
if f.IsDir() {
err = os.Mkdir(path, 0o755)
if err != nil {
return err
}
} else {
outFl, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, fm.Perm())
if err != nil {
return err
}
defer outFl.Close()
_, err = io.Copy(outFl, fr)
return err
}
return nil
})
if err != nil {
return err
}
case archiver.Decompressor:
rc, err := format.OpenReader(r)
if err != nil {
return err
}
defer rc.Close()
path := filepath.Join(opts.Destination, name)
path = strings.TrimSuffix(path, fname)
outFl, err := os.Create(path)
if err != nil {
return err
}
_, err = io.Copy(outFl, rc)
if err != nil {
return err
}
}
if opts.SHA256Sum != nil {
sum := hash.Sum(nil)
if !bytes.Equal(opts.SHA256Sum, sum) {
return ErrChecksumMismatch
}
}

7
go.mod
View File

@@ -2,12 +2,10 @@ module go.arsenm.dev/lure
go 1.18
replace github.com/goreleaser/nfpm/v2 => github.com/Arsen6331/nfpm/v2 v2.0.0-20220922210414-eae88e8ea4b5
require (
github.com/AlecAivazis/survey/v2 v2.3.6
github.com/go-git/go-git/v5 v5.4.2
github.com/goreleaser/nfpm/v2 v2.18.1
github.com/goreleaser/nfpm/v2 v2.20.0
github.com/mholt/archiver/v4 v4.0.0-alpha.7
github.com/mitchellh/mapstructure v1.5.0
github.com/pelletier/go-toml/v2 v2.0.5
@@ -39,7 +37,6 @@ require (
github.com/go-git/go-billy/v5 v5.3.1 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/rpmpack v0.0.0-20220314092521-38642b5e571e // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/gookit/color v1.5.1 // indirect
github.com/goreleaser/chglog v0.2.2 // indirect
@@ -49,7 +46,7 @@ require (
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/kevinburke/ssh_config v1.1.0 // indirect
github.com/klauspost/compress v1.15.5 // indirect
github.com/klauspost/compress v1.15.11 // indirect
github.com/klauspost/pgzip v1.2.5 // indirect
github.com/mattn/go-colorable v0.1.2 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect

14
go.sum
View File

@@ -2,8 +2,6 @@ github.com/AlecAivazis/survey/v2 v2.3.6 h1:NvTuVHISgTHEHeBFqt6BHOe4Ny/NwGZr7w+F8
github.com/AlecAivazis/survey/v2 v2.3.6/go.mod h1:4AuI9b7RjAR+G7v9+C4YSlX/YL3K3cWNXgWXOhllqvI=
github.com/AlekSi/pointer v1.2.0 h1:glcy/gc4h8HnG2Z3ZECSzZ1IX1x2JxRVuDzaJwQE0+w=
github.com/AlekSi/pointer v1.2.0/go.mod h1:gZGfd3dpW4vEc/UlyfKKi1roIqcCgwOIvb0tSNSBle0=
github.com/Arsen6331/nfpm/v2 v2.0.0-20220922210414-eae88e8ea4b5 h1:SFWe7Ho60w43hXEIxCdiAXZvUyM9GF/L90jMK42s8gU=
github.com/Arsen6331/nfpm/v2 v2.0.0-20220922210414-eae88e8ea4b5/go.mod h1:O4K1mvEORY78CSCInptGG5MWJ19yr9xFTgWWUtY1R7o=
github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
@@ -72,11 +70,8 @@ github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJA
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/rpmpack v0.0.0-20220314092521-38642b5e571e h1:6Jn9JtfCn20uycra92LxTkq5yfBKNSFlRJPBk8/Cxhg=
github.com/google/rpmpack v0.0.0-20220314092521-38642b5e571e/go.mod h1:83rLnx5vhPyN/mDzBYJWtiPf+9xnSVQynTpqZWe7OnY=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gookit/color v1.5.1 h1:Vjg2VEcdHpwq+oY63s/ksHrgJYCTo0bwWvmmYWdE9fQ=
@@ -86,6 +81,8 @@ github.com/goreleaser/chglog v0.2.2 h1:V7nf07baXtGAgGevvqgW2MM4kZ6gOr12vKNSAU3VI
github.com/goreleaser/chglog v0.2.2/go.mod h1:2s5JwtCOWjZa8AIneL+xdUl9SRuigCjRHNHsX30dupE=
github.com/goreleaser/fileglob v1.3.0 h1:/X6J7U8lbDpQtBvGcwwPS6OpzkNVlVEsFUVRx9+k+7I=
github.com/goreleaser/fileglob v1.3.0/go.mod h1:Jx6BoXv3mbYkEzwm9THo7xbr5egkAraxkGorbJb4RxU=
github.com/goreleaser/nfpm/v2 v2.20.0 h1:Q/CrX54KUMluz6+M/pjTbknFd5Dao8qXi0C6ZuFCtfY=
github.com/goreleaser/nfpm/v2 v2.20.0/go.mod h1:/Fh6XfwT/T+D4qtNC2iXmHSD/1UT20JkvBXyJ6nFmOY=
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw=
@@ -103,9 +100,8 @@ github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT
github.com/kevinburke/ssh_config v1.1.0 h1:pH/t1WS9NzT8go394IqZeJTMHVm6Cr6ZJ6AQ+mdNo/o=
github.com/kevinburke/ssh_config v1.1.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.15.5 h1:qyCLMz2JCrKADihKOh9FxnW3houKeNsp2h5OEz0QSEA=
github.com/klauspost/compress v1.15.5/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
github.com/klauspost/compress v1.15.11 h1:Lcadnb3RKGin4FYM/orgq0qde+nc15E5Cbqg4B9Sx9c=
github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE=
github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=

212
helpers.go Normal file
View File

@@ -0,0 +1,212 @@
package main
import (
"errors"
"fmt"
"io"
"os"
"path/filepath"
"strconv"
"strings"
"unsafe"
"go.arsenm.dev/lure/internal/shutils"
"golang.org/x/exp/slices"
"mvdan.cc/sh/v3/interp"
)
var (
ErrNoPipe = errors.New("command requires data to be piped in")
ErrNoDetectManNum = errors.New("manual number cannot be detected from the filename")
)
var helpers = shutils.ExecFuncs{
"install-binary": installHelperCmd("/usr/bin", 0o755),
"install-systemd-user": installHelperCmd("/usr/lib/systemd/user", 0o644),
"install-systemd": installHelperCmd("/usr/lib/systemd/system", 0o644),
"install-config": installHelperCmd("/etc", 0o644),
"install-license": installHelperCmd("/usr/share/licenses", 0o644),
"install-desktop": installHelperCmd("/usr/share/applications", 0o644),
"install-manual": installManualCmd,
"install-completion": installCompletionCmd,
"install-library": installLibraryCmd,
}
func installHelperCmd(prefix string, perms os.FileMode) shutils.ExecFunc {
return func(hc interp.HandlerContext, cmd string, args []string) error {
if len(args) < 1 {
return shutils.InsufficientArgsError(cmd, 1, len(args))
}
from := args[0]
to := ""
if len(args) > 1 {
to = filepath.Join(hc.Env.Get("pkgdir").Str, prefix, args[1])
} else {
to = filepath.Join(hc.Env.Get("pkgdir").Str, prefix, filepath.Base(from))
}
err := helperInstall(from, to, perms)
if err != nil {
return fmt.Errorf("%s: %w", cmd, err)
}
return nil
}
}
func installManualCmd(hc interp.HandlerContext, cmd string, args []string) error {
if len(args) < 1 {
return shutils.InsufficientArgsError(cmd, 1, len(args))
}
from := args[0]
number := filepath.Base(from)
// The man page may be compressed with gzip.
// If it is, the .gz extension must be removed to properly
// detect the number at the end of the filename.
number = strings.TrimSuffix(number, ".gz")
number = strings.TrimPrefix(filepath.Ext(number), ".")
// If number is not actually a number, return an error
if _, err := strconv.Atoi(number); err != nil {
return fmt.Errorf("install-manual: %w", ErrNoDetectManNum)
}
prefix := "/usr/share/man/man" + number
to := filepath.Join(hc.Env.Get("pkgdir").Str, prefix, filepath.Base(from))
return helperInstall(from, to, 0o644)
}
func installCompletionCmd(hc interp.HandlerContext, cmd string, args []string) error {
// If the command's stdin is the same as the system's,
// that means nothing was piped in. In this case, return an error.
if hc.Stdin == os.Stdin {
return fmt.Errorf("install-completion: %w", ErrNoPipe)
}
if len(args) < 2 {
return shutils.InsufficientArgsError(cmd, 2, len(args))
}
shell := args[0]
name := args[1]
var prefix string
switch shell {
case "bash":
prefix = "/usr/share/bash-completion/completion"
case "zsh":
prefix = "/usr/share/zsh/site-functions"
name = "_" + name
case "fish":
prefix = "/usr/share/fish/vendor_completions.d"
name += ".fish"
}
path := filepath.Join(hc.Env.Get("pkgdir").Str, prefix, name)
err := os.MkdirAll(filepath.Dir(path), 0o755)
if err != nil {
return err
}
dst, err := os.OpenFile(path, os.O_TRUNC|os.O_CREATE|os.O_RDWR, 0o644)
if err != nil {
return err
}
defer dst.Close()
_, err = io.Copy(dst, hc.Stdin)
return err
}
func installLibraryCmd(hc interp.HandlerContext, cmd string, args []string) error {
prefix := getLibPrefix(hc)
fn := installHelperCmd(prefix, 0o755)
return fn(hc, cmd, args)
}
// See https://wiki.debian.org/Multiarch/Tuples
var multiarchTupleMap = map[string]string{
"386": "i386-linux-gnu",
"amd64": "x86_64-linux-gnu",
"arm5": "arm-linux-gnueabi",
"arm6": "arm-linux-gnueabihf",
"arm7": "arm-linux-gnueabihf",
"arm64": "aarch64-linux-gnu",
"mips": "mips-linux-gnu",
"mipsle": "mipsel-linux-gnu",
"mips64": "mips64-linux-gnuabi64",
"mips64le": "mips64el-linux-gnuabi64",
"ppc64": "powerpc64-linux-gnu",
"ppc64le": "powerpc64le-linux-gnu",
"s390x": "s390x-linux-gnu",
"riscv64": "riscv64-linux-gnu",
"loong64": "loongarch64-linux-gnu",
}
// usrLibDistros is a list of distros that don't support
// /usr/lib64, and must use /usr/lib
var usrLibDistros = []string{
"arch",
"alpine",
"void",
"chimera",
}
// Based on CMake's GNUInstallDirs
func getLibPrefix(hc interp.HandlerContext) string {
if dir, ok := os.LookupEnv("LURE_LIB_DIR"); ok {
return dir
}
out := "/usr/lib"
distroID := hc.Env.Get("DISTRO_ID").Str
distroLike := strings.Split(hc.Env.Get("DISTRO_ID_LIKE").Str, " ")
for _, usrLibDistro := range usrLibDistros {
if distroID == usrLibDistro || slices.Contains(distroLike, usrLibDistro) {
return out
}
}
wordSize := unsafe.Sizeof(uintptr(0))
if wordSize == 8 {
out = "/usr/lib64"
}
architecture := hc.Env.Get("ARCH").Str
if distroID == "debian" || slices.Contains(distroLike, "debian") {
triple, ok := multiarchTupleMap[architecture]
if ok {
out = filepath.Join("/usr/lib", triple)
}
}
return out
}
func helperInstall(from, to string, perms os.FileMode) error {
err := os.MkdirAll(filepath.Dir(to), 0o755)
if err != nil {
return err
}
src, err := os.Open(from)
if err != nil {
return err
}
defer src.Close()
dst, err := os.OpenFile(to, os.O_TRUNC|os.O_CREATE|os.O_RDWR, perms)
if err != nil {
return err
}
defer dst.Close()
_, err = io.Copy(dst, src)
return err
}

View File

@@ -36,21 +36,23 @@ func installCmd(c *cli.Context) error {
log.Fatal("Unable to detect supported package manager on system").Send()
}
installPkgs(c.Context, args.Slice(), mgr)
installPkgs(c.Context, args.Slice(), mgr, true)
return nil
}
func installPkgs(ctx context.Context, pkgs []string, mgr manager.Manager) {
err := pullRepos(ctx)
if err != nil {
log.Fatal("Error pulling repositories").Err(err).Send()
func installPkgs(ctx context.Context, pkgs []string, mgr manager.Manager, pull bool) {
if pull {
err := pullRepos(ctx)
if err != nil {
log.Fatal("Error pulling repositories").Err(err).Send()
}
}
scripts, notFound := findPkgs(pkgs)
if len(notFound) > 0 {
err = mgr.Install(nil, notFound...)
err := mgr.Install(nil, notFound...)
if err != nil {
log.Fatal("Error installing native packages").Err(err).Send()
}

View File

@@ -1,3 +1,21 @@
/*
* LURE - Linux User REpository
* Copyright (C) 2022 Arsen Musayelyan
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cpu
import (

View File

@@ -142,7 +142,7 @@ func (d *Decoder) DecodeVars(val any) error {
return nil
}
type ScriptFunc func(ctx context.Context, sir string, args ...string) error
type ScriptFunc func(ctx context.Context, opts ...interp.RunnerOption) error
// GetFunc returns a function corresponding to a bash function
// with the given name
@@ -152,10 +152,11 @@ func (d *Decoder) GetFunc(name string) (ScriptFunc, bool) {
return nil, false
}
return func(ctx context.Context, dir string, args ...string) error {
return func(ctx context.Context, opts ...interp.RunnerOption) error {
sub := d.runner.Subshell()
interp.Params(args...)(sub)
interp.Dir(dir)(sub)
for _, opt := range opts {
opt(sub)
}
return sub.Run(ctx, fn)
}, true
}

View File

@@ -20,21 +20,34 @@ package shutils
import (
"context"
"fmt"
"time"
"mvdan.cc/sh/v3/interp"
)
type ExecFuncs map[string]func(interp.HandlerContext, []string) uint8
func InsufficientArgsError(cmd string, exp, got int) error {
argsWord := "arguments"
if exp == 1 {
argsWord = "argument"
}
return fmt.Errorf("%s: command requires at least %d %s, got %d", cmd, exp, argsWord, got)
}
type ExecFunc func(hc interp.HandlerContext, name string, args []string) error
type ExecFuncs map[string]ExecFunc
func (ef ExecFuncs) ExecHandler(ctx context.Context, args []string) error {
name := args[0]
if fn, ok := ef[name]; ok {
hctx := interp.HandlerCtx(ctx)
ec := fn(hctx, args)
if ec != 0 {
return interp.NewExitStatus(ec)
if len(args) > 1 {
return fn(hctx, args[0], args[1:])
} else {
return fn(hctx, args[0], nil)
}
}

18
list.go
View File

@@ -1,3 +1,21 @@
/*
* 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 (

10
main.go
View File

@@ -128,6 +128,11 @@ func main() {
Aliases: []string{"ref"},
Action: refreshCmd,
},
{
Name: "version",
Usage: "Display the current LURE version and exit",
Action: displayVersion,
},
},
}
@@ -136,3 +141,8 @@ func main() {
log.Error("Error while running app").Err(err).Send()
}
}
func displayVersion(c *cli.Context) error {
print(version)
return nil
}

View File

@@ -137,6 +137,7 @@ func (d *DNF) ListInstalled(opts *Opts) (map[string]string, error) {
if !ok {
continue
}
version = strings.TrimPrefix(version, "0:")
out[name] = version
}

View File

@@ -60,7 +60,7 @@ func (p *Pacman) Sync(opts *Opts) error {
func (p *Pacman) Install(opts *Opts, pkgs ...string) error {
opts = ensureOpts(opts)
cmd := p.getCmd(opts, "pacman", "-S")
cmd := p.getCmd(opts, "pacman", "-S", "--needed")
cmd.Args = append(cmd.Args, pkgs...)
setCmdEnv(cmd)
err := cmd.Run()
@@ -72,7 +72,7 @@ func (p *Pacman) Install(opts *Opts, pkgs ...string) error {
func (p *Pacman) InstallLocal(opts *Opts, pkgs ...string) error {
opts = ensureOpts(opts)
cmd := p.getCmd(opts, "pacman", "-U")
cmd := p.getCmd(opts, "pacman", "-U", "--needed")
cmd.Args = append(cmd.Args, pkgs...)
setCmdEnv(cmd)
err := cmd.Run()

View File

@@ -137,6 +137,7 @@ func (y *YUM) ListInstalled(opts *Opts) (map[string]string, error) {
if !ok {
continue
}
version = strings.TrimPrefix(version, "0:")
out[name] = version
}

View File

@@ -137,6 +137,7 @@ func (z *Zypper) ListInstalled(opts *Opts) (map[string]string, error) {
if !ok {
continue
}
version = strings.TrimPrefix(version, "0:")
out[name] = version
}

47
repo.go
View File

@@ -41,6 +41,12 @@ func (p PkgNotFoundError) Error() string {
return "package '" + p.pkgName + "' could not be found in any repository"
}
type RepoConfig struct {
Repo struct {
MinVersion string `toml:"minVersion"`
}
}
func addrepoCmd(c *cli.Context) error {
name := c.String("name")
repoURL := c.String("url")
@@ -146,7 +152,9 @@ func findPkg(pkg string) ([]string, error) {
func pkgPrompt(options []string) ([]string, error) {
names := make([]string, len(options))
for i, option := range options {
names[i] = filepath.Base(filepath.Dir(option))
pkgDir := filepath.Dir(option)
repoDir := filepath.Dir(pkgDir)
names[i] = filepath.Base(repoDir) + "/" + filepath.Base(pkgDir)
}
prompt := &survey.MultiSelect{
@@ -221,6 +229,26 @@ func pullRepos(ctx context.Context) error {
} else if err != nil {
return err
}
fl, err := w.Filesystem.Open("lure-repo.toml")
if err != nil {
log.Warn("Git repository does not appear to be a valid LURE repo").Str("repo", repo.Name).Send()
continue
}
var repoCfg RepoConfig
err = toml.NewDecoder(fl).Decode(&repoCfg)
if err != nil {
return err
}
fl.Close()
currentVer, _, _ := strings.Cut(version, "-")
if vercmp(currentVer, repoCfg.Repo.MinVersion) == -1 {
log.Warn("LURE repo's minumum LURE version is greater than the current version. Try updating LURE if something doesn't work.").Str("repo", repo.Name).Send()
}
continue
}
err = os.RemoveAll(repoDir)
@@ -244,6 +272,23 @@ func pullRepos(ctx context.Context) error {
if err != nil {
return err
}
fl, err := os.Open(filepath.Join(repoDir, "lure-repo.toml"))
if err != nil {
log.Warn("Git repository does not appear to be a valid LURE repo").Str("repo", repo.Name).Send()
}
var repoCfg RepoConfig
err = toml.NewDecoder(fl).Decode(&repoCfg)
if err != nil {
return err
}
fl.Close()
currentVer, _, _ := strings.Cut(version, "-")
if vercmp(currentVer, repoCfg.Repo.MinVersion) == -1 {
log.Warn("LURE repo's minumum LURE version is greater than the current version. Try updating LURE if something doesn't work.").Str("repo", repo.Name).Send()
}
}
return nil

3
scripts/gen-version.sh Executable file
View File

@@ -0,0 +1,3 @@
#!/bin/bash
git describe --tags > version.txt

View File

@@ -38,13 +38,18 @@ func upgradeCmd(c *cli.Context) error {
log.Fatal("Unable to detect supported package manager on system").Send()
}
err = pullRepos(c.Context)
if err != nil {
log.Fatal("Error pulling repos").Err(err).Send()
}
updates, err := checkForUpdates(c.Context, mgr, info)
if err != nil {
log.Fatal("Error checking for updates").Err(err).Send()
}
if len(updates) > 0 {
installPkgs(c.Context, updates, mgr)
installPkgs(c.Context, updates, mgr, false)
} else {
log.Info("There is nothing to do.").Send()
}

View File

@@ -19,12 +19,18 @@
package main
import (
_ "embed"
"strconv"
"strings"
"golang.org/x/exp/slices"
)
//go:generate scripts/gen-version.sh
//go:embed version.txt
var version string
// vercmp compares two version strings.
// It returns 1 if v1 is greater,
// 0 if the versions are equal,