143 Commits

Author SHA1 Message Date
43baf8024a Return error if db.JSON decode hook gets an invalid type
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-01-01 19:28:16 -08:00
8a1d0f4f54 Add installmisc target for non-binary files
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-01-01 19:17:06 -08:00
806f49c472 Move CLI helper functions into internal/cliutils
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-29 12:25:37 -08:00
55132437b3 Skip packages with empty name
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-29 12:01:54 -08:00
8b26e96af0 Create variable for all flag 2022-12-29 11:53:57 -08:00
bd41075e8a Display only info for current distro in lure info
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-28 19:04:38 -08:00
8225f41d0e Fix nil argument when -P flag is not given
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-28 18:44:48 -08:00
db060db4b1 Convert distro.ParseOSRelease() into a singleton 2022-12-28 18:40:20 -08:00
863b6e923a Split overrides into separate package with tests 2022-12-28 18:39:31 -08:00
51b41bdd90 Remove replace directive for pebble
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-28 20:07:58 +00:00
76e073c77f Remove debug code
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-28 12:00:38 -08:00
770881bf67 Provide flag for passing package manager arguments
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-28 11:57:07 -08:00
be79eba4c2 Provide ability to pass args to package manager, add --allowerasing for dnf and yum 2022-12-27 14:44:48 -08:00
3e6d5f57cf Add tests for internal/shutils package
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-27 12:13:22 -08:00
db5c344b2d Add tests for internal/repos package 2022-12-27 11:23:34 -08:00
a750f4630e Move json_array_contains sql function registration to internal/db 2022-12-27 11:14:04 -08:00
852e98088b Add tests for internal/db package 2022-12-27 11:11:19 -08:00
c832359e43 Add noconfirm flags to install script
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-24 16:35:00 -08:00
fae4337748 Replace GenjiDB with SQLite in README
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-24 16:31:42 -08:00
f21d02e5d1 Fix Supports Architecture filter
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-24 16:21:02 -08:00
ef98a37b4a Add back 32-bit architectures
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-24 14:09:41 -08:00
3829dc4cc4 Fix old database check
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-24 14:03:19 -08:00
efca9d116e Actually eliminate duplicate results this time
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-24 13:58:05 -08:00
acb71b873e Eliminate duplicate results
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-24 13:04:34 -08:00
8366a2ce86 Switch to SQLite DB
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-24 12:56:02 -08:00
37a6c682b5 Restructure and add more documentation
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-23 18:09:27 -08:00
80187b0969 Improve README
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-23 17:49:02 -08:00
cecaead5b0 Fix symlink resolution during package build
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-22 14:18:41 -08:00
a84622e182 Mention web interface in README
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-22 14:00:06 -08:00
dd86148c22 Move logo into repo
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-22 13:55:29 -08:00
6aed71af24 Add Logo to README
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-22 21:52:36 +00:00
422e41db40 Disable docker build cache
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-21 11:37:30 -08:00
8dc0e53700 Add lure-api-server README
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-20 21:46:55 -08:00
891df91fee Add webhook handler to api server
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-20 21:46:04 -08:00
b7922f9687 Add Dockerfile and docker.sh script
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-20 21:24:15 -08:00
0b53c16f9c Allow all CORS origins and headers
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-20 14:02:57 -08:00
81013ce376 Switch to CLI logger
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-19 19:30:35 -08:00
eb4e2281fa Add completion files to makefile and goreleaser
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-18 18:27:02 -08:00
c9bc1e7fc3 Remove unused function 2022-12-18 17:59:58 -08:00
22a74aa793 Add ignorePkgUpdates field to config
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-18 17:46:40 -08:00
964c45ffc4 Add GetBuildScript API endpoint
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-18 13:52:57 -08:00
9c0d9d0a34 Move generated api to internal, rename lure-api to lure-api-server
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-18 13:41:52 -08:00
a8a870ce86 Remove comments from DB and API
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-18 00:15:14 -08:00
fe477ba6d1 Add GetPkg endpoint to API
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-17 23:10:34 -08:00
6cd0802f64 Add twirp RPC backend API for lure-web
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-17 21:01:50 -08:00
19ced9795c Switch to riscv64 fork of pebble
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-04 16:03:13 -08:00
dbfcde0125 Remove 32-bit architectures until 32-bit support is added to Pebble
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-04 15:44:18 -08:00
14078ec904 Fix SQL for deleting packages
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-04 14:52:46 -08:00
715ec7fbe8 Clear screen when starting pager
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-03 18:22:58 -08:00
3c260a7c5f Allow parsing additional scripts via source when pulling repos
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-03 13:09:42 -08:00
5e8ed1c2a0 Provide scriptdir during first interpreter pass
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-03 12:08:58 -08:00
c51248793e Do two parsing passes when building a package, and prompt user to view script after the first
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-03 11:52:09 -08:00
5ed538c2c4 Move promptViewScript() call to installScripts()
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-02 23:28:49 -08:00
56ba3ec644 Add build script viewer and prompt users asking if they want to read the script
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-02 22:10:03 -08:00
5d411ac538 Add new commands to usage.md table of contents
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-02 13:45:57 -08:00
269222b688 Update usage docs
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-02 13:45:04 -08:00
79c0e7eb12 Add ability to list packages matching pattern
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-02 13:15:07 -08:00
fcd5c1c14b Add info logs to fix command
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-02 12:45:34 -08:00
9fa3977d3a Add fix command
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-02 12:43:00 -08:00
c2d396d68f Fix bash completion install path
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-02 12:31:53 -08:00
23fd711be2 Add bash and zsh completions
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-02 12:29:55 -08:00
8f9bdf69ad Make sure scripts are valid when updating DB
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-01 11:08:20 -08:00
dd33a30c35 Rename repodir to scriptdir
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-01 10:53:03 -08:00
a106100312 Show version of installed package when listing installed packages
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-01 00:14:07 -08:00
df72d95ab7 Disable root in ls command 2022-12-01 00:11:27 -08:00
40cf0da2c0 Fix closing brace in infoCmd() function
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-12-01 07:16:54 +00:00
66a6b30d87 Pull repos before every command that uses them
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-30 23:04:52 -08:00
4eac9cd8aa Remove unused internal/db.GetPkg() function
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-30 22:54:03 -08:00
664f01a794 Fix spacing in database schema
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-30 22:52:21 -08:00
0ac8ccac81 Handle the possibility of the DB not existing on startup
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-30 22:38:22 -08:00
56550a5135 Merge pull request #44 from Arsen6331/add-database
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-30 22:23:56 -08:00
b4f4633f6a Use strict database table schema
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-30 22:22:37 -08:00
67b9801f42 Add doc comments 2022-11-30 22:15:34 -08:00
7164aac0b4 Create function for getting canonical CPU architecture
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-30 22:02:36 -08:00
4e71a5c35c Create function for asking yes or no questions
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-30 21:58:21 -08:00
eaf49a4594 Make actions in internal/repos unexported
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-30 21:47:07 -08:00
c0439a2b90 Remove unused imports 2022-11-30 21:46:41 -08:00
43d6461c71 Move pkgPrompt() to cli.go 2022-11-30 21:43:52 -08:00
2e591d9f1c Remove unnecessary PkgNotFoundError 2022-11-30 21:43:10 -08:00
7d00c7b5fb Add --installed/-I flag to ls command
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-30 21:42:04 -08:00
ead0c79139 Use correct verb when prompting user to choose packages
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-30 19:33:40 -08:00
1377ef1bc9 Remove packages from DB when removing repository
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-30 19:14:07 -08:00
a88adb43fe Switch all LURE operations to use new DB
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-30 19:10:17 -08:00
3663a8ef8f Add FindPkgs to repos package
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-30 18:26:25 -08:00
5f12d2aee2 Build and update database when repo pulled
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-30 14:34:52 -08:00
99b70859d1 Move version comparison to separate package
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-30 11:46:51 -08:00
715fd6ccc9 Begin moving repository operations into separate package
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-30 11:45:13 -08:00
9a06894cfa Move configuration into separate package
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-30 11:14:21 -08:00
01a9f23a64 Switch to global log variable 2022-11-30 10:00:50 -08:00
6013bdf8b9 Add git-version to table of contents
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-29 13:50:35 -08:00
fbf0aa3b4f Add git-version helper command
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-29 13:49:26 -08:00
e4b8348823 Run version function in srcdir
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-29 13:33:47 -08:00
554987325b Revert 8ceb61d
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-29 13:30:58 -08:00
74051861bf Properly resolve relative paths in helper functions
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-29 13:02:48 -08:00
edf5b67825 Mention install script in README
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-27 12:46:08 -08:00
84336e4a30 Add install script
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-27 12:30:27 -08:00
b3479bdf91 Add archlinux packager
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-27 19:59:23 +00:00
a2bd151837 Fix install-completion example
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-11-22 22:49:30 +00:00
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
75 changed files with 7018 additions and 696 deletions

5
.gitignore vendored
View File

@@ -1,2 +1,5 @@
/lure
/dist/
/lure-api-server
/cmd/lure-api-server/lure-api-server
/dist/
/internal/config/version.txt

View File

@@ -1,6 +1,7 @@
before:
hooks:
- go mod tidy
- go generate
builds:
- id: lure
env:
@@ -9,10 +10,10 @@ builds:
goos:
- linux
goarch:
- 386
- amd64
- arm
- 386
- arm64
- arm
- riscv64
archives:
- replacements:
@@ -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,28 @@ 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
- archlinux
provides:
- lure
conflicts:
- lure
contents:
- src: scripts/completion/bash
dst: /usr/share/bash-completion/completions/lure
- src: scripts/completion/zsh
dst: /usr/share/zsh/site-functions/_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'
@@ -52,7 +64,11 @@ aurs:
- pacman
package: |-
# binaries
install -Dm755 "./lure" "${pkgdir}/usr/bin/lure"
install -Dm755 ./lure "${pkgdir}/usr/bin/lure"
# completions
install -Dm755 ./scripts/completion/bash /usr/share/bash-completion/completions/lure
install -Dm755 ./scripts/completion/zsh /usr/share/zsh/site-functions/_lure
release:
gitea:
owner: Arsen6331

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,13 +1,22 @@
lure:
PREFIX ?= /usr/local
lure: version.txt
go build
clean:
rm -f lure
install: lure
sudo install -Dm755 lure /usr/local/bin/lure
install: lure installmisc
install -Dm755 lure $(DESTDIR)$(PREFIX)/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:
rm -f /usr/local/bin/lure
version.txt:
go generate ./...
.PHONY: install clean uninstall

View File

@@ -1,16 +1,31 @@
<img src="assets/logo.png" alt="LURE Logo" width="200">
# 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.
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 an ***alpha*** state and may not be stable. It is currently able to successfully and consistently build and install packages on various distributions, but there are some bugs that still need to be ironed out.
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.
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.
---
## Installation
### Install script
The LURE install script will automatically download and install the appropriate LURE package on your system. To use it, simply run the following command:
```bash
curl https://www.arsenm.dev/lure.sh | bash
```
**IMPORTANT**: This method is not recommended as it executes any code that is stored at that URL. In order to make sure nothing malicious is going to occur, download the script and inspect it before running.
### Packages
Distro packages and binary archives are provided at the latest Gitea release: https://gitea.arsenm.dev/Arsen6331/lure/releases/latest
LURE is also available on the AUR as [lure-bin](https://aur.archlinux.org/packages/lure-bin)
@@ -27,11 +42,7 @@ sudo make install
## Why?
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`.
Arch Linux's AUR is a very useful feature. It's one of the main reasons I and many others use Arch on most of their devices. However, Arch is not always a good choice. Whether you're running a server that needs to be stable over long periods of time, or you're a beginner and feel intimidated by Arch, there are many different reasons not to use it. Such useful functionality should not be restricted to only a single distro. That is what LURE is meant to solve.
---
@@ -41,25 +52,31 @@ The documentation for LURE is in the [docs](docs) directory in this repo.
---
## Web Interface
LURE now has a web interface! It's open source, licensed under the AGPLv3 (https://gitea.arsenm.dev/Arsen6331/lure-web), and is available at https://lure.arsenm.dev.
---
## Repositories
Unlike the AUR, LURE supports using multiple repos. Also unlike the AUR, LURE's repos are a single git repo containing all the build scripts. Inside each LURE repo, there should be a separate directory for each package containing a `lure.sh` script, which is a PKGBUILD-like build script for LURE. The default repository is hosted on Github: https://github.com/Arsen6331/lure-repo.
Unlike the AUR, LURE supports third-party repositories. Also unlike the AUR, LURE's repos are single git repositories containing all the build scripts. Inside each LURE repo, there is 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, and information about its packages is displayed at https://lure.arsenm.dev/pkgs.
---
## Dependencies
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:
As mentioned before, LURE has zero dependencies after compilation. Thanks to the following projects for making this possible:
- Bash: https://github.com/mvdan/sh
- Git: https://github.com/go-git/go-git
- Archiver: https://github.com/mholt/archiver
- nfpm: https://github.com/goreleaser/nfpm
- https://github.com/mvdan/sh
- https://github.com/go-git/go-git
- https://github.com/mholt/archiver
- 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
- Automated docker-based testing tool

BIN
assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

237
build.go
View File

@@ -19,6 +19,7 @@
package main
import (
"bytes"
"context"
"encoding/hex"
"io"
@@ -28,7 +29,6 @@ import (
"strconv"
"strings"
"github.com/AlecAivazis/survey/v2"
_ "github.com/goreleaser/nfpm/v2/apk"
_ "github.com/goreleaser/nfpm/v2/arch"
_ "github.com/goreleaser/nfpm/v2/deb"
@@ -38,9 +38,13 @@ import (
"github.com/goreleaser/nfpm/v2"
"github.com/goreleaser/nfpm/v2/files"
"go.arsenm.dev/logger/log"
"go.arsenm.dev/lure/distro"
"go.arsenm.dev/lure/download"
"go.arsenm.dev/lure/internal/cliutils"
"go.arsenm.dev/lure/internal/config"
"go.arsenm.dev/lure/internal/cpu"
"go.arsenm.dev/lure/internal/repos"
"go.arsenm.dev/lure/internal/shutils"
"go.arsenm.dev/lure/internal/shutils/decoder"
"go.arsenm.dev/lure/manager"
@@ -76,7 +80,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"`
@@ -86,21 +90,39 @@ type Scripts struct {
func buildCmd(c *cli.Context) error {
script := c.String("script")
err := repos.Pull(c.Context, gdb, cfg.Repos)
if err != nil {
log.Fatal("Error pulling repositories").Err(err).Send()
}
mgr := manager.Detect()
if mgr == nil {
log.Fatal("Unable to detect supported package manager on system").Send()
}
_, 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
}
// buildPackage builds the script at the given path. It returns two slices. One contains the paths
// to the built package(s), the other contains the names of the built package(s).
func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]string, []string, error) {
info, err := distro.ParseOSRelease(ctx)
if err != nil {
@@ -110,6 +132,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
}
@@ -125,11 +150,19 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st
fl.Close()
env := genBuildEnv(info)
scriptDir := filepath.Dir(script)
env := genBuildEnv(info, scriptDir)
// The first pass is just used to get variable values and runs before
// the script is displayed, so it is restricted so as to prevent malicious
// code from executing.
runner, err := interp.New(
interp.Env(expand.ListEnviron(env...)),
interp.StdIO(os.Stdin, os.Stdout, os.Stderr),
interp.ExecHandler(rHelpers.ExecHandler(shutils.NopExec)),
interp.ReadDirHandler(shutils.RestrictedReadDir(scriptDir)),
interp.StatHandler(shutils.RestrictedStat(scriptDir)),
interp.OpenHandler(shutils.RestrictedOpen(scriptDir)),
)
if err != nil {
return nil, nil, err
@@ -154,15 +187,17 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st
return nil, nil, err
}
err = cliutils.PromptViewScript(script, vars.Name, cfg.PagerStyle)
if err != nil {
log.Fatal("Failed to prompt user to view build script").Err(err).Send()
}
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,
)
buildAnyway, err := cliutils.YesNoPrompt("Your system's CPU architecture doesn't match this package. Do you want to build anyway?", true)
if err != nil {
return nil, nil, err
}
if !buildAnyway {
os.Exit(1)
}
@@ -170,7 +205,32 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st
log.Info("Building package").Str("name", vars.Name).Str("version", vars.Version).Send()
baseDir := filepath.Join(cacheDir, "pkgs", vars.Name)
// The second pass will be used to execute the actual functions,
// so it cannot be restricted. The script has already been displayed
// to the user by this point, so it should be safe
runner, err = interp.New(
interp.Env(expand.ListEnviron(env...)),
interp.StdIO(os.Stdin, os.Stdout, os.Stderr),
interp.ExecHandler(helpers.ExecHandler(nil)),
)
if err != nil {
return nil, nil, err
}
err = runner.Run(ctx, file)
if err != nil {
return nil, nil, err
}
dec = decoder.New(info, runner)
// If distro was changed, the list of like distros
// no longer applies, so disable its use
if distroChanged {
dec.LikeDistros = false
}
baseDir := filepath.Join(config.PkgsDir, vars.Name)
srcdir := filepath.Join(baseDir, "src")
pkgdir := filepath.Join(baseDir, "pkg")
@@ -189,16 +249,38 @@ 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 {
found, notFound, err := repos.FindPkgs(gdb, buildDeps)
if err != nil {
return nil, nil, err
}
log.Info("Installing build dependencies").Send()
installPkgs(ctx, vars.BuildDepends, mgr)
installPkgs(ctx, cliutils.FlattenPkgs(found, "install"), notFound, mgr)
}
var builtDeps, builtNames, repoDeps []string
if len(vars.Depends) > 0 {
log.Info("Installing dependencies").Send()
scripts, notFound := findPkgs(vars.Depends)
found, notFound, err := repos.FindPkgs(gdb, vars.Depends)
if err != nil {
return nil, nil, err
}
scripts := getScriptPaths(cliutils.FlattenPkgs(found, "install"))
for _, script := range scripts {
pkgPaths, pkgNames, err := buildPackage(ctx, script, mgr)
if err != nil {
@@ -223,11 +305,36 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st
return nil, nil, err
}
fn, ok := dec.GetFunc("prepare")
fn, ok := dec.GetFunc("version")
if ok {
log.Info("Executing version()").Send()
buf := &bytes.Buffer{}
err = fn(
ctx,
interp.Dir(srcdir),
interp.StdIO(os.Stdin, buf, os.Stderr),
)
if err != nil {
return nil, nil, err
}
newVer := strings.TrimSpace(buf.String())
err = setVersion(ctx, runner, newVer)
if err != nil {
return nil, nil, err
}
vars.Version = newVer
log.Info("Updating version").Str("new", newVer).Send()
}
fn, ok = dec.GetFunc("prepare")
if ok {
log.Info("Executing prepare()").Send()
err = fn(ctx, srcdir)
err = fn(ctx, interp.Dir(srcdir))
if err != nil {
return nil, nil, err
}
@@ -237,7 +344,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 +354,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
}
@@ -264,7 +371,7 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st
pkgInfo := &nfpm.Info{
Name: vars.Name,
Description: vars.Description,
Arch: runtime.GOARCH,
Arch: cpu.Arch(),
Version: vars.Version,
Release: strconv.Itoa(vars.Release),
Homepage: vars.Homepage,
@@ -288,10 +395,6 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st
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)
@@ -325,6 +428,7 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st
if err != nil {
return err
}
link = strings.TrimPrefix(link, pkgdir)
contents = append(contents, &files.Content{
Source: link,
@@ -386,11 +490,8 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st
return nil, nil, err
}
if len(vars.BuildDepends) > 0 {
var removeBuildDeps bool
err = survey.AskOne(&survey.Confirm{
Message: "Would you like to remove build dependencies?",
}, &removeBuildDeps)
if len(buildDeps) > 0 {
removeBuildDeps, err := cliutils.YesNoPrompt("Would you like to remove build dependencies?", false)
if err != nil {
return nil, nil, err
}
@@ -401,7 +502,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
@@ -414,17 +515,21 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st
return pkgPaths, pkgNames, nil
}
func genBuildEnv(info *distro.OSRelease) []string {
func genBuildEnv(info *distro.OSRelease, scriptdir string) []string {
env := os.Environ()
env = append(
env,
"DISTRO_NAME="+info.Name,
"DISTRO_PRETTY_NAME="+info.PrettyName,
"DISTRO_ID="+info.ID,
"DISTRO_BUILD_ID="+info.BuildID,
"DISTRO_VERSION_ID="+info.VersionID,
"DISTRO_ID_LIKE="+strings.Join(info.Like, " "),
"ARCH="+runtime.GOARCH,
"ARCH="+cpu.Arch(),
"NCPU="+strconv.Itoa(runtime.NumCPU()),
"scriptdir="+scriptdir,
)
return env
@@ -442,7 +547,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
@@ -506,61 +611,29 @@ func setScripts(vars *BuildVars, info *nfpm.Info, scriptDir string) {
}
}
// getBuildVars only gets the build variables, while disabling exec, stat, open, and readdir
func getBuildVars(ctx context.Context, script string, info *distro.OSRelease) (*BuildVars, error) {
fl, err := os.Open(script)
if err != nil {
return nil, err
}
file, err := syntax.NewParser().Parse(fl, "lure.sh")
if err != nil {
return nil, err
}
fl.Close()
runner, err := interp.New(
interp.Env(expand.ListEnviron()),
interp.ExecHandler(shutils.NopExec),
interp.StatHandler(shutils.NopStat),
interp.OpenHandler(shutils.NopOpen),
interp.ReadDirHandler(shutils.NopReadDir),
)
if err != nil {
return nil, err
}
err = runner.Run(ctx, file)
if err != nil {
return nil, err
}
dec := decoder.New(info, runner)
var vars BuildVars
err = dec.DecodeVars(&vars)
if err != nil {
return nil, err
}
return &vars, nil
}
// archMatches checks if your system architecture matches
// one of the provided architectures
func archMatches(architectures []string) bool {
arch := runtime.GOARCH
if arch == "arm" {
arch = cpu.ARMVariant()
if slices.Contains(architectures, "all") {
return true
}
if slices.Contains(architectures, "arm") {
architectures = append(architectures, cpu.ARMVariant())
}
return slices.Contains(architectures, arch)
return slices.Contains(architectures, cpu.Arch())
}
func setVersion(ctx context.Context, r *interp.Runner, to string) error {
fl, err := syntax.NewParser().Parse(strings.NewReader("version='"+to+"'"), "")
if err != nil {
return err
}
return r.Run(ctx, fl)
}
// uniq removes all duplicates from string slices
func uniq(ss ...*[]string) {
for _, s := range ss {
slices.Sort(*s)

View File

@@ -0,0 +1,3 @@
FROM alpine:latest
COPY lure-api-server /usr/bin/lure-api-server
ENTRYPOINT lure-api-server

View File

@@ -0,0 +1,3 @@
# lure-api-server
`lure-api-server` is the backend API server for lure-web, the web interface for LURE.

127
cmd/lure-api-server/api.go Normal file
View File

@@ -0,0 +1,127 @@
package main
import (
"context"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/jmoiron/sqlx"
"github.com/twitchtv/twirp"
"go.arsenm.dev/lure/internal/api"
"go.arsenm.dev/lure/internal/config"
"go.arsenm.dev/lure/internal/db"
)
type lureWebAPI struct {
db *sqlx.DB
}
func (l lureWebAPI) Search(ctx context.Context, req *api.SearchRequest) (*api.SearchResponse, error) {
query := "(name LIKE ? OR description LIKE ? OR json_array_contains(provides, ?))"
args := []any{"%" + req.Query + "%", "%" + req.Query + "%", req.Query}
if req.FilterValue != nil && req.FilterType != api.FILTER_TYPE_NO_FILTER {
switch req.FilterType {
case api.FILTER_TYPE_IN_REPOSITORY:
query += " AND repository = ?"
case api.FILTER_TYPE_SUPPORTS_ARCH:
query += " AND json_array_contains(architectures, ?)"
}
args = append(args, *req.FilterValue)
}
if req.SortBy != api.SORT_BY_UNSORTED {
switch req.SortBy {
case api.SORT_BY_NAME:
query += " ORDER BY name"
case api.SORT_BY_REPOSITORY:
query += " ORDER BY repository"
case api.SORT_BY_VERSION:
query += " ORDER BY version"
}
}
if req.Limit != 0 {
query += " LIMIT " + strconv.FormatInt(req.Limit, 10)
}
result, err := db.GetPkgs(l.db, query, args...)
if err != nil {
return nil, err
}
out := &api.SearchResponse{}
for result.Next() {
pkg := &db.Package{}
err = result.StructScan(pkg)
if err != nil {
return nil, err
}
out.Packages = append(out.Packages, dbPkgToAPI(pkg))
}
return out, err
}
func (l lureWebAPI) GetPkg(ctx context.Context, req *api.GetPackageRequest) (*api.Package, error) {
pkg, err := db.GetPkg(l.db, "name = ? AND repository = ?", req.Name, req.Repository)
if err != nil {
return nil, err
}
return dbPkgToAPI(pkg), nil
}
func (l lureWebAPI) GetBuildScript(ctx context.Context, req *api.GetBuildScriptRequest) (*api.GetBuildScriptResponse, error) {
if strings.ContainsAny(req.Name, "./") || strings.ContainsAny(req.Repository, "./") {
return nil, twirp.NewError(twirp.InvalidArgument, "name and repository must not contain . or /")
}
scriptPath := filepath.Join(config.RepoDir, req.Repository, req.Name, "lure.sh")
_, err := os.Stat(scriptPath)
if os.IsNotExist(err) {
return nil, twirp.NewError(twirp.NotFound, "requested package not found")
} else if err != nil {
return nil, err
}
data, err := os.ReadFile(scriptPath)
if err != nil {
return nil, err
}
return &api.GetBuildScriptResponse{Script: string(data)}, nil
}
func dbPkgToAPI(pkg *db.Package) *api.Package {
return &api.Package{
Name: pkg.Name,
Repository: pkg.Repository,
Version: pkg.Version,
Release: int64(pkg.Release),
Epoch: ptr(int64(pkg.Epoch)),
Description: &pkg.Description,
Homepage: &pkg.Homepage,
Maintainer: &pkg.Maintainer,
Architectures: pkg.Architectures.Val,
Licenses: pkg.Licenses.Val,
Provides: pkg.Provides.Val,
Conflicts: pkg.Conflicts.Val,
Replaces: pkg.Replaces.Val,
Depends: dbMapToAPI(pkg.Depends.Val),
BuildDepends: dbMapToAPI(pkg.BuildDepends.Val),
}
}
func ptr[T any](v T) *T {
return &v
}
func dbMapToAPI(m map[string][]string) map[string]*api.StringList {
out := make(map[string]*api.StringList, len(m))
for override, list := range m {
out[override] = &api.StringList{Entries: list}
}
return out
}

View File

@@ -0,0 +1,16 @@
package main
import (
"go.arsenm.dev/logger/log"
"go.arsenm.dev/lure/internal/config"
"go.arsenm.dev/lure/internal/types"
)
var cfg types.Config
func init() {
err := config.Decode(&cfg)
if err != nil {
log.Fatal("Error decoding config file").Err(err).Send()
}
}

32
cmd/lure-api-server/db.go Normal file
View File

@@ -0,0 +1,32 @@
package main
import (
"os"
"github.com/jmoiron/sqlx"
"go.arsenm.dev/logger/log"
"go.arsenm.dev/lure/internal/config"
"go.arsenm.dev/lure/internal/db"
)
var gdb *sqlx.DB
func init() {
fi, err := os.Stat(config.DBPath)
if err == nil {
// TODO: This should be removed by the first stable release.
if fi.IsDir() {
log.Fatal("Your package cache database is using the old database engine. Please remove ~/.cache/lure and then run `lure ref`.").Send()
}
}
gdb, err = sqlx.Open("sqlite", config.DBPath)
if err != nil {
log.Fatal("Error opening database").Err(err).Send()
}
err = db.Init(gdb)
if err != nil {
log.Fatal("Error initializing database").Err(err).Send()
}
}

14
cmd/lure-api-server/docker.sh Executable file
View File

@@ -0,0 +1,14 @@
#!/bin/bash
CGO_ENABLED=0 GOARCH=amd64 GOOS=linux go build
docker buildx build --platform linux/amd64 --tag arsen6331/lure-api-server:amd64 --no-cache .
CGO_ENABLED=0 GOARCH=arm64 GOOS=linux go build
docker buildx build --platform linux/arm64/v8 --tag arsen6331/lure-api-server:arm64 --no-cache .
docker login
docker push arsen6331/lure-api-server -a
docker manifest rm arsen6331/lure-api-server:latest
docker manifest create arsen6331/lure-api-server:latest --amend arsen6331/lure-api-server:arm64 --amend arsen6331/lure-api-server:amd64
docker manifest push arsen6331/lure-api-server:latest

View File

@@ -0,0 +1,77 @@
package main
import (
"context"
"flag"
"net"
"net/http"
"os"
"github.com/twitchtv/twirp"
"go.arsenm.dev/logger"
"go.arsenm.dev/logger/log"
"go.arsenm.dev/lure/internal/api"
"go.arsenm.dev/lure/internal/repos"
)
func init() {
log.Logger = logger.NewPretty(os.Stderr)
}
func main() {
ctx := context.Background()
addr := flag.String("a", ":8080", "Listen address for API server")
logFile := flag.String("l", "", "Output file for JSON log")
flag.Parse()
if *logFile != "" {
fl, err := os.Create(*logFile)
if err != nil {
log.Fatal("Error creating log file").Err(err).Send()
}
defer fl.Close()
log.Logger = logger.NewMulti(log.Logger, logger.NewJSON(fl))
}
err := repos.Pull(ctx, gdb, cfg.Repos)
if err != nil {
log.Fatal("Error pulling repositories").Err(err).Send()
}
sigCh := make(chan struct{}, 200)
go repoPullWorker(ctx, sigCh)
var handler http.Handler
handler = api.NewAPIServer(
lureWebAPI{db: gdb},
twirp.WithServerPathPrefix(""),
)
handler = allowAllCORSHandler(handler)
handler = handleWebhook(handler, sigCh)
ln, err := net.Listen("tcp", *addr)
if err != nil {
log.Fatal("Error starting listener").Err(err).Send()
}
log.Info("Starting HTTP API server").Str("addr", ln.Addr().String()).Send()
err = http.Serve(ln, handler)
if err != nil {
log.Fatal("Error while running server").Err(err).Send()
}
}
func allowAllCORSHandler(h http.Handler) http.Handler {
return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
res.Header().Set("Access-Control-Allow-Origin", "*")
res.Header().Set("Access-Control-Allow-Headers", "*")
if req.Method == http.MethodOptions {
return
}
h.ServeHTTP(res, req)
})
}

View File

@@ -0,0 +1,89 @@
package main
import (
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"errors"
"io"
"net/http"
"os"
"strings"
"go.arsenm.dev/logger/log"
"go.arsenm.dev/lure/internal/repos"
)
func handleWebhook(next http.Handler, sigCh chan<- struct{}) http.Handler {
return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
if req.URL.Path == "/webhook" {
if req.Method != http.MethodPost {
res.WriteHeader(http.StatusMethodNotAllowed)
return
}
if req.Header.Get("X-GitHub-Event") != "push" {
http.Error(res, "Only push events are accepted by this bot", http.StatusBadRequest)
return
}
err := verifySecure(req)
if err != nil {
http.Error(res, err.Error(), http.StatusInternalServerError)
return
}
sigCh <- struct{}{}
return
}
next.ServeHTTP(res, req)
})
}
func verifySecure(req *http.Request) error {
sigStr := req.Header.Get("X-Hub-Signature-256")
sig, err := hex.DecodeString(strings.TrimPrefix(sigStr, "sha256="))
if err != nil {
return err
}
secretStr, ok := os.LookupEnv("LURE_API_GITHUB_SECRET")
if !ok {
return errors.New("LURE_API_GITHUB_SECRET must be set to the secret used for setting up the github webhook")
}
secret := []byte(secretStr)
h := hmac.New(sha256.New, secret)
_, err = io.Copy(h, req.Body)
if err != nil {
return err
}
if !hmac.Equal(h.Sum(nil), sig) {
log.Warn("Insecure webhook request").
Str("from", req.RemoteAddr).
Bytes("sig", sig).
Bytes("hmac", h.Sum(nil)).
Send()
return errors.New("webhook signature mismatch")
}
return nil
}
func repoPullWorker(ctx context.Context, sigCh <-chan struct{}) {
for {
select {
case <-sigCh:
err := repos.Pull(ctx, gdb, cfg.Repos)
if err != nil {
log.Warn("Error while pulling repositories").Err(err).Send()
}
case <-ctx.Done():
return
}
}
}

101
config.go
View File

@@ -19,107 +19,18 @@
package main
import (
"os"
"path/filepath"
"github.com/pelletier/go-toml/v2"
"go.arsenm.dev/logger/log"
"go.arsenm.dev/lure/internal/config"
"go.arsenm.dev/lure/internal/types"
"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",
},
},
}
var cfg types.Config
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)
err := config.Decode(&cfg)
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
manager.DefaultRootCmd = cfg.RootCmd
}

32
db.go Normal file
View File

@@ -0,0 +1,32 @@
package main
import (
"os"
"github.com/jmoiron/sqlx"
"go.arsenm.dev/logger/log"
"go.arsenm.dev/lure/internal/config"
"go.arsenm.dev/lure/internal/db"
)
var gdb *sqlx.DB
func init() {
fi, err := os.Stat(config.DBPath)
if err == nil {
// TODO: This should be removed by the first stable release.
if fi.IsDir() {
log.Fatal("Your package cache database is using the old database engine. Please remove ~/.cache/lure and then run `lure ref`.").Send()
}
}
gdb, err = sqlx.Open("sqlite", config.DBPath)
if err != nil {
log.Fatal("Error opening database").Err(err).Send()
}
err = db.Init(gdb)
if err != nil {
log.Fatal("Error initializing database").Err(err).Send()
}
}

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
@@ -46,8 +46,16 @@ type OSRelease struct {
Logo string
}
// OSReleaseName returns the NAME field of the
var parsed *OSRelease
// 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 returned OSRelease struct is a singleton.
func ParseOSRelease(ctx context.Context) (*OSRelease, error) {
if parsed != nil {
return parsed, nil
}
fl, err := os.Open("/usr/lib/os-release")
if err != nil {
fl, err = os.Open("/etc/os-release")
@@ -86,7 +94,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,
@@ -99,5 +107,6 @@ func ParseOSRelease(ctx context.Context) (*OSRelease, error) {
out.Like = strings.Split(runner.Vars["ID_LIKE"].Str, " ")
}
parsed = out
return out, nil
}

View File

@@ -1,5 +1,8 @@
# LURE Docs
- [Build Scripts](build-scripts.md)
- [Configuration](configuration.md)
- [Usage](usage.md)
- [Configuration](configuration.md)
- [Packages](packages)
- [Build Scripts](packages/build-scripts.md)
- [Package Conventions](packages/conventions.md)
- [Adding Packages to LURE's repo](packages/adding-packages.md)

5
docs/packages/README.md Normal file
View File

@@ -0,0 +1,5 @@
# LURE Docs > Packages
- [Build Scripts](build-scripts.md)
- [Package Conventions](conventions.md)
- [Adding Packages to LURE's repo](adding-packages.md)

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,29 @@ 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)
- [git-version](#git-version)
---
@@ -119,7 +142,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 +186,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 +241,28 @@ 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 are executed in the `$srcdir` directory
### 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.
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)
### 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 +285,202 @@ 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.bash
```
### 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
```
### 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"
```

View File

@@ -0,0 +1,36 @@
# 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.

View File

@@ -12,6 +12,8 @@
- [addrepo](#addrepo)
- [removerepo](#removerepo)
- [refresh](#refresh)
- [fix](#fix)
- [version](#version)
- [Environment Variables](#environment-variables)
- [LURE_DISTRO](#lure_distro)
- [LURE_PKG_FORMAT](#lure_pkg_format)
@@ -23,12 +25,18 @@
### install
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 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.
Example:
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 install.
Examples:
```shell
lure in itd-bin
lure in itd-bin # only finds itd-bin
lure in itd # finds itd-bin and itd-git
lure in it% # finds itd-bin, itd-git, and itgui-git
```
### remove
@@ -55,20 +63,36 @@ lure up
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:
```shell
lure info itd-bin
lure info itd-bin # only finds itd-bin
lure info itd # finds itd-bin and itd-git
lure info it% # finds itd-bin, itd-git, and itgui-git
```
### list
The list command lists all LURE repo packages as well as their versions
Example:
This command accepts a single optional argument. This argument is a pattern to filter found packages against.
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
lure ls
lure ls # lists all LURE packages
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
@@ -111,6 +135,26 @@ Example:
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

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
}
}

52
fix.go Normal file
View File

@@ -0,0 +1,52 @@
package main
import (
"os"
"github.com/jmoiron/sqlx"
"github.com/urfave/cli/v2"
"go.arsenm.dev/logger/log"
"go.arsenm.dev/lure/internal/config"
"go.arsenm.dev/lure/internal/db"
"go.arsenm.dev/lure/internal/repos"
)
func fixCmd(c *cli.Context) error {
gdb.Close()
log.Info("Removing cache directory").Send()
err := os.RemoveAll(config.CacheDir)
if err != nil {
log.Fatal("Unable to remove cache directory").Err(err).Send()
}
log.Info("Rebuilding cache").Send()
err = os.MkdirAll(config.CacheDir, 0o755)
if err != nil {
log.Fatal("Unable to create new cache directory").Err(err).Send()
}
gdb, err = sqlx.Open("sqlite", config.DBPath)
if err != nil {
log.Fatal("Unable to create new database").Err(err).Send()
}
// Make sure the DB is rebuilt when repos are pulled
config.DBPresent = false
err = db.Init(gdb)
if err != nil {
log.Fatal("Error initializing database").Err(err).Send()
}
err = repos.Pull(c.Context, gdb, cfg.Repos)
if err != nil {
log.Fatal("Error pulling repos").Err(err).Send()
}
log.Info("Done").Send()
return nil
}

61
go.mod
View File

@@ -2,20 +2,28 @@ 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/alecthomas/chroma/v2 v2.4.0
github.com/charmbracelet/bubbles v0.14.0
github.com/charmbracelet/bubbletea v0.23.1
github.com/charmbracelet/lipgloss v0.6.0
github.com/go-git/go-billy/v5 v5.3.1
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/jmoiron/sqlx v1.3.5
github.com/mholt/archiver/v4 v4.0.0-alpha.7
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/twitchtv/twirp v8.1.3+incompatible
github.com/urfave/cli/v2 v2.16.3
go.arsenm.dev/logger v0.0.0-20220630204155-5ba23e583f0a
go.arsenm.dev/logger v0.0.0-20221220032833-ba8a3cfb4668
golang.org/x/exp v0.0.0-20220916125017-b168a2c6b86b
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f
golang.org/x/sys v0.3.0
google.golang.org/protobuf v1.27.1
gopkg.in/yaml.v3 v3.0.1
modernc.org/sqlite v1.20.0
mvdan.cc/sh/v3 v3.5.1
)
@@ -25,23 +33,24 @@ require (
github.com/Masterminds/semver v1.5.0 // indirect
github.com/Masterminds/semver/v3 v3.1.1 // indirect
github.com/Masterminds/sprig v2.22.0+incompatible // indirect
github.com/Microsoft/go-winio v0.5.1 // indirect
github.com/Microsoft/go-winio v0.5.2 // 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/aymanbagabas/go-osc52 v1.0.3 // indirect
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect
github.com/cavaliergopher/cpio v1.0.1 // indirect
github.com/containerd/console v1.0.3 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/dlclark/regexp2 v1.4.0 // indirect
github.com/dsnet/compress v0.0.1 // indirect
github.com/emirpasic/gods v1.12.0 // indirect
github.com/frankban/quicktest v1.14.3 // 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/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/gookit/color v1.5.2 // indirect
github.com/goreleaser/chglog v0.2.2 // indirect
github.com/goreleaser/fileglob v1.3.0 // indirect
github.com/huandu/xstrings v1.3.2 // indirect
@@ -49,29 +58,49 @@ 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
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-colorable v0.1.11 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // 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/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/termenv v0.13.0 // indirect
github.com/nwaples/rardecode/v2 v2.0.0-beta.2 // indirect
github.com/pierrec/lz4/v4 v4.1.14 // indirect
github.com/pierrec/lz4/v4 v4.1.15 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sergi/go-diff v1.2.0 // indirect
github.com/sirupsen/logrus v1.9.0 // indirect
github.com/therootcompany/xz v1.0.1 // indirect
github.com/ulikunitz/xz v0.5.10 // indirect
github.com/xanzy/ssh-agent v0.3.1 // indirect
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
gitlab.com/digitalxero/go-conventional-commit v1.0.7 // indirect
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect
golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
golang.org/x/net v0.0.0-20220812174116-3211cb980234 // indirect
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/tools v0.1.12 // 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.21.5 // indirect
modernc.org/mathutil v1.5.0 // indirect
modernc.org/memory v1.4.0 // indirect
modernc.org/opt v0.1.3 // indirect
modernc.org/strutil v1.1.3 // indirect
modernc.org/token v1.0.1 // indirect
)

154
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=
@@ -16,8 +14,8 @@ github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuN
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
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/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA=
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
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/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
@@ -27,12 +25,19 @@ github.com/ProtonMail/go-mime v0.0.0-20220302105931-303f85f7fe0f h1:CGq7OieOz3wy
github.com/ProtonMail/gopenpgp/v2 v2.2.2 h1:u2m7xt+CZWj88qK1UUNBoXeJCFJwJCZ/Ff4ymGoxEXs=
github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk=
github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
github.com/alecthomas/assert/v2 v2.2.0 h1:f6L/b7KE2bfA+9O4FL3CM/xJccDEwPVYd5fALBiuwvw=
github.com/alecthomas/chroma/v2 v2.4.0 h1:Loe2ZjT5x3q1bcWwemqyqEi8p11/IV/ncFCeLYDpWC4=
github.com/alecthomas/chroma/v2 v2.4.0/go.mod h1:6kHzqF5O6FUSJzBXW7fXELjb+e+7OXW4UpoPqMO7IBQ=
github.com/alecthomas/repr v0.1.0 h1:ENn2e1+J3k09gyj2shc0dHr/yjaWSHRlrJ4DPMevDqE=
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
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/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aymanbagabas/go-osc52 v1.0.3 h1:DTwqENW7X9arYimJrPeGZcV0ln14sGMt3pHZspWD+Mg=
github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4=
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/caarlos0/go-rpmutils v0.2.1-0.20211112020245-2cd62ff89b11 h1:IRrDwVlWQr6kS1U8/EtyA1+EHcc4yl8pndcqXWrEamg=
@@ -40,6 +45,17 @@ 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/cavaliergopher/cpio v1.0.1 h1:KQFSeKmZhv0cr+kawA3a0xTQCU4QxXF1vhU7P7av2KM=
github.com/cavaliergopher/cpio v1.0.1/go.mod h1:pBdaqQjnvXxdS/6CvNDwIANIFSP0xRKI16PX4xejRQc=
github.com/charmbracelet/bubbles v0.14.0 h1:DJfCwnARfWjZLvMglhSQzo76UZ2gucuHPy9jLWX45Og=
github.com/charmbracelet/bubbles v0.14.0/go.mod h1:bbeTiXwPww4M031aGi8UK2HT9RDWoiNibae+1yCMtcc=
github.com/charmbracelet/bubbletea v0.21.0/go.mod h1:GgmJMec61d08zXsOhqRC/AiOx4K4pmz+VIcRIm1FKr4=
github.com/charmbracelet/bubbletea v0.23.1 h1:CYdteX1wCiCzKNUlwm25ZHBIc1GXlYFyUIte8WPvhck=
github.com/charmbracelet/bubbletea v0.23.1/go.mod h1:JAfGK/3/pPKHTnAS8JIE2u9f61BjWTQY57RbT25aMXU=
github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
github.com/charmbracelet/lipgloss v0.5.0/go.mod h1:EZLha/HbzEt7cYqdFPovlqy5FZPj0xFhg5SaqxScmgs=
github.com/charmbracelet/lipgloss v0.6.0 h1:1StyZB9vBSOyuZxQUcUwGr17JmojPNm87inij9N3wJY=
github.com/charmbracelet/lipgloss v0.6.0/go.mod h1:tHh2wr34xcHjC2HCXIlGSG1jaDF0S0atAUvBMP6Ppuk=
github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw=
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
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/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
@@ -48,9 +64,12 @@ github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr
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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
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/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
@@ -67,25 +86,30 @@ github.com/go-git/go-git-fixtures/v4 v4.2.1 h1:n9gGL1Ct/yIw+nfsfr8s4+sbhT+Ncu2Su
github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0=
github.com/go-git/go-git/v5 v5.4.2 h1:BXyZu9t0VkbiHtqrsvdq39UDhGJTl1h55VW6CSC4aY4=
github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc=
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/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
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.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
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/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
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=
github.com/gookit/color v1.5.1/go.mod h1:wZFzea4X8qN6vHOSP2apMb4/+w/orMznEzYsIHPaqKM=
github.com/gookit/color v1.5.2 h1:uLnfXcaFjlrDnQDT+NCBcfhrXqYTx/rcCa6xn01Y8yI=
github.com/gookit/color v1.5.2/go.mod h1:w8h4bGiHeeBpvQVePTutdbERIUf3oJE5lZ8HM0UgXyg=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/goreleaser/chglog v0.2.2 h1:V7nf07baXtGAgGevvqgW2MM4kZ6gOr12vKNSAU3VIZ0=
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/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
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=
@@ -96,6 +120,8 @@ github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK
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/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
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=
@@ -103,9 +129,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=
@@ -118,14 +143,30 @@ 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.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
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.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
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/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs=
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
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.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
github.com/mattn/go-runewidth v0.0.14/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.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
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/mholt/archiver/v4 v4.0.0-alpha.7 h1:xzByj8G8tj0Oq7ZYYU4+ixL/CVb5ruWCm0EZQ1PjOkE=
@@ -138,28 +179,47 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
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/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
github.com/muesli/cancelreader v0.2.0/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
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.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ=
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.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs=
github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs=
github.com/muesli/termenv v0.13.0 h1:wK20DRpJdDX8b7Ek2QfhvqhRQFZ237RGRO0RQ/Iqdy0=
github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
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/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg=
github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas=
github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE=
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/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg=
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/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
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/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
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.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs=
github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -170,11 +230,12 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
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.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/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw=
github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY=
github.com/twitchtv/twirp v8.1.3+incompatible h1:+F4TdErPgSUbMZMwp13Q/KgDVuI7HJXP61mNV3/7iuU=
github.com/twitchtv/twirp v8.1.3+incompatible/go.mod h1:RRJoFSAmTEh2weEqWtpPE3vFK5YBhA6bqp2l1kfCC5A=
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8=
github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
@@ -184,26 +245,29 @@ github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6e
github.com/xanzy/ssh-agent v0.3.1 h1:AmzO1SSWxw73zxFZPRwaMN1MohDw8UyHnmuxyceTEGo=
github.com/xanzy/ssh-agent v0.3.1/go.mod h1:QIE4lCeL7nkC25x+yA3LBIYfwCc1TFziCtG7cBAac6w=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
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-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
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=
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=
go.arsenm.dev/logger v0.0.0-20220630204155-5ba23e583f0a h1:9SF7Af1OnN9BlWk9yNYCZuv/uAhDMhzfZxtWJEqy6EE=
go.arsenm.dev/logger v0.0.0-20220630204155-5ba23e583f0a/go.mod h1:RV2qydKDdoyaRkhAq8JEGvojR8eJ6bjq5WnSIlH7gYw=
go.arsenm.dev/logger v0.0.0-20221220032833-ba8a3cfb4668 h1:7dSmQ79slzFpcii8zgQbEStxpkTPvq3tzWc7KX5uwGc=
go.arsenm.dev/logger v0.0.0-20221220032833-ba8a3cfb4668/go.mod h1:RV2qydKDdoyaRkhAq8JEGvojR8eJ6bjq5WnSIlH7gYw=
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA=
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20220916125017-b168a2c6b86b h1:SCE/18RnFsLrjydh/R/s5EVvHoZprqEQUuoxK8q2Pc4=
golang.org/x/exp v0.0.0-20220916125017-b168a2c6b86b/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
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-20220812174116-3211cb980234 h1:RDqmgfe7SvlMWoqC3xwQ2blLO3fcWcxMa3eBLRdRW7E=
golang.org/x/net v0.0.0-20220812174116-3211cb980234/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -220,9 +284,14 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w
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-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/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-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
golang.org/x/sys v0.3.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-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
@@ -231,7 +300,12 @@ golang.org/x/text v0.3.3/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/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
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-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -248,5 +322,29 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
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/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
modernc.org/libc v1.21.5 h1:xBkU9fnHV+hvZuPSRszN0AXDG4M7nwPLwTWwkYcvLCI=
modernc.org/libc v1.21.5/go.mod h1:przBsL5RDOZajTVslkugzLBj1evTue36jEomFQOoYuI=
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.4.0 h1:crykUfNSnMAXaOJnnxcSzbUGMqkLWjklJKkBK2nwZwk=
modernc.org/memory v1.4.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.20.0 h1:80zmD3BGkm8BZ5fUi/4lwJQHiO3GXgIUvZRXpoIfROY=
modernc.org/sqlite v1.20.0/go.mod h1:EsYz8rfOvLCiYTy5ZFsOYzoCcRMu98YYkwAcCw5YIYw=
modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
modernc.org/tcl v1.15.0 h1:oY+JeD11qVVSgVvodMJsu7Edf8tr5E/7tuhF5cNYz34=
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.0 h1:xkDw/KepgEjeizO2sNco+hqYkU12taxQFqPEmgm1GWE=
mvdan.cc/sh/v3 v3.5.1 h1:hmP3UOw4f+EYexsJjFxvU38+kn+V/s2CclXHanIBkmQ=
mvdan.cc/sh/v3 v3.5.1/go.mod h1:1JcoyAKm1lZw/2bZje/iYKWicU/KMd0rsyJeKHnsK4E=

261
helpers.go Normal file
View File

@@ -0,0 +1,261 @@
package main
import (
"errors"
"fmt"
"io"
"os"
"path/filepath"
"strconv"
"strings"
"unsafe"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing/object"
"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,
"git-version": gitVersionCmd,
}
// rHelpers contains restricted read-only helpers that don't modify any state
var rHelpers = shutils.ExecFuncs{
"git-version": gitVersionCmd,
}
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 := resolvePath(hc, 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 := resolvePath(hc, 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/completions"
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 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", revNum, hash[:7])
return nil
}
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
}
func resolvePath(hc interp.HandlerContext, path string) string {
if !filepath.IsAbs(path) {
return filepath.Join(hc.Dir, path)
}
return path
}

68
info.go
View File

@@ -19,10 +19,16 @@
package main
import (
"fmt"
"os"
"go.arsenm.dev/logger/log"
"github.com/urfave/cli/v2"
"go.arsenm.dev/lure/distro"
"go.arsenm.dev/lure/internal/cliutils"
"go.arsenm.dev/lure/internal/overrides"
"go.arsenm.dev/lure/internal/repos"
"gopkg.in/yaml.v3"
)
@@ -32,27 +38,63 @@ func infoCmd(c *cli.Context) error {
log.Fatalf("Command info expected at least 1 argument, got %d", args.Len()).Send()
}
info, err := distro.ParseOSRelease(c.Context)
err := repos.Pull(c.Context, gdb, cfg.Repos)
if err != nil {
log.Fatal("Error parsing os-release").Err(err).Send()
log.Fatal("Error pulling repositories").Err(err).Send()
}
found, err := findPkg(args.First())
found, _, err := repos.FindPkgs(gdb, args.Slice())
if err != nil {
log.Fatal("Error finding package").Err(err).Send()
log.Fatal("Error finding packages").Err(err).Send()
}
// if multiple are matched, only use the first one
script := found[0]
vars, err := getBuildVars(c.Context, script, info)
if err != nil {
log.Fatal("Error getting build variables").Err(err).Send()
if len(found) == 0 {
os.Exit(1)
}
err = yaml.NewEncoder(os.Stdout).Encode(vars)
if err != nil {
log.Fatal("Error encoding script variables").Err(err).Send()
pkgs := cliutils.FlattenPkgs(found, "show")
var names []string
all := c.Bool("all")
if !all {
info, err := distro.ParseOSRelease(c.Context)
if err != nil {
log.Fatal("Error parsing os-release file").Err(err).Send()
}
names = overrides.Resolve(info, overrides.DefaultOpts)
}
for _, pkg := range pkgs {
if !all {
depsSet := false
buildDepsSet := false
for _, name := range names {
if deps, ok := pkg.Depends.Val[name]; ok && !depsSet {
pkg.Depends.Val = map[string][]string{name: deps}
depsSet = true
}
if buildDeps, ok := pkg.BuildDepends.Val[name]; ok && !buildDepsSet {
pkg.BuildDepends.Val = map[string][]string{name: buildDeps}
buildDepsSet = true
}
}
if !depsSet {
pkg.Depends.Val = nil
}
if !buildDepsSet {
pkg.BuildDepends.Val = nil
}
}
err = yaml.NewEncoder(os.Stdout).Encode(pkg)
if err != nil {
log.Fatal("Error encoding script variables").Err(err).Send()
}
fmt.Println("---")
}
return nil

View File

@@ -20,8 +20,15 @@ package main
import (
"context"
"path/filepath"
"go.arsenm.dev/logger/log"
"github.com/urfave/cli/v2"
"go.arsenm.dev/lure/internal/cliutils"
"go.arsenm.dev/lure/internal/config"
"go.arsenm.dev/lure/internal/db"
"go.arsenm.dev/lure/internal/repos"
"go.arsenm.dev/lure/manager"
)
@@ -36,29 +43,45 @@ func installCmd(c *cli.Context) error {
log.Fatal("Unable to detect supported package manager on system").Send()
}
installPkgs(c.Context, args.Slice(), mgr)
return nil
}
func installPkgs(ctx context.Context, pkgs []string, mgr manager.Manager) {
err := pullRepos(ctx)
err := repos.Pull(c.Context, gdb, cfg.Repos)
if err != nil {
log.Fatal("Error pulling repositories").Err(err).Send()
}
scripts, notFound := findPkgs(pkgs)
found, notFound, err := repos.FindPkgs(gdb, args.Slice())
if err != nil {
log.Fatal("Error finding packages").Err(err).Send()
}
installPkgs(c.Context, cliutils.FlattenPkgs(found, "install"), notFound, mgr)
return nil
}
// installPkgs installs non-LURE packages via the package manager, then builds and installs LURE
// packages
func installPkgs(ctx context.Context, pkgs []db.Package, notFound []string, mgr manager.Manager) {
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()
}
}
installScripts(ctx, mgr, scripts)
installScripts(ctx, mgr, getScriptPaths(pkgs))
}
// getScriptPaths generates a slice of script paths corresponding to the
// given packages
func getScriptPaths(pkgs []db.Package) []string {
var scripts []string
for _, pkg := range pkgs {
scriptPath := filepath.Join(config.RepoDir, pkg.Repository, pkg.Name, "lure.sh")
scripts = append(scripts, scriptPath)
}
return scripts
}
// installScripts builds and installs LURE build scripts
func installScripts(ctx context.Context, mgr manager.Manager, scripts []string) {
for _, script := range scripts {
builtPkgs, _, err := buildPackage(ctx, script, mgr)

4
internal/api/gen.go Normal file
View File

@@ -0,0 +1,4 @@
package api
//go:generate protoc --twirp_out=. lure.proto
//go:generate protoc --go_out=. lure.proto

885
internal/api/lure.pb.go Normal file
View File

@@ -0,0 +1,885 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.1
// protoc v3.21.9
// source: lure.proto
package api
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// SORT_BY represents possible things to sort packages by
type SORT_BY int32
const (
SORT_BY_UNSORTED SORT_BY = 0
SORT_BY_NAME SORT_BY = 1
SORT_BY_REPOSITORY SORT_BY = 2
SORT_BY_VERSION SORT_BY = 3
)
// Enum value maps for SORT_BY.
var (
SORT_BY_name = map[int32]string{
0: "UNSORTED",
1: "NAME",
2: "REPOSITORY",
3: "VERSION",
}
SORT_BY_value = map[string]int32{
"UNSORTED": 0,
"NAME": 1,
"REPOSITORY": 2,
"VERSION": 3,
}
)
func (x SORT_BY) Enum() *SORT_BY {
p := new(SORT_BY)
*p = x
return p
}
func (x SORT_BY) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (SORT_BY) Descriptor() protoreflect.EnumDescriptor {
return file_lure_proto_enumTypes[0].Descriptor()
}
func (SORT_BY) Type() protoreflect.EnumType {
return &file_lure_proto_enumTypes[0]
}
func (x SORT_BY) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use SORT_BY.Descriptor instead.
func (SORT_BY) EnumDescriptor() ([]byte, []int) {
return file_lure_proto_rawDescGZIP(), []int{0}
}
// FILTER_TYPE represents possible filters for packages
type FILTER_TYPE int32
const (
FILTER_TYPE_NO_FILTER FILTER_TYPE = 0
FILTER_TYPE_IN_REPOSITORY FILTER_TYPE = 1
FILTER_TYPE_SUPPORTS_ARCH FILTER_TYPE = 2
)
// Enum value maps for FILTER_TYPE.
var (
FILTER_TYPE_name = map[int32]string{
0: "NO_FILTER",
1: "IN_REPOSITORY",
2: "SUPPORTS_ARCH",
}
FILTER_TYPE_value = map[string]int32{
"NO_FILTER": 0,
"IN_REPOSITORY": 1,
"SUPPORTS_ARCH": 2,
}
)
func (x FILTER_TYPE) Enum() *FILTER_TYPE {
p := new(FILTER_TYPE)
*p = x
return p
}
func (x FILTER_TYPE) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (FILTER_TYPE) Descriptor() protoreflect.EnumDescriptor {
return file_lure_proto_enumTypes[1].Descriptor()
}
func (FILTER_TYPE) Type() protoreflect.EnumType {
return &file_lure_proto_enumTypes[1]
}
func (x FILTER_TYPE) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use FILTER_TYPE.Descriptor instead.
func (FILTER_TYPE) EnumDescriptor() ([]byte, []int) {
return file_lure_proto_rawDescGZIP(), []int{1}
}
// SearchRequest is a request to search for packages
type SearchRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Query string `protobuf:"bytes,1,opt,name=query,proto3" json:"query,omitempty"`
Limit int64 `protobuf:"varint,2,opt,name=limit,proto3" json:"limit,omitempty"`
SortBy SORT_BY `protobuf:"varint,3,opt,name=sort_by,json=sortBy,proto3,enum=lure.SORT_BY" json:"sort_by,omitempty"`
FilterType FILTER_TYPE `protobuf:"varint,4,opt,name=filter_type,json=filterType,proto3,enum=lure.FILTER_TYPE" json:"filter_type,omitempty"`
FilterValue *string `protobuf:"bytes,5,opt,name=filter_value,json=filterValue,proto3,oneof" json:"filter_value,omitempty"`
}
func (x *SearchRequest) Reset() {
*x = SearchRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lure_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SearchRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SearchRequest) ProtoMessage() {}
func (x *SearchRequest) ProtoReflect() protoreflect.Message {
mi := &file_lure_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SearchRequest.ProtoReflect.Descriptor instead.
func (*SearchRequest) Descriptor() ([]byte, []int) {
return file_lure_proto_rawDescGZIP(), []int{0}
}
func (x *SearchRequest) GetQuery() string {
if x != nil {
return x.Query
}
return ""
}
func (x *SearchRequest) GetLimit() int64 {
if x != nil {
return x.Limit
}
return 0
}
func (x *SearchRequest) GetSortBy() SORT_BY {
if x != nil {
return x.SortBy
}
return SORT_BY_UNSORTED
}
func (x *SearchRequest) GetFilterType() FILTER_TYPE {
if x != nil {
return x.FilterType
}
return FILTER_TYPE_NO_FILTER
}
func (x *SearchRequest) GetFilterValue() string {
if x != nil && x.FilterValue != nil {
return *x.FilterValue
}
return ""
}
// StringList contains a list of strings
type StringList struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Entries []string `protobuf:"bytes,1,rep,name=entries,proto3" json:"entries,omitempty"`
}
func (x *StringList) Reset() {
*x = StringList{}
if protoimpl.UnsafeEnabled {
mi := &file_lure_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *StringList) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*StringList) ProtoMessage() {}
func (x *StringList) ProtoReflect() protoreflect.Message {
mi := &file_lure_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use StringList.ProtoReflect.Descriptor instead.
func (*StringList) Descriptor() ([]byte, []int) {
return file_lure_proto_rawDescGZIP(), []int{1}
}
func (x *StringList) GetEntries() []string {
if x != nil {
return x.Entries
}
return nil
}
// Package represents a LURE package
type Package struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
Repository string `protobuf:"bytes,2,opt,name=repository,proto3" json:"repository,omitempty"`
Version string `protobuf:"bytes,3,opt,name=version,proto3" json:"version,omitempty"`
Release int64 `protobuf:"varint,4,opt,name=release,proto3" json:"release,omitempty"`
Epoch *int64 `protobuf:"varint,5,opt,name=epoch,proto3,oneof" json:"epoch,omitempty"`
Description *string `protobuf:"bytes,6,opt,name=description,proto3,oneof" json:"description,omitempty"`
Homepage *string `protobuf:"bytes,7,opt,name=homepage,proto3,oneof" json:"homepage,omitempty"`
Maintainer *string `protobuf:"bytes,8,opt,name=maintainer,proto3,oneof" json:"maintainer,omitempty"`
Architectures []string `protobuf:"bytes,9,rep,name=architectures,proto3" json:"architectures,omitempty"`
Licenses []string `protobuf:"bytes,10,rep,name=licenses,proto3" json:"licenses,omitempty"`
Provides []string `protobuf:"bytes,11,rep,name=provides,proto3" json:"provides,omitempty"`
Conflicts []string `protobuf:"bytes,12,rep,name=conflicts,proto3" json:"conflicts,omitempty"`
Replaces []string `protobuf:"bytes,13,rep,name=replaces,proto3" json:"replaces,omitempty"`
Depends map[string]*StringList `protobuf:"bytes,14,rep,name=depends,proto3" json:"depends,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
BuildDepends map[string]*StringList `protobuf:"bytes,15,rep,name=build_depends,json=buildDepends,proto3" json:"build_depends,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
}
func (x *Package) Reset() {
*x = Package{}
if protoimpl.UnsafeEnabled {
mi := &file_lure_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Package) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Package) ProtoMessage() {}
func (x *Package) ProtoReflect() protoreflect.Message {
mi := &file_lure_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Package.ProtoReflect.Descriptor instead.
func (*Package) Descriptor() ([]byte, []int) {
return file_lure_proto_rawDescGZIP(), []int{2}
}
func (x *Package) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *Package) GetRepository() string {
if x != nil {
return x.Repository
}
return ""
}
func (x *Package) GetVersion() string {
if x != nil {
return x.Version
}
return ""
}
func (x *Package) GetRelease() int64 {
if x != nil {
return x.Release
}
return 0
}
func (x *Package) GetEpoch() int64 {
if x != nil && x.Epoch != nil {
return *x.Epoch
}
return 0
}
func (x *Package) GetDescription() string {
if x != nil && x.Description != nil {
return *x.Description
}
return ""
}
func (x *Package) GetHomepage() string {
if x != nil && x.Homepage != nil {
return *x.Homepage
}
return ""
}
func (x *Package) GetMaintainer() string {
if x != nil && x.Maintainer != nil {
return *x.Maintainer
}
return ""
}
func (x *Package) GetArchitectures() []string {
if x != nil {
return x.Architectures
}
return nil
}
func (x *Package) GetLicenses() []string {
if x != nil {
return x.Licenses
}
return nil
}
func (x *Package) GetProvides() []string {
if x != nil {
return x.Provides
}
return nil
}
func (x *Package) GetConflicts() []string {
if x != nil {
return x.Conflicts
}
return nil
}
func (x *Package) GetReplaces() []string {
if x != nil {
return x.Replaces
}
return nil
}
func (x *Package) GetDepends() map[string]*StringList {
if x != nil {
return x.Depends
}
return nil
}
func (x *Package) GetBuildDepends() map[string]*StringList {
if x != nil {
return x.BuildDepends
}
return nil
}
type GetPackageRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
Repository string `protobuf:"bytes,2,opt,name=repository,proto3" json:"repository,omitempty"`
}
func (x *GetPackageRequest) Reset() {
*x = GetPackageRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lure_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetPackageRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetPackageRequest) ProtoMessage() {}
func (x *GetPackageRequest) ProtoReflect() protoreflect.Message {
mi := &file_lure_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetPackageRequest.ProtoReflect.Descriptor instead.
func (*GetPackageRequest) Descriptor() ([]byte, []int) {
return file_lure_proto_rawDescGZIP(), []int{3}
}
func (x *GetPackageRequest) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *GetPackageRequest) GetRepository() string {
if x != nil {
return x.Repository
}
return ""
}
// SearchResponse contains returned packages
type SearchResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Packages []*Package `protobuf:"bytes,1,rep,name=packages,proto3" json:"packages,omitempty"`
}
func (x *SearchResponse) Reset() {
*x = SearchResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_lure_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SearchResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SearchResponse) ProtoMessage() {}
func (x *SearchResponse) ProtoReflect() protoreflect.Message {
mi := &file_lure_proto_msgTypes[4]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SearchResponse.ProtoReflect.Descriptor instead.
func (*SearchResponse) Descriptor() ([]byte, []int) {
return file_lure_proto_rawDescGZIP(), []int{4}
}
func (x *SearchResponse) GetPackages() []*Package {
if x != nil {
return x.Packages
}
return nil
}
type GetBuildScriptRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
Repository string `protobuf:"bytes,2,opt,name=repository,proto3" json:"repository,omitempty"`
}
func (x *GetBuildScriptRequest) Reset() {
*x = GetBuildScriptRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_lure_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetBuildScriptRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetBuildScriptRequest) ProtoMessage() {}
func (x *GetBuildScriptRequest) ProtoReflect() protoreflect.Message {
mi := &file_lure_proto_msgTypes[5]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetBuildScriptRequest.ProtoReflect.Descriptor instead.
func (*GetBuildScriptRequest) Descriptor() ([]byte, []int) {
return file_lure_proto_rawDescGZIP(), []int{5}
}
func (x *GetBuildScriptRequest) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *GetBuildScriptRequest) GetRepository() string {
if x != nil {
return x.Repository
}
return ""
}
type GetBuildScriptResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Script string `protobuf:"bytes,1,opt,name=script,proto3" json:"script,omitempty"`
}
func (x *GetBuildScriptResponse) Reset() {
*x = GetBuildScriptResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_lure_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetBuildScriptResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetBuildScriptResponse) ProtoMessage() {}
func (x *GetBuildScriptResponse) ProtoReflect() protoreflect.Message {
mi := &file_lure_proto_msgTypes[6]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetBuildScriptResponse.ProtoReflect.Descriptor instead.
func (*GetBuildScriptResponse) Descriptor() ([]byte, []int) {
return file_lure_proto_rawDescGZIP(), []int{6}
}
func (x *GetBuildScriptResponse) GetScript() string {
if x != nil {
return x.Script
}
return ""
}
var File_lure_proto protoreflect.FileDescriptor
var file_lure_proto_rawDesc = []byte{
0x0a, 0x0a, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x04, 0x6c, 0x75,
0x72, 0x65, 0x22, 0xd0, 0x01, 0x0a, 0x0d, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x01, 0x20,
0x01, 0x28, 0x09, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69,
0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74,
0x12, 0x26, 0x0a, 0x07, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x62, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28,
0x0e, 0x32, 0x0d, 0x2e, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x42, 0x59,
0x52, 0x06, 0x73, 0x6f, 0x72, 0x74, 0x42, 0x79, 0x12, 0x32, 0x0a, 0x0b, 0x66, 0x69, 0x6c, 0x74,
0x65, 0x72, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x11, 0x2e,
0x6c, 0x75, 0x72, 0x65, 0x2e, 0x46, 0x49, 0x4c, 0x54, 0x45, 0x52, 0x5f, 0x54, 0x59, 0x50, 0x45,
0x52, 0x0a, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x26, 0x0a, 0x0c,
0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x05, 0x20, 0x01,
0x28, 0x09, 0x48, 0x00, 0x52, 0x0b, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75,
0x65, 0x88, 0x01, 0x01, 0x42, 0x0f, 0x0a, 0x0d, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f,
0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x26, 0x0a, 0x0a, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4c,
0x69, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x01,
0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x22, 0xe4, 0x05,
0x0a, 0x07, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d,
0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1e, 0x0a,
0x0a, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28,
0x09, 0x52, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x18, 0x0a,
0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07,
0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x65, 0x6c, 0x65, 0x61,
0x73, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73,
0x65, 0x12, 0x19, 0x0a, 0x05, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03,
0x48, 0x00, 0x52, 0x05, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x88, 0x01, 0x01, 0x12, 0x25, 0x0a, 0x0b,
0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28,
0x09, 0x48, 0x01, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e,
0x88, 0x01, 0x01, 0x12, 0x1f, 0x0a, 0x08, 0x68, 0x6f, 0x6d, 0x65, 0x70, 0x61, 0x67, 0x65, 0x18,
0x07, 0x20, 0x01, 0x28, 0x09, 0x48, 0x02, 0x52, 0x08, 0x68, 0x6f, 0x6d, 0x65, 0x70, 0x61, 0x67,
0x65, 0x88, 0x01, 0x01, 0x12, 0x23, 0x0a, 0x0a, 0x6d, 0x61, 0x69, 0x6e, 0x74, 0x61, 0x69, 0x6e,
0x65, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x48, 0x03, 0x52, 0x0a, 0x6d, 0x61, 0x69, 0x6e,
0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x88, 0x01, 0x01, 0x12, 0x24, 0x0a, 0x0d, 0x61, 0x72, 0x63,
0x68, 0x69, 0x74, 0x65, 0x63, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x09,
0x52, 0x0d, 0x61, 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, 0x63, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12,
0x1a, 0x0a, 0x08, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28,
0x09, 0x52, 0x08, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x70,
0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x70,
0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x66, 0x6c,
0x69, 0x63, 0x74, 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x66,
0x6c, 0x69, 0x63, 0x74, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65,
0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65,
0x73, 0x12, 0x34, 0x0a, 0x07, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x73, 0x18, 0x0e, 0x20, 0x03,
0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67,
0x65, 0x2e, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07,
0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x73, 0x12, 0x44, 0x0a, 0x0d, 0x62, 0x75, 0x69, 0x6c, 0x64,
0x5f, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x73, 0x18, 0x0f, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f,
0x2e, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x2e, 0x42, 0x75,
0x69, 0x6c, 0x64, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52,
0x0c, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x73, 0x1a, 0x4c, 0x0a,
0x0c, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a,
0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12,
0x26, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10,
0x2e, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x69, 0x73, 0x74,
0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x51, 0x0a, 0x11, 0x42,
0x75, 0x69, 0x6c, 0x64, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79,
0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b,
0x65, 0x79, 0x12, 0x26, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x10, 0x2e, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4c,
0x69, 0x73, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x08,
0x0a, 0x06, 0x5f, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x64, 0x65, 0x73,
0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x68, 0x6f, 0x6d,
0x65, 0x70, 0x61, 0x67, 0x65, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x6d, 0x61, 0x69, 0x6e, 0x74, 0x61,
0x69, 0x6e, 0x65, 0x72, 0x22, 0x47, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x50, 0x61, 0x63, 0x6b, 0x61,
0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d,
0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1e, 0x0a,
0x0a, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28,
0x09, 0x52, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x22, 0x3b, 0x0a,
0x0e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
0x29, 0x0a, 0x08, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28,
0x0b, 0x32, 0x0d, 0x2e, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65,
0x52, 0x08, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x22, 0x4b, 0x0a, 0x15, 0x47, 0x65,
0x74, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x73,
0x69, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x70,
0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x22, 0x30, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x42, 0x75,
0x69, 0x6c, 0x64, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x2a, 0x3e, 0x0a, 0x07, 0x53, 0x4f, 0x52,
0x54, 0x5f, 0x42, 0x59, 0x12, 0x0c, 0x0a, 0x08, 0x55, 0x4e, 0x53, 0x4f, 0x52, 0x54, 0x45, 0x44,
0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x41, 0x4d, 0x45, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a,
0x52, 0x45, 0x50, 0x4f, 0x53, 0x49, 0x54, 0x4f, 0x52, 0x59, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07,
0x56, 0x45, 0x52, 0x53, 0x49, 0x4f, 0x4e, 0x10, 0x03, 0x2a, 0x42, 0x0a, 0x0b, 0x46, 0x49, 0x4c,
0x54, 0x45, 0x52, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x12, 0x0d, 0x0a, 0x09, 0x4e, 0x4f, 0x5f, 0x46,
0x49, 0x4c, 0x54, 0x45, 0x52, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x49, 0x4e, 0x5f, 0x52, 0x45,
0x50, 0x4f, 0x53, 0x49, 0x54, 0x4f, 0x52, 0x59, 0x10, 0x01, 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x55,
0x50, 0x50, 0x4f, 0x52, 0x54, 0x53, 0x5f, 0x41, 0x52, 0x43, 0x48, 0x10, 0x02, 0x32, 0xb9, 0x01,
0x0a, 0x03, 0x41, 0x50, 0x49, 0x12, 0x33, 0x0a, 0x06, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x12,
0x13, 0x2e, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x53, 0x65, 0x61, 0x72,
0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x30, 0x0a, 0x06, 0x47, 0x65,
0x74, 0x50, 0x6b, 0x67, 0x12, 0x17, 0x2e, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x50,
0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0d, 0x2e,
0x6c, 0x75, 0x72, 0x65, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x12, 0x4b, 0x0a, 0x0e,
0x47, 0x65, 0x74, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x12, 0x1b,
0x2e, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x63,
0x72, 0x69, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6c, 0x75,
0x72, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x63, 0x72, 0x69, 0x70,
0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x08, 0x5a, 0x06, 0x2e, 0x2e, 0x2f,
0x61, 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_lure_proto_rawDescOnce sync.Once
file_lure_proto_rawDescData = file_lure_proto_rawDesc
)
func file_lure_proto_rawDescGZIP() []byte {
file_lure_proto_rawDescOnce.Do(func() {
file_lure_proto_rawDescData = protoimpl.X.CompressGZIP(file_lure_proto_rawDescData)
})
return file_lure_proto_rawDescData
}
var file_lure_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
var file_lure_proto_msgTypes = make([]protoimpl.MessageInfo, 9)
var file_lure_proto_goTypes = []interface{}{
(SORT_BY)(0), // 0: lure.SORT_BY
(FILTER_TYPE)(0), // 1: lure.FILTER_TYPE
(*SearchRequest)(nil), // 2: lure.SearchRequest
(*StringList)(nil), // 3: lure.StringList
(*Package)(nil), // 4: lure.Package
(*GetPackageRequest)(nil), // 5: lure.GetPackageRequest
(*SearchResponse)(nil), // 6: lure.SearchResponse
(*GetBuildScriptRequest)(nil), // 7: lure.GetBuildScriptRequest
(*GetBuildScriptResponse)(nil), // 8: lure.GetBuildScriptResponse
nil, // 9: lure.Package.DependsEntry
nil, // 10: lure.Package.BuildDependsEntry
}
var file_lure_proto_depIdxs = []int32{
0, // 0: lure.SearchRequest.sort_by:type_name -> lure.SORT_BY
1, // 1: lure.SearchRequest.filter_type:type_name -> lure.FILTER_TYPE
9, // 2: lure.Package.depends:type_name -> lure.Package.DependsEntry
10, // 3: lure.Package.build_depends:type_name -> lure.Package.BuildDependsEntry
4, // 4: lure.SearchResponse.packages:type_name -> lure.Package
3, // 5: lure.Package.DependsEntry.value:type_name -> lure.StringList
3, // 6: lure.Package.BuildDependsEntry.value:type_name -> lure.StringList
2, // 7: lure.API.Search:input_type -> lure.SearchRequest
5, // 8: lure.API.GetPkg:input_type -> lure.GetPackageRequest
7, // 9: lure.API.GetBuildScript:input_type -> lure.GetBuildScriptRequest
6, // 10: lure.API.Search:output_type -> lure.SearchResponse
4, // 11: lure.API.GetPkg:output_type -> lure.Package
8, // 12: lure.API.GetBuildScript:output_type -> lure.GetBuildScriptResponse
10, // [10:13] is the sub-list for method output_type
7, // [7:10] is the sub-list for method input_type
7, // [7:7] is the sub-list for extension type_name
7, // [7:7] is the sub-list for extension extendee
0, // [0:7] is the sub-list for field type_name
}
func init() { file_lure_proto_init() }
func file_lure_proto_init() {
if File_lure_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_lure_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SearchRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lure_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*StringList); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lure_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Package); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lure_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetPackageRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lure_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SearchResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lure_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetBuildScriptRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_lure_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetBuildScriptResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
file_lure_proto_msgTypes[0].OneofWrappers = []interface{}{}
file_lure_proto_msgTypes[2].OneofWrappers = []interface{}{}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_lure_proto_rawDesc,
NumEnums: 2,
NumMessages: 9,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_lure_proto_goTypes,
DependencyIndexes: file_lure_proto_depIdxs,
EnumInfos: file_lure_proto_enumTypes,
MessageInfos: file_lure_proto_msgTypes,
}.Build()
File_lure_proto = out.File
file_lure_proto_rawDesc = nil
file_lure_proto_goTypes = nil
file_lure_proto_depIdxs = nil
}

82
internal/api/lure.proto Normal file
View File

@@ -0,0 +1,82 @@
syntax = "proto3";
package lure;
// Slight hack to provide protoc with a package name
option go_package = "../api";
// SORT_BY represents possible things to sort packages by
enum SORT_BY {
UNSORTED = 0;
NAME = 1;
REPOSITORY = 2;
VERSION = 3;
}
// FILTER_TYPE represents possible filters for packages
enum FILTER_TYPE {
NO_FILTER = 0;
IN_REPOSITORY = 1;
SUPPORTS_ARCH = 2;
}
// SearchRequest is a request to search for packages
message SearchRequest {
string query = 1;
int64 limit = 2;
SORT_BY sort_by = 3;
FILTER_TYPE filter_type = 4;
optional string filter_value = 5;
}
// StringList contains a list of strings
message StringList {
repeated string entries = 1;
}
// Package represents a LURE package
message Package {
string name = 1;
string repository = 2;
string version = 3;
int64 release = 4;
optional int64 epoch = 5;
optional string description = 6;
optional string homepage = 7;
optional string maintainer = 8;
repeated string architectures = 9;
repeated string licenses = 10;
repeated string provides = 11;
repeated string conflicts = 12;
repeated string replaces = 13;
map<string, StringList> depends = 14;
map<string, StringList> build_depends = 15;
}
message GetPackageRequest {
string name = 1;
string repository = 2;
}
// SearchResponse contains returned packages
message SearchResponse {
repeated Package packages = 1;
}
message GetBuildScriptRequest {
string name = 1;
string repository = 2;
}
message GetBuildScriptResponse {
string script = 1;
}
// Web is the LURE Web service
service API {
// Search searches through LURE packages in the database
rpc Search(SearchRequest) returns (SearchResponse);
// GetPkg gets a single LURE package from the database
rpc GetPkg(GetPackageRequest) returns (Package);
// GetBuildScript returns the build script for the given package
rpc GetBuildScript(GetBuildScriptRequest) returns (GetBuildScriptResponse);
}

1703
internal/api/lure.twirp.go Normal file

File diff suppressed because it is too large Load Diff

114
internal/cliutils/prompt.go Normal file
View File

@@ -0,0 +1,114 @@
package cliutils
import (
"os"
"github.com/AlecAivazis/survey/v2"
"go.arsenm.dev/logger/log"
"go.arsenm.dev/lure/internal/db"
"go.arsenm.dev/lure/internal/pager"
)
// YesNoPrompt asks the user a yes or no question, using def as the default answer
func YesNoPrompt(msg string, def bool) (bool, error) {
var answer bool
err := survey.AskOne(
&survey.Confirm{
Message: msg,
Default: def,
},
&answer,
)
return answer, err
}
// PromptViewScript asks the user if they'd like to see a script,
// shows it if they answer yes, then asks if they'd still like to
// continue, and exits if they answer no.
func PromptViewScript(script, name, style string) error {
view, err := YesNoPrompt("Would you like to view the build script for "+name, false)
if err != nil {
return err
}
if view {
err = ShowScript(script, name, style)
if err != nil {
return err
}
cont, err := YesNoPrompt("Would you still like to continue?", false)
if err != nil {
return err
}
if !cont {
log.Fatal("User chose not to continue after reading script").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(found map[string][]db.Package, verb string) []db.Package {
var outPkgs []db.Package
for _, pkgs := range found {
if len(pkgs) > 1 {
choices, err := PkgPrompt(pkgs, verb)
if err != nil {
log.Fatal("Error prompting for choice of package").Send()
}
outPkgs = append(outPkgs, choices...)
} else if len(pkgs) == 1 {
outPkgs = append(outPkgs, pkgs[0])
}
}
return outPkgs
}
// PkgPrompt asks the user to choose between multiple packages.
// The user may choose multiple packages.
func PkgPrompt(options []db.Package, verb string) ([]db.Package, error) {
names := make([]string, len(options))
for i, option := range options {
names[i] = option.Repository + "/" + option.Name + " " + option.Version
}
prompt := &survey.MultiSelect{
Options: names,
Message: "Choose which package(s) to " + verb,
}
var choices []int
err := survey.AskOne(prompt, &choices)
if err != nil {
return nil, err
}
out := make([]db.Package, len(choices))
for i, choiceIndex := range choices {
out[i] = options[choiceIndex]
}
return out, nil
}

36
internal/config/config.go Normal file
View File

@@ -0,0 +1,36 @@
package config
import (
"os"
"github.com/pelletier/go-toml/v2"
"go.arsenm.dev/lure/internal/types"
)
var defaultConfig = types.Config{
RootCmd: "sudo",
PagerStyle: "native",
IgnorePkgUpdates: []string{},
Repos: []types.Repo{
{
Name: "default",
URL: "https://github.com/Arsen6331/lure-repo.git",
},
},
}
// Decode decodes the config file into the given
// pointer
func Decode(cfg *types.Config) error {
cfgFl, err := os.Open(ConfigPath)
if err != nil {
return err
}
defer cfgFl.Close()
// Write defaults to pointer in case some values are not set in the config
*cfg = defaultConfig
// Set repos to nil so as to avoid a duplicate default
cfg.Repos = nil
return toml.NewDecoder(cfgFl).Decode(cfg)
}

76
internal/config/dirs.go Normal file
View File

@@ -0,0 +1,76 @@
package config
import (
"os"
"path/filepath"
"github.com/pelletier/go-toml/v2"
"go.arsenm.dev/logger/log"
)
var (
ConfigDir string
ConfigPath string
CacheDir string
RepoDir string
PkgsDir string
DBPath string
)
// DBPresent is true if the database
// was present when LURE was started
var DBPresent bool
func init() {
cfgDir, err := os.UserConfigDir()
if err != nil {
log.Fatal("Unable to detect user config directory").Err(err).Send()
}
ConfigDir = filepath.Join(cfgDir, "lure")
err = os.MkdirAll(ConfigDir, 0o755)
if err != nil {
log.Fatal("Unable to create LURE config directory").Err(err).Send()
}
ConfigPath = filepath.Join(ConfigDir, "lure.toml")
if _, err := os.Stat(ConfigPath); err != nil {
cfgFl, err := os.Create(ConfigPath)
if err != nil {
log.Fatal("Unable to create LURE config file").Err(err).Send()
}
err = toml.NewEncoder(cfgFl).Encode(&defaultConfig)
if err != nil {
log.Fatal("Error encoding default configuration").Err(err).Send()
}
cfgFl.Close()
}
cacheDir, err := os.UserCacheDir()
if err != nil {
log.Fatal("Unable to detect cache directory").Err(err).Send()
}
CacheDir = filepath.Join(cacheDir, "lure")
RepoDir = filepath.Join(CacheDir, "repo")
PkgsDir = filepath.Join(CacheDir, "pkgs")
err = os.MkdirAll(RepoDir, 0o755)
if err != nil {
log.Fatal("Unable to create repo cache directory").Err(err).Send()
}
err = os.MkdirAll(PkgsDir, 0o755)
if err != nil {
log.Fatal("Unable to create package cache directory").Err(err).Send()
}
DBPath = filepath.Join(CacheDir, "db")
_, err = os.ReadDir(DBPath)
DBPresent = err == nil
}

View File

@@ -0,0 +1,6 @@
package config
import _ "embed"
//go:embed version.txt
var Version string

View File

@@ -1,7 +1,26 @@
/*
* LURE - Linux User REpository
* Copyright (C) 2022 Arsen Musayelyan
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cpu
import (
"os"
"runtime"
"strings"
"golang.org/x/sys/cpu"
@@ -24,3 +43,12 @@ func ARMVariant() string {
return "arm5"
}
}
// Arch returns the canonical CPU architecture of the system
func Arch() string {
arch := runtime.GOARCH
if arch == "arm" {
arch = ARMVariant()
}
return arch
}

197
internal/db/db.go Normal file
View File

@@ -0,0 +1,197 @@
package db
import (
"database/sql"
"database/sql/driver"
"encoding/json"
"errors"
"fmt"
"github.com/jmoiron/sqlx"
"golang.org/x/exp/slices"
"modernc.org/sqlite"
)
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 string `sh:"desc" db:"description"`
Homepage string `sh:"homepage" db:"homepage"`
Maintainer string `sh:"maintainer" 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"`
Repository string `db:"repository"`
}
// Init initializes the database
func Init(db *sqlx.DB) error {
*db = *db.Unsafe()
_, err := db.Exec(`
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,
homepage TEXT,
maintainer TEXT,
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')),
UNIQUE(name, repository)
);
`)
return err
}
// InsertPackage adds a package to the database
func InsertPackage(db *sqlx.DB, pkg Package) error {
_, err := db.NamedExec(`
INSERT OR REPLACE INTO pkgs (
name,
repository,
version,
release,
epoch,
description,
homepage,
maintainer,
architectures,
licenses,
provides,
conflicts,
replaces,
depends,
builddepends
) VALUES (
:name,
:repository,
:version,
:release,
:epoch,
:description,
:homepage,
:maintainer,
:architectures,
:licenses,
:provides,
:conflicts,
:replaces,
:depends,
:builddepends
);
`, pkg)
return err
}
// GetPkgs returns a result containing packages that match the where conditions
func GetPkgs(db *sqlx.DB, where string, args ...any) (*sqlx.Rows, error) {
stream, err := db.Queryx("SELECT * FROM pkgs WHERE "+where, args...)
if err != nil {
return nil, err
}
return stream, nil
}
// GetPkg returns a single package that match the where conditions
func GetPkg(db *sqlx.DB, where string, args ...any) (*Package, error) {
out := &Package{}
err := db.Get(out, "SELECT * FROM pkgs WHERE "+where+" LIMIT 1", args...)
return out, err
}
// DeletePkgs deletes all packages matching the where conditions
func DeletePkgs(db *sqlx.DB, where string, args ...any) error {
_, err := db.Exec("DELETE FROM pkgs WHERE "+where, args...)
return err
}
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
}
type JSON[T any] struct {
Val T
}
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)
}

234
internal/db/db_test.go Normal file
View File

@@ -0,0 +1,234 @@
package db_test
import (
"reflect"
"strings"
"testing"
"github.com/jmoiron/sqlx"
"go.arsenm.dev/lure/internal/db"
)
var testPkg = db.Package{
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: 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 getDB(t *testing.T) (*sqlx.DB, error) {
t.Helper()
gdb, err := sqlx.Open("sqlite", ":memory:")
if err != nil {
return nil, err
}
err = db.Init(gdb)
return gdb, err
}
func TestInit(t *testing.T) {
gdb, err := sqlx.Open("sqlite", ":memory:")
if err != nil {
t.Fatalf("Expected no error, got %s", err)
}
defer gdb.Close()
err = db.Init(gdb)
if err != nil {
t.Fatalf("Expected no error, got %s", err)
}
_, err = gdb.Exec("SELECT * FROM pkgs")
if err != nil {
t.Fatalf("Expected no error, got %s", err)
}
}
func TestInsertPackage(t *testing.T) {
gdb, err := getDB(t)
if err != nil {
t.Fatalf("Expected no error, got %s", err)
}
defer gdb.Close()
err = db.InsertPackage(gdb, testPkg)
if err != nil {
t.Fatalf("Expected no error, got %s", err)
}
dbPkg := db.Package{}
err = sqlx.Get(gdb, &dbPkg, "SELECT * FROM pkgs WHERE name = 'test' AND repository = 'default'")
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) {
gdb, err := getDB(t)
if err != nil {
t.Fatalf("Expected no error, got %s", err)
}
defer gdb.Close()
x1 := testPkg
x1.Name = "x1"
x2 := testPkg
x2.Name = "x2"
err = db.InsertPackage(gdb, x1)
if err != nil {
t.Errorf("Expected no error, got %s", err)
}
err = db.InsertPackage(gdb, x2)
if err != nil {
t.Errorf("Expected no error, got %s", err)
}
result, err := db.GetPkgs(gdb, "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) {
gdb, err := getDB(t)
if err != nil {
t.Fatalf("Expected no error, got %s", err)
}
defer gdb.Close()
x1 := testPkg
x1.Name = "x1"
x2 := testPkg
x2.Name = "x2"
err = db.InsertPackage(gdb, x1)
if err != nil {
t.Errorf("Expected no error, got %s", err)
}
err = db.InsertPackage(gdb, x2)
if err != nil {
t.Errorf("Expected no error, got %s", err)
}
pkg, err := db.GetPkg(gdb, "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) {
gdb, err := getDB(t)
if err != nil {
t.Fatalf("Expected no error, got %s", err)
}
defer gdb.Close()
x1 := testPkg
x1.Name = "x1"
x2 := testPkg
x2.Name = "x2"
err = db.InsertPackage(gdb, x1)
if err != nil {
t.Errorf("Expected no error, got %s", err)
}
err = db.InsertPackage(gdb, x2)
if err != nil {
t.Errorf("Expected no error, got %s", err)
}
err = db.DeletePkgs(gdb, "name = 'x1'")
if err != nil {
t.Errorf("Expected no error, got %s", err)
}
var dbPkg db.Package
err = gdb.Get(&dbPkg, "SELECT * FROM pkgs WHERE name LIKE 'x%' ORDER BY name LIMIT 1;")
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) {
gdb, err := getDB(t)
if err != nil {
t.Fatalf("Expected no error, got %s", err)
}
defer gdb.Close()
x1 := testPkg
x1.Name = "x1"
x2 := testPkg
x2.Name = "x2"
x2.Provides.Val = append(x2.Provides.Val, "x")
err = db.InsertPackage(gdb, x1)
if err != nil {
t.Errorf("Expected no error, got %s", err)
}
err = db.InsertPackage(gdb, x2)
if err != nil {
t.Errorf("Expected no error, got %s", err)
}
var dbPkg db.Package
err = gdb.Get(&dbPkg, "SELECT * FROM pkgs WHERE json_array_contains(provides, 'x');")
if err != nil {
t.Fatalf("Expected no error, got %s", err)
}
if dbPkg.Name != "x2" {
t.Errorf("Expected x2 package, got %s", dbPkg.Name)
}
}

View File

@@ -0,0 +1,99 @@
package overrides
import (
"runtime"
"strings"
"go.arsenm.dev/lure/distro"
"go.arsenm.dev/lure/internal/cpu"
)
type Opts struct {
Name string
Overrides bool
LikeDistros bool
}
var DefaultOpts = &Opts{
Overrides: true,
LikeDistros: true,
}
// Resolve generates a slice of possible override names in the order that they should be checked
func Resolve(info *distro.OSRelease, opts *Opts) []string {
if opts == nil {
opts = DefaultOpts
}
if !opts.Overrides {
return []string{opts.Name}
}
architectures := []string{runtime.GOARCH}
if runtime.GOARCH == "arm" {
// More specific goes first
architectures[0] = cpu.ARMVariant()
architectures = append(architectures, "arm")
}
distros := []string{info.ID}
if opts.LikeDistros {
distros = append(distros, info.Like...)
}
var out []string
for _, arch := range architectures {
for _, distro := range distros {
if opts.Name == "" {
out = append(
out,
arch+"_"+distro,
distro,
)
} else {
out = append(
out,
opts.Name+"_"+arch+"_"+distro,
opts.Name+"_"+distro,
)
}
}
if opts.Name == "" {
out = append(out, arch)
} else {
out = append(out, opts.Name+"_"+arch)
}
}
out = append(out, opts.Name)
for index, item := range out {
out[index] = strings.ReplaceAll(item, "-", "_")
}
return out
}
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
}

View File

@@ -0,0 +1,88 @@
package overrides_test
import (
"reflect"
"testing"
"go.arsenm.dev/lure/distro"
"go.arsenm.dev/lure/internal/overrides"
)
var info = &distro.OSRelease{
ID: "centos",
Like: []string{"rhel", "fedora"},
}
func TestResolve(t *testing.T) {
names := overrides.Resolve(info, nil)
expected := []string{
"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 := overrides.Resolve(info, &overrides.Opts{
Name: "deps",
Overrides: true,
LikeDistros: true,
})
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 TestResolveNoLikeDistros(t *testing.T) {
names := overrides.Resolve(info, &overrides.Opts{
Overrides: true,
LikeDistros: false,
})
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 := overrides.Resolve(info, &overrides.Opts{
Name: "deps",
Overrides: false,
LikeDistros: false,
})
expected := []string{"deps"}
if !reflect.DeepEqual(names, expected) {
t.Errorf("expected %v, got %v", expected, names)
}
}

View File

@@ -0,0 +1,19 @@
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
}

124
internal/pager/pager.go Normal file
View File

@@ -0,0 +1,124 @@
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() {
b := lipgloss.RoundedBorder()
b.Right = "\u251C"
titleStyle = lipgloss.NewStyle().BorderStyle(b).Padding(0, 1)
b = lipgloss.RoundedBorder()
b.Left = "\u2524"
infoStyle = titleStyle.Copy().BorderStyle(b)
}
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(),
)
_, err := prog.Run()
return err
}
type pagerModel struct {
name string
content string
ready bool
viewport viewport.Model
}
func (pm pagerModel) Init() tea.Cmd {
return tea.ClearScreen
}
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
}

64
internal/repos/find.go Normal file
View File

@@ -0,0 +1,64 @@
package repos
import (
"github.com/jmoiron/sqlx"
"go.arsenm.dev/lure/internal/db"
)
// FindPkgs looks for packages matching the inputs inside the database.
// It returns a map that maps the package name input to the packages found for it.
// It also returns a slice that contains the names of all packages that were not found.
func FindPkgs(gdb *sqlx.DB, pkgs []string) (map[string][]db.Package, []string, error) {
found := map[string][]db.Package{}
notFound := []string(nil)
for _, pkgName := range pkgs {
if pkgName == "" {
continue
}
result, err := db.GetPkgs(gdb, "name LIKE ?", 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(gdb, "json_array_contains(provides, ?)", 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
}

135
internal/repos/find_test.go Normal file
View File

@@ -0,0 +1,135 @@
package repos_test
import (
"context"
"reflect"
"strings"
"testing"
"github.com/jmoiron/sqlx"
"go.arsenm.dev/lure/internal/db"
"go.arsenm.dev/lure/internal/repos"
"go.arsenm.dev/lure/internal/types"
)
func TestFindPkgs(t *testing.T) {
gdb, err := sqlx.Open("sqlite", ":memory:")
if err != nil {
t.Fatalf("Expected no error, got %s", err)
}
defer gdb.Close()
err = db.Init(gdb)
if err != nil {
t.Fatalf("Expected no error, got %s", err)
}
setCfgDirs(t)
defer removeCacheDir(t)
ctx := context.Background()
err = repos.Pull(ctx, gdb, []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(gdb, []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) {
gdb, err := sqlx.Open("sqlite", ":memory:")
if err != nil {
t.Fatalf("Expected no error, got %s", err)
}
defer gdb.Close()
err = db.Init(gdb)
if err != nil {
t.Fatalf("Expected no error, got %s", err)
}
setCfgDirs(t)
defer removeCacheDir(t)
err = db.InsertPackage(gdb, db.Package{
Name: "test1",
Repository: "default",
Version: "0.0.1",
Release: 1,
Description: "Test package 1",
Provides: db.NewJSON([]string{""}),
})
if err != nil {
t.Fatalf("Expected no error, got %s", err)
}
err = db.InsertPackage(gdb, db.Package{
Name: "test2",
Repository: "default",
Version: "0.0.1",
Release: 1,
Description: "Test package 2",
Provides: db.NewJSON([]string{"test"}),
})
if err != nil {
t.Fatalf("Expected no error, got %s", err)
}
found, notFound, err := repos.FindPkgs(gdb, []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)
}
}

397
internal/repos/pull.go Normal file
View File

@@ -0,0 +1,397 @@
package repos
import (
"context"
"errors"
"io"
"net/url"
"os"
"path/filepath"
"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/jmoiron/sqlx"
"github.com/pelletier/go-toml/v2"
"go.arsenm.dev/logger/log"
"go.arsenm.dev/lure/distro"
"go.arsenm.dev/lure/download"
"go.arsenm.dev/lure/internal/config"
"go.arsenm.dev/lure/internal/db"
"go.arsenm.dev/lure/internal/shutils"
"go.arsenm.dev/lure/internal/shutils/decoder"
"go.arsenm.dev/lure/internal/types"
"go.arsenm.dev/lure/vercmp"
"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.
func Pull(ctx context.Context, gdb *sqlx.DB, repos []types.Repo) error {
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.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) || !config.DBPresent {
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 config.DBPresent {
err = processRepoChanges(ctx, repo, r, w, old, new, gdb)
if err != nil {
return err
}
} else {
err = processRepoFull(ctx, repo, repoDir, gdb)
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
}
if !strings.HasPrefix(repoURL.Scheme, "git+") {
repoURL.Scheme = "git+" + repoURL.Scheme
}
err = download.Get(ctx, download.GetOptions{
SourceURL: repoURL.String(),
Destination: repoDir,
})
if err != nil {
return err
}
err = processRepoFull(ctx, repo, repoDir, gdb)
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()
currentVer, _, _ := strings.Cut(config.Version, "-")
if vercmp.Compare(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
}
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, gdb *sqlx.DB) 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(shutils.NopExec),
interp.ReadDirHandler(shutils.RestrictedReadDir(repoDir)),
interp.StatHandler(shutils.RestrictedStat(repoDir)),
interp.OpenHandler(shutils.RestrictedOpen(repoDir)),
interp.StdIO(shutils.NopRWC{}, shutils.NopRWC{}, shutils.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(gdb, "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{
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(gdb, 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, gdb *sqlx.DB) 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(shutils.NopExec),
interp.ReadDirHandler(shutils.RestrictedReadDir(repoDir)),
interp.StatHandler(shutils.RestrictedStat(repoDir)),
interp.OpenHandler(shutils.RestrictedOpen(repoDir)),
interp.StdIO(shutils.NopRWC{}, shutils.NopRWC{}, shutils.NopRWC{}),
)
if err != nil {
return err
}
scriptFl, err := os.Open(match)
if err != nil {
return err
}
pkg := db.Package{
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(gdb, 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)
}
func resolveOverrides(runner *interp.Runner, pkg *db.Package) {
for name, val := range runner.Vars {
if strings.HasPrefix(name, "deps") {
override := strings.TrimPrefix(name, "deps")
override = strings.TrimPrefix(override, "_")
pkg.Depends.Val[override] = val.List
} else if strings.HasPrefix(name, "build_deps") {
override := strings.TrimPrefix(name, "build_deps")
override = strings.TrimPrefix(override, "_")
pkg.BuildDepends.Val[override] = val.List
} else {
continue
}
}
}

View File

@@ -0,0 +1,95 @@
package repos_test
import (
"context"
"os"
"path/filepath"
"testing"
"github.com/jmoiron/sqlx"
"go.arsenm.dev/lure/internal/config"
"go.arsenm.dev/lure/internal/db"
"go.arsenm.dev/lure/internal/repos"
"go.arsenm.dev/lure/internal/types"
)
func setCfgDirs(t *testing.T) {
t.Helper()
var err error
config.CacheDir, err = os.MkdirTemp("/tmp", "lure-pull-test.*")
if err != nil {
t.Fatalf("Expected no error, got %s", err)
}
config.RepoDir = filepath.Join(config.CacheDir, "repo")
config.PkgsDir = filepath.Join(config.CacheDir, "pkgs")
err = os.MkdirAll(config.RepoDir, 0o755)
if err != nil {
t.Fatalf("Expected no error, got %s", err)
}
err = os.MkdirAll(config.PkgsDir, 0o755)
if err != nil {
t.Fatalf("Expected no error, got %s", err)
}
config.DBPath = filepath.Join(config.CacheDir, "db")
}
func removeCacheDir(t *testing.T) {
t.Helper()
err := os.RemoveAll(config.CacheDir)
if err != nil {
t.Fatalf("Expected no error, got %s", err)
}
}
func TestPull(t *testing.T) {
gdb, err := sqlx.Open("sqlite", ":memory:")
if err != nil {
t.Fatalf("Expected no error, got %s", err)
}
defer gdb.Close()
err = db.Init(gdb)
if err != nil {
t.Fatalf("Expected no error, got %s", err)
}
setCfgDirs(t)
defer removeCacheDir(t)
ctx := context.Background()
err = repos.Pull(ctx, gdb, []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(gdb, "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)
}
}

View File

@@ -21,21 +21,19 @@ package decoder
import (
"context"
"errors"
"fmt"
"reflect"
"runtime"
"strings"
"github.com/mitchellh/mapstructure"
"go.arsenm.dev/lure/distro"
"go.arsenm.dev/lure/internal/cpu"
"go.arsenm.dev/lure/internal/overrides"
"golang.org/x/exp/slices"
"mvdan.cc/sh/v3/expand"
"mvdan.cc/sh/v3/interp"
"mvdan.cc/sh/v3/syntax"
)
var ErrInvalidType = errors.New("val must be a pointer to a struct")
var ErrNotPointerToStruct = errors.New("val must be a pointer to a struct")
type VarNotFoundError struct {
name string
@@ -45,6 +43,16 @@ func (nfe VarNotFoundError) Error() string {
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
type Decoder struct {
info *distro.OSRelease
@@ -70,8 +78,20 @@ func (d *Decoder) DecodeVar(name string, val any) error {
dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
WeaklyTypedInput: true,
Result: val,
TagName: "sh",
DecodeHook: mapstructure.DecodeHookFuncValue(func(from, to reflect.Value) (interface{}, error) {
if strings.Contains(to.Type().String(), "db.JSON") {
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 {
return err
@@ -92,11 +112,11 @@ func (d *Decoder) DecodeVar(name string, val any) error {
func (d *Decoder) DecodeVars(val any) error {
valKind := reflect.TypeOf(val).Kind()
if valKind != reflect.Pointer {
return ErrInvalidType
return ErrNotPointerToStruct
} else {
elemKind := reflect.TypeOf(val).Elem().Kind()
if elemKind != reflect.Struct {
return ErrInvalidType
return ErrNotPointerToStruct
}
}
@@ -142,7 +162,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,16 +172,17 @@ 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
}
func (d *Decoder) getFunc(name string) *syntax.Stmt {
names := d.genPossibleNames(name)
names := overrides.Resolve(d.info, overrides.DefaultOpts.WithName(name))
for _, fnName := range names {
fn, ok := d.runner.Funcs[fnName]
if ok {
@@ -174,7 +195,7 @@ func (d *Decoder) getFunc(name string) *syntax.Stmt {
// getVar gets a variable based on its name, taking into account
// override variables and nameref variables.
func (d *Decoder) getVar(name string) *expand.Variable {
names := d.genPossibleNames(name)
names := overrides.Resolve(d.info, overrides.DefaultOpts.WithName(name))
for _, varName := range names {
val, ok := d.runner.Vars[varName]
if ok {
@@ -192,43 +213,3 @@ func (d *Decoder) getVar(name string) *expand.Variable {
}
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
}

View File

@@ -0,0 +1,206 @@
package decoder_test
import (
"bytes"
"context"
"errors"
"os"
"reflect"
"strings"
"testing"
"go.arsenm.dev/lure/distro"
"go.arsenm.dev/lure/internal/shutils/decoder"
"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, &notFoundErr) {
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())
}
}

View File

@@ -20,24 +20,44 @@ package shutils
import (
"context"
"fmt"
"time"
"mvdan.cc/sh/v3/interp"
)
type ExecFuncs map[string]func(interp.HandlerContext, []string) uint8
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)
}
func InsufficientArgsError(cmd string, exp, got int) error {
argsWord := "arguments"
if exp == 1 {
argsWord = "argument"
}
defExec := interp.DefaultExecHandler(2 * time.Second)
return defExec(ctx, args)
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
// ExecHandler returns a new ExecHandlerFunc that falls back to fallback
// if the command cannot be found in the map. If fallback is nil, the default
// 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 {
hctx := interp.HandlerCtx(ctx)
if len(args) > 1 {
return fn(hctx, args[0], args[1:])
} else {
return fn(hctx, args[0], nil)
}
}
if fallback == nil {
fallback = interp.DefaultExecHandler(2 * time.Second)
}
return fallback(ctx, args)
}
}

View File

@@ -0,0 +1,106 @@
package shutils_test
import (
"context"
"strings"
"testing"
"go.arsenm.dev/lure/distro"
"go.arsenm.dev/lure/internal/shutils"
"go.arsenm.dev/lure/internal/shutils/decoder"
"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.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() {
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")
}
}

View File

@@ -0,0 +1,40 @@
package shutils_test
import (
"bytes"
"context"
"os"
"strings"
"testing"
"go.arsenm.dev/lure/internal/shutils"
"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(shutils.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())
}
}

View File

@@ -0,0 +1,76 @@
/*
* 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 shutils
import (
"context"
"io"
"os"
"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) ([]os.FileInfo, error) {
for _, allowedPrefix := range allowedPrefixes {
if strings.HasPrefix(s, allowedPrefix) {
return interp.DefaultReadDirHandler()(ctx, s)
}
}
return nil, os.ErrNotExist
}
}
func RestrictedStat(allowedPrefixes ...string) interp.StatHandlerFunc {
return func(ctx context.Context, s string, b bool) (os.FileInfo, error) {
for _, allowedPrefix := range allowedPrefixes {
if strings.HasPrefix(s, allowedPrefix) {
return interp.DefaultStatHandler()(ctx, s, b)
}
}
return nil, os.ErrNotExist
}
}
func RestrictedOpen(allowedPrefixes ...string) interp.OpenHandlerFunc {
return func(ctx context.Context, s string, i int, fm os.FileMode) (io.ReadWriteCloser, error) {
for _, allowedPrefix := range allowedPrefixes {
if strings.HasPrefix(s, 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
}
}

15
internal/types/config.go Normal file
View File

@@ -0,0 +1,15 @@
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"`
}
// Repo represents a LURE repo within a configuration file
type Repo struct {
Name string `toml:"name"`
URL string `toml:"url"`
}

8
internal/types/repo.go Normal file
View File

@@ -0,0 +1,8 @@
package types
// RepoConfig represents a LURE repo's lure-repo.toml file.
type RepoConfig struct {
Repo struct {
MinVersion string `toml:"minVersion"`
}
}

79
list.go
View File

@@ -1,30 +1,87 @@
/*
* LURE - Linux User REpository
* Copyright (C) 2022 Arsen Musayelyan
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package main
import (
"fmt"
"github.com/urfave/cli/v2"
"go.arsenm.dev/lure/distro"
"go.arsenm.dev/logger/log"
"go.arsenm.dev/lure/internal/db"
"go.arsenm.dev/lure/internal/repos"
"go.arsenm.dev/lure/manager"
)
func listCmd(c *cli.Context) error {
info, err := distro.ParseOSRelease(c.Context)
err := repos.Pull(c.Context, gdb, cfg.Repos)
if err != nil {
log.Fatal("Error parsing os-release").Err(err).Send()
log.Fatal("Error pulling repositories").Err(err).Send()
}
pkgs, err := findPkg("*")
if err != nil {
log.Fatal("Error finding packages").Err(err).Send()
where := "true"
args := []any(nil)
if c.NArg() > 0 {
where = "name LIKE ? OR json_array_contains(provides, ?)"
args = []any{c.Args().First(), c.Args().First()}
}
for _, script := range pkgs {
vars, err := getBuildVars(c.Context, script, info)
if err != nil {
log.Fatal("Error getting build variables").Err(err).Send()
result, err := db.GetPkgs(gdb, where, args...)
if err != nil {
log.Fatal("Error getting packages").Err(err).Send()
}
defer result.Close()
var installed map[string]string
if c.Bool("installed") {
mgr := manager.Detect()
if mgr == nil {
log.Fatal("Unable to detect supported package manager on system").Send()
}
fmt.Println(vars.Name, vars.Version)
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
}
version := pkg.Version
if c.Bool("installed") {
instVersion, ok := installed[pkg.Name]
if !ok {
return nil
} 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

88
main.go
View File

@@ -20,16 +20,26 @@ package main
import (
"context"
"fmt"
"os"
"os/signal"
"strings"
"syscall"
"time"
"github.com/urfave/cli/v2"
"go.arsenm.dev/logger"
"go.arsenm.dev/logger/log"
"go.arsenm.dev/lure/internal/config"
"go.arsenm.dev/lure/internal/db"
"go.arsenm.dev/lure/manager"
)
var log = logger.NewPretty(os.Stderr)
//go:generate scripts/gen-version.sh
func init() {
log.Logger = logger.NewCLI(os.Stderr)
}
func main() {
ctx := context.Background()
@@ -39,18 +49,27 @@ func main() {
<-ctx.Done()
// Exit the program after a maximum of 200ms
time.Sleep(200 * time.Millisecond)
gdb.Close()
os.Exit(0)
}()
app := &cli.App{
Name: "lure",
Usage: "Linux User REpository",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "pm-args",
Aliases: []string{"P"},
Usage: "Arguments to be passed on to the package manager",
},
},
Commands: []*cli.Command{
{
Name: "install",
Usage: "Install a new package",
Aliases: []string{"in"},
Action: installCmd,
Name: "install",
Usage: "Install a new package",
Aliases: []string{"in"},
Action: installCmd,
BashComplete: completionInstall,
},
{
Name: "remove",
@@ -65,11 +84,24 @@ func main() {
Action: upgradeCmd,
},
{
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "all",
Aliases: []string{"a"},
Usage: "Show all information, not just for the current distro",
},
},
Name: "info",
Usage: "Print information about a package",
Action: infoCmd,
},
{
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "installed",
Aliases: []string{"I"},
},
},
Name: "list",
Usage: "List LURE repo packages",
Aliases: []string{"ls"},
@@ -128,7 +160,30 @@ func main() {
Aliases: []string{"ref"},
Action: refreshCmd,
},
{
Name: "fix",
Usage: "Attempt to fix problems with LURE",
Action: fixCmd,
},
{
Name: "version",
Usage: "Display the current LURE version and exit",
Action: displayVersion,
},
},
Before: func(c *cli.Context) error {
args := strings.Split(c.String("pm-args"), " ")
if len(args) == 1 && args[0] == "" {
args = nil
}
manager.Args = append(manager.Args, args...)
return nil
},
After: func(ctx *cli.Context) error {
return gdb.Close()
},
EnableBashCompletion: true,
}
err := app.RunContext(ctx, os.Args)
@@ -136,3 +191,26 @@ func main() {
log.Error("Error while running app").Err(err).Send()
}
}
func displayVersion(c *cli.Context) error {
print(config.Version)
return nil
}
func completionInstall(c *cli.Context) {
result, err := db.GetPkgs(gdb, "true")
if err != nil {
log.Fatal("Error getting packages").Err(err).Send()
}
defer result.Close()
for result.Next() {
var pkg db.Package
err = result.StructScan(&pkg)
if err != nil {
log.Fatal("Error iterating over packages").Err(err).Send()
}
fmt.Println(pkg.Name)
}
}

View File

@@ -159,6 +159,7 @@ func (a *APK) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd {
var cmd *exec.Cmd
if opts.AsRoot {
cmd = exec.Command(getRootCmd(a.rootCmd), mgrCmd)
cmd.Args = append(cmd.Args, opts.Args...)
cmd.Args = append(cmd.Args, args...)
} else {
cmd = exec.Command(mgrCmd, args...)

View File

@@ -145,6 +145,7 @@ func (a *APT) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd {
var cmd *exec.Cmd
if opts.AsRoot {
cmd = exec.Command(getRootCmd(a.rootCmd), mgrCmd)
cmd.Args = append(cmd.Args, opts.Args...)
cmd.Args = append(cmd.Args, args...)
} else {
cmd = exec.Command(mgrCmd, args...)

View File

@@ -60,7 +60,7 @@ func (d *DNF) Sync(opts *Opts) error {
func (d *DNF) Install(opts *Opts, pkgs ...string) error {
opts = ensureOpts(opts)
cmd := d.getCmd(opts, "dnf", "install")
cmd := d.getCmd(opts, "dnf", "install", "--allowerasing")
cmd.Args = append(cmd.Args, pkgs...)
setCmdEnv(cmd)
err := cmd.Run()
@@ -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
}
@@ -152,6 +153,7 @@ func (d *DNF) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd {
var cmd *exec.Cmd
if opts.AsRoot {
cmd = exec.Command(getRootCmd(d.rootCmd), mgrCmd)
cmd.Args = append(cmd.Args, opts.Args...)
cmd.Args = append(cmd.Args, args...)
} else {
cmd = exec.Command(mgrCmd, args...)

View File

@@ -23,9 +23,12 @@ import (
"os/exec"
)
var Args []string
type Opts struct {
AsRoot bool
NoConfirm bool
Args []string
}
var DefaultOpts = &Opts{
@@ -114,7 +117,8 @@ func setCmdEnv(cmd *exec.Cmd) {
func ensureOpts(opts *Opts) *Opts {
if opts == nil {
return DefaultOpts
opts = DefaultOpts
}
opts.Args = append(opts.Args, Args...)
return opts
}

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()
@@ -152,6 +152,7 @@ func (p *Pacman) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd {
var cmd *exec.Cmd
if opts.AsRoot {
cmd = exec.Command(getRootCmd(p.rootCmd), mgrCmd)
cmd.Args = append(cmd.Args, opts.Args...)
cmd.Args = append(cmd.Args, args...)
} else {
cmd = exec.Command(mgrCmd, args...)

View File

@@ -60,7 +60,7 @@ func (y *YUM) Sync(opts *Opts) error {
func (y *YUM) Install(opts *Opts, pkgs ...string) error {
opts = ensureOpts(opts)
cmd := y.getCmd(opts, "yum", "install")
cmd := y.getCmd(opts, "yum", "install", "--allowerasing")
cmd.Args = append(cmd.Args, pkgs...)
setCmdEnv(cmd)
err := cmd.Run()
@@ -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
}
@@ -152,6 +153,7 @@ func (y *YUM) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd {
var cmd *exec.Cmd
if opts.AsRoot {
cmd = exec.Command(getRootCmd(y.rootCmd), mgrCmd)
cmd.Args = append(cmd.Args, opts.Args...)
cmd.Args = append(cmd.Args, args...)
} else {
cmd = exec.Command(mgrCmd, args...)

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
}
@@ -152,6 +153,7 @@ func (z *Zypper) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd {
var cmd *exec.Cmd
if opts.AsRoot {
cmd = exec.Command(getRootCmd(z.rootCmd), mgrCmd)
cmd.Args = append(cmd.Args, opts.Args...)
cmd.Args = append(cmd.Args, args...)
} else {
cmd = exec.Command(mgrCmd, args...)

186
repo.go
View File

@@ -19,53 +19,49 @@
package main
import (
"context"
"net/url"
"os"
"path/filepath"
"strings"
"github.com/AlecAivazis/survey/v2"
"github.com/go-git/go-git/v5"
"github.com/pelletier/go-toml/v2"
"github.com/urfave/cli/v2"
"go.arsenm.dev/lure/download"
"go.arsenm.dev/logger/log"
"go.arsenm.dev/lure/internal/config"
"go.arsenm.dev/lure/internal/db"
"go.arsenm.dev/lure/internal/repos"
"go.arsenm.dev/lure/internal/types"
"golang.org/x/exp/slices"
)
type PkgNotFoundError struct {
pkgName string
}
func (p PkgNotFoundError) Error() string {
return "package '" + p.pkgName + "' could not be found in any repository"
}
func addrepoCmd(c *cli.Context) error {
name := c.String("name")
repoURL := c.String("url")
for _, repo := range config.Repos {
for _, repo := range cfg.Repos {
if repo.URL == repoURL {
log.Fatal("Repo already exists").Str("name", repo.Name).Send()
}
}
config.Repos = append(config.Repos, Repo{
cfg.Repos = append(cfg.Repos, types.Repo{
Name: name,
URL: repoURL,
})
cfgFl, err := os.Create(cfgPath)
cfgFl, err := os.Create(config.ConfigPath)
if err != nil {
log.Fatal("Error opening config file").Err(err).Send()
}
err = toml.NewEncoder(cfgFl).Encode(&config)
err = toml.NewEncoder(cfgFl).Encode(&cfg)
if err != nil {
log.Fatal("Error encoding config").Err(err).Send()
}
err = repos.Pull(c.Context, gdb, cfg.Repos)
if err != nil {
log.Fatal("Error pulling repos").Err(err).Send()
}
return nil
}
@@ -74,7 +70,7 @@ func removerepoCmd(c *cli.Context) error {
found := false
index := 0
for i, repo := range config.Repos {
for i, repo := range cfg.Repos {
if repo.Name == name {
index = i
found = true
@@ -84,167 +80,35 @@ func removerepoCmd(c *cli.Context) error {
log.Fatal("Repo does not exist").Str("name", name).Send()
}
config.Repos = slices.Delete(config.Repos, index, index+1)
cfg.Repos = slices.Delete(cfg.Repos, index, index+1)
cfgFl, err := os.Create(cfgPath)
cfgFl, err := os.Create(config.ConfigPath)
if err != nil {
log.Fatal("Error opening config file").Err(err).Send()
}
err = toml.NewEncoder(cfgFl).Encode(&config)
err = toml.NewEncoder(cfgFl).Encode(&cfg)
if err != nil {
log.Fatal("Error encoding config").Err(err).Send()
}
err = os.RemoveAll(filepath.Join(cacheDir, "repo", name))
err = os.RemoveAll(filepath.Join(config.RepoDir, name))
if err != nil {
log.Fatal("Error removing repo directory").Err(err).Send()
}
err = db.DeletePkgs(gdb, "repository = ?", name)
if err != nil {
log.Fatal("Error removing packages from database").Err(err).Send()
}
return nil
}
func refreshCmd(c *cli.Context) error {
err := pullRepos(c.Context)
err := repos.Pull(c.Context, gdb, cfg.Repos)
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
}
glob := filepath.Join(repoDir, pkg, "lure.sh")
matches, err := filepath.Glob(glob)
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 {
names[i] = filepath.Base(filepath.Dir(option))
}
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
}
}
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,
})
if err != nil {
return err
}
}
return nil
}

21
scripts/completion/bash Normal file
View File

@@ -0,0 +1,21 @@
#! /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

20
scripts/completion/zsh Normal file
View File

@@ -0,0 +1,20 @@
#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
View File

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

82
scripts/install.sh Normal file
View File

@@ -0,0 +1,82 @@
#!/bin/bash
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} ;;
*) $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.arsenm.dev/Arsen6331/lure/releases/latest' | grep -o '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.arsenm.dev/Arsen6331/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!"

View File

@@ -23,8 +23,14 @@ import (
"fmt"
"github.com/urfave/cli/v2"
"go.arsenm.dev/logger/log"
"go.arsenm.dev/lure/distro"
"go.arsenm.dev/lure/internal/db"
"go.arsenm.dev/lure/internal/repos"
"go.arsenm.dev/lure/manager"
"go.arsenm.dev/lure/vercmp"
"golang.org/x/exp/maps"
"golang.org/x/exp/slices"
)
func upgradeCmd(c *cli.Context) error {
@@ -38,13 +44,18 @@ func upgradeCmd(c *cli.Context) error {
log.Fatal("Unable to detect supported package manager on system").Send()
}
err = repos.Pull(c.Context, gdb, cfg.Repos)
if err != nil {
log.Fatal("Error pulling 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, nil, mgr)
} else {
log.Info("There is nothing to do.").Send()
}
@@ -52,41 +63,47 @@ func upgradeCmd(c *cli.Context) error {
return nil
}
func checkForUpdates(ctx context.Context, mgr manager.Manager, info *distro.OSRelease) ([]string, error) {
func checkForUpdates(ctx context.Context, mgr manager.Manager, info *distro.OSRelease) ([]db.Package, error) {
installed, err := mgr.ListInstalled(nil)
if err != nil {
return nil, err
}
var out []string
for name, version := range installed {
scripts, err := findPkg(name)
if err != nil {
pkgNames := maps.Keys(installed)
found, _, err := repos.FindPkgs(gdb, pkgNames)
if err != nil {
return nil, err
}
var out []db.Package
for pkgName, pkgs := range found {
if slices.Contains(cfg.IgnorePkgUpdates, pkgName) {
continue
}
// since we're not using a glob, we can assume a single item
script := scripts[0]
vars, err := getBuildVars(ctx, script, info)
if err != nil {
log.Fatal("Error getting build variables").Err(err).Send()
if len(pkgs) > 1 {
// Puts the element with the highest version first
slices.SortFunc(pkgs, func(a, b db.Package) bool {
return vercmp.Compare(a.Version, b.Version) == 1
})
}
repoVer := vars.Version
if vars.Release != 0 && vars.Epoch == 0 {
repoVer = fmt.Sprintf("%s-%d", vars.Version, vars.Release)
} else if vars.Release != 0 && vars.Epoch != 0 {
repoVer = fmt.Sprintf("%d:%s-%d", vars.Epoch, vars.Version, vars.Release)
// First element is the package we want to install
pkg := pkgs[0]
repoVer := pkg.Version
if pkg.Release != 0 && pkg.Epoch == 0 {
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(repoVer, version)
c := vercmp.Compare(repoVer, installed[pkgName])
if c == 0 || c == -1 {
continue
} else if c == 1 {
out = append(out, name)
out = append(out, pkg)
}
}
return out, nil
}

View File

@@ -16,20 +16,21 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package main
package vercmp
import (
_ "embed"
"strconv"
"strings"
"golang.org/x/exp/slices"
)
// vercmp compares two version strings.
// Compare 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 {
func Compare(v1, v2 string) int {
if v1 == v2 {
return 0
}

View File

@@ -16,7 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package main
package vercmp
import (
"testing"
@@ -67,13 +67,13 @@ func TestVerCmp(t *testing.T) {
for _, it := range table {
t.Run(it.v1+"/"+it.v2, func(t *testing.T) {
c := vercmp(it.v1, it.v2)
c := Compare(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)
c = -Compare(it.v2, it.v1)
if c != it.expected {
t.Errorf("Expected %d, got %d (opposite)", it.expected, c)
}